@allxsmith/bestax-bulma 5.61.0 → 5.63.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 (100) 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 -6
  4. package/dist/esm/components/EmptyState.js.map +1 -1
  5. package/dist/esm/components/Timeline.js +5 -9
  6. package/dist/esm/components/Timeline.js.map +1 -1
  7. package/dist/esm/components/TreeView.js +16 -16
  8. package/dist/esm/components/TreeView.js.map +1 -1
  9. package/dist/esm/elements/Buttons.js +2 -0
  10. package/dist/esm/elements/Buttons.js.map +1 -1
  11. package/dist/esm/elements/CopyButton.js +132 -0
  12. package/dist/esm/elements/CopyButton.js.map +1 -0
  13. package/dist/esm/elements/DescriptionList.js +1 -5
  14. package/dist/esm/elements/DescriptionList.js.map +1 -1
  15. package/dist/esm/elements/Stat.js +196 -0
  16. package/dist/esm/elements/Stat.js.map +1 -0
  17. package/dist/esm/elements/Stats.js +48 -0
  18. package/dist/esm/elements/Stats.js.map +1 -0
  19. package/dist/esm/elements/statContext.js +10 -0
  20. package/dist/esm/elements/statContext.js.map +1 -0
  21. package/dist/esm/form/Combobox.js +10 -14
  22. package/dist/esm/form/Combobox.js.map +1 -1
  23. package/dist/esm/form/Field.js.map +1 -1
  24. package/dist/esm/form/TreeSelect.js +395 -0
  25. package/dist/esm/form/TreeSelect.js.map +1 -0
  26. package/dist/esm/form/_pickerInternals/PickerPopover.js +5 -1
  27. package/dist/esm/form/_pickerInternals/PickerPopover.js.map +1 -1
  28. package/dist/esm/form/_selectInternals/DialogSelect.js +208 -0
  29. package/dist/esm/form/_selectInternals/DialogSelect.js.map +1 -0
  30. package/dist/esm/form/_selectInternals/ValueTags.js +28 -0
  31. package/dist/esm/form/_selectInternals/ValueTags.js.map +1 -0
  32. package/dist/esm/helpers/checkIcon.js +20 -0
  33. package/dist/esm/helpers/checkIcon.js.map +1 -0
  34. package/dist/esm/helpers/isGiven.js +13 -0
  35. package/dist/esm/helpers/isGiven.js.map +1 -0
  36. package/dist/esm/helpers/statusRegion.js +3 -3
  37. package/dist/esm/helpers/treeData.js +97 -0
  38. package/dist/esm/helpers/treeData.js.map +1 -0
  39. package/dist/esm/helpers/useClipboard.js +176 -0
  40. package/dist/esm/helpers/useClipboard.js.map +1 -0
  41. package/dist/esm/helpers/useComboboxEngine.js +2 -2
  42. package/dist/esm/helpers/useComboboxEngine.js.map +1 -1
  43. package/dist/esm/helpers/useControlled.js +30 -0
  44. package/dist/esm/helpers/useControlled.js.map +1 -0
  45. package/dist/esm/helpers/useTreeView.js +15 -13
  46. package/dist/esm/helpers/useTreeView.js.map +1 -1
  47. package/dist/extras.css +1 -1
  48. package/dist/extras.css.map +1 -1
  49. package/dist/index.cjs +10605 -9380
  50. package/dist/index.cjs.map +1 -1
  51. package/dist/index.esm.js +5 -0
  52. package/dist/index.esm.js.map +1 -1
  53. package/dist/types/elements/Buttons.d.ts +1 -0
  54. package/dist/types/elements/CopyButton.d.ts +105 -0
  55. package/dist/types/elements/Stat.d.ts +242 -0
  56. package/dist/types/elements/Stats.d.ts +40 -0
  57. package/dist/types/elements/statContext.d.ts +18 -0
  58. package/dist/types/form/Field.d.ts +1 -1
  59. package/dist/types/form/TreeSelect.d.ts +308 -0
  60. package/dist/types/form/_pickerInternals/PickerPopover.d.ts +8 -0
  61. package/dist/types/form/_selectInternals/DialogSelect.d.ts +86 -0
  62. package/dist/types/form/_selectInternals/ValueTags.d.ts +40 -0
  63. package/dist/types/helpers/checkIcon.d.ts +10 -0
  64. package/dist/types/helpers/isGiven.d.ts +7 -0
  65. package/dist/types/helpers/treeData.d.ts +48 -0
  66. package/dist/types/helpers/useClipboard.d.ts +65 -0
  67. package/dist/types/helpers/useControlled.d.ts +14 -0
  68. package/dist/types/helpers/useTreeView.d.ts +30 -0
  69. package/dist/types/index.d.ts +6 -0
  70. package/dist/types-cjs/elements/Buttons.d.ts +1 -0
  71. package/dist/types-cjs/elements/CopyButton.d.ts +105 -0
  72. package/dist/types-cjs/elements/Stat.d.ts +242 -0
  73. package/dist/types-cjs/elements/Stats.d.ts +40 -0
  74. package/dist/types-cjs/elements/statContext.d.ts +18 -0
  75. package/dist/types-cjs/form/Field.d.ts +1 -1
  76. package/dist/types-cjs/form/TreeSelect.d.ts +308 -0
  77. package/dist/types-cjs/form/_pickerInternals/PickerPopover.d.ts +8 -0
  78. package/dist/types-cjs/form/_selectInternals/DialogSelect.d.ts +86 -0
  79. package/dist/types-cjs/form/_selectInternals/ValueTags.d.ts +40 -0
  80. package/dist/types-cjs/helpers/checkIcon.d.ts +10 -0
  81. package/dist/types-cjs/helpers/isGiven.d.ts +7 -0
  82. package/dist/types-cjs/helpers/treeData.d.ts +48 -0
  83. package/dist/types-cjs/helpers/useClipboard.d.ts +65 -0
  84. package/dist/types-cjs/helpers/useControlled.d.ts +14 -0
  85. package/dist/types-cjs/helpers/useTreeView.d.ts +30 -0
  86. package/dist/types-cjs/index.d.ts +6 -0
  87. package/dist/versions/bestax-no-dark-mode.css +1 -1
  88. package/dist/versions/bestax-no-dark-mode.css.map +1 -1
  89. package/dist/versions/bestax-no-helpers-prefixed.css +1 -1
  90. package/dist/versions/bestax-no-helpers-prefixed.css.map +1 -1
  91. package/dist/versions/bestax-no-helpers.css +1 -1
  92. package/dist/versions/bestax-no-helpers.css.map +1 -1
  93. package/dist/versions/bestax-prefixed.css +1 -1
  94. package/dist/versions/bestax-prefixed.css.map +1 -1
  95. package/package.json +1 -1
  96. package/src/scss/elements/_index.scss +1 -0
  97. package/src/scss/elements/_stat.scss +153 -0
  98. package/src/scss/form/_index.scss +1 -0
  99. package/src/scss/form/_picker-popover.scss +6 -0
  100. package/src/scss/form/_treeselect.scss +146 -0
