najm-kit 2.11.6 → 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 +36 -11
- package/dist/index.d.ts +2 -0
- package/dist/index.mjs +58 -9
- package/dist/theme.css +5 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,14 +1,39 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
## 2.11.
|
|
4
|
-
|
|
5
|
-
- Fixed `
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
## 2.11.
|
|
1
|
+
# Changelog
|
|
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
|
+
|
|
11
|
+
## 2.11.7 - 2026-08-17
|
|
12
|
+
|
|
13
|
+
- Fixed date, combobox, and multiselect `FormInput` controls reaching the
|
|
14
|
+
accessibility tree with no accessible name. These types render their trigger
|
|
15
|
+
as a styled `div`, so the `<label for>` that `FormControl` wires up pointed at
|
|
16
|
+
a non-labelable element and named nothing: screen readers announced an unnamed
|
|
17
|
+
control, and `getByRole(..., { name })` could not resolve one. The form label
|
|
18
|
+
now becomes the trigger's `aria-label`, and `DateInput` and `ComboboxInput`
|
|
19
|
+
accept an explicit `ariaLabel` to override it. `select` keeps naming itself
|
|
20
|
+
from `ariaLabel || placeholder`; renaming it would break consumers that select
|
|
21
|
+
it by placeholder today.
|
|
22
|
+
- Fixed `DateInput` rendering its popover trigger as a `div`. Radix only merges
|
|
23
|
+
trigger props onto the child, so the control had no role, no tab stop, and no
|
|
24
|
+
keyboard activation — it was unreachable without a mouse. It is now a real
|
|
25
|
+
`<button type="button">`, which also keeps it from submitting the surrounding
|
|
26
|
+
form when the calendar opens.
|
|
27
|
+
|
|
28
|
+
## 2.11.6 - 2026-08-15
|
|
29
|
+
|
|
30
|
+
- Fixed `FormInput` dropping React Hook Form's native field binding for text,
|
|
31
|
+
number, password, textarea, and time controls. These controls now forward
|
|
32
|
+
their field name, composed blur handler, and focus ref to the actual native
|
|
33
|
+
element, preserving browser form semantics, touched-state tracking, and
|
|
34
|
+
programmatic focus without changing composite-control contracts.
|
|
35
|
+
|
|
36
|
+
## 2.11.5 - 2026-08-14
|
|
12
37
|
|
|
13
38
|
- Fixed numbered lists staying on an invalid final page after a deletion or
|
|
14
39
|
another mutation reduced the server total. Paged lists now clamp before
|
package/dist/index.d.ts
CHANGED
|
@@ -2238,6 +2238,7 @@ interface ComboboxInputProps extends BaseProps {
|
|
|
2238
2238
|
showIcon?: boolean;
|
|
2239
2239
|
disabled?: boolean;
|
|
2240
2240
|
allowFreeText?: boolean;
|
|
2241
|
+
ariaLabel?: string;
|
|
2241
2242
|
}
|
|
2242
2243
|
interface MultiSelectInputProps extends BaseProps {
|
|
2243
2244
|
value: string[];
|
|
@@ -2294,6 +2295,7 @@ interface DateInputProps extends BaseProps {
|
|
|
2294
2295
|
placeholder?: string;
|
|
2295
2296
|
icon?: InputIcon;
|
|
2296
2297
|
showIcon?: boolean;
|
|
2298
|
+
ariaLabel?: string;
|
|
2297
2299
|
}
|
|
2298
2300
|
interface StarRatingInputProps extends BaseProps {
|
|
2299
2301
|
value: number;
|
package/dist/index.mjs
CHANGED
|
@@ -4551,7 +4551,7 @@ var TextAreaInput = React66__default.forwardRef(({ value, onChange, placeholder
|
|
|
4551
4551
|
);
|
|
4552
4552
|
});
|
|
4553
4553
|
TextAreaInput.displayName = "TextAreaInput";
|
|
4554
|
-
var ComboboxInput = ({ placeholder = "Select...", searchPlaceholder = "Search...", emptyMessage = "No results found.", loading = false, loadingMessage = "Loading...", onSearchChange, shouldFilter = true, value, onChange, icon, showIcon = true, iconColor, items = [], className = "", variant = "default", status = "default", bordered, borderColor, disabled = false, allowFreeText = false }) => {
|
|
4554
|
+
var ComboboxInput = ({ placeholder = "Select...", searchPlaceholder = "Search...", emptyMessage = "No results found.", loading = false, loadingMessage = "Loading...", onSearchChange, shouldFilter = true, value, onChange, icon, showIcon = true, iconColor, items = [], className = "", variant = "default", status = "default", bordered, borderColor, disabled = false, allowFreeText = false, ariaLabel }) => {
|
|
4555
4555
|
const [open, setOpen] = useState(false);
|
|
4556
4556
|
const triggerRef = React66__default.useRef(null);
|
|
4557
4557
|
const pointerDismissedRef = React66__default.useRef(false);
|
|
@@ -4590,6 +4590,7 @@ var ComboboxInput = ({ placeholder = "Select...", searchPlaceholder = "Search...
|
|
|
4590
4590
|
bordered,
|
|
4591
4591
|
borderColor,
|
|
4592
4592
|
role: "combobox",
|
|
4593
|
+
"aria-label": ariaLabel,
|
|
4593
4594
|
tabIndex: disabled ? -1 : 0,
|
|
4594
4595
|
"aria-expanded": open,
|
|
4595
4596
|
className: cn(
|
|
@@ -4782,13 +4783,13 @@ function Calendar(props) {
|
|
|
4782
4783
|
}
|
|
4783
4784
|
);
|
|
4784
4785
|
}
|
|
4785
|
-
var DateInput = ({ value, onChange, placeholder = "Pick a date", className = "", icon, showIcon = true, iconColor, variant = "default", status = "default", bordered, borderColor }) => {
|
|
4786
|
+
var DateInput = ({ value, onChange, placeholder = "Pick a date", className = "", icon, showIcon = true, iconColor, variant = "default", status = "default", bordered, borderColor, ariaLabel }) => {
|
|
4786
4787
|
const iconProps = getIconColorProps(iconColor, "h-4 w-4");
|
|
4787
4788
|
const toDateString = (date) => date ? format(date, "yyyy-MM-dd") : void 0;
|
|
4788
4789
|
const toDate = (val) => typeof val === "string" ? parseISO(val) : val;
|
|
4789
4790
|
return /* @__PURE__ */ jsxs(BaseInput, { variant, status, bordered, borderColor, className, children: [
|
|
4790
4791
|
/* @__PURE__ */ jsxs(Popover, { children: [
|
|
4791
|
-
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx("
|
|
4792
|
+
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx("button", { type: "button", "aria-label": ariaLabel, className: "w-full flex items-center cursor-pointer gap-2 justify-start text-left font-normal bg-transparent border-0 p-0 outline-none", children: /* @__PURE__ */ jsx("span", { className: cn("cursor-pointer", value ? "text-foreground" : "text-muted-foreground"), children: value ? format(toDate(value), "PPP") : placeholder }) }) }),
|
|
4792
4793
|
/* @__PURE__ */ jsx(PopoverContent, { className: "w-auto p-0", align: "start", children: /* @__PURE__ */ jsx(Calendar, { mode: "single", selected: toDate(value), onSelect: (date) => onChange(toDateString(date)), captionLayout: "dropdown" }) })
|
|
4793
4794
|
] }),
|
|
4794
4795
|
!icon && showIcon && /* @__PURE__ */ jsx(Calendar$1, { className: iconProps.className, style: iconProps.style })
|
|
@@ -9878,6 +9879,7 @@ function NViewBody({ children, className, variant = "table" }) {
|
|
|
9878
9879
|
function NContextMenu({ x, y, items, onAction, onClose, className }) {
|
|
9879
9880
|
const [openSub, setOpenSub] = useState(null);
|
|
9880
9881
|
const menuRef = useRef(null);
|
|
9882
|
+
const returnFocusRef = useRef(null);
|
|
9881
9883
|
const [pos, setPos] = useState({ left: x, top: y });
|
|
9882
9884
|
useLayoutEffect(() => {
|
|
9883
9885
|
if (!menuRef.current) return;
|
|
@@ -9894,13 +9896,54 @@ function NContextMenu({ x, y, items, onAction, onClose, className }) {
|
|
|
9894
9896
|
(prev) => prev.left === next.left && prev.top === next.top ? prev : next
|
|
9895
9897
|
);
|
|
9896
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
|
+
}, []);
|
|
9897
9906
|
useEffect(() => {
|
|
9898
9907
|
const onKey = (e) => {
|
|
9899
|
-
if (e.key
|
|
9908
|
+
if (e.key !== "Escape") return;
|
|
9909
|
+
e.preventDefault();
|
|
9910
|
+
onClose();
|
|
9911
|
+
queueMicrotask(() => returnFocusRef.current?.focus());
|
|
9900
9912
|
};
|
|
9901
9913
|
window.addEventListener("keydown", onKey);
|
|
9902
9914
|
return () => window.removeEventListener("keydown", onKey);
|
|
9903
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
|
+
};
|
|
9904
9947
|
useEffect(() => {
|
|
9905
9948
|
const isInsideMenu = (target) => target instanceof Node && !!menuRef.current?.contains(target);
|
|
9906
9949
|
const onMouseDown = (e) => {
|
|
@@ -9929,6 +9972,7 @@ function NContextMenu({ x, y, items, onAction, onClose, className }) {
|
|
|
9929
9972
|
),
|
|
9930
9973
|
style: { left: pos.left, top: pos.top },
|
|
9931
9974
|
onContextMenu: (e) => e.preventDefault(),
|
|
9975
|
+
onKeyDown: onMenuKeyDown,
|
|
9932
9976
|
children: items.map((item) => {
|
|
9933
9977
|
const Icon2 = item.icon;
|
|
9934
9978
|
const hasSub = !!item.submenu?.length;
|
|
@@ -9946,6 +9990,8 @@ function NContextMenu({ x, y, items, onAction, onClose, className }) {
|
|
|
9946
9990
|
{
|
|
9947
9991
|
type: "button",
|
|
9948
9992
|
role: "menuitem",
|
|
9993
|
+
tabIndex: -1,
|
|
9994
|
+
"data-context-menu-item": true,
|
|
9949
9995
|
disabled: item.disabled,
|
|
9950
9996
|
onClick: () => {
|
|
9951
9997
|
if (hasSub) return;
|
|
@@ -9953,8 +9999,8 @@ function NContextMenu({ x, y, items, onAction, onClose, className }) {
|
|
|
9953
9999
|
onClose();
|
|
9954
10000
|
},
|
|
9955
10001
|
className: cn(
|
|
9956
|
-
"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",
|
|
9957
|
-
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"
|
|
9958
10004
|
),
|
|
9959
10005
|
children: [
|
|
9960
10006
|
/* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
|
|
@@ -9973,14 +10019,15 @@ function NContextMenu({ x, y, items, onAction, onClose, className }) {
|
|
|
9973
10019
|
{
|
|
9974
10020
|
type: "button",
|
|
9975
10021
|
role: "menuitem",
|
|
10022
|
+
tabIndex: -1,
|
|
9976
10023
|
disabled: sub.disabled,
|
|
9977
10024
|
onClick: () => {
|
|
9978
10025
|
onAction(sub.id);
|
|
9979
10026
|
onClose();
|
|
9980
10027
|
},
|
|
9981
10028
|
className: cn(
|
|
9982
|
-
"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",
|
|
9983
|
-
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"
|
|
9984
10031
|
),
|
|
9985
10032
|
children: [
|
|
9986
10033
|
SubIcon && /* @__PURE__ */ jsx(SubIcon, { size: 14 }),
|
|
@@ -10879,6 +10926,7 @@ var Inputs = {
|
|
|
10879
10926
|
slider: SliderInput
|
|
10880
10927
|
};
|
|
10881
10928
|
var nativeFieldTypes = /* @__PURE__ */ new Set(["text", "number", "password", "textarea", "time"]);
|
|
10929
|
+
var labelNamedCompositeTypes = /* @__PURE__ */ new Set(["date", "combobox", "multiselect"]);
|
|
10882
10930
|
var FormInput = ({ name, type, formLabel, formDescription, required = false, disabled = false, readOnly = false, hidden = false, icon, iconColor, classNames, background, ...rest }) => {
|
|
10883
10931
|
const InputComponent = Inputs[type];
|
|
10884
10932
|
const { control } = useFormContext();
|
|
@@ -10897,6 +10945,7 @@ var FormInput = ({ name, type, formLabel, formDescription, required = false, dis
|
|
|
10897
10945
|
};
|
|
10898
10946
|
const presetInput = type === "textarea" ? preset.input?.replace(/\bh-\d+\b/g, "").trim() : preset.input;
|
|
10899
10947
|
const isFillAvatar = type === "avatar" && inputRest.fill === true;
|
|
10948
|
+
const compositeAriaLabel = labelNamedCompositeTypes.has(type) && typeof formLabel === "string" ? { ariaLabel: inputRest.ariaLabel ?? formLabel } : {};
|
|
10900
10949
|
const slot = {
|
|
10901
10950
|
item: cn(
|
|
10902
10951
|
preset.item,
|
|
@@ -10929,7 +10978,7 @@ var FormInput = ({ name, type, formLabel, formDescription, required = false, dis
|
|
|
10929
10978
|
formLabel,
|
|
10930
10979
|
required && !disabled && !readOnly && /* @__PURE__ */ jsx("span", { className: "text-destructive ml-1", children: "*" })
|
|
10931
10980
|
] }),
|
|
10932
|
-
/* @__PURE__ */ jsx(FormControl, { children: /* @__PURE__ */ jsx(InputComponent, { value: field.value ?? getDefaultValue(), onChange: handleChange, status: hasError && !isHidden ? "error" : "default", bordered: contextBordered, icon: formLabel ? void 0 : icon, iconColor: formLabel ? void 0 : iconColor, disabled, readOnly, ...inputRest, ...nativeFieldBinding, className: slot.input }) }),
|
|
10981
|
+
/* @__PURE__ */ jsx(FormControl, { children: /* @__PURE__ */ jsx(InputComponent, { value: field.value ?? getDefaultValue(), onChange: handleChange, status: hasError && !isHidden ? "error" : "default", bordered: contextBordered, icon: formLabel ? void 0 : icon, iconColor: formLabel ? void 0 : iconColor, disabled, readOnly, ...inputRest, ...compositeAriaLabel, ...nativeFieldBinding, className: slot.input }) }),
|
|
10933
10982
|
!hasError && !disabled && !readOnly && formDescription && /* @__PURE__ */ jsx(FormDescription, { className: slot.description, children: formDescription }),
|
|
10934
10983
|
!isHidden && /* @__PURE__ */ jsx(FormMessage, { className: slot.error })
|
|
10935
10984
|
] });
|
package/dist/theme.css
CHANGED
|
@@ -307,10 +307,10 @@ input:autofill {
|
|
|
307
307
|
overflow-x: auto;
|
|
308
308
|
scrollbar-width: none;
|
|
309
309
|
}
|
|
310
|
-
.najm-overlay-scroll-x::-webkit-scrollbar {
|
|
311
|
-
display: none;
|
|
312
|
-
}
|
|
313
|
-
|
|
310
|
+
.najm-overlay-scroll-x::-webkit-scrollbar {
|
|
311
|
+
display: none;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
314
|
/* Responsive table actions stay discoverable on touch and tablet layouts.
|
|
315
315
|
Fine-pointer desktops may keep the quieter hover/focus reveal treatment. */
|
|
316
316
|
.ntable-card-action {
|
|
@@ -367,7 +367,7 @@ input:autofill {
|
|
|
367
367
|
.nimage-input-compact-overlay:focus-visible {
|
|
368
368
|
opacity: 1;
|
|
369
369
|
}
|
|
370
|
-
}
|
|
370
|
+
}
|
|
371
371
|
|
|
372
372
|
/* OverlayScrollbars theme used by the <NajmScroll> component — a thin,
|
|
373
373
|
translucent slate bar that floats over content with no reserved space.
|