@particle-academy/fancy-flow 0.33.3 → 0.34.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/README.md CHANGED
@@ -228,9 +228,9 @@ To pass a custom element registry (the same one you give `CmsPage` at runtime,
228
228
  or the edit canvas renders your node types as blank placeholders):
229
229
 
230
230
  ```ts
231
- import { useFancyCmsForRichInput } from "@particle-academy/fancy-flow/rich-input";
231
+ import { registerFancyCmsForRichInput } from "@particle-academy/fancy-flow/rich-input";
232
232
 
233
- useFancyCmsForRichInput({ registry: myElements, data: previewData });
233
+ registerFancyCmsForRichInput({ registry: myElements, data: previewData });
234
234
  ```
235
235
 
236
236
  The underlying seam (`registerRichInputAdapter`) stays public if you need a
@@ -28,7 +28,7 @@ function isPageDoc(value) {
28
28
  const v = value;
29
29
  return Array.isArray(v.sections) && typeof v.nodes === "object" && v.nodes !== null;
30
30
  }
31
- function useFancyCmsForRichInput(options = {}) {
31
+ function registerFancyCmsForRichInput(options = {}) {
32
32
  return registerRichInputAdapter({
33
33
  FauxClient: reactFancy.FauxClient,
34
34
  // Render the page at a real desktop width and scale it down, rather than
@@ -47,10 +47,12 @@ function useFancyCmsForRichInput(options = {}) {
47
47
  })
48
48
  });
49
49
  }
50
- useFancyCmsForRichInput();
50
+ registerFancyCmsForRichInput();
51
+ var useFancyCmsForRichInput = registerFancyCmsForRichInput;
51
52
 
52
53
  exports.emptyRichInputDoc = emptyRichInputDoc;
53
54
  exports.isPageDoc = isPageDoc;
55
+ exports.registerFancyCmsForRichInput = registerFancyCmsForRichInput;
54
56
  exports.useFancyCmsForRichInput = useFancyCmsForRichInput;
55
57
  //# sourceMappingURL=rich-input.cjs.map
