@kerfjs/ui 5.0.0-beta.53 → 5.0.0-beta.54
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/README.md +1 -0
- package/ai/application-ui-diagnostic-ids-v1.json +1 -0
- package/ai/component-catalog-v2.json +82 -6
- package/ai/component-catalog-v2.schema.json +2 -1
- package/ai/component-catalog.json +105 -10
- package/ai/public-api-signatures-v1.md +204 -18
- package/ai/skill.md +23 -17
- package/ai/webawesome-jsx-signatures-v1.md +1 -1
- package/analyzer/index.mjs +48 -0
- package/analyzer/loud-pairs.d.mts +49 -0
- package/analyzer/loud-pairs.mjs +357 -0
- package/dist/app-tab.d.ts +7 -5
- package/dist/app-tab.js +1 -1
- package/dist/{chunk-EKYZCRCX.js → chunk-3CGQ7ZNO.js} +54 -22
- package/dist/chunk-3CGQ7ZNO.js.map +1 -0
- package/dist/{chunk-UKKSMZYT.js → chunk-46263QLK.js} +51 -1
- package/dist/chunk-46263QLK.js.map +1 -0
- package/dist/{chunk-NDUGJ4TU.js → chunk-5NDQABWK.js} +1 -1
- package/dist/{chunk-NDUGJ4TU.js.map → chunk-5NDQABWK.js.map} +1 -1
- package/dist/{chunk-CCMKJWLX.js → chunk-NE5JAOQS.js} +7 -7
- package/dist/chunk-NE5JAOQS.js.map +1 -0
- package/dist/chunk-PSHUSWQH.js +18 -0
- package/dist/chunk-PSHUSWQH.js.map +1 -0
- package/dist/{chunk-VOCPXGQN.js → chunk-VHADGP3B.js} +9 -8
- package/dist/chunk-VHADGP3B.js.map +1 -0
- package/dist/chunk-XQV2B4IL.js +14 -0
- package/dist/chunk-XQV2B4IL.js.map +1 -0
- package/dist/collapsible-panel.d.ts +6 -2
- package/dist/collapsible-panel.js +1 -0
- package/dist/collapsible-panel.js.map +1 -1
- package/dist/floating-toolbar.d.ts +5 -2
- package/dist/floating-toolbar.js +1 -1
- package/dist/index.js +6 -5
- package/dist/resizable-region.d.ts +4 -1
- package/dist/resizable-region.js +1 -1
- package/dist/split-view.js +1 -1
- package/dist/styles/collapsible-panel.css +88 -10
- package/dist/styles/floating-toolbar.css +11 -6
- package/dist/styles/resizable-region.css +81 -13
- package/dist/styles/workbench.css +472 -10
- package/dist/wire-resizable-regions.d.ts +2 -1
- package/dist/wire-resizable-regions.js +2 -1
- package/dist/wire-tab-bars.js +1 -1
- package/dist/wire-workbench.d.ts +109 -0
- package/dist/wire-workbench.js +355 -0
- package/dist/wire-workbench.js.map +1 -0
- package/dist/workbench.d.ts +65 -6
- package/dist/workbench.js +180 -76
- package/dist/workbench.js.map +1 -1
- package/docs/accessibility.md +54 -9
- package/docs/app-layouts.md +12 -1
- package/docs/collapsible-panel.md +59 -1
- package/docs/component-selection.md +16 -6
- package/docs/document-baseline.md +4 -0
- package/docs/layout.md +5 -1
- package/docs/recipes.md +5 -3
- package/docs/type-contracts.md +2 -1
- package/docs/ui-analyzer.md +94 -19
- package/docs/ui-doctor.md +6 -0
- package/docs/ux-demo.md +1 -1
- package/docs/webawesome-theme.md +11 -2
- package/docs/workbench.md +302 -7
- package/llms.txt +7 -3
- package/package.json +5 -1
- package/ux-demo/recipes/collapsible-sidebar.tsx +8 -7
- package/dist/chunk-CCMKJWLX.js.map +0 -1
- package/dist/chunk-EKYZCRCX.js.map +0 -1
- package/dist/chunk-UKKSMZYT.js.map +0 -1
- package/dist/chunk-VOCPXGQN.js.map +0 -1
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import postcss from 'postcss';
|
|
2
|
+
|
|
3
|
+
// KUI-L018: the shipped Web Awesome theme pairs every loud fill with an
|
|
4
|
+
// on-loud foreground that clears WCAG AA; an override that moves one without
|
|
5
|
+
// the other silently drops that guarantee (see docs/webawesome-theme.md).
|
|
6
|
+
const loudToken =
|
|
7
|
+
/^--wa-color-(neutral|brand|success|warning|danger|pop)-(fill-loud|on-loud)$/;
|
|
8
|
+
|
|
9
|
+
/** WCAG AA contrast for normal text. */
|
|
10
|
+
export const LOUD_PAIR_MIN_CONTRAST = 4.5;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The on-loud foreground `@kerfjs/ui/webawesome.css` ships for each tone,
|
|
14
|
+
* where it is a literal color. An unpaired fill that still clears AA against
|
|
15
|
+
* it keeps the guarantee. `neutral` follows the surface token and `pop` has no
|
|
16
|
+
* Web Awesome default, so neither can be proven and an unpaired fill stays a
|
|
17
|
+
* finding. A unit test keeps this table equal to the shipped stylesheet.
|
|
18
|
+
*/
|
|
19
|
+
export const DEFAULT_ON_LOUD = Object.freeze({
|
|
20
|
+
brand: 'light-dark(#fff, #111113)',
|
|
21
|
+
success: '#1d1d1f',
|
|
22
|
+
warning: '#1d1d1f',
|
|
23
|
+
danger: 'light-dark(#fff, #111113)',
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const NAMED_COLORS = new Map([
|
|
27
|
+
['white', [255, 255, 255]],
|
|
28
|
+
['black', [0, 0, 0]],
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
function channel(value, scale) {
|
|
32
|
+
const text = value.trim();
|
|
33
|
+
if (text.endsWith('%')) return (Number(text.slice(0, -1)) / 100) * scale;
|
|
34
|
+
return Number(text);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function opaque(alpha) {
|
|
38
|
+
if (alpha === undefined) return true;
|
|
39
|
+
const text = alpha.trim();
|
|
40
|
+
const value = text.endsWith('%')
|
|
41
|
+
? Number(text.slice(0, -1)) / 100
|
|
42
|
+
: Number(text);
|
|
43
|
+
return value === 1;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function hslToRgb(hue, saturation, lightness) {
|
|
47
|
+
const h = (((hue % 360) + 360) % 360) / 360;
|
|
48
|
+
const s = saturation / 100;
|
|
49
|
+
const l = lightness / 100;
|
|
50
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
51
|
+
const p = 2 * l - q;
|
|
52
|
+
const convert = (t) => {
|
|
53
|
+
const x = t < 0 ? t + 1 : t > 1 ? t - 1 : t;
|
|
54
|
+
if (x < 1 / 6) return p + (q - p) * 6 * x;
|
|
55
|
+
if (x < 1 / 2) return q;
|
|
56
|
+
if (x < 2 / 3) return p + (q - p) * (2 / 3 - x) * 6;
|
|
57
|
+
return p;
|
|
58
|
+
};
|
|
59
|
+
return [convert(h + 1 / 3), convert(h), convert(h - 1 / 3)].map(
|
|
60
|
+
(component) => component * 255,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** An opaque literal sRGB color as `[r, g, b]` (0-255), else `undefined`. */
|
|
65
|
+
function parseOpaqueColor(value) {
|
|
66
|
+
const text = value.trim().toLowerCase();
|
|
67
|
+
if (NAMED_COLORS.has(text)) return NAMED_COLORS.get(text);
|
|
68
|
+
const hex = /^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/.exec(text);
|
|
69
|
+
if (hex) {
|
|
70
|
+
const digits =
|
|
71
|
+
hex[1].length <= 4
|
|
72
|
+
? [...hex[1]].map((digit) => digit + digit).join('')
|
|
73
|
+
: hex[1];
|
|
74
|
+
if (digits.length === 8 && digits.slice(6) !== 'ff') return undefined;
|
|
75
|
+
return [0, 2, 4].map((start) =>
|
|
76
|
+
Number.parseInt(digits.slice(start, start + 2), 16),
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
const functional = /^(rgba?|hsla?)\(([^()]*)\)$/.exec(text);
|
|
80
|
+
if (!functional) return undefined;
|
|
81
|
+
const [components, alpha] = functional[2].includes('/')
|
|
82
|
+
? functional[2].split('/')
|
|
83
|
+
: [functional[2]];
|
|
84
|
+
const parts = components.split(/[\s,]+/).filter(Boolean);
|
|
85
|
+
const alphaPart = alpha ?? parts[3];
|
|
86
|
+
if (parts.length < 3 || !opaque(alphaPart)) return undefined;
|
|
87
|
+
const rgb = functional[1].startsWith('rgb')
|
|
88
|
+
? parts.slice(0, 3).map((part) => channel(part, 255))
|
|
89
|
+
: hslToRgb(
|
|
90
|
+
Number(parts[0].replace(/deg$/, '')),
|
|
91
|
+
Number(parts[1].replace(/%$/, '')),
|
|
92
|
+
Number(parts[2].replace(/%$/, '')),
|
|
93
|
+
);
|
|
94
|
+
return rgb.every((component) => Number.isFinite(component))
|
|
95
|
+
? rgb.map((component) => Math.min(255, Math.max(0, component)))
|
|
96
|
+
: undefined;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* A literal color value resolved per color scheme: `{ light, dark }` of
|
|
101
|
+
* `[r, g, b]`. `light-dark(a, b)` splits by scheme; a plain literal applies to
|
|
102
|
+
* both. Anything else (a `var()`, `color-mix()`, a keyword, transparency) is
|
|
103
|
+
* not provable and returns `undefined`.
|
|
104
|
+
*/
|
|
105
|
+
export function literalSchemeColors(value) {
|
|
106
|
+
const text = value
|
|
107
|
+
.replace(/!important\s*$/i, '')
|
|
108
|
+
.trim()
|
|
109
|
+
.toLowerCase();
|
|
110
|
+
const lightDark = /^light-dark\((.*)\)$/.exec(text);
|
|
111
|
+
if (lightDark) {
|
|
112
|
+
const parts = postcss.list.comma(lightDark[1]);
|
|
113
|
+
if (parts.length !== 2) return undefined;
|
|
114
|
+
const [light, dark] = parts.map(parseOpaqueColor);
|
|
115
|
+
return light && dark ? { light, dark } : undefined;
|
|
116
|
+
}
|
|
117
|
+
const color = parseOpaqueColor(text);
|
|
118
|
+
return color ? { light: color, dark: color } : undefined;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function luminance([red, green, blue]) {
|
|
122
|
+
const linear = (component) => {
|
|
123
|
+
const value = component / 255;
|
|
124
|
+
return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
|
|
125
|
+
};
|
|
126
|
+
return 0.2126 * linear(red) + 0.7152 * linear(green) + 0.0722 * linear(blue);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** WCAG 2 contrast ratio between two `[r, g, b]` colors. */
|
|
130
|
+
export function contrastRatio(first, second) {
|
|
131
|
+
const [lighter, darker] = [luminance(first), luminance(second)].sort(
|
|
132
|
+
(a, b) => b - a,
|
|
133
|
+
);
|
|
134
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* The lowest per-scheme contrast of a fill/foreground pair, or `undefined`
|
|
139
|
+
* when either value is not a provable literal color.
|
|
140
|
+
*/
|
|
141
|
+
export function loudPairContrast(fillValue, onLoudValue) {
|
|
142
|
+
const fill = literalSchemeColors(fillValue);
|
|
143
|
+
const foreground = literalSchemeColors(onLoudValue);
|
|
144
|
+
if (!fill || !foreground) return undefined;
|
|
145
|
+
const results = ['light', 'dark'].map((scheme) => ({
|
|
146
|
+
scheme,
|
|
147
|
+
ratio: contrastRatio(fill[scheme], foreground[scheme]),
|
|
148
|
+
}));
|
|
149
|
+
return results.reduce((lowest, item) =>
|
|
150
|
+
item.ratio < lowest.ratio ? item : lowest,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function normalizeSelector(selector) {
|
|
155
|
+
return selector
|
|
156
|
+
.replace(/\s+/g, ' ')
|
|
157
|
+
.replace(/\s*([>+~])\s*/g, '$1')
|
|
158
|
+
.trim();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function resolvedSelectors(node) {
|
|
162
|
+
const chain = [];
|
|
163
|
+
let current = node;
|
|
164
|
+
while (current && current.type !== 'root') {
|
|
165
|
+
chain.unshift(current);
|
|
166
|
+
current = current.parent;
|
|
167
|
+
}
|
|
168
|
+
let selectors = [''];
|
|
169
|
+
const atRules = [];
|
|
170
|
+
for (const item of chain) {
|
|
171
|
+
if (item.type === 'atrule')
|
|
172
|
+
atRules.push(`@${item.name} ${item.params.replace(/\s+/g, ' ').trim()}`);
|
|
173
|
+
if (item.type !== 'rule') continue;
|
|
174
|
+
const own = postcss.list.comma(item.selector).map(normalizeSelector);
|
|
175
|
+
selectors = selectors.flatMap((parent) =>
|
|
176
|
+
own.map((child) => {
|
|
177
|
+
if (child.includes('&'))
|
|
178
|
+
return child.replaceAll('&', parent || ':root');
|
|
179
|
+
return parent ? `${parent} ${child}` : child;
|
|
180
|
+
}),
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
const atScope = atRules.join(' ');
|
|
184
|
+
return selectors.map((selector) => ({
|
|
185
|
+
key: `${atScope}|${selector}`,
|
|
186
|
+
atScope,
|
|
187
|
+
bare: selector,
|
|
188
|
+
selector: atScope ? `${atScope} ${selector}`.trim() : selector,
|
|
189
|
+
}));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Every loud-token declaration in a stylesheet, one record per resolved
|
|
194
|
+
* selector scope: `{ decl, file, tone, kind, value, scope }`.
|
|
195
|
+
*/
|
|
196
|
+
export function collectLoudDeclarations(file, root) {
|
|
197
|
+
const records = [];
|
|
198
|
+
root.walkDecls((decl) => {
|
|
199
|
+
const match = loudToken.exec(decl.prop.trim());
|
|
200
|
+
if (!match) return;
|
|
201
|
+
for (const scope of resolvedSelectors(decl.parent))
|
|
202
|
+
records.push({
|
|
203
|
+
decl,
|
|
204
|
+
file,
|
|
205
|
+
tone: match[1],
|
|
206
|
+
kind: match[2],
|
|
207
|
+
value: decl.value,
|
|
208
|
+
scope,
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
return records;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Selectors that match the document root (or a shadow root's host), so a
|
|
215
|
+
// custom property set there inherits into every other scope.
|
|
216
|
+
const rootSelectors = new Set(['', ':root', 'html', ':host', '*']);
|
|
217
|
+
|
|
218
|
+
// Whether an on-loud declared in `outer` provably reaches an element that
|
|
219
|
+
// `inner` matches: the same scope, or an ancestor one. The outer at-rule chain
|
|
220
|
+
// must be absent or a prefix of the inner one (an unconditional value applies
|
|
221
|
+
// inside `@media`, not the reverse), and the outer selector must be a document
|
|
222
|
+
// root selector or the leading compound selector of a descendant/child chain.
|
|
223
|
+
function reaches(outer, inner) {
|
|
224
|
+
if (
|
|
225
|
+
outer.atScope &&
|
|
226
|
+
outer.atScope !== inner.atScope &&
|
|
227
|
+
!inner.atScope.startsWith(`${outer.atScope} `)
|
|
228
|
+
)
|
|
229
|
+
return false;
|
|
230
|
+
if (outer.bare === inner.bare || rootSelectors.has(outer.bare)) return true;
|
|
231
|
+
return (
|
|
232
|
+
inner.bare.startsWith(outer.bare) &&
|
|
233
|
+
[' ', '>'].includes(inner.bare[outer.bare.length])
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// The on-loud that governs a fill scope: the same scope wins (this stylesheet
|
|
238
|
+
// before a co-loaded one), then the nearest ancestor (the longest selector).
|
|
239
|
+
function governingOnLoud(fill, onLoud) {
|
|
240
|
+
const candidates = onLoud.filter(
|
|
241
|
+
(record) => record.tone === fill.tone && reaches(record.scope, fill.scope),
|
|
242
|
+
);
|
|
243
|
+
const rank = (record) => [
|
|
244
|
+
record.scope.key === fill.scope.key ? 1 : 0,
|
|
245
|
+
record.file === fill.file ? 1 : 0,
|
|
246
|
+
record.scope.atScope.length + record.scope.bare.length,
|
|
247
|
+
];
|
|
248
|
+
return candidates.sort((a, b) => {
|
|
249
|
+
const [left, right] = [rank(a), rank(b)];
|
|
250
|
+
for (let index = 0; index < left.length; index += 1)
|
|
251
|
+
if (left[index] !== right[index]) return right[index] - left[index];
|
|
252
|
+
return 0;
|
|
253
|
+
})[0];
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const scopeLabel = (selector) => selector || '(top level)';
|
|
257
|
+
// Truncated rather than rounded, so a pair just under 4.5:1 never reads as
|
|
258
|
+
// passing.
|
|
259
|
+
const truncatedRatio = (ratio) => Math.floor(ratio * 100) / 100;
|
|
260
|
+
const ratioLabel = (ratio) => `${truncatedRatio(ratio).toFixed(2)}:1`;
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Report each loud fill override whose tone lacks a governing on-loud (unless
|
|
264
|
+
* the fill provably clears AA against the theme's default foreground), and
|
|
265
|
+
* each literal pair that is present but below 4.5:1. `siblingOnLoud` holds
|
|
266
|
+
* on-loud records from the other stylesheets the same entry loads.
|
|
267
|
+
*/
|
|
268
|
+
export function inspectLoudPairs(file, root, report, siblingOnLoud = []) {
|
|
269
|
+
const records = collectLoudDeclarations(file, root);
|
|
270
|
+
const onLoud = [
|
|
271
|
+
...records.filter(({ kind }) => kind === 'on-loud'),
|
|
272
|
+
...siblingOnLoud,
|
|
273
|
+
];
|
|
274
|
+
const fills = new Map();
|
|
275
|
+
for (const record of records.filter(({ kind }) => kind === 'fill-loud')) {
|
|
276
|
+
const group = fills.get(record.decl) ?? [];
|
|
277
|
+
group.push(record);
|
|
278
|
+
fills.set(record.decl, group);
|
|
279
|
+
}
|
|
280
|
+
for (const [decl, scopes] of fills) {
|
|
281
|
+
const { tone } = scopes[0];
|
|
282
|
+
const pair = `--wa-color-${tone}-on-loud`;
|
|
283
|
+
const missing = [];
|
|
284
|
+
const failing = [];
|
|
285
|
+
for (const fill of scopes) {
|
|
286
|
+
const governing = governingOnLoud(fill, onLoud);
|
|
287
|
+
if (governing) {
|
|
288
|
+
const contrast = loudPairContrast(fill.value, governing.value);
|
|
289
|
+
if (contrast && contrast.ratio < LOUD_PAIR_MIN_CONTRAST)
|
|
290
|
+
failing.push({ fill, governing, contrast });
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
const fallback = DEFAULT_ON_LOUD[tone]
|
|
294
|
+
? loudPairContrast(fill.value, DEFAULT_ON_LOUD[tone])
|
|
295
|
+
: undefined;
|
|
296
|
+
if (fallback && fallback.ratio >= LOUD_PAIR_MIN_CONTRAST) continue;
|
|
297
|
+
missing.push({ fill, fallback });
|
|
298
|
+
}
|
|
299
|
+
if (missing.length) {
|
|
300
|
+
const [measured] = missing
|
|
301
|
+
.map(({ fallback }) => fallback)
|
|
302
|
+
.filter(Boolean)
|
|
303
|
+
.sort((a, b) => a.ratio - b.ratio);
|
|
304
|
+
report(
|
|
305
|
+
decl,
|
|
306
|
+
`${decl.prop} is overridden without ${pair} in the same or an ancestor scope (${missing
|
|
307
|
+
.map(({ fill }) => scopeLabel(fill.scope.selector))
|
|
308
|
+
.join(', ')})${
|
|
309
|
+
measured
|
|
310
|
+
? `, and it measures ${ratioLabel(measured.ratio)} against the theme's default ${pair} in the ${measured.scheme} scheme`
|
|
311
|
+
: ''
|
|
312
|
+
}; set ${pair} alongside it so the loud fill keeps an AA-clearing foreground.`,
|
|
313
|
+
{
|
|
314
|
+
property: decl.prop,
|
|
315
|
+
value: decl.value,
|
|
316
|
+
tone,
|
|
317
|
+
pairProperty: pair,
|
|
318
|
+
selectors: missing.map(({ fill }) => fill.scope.selector),
|
|
319
|
+
...(measured
|
|
320
|
+
? {
|
|
321
|
+
defaultPairValue: DEFAULT_ON_LOUD[tone],
|
|
322
|
+
contrast: truncatedRatio(measured.ratio),
|
|
323
|
+
scheme: measured.scheme,
|
|
324
|
+
}
|
|
325
|
+
: {}),
|
|
326
|
+
},
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
if (failing.length) {
|
|
330
|
+
const worst = failing.reduce((lowest, item) =>
|
|
331
|
+
item.contrast.ratio < lowest.contrast.ratio ? item : lowest,
|
|
332
|
+
);
|
|
333
|
+
report(
|
|
334
|
+
decl,
|
|
335
|
+
`${decl.prop} (${decl.value.trim()}) and its ${pair} (${worst.governing.value.trim()}) measure ${ratioLabel(worst.contrast.ratio)} in the ${worst.contrast.scheme} scheme, below the ${LOUD_PAIR_MIN_CONTRAST}:1 AA minimum (${failing
|
|
336
|
+
.map(({ fill }) => scopeLabel(fill.scope.selector))
|
|
337
|
+
.join(
|
|
338
|
+
', ',
|
|
339
|
+
)}); choose a fill or foreground that clears 4.5:1 in every scheme.`,
|
|
340
|
+
{
|
|
341
|
+
property: decl.prop,
|
|
342
|
+
value: decl.value,
|
|
343
|
+
tone,
|
|
344
|
+
pairProperty: pair,
|
|
345
|
+
pairValue: worst.governing.value,
|
|
346
|
+
pairLocation: {
|
|
347
|
+
file: worst.governing.file,
|
|
348
|
+
line: worst.governing.decl.source?.start?.line ?? 1,
|
|
349
|
+
},
|
|
350
|
+
contrast: truncatedRatio(worst.contrast.ratio),
|
|
351
|
+
scheme: worst.contrast.scheme,
|
|
352
|
+
selectors: failing.map(({ fill }) => fill.scope.selector),
|
|
353
|
+
},
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
package/dist/app-tab.d.ts
CHANGED
|
@@ -33,11 +33,13 @@ interface AppTabProps {
|
|
|
33
33
|
/** Render as an unanimated loading skeleton, disabling select/close and dragging. */
|
|
34
34
|
placeholder?: boolean;
|
|
35
35
|
/**
|
|
36
|
-
*
|
|
37
|
-
* visible in the quiet text color and as the tab's accessible
|
|
38
|
-
* `trailing` (for example a `LoadingSpinner`), and
|
|
39
|
-
*
|
|
40
|
-
*
|
|
36
|
+
* Named and still opening: the tab is known but its content is loading.
|
|
37
|
+
* Keeps `name` visible in the quiet text color and as the tab's accessible
|
|
38
|
+
* name, shows `trailing` (for example a `LoadingSpinner`), and marks the
|
|
39
|
+
* tab `aria-busy`. Unlike `placeholder` it stays selectable — the
|
|
40
|
+
* application shows placeholder content in its panel until loading
|
|
41
|
+
* completes — while close and dragging stay disabled. Same pill geometry
|
|
42
|
+
* as the live tab, so swapping it in place does not shift the bar.
|
|
41
43
|
* `placeholder` wins when both are set.
|
|
42
44
|
*/
|
|
43
45
|
pending?: boolean;
|
package/dist/app-tab.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import { resizeRegionFromPointer, clampRegionSize } from './chunk-
|
|
1
|
+
import { resizeRegionFromPointer, clampRegionSize } from './chunk-VHADGP3B.js';
|
|
2
2
|
import { delegate } from 'kerfjs';
|
|
3
3
|
|
|
4
|
-
var REGION_SELECTOR = '[data-component="resizable-region"]';
|
|
5
4
|
var HANDLE_SELECTOR = "[data-kui-resize-handle]";
|
|
6
5
|
var INSET_ATTRIBUTE = "data-handle-inset";
|
|
7
|
-
var REPORTED_ATTRIBUTES = [
|
|
6
|
+
var REPORTED_ATTRIBUTES = [
|
|
7
|
+
"aria-valuenow",
|
|
8
|
+
"aria-valuemin",
|
|
9
|
+
"aria-valuemax",
|
|
10
|
+
INSET_ATTRIBUTE
|
|
11
|
+
];
|
|
8
12
|
var HANDLE_OVERHANG = 10;
|
|
9
13
|
function renderedSize(region, handle) {
|
|
10
14
|
const size = Number.parseFloat(
|
|
@@ -35,36 +39,58 @@ function visibleMax(region, axis, min, max) {
|
|
|
35
39
|
if (!Number.isFinite(basis) || track <= 0 || track >= basis - 0.5) return max;
|
|
36
40
|
return Math.max(min, Math.min(max, Math.floor(track - (basis - max))));
|
|
37
41
|
}
|
|
42
|
+
function shownSize(region, axis, size) {
|
|
43
|
+
const basis = Number.parseFloat(
|
|
44
|
+
globalThis.getComputedStyle(region).flexBasis
|
|
45
|
+
);
|
|
46
|
+
const box = region.getBoundingClientRect();
|
|
47
|
+
const track = axis === "horizontal" ? box.width : box.height;
|
|
48
|
+
if (!Number.isFinite(basis) || track <= 0 || track >= basis - 0.5)
|
|
49
|
+
return void 0;
|
|
50
|
+
return Math.max(0, Math.floor(track - (basis - size)));
|
|
51
|
+
}
|
|
52
|
+
function reported(state) {
|
|
53
|
+
const { squeezed } = state;
|
|
54
|
+
return squeezed === void 0 ? { min: state.min, max: state.max, now: state.size } : { min: squeezed, max: squeezed, now: squeezed };
|
|
55
|
+
}
|
|
56
|
+
function pinned(state) {
|
|
57
|
+
return state.squeezed !== void 0 || state.max <= state.min;
|
|
58
|
+
}
|
|
38
59
|
function applySize(state, size) {
|
|
39
60
|
const value = `${size}px`;
|
|
40
61
|
state.region.style.setProperty("--kui-resizable-region-size", value);
|
|
41
62
|
state.region.style.setProperty("--kui-resizable-region-expanded-size", value);
|
|
42
|
-
state.
|
|
63
|
+
if (state.squeezed === void 0)
|
|
64
|
+
state.handle.setAttribute("aria-valuenow", String(size));
|
|
43
65
|
setInset(state.region, handleInset(state, size));
|
|
44
66
|
}
|
|
45
|
-
function
|
|
67
|
+
function wireResizeHandles(root, {
|
|
46
68
|
step = 16,
|
|
47
69
|
largeStep = 64,
|
|
48
70
|
onPreview,
|
|
49
71
|
onCommit
|
|
50
|
-
}) {
|
|
72
|
+
}, regionSelector, limit) {
|
|
73
|
+
const REGION_SELECTOR = regionSelector;
|
|
51
74
|
let stopPointer;
|
|
52
75
|
const announced = /* @__PURE__ */ new WeakMap();
|
|
53
76
|
function regionState(handle) {
|
|
54
77
|
const region = handle.closest(REGION_SELECTOR);
|
|
55
|
-
if (!(region instanceof HTMLElement) || !(handle instanceof HTMLElement))
|
|
78
|
+
if (!(region instanceof HTMLElement) || !(handle instanceof HTMLElement) || handle.parentElement !== region)
|
|
56
79
|
return void 0;
|
|
57
80
|
const id = region.dataset.regionId;
|
|
58
81
|
const axis = region.dataset.axis;
|
|
59
82
|
const edge = region.dataset.edge;
|
|
60
|
-
const
|
|
83
|
+
const valueMin = Number(handle.getAttribute("aria-valuemin"));
|
|
61
84
|
const valueMax = Number(handle.getAttribute("aria-valuemax"));
|
|
62
85
|
const remembered = announced.get(handle);
|
|
86
|
+
const min = remembered?.min === valueMin ? remembered.declaredMin : valueMin;
|
|
63
87
|
const declaredMax = remembered?.max === valueMax ? remembered.declaredMax : valueMax;
|
|
64
88
|
const size = renderedSize(region, handle);
|
|
65
89
|
if (!id || region.dataset.collapsed === "true" || region.dataset.presentation !== "inline" || axis !== "horizontal" && axis !== "vertical" || edge !== "start" && edge !== "end" || !Number.isFinite(min) || !Number.isFinite(declaredMax) || !Number.isFinite(size))
|
|
66
90
|
return void 0;
|
|
67
|
-
const
|
|
91
|
+
const shown = shownSize(region, axis, size);
|
|
92
|
+
const visible = visibleMax(region, axis, min, declaredMax);
|
|
93
|
+
const max = limit ? Math.max(min, Math.min(visible, limit(region, { min, max: visible }))) : visible;
|
|
68
94
|
return {
|
|
69
95
|
region,
|
|
70
96
|
handle,
|
|
@@ -74,19 +100,24 @@ function wireResizableRegions(root, {
|
|
|
74
100
|
min,
|
|
75
101
|
declaredMax,
|
|
76
102
|
max,
|
|
77
|
-
size: clampRegionSize(size, min, max)
|
|
103
|
+
size: clampRegionSize(size, min, max),
|
|
104
|
+
squeezed: shown !== void 0 && shown < min ? shown : void 0
|
|
78
105
|
};
|
|
79
106
|
}
|
|
80
107
|
function announce(state) {
|
|
81
108
|
const inset = handleInset(state, state.size);
|
|
109
|
+
const { min, max, now } = reported(state);
|
|
82
110
|
announced.set(state.handle, {
|
|
111
|
+
declaredMin: state.min,
|
|
83
112
|
declaredMax: state.declaredMax,
|
|
84
|
-
|
|
85
|
-
|
|
113
|
+
min,
|
|
114
|
+
max,
|
|
115
|
+
size: now,
|
|
86
116
|
inset
|
|
87
117
|
});
|
|
88
|
-
setAttributeIfChanged(state.handle, "aria-
|
|
89
|
-
setAttributeIfChanged(state.handle, "aria-
|
|
118
|
+
setAttributeIfChanged(state.handle, "aria-valuemin", String(min));
|
|
119
|
+
setAttributeIfChanged(state.handle, "aria-valuemax", String(max));
|
|
120
|
+
setAttributeIfChanged(state.handle, "aria-valuenow", String(now));
|
|
90
121
|
setInset(state.region, inset);
|
|
91
122
|
}
|
|
92
123
|
const handleOf = (region) => region.querySelector(`:scope > ${HANDLE_SELECTOR}`);
|
|
@@ -99,7 +130,7 @@ function wireResizableRegions(root, {
|
|
|
99
130
|
function reportedByUs(region) {
|
|
100
131
|
const handle = handleOf(region);
|
|
101
132
|
const remembered = handle ? announced.get(handle) : void 0;
|
|
102
|
-
return remembered !== void 0 && handle?.getAttribute("aria-valuemax") === String(remembered.max) && handle.getAttribute("aria-valuenow") === String(remembered.size) && region.hasAttribute(INSET_ATTRIBUTE) === remembered.inset;
|
|
133
|
+
return remembered !== void 0 && handle?.getAttribute("aria-valuemin") === String(remembered.min) && handle.getAttribute("aria-valuemax") === String(remembered.max) && handle.getAttribute("aria-valuenow") === String(remembered.size) && region.hasAttribute(INSET_ATTRIBUTE) === remembered.inset;
|
|
103
134
|
}
|
|
104
135
|
const observed = /* @__PURE__ */ new Set();
|
|
105
136
|
const resizeObserver = typeof ResizeObserver === "function" ? new ResizeObserver((entries) => {
|
|
@@ -107,10 +138,9 @@ function wireResizableRegions(root, {
|
|
|
107
138
|
for (const { target } of entries) {
|
|
108
139
|
if (target.matches(REGION_SELECTOR))
|
|
109
140
|
regions.add(target);
|
|
110
|
-
for (const child of target.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
regions.add(child);
|
|
141
|
+
for (const child of target.children)
|
|
142
|
+
if (child.matches(REGION_SELECTOR))
|
|
143
|
+
regions.add(child);
|
|
114
144
|
}
|
|
115
145
|
for (const region of regions) sync(region);
|
|
116
146
|
}) : void 0;
|
|
@@ -141,7 +171,7 @@ function wireResizableRegions(root, {
|
|
|
141
171
|
continue;
|
|
142
172
|
}
|
|
143
173
|
const target = record.target;
|
|
144
|
-
const region = target.matches(REGION_SELECTOR) ? target : target.matches(HANDLE_SELECTOR) ? target.
|
|
174
|
+
const region = target.matches(REGION_SELECTOR) ? target : target.matches(HANDLE_SELECTOR) ? target.parentElement?.matches(REGION_SELECTOR) ? target.parentElement : null : null;
|
|
145
175
|
if (region && !reportedByUs(region)) regions.add(region);
|
|
146
176
|
}
|
|
147
177
|
if (structural) observeRegions();
|
|
@@ -191,6 +221,7 @@ function wireResizableRegions(root, {
|
|
|
191
221
|
next = resizeRegionFromPointer(state.size, increment, state.edge);
|
|
192
222
|
if (next === void 0) return;
|
|
193
223
|
keyboardEvent.preventDefault();
|
|
224
|
+
if (pinned(state)) return;
|
|
194
225
|
const size = clampRegionSize(next, state.min, state.max);
|
|
195
226
|
applySize(state, size);
|
|
196
227
|
commit(state, { id: state.id, size, source: "keyboard" });
|
|
@@ -207,6 +238,7 @@ function wireResizableRegions(root, {
|
|
|
207
238
|
if (!state) return;
|
|
208
239
|
pointerEvent.preventDefault();
|
|
209
240
|
announce(state);
|
|
241
|
+
if (pinned(state)) return;
|
|
210
242
|
state.handle.setPointerCapture?.(pointerEvent.pointerId);
|
|
211
243
|
const startCoordinate = state.axis === "horizontal" ? pointerEvent.clientX : pointerEvent.clientY;
|
|
212
244
|
let next = state.size;
|
|
@@ -251,6 +283,6 @@ function wireResizableRegions(root, {
|
|
|
251
283
|
};
|
|
252
284
|
}
|
|
253
285
|
|
|
254
|
-
export {
|
|
286
|
+
export { wireResizeHandles };
|
|
255
287
|
|
|
256
|
-
//# sourceMappingURL=chunk-
|
|
288
|
+
//# sourceMappingURL=chunk-3CGQ7ZNO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/resize-wiring.ts"],"names":[],"mappings":"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","file":"chunk-3CGQ7ZNO.js","sourcesContent":["import { delegate } from 'kerfjs';\n\nimport {\n clampRegionSize,\n type ResizableRegionAxis,\n type ResizableRegionEdge,\n resizeRegionFromPointer,\n} from './resizable-region.js';\n\nexport interface ResizeCommit {\n id: string;\n size: number;\n source: 'keyboard' | 'pointer';\n}\n\nexport interface WireResizableRegionsOptions {\n step?: number;\n largeStep?: number;\n onPreview?: (change: ResizeCommit) => void;\n onCommit: (change: ResizeCommit) => void;\n}\n\ninterface RegionState {\n region: HTMLElement;\n handle: HTMLElement;\n id: string;\n axis: ResizableRegionAxis;\n edge: ResizableRegionEdge;\n /** The rendered `min` prop: every size the wiring commits stays at or above it. */\n min: number;\n /** The rendered `max` prop. */\n declaredMax: number;\n /** The largest size the region can actually show: `declaredMax`, or less when a parent clamps the track. */\n max: number;\n /** The shown size, which a parent clamp can hold below the rendered size. */\n size: number;\n /**\n * The size the track shows when its container squeezes it below `min` (a\n * narrowing Workbench shrinks its resizable rails), otherwise undefined.\n */\n squeezed?: number;\n}\n\n/**\n * A composition-specific bound on a region's size, applied after the parent\n * clamp: given the region and its limits, the largest size it may take. A\n * `Workbench` rail uses it to leave the work area its minimum width.\n */\nexport type ResizeLimit = (\n region: HTMLElement,\n limits: { min: number; max: number },\n) => number;\n\nconst HANDLE_SELECTOR = '[data-kui-resize-handle]';\nconst INSET_ATTRIBUTE = 'data-handle-inset';\n/** Attributes a re-render can write back over the reported state. */\nconst REPORTED_ATTRIBUTES = [\n 'aria-valuenow',\n 'aria-valuemin',\n 'aria-valuemax',\n INSET_ATTRIBUTE,\n];\n/**\n * How far the handle's hit target reaches past the separator (the CSS places\n * the 20px handle at `-10px` on the region's edge).\n */\nconst HANDLE_OVERHANG = 10;\n\n/**\n * The size the region renders or was last resized to. The track's custom\n * property is the source, not aria-valuenow, which reports the shown size and\n * can sit below the rendered size while a parent clamps the track.\n */\nfunction renderedSize(region: HTMLElement, handle: HTMLElement) {\n const size = Number.parseFloat(\n region.style.getPropertyValue('--kui-resizable-region-size'),\n );\n return Number.isFinite(size)\n ? size\n : Number(handle.getAttribute('aria-valuenow'));\n}\n\n/**\n * Whether the handle must sit inside the region. When a parent clamps the\n * track, the separator reaches the parent's edge, where a clipping ancestor\n * (an app shell, a scroll frame) would cut off the half of the hit target\n * and its focus ring that overhang the region. With less room past the\n * separator than that overhang, the region carries `data-handle-inset`, and\n * the CSS moves the handle and its ring inside.\n */\nfunction handleInset(state: RegionState, size: number) {\n return state.max < state.declaredMax && state.max - size < HANDLE_OVERHANG;\n}\n\nfunction setInset(region: HTMLElement, inset: boolean) {\n if (region.hasAttribute(INSET_ATTRIBUTE) !== inset)\n region.toggleAttribute(INSET_ATTRIBUTE, inset);\n}\n\nfunction setAttributeIfChanged(\n element: HTMLElement,\n name: string,\n value: string,\n) {\n if (element.getAttribute(name) !== value) element.setAttribute(name, value);\n}\n\n/**\n * The largest size a parent lets the region show. The track is\n * `size + edge extent` (its flex basis), but a parent may clamp it (a\n * `max-width: 100%` stage, a narrow container). Probing the track at the\n * declared maximum for one synchronous layout read measures how much of that\n * size is visible; the probe is restored before anything paints. Without\n * layout (no box yet, or an `auto` basis under a responsive fill) the declared\n * maximum stands.\n */\nfunction visibleMax(\n region: HTMLElement,\n axis: ResizableRegionAxis,\n min: number,\n max: number,\n) {\n const property = '--kui-resizable-region-size';\n const previous = region.style.getPropertyValue(property);\n region.style.setProperty(property, `${max}px`);\n const basis = Number.parseFloat(\n globalThis.getComputedStyle(region).flexBasis,\n );\n const box = region.getBoundingClientRect();\n region.style.setProperty(property, previous);\n const track = axis === 'horizontal' ? box.width : box.height;\n if (!Number.isFinite(basis) || track <= 0 || track >= basis - 0.5) return max;\n return Math.max(min, Math.min(max, Math.floor(track - (basis - max))));\n}\n\n/**\n * The size the track shows at its rendered size, when that is less than the\n * rendered size: a container that lets the region shrink (a narrowing\n * Workbench squeezes its resizable rails in proportion) can show it below its\n * own minimum. Undefined without layout or when the whole size shows.\n */\nfunction shownSize(\n region: HTMLElement,\n axis: ResizableRegionAxis,\n size: number,\n) {\n const basis = Number.parseFloat(\n globalThis.getComputedStyle(region).flexBasis,\n );\n const box = region.getBoundingClientRect();\n const track = axis === 'horizontal' ? box.width : box.height;\n if (!Number.isFinite(basis) || track <= 0 || track >= basis - 0.5)\n return undefined;\n return Math.max(0, Math.floor(track - (basis - size)));\n}\n\n/**\n * The separator's reported range and value. A squeezed region reports the\n * size it actually shows as its value and pins its minimum and maximum there:\n * WAI-ARIA requires the value to sit inside the range, and the separator\n * cannot move a track its container holds below the minimum. The configured\n * limits still bound every size the wiring commits.\n */\nfunction reported(state: RegionState) {\n const { squeezed } = state;\n return squeezed === undefined\n ? { min: state.min, max: state.max, now: state.size }\n : { min: squeezed, max: squeezed, now: squeezed };\n}\n\n/**\n * Whether the separator cannot move its region: the container squeezes the\n * track below its minimum, or the visible range has collapsed to a single\n * size. A pinned separator commits nothing, so a key press or drag never\n * overwrites the app's remembered size (and its storage) with the limit.\n */\nfunction pinned(state: RegionState) {\n return state.squeezed !== undefined || state.max <= state.min;\n}\n\n/**\n * Apply a live size to an expanded region before the app commits it. The\n * collapse-motion content keeps a fixed width from the expanded size so its\n * slide reads as a slide, not a squeeze; it has to follow the live size too, or\n * the content stays at the previous width until the app re-renders.\n */\nfunction applySize(state: RegionState, size: number) {\n const value = `${size}px`;\n state.region.style.setProperty('--kui-resizable-region-size', value);\n state.region.style.setProperty('--kui-resizable-region-expanded-size', value);\n // A squeezed region's container holds it where it is, so its reported\n // value stays the squeezed size.\n if (state.squeezed === undefined)\n state.handle.setAttribute('aria-valuenow', String(size));\n setInset(state.region, handleInset(state, size));\n}\n\n/**\n * Wire pointer and separator-keyboard behavior for every resizable region\n * below root that matches `regionSelector`. A region carries `data-region-id`,\n * `data-axis`, `data-edge`, `data-collapsed`, and `data-presentation`, sizes\n * its track from `--kui-resizable-region-size`, and owns its separator handle\n * as a direct child. `ResizableRegion` renders that contract, and so does a\n * resizable `Workbench` panel; each public wire passes the selector for the\n * regions it owns, so two wires never drive the same handle. An optional\n * `limit` tightens each region's maximum for its composition.\n */\nexport function wireResizeHandles(\n root: HTMLElement,\n {\n step = 16,\n largeStep = 64,\n onPreview,\n onCommit,\n }: WireResizableRegionsOptions,\n regionSelector: string,\n limit?: ResizeLimit,\n) {\n const REGION_SELECTOR = regionSelector;\n let stopPointer: (() => void) | undefined;\n // The handle announces its visible maximum in aria-valuemax, which hides the\n // rendered `max` prop. Remember it per handle; a re-render that writes a\n // different aria-valuemax than the one announced here supersedes it.\n const announced = new WeakMap<\n HTMLElement,\n {\n declaredMin: number;\n declaredMax: number;\n min: number;\n max: number;\n size: number;\n inset: boolean;\n }\n >();\n\n function regionState(handle: Element): RegionState | undefined {\n const region = handle.closest<HTMLElement>(REGION_SELECTOR);\n // A handle belongs only to the region it is a direct child of: a region of\n // another kind that merely contains it (a Workbench panel around a nested\n // ResizableRegion, or the reverse) is not the handle's region.\n if (\n !(region instanceof HTMLElement) ||\n !(handle instanceof HTMLElement) ||\n handle.parentElement !== region\n )\n return undefined;\n const id = region.dataset.regionId;\n const axis = region.dataset.axis;\n const edge = region.dataset.edge;\n const valueMin = Number(handle.getAttribute('aria-valuemin'));\n const valueMax = Number(handle.getAttribute('aria-valuemax'));\n const remembered = announced.get(handle);\n // A squeezed region reports a lowered minimum; the rendered one stands\n // until a re-render writes a different value.\n const min =\n remembered?.min === valueMin ? remembered.declaredMin : valueMin;\n const declaredMax =\n remembered?.max === valueMax ? remembered.declaredMax : valueMax;\n const size = renderedSize(region, handle);\n if (\n !id ||\n region.dataset.collapsed === 'true' ||\n region.dataset.presentation !== 'inline' ||\n (axis !== 'horizontal' && axis !== 'vertical') ||\n (edge !== 'start' && edge !== 'end') ||\n !Number.isFinite(min) ||\n !Number.isFinite(declaredMax) ||\n !Number.isFinite(size)\n )\n return undefined;\n // The shown size is read before the probe, from the layout already in\n // place, so the probe stays the one forced layout.\n const shown = shownSize(region, axis, size);\n const visible = visibleMax(region, axis, min, declaredMax);\n // A composition bound never takes a region below its own minimum.\n const max = limit\n ? Math.max(min, Math.min(visible, limit(region, { min, max: visible })))\n : visible;\n return {\n region,\n handle,\n id,\n axis,\n edge,\n min,\n declaredMax,\n max,\n size: clampRegionSize(size, min, max),\n squeezed: shown !== undefined && shown < min ? shown : undefined,\n };\n }\n\n /**\n * Report what is shown: the visible maximum and the shown size, or the\n * squeezed size a container holds the region at. Nothing is committed — the\n * app's size stays its own until the user resizes.\n */\n function announce(state: RegionState) {\n const inset = handleInset(state, state.size);\n const { min, max, now } = reported(state);\n announced.set(state.handle, {\n declaredMin: state.min,\n declaredMax: state.declaredMax,\n min,\n max,\n size: now,\n inset,\n });\n // Unchanged values are not rewritten, so the observer below never sees\n // its own report as a re-render.\n setAttributeIfChanged(state.handle, 'aria-valuemin', String(min));\n setAttributeIfChanged(state.handle, 'aria-valuemax', String(max));\n setAttributeIfChanged(state.handle, 'aria-valuenow', String(now));\n setInset(state.region, inset);\n }\n\n const handleOf = (region: HTMLElement) =>\n region.querySelector<HTMLElement>(`:scope > ${HANDLE_SELECTOR}`);\n\n /** Re-measure a region at rest and report what it shows. */\n function sync(region: HTMLElement) {\n if (!region.isConnected || region.dataset.resizing === 'true') return;\n const handle = handleOf(region);\n const state = handle ? regionState(handle) : undefined;\n if (state) announce(state);\n }\n\n /** Whether the region still shows the state this wiring last reported. */\n function reportedByUs(region: HTMLElement) {\n const handle = handleOf(region);\n const remembered = handle ? announced.get(handle) : undefined;\n return (\n remembered !== undefined &&\n handle?.getAttribute('aria-valuemin') === String(remembered.min) &&\n handle.getAttribute('aria-valuemax') === String(remembered.max) &&\n handle.getAttribute('aria-valuenow') === String(remembered.size) &&\n region.hasAttribute(INSET_ATTRIBUTE) === remembered.inset\n );\n }\n\n // Keep the reported values current without a focus or an interaction.\n // A ResizeObserver on every region and its parent re-measures when the\n // space changes (a narrowed viewport, a parent that grows back); a\n // MutationObserver catches a re-render that writes the rendered props back\n // over the reported values, and regions a re-render adds or removes. Both\n // measure once per affected region per batch, after layout or in a\n // microtask, never per frame.\n const observed = new Set<Element>();\n const resizeObserver =\n typeof ResizeObserver === 'function'\n ? new ResizeObserver((entries) => {\n const regions = new Set<HTMLElement>();\n for (const { target } of entries) {\n if (target.matches(REGION_SELECTOR))\n regions.add(target as HTMLElement);\n for (const child of target.children)\n if (child.matches(REGION_SELECTOR))\n regions.add(child as HTMLElement);\n }\n for (const region of regions) sync(region);\n })\n : undefined;\n\n /** Observe every region below root and its parent; release the rest. */\n function observeRegions() {\n if (!resizeObserver) return;\n const wanted = new Set<Element>();\n for (const region of root.querySelectorAll(REGION_SELECTOR)) {\n wanted.add(region);\n // A region found below root always has a parent element.\n wanted.add(region.parentElement as Element);\n }\n for (const element of observed)\n if (!wanted.has(element)) {\n resizeObserver.unobserve(element);\n observed.delete(element);\n }\n for (const element of wanted)\n if (!observed.has(element)) {\n resizeObserver.observe(element);\n observed.add(element);\n }\n }\n\n const mutationObserver = new MutationObserver((records) => {\n const regions = new Set<HTMLElement>();\n let structural = false;\n for (const record of records) {\n if (record.type === 'childList') {\n structural = true;\n continue;\n }\n const target = record.target as HTMLElement;\n const region = target.matches(REGION_SELECTOR)\n ? target\n : target.matches(HANDLE_SELECTOR)\n ? target.parentElement?.matches(REGION_SELECTOR)\n ? target.parentElement\n : null\n : null;\n if (region && !reportedByUs(region)) regions.add(region);\n }\n // Newly observed regions are measured by the ResizeObserver's first\n // notification.\n if (structural) observeRegions();\n for (const region of regions) sync(region);\n });\n mutationObserver.observe(root, {\n subtree: true,\n childList: true,\n attributes: true,\n attributeFilter: REPORTED_ATTRIBUTES,\n });\n observeRegions();\n\n /**\n * Commit a size, then re-announce: an app that re-renders the committed\n * size synchronously writes the rendered `max` prop back to aria-valuemax.\n */\n function commit(state: RegionState, change: ResizeCommit) {\n onCommit(change);\n const settled = state.handle.isConnected\n ? regionState(state.handle)\n : undefined;\n if (settled) announce(settled);\n }\n\n const stopFocus = delegate(\n root,\n 'focusin',\n HANDLE_SELECTOR,\n (_event, handle) => {\n const state = regionState(handle);\n if (state) announce(state);\n },\n );\n\n const stopKeydown = delegate(\n root,\n 'keydown',\n HANDLE_SELECTOR,\n (event, handle) => {\n const keyboardEvent = event as KeyboardEvent;\n const state = regionState(handle);\n if (!state) return;\n announce(state);\n let next: number | undefined;\n if (keyboardEvent.key === 'Home') next = state.min;\n if (keyboardEvent.key === 'End') next = state.max;\n const increment = keyboardEvent.shiftKey ? largeStep : step;\n if (state.axis === 'horizontal' && keyboardEvent.key === 'ArrowLeft')\n next = resizeRegionFromPointer(state.size, -increment, state.edge);\n if (state.axis === 'horizontal' && keyboardEvent.key === 'ArrowRight')\n next = resizeRegionFromPointer(state.size, increment, state.edge);\n if (state.axis === 'vertical' && keyboardEvent.key === 'ArrowUp')\n next = resizeRegionFromPointer(state.size, -increment, state.edge);\n if (state.axis === 'vertical' && keyboardEvent.key === 'ArrowDown')\n next = resizeRegionFromPointer(state.size, increment, state.edge);\n if (next === undefined) return;\n keyboardEvent.preventDefault();\n if (pinned(state)) return;\n const size = clampRegionSize(next, state.min, state.max);\n applySize(state, size);\n commit(state, { id: state.id, size, source: 'keyboard' });\n },\n );\n\n const stopPointerDown = delegate(\n root,\n 'pointerdown',\n HANDLE_SELECTOR,\n (event, handle) => {\n const pointerEvent = event as PointerEvent;\n if (pointerEvent.button !== 0) return;\n const state = regionState(handle);\n if (!state) return;\n pointerEvent.preventDefault();\n announce(state);\n if (pinned(state)) return;\n state.handle.setPointerCapture?.(pointerEvent.pointerId);\n const startCoordinate =\n state.axis === 'horizontal'\n ? pointerEvent.clientX\n : pointerEvent.clientY;\n let next = state.size;\n const move = (moveEvent: PointerEvent) => {\n const coordinate =\n state.axis === 'horizontal' ? moveEvent.clientX : moveEvent.clientY;\n next = clampRegionSize(\n resizeRegionFromPointer(\n state.size,\n coordinate - startCoordinate,\n state.edge,\n ),\n state.min,\n state.max,\n );\n applySize(state, next);\n onPreview?.({ id: state.id, size: next, source: 'pointer' });\n };\n const finish = () => {\n root.removeEventListener('pointermove', move);\n root.removeEventListener('pointerup', finish);\n root.removeEventListener('pointercancel', finish);\n stopPointer = undefined;\n delete state.region.dataset.resizing;\n commit(state, { id: state.id, size: next, source: 'pointer' });\n };\n stopPointer?.();\n stopPointer = finish;\n state.region.dataset.resizing = 'true';\n root.addEventListener('pointermove', move);\n root.addEventListener('pointerup', finish, { once: true });\n root.addEventListener('pointercancel', finish, { once: true });\n },\n );\n\n return () => {\n mutationObserver.disconnect();\n resizeObserver?.disconnect();\n observed.clear();\n stopPointer?.();\n stopFocus();\n stopKeydown();\n stopPointerDown();\n };\n}\n"]}
|