browser-debugger-cli 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/.claude/skills/bdg/SKILL.md +268 -0
  2. package/README.md +15 -1
  3. package/dist/commands/dom/a11y.js +2 -1
  4. package/dist/commands/dom/formInteraction.js +56 -25
  5. package/dist/commands/dom/helpers/keyAttributes.d.ts +20 -0
  6. package/dist/commands/dom/helpers/keyAttributes.js +54 -0
  7. package/dist/commands/dom/helpers/query.d.ts +1 -1
  8. package/dist/commands/dom/helpers/query.js +66 -19
  9. package/dist/commands/dom/helpers/runElementCommand.js +4 -3
  10. package/dist/commands/dom/helpers/screenshot.js +85 -12
  11. package/dist/commands/dom/index.d.ts +1 -0
  12. package/dist/commands/dom/index.js +8 -3
  13. package/dist/commands/dom/inspect.d.ts +15 -0
  14. package/dist/commands/dom/inspect.js +82 -0
  15. package/dist/commands/dom/layout.js +2 -2
  16. package/dist/commands/dom/listeners.js +2 -2
  17. package/dist/commands/dom/semanticUtils.d.ts +14 -1
  18. package/dist/commands/dom/semanticUtils.js +44 -3
  19. package/dist/commands/installSkill.d.ts +20 -0
  20. package/dist/commands/installSkill.js +87 -0
  21. package/dist/commands/network/list.js +13 -2
  22. package/dist/commands/optionBehaviors.js +48 -6
  23. package/dist/commands/page.d.ts +1 -1
  24. package/dist/commands/page.js +62 -3
  25. package/dist/commands/shared/commonOptions.d.ts +4 -0
  26. package/dist/commands/shared/commonOptions.js +9 -0
  27. package/dist/commands/shared/optionTypes.d.ts +21 -0
  28. package/dist/commands/shared/startHelpers.d.ts +66 -0
  29. package/dist/commands/shared/startHelpers.js +91 -10
  30. package/dist/commands/shared/validation.d.ts +11 -0
  31. package/dist/commands/shared/validation.js +16 -0
  32. package/dist/commands.js +3 -0
  33. package/dist/daemon/launcher.d.ts +8 -1
  34. package/dist/daemon/launcher.js +3 -1
  35. package/dist/daemon/session/Session.d.ts +7 -0
  36. package/dist/daemon/session/Session.js +23 -1
  37. package/dist/daemon/session/commandRegistry.d.ts +14 -1
  38. package/dist/daemon/session/commandRegistry.js +65 -9
  39. package/dist/daemon/session/interactions.d.ts +18 -5
  40. package/dist/daemon/session/interactions.js +22 -12
  41. package/dist/daemon.js +3565 -329
  42. package/dist/errors/messages.d.ts +85 -0
  43. package/dist/errors/messages.js +128 -1
  44. package/dist/index.js +2151 -960
  45. package/dist/ipc/client.d.ts +9 -0
  46. package/dist/ipc/client.js +13 -0
  47. package/dist/ipc/protocol/commands.d.ts +56 -1
  48. package/dist/ipc/protocol/commands.js +2 -0
  49. package/dist/ipc/protocol/domTypes.d.ts +35 -2
  50. package/dist/ipc/protocol/inspectTypes.d.ts +388 -0
  51. package/dist/ipc/protocol/inspectTypes.js +10 -0
  52. package/dist/runtime/dom/actionEffects.d.ts +94 -15
  53. package/dist/runtime/dom/actionEffects.js +173 -27
  54. package/dist/runtime/dom/actionEffectsScripts.d.ts +52 -14
  55. package/dist/runtime/dom/actionEffectsScripts.js +224 -32
  56. package/dist/runtime/dom/elementInfo.d.ts +26 -0
  57. package/dist/runtime/dom/elementInfo.js +65 -0
  58. package/dist/runtime/dom/eventListeners.js +14 -4
  59. package/dist/runtime/dom/formFillHelpers/fill.d.ts +3 -4
  60. package/dist/runtime/dom/formFillHelpers/fill.js +77 -28
  61. package/dist/runtime/dom/frameSelection.d.ts +11 -0
  62. package/dist/runtime/dom/frameSelection.js +20 -1
  63. package/dist/runtime/dom/frames.d.ts +38 -5
  64. package/dist/runtime/dom/frames.js +136 -21
  65. package/dist/runtime/dom/inspect.d.ts +28 -0
  66. package/dist/runtime/dom/inspect.js +557 -0
  67. package/dist/runtime/dom/inspectAllStyles.d.ts +62 -0
  68. package/dist/runtime/dom/inspectAllStyles.js +385 -0
  69. package/dist/runtime/dom/inspectCascade.d.ts +94 -0
  70. package/dist/runtime/dom/inspectCascade.js +371 -0
  71. package/dist/runtime/dom/inspectCascadeModel.d.ts +39 -0
  72. package/dist/runtime/dom/inspectCascadeModel.js +232 -0
  73. package/dist/runtime/dom/inspectHints.d.ts +62 -0
  74. package/dist/runtime/dom/inspectHints.js +305 -0
  75. package/dist/runtime/dom/inspectLayoutModel.d.ts +87 -0
  76. package/dist/runtime/dom/inspectLayoutModel.js +346 -0
  77. package/dist/runtime/dom/inspectModel.d.ts +74 -0
  78. package/dist/runtime/dom/inspectModel.js +184 -0
  79. package/dist/runtime/dom/inspectPaintModel.d.ts +157 -0
  80. package/dist/runtime/dom/inspectPaintModel.js +461 -0
  81. package/dist/runtime/dom/inspectRules.d.ts +37 -0
  82. package/dist/runtime/dom/inspectRules.js +101 -0
  83. package/dist/runtime/dom/inspectScripts.d.ts +132 -0
  84. package/dist/runtime/dom/inspectScripts.js +263 -0
  85. package/dist/runtime/dom/inspectTree.d.ts +40 -0
  86. package/dist/runtime/dom/inspectTree.js +134 -0
  87. package/dist/runtime/dom/inspectVariables.d.ts +33 -0
  88. package/dist/runtime/dom/inspectVariables.js +94 -0
  89. package/dist/runtime/dom/inspectWhyModel.d.ts +20 -0
  90. package/dist/runtime/dom/inspectWhyModel.js +134 -0
  91. package/dist/runtime/dom/layout.d.ts +5 -1
  92. package/dist/runtime/dom/layout.js +10 -3
  93. package/dist/runtime/dom/listenerPageScripts.d.ts +11 -5
  94. package/dist/runtime/dom/listenerPageScripts.js +95 -9
  95. package/dist/runtime/dom/listenerSummary.d.ts +4 -0
  96. package/dist/runtime/dom/listenerSummary.js +26 -9
  97. package/dist/runtime/dom/reactEventHelpers.d.ts +5 -0
  98. package/dist/runtime/dom/reactEventHelpers.js +12 -4
  99. package/dist/runtime/page/emulation.d.ts +20 -0
  100. package/dist/runtime/page/emulation.js +37 -0
  101. package/dist/telemetry/a11y.d.ts +10 -0
  102. package/dist/telemetry/a11y.js +78 -1
  103. package/dist/telemetry/console.d.ts +1 -0
  104. package/dist/telemetry/console.js +100 -5
  105. package/dist/telemetry/network.js +3 -1
  106. package/dist/types.d.ts +40 -0
  107. package/dist/ui/formatters/details.d.ts +8 -0
  108. package/dist/ui/formatters/details.js +59 -3
  109. package/dist/ui/formatters/dom.d.ts +2 -1
  110. package/dist/ui/formatters/dom.js +25 -9
  111. package/dist/ui/formatters/inspect.d.ts +39 -0
  112. package/dist/ui/formatters/inspect.js +596 -0
  113. package/dist/ui/formatters/installSkill.d.ts +11 -0
  114. package/dist/ui/formatters/installSkill.js +31 -0
  115. package/dist/ui/formatters/keyAttributes.d.ts +19 -0
  116. package/dist/ui/formatters/keyAttributes.js +84 -0
  117. package/dist/ui/formatters/layout.js +2 -2
  118. package/dist/ui/formatters/networkHeaders.d.ts +13 -0
  119. package/dist/ui/formatters/networkHeaders.js +23 -3
  120. package/dist/ui/formatters/networkList.d.ts +29 -1
  121. package/dist/ui/formatters/networkList.js +86 -20
  122. package/dist/ui/formatters/status.js +1 -1
  123. package/dist/ui/formatting.d.ts +9 -0
  124. package/dist/ui/formatting.js +6 -3
  125. package/dist/ui/messages/commands.d.ts +123 -7
  126. package/dist/ui/messages/commands.js +181 -10
  127. package/dist/ui/messages/networkMessages.d.ts +14 -0
  128. package/dist/ui/messages/networkMessages.js +18 -0
  129. package/dist/ui/messages/session.d.ts +14 -0
  130. package/dist/ui/messages/session.js +20 -0
  131. package/dist/utils/async.d.ts +9 -0
  132. package/dist/utils/async.js +17 -0
  133. package/dist/utils/color.d.ts +84 -0
  134. package/dist/utils/color.js +376 -0
  135. package/dist/utils/cssValues.d.ts +109 -0
  136. package/dist/utils/cssValues.js +236 -0
  137. package/dist/utils/selectorFilters.d.ts +12 -0
  138. package/dist/utils/selectorFilters.js +29 -0
  139. package/package.json +2 -1