56
58
  //# sourceMappingURL=rich-input.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/registry/rich-input.tsx","../src/rich-input.tsx"],"names":["emptyDoc","FauxClient","createElement","CmsPage","Editor"],"mappings":";;;;;;;;AA+CA,IAAI,OAAA,GAAmC,IAAA;AACvC,IAAM,SAAA,uBAAgB,GAAA,EAAgB;AAG/B,SAAS,yBAAyB,IAAA,EAAoC;AAC3E,EAAA,OAAA,GAAU,IAAA;AACV,EAAA,KAAA,MAAW,CAAA,IAAK,WAAW,CAAA,EAAE;AAC7B,EAAA,OAAO,MAAM;AACX,IAAA,IAAI,YAAY,IAAA,EAAM;AACpB,MAAA,OAAA,GAAU,IAAA;AACV,MAAA,KAAA,MAAW,CAAA,IAAK,WAAW,CAAA,EAAE;AAAA,IAC/B;AAAA,EACF,CAAA;AACF;;;ACvBO,SAAS,iBAAA,CAAkB,KAAK,YAAA,EAAuB;AAC5D,EAAA,OAAOA,oBAAS,EAAE,CAAA;AACpB;AAGO,SAAS,UAAU,KAAA,EAAkC;AAC1D,EAAA,IAAI,KAAA,KAAU,IAAA,IAAQ,OAAO,KAAA,KAAU,UAAU,OAAO,KAAA;AACxD,EAAA,MAAM,CAAA,GAAI,KAAA;AACV,EAAA,OAAO,KAAA,CAAM,OAAA,CAAQ,CAAA,CAAE,QAAQ,CAAA,IAAK,OAAO,CAAA,CAAE,KAAA,KAAU,QAAA,IAAY,CAAA,CAAE,KAAA,KAAU,IAAA;AACjF;AAQO,SAAS,uBAAA,CAAwB,OAAA,GAMpC,EAAC,EAAe;AAClB,EAAA,OAAO,wBAAA,CAAyB;AAAA,gBAC9BC,qBAAA;AAAA;AAAA;AAAA;AAAA,IAIA,UAAA,EAAY,EAAE,OAAA,EAAS,SAAA,EAAW,OAAO,IAAA,EAAM,KAAA,EAAO,KAAA,EAAO,IAAA,EAAM,IAAA,EAAK;AAAA;AAAA;AAAA,IAIxE,gBAAgB,CAAC,GAAA,KACf,SAAA,CAAU,GAAG,IACTC,mBAAA,CAAcC,kBAAA,EAAS,EAAE,GAAA,EAAK,UAAU,OAAA,CAAQ,QAAA,EAAU,MAAM,OAAA,CAAQ,IAAA,EAAM,CAAA,GAC9E,IAAA;AAAA;AAAA,IAGN,cAAc,CAAC,EAAE,OAAO,QAAA,EAAS,KAC/BD,oBAAcE,iBAAA,EAAQ;AAAA,MACpB,YAAA,EAAc,SAAA,CAAU,KAAK,CAAA,GAAI,QAAQ,iBAAA,EAAkB;AAAA,MAC3D,QAAA;AAAA,MACA,UAAU,OAAA,CAAQ,QAAA;AAAA,MAClB,MAAM,OAAA,CAAQ;AAAA,KACf;AAAA,GACJ,CAAA;AACH;AAEA,uBAAA,EAAwB","file":"rich-input.cjs","sourcesContent":["import type { ComponentType, ReactNode } from \"react\";\n\n/**\n * Rich user input — the injection point.\n *\n * `rich_user_input` pauses a run on a fully authored page (long-form content,\n * required reading + confirmation, multi-section forms) rather than a flat\n * field list. That page IS a fancy-cms document — fancy-flow defines no\n * document schema of its own and never re-implements one.\n *\n * The wiring ships in the box. Import the subpath once and the node lights up:\n *\n * ```ts\n * import \"@particle-academy/fancy-flow/rich-input\";\n * ```\n *\n * That module registers fancy-cms's `PageDoc` + `CmsPage` renderer + `Editor`\n * against this seam. It lives on a separate entry so `@particle-academy/\n * fancy-cms-ui` and `@particle-academy/react-fancy` stay OPTIONAL peers — most\n * flows never use a rich input, and a workflow editor should not drag a CMS\n * into every install.\n *\n * The seam stays public so a host can substitute a different document engine,\n * but that is the escape hatch, not the expected path.\n *\n * Until something registers, the node still registers and still round-trips\n * its config — it renders a \"how to enable\" body rather than an empty card.\n */\nexport type RichInputAdapter = {\n /**\n * react-fancy's `FauxClient` (or any component with the same shape) — a\n * frame that mimics a browser window or device and scales its content down\n * to fit. Used to preview the authored page inside the node card.\n */\n FauxClient?: ComponentType<any>;\n /**\n * Props for the frame — e.g. `{ variant: \"browser\", width: 1280, scale: \"fit\" }`\n * so a full-width page renders at its real width and scales down into the\n * node card instead of reflowing to a cramped layout that misrepresents it.\n */\n frameProps?: Record<string, unknown>;\n /** Render the stored document read-only, for the in-node preview. */\n renderDocument?: (doc: unknown) => ReactNode;\n /** Editor mounted in the config panel via `renderDocumentField`. */\n renderEditor?: (props: { value: unknown; onChange: (next: unknown) => void }) => ReactNode;\n};\n\nlet adapter: RichInputAdapter | null = null;\nconst listeners = new Set<() => void>();\n\n/** Install the host's document editor + preview frame. Returns an unregister fn. */\nexport function registerRichInputAdapter(next: RichInputAdapter): () => void {\n adapter = next;\n for (const l of listeners) l();\n return () => {\n if (adapter === next) {\n adapter = null;\n for (const l of listeners) l();\n }\n };\n}\n\n/** The registered adapter, or null when the host hasn't wired one. */\nexport function getRichInputAdapter(): RichInputAdapter | null {\n return adapter;\n}\n\n/** True once a host has wired an adapter — i.e. the node is usable. */\nexport function isRichInputEnabled(): boolean {\n return adapter !== null && (adapter.renderDocument !== undefined || adapter.renderEditor !== undefined);\n}\n\n/** Subscribe to adapter changes (so nodes re-render when it lands). */\nexport function onRichInputAdapterChanged(fn: () => void): () => void {\n listeners.add(fn);\n return () => listeners.delete(fn);\n}\n\n/**\n * RichInputPreview — the node card body. Shows the authored page inside a\n * FauxClient frame so an author can see, at a glance on the canvas, what the\n * person hitting this step will be looking at.\n *\n * Degrades in two steps rather than one: no adapter at all → install hint;\n * adapter but nothing authored yet → an empty frame with the step title. A\n * blank node body would read as \"broken\" in both cases.\n */\nexport function RichInputPreview({ config }: { config: Record<string, unknown> }) {\n const a = getRichInputAdapter();\n const title = typeof config.title === \"string\" && config.title.trim() !== \"\" ? config.title : \"Untitled step\";\n const doc = config.document;\n\n if (!a || (!a.renderDocument && !a.renderEditor)) {\n return (\n <div className=\"ff-rich-preview\">\n <span className=\"ff-rich-preview__title\">{title}</span>\n <div className=\"ff-rich-preview__unavailable\">\n Add <code>@particle-academy/fancy-cms-ui</code> +{\" \"}\n <code>@particle-academy/react-fancy</code>, then{\" \"}\n <code>import \"@particle-academy/fancy-flow/rich-input\"</code>.\n </div>\n </div>\n );\n }\n\n const body = doc === undefined || doc === null\n ? <p className=\"ff-rich-preview__unavailable\">Nothing authored yet.</p>\n : a.renderDocument?.(doc) ?? null;\n\n const Frame = a.FauxClient;\n\n return (\n <div className=\"ff-rich-preview\">\n <span className=\"ff-rich-preview__title\">{title}</span>\n <div className=\"ff-rich-preview__frame\">\n {Frame ? <Frame {...(a.frameProps ?? { variant: \"browser\" })}>{body}</Frame> : body}\n </div>\n </div>\n );\n}\n","/**\n * `@particle-academy/fancy-flow/rich-input` — wires `rich_user_input` to the\n * real CMS.\n *\n * Import this entry once, anywhere in your app, and the node lights up:\n *\n * ```ts\n * import \"@particle-academy/fancy-flow/rich-input\";\n * ```\n *\n * ## Why this is a separate entry\n *\n * The document a rich input step shows IS a fancy-cms page — same `PageDoc`,\n * same renderer, same editor. fancy-flow declares no document schema of its\n * own and never re-implements one; duplicating the model would guarantee the\n * two drift, and a step authored in fancy-flow would stop being a thing\n * fancy-cms could open.\n *\n * But most flows never use a rich input, and a workflow editor should not drag\n * a CMS into every install. So the dependency lives HERE, on an opt-in\n * subpath, instead of in the main entry. `@particle-academy/fancy-cms-ui` and\n * `@particle-academy/react-fancy` are optional peers: required to import this\n * module, irrelevant if you never do.\n */\nimport { createElement } from \"react\";\nimport { CmsPage, Editor, emptyDoc, type PageDoc } from \"@particle-academy/fancy-cms-ui\";\nimport { FauxClient } from \"@particle-academy/react-fancy\";\nimport { registerRichInputAdapter } from \"./registry/rich-input\";\n\nexport type { PageDoc };\n\n/**\n * A blank page for a step that hasn't been authored yet.\n *\n * `Editor` takes a `defaultValue`, not a nullable doc — so a never-authored\n * step needs a real empty document to open into rather than a blank screen.\n */\nexport function emptyRichInputDoc(id = \"rich-input\"): PageDoc {\n return emptyDoc(id);\n}\n\n/** True when a stored value is a fancy-cms page document. */\nexport function isPageDoc(value: unknown): value is PageDoc {\n if (value === null || typeof value !== \"object\") return false;\n const v = value as Partial<PageDoc>;\n return Array.isArray(v.sections) && typeof v.nodes === \"object\" && v.nodes !== null;\n}\n\n/**\n * Register fancy-cms as the document engine for `rich_user_input`.\n *\n * Called automatically on import. Exported so a host can re-register after\n * swapping in a custom element registry, and so the call is testable.\n */\nexport function useFancyCmsForRichInput(options: {\n /** Custom element registry — pass the SAME one you give `CmsPage` at runtime,\n * or the edit canvas renders your node types as blank placeholders. */\n registry?: Parameters<typeof CmsPage>[0][\"registry\"];\n /** Data context that `{ $bind }` props preview against. */\n data?: Parameters<typeof CmsPage>[0][\"data\"];\n} = {}): () => void {\n return registerRichInputAdapter({\n FauxClient,\n // Render the page at a real desktop width and scale it down, rather than\n // letting it reflow into a card-sized viewport — a preview that reflows\n // shows a layout the person hitting the step will never see.\n frameProps: { variant: \"browser\", width: 1280, scale: \"fit\", dots: true },\n\n // Preview: the CMS's own renderer, so the node card shows exactly what the\n // person hitting this step will see. Styles are scoped by the frame.\n renderDocument: (doc) =>\n isPageDoc(doc)\n ? createElement(CmsPage, { doc, registry: options.registry, data: options.data })\n : null,\n\n // Authoring: the CMS's own WYSIWYG editor, in the config panel.\n renderEditor: ({ value, onChange }) =>\n createElement(Editor, {\n defaultValue: isPageDoc(value) ? value : emptyRichInputDoc(),\n onChange: onChange as (doc: PageDoc) => void,\n registry: options.registry,\n data: options.data,\n }),\n });\n}\n\nuseFancyCmsForRichInput();\n"]}
