@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.
- package/dist/cjs/components/src/components/Atoms/Price/Price.js +2 -2
- package/dist/cjs/components/src/components/Molecules/CustomComponentWrapper/CustomComponentWrapper.js +1 -1
- package/dist/cjs/components/src/components/Molecules/Slideshow/Slideshow.js +59 -30
- package/dist/cjs/components/src/components/Organisms/AutocompleteLayout/AutocompleteLayout.js +13 -8
- package/dist/cjs/components/src/components/Organisms/QuickviewLayout/QuickviewLayout.js +13 -8
- package/dist/cjs/components/src/hooks/useLayoutOptions.js +19 -14
- package/dist/cjs/components/src/utilities/mergeProps.js +647 -184
- package/dist/cjs/src/Snap.js +1 -1
- package/dist/cjs/src/Templates/SnapTemplates.js +17 -53
- package/dist/cjs/src/Templates/Stores/LibraryStore.js +2 -0
- package/dist/cjs/src/Templates/Stores/TemplateStore.js +37 -8
- package/dist/cjs/src/Templates/Stores/ThemeStore.js +125 -32
- package/dist/cjs/src/Templates/Stores/library/currencies/currencies.js +52 -29
- package/dist/cjs/src/Templates/Stores/library/plugins/shopify/pluginCurrency.js +5 -0
- package/dist/esm/components/src/components/Atoms/Price/Price.d.ts +2 -0
- package/dist/esm/components/src/components/Atoms/Price/Price.d.ts.map +1 -1
- package/dist/esm/components/src/components/Atoms/Price/Price.js +2 -2
- package/dist/esm/components/src/components/Molecules/CustomComponentWrapper/CustomComponentWrapper.d.ts.map +1 -1
- package/dist/esm/components/src/components/Molecules/CustomComponentWrapper/CustomComponentWrapper.js +1 -1
- package/dist/esm/components/src/components/Molecules/Select/Select.d.ts +2 -2
- package/dist/esm/components/src/components/Molecules/Select/Select.d.ts.map +1 -1
- package/dist/esm/components/src/components/Molecules/Slideshow/Slideshow.d.ts +1 -0
- package/dist/esm/components/src/components/Molecules/Slideshow/Slideshow.d.ts.map +1 -1
- package/dist/esm/components/src/components/Molecules/Slideshow/Slideshow.js +59 -30
- package/dist/esm/components/src/components/Organisms/AutocompleteLayout/AutocompleteLayout.d.ts.map +1 -1
- package/dist/esm/components/src/components/Organisms/AutocompleteLayout/AutocompleteLayout.js +13 -8
- package/dist/esm/components/src/components/Organisms/QuickviewLayout/QuickviewLayout.d.ts.map +1 -1
- package/dist/esm/components/src/components/Organisms/QuickviewLayout/QuickviewLayout.js +13 -8
- package/dist/esm/components/src/hooks/useLayoutOptions.d.ts.map +1 -1
- package/dist/esm/components/src/hooks/useLayoutOptions.js +19 -14
- package/dist/esm/components/src/providers/langComponents.d.ts +5 -0
- package/dist/esm/components/src/providers/langComponents.d.ts.map +1 -1
- package/dist/esm/components/src/providers/themeComponents.d.ts +214 -193
- package/dist/esm/components/src/providers/themeComponents.d.ts.map +1 -1
- package/dist/esm/components/src/utilities/mergeProps.d.ts +11 -0
- package/dist/esm/components/src/utilities/mergeProps.d.ts.map +1 -1
- package/dist/esm/components/src/utilities/mergeProps.js +647 -184
- package/dist/esm/src/Snap.js +1 -1
- package/dist/esm/src/Templates/SnapTemplates.d.ts +32 -19
- package/dist/esm/src/Templates/SnapTemplates.d.ts.map +1 -1
- package/dist/esm/src/Templates/SnapTemplates.js +16 -51
- package/dist/esm/src/Templates/Stores/LibraryStore.d.ts +7 -0
- package/dist/esm/src/Templates/Stores/LibraryStore.d.ts.map +1 -1
- package/dist/esm/src/Templates/Stores/LibraryStore.js +2 -0
- package/dist/esm/src/Templates/Stores/TemplateStore.d.ts +11 -7
- package/dist/esm/src/Templates/Stores/TemplateStore.d.ts.map +1 -1
- package/dist/esm/src/Templates/Stores/TemplateStore.js +31 -8
- package/dist/esm/src/Templates/Stores/ThemeStore.d.ts +5 -2
- package/dist/esm/src/Templates/Stores/ThemeStore.d.ts.map +1 -1
- package/dist/esm/src/Templates/Stores/ThemeStore.js +125 -32
- package/dist/esm/src/Templates/Stores/library/currencies/currencies.d.ts +27 -0
- package/dist/esm/src/Templates/Stores/library/currencies/currencies.d.ts.map +1 -1
- package/dist/esm/src/Templates/Stores/library/currencies/currencies.js +52 -29
- package/dist/esm/src/Templates/Stores/library/plugins/shopify/pluginCurrency.d.ts +2 -0
- package/dist/esm/src/Templates/Stores/library/plugins/shopify/pluginCurrency.d.ts.map +1 -0
- package/dist/esm/src/Templates/Stores/library/plugins/shopify/pluginCurrency.js +1 -0
- package/eslint/src/validate-config.cjs +520 -0
- package/package.json +13 -13
package/dist/cjs/src/Snap.js
CHANGED
|
@@ -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.
|
|
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 (
|
|
107
|
-
|
|
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:
|
|
110
|
-
const snapConfig = createSnapConfig(
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
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(
|
|
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:
|
|
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
|
-
//
|
|
21
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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 =
|
|
35
|
-
this.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.
|
|
60
|
-
5. language
|
|
61
|
-
6.
|
|
62
|
-
7. theme overrides
|
|
63
|
-
8.
|
|
64
|
-
9.
|
|
65
|
-
10. stored theme editor overrides
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|