@puckeditor/plugin-ai 0.9.0-canary.d8bdabee → 0.9.0-canary.f2867fd7

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 (3) hide show
  1. package/dist/index.js +623 -411
  2. package/dist/index.mjs +607 -395
  3. package/package.json +9 -9
package/dist/index.js CHANGED
@@ -995,7 +995,7 @@ function useChat({
995
995
  // src/components/Chat/index.tsx
996
996
  var import_core5 = require("@puckeditor/core");
997
997
  var import_ai5 = require("ai");
998
- var import_react28 = require("react");
998
+ var import_react27 = require("react");
999
999
  var import_ulid = require("ulid");
1000
1000
 
1001
1001
  // ../utils/lib/get-class-name-factory.ts
@@ -1814,6 +1814,42 @@ var matchesAccept = (file, accept) => {
1814
1814
  return file.type === entry;
1815
1815
  });
1816
1816
  };
1817
+ var documentExtensionByMediaType = {
1818
+ "application/msword": "doc",
1819
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document": "docx",
1820
+ "text/markdown": "md",
1821
+ "application/pdf": "pdf",
1822
+ "text/plain": "txt"
1823
+ };
1824
+ var mediaTypeToExtension = (mediaType) => {
1825
+ const mapped = documentExtensionByMediaType[mediaType];
1826
+ if (mapped) {
1827
+ return mapped;
1828
+ }
1829
+ const subtype = mediaType.split("/")[1] ?? "";
1830
+ return subtype.split("+")[0] || "bin";
1831
+ };
1832
+ var getFilesFromClipboard = (data) => {
1833
+ const files = [];
1834
+ for (const item of data.items ? Array.from(data.items) : []) {
1835
+ if (item.kind === "file") {
1836
+ const file = item.getAsFile();
1837
+ if (file) {
1838
+ files.push(file);
1839
+ }
1840
+ }
1841
+ }
1842
+ if (files.length === 0 && data.files) {
1843
+ files.push(...Array.from(data.files));
1844
+ }
1845
+ return files.map(
1846
+ (file, index) => file.name ? file : new File(
1847
+ [file],
1848
+ `pasted-${Date.now()}-${index}.${mediaTypeToExtension(file.type)}`,
1849
+ { type: file.type }
1850
+ )
1851
+ );
1852
+ };
1817
1853
  var getAttachmentIdFromRef = (url) => url.startsWith("attachment://") ? url.slice("attachment://".length) : null;
