@puzzle-ui/ui 0.4.0 → 0.5.1
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 +61 -77
- package/dist/components/actions/button/button.css +5 -2
- package/dist/components/actions/button-group/button-group.css +16 -17
- package/dist/components/data-display/badge/badge.css +15 -2
- package/dist/components/data-display/badge/index.cjs +13 -2
- package/dist/components/data-display/badge/index.cjs.map +1 -1
- package/dist/components/data-display/badge/index.d.cts +7 -2
- package/dist/components/data-display/badge/index.d.ts +7 -2
- package/dist/components/data-display/badge/index.js +13 -2
- package/dist/components/data-display/badge/index.js.map +1 -1
- package/dist/components/data-display/rating/index.cjs +84 -0
- package/dist/components/data-display/rating/index.cjs.map +1 -0
- package/dist/components/data-display/rating/index.d.cts +3 -0
- package/dist/components/data-display/rating/index.d.ts +3 -0
- package/dist/components/data-display/rating/index.js +84 -0
- package/dist/components/data-display/rating/index.js.map +1 -0
- package/dist/components/data-display/rating/rating.css +72 -0
- package/dist/components/data-display/rating/rating.types.d.cts +15 -0
- package/dist/components/data-display/rating/rating.types.d.ts +15 -0
- package/dist/components/data-display/rating/rating.utils.cjs +27 -0
- package/dist/components/data-display/rating/rating.utils.cjs.map +1 -0
- package/dist/components/data-display/rating/rating.utils.js +25 -0
- package/dist/components/data-display/rating/rating.utils.js.map +1 -0
- package/dist/components/forms/checkbox/checkbox.css +2 -10
- package/dist/components/forms/input-group/input-group.d.cts +4 -2
- package/dist/components/forms/input-group/input-group.d.ts +4 -2
- package/dist/components/forms/radio-group/index.d.cts +2 -1
- package/dist/components/forms/radio-group/index.d.ts +2 -1
- package/dist/components/forms/stepper/index.d.cts +4 -2
- package/dist/components/forms/stepper/index.d.ts +4 -2
- package/dist/components/forms/time-picker/index.cjs +108 -0
- package/dist/components/forms/time-picker/index.cjs.map +1 -0
- package/dist/components/forms/time-picker/index.d.cts +3 -0
- package/dist/components/forms/time-picker/index.d.ts +3 -0
- package/dist/components/forms/time-picker/index.js +108 -0
- package/dist/components/forms/time-picker/index.js.map +1 -0
- package/dist/components/forms/time-picker/time-picker.css +166 -0
- package/dist/components/forms/time-picker/time-picker.types.d.cts +34 -0
- package/dist/components/forms/time-picker/time-picker.types.d.ts +34 -0
- package/dist/components/forms/time-picker/time-picker.utils.cjs +25 -0
- package/dist/components/forms/time-picker/time-picker.utils.cjs.map +1 -0
- package/dist/components/forms/time-picker/time-picker.utils.js +24 -0
- package/dist/components/forms/time-picker/time-picker.utils.js.map +1 -0
- package/dist/components/forms/token-input/index.cjs +189 -0
- package/dist/components/forms/token-input/index.cjs.map +1 -0
- package/dist/components/forms/token-input/index.d.cts +3 -0
- package/dist/components/forms/token-input/index.d.ts +3 -0
- package/dist/components/forms/token-input/index.js +189 -0
- package/dist/components/forms/token-input/index.js.map +1 -0
- package/dist/components/forms/token-input/token-input.css +145 -0
- package/dist/components/forms/token-input/token-input.types.d.cts +48 -0
- package/dist/components/forms/token-input/token-input.types.d.ts +48 -0
- package/dist/components/forms/token-input/token-input.utils.cjs +30 -0
- package/dist/components/forms/token-input/token-input.utils.cjs.map +1 -0
- package/dist/components/forms/token-input/token-input.utils.js +24 -0
- package/dist/components/forms/token-input/token-input.utils.js.map +1 -0
- package/dist/components/layout/aspect-ratio/aspect-ratio.css +14 -0
- package/dist/components/layout/aspect-ratio/aspect-ratio.utils.cjs +11 -0
- package/dist/components/layout/aspect-ratio/aspect-ratio.utils.cjs.map +1 -0
- package/dist/components/layout/aspect-ratio/aspect-ratio.utils.js +11 -0
- package/dist/components/layout/aspect-ratio/aspect-ratio.utils.js.map +1 -0
- package/dist/components/layout/aspect-ratio/index.cjs +16 -0
- package/dist/components/layout/aspect-ratio/index.cjs.map +1 -0
- package/dist/components/layout/aspect-ratio/index.d.cts +11 -0
- package/dist/components/layout/aspect-ratio/index.d.ts +11 -0
- package/dist/components/layout/aspect-ratio/index.js +16 -0
- package/dist/components/layout/aspect-ratio/index.js.map +1 -0
- package/dist/components/layout/container/container.css +1 -0
- package/dist/components/layout/page-layout/page-layout.css +1 -0
- package/dist/components/layout/scroll-area/index.cjs +27 -0
- package/dist/components/layout/scroll-area/index.cjs.map +1 -0
- package/dist/components/layout/scroll-area/index.d.cts +15 -0
- package/dist/components/layout/scroll-area/index.d.ts +15 -0
- package/dist/components/layout/scroll-area/index.js +27 -0
- package/dist/components/layout/scroll-area/index.js.map +1 -0
- package/dist/components/layout/scroll-area/scroll-area.css +25 -0
- package/dist/components/layout/scroll-area/scroll-area.utils.cjs +8 -0
- package/dist/components/layout/scroll-area/scroll-area.utils.cjs.map +1 -0
- package/dist/components/layout/scroll-area/scroll-area.utils.d.cts +2 -0
- package/dist/components/layout/scroll-area/scroll-area.utils.d.ts +2 -0
- package/dist/components/layout/scroll-area/scroll-area.utils.js +8 -0
- package/dist/components/layout/scroll-area/scroll-area.utils.js.map +1 -0
- package/dist/components/layout/section/index.cjs +10 -4
- package/dist/components/layout/section/index.cjs.map +1 -1
- package/dist/components/layout/section/index.d.cts +1 -1
- package/dist/components/layout/section/index.d.ts +1 -1
- package/dist/components/layout/section/index.js +11 -5
- package/dist/components/layout/section/index.js.map +1 -1
- package/dist/components/layout/section/section.css +9 -3
- package/dist/components/layout/section/types.d.cts +5 -5
- package/dist/components/layout/section/types.d.ts +5 -5
- package/dist/components/layout/separator/index.cjs +18 -0
- package/dist/components/layout/separator/index.cjs.map +1 -0
- package/dist/components/layout/separator/index.d.cts +17 -0
- package/dist/components/layout/separator/index.d.ts +17 -0
- package/dist/components/layout/separator/index.js +18 -0
- package/dist/components/layout/separator/index.js.map +1 -0
- package/dist/components/layout/separator/separator.css +15 -0
- package/dist/components/layout/separator/separator.utils.cjs +11 -0
- package/dist/components/layout/separator/separator.utils.cjs.map +1 -0
- package/dist/components/layout/separator/separator.utils.d.cts +2 -0
- package/dist/components/layout/separator/separator.utils.d.ts +2 -0
- package/dist/components/layout/separator/separator.utils.js +11 -0
- package/dist/components/layout/separator/separator.utils.js.map +1 -0
- package/dist/components/layout/stack/index.cjs +18 -2
- package/dist/components/layout/stack/index.cjs.map +1 -1
- package/dist/components/layout/stack/index.d.cts +8 -6
- package/dist/components/layout/stack/index.d.ts +8 -6
- package/dist/components/layout/stack/index.js +18 -2
- package/dist/components/layout/stack/index.js.map +1 -1
- package/dist/components/layout/stack/stack.css +118 -138
- package/dist/components/navigation/carousel/index.cjs +4 -1
- package/dist/components/navigation/carousel/index.cjs.map +1 -1
- package/dist/components/navigation/carousel/index.js +4 -1
- package/dist/components/navigation/carousel/index.js.map +1 -1
- package/dist/components/navigation/tabs/index.d.cts +3 -2
- package/dist/components/navigation/tabs/index.d.ts +3 -2
- package/dist/components/navigation/tree-view/index.cjs +1 -1
- package/dist/components/navigation/tree-view/index.cjs.map +1 -1
- package/dist/components/navigation/tree-view/index.d.cts +5 -1
- package/dist/components/navigation/tree-view/index.d.ts +5 -1
- package/dist/components/navigation/tree-view/index.js +1 -1
- package/dist/components/navigation/tree-view/index.js.map +1 -1
- package/dist/components/navigation/tree-view/tree-view.css +5 -3
- package/dist/components/overlays/context-menu/context-menu.css +151 -0
- package/dist/components/overlays/context-menu/context-menu.types.d.cts +69 -0
- package/dist/components/overlays/context-menu/context-menu.types.d.ts +69 -0
- package/dist/components/overlays/context-menu/context-menu.utils.cjs +46 -0
- package/dist/components/overlays/context-menu/context-menu.utils.cjs.map +1 -0
- package/dist/components/overlays/context-menu/context-menu.utils.js +40 -0
- package/dist/components/overlays/context-menu/context-menu.utils.js.map +1 -0
- package/dist/components/overlays/context-menu/index.cjs +830 -0
- package/dist/components/overlays/context-menu/index.cjs.map +1 -0
- package/dist/components/overlays/context-menu/index.d.cts +42 -0
- package/dist/components/overlays/context-menu/index.d.ts +42 -0
- package/dist/components/overlays/context-menu/index.js +818 -0
- package/dist/components/overlays/context-menu/index.js.map +1 -0
- package/dist/components/typography/heading.cjs +6 -2
- package/dist/components/typography/heading.cjs.map +1 -1
- package/dist/components/typography/heading.d.cts +3 -2
- package/dist/components/typography/heading.d.ts +3 -2
- package/dist/components/typography/heading.js +6 -2
- package/dist/components/typography/heading.js.map +1 -1
- package/dist/components/typography/text.cjs +6 -2
- package/dist/components/typography/text.cjs.map +1 -1
- package/dist/components/typography/text.d.cts +3 -2
- package/dist/components/typography/text.d.ts +3 -2
- package/dist/components/typography/text.js +6 -2
- package/dist/components/typography/text.js.map +1 -1
- package/dist/components/utilities/preview/index.cjs +88 -20
- package/dist/components/utilities/preview/index.cjs.map +1 -1
- package/dist/components/utilities/preview/index.d.cts +6 -2
- package/dist/components/utilities/preview/index.d.ts +6 -2
- package/dist/components/utilities/preview/index.js +88 -20
- package/dist/components/utilities/preview/index.js.map +1 -1
- package/dist/components/utilities/preview/preview.css +26 -11
- package/dist/components/utilities/transition/index.d.cts +1 -1
- package/dist/components/utilities/transition/index.d.ts +1 -1
- package/dist/index.cjs +123 -97
- package/dist/index.d.cts +15 -2
- package/dist/index.d.ts +15 -2
- package/dist/index.js +8 -1
- package/package.json +11 -6
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { ButtonHTMLAttributes, CSSProperties, HTMLAttributes, MouseEvent, ReactNode, Ref } from "react";
|
|
2
|
+
export type ContextMenuItemVariant = "default" | "danger";
|
|
3
|
+
export type ContextMenuCheckedState = boolean | "indeterminate";
|
|
4
|
+
export type ContextMenuProps = {
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
open?: boolean;
|
|
7
|
+
defaultOpen?: boolean;
|
|
8
|
+
onOpenChange?: (open: boolean) => void;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
triggerClassName?: string;
|
|
11
|
+
contentClassName?: string;
|
|
12
|
+
};
|
|
13
|
+
export type ContextMenuTriggerProps = Omit<HTMLAttributes<HTMLElement>, "children"> & {
|
|
14
|
+
children: ReactNode;
|
|
15
|
+
asChild?: boolean;
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
ref?: Ref<HTMLElement>;
|
|
18
|
+
};
|
|
19
|
+
export type ContextMenuContentProps = Omit<HTMLAttributes<HTMLDivElement>, "children"> & {
|
|
20
|
+
children: ReactNode;
|
|
21
|
+
maxHeight?: CSSProperties["maxHeight"];
|
|
22
|
+
};
|
|
23
|
+
export type ContextMenuItemProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children" | "onSelect"> & {
|
|
24
|
+
children: ReactNode;
|
|
25
|
+
textValue?: string;
|
|
26
|
+
closeOnSelect?: boolean;
|
|
27
|
+
variant?: ContextMenuItemVariant;
|
|
28
|
+
onSelect?: (event: MouseEvent<HTMLElement>) => void;
|
|
29
|
+
};
|
|
30
|
+
export type ContextMenuGroupProps = Omit<HTMLAttributes<HTMLDivElement>, "children"> & {
|
|
31
|
+
children: ReactNode;
|
|
32
|
+
};
|
|
33
|
+
export type ContextMenuLabelProps = Omit<HTMLAttributes<HTMLDivElement>, "children"> & {
|
|
34
|
+
children: ReactNode;
|
|
35
|
+
};
|
|
36
|
+
export type ContextMenuSeparatorProps = HTMLAttributes<HTMLDivElement>;
|
|
37
|
+
export type ContextMenuCheckboxItemProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children" | "onSelect"> & {
|
|
38
|
+
children: ReactNode;
|
|
39
|
+
checked?: ContextMenuCheckedState;
|
|
40
|
+
defaultChecked?: boolean;
|
|
41
|
+
onCheckedChange?: (checked: boolean) => void;
|
|
42
|
+
closeOnSelect?: boolean;
|
|
43
|
+
textValue?: string;
|
|
44
|
+
onSelect?: (event: MouseEvent<HTMLElement>) => void;
|
|
45
|
+
};
|
|
46
|
+
export type ContextMenuRadioGroupProps = Omit<HTMLAttributes<HTMLDivElement>, "children"> & {
|
|
47
|
+
children: ReactNode;
|
|
48
|
+
value?: string;
|
|
49
|
+
defaultValue?: string;
|
|
50
|
+
onValueChange?: (value: string) => void;
|
|
51
|
+
};
|
|
52
|
+
export type ContextMenuRadioItemProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children" | "onSelect"> & {
|
|
53
|
+
children: ReactNode;
|
|
54
|
+
value: string;
|
|
55
|
+
closeOnSelect?: boolean;
|
|
56
|
+
textValue?: string;
|
|
57
|
+
onSelect?: (event: MouseEvent<HTMLElement>) => void;
|
|
58
|
+
};
|
|
59
|
+
export type ContextMenuSubProps = {
|
|
60
|
+
children: ReactNode;
|
|
61
|
+
defaultOpen?: boolean;
|
|
62
|
+
};
|
|
63
|
+
export type ContextMenuSubTriggerProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children"> & {
|
|
64
|
+
children: ReactNode;
|
|
65
|
+
};
|
|
66
|
+
export type ContextMenuSubContentProps = Omit<HTMLAttributes<HTMLDivElement>, "children"> & {
|
|
67
|
+
children: ReactNode;
|
|
68
|
+
maxHeight?: CSSProperties["maxHeight"];
|
|
69
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { ButtonHTMLAttributes, CSSProperties, HTMLAttributes, MouseEvent, ReactNode, Ref } from "react";
|
|
2
|
+
export type ContextMenuItemVariant = "default" | "danger";
|
|
3
|
+
export type ContextMenuCheckedState = boolean | "indeterminate";
|
|
4
|
+
export type ContextMenuProps = {
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
open?: boolean;
|
|
7
|
+
defaultOpen?: boolean;
|
|
8
|
+
onOpenChange?: (open: boolean) => void;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
triggerClassName?: string;
|
|
11
|
+
contentClassName?: string;
|
|
12
|
+
};
|
|
13
|
+
export type ContextMenuTriggerProps = Omit<HTMLAttributes<HTMLElement>, "children"> & {
|
|
14
|
+
children: ReactNode;
|
|
15
|
+
asChild?: boolean;
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
ref?: Ref<HTMLElement>;
|
|
18
|
+
};
|
|
19
|
+
export type ContextMenuContentProps = Omit<HTMLAttributes<HTMLDivElement>, "children"> & {
|
|
20
|
+
children: ReactNode;
|
|
21
|
+
maxHeight?: CSSProperties["maxHeight"];
|
|
22
|
+
};
|
|
23
|
+
export type ContextMenuItemProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children" | "onSelect"> & {
|
|
24
|
+
children: ReactNode;
|
|
25
|
+
textValue?: string;
|
|
26
|
+
closeOnSelect?: boolean;
|
|
27
|
+
variant?: ContextMenuItemVariant;
|
|
28
|
+
onSelect?: (event: MouseEvent<HTMLElement>) => void;
|
|
29
|
+
};
|
|
30
|
+
export type ContextMenuGroupProps = Omit<HTMLAttributes<HTMLDivElement>, "children"> & {
|
|
31
|
+
children: ReactNode;
|
|
32
|
+
};
|
|
33
|
+
export type ContextMenuLabelProps = Omit<HTMLAttributes<HTMLDivElement>, "children"> & {
|
|
34
|
+
children: ReactNode;
|
|
35
|
+
};
|
|
36
|
+
export type ContextMenuSeparatorProps = HTMLAttributes<HTMLDivElement>;
|
|
37
|
+
export type ContextMenuCheckboxItemProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children" | "onSelect"> & {
|
|
38
|
+
children: ReactNode;
|
|
39
|
+
checked?: ContextMenuCheckedState;
|
|
40
|
+
defaultChecked?: boolean;
|
|
41
|
+
onCheckedChange?: (checked: boolean) => void;
|
|
42
|
+
closeOnSelect?: boolean;
|
|
43
|
+
textValue?: string;
|
|
44
|
+
onSelect?: (event: MouseEvent<HTMLElement>) => void;
|
|
45
|
+
};
|
|
46
|
+
export type ContextMenuRadioGroupProps = Omit<HTMLAttributes<HTMLDivElement>, "children"> & {
|
|
47
|
+
children: ReactNode;
|
|
48
|
+
value?: string;
|
|
49
|
+
defaultValue?: string;
|
|
50
|
+
onValueChange?: (value: string) => void;
|
|
51
|
+
};
|
|
52
|
+
export type ContextMenuRadioItemProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children" | "onSelect"> & {
|
|
53
|
+
children: ReactNode;
|
|
54
|
+
value: string;
|
|
55
|
+
closeOnSelect?: boolean;
|
|
56
|
+
textValue?: string;
|
|
57
|
+
onSelect?: (event: MouseEvent<HTMLElement>) => void;
|
|
58
|
+
};
|
|
59
|
+
export type ContextMenuSubProps = {
|
|
60
|
+
children: ReactNode;
|
|
61
|
+
defaultOpen?: boolean;
|
|
62
|
+
};
|
|
63
|
+
export type ContextMenuSubTriggerProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children"> & {
|
|
64
|
+
children: ReactNode;
|
|
65
|
+
};
|
|
66
|
+
export type ContextMenuSubContentProps = Omit<HTMLAttributes<HTMLDivElement>, "children"> & {
|
|
67
|
+
children: ReactNode;
|
|
68
|
+
maxHeight?: CSSProperties["maxHeight"];
|
|
69
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// src/components/overlays/context-menu/context-menu.utils.ts
|
|
2
|
+
const normalizeMenuText = (value) => value.normalize("NFKD").replace(/\p{Diacritic}/gu, "").trim().toLocaleLowerCase();
|
|
3
|
+
const findNextMenuItemIndex = (items, currentIndex, direction, loop = true) => {
|
|
4
|
+
if (items.length === 0) return -1;
|
|
5
|
+
for (let step = 1; step <= items.length; step += 1) {
|
|
6
|
+
let index = currentIndex + step * direction;
|
|
7
|
+
if (loop) index = (index + items.length) % items.length;
|
|
8
|
+
else if (index < 0 || index >= items.length) return -1;
|
|
9
|
+
if (!items[index].disabled) return index;
|
|
10
|
+
}
|
|
11
|
+
return -1;
|
|
12
|
+
};
|
|
13
|
+
const findMenuItemIndexByText = (items, query, currentIndex = -1) => {
|
|
14
|
+
const normalizedQuery = normalizeMenuText(query);
|
|
15
|
+
if (!normalizedQuery || items.length === 0) return -1;
|
|
16
|
+
for (let step = 1; step <= items.length; step += 1) {
|
|
17
|
+
const index = (currentIndex + step + items.length) % items.length;
|
|
18
|
+
const item = items[index];
|
|
19
|
+
if (!item.disabled && normalizeMenuText(item.text ?? "").startsWith(normalizedQuery)) return index;
|
|
20
|
+
}
|
|
21
|
+
return -1;
|
|
22
|
+
};
|
|
23
|
+
const setRef = (ref, value) => {
|
|
24
|
+
if (typeof ref === "function") ref(value);
|
|
25
|
+
else if (ref) ref.current = value;
|
|
26
|
+
};
|
|
27
|
+
const getEnabledMenuItems = (menu) => Array.from(menu.querySelectorAll("[data-puzzle-context-menu-item=\"true\"]")).filter((item) => item.getAttribute("aria-disabled") !== "true" && !("disabled" in item && Boolean(item.disabled)) && !item.hasAttribute("hidden") && !item.closest("[hidden], [inert], [aria-hidden='true']"));
|
|
28
|
+
const focusElement = (element) => {
|
|
29
|
+
if (!element) return;
|
|
30
|
+
try {
|
|
31
|
+
element.focus({ preventScroll: true });
|
|
32
|
+
} catch {
|
|
33
|
+
element.focus();
|
|
34
|
+
}
|
|
35
|
+
element.scrollIntoView?.({ block: "nearest" });
|
|
36
|
+
};
|
|
37
|
+
const getMenuItemText = (item) => item.dataset.textValue ?? item.querySelector(".puzzle-context-menu__item-label")?.textContent ?? item.textContent ?? "";
|
|
38
|
+
exports.findMenuItemIndexByText = findMenuItemIndexByText;
|
|
39
|
+
exports.findNextMenuItemIndex = findNextMenuItemIndex;
|
|
40
|
+
exports.focusElement = focusElement;
|
|
41
|
+
exports.getEnabledMenuItems = getEnabledMenuItems;
|
|
42
|
+
exports.getMenuItemText = getMenuItemText;
|
|
43
|
+
exports.normalizeMenuText = normalizeMenuText;
|
|
44
|
+
exports.setRef = setRef;
|
|
45
|
+
|
|
46
|
+
//# sourceMappingURL=context-menu.utils.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-menu.utils.cjs","names":[],"sources":["../../../../src/components/overlays/context-menu/context-menu.utils.ts"],"sourcesContent":["import type { MutableRefObject, Ref } from \"react\";\n\nexport type MenuItemLike = {\n disabled?: boolean;\n text?: string;\n};\n\nconst normalizeMenuText = (value: string): string =>\n value\n .normalize(\"NFKD\")\n .replace(/\\p{Diacritic}/gu, \"\")\n .trim()\n .toLocaleLowerCase();\n\nconst findNextMenuItemIndex = <T extends MenuItemLike>(\n items: readonly T[],\n currentIndex: number,\n direction: -1 | 1,\n loop = true,\n): number => {\n if (items.length === 0) {\n return -1;\n }\n\n for (let step = 1; step <= items.length; step += 1) {\n let index = currentIndex + step * direction;\n\n if (loop) {\n index = (index + items.length) % items.length;\n } else if (index < 0 || index >= items.length) {\n return -1;\n }\n\n if (!items[index]!.disabled) {\n return index;\n }\n }\n\n return -1;\n};\n\nconst findMenuItemIndexByText = <T extends MenuItemLike>(\n items: readonly T[],\n query: string,\n currentIndex = -1,\n): number => {\n const normalizedQuery = normalizeMenuText(query);\n\n if (!normalizedQuery || items.length === 0) {\n return -1;\n }\n\n for (let step = 1; step <= items.length; step += 1) {\n const index = (currentIndex + step + items.length) % items.length;\n const item = items[index]!;\n\n if (\n !item.disabled &&\n normalizeMenuText(item.text ?? \"\").startsWith(normalizedQuery)\n ) {\n return index;\n }\n }\n\n return -1;\n};\n\nconst setRef = <T>(ref: Ref<T> | undefined, value: T | null): void => {\n if (typeof ref === \"function\") {\n ref(value);\n } else if (ref) {\n (ref as MutableRefObject<T | null>).current = value;\n }\n};\n\nconst getEnabledMenuItems = (menu: HTMLElement): HTMLElement[] =>\n Array.from(\n menu.querySelectorAll<HTMLElement>(\n '[data-puzzle-context-menu-item=\"true\"]',\n ),\n ).filter(\n (item) =>\n item.getAttribute(\"aria-disabled\") !== \"true\" &&\n !(\"disabled\" in item && Boolean((item as HTMLButtonElement).disabled)) &&\n !item.hasAttribute(\"hidden\") &&\n !item.closest(\"[hidden], [inert], [aria-hidden='true']\"),\n );\n\nconst focusElement = (element: HTMLElement | undefined): void => {\n if (!element) {\n return;\n }\n\n try {\n element.focus({ preventScroll: true });\n } catch {\n element.focus();\n }\n\n element.scrollIntoView?.({ block: \"nearest\" });\n};\n\nconst getMenuItemText = (item: HTMLElement): string =>\n item.dataset.textValue ??\n item.querySelector(\".puzzle-context-menu__item-label\")?.textContent ??\n item.textContent ??\n \"\";\n\nexport {\n findMenuItemIndexByText,\n findNextMenuItemIndex,\n focusElement,\n getEnabledMenuItems,\n getMenuItemText,\n normalizeMenuText,\n setRef,\n};\n"],"mappings":";AAOA,MAAM,qBAAqB,UACzB,MACG,UAAU,MAAM,CAAC,CACjB,QAAQ,mBAAmB,EAAE,CAAC,CAC9B,KAAK,CAAC,CACN,kBAAkB;AAEvB,MAAM,yBACJ,OACA,cACA,WACA,OAAO,SACI;CACX,IAAI,MAAM,WAAW,GACnB,OAAO;CAGT,KAAK,IAAI,OAAO,GAAG,QAAQ,MAAM,QAAQ,QAAQ,GAAG;EAClD,IAAI,QAAQ,eAAe,OAAO;EAElC,IAAI,MACF,SAAS,QAAQ,MAAM,UAAU,MAAM;OAClC,IAAI,QAAQ,KAAK,SAAS,MAAM,QACrC,OAAO;EAGT,IAAI,CAAC,MAAM,MAAM,CAAE,UACjB,OAAO;CAEX;CAEA,OAAO;AACT;AAEA,MAAM,2BACJ,OACA,OACA,eAAe,OACJ;CACX,MAAM,kBAAkB,kBAAkB,KAAK;CAE/C,IAAI,CAAC,mBAAmB,MAAM,WAAW,GACvC,OAAO;CAGT,KAAK,IAAI,OAAO,GAAG,QAAQ,MAAM,QAAQ,QAAQ,GAAG;EAClD,MAAM,SAAS,eAAe,OAAO,MAAM,UAAU,MAAM;EAC3D,MAAM,OAAO,MAAM;EAEnB,IACE,CAAC,KAAK,YACN,kBAAkB,KAAK,QAAQ,EAAE,CAAC,CAAC,WAAW,eAAe,GAE7D,OAAO;CAEX;CAEA,OAAO;AACT;AAEA,MAAM,UAAa,KAAyB,UAA0B;CACpE,IAAI,OAAO,QAAQ,YACjB,IAAI,KAAK;MACJ,IAAI,KACT,IAAoC,UAAU;AAElD;AAEA,MAAM,uBAAuB,SAC3B,MAAM,KACJ,KAAK,iBACH,0CACF,CACF,CAAC,CAAC,QACC,SACC,KAAK,aAAa,eAAe,MAAM,UACvC,EAAE,cAAc,QAAQ,QAAS,KAA2B,QAAQ,MACpE,CAAC,KAAK,aAAa,QAAQ,KAC3B,CAAC,KAAK,QAAQ,yCAAyC,CAC3D;AAEF,MAAM,gBAAgB,YAA2C;CAC/D,IAAI,CAAC,SACH;CAGF,IAAI;EACF,QAAQ,MAAM,EAAE,eAAe,KAAK,CAAC;CACvC,QAAQ;EACN,QAAQ,MAAM;CAChB;CAEA,QAAQ,iBAAiB,EAAE,OAAO,UAAU,CAAC;AAC/C;AAEA,MAAM,mBAAmB,SACvB,KAAK,QAAQ,aACb,KAAK,cAAc,kCAAkC,CAAC,EAAE,eACxD,KAAK,eACL"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// src/components/overlays/context-menu/context-menu.utils.ts
|
|
2
|
+
const normalizeMenuText = (value) => value.normalize("NFKD").replace(/\p{Diacritic}/gu, "").trim().toLocaleLowerCase();
|
|
3
|
+
const findNextMenuItemIndex = (items, currentIndex, direction, loop = true) => {
|
|
4
|
+
if (items.length === 0) return -1;
|
|
5
|
+
for (let step = 1; step <= items.length; step += 1) {
|
|
6
|
+
let index = currentIndex + step * direction;
|
|
7
|
+
if (loop) index = (index + items.length) % items.length;
|
|
8
|
+
else if (index < 0 || index >= items.length) return -1;
|
|
9
|
+
if (!items[index].disabled) return index;
|
|
10
|
+
}
|
|
11
|
+
return -1;
|
|
12
|
+
};
|
|
13
|
+
const findMenuItemIndexByText = (items, query, currentIndex = -1) => {
|
|
14
|
+
const normalizedQuery = normalizeMenuText(query);
|
|
15
|
+
if (!normalizedQuery || items.length === 0) return -1;
|
|
16
|
+
for (let step = 1; step <= items.length; step += 1) {
|
|
17
|
+
const index = (currentIndex + step + items.length) % items.length;
|
|
18
|
+
const item = items[index];
|
|
19
|
+
if (!item.disabled && normalizeMenuText(item.text ?? "").startsWith(normalizedQuery)) return index;
|
|
20
|
+
}
|
|
21
|
+
return -1;
|
|
22
|
+
};
|
|
23
|
+
const setRef = (ref, value) => {
|
|
24
|
+
if (typeof ref === "function") ref(value);
|
|
25
|
+
else if (ref) ref.current = value;
|
|
26
|
+
};
|
|
27
|
+
const getEnabledMenuItems = (menu) => Array.from(menu.querySelectorAll("[data-puzzle-context-menu-item=\"true\"]")).filter((item) => item.getAttribute("aria-disabled") !== "true" && !("disabled" in item && Boolean(item.disabled)) && !item.hasAttribute("hidden") && !item.closest("[hidden], [inert], [aria-hidden='true']"));
|
|
28
|
+
const focusElement = (element) => {
|
|
29
|
+
if (!element) return;
|
|
30
|
+
try {
|
|
31
|
+
element.focus({ preventScroll: true });
|
|
32
|
+
} catch {
|
|
33
|
+
element.focus();
|
|
34
|
+
}
|
|
35
|
+
element.scrollIntoView?.({ block: "nearest" });
|
|
36
|
+
};
|
|
37
|
+
const getMenuItemText = (item) => item.dataset.textValue ?? item.querySelector(".puzzle-context-menu__item-label")?.textContent ?? item.textContent ?? "";
|
|
38
|
+
export { findMenuItemIndexByText, findNextMenuItemIndex, focusElement, getEnabledMenuItems, getMenuItemText, normalizeMenuText, setRef };
|
|
39
|
+
|
|
40
|
+
//# sourceMappingURL=context-menu.utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-menu.utils.js","names":[],"sources":["../../../../src/components/overlays/context-menu/context-menu.utils.ts"],"sourcesContent":["import type { MutableRefObject, Ref } from \"react\";\n\nexport type MenuItemLike = {\n disabled?: boolean;\n text?: string;\n};\n\nconst normalizeMenuText = (value: string): string =>\n value\n .normalize(\"NFKD\")\n .replace(/\\p{Diacritic}/gu, \"\")\n .trim()\n .toLocaleLowerCase();\n\nconst findNextMenuItemIndex = <T extends MenuItemLike>(\n items: readonly T[],\n currentIndex: number,\n direction: -1 | 1,\n loop = true,\n): number => {\n if (items.length === 0) {\n return -1;\n }\n\n for (let step = 1; step <= items.length; step += 1) {\n let index = currentIndex + step * direction;\n\n if (loop) {\n index = (index + items.length) % items.length;\n } else if (index < 0 || index >= items.length) {\n return -1;\n }\n\n if (!items[index]!.disabled) {\n return index;\n }\n }\n\n return -1;\n};\n\nconst findMenuItemIndexByText = <T extends MenuItemLike>(\n items: readonly T[],\n query: string,\n currentIndex = -1,\n): number => {\n const normalizedQuery = normalizeMenuText(query);\n\n if (!normalizedQuery || items.length === 0) {\n return -1;\n }\n\n for (let step = 1; step <= items.length; step += 1) {\n const index = (currentIndex + step + items.length) % items.length;\n const item = items[index]!;\n\n if (\n !item.disabled &&\n normalizeMenuText(item.text ?? \"\").startsWith(normalizedQuery)\n ) {\n return index;\n }\n }\n\n return -1;\n};\n\nconst setRef = <T>(ref: Ref<T> | undefined, value: T | null): void => {\n if (typeof ref === \"function\") {\n ref(value);\n } else if (ref) {\n (ref as MutableRefObject<T | null>).current = value;\n }\n};\n\nconst getEnabledMenuItems = (menu: HTMLElement): HTMLElement[] =>\n Array.from(\n menu.querySelectorAll<HTMLElement>(\n '[data-puzzle-context-menu-item=\"true\"]',\n ),\n ).filter(\n (item) =>\n item.getAttribute(\"aria-disabled\") !== \"true\" &&\n !(\"disabled\" in item && Boolean((item as HTMLButtonElement).disabled)) &&\n !item.hasAttribute(\"hidden\") &&\n !item.closest(\"[hidden], [inert], [aria-hidden='true']\"),\n );\n\nconst focusElement = (element: HTMLElement | undefined): void => {\n if (!element) {\n return;\n }\n\n try {\n element.focus({ preventScroll: true });\n } catch {\n element.focus();\n }\n\n element.scrollIntoView?.({ block: \"nearest\" });\n};\n\nconst getMenuItemText = (item: HTMLElement): string =>\n item.dataset.textValue ??\n item.querySelector(\".puzzle-context-menu__item-label\")?.textContent ??\n item.textContent ??\n \"\";\n\nexport {\n findMenuItemIndexByText,\n findNextMenuItemIndex,\n focusElement,\n getEnabledMenuItems,\n getMenuItemText,\n normalizeMenuText,\n setRef,\n};\n"],"mappings":";AAOA,MAAM,qBAAqB,UACzB,MACG,UAAU,MAAM,CAAC,CACjB,QAAQ,mBAAmB,EAAE,CAAC,CAC9B,KAAK,CAAC,CACN,kBAAkB;AAEvB,MAAM,yBACJ,OACA,cACA,WACA,OAAO,SACI;CACX,IAAI,MAAM,WAAW,GACnB,OAAO;CAGT,KAAK,IAAI,OAAO,GAAG,QAAQ,MAAM,QAAQ,QAAQ,GAAG;EAClD,IAAI,QAAQ,eAAe,OAAO;EAElC,IAAI,MACF,SAAS,QAAQ,MAAM,UAAU,MAAM;OAClC,IAAI,QAAQ,KAAK,SAAS,MAAM,QACrC,OAAO;EAGT,IAAI,CAAC,MAAM,MAAM,CAAE,UACjB,OAAO;CAEX;CAEA,OAAO;AACT;AAEA,MAAM,2BACJ,OACA,OACA,eAAe,OACJ;CACX,MAAM,kBAAkB,kBAAkB,KAAK;CAE/C,IAAI,CAAC,mBAAmB,MAAM,WAAW,GACvC,OAAO;CAGT,KAAK,IAAI,OAAO,GAAG,QAAQ,MAAM,QAAQ,QAAQ,GAAG;EAClD,MAAM,SAAS,eAAe,OAAO,MAAM,UAAU,MAAM;EAC3D,MAAM,OAAO,MAAM;EAEnB,IACE,CAAC,KAAK,YACN,kBAAkB,KAAK,QAAQ,EAAE,CAAC,CAAC,WAAW,eAAe,GAE7D,OAAO;CAEX;CAEA,OAAO;AACT;AAEA,MAAM,UAAa,KAAyB,UAA0B;CACpE,IAAI,OAAO,QAAQ,YACjB,IAAI,KAAK;MACJ,IAAI,KACT,IAAoC,UAAU;AAElD;AAEA,MAAM,uBAAuB,SAC3B,MAAM,KACJ,KAAK,iBACH,0CACF,CACF,CAAC,CAAC,QACC,SACC,KAAK,aAAa,eAAe,MAAM,UACvC,EAAE,cAAc,QAAQ,QAAS,KAA2B,QAAQ,MACpE,CAAC,KAAK,aAAa,QAAQ,KAC3B,CAAC,KAAK,QAAQ,yCAAyC,CAC3D;AAEF,MAAM,gBAAgB,YAA2C;CAC/D,IAAI,CAAC,SACH;CAGF,IAAI;EACF,QAAQ,MAAM,EAAE,eAAe,KAAK,CAAC;CACvC,QAAQ;EACN,QAAQ,MAAM;CAChB;CAEA,QAAQ,iBAAiB,EAAE,OAAO,UAAU,CAAC;AAC/C;AAEA,MAAM,mBAAmB,SACvB,KAAK,QAAQ,aACb,KAAK,cAAc,kCAAkC,CAAC,EAAE,eACxD,KAAK,eACL"}
|