@flood-md/react 0.2.3 → 0.2.4
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 +308 -3
- package/dist/avatar.d.ts +5 -0
- package/dist/avatar.js +8 -0
- package/dist/checkbox.d.ts +4 -0
- package/dist/checkbox.js +15 -0
- package/dist/data-display.d.ts +3 -0
- package/dist/data-display.js +11 -0
- package/dist/disclosure.js +2 -1
- package/dist/feedback.d.ts +8 -0
- package/dist/feedback.js +29 -0
- package/dist/focusable-hidden.d.ts +3 -0
- package/dist/focusable-hidden.js +99 -0
- package/dist/fonts/InterVariable-Italic.woff2 +0 -0
- package/dist/fonts/README.md +9 -5
- package/dist/foundations.css +196 -12
- package/dist/index.d.ts +191 -2
- package/dist/index.js +145 -36
- package/dist/menu.d.ts +2 -0
- package/dist/menu.js +130 -0
- package/dist/navigation.js +8 -8
- package/dist/overlay-runtime.js +10 -4
- package/dist/overlays.js +12 -3
- package/dist/scroll-area.js +17 -2
- package/dist/select.js +96 -47
- package/dist/styles.css +9 -0
- package/dist/toolbar.d.ts +2 -0
- package/dist/toolbar.js +80 -0
- package/package.json +2 -2
package/dist/select.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { resolveFieldStatus } from "./field-state.js";
|
|
3
3
|
import { Children, Fragment, forwardRef, isValidElement, useEffect, useId, useImperativeHandle, useLayoutEffect, useRef, useState, } from "react";
|
|
4
|
-
import {
|
|
4
|
+
import { ScrollArea } from "./index.js";
|
|
5
|
+
import { positionLayer, registerLayer, returnFocus } from "./overlay-runtime.js";
|
|
5
6
|
const useBrowserLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
6
7
|
const text = (children) => Children.toArray(children).map((child) => typeof child === "string" || typeof child === "number" ? String(child) : isValidElement(child) ? text(child.props.children) : "").join("");
|
|
7
8
|
function initialChoices(children, group, groupDisabled = false, groupHidden = false) {
|
|
@@ -28,8 +29,9 @@ export const SingleSelect = forwardRef(function SingleSelect({ children, value,
|
|
|
28
29
|
const [choices, setChoices] = useState(() => initialChoices(children));
|
|
29
30
|
const [selected, setSelected] = useState(() => String(defaultValue ?? initialChoices(children).find((choice) => !choice.disabled && !choice.hidden)?.value ?? ""));
|
|
30
31
|
const [open, setOpen] = useState(false), [active, setActive] = useState(-1);
|
|
32
|
+
const activeIndex = useRef(active);
|
|
33
|
+
activeIndex.current = active;
|
|
31
34
|
const [validation, setValidation] = useState("");
|
|
32
|
-
const [position, setPosition] = useState({ left: 0, top: 0, width: 0, maxHeight: 0, viewportHeight: 0 });
|
|
33
35
|
const controlled = value !== undefined;
|
|
34
36
|
const currentValue = controlled ? String(value) : selected;
|
|
35
37
|
const current = useRef({ controlled, value: currentValue });
|
|
@@ -152,54 +154,101 @@ export const SingleSelect = forwardRef(function SingleSelect({ children, value,
|
|
|
152
154
|
}
|
|
153
155
|
}
|
|
154
156
|
}
|
|
157
|
+
function revealOption(index) {
|
|
158
|
+
const option = menu.current?.querySelector(`[data-option-index="${index}"]`), viewport = scrollViewport.current;
|
|
159
|
+
if (!option || !viewport)
|
|
160
|
+
return;
|
|
161
|
+
const item = option.getBoundingClientRect(), bounds = viewport.getBoundingClientRect();
|
|
162
|
+
if (item.top < bounds.top)
|
|
163
|
+
viewport.scrollTop -= bounds.top - item.top;
|
|
164
|
+
else if (item.bottom > bounds.bottom)
|
|
165
|
+
viewport.scrollTop += item.bottom - bounds.bottom;
|
|
166
|
+
}
|
|
155
167
|
useBrowserLayoutEffect(() => {
|
|
156
168
|
if (!open || !menu.current || !trigger.current)
|
|
157
169
|
return;
|
|
158
|
-
const popup = menu.current, button = trigger.current;
|
|
159
|
-
|
|
170
|
+
const popup = menu.current, button = trigger.current, document = popup.ownerDocument, view = document.defaultView;
|
|
171
|
+
let alive = true, frame = 0, lastViewportHeight = -1, unregister = () => { };
|
|
172
|
+
const assign = (element, property, value) => {
|
|
173
|
+
if (element.style.getPropertyValue(property) !== value)
|
|
174
|
+
element.style.setProperty(property, value);
|
|
175
|
+
};
|
|
176
|
+
const update = () => {
|
|
177
|
+
if (!alive)
|
|
178
|
+
return;
|
|
179
|
+
if (!button.isConnected || !button.getClientRects().length) {
|
|
180
|
+
setOpen(false);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const rect = button.getBoundingClientRect(), visual = view.visualViewport;
|
|
184
|
+
const viewportTop = visual?.offsetTop ?? 0, viewportWidth = visual?.width ?? view.innerWidth, viewportHeight = visual?.height ?? view.innerHeight;
|
|
185
|
+
const rootSize = parseFloat(view.getComputedStyle(document.documentElement).fontSize);
|
|
186
|
+
const length = (name) => { const raw = view.getComputedStyle(button).getPropertyValue(name).trim(); return parseFloat(raw) * (raw.endsWith("rem") ? rootSize : 1); };
|
|
187
|
+
const gap = length("--flood-sys-space-2") || 0;
|
|
188
|
+
const below = Math.max(0, viewportTop + viewportHeight - rect.bottom - gap * 2), above = Math.max(0, rect.top - viewportTop - gap * 2);
|
|
189
|
+
const maxHeight = Math.min(length("--flood-sys-control-md-height") * 8, Math.max(below, above));
|
|
190
|
+
assign(popup, "width", `${Math.min(rect.width, Math.max(0, viewportWidth - gap * 2))}px`);
|
|
191
|
+
assign(popup, "max-height", `${maxHeight}px`);
|
|
192
|
+
// The shared positioner reads column-gap as its placement gap. On this
|
|
193
|
+
// block listbox it does not add layout space between options.
|
|
194
|
+
assign(popup, "column-gap", "var(--flood-sys-space-2)");
|
|
195
|
+
const css = view.getComputedStyle(popup);
|
|
196
|
+
const inset = parseFloat(css.paddingTop) + parseFloat(css.paddingBottom) + parseFloat(css.borderTopWidth) + parseFloat(css.borderBottomWidth);
|
|
197
|
+
const optionHeight = Math.max(0, maxHeight - inset);
|
|
198
|
+
if (scrollViewport.current)
|
|
199
|
+
assign(scrollViewport.current, "max-height", `${optionHeight}px`);
|
|
200
|
+
positionLayer(button, popup, "bottom", "start");
|
|
201
|
+
if (optionHeight !== lastViewportHeight) {
|
|
202
|
+
lastViewportHeight = optionHeight;
|
|
203
|
+
revealOption(activeIndex.current);
|
|
204
|
+
}
|
|
205
|
+
frame = view.requestAnimationFrame(update);
|
|
206
|
+
};
|
|
207
|
+
const start = () => {
|
|
208
|
+
if (!alive || !button.isConnected)
|
|
209
|
+
return;
|
|
210
|
+
const parent = button.closest("dialog");
|
|
211
|
+
if (parent && !parent.open) {
|
|
212
|
+
frame = view.requestAnimationFrame(start);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (typeof popup.showPopover !== "function")
|
|
216
|
+
throw new Error("Flood Select requires the native Popover API");
|
|
217
|
+
popup.style.visibility = "hidden";
|
|
160
218
|
popup.showPopover();
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
219
|
+
update();
|
|
220
|
+
unregister = registerLayer(document, {
|
|
221
|
+
panel: popup,
|
|
222
|
+
// Native callbacks are mirrored on the hidden select inside this span.
|
|
223
|
+
// Those synthetic focus events belong to this control, not outside it.
|
|
224
|
+
anchor: () => trigger.current?.parentElement ?? trigger.current,
|
|
225
|
+
close: () => setOpen(false),
|
|
226
|
+
});
|
|
227
|
+
};
|
|
228
|
+
const toggle = (event) => {
|
|
229
|
+
if (alive && event.newState === "closed" && !popup.matches(":popover-open"))
|
|
230
|
+
setOpen(false);
|
|
231
|
+
};
|
|
232
|
+
popup.addEventListener("toggle", toggle);
|
|
233
|
+
start();
|
|
234
|
+
return () => {
|
|
235
|
+
alive = false;
|
|
236
|
+
view.cancelAnimationFrame(frame);
|
|
237
|
+
unregister();
|
|
238
|
+
popup.removeEventListener("toggle", toggle);
|
|
239
|
+
returnFocus(popup, trigger.current);
|
|
240
|
+
if (popup.matches(":popover-open"))
|
|
241
|
+
popup.hidePopover();
|
|
242
|
+
};
|
|
243
|
+
}, [open]);
|
|
179
244
|
useEffect(() => {
|
|
180
245
|
if (!open || (choices[active] && enabled(choices[active])))
|
|
181
246
|
return;
|
|
182
247
|
const selectedIndex = choices.findIndex((choice) => choice.value === currentValue && enabled(choice));
|
|
183
248
|
setActive(selectedIndex >= 0 ? selectedIndex : choices.findIndex(enabled));
|
|
184
249
|
}, [choices, open, active, currentValue]);
|
|
185
|
-
useEffect(() => {
|
|
186
|
-
|
|
187
|
-
return;
|
|
188
|
-
menu.current?.querySelector(`[data-option-index="${active}"]`)?.scrollIntoView({ block: "nearest" });
|
|
189
|
-
}, [open, active]);
|
|
190
|
-
useEffect(() => {
|
|
191
|
-
if (!open)
|
|
192
|
-
return;
|
|
193
|
-
const outside = (event) => { if (event.target instanceof Node && !trigger.current?.contains(event.target) && !menu.current?.contains(event.target))
|
|
194
|
-
setOpen(false); };
|
|
195
|
-
const scroll = (event) => { if (event.target instanceof Node && menu.current?.contains(event.target))
|
|
196
|
-
return; setOpen(false); };
|
|
197
|
-
const resize = () => setOpen(false);
|
|
198
|
-
document.addEventListener("pointerdown", outside, true);
|
|
199
|
-
document.addEventListener("scroll", scroll, true);
|
|
200
|
-
window.addEventListener("resize", resize);
|
|
201
|
-
return () => { document.removeEventListener("pointerdown", outside, true); document.removeEventListener("scroll", scroll, true); window.removeEventListener("resize", resize); };
|
|
202
|
-
}, [open]);
|
|
250
|
+
useEffect(() => { if (open)
|
|
251
|
+
revealOption(active); }, [open, active]);
|
|
203
252
|
const displayed = choices.find((choice) => choice.value === currentValue)?.label ?? "Select an option";
|
|
204
253
|
const descriptions = [describedBy, validation && validationId].filter(Boolean).join(" ") || undefined;
|
|
205
254
|
const visibleAttributes = Object.fromEntries(Object.entries(nativeProps).filter(([key]) => key.startsWith("data-") || key.startsWith("aria-")));
|
|
@@ -214,14 +263,13 @@ export const SingleSelect = forwardRef(function SingleSelect({ children, value,
|
|
|
214
263
|
else
|
|
215
264
|
groups.push({ label: choice.group, items: [{ choice, index }] });
|
|
216
265
|
});
|
|
217
|
-
const renderOption = ({ choice, index }) => _jsxs("div", { id: `${menuId}-${index}`, role: "option", "aria-selected": choice.value === currentValue, "aria-disabled": choice.disabled || undefined, className: "flood-select__option", "data-active": index === active || undefined, "data-option-index": index, onPointerDown: (event) =>
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
} }, children: [_jsx("span", { children: choice.label }), _jsx("span", { className: "flood-select__selected", "aria-hidden": "true", children: choice.value === currentValue ? "✓" : "" })] }, `${index}-${choice.value}`);
|
|
266
|
+
const renderOption = ({ choice, index }) => _jsxs("div", { id: `${menuId}-${index}`, role: "option", "aria-selected": choice.value === currentValue, "aria-disabled": choice.disabled || undefined, className: "flood-select__option", "data-active": index === active || undefined, "data-option-index": index, onPointerDown: (event) => { if (event.pointerType === "mouse")
|
|
267
|
+
event.preventDefault(); }, onPointerMove: () => { if (enabled(choice))
|
|
268
|
+
setActive(index); }, onClick: () => { if (enabled(choice))
|
|
269
|
+
commit(index); }, children: [_jsx("span", { children: choice.label }), _jsx("span", { className: "flood-select__selected", "aria-hidden": "true", children: choice.value === currentValue ? "✓" : "" })] }, `${index}-${choice.value}`);
|
|
222
270
|
return _jsxs("span", { className: `flood-select flood-select--custom flood-control--${controlSize}`, dir: nativeProps.dir, lang: nativeProps.lang, hidden: nativeProps.hidden, children: [_jsx("button", { ...visibleAttributes, ref: trigger, id: triggerId, type: "button", role: "combobox", disabled: disabled, tabIndex: nativeProps.tabIndex, accessKey: nativeProps.accessKey, dir: nativeProps.dir, lang: nativeProps.lang, hidden: nativeProps.hidden, className: ["flood-input flood-select__control", `flood-control--${controlSize}`, className].filter(Boolean).join(" "), style: style, title: title, "aria-label": ariaLabel, "aria-labelledby": labelledBy, "aria-describedby": descriptions, "data-status": resolveFieldStatus(status, invalid || Boolean(validation), ariaInvalid), "aria-invalid": invalid || status === "error" || ariaInvalid || Boolean(validation) || undefined, "aria-required": required || undefined, "aria-expanded": open, "aria-haspopup": "listbox", "aria-controls": menuId, "aria-activedescendant": open && active >= 0 && choices[active] && !choices[active].hidden ? `${menuId}-${active}` : undefined, onFocus: (event) => { if (onFocus)
|
|
223
271
|
mirror(new FocusEvent("focusin", { bubbles: true, relatedTarget: event.relatedTarget })); }, onBlur: (event) => { if (onBlur)
|
|
224
|
-
mirror(new FocusEvent("focusout", { bubbles: true, relatedTarget: event.relatedTarget }));
|
|
272
|
+
mirror(new FocusEvent("focusout", { bubbles: true, relatedTarget: event.relatedTarget })); }, onClick: () => { if (onClick && !mirror(new MouseEvent("click", { bubbles: true, cancelable: true })))
|
|
225
273
|
return; if (open)
|
|
226
274
|
setOpen(false);
|
|
227
275
|
else
|
|
@@ -280,5 +328,6 @@ export const SingleSelect = forwardRef(function SingleSelect({ children, value,
|
|
|
280
328
|
onKeyDown: (event) => { onKeyDown?.(event); event.stopPropagation(); }, onKeyUp: onKeyUp, onClick: onClick, onInvalid: (event) => { onInvalid?.(event); const prevented = event.defaultPrevented; event.preventDefault(); if (!prevented) {
|
|
281
329
|
setValidation(event.currentTarget.validationMessage);
|
|
282
330
|
trigger.current?.focus();
|
|
283
|
-
} }, children: children }), open && _jsx("div", { ref: menu, id: menuId, role: "listbox", "aria-label": ariaLabel, "aria-labelledby": ariaLabel ? undefined : labelledBy ?? triggerId, popover: "manual", className: "flood-select__menu", onPointerDown: (event) =>
|
|
331
|
+
} }, children: children }), open && _jsx("div", { ref: menu, id: menuId, role: "listbox", "aria-label": ariaLabel, "aria-labelledby": ariaLabel ? undefined : labelledBy ?? triggerId, popover: "manual", className: "flood-select__menu", onPointerDown: (event) => { if (event.pointerType === "mouse")
|
|
332
|
+
event.preventDefault(); }, onClick: (event) => event.preventDefault(), children: _jsxs(ScrollArea, { ref: scrollViewport, orientation: "vertical", tabIndex: -1, className: "flood-select__viewport", children: [!choices.some((choice) => !choice.hidden) && _jsx("div", { className: "flood-select__empty", children: "No options" }), groups.map((group, index) => group.label ? _jsxs("div", { role: "group", "aria-label": group.label, children: [_jsx("div", { className: "flood-select__group", "aria-hidden": "true", children: group.label }), group.items.map(renderOption)] }, index) : _jsx(Fragment, { children: group.items.map(renderOption) }, index))] }) }), validation && _jsx("span", { className: "flood-select__validation", id: validationId, role: "alert", children: validation })] });
|
|
284
333
|
});
|
package/dist/styles.css
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
@import "@flood-md/tokens/tokens.css";
|
|
2
2
|
@import "./foundations.css";
|
|
3
3
|
|
|
4
|
+
/* A named loading indicator; buttons opt into decorative presentation. */
|
|
5
|
+
.flood-loading { --flood-loading-size: 1.25rem; display: inline-flex; position: relative; flex: none; vertical-align: middle; inline-size: var(--flood-loading-size); block-size: var(--flood-loading-size); }
|
|
6
|
+
.flood-loading[data-size="sm"] { --flood-loading-size: 1rem; }
|
|
7
|
+
.flood-loading[data-size="lg"] { --flood-loading-size: 1.5rem; }
|
|
8
|
+
.flood-loading > .flood-spinner { display: block; box-sizing: border-box; width: 100%; height: 100%; }
|
|
9
|
+
.flood-root .flood-loading .flood-spinner { inline-size: 100%; block-size: 100%; }
|
|
10
|
+
.flood-root .flood-button .flood-loading { --flood-loading-size: var(--flood-control-icon); }
|
|
11
|
+
.flood-loading[hidden] { display: none; }
|
|
12
|
+
|
|
4
13
|
:where(.flood-button, .flood-input, .flood-choice, .flood-field, .flood-link) {
|
|
5
14
|
box-sizing: border-box;
|
|
6
15
|
font-family: var(--flood-font-sans);
|
package/dist/toolbar.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useEffect, useImperativeHandle, useLayoutEffect, useRef } from "react";
|
|
3
|
+
const useBrowserLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
4
|
+
const selector = 'button:not([role]),button[role="button"],a[href]:not([role]),a[href][role="link"]';
|
|
5
|
+
const independent = '[role="menu"],[role="tablist"],[role="radiogroup"],[role="listbox"],[role="combobox"],.flood-toggle-group';
|
|
6
|
+
export const RovingToolbar = forwardRef(function RovingToolbar({ label, orientation = "horizontal", loop = true, className, onKeyDown, onKeyDownCapture, onFocus, ...props }, ref) {
|
|
7
|
+
const root = useRef(null), current = useRef(null), within = useRef(false);
|
|
8
|
+
const refresh = useRef(() => []);
|
|
9
|
+
useImperativeHandle(ref, () => root.current, []);
|
|
10
|
+
useBrowserLayoutEffect(() => {
|
|
11
|
+
const element = root.current, document = element.ownerDocument, view = document.defaultView;
|
|
12
|
+
const original = new Map();
|
|
13
|
+
let alive = true;
|
|
14
|
+
const restore = (node) => { const value = original.get(node); if (value == null)
|
|
15
|
+
node.removeAttribute("tabindex");
|
|
16
|
+
else
|
|
17
|
+
node.setAttribute("tabindex", value); };
|
|
18
|
+
const sync = () => {
|
|
19
|
+
if (!alive)
|
|
20
|
+
return [];
|
|
21
|
+
const owned = Array.from(element.querySelectorAll(selector)).filter(node => node.closest('[role="toolbar"]') === element && !node.closest(independent));
|
|
22
|
+
const active = document.activeElement;
|
|
23
|
+
const hadFocus = active === element || original.has(active) || (active === document.body && within.current);
|
|
24
|
+
for (const node of original.keys())
|
|
25
|
+
if (!owned.includes(node)) {
|
|
26
|
+
restore(node);
|
|
27
|
+
original.delete(node);
|
|
28
|
+
}
|
|
29
|
+
const enabled = owned.filter(node => !node.matches(':disabled,[aria-disabled="true"]') && !node.closest('[hidden],[inert]') && node.getClientRects().length > 0 && getComputedStyle(node).visibility !== "hidden");
|
|
30
|
+
const entry = enabled.includes(active) ? active : enabled.includes(current.current) ? current.current : enabled[0] ?? null;
|
|
31
|
+
current.current = entry;
|
|
32
|
+
for (const node of owned) {
|
|
33
|
+
if (!original.has(node))
|
|
34
|
+
original.set(node, node.getAttribute("tabindex"));
|
|
35
|
+
const value = node === entry ? "0" : "-1";
|
|
36
|
+
if (node.getAttribute("tabindex") !== value)
|
|
37
|
+
node.setAttribute("tabindex", value);
|
|
38
|
+
}
|
|
39
|
+
const value = entry ? "-1" : "0";
|
|
40
|
+
if (element.getAttribute("tabindex") !== value)
|
|
41
|
+
element.setAttribute("tabindex", value);
|
|
42
|
+
if (hadFocus && active !== entry && !enabled.includes(active) && element.getClientRects().length)
|
|
43
|
+
(entry ?? element).focus({ preventScroll: true });
|
|
44
|
+
return enabled;
|
|
45
|
+
};
|
|
46
|
+
refresh.current = sync;
|
|
47
|
+
sync();
|
|
48
|
+
const observer = new MutationObserver(records => { if (records.some(record => !(record.target instanceof Element) || !record.target.closest(independent)))
|
|
49
|
+
sync(); });
|
|
50
|
+
observer.observe(element, { subtree: true, childList: true, attributes: true, attributeFilter: ["disabled", "aria-disabled", "hidden", "inert", "style", "class", "tabindex", "href", "role"] });
|
|
51
|
+
const resize = typeof ResizeObserver === "function" ? new ResizeObserver(() => sync()) : undefined;
|
|
52
|
+
resize?.observe(element);
|
|
53
|
+
const focus = (event) => { within.current = element.contains(event.target); };
|
|
54
|
+
document.addEventListener("focusin", focus);
|
|
55
|
+
view.addEventListener("resize", sync);
|
|
56
|
+
return () => { alive = false; observer.disconnect(); resize?.disconnect(); document.removeEventListener("focusin", focus); view.removeEventListener("resize", sync); for (const node of original.keys())
|
|
57
|
+
restore(node); refresh.current = () => []; };
|
|
58
|
+
}, []);
|
|
59
|
+
const move = (event) => {
|
|
60
|
+
const controls = refresh.current(), index = controls.indexOf(event.target);
|
|
61
|
+
if (index < 0 || event.altKey || event.ctrlKey || event.metaKey || event.nativeEvent.isComposing)
|
|
62
|
+
return;
|
|
63
|
+
const rtl = getComputedStyle(event.currentTarget).direction === "rtl";
|
|
64
|
+
const nextKey = orientation === "vertical" ? "ArrowDown" : rtl ? "ArrowLeft" : "ArrowRight", previousKey = orientation === "vertical" ? "ArrowUp" : rtl ? "ArrowRight" : "ArrowLeft";
|
|
65
|
+
const step = event.key === nextKey ? 1 : event.key === previousKey ? -1 : 0;
|
|
66
|
+
const wanted = event.key === "Home" ? 0 : event.key === "End" ? controls.length - 1 : step ? index + step : -1;
|
|
67
|
+
if (wanted < 0 && !step)
|
|
68
|
+
return;
|
|
69
|
+
const next = loop ? (wanted + controls.length) % controls.length : Math.max(0, Math.min(controls.length - 1, wanted));
|
|
70
|
+
event.preventDefault();
|
|
71
|
+
current.current = controls[next];
|
|
72
|
+
controls[next].focus({ preventScroll: true });
|
|
73
|
+
controls[next].scrollIntoView({ block: "nearest", inline: "nearest" });
|
|
74
|
+
refresh.current();
|
|
75
|
+
};
|
|
76
|
+
return _jsx("div", { ...props, ref: root, role: "toolbar", "aria-label": label, "aria-orientation": orientation, tabIndex: -1, "data-orientation": orientation, className: ["flood-toolbar", className].filter(Boolean).join(" "), onFocus: event => { onFocus?.(event); if (event.defaultPrevented)
|
|
77
|
+
return; within.current = true; refresh.current(); }, onKeyDownCapture: event => { onKeyDownCapture?.(event); if (!event.defaultPrevented && orientation === "vertical" && event.target.getAttribute("aria-haspopup") === "menu" && ["ArrowUp", "ArrowDown"].includes(event.key))
|
|
78
|
+
move(event); }, onKeyDown: event => { onKeyDown?.(event); if (!event.defaultPrevented)
|
|
79
|
+
move(event); } });
|
|
80
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flood-md/react",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Original Flood components for React and Next.js.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"react-dom": "^18.3.0 || ^19.0.0"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@flood-md/tokens": "0.2.
|
|
33
|
+
"@flood-md/tokens": "0.2.4"
|
|
34
34
|
},
|
|
35
35
|
"publishConfig": {
|
|
36
36
|
"access": "public"
|