1
+ {"version":3,"sources":["../src/registry/rich-input.tsx","../src/rich-input.tsx"],"names":["emptyDoc","FauxClient","createElement","CmsPage","Editor"],"mappings":";;;;;;;;AA+CA,IAAI,OAAA,GAAmC,IAAA;AACvC,IAAM,SAAA,uBAAgB,GAAA,EAAgB;AAG/B,SAAS,yBAAyB,IAAA,EAAoC;AAC3E,EAAA,OAAA,GAAU,IAAA;AACV,EAAA,KAAA,MAAW,CAAA,IAAK,WAAW,CAAA,EAAE;AAC7B,EAAA,OAAO,MAAM;AACX,IAAA,IAAI,YAAY,IAAA,EAAM;AACpB,MAAA,OAAA,GAAU,IAAA;AACV,MAAA,KAAA,MAAW,CAAA,IAAK,WAAW,CAAA,EAAE;AAAA,IAC/B;AAAA,EACF,CAAA;AACF;;;ACvBO,SAAS,iBAAA,CAAkB,KAAK,YAAA,EAAuB;AAC5D,EAAA,OAAOA,oBAAS,EAAE,CAAA;AACpB;AAGO,SAAS,UAAU,KAAA,EAAkC;AAC1D,EAAA,IAAI,KAAA,KAAU,IAAA,IAAQ,OAAO,KAAA,KAAU,UAAU,OAAO,KAAA;AACxD,EAAA,MAAM,CAAA,GAAI,KAAA;AACV,EAAA,OAAO,KAAA,CAAM,OAAA,CAAQ,CAAA,CAAE,QAAQ,CAAA,IAAK,OAAO,CAAA,CAAE,KAAA,KAAU,QAAA,IAAY,CAAA,CAAE,KAAA,KAAU,IAAA;AACjF;AAQO,SAAS,4BAAA,CAA6B,OAAA,GAMzC,EAAC,EAAe;AAClB,EAAA,OAAO,wBAAA,CAAyB;AAAA,gBAC9BC,qBAAA;AAAA;AAAA;AAAA;AAAA,IAIA,UAAA,EAAY,EAAE,OAAA,EAAS,SAAA,EAAW,OAAO,IAAA,EAAM,KAAA,EAAO,KAAA,EAAO,IAAA,EAAM,IAAA,EAAK;AAAA;AAAA;AAAA,IAIxE,gBAAgB,CAAC,GAAA,KACf,SAAA,CAAU,GAAG,IACTC,mBAAA,CAAcC,kBAAA,EAAS,EAAE,GAAA,EAAK,UAAU,OAAA,CAAQ,QAAA,EAAU,MAAM,OAAA,CAAQ,IAAA,EAAM,CAAA,GAC9E,IAAA;AAAA;AAAA,IAGN,cAAc,CAAC,EAAE,OAAO,QAAA,EAAS,KAC/BD,oBAAcE,iBAAA,EAAQ;AAAA,MACpB,YAAA,EAAc,SAAA,CAAU,KAAK,CAAA,GAAI,QAAQ,iBAAA,EAAkB;AAAA,MAC3D,QAAA;AAAA,MACA,UAAU,OAAA,CAAQ,QAAA;AAAA,MAClB,MAAM,OAAA,CAAQ;AAAA,KACf;AAAA,GACJ,CAAA;AACH;AAEA,4BAAA,EAA6B;AAatB,IAAM,uBAAA,GAA0B","file":"rich-input.cjs","sourcesContent":["import type { ComponentType, ReactNode } from \"react\";\n\n/**\n * Rich user input — the injection point.\n *\n * `rich_user_input` pauses a run on a fully authored page (long-form content,\n * required reading + confirmation, multi-section forms) rather than a flat\n * field list. That page IS a fancy-cms document — fancy-flow defines no\n * document schema of its own and never re-implements one.\n *\n * The wiring ships in the box. Import the subpath once and the node lights up:\n *\n * ```ts\n * import \"@particle-academy/fancy-flow/rich-input\";\n * ```\n *\n * That module registers fancy-cms's `PageDoc` + `CmsPage` renderer + `Editor`\n * against this seam. It lives on a separate entry so `@particle-academy/\n * fancy-cms-ui` and `@particle-academy/react-fancy` stay OPTIONAL peers — most\n * flows never use a rich input, and a workflow editor should not drag a CMS\n * into every install.\n *\n * The seam stays public so a host can substitute a different document engine,\n * but that is the escape hatch, not the expected path.\n *\n * Until something registers, the node still registers and still round-trips\n * its config — it renders a \"how to enable\" body rather than an empty card.\n */\nexport type RichInputAdapter = {\n /**\n * react-fancy's `FauxClient` (or any component with the same shape) — a\n * frame that mimics a browser window or device and scales its content down\n * to fit. Used to preview the authored page inside the node card.\n */\n FauxClient?: ComponentType<any>;\n /**\n * Props for the frame — e.g. `{ variant: \"browser\", width: 1280, scale: \"fit\" }`\n * so a full-width page renders at its real width and scales down into the\n * node card instead of reflowing to a cramped layout that misrepresents it.\n */\n frameProps?: Record<string, unknown>;\n /** Render the stored document read-only, for the in-node preview. */\n renderDocument?: (doc: unknown) => ReactNode;\n /** Editor mounted in the config panel via `renderDocumentField`. */\n renderEditor?: (props: { value: unknown; onChange: (next: unknown) => void }) => ReactNode;\n};\n\nlet adapter: RichInputAdapter | null = null;\nconst listeners = new Set<() => void>();\n\n/** Install the host's document editor + preview frame. Returns an unregister fn. */\nexport function registerRichInputAdapter(next: RichInputAdapter): () => void {\n adapter = next;\n for (const l of listeners) l();\n return () => {\n if (adapter === next) {\n adapter = null;\n for (const l of listeners) l();\n }\n };\n}\n\n/** The registered adapter, or null when the host hasn't wired one. */\nexport function getRichInputAdapter(): RichInputAdapter | null {\n return adapter;\n}\n\n/** True once a host has wired an adapter — i.e. the node is usable. */\nexport function isRichInputEnabled(): boolean {\n return adapter !== null && (adapter.renderDocument !== undefined || adapter.renderEditor !== undefined);\n}\n\n/** Subscribe to adapter changes (so nodes re-render when it lands). */\nexport function onRichInputAdapterChanged(fn: () => void): () => void {\n listeners.add(fn);\n return () => listeners.delete(fn);\n}\n\n/**\n * RichInputPreview — the node card body. Shows the authored page inside a\n * FauxClient frame so an author can see, at a glance on the canvas, what the\n * person hitting this step will be looking at.\n *\n * Degrades in two steps rather than one: no adapter at all → install hint;\n * adapter but nothing authored yet → an empty frame with the step title. A\n * blank node body would read as \"broken\" in both cases.\n */\nexport function RichInputPreview({ config }: { config: Record<string, unknown> }) {\n const a = getRichInputAdapter();\n const title = typeof config.title === \"string\" && config.title.trim() !== \"\" ? config.title : \"Untitled step\";\n const doc = config.document;\n\n if (!a || (!a.renderDocument && !a.renderEditor)) {\n return (\n <div className=\"ff-rich-preview\">\n <span className=\"ff-rich-preview__title\">{title}</span>\n <div className=\"ff-rich-preview__unavailable\">\n Add <code>@particle-academy/fancy-cms-ui</code> +{\" \"}\n <code>@particle-academy/react-fancy</code>, then{\" \"}\n <code>import \"@particle-academy/fancy-flow/rich-input\"</code>.\n </div>\n </div>\n );\n }\n\n const body = doc === undefined || doc === null\n ? <p className=\"ff-rich-preview__unavailable\">Nothing authored yet.</p>\n : a.renderDocument?.(doc) ?? null;\n\n const Frame = a.FauxClient;\n\n return (\n <div className=\"ff-rich-preview\">\n <span className=\"ff-rich-preview__title\">{title}</span>\n <div className=\"ff-rich-preview__frame\">\n {Frame ? <Frame {...(a.frameProps ?? { variant: \"browser\" })}>{body}</Frame> : body}\n </div>\n </div>\n );\n}\n","/**\n * `@particle-academy/fancy-flow/rich-input` — wires `rich_user_input` to the\n * real CMS.\n *\n * Import this entry once, anywhere in your app, and the node lights up:\n *\n * ```ts\n * import \"@particle-academy/fancy-flow/rich-input\";\n * ```\n *\n * ## Why this is a separate entry\n *\n * The document a rich input step shows IS a fancy-cms page — same `PageDoc`,\n * same renderer, same editor. fancy-flow declares no document schema of its\n * own and never re-implements one; duplicating the model would guarantee the\n * two drift, and a step authored in fancy-flow would stop being a thing\n * fancy-cms could open.\n *\n * But most flows never use a rich input, and a workflow editor should not drag\n * a CMS into every install. So the dependency lives HERE, on an opt-in\n * subpath, instead of in the main entry. `@particle-academy/fancy-cms-ui` and\n * `@particle-academy/react-fancy` are optional peers: required to import this\n * module, irrelevant if you never do.\n */\nimport { createElement } from \"react\";\nimport { CmsPage, Editor, emptyDoc, type PageDoc } from \"@particle-academy/fancy-cms-ui\";\nimport { FauxClient } from \"@particle-academy/react-fancy\";\nimport { registerRichInputAdapter } from \"./registry/rich-input\";\n\nexport type { PageDoc };\n\n/**\n * A blank page for a step that hasn't been authored yet.\n *\n * `Editor` takes a `defaultValue`, not a nullable doc — so a never-authored\n * step needs a real empty document to open into rather than a blank screen.\n */\nexport function emptyRichInputDoc(id = \"rich-input\"): PageDoc {\n return emptyDoc(id);\n}\n\n/** True when a stored value is a fancy-cms page document. */\nexport function isPageDoc(value: unknown): value is PageDoc {\n if (value === null || typeof value !== \"object\") return false;\n const v = value as Partial<PageDoc>;\n return Array.isArray(v.sections) && typeof v.nodes === \"object\" && v.nodes !== null;\n}\n\n/**\n * Register fancy-cms as the document engine for `rich_user_input`.\n *\n * Called automatically on import. Exported so a host can re-register after\n * swapping in a custom element registry, and so the call is testable.\n */\nexport function registerFancyCmsForRichInput(options: {\n /** Custom element registry — pass the SAME one you give `CmsPage` at runtime,\n * or the edit canvas renders your node types as blank placeholders. */\n registry?: Parameters<typeof CmsPage>[0][\"registry\"];\n /** Data context that `{ $bind }` props preview against. */\n data?: Parameters<typeof CmsPage>[0][\"data\"];\n} = {}): () => void {\n return registerRichInputAdapter({\n FauxClient,\n // Render the page at a real desktop width and scale it down, rather than\n // letting it reflow into a card-sized viewport — a preview that reflows\n // shows a layout the person hitting the step will never see.\n frameProps: { variant: \"browser\", width: 1280, scale: \"fit\", dots: true },\n\n // Preview: the CMS's own renderer, so the node card shows exactly what the\n // person hitting this step will see. Styles are scoped by the frame.\n renderDocument: (doc) =>\n isPageDoc(doc)\n ? createElement(CmsPage, { doc, registry: options.registry, data: options.data })\n : null,\n\n // Authoring: the CMS's own WYSIWYG editor, in the config panel.\n renderEditor: ({ value, onChange }) =>\n createElement(Editor, {\n defaultValue: isPageDoc(value) ? value : emptyRichInputDoc(),\n onChange: onChange as (doc: PageDoc) => void,\n registry: options.registry,\n data: options.data,\n }),\n });\n}\n\nregisterFancyCmsForRichInput();\n\n/**\n * @deprecated Renamed to `registerFancyCmsForRichInput`. This alias still works\n * and will not be removed in 0.x.\n *\n * The `use` prefix was wrong: this registers a document engine and returns a\n * dispose function — it is not a React hook and calls none. React reserves\n * `use*` for hooks, and the name made `react-hooks/rules-of-hooks` report the\n * module-scope call on import as a hook called outside a component. A linter\n * being misled by a name is the mild version; a reader assuming hook rules\n * apply, or that it must be called during render, is the real cost.\n */\nexport const useFancyCmsForRichInput = registerFancyCmsForRichInput;\n"]}
@@ -16,12 +16,24 @@ declare function isPageDoc(value: unknown): value is PageDoc;
16
16
  * Called automatically on import. Exported so a host can re-register after
