react-roving-focus
Version:
Flexible roving focus for React with support for any fixed or responsive layout.
302 lines (301 loc) • 8.49 kB
JavaScript
import { useRef as R, createContext as oe, useContext as ie, useState as se, useEffect as U, useCallback as g, useMemo as Y } from "react";
import { jsx as ce } from "react/jsx-runtime";
function ue(e) {
const t = R(null);
return e !== void 0 ? e : t;
}
const Q = oe(
null
);
function fe() {
const e = ie(Q);
if (!e)
throw new Error("useRovingFocus must be used within a RovingFocusGroup");
return e;
}
const je = ({
ref: e,
disabled: t = !1
} = {}) => {
const n = fe(), [o, i] = se(-1), a = ue(e);
U(() => {
const s = a.current;
if (!(!s || t))
return n.registerElement(s, { onTabIndexChange: i }), () => {
n.unregisterElement(s);
};
}, [a, n, t]);
const m = g(
(s) => {
switch (s.key) {
case "ArrowLeft":
n.focusNextElement("left");
break;
case "ArrowRight":
n.focusNextElement("right");
break;
case "ArrowUp":
n.focusNextElement("up");
break;
case "ArrowDown":
n.focusNextElement("down");
break;
case "Home":
n.focusFirstElement();
break;
case "End":
n.focusLastElement();
break;
default:
return;
}
s.preventDefault(), s.stopPropagation();
},
[n]
), f = g(() => {
const s = a.current;
!s || t || n.setFocusedElement(s);
}, [a, n, t]);
return U(() => {
const s = a.current;
if (!(!s || t))
return s.addEventListener("focus", f), s.addEventListener("keydown", m), () => {
s.removeEventListener("focus", f), s.removeEventListener("keydown", m);
};
}, [a, n, t, f, m]), { ref: a, tabIndex: o };
};
var A = typeof globalThis < "u" ? globalThis : typeof window < "u" ? window : typeof global < "u" ? global : typeof self < "u" ? self : {};
function le(e) {
return e && e.__esModule && Object.prototype.hasOwnProperty.call(e, "default") ? e.default : e;
}
var G, z;
function ae() {
if (z) return G;
z = 1;
var e = "Expected a function", t = NaN, n = "[object Symbol]", o = /^\s+|\s+$/g, i = /^[-+]0x[0-9a-f]+$/i, a = /^0b[01]+$/i, m = /^0o[0-7]+$/i, f = parseInt, s = typeof A == "object" && A && A.Object === Object && A, O = typeof self == "object" && self && self.Object === Object && self, C = s || O || Function("return this")(), y = Object.prototype, b = y.toString, L = Math.max, M = Math.min, w = function() {
return C.Date.now();
};
function P(r, l, p) {
var T, D, N, k, d, x, j = 0, q = !1, F = !1, _ = !0;
if (typeof r != "function")
throw new TypeError(e);
l = $(l) || 0, c(p) && (q = !!p.leading, F = "maxWait" in p, N = F ? L($(p.maxWait) || 0, l) : N, _ = "trailing" in p ? !!p.trailing : _);
function B(u) {
var v = T, I = D;
return T = D = void 0, j = u, k = r.apply(I, v), k;
}
function ee(u) {
return j = u, d = setTimeout(S, l), q ? B(u) : k;
}
function te(u) {
var v = u - x, I = u - j, V = l - v;
return F ? M(V, N - I) : V;
}
function H(u) {
var v = u - x, I = u - j;
return x === void 0 || v >= l || v < 0 || F && I >= N;
}
function S() {
var u = w();
if (H(u))
return K(u);
d = setTimeout(S, te(u));
}
function K(u) {
return d = void 0, _ && T ? B(u) : (T = D = void 0, k);
}
function ne() {
d !== void 0 && clearTimeout(d), j = 0, T = x = D = d = void 0;
}
function re() {
return d === void 0 ? k : K(w());
}
function X() {
var u = w(), v = H(u);
if (T = arguments, D = this, x = u, v) {
if (d === void 0)
return ee(x);
if (F)
return d = setTimeout(S, l), B(x);
}
return d === void 0 && (d = setTimeout(S, l)), k;
}
return X.cancel = ne, X.flush = re, X;
}
function c(r) {
var l = typeof r;
return !!r && (l == "object" || l == "function");
}
function h(r) {
return !!r && typeof r == "object";
}
function E(r) {
return typeof r == "symbol" || h(r) && b.call(r) == n;
}
function $(r) {
if (typeof r == "number")
return r;
if (E(r))
return t;
if (c(r)) {
var l = typeof r.valueOf == "function" ? r.valueOf() : r;
r = c(l) ? l + "" : l;
}
if (typeof r != "string")
return r === 0 ? r : +r;
r = r.replace(o, "");
var p = a.test(r);
return p || m.test(r) ? f(r.slice(2), p ? 2 : 8) : i.test(r) ? t : +r;
}
return G = P, G;
}
var me = ae();
const de = /* @__PURE__ */ le(me), ge = (e) => {
U(() => e, []);
};
function Z(e) {
const t = e.getBoundingClientRect();
return {
left: t.left,
right: t.right,
top: t.top,
bottom: t.bottom,
centerX: t.left + t.width / 2,
centerY: t.top + t.height / 2
};
}
function W(e, t, n) {
return n === "row" ? Math.min(e.bottom, t.bottom) - Math.max(e.top, t.top) > 1 : Math.min(e.right, t.right) - Math.max(e.left, t.left) > 1;
}
function pe(e, t, n) {
switch (n) {
case "left":
return e.centerX < t.left;
case "right":
return e.centerX > t.right;
case "up":
return e.centerY < t.top;
case "down":
return e.centerY > t.bottom;
}
}
function be(e) {
var n;
return ((n = e.sort((o, i) => W(o.position, i.position, "row") ? o.position.left - i.position.left : o.position.top - i.position.top)[0]) == null ? void 0 : n.element) ?? null;
}
function he(e) {
var n;
return ((n = e.sort((o, i) => W(o.position, i.position, "row") ? i.position.right - o.position.right : i.position.bottom - o.position.bottom)[0]) == null ? void 0 : n.element) ?? null;
}
function Ee(e, t) {
return e.right <= t.left ? t.left - e.right : t.right <= e.left ? e.left - t.right : e.bottom <= t.top ? t.top - e.bottom : t.bottom <= e.top ? e.top - t.bottom : 0;
}
function ve(e, t) {
const n = e.centerX - t.centerX, o = e.centerY - t.centerY;
return Math.sqrt(n * n + o * o);
}
function J(e, t) {
const n = e.map(({ element: o, position: i }) => ({
element: o,
edgeDistance: Ee(i, t),
centerDistance: ve(i, t)
})).sort((o, i) => o.edgeDistance !== i.edgeDistance ? o.edgeDistance - i.edgeDistance : o.centerDistance - i.centerDistance);
return n.length === 0 ? null : n[0].element;
}
function xe(e) {
return e === "left" || e === "right" ? "row" : "column";
}
function ye(e, t, n) {
const o = Z(e), i = n.filter(({ element: f, position: s }) => f !== e && pe(s, o, t));
if (i.length === 0)
return null;
const a = xe(t), m = i.filter(
({ position: f }) => W(f, o, a)
);
return m.length > 0 ? J(m, o) : J(i, o);
}
function we(e) {
return !Te(e);
}
function Te(e) {
return "disabled" in e && e.disabled === !0 || e.ariaDisabled === "true";
}
function Fe({ children: e }) {
const t = R(null), n = R(null), o = R(null), i = R(/* @__PURE__ */ new Map()), a = () => Array.from(i.current.keys()).filter(we).map((c) => ({ element: c, position: Z(c) })), m = Y(
() => de(() => {
const c = a();
n.current = be(c), o.current = he(c), t.current && !i.current.has(t.current) && (t.current = null);
for (const [h, E] of i.current.entries())
t.current === h || n.current === h && !t.current ? E.onTabIndexChange(0) : E.onTabIndexChange(-1);
}),
[]
), f = g(m, [m]), s = Y(
() => new MutationObserver(f),
[f]
);
ge(() => {
m.cancel(), s.disconnect();
});
const O = g(
(c, h) => {
i.current.set(c, h), f(), s.observe(c, {
attributes: !0,
attributeFilter: ["disabled", "aria-disabled"]
});
},
[f, s]
), C = g(
(c) => {
i.current.delete(c), f();
},
[f]
), y = g(
(c) => {
t.current = c, f();
},
[f]
), b = g(
(c) => {
y(c), c.focus();
},
[y]
), L = g(
(c) => {
if (!t.current)
return;
const h = a(), E = ye(
t.current,
c,
h
);
E && b(E);
},
[b]
), M = g(() => {
n.current && b(n.current);
}, [b]), w = g(() => {
o.current && b(o.current);
}, [b]), P = Y(
() => ({
registerElement: O,
unregisterElement: C,
setFocusedElement: y,
focusNextElement: L,
focusFirstElement: M,
focusLastElement: w
}),
[
O,
C,
y,
L,
M,
w
]
);
return /* @__PURE__ */ ce(Q.Provider, { value: P, children: e });
}
export {
Fe as RovingFocusGroup,
je as useRovingFocus
};