@@ -0,0 +1,176 @@
1
+ /* @allxsmith/bestax-bulma — AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */
2
+ 'use client';
3
+ import { useState, useRef, useEffect, useCallback } from 'react';
4
+ import { useLatestRef } from './useLatestRef.js';
5
+
6
+ /** How long `copied` lasts when `duration` doesn't say, in ms. */
7
+ const defaultDuration = 2000;
8
+ /**
9
+ * The longest delay `setTimeout` takes, in ms. A longer one, `Infinity`
10
+ * included, runs at once.
11
+ */
12
+ const longestDelay = 2 ** 31 - 1;
13
+ /**
14
+ * `duration` as a delay `setTimeout` keeps, or `null` for no timer: `null`
15
+ * for `0`, as on Toast, the default when it's missing or `NaN` (or a
16
+ * JavaScript caller's `null`), `0` for a negative one, which ends `copied` at
17
+ * once, and the longest delay for `Infinity` or anything longer.
18
+ */
19
+ const delayOf = (duration) => {
20
+ if (duration === 0)
21
+ return null;
22
+ return duration == null || Number.isNaN(duration)
23
+ ? defaultDuration
24
+ : Math.min(Math.max(duration, 0), longestDelay);
25
+ };
26
+ /** The error a copy reports when nothing could write and nothing said why. */
27
+ const cannotCopy = 'The Clipboard API is missing or refused, and the copy command copied nothing.';
28
+ /**
29
+ * Copies `text` the way browsers did before the Clipboard API: it runs the
30
+ * `copy` command and puts the text on the `copy` event that command fires.
31
+ * Success is the event having fired, not the command's return value, which
32
+ * WebKit reports as `false` for a copy that happened.
33
+ *
34
+ * This is the library's only call to the deprecated `document.execCommand`,
35
+ * so it can go when browsers drop it. Until then it's what writes on a page
36
+ * without the Clipboard API, such as one served over plain `http://`.
37
+ *
38
+ * @param text - The text to copy.
39
+ * @returns Whether the `copy` event fired and took the text.
40
+ */
41
+ function copyWithCommand(text) {
42
+ let written = false;
43
+ // WebKit enables the copy command only when something is selected or a
44
+ // `beforecopy` listener cancels that event, and nothing is selected here.
45
+ const enable = (event) => event.preventDefault();
46
+ // On `window` while capturing, so it runs before the page's listeners on
47
+ // the document and its elements. The copy is this hook's own, so it's
48
+ // stopped there: a page's own copy handler, such as one that adds a
49
+ // credit line to whatever is copied, never sees it to rewrite it.
50
+ const onCopy = (event) => {
51
+ if (!event.clipboardData)
52
+ return;
53
+ event.clipboardData.setData('text/plain', text);
54
+ event.preventDefault();
55
+ event.stopImmediatePropagation();
56
+ written = true;
57
+ };
58
+ window.addEventListener('beforecopy', enable, true);
59
+ window.addEventListener('copy', onCopy, true);
60
+ try {
61
+ document.execCommand('copy');
62
+ }
63
+ catch {
64
+ // An engine that refuses, or no longer has the command, can throw, and
65
+ // the event not firing already says so.
66
+ }
67
+ finally {
68
+ window.removeEventListener('beforecopy', enable, true);
69
+ window.removeEventListener('copy', onCopy, true);
70
+ }
71
+ return written;
72
+ }
73
+ /**
74
+ * Writes `text` through the Clipboard API, and through the copy command when
75
+ * that's missing or refuses. Where it's missing, the copy command runs before
76
+ * the first `await`, so it's still inside the click that called it. After a
77
+ * refusal it runs once the refusal arrives, which writes only where the
78
+ * browser still counts the click by then, and only if no later copy has
79
+ * started in the meantime, which it would otherwise write over.
80
+ *
81
+ * @param text - The text to write.
82
+ * @param isLatest - Whether this is still the latest copy.
83
+ * @returns `null` once it's written, or why it couldn't be.
84
+ */
85
+ async function writeToClipboard(text, isLatest) {
86
+ let refusal = null;
87
+ // Read at the click, never while rendering, so there's always a navigator.
88
+ const { clipboard } = navigator;
89
+ if (clipboard?.writeText) {
90
+ try {
91
+ await clipboard.writeText(text);
92
+ return null;
93
+ }
94
+ catch (reason) {
95
+ refusal = reason;
96
+ }
97
+ }
98
+ if (isLatest() && copyWithCommand(text))
99
+ return null;
100
+ return refusal instanceof Error ? refusal : new Error(cannotCopy);
101
+ }
102
+ /**
103
+ * Copies text to the clipboard through the Clipboard API, falling back to the copy command where that's missing or refused, and keeps a `copied` flag for `duration` after a copy.
104
+ *
105
+ * It announces nothing. A trigger of your own says "Copied" in a
106
+ * `role="status"` region that's in the page before the click; `CopyButton`
107
+ * is this hook with the button, the label and the announcement built in.
108
+ *
109
+ * A failed copy sets `error`, calls `onCopyError`, and ends `copied` if a
110
+ * copy before it had set it. When copies overlap, only the latest one's
111
+ * outcome sets the state and fires the callbacks, whichever finishes first.
112
+ * Nothing changes and no callback fires once the component using it has
113
+ * unmounted, though a write already under way still finishes.
114
+ *
115
+ * @function useClipboard
116
+ * @param options - How long `copied` lasts, and the callbacks.
117
+ * @returns `copy`, which writes, and the `copied` and `error` state.
118
+ *
119
+ * @example
120
+ * const { copy, copied } = useClipboard();
121
+ * <Button onClick={() => copy(url)}>{copied ? 'Copied' : 'Copy link'}</Button>
122
+ */
123
+ function useClipboard(options = {}) {
124
+ const [copied, setCopied] = useState(false);
125
+ const [error, setError] = useState(null);
126
+ const optionsRef = useLatestRef(options);
127
+ const copiedRef = useRef(false);
128
+ const timerRef = useRef(undefined);
129
+ const mountedRef = useRef(false);
130
+ // Counts the calls to `copy`, so only the latest one's outcome is kept.
131
+ const latestCallRef = useRef(0);
132
+ useEffect(() => {
133
+ mountedRef.current = true;
134
+ return () => {
135
+ mountedRef.current = false;
136
+ clearTimeout(timerRef.current);
137
+ };
138
+ }, []);
139
+ const copy = useCallback(async (text) => {
140
+ latestCallRef.current += 1;
141
+ const call = latestCallRef.current;
142
+ const isLatest = () => call === latestCallRef.current;
143
+ const failure = await writeToClipboard(text, isLatest);
144
+ // A write overtaken by a later copy, which may finish first, reports
145
+ // through its promise only.
146
+ if (!mountedRef.current || !isLatest()) {
147
+ return failure === null;
148
+ }
149
+ clearTimeout(timerRef.current);
150
+ const { duration, onCopiedChange, onCopyError } = optionsRef.current;
151
+ const wasCopied = copiedRef.current;
152
+ copiedRef.current = failure === null;
153
+ setCopied(failure === null);
154
+ setError(failure);
155
+ if (failure) {
156
+ if (wasCopied)
157
+ onCopiedChange?.(false);
158
+ onCopyError?.(failure);
159
+ return false;
160
+ }
161
+ onCopiedChange?.(true);
162
+ const delay = delayOf(duration);
163
+ if (delay !== null) {
164
+ timerRef.current = setTimeout(() => {
165
+ copiedRef.current = false;
166
+ setCopied(false);
167
+ optionsRef.current.onCopiedChange?.(false);
168
+ }, delay);
169
+ }
170
+ return true;
171
+ }, [optionsRef]);
172
+ return { copy, copied, error };
173
+ }
174
+
175
+ export { useClipboard };
176
+ //# sourceMappingURL=useClipboard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useClipboard.js","sources":["../../../src/helpers/useClipboard.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\nimport { useLatestRef } from './useLatestRef';\n\n/** Options for useClipboard. */\nexport interface UseClipboardOptions {\n /**\n * How long `copied` stays `true` after a copy, in milliseconds. Another\n * copy restarts it. `0` turns the timer off, as Toast's `0` does, so\n * `copied` stays `true` until a later copy fails. A missing or `NaN` value\n * uses the default, a negative one ends it at once, and `Infinity`, or\n * anything longer than the longest delay `setTimeout` takes (2147483647 ms,\n * about 24.8 days), is that longest delay.\n * @defaultValue 2000\n */\n duration?: number;\n /**\n * Called with `true` after each copy that worked, a repeat included, and\n * with `false` when `duration` runs out or a later copy fails.\n */\n onCopiedChange?: (copied: boolean) => void;\n /**\n * Called when neither the Clipboard API nor the copy command could write,\n * with the Clipboard API's own error when it refused, or an `Error` saying\n * neither could.\n */\n onCopyError?: (error: Error) => void;\n}\n\n/** What useClipboard returns. */\nexport interface UseClipboardResult {\n /**\n * Writes `text` to the clipboard, and resolves `true` once it's written or\n * `false` when it couldn't be. It never rejects. Call it from a click or a\n * key press, with no `await` before it: browsers only allow a write\n * shortly after one. A call that a later one overtakes leaves `copied`,\n * `error` and the callbacks to the later one, and doesn't fall back to the\n * copy command once the later one has started.\n */\n copy: (text: string) => Promise<boolean>;\n /**\n * `true` for `duration` after a copy that worked, or, with a `duration` of\n * `0`, until a later copy fails.\n */\n copied: boolean;\n /** Why the last copy failed, or `null` after one that worked. */\n error: Error | null;\n}\n\n/** How long `copied` lasts when `duration` doesn't say, in ms. */\nconst defaultDuration = 2000;\n\n/**\n * The longest delay `setTimeout` takes, in ms. A longer one, `Infinity`\n * included, runs at once.\n */\nconst longestDelay = 2 ** 31 - 1;\n\n/**\n * `duration` as a delay `setTimeout` keeps, or `null` for no timer: `null`\n * for `0`, as on Toast, the default when it's missing or `NaN` (or a\n * JavaScript caller's `null`), `0` for a negative one, which ends `copied` at\n * once, and the longest delay for `Infinity` or anything longer.\n */\nconst delayOf = (duration: number | null | undefined) => {\n if (duration === 0) return null;\n return duration == null || Number.isNaN(duration)\n ? defaultDuration\n : Math.min(Math.max(duration, 0), longestDelay);\n};\n\n/** The error a copy reports when nothing could write and nothing said why. */\nconst cannotCopy =\n 'The Clipboard API is missing or refused, and the copy command copied nothing.';\n\n/**\n * Copies `text` the way browsers did before the Clipboard API: it runs the\n * `copy` command and puts the text on the `copy` event that command fires.\n * Success is the event having fired, not the command's return value, which\n * WebKit reports as `false` for a copy that happened.\n *\n * This is the library's only call to the deprecated `document.execCommand`,\n * so it can go when browsers drop it. Until then it's what writes on a page\n * without the Clipboard API, such as one served over plain `http://`.\n *\n * @param text - The text to copy.\n * @returns Whether the `copy` event fired and took the text.\n */\nfunction copyWithCommand(text: string): boolean {\n let written = false;\n // WebKit enables the copy command only when something is selected or a\n // `beforecopy` listener cancels that event, and nothing is selected here.\n const enable = (event: Event) => event.preventDefault();\n // On `window` while capturing, so it runs before the page's listeners on\n // the document and its elements. The copy is this hook's own, so it's\n // stopped there: a page's own copy handler, such as one that adds a\n // credit line to whatever is copied, never sees it to rewrite it.\n const onCopy = (event: ClipboardEvent) => {\n if (!event.clipboardData) return;\n event.clipboardData.setData('text/plain', text);\n event.preventDefault();\n event.stopImmediatePropagation();\n written = true;\n };\n window.addEventListener('beforecopy', enable, true);\n window.addEventListener('copy', onCopy, true);\n try {\n document.execCommand('copy');\n } catch {\n // An engine that refuses, or no longer has the command, can throw, and\n // the event not firing already says so.\n } finally {\n window.removeEventListener('beforecopy', enable, true);\n window.removeEventListener('copy', onCopy, true);\n }\n return written;\n}\n\n/**\n * Writes `text` through the Clipboard API, and through the copy command when\n * that's missing or refuses. Where it's missing, the copy command runs before\n * the first `await`, so it's still inside the click that called it. After a\n * refusal it runs once the refusal arrives, which writes only where the\n * browser still counts the click by then, and only if no later copy has\n * started in the meantime, which it would otherwise write over.\n *\n * @param text - The text to write.\n * @param isLatest - Whether this is still the latest copy.\n * @returns `null` once it's written, or why it couldn't be.\n */\nasync function writeToClipboard(\n text: string,\n isLatest: () => boolean\n): Promise<Error | null> {\n let refusal: unknown = null;\n // Read at the click, never while rendering, so there's always a navigator.\n const { clipboard } = navigator;\n if (clipboard?.writeText) {\n try {\n await clipboard.writeText(text);\n return null;\n } catch (reason) {\n refusal = reason;\n }\n }\n if (isLatest() && copyWithCommand(text)) return null;\n return refusal instanceof Error ? refusal : new Error(cannotCopy);\n}\n\n/**\n * Copies text to the clipboard through the Clipboard API, falling back to the copy command where that's missing or refused, and keeps a `copied` flag for `duration` after a copy.\n *\n * It announces nothing. A trigger of your own says \"Copied\" in a\n * `role=\"status\"` region that's in the page before the click; `CopyButton`\n * is this hook with the button, the label and the announcement built in.\n *\n * A failed copy sets `error`, calls `onCopyError`, and ends `copied` if a\n * copy before it had set it. When copies overlap, only the latest one's\n * outcome sets the state and fires the callbacks, whichever finishes first.\n * Nothing changes and no callback fires once the component using it has\n * unmounted, though a write already under way still finishes.\n *\n * @function useClipboard\n * @param options - How long `copied` lasts, and the callbacks.\n * @returns `copy`, which writes, and the `copied` and `error` state.\n *\n * @example\n * const { copy, copied } = useClipboard();\n * <Button onClick={() => copy(url)}>{copied ? 'Copied' : 'Copy link'}</Button>\n */\nexport function useClipboard(\n options: UseClipboardOptions = {}\n): UseClipboardResult {\n const [copied, setCopied] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const optionsRef = useLatestRef(options);\n const copiedRef = useRef(false);\n const timerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n const mountedRef = useRef(false);\n // Counts the calls to `copy`, so only the latest one's outcome is kept.\n const latestCallRef = useRef(0);\n\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n clearTimeout(timerRef.current);\n };\n }, []);\n\n const copy = useCallback(\n async (text: string) => {\n latestCallRef.current += 1;\n const call = latestCallRef.current;\n const isLatest = () => call === latestCallRef.current;\n const failure = await writeToClipboard(text, isLatest);\n // A write overtaken by a later copy, which may finish first, reports\n // through its promise only.\n if (!mountedRef.current || !isLatest()) {\n return failure === null;\n }\n clearTimeout(timerRef.current);\n const { duration, onCopiedChange, onCopyError } = optionsRef.current;\n const wasCopied = copiedRef.current;\n copiedRef.current = failure === null;\n setCopied(failure === null);\n setError(failure);\n if (failure) {\n if (wasCopied) onCopiedChange?.(false);\n onCopyError?.(failure);\n return false;\n }\n onCopiedChange?.(true);\n const delay = delayOf(duration);\n if (delay !== null) {\n timerRef.current = setTimeout(() => {\n copiedRef.current = false;\n setCopied(false);\n optionsRef.current.onCopiedChange?.(false);\n }, delay);\n }\n return true;\n },\n [optionsRef]\n );\n\n return { copy, copied, error };\n}\n"],"names":[],"mappings":";;;;;AAgDA;AACA,MAAM,eAAe,GAAG,IAAI;AAE5B;;;AAGG;AACH,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC;AAEhC;;;;;AAKG;AACH,MAAM,OAAO,GAAG,CAAC,QAAmC,KAAI;IACtD,IAAI,QAAQ,KAAK,CAAC;AAAE,QAAA,OAAO,IAAI;IAC/B,OAAO,QAAQ,IAAI,IAAI,IAAI,MAAM,CAAC,KAAK,CAAC,QAAQ;AAC9C,UAAE;AACF,UAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,YAAY,CAAC;AACnD,CAAC;AAED;AACA,MAAM,UAAU,GACd,+EAA+E;AAEjF;;;;;;;;;;;;AAYG;AACH,SAAS,eAAe,CAAC,IAAY,EAAA;IACnC,IAAI,OAAO,GAAG,KAAK;;;IAGnB,MAAM,MAAM,GAAG,CAAC,KAAY,KAAK,KAAK,CAAC,cAAc,EAAE;;;;;AAKvD,IAAA,MAAM,MAAM,GAAG,CAAC,KAAqB,KAAI;QACvC,IAAI,CAAC,KAAK,CAAC,aAAa;YAAE;QAC1B,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,YAAY,EAAE,IAAI,CAAC;QAC/C,KAAK,CAAC,cAAc,EAAE;QACtB,KAAK,CAAC,wBAAwB,EAAE;QAChC,OAAO,GAAG,IAAI;AAChB,IAAA,CAAC;IACD,MAAM,CAAC,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,CAAC;IACnD,MAAM,CAAC,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC;AAC7C,IAAA,IAAI;AACF,QAAA,QAAQ,CAAC,WAAW,CAAC,MAAM,CAAC;IAC9B;AAAE,IAAA,MAAM;;;IAGR;YAAU;QACR,MAAM,CAAC,mBAAmB,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,CAAC;QACtD,MAAM,CAAC,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC;IAClD;AACA,IAAA,OAAO,OAAO;AAChB;AAEA;;;;;;;;;;;AAWG;AACH,eAAe,gBAAgB,CAC7B,IAAY,EACZ,QAAuB,EAAA;IAEvB,IAAI,OAAO,GAAY,IAAI;;AAE3B,IAAA,MAAM,EAAE,SAAS,EAAE,GAAG,SAAS;AAC/B,IAAA,IAAI,SAAS,EAAE,SAAS,EAAE;AACxB,QAAA,IAAI;AACF,YAAA,MAAM,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC;AAC/B,YAAA,OAAO,IAAI;QACb;QAAE,OAAO,MAAM,EAAE;YACf,OAAO,GAAG,MAAM;QAClB;IACF;AACA,IAAA,IAAI,QAAQ,EAAE,IAAI,eAAe,CAAC,IAAI,CAAC;AAAE,QAAA,OAAO,IAAI;AACpD,IAAA,OAAO,OAAO,YAAY,KAAK,GAAG,OAAO,GAAG,IAAI,KAAK,CAAC,UAAU,CAAC;AACnE;AAEA;;;;;;;;;;;;;;;;;;;;AAoBG;AACG,SAAU,YAAY,CAC1B,OAAA,GAA+B,EAAE,EAAA;IAEjC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC;IAC3C,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAe,IAAI,CAAC;AACtD,IAAA,MAAM,UAAU,GAAG,YAAY,CAAC,OAAO,CAAC;AACxC,IAAA,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC;AAC/B,IAAA,MAAM,QAAQ,GAAG,MAAM,CAA4C,SAAS,CAAC;AAC7E,IAAA,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC;;AAEhC,IAAA,MAAM,aAAa,GAAG,MAAM,CAAC,CAAC,CAAC;IAE/B,SAAS,CAAC,MAAK;AACb,QAAA,UAAU,CAAC,OAAO,GAAG,IAAI;AACzB,QAAA,OAAO,MAAK;AACV,YAAA,UAAU,CAAC,OAAO,GAAG,KAAK;AAC1B,YAAA,YAAY,CAAC,QAAQ,CAAC,OAAO,CAAC;AAChC,QAAA,CAAC;IACH,CAAC,EAAE,EAAE,CAAC;IAEN,MAAM,IAAI,GAAG,WAAW,CACtB,OAAO,IAAY,KAAI;AACrB,QAAA,aAAa,CAAC,OAAO,IAAI,CAAC;AAC1B,QAAA,MAAM,IAAI,GAAG,aAAa,CAAC,OAAO;QAClC,MAAM,QAAQ,GAAG,MAAM,IAAI,KAAK,aAAa,CAAC,OAAO;QACrD,MAAM,OAAO,GAAG,MAAM,gBAAgB,CAAC,IAAI,EAAE,QAAQ,CAAC;;;QAGtD,IAAI,CAAC,UAAU,CAAC,OAAO,IAAI,CAAC,QAAQ,EAAE,EAAE;YACtC,OAAO,OAAO,KAAK,IAAI;QACzB;AACA,QAAA,YAAY,CAAC,QAAQ,CAAC,OAAO,CAAC;QAC9B,MAAM,EAAE,QAAQ,EAAE,cAAc,EAAE,WAAW,EAAE,GAAG,UAAU,CAAC,OAAO;AACpE,QAAA,MAAM,SAAS,GAAG,SAAS,CAAC,OAAO;AACnC,QAAA,SAAS,CAAC,OAAO,GAAG,OAAO,KAAK,IAAI;AACpC,QAAA,SAAS,CAAC,OAAO,KAAK,IAAI,CAAC;QAC3B,QAAQ,CAAC,OAAO,CAAC;QACjB,IAAI,OAAO,EAAE;AACX,YAAA,IAAI,SAAS;AAAE,gBAAA,cAAc,GAAG,KAAK,CAAC;AACtC,YAAA,WAAW,GAAG,OAAO,CAAC;AACtB,YAAA,OAAO,KAAK;QACd;AACA,QAAA,cAAc,GAAG,IAAI,CAAC;AACtB,QAAA,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ,CAAC;AAC/B,QAAA,IAAI,KAAK,KAAK,IAAI,EAAE;AAClB,YAAA,QAAQ,CAAC,OAAO,GAAG,UAAU,CAAC,MAAK;AACjC,gBAAA,SAAS,CAAC,OAAO,GAAG,KAAK;gBACzB,SAAS,CAAC,KAAK,CAAC;gBAChB,UAAU,CAAC,OAAO,CAAC,cAAc,GAAG,KAAK,CAAC;YAC5C,CAAC,EAAE,KAAK,CAAC;QACX;AACA,QAAA,OAAO,IAAI;AACb,IAAA,CAAC,EACD,CAAC,UAAU,CAAC,CACb;AAED,IAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE;AAChC;;"}
@@ -7,7 +7,7 @@ import { getActiveElementInTree, isEventInside } from './shadowDom.js';
7
7
  // INTERNAL: deliberately not exported from src/index.ts.
