@scaleflex/template-builder 0.4.2 → 0.5.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.
@@ -25,7 +25,7 @@ metadata:
25
25
  - User wants a banner / creative editor backed by Scaleflex rendering
26
26
 
27
27
  A complete working React host (gallery, save round trip, live render
28
- preview) is published at <https://stackblitz.com/edit/sotacrwx> — read its
28
+ preview) is published at <https://stackblitz.com/edit/fzfz7f2j> — read its
29
29
  `App.tsx` for the whole contract in one file.
30
30
 
31
31
  ## Step 1 — Decide who stores the template
@@ -65,7 +65,7 @@ npm i @scaleflex/template-builder
65
65
  Or via CDN (self-registering, Lit bundled in — pin the major):
66
66
 
67
67
  ```html
68
- <script type="module" src="https://cdn.cloudimage.io/template-builder/0.4.1/template-builder.min.js"></script>
68
+ <script type="module" src="https://cdn.cloudimage.io/template-builder/0.5.0/template-builder.min.js"></script>
69
69
  ```
70
70
 
71
71
  **SSR caution:** the element extends `HTMLElement`, so importing `.`,
@@ -367,8 +367,8 @@ template document.
367
367
  | `dam-store` / `damStore` | no | Stateless: store each save in Filerobot too; the `save` detail gains `stored: { uuid, url }` or `storeError` (Step 6) |
368
368
  | `store-folder` / `storeFolder` | no | `dam-store`: folder for templates with no existing DAM file (default `/`) |
369
369
  | `stored-uuid` / `storedUuid` | no | `dam-store`: the persisted `stored.uuid`, passed back so re-saves version the existing copy across reloads (Step 6) |
370
- | `custom-metadata` / `customMetadata` | no | Metadata model offered as the **Custom metadata** value source: `[{ key, title?, group? }]` — names only, values go into the render query as `$slug=value` |
371
- | `custom-metadata-label` / `customMetadataLabel` | no | Renames that value source in the editor's UI (wording only) |
370
+ | `custom-metadata` / `customMetadata` | no | Metadata model offered as the **Custom metadata** source type: `[{ key, title?, group? }]` — names only, values go into the render query as `$slug=value` |
371
+ | `custom-metadata-label` / `customMetadataLabel` | no | Renames that source type in the editor's UI (wording only) |
372
372
  | `brand-color`, `theme` | no | See Step 10 |
373
373
  | `ready-timeout` / `readyTimeout` | no | Ms before `handshake-timeout` (default 20000) |
374
374
 
package/CHANGELOG.md CHANGED
@@ -8,7 +8,27 @@ Protocol message *values* are wire format: an existing string is never changed,
8
8
  only new messages added, so an older widget keeps working against a newer app
9
9
  deployment and vice versa.
10
10
 
11
- ## [Unreleased]
11
+ ## [0.5.0] - 2026-08-31
12
+
13
+ ### Changed
14
+
15
+ - Documentation follows the editor's renamed source-type labels: the value
16
+ source a text variable draws on is now **Source type**, and its options are
17
+ **Manual** (was "Free text") and **Asset metadata** (was "File metadata").
18
+ **Custom metadata** and `custom-metadata-label` are unchanged — the label
19
+ stays host-controlled, and the worked example still renames it to
20
+ `External metadata`. Docs only; no library or protocol changes, and nothing
21
+ in the saved template or the render query moves.
22
+
23
+ ## [0.4.2] - 2026-08-26
24
+
25
+ ### Changed
26
+
27
+ - The bundled `integrate-template-builder` skill now documents the 0.4.x
28
+ API: `dam-store` as a first-class integration step (detail-based
29
+ branching, `stored-uuid` seeding, saves trailing `close`), the full
30
+ attribute/event/method references, and a link to the published StackBlitz
31
+ host example. Docs only — no library changes.
12
32
 
13
33
  ## [0.4.1] - 2026-08-26
14
34
 
package/README.md CHANGED
@@ -115,7 +115,7 @@ npm i @scaleflex/template-builder
115
115
  ### CDN
116
116
 
117
117
  ```html
118
- <script type="module" src="https://cdn.cloudimage.io/template-builder/0.4.1/template-builder.min.js"></script>
118
+ <script type="module" src="https://cdn.cloudimage.io/template-builder/0.5.0/template-builder.min.js"></script>
119
119
  ```
120
120
 
121
121
  The CDN bundle is self-registering — it defines `<sfx-template-builder>` on
