@scaffold-ui-kit/preview 0.0.0-stage → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1868 -2
- package/application/host/bootstrap.ts +43 -0
- package/application/host/main.ts +31 -0
- package/application/host/shell-host.ts +47 -0
- package/application/index.html +12 -0
- package/application/lib/base-url.ts +15 -0
- package/application/lib/no-code-block.ts +7 -0
- package/application/lib/no-setup.ts +3 -0
- package/application/lib/no-views.ts +6 -0
- package/application/preview/code/code.html +12 -0
- package/application/preview/code/main.ts +89 -0
- package/application/preview/globals.ts +254 -0
- package/application/preview/html/main.ts +93 -0
- package/application/preview/html/preview.html +12 -0
- package/application/preview/react/main.tsx +146 -0
- package/application/preview/react/preview.html +12 -0
- package/application/preview/svelte/Preview.svelte +104 -0
- package/application/preview/svelte/main.ts +13 -0
- package/application/preview/svelte/preview.html +12 -0
- package/application/preview/svelte/story-props.ts +172 -0
- package/application/preview/view/main.ts +124 -0
- package/application/preview/view/view.html +12 -0
- package/application/preview/vue/main.ts +145 -0
- package/application/preview/vue/preview.html +12 -0
- package/application/shells/charcell/components/Application/index.css +37 -0
- package/application/shells/charcell/components/Application/index.tsx +65 -0
- package/application/shells/charcell/components/Bar/index.css +68 -0
- package/application/shells/charcell/components/Bar/index.tsx +60 -0
- package/application/shells/charcell/components/CodePane/index.css +60 -0
- package/application/shells/charcell/components/CodePane/index.tsx +113 -0
- package/application/shells/charcell/components/ComponentStage/index.css +26 -0
- package/application/shells/charcell/components/ComponentStage/index.tsx +110 -0
- package/application/shells/charcell/components/Control/index.css +20 -0
- package/application/shells/charcell/components/Control/index.tsx +118 -0
- package/application/shells/charcell/components/CopyLink/index.tsx +35 -0
- package/application/shells/charcell/components/Description/index.css +6 -0
- package/application/shells/charcell/components/Description/index.tsx +24 -0
- package/application/shells/charcell/components/EditorAddDialog/index.css +9 -0
- package/application/shells/charcell/components/EditorAddDialog/index.tsx +158 -0
- package/application/shells/charcell/components/EditorPanel/index.css +48 -0
- package/application/shells/charcell/components/EditorPanel/index.tsx +496 -0
- package/application/shells/charcell/components/EventsRow/index.css +42 -0
- package/application/shells/charcell/components/EventsRow/index.tsx +68 -0
- package/application/shells/charcell/components/HeadingRow/index.css +11 -0
- package/application/shells/charcell/components/HeadingRow/index.tsx +16 -0
- package/application/shells/charcell/components/Hint/index.css +16 -0
- package/application/shells/charcell/components/Hint/index.tsx +81 -0
- package/application/shells/charcell/components/HtmlWarnings/index.tsx +45 -0
- package/application/shells/charcell/components/KeyHints/index.css +27 -0
- package/application/shells/charcell/components/KeyHints/index.tsx +106 -0
- package/application/shells/charcell/components/KitCodeBlock/index.css +55 -0
- package/application/shells/charcell/components/KitCodeBlock/index.tsx +18 -0
- package/application/shells/charcell/components/Markdown/index.tsx +16 -0
- package/application/shells/charcell/components/Navigation/index.css +13 -0
- package/application/shells/charcell/components/Navigation/index.tsx +98 -0
- package/application/shells/charcell/components/NavigationSplit/index.css +16 -0
- package/application/shells/charcell/components/NavigationSplit/index.tsx +82 -0
- package/application/shells/charcell/components/OptionControls/index.tsx +93 -0
- package/application/shells/charcell/components/PaneStatus/index.css +7 -0
- package/application/shells/charcell/components/PaneStatus/index.tsx +47 -0
- package/application/shells/charcell/components/PaneWidth/index.tsx +45 -0
- package/application/shells/charcell/components/PreviewGrid/index.css +39 -0
- package/application/shells/charcell/components/PreviewGrid/index.tsx +344 -0
- package/application/shells/charcell/components/PreviewPane/index.css +127 -0
- package/application/shells/charcell/components/PreviewPane/index.tsx +182 -0
- package/application/shells/charcell/components/PromptBar/index.tsx +133 -0
- package/application/shells/charcell/components/ScenarioRow/index.css +36 -0
- package/application/shells/charcell/components/ScenarioRow/index.tsx +71 -0
- package/application/shells/charcell/components/Select/index.tsx +48 -0
- package/application/shells/charcell/components/SlotOutlet/index.css +7 -0
- package/application/shells/charcell/components/SlotOutlet/index.tsx +25 -0
- package/application/shells/charcell/components/StageBar/index.css +7 -0
- package/application/shells/charcell/components/StageBar/index.tsx +109 -0
- package/application/shells/charcell/components/StageSplit/index.css +11 -0
- package/application/shells/charcell/components/StageSplit/index.tsx +99 -0
- package/application/shells/charcell/components/StatusBar/index.tsx +30 -0
- package/application/shells/charcell/components/ViewControls/index.css +15 -0
- package/application/shells/charcell/components/ViewControls/index.tsx +58 -0
- package/application/shells/charcell/components/ViewHeading/index.css +13 -0
- package/application/shells/charcell/components/ViewHeading/index.tsx +8 -0
- package/application/shells/charcell/components/ViewStage/index.css +9 -0
- package/application/shells/charcell/components/ViewStage/index.tsx +25 -0
- package/application/shells/charcell/components/WrapToggle/index.tsx +27 -0
- package/application/shells/charcell/configuration.ts +45 -0
- package/application/shells/charcell/controller-context.ts +27 -0
- package/application/shells/charcell/fit-labels.ts +42 -0
- package/application/shells/charcell/html-warnings.ts +18 -0
- package/application/shells/charcell/index.tsx +82 -0
- package/application/shells/charcell/page-loading.html +57 -0
- package/application/shells/charcell/placement.ts +24 -0
- package/application/shells/charcell/shell-options.ts +23 -0
- package/application/shells/charcell/shell.css +37 -0
- package/application/shells/charcell/shell.tsx +25 -0
- package/application/shells/charcell/split-reset.ts +47 -0
- package/application/shells/charcell/stacked-rows.ts +117 -0
- package/application/shells/charcell/stage-toggles.ts +93 -0
- package/application/types/manifest.d.ts +6 -0
- package/application/types/preview-configuration.d.ts +10 -0
- package/application/types/preview-shell.d.ts +7 -0
- package/application/types/react-registry.d.ts +15 -0
- package/application/types/vue-registry.d.ts +6 -0
- package/dist/build.d.ts +24 -0
- package/dist/build.js +34 -0
- package/dist/check.d.ts +48 -0
- package/dist/check.js +781 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +88 -0
- package/dist/context.d.ts +179 -0
- package/dist/context.js +783 -0
- package/dist/controller/catalog.d.ts +339 -0
- package/dist/controller/catalog.js +367 -0
- package/dist/controller/create-preview-controller.d.ts +532 -0
- package/dist/controller/create-preview-controller.js +1707 -0
- package/dist/controller/editor-tree.d.ts +161 -0
- package/dist/controller/editor-tree.js +373 -0
- package/dist/controller/html-source.d.ts +62 -0
- package/dist/controller/html-source.js +61 -0
- package/dist/controller/index.d.ts +9 -0
- package/dist/controller/index.js +12 -0
- package/dist/controller/persistence.d.ts +191 -0
- package/dist/controller/persistence.js +315 -0
- package/dist/controller/regeneration.d.ts +30 -0
- package/dist/controller/regeneration.js +27 -0
- package/dist/controller/source.d.ts +80 -0
- package/dist/controller/source.js +236 -0
- package/dist/controller/store.d.ts +27 -0
- package/dist/controller/store.js +40 -0
- package/dist/declarations/application/shells/charcell/components/Application/index.d.ts +2 -0
- package/dist/declarations/application/shells/charcell/components/Bar/index.d.ts +11 -0
- package/dist/declarations/application/shells/charcell/components/CodePane/index.d.ts +5 -0
- package/dist/declarations/application/shells/charcell/components/ComponentStage/index.d.ts +2 -0
- package/dist/declarations/application/shells/charcell/components/Control/index.d.ts +9 -0
- package/dist/declarations/application/shells/charcell/components/CopyLink/index.d.ts +1 -0
- package/dist/declarations/application/shells/charcell/components/Description/index.d.ts +5 -0
- package/dist/declarations/application/shells/charcell/components/EditorAddDialog/index.d.ts +6 -0
- package/dist/declarations/application/shells/charcell/components/EditorPanel/index.d.ts +2 -0
- package/dist/declarations/application/shells/charcell/components/EventsRow/index.d.ts +4 -0
- package/dist/declarations/application/shells/charcell/components/HeadingRow/index.d.ts +6 -0
- package/dist/declarations/application/shells/charcell/components/Hint/index.d.ts +11 -0
- package/dist/declarations/application/shells/charcell/components/HtmlWarnings/index.d.ts +5 -0
- package/dist/declarations/application/shells/charcell/components/KeyHints/index.d.ts +2 -0
- package/dist/declarations/application/shells/charcell/components/KitCodeBlock/index.d.ts +5 -0
- package/dist/declarations/application/shells/charcell/components/Markdown/index.d.ts +3 -0
- package/dist/declarations/application/shells/charcell/components/Navigation/index.d.ts +8 -0
- package/dist/declarations/application/shells/charcell/components/NavigationSplit/index.d.ts +7 -0
- package/dist/declarations/application/shells/charcell/components/OptionControls/index.d.ts +7 -0
- package/dist/declarations/application/shells/charcell/components/PaneStatus/index.d.ts +2 -0
- package/dist/declarations/application/shells/charcell/components/PaneWidth/index.d.ts +4 -0
- package/dist/declarations/application/shells/charcell/components/PreviewGrid/index.d.ts +7 -0
- package/dist/declarations/application/shells/charcell/components/PreviewPane/index.d.ts +9 -0
- package/dist/declarations/application/shells/charcell/components/PromptBar/index.d.ts +1 -0
- package/dist/declarations/application/shells/charcell/components/ScenarioRow/index.d.ts +2 -0
- package/dist/declarations/application/shells/charcell/components/Select/index.d.ts +16 -0
- package/dist/declarations/application/shells/charcell/components/SlotOutlet/index.d.ts +5 -0
- package/dist/declarations/application/shells/charcell/components/StageBar/index.d.ts +2 -0
- package/dist/declarations/application/shells/charcell/components/StageSplit/index.d.ts +17 -0
- package/dist/declarations/application/shells/charcell/components/StatusBar/index.d.ts +1 -0
- package/dist/declarations/application/shells/charcell/components/ViewControls/index.d.ts +9 -0
- package/dist/declarations/application/shells/charcell/components/ViewHeading/index.d.ts +4 -0
- package/dist/declarations/application/shells/charcell/components/ViewStage/index.d.ts +4 -0
- package/dist/declarations/application/shells/charcell/components/WrapToggle/index.d.ts +1 -0
- package/dist/declarations/application/shells/charcell/configuration.d.ts +31 -0
- package/dist/declarations/application/shells/charcell/controller-context.d.ts +9 -0
- package/dist/declarations/application/shells/charcell/fit-labels.d.ts +18 -0
- package/dist/declarations/application/shells/charcell/html-warnings.d.ts +5 -0
- package/dist/declarations/application/shells/charcell/index.d.ts +39 -0
- package/dist/declarations/application/shells/charcell/placement.d.ts +7 -0
- package/dist/declarations/application/shells/charcell/shell-options.d.ts +15 -0
- package/dist/declarations/application/shells/charcell/shell.d.ts +9 -0
- package/dist/declarations/application/shells/charcell/split-reset.d.ts +30 -0
- package/dist/declarations/application/shells/charcell/stacked-rows.d.ts +58 -0
- package/dist/declarations/application/shells/charcell/stage-toggles.d.ts +28 -0
- package/dist/declarations/src/controller/catalog.d.ts +339 -0
- package/dist/declarations/src/controller/create-preview-controller.d.ts +532 -0
- package/dist/declarations/src/controller/editor-tree.d.ts +161 -0
- package/dist/declarations/src/controller/html-source.d.ts +62 -0
- package/dist/declarations/src/controller/index.d.ts +9 -0
- package/dist/declarations/src/controller/persistence.d.ts +191 -0
- package/dist/declarations/src/controller/regeneration.d.ts +30 -0
- package/dist/declarations/src/controller/source.d.ts +80 -0
- package/dist/declarations/src/controller/store.d.ts +27 -0
- package/dist/declarations/src/emit/common.d.ts +103 -0
- package/dist/declarations/src/emit/manifest.d.ts +138 -0
- package/dist/declarations/src/emit/source.d.ts +87 -0
- package/dist/declarations/src/lib/jsx-roots.d.ts +22 -0
- package/dist/declarations/src/lib/module-of-source.d.ts +18 -0
- package/dist/declarations/src/lib/naming.d.ts +29 -0
- package/dist/declarations/src/lib/react-spelling.d.ts +68 -0
- package/dist/declarations/src/lib/scenario-story.d.ts +41 -0
- package/dist/declarations/src/lib/slot-fill.d.ts +15 -0
- package/dist/declarations/src/lib/types.d.ts +741 -0
- package/dist/declarations/src/options.d.ts +224 -0
- package/dist/declarations/src/protocol.d.ts +541 -0
- package/dist/declarations/src/react-view.d.ts +22 -0
- package/dist/declarations/src/react.d.ts +17 -0
- package/dist/declarations/src/shell.d.ts +99 -0
- package/dist/declarations/src/views.d.ts +152 -0
- package/dist/development-server.d.ts +121 -0
- package/dist/development-server.js +331 -0
- package/dist/emit/common.d.ts +103 -0
- package/dist/emit/common.js +170 -0
- package/dist/emit/html-source.d.ts +46 -0
- package/dist/emit/html-source.js +322 -0
- package/dist/emit/manifest.d.ts +138 -0
- package/dist/emit/manifest.js +74 -0
- package/dist/emit/react.d.ts +16 -0
- package/dist/emit/react.js +210 -0
- package/dist/emit/scratch.d.ts +40 -0
- package/dist/emit/scratch.js +116 -0
- package/dist/emit/source.d.ts +87 -0
- package/dist/emit/source.js +789 -0
- package/dist/emit/svelte.d.ts +9 -0
- package/dist/emit/svelte.js +177 -0
- package/dist/emit/vue.d.ts +2 -0
- package/dist/emit/vue.js +152 -0
- package/dist/enumerate.d.ts +2 -0
- package/dist/enumerate.js +162 -0
- package/dist/generate.d.ts +43 -0
- package/dist/generate.js +302 -0
- package/dist/html-stylesheet.d.ts +43 -0
- package/dist/html-stylesheet.js +145 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +21 -0
- package/dist/landing.d.ts +16 -0
- package/dist/landing.js +45 -0
- package/dist/lib/exported-types.d.ts +42 -0
- package/dist/lib/exported-types.js +86 -0
- package/dist/lib/html-attributes.d.ts +2 -0
- package/dist/lib/html-attributes.js +111 -0
- package/dist/lib/html-warnings.d.ts +19 -0
- package/dist/lib/html-warnings.js +79 -0
- package/dist/lib/jsx-roots.d.ts +22 -0
- package/dist/lib/jsx-roots.js +243 -0
- package/dist/lib/module-of-source.d.ts +18 -0
- package/dist/lib/module-of-source.js +24 -0
- package/dist/lib/naming.d.ts +29 -0
- package/dist/lib/naming.js +56 -0
- package/dist/lib/parse-type.d.ts +32 -0
- package/dist/lib/parse-type.js +103 -0
- package/dist/lib/react-spelling.d.ts +68 -0
- package/dist/lib/react-spelling.js +197 -0
- package/dist/lib/scenario-story.d.ts +41 -0
- package/dist/lib/scenario-story.js +84 -0
- package/dist/lib/slot-fill.d.ts +15 -0
- package/dist/lib/slot-fill.js +92 -0
- package/dist/lib/template-files.d.ts +14 -0
- package/dist/lib/template-files.js +25 -0
- package/dist/lib/types.d.ts +741 -0
- package/dist/lib/types.js +13 -0
- package/dist/lib/void-elements.d.ts +7 -0
- package/dist/lib/void-elements.js +13 -0
- package/dist/load.d.ts +37 -0
- package/dist/load.js +82 -0
- package/dist/model.d.ts +44 -0
- package/dist/model.js +464 -0
- package/dist/options.d.ts +224 -0
- package/dist/options.js +223 -0
- package/dist/prop-types.d.ts +26 -0
- package/dist/prop-types.js +102 -0
- package/dist/props-from.d.ts +84 -0
- package/dist/props-from.js +510 -0
- package/dist/protocol.d.ts +541 -0
- package/dist/protocol.js +819 -0
- package/dist/react-view.d.ts +22 -0
- package/dist/react-view.js +36 -0
- package/dist/react.d.ts +17 -0
- package/dist/react.js +23 -0
- package/dist/sass-importers.d.ts +26 -0
- package/dist/sass-importers.js +32 -0
- package/dist/shell-acceptance.d.ts +21 -0
- package/dist/shell-acceptance.js +37 -0
- package/dist/shell-page.d.ts +36 -0
- package/dist/shell-page.js +90 -0
- package/dist/shell-peers.d.ts +38 -0
- package/dist/shell-peers.js +61 -0
- package/dist/shell.d.ts +99 -0
- package/dist/shell.js +52 -0
- package/dist/shells/charcell/page.d.ts +8 -0
- package/dist/shells/charcell/page.js +69 -0
- package/dist/story-source.d.ts +113 -0
- package/dist/story-source.js +271 -0
- package/dist/story.d.ts +5 -0
- package/dist/story.js +28 -0
- package/dist/testing.d.ts +218 -0
- package/dist/testing.js +284 -0
- package/dist/views.d.ts +152 -0
- package/dist/views.js +7 -0
- package/dist/vite-configuration.d.ts +114 -0
- package/dist/vite-configuration.js +578 -0
- package/package.json +123 -3
- package/src/build.ts +51 -0
- package/src/check.ts +1062 -0
- package/src/cli.ts +123 -0
- package/src/context.ts +1168 -0
- package/src/controller/catalog.ts +719 -0
- package/src/controller/create-preview-controller.ts +2683 -0
- package/src/controller/editor-tree.ts +530 -0
- package/src/controller/html-source.ts +114 -0
- package/src/controller/index.ts +105 -0
- package/src/controller/persistence.ts +403 -0
- package/src/controller/regeneration.ts +55 -0
- package/src/controller/source.ts +333 -0
- package/src/controller/store.ts +55 -0
- package/src/development-server.ts +442 -0
- package/src/emit/common.ts +201 -0
- package/src/emit/html-source.ts +402 -0
- package/src/emit/manifest.ts +241 -0
- package/src/emit/react.ts +263 -0
- package/src/emit/scratch.ts +175 -0
- package/src/emit/source.ts +1094 -0
- package/src/emit/svelte.ts +230 -0
- package/src/emit/vue.ts +194 -0
- package/src/enumerate.ts +220 -0
- package/src/generate.ts +443 -0
- package/src/html-stylesheet.ts +173 -0
- package/src/index.ts +60 -0
- package/src/landing.ts +58 -0
- package/src/lib/exported-types.ts +114 -0
- package/src/lib/html-attributes.ts +113 -0
- package/src/lib/html-warnings.ts +81 -0
- package/src/lib/jsx-roots.ts +238 -0
- package/src/lib/module-of-source.ts +26 -0
- package/src/lib/naming.ts +65 -0
- package/src/lib/parse-type.ts +143 -0
- package/src/lib/react-spelling.ts +235 -0
- package/src/lib/scenario-story.ts +97 -0
- package/src/lib/slot-fill.ts +102 -0
- package/src/lib/template-files.ts +31 -0
- package/src/lib/types.ts +778 -0
- package/src/lib/void-elements.ts +15 -0
- package/src/load.ts +144 -0
- package/src/model.ts +568 -0
- package/src/options.ts +435 -0
- package/src/prop-types.ts +133 -0
- package/src/props-from.ts +678 -0
- package/src/protocol.ts +1135 -0
- package/src/react-view.ts +45 -0
- package/src/react.ts +34 -0
- package/src/sass-importers.ts +45 -0
- package/src/shell-acceptance.ts +45 -0
- package/src/shell-page.ts +133 -0
- package/src/shell-peers.ts +70 -0
- package/src/shell.ts +194 -0
- package/src/shells/charcell/page.ts +82 -0
- package/src/story-source.ts +391 -0
- package/src/story.ts +42 -0
- package/src/testing.ts +497 -0
- package/src/views.ts +183 -0
- package/src/vite-configuration.ts +675 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Builds the host's controller from what the build aliases in: the generated
|
|
2
|
+
// manifest (which also carries the kit's persistence setting), the kit's
|
|
3
|
+
// preview options and its registered views, plus the shell's own options,
|
|
4
|
+
// which may depend on the catalog, so the catalog is built first.
|
|
5
|
+
// The regeneration endpoint exists on the development server only, so the static
|
|
6
|
+
// build gets a controller without a regeneration transport. The html source
|
|
7
|
+
// is the development server's to print on request; the static build reads
|
|
8
|
+
// what generation printed for each scenario. The iframes load
|
|
9
|
+
// from the URL the preview is served under, so a static build works from a
|
|
10
|
+
// subpath as well as from a domain's root.
|
|
11
|
+
import manifest from '@preview-generated/manifest.json';
|
|
12
|
+
import { options as previewOptions } from '@preview-configuration';
|
|
13
|
+
import { views } from '@preview-views';
|
|
14
|
+
import {
|
|
15
|
+
catalogFromManifest,
|
|
16
|
+
createFetchHtmlSourceTransport,
|
|
17
|
+
createFetchRegenerationTransport,
|
|
18
|
+
createPreviewController,
|
|
19
|
+
createStaticHtmlSourceLoader,
|
|
20
|
+
type PreviewController,
|
|
21
|
+
} from '../../src/controller';
|
|
22
|
+
import type { PreviewShell } from '../../src/shell';
|
|
23
|
+
import { shellOptionsFor } from '../../src/shell-acceptance';
|
|
24
|
+
import { previewBaseUrl } from '../lib/base-url';
|
|
25
|
+
|
|
26
|
+
export function createHostController(
|
|
27
|
+
shell: Pick<PreviewShell, 'options'> = {}
|
|
28
|
+
): PreviewController {
|
|
29
|
+
const catalog = catalogFromManifest(manifest, views);
|
|
30
|
+
return createPreviewController({
|
|
31
|
+
catalog,
|
|
32
|
+
options: previewOptions,
|
|
33
|
+
shellOptions: shellOptionsFor(shell, catalog),
|
|
34
|
+
regeneration: import.meta.env.DEV
|
|
35
|
+
? createFetchRegenerationTransport()
|
|
36
|
+
: undefined,
|
|
37
|
+
...(import.meta.env.DEV
|
|
38
|
+
? { htmlSource: createFetchHtmlSourceTransport() }
|
|
39
|
+
: { scenarioHtmlSources: createStaticHtmlSourceLoader(previewBaseUrl) }),
|
|
40
|
+
persistence: manifest.persistence,
|
|
41
|
+
baseUrl: previewBaseUrl,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// The host page's bootstrap: the one module that reads the build aliases. It
|
|
2
|
+
// loads the shell the kit configured (`@preview-shell`), checks it, builds the
|
|
3
|
+
// controller with the shell's own options (resolved against the catalog when
|
|
4
|
+
// the shell gives them as a function), and mounts the shell into the root
|
|
5
|
+
// element. During development a changed shell is unmounted and the new one
|
|
6
|
+
// mounted over the same controller, so the selection and the editor's tree
|
|
7
|
+
// survive an edit to the shell.
|
|
8
|
+
import shellModule from '@preview-shell';
|
|
9
|
+
import { createHostController } from './bootstrap';
|
|
10
|
+
import { acceptShell, mountShell } from './shell-host';
|
|
11
|
+
|
|
12
|
+
const root = document.getElementById('root')!;
|
|
13
|
+
const shell = acceptShell(shellModule, root);
|
|
14
|
+
|
|
15
|
+
/** The host's controller, built once per page load. */
|
|
16
|
+
export const controller = createHostController(shell);
|
|
17
|
+
|
|
18
|
+
let unmountShell = mountShell(shell, root, controller);
|
|
19
|
+
|
|
20
|
+
/** Unmount the shell; the controller stays. */
|
|
21
|
+
export function unmount(): void {
|
|
22
|
+
unmountShell();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (import.meta.hot) {
|
|
26
|
+
import.meta.hot.accept('@preview-shell', (updated) => {
|
|
27
|
+
if (!updated) return;
|
|
28
|
+
unmountShell();
|
|
29
|
+
unmountShell = mountShell(acceptShell(updated.default, root), root, controller);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// How the host page takes a shell: it checks the module's default export
|
|
2
|
+
// against the contract version it serves, refusing one it cannot serve with a
|
|
3
|
+
// message shown in the page as well as thrown, and mounts an accepted shell
|
|
4
|
+
// into the root element over the controller. Framework-free; the bootstrap
|
|
5
|
+
// (main.ts) calls it on load and again on a hot update of the shell.
|
|
6
|
+
import type { PreviewController, PreviewShell } from '../../src/shell';
|
|
7
|
+
import { shellProblem } from '../../src/shell-acceptance';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The shell a module default-exports, once checked. A module whose export is
|
|
11
|
+
* not a shell this preview serves is refused: the reason is written into
|
|
12
|
+
* `root` for the person looking at the page and thrown for the console.
|
|
13
|
+
*
|
|
14
|
+
* @param candidate - The shell module's default export.
|
|
15
|
+
* @param root - The host page's root element.
|
|
16
|
+
* @returns The shell.
|
|
17
|
+
*/
|
|
18
|
+
export function acceptShell(candidate: unknown, root: HTMLElement): PreviewShell {
|
|
19
|
+
const problem = shellProblem(candidate);
|
|
20
|
+
if (problem !== undefined) {
|
|
21
|
+
const message = `[preview] The configured shell cannot be used. ${problem}`;
|
|
22
|
+
root.replaceChildren();
|
|
23
|
+
const notice = root.ownerDocument.createElement('pre');
|
|
24
|
+
notice.setAttribute('data-preview-shell-problem', '');
|
|
25
|
+
notice.textContent = message;
|
|
26
|
+
root.appendChild(notice);
|
|
27
|
+
throw new Error(message);
|
|
28
|
+
}
|
|
29
|
+
return candidate as PreviewShell;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Mount `shell` into `root`, emptied first, over `controller`.
|
|
34
|
+
*
|
|
35
|
+
* @param shell - An accepted shell.
|
|
36
|
+
* @param root - The host page's root element.
|
|
37
|
+
* @param controller - The preview's controller, which outlives the shell.
|
|
38
|
+
* @returns A function that unmounts the shell and empties the root.
|
|
39
|
+
*/
|
|
40
|
+
export function mountShell(shell: PreviewShell, root: HTMLElement, controller: PreviewController): () => void {
|
|
41
|
+
root.replaceChildren();
|
|
42
|
+
const unmount = shell.mount(root, controller);
|
|
43
|
+
return () => {
|
|
44
|
+
unmount?.();
|
|
45
|
+
root.replaceChildren();
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>scaffold-ui-kit preview</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="./host/main.ts"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// The URL the preview is served under: the directory holding the host page,
|
|
2
|
+
// with the iframes' pages below it. It never comes from the page's location,
|
|
3
|
+
// which a shell may change for its own routing. The development server
|
|
4
|
+
// serves the preview from its root. The static build is served from wherever
|
|
5
|
+
// a file server puts it, so there the URL comes from this module's own: the
|
|
6
|
+
// build puts every script, this one bundled among them, in `assets/`
|
|
7
|
+
// (build.assetsDir in the Vite configuration), one directory below the
|
|
8
|
+
// preview's. The relative path is held in a variable so the build does not
|
|
9
|
+
// take `new URL(..., import.meta.url)` for an asset to bundle.
|
|
10
|
+
const SCRIPT_DIRECTORY_PARENT = '../';
|
|
11
|
+
|
|
12
|
+
/** The URL the preview is served under, ending in `/`. */
|
|
13
|
+
export const previewBaseUrl: string = import.meta.env.DEV
|
|
14
|
+
? import.meta.env.BASE_URL
|
|
15
|
+
: new URL(SCRIPT_DIRECTORY_PARENT, import.meta.url).href;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// The empty code block - aliased in as `@preview-code-block` when the kit
|
|
2
|
+
// names no `codeBlock`: the shell then shows the source itself, and no code
|
|
3
|
+
// iframe loads.
|
|
4
|
+
import type { CodeBlockMount } from '../../src/views';
|
|
5
|
+
|
|
6
|
+
const mount: CodeBlockMount | undefined = undefined;
|
|
7
|
+
export default mount;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// The empty custom-view registry - aliased in as `@preview-views` when the kit
|
|
2
|
+
// registers no `viewsModule`.
|
|
3
|
+
import type { PreviewView } from '../../src/views';
|
|
4
|
+
|
|
5
|
+
export const views: PreviewView[] = [];
|
|
6
|
+
export const viewById = (_id: string): PreviewView | undefined => undefined;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en" data-preview-frame="code">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>preview - code</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="app" data-preview-code></div>
|
|
10
|
+
<script type="module" src="./main.ts"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// Code iframe entry: the kit's own code block (`preview.codeBlock`),
|
|
2
|
+
// showing one target's source of the story, named by `?target=`. The entry
|
|
3
|
+
// loads the kit's setup module for the block's framework (named by
|
|
4
|
+
// `?framework=`, so its stylesheets come first, as in the view iframes), then
|
|
5
|
+
// the theme and canvas, then the block; it mounts the block with the first
|
|
6
|
+
// source the host sends, and hands it each one after through the `updates`
|
|
7
|
+
// it mounts with. The host sends a new source whenever the story, its props
|
|
8
|
+
// or its content change, or the shell's wrapping does, and the option values
|
|
9
|
+
// as it sends a story pane's.
|
|
10
|
+
import type { CodeBlockContent, CodeBlockLanguage, ViewFramework } from '../../../src/views';
|
|
11
|
+
import {
|
|
12
|
+
announcePreviewReady,
|
|
13
|
+
announcePreviewShown,
|
|
14
|
+
CODE_TARGET_PARAMETER,
|
|
15
|
+
PREVIEW_MESSAGE,
|
|
16
|
+
PREVIEW_MOUNT_ELEMENT_ID,
|
|
17
|
+
readPreviewMessage,
|
|
18
|
+
VIEW_FRAMEWORK_PARAMETER,
|
|
19
|
+
} from '../../../src/protocol';
|
|
20
|
+
|
|
21
|
+
const parameters = new URLSearchParams(window.location.search);
|
|
22
|
+
const target = (parameters.get(CODE_TARGET_PARAMETER) ?? 'react') as CodeBlockContent['target'];
|
|
23
|
+
|
|
24
|
+
/** Each target's language, as a highlighter names it. */
|
|
25
|
+
const LANGUAGES: Record<CodeBlockContent['target'], CodeBlockLanguage> = {
|
|
26
|
+
react: 'tsx',
|
|
27
|
+
vue: 'vue',
|
|
28
|
+
svelte: 'svelte',
|
|
29
|
+
html: 'html',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
type SetupFramework = Exclude<ViewFramework, 'dom'>;
|
|
33
|
+
|
|
34
|
+
// Each framework's setup module, loaded only for a block of that framework.
|
|
35
|
+
const SETUP_MODULES: Record<SetupFramework, () => Promise<unknown>> = {
|
|
36
|
+
react: () => import('@preview-setup/react'),
|
|
37
|
+
vue: () => import('@preview-setup/vue'),
|
|
38
|
+
svelte: () => import('@preview-setup/svelte'),
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
function isSetupFramework(value: string | null): value is SetupFramework {
|
|
42
|
+
return value !== null && Object.hasOwn(SETUP_MODULES, value);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let content: CodeBlockContent = { target, language: LANGUAGES[target], wrap: false };
|
|
46
|
+
const listeners = new Set<(content: CodeBlockContent) => void>();
|
|
47
|
+
|
|
48
|
+
async function start(): Promise<void> {
|
|
49
|
+
const framework = parameters.get(VIEW_FRAMEWORK_PARAMETER);
|
|
50
|
+
if (isSetupFramework(framework)) await SETUP_MODULES[framework]();
|
|
51
|
+
const { applyGlobals } = await import('../globals');
|
|
52
|
+
const { default: mount } = await import('@preview-code-block');
|
|
53
|
+
const element = document.getElementById(PREVIEW_MOUNT_ELEMENT_ID);
|
|
54
|
+
let mounted = false;
|
|
55
|
+
|
|
56
|
+
window.addEventListener('message', (event: MessageEvent) => {
|
|
57
|
+
const message = readPreviewMessage(event.data);
|
|
58
|
+
if (!message) return;
|
|
59
|
+
if (message.type === PREVIEW_MESSAGE.globals) applyGlobals(message.options);
|
|
60
|
+
if (message.type !== PREVIEW_MESSAGE.code) return;
|
|
61
|
+
content = {
|
|
62
|
+
target,
|
|
63
|
+
language: LANGUAGES[target],
|
|
64
|
+
source: message.source,
|
|
65
|
+
reason: message.reason,
|
|
66
|
+
wrap: message.wrap === true,
|
|
67
|
+
};
|
|
68
|
+
if (!mounted && mount && element) {
|
|
69
|
+
mounted = true;
|
|
70
|
+
mount(element, content, {
|
|
71
|
+
subscribe(listener) {
|
|
72
|
+
listeners.add(listener);
|
|
73
|
+
return () => {
|
|
74
|
+
listeners.delete(listener);
|
|
75
|
+
};
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
// The block, once painted, lets the host show the pane.
|
|
79
|
+
announcePreviewShown();
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
for (const listener of listeners) listener(content);
|
|
83
|
+
});
|
|
84
|
+
// Signal readiness so the host sends the options and the source (an iframe
|
|
85
|
+
// re-handshakes after each reload).
|
|
86
|
+
announcePreviewReady();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
void start();
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
// Apply the kit-authored preview options to the preview document. Each option
|
|
2
|
+
// declares its effect in the kit's preview block (`apply`); this module is the
|
|
3
|
+
// single function that executes those effects on the iframe DOM - a generic loop
|
|
4
|
+
// over the registry, so adding an option needs no change here. Everything
|
|
5
|
+
// kit-specific (theme list, the page background, any wrapper the kit puts
|
|
6
|
+
// around the mount, the DOM-name prefix) comes from the kit's preview block,
|
|
7
|
+
// served as `@preview-configuration`; this module holds no kit vocabulary.
|
|
8
|
+
import {
|
|
9
|
+
options as previewOptions,
|
|
10
|
+
canvas as previewCanvas,
|
|
11
|
+
themes as previewThemes,
|
|
12
|
+
mount as previewMount,
|
|
13
|
+
viewCanvas as previewViewCanvas,
|
|
14
|
+
} from '@preview-configuration';
|
|
15
|
+
import {
|
|
16
|
+
optionsById as buildOptionsById,
|
|
17
|
+
optionDefaults,
|
|
18
|
+
themeHref,
|
|
19
|
+
type ApplyTarget,
|
|
20
|
+
type OptionValue,
|
|
21
|
+
type PreviewOption,
|
|
22
|
+
type PreviewMountNode,
|
|
23
|
+
} from '../../src/options';
|
|
24
|
+
import { announcePreviewOptions } from '../../src/protocol';
|
|
25
|
+
import { previewBaseUrl } from '../lib/base-url';
|
|
26
|
+
|
|
27
|
+
const canvas = previewCanvas;
|
|
28
|
+
const optionsById = buildOptionsById(previewOptions);
|
|
29
|
+
announcePreviewOptions(Object.values(optionsById));
|
|
30
|
+
|
|
31
|
+
// The code iframe (its mount marked in code.html) shows the kit's code block,
|
|
32
|
+
// not a component: it gets no mount wrapper and no story option, only the
|
|
33
|
+
// global options that set the page's look, and its mount fills the frame.
|
|
34
|
+
const codeFrame =
|
|
35
|
+
document.getElementById('app')?.hasAttribute('data-preview-code') ?? false;
|
|
36
|
+
const defaultTheme = previewThemes[0]?.id;
|
|
37
|
+
const themeIds = new Set(previewThemes.map((theme) => theme.id));
|
|
38
|
+
|
|
39
|
+
const THEME_LINK_ID = `${canvas.namePrefix}-theme-css`;
|
|
40
|
+
|
|
41
|
+
function setTheme(theme: string): void {
|
|
42
|
+
const id = themeIds.has(theme) ? theme : defaultTheme;
|
|
43
|
+
if (!id) return;
|
|
44
|
+
const href = themeHref(id, previewBaseUrl);
|
|
45
|
+
let link = document.getElementById(THEME_LINK_ID) as HTMLLinkElement | null;
|
|
46
|
+
if (!link) {
|
|
47
|
+
link = document.createElement('link');
|
|
48
|
+
link.id = THEME_LINK_ID;
|
|
49
|
+
link.rel = 'stylesheet';
|
|
50
|
+
document.head.appendChild(link);
|
|
51
|
+
}
|
|
52
|
+
if (link.getAttribute('href') !== href) link.setAttribute('href', href);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ── Mount wrapper ────────────────────────────────────────────────────────────
|
|
56
|
+
// A kit may declare a wrapper subtree (`mount`) rendered around the
|
|
57
|
+
// `#app` mount - framework-agnostic DOM the preview builds once at init and
|
|
58
|
+
// reparents `#app` into. The preview knows nothing of what the nodes mean (a
|
|
59
|
+
// kit's surface/box vocabulary lives entirely in its `mount` + CSS); it
|
|
60
|
+
// only records each `ref` so option applies can target a wrapper node. Omitted,
|
|
61
|
+
// `#app` stays a direct child of `<body>`.
|
|
62
|
+
const WRAPPER_MARK = `data-${canvas.namePrefix}-preview-wrapper`;
|
|
63
|
+
|
|
64
|
+
/** ref name → element, for `apply.target = { ref }` resolution. */
|
|
65
|
+
const referencedElements: Record<string, HTMLElement> = {};
|
|
66
|
+
|
|
67
|
+
function buildNode(node: PreviewMountNode): {
|
|
68
|
+
root: HTMLElement;
|
|
69
|
+
mount: HTMLElement | null;
|
|
70
|
+
} {
|
|
71
|
+
const element = document.createElement(node.tag ?? 'div');
|
|
72
|
+
if (node.attributes)
|
|
73
|
+
for (const [name, value] of Object.entries(node.attributes))
|
|
74
|
+
element.setAttribute(name, value);
|
|
75
|
+
if (node.classes?.length) element.classList.add(...node.classes);
|
|
76
|
+
if (node.style) element.setAttribute('style', node.style);
|
|
77
|
+
if (node.ref) referencedElements[node.ref] = element;
|
|
78
|
+
let mount = node.mount ? element : null;
|
|
79
|
+
if (node.children)
|
|
80
|
+
for (const child of node.children) {
|
|
81
|
+
const built = buildNode(child);
|
|
82
|
+
element.appendChild(built.root);
|
|
83
|
+
// A deeper `mount: true` wins over an ancestor's (there is exactly one).
|
|
84
|
+
if (built.mount) mount = built.mount;
|
|
85
|
+
}
|
|
86
|
+
return { root: element, mount };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function buildMountWrapper(): void {
|
|
90
|
+
if (!previewMount) return;
|
|
91
|
+
// The mount wrapper is the component stage. The custom-view iframe (the
|
|
92
|
+
// preview's own `?view=` entry) hosts a view, not a component mount, and
|
|
93
|
+
// shares this module only for theme + option apply - it gets no wrapper.
|
|
94
|
+
if (new URLSearchParams(location.search).has('view') || codeFrame) return;
|
|
95
|
+
const mountElement = document.getElementById('app');
|
|
96
|
+
// Already wrapped (a redundant init on HMR) - leave it be.
|
|
97
|
+
if (!mountElement || mountElement.closest(`[${WRAPPER_MARK}]`)) return;
|
|
98
|
+
const parent = mountElement.parentElement ?? document.body;
|
|
99
|
+
const { root, mount } = buildNode(previewMount);
|
|
100
|
+
root.setAttribute(WRAPPER_MARK, '');
|
|
101
|
+
// Slot the wrapper in where `#app` sat, then move `#app` into the mount node.
|
|
102
|
+
parent.insertBefore(root, mountElement);
|
|
103
|
+
(mount ?? root).appendChild(mountElement);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ── Canvas ───────────────────────────────────────────────────────────────────
|
|
107
|
+
// One preview stylesheet, injected once: the page (`body`) paints the kit's page
|
|
108
|
+
// background only if the kit states one - a kit whose own stylesheet carries
|
|
109
|
+
// its page canvas omits `pageBackground` and the document is simply that
|
|
110
|
+
// page - and any wrapper geometry the kit wants (surface fills, insets,
|
|
111
|
+
// padding) rides in `canvas.extraCss`, keyed off the nodes the kit
|
|
112
|
+
// declares in `mount`. The preview authors none of it.
|
|
113
|
+
const PREVIEW_STYLE_ID = `${canvas.namePrefix}-preview-canvas`;
|
|
114
|
+
const classAttribute = `data-${canvas.namePrefix}-preview-class`;
|
|
115
|
+
|
|
116
|
+
// A scroll that reaches a frame's edge ends there: chained on to the host, it
|
|
117
|
+
// would scroll or bounce the shell around the pane.
|
|
118
|
+
const OVERSCROLL = `html { overscroll-behavior: contain; }`;
|
|
119
|
+
|
|
120
|
+
function ensureCanvasStyle(): void {
|
|
121
|
+
if (document.getElementById(PREVIEW_STYLE_ID)) return;
|
|
122
|
+
// A custom view's iframe has its own canvas: the kit's `viewCanvas`,
|
|
123
|
+
// else the page background alone, since the component canvas's extra CSS
|
|
124
|
+
// styles a mount wrapper a view does not have.
|
|
125
|
+
const pageCanvas = new URLSearchParams(location.search).has('view')
|
|
126
|
+
? (previewViewCanvas ?? { pageBackground: canvas.pageBackground })
|
|
127
|
+
: canvas;
|
|
128
|
+
const style = document.createElement('style');
|
|
129
|
+
style.id = PREVIEW_STYLE_ID;
|
|
130
|
+
if (codeFrame) {
|
|
131
|
+
// The page background alone, under a mount that fills the frame.
|
|
132
|
+
style.textContent = [
|
|
133
|
+
OVERSCROLL,
|
|
134
|
+
`html, body, #app { height: 100%; }`,
|
|
135
|
+
`body { margin: 0; }`,
|
|
136
|
+
...(canvas.pageBackground
|
|
137
|
+
? [`body { background: ${canvas.pageBackground}; }`]
|
|
138
|
+
: []),
|
|
139
|
+
].join('\n');
|
|
140
|
+
document.head.appendChild(style);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
style.textContent = [
|
|
144
|
+
OVERSCROLL,
|
|
145
|
+
`body { min-height: 100vh; }`,
|
|
146
|
+
...(pageCanvas.pageBackground
|
|
147
|
+
? [`body { background: ${pageCanvas.pageBackground}; }`]
|
|
148
|
+
: []),
|
|
149
|
+
...(pageCanvas.extraCss ? [pageCanvas.extraCss] : []),
|
|
150
|
+
].join('\n');
|
|
151
|
+
document.head.appendChild(style);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Build the wrapper, load the default theme + canvas stylesheet, and paint the
|
|
155
|
+
// options' defaults - all at module init, before the host sends its globals, so
|
|
156
|
+
// the first paint is fully styled. globals.ts runs at import, ahead of each
|
|
157
|
+
// entry's `createRoot`/`mount`, and `getElementById('app')` resolves after the
|
|
158
|
+
// reparent.
|
|
159
|
+
if (defaultTheme) setTheme(defaultTheme);
|
|
160
|
+
buildMountWrapper();
|
|
161
|
+
ensureCanvasStyle();
|
|
162
|
+
applyGlobals(optionDefaults(previewOptions));
|
|
163
|
+
|
|
164
|
+
// Story links are illustrations. A real navigation would load whatever the
|
|
165
|
+
// static server serves at that path INSIDE the pane - for the host's own
|
|
166
|
+
// document, the preview recursively - so every non-hash link click is made
|
|
167
|
+
// inert. Capture-phase, so a story component that stops propagation cannot
|
|
168
|
+
// reintroduce the navigation; preventDefault kills only the navigation, the
|
|
169
|
+
// component's own click handling still runs.
|
|
170
|
+
document.addEventListener(
|
|
171
|
+
'click',
|
|
172
|
+
(event) => {
|
|
173
|
+
const target = event.target instanceof Element ? event.target : null;
|
|
174
|
+
const anchor = target?.closest('a[href]');
|
|
175
|
+
if (!anchor) return;
|
|
176
|
+
const href = anchor.getAttribute('href') ?? '';
|
|
177
|
+
if (href.startsWith('#')) return;
|
|
178
|
+
event.preventDefault();
|
|
179
|
+
},
|
|
180
|
+
{ capture: true }
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
function elementForTarget(target: ApplyTarget): HTMLElement | null {
|
|
184
|
+
if (target === 'root') return document.documentElement;
|
|
185
|
+
if (target === 'body') return document.body;
|
|
186
|
+
return referencedElements[target.ref] ?? null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** An option value as an attribute or property value: `false`, empty or
|
|
190
|
+
* unset clears it (`undefined`); anything else is its string form. */
|
|
191
|
+
function settableValue(
|
|
192
|
+
value: OptionValue | null | undefined
|
|
193
|
+
): string | undefined {
|
|
194
|
+
if (value === false || value === '' || value == null) return undefined;
|
|
195
|
+
return String(value);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function setOrRemoveAttribute(
|
|
199
|
+
element: HTMLElement,
|
|
200
|
+
name: string,
|
|
201
|
+
value: OptionValue
|
|
202
|
+
): void {
|
|
203
|
+
const settable = settableValue(value);
|
|
204
|
+
if (settable === undefined) element.removeAttribute(name);
|
|
205
|
+
else element.setAttribute(name, settable);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function applyOne(option: PreviewOption, value: OptionValue): void {
|
|
209
|
+
const apply = option.apply;
|
|
210
|
+
switch (apply.type) {
|
|
211
|
+
case 'root-attribute': {
|
|
212
|
+
const element = elementForTarget(apply.target ?? 'root');
|
|
213
|
+
if (!element) break;
|
|
214
|
+
if (apply.omitAtDefault === true && value === option.default) {
|
|
215
|
+
element.removeAttribute(apply.attribute);
|
|
216
|
+
} else {
|
|
217
|
+
setOrRemoveAttribute(element, apply.attribute, value);
|
|
218
|
+
}
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
case 'stylesheet':
|
|
222
|
+
if (typeof value === 'string' && value) setTheme(value);
|
|
223
|
+
break;
|
|
224
|
+
case 'class': {
|
|
225
|
+
const element = elementForTarget(apply.target ?? apply.on ?? 'root');
|
|
226
|
+
if (!element) break;
|
|
227
|
+
if (apply.className)
|
|
228
|
+
element.classList.toggle(apply.className, Boolean(value));
|
|
229
|
+
else setOrRemoveAttribute(element, classAttribute, value);
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
case 'css-var': {
|
|
233
|
+
const element = elementForTarget(apply.target ?? 'root');
|
|
234
|
+
if (!element) break;
|
|
235
|
+
const settable = settableValue(value);
|
|
236
|
+
if (settable === undefined) element.style.removeProperty(apply.var);
|
|
237
|
+
else element.style.setProperty(apply.var, settable);
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** Apply an options payload (id → value) to the preview document. Unknown ids and
|
|
244
|
+
* ids not in the payload are ignored, so partial payloads are safe. */
|
|
245
|
+
export function applyGlobals(
|
|
246
|
+
options: Record<string, OptionValue> | undefined
|
|
247
|
+
): void {
|
|
248
|
+
if (!options) return;
|
|
249
|
+
for (const [id, value] of Object.entries(options)) {
|
|
250
|
+
const option = optionsById[id];
|
|
251
|
+
if (option && !(codeFrame && option.scope === 'story'))
|
|
252
|
+
applyOne(option, value);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// Html preview entry. Shows the markup a story renders to, as the host sends
|
|
2
|
+
// it, under what every story pane has: the kit's theme stylesheet, its canvas,
|
|
3
|
+
// its mount wrapper and its option values (globals.ts), with the stylesheets
|
|
4
|
+
// the kit lists for it (`htmlStylesheets`) and the styles the kit's build
|
|
5
|
+
// wrote for the components the markup renders, which arrive with it. No
|
|
6
|
+
// framework loads and no setup module runs: the markup is the kit's html at
|
|
7
|
+
// rest, and the pane runs none of the story's behavior. Html mode writes a
|
|
8
|
+
// declared event as a comment, so it does nothing; markup that carries
|
|
9
|
+
// behavior of its own (a script, an inline handler, a `javascript:` URL) has
|
|
10
|
+
// it taken out before it shows, and a form's submission is stopped, as
|
|
11
|
+
// globals.ts stops a link's navigation. While the next markup is printed the
|
|
12
|
+
// pane keeps what it shows; a story with none shows why.
|
|
13
|
+
import '@preview-html-stylesheets';
|
|
14
|
+
import { applyGlobals } from '../globals';
|
|
15
|
+
import {
|
|
16
|
+
announcePreviewReady,
|
|
17
|
+
announcePreviewShown,
|
|
18
|
+
PREVIEW_MESSAGE,
|
|
19
|
+
PREVIEW_MOUNT_ELEMENT_ID,
|
|
20
|
+
readPreviewMessage,
|
|
21
|
+
reportContentSize,
|
|
22
|
+
} from '../../../src/protocol';
|
|
23
|
+
|
|
24
|
+
const mount = document.getElementById(PREVIEW_MOUNT_ELEMENT_ID)!;
|
|
25
|
+
|
|
26
|
+
/** The attribute the pane marks its note with, when a story has no html. */
|
|
27
|
+
const UNAVAILABLE_ATTRIBUTE = 'data-preview-html-unavailable';
|
|
28
|
+
|
|
29
|
+
/** Elements that would run code or load a document of their own. */
|
|
30
|
+
const ACTIVE_ELEMENTS = 'script, iframe, object, embed, frame, frameset';
|
|
31
|
+
|
|
32
|
+
/** Attributes whose value a browser may run as a script. */
|
|
33
|
+
const URL_ATTRIBUTES = new Set(['href', 'src', 'action', 'formaction', 'xlink:href']);
|
|
34
|
+
|
|
35
|
+
/** Take out of `fragment` whatever would run: scripts and embedded
|
|
36
|
+
* documents, inline event handlers and `javascript:` URLs. */
|
|
37
|
+
function makeInert(fragment: DocumentFragment): void {
|
|
38
|
+
for (const element of fragment.querySelectorAll(ACTIVE_ELEMENTS)) element.remove();
|
|
39
|
+
for (const element of fragment.querySelectorAll('*')) {
|
|
40
|
+
for (const attribute of [...element.attributes]) {
|
|
41
|
+
const name = attribute.name.toLowerCase();
|
|
42
|
+
const runs =
|
|
43
|
+
name.startsWith('on') ||
|
|
44
|
+
(URL_ATTRIBUTES.has(name) && /^\s*javascript:/i.test(attribute.value));
|
|
45
|
+
if (runs) element.removeAttribute(attribute.name);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** The element the styles of the shown markup's components are set in,
|
|
51
|
+
* after the theme's and the kit's stylesheets. */
|
|
52
|
+
const componentStyle = document.createElement('style');
|
|
53
|
+
componentStyle.setAttribute('data-preview-html-components', '');
|
|
54
|
+
document.head.appendChild(componentStyle);
|
|
55
|
+
|
|
56
|
+
function showMarkup(markup: string, stylesheet: string): void {
|
|
57
|
+
if (componentStyle.textContent !== stylesheet) componentStyle.textContent = stylesheet;
|
|
58
|
+
const template = document.createElement('template');
|
|
59
|
+
template.innerHTML = markup;
|
|
60
|
+
makeInert(template.content);
|
|
61
|
+
mount.replaceChildren(template.content);
|
|
62
|
+
mount.removeAttribute('aria-busy');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function showReason(reason: string): void {
|
|
66
|
+
const note = document.createElement('p');
|
|
67
|
+
note.setAttribute(UNAVAILABLE_ATTRIBUTE, '');
|
|
68
|
+
note.textContent = reason;
|
|
69
|
+
mount.replaceChildren(note);
|
|
70
|
+
mount.removeAttribute('aria-busy');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// A form in the markup is an illustration: submitting it would load whatever
|
|
74
|
+
// its action names inside the pane.
|
|
75
|
+
document.addEventListener('submit', (event) => event.preventDefault(), { capture: true });
|
|
76
|
+
|
|
77
|
+
window.addEventListener('message', (event: MessageEvent) => {
|
|
78
|
+
const message = readPreviewMessage(event.data);
|
|
79
|
+
if (!message) return;
|
|
80
|
+
if (message.type === PREVIEW_MESSAGE.globals) applyGlobals(message.options);
|
|
81
|
+
else if (message.type === PREVIEW_MESSAGE.markup) {
|
|
82
|
+
if (typeof message.markup === 'string')
|
|
83
|
+
showMarkup(message.markup, typeof message.stylesheet === 'string' ? message.stylesheet : '');
|
|
84
|
+
else if (typeof message.reason === 'string') showReason(message.reason);
|
|
85
|
+
else mount.setAttribute('aria-busy', 'true');
|
|
86
|
+
// The first markup or reason, once painted, lets the host show the pane.
|
|
87
|
+
if (typeof message.markup === 'string' || typeof message.reason === 'string') announcePreviewShown();
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
announcePreviewReady();
|
|
92
|
+
// The story's size reaches the host on each change, so a shell can size the pane to it.
|
|
93
|
+
reportContentSize(mount);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>preview - html</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="app"></div>
|
|
10
|
+
<script type="module" src="./main.ts"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|