@pixldocs/canvas-renderer 0.5.188 → 0.5.190

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/README.md CHANGED
@@ -483,7 +483,7 @@ const blurFieldIds = ['text-reference-name', 'text-phone', 'text-address'];
483
483
  the resolved config before handing it to the imperative renderer, so the
484
484
  blur is baked into the exported pixels.
485
485
 
486
- ### Biodata teaser previews — blur by flat form key (v0.5.187+)
486
+ ### Biodata teaser previews — blur by flat form key (v0.5.187+, hardened in v0.5.189, pagination-safe in v0.5.190)
487
487
 
488
488
  Form-driven apps (BioMaker, the pixldocs.com Use page, etc.) already
489
489
  speak the **flat form-key** language produced by `applyFormDataToConfig`
@@ -499,7 +499,10 @@ Instead of reverse-engineering `config.__cloneIdMap` keys or the
499
499
  flat keys straight to `<PixldocsPreview>` and the package resolves them:
500
500
 
501
501
  ```tsx
502
- import { PixldocsPreview } from '@pixldocs/canvas-renderer';
502
+ import {
503
+ PixldocsPreview,
504
+ buildTeaserBlurFlatKeys,
505
+ } from '@pixldocs/canvas-renderer';
503
506
 
504
507
  // e.g. for a biodata teaser: blur every detail-row VALUE after row 3.
505
508
  const blurFlatFormKeys = buildTeaserBlurFlatKeys(sectionState, schema, {
@@ -509,11 +512,14 @@ const blurFlatFormKeys = buildTeaserBlurFlatKeys(sectionState, schema, {
509
512
 
510
513
  <PixldocsPreview
511
514
  config={displayConfig}
515
+ pageIndex={pageIndex}
512
516
  frostedBlur
513
517
  blurFlatFormKeys={blurFlatFormKeys}
514
518
  // Optional — defaults to { bindings: 'value' } so only `*_value` keys
515
- // are honoured. Pass 'all' to blur labels / titles too.
516
- blurFlatFormKeyOptions={{ bindings: 'value' }}
519
+ // are honoured. Pass 'all' to blur labels / titles too. `pageIndex` is
520
+ // forwarded into the resolver so verification scopes to the page
521
+ // currently being rendered (recommended for stacked multi-page previews).
522
+ blurFlatFormKeyOptions={{ bindings: 'value', pageIndex }}
517
523
  />
518
524
  ```
519
525
 
@@ -521,10 +527,28 @@ Notes:
521
527
 
522
528
  - Matching is done on `config.__cloneIdMap` and handles all alias
