@particle-academy/fancy-flow 0.8.0 → 0.10.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.
Files changed (55) hide show
  1. package/README.md +104 -0
  2. package/dist/{chunk-BCXECQUC.js → chunk-6GGFEZH7.js} +3 -3
  3. package/dist/{chunk-BCXECQUC.js.map → chunk-6GGFEZH7.js.map} +1 -1
  4. package/dist/{chunk-WNVBXXOL.js → chunk-CPSOC27D.js} +43 -2
  5. package/dist/chunk-CPSOC27D.js.map +1 -0
  6. package/dist/chunk-F5RPRB7A.js +56 -0
  7. package/dist/chunk-F5RPRB7A.js.map +1 -0
  8. package/dist/{chunk-NVULCEDX.js → chunk-HNBO4HP3.js} +8 -4
  9. package/dist/chunk-HNBO4HP3.js.map +1 -0
  10. package/dist/{chunk-M2XKGQQL.js → chunk-NCPQDVUE.js} +178 -16
  11. package/dist/chunk-NCPQDVUE.js.map +1 -0
  12. package/dist/chunk-TITD5W4Y.js +26 -0
  13. package/dist/chunk-TITD5W4Y.js.map +1 -0
  14. package/dist/{chunk-QSSQRQN4.js → chunk-VEI743ZX.js} +3 -3
  15. package/dist/{chunk-QSSQRQN4.js.map → chunk-VEI743ZX.js.map} +1 -1
  16. package/dist/engine.cjs +32 -2
  17. package/dist/engine.cjs.map +1 -1
  18. package/dist/engine.js +3 -1
  19. package/dist/index.cjs +716 -40
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.d.cts +45 -6
  22. package/dist/index.d.ts +45 -6
  23. package/dist/index.js +417 -23
  24. package/dist/index.js.map +1 -1
  25. package/dist/registry/index.d.cts +112 -4
  26. package/dist/registry/index.d.ts +112 -4
  27. package/dist/registry.cjs +312 -31
  28. package/dist/registry.cjs.map +1 -1
  29. package/dist/registry.js +4 -2
  30. package/dist/rich-input.cjs +56 -0
  31. package/dist/rich-input.cjs.map +1 -0
  32. package/dist/rich-input.d.cts +27 -0
  33. package/dist/rich-input.d.ts +27 -0
  34. package/dist/rich-input.js +37 -0
  35. package/dist/rich-input.js.map +1 -0
  36. package/dist/runtime.cjs +32 -2
  37. package/dist/runtime.cjs.map +1 -1
  38. package/dist/runtime.js +4 -2
  39. package/dist/schema.cjs +41 -0
  40. package/dist/schema.cjs.map +1 -1
  41. package/dist/schema.js +2 -2
  42. package/dist/styles.css +191 -0
  43. package/dist/styles.css.map +1 -1
  44. package/dist/types-BocBFh6l.d.ts +221 -0
  45. package/dist/types-DKqaUjF_.d.cts +221 -0
  46. package/dist/ux.cjs.map +1 -1
  47. package/dist/ux.d.cts +1 -1
  48. package/dist/ux.d.ts +1 -1
  49. package/dist/ux.js +1 -1
  50. package/package.json +24 -2
  51. package/dist/chunk-M2XKGQQL.js.map +0 -1
  52. package/dist/chunk-NVULCEDX.js.map +0 -1
  53. package/dist/chunk-WNVBXXOL.js.map +0 -1
  54. package/dist/types-C0wdN6QX.d.cts +0 -121
  55. package/dist/types-DnMe9Vsf.d.ts +0 -121
package/dist/registry.js CHANGED
@@ -1,5 +1,7 @@
1
- export { BUILTIN_KINDS, RegistryNode, buildNodeTypes, registerBuiltinKinds } from './chunk-M2XKGQQL.js';
1
+ export { BUILTIN_KINDS, RegistryNode, buildNodeTypes, registerBuiltinKinds } from './chunk-NCPQDVUE.js';
2
2
  import './chunk-NF6NPY5N.js';
3
- export { categoryAccent, defaultConfigFor, getNodeKind, listNodeKinds, onNodeKindsChanged, registerNodeKind, validateConfig } from './chunk-WNVBXXOL.js';
3
+ export { nodeConfig, resolveNodePorts, resolvePortSpec } from './chunk-TITD5W4Y.js';
4
+ export { categoryAccent, defaultConfigFor, getNodeKind, listNodeKinds, onNodeKindsChanged, registerNodeKind, validateConfig } from './chunk-CPSOC27D.js';
5
+ export { RichInputPreview, getRichInputAdapter, isRichInputEnabled, onRichInputAdapterChanged, registerRichInputAdapter } from './chunk-F5RPRB7A.js';
4
6
  //# sourceMappingURL=registry.js.map
5
7
  //# sourceMappingURL=registry.js.map
