@snowcone-app/sdk 0.1.10

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.js ADDED
@@ -0,0 +1,4424 @@
1
+ import {
2
+ createDevFetcher
3
+ } from "./chunk-7VO4EL2V.js";
4
+ import {
5
+ RealtimeMockupService
6
+ } from "./chunk-UJFJ7REN.js";
7
+
8
+ // src/validation.ts
9
+ import { z } from "zod";
10
+ var CatalogProductSchema = z.object({
11
+ id: z.string(),
12
+ name: z.string(),
13
+ slug: z.string(),
14
+ tags: z.array(z.string()).optional(),
15
+ price: z.number().int(),
16
+ lowestPrice: z.number().int().optional(),
17
+ highestPrice: z.number().int().optional(),
18
+ mockups: z.array(
19
+ z.object({ id: z.string(), ar: z.number(), gvids: z.array(z.string()) })
20
+ ).optional(),
21
+ options: z.object({
22
+ selected: z.record(z.string(), z.union([z.string(), z.number(), z.null()])).optional(),
23
+ // Accept any attribute object shape; upstream varies between choices-only and typed objects
24
+ attributes: z.record(z.string(), z.any()).optional(),
25
+ combinations: z.array(z.any()).optional(),
26
+ attributesList: z.array(z.string()).optional()
27
+ }).optional(),
28
+ care_guide: z.array(z.string()).optional(),
29
+ description: z.array(z.string()).optional(),
30
+ key_features: z.array(z.string()).optional(),
31
+ days_to_deliver: z.union([z.string(), z.number()]).optional(),
32
+ size_chart_data: z.any().optional(),
33
+ ships_from_country: z.string().optional(),
34
+ variants: z.array(
35
+ z.object({
36
+ gvid: z.string(),
37
+ gvidForMockup: z.string().nullable().optional(),
38
+ placements: z.array(
39
+ z.object({
40
+ label: z.string(),
41
+ width: z.number().nullable(),
42
+ height: z.number().nullable()
43
+ })
44
+ )
45
+ })
46
+ ).optional(),
47
+ placements: z.array(
48
+ z.object({
49
+ label: z.string(),
50
+ type: z.enum(["image", "color"]),
51
+ width: z.number().int(),
52
+ height: z.number().int(),
53
+ defaultScaleMode: z.enum(["fill", "fit"]).optional(),
54
+ fitMarginTop: z.number().int().optional(),
55
+ fitMarginRight: z.number().int().optional(),
56
+ fitMarginBottom: z.number().int().optional(),
57
+ fitMarginLeft: z.number().int().optional(),
58
+ fitAlign: z.string().optional(),
59
+ align: z.string().optional(),
60
+ scale: z.number().optional(),
61
+ offsetX: z.number().optional(),
62
+ offsetY: z.number().optional()
63
+ })
64
+ ).optional(),
65
+ defaultGvid: z.string().optional()
66
+ });
67
+
68
+ // src/mockup/cache.ts
69
+ var SignatureCache = class {
70
+ memoryCache = /* @__PURE__ */ new Map();
71
+ maxMemoryEntries;
72
+ maxLocalStorageEntries;
73
+ localStorageKey = "merchify_signature_cache";
74
+ localStorageAvailable;
75
+ constructor(maxMemoryEntries = 500, maxLocalStorageEntries = 100) {
76
+ this.maxMemoryEntries = maxMemoryEntries;
77
+ this.maxLocalStorageEntries = maxLocalStorageEntries;
78
+ this.localStorageAvailable = this.checkLocalStorageAvailability();
79
+ if (this.localStorageAvailable) {
80
+ this.loadFromLocalStorage();
81
+ }
82
+ }
83
+ checkLocalStorageAvailability() {
84
+ try {
85
+ const test = "__storage_test__";
86
+ localStorage.setItem(test, test);
87
+ localStorage.removeItem(test);
88
+ return true;
89
+ } catch {
90
+ return false;
91
+ }
92
+ }
93
+ loadFromLocalStorage() {
94
+ try {
95
+ const stored = localStorage.getItem(this.localStorageKey);
96
+ if (stored) {
97
+ const parsed = JSON.parse(stored);
98
+ if (Array.isArray(parsed)) {
99
+ parsed.forEach(([key, value]) => {
100
+ if (this.memoryCache.size < this.maxMemoryEntries) {
101
+ this.memoryCache.set(key, value);
102
+ }
103
+ });
104
+ }
105
+ }
106
+ } catch (error) {
107
+ console.warn("Failed to load signature cache from localStorage:", error);
108
+ }
109
+ }
110
+ saveToLocalStorage() {
111
+ if (!this.localStorageAvailable) return;
112
+ try {
113
+ const entries = Array.from(this.memoryCache.entries()).sort((a, b) => b[1].timestamp - a[1].timestamp).slice(0, this.maxLocalStorageEntries);
114
+ localStorage.setItem(this.localStorageKey, JSON.stringify(entries));
115
+ } catch (error) {
116
+ console.warn("Failed to save signature cache to localStorage:", error);
117
+ this.localStorageAvailable = false;
118
+ }
119
+ }
120
+ get(key) {
121
+ const entry = this.memoryCache.get(key);
122
+ if (entry) {
123
+ entry.timestamp = Date.now();
124
+ this.memoryCache.delete(key);
125
+ this.memoryCache.set(key, entry);
126
+ return entry.url;
127
+ }
128
+ return null;
129
+ }
130
+ set(key, url) {
131
+ if (this.memoryCache.size >= this.maxMemoryEntries) {
132
+ const oldestKey = this.findOldestKey();
133
+ if (oldestKey) {
134
+ this.memoryCache.delete(oldestKey);
135
+ }
136
+ }
137
+ this.memoryCache.set(key, {
138
+ url,
139
+ timestamp: Date.now()
140
+ });
141
+ this.saveToLocalStorage();
142
+ }
143
+ findOldestKey() {
144
+ let oldestKey = null;
145
+ let oldestTime = Infinity;
146
+ for (const [key, entry] of this.memoryCache.entries()) {
147
+ if (entry.timestamp < oldestTime) {
148
+ oldestTime = entry.timestamp;
149
+ oldestKey = key;
150
+ }
151
+ }
152
+ return oldestKey;
153
+ }
154
+ clear() {
155
+ this.memoryCache.clear();
156
+ if (this.localStorageAvailable) {
157
+ try {
158
+ localStorage.removeItem(this.localStorageKey);
159
+ } catch (error) {
160
+ console.warn("Failed to clear localStorage cache:", error);
161
+ }
162
+ }
163
+ }
164
+ size() {
165
+ return this.memoryCache.size;
166
+ }
167
+ };
168
+
169
+ // src/mockup/service.ts
170
+ var DEFAULT_IMAGE_URL = typeof process !== "undefined" && process.env?.MERCHIFY_IMAGE_URL || "https://i.snowcone.app";
171
+ var DEFAULT_SIGNER_URL = typeof process !== "undefined" && process.env?.MERCHIFY_SIGNER_URL || "https://s.snowcone.app/sign";
172
+ var RATE_LIMIT_PER_MINUTE = 450;
173
+ var RATE_LIMIT_PER_SECOND = 20;
174
+ var MockupServiceImpl = class {
175
+ config;
176
+ cache;
177
+ rateLimitState;
178
+ fetch;
179
+ constructor(config2, customFetch) {
180
+ this.config = {
181
+ imageUrl: config2.imageUrl || DEFAULT_IMAGE_URL,
182
+ signerUrl: config2.signerUrl || DEFAULT_SIGNER_URL,
183
+ accountId: config2.accountId || typeof process !== "undefined" && process.env?.MERCHIFY_ACCOUNT_ID || ""
184
+ };
185
+ this.cache = new SignatureCache();
186
+ this.fetch = customFetch || fetch.bind(globalThis);
187
+ this.rateLimitState = {
188
+ perMinute: { count: 0, resetTime: Date.now() + 6e4 },
189
+ perSecond: { count: 0, resetTime: Date.now() + 1e3 }
190
+ };
191
+ }
192
+ async getMockupUrl(options) {
193
+ if (!options.design || !options.product) {
194
+ throw new Error("Missing required options: design and product are required");
195
+ }
196
+ if (!this.config.accountId) {
197
+ throw new Error("Account ID is required for mockup generation");
198
+ }
199
+ this.checkRateLimits();
200
+ const relativeUrl = this.buildMockupUrlWithParams(options);
201
+ const cacheKey = `${relativeUrl}:${this.config.accountId}`;
202
+ const cachedUrl = this.cache.get(cacheKey);
203
+ if (cachedUrl) {
204
+ return cachedUrl;
205
+ }
206
+ const signedUrl = await this.getSignedUrl(relativeUrl);
207
+ this.cache.set(cacheKey, signedUrl);
208
+ this.incrementRateLimitCounters();
209
+ return signedUrl;
210
+ }
211
+ buildMockupUrlWithParams(options) {
212
+ const { design, product, effects, ar } = options;
213
+ const designBase64 = typeof window !== "undefined" && window.btoa ? window.btoa(JSON.stringify(design)) : Buffer.from(JSON.stringify(design)).toString("base64");
214
+ const encodedDesign = encodeURIComponent(designBase64);
215
+ let url = `/mockup?productId=${encodeURIComponent(product.productId)}`;
216
+ url += `&mockupId=${encodeURIComponent(product.mockupId)}`;
217
+ url += `&variantId=${encodeURIComponent(product.variantId)}`;
218
+ url += `&design=${encodedDesign}`;
219
+ url += `&width=${product.width}`;
220
+ if (effects?.grain) {
221
+ url += `&grain=${effects.grain}`;
222
+ }
223
+ if (ar && ar !== "16:9") {
224
+ url += `&ar=${encodeURIComponent(ar)}`;
225
+ }
226
+ if (product.optionSelections && Object.keys(product.optionSelections).length > 0) {
227
+ const optionSelectionsBase64 = typeof window !== "undefined" && window.btoa ? window.btoa(JSON.stringify(product.optionSelections)) : Buffer.from(JSON.stringify(product.optionSelections)).toString("base64");
228
+ url += `&optionSelections=${encodeURIComponent(optionSelectionsBase64)}`;
229
+ }
230
+ return url;
231
+ }
232
+ buildMockupUrl(options) {
233
+ const { design, product, effects } = options;
234
+ const pathSegments = ["m"];
235
+ pathSegments.push(product.productId);
236
+ pathSegments.push(product.mockupId);
237
+ pathSegments.push(product.variantId);
238
+ pathSegments.push(String(product.width));
239
+ const designParts = [];
240
+ for (const element of design) {
241
+ const part = this.encodeDesignElement(element);
242
+ designParts.push(part);
243
+ }
244
+ if (designParts.length > 0) {
245
+ pathSegments.push(designParts.join("_"));
246
+ } else {
247
+ pathSegments.push("");
248
+ }
249
+ if (effects?.grain) {
250
+ pathSegments.push(`g${effects.grain}`);
251
+ }
252
+ return "/" + pathSegments.join("/");
253
+ }
254
+ encodeDesignElement(element) {
255
+ const parts = [];
256
+ if (element.type === "image") {
257
+ const encodedUrl = this.encodeImageUrl(element.imageUrl);
258
+ parts.push(encodedUrl);
259
+ parts.push(element.placement);
260
+ parts.push(String(element.width));
261
+ parts.push(String(element.height));
262
+ parts.push(element.alignment);
263
+ if (element.tiles && element.tiles > 1) {
264
+ parts.push(`t${element.tiles}`);
265
+ }
266
+ } else if (element.type === "color") {
267
+ const colorCode = element.hex.replace("#", "");
268
+ parts.push(colorCode);
269
+ parts.push(element.placement);
270
+ parts.push(String(element.width));
271
+ parts.push(String(element.height));
272
+ parts.push(element.alignment);
273
+ }
274
+ return parts.join(",");
275
+ }
276
+ encodeImageUrl(url) {
277
+ if (typeof window !== "undefined" && window.btoa) {
278
+ const base64 = window.btoa(url);
279
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
280
+ } else {
281
+ const base64 = Buffer.from(url).toString("base64");
282
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
283
+ }
284
+ }
285
+ async getSignedUrl(relativeUrl) {
286
+ const urlWithAccountId = relativeUrl + (relativeUrl.includes("?") ? "&" : "?") + `accountId=${encodeURIComponent(this.config.accountId)}`;
287
+ const cacheKey = urlWithAccountId;
288
+ const cachedUrl = this.cache.get(cacheKey);
289
+ if (cachedUrl) {
290
+ return cachedUrl;
291
+ }
292
+ if (this.config.signerUrl?.includes("localhost") || this.config.signerUrl === "mock") {
293
+ const signedUrl = this.config.imageUrl + urlWithAccountId + "&sig=bypass-sig-for-k6-load-test";
294
+ this.cache.set(cacheKey, signedUrl);
295
+ return signedUrl;
296
+ }
297
+ const signerUrl = new URL(this.config.signerUrl);
298
+ signerUrl.searchParams.set("url", urlWithAccountId);
299
+ try {
300
+ const response = await this.fetch(signerUrl.toString(), {
301
+ method: "GET",
302
+ headers: {
303
+ "Accept": "application/json"
304
+ }
305
+ });
306
+ if (!response.ok) {
307
+ throw new Error(`Failed to sign URL: ${response.statusText}`);
308
+ }
309
+ const data = await response.json();
310
+ const signedUrl = this.config.imageUrl + (data.urlWithSignature || data.url);
311
+ this.cache.set(cacheKey, signedUrl);
312
+ return signedUrl;
313
+ } catch (error) {
314
+ throw new Error(`Failed to get signed URL: ${error}`);
315
+ }
316
+ }
317
+ checkRateLimits() {
318
+ const now = Date.now();
319
+ if (now >= this.rateLimitState.perMinute.resetTime) {
320
+ this.rateLimitState.perMinute.count = 0;
321
+ this.rateLimitState.perMinute.resetTime = now + 6e4;
322
+ }
323
+ if (now >= this.rateLimitState.perSecond.resetTime) {
324
+ this.rateLimitState.perSecond.count = 0;
325
+ this.rateLimitState.perSecond.resetTime = now + 1e3;
326
+ }
327
+ if (this.rateLimitState.perMinute.count >= RATE_LIMIT_PER_MINUTE) {
328
+ throw new Error("Rate limit exceeded: Too many requests per minute");
329
+ }
330
+ if (this.rateLimitState.perSecond.count >= RATE_LIMIT_PER_SECOND) {
331
+ throw new Error("Rate limit exceeded: Too many requests per second");
332
+ }
333
+ }
334
+ incrementRateLimitCounters() {
335
+ this.rateLimitState.perMinute.count++;
336
+ this.rateLimitState.perSecond.count++;
337
+ }
338
+ getConfig() {
339
+ return { ...this.config };
340
+ }
341
+ };
342
+
343
+ // src/state/optionSelection.ts
344
+ function resolveBestCombination(selection, attributes, combinations) {
345
+ const relevant = Object.fromEntries(
346
+ Object.entries(selection).filter(
347
+ ([k]) => attributes[k]?.affectsCombinations !== false
348
+ )
349
+ );
350
+ let best;
351
+ for (const combo of combinations) {
352
+ let score = 0;
353
+ for (const [k, v] of Object.entries(relevant)) {
354
+ if (combo[k] === v) score += 100;
355
+ else score -= 1;
356
+ }
357
+ if (!best || score > best.score) best = { combo, score };
358
+ }
359
+ return best?.combo;
360
+ }
361
+ function computeDisabledChoices(selection, attributes, combinations) {
362
+ const disabledByKey = {};
363
+ const attributeKeys = Object.keys(attributes || {});
364
+ for (const key of attributeKeys) {
365
+ const attr = attributes[key];
366
+ if (!attr) continue;
367
+ if (attr.affectsCombinations === false) {
368
+ disabledByKey[key] = [];
369
+ continue;
370
+ }
371
+ const disabledChoices = [];
372
+ for (const choice of attr.choices || []) {
373
+ const candidateSelection = {
374
+ ...selection,
375
+ [key]: choice
376
+ };
377
+ const relevantEntries = Object.entries(candidateSelection).filter(
378
+ ([k]) => attributes[k]?.affectsCombinations !== false
379
+ );
380
+ const hasViable = combinations.some(
381
+ (combo) => relevantEntries.every(([k, v]) => combo[k] === v)
382
+ );
383
+ if (!hasViable) disabledChoices.push(choice);
384
+ }
385
+ disabledByKey[key] = disabledChoices;
386
+ }
387
+ return disabledByKey;
388
+ }
389
+ function deriveDefaultSelection(attributes, combinations) {
390
+ const best = resolveBestCombination({}, attributes, combinations);
391
+ const selection = {};
392
+ if (best) {
393
+ for (const [key, attr] of Object.entries(attributes)) {
394
+ if (attr.affectsCombinations !== false) {
395
+ const value = best[key];
396
+ if (typeof value === "string") {
397
+ selection[key] = value;
398
+ }
399
+ }
400
+ }
401
+ }
402
+ for (const [key, attr] of Object.entries(attributes)) {
403
+ if (!selection[key]) {
404
+ if (attr.type === "color-picker") {
405
+ selection[key] = "#000000";
406
+ } else if (attr.choices && attr.choices.length > 0) {
407
+ const firstChoice = attr.choices[0];
408
+ selection[key] = typeof firstChoice === "string" ? firstChoice : firstChoice.label || "";
409
+ }
410
+ }
411
+ }
412
+ return selection;
413
+ }
414
+ function getPricePreview(selection, attributes, combinations) {
415
+ const best = resolveBestCombination(selection, attributes, combinations);
416
+ const price = best?.price;
417
+ return typeof price === "number" ? price : void 0;
418
+ }
419
+ function findBestCombination(selection, combinations, attributes) {
420
+ const relevantSelection = {};
421
+ if (attributes) {
422
+ Object.entries(selection).forEach(([key, value]) => {
423
+ const attr = attributes[key];
424
+ if (attr && attr.affectsCombinations !== false) {
425
+ relevantSelection[key] = value;
426
+ }
427
+ });
428
+ } else {
429
+ Object.assign(relevantSelection, selection);
430
+ }
431
+ return combinations.find((combo) => {
432
+ return Object.entries(relevantSelection).every(([key, value]) => {
433
+ return !combo[key] || combo[key] === value;
434
+ });
435
+ });
436
+ }
437
+ function isOptionAvailable(optionName, attributeName, selection, attributes, combinations) {
438
+ if (!combinations || combinations.length === 0 || !attributes[attributeName]?.affectsCombinations) {
439
+ return true;
440
+ }
441
+ return combinations.some((combination) => {
442
+ if (combination[attributeName] !== optionName) {
443
+ return false;
444
+ }
445
+ return Object.entries(selection).every(
446
+ ([key, value]) => key === attributeName || // Skip the group we're checking
447
+ value === void 0 || value === "" || // Skip unselected options
448
+ !attributes[key]?.affectsCombinations || // Skip attributes that don't affect combinations
449
+ combination[key] === value
450
+ // Check if the selection matches
451
+ );
452
+ });
453
+ }
454
+
455
+ // src/state/manager.ts
456
+ var StateManager = class {
457
+ constructor(options) {
458
+ this.options = options;
459
+ this.state = this.loadPersistedState() || options.initialState;
460
+ this.middleware = options.middleware || [];
461
+ if (options.debug) {
462
+ this.middleware.push({
463
+ name: "debug",
464
+ afterUpdate: (state, prevState) => {
465
+ }
466
+ });
467
+ }
468
+ }
469
+ state;
470
+ prevState;
471
+ subscribers = /* @__PURE__ */ new Set();
472
+ selectorSubscribers = /* @__PURE__ */ new Map();
473
+ updateQueue = [];
474
+ isUpdating = false;
475
+ middleware;
476
+ /**
477
+ * Get current state
478
+ */
479
+ getState() {
480
+ return this.state;
481
+ }
482
+ /**
483
+ * Update state with partial updates or updater function
484
+ */
485
+ setState(updater) {
486
+ const update = typeof updater === "function" ? updater(this.state) : updater;
487
+ this.updateQueue.push(update);
488
+ if (!this.isUpdating) {
489
+ this.processUpdateQueue();
490
+ }
491
+ }
492
+ /**
493
+ * Subscribe to all state changes
494
+ */
495
+ subscribe(callback) {
496
+ this.subscribers.add(callback);
497
+ callback(this.state);
498
+ return () => {
499
+ this.subscribers.delete(callback);
500
+ };
501
+ }
502
+ /**
503
+ * Subscribe to specific state selections (computed values)
504
+ */
505
+ subscribeToSelector(selector, callback) {
506
+ if (!this.selectorSubscribers.has(selector)) {
507
+ this.selectorSubscribers.set(selector, /* @__PURE__ */ new Set());
508
+ }
509
+ const subscribers = this.selectorSubscribers.get(selector);
510
+ subscribers.add(callback);
511
+ callback(selector(this.state));
512
+ return () => {
513
+ subscribers.delete(callback);
514
+ if (subscribers.size === 0) {
515
+ this.selectorSubscribers.delete(selector);
516
+ }
517
+ };
518
+ }
519
+ /**
520
+ * Reset state to initial value
521
+ */
522
+ reset() {
523
+ this.setState(this.options.initialState);
524
+ }
525
+ /**
526
+ * Get a derived value from state
527
+ */
528
+ select(selector) {
529
+ return selector(this.state);
530
+ }
531
+ processUpdateQueue() {
532
+ if (this.updateQueue.length === 0) return;
533
+ this.isUpdating = true;
534
+ this.prevState = { ...this.state };
535
+ let newState = { ...this.state };
536
+ while (this.updateQueue.length > 0) {
537
+ const update = this.updateQueue.shift();
538
+ let processedUpdate = update;
539
+ for (const mw of this.middleware) {
540
+ if (mw.beforeUpdate) {
541
+ processedUpdate = mw.beforeUpdate(newState, processedUpdate);
542
+ }
543
+ }
544
+ newState = { ...newState, ...processedUpdate };
545
+ }
546
+ if (this.hasStateChanged(this.state, newState)) {
547
+ this.state = newState;
548
+ this.persistState();
549
+ this.notifySubscribers();
550
+ for (const mw of this.middleware) {
551
+ if (mw.afterUpdate) {
552
+ mw.afterUpdate(this.state, this.prevState);
553
+ }
554
+ }
555
+ }
556
+ this.isUpdating = false;
557
+ if (this.updateQueue.length > 0) {
558
+ this.processUpdateQueue();
559
+ }
560
+ }
561
+ notifySubscribers() {
562
+ this.subscribers.forEach((subscriber) => {
563
+ try {
564
+ subscriber(this.state, this.prevState);
565
+ } catch (error) {
566
+ console.error("[StateManager] Subscriber error:", error);
567
+ }
568
+ });
569
+ this.selectorSubscribers.forEach((subscribers, selector) => {
570
+ const currentValue = selector(this.state);
571
+ const prevValue = this.prevState ? selector(this.prevState) : void 0;
572
+ if (currentValue !== prevValue) {
573
+ subscribers.forEach((callback) => {
574
+ try {
575
+ callback(currentValue, prevValue);
576
+ } catch (error) {
577
+ console.error("[StateManager] Selector subscriber error:", error);
578
+ }
579
+ });
580
+ }
581
+ });
582
+ }
583
+ hasStateChanged(prev, next) {
584
+ return JSON.stringify(prev) !== JSON.stringify(next);
585
+ }
586
+ getStateDiff(prev, next) {
587
+ const diff = {};
588
+ for (const key in next) {
589
+ if (prev[key] !== next[key]) {
590
+ diff[key] = next[key];
591
+ }
592
+ }
593
+ return diff;
594
+ }
595
+ persistState() {
596
+ if (this.options.persist) {
597
+ const storage = this.options.persist.storage || localStorage;
598
+ try {
599
+ storage.setItem(
600
+ this.options.persist.key,
601
+ JSON.stringify(this.state)
602
+ );
603
+ } catch (error) {
604
+ console.error("[StateManager] Failed to persist state:", error);
605
+ }
606
+ }
607
+ }
608
+ loadPersistedState() {
609
+ if (!this.options.persist) return null;
610
+ const storage = this.options.persist.storage || localStorage;
611
+ try {
612
+ const stored = storage.getItem(this.options.persist.key);
613
+ return stored ? JSON.parse(stored) : null;
614
+ } catch (error) {
615
+ console.error("[StateManager] Failed to load persisted state:", error);
616
+ return null;
617
+ }
618
+ }
619
+ };
620
+ function createStateStore(initialState, options) {
621
+ return new StateManager({
622
+ ...options,
623
+ initialState
624
+ });
625
+ }
626
+ function createStateHook(store) {
627
+ return {
628
+ getState: () => store.getState(),
629
+ setState: (updater) => store.setState(updater),
630
+ subscribe: (callback) => store.subscribe(callback),
631
+ select: (selector) => store.select(selector),
632
+ reset: () => store.reset()
633
+ };
634
+ }
635
+
636
+ // src/rendering/productTitle.ts
637
+ function describeProductTitle(options) {
638
+ return {
639
+ type: "product-title",
640
+ product: options.product,
641
+ showPrice: options.showPrice ?? true,
642
+ showDescription: options.showDescription ?? true,
643
+ size: options.size ?? "md",
644
+ className: options.className
645
+ };
646
+ }
647
+
648
+ // src/rendering/productPrice.ts
649
+ function formatPrice(cents, locale, currency, showCurrency) {
650
+ const amount = cents / 100;
651
+ if (showCurrency) {
652
+ return new Intl.NumberFormat(locale, {
653
+ style: "currency",
654
+ currency
655
+ }).format(amount);
656
+ }
657
+ return new Intl.NumberFormat(locale, {
658
+ minimumFractionDigits: 2,
659
+ maximumFractionDigits: 2
660
+ }).format(amount);
661
+ }
662
+ function describeProductPrice(options, contextPrice) {
663
+ const displayPrice = options.price ?? contextPrice;
664
+ if (!displayPrice) {
665
+ return null;
666
+ }
667
+ return {
668
+ type: "price",
669
+ price: displayPrice,
670
+ currency: options.currency || "USD",
671
+ locale: options.locale || "en-US",
672
+ showCurrency: options.showCurrency !== false,
673
+ className: options.className
674
+ };
675
+ }
676
+
677
+ // src/rendering/productImage.ts
678
+ function describeProductImage(options, contextImage) {
679
+ const src = options.src || contextImage?.src;
680
+ if (!src) {
681
+ return null;
682
+ }
683
+ return {
684
+ type: "image",
685
+ src,
686
+ alt: options.alt || contextImage?.alt || "Product image",
687
+ width: options.width,
688
+ height: options.height,
689
+ aspectRatio: options.aspectRatio || "1",
690
+ className: options.className
691
+ };
692
+ }
693
+
694
+ // src/rendering/productOptions.ts
695
+ function describeProductOptions(options) {
696
+ return {
697
+ type: "product-options",
698
+ options: options.options,
699
+ selectedOptions: options.selectedOptions || {},
700
+ className: options.className
701
+ };
702
+ }
703
+
704
+ // src/rendering/productArtAlignment.ts
705
+ function getSnapPoints(containerSize, maskDimensions, effectiveAlignment) {
706
+ const maxDistance = effectiveAlignment === "horizontal" ? (containerSize.width - maskDimensions.width) / 2 : (containerSize.height - maskDimensions.height) / 2;
707
+ const containerDimension = effectiveAlignment === "horizontal" ? containerSize.width : containerSize.height;
708
+ const movementRatio = maxDistance / containerDimension;
709
+ const useCenterOnly = movementRatio < 0.05;
710
+ const useThreePoints = movementRatio < 0.15;
711
+ if (effectiveAlignment === "horizontal") {
712
+ if (useCenterOnly) {
713
+ return {
714
+ center: 0
715
+ };
716
+ }
717
+ if (useThreePoints) {
718
+ return {
719
+ "far-left": -maxDistance,
720
+ center: 0,
721
+ "far-right": maxDistance
722
+ };
723
+ }
724
+ return {
725
+ "far-left": -maxDistance,
726
+ left: -maxDistance / 2,
727
+ center: 0,
728
+ right: maxDistance / 2,
729
+ "far-right": maxDistance
730
+ };
731
+ }
732
+ if (useCenterOnly) {
733
+ return {
734
+ center: 0
735
+ };
736
+ }
737
+ if (useThreePoints) {
738
+ return {
739
+ "far-top": -maxDistance,
740
+ center: 0,
741
+ "far-bottom": maxDistance
742
+ };
743
+ }
744
+ return {
745
+ "far-top": -maxDistance,
746
+ top: -maxDistance / 2,
747
+ center: 0,
748
+ bottom: maxDistance / 2,
749
+ "far-bottom": maxDistance
750
+ };
751
+ }
752
+ function getEffectiveAlignment(artworkAspectRatio, maskAspectRatio) {
753
+ if (artworkAspectRatio < maskAspectRatio) {
754
+ return "vertical";
755
+ }
756
+ return "horizontal";
757
+ }
758
+ function describeProductArtAlignment(options, context) {
759
+ const src = options.src || context?.product?.mockupUrl;
760
+ if (!src) {
761
+ return null;
762
+ }
763
+ let maskAspectRatio = options.maskAspectRatio;
764
+ if (!maskAspectRatio && context?.product?.placements) {
765
+ const placements = context.product.placements;
766
+ let placement;
767
+ if (options.placement) {
768
+ placement = placements.find((p) => p.label === options.placement);
769
+ } else {
770
+ placement = placements.find((p) => p.type === "image") || placements[0];
771
+ }
772
+ if (placement && placement.width && placement.height) {
773
+ maskAspectRatio = placement.width / placement.height;
774
+ }
775
+ }
776
+ if (!maskAspectRatio) {
777
+ maskAspectRatio = 1;
778
+ }
779
+ const artworkAspectRatio = options.artworkAspectRatio || context?.product?.aspectRatio || 1;
780
+ const alignment = options.alignment || context?.selection?.alignment || "center";
781
+ const effectiveAlignment = getEffectiveAlignment(artworkAspectRatio, maskAspectRatio);
782
+ return {
783
+ type: "art-alignment",
784
+ src,
785
+ artworkAspectRatio,
786
+ maskAspectRatio,
787
+ effectiveAlignment,
788
+ alignment,
789
+ className: options.className
790
+ };
791
+ }
792
+ function findClosestSnapPoint(position, containerSize, maskDimensions, effectiveAlignment) {
793
+ const snapPoints = getSnapPoints(containerSize, maskDimensions, effectiveAlignment);
794
+ let closestKey = "center";
795
+ let closestDistance = Infinity;
796
+ for (const [key, value] of Object.entries(snapPoints)) {
797
+ const distance = Math.abs(position - value);
798
+ if (distance < closestDistance) {
799
+ closestDistance = distance;
800
+ closestKey = key;
801
+ }
802
+ }
803
+ return closestKey;
804
+ }
805
+
806
+ // src/rendering/artSelector.ts
807
+ function describeArtSelector(options, context) {
808
+ const artworks = [];
809
+ const defaultArtworks = [
810
+ "https://images.unsplash.com/photo-1533738363-b7f9aef128ce?q=80&w=2670&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
811
+ "https://images.unsplash.com/photo-1754999961467-0d6e4c2551e3?q=80&w=2940&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
812
+ ];
813
+ const sources = options.artworks || defaultArtworks;
814
+ sources.forEach((src) => {
815
+ artworks.push({
816
+ type: "regular",
817
+ src
818
+ });
819
+ });
820
+ return {
821
+ type: "art-selector",
822
+ artworks,
823
+ selectedArtwork: context?.selectedArtwork || null,
824
+ className: options.className
825
+ };
826
+ }
827
+
828
+ // src/rendering/optionSelection.ts
829
+ function getOptionRenderType(attr) {
830
+ if (attr.type === "color-picker") {
831
+ return "color-picker";
832
+ }
833
+ const hasVisualChoices = attr.choices?.some(
834
+ (choice) => typeof choice === "object" && (choice.hex || choice.imageUrl)
835
+ );
836
+ if (hasVisualChoices) {
837
+ return "swatch";
838
+ }
839
+ return "select";
840
+ }
841
+ function normalizeChoice(choice) {
842
+ if (typeof choice === "string") {
843
+ return { label: choice, value: choice };
844
+ }
845
+ return {
846
+ ...choice,
847
+ value: choice.value || choice.label
848
+ };
849
+ }
850
+ function prepareOptionRenderData(attributes, selection, combinations) {
851
+ return Object.entries(attributes).map(([key, attr]) => {
852
+ const currentValue = selection[key] || "";
853
+ const renderType = getOptionRenderType(attr);
854
+ const choices = (attr.choices || []).map((choice) => {
855
+ const normalized = normalizeChoice(choice);
856
+ const value = normalized.value || normalized.label;
857
+ const isAvailable = isOptionAvailable(
858
+ value,
859
+ key,
860
+ selection,
861
+ attributes,
862
+ combinations
863
+ );
864
+ return {
865
+ value,
866
+ label: normalized.label,
867
+ disabled: !isAvailable,
868
+ selected: currentValue === value,
869
+ hex: normalized.hex,
870
+ imageUrl: normalized.imageUrl
871
+ };
872
+ });
873
+ return {
874
+ key,
875
+ label: key.charAt(0).toUpperCase() + key.slice(1).replace(/_/g, " "),
876
+ type: renderType,
877
+ value: currentValue,
878
+ choices,
879
+ required: attr.affectsCombinations !== false
880
+ };
881
+ });
882
+ }
883
+ function handleOptionChange(optionKey, newValue, currentSelection, attributes) {
884
+ const newSelection = {
885
+ ...currentSelection,
886
+ [optionKey]: newValue
887
+ };
888
+ const clearedSelection = clearDependentSelections(
889
+ optionKey,
890
+ newSelection,
891
+ attributes
892
+ );
893
+ return clearedSelection;
894
+ }
895
+ function clearDependentSelections(changedKey, selection, attributes) {
896
+ return selection;
897
+ }
898
+ function validateRequiredOptions(selection, attributes) {
899
+ const missing = [];
900
+ for (const [key, attr] of Object.entries(attributes)) {
901
+ if (attr.affectsCombinations === false) continue;
902
+ const value = selection[key];
903
+ if (!value || value === "") {
904
+ missing.push(key);
905
+ }
906
+ }
907
+ return {
908
+ valid: missing.length === 0,
909
+ missing
910
+ };
911
+ }
912
+ function getSelectionDisplayText(selection, attributes) {
913
+ const parts = [];
914
+ for (const [key, value] of Object.entries(selection)) {
915
+ if (!value || value === "") continue;
916
+ const attr = attributes[key];
917
+ if (!attr) continue;
918
+ const choice = attr.choices?.find((c) => {
919
+ const normalized = normalizeChoice(c);
920
+ return (normalized.value || normalized.label) === value;
921
+ });
922
+ const label = choice ? normalizeChoice(choice).label : value;
923
+ parts.push(label);
924
+ }
925
+ return parts.join(" / ");
926
+ }
927
+ var ColorPickerUtils = {
928
+ /**
929
+ * Validates a hex color
930
+ */
931
+ isValidHex(hex) {
932
+ return /^#[0-9A-F]{6}$/i.test(hex);
933
+ },
934
+ /**
935
+ * Converts hex to RGB
936
+ */
937
+ hexToRgb(hex) {
938
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
939
+ return result ? {
940
+ r: parseInt(result[1], 16),
941
+ g: parseInt(result[2], 16),
942
+ b: parseInt(result[3], 16)
943
+ } : null;
944
+ },
945
+ /**
946
+ * Gets contrast color for text on background
947
+ */
948
+ getContrastColor(hex) {
949
+ const rgb = this.hexToRgb(hex);
950
+ if (!rgb) return "black";
951
+ const luminance = (0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b) / 255;
952
+ return luminance > 0.5 ? "black" : "white";
953
+ }
954
+ };
955
+ var SwatchUtils = {
956
+ /**
957
+ * Gets swatch style object
958
+ */
959
+ getSwatchStyle(choice) {
960
+ const style = {};
961
+ if (choice.imageUrl) {
962
+ style.backgroundImage = `url(${choice.imageUrl})`;
963
+ style.backgroundSize = "cover";
964
+ style.backgroundPosition = "center";
965
+ style.border = "1px solid #e2e8f0";
966
+ } else if (choice.hex) {
967
+ style.backgroundColor = choice.hex;
968
+ style.border = "1px solid #e2e8f0";
969
+ } else {
970
+ style.border = "1px solid #e2e8f0";
971
+ style.display = "flex";
972
+ style.alignItems = "center";
973
+ style.justifyContent = "center";
974
+ }
975
+ if (choice.disabled) {
976
+ style.opacity = "0.5";
977
+ style.cursor = "not-allowed";
978
+ }
979
+ if (choice.selected) {
980
+ style.boxShadow = "0 0 0 2px #3b82f6";
981
+ }
982
+ return style;
983
+ },
984
+ /**
985
+ * Gets swatch class names
986
+ */
987
+ getSwatchClasses(choice) {
988
+ const classes = ["option-swatch"];
989
+ if (choice.disabled) classes.push("option-swatch--disabled");
990
+ if (choice.selected) classes.push("option-swatch--selected");
991
+ if (choice.hex) classes.push("option-swatch--color");
992
+ if (choice.imageUrl) classes.push("option-swatch--image");
993
+ return classes;
994
+ }
995
+ };
996
+
997
+ // src/constants/mockup.ts
998
+ var DEFAULT_ARTWORK_URL = "https://images.unsplash.com/photo-1533738363-b7f9aef128ce?q=80&w=2670&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D";
999
+ var DEFAULT_COLOR = "#000000";
1000
+ var DEFAULT_PLACEMENT_DIMENSIONS = {
1001
+ width: 1200,
1002
+ height: 1600
1003
+ };
1004
+ var DEFAULT_ASPECT_RATIO = 1;
1005
+
1006
+ // src/utils/url.ts
1007
+ function resolveUrlFromDOM(relativeUrl) {
1008
+ if (typeof document === "undefined") return null;
1009
+ const pathParts = relativeUrl.split("/");
1010
+ const filename = pathParts[pathParts.length - 1];
1011
+ if (!filename) return null;
1012
+ const filenameWithoutExt = filename.replace(/\.[^.]+$/, "");
1013
+ const images = document.querySelectorAll("img");
1014
+ for (const img of images) {
1015
+ const src = img.getAttribute("src");
1016
+ if (!src) continue;
1017
+ if (src.includes(filenameWithoutExt)) {
1018
+ return src;
1019
+ }
1020
+ }
1021
+ return null;
1022
+ }
1023
+ function normalizeImageUrl(url) {
1024
+ if (!url) return null;
1025
+ if (url.startsWith("http://") || url.startsWith("https://")) {
1026
+ return url;
1027
+ }
1028
+ if (url.startsWith("data:")) {
1029
+ return url;
1030
+ }
1031
+ if (url.startsWith("//")) {
1032
+ return url;
1033
+ }
1034
+ if (typeof window !== "undefined") {
1035
+ const domUrl = resolveUrlFromDOM(url);
1036
+ if (domUrl) {
1037
+ return domUrl;
1038
+ }
1039
+ try {
1040
+ const absoluteUrl = new URL(url, window.location.origin).href;
1041
+ if (absoluteUrl.includes("localhost") || absoluteUrl.includes("127.0.0.1")) {
1042
+ console.warn(
1043
+ `[merchify-sdk] Artwork URL "${absoluteUrl}" uses localhost. This will work in your browser but won't work for mockup generation. Deploy your app or use publicly accessible URLs for production mockups.`
1044
+ );
1045
+ }
1046
+ return absoluteUrl;
1047
+ } catch (e) {
1048
+ console.warn(`[merchify-sdk] Failed to normalize URL "${url}":`, e);
1049
+ return url;
1050
+ }
1051
+ }
1052
+ if (url.startsWith("/") || url.startsWith(".")) {
1053
+ console.warn(
1054
+ `[merchify-sdk] Relative URL "${url}" detected during SSR. URL will be resolved on the client. Consider using absolute URLs for SSR compatibility.`
1055
+ );
1056
+ }
1057
+ return url;
1058
+ }
1059
+
1060
+ // src/mockup/designGeneration.ts
1061
+ function createDesignForPlacements(placements, providedImages, context) {
1062
+ const design = [];
1063
+ const defaultImageUrl = context?.artworkSrc || context?.defaultImageUrl || null;
1064
+ const imagesByPlacement = /* @__PURE__ */ new Map();
1065
+ if (providedImages) {
1066
+ providedImages.forEach((img) => {
1067
+ if (img.placement) {
1068
+ imagesByPlacement.set(img.placement, img);
1069
+ }
1070
+ });
1071
+ }
1072
+ const selection = context?.selection || {};
1073
+ for (const placement of placements) {
1074
+ const providedImage = imagesByPlacement.get(placement.label);
1075
+ if (placement.type === "color") {
1076
+ let selectedColor = DEFAULT_COLOR;
1077
+ if (selection[placement.label]) {
1078
+ const selectedValue = selection[placement.label];
1079
+ if (typeof selectedValue === "string" && selectedValue.startsWith("#")) {
1080
+ selectedColor = selectedValue;
1081
+ } else {
1082
+ const optionAttr = context?.attributes?.[placement.label];
1083
+ if (optionAttr?.choices) {
1084
+ const choice = optionAttr.choices.find(
1085
+ (c) => typeof c === "object" && c.label === selectedValue
1086
+ );
1087
+ if (choice && typeof choice === "object" && "hex" in choice) {
1088
+ selectedColor = choice.hex;
1089
+ }
1090
+ }
1091
+ }
1092
+ }
1093
+ design.push({
1094
+ type: "color",
1095
+ hex: providedImage?.text || selectedColor,
1096
+ imageUrl: "",
1097
+ // Empty string for color type
1098
+ placement: placement.label,
1099
+ width: placement.width || 0,
1100
+ height: placement.height || 0,
1101
+ alignment: "center"
1102
+ });
1103
+ } else {
1104
+ const placementDesign = context?.placementDesigns?.[placement.label];
1105
+ const rawImageUrl = placementDesign?.imageUrl || providedImage?.imageUrl || defaultImageUrl;
1106
+ const alignment = placementDesign?.alignment || providedImage?.alignment || "center";
1107
+ const tiles = placementDesign?.tiles || providedImage?.tiles || context?.tiles;
1108
+ const imageUrl = normalizeImageUrl(rawImageUrl);
1109
+ if (imageUrl) {
1110
+ const designElement = {
1111
+ type: "image",
1112
+ imageUrl,
1113
+ placement: placement.label,
1114
+ width: placement.width || DEFAULT_PLACEMENT_DIMENSIONS.width,
1115
+ height: placement.height || DEFAULT_PLACEMENT_DIMENSIONS.height,
1116
+ alignment
1117
+ };
1118
+ if (tiles && [0.25, 0.5, 1, 2, 4].includes(tiles)) {
1119
+ designElement.tiles = tiles;
1120
+ }
1121
+ design.push(designElement);
1122
+ }
1123
+ }
1124
+ }
1125
+ if (placements.length === 0 && providedImages && providedImages.length > 0) {
1126
+ return providedImages.map((img) => ({
1127
+ type: img.imageUrl ? "image" : "color",
1128
+ imageUrl: normalizeImageUrl(img.imageUrl) || "",
1129
+ hex: img.text || DEFAULT_COLOR,
1130
+ placement: img.placement || "Front",
1131
+ width: img.size?.width || DEFAULT_PLACEMENT_DIMENSIONS.width,
1132
+ height: img.size?.height || DEFAULT_PLACEMENT_DIMENSIONS.height,
1133
+ alignment: img.alignment || "center"
1134
+ }));
1135
+ }
1136
+ if (placements.length === 0 && (!providedImages || providedImages.length === 0)) {
1137
+ const normalizedDefaultUrl = normalizeImageUrl(defaultImageUrl);
1138
+ if (normalizedDefaultUrl) {
1139
+ return [{
1140
+ type: "image",
1141
+ imageUrl: normalizedDefaultUrl,
1142
+ placement: "Front",
1143
+ width: DEFAULT_PLACEMENT_DIMENSIONS.width,
1144
+ height: DEFAULT_PLACEMENT_DIMENSIONS.height,
1145
+ alignment: "center"
1146
+ }];
1147
+ }
1148
+ return [];
1149
+ }
1150
+ return design;
1151
+ }
1152
+ function filterImagePlacements(placements) {
1153
+ return placements.filter((p) => p.type === "image" || p.type === null);
1154
+ }
1155
+
1156
+ // src/mockup/urlGenerator.ts
1157
+ var cachedMockupConfig = null;
1158
+ function resolveDefaultConfig() {
1159
+ const DEFAULT_MOCKUP_URL = "https://MOCKUP_URL_NOT_CONFIGURED.invalid";
1160
+ const DEFAULT_ACCOUNT_ID = "ACCOUNT_ID_NOT_CONFIGURED";
1161
+ const winConfig = typeof window !== "undefined" && window.merchify || {};
1162
+ const env = typeof process !== "undefined" ? process.env : void 0;
1163
+ const envEndpoint = env?.CATALOG_API_BASE_URL;
1164
+ const envImageUrl = env?.MERCHIFY_IMAGE_URL || env?.NEXT_PUBLIC_MERCH_MOCKUP_URL;
1165
+ const envSignerUrl = env?.MERCHIFY_SIGNER_URL || env?.NEXT_PUBLIC_MERCH_SIGNER_URL;
1166
+ const envAccountId = env?.MERCHIFY_ACCOUNT_ID || env?.NEXT_PUBLIC_MERCH_ACCOUNT_ID;
1167
+ return {
1168
+ endpoint: winConfig.endpoint || envEndpoint || void 0,
1169
+ mockupUrl: winConfig.mockupUrl || envImageUrl || DEFAULT_MOCKUP_URL,
1170
+ signerUrl: winConfig.signerUrl || envSignerUrl || void 0,
1171
+ accountId: winConfig.accountId || envAccountId || DEFAULT_ACCOUNT_ID,
1172
+ mode: winConfig.mode || "mock"
1173
+ };
1174
+ }
1175
+ var localhostWarningShown = false;
1176
+ function config(overrides) {
1177
+ if (overrides) {
1178
+ const base = resolveDefaultConfig();
1179
+ cachedMockupConfig = { ...base, ...overrides };
1180
+ } else if (!cachedMockupConfig) {
1181
+ cachedMockupConfig = resolveDefaultConfig();
1182
+ }
1183
+ const hostname = typeof window !== "undefined" ? window.location.hostname : "";
1184
+ const isLocalDev = hostname === "localhost" || hostname === "127.0.0.1" || hostname.includes("192.168.") || hostname.endsWith(".local") || hostname.endsWith(".lvh.me") || hostname === "lvh.me";
1185
+ if (!localhostWarningShown && typeof window !== "undefined" && !isLocalDev && cachedMockupConfig?.mockupUrl?.includes("localhost")) {
1186
+ console.error(
1187
+ `[@snowcone-app/sdk] WARNING: Using localhost mockup URL in production environment.
1188
+ mockupUrl: ${cachedMockupConfig.mockupUrl}
1189
+ This was likely caused by building with .env.local present.
1190
+ Redeploy with "pnpm run deploy" to fix.`
1191
+ );
1192
+ localhostWarningShown = true;
1193
+ }
1194
+ return cachedMockupConfig;
1195
+ }
1196
+ var resolveMockupConfig = config;
1197
+ function normalizeDesignElements(design) {
1198
+ return design.map((element) => {
1199
+ const normalized = {};
1200
+ const keys = Object.keys(element).sort();
1201
+ for (const key of keys) {
1202
+ if (key === "width" || key === "height" || key === "type") {
1203
+ continue;
1204
+ }
1205
+ if (key === "alignment" && element[key] === "center") {
1206
+ continue;
1207
+ }
1208
+ const value = element[key];
1209
+ if (value !== void 0 && value !== null) {
1210
+ normalized[key] = value;
1211
+ }
1212
+ }
1213
+ return normalized;
1214
+ });
1215
+ }
1216
+ function mockupUrl(options) {
1217
+ const mockupConfig = config();
1218
+ const mockupBaseUrl = mockupConfig.mockupUrl || "https://MOCKUP_URL_NOT_CONFIGURED.invalid";
1219
+ const accountId = mockupConfig.accountId || "ACCOUNT_ID_NOT_CONFIGURED";
1220
+ const normalizedDesign = normalizeDesignElements(options.design);
1221
+ const designBase64 = btoa(JSON.stringify(normalizedDesign));
1222
+ const encodedDesign = encodeURIComponent(designBase64);
1223
+ let queryString = `productId=${encodeURIComponent(options.productId)}`;
1224
+ queryString += `&mockupId=${encodeURIComponent(options.mockupId)}`;
1225
+ queryString += `&variantId=${encodeURIComponent(options.variantId)}`;
1226
+ queryString += `&design=${encodedDesign}`;
1227
+ queryString += `&width=${options.width}`;
1228
+ queryString += `&accountId=${encodeURIComponent(accountId)}`;
1229
+ if (options.effects?.grain) {
1230
+ queryString += `&grain=${options.effects.grain}`;
1231
+ }
1232
+ if (options.ar && options.ar !== "16:9") {
1233
+ queryString += `&ar=${encodeURIComponent(options.ar)}`;
1234
+ }
1235
+ if (options.maskOverrides && options.maskOverrides.length > 0) {
1236
+ const maskOverridesBase64 = btoa(JSON.stringify(options.maskOverrides));
1237
+ queryString += `&maskOverrides=${encodeURIComponent(maskOverridesBase64)}`;
1238
+ }
1239
+ if (mockupConfig.mode === "mock") {
1240
+ const mockSignature = "bypass-sig-for-k6-load-test";
1241
+ queryString += `&sig=${encodeURIComponent(mockSignature)}`;
1242
+ } else {
1243
+ console.warn("Live mode signature generation not implemented in mockupUrl");
1244
+ }
1245
+ return `${mockupBaseUrl}/mockup?${queryString}`;
1246
+ }
1247
+ function getVariant(selection, product) {
1248
+ const combination = findBestCombination(
1249
+ selection,
1250
+ product.combinations,
1251
+ product.attributes
1252
+ );
1253
+ const variantId = combination?.variantId;
1254
+ let mockupId;
1255
+ if (variantId && product.mockups && product.mockups.length > 0) {
1256
+ const mockupForVariant = product.mockups.find(
1257
+ (m) => m.gvids && m.gvids.includes(variantId)
1258
+ );
1259
+ mockupId = mockupForVariant?.id || product.mockups[0].id;
1260
+ }
1261
+ return {
1262
+ variantId,
1263
+ mockupId,
1264
+ price: combination?.price,
1265
+ combination
1266
+ };
1267
+ }
1268
+ function resolveVariantId(context, providedGvid, providedVariantId) {
1269
+ if (providedGvid) return providedGvid;
1270
+ if (providedVariantId) return providedVariantId;
1271
+ if (context?.combinations && context.combinations.length > 0 && context?.selection) {
1272
+ const best = findBestCombination(
1273
+ context.selection,
1274
+ context.combinations,
1275
+ context.optionAttributes || context.attributes
1276
+ );
1277
+ if (best?.variantId) {
1278
+ return best.variantId;
1279
+ } else if (context.combinations[0]?.variantId) {
1280
+ return context.combinations[0].variantId;
1281
+ }
1282
+ } else if (context?.combinations && context.combinations.length > 0) {
1283
+ return context.combinations[0].variantId || "default";
1284
+ } else if (context?.product?.variants && context.product.variants.length > 0) {
1285
+ return context.product.variants[0].gvid || "default";
1286
+ }
1287
+ if (context?.product) {
1288
+ const defaultGvid = context.product.defaultGvid;
1289
+ const firstVariant = context.product.variants?.[0];
1290
+ return defaultGvid || firstVariant?.gvid || "default";
1291
+ }
1292
+ return "default";
1293
+ }
1294
+ function resolveMockupId(context, variantId, providedMockupId) {
1295
+ if (providedMockupId) return providedMockupId;
1296
+ if (context?.product?.mockups?.length) {
1297
+ if (variantId) {
1298
+ const mockupForVariant = context.product.mockups.find(
1299
+ (m) => m.gvids && m.gvids.includes(variantId)
1300
+ );
1301
+ if (mockupForVariant) {
1302
+ return mockupForVariant.id;
1303
+ }
1304
+ }
1305
+ return context.product.mockups[0].id;
1306
+ }
1307
+ return null;
1308
+ }
1309
+
1310
+ // src/product/dataTransformation.ts
1311
+ function toOptionAttributes(product) {
1312
+ const attrs = {};
1313
+ const src = product?.options?.attributes;
1314
+ if (src && typeof src === "object") {
1315
+ for (const key of Object.keys(src)) {
1316
+ const a = src[key] || {};
1317
+ const choices = Array.isArray(a.choices) ? a.choices.map((c) => {
1318
+ if (typeof c === "string") {
1319
+ return { label: c };
1320
+ }
1321
+ return c;
1322
+ }) : Array.isArray(a) ? a.map((x) => typeof x === "string" ? { label: x } : x) : [];
1323
+ attrs[key] = {
1324
+ key,
1325
+ affectsCombinations: a.affectsCombinations !== false,
1326
+ choices,
1327
+ type: a.type
1328
+ // Preserve the type (e.g., 'color-picker')
1329
+ };
1330
+ }
1331
+ }
1332
+ return attrs;
1333
+ }
1334
+ function toCombinations(product) {
1335
+ const combos = product?.options?.combinations || [];
1336
+ if (Array.isArray(combos)) return combos;
1337
+ return [];
1338
+ }
1339
+ function extractProductId(product) {
1340
+ if (!product) return void 0;
1341
+ return product.id || product.productId || void 0;
1342
+ }
1343
+ function getDefaultVariantId(product) {
1344
+ if (!product) return "default";
1345
+ const defaultGvid = product.defaultGvid;
1346
+ const firstVariant = product.variants?.[0];
1347
+ return defaultGvid || firstVariant?.gvid || "default";
1348
+ }
1349
+
1350
+ // src/product/validation.ts
1351
+ function validateProductSelection(attributes, selection, combinations) {
1352
+ if (!attributes || Object.keys(attributes).length === 0) {
1353
+ return true;
1354
+ }
1355
+ const allRequiredSelected = Object.keys(attributes).every((key) => {
1356
+ const attr = attributes[key];
1357
+ if (attr.optional) return true;
1358
+ const hasSelection = selection[key] && selection[key] !== "";
1359
+ return hasSelection;
1360
+ });
1361
+ if (!allRequiredSelected) {
1362
+ return false;
1363
+ }
1364
+ if (!combinations || combinations.length === 0) {
1365
+ return true;
1366
+ }
1367
+ return combinations.some((combination) => {
1368
+ return Object.entries(selection).every(([key, value]) => {
1369
+ if (attributes[key] && attributes[key].affectsCombinations === false) {
1370
+ return true;
1371
+ }
1372
+ return !value || value === "" || combination[key] === value;
1373
+ });
1374
+ });
1375
+ }
1376
+ function getMissingSelections(attributes, selection) {
1377
+ const missing = [];
1378
+ Object.keys(attributes).forEach((key) => {
1379
+ const attr = attributes[key];
1380
+ if (attr.optional) return;
1381
+ if (!selection[key] || selection[key] === "") {
1382
+ missing.push(key);
1383
+ }
1384
+ });
1385
+ return missing;
1386
+ }
1387
+ function isSelectionComplete(attributes, selection) {
1388
+ return getMissingSelections(attributes, selection).length === 0;
1389
+ }
1390
+ function getIncompleteSelectionMessage(attributes, selection) {
1391
+ const missing = getMissingSelections(attributes, selection);
1392
+ if (missing.length === 0) {
1393
+ return "";
1394
+ }
1395
+ if (missing.length === 1) {
1396
+ return `Please select ${missing[0]}`;
1397
+ }
1398
+ const lastItem = missing.pop();
1399
+ return `Please select ${missing.join(", ")} and ${lastItem}`;
1400
+ }
1401
+
1402
+ // src/validation/mockup.ts
1403
+ function validateImageUrl(url) {
1404
+ const errors = [];
1405
+ if (!url) {
1406
+ errors.push({
1407
+ field: "imageUrl",
1408
+ message: "Image URL is required",
1409
+ value: url
1410
+ });
1411
+ } else if (
1412
+ // Allow absolute URLs
1413
+ !url.startsWith("http://") && !url.startsWith("https://") && // Allow data URLs
1414
+ !url.startsWith("data:") && // Allow protocol-relative URLs
1415
+ !url.startsWith("//") && // Allow relative URLs (will be normalized later)
1416
+ !url.startsWith("/") && !url.startsWith("./") && !url.startsWith("../")
1417
+ ) {
1418
+ errors.push({
1419
+ field: "imageUrl",
1420
+ message: "Image URL must be absolute (http://, https://), relative (/, ./, ../), protocol-relative (//), or a data URL",
1421
+ value: url
1422
+ });
1423
+ }
1424
+ return {
1425
+ valid: errors.length === 0,
1426
+ errors
1427
+ };
1428
+ }
1429
+ function validateAlignment(alignment) {
1430
+ const errors = [];
1431
+ const validAlignments = [
1432
+ "center",
1433
+ "top",
1434
+ "far-top",
1435
+ "bottom",
1436
+ "far-bottom",
1437
+ "left",
1438
+ "far-left",
1439
+ "right",
1440
+ "far-right"
1441
+ ];
1442
+ if (alignment && !validAlignments.includes(alignment)) {
1443
+ errors.push({
1444
+ field: "alignment",
1445
+ message: `Alignment must be one of: ${validAlignments.join(", ")}`,
1446
+ value: alignment
1447
+ });
1448
+ }
1449
+ return {
1450
+ valid: errors.length === 0,
1451
+ errors
1452
+ };
1453
+ }
1454
+ function validateTileCount(tiles) {
1455
+ const errors = [];
1456
+ const validTiles = [0.25, 0.5, 1, 2, 4];
1457
+ if (tiles && !validTiles.includes(tiles)) {
1458
+ errors.push({
1459
+ field: "tiles",
1460
+ message: `Tile count must be one of: ${validTiles.join(", ")}`,
1461
+ value: tiles
1462
+ });
1463
+ }
1464
+ return {
1465
+ valid: errors.length === 0,
1466
+ errors
1467
+ };
1468
+ }
1469
+ function validateDesignElement(element) {
1470
+ const errors = [];
1471
+ if (!element.placement) {
1472
+ errors.push({
1473
+ field: "placement",
1474
+ message: "Placement is required for design element",
1475
+ value: element
1476
+ });
1477
+ }
1478
+ if (element.type === "image") {
1479
+ if (!element.imageUrl) {
1480
+ errors.push({
1481
+ field: "imageUrl",
1482
+ message: "Image URL is required for image type",
1483
+ value: element
1484
+ });
1485
+ } else {
1486
+ const urlValidation = validateImageUrl(element.imageUrl);
1487
+ errors.push(...urlValidation.errors);
1488
+ }
1489
+ if (element.tiles) {
1490
+ const tilesValidation = validateTileCount(element.tiles);
1491
+ errors.push(...tilesValidation.errors);
1492
+ }
1493
+ } else if (element.type === "color") {
1494
+ if (!element.hex) {
1495
+ errors.push({
1496
+ field: "hex",
1497
+ message: "Hex color is required for color type",
1498
+ value: element
1499
+ });
1500
+ } else if (!/^#[0-9A-Fa-f]{6}$/.test(element.hex)) {
1501
+ errors.push({
1502
+ field: "hex",
1503
+ message: "Hex color must be in format #RRGGBB",
1504
+ value: element.hex
1505
+ });
1506
+ }
1507
+ }
1508
+ if (element.alignment) {
1509
+ const alignmentValidation = validateAlignment(element.alignment);
1510
+ errors.push(...alignmentValidation.errors);
1511
+ }
1512
+ if (element.width && element.width <= 0) {
1513
+ errors.push({
1514
+ field: "width",
1515
+ message: "Width must be greater than 0",
1516
+ value: element.width
1517
+ });
1518
+ }
1519
+ if (element.height && element.height <= 0) {
1520
+ errors.push({
1521
+ field: "height",
1522
+ message: "Height must be greater than 0",
1523
+ value: element.height
1524
+ });
1525
+ }
1526
+ return {
1527
+ valid: errors.length === 0,
1528
+ errors
1529
+ };
1530
+ }
1531
+ function validateProductSpec(spec) {
1532
+ const errors = [];
1533
+ if (!spec.productId) {
1534
+ errors.push({
1535
+ field: "productId",
1536
+ message: "Product ID is required",
1537
+ value: spec
1538
+ });
1539
+ }
1540
+ if (!spec.mockupId) {
1541
+ errors.push({
1542
+ field: "mockupId",
1543
+ message: "Mockup ID is required",
1544
+ value: spec
1545
+ });
1546
+ }
1547
+ if (!spec.variantId) {
1548
+ errors.push({
1549
+ field: "variantId",
1550
+ message: "Variant ID is required",
1551
+ value: spec
1552
+ });
1553
+ }
1554
+ if (!spec.width || spec.width <= 0) {
1555
+ errors.push({
1556
+ field: "width",
1557
+ message: "Width must be greater than 0",
1558
+ value: spec.width
1559
+ });
1560
+ } else if (spec.width > 4e3) {
1561
+ errors.push({
1562
+ field: "width",
1563
+ message: "Width cannot exceed 4000 pixels",
1564
+ value: spec.width
1565
+ });
1566
+ }
1567
+ return {
1568
+ valid: errors.length === 0,
1569
+ errors
1570
+ };
1571
+ }
1572
+ function validateEffects(effects) {
1573
+ const errors = [];
1574
+ if (effects.grain !== void 0) {
1575
+ if (effects.grain !== 1 && effects.grain !== 2) {
1576
+ errors.push({
1577
+ field: "grain",
1578
+ message: "Grain effect must be 1 (light) or 2 (heavy)",
1579
+ value: effects.grain
1580
+ });
1581
+ }
1582
+ }
1583
+ return {
1584
+ valid: errors.length === 0,
1585
+ errors
1586
+ };
1587
+ }
1588
+ function validateMockupOptions(options) {
1589
+ const errors = [];
1590
+ const productValidation = validateProductSpec(options.product);
1591
+ errors.push(...productValidation.errors);
1592
+ if (!options.design || options.design.length === 0) {
1593
+ errors.push({
1594
+ field: "design",
1595
+ message: "At least one design element is required",
1596
+ value: options.design
1597
+ });
1598
+ } else {
1599
+ options.design.forEach((element, index) => {
1600
+ const elementValidation = validateDesignElement(element);
1601
+ elementValidation.errors.forEach((error) => {
1602
+ errors.push({
1603
+ ...error,
1604
+ field: `design[${index}].${error.field}`
1605
+ });
1606
+ });
1607
+ });
1608
+ }
1609
+ if (options.effects) {
1610
+ const effectsValidation = validateEffects(options.effects);
1611
+ errors.push(...effectsValidation.errors);
1612
+ }
1613
+ return {
1614
+ valid: errors.length === 0,
1615
+ errors
1616
+ };
1617
+ }
1618
+ function formatValidationErrors(errors) {
1619
+ return errors.map((e) => `${e.field}: ${e.message}`).join("\n");
1620
+ }
1621
+ function isValidAlignment(value) {
1622
+ const validAlignments = [
1623
+ "center",
1624
+ "top",
1625
+ "far-top",
1626
+ "bottom",
1627
+ "far-bottom",
1628
+ "left",
1629
+ "far-left",
1630
+ "right",
1631
+ "far-right"
1632
+ ];
1633
+ return validAlignments.includes(value);
1634
+ }
1635
+ function isValidTileCount(value) {
1636
+ const validTiles = [0.25, 0.5, 1, 2, 4];
1637
+ return validTiles.includes(value);
1638
+ }
1639
+
1640
+ // src/cart/operations.ts
1641
+ function createCartDetail(options) {
1642
+ const { productId, quantity = 1, selection, combinations, attributes } = options;
1643
+ if (!validateProductSelection(attributes, selection, combinations)) {
1644
+ return null;
1645
+ }
1646
+ const bestCombination = findBestCombination(selection, combinations, attributes);
1647
+ return {
1648
+ productId,
1649
+ variantId: bestCombination?.variantId,
1650
+ quantity,
1651
+ selection,
1652
+ price: bestCombination?.price,
1653
+ attributes: Object.fromEntries(
1654
+ Object.entries(selection).map(([key, value]) => {
1655
+ const attr = attributes[key];
1656
+ return [key, {
1657
+ value,
1658
+ affectsCombinations: attr?.affectsCombinations !== false,
1659
+ type: attr?.type
1660
+ }];
1661
+ })
1662
+ )
1663
+ };
1664
+ }
1665
+ function findVariantForSelection(selection, combinations, attributes) {
1666
+ const bestCombination = findBestCombination(selection, combinations, attributes);
1667
+ return bestCombination?.variantId;
1668
+ }
1669
+ function createAddToCartEvent(cartDetail) {
1670
+ return {
1671
+ detail: {
1672
+ productId: cartDetail.productId,
1673
+ variantId: cartDetail.variantId,
1674
+ quantity: cartDetail.quantity,
1675
+ selection: cartDetail.selection,
1676
+ price: cartDetail.price,
1677
+ timestamp: Date.now()
1678
+ },
1679
+ bubbles: true,
1680
+ composed: true,
1681
+ cancelable: true
1682
+ };
1683
+ }
1684
+ async function simulateCartOperation(duration = 1e3) {
1685
+ return new Promise((resolve) => setTimeout(resolve, duration));
1686
+ }
1687
+ function createAddToCartHandler(options) {
1688
+ return async function handleAddToCart(productId, selection, combinations, attributes, quantity = 1) {
1689
+ try {
1690
+ if (options.onValidate && !options.onValidate()) {
1691
+ return null;
1692
+ }
1693
+ const cartDetail = createCartDetail({
1694
+ productId,
1695
+ quantity,
1696
+ selection,
1697
+ combinations,
1698
+ attributes
1699
+ });
1700
+ if (!cartDetail) {
1701
+ throw new Error("Invalid selection");
1702
+ }
1703
+ options.onStart?.();
1704
+ if (options.simulateDelay) {
1705
+ await simulateCartOperation(options.simulateDelay);
1706
+ }
1707
+ options.onSuccess?.(cartDetail);
1708
+ return cartDetail;
1709
+ } catch (error) {
1710
+ options.onError?.(error);
1711
+ return null;
1712
+ }
1713
+ };
1714
+ }
1715
+
1716
+ // src/error/handling.ts
1717
+ var ErrorManager = class {
1718
+ constructor(context, onError, onClear) {
1719
+ this.context = context;
1720
+ this.onError = onError;
1721
+ this.onClear = onClear;
1722
+ }
1723
+ lastError = null;
1724
+ errorCount = 0;
1725
+ maxRetries = 3;
1726
+ handle(error) {
1727
+ this.lastError = error;
1728
+ this.errorCount++;
1729
+ if (typeof process !== "undefined" && process.env?.NODE_ENV === "development") {
1730
+ console.error(`[${this.context.component}] ${this.context.operation}:`, error);
1731
+ if (this.context.metadata) {
1732
+ console.error("Error metadata:", this.context.metadata);
1733
+ }
1734
+ }
1735
+ this.onError?.(error, this.context);
1736
+ }
1737
+ async handleAsync(error) {
1738
+ this.handle(error);
1739
+ }
1740
+ clear() {
1741
+ this.lastError = null;
1742
+ this.errorCount = 0;
1743
+ this.onClear?.();
1744
+ }
1745
+ getLastError() {
1746
+ return this.lastError;
1747
+ }
1748
+ canRetry() {
1749
+ return this.context.retryable !== false && this.errorCount < this.maxRetries;
1750
+ }
1751
+ getUserMessage() {
1752
+ if (this.context.userMessage) {
1753
+ return this.context.userMessage;
1754
+ }
1755
+ const operationMessages = {
1756
+ "fetch-product": "Unable to load product information",
1757
+ "generate-mockup": "Unable to generate product preview",
1758
+ "add-to-cart": "Unable to add item to cart",
1759
+ "update-selection": "Unable to update selection",
1760
+ "load-image": "Unable to load image"
1761
+ };
1762
+ return operationMessages[this.context.operation] || "An error occurred";
1763
+ }
1764
+ };
1765
+ function createErrorHandler(context, callbacks) {
1766
+ const manager = new ErrorManager(context, callbacks?.onError, callbacks?.onClear);
1767
+ return {
1768
+ handle: (error) => manager.handle(error),
1769
+ handleAsync: (error) => manager.handleAsync(error),
1770
+ clear: () => manager.clear(),
1771
+ getLastError: () => manager.getLastError(),
1772
+ canRetry: () => manager.canRetry()
1773
+ };
1774
+ }
1775
+ async function withErrorHandling(operation, errorHandler, options) {
1776
+ try {
1777
+ errorHandler.clear();
1778
+ return await operation();
1779
+ } catch (error) {
1780
+ errorHandler.handle(error);
1781
+ if (options?.retry && errorHandler.canRetry()) {
1782
+ if (options.retryDelay) {
1783
+ await new Promise((resolve) => setTimeout(resolve, options.retryDelay));
1784
+ }
1785
+ return withErrorHandling(operation, errorHandler, {
1786
+ ...options,
1787
+ retry: false
1788
+ // Prevent infinite recursion
1789
+ });
1790
+ }
1791
+ if (options?.fallback !== void 0) {
1792
+ return options.fallback;
1793
+ }
1794
+ return null;
1795
+ }
1796
+ }
1797
+ function withSyncErrorHandling(operation, errorHandler, fallback) {
1798
+ try {
1799
+ errorHandler.clear();
1800
+ return operation();
1801
+ } catch (error) {
1802
+ errorHandler.handle(error);
1803
+ return fallback !== void 0 ? fallback : null;
1804
+ }
1805
+ }
1806
+ async function retryOperation(operation, options = {}) {
1807
+ const maxAttempts = options.maxAttempts || 3;
1808
+ const baseDelay = options.delay || 1e3;
1809
+ let lastError;
1810
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
1811
+ try {
1812
+ return await operation();
1813
+ } catch (error) {
1814
+ lastError = error;
1815
+ if (attempt < maxAttempts) {
1816
+ options.onRetry?.(attempt, lastError);
1817
+ const delay = options.backoff ? baseDelay * Math.pow(2, attempt - 1) : baseDelay;
1818
+ await new Promise((resolve) => setTimeout(resolve, delay));
1819
+ }
1820
+ }
1821
+ }
1822
+ throw lastError;
1823
+ }
1824
+
1825
+ // src/context/synchronization.ts
1826
+ var ContextSynchronizer = class {
1827
+ constructor(options = {}) {
1828
+ this.options = options;
1829
+ this.batchDelay = options.batchDelay || 0;
1830
+ }
1831
+ subscribers = /* @__PURE__ */ new Set();
1832
+ currentContext;
1833
+ previousContext;
1834
+ updateTimer;
1835
+ pendingUpdate;
1836
+ batchDelay;
1837
+ /**
1838
+ * Subscribe to context changes
1839
+ */
1840
+ subscribe(callback) {
1841
+ this.subscribers.add(callback);
1842
+ if (this.currentContext !== void 0) {
1843
+ callback(this.currentContext, this.previousContext);
1844
+ }
1845
+ return () => {
1846
+ this.subscribers.delete(callback);
1847
+ };
1848
+ }
1849
+ /**
1850
+ * Update context with optional batching
1851
+ */
1852
+ updateContext(context) {
1853
+ if (this.hasContextChanged(this.currentContext, context)) {
1854
+ if (this.batchDelay > 0) {
1855
+ this.scheduleBatchedUpdate(context);
1856
+ } else {
1857
+ this.applyContextUpdate(context);
1858
+ }
1859
+ }
1860
+ }
1861
+ /**
1862
+ * Force immediate context update (bypasses batching)
1863
+ */
1864
+ forceUpdate(context) {
1865
+ this.clearPendingUpdate();
1866
+ this.applyContextUpdate(context);
1867
+ }
1868
+ /**
1869
+ * Get current context
1870
+ */
1871
+ getContext() {
1872
+ return this.currentContext;
1873
+ }
1874
+ /**
1875
+ * Clear all subscribers
1876
+ */
1877
+ clear() {
1878
+ this.clearPendingUpdate();
1879
+ this.subscribers.clear();
1880
+ this.previousContext = this.currentContext;
1881
+ this.currentContext = void 0;
1882
+ }
1883
+ hasContextChanged(prev, next) {
1884
+ if (prev === void 0) return true;
1885
+ if (prev === next) return false;
1886
+ if (this.options.comparator) {
1887
+ return !this.options.comparator(prev, next);
1888
+ }
1889
+ if (typeof prev === "object" && typeof next === "object") {
1890
+ return !this.deepEqual(prev, next);
1891
+ }
1892
+ return prev !== next;
1893
+ }
1894
+ scheduleBatchedUpdate(context) {
1895
+ this.pendingUpdate = context;
1896
+ if (this.updateTimer) {
1897
+ clearTimeout(this.updateTimer);
1898
+ }
1899
+ this.updateTimer = setTimeout(() => {
1900
+ if (this.pendingUpdate !== void 0) {
1901
+ this.applyContextUpdate(this.pendingUpdate);
1902
+ this.pendingUpdate = void 0;
1903
+ }
1904
+ this.updateTimer = void 0;
1905
+ }, this.batchDelay);
1906
+ }
1907
+ clearPendingUpdate() {
1908
+ if (this.updateTimer) {
1909
+ clearTimeout(this.updateTimer);
1910
+ this.updateTimer = void 0;
1911
+ }
1912
+ this.pendingUpdate = void 0;
1913
+ }
1914
+ applyContextUpdate(context) {
1915
+ this.previousContext = this.currentContext;
1916
+ this.currentContext = context;
1917
+ if (this.options.debug) {
1918
+ }
1919
+ this.options.onUpdate?.(context);
1920
+ this.subscribers.forEach((subscriber) => {
1921
+ try {
1922
+ subscriber(context, this.previousContext);
1923
+ } catch (error) {
1924
+ console.error("[ContextSynchronizer] Subscriber error:", error);
1925
+ }
1926
+ });
1927
+ }
1928
+ deepEqual(obj1, obj2) {
1929
+ if (obj1 === obj2) return true;
1930
+ if (obj1 == null || obj2 == null) return false;
1931
+ if (typeof obj1 !== "object" || typeof obj2 !== "object") return false;
1932
+ const keys1 = Object.keys(obj1);
1933
+ const keys2 = Object.keys(obj2);
1934
+ if (keys1.length !== keys2.length) return false;
1935
+ for (const key of keys1) {
1936
+ if (!keys2.includes(key)) return false;
1937
+ if (!this.deepEqual(obj1[key], obj2[key])) return false;
1938
+ }
1939
+ return true;
1940
+ }
1941
+ };
1942
+ var ContextBridge = class {
1943
+ synchronizers = /* @__PURE__ */ new Map();
1944
+ /**
1945
+ * Register a synchronizer with a key
1946
+ */
1947
+ register(key, synchronizer) {
1948
+ this.synchronizers.set(key, synchronizer);
1949
+ }
1950
+ /**
1951
+ * Bridge context updates between synchronizers
1952
+ */
1953
+ bridge(fromKey, toKey) {
1954
+ const from = this.synchronizers.get(fromKey);
1955
+ const to = this.synchronizers.get(toKey);
1956
+ if (!from || !to) {
1957
+ throw new Error(`Cannot bridge: synchronizer not found`);
1958
+ }
1959
+ return from.subscribe((context) => {
1960
+ to.updateContext(context);
1961
+ });
1962
+ }
1963
+ /**
1964
+ * Broadcast context to all registered synchronizers
1965
+ */
1966
+ broadcast(context, excludeKey) {
1967
+ this.synchronizers.forEach((sync, key) => {
1968
+ if (key !== excludeKey) {
1969
+ sync.updateContext(context);
1970
+ }
1971
+ });
1972
+ }
1973
+ };
1974
+ function createContextProvider(initialContext, options) {
1975
+ const synchronizer = new ContextSynchronizer(options);
1976
+ if (initialContext) {
1977
+ synchronizer.updateContext(initialContext);
1978
+ }
1979
+ return {
1980
+ subscribe: (callback) => synchronizer.subscribe(callback),
1981
+ update: (context) => synchronizer.updateContext(context),
1982
+ get: () => synchronizer.getContext(),
1983
+ clear: () => synchronizer.clear()
1984
+ };
1985
+ }
1986
+
1987
+ // src/framework/events.ts
1988
+ var EventEmitter = class {
1989
+ listeners = /* @__PURE__ */ new Map();
1990
+ onceListeners = /* @__PURE__ */ new Map();
1991
+ /**
1992
+ * Add event listener
1993
+ */
1994
+ on(event, listener) {
1995
+ const listeners = this.listeners.get(event) || /* @__PURE__ */ new Set();
1996
+ listeners.add(listener);
1997
+ this.listeners.set(event, listeners);
1998
+ return () => this.off(event, listener);
1999
+ }
2000
+ /**
2001
+ * Add one-time event listener
2002
+ */
2003
+ once(event, listener) {
2004
+ const listeners = this.onceListeners.get(event) || /* @__PURE__ */ new Set();
2005
+ listeners.add(listener);
2006
+ this.onceListeners.set(event, listeners);
2007
+ return () => this.off(event, listener);
2008
+ }
2009
+ /**
2010
+ * Remove event listener
2011
+ */
2012
+ off(event, listener) {
2013
+ this.listeners.get(event)?.delete(listener);
2014
+ this.onceListeners.get(event)?.delete(listener);
2015
+ }
2016
+ /**
2017
+ * Emit event
2018
+ */
2019
+ emit(event, payload) {
2020
+ const eventPayload = {
2021
+ ...payload,
2022
+ timestamp: Date.now()
2023
+ };
2024
+ const listeners = this.listeners.get(event);
2025
+ if (listeners) {
2026
+ listeners.forEach((listener) => listener(eventPayload));
2027
+ }
2028
+ const onceListeners = this.onceListeners.get(event);
2029
+ if (onceListeners) {
2030
+ onceListeners.forEach((listener) => listener(eventPayload));
2031
+ this.onceListeners.delete(event);
2032
+ }
2033
+ return !!(listeners?.size || onceListeners?.size);
2034
+ }
2035
+ /**
2036
+ * Remove all listeners for an event
2037
+ */
2038
+ removeAllListeners(event) {
2039
+ if (event) {
2040
+ this.listeners.delete(event);
2041
+ this.onceListeners.delete(event);
2042
+ } else {
2043
+ this.listeners.clear();
2044
+ this.onceListeners.clear();
2045
+ }
2046
+ }
2047
+ /**
2048
+ * Get listener count
2049
+ */
2050
+ listenerCount(event) {
2051
+ const regular = this.listeners.get(event)?.size || 0;
2052
+ const once = this.onceListeners.get(event)?.size || 0;
2053
+ return regular + once;
2054
+ }
2055
+ };
2056
+ var ComponentEventManager = class {
2057
+ emitter = new EventEmitter();
2058
+ definitions = /* @__PURE__ */ new Map();
2059
+ element;
2060
+ constructor(element) {
2061
+ this.element = element;
2062
+ }
2063
+ /**
2064
+ * Define an event
2065
+ */
2066
+ define(definition) {
2067
+ this.definitions.set(definition.name, definition);
2068
+ }
2069
+ /**
2070
+ * Emit an event (works for both DOM and synthetic events)
2071
+ */
2072
+ emit(name, detail) {
2073
+ const definition = this.definitions.get(name) || { name };
2074
+ const handled = this.emitter.emit(name, { detail });
2075
+ if (this.element) {
2076
+ const event = new CustomEvent(name, {
2077
+ detail,
2078
+ bubbles: definition.bubbles !== false,
2079
+ cancelable: definition.cancelable !== false,
2080
+ composed: definition.composed !== false
2081
+ });
2082
+ this.element.dispatchEvent(event);
2083
+ }
2084
+ return handled;
2085
+ }
2086
+ /**
2087
+ * Listen to an event
2088
+ */
2089
+ on(event, listener) {
2090
+ return this.emitter.on(event, listener);
2091
+ }
2092
+ /**
2093
+ * Listen to an event once
2094
+ */
2095
+ once(event, listener) {
2096
+ return this.emitter.once(event, listener);
2097
+ }
2098
+ /**
2099
+ * Remove listener
2100
+ */
2101
+ off(event, listener) {
2102
+ this.emitter.off(event, listener);
2103
+ }
2104
+ /**
2105
+ * Set DOM element for event dispatching
2106
+ */
2107
+ setElement(element) {
2108
+ this.element = element;
2109
+ }
2110
+ /**
2111
+ * Get all defined events
2112
+ */
2113
+ getDefinitions() {
2114
+ return Array.from(this.definitions.values());
2115
+ }
2116
+ };
2117
+ var StandardEvents = {
2118
+ // Lifecycle events
2119
+ mounted: { name: "mounted", bubbles: false },
2120
+ unmounted: { name: "unmounted", bubbles: false },
2121
+ updated: { name: "updated", bubbles: false },
2122
+ // User interaction events
2123
+ change: { name: "change", bubbles: true },
2124
+ input: { name: "input", bubbles: true },
2125
+ click: { name: "click", bubbles: true },
2126
+ focus: { name: "focus", bubbles: false },
2127
+ blur: { name: "blur", bubbles: false },
2128
+ // Product-specific events
2129
+ selectionChange: { name: "selection-change", bubbles: true },
2130
+ priceUpdate: { name: "price-update", bubbles: true },
2131
+ addToCart: { name: "add-to-cart", bubbles: true },
2132
+ variantChange: { name: "variant-change", bubbles: true },
2133
+ // Context events
2134
+ contextRequest: { name: "context-request", bubbles: true },
2135
+ contextProvide: { name: "context-provide", bubbles: false },
2136
+ contextUpdate: { name: "context-update", bubbles: true }
2137
+ };
2138
+ var EventDelegator = class {
2139
+ element;
2140
+ handlers = /* @__PURE__ */ new Map();
2141
+ constructor(element) {
2142
+ this.element = element;
2143
+ }
2144
+ /**
2145
+ * Add delegated event listener
2146
+ */
2147
+ delegate(eventType, selector, handler) {
2148
+ const typeHandlers = this.handlers.get(eventType) || /* @__PURE__ */ new Map();
2149
+ const delegatedHandler = (event) => {
2150
+ const target = event.target;
2151
+ const matches = target.matches(selector) || target.closest(selector);
2152
+ if (matches) {
2153
+ handler({
2154
+ detail: event.detail,
2155
+ target,
2156
+ currentTarget: this.element,
2157
+ timestamp: Date.now()
2158
+ });
2159
+ }
2160
+ };
2161
+ typeHandlers.set(selector, delegatedHandler);
2162
+ this.handlers.set(eventType, typeHandlers);
2163
+ this.element.addEventListener(eventType, delegatedHandler);
2164
+ return () => {
2165
+ this.element.removeEventListener(eventType, delegatedHandler);
2166
+ typeHandlers.delete(selector);
2167
+ };
2168
+ }
2169
+ /**
2170
+ * Remove all delegated listeners
2171
+ */
2172
+ destroy() {
2173
+ for (const [eventType, typeHandlers] of this.handlers) {
2174
+ for (const handler of typeHandlers.values()) {
2175
+ this.element.removeEventListener(eventType, handler);
2176
+ }
2177
+ }
2178
+ this.handlers.clear();
2179
+ }
2180
+ };
2181
+ function createEventManager(element) {
2182
+ return new ComponentEventManager(element);
2183
+ }
2184
+
2185
+ // src/context/product.ts
2186
+ var ProductContextManager = class extends EventEmitter {
2187
+ context = {
2188
+ optionAttributes: {},
2189
+ combinations: [],
2190
+ selection: {},
2191
+ loading: false,
2192
+ error: null
2193
+ };
2194
+ updateQueue = [];
2195
+ updateTimer = null;
2196
+ /**
2197
+ * Get current context
2198
+ */
2199
+ getContext() {
2200
+ return { ...this.context };
2201
+ }
2202
+ /**
2203
+ * Set product data
2204
+ */
2205
+ setProduct(product) {
2206
+ if (!product) {
2207
+ this.updateContext({
2208
+ product: void 0,
2209
+ optionAttributes: {},
2210
+ combinations: [],
2211
+ selection: {}
2212
+ });
2213
+ return;
2214
+ }
2215
+ const optionAttributes = toOptionAttributes(product);
2216
+ const combinations = toCombinations(product);
2217
+ const selection = this.deriveSelection(optionAttributes, combinations);
2218
+ this.updateContext({
2219
+ product,
2220
+ optionAttributes,
2221
+ combinations,
2222
+ selection,
2223
+ loading: false,
2224
+ error: null
2225
+ });
2226
+ this.emit("product-load", { detail: product });
2227
+ }
2228
+ /**
2229
+ * Update selection
2230
+ */
2231
+ updateSelection(selection) {
2232
+ const completeSelection = this.deriveSelection(
2233
+ this.context.optionAttributes,
2234
+ this.context.combinations,
2235
+ selection
2236
+ );
2237
+ this.updateContext({ selection: completeSelection });
2238
+ this.emit("selection-change", { detail: completeSelection });
2239
+ }
2240
+ /**
2241
+ * Set loading state
2242
+ */
2243
+ setLoading(loading) {
2244
+ this.updateContext({ loading });
2245
+ this.emit("loading-change", { detail: loading });
2246
+ }
2247
+ /**
2248
+ * Set error state
2249
+ */
2250
+ setError(error) {
2251
+ this.updateContext({ error, loading: false });
2252
+ this.emit("product-error", { detail: error });
2253
+ }
2254
+ /**
2255
+ * Update context with batching
2256
+ */
2257
+ updateContext(updates) {
2258
+ this.updateQueue.push(updates);
2259
+ if (this.updateTimer) {
2260
+ clearTimeout(this.updateTimer);
2261
+ }
2262
+ this.updateTimer = setTimeout(() => {
2263
+ this.processBatchUpdate();
2264
+ }, 0);
2265
+ }
2266
+ /**
2267
+ * Process batched updates
2268
+ */
2269
+ processBatchUpdate() {
2270
+ const merged = {};
2271
+ for (const update of this.updateQueue) {
2272
+ Object.assign(merged, update);
2273
+ }
2274
+ this.context = { ...this.context, ...merged };
2275
+ this.updateQueue = [];
2276
+ this.updateTimer = null;
2277
+ this.emit("context-update", { detail: this.context });
2278
+ }
2279
+ /**
2280
+ * Derive complete selection
2281
+ */
2282
+ deriveSelection(attributes, combinations, partial) {
2283
+ if (!attributes || Object.keys(attributes).length === 0) {
2284
+ return {};
2285
+ }
2286
+ const defaultSelection = deriveDefaultSelection(attributes, combinations);
2287
+ if (partial) {
2288
+ return { ...defaultSelection, ...partial };
2289
+ }
2290
+ return defaultSelection;
2291
+ }
2292
+ /**
2293
+ * Subscribe to context updates
2294
+ */
2295
+ subscribe(callback) {
2296
+ return this.on("context-update", (event) => callback(event.detail));
2297
+ }
2298
+ /**
2299
+ * Reset context
2300
+ */
2301
+ reset() {
2302
+ this.context = {
2303
+ optionAttributes: {},
2304
+ combinations: [],
2305
+ selection: {},
2306
+ loading: false,
2307
+ error: null
2308
+ };
2309
+ this.updateQueue = [];
2310
+ if (this.updateTimer) {
2311
+ clearTimeout(this.updateTimer);
2312
+ this.updateTimer = null;
2313
+ }
2314
+ this.removeAllListeners();
2315
+ }
2316
+ };
2317
+ function createProductContext() {
2318
+ return new ProductContextManager();
2319
+ }
2320
+ var ProductLoader = class {
2321
+ context;
2322
+ fetcher;
2323
+ cache = /* @__PURE__ */ new Map();
2324
+ currentRequest;
2325
+ constructor(context, fetcher) {
2326
+ this.context = context;
2327
+ this.fetcher = fetcher;
2328
+ }
2329
+ /**
2330
+ * Load product by ID
2331
+ */
2332
+ async loadProduct(productId, options) {
2333
+ const cached = this.cache.get(productId);
2334
+ if (cached) {
2335
+ this.context.setProduct(cached);
2336
+ return;
2337
+ }
2338
+ this.context.setLoading(true);
2339
+ this.currentRequest = void 0;
2340
+ const fetchRequest = this.fetchProduct(productId, options);
2341
+ this.currentRequest = fetchRequest;
2342
+ try {
2343
+ await fetchRequest;
2344
+ } catch (error) {
2345
+ if (this.currentRequest === fetchRequest) {
2346
+ this.context.setError(error);
2347
+ }
2348
+ }
2349
+ }
2350
+ /**
2351
+ * Fetch product
2352
+ */
2353
+ async fetchProduct(productId, options) {
2354
+ if (!this.fetcher) {
2355
+ throw new Error("No fetcher configured");
2356
+ }
2357
+ const product = await this.fetcher.getProduct(productId, options);
2358
+ this.cache.set(productId, product);
2359
+ this.context.setProduct(product);
2360
+ }
2361
+ /**
2362
+ * Load first available product
2363
+ */
2364
+ async loadFirstProduct(options) {
2365
+ this.context.setLoading(true);
2366
+ try {
2367
+ if (!this.fetcher) {
2368
+ throw new Error("No fetcher configured");
2369
+ }
2370
+ const { items } = await this.fetcher.listProducts(options);
2371
+ const product = items?.[0];
2372
+ if (product) {
2373
+ const productId = product.id || product.productId;
2374
+ if (productId) {
2375
+ this.cache.set(productId, product);
2376
+ }
2377
+ this.context.setProduct(product);
2378
+ } else {
2379
+ throw new Error("No products available");
2380
+ }
2381
+ } catch (error) {
2382
+ this.context.setError(error);
2383
+ }
2384
+ }
2385
+ /**
2386
+ * Clear cache
2387
+ */
2388
+ clearCache() {
2389
+ this.cache.clear();
2390
+ }
2391
+ /**
2392
+ * Set fetcher
2393
+ */
2394
+ setFetcher(fetcher) {
2395
+ this.fetcher = fetcher;
2396
+ }
2397
+ };
2398
+ function createProductLoader(context, fetcher) {
2399
+ return new ProductLoader(context, fetcher);
2400
+ }
2401
+
2402
+ // src/context/provider.ts
2403
+ var UniversalContextProvider = class extends EventEmitter {
2404
+ contextManager;
2405
+ loader;
2406
+ config;
2407
+ consumers = /* @__PURE__ */ new Set();
2408
+ initialized = false;
2409
+ constructor(config2 = {}) {
2410
+ super();
2411
+ this.config = config2;
2412
+ this.contextManager = new ProductContextManager();
2413
+ this.loader = new ProductLoader(this.contextManager, config2.fetcher);
2414
+ this.setupSubscriptions();
2415
+ }
2416
+ /**
2417
+ * Initialize the provider
2418
+ */
2419
+ async initialize() {
2420
+ if (this.initialized) return;
2421
+ this.initialized = true;
2422
+ if (this.config.autoLoad !== false) {
2423
+ await this.loadProduct();
2424
+ }
2425
+ }
2426
+ /**
2427
+ * Load product data
2428
+ */
2429
+ async loadProduct(productId) {
2430
+ const id = productId || this.config.productId;
2431
+ if (id) {
2432
+ await this.loader.loadProduct(id, {
2433
+ baseUrl: this.config.endpoint,
2434
+ mode: this.config.mode
2435
+ });
2436
+ } else {
2437
+ await this.loader.loadFirstProduct({
2438
+ baseUrl: this.config.endpoint,
2439
+ mode: this.config.mode
2440
+ });
2441
+ }
2442
+ }
2443
+ /**
2444
+ * Get current context
2445
+ */
2446
+ getContext() {
2447
+ return this.contextManager.getContext();
2448
+ }
2449
+ /**
2450
+ * Update selection
2451
+ */
2452
+ updateSelection(selection) {
2453
+ this.contextManager.updateSelection(selection);
2454
+ }
2455
+ /**
2456
+ * Register a consumer
2457
+ */
2458
+ registerConsumer(consumer) {
2459
+ this.consumers.add(consumer);
2460
+ const context = this.getContext();
2461
+ consumer.onContextUpdate(context);
2462
+ if (context.loading && consumer.onContextLoading) {
2463
+ consumer.onContextLoading(true);
2464
+ }
2465
+ if (context.error && consumer.onContextError) {
2466
+ consumer.onContextError(context.error);
2467
+ }
2468
+ return () => {
2469
+ this.consumers.delete(consumer);
2470
+ };
2471
+ }
2472
+ /**
2473
+ * Setup internal subscriptions
2474
+ */
2475
+ setupSubscriptions() {
2476
+ this.contextManager.on("context-update", (event) => {
2477
+ const context = event.detail;
2478
+ this.consumers.forEach((consumer) => {
2479
+ consumer.onContextUpdate(context);
2480
+ });
2481
+ this.emit("context-update", { detail: context });
2482
+ });
2483
+ this.contextManager.on("loading-change", (event) => {
2484
+ const loading = event.detail;
2485
+ this.consumers.forEach((consumer) => {
2486
+ if (consumer.onContextLoading) {
2487
+ consumer.onContextLoading(loading);
2488
+ }
2489
+ });
2490
+ this.emit("loading-change", { detail: loading });
2491
+ });
2492
+ this.contextManager.on("product-error", (event) => {
2493
+ const error = event.detail;
2494
+ this.consumers.forEach((consumer) => {
2495
+ if (consumer.onContextError) {
2496
+ consumer.onContextError(error);
2497
+ }
2498
+ });
2499
+ this.emit("error", { detail: error });
2500
+ });
2501
+ }
2502
+ /**
2503
+ * Destroy the provider
2504
+ */
2505
+ destroy() {
2506
+ this.consumers.clear();
2507
+ this.contextManager.reset();
2508
+ this.removeAllListeners();
2509
+ }
2510
+ };
2511
+ var ContextInjector = class {
2512
+ /**
2513
+ * React context injection
2514
+ */
2515
+ static forReact(provider) {
2516
+ return {
2517
+ Provider: ({ children, value }) => {
2518
+ return { provider, children, value };
2519
+ },
2520
+ Consumer: ({ children }) => {
2521
+ return { provider, children };
2522
+ }
2523
+ };
2524
+ }
2525
+ /**
2526
+ * Vue context injection
2527
+ */
2528
+ static forVue(provider) {
2529
+ return {
2530
+ install(app) {
2531
+ app.provide("merchifyContext", provider);
2532
+ },
2533
+ inject() {
2534
+ return provider;
2535
+ }
2536
+ };
2537
+ }
2538
+ /**
2539
+ * Svelte context injection
2540
+ */
2541
+ static forSvelte(provider) {
2542
+ return {
2543
+ setContext(key) {
2544
+ return { key, provider };
2545
+ },
2546
+ getContext(key) {
2547
+ return provider;
2548
+ }
2549
+ };
2550
+ }
2551
+ /**
2552
+ * Web Components context injection
2553
+ */
2554
+ static forWebComponents(provider) {
2555
+ return {
2556
+ // Attach to element
2557
+ attach(element) {
2558
+ element.__contextProvider = provider;
2559
+ element.addEventListener("merchify:request-context", (event) => {
2560
+ event.detail.context = provider.getContext();
2561
+ });
2562
+ },
2563
+ // Get from parent
2564
+ getFromParent(element) {
2565
+ let parent = element.parentElement;
2566
+ while (parent) {
2567
+ if (parent.__contextProvider) {
2568
+ return parent.__contextProvider.getContext();
2569
+ }
2570
+ parent = parent.parentElement;
2571
+ }
2572
+ return void 0;
2573
+ }
2574
+ };
2575
+ }
2576
+ };
2577
+ function createUniversalProvider(config2) {
2578
+ return new UniversalContextProvider(config2);
2579
+ }
2580
+ function withContext(Base) {
2581
+ return class extends Base {
2582
+ context;
2583
+ contextUnsubscribe;
2584
+ connectedCallback() {
2585
+ super.connectedCallback?.();
2586
+ const provider = this.findProvider();
2587
+ if (provider) {
2588
+ this.contextUnsubscribe = provider.registerConsumer(this);
2589
+ }
2590
+ }
2591
+ disconnectedCallback() {
2592
+ super.disconnectedCallback?.();
2593
+ if (this.contextUnsubscribe) {
2594
+ this.contextUnsubscribe();
2595
+ }
2596
+ }
2597
+ onContextUpdate(context) {
2598
+ this.context = context;
2599
+ this.requestUpdate?.();
2600
+ }
2601
+ onContextError(error) {
2602
+ console.error("Context error:", error);
2603
+ }
2604
+ onContextLoading(loading) {
2605
+ }
2606
+ findProvider() {
2607
+ return void 0;
2608
+ }
2609
+ };
2610
+ }
2611
+
2612
+ // src/dom/utils.ts
2613
+ var ClassNames = {
2614
+ /**
2615
+ * Combine class names conditionally
2616
+ */
2617
+ combine(...args) {
2618
+ const classes = [];
2619
+ for (const arg of args) {
2620
+ if (!arg) continue;
2621
+ if (typeof arg === "string") {
2622
+ classes.push(arg);
2623
+ } else if (typeof arg === "object") {
2624
+ for (const [className, condition] of Object.entries(arg)) {
2625
+ if (condition) {
2626
+ classes.push(className);
2627
+ }
2628
+ }
2629
+ }
2630
+ }
2631
+ return classes.join(" ");
2632
+ },
2633
+ /**
2634
+ * Toggle a class on an element
2635
+ */
2636
+ toggle(element, className, force) {
2637
+ if (force !== void 0) {
2638
+ if (force) {
2639
+ element.classList.add(className);
2640
+ } else {
2641
+ element.classList.remove(className);
2642
+ }
2643
+ } else {
2644
+ element.classList.toggle(className);
2645
+ }
2646
+ },
2647
+ /**
2648
+ * Add classes with prefix
2649
+ */
2650
+ withPrefix(prefix, ...suffixes) {
2651
+ return suffixes.map((suffix) => `${prefix}${suffix}`).join(" ");
2652
+ }
2653
+ };
2654
+ var Styles = {
2655
+ /**
2656
+ * Merge style objects
2657
+ */
2658
+ merge(...styles) {
2659
+ return Object.assign({}, ...styles.filter(Boolean));
2660
+ },
2661
+ /**
2662
+ * Convert style object to CSS string
2663
+ */
2664
+ toCss(style) {
2665
+ return Object.entries(style).map(([key, value]) => {
2666
+ const cssKey = key.replace(/([A-Z])/g, "-$1").toLowerCase();
2667
+ return `${cssKey}: ${value}`;
2668
+ }).join("; ");
2669
+ },
2670
+ /**
2671
+ * Parse CSS string to style object
2672
+ */
2673
+ fromCss(css) {
2674
+ const style = {};
2675
+ css.split(";").forEach((rule) => {
2676
+ const [key, value] = rule.split(":").map((s) => s.trim());
2677
+ if (key && value) {
2678
+ const jsKey = key.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
2679
+ style[jsKey] = value;
2680
+ }
2681
+ });
2682
+ return style;
2683
+ }
2684
+ };
2685
+ var Attributes = {
2686
+ /**
2687
+ * Set attributes on an element
2688
+ */
2689
+ set(element, attributes) {
2690
+ for (const [key, value] of Object.entries(attributes)) {
2691
+ if (value === false || value === null || value === void 0) {
2692
+ element.removeAttribute(key);
2693
+ } else if (value === true) {
2694
+ element.setAttribute(key, "");
2695
+ } else {
2696
+ element.setAttribute(key, String(value));
2697
+ }
2698
+ }
2699
+ },
2700
+ /**
2701
+ * Get all attributes as an object
2702
+ */
2703
+ getAll(element) {
2704
+ const attrs = {};
2705
+ for (let i = 0; i < element.attributes.length; i++) {
2706
+ const attr = element.attributes[i];
2707
+ attrs[attr.name] = attr.value;
2708
+ }
2709
+ return attrs;
2710
+ },
2711
+ /**
2712
+ * Parse JSON attribute safely
2713
+ */
2714
+ parseJson(element, attributeName, defaultValue) {
2715
+ const value = element.getAttribute(attributeName);
2716
+ if (!value) return defaultValue;
2717
+ try {
2718
+ return JSON.parse(value);
2719
+ } catch {
2720
+ console.warn(`Failed to parse JSON attribute: ${attributeName}`);
2721
+ return defaultValue;
2722
+ }
2723
+ }
2724
+ };
2725
+ var Elements = {
2726
+ /**
2727
+ * Create element with properties
2728
+ */
2729
+ create(tag, props) {
2730
+ const element = document.createElement(tag);
2731
+ if (props?.className) {
2732
+ element.className = props.className;
2733
+ }
2734
+ if (props?.style) {
2735
+ Object.assign(element.style, props.style);
2736
+ }
2737
+ if (props?.attributes) {
2738
+ Attributes.set(element, props.attributes);
2739
+ }
2740
+ if (props?.text) {
2741
+ element.textContent = props.text;
2742
+ }
2743
+ if (props?.html) {
2744
+ element.innerHTML = props.html;
2745
+ }
2746
+ if (props?.children) {
2747
+ props.children.forEach((child) => {
2748
+ if (typeof child === "string") {
2749
+ element.appendChild(document.createTextNode(child));
2750
+ } else {
2751
+ element.appendChild(child);
2752
+ }
2753
+ });
2754
+ }
2755
+ return element;
2756
+ },
2757
+ /**
2758
+ * Create fragment with children
2759
+ */
2760
+ fragment(...children) {
2761
+ const fragment = document.createDocumentFragment();
2762
+ children.forEach((child) => {
2763
+ if (typeof child === "string") {
2764
+ fragment.appendChild(document.createTextNode(child));
2765
+ } else {
2766
+ fragment.appendChild(child);
2767
+ }
2768
+ });
2769
+ return fragment;
2770
+ },
2771
+ /**
2772
+ * Replace element's children
2773
+ */
2774
+ replaceChildren(element, ...children) {
2775
+ element.innerHTML = "";
2776
+ children.forEach((child) => {
2777
+ if (typeof child === "string") {
2778
+ element.appendChild(document.createTextNode(child));
2779
+ } else {
2780
+ element.appendChild(child);
2781
+ }
2782
+ });
2783
+ }
2784
+ };
2785
+ var Focus = {
2786
+ /**
2787
+ * Trap focus within an element
2788
+ */
2789
+ trap(container) {
2790
+ const focusableSelectors = [
2791
+ "a[href]",
2792
+ "button:not([disabled])",
2793
+ "input:not([disabled])",
2794
+ "select:not([disabled])",
2795
+ "textarea:not([disabled])",
2796
+ '[tabindex]:not([tabindex="-1"])'
2797
+ ];
2798
+ const getFocusableElements = () => {
2799
+ return Array.from(
2800
+ container.querySelectorAll(focusableSelectors.join(","))
2801
+ );
2802
+ };
2803
+ const handleKeyDown = (event) => {
2804
+ if (event.key !== "Tab") return;
2805
+ const focusable = getFocusableElements();
2806
+ if (focusable.length === 0) return;
2807
+ const first = focusable[0];
2808
+ const last = focusable[focusable.length - 1];
2809
+ if (event.shiftKey && document.activeElement === first) {
2810
+ event.preventDefault();
2811
+ last.focus();
2812
+ } else if (!event.shiftKey && document.activeElement === last) {
2813
+ event.preventDefault();
2814
+ first.focus();
2815
+ }
2816
+ };
2817
+ container.addEventListener("keydown", handleKeyDown);
2818
+ return () => {
2819
+ container.removeEventListener("keydown", handleKeyDown);
2820
+ };
2821
+ },
2822
+ /**
2823
+ * Restore focus to previous element
2824
+ */
2825
+ restore(previousElement) {
2826
+ if (previousElement && previousElement.focus) {
2827
+ previousElement.focus();
2828
+ }
2829
+ }
2830
+ };
2831
+ var Animations = {
2832
+ /**
2833
+ * Fade in element
2834
+ */
2835
+ fadeIn(element, duration = 300) {
2836
+ return new Promise((resolve) => {
2837
+ element.style.opacity = "0";
2838
+ element.style.transition = `opacity ${duration}ms`;
2839
+ element.offsetHeight;
2840
+ element.style.opacity = "1";
2841
+ setTimeout(resolve, duration);
2842
+ });
2843
+ },
2844
+ /**
2845
+ * Fade out element
2846
+ */
2847
+ fadeOut(element, duration = 300) {
2848
+ return new Promise((resolve) => {
2849
+ element.style.transition = `opacity ${duration}ms`;
2850
+ element.style.opacity = "0";
2851
+ setTimeout(resolve, duration);
2852
+ });
2853
+ },
2854
+ /**
2855
+ * Slide toggle
2856
+ */
2857
+ slideToggle(element, duration = 300) {
2858
+ return new Promise((resolve) => {
2859
+ const isHidden = element.style.maxHeight === "0px" || !element.style.maxHeight;
2860
+ element.style.transition = `max-height ${duration}ms`;
2861
+ element.style.overflow = "hidden";
2862
+ if (isHidden) {
2863
+ element.style.maxHeight = element.scrollHeight + "px";
2864
+ } else {
2865
+ element.style.maxHeight = "0px";
2866
+ }
2867
+ setTimeout(resolve, duration);
2868
+ });
2869
+ }
2870
+ };
2871
+
2872
+ // src/framework/adapter.ts
2873
+ function createComponent(descriptor, adapter) {
2874
+ return adapter.render(descriptor, {});
2875
+ }
2876
+ function defineComponent(definition) {
2877
+ return {
2878
+ name: definition.name,
2879
+ props: definition.props || {},
2880
+ state: definition.state,
2881
+ methods: definition.methods,
2882
+ lifecycle: definition.lifecycle,
2883
+ render: (adapter) => definition.render({}, adapter)
2884
+ };
2885
+ }
2886
+ var AdapterRegistry = class {
2887
+ adapters = /* @__PURE__ */ new Map();
2888
+ register(adapter) {
2889
+ this.adapters.set(adapter.name, adapter);
2890
+ }
2891
+ get(name) {
2892
+ return this.adapters.get(name);
2893
+ }
2894
+ getAll() {
2895
+ return Array.from(this.adapters.values());
2896
+ }
2897
+ };
2898
+ var adapterRegistry = new AdapterRegistry();
2899
+
2900
+ // src/framework/lifecycle.ts
2901
+ var LifecycleManager = class {
2902
+ currentPhase = "created";
2903
+ hooks = /* @__PURE__ */ new Map();
2904
+ transitions = [];
2905
+ listeners = /* @__PURE__ */ new Set();
2906
+ /**
2907
+ * Register a lifecycle hook
2908
+ */
2909
+ addHook(hook) {
2910
+ const phaseHooks = this.hooks.get(hook.phase) || /* @__PURE__ */ new Set();
2911
+ phaseHooks.add(hook);
2912
+ this.hooks.set(hook.phase, phaseHooks);
2913
+ return () => {
2914
+ phaseHooks.delete(hook);
2915
+ };
2916
+ }
2917
+ /**
2918
+ * Transition to a new lifecycle phase
2919
+ */
2920
+ async transitionTo(phase, context) {
2921
+ const startTime = performance.now();
2922
+ const transition = {
2923
+ from: this.currentPhase,
2924
+ to: phase,
2925
+ timestamp: Date.now()
2926
+ };
2927
+ const phaseHooks = Array.from(this.hooks.get(phase) || []).sort((a, b) => (a.priority || 0) - (b.priority || 0));
2928
+ for (const hook of phaseHooks) {
2929
+ try {
2930
+ await hook.handler(context);
2931
+ } catch (error) {
2932
+ console.error(`Lifecycle hook error in ${phase}:`, error);
2933
+ if (phase !== "error") {
2934
+ await this.transitionTo("error", { error, originalPhase: phase });
2935
+ }
2936
+ }
2937
+ }
2938
+ transition.duration = performance.now() - startTime;
2939
+ this.transitions.push(transition);
2940
+ this.currentPhase = phase;
2941
+ this.listeners.forEach((listener) => listener(phase));
2942
+ }
2943
+ /**
2944
+ * Get current lifecycle phase
2945
+ */
2946
+ getPhase() {
2947
+ return this.currentPhase;
2948
+ }
2949
+ /**
2950
+ * Subscribe to lifecycle changes
2951
+ */
2952
+ subscribe(listener) {
2953
+ this.listeners.add(listener);
2954
+ return () => this.listeners.delete(listener);
2955
+ }
2956
+ /**
2957
+ * Get lifecycle history
2958
+ */
2959
+ getHistory() {
2960
+ return [...this.transitions];
2961
+ }
2962
+ /**
2963
+ * Reset lifecycle state
2964
+ */
2965
+ reset() {
2966
+ this.currentPhase = "created";
2967
+ this.transitions = [];
2968
+ this.hooks.clear();
2969
+ this.listeners.clear();
2970
+ }
2971
+ };
2972
+ var ComponentLifecycle = class {
2973
+ manager = new LifecycleManager();
2974
+ mounted = false;
2975
+ /**
2976
+ * Component creation
2977
+ */
2978
+ async create(context) {
2979
+ await this.manager.transitionTo("created", context);
2980
+ }
2981
+ /**
2982
+ * Component mounting
2983
+ */
2984
+ async mount(context) {
2985
+ if (this.mounted) return;
2986
+ await this.manager.transitionTo("mounting", context);
2987
+ this.mounted = true;
2988
+ await this.manager.transitionTo("mounted", context);
2989
+ }
2990
+ /**
2991
+ * Component updating
2992
+ */
2993
+ async update(context) {
2994
+ if (!this.mounted) return;
2995
+ await this.manager.transitionTo("updating", context);
2996
+ await this.manager.transitionTo("updated", context);
2997
+ }
2998
+ /**
2999
+ * Component unmounting
3000
+ */
3001
+ async unmount(context) {
3002
+ if (!this.mounted) return;
3003
+ await this.manager.transitionTo("unmounting", context);
3004
+ this.mounted = false;
3005
+ await this.manager.transitionTo("unmounted", context);
3006
+ }
3007
+ /**
3008
+ * Handle error
3009
+ */
3010
+ async error(error, context) {
3011
+ await this.manager.transitionTo("error", { error, ...context });
3012
+ }
3013
+ /**
3014
+ * Add lifecycle hook
3015
+ */
3016
+ onPhase(phase, handler, priority = 0) {
3017
+ return this.manager.addHook({ phase, handler, priority });
3018
+ }
3019
+ /**
3020
+ * Subscribe to lifecycle changes
3021
+ */
3022
+ subscribe(listener) {
3023
+ return this.manager.subscribe(listener);
3024
+ }
3025
+ /**
3026
+ * Get current phase
3027
+ */
3028
+ getPhase() {
3029
+ return this.manager.getPhase();
3030
+ }
3031
+ /**
3032
+ * Check if mounted
3033
+ */
3034
+ isMounted() {
3035
+ return this.mounted;
3036
+ }
3037
+ };
3038
+ function createLifecycle() {
3039
+ return new ComponentLifecycle();
3040
+ }
3041
+
3042
+ // src/framework/properties.ts
3043
+ function validateType(value, type) {
3044
+ switch (type) {
3045
+ case "string":
3046
+ return typeof value === "string";
3047
+ case "number":
3048
+ return typeof value === "number" && !isNaN(value);
3049
+ case "boolean":
3050
+ return typeof value === "boolean";
3051
+ case "object":
3052
+ return value !== null && typeof value === "object" && !Array.isArray(value);
3053
+ case "array":
3054
+ return Array.isArray(value);
3055
+ case "function":
3056
+ return typeof value === "function";
3057
+ case "any":
3058
+ return true;
3059
+ default:
3060
+ return false;
3061
+ }
3062
+ }
3063
+ var PropertyManager = class {
3064
+ schema;
3065
+ values = {};
3066
+ listeners = /* @__PURE__ */ new Map();
3067
+ transforms = /* @__PURE__ */ new Map();
3068
+ constructor(schema) {
3069
+ this.schema = schema;
3070
+ this.initializeDefaults();
3071
+ }
3072
+ /**
3073
+ * Initialize default values
3074
+ */
3075
+ initializeDefaults() {
3076
+ for (const [key, definition] of Object.entries(this.schema)) {
3077
+ if (definition.default !== void 0) {
3078
+ this.values[key] = typeof definition.default === "function" ? definition.default() : definition.default;
3079
+ }
3080
+ if (definition.transform) {
3081
+ this.transforms.set(key, definition.transform);
3082
+ }
3083
+ }
3084
+ }
3085
+ /**
3086
+ * Set a property value
3087
+ */
3088
+ set(key, value) {
3089
+ const definition = this.schema[key];
3090
+ if (!definition) {
3091
+ console.warn(`Property "${String(key)}" is not defined in schema`);
3092
+ return false;
3093
+ }
3094
+ if (!this.validate(key, value)) {
3095
+ return false;
3096
+ }
3097
+ const transformedValue = this.transform(key, value);
3098
+ const oldValue = this.values[key];
3099
+ if (oldValue === transformedValue) {
3100
+ return true;
3101
+ }
3102
+ this.values[key] = transformedValue;
3103
+ this.notify(key, transformedValue);
3104
+ return true;
3105
+ }
3106
+ /**
3107
+ * Get a property value
3108
+ */
3109
+ get(key) {
3110
+ return this.values[key];
3111
+ }
3112
+ /**
3113
+ * Get all property values
3114
+ */
3115
+ getAll() {
3116
+ return { ...this.values };
3117
+ }
3118
+ /**
3119
+ * Validate a property value
3120
+ */
3121
+ validate(key, value) {
3122
+ const definition = this.schema[key];
3123
+ if (!definition) return false;
3124
+ if (definition.required && value == null) {
3125
+ console.error(`Property "${String(key)}" is required`);
3126
+ return false;
3127
+ }
3128
+ if (value == null && !definition.required) {
3129
+ return true;
3130
+ }
3131
+ const types = Array.isArray(definition.type) ? definition.type : [definition.type];
3132
+ const isValidType = types.some((type) => validateType(value, type));
3133
+ if (!isValidType) {
3134
+ console.error(`Property "${String(key)}" has invalid type`);
3135
+ return false;
3136
+ }
3137
+ if (definition.validator && !definition.validator(value)) {
3138
+ console.error(`Property "${String(key)}" failed custom validation`);
3139
+ return false;
3140
+ }
3141
+ return true;
3142
+ }
3143
+ /**
3144
+ * Transform a property value
3145
+ */
3146
+ transform(key, value) {
3147
+ const transform = this.transforms.get(key);
3148
+ return transform ? transform(value) : value;
3149
+ }
3150
+ /**
3151
+ * Subscribe to property changes
3152
+ */
3153
+ subscribe(key, listener) {
3154
+ const listeners = this.listeners.get(key) || /* @__PURE__ */ new Set();
3155
+ listeners.add(listener);
3156
+ this.listeners.set(key, listeners);
3157
+ return () => {
3158
+ listeners.delete(listener);
3159
+ };
3160
+ }
3161
+ /**
3162
+ * Subscribe to all property changes
3163
+ */
3164
+ subscribeAll(listener) {
3165
+ const wrappedListeners = /* @__PURE__ */ new Map();
3166
+ for (const key of Object.keys(this.schema)) {
3167
+ const wrapped = (value) => listener(key, value);
3168
+ wrappedListeners.set(key, wrapped);
3169
+ this.subscribe(key, wrapped);
3170
+ }
3171
+ return () => {
3172
+ for (const [key, wrapped] of wrappedListeners) {
3173
+ const listeners = this.listeners.get(key);
3174
+ listeners?.delete(wrapped);
3175
+ }
3176
+ };
3177
+ }
3178
+ /**
3179
+ * Notify listeners of property change
3180
+ */
3181
+ notify(key, value) {
3182
+ const listeners = this.listeners.get(key);
3183
+ if (listeners) {
3184
+ listeners.forEach((listener) => listener(value));
3185
+ }
3186
+ }
3187
+ /**
3188
+ * Batch update multiple properties
3189
+ */
3190
+ update(props) {
3191
+ for (const [key, value] of Object.entries(props)) {
3192
+ this.set(key, value);
3193
+ }
3194
+ }
3195
+ /**
3196
+ * Reset to default values
3197
+ */
3198
+ reset() {
3199
+ this.values = {};
3200
+ this.initializeDefaults();
3201
+ for (const [key, value] of Object.entries(this.values)) {
3202
+ this.notify(key, value);
3203
+ }
3204
+ }
3205
+ /**
3206
+ * Get schema
3207
+ */
3208
+ getSchema() {
3209
+ return this.schema;
3210
+ }
3211
+ };
3212
+ function createPropertyManager(schema) {
3213
+ return new PropertyManager(schema);
3214
+ }
3215
+ var CommonProps = {
3216
+ className: {
3217
+ type: "string",
3218
+ attribute: "class"
3219
+ },
3220
+ style: {
3221
+ type: "object"
3222
+ },
3223
+ id: {
3224
+ type: "string",
3225
+ reflect: true
3226
+ },
3227
+ disabled: {
3228
+ type: "boolean",
3229
+ default: false,
3230
+ reflect: true
3231
+ },
3232
+ hidden: {
3233
+ type: "boolean",
3234
+ default: false,
3235
+ reflect: true
3236
+ }
3237
+ };
3238
+ var ProductProps = {
3239
+ productId: {
3240
+ type: "string",
3241
+ attribute: "product-id",
3242
+ reflect: true
3243
+ },
3244
+ mode: {
3245
+ type: "string",
3246
+ default: "mock",
3247
+ validator: (value) => ["mock", "live"].includes(value)
3248
+ },
3249
+ endpoint: {
3250
+ type: "string"
3251
+ },
3252
+ ...CommonProps
3253
+ };
3254
+
3255
+ // src/framework/templates.ts
3256
+ var TemplateBuilder = class {
3257
+ /**
3258
+ * Create an element node
3259
+ */
3260
+ static element(tag, props, children) {
3261
+ return {
3262
+ type: "element",
3263
+ tag,
3264
+ props,
3265
+ children
3266
+ };
3267
+ }
3268
+ /**
3269
+ * Create a text node
3270
+ */
3271
+ static text(text) {
3272
+ return {
3273
+ type: "text",
3274
+ text
3275
+ };
3276
+ }
3277
+ /**
3278
+ * Create a component node
3279
+ */
3280
+ static component(component, props, children) {
3281
+ return {
3282
+ type: "component",
3283
+ component,
3284
+ props,
3285
+ children
3286
+ };
3287
+ }
3288
+ /**
3289
+ * Create a fragment node
3290
+ */
3291
+ static fragment(children) {
3292
+ return {
3293
+ type: "fragment",
3294
+ children
3295
+ };
3296
+ }
3297
+ /**
3298
+ * Create a conditional node
3299
+ */
3300
+ static if(condition, thenNode, elseNode) {
3301
+ const thenNodes = Array.isArray(thenNode) ? thenNode : [thenNode];
3302
+ const elseNodes = elseNode ? Array.isArray(elseNode) ? elseNode : [elseNode] : void 0;
3303
+ return {
3304
+ type: "conditional",
3305
+ condition,
3306
+ children: thenNodes,
3307
+ fallback: elseNodes
3308
+ };
3309
+ }
3310
+ /**
3311
+ * Create a loop node
3312
+ */
3313
+ static forEach(items, renderItem) {
3314
+ return {
3315
+ type: "loop",
3316
+ items,
3317
+ renderItem
3318
+ };
3319
+ }
3320
+ /**
3321
+ * Create a slot node
3322
+ */
3323
+ static slot(name, fallback) {
3324
+ return {
3325
+ type: "slot",
3326
+ name,
3327
+ fallback
3328
+ };
3329
+ }
3330
+ };
3331
+ var ComponentTemplates = class {
3332
+ /**
3333
+ * Product options template
3334
+ */
3335
+ static productOptions() {
3336
+ return {
3337
+ name: "ProductOptions",
3338
+ props: {
3339
+ attributes: { type: "object", required: true },
3340
+ selection: { type: "object", required: true },
3341
+ disabled: { type: "object" },
3342
+ onChange: { type: "function" }
3343
+ },
3344
+ events: [
3345
+ { name: "change", bubbles: true }
3346
+ ],
3347
+ render: (props) => {
3348
+ const { attributes = {}, selection = {}, disabled = {} } = props;
3349
+ return TemplateBuilder.element("div", { class: "product-options" }, [
3350
+ TemplateBuilder.forEach(
3351
+ () => Object.entries(attributes),
3352
+ ([key, attr]) => {
3353
+ return TemplateBuilder.element("div", { class: "option-group", key }, [
3354
+ TemplateBuilder.element("label", {}, [
3355
+ TemplateBuilder.text(attr.label || key)
3356
+ ]),
3357
+ TemplateBuilder.element("select", {
3358
+ value: selection[key],
3359
+ onChange: props.onChange,
3360
+ "data-attribute": key
3361
+ }, [
3362
+ TemplateBuilder.forEach(
3363
+ () => attr.choices || [],
3364
+ (choice) => {
3365
+ const value = typeof choice === "string" ? choice : choice.label;
3366
+ const isDisabled = disabled[key]?.includes(value);
3367
+ return TemplateBuilder.element("option", {
3368
+ value,
3369
+ disabled: isDisabled
3370
+ }, [
3371
+ TemplateBuilder.text(value)
3372
+ ]);
3373
+ }
3374
+ )
3375
+ ])
3376
+ ]);
3377
+ }
3378
+ )
3379
+ ]);
3380
+ }
3381
+ };
3382
+ }
3383
+ /**
3384
+ * Product price template
3385
+ */
3386
+ static productPrice() {
3387
+ return {
3388
+ name: "ProductPrice",
3389
+ props: {
3390
+ price: { type: "number" },
3391
+ currency: { type: "string", default: "USD" },
3392
+ locale: { type: "string", default: "en-US" },
3393
+ showCurrency: { type: "boolean", default: true }
3394
+ },
3395
+ render: (props) => {
3396
+ const { price, currency, locale, showCurrency } = props;
3397
+ return TemplateBuilder.if(
3398
+ () => price != null,
3399
+ TemplateBuilder.element("span", { class: "product-price" }, [
3400
+ TemplateBuilder.text(formatPrice2(price, locale, currency, showCurrency))
3401
+ ]),
3402
+ TemplateBuilder.element("span", { class: "product-price-empty" }, [
3403
+ TemplateBuilder.text("\u2014")
3404
+ ])
3405
+ );
3406
+ }
3407
+ };
3408
+ }
3409
+ /**
3410
+ * Product image template
3411
+ */
3412
+ static productImage() {
3413
+ return {
3414
+ name: "ProductImage",
3415
+ props: {
3416
+ src: { type: "string" },
3417
+ alt: { type: "string", default: "Product image" },
3418
+ width: { type: "number" },
3419
+ height: { type: "number" },
3420
+ loading: { type: "string", default: "lazy" }
3421
+ },
3422
+ slots: ["loading", "error"],
3423
+ render: (props) => {
3424
+ const { src, alt, width, height, loading } = props;
3425
+ return TemplateBuilder.if(
3426
+ () => !!src,
3427
+ TemplateBuilder.element("img", {
3428
+ src,
3429
+ alt,
3430
+ width,
3431
+ height,
3432
+ loading,
3433
+ class: "product-image"
3434
+ }),
3435
+ TemplateBuilder.slot("loading", [
3436
+ TemplateBuilder.element("div", { class: "product-image-placeholder" }, [
3437
+ TemplateBuilder.text("Loading...")
3438
+ ])
3439
+ ])
3440
+ );
3441
+ }
3442
+ };
3443
+ }
3444
+ /**
3445
+ * Add to cart button template
3446
+ */
3447
+ static addToCartButton() {
3448
+ return {
3449
+ name: "AddToCartButton",
3450
+ props: {
3451
+ disabled: { type: "boolean", default: false },
3452
+ loading: { type: "boolean", default: false },
3453
+ text: { type: "string", default: "Add to Cart" },
3454
+ onClick: { type: "function" }
3455
+ },
3456
+ events: [
3457
+ { name: "click", bubbles: true }
3458
+ ],
3459
+ render: (props) => {
3460
+ const { disabled, loading, text, onClick } = props;
3461
+ return TemplateBuilder.element("button", {
3462
+ class: "add-to-cart-button",
3463
+ disabled: disabled || loading,
3464
+ onClick
3465
+ }, [
3466
+ TemplateBuilder.if(
3467
+ () => loading,
3468
+ TemplateBuilder.text("Adding..."),
3469
+ TemplateBuilder.text(text)
3470
+ )
3471
+ ]);
3472
+ }
3473
+ };
3474
+ }
3475
+ };
3476
+ var AbstractTemplateRenderer = class {
3477
+ adapter;
3478
+ constructor(adapter) {
3479
+ this.adapter = adapter;
3480
+ }
3481
+ /**
3482
+ * Render a template node
3483
+ */
3484
+ render(node, context) {
3485
+ switch (node.type) {
3486
+ case "element":
3487
+ return this.renderElement(node, context);
3488
+ case "text":
3489
+ return this.renderText(node, context);
3490
+ case "component":
3491
+ return this.renderComponent(node, context);
3492
+ case "fragment":
3493
+ return this.renderFragment(node, context);
3494
+ case "conditional":
3495
+ return this.renderConditional(node, context);
3496
+ case "loop":
3497
+ return this.renderLoop(node, context);
3498
+ case "slot":
3499
+ return this.renderSlot(node, context);
3500
+ default:
3501
+ throw new Error(`Unknown node type: ${node.type}`);
3502
+ }
3503
+ }
3504
+ /**
3505
+ * Render child nodes
3506
+ */
3507
+ renderChildren(children, context) {
3508
+ if (!children) return [];
3509
+ return children.map((child) => this.render(child, context));
3510
+ }
3511
+ };
3512
+ function formatPrice2(cents, locale, currency, showCurrency) {
3513
+ const amount = cents / 100;
3514
+ if (showCurrency) {
3515
+ return new Intl.NumberFormat(locale, {
3516
+ style: "currency",
3517
+ currency
3518
+ }).format(amount);
3519
+ }
3520
+ return new Intl.NumberFormat(locale, {
3521
+ minimumFractionDigits: 2,
3522
+ maximumFractionDigits: 2
3523
+ }).format(amount);
3524
+ }
3525
+
3526
+ // src/framework/registry.ts
3527
+ var ComponentRegistry = class {
3528
+ components = /* @__PURE__ */ new Map();
3529
+ frameworks = /* @__PURE__ */ new Map();
3530
+ /**
3531
+ * Register a component
3532
+ */
3533
+ register(definition) {
3534
+ const { name, framework } = definition.metadata;
3535
+ this.components.set(name, definition);
3536
+ if (framework) {
3537
+ const frameworkComponents = this.frameworks.get(framework) || /* @__PURE__ */ new Set();
3538
+ frameworkComponents.add(name);
3539
+ this.frameworks.set(framework, frameworkComponents);
3540
+ }
3541
+ }
3542
+ /**
3543
+ * Get a component definition
3544
+ */
3545
+ get(name) {
3546
+ return this.components.get(name);
3547
+ }
3548
+ /**
3549
+ * Get all components for a framework
3550
+ */
3551
+ getByFramework(framework) {
3552
+ const names = this.frameworks.get(framework) || /* @__PURE__ */ new Set();
3553
+ return Array.from(names).map((name) => this.components.get(name)).filter(Boolean);
3554
+ }
3555
+ /**
3556
+ * Get all registered components
3557
+ */
3558
+ getAll() {
3559
+ return Array.from(this.components.values());
3560
+ }
3561
+ /**
3562
+ * Check if a component is registered
3563
+ */
3564
+ has(name) {
3565
+ return this.components.has(name);
3566
+ }
3567
+ /**
3568
+ * Clear registry
3569
+ */
3570
+ clear() {
3571
+ this.components.clear();
3572
+ this.frameworks.clear();
3573
+ }
3574
+ };
3575
+ var componentRegistry = new ComponentRegistry();
3576
+ var StandardComponents = {
3577
+ /**
3578
+ * Product component
3579
+ */
3580
+ Product: {
3581
+ metadata: {
3582
+ name: "Product",
3583
+ tagName: "merchify-product",
3584
+ displayName: "MerchifyProduct",
3585
+ description: "Product context provider component",
3586
+ props: {
3587
+ productId: { type: "string" },
3588
+ mode: {
3589
+ type: "string",
3590
+ default: "mock",
3591
+ validator: (v) => ["mock", "live"].includes(v)
3592
+ },
3593
+ endpoint: { type: "string" },
3594
+ autoLoad: { type: "boolean", default: true }
3595
+ },
3596
+ events: [
3597
+ { name: "ready", bubbles: true },
3598
+ { name: "error", bubbles: true },
3599
+ { name: "context-update", bubbles: true }
3600
+ ],
3601
+ slots: ["default", "loading", "error"]
3602
+ }
3603
+ },
3604
+ /**
3605
+ * Product Options component
3606
+ */
3607
+ ProductOptions: {
3608
+ metadata: {
3609
+ name: "ProductOptions",
3610
+ tagName: "merchify-product-options",
3611
+ displayName: "MerchifyProductOptions",
3612
+ description: "Product options selection component",
3613
+ props: {
3614
+ attributes: { type: "object" },
3615
+ selection: { type: "object" },
3616
+ combinations: { type: "array" },
3617
+ disabled: { type: "boolean", default: false }
3618
+ },
3619
+ events: [
3620
+ { name: "change", bubbles: true },
3621
+ { name: "selection-change", bubbles: true }
3622
+ ]
3623
+ }
3624
+ },
3625
+ /**
3626
+ * Product Price component
3627
+ */
3628
+ ProductPrice: {
3629
+ metadata: {
3630
+ name: "ProductPrice",
3631
+ tagName: "merchify-product-price",
3632
+ displayName: "MerchifyProductPrice",
3633
+ description: "Product price display component",
3634
+ props: {
3635
+ price: { type: "number" },
3636
+ currency: { type: "string", default: "USD" },
3637
+ locale: { type: "string", default: "en-US" },
3638
+ showCurrency: { type: "boolean", default: true }
3639
+ }
3640
+ }
3641
+ },
3642
+ /**
3643
+ * Product Image component
3644
+ */
3645
+ ProductImage: {
3646
+ metadata: {
3647
+ name: "ProductImage",
3648
+ tagName: "merchify-product-image",
3649
+ displayName: "MerchifyProductImage",
3650
+ description: "Product mockup image component",
3651
+ props: {
3652
+ productId: { type: "string" },
3653
+ mockupId: { type: "string" },
3654
+ variantId: { type: "string" },
3655
+ width: { type: "number", default: 400 },
3656
+ aspectRatio: { type: "string" },
3657
+ design: { type: "object" }
3658
+ },
3659
+ slots: ["loading", "error"]
3660
+ }
3661
+ },
3662
+ /**
3663
+ * Add to Cart component
3664
+ */
3665
+ AddToCart: {
3666
+ metadata: {
3667
+ name: "AddToCart",
3668
+ tagName: "merchify-add-to-cart",
3669
+ displayName: "MerchifyAddToCart",
3670
+ description: "Add to cart button component",
3671
+ props: {
3672
+ text: { type: "string", default: "Add to Cart" },
3673
+ loadingText: { type: "string", default: "Adding..." },
3674
+ successText: { type: "string", default: "Added!" },
3675
+ disabled: { type: "boolean", default: false },
3676
+ quantity: { type: "number", default: 1 }
3677
+ },
3678
+ events: [
3679
+ { name: "click", bubbles: true },
3680
+ { name: "add-to-cart", bubbles: true },
3681
+ { name: "success", bubbles: true },
3682
+ { name: "error", bubbles: true }
3683
+ ]
3684
+ }
3685
+ }
3686
+ };
3687
+ function registerStandardComponents() {
3688
+ Object.values(StandardComponents).forEach((component) => {
3689
+ componentRegistry.register({
3690
+ metadata: component.metadata,
3691
+ descriptor: {
3692
+ name: component.metadata.name,
3693
+ props: component.metadata.props,
3694
+ render: () => null
3695
+ // Placeholder - implementations will override
3696
+ }
3697
+ });
3698
+ });
3699
+ }
3700
+ var ComponentFactory = class {
3701
+ /**
3702
+ * Create a React component
3703
+ */
3704
+ static forReact(definition, React) {
3705
+ const { metadata, descriptor } = definition;
3706
+ const Component = (props) => {
3707
+ return React.createElement("div", props);
3708
+ };
3709
+ Component.displayName = metadata.displayName || metadata.name;
3710
+ return Component;
3711
+ }
3712
+ /**
3713
+ * Create a Vue component
3714
+ */
3715
+ static forVue(definition) {
3716
+ const { metadata, descriptor } = definition;
3717
+ return {
3718
+ name: metadata.name,
3719
+ props: metadata.props,
3720
+ emits: metadata.events?.map((e) => e.name),
3721
+ setup(props, context) {
3722
+ return () => null;
3723
+ }
3724
+ };
3725
+ }
3726
+ /**
3727
+ * Create a Svelte component
3728
+ */
3729
+ static forSvelte(definition) {
3730
+ return {
3731
+ metadata: definition.metadata,
3732
+ descriptor: definition.descriptor
3733
+ };
3734
+ }
3735
+ /**
3736
+ * Create a Web Component
3737
+ */
3738
+ static forWebComponent(definition, LitElement) {
3739
+ const { metadata, descriptor } = definition;
3740
+ class Component extends LitElement {
3741
+ static properties = metadata.props;
3742
+ render() {
3743
+ return null;
3744
+ }
3745
+ }
3746
+ if (metadata.tagName && !customElements.get(metadata.tagName)) {
3747
+ customElements.define(metadata.tagName, Component);
3748
+ }
3749
+ return Component;
3750
+ }
3751
+ };
3752
+ function autoRegister(framework, registrar) {
3753
+ const components = componentRegistry.getByFramework(framework);
3754
+ components.forEach((definition) => {
3755
+ registrar(definition);
3756
+ });
3757
+ }
3758
+
3759
+ // src/framework/adapters/lit.ts
3760
+ var LitAdapter = class {
3761
+ name = "lit";
3762
+ element;
3763
+ // LitElement instance
3764
+ properties = /* @__PURE__ */ new Map();
3765
+ subscriptions = /* @__PURE__ */ new Map();
3766
+ constructor(element) {
3767
+ this.element = element;
3768
+ }
3769
+ /**
3770
+ * Create a stateful value using reactive properties
3771
+ */
3772
+ createState(initialValue) {
3773
+ const key = `state_${Date.now()}_${Math.random()}`;
3774
+ this.properties.set(key, initialValue);
3775
+ return {
3776
+ get: () => {
3777
+ return this.properties.get(key);
3778
+ },
3779
+ set: (newValue) => {
3780
+ const current = this.properties.get(key);
3781
+ const nextValue = typeof newValue === "function" ? newValue(current) : newValue;
3782
+ this.properties.set(key, nextValue);
3783
+ if (this.element) {
3784
+ this.element.requestUpdate();
3785
+ }
3786
+ const subs = this.subscriptions.get(key);
3787
+ if (subs) {
3788
+ subs.forEach((callback) => callback(nextValue));
3789
+ }
3790
+ },
3791
+ subscribe: (callback) => {
3792
+ const subs = this.subscriptions.get(key) || /* @__PURE__ */ new Set();
3793
+ subs.add(callback);
3794
+ this.subscriptions.set(key, subs);
3795
+ return () => {
3796
+ subs.delete(callback);
3797
+ };
3798
+ }
3799
+ };
3800
+ }
3801
+ /**
3802
+ * Create a context provider/consumer
3803
+ */
3804
+ createContext(name) {
3805
+ return {
3806
+ provide: (value) => {
3807
+ if (this.element) {
3808
+ this.element[`__context_${name}`] = value;
3809
+ this.element.dispatchEvent(new CustomEvent("context-provide", {
3810
+ detail: { name, value },
3811
+ bubbles: true,
3812
+ composed: true
3813
+ }));
3814
+ }
3815
+ },
3816
+ consume: () => {
3817
+ if (!this.element) return void 0;
3818
+ const stored = this.element[`__context_${name}`];
3819
+ if (stored !== void 0) return stored;
3820
+ const detail = { context: void 0 };
3821
+ this.element.dispatchEvent(new CustomEvent("context-request", {
3822
+ detail: { name, ...detail },
3823
+ bubbles: true,
3824
+ composed: true
3825
+ }));
3826
+ return detail.context;
3827
+ },
3828
+ subscribe: (callback) => {
3829
+ if (!this.element) return () => {
3830
+ };
3831
+ const handler = (event) => {
3832
+ if (event.detail?.name === name) {
3833
+ callback(event.detail.value);
3834
+ }
3835
+ };
3836
+ this.element.addEventListener("context-update", handler);
3837
+ return () => {
3838
+ this.element.removeEventListener("context-update", handler);
3839
+ };
3840
+ }
3841
+ };
3842
+ }
3843
+ /**
3844
+ * Register lifecycle hooks
3845
+ */
3846
+ useLifecycle(lifecycle) {
3847
+ if (!this.element) return;
3848
+ const originalConnected = this.element.connectedCallback;
3849
+ const originalDisconnected = this.element.disconnectedCallback;
3850
+ const originalUpdated = this.element.updated;
3851
+ this.element.connectedCallback = function() {
3852
+ originalConnected?.call(this);
3853
+ lifecycle.onMount?.();
3854
+ };
3855
+ this.element.disconnectedCallback = function() {
3856
+ originalDisconnected?.call(this);
3857
+ lifecycle.onUnmount?.();
3858
+ };
3859
+ this.element.updated = function(changedProperties) {
3860
+ originalUpdated?.call(this, changedProperties);
3861
+ lifecycle.onUpdate?.({});
3862
+ };
3863
+ }
3864
+ /**
3865
+ * Register event handlers
3866
+ */
3867
+ useEvents(handlers) {
3868
+ if (!this.element) return;
3869
+ handlers.forEach(({ name, handler }) => {
3870
+ this.element.addEventListener(name, handler);
3871
+ });
3872
+ }
3873
+ /**
3874
+ * Create a ref to a DOM element
3875
+ */
3876
+ createRef() {
3877
+ const ref = { current: null };
3878
+ if (this.element) {
3879
+ this.element.updateComplete.then(() => {
3880
+ ref.current = this.element.renderRoot.querySelector("[ref]");
3881
+ });
3882
+ }
3883
+ return ref;
3884
+ }
3885
+ /**
3886
+ * Render a component
3887
+ */
3888
+ render(component, props) {
3889
+ return component.render(this);
3890
+ }
3891
+ /**
3892
+ * Get Lit-specific utilities
3893
+ */
3894
+ getUtilities() {
3895
+ return {
3896
+ batchUpdates: (callback) => {
3897
+ callback();
3898
+ if (this.element) {
3899
+ this.element.requestUpdate();
3900
+ }
3901
+ },
3902
+ nextTick: (callback) => {
3903
+ if (this.element) {
3904
+ this.element.updateComplete.then(callback);
3905
+ } else {
3906
+ Promise.resolve().then(callback);
3907
+ }
3908
+ },
3909
+ computed: (deps, compute) => {
3910
+ return compute();
3911
+ },
3912
+ memo: (value, deps) => {
3913
+ return value;
3914
+ }
3915
+ };
3916
+ }
3917
+ /**
3918
+ * Set the element instance
3919
+ */
3920
+ setElement(element) {
3921
+ this.element = element;
3922
+ }
3923
+ };
3924
+ function createLitComponent(descriptor, LitElement, html) {
3925
+ return class extends LitElement {
3926
+ adapter = new LitAdapter(this);
3927
+ connectedCallback() {
3928
+ super.connectedCallback();
3929
+ if (descriptor.lifecycle) {
3930
+ this.adapter.useLifecycle(descriptor.lifecycle);
3931
+ }
3932
+ }
3933
+ render() {
3934
+ return this.adapter.render(descriptor, this);
3935
+ }
3936
+ };
3937
+ }
3938
+
3939
+ // src/framework/adapters/vue.ts
3940
+ var VueAdapter = class {
3941
+ name = "vue";
3942
+ vue;
3943
+ // Vue instance
3944
+ instance;
3945
+ // Component instance
3946
+ constructor(vue, instance) {
3947
+ this.vue = vue;
3948
+ this.instance = instance;
3949
+ }
3950
+ /**
3951
+ * Create a stateful value using ref/reactive
3952
+ */
3953
+ createState(initialValue) {
3954
+ const { ref, watch } = this.vue;
3955
+ const state = ref(initialValue);
3956
+ const subscribers = /* @__PURE__ */ new Set();
3957
+ watch(state, (newValue) => {
3958
+ subscribers.forEach((callback) => callback(newValue));
3959
+ });
3960
+ return {
3961
+ get: () => state.value,
3962
+ set: (newValue) => {
3963
+ if (typeof newValue === "function") {
3964
+ state.value = newValue(state.value);
3965
+ } else {
3966
+ state.value = newValue;
3967
+ }
3968
+ },
3969
+ subscribe: (callback) => {
3970
+ subscribers.add(callback);
3971
+ return () => {
3972
+ subscribers.delete(callback);
3973
+ };
3974
+ }
3975
+ };
3976
+ }
3977
+ /**
3978
+ * Create a context provider/consumer
3979
+ */
3980
+ createContext(name) {
3981
+ const { provide, inject } = this.vue;
3982
+ const key = Symbol(name);
3983
+ return {
3984
+ provide: (value) => {
3985
+ provide(key, value);
3986
+ },
3987
+ consume: () => {
3988
+ return inject(key);
3989
+ },
3990
+ subscribe: (callback) => {
3991
+ const value = inject(key);
3992
+ if (value !== void 0) {
3993
+ const { watchEffect } = this.vue;
3994
+ const unwatch = watchEffect(() => {
3995
+ callback(value);
3996
+ });
3997
+ return unwatch;
3998
+ }
3999
+ return () => {
4000
+ };
4001
+ }
4002
+ };
4003
+ }
4004
+ /**
4005
+ * Register lifecycle hooks
4006
+ */
4007
+ useLifecycle(lifecycle) {
4008
+ const { onMounted, onUnmounted, onUpdated, onErrorCaptured } = this.vue;
4009
+ if (lifecycle.onMount) {
4010
+ onMounted(lifecycle.onMount);
4011
+ }
4012
+ if (lifecycle.onUnmount) {
4013
+ onUnmounted(lifecycle.onUnmount);
4014
+ }
4015
+ if (lifecycle.onUpdate) {
4016
+ onUpdated(() => lifecycle.onUpdate({}));
4017
+ }
4018
+ if (lifecycle.onError) {
4019
+ onErrorCaptured((error) => {
4020
+ lifecycle.onError(error);
4021
+ return false;
4022
+ });
4023
+ }
4024
+ }
4025
+ /**
4026
+ * Register event handlers
4027
+ */
4028
+ useEvents(handlers) {
4029
+ if (!this.instance) return;
4030
+ handlers.forEach(({ name, handler }) => {
4031
+ this.instance.$on?.(name, handler);
4032
+ });
4033
+ }
4034
+ /**
4035
+ * Create a ref to a DOM element
4036
+ */
4037
+ createRef() {
4038
+ const { ref } = this.vue;
4039
+ const elementRef = ref(null);
4040
+ return {
4041
+ get current() {
4042
+ return elementRef.value;
4043
+ },
4044
+ set current(value) {
4045
+ elementRef.value = value;
4046
+ }
4047
+ };
4048
+ }
4049
+ /**
4050
+ * Render a component
4051
+ */
4052
+ render(component, props) {
4053
+ const { h } = this.vue;
4054
+ return h("div", props, component.render(this));
4055
+ }
4056
+ /**
4057
+ * Get Vue-specific utilities
4058
+ */
4059
+ getUtilities() {
4060
+ const { nextTick, computed, watchEffect } = this.vue;
4061
+ return {
4062
+ batchUpdates: (callback) => {
4063
+ callback();
4064
+ },
4065
+ nextTick: (callback) => {
4066
+ nextTick(callback);
4067
+ },
4068
+ computed: (deps, compute) => {
4069
+ return computed(compute).value;
4070
+ },
4071
+ memo: (value, deps) => {
4072
+ return value;
4073
+ }
4074
+ };
4075
+ }
4076
+ };
4077
+ function createVueComponent(descriptor, vue) {
4078
+ const { defineComponent: defineComponent2, h } = vue;
4079
+ return defineComponent2({
4080
+ name: descriptor.name,
4081
+ props: descriptor.props || {},
4082
+ setup(props, context) {
4083
+ const adapter = new VueAdapter(vue, context);
4084
+ if (descriptor.lifecycle) {
4085
+ adapter.useLifecycle(descriptor.lifecycle);
4086
+ }
4087
+ return () => adapter.render(descriptor, props);
4088
+ }
4089
+ });
4090
+ }
4091
+ function useFrameworkAdapter(vue) {
4092
+ return new VueAdapter(vue);
4093
+ }
4094
+
4095
+ // src/framework/adapters/svelte.ts
4096
+ var SvelteAdapter = class {
4097
+ name = "svelte";
4098
+ svelte;
4099
+ // Svelte imports
4100
+ component;
4101
+ // Component instance
4102
+ constructor(svelte, component) {
4103
+ this.svelte = svelte;
4104
+ this.component = component;
4105
+ }
4106
+ /**
4107
+ * Create a stateful value using Svelte stores
4108
+ */
4109
+ createState(initialValue) {
4110
+ const { writable, get } = this.svelte;
4111
+ const store = writable(initialValue);
4112
+ return {
4113
+ get: () => get(store),
4114
+ set: (newValue) => {
4115
+ if (typeof newValue === "function") {
4116
+ store.update((prev) => newValue(prev));
4117
+ } else {
4118
+ store.set(newValue);
4119
+ }
4120
+ },
4121
+ subscribe: (callback) => {
4122
+ const unsubscribe = store.subscribe(callback);
4123
+ return unsubscribe;
4124
+ }
4125
+ };
4126
+ }
4127
+ /**
4128
+ * Create a context provider/consumer
4129
+ */
4130
+ createContext(name) {
4131
+ const { setContext, getContext, hasContext } = this.svelte;
4132
+ const key = Symbol(name);
4133
+ return {
4134
+ provide: (value) => {
4135
+ setContext(key, value);
4136
+ },
4137
+ consume: () => {
4138
+ if (hasContext(key)) {
4139
+ return getContext(key);
4140
+ }
4141
+ return void 0;
4142
+ },
4143
+ subscribe: (callback) => {
4144
+ const value = getContext(key);
4145
+ if (value && typeof value === "object" && "subscribe" in value) {
4146
+ const unsubscribe = value.subscribe(callback);
4147
+ return unsubscribe;
4148
+ }
4149
+ if (value !== void 0) {
4150
+ callback(value);
4151
+ }
4152
+ return () => {
4153
+ };
4154
+ }
4155
+ };
4156
+ }
4157
+ /**
4158
+ * Register lifecycle hooks
4159
+ */
4160
+ useLifecycle(lifecycle) {
4161
+ const { onMount, onDestroy, afterUpdate } = this.svelte;
4162
+ if (lifecycle.onMount) {
4163
+ onMount(lifecycle.onMount);
4164
+ }
4165
+ if (lifecycle.onUnmount) {
4166
+ onDestroy(lifecycle.onUnmount);
4167
+ }
4168
+ if (lifecycle.onUpdate) {
4169
+ afterUpdate(() => lifecycle.onUpdate({}));
4170
+ }
4171
+ }
4172
+ /**
4173
+ * Register event handlers
4174
+ */
4175
+ useEvents(handlers) {
4176
+ const { createEventDispatcher } = this.svelte;
4177
+ const dispatch = createEventDispatcher();
4178
+ handlers.forEach(({ name, handler }) => {
4179
+ this[`handle${name}`] = (event) => {
4180
+ handler(event);
4181
+ dispatch(name, event);
4182
+ };
4183
+ });
4184
+ }
4185
+ /**
4186
+ * Create a ref to a DOM element
4187
+ */
4188
+ createRef() {
4189
+ const ref = { current: null };
4190
+ return ref;
4191
+ }
4192
+ /**
4193
+ * Render a component
4194
+ */
4195
+ render(component, props) {
4196
+ return component.render(this);
4197
+ }
4198
+ /**
4199
+ * Get Svelte-specific utilities
4200
+ */
4201
+ getUtilities() {
4202
+ const { tick, derived } = this.svelte;
4203
+ return {
4204
+ batchUpdates: (callback) => {
4205
+ callback();
4206
+ },
4207
+ nextTick: (callback) => {
4208
+ tick().then(callback);
4209
+ },
4210
+ computed: (deps, compute) => {
4211
+ const { get } = this.svelte;
4212
+ const derivedStore = derived(deps, compute);
4213
+ return get(derivedStore);
4214
+ },
4215
+ memo: (value, deps) => {
4216
+ return value;
4217
+ }
4218
+ };
4219
+ }
4220
+ };
4221
+ function createSvelteComponent(descriptor, svelte) {
4222
+ return {
4223
+ name: descriptor.name,
4224
+ props: descriptor.props,
4225
+ setup: (props) => {
4226
+ const adapter = new SvelteAdapter(svelte);
4227
+ if (descriptor.lifecycle) {
4228
+ adapter.useLifecycle(descriptor.lifecycle);
4229
+ }
4230
+ return adapter.render(descriptor, props);
4231
+ }
4232
+ };
4233
+ }
4234
+
4235
+ // src/index.ts
4236
+ function getFetcher(config2) {
4237
+ return config2?.fetcher || globalThis.fetch.bind(globalThis);
4238
+ }
4239
+ function validateProductLoose(product) {
4240
+ try {
4241
+ return CatalogProductSchema.parse(product);
4242
+ } catch (_err) {
4243
+ return product;
4244
+ }
4245
+ }
4246
+ async function listProducts(config2) {
4247
+ const f = getFetcher(config2);
4248
+ const meilisearchHost = config2?.meilisearch?.host || typeof process !== "undefined" && process.env?.NEXT_PUBLIC_MEILISEARCH_HOST || "https://ms-e5d999b2eaca-15654.sfo.meilisearch.io";
4249
+ const meilisearchApiKey = config2?.meilisearch?.apiKey || typeof process !== "undefined" && process.env?.NEXT_PUBLIC_MEILISEARCH_API_KEY || "eee819b849798ad9091228c486ec05d0931e5292";
4250
+ const meilisearchIndex = config2?.meilisearch?.index || typeof process !== "undefined" && process.env?.NEXT_PUBLIC_MEILISEARCH_INDEX || "merchify";
4251
+ const headers = {
4252
+ "Content-Type": "application/json"
4253
+ };
4254
+ if (meilisearchApiKey) {
4255
+ headers["Authorization"] = `Bearer ${meilisearchApiKey}`;
4256
+ }
4257
+ const url = `${meilisearchHost}/indexes/${meilisearchIndex}/search`;
4258
+ const res = await f(url, {
4259
+ method: "POST",
4260
+ headers,
4261
+ body: JSON.stringify({
4262
+ q: "",
4263
+ limit: config2?.limit || 1e3,
4264
+ filter: "mockups IS NOT EMPTY"
4265
+ })
4266
+ });
4267
+ if (!res.ok) throw new Error(`listProducts failed: ${res.status}`);
4268
+ const data = await res.json();
4269
+ const items = (data.hits || []).map((p) => validateProductLoose(p));
4270
+ return { items, total: data.estimatedTotalHits || items.length };
4271
+ }
4272
+ async function getProduct(idOrSlug, config2) {
4273
+ const f = getFetcher(config2);
4274
+ const meilisearchHost = config2?.meilisearch?.host || typeof process !== "undefined" && process.env?.NEXT_PUBLIC_MEILISEARCH_HOST || "https://ms-e5d999b2eaca-15654.sfo.meilisearch.io";
4275
+ const meilisearchApiKey = config2?.meilisearch?.apiKey || typeof process !== "undefined" && process.env?.NEXT_PUBLIC_MEILISEARCH_API_KEY || "eee819b849798ad9091228c486ec05d0931e5292";
4276
+ const meilisearchIndex = config2?.meilisearch?.index || typeof process !== "undefined" && process.env?.NEXT_PUBLIC_MEILISEARCH_INDEX || "merchify";
4277
+ const headers = {
4278
+ "Content-Type": "application/json"
4279
+ };
4280
+ if (meilisearchApiKey) {
4281
+ headers["Authorization"] = `Bearer ${meilisearchApiKey}`;
4282
+ }
4283
+ const url = `${meilisearchHost}/indexes/${meilisearchIndex}/documents/${idOrSlug}`;
4284
+ const res = await f(url, { method: "GET", headers });
4285
+ if (res.status === 404)
4286
+ throw Object.assign(new Error("Not found"), { code: "NOT_FOUND" });
4287
+ if (!res.ok) throw new Error(`getProduct failed: ${res.status}`);
4288
+ const raw = await res.json();
4289
+ return validateProductLoose(raw);
4290
+ }
4291
+ function createClient(config2) {
4292
+ const fetcher = getFetcher(config2);
4293
+ const mockupService = new MockupServiceImpl(config2.mockup || {}, fetcher);
4294
+ return {
4295
+ catalog: {
4296
+ listProducts: (overrides) => listProducts({ ...config2, ...overrides }),
4297
+ getProduct: (idOrSlug, overrides) => getProduct(idOrSlug, { ...config2, ...overrides })
4298
+ },
4299
+ mockup: mockupService
4300
+ };
4301
+ }
4302
+ export {
4303
+ AbstractTemplateRenderer,
4304
+ AdapterRegistry,
4305
+ Animations,
4306
+ Attributes,
4307
+ ClassNames,
4308
+ ColorPickerUtils,
4309
+ CommonProps,
4310
+ ComponentEventManager,
4311
+ ComponentFactory,
4312
+ ComponentLifecycle,
4313
+ ComponentRegistry,
4314
+ ComponentTemplates,
4315
+ ContextBridge,
4316
+ ContextInjector,
4317
+ ContextSynchronizer,
4318
+ DEFAULT_ARTWORK_URL,
4319
+ DEFAULT_ASPECT_RATIO,
4320
+ DEFAULT_COLOR,
4321
+ DEFAULT_PLACEMENT_DIMENSIONS,
4322
+ Elements,
4323
+ ErrorManager,
4324
+ EventDelegator,
4325
+ EventEmitter,
4326
+ Focus,
4327
+ LifecycleManager,
4328
+ LitAdapter,
4329
+ ProductContextManager,
4330
+ ProductLoader,
4331
+ ProductProps,
4332
+ PropertyManager,
4333
+ RealtimeMockupService,
4334
+ StandardComponents,
4335
+ StandardEvents,
4336
+ StateManager,
4337
+ Styles,
4338
+ SvelteAdapter,
4339
+ SwatchUtils,
4340
+ TemplateBuilder,
4341
+ UniversalContextProvider,
4342
+ VueAdapter,
4343
+ adapterRegistry,
4344
+ autoRegister,
4345
+ componentRegistry,
4346
+ computeDisabledChoices,
4347
+ config,
4348
+ createAddToCartEvent,
4349
+ createAddToCartHandler,
4350
+ createCartDetail,
4351
+ createClient,
4352
+ createComponent,
4353
+ createContextProvider,
4354
+ createDesignForPlacements,
4355
+ createDevFetcher,
4356
+ createErrorHandler,
4357
+ createEventManager,
4358
+ createLifecycle,
4359
+ createLitComponent,
4360
+ createProductContext,
4361
+ createProductLoader,
4362
+ createPropertyManager,
4363
+ createStateHook,
4364
+ createStateStore,
4365
+ createSvelteComponent,
4366
+ createUniversalProvider,
4367
+ createVueComponent,
4368
+ defineComponent,
4369
+ deriveDefaultSelection,
4370
+ describeArtSelector,
4371
+ describeProductArtAlignment,
4372
+ describeProductImage,
4373
+ describeProductOptions,
4374
+ describeProductPrice,
4375
+ describeProductTitle,
4376
+ extractProductId,
4377
+ filterImagePlacements,
4378
+ findBestCombination,
4379
+ findClosestSnapPoint,
4380
+ findVariantForSelection,
4381
+ formatPrice,
4382
+ formatValidationErrors,
4383
+ getDefaultVariantId,
4384
+ getEffectiveAlignment,
4385
+ getIncompleteSelectionMessage,
4386
+ getMissingSelections,
4387
+ getOptionRenderType,
4388
+ getPricePreview,
4389
+ getProduct,
4390
+ getSelectionDisplayText,
4391
+ getSnapPoints,
4392
+ getVariant,
4393
+ handleOptionChange,
4394
+ isOptionAvailable,
4395
+ isSelectionComplete,
4396
+ isValidAlignment,
4397
+ isValidTileCount,
4398
+ listProducts,
4399
+ mockupUrl,
4400
+ normalizeChoice,
4401
+ prepareOptionRenderData,
4402
+ registerStandardComponents,
4403
+ resolveBestCombination,
4404
+ resolveMockupConfig,
4405
+ resolveMockupId,
4406
+ resolveVariantId,
4407
+ retryOperation,
4408
+ simulateCartOperation,
4409
+ toCombinations,
4410
+ toOptionAttributes,
4411
+ useFrameworkAdapter as useVueAdapter,
4412
+ validateAlignment,
4413
+ validateDesignElement,
4414
+ validateEffects,
4415
+ validateImageUrl,
4416
+ validateMockupOptions,
4417
+ validateProductSelection,
4418
+ validateProductSpec,
4419
+ validateRequiredOptions,
4420
+ validateTileCount,
4421
+ withContext,
4422
+ withErrorHandling,
4423
+ withSyncErrorHandling
4424
+ };