523
529
  variants (`grp-…_4_value`, `…_4_field_detail_section_N_field_N_value`,
524
- with or without the wrapper `field_` prefix). Unknown keys are
525
- silently ignored the package never blurs an element it can't
526
- positively identify, so a stale key won't accidentally blur the full
527
- name, photo, or section titles.
530
+ with or without the wrapper `field_` prefix). **As of v0.5.189**
531
+ every resolved id is then verified against the actual rendered page
532
+ tree (`config.pages[*]`) stale or alias map entries that don't
533
+ match a real element are dropped silently.
534
+ - **As of v0.5.190** `applyContentBoundsPagination` rewrites
535
+ `__cloneIdMap` after pagination so flat keys for overflow rows that
536
+ moved to continuation pages still resolve to ids that exist on the
537
+ tree (clones inherit `__sourceId` from the original element). If the
538
+ map is still stale for any reason, the resolver also falls back to
539
+ walking the page tree and matching `__sourceId` / `__baseNodeId`
540
+ against the raw resolved ids — the same strategy the theming pipeline
541
+ uses to track paginated clones. End result: frosted blur for row N
542
+ appears on whichever page row N actually renders on, even when
543
+ content-bounds pagination moved it.
544
+ - Pass `pageIndex` in `blurFlatFormKeyOptions` to scope verification
545
+ to a single page — clones that landed on a different page will not
546
+ contribute overlays to this page. This is the recommended pattern
547
+ for stacked multi-page previews. Default is `'all'` (verify across
548
+ every page).
549
+ - Use `skipTreeVerification: true` only if you fully trust your
550
+ `__cloneIdMap` (e.g. you just built the config in-process and
551
+ haven't mutated the tree since).
528
552
  - Defaults to `bindings: 'value'`, i.e. only flat keys whose last
529
553
  segment is `value` resolve. This is exactly the biodata teaser
530
554
  pattern: blur row values, leave labels / section titles / full-name /
@@ -533,6 +557,26 @@ Notes:
533
557
  PNG/PDF download path, resolve the same keys to exact ids and bake
534
558
  them in (see below).
535
559
 
560
+ #### `buildTeaserBlurFlatKeys(sectionState, sections, options)`
561
+
562
+ Emits the flat form-keys for every repeatable-section entry **after**
563
+ `afterRow` (1-indexed). Walks nested repeatables recursively.
564
+
565
+ - `sectionState` — the same `SectionFormState` you'd hand to
566
+ `resolveFromForm` / `<PixldocsPreview sectionState={…}>`.
567
+ - `sections` — `InferredSection[]` for the active form schema (you
568
+ already have these from `inferFormSchemaFromTemplate` or from the
569
+ `form_schemas` row converted via `fromFormDefSections`).
570
+ - `options.afterRow` — keep the first N entries sharp; blur entries
571
+ `N+1, N+2, …`.
572
+ - `options.bindings` — `'value'` (default) emits only `_value` keys
573
+ (the canonical biodata teaser pattern); `'all'` emits every entry
574
+ field key.
575
+
576
+ Returned keys can be fed straight into the `blurFlatFormKeys` prop
577
+ above and into `resolveBlurElementExactIdsFromFlatFormKeys` for the
578
+ watermarked download path below.
579
+
536
580
  #### Watermarked downloads — same resolver + `injectPreviewBlur`
537
581
 
538
582
  ```ts
@@ -544,7 +588,9 @@ import {
544
588
  const exactIds = resolveBlurElementExactIdsFromFlatFormKeys(
545
589
  resolvedConfig,
546
590
  blurFlatFormKeys,
547
- // { bindings: 'value' } by default
591
+ // { bindings: 'value', pageIndex: 'all' } by default. For the
592
+ // download path you usually want 'all' since the export covers
593
+ // every page.
548
594
  );
549
595
 
550
596
  const exportConfig = injectPreviewBlur(resolvedConfig, {
@@ -13327,6 +13327,7 @@ function isStaticOnNewPage(node) {
13327
13327
  function cloneNodeWithNewIds(node) {
13328
13328
  const base = node.__baseNodeId;
13329
13329
  const source = node.__sourceId;
13330
+ const effectiveSource = source ?? node.id;
13330
13331
  if (isGroup(node)) {
13331
13332
  const g = node;
13332
13333
  const cloned2 = {
@@ -13335,14 +13336,14 @@ function cloneNodeWithNewIds(node) {
13335
13336
  children: (g.children ?? []).map(cloneNodeWithNewIds)
13336
13337
  };
13337
13338
  if (base != null) cloned2.__baseNodeId = base;
13338
- if (source != null) cloned2.__sourceId = source;
13339
+ cloned2.__sourceId = effectiveSource;
13339
13340
  return cloned2;
13340
13341
  }
13341
13342
  const el = node;
13342
13343
  const prefix = el.type === "text" ? "text" : el.type === "image" ? "img" : el.type === "shape" ? "shape" : "line";
13343
13344
  const cloned = { ...el, id: generateId(prefix) };
13344
13345
  if (base != null) cloned.__baseNodeId = base;
13345
- if (source != null) cloned.__sourceId = source;
13346
+ cloned.__sourceId = effectiveSource;
13346
13347
  return cloned;
13347
13348
  }
13348
13349
  function cloneNodeWithStableIds(node, baseId2, path) {
@@ -13648,7 +13649,51 @@ function applyContentBoundsPagination(config) {
13648
13649
  resultPages.push(...paginated);
13649
13650
  }
13650
13651
  if (!mutated) return config;
13651
- return { ...config, pages: resultPages };
13652
+ const next = { ...config, pages: resultPages };
13653
+ remapCloneIdMapAfterPagination(next);
13654
+ return next;
13655
+ }
13656
+ function remapCloneIdMapAfterPagination(config) {
13657
+ const cloneIdMap = config.__cloneIdMap;
13658
+ if (!cloneIdMap || typeof cloneIdMap !== "object") return;
13659
+ const allIds = /* @__PURE__ */ new Set();
13660
+ const sourceToNew = /* @__PURE__ */ new Map();
13661
+ const walk = (node) => {
13662
+ if (!node || typeof node !== "object") return;
13663
+ const n = node;
13664
+ if (typeof n.id === "string") {
13665
+ allIds.add(n.id);
13666
+ const src = n.__sourceId;
13667
+ if (typeof src === "string" && src !== n.id) {
13668
+ const arr = sourceToNew.get(src);
13669
+ if (arr) arr.push(n.id);
13670
+ else sourceToNew.set(src, [n.id]);
13671
+ }
13672
+ }
13673
+ const children = n.children;
13674
+ if (Array.isArray(children)) for (const c of children) walk(c);
13675
+ };
13676
+ for (const page of config.pages ?? []) {
13677
+ const ch = page.children;
13678
+ if (Array.isArray(ch)) for (const c of ch) walk(c);
13679
+ }
13680
+ const updated = {};
13681
+ for (const [key, val] of Object.entries(cloneIdMap)) {
13682
+ const oldIds = Array.isArray(val) ? val : [val];
13683
+ const newIds = [];
13684
+ for (const oid of oldIds) {
13685
+ if (typeof oid !== "string") continue;
13686
+ if (allIds.has(oid)) newIds.push(oid);
13687
+ const clones = sourceToNew.get(oid);
13688
+ if (clones) {
13689
+ for (const c of clones) if (allIds.has(c)) newIds.push(c);
13690
+ }
13691
+ }
13692
+ if (newIds.length === 0) continue;
13693
+ const uniq = Array.from(new Set(newIds));
13694
+ updated[key] = uniq.length === 1 ? uniq[0] : uniq;
13695
+ }
13696
+ config.__cloneIdMap = updated;
13652
13697
  }
13653
13698
  const __vite_import_meta_env__ = {};
13654
13699
  const FONT_WEIGHT_LABELS = {
@@ -16257,6 +16302,49 @@ function addAllToSet(val, out) {
16257
16302
  out.add(val);
16258
16303
  return true;
16259
16304
  }
16305
+ function collectPageTreeElementIds(config, pageIndex = "all") {
16306
+ const out = /* @__PURE__ */ new Set();
16307
+ const pages = config == null ? void 0 : config.pages;
16308
+ if (!Array.isArray(pages)) return out;
16309
+ const targets = pageIndex === "all" ? pages : pages[pageIndex] != null ? [pages[pageIndex]] : [];
16310
+ const walk = (node) => {
16311
+ if (!node || typeof node !== "object") return;
16312
+ if (typeof node.id === "string") out.add(node.id);
16313
+ const children = node.children || node.elements;
16314
+ if (Array.isArray(children)) for (const c of children) walk(c);
16315
+ };
16316
+ for (const page of targets) {
16317
+ const children = (page == null ? void 0 : page.children) || (page == null ? void 0 : page.elements);
16318
+ if (Array.isArray(children)) for (const c of children) walk(c);
16319
+ }
16320
+ return out;
16321
+ }
16322
+ function collectIdsBySourceMatch(config, targetIds, pageIndex = "all") {
16323
+ const targets = /* @__PURE__ */ new Set();
16324
+ for (const t of targetIds) if (typeof t === "string" && t) targets.add(t);
16325
+ if (targets.size === 0) return [];
16326
+ const pages = config == null ? void 0 : config.pages;
16327
+ if (!Array.isArray(pages)) return [];
16328
+ const visitPages = pageIndex === "all" ? pages : pages[pageIndex] != null ? [pages[pageIndex]] : [];
16329
+ const out = /* @__PURE__ */ new Set();
16330
+ const walk = (node) => {
16331
+ if (!node || typeof node !== "object") return;
16332
+ const src = typeof node.__sourceId === "string" ? node.__sourceId : void 0;
16333
+ const base = typeof node.__baseNodeId === "string" ? node.__baseNodeId : void 0;
16334
+ if (typeof node.id === "string") {
16335
+ if (targets.has(node.id)) out.add(node.id);
16336
+ else if (src && targets.has(src)) out.add(node.id);
16337
+ else if (base && targets.has(base)) out.add(node.id);
16338
+ }
16339
+ const children = node.children || node.elements;
16340
+ if (Array.isArray(children)) for (const c of children) walk(c);
16341
+ };
16342
+ for (const page of visitPages) {
16343
+ const children = (page == null ? void 0 : page.children) || (page == null ? void 0 : page.elements);
16344
+ if (Array.isArray(children)) for (const c of children) walk(c);
16345
+ }
16346
+ return Array.from(out);
16347
+ }
16260
16348
  function resolveBlurElementExactIdsFromFlatFormKeys(config, flatFormKeys, options) {
16261
16349
  const cloneIdMap = (config == null ? void 0 : config.__cloneIdMap) || {};
16262
16350
  if (!cloneIdMap || typeof cloneIdMap !== "object") return [];
@@ -16281,10 +16369,60 @@ function resolveBlurElementExactIdsFromFlatFormKeys(config, flatFormKeys, option
16281
16369
  }
16282
16370
  }
16283
16371
  }
16284
- return Array.from(out);
16372
+ if (out.size === 0) return [];
16373
+ if (options == null ? void 0 : options.skipTreeVerification) return Array.from(out);
16374
+ const treeIds = collectPageTreeElementIds(
16375
+ config,
16376
+ (options == null ? void 0 : options.pageIndex) ?? "all"
16377
+ );
16378
+ if (treeIds.size === 0) return Array.from(out);
16379
+ const verified = [];
16380
+ for (const id of out) if (treeIds.has(id)) verified.push(id);
16381
+ if (verified.length > 0) return verified;
16382
+ return collectIdsBySourceMatch(config, out, (options == null ? void 0 : options.pageIndex) ?? "all");
16383
+ }
16384
+ function buildTeaserBlurFlatKeys(sectionState, sections, options) {
16385
+ const afterRow = Math.max(0, options.afterRow | 0);
16386
+ const bindings = options.bindings ?? "value";
16387
+ const out = [];
16388
+ if (!sectionState || !Array.isArray(sections)) return out;
16389
+ const repeatables = sections.filter((s) => (s == null ? void 0 : s.type) === "repeatable");
16390
+ const childrenByParent = /* @__PURE__ */ new Map();
16391
+ for (const r of repeatables) {
16392
+ if (!r.parentId) continue;
16393
+ const arr = childrenByParent.get(r.parentId) || [];
16394
+ arr.push(r);
16395
+ childrenByParent.set(r.parentId, arr);
16396
+ }
16397
+ const emitForEntryFields = (section, keyPrefix, entryIdx1) => {
16398
+ const fields = section.entryFields || [];
16399
+ for (const f of fields) {
16400
+ if (!(f == null ? void 0 : f.key)) continue;
16401
+ if (bindings === "value" && f.key !== "value") continue;
16402
+ out.push(`${keyPrefix}_${f.key}`);
16403
+ }
16404
+ };
16405
+ const walkRepeatable = (section, parentKeyPrefix) => {
16406
+ const entries = sectionState == null ? void 0 : sectionState[section.id];
16407
+ if (!Array.isArray(entries)) return;
16408
+ for (let i = 0; i < entries.length; i++) {
16409
+ const entryIdx1 = i + 1;
16410
+ const isBlurred = entryIdx1 > afterRow;
16411
+ const keyPrefix = parentKeyPrefix ? `${parentKeyPrefix}_field_${section.id}_${entryIdx1}` : `field_${section.id}_${entryIdx1}`;
16412
+ if (isBlurred) emitForEntryFields(section, keyPrefix);
16413
+ const children = childrenByParent.get(section.id) || [];
16414
+ for (const child of children) walkRepeatable(child, keyPrefix);
16415
+ }
16416
+ };
16417
+ for (const r of repeatables) {
16418
+ if (r.parentId) continue;
16419
+ walkRepeatable(r, "");
16420
+ }
16421
+ return out;
16285
16422
  }
16286
16423
  const previewBlur = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
16287
16424
  __proto__: null,
16425
+ buildTeaserBlurFlatKeys,
16288
16426
  hasAnyPreviewBlur,
16289
16427
  injectPreviewBlur,
16290
16428
  resolveBlurElementExactIdsFromFlatFormKeys
@@ -16769,9 +16907,9 @@ function captureFabricCanvasSvgForPdf(fabricInstance, canvasWidth, canvasHeight)
16769
16907
  }
16770
16908
  return svgString;
16771
16909
  }
16772
- const resolvedPackageVersion = "0.5.188";
16910
+ const resolvedPackageVersion = "0.5.190";
16773
16911
  const PACKAGE_VERSION = resolvedPackageVersion;
16774
- const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.188";
16912
+ const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.190";
16775
16913
  const roundParityValue = (value) => {
16776
16914
  if (typeof value !== "number") return value;
16777
16915
  return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
@@ -17279,7 +17417,7 @@ class PixldocsRenderer {
17279
17417
  await this.waitForCanvasScene(container, cloned, i);
17280
17418
  }
17281
17419
  console.log(`[canvas-renderer][pdf-unified] mounted ${cloned.pages.length} page(s), handing off to client exportMultiPagePdf`);
17282
- const { exportMultiPagePdf, preparePagesForExport } = await Promise.resolve().then(() => require("./vectorPdfExport-B2Cf-9eT.cjs"));
17420
+ const { exportMultiPagePdf, preparePagesForExport } = await Promise.resolve().then(() => require("./vectorPdfExport-Cgw8dB-L.cjs"));
17283
17421
  const prepared = preparePagesForExport(
17284
17422
  cloned.pages,
17285
17423
  canvasWidth,
@@ -19424,7 +19562,7 @@ async function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey
19424
19562
  if (options == null ? void 0 : options.stripPageBackground) stripRootPageBackgroundFromSvg(svgToDraw);
19425
19563
  sanitizeSvgTreeForPdf(svgToDraw);
19426
19564
  try {
19427
- const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await Promise.resolve().then(() => require("./vectorPdfExport-B2Cf-9eT.cjs"));
19565
+ const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await Promise.resolve().then(() => require("./vectorPdfExport-Cgw8dB-L.cjs"));
19428
19566
  try {
19429
19567
  await logTextMeasurementDiagnostic(svgToDraw);
19430
19568
  } catch {
@@ -19775,6 +19913,7 @@ exports.assemblePdfFromSvgs = assemblePdfFromSvgs;
19775
19913
  exports.awaitFontsForConfig = awaitFontsForConfig;
19776
19914
  exports.bakeEdgeFade = bakeEdgeFade;
19777
19915
  exports.buildRoundedTrianglePath = buildRoundedTrianglePath;
19916
+ exports.buildTeaserBlurFlatKeys = buildTeaserBlurFlatKeys;
19778
19917
  exports.canvasImageLoader = canvasImageLoader;
19779
19918
  exports.captureFabricCanvasSvgForPdf = captureFabricCanvasSvgForPdf;
19780
19919
  exports.collectFontDescriptorsFromConfig = collectFontDescriptorsFromConfig;
@@ -19820,4 +19959,4 @@ exports.setAutoShrinkDebug = setAutoShrinkDebug;
19820
19959
  exports.setBundledAssetPrefixes = setBundledAssetPrefixes;
19821
19960
  exports.warmResolvedTemplateForPreview = warmResolvedTemplateForPreview;
19822
19961
  exports.warmTemplateFromForm = warmTemplateFromForm;
19823
- //# sourceMappingURL=index-0rk-MRGy.cjs.map
19962
+ //# sourceMappingURL=index-BUm3wqlB.cjs.map