@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
@@ -1,6 +1,8 @@
1
1
  /* @allxsmith/bestax-bulma — AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */
2
- import { useRef, useEffect, useMemo } from 'react';
2
+ import { useMemo } from 'react';
3
3
  import { getActiveElementInTree } from './shadowDom.js';
4
+ import { useTypeahead, typeaheadMatch } from './typeahead.js';
5
+ export { TYPEAHEAD_TIMEOUT } from './typeahead.js';
4
6
 
5
7
  // INTERNAL: deliberately not exported from src/index.ts.
6
8
  /**
@@ -22,8 +24,6 @@ const isItemOf = (menu, node) => Boolean(node && menu.contains(node) && node.mat
22
24
  function enabledMenuItems(menu) {
23
25
  return Array.from(menu.querySelectorAll(MENU_ITEM_SELECTOR)).filter(isEnabledMenuItem);
24
26
  }
25
- /** How long typed characters keep adding up to one search, in milliseconds. */
26
- const TYPEAHEAD_TIMEOUT = 500;
27
27
  /**
28
28
  * Whether the browser activates `item` on this key by itself: a `<button>`
29
29
  * answers Enter and Space, and a link with an `href` answers Enter.
@@ -33,43 +33,6 @@ function activatesItself(item, key) {
33
33
  return true;
34
34
  return key === 'Enter' && item.tagName === 'A' && item.hasAttribute('href');
35
35
  }
36
- /**
37
- * The text typing finds an item by: what it says, without what's hidden from
38
- * assistive technology, such as an icon whose glyph is a ligature's name.
39
- */
40
- function label(item) {
41
- let text = '';
42
- const read = (node) => {
43
- node.childNodes.forEach(child => {
44
- if (child.nodeType === 3)
45
- text += child.textContent;
46
- else if (child.nodeType === 1 &&
47
- child.getAttribute('aria-hidden') !== 'true') {
48
- read(child);
49
- }
50
- });
51
- };
52
- read(item);
53
- return text.trim().toLowerCase();
54
- }
55
- /**
56
- * The item typing `search` moves to, or none. A single character looks from
57
- * the item after the focused one, so typing the same letter again cycles
58
- * through the items that start with it, and so does a run of one letter
59
- * ("dd"). A longer search looks from the focused item, so typing more of
60
- * its own text keeps it.
61
- */
62
- function typeaheadMatch(items, search, current) {
63
- const repeated = Array.from(search).every(char => char === search[0]);
64
- const wanted = repeated ? search[0] : search;
65
- const start = current < 0 ? 0 : current + (wanted.length === 1 ? 1 : 0);
66
- for (let i = 0; i < items.length; i += 1) {
67
- const item = items[(start + i) % items.length];
68
- if (label(item).startsWith(wanted))
69
- return item;
70
- }
71
- return undefined;
72
- }
73
36
  /**
74
37
  * Moves through and activates the items of a `role="menu"` element from the
75
38
  * keyboard. Put the `onKeyDown` it returns on that element. It reads the
@@ -101,17 +64,8 @@ function typeaheadMatch(items, search, current) {
101
64
  * what has been typed.
102
65
  */
103
66
  function useMenuKeys() {
104
- const typedRef = useRef({ text: '' });
105
- useEffect(() => {
106
- const typed = typedRef.current;
107
- return () => clearTimeout(typed.timer);
108
- }, []);
67
+ const typeahead = useTypeahead();
109
68
  return useMemo(() => {
110
- const reset = () => {
111
- const typed = typedRef.current;
112
- clearTimeout(typed.timer);
113
- typed.text = '';
114
- };
115
69
  const onKeyDown = (event) => {
116
70
  const menu = event.currentTarget;
117
71
  const focused = getActiveElementInTree(menu);
@@ -168,15 +122,11 @@ function useMenuKeys() {
168
122
  return;
169
123
  }
170
124
  event.preventDefault();
171
- const typed = typedRef.current;
172
- typed.text += event.key.toLowerCase();
173
- clearTimeout(typed.timer);
174
- typed.timer = setTimeout(reset, TYPEAHEAD_TIMEOUT);
175
- typeaheadMatch(items, typed.text, current)?.focus();
125
+ typeaheadMatch(items, typeahead.type(event.key), current)?.focus();
176
126
  };
177
- return { onKeyDown, reset };
178
- }, []);
127
+ return { onKeyDown, reset: typeahead.reset };
128
+ }, [typeahead]);
179
129
  }
180
130
 