1818
1854
  var resolveAttachmentUrl = (url, attachmentEndpoint) => {
1819
1855
  if (!attachmentEndpoint) {
@@ -1896,6 +1932,16 @@ function PromptForm({
1896
1932
  },
1897
1933
  [canDrop, onFileDrop]
1898
1934
  );
1935
+ const handlePaste = (0, import_react19.useCallback)(
1936
+ (e) => {
1937
+ if (!canDrop) return;
1938
+ const files = getFilesFromClipboard(e.clipboardData);
1939
+ if (files.length === 0) return;
1940
+ e.preventDefault();
1941
+ onFileDrop(files);
1942
+ },
1943
+ [canDrop, onFileDrop]
1944
+ );
1899
1945
  (0, import_react19.useEffect)(() => {
1900
1946
  setPrompt(value);
1901
1947
  }, [setPrompt, value]);
@@ -1997,6 +2043,7 @@ function PromptForm({
1997
2043
  internalRef.current = node;
1998
2044
  },
1999
2045
  onChange: (e) => setPrompt(e.target.value),
2046
+ onPaste: onFileDrop ? handlePaste : void 0,
2000
2047
  onKeyDown: (e) => {
2001
2048
  if (!e.shiftKey && e.key === "Enter") {
2002
2049
  e.preventDefault();
@@ -2490,7 +2537,6 @@ function ExamplePrompt({
2490
2537
  // src/components/Chat/index.tsx
2491
2538
  var import_qler = __toESM(require("qler"));
2492
2539
  var import_reducer = __toESM(require_dist());
2493
- var import_html2canvas_pro = __toESM(require("html2canvas-pro"));
2494
2540
  var import_use_debounce = require("use-debounce");
2495
2541
 
2496
2542
  // src/lib/scroll-tracking-events.ts
@@ -2546,151 +2592,20 @@ var import_core4 = require("@puckeditor/core");
2546
2592
  // src/lib/design/component.tsx
2547
2593
  init_react_import();
2548
2594
  var import_core3 = require("@puckeditor/core");
2549
- var import_react25 = __toESM(require("react"));
2595
+ var import_react24 = require("react");
2550
2596
 
2551
2597
  // src/lib/morph-html.ts
2552
2598
  init_react_import();
2553
- var PROTECTED_ATTRS = /* @__PURE__ */ new Set([
2554
- "contenteditable",
2555
- "data-puck-overlay-portal"
2556
- ]);
2557
- var SLOT_OWNED_ATTRS = /* @__PURE__ */ new Set(["style", "class"]);
2558
- var isEventHandlerAttr = (name) => /^on/i.test(name);
2559
- var cssEscape = (value) => typeof CSS !== "undefined" && CSS.escape ? CSS.escape(value) : value.replace(/["\\]/g, "\\$&");
2560
- var FIELD_ATTR_PREFIX = "data-puck-field-";
2561
- var hasJsonFieldBinding = (el) => {
2562
- const attrBoundNames = /* @__PURE__ */ new Set();
2563
- const jsonFieldNames = [];
2564
- for (const attr of Array.from(el.attributes)) {
2565
- if (!attr.name.startsWith(FIELD_ATTR_PREFIX)) continue;
2566
- if (attr.name.startsWith("data-puck-field-value-")) continue;
2567
- if (attr.value.trimStart().startsWith("{")) {
2568
- const fieldName = attr.name.slice(FIELD_ATTR_PREFIX.length);
2569
- if (fieldName && fieldName !== "name") {
2570
- jsonFieldNames.push(fieldName);
2571
- }
2572
- } else {
2573
- const fieldName = attr.value.trim();
2574
- if (fieldName) attrBoundNames.add(fieldName);
2575
- }
2576
- }
2577
- return jsonFieldNames.some((name) => !attrBoundNames.has(name));
2578
- };
2579
- var ownsItsChildren = (el) => el.hasAttribute("data-puck-slot") || el.hasAttribute("data-puck-field-name") || el.hasAttribute("data-puck-array") || hasJsonFieldBinding(el);
2580
- var syncAttributes = (target, source) => {
2581
- const isSlot = target.hasAttribute("data-puck-slot");
2582
- for (const attr of Array.from(target.attributes)) {
2583
- if (PROTECTED_ATTRS.has(attr.name)) continue;
2584
- if (isSlot && SLOT_OWNED_ATTRS.has(attr.name)) continue;
2585
- if (isEventHandlerAttr(attr.name) || !source.hasAttribute(attr.name)) {
2586
- target.removeAttribute(attr.name);
2587
- }
2588
- }
2589
- for (const attr of Array.from(source.attributes)) {
2590
- if (PROTECTED_ATTRS.has(attr.name)) continue;
2591
- if (isSlot && SLOT_OWNED_ATTRS.has(attr.name)) continue;
2592
- if (isEventHandlerAttr(attr.name)) continue;
2593
- if (target.getAttribute(attr.name) !== attr.value) {
2594
- target.setAttribute(attr.name, attr.value);
2595
- }
2596
- }
2597
- };
2598
- var isSameKind = (a, b) => a.nodeType === b.nodeType && (a.nodeType !== Node.ELEMENT_NODE || a.tagName === b.tagName);
2599
- var isIncompleteTagArtifact = (node) => node.nodeType === Node.TEXT_NODE && /^<\/?[a-zA-Z]*$/.test(node.nodeValue ?? "");
2600
- var stripIncompleteTagArtifacts = (root) => {
2601
- for (const child of Array.from(root.childNodes)) {
2602
- if (isIncompleteTagArtifact(child)) {
2603
- root.removeChild(child);
2604
- } else if (child.nodeType === Node.ELEMENT_NODE) {
2605
- stripIncompleteTagArtifacts(child);
2606
- }
2607
- }
2608
- };
2609
- var stripEventHandlerAttrs = (root) => {
2610
- root.querySelectorAll("*").forEach((el) => {
2611
- for (const attr of Array.from(el.attributes)) {
2612
- if (isEventHandlerAttr(attr.name)) el.removeAttribute(attr.name);
2613
- }
2614
- });
2615
- };
2616
- var morphChildren = (target, source, skipChildrenOf = ownsItsChildren) => {
2617
- const sourceChildren = Array.from(source.childNodes);
2618
- for (let i = 0; i < sourceChildren.length; i++) {
2619
- const sourceChild = sourceChildren[i];
2620
- const targetChild = target.childNodes[i];
2621
- if (!targetChild) {
2622
- target.appendChild(sourceChild);
2623
- continue;
2624
- }
2625
- if (!isSameKind(targetChild, sourceChild)) {
2626
- target.replaceChild(sourceChild, targetChild);
2627
- continue;
2628
- }
2629
- if (sourceChild.nodeType !== Node.ELEMENT_NODE) {
2630
- if (targetChild.nodeValue !== sourceChild.nodeValue) {
2631
- targetChild.nodeValue = sourceChild.nodeValue;
2632
- }
2633
- continue;
2634
- }
2635
- const targetEl = targetChild;
2636
- syncAttributes(targetEl, sourceChild);
2637
- if (!skipChildrenOf(targetEl)) {
2638
- morphChildren(targetEl, sourceChild, skipChildrenOf);
2639
- }
2640
- }
2641
- while (target.childNodes.length > sourceChildren.length) {
2642
- target.removeChild(target.lastChild);
2643
- }
2644
- };
2645
- function morphHtml(container, html, attrOverrides = [], skipChildrenOf = ownsItsChildren) {
2646
- const template = container.ownerDocument.createElement("template");
2647
- template.innerHTML = html;
2648
- stripIncompleteTagArtifacts(template.content);
2649
- stripEventHandlerAttrs(template.content);
2650
- for (const { name, attr, value } of attrOverrides) {
2651
- template.content.querySelectorAll(`[data-puck-field-${attr}="${cssEscape(name)}"]`).forEach((el) => {
2652
- if (el.closest("[data-puck-array]")) return;
2653
- el.setAttribute(attr, value);
2654
- });
2655
- }
2656
- morphChildren(container, template.content, skipChildrenOf);
2657
- }
2658
2599
 
2659
- // src/lib/design/observer-guard.ts
2600
+ // src/lib/design/annotations.ts
2660
2601
  init_react_import();
2661
- var wrapScriptWithObserverGuard = (script) => {
2662
- if (!script.includes("MutationObserver")) return script;
2663
- return `(function () {
2664
- var __NativeMutationObserver = window.MutationObserver;
2665
- function MutationObserver(callback) {
2666
- var observed = [];
2667
- var inner = new __NativeMutationObserver(function (records) {
2668
- inner.disconnect();
2669
- try {
2670
- callback(records, inner);
2671
- } finally {
2672
- for (var i = 0; i < observed.length; i++) {
2673
- __nativeObserve.call(inner, observed[i][0], observed[i][1]);
2674
- }
2675
- }
2676
- });
2677
- var __nativeObserve = inner.observe;
2678
- inner.observe = function (target, options) {
2679
- observed.push([target, options]);
2680
- return __nativeObserve.call(inner, target, options);
2681
- };
2682
- return inner;
2683
- }
2684
- ${script}
2685
- })();`;
2686
- };
2687
2602
 
2688
2603
  // src/lib/html-annotations.ts
2689
2604
  init_react_import();
2690
2605
  var import_htmlparser2 = require("htmlparser2");
2691
2606
  var import_domutils = require("domutils");
2692
2607
  var import_domhandler = require("domhandler");
2693
- var FIELD_ATTR_PREFIX2 = "data-puck-field-";
2608
+ var FIELD_ATTR_PREFIX = "data-puck-field-";
2694
2609
  var OPTIONS_ATTR_PREFIX = "data-puck-options-";
2695
2610
  var BIND_ATTR_PREFIX = "data-puck-bind-";
2696
2611
  var BIND_INPUT_ATTR_PREFIX = "data-puck-bind-input-";
@@ -2700,26 +2615,21 @@ var MAX_ATTR_PREFIX = "data-puck-max-";
2700
2615
  var STEP_ATTR_PREFIX = "data-puck-step-";
2701
2616
  var SLOT_ATTR = "data-puck-slot";
2702
2617
  var ARRAY_ATTR = "data-puck-array";
2703
- var BUILT_IN_FIELD_TYPES = /* @__PURE__ */ new Set([
2618
+ var RECOGNIZED_FIELD_TYPES = /* @__PURE__ */ new Set([
2704
2619
  "text",
2705
2620
  "textarea",
2706
2621
  "number",
2707
2622
  "richtext",
2708
2623
  "radio",
2709
2624
  "select",
2710
- "slot",
2711
- "array",
2712
- "object",
2713
- "external",
2714
- "custom"
2625
+ "slot"
2715
2626
  ]);
2716
- var isBuiltInFieldType = (type) => BUILT_IN_FIELD_TYPES.has(type);
2717
- var tryParseFieldShape = (attrValue) => {
2718
- const trimmed = attrValue.trimStart();
2627
+ var tryParseFieldShape = (value) => {
2628
+ const trimmed = value.trimStart();
2719
2629
  if (!trimmed.startsWith("{")) return null;
2720
2630
  try {
2721
2631
  const parsed = JSON.parse(trimmed);
2722
- if (isRecord(parsed) && typeof parsed.type === "string" && parsed.type) {
2632
+ if (isRecord(parsed) && typeof parsed.type === "string" && RECOGNIZED_FIELD_TYPES.has(parsed.type)) {
2723
2633
  return parsed;
2724
2634
  }
2725
2635
  } catch {
@@ -2743,6 +2653,19 @@ var textContentWithBreaks = (node) => {
2743
2653
  var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2744
2654
  var FIELD_NAME_RE = /^[A-Za-z0-9_]+(?:[-.][A-Za-z0-9_]+)*$/;
2745
2655
  var isValidFieldName = (name) => FIELD_NAME_RE.test(name);
2656
+ var isFieldNameClaimed = (name, claimed, includeExact = true) => {
2657
+ for (const entry of claimed) {
2658
+ if (includeExact && entry === name || entry.startsWith(`${name}.`) || name.startsWith(`${entry}.`)) {
2659
+ return true;
2660
+ }
2661
+ }
2662
+ return false;
2663
+ };
2664
+ var attrBoundFieldNames = (attribs) => new Set(
2665
+ Object.entries(attribs).filter(
2666
+ ([name, value]) => name.startsWith(FIELD_ATTR_PREFIX) && name !== `${FIELD_ATTR_PREFIX}name` && !name.startsWith(`${FIELD_ATTR_PREFIX}value-`) && !tryParseFieldShape(value)
2667
+ ).map(([, value]) => value.trim()).filter(isValidFieldName)
2668
+ );
2746
2669
  var getDeepValue = (source, path) => {
2747
2670
  if (!path.includes(".")) return source[path];
2748
2671
  let current = source;
@@ -2843,6 +2766,7 @@ var collectElementFields = (element, acc) => {
2843
2766
  return true;
2844
2767
  }
2845
2768
  let skipChildren = false;
2769
+ const attrBound = attrBoundFieldNames(attribs);
2846
2770
  for (const [attrName, attrValue] of Object.entries(attribs)) {
2847
2771
  if (attrName.startsWith(OPTIONS_ATTR_PREFIX)) {
2848
2772
  const fieldName = attrName.slice(OPTIONS_ATTR_PREFIX.length);
@@ -2871,8 +2795,8 @@ var collectElementFields = (element, acc) => {
2871
2795
  }
2872
2796
  continue;
2873
2797
  }
2874
- if (!attrName.startsWith(FIELD_ATTR_PREFIX2)) continue;
2875
- const target = attrName.slice(FIELD_ATTR_PREFIX2.length);
2798
+ if (!attrName.startsWith(FIELD_ATTR_PREFIX)) continue;
2799
+ const target = attrName.slice(FIELD_ATTR_PREFIX.length);
2876
2800
  if (!target) continue;
2877
2801
  const shape = tryParseFieldShape(attrValue);
2878
2802
  if (shape) {
@@ -2886,17 +2810,11 @@ var collectElementFields = (element, acc) => {
2886
2810
  if (existing) existing.shape = shape;
2887
2811
  continue;
2888
2812
  }
2813
+ if (isFieldNameClaimed(name2, attrBound, false) || isFieldNameClaimed(name2, acc.seenFields)) {
2814
+ continue;
2815
+ }
2889
2816
  acc.seenFields.add(name2);
2890
- if (!isBuiltInFieldType(shape.type)) {
2891
- const { type, ...value } = shape;
2892
- acc.fields.push({
2893
- name: name2,
2894
- binding: "text",
2895
- shape: { type },
2896
- format: "json"
2897
- });
2898
- setExtractedValue(acc.values, name2, value);
2899
- } else if (shape.type === "richtext") {
2817
+ if (shape.type === "richtext") {
2900
2818
  acc.fields.push({ name: name2, binding: "text", shape, format: "json" });
2901
2819
  setExtractedValue(acc.values, name2, (0, import_domutils.getInnerHTML)(element).trim());
2902
2820
  skipChildren = true;
@@ -2949,6 +2867,9 @@ var collectElementFields = (element, acc) => {
2949
2867
  }
2950
2868
  continue;
2951
2869
  }
2870
+ if (target === "name" && isFieldNameClaimed(name, attrBound) || isFieldNameClaimed(name, acc.seenFields)) {
2871
+ continue;
2872
+ }
2952
2873
  acc.seenFields.add(name);
2953
2874
  const fieldType = getTypeModifier(attribs, name);
2954
2875
  const constraints = fieldType === "number" ? getNumberConstraints(attribs, name) : {};
@@ -3042,7 +2963,7 @@ var parseHtmlAnnotations = (html) => {
3042
2963
  if (slotName && isValidFieldName(slotName) && !seenSlots.has(slotName)) {
3043
2964
  seenSlots.add(slotName);
3044
2965
  const shape = tryParseFieldShape(
3045
- attribs[FIELD_ATTR_PREFIX2 + slotName.toLowerCase()] ?? ""
2966
+ attribs[FIELD_ATTR_PREFIX + slotName.toLowerCase()] ?? ""
3046
2967
  );
3047
2968
  const allow = shape?.type === "slot" ? sanitizeStringArray(shape.allow) : void 0;
3048
2969
  const disallow = shape?.type === "slot" ? sanitizeStringArray(shape.disallow) : void 0;
@@ -3191,21 +3112,16 @@ var patchElement = (element, props) => {
3191
3112
  }
3192
3113
  return { didPatch, skipChildren: true };
3193
3114
  }
3194
- const attrBoundFields = /* @__PURE__ */ new Set();
3195
- for (const [an, av] of Object.entries(attribs)) {
3196
- if (an.startsWith(FIELD_ATTR_PREFIX2) && an !== `${FIELD_ATTR_PREFIX2}name` && !tryParseFieldShape(av)) {
3197
- attrBoundFields.add(av.trim());
3198
- }
3199
- }
3115
+ const attrBoundFields = attrBoundFieldNames(attribs);
3200
3116
  for (const [attrName, attrValue] of Object.entries(attribs)) {
3201
- if (!attrName.startsWith(FIELD_ATTR_PREFIX2)) continue;
3202
- const target = attrName.slice(FIELD_ATTR_PREFIX2.length);
3117
+ if (!attrName.startsWith(FIELD_ATTR_PREFIX)) continue;
3118
+ const target = attrName.slice(FIELD_ATTR_PREFIX.length);
3203
3119
  if (!target) continue;
3204
3120
  const shape = tryParseFieldShape(attrValue);
3205
3121
  if (shape) {
3206
3122
  if (shape.type === "slot") continue;
3207
3123
  const name2 = target;
3208
- if (attrBoundFields.has(name2)) continue;
3124
+ if (isFieldNameClaimed(name2, attrBoundFields)) continue;
3209
3125
  const value2 = getDeepValue(props, name2);
3210
3126
  if (shape.type === "richtext") {
3211
3127
  if (typeof value2 !== "string") continue;
@@ -3223,6 +3139,9 @@ var patchElement = (element, props) => {
3223
3139
  }
3224
3140
  const name = attrValue.trim();
3225
3141
  if (!name) continue;
3142
+ if (target === "name" && isFieldNameClaimed(name, attrBoundFields)) {
3143
+ continue;
3144
+ }
3226
3145
  const value = getDeepValue(props, name);
3227
3146
  if (target === "name" && getTypeModifier(attribs, name) === "richtext") {
3228
3147
  if (typeof value !== "string") continue;
@@ -3295,7 +3214,6 @@ var renderHtmlArrayItems = (html, arrayName, items) => {
3295
3214
  };
3296
3215
 
3297
3216
  // src/lib/design/annotations.ts
3298
- init_react_import();
3299
3217
  var cache = /* @__PURE__ */ new Map();
3300
3218
  var MAX_CACHE_ENTRIES = 32;
3301
3219
  var getAnnotations = (html) => {
@@ -3311,22 +3229,159 @@ var getAnnotations = (html) => {
3311
3229
  var getSlotFields = (html) => Object.fromEntries(
3312
3230
  getAnnotations(html).slots.map((slot) => [slot.name, toSlotField(slot)])
3313
3231
  );
3314
- var UNTRANSFORMABLE_FIELD_TYPES = /* @__PURE__ */ new Set(["richtext", "array", "slot"]);
3315
- var isTransformable = (field) => {
3316
- if (!field?.type) return false;
3317
- if (UNTRANSFORMABLE_FIELD_TYPES.has(field.type)) return false;
3318
- const nested = field.objectFields ?? field.arrayFields;
3319
- return nested ? Object.values(nested).every(isTransformable) : true;
3232
+
3233
+ // src/lib/morph-html.ts
3234
+ var PROTECTED_ATTRS = /* @__PURE__ */ new Set([
3235
+ "contenteditable",
3236
+ "data-puck-overlay-portal"
3237
+ ]);
3238
+ var SLOT_OWNED_ATTRS = /* @__PURE__ */ new Set(["style", "class"]);
3239
+ var isEventHandlerAttr = (name) => /^on/i.test(name);
3240
+ var FIELD_ATTR_PREFIX2 = "data-puck-field-";
3241
+ var LEGACY_TEXT_ATTR = `${FIELD_ATTR_PREFIX2}name`;
3242
+ var VALUE_ATTR_PREFIX = `${FIELD_ATTR_PREFIX2}value-`;
3243
+ var hasTextBinding = (el, parsedTextFields) => {
3244
+ for (const attr of Array.from(el.attributes)) {
3245
+ if (!attr.name.startsWith(FIELD_ATTR_PREFIX2)) continue;
3246
+ if (attr.name.startsWith(VALUE_ATTR_PREFIX)) continue;
3247
+ const isShape = attr.value.trimStart().startsWith("{");
3248
+ const fieldName = isShape ? attr.name.slice(FIELD_ATTR_PREFIX2.length) : attr.value.trim();
3249
+ if (isValidFieldName(fieldName) && (attr.name === LEGACY_TEXT_ATTR || isShape) && (!parsedTextFields || parsedTextFields.has(fieldName))) {
3250
+ return true;
3251
+ }
3252
+ }
3253
+ return false;
3320
3254
  };
3321
- var getTransformableFields = (reg) => {
3322
- const annotations = getAnnotations(reg.html ?? "");
3323
- const source = {
3324
- ...toPuckFields(annotations.fields, annotations.values),
3325
- ...reg.fields ?? {}
3326
- };
3327
- return Object.fromEntries(
3328
- Object.entries(source).filter(([, field]) => isTransformable(field))
3255
+ var ownsItsChildren = (el, parsedTextFields) => el.hasAttribute("data-puck-slot") || el.hasAttribute("data-puck-array") || hasTextBinding(el, parsedTextFields);
3256
+ var syncAttributes = (target, source) => {
3257
+ const isSlot = target.hasAttribute("data-puck-slot");
3258
+ for (const attr of Array.from(target.attributes)) {
3259
+ if (PROTECTED_ATTRS.has(attr.name)) continue;
3260
+ if (isSlot && SLOT_OWNED_ATTRS.has(attr.name)) continue;
3261
+ if (isEventHandlerAttr(attr.name) || !source.hasAttribute(attr.name)) {
3262
+ target.removeAttribute(attr.name);
3263
+ }
3264
+ }
3265
+ for (const attr of Array.from(source.attributes)) {
3266
+ if (PROTECTED_ATTRS.has(attr.name)) continue;
3267
+ if (isSlot && SLOT_OWNED_ATTRS.has(attr.name)) continue;
3268
+ if (isEventHandlerAttr(attr.name)) continue;
3269
+ if (target.getAttribute(attr.name) !== attr.value) {
3270
+ target.setAttribute(attr.name, attr.value);
3271
+ }
3272
+ }
3273
+ };
3274
+ var isSameKind = (a, b) => a.nodeType === b.nodeType && (a.nodeType !== Node.ELEMENT_NODE || a.tagName === b.tagName);
3275
+ var isIncompleteTagArtifact = (node) => node.nodeType === Node.TEXT_NODE && /^<\/?[a-zA-Z]*$/.test(node.nodeValue ?? "");
3276
+ var stripIncompleteTagArtifacts = (root) => {
3277
+ for (const child of Array.from(root.childNodes)) {
3278
+ if (isIncompleteTagArtifact(child)) {
3279
+ root.removeChild(child);
3280
+ } else if (child.nodeType === Node.ELEMENT_NODE) {
3281
+ stripIncompleteTagArtifacts(child);
3282
+ }
3283
+ }
3284
+ };
3285
+ var stripEventHandlerAttrs = (root) => {
3286
+ root.querySelectorAll("*").forEach((el) => {
3287
+ for (const attr of Array.from(el.attributes)) {
3288
+ if (isEventHandlerAttr(attr.name)) el.removeAttribute(attr.name);
3289
+ }
3290
+ });
3291
+ };
3292
+ var morphChildren = (target, source, skipChildrenOf = ownsItsChildren) => {
3293
+ const sourceChildren = Array.from(source.childNodes);
3294
+ for (let i = 0; i < sourceChildren.length; i++) {
3295
+ const sourceChild = sourceChildren[i];
3296
+ const targetChild = target.childNodes[i];
3297
+ if (!targetChild) {
3298
+ target.appendChild(sourceChild);
3299
+ continue;
3300
+ }
3301
+ if (!isSameKind(targetChild, sourceChild)) {
3302
+ target.replaceChild(sourceChild, targetChild);
3303
+ continue;
3304
+ }
3305
+ if (sourceChild.nodeType !== Node.ELEMENT_NODE) {
3306
+ if (targetChild.nodeValue !== sourceChild.nodeValue) {
3307
+ targetChild.nodeValue = sourceChild.nodeValue;
3308
+ }
3309
+ continue;
3310
+ }
3311
+ const targetEl = targetChild;
3312
+ syncAttributes(targetEl, sourceChild);
3313
+ if (!skipChildrenOf(targetEl)) {
3314
+ morphChildren(targetEl, sourceChild, skipChildrenOf);
3315
+ }
3316
+ }
3317
+ while (target.childNodes.length > sourceChildren.length) {
3318
+ target.removeChild(target.lastChild);
3319
+ }
3320
+ };
3321
+ function morphHtml(container, html, attrOverrides = [], skipChildrenOf) {
3322
+ const template = container.ownerDocument.createElement("template");
3323
+ template.innerHTML = html;
3324
+ stripIncompleteTagArtifacts(template.content);
3325
+ stripEventHandlerAttrs(template.content);
3326
+ if (attrOverrides.length > 0) {
3327
+ const overridesByAttr = /* @__PURE__ */ new Map();
3328
+ for (const override of attrOverrides) {
3329
+ if (isEventHandlerAttr(override.attr)) continue;
3330
+ const bindingAttr = `${FIELD_ATTR_PREFIX2}${override.attr}`;
3331
+ const fields = overridesByAttr.get(bindingAttr) ?? /* @__PURE__ */ new Map();
3332
+ fields.set(override.name, override);
3333
+ overridesByAttr.set(bindingAttr, fields);
3334
+ }
3335
+ template.content.querySelectorAll("*").forEach((el) => {
3336
+ for (const attr of Array.from(el.attributes)) {
3337
+ const override = overridesByAttr.get(attr.name)?.get(attr.value.trim());
3338
+ if (!override) continue;
3339
+ if (el.closest("[data-puck-array]")) return;
3340
+ el.setAttribute(override.attr, override.value);
3341
+ }
3342
+ });
3343
+ }
3344
+ if (skipChildrenOf) {
3345
+ morphChildren(container, template.content, skipChildrenOf);
3346
+ return;
3347
+ }
3348
+ const parsedTextFields = new Set(
3349
+ getAnnotations(html).fields.filter((field) => field.binding === "text").map((field) => field.name)
3350
+ );
3351
+ morphChildren(
3352
+ container,
3353
+ template.content,
3354
+ (el) => ownsItsChildren(el, parsedTextFields)
3329
3355
  );
3356
+ }
3357
+
3358
+ // src/lib/design/observer-guard.ts
3359
+ init_react_import();
3360
+ var wrapScriptWithObserverGuard = (script) => {
3361
+ if (!script.includes("MutationObserver")) return script;
3362
+ return `(function () {
3363
+ var __NativeMutationObserver = window.MutationObserver;
3364
+ function MutationObserver(callback) {
3365
+ var observed = [];
3366
+ var inner = new __NativeMutationObserver(function (records) {
3367
+ inner.disconnect();
3368
+ try {
3369
+ callback(records, inner);
3370
+ } finally {
3371
+ for (var i = 0; i < observed.length; i++) {
3372
+ __nativeObserve.call(inner, observed[i][0], observed[i][1]);
3373
+ }
3374
+ }
3375
+ });
3376
+ var __nativeObserve = inner.observe;
3377
+ inner.observe = function (target, options) {
3378
+ observed.push([target, options]);
3379
+ return __nativeObserve.call(inner, target, options);
3380
+ };
3381
+ return inner;
3382
+ }
3383
+ ${script}
3384
+ })();`;
3330
3385
  };
3331
3386
 
3332
3387
  // src/lib/design/registration-store.ts
@@ -3402,7 +3457,7 @@ init_react_import();
3402
3457
  var import_core2 = require("@puckeditor/core");
3403
3458
  var CONTENT_EDITABLE_ATTR = "data-puck-content-editable";
3404
3459
  var isInlineEditingDisabled = (el) => el.closest(`[${CONTENT_EDITABLE_ATTR}="false"]`) !== null;
3405
- var cssEscape2 = (value) => typeof CSS !== "undefined" && CSS.escape ? CSS.escape(value) : value.replace(/["\\]/g, "\\$&");
3460
+ var cssEscape = (value) => typeof CSS !== "undefined" && CSS.escape ? CSS.escape(value) : value.replace(/["\\]/g, "\\$&");
3406
3461
  var queryScopedFields = (entry, selector) => Array.from(entry.querySelectorAll(selector)).filter((el) => {
3407
3462
  if (el.closest("[data-puck-array]")) return false;
3408
3463
  const closestSlot = el.closest("[data-puck-slot]");
@@ -3410,7 +3465,7 @@ var queryScopedFields = (entry, selector) => Array.from(entry.querySelectorAll(s
3410
3465
  return true;
3411
3466
  });
3412
3467
  var findOwnArrayContainer = (entry, arrayName) => {
3413
- const selector = `[data-puck-array="${cssEscape2(arrayName)}"]`;
3468
+ const selector = `[data-puck-array="${cssEscape(arrayName)}"]`;
3414
3469
  for (const el of entry.querySelectorAll(selector)) {
3415
3470
  const closestSlot = el.closest("[data-puck-slot]");
3416
3471
  if (!closestSlot || !entry.contains(closestSlot)) return el;
@@ -3425,7 +3480,7 @@ var inlineEditModeFor = (field) => {
3425
3480
  const type = field.shape?.type ?? field.fieldType;
3426
3481
  return type === "richtext" ? "richtext" : type === "number" ? "number" : "text";
3427
3482
  };
3428
- var textFieldSelector = (field) => field.format === "json" ? `[${cssEscape2(`data-puck-field-${field.name}`)}]` : `[data-puck-field-name="${cssEscape2(field.name)}"]`;
3483
+ var textFieldSelector = (field) => field.format === "json" ? `[${cssEscape(`data-puck-field-${field.name}`)}]` : `[data-puck-field-name="${cssEscape(field.name)}"]`;
3429
3484
  var isHTMLElement = (el) => {
3430
3485
  const HTMLElementCtor = el.ownerDocument.defaultView?.HTMLElement;
3431
3486
  return !!HTMLElementCtor && el instanceof HTMLElementCtor;
@@ -3451,6 +3506,29 @@ var syncFieldValues = (entry, fields, props) => {
3451
3506
  });
3452
3507
  }
3453
3508
  };
3509
+ var claimedHostStyles = /* @__PURE__ */ new WeakMap();
3510
+ var claimHostStyles = (el, applyWhiteSpace) => {
3511
+ if (!claimedHostStyles.has(el)) {
3512
+ claimedHostStyles.set(el, {
3513
+ cursor: el.style.cursor,
3514
+ whiteSpace: applyWhiteSpace ? el.style.whiteSpace : null
3515
+ });
3516
+ }
3517
+ applyHostStyles(el);
3518
+ };
3519
+ var applyHostStyles = (el) => {
3520
+ const claimed = claimedHostStyles.get(el);
3521
+ if (!claimed) return;
3522
+ el.style.cursor = "text";
3523
+ if (claimed.whiteSpace !== null) el.style.whiteSpace = "pre-wrap";
3524
+ };
3525
+ var releaseHostStyles = (el) => {
3526
+ const claimed = claimedHostStyles.get(el);
3527
+ if (!claimed) return;
3528
+ el.style.cursor = claimed.cursor;
3529
+ if (claimed.whiteSpace !== null) el.style.whiteSpace = claimed.whiteSpace;
3530
+ claimedHostStyles.delete(el);
3531
+ };
3454
3532
  var attachInlineEditing = (el, {
3455
3533
  componentId,
3456
3534
  propPath,
@@ -3463,8 +3541,7 @@ var attachInlineEditing = (el, {
3463
3541
  const syncEditable = () => {
3464
3542
  el.contentEditable = hovering || focused ? mode === "richtext" ? "true" : "plaintext-only" : "false";
3465
3543
  };
3466
- const previousCursor = el.style.cursor;
3467
- el.style.cursor = "text";
3544
+ claimHostStyles(el, mode !== "richtext");
3468
3545
  syncEditable();
3469
3546
  let lastValidNumberText = el.innerText.replaceAll(/\n/gm, "");
3470
3547
  const handleInput = () => {
@@ -3542,7 +3619,7 @@ var attachInlineEditing = (el, {
3542
3619
  el.removeEventListener("mouseout", handleMouseOut, true);
3543
3620
  el.removeEventListener("focus", handleFocus);
3544
3621
  el.removeEventListener("blur", handleBlur);
3545
- el.style.cursor = previousCursor;
3622
+ releaseHostStyles(el);
3546
3623
  el.removeAttribute("contenteditable");
3547
3624
  cleanupPortal?.();
3548
3625
  };
@@ -3608,7 +3685,7 @@ var wireArrayItemEditing = ({
3608
3685
  const mode = inlineEditModeFor(field);
3609
3686
  const registration = wiring.get(target);
3610
3687
  if (registration?.propPath === propPath && registration.mode === mode) {
3611
- target.style.cursor = "text";
3688
+ applyHostStyles(target);
3612
3689
  continue;
3613
3690
  }
3614
3691
  registration?.cleanup();
@@ -3674,15 +3751,7 @@ var parseStyleString = (cssText) => {
3674
3751
  return style;
3675
3752
  };
3676
3753
  var isEventHandlerAttr2 = (name) => /^on/i.test(name);
3677
- var FIELD_ATTR_PREFIX3 = "data-puck-field-";
3678
- var fieldContentFor = (attribs, fields) => {
3679
- for (const name of Object.keys(fields)) {
3680
- if (attribs[`${FIELD_ATTR_PREFIX3}${name}`] !== void 0) return name;
3681
- if (attribs["data-puck-field-name"] === name) return name;
3682
- }
3683
- return void 0;
3684
- };
3685
- var nodeToReact = (node, slots, fields, key) => {
3754
+ var nodeToReact = (node, slots, key) => {
3686
3755
  if (node.type === "text") {
3687
3756
  return node.data;
3688
3757
  }
@@ -3713,17 +3782,13 @@ var nodeToReact = (node, slots, fields, key) => {
3713
3782
  if (VOID_ELEMENTS.has(tag)) {
3714
3783
  return (0, import_react22.createElement)(tag, props);
3715
3784
  }
3716
- const fieldName = fieldContentFor(attribs, fields);
3717
- if (fieldName !== void 0) {
3718
- return (0, import_react22.createElement)(tag, props, fields[fieldName]);
3719
- }
3720
- const children = el.children.map((child, i) => nodeToReact(child, slots, fields, i)).filter((c) => c !== null);
3785
+ const children = el.children.map((child, i) => nodeToReact(child, slots, i)).filter((c) => c !== null);
3721
3786
  return (0, import_react22.createElement)(tag, props, ...children);
3722
3787
  };
3723
- var htmlToReactTree = (html, slots, fields = {}) => {
3788
+ var htmlToReactTree = (html, slots) => {
3724
3789
  if (!html) return null;
3725
3790
  const doc = (0, import_htmlparser22.parseDocument)(html);
3726
- const children = doc.children.map((child, i) => nodeToReact(child, slots, fields, i)).filter((c) => c !== null);
3791
+ const children = doc.children.map((child, i) => nodeToReact(child, slots, i)).filter((c) => c !== null);
3727
3792
  if (children.length === 0) return null;
3728
3793
  if (children.length === 1) return children[0];
3729
3794
  return (0, import_react22.createElement)(import_react22.Fragment, null, ...children);
@@ -3762,7 +3827,7 @@ var SlotPortal = ({
3762
3827
  [slotStyle]
3763
3828
  );
3764
3829
  (0, import_react23.useEffect)(() => {
3765
- const el = entry.querySelector(`[data-puck-slot="${cssEscape2(slotName)}"]`);
3830
+ const el = entry.querySelector(`[data-puck-slot="${cssEscape(slotName)}"]`);
3766
3831
  if (el !== lastTarget.current) {
3767
3832
  el?.replaceChildren();
3768
3833
  lastTarget.current = el;
@@ -3780,32 +3845,10 @@ var SlotPortal = ({
3780
3845
  );
3781
3846
  };
3782
3847
 
3783
- // src/lib/design/field-portal.tsx
3784
- init_react_import();
3785
- var import_react24 = require("react");
3786
- var import_react_dom2 = require("react-dom");
3787
- var FieldPortal = ({
3788
- entry,
3789
- html,
3790
- selector,
3791
- children
3792
- }) => {
3793
- const [target, setTarget] = (0, import_react24.useState)(null);
3794
- const lastTarget = (0, import_react24.useRef)(null);
3795
- (0, import_react24.useEffect)(() => {
3796
- const el = queryScopedFields(entry, selector)[0] ?? null;
3797
- if (el !== lastTarget.current) {
3798
- el?.replaceChildren();
3799
- lastTarget.current = el;
3800
- setTarget(el);
3801
- }
3802
- }, [entry, html, selector]);
3803
- if (!target) return null;
3804
- return (0, import_react_dom2.createPortal)(children, target);
3805
- };
3806
-
3807
3848
  // src/lib/design/component.tsx
3808
3849
  var import_jsx_runtime32 = require("react/jsx-runtime");
3850
+ var DESIGN_COMPONENT = Symbol("puck-ai.designComponent");
3851
+ var isDesignComponent = (config) => !!config?.[DESIGN_COMPONENT];
3809
3852
  var streamingValues = /* @__PURE__ */ new Map();
3810
3853
  var instanceKey = (name, instanceId) => `${name}:${typeof instanceId === "string" ? instanceId : ""}`;
3811
3854
  var getRegistrationSlotFields = (reg) => {
@@ -3830,9 +3873,9 @@ function createDesignComponentConfig(registration) {
3830
3873
  registrationStore.apply(registration);
3831
3874
  }
3832
3875
  const slotFields = getRegistrationSlotFields(registration);
3833
- return {
3876
+ const componentConfig = {
3834
3877
  ...registration.label ? { label: registration.label } : {},
3835
- fields: { ...slotFields, ...getTransformableFields(registration) },
3878
+ fields: { ...slotFields },
3836
3879
  defaultProps: { ...registration.defaultProps ?? {} },
3837
3880
  resolveData: (data, { trigger }) => {
3838
3881
  if (trigger !== "replace" && trigger !== "insert" && trigger !== "force") {
@@ -3891,6 +3934,8 @@ function createDesignComponentConfig(registration) {
3891
3934
  },
3892
3935
  render: (renderProps) => renderProps.puck?.isEditing ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(EditorEntry, { ...renderProps }) : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(Entry, { ...renderProps, getPuck: null })
3893
3936
  };
3937
+ componentConfig[DESIGN_COMPONENT] = true;
3938
+ return componentConfig;
3894
3939
  function EditorEntry(renderProps) {
3895
3940
  const getPuck = (0, import_core3.useGetPuck)();
3896
3941
  return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(Entry, { ...renderProps, getPuck });
@@ -3902,11 +3947,11 @@ function createDesignComponentConfig(registration) {
3902
3947
  style,
3903
3948
  ...props
3904
3949
  }) {
3905
- const getSnapshot = (0, import_react25.useCallback)(
3950
+ const getSnapshot = (0, import_react24.useCallback)(
3906
3951
  () => registrationStore.get(name) ?? registration,
3907
3952
  []
3908
3953
  );
3909
- const reg = (0, import_react25.useSyncExternalStore)(
3954
+ const reg = (0, import_react24.useSyncExternalStore)(
3910
3955
  registrationStore.subscribe,
3911
3956
  getSnapshot,
3912
3957
  getSnapshot
@@ -3914,10 +3959,10 @@ function createDesignComponentConfig(registration) {
3914
3959
  const html = reg.html ?? "";
3915
3960
  const styles = reg.styles;
3916
3961
  const script = reg.script;
3917
- const [entryEl, setEntryEl] = (0, import_react25.useState)(null);
3918
- const scriptRef = (0, import_react25.useRef)(null);
3919
- const annotations = (0, import_react25.useMemo)(() => getAnnotations(html), [html]);
3920
- const { attrFields, arrayFields, attrSig, arraySig } = (0, import_react25.useMemo)(() => {
3962
+ const [entryEl, setEntryEl] = (0, import_react24.useState)(null);
3963
+ const scriptRef = (0, import_react24.useRef)(null);
3964
+ const annotations = (0, import_react24.useMemo)(() => getAnnotations(html), [html]);
3965
+ const { attrFields, arrayFields, attrSig, arraySig } = (0, import_react24.useMemo)(() => {
3921
3966
  const attr = [];
3922
3967
  const arrays = [];
3923
3968
  for (const field of annotations.fields) {
@@ -3939,86 +3984,56 @@ function createDesignComponentConfig(registration) {
3939
3984
  arraySig: JSON.stringify(arrays)
3940
3985
  };
3941
3986
  }, [props, annotations]);
3942
- const nodeFields = (0, import_react25.useMemo)(() => {
3943
- const out = [];
3944
- for (const field of annotations.fields) {
3945
- if (field.binding !== "text") continue;
3946
- const value = getDeepValue(props, field.name);
3947
- if (!import_react25.default.isValidElement(value)) continue;
3948
- out.push({
3949
- name: field.name,
3950
- selector: textFieldSelector(field),
3951
- node: value
3952
- });
3953
- }
3954
- return out;
3955
- }, [annotations, props]);
3956
- const storedProps = (0, import_react25.useMemo)(
3957
- () => getPuck ? getPuck().getItemById(id)?.props : void 0,
3958
- [getPuck, id, props]
3959
- );
3960
- const editableFields = (0, import_react25.useMemo)(() => {
3961
- const mounted = new Set(nodeFields.map((f) => f.name));
3962
- return annotations.fields.filter((field) => {
3963
- if (mounted.has(field.name)) return false;
3964
- if (field.binding === "array") return true;
3965
- const type = field.shape?.type;
3966
- if (type && !isBuiltInFieldType(type)) return false;
3967
- if (!storedProps) return true;
3968
- return getDeepValue(props, field.name) === getDeepValue(storedProps, field.name);
3969
- });
3970
- }, [annotations, nodeFields, props, storedProps]);
3971
- const patchedHtml = (0, import_react25.useMemo)(() => {
3987
+ const patchedHtml = (0, import_react24.useMemo)(() => {
3972
3988
  if (puck.isEditing) return html;
3973
3989
  return patchHtmlAnnotations(html, props);
3974
3990
  }, [html, props, puck.isEditing]);
3975
- const fieldValueAttrs = (0, import_react25.useMemo)(() => {
3991
+ const fieldValueAttrs = (0, import_react24.useMemo)(() => {
3976
3992
  const attrs = {};
3977
3993
  attrs["data-puck-design"] = name;
3978
3994
  for (const field of annotations.fields) {
3979
3995
  const value = getDeepValue(props, field.name);
3980
3996
  if (value === void 0 || value === null) continue;
3981
3997
  if (!isValidFieldName(field.name)) continue;
3982
- if (import_react25.default.isValidElement(value)) continue;
3983
3998
  attrs[`data-puck-field-value-${field.name}`] = typeof value === "string" || typeof value === "number" ? String(value) : JSON.stringify(value);
3984
3999
  }
3985
4000
  return attrs;
3986
4001
  }, [annotations, props, puck.isEditing]);
3987
- const editorEntryNode = (0, import_react25.useMemo)(
4002
+ const editorEntryNode = (0, import_react24.useMemo)(
3988
4003
  () => puck.isEditing ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { ref: setEntryEl }) : null,
3989
4004
  [puck.isEditing]
3990
4005
  );
3991
- (0, import_react25.useEffect)(() => {
4006
+ (0, import_react24.useEffect)(() => {
3992
4007
  if (!puck.isEditing || !getPuck) return;
3993
4008
  getPuck().resolveDataById(id, "force");
3994
4009
  }, [reg, puck.isEditing, getPuck, id]);
3995
- const fieldWiring = (0, import_react25.useRef)(/* @__PURE__ */ new Map());
3996
- const morphed = (0, import_react25.useRef)(null);
3997
- (0, import_react25.useLayoutEffect)(() => {
4010
+ const fieldWiring = (0, import_react24.useRef)(/* @__PURE__ */ new Map());
4011
+ const morphed = (0, import_react24.useRef)(null);
4012
+ (0, import_react24.useLayoutEffect)(() => {
3998
4013
  if (!puck.isEditing || !entryEl) return;
3999
4014
  if (morphed.current?.el !== entryEl || morphed.current?.html !== html || morphed.current?.attrSig !== attrSig) {
4000
4015
  morphed.current = { el: entryEl, html, attrSig };
4001
4016
  morphHtml(entryEl, html, attrFields);
4002
4017
  for (const el of fieldWiring.current.keys()) {
4003
- if (el.isConnected) el.style.cursor = "text";
4018
+ if (el.isConnected) applyHostStyles(el);
4004
4019
  }
4005
4020
  }
4006
4021
  syncFieldValues(entryEl, annotations.fields, props);
4007
4022
  if (getPuck) {
4008
- wireFieldEditing(entryEl, editableFields, fieldWiring.current, {
4023
+ wireFieldEditing(entryEl, annotations.fields, fieldWiring.current, {
4009
4024
  componentId: id,
4010
4025
  getPuck
4011
4026
  });
4012
4027
  }
4013
4028
  });
4014
- (0, import_react25.useEffect)(
4029
+ (0, import_react24.useEffect)(
4015
4030
  () => () => {
4016
4031
  fieldWiring.current.forEach((cleanup) => cleanup());
4017
4032
  fieldWiring.current.clear();
4018
4033
  },
4019
4034
  []
4020
4035
  );
4021
- (0, import_react25.useEffect)(() => {
4036
+ (0, import_react24.useEffect)(() => {
4022
4037
  if (!entryEl || !puck.isEditing) return;
4023
4038
  for (const attr of Array.from(entryEl.attributes)) {
4024
4039
  if ((attr.name === "data-puck-design" || attr.name.startsWith("data-puck-field-value-")) && !(attr.name in fieldValueAttrs)) {
@@ -4031,10 +4046,10 @@ function createDesignComponentConfig(registration) {
4031
4046
  }
4032
4047
  }
4033
4048
  }, [entryEl, fieldValueAttrs]);
4034
- const appliedArrayHtml = (0, import_react25.useRef)(/* @__PURE__ */ new WeakMap());
4035
- const arrayItemWiring = (0, import_react25.useRef)({});
4036
- const appliedArrays = (0, import_react25.useRef)(null);
4037
- (0, import_react25.useEffect)(() => {
4049
+ const appliedArrayHtml = (0, import_react24.useRef)(/* @__PURE__ */ new WeakMap());
4050
+ const arrayItemWiring = (0, import_react24.useRef)({});
4051
+ const appliedArrays = (0, import_react24.useRef)(null);
4052
+ (0, import_react24.useEffect)(() => {
4038
4053
  if (!puck.isEditing || !entryEl || !getPuck) return;
4039
4054
  if (appliedArrays.current?.el === entryEl && appliedArrays.current?.html === html && appliedArrays.current?.sig === arraySig) {
4040
4055
  return;
@@ -4086,7 +4101,7 @@ function createDesignComponentConfig(registration) {
4086
4101
  id,
4087
4102
  getPuck
4088
4103
  ]);
4089
- (0, import_react25.useEffect)(
4104
+ (0, import_react24.useEffect)(
4090
4105
  () => () => {
4091
4106
  Object.values(arrayItemWiring.current).forEach(
4092
4107
  (wiring) => wiring.forEach(({ cleanup }) => cleanup())
@@ -4094,7 +4109,7 @@ function createDesignComponentConfig(registration) {
4094
4109
  },
4095
4110
  []
4096
4111
  );
4097
- (0, import_react25.useEffect)(() => {
4112
+ (0, import_react24.useEffect)(() => {
4098
4113
  if (!script || !entryEl) return;
4099
4114
  scriptRef.current?.remove();
4100
4115
  const doc = entryEl.ownerDocument;
@@ -4121,30 +4136,16 @@ function createDesignComponentConfig(registration) {
4121
4136
  );
4122
4137
  }
4123
4138
  }
4124
- const fieldContent = {};
4125
- for (const { name: name2, node } of nodeFields) {
4126
- fieldContent[name2] = node;
4127
- }
4128
4139
  return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(import_jsx_runtime32.Fragment, { children: [
4129
4140
  styles && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("style", { children: styles }),
4130
4141
  style && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("style", { children: style }),
4131
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { ref: setEntryEl, ...fieldValueAttrs, children: htmlToReactTree(patchedHtml, slotContent, fieldContent) })
4142
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { ref: setEntryEl, ...fieldValueAttrs, children: htmlToReactTree(patchedHtml, slotContent) })
4132
4143
  ] });
4133
4144
  }
4134
4145
  return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(import_jsx_runtime32.Fragment, { children: [
4135
4146
  styles && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("style", { children: styles }),
4136
4147
  style && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("style", { children: style }),
4137
4148
  editorEntryNode,
4138
- entryEl && nodeFields.map(({ name: name2, selector, node }) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
4139
- FieldPortal,
4140
- {
4141
- entry: entryEl,
4142
- html,
4143
- selector,
4144
- children: node
4145
- },
4146
- name2
4147
- )),
4148
4149
  entryEl && annotations.slots.map((slot) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
4149
4150
  SlotPortal,
4150
4151
  {
@@ -4163,7 +4164,7 @@ function createDesignComponentConfig(registration) {
4163
4164
 
4164
4165
  // src/lib/design/globals.tsx
4165
4166
  init_react_import();
4166
- var import_react26 = require("react");
4167
+ var import_react25 = require("react");
4167
4168
  var import_jsx_runtime33 = require("react/jsx-runtime");
4168
4169
  function getDynamicConfigGlobals(dynamicConfig) {
4169
4170
  if (typeof dynamicConfig !== "object" || dynamicConfig === null) return {};
@@ -4173,13 +4174,15 @@ function getDynamicConfigGlobals(dynamicConfig) {
4173
4174
  ...typeof script === "string" ? { script } : {}
4174
4175
  };
4175
4176
  }
4177
+ var INLINE_EDIT_RESET = ":where([data-puck-design] [contenteditable]){overflow-wrap:inherit;line-break:inherit;-webkit-nbsp-mode:inherit}";
4176
4178
  function DesignGlobals({
4177
4179
  styles,
4178
- script
4180
+ script,
4181
+ isEditing
4179
4182
  }) {
4180
- const scriptRef = (0, import_react26.useRef)(null);
4181
- const hostRef = (0, import_react26.useRef)(null);
4182
- (0, import_react26.useEffect)(() => {
4183
+ const scriptRef = (0, import_react25.useRef)(null);
4184
+ const hostRef = (0, import_react25.useRef)(null);
4185
+ (0, import_react25.useEffect)(() => {
4183
4186
  if (!script) return;
4184
4187
  scriptRef.current?.remove();
4185
4188
  const doc = hostRef.current?.ownerDocument ?? document;
@@ -4193,6 +4196,7 @@ function DesignGlobals({
4193
4196
  };
4194
4197
  }, [script]);
4195
4198
  return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_jsx_runtime33.Fragment, { children: [
4199
+ isEditing && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("style", { children: INLINE_EDIT_RESET }),
4196
4200
  styles && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("style", { children: styles }),
4197
4201
  script && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { ref: hostRef, style: { display: "none" } })
4198
4202
  ] });
@@ -4205,7 +4209,14 @@ function withDesignGlobals(root) {
4205
4209
  const render = (props) => {
4206
4210
  const { styles, script } = getDynamicConfigGlobals(props._dynamicConfig);
4207
4211
  return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_jsx_runtime33.Fragment, { children: [
4208
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(DesignGlobals, { styles, script }),
4212
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
4213
+ DesignGlobals,
4214
+ {
4215
+ styles,
4216
+ script,
4217
+ isEditing: props.puck?.isEditing
4218
+ }
4219
+ ),
4209
4220
  userRender ? userRender(props) : props.children
4210
4221
  ] });
4211
4222
  };
@@ -4319,7 +4330,7 @@ function syncDesignComponents(getPuck) {
4319
4330
  ensureDynamicConfigField(config);
4320
4331
  for (const [name, persistedReg] of Object.entries(persisted)) {
4321
4332
  const prev = lastSynced.get(name);
4322
- if (registrationStore.has(name) && (prev === persistedReg || registrationContentEqual(prev, persistedReg))) {
4333
+ if (registrationStore.has(name) && config.components[name] && (prev === persistedReg || registrationContentEqual(prev, persistedReg))) {
4323
4334
  lastSynced.set(name, persistedReg);
4324
4335
  continue;
4325
4336
  }
@@ -4343,18 +4354,17 @@ function syncDesignComponents(getPuck) {
4343
4354
  };
4344
4355
  }
4345
4356
  }
4346
- config.components[name].fields = {
4347
- ...getSlotFields(registration.html ?? ""),
4348
- ...getTransformableFields(registration)
4349
- };
4357
+ config.components[name].fields = getSlotFields(registration.html ?? "");
4350
4358
  }
4351
4359
  let hasRemovals = false;
4352
4360
  for (const [name] of registrationStore.getAll()) {
4353
4361
  if (!(name in persisted)) {
4354
4362
  registrationStore.delete(name);
4355
4363
  lastSynced.delete(name);
4356
- delete config.components[name];
4357
- hasRemovals = true;
4364
+ if (isDesignComponent(config.components[name])) {
4365
+ delete config.components[name];
4366
+ hasRemovals = true;
4367
+ }
4358
4368
  }
4359
4369
  }
4360
4370
  if (createdSettled) {
@@ -4474,12 +4484,154 @@ var waitForIframeReady = async (root, imageTimeout = 1e4) => {
4474
4484
  await waitForNextPaint(document2);
4475
4485
  };
4476
4486
 
4487
+ // src/lib/capture-screenshot.ts
4488
+ init_react_import();
4489
+ var import_html2canvas_pro = __toESM(require("html2canvas-pro"));
4490
+
4491
+ // src/lib/screenshot-size.ts
4492
+ init_react_import();
4493
+ var MAX_WEBP_DIMENSION = 16383;
4494
+ var CAPTURE_SCALE = 2;
4495
+ var getFitRatio = (width, height, dimensionLimit = MAX_WEBP_DIMENSION) => {
4496
+ if (width <= 0 || height <= 0) return 1;
4497
+ return Math.min(1, dimensionLimit / Math.max(width, height));
4498
+ };
4499
+ var getCaptureScale = (width, height, scale) => scale * getFitRatio(width * scale, height * scale);
4500
+ var scaleDimension = (value, ratio) => Math.max(1, Math.floor(value * ratio));
4501
+ var getDocumentSize = (root) => {
4502
+ const { body, documentElement } = root.ownerDocument;
4503
+ return {
4504
+ width: Math.max(
4505
+ body?.scrollWidth ?? 0,
4506
+ body?.offsetWidth ?? 0,
4507
+ body?.clientWidth ?? 0,
4508
+ documentElement?.scrollWidth ?? 0,
4509
+ documentElement?.offsetWidth ?? 0,
4510
+ documentElement?.clientWidth ?? 0
4511
+ ),
4512
+ height: Math.max(
4513
+ body?.scrollHeight ?? 0,
4514
+ body?.offsetHeight ?? 0,
4515
+ body?.clientHeight ?? 0,
4516
+ documentElement?.scrollHeight ?? 0,
4517
+ documentElement?.offsetHeight ?? 0,
4518
+ documentElement?.clientHeight ?? 0
4519
+ )
4520
+ };
4521
+ };
4522
+
4523
+ // src/lib/screenshot-overflow.ts
4524
+ init_react_import();
4525
+ var applyBrowserOverflowStylesTo = (document2) => {
4526
+ for (const element of [document2.documentElement, document2.body]) {
4527
+ element?.style.setProperty("overflow", "visible", "important");
4528
+ }
4529
+ };
4530
+
4531
+ // src/lib/capture-screenshot.ts
4532
+ var editorOnlyClassPrefixes = ["_DraggableComponent--hover", "_ActionBar"];
4533
+ var releaseCanvas = (canvas) => {
4534
+ canvas.width = 0;
4535
+ canvas.height = 0;
4536
+ };
4537
+ var drawScaled = (source, ratio) => {
4538
+ const target = document.createElement("canvas");
4539
+ target.width = scaleDimension(source.width, ratio);
4540
+ target.height = scaleDimension(source.height, ratio);
4541
+ const ctx2 = target.getContext("2d");
4542
+ if (!ctx2) {
4543
+ releaseCanvas(target);
4544
+ return source;
4545
+ }
4546
+ ctx2.imageSmoothingEnabled = true;
4547
+ ctx2.imageSmoothingQuality = "high";
4548
+ ctx2.drawImage(source, 0, 0, target.width, target.height);
4549
+ return target;
4550
+ };
4551
+ var fitCanvas = (canvas) => {
4552
+ let remaining = getFitRatio(canvas.width, canvas.height);
4553
+ if (remaining >= 1) return canvas;
4554
+ let current = canvas;
4555
+ while (remaining < 0.5) {
4556
+ const next = drawScaled(current, 0.5);
4557
+ if (current !== canvas) releaseCanvas(current);
4558
+ current = next;
4559
+ remaining *= 2;
4560
+ }
4561
+ const fitted = drawScaled(current, remaining);
4562
+ if (current !== canvas) releaseCanvas(current);
4563
+ return fitted;
4564
+ };
4565
+ var toWebp = (canvas, quality) => new Promise((resolve, reject) => {
4566
+ canvas.toBlob(
4567
+ (blob) => {
4568
+ if (blob && blob.size > 0) {
4569
+ resolve(blob);
4570
+ return;
4571
+ }
4572
+ reject(
4573
+ new Error(
4574
+ `Could not encode a ${canvas.width}x${canvas.height} canvas as WebP`
4575
+ )
4576
+ );
4577
+ },
4578
+ "image/webp",
4579
+ quality
4580
+ );
4581
+ });
4582
+ var captureScreenshot = async (root, { scale = CAPTURE_SCALE, quality = 0.8, width } = {}) => {
4583
+ const documentSize = getDocumentSize(root);
4584
+ const safeWebPScale = getCaptureScale(
4585
+ width ?? documentSize.width,
4586
+ documentSize.height,
4587
+ scale
4588
+ );
4589
+ const canvas = await (0, import_html2canvas_pro.default)(root, {
4590
+ scale: safeWebPScale,
4591
+ backgroundColor: "#ffffff",
4592
+ ...width === void 0 ? {} : { width, windowWidth: width },
4593
+ foreignObjectRendering: false,
4594
+ imageTimeout: 3e4,
4595
+ logging: false,
4596
+ allowTaint: false,
4597
+ useCORS: true,
4598
+ scrollY: 0,
4599
+ onclone: applyBrowserOverflowStylesTo,
4600
+ ignoreElements: (el) => Array.from(el.classList).some(
4601
+ (c) => editorOnlyClassPrefixes.some((prefix) => c.startsWith(prefix))
4602
+ )
4603
+ });
4604
+ const fitted = fitCanvas(canvas);
4605
+ try {
4606
+ return await toWebp(fitted, quality);
4607
+ } finally {
4608
+ if (fitted !== canvas) releaseCanvas(fitted);
4609
+ releaseCanvas(canvas);
4610
+ }
4611
+ };
4612
+
4613
+ // src/lib/client-tool-response.ts
4614
+ init_react_import();
4615
+ async function postClientToolResponse(host, body) {
4616
+ const response = await fetch(`${host.replace(/\/+$/, "")}/tool`, {
4617
+ method: "POST",
4618
+ headers: { "Content-Type": "application/json" },
4619
+ body: JSON.stringify(body)
4620
+ });
4621
+ if (!response.ok) {
4622
+ throw new Error(
4623
+ `Client tool endpoint returned ${response.status} ${response.statusText}`.trim()
4624
+ );
4625
+ }
4626
+ }
4627
+
4477
4628
  // src/components/Chat/index.tsx
4478
4629
  var import_jsx_runtime35 = require("react/jsx-runtime");
4479
4630
  var q = (0, import_qler.default)();
4480
4631
  var DEFAULT_API_VERSION = "v2";
4481
4632
  var BUILD_OP_DEBOUNCE_MS = 1e3 / 60;
4482
- var PLUGIN_AI_VERSION = true ? "0.9.0-canary.d8bdabee" : "unknown";
4633
+ var CLIENT_TOOL_ENDPOINT_UNAVAILABLE_ERROR_MESSAGE = "Unable to verify the page data. Please enable the `/chat/tool` endpoint on the server and try again.";
4634
+ var PLUGIN_AI_VERSION = true ? "0.9.0-canary.f2867fd7" : "unknown";
4483
4635
  var BENCHMARK = false;
4484
4636
  var prefixedUlid = (prefix = "") => `${prefix ? `${prefix}_` : ""}${(0, import_ulid.ulid)()}`;
4485
4637
  var getClassName18 = getClassNameFactory("Chat", styles_module_default);
@@ -4502,28 +4654,32 @@ function Chat2(props) {
4502
4654
  } = chat ?? {};
4503
4655
  const puckDispatch = usePuck((s) => s.dispatch);
4504
4656
  const getPuck = (0, import_core5.useGetPuck)();
4505
- const localChatId = (0, import_react28.useRef)("");
4506
- const inputRef = (0, import_react28.useRef)(null);
4507
- const pluginRef = (0, import_react28.useRef)(null);
4508
- const [mode, setMode] = (0, import_react28.useState)(defaultMode);
4509
- const modeRef = (0, import_react28.useRef)(mode);
4657
+ const localChatId = (0, import_react27.useRef)("");
4658
+ const inputRef = (0, import_react27.useRef)(null);
4659
+ const pluginRef = (0, import_react27.useRef)(null);
4660
+ const [mode, setMode] = (0, import_react27.useState)(defaultMode);
4661
+ const modeRef = (0, import_react27.useRef)(mode);
4510
4662
  const designModeVisible = typeof designMode === "boolean" ? designMode : designMode.visible ?? false;
4511
- (0, import_react28.useEffect)(() => {
4663
+ (0, import_react27.useEffect)(() => {
4512
4664
  modeRef.current = mode;
4513
4665
  }, [mode]);
4514
4666
  const puckData = usePuck((s) => s.appState.data);
4515
- (0, import_react28.useEffect)(() => {
4667
+ (0, import_react27.useEffect)(() => {
4516
4668
  syncDesignComponents(getPuck);
4517
4669
  }, [puckData, getPuck]);
4518
- const [error, setError] = (0, import_react28.useState)();
4519
- const [userError, setUserError] = (0, import_react28.useState)();
4520
- const [composerAttachments, setComposerAttachments] = (0, import_react28.useState)([]);
4521
- const removedAttachmentClientIdsRef = (0, import_react28.useRef)(/* @__PURE__ */ new Set());
4522
- const uploadControllersRef = (0, import_react28.useRef)(/* @__PURE__ */ new Map());
4523
- const pendingBuildOpsRef = (0, import_react28.useRef)([]);
4524
- const [toolStatus, setToolStatus] = (0, import_react28.useState)({});
4525
- const [subagentState, setSubagentState] = (0, import_react28.useState)({});
4526
- const attachmentConfig = (0, import_react28.useMemo)(
4670
+ const [error, setError] = (0, import_react27.useState)();
4671
+ const [userError, setUserError] = (0, import_react27.useState)();
4672
+ const clientToolEndpointUnavailableRef = (0, import_react27.useRef)(false);
4673
+ const stopChatRef = (0, import_react27.useRef)(null);
4674
+ const [composerAttachments, setComposerAttachments] = (0, import_react27.useState)([]);
4675
+ const removedAttachmentClientIdsRef = (0, import_react27.useRef)(/* @__PURE__ */ new Set());
4676
+ const uploadControllersRef = (0, import_react27.useRef)(/* @__PURE__ */ new Map());
4677
+ const pendingBuildOpsRef = (0, import_react27.useRef)([]);
4678
+ const lastStreamActivityRef = (0, import_react27.useRef)(0);
4679
+ const hiddenSinceRef = (0, import_react27.useRef)(null);
4680
+ const [toolStatus, setToolStatus] = (0, import_react27.useState)({});
4681
+ const [subagentState, setSubagentState] = (0, import_react27.useState)({});
4682
+ const attachmentConfig = (0, import_react27.useMemo)(
4527
4683
  () => ({
4528
4684
  accept: attachments?.accept ?? defaultAttachmentAccept,
4529
4685
  enabled: attachments?.enabled ?? true,
@@ -4533,14 +4689,14 @@ function Chat2(props) {
4533
4689
  }),
4534
4690
  [attachments, host]
4535
4691
  );
4536
- (0, import_react28.useEffect)(
4692
+ (0, import_react27.useEffect)(
4537
4693
  () => () => {
4538
4694
  uploadControllersRef.current.forEach((controller) => controller.abort());
4539
4695
  uploadControllersRef.current.clear();
4540
4696
  },
4541
4697
  []
4542
4698
  );
4543
- const deleteAttachment = (0, import_react28.useCallback)(
4699
+ const deleteAttachment = (0, import_react27.useCallback)(
4544
4700
  async (attachmentId) => {
4545
4701
  try {
4546
4702
  await fetch(`${attachmentConfig.endpoint}/${attachmentId}`, {
@@ -4552,7 +4708,7 @@ function Chat2(props) {
4552
4708
  },
4553
4709
  [attachmentConfig.endpoint]
4554
4710
  );
4555
- const uploadScreenshot = (0, import_react28.useCallback)(
4711
+ const uploadScreenshot = (0, import_react27.useCallback)(
4556
4712
  async (width, bucketUrl) => {
4557
4713
  try {
4558
4714
  const iframeDocument = document?.getElementById("preview-frame")?.contentDocument?.documentElement ?? null;
@@ -4560,24 +4716,7 @@ function Chat2(props) {
4560
4716
  throw new Error("Preview frame not found");
4561
4717
  }
4562
4718
  await waitForIframeReady(iframeDocument);
4563
- const canvas = await (0, import_html2canvas_pro.default)(iframeDocument, {
4564
- scale: 2,
4565
- backgroundColor: "#ffffff",
4566
- width,
4567
- windowWidth: width,
4568
- foreignObjectRendering: false,
4569
- imageTimeout: 3e4,
4570
- logging: false,
4571
- allowTaint: false,
4572
- useCORS: true,
4573
- scrollY: 0,
4574
- ignoreElements: (el) => Array.from(el.classList).some(
4575
- (c) => c.startsWith("_DraggableComponent--hover") || c.startsWith("_ActionBar")
4576
- )
4577
- });
4578
- const image = canvas.toDataURL("image/webp", 0.8);
4579
- const imageResponse = await fetch(image);
4580
- const blob = await imageResponse.blob();
4719
+ const blob = await captureScreenshot(iframeDocument, { width });
4581
4720
  const uploadResponse = await fetch(bucketUrl, {
4582
4721
  method: "PUT",
4583
4722
  body: blob
@@ -4624,15 +4763,50 @@ function Chat2(props) {
4624
4763
  BUILD_OP_DEBOUNCE_MS,
4625
4764
  { maxWait: BUILD_OP_DEBOUNCE_MS }
4626
4765
  );
4627
- const queueBuildOp = (0, import_react28.useCallback)(
4766
+ const queueBuildOp = (0, import_react27.useCallback)(
4628
4767
  (operation) => {
4629
4768
  pendingBuildOpsRef.current.push(operation);
4630
4769
  debouncedFlushBuildOps();
4631
4770
  },
4632
4771
  [debouncedFlushBuildOps]
4633
4772
  );
4634
- const processData = (0, import_react28.useCallback)(
4773
+ const handleClientToolEndpointUnavailable = (0, import_react27.useCallback)(() => {
4774
+ if (clientToolEndpointUnavailableRef.current) return;
4775
+ clientToolEndpointUnavailableRef.current = true;
4776
+ setError(CLIENT_TOOL_ENDPOINT_UNAVAILABLE_ERROR_MESSAGE);
4777
+ console.error(CLIENT_TOOL_ENDPOINT_UNAVAILABLE_ERROR_MESSAGE);
4778
+ setToolStatus((current) => {
4779
+ let changed = false;
4780
+ const next = {};
4781
+ for (const [id, toolState] of Object.entries(current)) {
4782
+ if (toolState.loading) {
4783
+ changed = true;
4784
+ next[id] = {
4785
+ ...toolState,
4786
+ loading: false,
4787
+ label: "Verification unavailable"
4788
+ };
4789
+ } else {
4790
+ next[id] = toolState;
4791
+ }
4792
+ }
4793
+ return changed ? next : current;
4794
+ });
4795
+ stopChatRef.current?.();
4796
+ }, []);
4797
+ const sendClientToolResponse = (0, import_react27.useCallback)(
4798
+ async (body) => {
4799
+ try {
4800
+ await postClientToolResponse(host, body);
4801
+ } catch {
4802
+ handleClientToolEndpointUnavailable();
4803
+ }
4804
+ },
4805
+ [handleClientToolEndpointUnavailable, host]
4806
+ );
4807
+ const processData = (0, import_react27.useCallback)(
4635
4808
  (dataPart) => {
4809
+ lastStreamActivityRef.current = Date.now();
4636
4810
  switch (dataPart.type) {
4637
4811
  case "data-new-chat-created": {
4638
4812
  localChatId.current = dataPart.data.chatId;
@@ -4663,13 +4837,9 @@ function Chat2(props) {
4663
4837
  debouncedFlushBuildOps.flush();
4664
4838
  q.wait().then(() => {
4665
4839
  const { appState } = getPuck();
4666
- return fetch(`${host}/tool`, {
4667
- method: "POST",
4668
- headers: { "Content-Type": "application/json" },
4669
- body: JSON.stringify({
4670
- id: requestId,
4671
- responses: [{ action, output: appState.data }]
4672
- })
4840
+ return sendClientToolResponse({
4841
+ id: requestId,
4842
+ responses: [{ action, output: appState.data }]
4673
4843
  });
4674
4844
  }).catch((e) => {
4675
4845
  console.error("Failed to respond to client request:", e);
@@ -4686,22 +4856,7 @@ function Chat2(props) {
4686
4856
  throw new Error("Preview frame not found");
4687
4857
  }
4688
4858
  await waitForIframeReady(iframeDocument);
4689
- const canvas = await (0, import_html2canvas_pro.default)(iframeDocument, {
4690
- scale: 2,
4691
- backgroundColor: "#ffffff",
4692
- foreignObjectRendering: false,
4693
- imageTimeout: 3e4,
4694
- logging: false,
4695
- allowTaint: false,
4696
- useCORS: true,
4697
- scrollY: 0,
4698
- ignoreElements: (el) => Array.from(el.classList).some(
4699
- (c) => c.startsWith("_DraggableComponent--hover") || c.startsWith("_ActionBar")
4700
- )
4701
- });
4702
- const image = canvas.toDataURL("image/webp", 0.8);
4703
- const imageResponse = await fetch(image);
4704
- const blob = await imageResponse.blob();
4859
+ const blob = await captureScreenshot(iframeDocument);
4705
4860
  const uploadResponse = await fetch(putUrl, {
4706
4861
  method: "PUT",
4707
4862
  body: blob
@@ -4711,16 +4866,16 @@ function Chat2(props) {
4711
4866
  `Upload failed with status ${uploadResponse.status}`
4712
4867
  );
4713
4868
  }
4714
- return fetch(`${host}/tool`, {
4715
- method: "POST",
4716
- headers: { "Content-Type": "application/json" },
4717
- body: JSON.stringify({
4718
- id: requestId,
4719
- responses: [{ action, output: { sizeBytes: blob.size } }]
4720
- })
4869
+ return sendClientToolResponse({
4870
+ id: requestId,
4871
+ responses: [{ action, output: { sizeBytes: blob.size } }]
4721
4872
  });
4722
4873
  }).catch((e) => {
4723
4874
  console.error("Failed to respond to client request:", e);
4875
+ return sendClientToolResponse({
4876
+ id: requestId,
4877
+ responses: [{ action, output: { sizeBytes: 0 } }]
4878
+ });
4724
4879
  });
4725
4880
  }
4726
4881
  return;
@@ -4764,6 +4919,7 @@ function Chat2(props) {
4764
4919
  getPuck,
4765
4920
  puckDispatch,
4766
4921
  queueBuildOp,
4922
+ sendClientToolResponse,
4767
4923
  uploadScreenshot
4768
4924
  ]
4769
4925
  );
@@ -4833,12 +4989,14 @@ function Chat2(props) {
4833
4989
  if (BENCHMARK) {
4834
4990
  console.timeEnd("chat");
4835
4991
  }
4836
- setError(e.message);
4992
+ if (!clientToolEndpointUnavailableRef.current) {
4993
+ setError(e.message);
4994
+ }
4837
4995
  },
4838
4996
  onFinish: (options) => {
4839
4997
  debouncedFlushBuildOps.flush();
4840
4998
  q.wait().then(() => {
4841
- if (!options.isAbort) {
4999
+ if (!options.isAbort || clientToolEndpointUnavailableRef.current) {
4842
5000
  puckDispatch({
4843
5001
  type: "set",
4844
5002
  state: getPuck().appState,
@@ -4853,18 +5011,69 @@ function Chat2(props) {
4853
5011
  });
4854
5012
  }
4855
5013
  });
4856
- (0, import_react28.useEffect)(
4857
- () => () => {
5014
+ (0, import_react27.useEffect)(() => {
5015
+ stopChatRef.current = stop;
5016
+ return () => {
5017
+ stopChatRef.current = null;
4858
5018
  stop();
4859
- },
4860
- [stop]
4861
- );
4862
- const [forcedStatus, setForcedStatus] = (0, import_react28.useState)();
4863
- const resolvedStatus = (0, import_react28.useMemo)(
5019
+ };
5020
+ }, [stop]);
5021
+ const statusRef = (0, import_react27.useRef)(status);
5022
+ (0, import_react27.useEffect)(() => {
5023
+ statusRef.current = status;
5024
+ }, [status]);
5025
+ (0, import_react27.useEffect)(() => {
5026
+ lastStreamActivityRef.current = Date.now();
5027
+ }, [messages]);
5028
+ (0, import_react27.useEffect)(() => {
5029
+ const FROZEN_HIDDEN_MS = 3 * 6e4;
5030
+ const RESUME_GRACE_MS = 12e3;
5031
+ const isStreaming = () => statusRef.current === "streaming" || statusRef.current === "submitted";
5032
+ let graceTimer;
5033
+ const recoverIfSevered = () => {
5034
+ if (!isStreaming()) return;
5035
+ if (Date.now() - lastStreamActivityRef.current < RESUME_GRACE_MS) return;
5036
+ stop();
5037
+ setToolStatus((current) => {
5038
+ let changed = false;
5039
+ const next = {};
5040
+ for (const [id, toolState] of Object.entries(current)) {
5041
+ if (toolState.loading) {
5042
+ changed = true;
5043
+ next[id] = { ...toolState, loading: false, label: "Interrupted" };
5044
+ } else {
5045
+ next[id] = toolState;
5046
+ }
5047
+ }
5048
+ return changed ? next : current;
5049
+ });
5050
+ };
5051
+ const onVisibilityChange = () => {
5052
+ if (document.hidden) {
5053
+ hiddenSinceRef.current = isStreaming() ? Date.now() : null;
5054
+ return;
5055
+ }
5056
+ const hiddenSince = hiddenSinceRef.current;
5057
+ hiddenSinceRef.current = null;
5058
+ if (graceTimer) clearTimeout(graceTimer);
5059
+ if (hiddenSince === null || Date.now() - hiddenSince < FROZEN_HIDDEN_MS) {
5060
+ return;
5061
+ }
5062
+ lastStreamActivityRef.current = 0;
5063
+ graceTimer = setTimeout(recoverIfSevered, RESUME_GRACE_MS);
5064
+ };
5065
+ document.addEventListener("visibilitychange", onVisibilityChange);
5066
+ return () => {
5067
+ document.removeEventListener("visibilitychange", onVisibilityChange);
5068
+ if (graceTimer) clearTimeout(graceTimer);
5069
+ };
5070
+ }, [stop]);
5071
+ const [forcedStatus, setForcedStatus] = (0, import_react27.useState)();
5072
+ const resolvedStatus = (0, import_react27.useMemo)(
4864
5073
  () => forcedStatus ?? status,
4865
5074
  [status, forcedStatus]
4866
5075
  );
4867
- (0, import_react28.useEffect)(() => {
5076
+ (0, import_react27.useEffect)(() => {
4868
5077
  if (!scrollTrackingId) return;
4869
5078
  dispatchScrollTrackingEvent({
4870
5079
  id: scrollTrackingId,
@@ -4877,7 +5086,7 @@ function Chat2(props) {
4877
5086
  });
4878
5087
  };
4879
5088
  }, [resolvedStatus, scrollTrackingId]);
4880
- (0, import_react28.useEffect)(() => {
5089
+ (0, import_react27.useEffect)(() => {
4881
5090
  window.__PUCK_AI = {
4882
5091
  processData,
4883
5092
  setMessages,
@@ -4909,6 +5118,8 @@ function Chat2(props) {
4909
5118
  if (!text && readyAttachments.length === 0) {
4910
5119
  return false;
4911
5120
  }
5121
+ clientToolEndpointUnavailableRef.current = false;
5122
+ setError("");
4912
5123
  setUserError("");
4913
5124
  if (chat?.onSubmit) {
4914
5125
  try {
@@ -5086,7 +5297,7 @@ function Chat2(props) {
5086
5297
  if (!target?.attachmentId) return;
5087
5298
  await deleteAttachment(target.attachmentId);
5088
5299
  };
5089
- const messagesWithStatuses = (0, import_react28.useMemo)(() => {
5300
+ const messagesWithStatuses = (0, import_react27.useMemo)(() => {
5090
5301
  return messages.map((msg) => {
5091
5302
  return {
5092
5303
  ...msg,
@@ -5148,6 +5359,7 @@ function Chat2(props) {
5148
5359
  ),
5149
5360
  error,
5150
5361
  handleRetry: () => {
5362
+ clientToolEndpointUnavailableRef.current = false;
5151
5363
  setError("");
5152
5364
  regenerate();
5153
5365
  },
@@ -5270,8 +5482,8 @@ function AttachmentComposer({
5270
5482
  maxFiles,
5271
5483
  onChange
5272
5484
  }) {
5273
- const id = (0, import_react28.useId)();
5274
- const inputId = (0, import_react28.useMemo)(() => `attachment-input-${id}`, []);
5485
+ const id = (0, import_react27.useId)();
5486
+ const inputId = (0, import_react27.useMemo)(() => `attachment-input-${id}`, []);
5275
5487
  return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: getClassName18("attachmentControls"), children: [
5276
5488
  /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
5277
5489
  "input",
@@ -5371,7 +5583,7 @@ function isScrolledIntoView(el) {
5371
5583
 
5372
5584
  // src/components/ScrollTracking/ScrollTracking.tsx
5373
5585
  init_react_import();
5374
- var import_react30 = require("react");
5586
+ var import_react29 = require("react");
5375
5587
 
5376
5588
  // src/lib/scroll-into-view-local.ts
5377
5589
  init_react_import();
@@ -5394,9 +5606,9 @@ function scrollIntoViewLocal(el, win, behavior = "smooth") {
5394
5606
 
5395
5607
  // src/lib/use-frame-mutation-observer.ts
5396
5608
  init_react_import();
5397
- var import_react29 = require("react");
5609
+ var import_react28 = require("react");
5398
5610
  function useFrameMutationObserver(callback) {
5399
- return (0, import_react29.useCallback)(() => {
5611
+ return (0, import_react28.useCallback)(() => {
5400
5612
  const frame = document?.getElementById(
5401
5613
  "preview-frame"
5402
5614
  );
@@ -5441,9 +5653,9 @@ var ScrollTracking = ({
5441
5653
  children,
5442
5654
  trackingId
5443
5655
  }) => {
5444
- const followedRefs = (0, import_react30.useRef)([]);
5445
- const enabledRef = (0, import_react30.useRef)(false);
5446
- (0, import_react30.useEffect)(() => {
5656
+ const followedRefs = (0, import_react29.useRef)([]);
5657
+ const enabledRef = (0, import_react29.useRef)(false);
5658
+ (0, import_react29.useEffect)(() => {
5447
5659
  return subscribeToScrollTrackingEvents(({ id, enabled }) => {
5448
5660
  if (id !== trackingId) return;
5449
5661
  enabledRef.current = enabled;
@@ -5462,7 +5674,7 @@ var ScrollTracking = ({
5462
5674
  });
5463
5675
  }
5464
5676
  });
5465
- (0, import_react30.useEffect)(() => {
5677
+ (0, import_react29.useEffect)(() => {
5466
5678
  follow();
5467
5679
  }, [follow]);
5468
5680
  return children;
@@ -5475,7 +5687,7 @@ var import_core7 = require("@puckeditor/core");
5475
5687
  // src/components/ManageComponents/index.tsx
5476
5688
  init_react_import();
5477
5689
  var import_core6 = require("@puckeditor/core");
5478
- var import_react31 = require("react");
5690
+ var import_react30 = require("react");
5479
5691
 
5480
5692
  // css-module:/home/runner/work/platform/platform/packages/plugin-ai/src/components/ManageComponents/styles.module.css#css-module
5481
5693
  init_react_import();
@@ -5484,7 +5696,7 @@ var styles_module_default10 = { "ManageComponents-cta": "_ManageComponents-cta_x
5484
5696
  // src/components/ManageComponents/index.tsx
5485
5697
  var import_jsx_runtime36 = require("react/jsx-runtime");
5486
5698
  var cx10 = getClassNameFactory2("ManageComponents", styles_module_default10);
5487
- var useRegistrations = () => (0, import_react31.useSyncExternalStore)(
5699
+ var useRegistrations = () => (0, import_react30.useSyncExternalStore)(
5488
5700
  registrationStore.subscribe,
5489
5701
  () => registrationStore.getAll(),
5490
5702
  () => registrationStore.getAll()
@@ -5492,9 +5704,9 @@ var useRegistrations = () => (0, import_react31.useSyncExternalStore)(
5492
5704
  function ManageModalContent({ onClose }) {
5493
5705
  const getPuck = (0, import_core6.useGetPuck)();
5494
5706
  const { visible, setVisible } = useModal();
5495
- const [confirmTarget, setConfirmTarget] = (0, import_react31.useState)(null);
5707
+ const [confirmTarget, setConfirmTarget] = (0, import_react30.useState)(null);
5496
5708
  const entries = useRegistrations();
5497
- (0, import_react31.useEffect)(() => {
5709
+ (0, import_react30.useEffect)(() => {
5498
5710
  if (!visible) onClose();
5499
5711
  }, [visible, onClose]);
5500
5712
  const handleDelete = (name) => {
@@ -5549,9 +5761,9 @@ function ManageModalContent({ onClose }) {
5549
5761
  ] }) });
5550
5762
  }
5551
5763
  function ManageComponentsCta() {
5552
- const [open, setOpen] = (0, import_react31.useState)(false);
5764
+ const [open, setOpen] = (0, import_react30.useState)(false);
5553
5765
  const entries = useRegistrations();
5554
- const handleClose = (0, import_react31.useCallback)(() => setOpen(false), []);
5766
+ const handleClose = (0, import_react30.useCallback)(() => setOpen(false), []);
5555
5767
  if (entries.length === 0) return null;
5556
5768
  return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_jsx_runtime36.Fragment, { children: [
5557
5769
  /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("button", { className: cx10("cta"), onClick: () => setOpen(true), type: "button", children: [