@scaleflex/template-builder 0.2.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/integrate-template-builder/SKILL.md +27 -21
- package/CHANGELOG.md +178 -4
- package/README.md +207 -52
- package/dist/dam-store.d.ts +92 -0
- package/dist/define.cjs +1 -1
- package/dist/define.js +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +9 -8
- package/dist/protocol.d.ts +60 -2
- package/dist/react.cjs +1 -1
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.ts +34 -1
- package/dist/react.js +43 -28
- package/dist/react.js.map +1 -1
- package/dist/template-builder-B9Cwo_Q-.js +651 -0
- package/dist/template-builder-B9Cwo_Q-.js.map +1 -0
- package/dist/template-builder-Byqg1q93.cjs +53 -0
- package/dist/template-builder-Byqg1q93.cjs.map +1 -0
- package/dist/template-builder.d.ts +164 -4
- package/package.json +1 -1
- package/src/dam-store.ts +388 -0
- package/src/index.ts +2 -0
- package/src/protocol.ts +64 -2
- package/src/react.ts +111 -27
- package/src/template-builder.ts +405 -7
- package/dist/template-builder-CK2Zlo7E.cjs +0 -53
- package/dist/template-builder-CK2Zlo7E.cjs.map +0 -1
- package/dist/template-builder-De0hRO4s.js +0 -380
- package/dist/template-builder-De0hRO4s.js.map +0 -1
package/dist/protocol.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const PROTOCOL_VERSION =
|
|
1
|
+
export declare const PROTOCOL_VERSION = 3;
|
|
2
2
|
/** Editor mounted with valid auth — the embed handshake succeeded. */
|
|
3
3
|
export declare const BUILDER_READY = "design-templates:builder:ready";
|
|
4
4
|
/** Editor UI opened (kept for Hub backwards compatibility; implies ready). */
|
|
@@ -174,6 +174,64 @@ export interface HostLoadMessage {
|
|
|
174
174
|
* `src/lib/xml/__tests__/blank-template.test.ts`.
|
|
175
175
|
*/
|
|
176
176
|
export declare const BLANK_TEMPLATE_XML: string;
|
|
177
|
+
/**
|
|
178
|
+
* Host-supplied editor configuration (protocol v3). Sent once the app reports
|
|
179
|
+
* `BUILDER_READY`, and again whenever the host changes it.
|
|
180
|
+
*
|
|
181
|
+
* Separate from `HOST_LOAD` because it is not per-template and because it must
|
|
182
|
+
* also reach DAM-backed embeds, which never receive a `HOST_LOAD` at all — the
|
|
183
|
+
* app loads those templates itself.
|
|
184
|
+
*
|
|
185
|
+
* Purely additive: an app deployment that predates this message ignores it and
|
|
186
|
+
* behaves exactly as before, so a newer widget stays compatible with an older
|
|
187
|
+
* app. Held to the same origin bar as `HOST_LOAD`.
|
|
188
|
+
*/
|
|
189
|
+
export declare const HOST_CONFIG = "design-templates:host:config";
|
|
190
|
+
/**
|
|
191
|
+
* One field of a host-supplied metadata model, offered in the editor as the
|
|
192
|
+
* "Custom metadata" value source.
|
|
193
|
+
*
|
|
194
|
+
* The model is a vocabulary, not data: it names the fields the host can fill at
|
|
195
|
+
* render time, so an author can bind a variable to `sku` rather than having to
|
|
196
|
+
* remember that the variable's slug happens to mean the SKU. No value travels
|
|
197
|
+
* with it — the host substitutes one by putting `$slug=value` in the render
|
|
198
|
+
* query, exactly as it would for a free-text variable.
|
|
199
|
+
*
|
|
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
|
|
202
|
+
* unavailable.
|
|
203
|
+
*/
|
|
204
|
+
export interface CustomMetadataField {
|
|
205
|
+
/**
|
|
206
|
+
* Stable identifier stored in the template as `custom_ckey`. The host's own
|
|
207
|
+
* key for the field — the app never resolves it against anything.
|
|
208
|
+
*/
|
|
209
|
+
key: string;
|
|
210
|
+
/** Label shown in the editor's field picker. Falls back to `key` when empty. */
|
|
211
|
+
title?: string;
|
|
212
|
+
/** Optional section header, used to group fields in the picker. */
|
|
213
|
+
group?: string;
|
|
214
|
+
}
|
|
215
|
+
export interface HostConfigData {
|
|
216
|
+
/**
|
|
217
|
+
* Metadata model offered as the "Custom metadata" value source. Omitted or
|
|
218
|
+
* empty hides that source in the editor, so a host that sends nothing sees
|
|
219
|
+
* the two sources it always had.
|
|
220
|
+
*/
|
|
221
|
+
customMetadata?: CustomMetadataField[];
|
|
222
|
+
/**
|
|
223
|
+
* Display name for the custom-metadata value source in the editor's UI
|
|
224
|
+
* (source dropdowns, properties-panel section). Defaults to "Custom
|
|
225
|
+
* metadata"; a host can rename it after its own domain — e.g. "External
|
|
226
|
+
* metadata" or "Product attributes". Pure wording: the stored template is
|
|
227
|
+
* unaffected.
|
|
228
|
+
*/
|
|
229
|
+
customMetadataLabel?: string;
|
|
230
|
+
}
|
|
231
|
+
export interface HostConfigMessage {
|
|
232
|
+
type: typeof HOST_CONFIG;
|
|
233
|
+
data: HostConfigData;
|
|
234
|
+
}
|
|
177
235
|
/**
|
|
178
236
|
* Stateless mode only (protocol v2). Reports whether the host managed to
|
|
179
237
|
* persist the content it received in `BUILDER_CONTENT`.
|
|
@@ -195,7 +253,7 @@ export interface HostSavedMessage {
|
|
|
195
253
|
type: typeof HOST_SAVED;
|
|
196
254
|
data: HostSavedData;
|
|
197
255
|
}
|
|
198
|
-
export type HostMessage = HostLoadMessage | HostSavedMessage;
|
|
256
|
+
export type HostMessage = HostLoadMessage | HostSavedMessage | HostConfigMessage;
|
|
199
257
|
/**
|
|
200
258
|
* Query params the app's proxy middleware (`src/proxy.ts`) converts into auth
|
|
201
259
|
* cookies on first navigation. Names are wire format.
|
package/dist/react.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const a=require("react");require("./define.cjs");const
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const a=require("react");require("./define.cjs");const g=a.forwardRef(function(b,h){const{className:U,style:T,onReady:i,onOpen:c,onClose:m,onSave:f,onError:p,onDirtyChange:y,...e}=b,l=a.useRef(null);a.useImperativeHandle(h,()=>l.current,[]),a.useLayoutEffect(()=>{const t=l.current;t&&(t.baseUrl=e.baseUrl,t.token=e.token,t.sassKey=e.sassKey??"",t.sessionUuid=e.sessionUuid??"",t.secTemplate=e.secTemplate??"",t.companyUuid=e.companyUuid??"",t.projectUuid=e.projectUuid??"",t.templateId=e.templateId??"",t.mode=e.mode??"inline",t.stateless=e.stateless??!1,t.templateName=e.templateName??"",t.templateQuery=e.templateQuery??"",t.brandColor=e.brandColor??"",t.theme=e.theme??"",t.newTemplate=e.newTemplate??!1,t.customMetadata=e.customMetadata??[],t.customMetadataLabel=e.customMetadataLabel??"",t.damStore=e.damStore??!1,t.storeFolder=e.storeFolder??"/",t.storedUuid=e.storedUuid??"",t.content=e.content??"",e.readyTimeout!==void 0&&(t.readyTimeout=e.readyTimeout))},[e.baseUrl,e.token,e.sassKey,e.sessionUuid,e.secTemplate,e.companyUuid,e.projectUuid,e.templateId,e.mode,e.stateless,e.content,e.newTemplate,e.templateName,e.templateQuery,e.brandColor,e.theme,e.customMetadata,e.customMetadataLabel,e.damStore,e.storeFolder,e.storedUuid,e.readyTimeout]);const d=a.useRef({onReady:i,onOpen:c,onClose:m,onSave:f,onError:p,onDirtyChange:y});return a.useLayoutEffect(()=>{d.current={onReady:i,onOpen:c,onClose:m,onSave:f,onError:p,onDirtyChange:y}}),a.useLayoutEffect(()=>{const t=l.current;if(!t)return;const u=[],n=(o,r)=>{const s=(S=>r(d.current)?.(S.detail));t.addEventListener(o,s),u.push([o,s])};n("ready",o=>o.onReady),n("open",o=>o.onOpen),n("close",o=>o.onClose),n("error",o=>o.onError),n("dirtychange",o=>o.onDirtyChange);const v=(o=>{const r=d.current.onSave;r&&Promise.resolve().then(()=>r(o.detail)).then(s=>t.confirmSave(s!==!1)).catch(s=>{console.error("[sfx-template-builder] onSave failed:",s),t.confirmSave(!1)})});return t.addEventListener("save",v),u.push(["save",v]),()=>{typeof t.flushPendingSaves=="function"&&t.flushPendingSaves("widget removed before the rendering copy completed");for(const[o,r]of u)t.removeEventListener(o,r)}},[]),a.createElement("sfx-template-builder",{ref:l,class:U,style:T})});exports.TemplateBuilder=g;
|
|
2
2
|
//# sourceMappingURL=react.cjs.map
|
package/dist/react.cjs.map
CHANGED
|
@@ -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 { BuilderDirtyData, BuilderErrorData, BuilderTheme } 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 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 // 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.readyTimeout,\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 = (name: string, handler?: (detail: never) => void) => {\n if (!handler) return\n const listener = ((e: CustomEvent) => handler(e.detail as never)) as EventListener\n el.addEventListener(name, listener)\n subs.push([name, listener])\n }\n on('ready', onReady)\n on('open', onOpen)\n on('close', onClose)\n on('error', onError)\n on('dirtychange', 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 if (onSave) {\n const listener = ((e: CustomEvent<TemplateBuilderSaveDetail>) => {\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(() => onSave(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', listener)\n subs.push(['save', listener])\n }\n\n return () => {\n for (const [name, listener] of subs) el.removeEventListener(name, listener)\n }\n }, [onReady, onOpen, onClose, onSave, onError, onDirtyChange])\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","subs","on","name","handler","listener","e","result","err","createElement"],"mappings":"iIAqHO,MAAMA,EAAkBC,EAAAA,WAG7B,SAAyBC,EAAOC,EAA4B,CAC5D,KAAM,CACJ,UAAAC,EACA,MAAAC,EACA,QAAAC,EACA,OAAAC,EACA,QAAAC,EACA,OAAAC,EACA,QAAAC,EACA,cAAAC,EACA,GAAGC,CAAA,EACDV,EACEW,EAAMC,EAAAA,OAA2B,IAAI,EAI3CC,OAAAA,EAAAA,oBAAoBZ,EAAc,IAAMU,EAAI,QAA+B,CAAA,CAAE,EAG7EG,EAAAA,gBAAgB,IAAM,CACpB,MAAMC,EAAKJ,EAAI,QACVI,IACLA,EAAG,QAAUL,EAAO,QACpBK,EAAG,MAAQL,EAAO,MAClBK,EAAG,QAAUL,EAAO,SAAW,GAC/BK,EAAG,YAAcL,EAAO,aAAe,GACvCK,EAAG,YAAcL,EAAO,aAAe,GACvCK,EAAG,YAAcL,EAAO,aAAe,GACvCK,EAAG,YAAcL,EAAO,aAAe,GACvCK,EAAG,WAAaL,EAAO,YAAc,GACrCK,EAAG,KAAOL,EAAO,MAAQ,SACzBK,EAAG,UAAYL,EAAO,WAAa,GACnCK,EAAG,aAAeL,EAAO,cAAgB,GACzCK,EAAG,cAAgBL,EAAO,eAAiB,GAC3CK,EAAG,WAAaL,EAAO,YAAc,GACrCK,EAAG,MAAQL,EAAO,OAAS,GAC3BK,EAAG,YAAcL,EAAO,aAAe,GAIvCK,EAAG,QAAUL,EAAO,SAAW,GAC3BA,EAAO,eAAiB,SAAWK,EAAG,aAAeL,EAAO,cAClE,EAAG,CACDA,EAAO,QACPA,EAAO,MACPA,EAAO,QACPA,EAAO,YACPA,EAAO,YACPA,EAAO,YACPA,EAAO,YACPA,EAAO,WACPA,EAAO,KACPA,EAAO,UACPA,EAAO,QACPA,EAAO,YACPA,EAAO,aACPA,EAAO,cACPA,EAAO,WACPA,EAAO,MACPA,EAAO,YAAA,CACR,EAODI,EAAAA,gBAAgB,IAAM,CACpB,MAAMC,EAAKJ,EAAI,QACf,GAAI,CAACI,EAAI,OACT,MAAMC,EAAuC,CAAA,EACvCC,EAAK,CAACC,EAAcC,IAAsC,CAC9D,GAAI,CAACA,EAAS,OACd,MAAMC,GAAaC,GAAmBF,EAAQE,EAAE,MAAe,GAC/DN,EAAG,iBAAiBG,EAAME,CAAQ,EAClCJ,EAAK,KAAK,CAACE,EAAME,CAAQ,CAAC,CAC5B,EAUA,GATAH,EAAG,QAASb,CAAO,EACnBa,EAAG,OAAQZ,CAAM,EACjBY,EAAG,QAASX,CAAO,EACnBW,EAAG,QAAST,CAAO,EACnBS,EAAG,cAAeR,CAAa,EAK3BF,EAAQ,CACV,MAAMa,GAAaC,GAA8C,CAG/D,QAAQ,QAAA,EACL,KAAK,IAAMd,EAAOc,EAAE,MAAM,CAAC,EAC3B,KAAMC,GAAWP,EAAG,YAAYO,IAAW,EAAK,CAAC,EACjD,MAAOC,GAAQ,CAGd,QAAQ,MAAM,wCAAyCA,CAAG,EAC1DR,EAAG,YAAY,EAAK,CACtB,CAAC,CACL,GACAA,EAAG,iBAAiB,OAAQK,CAAQ,EACpCJ,EAAK,KAAK,CAAC,OAAQI,CAAQ,CAAC,CAC9B,CAEA,MAAO,IAAM,CACX,SAAW,CAACF,EAAME,CAAQ,IAAKJ,EAAMD,EAAG,oBAAoBG,EAAME,CAAQ,CAC5E,CACF,EAAG,CAAChB,EAASC,EAAQC,EAASC,EAAQC,EAASC,CAAa,CAAC,EAGtDe,EAAAA,cAAc,uBAAwB,CAAE,IAAAb,EAAK,MAAOT,EAAW,MAAAC,EAAO,CAC/E,CAAC"}
|
|
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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"}
|
package/dist/react.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { type CSSProperties } from 'react';
|
|
|
2
2
|
import './define';
|
|
3
3
|
import type { SfxTemplateBuilder } from './template-builder';
|
|
4
4
|
import type { TemplateBuilderSaveDetail } from './template-builder';
|
|
5
|
-
import type { BuilderDirtyData, BuilderErrorData, BuilderTheme } from './protocol';
|
|
5
|
+
import type { BuilderDirtyData, BuilderErrorData, BuilderTheme, CustomMetadataField } from './protocol';
|
|
6
6
|
/**
|
|
7
7
|
* Hub session — the full-featured credential.
|
|
8
8
|
*/
|
|
@@ -58,6 +58,39 @@ export interface TemplateBuilderBaseProps {
|
|
|
58
58
|
brandColor?: string;
|
|
59
59
|
/** Colour scheme for the editor chrome. */
|
|
60
60
|
theme?: BuilderTheme;
|
|
61
|
+
/**
|
|
62
|
+
* Metadata model offered in the editor as the "Custom metadata" value source
|
|
63
|
+
* — names only, no values. Omit it and the source is not offered.
|
|
64
|
+
*
|
|
65
|
+
* Compared by identity, like every other prop here, so a freshly built array
|
|
66
|
+
* counts as a change. Nothing is re-sent to the editor over it — the element
|
|
67
|
+
* de-dupes by value — but hoisting or memoising the array avoids the churn.
|
|
68
|
+
*/
|
|
69
|
+
customMetadata?: CustomMetadataField[];
|
|
70
|
+
/**
|
|
71
|
+
* Display name for the custom-metadata value source in the editor's UI —
|
|
72
|
+
* e.g. "External metadata". Wording only: the stored template is unaffected.
|
|
73
|
+
* Empty uses the editor's default, "Custom metadata".
|
|
74
|
+
*/
|
|
75
|
+
customMetadataLabel?: string;
|
|
76
|
+
/**
|
|
77
|
+
* Stateless only: store each save in Filerobot too, so the CDN can render
|
|
78
|
+
* it. `onSave`'s detail then carries `stored: { uuid, url }` next to the raw
|
|
79
|
+
* `content` — or `storeError` when the copy failed.
|
|
80
|
+
*/
|
|
81
|
+
damStore?: boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Folder new templates land in under `damStore` when the template id names
|
|
84
|
+
* no existing DAM file (an existing file's own folder always wins).
|
|
85
|
+
*/
|
|
86
|
+
storeFolder?: string;
|
|
87
|
+
/**
|
|
88
|
+
* `damStore`: the `stored.uuid` a previous session's save reported for THIS
|
|
89
|
+
* document, so re-saves after a reload resolve to (and version) the copy
|
|
90
|
+
* that already exists instead of erroring on unchanged content or starting
|
|
91
|
+
* a fresh file. Per-document — pass it with the content it belongs to.
|
|
92
|
+
*/
|
|
93
|
+
storedUuid?: string;
|
|
61
94
|
readyTimeout?: number;
|
|
62
95
|
className?: string;
|
|
63
96
|
style?: CSSProperties;
|
package/dist/react.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import { forwardRef as
|
|
1
|
+
import { forwardRef as L, useRef as h, useImperativeHandle as C, useLayoutEffect as i, createElement as E } from "react";
|
|
2
2
|
import "./define.js";
|
|
3
|
-
const
|
|
3
|
+
const I = L(function(U, b) {
|
|
4
4
|
const {
|
|
5
5
|
className: T,
|
|
6
|
-
style:
|
|
6
|
+
style: S,
|
|
7
7
|
onReady: m,
|
|
8
8
|
onOpen: c,
|
|
9
|
-
onClose:
|
|
10
|
-
onSave:
|
|
11
|
-
onError:
|
|
12
|
-
onDirtyChange:
|
|
9
|
+
onClose: u,
|
|
10
|
+
onSave: p,
|
|
11
|
+
onError: f,
|
|
12
|
+
onDirtyChange: y,
|
|
13
13
|
...e
|
|
14
|
-
} =
|
|
15
|
-
|
|
16
|
-
const t =
|
|
17
|
-
t && (t.baseUrl = e.baseUrl, t.token = e.token, t.sassKey = e.sassKey ?? "", t.sessionUuid = e.sessionUuid ?? "", t.secTemplate = e.secTemplate ?? "", t.companyUuid = e.companyUuid ?? "", t.projectUuid = e.projectUuid ?? "", t.templateId = e.templateId ?? "", t.mode = e.mode ?? "inline", t.stateless = e.stateless ?? !1, t.templateName = e.templateName ?? "", t.templateQuery = e.templateQuery ?? "", t.brandColor = e.brandColor ?? "", t.theme = e.theme ?? "", t.newTemplate = e.newTemplate ?? !1, t.content = e.content ?? "", e.readyTimeout !== void 0 && (t.readyTimeout = e.readyTimeout));
|
|
14
|
+
} = U, s = h(null);
|
|
15
|
+
C(b, () => s.current, []), i(() => {
|
|
16
|
+
const t = s.current;
|
|
17
|
+
t && (t.baseUrl = e.baseUrl, t.token = e.token, t.sassKey = e.sassKey ?? "", t.sessionUuid = e.sessionUuid ?? "", t.secTemplate = e.secTemplate ?? "", t.companyUuid = e.companyUuid ?? "", t.projectUuid = e.projectUuid ?? "", t.templateId = e.templateId ?? "", t.mode = e.mode ?? "inline", t.stateless = e.stateless ?? !1, t.templateName = e.templateName ?? "", t.templateQuery = e.templateQuery ?? "", t.brandColor = e.brandColor ?? "", t.theme = e.theme ?? "", t.newTemplate = e.newTemplate ?? !1, t.customMetadata = e.customMetadata ?? [], t.customMetadataLabel = e.customMetadataLabel ?? "", t.damStore = e.damStore ?? !1, t.storeFolder = e.storeFolder ?? "/", t.storedUuid = e.storedUuid ?? "", t.content = e.content ?? "", e.readyTimeout !== void 0 && (t.readyTimeout = e.readyTimeout));
|
|
18
18
|
}, [
|
|
19
19
|
e.baseUrl,
|
|
20
20
|
e.token,
|
|
@@ -32,29 +32,44 @@ const S = h(function(y, v) {
|
|
|
32
32
|
e.templateQuery,
|
|
33
33
|
e.brandColor,
|
|
34
34
|
e.theme,
|
|
35
|
+
e.customMetadata,
|
|
36
|
+
e.customMetadataLabel,
|
|
37
|
+
e.damStore,
|
|
38
|
+
e.storeFolder,
|
|
39
|
+
e.storedUuid,
|
|
35
40
|
e.readyTimeout
|
|
36
|
-
])
|
|
37
|
-
|
|
41
|
+
]);
|
|
42
|
+
const l = h({
|
|
43
|
+
onReady: m,
|
|
44
|
+
onOpen: c,
|
|
45
|
+
onClose: u,
|
|
46
|
+
onSave: p,
|
|
47
|
+
onError: f,
|
|
48
|
+
onDirtyChange: y
|
|
49
|
+
});
|
|
50
|
+
return i(() => {
|
|
51
|
+
l.current = { onReady: m, onOpen: c, onClose: u, onSave: p, onError: f, onDirtyChange: y };
|
|
52
|
+
}), i(() => {
|
|
53
|
+
const t = s.current;
|
|
38
54
|
if (!t) return;
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
t.addEventListener(s, r), i.push([s, r]);
|
|
55
|
+
const d = [], n = (o, a) => {
|
|
56
|
+
const r = ((g) => a(l.current)?.(g.detail));
|
|
57
|
+
t.addEventListener(o, r), d.push([o, r]);
|
|
43
58
|
};
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
59
|
+
n("ready", (o) => o.onReady), n("open", (o) => o.onOpen), n("close", (o) => o.onClose), n("error", (o) => o.onError), n("dirtychange", (o) => o.onDirtyChange);
|
|
60
|
+
const v = ((o) => {
|
|
61
|
+
const a = l.current.onSave;
|
|
62
|
+
a && Promise.resolve().then(() => a(o.detail)).then((r) => t.confirmSave(r !== !1)).catch((r) => {
|
|
63
|
+
console.error("[sfx-template-builder] onSave failed:", r), t.confirmSave(!1);
|
|
49
64
|
});
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
for (const [
|
|
65
|
+
});
|
|
66
|
+
return t.addEventListener("save", v), d.push(["save", v]), () => {
|
|
67
|
+
typeof t.flushPendingSaves == "function" && t.flushPendingSaves("widget removed before the rendering copy completed");
|
|
68
|
+
for (const [o, a] of d) t.removeEventListener(o, a);
|
|
54
69
|
};
|
|
55
|
-
}, [
|
|
70
|
+
}, []), E("sfx-template-builder", { ref: s, class: T, style: S });
|
|
56
71
|
});
|
|
57
72
|
export {
|
|
58
|
-
|
|
73
|
+
I as TemplateBuilder
|
|
59
74
|
};
|
|
60
75
|
//# sourceMappingURL=react.js.map
|
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 { BuilderDirtyData, BuilderErrorData, BuilderTheme } 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 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 // 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.readyTimeout,\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 = (name: string, handler?: (detail: never) => void) => {\n if (!handler) return\n const listener = ((e: CustomEvent) => handler(e.detail as never)) as EventListener\n el.addEventListener(name, listener)\n subs.push([name, listener])\n }\n on('ready', onReady)\n on('open', onOpen)\n on('close', onClose)\n on('error', onError)\n on('dirtychange', 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 if (onSave) {\n const listener = ((e: CustomEvent<TemplateBuilderSaveDetail>) => {\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(() => onSave(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', listener)\n subs.push(['save', listener])\n }\n\n return () => {\n for (const [name, listener] of subs) el.removeEventListener(name, listener)\n }\n }, [onReady, onOpen, onClose, onSave, onError, onDirtyChange])\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","subs","on","name","handler","listener","e","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\" 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":";;AA2JO,MAAMA,IAAkBC,EAG7B,SAAyBC,GAAOC,GAA4B;AAC5D,QAAM;AAAA,IACJ,WAAAC;AAAA,IACA,OAAAC;AAAA,IACA,SAAAC;AAAA,IACA,QAAAC;AAAA,IACA,SAAAC;AAAA,IACA,QAAAC;AAAA,IACA,SAAAC;AAAA,IACA,eAAAC;AAAA,IACA,GAAGC;AAAA,EAAA,IACDV,GACEW,IAAMC,EAA2B,IAAI;AAI3C,EAAAC,EAAoBZ,GAAc,MAAMU,EAAI,SAA+B,CAAA,CAAE,GAG7EG,EAAgB,MAAM;AACpB,UAAMC,IAAKJ,EAAI;AACf,IAAKI,MACLA,EAAG,UAAUL,EAAO,SACpBK,EAAG,QAAQL,EAAO,OAClBK,EAAG,UAAUL,EAAO,WAAW,IAC/BK,EAAG,cAAcL,EAAO,eAAe,IACvCK,EAAG,cAAcL,EAAO,eAAe,IACvCK,EAAG,cAAcL,EAAO,eAAe,IACvCK,EAAG,cAAcL,EAAO,eAAe,IACvCK,EAAG,aAAaL,EAAO,cAAc,IACrCK,EAAG,OAAOL,EAAO,QAAQ,UACzBK,EAAG,YAAYL,EAAO,aAAa,IACnCK,EAAG,eAAeL,EAAO,gBAAgB,IACzCK,EAAG,gBAAgBL,EAAO,iBAAiB,IAC3CK,EAAG,aAAaL,EAAO,cAAc,IACrCK,EAAG,QAAQL,EAAO,SAAS,IAC3BK,EAAG,cAAcL,EAAO,eAAe,IACvCK,EAAG,iBAAiBL,EAAO,kBAAkB,CAAA,GAC7CK,EAAG,sBAAsBL,EAAO,uBAAuB,IACvDK,EAAG,WAAWL,EAAO,YAAY,IACjCK,EAAG,cAAcL,EAAO,eAAe,KACvCK,EAAG,aAAaL,EAAO,cAAc,IAIrCK,EAAG,UAAUL,EAAO,WAAW,IAC3BA,EAAO,iBAAiB,WAAWK,EAAG,eAAeL,EAAO;AAAA,EAClE,GAAG;AAAA,IACDA,EAAO;AAAA,IACPA,EAAO;AAAA,IACPA,EAAO;AAAA,IACPA,EAAO;AAAA,IACPA,EAAO;AAAA,IACPA,EAAO;AAAA,IACPA,EAAO;AAAA,IACPA,EAAO;AAAA,IACPA,EAAO;AAAA,IACPA,EAAO;AAAA,IACPA,EAAO;AAAA,IACPA,EAAO;AAAA,IACPA,EAAO;AAAA,IACPA,EAAO;AAAA,IACPA,EAAO;AAAA,IACPA,EAAO;AAAA,IACPA,EAAO;AAAA,IACPA,EAAO;AAAA,IACPA,EAAO;AAAA,IACPA,EAAO;AAAA,IACPA,EAAO;AAAA,IACPA,EAAO;AAAA,EAAA,CACR;AAQD,QAAMM,IAAWJ,EAAO;AAAA,IACtB,SAAAR;AAAA,IACA,QAAAC;AAAA,IACA,SAAAC;AAAA,IACA,QAAAC;AAAA,IACA,SAAAC;AAAA,IACA,eAAAC;AAAA,EAAA,CACD;AACD,SAAAK,EAAgB,MAAM;AACpB,IAAAE,EAAS,UAAU,EAAE,SAAAZ,GAAS,QAAAC,GAAQ,SAAAC,GAAS,QAAAC,GAAQ,SAAAC,GAAS,eAAAC,EAAA;AAAA,EAClE,CAAC,GAODK,EAAgB,MAAM;AACpB,UAAMC,IAAKJ,EAAI;AACf,QAAI,CAACI,EAAI;AACT,UAAME,IAAuC,CAAA,GACvCC,IAAK,CACTC,GACAC,MACG;AACH,YAAMC,KAAY,CAACC,MACjBF,EAAKJ,EAAS,OAAO,IAAIM,EAAE,MAAe;AAC5C,MAAAP,EAAG,iBAAiBI,GAAME,CAAQ,GAClCJ,EAAK,KAAK,CAACE,GAAME,CAAQ,CAAC;AAAA,IAC5B;AACA,IAAAH,EAAG,SAAS,CAACK,MAAMA,EAAE,OAAO,GAC5BL,EAAG,QAAQ,CAACK,MAAMA,EAAE,MAAM,GAC1BL,EAAG,SAAS,CAACK,MAAMA,EAAE,OAAO,GAC5BL,EAAG,SAAS,CAACK,MAAMA,EAAE,OAAO,GAC5BL,EAAG,eAAe,CAACK,MAAMA,EAAE,aAAa;AAKxC,UAAMC,KAAgB,CAACF,MAA8C;AACnE,YAAMG,IAAYT,EAAS,QAAQ;AACnC,MAAKS,KAGL,QAAQ,QAAA,EACL,KAAK,MAAMA,EAAUH,EAAE,MAAM,CAAC,EAC9B,KAAK,CAACI,MAAWX,EAAG,YAAYW,MAAW,EAAK,CAAC,EACjD,MAAM,CAACC,MAAQ;AAGd,gBAAQ,MAAM,yCAAyCA,CAAG,GAC1DZ,EAAG,YAAY,EAAK;AAAA,MACtB,CAAC;AAAA,IACL;AACA,WAAAA,EAAG,iBAAiB,QAAQS,CAAY,GACxCP,EAAK,KAAK,CAAC,QAAQO,CAAY,CAAC,GAEzB,MAAM;AAaX,MAAI,OAAOT,EAAG,qBAAsB,cAClCA,EAAG,kBAAkB,oDAAoD;AAE3E,iBAAW,CAACI,GAAME,CAAQ,KAAKJ,EAAM,CAAAF,EAAG,oBAAoBI,GAAME,CAAQ;AAAA,IAC5E;AAAA,EACF,GAAG,CAAA,CAAE,GAGEO,EAAc,wBAAwB,EAAE,KAAAjB,GAAK,OAAOT,GAAW,OAAAC,GAAO;AAC/E,CAAC;"}
|