181
- export { MENU_ITEM_SELECTOR, TYPEAHEAD_TIMEOUT, enabledMenuItems, isEnabledMenuItem, useMenuKeys };
131
+ export { MENU_ITEM_SELECTOR, enabledMenuItems, isEnabledMenuItem, useMenuKeys };
182
132
  //# sourceMappingURL=useMenuKeys.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useMenuKeys.js","sources":["../../../src/helpers/useMenuKeys.ts"],"sourcesContent":["// INTERNAL: deliberately not exported from src/index.ts.\nimport { useEffect, useMemo, useRef } from 'react';\nimport type { KeyboardEvent } from 'react';\nimport { getActiveElementInTree } from './shadowDom';\n\n/**\n * Every menu-item role, so an item a caller gives `menuitemcheckbox` or\n * `menuitemradio` (a filter or sort menu) stays reachable.\n */\nexport const MENU_ITEM_SELECTOR =\n '[role=\"menuitem\"], [role=\"menuitemcheckbox\"], [role=\"menuitemradio\"]';\n\n/**\n * Whether a menu item can be reached and activated: it isn't disabled, as a\n * native control or through `aria-disabled=\"true\"`.\n */\nexport function isEnabledMenuItem(item: Element): boolean {\n return (\n !item.hasAttribute('disabled') &&\n item.getAttribute('aria-disabled') !== 'true'\n );\n}\n\n/** Whether `node` is one of `menu`'s items, enabled or not. */\nconst isItemOf = (menu: Element, node: Element | null): boolean =>\n Boolean(node && menu.contains(node) && node.matches(MENU_ITEM_SELECTOR));\n\n/** The items in `menu` the keys reach, in document order. */\nexport function enabledMenuItems(menu: Element): HTMLElement[] {\n return Array.from(\n menu.querySelectorAll<HTMLElement>(MENU_ITEM_SELECTOR)\n ).filter(isEnabledMenuItem);\n}\n\n/** How long typed characters keep adding up to one search, in milliseconds. */\nexport const TYPEAHEAD_TIMEOUT = 500;\n\n/**\n * Whether the browser activates `item` on this key by itself: a `<button>`\n * answers Enter and Space, and a link with an `href` answers Enter.\n */\nfunction activatesItself(item: HTMLElement, key: string): boolean {\n if (item.tagName === 'BUTTON') return true;\n return key === 'Enter' && item.tagName === 'A' && item.hasAttribute('href');\n}\n\n/**\n * The text typing finds an item by: what it says, without what's hidden from\n * assistive technology, such as an icon whose glyph is a ligature's name.\n */\nfunction label(item: Element): string {\n let text = '';\n const read = (node: Node) => {\n node.childNodes.forEach(child => {\n if (child.nodeType === 3) text += child.textContent;\n else if (\n child.nodeType === 1 &&\n (child as Element).getAttribute('aria-hidden') !== 'true'\n ) {\n read(child);\n }\n });\n };\n read(item);\n return text.trim().toLowerCase();\n}\n\n/**\n * The item typing `search` moves to, or none. A single character looks from\n * the item after the focused one, so typing the same letter again cycles\n * through the items that start with it, and so does a run of one letter\n * (\"dd\"). A longer search looks from the focused item, so typing more of\n * its own text keeps it.\n */\nfunction typeaheadMatch(\n items: HTMLElement[],\n search: string,\n current: number\n): HTMLElement | undefined {\n const repeated = Array.from(search).every(char => char === search[0]);\n const wanted = repeated ? search[0] : search;\n const start = current < 0 ? 0 : current + (wanted.length === 1 ? 1 : 0);\n for (let i = 0; i < items.length; i += 1) {\n const item = items[(start + i) % items.length];\n if (label(item).startsWith(wanted)) return item;\n }\n return undefined;\n}\n\n/** What `useMenuKeys` returns. */\nexport interface MenuKeys {\n /** The `keydown` handler for the `role=\"menu\"` element. */\n onKeyDown: (event: KeyboardEvent<HTMLElement>) => void;\n /**\n * Forgets what has been typed, so the next character starts a new search.\n * Call it when the menu opens, so a search typed just before it last\n * closed doesn't carry over.\n */\n reset: () => void;\n}\n\n/**\n * Moves through and activates the items of a `role=\"menu\"` element from the\n * keyboard. Put the `onKeyDown` it returns on that element. It reads the\n * items from the element each time, so items that come and go are found,\n * and it renders nothing.\n *\n * It acts only while focus is on the menu element itself or on one of its\n * items, so anything else focusable inside the menu, such as a filter\n * field, keeps its own keys.\n *\n * - Down and Up move to the next and previous enabled item, wrapping, and\n * Home and End go to the first and last. From no item, Down goes to the\n * first and Up to the last.\n * - Enter and Space activate the focused item. A `<button>`, and a link with\n * an `href` for Enter, get the browser's own click, and anything else gets\n * one from here, so its `onClick` runs either way. A key held down\n * activates it once. A key handler on the item that called\n * `preventDefault()` has claimed the key, and nothing else runs it.\n * - A printable character moves to the next enabled item whose text starts\n * with what has been typed. Characters typed within `TYPEAHEAD_TIMEOUT` of\n * each other add up to one search, until `reset` starts a new one.\n *\n * Items are `MENU_ITEM_SELECTOR` matches, and disabled ones are skipped by\n * all of the above. Escape and Tab are left to the menu's owner, since what\n * closing means is up to it, and so is any other key.\n *\n * @function useMenuKeys\n * @returns The `keydown` handler for the menu element, and a way to forget\n * what has been typed.\n */\nexport function useMenuKeys(): MenuKeys {\n const typedRef = useRef<{\n text: string;\n timer?: ReturnType<typeof setTimeout>;\n }>({ text: '' });\n\n useEffect(() => {\n const typed = typedRef.current;\n return () => clearTimeout(typed.timer);\n }, []);\n\n return useMemo<MenuKeys>(() => {\n const reset = () => {\n const typed = typedRef.current;\n clearTimeout(typed.timer);\n typed.text = '';\n };\n const onKeyDown = (event: KeyboardEvent<HTMLElement>) => {\n const menu = event.currentTarget;\n const focused = getActiveElementInTree(menu);\n if (focused !== menu && !isItemOf(menu, focused)) return;\n const items = enabledMenuItems(menu);\n if (!items.length) return;\n const current = items.indexOf(focused as HTMLElement);\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault();\n items[current >= 0 ? (current + 1) % items.length : 0].focus();\n return;\n case 'ArrowUp':\n event.preventDefault();\n items[\n current >= 0\n ? (current - 1 + items.length) % items.length\n : items.length - 1\n ].focus();\n return;\n case 'Home':\n event.preventDefault();\n items[0].focus();\n return;\n case 'End':\n event.preventDefault();\n items[items.length - 1].focus();\n return;\n case 'Enter':\n case ' ': {\n if (current < 0 || event.defaultPrevented) return;\n const item = items[current];\n if (activatesItself(item, event.key)) {\n // The browser clicks on Enter's keydown, and a held key sends one\n // per auto-repeat. Space clicks on keyup, once however long it's\n // held.\n if (event.repeat && event.key === 'Enter') event.preventDefault();\n return;\n }\n // Space would scroll the page, and a held key repeats.\n event.preventDefault();\n if (!event.repeat) item.click();\n return;\n }\n default:\n break;\n }\n\n if (\n event.key.length !== 1 ||\n event.ctrlKey ||\n event.metaKey ||\n event.altKey ||\n event.defaultPrevented\n ) {\n return;\n }\n event.preventDefault();\n const typed = typedRef.current;\n typed.text += event.key.toLowerCase();\n clearTimeout(typed.timer);\n typed.timer = setTimeout(reset, TYPEAHEAD_TIMEOUT);\n typeaheadMatch(items, typed.text, current)?.focus();\n };\n return { onKeyDown, reset };\n }, []);\n}\n"],"names":[],"mappings":";;;;AAAA;AAKA;;;AAGG;AACI,MAAM,kBAAkB,GAC7B;AAEF;;;AAGG;AACG,SAAU,iBAAiB,CAAC,IAAa,EAAA;AAC7C,IAAA,QACE,CAAC,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC;QAC9B,IAAI,CAAC,YAAY,CAAC,eAAe,CAAC,KAAK,MAAM;AAEjD;AAEA;AACA,MAAM,QAAQ,GAAG,CAAC,IAAa,EAAE,IAAoB,KACnD,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC;AAE1E;AACM,SAAU,gBAAgB,CAAC,IAAa,EAAA;AAC5C,IAAA,OAAO,KAAK,CAAC,IAAI,CACf,IAAI,CAAC,gBAAgB,CAAc,kBAAkB,CAAC,CACvD,CAAC,MAAM,CAAC,iBAAiB,CAAC;AAC7B;AAEA;AACO,MAAM,iBAAiB,GAAG;AAEjC;;;AAGG;AACH,SAAS,eAAe,CAAC,IAAiB,EAAE,GAAW,EAAA;AACrD,IAAA,IAAI,IAAI,CAAC,OAAO,KAAK,QAAQ;AAAE,QAAA,OAAO,IAAI;AAC1C,IAAA,OAAO,GAAG,KAAK,OAAO,IAAI,IAAI,CAAC,OAAO,KAAK,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC;AAC7E;AAEA;;;AAGG;AACH,SAAS,KAAK,CAAC,IAAa,EAAA;IAC1B,IAAI,IAAI,GAAG,EAAE;AACb,IAAA,MAAM,IAAI,GAAG,CAAC,IAAU,KAAI;AAC1B,QAAA,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,KAAK,IAAG;AAC9B,YAAA,IAAI,KAAK,CAAC,QAAQ,KAAK,CAAC;AAAE,gBAAA,IAAI,IAAI,KAAK,CAAC,WAAW;AAC9C,iBAAA,IACH,KAAK,CAAC,QAAQ,KAAK,CAAC;gBACnB,KAAiB,CAAC,YAAY,CAAC,aAAa,CAAC,KAAK,MAAM,EACzD;gBACA,IAAI,CAAC,KAAK,CAAC;YACb;AACF,QAAA,CAAC,CAAC;AACJ,IAAA,CAAC;IACD,IAAI,CAAC,IAAI,CAAC;AACV,IAAA,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE;AAClC;AAEA;;;;;;AAMG;AACH,SAAS,cAAc,CACrB,KAAoB,EACpB,MAAc,EACd,OAAe,EAAA;IAEf,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,IAAI,IAAI,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC;AACrE,IAAA,MAAM,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM;AAC5C,IAAA,MAAM,KAAK,GAAG,OAAO,GAAG,CAAC,GAAG,CAAC,GAAG,OAAO,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AACvE,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE;AACxC,QAAA,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,KAAK,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC;QAC9C,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC;AAAE,YAAA,OAAO,IAAI;IACjD;AACA,IAAA,OAAO,SAAS;AAClB;AAcA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BG;SACa,WAAW,GAAA;IACzB,MAAM,QAAQ,GAAG,MAAM,CAGpB,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;IAEhB,SAAS,CAAC,MAAK;AACb,QAAA,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO;QAC9B,OAAO,MAAM,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC;IACxC,CAAC,EAAE,EAAE,CAAC;IAEN,OAAO,OAAO,CAAW,MAAK;QAC5B,MAAM,KAAK,GAAG,MAAK;AACjB,YAAA,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO;AAC9B,YAAA,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC;AACzB,YAAA,KAAK,CAAC,IAAI,GAAG,EAAE;AACjB,QAAA,CAAC;AACD,QAAA,MAAM,SAAS,GAAG,CAAC,KAAiC,KAAI;AACtD,YAAA,MAAM,IAAI,GAAG,KAAK,CAAC,aAAa;AAChC,YAAA,MAAM,OAAO,GAAG,sBAAsB,CAAC,IAAI,CAAC;YAC5C,IAAI,OAAO,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;gBAAE;AAClD,YAAA,MAAM,KAAK,GAAG,gBAAgB,CAAC,IAAI,CAAC;YACpC,IAAI,CAAC,KAAK,CAAC,MAAM;gBAAE;YACnB,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,OAAsB,CAAC;AACrD,YAAA,QAAQ,KAAK,CAAC,GAAG;AACf,gBAAA,KAAK,WAAW;oBACd,KAAK,CAAC,cAAc,EAAE;oBACtB,KAAK,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,OAAO,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE;oBAC9D;AACF,gBAAA,KAAK,SAAS;oBACZ,KAAK,CAAC,cAAc,EAAE;oBACtB,KAAK,CACH,OAAO,IAAI;AACT,0BAAE,CAAC,OAAO,GAAG,CAAC,GAAG,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC;0BACrC,KAAK,CAAC,MAAM,GAAG,CAAC,CACrB,CAAC,KAAK,EAAE;oBACT;AACF,gBAAA,KAAK,MAAM;oBACT,KAAK,CAAC,cAAc,EAAE;AACtB,oBAAA,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE;oBAChB;AACF,gBAAA,KAAK,KAAK;oBACR,KAAK,CAAC,cAAc,EAAE;oBACtB,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE;oBAC/B;AACF,gBAAA,KAAK,OAAO;gBACZ,KAAK,GAAG,EAAE;AACR,oBAAA,IAAI,OAAO,GAAG,CAAC,IAAI,KAAK,CAAC,gBAAgB;wBAAE;AAC3C,oBAAA,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC;oBAC3B,IAAI,eAAe,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE;;;;wBAIpC,IAAI,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,OAAO;4BAAE,KAAK,CAAC,cAAc,EAAE;wBACjE;oBACF;;oBAEA,KAAK,CAAC,cAAc,EAAE;oBACtB,IAAI,CAAC,KAAK,CAAC,MAAM;wBAAE,IAAI,CAAC,KAAK,EAAE;oBAC/B;gBACF;;AAKF,YAAA,IACE,KAAK,CAAC,GAAG,CAAC,MAAM,KAAK,CAAC;AACtB,gBAAA,KAAK,CAAC,OAAO;AACb,gBAAA,KAAK,CAAC,OAAO;AACb,gBAAA,KAAK,CAAC,MAAM;gBACZ,KAAK,CAAC,gBAAgB,EACtB;gBACA;YACF;YACA,KAAK,CAAC,cAAc,EAAE;AACtB,YAAA,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO;YAC9B,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,GAAG,CAAC,WAAW,EAAE;AACrC,YAAA,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC;YACzB,KAAK,CAAC,KAAK,GAAG,UAAU,CAAC,KAAK,EAAE,iBAAiB,CAAC;AAClD,YAAA,cAAc,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,KAAK,EAAE;AACrD,QAAA,CAAC;AACD,QAAA,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE;IAC7B,CAAC,EAAE,EAAE,CAAC;AACR;;"}
