@01.software/sdk 0.44.0 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/CHANGELOG.md +358 -2
  2. package/MIGRATION.md +89 -10
  3. package/README.md +441 -98
  4. package/dist/{server-nXOezi4b.d.cts → catalog-mappers-sIshKNb3.d.cts} +14 -3
  5. package/dist/{server-nXOezi4b.d.ts → catalog-mappers-sIshKNb3.d.ts} +14 -3
  6. package/dist/client.cjs +2680 -345
  7. package/dist/client.cjs.map +1 -1
  8. package/dist/client.d.cts +10 -7
  9. package/dist/client.d.ts +10 -7
  10. package/dist/client.js +2680 -345
  11. package/dist/client.js.map +1 -1
  12. package/dist/{collection-client-Dyl-CDcj.d.cts → collection-client-BSo7VXQE.d.cts} +8 -5
  13. package/dist/{collection-client-DWooaiTr.d.ts → collection-client-Ckfusuee.d.ts} +8 -5
  14. package/dist/{const-pGDnsy0o.d.ts → const-BAG0MH0u.d.cts} +13 -10
  15. package/dist/{const-1vleZ6ov.d.cts → const-CaPq2AE4.d.ts} +13 -10
  16. package/dist/errors.cjs +74 -1
  17. package/dist/errors.cjs.map +1 -1
  18. package/dist/errors.d.cts +38 -1
  19. package/dist/errors.d.ts +38 -1
  20. package/dist/errors.js +74 -1
  21. package/dist/errors.js.map +1 -1
  22. package/dist/{index-DUNySyxV.d.ts → index-BNICJxBL.d.ts} +4 -4
  23. package/dist/{index-a6g7vqAD.d.cts → index-YGrZrxNn.d.cts} +4 -4
  24. package/dist/index.cjs +2741 -368
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.d.cts +18 -19
  27. package/dist/index.d.ts +18 -19
  28. package/dist/index.js +2741 -368
  29. package/dist/index.js.map +1 -1
  30. package/dist/{payload-types-CDsuOEfM.d.cts → payload-types-CJZuXcgF.d.cts} +6671 -4001
  31. package/dist/{payload-types-CDsuOEfM.d.ts → payload-types-CJZuXcgF.d.ts} +6671 -4001
  32. package/dist/query.cjs +550 -101
  33. package/dist/query.cjs.map +1 -1
  34. package/dist/query.d.cts +43 -45
  35. package/dist/query.d.ts +43 -45
  36. package/dist/query.js +550 -101
  37. package/dist/query.js.map +1 -1
  38. package/dist/realtime.cjs.map +1 -1
  39. package/dist/realtime.d.cts +2 -2
  40. package/dist/realtime.d.ts +2 -2
  41. package/dist/realtime.js.map +1 -1
  42. package/dist/server.cjs +2252 -444
  43. package/dist/server.cjs.map +1 -1
  44. package/dist/server.d.cts +11 -10
  45. package/dist/server.d.ts +11 -10
  46. package/dist/server.js +2252 -444
  47. package/dist/server.js.map +1 -1
  48. package/dist/storefront-cache.cjs +65 -1
  49. package/dist/storefront-cache.cjs.map +1 -1
  50. package/dist/storefront-cache.js +65 -1
  51. package/dist/storefront-cache.js.map +1 -1
  52. package/dist/types-BC6rVmJa.d.ts +47 -0
  53. package/dist/{types-CDND3hzq.d.cts → types-BlOvjoTX.d.ts} +1095 -509
  54. package/dist/{types-Dt9FJisi.d.ts → types-C4WuJfw7.d.cts} +1095 -509
  55. package/dist/types-D5p7-m1l.d.cts +47 -0
  56. package/dist/ui/canvas/server.cjs +244 -2
  57. package/dist/ui/canvas/server.cjs.map +1 -1
  58. package/dist/ui/canvas/server.d.cts +27 -1
  59. package/dist/ui/canvas/server.d.ts +27 -1
  60. package/dist/ui/canvas/server.js +244 -2
  61. package/dist/ui/canvas/server.js.map +1 -1
  62. package/dist/ui/canvas.cjs +419 -103
  63. package/dist/ui/canvas.cjs.map +1 -1
  64. package/dist/ui/canvas.d.cts +19 -7
  65. package/dist/ui/canvas.d.ts +19 -7
  66. package/dist/ui/canvas.js +419 -103
  67. package/dist/ui/canvas.js.map +1 -1
  68. package/dist/ui/form.d.cts +1 -1
  69. package/dist/ui/form.d.ts +1 -1
  70. package/dist/ui/video.d.cts +1 -1
  71. package/dist/ui/video.d.ts +1 -1
  72. package/dist/webhook.cjs +2 -1
  73. package/dist/webhook.cjs.map +1 -1
  74. package/dist/webhook.d.cts +4 -4
  75. package/dist/webhook.d.ts +4 -4
  76. package/dist/webhook.js +2 -1
  77. package/dist/webhook.js.map +1 -1
  78. package/package.json +12 -11
  79. package/dist/types-SoXsbKhj.d.cts +0 -33
  80. package/dist/types-oiae_LMm.d.ts +0 -33
@@ -31,8 +31,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  // src/ui/Canvas/index.tsx
32
32
  var Canvas_exports = {};