8
8
  /** An IME is composing, so the key belongs to it (`keyCode` 229 is Safari's word for it). */
9
9
  const isComposing$2 = (event) => event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229;
10
- const sameValues = (a, b) => a.length === b.length && a.every((value, i) => value === b[i]);
10
+ const sameValues$1 = (a, b) => a.length === b.length && a.every((value, i) => value === b[i]);
11
11
  /**
12
12
  * The keys, the open state, the highlight and the ARIA of an editable
13
13
  * combobox whose list the app renders and filters (#1083). DOM focus stays
@@ -109,7 +109,7 @@ function useComboboxEngine({ open: controlledOpen, defaultOpen, onOpenChange, on
109
109
  }
110
110
  const options = shownOptions();
111
111
  const values = options.map(option => option.value);
112
- const changed = !sameValues(values, shownValuesRef.current);
112
+ const changed = !sameValues$1(values, shownValuesRef.current);
113
113
  shownValuesRef.current = values;
114
114
  setListShown(values.length > 0);
115
115
  const landing = landingRef.current ?? (opened ? 'open' : null);
@@ -1 +1 @@
1
- {"version":3,"file":"useComboboxEngine.js","sources":["../../../src/helpers/useComboboxEngine.ts"],"sourcesContent":["// INTERNAL: deliberately not exported from src/index.ts.\nimport {\n useCallback,\n useEffect,\n useId,\n useReducer,\n useRef,\n useState,\n} from 'react';\nimport type React from 'react';\nimport { useClientLayoutEffect } from './useClientLayoutEffect';\nimport { getActiveElementInTree, isEventInside } from './shadowDom';\n\n/** What an item tells the engine about itself. */\nexport interface ComboboxItemState {\n /** What a pick hands back. Unique within the combobox. */\n value: string;\n /** Reachable with the arrows, never picked. */\n disabled: boolean;\n /** The value, or one of them. */\n checked: boolean;\n}\n\n/** An option on the page, with its element. */\ninterface ShownOption extends ComboboxItemState {\n element: HTMLElement;\n}\n\n/**\n * Where the highlight lands once an opened list has rendered, or once typing\n * has changed what's in it:\n *\n * - `first` and `last` (Down and Up Arrow): the checked option, otherwise the\n * first or last enabled one;\n * - `open` (a click, or any other way of opening): the checked option,\n * otherwise the first enabled one with `keepFirst`, otherwise nothing;\n * - `typed`: the first enabled option with `keepFirst`, otherwise nothing;\n * - `none` (Alt+Down Arrow): nothing.\n *\n * Only an enabled option takes it, so a disabled checked one is passed over.\n */\ntype Landing = 'first' | 'last' | 'open' | 'typed' | 'none';\n\n/** Options for `useComboboxEngine`. */\nexport interface ComboboxEngineOptions {\n /** Controlled open state. When set, opening and closing only report through `onOpenChange`. */\n open?: boolean;\n /** Initial open state when uncontrolled. */\n defaultOpen: boolean;\n /** Called with the open state asked for, when it differs from the current one. */\n onOpenChange?: (open: boolean) => void;\n /** Called with an item's `value` when it's picked. */\n onSelect?: (value: string) => void;\n /**\n * Called when Enter goes on to the form with nothing to pick, before the\n * list closes, so an owner that shows something other than the typing can\n * put it back before the form reads the field. An `inline` list doesn't\n * close, so this is the one word it gets.\n */\n onSettle?: () => void;\n /** Highlights the first enabled item whenever the list opens or its items change. */\n keepFirst: boolean;\n /** Several values: a pick keeps the list open. */\n multiple: boolean;\n /** The list is always on screen, so nothing opens or closes it. */\n inline: boolean;\n /**\n * The list's panel is portaled, so it has no server markup and shows only\n * from the commit after hydration. Until it has rendered, an open list\n * isn't taken to be on the page, so no `aria-controls` names it early.\n */\n deferred: boolean;\n}\n\n/** What `useComboboxEngine` hands the parts of a combobox. */\nexport interface ComboboxEngine {\n /** Whether the list is open. Always, with `inline`. */\n open: boolean;\n /**\n * The field's `aria-expanded`: open, with a listbox that has an option in\n * it on the page. Until the open list has rendered, it's taken to have\n * one, so server markup for an open list in place says it's expanded. A\n * `deferred` list isn't in server markup, so it's taken to have none.\n */\n expanded: boolean;\n /** The highlighted option's value, if any. */\n highlighted: string | null;\n /** Whether the user has typed since the list opened. */\n typed: boolean;\n /** The listbox's id. */\n listboxId: string;\n /** An item's id, which follows from its value. */\n itemId: (value: string) => string;\n /** For the element around the field and, unless it's portaled, the list. */\n rootRef: React.MutableRefObject<HTMLDivElement | null>;\n /** For the field. */\n inputRef: React.MutableRefObject<HTMLInputElement | null>;\n /** For the listbox. */\n listboxRef: React.MutableRefObject<HTMLDivElement | null>;\n /**\n * Records the floating panel while it's on screen, which makes it count as\n * inside, and makes an Escape that closes it stop there. Returns the\n * function that forgets it.\n */\n registerPopup: (element: HTMLElement) => () => void;\n /** Records an item's element. Returns the function that forgets it. */\n registerItem: (element: HTMLElement, state: ComboboxItemState) => () => void;\n /** The field's keys, to run after the caller's own handler. */\n onKeyDown: (event: React.KeyboardEvent<HTMLInputElement>) => void;\n /** A change to the field's value: the user typed. */\n onChange: () => void;\n /** A click in the field. */\n onClick: () => void;\n /** Focus leaving the field. */\n onBlur: (event: React.FocusEvent<HTMLInputElement>) => void;\n /** The pointer moved over an enabled option. */\n hover: (value: string) => void;\n /** A click on an enabled option. Focus ends up in the field. */\n pick: (value: string) => void;\n /** For anything in the list: a press there doesn't take focus from the field. */\n keepFocus: (event: React.MouseEvent) => void;\n /**\n * For the floating panel: marks a press as inside, which React reports from\n * content the panel portals elsewhere as well.\n */\n markInside: (event: React.PointerEvent) => void;\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\nconst sameValues = (a: string[], b: string[]): boolean =>\n a.length === b.length && a.every((value, i) => value === b[i]);\n\n/**\n * The keys, the open state, the highlight and the ARIA of an editable\n * combobox whose list the app renders and filters (#1083). DOM focus stays\n * in the field the whole time: the highlight is `aria-activedescendant`, as\n * the ARIA Authoring Practices ask of a combobox, where `useMenuKeys` moves\n * real focus, as a menu should.\n *\n * The options are read from the listbox in document order whenever they're\n * needed, so options in groups and options that come and go are found, and\n * the highlight is remembered by value, so it survives a re-render. The text\n * in the field and any value are the app's: a pick only calls\n * `onSelect`.\n *\n * @function useComboboxEngine\n * @param options - The open state, the pick callback and the shape.\n * @returns The state and handlers the parts wire up.\n */\nexport function useComboboxEngine({\n open: controlledOpen,\n defaultOpen,\n onOpenChange,\n onSelect,\n onSettle,\n keepFirst,\n multiple,\n inline,\n deferred,\n}: ComboboxEngineOptions): ComboboxEngine {\n const baseId = useId();\n const listboxId = `${baseId}-listbox`;\n const itemPrefix = `${baseId}-item-`;\n // The value goes into the id encoded, so a value with a space in it still\n // makes one id, not two.\n const itemId = useCallback(\n (value: string) => itemPrefix + encodeURIComponent(value),\n [itemPrefix]\n );\n\n const rootRef = useRef<HTMLDivElement | null>(null);\n const inputRef = useRef<HTMLInputElement | null>(null);\n const listboxRef = useRef<HTMLDivElement | null>(null);\n const popupRef = useRef<HTMLElement | null>(null);\n const insidePressRef = useRef<Event | null>(null);\n const optionsRef = useRef(new Map<HTMLElement, ComboboxItemState>());\n const landingRef = useRef<Landing | null>(null);\n const wasOpenRef = useRef(false);\n const shownValuesRef = useRef<string[]>([]);\n // Whether the next highlight change is one to scroll to: a key's or a\n // landing's. The pointer's isn't, since the pointer is already on that\n // option, and scrolling would move the list out from under it.\n const scrollToHighlightRef = useRef(false);\n\n const isControlled = controlledOpen !== undefined;\n const [internalOpen, setInternalOpen] = useState(defaultOpen);\n const open = inline || (controlledOpen ?? internalOpen);\n\n const [highlighted, setHighlighted] = useState<string | null>(null);\n const [typed, setTyped] = useState(false);\n // Whether a listbox with an option in it is on the page, which only the\n // page can say, so it's read once the open list has rendered.\n const [listShown, setListShown] = useState<boolean | null>(null);\n // Options register from their own effects, which needn't render the\n // component that holds this hook, so they ask for a render to be read.\n const [, refresh] = useReducer((n: number) => n + 1, 0);\n\n // Closing forgets the highlight, the typing and what was on the page,\n // during render, React's pattern for adjusting state when other state\n // changes.\n if (!open) {\n if (highlighted !== null) setHighlighted(null);\n if (typed) setTyped(false);\n if (listShown !== null) setListShown(null);\n }\n const expanded = open && (listShown ?? !deferred);\n\n /** The options on the page, in document order, groups included. */\n const shownOptions = useCallback((): ShownOption[] => {\n const listbox = listboxRef.current;\n if (!listbox) return [];\n const shown: ShownOption[] = [];\n listbox\n .querySelectorAll<HTMLElement>('[role=\"option\"]')\n .forEach(element => {\n // Only the Combobox's own options: an element given the role by\n // hand isn't one.\n const state = optionsRef.current.get(element);\n if (state) shown.push({ element, ...state });\n });\n return shown;\n }, []);\n\n const requestOpen = useCallback(\n (next: boolean, landing: Landing = 'open') => {\n if (inline || next === open) return;\n landingRef.current = next ? landing : null;\n onOpenChange?.(next);\n if (!isControlled) setInternalOpen(next);\n },\n [inline, open, onOpenChange, isControlled]\n );\n const close = useCallback(() => requestOpen(false), [requestOpen]);\n\n // After every render, read the list: whether it has options, and where\n // the highlight goes now. A highlighted option that has left takes the\n // highlight with it, or hands it to the first enabled one under\n // `keepFirst`, which also moves it there whenever the options change.\n // eslint-disable-next-line react-hooks/exhaustive-deps -- after every render on purpose\n useClientLayoutEffect(() => {\n const opened = open && !wasOpenRef.current;\n wasOpenRef.current = open;\n if (!open) {\n landingRef.current = null;\n shownValuesRef.current = [];\n return;\n }\n const options = shownOptions();\n const values = options.map(option => option.value);\n const changed = !sameValues(values, shownValuesRef.current);\n shownValuesRef.current = values;\n setListShown(values.length > 0);\n\n const landing = landingRef.current ?? (opened ? 'open' : null);\n landingRef.current = null;\n const enabled = options.filter(option => !option.disabled);\n const checked = enabled.find(option => option.checked)?.value;\n const first = enabled.length > 0 ? enabled[0].value : null;\n const last = enabled.length > 0 ? enabled[enabled.length - 1].value : null;\n let next = highlighted;\n if (landing === 'first') next = checked ?? first;\n else if (landing === 'last') next = checked ?? last;\n else if (landing === 'open') next = checked ?? (keepFirst ? first : null);\n else if (landing === 'typed') next = keepFirst ? first : null;\n else if (landing === 'none') next = null;\n else if (keepFirst && changed) next = first;\n // Without `keepFirst`, which moved it above, a highlighted option\n // that left takes the highlight with it.\n else if (next !== null && !values.includes(next)) next = null;\n // Only a change is set. StrictMode runs this twice on mount, and the\n // second run, with the landing used up, works out the rendered highlight\n // again, so it leaves the first run's landing in place.\n if (next !== highlighted) {\n scrollToHighlightRef.current = true;\n setHighlighted(next);\n }\n });\n\n // A browser scrolls to what has focus, not to an active descendant, so\n // the highlighted option is brought into view here, when a key or a\n // landing moved it there.\n useClientLayoutEffect(() => {\n if (highlighted === null || !scrollToHighlightRef.current) return;\n scrollToHighlightRef.current = false;\n const option = shownOptions().find(shown => shown.value === highlighted);\n option?.element.scrollIntoView?.({ block: 'nearest' });\n }, [highlighted, shownOptions]);\n\n // A press outside the field and the list closes it. The path is checked\n // rather than the target, so it works in a shadow root.\n useEffect(() => {\n const root = rootRef.current;\n if (!open || inline || !root) return undefined;\n const onPress = (event: PointerEvent) => {\n if (\n event === insidePressRef.current ||\n isEventInside(event, rootRef.current) ||\n isEventInside(event, popupRef.current)\n ) {\n return;\n }\n close();\n };\n // Its document, an iframe's when the tree renders into one.\n const doc = root.ownerDocument;\n doc.addEventListener('pointerdown', onPress);\n return () => doc.removeEventListener('pointerdown', onPress);\n }, [open, inline, close]);\n\n const submit = useCallback(\n (value: string) => {\n onSelect?.(value);\n if (!multiple) close();\n },\n [onSelect, multiple, close]\n );\n\n const onKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {\n // The caller's handler ran first and can keep a key, and an IME that's\n // composing owns every key.\n if (event.defaultPrevented || isComposing(event)) return;\n const { key, altKey } = event;\n\n if (!open) {\n if (key === 'ArrowDown' || (key === 'ArrowUp' && !altKey)) {\n event.preventDefault();\n requestOpen(\n true,\n altKey ? 'none' : key === 'ArrowDown' ? 'first' : 'last'\n );\n }\n return;\n }\n\n switch (key) {\n case 'ArrowDown':\n case 'ArrowUp': {\n if (altKey) {\n if (key === 'ArrowUp' && !inline) {\n event.preventDefault();\n close();\n }\n return;\n }\n event.preventDefault();\n // Disabled options too, so a screen reader user finds them.\n const options = shownOptions();\n if (options.length === 0) return;\n const at = options.findIndex(option => option.value === highlighted);\n const step = key === 'ArrowDown' ? 1 : -1;\n const index =\n at === -1\n ? step === 1\n ? 0\n : options.length - 1\n : (at + step + options.length) % options.length;\n scrollToHighlightRef.current = true;\n setHighlighted(options[index].value);\n return;\n }\n case 'Enter': {\n const option = shownOptions().find(\n shown => shown.value === highlighted\n );\n if (option) {\n event.preventDefault();\n if (!option.disabled) submit(option.value);\n return;\n }\n // Nothing to pick: the key goes on, so a form submits.\n onSettle?.();\n close();\n return;\n }\n case 'Escape':\n if (inline) return;\n // It stops here only when there was something to close, so a\n // Popover or Modal around it keeps its own Escape otherwise. Nothing\n // stops it going on: a handler further out reads `defaultPrevented`.\n if (popupRef.current) event.preventDefault();\n close();\n return;\n case 'Tab':\n close();\n return;\n case 'Home':\n case 'End':\n case 'ArrowLeft':\n case 'ArrowRight':\n // The field's own keys, which take the field back from the list.\n setHighlighted(null);\n return;\n default:\n }\n };\n\n const onChange = () => {\n setTyped(true);\n if (open) {\n landingRef.current = 'typed';\n setHighlighted(null);\n refresh();\n } else {\n requestOpen(true, 'typed');\n }\n };\n\n const onClick = () => {\n requestOpen(true, 'open');\n };\n\n const onBlur = (event: React.FocusEvent<HTMLInputElement>) => {\n const next = event.relatedTarget as Node | null;\n if (\n next &&\n (rootRef.current?.contains(next) || popupRef.current?.contains(next))\n ) {\n return;\n }\n close();\n };\n\n const hover = (value: string) => {\n scrollToHighlightRef.current = false;\n setHighlighted(value);\n };\n\n const pick = (value: string) => {\n submit(value);\n // A press in the list keeps focus in the field, but a browser that\n // ignores that, or a tap, can leave it elsewhere.\n const input = inputRef.current;\n if (input && input.isConnected && getActiveElementInTree(input) !== input) {\n input.focus();\n }\n };\n\n const keepFocus = (event: React.MouseEvent) => event.preventDefault();\n\n const markInside = (event: React.PointerEvent) => {\n insidePressRef.current = event.nativeEvent;\n };\n\n const registerPopup = useCallback((element: HTMLElement) => {\n popupRef.current = element;\n return () => {\n popupRef.current = null;\n };\n }, []);\n\n const registerItem = useCallback(\n (element: HTMLElement, state: ComboboxItemState) => {\n const options = optionsRef.current;\n options.set(element, state);\n refresh();\n return () => {\n options.delete(element);\n refresh();\n };\n },\n []\n );\n\n return {\n open,\n expanded,\n highlighted,\n typed,\n listboxId,\n itemId,\n rootRef,\n inputRef,\n listboxRef,\n registerPopup,\n registerItem,\n onKeyDown,\n onChange,\n onClick,\n onBlur,\n hover,\n pick,\n keepFocus,\n markInside,\n };\n}\n"],"names":["isComposing"],"mappings":";;;;;;AAAA;AAgIA;AACA,MAAMA,aAAW,GAAG,CAAC,KAA0B,KAC7C,KAAK,CAAC,WAAW,CAAC,WAAW,IAAI,KAAK,CAAC,WAAW,CAAC,OAAO,KAAK,GAAG;AAEpE,MAAM,UAAU,GAAG,CAAC,CAAW,EAAE,CAAW,KAC1C,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,CAAC,KAAK,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AAEhE;;;;;;;;;;;;;;;;AAgBG;AACG,SAAU,iBAAiB,CAAC,EAChC,IAAI,EAAE,cAAc,EACpB,WAAW,EACX,YAAY,EACZ,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,MAAM,EACN,QAAQ,GACc,EAAA;AACtB,IAAA,MAAM,MAAM,GAAG,KAAK,EAAE;AACtB,IAAA,MAAM,SAAS,GAAG,CAAA,EAAG,MAAM,UAAU;AACrC,IAAA,MAAM,UAAU,GAAG,CAAA,EAAG,MAAM,QAAQ;;;IAGpC,MAAM,MAAM,GAAG,WAAW,CACxB,CAAC,KAAa,KAAK,UAAU,GAAG,kBAAkB,CAAC,KAAK,CAAC,EACzD,CAAC,UAAU,CAAC,CACb;AAED,IAAA,MAAM,OAAO,GAAG,MAAM,CAAwB,IAAI,CAAC;AACnD,IAAA,MAAM,QAAQ,GAAG,MAAM,CAA0B,IAAI,CAAC;AACtD,IAAA,MAAM,UAAU,GAAG,MAAM,CAAwB,IAAI,CAAC;AACtD,IAAA,MAAM,QAAQ,GAAG,MAAM,CAAqB,IAAI,CAAC;AACjD,IAAA,MAAM,cAAc,GAAG,MAAM,CAAe,IAAI,CAAC;IACjD,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,GAAG,EAAkC,CAAC;AACpE,IAAA,MAAM,UAAU,GAAG,MAAM,CAAiB,IAAI,CAAC;AAC/C,IAAA,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC;AAChC,IAAA,MAAM,cAAc,GAAG,MAAM,CAAW,EAAE,CAAC;;;;AAI3C,IAAA,MAAM,oBAAoB,GAAG,MAAM,CAAC,KAAK,CAAC;AAE1C,IAAA,MAAM,YAAY,GAAG,cAAc,KAAK,SAAS;IACjD,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,QAAQ,CAAC,WAAW,CAAC;IAC7D,MAAM,IAAI,GAAG,MAAM,KAAK,cAAc,IAAI,YAAY,CAAC;IAEvD,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC;IACnE,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC;;;IAGzC,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAiB,IAAI,CAAC;;;AAGhE,IAAA,MAAM,GAAG,OAAO,CAAC,GAAG,UAAU,CAAC,CAAC,CAAS,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;;;;IAKvD,IAAI,CAAC,IAAI,EAAE;QACT,IAAI,WAAW,KAAK,IAAI;YAAE,cAAc,CAAC,IAAI,CAAC;AAC9C,QAAA,IAAI,KAAK;YAAE,QAAQ,CAAC,KAAK,CAAC;QAC1B,IAAI,SAAS,KAAK,IAAI;YAAE,YAAY,CAAC,IAAI,CAAC;IAC5C;IACA,MAAM,QAAQ,GAAG,IAAI,KAAK,SAAS,IAAI,CAAC,QAAQ,CAAC;;AAGjD,IAAA,MAAM,YAAY,GAAG,WAAW,CAAC,MAAoB;AACnD,QAAA,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO;AAClC,QAAA,IAAI,CAAC,OAAO;AAAE,YAAA,OAAO,EAAE;QACvB,MAAM,KAAK,GAAkB,EAAE;QAC/B;aACG,gBAAgB,CAAc,iBAAiB;aAC/C,OAAO,CAAC,OAAO,IAAG;;;YAGjB,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC;AAC7C,YAAA,IAAI,KAAK;gBAAE,KAAK,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,GAAG,KAAK,EAAE,CAAC;AAC9C,QAAA,CAAC,CAAC;AACJ,QAAA,OAAO,KAAK;IACd,CAAC,EAAE,EAAE,CAAC;IAEN,MAAM,WAAW,GAAG,WAAW,CAC7B,CAAC,IAAa,EAAE,OAAA,GAAmB,MAAM,KAAI;AAC3C,QAAA,IAAI,MAAM,IAAI,IAAI,KAAK,IAAI;YAAE;AAC7B,QAAA,UAAU,CAAC,OAAO,GAAG,IAAI,GAAG,OAAO,GAAG,IAAI;AAC1C,QAAA,YAAY,GAAG,IAAI,CAAC;AACpB,QAAA,IAAI,CAAC,YAAY;YAAE,eAAe,CAAC,IAAI,CAAC;IAC1C,CAAC,EACD,CAAC,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,YAAY,CAAC,CAC3C;AACD,IAAA,MAAM,KAAK,GAAG,WAAW,CAAC,MAAM,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC;;;;;;IAOlE,qBAAqB,CAAC,MAAK;QACzB,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,UAAU,CAAC,OAAO;AAC1C,QAAA,UAAU,CAAC,OAAO,GAAG,IAAI;QACzB,IAAI,CAAC,IAAI,EAAE;AACT,YAAA,UAAU,CAAC,OAAO,GAAG,IAAI;AACzB,YAAA,cAAc,CAAC,OAAO,GAAG,EAAE;YAC3B;QACF;AACA,QAAA,MAAM,OAAO,GAAG,YAAY,EAAE;AAC9B,QAAA,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,IAAI,MAAM,CAAC,KAAK,CAAC;QAClD,MAAM,OAAO,GAAG,CAAC,UAAU,CAAC,MAAM,EAAE,cAAc,CAAC,OAAO,CAAC;AAC3D,QAAA,cAAc,CAAC,OAAO,GAAG,MAAM;AAC/B,QAAA,YAAY,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;AAE/B,QAAA,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,KAAK,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;AAC9D,QAAA,UAAU,CAAC,OAAO,GAAG,IAAI;AACzB,QAAA,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC;AAC1D,QAAA,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,OAAO,CAAC,EAAE,KAAK;QAC7D,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI;QAC1D,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI;QAC1E,IAAI,IAAI,GAAG,WAAW;QACtB,IAAI,OAAO,KAAK,OAAO;AAAE,YAAA,IAAI,GAAG,OAAO,IAAI,KAAK;aAC3C,IAAI,OAAO,KAAK,MAAM;AAAE,YAAA,IAAI,GAAG,OAAO,IAAI,IAAI;aAC9C,IAAI,OAAO,KAAK,MAAM;AAAE,YAAA,IAAI,GAAG,OAAO,KAAK,SAAS,GAAG,KAAK,GAAG,IAAI,CAAC;aACpE,IAAI,OAAO,KAAK,OAAO;YAAE,IAAI,GAAG,SAAS,GAAG,KAAK,GAAG,IAAI;aACxD,IAAI,OAAO,KAAK,MAAM;YAAE,IAAI,GAAG,IAAI;aACnC,IAAI,SAAS,IAAI,OAAO;YAAE,IAAI,GAAG,KAAK;;;aAGtC,IAAI,IAAI,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,IAAI,GAAG,IAAI;;;;AAI7D,QAAA,IAAI,IAAI,KAAK,WAAW,EAAE;AACxB,YAAA,oBAAoB,CAAC,OAAO,GAAG,IAAI;YACnC,cAAc,CAAC,IAAI,CAAC;QACtB;AACF,IAAA,CAAC,CAAC;;;;IAKF,qBAAqB,CAAC,MAAK;AACzB,QAAA,IAAI,WAAW,KAAK,IAAI,IAAI,CAAC,oBAAoB,CAAC,OAAO;YAAE;AAC3D,QAAA,oBAAoB,CAAC,OAAO,GAAG,KAAK;AACpC,QAAA,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,KAAK,KAAK,WAAW,CAAC;AACxE,QAAA,MAAM,EAAE,OAAO,CAAC,cAAc,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;AACxD,IAAA,CAAC,EAAE,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;;;IAI/B,SAAS,CAAC,MAAK;AACb,QAAA,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO;AAC5B,QAAA,IAAI,CAAC,IAAI,IAAI,MAAM,IAAI,CAAC,IAAI;AAAE,YAAA,OAAO,SAAS;AAC9C,QAAA,MAAM,OAAO,GAAG,CAAC,KAAmB,KAAI;AACtC,YAAA,IACE,KAAK,KAAK,cAAc,CAAC,OAAO;AAChC,gBAAA,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,OAAO,CAAC;gBACrC,aAAa,CAAC,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC,EACtC;gBACA;YACF;AACA,YAAA,KAAK,EAAE;AACT,QAAA,CAAC;;AAED,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,aAAa;AAC9B,QAAA,GAAG,CAAC,gBAAgB,CAAC,aAAa,EAAE,OAAO,CAAC;QAC5C,OAAO,MAAM,GAAG,CAAC,mBAAmB,CAAC,aAAa,EAAE,OAAO,CAAC;IAC9D,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;AAEzB,IAAA,MAAM,MAAM,GAAG,WAAW,CACxB,CAAC,KAAa,KAAI;AAChB,QAAA,QAAQ,GAAG,KAAK,CAAC;AACjB,QAAA,IAAI,CAAC,QAAQ;AAAE,YAAA,KAAK,EAAE;IACxB,CAAC,EACD,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,CAAC,CAC5B;AAED,IAAA,MAAM,SAAS,GAAG,CAAC,KAA4C,KAAI;;;AAGjE,QAAA,IAAI,KAAK,CAAC,gBAAgB,IAAIA,aAAW,CAAC,KAAK,CAAC;YAAE;AAClD,QAAA,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,KAAK;QAE7B,IAAI,CAAC,IAAI,EAAE;AACT,YAAA,IAAI,GAAG,KAAK,WAAW,KAAK,GAAG,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,EAAE;gBACzD,KAAK,CAAC,cAAc,EAAE;gBACtB,WAAW,CACT,IAAI,EACJ,MAAM,GAAG,MAAM,GAAG,GAAG,KAAK,WAAW,GAAG,OAAO,GAAG,MAAM,CACzD;YACH;YACA;QACF;QAEA,QAAQ,GAAG;AACT,YAAA,KAAK,WAAW;YAChB,KAAK,SAAS,EAAE;gBACd,IAAI,MAAM,EAAE;AACV,oBAAA,IAAI,GAAG,KAAK,SAAS,IAAI,CAAC,MAAM,EAAE;wBAChC,KAAK,CAAC,cAAc,EAAE;AACtB,wBAAA,KAAK,EAAE;oBACT;oBACA;gBACF;gBACA,KAAK,CAAC,cAAc,EAAE;;AAEtB,gBAAA,MAAM,OAAO,GAAG,YAAY,EAAE;AAC9B,gBAAA,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;oBAAE;AAC1B,gBAAA,MAAM,EAAE,GAAG,OAAO,CAAC,SAAS,CAAC,MAAM,IAAI,MAAM,CAAC,KAAK,KAAK,WAAW,CAAC;AACpE,gBAAA,MAAM,IAAI,GAAG,GAAG,KAAK,WAAW,GAAG,CAAC,GAAG,EAAE;AACzC,gBAAA,MAAM,KAAK,GACT,EAAE,KAAK;sBACH,IAAI,KAAK;AACT,0BAAE;AACF,0BAAE,OAAO,CAAC,MAAM,GAAG;AACrB,sBAAE,CAAC,EAAE,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM;AACnD,gBAAA,oBAAoB,CAAC,OAAO,GAAG,IAAI;gBACnC,cAAc,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC;gBACpC;YACF;YACA,KAAK,OAAO,EAAE;AACZ,gBAAA,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC,IAAI,CAChC,KAAK,IAAI,KAAK,CAAC,KAAK,KAAK,WAAW,CACrC;gBACD,IAAI,MAAM,EAAE;oBACV,KAAK,CAAC,cAAc,EAAE;oBACtB,IAAI,CAAC,MAAM,CAAC,QAAQ;AAAE,wBAAA,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC;oBAC1C;gBACF;;gBAEA,QAAQ,IAAI;AACZ,gBAAA,KAAK,EAAE;gBACP;YACF;AACA,YAAA,KAAK,QAAQ;AACX,gBAAA,IAAI,MAAM;oBAAE;;;;gBAIZ,IAAI,QAAQ,CAAC,OAAO;oBAAE,KAAK,CAAC,cAAc,EAAE;AAC5C,gBAAA,KAAK,EAAE;gBACP;AACF,YAAA,KAAK,KAAK;AACR,gBAAA,KAAK,EAAE;gBACP;AACF,YAAA,KAAK,MAAM;AACX,YAAA,KAAK,KAAK;AACV,YAAA,KAAK,WAAW;AAChB,YAAA,KAAK,YAAY;;gBAEf,cAAc,CAAC,IAAI,CAAC;gBACpB;;AAGN,IAAA,CAAC;IAED,MAAM,QAAQ,GAAG,MAAK;QACpB,QAAQ,CAAC,IAAI,CAAC;QACd,IAAI,IAAI,EAAE;AACR,YAAA,UAAU,CAAC,OAAO,GAAG,OAAO;YAC5B,cAAc,CAAC,IAAI,CAAC;AACpB,YAAA,OAAO,EAAE;QACX;aAAO;AACL,YAAA,WAAW,CAAC,IAAI,EAAE,OAAO,CAAC;QAC5B;AACF,IAAA,CAAC;IAED,MAAM,OAAO,GAAG,MAAK;AACnB,QAAA,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC;AAC3B,IAAA,CAAC;AAED,IAAA,MAAM,MAAM,GAAG,CAAC,KAAyC,KAAI;AAC3D,QAAA,MAAM,IAAI,GAAG,KAAK,CAAC,aAA4B;AAC/C,QAAA,IACE,IAAI;AACJ,aAAC,OAAO,CAAC,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,EACrE;YACA;QACF;AACA,QAAA,KAAK,EAAE;AACT,IAAA,CAAC;AAED,IAAA,MAAM,KAAK,GAAG,CAAC,KAAa,KAAI;AAC9B,QAAA,oBAAoB,CAAC,OAAO,GAAG,KAAK;QACpC,cAAc,CAAC,KAAK,CAAC;AACvB,IAAA,CAAC;AAED,IAAA,MAAM,IAAI,GAAG,CAAC,KAAa,KAAI;QAC7B,MAAM,CAAC,KAAK,CAAC;;;AAGb,QAAA,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO;AAC9B,QAAA,IAAI,KAAK,IAAI,KAAK,CAAC,WAAW,IAAI,sBAAsB,CAAC,KAAK,CAAC,KAAK,KAAK,EAAE;YACzE,KAAK,CAAC,KAAK,EAAE;QACf;AACF,IAAA,CAAC;IAED,MAAM,SAAS,GAAG,CAAC,KAAuB,KAAK,KAAK,CAAC,cAAc,EAAE;AAErE,IAAA,MAAM,UAAU,GAAG,CAAC,KAAyB,KAAI;AAC/C,QAAA,cAAc,CAAC,OAAO,GAAG,KAAK,CAAC,WAAW;AAC5C,IAAA,CAAC;AAED,IAAA,MAAM,aAAa,GAAG,WAAW,CAAC,CAAC,OAAoB,KAAI;AACzD,QAAA,QAAQ,CAAC,OAAO,GAAG,OAAO;AAC1B,QAAA,OAAO,MAAK;AACV,YAAA,QAAQ,CAAC,OAAO,GAAG,IAAI;AACzB,QAAA,CAAC;IACH,CAAC,EAAE,EAAE,CAAC;IAEN,MAAM,YAAY,GAAG,WAAW,CAC9B,CAAC,OAAoB,EAAE,KAAwB,KAAI;AACjD,QAAA,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO;AAClC,QAAA,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC;AAC3B,QAAA,OAAO,EAAE;AACT,QAAA,OAAO,MAAK;AACV,YAAA,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC;AACvB,YAAA,OAAO,EAAE;AACX,QAAA,CAAC;IACH,CAAC,EACD,EAAE,CACH;IAED,OAAO;QACL,IAAI;QACJ,QAAQ;QACR,WAAW;QACX,KAAK;QACL,SAAS;QACT,MAAM;QACN,OAAO;QACP,QAAQ;QACR,UAAU;QACV,aAAa;QACb,YAAY;QACZ,SAAS;QACT,QAAQ;QACR,OAAO;QACP,MAAM;QACN,KAAK;QACL,IAAI;QACJ,SAAS;QACT,UAAU;KACX;AACH;;"}
1
+ {"version":3,"file":"useComboboxEngine.js","sources":["../../../src/helpers/useComboboxEngine.ts"],"sourcesContent":["// INTERNAL: deliberately not exported from src/index.ts.\nimport {\n useCallback,\n useEffect,\n useId,\n useReducer,\n useRef,\n useState,\n} from 'react';\nimport type React from 'react';\nimport { useClientLayoutEffect } from './useClientLayoutEffect';\nimport { getActiveElementInTree, isEventInside } from './shadowDom';\n\n/** What an item tells the engine about itself. */\nexport interface ComboboxItemState {\n /** What a pick hands back. Unique within the combobox. */\n value: string;\n /** Reachable with the arrows, never picked. */\n disabled: boolean;\n /** The value, or one of them. */\n checked: boolean;\n}\n\n/** An option on the page, with its element. */\ninterface ShownOption extends ComboboxItemState {\n element: HTMLElement;\n}\n\n/**\n * Where the highlight lands once an opened list has rendered, or once typing\n * has changed what's in it:\n *\n * - `first` and `last` (Down and Up Arrow): the checked option, otherwise the\n * first or last enabled one;\n * - `open` (a click, or any other way of opening): the checked option,\n * otherwise the first enabled one with `keepFirst`, otherwise nothing;\n * - `typed`: the first enabled option with `keepFirst`, otherwise nothing;\n * - `none` (Alt+Down Arrow): nothing.\n *\n * Only an enabled option takes it, so a disabled checked one is passed over.\n */\ntype Landing = 'first' | 'last' | 'open' | 'typed' | 'none';\n\n/** Options for `useComboboxEngine`. */\nexport interface ComboboxEngineOptions {\n /** Controlled open state. When set, opening and closing only report through `onOpenChange`. */\n open?: boolean;\n /** Initial open state when uncontrolled. */\n defaultOpen: boolean;\n /** Called with the open state asked for, when it differs from the current one. */\n onOpenChange?: (open: boolean) => void;\n /** Called with an item's `value` when it's picked. */\n onSelect?: (value: string) => void;\n /**\n * Called when Enter goes on to the form with nothing to pick, before the\n * list closes, so an owner that shows something other than the typing can\n * put it back before the form reads the field. An `inline` list doesn't\n * close, so this is the one word it gets.\n */\n onSettle?: () => void;\n /** Highlights the first enabled item whenever the list opens or its items change. */\n keepFirst: boolean;\n /** Several values: a pick keeps the list open. */\n multiple: boolean;\n /** The list is always on screen, so nothing opens or closes it. */\n inline: boolean;\n /**\n * The list's panel is portaled, so it has no server markup and shows only\n * from the commit after hydration. Until it has rendered, an open list\n * isn't taken to be on the page, so no `aria-controls` names it early.\n */\n deferred: boolean;\n}\n\n/** What `useComboboxEngine` hands the parts of a combobox. */\nexport interface ComboboxEngine {\n /** Whether the list is open. Always, with `inline`. */\n open: boolean;\n /**\n * The field's `aria-expanded`: open, with a listbox that has an option in\n * it on the page. Until the open list has rendered, it's taken to have\n * one, so server markup for an open list in place says it's expanded. A\n * `deferred` list isn't in server markup, so it's taken to have none.\n */\n expanded: boolean;\n /** The highlighted option's value, if any. */\n highlighted: string | null;\n /** Whether the user has typed since the list opened. */\n typed: boolean;\n /** The listbox's id. */\n listboxId: string;\n /** An item's id, which follows from its value. */\n itemId: (value: string) => string;\n /** For the element around the field and, unless it's portaled, the list. */\n rootRef: React.MutableRefObject<HTMLDivElement | null>;\n /** For the field. */\n inputRef: React.MutableRefObject<HTMLInputElement | null>;\n /** For the listbox. */\n listboxRef: React.MutableRefObject<HTMLDivElement | null>;\n /**\n * Records the floating panel while it's on screen, which makes it count as\n * inside, and makes an Escape that closes it stop there. Returns the\n * function that forgets it.\n */\n registerPopup: (element: HTMLElement) => () => void;\n /** Records an item's element. Returns the function that forgets it. */\n registerItem: (element: HTMLElement, state: ComboboxItemState) => () => void;\n /** The field's keys, to run after the caller's own handler. */\n onKeyDown: (event: React.KeyboardEvent<HTMLInputElement>) => void;\n /** A change to the field's value: the user typed. */\n onChange: () => void;\n /** A click in the field. */\n onClick: () => void;\n /** Focus leaving the field. */\n onBlur: (event: React.FocusEvent<HTMLInputElement>) => void;\n /** The pointer moved over an enabled option. */\n hover: (value: string) => void;\n /** A click on an enabled option. Focus ends up in the field. */\n pick: (value: string) => void;\n /** For anything in the list: a press there doesn't take focus from the field. */\n keepFocus: (event: React.MouseEvent) => void;\n /**\n * For the floating panel: marks a press as inside, which React reports from\n * content the panel portals elsewhere as well.\n */\n markInside: (event: React.PointerEvent) => void;\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\nconst sameValues = (a: string[], b: string[]): boolean =>\n a.length === b.length && a.every((value, i) => value === b[i]);\n\n/**\n * The keys, the open state, the highlight and the ARIA of an editable\n * combobox whose list the app renders and filters (#1083). DOM focus stays\n * in the field the whole time: the highlight is `aria-activedescendant`, as\n * the ARIA Authoring Practices ask of a combobox, where `useMenuKeys` moves\n * real focus, as a menu should.\n *\n * The options are read from the listbox in document order whenever they're\n * needed, so options in groups and options that come and go are found, and\n * the highlight is remembered by value, so it survives a re-render. The text\n * in the field and any value are the app's: a pick only calls\n * `onSelect`.\n *\n * @function useComboboxEngine\n * @param options - The open state, the pick callback and the shape.\n * @returns The state and handlers the parts wire up.\n */\nexport function useComboboxEngine({\n open: controlledOpen,\n defaultOpen,\n onOpenChange,\n onSelect,\n onSettle,\n keepFirst,\n multiple,\n inline,\n deferred,\n}: ComboboxEngineOptions): ComboboxEngine {\n const baseId = useId();\n const listboxId = `${baseId}-listbox`;\n const itemPrefix = `${baseId}-item-`;\n // The value goes into the id encoded, so a value with a space in it still\n // makes one id, not two.\n const itemId = useCallback(\n (value: string) => itemPrefix + encodeURIComponent(value),\n [itemPrefix]\n );\n\n const rootRef = useRef<HTMLDivElement | null>(null);\n const inputRef = useRef<HTMLInputElement | null>(null);\n const listboxRef = useRef<HTMLDivElement | null>(null);\n const popupRef = useRef<HTMLElement | null>(null);\n const insidePressRef = useRef<Event | null>(null);\n const optionsRef = useRef(new Map<HTMLElement, ComboboxItemState>());\n const landingRef = useRef<Landing | null>(null);\n const wasOpenRef = useRef(false);\n const shownValuesRef = useRef<string[]>([]);\n // Whether the next highlight change is one to scroll to: a key's or a\n // landing's. The pointer's isn't, since the pointer is already on that\n // option, and scrolling would move the list out from under it.\n const scrollToHighlightRef = useRef(false);\n\n const isControlled = controlledOpen !== undefined;\n const [internalOpen, setInternalOpen] = useState(defaultOpen);\n const open = inline || (controlledOpen ?? internalOpen);\n\n const [highlighted, setHighlighted] = useState<string | null>(null);\n const [typed, setTyped] = useState(false);\n // Whether a listbox with an option in it is on the page, which only the\n // page can say, so it's read once the open list has rendered.\n const [listShown, setListShown] = useState<boolean | null>(null);\n // Options register from their own effects, which needn't render the\n // component that holds this hook, so they ask for a render to be read.\n const [, refresh] = useReducer((n: number) => n + 1, 0);\n\n // Closing forgets the highlight, the typing and what was on the page,\n // during render, React's pattern for adjusting state when other state\n // changes.\n if (!open) {\n if (highlighted !== null) setHighlighted(null);\n if (typed) setTyped(false);\n if (listShown !== null) setListShown(null);\n }\n const expanded = open && (listShown ?? !deferred);\n\n /** The options on the page, in document order, groups included. */\n const shownOptions = useCallback((): ShownOption[] => {\n const listbox = listboxRef.current;\n if (!listbox) return [];\n const shown: ShownOption[] = [];\n listbox\n .querySelectorAll<HTMLElement>('[role=\"option\"]')\n .forEach(element => {\n // Only the Combobox's own options: an element given the role by\n // hand isn't one.\n const state = optionsRef.current.get(element);\n if (state) shown.push({ element, ...state });\n });\n return shown;\n }, []);\n\n const requestOpen = useCallback(\n (next: boolean, landing: Landing = 'open') => {\n if (inline || next === open) return;\n landingRef.current = next ? landing : null;\n onOpenChange?.(next);\n if (!isControlled) setInternalOpen(next);\n },\n [inline, open, onOpenChange, isControlled]\n );\n const close = useCallback(() => requestOpen(false), [requestOpen]);\n\n // After every render, read the list: whether it has options, and where\n // the highlight goes now. A highlighted option that has left takes the\n // highlight with it, or hands it to the first enabled one under\n // `keepFirst`, which also moves it there whenever the options change.\n // eslint-disable-next-line react-hooks/exhaustive-deps -- after every render on purpose\n useClientLayoutEffect(() => {\n const opened = open && !wasOpenRef.current;\n wasOpenRef.current = open;\n if (!open) {\n landingRef.current = null;\n shownValuesRef.current = [];\n return;\n }\n const options = shownOptions();\n const values = options.map(option => option.value);\n const changed = !sameValues(values, shownValuesRef.current);\n shownValuesRef.current = values;\n setListShown(values.length > 0);\n\n const landing = landingRef.current ?? (opened ? 'open' : null);\n landingRef.current = null;\n const enabled = options.filter(option => !option.disabled);\n const checked = enabled.find(option => option.checked)?.value;\n const first = enabled.length > 0 ? enabled[0].value : null;\n const last = enabled.length > 0 ? enabled[enabled.length - 1].value : null;\n let next = highlighted;\n if (landing === 'first') next = checked ?? first;\n else if (landing === 'last') next = checked ?? last;\n else if (landing === 'open') next = checked ?? (keepFirst ? first : null);\n else if (landing === 'typed') next = keepFirst ? first : null;\n else if (landing === 'none') next = null;\n else if (keepFirst && changed) next = first;\n // Without `keepFirst`, which moved it above, a highlighted option\n // that left takes the highlight with it.\n else if (next !== null && !values.includes(next)) next = null;\n // Only a change is set. StrictMode runs this twice on mount, and the\n // second run, with the landing used up, works out the rendered highlight\n // again, so it leaves the first run's landing in place.\n if (next !== highlighted) {\n scrollToHighlightRef.current = true;\n setHighlighted(next);\n }\n });\n\n // A browser scrolls to what has focus, not to an active descendant, so\n // the highlighted option is brought into view here, when a key or a\n // landing moved it there.\n useClientLayoutEffect(() => {\n if (highlighted === null || !scrollToHighlightRef.current) return;\n scrollToHighlightRef.current = false;\n const option = shownOptions().find(shown => shown.value === highlighted);\n option?.element.scrollIntoView?.({ block: 'nearest' });\n }, [highlighted, shownOptions]);\n\n // A press outside the field and the list closes it. The path is checked\n // rather than the target, so it works in a shadow root.\n useEffect(() => {\n const root = rootRef.current;\n if (!open || inline || !root) return undefined;\n const onPress = (event: PointerEvent) => {\n if (\n event === insidePressRef.current ||\n isEventInside(event, rootRef.current) ||\n isEventInside(event, popupRef.current)\n ) {\n return;\n }\n close();\n };\n // Its document, an iframe's when the tree renders into one.\n const doc = root.ownerDocument;\n doc.addEventListener('pointerdown', onPress);\n return () => doc.removeEventListener('pointerdown', onPress);\n }, [open, inline, close]);\n\n const submit = useCallback(\n (value: string) => {\n onSelect?.(value);\n if (!multiple) close();\n },\n [onSelect, multiple, close]\n );\n\n const onKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {\n // The caller's handler ran first and can keep a key, and an IME that's\n // composing owns every key.\n if (event.defaultPrevented || isComposing(event)) return;\n const { key, altKey } = event;\n\n if (!open) {\n if (key === 'ArrowDown' || (key === 'ArrowUp' && !altKey)) {\n event.preventDefault();\n requestOpen(\n true,\n altKey ? 'none' : key === 'ArrowDown' ? 'first' : 'last'\n );\n }\n return;\n }\n\n switch (key) {\n case 'ArrowDown':\n case 'ArrowUp': {\n if (altKey) {\n if (key === 'ArrowUp' && !inline) {\n event.preventDefault();\n close();\n }\n return;\n }\n event.preventDefault();\n // Disabled options too, so a screen reader user finds them.\n const options = shownOptions();\n if (options.length === 0) return;\n const at = options.findIndex(option => option.value === highlighted);\n const step = key === 'ArrowDown' ? 1 : -1;\n const index =\n at === -1\n ? step === 1\n ? 0\n : options.length - 1\n : (at + step + options.length) % options.length;\n scrollToHighlightRef.current = true;\n setHighlighted(options[index].value);\n return;\n }\n case 'Enter': {\n const option = shownOptions().find(\n shown => shown.value === highlighted\n );\n if (option) {\n event.preventDefault();\n if (!option.disabled) submit(option.value);\n return;\n }\n // Nothing to pick: the key goes on, so a form submits.\n onSettle?.();\n close();\n return;\n }\n case 'Escape':\n if (inline) return;\n // It stops here only when there was something to close, so a\n // Popover or Modal around it keeps its own Escape otherwise. Nothing\n // stops it going on: a handler further out reads `defaultPrevented`.\n if (popupRef.current) event.preventDefault();\n close();\n return;\n case 'Tab':\n close();\n return;\n case 'Home':\n case 'End':\n case 'ArrowLeft':\n case 'ArrowRight':\n // The field's own keys, which take the field back from the list.\n setHighlighted(null);\n return;\n default:\n }\n };\n\n const onChange = () => {\n setTyped(true);\n if (open) {\n landingRef.current = 'typed';\n setHighlighted(null);\n refresh();\n } else {\n requestOpen(true, 'typed');\n }\n };\n\n const onClick = () => {\n requestOpen(true, 'open');\n };\n\n const onBlur = (event: React.FocusEvent<HTMLInputElement>) => {\n const next = event.relatedTarget as Node | null;\n if (\n next &&\n (rootRef.current?.contains(next) || popupRef.current?.contains(next))\n ) {\n return;\n }\n close();\n };\n\n const hover = (value: string) => {\n scrollToHighlightRef.current = false;\n setHighlighted(value);\n };\n\n const pick = (value: string) => {\n submit(value);\n // A press in the list keeps focus in the field, but a browser that\n // ignores that, or a tap, can leave it elsewhere.\n const input = inputRef.current;\n if (input && input.isConnected && getActiveElementInTree(input) !== input) {\n input.focus();\n }\n };\n\n const keepFocus = (event: React.MouseEvent) => event.preventDefault();\n\n const markInside = (event: React.PointerEvent) => {\n insidePressRef.current = event.nativeEvent;\n };\n\n const registerPopup = useCallback((element: HTMLElement) => {\n popupRef.current = element;\n return () => {\n popupRef.current = null;\n };\n }, []);\n\n const registerItem = useCallback(\n (element: HTMLElement, state: ComboboxItemState) => {\n const options = optionsRef.current;\n options.set(element, state);\n refresh();\n return () => {\n options.delete(element);\n refresh();\n };\n },\n []\n );\n\n return {\n open,\n expanded,\n highlighted,\n typed,\n listboxId,\n itemId,\n rootRef,\n inputRef,\n listboxRef,\n registerPopup,\n registerItem,\n onKeyDown,\n onChange,\n onClick,\n onBlur,\n hover,\n pick,\n keepFocus,\n markInside,\n };\n}\n"],"names":["isComposing","sameValues"],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /* @allxsmith/bestax-bulma — AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */
2
+ 'use client';
3
+ import { useState } from 'react';
4
+
5
+ // INTERNAL: deliberately not exported from src/index.ts.
6
+ /**
7
+ * A value the caller holds, or one this hook holds when the caller doesn't:
8
+ * the `x` / `defaultX` pair of a component's props. The caller holds it
9
+ * whenever `controlled` isn't `undefined`, so `null` is a held value. The
10
+ * setter changes only the value held here, so a held one changes when the
11
+ * caller passes the new value, and it reports nothing: the component calls
12
+ * its own `onXChange`.
13
+ *
14
+ * @function useControlled
15
+ * @param controlled - The caller's value, or `undefined` to hold one here.
16
+ * @param initial - The value held here to begin with.
17
+ * @returns The current value, and the setter for the one held here.
18
+ */
19
+ function useControlled(controlled, initial) {
20
+ const [own, setOwn] = useState(initial);
21
+ const isControlled = controlled !== undefined;
22
+ const set = (next) => {
23
+ if (!isControlled)
24
+ setOwn(next);
25
+ };
26
+ return [isControlled ? controlled : own, set];
27
+ }
28
+
29
+ export { useControlled };
30
+ //# sourceMappingURL=useControlled.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useControlled.js","sources":["../../../src/helpers/useControlled.ts"],"sourcesContent":["// INTERNAL: deliberately not exported from src/index.ts.\nimport { useState } from 'react';\n\n/**\n * A value the caller holds, or one this hook holds when the caller doesn't:\n * the `x` / `defaultX` pair of a component's props. The caller holds it\n * whenever `controlled` isn't `undefined`, so `null` is a held value. The\n * setter changes only the value held here, so a held one changes when the\n * caller passes the new value, and it reports nothing: the component calls\n * its own `onXChange`.\n *\n * @function useControlled\n * @param controlled - The caller's value, or `undefined` to hold one here.\n * @param initial - The value held here to begin with.\n * @returns The current value, and the setter for the one held here.\n */\nexport function 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"],"names":[],"mappings":";;;;AAAA;AAGA;;;;;;;;;;;;AAYG;AACG,SAAU,aAAa,CAAI,UAAyB,EAAE,OAAU,EAAA;IACpE,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC;AACvC,IAAA,MAAM,YAAY,GAAG,UAAU,KAAK,SAAS;AAC7C,IAAA,MAAM,GAAG,GAAG,CAAC,IAAO,KAAI;AACtB,QAAA,IAAI,CAAC,YAAY;YAAE,MAAM,CAAC,IAAI,CAAC;AACjC,IAAA,CAAC;AACD,IAAA,OAAO,CAAC,YAAY,GAAG,UAAU,GAAG,GAAG,EAAE,GAAG,CAAU;AACxD;;"}
@@ -1,7 +1,8 @@
1
1
  /* @allxsmith/bestax-bulma — AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */
2
2
  'use client';
3
- import { useRef, useReducer, useState, useEffect, useCallback } from 'react';
3
+ import { createContext, useRef, useReducer, useState, useEffect, useCallback } from 'react';
4
4
  import { useClientLayoutEffect } from './useClientLayoutEffect.js';
5
+ import { useControlled } from './useControlled.js';
5
6
  import { getActiveElementInTree } from './shadowDom.js';
6
7
  import { useTypeahead, typeaheadMatch, typeaheadText } from './typeahead.js';
7
8
 
@@ -78,6 +79,14 @@ function pathOf(element, { values }) {
78
79
  }
79
80
  return path;
80
81
  }
82
+ /**
83
+ * The values of the items a tree hides while they stay in it, as TreeSelect's
84
+ * search hides the items that don't match. A hidden item renders as one under
85
+ * a closed parent does: nothing on the page, and in a `checkbox` tree it and
86
+ * its children still register, so a check still reaches them. Internal, for
87
+ * the components that put a TreeView inside them.
88
+ */
89
+ const TreeViewHiddenContext = createContext(new Set());
81
90
  /**
82
91
  * Every known item's check state, by the rules in #1096:
83
92
  *
@@ -153,16 +162,6 @@ function toggledList(records, list, value) {
153
162
  ...list.filter(item => !records.has(item)),
154
163
  ];
155
164
  }
156
- /** A value the caller holds, or one this hook holds when the caller doesn't. */
157
- function useControlled(controlled, initial) {
158
- const [own, setOwn] = useState(initial);
159
- const isControlled = controlled !== undefined;
160
- const set = (next) => {
161
- if (!isControlled)
162
- setOwn(next);
163
- };
164
- return [isControlled ? controlled : own, set];
165
- }
166
165
  /**
167
166
  * The list with `value` added at the end, or, when it's `present`, with
168
167
  * every copy of it taken out. Everything else stays as it was, in its order.
@@ -430,12 +429,15 @@ function useTreeView(options) {
430
429
  press(item, actions.space);
431
430
  break;
432
431
  case '*': {
432
+ // The siblings on the page: not the ones a component around the
433
+ // tree hides, which stay registered.
433
434
  const closed = [];
434
435
  registry.records.forEach((record, value) => {
435
436
  if (record.parent === item.parent &&
436
437
  record.isParent &&
437
438
  !record.disabled &&
438
- !expanded.includes(value)) {
439
+ !expanded.includes(value) &&
440
+ registry.elements.has(value)) {
439
441
  closed.push(value);
440
442
  }
441
443
  });
@@ -476,5 +478,5 @@ function useTreeView(options) {
476
478
  };
477
479
  }
478
480
 
479
- export { useTreeView };
481
+ export { TreeViewHiddenContext, readChecks, toggledList, useTreeView };
480
482
  //# sourceMappingURL=useTreeView.js.map