@pixldocs/canvas-renderer 0.3.13 → 0.3.15

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.d.ts CHANGED
@@ -181,7 +181,8 @@ export declare class PixldocsRenderer {
181
181
  renderById(templateId: string, formData?: Record<string, any>, options?: RenderOptions): Promise<RenderResult>;
182
182
  /**
183
183
  * Wait until every image on the Fabric canvas inside the container has loaded.
184
- * Polls img elements since we can't hook into Fabric's internal load events.
184
+ * Checks both DOM <img> elements AND Fabric canvas image objects to ensure
185
+ * all async assets are fully painted before capture.
185
186
  */
186
187
  private waitForCanvasImages;
187
188
  private getNormalizedGradientStops;
package/dist/index.js CHANGED
@@ -10369,79 +10369,6 @@ function paintRepeatableSections(config, repeatableSections) {
10369
10369
  }
10370
10370
  }
10371
10371
  }
10372
- function PixldocsPreview(props) {
10373
- const {
10374
- pageIndex = 0,
10375
- zoom = 1,
10376
- absoluteZoom = false,
10377
- imageProxyUrl,
10378
- className,
10379
- style,
10380
- onDynamicFieldClick,
10381
- onReady,
10382
- onError
10383
- } = props;
10384
- useEffect(() => {
10385
- setPackageApiUrl(imageProxyUrl);
10386
- }, [imageProxyUrl]);
10387
- const [resolvedConfig, setResolvedConfig] = useState(null);
10388
- const [isLoading, setIsLoading] = useState(false);
10389
- const isResolveMode = !("config" in props && props.config);
10390
- useEffect(() => {
10391
- if (!isResolveMode) {
10392
- setResolvedConfig(null);
10393
- return;
10394
- }
10395
- const p = props;
10396
- if (!p.templateId || !p.formSchemaId || !p.supabaseUrl || !p.supabaseAnonKey) return;
10397
- let cancelled = false;
10398
- setIsLoading(true);
10399
- resolveFromForm({
10400
- templateId: p.templateId,
10401
- formSchemaId: p.formSchemaId,
10402
- sectionState: p.sectionState,
10403
- themeId: p.themeId,
10404
- supabaseUrl: p.supabaseUrl,
10405
- supabaseAnonKey: p.supabaseAnonKey
10406
- }).then((resolved) => {
10407
- if (!cancelled) {
10408
- setResolvedConfig(resolved.config);
10409
- setIsLoading(false);
10410
- }
10411
- }).catch((err) => {
10412
- if (!cancelled) {
10413
- setIsLoading(false);
10414
- onError == null ? void 0 : onError(err instanceof Error ? err : new Error(String(err)));
10415
- }
10416
- });
10417
- return () => {
10418
- cancelled = true;
10419
- };
10420
- }, [
10421
- isResolveMode,
10422
- // For resolve mode, re-resolve when these change
10423
- isResolveMode ? props.templateId : void 0,
10424
- isResolveMode ? props.formSchemaId : void 0,
10425
- isResolveMode ? JSON.stringify(props.sectionState) : void 0,
10426
- isResolveMode ? props.themeId : void 0
10427
- ]);
10428
- const config = isResolveMode ? resolvedConfig : props.config;
10429
- if (isLoading) {
10430
- 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..." }) });
10431
- }
10432
- if (!config) return null;
10433
- return /* @__PURE__ */ jsx("div", { className, style, children: /* @__PURE__ */ jsx(
10434
- PreviewCanvas,
10435
- {
10436
- config,
10437
- pageIndex,
10438
- zoom,
10439
- absoluteZoom,
10440
- onDynamicFieldClick,
10441
- onReady
10442
- }
10443
- ) });
10444
- }
10445
10372
  function normalizeFontFamily(fontStack) {
10446
10373
  const first = fontStack.split(",")[0].trim();
10447
10374
  return first.replace(/^['"]|['"]$/g, "");
@@ -10456,12 +10383,7 @@ async function loadGoogleFontCSS(rawFontFamily) {
10456
10383
  const existing = loadingPromises.get(fontFamily);
10457
10384
  if (existing) return existing;
10458
10385
  const promise = (async () => {
10459
- var _a;
10460
10386
  try {
10461
- if ((_a = document.fonts) == null ? void 0 : _a.check(`16px "${fontFamily}"`)) {
10462
- loadedFonts.add(fontFamily);
10463
- return;
10464
- }
10465
10387
  const encoded = encodeURIComponent(fontFamily);
10466
10388
  const url = `https://fonts.googleapis.com/css?family=${encoded}:300,400,500,600,700&display=swap`;
10467
10389
  const link = document.createElement("link");
@@ -10490,6 +10412,7 @@ function collectFontsFromConfig(config) {
10490
10412
  var _a;
10491
10413
  const fonts = /* @__PURE__ */ new Set();
10492
10414
  fonts.add("Open Sans");
10415
+ fonts.add("Hind");
10493
10416
  function walk(nodes) {
10494
10417
  var _a2;
10495
10418
  if (!nodes) return;
@@ -10542,12 +10465,18 @@ function collectFontDescriptorsFromConfig(config) {
10542
10465
  for (const node of nodes) {
10543
10466
  if (node.fontFamily) {
10544
10467
  add(node.fontFamily, node.fontWeight, node.fontStyle);
10468
+ if (node.type === "text") {
10469
+ for (const w of [300, 400, 500, 600, 700]) {
10470
+ add(node.fontFamily, w, node.fontStyle);
10471
+ }
10472
+ }
10545
10473
  }
10546
10474
  if ((_a2 = node.smartProps) == null ? void 0 : _a2.fontFamily) {
10547
10475
  add(node.smartProps.fontFamily, node.smartProps.fontWeight, node.smartProps.fontStyle);
10548
10476
  }
10549
- if (node.styles && Array.isArray(node.styles)) {
10550
- for (const lineStyle of node.styles) {
10477
+ if (node.styles) {
10478
+ const styleEntries = Array.isArray(node.styles) ? node.styles : Object.values(node.styles);
10479
+ for (const lineStyle of styleEntries) {
10551
10480
  if (lineStyle && typeof lineStyle === "object") {
10552
10481
  for (const charStyle of Object.values(lineStyle)) {
10553
10482
  if (charStyle == null ? void 0 : charStyle.fontFamily) {
@@ -10561,6 +10490,8 @@ function collectFontDescriptorsFromConfig(config) {
10561
10490
  }
10562
10491
  }
10563
10492
  add("Open Sans", 400, "normal");
10493
+ add("Hind", 400, "normal");
10494
+ add("Hind", 700, "normal");
10564
10495
  for (const page of config.pages || []) {
10565
10496
  walk(page.children || []);
10566
10497
  }
@@ -10592,6 +10523,95 @@ async function ensureFontsForResolvedConfig(config) {
10592
10523
  await document.fonts.ready;
10593
10524
  }
10594
10525
  }
10526
+ function PixldocsPreview(props) {
10527
+ const {
10528
+ pageIndex = 0,
10529
+ zoom = 1,
10530
+ absoluteZoom = false,
10531
+ imageProxyUrl,
10532
+ className,
10533
+ style,
10534
+ onDynamicFieldClick,
10535
+ onReady,
10536
+ onError
10537
+ } = props;
10538
+ useEffect(() => {
10539
+ setPackageApiUrl(imageProxyUrl);
10540
+ }, [imageProxyUrl]);
10541
+ const [resolvedConfig, setResolvedConfig] = useState(null);
10542
+ const [isLoading, setIsLoading] = useState(false);
10543
+ const [fontsReady, setFontsReady] = useState(false);
10544
+ const isResolveMode = !("config" in props && props.config);
10545
+ useEffect(() => {
10546
+ if (!isResolveMode) {
10547
+ setResolvedConfig(null);
10548
+ return;
10549
+ }
10550
+ const p = props;
10551
+ if (!p.templateId || !p.formSchemaId || !p.supabaseUrl || !p.supabaseAnonKey) return;
10552
+ let cancelled = false;
10553
+ setIsLoading(true);
10554
+ resolveFromForm({
10555
+ templateId: p.templateId,
10556
+ formSchemaId: p.formSchemaId,
10557
+ sectionState: p.sectionState,
10558
+ themeId: p.themeId,
10559
+ supabaseUrl: p.supabaseUrl,
10560
+ supabaseAnonKey: p.supabaseAnonKey
10561
+ }).then((resolved) => {
10562
+ if (!cancelled) {
10563
+ setResolvedConfig(resolved.config);
10564
+ ensureFontsForResolvedConfig(resolved.config).then(() => {
10565
+ if (!cancelled) {
10566
+ setFontsReady(true);
10567
+ setIsLoading(false);
10568
+ }
10569
+ }).catch(() => {
10570
+ if (!cancelled) {
10571
+ setFontsReady(true);
10572
+ setIsLoading(false);
10573
+ }
10574
+ });
10575
+ }
10576
+ }).catch((err) => {
10577
+ if (!cancelled) {
10578
+ setIsLoading(false);
10579
+ onError == null ? void 0 : onError(err instanceof Error ? err : new Error(String(err)));
10580
+ }
10581
+ });
10582
+ return () => {
10583
+ cancelled = true;
10584
+ };
10585
+ }, [
10586
+ isResolveMode,
10587
+ // For resolve mode, re-resolve when these change
10588
+ isResolveMode ? props.templateId : void 0,
10589
+ isResolveMode ? props.formSchemaId : void 0,
10590
+ isResolveMode ? JSON.stringify(props.sectionState) : void 0,
10591
+ isResolveMode ? props.themeId : void 0
10592
+ ]);
10593
+ const config = isResolveMode ? resolvedConfig : props.config;
10594
+ useEffect(() => {
10595
+ if (isResolveMode || !config) return;
10596
+ setFontsReady(false);
10597
+ ensureFontsForResolvedConfig(config).then(() => setFontsReady(true)).catch(() => setFontsReady(true));
10598
+ }, [isResolveMode, config]);
10599
+ if (isLoading) {
10600
+ 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..." }) });
10601
+ }
10602
+ if (!config) return null;
10603
+ return /* @__PURE__ */ jsx("div", { className, style, children: /* @__PURE__ */ jsx(
10604
+ PreviewCanvas,
10605
+ {
10606
+ config,
10607
+ pageIndex,
10608
+ zoom,
10609
+ absoluteZoom,
10610
+ onDynamicFieldClick,
10611
+ onReady
10612
+ }
10613
+ ) });
10614
+ }
10595
10615
  class PixldocsRenderer {
10596
10616
  constructor(config) {
10597
10617
  __publicField(this, "config");
@@ -10671,7 +10691,8 @@ class PixldocsRenderer {
10671
10691
  // ─── Internal: render a page using the full PreviewCanvas engine ───
10672
10692
  /**
10673
10693
  * Wait until every image on the Fabric canvas inside the container has loaded.
10674
- * Polls img elements since we can't hook into Fabric's internal load events.
10694
+ * Checks both DOM <img> elements AND Fabric canvas image objects to ensure
10695
+ * all async assets are fully painted before capture.
10675
10696
  */
10676
10697
  waitForCanvasImages(container, maxWaitMs = 15e3, pollMs = 200) {
10677
10698
  return new Promise((resolve) => {
@@ -10682,6 +10703,29 @@ class PixldocsRenderer {
10682
10703
  images.forEach((img) => {
10683
10704
  if (!img.complete) allLoaded = false;
10684
10705
  });
10706
+ if (allLoaded) {
10707
+ const canvasEl = container.querySelector("canvas.lower-canvas, canvas");
10708
+ const fabricCanvas = canvasEl && canvasEl.__fabric;
10709
+ if (fabricCanvas && typeof fabricCanvas.getObjects === "function") {
10710
+ for (const obj of fabricCanvas.getObjects()) {
10711
+ const el = obj._element || obj._originalElement;
10712
+ if (el instanceof HTMLImageElement && !el.complete) {
10713
+ allLoaded = false;
10714
+ break;
10715
+ }
10716
+ if (obj._objects) {
10717
+ for (const child of obj._objects) {
10718
+ const childEl = child._element || child._originalElement;
10719
+ if (childEl instanceof HTMLImageElement && !childEl.complete) {
10720
+ allLoaded = false;
10721
+ break;
10722
+ }
10723
+ }
10724
+ if (!allLoaded) break;
10725
+ }
10726
+ }
10727
+ }
10728
+ }
10685
10729
  if (allLoaded || Date.now() - start > maxWaitMs) {
10686
10730
  requestAnimationFrame(() => setTimeout(resolve, 400));
10687
10731
  return;