@pixldocs/canvas-renderer 0.3.18 → 0.3.19

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
@@ -43,6 +43,15 @@ export declare function collectFontDescriptorsFromConfig(config: TemplateConfig)
43
43
  */
44
44
  export declare function collectFontsFromConfig(config: TemplateConfig): Set<string>;
45
45
 
46
+ /**
47
+ * Collect all image asset URLs from a TemplateConfig that the Fabric canvas
48
+ * will request during rendering. This is the canonical list — the same logic
49
+ * that `getExpectedImageCount` uses, but returning URLs instead of a count.
50
+ *
51
+ * Walks **all pages** and recurses into children / groups.
52
+ */
53
+ export declare function collectImageUrls(config: TemplateConfig): string[];
54
+
46
55
  export declare interface DynamicField {
47
56
  id: string;
48
57
  label: string;
@@ -316,4 +325,53 @@ export declare interface ThemeVariables {
316
325
  [variableName: string]: string;
317
326
  }
318
327
 
328
+ export declare interface WarmOptions {
329
+ /** AbortSignal to cancel in-flight prefetches (e.g. on route change). */
330
+ signal?: AbortSignal;
331
+ /** Image proxy base URL. Falls back to the value set via `setPackageApiUrl`. */
332
+ imageProxyUrl?: string;
333
+ }
334
+
335
+ /**
336
+ * Warm fonts **and** image assets for a fully-resolved `TemplateConfig`.
337
+ *
338
+ * Call this on idle / in the background before rendering so that `render()`
339
+ * resolves from browser caches instead of hitting the network.
340
+ *
341
+ * **Idempotent** — repeated calls for the same config are near-instant
342
+ * (fonts deduplicate internally; `fetch` hits HTTP cache).
343
+ *
344
+ * Does **not** mount a canvas or require React.
345
+ *
346
+ * @example
347
+ * ```ts
348
+ * import { warmResolvedTemplateForPreview } from '@pixldocs/canvas-renderer';
349
+ *
350
+ * const controller = new AbortController();
351
+ * await warmResolvedTemplateForPreview(resolvedConfig, { signal: controller.signal });
352
+ * // Later: renderer.render(resolvedConfig) — fonts & images are cached
353
+ * ```
354
+ */
355
+ export declare function warmResolvedTemplateForPreview(config: TemplateConfig, options?: WarmOptions): Promise<void>;
356
+
357
+ /**
358
+ * Convenience: resolve a template from the database **and** warm its assets
359
+ * in a single call. Combines `resolveFromForm` + `warmResolvedTemplateForPreview`.
360
+ *
361
+ * @example
362
+ * ```ts
363
+ * import { warmTemplateFromForm } from '@pixldocs/canvas-renderer';
364
+ *
365
+ * await warmTemplateFromForm({
366
+ * templateId: 'dc3fbb17-...',
367
+ * formSchemaId: 'b04cd362-...',
368
+ * sectionState: { ... },
369
+ * supabaseUrl: '...',
370
+ * supabaseAnonKey: '...',
371
+ * signal: controller.signal,
372
+ * });
373
+ * ```
374
+ */
375
+ export declare function warmTemplateFromForm(options: ResolveFromFormOptions & WarmOptions): Promise<void>;
376
+
319
377
  export { }
package/dist/index.js CHANGED
@@ -10923,16 +10923,95 @@ class PixldocsRenderer {
10923
10923
  });
10924
10924
  }
10925
10925
  }
