@allxsmith/bestax-bulma 5.30.0 → 5.31.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.
@@ -7,6 +7,7 @@ import { withSubComponents } from '../helpers/withSubComponents.js';
7
7
  import { isEventInside, getActiveElementInTree } from '../helpers/shadowDom.js';
8
8
  import { omitAttrs, ANCHOR_ONLY_ATTRS } from '../helpers/anchorAttrs.js';
9
9
  import { buttonType } from '../helpers/buttonType.js';
10
+ import { useMenuKeys, enabledMenuItems } from '../helpers/useMenuKeys.js';
10
11
 
11
12
  /**
12
13
  * Checks if code is running in a browser environment.
@@ -21,6 +22,19 @@ const isBrowser$1 = (win, doc) => typeof win !== 'undefined' && typeof doc !== '
21
22
  /**
22
23
  * The `Dropdown` component provides Bulma's versatile dropdown menu for your Bulma React UI.
23
24
  *
25
+ * Down, Enter and Space on the trigger open the menu on its first enabled
26
+ * item, and Up opens it on its last. On the trigger of a menu that's already
27
+ * open, Down and Up move focus to its first and last item. In the menu, the
28
+ * arrow keys, Home and End move between the items, and Enter and Space run
29
+ * the focused one the way a click does. Typing a letter moves to the next
30
+ * item whose text starts with it, skipping disabled ones, and letters typed
31
+ * in quick succession add up to one search. Typing searches once focus is in
32
+ * the menu, so a letter typed on the trigger does nothing. Escape closes the
33
+ * menu and focuses the trigger, and Tab closes it and lets focus move on.
34
+ * Anything focusable in the menu that isn't an item, such as a field, keeps
35
+ * its own keys apart from Escape and Tab. For a panel built around fields,
36
+ * use `Popover`.
37
+ *
24
38
  * @function
25
39
  * @param {DropdownProps} props - Props for the Dropdown component.
26
40
  * @param {React.Ref<HTMLDivElement>} ref - Forwarded ref to the root dropdown element.
@@ -31,6 +45,8 @@ const DropdownComponent = forwardRef(function DropdownComponent({ label, childre
31
45
  const [active, setActive] = useState(!!activeProp);
32
46
  const dropdownRef = useRef(null);
33
47
  const triggerRef = useRef(null);
48
+ const menuRef = useRef(null);
49
+ const menuKeys = useMenuKeys();
34
50
  const pendingFocusRef = useRef(null);
35
51
  // Cleanup returned by a consumer's callback ref on attach, held until detach.
36
52
  const consumerCleanupRef = useRef(null);
@@ -118,29 +134,23 @@ const DropdownComponent = forwardRef(function DropdownComponent({ label, childre
118
134
  }
119
135
  }
120
136
  };
121
- const getMenuItems = () => {
122
- /* istanbul ignore next: dropdownRef.current is never null once mounted */
123
- if (!dropdownRef.current)
124
- return [];
125
- return Array.from(
126
- // Every menu-item role, so an item a caller gives `menuitemcheckbox`
127
- // or `menuitemradio` (a filter or sort menu) stays reachable.
128
- dropdownRef.current.querySelectorAll('[role="menuitem"], [role="menuitemcheckbox"], [role="menuitemradio"]')).filter(el => !el.hasAttribute('disabled') &&
129
- el.getAttribute('aria-disabled') !== 'true');
137
+ const focusEdgeItem = (edge) => {
138
+ const items = enabledMenuItems(menuRef.current);
139
+ items[edge === 'first' ? 0 : items.length - 1]?.focus();
130
140
  };
