@scaleflex/template-builder 0.1.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.
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./template-builder-CSyPZni9.cjs");typeof customElements<"u"&&!customElements.get("sfx-template-builder")&&customElements.define("sfx-template-builder",e.SfxTemplateBuilder);exports.SfxTemplateBuilder=e.SfxTemplateBuilder;
2
+ //# sourceMappingURL=define.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define.cjs","sources":["../src/define.ts"],"sourcesContent":["import { SfxTemplateBuilder } from './template-builder'\n\nif (\n typeof customElements !== 'undefined' &&\n !customElements.get('sfx-template-builder')\n) {\n customElements.define('sfx-template-builder', SfxTemplateBuilder)\n}\n\nexport { SfxTemplateBuilder }\n"],"names":["SfxTemplateBuilder"],"mappings":"mIAGE,OAAO,eAAmB,KAC1B,CAAC,eAAe,IAAI,sBAAsB,GAE1C,eAAe,OAAO,uBAAwBA,oBAAkB"}
@@ -0,0 +1,2 @@
1
+ import { SfxTemplateBuilder } from './template-builder';
2
+ export { SfxTemplateBuilder };
package/dist/define.js ADDED
@@ -0,0 +1,6 @@
1
+ import { S as e } from "./template-builder-S33H_d5T.js";
2
+ typeof customElements < "u" && !customElements.get("sfx-template-builder") && customElements.define("sfx-template-builder", e);
3
+ export {
4
+ e as SfxTemplateBuilder
5
+ };
6
+ //# sourceMappingURL=define.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define.js","sources":["../src/define.ts"],"sourcesContent":["import { SfxTemplateBuilder } from './template-builder'\n\nif (\n typeof customElements !== 'undefined' &&\n !customElements.get('sfx-template-builder')\n) {\n customElements.define('sfx-template-builder', SfxTemplateBuilder)\n}\n\nexport { SfxTemplateBuilder }\n"],"names":["SfxTemplateBuilder"],"mappings":";AAGE,OAAO,iBAAmB,OAC1B,CAAC,eAAe,IAAI,sBAAsB,KAE1C,eAAe,OAAO,wBAAwBA,CAAkB;"}
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const E=require("./template-builder-CSyPZni9.cjs");exports.AUTH_MODES=E.AUTH_MODES;exports.BRAND_COLOR_PATTERN=E.BRAND_COLOR_PATTERN;exports.BUILDER_CLOSE=E.BUILDER_CLOSE;exports.BUILDER_CONTENT=E.BUILDER_CONTENT;exports.BUILDER_CONTENT_REQUEST=E.BUILDER_CONTENT_REQUEST;exports.BUILDER_DIRTY=E.BUILDER_DIRTY;exports.BUILDER_ERROR=E.BUILDER_ERROR;exports.BUILDER_OPEN=E.BUILDER_OPEN;exports.BUILDER_READY=E.BUILDER_READY;exports.BUILDER_SAVE=E.BUILDER_SAVE;exports.EMBED_PARAMS=E.EMBED_PARAMS;exports.EMBED_ROUTE=E.EMBED_ROUTE;exports.HOST_LOAD=E.HOST_LOAD;exports.HOST_SAVED=E.HOST_SAVED;exports.PROTOCOL_VERSION=E.PROTOCOL_VERSION;exports.SfxTemplateBuilder=E.SfxTemplateBuilder;exports.builderRoute=E.builderRoute;
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export { SfxTemplateBuilder, type TemplateBuilderStatus, type TemplateBuilderEventMap, type TemplateBuilderSaveDetail, } from './template-builder';
2
+ export * from './protocol';
package/dist/index.js ADDED
@@ -0,0 +1,21 @@
1
+ import { A as a, B as s, a as _, b as D, c as O, d as T, e as B, f as L, g as S, h as U, E as A, i as I, H as N, j as e, P as C, S as P, k as r } from "./template-builder-S33H_d5T.js";
2
+ export {
3
+ a as AUTH_MODES,
4
+ s as BRAND_COLOR_PATTERN,
5
+ _ as BUILDER_CLOSE,
6
+ D as BUILDER_CONTENT,
7
+ O as BUILDER_CONTENT_REQUEST,
8
+ T as BUILDER_DIRTY,
9
+ B as BUILDER_ERROR,
10
+ L as BUILDER_OPEN,
11
+ S as BUILDER_READY,
12
+ U as BUILDER_SAVE,
13
+ A as EMBED_PARAMS,
14
+ I as EMBED_ROUTE,
15
+ N as HOST_LOAD,
16
+ e as HOST_SAVED,
17
+ C as PROTOCOL_VERSION,
18
+ P as SfxTemplateBuilder,
19
+ r as builderRoute
20
+ };
21
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
@@ -0,0 +1,237 @@
1
+ export declare const PROTOCOL_VERSION = 2;
2
+ /** Editor mounted with valid auth — the embed handshake succeeded. */
3
+ export declare const BUILDER_READY = "design-templates:builder:ready";
4
+ /** Editor UI opened (kept for Hub backwards compatibility; implies ready). */
5
+ export declare const BUILDER_OPEN = "design-templates:builder:open";
6
+ /** Editor UI closed / unmounted. */
7
+ export declare const BUILDER_CLOSE = "design-templates:builder:close";
8
+ /** Template saved. `data` is absent on app deployments older than protocol v1. */
9
+ export declare const BUILDER_SAVE = "design-templates:builder:save";
10
+ /** The app cannot start (e.g. auth cookies missing/blocked). */
11
+ export declare const BUILDER_ERROR = "design-templates:builder:error";
12
+ /**
13
+ * Stateless mode only (protocol v2). The editor mounted without a template and
14
+ * is waiting for the host to send `HOST_LOAD`. Re-sent on nothing — the host
15
+ * may answer late; the app keeps waiting until content arrives.
16
+ */
17
+ export declare const BUILDER_CONTENT_REQUEST = "design-templates:builder:content-request";
18
+ /**
19
+ * Stateless mode only (protocol v2). The user saved and the app is handing the
20
+ * edited template back instead of uploading it. This is the stateless
21
+ * counterpart to `BUILDER_SAVE` — a separate message so that embedders written
22
+ * against v1 (which read `data.uuid` from a save they assume was persisted)
23
+ * never receive a payload that was not, in fact, stored anywhere.
24
+ */
25
+ export declare const BUILDER_CONTENT = "design-templates:builder:content";
26
+ /**
27
+ * Stateless mode only (protocol v2). The unsaved-changes flag flipped. Sent so
28
+ * a host can ask the user before swapping the template out from under them —
29
+ * `HOST_LOAD` is honoured unconditionally and discards whatever was in
30
+ * progress, and without this the host has no way to know there was anything to
31
+ * lose.
32
+ */
33
+ export declare const BUILDER_DIRTY = "design-templates:builder:dirty";
34
+ export interface BuilderReadyMessage {
35
+ type: typeof BUILDER_READY;
36
+ }
37
+ export interface BuilderOpenMessage {
38
+ type: typeof BUILDER_OPEN;
39
+ }
40
+ export interface BuilderCloseMessage {
41
+ type: typeof BUILDER_CLOSE;
42
+ }
43
+ export interface BuilderSaveData {
44
+ uuid: string;
45
+ name?: string;
46
+ }
47
+ export interface BuilderSaveMessage {
48
+ type: typeof BUILDER_SAVE;
49
+ data?: BuilderSaveData;
50
+ }
51
+ /**
52
+ * `auth` and `invalid-content` are the codes the app itself sends. The widget
53
+ * adds `handshake-timeout` (no ready signal — typically blocked third-party
54
+ * cookies or a missing frame-ancestors entry), `invalid-base-url` and
55
+ * `invalid-config`.
56
+ */
57
+ export type BuilderErrorCode =
58
+ /**
59
+ * The app could not authenticate. In `secTemplate` mode this also covers a
60
+ * security-template key the Filerobot API refused to exchange for a sass key.
61
+ */
62
+ 'auth'
63
+ /** Stateless mode: the `HOST_LOAD` content could not be parsed as a template. */
64
+ | 'invalid-content' | 'handshake-timeout' | 'invalid-base-url'
65
+ /** Attributes that contradict each other, e.g. `sec-template` without `stateless`. */
66
+ | 'invalid-config' | 'unknown';
67
+ export interface BuilderErrorData {
68
+ code: BuilderErrorCode;
69
+ message?: string;
70
+ }
71
+ export interface BuilderErrorMessage {
72
+ type: typeof BUILDER_ERROR;
73
+ data: BuilderErrorData;
74
+ }
75
+ export interface BuilderContentRequestMessage {
76
+ type: typeof BUILDER_CONTENT_REQUEST;
77
+ }
78
+ export interface BuilderContentData {
79
+ /**
80
+ * The id the host supplied in `HOST_LOAD`, echoed back verbatim. Absent when
81
+ * the host sent content without one.
82
+ */
83
+ templateId?: string;
84
+ /** The edited template, serialized as `.fdt` XML. */
85
+ content: string;
86
+ /** Display name the host supplied, echoed back. */
87
+ name?: string;
88
+ /**
89
+ * `template_query` for the default render — layout, variable values and
90
+ * locale. In DAM-backed mode this is stored as file metadata; a stateless
91
+ * host must persist it alongside `content` or renders will fall back to
92
+ * whatever defaults the XML alone implies.
93
+ */
94
+ templateQuery?: string;
95
+ }
96
+ export interface BuilderContentMessage {
97
+ type: typeof BUILDER_CONTENT;
98
+ data: BuilderContentData;
99
+ }
100
+ export interface BuilderDirtyData {
101
+ /** True when the editor holds edits that have not been handed back. */
102
+ isDirty: boolean;
103
+ }
104
+ export interface BuilderDirtyMessage {
105
+ type: typeof BUILDER_DIRTY;
106
+ data: BuilderDirtyData;
107
+ }
108
+ export type BuilderMessage = BuilderReadyMessage | BuilderOpenMessage | BuilderCloseMessage | BuilderSaveMessage | BuilderErrorMessage | BuilderContentRequestMessage | BuilderContentMessage | BuilderDirtyMessage;
109
+ /**
110
+ * Stateless mode only (protocol v2). Hands the app a template to edit. Sent in
111
+ * answer to `BUILDER_CONTENT_REQUEST`, and again whenever the host swaps the
112
+ * template without remounting the iframe.
113
+ *
114
+ * Content travels by postMessage rather than a URL param because template XML
115
+ * routinely exceeds practical URL length limits.
116
+ *
117
+ * The app accepts this message only from the origin pinned as `embedOrigin`
118
+ * when the session credentials were handed over, so an unrelated framing page
119
+ * cannot inject a template into someone else's session.
120
+ */
121
+ export declare const HOST_LOAD = "design-templates:host:load";
122
+ export interface HostLoadData {
123
+ /**
124
+ * Opaque host-side identifier, echoed back on save. It is never used to
125
+ * fetch anything, so it need not be a Filerobot uuid — any string the host
126
+ * can map back to its own record works.
127
+ */
128
+ templateId?: string;
129
+ /** Template to edit, as `.fdt` XML. */
130
+ content: string;
131
+ /** Display name for the editor header. */
132
+ name?: string;
133
+ /**
134
+ * `template_query` describing the render to open on — layout and variable
135
+ * values, in the same `$key=value&$key2=value2` form the editor hands back
136
+ * in `BuilderContentData.templateQuery`.
137
+ *
138
+ * Round-trips that value: a host that stored it on save and passes it back
139
+ * here reopens the template exactly as it was left. Omitting it falls back
140
+ * to the `default=` attributes in the XML, which is a different render
141
+ * whenever the query overrode any of them.
142
+ *
143
+ * Applied as display state, not as an edit — it selects the layout and fills
144
+ * variable values without marking the document dirty, so opening a template
145
+ * and closing it again does not look like an unsaved change.
146
+ */
147
+ templateQuery?: string;
148
+ }
149
+ export interface HostLoadMessage {
150
+ type: typeof HOST_LOAD;
151
+ data: HostLoadData;
152
+ }
153
+ /**
154
+ * Stateless mode only (protocol v2). Reports whether the host managed to
155
+ * persist the content it received in `BUILDER_CONTENT`.
156
+ *
157
+ * Optional by design. The editor clears its unsaved-changes flag optimistically
158
+ * when it posts `BUILDER_CONTENT`, so a host that never acks behaves exactly as
159
+ * before. Sending `ok: false` is what buys something: the editor restores the
160
+ * dirty flag and tells the user, instead of leaving a failed write looking
161
+ * saved.
162
+ */
163
+ export declare const HOST_SAVED = "design-templates:host:saved";
164
+ export interface HostSavedData {
165
+ /** False when the host could not persist the content. */
166
+ ok: boolean;
167
+ /** Shown to the user when `ok` is false. */
168
+ message?: string;
169
+ }
170
+ export interface HostSavedMessage {
171
+ type: typeof HOST_SAVED;
172
+ data: HostSavedData;
173
+ }
174
+ export type HostMessage = HostLoadMessage | HostSavedMessage;
175
+ /**
176
+ * Query params the app's proxy middleware (`src/proxy.ts`) converts into auth
177
+ * cookies on first navigation. Names are wire format.
178
+ */
179
+ export declare const EMBED_PARAMS: {
180
+ readonly SESSION_UUID: "suuid";
181
+ readonly COMPANY_UUID: "cuuid";
182
+ readonly PROJECT_UUID: "puuid";
183
+ readonly SASS_KEY: "sassKey";
184
+ readonly FILEROBOT_TOKEN: "ftoken";
185
+ /**
186
+ * Filerobot security-template key, the alternative to a Hub session. The app
187
+ * exchanges it for a sass key itself and runs in a reduced mode — see
188
+ * `AUTH_MODES`. Sent *instead of* `sassKey` + `suuid`, never alongside them.
189
+ */
190
+ readonly SEC_TEMPLATE: "secTemplate";
191
+ readonly IFRAME: "iframe";
192
+ /** Origin of the embedding page; the app uses it as postMessage targetOrigin. */
193
+ readonly EMBED_ORIGIN: "embedOrigin";
194
+ /**
195
+ * Accent colour for the editor chrome, as `#rgb` / `#rrggbb`. The app derives
196
+ * its whole accent ramp from it. Rejected server-side if it doesn't match
197
+ * that shape — it ends up inside a stylesheet.
198
+ */
199
+ readonly BRAND_COLOR: "brandColor";
200
+ /** Colour scheme for the editor chrome: `light` | `dark` | `auto`. */
201
+ readonly THEME: "theme";
202
+ };
203
+ /** Values the `theme` param accepts. */
204
+ export type BuilderTheme = 'light' | 'dark' | 'auto';
205
+ /**
206
+ * How the embedder authenticated.
207
+ *
208
+ * - `session` — a Hub session (`suuid` + `sassKey` + `ftoken`). Full features.
209
+ * - `secTemplate` — a Filerobot security-template key (`secTemplate` +
210
+ * `ftoken`). A guest credential: no user identity and no Hub project, so the
211
+ * app accepts it on {@link EMBED_ROUTE} only, and everything that reads the
212
+ * Hub project model (metadata fields, regional variants, project branding)
213
+ * comes back empty. Rendering, fonts and asset browsing work, scoped by
214
+ * whatever the security template grants.
215
+ *
216
+ * Derived by the app from the params it received; named here so both sides use
217
+ * the same vocabulary.
218
+ */
219
+ export declare const AUTH_MODES: {
220
+ readonly SESSION: "session";
221
+ readonly SEC_TEMPLATE: "secTemplate";
222
+ };
223
+ export type AuthMode = (typeof AUTH_MODES)[keyof typeof AUTH_MODES];
224
+ /**
225
+ * Shape the app requires of `brandColor`. Hex only: the value is interpolated
226
+ * into a `:root { … }` rule, so anything that could carry CSS syntax is
227
+ * refused rather than escaped. Mirrored in the app's proxy — keep in sync.
228
+ */
229
+ export declare const BRAND_COLOR_PATTERN: RegExp;
230
+ /** Editor route for an existing template, or the new-template route. */
231
+ export declare function builderRoute(templateId?: string): string;
232
+ /**
233
+ * Stateless editor route. Takes no template id — the id is an opaque host
234
+ * value that arrives with the content over `HOST_LOAD`, not something the app
235
+ * resolves against Filerobot, so it has no place in the URL.
236
+ */
237
+ export declare const EMBED_ROUTE = "/templates/embed";
package/dist/react.cjs ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=require("react");require("./define.cjs");const h=o.forwardRef(function(y,v){const{className:U,style:b,onReady:c,onOpen:d,onClose:m,onSave:i,onError:p,onDirtyChange:f,...e}=y,l=o.useRef(null);return o.useImperativeHandle(v,()=>l.current,[]),o.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.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.templateName,e.templateQuery,e.brandColor,e.theme,e.readyTimeout]),o.useLayoutEffect(()=>{const t=l.current;if(!t)return;const u=[],a=(r,s)=>{if(!s)return;const n=(T=>s(T.detail));t.addEventListener(r,n),u.push([r,n])};if(a("ready",c),a("open",d),a("close",m),a("error",p),a("dirtychange",f),i){const r=(s=>{Promise.resolve().then(()=>i(s.detail)).then(n=>t.confirmSave(n!==!1)).catch(n=>{console.error("[sfx-template-builder] onSave failed:",n),t.confirmSave(!1)})});t.addEventListener("save",r),u.push(["save",r])}return()=>{for(const[r,s]of u)t.removeEventListener(r,s)}},[c,d,m,i,p,f]),o.createElement("sfx-template-builder",{ref:l,class:U,style:b})});exports.TemplateBuilder=h;
2
+ //# sourceMappingURL=react.cjs.map
@@ -0,0 +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 /** 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 // 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.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":"iIA8GO,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,GAI3BK,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,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"}
@@ -0,0 +1,95 @@
1
+ import { type CSSProperties } from 'react';
2
+ import './define';
3
+ import type { SfxTemplateBuilder } from './template-builder';
4
+ import type { TemplateBuilderSaveDetail } from './template-builder';
5
+ import type { BuilderDirtyData, BuilderErrorData, BuilderTheme } from './protocol';
6
+ /**
7
+ * Hub session — the full-featured credential.
8
+ */
9
+ export interface TemplateBuilderSessionAuth {
10
+ sassKey: string;
11
+ sessionUuid: string;
12
+ companyUuid?: string;
13
+ projectUuid?: string;
14
+ secTemplate?: never;
15
+ }
16
+ /**
17
+ * Filerobot security template — a guest credential for hosts that have no Hub
18
+ * session to hand over. `stateless` is required rather than merely implied:
19
+ * the app takes a security template on the stateless embed route only, so the
20
+ * combination is a compile-time error instead of a runtime one.
21
+ *
22
+ * `companyUuid` / `projectUuid` are absent by design — they name a Hub project
23
+ * that cannot be looked up without a session.
24
+ */
25
+ export interface TemplateBuilderSecTemplateAuth {
26
+ secTemplate: string;
27
+ stateless: true;
28
+ sassKey?: never;
29
+ sessionUuid?: never;
30
+ companyUuid?: never;
31
+ projectUuid?: never;
32
+ }
33
+ export interface TemplateBuilderBaseProps {
34
+ baseUrl: string;
35
+ token: string;
36
+ templateId?: string;
37
+ mode?: 'inline' | 'modal';
38
+ /** Hand the template in and take it back out instead of using the DAM. */
39
+ stateless?: boolean;
40
+ /** Stateless mode: the template to edit, as `.fdt` XML. */
41
+ content?: string;
42
+ /** Stateless mode: display name for the editor header. */
43
+ templateName?: string;
44
+ /**
45
+ * Stateless mode: the `template_query` to open on — the value handed back on
46
+ * save. Reopens the template on the same layout and variable values; empty
47
+ * falls back to the XML's own `default=` attributes.
48
+ */
49
+ templateQuery?: string;
50
+ /** Accent colour for the editor chrome, as `#rgb` / `#rrggbb`. */
51
+ brandColor?: string;
52
+ /** Colour scheme for the editor chrome. */
53
+ theme?: BuilderTheme;
54
+ readyTimeout?: number;
55
+ className?: string;
56
+ style?: CSSProperties;
57
+ onReady?: () => void;
58
+ onOpen?: () => void;
59
+ onClose?: () => void;
60
+ /**
61
+ * Fired on save. In stateless mode the detail carries the edited `content`
62
+ * for you to persist; otherwise it reports the uuid the app uploaded to.
63
+ *
64
+ * In stateless mode the outcome is reported back to the editor: return (or
65
+ * resolve to) `false`, or throw, and the editor restores its unsaved-changes
66
+ * flag and tells the user the save failed. Anything else counts as persisted.
67
+ */
68
+ onSave?: (data: TemplateBuilderSaveDetail) => void | boolean | Promise<void | boolean>;
69
+ onError?: (data: BuilderErrorData) => void;
70
+ /**
71
+ * Stateless mode: unsaved-changes flag changed. Use it to prompt before
72
+ * swapping `content`, which discards in-progress edits.
73
+ */
74
+ onDirtyChange?: (data: BuilderDirtyData) => void;
75
+ }
76
+ export type TemplateBuilderProps = TemplateBuilderBaseProps & (TemplateBuilderSessionAuth | TemplateBuilderSecTemplateAuth);
77
+ /**
78
+ * React wrapper around `<sfx-template-builder>`. Props are assigned as
79
+ * element properties via ref (works on React 18 and 19 alike); callbacks
80
+ * subscribe to the element's CustomEvents.
81
+ *
82
+ * Forwards a ref to the underlying element, which is the only way to reach the
83
+ * imperative API — `open()` in particular, without which `mode="modal"` can
84
+ * never be shown:
85
+ *
86
+ * ```tsx
87
+ * const builder = useRef<SfxTemplateBuilder>(null)
88
+ * <TemplateBuilder ref={builder} mode="modal" … />
89
+ * <button onClick={() => builder.current?.open('tpl-1')}>Edit</button>
90
+ * ```
91
+ *
92
+ * `forwardRef` rather than a plain `ref` prop: React 19 accepts the latter for
93
+ * function components, React 18 does not, and both are supported peers.
94
+ */
95
+ export declare const TemplateBuilder: import("react").ForwardRefExoticComponent<TemplateBuilderProps & import("react").RefAttributes<SfxTemplateBuilder>>;
package/dist/react.js ADDED
@@ -0,0 +1,59 @@
1
+ import { forwardRef as T, useRef as E, useImperativeHandle as C, useLayoutEffect as f, createElement as I } from "react";
2
+ import "./define.js";
3
+ const g = T(function(y, v) {
4
+ const {
5
+ className: U,
6
+ style: b,
7
+ onReady: m,
8
+ onOpen: c,
9
+ onClose: d,
10
+ onSave: l,
11
+ onError: u,
12
+ onDirtyChange: p,
13
+ ...e
14
+ } = y, a = E(null);
15
+ return C(v, () => a.current, []), f(() => {
16
+ const t = a.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.content = e.content ?? "", e.readyTimeout !== void 0 && (t.readyTimeout = e.readyTimeout));
18
+ }, [
19
+ e.baseUrl,
20
+ e.token,
21
+ e.sassKey,
22
+ e.sessionUuid,
23
+ e.secTemplate,
24
+ e.companyUuid,
25
+ e.projectUuid,
26
+ e.templateId,
27
+ e.mode,
28
+ e.stateless,
29
+ e.content,
30
+ e.templateName,
31
+ e.templateQuery,
32
+ e.brandColor,
33
+ e.theme,
34
+ e.readyTimeout
35
+ ]), f(() => {
36
+ const t = a.current;
37
+ if (!t) return;
38
+ const i = [], n = (s, r) => {
39
+ if (!r) return;
40
+ const o = ((h) => r(h.detail));
41
+ t.addEventListener(s, o), i.push([s, o]);
42
+ };
43
+ if (n("ready", m), n("open", c), n("close", d), n("error", u), n("dirtychange", p), l) {
44
+ const s = ((r) => {
45
+ Promise.resolve().then(() => l(r.detail)).then((o) => t.confirmSave(o !== !1)).catch((o) => {
46
+ console.error("[sfx-template-builder] onSave failed:", o), t.confirmSave(!1);
47
+ });
48
+ });
49
+ t.addEventListener("save", s), i.push(["save", s]);
50
+ }
51
+ return () => {
52
+ for (const [s, r] of i) t.removeEventListener(s, r);
53
+ };
54
+ }, [m, c, d, l, u, p]), I("sfx-template-builder", { ref: a, class: U, style: b });
55
+ });
56
+ export {
57
+ g as TemplateBuilder
58
+ };
59
+ //# sourceMappingURL=react.js.map
@@ -0,0 +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 /** 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 // 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.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":";;AA8GO,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,SAAAC,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,IAI3BK,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,EAAA,CACR,GAODI,EAAgB,MAAM;AACpB,UAAMC,IAAKJ,EAAI;AACf,QAAI,CAACI,EAAI;AACT,UAAMC,IAAuC,CAAA,GACvCC,IAAK,CAACC,GAAcC,MAAsC;AAC9D,UAAI,CAACA,EAAS;AACd,YAAMC,KAAY,CAACC,MAAmBF,EAAQE,EAAE,MAAe;AAC/D,MAAAN,EAAG,iBAAiBG,GAAME,CAAQ,GAClCJ,EAAK,KAAK,CAACE,GAAME,CAAQ,CAAC;AAAA,IAC5B;AAUA,QATAH,EAAG,SAASb,CAAO,GACnBa,EAAG,QAAQZ,CAAM,GACjBY,EAAG,SAASX,CAAO,GACnBW,EAAG,SAAST,CAAO,GACnBS,EAAG,eAAeR,CAAa,GAK3BF,GAAQ;AACV,YAAMa,KAAY,CAACC,MAA8C;AAG/D,gBAAQ,QAAA,EACL,KAAK,MAAMd,EAAOc,EAAE,MAAM,CAAC,EAC3B,KAAK,CAACC,MAAWP,EAAG,YAAYO,MAAW,EAAK,CAAC,EACjD,MAAM,CAACC,MAAQ;AAGd,kBAAQ,MAAM,yCAAyCA,CAAG,GAC1DR,EAAG,YAAY,EAAK;AAAA,QACtB,CAAC;AAAA,MACL;AACA,MAAAA,EAAG,iBAAiB,QAAQK,CAAQ,GACpCJ,EAAK,KAAK,CAAC,QAAQI,CAAQ,CAAC;AAAA,IAC9B;AAEA,WAAO,MAAM;AACX,iBAAW,CAACF,GAAME,CAAQ,KAAKJ,EAAM,CAAAD,EAAG,oBAAoBG,GAAME,CAAQ;AAAA,IAC5E;AAAA,EACF,GAAG,CAAChB,GAASC,GAAQC,GAASC,GAAQC,GAASC,CAAa,CAAC,GAGtDe,EAAc,wBAAwB,EAAE,KAAAb,GAAK,OAAOT,GAAW,OAAAC,GAAO;AAC/E,CAAC;"}
@@ -0,0 +1,52 @@
1
+ "use strict";const p=require("lit"),i=require("lit/decorators.js"),g=2,m="design-templates:builder:ready",l="design-templates:builder:open",_="design-templates:builder:close",y="design-templates:builder:save",f="design-templates:builder:error",E="design-templates:builder:content-request",b="design-templates:builder:content",R="design-templates:builder:dirty",T="design-templates:host:load",U="design-templates:host:saved",n={SESSION_UUID:"suuid",COMPANY_UUID:"cuuid",PROJECT_UUID:"puuid",SASS_KEY:"sassKey",FILEROBOT_TOKEN:"ftoken",SEC_TEMPLATE:"secTemplate",IFRAME:"iframe",EMBED_ORIGIN:"embedOrigin",BRAND_COLOR:"brandColor",THEME:"theme"},S={SESSION:"session",SEC_TEMPLATE:"secTemplate"},I=/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;function O(h){return h?`/templates/${encodeURIComponent(h)}/edit`:"/templates/new"}const D="/templates/embed";var v=Object.defineProperty,r=(h,t,e,a)=>{for(var o=void 0,d=h.length-1,u;d>=0;d--)(u=h[d])&&(o=u(t,e,o)||o);return o&&v(t,e,o),o};const c=class c extends p.LitElement{constructor(){super(...arguments),this.baseUrl="",this.token="",this.sassKey="",this.sessionUuid="",this.secTemplate="",this.companyUuid="",this.projectUuid="",this.templateId="",this.mode="inline",this.stateless=!1,this.content="",this.templateName="",this.templateQuery="",this.brandColor="",this.theme="",this.readyTimeout=2e4,this._status="idle",this._open=!1,this._src="",this._contentRequested=!1,this._reportedStatelessRequired=!1,this._isDirty=!1,this._autoOpenDecided=!1,this._onMessage=t=>{if(!this._open||!t.origin||t.origin!==this._appOrigin)return;const e=this.shadowRoot?.querySelector("iframe");if(!e||t.source!==e.contentWindow)return;const a=t.data;if(!(!a||typeof a.type!="string"))switch(a.type){case m:case l:this._clearHandshakeTimer(),this._status!=="ready"&&(this._status="ready",this._emit("ready")),a.type===l&&this._emit("open");break;case y:this._emit("save",a.data);break;case E:this._contentRequested=!0,this._sentKey=void 0,this._maybeSendContent();break;case b:{const o=a.data;this._sentKey=this._contentKey(o.content),this._emit("save",o);break}case R:{const o=a.data;this._isDirty=!!o?.isDirty,this._emit("dirtychange",{isDirty:this._isDirty});break}case _:this._emit("close"),this.mode==="modal"&&(this._open=!1);break;case f:this._fail(a.data??{code:"unknown"});break}}}get status(){return this._status}get isDirty(){return this._isDirty}open(t){t!==void 0&&(this.templateId=t),this._open=!0}close(){this._open=!1}load({content:t,templateId:e,name:a,templateQuery:o}){e!==void 0&&(this.templateId=e),a!==void 0&&(this.templateName=a),o!==void 0&&(this.templateQuery=o),this.content=t,this._open=!0}confirmSave(t,e){this.stateless&&this._postToApp({type:U,data:{ok:t,message:e}})}connectedCallback(){super.connectedCallback(),window.addEventListener("message",this._onMessage)}disconnectedCallback(){super.disconnectedCallback(),window.removeEventListener("message",this._onMessage),this._clearHandshakeTimer()}willUpdate(t){super.willUpdate(t),this._autoOpenDecided||(this._autoOpenDecided=!0,this.mode==="inline"&&(this._open=!0));const e=this._computeSrc();e!==this._src&&(this._src=e,this._status=e?"loading":"idle")}updated(t){t.has("_src")&&(this._clearHandshakeTimer(),this._contentRequested=!1,this._sentKey=void 0,this._isDirty&&(this._isDirty=!1,this._emit("dirtychange",{isDirty:!1})),this._src&&this._startHandshakeTimer()),(t.has("content")||t.has("templateId")||t.has("templateName")||t.has("templateQuery"))&&this._maybeSendContent()}render(){const t=this._src?p.html`<iframe
2
+ part="iframe"
3
+ title="Template builder"
4
+ src=${this._src}
5
+ allow="clipboard-read; clipboard-write"
6
+ ></iframe>`:p.nothing,e=this._status==="loading"?p.html`<div class="spinner" part="spinner"></div>`:p.nothing;return this.mode==="modal"?this._open?p.html`<div class="overlay" part="overlay">
7
+ <div class="stage">${t}${e}</div>
8
+ </div>`:p.nothing:p.html`${t}${e}`}_computeSrc(){if(!this._open||!this.baseUrl||!this.token)return"";if(this.secTemplate){if(!this.stateless)return this._reportedStatelessRequired||(this._reportedStatelessRequired=!0,queueMicrotask(()=>this._fail({code:"invalid-config",message:"sec-template requires stateless mode — the app accepts a security template on the stateless embed route only."}))),"";this._reportedStatelessRequired=!1}else if(!this.sassKey||!this.sessionUuid)return"";let t;try{const e=this.stateless?D:O(this.templateId||void 0),a=this.baseUrl.endsWith("/")?this.baseUrl:`${this.baseUrl}/`;t=new URL(e.replace(/^\//,""),a)}catch{return this._reportedBadBaseUrl!==this.baseUrl&&(this._reportedBadBaseUrl=this.baseUrl,queueMicrotask(()=>this._fail({code:"invalid-base-url",message:`base-url is not a valid URL: ${this.baseUrl}`}))),""}return this._reportedBadBaseUrl=void 0,t.searchParams.set(n.FILEROBOT_TOKEN,this.token),this.secTemplate?t.searchParams.set(n.SEC_TEMPLATE,this.secTemplate):(t.searchParams.set(n.SASS_KEY,this.sassKey),t.searchParams.set(n.SESSION_UUID,this.sessionUuid),this.companyUuid&&t.searchParams.set(n.COMPANY_UUID,this.companyUuid),this.projectUuid&&t.searchParams.set(n.PROJECT_UUID,this.projectUuid)),this.brandColor&&t.searchParams.set(n.BRAND_COLOR,this.brandColor),this.theme&&t.searchParams.set(n.THEME,this.theme),t.searchParams.set(n.IFRAME,"1"),t.searchParams.set(n.EMBED_ORIGIN,window.location.origin),t.toString()}get _appOrigin(){try{return new URL(this.baseUrl).origin}catch{return null}}_maybeSendContent(){if(!this.stateless||!this._contentRequested||!this.content)return;const t={templateId:this.templateId||void 0,content:this.content,name:this.templateName||void 0,templateQuery:this.templateQuery||void 0},e=this._contentKey(this.content);e!==this._sentKey&&this._postToApp({type:T,data:t})&&(this._sentKey=e)}_contentKey(t){return JSON.stringify({templateId:this.templateId||void 0,content:t,name:this.templateName||void 0,templateQuery:this.templateQuery||void 0})}_postToApp(t){const e=this.shadowRoot?.querySelector("iframe")?.contentWindow,a=this._appOrigin;return!e||!a?!1:(e.postMessage(t,a),!0)}_startHandshakeTimer(){this.readyTimeout<=0||(this._handshakeTimer=window.setTimeout(()=>{this._fail({code:"handshake-timeout",message:`No ready signal from ${this.baseUrl} within ${this.readyTimeout}ms. Check that this origin is in the app's frame-ancestors allowlist and that third-party cookies are not blocked.`})},this.readyTimeout))}_clearHandshakeTimer(){this._handshakeTimer!==void 0&&(window.clearTimeout(this._handshakeTimer),this._handshakeTimer=void 0)}_fail(t){this._clearHandshakeTimer(),this._status="error",this._emit("error",t)}_emit(t,e){this.dispatchEvent(new CustomEvent(t,{detail:e,bubbles:!0,composed:!0}))}};c.styles=p.css`
9
+ :host {
10
+ display: block;
11
+ position: relative;
12
+ }
13
+ :host([mode='modal']) {
14
+ display: contents;
15
+ }
16
+ .overlay {
17
+ position: fixed;
18
+ inset: 0;
19
+ z-index: 2147483000;
20
+ background: rgba(0, 0, 0, 0.55);
21
+ display: flex;
22
+ }
23
+ .stage {
24
+ position: relative;
25
+ flex: 1;
26
+ display: flex;
27
+ }
28
+ iframe {
29
+ border: 0;
30
+ flex: 1;
31
+ width: 100%;
32
+ height: 100%;
33
+ }
34
+ .spinner {
35
+ position: absolute;
36
+ inset: 0;
37
+ margin: auto;
38
+ width: 32px;
39
+ height: 32px;
40
+ border: 3px solid rgba(128, 128, 128, 0.3);
41
+ border-top-color: currentColor;
42
+ border-radius: 50%;
43
+ animation: sfx-tb-spin 0.8s linear infinite;
44
+ pointer-events: none;
45
+ }
46
+ @keyframes sfx-tb-spin {
47
+ to {
48
+ transform: rotate(360deg);
49
+ }
50
+ }
51
+ `;let s=c;r([i.property({attribute:"base-url"})],s.prototype,"baseUrl");r([i.property()],s.prototype,"token");r([i.property({attribute:"sass-key"})],s.prototype,"sassKey");r([i.property({attribute:"session-uuid"})],s.prototype,"sessionUuid");r([i.property({attribute:"sec-template"})],s.prototype,"secTemplate");r([i.property({attribute:"company-uuid"})],s.prototype,"companyUuid");r([i.property({attribute:"project-uuid"})],s.prototype,"projectUuid");r([i.property({attribute:"template-id"})],s.prototype,"templateId");r([i.property({reflect:!0})],s.prototype,"mode");r([i.property({type:Boolean,reflect:!0})],s.prototype,"stateless");r([i.property({attribute:!1})],s.prototype,"content");r([i.property({attribute:"template-name"})],s.prototype,"templateName");r([i.property({attribute:"template-query"})],s.prototype,"templateQuery");r([i.property({attribute:"brand-color"})],s.prototype,"brandColor");r([i.property()],s.prototype,"theme");r([i.property({type:Number,attribute:"ready-timeout"})],s.prototype,"readyTimeout");r([i.state()],s.prototype,"_status");r([i.state()],s.prototype,"_open");r([i.state()],s.prototype,"_src");r([i.state()],s.prototype,"_isDirty");exports.AUTH_MODES=S;exports.BRAND_COLOR_PATTERN=I;exports.BUILDER_CLOSE=_;exports.BUILDER_CONTENT=b;exports.BUILDER_CONTENT_REQUEST=E;exports.BUILDER_DIRTY=R;exports.BUILDER_ERROR=f;exports.BUILDER_OPEN=l;exports.BUILDER_READY=m;exports.BUILDER_SAVE=y;exports.EMBED_PARAMS=n;exports.EMBED_ROUTE=D;exports.HOST_LOAD=T;exports.HOST_SAVED=U;exports.PROTOCOL_VERSION=g;exports.SfxTemplateBuilder=s;exports.builderRoute=O;
52
+ //# sourceMappingURL=template-builder-CSyPZni9.cjs.map