@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,161 @@
|
|
|
1
|
+
import type { AroundFill, JsonValue, SlotFill, SlotPlan, StoryAround } from '../lib/types.js';
|
|
2
|
+
export type { AroundFill, SlotFill, SlotPlan, StoryAround };
|
|
3
|
+
type Json = unknown;
|
|
4
|
+
/** A fill the editor cannot author: a `raw` body, a `scoped` slot or a
|
|
5
|
+
* `part` call, each per-target strings. It is held verbatim, so it can be
|
|
6
|
+
* moved or removed but not edited, and serializes back unchanged. */
|
|
7
|
+
export type OpaqueFill = Extract<SlotFill, {
|
|
8
|
+
kind: 'raw' | 'scoped' | 'part';
|
|
9
|
+
}>;
|
|
10
|
+
export type EditorNode = {
|
|
11
|
+
kind: 'text';
|
|
12
|
+
value: string;
|
|
13
|
+
id?: string;
|
|
14
|
+
} | {
|
|
15
|
+
kind: 'element';
|
|
16
|
+
component: string;
|
|
17
|
+
props: Record<string, Json>;
|
|
18
|
+
slots: Record<string, EditorNode[]>;
|
|
19
|
+
id?: string;
|
|
20
|
+
} | {
|
|
21
|
+
kind: 'html';
|
|
22
|
+
tag: string;
|
|
23
|
+
props: Record<string, Json>;
|
|
24
|
+
slots: Record<string, EditorNode[]>;
|
|
25
|
+
id?: string;
|
|
26
|
+
} | {
|
|
27
|
+
kind: 'opaque';
|
|
28
|
+
fill: OpaqueFill;
|
|
29
|
+
id?: string;
|
|
30
|
+
};
|
|
31
|
+
/** A node that holds content of its own: an element or an HTML tag. */
|
|
32
|
+
export type ContainerNode = Extract<EditorNode, {
|
|
33
|
+
kind: 'element' | 'html';
|
|
34
|
+
}>;
|
|
35
|
+
export declare function isContainer(node: EditorNode): node is ContainerNode;
|
|
36
|
+
export declare const DEFAULT_SLOT = "default";
|
|
37
|
+
/** The plain wrapper tags an HTML node can be, picked from a list rather than
|
|
38
|
+
* typed: the layout and content elements a slot fill might need around its
|
|
39
|
+
* children. */
|
|
40
|
+
export declare const HTML_TAGS: string[];
|
|
41
|
+
/** Gives every node in the content an id, in place: idempotent, and the spreads
|
|
42
|
+
* every update makes carry a node's id along. */
|
|
43
|
+
export declare function ensureIds(content: Record<string, EditorNode[]>): void;
|
|
44
|
+
/** The root's state: its component, its props and its content keyed by
|
|
45
|
+
* slot. The content the story renders beside the root is not the tree's: it
|
|
46
|
+
* is the story's (`PreviewComponent.around`), which no edit reaches. */
|
|
47
|
+
export interface EditorState {
|
|
48
|
+
root: string;
|
|
49
|
+
props: Record<string, Json>;
|
|
50
|
+
slots: Record<string, EditorNode[]>;
|
|
51
|
+
}
|
|
52
|
+
/** One hop into a slot: which slot, and the node's index in it. */
|
|
53
|
+
export interface Step {
|
|
54
|
+
slot: string;
|
|
55
|
+
index: number;
|
|
56
|
+
}
|
|
57
|
+
/** A node's address: the hops from the root, one per slot it descends into.
|
|
58
|
+
* The empty path `[]` addresses the root itself. */
|
|
59
|
+
export type NodePath = Step[];
|
|
60
|
+
/** Where a move lands: into `slot` of the node at `parent`, before `index`
|
|
61
|
+
* (counted before the moved node is removed). */
|
|
62
|
+
export interface DropTarget {
|
|
63
|
+
parent: Step[];
|
|
64
|
+
slot: string;
|
|
65
|
+
index: number;
|
|
66
|
+
}
|
|
67
|
+
/** Apply `transform` to the node at `path` (path must be non-empty). */
|
|
68
|
+
export declare function updateAt(content: Record<string, EditorNode[]>, path: Step[], transform: (node: EditorNode) => EditorNode): Record<string, EditorNode[]>;
|
|
69
|
+
/** Insert `child` into `slot` of the node at `parentPath` (empty path = the
|
|
70
|
+
* root's slots) at `index`; an omitted index appends. */
|
|
71
|
+
export declare function insertAt(content: Record<string, EditorNode[]>, parentPath: Step[], slot: string, child: EditorNode, index?: number): Record<string, EditorNode[]>;
|
|
72
|
+
/** Remove the node at `path` (path must be non-empty). */
|
|
73
|
+
export declare function removeAt(content: Record<string, EditorNode[]>, path: Step[]): Record<string, EditorNode[]>;
|
|
74
|
+
/** The node at `path`, or null when nothing is there. The root is the tree
|
|
75
|
+
* itself rather than a node of its content, so the empty path gives null. */
|
|
76
|
+
export declare function nodeAt(content: Record<string, EditorNode[]>, path: Step[]): EditorNode | null;
|
|
77
|
+
/**
|
|
78
|
+
* The path of a child: the node at `index` in `slot` of the node at `parent`.
|
|
79
|
+
*
|
|
80
|
+
* @param parent - The parent's path; `[]` for the root.
|
|
81
|
+
* @param slot - The slot the child is in.
|
|
82
|
+
* @param index - The child's index in the slot.
|
|
83
|
+
* @returns The child's path.
|
|
84
|
+
*/
|
|
85
|
+
export declare function childPath(parent: NodePath, slot: string, index: number): NodePath;
|
|
86
|
+
/**
|
|
87
|
+
* A string that identifies a path, for a key or a lookup: `'root'` for the
|
|
88
|
+
* root, otherwise each step's slot and index.
|
|
89
|
+
*
|
|
90
|
+
* @param path - The path.
|
|
91
|
+
* @returns The key, such as `'default:0/header:1'`.
|
|
92
|
+
*/
|
|
93
|
+
export declare function pathKey(path: NodePath): string;
|
|
94
|
+
/** Move the node at `from` to `target`: remove it, then insert it, adjusting the
|
|
95
|
+
* target for the index shift the removal causes in a shared sibling list. The
|
|
96
|
+
* root, a missing node, and a drop into the node's own subtree leave the
|
|
97
|
+
* content unchanged (the same object back). */
|
|
98
|
+
export declare function moveNode(content: Record<string, EditorNode[]>, from: Step[], target: DropTarget): Record<string, EditorNode[]>;
|
|
99
|
+
/** A sortable list's address: the parent whose slot it lists, and the slot. */
|
|
100
|
+
export interface SortableListAddress {
|
|
101
|
+
parent: Step[];
|
|
102
|
+
slot: string;
|
|
103
|
+
}
|
|
104
|
+
/** Translate a sortable move (where the node ends up in its list) into the path
|
|
105
|
+
* it leaves and the drop target `moveNode` takes (the index it goes before,
|
|
106
|
+
* counted before the removal). */
|
|
107
|
+
export declare function resolveSortableMove(source: SortableListAddress, target: SortableListAddress, sameList: boolean, fromIndex: number, toIndex: number): {
|
|
108
|
+
from: Step[];
|
|
109
|
+
target: DropTarget;
|
|
110
|
+
};
|
|
111
|
+
/** What the editor reads of a manifest component to seed a node from it. */
|
|
112
|
+
export interface SeedableComponent {
|
|
113
|
+
name: string;
|
|
114
|
+
defaultProps: Record<string, Json>;
|
|
115
|
+
content?: SlotPlan;
|
|
116
|
+
}
|
|
117
|
+
/** A component swapped in under an existing node: its default props, with a
|
|
118
|
+
* string `children` default as a default-slot text child (children is the
|
|
119
|
+
* default slot, not a prop). */
|
|
120
|
+
export declare function seedNode(component: SeedableComponent): EditorNode;
|
|
121
|
+
/** A component added through the add dialog. With `props` the node carries them
|
|
122
|
+
* plus the component's curated slot content (the default or a named scenario);
|
|
123
|
+
* without, it comes in empty - no props, no content. */
|
|
124
|
+
export declare function seedComponentNode(component: SeedableComponent, props?: Record<string, Json>): EditorNode;
|
|
125
|
+
/** The root seeded from its story file content, so the tree starts from what the
|
|
126
|
+
* browse view renders, with `props` (a story's scenario-merged props) or the
|
|
127
|
+
* component's defaults. */
|
|
128
|
+
export declare function seedRoot(component: SeedableComponent, props?: Record<string, Json>): EditorState;
|
|
129
|
+
/**
|
|
130
|
+
* A key for a tree's slot content, equal for two trees of the same content
|
|
131
|
+
* whatever their node ids or the order of their props' keys: what the
|
|
132
|
+
* regeneration payload would carry, serialized with sorted keys.
|
|
133
|
+
*/
|
|
134
|
+
export declare function slotsKey(slots: Record<string, EditorNode[]>): string;
|
|
135
|
+
/** A value as JSON with every object's keys sorted, so two values equal by
|
|
136
|
+
* content serialize the same; `undefined` entries are left out. */
|
|
137
|
+
export declare function stableStringify(value: unknown): string;
|
|
138
|
+
/**
|
|
139
|
+
* The props as JSON, each one left unset (`undefined`) left out. An empty
|
|
140
|
+
* string is a value like any other and stays, so a component given `''`
|
|
141
|
+
* keeps it on every update; a story unsets a prop with `null`.
|
|
142
|
+
*
|
|
143
|
+
* @param record - The props, from the controls or a pane's write-back.
|
|
144
|
+
* @returns The props that are set.
|
|
145
|
+
*/
|
|
146
|
+
export declare function cleanRecord(record: Record<string, Json>): Record<string, JsonValue>;
|
|
147
|
+
export declare function namedFills(slots: Record<string, EditorNode[]>): Record<string, SlotFill>;
|
|
148
|
+
export declare function fillToNodes(fill: SlotFill | undefined): EditorNode[];
|
|
149
|
+
/** Seed a root component's slot content from its story file SlotPlan, falling back to
|
|
150
|
+
* a default-slot text child when a string `children` prop is the only content. */
|
|
151
|
+
export declare function seedSlots(content: SlotPlan | undefined, props: Record<string, Json>): Record<string, EditorNode[]>;
|
|
152
|
+
/** The regeneration payload for a root component, its props and its slot
|
|
153
|
+
* content. */
|
|
154
|
+
export declare function toPayload(state: EditorState): {
|
|
155
|
+
rootComponent: string;
|
|
156
|
+
props: Record<string, JsonValue>;
|
|
157
|
+
slots: {
|
|
158
|
+
named?: Record<string, SlotFill> | undefined;
|
|
159
|
+
default?: SlotFill | undefined;
|
|
160
|
+
} | undefined;
|
|
161
|
+
};
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
export function isContainer(node) {
|
|
2
|
+
return node.kind === 'element' || node.kind === 'html';
|
|
3
|
+
}
|
|
4
|
+
export const DEFAULT_SLOT = 'default';
|
|
5
|
+
/** The plain wrapper tags an HTML node can be, picked from a list rather than
|
|
6
|
+
* typed: the layout and content elements a slot fill might need around its
|
|
7
|
+
* children. */
|
|
8
|
+
export const HTML_TAGS = [
|
|
9
|
+
'div',
|
|
10
|
+
'section',
|
|
11
|
+
'article',
|
|
12
|
+
'aside',
|
|
13
|
+
'header',
|
|
14
|
+
'footer',
|
|
15
|
+
'main',
|
|
16
|
+
'nav',
|
|
17
|
+
'p',
|
|
18
|
+
'span',
|
|
19
|
+
'strong',
|
|
20
|
+
'em',
|
|
21
|
+
'small',
|
|
22
|
+
'code',
|
|
23
|
+
'pre',
|
|
24
|
+
'blockquote',
|
|
25
|
+
'ul',
|
|
26
|
+
'ol',
|
|
27
|
+
'li',
|
|
28
|
+
];
|
|
29
|
+
let nodeIdCounter = 0;
|
|
30
|
+
/** Gives every node in the content an id, in place: idempotent, and the spreads
|
|
31
|
+
* every update makes carry a node's id along. */
|
|
32
|
+
export function ensureIds(content) {
|
|
33
|
+
for (const list of Object.values(content)) {
|
|
34
|
+
for (const node of list) {
|
|
35
|
+
if (!node.id)
|
|
36
|
+
node.id = `editor-node-${nodeIdCounter++}`;
|
|
37
|
+
if (isContainer(node))
|
|
38
|
+
ensureIds(node.slots);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** Apply `transform` to the node at `path` (path must be non-empty). */
|
|
43
|
+
export function updateAt(content, path, transform) {
|
|
44
|
+
const [step, ...rest] = path;
|
|
45
|
+
const list = content[step.slot] ?? [];
|
|
46
|
+
const nextList = list.map((node, index) => {
|
|
47
|
+
if (index !== step.index)
|
|
48
|
+
return node;
|
|
49
|
+
if (rest.length === 0)
|
|
50
|
+
return transform(node);
|
|
51
|
+
if (!isContainer(node))
|
|
52
|
+
return node;
|
|
53
|
+
return { ...node, slots: updateAt(node.slots, rest, transform) };
|
|
54
|
+
});
|
|
55
|
+
return { ...content, [step.slot]: nextList };
|
|
56
|
+
}
|
|
57
|
+
/** Insert `child` into `slot` of the node at `parentPath` (empty path = the
|
|
58
|
+
* root's slots) at `index`; an omitted index appends. */
|
|
59
|
+
export function insertAt(content, parentPath, slot, child, index) {
|
|
60
|
+
if (parentPath.length === 0) {
|
|
61
|
+
const list = content[slot] ?? [];
|
|
62
|
+
const at = index ?? list.length;
|
|
63
|
+
return {
|
|
64
|
+
...content,
|
|
65
|
+
[slot]: [...list.slice(0, at), child, ...list.slice(at)],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const [step, ...rest] = parentPath;
|
|
69
|
+
const list = content[step.slot] ?? [];
|
|
70
|
+
const nextList = list.map((node, position) => position === step.index && isContainer(node)
|
|
71
|
+
? { ...node, slots: insertAt(node.slots, rest, slot, child, index) }
|
|
72
|
+
: node);
|
|
73
|
+
return { ...content, [step.slot]: nextList };
|
|
74
|
+
}
|
|
75
|
+
/** Remove the node at `path` (path must be non-empty). */
|
|
76
|
+
export function removeAt(content, path) {
|
|
77
|
+
const [step, ...rest] = path;
|
|
78
|
+
const list = content[step.slot] ?? [];
|
|
79
|
+
if (rest.length === 0)
|
|
80
|
+
return {
|
|
81
|
+
...content,
|
|
82
|
+
[step.slot]: list.filter((_, index) => index !== step.index),
|
|
83
|
+
};
|
|
84
|
+
const nextList = list.map((node, index) => index === step.index && isContainer(node)
|
|
85
|
+
? { ...node, slots: removeAt(node.slots, rest) }
|
|
86
|
+
: node);
|
|
87
|
+
return { ...content, [step.slot]: nextList };
|
|
88
|
+
}
|
|
89
|
+
/** The node at `path`, or null when nothing is there. The root is the tree
|
|
90
|
+
* itself rather than a node of its content, so the empty path gives null. */
|
|
91
|
+
export function nodeAt(content, path) {
|
|
92
|
+
let slots = content;
|
|
93
|
+
let node = null;
|
|
94
|
+
for (const step of path) {
|
|
95
|
+
node = (slots[step.slot] ?? [])[step.index] ?? null;
|
|
96
|
+
if (!node)
|
|
97
|
+
return null;
|
|
98
|
+
slots = isContainer(node) ? node.slots : {};
|
|
99
|
+
}
|
|
100
|
+
return node;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* The path of a child: the node at `index` in `slot` of the node at `parent`.
|
|
104
|
+
*
|
|
105
|
+
* @param parent - The parent's path; `[]` for the root.
|
|
106
|
+
* @param slot - The slot the child is in.
|
|
107
|
+
* @param index - The child's index in the slot.
|
|
108
|
+
* @returns The child's path.
|
|
109
|
+
*/
|
|
110
|
+
export function childPath(parent, slot, index) {
|
|
111
|
+
return [...parent, { slot, index }];
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* A string that identifies a path, for a key or a lookup: `'root'` for the
|
|
115
|
+
* root, otherwise each step's slot and index.
|
|
116
|
+
*
|
|
117
|
+
* @param path - The path.
|
|
118
|
+
* @returns The key, such as `'default:0/header:1'`.
|
|
119
|
+
*/
|
|
120
|
+
export function pathKey(path) {
|
|
121
|
+
return path.length === 0
|
|
122
|
+
? 'root'
|
|
123
|
+
: path.map((step) => `${step.slot}:${step.index}`).join('/');
|
|
124
|
+
}
|
|
125
|
+
/** True if `candidate` addresses `path` itself or one of its ancestors
|
|
126
|
+
* (`candidate` is a prefix of `path`). Used to forbid dropping a node into its
|
|
127
|
+
* own subtree. */
|
|
128
|
+
function isAncestorOrSelf(candidate, path) {
|
|
129
|
+
if (candidate.length > path.length)
|
|
130
|
+
return false;
|
|
131
|
+
return candidate.every((step, position) => step.slot === path[position].slot && step.index === path[position].index);
|
|
132
|
+
}
|
|
133
|
+
/** Removing the node at `from` shifts the indices of its later siblings, so a
|
|
134
|
+
* target parent path that runs through one of them shifts down by one. */
|
|
135
|
+
function adjustForRemoval(target, from) {
|
|
136
|
+
const fromParent = from.slice(0, -1);
|
|
137
|
+
const fromStep = from[from.length - 1];
|
|
138
|
+
if (target.length <= fromParent.length)
|
|
139
|
+
return target;
|
|
140
|
+
for (let k = 0; k < fromParent.length; k++) {
|
|
141
|
+
if (target[k].slot !== fromParent[k].slot ||
|
|
142
|
+
target[k].index !== fromParent[k].index)
|
|
143
|
+
return target;
|
|
144
|
+
}
|
|
145
|
+
const divergence = target[fromParent.length];
|
|
146
|
+
if (divergence.slot === fromStep.slot && divergence.index > fromStep.index) {
|
|
147
|
+
const next = [...target];
|
|
148
|
+
next[fromParent.length] = { ...divergence, index: divergence.index - 1 };
|
|
149
|
+
return next;
|
|
150
|
+
}
|
|
151
|
+
return target;
|
|
152
|
+
}
|
|
153
|
+
/** Move the node at `from` to `target`: remove it, then insert it, adjusting the
|
|
154
|
+
* target for the index shift the removal causes in a shared sibling list. The
|
|
155
|
+
* root, a missing node, and a drop into the node's own subtree leave the
|
|
156
|
+
* content unchanged (the same object back). */
|
|
157
|
+
export function moveNode(content, from, target) {
|
|
158
|
+
if (from.length === 0)
|
|
159
|
+
return content;
|
|
160
|
+
if (isAncestorOrSelf(from, target.parent))
|
|
161
|
+
return content;
|
|
162
|
+
const node = nodeAt(content, from);
|
|
163
|
+
if (!node)
|
|
164
|
+
return content;
|
|
165
|
+
const fromParent = from.slice(0, -1);
|
|
166
|
+
const fromStep = from[from.length - 1];
|
|
167
|
+
const sameList = pathKey(fromParent) === pathKey(target.parent) &&
|
|
168
|
+
fromStep.slot === target.slot;
|
|
169
|
+
const index = sameList && fromStep.index < target.index ? target.index - 1 : target.index;
|
|
170
|
+
const removed = removeAt(content, from);
|
|
171
|
+
return insertAt(removed, adjustForRemoval(target.parent, from), target.slot, node, index);
|
|
172
|
+
}
|
|
173
|
+
/** Translate a sortable move (where the node ends up in its list) into the path
|
|
174
|
+
* it leaves and the drop target `moveNode` takes (the index it goes before,
|
|
175
|
+
* counted before the removal). */
|
|
176
|
+
export function resolveSortableMove(source, target, sameList, fromIndex, toIndex) {
|
|
177
|
+
return {
|
|
178
|
+
from: childPath(source.parent, source.slot, fromIndex),
|
|
179
|
+
target: {
|
|
180
|
+
parent: target.parent,
|
|
181
|
+
slot: target.slot,
|
|
182
|
+
index: sameList && toIndex > fromIndex ? toIndex + 1 : toIndex,
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
/** A component swapped in under an existing node: its default props, with a
|
|
187
|
+
* string `children` default as a default-slot text child (children is the
|
|
188
|
+
* default slot, not a prop). */
|
|
189
|
+
export function seedNode(component) {
|
|
190
|
+
const props = { ...component.defaultProps };
|
|
191
|
+
const slots = {};
|
|
192
|
+
if (typeof props.children === 'string')
|
|
193
|
+
slots[DEFAULT_SLOT] = [{ kind: 'text', value: props.children }];
|
|
194
|
+
delete props.children;
|
|
195
|
+
return { kind: 'element', component: component.name, props, slots };
|
|
196
|
+
}
|
|
197
|
+
/** A component added through the add dialog. With `props` the node carries them
|
|
198
|
+
* plus the component's curated slot content (the default or a named scenario);
|
|
199
|
+
* without, it comes in empty - no props, no content. */
|
|
200
|
+
export function seedComponentNode(component, props) {
|
|
201
|
+
if (!props)
|
|
202
|
+
return { kind: 'element', component: component.name, props: {}, slots: {} };
|
|
203
|
+
const nodeProps = { ...props };
|
|
204
|
+
const slots = seedSlots(component.content, nodeProps);
|
|
205
|
+
delete nodeProps.children;
|
|
206
|
+
return {
|
|
207
|
+
kind: 'element',
|
|
208
|
+
component: component.name,
|
|
209
|
+
props: nodeProps,
|
|
210
|
+
slots,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
/** The root seeded from its story file content, so the tree starts from what the
|
|
214
|
+
* browse view renders, with `props` (a story's scenario-merged props) or the
|
|
215
|
+
* component's defaults. */
|
|
216
|
+
export function seedRoot(component, props) {
|
|
217
|
+
const rootProps = { ...(props ?? component.defaultProps) };
|
|
218
|
+
const slots = seedSlots(component.content, rootProps);
|
|
219
|
+
delete rootProps.children;
|
|
220
|
+
return { root: component.name, props: rootProps, slots };
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* A key for a tree's slot content, equal for two trees of the same content
|
|
224
|
+
* whatever their node ids or the order of their props' keys: what the
|
|
225
|
+
* regeneration payload would carry, serialized with sorted keys.
|
|
226
|
+
*/
|
|
227
|
+
export function slotsKey(slots) {
|
|
228
|
+
return stableStringify(toPayload({ root: '', props: {}, slots }).slots);
|
|
229
|
+
}
|
|
230
|
+
/** A value as JSON with every object's keys sorted, so two values equal by
|
|
231
|
+
* content serialize the same; `undefined` entries are left out. */
|
|
232
|
+
export function stableStringify(value) {
|
|
233
|
+
if (Array.isArray(value))
|
|
234
|
+
return `[${value.map(stableStringify).join(',')}]`;
|
|
235
|
+
if (typeof value === 'object' && value !== null) {
|
|
236
|
+
const entries = Object.entries(value)
|
|
237
|
+
.filter(([, entry]) => entry !== undefined)
|
|
238
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
239
|
+
return `{${entries.map(([key, entry]) => `${JSON.stringify(key)}:${stableStringify(entry)}`).join(',')}}`;
|
|
240
|
+
}
|
|
241
|
+
return JSON.stringify(value) ?? 'null';
|
|
242
|
+
}
|
|
243
|
+
// --- serialization to the regeneration payload -------------------------------------
|
|
244
|
+
/**
|
|
245
|
+
* The props as JSON, each one left unset (`undefined`) left out. An empty
|
|
246
|
+
* string is a value like any other and stays, so a component given `''`
|
|
247
|
+
* keeps it on every update; a story unsets a prop with `null`.
|
|
248
|
+
*
|
|
249
|
+
* @param record - The props, from the controls or a pane's write-back.
|
|
250
|
+
* @returns The props that are set.
|
|
251
|
+
*/
|
|
252
|
+
export function cleanRecord(record) {
|
|
253
|
+
const out = {};
|
|
254
|
+
for (const [key, value] of Object.entries(record))
|
|
255
|
+
if (value !== undefined)
|
|
256
|
+
out[key] = value;
|
|
257
|
+
return out;
|
|
258
|
+
}
|
|
259
|
+
function listToFill(nodes) {
|
|
260
|
+
const fills = nodes.map(nodeToFill);
|
|
261
|
+
if (fills.length === 0)
|
|
262
|
+
return undefined;
|
|
263
|
+
if (fills.length === 1)
|
|
264
|
+
return fills[0];
|
|
265
|
+
return { kind: 'fragment', children: fills };
|
|
266
|
+
}
|
|
267
|
+
export function namedFills(slots) {
|
|
268
|
+
const out = {};
|
|
269
|
+
for (const [name, nodes] of Object.entries(slots)) {
|
|
270
|
+
if (name === DEFAULT_SLOT)
|
|
271
|
+
continue;
|
|
272
|
+
const fill = listToFill(nodes);
|
|
273
|
+
if (fill)
|
|
274
|
+
out[name] = fill;
|
|
275
|
+
}
|
|
276
|
+
return out;
|
|
277
|
+
}
|
|
278
|
+
function nodeToFill(node) {
|
|
279
|
+
if (node.kind === 'text')
|
|
280
|
+
return { kind: 'text', value: node.value };
|
|
281
|
+
if (node.kind === 'opaque')
|
|
282
|
+
return node.fill;
|
|
283
|
+
const children = listToFill(node.slots[DEFAULT_SLOT] ?? []);
|
|
284
|
+
if (node.kind === 'html') {
|
|
285
|
+
return {
|
|
286
|
+
kind: 'html',
|
|
287
|
+
tag: node.tag,
|
|
288
|
+
props: cleanRecord(node.props),
|
|
289
|
+
...(children ? { children } : {}),
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
const named = namedFills(node.slots);
|
|
293
|
+
return {
|
|
294
|
+
kind: 'element',
|
|
295
|
+
component: node.component,
|
|
296
|
+
props: cleanRecord(node.props),
|
|
297
|
+
...(children ? { children } : {}),
|
|
298
|
+
...(Object.keys(named).length ? { slots: named } : {}),
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
// --- seeding from story file content (SlotFill → EditorNode) --------------------
|
|
302
|
+
// Inverse of nodeToFill: turn a story file's SlotFill into the editor's node list so
|
|
303
|
+
// a per-story tree starts from the curated content the browse view renders. A
|
|
304
|
+
// `fragment` flattens into its siblings; a `raw` or `scoped` fill or a call to
|
|
305
|
+
// a part becomes one opaque node that keeps the fill as authored.
|
|
306
|
+
export function fillToNodes(fill) {
|
|
307
|
+
if (!fill)
|
|
308
|
+
return [];
|
|
309
|
+
if (fill.kind === 'text')
|
|
310
|
+
return [{ kind: 'text', value: fill.value }];
|
|
311
|
+
if (fill.kind === 'fragment')
|
|
312
|
+
return fill.children.flatMap(fillToNodes);
|
|
313
|
+
if (fill.kind === 'raw' || fill.kind === 'scoped' || fill.kind === 'part')
|
|
314
|
+
return [{ kind: 'opaque', fill }];
|
|
315
|
+
const slots = {};
|
|
316
|
+
const def = fillToNodes(fill.children);
|
|
317
|
+
if (def.length)
|
|
318
|
+
slots[DEFAULT_SLOT] = def;
|
|
319
|
+
if (fill.kind === 'html') {
|
|
320
|
+
return [
|
|
321
|
+
{ kind: 'html', tag: fill.tag, props: { ...(fill.props ?? {}) }, slots },
|
|
322
|
+
];
|
|
323
|
+
}
|
|
324
|
+
for (const [name, named] of Object.entries(fill.slots ?? {})) {
|
|
325
|
+
const nodes = fillToNodes(named);
|
|
326
|
+
if (nodes.length)
|
|
327
|
+
slots[name] = nodes;
|
|
328
|
+
}
|
|
329
|
+
return [
|
|
330
|
+
{
|
|
331
|
+
kind: 'element',
|
|
332
|
+
component: fill.component,
|
|
333
|
+
props: { ...(fill.props ?? {}) },
|
|
334
|
+
slots,
|
|
335
|
+
},
|
|
336
|
+
];
|
|
337
|
+
}
|
|
338
|
+
/** Seed a root component's slot content from its story file SlotPlan, falling back to
|
|
339
|
+
* a default-slot text child when a string `children` prop is the only content. */
|
|
340
|
+
export function seedSlots(content, props) {
|
|
341
|
+
if (content) {
|
|
342
|
+
const slots = {};
|
|
343
|
+
const def = fillToNodes(content.default);
|
|
344
|
+
if (def.length)
|
|
345
|
+
slots[DEFAULT_SLOT] = def;
|
|
346
|
+
for (const [name, fill] of Object.entries(content.named ?? {})) {
|
|
347
|
+
const nodes = fillToNodes(fill);
|
|
348
|
+
if (nodes.length)
|
|
349
|
+
slots[name] = nodes;
|
|
350
|
+
}
|
|
351
|
+
return slots;
|
|
352
|
+
}
|
|
353
|
+
if (typeof props.children === 'string')
|
|
354
|
+
return { [DEFAULT_SLOT]: [{ kind: 'text', value: props.children }] };
|
|
355
|
+
return {};
|
|
356
|
+
}
|
|
357
|
+
/** The regeneration payload for a root component, its props and its slot
|
|
358
|
+
* content. */
|
|
359
|
+
export function toPayload(state) {
|
|
360
|
+
const def = listToFill(state.slots[DEFAULT_SLOT] ?? []);
|
|
361
|
+
const named = namedFills(state.slots);
|
|
362
|
+
const hasContent = def || Object.keys(named).length > 0;
|
|
363
|
+
return {
|
|
364
|
+
rootComponent: state.root,
|
|
365
|
+
props: cleanRecord(state.props),
|
|
366
|
+
slots: hasContent
|
|
367
|
+
? {
|
|
368
|
+
...(def ? { default: def } : {}),
|
|
369
|
+
...(Object.keys(named).length ? { named } : {}),
|
|
370
|
+
}
|
|
371
|
+
: undefined,
|
|
372
|
+
};
|
|
373
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { JsonValue, SlotPlan, StoryAround } from '../lib/types.js';
|
|
2
|
+
/** A story to print as html: the root component, its props and its slot content. */
|
|
3
|
+
export interface HtmlSourceRequest {
|
|
4
|
+
/** The root component's name. */
|
|
5
|
+
rootComponent: string;
|
|
6
|
+
/** The props to render it with. */
|
|
7
|
+
props: Record<string, JsonValue>;
|
|
8
|
+
/** Its slot content: the default slot and the named slots. */
|
|
9
|
+
slots?: SlotPlan;
|
|
10
|
+
/** The content beside the root, as its siblings: `before` and `after` it. */
|
|
11
|
+
around?: StoryAround;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A story's html: its markup, with the styles the kit's build wrote for the
|
|
15
|
+
* components it renders when there are any and the warnings for what the
|
|
16
|
+
* markup leaves out when there are any, or what keeps it from having markup.
|
|
17
|
+
*/
|
|
18
|
+
export type HtmlSourceResponse = {
|
|
19
|
+
markup: string;
|
|
20
|
+
stylesheet?: string;
|
|
21
|
+
warnings?: string[];
|
|
22
|
+
} | {
|
|
23
|
+
missing: string[];
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Prints a story's html. It resolves with the markup or what the story holds
|
|
27
|
+
* that has no markup, and rejects with the reason it could not print; the
|
|
28
|
+
* controller aborts `signal` when the story changes before it answers.
|
|
29
|
+
*/
|
|
30
|
+
export type HtmlSourceTransport = (request: HtmlSourceRequest, signal: AbortSignal) => Promise<HtmlSourceResponse>;
|
|
31
|
+
/**
|
|
32
|
+
* Reads the html printed ahead of time for a component's scenarios, by
|
|
33
|
+
* scenario id (`''` for the anonymous default). It rejects when there is
|
|
34
|
+
* none to read.
|
|
35
|
+
*/
|
|
36
|
+
export type ScenarioHtmlSourceLoader = (componentId: string) => Promise<Record<string, HtmlSourceResponse>>;
|
|
37
|
+
/** The path the preview's development server answers html source requests on. */
|
|
38
|
+
export declare const HTML_SOURCE_ENDPOINT = "/__preview_html_source";
|
|
39
|
+
/** The directory, under the URL the preview is served from, that the static
|
|
40
|
+
* build keeps each component's scenario html in, one `<component id>.json`
|
|
41
|
+
* each. */
|
|
42
|
+
export declare const HTML_SOURCE_DIRECTORY = "html-source";
|
|
43
|
+
/**
|
|
44
|
+
* A transport that POSTs the story as JSON to the development server's html
|
|
45
|
+
* source endpoint and reads the JSON it answers. A response that is not ok
|
|
46
|
+
* rejects with the response's text, or its status when the text is empty.
|
|
47
|
+
*
|
|
48
|
+
* @param endpoint - The endpoint's path; the development server's by default.
|
|
49
|
+
* @param fetchImplementation - The `fetch` to use; the global one by default.
|
|
50
|
+
* @returns The transport.
|
|
51
|
+
*/
|
|
52
|
+
export declare function createFetchHtmlSourceTransport(endpoint?: string, fetchImplementation?: typeof fetch): HtmlSourceTransport;
|
|
53
|
+
/**
|
|
54
|
+
* A loader that fetches a component's scenario html from the files the
|
|
55
|
+
* static build serves, `<baseUrl>html-source/<component id>.json`, once per
|
|
56
|
+
* component.
|
|
57
|
+
*
|
|
58
|
+
* @param baseUrl - The URL the preview is served under, ending in `/`.
|
|
59
|
+
* @param fetchImplementation - The `fetch` to use; the global one by default.
|
|
60
|
+
* @returns The loader.
|
|
61
|
+
*/
|
|
62
|
+
export declare function createStaticHtmlSourceLoader(baseUrl: string, fetchImplementation?: typeof fetch): ScenarioHtmlSourceLoader;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Where the controller gets a story's html source: the markup the story
|
|
2
|
+
// renders to, which needs the kit's templates and so cannot be printed in the
|
|
3
|
+
// browser. The development server prints it for any story on request; the
|
|
4
|
+
// static build prints it once per scenario and serves one file per
|
|
5
|
+
// component. A controller with neither reports html as unavailable.
|
|
6
|
+
// Browser-safe.
|
|
7
|
+
/** The path the preview's development server answers html source requests on. */
|
|
8
|
+
export const HTML_SOURCE_ENDPOINT = '/__preview_html_source';
|
|
9
|
+
/** The directory, under the URL the preview is served from, that the static
|
|
10
|
+
* build keeps each component's scenario html in, one `<component id>.json`
|
|
11
|
+
* each. */
|
|
12
|
+
export const HTML_SOURCE_DIRECTORY = 'html-source';
|
|
13
|
+
/**
|
|
14
|
+
* A transport that POSTs the story as JSON to the development server's html
|
|
15
|
+
* source endpoint and reads the JSON it answers. A response that is not ok
|
|
16
|
+
* rejects with the response's text, or its status when the text is empty.
|
|
17
|
+
*
|
|
18
|
+
* @param endpoint - The endpoint's path; the development server's by default.
|
|
19
|
+
* @param fetchImplementation - The `fetch` to use; the global one by default.
|
|
20
|
+
* @returns The transport.
|
|
21
|
+
*/
|
|
22
|
+
export function createFetchHtmlSourceTransport(endpoint = HTML_SOURCE_ENDPOINT, fetchImplementation = (...parameters) => fetch(...parameters)) {
|
|
23
|
+
return async (request, signal) => {
|
|
24
|
+
const response = await fetchImplementation(endpoint, {
|
|
25
|
+
method: 'POST',
|
|
26
|
+
body: JSON.stringify(request),
|
|
27
|
+
signal,
|
|
28
|
+
});
|
|
29
|
+
if (!response.ok) {
|
|
30
|
+
const text = await response.text().catch(() => '');
|
|
31
|
+
throw new Error(text || `HTTP ${response.status}`);
|
|
32
|
+
}
|
|
33
|
+
return (await response.json());
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A loader that fetches a component's scenario html from the files the
|
|
38
|
+
* static build serves, `<baseUrl>html-source/<component id>.json`, once per
|
|
39
|
+
* component.
|
|
40
|
+
*
|
|
41
|
+
* @param baseUrl - The URL the preview is served under, ending in `/`.
|
|
42
|
+
* @param fetchImplementation - The `fetch` to use; the global one by default.
|
|
43
|
+
* @returns The loader.
|
|
44
|
+
*/
|
|
45
|
+
export function createStaticHtmlSourceLoader(baseUrl, fetchImplementation = (...parameters) => fetch(...parameters)) {
|
|
46
|
+
const loaded = new Map();
|
|
47
|
+
return (componentId) => {
|
|
48
|
+
let pending = loaded.get(componentId);
|
|
49
|
+
if (!pending) {
|
|
50
|
+
pending = fetchImplementation(`${baseUrl}${HTML_SOURCE_DIRECTORY}/${encodeURIComponent(componentId)}.json`).then(async (response) => {
|
|
51
|
+
if (!response.ok)
|
|
52
|
+
throw new Error(`HTTP ${response.status}`);
|
|
53
|
+
return (await response.json());
|
|
54
|
+
});
|
|
55
|
+
// A failed read is tried again the next time.
|
|
56
|
+
pending.catch(() => loaded.delete(componentId));
|
|
57
|
+
loaded.set(componentId, pending);
|
|
58
|
+
}
|
|
59
|
+
return pending;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { createPreviewController, PREVIEW_FRAME_SHOWN_ATTRIBUTE, REGENERATION_DEBOUNCE_MILLISECONDS, SCRATCH_STORY_ID, type PreviewController, type PreviewControllerOptions, type PreviewEditorState, type PreviewEvent, type PreviewFrameReadiness, type PreviewFrameTarget, type PreviewHtmlSource, type PreviewInsertTarget, type PreviewNodeRequest, type PreviewOptionDescriptor, type PreviewRegenerationStatus, type PreviewSelection, type PreviewSortableDrop, type PreviewCode, type PreviewState, type PreviewTimers, } from './create-preview-controller.js';
|
|
2
|
+
export { printStorySource, type PreviewTargetCode } from './source.js';
|
|
3
|
+
export { createFetchHtmlSourceTransport, createStaticHtmlSourceLoader, HTML_SOURCE_DIRECTORY, HTML_SOURCE_ENDPOINT, type HtmlSourceRequest, type HtmlSourceResponse, type HtmlSourceTransport, type ScenarioHtmlSourceLoader, } from './html-source.js';
|
|
4
|
+
export { previewStore, type PreviewStore } from './store.js';
|
|
5
|
+
export type { PreviewPaneWidth } from '../lib/types.js';
|
|
6
|
+
export { catalogFromManifest, DEFAULT_PANE_WIDTHS, defaultScenarioId, nodeAddSlots, nodeSlots, scenarioChoices, storyFrameKey, type CatalogManifest, type PreviewBinding, type PreviewCatalog, type PreviewCatalogView, type PreviewComponent, type PreviewImport, type PreviewLanding, type PreviewNavigationNode, type PreviewPropType, type PreviewScenario, type PreviewSource, type PreviewStoryReference, type ScenarioChoice, } from './catalog.js';
|
|
7
|
+
export { acceptsPropValue, changedProps, isPaneWidth, MAXIMUM_PANE_WIDTH, parseTargets, parseOptionValue, parsePaneWidth, parseProps, readUrlState, storageKey, URL_PARAMETER, writeUrlState, type PreviewStorage, type UrlState, type UrlStateToWrite, } from './persistence.js';
|
|
8
|
+
export { createFetchRegenerationTransport, REGENERATION_ENDPOINT, type RegenerationPayload, type RegenerationTransport, } from './regeneration.js';
|
|
9
|
+
export { childPath, cleanRecord, DEFAULT_SLOT, HTML_TAGS, isContainer, nodeAt, pathKey, seedNode, type AroundFill, type ContainerNode, type DropTarget, type EditorNode, type EditorState, type NodePath, type OpaqueFill, type SortableListAddress, type Step, type StoryAround, } from './editor-tree.js';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// The preview controller and what a shell reads from it: the catalog, the
|
|
2
|
+
// state, the editor's tree types and operations, the regeneration transport, and the
|
|
3
|
+
// query parameters it keeps its state in.
|
|
4
|
+
// Browser-safe: no node imports.
|
|
5
|
+
export { createPreviewController, PREVIEW_FRAME_SHOWN_ATTRIBUTE, REGENERATION_DEBOUNCE_MILLISECONDS, SCRATCH_STORY_ID, } from './create-preview-controller.js';
|
|
6
|
+
export { printStorySource } from './source.js';
|
|
7
|
+
export { createFetchHtmlSourceTransport, createStaticHtmlSourceLoader, HTML_SOURCE_DIRECTORY, HTML_SOURCE_ENDPOINT, } from './html-source.js';
|
|
8
|
+
export { previewStore } from './store.js';
|
|
9
|
+
export { catalogFromManifest, DEFAULT_PANE_WIDTHS, defaultScenarioId, nodeAddSlots, nodeSlots, scenarioChoices, storyFrameKey, } from './catalog.js';
|
|
10
|
+
export { acceptsPropValue, changedProps, isPaneWidth, MAXIMUM_PANE_WIDTH, parseTargets, parseOptionValue, parsePaneWidth, parseProps, readUrlState, storageKey, URL_PARAMETER, writeUrlState, } from './persistence.js';
|
|
11
|
+
export { createFetchRegenerationTransport, REGENERATION_ENDPOINT, } from './regeneration.js';
|
|
12
|
+
export { childPath, cleanRecord, DEFAULT_SLOT, HTML_TAGS, isContainer, nodeAt, pathKey, seedNode, } from './editor-tree.js';
|