@01.software/sdk 0.5.2 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/auth.d.cts +1 -1
  2. package/dist/auth.d.ts +1 -1
  3. package/dist/{const-Cy_NaRCl.d.cts → const-C9I6r5Wa.d.cts} +1 -1
  4. package/dist/{const-C6vryj3j.d.ts → const-JbuUTzeh.d.ts} +1 -1
  5. package/dist/index.d.cts +6 -6
  6. package/dist/index.d.ts +6 -6
  7. package/dist/{payload-types-JCbsSVeL.d.cts → payload-types-D8fN_vZR.d.cts} +5 -1
  8. package/dist/{payload-types-JCbsSVeL.d.ts → payload-types-D8fN_vZR.d.ts} +5 -1
  9. package/dist/realtime.d.cts +2 -2
  10. package/dist/realtime.d.ts +2 -2
  11. package/dist/{server-B80o7igg.d.cts → server-StNHlSjW.d.cts} +5 -1
  12. package/dist/{server-B80o7igg.d.ts → server-StNHlSjW.d.ts} +5 -1
  13. package/dist/ui/code-block.cjs +5 -1
  14. package/dist/ui/code-block.cjs.map +1 -1
  15. package/dist/ui/code-block.js +5 -1
  16. package/dist/ui/code-block.js.map +1 -1
  17. package/dist/ui/flow/server.cjs +101 -34
  18. package/dist/ui/flow/server.cjs.map +1 -1
  19. package/dist/ui/flow/server.d.cts +1 -1
  20. package/dist/ui/flow/server.d.ts +1 -1
  21. package/dist/ui/flow/server.js +101 -34
  22. package/dist/ui/flow/server.js.map +1 -1
  23. package/dist/ui/flow.cjs +718 -180
  24. package/dist/ui/flow.cjs.map +1 -1
  25. package/dist/ui/flow.d.cts +77 -11
  26. package/dist/ui/flow.d.ts +77 -11
  27. package/dist/ui/flow.js +705 -167
  28. package/dist/ui/flow.js.map +1 -1
  29. package/dist/ui/form.d.cts +1 -1
  30. package/dist/ui/form.d.ts +1 -1
  31. package/dist/ui/video.d.cts +1 -1
  32. package/dist/ui/video.d.ts +1 -1
  33. package/dist/{webhook-Dd_7Qgaa.d.ts → webhook-BkwMrrL1.d.ts} +2 -2
  34. package/dist/{webhook-CDu_s44-.d.cts → webhook-Dbx-pRib.d.cts} +2 -2
  35. package/dist/webhook.d.cts +3 -3
  36. package/dist/webhook.d.ts +3 -3
  37. package/package.json +3 -1
package/dist/ui/flow.js CHANGED
@@ -18,6 +18,18 @@ var __spreadValues = (a, b) => {
18
18
  return a;
19
19
  };
20
20
  var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
