@numueg/theme-sdk 0.6.0 → 0.9.0

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.
Files changed (52) hide show
  1. package/CHANGELOG.md +144 -0
  2. package/dist/chunk-3C37JX2X.cjs +207 -0
  3. package/dist/chunk-3C37JX2X.cjs.map +1 -0
  4. package/dist/chunk-JIAA7TGR.mjs +397 -0
  5. package/dist/chunk-JIAA7TGR.mjs.map +1 -0
  6. package/dist/chunk-PUR3FRGQ.mjs +183 -0
  7. package/dist/chunk-PUR3FRGQ.mjs.map +1 -0
  8. package/dist/chunk-TBSNHHFH.cjs +96 -0
  9. package/dist/chunk-TBSNHHFH.cjs.map +1 -0
  10. package/dist/chunk-X3J4GDI7.cjs +408 -0
  11. package/dist/chunk-X3J4GDI7.cjs.map +1 -0
  12. package/dist/chunk-XF2FGIVS.mjs +94 -0
  13. package/dist/chunk-XF2FGIVS.mjs.map +1 -0
  14. package/dist/{entities-6MGANln7.d.mts → entities-B8378GKp.d.mts} +12 -0
  15. package/dist/{entities-6MGANln7.d.ts → entities-B8378GKp.d.ts} +12 -0
  16. package/dist/index.cjs +643 -965
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.mts +215 -82
  19. package/dist/index.d.ts +215 -82
  20. package/dist/index.mjs +485 -908
  21. package/dist/index.mjs.map +1 -1
  22. package/dist/{mount-BGumg1JM.d.mts → mount-CM4kGg9w.d.ts} +87 -14
  23. package/dist/{mount-Bt-4ken5.d.ts → mount-NLjFNyBv.d.mts} +87 -14
  24. package/dist/normalize.cjs +7 -91
  25. package/dist/normalize.cjs.map +1 -1
  26. package/dist/normalize.d.mts +1 -1
  27. package/dist/normalize.d.ts +1 -1
  28. package/dist/normalize.mjs +1 -92
  29. package/dist/normalize.mjs.map +1 -1
  30. package/dist/{theme-D8MOopvi.d.mts → theme-CNTB4KnU.d.mts} +29 -3
  31. package/dist/{theme-D8MOopvi.d.ts → theme-CNTB4KnU.d.ts} +29 -3
  32. package/dist/types.d.mts +2 -2
  33. package/dist/types.d.ts +2 -2
  34. package/dist/v2-compat.cjs +6 -163
  35. package/dist/v2-compat.cjs.map +1 -1
  36. package/dist/v2-compat.d.mts +1 -1
  37. package/dist/v2-compat.d.ts +1 -1
  38. package/dist/v2-compat.mjs +2 -159
  39. package/dist/v2-compat.mjs.map +1 -1
  40. package/dist/validation.cjs +43 -403
  41. package/dist/validation.cjs.map +1 -1
  42. package/dist/validation.d.mts +1 -1
  43. package/dist/validation.d.ts +1 -1
  44. package/dist/validation.mjs +1 -395
  45. package/dist/validation.mjs.map +1 -1
  46. package/dist/verify.cjs +6 -21
  47. package/dist/verify.cjs.map +1 -1
  48. package/dist/verify.d.mts +3 -3
  49. package/dist/verify.d.ts +3 -3
  50. package/dist/verify.mjs +3 -20
  51. package/dist/verify.mjs.map +1 -1
  52. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -1,494 +1,13 @@
1
- import { createContext, forwardRef, useState, useImperativeHandle, useEffect, useCallback, useMemo, useRef, useContext, StrictMode, createElement, Component } from 'react';
1
+ export { resolveThemeSettings } from './chunk-XF2FGIVS.mjs';
2
+ export { KNOWN_SETTING_TYPES, KNOWN_TEMPLATES, REQUIRED_TEMPLATES, SDK_VERSION, THEME_CONTRACT_VERSION, mergeResults, validateBuiltManifest, validateManifest, validateSectionSchema, validateSettingsAgainstSchema } from './chunk-JIAA7TGR.mjs';
3
+ import { ShopContext, ThemeSettingsContext, CurrentTemplateContext, PageContext, LocalizationContext, CurrencyContext, CartContext, CustomerContext, NavigationContext, CollectionContext, ProductContext, useShop, useLocalization, useCustomer, useThemeSettings } from './chunk-PUR3FRGQ.mjs';
4
+ export { CartContext, CollectionContext, CurrencyContext, CustomerContext, LocalizationContext, NavigationContext, PageContext, ProductContext, ShopContext, ThemeSettingsContext, useCollections, useCustomer, useDirection, useFieldTranslation, useLocale, useLocalization, useNumberFormat, usePage, useProducts, useShop, useThemeSettings, useTranslation } from './chunk-PUR3FRGQ.mjs';
5
+ import { createContext, forwardRef, useState, useImperativeHandle, useEffect, useCallback, useMemo, useRef, useContext, useSyncExternalStore, StrictMode, createElement, Component } from 'react';
2
6
  import { hydrateRoot, createRoot } from 'react-dom/client';
3
7
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
4
8
 
5
9
  // src/types/theme.ts
6
10
  var MAX_BLOCK_DEPTH = 5;