33
33
  __export(Canvas_exports, {
34
+ CANVAS_SHAPE_TYPES: () => CANVAS_SHAPE_TYPES,
34
35
  CanvasFrame: () => CanvasFrame,
35
36
  CanvasRenderer: () => CanvasRenderer,
37
+ DefaultShapeNode: () => DefaultShapeNode,
36
38
  clearTemplateCache: () => clearTemplateCache,
37
39
  compileTemplate: () => compileTemplate,
38
40
  getBooleanField: () => getBooleanField,
@@ -44,6 +46,7 @@ __export(Canvas_exports, {
44
46
  getTextField: () => getTextField,
45
47
  isDynamicNode: () => isDynamicNode,
46
48
  isFrameNode: () => isFrameNode,
49
+ isShapeNode: () => isShapeNode,
47
50
  prefetchCanvas: () => prefetchCanvas,
48
51
  renderFieldValue: () => renderFieldValue,
49
52
  sanitizeCSS: () => sanitizeCSS,
@@ -57,12 +60,22 @@ __export(Canvas_exports, {
57
60
  module.exports = __toCommonJS(Canvas_exports);
58
61
 
59
62
  // src/ui/Canvas/types.ts
63
+ var CANVAS_SHAPE_TYPES = [
64
+ "terminator",
65
+ "process",
66
+ "decision",
67
+ "io",
68
+ "subprocess"
69
+ ];
60
70
  function isDynamicNode(node) {
61
71
  return node.type === "dynamic";
62
72
  }
63
73
  function isFrameNode(node) {
64
74
  return node.type === "frame";
65
75
  }
76
+ function isShapeNode(node) {
77
+ return node.type === "shape";
78
+ }
66
79
 
67
80
  // src/ui/Canvas/useCanvas.ts
68
81
  var import_react_query = require("@tanstack/react-query");
@@ -86,10 +99,13 @@ function collectionKeys(collection) {
86
99
  }
87
100
 
88
101
  // src/ui/Canvas/query-options.ts
102
+ var CANVAS_STALE_TIME_MS = 6e4;
103
+ var CANVAS_CATALOG_STALE_TIME_MS = 5 * 6e4;
89
104
  function canvasQueryOptions(client, slug, id) {
90
105
  const identifier = id ?? slug ?? "";
91
106
  return {
92
107
  queryKey: collectionKeys("canvases").detail(identifier),
108
+ staleTime: CANVAS_STALE_TIME_MS,
93
109
  queryFn: async () => {
94
110
  if (id) return client.collections.from("canvases").findById(id);
95
111
  const result = await client.collections.from("canvases").find({
@@ -105,8 +121,9 @@ function canvasQueryOptions(client, slug, id) {
105
121
  function nodeTypesQueryOptions(client) {
106
122
  return {
107
123
  queryKey: collectionKeys("canvas-node-types").lists(),
124
+ staleTime: CANVAS_CATALOG_STALE_TIME_MS,
108
125
  queryFn: async () => {
109
- const result = await client.collections.from("canvas-node-types").find({ limit: 0 });
126
+ const result = await client.collections.from("canvas-node-types").find({ limit: 100 });
110
127
  return result.docs;
111
128
  }
112
129
  };
@@ -114,8 +131,9 @@ function nodeTypesQueryOptions(client) {
114
131
  function edgeTypesQueryOptions(client) {
115
132
  return {
116
133
  queryKey: collectionKeys("canvas-edge-types").lists(),
134
+ staleTime: CANVAS_CATALOG_STALE_TIME_MS,
117
135
  queryFn: async () => {
118
- const result = await client.collections.from("canvas-edge-types").find({ limit: 0 });
136
+ const result = await client.collections.from("canvas-edge-types").find({ limit: 100 });
119
137
  return result.docs;
120
138
  }
121
139
  };
@@ -384,10 +402,9 @@ function getFrameData(data, frameId) {
384
402
  // src/ui/Canvas/template-compiler.ts
385
403
  var import_react4 = __toESM(require("react"), 1);
386
404
  var import_sucrase = require("sucrase");
387
- var WIRE_FORMAT_VERSION = 1;
388
- var MAX_CACHE_SIZE = 100;
389
- var MAX_MATERIALIZE_DEPTH = 20;
390
- var ALLOWED_ELEMENTS = /* @__PURE__ */ new Set([
405
+
406
+ // src/ui/Canvas/template-security.ts
407
+ var ALLOWED_TEMPLATE_ELEMENTS = [
391
408
  "div",
392
409
  "span",
393
410
  "p",
@@ -421,33 +438,188 @@ var ALLOWED_ELEMENTS = /* @__PURE__ */ new Set([
421
438
  "g",
422
439
  "circle",
423
440
  "rect"
424
- ]);
425
- var BLOCKED_PATTERNS = [
426
- /\bdocument\s*\./,
427
- /\bwindow\s*\./,
428
- /\bwindow\s*\[/,
429
- /\bglobalThis\s*\./,
430
- /\bfetch\s*\(/,
431
- /\bXMLHttpRequest/,
432
- /\beval\s*\(/,
433
- /\bFunction\s*\(/,
434
- /\bimport\s*\(/,
435
- /\blocalStorage/,
436
- /\bsessionStorage/,
437
- /\bcookie/,
438
- /\bpostMessage\s*\(/,
439
- /\blocation\s*[.=]/,
440
- /\bnavigator\s*\./,
441
- /\bsetTimeout\s*\(/,
442
- /\bsetInterval\s*\(/,
443
- /\bsetImmediate\s*\(/,
444
- /\brequire\s*\(/,
445
- /\bself\b/,
446
- /\bconstructor\s*\.\s*constructor/,
447
- /\bReflect\b/,
448
- /\bProxy\b/,
449
- /\b__proto__\b/
450
441
  ];
442
+ var ALLOWED_TEMPLATE_ELEMENT_SET = new Set(ALLOWED_TEMPLATE_ELEMENTS);
443
+ function isAllowedTemplateElement(value) {
444
+ return ALLOWED_TEMPLATE_ELEMENT_SET.has(value.toLowerCase());
445
+ }
446
+ var TEMPLATE_BLOCKED_PATTERNS = [
447
+ { pattern: /\bdocument\s*\./, hint: "document API" },
448
+ { pattern: /\bwindow\s*\./, hint: "window API" },
449
+ { pattern: /\bwindow\s*\[/, hint: "window indexer" },
450
+ { pattern: /\bglobalThis\s*\./, hint: "globalThis" },
451
+ { pattern: /\bfetch\s*\(/, hint: "fetch()" },
452
+ { pattern: /\bXMLHttpRequest/, hint: "XMLHttpRequest" },
453
+ { pattern: /\beval\s*\(/, hint: "eval()" },
454
+ { pattern: /\bFunction\s*\(/, hint: "Function constructor" },
455
+ { pattern: /\bimport\s*\(/, hint: "dynamic import()" },
456
+ { pattern: /\blocalStorage/, hint: "localStorage" },
457
+ { pattern: /\bsessionStorage/, hint: "sessionStorage" },
458
+ { pattern: /\bcookie/, hint: "cookie access" },
459
+ { pattern: /\bpostMessage\s*\(/, hint: "postMessage()" },
460
+ { pattern: /\blocation\s*[.=]/, hint: "location API" },
461
+ { pattern: /\bnavigator\s*\./, hint: "navigator API" },
462
+ { pattern: /\bsetTimeout\s*\(/, hint: "setTimeout()" },
463
+ { pattern: /\bsetInterval\s*\(/, hint: "setInterval()" },
464
+ { pattern: /\bsetImmediate\s*\(/, hint: "setImmediate()" },
465
+ { pattern: /\brequire\s*\(/, hint: "require()" },
466
+ { pattern: /\bself\b/, hint: "self alias" },
467
+ {
468
+ pattern: /\bconstructor\s*\.\s*constructor/,
469
+ hint: "constructor.constructor escape"
470
+ },
471
+ { pattern: /\bReflect\b/, hint: "Reflect" },
472
+ { pattern: /\bProxy\b/, hint: "Proxy" },
473
+ { pattern: /\b__proto__\b/, hint: "__proto__" }
474
+ ];
475
+ function findBlockedTemplatePattern(code) {
476
+ for (const { pattern, hint } of TEMPLATE_BLOCKED_PATTERNS) {
477
+ if (pattern.test(code)) return { hint };
478
+ }
479
+ return null;
480
+ }
481
+ var SAFE_DATA_URI_PREFIXES = [
482
+ "data:image/png;",
483
+ "data:image/jpeg;",
484
+ "data:image/gif;",
485
+ "data:image/webp;",
486
+ "data:image/avif;"
487
+ ];
488
+ function isSafeTemplateUrl(value) {
489
+ if (typeof value !== "string") return false;
490
+ if (value.startsWith("/") || value.startsWith("./") || value.startsWith("../") || value.startsWith("#")) {
491
+ return true;
492
+ }
493
+ if (SAFE_DATA_URI_PREFIXES.some((prefix) => value.startsWith(prefix))) {
494
+ return true;
495
+ }
496
+ try {
497
+ const url = new URL(value);
498
+ return url.protocol === "http:" || url.protocol === "https:";
499
+ } catch {
500
+ return false;
501
+ }
502
+ }
503
+ var URL_PROPS = /* @__PURE__ */ new Set([
504
+ "src",
505
+ "href",
506
+ "xlinkhref",
507
+ "xlink:href",
508
+ // Obsolete but still implemented URL-bearing attributes on allowed elements.
509
+ "background",
510
+ "longdesc",
511
+ "usemap"
512
+ ]);
513
+ function isTemplateUrlProp(name) {
514
+ return URL_PROPS.has(name.toLowerCase());
515
+ }
516
+ var SRCSET_PROPS = /* @__PURE__ */ new Set([
517
+ "srcset",
518
+ // React spells the preload attribute `imageSrcSet`; keep it guarded if the
519
+ // element allowlist ever expands to include `link`.
520
+ "imagesrcset"
521
+ ]);
522
+ function isTemplateSrcSetProp(name) {
523
+ return SRCSET_PROPS.has(name.toLowerCase());
524
+ }
525
+ function isSafeTemplateSrcSet(value) {
526
+ if (typeof value !== "string" || value.trim() === "") return false;
527
+ return value.split(",").every((candidate) => {
528
+ const parts = candidate.trim().split(/\s+/);
529
+ if (parts.length === 0 || parts.length > 2 || !isSafeTemplateUrl(parts[0])) {
530
+ return false;
531
+ }
532
+ const descriptor = parts[1];
533
+ if (descriptor === void 0) return true;
534
+ if (/^[1-9]\d*w$/.test(descriptor)) return true;
535
+ if (/^(?:\d+(?:\.\d+)?|\.\d+)x$/.test(descriptor)) {
536
+ return Number.parseFloat(descriptor) > 0;
537
+ }
538
+ return false;
539
+ });
540
+ }
541
+ var URL_FUNCTION_PATTERN = /url\s*\(/i;
542
+ var SVG_URL_FUNCTION_PROPS = /* @__PURE__ */ new Set([
543
+ "clip-path",
544
+ "clippath",
545
+ "color-profile",
546
+ "colorprofile",
547
+ "cursor",
548
+ "fill",
549
+ "filter",
550
+ "marker",
551
+ "marker-end",
552
+ "marker-mid",
553
+ "marker-start",
554
+ "markerend",
555
+ "markermid",
556
+ "markerstart",
557
+ "mask",
558
+ "stroke"
559
+ ]);
560
+ function isTemplatePaintProp(name) {
561
+ return SVG_URL_FUNCTION_PROPS.has(name.toLowerCase());
562
+ }
563
+ function isSafeTemplatePaintValue(value) {
564
+ if (typeof value === "string") return !URL_FUNCTION_PATTERN.test(value);
565
+ if (!Array.isArray(value) || value.length === 0) return false;
566
+ return value.every(
567
+ (candidate) => typeof candidate === "string" && !URL_FUNCTION_PATTERN.test(candidate)
568
+ );
569
+ }
570
+ var BLOCKED_STYLE_PROPS = /* @__PURE__ */ new Set([
571
+ "backgroundImage",
572
+ "background",
573
+ "listStyleImage",
574
+ "content",
575
+ "borderImage",
576
+ "borderImageSource",
577
+ "maskImage",
578
+ "mask",
579
+ "filter",
580
+ "cursor"
581
+ ]);
582
+ function sanitizeTemplateStyle(style) {
583
+ if (!style || typeof style !== "object" || Array.isArray(style)) return {};
584
+ const safe = {};
585
+ for (const [key, value] of Object.entries(style)) {
586
+ if (BLOCKED_STYLE_PROPS.has(key)) continue;
587
+ if (typeof value === "string" && URL_FUNCTION_PATTERN.test(value)) continue;
588
+ if (key === "position" && typeof value === "string" && /fixed|sticky/i.test(value)) {
589
+ continue;
590
+ }
591
+ if (key === "zIndex") {
592
+ const numericValue = typeof value === "number" ? value : parseInt(String(value), 10);
593
+ if (isNaN(numericValue) || numericValue > 9998) continue;
594
+ }
595
+ safe[key] = value;
596
+ }
597
+ return safe;
598
+ }
599
+ function sanitizeTemplateProps(props) {
600
+ const safeProps = {};
601
+ for (const [key, value] of Object.entries(props)) {
602
+ const normalizedKey = key.toLowerCase();
603
+ if (normalizedKey.startsWith("on")) continue;
604
+ if (key === "dangerouslySetInnerHTML" || key === "ref") continue;
605
+ if (isTemplateUrlProp(key) && !isSafeTemplateUrl(value)) continue;
606
+ if (isTemplateSrcSetProp(key) && !isSafeTemplateSrcSet(value)) continue;
607
+ if (isTemplatePaintProp(key) && !isSafeTemplatePaintValue(value)) {
608
+ continue;
609
+ }
610
+ if (normalizedKey === "style") {
611
+ safeProps.style = sanitizeTemplateStyle(value);
612
+ continue;
613
+ }
614
+ safeProps[key] = value;
615
+ }
616
+ return safeProps;
617
+ }
618
+
619
+ // src/ui/Canvas/template-compiler.ts
620
+ var WIRE_FORMAT_VERSION = 1;
621
+ var MAX_CACHE_SIZE = 100;
622
+ var MAX_MATERIALIZE_DEPTH = 20;
451
623
  var VM_SETUP = `
452
624
  var React = {
453
625
  createElement: function(type, props) {
@@ -484,52 +656,7 @@ function hashCode(str) {
484
656
  return hash.toString(36);
485
657
  }
486
658
  function validateTemplateCode(code) {
487
- return !BLOCKED_PATTERNS.some((pattern) => pattern.test(code));
488
- }
489
- var SAFE_DATA_URI_PREFIXES = [
490
- "data:image/png;",
491
- "data:image/jpeg;",
492
- "data:image/gif;",
493
- "data:image/webp;",
494
- "data:image/avif;"
495
- ];
496
- function isSafeUrl(value) {
497
- if (typeof value !== "string") return false;
498
- if (value.startsWith("/") || value.startsWith("./") || value.startsWith("../")) return true;
499
- if (SAFE_DATA_URI_PREFIXES.some((p) => value.startsWith(p))) return true;
500
- try {
501
- const url = new URL(value);
502
- return url.protocol === "http:" || url.protocol === "https:";
503
- } catch {
504
- return false;
505
- }
506
- }
507
- var BLOCKED_STYLE_PROPS = /* @__PURE__ */ new Set([
508
- "backgroundImage",
509
- "background",
510
- "listStyleImage",
511
- "content",
512
- "borderImage",
513
- "borderImageSource",
514
- "maskImage",
515
- "mask",
516
- "filter",
517
- "cursor"
518
- ]);
519
- function sanitizeStyle(style) {
520
- if (!style || typeof style !== "object" || Array.isArray(style)) return {};
521
- const safe = {};
522
- for (const [k, v] of Object.entries(style)) {
523
- if (BLOCKED_STYLE_PROPS.has(k)) continue;
524
- if (typeof v === "string" && /url\s*\(/i.test(v)) continue;
525
- if (k === "position" && typeof v === "string" && /fixed|sticky/i.test(v)) continue;
526
- if (k === "zIndex") {
527
- const n = typeof v === "number" ? v : parseInt(String(v), 10);
528
- if (isNaN(n) || n > 9998) continue;
529
- }
530
- safe[k] = v;
531
- }
532
- return safe;
659
+ return findBlockedTemplatePattern(code) === null;
533
660
  }
534
661
  function materialize(node, depth = 0) {
535
662
  if (depth > MAX_MATERIALIZE_DEPTH) return null;
@@ -557,18 +684,8 @@ function materialize(node, depth = 0) {
557
684
  const type = String(d.type ?? "");
558
685
  const props = d.props && typeof d.props === "object" ? d.props : {};
559
686
  const ch = Array.isArray(d.ch) ? d.ch : [];
560
- if (!ALLOWED_ELEMENTS.has(type.toLowerCase())) return null;
561
- const safeProps = {};
562
- for (const [k, v] of Object.entries(props)) {
563
- if (k.startsWith("on")) continue;
564
- if (k === "dangerouslySetInnerHTML" || k === "ref") continue;
565
- if ((k === "src" || k === "href") && !isSafeUrl(v)) continue;
566
- if (k === "style") {
567
- safeProps[k] = sanitizeStyle(v);
568
- continue;
569
- }
570
- safeProps[k] = v;
571
- }
687
+ if (!isAllowedTemplateElement(type)) return null;
688
+ const safeProps = sanitizeTemplateProps(props);
572
689
  const children = ch.map((c) => materialize(c, depth + 1));
573
690
  return import_react4.default.createElement(type, safeProps, ...children);
574
691
  }
@@ -852,19 +969,22 @@ function sanitizeImageData(image, safeUrl) {
852
969
  ) : image.sizes
853
970
  };
854
971
  }
855
- function renderFieldValue(key, val, fieldDef) {
972
+ function renderFieldValue(key, val, fieldDef, imageAlt) {
856
973
  if (val == null || val === "") return null;
857
974
  const fieldType = fieldDef?.fieldType;
858
975
  if (fieldType === "image" || typeof val === "object" && val !== null && "url" in val) {
859
976
  const imgUrl = typeof val === "string" ? val : val?.url;
860
977
  const safeUrl = sanitizeUrl(imgUrl);
861
978
  if (!safeUrl) return null;
979
+ const embeddedAlt = typeof val === "object" && val !== null && "alt" in val && typeof val.alt === "string" ? val.alt : void 0;
980
+ const alt = imageAlt?.authored !== void 0 ? imageAlt.authored : embeddedAlt !== void 0 ? embeddedAlt : imageAlt?.fallback ?? "";
862
981
  if (fieldType === "image" && typeof val === "object" && val !== null) {
863
982
  return /* @__PURE__ */ import_react6.default.createElement(
864
983
  Image,
865
984
  {
866
985
  key,
867
986
  image: sanitizeImageData(val, safeUrl),
987
+ alt,
868
988
  fill: true,
869
989
  objectFit: "contain",
870
990
  style: { flex: 1, minHeight: 0, width: "100%" }
@@ -876,7 +996,7 @@ function renderFieldValue(key, val, fieldDef) {
876
996
  {
877
997
  key,
878
998
  src: safeUrl,
879
- alt: "",
999
+ alt,
880
1000
  draggable: false,
881
1001
  style: { flex: 1, minHeight: 0, width: "100%", objectFit: "contain" }
882
1002
  }
@@ -899,6 +1019,7 @@ function renderFieldValue(key, val, fieldDef) {
899
1019
  }
900
1020
  function DefaultDynamicNode({ data }) {
901
1021
  const d = data;
1022
+ const authoredImageAlt = typeof d.fields?.alt === "string" ? d.fields.alt : void 0;
902
1023
  return /* @__PURE__ */ import_react6.default.createElement(
903
1024
  "div",
904
1025
  {
@@ -909,7 +1030,12 @@ function DefaultDynamicNode({ data }) {
909
1030
  flexDirection: "column"
910
1031
  }
911
1032
  },
912
- d.fields && Object.entries(d.fields).filter(([, v]) => v != null && v !== "").map(([key, val]) => renderFieldValue(key, val))
1033
+ d.fields && Object.entries(d.fields).filter(([, v]) => v != null && v !== "").map(
1034
+ ([key, val]) => renderFieldValue(key, val, void 0, {
1035
+ authored: authoredImageAlt,
1036
+ fallback: d.label
1037
+ })
1038
+ )
913
1039
  );
914
1040
  }
915
1041
  var TemplateErrorBoundary = class extends import_react6.default.Component {
@@ -939,13 +1065,14 @@ function EnhancedDynamicNode({
939
1065
  height
940
1066
  }) {
941
1067
  useQuickJS();
1068
+ const authoredImageAlt = typeof data.fields?.alt === "string" ? data.fields.alt : void 0;
942
1069
  if (typeDef.template) {
943
1070
  const Component = compileTemplate(typeDef.template, typeDef.slug);
944
1071
  if (Component) {
945
1072
  return /* @__PURE__ */ import_react6.default.createElement(
946
1073
  "div",
947
1074
  {
948
- className: `flow-node flow-node--${typeDef.slug}${typeDef.transparentBackground ? " flow-node--transparent-bg" : ""}`,
1075
+ className: `flow-node flow-node--${typeDef.slug}${typeDef.transparentBackground ? "flow-node--transparent-bg" : ""}`,
949
1076
  style: { width: "100%", height: "100%" }
950
1077
  },
951
1078
  /* @__PURE__ */ import_react6.default.createElement(TemplateErrorBoundary, { resetKey: typeDef.template }, /* @__PURE__ */ import_react6.default.createElement(
@@ -976,7 +1103,10 @@ function EnhancedDynamicNode({
976
1103
  typeDef.fields.map((f) => {
977
1104
  const val = data.fields[f.name];
978
1105
  if (val == null || val === "") return null;
979
- return renderFieldValue(f.name, val, f);
1106
+ return renderFieldValue(f.name, val, f, {
1107
+ authored: authoredImageAlt,
1108
+ fallback: data.label
1109
+ });
980
1110
  })
981
1111
  );
982
1112
  }
@@ -1022,6 +1152,90 @@ function DefaultFrameNode({ data }) {
1022
1152
  )
1023
1153
  );
1024
1154
  }
1155
+ var DEFAULT_SHAPE_COLOR = "#dbeafe";
1156
+ var SHAPE_STROKE = "#334155";
1157
+ function ShapeGeometry({ shape, color }) {
1158
+ const common = {
1159
+ fill: color,
1160
+ stroke: SHAPE_STROKE,
1161
+ strokeWidth: 2,
1162
+ vectorEffect: "non-scaling-stroke"
1163
+ };
1164
+ switch (shape) {
1165
+ case "terminator":
1166
+ return /* @__PURE__ */ import_react6.default.createElement("rect", { x: "1", y: "1", width: "198", height: "98", rx: "49", ...common });
1167
+ case "decision":
1168
+ return /* @__PURE__ */ import_react6.default.createElement("polygon", { points: "100,1 199,50 100,99 1,50", ...common });
1169
+ case "io":
1170
+ return /* @__PURE__ */ import_react6.default.createElement("polygon", { points: "25,1 199,1 175,99 1,99", ...common });
1171
+ case "subprocess":
1172
+ return /* @__PURE__ */ import_react6.default.createElement("g", null, /* @__PURE__ */ import_react6.default.createElement("rect", { x: "1", y: "1", width: "198", height: "98", rx: "3", ...common }), /* @__PURE__ */ import_react6.default.createElement(
1173
+ "path",
1174
+ {
1175
+ d: "M20 1V99M180 1V99",
1176
+ fill: "none",
1177
+ stroke: SHAPE_STROKE,
1178
+ strokeWidth: "2",
1179
+ vectorEffect: "non-scaling-stroke"
1180
+ }
1181
+ ));
1182
+ case "process":
1183
+ default:
1184
+ return /* @__PURE__ */ import_react6.default.createElement("rect", { x: "1", y: "1", width: "198", height: "98", rx: "3", ...common });
1185
+ }
1186
+ }
1187
+ function DefaultShapeNode({ data }) {
1188
+ const shapeData = data;
1189
+ return /* @__PURE__ */ import_react6.default.createElement(
1190
+ "div",
1191
+ {
1192
+ className: `flow-node flow-node--shape flow-node--shape-${shapeData.shape}`,
1193
+ style: { width: "100%", height: "100%", position: "relative" },
1194
+ "data-shape": shapeData.shape
1195
+ },
1196
+ /* @__PURE__ */ import_react6.default.createElement(
1197
+ "svg",
1198
+ {
1199
+ "aria-hidden": "true",
1200
+ focusable: "false",
1201
+ viewBox: "0 0 200 100",
1202
+ preserveAspectRatio: "none",
1203
+ style: {
1204
+ position: "absolute",
1205
+ inset: 0,
1206
+ width: "100%",
1207
+ height: "100%"
1208
+ }
1209
+ },
1210
+ /* @__PURE__ */ import_react6.default.createElement(
1211
+ ShapeGeometry,
1212
+ {
1213
+ shape: shapeData.shape,
1214
+ color: shapeData.color ?? DEFAULT_SHAPE_COLOR
1215
+ }
1216
+ )
1217
+ ),
1218
+ /* @__PURE__ */ import_react6.default.createElement(
1219
+ "div",
1220
+ {
1221
+ style: {
1222
+ position: "absolute",
1223
+ inset: "12%",
1224
+ display: "flex",
1225
+ alignItems: "center",
1226
+ justifyContent: "center",
1227
+ overflow: "hidden",
1228
+ color: "#0f172a",
1229
+ fontSize: 12,
1230
+ fontWeight: 600,
1231
+ textAlign: "center",
1232
+ wordBreak: "break-word"
1233
+ }
1234
+ },
1235
+ shapeData.label
1236
+ )
1237
+ );
1238
+ }
1025
1239
 
1026
1240
  // src/ui/Canvas/field-helpers.ts
1027
1241
  function getImageField(fields, name) {
@@ -1234,9 +1448,12 @@ var import_react12 = require("@xyflow/react");
1234
1448
 
1235
1449
  // src/ui/Canvas/node-types-factory.tsx
1236
1450
  var import_react7 = __toESM(require("react"), 1);
1451
+ function areNodePropsEqual(prev, next) {
1452
+ return prev.id === next.id && prev.data === next.data && prev.selected === next.selected && prev.dragging === next.dragging && prev.width === next.width && prev.height === next.height;
1453
+ }
1237
1454
  function createNodeTypes(nodeRenderers, nodeTypeDefsMap, frameRenderer, nodeWrapper, renderNode) {
1238
1455
  const types = {};
1239
- types.dynamic = ((props) => {
1456
+ const DynamicNode = (props) => {
1240
1457
  const d = props.data;
1241
1458
  const typeDef = nodeTypeDefsMap?.get(d.nodeTypeSlug);
1242
1459
  const CustomRenderer = nodeRenderers?.[d.nodeTypeSlug];
@@ -1285,8 +1502,12 @@ function createNodeTypes(nodeRenderers, nodeTypeDefsMap, frameRenderer, nodeWrap
1285
1502
  );
1286
1503
  }
1287
1504
  return content;
1288
- });
1289
- types.frame = frameRenderer ? ((props) => {
1505
+ };
1506
+ types.dynamic = import_react7.default.memo(
1507
+ DynamicNode,
1508
+ areNodePropsEqual
1509
+ );
1510
+ const FrameNode = frameRenderer ? (props) => {
1290
1511
  const d = props.data;
1291
1512
  const Renderer = frameRenderer;
1292
1513
  return /* @__PURE__ */ import_react7.default.createElement(
@@ -1302,7 +1523,10 @@ function createNodeTypes(nodeRenderers, nodeTypeDefsMap, frameRenderer, nodeWrap
1302
1523
  height: props.height
1303
1524
  }
1304
1525
  );
1305
- }) : DefaultFrameNode;
1526
+ } : DefaultFrameNode;
1527
+ types.frame = import_react7.default.memo(FrameNode, areNodePropsEqual);
1528
+ const ShapeNode = (props) => /* @__PURE__ */ import_react7.default.createElement(DefaultShapeNode, { ...props });
1529
+ types.shape = import_react7.default.memo(ShapeNode, areNodePropsEqual);
1306
1530
  return types;
1307
1531
  }
1308
1532
  function createEdgeTypes(edgeRenderers, edgeTypeDefsMap) {
@@ -1512,18 +1736,82 @@ function FocusHandler({
1512
1736
  );
1513
1737
  }
1514
1738
 
1739
+ // src/ui/Canvas/accessibility.ts
1740
+ var CANVAS_ACCESSIBILITY_LABELS = {
1741
+ ko: {
1742
+ canvas: "\uCE94\uBC84\uC2A4 \uB2E4\uC774\uC5B4\uADF8\uB7A8",
1743
+ node: "\uB178\uB4DC",
1744
+ frame: "\uD504\uB808\uC784",
1745
+ ariaLabelConfig: {
1746
+ "node.a11yDescription.default": "\uCE94\uBC84\uC2A4 \uB2E4\uC774\uC5B4\uADF8\uB7A8\uC758 \uB178\uB4DC\uC785\uB2C8\uB2E4.",
1747
+ "node.a11yDescription.keyboardDisabled": "\uCE94\uBC84\uC2A4 \uB2E4\uC774\uC5B4\uADF8\uB7A8\uC758 \uB178\uB4DC\uC785\uB2C8\uB2E4.",
1748
+ "edge.a11yDescription.default": "\uCE94\uBC84\uC2A4 \uB2E4\uC774\uC5B4\uADF8\uB7A8\uC758 \uC5F0\uACB0\uC120\uC785\uB2C8\uB2E4.",
1749
+ "controls.ariaLabel": "\uCE94\uBC84\uC2A4 \uC81C\uC5B4\uD310",
1750
+ "controls.zoomIn.ariaLabel": "\uD655\uB300",
1751
+ "controls.zoomOut.ariaLabel": "\uCD95\uC18C",
1752
+ "controls.fitView.ariaLabel": "\uD654\uBA74\uC5D0 \uB9DE\uCD94\uAE30",
1753
+ "controls.interactive.ariaLabel": "\uC0C1\uD638\uC791\uC6A9 \uC804\uD658",
1754
+ "minimap.ariaLabel": "\uBBF8\uB2C8\uB9F5",
1755
+ "handle.ariaLabel": "\uC5F0\uACB0\uC810"
1756
+ }
1757
+ },
1758
+ en: {
1759
+ canvas: "Canvas diagram",
1760
+ node: "Node",
1761
+ frame: "Frame",
1762
+ ariaLabelConfig: {
1763
+ "node.a11yDescription.default": "A node in the canvas diagram.",
1764
+ "node.a11yDescription.keyboardDisabled": "A node in the canvas diagram.",
1765
+ "edge.a11yDescription.default": "A connection in the canvas diagram.",
1766
+ "controls.ariaLabel": "Canvas controls",
1767
+ "controls.zoomIn.ariaLabel": "Zoom in",
1768
+ "controls.zoomOut.ariaLabel": "Zoom out",
1769
+ "controls.fitView.ariaLabel": "Fit view",
1770
+ "controls.interactive.ariaLabel": "Toggle interactivity",
1771
+ "minimap.ariaLabel": "Mini map",
1772
+ "handle.ariaLabel": "Handle"
1773
+ }
1774
+ }
1775
+ };
1776
+ function nodeAriaLabel(node, labels) {
1777
+ const explicitLabel = typeof node.ariaLabel === "string" ? node.ariaLabel.trim() : "";
1778
+ if (explicitLabel) return explicitLabel;
1779
+ const isFrame = node.type === "frame";
1780
+ const label = node.data?.label?.trim();
1781
+ if (!label) return isFrame ? labels.frame : labels.node;
1782
+ return isFrame ? `${label} (${labels.frame})` : label;
1783
+ }
1784
+
1515
1785
  // src/ui/Canvas/CanvasRenderer.tsx
1786
+ function usePrefersReducedMotion() {
1787
+ const query = "(prefers-reduced-motion: reduce)";
1788
+ const [prefersReducedMotion, setPrefersReducedMotion] = import_react11.default.useState(
1789
+ () => typeof window !== "undefined" && window.matchMedia?.(query).matches
1790
+ );
1791
+ import_react11.default.useEffect(() => {
1792
+ if (typeof window === "undefined" || !window.matchMedia) return;
1793
+ const mediaQuery = window.matchMedia(query);
1794
+ const handleChange = (event) => setPrefersReducedMotion(event.matches);
1795
+ setPrefersReducedMotion(mediaQuery.matches);
1796
+ mediaQuery.addEventListener("change", handleChange);
1797
+ return () => mediaQuery.removeEventListener("change", handleChange);
1798
+ }, []);
1799
+ return prefersReducedMotion;
1800
+ }
1516
1801
  var NullFrameRenderer = () => null;
1517
1802
  function CanvasRenderer({
1518
1803
  data,
1519
1804
  className,
1520
1805
  style,
1806
+ ariaLabel,
1807
+ locale = "en",
1521
1808
  nodeRenderers,
1522
1809
  nodeTypeDefs,
1523
1810
  edgeTypeDefs,
1524
1811
  background = false,
1525
1812
  interactive = false,
1526
1813
  fitView = true,
1814
+ onlyRenderVisibleElements = true,
1527
1815
  onNodeClick,
1528
1816
  onNodeDoubleClick,
1529
1817
  onNodeContextMenu,
@@ -1551,6 +1839,8 @@ function CanvasRenderer({
1551
1839
  minZoom: minZoomProp,
1552
1840
  maxZoom: maxZoomProp
1553
1841
  }) {
1842
+ const prefersReducedMotion = usePrefersReducedMotion();
1843
+ const accessibilityLabels = CANVAS_ACCESSIBILITY_LABELS[locale];
1554
1844
  const resolvedFrameRenderer = hideFrames ? NullFrameRenderer : frameRenderer;
1555
1845
  const nodeTypeDefsMap = import_react11.default.useMemo(() => {
1556
1846
  if (!nodeTypeDefs?.length) return void 0;
@@ -1568,7 +1858,13 @@ function CanvasRenderer({
1568
1858
  nodeWrapper,
1569
1859
  renderNode
1570
1860
  ),
1571
- [nodeRenderers, nodeTypeDefsMap, resolvedFrameRenderer, nodeWrapper, renderNode]
1861
+ [
1862
+ nodeRenderers,
1863
+ nodeTypeDefsMap,
1864
+ resolvedFrameRenderer,
1865
+ nodeWrapper,
1866
+ renderNode
1867
+ ]
1572
1868
  );
1573
1869
  const customEdgeTypes = import_react11.default.useMemo(
1574
1870
  () => createEdgeTypes(edgeRenderers, edgeTypeDefsMap),
@@ -1591,6 +1887,21 @@ function CanvasRenderer({
1591
1887
  }
1592
1888
  return edges;
1593
1889
  }, [data?.edges, edgeTypeDefsMap, edgeRenderers]);
1890
+ const accessibleNodes = import_react11.default.useMemo(
1891
+ () => (data?.nodes ?? []).map((node) => ({
1892
+ ...node,
1893
+ ...node.type === "shape" ? {
1894
+ style: {
1895
+ ...node.style,
1896
+ background: "transparent",
1897
+ border: "none",
1898
+ padding: 0
1899
+ }
1900
+ } : {},
1901
+ ariaLabel: nodeAriaLabel(node, accessibilityLabels)
1902
+ })),
1903
+ [data?.nodes, accessibilityLabels]
1904
+ );
1594
1905
  const translateExtent = import_react11.default.useMemo(() => {
1595
1906
  if (translateExtentProp) return translateExtentProp;
1596
1907
  const es = clampBounds ?? bounds;
@@ -1625,6 +1936,8 @@ function CanvasRenderer({
1625
1936
  return /* @__PURE__ */ import_react11.default.createElement(import_react12.ReactFlowProvider, null, /* @__PURE__ */ import_react11.default.createElement(
1626
1937
  "div",
1627
1938
  {
1939
+ role: "region",
1940
+ "aria-label": ariaLabel ?? accessibilityLabels.canvas,
1628
1941
  className,
1629
1942
  style: {
1630
1943
  width: "100%",
@@ -1636,12 +1949,13 @@ function CanvasRenderer({
1636
1949
  /* @__PURE__ */ import_react11.default.createElement(
1637
1950
  import_react12.ReactFlow,
1638
1951
  {
1639
- nodes: data.nodes ?? [],
1952
+ nodes: accessibleNodes,
1640
1953
  edges: styledEdges,
1641
1954
  nodeTypes,
1642
1955
  edgeTypes: customEdgeTypes,
1643
1956
  defaultViewport: resolvedDefaultViewport,
1644
1957
  fitView: bounds ? false : fitView,
1958
+ onlyRenderVisibleElements,
1645
1959
  translateExtent: activeExtent,
1646
1960
  onNodeClick,
1647
1961
  onNodeDoubleClick,
@@ -1655,13 +1969,15 @@ function CanvasRenderer({
1655
1969
  nodesDraggable: interactive,
1656
1970
  nodesConnectable: false,
1657
1971
  elementsSelectable: interactive || !!onNodeClick || !!onNodeDoubleClick || !!onEdgeClick,
1972
+ autoPanOnSelection: false,
1658
1973
  panOnDrag: interactive,
1659
1974
  zoomOnScroll: interactive,
1660
1975
  zoomOnPinch: interactive,
1661
1976
  zoomOnDoubleClick: false,
1662
1977
  minZoom: minZoomProp,
1663
1978
  maxZoom: maxZoomProp,
1664
- proOptions: { hideAttribution: true }
1979
+ proOptions: { hideAttribution: true },
1980
+ ariaLabelConfig: accessibilityLabels.ariaLabelConfig
1665
1981
  },
1666
1982
  mergedCSS && /* @__PURE__ */ import_react11.default.createElement("style", { dangerouslySetInnerHTML: { __html: mergedCSS } }),
1667
1983
  background && /* @__PURE__ */ import_react11.default.createElement(import_react12.Background, null),
@@ -1677,7 +1993,7 @@ function CanvasRenderer({
1677
1993
  {
1678
1994
  bounds,
1679
1995
  padding: focusPadding ?? 0.1,
1680
- animation: focusAnimation ?? true,
1996
+ animation: prefersReducedMotion ? false : focusAnimation ?? true,
1681
1997
  mode: focusMode,
1682
1998
  responsive: responsiveFit ?? true,
1683
1999
  extent: translateExtent,