@escapenavigator/utils 2.2.6 → 2.2.8
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/apply-background-palette.d.ts +1 -0
- package/dist/apply-background-palette.js +180 -0
- package/dist/format-amount/index.js +6 -9
- package/dist/format-amount/index.test.d.ts +1 -0
- package/dist/format-amount/index.test.js +18 -0
- package/dist/resolve-widget-theme.d.ts +25 -0
- package/dist/resolve-widget-theme.js +85 -0
- package/dist/serialize-slot.js +2 -0
- package/package.json +3 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const applyBackgroundPalette: (bgColor: string | undefined, setVar: (key: string, value: string | null) => void) => void;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.applyBackgroundPalette = void 0;
|
|
4
|
+
const toRgb = (color) => {
|
|
5
|
+
const canvas = document.createElement('canvas');
|
|
6
|
+
const ctx = canvas.getContext('2d');
|
|
7
|
+
if (!ctx)
|
|
8
|
+
return null;
|
|
9
|
+
ctx.fillStyle = '#000000';
|
|
10
|
+
ctx.fillStyle = color;
|
|
11
|
+
const normalized = ctx.fillStyle;
|
|
12
|
+
if (!normalized)
|
|
13
|
+
return null;
|
|
14
|
+
if (normalized.startsWith('#')) {
|
|
15
|
+
let hex = normalized.slice(1);
|
|
16
|
+
if (hex.length === 3) {
|
|
17
|
+
hex = hex
|
|
18
|
+
.split('')
|
|
19
|
+
.map((c) => c + c)
|
|
20
|
+
.join('');
|
|
21
|
+
}
|
|
22
|
+
if (hex.length !== 6)
|
|
23
|
+
return null;
|
|
24
|
+
return {
|
|
25
|
+
r: parseInt(hex.slice(0, 2), 16),
|
|
26
|
+
g: parseInt(hex.slice(2, 4), 16),
|
|
27
|
+
b: parseInt(hex.slice(4, 6), 16),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const match = normalized.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)/i);
|
|
31
|
+
if (!match)
|
|
32
|
+
return null;
|
|
33
|
+
return {
|
|
34
|
+
r: Number(match[1]),
|
|
35
|
+
g: Number(match[2]),
|
|
36
|
+
b: Number(match[3]),
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
const clamp = (value) => Math.max(0, Math.min(255, Math.round(value)));
|
|
40
|
+
const mixWithWhite = (color, ratio) => ({
|
|
41
|
+
r: clamp(color.r + (255 - color.r) * ratio),
|
|
42
|
+
g: clamp(color.g + (255 - color.g) * ratio),
|
|
43
|
+
b: clamp(color.b + (255 - color.b) * ratio),
|
|
44
|
+
});
|
|
45
|
+
const mixWithBlack = (color, ratio) => ({
|
|
46
|
+
r: clamp(color.r * (1 - ratio)),
|
|
47
|
+
g: clamp(color.g * (1 - ratio)),
|
|
48
|
+
b: clamp(color.b * (1 - ratio)),
|
|
49
|
+
});
|
|
50
|
+
const rgbToHsl = ({ r, g, b }) => {
|
|
51
|
+
const rn = r / 255;
|
|
52
|
+
const gn = g / 255;
|
|
53
|
+
const bn = b / 255;
|
|
54
|
+
const max = Math.max(rn, gn, bn);
|
|
55
|
+
const min = Math.min(rn, gn, bn);
|
|
56
|
+
const l = (max + min) / 2;
|
|
57
|
+
const d = max - min;
|
|
58
|
+
let h = 0;
|
|
59
|
+
let s = 0;
|
|
60
|
+
if (d !== 0) {
|
|
61
|
+
s = d / (1 - Math.abs(2 * l - 1));
|
|
62
|
+
switch (max) {
|
|
63
|
+
case rn:
|
|
64
|
+
h = ((gn - bn) / d) % 6;
|
|
65
|
+
break;
|
|
66
|
+
case gn:
|
|
67
|
+
h = (bn - rn) / d + 2;
|
|
68
|
+
break;
|
|
69
|
+
default:
|
|
70
|
+
h = (rn - gn) / d + 4;
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
h *= 60;
|
|
74
|
+
if (h < 0)
|
|
75
|
+
h += 360;
|
|
76
|
+
}
|
|
77
|
+
return { h, s, l };
|
|
78
|
+
};
|
|
79
|
+
const hslToRgb = ({ h, s, l }) => {
|
|
80
|
+
const c = (1 - Math.abs(2 * l - 1)) * s;
|
|
81
|
+
const hp = h / 60;
|
|
82
|
+
const x = c * (1 - Math.abs((hp % 2) - 1));
|
|
83
|
+
let r1 = 0;
|
|
84
|
+
let g1 = 0;
|
|
85
|
+
let b1 = 0;
|
|
86
|
+
if (hp >= 0 && hp < 1)
|
|
87
|
+
[r1, g1, b1] = [c, x, 0];
|
|
88
|
+
else if (hp < 2)
|
|
89
|
+
[r1, g1, b1] = [x, c, 0];
|
|
90
|
+
else if (hp < 3)
|
|
91
|
+
[r1, g1, b1] = [0, c, x];
|
|
92
|
+
else if (hp < 4)
|
|
93
|
+
[r1, g1, b1] = [0, x, c];
|
|
94
|
+
else if (hp < 5)
|
|
95
|
+
[r1, g1, b1] = [x, 0, c];
|
|
96
|
+
else
|
|
97
|
+
[r1, g1, b1] = [c, 0, x];
|
|
98
|
+
const m = l - c / 2;
|
|
99
|
+
return {
|
|
100
|
+
r: clamp((r1 + m) * 255),
|
|
101
|
+
g: clamp((g1 + m) * 255),
|
|
102
|
+
b: clamp((b1 + m) * 255),
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
const boostColorfulness = (color, factor = 1.22, minSaturation = 0.2) => {
|
|
106
|
+
const { h, s, l } = rgbToHsl(color);
|
|
107
|
+
const nextS = Math.max(minSaturation, Math.min(1, s * factor));
|
|
108
|
+
return hslToRgb({ h, s: nextS, l });
|
|
109
|
+
};
|
|
110
|
+
const rgbString = (color) => `rgb(${color.r}, ${color.g}, ${color.b})`;
|
|
111
|
+
const detectThemeByColor = (color) => {
|
|
112
|
+
const rgb = color ? toRgb(color) : null;
|
|
113
|
+
if (!rgb)
|
|
114
|
+
return null;
|
|
115
|
+
// Relative luminance threshold for light/dark theme split.
|
|
116
|
+
const luminance = (0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b) / 255;
|
|
117
|
+
return luminance >= 0.55 ? 'light' : 'dark';
|
|
118
|
+
};
|
|
119
|
+
const applyBackgroundPalette = (bgColor, setVar) => {
|
|
120
|
+
const color = (bgColor || '').trim();
|
|
121
|
+
const keys = [
|
|
122
|
+
'--color-bg-primary',
|
|
123
|
+
'--color-bg-secondary',
|
|
124
|
+
'--color-bg-thirdly',
|
|
125
|
+
'--color-bg-quaternary',
|
|
126
|
+
'--color-bg-accent-light',
|
|
127
|
+
'--color-bg-special-super-light',
|
|
128
|
+
'--color-bg-secondary-inverted',
|
|
129
|
+
'--color-bg-special-big-objects',
|
|
130
|
+
'--color-border-primary',
|
|
131
|
+
'--color-border-secondary',
|
|
132
|
+
'--color-border-thirdly',
|
|
133
|
+
'--color-graphic-secondary',
|
|
134
|
+
'--color-graphic-thirdly',
|
|
135
|
+
];
|
|
136
|
+
if (!color) {
|
|
137
|
+
keys.forEach((key) => {
|
|
138
|
+
setVar(key, null);
|
|
139
|
+
});
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const bgBase = toRgb(color);
|
|
143
|
+
if (!bgBase)
|
|
144
|
+
return;
|
|
145
|
+
const isLightBase = detectThemeByColor(color) === 'light';
|
|
146
|
+
const shift = (ratio) => isLightBase ? mixWithBlack(bgBase, ratio) : mixWithWhite(bgBase, ratio);
|
|
147
|
+
const shiftBg = (ratio) => isLightBase ? mixWithBlack(bgBase, ratio) : mixWithWhite(bgBase, ratio);
|
|
148
|
+
// Background colors: adaptive direction (lighter when visible, otherwise darker).
|
|
149
|
+
const adjustBg = (ratio) => {
|
|
150
|
+
const next = shiftBg(ratio);
|
|
151
|
+
return isLightBase ? boostColorfulness(next) : next;
|
|
152
|
+
};
|
|
153
|
+
const secondary = adjustBg(0.0581);
|
|
154
|
+
const thirdly = adjustBg(0.1037);
|
|
155
|
+
const quaternary = adjustBg(0.1659);
|
|
156
|
+
const accentLight = adjustBg(0.315);
|
|
157
|
+
const specialBigObjects = adjustBg(0.0373);
|
|
158
|
+
const superLight = adjustBg(0.0415);
|
|
159
|
+
const secondaryInverted = adjustBg(0.95);
|
|
160
|
+
// Borders/icons: keep existing direction logic.
|
|
161
|
+
const borderPrimary = shift(0.1659);
|
|
162
|
+
const borderSecondary = shift(0.1037);
|
|
163
|
+
const borderThirdly = shift(0.0581);
|
|
164
|
+
const graphicThirdly = shift(0.1992);
|
|
165
|
+
const graphicSecondary = shift(0.4938);
|
|
166
|
+
setVar('--color-bg-primary', rgbString(bgBase));
|
|
167
|
+
setVar('--color-bg-secondary', rgbString(secondary));
|
|
168
|
+
setVar('--color-bg-thirdly', rgbString(thirdly));
|
|
169
|
+
setVar('--color-bg-quaternary', rgbString(quaternary));
|
|
170
|
+
setVar('--color-bg-accent-light', rgbString(accentLight));
|
|
171
|
+
setVar('--color-bg-special-big-objects', rgbString(specialBigObjects));
|
|
172
|
+
setVar('--color-bg-special-super-light', `rgba(${superLight.r}, ${superLight.g}, ${superLight.b}, 0.44)`);
|
|
173
|
+
setVar('--color-bg-secondary-inverted', rgbString(secondaryInverted));
|
|
174
|
+
setVar('--color-border-primary', rgbString(borderPrimary));
|
|
175
|
+
setVar('--color-border-secondary', rgbString(borderSecondary));
|
|
176
|
+
setVar('--color-border-thirdly', rgbString(borderThirdly));
|
|
177
|
+
setVar('--color-graphic-thirdly', rgbString(graphicThirdly));
|
|
178
|
+
setVar('--color-graphic-secondary', rgbString(graphicSecondary));
|
|
179
|
+
};
|
|
180
|
+
exports.applyBackgroundPalette = applyBackgroundPalette;
|
|
@@ -38,6 +38,7 @@ const getCurrency = (currency) => {
|
|
|
38
38
|
[profile_currency_1.ProfileCurrencyEnum.MXN]: '$',
|
|
39
39
|
[profile_currency_1.ProfileCurrencyEnum.JPY]: '¥',
|
|
40
40
|
[profile_currency_1.ProfileCurrencyEnum.SGD]: '$',
|
|
41
|
+
[profile_currency_1.ProfileCurrencyEnum.AED]: 'AED',
|
|
41
42
|
};
|
|
42
43
|
return res[currency] || currency;
|
|
43
44
|
};
|
|
@@ -71,20 +72,16 @@ const roundDisplayAmount = (amount) => {
|
|
|
71
72
|
exports.roundDisplayAmount = roundDisplayAmount;
|
|
72
73
|
const formatAmount = (amount, currency) => {
|
|
73
74
|
const [major, minor] = ((+amount || 0) / 100).toFixed(2).split('.');
|
|
74
|
-
const
|
|
75
|
+
const hasMinor = Number(minor) !== 0;
|
|
75
76
|
const majorFormatted = major.replace(/\B(?=(\d{3})+(?!\d))/g, THINSP);
|
|
76
77
|
const c = getCurrency(currency);
|
|
77
78
|
if (currencyFirst(currency)) {
|
|
78
|
-
if (!
|
|
79
|
+
if (!hasMinor)
|
|
79
80
|
return c + THINSP + majorFormatted;
|
|
80
|
-
|
|
81
|
-
return `${c}${THINSP}${majorFormatted},${value}0`;
|
|
82
|
-
return `${c}${THINSP}${majorFormatted},${value}`;
|
|
81
|
+
return `${c}${THINSP}${majorFormatted},${minor}`;
|
|
83
82
|
}
|
|
84
|
-
if (!
|
|
83
|
+
if (!hasMinor)
|
|
85
84
|
return majorFormatted + THINSP + c;
|
|
86
|
-
|
|
87
|
-
return `${majorFormatted},${value}0${THINSP}${c}`;
|
|
88
|
-
return `${majorFormatted},${value}${THINSP}${c}`;
|
|
85
|
+
return `${majorFormatted},${minor}${THINSP}${c}`;
|
|
89
86
|
};
|
|
90
87
|
exports.formatAmount = formatAmount;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const index_1 = require("./index");
|
|
4
|
+
describe('formatAmount', () => {
|
|
5
|
+
it.each([
|
|
6
|
+
[17603, 'EUR', '176,03 €'],
|
|
7
|
+
[17603, 'USD', '$ 176,03'],
|
|
8
|
+
[10001, 'RUB', '100,01 руб.'],
|
|
9
|
+
[10009, 'EUR', '100,09 €'],
|
|
10
|
+
[-17603, 'EUR', '-176,03 €'],
|
|
11
|
+
[3, 'EUR', '0,03 €'],
|
|
12
|
+
[17630, 'EUR', '176,30 €'],
|
|
13
|
+
[17600, 'EUR', '176 €'],
|
|
14
|
+
[0, 'USD', '$ 0'],
|
|
15
|
+
])('formats minor units without changing their value: %s %s', (amount, currency, expected) => {
|
|
16
|
+
expect((0, index_1.formatAmount)(amount, currency)).toBe(expected);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a color reads as a light surface.
|
|
3
|
+
* `null` if the color cannot be parsed.
|
|
4
|
+
*/
|
|
5
|
+
export declare const isLightSurfaceColor: (color?: string) => boolean | null;
|
|
6
|
+
/** Near-black brand color that would disappear on a dark widget surface. */
|
|
7
|
+
export declare const isMainColorTooDarkForDarkTheme: (color?: string) => boolean;
|
|
8
|
+
type ResolveWidgetThemeArgs = {
|
|
9
|
+
/** Value from `data-theme` (orders booking page hardcodes `dark`). */
|
|
10
|
+
requestedTheme?: string | null;
|
|
11
|
+
bgColor?: string;
|
|
12
|
+
mainColor?: string;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Picks widget UI theme after customization is loaded.
|
|
16
|
+
*
|
|
17
|
+
* Priority:
|
|
18
|
+
* 1. Custom `bgColor` → light/dark by luminance
|
|
19
|
+
* 2. Requested dark + near-black `mainColor` → light
|
|
20
|
+
* (brand accents would vanish — typical for orders `/booking/:id`
|
|
21
|
+
* which always asks for dark)
|
|
22
|
+
* 3. Otherwise honor `data-theme` (default dark)
|
|
23
|
+
*/
|
|
24
|
+
export declare const resolveWidgetTheme: ({ requestedTheme, bgColor, mainColor, }: ResolveWidgetThemeArgs) => "light" | "dark";
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveWidgetTheme = exports.isMainColorTooDarkForDarkTheme = exports.isLightSurfaceColor = void 0;
|
|
4
|
+
/** Same threshold as background theme detection in WidgetProvider. */
|
|
5
|
+
const LIGHT_SURFACE_LUMINANCE = 0.55;
|
|
6
|
+
/**
|
|
7
|
+
* Brand accents below this luminance vanish on a dark surface
|
|
8
|
+
* (near-black mainColor + data-theme=dark). Kept well below typical
|
|
9
|
+
* brand blues (~0.5) so `#4A90E2` still keeps dark theme.
|
|
10
|
+
*/
|
|
11
|
+
const MAIN_COLOR_TOO_DARK_FOR_DARK_THEME = 0.28;
|
|
12
|
+
const parseCssColor = (color) => {
|
|
13
|
+
if (!color)
|
|
14
|
+
return null;
|
|
15
|
+
const trimmed = color.trim();
|
|
16
|
+
if (trimmed.startsWith('#')) {
|
|
17
|
+
let hex = trimmed.slice(1);
|
|
18
|
+
// #RGB / #RGBA → #RRGGBB(AA)
|
|
19
|
+
if (hex.length === 3 || hex.length === 4) {
|
|
20
|
+
hex = hex
|
|
21
|
+
.split('')
|
|
22
|
+
.map((c) => c + c)
|
|
23
|
+
.join('');
|
|
24
|
+
}
|
|
25
|
+
// Accept #RRGGBB and #RRGGBBAA (API stores mainColor with alpha).
|
|
26
|
+
if ((hex.length !== 6 && hex.length !== 8) || !/^[0-9a-fA-F]+$/.test(hex)) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
r: parseInt(hex.slice(0, 2), 16),
|
|
31
|
+
g: parseInt(hex.slice(2, 4), 16),
|
|
32
|
+
b: parseInt(hex.slice(4, 6), 16),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const match = trimmed.match(/^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/i);
|
|
36
|
+
if (!match)
|
|
37
|
+
return null;
|
|
38
|
+
return {
|
|
39
|
+
r: Number(match[1]),
|
|
40
|
+
g: Number(match[2]),
|
|
41
|
+
b: Number(match[3]),
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
const relativeLuminance = ({ r, g, b }) => (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
45
|
+
/**
|
|
46
|
+
* Whether a color reads as a light surface.
|
|
47
|
+
* `null` if the color cannot be parsed.
|
|
48
|
+
*/
|
|
49
|
+
const isLightSurfaceColor = (color) => {
|
|
50
|
+
const rgb = parseCssColor(color);
|
|
51
|
+
if (!rgb)
|
|
52
|
+
return null;
|
|
53
|
+
return relativeLuminance(rgb) >= LIGHT_SURFACE_LUMINANCE;
|
|
54
|
+
};
|
|
55
|
+
exports.isLightSurfaceColor = isLightSurfaceColor;
|
|
56
|
+
/** Near-black brand color that would disappear on a dark widget surface. */
|
|
57
|
+
const isMainColorTooDarkForDarkTheme = (color) => {
|
|
58
|
+
const rgb = parseCssColor(color);
|
|
59
|
+
if (!rgb)
|
|
60
|
+
return false;
|
|
61
|
+
return relativeLuminance(rgb) < MAIN_COLOR_TOO_DARK_FOR_DARK_THEME;
|
|
62
|
+
};
|
|
63
|
+
exports.isMainColorTooDarkForDarkTheme = isMainColorTooDarkForDarkTheme;
|
|
64
|
+
/**
|
|
65
|
+
* Picks widget UI theme after customization is loaded.
|
|
66
|
+
*
|
|
67
|
+
* Priority:
|
|
68
|
+
* 1. Custom `bgColor` → light/dark by luminance
|
|
69
|
+
* 2. Requested dark + near-black `mainColor` → light
|
|
70
|
+
* (brand accents would vanish — typical for orders `/booking/:id`
|
|
71
|
+
* which always asks for dark)
|
|
72
|
+
* 3. Otherwise honor `data-theme` (default dark)
|
|
73
|
+
*/
|
|
74
|
+
const resolveWidgetTheme = ({ requestedTheme, bgColor, mainColor, }) => {
|
|
75
|
+
const fromBg = (0, exports.isLightSurfaceColor)(bgColor);
|
|
76
|
+
if (fromBg !== null) {
|
|
77
|
+
return fromBg ? 'light' : 'dark';
|
|
78
|
+
}
|
|
79
|
+
const requested = requestedTheme === 'light' ? 'light' : 'dark';
|
|
80
|
+
if (requested === 'dark' && (0, exports.isMainColorTooDarkForDarkTheme)(mainColor)) {
|
|
81
|
+
return 'light';
|
|
82
|
+
}
|
|
83
|
+
return requested;
|
|
84
|
+
};
|
|
85
|
+
exports.resolveWidgetTheme = resolveWidgetTheme;
|
package/dist/serialize-slot.js
CHANGED
|
@@ -38,6 +38,8 @@ const serializeSlotOrderData = (order) => order && {
|
|
|
38
38
|
certificateTotal: order.certificateTotal,
|
|
39
39
|
upsellingsTotal: order.upsellingsTotal,
|
|
40
40
|
upsellingIcons: order.upsellingIcons ?? [],
|
|
41
|
+
mode: order.mode,
|
|
42
|
+
modeIcon: order.modeIcon ?? null,
|
|
41
43
|
emailTrouble: order.emailTrouble,
|
|
42
44
|
noShow: order.noShow,
|
|
43
45
|
// Иконка «есть комментарии»: денорм-флаг заметок сотрудников ИЛИ
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@escapenavigator/utils",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.8",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"test": "jest"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@escapenavigator/types": "^2.2.
|
|
17
|
+
"@escapenavigator/types": "^2.2.8",
|
|
18
18
|
"axios": "^0.21.4",
|
|
19
19
|
"class-transformer": "^0.5.1",
|
|
20
20
|
"class-validator": "^0.13.2",
|
|
@@ -29,5 +29,5 @@
|
|
|
29
29
|
"ts-jest": "^29.1.1",
|
|
30
30
|
"typescript": "^5.6"
|
|
31
31
|
},
|
|
32
|
-
"gitHead": "
|
|
32
|
+
"gitHead": "30230db507f7483ba425b0811f31d980d3b0a684"
|
|
33
33
|
}
|