@allxsmith/bestax-bulma 5.36.0 → 5.37.0

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.
Files changed (44) hide show
  1. package/dist/bestax.css +1 -1
  2. package/dist/bestax.css.map +1 -1
  3. package/dist/esm/components/EmptyState.js +2 -2
  4. package/dist/esm/components/Menu.js +9 -2
  5. package/dist/esm/components/Menu.js.map +1 -1
  6. package/dist/esm/components/Timeline.js +5 -5
  7. package/dist/esm/components/Timeline.js.map +1 -1
  8. package/dist/esm/components/TreeView.js +192 -0
  9. package/dist/esm/components/TreeView.js.map +1 -0
  10. package/dist/esm/helpers/typeahead.js +82 -0
  11. package/dist/esm/helpers/typeahead.js.map +1 -0
  12. package/dist/esm/helpers/useMenuKeys.js +8 -58
  13. package/dist/esm/helpers/useMenuKeys.js.map +1 -1
  14. package/dist/esm/helpers/useTreeView.js +380 -0
  15. package/dist/esm/helpers/useTreeView.js.map +1 -0
  16. package/dist/extras.css +1 -1
  17. package/dist/extras.css.map +1 -1
  18. package/dist/index.cjs +1954 -1370
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.esm.js +1 -0
  21. package/dist/index.esm.js.map +1 -1
  22. package/dist/types/components/Menu.d.ts +5 -0
  23. package/dist/types/components/TreeView.d.ts +115 -0
  24. package/dist/types/helpers/typeahead.d.ts +41 -0
  25. package/dist/types/helpers/useMenuKeys.d.ts +1 -2
  26. package/dist/types/helpers/useTreeView.d.ts +88 -0
  27. package/dist/types/index.d.ts +1 -0
  28. package/dist/types-cjs/components/Menu.d.ts +5 -0
  29. package/dist/types-cjs/components/TreeView.d.ts +115 -0
  30. package/dist/types-cjs/helpers/typeahead.d.ts +41 -0
  31. package/dist/types-cjs/helpers/useMenuKeys.d.ts +1 -2
  32. package/dist/types-cjs/helpers/useTreeView.d.ts +88 -0
  33. package/dist/types-cjs/index.d.ts +1 -0
  34. package/dist/versions/bestax-no-dark-mode.css +1 -1
  35. package/dist/versions/bestax-no-dark-mode.css.map +1 -1
  36. package/dist/versions/bestax-no-helpers-prefixed.css +1 -1
  37. package/dist/versions/bestax-no-helpers-prefixed.css.map +1 -1
  38. package/dist/versions/bestax-no-helpers.css +1 -1
  39. package/dist/versions/bestax-no-helpers.css.map +1 -1
  40. package/dist/versions/bestax-prefixed.css +1 -1
  41. package/dist/versions/bestax-prefixed.css.map +1 -1
  42. package/package.json +1 -1
  43. package/src/scss/components/_index.scss +1 -0
  44. package/src/scss/components/_treeview.scss +252 -0
package/dist/index.esm.js CHANGED
@@ -30,6 +30,7 @@ export { Toast, ToastContainer, toast } from './esm/components/Toast.js';
30
30
  export { Dialog, DialogContainer, dialog } from './esm/components/Dialog.js';
31
31
  export { Carousel, CarouselItem } from './esm/components/Carousel.js';
32
32
  export { Reveal } from './esm/components/Reveal.js';
33
+ export { TreeView, TreeViewItem } from './esm/components/TreeView.js';
33
34
  export { Block } from './esm/elements/Block.js';
34
35
  export { Box } from './esm/elements/Box.js';
35
36
  export { Button } from './esm/elements/Button.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -29,6 +29,7 @@ export interface MenuLabelProps extends Omit<React.HTMLAttributes<HTMLParagraphE
29
29
  export declare const MenuLabel: React.FC<MenuLabelProps>;
30
30
  /**
31
31
  * Props for the MenuList component.
32
+ * @extraProp {React.AriaRole} [role] - Ignored: every level renders `role="list"` in its place, so a menu or tree role can't go here.
32
33
  */
