@pixldocs/canvas-renderer 0.5.187 → 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, {
@@ -556,3 +596,13 @@ const exportConfig = injectPreviewBlur(resolvedConfig, {
556
596
 
557
597
  Same resolution path as the live preview — so the on-screen overlay and
558
598
  the downloaded PNG/PDF blur exactly the same elements.
599
+
600
+ #### Parity note
601
+
602
+ The pixldocs.com **Use page** (the in-app paid-template preview, including
603
+ the Biodata preset's 🔒 per-field checkboxes) and external consumers
604
+ (BioMaker, etc.) both call **the same `resolveBlurElementExactIdsFromFlatFormKeys`**
605
+ function — there is now exactly one resolver shared across the entire
606
+ stack. Anything that blurs correctly in one place blurs identically in
607
+ the other; visual/UX bugs introduced in one pipeline cannot diverge
608
+ silently from the other.
@@ -16133,10 +16133,122 @@ async function getTemplateForm(options) {
16133
16133
  initialSectionState
16134
16134
  };
16135
16135
  }
16136
+ const OVERLAY_ID_PREFIX = "__pb_";
16137
+ function getNumber(v, fallback = 0) {
16138
+ return typeof v === "number" && Number.isFinite(v) ? v : fallback;
16139
+ }
16140
+ function resolveSize(node, key) {
16141
+ const v = node == null ? void 0 : node[key];
16142
+ if (typeof v === "number" && Number.isFinite(v)) return v;
16143
+ return 0;
16144
+ }
16145
+ function collectOverlays(node, parentLeft, parentTop, inheritedBlur, extraBaseIds, extraExactIds, out) {
16146
+ if (!node || typeof node !== "object") return;
16147
+ const matchesBase = !!(extraBaseIds && typeof node.id === "string" && extraBaseIds.has(baseId(node.id)));
16148
+ const matchesExact = !!(extraExactIds && typeof node.id === "string" && extraExactIds.has(node.id));
16149
+ const isBlurred = inheritedBlur || node.previewBlur === true || matchesBase || matchesExact;
16150
+ const absLeft = parentLeft + getNumber(node.left, 0);
16151
+ const absTop = parentTop + getNumber(node.top, 0);
16152
+ if (node.type === "group") {
16153
+ const children = node.children || node.elements;
16154
+ if (Array.isArray(children)) {
16155
+ for (const child of children) {
16156
+ collectOverlays(child, absLeft, absTop, isBlurred, extraBaseIds, extraExactIds, out);
16157
+ }
16158
+ }
16159
+ return;
16160
+ }
16161
+ if (!isBlurred) return;
16162
+ const scaleX = getNumber(node.scaleX, 1) || 1;
16163
+ const scaleY = getNumber(node.scaleY, 1) || 1;
16164
+ const w = Math.max(4, resolveSize(node, "width") * scaleX);
16165
+ const h = Math.max(4, resolveSize(node, "height") * scaleY);
16166
+ out.push({
16167
+ left: absLeft,
16168
+ top: absTop,
16169
+ width: w,
16170
+ height: h,
16171
+ hintFill: typeof node.fill === "string" ? node.fill : void 0
16172
+ });
16173
+ }
16174
+ function parseColor$1(c) {
16175
+ if (!c) return null;
16176
+ const s = c.trim();
16177
+ const hex = s.match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/i);
16178
+ if (hex) {
16179
+ let h = hex[1];
16180
+ if (h.length === 3) h = h.split("").map((x) => x + x).join("");
16181
+ return {
16182
+ r: parseInt(h.slice(0, 2), 16),
16183
+ g: parseInt(h.slice(2, 4), 16),
16184
+ b: parseInt(h.slice(4, 6), 16)
16185
+ };
16186
+ }
16187
+ const rgb = s.match(/^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/i);
16188
+ if (rgb) return { r: +rgb[1], g: +rgb[2], b: +rgb[3] };
16189
+ return null;
16190
+ }
16191
+ function buildOverlay(b, idx) {
16192
+ const c = parseColor$1(b.hintFill);
16193
+ const lum = c ? (0.299 * c.r + 0.587 * c.g + 0.114 * c.b) / 255 : 0.2;
16194
+ const isLightGlass = lum < 0.5;
16195
+ const baseFill = isLightGlass ? "rgba(245,245,245,0.68)" : "rgba(30,30,30,0.62)";
16196
+ return {
16197
+ id: `${OVERLAY_ID_PREFIX}${idx}`,
16198
+ type: "shape",
16199
+ shapeType: "rounded-rect",
16200
+ left: b.left,
16201
+ top: b.top,
16202
+ width: b.width,
16203
+ height: b.height,
16204
+ cornerRadius: 0,
16205
+ fill: baseFill,
16206
+ stroke: "transparent",
16207
+ strokeWidth: 0,
16208
+ opacity: 1,
16209
+ selectable: false,
16210
+ locked: true,
16211
+ visible: true,
16212
+ scaleX: 1,
16213
+ scaleY: 1
16214
+ };
16215
+ }
16216
+ function injectPreviewBlur(config, options) {
16217
+ const cloned = typeof structuredClone === "function" ? structuredClone(config) : JSON.parse(JSON.stringify(config));
16218
+ const extraBase = (options == null ? void 0 : options.extraElementBaseIds) && options.extraElementBaseIds.size > 0 ? options.extraElementBaseIds : void 0;
16219
+ const extraExact = (options == null ? void 0 : options.extraElementExactIds) && options.extraElementExactIds.size > 0 ? options.extraElementExactIds : void 0;
16220
+ let idx = 0;
16221
+ for (const page of cloned.pages || []) {
16222
+ const children = page.children || page.elements;
16223
+ if (!Array.isArray(children)) continue;
16224
+ const overlays = [];
16225
+ for (const child of children) {
16226
+ collectOverlays(child, 0, 0, false, extraBase, extraExact, overlays);
16227
+ }
16228
+ if (overlays.length === 0) continue;
16229
+ for (const b of overlays) {
16230
+ children.push(buildOverlay(b, idx++));
16231
+ }
16232
+ }
16233
+ return cloned;
16234
+ }
16235
+ function hasAnyPreviewBlur(config) {
16236
+ function walk(node) {
16237
+ if (!node || typeof node !== "object") return false;
16238
+ if (node.previewBlur === true) return true;
16239
+ const children = node.children || node.elements;
16240
+ if (Array.isArray(children)) {
16241
+ for (const c of children) if (walk(c)) return true;
16242
+ }
16243
+ return false;
16244
+ }
16245
+ for (const page of config.pages || []) if (walk(page)) return true;
16246
+ return false;
16247
+ }
16136
16248
  function stripFieldPrefix(s) {
16137
16249
  return s.startsWith("field_") ? s.slice("field_".length) : s;
16138
16250
  }
16139
- function addAll(val, out) {
16251
+ function addAllToSet(val, out) {
16140
16252
  if (!val) return false;
16141
16253
  if (Array.isArray(val)) {
16142
16254
  for (const v of val) out.add(v);
@@ -16145,6 +16257,23 @@ function addAll(val, out) {
16145
16257
  out.add(val);
16146
16258
  return true;
16147
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
+ }
16148
16277
  function resolveBlurElementExactIdsFromFlatFormKeys(config, flatFormKeys, options) {
16149
16278
  const cloneIdMap = (config == null ? void 0 : config.__cloneIdMap) || {};
16150
16279
  if (!cloneIdMap || typeof cloneIdMap !== "object") return [];
@@ -16165,12 +16294,67 @@ function resolveBlurElementExactIdsFromFlatFormKeys(config, flatFormKeys, option
16165
16294
  }
16166
16295
  for (const k of candidates) {
16167
16296
  if (k in cloneIdMap) {
16168
- if (addAll(cloneIdMap[k], out)) break;
16297
+ if (addAllToSet(cloneIdMap[k], out)) break;
16169
16298
  }
16170
16299
  }
16171
16300
  }
16172
- 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;
16173
16350
  }
16351
+ const previewBlur = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
16352
+ __proto__: null,
16353
+ buildTeaserBlurFlatKeys,
16354
+ hasAnyPreviewBlur,
16355
+ injectPreviewBlur,
16356
+ resolveBlurElementExactIdsFromFlatFormKeys
16357
+ }, Symbol.toStringTag, { value: "Module" }));
16174
16358
  const PREVIEW_DEBUG_PREFIX = "[canvas-renderer][preview-debug]";
16175
16359
  function computeFontSignature(config) {
16176
16360
  var _a;
@@ -16651,9 +16835,9 @@ function captureFabricCanvasSvgForPdf(fabricInstance, canvasWidth, canvasHeight)
16651
16835
  }
16652
16836
  return svgString;
16653
16837
  }
