@bccampus/ui-components 0.6.1 → 0.7.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/dist/components/ui/button.d.ts +2 -1
- package/dist/components/ui/button.js +11 -2
- package/dist/components/ui/composite/CompositeDataItem.js +7 -12
- package/dist/components/ui/composite/FocusProvider/AbstractFocusProvider.d.ts +1 -0
- package/dist/components/ui/composite/FocusProvider/AbstractFocusProvider.js +1 -4
- package/dist/components/ui/composite/FocusProvider/ListboxFocusProvider.js +8 -16
- package/dist/components/ui/composite/SelectionProvider/AbstractSelectionProvider.d.ts +8 -0
- package/dist/components/ui/composite/SelectionProvider/AbstractSelectionProvider.js +4 -0
- package/dist/components/ui/composite/SelectionProvider/MultipleSelectionProvider.js +4 -0
- package/dist/components/ui/composite/SelectionProvider/SingleSelectionProvider.js +5 -0
- package/dist/components/ui/composite/composite-component-item.d.ts +1 -1
- package/dist/components/ui/composite/composite-component-item.js +5 -3
- package/dist/components/ui/composite/composite-component.d.ts +1 -1
- package/dist/components/ui/composite/composite-component.js +73 -25
- package/dist/components/ui/composite/listbox.d.ts +1 -1
- package/dist/components/ui/composite/listbox.js +39 -34
- package/dist/components/ui/composite/types.d.ts +13 -5
- package/dist/hooks/use-keyboard-event.d.ts +3 -3
- package/dist/hooks/use-keyboard-event.js +2 -2
- package/dist/hooks/use-required-ref.d.ts +1 -0
- package/dist/hooks/use-required-ref.js +8 -0
- package/package.json +1 -1
- package/src/components/ui/button.tsx +14 -3
- package/src/components/ui/composite/CompositeDataItem.ts +105 -110
- package/src/components/ui/composite/FocusProvider/AbstractFocusProvider.ts +8 -14
- package/src/components/ui/composite/FocusProvider/ListboxFocusProvider.ts +15 -29
- package/src/components/ui/composite/SelectionProvider/AbstractSelectionProvider.ts +22 -8
- package/src/components/ui/composite/SelectionProvider/MultipleSelectionProvider.ts +9 -5
- package/src/components/ui/composite/SelectionProvider/SingleSelectionProvider.ts +20 -7
- package/src/components/ui/composite/composite-component-item.tsx +3 -1
- package/src/components/ui/composite/composite-component.tsx +70 -15
- package/src/components/ui/composite/listbox.tsx +39 -36
- package/src/components/ui/composite/types.ts +69 -55
- package/src/hooks/use-keyboard-event.ts +13 -13
- package/src/hooks/use-required-ref.ts +6 -0
|
@@ -3,9 +3,10 @@ declare const buttonVariants: (props?: ({
|
|
|
3
3
|
variant?: "item" | "default" | "secondary" | "outline" | "ghost" | "destructive" | null | undefined;
|
|
4
4
|
block?: boolean | null | undefined;
|
|
5
5
|
size?: "text" | "default" | "sm" | "lg" | "icon" | null | undefined;
|
|
6
|
+
icon?: boolean | null | undefined;
|
|
6
7
|
} & import('class-variance-authority/dist/types').ClassProp) | undefined) => string;
|
|
7
8
|
export type ButtonProps = React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & {
|
|
8
9
|
asChild?: boolean;
|
|
9
10
|
};
|
|
10
|
-
declare function Button({ className, variant, size, block, asChild, ...props }: ButtonProps): import("react").JSX.Element;
|
|
11
|
+
declare function Button({ className, variant, size, block, icon, asChild, ...props }: ButtonProps): import("react").JSX.Element;
|
|
11
12
|
export { Button, buttonVariants };
|
|
@@ -24,8 +24,17 @@ const buttonVariants = cva(
|
|
|
24
24
|
lg: "h-10 text-base",
|
|
25
25
|
icon: "size-9",
|
|
26
26
|
text: "h-9 text-base"
|
|
27
|
+
},
|
|
28
|
+
icon: {
|
|
29
|
+
true: "py-1 px-1 has-[>svg]:px-1 gap-0 [&_svg:not([class*='size-'])]:size-full",
|
|
30
|
+
false: ""
|
|
27
31
|
}
|
|
28
32
|
},
|
|
33
|
+
compoundVariants: [
|
|
34
|
+
{ size: ["sm"], icon: true, className: "size-8" },
|
|
35
|
+
{ size: ["default", "icon", "text"], icon: true, className: "size-9" },
|
|
36
|
+
{ size: ["lg"], icon: true, className: "size-10" }
|
|
37
|
+
],
|
|
29
38
|
defaultVariants: {
|
|
30
39
|
size: "default",
|
|
31
40
|
block: false,
|
|
@@ -33,9 +42,9 @@ const buttonVariants = cva(
|
|
|
33
42
|
}
|
|
34
43
|
}
|
|
35
44
|
);
|
|
36
|
-
function Button({ className, variant, size, block, asChild = false, ...props }) {
|
|
45
|
+
function Button({ className, variant, size, block, icon, asChild = false, ...props }) {
|
|
37
46
|
const Comp = asChild ? Slot : "button";
|
|
38
|
-
return /* @__PURE__ */ jsx(Comp, { "data-slot": "button", className: cn(buttonVariants({ size, block, variant }), className), ...props });
|
|
47
|
+
return /* @__PURE__ */ jsx(Comp, { "data-slot": "button", className: cn(buttonVariants({ size, block, variant, icon }), className), ...props });
|
|
39
48
|
}
|
|
40
49
|
export {
|
|
41
50
|
Button,
|
|
@@ -35,29 +35,24 @@ class CompositeDataItem {
|
|
|
35
35
|
}
|
|
36
36
|
*[Symbol.iterator]() {
|
|
37
37
|
if (this.key !== "ALL") yield this;
|
|
38
|
-
if (this.children)
|
|
39
|
-
for (const child of this.children)
|
|
40
|
-
for (const item of child) yield item;
|
|
38
|
+
if (this.children) for (const child of this.children) for (const item of child) yield item;
|
|
41
39
|
}
|
|
42
40
|
toJSON() {
|
|
43
41
|
const json = { ...this.data.get() };
|
|
44
42
|
if (this.children) {
|
|
45
43
|
json[this.childrenProp] = [];
|
|
46
|
-
for (const child of this.children)
|
|
47
|
-
json[this.childrenProp].push(child.toJSON());
|
|
44
|
+
for (const child of this.children) json[this.childrenProp].push(child.toJSON());
|
|
48
45
|
}
|
|
49
46
|
return json;
|
|
50
47
|
}
|
|
51
48
|
descendants() {
|
|
52
49
|
if (!this.children) return [];
|
|
53
50
|
const descendants = [];
|
|
54
|
-
this.children.forEach(
|
|
55
|
-
(child)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
);
|
|
51
|
+
this.children.forEach((child) => {
|
|
52
|
+
descendants.push(child);
|
|
53
|
+
const childDescendants = child.descendants();
|
|
54
|
+
if (childDescendants) descendants.push(...childDescendants);
|
|
55
|
+
});
|
|
61
56
|
return descendants;
|
|
62
57
|
}
|
|
63
58
|
ancestors() {
|
|
@@ -26,6 +26,7 @@ export declare abstract class AbstractFocusProvider<T extends object> implements
|
|
|
26
26
|
isFocusable(itemAtom: CompositeDataItem<T>): boolean;
|
|
27
27
|
focus(itemKey: CompositeItemKey): void;
|
|
28
28
|
focus(item: CompositeDataItem<T>): void;
|
|
29
|
+
focus(item: CompositeDataItem<T> | CompositeItemKey | null): void;
|
|
29
30
|
abstract setFocusPointers(): readonly [first: CompositeDataItem<T> | null, last: CompositeDataItem<T> | null];
|
|
30
31
|
abstract focusUp(): void;
|
|
31
32
|
abstract focusDown(): void;
|
|
@@ -11,16 +11,13 @@ class AbstractFocusProvider {
|
|
|
11
11
|
this.dataOptions = dataOptions;
|
|
12
12
|
const [first, last] = this.setFocusPointers();
|
|
13
13
|
this.firstFocusableItem = first;
|
|
14
|
-
if (this.firstFocusableItem) {
|
|
15
|
-
this.firstFocusableItem.state.setKey("focused", true);
|
|
16
|
-
this.focusedItem.set(this.firstFocusableItem);
|
|
17
|
-
}
|
|
18
14
|
this.lastFocusableItem = last;
|
|
19
15
|
}
|
|
20
16
|
isFocusable(itemAtom) {
|
|
21
17
|
return !itemAtom.state.get().disabled;
|
|
22
18
|
}
|
|
23
19
|
focus(item) {
|
|
20
|
+
if (!item) return;
|
|
24
21
|
const _item = item instanceof CompositeDataItem ? item : this.data.lookup.get(item);
|
|
25
22
|
if (!_item) return;
|
|
26
23
|
if (this.focusedItem.get()) {
|
|
@@ -21,36 +21,28 @@ class ListboxFocusProvider extends AbstractFocusProvider {
|
|
|
21
21
|
return [first, last];
|
|
22
22
|
}
|
|
23
23
|
focusUp() {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
const focusedItem = this.focusedItem.get();
|
|
25
|
+
const focusTarget = focusedItem ? focusedItem.pointers.up : this.firstFocusableItem;
|
|
26
|
+
if (focusTarget) this.focus(focusTarget);
|
|
27
27
|
}
|
|
28
28
|
focusDown() {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
const focusedItem = this.focusedItem.get();
|
|
30
|
+
const focusTarget = focusedItem ? focusedItem.pointers.down : this.firstFocusableItem;
|
|
31
|
+
if (focusTarget) this.focus(focusTarget);
|
|
32
32
|
}
|
|
33
33
|
focusLeft() {
|
|
34
|
-
throw new Error("Method not implemented.");
|
|
35
34
|
}
|
|
36
35
|
focusRight() {
|
|
37
|
-
throw new Error("Method not implemented.");
|
|
38
36
|
}
|
|
39
37
|
focusPageUp() {
|
|
40
|
-
throw new Error("Method not implemented.");
|
|
41
38
|
}
|
|
42
39
|
focesPageDown() {
|
|
43
|
-
throw new Error("Method not implemented.");
|
|
44
40
|
}
|
|
45
41
|
focusToFirst() {
|
|
46
|
-
|
|
47
|
-
this.focus(this.firstFocusableItem);
|
|
48
|
-
}
|
|
42
|
+
this.focus(this.firstFocusableItem);
|
|
49
43
|
}
|
|
50
44
|
focusToLast() {
|
|
51
|
-
|
|
52
|
-
this.focus(this.lastFocusableItem);
|
|
53
|
-
}
|
|
45
|
+
this.focus(this.lastFocusableItem);
|
|
54
46
|
}
|
|
55
47
|
focusToFirstInRow() {
|
|
56
48
|
throw new Error("Method not implemented.");
|
|
@@ -7,10 +7,17 @@ export interface SelectionProvider {
|
|
|
7
7
|
deselect(itemKey?: CompositeItemKey, recursive?: boolean): void;
|
|
8
8
|
toggleSelect(itemKey?: CompositeItemKey, recursive?: boolean): void;
|
|
9
9
|
}
|
|
10
|
+
export interface SelectionProviderOptions<T extends object> {
|
|
11
|
+
onSelect?: (item: T) => void;
|
|
12
|
+
onDeselect?: (item: T) => void;
|
|
13
|
+
onChange?: (item: Set<CompositeItemKey>) => void;
|
|
14
|
+
}
|
|
10
15
|
export declare abstract class AbstractSelectionProvider<T extends object> {
|
|
11
16
|
protected data: CompositeData<T>;
|
|
12
17
|
protected dataOptions: CompositeDataOptions<T>;
|
|
13
18
|
selectedKeys: PreinitializedWritableAtom<Set<CompositeItemKey>>;
|
|
19
|
+
options?: SelectionProviderOptions<T>;
|
|
20
|
+
constructor(options?: SelectionProviderOptions<T>);
|
|
14
21
|
init(data: CompositeData<T>, dataOptions: CompositeDataOptions<T>): void;
|
|
15
22
|
abstract select(itemKey?: CompositeItemKey, recursive?: boolean): void;
|
|
16
23
|
abstract select(item: CompositeDataItem<T>, recursive?: boolean): void;
|
|
@@ -20,4 +27,5 @@ export declare abstract class AbstractSelectionProvider<T extends object> {
|
|
|
20
27
|
abstract deselect(item?: CompositeDataItem<T> | CompositeItemKey, recursive?: boolean): void;
|
|
21
28
|
toggleSelect(itemKey?: CompositeItemKey, recursive?: boolean): void;
|
|
22
29
|
toggleSelect(item: CompositeDataItem<T>, recursive?: boolean): void;
|
|
30
|
+
toggleSelect(item: CompositeDataItem<T> | CompositeItemKey, recursive?: boolean): void;
|
|
23
31
|
}
|
|
@@ -6,13 +6,17 @@ class MultipleSelectionProvider extends AbstractSelectionProvider {
|
|
|
6
6
|
const _item = item instanceof CompositeDataItem ? item : this.data.lookup.get(item);
|
|
7
7
|
if (!_item) return;
|
|
8
8
|
const selectedKeys = _item.select(recursive);
|
|
9
|
+
this.options?.onSelect?.(_item.data.get());
|
|
9
10
|
this.selectedKeys.set(union(this.selectedKeys.get(), selectedKeys));
|
|
11
|
+
this.options?.onChange?.(this.selectedKeys.get());
|
|
10
12
|
}
|
|
11
13
|
deselect(item, recursive = false) {
|
|
12
14
|
const _item = item instanceof CompositeDataItem ? item : this.data.lookup.get(item);
|
|
13
15
|
if (!_item) return;
|
|
14
16
|
const deselectedKeys = _item.deselect(recursive);
|
|
17
|
+
this.options?.onDeselect?.(_item.data.get());
|
|
15
18
|
this.selectedKeys.set(difference(this.selectedKeys.get(), deselectedKeys));
|
|
19
|
+
this.options?.onChange?.(this.selectedKeys.get());
|
|
16
20
|
}
|
|
17
21
|
}
|
|
18
22
|
export {
|
|
@@ -9,15 +9,20 @@ class SingleSelectionProvider extends AbstractSelectionProvider {
|
|
|
9
9
|
const _item2 = this.data.lookup.get(selectedKey);
|
|
10
10
|
if (!_item2) return;
|
|
11
11
|
_item2.deselect(false);
|
|
12
|
+
this.options?.onDeselect?.(_item2.data.get());
|
|
12
13
|
});
|
|
13
14
|
const selectedKeys = _item.select(false);
|
|
15
|
+
this.options?.onSelect?.(_item.data.get());
|
|
14
16
|
this.selectedKeys.set(new Set(selectedKeys));
|
|
17
|
+
this.options?.onChange?.(this.selectedKeys.get());
|
|
15
18
|
}
|
|
16
19
|
deselect(item, _recursive = false) {
|
|
17
20
|
const _item = item ? item instanceof CompositeDataItem ? item : this.data.lookup.get(item) : this.data.focusProvider.focusedItem.get();
|
|
18
21
|
if (!_item) return;
|
|
19
22
|
const deselectedKeys = _item.deselect(false);
|
|
23
|
+
this.options?.onDeselect?.(_item.data.get());
|
|
20
24
|
this.selectedKeys.set(difference(this.selectedKeys.get(), deselectedKeys));
|
|
25
|
+
this.options?.onChange?.(this.selectedKeys.get());
|
|
21
26
|
}
|
|
22
27
|
}
|
|
23
28
|
export {
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { CompositeItemProps } from './types';
|
|
2
|
-
export declare function CompositeComponentItem<T extends object>({ id, className, item, role, itemMouseEventHandler, itemKeyboardEventHandler, render, }: CompositeItemProps<T>): import("react").JSX.Element;
|
|
2
|
+
export declare function CompositeComponentItem<T extends object>({ id, className, item, role, itemMouseEventHandler, itemKeyboardEventHandler, render, softFocus, }: CompositeItemProps<T>): import("react").JSX.Element;
|
|
@@ -8,7 +8,8 @@ function CompositeComponentItem({
|
|
|
8
8
|
role,
|
|
9
9
|
itemMouseEventHandler,
|
|
10
10
|
itemKeyboardEventHandler,
|
|
11
|
-
render
|
|
11
|
+
render,
|
|
12
|
+
softFocus
|
|
12
13
|
}) {
|
|
13
14
|
const data = useStore(item.data);
|
|
14
15
|
const state = useStore(item.state);
|
|
@@ -26,7 +27,7 @@ function CompositeComponentItem({
|
|
|
26
27
|
role,
|
|
27
28
|
"aria-disabled": state.disabled,
|
|
28
29
|
"data-key": item.key,
|
|
29
|
-
tabIndex: !state.disabled ? state.focused ? 0 : -1 : void 0,
|
|
30
|
+
tabIndex: !softFocus && !state.disabled ? state.focused ? 0 : -1 : void 0,
|
|
30
31
|
className,
|
|
31
32
|
children: [
|
|
32
33
|
render({ data, key: item.key, level: item.level }, state, handlers),
|
|
@@ -39,7 +40,8 @@ function CompositeComponentItem({
|
|
|
39
40
|
render,
|
|
40
41
|
className,
|
|
41
42
|
itemMouseEventHandler,
|
|
42
|
-
itemKeyboardEventHandler
|
|
43
|
+
itemKeyboardEventHandler,
|
|
44
|
+
softFocus
|
|
43
45
|
},
|
|
44
46
|
child.key
|
|
45
47
|
))
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { CompositeProps } from './types';
|
|
2
|
-
export declare function CompositeComponent<T extends object>({ data, variant, rootRole, itemRole, groupRole, renderItem, className, itemClassName,
|
|
2
|
+
export declare function CompositeComponent<T extends object>({ data, variant, rootRole, itemRole, groupRole, renderItem, className, itemClassName, rootRef, handleRef, id, itemMouseEventHandler, itemKeyboardEventHandler, initialFocus, softFocus, ...props }: CompositeProps<T>): import("react").JSX.Element;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useImperativeHandle, useMemo } from "react";
|
|
2
|
+
import { useCallback, useImperativeHandle, useMemo, useEffect } from "react";
|
|
3
3
|
import { CompositeComponentItem } from "./composite-component-item.js";
|
|
4
4
|
import { useId } from "../../../hooks/use-id.js";
|
|
5
|
+
import { useRequiredRef } from "../../../hooks/use-required-ref.js";
|
|
5
6
|
const defaultRoles = {
|
|
6
7
|
listbox: {
|
|
7
8
|
rootRole: "listbox",
|
|
@@ -15,6 +16,11 @@ const defaultRoles = {
|
|
|
15
16
|
},
|
|
16
17
|
custom: void 0
|
|
17
18
|
};
|
|
19
|
+
const isOutsideElement = (parent, el) => {
|
|
20
|
+
if (parent === el) return false;
|
|
21
|
+
const nodeId = !!el?.id && CSS.escape(el.id);
|
|
22
|
+
return !nodeId || parent?.querySelector(`#${nodeId}`) === null;
|
|
23
|
+
};
|
|
18
24
|
function CompositeComponent({
|
|
19
25
|
data,
|
|
20
26
|
variant,
|
|
@@ -24,42 +30,84 @@ function CompositeComponent({
|
|
|
24
30
|
renderItem,
|
|
25
31
|
className,
|
|
26
32
|
itemClassName,
|
|
27
|
-
|
|
33
|
+
rootRef,
|
|
28
34
|
handleRef,
|
|
29
35
|
id,
|
|
30
36
|
itemMouseEventHandler,
|
|
31
37
|
itemKeyboardEventHandler,
|
|
38
|
+
initialFocus = "None",
|
|
39
|
+
softFocus,
|
|
32
40
|
...props
|
|
33
41
|
}) {
|
|
34
42
|
const compositeId = useId(id);
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
()
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
)
|
|
43
|
+
const $rootRef = useRequiredRef(rootRef);
|
|
44
|
+
const focusElement = useCallback(() => {
|
|
45
|
+
if (softFocus) return;
|
|
46
|
+
const itemKey = data.focusProvider.focusedItem.get()?.key;
|
|
47
|
+
if (itemKey && $rootRef.current) {
|
|
48
|
+
const focusedItemEl = $rootRef.current.querySelector(`[data-key="${itemKey}"]`);
|
|
49
|
+
if (focusedItemEl) focusedItemEl.focus();
|
|
50
|
+
}
|
|
51
|
+
}, [softFocus, data.focusProvider.focusedItem, $rootRef]);
|
|
52
|
+
useImperativeHandle(handleRef, () => {
|
|
53
|
+
return {
|
|
54
|
+
focusProvider: data.focusProvider,
|
|
55
|
+
selectionProvider: data.selectionProvider,
|
|
56
|
+
focusElement
|
|
57
|
+
};
|
|
58
|
+
}, [data, focusElement]);
|
|
45
59
|
const roles = useMemo(
|
|
46
60
|
() => defaultRoles[variant] ?? { rootRole, groupRole, itemRole },
|
|
47
61
|
[groupRole, itemRole, rootRole, variant]
|
|
48
62
|
);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
itemKeyboardEventHandler
|
|
63
|
+
const intiFocus = useCallback(
|
|
64
|
+
(onlyData) => {
|
|
65
|
+
let initialFocusItem = null;
|
|
66
|
+
if (initialFocus === "FirstItem") initialFocusItem = data.focusProvider.firstFocusableItem;
|
|
67
|
+
else if (initialFocus === "SelectedItem") {
|
|
68
|
+
const selecedItem = data.selectionProvider?.selectedKeys.get().values().next().value;
|
|
69
|
+
initialFocusItem = selecedItem ?? data.focusProvider.firstFocusableItem;
|
|
70
|
+
}
|
|
71
|
+
data.focusProvider.focus(initialFocusItem);
|
|
72
|
+
if (!onlyData) focusElement();
|
|
60
73
|
},
|
|
61
|
-
|
|
62
|
-
)
|
|
74
|
+
[data.focusProvider, data.selectionProvider?.selectedKeys, focusElement, initialFocus]
|
|
75
|
+
);
|
|
76
|
+
const onFocusHandler = useCallback(
|
|
77
|
+
(ev) => {
|
|
78
|
+
const fromOut = isOutsideElement($rootRef.current, ev.relatedTarget);
|
|
79
|
+
if (fromOut) intiFocus(false);
|
|
80
|
+
},
|
|
81
|
+
[$rootRef, intiFocus]
|
|
82
|
+
);
|
|
83
|
+
useEffect(() => intiFocus(true), [intiFocus]);
|
|
84
|
+
return /* @__PURE__ */ jsx(
|
|
85
|
+
"div",
|
|
86
|
+
{
|
|
87
|
+
ref: $rootRef,
|
|
88
|
+
id: compositeId,
|
|
89
|
+
className,
|
|
90
|
+
tabIndex: initialFocus === "None" ? 0 : -1,
|
|
91
|
+
role: roles.rootRole,
|
|
92
|
+
...props,
|
|
93
|
+
onFocus: onFocusHandler,
|
|
94
|
+
children: [...data].map((item) => /* @__PURE__ */ jsx(
|
|
95
|
+
CompositeComponentItem,
|
|
96
|
+
{
|
|
97
|
+
className: itemClassName,
|
|
98
|
+
id: `${compositeId}-${item.key}`,
|
|
99
|
+
role: roles.itemRole,
|
|
100
|
+
groupRole: roles.groupRole,
|
|
101
|
+
item,
|
|
102
|
+
render: renderItem,
|
|
103
|
+
itemMouseEventHandler,
|
|
104
|
+
itemKeyboardEventHandler,
|
|
105
|
+
softFocus
|
|
106
|
+
},
|
|
107
|
+
item.key
|
|
108
|
+
))
|
|
109
|
+
}
|
|
110
|
+
);
|
|
63
111
|
}
|
|
64
112
|
export {
|
|
65
113
|
CompositeComponent
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { BaseCompositeProps } from './types';
|
|
2
|
-
export declare function Listbox<T extends object>({ data, ...props }: BaseCompositeProps<T>): import("react").JSX.Element;
|
|
2
|
+
export declare function Listbox<T extends object>({ data, rootRef, handleRef, initialFocus, ...props }: BaseCompositeProps<T>): import("react").JSX.Element;
|
|
@@ -1,53 +1,58 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import { CompositeComponent } from "./composite-component.js";
|
|
3
|
-
import {
|
|
3
|
+
import { useCallback } from "react";
|
|
4
4
|
import "nanostores";
|
|
5
5
|
import { useKeyboardEvent } from "../../../hooks/use-keyboard-event.js";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const handleKeyboardEvent = useKeyboardEvent(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
6
|
+
import { useRequiredRef } from "../../../hooks/use-required-ref.js";
|
|
7
|
+
function Listbox({
|
|
8
|
+
data,
|
|
9
|
+
rootRef,
|
|
10
|
+
handleRef,
|
|
11
|
+
initialFocus = "SelectedItem",
|
|
12
|
+
...props
|
|
13
|
+
}) {
|
|
14
|
+
const $handleRef = useRequiredRef(handleRef);
|
|
15
|
+
const handleKeyboardEvent = useKeyboardEvent(
|
|
16
|
+
{
|
|
17
|
+
ArrowUp: () => {
|
|
18
|
+
data.focusProvider.focusUp();
|
|
19
|
+
$handleRef.current?.focusElement();
|
|
20
|
+
},
|
|
21
|
+
ArrowDown: () => {
|
|
22
|
+
data.focusProvider.focusDown();
|
|
23
|
+
$handleRef.current?.focusElement();
|
|
24
|
+
},
|
|
25
|
+
Home: () => {
|
|
26
|
+
data.focusProvider.focusToFirst();
|
|
27
|
+
$handleRef.current?.focusElement();
|
|
28
|
+
},
|
|
29
|
+
End: () => {
|
|
30
|
+
data.focusProvider.focusToLast();
|
|
31
|
+
$handleRef.current?.focusElement();
|
|
32
|
+
},
|
|
33
|
+
Space: () => {
|
|
34
|
+
data.selectionProvider?.toggleSelect();
|
|
35
|
+
$handleRef.current?.focusElement();
|
|
36
|
+
}
|
|
31
37
|
},
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
focusElement();
|
|
35
|
-
}
|
|
36
|
-
});
|
|
38
|
+
[$handleRef, data.focusProvider]
|
|
39
|
+
);
|
|
37
40
|
const handleItemMouseEvent = useCallback(
|
|
38
41
|
(item) => {
|
|
39
42
|
data.focusProvider.focus(item.key);
|
|
40
43
|
data.selectionProvider?.toggleSelect(item);
|
|
41
|
-
focusElement();
|
|
44
|
+
$handleRef.current?.focusElement();
|
|
42
45
|
},
|
|
43
|
-
[data.focusProvider, data.selectionProvider
|
|
46
|
+
[$handleRef, data.focusProvider, data.selectionProvider]
|
|
44
47
|
);
|
|
45
48
|
return /* @__PURE__ */ jsx(
|
|
46
49
|
CompositeComponent,
|
|
47
50
|
{
|
|
48
|
-
|
|
51
|
+
rootRef,
|
|
52
|
+
handleRef: $handleRef,
|
|
49
53
|
variant: "listbox",
|
|
50
54
|
data,
|
|
55
|
+
initialFocus,
|
|
51
56
|
onKeyDown: handleKeyboardEvent,
|
|
52
57
|
itemMouseEventHandler: handleItemMouseEvent,
|
|
53
58
|
...props
|
|
@@ -5,21 +5,22 @@ import { AbstractSelectionProvider, SelectionProvider } from './SelectionProvide
|
|
|
5
5
|
import { CompositeData } from './CompositeData';
|
|
6
6
|
export type CompositeItemKey = string | number;
|
|
7
7
|
export type CompositeRoles = {
|
|
8
|
-
variant:
|
|
8
|
+
variant: "listbox";
|
|
9
9
|
rootRole?: never;
|
|
10
10
|
itemRole?: never;
|
|
11
11
|
groupRole?: never;
|
|
12
12
|
} | {
|
|
13
|
-
variant:
|
|
13
|
+
variant: "grid";
|
|
14
14
|
rootRole?: never;
|
|
15
15
|
itemRole?: never;
|
|
16
16
|
groupRole?: never;
|
|
17
17
|
} | {
|
|
18
|
-
variant:
|
|
18
|
+
variant: "custom";
|
|
19
19
|
rootRole: AriaRole;
|
|
20
20
|
itemRole: AriaRole;
|
|
21
21
|
groupRole: AriaRole;
|
|
22
22
|
};
|
|
23
|
+
export type InitialFocusTarget = "None" | "LastFocusedItem" | "SelectedItem" | "FirstItem";
|
|
23
24
|
export interface CompositeOptions {
|
|
24
25
|
disabledKeys?: CompositeItemKey[];
|
|
25
26
|
selectedKeys?: CompositeItemKey[];
|
|
@@ -55,14 +56,20 @@ export interface CompositeItemEventHandlerFunctions<T extends object> {
|
|
|
55
56
|
export interface BaseCompositeProps<T extends object> extends React.ComponentPropsWithoutRef<"div"> {
|
|
56
57
|
data: CompositeData<T>;
|
|
57
58
|
className?: string;
|
|
58
|
-
|
|
59
|
-
handleRef?: React.
|
|
59
|
+
rootRef?: React.RefObject<HTMLDivElement | null>;
|
|
60
|
+
handleRef?: React.RefObject<CompositeHandle | null>;
|
|
60
61
|
renderItem: CompositeItemRenderFn<T>;
|
|
61
62
|
itemClassName?: string;
|
|
63
|
+
initialFocus?: InitialFocusTarget;
|
|
64
|
+
/**
|
|
65
|
+
* Set `item.focused = true`, but not focus on the HTMLElement
|
|
66
|
+
*/
|
|
67
|
+
softFocus?: boolean;
|
|
62
68
|
}
|
|
63
69
|
export type CompositeProps<T extends object> = BaseCompositeProps<T> & CompositeItemEventHandlerFunctions<T> & CompositeRoles;
|
|
64
70
|
export interface CompositeHandle {
|
|
65
71
|
focusProvider: FocusProvider;
|
|
72
|
+
focusElement: () => void;
|
|
66
73
|
selectionProvider?: SelectionProvider;
|
|
67
74
|
}
|
|
68
75
|
export type CompositeItemRenderFn<T extends object> = (item: {
|
|
@@ -78,5 +85,6 @@ export interface CompositeItemProps<T extends object> extends CompositeItemEvent
|
|
|
78
85
|
item: CompositeDataItem<T>;
|
|
79
86
|
remove?: () => void;
|
|
80
87
|
render: CompositeItemRenderFn<T>;
|
|
88
|
+
softFocus?: boolean;
|
|
81
89
|
}
|
|
82
90
|
export {};
|
|
@@ -3,7 +3,7 @@ interface KeyBindings {
|
|
|
3
3
|
[sequence: string]: (event: KeyboardEvent) => void;
|
|
4
4
|
}
|
|
5
5
|
interface UseKeyboardEventOptions {
|
|
6
|
-
eventKeyProp:
|
|
6
|
+
eventKeyProp: "key" | "code";
|
|
7
7
|
}
|
|
8
8
|
export declare function keyboardEventHandler(bindings: KeyBindings, options?: UseKeyboardEventOptions): KeyboardEventHandler;
|
|
9
9
|
/**
|
|
@@ -44,7 +44,7 @@ export declare function keyboardEventHandler(bindings: KeyBindings, options?: Us
|
|
|
44
44
|
* 'escape': clearInput,
|
|
45
45
|
* 'ctrl+c': clearInput,
|
|
46
46
|
* 'ctrl + shift + c': deleteAll,
|
|
47
|
-
* });
|
|
47
|
+
* },[]);
|
|
48
48
|
*
|
|
49
49
|
* return (
|
|
50
50
|
* <input
|
|
@@ -55,5 +55,5 @@ export declare function keyboardEventHandler(bindings: KeyBindings, options?: Us
|
|
|
55
55
|
* }
|
|
56
56
|
* ```
|
|
57
57
|
*/
|
|
58
|
-
export declare function useKeyboardEvent(bindings: KeyBindings, options?: UseKeyboardEventOptions): KeyboardEventHandler;
|
|
58
|
+
export declare function useKeyboardEvent(bindings: KeyBindings, deps: React.DependencyList, options?: UseKeyboardEventOptions): KeyboardEventHandler;
|
|
59
59
|
export {};
|
|
@@ -43,8 +43,8 @@ function keyboardEventHandler(bindings, options = defaultOptions) {
|
|
|
43
43
|
}
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
|
-
function useKeyboardEvent(bindings, options) {
|
|
47
|
-
return useMemo(() => keyboardEventHandler(bindings, options), [
|
|
46
|
+
function useKeyboardEvent(bindings, deps, options) {
|
|
47
|
+
return useMemo(() => keyboardEventHandler(bindings, options), [deps]);
|
|
48
48
|
}
|
|
49
49
|
export {
|
|
50
50
|
keyboardEventHandler,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function useRequiredRef<T>(ref?: React.RefObject<T | null>): import('react').RefObject<T | null>;
|
package/package.json
CHANGED
|
@@ -30,13 +30,22 @@ const buttonVariants = cva(
|
|
|
30
30
|
icon: "size-9",
|
|
31
31
|
text: "h-9 text-base",
|
|
32
32
|
},
|
|
33
|
+
icon: {
|
|
34
|
+
true: "py-1 px-1 has-[>svg]:px-1 gap-0 [&_svg:not([class*='size-'])]:size-full",
|
|
35
|
+
false: "",
|
|
36
|
+
},
|
|
33
37
|
},
|
|
38
|
+
compoundVariants: [
|
|
39
|
+
{ size: ["sm"], icon: true, className: "size-8" },
|
|
40
|
+
{ size: ["default", "icon", "text"], icon: true, className: "size-9" },
|
|
41
|
+
{ size: ["lg"], icon: true, className: "size-10" },
|
|
42
|
+
],
|
|
34
43
|
defaultVariants: {
|
|
35
44
|
size: "default",
|
|
36
45
|
block: false,
|
|
37
46
|
variant: "default",
|
|
38
47
|
},
|
|
39
|
-
}
|
|
48
|
+
},
|
|
40
49
|
);
|
|
41
50
|
|
|
42
51
|
export type ButtonProps = React.ComponentProps<"button"> &
|
|
@@ -44,10 +53,12 @@ export type ButtonProps = React.ComponentProps<"button"> &
|
|
|
44
53
|
asChild?: boolean;
|
|
45
54
|
};
|
|
46
55
|
|
|
47
|
-
function Button({ className, variant, size, block, asChild = false, ...props }: ButtonProps) {
|
|
56
|
+
function Button({ className, variant, size, block, icon, asChild = false, ...props }: ButtonProps) {
|
|
48
57
|
const Comp = asChild ? Slot : "button";
|
|
49
58
|
|
|
50
|
-
return
|
|
59
|
+
return (
|
|
60
|
+
<Comp data-slot="button" className={cn(buttonVariants({ size, block, variant, icon }), className)} {...props} />
|
|
61
|
+
);
|
|
51
62
|
}
|
|
52
63
|
|
|
53
64
|
export { Button, buttonVariants };
|