33
34
  export interface MenuListProps extends Omit<React.HTMLAttributes<HTMLUListElement>, keyof BulmaClassesProps>, BulmaClassesProps {
34
35
  /** Additional CSS classes. */
@@ -39,6 +40,10 @@ export interface MenuListProps extends Omit<React.HTMLAttributes<HTMLUListElemen
39
40
  /**
40
41
  * MenuList applies `menu-list` class only at the top level (not for nested lists).
41
42
  *
43
+ * Every level renders `role="list"`, which Safari needs: WebKit stops
44
+ * exposing a `<ul>` as a list once its markers are hidden, and Bulma's reset
45
+ * hides them.
46
+ *
42
47
  * @function
43
48
  * @param {MenuListProps} props - Props for the MenuList component.
44
49
  * @returns {JSX.Element} The rendered menu list.
@@ -0,0 +1,115 @@
1
+ import React from 'react';
2
+ import { BulmaClassesProps } from '../helpers/useBulmaClasses.js';
3
+ /**
4
+ * How items are picked: `none`, nothing is; `single`, one item at a time;
5
+ * `checkbox`, any number, with a box on every item and mixed parents.
6
+ */
7
+ export type TreeViewSelectionMode = 'none' | 'single' | 'checkbox';
8
+ /**
9
+ * Props for the TreeView component. The helper props, the other attributes
10
+ * and the `ref` land on the `<ul role="tree">`.
11
+ *
12
+ * @extraProp {string} [aria-label] - Names the tree, such as "Files". A tree needs a name, so pass this or `aria-labelledby`, and a development build warns when it has neither.
13
+ * @extraProp {string} [aria-labelledby] - The id of a visible heading or label that names the tree, in place of `aria-label`.
14
+ * @extraProp {React.Ref<HTMLUListElement>} [ref] - Ref forwarded to the `<ul>`.
15
+ */
16
+ export interface TreeViewProps extends Omit<React.HTMLAttributes<HTMLUListElement>, keyof BulmaClassesProps | 'role' | 'onSelect' | 'defaultValue' | 'defaultChecked'>, BulmaClassesProps {
17
+ /**
18
+ * `TreeView.Item`s, nested inside each other for their children. Render
19
+ * an item's children whenever you have them, open or not: the tree hides
20
+ * them while it's closed.
21
+ */
22
+ children?: React.ReactNode;
23
+ /**
24
+ * `none`: nothing is picked, and Enter and Space open and close a parent.
25
+ * `single`: one item, picked with Enter, Space or a click.
26
+ * `checkbox`: any number, checked with Space or a click. A check reaches
27
+ * every enabled item under it, and a parent with some of them checked is mixed.
28
+ */
29
+ selectionMode?: TreeViewSelectionMode;
30
+ /**
31
+ * The selected item's value when you hold the state, or `null` for none.
32
+ * Read with `single` only. Picking an item then only calls
33
+ * `onSelectedChange`, and nothing changes until you pass the new value.
34
+ */
35
+ selected?: string | null;
36
+ /** The item selected to begin with, when the tree holds its own state. Read with `single` only. */
37
+ defaultSelected?: string | null;
38
+ /**
39
+ * Called with the value of the item picked. Not called for the one
40
+ * already selected, and moving focus never calls it.
41
+ */
42
+ onSelectedChange?: (value: string) => void;
43
+ /**
44
+ * The checked items' values when you hold the state. Read with `checkbox`
45
+ * only. A value checks its item, and an enabled item also counts as checked
46
+ * when any item above it is in the list, so a parent's value stands for its
47
+ * whole branch, loaded or not. A disabled item is checked only by its own value.
48
+ */
49
+ checked?: string[];
50
+ /** The values checked to begin with, when the tree holds its own state. Read with `checkbox` only. */
51
+ defaultChecked?: string[];
52
+ /**
53
+ * Called after a check or an uncheck with every item that now shows
54
+ * checked, parents included, plus any earlier value that matches no item
55
+ * the tree knows, left as it was. The order means nothing. Passed back as
56
+ * `checked`, the list shows the same boxes.
57
+ */
58
+ onCheckedChange?: (checked: string[]) => void;
59
+ /**
60
+ * The open items' values when you hold the state. A value stays in the
61
+ * list while its item's parent is closed, so the item reopens as it was.
62
+ */
63
+ expanded?: string[];
64
+ /** The items open to begin with, when the tree holds its own state. */
65
+ defaultExpanded?: string[];
66
+ /** Called with the new list whenever an item opens or closes, `*` included. */
67
+ onExpandedChange?: (expanded: string[]) => void;
68
+ /** The text size, as Bulma's menu sizes it. The rows, the indent and the chevron scale with it. */
69
+ size?: 'small' | 'medium' | 'large';
70
+ }
71
+ /**
72
+ * Props for TreeView.Item, one row and, nested in it, its children. The other
73
+ * attributes and the `ref` land on its `<li role="treeitem">`.
74
+ *
75
+ * @extraProp {React.Ref<HTMLLIElement>} [ref] - Ref forwarded to the `<li>`.
76
+ */
77
+ export interface TreeViewItemProps extends Omit<React.LiHTMLAttributes<HTMLLIElement>, 'role' | 'value' | 'onSelect' | 'children'> {
78
+ /** **Required.** Identifies the item in `selected`, `checked` and `expanded`. Unique in the tree. */
79
+ value: string;
80
+ /**
81
+ * **Required.** What the row shows, and the item's name for assistive
82
+ * tech. Nothing in it should be a link or a button: a tree item does one
83
+ * thing, and nothing else in its row takes focus.
84
+ */
85
+ label: React.ReactNode;
86
+ /** A picture before the label: an `Icon` name, or a node. Hidden from assistive tech. */
87
+ icon?: React.ReactNode;
88
+ /**
89
+ * `TreeView.Item`s under this one. Render them whenever you have them,
90
+ * open or not: the tree hides them while it's closed, and a checkbox tree
91
+ * needs them to know what a check reaches.
92
+ */
93
+ children?: React.ReactNode;
94
+ /** Shows the item as a parent before its children are known, such as a folder that loads when it opens. */
95
+ hasChildren?: boolean;
96
+ /**
97
+ * Reachable with the arrow keys, Home, End and type-ahead, so a screen
98
+ * reader finds it, but never selected, checked, opened or closed. One
99
+ * that's already open stays open.
100
+ */
101
+ disabled?: boolean;
102
+ }
103
+ /**
104
+ * One item in the tree: a row with its chevron, its box, its icon and its label, and, while it's open, a group of the items under it.
105
+ *
106
+ * @function
107
+ * @param {TreeViewItemProps} props - Props for TreeView.Item.
108
+ * @param {React.Ref<HTMLLIElement>} ref - Forwarded ref to the `<li>`.
109
+ * @returns {JSX.Element | null} The rendered item, or nothing under a closed one.
110
+ */
111
+ export declare const TreeViewItem: React.ForwardRefExoticComponent<TreeViewItemProps & React.RefAttributes<HTMLLIElement>>;
112
+ export declare const TreeView: React.ForwardRefExoticComponent<TreeViewProps & React.RefAttributes<HTMLUListElement>> & {
113
+ Item: React.ForwardRefExoticComponent<TreeViewItemProps & React.RefAttributes<HTMLLIElement>>;
114
+ };
115
+ export default TreeView;
@@ -0,0 +1,41 @@
1
+ /** How long typed characters keep adding up to one search, in milliseconds. */
2
+ export declare const TYPEAHEAD_TIMEOUT = 500;
3
+ /**
4
+ * The text typing finds an element by: what it says, without what's hidden
5
+ * from assistive technology, such as an icon whose glyph is a ligature's name.
6
+ */
7
+ export declare function typeaheadText(element: Element): string;
8
+ /**
9
+ * The item typing `search` moves to, or none. A single character looks from
10
+ * the item after the focused one, so typing the same letter again cycles
11
+ * through the items that start with it, and so does a run of one letter
12
+ * ("dd"). A longer search looks from the focused item, so typing more of
13
+ * its own text keeps it.
14
+ *
15
+ * @param items - The items typing can reach, in the order they're shown.
16
+ * @param search - What's been typed, lowercased.
17
+ * @param current - The focused item's index in `items`, or -1 for none.
18
+ * @param textOf - The text an item is found by. By default it's the item's
19
+ * own, read by `typeaheadText`.
20
+ */
21
+ export declare function typeaheadMatch<T extends Element>(items: T[], search: string, current: number, textOf?: (item: T) => string): T | undefined;
22
+ /** What `useTypeahead` returns. */
23
+ export interface Typeahead {
24
+ /**
25
+ * Adds a typed character to the search and returns the whole search,
26
+ * lowercased. Characters typed within `TYPEAHEAD_TIMEOUT` of each other
27
+ * add up, and after that the next one starts a new search.
28
+ */
29
+ type: (char: string) => string;
30
+ /** Forgets what has been typed, so the next character starts a new search. */
31
+ reset: () => void;
32
+ }
33
+ /**
34
+ * Keeps what has been typed for a type-ahead search, and forgets it once
35
+ * `TYPEAHEAD_TIMEOUT` passes with nothing typed. Pair it with
36
+ * `typeaheadMatch` to find the item the search names.
37
+ *
38
+ * @function useTypeahead
39
+ * @returns A way to add a character to the search, and a way to forget it.
40
+ */
41
+ export declare function useTypeahead(): Typeahead;
@@ -1,4 +1,5 @@
1
1
  import type { KeyboardEvent } from 'react';
2
+ export { TYPEAHEAD_TIMEOUT } from './typeahead.js';
2
3
  /**
3
4
  * Every menu-item role, so an item a caller gives `menuitemcheckbox` or
4
5
  * `menuitemradio` (a filter or sort menu) stays reachable.
@@ -11,8 +12,6 @@ export declare const MENU_ITEM_SELECTOR = "[role=\"menuitem\"], [role=\"menuitem
11
12
  export declare function isEnabledMenuItem(item: Element): boolean;
12
13
  /** The items in `menu` the keys reach, in document order. */
13
14
  export declare function enabledMenuItems(menu: Element): HTMLElement[];
14
- /** How long typed characters keep adding up to one search, in milliseconds. */
15
- export declare const TYPEAHEAD_TIMEOUT = 500;
16
15
  /** What `useMenuKeys` returns. */
17
16
  export interface MenuKeys {
18
17
  /** The `keydown` handler for the `role="menu"` element. */
@@ -0,0 +1,88 @@
1
+ import type React from 'react';
2
+ import type { TreeViewSelectionMode } from '../components/TreeView.js';
3
+ /** What an item tells the tree about itself when it registers. */
4
+ export interface TreeViewItemRecord {
5
+ /** The value of the item it's in, or `null` at the top. */
6
+ parent: string | null;
7
+ disabled: boolean;
8
+ /** It has children, or `hasChildren` says it will. */
9
+ isParent: boolean;
10
+ }
11
+ /** An item as a key or a click on it sees it. */
12
+ export interface TreeViewItemInfo extends TreeViewItemRecord {
13
+ value: string;
14
+ open: boolean;
15
+ }
16
+ /** A box's state: checked, unchecked or mixed. */
17
+ export type TreeViewCheckState = boolean | 'mixed';
18
+ /**
19
+ * What an item shows of the selection: `selected` in a `single` tree,
20
+ * `checked` in a `checkbox` tree, and neither with `none`.
21
+ */
22
+ export interface TreeViewItemState {
23
+ selected?: boolean;
24
+ checked?: TreeViewCheckState;
25
+ }
26
+ /** Options for `useTreeView`: the TreeView's own props. */
27
+ export interface TreeViewOptions {
28
+ mode: TreeViewSelectionMode;
29
+ selected?: string | null;
30
+ defaultSelected: string | null;
31
+ onSelectedChange?: (value: string) => void;
32
+ checked?: string[];
33
+ defaultChecked: string[];
34
+ onCheckedChange?: (checked: string[]) => void;
35
+ expanded?: string[];
36
+ defaultExpanded: string[];
37
+ onExpandedChange?: (expanded: string[]) => void;
38
+ }
39
+ /** What `useTreeView` hands the root and its items. */
40
+ export interface TreeViewEngine {
41
+ /** For the `<ul role="tree">`. */
42
+ treeRef: React.MutableRefObject<HTMLUListElement | null>;
43
+ /**
44
+ * Whether a closed item still renders its children, drawing nothing, so a
45
+ * check knows everything under it. Only a checkbox tree needs to.
46
+ */
47
+ rendersClosed: boolean;
48
+ /** The item that holds `tabIndex={0}`. */
49
+ tabStop: string | null;
50
+ isOpen: (value: string) => boolean;
51
+ /**
52
+ * What the item shows of the selection. `path` is the values of the items
53
+ * above it, which decide its box until the items have registered, as on
54
+ * the server.
55
+ */
56
+ stateOf: (value: string, path: string[], disabled: boolean) => TreeViewItemState;
57
+ /** Records an item from its layout effect. Returns the function that forgets it. */
58
+ register: (value: string, record: TreeViewItemRecord) => () => void;
59
+ /** For each item's `<li>` ref: records it, and notices focus leaving with it. */
60
+ setElement: (value: string, element: HTMLLIElement | null) => void;
61
+ /** Focus arrived on the item's own `<li>`. */
62
+ onFocus: (value: string, element: HTMLLIElement) => void;
63
+ /** Focus left the item's own `<li>`. */
64
+ onBlur: () => void;
65
+ /** A key on the item's `<li>`, after its own `onKeyDown`. */
66
+ onKeyDown: (event: React.KeyboardEvent<HTMLLIElement>, item: TreeViewItemInfo) => void;
67
+ /**
68
+ * A click on the item's row, or with `onToggle` on its chevron. Focus and
69
+ * the tab stop move to the item.
70
+ */
71
+ onClick: (element: HTMLLIElement, item: TreeViewItemInfo, onToggle: boolean) => void;
72
+ }
73
+ /**
74
+ * The open state, the selection, the keys, the tab stop and the registry of
75
+ * a TreeView (#1096). Focus moves for real, with a roving tabindex, rather
76
+ * than through `aria-activedescendant`.
77
+ *
78
+ * Every item registers its value, its parent and whether it's disabled from
79
+ * a layout effect. In a checkbox tree a closed item's children still render
80
+ * and register while drawing nothing, so the registry knows every item and a
81
+ * check reaches all of them. The keys read the items on the page, in
82
+ * document order, each time they need them.
83
+ *
84
+ * @function useTreeView
85
+ * @param options - The TreeView's props.
86
+ * @returns The state and handlers the root and its items wire up.
87
+ */
88
+ export declare function useTreeView(options: TreeViewOptions): TreeViewEngine;
@@ -29,6 +29,7 @@ export * from './components/Toast.js';
29
29
  export * from './components/Dialog.js';
30
30
  export * from './components/Carousel.js';
31
31
  export * from './components/Reveal.js';
32
+ export * from './components/TreeView.js';
32
33
  export * from './elements/Block.js';
33
34
  export * from './elements/Box.js';
34
35
  export * from './elements/Button.js';
@@ -29,6 +29,7 @@ export interface MenuLabelProps extends Omit<React.HTMLAttributes<HTMLParagraphE
29
29
  export declare const MenuLabel: React.FC<MenuLabelProps>;
30
30
  /**
31
31
  * Props for the MenuList component.
32
+ * @extraProp {React.AriaRole} [role] - Ignored: every level renders `role="list"` in its place, so a menu or tree role can't go here.
32
33
  */
33
34
  export interface MenuListProps extends Omit<React.HTMLAttributes<HTMLUListElement>, keyof BulmaClassesProps>, BulmaClassesProps {
34
35
  /** Additional CSS classes. */
@@ -39,6 +40,10 @@ export interface MenuListProps extends Omit<React.HTMLAttributes<HTMLUListElemen
39
40
  /**
40
41
  * MenuList applies `menu-list` class only at the top level (not for nested lists).
41
42
  *
43
+ * Every level renders `role="list"`, which Safari needs: WebKit stops
44
+ * exposing a `<ul>` as a list once its markers are hidden, and Bulma's reset
45
+ * hides them.
46
+ *
42
47
  * @function
43
48
  * @param {MenuListProps} props - Props for the MenuList component.
44
49
  * @returns {JSX.Element} The rendered menu list.
@@ -0,0 +1,115 @@
1
+ import React from 'react';
2
+ import { BulmaClassesProps } from '../helpers/useBulmaClasses.js';
3
+ /**
4
+ * How items are picked: `none`, nothing is; `single`, one item at a time;
5
+ * `checkbox`, any number, with a box on every item and mixed parents.
6
+ */
7
+ export type TreeViewSelectionMode = 'none' | 'single' | 'checkbox';
8
+ /**
9
+ * Props for the TreeView component. The helper props, the other attributes
10
+ * and the `ref` land on the `<ul role="tree">`.
11
+ *
12
+ * @extraProp {string} [aria-label] - Names the tree, such as "Files". A tree needs a name, so pass this or `aria-labelledby`, and a development build warns when it has neither.
13
+ * @extraProp {string} [aria-labelledby] - The id of a visible heading or label that names the tree, in place of `aria-label`.
14
+ * @extraProp {React.Ref<HTMLUListElement>} [ref] - Ref forwarded to the `<ul>`.
15
+ */
16
+ export interface TreeViewProps extends Omit<React.HTMLAttributes<HTMLUListElement>, keyof BulmaClassesProps | 'role' | 'onSelect' | 'defaultValue' | 'defaultChecked'>, BulmaClassesProps {
17
+ /**
18
+ * `TreeView.Item`s, nested inside each other for their children. Render
19
+ * an item's children whenever you have them, open or not: the tree hides
20
+ * them while it's closed.
21
+ */
22
+ children?: React.ReactNode;
23
+ /**
24
+ * `none`: nothing is picked, and Enter and Space open and close a parent.
25
+ * `single`: one item, picked with Enter, Space or a click.
26
+ * `checkbox`: any number, checked with Space or a click. A check reaches
27
+ * every enabled item under it, and a parent with some of them checked is mixed.
28
+ */
29
+ selectionMode?: TreeViewSelectionMode;
30
+ /**
31
+ * The selected item's value when you hold the state, or `null` for none.
32
+ * Read with `single` only. Picking an item then only calls
33
+ * `onSelectedChange`, and nothing changes until you pass the new value.
34
+ */
35
+ selected?: string | null;
36
+ /** The item selected to begin with, when the tree holds its own state. Read with `single` only. */
37
+ defaultSelected?: string | null;
38
+ /**
39
+ * Called with the value of the item picked. Not called for the one
40
+ * already selected, and moving focus never calls it.
41
+ */
42
+ onSelectedChange?: (value: string) => void;
43
+ /**
44
+ * The checked items' values when you hold the state. Read with `checkbox`
45
+ * only. A value checks its item, and an enabled item also counts as checked
46
+ * when any item above it is in the list, so a parent's value stands for its
47
+ * whole branch, loaded or not. A disabled item is checked only by its own value.
48
+ */
49
+ checked?: string[];
50
+ /** The values checked to begin with, when the tree holds its own state. Read with `checkbox` only. */
51
+ defaultChecked?: string[];
52
+ /**
53
+ * Called after a check or an uncheck with every item that now shows
54
+ * checked, parents included, plus any earlier value that matches no item
55
+ * the tree knows, left as it was. The order means nothing. Passed back as
56
+ * `checked`, the list shows the same boxes.
57
+ */
58
+ onCheckedChange?: (checked: string[]) => void;
59
+ /**
60
+ * The open items' values when you hold the state. A value stays in the
61
+ * list while its item's parent is closed, so the item reopens as it was.
62
+ */
63
+ expanded?: string[];
64
+ /** The items open to begin with, when the tree holds its own state. */
65
+ defaultExpanded?: string[];
66
+ /** Called with the new list whenever an item opens or closes, `*` included. */
67
+ onExpandedChange?: (expanded: string[]) => void;
68
+ /** The text size, as Bulma's menu sizes it. The rows, the indent and the chevron scale with it. */
69
+ size?: 'small' | 'medium' | 'large';
70
+ }
71
+ /**
72
+ * Props for TreeView.Item, one row and, nested in it, its children. The other
73
+ * attributes and the `ref` land on its `<li role="treeitem">`.
74
+ *
75
+ * @extraProp {React.Ref<HTMLLIElement>} [ref] - Ref forwarded to the `<li>`.
76
+ */
77
+ export interface TreeViewItemProps extends Omit<React.LiHTMLAttributes<HTMLLIElement>, 'role' | 'value' | 'onSelect' | 'children'> {
78
+ /** **Required.** Identifies the item in `selected`, `checked` and `expanded`. Unique in the tree. */
79
+ value: string;
80
+ /**
81
+ * **Required.** What the row shows, and the item's name for assistive
82
+ * tech. Nothing in it should be a link or a button: a tree item does one
83
+ * thing, and nothing else in its row takes focus.
84
+ */
85
+ label: React.ReactNode;
86
+ /** A picture before the label: an `Icon` name, or a node. Hidden from assistive tech. */
87
+ icon?: React.ReactNode;
88
+ /**
89
+ * `TreeView.Item`s under this one. Render them whenever you have them,
90
+ * open or not: the tree hides them while it's closed, and a checkbox tree
91
+ * needs them to know what a check reaches.
92
+ */
93
+ children?: React.ReactNode;
94
+ /** Shows the item as a parent before its children are known, such as a folder that loads when it opens. */
95
+ hasChildren?: boolean;
96
+ /**
97
+ * Reachable with the arrow keys, Home, End and type-ahead, so a screen
98
+ * reader finds it, but never selected, checked, opened or closed. One
99
+ * that's already open stays open.
100
+ */
101
+ disabled?: boolean;
102
+ }
103
+ /**
104
+ * One item in the tree: a row with its chevron, its box, its icon and its label, and, while it's open, a group of the items under it.
105
+ *
106
+ * @function
107
+ * @param {TreeViewItemProps} props - Props for TreeView.Item.
108
+ * @param {React.Ref<HTMLLIElement>} ref - Forwarded ref to the `<li>`.
109
+ * @returns {JSX.Element | null} The rendered item, or nothing under a closed one.
110
+ */
111
+ export declare const TreeViewItem: React.ForwardRefExoticComponent<TreeViewItemProps & React.RefAttributes<HTMLLIElement>>;
112
+ export declare const TreeView: React.ForwardRefExoticComponent<TreeViewProps & React.RefAttributes<HTMLUListElement>> & {
113
+ Item: React.ForwardRefExoticComponent<TreeViewItemProps & React.RefAttributes<HTMLLIElement>>;
114
+ };
115
+ export default TreeView;
@@ -0,0 +1,41 @@
1
+ /** How long typed characters keep adding up to one search, in milliseconds. */
2
+ export declare const TYPEAHEAD_TIMEOUT = 500;
3
+ /**
4
+ * The text typing finds an element by: what it says, without what's hidden
5
+ * from assistive technology, such as an icon whose glyph is a ligature's name.
6
+ */
7
+ export declare function typeaheadText(element: Element): string;
8
+ /**
9
+ * The item typing `search` moves to, or none. A single character looks from
10
+ * the item after the focused one, so typing the same letter again cycles
11
+ * through the items that start with it, and so does a run of one letter
12
+ * ("dd"). A longer search looks from the focused item, so typing more of
13
+ * its own text keeps it.
14
+ *
15
+ * @param items - The items typing can reach, in the order they're shown.
16
+ * @param search - What's been typed, lowercased.
17
+ * @param current - The focused item's index in `items`, or -1 for none.
18
+ * @param textOf - The text an item is found by. By default it's the item's
19
+ * own, read by `typeaheadText`.
20
+ */
21
+ export declare function typeaheadMatch<T extends Element>(items: T[], search: string, current: number, textOf?: (item: T) => string): T | undefined;
22
+ /** What `useTypeahead` returns. */
23
+ export interface Typeahead {
24
+ /**
25
+ * Adds a typed character to the search and returns the whole search,
26
+ * lowercased. Characters typed within `TYPEAHEAD_TIMEOUT` of each other
27
+ * add up, and after that the next one starts a new search.
28
+ */
29
+ type: (char: string) => string;
30
+ /** Forgets what has been typed, so the next character starts a new search. */
31
+ reset: () => void;
32
+ }
33
+ /**
34
+ * Keeps what has been typed for a type-ahead search, and forgets it once
35
+ * `TYPEAHEAD_TIMEOUT` passes with nothing typed. Pair it with
36
+ * `typeaheadMatch` to find the item the search names.
37
+ *
38
+ * @function useTypeahead
39
+ * @returns A way to add a character to the search, and a way to forget it.
40
+ */
41
+ export declare function useTypeahead(): Typeahead;
@@ -1,4 +1,5 @@
1
1
  import type { KeyboardEvent } from 'react';
2
+ export { TYPEAHEAD_TIMEOUT } from './typeahead.js';
2
3
  /**
3
4
  * Every menu-item role, so an item a caller gives `menuitemcheckbox` or
4
5
  * `menuitemradio` (a filter or sort menu) stays reachable.
@@ -11,8 +12,6 @@ export declare const MENU_ITEM_SELECTOR = "[role=\"menuitem\"], [role=\"menuitem
11
12
  export declare function isEnabledMenuItem(item: Element): boolean;
12
13
  /** The items in `menu` the keys reach, in document order. */
13
14
  export declare function enabledMenuItems(menu: Element): HTMLElement[];
14
- /** How long typed characters keep adding up to one search, in milliseconds. */
15
- export declare const TYPEAHEAD_TIMEOUT = 500;
16
15
  /** What `useMenuKeys` returns. */
17
16
  export interface MenuKeys {
18
17
  /** The `keydown` handler for the `role="menu"` element. */
@@ -0,0 +1,88 @@
1
+ import type React from 'react';
2
+ import type { TreeViewSelectionMode } from '../components/TreeView.js';
3
+ /** What an item tells the tree about itself when it registers. */
4
+ export interface TreeViewItemRecord {
5
+ /** The value of the item it's in, or `null` at the top. */
6
+ parent: string | null;
7
+ disabled: boolean;
8
+ /** It has children, or `hasChildren` says it will. */
9
+ isParent: boolean;
10
+ }
11
+ /** An item as a key or a click on it sees it. */
12
+ export interface TreeViewItemInfo extends TreeViewItemRecord {
13
+ value: string;
14
+ open: boolean;
15
+ }
16
+ /** A box's state: checked, unchecked or mixed. */
17
+ export type TreeViewCheckState = boolean | 'mixed';
18
+ /**
19
+ * What an item shows of the selection: `selected` in a `single` tree,
20
+ * `checked` in a `checkbox` tree, and neither with `none`.
21
+ */
22
+ export interface TreeViewItemState {
23
+ selected?: boolean;
24
+ checked?: TreeViewCheckState;
25
+ }
26
+ /** Options for `useTreeView`: the TreeView's own props. */
27
+ export interface TreeViewOptions {
28
+ mode: TreeViewSelectionMode;
29
+ selected?: string | null;
30
+ defaultSelected: string | null;
31
+ onSelectedChange?: (value: string) => void;
32
+ checked?: string[];
33
+ defaultChecked: string[];
34
+ onCheckedChange?: (checked: string[]) => void;
35
+ expanded?: string[];
36
+ defaultExpanded: string[];
37
+ onExpandedChange?: (expanded: string[]) => void;
38
+ }
39
+ /** What `useTreeView` hands the root and its items. */
40
+ export interface TreeViewEngine {
41
+ /** For the `<ul role="tree">`. */
42
+ treeRef: React.MutableRefObject<HTMLUListElement | null>;
43
+ /**
44
+ * Whether a closed item still renders its children, drawing nothing, so a
45
+ * check knows everything under it. Only a checkbox tree needs to.
46
+ */
47
+ rendersClosed: boolean;
48
+ /** The item that holds `tabIndex={0}`. */
49
+ tabStop: string | null;
50
+ isOpen: (value: string) => boolean;
51
+ /**
52
+ * What the item shows of the selection. `path` is the values of the items
53
+ * above it, which decide its box until the items have registered, as on
54
+ * the server.
55
+ */
56
+ stateOf: (value: string, path: string[], disabled: boolean) => TreeViewItemState;
57
+ /** Records an item from its layout effect. Returns the function that forgets it. */
58
+ register: (value: string, record: TreeViewItemRecord) => () => void;
59
+ /** For each item's `<li>` ref: records it, and notices focus leaving with it. */
60
+ setElement: (value: string, element: HTMLLIElement | null) => void;
61
+ /** Focus arrived on the item's own `<li>`. */
62
+ onFocus: (value: string, element: HTMLLIElement) => void;
63
+ /** Focus left the item's own `<li>`. */
64
+ onBlur: () => void;
65
+ /** A key on the item's `<li>`, after its own `onKeyDown`. */
66
+ onKeyDown: (event: React.KeyboardEvent<HTMLLIElement>, item: TreeViewItemInfo) => void;
67
+ /**
68
+ * A click on the item's row, or with `onToggle` on its chevron. Focus and
69
+ * the tab stop move to the item.
70
+ */
71
+ onClick: (element: HTMLLIElement, item: TreeViewItemInfo, onToggle: boolean) => void;
72
+ }
73
+ /**
74
+ * The open state, the selection, the keys, the tab stop and the registry of
75
+ * a TreeView (#1096). Focus moves for real, with a roving tabindex, rather
76
+ * than through `aria-activedescendant`.
77
+ *
78
+ * Every item registers its value, its parent and whether it's disabled from
79
+ * a layout effect. In a checkbox tree a closed item's children still render
80
+ * and register while drawing nothing, so the registry knows every item and a
81
+ * check reaches all of them. The keys read the items on the page, in
82
+ * document order, each time they need them.
83
+ *
84
+ * @function useTreeView
85
+ * @param options - The TreeView's props.
86
+ * @returns The state and handlers the root and its items wire up.
87
+ */
88
+ export declare function useTreeView(options: TreeViewOptions): TreeViewEngine;
@@ -29,6 +29,7 @@ export * from './components/Toast.js';
29
29
  export * from './components/Dialog.js';
30
30
  export * from './components/Carousel.js';
31
31
  export * from './components/Reveal.js';
32
+ export * from './components/TreeView.js';
32
33
  export * from './elements/Block.js';
33
34
  export * from './elements/Box.js';
34
35
  export * from './elements/Button.js';