@jsenv/dom 0.1.0 → 0.4.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/jsenv_dom.js +4096 -1944
- package/index.js +12 -0
- package/package.json +1 -1
- package/src/color/color_constrast.js +69 -0
- package/src/color/color_parsing.js +319 -0
- package/src/color/color_scheme.js +28 -0
- package/src/color/pick_light_or_dark.js +34 -0
- package/src/color/resolve_css_color.js +60 -0
- package/src/position/visible_rect.js +6 -2
- package/src/pub_sub.js +4 -1
- package/src/style/style_default.js +153 -0
- package/src/style/style_default_demo.html +128 -0
- package/src/style/style_parsing.js +3 -0
package/index.js
CHANGED
|
@@ -5,11 +5,23 @@ export { createPubSub } from "./src/pub_sub.js";
|
|
|
5
5
|
// style
|
|
6
6
|
export { addWillChange, getStyle, setStyles } from "./src/style/dom_styles.js";
|
|
7
7
|
export { createStyleController } from "./src/style/style_controller.js";
|
|
8
|
+
export { getDefaultStyles } from "./src/style/style_default.js";
|
|
8
9
|
|
|
9
10
|
// attributes
|
|
10
11
|
export { addAttributeEffect } from "./src/attr/add_attribute_effect.js";
|
|
11
12
|
export { setAttribute, setAttributes } from "./src/attr/attributes.js";
|
|
12
13
|
|
|
14
|
+
// colors
|
|
15
|
+
export { getContrastRatio } from "./src/color/color_constrast.js";
|
|
16
|
+
export { parseCSSColor, stringifyCSSColor } from "./src/color/color_parsing.js";
|
|
17
|
+
export {
|
|
18
|
+
getPreferedColorScheme,
|
|
19
|
+
prefersDarkColors,
|
|
20
|
+
prefersLightColors,
|
|
21
|
+
} from "./src/color/color_scheme.js";
|
|
22
|
+
export { pickLightOrDark } from "./src/color/pick_light_or_dark.js";
|
|
23
|
+
export { resolveCSSColor } from "./src/color/resolve_css_color.js";
|
|
24
|
+
|
|
13
25
|
// traversal
|
|
14
26
|
export {
|
|
15
27
|
findAfter,
|
package/package.json
CHANGED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculates the contrast ratio between two RGBA colors
|
|
3
|
+
* Based on WCAG 2.1 specification
|
|
4
|
+
* @param {Array<number>} rgba1 - [r, g, b, a] values for first color
|
|
5
|
+
* @param {Array<number>} rgba2 - [r, g, b, a] values for second color
|
|
6
|
+
* @param {Array<number>} [background=[255, 255, 255, 1]] - Background color to composite against when colors have transparency
|
|
7
|
+
* @returns {number} Contrast ratio (1-21)
|
|
8
|
+
*/
|
|
9
|
+
export const getContrastRatio = (
|
|
10
|
+
rgba1,
|
|
11
|
+
rgba2,
|
|
12
|
+
background = [255, 255, 255, 1],
|
|
13
|
+
) => {
|
|
14
|
+
// When colors have transparency (alpha < 1), we need to composite them
|
|
15
|
+
// against a background to get their effective appearance
|
|
16
|
+
const composited1 = compositeColor(rgba1, background);
|
|
17
|
+
const composited2 = compositeColor(rgba2, background);
|
|
18
|
+
|
|
19
|
+
const lum1 = getLuminance(composited1[0], composited1[1], composited1[2]);
|
|
20
|
+
const lum2 = getLuminance(composited2[0], composited2[1], composited2[2]);
|
|
21
|
+
const brightest = Math.max(lum1, lum2);
|
|
22
|
+
const darkest = Math.min(lum1, lum2);
|
|
23
|
+
return (brightest + 0.05) / (darkest + 0.05);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Composites a color with alpha over a background color
|
|
28
|
+
* @param {Array<number>} foreground - [r, g, b, a] foreground color
|
|
29
|
+
* @param {Array<number>} background - [r, g, b, a] background color
|
|
30
|
+
* @returns {Array<number>} [r, g, b] composited color (alpha is flattened)
|
|
31
|
+
*/
|
|
32
|
+
const compositeColor = (foreground, background) => {
|
|
33
|
+
const [fr, fg, fb, fa] = foreground;
|
|
34
|
+
const [br, bg, bb, ba] = background;
|
|
35
|
+
|
|
36
|
+
// No transparency: return the foreground color as-is
|
|
37
|
+
if (fa === 1) {
|
|
38
|
+
return [fr, fg, fb];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Alpha compositing formula: C = αA * CA + αB * (1 - αA) * CB
|
|
42
|
+
const alpha = fa + ba * (1 - fa);
|
|
43
|
+
|
|
44
|
+
if (alpha === 0) {
|
|
45
|
+
return [0, 0, 0];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const r = (fa * fr + ba * (1 - fa) * br) / alpha;
|
|
49
|
+
const g = (fa * fg + ba * (1 - fa) * bg) / alpha;
|
|
50
|
+
const b = (fa * fb + ba * (1 - fa) * bb) / alpha;
|
|
51
|
+
|
|
52
|
+
return [Math.round(r), Math.round(g), Math.round(b)];
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Calculates the relative luminance of an RGB color
|
|
57
|
+
* Based on WCAG 2.1 specification
|
|
58
|
+
* @param {number} r - Red component (0-255)
|
|
59
|
+
* @param {number} g - Green component (0-255)
|
|
60
|
+
* @param {number} b - Blue component (0-255)
|
|
61
|
+
* @returns {number} Relative luminance (0-1)
|
|
62
|
+
*/
|
|
63
|
+
const getLuminance = (r, g, b) => {
|
|
64
|
+
const [rs, gs, bs] = [r, g, b].map((c) => {
|
|
65
|
+
c = c / 255;
|
|
66
|
+
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
67
|
+
});
|
|
68
|
+
return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
|
|
69
|
+
};
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses a CSS color string into RGBA values
|
|
3
|
+
* Supports hex (#rgb, #rrggbb, #rrggbbaa), rgb(), rgba(), hsl(), hsla()
|
|
4
|
+
* @param {string} color - CSS color string
|
|
5
|
+
* @returns {Array<number>|null} [r, g, b, a] values or null if parsing fails
|
|
6
|
+
*/
|
|
7
|
+
export const parseCSSColor = (color) => {
|
|
8
|
+
if (!color || typeof color !== "string") {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
color = color.trim().toLowerCase();
|
|
13
|
+
|
|
14
|
+
// Hex colors
|
|
15
|
+
if (color.startsWith("#")) {
|
|
16
|
+
const hex = color.slice(1);
|
|
17
|
+
if (hex.length === 3) {
|
|
18
|
+
// #rgb -> #rrggbb
|
|
19
|
+
const r = parseInt(hex[0] + hex[0], 16);
|
|
20
|
+
const g = parseInt(hex[1] + hex[1], 16);
|
|
21
|
+
const b = parseInt(hex[2] + hex[2], 16);
|
|
22
|
+
return [r, g, b, 1];
|
|
23
|
+
}
|
|
24
|
+
if (hex.length === 6) {
|
|
25
|
+
// #rrggbb
|
|
26
|
+
const r = parseInt(hex.slice(0, 2), 16);
|
|
27
|
+
const g = parseInt(hex.slice(2, 4), 16);
|
|
28
|
+
const b = parseInt(hex.slice(4, 6), 16);
|
|
29
|
+
return [r, g, b, 1];
|
|
30
|
+
}
|
|
31
|
+
if (hex.length === 8) {
|
|
32
|
+
// #rrggbbaa
|
|
33
|
+
const r = parseInt(hex.slice(0, 2), 16);
|
|
34
|
+
const g = parseInt(hex.slice(2, 4), 16);
|
|
35
|
+
const b = parseInt(hex.slice(4, 6), 16);
|
|
36
|
+
const a = parseInt(hex.slice(6, 8), 16) / 255;
|
|
37
|
+
return [r, g, b, a];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// RGB/RGBA colors
|
|
42
|
+
const rgbMatch = color.match(/rgba?\(([^)]+)\)/);
|
|
43
|
+
if (rgbMatch) {
|
|
44
|
+
const values = rgbMatch[1].split(",").map((v) => parseFloat(v.trim()));
|
|
45
|
+
if (values.length >= 3) {
|
|
46
|
+
const r = values[0];
|
|
47
|
+
const g = values[1];
|
|
48
|
+
const b = values[2];
|
|
49
|
+
const a = values.length >= 4 ? values[3] : 1;
|
|
50
|
+
return [r, g, b, a];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// HSL/HSLA colors - convert to RGB
|
|
55
|
+
const hslMatch = color.match(/hsla?\(([^)]+)\)/);
|
|
56
|
+
if (hslMatch) {
|
|
57
|
+
const values = hslMatch[1].split(",").map((v) => parseFloat(v.trim()));
|
|
58
|
+
if (values.length >= 3) {
|
|
59
|
+
const [h, s, l] = values;
|
|
60
|
+
const a = values.length >= 4 ? values[3] : 1;
|
|
61
|
+
const [r, g, b] = hslToRgb(h, s / 100, l / 100);
|
|
62
|
+
return [r, g, b, a];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Named colors (basic set)
|
|
67
|
+
if (namedColors[color]) {
|
|
68
|
+
return [...namedColors[color], 1];
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Converts RGBA values back to a CSS color string
|
|
75
|
+
* Prefers named colors when possible, then rgb() for opaque colors, rgba() for transparent
|
|
76
|
+
* @param {Array<number>} rgba - [r, g, b, a] values
|
|
77
|
+
* @returns {string|null} CSS color string or null if invalid input
|
|
78
|
+
*/
|
|
79
|
+
export const stringifyCSSColor = (rgba) => {
|
|
80
|
+
if (!Array.isArray(rgba) || rgba.length < 3) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const [r, g, b, a = 1] = rgba;
|
|
85
|
+
|
|
86
|
+
// Validate RGB values
|
|
87
|
+
if (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Validate alpha value
|
|
92
|
+
if (a < 0 || a > 1) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Round RGB values to integers
|
|
97
|
+
const rInt = Math.round(r);
|
|
98
|
+
const gInt = Math.round(g);
|
|
99
|
+
const bInt = Math.round(b);
|
|
100
|
+
|
|
101
|
+
// Check for named colors (only for fully opaque colors)
|
|
102
|
+
if (a === 1) {
|
|
103
|
+
for (const [name, [nameR, nameG, nameB]] of Object.entries(namedColors)) {
|
|
104
|
+
if (rInt === nameR && gInt === nameG && bInt === nameB) {
|
|
105
|
+
return name;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Use rgb() for opaque colors, rgba() for transparent
|
|
111
|
+
if (a === 1) {
|
|
112
|
+
return `rgb(${rInt}, ${gInt}, ${bInt})`;
|
|
113
|
+
}
|
|
114
|
+
return `rgba(${rInt}, ${gInt}, ${bInt}, ${a})`;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const namedColors = {
|
|
118
|
+
// Basic colors
|
|
119
|
+
black: [0, 0, 0],
|
|
120
|
+
white: [255, 255, 255],
|
|
121
|
+
red: [255, 0, 0],
|
|
122
|
+
green: [0, 128, 0],
|
|
123
|
+
blue: [0, 0, 255],
|
|
124
|
+
yellow: [255, 255, 0],
|
|
125
|
+
cyan: [0, 255, 255],
|
|
126
|
+
magenta: [255, 0, 255],
|
|
127
|
+
|
|
128
|
+
// Gray variations
|
|
129
|
+
silver: [192, 192, 192],
|
|
130
|
+
gray: [128, 128, 128],
|
|
131
|
+
grey: [128, 128, 128],
|
|
132
|
+
darkgray: [169, 169, 169],
|
|
133
|
+
darkgrey: [169, 169, 169],
|
|
134
|
+
lightgray: [211, 211, 211],
|
|
135
|
+
lightgrey: [211, 211, 211],
|
|
136
|
+
dimgray: [105, 105, 105],
|
|
137
|
+
dimgrey: [105, 105, 105],
|
|
138
|
+
gainsboro: [220, 220, 220],
|
|
139
|
+
whitesmoke: [245, 245, 245],
|
|
140
|
+
|
|
141
|
+
// Extended basic colors
|
|
142
|
+
maroon: [128, 0, 0],
|
|
143
|
+
olive: [128, 128, 0],
|
|
144
|
+
lime: [0, 255, 0],
|
|
145
|
+
aqua: [0, 255, 255],
|
|
146
|
+
teal: [0, 128, 128],
|
|
147
|
+
navy: [0, 0, 128],
|
|
148
|
+
fuchsia: [255, 0, 255],
|
|
149
|
+
purple: [128, 0, 128],
|
|
150
|
+
|
|
151
|
+
// Red variations
|
|
152
|
+
darkred: [139, 0, 0],
|
|
153
|
+
firebrick: [178, 34, 34],
|
|
154
|
+
crimson: [220, 20, 60],
|
|
155
|
+
indianred: [205, 92, 92],
|
|
156
|
+
lightcoral: [240, 128, 128],
|
|
157
|
+
salmon: [250, 128, 114],
|
|
158
|
+
darksalmon: [233, 150, 122],
|
|
159
|
+
lightsalmon: [255, 160, 122],
|
|
160
|
+
|
|
161
|
+
// Pink variations
|
|
162
|
+
pink: [255, 192, 203],
|
|
163
|
+
lightpink: [255, 182, 193],
|
|
164
|
+
hotpink: [255, 105, 180],
|
|
165
|
+
deeppink: [255, 20, 147],
|
|
166
|
+
mediumvioletred: [199, 21, 133],
|
|
167
|
+
palevioletred: [219, 112, 147],
|
|
168
|
+
|
|
169
|
+
// Orange variations
|
|
170
|
+
orange: [255, 165, 0],
|
|
171
|
+
darkorange: [255, 140, 0],
|
|
172
|
+
orangered: [255, 69, 0],
|
|
173
|
+
tomato: [255, 99, 71],
|
|
174
|
+
coral: [255, 127, 80],
|
|
175
|
+
|
|
176
|
+
// Yellow variations
|
|
177
|
+
gold: [255, 215, 0],
|
|
178
|
+
lightyellow: [255, 255, 224],
|
|
179
|
+
lemonchiffon: [255, 250, 205],
|
|
180
|
+
lightgoldenrodyellow: [250, 250, 210],
|
|
181
|
+
papayawhip: [255, 239, 213],
|
|
182
|
+
moccasin: [255, 228, 181],
|
|
183
|
+
peachpuff: [255, 218, 185],
|
|
184
|
+
palegoldenrod: [238, 232, 170],
|
|
185
|
+
khaki: [240, 230, 140],
|
|
186
|
+
darkkhaki: [189, 183, 107],
|
|
187
|
+
|
|
188
|
+
// Green variations
|
|
189
|
+
darkgreen: [0, 100, 0],
|
|
190
|
+
forestgreen: [34, 139, 34],
|
|
191
|
+
seagreen: [46, 139, 87],
|
|
192
|
+
mediumseagreen: [60, 179, 113],
|
|
193
|
+
springgreen: [0, 255, 127],
|
|
194
|
+
mediumspringgreen: [0, 250, 154],
|
|
195
|
+
lawngreen: [124, 252, 0],
|
|
196
|
+
chartreuse: [127, 255, 0],
|
|
197
|
+
greenyellow: [173, 255, 47],
|
|
198
|
+
limegreen: [50, 205, 50],
|
|
199
|
+
palegreen: [152, 251, 152],
|
|
200
|
+
lightgreen: [144, 238, 144],
|
|
201
|
+
mediumaquamarine: [102, 205, 170],
|
|
202
|
+
aquamarine: [127, 255, 212],
|
|
203
|
+
darkolivegreen: [85, 107, 47],
|
|
204
|
+
olivedrab: [107, 142, 35],
|
|
205
|
+
yellowgreen: [154, 205, 50],
|
|
206
|
+
|
|
207
|
+
// Blue variations
|
|
208
|
+
darkblue: [0, 0, 139],
|
|
209
|
+
mediumblue: [0, 0, 205],
|
|
210
|
+
royalblue: [65, 105, 225],
|
|
211
|
+
steelblue: [70, 130, 180],
|
|
212
|
+
dodgerblue: [30, 144, 255],
|
|
213
|
+
deepskyblue: [0, 191, 255],
|
|
214
|
+
skyblue: [135, 206, 235],
|
|
215
|
+
lightskyblue: [135, 206, 250],
|
|
216
|
+
lightblue: [173, 216, 230],
|
|
217
|
+
powderblue: [176, 224, 230],
|
|
218
|
+
lightcyan: [224, 255, 255],
|
|
219
|
+
paleturquoise: [175, 238, 238],
|
|
220
|
+
darkturquoise: [0, 206, 209],
|
|
221
|
+
mediumturquoise: [72, 209, 204],
|
|
222
|
+
turquoise: [64, 224, 208],
|
|
223
|
+
cadetblue: [95, 158, 160],
|
|
224
|
+
darkcyan: [0, 139, 139],
|
|
225
|
+
lightseagreen: [32, 178, 170],
|
|
226
|
+
|
|
227
|
+
// Purple variations
|
|
228
|
+
indigo: [75, 0, 130],
|
|
229
|
+
darkviolet: [148, 0, 211],
|
|
230
|
+
blueviolet: [138, 43, 226],
|
|
231
|
+
mediumpurple: [147, 112, 219],
|
|
232
|
+
mediumslateblue: [123, 104, 238],
|
|
233
|
+
slateblue: [106, 90, 205],
|
|
234
|
+
darkslateblue: [72, 61, 139],
|
|
235
|
+
lavender: [230, 230, 250],
|
|
236
|
+
thistle: [216, 191, 216],
|
|
237
|
+
plum: [221, 160, 221],
|
|
238
|
+
violet: [238, 130, 238],
|
|
239
|
+
orchid: [218, 112, 214],
|
|
240
|
+
mediumorchid: [186, 85, 211],
|
|
241
|
+
darkorchid: [153, 50, 204],
|
|
242
|
+
darkmagenta: [139, 0, 139],
|
|
243
|
+
|
|
244
|
+
// Brown variations
|
|
245
|
+
brown: [165, 42, 42],
|
|
246
|
+
saddlebrown: [139, 69, 19],
|
|
247
|
+
sienna: [160, 82, 45],
|
|
248
|
+
chocolate: [210, 105, 30],
|
|
249
|
+
darkgoldenrod: [184, 134, 11],
|
|
250
|
+
peru: [205, 133, 63],
|
|
251
|
+
rosybrown: [188, 143, 143],
|
|
252
|
+
goldenrod: [218, 165, 32],
|
|
253
|
+
sandybrown: [244, 164, 96],
|
|
254
|
+
tan: [210, 180, 140],
|
|
255
|
+
burlywood: [222, 184, 135],
|
|
256
|
+
wheat: [245, 222, 179],
|
|
257
|
+
navajowhite: [255, 222, 173],
|
|
258
|
+
bisque: [255, 228, 196],
|
|
259
|
+
blanchedalmond: [255, 235, 205],
|
|
260
|
+
cornsilk: [255, 248, 220],
|
|
261
|
+
|
|
262
|
+
// Special colors
|
|
263
|
+
transparent: [0, 0, 0], // Note: alpha will be 0 for transparent
|
|
264
|
+
aliceblue: [240, 248, 255],
|
|
265
|
+
antiquewhite: [250, 235, 215],
|
|
266
|
+
azure: [240, 255, 255],
|
|
267
|
+
beige: [245, 245, 220],
|
|
268
|
+
honeydew: [240, 255, 240],
|
|
269
|
+
ivory: [255, 255, 240],
|
|
270
|
+
lavenderblush: [255, 240, 245],
|
|
271
|
+
linen: [250, 240, 230],
|
|
272
|
+
mintcream: [245, 255, 250],
|
|
273
|
+
mistyrose: [255, 228, 225],
|
|
274
|
+
oldlace: [253, 245, 230],
|
|
275
|
+
seashell: [255, 245, 238],
|
|
276
|
+
snow: [255, 250, 250],
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Converts HSL color to RGB
|
|
281
|
+
* @param {number} h - Hue (0-360)
|
|
282
|
+
* @param {number} s - Saturation (0-1)
|
|
283
|
+
* @param {number} l - Lightness (0-1)
|
|
284
|
+
* @returns {Array<number>} [r, g, b] values
|
|
285
|
+
*/
|
|
286
|
+
const hslToRgb = (h, s, l) => {
|
|
287
|
+
h = h % 360;
|
|
288
|
+
const c = (1 - Math.abs(2 * l - 1)) * s;
|
|
289
|
+
const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
|
|
290
|
+
const m = l - c / 2;
|
|
291
|
+
const createRgb = (r, g, b) => {
|
|
292
|
+
return [
|
|
293
|
+
Math.round((r + m) * 255),
|
|
294
|
+
Math.round((g + m) * 255),
|
|
295
|
+
Math.round((b + m) * 255),
|
|
296
|
+
];
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
if (h >= 0 && h < 60) {
|
|
300
|
+
return createRgb(c, x, 0);
|
|
301
|
+
}
|
|
302
|
+
if (h >= 60 && h < 120) {
|
|
303
|
+
return createRgb(x, c, 0);
|
|
304
|
+
}
|
|
305
|
+
if (h >= 120 && h < 180) {
|
|
306
|
+
return createRgb(0, c, x);
|
|
307
|
+
}
|
|
308
|
+
if (h >= 180 && h < 240) {
|
|
309
|
+
return createRgb(0, x, c);
|
|
310
|
+
}
|
|
311
|
+
if (h >= 240 && h < 300) {
|
|
312
|
+
return createRgb(x, 0, c);
|
|
313
|
+
}
|
|
314
|
+
if (h >= 300 && h < 360) {
|
|
315
|
+
return createRgb(c, 0, x);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return createRgb(0, 0, 0);
|
|
319
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Determines if the current color scheme is dark mode
|
|
3
|
+
* @param {Element} [element] - DOM element to check color-scheme against (optional)
|
|
4
|
+
* @returns {boolean} True if dark mode is active
|
|
5
|
+
*/
|
|
6
|
+
export const prefersDarkColors = (element) => {
|
|
7
|
+
const colorScheme = getPreferedColorScheme(element);
|
|
8
|
+
return colorScheme.includes("dark");
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const prefersLightColors = (element) => {
|
|
12
|
+
return !prefersDarkColors(element);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const getPreferedColorScheme = (element) => {
|
|
16
|
+
const computedStyle = getComputedStyle(element || document.documentElement);
|
|
17
|
+
const colorScheme = computedStyle.colorScheme;
|
|
18
|
+
|
|
19
|
+
// If no explicit color-scheme is set, or it's "normal",
|
|
20
|
+
// fall back to prefers-color-scheme media query
|
|
21
|
+
if (!colorScheme || colorScheme === "normal") {
|
|
22
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches
|
|
23
|
+
? "dark"
|
|
24
|
+
: "light";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return colorScheme;
|
|
28
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chooses between light and dark colors based on which provides better contrast against a background
|
|
3
|
+
* @param {Element} element - DOM element to resolve CSS variables against
|
|
4
|
+
* @param {string} backgroundColor - CSS color value (hex, rgb, hsl, CSS variable, etc.)
|
|
5
|
+
* @param {string} lightColor - Light color option (typically for dark backgrounds)
|
|
6
|
+
* @param {string} darkColor - Dark color option (typically for light backgrounds)
|
|
7
|
+
* @returns {string} The color that provides better contrast (lightColor or darkColor)
|
|
8
|
+
*/
|
|
9
|
+
import { getContrastRatio } from "./color_constrast.js";
|
|
10
|
+
import { resolveCSSColor } from "./resolve_css_color.js";
|
|
11
|
+
|
|
12
|
+
export const pickLightOrDark = (
|
|
13
|
+
element,
|
|
14
|
+
backgroundColor,
|
|
15
|
+
lightColor = "white",
|
|
16
|
+
darkColor = "black",
|
|
17
|
+
) => {
|
|
18
|
+
const resolvedBgColor = resolveCSSColor(backgroundColor, element);
|
|
19
|
+
const resolvedLightColor = resolveCSSColor(lightColor, element);
|
|
20
|
+
const resolvedDarkColor = resolveCSSColor(darkColor, element);
|
|
21
|
+
|
|
22
|
+
if (!resolvedBgColor || !resolvedLightColor || !resolvedDarkColor) {
|
|
23
|
+
// Fallback to light color if parsing fails
|
|
24
|
+
return lightColor;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const contrastWithLight = getContrastRatio(
|
|
28
|
+
resolvedBgColor,
|
|
29
|
+
resolvedLightColor,
|
|
30
|
+
);
|
|
31
|
+
const contrastWithDark = getContrastRatio(resolvedBgColor, resolvedDarkColor);
|
|
32
|
+
|
|
33
|
+
return contrastWithLight > contrastWithDark ? lightColor : darkColor;
|
|
34
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { parseCSSColor, stringifyCSSColor } from "./color_parsing.js";
|
|
2
|
+
import { prefersDarkColors } from "./color_scheme.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Resolves a color value, handling CSS custom properties and light-dark() function
|
|
6
|
+
* @param {string} color - CSS color value (may include CSS variables, light-dark())
|
|
7
|
+
* @param {Element} element - DOM element to resolve CSS variables and light-dark() against
|
|
8
|
+
* @param {string} context - Return format: "js" for RGBA array, "css" for CSS string
|
|
9
|
+
* @returns {Array<number>|string|null} [r, g, b, a] values, CSS string, or null if parsing fails
|
|
10
|
+
*/
|
|
11
|
+
export const resolveCSSColor = (color, element, context = "js") => {
|
|
12
|
+
if (!color || typeof color !== "string") {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let resolvedColor = color;
|
|
17
|
+
|
|
18
|
+
// Handle light-dark() function
|
|
19
|
+
const lightDarkMatch = color.match(/light-dark\(([^,]+),([^)]+)\)/);
|
|
20
|
+
if (lightDarkMatch) {
|
|
21
|
+
const lightColor = lightDarkMatch[1].trim();
|
|
22
|
+
const darkColor = lightDarkMatch[2].trim();
|
|
23
|
+
|
|
24
|
+
// Select the appropriate color and recursively resolve it
|
|
25
|
+
const prefersDark = prefersDarkColors(element);
|
|
26
|
+
resolvedColor = prefersDark ? darkColor : lightColor;
|
|
27
|
+
return resolveCSSColor(resolvedColor, element, context);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// If it's a CSS custom property, resolve it using getComputedStyle
|
|
31
|
+
if (resolvedColor.includes("var(")) {
|
|
32
|
+
const computedStyle = getComputedStyle(element);
|
|
33
|
+
|
|
34
|
+
// Handle var() syntax
|
|
35
|
+
const varMatch = color.match(/var\(([^,)]+)(?:,([^)]+))?\)/);
|
|
36
|
+
if (varMatch) {
|
|
37
|
+
const propertyName = varMatch[1].trim();
|
|
38
|
+
const fallback = varMatch[2]?.trim();
|
|
39
|
+
|
|
40
|
+
const resolvedValue = computedStyle.getPropertyValue(propertyName).trim();
|
|
41
|
+
if (resolvedValue) {
|
|
42
|
+
// Recursively resolve in case the CSS variable contains light-dark() or other variables
|
|
43
|
+
return resolveCSSColor(resolvedValue, element, context);
|
|
44
|
+
}
|
|
45
|
+
if (fallback) {
|
|
46
|
+
// Recursively resolve fallback (in case it's also a CSS variable)
|
|
47
|
+
return resolveCSSColor(fallback, element, context);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Parse the resolved color and return in the requested format
|
|
53
|
+
const rgba = parseCSSColor(resolvedColor);
|
|
54
|
+
|
|
55
|
+
if (context === "css") {
|
|
56
|
+
return rgba ? stringifyCSSColor(rgba) : null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return rgba;
|
|
60
|
+
};
|
|
@@ -318,7 +318,11 @@ export const visibleRectEffect = (element, update) => {
|
|
|
318
318
|
export const pickPositionRelativeTo = (
|
|
319
319
|
element,
|
|
320
320
|
target,
|
|
321
|
-
{
|
|
321
|
+
{
|
|
322
|
+
alignToViewportEdgeWhenTargetNearEdge = 0,
|
|
323
|
+
minLeft = 0,
|
|
324
|
+
forcePosition,
|
|
325
|
+
} = {},
|
|
322
326
|
) => {
|
|
323
327
|
if (
|
|
324
328
|
import.meta.dev &&
|
|
@@ -392,7 +396,7 @@ export const pickPositionRelativeTo = (
|
|
|
392
396
|
const targetIsNearLeftEdge =
|
|
393
397
|
targetLeft < alignToViewportEdgeWhenTargetNearEdge;
|
|
394
398
|
if (elementIsWiderThanTarget && targetIsNearLeftEdge) {
|
|
395
|
-
elementPositionLeft =
|
|
399
|
+
elementPositionLeft = minLeft; // Left edge of viewport
|
|
396
400
|
}
|
|
397
401
|
}
|
|
398
402
|
}
|
package/src/pub_sub.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const createPubSub = () => {
|
|
1
|
+
export const createPubSub = (clearOnPublish = false) => {
|
|
2
2
|
const callbackSet = new Set();
|
|
3
3
|
|
|
4
4
|
const publish = (...args) => {
|
|
@@ -7,6 +7,9 @@ export const createPubSub = () => {
|
|
|
7
7
|
const result = callback(...args);
|
|
8
8
|
results.push(result);
|
|
9
9
|
}
|
|
10
|
+
if (clearOnPublish) {
|
|
11
|
+
callbackSet.clear();
|
|
12
|
+
}
|
|
10
13
|
return results;
|
|
11
14
|
};
|
|
12
15
|
|