From b7ac9af688e1dbf749fc6d5cb0ab312ba54c3aaf Mon Sep 17 00:00:00 2001 From: Krzysztof Rosinski Date: Wed, 14 Oct 2026 06:11:10 +0200 Subject: [PATCH 1/2] Highlight example source with the API documentation palette --- examples/README.md | 4 + examples/angular/page.ts | 5 +- examples/basic/highlight.ts | 48 +++ examples/basic/package.json | 1 + examples/basic/page.ts | 3 +- examples/package.json | 1 + examples/react/page.ts | 5 +- examples/source.ts | 5 +- package-lock.json | 622 +++++++++++++++++++++++++++++++++ package.json | 1 + tests/browser/examples.spec.ts | 38 ++ typedoc.json | 2 + 12 files changed, 728 insertions(+), 7 deletions(-) create mode 100644 examples/basic/highlight.ts diff --git a/examples/README.md b/examples/README.md index 2247662..e8973c8 100644 --- a/examples/README.md +++ b/examples/README.md @@ -34,6 +34,10 @@ Use the archive's absolute path when running a copied gallery. installed `smithkit` package and has no aliases to repository source. The basic example also remains runnable independently from its own directory. +Source panels use Shiki with the same `dark-plus` syntax theme as the API reference. +The shared presentation helper lives in `basic/highlight.ts` so the basic example +remains self-contained. Highlighting is not needed by the chart integration code. + The pages use ordinary HTML elements and containers with an explicit size. A white host background keeps the light theme's dark labels readable: the light preset's SVG background is transparent. The dark preset supplies its own opaque background. diff --git a/examples/angular/page.ts b/examples/angular/page.ts index c5e3dd2..7583714 100644 --- a/examples/angular/page.ts +++ b/examples/angular/page.ts @@ -4,6 +4,7 @@ import application from './main.ts?raw'; import data from '../framework-data.ts?raw'; import '../style.css'; import '../framework.css'; +import { showCode } from '../basic/highlight'; -document.querySelector('#source')!.textContent = source; -document.querySelector('#markup')!.textContent = application + '\n\n// framework-data.ts\n' + data; +void showCode('#source', source); +void showCode('#markup', application + '\n\n// framework-data.ts\n' + data); diff --git a/examples/basic/highlight.ts b/examples/basic/highlight.ts new file mode 100644 index 0000000..5e7b5b0 --- /dev/null +++ b/examples/basic/highlight.ts @@ -0,0 +1,48 @@ +import { createHighlighterCore } from 'shiki/core'; +import { createOnigurumaEngine } from 'shiki/engine/oniguruma'; +import html from 'shiki/langs/html.mjs'; +import tsx from 'shiki/langs/tsx.mjs'; +import typescript from 'shiki/langs/typescript.mjs'; +import darkPlus from 'shiki/themes/dark-plus.mjs'; + +// Keep this helper in the independently runnable basic example. The gallery shares it. +// Match TypeDoc's dark theme and load only the languages displayed in our examples. +const highlighter = createHighlighterCore({ + themes: [darkPlus], + langs: [html, tsx, typescript], + engine: createOnigurumaEngine(import('shiki/wasm')), +}); + +/** Color source without changing its copyable text or interpreting HTML examples as markup. */ +export async function showCode( + selector: string, + source: string, + language: 'typescript' | 'tsx' | 'html' = 'typescript', +): Promise { + const code = document.querySelector(selector)!; + code.textContent = source; + const { tokens, fg, bg } = (await highlighter).codeToTokens(source, { + lang: language, + theme: 'dark-plus', + }); + const fragment = document.createDocumentFragment(); + tokens.forEach((line, index) => { + if (index > 0) { + fragment.append('\n'); + } + for (const token of line) { + const span = document.createElement('span'); + span.textContent = token.content; + span.style.color = token.color ?? fg ?? ''; + if (token.fontStyle && token.fontStyle > 0) { + span.style.fontStyle = token.fontStyle & 1 ? 'italic' : ''; + span.style.fontWeight = token.fontStyle & 2 ? 'bold' : ''; + span.style.textDecoration = token.fontStyle & 4 ? 'underline' : ''; + } + fragment.append(span); + } + }); + code.replaceChildren(fragment); + code.style.color = fg ?? ''; + code.closest('pre')!.style.backgroundColor = bg ?? ''; +} diff --git a/examples/basic/package.json b/examples/basic/package.json index 16ab20f..2820cde 100644 --- a/examples/basic/package.json +++ b/examples/basic/package.json @@ -11,6 +11,7 @@ "smithkit": "^0.6.0" }, "devDependencies": { + "shiki": "4.5.0", "vite": "8.3.2" } } diff --git a/examples/basic/page.ts b/examples/basic/page.ts index 5200230..42820b2 100644 --- a/examples/basic/page.ts +++ b/examples/basic/page.ts @@ -1,4 +1,5 @@ import './main'; import source from './main.ts?raw'; +import { showCode } from './highlight'; -document.querySelector('#source')!.textContent = source; +void showCode('#source', source); diff --git a/examples/package.json b/examples/package.json index fc4f049..6b7445c 100644 --- a/examples/package.json +++ b/examples/package.json @@ -18,6 +18,7 @@ "rxjs": "7.8.2" }, "devDependencies": { + "shiki": "4.5.0", "vite": "8.3.2", "@types/react": "19.3.0", "@types/react-dom": "19.3.0", diff --git a/examples/react/page.ts b/examples/react/page.ts index fc740ac..fb0268f 100644 --- a/examples/react/page.ts +++ b/examples/react/page.ts @@ -4,6 +4,7 @@ import application from './main.tsx?raw'; import data from '../framework-data.ts?raw'; import '../style.css'; import '../framework.css'; +import { showCode } from '../basic/highlight'; -document.querySelector('#source')!.textContent = source; -document.querySelector('#markup')!.textContent = application + '\n\n// framework-data.ts\n' + data; +void showCode('#source', source, 'tsx'); +void showCode('#markup', application + '\n\n// framework-data.ts\n' + data, 'tsx'); diff --git a/examples/source.ts b/examples/source.ts index 63c1d11..c00eb25 100644 --- a/examples/source.ts +++ b/examples/source.ts @@ -1,8 +1,9 @@ import './style.css'; +import { showCode } from './basic/highlight'; /** Display the exact executed source and the required host markup. */ export function showSource(source: string): void { - document.querySelector('#source')!.textContent = source; + void showCode('#source', source); const markup = document.querySelector('.preview')!.cloneNode(true) as HTMLElement; const chart = markup.querySelector('#chart')!; chart.replaceChildren(); @@ -11,5 +12,5 @@ export function showSource(source: string): void { if (output) { output.replaceChildren(); } - document.querySelector('#markup')!.textContent = markup.innerHTML.trim(); + void showCode('#markup', markup.innerHTML.trim(), 'html'); } diff --git a/package-lock.json b/package-lock.json index c2b29a1..9973a53 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,7 @@ "react": "19.3.0", "react-dom": "19.3.0", "rxjs": "7.8.2", + "shiki": "4.5.0", "typedoc": "0.28.20", "typescript": "npm:@typescript/typescript6@^6.0.2", "typescript-eslint": "^8.71.0", @@ -1263,6 +1264,66 @@ "node": ">=20.9.0" } }, + "node_modules/@shikijs/core": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.5.0.tgz", + "integrity": "sha512-6CIgwq+bt2JZ3OSjb/nBkkwEf/CfpHcbam+Mg5Pllco7HAGRQ2/EFhZrrJtbyNTgKwknfVS+llsXGygiDGwJWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/primitive": "4.5.0", + "@shikijs/types": "4.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.5", + "hast-util-to-html": "^9.0.5" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/core/node_modules/@shikijs/types": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.5.0.tgz", + "integrity": "sha512-drtPrtD8N0DENyD2cyt6Ajy+IWsiCLgMxODwLTpvH1UsN6a9jf90tNTPjqwHvA+0Lzu9e0txk8DjfqH1p9UJRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.5" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.5.0.tgz", + "integrity": "sha512-Ed6UdEj84LyYkwnxM5CvSMWAD+id9ExTYri0/+x41U9zrgNifPSS9lZm0JqBP0VfYI8yr8tY5nbNq9+p9R0kFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.6" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/engine-javascript/node_modules/@shikijs/types": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.5.0.tgz", + "integrity": "sha512-drtPrtD8N0DENyD2cyt6Ajy+IWsiCLgMxODwLTpvH1UsN6a9jf90tNTPjqwHvA+0Lzu9e0txk8DjfqH1p9UJRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.5" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/@shikijs/engine-oniguruma": { "version": "3.23.0", "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.23.0.tgz", @@ -1284,6 +1345,35 @@ "@shikijs/types": "3.23.0" } }, + "node_modules/@shikijs/primitive": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.5.0.tgz", + "integrity": "sha512-UM7ShlA8oz0OWxLLO8BmO9Ip6fVqdkt8yF0EH7nOCqHfrx1eEAzF0ZUVK7L+I8NEgeu1PEWSRlxaiPPeCb4iDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.5" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/primitive/node_modules/@shikijs/types": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.5.0.tgz", + "integrity": "sha512-drtPrtD8N0DENyD2cyt6Ajy+IWsiCLgMxODwLTpvH1UsN6a9jf90tNTPjqwHvA+0Lzu9e0txk8DjfqH1p9UJRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.5" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/@shikijs/themes": { "version": "3.23.0", "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.23.0.tgz", @@ -1641,6 +1731,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/node": { "version": "24.19.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.19.1.tgz", @@ -2309,6 +2409,13 @@ "node": ">=16.20.0" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.4.0.tgz", + "integrity": "sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==", + "dev": true, + "license": "ISC" + }, "node_modules/@vitest/mocker": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-5.0.3.tgz", @@ -2580,6 +2687,17 @@ ], "license": "CC-BY-4.0" }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chai": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/chai/-/chai-6.3.0.tgz", @@ -2590,6 +2708,28 @@ "node": ">=18" } }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chokidar": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", @@ -2639,6 +2779,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/commander": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", @@ -3112,6 +3263,16 @@ "robust-predicates": "^3.0.2" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -3122,6 +3283,20 @@ "node": ">=8" } }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/diff": { "version": "8.0.4", "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", @@ -3654,6 +3829,44 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hookified": { "version": "1.15.1", "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz", @@ -3661,6 +3874,17 @@ "dev": true, "license": "MIT" }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -4221,6 +4445,28 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdurl": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.1.0.tgz", @@ -4228,6 +4474,100 @@ "dev": true, "license": "MIT" }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.3.tgz", + "integrity": "sha512-oxB2Ik03hI0gv+VNn9tnh1t1YEe9MDPptViAEgfdf3YQHsn0pzGTgCdlSCJXcwhqm8phaEuM7zeEu3QQzVBrPg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, "node_modules/minimatch": { "version": "10.2.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", @@ -4301,6 +4641,25 @@ "node": ">=12.20.0" } }, + "node_modules/oniguruma-parser": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.2.tgz", + "integrity": "sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==", + "dev": true, + "license": "MIT" + }, + "node_modules/oniguruma-to-es": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.6.tgz", + "integrity": "sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "oniguruma-parser": "^0.12.2", + "regex": "^6.1.0", + "regex-recursion": "^6.0.2" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -4505,6 +4864,17 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/property-information": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", + "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -4589,6 +4959,33 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", + "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "dev": true, + "license": "MIT" + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -4726,6 +5123,80 @@ "node": ">=8" } }, + "node_modules/shiki": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-4.5.0.tgz", + "integrity": "sha512-OcRW72ngQvc+MSVWBRIovC/BTW589iUE/Vxav7VeM4RcY9MUjqB+IR6ILG1k9nuW6LiG/nct9uma4/MBtnZbmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/core": "4.5.0", + "@shikijs/engine-javascript": "4.5.0", + "@shikijs/engine-oniguruma": "4.5.0", + "@shikijs/langs": "4.5.0", + "@shikijs/themes": "4.5.0", + "@shikijs/types": "4.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.5" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/shiki/node_modules/@shikijs/engine-oniguruma": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.5.0.tgz", + "integrity": "sha512-dKVmbJecVdB9recl8a/CU4jdpNXyjJQrIcesIkpS70s5HS02ohpnCSpMUbotPnEUzbAcLQ+S4hwZYbkBBiTcJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.5.0", + "@shikijs/vscode-textmate": "^10.0.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/shiki/node_modules/@shikijs/langs": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.5.0.tgz", + "integrity": "sha512-wwBTqwduXSz0X4v9Qa2EWeFaYJEw1clCncWjoRNQRbVDu3SPRTRWucnouYapKj3b8Q+sk/41Vbc4B5c8nuYzLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.5.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/shiki/node_modules/@shikijs/themes": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.5.0.tgz", + "integrity": "sha512-zGb87H3UBbpuI4yoh5UguB1XbU+2BDU3kflM7UfZ+Cy2hWo9lBtqjSVK20gmLY6ukYl040hAypGXMgud9vul8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.5.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/shiki/node_modules/@shikijs/types": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.5.0.tgz", + "integrity": "sha512-drtPrtD8N0DENyD2cyt6Ajy+IWsiCLgMxODwLTpvH1UsN6a9jf90tNTPjqwHvA+0Lzu9e0txk8DjfqH1p9UJRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.5" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -4746,6 +5217,17 @@ "node": ">=0.10.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -4787,6 +5269,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/strip-ansi": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", @@ -4869,6 +5366,17 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/ts-api-utils": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", @@ -4978,6 +5486,79 @@ "dev": true, "license": "MIT" }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -5042,6 +5623,36 @@ "url": "https://github.com/sponsors/sxzz" } }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/vite": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.2.tgz", @@ -5344,6 +5955,17 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/package.json b/package.json index 21de445..7531bfc 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "react": "19.3.0", "react-dom": "19.3.0", "rxjs": "7.8.2", + "shiki": "4.5.0", "typedoc": "0.28.20", "typescript": "npm:@typescript/typescript6@^6.0.2", "typescript-eslint": "^8.71.0", diff --git a/tests/browser/examples.spec.ts b/tests/browser/examples.spec.ts index abcd327..b6d9cef 100644 --- a/tests/browser/examples.spec.ts +++ b/tests/browser/examples.spec.ts @@ -1,6 +1,44 @@ import { expect, test } from '@playwright/test'; import { readFile } from 'node:fs/promises'; +test('source highlighting preserves copyable TS, TSX, and HTML in the API dark palette', async ({ + page, +}) => { + for (const [slug, file] of [ + ['static', 'main.ts'], + ['basic', 'main.ts'], + ['react', 'SmithChart.tsx'], + ['angular', 'SmithChartComponent.ts'], + ]) { + await page.goto(`examples/${slug}/index.html`); + const code = page.locator('#source'); + await expect(code.locator('span').first()).toBeAttached(); + expect(await code.textContent()).toBe(await readFile(`examples/${slug}/${file}`, 'utf8')); + await expect( + code + .locator('span') + .filter({ hasText: /^import$/ }) + .first(), + ).toHaveCSS('color', 'rgb(197, 134, 192)'); + await expect(code.locator('..')).toHaveCSS('background-color', 'rgb(30, 30, 30)'); + if (slug !== 'basic') { + await page.locator('details summary').click(); + await expect(page.locator('#markup span').first()).toBeVisible(); + await expect(page.locator('#markup :not(span)')).toHaveCount(0); + if (slug === 'static') { + await expect(page.locator('#markup')).toContainText('
{ const errors: string[] = []; page.on('pageerror', (error) => errors.push(error.message)); diff --git a/typedoc.json b/typedoc.json index 8e0fc6d..3762606 100644 --- a/typedoc.json +++ b/typedoc.json @@ -25,6 +25,8 @@ "searchInDocuments": true, "searchInComments": true, "customCss": "docs/api.css", + "darkHighlightTheme": "dark-plus", + "lightHighlightTheme": "light-plus", "treatWarningsAsErrors": true, "navigationLinks": { "Demo": "https://rosek86.github.io/smithkit/", From f27a8c947d867a642709a41c44759661a9dfffee Mon Sep 17 00:00:00 2001 From: Krzysztof Rosinski Date: Wed, 14 Oct 2026 06:15:44 +0200 Subject: [PATCH 2/2] Format example HTML snippets and improve code typography --- examples/README.md | 2 ++ examples/basic/style.css | 5 +++++ examples/package.json | 1 + examples/source.ts | 14 ++++++++++-- examples/style.css | 5 +++++ tests/browser/examples.spec.ts | 39 +++++++++++++++++++++++++++++++++- 6 files changed, 63 insertions(+), 3 deletions(-) diff --git a/examples/README.md b/examples/README.md index e8973c8..750e690 100644 --- a/examples/README.md +++ b/examples/README.md @@ -37,6 +37,8 @@ example also remains runnable independently from its own directory. Source panels use Shiki with the same `dark-plus` syntax theme as the API reference. The shared presentation helper lives in `basic/highlight.ts` so the basic example remains self-contained. Highlighting is not needed by the chart integration code. +HTML host snippets are formatted with Prettier before highlighting, including in +production builds where the page markup is minified. The pages use ordinary HTML elements and containers with an explicit size. A white host background keeps the light theme's dark labels readable: the light preset's diff --git a/examples/basic/style.css b/examples/basic/style.css index 6394eee..3284ed9 100644 --- a/examples/basic/style.css +++ b/examples/basic/style.css @@ -120,6 +120,10 @@ output { margin: 16px 0; font-variant-numeric: tabular-nums; } +code, +pre { + font-family: ui-monospace, 'SFMono-Regular', Menlo, Consolas, 'Liberation Mono', monospace; +} pre { overflow: auto; padding: 20px; @@ -128,6 +132,7 @@ pre { background: var(--page); font-size: 13px; line-height: 1.65; + tab-size: 2; } @media (max-width: 600px) { main { diff --git a/examples/package.json b/examples/package.json index 6b7445c..6b6fbf6 100644 --- a/examples/package.json +++ b/examples/package.json @@ -18,6 +18,7 @@ "rxjs": "7.8.2" }, "devDependencies": { + "prettier": "3.9.9", "shiki": "4.5.0", "vite": "8.3.2", "@types/react": "19.3.0", diff --git a/examples/source.ts b/examples/source.ts index c00eb25..91885f4 100644 --- a/examples/source.ts +++ b/examples/source.ts @@ -1,8 +1,10 @@ import './style.css'; import { showCode } from './basic/highlight'; +import { format } from 'prettier/standalone'; +import * as htmlPlugin from 'prettier/plugins/html'; /** Display the exact executed source and the required host markup. */ -export function showSource(source: string): void { +export async function showSource(source: string): Promise { void showCode('#source', source); const markup = document.querySelector('.preview')!.cloneNode(true) as HTMLElement; const chart = markup.querySelector('#chart')!; @@ -12,5 +14,13 @@ export function showSource(source: string): void { if (output) { output.replaceChildren(); } - void showCode('#markup', markup.innerHTML.trim(), 'html'); + const formattedMarkup = await format(markup.innerHTML, { + parser: 'html', + plugins: [htmlPlugin], + printWidth: 80, + tabWidth: 2, + useTabs: false, + htmlWhitespaceSensitivity: 'ignore', + }); + await showCode('#markup', formattedMarkup.trimEnd(), 'html'); } diff --git a/examples/style.css b/examples/style.css index d1d538d..f4872d5 100644 --- a/examples/style.css +++ b/examples/style.css @@ -178,6 +178,10 @@ output { margin: 16px 0; font-variant-numeric: tabular-nums; } +code, +pre { + font-family: ui-monospace, 'SFMono-Regular', Menlo, Consolas, 'Liberation Mono', monospace; +} pre { overflow: auto; padding: 20px; @@ -186,6 +190,7 @@ pre { background: var(--page); font-size: 13px; line-height: 1.65; + tab-size: 2; } summary { cursor: pointer; diff --git a/tests/browser/examples.spec.ts b/tests/browser/examples.spec.ts index b6d9cef..8ead6c1 100644 --- a/tests/browser/examples.spec.ts +++ b/tests/browser/examples.spec.ts @@ -26,7 +26,7 @@ test('source highlighting preserves copyable TS, TSX, and HTML in the API dark p await expect(page.locator('#markup span').first()).toBeVisible(); await expect(page.locator('#markup :not(span)')).toHaveCount(0); if (slug === 'static') { - await expect(page.locator('#markup')).toContainText('
{ + for (const slug of ['static', 'markers', 'appearance', 'export', 'touchstone', 'large-trace']) { + await page.goto(`examples/${slug}/index.html`); + await page.locator('details summary').click(); + await expect(page.locator('#markup span').first()).toBeVisible(); + const markup = (await page.locator('#markup').textContent())!; + expect(markup).toMatch(/^ { + const template = document.createElement('template'); + template.innerHTML = html; + const selectors = 'input, select, button, output'; + const describe = (element: Element) => ({ + tag: element.tagName, + id: element.id, + type: element.getAttribute('type'), + min: element.getAttribute('min'), + max: element.getAttribute('max'), + options: [...element.querySelectorAll('option')].map((option) => option.value), + }); + return { + snippet: [...template.content.querySelectorAll(selectors)].map(describe), + preview: [...document.querySelector('.preview')!.querySelectorAll(selectors)] + .filter((element) => !element.closest('#chart')) + .map(describe), + }; + }, markup); + expect(controls.snippet).toEqual(controls.preview); + if (slug === 'markers') { + expect(markup).toContain('