@@ -0,0 +1,376 @@
1
+ /**
2
+ * CSS colors as `bdg dom inspect` reports them: parsed from the computed
3
+ * values Chrome returns (`rgb()`/`rgba()`, and `lab()`, `lch()`, `oklab()`,
4
+ * `oklch()` and `color()` as Tailwind v4 and other modern CSS leave them),
5
+ * converted to sRGB the way Chrome paints them on an sRGB screen (channels
6
+ * outside the gamut are clipped), and printed as short hex. Also the WCAG
7
+ * contrast math over alpha-composited backgrounds.
8
+ *
9
+ * Conversions follow the CSS Color 4 sample code (Bradford D50 → D65, the
10
+ * OKLab matrices of Björn Ottosson).
11
+ */
12
+ const XYZ_D65_TO_LINEAR_SRGB = [
13
+ [3.2409699419045226, -1.537383177570094, -0.4986107602930034],
14
+ [-0.9692436362808796, 1.8759675015077202, 0.04155505740717559],
15
+ [0.05563007969699366, -0.20397695888897652, 1.0569715142428786],
16
+ ];
17
+ const D50_TO_D65 = [
18
+ [0.955473421488075, -0.02309845494876471, 0.06325924320057072],
19
+ [-0.0283697093338637, 1.0099953980813041, 0.021041441191917323],
20
+ [0.012314014864481998, -0.020507649298898964, 1.330365926242124],
21
+ ];
22
+ const LINEAR_P3_TO_XYZ_D65 = [
23
+ [0.4865709486482162, 0.26566769316909306, 0.1982172852343625],
24
+ [0.2289745640697488, 0.6917385218365064, 0.079286914093745],
25
+ [0, 0.04511338185890264, 1.043944368900976],
26
+ ];
27
+ const LINEAR_REC2020_TO_XYZ_D65 = [
28
+ [0.6369580483012914, 0.14461690358620832, 0.1688809751641721],
29
+ [0.2627002120112671, 0.6779980715188708, 0.05930171646986196],
30
+ [0, 0.028072693049087428, 1.060985057710791],
31
+ ];
32
+ const LINEAR_A98_TO_XYZ_D65 = [
33
+ [0.5766690429101305, 0.1855582379065463, 0.1882286462349947],
34
+ [0.29734497525053605, 0.6273635662554661, 0.07529145849399788],
35
+ [0.02703136138641234, 0.07068885253582723, 0.9913375368376388],
36
+ ];
37
+ const LINEAR_PROPHOTO_TO_XYZ_D50 = [
38
+ [0.7977666449006423, 0.13518129740053308, 0.0313477341283922],
39
+ [0.2880748288194013, 0.711835234241873, 0.00008993693872564],
40
+ [0, 0, 0.8251046025104602],
41
+ ];
42
+ /** D50 reference white (CSS Color 4) */
43
+ const D50_WHITE = [0.3457 / 0.3585, 1, (1 - 0.3457 - 0.3585) / 0.3585];
44
+ /**
45
+ * Multiply a 3×3 matrix by a vector.
46
+ *
47
+ * @param m - Matrix
48
+ * @param v - Vector
49
+ * @returns Product
50
+ */
51
+ function multiply(m, v) {
52
+ return [
53
+ m[0][0] * v[0] + m[0][1] * v[1] + m[0][2] * v[2],
54
+ m[1][0] * v[0] + m[1][1] * v[1] + m[1][2] * v[2],
55
+ m[2][0] * v[0] + m[2][1] * v[1] + m[2][2] * v[2],
56
+ ];
57
+ }
58
+ /**
59
+ * Apply a transfer function to each channel, keeping the sign.
60
+ *
61
+ * @param v - Channels
62
+ * @param fn - Transfer function for non-negative values
63
+ * @returns Transformed channels
64
+ */
65
+ function perChannel(v, fn) {
66
+ return v.map((x) => Math.sign(x) * fn(Math.abs(x)));
67
+ }
68
+ /** sRGB (also Display P3) gamma-encoded → linear */
69
+ const srgbToLinear = (x) => x <= 0.04045 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4;
70
+ /** Linear → sRGB gamma-encoded */
71
+ const linearToSrgb = (x) => x <= 0.0031308 ? 12.92 * x : 1.055 * x ** (1 / 2.4) - 0.055;
72
+ /** Rec. 2020 gamma-encoded → linear */
73
+ const rec2020ToLinear = (x) => {
74
+ const alpha = 1.09929682680944;
75
+ const beta = 0.018053968510807;
76
+ return x < beta * 4.5 ? x / 4.5 : ((x + alpha - 1) / alpha) ** (1 / 0.45);
77
+ };
78
+ /** ProPhoto gamma-encoded → linear */
79
+ const prophotoToLinear = (x) => (x <= 16 / 512 ? x / 16 : x ** 1.8);
80
+ /**
81
+ * Gamma-encoded sRGB channels from XYZ (D65).
82
+ *
83
+ * @param xyz - XYZ relative to D65
84
+ * @returns sRGB channels (unclipped)
85
+ */
86
+ function xyzD65ToSrgb(xyz) {
87
+ return perChannel(multiply(XYZ_D65_TO_LINEAR_SRGB, xyz), linearToSrgb);
88
+ }
89
+ /**
90
+ * XYZ (D50) from CIE Lab.
91
+ *
92
+ * @param lab - L (0-100), a, b
93
+ * @returns XYZ relative to D50
94
+ */
95
+ function labToXyzD50([l, a, b]) {
96
+ const kappa = 24389 / 27;
97
+ const epsilon = 216 / 24389;
98
+ const f1 = (l + 16) / 116;
99
+ const f0 = a / 500 + f1;
100
+ const f2 = f1 - b / 200;
101
+ const x = f0 ** 3 > epsilon ? f0 ** 3 : (116 * f0 - 16) / kappa;
102
+ const y = l > kappa * epsilon ? f1 ** 3 : l / kappa;
103
+ const z = f2 ** 3 > epsilon ? f2 ** 3 : (116 * f2 - 16) / kappa;
104
+ return [x * D50_WHITE[0], y * D50_WHITE[1], z * D50_WHITE[2]];
105
+ }
106
+ /**
107
+ * Gamma-encoded sRGB channels from OKLab.
108
+ *
109
+ * @param oklab - L (0-1), a, b
110
+ * @returns sRGB channels (unclipped)
111
+ */
112
+ function oklabToSrgb([l, a, b]) {
113
+ const lms = [
114
+ (l + 0.3963377774 * a + 0.2158037573 * b) ** 3,
115
+ (l - 0.1055613458 * a - 0.0638541728 * b) ** 3,
116
+ (l - 0.0894841775 * a - 1.291485548 * b) ** 3,
117
+ ];
118
+ const linear = [
119
+ 4.0767416621 * lms[0] - 3.3077115913 * lms[1] + 0.2309699292 * lms[2],
120
+ -1.2684380046 * lms[0] + 2.6097574011 * lms[1] - 0.3413193965 * lms[2],
121
+ -0.0041960863 * lms[0] - 0.7034186147 * lms[1] + 1.707614701 * lms[2],
122
+ ];
123
+ return perChannel(linear, linearToSrgb);
124
+ }
125
+ /**
126
+ * Cartesian a/b from a polar chroma and hue.
127
+ *
128
+ * @param lch - Lightness, chroma, hue in degrees
129
+ * @returns Lightness, a, b
130
+ */
131
+ function polarToCartesian([l, c, h]) {
132
+ const radians = (h * Math.PI) / 180;
133
+ return [l, c * Math.cos(radians), c * Math.sin(radians)];
134
+ }
135
+ /** Gamma-encoded sRGB from the channels of a `color()` space */
136
+ const COLOR_SPACES = {
137
+ srgb: (v) => v,
138
+ 'srgb-linear': (v) => perChannel(v, linearToSrgb),
139
+ 'display-p3': (v) => xyzD65ToSrgb(multiply(LINEAR_P3_TO_XYZ_D65, perChannel(v, srgbToLinear))),
140
+ rec2020: (v) => xyzD65ToSrgb(multiply(LINEAR_REC2020_TO_XYZ_D65, perChannel(v, rec2020ToLinear))),
141
+ 'a98-rgb': (v) => xyzD65ToSrgb(multiply(LINEAR_A98_TO_XYZ_D65, perChannel(v, (x) => x ** (563 / 256)))),
142
+ 'prophoto-rgb': (v) => xyzD65ToSrgb(multiply(D50_TO_D65, multiply(LINEAR_PROPHOTO_TO_XYZ_D50, perChannel(v, prophotoToLinear)))),
143
+ xyz: (v) => xyzD65ToSrgb(v),
144
+ 'xyz-d65': (v) => xyzD65ToSrgb(v),
145
+ 'xyz-d50': (v) => xyzD65ToSrgb(multiply(D50_TO_D65, v)),
146
+ };
147
+ /**
148
+ * One color function argument as a number: `none` is 0, a percentage is
149
+ * scaled to `percentScale` (100% = percentScale), an angle is in degrees.
150
+ *
151
+ * @param token - Argument, e.g. `37.3%`, `0.2`, `none`, `120deg`
152
+ * @param percentScale - Value of 100%
153
+ * @returns Number, or NaN when it is not one
154
+ */
155
+ function argument(token, percentScale) {
156
+ if (token === 'none')
157
+ return 0;
158
+ const match = /^(-?[\d.]+(?:e-?\d+)?)(%|deg|grad|rad|turn)?$/i.exec(token);
159
+ if (!match)
160
+ return NaN;
161
+ const value = Number(match[1]);
162
+ const unit = match[2]?.toLowerCase();
163
+ if (unit === '%')
164
+ return (value / 100) * percentScale;
165
+ if (unit === 'rad')
166
+ return (value * 180) / Math.PI;
167
+ if (unit === 'grad')
168
+ return value * 0.9;
169
+ if (unit === 'turn')
170
+ return value * 360;
171
+ return value;
172
+ }
173
+ /**
174
+ * Split the arguments of a color function into its channels and alpha
175
+ * (after a `/`, or the token after the channels in legacy `rgba(r, g, b, a)`).
176
+ *
177
+ * @param body - Text between the parentheses
178
+ * @param count - Tokens before the alpha (4 for `color()`: the space and three channels)
179
+ * @returns Channel tokens and the alpha token (when given)
180
+ */
181
+ function splitArguments(body, count) {
182
+ const [main = '', alpha] = body.split('/').map((part) => part.trim());
183
+ const channels = main.split(/[\s,]+/).filter(Boolean);
184
+ if (alpha !== undefined)
185
+ return { channels, alpha };
186
+ const legacyAlpha = channels.length === count + 1 ? channels[count] : undefined;
187
+ if (legacyAlpha !== undefined)
188
+ return { channels: channels.slice(0, count), alpha: legacyAlpha };
189
+ return { channels };
190
+ }
191
+ /** How each color function maps its three channels to sRGB, with each channel's 100% */
192
+ const FUNCTIONS = {
193
+ rgb: { scales: [255, 255, 255], toSrgb: (v) => v.map((x) => x / 255) },
194
+ lab: {
195
+ scales: [100, 125, 125],
196
+ toSrgb: (v) => xyzD65ToSrgb(multiply(D50_TO_D65, labToXyzD50(v))),
197
+ },
198
+ lch: {
199
+ scales: [100, 150, 1],
200
+ toSrgb: (v) => xyzD65ToSrgb(multiply(D50_TO_D65, labToXyzD50(polarToCartesian(v)))),
201
+ },
202
+ oklab: { scales: [1, 0.4, 0.4], toSrgb: oklabToSrgb },
203
+ oklch: { scales: [1, 0.4, 1], toSrgb: (v) => oklabToSrgb(polarToCartesian(v)) },
204
+ };
205
+ /**
206
+ * Parse a `#rgb`, `#rgba`, `#rrggbb` or `#rrggbbaa` color.
207
+ *
208
+ * @param hex - Hex digits without `#`
209
+ * @returns Color, or null when malformed
210
+ */
211
+ function parseHex(hex) {
212
+ if (!/^([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(hex))
213
+ return null;
214
+ const full = hex.length <= 4 ? [...hex].map((digit) => digit + digit).join('') : hex;
215
+ const channel = (i) => parseInt(full.slice(i * 2, i * 2 + 2), 16) / 255;
216
+ return { r: channel(0), g: channel(1), b: channel(2), a: full.length === 8 ? channel(3) : 1 };
217
+ }
218
+ /**
219
+ * Parse a functional color: `rgb()`/`rgba()`, `lab()`, `lch()`, `oklab()`,
220
+ * `oklch()` or `color(<space> …)`.
221
+ *
222
+ * @param name - Function name, lowercased
223
+ * @param body - Text between the parentheses
224
+ * @returns Color in sRGB, or null when it is not one
225
+ */
226
+ function parseFunction(name, body) {
227
+ const { channels, alpha } = splitArguments(body, name === 'color' ? 4 : 3);
228
+ const a = alpha === undefined ? 1 : argument(alpha, 1);
229
+ if (name === 'color') {
230
+ const [space = '', ...rest] = channels;
231
+ const toSrgb = COLOR_SPACES[space.toLowerCase()];
232
+ const values = rest.map((token) => argument(token, 1));
233
+ if (!toSrgb || values.length !== 3)
234
+ return null;
235
+ return withAlpha(toSrgb(values), a);
236
+ }
237
+ const fn = FUNCTIONS[name === 'rgba' ? 'rgb' : name];
238
+ if (!fn || channels.length !== 3)
239
+ return null;
240
+ const values = channels.map((token, i) => argument(token, fn.scales[i] ?? 1));
241
+ return withAlpha(fn.toSrgb(values), a);
242
+ }
243
+ /**
244
+ * A color from sRGB channels and an alpha, or null when a value is not a
245
+ * number. Channels outside sRGB (wide-gamut lab/oklch/color()) are clipped
246
+ * here, before any compositing, as Chrome paints them.
247
+ *
248
+ * @param rgb - sRGB channels
249
+ * @param a - Alpha
250
+ * @returns Color, or null
251
+ */
252
+ function withAlpha(rgb, a) {
253
+ if ([...rgb, a].some((value) => Number.isNaN(value)))
254
+ return null;
255
+ const clamp = (value) => Math.min(1, Math.max(0, value));
256
+ return { r: clamp(rgb[0]), g: clamp(rgb[1]), b: clamp(rgb[2]), a: clamp(a) };
257
+ }
258
+ /**
259
+ * Parse a CSS color as Chrome computes it.
260
+ *
261
+ * @param value - e.g. `rgba(0, 0, 0, 0.2)`, `oklch(0.373 0.034 259.733)`, `#fff`, `transparent`
262
+ * @returns The color in sRGB, or null for anything else (`currentcolor`, keywords, gradients)
263
+ */
264
+ export function parseColor(value) {
265
+ const text = value.trim().toLowerCase();
266
+ if (text === 'transparent')
267
+ return { r: 0, g: 0, b: 0, a: 0 };
268
+ if (text.startsWith('#'))
269
+ return parseHex(text.slice(1));
270
+ const match = /^([a-z]+)\((.*)\)$/.exec(text);
271
+ return match?.[1] && match[2] !== undefined ? parseFunction(match[1], match[2]) : null;
272
+ }
273
+ /**
274
+ * A channel (0-1, clipped to the sRGB gamut) as two hex digits.
275
+ *
276
+ * @param channel - Channel value
277
+ * @returns e.g. `0a`
278
+ */
279
+ function hexByte(channel) {
280
+ const byte = Math.round(Math.min(1, Math.max(0, channel)) * 255);
281
+ return byte.toString(16).padStart(2, '0');
282
+ }
283
+ /**
284
+ * A color as short hex: `#rgb` when every channel's digits repeat, else
285
+ * `#rrggbb`; `#rrggbbaa` when translucent; `transparent` at alpha 0.
286
+ *
287
+ * @param color - Color in sRGB
288
+ * @returns e.g. `#fff`, `#364153`, `#00000033`
289
+ */
290
+ export function toHex(color) {
291
+ const alpha = Math.round(color.a * 255);
292
+ if (alpha === 0)
293
+ return 'transparent';
294
+ const rgb = [color.r, color.g, color.b].map(hexByte);
295
+ if (alpha < 255)
296
+ return `#${rgb.join('')}${alpha.toString(16).padStart(2, '0')}`;
297
+ const short = rgb.every((byte) => byte[0] === byte[1]);
298
+ return `#${short ? rgb.map((byte) => byte[0]).join('') : rgb.join('')}`;
299
+ }
300
+ /**
301
+ * A computed color as short hex ({@link toHex}).
302
+ *
303
+ * @param value - Computed color
304
+ * @returns Hex, or the value unchanged when it is not a color
305
+ */
306
+ export function hexColor(value) {
307
+ const color = parseColor(value);
308
+ return color ? toHex(color) : value;
309
+ }
310
+ /** Color functions and hex colors inside a longer value (shadows, gradients) */
311
+ const COLOR_IN_VALUE = /\b(?:rgba?|lab|lch|oklab|oklch|color)\([^()]*\)|#[0-9a-f]{3,8}\b/gi;
312
+ /**
313
+ * Replace every color inside a value with its hex ({@link toHex}).
314
+ *
315
+ * @param value - e.g. `rgba(0, 0, 0, 0.2) 0px 1px 2px 0px`
316
+ * @returns e.g. `#00000033 0px 1px 2px 0px`
317
+ */
318
+ export function hexColorsIn(value) {
319
+ return value.replace(COLOR_IN_VALUE, (match) => hexColor(match));
320
+ }
321
+ /**
322
+ * Paint a color over another (source-over alpha compositing).
323
+ *
324
+ * @param top - Color on top
325
+ * @param bottom - Color below
326
+ * @returns The color a user sees
327
+ */
328
+ export function composite(top, bottom) {
329
+ const a = top.a + bottom.a * (1 - top.a);
330
+ if (a === 0)
331
+ return { r: 0, g: 0, b: 0, a: 0 };
332
+ const mix = (t, b) => (t * top.a + b * bottom.a * (1 - top.a)) / a;
333
+ return { r: mix(top.r, bottom.r), g: mix(top.g, bottom.g), b: mix(top.b, bottom.b), a };
334
+ }
335
+ /**
336
+ * WCAG relative luminance of an opaque color (clipped to sRGB).
337
+ *
338
+ * @param color - Color
339
+ * @returns Luminance 0-1
340
+ */
341
+ export function relativeLuminance(color) {
342
+ const linear = [color.r, color.g, color.b].map((channel) => srgbToLinear(Math.min(1, Math.max(0, channel))));
343
+ return 0.2126 * (linear[0] ?? 0) + 0.7152 * (linear[1] ?? 0) + 0.0722 * (linear[2] ?? 0);
344
+ }
345
+ /**
346
+ * WCAG contrast ratio of text over an opaque background; translucent text
347
+ * is composited over the background first.
348
+ *
349
+ * @param text - Text color
350
+ * @param background - Opaque background color
351
+ * @returns Ratio 1-21
352
+ */
353
+ export function contrastRatio(text, background) {
354
+ const lighter = relativeLuminance(composite(text, background));
355
+ const other = relativeLuminance(background);
356
+ const [high, low] = lighter > other ? [lighter, other] : [other, lighter];
357
+ return (high + 0.05) / (low + 0.05);
358
+ }
359
+ /**
360
+ * WCAG 2 level a contrast ratio reaches. Large text (24px, or 18.66px bold)
361
+ * needs 4.5 for AAA and 3 for AA; other text 7 and 4.5.
362
+ *
363
+ * @param ratio - Contrast ratio
364
+ * @param fontSize - Font size in px
365
+ * @param fontWeight - Font weight
366
+ * @returns Level reached
367
+ */
368
+ export function contrastLevel(ratio, fontSize, fontWeight) {
369
+ const large = fontSize >= 24 || (fontSize >= 18.66 && fontWeight >= 700);
370
+ if (ratio >= (large ? 4.5 : 7))
371
+ return 'AAA';
372
+ if (ratio >= 4.5)
373
+ return 'AA';
374
+ return large && ratio >= 3 ? 'AA large' : 'fail';
375
+ }
376
+ //# sourceMappingURL=color.js.map
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Normalization of computed CSS values for `bdg dom inspect`: px as numbers
3
+ * rounded to one decimal (no unit), colors as hex, 1-4 value shorthands,
4
+ * shadow layers, grid tracks with `repeat()`, transforms decomposed from the
5
+ * matrices Chrome computes, and the first font family.
6
+ */
7
+ /** A length: px as a number, anything else (`auto`, `50%`, `10px 20px`) as text */
8
+ export type CssLength = number | string;
9
+ /**
10
+ * Round to one decimal (and drop `-0`).
11
+ *
12
+ * @param value - Number
13
+ * @returns e.g. 12.5, 0
14
+ */
15
+ export declare function round1(value: number): number;
16
+ /**
17
+ * A px value as a number.
18
+ *
19
+ * @param value - Computed value, e.g. `12.5px`
20
+ * @returns Rounded number, or undefined when the value is not a single px length
21
+ */
22
+ export declare function pxNumber(value: string | undefined): number | undefined;
23
+ /**
24
+ * A length as {@link CssLength}.
25
+ *
26
+ * @param value - Computed value
27
+ * @returns Number for px, else the value as text
28
+ */
29
+ export declare function cssLength(value: string | undefined): CssLength;
30
+ /**
31
+ * A computed value with px lengths as rounded unitless numbers and colors as
32
+ * hex, e.g. `rgba(0, 0, 0, 0.2) 0px 1.25px 2px` → `#00000033 0 1.3 2`.
33
+ *
34
+ * @param value - Computed value
35
+ * @returns Normalized value
36
+ */
37
+ export declare function normalizeCssValue(value: string): string;
38
+ /**
39
+ * 1-4 values the way CSS shorthands write them (top, right, bottom, left).
40
+ *
41
+ * @param sides - Top, right, bottom, left
42
+ * @returns e.g. `10`, `10 0`, `10 0 1`, `1 2 3 4`
43
+ */
44
+ export declare function sidesShorthand(sides: readonly CssLength[]): string;
45
+ /**
46
+ * Split a value at commas (or another separator) outside parentheses.
47
+ *
48
+ * @param value - e.g. `rgb(0, 0, 0) 0px 1px, rgb(1, 1, 1) 0px 2px`
49
+ * @param separator - Character to split at
50
+ * @returns Parts, trimmed
51
+ */
52
+ export declare function splitTopLevel(value: string, separator?: string): string[];
53
+ /**
54
+ * The first family of a `font-family` list, without quotes.
55
+ *
56
+ * @param fontFamily - e.g. `"DM Sans", Arial, sans-serif`
57
+ * @returns e.g. `DM Sans`
58
+ */
59
+ export declare function firstFontFamily(fontFamily: string): string;
60
+ /** One layer of a `box-shadow` or `text-shadow` */
61
+ export interface ShadowLayer {
62
+ x: number;
63
+ y: number;
64
+ blur: number;
65
+ /** Not for text shadows */
66
+ spread: number;
67
+ color: string;
68
+ inset: boolean;
69
+ }
70
+ /**
71
+ * The visible layers of a shadow: transparent layers and layers with no
72
+ * offset, blur or spread (Tailwind's ring placeholders) are left out.
73
+ *
74
+ * @param value - Computed `box-shadow` or `text-shadow`
75
+ * @returns Layers, outermost first
76
+ */
77
+ export declare function parseShadows(value: string): ShadowLayer[];
78
+ /**
79
+ * A shadow layer as text, e.g. `#00000033 0 1 2 0`, `inset #000 0 0 0 1`.
80
+ *
81
+ * @param layer - Shadow layer
82
+ * @param withSpread - Include the spread (box shadows)
83
+ * @returns Text
84
+ */
85
+ export declare function shadowText(layer: ShadowLayer, withSpread?: boolean): string;
86
+ /**
87
+ * Grid tracks with runs of equal sizes written as `repeat()`.
88
+ *
89
+ * @param value - Computed `grid-template-columns`/`rows`, e.g. `100px 100px 100px 50px`
90
+ * @returns e.g. `repeat(3,100) 50`; `none` stays `none`
91
+ */
92
+ export declare function compressTracks(value: string): string;
93
+ /**
94
+ * A computed transform in readable form: a 2D matrix as
95
+ * translate/rotate/scale, other matrices with rounded numbers.
96
+ *
97
+ * @param value - Computed `transform`, e.g. `matrix(1, 0, 0, 1, 10, 20)`
98
+ * @returns e.g. `translate(10,20)`; `none` when it does nothing
99
+ */
100
+ export declare function readableTransform(value: string): string;
101
+ /**
102
+ * The file name of a URL in a CSS value: `url("https://x.com/a/b.png?v=1")` → `url(b.png)`;
103
+ * `data:` URLs become `url(data:…)`.
104
+ *
105
+ * @param value - Computed value with `url()`s
106
+ * @returns The value with short URLs
107
+ */
108
+ export declare function shortUrls(value: string): string;
109
+ //# sourceMappingURL=cssValues.d.ts.map