@@ -662,8 +662,8 @@ without it a failed write is invisible to the user.
662
662
  | `new-template` / `newTemplate` | no | Stateless: open on a new, empty template instead of supplying `content` — the widget provides the blank document. Ignored when `content` is set. See [Starting a template from scratch](#starting-a-template-from-scratch) |
663
663
  | `template-name` / `templateName` | no | Stateless: header title |
664
664
  | `template-query` / `templateQuery` | no | Stateless: the render to open on — the `templateQuery` from the last save. Empty uses the XML's `default=` values. |
665
- | `custom-metadata` / `customMetadata` | no | Metadata model offered as the **Custom metadata** value source: `[{ key, title?, group? }]`, as an array (property) or JSON (attribute). See [Custom metadata fields](#custom-metadata-fields) |
666
- | `custom-metadata-label` / `customMetadataLabel` | no | Renames the **Custom metadata** value source in the editor's UI (e.g. `External metadata`). Wording only — the stored template is unaffected. Empty uses the default |
665
+ | `custom-metadata` / `customMetadata` | no | Metadata model offered as the **Custom metadata** source type: `[{ key, title?, group? }]`, as an array (property) or JSON (attribute). See [Custom metadata fields](#custom-metadata-fields) |
666
+ | `custom-metadata-label` / `customMetadataLabel` | no | Renames the **Custom metadata** source type in the editor's UI (e.g. `External metadata`). Wording only — the stored template is unaffected. Empty uses the default |
667
667
  | `dam-store` / `damStore` | no | Stateless only: the element stores each save in Filerobot too, and `save`'s detail carries `stored: { uuid, url }` (or `storeError`) next to the raw `content`. See [Storing a rendering copy](#storing-a-rendering-copy-dam-store) |
668
668
  | `store-folder` / `storeFolder` | no | `dam-store`: folder for templates whose id names no existing DAM file (default `/`); an existing file's own folder always wins |
669
669
  | `stored-uuid` / `storedUuid` | no | `dam-store`: the `stored.uuid` you persisted for this document, passed back in so re-saves after a reload resolve to (and version) the existing copy. Per-document — `load()` clears it when omitted |
@@ -754,10 +754,10 @@ before you pick it:
754
754
 
755
755
  ### Custom metadata fields
756
756
 
757
- A text variable normally takes its value from the render query (**Free text**),
758
- or from the source asset's DAM metadata (**File metadata**, which needs a
757
+ A text variable normally takes its value from the render query (**Manual**),
758
+ or from the source asset's DAM metadata (**Asset metadata**, which needs a
759
759
  Hub session — it is unavailable under `sec-template`). `custom-metadata` adds a
760
- third source: your own field names, so an author binds a variable to `sku`
760
+ third source type: your own field names, so an author binds a variable to `sku`
761
761
  instead of having to remember which slug happens to mean the SKU.
762
762
 
763
763
  ```html
@@ -783,7 +783,7 @@ Fields appear in the editor's picker in the order you declare them, grouped
783
783
  under `group` where present, with `title` (or the bare `key`) as the label. Set
784
784
  no model and the source is not offered at all.
785
785
 
786
- The source is called **Custom metadata** in the editor by default;
786
+ The source type is called **Custom metadata** in the editor by default;
787
787
  `custom-metadata-label` renames it to fit your domain — `External metadata`,
788
788
  `Product attributes`, whatever your authors know it as. Wording only: the saved
789
789
  template carries the same `custom_ckey` either way. (The label is another
@@ -189,7 +189,7 @@ export declare const BLANK_TEMPLATE_XML: string;
189
189
  export declare const HOST_CONFIG = "design-templates:host:config";
190
190
  /**
191
191
  * One field of a host-supplied metadata model, offered in the editor as the
192
- * "Custom metadata" value source.
192
+ * "Custom metadata" source type.
193
193
  *
194
194
  * The model is a vocabulary, not data: it names the fields the host can fill at
195
195
  * render time, so an author can bind a variable to `sku` rather than having to
@@ -198,7 +198,7 @@ export declare const HOST_CONFIG = "design-templates:host:config";
198
198
  * query, exactly as it would for a free-text variable.
199
199
  *
200
200
  * This is what makes named fields workable in `secTemplate` / stateless embeds,
201
- * where the Hub project model (and with it the "File metadata" source) is
201
+ * where the Hub project model (and with it the "Asset metadata" source) is
202
202
  * unavailable.
203
203
  */
204
204
  export interface CustomMetadataField {
@@ -214,13 +214,13 @@ export interface CustomMetadataField {
214
214
  }
215
215
  export interface HostConfigData {
216
216
  /**
217
- * Metadata model offered as the "Custom metadata" value source. Omitted or
217
+ * Metadata model offered as the "Custom metadata" source type. Omitted or
218
218
  * empty hides that source in the editor, so a host that sends nothing sees
219
219
  * the two sources it always had.
220
220
  */
221
221
  customMetadata?: CustomMetadataField[];
222
222
  /**
223
- * Display name for the custom-metadata value source in the editor's UI
223
+ * Display name for the custom-metadata source type in the editor's UI
224
224
  * (source dropdowns, properties-panel section). Defaults to "Custom
225
225
  * metadata"; a host can rename it after its own domain — e.g. "External
226
226
  * metadata" or "Product attributes". Pure wording: the stored template is
@@ -1 +1 @@
1
- {"version":3,"file":"react.cjs","sources":["../src/react.ts"],"sourcesContent":["import {\n createElement,\n forwardRef,\n useImperativeHandle,\n useLayoutEffect,\n useRef,\n type CSSProperties,\n type ReactElement,\n} from 'react'\nimport './define'\nimport type { SfxTemplateBuilder } from './template-builder'\nimport type { TemplateBuilderSaveDetail } from './template-builder'\nimport type {\n BuilderDirtyData,\n BuilderErrorData,\n BuilderTheme,\n CustomMetadataField,\n} from './protocol'\n\n/**\n * Hub session — the full-featured credential.\n */\nexport interface TemplateBuilderSessionAuth {\n sassKey: string\n sessionUuid: string\n companyUuid?: string\n projectUuid?: string\n secTemplate?: never\n}\n\n/**\n * Filerobot security template — a guest credential for hosts that have no Hub\n * session to hand over. `stateless` is required rather than merely implied:\n * the app takes a security template on the stateless embed route only, so the\n * combination is a compile-time error instead of a runtime one.\n *\n * `companyUuid` / `projectUuid` are absent by design — they name a Hub project\n * that cannot be looked up without a session.\n */\nexport interface TemplateBuilderSecTemplateAuth {\n secTemplate: string\n stateless: true\n sassKey?: never\n sessionUuid?: never\n companyUuid?: never\n projectUuid?: never\n}\n\nexport interface TemplateBuilderBaseProps {\n baseUrl: string\n token: string\n templateId?: string\n mode?: 'inline' | 'modal'\n /** Hand the template in and take it back out instead of using the DAM. */\n stateless?: boolean\n /** Stateless mode: the template to edit, as `.fdt` XML. */\n content?: string\n /**\n * Stateless mode: open on a new, empty template instead of supplying\n * `content`. The widget provides the blank document, the user picks the\n * canvas size in the editor, and `onSave` receives a complete `.fdt` to\n * store. Ignored when `content` is set.\n */\n newTemplate?: boolean\n /** Stateless mode: display name for the editor header. */\n templateName?: string\n /**\n * Stateless mode: the `template_query` to open on — the value handed back on\n * save. Reopens the template on the same layout and variable values; empty\n * falls back to the XML's own `default=` attributes.\n */\n templateQuery?: string\n /** Accent colour for the editor chrome, as `#rgb` / `#rrggbb`. */\n brandColor?: string\n /** Colour scheme for the editor chrome. */\n theme?: BuilderTheme\n /**\n * Metadata model offered in the editor as the \"Custom metadata\" value source\n * — names only, no values. Omit it and the source is not offered.\n *\n * Compared by identity, like every other prop here, so a freshly built array\n * counts as a change. Nothing is re-sent to the editor over it — the element\n * de-dupes by value — but hoisting or memoising the array avoids the churn.\n */\n customMetadata?: CustomMetadataField[]\n /**\n * Display name for the custom-metadata value source in the editor's UI —\n * e.g. \"External metadata\". Wording only: the stored template is unaffected.\n * Empty uses the editor's default, \"Custom metadata\".\n */\n customMetadataLabel?: string\n /**\n * Stateless only: store each save in Filerobot too, so the CDN can render\n * it. `onSave`'s detail then carries `stored: { uuid, url }` next to the raw\n * `content` — or `storeError` when the copy failed.\n */\n damStore?: boolean\n /**\n * Folder new templates land in under `damStore` when the template id names\n * no existing DAM file (an existing file's own folder always wins).\n */\n storeFolder?: string\n /**\n * `damStore`: the `stored.uuid` a previous session's save reported for THIS\n * document, so re-saves after a reload resolve to (and version) the copy\n * that already exists instead of erroring on unchanged content or starting\n * a fresh file. Per-document — pass it with the content it belongs to.\n */\n storedUuid?: string\n readyTimeout?: number\n className?: string\n style?: CSSProperties\n onReady?: () => void\n onOpen?: () => void\n onClose?: () => void\n /**\n * Fired on save. In stateless mode the detail carries the edited `content`\n * for you to persist; otherwise it reports the uuid the app uploaded to.\n *\n * In stateless mode the outcome is reported back to the editor: return (or\n * resolve to) `false`, or throw, and the editor restores its unsaved-changes\n * flag and tells the user the save failed. Anything else counts as persisted.\n */\n onSave?: (\n data: TemplateBuilderSaveDetail,\n ) => void | boolean | Promise<void | boolean>\n onError?: (data: BuilderErrorData) => void\n /**\n * Stateless mode: unsaved-changes flag changed. Use it to prompt before\n * swapping `content`, which discards in-progress edits.\n */\n onDirtyChange?: (data: BuilderDirtyData) => void\n}\n\nexport type TemplateBuilderProps = TemplateBuilderBaseProps &\n (TemplateBuilderSessionAuth | TemplateBuilderSecTemplateAuth)\n\n/**\n * React wrapper around `<sfx-template-builder>`. Props are assigned as\n * element properties via ref (works on React 18 and 19 alike); callbacks\n * subscribe to the element's CustomEvents.\n *\n * Forwards a ref to the underlying element, which is the only way to reach the\n * imperative API — `open()` in particular, without which `mode=\"modal\"` can\n * never be shown:\n *\n * ```tsx\n * const builder = useRef<SfxTemplateBuilder>(null)\n * <TemplateBuilder ref={builder} mode=\"modal\" … />\n * <button onClick={() => builder.current?.open('tpl-1')}>Edit</button>\n * ```\n *\n * `forwardRef` rather than a plain `ref` prop: React 19 accepts the latter for\n * function components, React 18 does not, and both are supported peers.\n */\nexport const TemplateBuilder = forwardRef<\n SfxTemplateBuilder,\n TemplateBuilderProps\n>(function TemplateBuilder(props, forwardedRef): ReactElement {\n const {\n className,\n style,\n onReady,\n onOpen,\n onClose,\n onSave,\n onError,\n onDirtyChange,\n ...config\n } = props\n const ref = useRef<SfxTemplateBuilder>(null)\n\n // Hand the same element out to the caller without giving up the internal ref\n // the effects below rely on.\n useImperativeHandle(forwardedRef, () => ref.current as SfxTemplateBuilder, [])\n\n // Assign config before paint so the iframe doesn't first mount with defaults.\n useLayoutEffect(() => {\n const el = ref.current\n if (!el) return\n el.baseUrl = config.baseUrl\n el.token = config.token\n el.sassKey = config.sassKey ?? ''\n el.sessionUuid = config.sessionUuid ?? ''\n el.secTemplate = config.secTemplate ?? ''\n el.companyUuid = config.companyUuid ?? ''\n el.projectUuid = config.projectUuid ?? ''\n el.templateId = config.templateId ?? ''\n el.mode = config.mode ?? 'inline'\n el.stateless = config.stateless ?? false\n el.templateName = config.templateName ?? ''\n el.templateQuery = config.templateQuery ?? ''\n el.brandColor = config.brandColor ?? ''\n el.theme = config.theme ?? ''\n el.newTemplate = config.newTemplate ?? false\n el.customMetadata = config.customMetadata ?? []\n el.customMetadataLabel = config.customMetadataLabel ?? ''\n el.damStore = config.damStore ?? false\n el.storeFolder = config.storeFolder ?? '/'\n el.storedUuid = config.storedUuid ?? ''\n // Assigned last: the element sends content to the app as soon as it has\n // both a request and a value, so the id, name and query must already be\n // set — all four ship as one message.\n el.content = config.content ?? ''\n if (config.readyTimeout !== undefined) el.readyTimeout = config.readyTimeout\n }, [\n config.baseUrl,\n config.token,\n config.sassKey,\n config.sessionUuid,\n config.secTemplate,\n config.companyUuid,\n config.projectUuid,\n config.templateId,\n config.mode,\n config.stateless,\n config.content,\n config.newTemplate,\n config.templateName,\n config.templateQuery,\n config.brandColor,\n config.theme,\n config.customMetadata,\n config.customMetadataLabel,\n config.damStore,\n config.storeFolder,\n config.storedUuid,\n config.readyTimeout,\n ])\n\n // The callbacks the listeners read at event time. A ref rather than effect\n // dependencies: listeners are attached once per element (below), so a parent\n // re-render swapping handler identities costs nothing — and, decisively, the\n // listeners are still attached during the element's disconnect-time flush of\n // pending dam-store saves, which a resubscribe-per-change cleanup would have\n // already torn down.\n const handlers = useRef({\n onReady,\n onOpen,\n onClose,\n onSave,\n onError,\n onDirtyChange,\n })\n useLayoutEffect(() => {\n handlers.current = { onReady, onOpen, onClose, onSave, onError, onDirtyChange }\n })\n\n // Layout effect, not passive: the element reports config errors (e.g.\n // `invalid-base-url`) in a microtask queued during this same commit, and a\n // passive effect would subscribe only after that microtask has fired —\n // making a mount-time error unobservable from React. This effect is declared\n // after the config one, so it still runs once the config is assigned.\n useLayoutEffect(() => {\n const el = ref.current\n if (!el) return\n const subs: Array<[string, EventListener]> = []\n const on = (\n name: string,\n pick: (h: typeof handlers.current) => ((detail: never) => void) | undefined,\n ) => {\n const listener = ((e: CustomEvent) =>\n pick(handlers.current)?.(e.detail as never)) as EventListener\n el.addEventListener(name, listener)\n subs.push([name, listener])\n }\n on('ready', (h) => h.onReady)\n on('open', (h) => h.onOpen)\n on('close', (h) => h.onClose)\n on('error', (h) => h.onError)\n on('dirtychange', (h) => h.onDirtyChange)\n\n // `save` is not just re-emitted: in stateless mode the handler's outcome\n // is acked back, so a failed write on the host side doesn't leave the\n // editor showing the template as saved. `confirmSave` no-ops in DAM mode.\n const saveListener = ((e: CustomEvent<TemplateBuilderSaveDetail>) => {\n const onSaveNow = handlers.current.onSave\n if (!onSaveNow) return\n // Wrapped in a promise so a synchronous throw is handled like a\n // rejection, and a sync `false` like a resolved one.\n Promise.resolve()\n .then(() => onSaveNow(e.detail))\n .then((result) => el.confirmSave(result !== false))\n .catch((err) => {\n // No message: an internal error string is not something to put in\n // front of the end user. The editor uses its own wording.\n console.error('[sfx-template-builder] onSave failed:', err)\n el.confirmSave(false)\n })\n }) as EventListener\n el.addEventListener('save', saveListener)\n subs.push(['save', saveListener])\n\n return () => {\n // React runs this cleanup BEFORE it detaches the node, so the element's\n // own disconnect-time flush of in-flight dam-store saves would fire\n // after every listener is gone — and the raw save would be silently\n // lost. Flushing here, while the listeners are still attached, hands\n // those saves (with `storeError` in place of the links) to `onSave`\n // first. A no-op when nothing is pending, including StrictMode's\n // simulated unmount at mount time.\n //\n // Guarded: when an older CDN bundle registered the tag first, `el` is\n // that bundle's class and lacks the method — every other new-API use\n // degrades silently via property assignment, and unmount must not be\n // the one path that throws.\n if (typeof el.flushPendingSaves === 'function') {\n el.flushPendingSaves('widget removed before the rendering copy completed')\n }\n for (const [name, listener] of subs) el.removeEventListener(name, listener)\n }\n }, [])\n\n // eslint-disable-next-line react-hooks/refs -- ref is forwarded as a prop, not read during render\n return createElement('sfx-template-builder', { ref, class: className, style })\n})\n"],"names":["TemplateBuilder","forwardRef","props","forwardedRef","className","style","onReady","onOpen","onClose","onSave","onError","onDirtyChange","config","ref","useRef","useImperativeHandle","useLayoutEffect","el","handlers","subs","on","name","pick","listener","e","h","saveListener","onSaveNow","result","err","createElement"],"mappings":"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"}
1
+ {"version":3,"file":"react.cjs","sources":["../src/react.ts"],"sourcesContent":["import {\n createElement,\n forwardRef,\n useImperativeHandle,\n useLayoutEffect,\n useRef,\n type CSSProperties,\n type ReactElement,\n} from 'react'\nimport './define'\nimport type { SfxTemplateBuilder } from './template-builder'\nimport type { TemplateBuilderSaveDetail } from './template-builder'\nimport type {\n BuilderDirtyData,\n BuilderErrorData,\n BuilderTheme,\n CustomMetadataField,\n} from './protocol'\n\n/**\n * Hub session — the full-featured credential.\n */\nexport interface TemplateBuilderSessionAuth {\n sassKey: string\n sessionUuid: string\n companyUuid?: string\n projectUuid?: string\n secTemplate?: never\n}\n\n/**\n * Filerobot security template — a guest credential for hosts that have no Hub\n * session to hand over. `stateless` is required rather than merely implied:\n * the app takes a security template on the stateless embed route only, so the\n * combination is a compile-time error instead of a runtime one.\n *\n * `companyUuid` / `projectUuid` are absent by design — they name a Hub project\n * that cannot be looked up without a session.\n */\nexport interface TemplateBuilderSecTemplateAuth {\n secTemplate: string\n stateless: true\n sassKey?: never\n sessionUuid?: never\n companyUuid?: never\n projectUuid?: never\n}\n\nexport interface TemplateBuilderBaseProps {\n baseUrl: string\n token: string\n templateId?: string\n mode?: 'inline' | 'modal'\n /** Hand the template in and take it back out instead of using the DAM. */\n stateless?: boolean\n /** Stateless mode: the template to edit, as `.fdt` XML. */\n content?: string\n /**\n * Stateless mode: open on a new, empty template instead of supplying\n * `content`. The widget provides the blank document, the user picks the\n * canvas size in the editor, and `onSave` receives a complete `.fdt` to\n * store. Ignored when `content` is set.\n */\n newTemplate?: boolean\n /** Stateless mode: display name for the editor header. */\n templateName?: string\n /**\n * Stateless mode: the `template_query` to open on — the value handed back on\n * save. Reopens the template on the same layout and variable values; empty\n * falls back to the XML's own `default=` attributes.\n */\n templateQuery?: string\n /** Accent colour for the editor chrome, as `#rgb` / `#rrggbb`. */\n brandColor?: string\n /** Colour scheme for the editor chrome. */\n theme?: BuilderTheme\n /**\n * Metadata model offered in the editor as the \"Custom metadata\" source type\n * — names only, no values. Omit it and the source is not offered.\n *\n * Compared by identity, like every other prop here, so a freshly built array\n * counts as a change. Nothing is re-sent to the editor over it — the element\n * de-dupes by value — but hoisting or memoising the array avoids the churn.\n */\n customMetadata?: CustomMetadataField[]\n /**\n * Display name for the custom-metadata source type in the editor's UI —\n * e.g. \"External metadata\". Wording only: the stored template is unaffected.\n * Empty uses the editor's default, \"Custom metadata\".\n */\n customMetadataLabel?: string\n /**\n * Stateless only: store each save in Filerobot too, so the CDN can render\n * it. `onSave`'s detail then carries `stored: { uuid, url }` next to the raw\n * `content` — or `storeError` when the copy failed.\n */\n damStore?: boolean\n /**\n * Folder new templates land in under `damStore` when the template id names\n * no existing DAM file (an existing file's own folder always wins).\n */\n storeFolder?: string\n /**\n * `damStore`: the `stored.uuid` a previous session's save reported for THIS\n * document, so re-saves after a reload resolve to (and version) the copy\n * that already exists instead of erroring on unchanged content or starting\n * a fresh file. Per-document — pass it with the content it belongs to.\n */\n storedUuid?: string\n readyTimeout?: number\n className?: string\n style?: CSSProperties\n onReady?: () => void\n onOpen?: () => void\n onClose?: () => void\n /**\n * Fired on save. In stateless mode the detail carries the edited `content`\n * for you to persist; otherwise it reports the uuid the app uploaded to.\n *\n * In stateless mode the outcome is reported back to the editor: return (or\n * resolve to) `false`, or throw, and the editor restores its unsaved-changes\n * flag and tells the user the save failed. Anything else counts as persisted.\n */\n onSave?: (\n data: TemplateBuilderSaveDetail,\n ) => void | boolean | Promise<void | boolean>\n onError?: (data: BuilderErrorData) => void\n /**\n * Stateless mode: unsaved-changes flag changed. Use it to prompt before\n * swapping `content`, which discards in-progress edits.\n */\n onDirtyChange?: (data: BuilderDirtyData) => void\n}\n\nexport type TemplateBuilderProps = TemplateBuilderBaseProps &\n (TemplateBuilderSessionAuth | TemplateBuilderSecTemplateAuth)\n\n/**\n * React wrapper around `<sfx-template-builder>`. Props are assigned as\n * element properties via ref (works on React 18 and 19 alike); callbacks\n * subscribe to the element's CustomEvents.\n *\n * Forwards a ref to the underlying element, which is the only way to reach the\n * imperative API — `open()` in particular, without which `mode=\"modal\"` can\n * never be shown:\n *\n * ```tsx\n * const builder = useRef<SfxTemplateBuilder>(null)\n * <TemplateBuilder ref={builder} mode=\"modal\" … />\n * <button onClick={() => builder.current?.open('tpl-1')}>Edit</button>\n * ```\n *\n * `forwardRef` rather than a plain `ref` prop: React 19 accepts the latter for\n * function components, React 18 does not, and both are supported peers.\n */\nexport const TemplateBuilder = forwardRef<\n SfxTemplateBuilder,\n TemplateBuilderProps\n>(function TemplateBuilder(props, forwardedRef): ReactElement {\n const {\n className,\n style,\n onReady,\n onOpen,\n onClose,\n onSave,\n onError,\n onDirtyChange,\n ...config\n } = props\n const ref = useRef<SfxTemplateBuilder>(null)\n\n // Hand the same element out to the caller without giving up the internal ref\n // the effects below rely on.\n useImperativeHandle(forwardedRef, () => ref.current as SfxTemplateBuilder, [])\n\n // Assign config before paint so the iframe doesn't first mount with defaults.\n useLayoutEffect(() => {\n const el = ref.current\n if (!el) return\n el.baseUrl = config.baseUrl\n el.token = config.token\n el.sassKey = config.sassKey ?? ''\n el.sessionUuid = config.sessionUuid ?? ''\n el.secTemplate = config.secTemplate ?? ''\n el.companyUuid = config.companyUuid ?? ''\n el.projectUuid = config.projectUuid ?? ''\n el.templateId = config.templateId ?? ''\n el.mode = config.mode ?? 'inline'\n el.stateless = config.stateless ?? false\n el.templateName = config.templateName ?? ''\n el.templateQuery = config.templateQuery ?? ''\n el.brandColor = config.brandColor ?? ''\n el.theme = config.theme ?? ''\n el.newTemplate = config.newTemplate ?? false\n el.customMetadata = config.customMetadata ?? []\n el.customMetadataLabel = config.customMetadataLabel ?? ''\n el.damStore = config.damStore ?? false\n el.storeFolder = config.storeFolder ?? '/'\n el.storedUuid = config.storedUuid ?? ''\n // Assigned last: the element sends content to the app as soon as it has\n // both a request and a value, so the id, name and query must already be\n // set — all four ship as one message.\n el.content = config.content ?? ''\n if (config.readyTimeout !== undefined) el.readyTimeout = config.readyTimeout\n }, [\n config.baseUrl,\n config.token,\n config.sassKey,\n config.sessionUuid,\n config.secTemplate,\n config.companyUuid,\n config.projectUuid,\n config.templateId,\n config.mode,\n config.stateless,\n config.content,\n config.newTemplate,\n config.templateName,\n config.templateQuery,\n config.brandColor,\n config.theme,\n config.customMetadata,\n config.customMetadataLabel,\n config.damStore,\n config.storeFolder,\n config.storedUuid,\n config.readyTimeout,\n ])\n\n // The callbacks the listeners read at event time. A ref rather than effect\n // dependencies: listeners are attached once per element (below), so a parent\n // re-render swapping handler identities costs nothing — and, decisively, the\n // listeners are still attached during the element's disconnect-time flush of\n // pending dam-store saves, which a resubscribe-per-change cleanup would have\n // already torn down.\n const handlers = useRef({\n onReady,\n onOpen,\n onClose,\n onSave,\n onError,\n onDirtyChange,\n })\n useLayoutEffect(() => {\n handlers.current = { onReady, onOpen, onClose, onSave, onError, onDirtyChange }\n })\n\n // Layout effect, not passive: the element reports config errors (e.g.\n // `invalid-base-url`) in a microtask queued during this same commit, and a\n // passive effect would subscribe only after that microtask has fired —\n // making a mount-time error unobservable from React. This effect is declared\n // after the config one, so it still runs once the config is assigned.\n useLayoutEffect(() => {\n const el = ref.current\n if (!el) return\n const subs: Array<[string, EventListener]> = []\n const on = (\n name: string,\n pick: (h: typeof handlers.current) => ((detail: never) => void) | undefined,\n ) => {\n const listener = ((e: CustomEvent) =>\n pick(handlers.current)?.(e.detail as never)) as EventListener\n el.addEventListener(name, listener)\n subs.push([name, listener])\n }\n on('ready', (h) => h.onReady)\n on('open', (h) => h.onOpen)\n on('close', (h) => h.onClose)\n on('error', (h) => h.onError)\n on('dirtychange', (h) => h.onDirtyChange)\n\n // `save` is not just re-emitted: in stateless mode the handler's outcome\n // is acked back, so a failed write on the host side doesn't leave the\n // editor showing the template as saved. `confirmSave` no-ops in DAM mode.\n const saveListener = ((e: CustomEvent<TemplateBuilderSaveDetail>) => {\n const onSaveNow = handlers.current.onSave\n if (!onSaveNow) return\n // Wrapped in a promise so a synchronous throw is handled like a\n // rejection, and a sync `false` like a resolved one.\n Promise.resolve()\n .then(() => onSaveNow(e.detail))\n .then((result) => el.confirmSave(result !== false))\n .catch((err) => {\n // No message: an internal error string is not something to put in\n // front of the end user. The editor uses its own wording.\n console.error('[sfx-template-builder] onSave failed:', err)\n el.confirmSave(false)\n })\n }) as EventListener\n el.addEventListener('save', saveListener)\n subs.push(['save', saveListener])\n\n return () => {\n // React runs this cleanup BEFORE it detaches the node, so the element's\n // own disconnect-time flush of in-flight dam-store saves would fire\n // after every listener is gone — and the raw save would be silently\n // lost. Flushing here, while the listeners are still attached, hands\n // those saves (with `storeError` in place of the links) to `onSave`\n // first. A no-op when nothing is pending, including StrictMode's\n // simulated unmount at mount time.\n //\n // Guarded: when an older CDN bundle registered the tag first, `el` is\n // that bundle's class and lacks the method — every other new-API use\n // degrades silently via property assignment, and unmount must not be\n // the one path that throws.\n if (typeof el.flushPendingSaves === 'function') {\n el.flushPendingSaves('widget removed before the rendering copy completed')\n }\n for (const [name, listener] of subs) el.removeEventListener(name, listener)\n }\n }, [])\n\n // eslint-disable-next-line react-hooks/refs -- ref is forwarded as a prop, not read during render\n return createElement('sfx-template-builder', { ref, class: className, style })\n})\n"],"names":["TemplateBuilder","forwardRef","props","forwardedRef","className","style","onReady","onOpen","onClose","onSave","onError","onDirtyChange","config","ref","useRef","useImperativeHandle","useLayoutEffect","el","handlers","subs","on","name","pick","listener","e","h","saveListener","onSaveNow","result","err","createElement"],"mappings":"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"}
package/dist/react.d.ts CHANGED
@@ -59,7 +59,7 @@ export interface TemplateBuilderBaseProps {
59
59
  /** Colour scheme for the editor chrome. */
60
60
  theme?: BuilderTheme;
61
61
  /**
62
- * Metadata model offered in the editor as the "Custom metadata" value source
62
+ * Metadata model offered in the editor as the "Custom metadata" source type
63
63
  * — names only, no values. Omit it and the source is not offered.
64
64
  *
65
65
  * Compared by identity, like every other prop here, so a freshly built array
@@ -68,7 +68,7 @@ export interface TemplateBuilderBaseProps {
68
68
  */
69
69
  customMetadata?: CustomMetadataField[];
70
70
  /**
71
- * Display name for the custom-metadata value source in the editor's UI —
71
+ * Display name for the custom-metadata source type in the editor's UI —
72
72
  * e.g. "External metadata". Wording only: the stored template is unaffected.
73
73
  * Empty uses the editor's default, "Custom metadata".
74
74
  */
package/dist/react.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"react.js","sources":["../src/react.ts"],"sourcesContent":["import {\n createElement,\n forwardRef,\n useImperativeHandle,\n useLayoutEffect,\n useRef,\n type CSSProperties,\n type ReactElement,\n} from 'react'\nimport './define'\nimport type { SfxTemplateBuilder } from './template-builder'\nimport type { TemplateBuilderSaveDetail } from './template-builder'\nimport type {\n BuilderDirtyData,\n BuilderErrorData,\n BuilderTheme,\n CustomMetadataField,\n} from './protocol'\n\n/**\n * Hub session — the full-featured credential.\n */\nexport interface TemplateBuilderSessionAuth {\n sassKey: string\n sessionUuid: string\n companyUuid?: string\n projectUuid?: string\n secTemplate?: never\n}\n\n/**\n * Filerobot security template — a guest credential for hosts that have no Hub\n * session to hand over. `stateless` is required rather than merely implied:\n * the app takes a security template on the stateless embed route only, so the\n * combination is a compile-time error instead of a runtime one.\n *\n * `companyUuid` / `projectUuid` are absent by design — they name a Hub project\n * that cannot be looked up without a session.\n */\nexport interface TemplateBuilderSecTemplateAuth {\n secTemplate: string\n stateless: true\n sassKey?: never\n sessionUuid?: never\n companyUuid?: never\n projectUuid?: never\n}\n\nexport interface TemplateBuilderBaseProps {\n baseUrl: string\n token: string\n templateId?: string\n mode?: 'inline' | 'modal'\n /** Hand the template in and take it back out instead of using the DAM. */\n stateless?: boolean\n /** Stateless mode: the template to edit, as `.fdt` XML. */\n content?: string\n /**\n * Stateless mode: open on a new, empty template instead of supplying\n * `content`. The widget provides the blank document, the user picks the\n * canvas size in the editor, and `onSave` receives a complete `.fdt` to\n * store. Ignored when `content` is set.\n */\n newTemplate?: boolean\n /** Stateless mode: display name for the editor header. */\n templateName?: string\n /**\n * Stateless mode: the `template_query` to open on — the value handed back on\n * save. Reopens the template on the same layout and variable values; empty\n * falls back to the XML's own `default=` attributes.\n */\n templateQuery?: string\n /** Accent colour for the editor chrome, as `#rgb` / `#rrggbb`. */\n brandColor?: string\n /** Colour scheme for the editor chrome. */\n theme?: BuilderTheme\n /**\n * Metadata model offered in the editor as the \"Custom metadata\" value source\n * — names only, no values. Omit it and the source is not offered.\n *\n * Compared by identity, like every other prop here, so a freshly built array\n * counts as a change. Nothing is re-sent to the editor over it — the element\n * de-dupes by value — but hoisting or memoising the array avoids the churn.\n */\n customMetadata?: CustomMetadataField[]\n /**\n * Display name for the custom-metadata value source in the editor's UI —\n * e.g. \"External metadata\". Wording only: the stored template is unaffected.\n * Empty uses the editor's default, \"Custom metadata\".\n */\n customMetadataLabel?: string\n /**\n * Stateless only: store each save in Filerobot too, so the CDN can render\n * it. `onSave`'s detail then carries `stored: { uuid, url }` next to the raw\n * `content` — or `storeError` when the copy failed.\n */\n damStore?: boolean\n /**\n * Folder new templates land in under `damStore` when the template id names\n * no existing DAM file (an existing file's own folder always wins).\n */\n storeFolder?: string\n /**\n * `damStore`: the `stored.uuid` a previous session's save reported for THIS\n * document, so re-saves after a reload resolve to (and version) the copy\n * that already exists instead of erroring on unchanged content or starting\n * a fresh file. Per-document — pass it with the content it belongs to.\n */\n storedUuid?: string\n readyTimeout?: number\n className?: string\n style?: CSSProperties\n onReady?: () => void\n onOpen?: () => void\n onClose?: () => void\n /**\n * Fired on save. In stateless mode the detail carries the edited `content`\n * for you to persist; otherwise it reports the uuid the app uploaded to.\n *\n * In stateless mode the outcome is reported back to the editor: return (or\n * resolve to) `false`, or throw, and the editor restores its unsaved-changes\n * flag and tells the user the save failed. Anything else counts as persisted.\n */\n onSave?: (\n data: TemplateBuilderSaveDetail,\n ) => void | boolean | Promise<void | boolean>\n onError?: (data: BuilderErrorData) => void\n /**\n * Stateless mode: unsaved-changes flag changed. Use it to prompt before\n * swapping `content`, which discards in-progress edits.\n */\n onDirtyChange?: (data: BuilderDirtyData) => void\n}\n\nexport type TemplateBuilderProps = TemplateBuilderBaseProps &\n (TemplateBuilderSessionAuth | TemplateBuilderSecTemplateAuth)\n\n/**\n * React wrapper around `<sfx-template-builder>`. Props are assigned as\n * element properties via ref (works on React 18 and 19 alike); callbacks\n * subscribe to the element's CustomEvents.\n *\n * Forwards a ref to the underlying element, which is the only way to reach the\n * imperative API — `open()` in particular, without which `mode=\"modal\"` can\n * never be shown:\n *\n * ```tsx\n * const builder = useRef<SfxTemplateBuilder>(null)\n * <TemplateBuilder ref={builder} mode=\"modal\" … />\n * <button onClick={() => builder.current?.open('tpl-1')}>Edit</button>\n * ```\n *\n * `forwardRef` rather than a plain `ref` prop: React 19 accepts the latter for\n * function components, React 18 does not, and both are supported peers.\n */\nexport const TemplateBuilder = forwardRef<\n SfxTemplateBuilder,\n TemplateBuilderProps\n>(function TemplateBuilder(props, forwardedRef): ReactElement {\n const {\n className,\n style,\n onReady,\n onOpen,\n onClose,\n onSave,\n onError,\n onDirtyChange,\n ...config\n } = props\n const ref = useRef<SfxTemplateBuilder>(null)\n\n // Hand the same element out to the caller without giving up the internal ref\n // the effects below rely on.\n useImperativeHandle(forwardedRef, () => ref.current as SfxTemplateBuilder, [])\n\n // Assign config before paint so the iframe doesn't first mount with defaults.\n useLayoutEffect(() => {\n const el = ref.current\n if (!el) return\n el.baseUrl = config.baseUrl\n el.token = config.token\n el.sassKey = config.sassKey ?? ''\n el.sessionUuid = config.sessionUuid ?? ''\n el.secTemplate = config.secTemplate ?? ''\n el.companyUuid = config.companyUuid ?? ''\n el.projectUuid = config.projectUuid ?? ''\n el.templateId = config.templateId ?? ''\n el.mode = config.mode ?? 'inline'\n el.stateless = config.stateless ?? false\n el.templateName = config.templateName ?? ''\n el.templateQuery = config.templateQuery ?? ''\n el.brandColor = config.brandColor ?? ''\n el.theme = config.theme ?? ''\n el.newTemplate = config.newTemplate ?? false\n el.customMetadata = config.customMetadata ?? []\n el.customMetadataLabel = config.customMetadataLabel ?? ''\n el.damStore = config.damStore ?? false\n el.storeFolder = config.storeFolder ?? '/'\n el.storedUuid = config.storedUuid ?? ''\n // Assigned last: the element sends content to the app as soon as it has\n // both a request and a value, so the id, name and query must already be\n // set — all four ship as one message.\n el.content = config.content ?? ''\n if (config.readyTimeout !== undefined) el.readyTimeout = config.readyTimeout\n }, [\n config.baseUrl,\n config.token,\n config.sassKey,\n config.sessionUuid,\n config.secTemplate,\n config.companyUuid,\n config.projectUuid,\n config.templateId,\n config.mode,\n config.stateless,\n config.content,\n config.newTemplate,\n config.templateName,\n config.templateQuery,\n config.brandColor,\n config.theme,\n config.customMetadata,\n config.customMetadataLabel,\n config.damStore,\n config.storeFolder,\n config.storedUuid,\n config.readyTimeout,\n ])\n\n // The callbacks the listeners read at event time. A ref rather than effect\n // dependencies: listeners are attached once per element (below), so a parent\n // re-render swapping handler identities costs nothing — and, decisively, the\n // listeners are still attached during the element's disconnect-time flush of\n // pending dam-store saves, which a resubscribe-per-change cleanup would have\n // already torn down.\n const handlers = useRef({\n onReady,\n onOpen,\n onClose,\n onSave,\n onError,\n onDirtyChange,\n })\n useLayoutEffect(() => {\n handlers.current = { onReady, onOpen, onClose, onSave, onError, onDirtyChange }\n })\n\n // Layout effect, not passive: the element reports config errors (e.g.\n // `invalid-base-url`) in a microtask queued during this same commit, and a\n // passive effect would subscribe only after that microtask has fired —\n // making a mount-time error unobservable from React. This effect is declared\n // after the config one, so it still runs once the config is assigned.\n useLayoutEffect(() => {\n const el = ref.current\n if (!el) return\n const subs: Array<[string, EventListener]> = []\n const on = (\n name: string,\n pick: (h: typeof handlers.current) => ((detail: never) => void) | undefined,\n ) => {\n const listener = ((e: CustomEvent) =>\n pick(handlers.current)?.(e.detail as never)) as EventListener\n el.addEventListener(name, listener)\n subs.push([name, listener])\n }\n on('ready', (h) => h.onReady)\n on('open', (h) => h.onOpen)\n on('close', (h) => h.onClose)\n on('error', (h) => h.onError)\n on('dirtychange', (h) => h.onDirtyChange)\n\n // `save` is not just re-emitted: in stateless mode the handler's outcome\n // is acked back, so a failed write on the host side doesn't leave the\n // editor showing the template as saved. `confirmSave` no-ops in DAM mode.\n const saveListener = ((e: CustomEvent<TemplateBuilderSaveDetail>) => {\n const onSaveNow = handlers.current.onSave\n if (!onSaveNow) return\n // Wrapped in a promise so a synchronous throw is handled like a\n // rejection, and a sync `false` like a resolved one.\n Promise.resolve()\n .then(() => onSaveNow(e.detail))\n .then((result) => el.confirmSave(result !== false))\n .catch((err) => {\n // No message: an internal error string is not something to put in\n // front of the end user. The editor uses its own wording.\n console.error('[sfx-template-builder] onSave failed:', err)\n el.confirmSave(false)\n })\n }) as EventListener\n el.addEventListener('save', saveListener)\n subs.push(['save', saveListener])\n\n return () => {\n // React runs this cleanup BEFORE it detaches the node, so the element's\n // own disconnect-time flush of in-flight dam-store saves would fire\n // after every listener is gone — and the raw save would be silently\n // lost. Flushing here, while the listeners are still attached, hands\n // those saves (with `storeError` in place of the links) to `onSave`\n // first. A no-op when nothing is pending, including StrictMode's\n // simulated unmount at mount time.\n //\n // Guarded: when an older CDN bundle registered the tag first, `el` is\n // that bundle's class and lacks the method — every other new-API use\n // degrades silently via property assignment, and unmount must not be\n // the one path that throws.\n if (typeof el.flushPendingSaves === 'function') {\n el.flushPendingSaves('widget removed before the rendering copy completed')\n }\n for (const [name, listener] of subs) el.removeEventListener(name, listener)\n }\n }, [])\n\n // eslint-disable-next-line react-hooks/refs -- ref is forwarded as a prop, not read during render\n return createElement('sfx-template-builder', { ref, class: className, style })\n})\n"],"names":["TemplateBuilder","forwardRef","props","forwardedRef","className","style","onReady","onOpen","onClose","onSave","onError","onDirtyChange","config","ref","useRef","useImperativeHandle","useLayoutEffect","el","handlers","subs","on","name","pick","listener","e","h","saveListener","onSaveNow","result","err","createElement"],"mappings":"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"}
1
+ {"version":3,"file":"react.js","sources":["../src/react.ts"],"sourcesContent":["import {\n createElement,\n forwardRef,\n useImperativeHandle,\n useLayoutEffect,\n useRef,\n type CSSProperties,\n type ReactElement,\n} from 'react'\nimport './define'\nimport type { SfxTemplateBuilder } from './template-builder'\nimport type { TemplateBuilderSaveDetail } from './template-builder'\nimport type {\n BuilderDirtyData,\n BuilderErrorData,\n BuilderTheme,\n CustomMetadataField,\n} from './protocol'\n\n/**\n * Hub session — the full-featured credential.\n */\nexport interface TemplateBuilderSessionAuth {\n sassKey: string\n sessionUuid: string\n companyUuid?: string\n projectUuid?: string\n secTemplate?: never\n}\n\n/**\n * Filerobot security template — a guest credential for hosts that have no Hub\n * session to hand over. `stateless` is required rather than merely implied:\n * the app takes a security template on the stateless embed route only, so the\n * combination is a compile-time error instead of a runtime one.\n *\n * `companyUuid` / `projectUuid` are absent by design — they name a Hub project\n * that cannot be looked up without a session.\n */\nexport interface TemplateBuilderSecTemplateAuth {\n secTemplate: string\n stateless: true\n sassKey?: never\n sessionUuid?: never\n companyUuid?: never\n projectUuid?: never\n}\n\nexport interface TemplateBuilderBaseProps {\n baseUrl: string\n token: string\n templateId?: string\n mode?: 'inline' | 'modal'\n /** Hand the template in and take it back out instead of using the DAM. */\n stateless?: boolean\n /** Stateless mode: the template to edit, as `.fdt` XML. */\n content?: string\n /**\n * Stateless mode: open on a new, empty template instead of supplying\n * `content`. The widget provides the blank document, the user picks the\n * canvas size in the editor, and `onSave` receives a complete `.fdt` to\n * store. Ignored when `content` is set.\n */\n newTemplate?: boolean\n /** Stateless mode: display name for the editor header. */\n templateName?: string\n /**\n * Stateless mode: the `template_query` to open on — the value handed back on\n * save. Reopens the template on the same layout and variable values; empty\n * falls back to the XML's own `default=` attributes.\n */\n templateQuery?: string\n /** Accent colour for the editor chrome, as `#rgb` / `#rrggbb`. */\n brandColor?: string\n /** Colour scheme for the editor chrome. */\n theme?: BuilderTheme\n /**\n * Metadata model offered in the editor as the \"Custom metadata\" source type\n * — names only, no values. Omit it and the source is not offered.\n *\n * Compared by identity, like every other prop here, so a freshly built array\n * counts as a change. Nothing is re-sent to the editor over it — the element\n * de-dupes by value — but hoisting or memoising the array avoids the churn.\n */\n customMetadata?: CustomMetadataField[]\n /**\n * Display name for the custom-metadata source type in the editor's UI —\n * e.g. \"External metadata\". Wording only: the stored template is unaffected.\n * Empty uses the editor's default, \"Custom metadata\".\n */\n customMetadataLabel?: string\n /**\n * Stateless only: store each save in Filerobot too, so the CDN can render\n * it. `onSave`'s detail then carries `stored: { uuid, url }` next to the raw\n * `content` — or `storeError` when the copy failed.\n */\n damStore?: boolean\n /**\n * Folder new templates land in under `damStore` when the template id names\n * no existing DAM file (an existing file's own folder always wins).\n */\n storeFolder?: string\n /**\n * `damStore`: the `stored.uuid` a previous session's save reported for THIS\n * document, so re-saves after a reload resolve to (and version) the copy\n * that already exists instead of erroring on unchanged content or starting\n * a fresh file. Per-document — pass it with the content it belongs to.\n */\n storedUuid?: string\n readyTimeout?: number\n className?: string\n style?: CSSProperties\n onReady?: () => void\n onOpen?: () => void\n onClose?: () => void\n /**\n * Fired on save. In stateless mode the detail carries the edited `content`\n * for you to persist; otherwise it reports the uuid the app uploaded to.\n *\n * In stateless mode the outcome is reported back to the editor: return (or\n * resolve to) `false`, or throw, and the editor restores its unsaved-changes\n * flag and tells the user the save failed. Anything else counts as persisted.\n */\n onSave?: (\n data: TemplateBuilderSaveDetail,\n ) => void | boolean | Promise<void | boolean>\n onError?: (data: BuilderErrorData) => void\n /**\n * Stateless mode: unsaved-changes flag changed. Use it to prompt before\n * swapping `content`, which discards in-progress edits.\n */\n onDirtyChange?: (data: BuilderDirtyData) => void\n}\n\nexport type TemplateBuilderProps = TemplateBuilderBaseProps &\n (TemplateBuilderSessionAuth | TemplateBuilderSecTemplateAuth)\n\n/**\n * React wrapper around `<sfx-template-builder>`. Props are assigned as\n * element properties via ref (works on React 18 and 19 alike); callbacks\n * subscribe to the element's CustomEvents.\n *\n * Forwards a ref to the underlying element, which is the only way to reach the\n * imperative API — `open()` in particular, without which `mode=\"modal\"` can\n * never be shown:\n *\n * ```tsx\n * const builder = useRef<SfxTemplateBuilder>(null)\n * <TemplateBuilder ref={builder} mode=\"modal\" … />\n * <button onClick={() => builder.current?.open('tpl-1')}>Edit</button>\n * ```\n *\n * `forwardRef` rather than a plain `ref` prop: React 19 accepts the latter for\n * function components, React 18 does not, and both are supported peers.\n */\nexport const TemplateBuilder = forwardRef<\n SfxTemplateBuilder,\n TemplateBuilderProps\n>(function TemplateBuilder(props, forwardedRef): ReactElement {\n const {\n className,\n style,\n onReady,\n onOpen,\n onClose,\n onSave,\n onError,\n onDirtyChange,\n ...config\n } = props\n const ref = useRef<SfxTemplateBuilder>(null)\n\n // Hand the same element out to the caller without giving up the internal ref\n // the effects below rely on.\n useImperativeHandle(forwardedRef, () => ref.current as SfxTemplateBuilder, [])\n\n // Assign config before paint so the iframe doesn't first mount with defaults.\n useLayoutEffect(() => {\n const el = ref.current\n if (!el) return\n el.baseUrl = config.baseUrl\n el.token = config.token\n el.sassKey = config.sassKey ?? ''\n el.sessionUuid = config.sessionUuid ?? ''\n el.secTemplate = config.secTemplate ?? ''\n el.companyUuid = config.companyUuid ?? ''\n el.projectUuid = config.projectUuid ?? ''\n el.templateId = config.templateId ?? ''\n el.mode = config.mode ?? 'inline'\n el.stateless = config.stateless ?? false\n el.templateName = config.templateName ?? ''\n el.templateQuery = config.templateQuery ?? ''\n el.brandColor = config.brandColor ?? ''\n el.theme = config.theme ?? ''\n el.newTemplate = config.newTemplate ?? false\n el.customMetadata = config.customMetadata ?? []\n el.customMetadataLabel = config.customMetadataLabel ?? ''\n el.damStore = config.damStore ?? false\n el.storeFolder = config.storeFolder ?? '/'\n el.storedUuid = config.storedUuid ?? ''\n // Assigned last: the element sends content to the app as soon as it has\n // both a request and a value, so the id, name and query must already be\n // set — all four ship as one message.\n el.content = config.content ?? ''\n if (config.readyTimeout !== undefined) el.readyTimeout = config.readyTimeout\n }, [\n config.baseUrl,\n config.token,\n config.sassKey,\n config.sessionUuid,\n config.secTemplate,\n config.companyUuid,\n config.projectUuid,\n config.templateId,\n config.mode,\n config.stateless,\n config.content,\n config.newTemplate,\n config.templateName,\n config.templateQuery,\n config.brandColor,\n config.theme,\n config.customMetadata,\n config.customMetadataLabel,\n config.damStore,\n config.storeFolder,\n config.storedUuid,\n config.readyTimeout,\n ])\n\n // The callbacks the listeners read at event time. A ref rather than effect\n // dependencies: listeners are attached once per element (below), so a parent\n // re-render swapping handler identities costs nothing — and, decisively, the\n // listeners are still attached during the element's disconnect-time flush of\n // pending dam-store saves, which a resubscribe-per-change cleanup would have\n // already torn down.\n const handlers = useRef({\n onReady,\n onOpen,\n onClose,\n onSave,\n onError,\n onDirtyChange,\n })\n useLayoutEffect(() => {\n handlers.current = { onReady, onOpen, onClose, onSave, onError, onDirtyChange }\n })\n\n // Layout effect, not passive: the element reports config errors (e.g.\n // `invalid-base-url`) in a microtask queued during this same commit, and a\n // passive effect would subscribe only after that microtask has fired —\n // making a mount-time error unobservable from React. This effect is declared\n // after the config one, so it still runs once the config is assigned.\n useLayoutEffect(() => {\n const el = ref.current\n if (!el) return\n const subs: Array<[string, EventListener]> = []\n const on = (\n name: string,\n pick: (h: typeof handlers.current) => ((detail: never) => void) | undefined,\n ) => {\n const listener = ((e: CustomEvent) =>\n pick(handlers.current)?.(e.detail as never)) as EventListener\n el.addEventListener(name, listener)\n subs.push([name, listener])\n }\n on('ready', (h) => h.onReady)\n on('open', (h) => h.onOpen)\n on('close', (h) => h.onClose)\n on('error', (h) => h.onError)\n on('dirtychange', (h) => h.onDirtyChange)\n\n // `save` is not just re-emitted: in stateless mode the handler's outcome\n // is acked back, so a failed write on the host side doesn't leave the\n // editor showing the template as saved. `confirmSave` no-ops in DAM mode.\n const saveListener = ((e: CustomEvent<TemplateBuilderSaveDetail>) => {\n const onSaveNow = handlers.current.onSave\n if (!onSaveNow) return\n // Wrapped in a promise so a synchronous throw is handled like a\n // rejection, and a sync `false` like a resolved one.\n Promise.resolve()\n .then(() => onSaveNow(e.detail))\n .then((result) => el.confirmSave(result !== false))\n .catch((err) => {\n // No message: an internal error string is not something to put in\n // front of the end user. The editor uses its own wording.\n console.error('[sfx-template-builder] onSave failed:', err)\n el.confirmSave(false)\n })\n }) as EventListener\n el.addEventListener('save', saveListener)\n subs.push(['save', saveListener])\n\n return () => {\n // React runs this cleanup BEFORE it detaches the node, so the element's\n // own disconnect-time flush of in-flight dam-store saves would fire\n // after every listener is gone — and the raw save would be silently\n // lost. Flushing here, while the listeners are still attached, hands\n // those saves (with `storeError` in place of the links) to `onSave`\n // first. A no-op when nothing is pending, including StrictMode's\n // simulated unmount at mount time.\n //\n // Guarded: when an older CDN bundle registered the tag first, `el` is\n // that bundle's class and lacks the method — every other new-API use\n // degrades silently via property assignment, and unmount must not be\n // the one path that throws.\n if (typeof el.flushPendingSaves === 'function') {\n el.flushPendingSaves('widget removed before the rendering copy completed')\n }\n for (const [name, listener] of subs) el.removeEventListener(name, listener)\n }\n }, [])\n\n // eslint-disable-next-line react-hooks/refs -- ref is forwarded as a prop, not read during render\n return createElement('sfx-template-builder', { ref, class: className, style })\n})\n"],"names":["TemplateBuilder","forwardRef","props","forwardedRef","className","style","onReady","onOpen","onClose","onSave","onError","onDirtyChange","config","ref","useRef","useImperativeHandle","useLayoutEffect","el","handlers","subs","on","name","pick","listener","e","h","saveListener","onSaveNow","result","err","createElement"],"mappings":"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"}