@pyreon/storybook 0.10.0 → 0.11.1

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.
@@ -5386,7 +5386,7 @@ var drawChart = (function (exports) {
5386
5386
  </script>
5387
5387
  <script>
5388
5388
  /*<!--*/
5389
- const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"home/runner/work/fundamentals/fundamentals/node_modules/.bun/@pyreon+core@0.7.11/node_modules/@pyreon/core/lib/jsx-runtime.js","uid":"433454f0-1"},{"name":"src","children":[{"uid":"433454f0-3","name":"render-impl.tsx"},{"uid":"433454f0-5","name":"index.ts"}]}]}],"isRoot":true},"nodeParts":{"433454f0-1":{"renderedLength":1641,"gzipLength":828,"brotliLength":0,"metaUid":"433454f0-0"},"433454f0-3":{"renderedLength":1280,"gzipLength":673,"brotliLength":0,"metaUid":"433454f0-2"},"433454f0-5":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"433454f0-4"}},"nodeMetas":{"433454f0-0":{"id":"/home/runner/work/fundamentals/fundamentals/node_modules/.bun/@pyreon+core@0.7.11/node_modules/@pyreon/core/lib/jsx-runtime.js","moduleParts":{"index.js":"433454f0-1"},"imported":[],"importedBy":[{"uid":"433454f0-2"}]},"433454f0-2":{"id":"/src/render-impl.tsx","moduleParts":{"index.js":"433454f0-3"},"imported":[{"uid":"433454f0-9"},{"uid":"433454f0-0"}],"importedBy":[{"uid":"433454f0-6"}]},"433454f0-4":{"id":"/src/index.ts","moduleParts":{"index.js":"433454f0-5"},"imported":[{"uid":"433454f0-6"},{"uid":"433454f0-7"},{"uid":"433454f0-8"},{"uid":"433454f0-9"}],"importedBy":[],"isEntry":true},"433454f0-6":{"id":"/src/render.ts","moduleParts":{},"imported":[{"uid":"433454f0-2"}],"importedBy":[{"uid":"433454f0-4"}]},"433454f0-7":{"id":"@pyreon/core","moduleParts":{},"imported":[],"importedBy":[{"uid":"433454f0-4"}]},"433454f0-8":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"433454f0-4"}]},"433454f0-9":{"id":"@pyreon/runtime-dom","moduleParts":{},"imported":[],"importedBy":[{"uid":"433454f0-4"},{"uid":"433454f0-2"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
5389
+ const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"92260a4b-1","name":"render-impl.tsx"},{"uid":"92260a4b-3","name":"index.ts"}]}]}],"isRoot":true},"nodeParts":{"92260a4b-1":{"renderedLength":1280,"gzipLength":673,"brotliLength":0,"metaUid":"92260a4b-0"},"92260a4b-3":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"92260a4b-2"}},"nodeMetas":{"92260a4b-0":{"id":"/src/render-impl.tsx","moduleParts":{"index.js":"92260a4b-1"},"imported":[{"uid":"92260a4b-7"},{"uid":"92260a4b-8"}],"importedBy":[{"uid":"92260a4b-4"}]},"92260a4b-2":{"id":"/src/index.ts","moduleParts":{"index.js":"92260a4b-3"},"imported":[{"uid":"92260a4b-4"},{"uid":"92260a4b-5"},{"uid":"92260a4b-6"},{"uid":"92260a4b-7"}],"importedBy":[],"isEntry":true},"92260a4b-4":{"id":"/src/render.ts","moduleParts":{},"imported":[{"uid":"92260a4b-0"}],"importedBy":[{"uid":"92260a4b-2"}]},"92260a4b-5":{"id":"@pyreon/core","moduleParts":{},"imported":[],"importedBy":[{"uid":"92260a4b-2"}]},"92260a4b-6":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"92260a4b-2"}]},"92260a4b-7":{"id":"@pyreon/runtime-dom","moduleParts":{},"imported":[],"importedBy":[{"uid":"92260a4b-2"},{"uid":"92260a4b-0"}]},"92260a4b-8":{"id":"@pyreon/core/jsx-runtime","moduleParts":{},"imported":[],"importedBy":[{"uid":"92260a4b-0"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
5390
5390
 
5391
5391
  const run = () => {
5392
5392
  const width = window.innerWidth;
@@ -5386,7 +5386,7 @@ var drawChart = (function (exports) {
5386
5386
  </script>
5387
5387
  <script>
5388
5388
  /*<!--*/
5389
- const data = {"version":2,"tree":{"name":"root","children":[{"name":"preview.js","children":[{"name":"home/runner/work/fundamentals/fundamentals/node_modules/.bun/@pyreon+core@0.7.11/node_modules/@pyreon/core/lib/jsx-runtime.js","uid":"5064bfa4-1"},{"name":"src","children":[{"uid":"5064bfa4-3","name":"render-impl.tsx"},{"uid":"5064bfa4-5","name":"preview-impl.tsx"},{"uid":"5064bfa4-7","name":"preview.ts"}]}]}],"isRoot":true},"nodeParts":{"5064bfa4-1":{"renderedLength":1635,"gzipLength":825,"brotliLength":0,"metaUid":"5064bfa4-0"},"5064bfa4-3":{"renderedLength":1100,"gzipLength":595,"brotliLength":0,"metaUid":"5064bfa4-2"},"5064bfa4-5":{"renderedLength":452,"gzipLength":313,"brotliLength":0,"metaUid":"5064bfa4-4"},"5064bfa4-7":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"5064bfa4-6"}},"nodeMetas":{"5064bfa4-0":{"id":"/home/runner/work/fundamentals/fundamentals/node_modules/.bun/@pyreon+core@0.7.11/node_modules/@pyreon/core/lib/jsx-runtime.js","moduleParts":{"preview.js":"5064bfa4-1"},"imported":[],"importedBy":[{"uid":"5064bfa4-4"},{"uid":"5064bfa4-2"}]},"5064bfa4-2":{"id":"/src/render-impl.tsx","moduleParts":{"preview.js":"5064bfa4-3"},"imported":[{"uid":"5064bfa4-8"},{"uid":"5064bfa4-0"}],"importedBy":[{"uid":"5064bfa4-4"}]},"5064bfa4-4":{"id":"/src/preview-impl.tsx","moduleParts":{"preview.js":"5064bfa4-5"},"imported":[{"uid":"5064bfa4-2"},{"uid":"5064bfa4-0"}],"importedBy":[{"uid":"5064bfa4-6"}]},"5064bfa4-6":{"id":"/src/preview.ts","moduleParts":{"preview.js":"5064bfa4-7"},"imported":[{"uid":"5064bfa4-4"}],"importedBy":[],"isEntry":true},"5064bfa4-8":{"id":"@pyreon/runtime-dom","moduleParts":{},"imported":[],"importedBy":[{"uid":"5064bfa4-2"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
5389
+ const data = {"version":2,"tree":{"name":"root","children":[{"name":"preview.js","children":[{"name":"src","children":[{"uid":"08ed9fb2-1","name":"render-impl.tsx"},{"uid":"08ed9fb2-3","name":"preview-impl.tsx"},{"uid":"08ed9fb2-5","name":"preview.ts"}]}]}],"isRoot":true},"nodeParts":{"08ed9fb2-1":{"renderedLength":1100,"gzipLength":595,"brotliLength":0,"metaUid":"08ed9fb2-0"},"08ed9fb2-3":{"renderedLength":452,"gzipLength":313,"brotliLength":0,"metaUid":"08ed9fb2-2"},"08ed9fb2-5":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"08ed9fb2-4"}},"nodeMetas":{"08ed9fb2-0":{"id":"/src/render-impl.tsx","moduleParts":{"preview.js":"08ed9fb2-1"},"imported":[{"uid":"08ed9fb2-7"},{"uid":"08ed9fb2-6"}],"importedBy":[{"uid":"08ed9fb2-2"}]},"08ed9fb2-2":{"id":"/src/preview-impl.tsx","moduleParts":{"preview.js":"08ed9fb2-3"},"imported":[{"uid":"08ed9fb2-0"},{"uid":"08ed9fb2-6"}],"importedBy":[{"uid":"08ed9fb2-4"}]},"08ed9fb2-4":{"id":"/src/preview.ts","moduleParts":{"preview.js":"08ed9fb2-5"},"imported":[{"uid":"08ed9fb2-2"}],"importedBy":[],"isEntry":true},"08ed9fb2-6":{"id":"@pyreon/core/jsx-runtime","moduleParts":{},"imported":[],"importedBy":[{"uid":"08ed9fb2-2"},{"uid":"08ed9fb2-0"}]},"08ed9fb2-7":{"id":"@pyreon/runtime-dom","moduleParts":{},"imported":[],"importedBy":[{"uid":"08ed9fb2-0"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
5390
5390
 
5391
5391
  const run = () => {
5392
5392
  const width = window.innerWidth;
package/lib/index.js CHANGED
@@ -1,56 +1,8 @@
1
1
  import { mount, mount as mount$1 } from "@pyreon/runtime-dom";
2
+ import { jsx } from "@pyreon/core/jsx-runtime";
2
3
  import { Fragment, h } from "@pyreon/core";
3
4
  import { computed, effect, signal } from "@pyreon/reactivity";
4
5
 
5
- //#region ../../node_modules/.bun/@pyreon+core@0.7.11/node_modules/@pyreon/core/lib/jsx-runtime.js
6
- /**
7
- * Hyperscript function — the compiled output of JSX.
8
- * `<div class="x">hello</div>` → `h("div", { class: "x" }, "hello")`
9
- *
10
- * Generic on P so TypeScript validates props match the component's signature
11
- * at the call site, then stores the result in the loosely-typed VNode.
12
- */
13
- /** Shared empty props sentinel — identity-checked in mountElement to skip applyProps. */
14
- const EMPTY_PROPS = {};
15
- function h$1(type, props, ...children) {
16
- return {
17
- type,
18
- props: props ?? EMPTY_PROPS,
19
- children: normalizeChildren(children),
20
- key: props?.key ?? null
21
- };
22
- }
23
- function normalizeChildren(children) {
24
- for (let i = 0; i < children.length; i++) if (Array.isArray(children[i])) return flattenChildren(children);
25
- return children;
26
- }
27
- function flattenChildren(children) {
28
- const result = [];
29
- for (const child of children) if (Array.isArray(child)) result.push(...flattenChildren(child));
30
- else result.push(child);
31
- return result;
32
- }
33
- /**
34
- * JSX automatic runtime.
35
- *
36
- * When tsconfig has `"jsxImportSource": "@pyreon/core"`, the TS/bundler compiler
37
- * rewrites JSX to imports from this file automatically:
38
- * <div class="x" /> → jsx("div", { class: "x" })
39
- */
40
- function jsx(type, props, key) {
41
- const { children, ...rest } = props;
42
- const propsWithKey = key != null ? {
43
- ...rest,
44
- key
45
- } : rest;
46
- if (typeof type === "function") return h$1(type, children !== void 0 ? {
47
- ...propsWithKey,
48
- children
49
- } : propsWithKey);
50
- return h$1(type, propsWithKey, ...children === void 0 ? [] : Array.isArray(children) ? children : [children]);
51
- }
52
-
53
- //#endregion
54
6
  //#region src/render-impl.tsx
55
7
  /**
56
8
  * State tracked per canvas element so we can clean up between renders.
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["h","mount"],"sources":["../../../node_modules/.bun/@pyreon+core@0.7.11/node_modules/@pyreon/core/lib/jsx-runtime.js","../src/render-impl.tsx"],"sourcesContent":["//#region src/h.ts\n/** Marker for fragment nodes — renders children without a wrapper element */\nconst Fragment = Symbol(\"Pyreon.Fragment\");\n/**\n* Hyperscript function — the compiled output of JSX.\n* `<div class=\"x\">hello</div>` → `h(\"div\", { class: \"x\" }, \"hello\")`\n*\n* Generic on P so TypeScript validates props match the component's signature\n* at the call site, then stores the result in the loosely-typed VNode.\n*/\n/** Shared empty props sentinel — identity-checked in mountElement to skip applyProps. */\nconst EMPTY_PROPS = {};\nfunction h(type, props, ...children) {\n\treturn {\n\t\ttype,\n\t\tprops: props ?? EMPTY_PROPS,\n\t\tchildren: normalizeChildren(children),\n\t\tkey: props?.key ?? null\n\t};\n}\nfunction normalizeChildren(children) {\n\tfor (let i = 0; i < children.length; i++) if (Array.isArray(children[i])) return flattenChildren(children);\n\treturn children;\n}\nfunction flattenChildren(children) {\n\tconst result = [];\n\tfor (const child of children) if (Array.isArray(child)) result.push(...flattenChildren(child));\n\telse result.push(child);\n\treturn result;\n}\n\n//#endregion\n//#region src/jsx-runtime.ts\n/**\n* JSX automatic runtime.\n*\n* When tsconfig has `\"jsxImportSource\": \"@pyreon/core\"`, the TS/bundler compiler\n* rewrites JSX to imports from this file automatically:\n* <div class=\"x\" /> → jsx(\"div\", { class: \"x\" })\n*/\nfunction jsx(type, props, key) {\n\tconst { children, ...rest } = props;\n\tconst propsWithKey = key != null ? {\n\t\t...rest,\n\t\tkey\n\t} : rest;\n\tif (typeof type === \"function\") return h(type, children !== void 0 ? {\n\t\t...propsWithKey,\n\t\tchildren\n\t} : propsWithKey);\n\treturn h(type, propsWithKey, ...children === void 0 ? [] : Array.isArray(children) ? children : [children]);\n}\nconst jsxs = jsx;\n\n//#endregion\nexport { Fragment, jsx, jsxs };\n//# sourceMappingURL=jsx-runtime.js.map","import type { ComponentFn, VNodeChild } from '@pyreon/core'\nimport { mount } from '@pyreon/runtime-dom'\n\n/**\n * State tracked per canvas element so we can clean up between renders.\n */\nconst canvasState = new WeakMap<HTMLElement, () => void>()\n\n/**\n * Render a Pyreon story into a Storybook canvas element.\n *\n * This is the core integration point — Storybook calls this function\n * every time a story needs to be displayed or re-rendered (e.g. when\n * the user changes args via the Controls panel).\n *\n * It handles:\n * 1. Cleaning up the previous mount (disposing effects, removing DOM)\n * 2. Building the VNode from the story function or component + args\n * 3. Mounting the new VNode into the canvas\n */\nexport function renderToCanvas(\n {\n storyFn,\n showMain,\n showError,\n }: {\n storyFn: () => VNodeChild\n storyContext: {\n component?: ComponentFn<any>\n args: Record<string, unknown>\n [key: string]: unknown\n }\n showMain: () => void\n showError: (error: { title: string; description: string }) => void\n forceRemount: boolean\n },\n canvasElement: HTMLElement,\n): void {\n // Always clean up the previous render\n const prevUnmount = canvasState.get(canvasElement)\n if (prevUnmount) {\n prevUnmount()\n canvasState.delete(canvasElement)\n }\n\n try {\n const element = storyFn()\n const unmount = mount(element, canvasElement)\n canvasState.set(canvasElement, unmount)\n showMain()\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err))\n showError({\n title: `Error rendering story`,\n description: error.message,\n })\n }\n}\n\n/**\n * Default render implementation used when no custom `render` is provided.\n */\nexport function defaultRender(\n component: ComponentFn<any>,\n args: Record<string, unknown>,\n): VNodeChild {\n const Component = component\n return <Component {...args} />\n}\n"],"x_google_ignoreList":[0],"mappings":";;;;;;;;;;;;;AAWA,MAAM,cAAc,EAAE;AACtB,SAASA,IAAE,MAAM,OAAO,GAAG,UAAU;AACpC,QAAO;EACN;EACA,OAAO,SAAS;EAChB,UAAU,kBAAkB,SAAS;EACrC,KAAK,OAAO,OAAO;EACnB;;AAEF,SAAS,kBAAkB,UAAU;AACpC,MAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,IAAK,KAAI,MAAM,QAAQ,SAAS,GAAG,CAAE,QAAO,gBAAgB,SAAS;AAC1G,QAAO;;AAER,SAAS,gBAAgB,UAAU;CAClC,MAAM,SAAS,EAAE;AACjB,MAAK,MAAM,SAAS,SAAU,KAAI,MAAM,QAAQ,MAAM,CAAE,QAAO,KAAK,GAAG,gBAAgB,MAAM,CAAC;KACzF,QAAO,KAAK,MAAM;AACvB,QAAO;;;;;;;;;AAYR,SAAS,IAAI,MAAM,OAAO,KAAK;CAC9B,MAAM,EAAE,UAAU,GAAG,SAAS;CAC9B,MAAM,eAAe,OAAO,OAAO;EAClC,GAAG;EACH;EACA,GAAG;AACJ,KAAI,OAAO,SAAS,WAAY,QAAOA,IAAE,MAAM,aAAa,KAAK,IAAI;EACpE,GAAG;EACH;EACA,GAAG,aAAa;AACjB,QAAOA,IAAE,MAAM,cAAc,GAAG,aAAa,KAAK,IAAI,EAAE,GAAG,MAAM,QAAQ,SAAS,GAAG,WAAW,CAAC,SAAS,CAAC;;;;;;;;AC5C5G,MAAM,8BAAc,IAAI,SAAkC;;;;;;;;;;;;;AAc1D,SAAgB,eACd,EACE,SACA,UACA,aAYF,eACM;CAEN,MAAM,cAAc,YAAY,IAAI,cAAc;AAClD,KAAI,aAAa;AACf,eAAa;AACb,cAAY,OAAO,cAAc;;AAGnC,KAAI;EAEF,MAAM,UAAUC,QADA,SAAS,EACM,cAAc;AAC7C,cAAY,IAAI,eAAe,QAAQ;AACvC,YAAU;UACH,KAAK;AAEZ,YAAU;GACR,OAAO;GACP,cAHY,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,IAAI,CAAC,EAG5C;GACpB,CAAC;;;;;;AAON,SAAgB,cACd,WACA,MACY;AAEZ,QAAO,oBADW,WACX,EAAW,GAAI,MAAQ"}
1
+ {"version":3,"file":"index.js","names":["mount"],"sources":["../src/render-impl.tsx"],"sourcesContent":["import type { ComponentFn, VNodeChild } from \"@pyreon/core\"\nimport { mount } from \"@pyreon/runtime-dom\"\n\n/**\n * State tracked per canvas element so we can clean up between renders.\n */\nconst canvasState = new WeakMap<HTMLElement, () => void>()\n\n/**\n * Render a Pyreon story into a Storybook canvas element.\n *\n * This is the core integration point — Storybook calls this function\n * every time a story needs to be displayed or re-rendered (e.g. when\n * the user changes args via the Controls panel).\n *\n * It handles:\n * 1. Cleaning up the previous mount (disposing effects, removing DOM)\n * 2. Building the VNode from the story function or component + args\n * 3. Mounting the new VNode into the canvas\n */\nexport function renderToCanvas(\n {\n storyFn,\n showMain,\n showError,\n }: {\n storyFn: () => VNodeChild\n storyContext: {\n component?: ComponentFn<any>\n args: Record<string, unknown>\n [key: string]: unknown\n }\n showMain: () => void\n showError: (error: { title: string; description: string }) => void\n forceRemount: boolean\n },\n canvasElement: HTMLElement,\n): void {\n // Always clean up the previous render\n const prevUnmount = canvasState.get(canvasElement)\n if (prevUnmount) {\n prevUnmount()\n canvasState.delete(canvasElement)\n }\n\n try {\n const element = storyFn()\n const unmount = mount(element, canvasElement)\n canvasState.set(canvasElement, unmount)\n showMain()\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err))\n showError({\n title: `Error rendering story`,\n description: error.message,\n })\n }\n}\n\n/**\n * Default render implementation used when no custom `render` is provided.\n */\nexport function defaultRender(\n component: ComponentFn<any>,\n args: Record<string, unknown>,\n): VNodeChild {\n const Component = component\n return <Component {...args} />\n}\n"],"mappings":";;;;;;;;;AAMA,MAAM,8BAAc,IAAI,SAAkC;;;;;;;;;;;;;AAc1D,SAAgB,eACd,EACE,SACA,UACA,aAYF,eACM;CAEN,MAAM,cAAc,YAAY,IAAI,cAAc;AAClD,KAAI,aAAa;AACf,eAAa;AACb,cAAY,OAAO,cAAc;;AAGnC,KAAI;EAEF,MAAM,UAAUA,QADA,SAAS,EACM,cAAc;AAC7C,cAAY,IAAI,eAAe,QAAQ;AACvC,YAAU;UACH,KAAK;AAEZ,YAAU;GACR,OAAO;GACP,cAHY,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,IAAI,CAAC,EAG5C;GACpB,CAAC;;;;;;AAON,SAAgB,cACd,WACA,MACY;AAEZ,QAAO,oBADW,WACX,EAAW,GAAI,MAAQ"}
package/lib/preset.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"preset.js","names":[],"sources":["../src/preset.ts"],"sourcesContent":["/**\n * Storybook preset for @pyreon/storybook.\n *\n * This file is loaded by Storybook's server when the user sets\n * `framework: \"@pyreon/storybook\"` in their `.storybook/main.ts`.\n *\n * It tells Storybook:\n * - Which renderer to use (via the preview entry)\n * - What framework name to report\n */\n\nimport { dirname, join } from 'node:path'\n\nfunction _getAbsolutePath(value: string): string {\n return dirname(require.resolve(join(value, 'package.json')))\n}\n\nexport const addons: string[] = []\n\nexport const previewAnnotations: string[] = [join(__dirname, 'preview')]\n\nexport const core = {\n renderer: '@pyreon/storybook',\n}\n"],"mappings":";;;;;;;;;;;;;AAiBA,MAAa,SAAmB,EAAE;AAElC,MAAa,qBAA+B,CAAC,KAAK,WAAW,UAAU,CAAC;AAExE,MAAa,OAAO,EAClB,UAAU,qBACX"}
1
+ {"version":3,"file":"preset.js","names":[],"sources":["../src/preset.ts"],"sourcesContent":["/**\n * Storybook preset for @pyreon/storybook.\n *\n * This file is loaded by Storybook's server when the user sets\n * `framework: \"@pyreon/storybook\"` in their `.storybook/main.ts`.\n *\n * It tells Storybook:\n * - Which renderer to use (via the preview entry)\n * - What framework name to report\n */\n\nimport { dirname, join } from \"node:path\"\n\nfunction _getAbsolutePath(value: string): string {\n return dirname(require.resolve(join(value, \"package.json\")))\n}\n\nexport const addons: string[] = []\n\nexport const previewAnnotations: string[] = [join(__dirname, \"preview\")]\n\nexport const core = {\n renderer: \"@pyreon/storybook\",\n}\n"],"mappings":";;;;;;;;;;;;;AAiBA,MAAa,SAAmB,EAAE;AAElC,MAAa,qBAA+B,CAAC,KAAK,WAAW,UAAU,CAAC;AAExE,MAAa,OAAO,EAClB,UAAU,qBACX"}
package/lib/preview.js CHANGED
@@ -1,54 +1,6 @@
1
1
  import { mount } from "@pyreon/runtime-dom";
2
+ import { jsx } from "@pyreon/core/jsx-runtime";
2
3
 
3
- //#region ../../node_modules/.bun/@pyreon+core@0.7.11/node_modules/@pyreon/core/lib/jsx-runtime.js
4
- /**
5
- * Hyperscript function — the compiled output of JSX.
6
- * `<div class="x">hello</div>` → `h("div", { class: "x" }, "hello")`
7
- *
8
- * Generic on P so TypeScript validates props match the component's signature
9
- * at the call site, then stores the result in the loosely-typed VNode.
10
- */
11
- /** Shared empty props sentinel — identity-checked in mountElement to skip applyProps. */
12
- const EMPTY_PROPS = {};
13
- function h(type, props, ...children) {
14
- return {
15
- type,
16
- props: props ?? EMPTY_PROPS,
17
- children: normalizeChildren(children),
18
- key: props?.key ?? null
19
- };
20
- }
21
- function normalizeChildren(children) {
22
- for (let i = 0; i < children.length; i++) if (Array.isArray(children[i])) return flattenChildren(children);
23
- return children;
24
- }
25
- function flattenChildren(children) {
26
- const result = [];
27
- for (const child of children) if (Array.isArray(child)) result.push(...flattenChildren(child));
28
- else result.push(child);
29
- return result;
30
- }
31
- /**
32
- * JSX automatic runtime.
33
- *
34
- * When tsconfig has `"jsxImportSource": "@pyreon/core"`, the TS/bundler compiler
35
- * rewrites JSX to imports from this file automatically:
36
- * <div class="x" /> → jsx("div", { class: "x" })
37
- */
38
- function jsx(type, props, key) {
39
- const { children, ...rest } = props;
40
- const propsWithKey = key != null ? {
41
- ...rest,
42
- key
43
- } : rest;
44
- if (typeof type === "function") return h(type, children !== void 0 ? {
45
- ...propsWithKey,
46
- children
47
- } : propsWithKey);
48
- return h(type, propsWithKey, ...children === void 0 ? [] : Array.isArray(children) ? children : [children]);
49
- }
50
-
51
- //#endregion
52
4
  //#region src/render-impl.tsx
53
5
  /**
54
6
  * State tracked per canvas element so we can clean up between renders.
@@ -1 +1 @@
1
- {"version":3,"file":"preview.js","names":[],"sources":["../../../node_modules/.bun/@pyreon+core@0.7.11/node_modules/@pyreon/core/lib/jsx-runtime.js","../src/render-impl.tsx","../src/preview-impl.tsx"],"sourcesContent":["//#region src/h.ts\n/** Marker for fragment nodes — renders children without a wrapper element */\nconst Fragment = Symbol(\"Pyreon.Fragment\");\n/**\n* Hyperscript function — the compiled output of JSX.\n* `<div class=\"x\">hello</div>` → `h(\"div\", { class: \"x\" }, \"hello\")`\n*\n* Generic on P so TypeScript validates props match the component's signature\n* at the call site, then stores the result in the loosely-typed VNode.\n*/\n/** Shared empty props sentinel — identity-checked in mountElement to skip applyProps. */\nconst EMPTY_PROPS = {};\nfunction h(type, props, ...children) {\n\treturn {\n\t\ttype,\n\t\tprops: props ?? EMPTY_PROPS,\n\t\tchildren: normalizeChildren(children),\n\t\tkey: props?.key ?? null\n\t};\n}\nfunction normalizeChildren(children) {\n\tfor (let i = 0; i < children.length; i++) if (Array.isArray(children[i])) return flattenChildren(children);\n\treturn children;\n}\nfunction flattenChildren(children) {\n\tconst result = [];\n\tfor (const child of children) if (Array.isArray(child)) result.push(...flattenChildren(child));\n\telse result.push(child);\n\treturn result;\n}\n\n//#endregion\n//#region src/jsx-runtime.ts\n/**\n* JSX automatic runtime.\n*\n* When tsconfig has `\"jsxImportSource\": \"@pyreon/core\"`, the TS/bundler compiler\n* rewrites JSX to imports from this file automatically:\n* <div class=\"x\" /> → jsx(\"div\", { class: \"x\" })\n*/\nfunction jsx(type, props, key) {\n\tconst { children, ...rest } = props;\n\tconst propsWithKey = key != null ? {\n\t\t...rest,\n\t\tkey\n\t} : rest;\n\tif (typeof type === \"function\") return h(type, children !== void 0 ? {\n\t\t...propsWithKey,\n\t\tchildren\n\t} : propsWithKey);\n\treturn h(type, propsWithKey, ...children === void 0 ? [] : Array.isArray(children) ? children : [children]);\n}\nconst jsxs = jsx;\n\n//#endregion\nexport { Fragment, jsx, jsxs };\n//# sourceMappingURL=jsx-runtime.js.map","import type { ComponentFn, VNodeChild } from '@pyreon/core'\nimport { mount } from '@pyreon/runtime-dom'\n\n/**\n * State tracked per canvas element so we can clean up between renders.\n */\nconst canvasState = new WeakMap<HTMLElement, () => void>()\n\n/**\n * Render a Pyreon story into a Storybook canvas element.\n *\n * This is the core integration point — Storybook calls this function\n * every time a story needs to be displayed or re-rendered (e.g. when\n * the user changes args via the Controls panel).\n *\n * It handles:\n * 1. Cleaning up the previous mount (disposing effects, removing DOM)\n * 2. Building the VNode from the story function or component + args\n * 3. Mounting the new VNode into the canvas\n */\nexport function renderToCanvas(\n {\n storyFn,\n showMain,\n showError,\n }: {\n storyFn: () => VNodeChild\n storyContext: {\n component?: ComponentFn<any>\n args: Record<string, unknown>\n [key: string]: unknown\n }\n showMain: () => void\n showError: (error: { title: string; description: string }) => void\n forceRemount: boolean\n },\n canvasElement: HTMLElement,\n): void {\n // Always clean up the previous render\n const prevUnmount = canvasState.get(canvasElement)\n if (prevUnmount) {\n prevUnmount()\n canvasState.delete(canvasElement)\n }\n\n try {\n const element = storyFn()\n const unmount = mount(element, canvasElement)\n canvasState.set(canvasElement, unmount)\n showMain()\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err))\n showError({\n title: `Error rendering story`,\n description: error.message,\n })\n }\n}\n\n/**\n * Default render implementation used when no custom `render` is provided.\n */\nexport function defaultRender(\n component: ComponentFn<any>,\n args: Record<string, unknown>,\n): VNodeChild {\n const Component = component\n return <Component {...args} />\n}\n","import type { ComponentFn, VNodeChild } from '@pyreon/core'\nimport { renderToCanvas } from './render-impl'\n\n/**\n * Preview entry — Storybook loads this in the preview iframe.\n *\n * Exports the render function and default decorators/parameters\n * that apply to all stories using this renderer.\n */\n\nexport { renderToCanvas }\n\n/**\n * Default render function — if the story CSF has a `component` but no\n * explicit `render`, this is used to create the VNode.\n */\nexport function render<TArgs extends Record<string, unknown>>(\n args: TArgs,\n context: { component?: ComponentFn<any> },\n): VNodeChild {\n const Component = context.component\n if (!Component) {\n throw new Error(\n '[@pyreon/storybook] No component provided. Either set `component` in your meta or provide a `render` function.',\n )\n }\n return <Component {...args} />\n}\n"],"x_google_ignoreList":[0],"mappings":";;;;;;;;;;;AAWA,MAAM,cAAc,EAAE;AACtB,SAAS,EAAE,MAAM,OAAO,GAAG,UAAU;AACpC,QAAO;EACN;EACA,OAAO,SAAS;EAChB,UAAU,kBAAkB,SAAS;EACrC,KAAK,OAAO,OAAO;EACnB;;AAEF,SAAS,kBAAkB,UAAU;AACpC,MAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,IAAK,KAAI,MAAM,QAAQ,SAAS,GAAG,CAAE,QAAO,gBAAgB,SAAS;AAC1G,QAAO;;AAER,SAAS,gBAAgB,UAAU;CAClC,MAAM,SAAS,EAAE;AACjB,MAAK,MAAM,SAAS,SAAU,KAAI,MAAM,QAAQ,MAAM,CAAE,QAAO,KAAK,GAAG,gBAAgB,MAAM,CAAC;KACzF,QAAO,KAAK,MAAM;AACvB,QAAO;;;;;;;;;AAYR,SAAS,IAAI,MAAM,OAAO,KAAK;CAC9B,MAAM,EAAE,UAAU,GAAG,SAAS;CAC9B,MAAM,eAAe,OAAO,OAAO;EAClC,GAAG;EACH;EACA,GAAG;AACJ,KAAI,OAAO,SAAS,WAAY,QAAO,EAAE,MAAM,aAAa,KAAK,IAAI;EACpE,GAAG;EACH;EACA,GAAG,aAAa;AACjB,QAAO,EAAE,MAAM,cAAc,GAAG,aAAa,KAAK,IAAI,EAAE,GAAG,MAAM,QAAQ,SAAS,GAAG,WAAW,CAAC,SAAS,CAAC;;;;;;;;AC5C5G,MAAM,8BAAc,IAAI,SAAkC;;;;;;;;;;;;;AAc1D,SAAgB,eACd,EACE,SACA,UACA,aAYF,eACM;CAEN,MAAM,cAAc,YAAY,IAAI,cAAc;AAClD,KAAI,aAAa;AACf,eAAa;AACb,cAAY,OAAO,cAAc;;AAGnC,KAAI;EAEF,MAAM,UAAU,MADA,SAAS,EACM,cAAc;AAC7C,cAAY,IAAI,eAAe,QAAQ;AACvC,YAAU;UACH,KAAK;AAEZ,YAAU;GACR,OAAO;GACP,cAHY,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,IAAI,CAAC,EAG5C;GACpB,CAAC;;;;;;;;;;ACvCN,SAAgB,OACd,MACA,SACY;CACZ,MAAM,YAAY,QAAQ;AAC1B,KAAI,CAAC,UACH,OAAM,IAAI,MACR,iHACD;AAEH,QAAO,oBAAC,WAAD,EAAW,GAAI,MAAQ"}
1
+ {"version":3,"file":"preview.js","names":[],"sources":["../src/render-impl.tsx","../src/preview-impl.tsx"],"sourcesContent":["import type { ComponentFn, VNodeChild } from \"@pyreon/core\"\nimport { mount } from \"@pyreon/runtime-dom\"\n\n/**\n * State tracked per canvas element so we can clean up between renders.\n */\nconst canvasState = new WeakMap<HTMLElement, () => void>()\n\n/**\n * Render a Pyreon story into a Storybook canvas element.\n *\n * This is the core integration point — Storybook calls this function\n * every time a story needs to be displayed or re-rendered (e.g. when\n * the user changes args via the Controls panel).\n *\n * It handles:\n * 1. Cleaning up the previous mount (disposing effects, removing DOM)\n * 2. Building the VNode from the story function or component + args\n * 3. Mounting the new VNode into the canvas\n */\nexport function renderToCanvas(\n {\n storyFn,\n showMain,\n showError,\n }: {\n storyFn: () => VNodeChild\n storyContext: {\n component?: ComponentFn<any>\n args: Record<string, unknown>\n [key: string]: unknown\n }\n showMain: () => void\n showError: (error: { title: string; description: string }) => void\n forceRemount: boolean\n },\n canvasElement: HTMLElement,\n): void {\n // Always clean up the previous render\n const prevUnmount = canvasState.get(canvasElement)\n if (prevUnmount) {\n prevUnmount()\n canvasState.delete(canvasElement)\n }\n\n try {\n const element = storyFn()\n const unmount = mount(element, canvasElement)\n canvasState.set(canvasElement, unmount)\n showMain()\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err))\n showError({\n title: `Error rendering story`,\n description: error.message,\n })\n }\n}\n\n/**\n * Default render implementation used when no custom `render` is provided.\n */\nexport function defaultRender(\n component: ComponentFn<any>,\n args: Record<string, unknown>,\n): VNodeChild {\n const Component = component\n return <Component {...args} />\n}\n","import type { ComponentFn, VNodeChild } from \"@pyreon/core\"\nimport { renderToCanvas } from \"./render-impl\"\n\n/**\n * Preview entry — Storybook loads this in the preview iframe.\n *\n * Exports the render function and default decorators/parameters\n * that apply to all stories using this renderer.\n */\n\nexport { renderToCanvas }\n\n/**\n * Default render function — if the story CSF has a `component` but no\n * explicit `render`, this is used to create the VNode.\n */\nexport function render<TArgs extends Record<string, unknown>>(\n args: TArgs,\n context: { component?: ComponentFn<any> },\n): VNodeChild {\n const Component = context.component\n if (!Component) {\n throw new Error(\n \"[@pyreon/storybook] No component provided. Either set `component` in your meta or provide a `render` function.\",\n )\n }\n return <Component {...args} />\n}\n"],"mappings":";;;;;;;AAMA,MAAM,8BAAc,IAAI,SAAkC;;;;;;;;;;;;;AAc1D,SAAgB,eACd,EACE,SACA,UACA,aAYF,eACM;CAEN,MAAM,cAAc,YAAY,IAAI,cAAc;AAClD,KAAI,aAAa;AACf,eAAa;AACb,cAAY,OAAO,cAAc;;AAGnC,KAAI;EAEF,MAAM,UAAU,MADA,SAAS,EACM,cAAc;AAC7C,cAAY,IAAI,eAAe,QAAQ;AACvC,YAAU;UACH,KAAK;AAEZ,YAAU;GACR,OAAO;GACP,cAHY,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,IAAI,CAAC,EAG5C;GACpB,CAAC;;;;;;;;;;ACvCN,SAAgB,OACd,MACA,SACY;CACZ,MAAM,YAAY,QAAQ;AAC1B,KAAI,CAAC,UACH,OAAM,IAAI,MACR,iHACD;AAEH,QAAO,oBAAC,WAAD,EAAW,GAAI,MAAQ"}
@@ -21,7 +21,7 @@ interface StoryContext<TArgs = Props$1> {
21
21
  id: string;
22
22
  kind: string;
23
23
  name: string;
24
- viewMode: 'story' | 'docs';
24
+ viewMode: "story" | "docs";
25
25
  }
26
26
  type StoryFn<TArgs = Props$1> = (args: TArgs, context: StoryContext<TArgs>) => VNodeChild$1;
27
27
  type DecoratorFn<TArgs = Props$1> = (storyFn: StoryFn<TArgs>, context: StoryContext<TArgs>) => VNodeChild$1;
@@ -1 +1 @@
1
- {"version":3,"file":"index2.d.ts","names":[],"sources":["../../../src/types.ts","../../../src/render-impl.tsx"],"mappings":";;;;;;;;;UAQiB,cAAA;EACf,SAAA,EAAW,aAAA;EACX,WAAA,EAAa,YAAA;EACb,aAAA,EAAe,WAAA;AAAA;;KAML,UAAA,MAAgB,CAAA,SAAU,aAAA,YAAuB,CAAA,GAAI,OAAA;AAAA,UAIhD,YAAA,SAAqB,OAAA;EACpC,IAAA,EAAM,KAAA;EACN,QAAA,EAAU,MAAA;EACV,OAAA,EAAS,MAAA;EACT,EAAA;EACA,IAAA;EACA,IAAA;EACA,QAAA;AAAA;AAAA,KAGU,OAAA,SAAgB,OAAA,KAC1B,IAAA,EAAM,KAAA,EACN,OAAA,EAAS,YAAA,CAAa,KAAA,MACnB,YAAA;AAAA,KAEO,WAAA,SAAoB,OAAA,KAC9B,OAAA,EAAS,OAAA,CAAQ,KAAA,GACjB,OAAA,EAAS,YAAA,CAAa,KAAA,MACnB,YAAA;AAAA,UAIY,IAAA,oBAAwB,aAAA,QAAmB,aAAA;EA1BhC;EA4B1B,SAAA,GAAY,UAAA;EA5BmD;EA8B/D,KAAA;EA9BoE;EAgCpE,UAAA,GAAa,WAAA,CAAY,UAAA,CAAW,UAAA;EAhCV;EAkC1B,IAAA,GAAO,OAAA,CAAQ,UAAA,CAAW,UAAA;EAlC4B;EAoCtD,QAAA,GAAW,MAAA;EApCoD;EAsC/D,UAAA,GAAa,MAAA;EAtCuD;EAwCpE,IAAA;EApC2B;;;;EAyC3B,MAAA,IACE,IAAA,EAAM,UAAA,CAAW,UAAA,GACjB,OAAA,EAAS,YAAA,CAAa,UAAA,CAAW,UAAA,OAC9B,YAAA;EAzCI;EA2CT,cAAA,uBAAqC,MAAA;EA3CtB;EA6Cf,cAAA,uBAAqC,MAAA;AAAA;AAAA,UAKtB,QAAA,eAAuB,IAAA,QAAY,IAAA;EApD5C;EAsDN,IAAA,GAAO,OAAA,CAAQ,QAAA,CAAS,KAAA;EArDd;EAuDV,QAAA,GAAW,MAAA;EAtDF;EAwDT,UAAA,GAAa,WAAA,CAAY,QAAA,CAAS,KAAA;EAtDlC;EAwDA,UAAA,GAAa,MAAA;EAtDb;EAwDA,IAAA;EAxDQ;EA0DR,MAAA,IACE,IAAA,EAAM,QAAA,CAAS,KAAA,GACf,OAAA,EAAS,YAAA,CAAa,QAAA,CAAS,KAAA,OAC5B,YAAA;EA1DY;EA4DjB,IAAA;EA5D0B;EA8D1B,IAAA,IAAQ,OAAA;IACN,aAAA,EAAe,WAAA;IACf,IAAA,EAAM,QAAA,CAAS,KAAA;IACf,IAAA,GAAO,IAAA,UAAc,EAAA,QAAU,OAAA,WAAkB,OAAA;EAAA,MAC7C,OAAA;AAAA;;KAIH,QAAA,UAAkB,KAAA,SAAc,IAAA,YAAgB,UAAA,CAAW,CAAA,IAAK,OAAA;;;;;;;AA7FrE;;;;;;;;iBCYgB,cAAA,CAAA;EAEZ,OAAA;EACA,QAAA;EACA;AAAA;EAEA,OAAA,QAAe,YAAA;EACf,YAAA;IACE,SAAA,GAAY,aAAA;IACZ,IAAA,EAAM,MAAA;IAAA,CACL,GAAA;EAAA;EAEH,QAAA;EACA,SAAA,GAAY,KAAA;IAAS,KAAA;IAAe,WAAA;EAAA;EACpC,YAAA;AAAA,GAEF,aAAA,EAAe,WAAA;;;;iBA0BD,aAAA,CACd,SAAA,EAAW,aAAA,OACX,IAAA,EAAM,MAAA,oBACL,YAAA"}
1
+ {"version":3,"file":"index2.d.ts","names":[],"sources":["../../../src/types.ts","../../../src/render-impl.tsx"],"mappings":";;;;;;;;;UAQiB,cAAA;EACf,SAAA,EAAW,aAAA;EACX,WAAA,EAAa,YAAA;EACb,aAAA,EAAe,WAAA;AAAA;;KAML,UAAA,MAAgB,CAAA,SAAU,aAAA,YAAuB,CAAA,GAAI,OAAA;AAAA,UAIhD,YAAA,SAAqB,OAAA;EACpC,IAAA,EAAM,KAAA;EACN,QAAA,EAAU,MAAA;EACV,OAAA,EAAS,MAAA;EACT,EAAA;EACA,IAAA;EACA,IAAA;EACA,QAAA;AAAA;AAAA,KAGU,OAAA,SAAgB,OAAA,KAAU,IAAA,EAAM,KAAA,EAAO,OAAA,EAAS,YAAA,CAAa,KAAA,MAAW,YAAA;AAAA,KAExE,WAAA,SAAoB,OAAA,KAC9B,OAAA,EAAS,OAAA,CAAQ,KAAA,GACjB,OAAA,EAAS,YAAA,CAAa,KAAA,MACnB,YAAA;AAAA,UAIY,IAAA,oBAAwB,aAAA,QAAmB,aAAA;EAvBhC;EAyB1B,SAAA,GAAY,UAAA;EAzBmD;EA2B/D,KAAA;EA3BoE;EA6BpE,UAAA,GAAa,WAAA,CAAY,UAAA,CAAW,UAAA;EA7BV;EA+B1B,IAAA,GAAO,OAAA,CAAQ,UAAA,CAAW,UAAA;EA/B4B;EAiCtD,QAAA,GAAW,MAAA;EAjCoD;EAmC/D,UAAA,GAAa,MAAA;EAnCuD;EAqCpE,IAAA;EAjC2B;;;;EAsC3B,MAAA,IACE,IAAA,EAAM,UAAA,CAAW,UAAA,GACjB,OAAA,EAAS,YAAA,CAAa,UAAA,CAAW,UAAA,OAC9B,YAAA;EAtCI;EAwCT,cAAA,uBAAqC,MAAA;EAxCtB;EA0Cf,cAAA,uBAAqC,MAAA;AAAA;AAAA,UAKtB,QAAA,eAAuB,IAAA,QAAY,IAAA;EAjD5C;EAmDN,IAAA,GAAO,OAAA,CAAQ,QAAA,CAAS,KAAA;EAlDd;EAoDV,QAAA,GAAW,MAAA;EAnDF;EAqDT,UAAA,GAAa,WAAA,CAAY,QAAA,CAAS,KAAA;EAnDlC;EAqDA,UAAA,GAAa,MAAA;EAnDb;EAqDA,IAAA;EArDQ;EAuDR,MAAA,IAAU,IAAA,EAAM,QAAA,CAAS,KAAA,GAAQ,OAAA,EAAS,YAAA,CAAa,QAAA,CAAS,KAAA,OAAY,YAAA;EApD3D;EAsDjB,IAAA;EAtD0B;EAwD1B,IAAA,IAAQ,OAAA;IACN,aAAA,EAAe,WAAA;IACf,IAAA,EAAM,QAAA,CAAS,KAAA;IACf,IAAA,GAAO,IAAA,UAAc,EAAA,QAAU,OAAA,WAAkB,OAAA;EAAA,MAC7C,OAAA;AAAA;;KAIH,QAAA,UAAkB,KAAA,SAAc,IAAA,YAAgB,UAAA,CAAW,CAAA,IAAK,OAAA;;;;;;;AAvFrE;;;;;;;;iBCYgB,cAAA,CAAA;EAEZ,OAAA;EACA,QAAA;EACA;AAAA;EAEA,OAAA,QAAe,YAAA;EACf,YAAA;IACE,SAAA,GAAY,aAAA;IACZ,IAAA,EAAM,MAAA;IAAA,CACL,GAAA;EAAA;EAEH,QAAA;EACA,SAAA,GAAY,KAAA;IAAS,KAAA;IAAe,WAAA;EAAA;EACpC,YAAA;AAAA,GAEF,aAAA,EAAe,WAAA;;;;iBA0BD,aAAA,CACd,SAAA,EAAW,aAAA,OACX,IAAA,EAAM,MAAA,oBACL,YAAA"}
package/package.json CHANGED
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "@pyreon/storybook",
3
- "version": "0.10.0",
3
+ "version": "0.11.1",
4
4
  "description": "Storybook renderer for Pyreon — mount, render, and interact with Pyreon components in Storybook",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
8
- "url": "https://github.com/pyreon/fundamentals.git",
9
- "directory": "packages/storybook"
8
+ "url": "https://github.com/pyreon/pyreon.git",
9
+ "directory": "packages/tools/storybook"
10
10
  },
11
11
  "homepage": "https://github.com/pyreon/fundamentals/tree/main/packages/storybook#readme",
12
12
  "bugs": {
13
- "url": "https://github.com/pyreon/fundamentals/issues"
13
+ "url": "https://github.com/pyreon/pyreon/issues"
14
14
  },
15
15
  "publishConfig": {
16
16
  "access": "public"
@@ -48,12 +48,13 @@
48
48
  "build": "vl_rolldown_build",
49
49
  "dev": "vl_rolldown_build-watch",
50
50
  "test": "vitest run",
51
- "typecheck": "tsc --noEmit"
51
+ "typecheck": "tsc --noEmit",
52
+ "lint": "biome check ."
52
53
  },
53
54
  "peerDependencies": {
54
- "@pyreon/core": ">=0.7.0 <0.8.0",
55
- "@pyreon/reactivity": ">=0.7.0 <0.8.0",
56
- "@pyreon/runtime-dom": ">=0.7.0 <0.8.0",
55
+ "@pyreon/core": "^0.11.1",
56
+ "@pyreon/reactivity": "^0.11.1",
57
+ "@pyreon/runtime-dom": "^0.11.1",
57
58
  "storybook": ">=8.0.0"
58
59
  }
59
60
  }
package/src/index.ts CHANGED
@@ -31,15 +31,15 @@ export type {
31
31
  StoryContext,
32
32
  StoryFn,
33
33
  StoryObj,
34
- } from './types'
34
+ } from "./types"
35
35
 
36
36
  // ─── Renderer ────────────────────────────────────────────────────────────────
37
37
 
38
- export { defaultRender, renderToCanvas } from './render'
38
+ export { defaultRender, renderToCanvas } from "./render"
39
39
 
40
40
  // ─── Pyreon re-exports for convenience ───────────────────────────────────────
41
41
 
42
- export type { ComponentFn, Props, VNode, VNodeChild } from '@pyreon/core'
43
- export { Fragment, h } from '@pyreon/core'
44
- export { computed, effect, signal } from '@pyreon/reactivity'
45
- export { mount } from '@pyreon/runtime-dom'
42
+ export type { ComponentFn, Props, VNode, VNodeChild } from "@pyreon/core"
43
+ export { Fragment, h } from "@pyreon/core"
44
+ export { computed, effect, signal } from "@pyreon/reactivity"
45
+ export { mount } from "@pyreon/runtime-dom"
package/src/preset.ts CHANGED
@@ -9,16 +9,16 @@
9
9
  * - What framework name to report
10
10
  */
11
11
 
12
- import { dirname, join } from 'node:path'
12
+ import { dirname, join } from "node:path"
13
13
 
14
14
  function _getAbsolutePath(value: string): string {
15
- return dirname(require.resolve(join(value, 'package.json')))
15
+ return dirname(require.resolve(join(value, "package.json")))
16
16
  }
17
17
 
18
18
  export const addons: string[] = []
19
19
 
20
- export const previewAnnotations: string[] = [join(__dirname, 'preview')]
20
+ export const previewAnnotations: string[] = [join(__dirname, "preview")]
21
21
 
22
22
  export const core = {
23
- renderer: '@pyreon/storybook',
23
+ renderer: "@pyreon/storybook",
24
24
  }
@@ -1,5 +1,5 @@
1
- import type { ComponentFn, VNodeChild } from '@pyreon/core'
2
- import { renderToCanvas } from './render-impl'
1
+ import type { ComponentFn, VNodeChild } from "@pyreon/core"
2
+ import { renderToCanvas } from "./render-impl"
3
3
 
4
4
  /**
5
5
  * Preview entry — Storybook loads this in the preview iframe.
@@ -21,7 +21,7 @@ export function render<TArgs extends Record<string, unknown>>(
21
21
  const Component = context.component
22
22
  if (!Component) {
23
23
  throw new Error(
24
- '[@pyreon/storybook] No component provided. Either set `component` in your meta or provide a `render` function.',
24
+ "[@pyreon/storybook] No component provided. Either set `component` in your meta or provide a `render` function.",
25
25
  )
26
26
  }
27
27
  return <Component {...args} />
package/src/preview.ts CHANGED
@@ -1 +1 @@
1
- export { render, renderToCanvas } from './preview-impl'
1
+ export { render, renderToCanvas } from "./preview-impl"
@@ -1,5 +1,5 @@
1
- import type { ComponentFn, VNodeChild } from '@pyreon/core'
2
- import { mount } from '@pyreon/runtime-dom'
1
+ import type { ComponentFn, VNodeChild } from "@pyreon/core"
2
+ import { mount } from "@pyreon/runtime-dom"
3
3
 
4
4
  /**
5
5
  * State tracked per canvas element so we can clean up between renders.
package/src/render.ts CHANGED
@@ -1 +1 @@
1
- export { defaultRender, renderToCanvas } from './render-impl'
1
+ export { defaultRender, renderToCanvas } from "./render-impl"
@@ -1,20 +1,14 @@
1
- import type { ComponentFn, VNodeChild } from '@pyreon/core'
2
- import { effect, signal } from '@pyreon/reactivity'
3
- import { mount } from '@pyreon/runtime-dom'
4
- import { render as previewRender } from '../preview'
5
- import { defaultRender, renderToCanvas } from '../render'
6
- import type {
7
- DecoratorFn,
8
- Meta,
9
- StoryContext,
10
- StoryFn,
11
- StoryObj,
12
- } from '../types'
1
+ import type { ComponentFn, VNodeChild } from "@pyreon/core"
2
+ import { effect, signal } from "@pyreon/reactivity"
3
+ import { mount } from "@pyreon/runtime-dom"
4
+ import { render as previewRender } from "../preview"
5
+ import { defaultRender, renderToCanvas } from "../render"
6
+ import type { DecoratorFn, Meta, StoryContext, StoryFn, StoryObj } from "../types"
13
7
 
14
8
  // ─── Helpers ──────────────────────────────────────────────────────────────────
15
9
 
16
10
  function createCanvas(): HTMLElement {
17
- const el = document.createElement('div')
11
+ const el = document.createElement("div")
18
12
  document.body.appendChild(el)
19
13
  return el
20
14
  }
@@ -27,9 +21,7 @@ function makeRenderContext(overrides: {
27
21
  return {
28
22
  storyFn: overrides.storyFn ?? (() => <div>default</div>),
29
23
  storyContext: {
30
- ...(overrides.component != null
31
- ? { component: overrides.component }
32
- : {}),
24
+ ...(overrides.component != null ? { component: overrides.component } : {}),
33
25
  args: overrides.args ?? {},
34
26
  },
35
27
  showMain: () => {
@@ -44,8 +36,8 @@ function makeRenderContext(overrides: {
44
36
 
45
37
  // ─── renderToCanvas ──────────────────────────────────────────────────────────
46
38
 
47
- describe('renderToCanvas', () => {
48
- it('mounts a simple VNode into the canvas', () => {
39
+ describe("renderToCanvas", () => {
40
+ it("mounts a simple VNode into the canvas", () => {
49
41
  const canvas = createCanvas()
50
42
  const ctx = makeRenderContext({
51
43
  storyFn: () => <button>Click me</button>,
@@ -53,12 +45,12 @@ describe('renderToCanvas', () => {
53
45
 
54
46
  renderToCanvas(ctx, canvas)
55
47
 
56
- expect(canvas.innerHTML).toContain('Click me')
57
- expect(canvas.querySelector('button')).toBeTruthy()
48
+ expect(canvas.innerHTML).toContain("Click me")
49
+ expect(canvas.querySelector("button")).toBeTruthy()
58
50
  canvas.remove()
59
51
  })
60
52
 
61
- it('mounts a Pyreon component with props', () => {
53
+ it("mounts a Pyreon component with props", () => {
62
54
  function Button(props: { label: string; disabled?: boolean }) {
63
55
  return <button disabled={props.disabled ?? false}>{props.label}</button>
64
56
  }
@@ -70,33 +62,27 @@ describe('renderToCanvas', () => {
70
62
 
71
63
  renderToCanvas(ctx, canvas)
72
64
 
73
- const btn = canvas.querySelector('button')!
65
+ const btn = canvas.querySelector("button")!
74
66
  expect(btn).toBeTruthy()
75
- expect(btn.textContent).toBe('Submit')
76
- expect(btn.getAttribute('disabled')).not.toBeNull()
67
+ expect(btn.textContent).toBe("Submit")
68
+ expect(btn.getAttribute("disabled")).not.toBeNull()
77
69
  canvas.remove()
78
70
  })
79
71
 
80
- it('cleans up previous mount on re-render', () => {
72
+ it("cleans up previous mount on re-render", () => {
81
73
  const canvas = createCanvas()
82
74
 
83
- renderToCanvas(
84
- makeRenderContext({ storyFn: () => <div>First</div> }),
85
- canvas,
86
- )
87
- expect(canvas.textContent).toBe('First')
75
+ renderToCanvas(makeRenderContext({ storyFn: () => <div>First</div> }), canvas)
76
+ expect(canvas.textContent).toBe("First")
88
77
 
89
- renderToCanvas(
90
- makeRenderContext({ storyFn: () => <div>Second</div> }),
91
- canvas,
92
- )
93
- expect(canvas.textContent).toBe('Second')
78
+ renderToCanvas(makeRenderContext({ storyFn: () => <div>Second</div> }), canvas)
79
+ expect(canvas.textContent).toBe("Second")
94
80
  // Only one child — previous mount was cleaned up
95
81
  expect(canvas.children.length).toBe(1)
96
82
  canvas.remove()
97
83
  })
98
84
 
99
- it('disposes reactive effects on cleanup', () => {
85
+ it("disposes reactive effects on cleanup", () => {
100
86
  const canvas = createCanvas()
101
87
  let effectRunCount = 0
102
88
 
@@ -116,10 +102,7 @@ describe('renderToCanvas', () => {
116
102
  expect(effectRunCount).toBe(initialCount + 1)
117
103
 
118
104
  // Re-render with a different story — should dispose previous effects
119
- renderToCanvas(
120
- makeRenderContext({ storyFn: () => <div>New story</div> }),
121
- canvas,
122
- )
105
+ renderToCanvas(makeRenderContext({ storyFn: () => <div>New story</div> }), canvas)
123
106
 
124
107
  const countAfterCleanup = effectRunCount
125
108
  count.set(2)
@@ -129,13 +112,13 @@ describe('renderToCanvas', () => {
129
112
  canvas.remove()
130
113
  })
131
114
 
132
- it('shows error when storyFn throws an Error', () => {
115
+ it("shows error when storyFn throws an Error", () => {
133
116
  const canvas = createCanvas()
134
117
  let errorShown: { title: string; description: string } | null = null
135
118
 
136
119
  const ctx = {
137
120
  storyFn: () => {
138
- throw new Error('Boom')
121
+ throw new Error("Boom")
139
122
  },
140
123
  storyContext: { args: {} },
141
124
  showMain: () => {
@@ -150,17 +133,17 @@ describe('renderToCanvas', () => {
150
133
  renderToCanvas(ctx, canvas)
151
134
 
152
135
  expect(errorShown).not.toBeNull()
153
- expect(errorShown!.description).toBe('Boom')
136
+ expect(errorShown!.description).toBe("Boom")
154
137
  canvas.remove()
155
138
  })
156
139
 
157
- it('shows error when storyFn throws a non-Error value', () => {
140
+ it("shows error when storyFn throws a non-Error value", () => {
158
141
  const canvas = createCanvas()
159
142
  let errorShown: { title: string; description: string } | null = null
160
143
 
161
144
  const ctx = {
162
145
  storyFn: () => {
163
- throw 'string error'
146
+ throw "string error"
164
147
  },
165
148
  storyContext: { args: {} },
166
149
  showMain: () => {
@@ -175,11 +158,11 @@ describe('renderToCanvas', () => {
175
158
  renderToCanvas(ctx, canvas)
176
159
 
177
160
  expect(errorShown).not.toBeNull()
178
- expect(errorShown!.description).toBe('string error')
161
+ expect(errorShown!.description).toBe("string error")
179
162
  canvas.remove()
180
163
  })
181
164
 
182
- it('renders reactive components that update the DOM', () => {
165
+ it("renders reactive components that update the DOM", () => {
183
166
  const canvas = createCanvas()
184
167
  const count = signal(0)
185
168
 
@@ -189,27 +172,27 @@ describe('renderToCanvas', () => {
189
172
 
190
173
  renderToCanvas(makeRenderContext({ storyFn: () => <Counter /> }), canvas)
191
174
 
192
- expect(canvas.textContent).toBe('Count: 0')
175
+ expect(canvas.textContent).toBe("Count: 0")
193
176
 
194
177
  count.set(5)
195
- expect(canvas.textContent).toBe('Count: 5')
178
+ expect(canvas.textContent).toBe("Count: 5")
196
179
  canvas.remove()
197
180
  })
198
181
  })
199
182
 
200
183
  // ─── defaultRender ───────────────────────────────────────────────────────────
201
184
 
202
- describe('defaultRender', () => {
203
- it('creates a VNode from component + args', () => {
185
+ describe("defaultRender", () => {
186
+ it("creates a VNode from component + args", () => {
204
187
  function Greeting(props: { name: string }) {
205
188
  return <p>Hello, {props.name}!</p>
206
189
  }
207
190
 
208
191
  const canvas = createCanvas()
209
- const vnode = defaultRender(Greeting, { name: 'World' })
192
+ const vnode = defaultRender(Greeting, { name: "World" })
210
193
  const unmount = mount(vnode, canvas)
211
194
 
212
- expect(canvas.textContent).toBe('Hello, World!')
195
+ expect(canvas.textContent).toBe("Hello, World!")
213
196
  unmount()
214
197
  canvas.remove()
215
198
  })
@@ -217,12 +200,9 @@ describe('defaultRender', () => {
217
200
 
218
201
  // ─── Type-level tests (Meta / StoryObj) ──────────────────────────────────────
219
202
 
220
- describe('Meta and StoryObj types', () => {
221
- it('Meta accepts a component and typed args', () => {
222
- function Button(props: {
223
- label: string
224
- variant?: 'primary' | 'secondary'
225
- }) {
203
+ describe("Meta and StoryObj types", () => {
204
+ it("Meta accepts a component and typed args", () => {
205
+ function Button(props: { label: string; variant?: "primary" | "secondary" }) {
226
206
  return (
227
207
  <button type="button" class={props.variant}>
228
208
  {props.label}
@@ -232,23 +212,23 @@ describe('Meta and StoryObj types', () => {
232
212
 
233
213
  const meta = {
234
214
  component: Button,
235
- title: 'Button',
236
- args: { label: 'Click', variant: 'primary' as const },
237
- tags: ['autodocs'],
215
+ title: "Button",
216
+ args: { label: "Click", variant: "primary" as const },
217
+ tags: ["autodocs"],
238
218
  } satisfies Meta<typeof Button>
239
219
 
240
220
  expect(meta.component).toBe(Button)
241
- expect(meta.args!.label).toBe('Click')
221
+ expect(meta.args!.label).toBe("Click")
242
222
  })
243
223
 
244
- it('StoryObj inherits args from Meta', () => {
224
+ it("StoryObj inherits args from Meta", () => {
245
225
  function Input(props: { placeholder: string; disabled?: boolean }) {
246
226
  return <input placeholder={props.placeholder} disabled={props.disabled} />
247
227
  }
248
228
 
249
229
  const _meta = {
250
230
  component: Input,
251
- args: { placeholder: 'Type here' },
231
+ args: { placeholder: "Type here" },
252
232
  } satisfies Meta<typeof Input>
253
233
 
254
234
  type Story = StoryObj<typeof _meta>
@@ -260,7 +240,7 @@ describe('Meta and StoryObj types', () => {
260
240
  expect(primary.args!.disabled).toBe(true)
261
241
  })
262
242
 
263
- it('StoryObj supports custom render function', () => {
243
+ it("StoryObj supports custom render function", () => {
264
244
  function Card(props: { title: string }) {
265
245
  return (
266
246
  <div class="card">
@@ -271,7 +251,7 @@ describe('Meta and StoryObj types', () => {
271
251
 
272
252
  const _meta = {
273
253
  component: Card,
274
- args: { title: 'Default' },
254
+ args: { title: "Default" },
275
255
  } satisfies Meta<typeof Card>
276
256
 
277
257
  type Story = StoryObj<typeof _meta>
@@ -285,12 +265,12 @@ describe('Meta and StoryObj types', () => {
285
265
  }
286
266
 
287
267
  const canvas = createCanvas()
288
- const vnode = withWrapper.render!({ title: 'Custom' }, {} as any)
268
+ const vnode = withWrapper.render!({ title: "Custom" }, {} as any)
289
269
  const unmount = mount(vnode, canvas)
290
270
 
291
- expect(canvas.querySelector('.wrapper')).toBeTruthy()
292
- expect(canvas.querySelector('.card')).toBeTruthy()
293
- expect(canvas.textContent).toBe('Custom')
271
+ expect(canvas.querySelector(".wrapper")).toBeTruthy()
272
+ expect(canvas.querySelector(".card")).toBeTruthy()
273
+ expect(canvas.textContent).toBe("Custom")
294
274
  unmount()
295
275
  canvas.remove()
296
276
  })
@@ -298,8 +278,8 @@ describe('Meta and StoryObj types', () => {
298
278
 
299
279
  // ─── Decorators ──────────────────────────────────────────────────────────────
300
280
 
301
- describe('Decorators', () => {
302
- it('decorator wraps a story', () => {
281
+ describe("Decorators", () => {
282
+ it("decorator wraps a story", () => {
303
283
  function Button(props: { label: string }) {
304
284
  return <button>{props.label}</button>
305
285
  }
@@ -310,23 +290,23 @@ describe('Decorators', () => {
310
290
 
311
291
  const canvas = createCanvas()
312
292
  const storyResult = withPadding((args) => <Button {...args} />, {
313
- args: { label: 'Wrapped' },
293
+ args: { label: "Wrapped" },
314
294
  argTypes: {},
315
295
  globals: {},
316
- id: '1',
317
- kind: 'Button',
318
- name: 'Primary',
319
- viewMode: 'story',
296
+ id: "1",
297
+ kind: "Button",
298
+ name: "Primary",
299
+ viewMode: "story",
320
300
  })
321
301
 
322
302
  const unmount = mount(storyResult, canvas)
323
- expect(canvas.querySelector('div[style]')).toBeTruthy()
324
- expect(canvas.querySelector('button')!.textContent).toBe('Wrapped')
303
+ expect(canvas.querySelector("div[style]")).toBeTruthy()
304
+ expect(canvas.querySelector("button")!.textContent).toBe("Wrapped")
325
305
  unmount()
326
306
  canvas.remove()
327
307
  })
328
308
 
329
- it('multiple decorators compose correctly', () => {
309
+ it("multiple decorators compose correctly", () => {
330
310
  function Text(props: { content: string }) {
331
311
  return <span>{props.content}</span>
332
312
  }
@@ -340,13 +320,13 @@ describe('Decorators', () => {
340
320
  )
341
321
 
342
322
  const context: StoryContext<{ content: string }> = {
343
- args: { content: 'Hello' },
323
+ args: { content: "Hello" },
344
324
  argTypes: {},
345
325
  globals: {},
346
- id: '1',
347
- kind: 'Text',
348
- name: 'Default',
349
- viewMode: 'story',
326
+ id: "1",
327
+ kind: "Text",
328
+ name: "Default",
329
+ viewMode: "story",
350
330
  }
351
331
 
352
332
  // Compose: withTheme(withBorder(story))
@@ -355,9 +335,9 @@ describe('Decorators', () => {
355
335
 
356
336
  const canvas = createCanvas()
357
337
  const unmount = mount(decorated, canvas)
358
- expect(canvas.querySelector('.theme-dark')).toBeTruthy()
359
- expect(canvas.querySelector('.border')).toBeTruthy()
360
- expect(canvas.querySelector('span')!.textContent).toBe('Hello')
338
+ expect(canvas.querySelector(".theme-dark")).toBeTruthy()
339
+ expect(canvas.querySelector(".border")).toBeTruthy()
340
+ expect(canvas.querySelector("span")!.textContent).toBe("Hello")
361
341
  unmount()
362
342
  canvas.remove()
363
343
  })
@@ -365,8 +345,8 @@ describe('Decorators', () => {
365
345
 
366
346
  // ─── Fragment and multiple children ──────────────────────────────────────────
367
347
 
368
- describe('Fragment stories', () => {
369
- it('renders a story returning a Fragment', () => {
348
+ describe("Fragment stories", () => {
349
+ it("renders a story returning a Fragment", () => {
370
350
  const canvas = createCanvas()
371
351
  renderToCanvas(
372
352
  makeRenderContext({
@@ -380,40 +360,320 @@ describe('Fragment stories', () => {
380
360
  canvas,
381
361
  )
382
362
 
383
- const paragraphs = canvas.querySelectorAll('p')
363
+ const paragraphs = canvas.querySelectorAll("p")
384
364
  expect(paragraphs.length).toBe(2)
385
- expect(paragraphs[0]!.textContent).toBe('Line 1')
386
- expect(paragraphs[1]!.textContent).toBe('Line 2')
365
+ expect(paragraphs[0]!.textContent).toBe("Line 1")
366
+ expect(paragraphs[1]!.textContent).toBe("Line 2")
387
367
  canvas.remove()
388
368
  })
389
369
  })
390
370
 
391
371
  // ─── Preview render function ─────────────────────────────────────────────────
392
372
 
393
- describe('preview render', () => {
394
- it('renders a component with args', () => {
373
+ describe("preview render", () => {
374
+ it("renders a component with args", () => {
395
375
  function Badge(props: { text: string }) {
396
376
  return <span class="badge">{props.text}</span>
397
377
  }
398
378
 
399
379
  const canvas = createCanvas()
400
- const vnode = previewRender({ text: 'New' }, { component: Badge })
380
+ const vnode = previewRender({ text: "New" }, { component: Badge })
401
381
  const unmount = mount(vnode, canvas)
402
382
 
403
- expect(canvas.querySelector('.badge')!.textContent).toBe('New')
383
+ expect(canvas.querySelector(".badge")!.textContent).toBe("New")
404
384
  unmount()
405
385
  canvas.remove()
406
386
  })
407
387
 
408
- it('throws when no component is provided', () => {
409
- expect(() => previewRender({ foo: 'bar' }, {})).toThrow(
410
- '[@pyreon/storybook] No component provided',
388
+ it("throws when no component is provided", () => {
389
+ expect(() => previewRender({ foo: "bar" }, {})).toThrow(
390
+ "[@pyreon/storybook] No component provided",
391
+ )
392
+ })
393
+
394
+ it("throws when component is undefined", () => {
395
+ expect(() => previewRender({ foo: "bar" }, { component: undefined } as any)).toThrow(
396
+ "[@pyreon/storybook] No component provided",
411
397
  )
412
398
  })
399
+ })
400
+
401
+ // ─── Decorator wrapping ─────────────────────────────────────────────────────
402
+
403
+ describe("decorator wrapping", () => {
404
+ it("decorator modifies the rendered VNode structure", () => {
405
+ function Badge(props: { text: string }) {
406
+ return <span class="badge">{props.text}</span>
407
+ }
408
+
409
+ const withCard: DecoratorFn<{ text: string }> = (storyFn, ctx) => {
410
+ return (
411
+ <div class="card">
412
+ <h3>Decorated</h3>
413
+ {storyFn(ctx.args, ctx)}
414
+ </div>
415
+ )
416
+ }
417
+
418
+ const canvas = createCanvas()
419
+ const context: StoryContext<{ text: string }> = {
420
+ args: { text: "Info" },
421
+ argTypes: {},
422
+ globals: {},
423
+ id: "1",
424
+ kind: "Badge",
425
+ name: "Default",
426
+ viewMode: "story",
427
+ }
428
+
429
+ const decorated = withCard((args) => <Badge {...args} />, context)
430
+ const unmount = mount(decorated, canvas)
431
+
432
+ expect(canvas.querySelector(".card")).toBeTruthy()
433
+ expect(canvas.querySelector("h3")!.textContent).toBe("Decorated")
434
+ expect(canvas.querySelector(".badge")!.textContent).toBe("Info")
435
+ unmount()
436
+ canvas.remove()
437
+ })
438
+ })
439
+
440
+ // ─── Component with no args ─────────────────────────────────────────────────
441
+
442
+ describe("component with no args", () => {
443
+ it("renders a component without any props via renderToCanvas", () => {
444
+ function Logo() {
445
+ return <img src="/logo.png" alt="Logo" />
446
+ }
447
+
448
+ const canvas = createCanvas()
449
+ renderToCanvas(
450
+ makeRenderContext({
451
+ storyFn: () => <Logo />,
452
+ args: {},
453
+ }),
454
+ canvas,
455
+ )
456
+
457
+ expect(canvas.querySelector("img")).toBeTruthy()
458
+ expect(canvas.querySelector("img")!.getAttribute("alt")).toBe("Logo")
459
+ canvas.remove()
460
+ })
461
+
462
+ it("renders a component with no args via defaultRender", () => {
463
+ function Divider() {
464
+ return <hr class="divider" />
465
+ }
466
+
467
+ const canvas = createCanvas()
468
+ const vnode = defaultRender(Divider, {})
469
+ const unmount = mount(vnode, canvas)
470
+
471
+ expect(canvas.querySelector("hr")).toBeTruthy()
472
+ expect(canvas.querySelector(".divider")).toBeTruthy()
473
+ unmount()
474
+ canvas.remove()
475
+ })
476
+
477
+ it("preview render works with empty args", () => {
478
+ function Spinner() {
479
+ return <div class="spinner">Loading...</div>
480
+ }
481
+
482
+ const canvas = createCanvas()
483
+ const vnode = previewRender({}, { component: Spinner })
484
+ const unmount = mount(vnode, canvas)
485
+
486
+ expect(canvas.querySelector(".spinner")!.textContent).toBe("Loading...")
487
+ unmount()
488
+ canvas.remove()
489
+ })
490
+ })
491
+
492
+ // ─── Error handling ──────────────────────────────────────────────────────────
493
+
494
+ describe("error handling", () => {
495
+ it("showError is called when storyFn itself throws", () => {
496
+ const canvas = createCanvas()
497
+ let errorShown: { title: string; description: string } | null = null
498
+
499
+ renderToCanvas(
500
+ {
501
+ storyFn: () => {
502
+ throw new Error("storyFn exploded")
503
+ },
504
+ storyContext: { args: {} },
505
+ showMain: () => {
506
+ /* noop */
507
+ },
508
+ showError: (err: { title: string; description: string }) => {
509
+ errorShown = err
510
+ },
511
+ forceRemount: false,
512
+ },
513
+ canvas,
514
+ )
413
515
 
414
- it('throws when component is undefined', () => {
415
- expect(() =>
416
- previewRender({ foo: 'bar' }, { component: undefined } as any),
417
- ).toThrow('[@pyreon/storybook] No component provided')
516
+ expect(errorShown).not.toBeNull()
517
+ expect(errorShown!.description).toBe("storyFn exploded")
518
+ canvas.remove()
519
+ })
520
+
521
+ it("showError receives string-coerced non-Error throws", () => {
522
+ const canvas = createCanvas()
523
+ let errorShown: { title: string; description: string } | null = null
524
+
525
+ renderToCanvas(
526
+ {
527
+ storyFn: () => {
528
+ throw "raw string error"
529
+ },
530
+ storyContext: { args: {} },
531
+ showMain: () => {
532
+ /* noop */
533
+ },
534
+ showError: (err: { title: string; description: string }) => {
535
+ errorShown = err
536
+ },
537
+ forceRemount: false,
538
+ },
539
+ canvas,
540
+ )
541
+
542
+ expect(errorShown).not.toBeNull()
543
+ expect(errorShown!.description).toBe("raw string error")
544
+ canvas.remove()
545
+ })
546
+
547
+ it("component that throws during setup is handled by the framework", () => {
548
+ const canvas = createCanvas()
549
+
550
+ function Broken(): never {
551
+ throw new Error("Component exploded")
552
+ }
553
+
554
+ // Pyreon's mount catches component setup errors internally,
555
+ // so renderToCanvas proceeds normally (showMain is called)
556
+ let mainShown = false
557
+ renderToCanvas(
558
+ {
559
+ storyFn: () => <Broken />,
560
+ storyContext: { args: {} },
561
+ showMain: () => {
562
+ mainShown = true
563
+ },
564
+ showError: () => {
565
+ /* noop */
566
+ },
567
+ forceRemount: false,
568
+ },
569
+ canvas,
570
+ )
571
+
572
+ // mount() catches the component error internally — renderToCanvas's
573
+ // try/catch does not see it, so showMain is called
574
+ expect(mainShown).toBe(true)
575
+ canvas.remove()
576
+ })
577
+ })
578
+
579
+ // ─── Re-render cleanup ──────────────────────────────────────────────────────
580
+
581
+ describe("re-render cleanup", () => {
582
+ it("calling renderToCanvas twice cleans up previous mount", () => {
583
+ const canvas = createCanvas()
584
+
585
+ renderToCanvas(makeRenderContext({ storyFn: () => <div class="first">First</div> }), canvas)
586
+ expect(canvas.querySelector(".first")).toBeTruthy()
587
+
588
+ renderToCanvas(makeRenderContext({ storyFn: () => <div class="second">Second</div> }), canvas)
589
+
590
+ // Previous content should be gone
591
+ expect(canvas.querySelector(".first")).toBeNull()
592
+ expect(canvas.querySelector(".second")).toBeTruthy()
593
+ expect(canvas.textContent).toBe("Second")
594
+ canvas.remove()
595
+ })
596
+
597
+ it("re-render disposes effects from previous story", () => {
598
+ const canvas = createCanvas()
599
+ let effectCount = 0
600
+
601
+ const sig = signal(0)
602
+ function ReactiveStory() {
603
+ effect(() => {
604
+ sig()
605
+ effectCount++
606
+ })
607
+ return <div>Reactive</div>
608
+ }
609
+
610
+ renderToCanvas(makeRenderContext({ storyFn: () => <ReactiveStory /> }), canvas)
611
+
612
+ const afterFirst = effectCount
613
+ sig.set(1)
614
+ expect(effectCount).toBe(afterFirst + 1)
615
+
616
+ // Re-render with a different story
617
+ renderToCanvas(makeRenderContext({ storyFn: () => <div>Static</div> }), canvas)
618
+
619
+ const afterSecond = effectCount
620
+ sig.set(2)
621
+ sig.set(3)
622
+ // Old effect should be disposed — count should not increase
623
+ expect(effectCount).toBe(afterSecond)
624
+ canvas.remove()
625
+ })
626
+
627
+ it("showMain is called on successful render", () => {
628
+ const canvas = createCanvas()
629
+ let mainShown = false
630
+
631
+ renderToCanvas(
632
+ {
633
+ storyFn: () => <div>OK</div>,
634
+ storyContext: { args: {} },
635
+ showMain: () => {
636
+ mainShown = true
637
+ },
638
+ showError: () => {
639
+ /* noop */
640
+ },
641
+ forceRemount: false,
642
+ },
643
+ canvas,
644
+ )
645
+
646
+ expect(mainShown).toBe(true)
647
+ canvas.remove()
648
+ })
649
+ })
650
+
651
+ // ─── Missing component in context ───────────────────────────────────────────
652
+
653
+ describe("missing component in context", () => {
654
+ it("preview render throws when context has no component", () => {
655
+ expect(() => previewRender({ value: 1 }, {})).toThrow(
656
+ "[@pyreon/storybook] No component provided",
657
+ )
658
+ })
659
+
660
+ it("preview render throws when context.component is null", () => {
661
+ expect(() => previewRender({ value: 1 }, { component: null } as any)).toThrow(
662
+ "[@pyreon/storybook] No component provided",
663
+ )
664
+ })
665
+
666
+ it("renderToCanvas works without component in storyContext when storyFn is provided", () => {
667
+ const canvas = createCanvas()
668
+
669
+ renderToCanvas(
670
+ makeRenderContext({
671
+ storyFn: () => <div>No component needed</div>,
672
+ }),
673
+ canvas,
674
+ )
675
+
676
+ expect(canvas.textContent).toBe("No component needed")
677
+ canvas.remove()
418
678
  })
419
679
  })
package/src/types.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ComponentFn, Props, VNodeChild } from '@pyreon/core'
1
+ import type { ComponentFn, Props, VNodeChild } from "@pyreon/core"
2
2
 
3
3
  // ─── Storybook Renderer Interface ────────────────────────────────────────────
4
4
 
@@ -26,13 +26,10 @@ export interface StoryContext<TArgs = Props> {
26
26
  id: string
27
27
  kind: string
28
28
  name: string
29
- viewMode: 'story' | 'docs'
29
+ viewMode: "story" | "docs"
30
30
  }
31
31
 
32
- export type StoryFn<TArgs = Props> = (
33
- args: TArgs,
34
- context: StoryContext<TArgs>,
35
- ) => VNodeChild
32
+ export type StoryFn<TArgs = Props> = (args: TArgs, context: StoryContext<TArgs>) => VNodeChild
36
33
 
37
34
  export type DecoratorFn<TArgs = Props> = (
38
35
  storyFn: StoryFn<TArgs>,
@@ -84,10 +81,7 @@ export interface StoryObj<TMeta extends Meta<any> = Meta> {
84
81
  /** Tags for this story. */
85
82
  tags?: string[]
86
83
  /** Override the render function for this story. */
87
- render?: (
88
- args: MetaArgs<TMeta>,
89
- context: StoryContext<MetaArgs<TMeta>>,
90
- ) => VNodeChild
84
+ render?: (args: MetaArgs<TMeta>, context: StoryContext<MetaArgs<TMeta>>) => VNodeChild
91
85
  /** Story name override. */
92
86
  name?: string
93
87
  /** Play function for interaction tests. */