├── .gitignore ├── README.md ├── bin ├── build-cli.js ├── build.js ├── loader.js └── server.js ├── package-lock.json ├── package.json ├── src ├── components │ ├── css-module.js │ ├── esbuild-module.js │ └── markdown-module.js ├── example-setup.js ├── fonts │ ├── FiraMono-Regular.woff2 │ ├── Inter-Black.woff2 │ └── Inter-Light.woff2 ├── images │ └── logo.svg ├── index.js ├── markdown │ ├── components │ │ ├── accordion.md │ │ ├── badge.md │ │ ├── button.md │ │ ├── chart.md │ │ ├── checkbox.md │ │ ├── dialog.md │ │ ├── form.md │ │ ├── icon.md │ │ ├── input.md │ │ ├── loader.md │ │ ├── panel.md │ │ ├── popover.md │ │ ├── profile-image.md │ │ ├── progress-bar.md │ │ ├── range.md │ │ ├── relative-time.md │ │ ├── router.md │ │ ├── select.md │ │ ├── split.md │ │ ├── sprite.md │ │ ├── tabs.md │ │ ├── textarea.md │ │ ├── toaster-inline.md │ │ ├── toaster.md │ │ ├── toggle.md │ │ ├── tooltip.md │ │ └── windowed.md │ └── guides │ │ ├── composition.md │ │ ├── csp.md │ │ ├── events.md │ │ ├── gettingstarted.md │ │ ├── intro.md │ │ ├── methods.md │ │ ├── props.md │ │ ├── ssr.md │ │ ├── state.md │ │ └── styles.md ├── pages │ ├── examples.js │ ├── index.js │ └── mixins │ │ ├── head.js │ │ └── logo.js └── styles │ ├── examples.css │ ├── highlight.js.css │ ├── index.css │ └── theme.css └── website.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build/*.js 3 | build/*.html 4 | build/* 5 | build-output.txt 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![tonic](https://raw.githubusercontent.com/heapwolf/tonic/master/readme-tonic.png) 2 | 3 |

4 | Documentation 5 |
6 | https://tonicframework.dev 7 |

8 |
9 |
10 | -------------------------------------------------------------------------------- /bin/build-cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // @ts-check 3 | 4 | import minimist from 'minimist' 5 | import { build } from './build.js' 6 | 7 | function main() { 8 | console.log('build now') 9 | return build(minimist(process.argv.slice(2))) 10 | } 11 | 12 | main().then(() => { 13 | process.exit(0) 14 | }).catch(err => { 15 | console.error(err) 16 | process.exit(1) 17 | }) 18 | -------------------------------------------------------------------------------- /bin/build.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import fs from 'node:fs/promises' 4 | import path from 'node:path' 5 | import Tonic from 'tonic-ssr' 6 | 7 | const dirname = meta => path.dirname(new URL(meta.url).pathname) 8 | const componentsDir = path.join(dirname(import.meta), '../src/components') 9 | 10 | const load = async src => { 11 | const mod = await import(`${src}?t=${Date.now()}`) 12 | return Tonic.add(mod.default) 13 | } 14 | 15 | const compile = async (src, dest) => { 16 | const p = path.resolve(src) 17 | const t = new Date() 18 | 19 | t.setMinutes(t.getMinutes() - 1) 20 | if ((await fs.stat(src)).mtime > t) return Promise.resolve() 21 | 22 | const Page = await load(p) 23 | const page = new Page() 24 | 25 | try { await fs.mkdir(path.dirname(dest), { recursive: true }) } catch {} 26 | const r = fs.writeFile(dest, await page.preRender()) 27 | return r 28 | } 29 | 30 | export async function build (argv) { 31 | console.log('export build') 32 | const base = path.join(dirname(import.meta), '..') 33 | 34 | const dest = typeof argv.out === 'string' 35 | ? argv.out : path.join(base, 'build') 36 | 37 | // 38 | // clean and recreate the build directory if it exists 39 | try { 40 | await fs.rm(dest, { force: true, recursive: true }) 41 | await fs.mkdir(dest) 42 | 43 | // 44 | // add symbolic links to the source fonts and images 45 | // 46 | for (const dir of ['fonts', 'images', 'styles']) { 47 | try { 48 | await fs.symlink( 49 | path.join(base, 'src', dir), 50 | path.join(dest, dir) 51 | ) 52 | } catch (err) { 53 | console.error('Could not create symlink', err) 54 | } 55 | } 56 | } catch {} 57 | 58 | console.log('call load()') 59 | 60 | // 61 | // decide which urls we want to build 62 | // 63 | await Promise.all([ 64 | load(path.join(componentsDir, 'css-module.js')), 65 | load(path.join(componentsDir, 'esbuild-module.js')), 66 | load(path.join(componentsDir, 'markdown-module.js')) 67 | ]) 68 | 69 | console.log('compile pages()') 70 | 71 | const pages = Promise.all([ 72 | compile('src/pages/index.js', `${dest}/index.html`), 73 | compile('src/pages/examples.js', `${dest}/examples.html`) 74 | ]) 75 | 76 | await pages 77 | } 78 | -------------------------------------------------------------------------------- /bin/loader.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // @ts-check 4 | 5 | // 6 | // Local servers aren't concurrent. So a cheap, fast and stable way to 7 | // achieve hot-reloading is to restart the server after each request cycle, 8 | // you end up with a lambda-like provider. 9 | // 10 | import { spawn } from 'child_process' 11 | 12 | let childProc 13 | let shutdown 14 | 15 | const start = () => { 16 | childProc = spawn('node', ['bin/server']) 17 | 18 | childProc.stdout.on('data', data => process.stdout.write(data.toString())) 19 | childProc.stderr.on('data', data => process.stderr.write(data.toString())) 20 | childProc.on('close', (code) => { 21 | if (code && code !== 0) { 22 | process.exit(code) 23 | } 24 | 25 | if (!shutdown) { 26 | start() 27 | } 28 | }) 29 | } 30 | 31 | start() 32 | 33 | process.on('SIGTERM', () => { 34 | shutdown = true 35 | 36 | if (childProc) { 37 | childProc.kill() 38 | } 39 | }) 40 | -------------------------------------------------------------------------------- /bin/server.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // @ts-check 3 | 4 | import http from 'node:http' 5 | import send from '@pre-bundled/send' 6 | import minimist from 'minimist' 7 | 8 | import { build } from './build.js' 9 | 10 | process.on('unhandledRejection', (err) => { 11 | process.nextTick(() => { 12 | throw err 13 | }) 14 | }) 15 | 16 | let die = null 17 | let port = 8081 18 | let url = 'http://dev.socketsupply.co' 19 | 20 | const opts = { root: new URL('../build', import.meta.url).pathname } 21 | 22 | const PENDING_REQUESTS = new Set() 23 | 24 | async function handler (req, res) { 25 | PENDING_REQUESTS.add(req) 26 | 27 | res.setMaxListeners(50) 28 | res.on('finish', function _onFinishDelete () { 29 | PENDING_REQUESTS.delete(req) 30 | }) 31 | 32 | const { pathname } = new URL(req.url, `${url}:${port}`) 33 | 34 | const onError = err => { 35 | console.log('Got an error', err) 36 | 37 | if (err.status === 404) { 38 | req.url = '/' 39 | handler(req, res) 40 | } else { 41 | res.statusCode = err.status || 500 42 | res.end('Internal Server Error') 43 | } 44 | } 45 | 46 | return send(req, pathname, opts) 47 | .once('error', onError) 48 | .on('end', () => { 49 | clearTimeout(die) 50 | die = setTimeout(teardown, 512) 51 | }) 52 | .pipe(res) 53 | } 54 | 55 | async function teardown () { 56 | if (PENDING_REQUESTS.size > 0) { 57 | clearTimeout(die) 58 | die = setTimeout(teardown, 512) 59 | return 60 | } 61 | 62 | process.exit(0) 63 | } 64 | 65 | async function main (argv) { 66 | port = process.env.PORT 67 | ? parseInt(process.env.PORT) 68 | : argv.p || port 69 | 70 | if (argv.url) url = argv.url 71 | 72 | await build(argv) 73 | http.createServer(handler).listen(port, async () => { 74 | console.log(`listening on ${url}:${port}`) 75 | }) 76 | } 77 | 78 | main(minimist(process.argv.slice(2))) 79 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tonic-docs", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "tonic-docs", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@socketsupply/components": "13.2.5", 13 | "@socketsupply/tonic": "14.1.1", 14 | "chart.js": "^2.9.4", 15 | "highlight.js": "^11.4.0", 16 | "minimist": "^1.2.5", 17 | "node-fetch": "^3.2.0" 18 | }, 19 | "devDependencies": { 20 | "@pre-bundled/send": "^0.16.2-patch-1", 21 | "@types/node": "17.0.21", 22 | "clean-css": "^4.2.3", 23 | "esbuild": "^0.8.36", 24 | "marked": "^4.0.10", 25 | "tonic-ssr": "^1.1.0" 26 | } 27 | }, 28 | "node_modules/@optoolco/tonic": { 29 | "version": "13.3.0", 30 | "resolved": "https://registry.npmjs.org/@optoolco/tonic/-/tonic-13.3.0.tgz", 31 | "integrity": "sha512-2EADB1CGpiMsEPJuPOnIXIwGJv5phjJ3HPyKxlNdVCYan1CPKvO6fSH64w2jn5zmbEH15MCHc/Be3rqBQrBn3Q==", 32 | "dev": true 33 | }, 34 | "node_modules/@pre-bundled/send": { 35 | "version": "0.16.2-patch-1", 36 | "resolved": "https://registry.npmjs.org/@pre-bundled/send/-/send-0.16.2-patch-1.tgz", 37 | "integrity": "sha512-iPiTUhb4zUmrjLPWXo0SQeixIah4fMHvmuU7IFyNr2FWDspIpSQYgEMMpa+CNSmF5iI5yEodBIlXr1+4LZ8r1g==", 38 | "dev": true, 39 | "engines": { 40 | "node": ">= 0.8.0" 41 | } 42 | }, 43 | "node_modules/@socketsupply/components": { 44 | "version": "13.2.5", 45 | "resolved": "https://registry.npmjs.org/@socketsupply/components/-/components-13.2.5.tgz", 46 | "integrity": "sha512-qEf367hi0Tp9EtnWViBQeyzA4o56PI2u43tUr9CM+1W/HkFTgYa+VFuy1Jr62YCprYdOO9SFcvsNU1BzztGqBw==" 47 | }, 48 | "node_modules/@socketsupply/tonic": { 49 | "version": "14.1.1", 50 | "resolved": "https://registry.npmjs.org/@socketsupply/tonic/-/tonic-14.1.1.tgz", 51 | "integrity": "sha512-84plLg5I7EyZiK6BCJMp/7lgptxZZ9XF+LgxufWHzptDK1judbAP2D2klCr265W95AKwPHIIbs7hcYxP+I+d0w==" 52 | }, 53 | "node_modules/@types/node": { 54 | "version": "17.0.21", 55 | "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", 56 | "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==", 57 | "dev": true 58 | }, 59 | "node_modules/chart.js": { 60 | "version": "2.9.4", 61 | "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.9.4.tgz", 62 | "integrity": "sha512-B07aAzxcrikjAPyV+01j7BmOpxtQETxTSlQ26BEYJ+3iUkbNKaOJ/nDbT6JjyqYxseM0ON12COHYdU2cTIjC7A==", 63 | "dependencies": { 64 | "chartjs-color": "^2.1.0", 65 | "moment": "^2.10.2" 66 | } 67 | }, 68 | "node_modules/chartjs-color": { 69 | "version": "2.4.1", 70 | "resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.4.1.tgz", 71 | "integrity": "sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w==", 72 | "dependencies": { 73 | "chartjs-color-string": "^0.6.0", 74 | "color-convert": "^1.9.3" 75 | } 76 | }, 77 | "node_modules/chartjs-color-string": { 78 | "version": "0.6.0", 79 | "resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz", 80 | "integrity": "sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==", 81 | "dependencies": { 82 | "color-name": "^1.0.0" 83 | } 84 | }, 85 | "node_modules/clean-css": { 86 | "version": "4.2.3", 87 | "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz", 88 | "integrity": "sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==", 89 | "dev": true, 90 | "dependencies": { 91 | "source-map": "~0.6.0" 92 | }, 93 | "engines": { 94 | "node": ">= 4.0" 95 | } 96 | }, 97 | "node_modules/color-convert": { 98 | "version": "1.9.3", 99 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 100 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 101 | "dependencies": { 102 | "color-name": "1.1.3" 103 | } 104 | }, 105 | "node_modules/color-convert/node_modules/color-name": { 106 | "version": "1.1.3", 107 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 108 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" 109 | }, 110 | "node_modules/color-name": { 111 | "version": "1.1.4", 112 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 113 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" 114 | }, 115 | "node_modules/data-uri-to-buffer": { 116 | "version": "4.0.0", 117 | "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz", 118 | "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==", 119 | "engines": { 120 | "node": ">= 12" 121 | } 122 | }, 123 | "node_modules/esbuild": { 124 | "version": "0.8.36", 125 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.8.36.tgz", 126 | "integrity": "sha512-kcUQB61Tf8rLJ3mOwP2ruWi/iFufaQcEs4No+JA6e7W2kMOtFExOsbyeFpEF6zNacwk2RF5fYUz5jfZwgn/SJg==", 127 | "dev": true, 128 | "hasInstallScript": true, 129 | "bin": { 130 | "esbuild": "bin/esbuild" 131 | } 132 | }, 133 | "node_modules/fetch-blob": { 134 | "version": "3.1.4", 135 | "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.4.tgz", 136 | "integrity": "sha512-Eq5Xv5+VlSrYWEqKrusxY1C3Hm/hjeAsCGVG3ft7pZahlUAChpGZT/Ms1WmSLnEAisEXszjzu/s+ce6HZB2VHA==", 137 | "funding": [ 138 | { 139 | "type": "github", 140 | "url": "https://github.com/sponsors/jimmywarting" 141 | }, 142 | { 143 | "type": "paypal", 144 | "url": "https://paypal.me/jimmywarting" 145 | } 146 | ], 147 | "dependencies": { 148 | "node-domexception": "^1.0.0", 149 | "web-streams-polyfill": "^3.0.3" 150 | }, 151 | "engines": { 152 | "node": "^12.20 || >= 14.13" 153 | } 154 | }, 155 | "node_modules/formdata-polyfill": { 156 | "version": "4.0.10", 157 | "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", 158 | "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", 159 | "dependencies": { 160 | "fetch-blob": "^3.1.2" 161 | }, 162 | "engines": { 163 | "node": ">=12.20.0" 164 | } 165 | }, 166 | "node_modules/highlight.js": { 167 | "version": "11.4.0", 168 | "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.4.0.tgz", 169 | "integrity": "sha512-nawlpCBCSASs7EdvZOYOYVkJpGmAOKMYZgZtUqSRqodZE0GRVcFKwo1RcpeOemqh9hyttTdd5wDBwHkuSyUfnA==", 170 | "engines": { 171 | "node": ">=12.0.0" 172 | } 173 | }, 174 | "node_modules/marked": { 175 | "version": "4.0.10", 176 | "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.10.tgz", 177 | "integrity": "sha512-+QvuFj0nGgO970fySghXGmuw+Fd0gD2x3+MqCWLIPf5oxdv1Ka6b2q+z9RP01P/IaKPMEramy+7cNy/Lw8c3hw==", 178 | "dev": true, 179 | "bin": { 180 | "marked": "bin/marked.js" 181 | }, 182 | "engines": { 183 | "node": ">= 12" 184 | } 185 | }, 186 | "node_modules/minimist": { 187 | "version": "1.2.5", 188 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 189 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" 190 | }, 191 | "node_modules/moment": { 192 | "version": "2.29.1", 193 | "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", 194 | "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", 195 | "engines": { 196 | "node": "*" 197 | } 198 | }, 199 | "node_modules/node-domexception": { 200 | "version": "1.0.0", 201 | "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", 202 | "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", 203 | "funding": [ 204 | { 205 | "type": "github", 206 | "url": "https://github.com/sponsors/jimmywarting" 207 | }, 208 | { 209 | "type": "github", 210 | "url": "https://paypal.me/jimmywarting" 211 | } 212 | ], 213 | "engines": { 214 | "node": ">=10.5.0" 215 | } 216 | }, 217 | "node_modules/node-fetch": { 218 | "version": "3.2.0", 219 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.0.tgz", 220 | "integrity": "sha512-8xeimMwMItMw8hRrOl3C9/xzU49HV/yE6ORew/l+dxWimO5A4Ra8ld2rerlJvc/O7et5Z1zrWsPX43v1QBjCxw==", 221 | "dependencies": { 222 | "data-uri-to-buffer": "^4.0.0", 223 | "fetch-blob": "^3.1.4", 224 | "formdata-polyfill": "^4.0.10" 225 | }, 226 | "engines": { 227 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 228 | }, 229 | "funding": { 230 | "type": "opencollective", 231 | "url": "https://opencollective.com/node-fetch" 232 | } 233 | }, 234 | "node_modules/parse5": { 235 | "version": "6.0.1", 236 | "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", 237 | "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", 238 | "dev": true 239 | }, 240 | "node_modules/source-map": { 241 | "version": "0.6.1", 242 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 243 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 244 | "dev": true, 245 | "engines": { 246 | "node": ">=0.10.0" 247 | } 248 | }, 249 | "node_modules/tonic-ssr": { 250 | "version": "1.1.0", 251 | "resolved": "https://registry.npmjs.org/tonic-ssr/-/tonic-ssr-1.1.0.tgz", 252 | "integrity": "sha512-skamfiBlEM818YkWxT1pQf5G8kEP8qDaWX4Ee5p+6IWq5B6LQtnKO2BghnmoGcBvLOVMZMpfsfwl9RIFxLBUig==", 253 | "dev": true, 254 | "dependencies": { 255 | "@optoolco/tonic": "^13.1.4", 256 | "parse5": "^6.0.1" 257 | } 258 | }, 259 | "node_modules/web-streams-polyfill": { 260 | "version": "3.2.0", 261 | "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz", 262 | "integrity": "sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA==", 263 | "engines": { 264 | "node": ">= 8" 265 | } 266 | } 267 | }, 268 | "dependencies": { 269 | "@optoolco/tonic": { 270 | "version": "13.3.0", 271 | "resolved": "https://registry.npmjs.org/@optoolco/tonic/-/tonic-13.3.0.tgz", 272 | "integrity": "sha512-2EADB1CGpiMsEPJuPOnIXIwGJv5phjJ3HPyKxlNdVCYan1CPKvO6fSH64w2jn5zmbEH15MCHc/Be3rqBQrBn3Q==", 273 | "dev": true 274 | }, 275 | "@pre-bundled/send": { 276 | "version": "0.16.2-patch-1", 277 | "resolved": "https://registry.npmjs.org/@pre-bundled/send/-/send-0.16.2-patch-1.tgz", 278 | "integrity": "sha512-iPiTUhb4zUmrjLPWXo0SQeixIah4fMHvmuU7IFyNr2FWDspIpSQYgEMMpa+CNSmF5iI5yEodBIlXr1+4LZ8r1g==", 279 | "dev": true 280 | }, 281 | "@socketsupply/components": { 282 | "version": "13.2.5", 283 | "resolved": "https://registry.npmjs.org/@socketsupply/components/-/components-13.2.5.tgz", 284 | "integrity": "sha512-qEf367hi0Tp9EtnWViBQeyzA4o56PI2u43tUr9CM+1W/HkFTgYa+VFuy1Jr62YCprYdOO9SFcvsNU1BzztGqBw==" 285 | }, 286 | "@socketsupply/tonic": { 287 | "version": "14.1.1", 288 | "resolved": "https://registry.npmjs.org/@socketsupply/tonic/-/tonic-14.1.1.tgz", 289 | "integrity": "sha512-84plLg5I7EyZiK6BCJMp/7lgptxZZ9XF+LgxufWHzptDK1judbAP2D2klCr265W95AKwPHIIbs7hcYxP+I+d0w==" 290 | }, 291 | "@types/node": { 292 | "version": "17.0.21", 293 | "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", 294 | "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==", 295 | "dev": true 296 | }, 297 | "chart.js": { 298 | "version": "2.9.4", 299 | "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.9.4.tgz", 300 | "integrity": "sha512-B07aAzxcrikjAPyV+01j7BmOpxtQETxTSlQ26BEYJ+3iUkbNKaOJ/nDbT6JjyqYxseM0ON12COHYdU2cTIjC7A==", 301 | "requires": { 302 | "chartjs-color": "^2.1.0", 303 | "moment": "^2.10.2" 304 | } 305 | }, 306 | "chartjs-color": { 307 | "version": "2.4.1", 308 | "resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.4.1.tgz", 309 | "integrity": "sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w==", 310 | "requires": { 311 | "chartjs-color-string": "^0.6.0", 312 | "color-convert": "^1.9.3" 313 | } 314 | }, 315 | "chartjs-color-string": { 316 | "version": "0.6.0", 317 | "resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz", 318 | "integrity": "sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==", 319 | "requires": { 320 | "color-name": "^1.0.0" 321 | } 322 | }, 323 | "clean-css": { 324 | "version": "4.2.3", 325 | "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz", 326 | "integrity": "sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==", 327 | "dev": true, 328 | "requires": { 329 | "source-map": "~0.6.0" 330 | } 331 | }, 332 | "color-convert": { 333 | "version": "1.9.3", 334 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 335 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 336 | "requires": { 337 | "color-name": "1.1.3" 338 | }, 339 | "dependencies": { 340 | "color-name": { 341 | "version": "1.1.3", 342 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 343 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" 344 | } 345 | } 346 | }, 347 | "color-name": { 348 | "version": "1.1.4", 349 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 350 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" 351 | }, 352 | "data-uri-to-buffer": { 353 | "version": "4.0.0", 354 | "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz", 355 | "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==" 356 | }, 357 | "esbuild": { 358 | "version": "0.8.36", 359 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.8.36.tgz", 360 | "integrity": "sha512-kcUQB61Tf8rLJ3mOwP2ruWi/iFufaQcEs4No+JA6e7W2kMOtFExOsbyeFpEF6zNacwk2RF5fYUz5jfZwgn/SJg==", 361 | "dev": true 362 | }, 363 | "fetch-blob": { 364 | "version": "3.1.4", 365 | "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.4.tgz", 366 | "integrity": "sha512-Eq5Xv5+VlSrYWEqKrusxY1C3Hm/hjeAsCGVG3ft7pZahlUAChpGZT/Ms1WmSLnEAisEXszjzu/s+ce6HZB2VHA==", 367 | "requires": { 368 | "node-domexception": "^1.0.0", 369 | "web-streams-polyfill": "^3.0.3" 370 | } 371 | }, 372 | "formdata-polyfill": { 373 | "version": "4.0.10", 374 | "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", 375 | "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", 376 | "requires": { 377 | "fetch-blob": "^3.1.2" 378 | } 379 | }, 380 | "highlight.js": { 381 | "version": "11.4.0", 382 | "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.4.0.tgz", 383 | "integrity": "sha512-nawlpCBCSASs7EdvZOYOYVkJpGmAOKMYZgZtUqSRqodZE0GRVcFKwo1RcpeOemqh9hyttTdd5wDBwHkuSyUfnA==" 384 | }, 385 | "marked": { 386 | "version": "4.0.10", 387 | "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.10.tgz", 388 | "integrity": "sha512-+QvuFj0nGgO970fySghXGmuw+Fd0gD2x3+MqCWLIPf5oxdv1Ka6b2q+z9RP01P/IaKPMEramy+7cNy/Lw8c3hw==", 389 | "dev": true 390 | }, 391 | "minimist": { 392 | "version": "1.2.5", 393 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 394 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" 395 | }, 396 | "moment": { 397 | "version": "2.29.1", 398 | "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", 399 | "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" 400 | }, 401 | "node-domexception": { 402 | "version": "1.0.0", 403 | "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", 404 | "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==" 405 | }, 406 | "node-fetch": { 407 | "version": "3.2.0", 408 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.0.tgz", 409 | "integrity": "sha512-8xeimMwMItMw8hRrOl3C9/xzU49HV/yE6ORew/l+dxWimO5A4Ra8ld2rerlJvc/O7et5Z1zrWsPX43v1QBjCxw==", 410 | "requires": { 411 | "data-uri-to-buffer": "^4.0.0", 412 | "fetch-blob": "^3.1.4", 413 | "formdata-polyfill": "^4.0.10" 414 | } 415 | }, 416 | "parse5": { 417 | "version": "6.0.1", 418 | "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", 419 | "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", 420 | "dev": true 421 | }, 422 | "source-map": { 423 | "version": "0.6.1", 424 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 425 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 426 | "dev": true 427 | }, 428 | "tonic-ssr": { 429 | "version": "1.1.0", 430 | "resolved": "https://registry.npmjs.org/tonic-ssr/-/tonic-ssr-1.1.0.tgz", 431 | "integrity": "sha512-skamfiBlEM818YkWxT1pQf5G8kEP8qDaWX4Ee5p+6IWq5B6LQtnKO2BghnmoGcBvLOVMZMpfsfwl9RIFxLBUig==", 432 | "dev": true, 433 | "requires": { 434 | "@optoolco/tonic": "^13.1.4", 435 | "parse5": "^6.0.1" 436 | } 437 | }, 438 | "web-streams-polyfill": { 439 | "version": "3.2.0", 440 | "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz", 441 | "integrity": "sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA==" 442 | } 443 | } 444 | } 445 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tonic-docs", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "src/index.server.js", 6 | "scripts": { 7 | "start": "./bin/loader.js", 8 | "build": "./bin/build-cli.js" 9 | }, 10 | "type": "module", 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "@socketsupply/components": "13.2.5", 15 | "@socketsupply/tonic": "14.1.1", 16 | "chart.js": "^2.9.4", 17 | "highlight.js": "^11.4.0", 18 | "minimist": "^1.2.5", 19 | "node-fetch": "^3.2.0" 20 | }, 21 | "devDependencies": { 22 | "@pre-bundled/send": "^0.16.2-patch-1", 23 | "@types/node": "17.0.21", 24 | "clean-css": "^4.2.3", 25 | "esbuild": "^0.8.36", 26 | "marked": "^4.0.10", 27 | "tonic-ssr": "^1.1.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/components/css-module.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import path from 'path' 3 | 4 | import Tonic from 'tonic-ssr' 5 | import CleanCSS from 'clean-css' 6 | 7 | const minifier = new CleanCSS({}) 8 | 9 | const closest = (node, fn) => { 10 | while (node.parentNode) { 11 | node = node.parentNode 12 | if (fn(node)) return node 13 | } 14 | } 15 | 16 | export default class CssModule extends Tonic { 17 | async render () { 18 | const html = closest(this.node, n => n.tagName === 'html') 19 | const body = closest(this.node, n => n.tagName === 'body') 20 | 21 | // 22 | // We can remove nodes and create new ones in other 23 | // branches of the tree at run-time. 24 | // 25 | const i = body.childNodes.findIndex(node => { 26 | return node.tagName === 'css-module' 27 | }) 28 | 29 | body.childNodes.splice(i, 1) 30 | 31 | // 32 | // Even change the tag at render-time! 33 | // 34 | this.node.tagName = 'style' 35 | 36 | const head = html.childNodes.find(node => node.tagName === 'head') 37 | 38 | if (head) { 39 | this.node.attrs = {} 40 | head.childNodes.push(this.node) 41 | } 42 | 43 | try { 44 | const location = path.resolve(this.props.src) 45 | let s = await fs.promises.readFile(location, 'utf8') 46 | 47 | // 48 | // make @import path("...") a compile time directive 49 | // 50 | s = s.replace(/@import path\("(.*)\"\);/g, (_, p) => { 51 | const r = path.resolve(path.dirname(location), p) 52 | 53 | try { 54 | return fs.readFileSync(r, 'utf8') 55 | } catch (err) { 56 | return `// Failed to import ${p}: ${err.message}` 57 | } 58 | }) 59 | 60 | const result = minifier.minify(s) 61 | 62 | return this.html`${Tonic.unsafeRawString(result.styles)}` 63 | } catch (err) { 64 | return this.html`${err.message}` 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/components/esbuild-module.js: -------------------------------------------------------------------------------- 1 | import esbuild from 'esbuild' 2 | import Tonic from 'tonic-ssr' 3 | 4 | export default class EsbuildModule extends Tonic { 5 | async render () { 6 | const { 7 | src, 8 | dest, 9 | url 10 | } = this.props 11 | 12 | delete this.props.src 13 | delete this.props.dest 14 | delete this.props.url 15 | 16 | try { 17 | await esbuild.build({ 18 | ...this.props, 19 | entryPoints: [src], 20 | bundle: true, 21 | outfile: dest 22 | }) 23 | 24 | return this.html` 25 | 26 | ` 27 | } catch (err) { 28 | return this.html` 29 | Unable to bundle (${err.message}). 30 | ` 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/components/markdown-module.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import fs from 'node:fs/promises' 4 | import path from 'node:path' 5 | 6 | import fetch from 'node-fetch' 7 | import Tonic from 'tonic-ssr' 8 | import { marked } from 'marked' 9 | import hl from 'highlight.js' 10 | 11 | const renderer = new marked.Renderer() 12 | 13 | const highlight = (code, lang = 'javascript', escaped) => { 14 | if (!lang) lang = 'javascript' 15 | return hl.highlight(code, { language: 'javascript', ignoreIllegals: true }).value 16 | } 17 | 18 | marked.setOptions({ 19 | renderer, 20 | highlight // doubles the build time 21 | }) 22 | 23 | export default class MarkdownModule extends Tonic { 24 | async render () { 25 | if (!this.props.src) { 26 | const raw = this.node.childNodes[0].value 27 | this.node.childNodes.length = 0 28 | return Tonic.unsafeRawString(marked(raw)) 29 | } 30 | 31 | try { 32 | let raw = '' 33 | 34 | if (this.props.src.startsWith('https://')) { 35 | const last = Tonic.cache[this.props.src] 36 | 37 | if (last && (last.timestamp > Date.now() - 5e4)) { 38 | return last.value 39 | } 40 | 41 | const res = await fetch(this.props.src) 42 | raw = await res.text() 43 | 44 | Tonic.cache[this.props.src] = { 45 | timestamp: Date.now(), 46 | value 47 | } 48 | 49 | } else { 50 | const src = path.resolve(this.props.src) 51 | raw = await fs.readFile(src, 'utf8') 52 | } 53 | 54 | const value = Tonic.unsafeRawString(marked(raw)) 55 | 56 | return value 57 | } catch (err) { 58 | return this.html` 59 |
60 | Unable to read file ${this.props.src} (${err.message}). 61 |
62 | ` 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/example-setup.js: -------------------------------------------------------------------------------- 1 | // 2 | // This file contains code that sets up some interactions 3 | // for a few of the components on the example page. 4 | // 5 | import Tonic from '@socketsupply/tonic' 6 | import { Windowed } from '@socketsupply/components/windowed' 7 | 8 | export default () => { 9 | // 10 | // badge 11 | // 12 | const add = document.getElementById('add-notification') 13 | const subtract = document.getElementById('subtract-notification') 14 | const tonicBadge = document.querySelector('tonic-badge') 15 | 16 | add.addEventListener('click', (e) => { 17 | ++tonicBadge.state.count 18 | 19 | tonicBadge.reRender() 20 | }) 21 | 22 | subtract.addEventListener('click', e => { 23 | let count = tonicBadge.state.count 24 | tonicBadge.state.count = count > 0 ? --count : count 25 | 26 | tonicBadge.reRender() 27 | }) 28 | 29 | // 30 | // button 31 | // 32 | const buttons = [...document.querySelectorAll('.tonic-button-example')] 33 | 34 | for (const button of buttons) { 35 | button.addEventListener('click', e => { 36 | clearTimeout(button.timeout) 37 | button.timeout = setTimeout(() => { 38 | button.loading(false) 39 | }, 3e3) 40 | }) 41 | } 42 | 43 | // 44 | // chart 45 | // 46 | const chart = document.querySelector('tonic-chart') 47 | chart.library = require('chart.js') 48 | chart.redraw() 49 | 50 | // 51 | // dialog 52 | // 53 | class ShowRandom extends Tonic { 54 | async click (e) { 55 | if (Tonic.match(e.target, '#update')) { 56 | this.state.message = String(Math.random()) 57 | this.reRender() 58 | } 59 | } 60 | 61 | render () { 62 | return this.html` 63 |
Dialog
64 | 65 |
66 | ${this.state.message || 'Ready'} 67 |
68 | 69 | 72 | ` 73 | } 74 | } 75 | 76 | Tonic.add(ShowRandom) 77 | 78 | const link = document.getElementById('example-dialog-link') 79 | const dialog = document.getElementById('example-dialog') 80 | 81 | link.addEventListener('click', async e => { 82 | await dialog.reRender() 83 | await dialog.show() 84 | }) 85 | 86 | // 87 | // form 88 | // 89 | const form = document.getElementById('form-example') 90 | const button = document.getElementById('form-submit') 91 | 92 | form.addEventListener('input', e => { 93 | button.disabled = !form.validate() 94 | }) 95 | 96 | form.addEventListener('change', e => { 97 | button.disabled = !form.validate() 98 | }) 99 | 100 | // 101 | // input 102 | // 103 | const input = document.getElementById('tonic-input-example') 104 | const span = document.getElementById('tonic-input-state') 105 | 106 | const listener = e => { 107 | const state = input.state 108 | span.textContent = `Value: "${state.value || 'Empty'}", Focus: ${state.focus}` 109 | } 110 | 111 | input.addEventListener('input', listener) 112 | input.addEventListener('blur', listener) 113 | input.addEventListener('focus', listener) 114 | 115 | // 116 | // panel 117 | // 118 | class ReadWikipedia extends Tonic { 119 | async getArticle (title) { 120 | try { 121 | const res = await window.fetch(`https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts&exintro=&explaintext=&titles=${title}&origin=*`) 122 | return Object.values((await res.json()).query.pages)[0] 123 | } catch (err) { 124 | return { title: 'Error', extract: err.message } 125 | } 126 | } 127 | 128 | async click (e) { 129 | if (e.target.value === 'close') { 130 | return this.parentElement.hide() 131 | } 132 | 133 | if (e.target.value === 'get') { 134 | const page = await this.getArticle('HTML') 135 | 136 | this.reRender(props => ({ 137 | ...props, 138 | ...page 139 | })) 140 | } 141 | } 142 | 143 | async * render () { 144 | const title = this.props.title || 'Hello' 145 | const content = this.props.extract 146 | ? this.props.extract 147 | : 'Click "get" to fetch the content from Wikipedia.' 148 | 149 | return this.html` 150 |
Panel Example
151 |
152 |

${title}

153 |

${content}

154 |
155 |