shaders 2.5.114 → 2.5.115

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.
@@ -15,6 +15,16 @@ export interface ShaderDefinition {
15
15
  colorSpace?: 'p3-linear' | 'srgb';
16
16
  devicePreview?: string;
17
17
  }
18
+ export interface KeyPropTarget {
19
+ component_id: string;
20
+ prop: string;
21
+ }
22
+ export interface KeyProp {
23
+ label: string;
24
+ type: 'color' | 'range' | 'logo' | 'image';
25
+ category?: string;
26
+ targets: KeyPropTarget[];
27
+ }
18
28
  export interface PreviewProps {
19
29
  shader?: string;
20
30
  presetId?: string;
@@ -24,6 +34,25 @@ export interface PreviewProps {
24
34
  watermarkLink?: string;
25
35
  style?: CSSProperties | string;
26
36
  className?: string;
37
+ /**
38
+ * Map of camelCase key_prop identifier → user value, layered on top of the
39
+ * preset's authored defaults. Identifiers are derived from `key_prop.label`
40
+ * via the same slugify rules the Pro Framer exporter uses, so identifiers
41
+ * generated by codegen and identifiers expected here always match.
42
+ *
43
+ * Only applies on the `presetId` path — `shader` token previews don't carry
44
+ * key_props and silently ignore this prop.
45
+ */
46
+ configuration?: Record<string, unknown>;
47
+ /**
48
+ * Lock the preview to a specific snapshot of the preset, addressed by
49
+ * content hash. Customized snippets pin a version so subsequent upstream
50
+ * edits (label renames, key_prop regenerations) don't silently break the
51
+ * configuration. Server falls back to latest with the
52
+ * `X-Shaders-Version-Fallback` header when the requested snapshot is
53
+ * missing. Only meaningful on the `presetId` path.
54
+ */
55
+ version?: string;
27
56
  [key: string]: any;
28
57
  }
29
58
  export declare const Preview: FC<PreviewProps>;
@@ -1 +1 @@
1
- {"version":3,"file":"Preview.d.ts","sourceRoot":"","sources":["../../src/engine/Preview.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA4E,KAAK,aAAa,EAAE,KAAK,EAAE,EAA4B,MAAM,OAAO,CAAA;AAKvJ,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC3B,QAAQ,CAAC,EAAE,eAAe,EAAE,CAAA;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,eAAe,EAAE,CAAA;IAC7B,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,UAAU,CAAC,EAAE,WAAW,GAAG,MAAM,CAAA;IACjC,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAqKD,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,KAAK,CAAC,EAAE,aAAa,GAAG,MAAM,CAAA;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CACnB;AA6BD,eAAO,MAAM,OAAO,EAAE,EAAE,CAAC,YAAY,CA+HpC,CAAA;AAED,eAAe,OAAO,CAAA"}
1
+ {"version":3,"file":"Preview.d.ts","sourceRoot":"","sources":["../../src/engine/Preview.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA4E,KAAK,aAAa,EAAE,KAAK,EAAE,EAA4B,MAAM,OAAO,CAAA;AAKvJ,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC3B,QAAQ,CAAC,EAAE,eAAe,EAAE,CAAA;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,eAAe,EAAE,CAAA;IAC7B,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,UAAU,CAAC,EAAE,WAAW,GAAG,MAAM,CAAA;IACjC,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAA;IACpB,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,CAAA;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,aAAa,EAAE,CAAA;CACzB;AAgPD,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,KAAK,CAAC,EAAE,aAAa,GAAG,MAAM,CAAA;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACvC;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CACnB;AA6BD,eAAO,MAAM,OAAO,EAAE,EAAE,CAAC,YAAY,CAwIpC,CAAA;AAED,eAAe,OAAO,CAAA"}
@@ -8,6 +8,24 @@ export interface PreviewProps {
8
8
  class?: string;
9
9
  watermarkText?: string;
10
10
  watermarkLink?: string;
11
+ /**
12
+ * Map of camelCase key_prop identifier → user value, layered on top of the
13
+ * preset's authored defaults. Identifiers are derived from `key_prop.label`
14
+ * via the same slugify rules the Pro Framer exporter uses, so identifiers
15
+ * generated by codegen and identifiers expected here always match.
16
+ *
17
+ * Only applies on the `presetId` path — `shader` token previews don't carry
18
+ * key_props and silently ignore this prop.
19
+ */
20
+ configuration?: Record<string, unknown>;
21
+ /**
22
+ * Lock the preview to a specific snapshot of the preset, addressed by
23
+ * content hash. Customized snippets pin a version so subsequent upstream
24
+ * edits don't silently break the configuration. Server falls back to
25
+ * latest with the `X-Shaders-Version-Fallback` header when the requested
26
+ * snapshot is missing. Only meaningful on the `presetId` path.
27
+ */
28
+ version?: string;
11
29
  }
12
30
  export default function Preview(props: PreviewProps): JSX.Element;
13
31
  //# sourceMappingURL=Preview.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Preview.d.ts","sourceRoot":"","sources":["../../src/engine/Preview.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA+C,KAAK,GAAG,EAAoC,MAAM,UAAU,CAAA;AAsUlH,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,KAAK,CAAC,EAAE,GAAG,CAAC,aAAa,GAAG,MAAM,CAAA;IAClC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAID,MAAM,CAAC,OAAO,UAAU,OAAO,CAAC,KAAK,EAAE,YAAY,eAmHlD"}
1
+ {"version":3,"file":"Preview.d.ts","sourceRoot":"","sources":["../../src/engine/Preview.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA+C,KAAK,GAAG,EAAoC,MAAM,UAAU,CAAA;AA6ZlH,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,KAAK,CAAC,EAAE,GAAG,CAAC,aAAa,GAAG,MAAM,CAAA;IAClC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACvC;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAID,MAAM,CAAC,OAAO,UAAU,OAAO,CAAC,KAAK,EAAE,YAAY,eA6HlD"}
@@ -243,6 +243,64 @@ function decodePreviewDefinition(encoded, key) {
243
243
  throw new Error("Failed to decode shader definition: invalid data or incorrect key");
244
244
  }
245
245
  }
246
+ const RESERVED_IDENTIFIERS = /* @__PURE__ */ new Set(["break", "case", "catch", "class", "const", "continue", "default", "delete", "do", "else", "export", "extends", "finally", "for", "function", "if", "import", "in", "instanceof", "new", "return", "super", "switch", "this", "throw", "try", "typeof", "var", "void", "while", "with", "yield"]);
247
+ function slugifyIdentifier(label, fallback) {
248
+ const cleaned = label.replace(/[^A-Za-z0-9]+/g, " ").trim();
249
+ if (!cleaned) return fallback;
250
+ const parts = cleaned.split(/\s+/);
251
+ const camel = parts.map((part, i) => i === 0 ? part.charAt(0).toLowerCase() + part.slice(1) : part.charAt(0).toUpperCase() + part.slice(1)).join("");
252
+ const safe = /^[A-Za-z_$]/.test(camel) ? camel : `_${camel}`;
253
+ return RESERVED_IDENTIFIERS.has(safe) ? `_${safe}` : safe;
254
+ }
255
+ function buildKeyPropIdentifierMap(keyProps) {
256
+ const used = /* @__PURE__ */ new Set(["style"]);
257
+ const map = /* @__PURE__ */ new Map();
258
+ if (!(keyProps == null ? void 0 : keyProps.length)) return map;
259
+ keyProps.forEach((kp, i) => {
260
+ var _a;
261
+ if (!((_a = kp.targets) == null ? void 0 : _a.length)) return;
262
+ const base = slugifyIdentifier(kp.label, `keyProp${i}`);
263
+ let ident = base;
264
+ let suffix = 1;
265
+ while (used.has(ident)) {
266
+ suffix++;
267
+ ident = `${base}${suffix}`;
268
+ }
269
+ used.add(ident);
270
+ map.set(ident, kp);
271
+ });
272
+ return map;
273
+ }
274
+ function findInTree(id, list) {
275
+ var _a;
276
+ for (const c of list) {
277
+ if (c.id === id) return c;
278
+ if ((_a = c.children) == null ? void 0 : _a.length) {
279
+ const hit = findInTree(id, c.children);
280
+ if (hit) return hit;
281
+ }
282
+ }
283
+ return null;
284
+ }
285
+ function applyConfiguration(definition, configuration, keyProps) {
286
+ if (!configuration || !(keyProps == null ? void 0 : keyProps.length)) return definition;
287
+ const entries = Object.entries(configuration);
288
+ if (entries.length === 0) return definition;
289
+ const identMap = buildKeyPropIdentifierMap(keyProps);
290
+ const cloned = JSON.parse(JSON.stringify(definition.components));
291
+ for (const [ident, value] of entries) {
292
+ const kp = identMap.get(ident);
293
+ if (!kp) continue;
294
+ for (const t of kp.targets ?? []) {
295
+ const comp = findInTree(t.component_id, cloned);
296
+ if ((comp == null ? void 0 : comp.props) && t.prop in comp.props) comp.props[t.prop] = value;
297
+ }
298
+ }
299
+ return {
300
+ ...definition,
301
+ components: cloned
302
+ };
303
+ }
246
304
  function RenderComponent(props) {
247
305
  const Component = componentMap[props.config.type];
248
306
  if (!Component) return null;
@@ -297,9 +355,11 @@ function Preview(props) {
297
355
  console.warn("[Shaders] The Preview component requires a Shaders license for production use. Visit https://shaders.com for more information.");
298
356
  }
299
357
  const [fetchedDefinition, setFetchedDefinition] = createSignal(null);
358
+ const [fetchedKeyProps, setFetchedKeyProps] = createSignal(null);
300
359
  function fetchAndDecode(url) {
301
360
  const key = props.obfuscationKey || DEFAULT_KEY;
302
361
  setFetchedDefinition(null);
362
+ setFetchedKeyProps(null);
303
363
  fetch(url).then((res) => {
304
364
  if (!res.ok) throw new Error(`Failed to fetch: ${res.status}`);
305
365
  return res.json();
@@ -307,6 +367,7 @@ function Preview(props) {
307
367
  const item = data.preset || data.shader;
308
368
  if ((item == null ? void 0 : item.definition) && typeof item.definition === "string") {
309
369
  setFetchedDefinition(decodePreviewDefinition(item.definition, key));
370
+ setFetchedKeyProps(Array.isArray(item.key_props) ? item.key_props : null);
310
371
  }
311
372
  }).catch((err) => {
312
373
  console.error("[Shaders Preview] Failed to fetch preview:", err);
@@ -319,19 +380,25 @@ function Preview(props) {
319
380
  fetchAndDecode(`${baseUrl}/api/preview/shader/${token}`);
320
381
  } else if (!props.presetId) {
321
382
  setFetchedDefinition(null);
383
+ setFetchedKeyProps(null);
322
384
  }
323
385
  });
324
386
  createEffect(() => {
325
387
  const id = props.presetId;
388
+ const version = props.version;
326
389
  const baseUrl = props.apiBaseUrl || "https://shaders.com";
327
390
  if (id) {
328
- fetchAndDecode(`${baseUrl}/api/preview/preset/${id}`);
391
+ const versionParam = version ? `?version=${encodeURIComponent(version)}` : "";
392
+ fetchAndDecode(`${baseUrl}/api/preview/preset/${id}${versionParam}`);
329
393
  } else if (!props.shader) {
330
394
  setFetchedDefinition(null);
395
+ setFetchedKeyProps(null);
331
396
  }
332
397
  });
333
398
  const definition = createMemo(() => {
334
- return fetchedDefinition() ?? null;
399
+ const def = fetchedDefinition();
400
+ if (!def) return null;
401
+ return applyConfiguration(def, props.configuration, fetchedKeyProps());
335
402
  });
336
403
  const watermarkText = () => props.watermarkText || "Unlock your Shaders Pro license";
337
404
  const watermarkLink = () => props.watermarkLink || "https://shaders.com/dashboard?pricing=true";
@@ -67,7 +67,7 @@ function Shader(allProps) {
67
67
  const checkRendering = () => {
68
68
  const stats = rendererInstance.getPerformanceStats();
69
69
  if (stats.fps > 0) {
70
- const version = "2.5.114";
70
+ const version = "2.5.115";
71
71
  telemetryCollector = startTelemetry(rendererInstance, version, props.disableTelemetry, props.isPreview);
72
72
  if (telemetryCollector) {
73
73
  telemetryCollector.start();
@@ -6,6 +6,24 @@ interface Props {
6
6
  watermarkText?: string;
7
7
  watermarkLink?: string;
8
8
  style?: string;
9
+ /**
10
+ * Map of camelCase key_prop identifier → user value, layered on top of the
11
+ * preset's authored defaults. Identifiers are derived from `key_prop.label`
12
+ * via the same slugify rules the Pro Framer exporter uses, so identifiers
13
+ * generated by codegen and identifiers expected here always match.
14
+ *
15
+ * Only applies on the `presetId` path — `shader` token previews don't carry
16
+ * key_props and silently ignore this prop.
17
+ */
18
+ configuration?: Record<string, unknown>;
19
+ /**
20
+ * Lock the preview to a specific snapshot of the preset, addressed by
21
+ * content hash. Customized snippets pin a version so subsequent upstream
22
+ * edits don't silently break the configuration. Server falls back to
23
+ * latest with the `X-Shaders-Version-Fallback` header when the requested
24
+ * snapshot is missing. Only meaningful on the `presetId` path.
25
+ */
26
+ version?: string;
9
27
  [key: string]: any;
10
28
  }
11
29
  declare const Preview: import("svelte").Component<Props, {}, "">;
@@ -21540,7 +21540,7 @@ function Shader($$anchor, $$props) {
21540
21540
  const checkRendering = () => {
21541
21541
  if (!rendererInstance) return;
21542
21542
  if (rendererInstance.getPerformanceStats().fps > 0) {
21543
- telemetryCollector = startTelemetry(rendererInstance, "2.5.114", disableTelemetry(), isPreview());
21543
+ telemetryCollector = startTelemetry(rendererInstance, "2.5.115", disableTelemetry(), isPreview());
21544
21544
  if (telemetryCollector) telemetryCollector.start();
21545
21545
  telemetryStartTimeout = null;
21546
21546
  } else telemetryStartTimeout = window.setTimeout(checkRendering, 500);
@@ -21855,14 +21855,111 @@ function Preview($$anchor, $$props) {
21855
21855
  return res.json();
21856
21856
  }).then((data) => {
21857
21857
  const item = data.preset || data.shader;
21858
- if (item?.definition && typeof item.definition === "string") return decodePreviewDefinition(item.definition, key);
21859
- return null;
21858
+ if (item?.definition && typeof item.definition === "string") return {
21859
+ definition: decodePreviewDefinition(item.definition, key),
21860
+ keyProps: Array.isArray(item.key_props) ? item.key_props : null
21861
+ };
21862
+ return {
21863
+ definition: null,
21864
+ keyProps: null
21865
+ };
21860
21866
  }).catch((err) => {
21861
21867
  console.error("[Shaders Preview] Failed to fetch preview:", err);
21862
- return null;
21868
+ return {
21869
+ definition: null,
21870
+ keyProps: null
21871
+ };
21872
+ });
21873
+ }
21874
+ const RESERVED_IDENTIFIERS = new Set([
21875
+ "break",
21876
+ "case",
21877
+ "catch",
21878
+ "class",
21879
+ "const",
21880
+ "continue",
21881
+ "default",
21882
+ "delete",
21883
+ "do",
21884
+ "else",
21885
+ "export",
21886
+ "extends",
21887
+ "finally",
21888
+ "for",
21889
+ "function",
21890
+ "if",
21891
+ "import",
21892
+ "in",
21893
+ "instanceof",
21894
+ "new",
21895
+ "return",
21896
+ "super",
21897
+ "switch",
21898
+ "this",
21899
+ "throw",
21900
+ "try",
21901
+ "typeof",
21902
+ "var",
21903
+ "void",
21904
+ "while",
21905
+ "with",
21906
+ "yield"
21907
+ ]);
21908
+ function slugifyIdentifier(label, fallback) {
21909
+ const cleaned = label.replace(/[^A-Za-z0-9]+/g, " ").trim();
21910
+ if (!cleaned) return fallback;
21911
+ const camel = cleaned.split(/\s+/).map((part, i) => i === 0 ? part.charAt(0).toLowerCase() + part.slice(1) : part.charAt(0).toUpperCase() + part.slice(1)).join("");
21912
+ const safe = /^[A-Za-z_$]/.test(camel) ? camel : `_${camel}`;
21913
+ return RESERVED_IDENTIFIERS.has(safe) ? `_${safe}` : safe;
21914
+ }
21915
+ function buildKeyPropIdentifierMap(keyProps) {
21916
+ const used = new Set(["style"]);
21917
+ const map = /* @__PURE__ */ new Map();
21918
+ if (!keyProps?.length) return map;
21919
+ keyProps.forEach((kp, i) => {
21920
+ if (!kp.targets?.length) return;
21921
+ const base = slugifyIdentifier(kp.label, `keyProp${i}`);
21922
+ let ident = base;
21923
+ let suffix = 1;
21924
+ while (used.has(ident)) {
21925
+ suffix++;
21926
+ ident = `${base}${suffix}`;
21927
+ }
21928
+ used.add(ident);
21929
+ map.set(ident, kp);
21863
21930
  });
21931
+ return map;
21932
+ }
21933
+ function findInTree(id, list) {
21934
+ for (const c of list) {
21935
+ if (c.id === id) return c;
21936
+ if (c.children?.length) {
21937
+ const hit = findInTree(id, c.children);
21938
+ if (hit) return hit;
21939
+ }
21940
+ }
21941
+ return null;
21942
+ }
21943
+ function applyConfiguration(definition, configuration$1, keyProps) {
21944
+ if (!configuration$1 || !keyProps?.length) return definition;
21945
+ const entries = Object.entries(configuration$1);
21946
+ if (entries.length === 0) return definition;
21947
+ const identMap = buildKeyPropIdentifierMap(keyProps);
21948
+ const cloned = JSON.parse(JSON.stringify(definition.components));
21949
+ for (const [ident, value] of entries) {
21950
+ const kp = identMap.get(ident);
21951
+ if (!kp) continue;
21952
+ for (const t of kp.targets ?? []) {
21953
+ const comp = findInTree(t.component_id, cloned);
21954
+ if (comp?.props && t.prop in comp.props) comp.props[t.prop] = value;
21955
+ }
21956
+ }
21957
+ return {
21958
+ ...definition,
21959
+ components: cloned
21960
+ };
21864
21961
  }
21865
- const shader = $.prop($$props, "shader", 3, void 0), presetId = $.prop($$props, "presetId", 3, void 0), apiBaseUrl = $.prop($$props, "apiBaseUrl", 3, "https://shaders.com"), obfuscationKey = $.prop($$props, "obfuscationKey", 3, "shaders-preview-key"), watermarkText = $.prop($$props, "watermarkText", 3, "Unlock your Shaders Pro license"), watermarkLink = $.prop($$props, "watermarkLink", 3, "https://shaders.com/dashboard?pricing=true"), style = $.prop($$props, "style", 3, void 0), restProps = $.rest_props($$props, [
21962
+ const shader = $.prop($$props, "shader", 3, void 0), presetId = $.prop($$props, "presetId", 3, void 0), apiBaseUrl = $.prop($$props, "apiBaseUrl", 3, "https://shaders.com"), obfuscationKey = $.prop($$props, "obfuscationKey", 3, "shaders-preview-key"), watermarkText = $.prop($$props, "watermarkText", 3, "Unlock your Shaders Pro license"), watermarkLink = $.prop($$props, "watermarkLink", 3, "https://shaders.com/dashboard?pricing=true"), style = $.prop($$props, "style", 3, void 0), configuration = $.prop($$props, "configuration", 3, void 0), version = $.prop($$props, "version", 3, void 0), restProps = $.rest_props($$props, [
21866
21963
  "$$slots",
21867
21964
  "$$events",
21868
21965
  "$$legacy",
@@ -21872,26 +21969,40 @@ function Preview($$anchor, $$props) {
21872
21969
  "obfuscationKey",
21873
21970
  "watermarkText",
21874
21971
  "watermarkLink",
21875
- "style"
21972
+ "style",
21973
+ "configuration",
21974
+ "version"
21876
21975
  ]);
21877
21976
  let fetchedDefinition = $.state(null);
21977
+ let fetchedKeyProps = $.state(null);
21878
21978
  let watermarkHovered = $.state(false);
21879
- let resolvedDefinition = $.derived(() => $.get(fetchedDefinition) ?? null);
21979
+ let resolvedDefinition = $.derived(() => $.get(fetchedDefinition) ? applyConfiguration($.get(fetchedDefinition), configuration(), $.get(fetchedKeyProps)) : null);
21880
21980
  $.user_effect(() => {
21881
21981
  if (shader()) {
21882
21982
  $.set(fetchedDefinition, null);
21883
- fetchAndDecode(`${apiBaseUrl()}/api/preview/shader/${shader()}`, obfuscationKey()).then((def) => {
21884
- $.set(fetchedDefinition, def, true);
21983
+ $.set(fetchedKeyProps, null);
21984
+ fetchAndDecode(`${apiBaseUrl()}/api/preview/shader/${shader()}`, obfuscationKey()).then((result) => {
21985
+ $.set(fetchedDefinition, result.definition, true);
21986
+ $.set(fetchedKeyProps, result.keyProps, true);
21885
21987
  });
21886
- } else if (!presetId()) $.set(fetchedDefinition, null);
21988
+ } else if (!presetId()) {
21989
+ $.set(fetchedDefinition, null);
21990
+ $.set(fetchedKeyProps, null);
21991
+ }
21887
21992
  });
21888
21993
  $.user_effect(() => {
21889
21994
  if (presetId()) {
21890
21995
  $.set(fetchedDefinition, null);
21891
- fetchAndDecode(`${apiBaseUrl()}/api/preview/preset/${presetId()}`, obfuscationKey()).then((def) => {
21892
- $.set(fetchedDefinition, def, true);
21996
+ $.set(fetchedKeyProps, null);
21997
+ const versionParam = version() ? `?version=${encodeURIComponent(version())}` : "";
21998
+ fetchAndDecode(`${apiBaseUrl()}/api/preview/preset/${presetId()}${versionParam}`, obfuscationKey()).then((result) => {
21999
+ $.set(fetchedDefinition, result.definition, true);
22000
+ $.set(fetchedKeyProps, result.keyProps, true);
21893
22001
  });
21894
- } else if (!shader()) $.set(fetchedDefinition, null);
22002
+ } else if (!shader()) {
22003
+ $.set(fetchedDefinition, null);
22004
+ $.set(fetchedKeyProps, null);
22005
+ }
21895
22006
  });
21896
22007
  onMount(() => {
21897
22008
  console.warn("[Shaders] Preview component is intended for use with a Shaders license. Visit https://shaders.com for more information.");
@@ -247,6 +247,18 @@ interface ShaderDefinition {
247
247
  devicePreview?: string
248
248
  }
249
249
 
250
+ interface KeyPropTarget {
251
+ component_id: string
252
+ prop: string
253
+ }
254
+
255
+ interface KeyProp {
256
+ label: string
257
+ type: 'color' | 'range' | 'logo' | 'image'
258
+ category?: string
259
+ targets: KeyPropTarget[]
260
+ }
261
+
250
262
  // --- Preview Decode Logic ---
251
263
  // No name mapping — preview definitions contain readable JSON.
252
264
 
@@ -274,8 +286,13 @@ function decodePreviewDefinition(encoded: string, key: string): ShaderDefinition
274
286
  }
275
287
  }
276
288
 
277
- // Fetch from API and decode response
278
- function fetchAndDecode(url: string, key: string): Promise<ShaderDefinition | null> {
289
+ // Fetch from API and decode response, returning both the definition and key_props
290
+ interface FetchResult {
291
+ definition: ShaderDefinition | null
292
+ keyProps: KeyProp[] | null
293
+ }
294
+
295
+ function fetchAndDecode(url: string, key: string): Promise<FetchResult> {
279
296
  return fetch(url)
280
297
  .then(res => {
281
298
  if (!res.ok) throw new Error(`Failed to fetch: ${res.status}`)
@@ -284,16 +301,94 @@ function fetchAndDecode(url: string, key: string): Promise<ShaderDefinition | nu
284
301
  .then(data => {
285
302
  const item = data.preset || data.shader
286
303
  if (item?.definition && typeof item.definition === 'string') {
287
- return decodePreviewDefinition(item.definition, key)
304
+ return {
305
+ definition: decodePreviewDefinition(item.definition, key),
306
+ keyProps: Array.isArray(item.key_props) ? item.key_props : null,
307
+ }
288
308
  }
289
- return null
309
+ return { definition: null, keyProps: null }
290
310
  })
291
311
  .catch(err => {
292
312
  console.error('[Shaders Preview] Failed to fetch preview:', err)
293
- return null
313
+ return { definition: null, keyProps: null }
294
314
  })
295
315
  }
296
316
 
317
+ // --- Configuration apply ---
318
+ // Source of truth: dotcom/utils/keyPropsIdentifier.ts. Inlined per framework
319
+ // because the build pipeline copies framework dist into the published npm
320
+ // package and won't carry a cross-package import.
321
+
322
+ const RESERVED_IDENTIFIERS = new Set([
323
+ 'break', 'case', 'catch', 'class', 'const', 'continue', 'default',
324
+ 'delete', 'do', 'else', 'export', 'extends', 'finally', 'for',
325
+ 'function', 'if', 'import', 'in', 'instanceof', 'new', 'return',
326
+ 'super', 'switch', 'this', 'throw', 'try', 'typeof', 'var', 'void',
327
+ 'while', 'with', 'yield',
328
+ ])
329
+
330
+ function slugifyIdentifier(label: string, fallback: string): string {
331
+ const cleaned = label.replace(/[^A-Za-z0-9]+/g, ' ').trim()
332
+ if (!cleaned) return fallback
333
+ const parts = cleaned.split(/\s+/)
334
+ const camel = parts
335
+ .map((part, i) => i === 0 ? part.charAt(0).toLowerCase() + part.slice(1) : part.charAt(0).toUpperCase() + part.slice(1))
336
+ .join('')
337
+ const safe = /^[A-Za-z_$]/.test(camel) ? camel : `_${camel}`
338
+ return RESERVED_IDENTIFIERS.has(safe) ? `_${safe}` : safe
339
+ }
340
+
341
+ function buildKeyPropIdentifierMap(keyProps: KeyProp[] | null | undefined): Map<string, KeyProp> {
342
+ const used = new Set<string>(['style'])
343
+ const map = new Map<string, KeyProp>()
344
+ if (!keyProps?.length) return map
345
+ keyProps.forEach((kp, i) => {
346
+ if (!kp.targets?.length) return
347
+ const base = slugifyIdentifier(kp.label, `keyProp${i}`)
348
+ let ident = base
349
+ let suffix = 1
350
+ while (used.has(ident)) {
351
+ suffix++
352
+ ident = `${base}${suffix}`
353
+ }
354
+ used.add(ident)
355
+ map.set(ident, kp)
356
+ })
357
+ return map
358
+ }
359
+
360
+ function findInTree(id: string, list: ShaderComponent[]): ShaderComponent | null {
361
+ for (const c of list) {
362
+ if (c.id === id) return c
363
+ if (c.children?.length) {
364
+ const hit = findInTree(id, c.children)
365
+ if (hit) return hit
366
+ }
367
+ }
368
+ return null
369
+ }
370
+
371
+ function applyConfiguration(
372
+ definition: ShaderDefinition,
373
+ configuration: Record<string, unknown> | null | undefined,
374
+ keyProps: KeyProp[] | null | undefined
375
+ ): ShaderDefinition {
376
+ if (!configuration || !keyProps?.length) return definition
377
+ const entries = Object.entries(configuration)
378
+ if (entries.length === 0) return definition
379
+ const identMap = buildKeyPropIdentifierMap(keyProps)
380
+ const cloned = JSON.parse(JSON.stringify(definition.components)) as ShaderComponent[]
381
+ for (const [ident, value] of entries) {
382
+ const kp = identMap.get(ident)
383
+ if (!kp) continue
384
+ for (const t of kp.targets ?? []) {
385
+ const comp = findInTree(t.component_id, cloned)
386
+ if (comp?.props && t.prop in comp.props) comp.props[t.prop] = value
387
+ }
388
+ }
389
+ return { ...definition, components: cloned }
390
+ }
391
+
297
392
  // --- Props ---
298
393
 
299
394
  interface Props {
@@ -304,6 +399,24 @@ interface Props {
304
399
  watermarkText?: string
305
400
  watermarkLink?: string
306
401
  style?: string
402
+ /**
403
+ * Map of camelCase key_prop identifier → user value, layered on top of the
404
+ * preset's authored defaults. Identifiers are derived from `key_prop.label`
405
+ * via the same slugify rules the Pro Framer exporter uses, so identifiers
406
+ * generated by codegen and identifiers expected here always match.
407
+ *
408
+ * Only applies on the `presetId` path — `shader` token previews don't carry
409
+ * key_props and silently ignore this prop.
410
+ */
411
+ configuration?: Record<string, unknown>
412
+ /**
413
+ * Lock the preview to a specific snapshot of the preset, addressed by
414
+ * content hash. Customized snippets pin a version so subsequent upstream
415
+ * edits don't silently break the configuration. Server falls back to
416
+ * latest with the `X-Shaders-Version-Fallback` header when the requested
417
+ * snapshot is missing. Only meaningful on the `presetId` path.
418
+ */
419
+ version?: string
307
420
  [key: string]: any
308
421
  }
309
422
 
@@ -315,35 +428,46 @@ const {
315
428
  watermarkText = 'Unlock your Shaders Pro license',
316
429
  watermarkLink = 'https://shaders.com/dashboard?pricing=true',
317
430
  style = undefined,
431
+ configuration = undefined,
432
+ version = undefined,
318
433
  ...restProps
319
434
  }: Props = $props()
320
435
 
321
436
  // --- Decode & Resolve ---
322
437
 
323
438
  let fetchedDefinition: ShaderDefinition | null = $state(null)
439
+ let fetchedKeyProps: KeyProp[] | null = $state(null)
324
440
  let watermarkHovered = $state(false)
325
441
 
326
- let resolvedDefinition: ShaderDefinition | null = $derived(fetchedDefinition ?? null)
442
+ let resolvedDefinition: ShaderDefinition | null = $derived(
443
+ fetchedDefinition ? applyConfiguration(fetchedDefinition, configuration, fetchedKeyProps) : null
444
+ )
327
445
 
328
446
  // Fetch shader by token
329
447
  $effect(() => {
330
448
  if (shader) {
331
449
  fetchedDefinition = null
450
+ fetchedKeyProps = null
332
451
  fetchAndDecode(`${apiBaseUrl}/api/preview/shader/${shader}`, obfuscationKey)
333
- .then(def => { fetchedDefinition = def })
452
+ .then(result => { fetchedDefinition = result.definition; fetchedKeyProps = result.keyProps })
334
453
  } else if (!presetId) {
335
454
  fetchedDefinition = null
455
+ fetchedKeyProps = null
336
456
  }
337
457
  })
338
458
 
339
- // Fetch preset by ID
459
+ // Fetch preset by ID. Version pins the preview to a snapshot; when omitted,
460
+ // the server returns latest.
340
461
  $effect(() => {
341
462
  if (presetId) {
342
463
  fetchedDefinition = null
343
- fetchAndDecode(`${apiBaseUrl}/api/preview/preset/${presetId}`, obfuscationKey)
344
- .then(def => { fetchedDefinition = def })
464
+ fetchedKeyProps = null
465
+ const versionParam = version ? `?version=${encodeURIComponent(version)}` : ''
466
+ fetchAndDecode(`${apiBaseUrl}/api/preview/preset/${presetId}${versionParam}`, obfuscationKey)
467
+ .then(result => { fetchedDefinition = result.definition; fetchedKeyProps = result.keyProps })
345
468
  } else if (!shader) {
346
469
  fetchedDefinition = null
470
+ fetchedKeyProps = null
347
471
  }
348
472
  })
349
473