@akinon/next 2.0.0-beta.2 → 2.0.0-beta.20
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/.eslintrc.js +12 -0
- package/CHANGELOG.md +377 -7
- package/__tests__/next-config.test.ts +83 -0
- package/__tests__/tsconfig.json +23 -0
- package/api/auth.ts +133 -44
- package/api/barcode-search.ts +59 -0
- package/api/cache.ts +41 -5
- package/api/client.ts +21 -4
- package/api/form.ts +85 -0
- package/api/image-proxy.ts +75 -0
- package/api/product-categories.ts +53 -0
- package/api/similar-product-list.ts +63 -0
- package/api/similar-products.ts +111 -0
- package/api/virtual-try-on.ts +382 -0
- package/assets/styles/index.scss +84 -0
- package/babel.config.js +6 -0
- package/bin/pz-generate-routes.js +115 -0
- package/bin/pz-prebuild.js +1 -0
- package/bin/pz-predev.js +1 -0
- package/bin/pz-run-tests.js +99 -0
- package/bin/run-prebuild-tests.js +46 -0
- package/components/accordion.tsx +20 -5
- package/components/button.tsx +51 -36
- package/components/client-root.tsx +138 -2
- package/components/file-input.tsx +65 -3
- package/components/index.ts +1 -0
- package/components/input.tsx +1 -1
- package/components/link.tsx +46 -16
- package/components/logger-popup.tsx +213 -0
- package/components/modal.tsx +32 -16
- package/components/plugin-module.tsx +62 -3
- package/components/price.tsx +2 -2
- package/components/select.tsx +1 -1
- package/components/selected-payment-option-view.tsx +21 -0
- package/components/theme-editor/blocks/accordion-block.tsx +136 -0
- package/components/theme-editor/blocks/block-renderer-registry.tsx +77 -0
- package/components/theme-editor/blocks/button-block.tsx +593 -0
- package/components/theme-editor/blocks/counter-block.tsx +348 -0
- package/components/theme-editor/blocks/divider-block.tsx +20 -0
- package/components/theme-editor/blocks/embed-block.tsx +208 -0
- package/components/theme-editor/blocks/group-block.tsx +116 -0
- package/components/theme-editor/blocks/hotspot-block.tsx +147 -0
- package/components/theme-editor/blocks/icon-block.tsx +230 -0
- package/components/theme-editor/blocks/image-block.tsx +137 -0
- package/components/theme-editor/blocks/image-gallery-block.tsx +269 -0
- package/components/theme-editor/blocks/input-block.tsx +123 -0
- package/components/theme-editor/blocks/link-block.tsx +216 -0
- package/components/theme-editor/blocks/lottie-block.tsx +325 -0
- package/components/theme-editor/blocks/map-block.tsx +89 -0
- package/components/theme-editor/blocks/slider-block.tsx +595 -0
- package/components/theme-editor/blocks/tab-block.tsx +10 -0
- package/components/theme-editor/blocks/text-block.tsx +52 -0
- package/components/theme-editor/blocks/video-block.tsx +122 -0
- package/components/theme-editor/components/action-toolbar.tsx +305 -0
- package/components/theme-editor/components/designer-overlay.tsx +74 -0
- package/components/theme-editor/components/with-designer-features.tsx +142 -0
- package/components/theme-editor/dynamic-font-loader.tsx +79 -0
- package/components/theme-editor/hooks/use-designer-features.tsx +100 -0
- package/components/theme-editor/hooks/use-external-designer.tsx +95 -0
- package/components/theme-editor/hooks/use-native-widget-data.ts +188 -0
- package/components/theme-editor/hooks/use-visibility-context.ts +27 -0
- package/components/theme-editor/placeholder-registry.ts +31 -0
- package/components/theme-editor/sections/before-after-section.tsx +245 -0
- package/components/theme-editor/sections/contact-form-section.tsx +563 -0
- package/components/theme-editor/sections/countdown-campaign-banner-section.tsx +433 -0
- package/components/theme-editor/sections/coupon-banner-section.tsx +710 -0
- package/components/theme-editor/sections/divider-section.tsx +62 -0
- package/components/theme-editor/sections/featured-product-spotlight-section.tsx +507 -0
- package/components/theme-editor/sections/find-in-store-section.tsx +1995 -0
- package/components/theme-editor/sections/hover-showcase-section.tsx +326 -0
- package/components/theme-editor/sections/image-hotspot-section.tsx +142 -0
- package/components/theme-editor/sections/installment-options-section.tsx +1065 -0
- package/components/theme-editor/sections/notification-banner-section.tsx +173 -0
- package/components/theme-editor/sections/order-tracking-lookup-section.tsx +1379 -0
- package/components/theme-editor/sections/posts-slider-section.tsx +472 -0
- package/components/theme-editor/sections/pre-order-launch-banner-section.tsx +663 -0
- package/components/theme-editor/sections/section-renderer-registry.tsx +89 -0
- package/components/theme-editor/sections/section-wrapper.tsx +135 -0
- package/components/theme-editor/sections/shipping-threshold-progress-section.tsx +586 -0
- package/components/theme-editor/sections/stats-counter-section.tsx +486 -0
- package/components/theme-editor/sections/tabs-section.tsx +578 -0
- package/components/theme-editor/theme-block.tsx +102 -0
- package/components/theme-editor/theme-placeholder-client.tsx +218 -0
- package/components/theme-editor/theme-placeholder-wrapper.tsx +732 -0
- package/components/theme-editor/theme-placeholder.tsx +288 -0
- package/components/theme-editor/theme-section.tsx +1224 -0
- package/components/theme-editor/theme-settings-context.tsx +13 -0
- package/components/theme-editor/utils/index.ts +792 -0
- package/components/theme-editor/utils/iterator-utils.ts +234 -0
- package/components/theme-editor/utils/publish-window.ts +86 -0
- package/components/theme-editor/utils/visibility-rules.ts +188 -0
- package/data/client/account.ts +17 -2
- package/data/client/api.ts +2 -0
- package/data/client/basket.ts +66 -5
- package/data/client/checkout.ts +391 -99
- package/data/client/misc.ts +38 -2
- package/data/client/product.ts +19 -2
- package/data/client/user.ts +16 -8
- package/data/server/category.ts +11 -9
- package/data/server/flatpage.ts +11 -4
- package/data/server/form.ts +15 -4
- package/data/server/landingpage.ts +11 -4
- package/data/server/list.ts +5 -4
- package/data/server/menu.ts +11 -3
- package/data/server/product.ts +111 -55
- package/data/server/seo.ts +14 -4
- package/data/server/special-page.ts +5 -4
- package/data/server/widget.ts +90 -5
- package/data/urls.ts +16 -5
- package/hocs/client/with-segment-defaults.tsx +2 -2
- package/hocs/server/with-segment-defaults.tsx +65 -20
- package/hooks/index.ts +4 -0
- package/hooks/use-localization.ts +24 -10
- package/hooks/use-logger-context.tsx +114 -0
- package/hooks/use-logger.ts +92 -0
- package/hooks/use-loyalty-availability.ts +21 -0
- package/hooks/use-payment-options.ts +2 -1
- package/hooks/use-pz-params.ts +37 -0
- package/hooks/use-router.ts +51 -14
- package/hooks/use-sentry-uncaught-errors.ts +24 -0
- package/instrumentation/index.ts +10 -1
- package/instrumentation/node.ts +2 -20
- package/jest.config.js +25 -0
- package/lib/cache-handler.mjs +534 -16
- package/lib/cache.ts +272 -37
- package/localization/index.ts +2 -1
- package/localization/provider.tsx +2 -5
- package/middlewares/bfcache-headers.ts +18 -0
- package/middlewares/checkout-provider.ts +1 -1
- package/middlewares/complete-gpay.ts +32 -26
- package/middlewares/complete-masterpass.ts +33 -26
- package/middlewares/complete-wallet.ts +182 -0
- package/middlewares/default.ts +360 -215
- package/middlewares/index.ts +10 -2
- package/middlewares/locale.ts +34 -11
- package/middlewares/masterpass-rest-callback.ts +230 -0
- package/middlewares/oauth-login.ts +200 -57
- package/middlewares/pretty-url.ts +21 -8
- package/middlewares/redirection-payment.ts +32 -26
- package/middlewares/saved-card-redirection.ts +33 -26
- package/middlewares/three-d-redirection.ts +32 -26
- package/middlewares/url-redirection.ts +11 -1
- package/middlewares/wallet-complete-redirection.ts +206 -0
- package/package.json +25 -10
- package/plugins.d.ts +19 -4
- package/plugins.js +10 -1
- package/redux/actions.ts +47 -0
- package/redux/middlewares/checkout.ts +63 -138
- package/redux/middlewares/index.ts +14 -10
- package/redux/middlewares/pre-order/address.ts +7 -2
- package/redux/middlewares/pre-order/attribute-based-shipping-option.ts +7 -1
- package/redux/middlewares/pre-order/data-source-shipping-option.ts +7 -1
- package/redux/middlewares/pre-order/delivery-option.ts +7 -1
- package/redux/middlewares/pre-order/index.ts +16 -10
- package/redux/middlewares/pre-order/installment-option.ts +8 -1
- package/redux/middlewares/pre-order/payment-option-reset.ts +37 -0
- package/redux/middlewares/pre-order/payment-option.ts +7 -1
- package/redux/middlewares/pre-order/pre-order-validation.ts +8 -3
- package/redux/middlewares/pre-order/redirection.ts +8 -2
- package/redux/middlewares/pre-order/set-pre-order.ts +6 -2
- package/redux/middlewares/pre-order/shipping-option.ts +7 -1
- package/redux/middlewares/pre-order/shipping-step.ts +5 -1
- package/redux/reducers/checkout.ts +23 -3
- package/redux/reducers/index.ts +11 -3
- package/redux/reducers/root.ts +7 -2
- package/redux/reducers/widget.ts +80 -0
- package/sentry/index.ts +69 -13
- package/tailwind/content.js +16 -0
- package/types/commerce/account.ts +5 -1
- package/types/commerce/checkout.ts +35 -1
- package/types/commerce/widget.ts +33 -0
- package/types/index.ts +101 -6
- package/types/next-auth.d.ts +2 -2
- package/types/widget.ts +80 -0
- package/utils/app-fetch.ts +7 -2
- package/utils/generate-commerce-search-params.ts +3 -2
- package/utils/get-checkout-path.ts +3 -0
- package/utils/get-root-hostname.ts +28 -0
- package/utils/index.ts +64 -10
- package/utils/localization.ts +4 -0
- package/utils/mobile-3d-iframe.ts +8 -2
- package/utils/override-middleware.ts +7 -12
- package/utils/pz-segments.ts +92 -0
- package/utils/redirect-ignore.ts +35 -0
- package/utils/redirect.ts +9 -3
- package/utils/redirection-iframe.ts +8 -2
- package/utils/widget-styles.ts +107 -0
- package/views/error-page.tsx +93 -0
- package/with-pz-config.js +13 -6
|
@@ -0,0 +1,792 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export * from './publish-window';
|
|
3
|
+
|
|
4
|
+
type ThemeSettings = Record<string, unknown> | null;
|
|
5
|
+
|
|
6
|
+
const THEME_CSS_VARIABLE_MAP: Record<string, string> = {
|
|
7
|
+
'--theme-primary': 'primaryColor',
|
|
8
|
+
'--theme-secondary': 'secondaryColor',
|
|
9
|
+
'--theme-color3': 'color3',
|
|
10
|
+
'--theme-color4': 'color4',
|
|
11
|
+
'--theme-background': 'backgroundColor'
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const CSS_VAR_REGEX = /var\((--[\w-]+)(?:,\s*([^)]+))?\)/g;
|
|
15
|
+
|
|
16
|
+
const stripQuotes = (value: string) => value.replace(/^['"]|['"]$/g, '').trim();
|
|
17
|
+
|
|
18
|
+
const getComputedCssVariable = (variableName: string): string | null => {
|
|
19
|
+
if (typeof window === 'undefined') {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const computedValue = getComputedStyle(
|
|
24
|
+
document.documentElement
|
|
25
|
+
).getPropertyValue(variableName);
|
|
26
|
+
return computedValue ? computedValue.trim() : null;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const clampAlpha = (
|
|
30
|
+
value: unknown,
|
|
31
|
+
fallbackPercent = 100
|
|
32
|
+
): number | null => {
|
|
33
|
+
const numeric = Number(
|
|
34
|
+
value === undefined || value === null || value === ''
|
|
35
|
+
? fallbackPercent
|
|
36
|
+
: value
|
|
37
|
+
);
|
|
38
|
+
if (Number.isNaN(numeric)) return null;
|
|
39
|
+
return Math.max(0, Math.min(1, numeric / 100));
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const colorToRgba = (color: string, alpha: number): string | null => {
|
|
43
|
+
let normalized = color.trim();
|
|
44
|
+
// Some theme settings may provide hex without a leading '#'
|
|
45
|
+
if (
|
|
46
|
+
!normalized.startsWith('#') &&
|
|
47
|
+
/^[0-9a-f]{3}([0-9a-f]{3})?$/i.test(normalized)
|
|
48
|
+
) {
|
|
49
|
+
normalized = `#${normalized}`;
|
|
50
|
+
}
|
|
51
|
+
const short = normalized.match(/^#([0-9a-f]{3})$/i);
|
|
52
|
+
const long = normalized.match(/^#([0-9a-f]{6})$/i);
|
|
53
|
+
|
|
54
|
+
if (short) {
|
|
55
|
+
const h = short[1];
|
|
56
|
+
const r = parseInt(h[0] + h[0], 16);
|
|
57
|
+
const g = parseInt(h[1] + h[1], 16);
|
|
58
|
+
const b = parseInt(h[2] + h[2], 16);
|
|
59
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (long) {
|
|
63
|
+
const h = long[1];
|
|
64
|
+
const r = parseInt(h.slice(0, 2), 16);
|
|
65
|
+
const g = parseInt(h.slice(2, 4), 16);
|
|
66
|
+
const b = parseInt(h.slice(4, 6), 16);
|
|
67
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// rgb(12, 34, 56) or rgba(12, 34, 56, 0.5)
|
|
71
|
+
const commaRgb = normalized.match(
|
|
72
|
+
/^rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})(?:\s*,\s*([0-9.]+))?\s*\)$/i
|
|
73
|
+
);
|
|
74
|
+
if (commaRgb) {
|
|
75
|
+
const r = Number(commaRgb[1]);
|
|
76
|
+
const g = Number(commaRgb[2]);
|
|
77
|
+
const b = Number(commaRgb[3]);
|
|
78
|
+
if ([r, g, b].some((v) => Number.isNaN(v))) return null;
|
|
79
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// rgb(12 34 56) or rgb(12 34 56 / 0.5)
|
|
83
|
+
const spaceRgb = normalized.match(
|
|
84
|
+
/^rgb\(\s*(\d{1,3})\s+(\d{1,3})\s+(\d{1,3})(?:\s*\/\s*([0-9.]+%?))?\s*\)$/i
|
|
85
|
+
);
|
|
86
|
+
if (spaceRgb) {
|
|
87
|
+
const r = Number(spaceRgb[1]);
|
|
88
|
+
const g = Number(spaceRgb[2]);
|
|
89
|
+
const b = Number(spaceRgb[3]);
|
|
90
|
+
if ([r, g, b].some((v) => Number.isNaN(v))) return null;
|
|
91
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return null;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export const applyAlphaToColor = (color: string, alpha: number): string => {
|
|
98
|
+
if (alpha <= 0) return 'transparent';
|
|
99
|
+
if (alpha >= 1) return color;
|
|
100
|
+
|
|
101
|
+
const rgba = colorToRgba(color, alpha);
|
|
102
|
+
if (rgba) return rgba;
|
|
103
|
+
|
|
104
|
+
// Fallback for values like `var(--theme-secondary)` or named colors.
|
|
105
|
+
// Modern browsers support `color-mix` which lets us blend with transparent.
|
|
106
|
+
const percent = Math.round(alpha * 100);
|
|
107
|
+
return `color-mix(in srgb, ${color} ${percent}%, transparent)`;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export const resolveThemeCssVariables = (
|
|
111
|
+
value: string,
|
|
112
|
+
themeSettings?: ThemeSettings
|
|
113
|
+
): string => {
|
|
114
|
+
if (typeof value !== 'string' || !value.includes('var(')) {
|
|
115
|
+
return value;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return value.replace(
|
|
119
|
+
CSS_VAR_REGEX,
|
|
120
|
+
(_match, variableName: string, fallbackValue?: string) => {
|
|
121
|
+
const mappedKey = THEME_CSS_VARIABLE_MAP[variableName];
|
|
122
|
+
const themeColor = mappedKey
|
|
123
|
+
? (themeSettings?.[mappedKey] as string | undefined)
|
|
124
|
+
: undefined;
|
|
125
|
+
const computedValue = getComputedCssVariable(variableName);
|
|
126
|
+
const fallback = fallbackValue ? stripQuotes(fallbackValue) : undefined;
|
|
127
|
+
|
|
128
|
+
return themeColor || computedValue || fallback || `var(${variableName})`;
|
|
129
|
+
}
|
|
130
|
+
);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export const resolveThemeStyleObject = (
|
|
134
|
+
styles: React.CSSProperties,
|
|
135
|
+
themeSettings?: ThemeSettings
|
|
136
|
+
): React.CSSProperties => {
|
|
137
|
+
const resolvedEntries = Object.entries(styles).map(([key, val]) => {
|
|
138
|
+
if (typeof val === 'string') {
|
|
139
|
+
return [key, resolveThemeCssVariables(val, themeSettings)];
|
|
140
|
+
}
|
|
141
|
+
return [key, val];
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
return Object.fromEntries(resolvedEntries) as React.CSSProperties;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
export const getResponsiveValue = (
|
|
148
|
+
value: unknown,
|
|
149
|
+
breakpoint = 'desktop',
|
|
150
|
+
fallback: unknown = ''
|
|
151
|
+
) => {
|
|
152
|
+
if (value === undefined || value === null || value === '') return fallback;
|
|
153
|
+
if (typeof value === 'object') {
|
|
154
|
+
const responsiveValue = value as Record<string, unknown>;
|
|
155
|
+
const matched = responsiveValue[breakpoint];
|
|
156
|
+
if (matched !== undefined) return matched;
|
|
157
|
+
if (responsiveValue.desktop !== undefined) return responsiveValue.desktop;
|
|
158
|
+
return fallback;
|
|
159
|
+
}
|
|
160
|
+
return value;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const kebabToCamel = (str: string): string => {
|
|
164
|
+
return str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
export const getCSSStyles = (
|
|
168
|
+
styles?: Record<string, unknown>,
|
|
169
|
+
themeSettings?: ThemeSettings,
|
|
170
|
+
breakpoint: string = 'desktop'
|
|
171
|
+
): React.CSSProperties => {
|
|
172
|
+
if (!styles) return {};
|
|
173
|
+
|
|
174
|
+
const cssStyles: Record<string, string | number> = {};
|
|
175
|
+
|
|
176
|
+
const normalizeKey = (key: string): string => {
|
|
177
|
+
if (key.includes('-')) return key;
|
|
178
|
+
return key.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const getStyleEntry = (kebabKey: string): unknown => {
|
|
182
|
+
// Some sources may serialize keys in camelCase.
|
|
183
|
+
// Prefer kebab-case but fall back to camelCase.
|
|
184
|
+
const camelKey = kebabToCamel(kebabKey);
|
|
185
|
+
return styles[kebabKey] ?? styles[camelKey];
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const textOpacityEntry = getStyleEntry('text-opacity');
|
|
189
|
+
const textOpacityValue = textOpacityEntry
|
|
190
|
+
? getResponsiveValue(textOpacityEntry, breakpoint, '100')
|
|
191
|
+
: '100';
|
|
192
|
+
const textOpacity = Number(textOpacityValue);
|
|
193
|
+
const textAlpha = !Number.isNaN(textOpacity)
|
|
194
|
+
? Math.max(0, Math.min(1, textOpacity / 100))
|
|
195
|
+
: null;
|
|
196
|
+
|
|
197
|
+
const backgroundOpacityEntry = getStyleEntry('background-opacity');
|
|
198
|
+
const backgroundOpacityValue = backgroundOpacityEntry
|
|
199
|
+
? getResponsiveValue(backgroundOpacityEntry, breakpoint, '100')
|
|
200
|
+
: '100';
|
|
201
|
+
const backgroundOpacity = Number(backgroundOpacityValue);
|
|
202
|
+
const backgroundAlpha = !Number.isNaN(backgroundOpacity)
|
|
203
|
+
? Math.max(0, Math.min(1, backgroundOpacity / 100))
|
|
204
|
+
: null;
|
|
205
|
+
|
|
206
|
+
Object.keys(styles).forEach((key) => {
|
|
207
|
+
const value = getResponsiveValue(styles[key], breakpoint);
|
|
208
|
+
if (value === undefined || value === null) return;
|
|
209
|
+
|
|
210
|
+
const normalizedKey = normalizeKey(key);
|
|
211
|
+
|
|
212
|
+
// `text-opacity` is not a real CSS property; it should influence `color`.
|
|
213
|
+
if (normalizedKey === 'text-opacity') {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// `background-opacity` is not a real CSS property; it should influence `background-color`.
|
|
218
|
+
if (normalizedKey === 'background-opacity') {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// `hover-opacity` is not a real CSS property; it should influence hover state.
|
|
223
|
+
if (normalizedKey === 'hover-opacity') {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Skip generic `opacity` from button defaults to prevent element-wide transparency.
|
|
228
|
+
if (normalizedKey === 'opacity') {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const camelKey = kebabToCamel(key);
|
|
233
|
+
|
|
234
|
+
if (
|
|
235
|
+
(key.startsWith('padding-') || key.startsWith('margin-')) &&
|
|
236
|
+
typeof value === 'number'
|
|
237
|
+
) {
|
|
238
|
+
cssStyles[camelKey] = `${value}px`;
|
|
239
|
+
} else if (key === 'width' && value === 'fill') {
|
|
240
|
+
cssStyles[camelKey] = '100%';
|
|
241
|
+
} else if (key === 'width' && value === 'fit') {
|
|
242
|
+
cssStyles[camelKey] = 'fit-content';
|
|
243
|
+
} else if (key === 'height' && value === 'fill') {
|
|
244
|
+
cssStyles[camelKey] = '100%';
|
|
245
|
+
} else if (key === 'height' && value === 'fit') {
|
|
246
|
+
cssStyles[camelKey] = 'fit-content';
|
|
247
|
+
} else if (typeof value === 'string') {
|
|
248
|
+
const resolved = resolveThemeCssVariables(value, themeSettings);
|
|
249
|
+
|
|
250
|
+
if (normalizedKey === 'color' && textAlpha !== null) {
|
|
251
|
+
const rgba = colorToRgba(resolved, textAlpha);
|
|
252
|
+
if (rgba) {
|
|
253
|
+
cssStyles[camelKey] = rgba;
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (normalizedKey === 'background-color' && backgroundAlpha !== null) {
|
|
259
|
+
if (backgroundAlpha === 0) {
|
|
260
|
+
cssStyles[camelKey] = 'transparent';
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const rgba = colorToRgba(resolved, backgroundAlpha);
|
|
265
|
+
if (rgba) {
|
|
266
|
+
cssStyles[camelKey] = rgba;
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
cssStyles[camelKey] = resolved;
|
|
272
|
+
} else {
|
|
273
|
+
cssStyles[camelKey] = value as number;
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
return cssStyles as React.CSSProperties;
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
interface ResponsiveStyles {
|
|
281
|
+
[key: string]:
|
|
282
|
+
| {
|
|
283
|
+
desktop?: string | number;
|
|
284
|
+
mobile?: string | number;
|
|
285
|
+
}
|
|
286
|
+
| string
|
|
287
|
+
| number;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
interface Block {
|
|
291
|
+
id: string;
|
|
292
|
+
styles?: ResponsiveStyles;
|
|
293
|
+
blocks?: Block[];
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
interface Section {
|
|
297
|
+
id: string;
|
|
298
|
+
styles?: ResponsiveStyles;
|
|
299
|
+
blocks?: Block[];
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const camelToKebab = (str: string): string => {
|
|
303
|
+
return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
const formatCSSValue = (property: string, value: string | number): string => {
|
|
307
|
+
if (value === undefined || value === null || value === '') return '';
|
|
308
|
+
|
|
309
|
+
const kebabProperty = camelToKebab(property);
|
|
310
|
+
|
|
311
|
+
if (kebabProperty === 'width' || kebabProperty === 'height') {
|
|
312
|
+
if (value === 'fill') return '100%';
|
|
313
|
+
if (value === 'fit') return 'fit-content';
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const needsUnit = [
|
|
317
|
+
'width',
|
|
318
|
+
'height',
|
|
319
|
+
'max-width',
|
|
320
|
+
'max-height',
|
|
321
|
+
'min-width',
|
|
322
|
+
'min-height',
|
|
323
|
+
'padding',
|
|
324
|
+
'padding-top',
|
|
325
|
+
'padding-right',
|
|
326
|
+
'padding-bottom',
|
|
327
|
+
'padding-left',
|
|
328
|
+
'margin',
|
|
329
|
+
'margin-top',
|
|
330
|
+
'margin-right',
|
|
331
|
+
'margin-bottom',
|
|
332
|
+
'margin-left',
|
|
333
|
+
'gap',
|
|
334
|
+
'row-gap',
|
|
335
|
+
'column-gap',
|
|
336
|
+
'top',
|
|
337
|
+
'right',
|
|
338
|
+
'bottom',
|
|
339
|
+
'left',
|
|
340
|
+
'font-size',
|
|
341
|
+
'line-height',
|
|
342
|
+
'letter-spacing',
|
|
343
|
+
'border-width',
|
|
344
|
+
'border-radius',
|
|
345
|
+
'border-top-left-radius',
|
|
346
|
+
'border-top-right-radius',
|
|
347
|
+
'border-bottom-left-radius',
|
|
348
|
+
'border-bottom-right-radius'
|
|
349
|
+
];
|
|
350
|
+
|
|
351
|
+
if (typeof value === 'number' && needsUnit.includes(kebabProperty)) {
|
|
352
|
+
return `${value}px`;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return String(value);
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
const generateElementCSS = (
|
|
359
|
+
selector: string,
|
|
360
|
+
styles: ResponsiveStyles,
|
|
361
|
+
targetBreakpoint?: string
|
|
362
|
+
): string => {
|
|
363
|
+
if (!styles || Object.keys(styles).length === 0) return '';
|
|
364
|
+
|
|
365
|
+
const getStyleEntry = (kebabKey: string): unknown => {
|
|
366
|
+
const camelKey = kebabToCamel(kebabKey);
|
|
367
|
+
return styles[kebabKey] ?? styles[camelKey];
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
const getBackgroundAlpha = (breakpoint: string): number | null => {
|
|
371
|
+
const entry = getStyleEntry('background-opacity');
|
|
372
|
+
const value = entry ? getResponsiveValue(entry, breakpoint, '100') : '100';
|
|
373
|
+
return clampAlpha(value, 100);
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
const getHoverAlpha = (breakpoint: string): number | null => {
|
|
377
|
+
const entry = getStyleEntry('hover-opacity');
|
|
378
|
+
const value = entry ? getResponsiveValue(entry, breakpoint, '100') : '100';
|
|
379
|
+
return clampAlpha(value, 100);
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
const getHoverColor = (breakpoint: string): string | null => {
|
|
383
|
+
const entry = getStyleEntry('hover-color');
|
|
384
|
+
const value = entry
|
|
385
|
+
? getResponsiveValue(entry, breakpoint, 'var(--theme-secondary)')
|
|
386
|
+
: undefined;
|
|
387
|
+
if (value === undefined || value === null || value === '') return null;
|
|
388
|
+
return String(value);
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
const getHoverFlex = (breakpoint: string): string | null => {
|
|
392
|
+
const entry = getStyleEntry('hover-flex');
|
|
393
|
+
const value = entry
|
|
394
|
+
? getResponsiveValue(entry, breakpoint, undefined)
|
|
395
|
+
: undefined;
|
|
396
|
+
if (value === undefined || value === null || value === '') return null;
|
|
397
|
+
return String(value);
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
const getHoverChildFlex = (breakpoint: string): string | null => {
|
|
401
|
+
const entry = getStyleEntry('hover-child-flex');
|
|
402
|
+
const value = entry
|
|
403
|
+
? getResponsiveValue(entry, breakpoint, undefined)
|
|
404
|
+
: undefined;
|
|
405
|
+
if (value === undefined || value === null || value === '') return null;
|
|
406
|
+
return String(value);
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
const buildHoverDeclarations = (breakpoint: string): string[] => {
|
|
410
|
+
const declarations: string[] = [];
|
|
411
|
+
|
|
412
|
+
const hoverColor = getHoverColor(breakpoint);
|
|
413
|
+
if (hoverColor) {
|
|
414
|
+
const alpha = getHoverAlpha(breakpoint);
|
|
415
|
+
if (alpha === null) {
|
|
416
|
+
declarations.push(` background-color: ${hoverColor}`);
|
|
417
|
+
declarations.push(` border-color: ${hoverColor}`);
|
|
418
|
+
} else {
|
|
419
|
+
const resolved = applyAlphaToColor(hoverColor, alpha);
|
|
420
|
+
declarations.push(` background-color: ${resolved}`);
|
|
421
|
+
declarations.push(` border-color: ${resolved}`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const hoverFlex = getHoverFlex(breakpoint);
|
|
426
|
+
if (hoverFlex) {
|
|
427
|
+
declarations.push(` flex: ${hoverFlex}`);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return declarations;
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
const buildHoverCss = (breakpoint: string, indent = ''): string => {
|
|
434
|
+
let css = '';
|
|
435
|
+
|
|
436
|
+
const hoverChildFlex = getHoverChildFlex(breakpoint);
|
|
437
|
+
if (hoverChildFlex) {
|
|
438
|
+
css += `${indent}${selector}:hover > [data-block-id] {\n${indent} flex: ${hoverChildFlex} !important;\n${indent}}\n`;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const hoverDecls = buildHoverDeclarations(breakpoint);
|
|
442
|
+
if (hoverDecls.length === 0) return css;
|
|
443
|
+
|
|
444
|
+
const joined = hoverDecls
|
|
445
|
+
.map((line) => `${indent}${line.trimStart()}`)
|
|
446
|
+
.join(`;\n${indent}`);
|
|
447
|
+
|
|
448
|
+
const importantJoined = hoverDecls
|
|
449
|
+
.map((line) => {
|
|
450
|
+
const trimmed = line.trimStart();
|
|
451
|
+
return `${indent}${trimmed.replace(/:\s*(.+)$/, ': $1 !important')}`;
|
|
452
|
+
})
|
|
453
|
+
.join(`;\n${indent}`);
|
|
454
|
+
|
|
455
|
+
const wrapperRule = `${indent}${selector}:hover {\n${importantJoined};\n${indent}}\n`;
|
|
456
|
+
// Scope to the last child wrapper (button block content) so designer overlay controls aren't affected.
|
|
457
|
+
const childRule = `${indent}${selector}:hover > div:last-child a,\n${indent}${selector}:hover > div:last-child button {\n${importantJoined};\n${indent}}\n`;
|
|
458
|
+
|
|
459
|
+
css += `${wrapperRule}${childRule}`;
|
|
460
|
+
return css;
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
const cssStyles: string[] = [];
|
|
464
|
+
|
|
465
|
+
Object.entries(styles).forEach(([property, value]) => {
|
|
466
|
+
if (value === undefined || value === null) return;
|
|
467
|
+
|
|
468
|
+
const cssProperty = camelToKebab(property);
|
|
469
|
+
|
|
470
|
+
// Skip non-CSS helper keys.
|
|
471
|
+
if (
|
|
472
|
+
cssProperty === 'text-opacity' ||
|
|
473
|
+
cssProperty === 'background-opacity' ||
|
|
474
|
+
cssProperty === 'hover-opacity' ||
|
|
475
|
+
cssProperty === 'hover-color' ||
|
|
476
|
+
cssProperty === 'hover-flex' ||
|
|
477
|
+
cssProperty === 'hover-child-flex' ||
|
|
478
|
+
cssProperty === 'opacity'
|
|
479
|
+
) {
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
484
|
+
const responsiveValue = value as {
|
|
485
|
+
desktop?: string | number;
|
|
486
|
+
mobile?: string | number;
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
if (targetBreakpoint) {
|
|
490
|
+
const breakpointValue =
|
|
491
|
+
responsiveValue[targetBreakpoint as keyof typeof responsiveValue] ??
|
|
492
|
+
responsiveValue.desktop;
|
|
493
|
+
if (
|
|
494
|
+
breakpointValue !== undefined &&
|
|
495
|
+
breakpointValue !== null &&
|
|
496
|
+
breakpointValue !== ''
|
|
497
|
+
) {
|
|
498
|
+
if (
|
|
499
|
+
cssProperty === 'background-color' &&
|
|
500
|
+
typeof breakpointValue === 'string'
|
|
501
|
+
) {
|
|
502
|
+
const backgroundAlpha = getBackgroundAlpha(
|
|
503
|
+
targetBreakpoint === 'mobile' ? 'mobile' : 'desktop'
|
|
504
|
+
);
|
|
505
|
+
if (backgroundAlpha !== null) {
|
|
506
|
+
if (backgroundAlpha === 0) {
|
|
507
|
+
cssStyles.push(` ${cssProperty}: transparent`);
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
const rgba = colorToRgba(breakpointValue, backgroundAlpha);
|
|
511
|
+
if (rgba) {
|
|
512
|
+
cssStyles.push(` ${cssProperty}: ${rgba}`);
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const formattedValue = formatCSSValue(property, breakpointValue);
|
|
519
|
+
if (formattedValue) {
|
|
520
|
+
cssStyles.push(` ${cssProperty}: ${formattedValue}`);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
} else {
|
|
524
|
+
if (
|
|
525
|
+
responsiveValue.desktop !== undefined &&
|
|
526
|
+
responsiveValue.desktop !== null &&
|
|
527
|
+
responsiveValue.desktop !== ''
|
|
528
|
+
) {
|
|
529
|
+
if (
|
|
530
|
+
cssProperty === 'background-color' &&
|
|
531
|
+
typeof responsiveValue.desktop === 'string'
|
|
532
|
+
) {
|
|
533
|
+
const backgroundAlpha = getBackgroundAlpha('desktop');
|
|
534
|
+
if (backgroundAlpha !== null) {
|
|
535
|
+
if (backgroundAlpha === 0) {
|
|
536
|
+
cssStyles.push(` ${cssProperty}: transparent`);
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
const rgba = colorToRgba(
|
|
540
|
+
responsiveValue.desktop,
|
|
541
|
+
backgroundAlpha
|
|
542
|
+
);
|
|
543
|
+
if (rgba) {
|
|
544
|
+
cssStyles.push(` ${cssProperty}: ${rgba}`);
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const formattedValue = formatCSSValue(
|
|
551
|
+
property,
|
|
552
|
+
responsiveValue.desktop
|
|
553
|
+
);
|
|
554
|
+
if (formattedValue) {
|
|
555
|
+
cssStyles.push(` ${cssProperty}: ${formattedValue}`);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
} else {
|
|
560
|
+
if (cssProperty === 'background-color' && typeof value === 'string') {
|
|
561
|
+
const backgroundAlpha = getBackgroundAlpha('desktop');
|
|
562
|
+
if (backgroundAlpha !== null) {
|
|
563
|
+
if (backgroundAlpha === 0) {
|
|
564
|
+
cssStyles.push(` ${cssProperty}: transparent`);
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
const rgba = colorToRgba(value, backgroundAlpha);
|
|
568
|
+
if (rgba) {
|
|
569
|
+
cssStyles.push(` ${cssProperty}: ${rgba}`);
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const formattedValue = formatCSSValue(property, value as string | number);
|
|
576
|
+
if (formattedValue) {
|
|
577
|
+
cssStyles.push(` ${cssProperty}: ${formattedValue}`);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
if (targetBreakpoint) {
|
|
583
|
+
const breakpointKey = targetBreakpoint === 'mobile' ? 'mobile' : 'desktop';
|
|
584
|
+
const hoverCss = buildHoverCss(breakpointKey);
|
|
585
|
+
|
|
586
|
+
if (cssStyles.length > 0) {
|
|
587
|
+
return `${selector} {\n${cssStyles.join(';\n')};\n}\n${hoverCss}`;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
if (hoverCss) {
|
|
591
|
+
return hoverCss;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
return '';
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
const desktopStyles: string[] = [];
|
|
598
|
+
const mobileStyles: string[] = [];
|
|
599
|
+
|
|
600
|
+
Object.entries(styles).forEach(([property, value]) => {
|
|
601
|
+
if (value === undefined || value === null) return;
|
|
602
|
+
|
|
603
|
+
const cssProperty = camelToKebab(property);
|
|
604
|
+
|
|
605
|
+
// Skip non-CSS helper keys.
|
|
606
|
+
if (
|
|
607
|
+
cssProperty === 'text-opacity' ||
|
|
608
|
+
cssProperty === 'background-opacity' ||
|
|
609
|
+
cssProperty === 'hover-opacity' ||
|
|
610
|
+
cssProperty === 'hover-color' ||
|
|
611
|
+
cssProperty === 'hover-flex' ||
|
|
612
|
+
cssProperty === 'hover-child-flex' ||
|
|
613
|
+
cssProperty === 'opacity'
|
|
614
|
+
) {
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
619
|
+
const responsiveValue = value as {
|
|
620
|
+
desktop?: string | number;
|
|
621
|
+
mobile?: string | number;
|
|
622
|
+
};
|
|
623
|
+
|
|
624
|
+
if (
|
|
625
|
+
responsiveValue.desktop !== undefined &&
|
|
626
|
+
responsiveValue.desktop !== null &&
|
|
627
|
+
responsiveValue.desktop !== ''
|
|
628
|
+
) {
|
|
629
|
+
if (
|
|
630
|
+
cssProperty === 'background-color' &&
|
|
631
|
+
typeof responsiveValue.desktop === 'string'
|
|
632
|
+
) {
|
|
633
|
+
const backgroundAlpha = getBackgroundAlpha('desktop');
|
|
634
|
+
if (backgroundAlpha !== null) {
|
|
635
|
+
if (backgroundAlpha === 0) {
|
|
636
|
+
desktopStyles.push(` ${cssProperty}: transparent`);
|
|
637
|
+
} else {
|
|
638
|
+
const rgba = colorToRgba(
|
|
639
|
+
responsiveValue.desktop,
|
|
640
|
+
backgroundAlpha
|
|
641
|
+
);
|
|
642
|
+
desktopStyles.push(
|
|
643
|
+
` ${cssProperty}: ${rgba || responsiveValue.desktop}`
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
} else {
|
|
647
|
+
const formattedValue = formatCSSValue(
|
|
648
|
+
property,
|
|
649
|
+
responsiveValue.desktop
|
|
650
|
+
);
|
|
651
|
+
if (formattedValue) {
|
|
652
|
+
desktopStyles.push(` ${cssProperty}: ${formattedValue}`);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
} else {
|
|
656
|
+
const formattedValue = formatCSSValue(
|
|
657
|
+
property,
|
|
658
|
+
responsiveValue.desktop
|
|
659
|
+
);
|
|
660
|
+
if (formattedValue) {
|
|
661
|
+
desktopStyles.push(` ${cssProperty}: ${formattedValue}`);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
if (
|
|
667
|
+
responsiveValue.mobile !== undefined &&
|
|
668
|
+
responsiveValue.mobile !== null &&
|
|
669
|
+
responsiveValue.mobile !== ''
|
|
670
|
+
) {
|
|
671
|
+
if (
|
|
672
|
+
cssProperty === 'background-color' &&
|
|
673
|
+
typeof responsiveValue.mobile === 'string'
|
|
674
|
+
) {
|
|
675
|
+
const backgroundAlpha = getBackgroundAlpha('mobile');
|
|
676
|
+
if (backgroundAlpha !== null) {
|
|
677
|
+
if (backgroundAlpha === 0) {
|
|
678
|
+
mobileStyles.push(` ${cssProperty}: transparent`);
|
|
679
|
+
} else {
|
|
680
|
+
const rgba = colorToRgba(responsiveValue.mobile, backgroundAlpha);
|
|
681
|
+
mobileStyles.push(
|
|
682
|
+
` ${cssProperty}: ${rgba || responsiveValue.mobile}`
|
|
683
|
+
);
|
|
684
|
+
}
|
|
685
|
+
} else {
|
|
686
|
+
const formattedMobile = formatCSSValue(
|
|
687
|
+
property,
|
|
688
|
+
responsiveValue.mobile
|
|
689
|
+
);
|
|
690
|
+
const formattedDesktop =
|
|
691
|
+
responsiveValue.desktop !== undefined
|
|
692
|
+
? formatCSSValue(property, responsiveValue.desktop)
|
|
693
|
+
: '';
|
|
694
|
+
|
|
695
|
+
if (formattedMobile && formattedMobile !== formattedDesktop) {
|
|
696
|
+
mobileStyles.push(` ${cssProperty}: ${formattedMobile}`);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
} else {
|
|
700
|
+
const formattedMobile = formatCSSValue(
|
|
701
|
+
property,
|
|
702
|
+
responsiveValue.mobile
|
|
703
|
+
);
|
|
704
|
+
const formattedDesktop =
|
|
705
|
+
responsiveValue.desktop !== undefined
|
|
706
|
+
? formatCSSValue(property, responsiveValue.desktop)
|
|
707
|
+
: '';
|
|
708
|
+
|
|
709
|
+
if (formattedMobile && formattedMobile !== formattedDesktop) {
|
|
710
|
+
mobileStyles.push(` ${cssProperty}: ${formattedMobile}`);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
} else {
|
|
715
|
+
if (cssProperty === 'background-color' && typeof value === 'string') {
|
|
716
|
+
const backgroundAlpha = getBackgroundAlpha('desktop');
|
|
717
|
+
if (backgroundAlpha !== null) {
|
|
718
|
+
if (backgroundAlpha === 0) {
|
|
719
|
+
desktopStyles.push(` ${cssProperty}: transparent`);
|
|
720
|
+
} else {
|
|
721
|
+
const rgba = colorToRgba(value, backgroundAlpha);
|
|
722
|
+
desktopStyles.push(` ${cssProperty}: ${rgba || value}`);
|
|
723
|
+
}
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
const formattedValue = formatCSSValue(property, value as string | number);
|
|
729
|
+
if (formattedValue) {
|
|
730
|
+
desktopStyles.push(` ${cssProperty}: ${formattedValue}`);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
let css = '';
|
|
736
|
+
|
|
737
|
+
if (desktopStyles.length > 0) {
|
|
738
|
+
css += `${selector} {\n${desktopStyles.join(';\n')};\n}\n`;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
css += buildHoverCss('desktop');
|
|
742
|
+
|
|
743
|
+
if (mobileStyles.length > 0) {
|
|
744
|
+
const mobileHoverCss = buildHoverCss('mobile', ' ');
|
|
745
|
+
|
|
746
|
+
css += `@media (max-width: 1023px) {\n ${selector} {\n ${mobileStyles.join(
|
|
747
|
+
';\n '
|
|
748
|
+
)};\n }\n${mobileHoverCss}}\n`;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
return css;
|
|
752
|
+
};
|
|
753
|
+
|
|
754
|
+
const collectBlockCSS = (
|
|
755
|
+
blocks: Block[],
|
|
756
|
+
targetBreakpoint?: string
|
|
757
|
+
): string => {
|
|
758
|
+
let css = '';
|
|
759
|
+
|
|
760
|
+
blocks.forEach((block) => {
|
|
761
|
+
if (block.styles && Object.keys(block.styles).length > 0) {
|
|
762
|
+
const selector = `[data-block-id="${block.id}"]`;
|
|
763
|
+
css += generateElementCSS(selector, block.styles, targetBreakpoint);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
if (block.blocks && block.blocks.length > 0) {
|
|
767
|
+
css += collectBlockCSS(block.blocks, targetBreakpoint);
|
|
768
|
+
}
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
return css;
|
|
772
|
+
};
|
|
773
|
+
|
|
774
|
+
export const generateThemeCSS = (
|
|
775
|
+
sections: Section[],
|
|
776
|
+
targetBreakpoint?: string
|
|
777
|
+
): string => {
|
|
778
|
+
let css = '';
|
|
779
|
+
|
|
780
|
+
sections.forEach((section) => {
|
|
781
|
+
if (section.styles && Object.keys(section.styles).length > 0) {
|
|
782
|
+
const selector = `[data-section-id="${section.id}"]`;
|
|
783
|
+
css += generateElementCSS(selector, section.styles, targetBreakpoint);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
if (section.blocks && section.blocks.length > 0) {
|
|
787
|
+
css += collectBlockCSS(section.blocks, targetBreakpoint);
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
return css;
|
|
792
|
+
};
|