@athoscommerce/snap-preact 1.12.0 → 1.13.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 (58) hide show
  1. package/dist/cjs/components/src/components/Atoms/Price/Price.js +2 -2
  2. package/dist/cjs/components/src/components/Molecules/CustomComponentWrapper/CustomComponentWrapper.js +1 -1
  3. package/dist/cjs/components/src/components/Molecules/Slideshow/Slideshow.js +59 -30
  4. package/dist/cjs/components/src/components/Organisms/AutocompleteLayout/AutocompleteLayout.js +13 -8
  5. package/dist/cjs/components/src/components/Organisms/QuickviewLayout/QuickviewLayout.js +13 -8
  6. package/dist/cjs/components/src/hooks/useLayoutOptions.js +19 -14
  7. package/dist/cjs/components/src/utilities/mergeProps.js +647 -184
  8. package/dist/cjs/src/Snap.js +1 -1
  9. package/dist/cjs/src/Templates/SnapTemplates.js +17 -53
  10. package/dist/cjs/src/Templates/Stores/LibraryStore.js +2 -0
  11. package/dist/cjs/src/Templates/Stores/TemplateStore.js +37 -8
  12. package/dist/cjs/src/Templates/Stores/ThemeStore.js +125 -32
  13. package/dist/cjs/src/Templates/Stores/library/currencies/currencies.js +52 -29
  14. package/dist/cjs/src/Templates/Stores/library/plugins/shopify/pluginCurrency.js +5 -0
  15. package/dist/esm/components/src/components/Atoms/Price/Price.d.ts +2 -0
  16. package/dist/esm/components/src/components/Atoms/Price/Price.d.ts.map +1 -1
  17. package/dist/esm/components/src/components/Atoms/Price/Price.js +2 -2
  18. package/dist/esm/components/src/components/Molecules/CustomComponentWrapper/CustomComponentWrapper.d.ts.map +1 -1
  19. package/dist/esm/components/src/components/Molecules/CustomComponentWrapper/CustomComponentWrapper.js +1 -1
  20. package/dist/esm/components/src/components/Molecules/Select/Select.d.ts +2 -2
  21. package/dist/esm/components/src/components/Molecules/Select/Select.d.ts.map +1 -1
  22. package/dist/esm/components/src/components/Molecules/Slideshow/Slideshow.d.ts +1 -0
  23. package/dist/esm/components/src/components/Molecules/Slideshow/Slideshow.d.ts.map +1 -1
  24. package/dist/esm/components/src/components/Molecules/Slideshow/Slideshow.js +59 -30
  25. package/dist/esm/components/src/components/Organisms/AutocompleteLayout/AutocompleteLayout.d.ts.map +1 -1
  26. package/dist/esm/components/src/components/Organisms/AutocompleteLayout/AutocompleteLayout.js +13 -8
  27. package/dist/esm/components/src/components/Organisms/QuickviewLayout/QuickviewLayout.d.ts.map +1 -1
  28. package/dist/esm/components/src/components/Organisms/QuickviewLayout/QuickviewLayout.js +13 -8
  29. package/dist/esm/components/src/hooks/useLayoutOptions.d.ts.map +1 -1
  30. package/dist/esm/components/src/hooks/useLayoutOptions.js +19 -14
  31. package/dist/esm/components/src/providers/langComponents.d.ts +5 -0
  32. package/dist/esm/components/src/providers/langComponents.d.ts.map +1 -1
  33. package/dist/esm/components/src/providers/themeComponents.d.ts +214 -193
  34. package/dist/esm/components/src/providers/themeComponents.d.ts.map +1 -1
  35. package/dist/esm/components/src/utilities/mergeProps.d.ts +11 -0
  36. package/dist/esm/components/src/utilities/mergeProps.d.ts.map +1 -1
  37. package/dist/esm/components/src/utilities/mergeProps.js +647 -184
  38. package/dist/esm/src/Snap.js +1 -1
  39. package/dist/esm/src/Templates/SnapTemplates.d.ts +32 -19
  40. package/dist/esm/src/Templates/SnapTemplates.d.ts.map +1 -1
  41. package/dist/esm/src/Templates/SnapTemplates.js +16 -51
  42. package/dist/esm/src/Templates/Stores/LibraryStore.d.ts +7 -0
  43. package/dist/esm/src/Templates/Stores/LibraryStore.d.ts.map +1 -1
  44. package/dist/esm/src/Templates/Stores/LibraryStore.js +2 -0
  45. package/dist/esm/src/Templates/Stores/TemplateStore.d.ts +11 -7
  46. package/dist/esm/src/Templates/Stores/TemplateStore.d.ts.map +1 -1
  47. package/dist/esm/src/Templates/Stores/TemplateStore.js +31 -8
  48. package/dist/esm/src/Templates/Stores/ThemeStore.d.ts +5 -2
  49. package/dist/esm/src/Templates/Stores/ThemeStore.d.ts.map +1 -1
  50. package/dist/esm/src/Templates/Stores/ThemeStore.js +125 -32
  51. package/dist/esm/src/Templates/Stores/library/currencies/currencies.d.ts +27 -0
  52. package/dist/esm/src/Templates/Stores/library/currencies/currencies.d.ts.map +1 -1
  53. package/dist/esm/src/Templates/Stores/library/currencies/currencies.js +52 -29
  54. package/dist/esm/src/Templates/Stores/library/plugins/shopify/pluginCurrency.d.ts +2 -0
  55. package/dist/esm/src/Templates/Stores/library/plugins/shopify/pluginCurrency.d.ts.map +1 -0
  56. package/dist/esm/src/Templates/Stores/library/plugins/shopify/pluginCurrency.js +1 -0
  57. package/eslint/src/validate-config.cjs +520 -0
  58. package/package.json +13 -13