21
+ var __objRest = (source, exclude) => {
22
+ var target = {};
23
+ for (var prop in source)
24
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
25
+ target[prop] = source[prop];
26
+ if (source != null && __getOwnPropSymbols)
27
+ for (var prop of __getOwnPropSymbols(source)) {
28
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
29
+ target[prop] = source[prop];
30
+ }
31
+ return target;
32
+ };
21
33
  var __async = (__this, __arguments, generator) => {
22
34
  return new Promise((resolve, reject) => {
23
35
  var fulfilled = (value) => {
@@ -102,6 +114,43 @@ function collectionKeys(collection) {
102
114
  };
103
115
  }
104
116
 
117
+ // src/ui/Flow/query-options.ts
118
+ function flowQueryOptions(client, slug, id) {
119
+ var _a;
120
+ const identifier = (_a = id != null ? id : slug) != null ? _a : "";
121
+ return {
122
+ queryKey: collectionKeys("flows").detail(identifier),
123
+ queryFn: () => __async(null, null, function* () {
124
+ if (id) return client.from("flows").findById(id);
125
+ const result = yield client.from("flows").find({
126
+ where: { slug: { equals: slug } },
127
+ limit: 1
128
+ });
129
+ const doc = result.docs[0];
130
+ if (!doc) throw new Error(`Flow not found: ${slug}`);
131
+ return doc;
132
+ })
133
+ };
134
+ }
135
+ function nodeTypesQueryOptions(client) {
136
+ return {
137
+ queryKey: collectionKeys("flow-node-types").lists(),
138
+ queryFn: () => __async(null, null, function* () {
139
+ const result = yield client.from("flow-node-types").find({ limit: 0 });
140
+ return result.docs;
141
+ })
142
+ };
143
+ }
144
+ function edgeTypesQueryOptions(client) {
145
+ return {
146
+ queryKey: collectionKeys("flow-edge-types").lists(),
147
+ queryFn: () => __async(null, null, function* () {
148
+ const result = yield client.from("flow-edge-types").find({ limit: 0 });
149
+ return result.docs;
150
+ })
151
+ };
152
+ }
153
+
105
154
  // src/ui/Flow/useFlow.ts
106
155
  function toNodeTypeDef(doc) {
107
156
  var _a, _b, _c, _d, _e, _f;
@@ -134,49 +183,25 @@ function toEdgeTypeDef(doc) {
134
183
  };
135
184
  }
136
185
  function useFlow(options) {
137
- var _a, _b, _c;
186
+ var _a, _b;
138
187
  const { client, slug, id, enabled = true } = options;
139
188
  const hasIdentifier = !!(slug || id);
140
- const identifier = (_a = id != null ? id : slug) != null ? _a : "";
141
189
  const flowQuery = useQuery(
142
- {
143
- queryKey: collectionKeys("flows").detail(identifier),
144
- queryFn: () => __async(null, null, function* () {
145
- if (id) {
146
- return client.from("flows").findById(id);
147
- }
148
- const result = yield client.from("flows").find({
149
- where: { slug: { equals: slug } },
150
- limit: 1
151
- });
152
- const doc = result.docs[0];
153
- if (!doc) throw new Error(`Flow not found: ${slug}`);
154
- return doc;
155
- }),
190
+ __spreadProps(__spreadValues({}, flowQueryOptions(client, slug, id)), {
156
191
  enabled: enabled && hasIdentifier
157
- },
192
+ }),
158
193
  client.queryClient
159
194
  );
160
195
  const nodeTypesQuery = useQuery(
161
- {
162
- queryKey: collectionKeys("flow-node-types").lists(),
163
- queryFn: () => __async(null, null, function* () {
164
- const result = yield client.from("flow-node-types").find({ limit: 100 });
165
- return result.docs;
166
- }),
196
+ __spreadProps(__spreadValues({}, nodeTypesQueryOptions(client)), {
167
197
  enabled
168
- },
198
+ }),
169
199
  client.queryClient
170
200
  );
171
201
  const edgeTypesQuery = useQuery(
172
- {
173
- queryKey: collectionKeys("flow-edge-types").lists(),
174
- queryFn: () => __async(null, null, function* () {
175
- const result = yield client.from("flow-edge-types").find({ limit: 100 });
176
- return result.docs;
177
- }),
202
+ __spreadProps(__spreadValues({}, edgeTypesQueryOptions(client)), {
178
203
  enabled
179
- },
204
+ }),
180
205
  client.queryClient
181
206
  );
182
207
  const nodeTypeDefs = useMemo(() => {
@@ -201,44 +226,21 @@ function useFlow(options) {
201
226
  edgeTypeDefs,
202
227
  flow,
203
228
  isLoading: flowQuery.isLoading || nodeTypesQuery.isLoading || edgeTypesQuery.isLoading,
204
- error: (_c = (_b = flowQuery.error) != null ? _b : nodeTypesQuery.error) != null ? _c : edgeTypesQuery.error
229
+ error: (_b = (_a = flowQuery.error) != null ? _a : nodeTypesQuery.error) != null ? _b : edgeTypesQuery.error
205
230
  };
206
231
  }
207
232
 
208
233
  // src/ui/Flow/prefetchFlow.ts
209
234
  function prefetchFlow(options) {
210
235
  return __async(this, null, function* () {
211
- var _a;
212
236
  const { client, slug, id } = options;
213
- const identifier = (_a = id != null ? id : slug) != null ? _a : "";
237
+ if (!slug && !id) {
238
+ throw new Error("prefetchFlow requires either slug or id");
239
+ }
214
240
  yield Promise.all([
215
- client.queryClient.prefetchQuery({
216
- queryKey: collectionKeys("flows").detail(identifier),
217
- queryFn: () => __async(null, null, function* () {
218
- if (id) return client.from("flows").findById(id);
219
- const result = yield client.from("flows").find({
220
- where: { slug: { equals: slug } },
221
- limit: 1
222
- });
223
- const doc = result.docs[0];
224
- if (!doc) throw new Error(`Flow not found: ${slug}`);
225
- return doc;
226
- })
227
- }),
228
- client.queryClient.prefetchQuery({
229
- queryKey: collectionKeys("flow-node-types").lists(),
230
- queryFn: () => __async(null, null, function* () {
231
- const result = yield client.from("flow-node-types").find({ limit: 100 });
232
- return result.docs;
233
- })
234
- }),
235
- client.queryClient.prefetchQuery({
236
- queryKey: collectionKeys("flow-edge-types").lists(),
237
- queryFn: () => __async(null, null, function* () {
238
- const result = yield client.from("flow-edge-types").find({ limit: 100 });
239
- return result.docs;
240
- })
241
- })
241
+ client.queryClient.prefetchQuery(flowQueryOptions(client, slug, id)),
242
+ client.queryClient.prefetchQuery(nodeTypesQueryOptions(client)),
243
+ client.queryClient.prefetchQuery(edgeTypesQueryOptions(client))
242
244
  ]);
243
245
  });
244
246
  }
@@ -297,15 +299,28 @@ function getAbsolutePosition(node, nodeMap) {
297
299
  return { x, y };
298
300
  }
299
301
  function collectDescendants(nodes, rootId) {
302
+ const childrenMap = /* @__PURE__ */ new Map();
303
+ for (const n of nodes) {
304
+ if (n.parentId) {
305
+ let siblings = childrenMap.get(n.parentId);
306
+ if (!siblings) {
307
+ siblings = [];
308
+ childrenMap.set(n.parentId, siblings);
309
+ }
310
+ siblings.push(n.id);
311
+ }
312
+ }
300
313
  const result = /* @__PURE__ */ new Set([rootId]);
301
314
  const queue = [rootId];
302
315
  let i = 0;
303
316
  while (i < queue.length) {
304
- const current = queue[i++];
305
- for (const n of nodes) {
306
- if (n.parentId === current && !result.has(n.id)) {
307
- result.add(n.id);
308
- queue.push(n.id);
317
+ const children = childrenMap.get(queue[i++]);
318
+ if (children) {
319
+ for (const childId of children) {
320
+ if (!result.has(childId)) {
321
+ result.add(childId);
322
+ queue.push(childId);
323
+ }
309
324
  }
310
325
  }
311
326
  }
@@ -380,16 +395,75 @@ function getFrameData(data, frameId) {
380
395
  // src/ui/Flow/template-compiler.ts
381
396
  import React from "react";
382
397
  import { transform } from "sucrase";
383
- var MAX_CACHE_SIZE = 100;
384
- var componentCache = /* @__PURE__ */ new Map();
385
- function hashCode(str) {
386
- let hash = 0;
387
- for (let i = 0; i < str.length; i++) {
388
- const char = str.charCodeAt(i);
389
- hash = (hash << 5) - hash + char | 0;
390
- }
391
- return hash.toString(36);
398
+
399
+ // src/ui/Flow/quickjs-loader.ts
400
+ import { useSyncExternalStore } from "react";
401
+ var _state = { status: "idle", module: null };
402
+ var _listeners = /* @__PURE__ */ new Set();
403
+ function _notify() {
404
+ _listeners.forEach((fn) => fn());
405
+ }
406
+ function subscribeQuickJS(cb) {
407
+ _listeners.add(cb);
408
+ return () => _listeners.delete(cb);
409
+ }
410
+ function getQuickJSSnapshot() {
411
+ return _state.module;
412
+ }
413
+ function ensureQuickJSLoaded() {
414
+ if (_state.status !== "idle") return;
415
+ _state = { status: "loading", module: null };
416
+ import("quickjs-emscripten").then(
417
+ (mod) => mod.getQuickJS()
418
+ ).then((module) => {
419
+ _state = { status: "ready", module };
420
+ _notify();
421
+ }).catch(() => {
422
+ _state = { status: "failed", module: null };
423
+ });
424
+ }
425
+ function useQuickJS() {
426
+ return useSyncExternalStore(subscribeQuickJS, getQuickJSSnapshot, () => null);
392
427
  }
428
+
429
+ // src/ui/Flow/template-compiler.ts
430
+ var MAX_CACHE_SIZE = 100;
431
+ var MAX_MATERIALIZE_DEPTH = 20;
432
+ var ALLOWED_ELEMENTS = /* @__PURE__ */ new Set([
433
+ "div",
434
+ "span",
435
+ "p",
436
+ "h1",
437
+ "h2",
438
+ "h3",
439
+ "h4",
440
+ "h5",
441
+ "h6",
442
+ "ul",
443
+ "ol",
444
+ "li",
445
+ "strong",
446
+ "em",
447
+ "b",
448
+ "i",
449
+ "br",
450
+ "img",
451
+ "figure",
452
+ "section",
453
+ "table",
454
+ "thead",
455
+ "tbody",
456
+ "tr",
457
+ "th",
458
+ "td",
459
+ "pre",
460
+ "code",
461
+ "svg",
462
+ "path",
463
+ "g",
464
+ "circle",
465
+ "rect"
466
+ ]);
393
467
  var BLOCKED_PATTERNS = [
394
468
  /\bdocument\s*\./,
395
469
  /\bwindow\s*\./,
@@ -409,79 +483,212 @@ var BLOCKED_PATTERNS = [
409
483
  /\bsetTimeout\s*\(/,
410
484
  /\bsetInterval\s*\(/,
411
485
  /\bsetImmediate\s*\(/,
412
- /\brequire\s*\(/
486
+ /\brequire\s*\(/,
487
+ /\bself\b/,
488
+ /\bconstructor\s*\.\s*constructor/,
489
+ /\bReflect\b/,
490
+ /\bProxy\b/,
491
+ /\b__proto__\b/
413
492
  ];
493
+ var VM_SETUP = `
494
+ var React = {
495
+ createElement: function(type, props) {
496
+ var args = Array.prototype.slice.call(arguments, 2);
497
+ return { $$t: 'el', type: String(type), props: props || {}, ch: args };
498
+ },
499
+ Fragment: '__frag__'
500
+ };
501
+ var exports = {};
502
+ var module = { exports: exports };
503
+ var window = undefined;
504
+ var document = undefined;
505
+ var globalThis = undefined;
506
+ var self = undefined;
507
+ var setTimeout = undefined;
508
+ var setInterval = undefined;
509
+ var setImmediate = undefined;
510
+ var fetch = undefined;
511
+ var XMLHttpRequest = undefined;
512
+ var navigator = undefined;
513
+ var location = undefined;
514
+ var localStorage = undefined;
515
+ var sessionStorage = undefined;
516
+ var cookie = undefined;
517
+ var postMessage = undefined;
518
+ `;
519
+ var rendererCache = /* @__PURE__ */ new Map();
520
+ function hashCode(str) {
521
+ let hash = 0;
522
+ for (let i = 0; i < str.length; i++) {
523
+ const char = str.charCodeAt(i);
524
+ hash = (hash << 5) - hash + char | 0;
525
+ }
526
+ return hash.toString(36);
527
+ }
414
528
  function validateTemplateCode(code) {
415
529
  return !BLOCKED_PATTERNS.some((pattern) => pattern.test(code));
416
530
  }
417
- function compileTemplate(code, slug) {
418
- const cacheKey = `${slug}:${hashCode(code)}`;
419
- if (componentCache.has(cacheKey)) {
420
- const cached = componentCache.get(cacheKey);
421
- componentCache.delete(cacheKey);
422
- componentCache.set(cacheKey, cached);
423
- return cached;
531
+ var SAFE_DATA_URI_PREFIXES = [
532
+ "data:image/png;",
533
+ "data:image/jpeg;",
534
+ "data:image/gif;",
535
+ "data:image/webp;",
536
+ "data:image/avif;"
537
+ ];
538
+ function isSafeUrl(value) {
539
+ if (typeof value !== "string") return false;
540
+ if (value.startsWith("/") || value.startsWith("./") || value.startsWith("../")) return true;
541
+ if (SAFE_DATA_URI_PREFIXES.some((p) => value.startsWith(p))) return true;
542
+ try {
543
+ const url = new URL(value);
544
+ return url.protocol === "http:" || url.protocol === "https:";
545
+ } catch (e) {
546
+ return false;
424
547
  }
425
- if (!validateTemplateCode(code)) {
426
- console.warn(`[flow] Template "${slug}" contains blocked patterns`);
427
- return null;
548
+ }
549
+ var BLOCKED_STYLE_PROPS = /* @__PURE__ */ new Set([
550
+ "backgroundImage",
551
+ "background",
552
+ "listStyleImage",
553
+ "content",
554
+ "borderImage",
555
+ "borderImageSource",
556
+ "maskImage",
557
+ "mask",
558
+ "filter",
559
+ "cursor"
560
+ ]);
561
+ function sanitizeStyle(style) {
562
+ if (!style || typeof style !== "object" || Array.isArray(style)) return {};
563
+ const safe = {};
564
+ for (const [k, v] of Object.entries(style)) {
565
+ if (BLOCKED_STYLE_PROPS.has(k)) continue;
566
+ if (typeof v === "string" && /url\s*\(/i.test(v)) continue;
567
+ if (k === "position" && typeof v === "string" && /fixed|absolute/i.test(v)) continue;
568
+ safe[k] = v;
569
+ }
570
+ return safe;
571
+ }
572
+ function materialize(node, depth = 0) {
573
+ var _a;
574
+ if (depth > MAX_MATERIALIZE_DEPTH) return null;
575
+ if (node == null) return null;
576
+ if (typeof node === "string" || typeof node === "number") return node;
577
+ if (Array.isArray(node)) {
578
+ return React.createElement(
579
+ React.Fragment,
580
+ null,
581
+ ...node.map((c) => materialize(c, depth + 1))
582
+ );
583
+ }
584
+ if (typeof node !== "object") return null;
585
+ const d = node;
586
+ if (d.$$t === "frag") {
587
+ const ch = Array.isArray(d.ch) ? d.ch : [];
588
+ return React.createElement(
589
+ React.Fragment,
590
+ null,
591
+ ...ch.map((c) => materialize(c, depth + 1))
592
+ );
428
593
  }
594
+ if (d.$$t === "el") {
595
+ const type = String((_a = d.type) != null ? _a : "");
596
+ const props = d.props && typeof d.props === "object" ? d.props : {};
597
+ const ch = Array.isArray(d.ch) ? d.ch : [];
598
+ if (!ALLOWED_ELEMENTS.has(type.toLowerCase())) return null;
599
+ const safeProps = {};
600
+ for (const [k, v] of Object.entries(props)) {
601
+ if (k.startsWith("on")) continue;
602
+ if (k === "dangerouslySetInnerHTML" || k === "ref") continue;
603
+ if ((k === "src" || k === "href") && !isSafeUrl(v)) continue;
604
+ if (k === "style") {
605
+ safeProps[k] = sanitizeStyle(v);
606
+ continue;
607
+ }
608
+ safeProps[k] = v;
609
+ }
610
+ const children = ch.map((c) => materialize(c, depth + 1));
611
+ return React.createElement(type, safeProps, ...children);
612
+ }
613
+ return null;
614
+ }
615
+ function evalInContext(vm, code) {
616
+ const result = vm.evalCode(code);
617
+ if (result.error) {
618
+ result.error.dispose();
619
+ return { ok: false };
620
+ }
621
+ return { ok: true, handle: result.value };
622
+ }
623
+ var MAX_VM_CYCLES = 2e5;
624
+ function makeRenderer(jsCode) {
625
+ return function runInQuickJS(qjs, props) {
626
+ const vm = qjs.newContext();
627
+ let cycles = 0;
628
+ vm.runtime.setInterruptHandler(() => {
629
+ cycles++;
630
+ return cycles > MAX_VM_CYCLES;
631
+ });
632
+ try {
633
+ const setupResult = evalInContext(vm, VM_SETUP + jsCode);
634
+ if (!setupResult.ok) return null;
635
+ setupResult.handle.dispose();
636
+ const propsJson = JSON.stringify(props);
637
+ const callCode = `(function(){var __c=module.exports.default||module.exports;return JSON.stringify(__c(${propsJson}));})();`;
638
+ const callResult = evalInContext(vm, callCode);
639
+ if (!callResult.ok) return null;
640
+ const raw = vm.dump(callResult.handle);
641
+ callResult.handle.dispose();
642
+ return materialize(JSON.parse(String(raw)));
643
+ } catch (e) {
644
+ return null;
645
+ } finally {
646
+ vm.dispose();
647
+ }
648
+ };
649
+ }
650
+ function makeReactFC(renderer, qjs) {
651
+ return function QuickJSTemplateComponent(props) {
652
+ var _a;
653
+ return (_a = renderer(qjs, props)) != null ? _a : null;
654
+ };
655
+ }
656
+ function compileTemplate(code, slug) {
657
+ ensureQuickJSLoaded();
658
+ if (!validateTemplateCode(code)) return null;
659
+ const qjs = getQuickJSSnapshot();
660
+ if (!qjs) return null;
661
+ const cacheKey = `${slug}:${code.length}:${hashCode(code)}`;
662
+ if (rendererCache.has(cacheKey)) {
663
+ const renderer2 = rendererCache.get(cacheKey);
664
+ rendererCache.delete(cacheKey);
665
+ rendererCache.set(cacheKey, renderer2);
666
+ return makeReactFC(renderer2, qjs);
667
+ }
668
+ let jsCode;
429
669
  try {
430
- const { code: jsCode } = transform(code, {
670
+ const result = transform(code, {
431
671
  transforms: ["typescript", "jsx", "imports"],
432
672
  jsxRuntime: "classic",
433
673
  jsxPragma: "React.createElement",
434
674
  jsxFragmentPragma: "React.Fragment"
435
675
  });
436
- const factory = new Function(
437
- "React",
438
- `
439
- var window = undefined;
440
- var document = undefined;
441
- var globalThis = undefined;
442
- var setTimeout = undefined;
443
- var setInterval = undefined;
444
- var setImmediate = undefined;
445
- var fetch = undefined;
446
- var XMLHttpRequest = undefined;
447
- var navigator = undefined;
448
- var location = undefined;
449
- var exports = {};
450
- var module = { exports: exports };
451
- ${jsCode}
452
- return module.exports.default || module.exports;
453
- `
454
- );
455
- const Component = factory(React);
456
- if (typeof Component !== "function") return null;
457
- if (componentCache.size >= MAX_CACHE_SIZE) {
458
- const oldestKey = componentCache.keys().next().value;
459
- if (oldestKey) componentCache.delete(oldestKey);
460
- }
461
- componentCache.set(cacheKey, Component);
462
- return Component;
676
+ jsCode = result.code;
463
677
  } catch (e) {
464
- console.warn(`[flow] Failed to compile template for "${slug}":`, e);
465
678
  return null;
466
679
  }
680
+ const renderer = makeRenderer(jsCode);
681
+ if (rendererCache.size >= MAX_CACHE_SIZE) {
682
+ const oldest = rendererCache.keys().next().value;
683
+ if (oldest) rendererCache.delete(oldest);
684
+ }
685
+ rendererCache.set(cacheKey, renderer);
686
+ return makeReactFC(renderer, qjs);
467
687
  }
468
688
  function clearTemplateCache() {
469
- componentCache.clear();
689
+ rendererCache.clear();
470
690
  }
471
691
 
472
- // src/ui/Flow/FlowRenderer.tsx
473
- import React5 from "react";
474
- import {
475
- ReactFlow,
476
- ReactFlowProvider,
477
- Background,
478
- Controls,
479
- MiniMap
480
- } from "@xyflow/react";
481
-
482
- // src/ui/Flow/node-types-factory.tsx
483
- import React3 from "react";
484
-
485
692
  // src/ui/Flow/node-renderers.tsx
486
693
  import React2 from "react";
487
694
  function sanitizeUrl(url) {
@@ -557,7 +764,7 @@ var TemplateErrorBoundary = class extends React2.Component {
557
764
  }
558
765
  render() {
559
766
  if (this.state.error) {
560
- return /* @__PURE__ */ React2.createElement("div", { style: { padding: 8, fontSize: 11, color: "#ef4444" } }, /* @__PURE__ */ React2.createElement("strong", null, "Render error"), /* @__PURE__ */ React2.createElement("pre", { style: { fontSize: 10, whiteSpace: "pre-wrap" } }, this.state.error.message));
767
+ return /* @__PURE__ */ React2.createElement("div", { style: { padding: 8, fontSize: 11, color: "#ef4444" } }, /* @__PURE__ */ React2.createElement("strong", null, "Render error"), /* @__PURE__ */ React2.createElement("pre", { style: { fontSize: 10, whiteSpace: "pre-wrap" } }, process.env.NODE_ENV === "development" ? this.state.error.message : "Template render failed"));
561
768
  }
562
769
  return this.props.children;
563
770
  }
@@ -568,6 +775,7 @@ function EnhancedDynamicNode({
568
775
  width,
569
776
  height
570
777
  }) {
778
+ useQuickJS();
571
779
  if (typeDef.template) {
572
780
  const Component = compileTemplate(typeDef.template, typeDef.slug);
573
781
  if (Component) {
@@ -594,6 +802,7 @@ function EnhancedDynamicNode({
594
802
  return /* @__PURE__ */ React2.createElement(
595
803
  "div",
596
804
  {
805
+ className: `flow-node flow-node--${typeDef.slug}`,
597
806
  style: {
598
807
  width: "100%",
599
808
  height: "100%",
@@ -646,14 +855,248 @@ function DefaultFrameNode({ data }) {
646
855
  );
647
856
  }
648
857
 
858
+ // src/ui/Flow/field-helpers.ts
859
+ function getImageField(fields, name) {
860
+ const val = fields == null ? void 0 : fields[name];
861
+ if (val && typeof val === "object" && val !== null && "url" in val)
862
+ return val;
863
+ return void 0;
864
+ }
865
+ function getTextField(fields, name) {
866
+ const val = fields == null ? void 0 : fields[name];
867
+ return typeof val === "string" ? val : void 0;
868
+ }
869
+ function getNumberField(fields, name) {
870
+ const val = fields == null ? void 0 : fields[name];
871
+ return typeof val === "number" ? val : void 0;
872
+ }
873
+ function getBooleanField(fields, name) {
874
+ const val = fields == null ? void 0 : fields[name];
875
+ return typeof val === "boolean" ? val : void 0;
876
+ }
877
+
878
+ // src/ui/Flow/css-sanitizer.ts
879
+ import postcss from "postcss";
880
+ var ALLOWED_AT_RULES = /* @__PURE__ */ new Set(["keyframes", "media", "supports", "container", "layer"]);
881
+ var BLOCKED_VALUE_PATTERNS = [/url\s*\(/i, /expression\s*\(/i, /paint\s*\(/i, /-moz-binding/i];
882
+ var DANGEROUS_SELECTOR = /(?:^|[\s,])(?::root|html|body)\b/;
883
+ function sanitizeCSS(css, scopeClass) {
884
+ let root;
885
+ try {
886
+ root = postcss.parse(css);
887
+ } catch (e) {
888
+ return "";
889
+ }
890
+ root.walkAtRules((node) => {
891
+ if (!ALLOWED_AT_RULES.has(node.name.toLowerCase())) {
892
+ node.remove();
893
+ }
894
+ });
895
+ root.walkDecls((node) => {
896
+ if (BLOCKED_VALUE_PATTERNS.some((p) => p.test(node.value))) {
897
+ node.remove();
898
+ }
899
+ });
900
+ root.walkRules((rule) => {
901
+ rule.selectors = rule.selectors.filter((s) => !DANGEROUS_SELECTOR.test(s));
902
+ if (!rule.selectors.length) {
903
+ rule.remove();
904
+ return;
905
+ }
906
+ if (scopeClass) {
907
+ const safeScopeClass = scopeClass.replace(/[{}()[\];,'"\\<>]/g, "");
908
+ if (!safeScopeClass) {
909
+ rule.remove();
910
+ return;
911
+ }
912
+ const parent = rule.parent;
913
+ if ((parent == null ? void 0 : parent.type) === "atrule" && parent.name.toLowerCase() === "keyframes") {
914
+ return;
915
+ }
916
+ rule.selectors = rule.selectors.map((sel) => `.${safeScopeClass} ${sel}`);
917
+ }
918
+ });
919
+ return root.toString().replace(/<\/style/gi, "<\\/style");
920
+ }
921
+
922
+ // src/ui/Flow/FlowRenderer.tsx
923
+ import React6 from "react";
924
+ import {
925
+ ReactFlow,
926
+ ReactFlowProvider,
927
+ Background,
928
+ Controls,
929
+ MiniMap
930
+ } from "@xyflow/react";
931
+
932
+ // src/ui/Flow/node-types-factory.tsx
933
+ import React4 from "react";
934
+
935
+ // src/ui/Image/index.tsx
936
+ import React3, { useCallback, useRef, useState } from "react";
937
+
938
+ // src/utils/image.ts
939
+ var IMAGE_SIZES = [384, 768, 1536];
940
+ function getImageSrcSet(image) {
941
+ const parts = [];
942
+ const sizes = image.sizes;
943
+ if (sizes) {
944
+ for (const size of IMAGE_SIZES) {
945
+ const entry = sizes[String(size)];
946
+ if ((entry == null ? void 0 : entry.url) && entry.width) {
947
+ parts.push(`${entry.url} ${entry.width}w`);
948
+ }
949
+ }
950
+ }
951
+ if (image.url && image.width) {
952
+ parts.push(`${image.url} ${image.width}w`);
953
+ }
954
+ return parts.join(", ");
955
+ }
956
+ function getImagePlaceholderStyle(image, options) {
957
+ var _a, _b, _c;
958
+ const type = (_a = options == null ? void 0 : options.type) != null ? _a : "blur";
959
+ const paletteColor = (_b = options == null ? void 0 : options.paletteColor) != null ? _b : "muted";
960
+ if (type === "none") return {};
961
+ const color = (_c = image.palette) == null ? void 0 : _c[paletteColor];
962
+ if (type === "blur") {
963
+ const lqip = image.lqip;
964
+ if (lqip) {
965
+ return {
966
+ backgroundImage: `url(${lqip})`,
967
+ backgroundSize: "cover",
968
+ backgroundPosition: "center"
969
+ };
970
+ }
971
+ if (color) {
972
+ return { backgroundColor: color };
973
+ }
974
+ return {};
975
+ }
976
+ if (color) {
977
+ return { backgroundColor: color };
978
+ }
979
+ return {};
980
+ }
981
+
982
+ // src/ui/Image/index.tsx
983
+ function Image({
984
+ image,
985
+ width,
986
+ dpr = 1,
987
+ placeholder: placeholderProp,
988
+ className,
989
+ style,
990
+ imgClassName,
991
+ imgStyle,
992
+ sizes,
993
+ loading: loadingProp,
994
+ onLoad,
995
+ objectFit = "cover",
996
+ priority = false,
997
+ fill = false,
998
+ imageRendering
999
+ }) {
1000
+ var _a, _b;
1001
+ const [loaded, setLoaded] = useState(false);
1002
+ const firedRef = useRef(false);
1003
+ const isPixelRendering = imageRendering === "pixelated" || imageRendering === "crisp-edges";
1004
+ const placeholder = placeholderProp != null ? placeholderProp : isPixelRendering ? "none" : "blur";
1005
+ const loading = priority ? "eager" : loadingProp != null ? loadingProp : "lazy";
1006
+ const aspectRatio = !fill && image.width && image.height ? `${image.width} / ${image.height}` : void 0;
1007
+ const srcSet = getImageSrcSet(image);
1008
+ const src = (_a = image.url) != null ? _a : void 0;
1009
+ const hasLqip = placeholder === "blur" && !!image.lqip;
1010
+ const placeholderStyle = getImagePlaceholderStyle(image, {
1011
+ type: placeholder
1012
+ });
1013
+ const placeholderColor = !hasLqip && "backgroundColor" in placeholderStyle ? placeholderStyle.backgroundColor : void 0;
1014
+ const fireLoad = useCallback(() => {
1015
+ if (firedRef.current) return;
1016
+ firedRef.current = true;
1017
+ setLoaded(true);
1018
+ onLoad == null ? void 0 : onLoad();
1019
+ }, [onLoad]);
1020
+ const imgRef = useCallback(
1021
+ (node) => {
1022
+ if (node && node.complete && node.naturalWidth > 0) {
1023
+ fireLoad();
1024
+ }
1025
+ },
1026
+ [fireLoad]
1027
+ );
1028
+ const containerStyle = __spreadValues(__spreadValues(__spreadValues({
1029
+ position: "relative",
1030
+ overflow: "hidden"
1031
+ }, fill ? { width: "100%", height: "100%" } : {}), aspectRatio ? { aspectRatio } : {}), style);
1032
+ const overlayBase = {
1033
+ position: "absolute",
1034
+ top: 0,
1035
+ left: 0,
1036
+ width: "100%",
1037
+ height: "100%",
1038
+ opacity: loaded ? 0 : 1,
1039
+ transition: "opacity 0.3s ease",
1040
+ pointerEvents: "none"
1041
+ };
1042
+ const mainImgStyle = __spreadValues(__spreadProps(__spreadValues({
1043
+ display: "block",
1044
+ width: "100%",
1045
+ height: "100%",
1046
+ objectFit
1047
+ }, imageRendering ? { imageRendering } : {}), {
1048
+ opacity: loaded ? 1 : 0,
1049
+ transition: "opacity 0.3s ease"
1050
+ }), imgStyle);
1051
+ return /* @__PURE__ */ React3.createElement("div", { className, style: containerStyle }, hasLqip && /* @__PURE__ */ React3.createElement(
1052
+ "img",
1053
+ {
1054
+ "aria-hidden": true,
1055
+ alt: "",
1056
+ src: image.lqip,
1057
+ style: __spreadProps(__spreadValues({}, overlayBase), {
1058
+ display: "block",
1059
+ objectFit,
1060
+ filter: "blur(20px)",
1061
+ transform: "scale(1.1)"
1062
+ })
1063
+ }
1064
+ ), placeholderColor && /* @__PURE__ */ React3.createElement(
1065
+ "div",
1066
+ {
1067
+ "aria-hidden": true,
1068
+ style: __spreadProps(__spreadValues({}, overlayBase), {
1069
+ backgroundColor: placeholderColor
1070
+ })
1071
+ }
1072
+ ), /* @__PURE__ */ React3.createElement(
1073
+ "img",
1074
+ {
1075
+ ref: imgRef,
1076
+ alt: (_b = image.alt) != null ? _b : "",
1077
+ src,
1078
+ srcSet: srcSet || void 0,
1079
+ sizes,
1080
+ width: width ? width * dpr : void 0,
1081
+ loading,
1082
+ decoding: "async",
1083
+ fetchPriority: priority ? "high" : void 0,
1084
+ onLoad: fireLoad,
1085
+ className: imgClassName,
1086
+ style: mainImgStyle
1087
+ }
1088
+ ));
1089
+ }
1090
+
649
1091
  // src/ui/Flow/node-types-factory.tsx
650
1092
  function createNodeTypes(nodeRenderers, nodeTypeDefsMap, frameRenderer, nodeWrapper, renderNode) {
651
1093
  const types = {};
652
1094
  types.dynamic = ((props) => {
1095
+ var _a;
653
1096
  const d = props.data;
654
1097
  const typeDef = nodeTypeDefsMap == null ? void 0 : nodeTypeDefsMap.get(d.nodeTypeSlug);
655
1098
  const CustomRenderer = nodeRenderers == null ? void 0 : nodeRenderers[d.nodeTypeSlug];
656
- const defaultRender = typeDef ? /* @__PURE__ */ React3.createElement(
1099
+ const defaultRender = typeDef ? /* @__PURE__ */ React4.createElement(
657
1100
  EnhancedDynamicNode,
658
1101
  {
659
1102
  data: d,
@@ -661,7 +1104,7 @@ function createNodeTypes(nodeRenderers, nodeTypeDefsMap, frameRenderer, nodeWrap
661
1104
  width: props.width,
662
1105
  height: props.height
663
1106
  }
664
- ) : /* @__PURE__ */ React3.createElement(DefaultDynamicNode, __spreadValues({}, props));
1107
+ ) : /* @__PURE__ */ React4.createElement(DefaultDynamicNode, __spreadValues({}, props));
665
1108
  const slotProps = {
666
1109
  id: props.id,
667
1110
  nodeTypeSlug: d.nodeTypeSlug,
@@ -673,14 +1116,35 @@ function createNodeTypes(nodeRenderers, nodeTypeDefsMap, frameRenderer, nodeWrap
673
1116
  height: props.height,
674
1117
  defaultRender
675
1118
  };
676
- let content = CustomRenderer ? /* @__PURE__ */ React3.createElement(CustomRenderer, __spreadValues({}, slotProps)) : defaultRender;
1119
+ let content;
1120
+ if (CustomRenderer) {
1121
+ content = /* @__PURE__ */ React4.createElement(CustomRenderer, __spreadValues({}, slotProps));
1122
+ } else if (d.nodeTypeSlug === "image") {
1123
+ const imageVal = (_a = d.fields) == null ? void 0 : _a.image;
1124
+ if (imageVal && typeof imageVal === "object" && "url" in imageVal) {
1125
+ content = /* @__PURE__ */ React4.createElement(
1126
+ Image,
1127
+ {
1128
+ image: imageVal,
1129
+ width: props.width,
1130
+ fill: true,
1131
+ priority: true,
1132
+ objectFit: "contain"
1133
+ }
1134
+ );
1135
+ } else {
1136
+ content = defaultRender;
1137
+ }
1138
+ } else {
1139
+ content = defaultRender;
1140
+ }
677
1141
  if (renderNode) {
678
1142
  const result = renderNode(slotProps, content);
679
1143
  if (result !== null) content = result;
680
1144
  }
681
1145
  if (nodeWrapper) {
682
1146
  const Wrapper = nodeWrapper;
683
- content = /* @__PURE__ */ React3.createElement(
1147
+ content = /* @__PURE__ */ React4.createElement(
684
1148
  Wrapper,
685
1149
  {
686
1150
  id: props.id,
@@ -697,7 +1161,7 @@ function createNodeTypes(nodeRenderers, nodeTypeDefsMap, frameRenderer, nodeWrap
697
1161
  types.frame = frameRenderer ? ((props) => {
698
1162
  const d = props.data;
699
1163
  const Renderer = frameRenderer;
700
- return /* @__PURE__ */ React3.createElement(
1164
+ return /* @__PURE__ */ React4.createElement(
701
1165
  Renderer,
702
1166
  {
703
1167
  id: props.id,
@@ -721,7 +1185,7 @@ function createEdgeTypes(edgeRenderers, edgeTypeDefsMap) {
721
1185
  types[slug] = ((props) => {
722
1186
  var _a;
723
1187
  const def = edgeTypeDefsMap == null ? void 0 : edgeTypeDefsMap.get(slug);
724
- return /* @__PURE__ */ React3.createElement(
1188
+ return /* @__PURE__ */ React4.createElement(
725
1189
  Renderer,
726
1190
  {
727
1191
  id: props.id,
@@ -787,7 +1251,7 @@ function applyEdgeStyles(edges, edgeTypeDefsMap) {
787
1251
  }
788
1252
 
789
1253
  // src/ui/Flow/focus-handler.tsx
790
- import React4 from "react";
1254
+ import React5 from "react";
791
1255
  import { useReactFlow, useStoreApi } from "@xyflow/react";
792
1256
  function clampViewport(vp, cw, ch, extent) {
793
1257
  const left = -vp.x / vp.zoom;
@@ -816,14 +1280,14 @@ function FocusHandler({
816
1280
  }) {
817
1281
  const { setViewport } = useReactFlow();
818
1282
  const store = useStoreApi();
819
- const containerRef = React4.useRef(null);
1283
+ const containerRef = React5.useRef(null);
820
1284
  const boundsKey = `${bounds.x},${bounds.y},${bounds.width},${bounds.height}`;
821
- const boundsRef = React4.useRef(bounds);
1285
+ const boundsRef = React5.useRef(bounds);
822
1286
  boundsRef.current = bounds;
823
- const [containerSize, setContainerSize] = React4.useState({ w: 0, h: 0 });
824
- const prevBoundsKeyRef = React4.useRef(null);
825
- const prevSizeRef = React4.useRef({ w: 0, h: 0 });
826
- React4.useEffect(() => {
1287
+ const [containerSize, setContainerSize] = React5.useState({ w: 0, h: 0 });
1288
+ const prevBoundsKeyRef = React5.useRef(null);
1289
+ const prevSizeRef = React5.useRef({ w: 0, h: 0 });
1290
+ React5.useEffect(() => {
827
1291
  const el = containerRef.current;
828
1292
  if (!el) return;
829
1293
  const observer = new ResizeObserver((entries) => {
@@ -835,7 +1299,7 @@ function FocusHandler({
835
1299
  observer.observe(el);
836
1300
  return () => observer.disconnect();
837
1301
  }, []);
838
- React4.useEffect(() => {
1302
+ React5.useEffect(() => {
839
1303
  if (containerSize.w === 0 || containerSize.h === 0) return;
840
1304
  const prevKey = prevBoundsKeyRef.current;
841
1305
  const prevSize = prevSizeRef.current;
@@ -902,7 +1366,7 @@ function FocusHandler({
902
1366
  store,
903
1367
  onInitialFit
904
1368
  ]);
905
- return /* @__PURE__ */ React4.createElement(
1369
+ return /* @__PURE__ */ React5.createElement(
906
1370
  "div",
907
1371
  {
908
1372
  ref: containerRef,
@@ -917,6 +1381,7 @@ function FocusHandler({
917
1381
  }
918
1382
 
919
1383
  // src/ui/Flow/FlowRenderer.tsx
1384
+ var NullFrameRenderer = () => null;
920
1385
  function FlowRenderer({
921
1386
  data,
922
1387
  className,
@@ -934,6 +1399,7 @@ function FlowRenderer({
934
1399
  onNodeMouseLeave,
935
1400
  onEdgeClick,
936
1401
  frameRenderer,
1402
+ hideFrames = false,
937
1403
  edgeRenderers,
938
1404
  nodeWrapper,
939
1405
  controls,
@@ -954,33 +1420,34 @@ function FlowRenderer({
954
1420
  maxZoom: maxZoomProp
955
1421
  }) {
956
1422
  var _a, _b;
957
- const nodeTypeDefsMap = React5.useMemo(() => {
1423
+ const resolvedFrameRenderer = hideFrames ? NullFrameRenderer : frameRenderer;
1424
+ const nodeTypeDefsMap = React6.useMemo(() => {
958
1425
  if (!(nodeTypeDefs == null ? void 0 : nodeTypeDefs.length)) return void 0;
959
1426
  return new Map(nodeTypeDefs.map((d) => [d.slug, d]));
960
1427
  }, [nodeTypeDefs]);
961
- const edgeTypeDefsMap = React5.useMemo(() => {
1428
+ const edgeTypeDefsMap = React6.useMemo(() => {
962
1429
  if (!(edgeTypeDefs == null ? void 0 : edgeTypeDefs.length)) return void 0;
963
1430
  return new Map(edgeTypeDefs.map((d) => [d.slug, d]));
964
1431
  }, [edgeTypeDefs]);
965
- const nodeTypes = React5.useMemo(
1432
+ const nodeTypes = React6.useMemo(
966
1433
  () => createNodeTypes(
967
1434
  nodeRenderers,
968
1435
  nodeTypeDefsMap,
969
- frameRenderer,
1436
+ resolvedFrameRenderer,
970
1437
  nodeWrapper,
971
1438
  renderNode
972
1439
  ),
973
- [nodeRenderers, nodeTypeDefsMap, frameRenderer, nodeWrapper, renderNode]
1440
+ [nodeRenderers, nodeTypeDefsMap, resolvedFrameRenderer, nodeWrapper, renderNode]
974
1441
  );
975
- const customEdgeTypes = React5.useMemo(
1442
+ const customEdgeTypes = React6.useMemo(
976
1443
  () => createEdgeTypes(edgeRenderers, edgeTypeDefsMap),
977
1444
  [edgeRenderers, edgeTypeDefsMap]
978
1445
  );
979
- const mergedCSS = React5.useMemo(() => {
1446
+ const mergedCSS = React6.useMemo(() => {
980
1447
  if (!(nodeTypeDefs == null ? void 0 : nodeTypeDefs.length)) return "";
981
- return nodeTypeDefs.filter((d) => d.customCSS).map((d) => d.customCSS).join("\n");
1448
+ return nodeTypeDefs.filter((d) => d.customCSS).map((d) => sanitizeCSS(d.customCSS, `flow-node--${d.slug}`)).join("\n");
982
1449
  }, [nodeTypeDefs]);
983
- const styledEdges = React5.useMemo(() => {
1450
+ const styledEdges = React6.useMemo(() => {
984
1451
  var _a2;
985
1452
  let edges = applyEdgeStyles((_a2 = data == null ? void 0 : data.edges) != null ? _a2 : [], edgeTypeDefsMap);
986
1453
  if (edgeRenderers) {
@@ -994,7 +1461,7 @@ function FlowRenderer({
994
1461
  }
995
1462
  return edges;
996
1463
  }, [data == null ? void 0 : data.edges, edgeTypeDefsMap, edgeRenderers]);
997
- const translateExtent = React5.useMemo(() => {
1464
+ const translateExtent = React6.useMemo(() => {
998
1465
  if (translateExtentProp) return translateExtentProp;
999
1466
  const es = clampBounds != null ? clampBounds : bounds;
1000
1467
  if (!es) return void 0;
@@ -1005,16 +1472,16 @@ function FlowRenderer({
1005
1472
  ];
1006
1473
  }, [translateExtentProp, clampBounds, bounds, focusPadding]);
1007
1474
  const boundsKey = bounds ? `${bounds.x},${bounds.y},${bounds.width},${bounds.height}` : "";
1008
- const extentReadyRef = React5.useRef(false);
1009
- const expandedExtentRef = React5.useRef(void 0);
1010
- const prevBoundsKeyRef = React5.useRef(boundsKey);
1475
+ const extentReadyRef = React6.useRef(false);
1476
+ const expandedExtentRef = React6.useRef(void 0);
1477
+ const prevBoundsKeyRef = React6.useRef(boundsKey);
1011
1478
  if (prevBoundsKeyRef.current !== boundsKey) {
1012
1479
  prevBoundsKeyRef.current = boundsKey;
1013
1480
  extentReadyRef.current = false;
1014
1481
  expandedExtentRef.current = void 0;
1015
1482
  }
1016
- const [, rerender] = React5.useReducer((x) => x + 1, 0);
1017
- const handleInitialFit = React5.useCallback(
1483
+ const [, rerender] = React6.useReducer((x) => x + 1, 0);
1484
+ const handleInitialFit = React6.useCallback(
1018
1485
  (expandedExtent) => {
1019
1486
  extentReadyRef.current = true;
1020
1487
  expandedExtentRef.current = expandedExtent;
@@ -1025,7 +1492,7 @@ function FlowRenderer({
1025
1492
  const activeExtent = !bounds || extentReadyRef.current ? (_a = expandedExtentRef.current) != null ? _a : translateExtent : void 0;
1026
1493
  if (!data) return null;
1027
1494
  const resolvedDefaultViewport = defaultViewportProp != null ? defaultViewportProp : !fitView && data.viewport ? data.viewport : void 0;
1028
- return /* @__PURE__ */ React5.createElement(ReactFlowProvider, null, /* @__PURE__ */ React5.createElement(
1495
+ return /* @__PURE__ */ React6.createElement(ReactFlowProvider, null, /* @__PURE__ */ React6.createElement(
1029
1496
  "div",
1030
1497
  {
1031
1498
  className,
@@ -1035,7 +1502,7 @@ function FlowRenderer({
1035
1502
  background: "transparent"
1036
1503
  }, style)
1037
1504
  },
1038
- /* @__PURE__ */ React5.createElement(
1505
+ /* @__PURE__ */ React6.createElement(
1039
1506
  ReactFlow,
1040
1507
  {
1041
1508
  nodes: (_b = data.nodes) != null ? _b : [],
@@ -1065,16 +1532,16 @@ function FlowRenderer({
1065
1532
  maxZoom: maxZoomProp,
1066
1533
  proOptions: { hideAttribution: true }
1067
1534
  },
1068
- mergedCSS && /* @__PURE__ */ React5.createElement("style", { dangerouslySetInnerHTML: { __html: mergedCSS } }),
1069
- background && /* @__PURE__ */ React5.createElement(Background, null),
1070
- controls && /* @__PURE__ */ React5.createElement(Controls, null),
1071
- minimap && /* @__PURE__ */ React5.createElement(
1535
+ mergedCSS && /* @__PURE__ */ React6.createElement("style", { dangerouslySetInnerHTML: { __html: mergedCSS } }),
1536
+ background && /* @__PURE__ */ React6.createElement(Background, null),
1537
+ controls && /* @__PURE__ */ React6.createElement(Controls, null),
1538
+ minimap && /* @__PURE__ */ React6.createElement(
1072
1539
  MiniMap,
1073
1540
  {
1074
1541
  nodeColor: minimapNodeColor
1075
1542
  }
1076
1543
  ),
1077
- bounds && /* @__PURE__ */ React5.createElement(
1544
+ bounds && /* @__PURE__ */ React6.createElement(
1078
1545
  FocusHandler,
1079
1546
  {
1080
1547
  bounds,
@@ -1092,18 +1559,89 @@ function FlowRenderer({
1092
1559
  )
1093
1560
  ));
1094
1561
  }
1562
+
1563
+ // src/ui/Flow/FlowFrame.tsx
1564
+ import React7, { useMemo as useMemo3, useEffect, useRef as useRef2 } from "react";
1565
+ function FlowFrame(_a) {
1566
+ var _b = _a, {
1567
+ client,
1568
+ slug,
1569
+ id,
1570
+ frameId,
1571
+ loading = null,
1572
+ error: renderError,
1573
+ onDataReady
1574
+ } = _b, rendererProps = __objRest(_b, [
1575
+ "client",
1576
+ "slug",
1577
+ "id",
1578
+ "frameId",
1579
+ "loading",
1580
+ "error",
1581
+ "onDataReady"
1582
+ ]);
1583
+ const { data, nodeTypeDefs, edgeTypeDefs, flow, isLoading, error } = useFlow({
1584
+ client,
1585
+ slug,
1586
+ id
1587
+ });
1588
+ const frameData = useMemo3(
1589
+ () => data ? getFrameData(data, frameId) : void 0,
1590
+ [data, frameId]
1591
+ );
1592
+ const onDataReadyRef = useRef2(onDataReady);
1593
+ onDataReadyRef.current = onDataReady;
1594
+ useEffect(() => {
1595
+ var _a2;
1596
+ if (frameData && flow) {
1597
+ (_a2 = onDataReadyRef.current) == null ? void 0 : _a2.call(onDataReadyRef, frameData, flow);
1598
+ }
1599
+ }, [frameData, flow]);
1600
+ if (process.env.NODE_ENV !== "production" && !slug && !id) {
1601
+ console.warn('[FlowFrame] Either "slug" or "id" must be provided.');
1602
+ }
1603
+ if (isLoading) return /* @__PURE__ */ React7.createElement(React7.Fragment, null, loading);
1604
+ if (error) return renderError ? /* @__PURE__ */ React7.createElement(React7.Fragment, null, renderError(error)) : null;
1605
+ if (!frameData) {
1606
+ if (process.env.NODE_ENV !== "production" && data) {
1607
+ const frames = data.nodes.filter(isFrameNode).map((n) => n.id);
1608
+ console.warn(
1609
+ `[FlowFrame] Frame "${frameId}" not found. Available frames: ${frames.join(", ") || "(none)"}`
1610
+ );
1611
+ }
1612
+ return null;
1613
+ }
1614
+ return /* @__PURE__ */ React7.createElement(
1615
+ FlowRenderer,
1616
+ __spreadProps(__spreadValues({}, rendererProps), {
1617
+ data: frameData.data,
1618
+ nodeTypeDefs,
1619
+ edgeTypeDefs,
1620
+ bounds: frameData.fitBounds,
1621
+ clampBounds: frameData.clampBounds,
1622
+ hideFrames: true
1623
+ })
1624
+ );
1625
+ }
1095
1626
  export {
1096
1627
  BUILT_IN_EDGE_TYPES,
1097
1628
  BUILT_IN_NODE_TYPES,
1629
+ FlowFrame,
1098
1630
  FlowRenderer,
1099
1631
  clearTemplateCache,
1100
1632
  compileTemplate,
1633
+ getBooleanField,
1101
1634
  getFrameData,
1102
1635
  getFrames,
1636
+ getImageField,
1103
1637
  getNodeBounds,
1638
+ getNumberField,
1639
+ getTextField,
1104
1640
  isDynamicNode,
1105
1641
  isFrameNode,
1106
1642
  prefetchFlow,
1643
+ renderFieldValue,
1644
+ sanitizeCSS,
1107
1645
  useFlow,
1108
1646
  useFlowData
1109
1647
  };