@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,45 @@
|
|
|
1
|
+
// A custom view written as a React render function, adapted to the
|
|
2
|
+
// framework-neutral view contract (views.ts): it renders into its own React
|
|
3
|
+
// root and re-renders with each new context, so the view's own state lasts
|
|
4
|
+
// across option changes.
|
|
5
|
+
|
|
6
|
+
import type { ReactNode } from 'react';
|
|
7
|
+
import { createRoot } from 'react-dom/client';
|
|
8
|
+
import type { RenderedView, ViewContext } from './views.js';
|
|
9
|
+
|
|
10
|
+
/** A React view's render function: the view's element tree for a context. */
|
|
11
|
+
export type ReactViewRender = (context: ViewContext) => ReactNode;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Make a view's `framework` and `mount` from a React render function. The
|
|
15
|
+
* view iframe then loads the kit's React setup module before mounting it.
|
|
16
|
+
*
|
|
17
|
+
* @param render - Renders the view for a context; called again with each new one.
|
|
18
|
+
* @returns The view's `framework` (`'react'`) and `mount`, to spread into its entry.
|
|
19
|
+
* @example
|
|
20
|
+
* ```tsx
|
|
21
|
+
* export const views: PreviewView[] = [
|
|
22
|
+
* {
|
|
23
|
+
* id: 'foundations',
|
|
24
|
+
* label: 'Foundations',
|
|
25
|
+
* ...reactView((context) => <Foundations theme={String(context.options.theme)} />),
|
|
26
|
+
* },
|
|
27
|
+
* ];
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export function reactView(
|
|
31
|
+
render: ReactViewRender
|
|
32
|
+
): Required<Pick<RenderedView, 'framework' | 'mount'>> {
|
|
33
|
+
return {
|
|
34
|
+
framework: 'react',
|
|
35
|
+
mount(element, context, updates) {
|
|
36
|
+
const root = createRoot(element);
|
|
37
|
+
root.render(render(context));
|
|
38
|
+
const stop = updates?.subscribe((next) => root.render(render(next)));
|
|
39
|
+
return () => {
|
|
40
|
+
stop?.();
|
|
41
|
+
root.unmount();
|
|
42
|
+
};
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
package/src/react.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// React bindings for the preview controller: one hook that reads a slice of
|
|
2
|
+
// its state and re-renders when that slice changes. A shell written in React
|
|
3
|
+
// keeps the controller wherever it likes (a context of its own, a prop) and
|
|
4
|
+
// passes it in; the bindings hold nothing of their own.
|
|
5
|
+
|
|
6
|
+
import { useSyncExternalStore } from 'react';
|
|
7
|
+
import type { PreviewController, PreviewState } from './controller/index.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Read a slice of the controller's state. The component re-renders when the
|
|
11
|
+
* slice changes by identity, so `selector` returns a field of the state (or
|
|
12
|
+
* a primitive derived from it) rather than a fresh object or array; derive
|
|
13
|
+
* those from the slice afterwards.
|
|
14
|
+
*
|
|
15
|
+
* @param controller - The controller to read.
|
|
16
|
+
* @param selector - Picks the slice; the whole state when omitted.
|
|
17
|
+
* @returns The slice.
|
|
18
|
+
* @example
|
|
19
|
+
* ```tsx
|
|
20
|
+
* const scenario = usePreview(controller, (state) => state.scenario);
|
|
21
|
+
* const panes = usePreview(controller, (state) => state.targets.panes);
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export function usePreview<Selected = PreviewState>(
|
|
25
|
+
controller: PreviewController,
|
|
26
|
+
selector: (state: PreviewState) => Selected = (state) =>
|
|
27
|
+
state as unknown as Selected
|
|
28
|
+
): Selected {
|
|
29
|
+
return useSyncExternalStore(
|
|
30
|
+
controller.subscribe,
|
|
31
|
+
() => selector(controller.getState()),
|
|
32
|
+
() => selector(controller.getState())
|
|
33
|
+
);
|
|
34
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// The Sass importers the preview compiles a kit's SCSS with, in the html
|
|
2
|
+
// pane's stylesheets and in Vite: the kit's own `pkg:` URLs resolve as the
|
|
3
|
+
// kit's build resolves them.
|
|
4
|
+
|
|
5
|
+
import { createRequire } from 'node:module';
|
|
6
|
+
import type { KitPackageImporter } from 'scaffold-ui-kit';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The Sass importers the preview compiles a kit's SCSS with: one per
|
|
10
|
+
* package of the kit that resolves its own `pkg:` URLs, as the kit's build
|
|
11
|
+
* does. Sass's own package importer is left to the copy of Sass that
|
|
12
|
+
* compiles.
|
|
13
|
+
*
|
|
14
|
+
* @param kitRoot - The kit root.
|
|
15
|
+
* @param packageDirectories - The kit's other packages' directories, absolute,
|
|
16
|
+
* for a kit built into one package per target.
|
|
17
|
+
* @returns The importers, the kit root's first; none for a package whose
|
|
18
|
+
* package.json names no package.
|
|
19
|
+
*/
|
|
20
|
+
export function kitImporters(
|
|
21
|
+
kitRoot: string,
|
|
22
|
+
packageDirectories: readonly string[] = []
|
|
23
|
+
): KitPackageImporter[] {
|
|
24
|
+
const { kitPackageImporter } = createRequire(import.meta.url)(
|
|
25
|
+
'scaffold-ui-kit'
|
|
26
|
+
) as typeof import('scaffold-ui-kit');
|
|
27
|
+
return [kitRoot, ...packageDirectories]
|
|
28
|
+
.map((directory) => kitPackageImporter(directory))
|
|
29
|
+
.filter((importer) => importer !== undefined);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The directories of a kit's packages besides the kit root, for
|
|
34
|
+
* {@link kitImporters}: none for a kit built as one package.
|
|
35
|
+
*
|
|
36
|
+
* @param context - The preview context's `kitPackages`.
|
|
37
|
+
* @returns The directories, absolute.
|
|
38
|
+
*/
|
|
39
|
+
export function kitPackageDirectories(context: {
|
|
40
|
+
kitPackages?: Array<{ directoryAbsolute: string }>;
|
|
41
|
+
}): string[] {
|
|
42
|
+
return (context.kitPackages ?? []).map(
|
|
43
|
+
({ directoryAbsolute }) => directoryAbsolute
|
|
44
|
+
);
|
|
45
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// How the preview takes a shell: whether a module's default export is a
|
|
2
|
+
// shell this preview serves, and the shell's options for a kit. The host page
|
|
3
|
+
// uses these; a shell does not. Browser-safe.
|
|
4
|
+
|
|
5
|
+
import type { PreviewCatalog } from './controller/index.js';
|
|
6
|
+
import type { PreviewShellOption } from './options.js';
|
|
7
|
+
import { PREVIEW_SHELL_API_VERSION, type PreviewShell } from './shell.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A shell's own options for a kit: its `options`, called with the catalog
|
|
11
|
+
* when it is a function.
|
|
12
|
+
*
|
|
13
|
+
* @param shell - The shell.
|
|
14
|
+
* @param catalog - The kit's catalog.
|
|
15
|
+
* @returns The options, none when the shell declares none.
|
|
16
|
+
*/
|
|
17
|
+
export function shellOptionsFor(
|
|
18
|
+
shell: Pick<PreviewShell, 'options'>,
|
|
19
|
+
catalog: PreviewCatalog
|
|
20
|
+
): PreviewShellOption[] {
|
|
21
|
+
const options = shell.options;
|
|
22
|
+
return typeof options === 'function' ? options(catalog) : (options ?? []);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Why `candidate` cannot serve as this preview's shell: it is not a shell, or
|
|
27
|
+
* it was written against another contract version. The message names both
|
|
28
|
+
* versions.
|
|
29
|
+
*
|
|
30
|
+
* @param candidate - A shell module's default export.
|
|
31
|
+
* @returns The problem, or `undefined` when the candidate is a shell this preview serves.
|
|
32
|
+
*/
|
|
33
|
+
export function shellProblem(candidate: unknown): string | undefined {
|
|
34
|
+
if (typeof candidate !== 'object' || candidate === null) {
|
|
35
|
+
return 'The shell module does not default-export a shell: expected an object made with defineShell.';
|
|
36
|
+
}
|
|
37
|
+
const shell = candidate as Partial<PreviewShell>;
|
|
38
|
+
if (typeof shell.mount !== 'function') {
|
|
39
|
+
return 'The shell module does not default-export a shell: it has no mount function.';
|
|
40
|
+
}
|
|
41
|
+
if (shell.apiVersion !== PREVIEW_SHELL_API_VERSION) {
|
|
42
|
+
return `This preview serves shell API version ${PREVIEW_SHELL_API_VERSION}, and the shell declares version ${String(shell.apiVersion)}.`;
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// The host page's start, as the shell's page module gives it: the module is
|
|
2
|
+
// loaded in Node, its `head` and `root` are worked out once, and the files
|
|
3
|
+
// they reference are given addresses the development server or the static
|
|
4
|
+
// build serves.
|
|
5
|
+
|
|
6
|
+
import { dirname, isAbsolute, relative, resolve, sep } from 'node:path';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import type { PreviewContext } from './context.js';
|
|
9
|
+
import type { ShellPage, ShellPageMarkup } from './shell.js';
|
|
10
|
+
|
|
11
|
+
/** The host page shipped with the preview, which a shell's page is written
|
|
12
|
+
* into. */
|
|
13
|
+
export const HOST_PAGE = resolve(
|
|
14
|
+
dirname(fileURLToPath(import.meta.url)),
|
|
15
|
+
'../application/index.html'
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
/** A shell's page, worked out: what goes where in the host page. */
|
|
19
|
+
export interface ResolvedShellPage {
|
|
20
|
+
/** Markup for the end of the page's `<head>`; empty with none. */
|
|
21
|
+
head: string;
|
|
22
|
+
/** Markup for the page's root element; empty with none. */
|
|
23
|
+
root: string;
|
|
24
|
+
/** The directories of the files the markup references, which the
|
|
25
|
+
* development server has to be allowed to serve. */
|
|
26
|
+
assetDirectories: string[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Where the page is going. */
|
|
30
|
+
export interface ShellPageTarget {
|
|
31
|
+
/** The host page's absolute path. */
|
|
32
|
+
hostPage: string;
|
|
33
|
+
/** Whether the development server serves the page; the static build
|
|
34
|
+
* writes it otherwise. */
|
|
35
|
+
serving: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Load the shell's page module and work out its markup.
|
|
40
|
+
*
|
|
41
|
+
* @param context - The kit's preview context, for the page module and the
|
|
42
|
+
* importer that loads it.
|
|
43
|
+
* @param target - Where the page is going.
|
|
44
|
+
* @returns The page's markup and the directories its files are in, or
|
|
45
|
+
* `undefined` when the shell has no page.
|
|
46
|
+
* @throws When the module's default export is not a shell page, a part of
|
|
47
|
+
* it is neither markup nor a function, a function gives something other
|
|
48
|
+
* than markup, `asset` is given a path that is not absolute, or the module
|
|
49
|
+
* cannot be loaded.
|
|
50
|
+
*/
|
|
51
|
+
export async function loadShellPage(
|
|
52
|
+
context: Pick<PreviewContext, 'shellPageAbsolute' | 'importModule'>,
|
|
53
|
+
target: ShellPageTarget
|
|
54
|
+
): Promise<ResolvedShellPage | undefined> {
|
|
55
|
+
const modulePath = context.shellPageAbsolute;
|
|
56
|
+
if (modulePath === undefined) return undefined;
|
|
57
|
+
const fail = (problem: string): never => {
|
|
58
|
+
throw new Error(`[preview] The shell page ${modulePath} ${problem}`);
|
|
59
|
+
};
|
|
60
|
+
let loaded: { default?: unknown } | null | undefined;
|
|
61
|
+
try {
|
|
62
|
+
loaded = (await context.importModule(modulePath)) as typeof loaded;
|
|
63
|
+
} catch (error) {
|
|
64
|
+
return fail(`could not be loaded: ${(error as Error).message}`);
|
|
65
|
+
}
|
|
66
|
+
const page = loaded?.default as ShellPage | undefined;
|
|
67
|
+
if (typeof page !== 'object' || page === null || Array.isArray(page)) {
|
|
68
|
+
return fail(
|
|
69
|
+
'must default-export a shell page, as defineShellPage({ head, root }) gives.'
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
const unknown = Object.keys(page).filter(
|
|
73
|
+
(key) => key !== 'head' && key !== 'root'
|
|
74
|
+
);
|
|
75
|
+
if (unknown.length > 0) {
|
|
76
|
+
return fail(
|
|
77
|
+
`has the unknown key '${unknown[0]}'; a shell page takes head and root.`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
const assetDirectories = new Set<string>();
|
|
81
|
+
const asset = (file: string): string => {
|
|
82
|
+
if (typeof file !== 'string' || !isAbsolute(file)) {
|
|
83
|
+
return fail(
|
|
84
|
+
`called asset(${JSON.stringify(file)}); asset takes a file's absolute path.`
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
assetDirectories.add(dirname(file));
|
|
88
|
+
if (target.serving) {
|
|
89
|
+
// The development server serves a file outside its root by its
|
|
90
|
+
// absolute path. Each segment is encoded, so a `#`, a `?` or a space
|
|
91
|
+
// in a directory's name stays part of the path; a drive's colon is
|
|
92
|
+
// kept. An `@` or a `+`, as a package manager's store has in its
|
|
93
|
+
// directory names, is left as it is: the server reads neither back
|
|
94
|
+
// from its encoded form.
|
|
95
|
+
const segments = resolve(file)
|
|
96
|
+
.split(sep)
|
|
97
|
+
.map((segment, index) =>
|
|
98
|
+
index === 0 && /^[A-Za-z]:$/.test(segment)
|
|
99
|
+
? segment
|
|
100
|
+
: encodeURI(segment).replace(/#/g, '%23').replace(/\?/g, '%3F')
|
|
101
|
+
);
|
|
102
|
+
const path = segments.join('/');
|
|
103
|
+
return `/@fs${path.startsWith('/') ? '' : '/'}${path}`;
|
|
104
|
+
}
|
|
105
|
+
// The build takes the file relative to the page.
|
|
106
|
+
const fromPage = relative(dirname(target.hostPage), file);
|
|
107
|
+
if (isAbsolute(fromPage)) {
|
|
108
|
+
return fail(
|
|
109
|
+
`called asset(${JSON.stringify(file)}), a file on another drive than the preview, which the static build cannot reach.`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
return fromPage.split(sep).join('/');
|
|
113
|
+
};
|
|
114
|
+
const markup = async (
|
|
115
|
+
name: 'head' | 'root',
|
|
116
|
+
part: ShellPageMarkup | undefined
|
|
117
|
+
): Promise<string> => {
|
|
118
|
+
if (part === undefined) return '';
|
|
119
|
+
if (typeof part !== 'string' && typeof part !== 'function') {
|
|
120
|
+
return fail(`has a '${name}' that is neither markup nor a function.`);
|
|
121
|
+
}
|
|
122
|
+
const given = typeof part === 'function' ? await part({ asset }) : part;
|
|
123
|
+
if (typeof given !== 'string') {
|
|
124
|
+
return fail(`has a '${name}' function that gave no markup.`);
|
|
125
|
+
}
|
|
126
|
+
return given.trim();
|
|
127
|
+
};
|
|
128
|
+
return {
|
|
129
|
+
head: await markup('head', page.head),
|
|
130
|
+
root: await markup('root', page.root),
|
|
131
|
+
assetDirectories: [...assetDirectories],
|
|
132
|
+
};
|
|
133
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// The packages a shell needs installed beside the preview: the charcell
|
|
2
|
+
// shell imports charcell and @zag-js/react, which the preview declares as
|
|
3
|
+
// optional peers rather than installing for every kit. A kit that names the
|
|
4
|
+
// shell without one would otherwise get a bundler error about an import it
|
|
5
|
+
// cannot see, from inside the shell, once Vite runs.
|
|
6
|
+
|
|
7
|
+
import { existsSync } from 'node:fs';
|
|
8
|
+
import { dirname, resolve } from 'node:path';
|
|
9
|
+
import type { PreviewContext } from './context.js';
|
|
10
|
+
|
|
11
|
+
/** The packages the charcell shell imports that the kit installs: the
|
|
12
|
+
* preview's peer dependencies. */
|
|
13
|
+
export const CHARCELL_SHELL_PEERS = ['charcell', '@zag-js/react'] as const;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Whether package `name` resolves from `directory` by Node's lookup: a
|
|
17
|
+
* `node_modules/<name>` in it or any directory above it.
|
|
18
|
+
*
|
|
19
|
+
* @param name - The package name.
|
|
20
|
+
* @param directory - The directory an import of it is made from.
|
|
21
|
+
* @returns Whether a `node_modules/<name>/package.json` is found.
|
|
22
|
+
*/
|
|
23
|
+
export function resolvesFrom(name: string, directory: string): boolean {
|
|
24
|
+
for (let current = directory; ; current = dirname(current)) {
|
|
25
|
+
if (existsSync(resolve(current, 'node_modules', name, 'package.json')))
|
|
26
|
+
return true;
|
|
27
|
+
if (current === dirname(current)) return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The packages the kit's shell imports that do not resolve from where the
|
|
33
|
+
* shell is, as the bundler would look for them: for the charcell shell,
|
|
34
|
+
* named as it is or configured through a module of the kit's, each of
|
|
35
|
+
* {@link CHARCELL_SHELL_PEERS} the kit has not installed. A shell of the
|
|
36
|
+
* kit's own, or from another package, declares its own dependencies, and
|
|
37
|
+
* gets nothing here.
|
|
38
|
+
*
|
|
39
|
+
* @param context - The preview context.
|
|
40
|
+
* @returns The missing packages' names, empty when none is missing.
|
|
41
|
+
*/
|
|
42
|
+
export function missingShellPeers(
|
|
43
|
+
context: Pick<PreviewContext, 'charcellShellAbsolute'>
|
|
44
|
+
): string[] {
|
|
45
|
+
if (context.charcellShellAbsolute === undefined) return [];
|
|
46
|
+
const shellDirectory = dirname(context.charcellShellAbsolute);
|
|
47
|
+
return CHARCELL_SHELL_PEERS.filter(
|
|
48
|
+
(name) => !resolvesFrom(name, shellDirectory)
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* What to say of the shell's missing packages, or `undefined` when none is
|
|
54
|
+
* missing: the packages to install, and the command that installs them.
|
|
55
|
+
*
|
|
56
|
+
* @param missing - The packages {@link missingShellPeers} gives.
|
|
57
|
+
* @returns The message, naming each package.
|
|
58
|
+
* @example
|
|
59
|
+
* ```ts
|
|
60
|
+
* missingShellPeersProblem(['@zag-js/react']);
|
|
61
|
+
* // "the charcell shell needs @zag-js/react, which the kit has not installed: ..."
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export function missingShellPeersProblem(
|
|
65
|
+
missing: readonly string[]
|
|
66
|
+
): string | undefined {
|
|
67
|
+
if (missing.length === 0) return undefined;
|
|
68
|
+
const names = missing.join(' and ');
|
|
69
|
+
return `the charcell shell needs ${names}, which the kit has not installed: add ${missing.length === 1 ? 'it' : 'them'} to the kit's devDependencies, as in \`npm install --save-dev ${missing.join(' ')}\``;
|
|
70
|
+
}
|
package/src/shell.ts
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
// The shell contract: what a module named by `preview.shell` exports, what a
|
|
2
|
+
// shell's page module exports, and everything a shell author reads from the
|
|
3
|
+
// preview - the controller's state and actions, a store view of that state,
|
|
4
|
+
// the catalog, the editor's tree, the option helpers, the scenario helpers
|
|
5
|
+
// and a story's source. A shell renders the whole host interface around the
|
|
6
|
+
// preview's iframes, in React, Vue, Svelte or plain DOM; the preview provides
|
|
7
|
+
// the behavior. Browser-safe: no node imports, and nothing here holds state
|
|
8
|
+
// of its own.
|
|
9
|
+
|
|
10
|
+
import type { PreviewCatalog, PreviewController } from './controller/index.js';
|
|
11
|
+
import type { PreviewShellOption } from './options.js';
|
|
12
|
+
|
|
13
|
+
export {
|
|
14
|
+
childPath,
|
|
15
|
+
DEFAULT_PANE_WIDTHS,
|
|
16
|
+
DEFAULT_SLOT,
|
|
17
|
+
defaultScenarioId,
|
|
18
|
+
isContainer,
|
|
19
|
+
MAXIMUM_PANE_WIDTH,
|
|
20
|
+
PREVIEW_FRAME_SHOWN_ATTRIBUTE,
|
|
21
|
+
nodeAt,
|
|
22
|
+
nodeAddSlots,
|
|
23
|
+
nodeSlots,
|
|
24
|
+
previewStore,
|
|
25
|
+
printStorySource,
|
|
26
|
+
scenarioChoices,
|
|
27
|
+
storyFrameKey,
|
|
28
|
+
type AroundFill,
|
|
29
|
+
type ContainerNode,
|
|
30
|
+
type DropTarget,
|
|
31
|
+
type EditorNode,
|
|
32
|
+
type EditorState,
|
|
33
|
+
type NodePath,
|
|
34
|
+
type OpaqueFill,
|
|
35
|
+
type PreviewBinding,
|
|
36
|
+
type PreviewCatalog,
|
|
37
|
+
type PreviewCatalogView,
|
|
38
|
+
type PreviewComponent,
|
|
39
|
+
type PreviewController,
|
|
40
|
+
type PreviewEditorState,
|
|
41
|
+
type PreviewEvent,
|
|
42
|
+
type PreviewFrameReadiness,
|
|
43
|
+
type PreviewFrameTarget,
|
|
44
|
+
type PreviewTargetCode,
|
|
45
|
+
type PreviewHtmlSource,
|
|
46
|
+
type PreviewImport,
|
|
47
|
+
type PreviewInsertTarget,
|
|
48
|
+
type PreviewLanding,
|
|
49
|
+
type PreviewNavigationNode,
|
|
50
|
+
type PreviewNodeRequest,
|
|
51
|
+
type PreviewOptionDescriptor,
|
|
52
|
+
type PreviewPaneWidth,
|
|
53
|
+
type PreviewPropType,
|
|
54
|
+
type PreviewRegenerationStatus,
|
|
55
|
+
type PreviewScenario,
|
|
56
|
+
type PreviewSource,
|
|
57
|
+
type PreviewSelection,
|
|
58
|
+
type PreviewSortableDrop,
|
|
59
|
+
type PreviewCode,
|
|
60
|
+
type PreviewState,
|
|
61
|
+
type PreviewStore,
|
|
62
|
+
type PreviewStoryReference,
|
|
63
|
+
type ScenarioChoice,
|
|
64
|
+
type SortableListAddress,
|
|
65
|
+
type Step,
|
|
66
|
+
type StoryAround,
|
|
67
|
+
} from './controller/index.js';
|
|
68
|
+
export {
|
|
69
|
+
CONTEXT_OPTION_GROUP,
|
|
70
|
+
KIT_OPTION_GROUP,
|
|
71
|
+
optionGroup,
|
|
72
|
+
optionKind,
|
|
73
|
+
shellOptionId,
|
|
74
|
+
type OptionChoice,
|
|
75
|
+
type OptionKind,
|
|
76
|
+
type OptionValue,
|
|
77
|
+
type PreviewShellOption,
|
|
78
|
+
} from './options.js';
|
|
79
|
+
export {
|
|
80
|
+
PREVIEW_SCRIPTED_TARGETS,
|
|
81
|
+
PREVIEW_TARGETS,
|
|
82
|
+
type PreviewContentSize,
|
|
83
|
+
type PreviewScriptedTarget,
|
|
84
|
+
type PreviewTarget,
|
|
85
|
+
} from './protocol.js';
|
|
86
|
+
export type { ViewFramework } from './views.js';
|
|
87
|
+
|
|
88
|
+
/** The version of the shell contract this preview serves. */
|
|
89
|
+
export const PREVIEW_SHELL_API_VERSION = 1;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* What a shell module default-exports: the contract version it was written
|
|
93
|
+
* against, its own options, and how it mounts.
|
|
94
|
+
*/
|
|
95
|
+
export interface PreviewShell {
|
|
96
|
+
/** The shell contract version the shell was written against. The preview
|
|
97
|
+
* refuses a shell whose version it does not serve. */
|
|
98
|
+
apiVersion: number;
|
|
99
|
+
/** The shell's own options, such as its color mode or density. The preview
|
|
100
|
+
* keeps their values beside the kit's, under `shell.<id>`. A function is
|
|
101
|
+
* called once with the kit's catalog before the preview starts, for options
|
|
102
|
+
* that depend on the kit, such as a choice among its targets. */
|
|
103
|
+
options?:
|
|
104
|
+
| PreviewShellOption[]
|
|
105
|
+
| ((catalog: PreviewCatalog) => PreviewShellOption[]);
|
|
106
|
+
/**
|
|
107
|
+
* Render the shell into `root`, reading and driving `controller`.
|
|
108
|
+
*
|
|
109
|
+
* @param root - The host page's root element, empty.
|
|
110
|
+
* @param controller - The preview's behavior.
|
|
111
|
+
* @returns A function that unmounts the shell, or nothing when there is
|
|
112
|
+
* nothing to undo. The preview calls it before mounting a changed shell
|
|
113
|
+
* over the same controller during development.
|
|
114
|
+
*/
|
|
115
|
+
mount(root: HTMLElement, controller: PreviewController): (() => void) | void;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Declare a shell. The function only types its argument and returns it, so a
|
|
120
|
+
* shell module reads as one declaration.
|
|
121
|
+
*
|
|
122
|
+
* @param shell - The shell.
|
|
123
|
+
* @returns The shell, unchanged.
|
|
124
|
+
* @example
|
|
125
|
+
* ```ts
|
|
126
|
+
* export default defineShell({
|
|
127
|
+
* apiVersion: 1,
|
|
128
|
+
* options: [{ id: 'density', label: 'density', default: false }],
|
|
129
|
+
* mount(root, controller) {
|
|
130
|
+
* const stop = controller.subscribe((state) => render(root, state));
|
|
131
|
+
* render(root, controller.getState());
|
|
132
|
+
* return stop;
|
|
133
|
+
* },
|
|
134
|
+
* });
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
export function defineShell(shell: PreviewShell): PreviewShell {
|
|
138
|
+
return shell;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** What a shell page's `head` and `root` functions are given. */
|
|
142
|
+
export interface ShellPageContext {
|
|
143
|
+
/**
|
|
144
|
+
* The address of a file the page's markup references, such as a font to
|
|
145
|
+
* preload: the development server serves the file from where it is, and the
|
|
146
|
+
* static build copies it into its output.
|
|
147
|
+
*
|
|
148
|
+
* @param file - The file's absolute path.
|
|
149
|
+
* @returns The address to write into the markup, in an `href`, a `src` or
|
|
150
|
+
* a CSS `url()`.
|
|
151
|
+
*/
|
|
152
|
+
asset(file: string): string;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** A part of the host page a shell supplies: markup, or a function that
|
|
156
|
+
* gives it. */
|
|
157
|
+
export type ShellPageMarkup =
|
|
158
|
+
| string
|
|
159
|
+
| ((context: ShellPageContext) => string | Promise<string>);
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* What a shell's page module default-exports: the static start of the host
|
|
163
|
+
* page, which shows before the shell's script has loaded. The module is
|
|
164
|
+
* evaluated in Node as the preview starts or builds, so it reads files and
|
|
165
|
+
* resolves packages freely and holds nothing that needs a browser.
|
|
166
|
+
*/
|
|
167
|
+
export interface ShellPage {
|
|
168
|
+
/** Markup for the end of the page's `<head>`: styles, a font face, links
|
|
169
|
+
* that preload what the shell will need. It stays for the life of the
|
|
170
|
+
* page. */
|
|
171
|
+
head?: ShellPageMarkup;
|
|
172
|
+
/** Markup for the page's root element, shown until the shell mounts: a
|
|
173
|
+
* loading note, with the styles only it needs. The preview empties the
|
|
174
|
+
* root before it mounts the shell. */
|
|
175
|
+
root?: ShellPageMarkup;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Declare a shell's page. The function only types its argument and returns
|
|
180
|
+
* it, so a page module reads as one declaration.
|
|
181
|
+
*
|
|
182
|
+
* @param page - The page.
|
|
183
|
+
* @returns The page, unchanged.
|
|
184
|
+
* @example
|
|
185
|
+
* ```ts
|
|
186
|
+
* export default defineShellPage({
|
|
187
|
+
* head: '<style>html { background: #101010; }</style>',
|
|
188
|
+
* root: '<p role="status">loading</p>',
|
|
189
|
+
* });
|
|
190
|
+
* ```
|
|
191
|
+
*/
|
|
192
|
+
export function defineShellPage(page: ShellPage): ShellPage {
|
|
193
|
+
return page;
|
|
194
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// The charcell shell's page: what the host page shows before the shell's
|
|
2
|
+
// script has loaded. charcell's dark ground paints from the first frame, the
|
|
3
|
+
// upright face is declared and its font preloaded, and the root holds the
|
|
4
|
+
// loading note the shell mounts over.
|
|
5
|
+
|
|
6
|
+
import { readFileSync } from 'node:fs';
|
|
7
|
+
import { createRequire } from 'node:module';
|
|
8
|
+
import { dirname, resolve } from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import { defineShellPage } from '../../shell.js';
|
|
11
|
+
|
|
12
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
|
|
14
|
+
/** The loading note and its styles, kept beside the shell. */
|
|
15
|
+
const loadingNote = resolve(
|
|
16
|
+
here,
|
|
17
|
+
'../../../application/shells/charcell/page-loading.html'
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* charcell's upright face, as its base stylesheet declares it.
|
|
22
|
+
*
|
|
23
|
+
* @returns The `@font-face` rule, the address it gives the font file, and
|
|
24
|
+
* the file's absolute path; or nothing when charcell or the rule cannot be
|
|
25
|
+
* found.
|
|
26
|
+
*/
|
|
27
|
+
function uprightFace():
|
|
28
|
+
| { rule: string; address: string; file: string }
|
|
29
|
+
| undefined {
|
|
30
|
+
let stylesheet: string;
|
|
31
|
+
let source: string;
|
|
32
|
+
try {
|
|
33
|
+
stylesheet = createRequire(import.meta.url).resolve('charcell/css/base');
|
|
34
|
+
source = readFileSync(stylesheet, 'utf8');
|
|
35
|
+
} catch {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
const rule = (source.match(/@font-face\s*\{[^}]*\}/g) ?? []).find((face) =>
|
|
39
|
+
/font-style:\s*normal/.test(face)
|
|
40
|
+
);
|
|
41
|
+
const address = rule?.match(/url\((["']?)([^"')]+)\1\)/)?.[2];
|
|
42
|
+
// Only a font file beside the stylesheet: an address of another kind, a
|
|
43
|
+
// data address or one with a query, names no file to preload.
|
|
44
|
+
if (
|
|
45
|
+
rule === undefined ||
|
|
46
|
+
address === undefined ||
|
|
47
|
+
!/^\.{0,2}\/?[\w./-]+\.woff2$/.test(address)
|
|
48
|
+
) {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
return { rule, address, file: resolve(dirname(stylesheet), address) };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The charcell shell's page: charcell's dark ground and its face in the
|
|
56
|
+
* head, the font preloaded, and the loading note in the root. A kit that
|
|
57
|
+
* names the charcell shell as it is gets it without naming it; a module of
|
|
58
|
+
* the kit's that configures the shell names it as `preview.shell.page`.
|
|
59
|
+
*/
|
|
60
|
+
export default defineShellPage({
|
|
61
|
+
head({ asset }) {
|
|
62
|
+
// The ground sits in the first, lowest cascade layer, so charcell's own
|
|
63
|
+
// page rule and a kit's overrides win once they load.
|
|
64
|
+
const ground =
|
|
65
|
+
'<style>@layer { html { color-scheme: dark; background: #000000; } }</style>';
|
|
66
|
+
const face = uprightFace();
|
|
67
|
+
if (face === undefined) return ground;
|
|
68
|
+
const font = asset(face.file);
|
|
69
|
+
// The face as charcell declares it, except that text waits for the font
|
|
70
|
+
// rather than showing in another face first: the loading note appears in
|
|
71
|
+
// the face the shell's text has, on the same cells.
|
|
72
|
+
const rule = face.rule
|
|
73
|
+
.replace(face.address, () => font)
|
|
74
|
+
.replace(/font-display:\s*[a-z]+/, 'font-display:block');
|
|
75
|
+
return [
|
|
76
|
+
ground,
|
|
77
|
+
`<link rel="preload" as="font" type="font/woff2" crossorigin href="${font}" />`,
|
|
78
|
+
`<style>${rule}</style>`,
|
|
79
|
+
].join('\n');
|
|
80
|
+
},
|
|
81
|
+
root: () => readFileSync(loadingNote, 'utf8'),
|
|
82
|
+
});
|