131
- // Focus the pending menu item once the menu becomes visible; the item is
132
- // unfocusable while `display: none` applies, so this must wait for the
133
- // `is-active` class to actually land in the DOM.
141
+ // Once the menu shows, forget a search typed just before it last closed,
142
+ // and focus the pending item. The item is unfocusable while
143
+ // `display: none` applies, so this must wait for the `is-active` class to
144
+ // actually land in the DOM.
134
145
  useEffect(() => {
135
- if (!active || !pendingFocusRef.current)
146
+ if (!active)
136
147
  return;
137
- const items = getMenuItems();
138
- if (items.length) {
139
- const index = pendingFocusRef.current === 'first' ? 0 : items.length - 1;
140
- items[index].focus();
141
- }
148
+ menuKeys.reset();
149
+ if (!pendingFocusRef.current)
150
+ return;
151
+ focusEdgeItem(pendingFocusRef.current);
142
152
  pendingFocusRef.current = null;
143
- }, [active]);
153
+ }, [active, menuKeys]);
144
154
  const handleTriggerKeyDown = (e) => {
145
155
  if (disabled)
146
156
  return;
@@ -153,9 +163,7 @@ const DropdownComponent = forwardRef(function DropdownComponent({ label, childre
153
163
  onActiveChange?.(true);
154
164
  }
155
165
  else {
156
- const items = getMenuItems();
157
- if (items.length)
158
- items[0].focus();
166
+ focusEdgeItem('first');
159
167
  pendingFocusRef.current = null;
160
168
  }
161
169
  break;
@@ -167,9 +175,7 @@ const DropdownComponent = forwardRef(function DropdownComponent({ label, childre
167
175
  onActiveChange?.(true);
168
176
  }
169
177
  else {
170
- const items = getMenuItems();
171
- if (items.length)
172
- items[items.length - 1].focus();
178
+ focusEdgeItem('last');
173
179
  pendingFocusRef.current = null;
174
180
  }
175
181
  break;
@@ -201,8 +207,10 @@ const DropdownComponent = forwardRef(function DropdownComponent({ label, childre
201
207
  }
202
208
  };
203
209
  const handleMenuKeyDown = (e) => {
204
- // Escape and Tab close the menu regardless of whether it has any focusable
205
- // items — an empty or all-disabled menu must still honor the close contract.
210
+ // Closing is Dropdown's: Escape and Tab close the menu from anywhere in
211
+ // it, a field included, and an empty or all-disabled menu too. Moving
212
+ // between the items, running them and typeahead are the hook's, which
213
+ // acts only while focus is on the menu or one of its items.
206
214
  if (e.key === 'Escape') {
207
215
  e.preventDefault();
208
216
  setActive(false);
@@ -215,66 +223,10 @@ const DropdownComponent = forwardRef(function DropdownComponent({ label, childre
215
223
  onActiveChange?.(false);
216
224
  return;
217
225
  }
218
- const items = getMenuItems();
219
- if (!items.length)
220
- return;
221
- const currentIndex = items.indexOf(getActiveElementInTree(e.currentTarget));
222
- switch (e.key) {
223
- case 'ArrowDown': {
224
- e.preventDefault();
225
- const next = currentIndex >= 0 ? (currentIndex + 1) % items.length : 0;
226
- items[next].focus();
227
- break;
228
- }
229
- case 'ArrowUp': {
230
- e.preventDefault();
231
- const prev = currentIndex >= 0
232
- ? (currentIndex - 1 + items.length) % items.length
233
- : items.length - 1;
234
- items[prev].focus();
235
- break;
236
- }
237
- case 'Home':
238
- e.preventDefault();
239
- items[0].focus();
240
- break;
241
- case 'End':
242
- e.preventDefault();
243
- items[items.length - 1].focus();
244
- break;
245
- case 'Enter':
246
- case ' ': {
247
- // Activate the focused item by clicking it, so its `onClick` runs and
248
- // closing follows `closeOnClick` as it does for the mouse. A caller's
249
- // own key handler that prevented the default has claimed the key, and
250
- // is left to it rather than followed by a second activation.
251
- if (currentIndex < 0 || e.defaultPrevented)
252
- break;
253
- const item = items[currentIndex];
254
- // Leave the browser's own activation alone, or the item runs twice: a
255
- // `<button>` answers both keys, and a link with an `href` answers
256
- // Enter. A link does not answer Space, so Space on a link is handled
257
- // here too, which also keeps the page from scrolling.
258
- if (item.tagName === 'BUTTON')
259
- break;
260
- if (e.key === 'Enter' &&
261
- item.tagName === 'A' &&
262
- item.hasAttribute('href')) {
263
- break;
264
- }
265
- e.preventDefault();
266
- // A held key sends a keydown per auto-repeat. Clicking on each would
267
- // toggle a checkbox item over and over while `closeOnClick` is off,
268
- // so only the first press activates. The default is still prevented
269
- // on the repeats above, so a held Space does not scroll the page.
270
- if (!e.repeat)
271
- item.click();
272
- break;
273
- }
274
- }
226
+ menuKeys.onKeyDown(e);
275
227
  };
276
228
  const dropdownClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
277
- return (jsxs("div", { className: dropdownClasses, ref: setRefs, id: id, "data-testid": "dropdown-root", ...rest, children: [jsx("div", { className: usePrefixedClassNames('dropdown-trigger'), children: jsxs("button", { ref: triggerRef, className: buttonClass, "aria-haspopup": "true", "aria-controls": id ? `${id}-menu` : undefined, "aria-expanded": active, onClick: handleToggle, onKeyDown: handleTriggerKeyDown, disabled: disabled, type: "button", children: [jsx("span", { children: label }), jsx("span", { className: usePrefixedClassNames('icon', 'is-small'), "aria-hidden": "true", children: jsx("i", { className: "fas fa-angle-down" }) })] }) }), jsx("div", { className: classNames(usePrefixedClassNames('dropdown-menu'), menuClassName), id: id ? `${id}-menu` : undefined, role: "menu", "data-testid": "dropdown-menu", onKeyDown: handleMenuKeyDown,
229
+ return (jsxs("div", { className: dropdownClasses, ref: setRefs, id: id, "data-testid": "dropdown-root", ...rest, children: [jsx("div", { className: usePrefixedClassNames('dropdown-trigger'), children: jsxs("button", { ref: triggerRef, className: buttonClass, "aria-haspopup": "true", "aria-controls": id ? `${id}-menu` : undefined, "aria-expanded": active, onClick: handleToggle, onKeyDown: handleTriggerKeyDown, disabled: disabled, type: "button", children: [jsx("span", { children: label }), jsx("span", { className: usePrefixedClassNames('icon', 'is-small'), "aria-hidden": "true", children: jsx("i", { className: "fas fa-angle-down" }) })] }) }), jsx("div", { className: classNames(usePrefixedClassNames('dropdown-menu'), menuClassName), id: id ? `${id}-menu` : undefined, ref: menuRef, role: "menu", "data-testid": "dropdown-menu", onKeyDown: handleMenuKeyDown,
278
230
  // The menu, not its content, takes focus from a click between items,
279
231
  // so the menu owns nothing focusable but its items.
280
232
  tabIndex: -1, children: jsx("div", { className: usePrefixedClassNames('dropdown-content'), onClick: handleMenuClick, children: children }) })] }));
@@ -1 +1 @@
1
- {"version":3,"file":"Dropdown.js","sources":["../../../src/components/Dropdown.tsx"],"sourcesContent":["import React, {\n forwardRef,\n useCallback,\n useState,\n useRef,\n useEffect,\n} from 'react';\nimport { classNames, usePrefixedClassNames } from '../helpers/classNames';\nimport { useBulmaClasses, BulmaClassesProps } from '../helpers/useBulmaClasses';\nimport { withSubComponents } from '../helpers/withSubComponents';\nimport { getActiveElementInTree, isEventInside } from '../helpers/shadowDom';\nimport type { ConstrainedPolymorphicComponentWithoutRef } from '../helpers/polymorphic';\nimport {\n type AnchorOnlyAttributes,\n ANCHOR_ONLY_ATTRS,\n omitAttrs,\n} from '../helpers/anchorAttrs';\nimport { buttonType } from '../helpers/buttonType';\n\n/**\n * Checks if code is running in a browser environment.\n * @deprecated Dropdown's internal SSR guard, exported by accident; it will\n * be removed in the next major. Check `typeof window !== 'undefined' &&\n * typeof document !== 'undefined'` instead.\n * @param win - Window object.\n * @param doc - Document object.\n * @returns {boolean} True if in browser, false otherwise.\n */\nexport const isBrowser = (win?: typeof window, doc?: typeof document) =>\n typeof win !== 'undefined' && typeof doc !== 'undefined';\n\n/**\n * Props for the Dropdown component.\n * @extraProp {React.Ref<HTMLDivElement>} [ref] - Ref forwarded to the root dropdown element.\n */\nexport interface DropdownProps\n extends\n Omit<React.HTMLAttributes<HTMLDivElement>, keyof BulmaClassesProps>,\n BulmaClassesProps {\n /** The dropdown button/trigger content. */\n label: React.ReactNode;\n /** Dropdown menu items and dividers. */\n children: React.ReactNode;\n /** Additional CSS classes for root. */\n className?: string;\n /** Additional CSS classes for the dropdown menu. */\n menuClassName?: string;\n /** Whether the dropdown is open (controlled). */\n active?: boolean;\n /** Dropdown menu opens upward. */\n up?: boolean;\n /** Menu is right-aligned. */\n right?: boolean;\n /** Open on hover instead of click. */\n hoverable?: boolean;\n /** Disables the dropdown trigger. */\n disabled?: boolean;\n /** Callback when dropdown active state changes. */\n onActiveChange?: (active: boolean) => void;\n /** Close dropdown when a menu item is clicked, or activated with Enter or Space. */\n closeOnClick?: boolean;\n /** Root element ID (for aria-controls, etc). */\n id?: string;\n}\n\n/**\n * The `Dropdown` component provides Bulma's versatile dropdown menu for your Bulma React UI.\n *\n * @function\n * @param {DropdownProps} props - Props for the Dropdown component.\n * @param {React.Ref<HTMLDivElement>} ref - Forwarded ref to the root dropdown element.\n * @returns {JSX.Element} The rendered dropdown.\n * @see {@link https://bulma.io/documentation/components/dropdown/ | Bulma Dropdown documentation}\n */\nconst DropdownComponent = forwardRef<HTMLDivElement, DropdownProps>(\n function DropdownComponent(\n {\n label,\n children,\n className,\n menuClassName,\n active: activeProp,\n up,\n right,\n hoverable,\n disabled,\n onActiveChange,\n closeOnClick = true,\n id,\n ...props\n },\n ref\n ) {\n const [active, setActive] = useState<boolean>(!!activeProp);\n const dropdownRef = useRef<HTMLDivElement>(null);\n const triggerRef = useRef<HTMLButtonElement>(null);\n const pendingFocusRef = useRef<'first' | 'last' | null>(null);\n\n // Cleanup returned by a consumer's callback ref on attach, held until detach.\n const consumerCleanupRef = useRef<(() => void) | null>(null);\n\n const setRefs = useCallback(\n (node: HTMLDivElement | null) => {\n (dropdownRef as React.MutableRefObject<HTMLDivElement | null>).current =\n node;\n if (typeof ref === 'function') {\n // React 19 lets a callback ref return a cleanup function and detaches\n // by running it instead of calling the ref with `null`; React 18\n // discards the return value entirely. Returning it from here would be\n // a React-19-only contract, so instead we hold the cleanup and run it\n // ourselves on detach — a consumer's cleanup ref then behaves the same\n // on both majors of the CI matrix.\n if (node === null) {\n const consumerCleanup = consumerCleanupRef.current;\n consumerCleanupRef.current = null;\n if (consumerCleanup) {\n consumerCleanup();\n } else {\n ref(null);\n }\n return;\n }\n const cleanup: unknown = ref(node);\n consumerCleanupRef.current =\n typeof cleanup === 'function' ? (cleanup as () => void) : null;\n } else if (ref) {\n (ref as React.MutableRefObject<HTMLDivElement | null>).current = node;\n }\n },\n [ref]\n );\n\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n\n // Generate Bulma classes with prefix\n const bulmaClasses = usePrefixedClassNames('dropdown', {\n 'is-active': active,\n 'is-up': up,\n 'is-right': right,\n 'is-hoverable': hoverable,\n 'is-disabled': disabled,\n });\n\n const buttonClass = usePrefixedClassNames('button');\n\n // Controlled mode support: mirror the controlled `active` prop into local\n // state. This is an intentional external-prop sync (controlled/uncontrolled\n // hybrid); behavior is covered by tests and certified in-browser.\n useEffect(() => {\n // eslint-disable-next-line react-hooks/set-state-in-effect -- syncing to controlled prop\n if (typeof activeProp === 'boolean') setActive(activeProp);\n }, [activeProp]);\n\n // SSR-safe outside click\n useEffect(() => {\n if (!active) return;\n\n if (!isBrowser(window, document)) return;\n\n const handleClick = (e: MouseEvent) => {\n if (!isEventInside(e, dropdownRef.current)) {\n setActive(false);\n onActiveChange?.(false);\n }\n };\n document.addEventListener('mousedown', handleClick);\n return () => document.removeEventListener('mousedown', handleClick);\n }, [active, onActiveChange]);\n\n const handleToggle = () => {\n /* istanbul ignore next: guard is enforced by button[disabled] at the DOM level */\n if (disabled) return;\n\n const newActive = !active;\n setActive(newActive);\n onActiveChange?.(newActive);\n };\n\n const handleMenuClick = (e: React.MouseEvent<HTMLDivElement>) => {\n if (closeOnClick) {\n setActive(false);\n onActiveChange?.(false);\n // Closing hides the item that has focus, and the browser drops focus\n // to the page when that happens. Hand it back to the trigger, as\n // Escape does. An item that moved focus somewhere else keeps it there.\n // The menu around this content is what a click between items focuses.\n const menu = e.currentTarget.parentElement as HTMLElement;\n if (menu.contains(getActiveElementInTree(e.currentTarget))) {\n triggerRef.current?.focus();\n }\n }\n };\n\n const getMenuItems = (): HTMLElement[] => {\n /* istanbul ignore next: dropdownRef.current is never null once mounted */\n if (!dropdownRef.current) return [];\n return Array.from(\n // Every menu-item role, so an item a caller gives `menuitemcheckbox`\n // or `menuitemradio` (a filter or sort menu) stays reachable.\n dropdownRef.current.querySelectorAll<HTMLElement>(\n '[role=\"menuitem\"], [role=\"menuitemcheckbox\"], [role=\"menuitemradio\"]'\n )\n ).filter(\n el =>\n !el.hasAttribute('disabled') &&\n el.getAttribute('aria-disabled') !== 'true'\n );\n };\n\n // Focus the pending menu item once the menu becomes visible; the item is\n // unfocusable while `display: none` applies, so this must wait for the\n // `is-active` class to actually land in the DOM.\n useEffect(() => {\n if (!active || !pendingFocusRef.current) return;\n const items = getMenuItems();\n if (items.length) {\n const index =\n pendingFocusRef.current === 'first' ? 0 : items.length - 1;\n items[index].focus();\n }\n pendingFocusRef.current = null;\n }, [active]);\n\n const handleTriggerKeyDown = (\n e: React.KeyboardEvent<HTMLButtonElement>\n ) => {\n if (disabled) return;\n switch (e.key) {\n case 'ArrowDown':\n e.preventDefault();\n pendingFocusRef.current = 'first';\n if (!active) {\n setActive(true);\n onActiveChange?.(true);\n } else {\n const items = getMenuItems();\n if (items.length) items[0].focus();\n pendingFocusRef.current = null;\n }\n break;\n case 'ArrowUp':\n e.preventDefault();\n pendingFocusRef.current = 'last';\n if (!active) {\n setActive(true);\n onActiveChange?.(true);\n } else {\n const items = getMenuItems();\n if (items.length) items[items.length - 1].focus();\n pendingFocusRef.current = null;\n }\n break;\n case 'Enter':\n case ' ':\n e.preventDefault();\n // A held key sends a keydown per auto-repeat. Running an item hands\n // focus back here, so without this a held Enter would reopen the\n // menu and run whichever item it focused next.\n if (e.repeat) break;\n if (!active) {\n pendingFocusRef.current = 'first';\n setActive(true);\n onActiveChange?.(true);\n } else {\n setActive(false);\n onActiveChange?.(false);\n }\n break;\n case 'Escape':\n if (active) {\n e.preventDefault();\n setActive(false);\n onActiveChange?.(false);\n }\n break;\n default:\n break;\n }\n };\n\n const handleMenuKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n // Escape and Tab close the menu regardless of whether it has any focusable\n // items — an empty or all-disabled menu must still honor the close contract.\n if (e.key === 'Escape') {\n e.preventDefault();\n setActive(false);\n onActiveChange?.(false);\n triggerRef.current?.focus();\n return;\n }\n if (e.key === 'Tab') {\n setActive(false);\n onActiveChange?.(false);\n return;\n }\n\n const items = getMenuItems();\n if (!items.length) return;\n const currentIndex = items.indexOf(\n getActiveElementInTree(e.currentTarget) as HTMLElement\n );\n switch (e.key) {\n case 'ArrowDown': {\n e.preventDefault();\n const next =\n currentIndex >= 0 ? (currentIndex + 1) % items.length : 0;\n items[next].focus();\n break;\n }\n case 'ArrowUp': {\n e.preventDefault();\n const prev =\n currentIndex >= 0\n ? (currentIndex - 1 + items.length) % items.length\n : items.length - 1;\n items[prev].focus();\n break;\n }\n case 'Home':\n e.preventDefault();\n items[0].focus();\n break;\n case 'End':\n e.preventDefault();\n items[items.length - 1].focus();\n break;\n case 'Enter':\n case ' ': {\n // Activate the focused item by clicking it, so its `onClick` runs and\n // closing follows `closeOnClick` as it does for the mouse. A caller's\n // own key handler that prevented the default has claimed the key, and\n // is left to it rather than followed by a second activation.\n if (currentIndex < 0 || e.defaultPrevented) break;\n const item = items[currentIndex];\n // Leave the browser's own activation alone, or the item runs twice: a\n // `<button>` answers both keys, and a link with an `href` answers\n // Enter. A link does not answer Space, so Space on a link is handled\n // here too, which also keeps the page from scrolling.\n if (item.tagName === 'BUTTON') break;\n if (\n e.key === 'Enter' &&\n item.tagName === 'A' &&\n item.hasAttribute('href')\n ) {\n break;\n }\n e.preventDefault();\n // A held key sends a keydown per auto-repeat. Clicking on each would\n // toggle a checkbox item over and over while `closeOnClick` is off,\n // so only the first press activates. The default is still prevented\n // on the repeats above, so a held Space does not scroll the page.\n if (!e.repeat) item.click();\n break;\n }\n default:\n break;\n }\n };\n\n const dropdownClasses = classNames(\n bulmaClasses,\n bulmaHelperClasses,\n className\n );\n\n return (\n <div\n className={dropdownClasses}\n ref={setRefs}\n id={id}\n data-testid=\"dropdown-root\"\n {...rest}\n >\n <div className={usePrefixedClassNames('dropdown-trigger')}>\n <button\n ref={triggerRef}\n className={buttonClass}\n aria-haspopup=\"true\"\n aria-controls={id ? `${id}-menu` : undefined}\n aria-expanded={active}\n onClick={handleToggle}\n onKeyDown={handleTriggerKeyDown}\n disabled={disabled}\n type=\"button\"\n >\n <span>{label}</span>\n <span\n className={usePrefixedClassNames('icon', 'is-small')}\n aria-hidden=\"true\"\n >\n <i className=\"fas fa-angle-down\" />\n </span>\n </button>\n </div>\n <div\n className={classNames(\n usePrefixedClassNames('dropdown-menu'),\n menuClassName\n )}\n id={id ? `${id}-menu` : undefined}\n role=\"menu\"\n data-testid=\"dropdown-menu\"\n onKeyDown={handleMenuKeyDown}\n // The menu, not its content, takes focus from a click between items,\n // so the menu owns nothing focusable but its items.\n tabIndex={-1}\n >\n <div\n className={usePrefixedClassNames('dropdown-content')}\n onClick={handleMenuClick}\n >\n {children}\n </div>\n </div>\n </div>\n );\n }\n);\n\n/**\n * The elements a dropdown item may render as. Bulma's dropdown markup names\n * these three and no others, so `as` is a closed set rather than an open\n * `React.ElementType`.\n */\nexport type DropdownItemElement = 'a' | 'div' | 'button';\n\n/**\n * The DropdownItem component's own props.\n */\nexport interface DropdownItemOwnProps extends BulmaClassesProps {\n /** Whether the item is active. */\n active?: boolean;\n /** Additional CSS classes. */\n className?: string;\n /** Marks the item as disabled; disabled items are skipped during keyboard navigation. Use with `as=\"button\"` for a native disabled control, or pair with `aria-disabled` on a link. */\n disabled?: boolean;\n /** Item content. */\n children?: React.ReactNode;\n}\n\n/**\n * Props for the DropdownItem component. Everything the item does not own\n * follows `as`: `href`, `target` and `rel` under the default `'a'`, `type` and\n * `form` under `'button'`, and the shared HTML attributes under any of them.\n *\n * Pinning these to `React.HTMLAttributes<HTMLElement>` instead — which is what\n * this type did before — rejected `href` on an anchor and `type` on a button,\n * both of which have always worked at runtime. Same defect as #641, in the\n * narrower shape a constrained `as` takes.\n *\n * **Name the tag you mean.** The type parameter defaults to the whole union, not\n * to the rendered element, so bare `DropdownItemProps` keeps accepting\n * `{ as: 'div' }` the way it always has. The cost is that it is not\n * distributive: `Omit` over a union keeps only the shared keys, so the bare\n * alias carries no `href` even though the component takes one under `as=\"a\"`.\n * Write `DropdownItemProps<'a'>` for the anchor's props. That is the #667 alias\n * limitation Button carries, labelled next-major because closing it is\n * source-breaking, and pinned for this component in\n * `__typetests__/polymorphic.tsx`; the component itself is unaffected.\n */\nexport type DropdownItemProps<\n T extends DropdownItemElement = DropdownItemElement,\n> = DropdownItemOwnProps &\n Omit<React.ComponentPropsWithoutRef<T>, keyof DropdownItemOwnProps | 'as'> & {\n /**\n * The element type to render.\n *\n * `'button'` renders `type=\"button\"` unless you pass `type=\"submit\"` or `type=\"reset\"`, so\n * an item inside a form does not submit it.\n */\n as?: T;\n };\n\n/**\n * The anchor's attributes, minus the one a `<button>` legitimately takes.\n *\n * `type` stays: `as=\"button\"` is a supported form and `type=\"submit\"` is valid\n * there, so stripping it would remove a working attribute. A `<div>` takes no\n * `type`, so it gets the whole `ANCHOR_ONLY_ATTRS` set instead. The set is\n * chosen by the rendered tag because that is where the reason lives (#692).\n *\n * Neither set withholds `rel`, which `Level.Item` does: React declares `rel` on\n * `HTMLAttributes` for every element, so withholding it would diverge from\n * React's own typing, the call #641 recorded for `Navbar.Link`. Level\n * withholds it anyway, because it always has.\n */\nconst STRIP_FROM_BUTTON: Readonly<\n Record<Exclude<keyof AnchorOnlyAttributes, 'type'>, true>\n> = (() => {\n const { type: _type, ...rest } = ANCHOR_ONLY_ATTRS;\n return rest;\n})();\n\n/**\n * The shape the implementation destructures. The public contract is the generic\n * `DropdownItemProps<T>` above — the body cannot see through `T`.\n */\ntype DropdownItemImplProps = DropdownItemOwnProps & {\n as?: DropdownItemElement;\n};\n\n/**\n * Bulma Dropdown item.\n *\n * @function\n * @param {DropdownItemProps} props - Props for the DropdownItem component.\n * @returns {JSX.Element} The rendered dropdown item.\n */\nexport const DropdownItem = ((itemProps: DropdownItemProps) => {\n const {\n children,\n active,\n className,\n as: Component = 'a',\n ...props\n } = itemProps as DropdownItemImplProps;\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n // The anchor's attributes reach an anchor and nothing else — `<div href>` and\n // `<button target>` are invalid HTML. The same rule Menu applies, for the same\n // reason: the type now derives these from `as`, and deriving them must not\n // quietly widen where they land. The type stops a direct caller; this stops a\n // plain-JS one, a loose `{...props}` spread, and the genericity a wrapping HOC\n // erases.\n //\n // The whole link set, not `href` alone: they all arrive together under\n // `as=\"a\"`. Menu strips `href` only, because its `as` is open and a flat set\n // would delete attributes legal on the element a caller named.\n //\n // `bestax-migrate` tracks the same question from the other side, per attribute\n // per element (`LINK_ATTR_ELEMENTS`) rather than as one set — so the two lists\n // are not interchangeable and neither derives from the other.\n //\n // Menu's condition also admits a custom component and a custom element, which\n // own their prop contracts. Here the set follows the tag: an `<a>` keeps all\n // of them, a `<button>` keeps `type`, and any other `as` keeps none of them.\n const forwarded =\n Component === 'a'\n ? rest\n : Component === 'button'\n ? omitAttrs(rest, STRIP_FROM_BUTTON)\n : omitAttrs(rest, ANCHOR_ONLY_ATTRS);\n return (\n <Component\n className={classNames(\n usePrefixedClassNames('dropdown-item', {\n 'is-active': active,\n }),\n bulmaHelperClasses,\n className\n )}\n data-testid=\"dropdown-item\"\n {...forwarded}\n // After `forwarded` for the same reason as `role` and `type` below: a\n // spread carrying `tabIndex: undefined` would otherwise erase the\n // default. The item keeps its menu role and its place in the arrow-key\n // order, but a `<div>` or an anchor without an `href` cannot take focus\n // without a tabindex, so the arrow keys stall on it.\n tabIndex={(forwarded as { tabIndex?: number }).tabIndex ?? 0}\n // After `forwarded` for the same reason as `type` below: a spread\n // carrying `role: undefined` would otherwise erase the default, and an\n // item with no role drops out of the menu and its arrow-key order.\n role={(forwarded as { role?: React.AriaRole }).role ?? 'menuitem'}\n // A menu item inside a form must not submit it. `<button>` defaults to\n // type=\"submit\", and a filter or sort menu sitting in a form is ordinary.\n // Dropdown's own trigger sets it, and `Avatar` and `Menu.Item` default it\n // the same way. A caller's `submit` or `reset` still wins, and anything\n // else becomes `button`, since HTML reads a value it does not define for\n // a button as submit too; `buttonType` says more.\n //\n // After `forwarded`, reading through it rather than before it: React\n // treats `type={undefined}` as \"remove the attribute\", and a spread\n // carrying an absent key is how that arrives. Spreading the default first\n // let such a spread erase it and restore the submit behaviour, so the\n // guard only held for callers who passed nothing.\n {...(Component === 'button'\n ? { type: buttonType((forwarded as { type?: unknown }).type) }\n : {})}\n >\n {children}\n </Component>\n );\n}) as ConstrainedPolymorphicComponentWithoutRef<\n DropdownItemOwnProps,\n DropdownItemElement,\n 'a'\n>;\n\n/**\n * Bulma Dropdown divider.\n *\n * @function\n * @returns {JSX.Element} The rendered divider element.\n */\nexport const DropdownDivider: React.FC = () => (\n <hr className={usePrefixedClassNames('dropdown-divider')} />\n);\n\n/** Bulma Dropdown component with Item and Divider sub-components. */\nexport const Dropdown = withSubComponents(\n DropdownComponent,\n {\n Item: DropdownItem,\n Divider: DropdownDivider,\n },\n 'Dropdown'\n);\n\nexport default Dropdown;\n"],"names":["isBrowser","_jsxs","_jsx"],"mappings":"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"}
1
+ {"version":3,"file":"Dropdown.js","sources":["../../../src/components/Dropdown.tsx"],"sourcesContent":["import React, {\n forwardRef,\n useCallback,\n useState,\n useRef,\n useEffect,\n} from 'react';\nimport { classNames, usePrefixedClassNames } from '../helpers/classNames';\nimport { useBulmaClasses, BulmaClassesProps } from '../helpers/useBulmaClasses';\nimport { withSubComponents } from '../helpers/withSubComponents';\nimport { getActiveElementInTree, isEventInside } from '../helpers/shadowDom';\nimport type { ConstrainedPolymorphicComponentWithoutRef } from '../helpers/polymorphic';\nimport {\n type AnchorOnlyAttributes,\n ANCHOR_ONLY_ATTRS,\n omitAttrs,\n} from '../helpers/anchorAttrs';\nimport { buttonType } from '../helpers/buttonType';\nimport { enabledMenuItems, useMenuKeys } from '../helpers/useMenuKeys';\n\n/**\n * Checks if code is running in a browser environment.\n * @deprecated Dropdown's internal SSR guard, exported by accident; it will\n * be removed in the next major. Check `typeof window !== 'undefined' &&\n * typeof document !== 'undefined'` instead.\n * @param win - Window object.\n * @param doc - Document object.\n * @returns {boolean} True if in browser, false otherwise.\n */\nexport const isBrowser = (win?: typeof window, doc?: typeof document) =>\n typeof win !== 'undefined' && typeof doc !== 'undefined';\n\n/**\n * Props for the Dropdown component.\n * @extraProp {React.Ref<HTMLDivElement>} [ref] - Ref forwarded to the root dropdown element.\n */\nexport interface DropdownProps\n extends\n Omit<React.HTMLAttributes<HTMLDivElement>, keyof BulmaClassesProps>,\n BulmaClassesProps {\n /** The dropdown button/trigger content. */\n label: React.ReactNode;\n /** Dropdown menu items and dividers. */\n children: React.ReactNode;\n /** Additional CSS classes for root. */\n className?: string;\n /** Additional CSS classes for the dropdown menu. */\n menuClassName?: string;\n /** Whether the dropdown is open (controlled). */\n active?: boolean;\n /** Dropdown menu opens upward. */\n up?: boolean;\n /** Menu is right-aligned. */\n right?: boolean;\n /** Open on hover instead of click. */\n hoverable?: boolean;\n /** Disables the dropdown trigger. */\n disabled?: boolean;\n /** Callback when dropdown active state changes. */\n onActiveChange?: (active: boolean) => void;\n /** Close dropdown when a menu item is clicked, or activated with Enter or Space. */\n closeOnClick?: boolean;\n /** Root element ID (for aria-controls, etc). */\n id?: string;\n}\n\n/**\n * The `Dropdown` component provides Bulma's versatile dropdown menu for your Bulma React UI.\n *\n * Down, Enter and Space on the trigger open the menu on its first enabled\n * item, and Up opens it on its last. On the trigger of a menu that's already\n * open, Down and Up move focus to its first and last item. In the menu, the\n * arrow keys, Home and End move between the items, and Enter and Space run\n * the focused one the way a click does. Typing a letter moves to the next\n * item whose text starts with it, skipping disabled ones, and letters typed\n * in quick succession add up to one search. Typing searches once focus is in\n * the menu, so a letter typed on the trigger does nothing. Escape closes the\n * menu and focuses the trigger, and Tab closes it and lets focus move on.\n * Anything focusable in the menu that isn't an item, such as a field, keeps\n * its own keys apart from Escape and Tab. For a panel built around fields,\n * use `Popover`.\n *\n * @function\n * @param {DropdownProps} props - Props for the Dropdown component.\n * @param {React.Ref<HTMLDivElement>} ref - Forwarded ref to the root dropdown element.\n * @returns {JSX.Element} The rendered dropdown.\n * @see {@link https://bulma.io/documentation/components/dropdown/ | Bulma Dropdown documentation}\n */\nconst DropdownComponent = forwardRef<HTMLDivElement, DropdownProps>(\n function DropdownComponent(\n {\n label,\n children,\n className,\n menuClassName,\n active: activeProp,\n up,\n right,\n hoverable,\n disabled,\n onActiveChange,\n closeOnClick = true,\n id,\n ...props\n },\n ref\n ) {\n const [active, setActive] = useState<boolean>(!!activeProp);\n const dropdownRef = useRef<HTMLDivElement>(null);\n const triggerRef = useRef<HTMLButtonElement>(null);\n const menuRef = useRef<HTMLDivElement>(null);\n const menuKeys = useMenuKeys();\n const pendingFocusRef = useRef<'first' | 'last' | null>(null);\n\n // Cleanup returned by a consumer's callback ref on attach, held until detach.\n const consumerCleanupRef = useRef<(() => void) | null>(null);\n\n const setRefs = useCallback(\n (node: HTMLDivElement | null) => {\n (dropdownRef as React.MutableRefObject<HTMLDivElement | null>).current =\n node;\n if (typeof ref === 'function') {\n // React 19 lets a callback ref return a cleanup function and detaches\n // by running it instead of calling the ref with `null`; React 18\n // discards the return value entirely. Returning it from here would be\n // a React-19-only contract, so instead we hold the cleanup and run it\n // ourselves on detach — a consumer's cleanup ref then behaves the same\n // on both majors of the CI matrix.\n if (node === null) {\n const consumerCleanup = consumerCleanupRef.current;\n consumerCleanupRef.current = null;\n if (consumerCleanup) {\n consumerCleanup();\n } else {\n ref(null);\n }\n return;\n }\n const cleanup: unknown = ref(node);\n consumerCleanupRef.current =\n typeof cleanup === 'function' ? (cleanup as () => void) : null;\n } else if (ref) {\n (ref as React.MutableRefObject<HTMLDivElement | null>).current = node;\n }\n },\n [ref]\n );\n\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n\n // Generate Bulma classes with prefix\n const bulmaClasses = usePrefixedClassNames('dropdown', {\n 'is-active': active,\n 'is-up': up,\n 'is-right': right,\n 'is-hoverable': hoverable,\n 'is-disabled': disabled,\n });\n\n const buttonClass = usePrefixedClassNames('button');\n\n // Controlled mode support: mirror the controlled `active` prop into local\n // state. This is an intentional external-prop sync (controlled/uncontrolled\n // hybrid); behavior is covered by tests and certified in-browser.\n useEffect(() => {\n // eslint-disable-next-line react-hooks/set-state-in-effect -- syncing to controlled prop\n if (typeof activeProp === 'boolean') setActive(activeProp);\n }, [activeProp]);\n\n // SSR-safe outside click\n useEffect(() => {\n if (!active) return;\n\n if (!isBrowser(window, document)) return;\n\n const handleClick = (e: MouseEvent) => {\n if (!isEventInside(e, dropdownRef.current)) {\n setActive(false);\n onActiveChange?.(false);\n }\n };\n document.addEventListener('mousedown', handleClick);\n return () => document.removeEventListener('mousedown', handleClick);\n }, [active, onActiveChange]);\n\n const handleToggle = () => {\n /* istanbul ignore next: guard is enforced by button[disabled] at the DOM level */\n if (disabled) return;\n\n const newActive = !active;\n setActive(newActive);\n onActiveChange?.(newActive);\n };\n\n const handleMenuClick = (e: React.MouseEvent<HTMLDivElement>) => {\n if (closeOnClick) {\n setActive(false);\n onActiveChange?.(false);\n // Closing hides the item that has focus, and the browser drops focus\n // to the page when that happens. Hand it back to the trigger, as\n // Escape does. An item that moved focus somewhere else keeps it there.\n // The menu around this content is what a click between items focuses.\n const menu = e.currentTarget.parentElement as HTMLElement;\n if (menu.contains(getActiveElementInTree(e.currentTarget))) {\n triggerRef.current?.focus();\n }\n }\n };\n\n const focusEdgeItem = (edge: 'first' | 'last') => {\n const items = enabledMenuItems(menuRef.current as HTMLElement);\n items[edge === 'first' ? 0 : items.length - 1]?.focus();\n };\n\n // Once the menu shows, forget a search typed just before it last closed,\n // and focus the pending item. The item is unfocusable while\n // `display: none` applies, so this must wait for the `is-active` class to\n // actually land in the DOM.\n useEffect(() => {\n if (!active) return;\n menuKeys.reset();\n if (!pendingFocusRef.current) return;\n focusEdgeItem(pendingFocusRef.current);\n pendingFocusRef.current = null;\n }, [active, menuKeys]);\n\n const handleTriggerKeyDown = (\n e: React.KeyboardEvent<HTMLButtonElement>\n ) => {\n if (disabled) return;\n switch (e.key) {\n case 'ArrowDown':\n e.preventDefault();\n pendingFocusRef.current = 'first';\n if (!active) {\n setActive(true);\n onActiveChange?.(true);\n } else {\n focusEdgeItem('first');\n pendingFocusRef.current = null;\n }\n break;\n case 'ArrowUp':\n e.preventDefault();\n pendingFocusRef.current = 'last';\n if (!active) {\n setActive(true);\n onActiveChange?.(true);\n } else {\n focusEdgeItem('last');\n pendingFocusRef.current = null;\n }\n break;\n case 'Enter':\n case ' ':\n e.preventDefault();\n // A held key sends a keydown per auto-repeat. Running an item hands\n // focus back here, so without this a held Enter would reopen the\n // menu and run whichever item it focused next.\n if (e.repeat) break;\n if (!active) {\n pendingFocusRef.current = 'first';\n setActive(true);\n onActiveChange?.(true);\n } else {\n setActive(false);\n onActiveChange?.(false);\n }\n break;\n case 'Escape':\n if (active) {\n e.preventDefault();\n setActive(false);\n onActiveChange?.(false);\n }\n break;\n default:\n break;\n }\n };\n\n const handleMenuKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n // Closing is Dropdown's: Escape and Tab close the menu from anywhere in\n // it, a field included, and an empty or all-disabled menu too. Moving\n // between the items, running them and typeahead are the hook's, which\n // acts only while focus is on the menu or one of its items.\n if (e.key === 'Escape') {\n e.preventDefault();\n setActive(false);\n onActiveChange?.(false);\n triggerRef.current?.focus();\n return;\n }\n if (e.key === 'Tab') {\n setActive(false);\n onActiveChange?.(false);\n return;\n }\n\n menuKeys.onKeyDown(e);\n };\n\n const dropdownClasses = classNames(\n bulmaClasses,\n bulmaHelperClasses,\n className\n );\n\n return (\n <div\n className={dropdownClasses}\n ref={setRefs}\n id={id}\n data-testid=\"dropdown-root\"\n {...rest}\n >\n <div className={usePrefixedClassNames('dropdown-trigger')}>\n <button\n ref={triggerRef}\n className={buttonClass}\n aria-haspopup=\"true\"\n aria-controls={id ? `${id}-menu` : undefined}\n aria-expanded={active}\n onClick={handleToggle}\n onKeyDown={handleTriggerKeyDown}\n disabled={disabled}\n type=\"button\"\n >\n <span>{label}</span>\n <span\n className={usePrefixedClassNames('icon', 'is-small')}\n aria-hidden=\"true\"\n >\n <i className=\"fas fa-angle-down\" />\n </span>\n </button>\n </div>\n <div\n className={classNames(\n usePrefixedClassNames('dropdown-menu'),\n menuClassName\n )}\n id={id ? `${id}-menu` : undefined}\n ref={menuRef}\n role=\"menu\"\n data-testid=\"dropdown-menu\"\n onKeyDown={handleMenuKeyDown}\n // The menu, not its content, takes focus from a click between items,\n // so the menu owns nothing focusable but its items.\n tabIndex={-1}\n >\n <div\n className={usePrefixedClassNames('dropdown-content')}\n onClick={handleMenuClick}\n >\n {children}\n </div>\n </div>\n </div>\n );\n }\n);\n\n/**\n * The elements a dropdown item may render as. Bulma's dropdown markup names\n * these three and no others, so `as` is a closed set rather than an open\n * `React.ElementType`.\n */\nexport type DropdownItemElement = 'a' | 'div' | 'button';\n\n/**\n * The DropdownItem component's own props.\n */\nexport interface DropdownItemOwnProps extends BulmaClassesProps {\n /** Whether the item is active. */\n active?: boolean;\n /** Additional CSS classes. */\n className?: string;\n /** Marks the item as disabled; disabled items are skipped during keyboard navigation. Use with `as=\"button\"` for a native disabled control, or pair with `aria-disabled` on a link. */\n disabled?: boolean;\n /** Item content. */\n children?: React.ReactNode;\n}\n\n/**\n * Props for the DropdownItem component. Everything the item does not own\n * follows `as`: `href`, `target` and `rel` under the default `'a'`, `type` and\n * `form` under `'button'`, and the shared HTML attributes under any of them.\n *\n * Pinning these to `React.HTMLAttributes<HTMLElement>` instead — which is what\n * this type did before — rejected `href` on an anchor and `type` on a button,\n * both of which have always worked at runtime. Same defect as #641, in the\n * narrower shape a constrained `as` takes.\n *\n * **Name the tag you mean.** The type parameter defaults to the whole union, not\n * to the rendered element, so bare `DropdownItemProps` keeps accepting\n * `{ as: 'div' }` the way it always has. The cost is that it is not\n * distributive: `Omit` over a union keeps only the shared keys, so the bare\n * alias carries no `href` even though the component takes one under `as=\"a\"`.\n * Write `DropdownItemProps<'a'>` for the anchor's props. That is the #667 alias\n * limitation Button carries, labelled next-major because closing it is\n * source-breaking, and pinned for this component in\n * `__typetests__/polymorphic.tsx`; the component itself is unaffected.\n */\nexport type DropdownItemProps<\n T extends DropdownItemElement = DropdownItemElement,\n> = DropdownItemOwnProps &\n Omit<React.ComponentPropsWithoutRef<T>, keyof DropdownItemOwnProps | 'as'> & {\n /**\n * The element type to render.\n *\n * `'button'` renders `type=\"button\"` unless you pass `type=\"submit\"` or `type=\"reset\"`, so\n * an item inside a form does not submit it.\n */\n as?: T;\n };\n\n/**\n * The anchor's attributes, minus the one a `<button>` legitimately takes.\n *\n * `type` stays: `as=\"button\"` is a supported form and `type=\"submit\"` is valid\n * there, so stripping it would remove a working attribute. A `<div>` takes no\n * `type`, so it gets the whole `ANCHOR_ONLY_ATTRS` set instead. The set is\n * chosen by the rendered tag because that is where the reason lives (#692).\n *\n * Neither set withholds `rel`, which `Level.Item` does: React declares `rel` on\n * `HTMLAttributes` for every element, so withholding it would diverge from\n * React's own typing, the call #641 recorded for `Navbar.Link`. Level\n * withholds it anyway, because it always has.\n */\nconst STRIP_FROM_BUTTON: Readonly<\n Record<Exclude<keyof AnchorOnlyAttributes, 'type'>, true>\n> = (() => {\n const { type: _type, ...rest } = ANCHOR_ONLY_ATTRS;\n return rest;\n})();\n\n/**\n * The shape the implementation destructures. The public contract is the generic\n * `DropdownItemProps<T>` above — the body cannot see through `T`.\n */\ntype DropdownItemImplProps = DropdownItemOwnProps & {\n as?: DropdownItemElement;\n};\n\n/**\n * Bulma Dropdown item.\n *\n * @function\n * @param {DropdownItemProps} props - Props for the DropdownItem component.\n * @returns {JSX.Element} The rendered dropdown item.\n */\nexport const DropdownItem = ((itemProps: DropdownItemProps) => {\n const {\n children,\n active,\n className,\n as: Component = 'a',\n ...props\n } = itemProps as DropdownItemImplProps;\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n // The anchor's attributes reach an anchor and nothing else — `<div href>` and\n // `<button target>` are invalid HTML. The same rule Menu applies, for the same\n // reason: the type now derives these from `as`, and deriving them must not\n // quietly widen where they land. The type stops a direct caller; this stops a\n // plain-JS one, a loose `{...props}` spread, and the genericity a wrapping HOC\n // erases.\n //\n // The whole link set, not `href` alone: they all arrive together under\n // `as=\"a\"`. Menu strips `href` only, because its `as` is open and a flat set\n // would delete attributes legal on the element a caller named.\n //\n // `bestax-migrate` tracks the same question from the other side, per attribute\n // per element (`LINK_ATTR_ELEMENTS`) rather than as one set — so the two lists\n // are not interchangeable and neither derives from the other.\n //\n // Menu's condition also admits a custom component and a custom element, which\n // own their prop contracts. Here the set follows the tag: an `<a>` keeps all\n // of them, a `<button>` keeps `type`, and any other `as` keeps none of them.\n const forwarded =\n Component === 'a'\n ? rest\n : Component === 'button'\n ? omitAttrs(rest, STRIP_FROM_BUTTON)\n : omitAttrs(rest, ANCHOR_ONLY_ATTRS);\n return (\n <Component\n className={classNames(\n usePrefixedClassNames('dropdown-item', {\n 'is-active': active,\n }),\n bulmaHelperClasses,\n className\n )}\n data-testid=\"dropdown-item\"\n {...forwarded}\n // After `forwarded` for the same reason as `role` and `type` below: a\n // spread carrying `tabIndex: undefined` would otherwise erase the\n // default. The item keeps its menu role and its place in the arrow-key\n // order, but a `<div>` or an anchor without an `href` cannot take focus\n // without a tabindex, so the arrow keys stall on it.\n tabIndex={(forwarded as { tabIndex?: number }).tabIndex ?? 0}\n // After `forwarded` for the same reason as `type` below: a spread\n // carrying `role: undefined` would otherwise erase the default, and an\n // item with no role drops out of the menu and its arrow-key order.\n role={(forwarded as { role?: React.AriaRole }).role ?? 'menuitem'}\n // A menu item inside a form must not submit it. `<button>` defaults to\n // type=\"submit\", and a filter or sort menu sitting in a form is ordinary.\n // Dropdown's own trigger sets it, and `Avatar` and `Menu.Item` default it\n // the same way. A caller's `submit` or `reset` still wins, and anything\n // else becomes `button`, since HTML reads a value it does not define for\n // a button as submit too; `buttonType` says more.\n //\n // After `forwarded`, reading through it rather than before it: React\n // treats `type={undefined}` as \"remove the attribute\", and a spread\n // carrying an absent key is how that arrives. Spreading the default first\n // let such a spread erase it and restore the submit behaviour, so the\n // guard only held for callers who passed nothing.\n {...(Component === 'button'\n ? { type: buttonType((forwarded as { type?: unknown }).type) }\n : {})}\n >\n {children}\n </Component>\n );\n}) as ConstrainedPolymorphicComponentWithoutRef<\n DropdownItemOwnProps,\n DropdownItemElement,\n 'a'\n>;\n\n/**\n * Bulma Dropdown divider.\n *\n * @function\n * @returns {JSX.Element} The rendered divider element.\n */\nexport const DropdownDivider: React.FC = () => (\n <hr className={usePrefixedClassNames('dropdown-divider')} />\n);\n\n/** Bulma Dropdown component with Item and Divider sub-components. */\nexport const Dropdown = withSubComponents(\n DropdownComponent,\n {\n Item: DropdownItem,\n Divider: DropdownDivider,\n },\n 'Dropdown'\n);\n\nexport default Dropdown;\n"],"names":["isBrowser","_jsxs","_jsx"],"mappings":"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"}
@@ -1,10 +1,25 @@
1
1
  /* @allxsmith/bestax-bulma — AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */
2
2
  import { jsxs, jsx } from 'react/jsx-runtime';
3
- import { useState, useRef, useLayoutEffect } from 'react';
3
+ import { useState, useRef, useId, isValidElement, Fragment, useEffect, useLayoutEffect, cloneElement } from 'react';
4
4
  import { usePrefixedClassNames, classNames } from '../helpers/classNames.js';
5
5
  import { useBulmaClasses } from '../helpers/useBulmaClasses.js';
6
6
  import { useHoverOpen, chain } from '../helpers/useHoverOpen.js';
7
+ import { isTabbableKind } from '../helpers/useFocusTrap.js';
8
+ import { isDev, warnOnce } from '../helpers/devWarnings.js';
7
9
 
10
+ /** An id list with `id` added at the end, unless it's in there already. */
11
+ const withId = (ids, id) => [...new Set([...(ids ?? '').split(/\s+/), id])].filter(Boolean).join(' ');
12
+ /**
13
+ * The first element in the trigger that Tab lands on, judged by what it is
14
+ * rather than by its styles, which can hide it for now. The trigger is all
15
+ * of the wrapper that comes before the tooltip.
16
+ */
17
+ const triggerTabStop = (wrapper, tooltip) => {
18
+ const elements = Array.from(wrapper.querySelectorAll('*'));
19
+ return elements
20
+ .slice(0, elements.indexOf(tooltip))
21
+ .find(el => isTabbableKind(el) && !el.matches(':disabled'));
22
+ };
8
23
  /**
9
24
  * The `Tooltip` component displays helpful information when users hover over or focus on an element.
10
25
  *
@@ -16,7 +31,8 @@ import { useHoverOpen, chain } from '../helpers/useHoverOpen.js';
16
31
  * the trigger and comes back, or, with `openOnTap`, until the next tap on
17
32
  * the trigger. The key always goes on too, to whatever has focus or a
18
33
  * `Modal` or `Popover` around it, since a tooltip only adds to what's
19
- * there.
34
+ * there. The tooltip is its trigger's description, so a screen reader reads
35
+ * it as the trigger takes focus, whether it's showing yet or not.
20
36
  *
21
37
  * @function
22
38
  * @param {TooltipProps} props - Props for the Tooltip component.
@@ -29,15 +45,17 @@ import { useHoverOpen, chain } from '../helpers/useHoverOpen.js';
29
45
  * </Tooltip>
30
46
  *
31
47
  * @example
32
- * // Positioned tooltip with color
33
- * <Tooltip label="Delete item" position="right" color="danger">
34
- * <Icon icon="fas fa-trash" />
48
+ * // Positioned tooltip with color, on an icon-only button with a name of its own
49
+ * <Tooltip label="Moves it to the trash" position="right" color="danger">
50
+ * <Button aria-label="Delete">
51
+ * <Icon name="fas fa-trash" />
52
+ * </Button>
35
53
  * </Tooltip>
36
54
  *
37
55
  * @example
38
56
  * // Multiline tooltip
39
57
  * <Tooltip label="This is a longer tooltip that wraps to multiple lines" multiline>
40
- * <span>?</span>
58
+ * <button>More info</button>
41
59
  * </Tooltip>
42
60
  */
43
61
  const Tooltip = ({ label, content, position = 'top', color, size, active: alwaysActive, multiline = false, animated = true, square = false, dashed = false, delay = 0, closeDelay = 0, openOnTap = false, children, className, tooltipClassName, ...props }) => {
@@ -48,6 +66,30 @@ const Tooltip = ({ label, content, position = 'top', color, size, active: always
48
66
  const [contentStyle, setContentStyle] = useState({});
49
67
  const wrapperRef = useRef(null);
50
68
  const contentRef = useRef(null);
69
+ const tooltipId = `${useId()}-tooltip`;
70
+ // Only a single element can be given the link, and a fragment takes no
71
+ // props of its own.
72
+ const linkable = isValidElement(children) &&
73
+ children.type !== Fragment;
74
+ // A screen reader reads the tooltip only when Tab lands on an element that
75
+ // carries the link. Only the page shows whether it does, since a component
76
+ // may not pass the attribute on, or what takes focus may sit inside the
77
+ // element that took it. After every commit, since the children can change,
78
+ // and only in development.
79
+ useEffect(() => {
80
+ if (!isDev())
81
+ return;
82
+ const stop = triggerTabStop(wrapperRef.current, contentRef.current);
83
+ if (stop?.getAttribute('aria-describedby')?.split(/\s+/).includes(tooltipId)) {
84
+ return;
85
+ }
86
+ warnOnce('Tooltip:unlinked-trigger', "[bestax-bulma] <Tooltip> can't describe its trigger, so a screen " +
87
+ "reader won't read the tooltip: Tab reaches nothing in the trigger, " +
88
+ "or what it reaches doesn't carry the aria-describedby that points " +
89
+ 'at the tooltip. Pass one element that takes focus and passes ' +
90
+ 'aria-describedby on to its DOM node, such as a button, a link or a ' +
91
+ 'Span with tabIndex={0}.');
92
+ });
51
93
  const hover = useHoverOpen({
52
94
  // The pointer and focus as they are. `active` shows it on top of that, so
53
95
  // dropping `active` leaves it shown while the pointer or focus holds it.
@@ -170,7 +212,15 @@ const Tooltip = ({ label, content, position = 'top', color, size, active: always
170
212
  });
171
213
  const combinedClasses = classNames(tooltipClasses, bulmaHelperClasses, className);
172
214
  const combinedTooltipClasses = usePrefixedClassNames('tooltip-content', tooltipClassName);
173
- return (jsxs("span", { ref: wrapperRef, className: combinedClasses, onPointerEnter: chain(onPointerEnter, hover.onPointerEnter), onPointerOver: chain(onPointerOver, hover.onPointerOver), onPointerLeave: chain(onPointerLeave, hover.onPointerLeave), onFocus: chain(onFocus, hover.onFocus), onBlur: chain(onBlur, hover.onBlur), onPointerDown: chain(onPointerDown, hover.onPointerDown), onPointerUp: chain(onPointerUp, hover.onPointerUp), ...(label ? { 'data-tooltip': label } : {}), ...attrs, children: [children, jsx("span", { ref: contentRef, className: combinedTooltipClasses, role: "tooltip", "aria-hidden": !isVisible && !alwaysActive, style: Object.keys(contentStyle).length > 0 ? contentStyle : undefined, children: content || label })] }));
215
+ return (jsxs("span", { ref: wrapperRef, className: combinedClasses, onPointerEnter: chain(onPointerEnter, hover.onPointerEnter), onPointerOver: chain(onPointerOver, hover.onPointerOver), onPointerLeave: chain(onPointerLeave, hover.onPointerLeave), onFocus: chain(onFocus, hover.onFocus), onBlur: chain(onBlur, hover.onBlur), onPointerDown: chain(onPointerDown, hover.onPointerDown), onPointerUp: chain(onPointerUp, hover.onPointerUp), ...(label ? { 'data-tooltip': label } : {}), ...attrs, children: [linkable
216
+ ? cloneElement(children, {
217
+ 'aria-describedby': withId(children.props['aria-describedby'], tooltipId),
218
+ })
219
+ : children, jsx("span", { ref: contentRef, id: tooltipId, className: combinedTooltipClasses,
220
+ // Hidden by `visibility: hidden` in the stylesheet, which keeps it out
221
+ // of the accessibility tree and still lets it describe the trigger
222
+ // (accname step 2A), so it takes no aria-hidden.
223
+ role: "tooltip", style: Object.keys(contentStyle).length > 0 ? contentStyle : undefined, children: content || label })] }));
174
224
  };
175
225
 
176
226
  export { Tooltip };
@@ -1 +1 @@
1
- {"version":3,"file":"Tooltip.js","sources":["../../../src/components/Tooltip.tsx"],"sourcesContent":["import React, { useState, useRef, useLayoutEffect } from 'react';\nimport { classNames, usePrefixedClassNames } from '../helpers/classNames';\nimport { useBulmaClasses, BulmaClassesProps } from '../helpers/useBulmaClasses';\nimport { chain, useHoverOpen } from '../helpers/useHoverOpen';\n\n/** Screen positions for the tooltip. */\nexport type TooltipPosition = 'top' | 'bottom' | 'left' | 'right' | 'auto';\n/** Color variants for the tooltip. */\nexport type TooltipColor =\n | 'primary'\n | 'link'\n | 'info'\n | 'success'\n | 'warning'\n | 'danger'\n | 'dark'\n | 'light';\n/** Size variants for the tooltip. */\nexport type TooltipSize = 'small' | 'medium' | 'large';\n\n/**\n * Props for the Tooltip component.\n * @extraProp {React.ReactNode} [children] - The element that triggers the tooltip.\n * @extraProp {string} [className] - Additional CSS classes.\n */\nexport interface TooltipProps\n extends\n Omit<React.HTMLAttributes<HTMLSpanElement>, 'color' | 'content'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /** The tooltip text content. */\n label?: string;\n /**\n * Rich tooltip content (alternative to `label`), to read rather than use.\n * Nothing in it should take focus or a click: it isn't in the Tab order\n * while hidden, focus there isn't counted as being in the tooltip, and\n * Escape hiding it would drop that focus. For links or buttons, use\n * `HoverCard` or `Popover`.\n */\n content?: React.ReactNode;\n /** Position of the tooltip. Default: 'top'. */\n position?: TooltipPosition;\n /** Color variant for the tooltip. */\n color?: TooltipColor;\n /** Size of the tooltip. */\n size?: TooltipSize;\n /**\n * Force tooltip to be always visible. Hover, focus and Escape leave it\n * shown, so Escape goes on to whatever has focus, or to a `Modal` or\n * `Popover` around it.\n */\n active?: boolean;\n /** Allow tooltip to wrap to multiple lines. */\n multiline?: boolean;\n /** Enable fade animation. Default: true. */\n animated?: boolean;\n /** Use square corners instead of rounded. */\n square?: boolean;\n /** Show dashed underline on trigger. */\n dashed?: boolean;\n /**\n * How long the pointer or keyboard focus waits on the trigger before the\n * tooltip shows, in milliseconds.\n */\n delay?: number;\n /**\n * How long the tooltip stays after the pointer and focus have left the\n * trigger and the tooltip, in milliseconds. The pointer can move from the\n * trigger onto a shown tooltip without one, across the default gap. A\n * theme that raises `--bulma-tooltip-arrow-size` or\n * `--bulma-tooltip-arrow-margin` widens the gap past what the pointer can\n * cross on its own, so give it a `closeDelay` long enough to cross the\n * rest.\n */\n closeDelay?: number;\n /**\n * Show the tooltip with a tap on a touch screen. Touch has no hover, so\n * without this a phone or tablet never shows it. With it, a tap on the\n * trigger shows it at once, and it stays until another tap on the trigger\n * or the tooltip, a tap anywhere else, or Escape. The tap still does\n * whatever the trigger does, so turn this on only for a trigger with no\n * tap action of its own. Mouse and pen work the same either way.\n * @defaultValue false\n */\n openOnTap?: boolean;\n /** Additional classes for the tooltip element. */\n tooltipClassName?: string;\n}\n\n/**\n * The `Tooltip` component displays helpful information when users hover over or focus on an element.\n *\n * Hovering the trigger or giving it keyboard focus shows it. Touch doesn't,\n * unless `openOnTap` is on, and neither does focus a click or a tap brings,\n * so tapping the trigger just does what the trigger does. The pointer can\n * move onto a shown tooltip. Escape hides it wherever focus is, without\n * moving the pointer or focus, and it stays hidden until one of them leaves\n * the trigger and comes back, or, with `openOnTap`, until the next tap on\n * the trigger. The key always goes on too, to whatever has focus or a\n * `Modal` or `Popover` around it, since a tooltip only adds to what's\n * there.\n *\n * @function\n * @param {TooltipProps} props - Props for the Tooltip component.\n * @returns {JSX.Element} The rendered tooltip wrapper.\n *\n * @example\n * // Basic tooltip\n * <Tooltip label=\"This is helpful info\">\n * <button>Hover me</button>\n * </Tooltip>\n *\n * @example\n * // Positioned tooltip with color\n * <Tooltip label=\"Delete item\" position=\"right\" color=\"danger\">\n * <Icon icon=\"fas fa-trash\" />\n * </Tooltip>\n *\n * @example\n * // Multiline tooltip\n * <Tooltip label=\"This is a longer tooltip that wraps to multiple lines\" multiline>\n * <span>?</span>\n * </Tooltip>\n */\nexport const Tooltip: React.FC<TooltipProps> = ({\n label,\n content,\n position = 'top',\n color,\n size,\n active: alwaysActive,\n multiline = false,\n animated = true,\n square = false,\n dashed = false,\n delay = 0,\n closeDelay = 0,\n openOnTap = false,\n children,\n className,\n tooltipClassName,\n ...props\n}) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n const {\n onPointerEnter,\n onPointerOver,\n onPointerLeave,\n onPointerDown,\n onPointerUp,\n onFocus,\n onBlur,\n ...attrs\n } = rest;\n const [isVisible, setIsVisible] = useState(false);\n const [resolvedPosition, setResolvedPosition] = useState<\n Exclude<TooltipPosition, 'auto'>\n >(position === 'auto' ? 'top' : position);\n const [contentStyle, setContentStyle] = useState<React.CSSProperties>({});\n const wrapperRef = useRef<HTMLSpanElement | null>(null);\n const contentRef = useRef<HTMLSpanElement | null>(null);\n\n const hover = useHoverOpen({\n // The pointer and focus as they are. `active` shows it on top of that, so\n // dropping `active` leaves it shown while the pointer or focus holds it.\n // An ask to close can come while it's already hidden, which is no change.\n open: isVisible,\n onOpenChange: setIsVisible,\n openDelay: delay,\n closeDelay,\n // The wrapper stands in for the trigger. It holds only the children and\n // the tooltip, and nothing in the tooltip takes focus, so focus anywhere\n // in it is focus on the trigger.\n triggerRef: wrapperRef,\n floatingRef: contentRef,\n // Escape hides it and carries on, so a control in the trigger, or a\n // Modal around it, still gets the key.\n consumeEscape: false,\n openOnTap,\n });\n\n // Auto-placement and overflow correction\n useLayoutEffect(() => {\n const isAuto = position === 'auto';\n\n if (!isAuto) {\n // eslint-disable-next-line react-hooks/set-state-in-effect -- layout measurement effect resolves placement\n setResolvedPosition(position);\n }\n\n const showing = isVisible || alwaysActive;\n if (!showing) {\n setContentStyle({});\n return;\n }\n\n const wrapper = wrapperRef.current;\n const content = contentRef.current;\n if (!wrapper || !content) return;\n\n const wrapperRect = wrapper.getBoundingClientRect();\n const margin = 40;\n const edgePadding = 8; // minimum distance from viewport edge\n\n // Step 1: determine position (auto mode only)\n if (isAuto) {\n const spaceTop = wrapperRect.top;\n const spaceBottom = window.innerHeight - wrapperRect.bottom;\n\n if (spaceTop >= margin) {\n setResolvedPosition('top');\n } else if (spaceBottom >= margin) {\n setResolvedPosition('bottom');\n } else if (window.innerWidth - wrapperRect.right >= wrapperRect.left) {\n setResolvedPosition('right');\n } else {\n setResolvedPosition('left');\n }\n }\n\n // Step 2: measure tooltip content and correct overflow.\n // Use requestAnimationFrame so the resolved position class is applied first.\n requestAnimationFrame(() => {\n const contentEl = contentRef.current;\n if (!contentEl) return;\n\n const contentRect = contentEl.getBoundingClientRect();\n const style: React.CSSProperties = {};\n\n // For top/bottom positions, tooltip is centered horizontally via CSS\n // (left: 50%; transform: translateX(-50%)).\n // Check if it overflows left or right and apply a corrective shift.\n const pos = isAuto ? undefined : position; // read from state won't be updated yet in auto\n const effectivePos = pos || 'top'; // fallback, actual is set by state\n const isVertical =\n effectivePos === 'top' ||\n effectivePos === 'bottom' ||\n resolvedPosition === 'top' ||\n resolvedPosition === 'bottom';\n\n if (isVertical) {\n if (contentRect.left < edgePadding) {\n // Overflows left — shift right\n const shift = edgePadding - contentRect.left;\n style.left = `calc(50% + ${shift}px)`;\n (style as Record<string, string>)['--tooltip-arrow-offset'] =\n `${-shift}px`;\n } else if (contentRect.right > window.innerWidth - edgePadding) {\n // Overflows right — shift left\n const shift = contentRect.right - (window.innerWidth - edgePadding);\n style.left = `calc(50% - ${shift}px)`;\n (style as Record<string, string>)['--tooltip-arrow-offset'] =\n `${shift}px`;\n }\n } else {\n // For left/right positions, tooltip is centered vertically via CSS\n // (top: 50%; transform: translateY(-50%)).\n if (contentRect.top < edgePadding) {\n const shift = edgePadding - contentRect.top;\n style.top = `calc(50% + ${shift}px)`;\n (style as Record<string, string>)['--tooltip-arrow-offset'] =\n `${-shift}px`;\n } else if (contentRect.bottom > window.innerHeight - edgePadding) {\n const shift = contentRect.bottom - (window.innerHeight - edgePadding);\n style.top = `calc(50% - ${shift}px)`;\n (style as Record<string, string>)['--tooltip-arrow-offset'] =\n `${shift}px`;\n }\n }\n\n setContentStyle(style);\n });\n }, [position, resolvedPosition, isVisible, alwaysActive, label, content]);\n\n // Generate Bulma classes\n const tooltipClasses = usePrefixedClassNames('tooltip', {\n 'is-active': alwaysActive || isVisible,\n // Open by the pointer, focus or a tap, rather than shown by `active`\n // alone: only then does the tooltip, and the gap to it, take the pointer.\n 'is-open': isVisible,\n [`is-${resolvedPosition}`]: resolvedPosition,\n [`is-${color}`]: color,\n [`is-${size}`]: size,\n 'is-multiline': multiline,\n 'is-animated': animated,\n 'is-square': square,\n 'is-dashed': dashed,\n });\n\n const combinedClasses = classNames(\n tooltipClasses,\n bulmaHelperClasses,\n className\n );\n const combinedTooltipClasses = usePrefixedClassNames(\n 'tooltip-content',\n tooltipClassName\n );\n\n return (\n // The tooltip is inside the wrapper, so the pointer on it is still on the\n // wrapper, and the stylesheet bridges the gap between them.\n <span\n ref={wrapperRef}\n className={combinedClasses}\n onPointerEnter={chain(onPointerEnter, hover.onPointerEnter)}\n onPointerOver={chain(onPointerOver, hover.onPointerOver)}\n onPointerLeave={chain(onPointerLeave, hover.onPointerLeave)}\n onFocus={chain(onFocus, hover.onFocus)}\n onBlur={chain(onBlur, hover.onBlur)}\n onPointerDown={chain(onPointerDown, hover.onPointerDown)}\n onPointerUp={chain(onPointerUp, hover.onPointerUp)}\n {...(label ? { 'data-tooltip': label } : {})}\n {...attrs}\n >\n {children}\n <span\n ref={contentRef}\n className={combinedTooltipClasses}\n role=\"tooltip\"\n aria-hidden={!isVisible && !alwaysActive}\n style={Object.keys(contentStyle).length > 0 ? contentStyle : undefined}\n >\n {content || label}\n </span>\n </span>\n );\n};\n\nexport default Tooltip;\n"],"names":["_jsxs","_jsx"],"mappings":"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"}
1
+ {"version":3,"file":"Tooltip.js","sources":["../../../src/components/Tooltip.tsx"],"sourcesContent":["import React, {\n Fragment,\n cloneElement,\n isValidElement,\n useEffect,\n useId,\n useState,\n useRef,\n useLayoutEffect,\n} from 'react';\nimport { classNames, usePrefixedClassNames } from '../helpers/classNames';\nimport { useBulmaClasses, BulmaClassesProps } from '../helpers/useBulmaClasses';\nimport { chain, useHoverOpen } from '../helpers/useHoverOpen';\nimport { isTabbableKind } from '../helpers/useFocusTrap';\nimport { isDev, warnOnce } from '../helpers/devWarnings';\n\n/** Screen positions for the tooltip. */\nexport type TooltipPosition = 'top' | 'bottom' | 'left' | 'right' | 'auto';\n/** Color variants for the tooltip. */\nexport type TooltipColor =\n | 'primary'\n | 'link'\n | 'info'\n | 'success'\n | 'warning'\n | 'danger'\n | 'dark'\n | 'light';\n/** Size variants for the tooltip. */\nexport type TooltipSize = 'small' | 'medium' | 'large';\n\n/** An id list with `id` added at the end, unless it's in there already. */\nconst withId = (ids: string | undefined, id: string) =>\n [...new Set([...(ids ?? '').split(/\\s+/), id])].filter(Boolean).join(' ');\n\n/**\n * The first element in the trigger that Tab lands on, judged by what it is\n * rather than by its styles, which can hide it for now. The trigger is all\n * of the wrapper that comes before the tooltip.\n */\nconst triggerTabStop = (wrapper: Element, tooltip: Element) => {\n const elements = Array.from(wrapper.querySelectorAll('*'));\n return elements\n .slice(0, elements.indexOf(tooltip))\n .find(el => isTabbableKind(el) && !el.matches(':disabled'));\n};\n\n/**\n * Props for the Tooltip component.\n * @extraProp {React.ReactNode} [children] - The element that triggers the\n * tooltip. Its `aria-describedby` points at the tooltip, after any ids it\n * already had, so a screen reader reads the tooltip as its description when\n * it takes focus, even while the tooltip is hidden. That takes a single\n * element which passes `aria-describedby` on to its DOM node, as native\n * elements and bestax components do, and takes focus itself, such as a\n * button, a link or a `Span` with `tabIndex={0}`: something focusable inside\n * it doesn't get the link, and text, a fragment or several elements get none.\n * A development build warns when the first element Tab reaches in the\n * trigger doesn't carry the link, or there isn't one.\n * @extraProp {string} [className] - Additional CSS classes.\n */\nexport interface TooltipProps\n extends\n Omit<React.HTMLAttributes<HTMLSpanElement>, 'color' | 'content'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /** The tooltip text content. */\n label?: string;\n /**\n * Rich tooltip content (alternative to `label`), to read rather than use.\n * Nothing in it should take focus or a click: it isn't in the Tab order\n * while hidden, focus there isn't counted as being in the tooltip, and\n * Escape hiding it would drop that focus. For links or buttons, use\n * `HoverCard` or `Popover`. A screen reader reads it as plain text, since\n * it's the trigger's description.\n */\n content?: React.ReactNode;\n /** Position of the tooltip. Default: 'top'. */\n position?: TooltipPosition;\n /** Color variant for the tooltip. */\n color?: TooltipColor;\n /** Size of the tooltip. */\n size?: TooltipSize;\n /**\n * Force tooltip to be always visible. Hover, focus and Escape leave it\n * shown, so Escape goes on to whatever has focus, or to a `Modal` or\n * `Popover` around it.\n */\n active?: boolean;\n /** Allow tooltip to wrap to multiple lines. */\n multiline?: boolean;\n /** Enable fade animation. Default: true. */\n animated?: boolean;\n /** Use square corners instead of rounded. */\n square?: boolean;\n /** Show dashed underline on trigger. */\n dashed?: boolean;\n /**\n * How long the pointer or keyboard focus waits on the trigger before the\n * tooltip shows, in milliseconds.\n */\n delay?: number;\n /**\n * How long the tooltip stays after the pointer and focus have left the\n * trigger and the tooltip, in milliseconds. The pointer can move from the\n * trigger onto a shown tooltip without one, across the default gap. A\n * theme that raises `--bulma-tooltip-arrow-size` or\n * `--bulma-tooltip-arrow-margin` widens the gap past what the pointer can\n * cross on its own, so give it a `closeDelay` long enough to cross the\n * rest.\n */\n closeDelay?: number;\n /**\n * Show the tooltip with a tap on a touch screen. Touch has no hover, so\n * without this a phone or tablet never shows it. With it, a tap on the\n * trigger shows it at once, and it stays until another tap on the trigger\n * or the tooltip, a tap anywhere else, or Escape. The tap still does\n * whatever the trigger does, so turn this on only for a trigger with no\n * tap action of its own. Mouse and pen work the same either way.\n * @defaultValue false\n */\n openOnTap?: boolean;\n /** Additional classes for the tooltip element. */\n tooltipClassName?: string;\n}\n\n/**\n * The `Tooltip` component displays helpful information when users hover over or focus on an element.\n *\n * Hovering the trigger or giving it keyboard focus shows it. Touch doesn't,\n * unless `openOnTap` is on, and neither does focus a click or a tap brings,\n * so tapping the trigger just does what the trigger does. The pointer can\n * move onto a shown tooltip. Escape hides it wherever focus is, without\n * moving the pointer or focus, and it stays hidden until one of them leaves\n * the trigger and comes back, or, with `openOnTap`, until the next tap on\n * the trigger. The key always goes on too, to whatever has focus or a\n * `Modal` or `Popover` around it, since a tooltip only adds to what's\n * there. The tooltip is its trigger's description, so a screen reader reads\n * it as the trigger takes focus, whether it's showing yet or not.\n *\n * @function\n * @param {TooltipProps} props - Props for the Tooltip component.\n * @returns {JSX.Element} The rendered tooltip wrapper.\n *\n * @example\n * // Basic tooltip\n * <Tooltip label=\"This is helpful info\">\n * <button>Hover me</button>\n * </Tooltip>\n *\n * @example\n * // Positioned tooltip with color, on an icon-only button with a name of its own\n * <Tooltip label=\"Moves it to the trash\" position=\"right\" color=\"danger\">\n * <Button aria-label=\"Delete\">\n * <Icon name=\"fas fa-trash\" />\n * </Button>\n * </Tooltip>\n *\n * @example\n * // Multiline tooltip\n * <Tooltip label=\"This is a longer tooltip that wraps to multiple lines\" multiline>\n * <button>More info</button>\n * </Tooltip>\n */\nexport const Tooltip: React.FC<TooltipProps> = ({\n label,\n content,\n position = 'top',\n color,\n size,\n active: alwaysActive,\n multiline = false,\n animated = true,\n square = false,\n dashed = false,\n delay = 0,\n closeDelay = 0,\n openOnTap = false,\n children,\n className,\n tooltipClassName,\n ...props\n}) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n const {\n onPointerEnter,\n onPointerOver,\n onPointerLeave,\n onPointerDown,\n onPointerUp,\n onFocus,\n onBlur,\n ...attrs\n } = rest;\n const [isVisible, setIsVisible] = useState(false);\n const [resolvedPosition, setResolvedPosition] = useState<\n Exclude<TooltipPosition, 'auto'>\n >(position === 'auto' ? 'top' : position);\n const [contentStyle, setContentStyle] = useState<React.CSSProperties>({});\n const wrapperRef = useRef<HTMLSpanElement | null>(null);\n const contentRef = useRef<HTMLSpanElement | null>(null);\n const tooltipId = `${useId()}-tooltip`;\n\n // Only a single element can be given the link, and a fragment takes no\n // props of its own.\n const linkable =\n isValidElement<{ 'aria-describedby'?: string }>(children) &&\n children.type !== Fragment;\n\n // A screen reader reads the tooltip only when Tab lands on an element that\n // carries the link. Only the page shows whether it does, since a component\n // may not pass the attribute on, or what takes focus may sit inside the\n // element that took it. After every commit, since the children can change,\n // and only in development.\n useEffect(() => {\n if (!isDev()) return;\n const stop = triggerTabStop(\n wrapperRef.current as HTMLSpanElement,\n contentRef.current as HTMLSpanElement\n );\n if (\n stop?.getAttribute('aria-describedby')?.split(/\\s+/).includes(tooltipId)\n ) {\n return;\n }\n warnOnce(\n 'Tooltip:unlinked-trigger',\n \"[bestax-bulma] <Tooltip> can't describe its trigger, so a screen \" +\n \"reader won't read the tooltip: Tab reaches nothing in the trigger, \" +\n \"or what it reaches doesn't carry the aria-describedby that points \" +\n 'at the tooltip. Pass one element that takes focus and passes ' +\n 'aria-describedby on to its DOM node, such as a button, a link or a ' +\n 'Span with tabIndex={0}.'\n );\n });\n\n const hover = useHoverOpen({\n // The pointer and focus as they are. `active` shows it on top of that, so\n // dropping `active` leaves it shown while the pointer or focus holds it.\n // An ask to close can come while it's already hidden, which is no change.\n open: isVisible,\n onOpenChange: setIsVisible,\n openDelay: delay,\n closeDelay,\n // The wrapper stands in for the trigger. It holds only the children and\n // the tooltip, and nothing in the tooltip takes focus, so focus anywhere\n // in it is focus on the trigger.\n triggerRef: wrapperRef,\n floatingRef: contentRef,\n // Escape hides it and carries on, so a control in the trigger, or a\n // Modal around it, still gets the key.\n consumeEscape: false,\n openOnTap,\n });\n\n // Auto-placement and overflow correction\n useLayoutEffect(() => {\n const isAuto = position === 'auto';\n\n if (!isAuto) {\n // eslint-disable-next-line react-hooks/set-state-in-effect -- layout measurement effect resolves placement\n setResolvedPosition(position);\n }\n\n const showing = isVisible || alwaysActive;\n if (!showing) {\n setContentStyle({});\n return;\n }\n\n const wrapper = wrapperRef.current;\n const content = contentRef.current;\n if (!wrapper || !content) return;\n\n const wrapperRect = wrapper.getBoundingClientRect();\n const margin = 40;\n const edgePadding = 8; // minimum distance from viewport edge\n\n // Step 1: determine position (auto mode only)\n if (isAuto) {\n const spaceTop = wrapperRect.top;\n const spaceBottom = window.innerHeight - wrapperRect.bottom;\n\n if (spaceTop >= margin) {\n setResolvedPosition('top');\n } else if (spaceBottom >= margin) {\n setResolvedPosition('bottom');\n } else if (window.innerWidth - wrapperRect.right >= wrapperRect.left) {\n setResolvedPosition('right');\n } else {\n setResolvedPosition('left');\n }\n }\n\n // Step 2: measure tooltip content and correct overflow.\n // Use requestAnimationFrame so the resolved position class is applied first.\n requestAnimationFrame(() => {\n const contentEl = contentRef.current;\n if (!contentEl) return;\n\n const contentRect = contentEl.getBoundingClientRect();\n const style: React.CSSProperties = {};\n\n // For top/bottom positions, tooltip is centered horizontally via CSS\n // (left: 50%; transform: translateX(-50%)).\n // Check if it overflows left or right and apply a corrective shift.\n const pos = isAuto ? undefined : position; // read from state won't be updated yet in auto\n const effectivePos = pos || 'top'; // fallback, actual is set by state\n const isVertical =\n effectivePos === 'top' ||\n effectivePos === 'bottom' ||\n resolvedPosition === 'top' ||\n resolvedPosition === 'bottom';\n\n if (isVertical) {\n if (contentRect.left < edgePadding) {\n // Overflows left — shift right\n const shift = edgePadding - contentRect.left;\n style.left = `calc(50% + ${shift}px)`;\n (style as Record<string, string>)['--tooltip-arrow-offset'] =\n `${-shift}px`;\n } else if (contentRect.right > window.innerWidth - edgePadding) {\n // Overflows right — shift left\n const shift = contentRect.right - (window.innerWidth - edgePadding);\n style.left = `calc(50% - ${shift}px)`;\n (style as Record<string, string>)['--tooltip-arrow-offset'] =\n `${shift}px`;\n }\n } else {\n // For left/right positions, tooltip is centered vertically via CSS\n // (top: 50%; transform: translateY(-50%)).\n if (contentRect.top < edgePadding) {\n const shift = edgePadding - contentRect.top;\n style.top = `calc(50% + ${shift}px)`;\n (style as Record<string, string>)['--tooltip-arrow-offset'] =\n `${-shift}px`;\n } else if (contentRect.bottom > window.innerHeight - edgePadding) {\n const shift = contentRect.bottom - (window.innerHeight - edgePadding);\n style.top = `calc(50% - ${shift}px)`;\n (style as Record<string, string>)['--tooltip-arrow-offset'] =\n `${shift}px`;\n }\n }\n\n setContentStyle(style);\n });\n }, [position, resolvedPosition, isVisible, alwaysActive, label, content]);\n\n // Generate Bulma classes\n const tooltipClasses = usePrefixedClassNames('tooltip', {\n 'is-active': alwaysActive || isVisible,\n // Open by the pointer, focus or a tap, rather than shown by `active`\n // alone: only then does the tooltip, and the gap to it, take the pointer.\n 'is-open': isVisible,\n [`is-${resolvedPosition}`]: resolvedPosition,\n [`is-${color}`]: color,\n [`is-${size}`]: size,\n 'is-multiline': multiline,\n 'is-animated': animated,\n 'is-square': square,\n 'is-dashed': dashed,\n });\n\n const combinedClasses = classNames(\n tooltipClasses,\n bulmaHelperClasses,\n className\n );\n const combinedTooltipClasses = usePrefixedClassNames(\n 'tooltip-content',\n tooltipClassName\n );\n\n return (\n // The tooltip is inside the wrapper, so the pointer on it is still on the\n // wrapper, and the stylesheet bridges the gap between them.\n <span\n ref={wrapperRef}\n className={combinedClasses}\n onPointerEnter={chain(onPointerEnter, hover.onPointerEnter)}\n onPointerOver={chain(onPointerOver, hover.onPointerOver)}\n onPointerLeave={chain(onPointerLeave, hover.onPointerLeave)}\n onFocus={chain(onFocus, hover.onFocus)}\n onBlur={chain(onBlur, hover.onBlur)}\n onPointerDown={chain(onPointerDown, hover.onPointerDown)}\n onPointerUp={chain(onPointerUp, hover.onPointerUp)}\n {...(label ? { 'data-tooltip': label } : {})}\n {...attrs}\n >\n {linkable\n ? cloneElement(children, {\n 'aria-describedby': withId(\n children.props['aria-describedby'],\n tooltipId\n ),\n })\n : children}\n <span\n ref={contentRef}\n id={tooltipId}\n className={combinedTooltipClasses}\n // Hidden by `visibility: hidden` in the stylesheet, which keeps it out\n // of the accessibility tree and still lets it describe the trigger\n // (accname step 2A), so it takes no aria-hidden.\n role=\"tooltip\"\n style={Object.keys(contentStyle).length > 0 ? contentStyle : undefined}\n >\n {content || label}\n </span>\n </span>\n );\n};\n\nexport default Tooltip;\n"],"names":["_jsxs","_jsx"],"mappings":"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"}
@@ -4,6 +4,7 @@ import { usePrefixedClassNames, classNames } from '../helpers/classNames.js';
4
4
  import { withSubComponents } from '../helpers/withSubComponents.js';
5
5
  import { Button } from './Button.js';
6
6
  import { LinkButton } from './LinkButton.js';
7
+ import { ToggleButton } from './ToggleButton.js';
7
8
  import { useBulmaClasses } from '../helpers/useBulmaClasses.js';
8
9
 
9
10
  const validButtonsSizes = ['small', 'medium', 'large'];
@@ -36,6 +37,7 @@ const ButtonsComponent = ({ className, textColor, color, bgColor, isCentered, is
36
37
  const Buttons = withSubComponents(ButtonsComponent, {
37
38
  Button,
38
39
  LinkButton,
40
+ ToggleButton,
39
41
  }, 'Buttons');
40
42
 
41
43
  export { Buttons };
@@ -1 +1 @@
1
- {"version":3,"file":"Buttons.js","sources":["../../../src/elements/Buttons.tsx"],"sourcesContent":["import React from 'react';\nimport { classNames, usePrefixedClassNames } from '../helpers/classNames';\nimport { withSubComponents } from '../helpers/withSubComponents';\nimport { Button } from './Button';\nimport { LinkButton } from './LinkButton';\nimport {\n useBulmaClasses,\n BulmaClassesProps,\n validColors,\n} from '../helpers/useBulmaClasses';\n\nconst validButtonsSizes = ['small', 'medium', 'large'] as const;\n/**\n * Valid size values for the Buttons component (Bulma button group sizes).\n */\nexport type ButtonsSize = (typeof validButtonsSizes)[number];\n\n/**\n * Props for the Buttons component.\n */\ninterface ButtonsProps\n extends\n React.HTMLAttributes<HTMLDivElement>,\n Omit<BulmaClassesProps, 'size'> {\n /** Additional CSS classes to apply. */\n className?: string;\n /** Text color helper for the button group. */\n textColor?: (typeof validColors)[number] | 'inherit' | 'current';\n /**\n * Text color alias: renders `has-text-<color>`, exactly like `textColor`.\n * Not a filled variant (no `.buttons.is-<color>` CSS exists; color the\n * individual `Button`s instead). Prefer `textColor`, which takes precedence\n * when both are set; use `bgColor` for a colored surface.\n */\n color?: 'primary' | 'link' | 'info' | 'success' | 'warning' | 'danger';\n /** Background color helper for the button group. */\n bgColor?: (typeof validColors)[number] | 'inherit' | 'current';\n /** Center the group of buttons. */\n isCentered?: boolean;\n /** Align the group of buttons to the right. */\n isRight?: boolean;\n /** Group buttons together as addons (removes spacing between them). */\n hasAddons?: boolean;\n /** Size of the button group. */\n size?: ButtonsSize;\n /** The button elements to render inside the group. */\n children: React.ReactNode;\n}\n\n/**\n * The `Buttons` component lets you group multiple `Button` elements together with Bulma's spacing, alignment, and add-on features.\n *\n * @function\n * @param {ButtonsProps} props - Props for the Buttons component.\n * @returns {JSX.Element} The rendered group of buttons.\n * @see {@link https://bulma.io/documentation/elements/button/#group | Bulma Button Group documentation}\n */\nconst ButtonsComponent: React.FC<ButtonsProps> = ({\n className,\n textColor,\n color,\n bgColor,\n isCentered,\n isRight,\n hasAddons,\n size,\n children,\n ...props\n}) => {\n const buttonsClasses = usePrefixedClassNames('buttons', {\n 'is-centered': isCentered,\n 'is-right': isRight,\n 'has-addons': hasAddons,\n [`are-${size}`]: size && validButtonsSizes.includes(size),\n });\n\n /**\n * Generates Bulma helper classes and separates out remaining props.\n */\n const { bulmaHelperClasses, rest } = useBulmaClasses({\n color: textColor ?? color,\n backgroundColor: bgColor,\n ...props,\n });\n\n const combinedClasses = classNames(\n buttonsClasses,\n className,\n bulmaHelperClasses\n );\n\n return (\n <div className={combinedClasses} {...rest}>\n {children}\n </div>\n );\n};\n\nexport const Buttons = withSubComponents(\n ButtonsComponent,\n {\n Button,\n LinkButton,\n },\n 'Buttons'\n);\n"],"names":["_jsx"],"mappings":";;;;;;;;AAWA,MAAM,iBAAiB,GAAG,CAAC,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAU;AAsC/D;;;;;;;AAOG;AACH,MAAM,gBAAgB,GAA2B,CAAC,EAChD,SAAS,EACT,SAAS,EACT,KAAK,EACL,OAAO,EACP,UAAU,EACV,OAAO,EACP,SAAS,EACT,IAAI,EACJ,QAAQ,EACR,GAAG,KAAK,EACT,KAAI;AACH,IAAA,MAAM,cAAc,GAAG,qBAAqB,CAAC,SAAS,EAAE;AACtD,QAAA,aAAa,EAAE,UAAU;AACzB,QAAA,UAAU,EAAE,OAAO;AACnB,QAAA,YAAY,EAAE,SAAS;AACvB,QAAA,CAAC,CAAA,IAAA,EAAO,IAAI,CAAA,CAAE,GAAG,IAAI,IAAI,iBAAiB,CAAC,QAAQ,CAAC,IAAI,CAAC;AAC1D,KAAA,CAAC;AAEF;;AAEG;AACH,IAAA,MAAM,EAAE,kBAAkB,EAAE,IAAI,EAAE,GAAG,eAAe,CAAC;QACnD,KAAK,EAAE,SAAS,IAAI,KAAK;AACzB,QAAA,eAAe,EAAE,OAAO;AACxB,QAAA,GAAG,KAAK;AACT,KAAA,CAAC;IAEF,MAAM,eAAe,GAAG,UAAU,CAChC,cAAc,EACd,SAAS,EACT,kBAAkB,CACnB;IAED,QACEA,GAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAE,eAAe,EAAA,GAAM,IAAI,EAAA,QAAA,EACtC,QAAQ,EAAA,CACL;AAEV,CAAC;AAEM,MAAM,OAAO,GAAG,iBAAiB,CACtC,gBAAgB,EAChB;IACE,MAAM;IACN,UAAU;CACX,EACD,SAAS;;"}
1
+ {"version":3,"file":"Buttons.js","sources":["../../../src/elements/Buttons.tsx"],"sourcesContent":["import React from 'react';\nimport { classNames, usePrefixedClassNames } from '../helpers/classNames';\nimport { withSubComponents } from '../helpers/withSubComponents';\nimport { Button } from './Button';\nimport { LinkButton } from './LinkButton';\nimport { ToggleButton } from './ToggleButton';\nimport {\n useBulmaClasses,\n BulmaClassesProps,\n validColors,\n} from '../helpers/useBulmaClasses';\n\nconst validButtonsSizes = ['small', 'medium', 'large'] as const;\n/**\n * Valid size values for the Buttons component (Bulma button group sizes).\n */\nexport type ButtonsSize = (typeof validButtonsSizes)[number];\n\n/**\n * Props for the Buttons component.\n */\ninterface ButtonsProps\n extends\n React.HTMLAttributes<HTMLDivElement>,\n Omit<BulmaClassesProps, 'size'> {\n /** Additional CSS classes to apply. */\n className?: string;\n /** Text color helper for the button group. */\n textColor?: (typeof validColors)[number] | 'inherit' | 'current';\n /**\n * Text color alias: renders `has-text-<color>`, exactly like `textColor`.\n * Not a filled variant (no `.buttons.is-<color>` CSS exists; color the\n * individual `Button`s instead). Prefer `textColor`, which takes precedence\n * when both are set; use `bgColor` for a colored surface.\n */\n color?: 'primary' | 'link' | 'info' | 'success' | 'warning' | 'danger';\n /** Background color helper for the button group. */\n bgColor?: (typeof validColors)[number] | 'inherit' | 'current';\n /** Center the group of buttons. */\n isCentered?: boolean;\n /** Align the group of buttons to the right. */\n isRight?: boolean;\n /** Group buttons together as addons (removes spacing between them). */\n hasAddons?: boolean;\n /** Size of the button group. */\n size?: ButtonsSize;\n /** The button elements to render inside the group. */\n children: React.ReactNode;\n}\n\n/**\n * The `Buttons` component lets you group multiple `Button` elements together with Bulma's spacing, alignment, and add-on features.\n *\n * @function\n * @param {ButtonsProps} props - Props for the Buttons component.\n * @returns {JSX.Element} The rendered group of buttons.\n * @see {@link https://bulma.io/documentation/elements/button/#group | Bulma Button Group documentation}\n */\nconst ButtonsComponent: React.FC<ButtonsProps> = ({\n className,\n textColor,\n color,\n bgColor,\n isCentered,\n isRight,\n hasAddons,\n size,\n children,\n ...props\n}) => {\n const buttonsClasses = usePrefixedClassNames('buttons', {\n 'is-centered': isCentered,\n 'is-right': isRight,\n 'has-addons': hasAddons,\n [`are-${size}`]: size && validButtonsSizes.includes(size),\n });\n\n /**\n * Generates Bulma helper classes and separates out remaining props.\n */\n const { bulmaHelperClasses, rest } = useBulmaClasses({\n color: textColor ?? color,\n backgroundColor: bgColor,\n ...props,\n });\n\n const combinedClasses = classNames(\n buttonsClasses,\n className,\n bulmaHelperClasses\n );\n\n return (\n <div className={combinedClasses} {...rest}>\n {children}\n </div>\n );\n};\n\nexport const Buttons = withSubComponents(\n ButtonsComponent,\n {\n Button,\n LinkButton,\n ToggleButton,\n },\n 'Buttons'\n);\n"],"names":["_jsx"],"mappings":";;;;;;;;;AAYA,MAAM,iBAAiB,GAAG,CAAC,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAU;AAsC/D;;;;;;;AAOG;AACH,MAAM,gBAAgB,GAA2B,CAAC,EAChD,SAAS,EACT,SAAS,EACT,KAAK,EACL,OAAO,EACP,UAAU,EACV,OAAO,EACP,SAAS,EACT,IAAI,EACJ,QAAQ,EACR,GAAG,KAAK,EACT,KAAI;AACH,IAAA,MAAM,cAAc,GAAG,qBAAqB,CAAC,SAAS,EAAE;AACtD,QAAA,aAAa,EAAE,UAAU;AACzB,QAAA,UAAU,EAAE,OAAO;AACnB,QAAA,YAAY,EAAE,SAAS;AACvB,QAAA,CAAC,CAAA,IAAA,EAAO,IAAI,CAAA,CAAE,GAAG,IAAI,IAAI,iBAAiB,CAAC,QAAQ,CAAC,IAAI,CAAC;AAC1D,KAAA,CAAC;AAEF;;AAEG;AACH,IAAA,MAAM,EAAE,kBAAkB,EAAE,IAAI,EAAE,GAAG,eAAe,CAAC;QACnD,KAAK,EAAE,SAAS,IAAI,KAAK;AACzB,QAAA,eAAe,EAAE,OAAO;AACxB,QAAA,GAAG,KAAK;AACT,KAAA,CAAC;IAEF,MAAM,eAAe,GAAG,UAAU,CAChC,cAAc,EACd,SAAS,EACT,kBAAkB,CACnB;IAED,QACEA,GAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAE,eAAe,EAAA,GAAM,IAAI,EAAA,QAAA,EACtC,QAAQ,EAAA,CACL;AAEV,CAAC;AAEM,MAAM,OAAO,GAAG,iBAAiB,CACtC,gBAAgB,EAChB;IACE,MAAM;IACN,UAAU;IACV,YAAY;CACb,EACD,SAAS;;"}