@rtzll/reflow 0.1.0 → 0.3.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/README.md +1 -1
- package/dist/basic.js +30 -1
- package/dist/breadcrumb.css +165 -0
- package/dist/breadcrumb.d.ts +30 -0
- package/dist/breadcrumb.js +164 -0
- package/dist/color-picker.css +29 -0
- package/dist/color-picker.js +90 -11
- package/dist/compositions.css +27 -7
- package/dist/compositions.js +67 -2
- package/dist/dates.css +108 -3
- package/dist/dates.js +16 -5
- package/dist/flows.css +69 -1
- package/dist/groups.css +34 -0
- package/dist/groups.js +9 -1
- package/dist/icon-data.d.ts +26 -0
- package/dist/icon-data.js +9 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/interactive.d.ts +9 -1
- package/dist/interactive.js +113 -9
- package/dist/selection-runs.d.ts +4 -0
- package/dist/selection-runs.js +221 -0
- package/dist/sidebar.css +8 -0
- package/dist/styles.css +1 -0
- package/package.json +1 -1
package/dist/interactive.js
CHANGED
|
@@ -250,7 +250,86 @@ export function Tabs(handle) {
|
|
|
250
250
|
], children: [_jsx("span", { class: "rf-highlight rf-tab-selection", "aria-hidden": "true", "data-rf-selection-layer": true }), _jsx("span", { class: "rf-highlight rf-tab-hover", "aria-hidden": "true", "data-rf-hover-layer": true }), _jsx("span", { class: "rf-highlight rf-focus-ring", "aria-hidden": "true", "data-rf-focus-layer": true }), items.map((item) => (_jsxs("button", { class: "rf-tab", type: "button", "aria-label": item.label, "data-rf-hover-item": true, "data-active-label": activeLabel && item.icon != null ? '' : undefined, mix: tabs.tab({ name: item.value, disabled: item.disabled }), children: [item.icon != null && (_jsx("span", { class: "rf-tab-icon", "aria-hidden": "true", children: item.icon })), _jsx("span", { class: "rf-tab-label", "data-label": item.label, children: _jsx("span", { children: item.label }) })] }, item.value)))] }), items.map((item) => (_jsx("div", { class: "rf-tab-panel", tabIndex: item.panel != null ? 0 : undefined, mix: tabs.panel({ name: item.value }), children: item.panel }, item.value)))] }) }));
|
|
251
251
|
};
|
|
252
252
|
}
|
|
253
|
+
/**
|
|
254
|
+
* One timer shared by a hover-opening trigger and its menu surface, as in the
|
|
255
|
+
* reference. A menu opened by hover closes when the pointer leaves both; one
|
|
256
|
+
* opened by a click or the keyboard, or pinned by a click, stays until it is
|
|
257
|
+
* dismissed as usual. A hover open is a preview, so it leaves focus in place.
|
|
258
|
+
*/
|
|
259
|
+
function createHoverControl(signal) {
|
|
260
|
+
let timer;
|
|
261
|
+
let menu;
|
|
262
|
+
// True while the open menu is one the hover timer opened.
|
|
263
|
+
let hoverOpened = false;
|
|
264
|
+
const cancel = () => clearTimeout(timer);
|
|
265
|
+
signal.addEventListener('abort', cancel, { once: true });
|
|
266
|
+
return {
|
|
267
|
+
attach(next) {
|
|
268
|
+
menu = next;
|
|
269
|
+
},
|
|
270
|
+
cancel,
|
|
271
|
+
enter(delay) {
|
|
272
|
+
cancel();
|
|
273
|
+
const open = () => {
|
|
274
|
+
// A menu already open from a click or a pin stays the reader's.
|
|
275
|
+
if (!menu || menu.isOpen)
|
|
276
|
+
return;
|
|
277
|
+
// The menu ignores opens while a close is still settling; keep
|
|
278
|
+
// asking while the pointer rests on the trigger.
|
|
279
|
+
if (menu.state !== 'idle') {
|
|
280
|
+
timer = setTimeout(open, 16);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
hoverOpened = true;
|
|
284
|
+
void menu.open({ strategy: 'none' });
|
|
285
|
+
};
|
|
286
|
+
timer = setTimeout(open, delay);
|
|
287
|
+
},
|
|
288
|
+
leave(closeDelay) {
|
|
289
|
+
cancel();
|
|
290
|
+
timer = setTimeout(() => {
|
|
291
|
+
if (!menu || !hoverOpened)
|
|
292
|
+
return;
|
|
293
|
+
hoverOpened = false;
|
|
294
|
+
if (menu.isOpen)
|
|
295
|
+
void menu.closeAll({ focusRoot: false });
|
|
296
|
+
}, closeDelay);
|
|
297
|
+
},
|
|
298
|
+
/** A click on a hover-opened trigger pins the menu instead of closing it. Returns true when it pinned. */
|
|
299
|
+
pin() {
|
|
300
|
+
cancel();
|
|
301
|
+
const pinned = hoverOpened && !!menu?.isOpen;
|
|
302
|
+
hoverOpened = false;
|
|
303
|
+
return pinned;
|
|
304
|
+
},
|
|
305
|
+
/** Any other open (click, keyboard) belongs to the reader. */
|
|
306
|
+
release() {
|
|
307
|
+
hoverOpened = false;
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
function hoverTrigger(hover, delay, closeDelay) {
|
|
312
|
+
return [
|
|
313
|
+
on('pointerenter', (event) => {
|
|
314
|
+
if (event.pointerType === 'mouse')
|
|
315
|
+
hover.enter(delay);
|
|
316
|
+
}),
|
|
317
|
+
on('pointerleave', (event) => {
|
|
318
|
+
if (event.pointerType === 'mouse')
|
|
319
|
+
hover.leave(closeDelay);
|
|
320
|
+
}),
|
|
321
|
+
// Capture runs before the menu trigger's own click toggle.
|
|
322
|
+
on('click', (event) => {
|
|
323
|
+
if (!hover.pin())
|
|
324
|
+
return;
|
|
325
|
+
event.preventDefault();
|
|
326
|
+
event.stopImmediatePropagation();
|
|
327
|
+
}, true),
|
|
328
|
+
on('keydown', () => hover.release()),
|
|
329
|
+
];
|
|
330
|
+
}
|
|
253
331
|
export function Dropdown(handle) {
|
|
332
|
+
const hover = createHoverControl(handle.signal);
|
|
254
333
|
let query = '';
|
|
255
334
|
let created = [];
|
|
256
335
|
const createValue = `${handle.id}-create`;
|
|
@@ -261,7 +340,9 @@ export function Dropdown(handle) {
|
|
|
261
340
|
void handle.update();
|
|
262
341
|
}
|
|
263
342
|
return () => {
|
|
264
|
-
const { label, items, size, align = 'start', variant = 'outline', searchable, searchPlaceholder = 'Search…', } = handle.props;
|
|
343
|
+
const { label, items, size, align = 'start', variant = 'outline', searchable, searchPlaceholder = 'Search…', delay = 100, closeDelay = 150, } = handle.props;
|
|
344
|
+
const hovering = !!handle.props.openOnHover && !handle.props.inline;
|
|
345
|
+
const hoverMix = hovering ? hoverTrigger(hover, delay, closeDelay) : undefined;
|
|
265
346
|
const allItems = [
|
|
266
347
|
...items,
|
|
267
348
|
...created.filter((entry) => !items.some((item) => item.value === entry.value)),
|
|
@@ -289,13 +370,21 @@ export function Dropdown(handle) {
|
|
|
289
370
|
return;
|
|
290
371
|
}
|
|
291
372
|
handle.props.onSelect?.(event.item.value ?? event.item.name, event.item.checked);
|
|
292
|
-
}), children: _jsxs(menu.Context, { label: label, children: [!handle.props.inline && variant === 'bare' && (_jsx("button", { type: "button", "aria-label": label, class: `rf-dropdown-trigger ${handle.props.triggerClass ?? ''}`, mix:
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
373
|
+
}), children: _jsxs(menu.Context, { label: label, children: [!handle.props.inline && variant === 'bare' && (_jsx("button", { type: "button", "aria-label": label, class: `rf-dropdown-trigger ${handle.props.triggerClass ?? ''}`, mix: [
|
|
374
|
+
menu.trigger({
|
|
375
|
+
placement: `${handle.props.side ?? 'bottom'}-${align}`,
|
|
376
|
+
offset: 6,
|
|
377
|
+
}),
|
|
378
|
+
hoverMix,
|
|
379
|
+
handle.props.triggerMix,
|
|
380
|
+
], children: handle.props.triggerContent ?? label })), !handle.props.inline && variant !== 'bare' && (_jsx(Button, { class: "rf-dropdown-trigger", variant: variant === 'ghost' ? 'ghost' : 'tertiary', size: size, trailingIcon: handle.props.chevron === false ? undefined : (_jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: _jsx("path", { d: "m4 6 4 4 4-4", stroke: "currentColor", "stroke-linecap": "round", "stroke-linejoin": "round" }) })), mix: [
|
|
381
|
+
menu.trigger({
|
|
382
|
+
placement: `${handle.props.side ?? 'bottom'}-${align}`,
|
|
383
|
+
offset: 6,
|
|
384
|
+
}),
|
|
385
|
+
hoverMix,
|
|
386
|
+
handle.props.triggerMix,
|
|
387
|
+
], children: handle.props.triggerContent ?? label })), _jsx(DropdownSurface, { inline: handle.props.inline, hover: hovering ? { control: hover, closeDelay } : undefined, items: displayedItems, emptyMessage: handle.props.emptyMessage, search: searchable || handle.props.creatable
|
|
299
388
|
? { query, setQuery, placeholder: searchPlaceholder }
|
|
300
389
|
: undefined })] }) }));
|
|
301
390
|
};
|
|
@@ -306,6 +395,7 @@ function matchesMenuQuery(item, query) {
|
|
|
306
395
|
}
|
|
307
396
|
function DropdownSurface(handle) {
|
|
308
397
|
const context = handle.context.get(menu.Context);
|
|
398
|
+
handle.props.hover?.control.attach(context);
|
|
309
399
|
const highlight = fluidHover({ highlightedSelector: '[data-highlighted]' });
|
|
310
400
|
const checked = new Map();
|
|
311
401
|
// Remix UI 0.13.1 unconditionally closes after its activation path. Keep
|
|
@@ -361,6 +451,20 @@ function DropdownSurface(handle) {
|
|
|
361
451
|
: handle.props.items;
|
|
362
452
|
return (_jsxs("div", { class: "rf-menu-surface rf-dropdown-surface", "data-inline": handle.props.inline || undefined, mix: [
|
|
363
453
|
!handle.props.inline && menu.popover(),
|
|
454
|
+
// A hover-opened menu stays while the pointer is over it, submenus
|
|
455
|
+
// included (they render inside this surface), and closes once the
|
|
456
|
+
// pointer leaves both the menu and its trigger.
|
|
457
|
+
handle.props.hover && [
|
|
458
|
+
on('pointerenter', (event) => {
|
|
459
|
+
if (event.pointerType === 'mouse')
|
|
460
|
+
handle.props.hover?.control.cancel();
|
|
461
|
+
}),
|
|
462
|
+
on('pointerleave', (event) => {
|
|
463
|
+
const hover = handle.props.hover;
|
|
464
|
+
if (event.pointerType === 'mouse' && hover)
|
|
465
|
+
hover.control.leave(hover.closeDelay);
|
|
466
|
+
}),
|
|
467
|
+
],
|
|
364
468
|
search &&
|
|
365
469
|
on('toggle', (event) => {
|
|
366
470
|
if (event.newState === 'open')
|
|
@@ -415,7 +519,7 @@ function DropdownSurface(handle) {
|
|
|
415
519
|
? search.query.slice(0, -1)
|
|
416
520
|
: search.query + event.key);
|
|
417
521
|
}, true),
|
|
418
|
-
], children: [_jsx("span", { class: "rf-highlight rf-menu-hover", "aria-hidden": "true", "data-rf-hover-layer": true }),
|
|
522
|
+
], children: [_jsx("span", { class: "rf-highlight rf-menu-hover", "aria-hidden": "true", "data-rf-hover-layer": true }), _jsx("span", { class: "rf-highlight rf-focus-ring", "aria-hidden": "true", "data-rf-focus-layer": true }), items.length === 0 && search && (_jsx("div", { class: "rf-menu-empty", role: "status", children: handle.props.emptyMessage ?? 'No results' })), items.map((item, index) => (_jsxs("div", { role: "none", class: "rf-menu-entry", children: [item.separatorBefore && (index > 0 || !normalized) && (_jsx("div", { class: "rf-menu-separator", role: "separator" })), item.group && item.group !== items[index - 1]?.group && (_jsx("div", { class: "rf-menu-group-label", children: item.group })), item.children && item.children.length > 0 ? (_jsxs(menu.Context, { label: item.label, children: [_jsx("div", { class: "rf-menu-item", "data-rf-hover-item": true, mix: menu.submenuTrigger({
|
|
419
523
|
value: item.value,
|
|
420
524
|
label: item.label,
|
|
421
525
|
disabled: item.disabled,
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { ref } from 'remix/component';
|
|
2
|
+
// The reference's merged-background timing: the inner corners trail the edges
|
|
3
|
+
// by 70ms, and a merge commits once the halves have met.
|
|
4
|
+
const cornerDelay = 70;
|
|
5
|
+
const springMs = 200;
|
|
6
|
+
const convergeMs = springMs + cornerDelay + 80;
|
|
7
|
+
/** Groups sorted row indices into runs, keeping each run's id while any of its rows had one. */
|
|
8
|
+
function assignRuns(sorted, previous, last) {
|
|
9
|
+
const spans = [];
|
|
10
|
+
for (const index of sorted) {
|
|
11
|
+
const span = spans[spans.length - 1];
|
|
12
|
+
if (span && index === span.end + 1)
|
|
13
|
+
span.end = index;
|
|
14
|
+
else
|
|
15
|
+
spans.push({ start: index, end: index });
|
|
16
|
+
}
|
|
17
|
+
const used = new Set();
|
|
18
|
+
const byRow = new Map();
|
|
19
|
+
const runs = spans.map((span) => {
|
|
20
|
+
let id;
|
|
21
|
+
for (let i = span.start; i <= span.end && id === undefined; i++) {
|
|
22
|
+
const prior = previous.get(i);
|
|
23
|
+
if (prior !== undefined && !used.has(prior))
|
|
24
|
+
id = prior;
|
|
25
|
+
}
|
|
26
|
+
id ??= ++last.id;
|
|
27
|
+
used.add(id);
|
|
28
|
+
for (let i = span.start; i <= span.end; i++)
|
|
29
|
+
byRow.set(i, id);
|
|
30
|
+
return { ...span, id };
|
|
31
|
+
});
|
|
32
|
+
return { runs, byRow };
|
|
33
|
+
}
|
|
34
|
+
/** Two runs inside `outer`, separated by exactly one row: the shape one click merges or splits. */
|
|
35
|
+
function bridge(outer, runs) {
|
|
36
|
+
const inside = runs
|
|
37
|
+
.filter((run) => run.start >= outer.start && run.end <= outer.end)
|
|
38
|
+
.sort((a, b) => a.start - b.start);
|
|
39
|
+
if (inside.length !== 2)
|
|
40
|
+
return null;
|
|
41
|
+
const [up, low] = inside;
|
|
42
|
+
return low.start === up.end + 2 ? { up, low, gap: up.end + 1 } : null;
|
|
43
|
+
}
|
|
44
|
+
export function selectionRuns() {
|
|
45
|
+
let host = null;
|
|
46
|
+
let layer = null;
|
|
47
|
+
let runs = [];
|
|
48
|
+
let byRow = new Map();
|
|
49
|
+
const last = { id: 0 };
|
|
50
|
+
const blocks = new Map();
|
|
51
|
+
// Halves absorbed by a merge, dropped when the merge commits.
|
|
52
|
+
const ghosts = new Set();
|
|
53
|
+
let commit;
|
|
54
|
+
let frame = 0;
|
|
55
|
+
function rowBoxes(rows) {
|
|
56
|
+
const parent = host.getBoundingClientRect();
|
|
57
|
+
// Translate back into local CSS pixels when a preview scales its contents.
|
|
58
|
+
const scaleX = parent.width / host.offsetWidth || 1;
|
|
59
|
+
const scaleY = parent.height / host.offsetHeight || 1;
|
|
60
|
+
return rows.map((row) => {
|
|
61
|
+
const rect = row.getBoundingClientRect();
|
|
62
|
+
return {
|
|
63
|
+
top: (rect.top - parent.top) / scaleY - host.clientTop,
|
|
64
|
+
left: (rect.left - parent.left) / scaleX - host.clientLeft,
|
|
65
|
+
width: rect.width / scaleX,
|
|
66
|
+
height: rect.height / scaleY,
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
function paint(block, box, radii, { instant = false, delayCorners = false } = {}) {
|
|
71
|
+
block.toggleAttribute('data-instant', instant);
|
|
72
|
+
block.style.setProperty('--rf-corner-delay', delayCorners ? `${cornerDelay}ms` : '0ms');
|
|
73
|
+
block.style.top = `${box.top}px`;
|
|
74
|
+
block.style.left = `${box.left}px`;
|
|
75
|
+
block.style.width = `${box.width}px`;
|
|
76
|
+
block.style.height = `${box.height}px`;
|
|
77
|
+
block.style.borderRadius = radii.map((r) => `${r}px`).join(' ');
|
|
78
|
+
if (instant)
|
|
79
|
+
void block.offsetWidth;
|
|
80
|
+
}
|
|
81
|
+
function create() {
|
|
82
|
+
const block = host.ownerDocument.createElement('span');
|
|
83
|
+
block.className = 'rf-choice-run';
|
|
84
|
+
block.setAttribute('data-instant', '');
|
|
85
|
+
layer.append(block);
|
|
86
|
+
return block;
|
|
87
|
+
}
|
|
88
|
+
function drop(block, instant) {
|
|
89
|
+
ghosts.delete(block);
|
|
90
|
+
if (instant) {
|
|
91
|
+
block.remove();
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
block.setAttribute('data-leaving', '');
|
|
95
|
+
setTimeout(() => block.remove(), 200);
|
|
96
|
+
}
|
|
97
|
+
function sync() {
|
|
98
|
+
frame = 0;
|
|
99
|
+
if (!host || !layer)
|
|
100
|
+
return;
|
|
101
|
+
const rows = Array.from(host.querySelectorAll(':scope > .rf-choice-group-row'));
|
|
102
|
+
const checked = rows.flatMap((row, index) => row.querySelector('input:checked') ? [index] : []);
|
|
103
|
+
const previous = runs;
|
|
104
|
+
const next = assignRuns(checked, byRow, last);
|
|
105
|
+
runs = next.runs;
|
|
106
|
+
byRow = next.byRow;
|
|
107
|
+
const boxes = rowBoxes(rows);
|
|
108
|
+
const radius = parseFloat(getComputedStyle(rows[0] ?? host).borderTopLeftRadius) || 0;
|
|
109
|
+
const full = [radius, radius, radius, radius];
|
|
110
|
+
const span = (run) => {
|
|
111
|
+
const start = boxes[run.start];
|
|
112
|
+
const end = boxes[run.end];
|
|
113
|
+
return {
|
|
114
|
+
top: start.top,
|
|
115
|
+
left: Math.min(start.left, end.left),
|
|
116
|
+
width: Math.max(start.width, end.width),
|
|
117
|
+
height: end.top + end.height - start.top,
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
// A merge in flight that this change undid leaves its ghost behind.
|
|
121
|
+
clearTimeout(commit);
|
|
122
|
+
for (const ghost of ghosts)
|
|
123
|
+
drop(ghost, true);
|
|
124
|
+
// Runs that are gone fade out, unless a merge absorbs them below.
|
|
125
|
+
const live = new Set(runs.map((run) => run.id));
|
|
126
|
+
const merges = runs.flatMap((run) => {
|
|
127
|
+
const pair = bridge(run, previous);
|
|
128
|
+
return pair && (pair.up.id === run.id || pair.low.id === run.id) ? [{ run, ...pair }] : [];
|
|
129
|
+
});
|
|
130
|
+
const splits = previous.flatMap((run) => {
|
|
131
|
+
const pair = bridge(run, runs);
|
|
132
|
+
return pair ? [{ run, ...pair }] : [];
|
|
133
|
+
});
|
|
134
|
+
const absorbed = new Set(merges.map(({ run, up, low }) => (run.id === up.id ? low.id : up.id)));
|
|
135
|
+
// Blocks of runs a merge absorbed, kept to glide in as the lower half.
|
|
136
|
+
const retired = new Map();
|
|
137
|
+
for (const [id, block] of blocks) {
|
|
138
|
+
if (live.has(id))
|
|
139
|
+
continue;
|
|
140
|
+
blocks.delete(id);
|
|
141
|
+
if (absorbed.has(id))
|
|
142
|
+
retired.set(id, block);
|
|
143
|
+
else
|
|
144
|
+
drop(block, false);
|
|
145
|
+
}
|
|
146
|
+
for (const run of runs) {
|
|
147
|
+
const fresh = !blocks.has(run.id);
|
|
148
|
+
const block = blocks.get(run.id) ?? create();
|
|
149
|
+
blocks.set(run.id, block);
|
|
150
|
+
if (fresh)
|
|
151
|
+
paint(block, span(run), full, { instant: true });
|
|
152
|
+
else
|
|
153
|
+
paint(block, span(run), full);
|
|
154
|
+
}
|
|
155
|
+
for (const { run, up, low, gap } of merges) {
|
|
156
|
+
const otherId = run.id === up.id ? low.id : up.id;
|
|
157
|
+
const ghost = retired.get(otherId);
|
|
158
|
+
const survivor = blocks.get(run.id);
|
|
159
|
+
const box = span(run);
|
|
160
|
+
const middle = boxes[gap].top + boxes[gap].height / 2;
|
|
161
|
+
// The halves glide to the middle of the bridge row, inner corners straightening.
|
|
162
|
+
paint(survivor, { ...box, height: middle - box.top }, [radius, radius, 0, 0], {
|
|
163
|
+
delayCorners: true,
|
|
164
|
+
});
|
|
165
|
+
if (ghost) {
|
|
166
|
+
ghosts.add(ghost);
|
|
167
|
+
paint(ghost, { ...box, top: middle, height: box.top + box.height - middle }, [0, 0, radius, radius], { delayCorners: true });
|
|
168
|
+
}
|
|
169
|
+
commit = setTimeout(() => {
|
|
170
|
+
// One block over the whole run, swapped in without visible motion.
|
|
171
|
+
paint(survivor, span(run), full, { instant: true });
|
|
172
|
+
for (const ghost of ghosts)
|
|
173
|
+
drop(ghost, true);
|
|
174
|
+
}, convergeMs);
|
|
175
|
+
}
|
|
176
|
+
for (const { run, up, low, gap } of splits) {
|
|
177
|
+
const upper = blocks.get(up.id);
|
|
178
|
+
const lower = blocks.get(low.id);
|
|
179
|
+
if (!upper || !lower)
|
|
180
|
+
continue;
|
|
181
|
+
const box = span(run);
|
|
182
|
+
const middle = boxes[gap].top + boxes[gap].height / 2;
|
|
183
|
+
// Snap into two abutting halves at the seam, then glide apart.
|
|
184
|
+
paint(upper, { ...box, height: middle - box.top }, [radius, radius, 0, 0], {
|
|
185
|
+
instant: true,
|
|
186
|
+
});
|
|
187
|
+
paint(lower, { ...box, top: middle, height: box.top + box.height - middle }, [0, 0, radius, radius], { instant: true });
|
|
188
|
+
paint(upper, span(up), full);
|
|
189
|
+
paint(lower, span(low), full);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function schedule() {
|
|
193
|
+
if (!frame)
|
|
194
|
+
frame = requestAnimationFrame(sync);
|
|
195
|
+
}
|
|
196
|
+
const mix = ref((element, signal) => {
|
|
197
|
+
if (!(element instanceof HTMLElement))
|
|
198
|
+
return;
|
|
199
|
+
host = element;
|
|
200
|
+
layer = element.querySelector(':scope > [data-rf-selection-runs]');
|
|
201
|
+
if (!layer)
|
|
202
|
+
return;
|
|
203
|
+
host.setAttribute('data-rf-selection-blocks', '');
|
|
204
|
+
const size = new ResizeObserver(schedule);
|
|
205
|
+
size.observe(element);
|
|
206
|
+
element.addEventListener('change', () => sync(), { signal });
|
|
207
|
+
element.closest('form')?.addEventListener('reset', () => setTimeout(sync), { signal });
|
|
208
|
+
sync();
|
|
209
|
+
signal.addEventListener('abort', () => {
|
|
210
|
+
size.disconnect();
|
|
211
|
+
cancelAnimationFrame(frame);
|
|
212
|
+
clearTimeout(commit);
|
|
213
|
+
if (host === element) {
|
|
214
|
+
host.removeAttribute('data-rf-selection-blocks');
|
|
215
|
+
host = null;
|
|
216
|
+
layer = null;
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
return { mix, sync: schedule };
|
|
221
|
+
}
|
package/dist/sidebar.css
CHANGED
|
@@ -184,6 +184,14 @@
|
|
|
184
184
|
}
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
|
+
/* These menus mark keyboard focus with the row highlight, not the moving ring. */
|
|
188
|
+
:is(.rf-sidebar-header, .rf-sidebar-footer)
|
|
189
|
+
.rf-sidebar-row
|
|
190
|
+
+ .rf-dropdown-surface
|
|
191
|
+
> .rf-menu-list
|
|
192
|
+
> .rf-focus-ring {
|
|
193
|
+
display: none;
|
|
194
|
+
}
|
|
187
195
|
.rf-sidebar-row {
|
|
188
196
|
position: relative;
|
|
189
197
|
display: flex;
|
package/dist/styles.css
CHANGED