@@ -0,0 +1,56 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var fancyCmsUi = require('@particle-academy/fancy-cms-ui');
5
+ var reactFancy = require('@particle-academy/react-fancy');
6
+ require('react/jsx-runtime');
7
+
8
+ // src/rich-input.tsx
9
+ var adapter = null;
10
+ var listeners = /* @__PURE__ */ new Set();
11
+ function registerRichInputAdapter(next) {
12
+ adapter = next;
13
+ for (const l of listeners) l();
14
+ return () => {
15
+ if (adapter === next) {
16
+ adapter = null;
17
+ for (const l of listeners) l();
18
+ }
19
+ };
20
+ }
21
+
22
+ // src/rich-input.tsx
23
+ function emptyRichInputDoc(id = "rich-input") {
24
+ return fancyCmsUi.emptyDoc(id);
25
+ }
26
+ function isPageDoc(value) {
27
+ if (value === null || typeof value !== "object") return false;
28
+ const v = value;
29
+ return Array.isArray(v.sections) && typeof v.nodes === "object" && v.nodes !== null;
30
+ }
31
+ function useFancyCmsForRichInput(options = {}) {
32
+ return registerRichInputAdapter({
33
+ FauxClient: reactFancy.FauxClient,
34
+ // Render the page at a real desktop width and scale it down, rather than
35
+ // letting it reflow into a card-sized viewport — a preview that reflows
36
+ // shows a layout the person hitting the step will never see.
37
+ frameProps: { variant: "browser", width: 1280, scale: "fit", dots: true },
38
+ // Preview: the CMS's own renderer, so the node card shows exactly what the
39
+ // person hitting this step will see. Styles are scoped by the frame.
40
+ renderDocument: (doc) => isPageDoc(doc) ? react.createElement(fancyCmsUi.CmsPage, { doc, registry: options.registry, data: options.data }) : null,
41
+ // Authoring: the CMS's own WYSIWYG editor, in the config panel.
42
+ renderEditor: ({ value, onChange }) => react.createElement(fancyCmsUi.Editor, {
43
+ defaultValue: isPageDoc(value) ? value : emptyRichInputDoc(),
44
+ onChange,
45
+ registry: options.registry,
46
+ data: options.data
47
+ })
48
+ });
49
+ }
50
+ useFancyCmsForRichInput();
51
+
52
+ exports.emptyRichInputDoc = emptyRichInputDoc;
53
+ exports.isPageDoc = isPageDoc;
54
+ exports.useFancyCmsForRichInput = useFancyCmsForRichInput;
55
+ //# sourceMappingURL=rich-input.cjs.map
56
+ //# sourceMappingURL=rich-input.cjs.map
@@ -0,0 +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"]}
@@ -0,0 +1,27 @@
1
+ import { PageDoc, CmsPage } from '@particle-academy/fancy-cms-ui';
2
+ export { PageDoc } from '@particle-academy/fancy-cms-ui';
3
+
4
+ /**
5
+ * A blank page for a step that hasn't been authored yet.
6
+ *
7
+ * `Editor` takes a `defaultValue`, not a nullable doc — so a never-authored
8
+ * step needs a real empty document to open into rather than a blank screen.
9
+ */
10
+ declare function emptyRichInputDoc(id?: string): PageDoc;
11
+ /** True when a stored value is a fancy-cms page document. */
12
+ declare function isPageDoc(value: unknown): value is PageDoc;
13
+ /**
14
+ * Register fancy-cms as the document engine for `rich_user_input`.
15
+ *
16
+ * Called automatically on import. Exported so a host can re-register after
17
+ * swapping in a custom element registry, and so the call is testable.
18
+ */
19
+ declare function useFancyCmsForRichInput(options?: {
20
+ /** Custom element registry — pass the SAME one you give `CmsPage` at runtime,
21
+ * or the edit canvas renders your node types as blank placeholders. */
22
+ registry?: Parameters<typeof CmsPage>[0]["registry"];
23
+ /** Data context that `{ $bind }` props preview against. */
24
+ data?: Parameters<typeof CmsPage>[0]["data"];
25
+ }): () => void;
26
+
27
+ export { emptyRichInputDoc, isPageDoc, useFancyCmsForRichInput };
@@ -0,0 +1,27 @@
1
+ import { PageDoc, CmsPage } from '@particle-academy/fancy-cms-ui';
2
+ export { PageDoc } from '@particle-academy/fancy-cms-ui';
3
+
4
+ /**
5
+ * A blank page for a step that hasn't been authored yet.
6
+ *
7
+ * `Editor` takes a `defaultValue`, not a nullable doc — so a never-authored
8
+ * step needs a real empty document to open into rather than a blank screen.
9
+ */
10
+ declare function emptyRichInputDoc(id?: string): PageDoc;
11
+ /** True when a stored value is a fancy-cms page document. */
12
+ declare function isPageDoc(value: unknown): value is PageDoc;
13
+ /**
14
+ * Register fancy-cms as the document engine for `rich_user_input`.
15
+ *
16
+ * Called automatically on import. Exported so a host can re-register after
17
+ * swapping in a custom element registry, and so the call is testable.
18
+ */
19
+ declare function useFancyCmsForRichInput(options?: {
20
+ /** Custom element registry — pass the SAME one you give `CmsPage` at runtime,
21
+ * or the edit canvas renders your node types as blank placeholders. */
22
+ registry?: Parameters<typeof CmsPage>[0]["registry"];
23
+ /** Data context that `{ $bind }` props preview against. */
24
+ data?: Parameters<typeof CmsPage>[0]["data"];
25
+ }): () => void;
26
+
27
+ export { emptyRichInputDoc, isPageDoc, useFancyCmsForRichInput };
@@ -0,0 +1,37 @@
1
+ import { registerRichInputAdapter } from './chunk-F5RPRB7A.js';
2
+ import { createElement } from 'react';
3
+ import { Editor, CmsPage, emptyDoc } from '@particle-academy/fancy-cms-ui';
4
+ import { FauxClient } from '@particle-academy/react-fancy';
5
+
6
+ function emptyRichInputDoc(id = "rich-input") {
7
+ return emptyDoc(id);
8
+ }
9
+ function isPageDoc(value) {
10
+ if (value === null || typeof value !== "object") return false;
11
+ const v = value;
12
+ return Array.isArray(v.sections) && typeof v.nodes === "object" && v.nodes !== null;
13
+ }
14
+ function useFancyCmsForRichInput(options = {}) {
15
+ return registerRichInputAdapter({
16
+ FauxClient,
17
+ // Render the page at a real desktop width and scale it down, rather than
18
+ // letting it reflow into a card-sized viewport — a preview that reflows
19
+ // shows a layout the person hitting the step will never see.
20
+ frameProps: { variant: "browser", width: 1280, scale: "fit", dots: true },
21
+ // Preview: the CMS's own renderer, so the node card shows exactly what the
22
+ // person hitting this step will see. Styles are scoped by the frame.
23
+ renderDocument: (doc) => isPageDoc(doc) ? createElement(CmsPage, { doc, registry: options.registry, data: options.data }) : null,
24
+ // Authoring: the CMS's own WYSIWYG editor, in the config panel.
25
+ renderEditor: ({ value, onChange }) => createElement(Editor, {
26
+ defaultValue: isPageDoc(value) ? value : emptyRichInputDoc(),
27
+ onChange,
28
+ registry: options.registry,
29
+ data: options.data
30
+ })
31
+ });
32
+ }
33
+ useFancyCmsForRichInput();
34
+
35
+ export { emptyRichInputDoc, isPageDoc, useFancyCmsForRichInput };
36
+ //# sourceMappingURL=rich-input.js.map
37
+ //# sourceMappingURL=rich-input.js.map
@@ -0,0 +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"]}
package/dist/runtime.cjs CHANGED
@@ -8,6 +8,35 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
8
8
 
