@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.js CHANGED
@@ -2184,6 +2184,18 @@ const useEditorStore = create((set, get) => ({
2184
2184
  const committed = commitFromState(state, nextCanvas);
2185
2185
  return { canvas: nextCanvas, ...committed };
2186
2186
  }),
2187
+ setPageBoundRepeatablePage: (pageId, repeatablePageId) => set((state) => {
2188
+ const updatedPages = state.canvas.pages.map((p) => {
2189
+ if (p.id !== pageId) return p;
2190
+ const next = { ...p };
2191
+ if (repeatablePageId) next.boundRepeatablePageId = repeatablePageId;
2192
+ else delete next.boundRepeatablePageId;
2193
+ return next;
2194
+ });
2195
+ const nextCanvas = { ...state.canvas, pages: updatedPages };
2196
+ const committed = commitFromState(state, nextCanvas);
2197
+ return { canvas: nextCanvas, ...committed };
2198
+ }),
2187
2199
  moveElementsToPage: (elementIds, targetPageId) => set((state) => {
2188
2200
  const currentPage = getCurrentPageFromCanvas(state.canvas);
2189
2201
  const nodesToMove = [];
@@ -2328,7 +2340,8 @@ const useEditorStore = create((set, get) => ({
2328
2340
  id: p.id,
2329
2341
  name: p.name,
2330
2342
  children,
2331
- settings: { backgroundColor: "#ffffff", ...p.settings }
2343
+ settings: { backgroundColor: "#ffffff", ...p.settings },
2344
+ ...p.boundRepeatablePageId ? { boundRepeatablePageId: p.boundRepeatablePageId } : {}
2332
2345
  };
2333
2346
  });
2334
2347
  pagesWithPositions = pagesWithPositions.map((p) => {
@@ -4861,6 +4874,7 @@ const PageCanvas = forwardRef(
4861
4874
  const prevCanvasUpdateVersionRef = useRef(canvasUpdateVersion);
4862
4875
  const syncTriggeredByPanelRef = useRef(false);
4863
4876
  const hasRunPostReadyReflowForPageRef = useRef(null);
4877
+ const hasNotifiedReadyForPageRef = useRef(null);
4864
4878
  const hasClearedCachesBeforeFirstSyncRef = useRef(false);
4865
4879
  const [guides, setGuides] = useState([]);
4866
4880
  const [gridResizeLabel, setGridResizeLabel] = useState(null);
@@ -5123,10 +5137,8 @@ const PageCanvas = forwardRef(
5123
5137
  clearFabricCharCache();
5124
5138
  clearMeasurementCache();
5125
5139
  setReady(true);
5126
- if (onReady) onReady();
5127
5140
  } catch (e) {
5128
5141
  setReady(true);
5129
- if (onReady) onReady();
5130
5142
  }
5131
5143
  };
5132
5144
  initFonts();
@@ -7098,6 +7110,11 @@ const PageCanvas = forwardRef(
7098
7110
  if (!ready || hasRunPostReadyReflowForPageRef.current === pageId) return;
7099
7111
  hasRunPostReadyReflowForPageRef.current = pageId;
7100
7112
  let cancelled = false;
7113
+ const notifyReady = () => {
7114
+ if (cancelled || hasNotifiedReadyForPageRef.current === pageId) return;
7115
+ hasNotifiedReadyForPageRef.current = pageId;
7116
+ onReady == null ? void 0 : onReady();
7117
+ };
7101
7118
  const runReflowAndPersist = () => {
7102
7119
  const fc = fabricRef.current;
7103
7120
  if (!fc || cancelled) return;
@@ -7139,6 +7156,7 @@ const PageCanvas = forwardRef(
7139
7156
  requestAnimationFrame(() => {
7140
7157
  if (cancelled) return;
7141
7158
  runReflowAndPersist();
7159
+ notifyReady();
7142
7160
  });
7143
7161
  });
7144
7162
  });
@@ -7147,7 +7165,7 @@ const PageCanvas = forwardRef(
7147
7165
  cancelled = true;
7148
7166
  cancelAnimationFrame(raf1);
7149
7167
  };
7150
- }, [ready, pageId]);
7168
+ }, [ready, pageId, onReady]);
7151
7169
  useEffect(() => {
7152
7170
  const fc = fabricRef.current;
7153
7171
  if (!fc || isRebuildingRef.current || !isActive) return;
@@ -8761,13 +8779,14 @@ function formDefSectionsToInferred(schemaSections, repeatableNodeMap) {
8761
8779
  const labelPrefix = rawPrefix.startsWith("field_") ? rawPrefix : `field_${rawPrefix}`;
8762
8780
  const treeNodeId = repeatableNodeMap == null ? void 0 : repeatableNodeMap.get(def.label.trim().toLowerCase());
8763
8781
  const prefix = treeNodeId ? `field_${treeNodeId}` : labelPrefix;
8782
+ const minEntries = def.minEntries != null ? Math.max(0, def.minEntries) : 1;
8764
8783
  const section = {
8765
8784
  id: def.id,
8766
8785
  label: def.label,
8767
8786
  order: def.order ?? 0,
8768
8787
  type: "repeatable",
8769
8788
  templateKeyPrefix: prefix,
8770
- minEntries: def.minEntries ?? 1,
8789
+ minEntries,
8771
8790
  maxEntries: def.maxEntries,
8772
8791
  entryFields: def.fields.map((f, i) => ({
8773
8792
  key: f.key,
@@ -8777,7 +8796,8 @@ function formDefSectionsToInferred(schemaSections, repeatableNodeMap) {
8777
8796
  templateKey: f.key,
8778
8797
  placeholder: f.placeholder
8779
8798
  })),
8780
- initialEntryCount: def.minEntries ?? 1,
8799
+ // Honor minEntries: 0 — start with no entries, user adds via "+ Add" button.
8800
+ initialEntryCount: minEntries,
8781
8801
  parentId
8782
8802
  };
8783
8803
  if (treeNodeId) section.treeNodeId = treeNodeId;
@@ -9333,11 +9353,11 @@ function idPrefix(node) {
9333
9353
  if (t === "shape") return "shape";
9334
9354
  return "el";
9335
9355
  }
9336
- function cloneNodeWithNewIds$1(node) {
9356
+ function cloneNodeWithNewIds$1(node, cloneSuffix) {
9337
9357
  const oldToNew = /* @__PURE__ */ new Map();
9338
9358
  function clone(n) {
9339
9359
  var _a;
9340
- const newId = generateId(idPrefix(n));
9360
+ const newId = cloneSuffix ? `${baseId(n.id)}__c${cloneSuffix}` : generateId(idPrefix(n));
9341
9361
  oldToNew.set(n.id, newId);
9342
9362
  const base = baseId(n.id);
9343
9363
  const withMeta = { ...n, id: newId, __baseNodeId: base, __sourceId: n.id };
@@ -9503,12 +9523,12 @@ function getNestedRepeatableEntryCount(parentId, parentIndex, childId, formValue
9503
9523
  }
9504
9524
  return Math.max(1, maxIndex);
9505
9525
  }
9506
- function applyFormDataToConfig(config, mappings, formValues, repeatableSectionsFromSchema, repeatableEntryCounts, repeatableNestedEntryCounts, displayFormatMap) {
9526
+ function applyFormDataToConfig(config, mappings, formValues, repeatableSectionsFromSchema, repeatableEntryCounts, repeatableNestedEntryCounts, displayFormatMap, repeatablePagesFromSchema) {
9507
9527
  var _a, _b, _c, _d, _e;
9508
9528
  const cloned = JSON.parse(JSON.stringify(config));
9509
9529
  if (!cloned.pages) return cloned;
9510
9530
  const dynamicFields = cloned.dynamicFields;
9511
- const pages = cloned.pages;
9531
+ let pages = cloned.pages;
9512
9532
  const repeatableList = (repeatableSectionsFromSchema == null ? void 0 : repeatableSectionsFromSchema.length) ? repeatableSectionsFromSchema : getRepeatableFromConfig(pages);
9513
9533
  const nodeIds = repeatableList.map((r) => r.nodeId);
9514
9534
  const entryCountFromList = (baseNodeId) => {
@@ -9551,7 +9571,7 @@ function applyFormDataToConfig(config, mappings, formValues, repeatableSectionsF
9551
9571
  const N = Math.max(1, countFromState ?? getRepeatableEntryCount(baseNodeId, formValues));
9552
9572
  const clones = [];
9553
9573
  for (let i = 1; i <= N; i++) {
9554
- const [clone, oldToNew] = cloneNodeWithNewIds$1(node);
9574
+ const [clone, oldToNew] = cloneNodeWithNewIds$1(node, `${baseNodeId}_e${i}`);
9555
9575
  delete clone.repeatableSection;
9556
9576
  clones.push(clone);
9557
9577
  const newToOriginal = /* @__PURE__ */ new Map();
@@ -9587,7 +9607,7 @@ function applyFormDataToConfig(config, mappings, formValues, repeatableSectionsF
9587
9607
  const phase1Map = phase1NewToOriginal.get(`${parentBaseNodeId}_${parentIndex1Based}`);
9588
9608
  const clones = [];
9589
9609
  for (let i = 1; i <= N; i++) {
9590
- const [clone, oldToNew] = cloneNodeWithNewIds$1(node);
9610
+ const [clone, oldToNew] = cloneNodeWithNewIds$1(node, `${parentBaseNodeId}_p${parentIndex1Based}_${baseNodeId}_e${i}`);
9591
9611
  delete clone.repeatableSection;
9592
9612
  clones.push(clone);
9593
9613
  for (const [oldId, newId] of oldToNew) {
@@ -9801,9 +9821,20 @@ function applyFormDataToConfig(config, mappings, formValues, repeatableSectionsF
9801
9821
  }
9802
9822
  }
9803
9823
  }
9824
+ const repeatablePagePrefixSet = new Set(
9825
+ [].map((p) => `field_${p.templateKeyPrefix}_`)
9826
+ );
9804
9827
  if (dynamicFields == null ? void 0 : dynamicFields.length) {
9805
9828
  for (const key of Object.keys(formValues)) {
9806
9829
  if (NESTED_REPEATABLE_KEY_REGEX.test(key)) continue;
9830
+ let isPageKey = false;
9831
+ for (const pfx of repeatablePagePrefixSet) {
9832
+ if (key.startsWith(pfx)) {
9833
+ isPageKey = true;
9834
+ break;
9835
+ }
9836
+ }
9837
+ if (isPageKey) continue;
9807
9838
  const match = key.match(REPEATABLE_KEY_REGEX);
9808
9839
  if (!match) continue;
9809
9840
  const [, nodeId, indexStr, fieldId] = match;
@@ -10710,6 +10741,7 @@ function PixldocsPreview(props) {
10710
10741
  const [resolvedConfig, setResolvedConfig] = useState(null);
10711
10742
  const [isLoading, setIsLoading] = useState(false);
10712
10743
  const [fontsReady, setFontsReady] = useState(false);
10744
+ const [fontsReadyVersion, setFontsReadyVersion] = useState(0);
10713
10745
  const isResolveMode = !("config" in props && props.config);
10714
10746
  useEffect(() => {
10715
10747
  if (!isResolveMode) {
@@ -10720,6 +10752,7 @@ function PixldocsPreview(props) {
10720
10752
  if (!p.templateId || !p.formSchemaId || !p.supabaseUrl || !p.supabaseAnonKey) return;
10721
10753
  let cancelled = false;
10722
10754
  setIsLoading(true);
10755
+ setFontsReady(false);
10723
10756
  resolveFromForm({
10724
10757
  templateId: p.templateId,
10725
10758
  formSchemaId: p.formSchemaId,
@@ -10761,7 +10794,28 @@ function PixldocsPreview(props) {
10761
10794
  ]);
10762
10795
  const config = isResolveMode ? resolvedConfig : props.config;
10763
10796
  useEffect(() => {
10764
- if (isResolveMode || !config) return;
10797
+ var _a, _b;
10798
+ if (!config) return;
10799
+ let cancelled = false;
10800
+ const bump = () => {
10801
+ if (cancelled) return;
10802
+ clearMeasurementCache();
10803
+ setFontsReadyVersion((v) => v + 1);
10804
+ };
10805
+ (_b = (_a = document.fonts) == null ? void 0 : _a.ready) == null ? void 0 : _b.then(bump);
10806
+ const timeoutId = window.setTimeout(bump, 350);
10807
+ return () => {
10808
+ cancelled = true;
10809
+ window.clearTimeout(timeoutId);
10810
+ };
10811
+ }, [config]);
10812
+ const previewKey = useMemo(() => `${pageIndex}-${fontsReadyVersion}`, [pageIndex, fontsReadyVersion]);
10813
+ useEffect(() => {
10814
+ if (isResolveMode) return;
10815
+ if (!config) {
10816
+ setFontsReady(false);
10817
+ return;
10818
+ }
10765
10819
  setFontsReady(false);
10766
10820
  ensureFontsForResolvedConfig(config).then(() => setFontsReady(true)).catch(() => setFontsReady(true));
10767
10821
  }, [isResolveMode, config]);
@@ -10769,6 +10823,9 @@ function PixldocsPreview(props) {
10769
10823
  return /* @__PURE__ */ jsx("div", { className, style: { ...style, display: "flex", alignItems: "center", justifyContent: "center", minHeight: 200 }, children: /* @__PURE__ */ jsx("div", { style: { color: "#888", fontSize: 14 }, children: "Loading preview..." }) });
10770
10824
  }
10771
10825
  if (!config) return null;
10826
+ if (!fontsReady) {
10827
+ return /* @__PURE__ */ jsx("div", { className, style: { ...style, display: "flex", alignItems: "center", justifyContent: "center", minHeight: 200 }, children: /* @__PURE__ */ jsx("div", { style: { color: "#888", fontSize: 14 }, children: "Loading preview..." }) });
10828
+ }
10772
10829
  return /* @__PURE__ */ jsx("div", { className, style, children: /* @__PURE__ */ jsx(
10773
10830
  PreviewCanvas,
10774
10831
  {
@@ -10778,7 +10835,8 @@ function PixldocsPreview(props) {
10778
10835
  absoluteZoom,
10779
10836
  onDynamicFieldClick,
10780
10837
  onReady
10781
- }
10838
+ },
10839
+ previewKey
10782
10840
  ) });
10783
10841
  }
10784
10842
  class PixldocsRenderer {