16654
- const resolvedPackageVersion = "0.5.187";
16838
+ const resolvedPackageVersion = "0.5.189";
16655
16839
  const PACKAGE_VERSION = resolvedPackageVersion;
16656
- const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.187";
16840
+ const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.189";
16657
16841
  const roundParityValue = (value) => {
16658
16842
  if (typeof value !== "number") return value;
16659
16843
  return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
@@ -17161,7 +17345,7 @@ class PixldocsRenderer {
17161
17345
  await this.waitForCanvasScene(container, cloned, i);
17162
17346
  }
17163
17347
  console.log(`[canvas-renderer][pdf-unified] mounted ${cloned.pages.length} page(s), handing off to client exportMultiPagePdf`);
17164
- const { exportMultiPagePdf, preparePagesForExport } = await Promise.resolve().then(() => require("./vectorPdfExport-BknzMTNP.cjs"));
17348
+ const { exportMultiPagePdf, preparePagesForExport } = await Promise.resolve().then(() => require("./vectorPdfExport-BAAJU0RL.cjs"));
17165
17349
  const prepared = preparePagesForExport(
17166
17350
  cloned.pages,
17167
17351
  canvasWidth,
@@ -18003,7 +18187,7 @@ function collectFontSpecsFromMarkup(markup) {
18003
18187
  }
18004
18188
  return Array.from(specs);
18005
18189
  }
18006
- function parseColor$1(color) {
18190
+ function parseColor(color) {
18007
18191
  if (!color) return null;
18008
18192
  const raw = color.trim().toLowerCase();
18009
18193
  if (!raw || raw === "transparent" || raw === "none") return null;
@@ -18556,7 +18740,7 @@ function stripSuspiciousFullPageOverlayNodes(svg) {
18556
18740
  if (!(pageWidth > 0 && pageHeight > 0)) return;
18557
18741
  const isNear = (a, b, tolerance = 0.75) => Math.abs(a - b) <= tolerance;
18558
18742
  const isDarkPaint = (value) => {
18559
- const rgb = value ? parseColor$1(value) : null;
18743
+ const rgb = value ? parseColor(value) : null;
18560
18744
  return rgb ? rgb.r <= 32 && rgb.g <= 32 && rgb.b <= 32 : false;
18561
18745
  };
18562
18746
  const removeIfSuspicious = (el) => {
@@ -18669,13 +18853,13 @@ function inlineComputedStyles(svg) {
18669
18853
  const fill = cs.fill;
18670
18854
  const stroke = cs.stroke;
18671
18855
  if (fill && fill !== "none" && fill !== "rgba(0, 0, 0, 0)") {
18672
- const parsed = parseColor$1(fill);
18856
+ const parsed = parseColor(fill);
18673
18857
  if (parsed) el.setAttribute("fill", rgbToHex(parsed.r, parsed.g, parsed.b));
18674
18858
  } else if (fill === "rgba(0, 0, 0, 0)" || fill === "transparent") {
18675
18859
  el.setAttribute("fill", "none");
18676
18860
  }
18677
18861
  if (stroke && stroke !== "none" && stroke !== "rgba(0, 0, 0, 0)") {
18678
- const parsed = parseColor$1(stroke);
18862
+ const parsed = parseColor(stroke);
18679
18863
  if (parsed) el.setAttribute("stroke", rgbToHex(parsed.r, parsed.g, parsed.b));
18680
18864
  }
18681
18865
  }
@@ -18823,7 +19007,7 @@ function setPdfColorFromSvg(pdf, svg, _elementId) {
18823
19007
  const { fill, stroke } = getFirstExplicitColorFromSvg(svg);
18824
19008
  const setColor = (hex, setter) => {
18825
19009
  if (!hex) return;
18826
- const c = parseColor$1(hex);
19010
+ const c = parseColor(hex);
18827
19011
  if (c) pdf[setter](c.r, c.g, c.b);
18828
19012
  };
18829
19013
  setColor(fill, "setFillColor");
@@ -19306,7 +19490,7 @@ async function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey
19306
19490
  if (options == null ? void 0 : options.stripPageBackground) stripRootPageBackgroundFromSvg(svgToDraw);
19307
19491
  sanitizeSvgTreeForPdf(svgToDraw);
19308
19492
  try {
19309
- const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await Promise.resolve().then(() => require("./vectorPdfExport-BknzMTNP.cjs"));
19493
+ const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await Promise.resolve().then(() => require("./vectorPdfExport-BAAJU0RL.cjs"));
19310
19494
  try {
19311
19495
  await logTextMeasurementDiagnostic(svgToDraw);
19312
19496
  } catch {
@@ -19326,7 +19510,7 @@ function drawPageBackground(pdf, pageIndex, pageWidth, pageHeight, backgroundCol
19326
19510
  if (backgroundGradient && ((_a = backgroundGradient.stops) == null ? void 0 : _a.length) >= 2) {
19327
19511
  const grad = backgroundGradient;
19328
19512
  const colorStops = grad.stops.map((s) => {
19329
- const c = parseColor$1(s.color);
19513
+ const c = parseColor(s.color);
19330
19514
  return {
19331
19515
  offset: Math.max(0, Math.min(1, Number(s.offset))),
19332
19516
  color: c ? [c.r, c.g, c.b] : [0, 0, 0]
@@ -19382,7 +19566,7 @@ function drawPageBackground(pdf, pageIndex, pageWidth, pageHeight, backgroundCol
19382
19566
  pdf.rect(0, 0, pageWidth, pageHeight, "F");
19383
19567
  }
19384
19568
  } else {
19385
- const bgColor = parseColor$1(backgroundColor && backgroundColor !== "transparent" ? backgroundColor : "#ffffff");
19569
+ const bgColor = parseColor(backgroundColor && backgroundColor !== "transparent" ? backgroundColor : "#ffffff");
19386
19570
  if (bgColor) {
19387
19571
  pdf.setFillColor(bgColor.r, bgColor.g, bgColor.b);
19388
19572
  pdf.rect(0, 0, pageWidth, pageHeight, "F");
@@ -19505,123 +19689,6 @@ async function assemblePdfFromSvgs(svgResults, options = {}) {
19505
19689
  pages: svgResults.map((p) => ({ width: p.width, height: p.height }))
19506
19690
  };
19507
19691
  }
19508
- const OVERLAY_ID_PREFIX = "__pb_";
19509
- function getNumber(v, fallback = 0) {
19510
- return typeof v === "number" && Number.isFinite(v) ? v : fallback;
19511
- }
19512
- function resolveSize(node, key) {
19513
- const v = node == null ? void 0 : node[key];
19514
- if (typeof v === "number" && Number.isFinite(v)) return v;
19515
- return 0;
19516
- }
19517
- function collectOverlays(node, parentLeft, parentTop, inheritedBlur, extraBaseIds, extraExactIds, out) {
19518
- if (!node || typeof node !== "object") return;
19519
- const matchesBase = !!(extraBaseIds && typeof node.id === "string" && extraBaseIds.has(baseId(node.id)));
19520
- const matchesExact = !!(extraExactIds && typeof node.id === "string" && extraExactIds.has(node.id));
19521
- const isBlurred = inheritedBlur || node.previewBlur === true || matchesBase || matchesExact;
19522
- const absLeft = parentLeft + getNumber(node.left, 0);
19523
- const absTop = parentTop + getNumber(node.top, 0);
19524
- if (node.type === "group") {
19525
- const children = node.children || node.elements;
19526
- if (Array.isArray(children)) {
19527
- for (const child of children) {
19528
- collectOverlays(child, absLeft, absTop, isBlurred, extraBaseIds, extraExactIds, out);
19529
- }
19530
- }
19531
- return;
19532
- }
19533
- if (!isBlurred) return;
19534
- const scaleX = getNumber(node.scaleX, 1) || 1;
19535
- const scaleY = getNumber(node.scaleY, 1) || 1;
19536
- const w = Math.max(4, resolveSize(node, "width") * scaleX);
19537
- const h = Math.max(4, resolveSize(node, "height") * scaleY);
19538
- out.push({
19539
- left: absLeft,
19540
- top: absTop,
19541
- width: w,
19542
- height: h,
19543
- hintFill: typeof node.fill === "string" ? node.fill : void 0
19544
- });
19545
- }
19546
- function parseColor(c) {
19547
- if (!c) return null;
19548
- const s = c.trim();
19549
- const hex = s.match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/i);
19550
- if (hex) {
19551
- let h = hex[1];
19552
- if (h.length === 3) h = h.split("").map((x) => x + x).join("");
19553
- return {
19554
- r: parseInt(h.slice(0, 2), 16),
19555
- g: parseInt(h.slice(2, 4), 16),
19556
- b: parseInt(h.slice(4, 6), 16)
19557
- };
19558
- }
19559
- const rgb = s.match(/^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/i);
19560
- if (rgb) return { r: +rgb[1], g: +rgb[2], b: +rgb[3] };
19561
- return null;
19562
- }
19563
- function buildOverlay(b, idx) {
19564
- const c = parseColor(b.hintFill);
19565
- const lum = c ? (0.299 * c.r + 0.587 * c.g + 0.114 * c.b) / 255 : 0.2;
19566
- const isLightGlass = lum < 0.5;
19567
- const baseFill = isLightGlass ? "rgba(245,245,245,0.68)" : "rgba(30,30,30,0.62)";
19568
- return {
19569
- id: `${OVERLAY_ID_PREFIX}${idx}`,
19570
- type: "shape",
19571
- shapeType: "rounded-rect",
19572
- left: b.left,
19573
- top: b.top,
19574
- width: b.width,
19575
- height: b.height,
19576
- cornerRadius: 0,
19577
- fill: baseFill,
19578
- stroke: "transparent",
19579
- strokeWidth: 0,
19580
- opacity: 1,
19581
- selectable: false,
19582
- locked: true,
19583
- visible: true,
19584
- scaleX: 1,
19585
- scaleY: 1
19586
- };
19587
- }
19588
- function injectPreviewBlur(config, options) {
19589
- const cloned = typeof structuredClone === "function" ? structuredClone(config) : JSON.parse(JSON.stringify(config));
19590
- const extraBase = (options == null ? void 0 : options.extraElementBaseIds) && options.extraElementBaseIds.size > 0 ? options.extraElementBaseIds : void 0;
19591
- const extraExact = (options == null ? void 0 : options.extraElementExactIds) && options.extraElementExactIds.size > 0 ? options.extraElementExactIds : void 0;
19592
- let idx = 0;
19593
- for (const page of cloned.pages || []) {
19594
- const children = page.children || page.elements;
19595
- if (!Array.isArray(children)) continue;
19596
- const overlays = [];
19597
- for (const child of children) {
19598
- collectOverlays(child, 0, 0, false, extraBase, extraExact, overlays);
19599
- }
19600
- if (overlays.length === 0) continue;
19601
- for (const b of overlays) {
19602
- children.push(buildOverlay(b, idx++));
19603
- }
19604
- }
19605
- return cloned;
19606
- }
19607
- function hasAnyPreviewBlur(config) {
19608
- function walk(node) {
19609
- if (!node || typeof node !== "object") return false;
19610
- if (node.previewBlur === true) return true;
19611
- const children = node.children || node.elements;
19612
- if (Array.isArray(children)) {
19613
- for (const c of children) if (walk(c)) return true;
19614
- }
19615
- return false;
19616
- }
19617
- for (const page of config.pages || []) if (walk(page)) return true;
19618
- return false;
19619
- }
19620
- const previewBlur = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
19621
- __proto__: null,
19622
- hasAnyPreviewBlur,
19623
- injectPreviewBlur
19624
- }, Symbol.toStringTag, { value: "Module" }));
19625
19692
  const SELECT_COLUMNS = "id,name,description,category,thumbnail_url,preview_images,price,download_count,workspace_id,sort_order,created_at,updated_at";
19626
19693
  async function listPublishedTemplates(options) {
19627
19694
  const { workspaceId, supabaseUrl, supabaseAnonKey, category, limit = 200, offset = 0 } = options;
@@ -19774,6 +19841,7 @@ exports.assemblePdfFromSvgs = assemblePdfFromSvgs;
19774
19841
  exports.awaitFontsForConfig = awaitFontsForConfig;
19775
19842
  exports.bakeEdgeFade = bakeEdgeFade;
19776
19843
  exports.buildRoundedTrianglePath = buildRoundedTrianglePath;
19844
+ exports.buildTeaserBlurFlatKeys = buildTeaserBlurFlatKeys;
19777
19845
  exports.canvasImageLoader = canvasImageLoader;
19778
19846
  exports.captureFabricCanvasSvgForPdf = captureFabricCanvasSvgForPdf;
19779
19847
  exports.collectFontDescriptorsFromConfig = collectFontDescriptorsFromConfig;
@@ -19819,4 +19887,4 @@ exports.setAutoShrinkDebug = setAutoShrinkDebug;
19819
19887
  exports.setBundledAssetPrefixes = setBundledAssetPrefixes;
19820
19888
  exports.warmResolvedTemplateForPreview = warmResolvedTemplateForPreview;
19821
19889
  exports.warmTemplateFromForm = warmTemplateFromForm;
19822
- //# sourceMappingURL=index-DgMi1JSR.cjs.map
19890
+ //# sourceMappingURL=index-CmrxeQ_K.cjs.map