10926
+ function collectImageUrls(config) {
10927
+ const urls = [];
10928
+ const walk = (nodes) => {
10929
+ for (const node of nodes) {
10930
+ if (!node || node.visible === false) continue;
10931
+ const src = typeof node.src === "string" ? node.src.trim() : "";
10932
+ const imageUrl = typeof node.imageUrl === "string" ? node.imageUrl.trim() : "";
10933
+ if (node.type === "image") {
10934
+ const url = src || imageUrl;
10935
+ if (url) urls.push(url);
10936
+ }
10937
+ if (Array.isArray(node.children) && node.children.length > 0) {
10938
+ walk(node.children);
10939
+ }
10940
+ }
10941
+ };
10942
+ for (const page of config.pages || []) {
10943
+ walk(page.children || []);
10944
+ }
10945
+ return urls;
10946
+ }
10947
+ function normalizeAssetUrl(rawUrl, imageProxyUrl) {
10948
+ if (!rawUrl) return null;
10949
+ if (rawUrl.startsWith("data:") || rawUrl.startsWith("blob:")) return null;
10950
+ try {
10951
+ const h = new URL(rawUrl).hostname.toLowerCase();
10952
+ if (h === "localhost" || h === "127.0.0.1" || h === "0.0.0.0" || h.endsWith(".local") || /^(10\.|192\.168\.|169\.254\.)/.test(h)) {
10953
+ return null;
10954
+ }
10955
+ } catch {
10956
+ return null;
10957
+ }
10958
+ const supabaseUrl = typeof globalThis.__VITE_SUPABASE_URL === "string" ? globalThis.__VITE_SUPABASE_URL : "";
10959
+ if (supabaseUrl && rawUrl.includes(supabaseUrl)) {
10960
+ const signedMatch = rawUrl.match(/\/storage\/v1\/object\/sign\/([^?]+)/);
10961
+ if (signedMatch) return `${supabaseUrl}/storage/v1/object/public/${signedMatch[1]}`;
10962
+ if (rawUrl.includes("/storage/v1/object/public/")) return rawUrl;
10963
+ }
10964
+ const proxyBase = imageProxyUrl ? imageProxyUrl.replace(/\/image-proxy(?:\?.*)?$/, "") : API_URL;
10965
+ if (proxyBase) {
10966
+ return `${proxyBase}/image-proxy?url=${encodeURIComponent(rawUrl)}`;
10967
+ }
10968
+ return rawUrl;
10969
+ }
10970
+ const CONCURRENCY = 6;
10971
+ async function prefetchUrls(urls, signal) {
10972
+ const unique = [...new Set(urls)];
10973
+ if (unique.length === 0) return;
10974
+ let i = 0;
10975
+ const next = async () => {
10976
+ while (i < unique.length) {
10977
+ if (signal == null ? void 0 : signal.aborted) return;
10978
+ const url = unique[i++];
10979
+ try {
10980
+ await fetch(url, { signal, mode: "cors", credentials: "omit" });
10981
+ } catch {
10982
+ }
10983
+ }
10984
+ };
10985
+ const workers = Array.from({ length: Math.min(CONCURRENCY, unique.length) }, () => next());
10986
+ await Promise.all(workers);
10987
+ }
10988
+ async function warmResolvedTemplateForPreview(config, options) {
10989
+ const { signal, imageProxyUrl } = options ?? {};
10990
+ await ensureFontsForResolvedConfig(config);
10991
+ if (signal == null ? void 0 : signal.aborted) return;
10992
+ const rawUrls = collectImageUrls(config);
10993
+ const resolvedUrls = rawUrls.map((u) => normalizeAssetUrl(u, imageProxyUrl)).filter((u) => u !== null);
10994
+ await prefetchUrls(resolvedUrls, signal);
10995
+ }
10996
+ async function warmTemplateFromForm(options) {
10997
+ const { signal, imageProxyUrl, ...resolveOpts } = options;
10998
+ const resolved = await resolveFromForm(resolveOpts);
10999
+ if (signal == null ? void 0 : signal.aborted) return;
11000
+ await warmResolvedTemplateForPreview(resolved.config, { signal, imageProxyUrl });
11001
+ }
10926
11002
  export {
10927
11003
  PixldocsPreview,
10928
11004
  PixldocsRenderer,
10929
11005
  applyThemeToConfig,
10930
11006
  collectFontDescriptorsFromConfig,
10931
11007
  collectFontsFromConfig,
11008
+ collectImageUrls,
10932
11009
  ensureFontsForResolvedConfig,
10933
11010
  loadGoogleFontCSS,
10934
11011
  normalizeFontFamily,
10935
11012
  resolveFromForm,
10936
- resolveTemplateData
11013
+ resolveTemplateData,
11014
+ warmResolvedTemplateForPreview,
11015
+ warmTemplateFromForm
10937
11016
  };
10938
11017
  //# sourceMappingURL=index.js.map