najm-kit 2.11.7 → 2.11.8
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/CHANGELOG.md +8 -0
- package/dist/index.mjs +51 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.11.8 - 2026-08-28
|
|
4
|
+
|
|
5
|
+
- Fixed `NContextMenu` leaving keyboard focus on its opener after the menu
|
|
6
|
+
appeared. The first enabled action now receives focus, disabled actions are
|
|
7
|
+
skipped, Arrow Up/Down wrap, Home/End jump to the bounds, Tab dismisses the
|
|
8
|
+
menu, and Escape restores focus to the opener. Focused actions also receive
|
|
9
|
+
the same visual treatment as hovered actions.
|
|
10
|
+
|
|
3
11
|
## 2.11.7 - 2026-08-17
|
|
4
12
|
|
|
5
13
|
- Fixed date, combobox, and multiselect `FormInput` controls reaching the
|
package/dist/index.mjs
CHANGED
|
@@ -9879,6 +9879,7 @@ function NViewBody({ children, className, variant = "table" }) {
|
|
|
9879
9879
|
function NContextMenu({ x, y, items, onAction, onClose, className }) {
|
|
9880
9880
|
const [openSub, setOpenSub] = useState(null);
|
|
9881
9881
|
const menuRef = useRef(null);
|
|
9882
|
+
const returnFocusRef = useRef(null);
|
|
9882
9883
|
const [pos, setPos] = useState({ left: x, top: y });
|
|
9883
9884
|
useLayoutEffect(() => {
|
|
9884
9885
|
if (!menuRef.current) return;
|
|
@@ -9895,13 +9896,54 @@ function NContextMenu({ x, y, items, onAction, onClose, className }) {
|
|
|
9895
9896
|
(prev) => prev.left === next.left && prev.top === next.top ? prev : next
|
|
9896
9897
|
);
|
|
9897
9898
|
}, [x, y]);
|
|
9899
|
+
useLayoutEffect(() => {
|
|
9900
|
+
const activeElement = document.activeElement;
|
|
9901
|
+
if (activeElement instanceof HTMLElement && !menuRef.current?.contains(activeElement)) {
|
|
9902
|
+
returnFocusRef.current = activeElement;
|
|
9903
|
+
}
|
|
9904
|
+
menuRef.current?.querySelector("[data-context-menu-item]:not(:disabled)")?.focus();
|
|
9905
|
+
}, []);
|
|
9898
9906
|
useEffect(() => {
|
|
9899
9907
|
const onKey = (e) => {
|
|
9900
|
-
if (e.key
|
|
9908
|
+
if (e.key !== "Escape") return;
|
|
9909
|
+
e.preventDefault();
|
|
9910
|
+
onClose();
|
|
9911
|
+
queueMicrotask(() => returnFocusRef.current?.focus());
|
|
9901
9912
|
};
|
|
9902
9913
|
window.addEventListener("keydown", onKey);
|
|
9903
9914
|
return () => window.removeEventListener("keydown", onKey);
|
|
9904
9915
|
}, [onClose]);
|
|
9916
|
+
const onMenuKeyDown = (e) => {
|
|
9917
|
+
const enabledItems = Array.from(
|
|
9918
|
+
menuRef.current?.querySelectorAll(
|
|
9919
|
+
"[data-context-menu-item]:not(:disabled)"
|
|
9920
|
+
) ?? []
|
|
9921
|
+
);
|
|
9922
|
+
if (!enabledItems.length) return;
|
|
9923
|
+
const currentIndex = enabledItems.indexOf(document.activeElement);
|
|
9924
|
+
let nextIndex = null;
|
|
9925
|
+
switch (e.key) {
|
|
9926
|
+
case "ArrowDown":
|
|
9927
|
+
nextIndex = currentIndex < 0 ? 0 : (currentIndex + 1) % enabledItems.length;
|
|
9928
|
+
break;
|
|
9929
|
+
case "ArrowUp":
|
|
9930
|
+
nextIndex = currentIndex < 0 ? enabledItems.length - 1 : (currentIndex - 1 + enabledItems.length) % enabledItems.length;
|
|
9931
|
+
break;
|
|
9932
|
+
case "Home":
|
|
9933
|
+
nextIndex = 0;
|
|
9934
|
+
break;
|
|
9935
|
+
case "End":
|
|
9936
|
+
nextIndex = enabledItems.length - 1;
|
|
9937
|
+
break;
|
|
9938
|
+
case "Tab":
|
|
9939
|
+
onClose();
|
|
9940
|
+
return;
|
|
9941
|
+
default:
|
|
9942
|
+
return;
|
|
9943
|
+
}
|
|
9944
|
+
e.preventDefault();
|
|
9945
|
+
enabledItems[nextIndex].focus();
|
|
9946
|
+
};
|
|
9905
9947
|
useEffect(() => {
|
|
9906
9948
|
const isInsideMenu = (target) => target instanceof Node && !!menuRef.current?.contains(target);
|
|
9907
9949
|
const onMouseDown = (e) => {
|
|
@@ -9930,6 +9972,7 @@ function NContextMenu({ x, y, items, onAction, onClose, className }) {
|
|
|
9930
9972
|
),
|
|
9931
9973
|
style: { left: pos.left, top: pos.top },
|
|
9932
9974
|
onContextMenu: (e) => e.preventDefault(),
|
|
9975
|
+
onKeyDown: onMenuKeyDown,
|
|
9933
9976
|
children: items.map((item) => {
|
|
9934
9977
|
const Icon2 = item.icon;
|
|
9935
9978
|
const hasSub = !!item.submenu?.length;
|
|
@@ -9947,6 +9990,8 @@ function NContextMenu({ x, y, items, onAction, onClose, className }) {
|
|
|
9947
9990
|
{
|
|
9948
9991
|
type: "button",
|
|
9949
9992
|
role: "menuitem",
|
|
9993
|
+
tabIndex: -1,
|
|
9994
|
+
"data-context-menu-item": true,
|
|
9950
9995
|
disabled: item.disabled,
|
|
9951
9996
|
onClick: () => {
|
|
9952
9997
|
if (hasSub) return;
|
|
@@ -9954,8 +9999,8 @@ function NContextMenu({ x, y, items, onAction, onClose, className }) {
|
|
|
9954
9999
|
onClose();
|
|
9955
10000
|
},
|
|
9956
10001
|
className: cn(
|
|
9957
|
-
"flex w-full cursor-pointer items-center justify-between gap-2 px-3 py-1.5 text-xs hover:bg-accent hover:text-accent-foreground disabled:cursor-not-allowed disabled:opacity-40",
|
|
9958
|
-
item.danger && "text-destructive hover:text-destructive"
|
|
10002
|
+
"flex w-full cursor-pointer items-center justify-between gap-2 px-3 py-1.5 text-xs hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:cursor-not-allowed disabled:opacity-40",
|
|
10003
|
+
item.danger && "text-destructive hover:text-destructive focus:text-destructive"
|
|
9959
10004
|
),
|
|
9960
10005
|
children: [
|
|
9961
10006
|
/* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
|
|
@@ -9974,14 +10019,15 @@ function NContextMenu({ x, y, items, onAction, onClose, className }) {
|
|
|
9974
10019
|
{
|
|
9975
10020
|
type: "button",
|
|
9976
10021
|
role: "menuitem",
|
|
10022
|
+
tabIndex: -1,
|
|
9977
10023
|
disabled: sub.disabled,
|
|
9978
10024
|
onClick: () => {
|
|
9979
10025
|
onAction(sub.id);
|
|
9980
10026
|
onClose();
|
|
9981
10027
|
},
|
|
9982
10028
|
className: cn(
|
|
9983
|
-
"flex w-full cursor-pointer items-center gap-2 px-3 py-1.5 text-xs hover:bg-accent hover:text-accent-foreground disabled:cursor-not-allowed disabled:opacity-40",
|
|
9984
|
-
sub.danger && "text-destructive hover:text-destructive"
|
|
10029
|
+
"flex w-full cursor-pointer items-center gap-2 px-3 py-1.5 text-xs hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:cursor-not-allowed disabled:opacity-40",
|
|
10030
|
+
sub.danger && "text-destructive hover:text-destructive focus:text-destructive"
|
|
9985
10031
|
),
|
|
9986
10032
|
children: [
|
|
9987
10033
|
SubIcon && /* @__PURE__ */ jsx(SubIcon, { size: 14 }),
|