1
+ {"version":3,"file":"useMenuKeys.js","sources":["../../../src/helpers/useMenuKeys.ts"],"sourcesContent":["// INTERNAL: deliberately not exported from src/index.ts.\nimport { useMemo } from 'react';\nimport type { KeyboardEvent } from 'react';\nimport { getActiveElementInTree } from './shadowDom';\nimport { typeaheadMatch, useTypeahead } from './typeahead';\n\nexport { TYPEAHEAD_TIMEOUT } from './typeahead';\n\n/**\n * Every menu-item role, so an item a caller gives `menuitemcheckbox` or\n * `menuitemradio` (a filter or sort menu) stays reachable.\n */\nexport const MENU_ITEM_SELECTOR =\n '[role=\"menuitem\"], [role=\"menuitemcheckbox\"], [role=\"menuitemradio\"]';\n\n/**\n * Whether a menu item can be reached and activated: it isn't disabled, as a\n * native control or through `aria-disabled=\"true\"`.\n */\nexport function isEnabledMenuItem(item: Element): boolean {\n return (\n !item.hasAttribute('disabled') &&\n item.getAttribute('aria-disabled') !== 'true'\n );\n}\n\n/** Whether `node` is one of `menu`'s items, enabled or not. */\nconst isItemOf = (menu: Element, node: Element | null): boolean =>\n Boolean(node && menu.contains(node) && node.matches(MENU_ITEM_SELECTOR));\n\n/** The items in `menu` the keys reach, in document order. */\nexport function enabledMenuItems(menu: Element): HTMLElement[] {\n return Array.from(\n menu.querySelectorAll<HTMLElement>(MENU_ITEM_SELECTOR)\n ).filter(isEnabledMenuItem);\n}\n\n/**\n * Whether the browser activates `item` on this key by itself: a `<button>`\n * answers Enter and Space, and a link with an `href` answers Enter.\n */\nfunction activatesItself(item: HTMLElement, key: string): boolean {\n if (item.tagName === 'BUTTON') return true;\n return key === 'Enter' && item.tagName === 'A' && item.hasAttribute('href');\n}\n\n/** What `useMenuKeys` returns. */\nexport interface MenuKeys {\n /** The `keydown` handler for the `role=\"menu\"` element. */\n onKeyDown: (event: KeyboardEvent<HTMLElement>) => void;\n /**\n * Forgets what has been typed, so the next character starts a new search.\n * Call it when the menu opens, so a search typed just before it last\n * closed doesn't carry over.\n */\n reset: () => void;\n}\n\n/**\n * Moves through and activates the items of a `role=\"menu\"` element from the\n * keyboard. Put the `onKeyDown` it returns on that element. It reads the\n * items from the element each time, so items that come and go are found,\n * and it renders nothing.\n *\n * It acts only while focus is on the menu element itself or on one of its\n * items, so anything else focusable inside the menu, such as a filter\n * field, keeps its own keys.\n *\n * - Down and Up move to the next and previous enabled item, wrapping, and\n * Home and End go to the first and last. From no item, Down goes to the\n * first and Up to the last.\n * - Enter and Space activate the focused item. A `<button>`, and a link with\n * an `href` for Enter, get the browser's own click, and anything else gets\n * one from here, so its `onClick` runs either way. A key held down\n * activates it once. A key handler on the item that called\n * `preventDefault()` has claimed the key, and nothing else runs it.\n * - A printable character moves to the next enabled item whose text starts\n * with what has been typed. Characters typed within `TYPEAHEAD_TIMEOUT` of\n * each other add up to one search, until `reset` starts a new one.\n *\n * Items are `MENU_ITEM_SELECTOR` matches, and disabled ones are skipped by\n * all of the above. Escape and Tab are left to the menu's owner, since what\n * closing means is up to it, and so is any other key.\n *\n * @function useMenuKeys\n * @returns The `keydown` handler for the menu element, and a way to forget\n * what has been typed.\n */\nexport function useMenuKeys(): MenuKeys {\n const typeahead = useTypeahead();\n\n return useMemo<MenuKeys>(() => {\n const onKeyDown = (event: KeyboardEvent<HTMLElement>) => {\n const menu = event.currentTarget;\n const focused = getActiveElementInTree(menu);\n if (focused !== menu && !isItemOf(menu, focused)) return;\n const items = enabledMenuItems(menu);\n if (!items.length) return;\n const current = items.indexOf(focused as HTMLElement);\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault();\n items[current >= 0 ? (current + 1) % items.length : 0].focus();\n return;\n case 'ArrowUp':\n event.preventDefault();\n items[\n current >= 0\n ? (current - 1 + items.length) % items.length\n : items.length - 1\n ].focus();\n return;\n case 'Home':\n event.preventDefault();\n items[0].focus();\n return;\n case 'End':\n event.preventDefault();\n items[items.length - 1].focus();\n return;\n case 'Enter':\n case ' ': {\n if (current < 0 || event.defaultPrevented) return;\n const item = items[current];\n if (activatesItself(item, event.key)) {\n // The browser clicks on Enter's keydown, and a held key sends one\n // per auto-repeat. Space clicks on keyup, once however long it's\n // held.\n if (event.repeat && event.key === 'Enter') event.preventDefault();\n return;\n }\n // Space would scroll the page, and a held key repeats.\n event.preventDefault();\n if (!event.repeat) item.click();\n return;\n }\n default:\n break;\n }\n\n if (\n event.key.length !== 1 ||\n event.ctrlKey ||\n event.metaKey ||\n event.altKey ||\n event.defaultPrevented\n ) {\n return;\n }\n event.preventDefault();\n typeaheadMatch(items, typeahead.type(event.key), current)?.focus();\n };\n return { onKeyDown, reset: typeahead.reset };\n }, [typeahead]);\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,380 @@
1
+ /* @allxsmith/bestax-bulma — AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */
2
+ import { useRef, useReducer, useState, useCallback } from 'react';
3
+ import { useClientLayoutEffect } from './useClientLayoutEffect.js';
4
+ import { getActiveElementInTree } from './shadowDom.js';
5
+ import { useTypeahead, typeaheadMatch, typeaheadText } from './typeahead.js';
6
+
7
+ // INTERNAL: deliberately not exported from src/index.ts.
8
+ /**
9
+ * What Enter, Space and a click on the row do in each mode, from the tables
10
+ * in #1096. `open` opens or closes a parent and does nothing on a leaf. A
11
+ * click on the chevron only ever opens or closes.
12
+ */
13
+ const MODES = {
14
+ none: { enter: ['open'], space: ['open'], click: ['open'] },
15
+ single: { enter: ['select'], space: ['select'], click: ['select', 'open'] },
16
+ checkbox: { enter: ['open'], space: ['check'], click: ['check'] },
17
+ };
18
+ /** Left and Right, swapped for a right-to-left page. */
19
+ const MIRRORED = {
20
+ ArrowLeft: 'ArrowRight',
21
+ ArrowRight: 'ArrowLeft',
22
+ };
23
+ /** An IME is composing, so the key belongs to it (`keyCode` 229 is Safari's word for it). */
24
+ const isComposing$2 = (event) => event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229;
25
+ /** The registry's items on the page, in document order. */
26
+ const shownIn = (tree, { values }) => Array.from(tree.querySelectorAll('[role="treeitem"]')).filter(element => values.has(element));
27
+ /** The item an item on the page sits in: its group's `<li>`, or none at the top. */
28
+ function parentItemOf(element) {
29
+ const group = element.parentElement;
30
+ return group.getAttribute('role') === 'group' ? group.parentElement : null;
31
+ }
32
+ /** The values of the items an item on the page is in, from the top. */
33
+ function pathOf(element, { values }) {
34
+ const path = [];
35
+ for (let at = parentItemOf(element); at; at = parentItemOf(at)) {
36
+ path.unshift(values.get(at));
37
+ }
38
+ return path;
39
+ }
40
+ /**
41
+ * Every known item's check state, by the rules in #1096:
42
+ *
43
+ * - a disabled item is checked only when its own value is listed;
44
+ * - an enabled item with no enabled items anywhere under it is checked when
45
+ * it, or any item above it, is listed;
46
+ * - any other enabled item follows the items of that second kind in its
47
+ * branch: checked when they all are, unchecked when none is, mixed between.
48
+ *
49
+ * Also returns the items of the second kind, which are what a toggle sets.
50
+ */
51
+ function readChecks(records, listed) {
52
+ const kids = new Map();
53
+ records.forEach(({ parent }, value) => {
54
+ kids.set(parent, [...(kids.get(parent) ?? []), value]);
55
+ });
56
+ const states = new Map();
57
+ const ends = [];
58
+ // How many enabled ends the branch holds, and how many are checked.
59
+ const visit = (value, above) => {
60
+ const marked = above || listed.has(value);
61
+ let total = 0;
62
+ let on = 0;
63
+ for (const kid of kids.get(value) ?? []) {
64
+ const [t, o] = visit(kid, marked);
65
+ total += t;
66
+ on += o;
67
+ }
68
+ if (records.get(value).disabled) {
69
+ states.set(value, listed.has(value));
70
+ return [total, on];
71
+ }
72
+ if (total === 0) {
73
+ states.set(value, marked);
74
+ ends.push(value);
75
+ return [1, marked ? 1 : 0];
76
+ }
77
+ states.set(value, on === total ? true : on > 0 && 'mixed');
78
+ return [total, on];
79
+ };
80
+ for (const top of kids.get(null) ?? [])
81
+ visit(top, false);
82
+ return { states, ends };
83
+ }
84
+ /**
85
+ * The list a toggle of `value` leaves: it and every enabled item under it
86
+ * take the opposite of its state (a mixed one checks), and the rest is read
87
+ * back by the rules. That's every item that shows checked, parents
88
+ * included, plus any listed value no item has.
89
+ */
90
+ function toggledList(records, list, value) {
91
+ const { states, ends } = readChecks(records, new Set(list));
92
+ const to = states.get(value) !== true;
93
+ // An end was reached from the top, so everything above it is known.
94
+ const under = (end) => {
95
+ for (let at = end; at !== null;) {
96
+ if (at === value)
97
+ return true;
98
+ at = records.get(at).parent;
99
+ }
100
+ return false;
101
+ };
102
+ const seed = new Set(ends.filter(end => (under(end) ? to : states.get(end) === true)));
103
+ records.forEach(({ disabled }, item) => {
104
+ if (disabled && states.get(item) === true)
105
+ seed.add(item);
106
+ });
107
+ const after = readChecks(records, seed).states;
108
+ return [
109
+ ...Array.from(after)
110
+ .filter(([, state]) => state === true)
111
+ .map(([item]) => item),
112
+ ...list.filter(item => !records.has(item)),
113
+ ];
114
+ }
115
+ /** A value the caller holds, or one this hook holds when the caller doesn't. */
116
+ function useControlled(controlled, initial) {
117
+ const [own, setOwn] = useState(initial);
118
+ const isControlled = controlled !== undefined;
119
+ const set = (next) => {
120
+ if (!isControlled)
121
+ setOwn(next);
122
+ };
123
+ return [isControlled ? controlled : own, set];
124
+ }
125
+ /**
126
+ * The open state, the selection, the keys, the tab stop and the registry of
127
+ * a TreeView (#1096). Focus moves for real, with a roving tabindex, rather
128
+ * than through `aria-activedescendant`.
129
+ *
130
+ * Every item registers its value, its parent and whether it's disabled from
131
+ * a layout effect. In a checkbox tree a closed item's children still render
132
+ * and register while drawing nothing, so the registry knows every item and a
133
+ * check reaches all of them. The keys read the items on the page, in
134
+ * document order, each time they need them.
135
+ *
136
+ * @function useTreeView
137
+ * @param options - The TreeView's props.
138
+ * @returns The state and handlers the root and its items wire up.
139
+ */
140
+ function useTreeView(options) {
141
+ // An own key only, so a name every object inherits, like `toString`, is a
142
+ // mode the tree doesn't know too.
143
+ const mode = Object.prototype.hasOwnProperty.call(MODES, options.mode)
144
+ ? options.mode
145
+ : 'none';
146
+ const actions = MODES[mode];
147
+ const [expanded, setExpanded] = useControlled(options.expanded, options.defaultExpanded);
148
+ const [selection, setSelection] = useControlled(options.selected, options.defaultSelected);
149
+ const [checkedList, setChecked] = useControlled(options.checked, options.defaultChecked);
150
+ // Each is read in its own mode only.
151
+ const selected = mode === 'single' ? selection : null;
152
+ const listed = new Set(mode === 'checkbox' ? checkedList : []);
153
+ const treeRef = useRef(null);
154
+ const registryRef = useRef({
155
+ records: new Map(),
156
+ elements: new Map(),
157
+ values: new WeakMap(),
158
+ });
159
+ // Items register from their own effects, so they ask for a render to be read.
160
+ const [, refresh] = useReducer((n) => n + 1, 0);
161
+ const typeahead = useTypeahead();
162
+ // The item with focus, and where the stop rests while focus is elsewhere:
163
+ // `undefined` until the tree has mounted. With the values above it, for
164
+ // when it goes away.
165
+ const [focused, setFocused] = useState(null);
166
+ const [resting, setResting] = useState();
167
+ const restingPathRef = useRef([]);
168
+ // The values above an item that left the page with focus in it.
169
+ const lostRef = useRef(null);
170
+ const { records } = registryRef.current;
171
+ const checks = mode === 'checkbox'
172
+ ? // eslint-disable-next-line react-hooks/refs -- read in render on purpose: items write the registry from their layout effects and then ask for this render with `refresh`
173
+ readChecks(records, listed).states
174
+ : undefined;
175
+ const tabStop = focused ?? (resting === undefined ? selected : resting);
176
+ const isOpen = (value) => expanded.includes(value);
177
+ const stateOf = (value, path, disabled) => {
178
+ if (mode === 'single') {
179
+ return disabled ? {} : { selected: value === selected };
180
+ }
181
+ if (checks) {
182
+ // Until the items register, a value stands for its branch, and a
183
+ // parent can't know it's mixed.
184
+ const fromList = listed.has(value) || (!disabled && path.some(v => listed.has(v)));
185
+ return { checked: checks.get(value) ?? fromList };
186
+ }
187
+ return {};
188
+ };
189
+ const changeOpen = (next) => {
190
+ setExpanded(next);
191
+ options.onExpandedChange?.(next);
192
+ };
193
+ const run = {
194
+ open: ({ value, isParent, open }) => {
195
+ if (isParent) {
196
+ changeOpen(open ? expanded.filter(v => v !== value) : [...expanded, value]);
197
+ }
198
+ },
199
+ select: ({ value }) => {
200
+ if (value === selected)
201
+ return;
202
+ setSelection(value);
203
+ options.onSelectedChange?.(value);
204
+ },
205
+ check: ({ value }) => {
206
+ const next = toggledList(records, checkedList, value);
207
+ setChecked(next);
208
+ options.onCheckedChange?.(next);
209
+ },
210
+ };
211
+ /** Disabled items are never selected, checked, opened or closed. */
212
+ const act = (item, list) => {
213
+ if (!item.disabled)
214
+ list.forEach(action => run[action](item));
215
+ };
216
+ // After every render, put the stop where it belongs. While focus is in
217
+ // the tree it's the focused item. Otherwise it's the selected item, or the
218
+ // first checked one on the page, and failing that it stays where it was.
219
+ // An item that goes away hands it to its nearest ancestor on the page, or
220
+ // to the first item, and focus goes with it if it had focus.
221
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- after every render on purpose: it reads the page, and sets state only when what it reads has changed
222
+ useClientLayoutEffect(() => {
223
+ const tree = treeRef.current;
224
+ const registry = registryRef.current;
225
+ // Each item's element is read off the page with its value, rather than
226
+ // out of `elements`, which holds one element per value: two items
227
+ // sharing a value, which they shouldn't, can leave it without one the
228
+ // page still has.
229
+ const shown = shownIn(tree, registry);
230
+ const onPage = shown.map(element => registry.values.get(element));
231
+ const elementOf = (value) => shown[onPage.indexOf(value)];
232
+ const isShown = (value) => value != null && onPage.includes(value);
233
+ const heir = (path) => [...path].reverse().find(isShown) ?? onPage[0] ?? null;
234
+ const lost = lostRef.current;
235
+ lostRef.current = null;
236
+ if (lost && !tree.contains(getActiveElementInTree(tree))) {
237
+ const to = heir(lost);
238
+ if (to)
239
+ elementOf(to).focus();
240
+ return;
241
+ }
242
+ if (focused !== null && !isShown(focused))
243
+ setFocused(null);
244
+ const preferred = onPage.find(value => value === selected || checks?.get(value) === true);
245
+ const next = preferred ?? (isShown(resting) ? resting : heir(restingPathRef.current));
246
+ if (next !== resting) {
247
+ setResting(next);
248
+ restingPathRef.current = next ? pathOf(elementOf(next), registry) : [];
249
+ }
250
+ });
251
+ const register = useCallback((value, record) => {
252
+ const { records: known } = registryRef.current;
253
+ known.set(value, record);
254
+ refresh();
255
+ return () => {
256
+ if (known.get(value) !== record)
257
+ return;
258
+ known.delete(value);
259
+ refresh();
260
+ };
261
+ }, []);
262
+ const setElement = useCallback((value, element) => {
263
+ const registry = registryRef.current;
264
+ if (element) {
265
+ registry.elements.set(value, element);
266
+ registry.values.set(element, value);
267
+ return;
268
+ }
269
+ const gone = registry.elements.get(value);
270
+ if (!gone)
271
+ return;
272
+ // The ref lets go before the element leaves the page, so it can still
273
+ // say whether focus was in it, and where it sat.
274
+ if (!lostRef.current && gone.contains(getActiveElementInTree(gone))) {
275
+ lostRef.current = [...pathOf(gone, registry), value];
276
+ }
277
+ registry.elements.delete(value);
278
+ }, []);
279
+ const onFocus = (value, element) => {
280
+ setFocused(value);
281
+ setResting(value);
282
+ restingPathRef.current = pathOf(element, registryRef.current);
283
+ };
284
+ const onBlur = () => setFocused(null);
285
+ const onKeyDown = (event, item) => {
286
+ const element = event.currentTarget;
287
+ // A key on an item inside this one bubbles here too, and is its own.
288
+ if (event.target !== element ||
289
+ event.defaultPrevented ||
290
+ event.altKey ||
291
+ event.ctrlKey ||
292
+ event.metaKey ||
293
+ isComposing$2(event)) {
294
+ return;
295
+ }
296
+ const registry = registryRef.current;
297
+ const items = shownIn(treeRef.current, registry);
298
+ const at = items.indexOf(element);
299
+ const rtl = getComputedStyle(element).direction === 'rtl';
300
+ const key = (rtl && MIRRORED[event.key]) || event.key;
301
+ switch (key) {
302
+ case 'ArrowDown':
303
+ items[at + 1]?.focus();
304
+ break;
305
+ case 'ArrowUp':
306
+ items[at - 1]?.focus();
307
+ break;
308
+ case 'Home':
309
+ items[0].focus();
310
+ break;
311
+ case 'End':
312
+ items[items.length - 1].focus();
313
+ break;
314
+ case 'ArrowRight': {
315
+ const next = items[at + 1];
316
+ if (!item.open)
317
+ act(item, ['open']);
318
+ else if (next && parentItemOf(next) === element)
319
+ next.focus();
320
+ break;
321
+ }
322
+ case 'ArrowLeft':
323
+ if (item.open && !item.disabled)
324
+ act(item, ['open']);
325
+ else
326
+ parentItemOf(element)?.focus();
327
+ break;
328
+ // A held key acts once.
329
+ case 'Enter':
330
+ if (!event.repeat)
331
+ act(item, actions.enter);
332
+ break;
333
+ case ' ':
334
+ if (!event.repeat)
335
+ act(item, actions.space);
336
+ break;
337
+ case '*': {
338
+ const closed = [];
339
+ registry.records.forEach((record, value) => {
340
+ if (record.parent === item.parent &&
341
+ record.isParent &&
342
+ !record.disabled &&
343
+ !expanded.includes(value)) {
344
+ closed.push(value);
345
+ }
346
+ });
347
+ if (closed.length)
348
+ changeOpen([...expanded, ...closed]);
349
+ break;
350
+ }
351
+ default:
352
+ // Type-ahead takes a printable character. Anything else, Escape and
353
+ // Tab included, is left to whatever is around the tree.
354
+ if (event.key.length !== 1)
355
+ return;
356
+ typeaheadMatch(items, typeahead.type(event.key), at, el => typeaheadText(el.firstElementChild))?.focus();
357
+ }
358
+ event.preventDefault();
359
+ };
360
+ const onClick = (element, item, onToggle) => {
361
+ element.focus();
362
+ act(item, onToggle ? ['open'] : actions.click);
363
+ };
364
+ return {
365
+ treeRef,
366
+ rendersClosed: mode === 'checkbox',
367
+ tabStop,
368
+ isOpen,
369
+ stateOf,
370
+ register,
371
+ setElement,
372
+ onFocus,
373
+ onBlur,
374
+ onKeyDown,
375
+ onClick,
376
+ };
377
+ }
378
+
379
+ export { useTreeView };
380
+ //# sourceMappingURL=useTreeView.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTreeView.js","sources":["../../../src/helpers/useTreeView.ts"],"sourcesContent":["// INTERNAL: deliberately not exported from src/index.ts.\nimport { useCallback, useReducer, useRef, useState } from 'react';\nimport type React from 'react';\nimport type { TreeViewSelectionMode } from '../components/TreeView';\nimport { useClientLayoutEffect } from './useClientLayoutEffect';\nimport { getActiveElementInTree } from './shadowDom';\nimport { typeaheadMatch, typeaheadText, useTypeahead } from './typeahead';\n\n/** What an item tells the tree about itself when it registers. */\nexport interface TreeViewItemRecord {\n /** The value of the item it's in, or `null` at the top. */\n parent: string | null;\n disabled: boolean;\n /** It has children, or `hasChildren` says it will. */\n isParent: boolean;\n}\n\n/** An item as a key or a click on it sees it. */\nexport interface TreeViewItemInfo extends TreeViewItemRecord {\n value: string;\n open: boolean;\n}\n\n/** A box's state: checked, unchecked or mixed. */\nexport type TreeViewCheckState = boolean | 'mixed';\n\n/**\n * What an item shows of the selection: `selected` in a `single` tree,\n * `checked` in a `checkbox` tree, and neither with `none`.\n */\nexport interface TreeViewItemState {\n selected?: boolean;\n checked?: TreeViewCheckState;\n}\n\ntype Action = 'open' | 'select' | 'check';\n\n/**\n * What Enter, Space and a click on the row do in each mode, from the tables\n * in #1096. `open` opens or closes a parent and does nothing on a leaf. A\n * click on the chevron only ever opens or closes.\n */\nconst MODES: Record<\n TreeViewSelectionMode,\n { enter: Action[]; space: Action[]; click: Action[] }\n> = {\n none: { enter: ['open'], space: ['open'], click: ['open'] },\n single: { enter: ['select'], space: ['select'], click: ['select', 'open'] },\n checkbox: { enter: ['open'], space: ['check'], click: ['check'] },\n};\n\n/** Left and Right, swapped for a right-to-left page. */\nconst MIRRORED: Record<string, string> = {\n ArrowLeft: 'ArrowRight',\n ArrowRight: 'ArrowLeft',\n};\n\n/** An IME is composing, so the key belongs to it (`keyCode` 229 is Safari's word for it). */\nconst isComposing = (event: React.KeyboardEvent): boolean =>\n event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229;\n\n/** The items a tree knows, and their elements while they're on the page. */\ninterface Registry {\n records: Map<string, TreeViewItemRecord>;\n // By value, for an item whose ref lets go, which hands over no element.\n elements: Map<string, HTMLLIElement>;\n // Weak, so an element that leaves the page needn't be forgotten by hand.\n values: WeakMap<Element, string>;\n}\n\n/** The registry's items on the page, in document order. */\nconst shownIn = (tree: Element, { values }: Registry): HTMLElement[] =>\n Array.from(tree.querySelectorAll<HTMLElement>('[role=\"treeitem\"]')).filter(\n element => values.has(element)\n );\n\n/** The item an item on the page sits in: its group's `<li>`, or none at the top. */\nfunction parentItemOf(element: Element): HTMLElement | null {\n const group = element.parentElement!;\n return group.getAttribute('role') === 'group' ? group.parentElement : null;\n}\n\n/** The values of the items an item on the page is in, from the top. */\nfunction pathOf(element: Element, { values }: Registry): string[] {\n const path: string[] = [];\n for (let at = parentItemOf(element); at; at = parentItemOf(at)) {\n path.unshift(values.get(at)!);\n }\n return path;\n}\n\n/**\n * Every known item's check state, by the rules in #1096:\n *\n * - a disabled item is checked only when its own value is listed;\n * - an enabled item with no enabled items anywhere under it is checked when\n * it, or any item above it, is listed;\n * - any other enabled item follows the items of that second kind in its\n * branch: checked when they all are, unchecked when none is, mixed between.\n *\n * Also returns the items of the second kind, which are what a toggle sets.\n */\nfunction readChecks(\n records: Map<string, TreeViewItemRecord>,\n listed: Set<string>\n): { states: Map<string, TreeViewCheckState>; ends: string[] } {\n const kids = new Map<string | null, string[]>();\n records.forEach(({ parent }, value) => {\n kids.set(parent, [...(kids.get(parent) ?? []), value]);\n });\n const states = new Map<string, TreeViewCheckState>();\n const ends: string[] = [];\n // How many enabled ends the branch holds, and how many are checked.\n const visit = (value: string, above: boolean): [number, number] => {\n const marked = above || listed.has(value);\n let total = 0;\n let on = 0;\n for (const kid of kids.get(value) ?? []) {\n const [t, o] = visit(kid, marked);\n total += t;\n on += o;\n }\n if (records.get(value)!.disabled) {\n states.set(value, listed.has(value));\n return [total, on];\n }\n if (total === 0) {\n states.set(value, marked);\n ends.push(value);\n return [1, marked ? 1 : 0];\n }\n states.set(value, on === total ? true : on > 0 && 'mixed');\n return [total, on];\n };\n for (const top of kids.get(null) ?? []) visit(top, false);\n return { states, ends };\n}\n\n/**\n * The list a toggle of `value` leaves: it and every enabled item under it\n * take the opposite of its state (a mixed one checks), and the rest is read\n * back by the rules. That's every item that shows checked, parents\n * included, plus any listed value no item has.\n */\nfunction toggledList(\n records: Map<string, TreeViewItemRecord>,\n list: string[],\n value: string\n): string[] {\n const { states, ends } = readChecks(records, new Set(list));\n const to = states.get(value) !== true;\n // An end was reached from the top, so everything above it is known.\n const under = (end: string) => {\n for (let at: string | null = end; at !== null;) {\n if (at === value) return true;\n at = records.get(at)!.parent;\n }\n return false;\n };\n const seed = new Set(\n ends.filter(end => (under(end) ? to : states.get(end) === true))\n );\n records.forEach(({ disabled }, item) => {\n if (disabled && states.get(item) === true) seed.add(item);\n });\n const after = readChecks(records, seed).states;\n return [\n ...Array.from(after)\n .filter(([, state]) => state === true)\n .map(([item]) => item),\n ...list.filter(item => !records.has(item)),\n ];\n}\n\n/** A value the caller holds, or one this hook holds when the caller doesn't. */\nfunction useControlled<T>(controlled: T | undefined, initial: T) {\n const [own, setOwn] = useState(initial);\n const isControlled = controlled !== undefined;\n const set = (next: T) => {\n if (!isControlled) setOwn(next);\n };\n return [isControlled ? controlled : own, set] as const;\n}\n\n/** Options for `useTreeView`: the TreeView's own props. */\nexport interface TreeViewOptions {\n mode: TreeViewSelectionMode;\n selected?: string | null;\n defaultSelected: string | null;\n onSelectedChange?: (value: string) => void;\n checked?: string[];\n defaultChecked: string[];\n onCheckedChange?: (checked: string[]) => void;\n expanded?: string[];\n defaultExpanded: string[];\n onExpandedChange?: (expanded: string[]) => void;\n}\n\n/** What `useTreeView` hands the root and its items. */\nexport interface TreeViewEngine {\n /** For the `<ul role=\"tree\">`. */\n treeRef: React.MutableRefObject<HTMLUListElement | null>;\n /**\n * Whether a closed item still renders its children, drawing nothing, so a\n * check knows everything under it. Only a checkbox tree needs to.\n */\n rendersClosed: boolean;\n /** The item that holds `tabIndex={0}`. */\n tabStop: string | null;\n isOpen: (value: string) => boolean;\n /**\n * What the item shows of the selection. `path` is the values of the items\n * above it, which decide its box until the items have registered, as on\n * the server.\n */\n stateOf: (\n value: string,\n path: string[],\n disabled: boolean\n ) => TreeViewItemState;\n /** Records an item from its layout effect. Returns the function that forgets it. */\n register: (value: string, record: TreeViewItemRecord) => () => void;\n /** For each item's `<li>` ref: records it, and notices focus leaving with it. */\n setElement: (value: string, element: HTMLLIElement | null) => void;\n /** Focus arrived on the item's own `<li>`. */\n onFocus: (value: string, element: HTMLLIElement) => void;\n /** Focus left the item's own `<li>`. */\n onBlur: () => void;\n /** A key on the item's `<li>`, after its own `onKeyDown`. */\n onKeyDown: (\n event: React.KeyboardEvent<HTMLLIElement>,\n item: TreeViewItemInfo\n ) => void;\n /**\n * A click on the item's row, or with `onToggle` on its chevron. Focus and\n * the tab stop move to the item.\n */\n onClick: (\n element: HTMLLIElement,\n item: TreeViewItemInfo,\n onToggle: boolean\n ) => void;\n}\n\n/**\n * The open state, the selection, the keys, the tab stop and the registry of\n * a TreeView (#1096). Focus moves for real, with a roving tabindex, rather\n * than through `aria-activedescendant`.\n *\n * Every item registers its value, its parent and whether it's disabled from\n * a layout effect. In a checkbox tree a closed item's children still render\n * and register while drawing nothing, so the registry knows every item and a\n * check reaches all of them. The keys read the items on the page, in\n * document order, each time they need them.\n *\n * @function useTreeView\n * @param options - The TreeView's props.\n * @returns The state and handlers the root and its items wire up.\n */\nexport function useTreeView(options: TreeViewOptions): TreeViewEngine {\n // An own key only, so a name every object inherits, like `toString`, is a\n // mode the tree doesn't know too.\n const mode = Object.prototype.hasOwnProperty.call(MODES, options.mode)\n ? options.mode\n : 'none';\n const actions = MODES[mode];\n\n const [expanded, setExpanded] = useControlled(\n options.expanded,\n options.defaultExpanded\n );\n const [selection, setSelection] = useControlled(\n options.selected,\n options.defaultSelected\n );\n const [checkedList, setChecked] = useControlled(\n options.checked,\n options.defaultChecked\n );\n // Each is read in its own mode only.\n const selected = mode === 'single' ? selection : null;\n const listed = new Set(mode === 'checkbox' ? checkedList : []);\n\n const treeRef = useRef<HTMLUListElement | null>(null);\n const registryRef = useRef<Registry>({\n records: new Map(),\n elements: new Map(),\n values: new WeakMap(),\n });\n // Items register from their own effects, so they ask for a render to be read.\n const [, refresh] = useReducer((n: number) => n + 1, 0);\n const typeahead = useTypeahead();\n\n // The item with focus, and where the stop rests while focus is elsewhere:\n // `undefined` until the tree has mounted. With the values above it, for\n // when it goes away.\n const [focused, setFocused] = useState<string | null>(null);\n const [resting, setResting] = useState<string | null | undefined>();\n const restingPathRef = useRef<string[]>([]);\n // The values above an item that left the page with focus in it.\n const lostRef = useRef<string[] | null>(null);\n\n const { records } = registryRef.current;\n const checks =\n mode === 'checkbox'\n ? // eslint-disable-next-line react-hooks/refs -- read in render on purpose: items write the registry from their layout effects and then ask for this render with `refresh`\n readChecks(records, listed).states\n : undefined;\n const tabStop = focused ?? (resting === undefined ? selected : resting);\n\n const isOpen = (value: string) => expanded.includes(value);\n\n const stateOf = (\n value: string,\n path: string[],\n disabled: boolean\n ): TreeViewItemState => {\n if (mode === 'single') {\n return disabled ? {} : { selected: value === selected };\n }\n if (checks) {\n // Until the items register, a value stands for its branch, and a\n // parent can't know it's mixed.\n const fromList =\n listed.has(value) || (!disabled && path.some(v => listed.has(v)));\n return { checked: checks.get(value) ?? fromList };\n }\n return {};\n };\n\n const changeOpen = (next: string[]) => {\n setExpanded(next);\n options.onExpandedChange?.(next);\n };\n\n const run: Record<Action, (item: TreeViewItemInfo) => void> = {\n open: ({ value, isParent, open }) => {\n if (isParent) {\n changeOpen(\n open ? expanded.filter(v => v !== value) : [...expanded, value]\n );\n }\n },\n select: ({ value }) => {\n if (value === selected) return;\n setSelection(value);\n options.onSelectedChange?.(value);\n },\n check: ({ value }) => {\n const next = toggledList(records, checkedList, value);\n setChecked(next);\n options.onCheckedChange?.(next);\n },\n };\n\n /** Disabled items are never selected, checked, opened or closed. */\n const act = (item: TreeViewItemInfo, list: Action[]) => {\n if (!item.disabled) list.forEach(action => run[action](item));\n };\n\n // After every render, put the stop where it belongs. While focus is in\n // the tree it's the focused item. Otherwise it's the selected item, or the\n // first checked one on the page, and failing that it stays where it was.\n // An item that goes away hands it to its nearest ancestor on the page, or\n // to the first item, and focus goes with it if it had focus.\n // eslint-disable-next-line react-hooks/exhaustive-deps -- after every render on purpose: it reads the page, and sets state only when what it reads has changed\n useClientLayoutEffect(() => {\n const tree = treeRef.current!;\n const registry = registryRef.current;\n // Each item's element is read off the page with its value, rather than\n // out of `elements`, which holds one element per value: two items\n // sharing a value, which they shouldn't, can leave it without one the\n // page still has.\n const shown = shownIn(tree, registry);\n const onPage = shown.map(element => registry.values.get(element)!);\n const elementOf = (value: string) => shown[onPage.indexOf(value)];\n const isShown = (value: string | null | undefined): value is string =>\n value != null && onPage.includes(value);\n const heir = (path: string[]) =>\n [...path].reverse().find(isShown) ?? onPage[0] ?? null;\n\n const lost = lostRef.current;\n lostRef.current = null;\n if (lost && !tree.contains(getActiveElementInTree(tree))) {\n const to = heir(lost);\n if (to) elementOf(to).focus();\n return;\n }\n if (focused !== null && !isShown(focused)) setFocused(null);\n\n const preferred = onPage.find(\n value => value === selected || checks?.get(value) === true\n );\n const next =\n preferred ?? (isShown(resting) ? resting : heir(restingPathRef.current));\n if (next !== resting) {\n setResting(next);\n restingPathRef.current = next ? pathOf(elementOf(next), registry) : [];\n }\n });\n\n const register = useCallback((value: string, record: TreeViewItemRecord) => {\n const { records: known } = registryRef.current;\n known.set(value, record);\n refresh();\n return () => {\n if (known.get(value) !== record) return;\n known.delete(value);\n refresh();\n };\n }, []);\n\n const setElement = useCallback(\n (value: string, element: HTMLLIElement | null) => {\n const registry = registryRef.current;\n if (element) {\n registry.elements.set(value, element);\n registry.values.set(element, value);\n return;\n }\n const gone = registry.elements.get(value);\n if (!gone) return;\n // The ref lets go before the element leaves the page, so it can still\n // say whether focus was in it, and where it sat.\n if (!lostRef.current && gone.contains(getActiveElementInTree(gone))) {\n lostRef.current = [...pathOf(gone, registry), value];\n }\n registry.elements.delete(value);\n },\n []\n );\n\n const onFocus = (value: string, element: HTMLLIElement) => {\n setFocused(value);\n setResting(value);\n restingPathRef.current = pathOf(element, registryRef.current);\n };\n\n const onBlur = () => setFocused(null);\n\n const onKeyDown = (\n event: React.KeyboardEvent<HTMLLIElement>,\n item: TreeViewItemInfo\n ) => {\n const element = event.currentTarget;\n // A key on an item inside this one bubbles here too, and is its own.\n if (\n event.target !== element ||\n event.defaultPrevented ||\n event.altKey ||\n event.ctrlKey ||\n event.metaKey ||\n isComposing(event)\n ) {\n return;\n }\n const registry = registryRef.current;\n const items = shownIn(treeRef.current!, registry);\n const at = items.indexOf(element);\n const rtl = getComputedStyle(element).direction === 'rtl';\n const key = (rtl && MIRRORED[event.key]) || event.key;\n\n switch (key) {\n case 'ArrowDown':\n items[at + 1]?.focus();\n break;\n case 'ArrowUp':\n items[at - 1]?.focus();\n break;\n case 'Home':\n items[0].focus();\n break;\n case 'End':\n items[items.length - 1].focus();\n break;\n case 'ArrowRight': {\n const next = items[at + 1];\n if (!item.open) act(item, ['open']);\n else if (next && parentItemOf(next) === element) next.focus();\n break;\n }\n case 'ArrowLeft':\n if (item.open && !item.disabled) act(item, ['open']);\n else parentItemOf(element)?.focus();\n break;\n // A held key acts once.\n case 'Enter':\n if (!event.repeat) act(item, actions.enter);\n break;\n case ' ':\n if (!event.repeat) act(item, actions.space);\n break;\n case '*': {\n const closed: string[] = [];\n registry.records.forEach((record, value) => {\n if (\n record.parent === item.parent &&\n record.isParent &&\n !record.disabled &&\n !expanded.includes(value)\n ) {\n closed.push(value);\n }\n });\n if (closed.length) changeOpen([...expanded, ...closed]);\n break;\n }\n default:\n // Type-ahead takes a printable character. Anything else, Escape and\n // Tab included, is left to whatever is around the tree.\n if (event.key.length !== 1) return;\n typeaheadMatch(items, typeahead.type(event.key), at, el =>\n typeaheadText(el.firstElementChild!)\n )?.focus();\n }\n event.preventDefault();\n };\n\n const onClick = (\n element: HTMLLIElement,\n item: TreeViewItemInfo,\n onToggle: boolean\n ) => {\n element.focus();\n act(item, onToggle ? ['open'] : actions.click);\n };\n\n return {\n treeRef,\n rendersClosed: mode === 'checkbox',\n tabStop,\n isOpen,\n stateOf,\n register,\n setElement,\n onFocus,\n onBlur,\n onKeyDown,\n onClick,\n };\n}\n"],"names":["isComposing"],"mappings":"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"}