9
9
  var ReactExports__default = /*#__PURE__*/_interopDefault(ReactExports);
10
10
 
11
+ // src/registry/registry.ts
12
+ var kinds = /* @__PURE__ */ new Map();
13
+ function getNodeKind(name) {
14
+ return kinds.get(name) ?? null;
15
+ }
16
+
17
+ // src/registry/ports.ts
18
+ function resolvePortSpec(spec, config) {
19
+ if (spec === void 0) return void 0;
20
+ if (typeof spec !== "function") return spec;
21
+ try {
22
+ const resolved = spec(config);
23
+ return Array.isArray(resolved) ? resolved : void 0;
24
+ } catch {
25
+ return void 0;
26
+ }
27
+ }
28
+ function nodeConfig(node) {
29
+ return node.data?.config ?? {};
30
+ }
31
+ function resolveNodePorts(node, kind) {
32
+ const config = nodeConfig(node);
33
+ const data = node.data;
34
+ return {
35
+ inputs: data?.inputs ?? resolvePortSpec(kind?.inputs, config),
36
+ outputs: data?.outputs ?? resolvePortSpec(kind?.outputs, config)
37
+ };
38
+ }
39
+
11
40
  // src/runtime/run-flow.ts
12
41
  async function runFlow(graph, executors, onEvent = () => {
13
42
  }, options = {}) {
@@ -139,8 +168,9 @@ function activatedPorts(node, result) {
139
168
  return { ports: [r.branch], value: r.value ?? r };
140
169
  }
141
170
  }
142
- const declared = node.data.outputs?.map((p) => p.id) ?? ["out"];
143
- return { ports: declared, value: result };
171
+ const kind = getNodeKind(node.data?.kind ?? node.type ?? "") ?? void 0;
172
+ const declared = resolveNodePorts(node, kind).outputs?.map((p) => p.id);
173
+ return { ports: declared?.length ? declared : ["out"], value: result };
144
174
  }
145
175
  function useFlowRun({ maxFeed = 200 } = {}) {
146
176
  const [statuses, setStatuses] = ReactExports.useState({});