@pixldocs/canvas-renderer 0.4.8 → 0.5.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.
package/dist/index.cjs CHANGED
@@ -2203,6 +2203,18 @@ const useEditorStore = zustand.create((set, get) => ({
2203
2203
  const committed = commitFromState(state, nextCanvas);
2204
2204
  return { canvas: nextCanvas, ...committed };
2205
2205
  }),
2206
+ setPageBoundRepeatablePage: (pageId, repeatablePageId) => set((state) => {
2207
+ const updatedPages = state.canvas.pages.map((p) => {
2208
+ if (p.id !== pageId) return p;
2209
+ const next = { ...p };
2210
+ if (repeatablePageId) next.boundRepeatablePageId = repeatablePageId;
2211
+ else delete next.boundRepeatablePageId;
2212
+ return next;
2213
+ });
2214
+ const nextCanvas = { ...state.canvas, pages: updatedPages };
2215
+ const committed = commitFromState(state, nextCanvas);
2216
+ return { canvas: nextCanvas, ...committed };
2217
+ }),
2206
2218
  moveElementsToPage: (elementIds, targetPageId) => set((state) => {
2207
2219
  const currentPage = getCurrentPageFromCanvas(state.canvas);
2208
2220
  const nodesToMove = [];
@@ -2347,7 +2359,8 @@ const useEditorStore = zustand.create((set, get) => ({
2347
2359
  id: p.id,
2348
2360
  name: p.name,
2349
2361
  children,
2350
- settings: { backgroundColor: "#ffffff", ...p.settings }
2362
+ settings: { backgroundColor: "#ffffff", ...p.settings },
2363
+ ...p.boundRepeatablePageId ? { boundRepeatablePageId: p.boundRepeatablePageId } : {}
2351
2364
  };
2352
2365
  });
2353
2366
  pagesWithPositions = pagesWithPositions.map((p) => {
@@ -4880,6 +4893,7 @@ const PageCanvas = react.forwardRef(
4880
4893
  const prevCanvasUpdateVersionRef = react.useRef(canvasUpdateVersion);
4881
4894
  const syncTriggeredByPanelRef = react.useRef(false);
4882
4895
  const hasRunPostReadyReflowForPageRef = react.useRef(null);
4896
+ const hasNotifiedReadyForPageRef = react.useRef(null);
4883
4897
  const hasClearedCachesBeforeFirstSyncRef = react.useRef(false);
4884
4898
  const [guides, setGuides] = react.useState([]);
4885
4899
  const [gridResizeLabel, setGridResizeLabel] = react.useState(null);
@@ -5142,10 +5156,8 @@ const PageCanvas = react.forwardRef(
5142
5156
  clearFabricCharCache();
5143
5157
  clearMeasurementCache();
5144
5158
  setReady(true);
5145
- if (onReady) onReady();
5146
5159
  } catch (e) {
5147
5160
  setReady(true);
5148
- if (onReady) onReady();
5149
5161
  }
5150
5162
  };
5151
5163
  initFonts();
@@ -7117,6 +7129,11 @@ const PageCanvas = react.forwardRef(
7117
7129
  if (!ready || hasRunPostReadyReflowForPageRef.current === pageId) return;
7118
7130
  hasRunPostReadyReflowForPageRef.current = pageId;
7119
7131
  let cancelled = false;
7132
+ const notifyReady = () => {
7133
+ if (cancelled || hasNotifiedReadyForPageRef.current === pageId) return;
7134
+ hasNotifiedReadyForPageRef.current = pageId;
7135
+ onReady == null ? void 0 : onReady();
7136
+ };
7120
7137
  const runReflowAndPersist = () => {
7121
7138
  const fc = fabricRef.current;
7122
7139
  if (!fc || cancelled) return;
@@ -7158,6 +7175,7 @@ const PageCanvas = react.forwardRef(
7158
7175
  requestAnimationFrame(() => {
7159
7176
  if (cancelled) return;
7160
7177
  runReflowAndPersist();
7178
+ notifyReady();
7161
7179
  });
7162
7180
  });
7163
7181
  });
@@ -7166,7 +7184,7 @@ const PageCanvas = react.forwardRef(
7166
7184
  cancelled = true;
7167
7185
  cancelAnimationFrame(raf1);
7168
7186
  };
7169
- }, [ready, pageId]);
7187
+ }, [ready, pageId, onReady]);
7170
7188
  react.useEffect(() => {
7171
7189
  const fc = fabricRef.current;
7172
7190
  if (!fc || isRebuildingRef.current || !isActive) return;
@@ -8780,13 +8798,14 @@ function formDefSectionsToInferred(schemaSections, repeatableNodeMap) {
8780
8798
  const labelPrefix = rawPrefix.startsWith("field_") ? rawPrefix : `field_${rawPrefix}`;
8781
8799
  const treeNodeId = repeatableNodeMap == null ? void 0 : repeatableNodeMap.get(def.label.trim().toLowerCase());
8782
8800
  const prefix = treeNodeId ? `field_${treeNodeId}` : labelPrefix;
8801
+ const minEntries = def.minEntries != null ? Math.max(0, def.minEntries) : 1;
8783
8802
  const section = {
8784
8803
  id: def.id,
8785
8804
  label: def.label,
8786
8805
  order: def.order ?? 0,
8787
8806
  type: "repeatable",
8788
8807
  templateKeyPrefix: prefix,
8789
- minEntries: def.minEntries ?? 1,
8808
+ minEntries,
8790
8809
  maxEntries: def.maxEntries,
8791
8810
  entryFields: def.fields.map((f, i) => ({
8792
8811
  key: f.key,
@@ -8796,7 +8815,8 @@ function formDefSectionsToInferred(schemaSections, repeatableNodeMap) {
8796
8815
  templateKey: f.key,
8797
8816
  placeholder: f.placeholder
8798
8817
  })),
8799
- initialEntryCount: def.minEntries ?? 1,
8818
+ // Honor minEntries: 0 — start with no entries, user adds via "+ Add" button.
8819
+ initialEntryCount: minEntries,
8800
8820
  parentId
8801
8821
  };
8802
8822
  if (treeNodeId) section.treeNodeId = treeNodeId;
@@ -9352,11 +9372,11 @@ function idPrefix(node) {
9352
9372
  if (t === "shape") return "shape";
9353
9373
  return "el";
9354
9374
  }
9355
- function cloneNodeWithNewIds$1(node) {
9375
+ function cloneNodeWithNewIds$1(node, cloneSuffix) {
9356
9376
  const oldToNew = /* @__PURE__ */ new Map();
9357
9377
  function clone(n) {
9358
9378
  var _a;
9359
- const newId = generateId(idPrefix(n));
9379
+ const newId = cloneSuffix ? `${baseId(n.id)}__c${cloneSuffix}` : generateId(idPrefix(n));
9360
9380
  oldToNew.set(n.id, newId);
9361
9381
  const base = baseId(n.id);
9362
9382
  const withMeta = { ...n, id: newId, __baseNodeId: base, __sourceId: n.id };
@@ -9522,12 +9542,12 @@ function getNestedRepeatableEntryCount(parentId, parentIndex, childId, formValue
9522
9542
  }
9523
9543
  return Math.max(1, maxIndex);
9524
9544
  }
9525
- function applyFormDataToConfig(config, mappings, formValues, repeatableSectionsFromSchema, repeatableEntryCounts, repeatableNestedEntryCounts, displayFormatMap) {
9545
+ function applyFormDataToConfig(config, mappings, formValues, repeatableSectionsFromSchema, repeatableEntryCounts, repeatableNestedEntryCounts, displayFormatMap, repeatablePagesFromSchema) {
9526
9546
  var _a, _b, _c, _d, _e;
9527
9547
  const cloned = JSON.parse(JSON.stringify(config));
9528
9548
  if (!cloned.pages) return cloned;
9529
9549
  const dynamicFields = cloned.dynamicFields;
9530
- const pages = cloned.pages;
9550
+ let pages = cloned.pages;
9531
9551
  const repeatableList = (repeatableSectionsFromSchema == null ? void 0 : repeatableSectionsFromSchema.length) ? repeatableSectionsFromSchema : getRepeatableFromConfig(pages);
9532
9552
  const nodeIds = repeatableList.map((r) => r.nodeId);
9533
9553
  const entryCountFromList = (baseNodeId) => {
@@ -9570,7 +9590,7 @@ function applyFormDataToConfig(config, mappings, formValues, repeatableSectionsF
9570
9590
  const N = Math.max(1, countFromState ?? getRepeatableEntryCount(baseNodeId, formValues));
9571
9591
  const clones = [];
9572
9592
  for (let i = 1; i <= N; i++) {
9573
- const [clone, oldToNew] = cloneNodeWithNewIds$1(node);
9593
+ const [clone, oldToNew] = cloneNodeWithNewIds$1(node, `${baseNodeId}_e${i}`);
9574
9594
  delete clone.repeatableSection;
9575
9595
  clones.push(clone);
9576
9596
  const newToOriginal = /* @__PURE__ */ new Map();
@@ -9606,7 +9626,7 @@ function applyFormDataToConfig(config, mappings, formValues, repeatableSectionsF
9606
9626
  const phase1Map = phase1NewToOriginal.get(`${parentBaseNodeId}_${parentIndex1Based}`);
9607
9627
  const clones = [];
9608
9628
  for (let i = 1; i <= N; i++) {
9609
- const [clone, oldToNew] = cloneNodeWithNewIds$1(node);
9629
+ const [clone, oldToNew] = cloneNodeWithNewIds$1(node, `${parentBaseNodeId}_p${parentIndex1Based}_${baseNodeId}_e${i}`);
9610
9630
  delete clone.repeatableSection;
9611
9631
  clones.push(clone);
9612
9632
  for (const [oldId, newId] of oldToNew) {
@@ -9820,9 +9840,20 @@ function applyFormDataToConfig(config, mappings, formValues, repeatableSectionsF
9820
9840
  }
9821
9841
  }
9822
9842
  }
9843
+ const repeatablePagePrefixSet = new Set(
9844
+ [].map((p) => `field_${p.templateKeyPrefix}_`)
9845
+ );
9823
9846
  if (dynamicFields == null ? void 0 : dynamicFields.length) {
9824
9847
  for (const key of Object.keys(formValues)) {
9825
9848
  if (NESTED_REPEATABLE_KEY_REGEX.test(key)) continue;
9849
+ let isPageKey = false;
9850
+ for (const pfx of repeatablePagePrefixSet) {
9851
+ if (key.startsWith(pfx)) {
9852
+ isPageKey = true;
9853
+ break;
9854
+ }
9855
+ }
9856
+ if (isPageKey) continue;
9826
9857
  const match = key.match(REPEATABLE_KEY_REGEX);
9827
9858
  if (!match) continue;
9828
9859
  const [, nodeId, indexStr, fieldId] = match;
@@ -10729,6 +10760,7 @@ function PixldocsPreview(props) {
10729
10760
  const [resolvedConfig, setResolvedConfig] = react.useState(null);
10730
10761
  const [isLoading, setIsLoading] = react.useState(false);
10731
10762
  const [fontsReady, setFontsReady] = react.useState(false);
10763
+ const [fontsReadyVersion, setFontsReadyVersion] = react.useState(0);
10732
10764
  const isResolveMode = !("config" in props && props.config);
10733
10765
  react.useEffect(() => {
10734
10766
  if (!isResolveMode) {
@@ -10739,6 +10771,7 @@ function PixldocsPreview(props) {
10739
10771
  if (!p.templateId || !p.formSchemaId || !p.supabaseUrl || !p.supabaseAnonKey) return;
10740
10772
  let cancelled = false;
10741
10773
  setIsLoading(true);
10774
+ setFontsReady(false);
10742
10775
  resolveFromForm({
10743
10776
  templateId: p.templateId,
10744
10777
  formSchemaId: p.formSchemaId,
@@ -10780,7 +10813,28 @@ function PixldocsPreview(props) {
10780
10813
  ]);
10781
10814
  const config = isResolveMode ? resolvedConfig : props.config;
10782
10815
  react.useEffect(() => {
10783
- if (isResolveMode || !config) return;
10816
+ var _a, _b;
10817
+ if (!config) return;
10818
+ let cancelled = false;
10819
+ const bump = () => {
10820
+ if (cancelled) return;
10821
+ clearMeasurementCache();
10822
+ setFontsReadyVersion((v) => v + 1);
10823
+ };
10824
+ (_b = (_a = document.fonts) == null ? void 0 : _a.ready) == null ? void 0 : _b.then(bump);
10825
+ const timeoutId = window.setTimeout(bump, 350);
10826
+ return () => {
10827
+ cancelled = true;
10828
+ window.clearTimeout(timeoutId);
10829
+ };
10830
+ }, [config]);
10831
+ const previewKey = react.useMemo(() => `${pageIndex}-${fontsReadyVersion}`, [pageIndex, fontsReadyVersion]);
10832
+ react.useEffect(() => {
10833
+ if (isResolveMode) return;
10834
+ if (!config) {
10835
+ setFontsReady(false);
10836
+ return;
10837
+ }
10784
10838
  setFontsReady(false);
10785
10839
  ensureFontsForResolvedConfig(config).then(() => setFontsReady(true)).catch(() => setFontsReady(true));
10786
10840
  }, [isResolveMode, config]);
@@ -10788,6 +10842,9 @@ function PixldocsPreview(props) {
10788
10842
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className, style: { ...style, display: "flex", alignItems: "center", justifyContent: "center", minHeight: 200 }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: "#888", fontSize: 14 }, children: "Loading preview..." }) });
10789
10843
  }
10790
10844
  if (!config) return null;
10845
+ if (!fontsReady) {
10846
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className, style: { ...style, display: "flex", alignItems: "center", justifyContent: "center", minHeight: 200 }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: "#888", fontSize: 14 }, children: "Loading preview..." }) });
10847
+ }
10791
10848
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className, style, children: /* @__PURE__ */ jsxRuntime.jsx(
10792
10849
  PreviewCanvas,
10793
10850
  {
@@ -10797,7 +10854,8 @@ function PixldocsPreview(props) {
10797
10854
  absoluteZoom,
10798
10855
  onDynamicFieldClick,
10799
10856
  onReady
10800
- }
10857
+ },
10858
+ previewKey
10801
10859
  ) });
10802
10860
  }
10803
10861
  class PixldocsRenderer {