17
17
  * swapping in a custom element registry, and so the call is testable.
18
18
  */
19
- declare function useFancyCmsForRichInput(options?: {
19
+ declare function registerFancyCmsForRichInput(options?: {
20
20
  /** Custom element registry — pass the SAME one you give `CmsPage` at runtime,
21
21
  * or the edit canvas renders your node types as blank placeholders. */
22
22
  registry?: Parameters<typeof CmsPage>[0]["registry"];
23
23
  /** Data context that `{ $bind }` props preview against. */
24
24
  data?: Parameters<typeof CmsPage>[0]["data"];
25
25
  }): () => void;
26
+ /**
27
+ * @deprecated Renamed to `registerFancyCmsForRichInput`. This alias still works
28
+ * and will not be removed in 0.x.
29
+ *
30
+ * The `use` prefix was wrong: this registers a document engine and returns a
31
+ * dispose function — it is not a React hook and calls none. React reserves
32
+ * `use*` for hooks, and the name made `react-hooks/rules-of-hooks` report the
33
+ * module-scope call on import as a hook called outside a component. A linter
34
+ * being misled by a name is the mild version; a reader assuming hook rules
35
+ * apply, or that it must be called during render, is the real cost.
36
+ */
37
+ declare const useFancyCmsForRichInput: typeof registerFancyCmsForRichInput;
26
38
 
27
- export { emptyRichInputDoc, isPageDoc, useFancyCmsForRichInput };
39
+ export { emptyRichInputDoc, isPageDoc, registerFancyCmsForRichInput, useFancyCmsForRichInput };
@@ -16,12 +16,24 @@ declare function isPageDoc(value: unknown): value is PageDoc;
16
16
  * Called automatically on import. Exported so a host can re-register after
17
17
  * swapping in a custom element registry, and so the call is testable.
18
18
  */
19
- declare function useFancyCmsForRichInput(options?: {
19
+ declare function registerFancyCmsForRichInput(options?: {
20
20
  /** Custom element registry — pass the SAME one you give `CmsPage` at runtime,
21
21
  * or the edit canvas renders your node types as blank placeholders. */
22
22
  registry?: Parameters<typeof CmsPage>[0]["registry"];
23
23
  /** Data context that `{ $bind }` props preview against. */
24
24
  data?: Parameters<typeof CmsPage>[0]["data"];
25
25
  }): () => void;
26
+ /**
27
+ * @deprecated Renamed to `registerFancyCmsForRichInput`. This alias still works
28
+ * and will not be removed in 0.x.
29
+ *
30
+ * The `use` prefix was wrong: this registers a document engine and returns a
31
+ * dispose function — it is not a React hook and calls none. React reserves
32
+ * `use*` for hooks, and the name made `react-hooks/rules-of-hooks` report the
33
+ * module-scope call on import as a hook called outside a component. A linter
34
+ * being misled by a name is the mild version; a reader assuming hook rules
35
+ * apply, or that it must be called during render, is the real cost.
36
+ */
37
+ declare const useFancyCmsForRichInput: typeof registerFancyCmsForRichInput;
26
38
 
27
- export { emptyRichInputDoc, isPageDoc, useFancyCmsForRichInput };
39
+ export { emptyRichInputDoc, isPageDoc, registerFancyCmsForRichInput, useFancyCmsForRichInput };
@@ -11,7 +11,7 @@ function isPageDoc(value) {
11
11
  const v = value;
12
12
  return Array.isArray(v.sections) && typeof v.nodes === "object" && v.nodes !== null;
13
13
  }
14
- function useFancyCmsForRichInput(options = {}) {
14
+ function registerFancyCmsForRichInput(options = {}) {
15
15
  return registerRichInputAdapter({
16
16
  FauxClient,
17
17
  // Render the page at a real desktop width and scale it down, rather than
@@ -30,8 +30,9 @@ function useFancyCmsForRichInput(options = {}) {
30
30
  })
31
31
  });
32
32
  }
33
- useFancyCmsForRichInput();
33
+ registerFancyCmsForRichInput();
34
+ var useFancyCmsForRichInput = registerFancyCmsForRichInput;
34
35
 
35
- export { emptyRichInputDoc, isPageDoc, useFancyCmsForRichInput };
36
+ export { emptyRichInputDoc, isPageDoc, registerFancyCmsForRichInput, useFancyCmsForRichInput };
36
37
  //# sourceMappingURL=rich-input.js.map
37
38
  //# sourceMappingURL=rich-input.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/rich-input.tsx"],"names":[],"mappings":";;;;;AAqCO,SAAS,iBAAA,CAAkB,KAAK,YAAA,EAAuB;AAC5D,EAAA,OAAO,SAAS,EAAE,CAAA;AACpB;AAGO,SAAS,UAAU,KAAA,EAAkC;AAC1D,EAAA,IAAI,KAAA,KAAU,IAAA,IAAQ,OAAO,KAAA,KAAU,UAAU,OAAO,KAAA;AACxD,EAAA,MAAM,CAAA,GAAI,KAAA;AACV,EAAA,OAAO,KAAA,CAAM,OAAA,CAAQ,CAAA,CAAE,QAAQ,CAAA,IAAK,OAAO,CAAA,CAAE,KAAA,KAAU,QAAA,IAAY,CAAA,CAAE,KAAA,KAAU,IAAA;AACjF;AAQO,SAAS,uBAAA,CAAwB,OAAA,GAMpC,EAAC,EAAe;AAClB,EAAA,OAAO,wBAAA,CAAyB;AAAA,IAC9B,UAAA;AAAA;AAAA;AAAA;AAAA,IAIA,UAAA,EAAY,EAAE,OAAA,EAAS,SAAA,EAAW,OAAO,IAAA,EAAM,KAAA,EAAO,KAAA,EAAO,IAAA,EAAM,IAAA,EAAK;AAAA;AAAA;AAAA,IAIxE,gBAAgB,CAAC,GAAA,KACf,SAAA,CAAU,GAAG,IACT,aAAA,CAAc,OAAA,EAAS,EAAE,GAAA,EAAK,UAAU,OAAA,CAAQ,QAAA,EAAU,MAAM,OAAA,CAAQ,IAAA,EAAM,CAAA,GAC9E,IAAA;AAAA;AAAA,IAGN,cAAc,CAAC,EAAE,OAAO,QAAA,EAAS,KAC/B,cAAc,MAAA,EAAQ;AAAA,MACpB,YAAA,EAAc,SAAA,CAAU,KAAK,CAAA,GAAI,QAAQ,iBAAA,EAAkB;AAAA,MAC3D,QAAA;AAAA,MACA,UAAU,OAAA,CAAQ,QAAA;AAAA,MAClB,MAAM,OAAA,CAAQ;AAAA,KACf;AAAA,GACJ,CAAA;AACH;AAEA,uBAAA,EAAwB","file":"rich-input.js","sourcesContent":["/**\n * `@particle-academy/fancy-flow/rich-input` — wires `rich_user_input` to the\n * real CMS.\n *\n * Import this entry once, anywhere in your app, and the node lights up:\n *\n * ```ts\n * import \"@particle-academy/fancy-flow/rich-input\";\n * ```\n *\n * ## Why this is a separate entry\n *\n * The document a rich input step shows IS a fancy-cms page — same `PageDoc`,\n * same renderer, same editor. fancy-flow declares no document schema of its\n * own and never re-implements one; duplicating the model would guarantee the\n * two drift, and a step authored in fancy-flow would stop being a thing\n * fancy-cms could open.\n *\n * But most flows never use a rich input, and a workflow editor should not drag\n * a CMS into every install. So the dependency lives HERE, on an opt-in\n * subpath, instead of in the main entry. `@particle-academy/fancy-cms-ui` and\n * `@particle-academy/react-fancy` are optional peers: required to import this\n * module, irrelevant if you never do.\n */\nimport { createElement } from \"react\";\nimport { CmsPage, Editor, emptyDoc, type PageDoc } from \"@particle-academy/fancy-cms-ui\";\nimport { FauxClient } from \"@particle-academy/react-fancy\";\nimport { registerRichInputAdapter } from \"./registry/rich-input\";\n\nexport type { PageDoc };\n\n/**\n * A blank page for a step that hasn't been authored yet.\n *\n * `Editor` takes a `defaultValue`, not a nullable doc — so a never-authored\n * step needs a real empty document to open into rather than a blank screen.\n */\nexport function emptyRichInputDoc(id = \"rich-input\"): PageDoc {\n return emptyDoc(id);\n}\n\n/** True when a stored value is a fancy-cms page document. */\nexport function isPageDoc(value: unknown): value is PageDoc {\n if (value === null || typeof value !== \"object\") return false;\n const v = value as Partial<PageDoc>;\n return Array.isArray(v.sections) && typeof v.nodes === \"object\" && v.nodes !== null;\n}\n\n/**\n * Register fancy-cms as the document engine for `rich_user_input`.\n *\n * Called automatically on import. Exported so a host can re-register after\n * swapping in a custom element registry, and so the call is testable.\n */\nexport function useFancyCmsForRichInput(options: {\n /** Custom element registry — pass the SAME one you give `CmsPage` at runtime,\n * or the edit canvas renders your node types as blank placeholders. */\n registry?: Parameters<typeof CmsPage>[0][\"registry\"];\n /** Data context that `{ $bind }` props preview against. */\n data?: Parameters<typeof CmsPage>[0][\"data\"];\n} = {}): () => void {\n return registerRichInputAdapter({\n FauxClient,\n // Render the page at a real desktop width and scale it down, rather than\n // letting it reflow into a card-sized viewport — a preview that reflows\n // shows a layout the person hitting the step will never see.\n frameProps: { variant: \"browser\", width: 1280, scale: \"fit\", dots: true },\n\n // Preview: the CMS's own renderer, so the node card shows exactly what the\n // person hitting this step will see. Styles are scoped by the frame.\n renderDocument: (doc) =>\n isPageDoc(doc)\n ? createElement(CmsPage, { doc, registry: options.registry, data: options.data })\n : null,\n\n // Authoring: the CMS's own WYSIWYG editor, in the config panel.\n renderEditor: ({ value, onChange }) =>\n createElement(Editor, {\n defaultValue: isPageDoc(value) ? value : emptyRichInputDoc(),\n onChange: onChange as (doc: PageDoc) => void,\n registry: options.registry,\n data: options.data,\n }),\n });\n}\n\nuseFancyCmsForRichInput();\n"]}
1
+ {"version":3,"sources":["../src/rich-input.tsx"],"names":[],"mappings":";;;;;AAqCO,SAAS,iBAAA,CAAkB,KAAK,YAAA,EAAuB;AAC5D,EAAA,OAAO,SAAS,EAAE,CAAA;AACpB;AAGO,SAAS,UAAU,KAAA,EAAkC;AAC1D,EAAA,IAAI,KAAA,KAAU,IAAA,IAAQ,OAAO,KAAA,KAAU,UAAU,OAAO,KAAA;AACxD,EAAA,MAAM,CAAA,GAAI,KAAA;AACV,EAAA,OAAO,KAAA,CAAM,OAAA,CAAQ,CAAA,CAAE,QAAQ,CAAA,IAAK,OAAO,CAAA,CAAE,KAAA,KAAU,QAAA,IAAY,CAAA,CAAE,KAAA,KAAU,IAAA;AACjF;AAQO,SAAS,4BAAA,CAA6B,OAAA,GAMzC,EAAC,EAAe;AAClB,EAAA,OAAO,wBAAA,CAAyB;AAAA,IAC9B,UAAA;AAAA;AAAA;AAAA;AAAA,IAIA,UAAA,EAAY,EAAE,OAAA,EAAS,SAAA,EAAW,OAAO,IAAA,EAAM,KAAA,EAAO,KAAA,EAAO,IAAA,EAAM,IAAA,EAAK;AAAA;AAAA;AAAA,IAIxE,gBAAgB,CAAC,GAAA,KACf,SAAA,CAAU,GAAG,IACT,aAAA,CAAc,OAAA,EAAS,EAAE,GAAA,EAAK,UAAU,OAAA,CAAQ,QAAA,EAAU,MAAM,OAAA,CAAQ,IAAA,EAAM,CAAA,GAC9E,IAAA;AAAA;AAAA,IAGN,cAAc,CAAC,EAAE,OAAO,QAAA,EAAS,KAC/B,cAAc,MAAA,EAAQ;AAAA,MACpB,YAAA,EAAc,SAAA,CAAU,KAAK,CAAA,GAAI,QAAQ,iBAAA,EAAkB;AAAA,MAC3D,QAAA;AAAA,MACA,UAAU,OAAA,CAAQ,QAAA;AAAA,MAClB,MAAM,OAAA,CAAQ;AAAA,KACf;AAAA,GACJ,CAAA;AACH;AAEA,4BAAA,EAA6B;AAatB,IAAM,uBAAA,GAA0B","file":"rich-input.js","sourcesContent":["/**\n * `@particle-academy/fancy-flow/rich-input` — wires `rich_user_input` to the\n * real CMS.\n *\n * Import this entry once, anywhere in your app, and the node lights up:\n *\n * ```ts\n * import \"@particle-academy/fancy-flow/rich-input\";\n * ```\n *\n * ## Why this is a separate entry\n *\n * The document a rich input step shows IS a fancy-cms page — same `PageDoc`,\n * same renderer, same editor. fancy-flow declares no document schema of its\n * own and never re-implements one; duplicating the model would guarantee the\n * two drift, and a step authored in fancy-flow would stop being a thing\n * fancy-cms could open.\n *\n * But most flows never use a rich input, and a workflow editor should not drag\n * a CMS into every install. So the dependency lives HERE, on an opt-in\n * subpath, instead of in the main entry. `@particle-academy/fancy-cms-ui` and\n * `@particle-academy/react-fancy` are optional peers: required to import this\n * module, irrelevant if you never do.\n */\nimport { createElement } from \"react\";\nimport { CmsPage, Editor, emptyDoc, type PageDoc } from \"@particle-academy/fancy-cms-ui\";\nimport { FauxClient } from \"@particle-academy/react-fancy\";\nimport { registerRichInputAdapter } from \"./registry/rich-input\";\n\nexport type { PageDoc };\n\n/**\n * A blank page for a step that hasn't been authored yet.\n *\n * `Editor` takes a `defaultValue`, not a nullable doc — so a never-authored\n * step needs a real empty document to open into rather than a blank screen.\n */\nexport function emptyRichInputDoc(id = \"rich-input\"): PageDoc {\n return emptyDoc(id);\n}\n\n/** True when a stored value is a fancy-cms page document. */\nexport function isPageDoc(value: unknown): value is PageDoc {\n if (value === null || typeof value !== \"object\") return false;\n const v = value as Partial<PageDoc>;\n return Array.isArray(v.sections) && typeof v.nodes === \"object\" && v.nodes !== null;\n}\n\n/**\n * Register fancy-cms as the document engine for `rich_user_input`.\n *\n * Called automatically on import. Exported so a host can re-register after\n * swapping in a custom element registry, and so the call is testable.\n */\nexport function registerFancyCmsForRichInput(options: {\n /** Custom element registry — pass the SAME one you give `CmsPage` at runtime,\n * or the edit canvas renders your node types as blank placeholders. */\n registry?: Parameters<typeof CmsPage>[0][\"registry\"];\n /** Data context that `{ $bind }` props preview against. */\n data?: Parameters<typeof CmsPage>[0][\"data\"];\n} = {}): () => void {\n return registerRichInputAdapter({\n FauxClient,\n // Render the page at a real desktop width and scale it down, rather than\n // letting it reflow into a card-sized viewport — a preview that reflows\n // shows a layout the person hitting the step will never see.\n frameProps: { variant: \"browser\", width: 1280, scale: \"fit\", dots: true },\n\n // Preview: the CMS's own renderer, so the node card shows exactly what the\n // person hitting this step will see. Styles are scoped by the frame.\n renderDocument: (doc) =>\n isPageDoc(doc)\n ? createElement(CmsPage, { doc, registry: options.registry, data: options.data })\n : null,\n\n // Authoring: the CMS's own WYSIWYG editor, in the config panel.\n renderEditor: ({ value, onChange }) =>\n createElement(Editor, {\n defaultValue: isPageDoc(value) ? value : emptyRichInputDoc(),\n onChange: onChange as (doc: PageDoc) => void,\n registry: options.registry,\n data: options.data,\n }),\n });\n}\n\nregisterFancyCmsForRichInput();\n\n/**\n * @deprecated Renamed to `registerFancyCmsForRichInput`. This alias still works\n * and will not be removed in 0.x.\n *\n * The `use` prefix was wrong: this registers a document engine and returns a\n * dispose function — it is not a React hook and calls none. React reserves\n * `use*` for hooks, and the name made `react-hooks/rules-of-hooks` report the\n * module-scope call on import as a hook called outside a component. A linter\n * being misled by a name is the mild version; a reader assuming hook rules\n * apply, or that it must be called during render, is the real cost.\n */\nexport const useFancyCmsForRichInput = registerFancyCmsForRichInput;\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@particle-academy/fancy-flow",
3
- "version": "0.33.3",
3
+ "version": "0.34.0",
4
4
  "description": "Workflow editor + runner. Six built-in node kits (trigger / action / decision / output / note / subgraph), tokenized theme, topological execution with per-node status. React-flow bundled; consumers npm install fancy-flow and get nothing extra.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -123,7 +123,7 @@
123
123
  "scripts": {
124
124
  "build": "tsup",
125
125
  "dev": "tsup --watch",
126
- "lint": "tsc --noEmit",
126
+ "lint": "tsc --noEmit && eslint .",
127
127
  "test": "vitest run",
128
128
  "clean": "rm -rf dist",
129
129
  "prepublishOnly": "tsup"
@@ -144,7 +144,7 @@
144
144
  "react-dom": "^18.0.0 || ^19.0.0"
145
145
  },
146
146
  "dependencies": {
147
- "@particle-academy/fancy-auto-common": "^0.1.0"
147
+ "@particle-academy/fancy-auto-common": ">=0.1 <2.0"
148
148
  },
149
149
  "devDependencies": {
150
150
  "@dagrejs/dagre": "^3.0.0",
@@ -157,11 +157,14 @@
157
157
  "@xyflow/react": "^12.4.0",
158
158
  "ai": "^7.0.31",
159
159
  "clsx": "^2.1.0",
160
+ "eslint": "^10.8.0",
161
+ "eslint-plugin-react-hooks": "^7.1.1",
160
162
  "jsdom": "^29.1.1",
161
163
  "react": "^19.0.0",
162
164
  "react-dom": "^19.0.0",
163
165
  "tsup": "^8.0.0",
164
166
  "typescript": "^5.7.0",
167
+ "typescript-eslint": "^8.65.0",
165
168
  "vitest": "^3.2.6",
166
169
  "zod": "^4.4.3"
167
170
  },