@@ -284,7 +284,7 @@ class Snap {
284
284
  3. override via query param / cookie
285
285
  */
286
286
  // node env
287
- if (process.env.NODE_ENV && Object.values(snap_toolbox_1.AppMode).includes(process.env.NODE_ENV)) {
287
+ if (typeof process !== 'undefined' && process.env.NODE_ENV && Object.values(snap_toolbox_1.AppMode).includes(process.env.NODE_ENV)) {
288
288
  this.mode = process.env.NODE_ENV;
289
289
  }
290
290
  // config
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.createSearchTargeters = exports.SnapTemplates = exports.applyAutomaticThemeOverrides = exports.TAB_PREFIXED_PARAMS = exports.DEFAULT_AUTOCOMPLETE_CONTROLLER_SETTINGS = exports.DEFAULT_FEATURES = exports.TEMPLATE_EDITOR_UI_PARAM = exports.TEMPLATE_EDITOR_COOKIE = void 0;
6
+ exports.createSearchTargeters = exports.SnapTemplates = exports.TAB_PREFIXED_PARAMS = exports.DEFAULT_AUTOCOMPLETE_CONTROLLER_SETTINGS = exports.DEFAULT_FEATURES = exports.TEMPLATE_EDITOR_UI_PARAM = exports.TEMPLATE_EDITOR_COOKIE = void 0;
7
7
  exports.validateTemplatesConfig = validateTemplatesConfig;
8
8
  exports.validateTemplatesConfigUnlocked = validateTemplatesConfigUnlocked;
9
9
  exports.mapBreakpoints = mapBreakpoints;
@@ -24,14 +24,17 @@ const TargetStore_1 = require("./Stores/TargetStore");
24
24
  const TabManagerStore_1 = require("./Stores/TabManagerStore");
25
25
  const snap_client_1 = require("@athoscommerce/snap-client");
26
26
  const snap_tracker_1 = require("@athoscommerce/snap-tracker");
27
- const shopify_1 = require("@athoscommerce/snap-platforms/shopify");
28
27
  const utils_1 = require("../utils");
29
28
  exports.TEMPLATE_EDITOR_COOKIE = 'athosEditor';
30
29
  exports.TEMPLATE_EDITOR_UI_PARAM = 'athos-editor';
30
+ // prettier-ignore
31
31
  function validateTemplatesConfig(config) {
32
+ // the return type is a compile-time verdict; at runtime the config passes straight through
32
33
  return config;
33
34
  }
35
+ // prettier-ignore
34
36
  function validateTemplatesConfigUnlocked(config) {
37
+ // the return type is a compile-time verdict; at runtime the config passes straight through
35
38
  return config;
36
39
  }
37
40
  exports.DEFAULT_FEATURES = {};
@@ -42,54 +45,8 @@ exports.DEFAULT_AUTOCOMPLETE_CONTROLLER_SETTINGS = {
42
45
  };
43
46
  // tabs namespace their catalog specific url state - the query is shared so a single search spans every tab
44
47
  exports.TAB_PREFIXED_PARAMS = ['filter', 'sort', 'pageSize', 'rq', 'page'];
45
- const hasShopifyMarketsPluginConfig = (templateConfig) => {
46
- const pluginConfigs = [
47
- templateConfig.plugins?.shopify?.markets,
48
- templateConfig.search?.plugins?.shopify?.markets,
49
- templateConfig.autocomplete?.plugins?.shopify?.markets,
50
- templateConfig.recommendation?.plugins?.shopify?.markets,
51
- ];
52
- return pluginConfigs.some((pluginConfig) => {
53
- return typeof pluginConfig?.token === 'string' && pluginConfig.token.length > 0;
54
- });
55
- };
56
- const applyAutomaticThemeOverrides = (templateConfig) => {
57
- if (templateConfig.config?.platform !== 'shopify') {
58
- return templateConfig;
59
- }
60
- if (!hasShopifyMarketsPluginConfig(templateConfig)) {
61
- return templateConfig;
62
- }
63
- const defaultOverrides = templateConfig.theme?.overrides?.default;
64
- const priceOverride = defaultOverrides?.price;
65
- if (typeof priceOverride?.format !== 'undefined') {
66
- return templateConfig;
67
- }
68
- let context;
69
- try {
70
- context = (0, snap_toolbox_1.getContext)(['format']);
71
- }
72
- catch {
73
- context = undefined;
74
- }
75
- const currencyFormat = context?.format || '${{amount}}';
76
- // todo: consider moving this inside of the plugin if possible. seems odd to have this happen outside of it.
77
- return (0, deepmerge_1.default)(templateConfig, {
78
- theme: {
79
- overrides: {
80
- default: {
81
- price: {
82
- format: (number) => (0, shopify_1.shopifyMarketsPriceFormat)(number, currencyFormat),
83
- },
84
- },
85
- },
86
- },
87
- });
88
- };
89
- exports.applyAutomaticThemeOverrides = applyAutomaticThemeOverrides;
90
48
  class SnapTemplates extends Snap_1.Snap {
91
49
  constructor(config) {
92
- const modifiedConfig = (0, exports.applyAutomaticThemeOverrides)(config);
93
50
  let context = {};
94
51
  try {
95
52
  context = (0, snap_toolbox_1.getContext)(['editor']);
@@ -103,11 +60,11 @@ class SnapTemplates extends Snap_1.Snap {
103
60
  const editUIMode = Boolean((urlParams?.params?.query && exports.TEMPLATE_EDITOR_UI_PARAM in urlParams.params.query) || editorCookieValue === 'ui' || editor?.mode === 'ui');
104
61
  const editMode = Boolean(editorCookieValue) || editUIMode || Boolean(editor?.mode === 'headless');
105
62
  // handle "global" result component configuration
106
- if (modifiedConfig.theme.globalResultComponent) {
107
- modifiedConfig.theme.overrides = (0, deepmerge_1.default)({ default: { result: { customComponent: modifiedConfig.theme.globalResultComponent } } }, modifiedConfig.theme.overrides || {});
63
+ if (config.theme.globalResultComponent) {
64
+ config.theme.overrides = (0, deepmerge_1.default)({ default: { result: { customComponent: config.theme.globalResultComponent } } }, config.theme.overrides || {});
108
65
  }
109
- const templatesStore = new TemplateStore_1.TemplatesStore({ config: modifiedConfig, settings: { editMode } });
110
- const snapConfig = createSnapConfig(modifiedConfig, templatesStore);
66
+ const templatesStore = new TemplateStore_1.TemplatesStore({ config: config, settings: { editMode } });
67
+ const snapConfig = createSnapConfig(config, templatesStore);
111
68
  super(snapConfig, { templatesStore });
112
69
  this.templates = templatesStore;
113
70
  setTimeout(async () => {
@@ -597,9 +554,16 @@ function createPlugins(templateConfig, templatesStore, controllerType) {
597
554
  ]);
598
555
  const marketsConfig = (0, deepmerge_1.default)(templateConfig.plugins?.shopify?.markets || {}, controllerConfig?.plugins?.shopify?.markets || {});
599
556
  //only push if token is defined and non-empty
600
- if (typeof marketsConfig?.token === 'string' && marketsConfig.token.length > 0) {
557
+ const marketsEnabled = typeof marketsConfig?.token === 'string' && marketsConfig.token.length > 0;
558
+ if (marketsEnabled) {
601
559
  plugins.push([templatesStore.library.import.plugins.shopify.markets, marketsConfig]);
602
560
  }
561
+ // markets plugin enables the currency plugin unless it has been explicitly disabled
562
+ const currencyConfig = (0, deepmerge_1.default)(templateConfig.plugins?.shopify?.currency || {}, controllerConfig?.plugins?.shopify?.currency || {});
563
+ if (currencyConfig?.enabled === true || (marketsEnabled && currencyConfig?.enabled !== false)) {
564
+ // `enabled` is resolved onto the config so the plugin survives the tab plugin filter below
565
+ plugins.push([templatesStore.library.import.plugins.shopify.currency, { ...currencyConfig, enabled: true }, templatesStore]);
566
+ }
603
567
  break;
604
568
  case 'bigCommerce':
605
569
  plugins.push([
@@ -7,6 +7,7 @@ const pluginBackgroundFilters_1 = require("./library/plugins/shopify/pluginBackg
7
7
  const pluginMutateResults_1 = require("./library/plugins/shopify/pluginMutateResults");
8
8
  const pluginAddToCart_1 = require("./library/plugins/shopify/pluginAddToCart");
9
9
  const pluginMarkets_1 = require("./library/plugins/shopify/pluginMarkets");
10
+ const pluginCurrency_1 = require("./library/plugins/shopify/pluginCurrency");
10
11
  const pluginAddToCart_2 = require("./library/plugins/bigCommerce/pluginAddToCart");
11
12
  const pluginAddToCart_3 = require("./library/plugins/magento2/pluginAddToCart");
12
13
  const pluginAddToCart_4 = require("./library/plugins/common/pluginAddToCart");
@@ -133,6 +134,7 @@ class LibraryStore {
133
134
  mutateResults: pluginMutateResults_1.pluginMutateResults,
134
135
  addToCart: pluginAddToCart_1.pluginAddToCart,
135
136
  markets: pluginMarkets_1.pluginMarkets,
137
+ currency: pluginCurrency_1.pluginCurrency,
136
138
  },
137
139
  bigcommerce: {
138
140
  backgroundFilters: pluginBackgroundFilters_2.pluginBackgroundFilters,
@@ -1,7 +1,14 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.TemplatesStore = exports.TEMPLATE_STORE_KEY = void 0;
7
+ exports.resolveCurrencyOverridesTheme = resolveCurrencyOverridesTheme;
8
+ exports.withCurrencyCode = withCurrencyCode;
4
9
  exports.transformTranslationsToTheme = transformTranslationsToTheme;
10
+ exports.resolveTranslationsOverridesTheme = resolveTranslationsOverridesTheme;
11
+ const deepmerge_1 = __importDefault(require("deepmerge"));
5
12
  const mobx_1 = require("mobx");
6
13
  const snap_toolbox_1 = require("@athoscommerce/snap-toolbox");
7
14
  const ThemeStore_1 = require("./ThemeStore");
@@ -79,9 +86,10 @@ class TemplatesStore {
79
86
  const base = this.library.themes[themeConfiguration.extends];
80
87
  const overrides = themeConfiguration.overrides || {};
81
88
  const variables = themeConfiguration.variables || {};
82
- const currency = this.library.locales.currencies[this.currency] || {};
89
+ const currency = withCurrencyCode(this.currency, this.library.locales.currencies[this.currency] || {});
90
+ const currencyOverrides = resolveCurrencyOverridesTheme(this.config.currencies, this.currency);
83
91
  const language = this.library.locales.languages[this.language] || {};
84
- const languageOverrides = transformTranslationsToTheme((this.config.translations && this.config.translations[this.language]) || {});
92
+ const languageOverrides = resolveTranslationsOverridesTheme(this.config.translations, this.language);
85
93
  const translatedOverrides = {
86
94
  components: overrides.default,
87
95
  responsive: {
@@ -98,6 +106,7 @@ class TemplatesStore {
98
106
  overrides: translatedOverrides,
99
107
  variables,
100
108
  currency,
109
+ currencyOverrides,
101
110
  language,
102
111
  languageOverrides,
103
112
  innerWidth: this.window.innerWidth,
@@ -189,13 +198,15 @@ class TemplatesStore {
189
198
  if (currency) {
190
199
  this.currency = code;
191
200
  this.storage.set('overrides.config.currency', this.currency);
201
+ const currencyLayer = withCurrencyCode(code, currency);
202
+ const currencyOverrides = resolveCurrencyOverridesTheme(this.config.currencies, code);
192
203
  for (const themeName in this.themes.local) {
193
204
  const theme = this.themes.local[themeName];
194
- theme.setCurrency(currency);
205
+ theme.setCurrency(currencyLayer, currencyOverrides);
195
206
  }
196
207
  for (const themeName in this.themes.library) {
197
208
  const theme = this.themes.library[themeName];
198
- theme.setCurrency(currency);
209
+ theme.setCurrency(currencyLayer, currencyOverrides);
199
210
  }
200
211
  }
201
212
  }
@@ -211,13 +222,14 @@ class TemplatesStore {
211
222
  if (language) {
212
223
  this.language = code;
213
224
  this.storage.set('overrides.config.language', this.language);
225
+ const languageOverrides = resolveTranslationsOverridesTheme(this.config.translations, code);
214
226
  for (const themeName in this.themes.local) {
215
227
  const theme = this.themes.local[themeName];
216
- theme.setLanguage(language);
228
+ theme.setLanguage(language, languageOverrides);
217
229
  }
218
230
  for (const themeName in this.themes.library) {
219
231
  const theme = this.themes.library[themeName];
220
- theme.setLanguage(language);
232
+ theme.setLanguage(language, languageOverrides);
221
233
  }
222
234
  }
223
235
  }
@@ -238,8 +250,9 @@ class TemplatesStore {
238
250
  type: 'library',
239
251
  base: theme,
240
252
  language: this.library.locales.languages[this.language] || {},
241
- languageOverrides: transformTranslationsToTheme((this.config.translations && this.config.translations[this.language]) || {}),
242
- currency: this.library.locales.currencies[this.currency] || {},
253
+ languageOverrides: resolveTranslationsOverridesTheme(this.config.translations, this.language),
254
+ currency: withCurrencyCode(this.currency, this.library.locales.currencies[this.currency] || {}),
255
+ currencyOverrides: resolveCurrencyOverridesTheme(this.config.currencies, this.currency),
243
256
  innerWidth: this.window.innerWidth,
244
257
  };
245
258
  if (this.settings.editMode) {
@@ -262,6 +275,18 @@ function getTargetArray(targets, type) {
262
275
  }
263
276
  return undefined;
264
277
  }
278
+ /*
279
+ Resolves the configured component overrides for a currency into a theme layer.
280
+ */
281
+ function resolveCurrencyOverridesTheme(currencies, code) {
282
+ const overrides = currencies?.[code] || currencies?.[code.toUpperCase()];
283
+ return overrides ? { components: overrides } : {};
284
+ }
285
+ // the ISO code is not stored in the locale data - it is derived from the active currency so the `Price`
286
+ // component can render it after the amount when `showCode` is enabled
287
+ function withCurrencyCode(code, currency) {
288
+ return (0, deepmerge_1.default)(currency, { components: { price: { code: code.toUpperCase() } } });
289
+ }
265
290
  function transformTranslationsToTheme(translations) {
266
291
  const components = {};
267
292
  Object.keys(translations).forEach((component) => {
@@ -274,6 +299,10 @@ function transformTranslationsToTheme(translations) {
274
299
  components,
275
300
  };
276
301
  }
302
+ function resolveTranslationsOverridesTheme(translations, code) {
303
+ const overrides = translations?.[code] || translations?.[code.toUpperCase()];
304
+ return transformTranslationsToTheme(overrides || {});
305
+ }
277
306
  class Deferred {
278
307
  constructor() {
279
308
  this.promise = new Promise((resolve, reject) => {
@@ -16,26 +16,41 @@ class ThemeStore {
16
16
  const { config, dependencies, settings } = params;
17
17
  this.dependencies = dependencies;
18
18
  this.editMode = settings.editMode;
19
- const { name, style, type, base, overrides, editorOverrides, variables, currency, language, languageOverrides, innerWidth } = config;
20
- // add prefixes to base theme components and responsive components
21
- base.components = prefixComponentKeys('*', base.components);
19
+ const { name, style, type, base, overrides, editorOverrides, variables, currency, currencyOverrides, language, languageOverrides, innerWidth } = config;
20
+ // Prefix base + normalize override selector keys into ThemeStore-OWNED copies. `base` is a
21
+ // shared library theme object (the same instance is reused to build multiple ThemeStores),
22
+ // so it must NOT be mutated in place — build fresh objects instead. prefixComponentKeys /
23
+ // normalizeCommaSeparatedKeys already return new objects; here we just avoid reassigning
24
+ // onto the shared inputs.
25
+ const prefixedBase = { ...base };
26
+ prefixedBase.components = prefixComponentKeys('*', flattenCascadingOverrides(base.components));
22
27
  if (base.responsive) {
23
- base.responsive.mobile = prefixComponentKeys('*(M)', base.responsive?.mobile);
24
- base.responsive.tablet = prefixComponentKeys('*(T)', base.responsive?.tablet);
25
- base.responsive.desktop = prefixComponentKeys('*(D)', base.responsive?.desktop);
28
+ prefixedBase.responsive = {
29
+ mobile: prefixComponentKeys('*(M)', flattenCascadingOverrides(base.responsive.mobile)),
30
+ tablet: prefixComponentKeys('*(T)', flattenCascadingOverrides(base.responsive.tablet)),
31
+ desktop: prefixComponentKeys('*(D)', flattenCascadingOverrides(base.responsive.desktop)),
32
+ };
26
33
  }
34
+ const prefixedOverrides = { ...(overrides || {}) };
27
35
  if (overrides?.responsive) {
28
- overrides.responsive.mobile = prefixComponentKeys('(M)', overrides.responsive?.mobile);
29
- overrides.responsive.tablet = prefixComponentKeys('(T)', overrides.responsive?.tablet);
30
- overrides.responsive.desktop = prefixComponentKeys('(D)', overrides.responsive?.desktop);
36
+ prefixedOverrides.responsive = {
37
+ mobile: prefixComponentKeys('(M)', flattenCascadingOverrides(overrides.responsive.mobile)),
38
+ tablet: prefixComponentKeys('(T)', flattenCascadingOverrides(overrides.responsive.tablet)),
39
+ desktop: prefixComponentKeys('(D)', flattenCascadingOverrides(overrides.responsive.desktop)),
40
+ };
41
+ }
42
+ // Normalize comma-separated selectors in override default components (no prefix needed, but commas need normalizing)
43
+ if (overrides?.components) {
44
+ prefixedOverrides.components = normalizeCommaSeparatedKeys(flattenCascadingOverrides(overrides.components));
31
45
  }
32
46
  this.name = name;
33
47
  this.type = type;
34
- this.base = base;
35
- this.overrides = overrides || {};
48
+ this.base = prefixedBase;
49
+ this.overrides = prefixedOverrides;
36
50
  this.editorOverrides = editorOverrides || {};
37
51
  this.variables = variables || {};
38
52
  this.currency = currency;
53
+ this.currencyOverrides = currencyOverrides || {};
39
54
  this.language = language;
40
55
  this.languageOverrides = languageOverrides;
41
56
  this.innerWidth = innerWidth;
@@ -44,47 +59,64 @@ class ThemeStore {
44
59
  name: mobx_1.observable,
45
60
  variables: mobx_1.observable,
46
61
  currency: mobx_1.observable,
62
+ currencyOverrides: mobx_1.observable,
47
63
  language: mobx_1.observable,
64
+ languageOverrides: mobx_1.observable,
48
65
  editorOverrides: mobx_1.observable,
49
66
  innerWidth: mobx_1.observable,
67
+ // memoized breakpoint band. `theme` depends on THIS computed, not on `innerWidth`
68
+ // directly, so a resize that stays within the same band (a common case) recomputes
69
+ // this cheap primitive to the same value and does NOT invalidate the expensive theme
70
+ // merge below.
71
+ activeBreakpoint: mobx_1.computed,
50
72
  theme: mobx_1.computed, // make theme getter a computed property (memoized)
51
73
  });
52
74
  }
75
+ // Current breakpoint band derived from innerWidth. Kept as its own computed so that
76
+ // within-band resizes (which return the same string) do not invalidate the `theme` computed.
77
+ get activeBreakpoint() {
78
+ // const breakpoints = this.variables.breakpoints || this.base.variables?.breakpoints;
79
+ const breakpoints = deepmerge_1.default.all([
80
+ this.base.variables.breakpoints,
81
+ this.variables.breakpoints || {},
82
+ (this.editMode && this.editorOverrides?.variables?.breakpoints) || {},
83
+ ]);
84
+ return getActiveBreakpoint(this.innerWidth, breakpoints);
85
+ }
53
86
  get theme() {
54
87
  /*
55
88
  Themes consist of layers which are deep merged together in order (last merge has highest priority)
56
89
  1. base theme
57
90
  2. base theme responsive breakpoints
58
91
  3. currency
59
- 4. language
60
- 5. language translation overrides
61
- 6. theme overrides
62
- 7. theme overrides at responsive breakpoints
63
- 8. altered theme variables
64
- 9. stored theme editor overrides
65
- 10. stored theme editor overrides at responsive breakpoints
92
+ 4. per currency component overrides
93
+ 5. language
94
+ 6. language translation overrides
95
+ 7. theme overrides
96
+ 8. theme overrides at responsive breakpoints
97
+ 9. altered theme variables
98
+ 10. stored theme editor overrides
99
+ 11. stored theme editor overrides at responsive breakpoints
66
100
  */
67
- // const breakpoints = this.variables.breakpoints || this.base.variables?.breakpoints;
68
- const breakpoints = deepmerge_1.default.all([
69
- this.base.variables.breakpoints,
70
- this.variables.breakpoints || {},
71
- (this.editMode && this.editorOverrides?.variables?.breakpoints) || {},
72
- ]);
73
- const activeBreakpoint = getActiveBreakpoint(this.innerWidth, breakpoints);
101
+ const activeBreakpoint = this.activeBreakpoint;
74
102
  // overrides breakpoint is index file responsive overrides that match current breakpoint
75
103
  const overrideBreakpoint = getOverridesAtActiveBreakpoint(activeBreakpoint, this.overrides);
76
104
  // currently selected theme layer for current breakpoint
77
105
  const baseBreakpoint = getOverridesAtActiveBreakpoint(activeBreakpoint, this.base);
78
- // currently selected theme
79
- const base = { ...this.base };
80
106
  // overrides is index file default overrides
81
107
  const overrides = { ...this.overrides };
82
108
  const themeOverrides = mergeThemeLayers(overrides, overrideBreakpoint, {
83
109
  variables: (0, mobx_1.toJS)(this.variables),
84
110
  });
85
- let theme = mergeThemeLayers(base, baseBreakpoint, this.currency, this.language, this.languageOverrides, themeOverrides, {
111
+ // PERF: deepmerge.all deep-clones its accumulator on EVERY layer, so passing the large base
112
+ // theme as the first of 7 layers re-clones it ~6x per rebuild. Instead, merge the small
113
+ // (non-base) layers together first, then do a SINGLE merge with base — base is deep-cloned
114
+ // once. Output is identical: last-wins deep object/array merge is associative for these
115
+ // layers (verified by the theme-getter tests below, which pin full theme output).
116
+ const overlay = mergeThemeLayers(baseBreakpoint, this.currency, this.currencyOverrides, this.language, this.languageOverrides, themeOverrides, {
86
117
  activeBreakpoint: activeBreakpoint,
87
118
  });
119
+ let theme = mergeThemeLayers(this.base, overlay);
88
120
  /*
89
121
  Ensure 'theme' prop has overrides applied to it
90
122
  - separate the "base" theme from "overrides"
@@ -112,9 +144,11 @@ class ThemeStore {
112
144
  }
113
145
  // TemplateEditor overrides
114
146
  if (this.editMode) {
115
- theme = mergeThemeLayers(theme, this.editorOverrides);
147
+ // PERF: same single-clone pattern — pre-merge the small editor layers, then one merge
148
+ // with the (large) theme instead of cloning the theme twice.
116
149
  const editorOverrideBreakpoint = getOverridesAtActiveBreakpoint(activeBreakpoint, this.editorOverrides);
117
- theme = mergeThemeLayers(theme, editorOverrideBreakpoint);
150
+ const editorOverlay = mergeThemeLayers(this.editorOverrides, editorOverrideBreakpoint);
151
+ theme = mergeThemeLayers(theme, editorOverlay);
118
152
  }
119
153
  const activeStyleFns = [this.base.globalStyle, this.style].filter(Boolean);
120
154
  if (activeStyleFns.length > 0) {
@@ -127,11 +161,13 @@ class ThemeStore {
127
161
  setInnerWidth(innerWidth) {
128
162
  this.innerWidth = innerWidth;
129
163
  }
130
- setCurrency(currency) {
164
+ setCurrency(currency, currencyOverrides = {}) {
131
165
  this.currency = currency;
166
+ this.currencyOverrides = currencyOverrides;
132
167
  }
133
- setLanguage(language) {
168
+ setLanguage(language, languageOverrides = {}) {
134
169
  this.language = language;
170
+ this.languageOverrides = languageOverrides;
135
171
  }
136
172
  setEditorOverrides(overrides) {
137
173
  this.editorOverrides = overrides;
@@ -177,11 +213,55 @@ const arrayMerge = (target, source, options) => {
177
213
  });
178
214
  return destination;
179
215
  };
216
+ function flattenCascadingOverrides(components) {
217
+ if (!components)
218
+ return {};
219
+ const flattened = {};
220
+ Object.keys(components).forEach((key) => {
221
+ //grab the $children property and remove it from the component props so it doesn't get merged into the flattened component
222
+ const { $children, ...ownProps } = components[key] || {};
223
+ flattened[key] = { ...flattened[key], ...ownProps };
224
+ // if there are $children, flatten them and prefix the parent key to each child key
225
+ if ($children) {
226
+ const flatChildren = flattenCascadingOverrides($children);
227
+ const parentParts = key.split(/\s*,\s*/);
228
+ Object.keys(flatChildren).forEach((childKey) => {
229
+ const childParts = childKey.split(/\s*,\s*/);
230
+ const prefixedParts = [];
231
+ parentParts.forEach((parentPart) => {
232
+ childParts.forEach((childPart) => {
233
+ // rebuild the treepath with the parent key and the child key.
234
+ // so that merge props takes these flattened overrides and applied them like normal
235
+ prefixedParts.push(`${parentPart} ${childPart}`);
236
+ });
237
+ });
238
+ const prefixedKey = prefixedParts.join(', ');
239
+ flattened[prefixedKey] = { ...flattened[prefixedKey], ...flatChildren[childKey] };
240
+ });
241
+ }
242
+ });
243
+ return flattened;
244
+ }
180
245
  function prefixComponentKeys(prefix, components) {
181
246
  // TODO: remove any?
182
247
  const newComponents = {};
183
248
  if (components) {
184
249
  Object.keys(components).forEach((key) => {
250
+ // Handle comma-separated selectors by prefixing each individual selector
251
+ // Split on comma with optional surrounding whitespace, normalize to ', '
252
+ if (key.includes(',')) {
253
+ const prefixedKey = key
254
+ .split(/\s*,\s*/)
255
+ .map((part) => {
256
+ // does the part already have the prefix?
257
+ if (part.indexOf(prefix) === 0)
258
+ return part;
259
+ return `${prefix}${part}`;
260
+ })
261
+ .join(', ');
262
+ newComponents[prefixedKey] = components[key];
263
+ return;
264
+ }
185
265
  //does the key already have the prefix? - this is needed when using the editor.
186
266
  if (key.indexOf(prefix) === 0) {
187
267
  newComponents[key] = components[key];
@@ -193,3 +273,16 @@ function prefixComponentKeys(prefix, components) {
193
273
  }
194
274
  return newComponents;
195
275
  }
276
+ function normalizeCommaSeparatedKeys(components) {
277
+ const newComponents = {};
278
+ Object.keys(components).forEach((key) => {
279
+ if (key.includes(',')) {
280
+ const normalized = key.split(/\s*,\s*/).join(', ');
281
+ newComponents[normalized] = components[key];
282
+ }
283
+ else {
284
+ newComponents[key] = components[key];
285
+ }
286
+ });
287
+ return newComponents;
288
+ }