@pixldocs/canvas-renderer 0.5.188 → 0.5.189

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)
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,22 @@ 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. This eliminates the
534
+ "phantom frost rectangle near a section title" class of bug that
535
+ showed up in templates with nested repeatables, where
536
+ `__cloneIdMap` returned ids like `text-…__cgroup-…_e4` but the
537
+ rendered tree used `text-…__cgrp-…_pN_grp-…_eN`.
538
+ - Pass `pageIndex` in `blurFlatFormKeyOptions` to scope verification
539
+ to a single page — clones that landed on a different page will not
540
+ contribute overlays to this page. This is the recommended pattern
541
+ for stacked multi-page previews. Default is `'all'` (verify across
542
+ every page).
543
+ - Use `skipTreeVerification: true` only if you fully trust your
544
+ `__cloneIdMap` (e.g. you just built the config in-process and
545
+ haven't mutated the tree since).
528
546
  - Defaults to `bindings: 'value'`, i.e. only flat keys whose last
529
547
  segment is `value` resolve. This is exactly the biodata teaser
530
548
  pattern: blur row values, leave labels / section titles / full-name /
@@ -533,6 +551,26 @@ Notes:
533
551
  PNG/PDF download path, resolve the same keys to exact ids and bake
534
552
  them in (see below).
535
553
 
554
+ #### `buildTeaserBlurFlatKeys(sectionState, sections, options)`
555
+
556
+ Emits the flat form-keys for every repeatable-section entry **after**
557
+ `afterRow` (1-indexed). Walks nested repeatables recursively.
558
+
559
+ - `sectionState` — the same `SectionFormState` you'd hand to
560
+ `resolveFromForm` / `<PixldocsPreview sectionState={…}>`.
561
+ - `sections` — `InferredSection[]` for the active form schema (you
562
+ already have these from `inferFormSchemaFromTemplate` or from the
563
+ `form_schemas` row converted via `fromFormDefSections`).
564
+ - `options.afterRow` — keep the first N entries sharp; blur entries
565
+ `N+1, N+2, …`.
566
+ - `options.bindings` — `'value'` (default) emits only `_value` keys
567
+ (the canonical biodata teaser pattern); `'all'` emits every entry
568
+ field key.
569
+
570
+ Returned keys can be fed straight into the `blurFlatFormKeys` prop
571
+ above and into `resolveBlurElementExactIdsFromFlatFormKeys` for the
572
+ watermarked download path below.
573
+
536
574
  #### Watermarked downloads — same resolver + `injectPreviewBlur`
537
575
 
538
576
  ```ts
@@ -544,7 +582,9 @@ import {
544
582
  const exactIds = resolveBlurElementExactIdsFromFlatFormKeys(
545
583
  resolvedConfig,
546
584
  blurFlatFormKeys,
547
- // { bindings: 'value' } by default
585
+ // { bindings: 'value', pageIndex: 'all' } by default. For the
586
+ // download path you usually want 'all' since the export covers
587
+ // every page.
548
588
  );
549
589
 
550
590
  const exportConfig = injectPreviewBlur(resolvedConfig, {
@@ -16257,6 +16257,23 @@ function addAllToSet(val, out) {
16257
16257
  out.add(val);
16258
16258
  return true;
16259
16259
  }
16260
+ function collectPageTreeElementIds(config, pageIndex = "all") {
16261
+ const out = /* @__PURE__ */ new Set();
16262
+ const pages = config == null ? void 0 : config.pages;
16263
+ if (!Array.isArray(pages)) return out;
16264
+ const targets = pageIndex === "all" ? pages : pages[pageIndex] != null ? [pages[pageIndex]] : [];
16265
+ const walk = (node) => {
16266
+ if (!node || typeof node !== "object") return;
16267
+ if (typeof node.id === "string") out.add(node.id);
16268
+ const children = node.children || node.elements;
16269
+ if (Array.isArray(children)) for (const c of children) walk(c);
16270
+ };
16271
+ for (const page of targets) {
16272
+ const children = (page == null ? void 0 : page.children) || (page == null ? void 0 : page.elements);
16273
+ if (Array.isArray(children)) for (const c of children) walk(c);
16274
+ }
16275
+ return out;
16276
+ }
16260
16277
  function resolveBlurElementExactIdsFromFlatFormKeys(config, flatFormKeys, options) {
16261
16278
  const cloneIdMap = (config == null ? void 0 : config.__cloneIdMap) || {};
16262
16279
  if (!cloneIdMap || typeof cloneIdMap !== "object") return [];
@@ -16281,10 +16298,59 @@ function resolveBlurElementExactIdsFromFlatFormKeys(config, flatFormKeys, option
16281
16298
  }
16282
16299
  }
16283
16300
  }
16284
- return Array.from(out);
16301
+ if (out.size === 0) return [];
16302
+ if (options == null ? void 0 : options.skipTreeVerification) return Array.from(out);
16303
+ const treeIds = collectPageTreeElementIds(
16304
+ config,
16305
+ (options == null ? void 0 : options.pageIndex) ?? "all"
16306
+ );
16307
+ if (treeIds.size === 0) return Array.from(out);
16308
+ const verified = [];
16309
+ for (const id of out) if (treeIds.has(id)) verified.push(id);
16310
+ return verified;
16311
+ }
16312
+ function buildTeaserBlurFlatKeys(sectionState, sections, options) {
16313
+ const afterRow = Math.max(0, options.afterRow | 0);
16314
+ const bindings = options.bindings ?? "value";
16315
+ const out = [];
16316
+ if (!sectionState || !Array.isArray(sections)) return out;
16317
+ const repeatables = sections.filter((s) => (s == null ? void 0 : s.type) === "repeatable");
16318
+ const childrenByParent = /* @__PURE__ */ new Map();
16319
+ for (const r of repeatables) {
16320
+ if (!r.parentId) continue;
16321
+ const arr = childrenByParent.get(r.parentId) || [];
16322
+ arr.push(r);
16323
+ childrenByParent.set(r.parentId, arr);
16324
+ }
16325
+ const emitForEntryFields = (section, keyPrefix, entryIdx1) => {
16326
+ const fields = section.entryFields || [];
16327
+ for (const f of fields) {
16328
+ if (!(f == null ? void 0 : f.key)) continue;
16329
+ if (bindings === "value" && f.key !== "value") continue;
16330
+ out.push(`${keyPrefix}_${f.key}`);
16331
+ }
16332
+ };
16333
+ const walkRepeatable = (section, parentKeyPrefix) => {
16334
+ const entries = sectionState == null ? void 0 : sectionState[section.id];
16335
+ if (!Array.isArray(entries)) return;
16336
+ for (let i = 0; i < entries.length; i++) {
16337
+ const entryIdx1 = i + 1;
16338
+ const isBlurred = entryIdx1 > afterRow;
16339
+ const keyPrefix = parentKeyPrefix ? `${parentKeyPrefix}_field_${section.id}_${entryIdx1}` : `field_${section.id}_${entryIdx1}`;
16340
+ if (isBlurred) emitForEntryFields(section, keyPrefix);
16341
+ const children = childrenByParent.get(section.id) || [];
16342
+ for (const child of children) walkRepeatable(child, keyPrefix);
16343
+ }
16344
+ };
16345
+ for (const r of repeatables) {
16346
+ if (r.parentId) continue;
16347
+ walkRepeatable(r, "");
16348
+ }
16349
+ return out;
16285
16350
  }
16286
16351
  const previewBlur = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
16287
16352
  __proto__: null,
16353
+ buildTeaserBlurFlatKeys,
16288
16354
  hasAnyPreviewBlur,
16289
16355
  injectPreviewBlur,
16290
16356
  resolveBlurElementExactIdsFromFlatFormKeys
@@ -16769,9 +16835,9 @@ function captureFabricCanvasSvgForPdf(fabricInstance, canvasWidth, canvasHeight)
16769
16835
  }
16770
16836
  return svgString;
16771
16837
  }
16772
- const resolvedPackageVersion = "0.5.188";
16838
+ const resolvedPackageVersion = "0.5.189";
16773
16839
  const PACKAGE_VERSION = resolvedPackageVersion;
16774
- const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.188";
16840
+ const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.189";
16775
16841
  const roundParityValue = (value) => {
16776
16842
  if (typeof value !== "number") return value;
16777
16843
  return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
@@ -17279,7 +17345,7 @@ class PixldocsRenderer {
17279
17345
  await this.waitForCanvasScene(container, cloned, i);
17280
17346
  }
17281
17347
  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"));
17348
+ const { exportMultiPagePdf, preparePagesForExport } = await Promise.resolve().then(() => require("./vectorPdfExport-BAAJU0RL.cjs"));
17283
17349
  const prepared = preparePagesForExport(
17284
17350
  cloned.pages,
17285
17351
  canvasWidth,
@@ -19424,7 +19490,7 @@ async function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey
19424
19490
  if (options == null ? void 0 : options.stripPageBackground) stripRootPageBackgroundFromSvg(svgToDraw);
19425
19491
  sanitizeSvgTreeForPdf(svgToDraw);
19426
19492
  try {
19427
- const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await Promise.resolve().then(() => require("./vectorPdfExport-B2Cf-9eT.cjs"));
19493
+ const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await Promise.resolve().then(() => require("./vectorPdfExport-BAAJU0RL.cjs"));
19428
19494
  try {
19429
19495
  await logTextMeasurementDiagnostic(svgToDraw);
19430
19496
  } catch {
@@ -19775,6 +19841,7 @@ exports.assemblePdfFromSvgs = assemblePdfFromSvgs;
19775
19841
  exports.awaitFontsForConfig = awaitFontsForConfig;
19776
19842
  exports.bakeEdgeFade = bakeEdgeFade;
19777
19843
  exports.buildRoundedTrianglePath = buildRoundedTrianglePath;
19844
+ exports.buildTeaserBlurFlatKeys = buildTeaserBlurFlatKeys;
19778
19845
  exports.canvasImageLoader = canvasImageLoader;
19779
19846
  exports.captureFabricCanvasSvgForPdf = captureFabricCanvasSvgForPdf;
19780
19847
  exports.collectFontDescriptorsFromConfig = collectFontDescriptorsFromConfig;
@@ -19820,4 +19887,4 @@ exports.setAutoShrinkDebug = setAutoShrinkDebug;
19820
19887
  exports.setBundledAssetPrefixes = setBundledAssetPrefixes;
19821
19888
  exports.warmResolvedTemplateForPreview = warmResolvedTemplateForPreview;
19822
19889
  exports.warmTemplateFromForm = warmTemplateFromForm;
19823
- //# sourceMappingURL=index-0rk-MRGy.cjs.map
19890
+ //# sourceMappingURL=index-CmrxeQ_K.cjs.map