7
-
8
- // src/validation/index.ts
9
- var THEME_CONTRACT_VERSION = 1;
10
- var SDK_VERSION = "0.6.0" ;
11
- var REQUIRED_TEMPLATES = [
12
- "home",
13
- "product",
14
- "collection",
15
- "cart",
16
- "page",
17
- "search",
18
- "404"
19
- ];
20
- var KNOWN_TEMPLATES = /* @__PURE__ */ new Set([
21
- ...REQUIRED_TEMPLATES,
22
- "blog",
23
- "article",
24
- "policies",
25
- "password",
26
- "account",
27
- "checkout"
28
- ]);
29
- var KNOWN_SETTING_TYPES = /* @__PURE__ */ new Set([
30
- "text",
31
- "textarea",
32
- "richtext",
33
- "number",
34
- "range",
35
- "color",
36
- "checkbox",
37
- "select",
38
- "radio",
39
- "font",
40
- "image_picker",
41
- "url",
42
- "product",
43
- "product_list",
44
- "collection",
45
- "collection_list",
46
- "header",
47
- "paragraph",
48
- "html",
49
- "date",
50
- "time",
51
- "video_picker",
52
- "color_scheme",
53
- "page_picker",
54
- "blog_picker",
55
- "link_list_picker",
56
- "variant_picker",
57
- "file_upload",
58
- "icon_picker",
59
- "icon"
60
- ]);
61
- var PRESENTATIONAL_SETTING_TYPES = /* @__PURE__ */ new Set([
62
- "header",
63
- "paragraph",
64
- "html"
65
- ]);
66
- var SECTION_TYPE_RE = /^[a-z][a-z0-9_-]*$/;
67
- var THEME_ID_RE = /^[a-z0-9][a-z0-9_-]*[a-z0-9]$/;
68
- var SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?(?:\+[0-9A-Za-z-.]+)?$/;
69
- function isObject(v) {
70
- return typeof v === "object" && v !== null && !Array.isArray(v);
71
- }
72
- function isNonEmptyString(v) {
73
- return typeof v === "string" && v.trim().length > 0;
74
- }
75
- var IssueBag = class {
76
- constructor() {
77
- this.issues = [];
78
- }
79
- err(code, message, path) {
80
- this.issues.push({ level: "error", code, message, path });
81
- }
82
- warn(code, message, path) {
83
- this.issues.push({ level: "warning", code, message, path });
84
- }
85
- result() {
86
- return {
87
- valid: !this.issues.some((i) => i.level === "error"),
88
- issues: this.issues
89
- };
90
- }
91
- };
92
- function collectPresetSectionTypes(manifest) {
93
- const types = /* @__PURE__ */ new Set();
94
- const presets = manifest.presets;
95
- if (!isObject(presets)) return types;
96
- const buckets = [presets.templates, presets.section_groups];
97
- for (const bucket of buckets) {
98
- if (!isObject(bucket)) continue;
99
- for (const entry of Object.values(bucket)) {
100
- if (!isObject(entry)) continue;
101
- const sections = entry.sections;
102
- const instances = Array.isArray(sections) ? sections : isObject(sections) ? Object.values(sections) : [];
103
- for (const inst of instances) {
104
- if (isObject(inst) && isNonEmptyString(inst.type)) types.add(inst.type);
105
- }
106
- }
107
- }
108
- return types;
109
- }
110
- function validateSettingInto(bag, setting, pathPrefix, seenIds) {
111
- if (!isObject(setting)) {
112
- bag.err("schema.setting.invalid", "Setting must be an object.", pathPrefix);
113
- return;
114
- }
115
- const type = setting.type;
116
- if (!isNonEmptyString(type)) {
117
- bag.err("schema.setting.type.missing", "Setting is missing a `type`.", pathPrefix);
118
- } else if (!KNOWN_SETTING_TYPES.has(type)) {
119
- bag.warn(
120
- "schema.setting.type.unknown",
121
- `Unknown setting type "${type}" \u2014 the customizer will fall back to a text input.`,
122
- pathPrefix
123
- );
124
- }
125
- const presentational = isNonEmptyString(type) && PRESENTATIONAL_SETTING_TYPES.has(type);
126
- if (!presentational) {
127
- if (!isNonEmptyString(setting.id)) {
128
- bag.err("schema.setting.id.missing", "Setting is missing an `id`.", pathPrefix);
129
- } else {
130
- if (seenIds.has(setting.id)) {
131
- bag.err(
132
- "schema.setting.id.duplicate",
133
- `Duplicate setting id "${setting.id}".`,
134
- pathPrefix
135
- );
136
- }
137
- seenIds.add(setting.id);
138
- }
139
- if (!isNonEmptyString(setting.label)) {
140
- bag.warn("schema.setting.label.missing", "Setting has no `label`.", pathPrefix);
141
- }
142
- }
143
- if (type === "select" || type === "radio") {
144
- const options = setting.options;
145
- if (!Array.isArray(options) || options.length === 0) {
146
- bag.err(
147
- "schema.setting.options.missing",
148
- `"${type}" setting "${String(setting.id)}" must declare a non-empty \`options\` array.`,
149
- pathPrefix
150
- );
151
- }
152
- }
153
- if (type === "range") {
154
- if (typeof setting.min !== "number" || typeof setting.max !== "number") {
155
- bag.err(
156
- "schema.setting.range.bounds",
157
- `"range" setting "${String(setting.id)}" must declare numeric \`min\` and \`max\`.`,
158
- pathPrefix
159
- );
160
- } else if (setting.min >= setting.max) {
161
- bag.err(
162
- "schema.setting.range.order",
163
- `"range" setting "${String(setting.id)}" has min >= max.`,
164
- pathPrefix
165
- );
166
- }
167
- }
168
- }
169
- function validateSectionSchema(schema, opts = {}) {
170
- const bag = new IssueBag();
171
- const where = opts.filenameType ? `${opts.filenameType}.json` : "section schema";
172
- if (!isObject(schema)) {
173
- bag.err("schema.invalid", "Section schema must be a JSON object.", where);
174
- return bag.result();
175
- }
176
- const type = schema.type;
177
- if (!isNonEmptyString(type)) {
178
- bag.err("schema.type.missing", "Section schema is missing a `type`.", where);
179
- } else {
180
- if (!SECTION_TYPE_RE.test(type)) {
181
- bag.err(
182
- "schema.type.format",
183
- `Section type "${type}" must match ${SECTION_TYPE_RE} (lowercase, start with a letter).`,
184
- where
185
- );
186
- }
187
- if (opts.filenameType && type !== opts.filenameType) {
188
- bag.err(
189
- "schema.type.filename_mismatch",
190
- `Schema type "${type}" must equal its filename "${opts.filenameType}" (component-filename = schema-type convention).`,
191
- where
192
- );
193
- }
194
- }
195
- if (!isNonEmptyString(schema.name)) {
196
- bag.err("schema.name.missing", "Section schema is missing a `name`.", where);
197
- }
198
- if (schema.settings === void 0) {
199
- bag.warn("schema.settings.missing", "Section schema has no `settings`.", where);
200
- } else if (!Array.isArray(schema.settings)) {
201
- bag.err("schema.settings.invalid", "`settings` must be an array.", where);
202
- } else {
203
- const seen = /* @__PURE__ */ new Set();
204
- schema.settings.forEach(
205
- (s, i) => validateSettingInto(bag, s, `${where}.settings[${i}]`, seen)
206
- );
207
- }
208
- if (schema.blocks !== void 0) {
209
- if (!Array.isArray(schema.blocks)) {
210
- bag.err("schema.blocks.invalid", "`blocks` must be an array.", where);
211
- } else {
212
- schema.blocks.forEach((b, i) => {
213
- if (!isObject(b) || !isNonEmptyString(b.type)) {
214
- bag.err("schema.block.type.missing", `Block[${i}] is missing a \`type\`.`, where);
215
- return;
216
- }
217
- if (!SECTION_TYPE_RE.test(b.type)) {
218
- bag.err(
219
- "schema.block.type.format",
220
- `Block type "${b.type}" must match ${SECTION_TYPE_RE}.`,
221
- where
222
- );
223
- }
224
- if (Array.isArray(b.settings)) {
225
- const seen = /* @__PURE__ */ new Set();
226
- b.settings.forEach(
227
- (s, j) => validateSettingInto(bag, s, `${where}.blocks[${i}].settings[${j}]`, seen)
228
- );
229
- }
230
- });
231
- }
232
- }
233
- return bag.result();
234
- }
235
- function validateSettingsAgainstSchema(settings, schema, pathPrefix = "settings") {
236
- const bag = new IssueBag();
237
- if (!isObject(settings)) {
238
- bag.err("instance.settings.invalid", "Section settings must be an object.", pathPrefix);
239
- return bag.result();
240
- }
241
- const defs = Array.isArray(schema.settings) ? schema.settings : [];
242
- const byId = /* @__PURE__ */ new Map();
243
- for (const d of defs) if (isNonEmptyString(d?.id)) byId.set(d.id, d);
244
- for (const [key, value] of Object.entries(settings)) {
245
- const def = byId.get(key);
246
- if (!def) {
247
- bag.warn(
248
- "instance.setting.unknown",
249
- `Setting "${key}" is not declared in the "${String(schema.type)}" schema.`,
250
- `${pathPrefix}.${key}`
251
- );
252
- continue;
253
- }
254
- if (value === null || value === void 0) continue;
255
- const p = `${pathPrefix}.${key}`;
256
- if ((def.type === "select" || def.type === "radio") && Array.isArray(def.options)) {
257
- const allowed = def.options.map((o) => o.value);
258
- if (typeof value === "string" && !allowed.includes(value)) {
259
- bag.err(
260
- "instance.setting.option.invalid",
261
- `"${key}" = "${value}" is not one of: ${allowed.join(", ")}.`,
262
- p
263
- );
264
- }
265
- }
266
- if (def.type === "range" && typeof value === "number") {
267
- if (typeof def.min === "number" && value < def.min) {
268
- bag.err("instance.setting.range.under", `"${key}" = ${value} is below min ${def.min}.`, p);
269
- }
270
- if (typeof def.max === "number" && value > def.max) {
271
- bag.err("instance.setting.range.over", `"${key}" = ${value} is above max ${def.max}.`, p);
272
- }
273
- }
274
- if (def.type === "checkbox" && typeof value !== "boolean") {
275
- bag.warn("instance.setting.type.mismatch", `"${key}" should be a boolean.`, p);
276
- }
277
- if ((def.type === "number" || def.type === "range") && typeof value !== "number") {
278
- bag.warn("instance.setting.type.mismatch", `"${key}" should be a number.`, p);
279
- }
280
- }
281
- return bag.result();
282
- }
283
- function validateManifestCore(bag, m) {
284
- if (!isNonEmptyString(m.id)) {
285
- bag.err("manifest.id.missing", "theme.json is missing `id`.", "id");
286
- } else if (!THEME_ID_RE.test(m.id)) {
287
- bag.err(
288
- "manifest.id.format",
289
- `Theme id "${m.id}" must be lowercase alphanumeric with dashes/underscores (no leading/trailing separator).`,
290
- "id"
291
- );
292
- }
293
- const name = m.name;
294
- const nameOk = isNonEmptyString(name) || isObject(name) && Object.values(name).some((v) => isNonEmptyString(v));
295
- if (!nameOk) {
296
- bag.err("manifest.name.missing", "theme.json is missing a non-empty `name`.", "name");
297
- }
298
- if (!isNonEmptyString(m.version)) {
299
- bag.err("manifest.version.missing", "theme.json is missing `version`.", "version");
300
- } else if (!SEMVER_RE.test(m.version)) {
301
- bag.err("manifest.version.invalid", `Version "${m.version}" is not valid semver.`, "version");
302
- }
303
- if (!isNonEmptyString(m.author)) {
304
- bag.err("manifest.author.missing", "theme.json is missing `author`.", "author");
305
- }
306
- if (m.min_sdk_version !== void 0 && !SEMVER_RE.test(String(m.min_sdk_version))) {
307
- bag.warn("manifest.min_sdk_version.invalid", "`min_sdk_version` is not valid semver.", "min_sdk_version");
308
- }
309
- }
310
- function validateManifest(manifest, ctx = {}) {
311
- const bag = new IssueBag();
312
- if (!isObject(manifest)) {
313
- bag.err("manifest.invalid", "theme.json must be a JSON object.", "theme.json");
314
- return bag.result();
315
- }
316
- validateManifestCore(bag, manifest);
317
- const presets = manifest.presets;
318
- if (!isObject(presets) || Object.keys(presets).length === 0) {
319
- bag.warn("manifest.presets.empty", "theme.json has no presets \u2014 merchants start with an empty page.", "presets");
320
- } else {
321
- if (ctx.sectionTypes) {
322
- for (const type of collectPresetSectionTypes(manifest)) {
323
- if (!ctx.sectionTypes.has(type)) {
324
- bag.err(
325
- "manifest.preset.unknown_section",
326
- `Preset references section type "${type}" with no schemas/sections/${type}.json.`,
327
- "presets"
328
- );
329
- }
330
- }
331
- }
332
- const templates = isObject(presets.templates) ? presets.templates : {};
333
- for (const t of REQUIRED_TEMPLATES) {
334
- if (!(t in templates)) {
335
- bag.warn(
336
- "manifest.template.missing",
337
- `No preset for required template "${t}" \u2014 the storefront will fall back to its built-in.`,
338
- `presets.templates.${t}`
339
- );
340
- }
341
- }
342
- }
343
- return bag.result();
344
- }
345
- function validateBuiltManifest(builtManifest, importMap, opts = {}) {
346
- const bag = new IssueBag();
347
- if (!isObject(builtManifest)) {
348
- bag.err("built.invalid", "manifest.json must be a JSON object.", "manifest.json");
349
- return bag.result();
350
- }
351
- validateManifestCore(bag, builtManifest);
352
- const schemas = builtManifest.section_schemas;
353
- const shipped = new Set(isObject(schemas) ? Object.keys(schemas) : []);
354
- if (!isObject(schemas)) {
355
- bag.warn("built.section_schemas.missing", "manifest.json has no `section_schemas`.", "section_schemas");
356
- }
357
- for (const type of collectPresetSectionTypes(builtManifest)) {
358
- if (!shipped.has(type)) {
359
- bag.err(
360
- "built.preset.unknown_section",
361
- `Preset references section type "${type}" not present in section_schemas.`,
362
- "presets"
363
- );
364
- }
365
- }
366
- if (isObject(schemas)) {
367
- for (const [type, schema] of Object.entries(schemas)) {
368
- const r = validateSectionSchema(schema, { filenameType: type });
369
- for (const issue of r.issues) bag.issues.push(issue);
370
- }
371
- }
372
- if (!isNonEmptyString(builtManifest.plugin_version)) {
373
- bag.warn("built.plugin_version.missing", "manifest.json has no `plugin_version`.", "plugin_version");
374
- }
375
- if (importMap !== void 0) {
376
- if (!isObject(importMap)) {
377
- bag.err("importmap.invalid", "import-map.json must be a JSON object.", "import-map.json");
378
- } else {
379
- const cv = importMap.contract_version;
380
- if (typeof cv !== "number") {
381
- bag.warn(
382
- "importmap.contract_version.missing",
383
- "import-map.json has no numeric `contract_version` \u2014 built by an older plugin.",
384
- "contract_version"
385
- );
386
- } else if (typeof opts.hostContractVersion === "number" && cv > opts.hostContractVersion) {
387
- bag.err(
388
- "importmap.contract_version.incompatible",
389
- `Theme built for contract v${cv}; this platform supports up to v${opts.hostContractVersion}.`,
390
- "contract_version"
391
- );
392
- }
393
- }
394
- }
395
- return bag.result();
396
- }
397
- function mergeResults(...results) {
398
- const issues = results.flatMap((r) => r.issues);
399
- return { valid: !issues.some((i) => i.level === "error"), issues };
400
- }
401
- var ShopContext = createContext(null);
402
- var ProductContext = createContext(null);
403
- var CollectionContext = createContext(null);
404
- var CartContext = createContext(null);
405
- var CustomerContext = createContext(null);
406
- var ThemeSettingsContext = createContext(null);
407
- var LocalizationContext = createContext(null);
408
- var PageContext = createContext(null);
409
- var CurrentTemplateContext = createContext("home");
410
- var NavigationContext = createContext(
411
- {}
412
- );
413
- function useLocalization() {
414
- const ctx = useContext(LocalizationContext);
415
- if (!ctx) throw new Error("useLocalization must be used within NuMuProvider");
416
- return ctx;
417
- }
418
- function useDirection() {
419
- const { direction } = useLocalization();
420
- return direction;
421
- }
422
- function useLocale() {
423
- const { locale } = useLocalization();
424
- return locale;
425
- }
426
- function useTranslation() {
427
- const { translations, locale } = useLocalization();
428
- return {
429
- t: (key, fallback) => translations[key] || fallback || key,
430
- locale
431
- };
432
- }
433
- function useFieldTranslation(entity, field) {
434
- const { locale } = useLocalization();
435
- if (!entity) return void 0;
436
- const translated = entity.attributes && typeof entity.attributes === "object" ? entity.attributes[`${field}_${locale}`] : void 0;
437
- if (typeof translated === "string" && translated) return translated;
438
- const base = entity[field];
439
- if (typeof base === "string") return base;
440
- if (base == null) return void 0;
441
- return String(base);
442
- }
443
- function useNumberFormat() {
444
- const { formatNumber } = useLocalization();
445
- return formatNumber;
446
- }
447
-
448
- // src/hooks/useShop.ts
449
- var DEFAULT_PROTOCOL = typeof window !== "undefined" ? window.location.protocol.replace(":", "") : "https";
450
- function resolveDomain(store) {
451
- if (store.domain) return store.domain;
452
- if (store.subdomain) {
453
- const platform = typeof window !== "undefined" && window.__NUMU_PLATFORM_DOMAIN || "numueg.app";
454
- return `${store.subdomain}.${platform}`;
455
- }
456
- return store.slug;
457
- }
458
- function useShop() {
459
- const ctx = useContext(ShopContext);
460
- if (!ctx) throw new Error("useShop must be used within NuMuProvider");
461
- const locale = useLocale();
462
- return useMemo(() => {
463
- const domain = resolveDomain(ctx);
464
- const settings = ctx.settings;
465
- const localePrefixEnabled = Boolean(
466
- settings && settings.locale_url_prefix_enabled
467
- );
468
- const defaultLocale = ctx.default_language || "en";
469
- const prefixedLocales = settings?.locale_url_prefix_locales || null;
470
- const shouldPrefix = (l) => {
471
- if (!localePrefixEnabled) return false;
472
- if (prefixedLocales) return prefixedLocales.includes(l);
473
- return l !== defaultLocale;
474
- };
475
- const formatUrl = (path) => {
476
- if (!path) return `${DEFAULT_PROTOCOL}://${domain}/`;
477
- if (/^https?:\/\//i.test(path)) return path;
478
- if (path.startsWith("//")) return `${DEFAULT_PROTOCOL}:${path}`;
479
- let normalized = path.startsWith("/") ? path : `/${path}`;
480
- if (locale && shouldPrefix(locale)) {
481
- const prefix = `/${locale}/`;
482
- const root = `/${locale}`;
483
- if (normalized !== root && !normalized.startsWith(prefix)) {
484
- normalized = `${root}${normalized}`;
485
- }
486
- }
487
- return `${DEFAULT_PROTOCOL}://${domain}${normalized}`;
488
- };
489
- return { ...ctx, domain, formatUrl };
490
- }, [ctx, locale]);
491
- }
492
11
  function useProduct() {
493
12
  const ctx = useContext(ProductContext);
494
13
  if (!ctx) throw new Error("useProduct must be used within ProductProvider");
@@ -510,20 +29,9 @@ function useCart() {
510
29
  if (!ctx) throw new Error("useCart must be used within NuMuProvider");
511
30
  return ctx;
512
31
  }
513
- function useCustomer() {
514
- return useContext(CustomerContext);
515
- }
516
- function useThemeSettings() {
517
- const ctx = useContext(ThemeSettingsContext);
518
- if (!ctx) throw new Error("useThemeSettings must be used within NuMuProvider");
519
- return ctx;
520
- }
521
32
  function useCurrentTemplate() {
522
33
  return useContext(CurrentTemplateContext);
523
34
  }
524
- function usePage() {
525
- return useContext(PageContext);
526
- }
527
35
  var SectionContext = createContext(null);
528
36
  function useSection() {
529
37
  const ctx = useContext(SectionContext);
@@ -533,13 +41,57 @@ function useSection() {
533
41
  function useSectionOptional() {
534
42
  return useContext(SectionContext);
535
43
  }
44
+ var EMPTY = Object.freeze([]);
45
+ function useSectionGroup(group) {
46
+ const settings = useContext(ThemeSettingsContext);
47
+ return useMemo(() => {
48
+ const grp = settings?.section_groups?.[group];
49
+ if (!grp || !Array.isArray(grp.order) || grp.order.length === 0) {
50
+ return EMPTY;
51
+ }
52
+ const sections = grp.sections ?? {};
53
+ const out = [];
54
+ for (const id of grp.order) {
55
+ const instance = sections[id];
56
+ if (!instance) continue;
57
+ out.push({ id, ...instance });
58
+ }
59
+ return out;
60
+ }, [settings, group]);
61
+ }
62
+ function useCurrency() {
63
+ const ctx = useContext(CurrencyContext);
64
+ const shop = useShop();
65
+ const fallback = useMemo(() => {
66
+ const ccy = shop?.currency || "EGP";
67
+ return {
68
+ base: ccy,
69
+ selected: ccy,
70
+ presentment: [ccy],
71
+ rates: {},
72
+ autoConvert: false,
73
+ loading: false,
74
+ setSelected: () => {
75
+ },
76
+ convert: (cents) => cents
77
+ };
78
+ }, [shop?.currency]);
79
+ return ctx ?? fallback;
80
+ }
536
81
 
537
82
  // src/hooks/useMoney.ts
538
83
  function useMoney(currencyOverride) {
539
84
  const { formatMoney } = useLocalization();
540
85
  const shop = useShop();
541
- const ccy = currencyOverride || shop?.currency;
542
- return (amount) => formatMoney(amount, ccy);
86
+ const { selected, base, autoConvert, convert } = useCurrency();
87
+ return (amount) => {
88
+ const shouldConvert = autoConvert && !currencyOverride && !!selected && selected !== base;
89
+ if (shouldConvert) {
90
+ const converted = convert(Math.round(amount * 100), selected) / 100;
91
+ return formatMoney(converted, selected);
92
+ }
93
+ return formatMoney(amount, currencyOverride || shop?.currency);
94
+ };
543
95
  }
544
96
  var DEFAULT_WIDTHS = [320, 480, 640, 768, 1024, 1280, 1600, 1920];
545
97
  var DEFAULT_SIZES = "(min-width: 1024px) 25vw, (min-width: 640px) 50vw, 100vw";
@@ -553,81 +105,6 @@ function useImage(src, opts = {}) {
553
105
  return { src, srcSet, sizes, alt };
554
106
  }, [src, sizes, widths, alt]);
555
107
  }
556
- function useProducts(opts = {}) {
557
- const { limit, fetchIfMissing = false } = opts;
558
- const page = usePage();
559
- const shop = useShop();
560
- const initial = page?.data?.products ?? null;
561
- const [products, setProducts] = useState(initial ?? []);
562
- const [loading, setLoading] = useState(
563
- initial == null && fetchIfMissing
564
- );
565
- const [error, setError] = useState(null);
566
- useEffect(() => {
567
- if (initial != null) return;
568
- if (!fetchIfMissing) return;
569
- if (!shop?.id) return;
570
- let cancelled = false;
571
- (async () => {
572
- try {
573
- const params = new URLSearchParams({ store_id: shop.id });
574
- if (limit) params.set("limit", String(limit));
575
- const res = await fetch(`/api/products?${params.toString()}`);
576
- if (!res.ok) throw new Error(`/api/products \u2192 ${res.status}`);
577
- const data = await res.json();
578
- if (cancelled) return;
579
- setProducts(data.products ?? []);
580
- setLoading(false);
581
- } catch (err) {
582
- if (cancelled) return;
583
- setError(err instanceof Error ? err : new Error(String(err)));
584
- setLoading(false);
585
- }
586
- })();
587
- return () => {
588
- cancelled = true;
589
- };
590
- }, [initial, fetchIfMissing, limit, shop?.id]);
591
- const sliced = limit != null ? products.slice(0, limit) : products;
592
- return { products: sliced, loading, error };
593
- }
594
- function useCollections(opts = {}) {
595
- const { limit, fetchIfMissing = false } = opts;
596
- const page = usePage();
597
- const shop = useShop();
598
- const initial = page?.data?.collections ?? null;
599
- const [collections, setCollections] = useState(initial ?? []);
600
- const [loading, setLoading] = useState(
601
- initial == null && fetchIfMissing
602
- );
603
- const [error, setError] = useState(null);
604
- useEffect(() => {
605
- if (initial != null) return;
606
- if (!fetchIfMissing) return;
607
- if (!shop?.id) return;
608
- let cancelled = false;
609
- (async () => {
610
- try {
611
- const params = new URLSearchParams({ store_id: shop.id });
612
- const res = await fetch(`/api/collections?${params.toString()}`);
613
- if (!res.ok) throw new Error(`/api/collections \u2192 ${res.status}`);
614
- const data = await res.json();
615
- if (cancelled) return;
616
- setCollections(data.collections ?? []);
617
- setLoading(false);
618
- } catch (err) {
619
- if (cancelled) return;
620
- setError(err instanceof Error ? err : new Error(String(err)));
621
- setLoading(false);
622
- }
623
- })();
624
- return () => {
625
- cancelled = true;
626
- };
627
- }, [initial, fetchIfMissing, shop?.id]);
628
- const sliced = limit != null ? collections.slice(0, limit) : collections;
629
- return { collections: sliced, loading, error };
630
- }
631
108
  var NOOP_ACTIONS = {
632
109
  login: async () => ({}),
633
110
  register: async () => ({}),
@@ -997,7 +474,7 @@ function useNavigation(handle, options) {
997
474
  }, [handle, hostItems === null]);
998
475
  return { items, loading, error };
999
476
  }
1000
- var EMPTY = {
477
+ var EMPTY2 = {
1001
478
  products: [],
1002
479
  collections: [],
1003
480
  pages: [],
@@ -1012,7 +489,7 @@ function useSearch(query, options = {}) {
1012
489
  debounceMs = 200,
1013
490
  immediate = false
1014
491
  } = options;
1015
- const [results, setResults] = useState(EMPTY);
492
+ const [results, setResults] = useState(EMPTY2);
1016
493
  const [loading, setLoading] = useState(false);
1017
494
  const [error, setError] = useState(null);
1018
495
  const lastRequestId = useRef(0);
@@ -1020,7 +497,7 @@ function useSearch(query, options = {}) {
1020
497
  if (typeof window === "undefined") return;
1021
498
  const trimmed = query.trim();
1022
499
  if (!trimmed) {
1023
- setResults(EMPTY);
500
+ setResults(EMPTY2);
1024
501
  setLoading(false);
1025
502
  setError(null);
1026
503
  return;
@@ -1038,7 +515,7 @@ function useSearch(query, options = {}) {
1038
515
  });
1039
516
  if (requestId !== lastRequestId.current) return;
1040
517
  if (!res.ok) {
1041
- setResults(EMPTY);
518
+ setResults(EMPTY2);
1042
519
  return;
1043
520
  }
1044
521
  const json = await res.json();
@@ -1054,7 +531,7 @@ function useSearch(query, options = {}) {
1054
531
  } catch (err) {
1055
532
  if (requestId !== lastRequestId.current) return;
1056
533
  setError(err instanceof Error ? err : new Error(String(err)));
1057
- setResults(EMPTY);
534
+ setResults(EMPTY2);
1058
535
  } finally {
1059
536
  if (requestId === lastRequestId.current) setLoading(false);
1060
537
  }
@@ -1159,63 +636,218 @@ function useAnalytics() {
1159
636
  );
1160
637
  return useMemo(() => ({ track }), [track]);
1161
638
  }
639
+ var cache2 = /* @__PURE__ */ new Map();
640
+ function buildSnapshot(entry) {
641
+ return {
642
+ data: entry.data,
643
+ error: entry.error,
644
+ isLoading: !entry.settled,
645
+ isValidating: entry.inFlight
646
+ };
647
+ }
648
+ function commit(entry) {
649
+ entry.snapshot = buildSnapshot(entry);
650
+ entry.subscribers.forEach((cb) => cb());
651
+ }
652
+ function getEntry(key, initialData) {
653
+ let entry = cache2.get(key);
654
+ if (!entry) {
655
+ entry = {
656
+ data: initialData,
657
+ error: void 0,
658
+ settled: false,
659
+ inFlight: false,
660
+ seq: 0,
661
+ lastFetchAt: 0,
662
+ promise: void 0,
663
+ controller: void 0,
664
+ fetcher: void 0,
665
+ subscribers: /* @__PURE__ */ new Set(),
666
+ snapshot: {
667
+ data: initialData,
668
+ error: void 0,
669
+ isLoading: true,
670
+ isValidating: false
671
+ }
672
+ };
673
+ cache2.set(key, entry);
674
+ }
675
+ return entry;
676
+ }
677
+ function revalidateResource(key, fetcher, opts) {
678
+ const entry = getEntry(key);
679
+ entry.fetcher = fetcher;
680
+ if (!opts?.force && entry.promise) return entry.promise;
681
+ const mySeq = ++entry.seq;
682
+ if (entry.controller) entry.controller.abort();
683
+ const controller = new AbortController();
684
+ entry.controller = controller;
685
+ entry.inFlight = true;
686
+ commit(entry);
687
+ const run = (async () => {
688
+ try {
689
+ const data = await fetcher(controller.signal);
690
+ if (mySeq !== entry.seq) return;
691
+ entry.data = data;
692
+ entry.error = void 0;
693
+ entry.settled = true;
694
+ entry.lastFetchAt = Date.now();
695
+ } catch (err) {
696
+ if (controller.signal.aborted) return;
697
+ if (mySeq !== entry.seq) return;
698
+ const error = err instanceof Error ? err : new Error(String(err));
699
+ entry.error = error;
700
+ entry.settled = true;
701
+ entry.lastFetchAt = Date.now();
702
+ opts?.onError?.(error, key);
703
+ } finally {
704
+ if (mySeq === entry.seq) {
705
+ entry.inFlight = false;
706
+ entry.controller = void 0;
707
+ commit(entry);
708
+ }
709
+ }
710
+ })();
711
+ entry.promise = run;
712
+ void run.finally(() => {
713
+ if (entry.promise === run) entry.promise = void 0;
714
+ });
715
+ return run;
716
+ }
717
+ function mutateResource(key, next, opts) {
718
+ const entry = getEntry(key);
719
+ const value = typeof next === "function" ? next(entry.data) : next;
720
+ entry.data = value;
721
+ entry.error = void 0;
722
+ entry.settled = true;
723
+ commit(entry);
724
+ if (opts?.revalidate && entry.fetcher) {
725
+ void revalidateResource(key, entry.fetcher, { force: true });
726
+ }
727
+ }
728
+ function readResource(key) {
729
+ return cache2.get(key)?.data;
730
+ }
731
+ var NOOP = () => {
732
+ };
733
+ function useCachedResource(key, fetcher, options) {
734
+ const enabled = options?.enabled !== false && key != null && key !== "";
735
+ const revalidateOnMount = options?.revalidateOnMount !== false;
736
+ const dedupeIntervalMs = options?.dedupeIntervalMs ?? 2e3;
737
+ const cacheKey = enabled ? key : "";
738
+ const fetcherRef = useRef(fetcher);
739
+ fetcherRef.current = fetcher;
740
+ const onErrorRef = useRef(options?.onError);
741
+ onErrorRef.current = options?.onError;
742
+ const initialDataRef = useRef(options?.initialData);
743
+ const fallbackRef = useRef(null);
744
+ if (fallbackRef.current === null) {
745
+ fallbackRef.current = {
746
+ data: initialDataRef.current,
747
+ error: void 0,
748
+ isLoading: enabled && revalidateOnMount,
749
+ isValidating: false
750
+ };
751
+ }
752
+ const subscribe = useCallback(
753
+ (cb) => {
754
+ if (!enabled) return NOOP;
755
+ const entry = getEntry(cacheKey, initialDataRef.current);
756
+ entry.subscribers.add(cb);
757
+ return () => {
758
+ entry.subscribers.delete(cb);
759
+ };
760
+ },
761
+ [cacheKey, enabled]
762
+ );
763
+ const getSnapshot = useCallback(() => {
764
+ if (!enabled) return fallbackRef.current;
765
+ return getEntry(cacheKey, initialDataRef.current).snapshot;
766
+ }, [cacheKey, enabled]);
767
+ const getServerSnapshot = useCallback(
768
+ () => fallbackRef.current,
769
+ []
770
+ );
771
+ const state = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
772
+ useEffect(() => {
773
+ if (!enabled) return;
774
+ const entry = getEntry(cacheKey, initialDataRef.current);
775
+ const fresh = entry.settled && Date.now() - entry.lastFetchAt < dedupeIntervalMs;
776
+ if (revalidateOnMount && !fresh) {
777
+ void revalidateResource(
778
+ cacheKey,
779
+ (signal) => fetcherRef.current(signal),
780
+ { onError: (e, k) => onErrorRef.current?.(e, k) }
781
+ );
782
+ }
783
+ }, [cacheKey, enabled, revalidateOnMount, dedupeIntervalMs]);
784
+ const mutate = useCallback(
785
+ (next, opts) => {
786
+ if (!enabled) return;
787
+ mutateResource(cacheKey, next, opts);
788
+ },
789
+ [cacheKey, enabled]
790
+ );
791
+ const revalidate = useCallback(() => {
792
+ if (!enabled) return Promise.resolve();
793
+ return revalidateResource(
794
+ cacheKey,
795
+ (signal) => fetcherRef.current(signal),
796
+ { force: true, onError: (e, k) => onErrorRef.current?.(e, k) }
797
+ );
798
+ }, [cacheKey, enabled]);
799
+ return {
800
+ data: state.data,
801
+ error: state.error,
802
+ isLoading: state.isLoading,
803
+ isValidating: state.isValidating,
804
+ mutate,
805
+ revalidate
806
+ };
807
+ }
808
+
809
+ // src/hooks/useApp.ts
1162
810
  function useApp(slug) {
1163
811
  const shop = useShop();
1164
- const [state, setState] = useState({ data: null, loading: true, available: false, error: null });
1165
- const fetchApp = useCallback(async () => {
1166
- if (!slug) {
1167
- setState({ data: null, loading: false, available: false, error: null });
1168
- return;
1169
- }
1170
- setState((s) => ({ ...s, loading: true, error: null }));
1171
- try {
812
+ const key = slug ? `numu:app:${shop.id}:${slug}` : "";
813
+ const fetcher = useCallback(
814
+ async (signal) => {
1172
815
  const url = `/api/storefront/apps/${encodeURIComponent(slug)}`;
1173
816
  const res = await fetch(url, {
1174
817
  credentials: "include",
1175
- headers: { Accept: "application/json" }
818
+ headers: { Accept: "application/json" },
819
+ signal
1176
820
  });
1177
821
  if (res.status === 404) {
1178
- setState({
1179
- data: null,
1180
- loading: false,
1181
- available: false,
1182
- error: null
1183
- });
1184
- return;
822
+ return { payload: null, available: false };
1185
823
  }
1186
824
  if (!res.ok) {
1187
825
  throw new Error(`useApp(${slug}) failed: HTTP ${res.status}`);
1188
826
  }
1189
827
  const body = await res.json();
1190
- const payload = body.data;
1191
- if (!payload) {
1192
- setState({
1193
- data: null,
1194
- loading: false,
1195
- available: false,
1196
- error: null
1197
- });
1198
- return;
1199
- }
1200
- setState({
1201
- data: payload,
1202
- loading: false,
1203
- available: payload.available !== false,
1204
- error: null
1205
- });
1206
- } catch (e) {
1207
- setState({
1208
- data: null,
1209
- loading: false,
1210
- available: false,
1211
- error: e instanceof Error ? e : new Error(String(e))
1212
- });
1213
- }
1214
- }, [slug, shop.id]);
1215
- useEffect(() => {
1216
- void fetchApp();
1217
- }, [fetchApp]);
1218
- return { ...state, refresh: fetchApp };
828
+ const payload = body.data ?? null;
829
+ if (!payload) return { payload: null, available: false };
830
+ return {
831
+ payload,
832
+ available: payload.available !== false
833
+ };
834
+ },
835
+ [slug]
836
+ );
837
+ const { data, isLoading, error, revalidate } = useCachedResource(key, fetcher, {
838
+ enabled: Boolean(slug),
839
+ initialData: { payload: null, available: false }
840
+ });
841
+ const refresh = useCallback(async () => {
842
+ await revalidate();
843
+ }, [revalidate]);
844
+ return {
845
+ data: error ? null : data?.payload ?? null,
846
+ loading: isLoading,
847
+ available: error ? false : data?.available ?? false,
848
+ error: error ?? null,
849
+ refresh
850
+ };
1219
851
  }
1220
852
  function storageKey(storeId) {
1221
853
  return `numu_wishlist_${storeId}`;
@@ -1231,109 +863,95 @@ function readLocal(storeId) {
1231
863
  return [];
1232
864
  }
1233
865
  }
1234
- function writeLocal(storeId, items) {
866
+ function persistWishlist(storeId, items) {
1235
867
  if (typeof window === "undefined") return;
1236
- try {
1237
- window.localStorage.setItem(storageKey(storeId), JSON.stringify(items));
1238
- } catch {
1239
- }
868
+ window.localStorage.setItem(storageKey(storeId), JSON.stringify(items));
1240
869
  }
1241
870
  function sameItem(a, productId, variantId) {
1242
871
  return a.product_id === productId && (a.variant_id ?? null) === (variantId ?? null);
1243
872
  }
873
+ var EMPTY3 = [];
1244
874
  function useWishlist(storeId) {
1245
- const customer = useCustomer();
1246
- const [items, setItems] = useState([]);
1247
- const [loading, setLoading] = useState(true);
1248
- useEffect(() => {
1249
- if (typeof window === "undefined") return;
1250
- setItems(readLocal(storeId));
1251
- setLoading(false);
1252
- }, [storeId, customer?.id]);
875
+ const key = storageKey(storeId);
876
+ const fetcher = useCallback(
877
+ () => Promise.resolve(readLocal(storeId)),
878
+ [storeId]
879
+ );
880
+ const { data, isLoading, mutate } = useCachedResource(
881
+ key,
882
+ fetcher,
883
+ { initialData: EMPTY3 }
884
+ );
885
+ const items = data ?? EMPTY3;
1253
886
  const has = useCallback(
1254
887
  (productId, variantId) => items.some((it) => sameItem(it, productId, variantId)),
1255
888
  [items]
1256
889
  );
890
+ const applyOptimistic = useCallback(
891
+ (compute) => {
892
+ const prev = readResource(key) ?? EMPTY3;
893
+ const next = compute(prev);
894
+ if (next === null) return;
895
+ mutate(next, { revalidate: false });
896
+ try {
897
+ persistWishlist(storeId, next);
898
+ } catch {
899
+ mutate(prev, { revalidate: false });
900
+ }
901
+ },
902
+ [key, storeId, mutate]
903
+ );
1257
904
  const addToWishlist = useCallback(
1258
- (productId, variantId) => {
1259
- setItems((prev) => {
1260
- if (prev.some((it) => sameItem(it, productId, variantId))) {
1261
- return prev;
905
+ (productId, variantId) => applyOptimistic(
906
+ (prev) => prev.some((it) => sameItem(it, productId, variantId)) ? null : [
907
+ ...prev,
908
+ {
909
+ product_id: productId,
910
+ variant_id: variantId ?? null,
911
+ added_at: Date.now()
1262
912
  }
1263
- const next = [
1264
- ...prev,
1265
- {
1266
- product_id: productId,
1267
- variant_id: variantId ?? null,
1268
- added_at: Date.now()
1269
- }
1270
- ];
1271
- writeLocal(storeId, next);
1272
- return next;
1273
- });
1274
- },
1275
- [storeId]
913
+ ]
914
+ ),
915
+ [applyOptimistic]
1276
916
  );
1277
917
  const removeFromWishlist = useCallback(
1278
- (productId, variantId) => {
1279
- setItems((prev) => {
1280
- const next = prev.filter((it) => !sameItem(it, productId, variantId));
1281
- writeLocal(storeId, next);
1282
- return next;
1283
- });
1284
- },
1285
- [storeId]
918
+ (productId, variantId) => applyOptimistic((prev) => {
919
+ const next = prev.filter((it) => !sameItem(it, productId, variantId));
920
+ return next.length === prev.length ? null : next;
921
+ }),
922
+ [applyOptimistic]
923
+ );
924
+ const clear = useCallback(
925
+ () => applyOptimistic((prev) => prev.length === 0 ? null : []),
926
+ [applyOptimistic]
1286
927
  );
1287
- const clear = useCallback(() => {
1288
- setItems([]);
1289
- writeLocal(storeId, []);
1290
- }, [storeId]);
1291
- return { items, loading, has, addToWishlist, removeFromWishlist, clear };
928
+ return { items, loading: isLoading, has, addToWishlist, removeFromWishlist, clear };
1292
929
  }
930
+ var EMPTY4 = [];
1293
931
  function useRelatedProducts(productId, options = {}) {
1294
932
  const limit = options.limit ?? 4;
1295
- const [items, setItems] = useState([]);
1296
- const [loading, setLoading] = useState(false);
1297
- const [error, setError] = useState(null);
1298
- useEffect(() => {
1299
- if (typeof window === "undefined") return;
1300
- if (!productId) {
1301
- setItems([]);
1302
- setLoading(false);
1303
- setError(null);
1304
- return;
1305
- }
1306
- let cancelled = false;
1307
- setLoading(true);
1308
- setError(null);
1309
- void (async () => {
1310
- try {
1311
- const res = await fetch(
1312
- `/api/storefront/products/${encodeURIComponent(productId)}/related?limit=${limit}`,
1313
- { cache: "no-store" }
1314
- );
1315
- if (!res.ok) {
1316
- if (cancelled) return;
1317
- setItems([]);
1318
- return;
1319
- }
1320
- const json = await res.json();
1321
- const list = Array.isArray(json) ? json : Array.isArray(json.data) ? json.data : Array.isArray(json.items) ? json.items : [];
1322
- if (cancelled) return;
1323
- setItems(list);
1324
- } catch (err) {
1325
- if (cancelled) return;
1326
- setError(err instanceof Error ? err : new Error(String(err)));
1327
- setItems([]);
1328
- } finally {
1329
- if (!cancelled) setLoading(false);
933
+ const key = productId ? `numu:related:${productId}:${limit}` : "";
934
+ const fetcher = useCallback(
935
+ async (signal) => {
936
+ const res = await fetch(
937
+ `/api/storefront/products/${encodeURIComponent(
938
+ productId
939
+ )}/related?limit=${limit}`,
940
+ { cache: "no-store", signal }
941
+ );
942
+ if (!res.ok) {
943
+ return EMPTY4;
1330
944
  }
1331
- })();
1332
- return () => {
1333
- cancelled = true;
1334
- };
1335
- }, [productId, limit]);
1336
- return { items, loading, error };
945
+ const json = await res.json();
946
+ return Array.isArray(json) ? json : Array.isArray(json.data) ? json.data : Array.isArray(json.items) ? json.items : EMPTY4;
947
+ },
948
+ [productId, limit]
949
+ );
950
+ const { data, isLoading, error } = useCachedResource(key, fetcher, {
951
+ enabled: Boolean(productId),
952
+ initialData: EMPTY4
953
+ });
954
+ return { items: data ?? EMPTY4, loading: isLoading, error: error ?? null };
1337
955
  }
1338
956
  function useProductSizeChart(productOverride) {
1339
957
  const ctxProduct = useProductOptional();
@@ -1358,86 +976,6 @@ function resolveSizeChart(productAttributes, storeSettings) {
1358
976
  if (hasRows(storeDefault)) return storeDefault;
1359
977
  return null;
1360
978
  }
1361
- var COOKIE_NAME = "numu_currency";
1362
- var COOKIE_MAX_AGE = 60 * 60 * 24 * 30;
1363
- function readCookie(name) {
1364
- if (typeof document === "undefined") return null;
1365
- const match = document.cookie.match(
1366
- new RegExp(`(?:^|;\\s*)${name}=([^;]+)`)
1367
- );
1368
- return match ? decodeURIComponent(match[1]) : null;
1369
- }
1370
- function writeCookie(name, value) {
1371
- if (typeof document === "undefined") return;
1372
- document.cookie = `${name}=${encodeURIComponent(value)}; path=/; max-age=${COOKIE_MAX_AGE}; SameSite=Lax`;
1373
- }
1374
- function useCurrency() {
1375
- const shop = useShop();
1376
- const [config, setConfig] = useState(null);
1377
- const [loading, setLoading] = useState(true);
1378
- const [selected, setSelectedState] = useState("");
1379
- useEffect(() => {
1380
- let cancelled = false;
1381
- (async () => {
1382
- try {
1383
- const res = await fetch(`/api/storefront/currencies`, {
1384
- credentials: "include",
1385
- headers: { Accept: "application/json" }
1386
- });
1387
- if (!res.ok) throw new Error(`currencies: HTTP ${res.status}`);
1388
- const body = await res.json();
1389
- if (cancelled) return;
1390
- setConfig(body.data);
1391
- const cookie = readCookie(COOKIE_NAME);
1392
- const valid = cookie && body.data.presentment.includes(cookie) ? cookie : null;
1393
- setSelectedState(valid || body.data.default_presentment || body.data.base);
1394
- } catch {
1395
- if (cancelled) return;
1396
- setConfig(null);
1397
- setSelectedState(shop.currency || "EGP");
1398
- } finally {
1399
- if (!cancelled) setLoading(false);
1400
- }
1401
- })();
1402
- return () => {
1403
- cancelled = true;
1404
- };
1405
- }, [shop.id, shop.currency]);
1406
- const rates = useMemo(() => {
1407
- const out = {};
1408
- if (!config) return out;
1409
- for (const [k, v] of Object.entries(config.rates)) {
1410
- const n = Number.parseFloat(v);
1411
- if (Number.isFinite(n)) out[k] = n;
1412
- }
1413
- return out;
1414
- }, [config]);
1415
- const convert = useCallback(
1416
- (cents, target) => {
1417
- if (!config) return cents;
1418
- const to = target || selected;
1419
- if (!to || to === config.base) return cents;
1420
- const rate = rates[to];
1421
- if (!rate || !Number.isFinite(rate)) return cents;
1422
- return Math.round(cents * rate);
1423
- },
1424
- [config, rates, selected]
1425
- );
1426
- const setSelected = useCallback((currency) => {
1427
- setSelectedState(currency);
1428
- writeCookie(COOKIE_NAME, currency);
1429
- }, []);
1430
- return {
1431
- base: config?.base || shop.currency || "EGP",
1432
- selected: selected || config?.base || shop.currency || "EGP",
1433
- presentment: config?.presentment || [shop.currency || "EGP"],
1434
- rates,
1435
- autoConvert: Boolean(config?.auto_convert),
1436
- loading,
1437
- setSelected,
1438
- convert
1439
- };
1440
- }
1441
979
 
1442
980
  // src/utils/variants.ts
1443
981
  function findVariantByOptions(product, selection) {
@@ -1893,6 +1431,19 @@ function readCsrfCookie() {
1893
1431
  const match = document.cookie.match(/(?:^|;\s*)numu_csrf=([^;]+)/);
1894
1432
  return match ? decodeURIComponent(match[1]) : null;
1895
1433
  }
1434
+ var CURRENCY_COOKIE_NAME = "numu_currency";
1435
+ var CURRENCY_COOKIE_MAX_AGE = 60 * 60 * 24 * 30;
1436
+ function readCurrencyCookie() {
1437
+ if (typeof document === "undefined") return null;
1438
+ const match = document.cookie.match(
1439
+ new RegExp(`(?:^|;\\s*)${CURRENCY_COOKIE_NAME}=([^;]+)`)
1440
+ );
1441
+ return match ? decodeURIComponent(match[1]) : null;
1442
+ }
1443
+ function writeCurrencyCookie(value) {
1444
+ if (typeof document === "undefined") return;
1445
+ document.cookie = `${CURRENCY_COOKIE_NAME}=${encodeURIComponent(value)}; path=/; max-age=${CURRENCY_COOKIE_MAX_AGE}; SameSite=Lax`;
1446
+ }
1896
1447
  async function postCartMutation(endpoint, body, applyCart, reserveToken) {
1897
1448
  const headers = {
1898
1449
  "Content-Type": "application/json"
@@ -1906,9 +1457,21 @@ async function postCartMutation(endpoint, body, applyCart, reserveToken) {
1906
1457
  headers,
1907
1458
  body: body === void 0 ? void 0 : JSON.stringify(body)
1908
1459
  });
1909
- if (!res.ok) return;
1460
+ if (!res.ok) {
1461
+ let message;
1462
+ try {
1463
+ const err = await res.json();
1464
+ if (err && typeof err === "object") {
1465
+ const m = err.message ?? err.error ?? err.detail;
1466
+ if (typeof m === "string") message = m;
1467
+ }
1468
+ } catch {
1469
+ }
1470
+ return { ok: false, status: res.status, message };
1471
+ }
1910
1472
  const json = await res.json();
1911
1473
  applyCart(unwrapCart(json));
1474
+ return { ok: true, status: res.status };
1912
1475
  }
1913
1476
  function NuMuProvider({
1914
1477
  store,
@@ -1918,6 +1481,7 @@ function NuMuProvider({
1918
1481
  locale: initialLocale,
1919
1482
  translations: initialTranslations,
1920
1483
  currentTemplate = "home",
1484
+ pageTemplate,
1921
1485
  initialProducts,
1922
1486
  initialCollections,
1923
1487
  navigation,
@@ -1927,17 +1491,21 @@ function NuMuProvider({
1927
1491
  () => ({
1928
1492
  type: currentTemplate,
1929
1493
  title: store?.name ?? "",
1494
+ // I3 — resolved alternate template key (e.g. "product.wholesale"),
1495
+ // surfaced via usePage()?.template. Omitted when undefined so a
1496
+ // page on its default template exposes no `template` field.
1497
+ ...pageTemplate ? { template: pageTemplate } : {},
1930
1498
  data: {
1931
1499
  products: initialProducts ?? [],
1932
1500
  collections: initialCollections ?? []
1933
1501
  }
1934
1502
  }),
1935
- [currentTemplate, store?.name, initialProducts, initialCollections]
1503
+ [currentTemplate, pageTemplate, store?.name, initialProducts, initialCollections]
1936
1504
  );
1937
1505
  const [cart, setCart] = useState(
1938
1506
  initialCart || { ...EMPTY_CART, currency: store.currency }
1939
1507
  );
1940
- const [loading, setLoading] = useState(false);
1508
+ const [loading, setLoading] = useState(!initialCart);
1941
1509
  const [locale, setLocale] = useState(() => {
1942
1510
  if (initialLocale) return initialLocale;
1943
1511
  if (typeof document !== "undefined") {
@@ -2094,6 +1662,8 @@ function NuMuProvider({
2094
1662
  setCart(normalizeCartFromServer(data));
2095
1663
  }
2096
1664
  } catch {
1665
+ } finally {
1666
+ if (!cancelled) setLoading(false);
2097
1667
  }
2098
1668
  })();
2099
1669
  return () => {
@@ -2121,7 +1691,7 @@ function NuMuProvider({
2121
1691
  const token = reserveToken();
2122
1692
  setLoading(true);
2123
1693
  try {
2124
- await postCartMutation(
1694
+ return await postCartMutation(
2125
1695
  endpoint,
2126
1696
  body,
2127
1697
  buildApplyCart(token),
@@ -2138,12 +1708,13 @@ function NuMuProvider({
2138
1708
  async (productId, variantId, quantity) => {
2139
1709
  const eventId = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : `${Date.now()}`;
2140
1710
  const qty = quantity || 1;
2141
- await mutate("/api/cart/add", {
1711
+ const result = await mutate("/api/cart/add", {
2142
1712
  product_id: productId,
2143
1713
  variant_id: variantId,
2144
1714
  quantity: qty,
2145
1715
  _event_id: eventId
2146
1716
  });
1717
+ if (!result.ok) return result;
2147
1718
  try {
2148
1719
  if (typeof window !== "undefined") {
2149
1720
  window.dispatchEvent(
@@ -2162,34 +1733,29 @@ function NuMuProvider({
2162
1733
  }
2163
1734
  } catch {
2164
1735
  }
1736
+ return result;
2165
1737
  },
2166
1738
  [mutate]
2167
1739
  );
2168
1740
  const removeItem = useCallback(
2169
- async (itemId) => {
2170
- await mutate("/api/cart/remove", { item_id: itemId });
2171
- },
1741
+ (itemId) => mutate("/api/cart/remove", { item_id: itemId }),
2172
1742
  [mutate]
2173
1743
  );
2174
1744
  const updateQuantity = useCallback(
2175
- async (itemId, quantity) => {
2176
- await mutate("/api/cart/update", { item_id: itemId, quantity });
2177
- },
1745
+ (itemId, quantity) => mutate("/api/cart/update", { item_id: itemId, quantity }),
2178
1746
  [mutate]
2179
1747
  );
2180
1748
  const applyDiscount = useCallback(
2181
- async (code) => {
2182
- await mutate("/api/cart/discount", { code });
2183
- },
1749
+ (code) => mutate("/api/cart/discount", { code }),
1750
+ [mutate]
1751
+ );
1752
+ const removeDiscount = useCallback(
1753
+ // DELETE — postCartMutation interprets undefined body as DELETE.
1754
+ () => mutate("/api/cart/discount", void 0),
2184
1755
  [mutate]
2185
1756
  );
2186
- const removeDiscount = useCallback(async () => {
2187
- await mutate("/api/cart/discount", void 0);
2188
- }, [mutate]);
2189
1757
  const updateNote = useCallback(
2190
- async (note) => {
2191
- await mutate("/api/cart/update", { note });
2192
- },
1758
+ (note) => mutate("/api/cart/update", { note }),
2193
1759
  [mutate]
2194
1760
  );
2195
1761
  const clearCart = useCallback(async () => {
@@ -2325,7 +1891,87 @@ function NuMuProvider({
2325
1891
  defaultNumberFmt
2326
1892
  ]
2327
1893
  );
2328
- return /* @__PURE__ */ jsx(ShopContext.Provider, { value: store, children: /* @__PURE__ */ jsx(ThemeSettingsContext.Provider, { value: themeSettings, children: /* @__PURE__ */ jsx(CurrentTemplateContext.Provider, { value: currentTemplate, children: /* @__PURE__ */ jsx(PageContext.Provider, { value: pageValue, children: /* @__PURE__ */ jsx(LocalizationContext.Provider, { value: localization, children: /* @__PURE__ */ jsx(CartContext.Provider, { value: cartValue, children: /* @__PURE__ */ jsx(CustomerContext.Provider, { value: customerState, children: /* @__PURE__ */ jsx(CustomerActionsContext.Provider, { value: customerActions, children: /* @__PURE__ */ jsx(NavigationContext.Provider, { value: navigation ?? {}, children }) }) }) }) }) }) }) }) });
1894
+ const [currencyConfig, setCurrencyConfig] = useState(
1895
+ null
1896
+ );
1897
+ const [currencyLoading, setCurrencyLoading] = useState(true);
1898
+ const [currencySelected, setCurrencySelected] = useState("");
1899
+ useEffect(() => {
1900
+ if (typeof window === "undefined") return;
1901
+ let cancelled = false;
1902
+ void (async () => {
1903
+ try {
1904
+ const res = await fetch("/api/storefront/currencies", {
1905
+ credentials: "include",
1906
+ headers: { Accept: "application/json" }
1907
+ });
1908
+ if (!res.ok) throw new Error(`currencies: HTTP ${res.status}`);
1909
+ const body = await res.json();
1910
+ if (cancelled) return;
1911
+ setCurrencyConfig(body.data);
1912
+ const cookie = readCurrencyCookie();
1913
+ const valid = cookie && body.data.presentment?.includes(cookie) ? cookie : null;
1914
+ setCurrencySelected(
1915
+ valid || body.data.default_presentment || body.data.base
1916
+ );
1917
+ } catch {
1918
+ if (cancelled) return;
1919
+ setCurrencyConfig(null);
1920
+ setCurrencySelected(store.currency || "EGP");
1921
+ } finally {
1922
+ if (!cancelled) setCurrencyLoading(false);
1923
+ }
1924
+ })();
1925
+ return () => {
1926
+ cancelled = true;
1927
+ };
1928
+ }, [store.id, store.currency]);
1929
+ const currencyRates = useMemo(() => {
1930
+ const out = {};
1931
+ if (!currencyConfig) return out;
1932
+ for (const [k, v] of Object.entries(currencyConfig.rates)) {
1933
+ const n = Number.parseFloat(v);
1934
+ if (Number.isFinite(n)) out[k] = n;
1935
+ }
1936
+ return out;
1937
+ }, [currencyConfig]);
1938
+ const convertCurrency = useCallback(
1939
+ (cents, target) => {
1940
+ if (!currencyConfig) return cents;
1941
+ const to = target || currencySelected;
1942
+ if (!to || to === currencyConfig.base) return cents;
1943
+ const rate = currencyRates[to];
1944
+ if (!rate || !Number.isFinite(rate)) return cents;
1945
+ return Math.round(cents * rate);
1946
+ },
1947
+ [currencyConfig, currencyRates, currencySelected]
1948
+ );
1949
+ const setCurrency = useCallback((currency) => {
1950
+ setCurrencySelected(currency);
1951
+ writeCurrencyCookie(currency);
1952
+ }, []);
1953
+ const currencyValue = useMemo(
1954
+ () => ({
1955
+ base: currencyConfig?.base || store.currency || "EGP",
1956
+ selected: currencySelected || currencyConfig?.base || store.currency || "EGP",
1957
+ presentment: currencyConfig?.presentment || [store.currency || "EGP"],
1958
+ rates: currencyRates,
1959
+ autoConvert: Boolean(currencyConfig?.auto_convert),
1960
+ loading: currencyLoading,
1961
+ setSelected: setCurrency,
1962
+ convert: convertCurrency
1963
+ }),
1964
+ [
1965
+ currencyConfig,
1966
+ currencySelected,
1967
+ currencyRates,
1968
+ currencyLoading,
1969
+ store.currency,
1970
+ setCurrency,
1971
+ convertCurrency
1972
+ ]
1973
+ );
1974
+ return /* @__PURE__ */ jsx(ShopContext.Provider, { value: store, children: /* @__PURE__ */ jsx(ThemeSettingsContext.Provider, { value: themeSettings, children: /* @__PURE__ */ jsx(CurrentTemplateContext.Provider, { value: currentTemplate, children: /* @__PURE__ */ jsx(PageContext.Provider, { value: pageValue, children: /* @__PURE__ */ jsx(LocalizationContext.Provider, { value: localization, children: /* @__PURE__ */ jsx(CurrencyContext.Provider, { value: currencyValue, children: /* @__PURE__ */ jsx(CartContext.Provider, { value: cartValue, children: /* @__PURE__ */ jsx(CustomerContext.Provider, { value: customerState, children: /* @__PURE__ */ jsx(CustomerActionsContext.Provider, { value: customerActions, children: /* @__PURE__ */ jsx(NavigationContext.Provider, { value: navigation ?? {}, children }) }) }) }) }) }) }) }) }) });
2329
1975
  }
2330
1976
  function ProductProvider({ product, children }) {
2331
1977
  return /* @__PURE__ */ jsx(ProductContext.Provider, { value: product, children });
@@ -2626,6 +2272,7 @@ var ThemeMountBridge = forwardRef(function ThemeMountBridge2({ ctx, mountEl, ren
2626
2272
  initialProducts: pageData.products,
2627
2273
  initialCollections: pageData.collections,
2628
2274
  currentTemplate: template,
2275
+ pageTemplate: ctx.page?.template,
2629
2276
  children: wrapEntityProviders(app, pageData)
2630
2277
  }
2631
2278
  );
@@ -2671,15 +2318,18 @@ function Money({
2671
2318
  }) {
2672
2319
  const { formatMoney } = useLocalization();
2673
2320
  const shop = useShop();
2674
- const ccy = currency || shop?.currency;
2321
+ const { selected, base, autoConvert, convert } = useCurrency();
2322
+ const shouldConvert = autoConvert && !currency && !!selected && selected !== base;
2323
+ const ccy = shouldConvert ? selected : currency || shop?.currency;
2324
+ const toDisplay = (major) => shouldConvert ? convert(Math.round(major * 100), selected) / 100 : major;
2675
2325
  const showCompare = compareAt != null && compareAt > amount;
2676
2326
  const children = [
2677
- /* @__PURE__ */ jsx("span", { children: formatMoney(amount, ccy) }, "amt")
2327
+ /* @__PURE__ */ jsx("span", { children: formatMoney(toDisplay(amount), ccy) }, "amt")
2678
2328
  ];
2679
2329
  if (showCompare) {
2680
2330
  children.push(" ");
2681
2331
  children.push(
2682
- /* @__PURE__ */ jsx("s", { style: { opacity: 0.6 }, children: formatMoney(compareAt, ccy) }, "cmp")
2332
+ /* @__PURE__ */ jsx("s", { style: { opacity: 0.6 }, children: formatMoney(toDisplay(compareAt), ccy) }, "cmp")
2683
2333
  );
2684
2334
  }
2685
2335
  return createElement(
@@ -3415,11 +3065,29 @@ function walk(node) {
3415
3065
  }
3416
3066
  }
3417
3067
  function sanitizeHtmlServer(input) {
3418
- let s = input.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<iframe[\s\S]*?<\/iframe>/gi, "").replace(/<object[\s\S]*?<\/object>/gi, "").replace(/<embed[\s\S]*?<\/embed>/gi, "").replace(/<link[\s\S]*?>/gi, "").replace(/<meta[\s\S]*?>/gi, "");
3419
- s = s.replace(/\s+on\w+\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/gi, "");
3068
+ if (!input) return "";
3069
+ let s = input;
3070
+ let prev;
3071
+ const DANGEROUS = [
3072
+ /<script\b[\s\S]*?<\/script\s*>/gi,
3073
+ /<style\b[\s\S]*?<\/style\s*>/gi,
3074
+ /<iframe\b[\s\S]*?<\/iframe\s*>/gi,
3075
+ /<object\b[\s\S]*?<\/object\s*>/gi,
3076
+ /<embed\b[\s\S]*?<\/embed\s*>/gi,
3077
+ /<\/?(?:script|style|iframe|object|embed|link|meta|form|input)\b[^>]*>/gi
3078
+ ];
3079
+ do {
3080
+ prev = s;
3081
+ for (const re of DANGEROUS) s = s.replace(re, "");
3082
+ } while (s !== prev);
3083
+ const HANDLER = /[\s/"'<]on\w+\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/gi;
3084
+ do {
3085
+ prev = s;
3086
+ s = s.replace(HANDLER, "");
3087
+ } while (s !== prev);
3420
3088
  s = s.replace(
3421
- /\s+(href|src)\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/gi,
3422
- (full, attr, val) => {
3089
+ /[\s/"'<](href|src)\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/gi,
3090
+ (full, _attr, val) => {
3423
3091
  const cleaned = String(val).replace(/^['"]|['"]$/g, "");
3424
3092
  return isSafeUrl(cleaned) ? full : "";
3425
3093
  }
@@ -3764,97 +3432,6 @@ function Icon({
3764
3432
  );
3765
3433
  }
3766
3434
 
3767
- // src/utils/normalize.ts
3768
- var DEFAULT_THEME_ID = typeof process !== "undefined" && process.env?.NUMU_DEFAULT_THEME_ID || "modern";
3769
- function resolveThemeSettings(raw) {
3770
- const safeRaw = raw ?? {};
3771
- if (safeRaw.schema_version === 3) {
3772
- return safeRaw;
3773
- }
3774
- const themeBlock = safeRaw.theme || {};
3775
- const themeId = typeof themeBlock.base_theme === "string" && themeBlock.base_theme || DEFAULT_THEME_ID;
3776
- const globalSettings = {};
3777
- for (const key of [
3778
- "primary_color",
3779
- "secondary_color",
3780
- "font_family",
3781
- "logo_url"
3782
- ]) {
3783
- if (themeBlock[key] !== void 0) globalSettings[key] = themeBlock[key];
3784
- }
3785
- if (safeRaw.identity) globalSettings.identity = safeRaw.identity;
3786
- const sections = {};
3787
- const order = [];
3788
- const hero = safeRaw.hero;
3789
- if (hero) {
3790
- sections["hero_1"] = {
3791
- type: "hero",
3792
- settings: {
3793
- // Mirrors backend `normalize_legacy_to_v3` exactly — includes
3794
- // headline_ar so Arabic content survives the round-trip.
3795
- headline: hero.headline ?? "",
3796
- headline_ar: hero.headline_ar ?? "",
3797
- subtitle: hero.subtitle ?? "",
3798
- background_image: hero.hero_image_url ?? "",
3799
- cta_text: hero.cta_text ?? "",
3800
- cta_link: hero.cta_link ?? ""
3801
- }
3802
- };
3803
- order.push("hero_1");
3804
- }
3805
- const products = safeRaw.products;
3806
- if (products) {
3807
- sections["featured_1"] = {
3808
- type: "featured-products",
3809
- settings: products
3810
- };
3811
- order.push("featured_1");
3812
- }
3813
- const templates = {};
3814
- if (Object.keys(sections).length > 0) {
3815
- templates["home"] = { name: "Home", sections, order };
3816
- }
3817
- const sectionGroups = {
3818
- header: {
3819
- name: "Header Group",
3820
- sections: {
3821
- header_1: {
3822
- type: "header",
3823
- settings: safeRaw.header || {}
3824
- }
3825
- },
3826
- order: ["header_1"]
3827
- },
3828
- footer: {
3829
- name: "Footer Group",
3830
- sections: {
3831
- footer_1: {
3832
- type: "footer",
3833
- settings: safeRaw.footer || {}
3834
- }
3835
- },
3836
- order: ["footer_1"]
3837
- }
3838
- };
3839
- let externalTheme = null;
3840
- const ext = safeRaw.external_theme;
3841
- if (ext && typeof ext === "object" && typeof ext.bundle_url === "string" && ext.bundle_url) {
3842
- externalTheme = {
3843
- bundle_url: ext.bundle_url,
3844
- css_url: typeof ext.css_url === "string" ? ext.css_url : null,
3845
- mode: typeof ext.mode === "string" ? ext.mode : "production"
3846
- };
3847
- }
3848
- return {
3849
- schema_version: 3,
3850
- theme_id: themeId,
3851
- global_settings: globalSettings,
3852
- templates,
3853
- section_groups: sectionGroups,
3854
- external_theme: externalTheme
3855
- };
3856
- }
3857
-
3858
3435
  // src/utils/federation.ts
3859
3436
  function sdkSlot() {
3860
3437
  return /* @__PURE__ */ Symbol.for("@numueg/theme-sdk:singleton");
@@ -4167,6 +3744,6 @@ function buildLocaleBundle(modules) {
4167
3744
  return bundle;
4168
3745
  }
4169
3746
 
4170
- export { AddToCartButton, Block, CartContext, CollectionCard, CollectionContext, CollectionProvider, CurrencySwitcher, CustomerContext, EditableImage, EditableText, Form, HeroMedia, ICON_NAMES, Icon, IconMap, Image, KNOWN_SETTING_TYPES, KNOWN_TEMPLATES, LOGO_SHAPE_OPTIONS, LOGO_SIZE_OPTIONS, Link, LocaleSwitcher, LocalizationContext, Logo, MAX_BLOCK_DEPTH, Money, NavigationContext, NuMuProvider, PageContext, ProductCard, ProductContext, ProductProvider, REQUIRED_TEMPLATES, RichText, SDK_VERSION, Section, SectionContext, ShopContext, THEME_CONTRACT_VERSION, ThemeSettingsContext, applyGlobalStyleTokens, applyImageTransform, asImageTransform, assetUrl, availableValues, buildLocaleBundle, buildThemeElement, clearSdkSingleton, collectBlocks, collectSections, computeGlobalStyleTokens, defaultVariant, defineBlock, defineSection, defineThemeEntry, dynamicSource, findVariantByOptions, flattenMessages, focalSrc, getReactSingleton, getSdkSingleton, isDefinedBlock, isDefinedSection, isDynamicSource, isSdkAvailable, logoImgStyle, logoStyleTokens, mergeResults, mountTheme, pickTranslations, registerReactSingleton, registerSdkSingleton, resolveDynamicValue, resolveFontStack, resolveSettingsMap, resolveSizeChart, resolveSourcePath, resolveThemeSettings, sanitizeHtml, useAnalytics, useApp, useCart, useCheckout, useCollection, useCollectionOptional, useCollections, useCurrency, useCurrentTemplate, useCustomer, useCustomerActions, useCustomerAddresses, useDirection, useFieldTranslation, useGiftCardBalance, useImage, useLocale, useLocalization, useMoney, useNavigation, useNumberFormat, useOrder, useOrders, usePage, useProduct, useProductOptional, useProductSizeChart, useProducts, useRelatedProducts, useReorder, useResolvedSettings, useSearch, useSection, useSectionOptional, useShippingRates, useShop, useThemeSettings, useTranslation, useVariantSelection, useWishlist, validateBuiltManifest, validateManifest, validateSectionSchema, validateSettingsAgainstSchema };
3747
+ export { AddToCartButton, Block, CollectionCard, CollectionProvider, CurrencySwitcher, EditableImage, EditableText, Form, HeroMedia, ICON_NAMES, Icon, IconMap, Image, LOGO_SHAPE_OPTIONS, LOGO_SIZE_OPTIONS, Link, LocaleSwitcher, Logo, MAX_BLOCK_DEPTH, Money, NuMuProvider, ProductCard, ProductProvider, RichText, Section, SectionContext, applyGlobalStyleTokens, applyImageTransform, asImageTransform, assetUrl, availableValues, buildLocaleBundle, buildThemeElement, clearSdkSingleton, collectBlocks, collectSections, computeGlobalStyleTokens, defaultVariant, defineBlock, defineSection, defineThemeEntry, dynamicSource, findVariantByOptions, flattenMessages, focalSrc, getReactSingleton, getSdkSingleton, isDefinedBlock, isDefinedSection, isDynamicSource, isSdkAvailable, logoImgStyle, logoStyleTokens, mountTheme, pickTranslations, registerReactSingleton, registerSdkSingleton, resolveDynamicValue, resolveFontStack, resolveSettingsMap, resolveSizeChart, resolveSourcePath, sanitizeHtml, useAnalytics, useApp, useCachedResource, useCart, useCheckout, useCollection, useCollectionOptional, useCurrency, useCurrentTemplate, useCustomerActions, useCustomerAddresses, useGiftCardBalance, useImage, useMoney, useNavigation, useOrder, useOrders, useProduct, useProductOptional, useProductSizeChart, useRelatedProducts, useReorder, useResolvedSettings, useSearch, useSection, useSectionGroup, useSectionOptional, useShippingRates, useVariantSelection, useWishlist };
4171
3748
  //# sourceMappingURL=index.mjs.map
4172
3749
  //# sourceMappingURL=index.mjs.map