@allxsmith/bestax-bulma 5.29.0 → 5.30.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 (61) hide show
  1. package/dist/bestax.css +1 -1
  2. package/dist/bestax.css.map +1 -1
  3. package/dist/esm/components/ContextMenu.js +244 -0
  4. package/dist/esm/components/ContextMenu.js.map +1 -0
  5. package/dist/esm/components/HoverCard.js +3 -10
  6. package/dist/esm/components/HoverCard.js.map +1 -1
  7. package/dist/esm/components/Tooltip.js +36 -61
  8. package/dist/esm/components/Tooltip.js.map +1 -1
  9. package/dist/esm/elements/Button.js.map +1 -1
  10. package/dist/esm/elements/ToggleButton.js +60 -0
  11. package/dist/esm/elements/ToggleButton.js.map +1 -0
  12. package/dist/esm/form/DateTimeInputBase.js.map +1 -1
  13. package/dist/esm/form/TimeInputBase.js.map +1 -1
  14. package/dist/esm/form/_pickerInternals/pickerTypes.js +14 -6
  15. package/dist/esm/form/_pickerInternals/pickerTypes.js.map +1 -1
  16. package/dist/esm/helpers/useContextMenuTrigger.js +166 -0
  17. package/dist/esm/helpers/useContextMenuTrigger.js.map +1 -0
  18. package/dist/esm/helpers/useHoverOpen.js +40 -6
  19. package/dist/esm/helpers/useHoverOpen.js.map +1 -1
  20. package/dist/esm/helpers/useMenuKeys.js +182 -0
  21. package/dist/esm/helpers/useMenuKeys.js.map +1 -0
  22. package/dist/extras.css +1 -1
  23. package/dist/extras.css.map +1 -1
  24. package/dist/index.cjs +2078 -1451
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.esm.js +2 -0
  27. package/dist/index.esm.js.map +1 -1
  28. package/dist/types/components/ContextMenu.d.ts +66 -0
  29. package/dist/types/components/Tooltip.d.ts +45 -4
  30. package/dist/types/elements/Button.d.ts +1 -1
  31. package/dist/types/elements/ToggleButton.d.ts +90 -0
  32. package/dist/types/form/DateTimeInputBase.d.ts +4 -1
  33. package/dist/types/form/TimeInputBase.d.ts +5 -2
  34. package/dist/types/form/_pickerInternals/pickerTypes.d.ts +16 -1
  35. package/dist/types/helpers/useContextMenuTrigger.d.ts +67 -0
  36. package/dist/types/helpers/useHoverOpen.d.ts +19 -3
  37. package/dist/types/helpers/useMenuKeys.d.ts +57 -0
  38. package/dist/types/index.d.ts +2 -0
  39. package/dist/types-cjs/components/ContextMenu.d.ts +66 -0
  40. package/dist/types-cjs/components/Tooltip.d.ts +45 -4
  41. package/dist/types-cjs/elements/Button.d.ts +1 -1
  42. package/dist/types-cjs/elements/ToggleButton.d.ts +90 -0
  43. package/dist/types-cjs/form/DateTimeInputBase.d.ts +4 -1
  44. package/dist/types-cjs/form/TimeInputBase.d.ts +5 -2
  45. package/dist/types-cjs/form/_pickerInternals/pickerTypes.d.ts +16 -1
  46. package/dist/types-cjs/helpers/useContextMenuTrigger.d.ts +67 -0
  47. package/dist/types-cjs/helpers/useHoverOpen.d.ts +19 -3
  48. package/dist/types-cjs/helpers/useMenuKeys.d.ts +57 -0
  49. package/dist/types-cjs/index.d.ts +2 -0
  50. package/dist/versions/bestax-no-dark-mode.css +1 -1
  51. package/dist/versions/bestax-no-dark-mode.css.map +1 -1
  52. package/dist/versions/bestax-no-helpers-prefixed.css +1 -1
  53. package/dist/versions/bestax-no-helpers-prefixed.css.map +1 -1
  54. package/dist/versions/bestax-no-helpers.css +1 -1
  55. package/dist/versions/bestax-no-helpers.css.map +1 -1
  56. package/dist/versions/bestax-prefixed.css +1 -1
  57. package/dist/versions/bestax-prefixed.css.map +1 -1
  58. package/package.json +1 -1
  59. package/src/scss/components/_contextmenu.scss +50 -0
  60. package/src/scss/components/_index.scss +1 -0
  61. package/src/scss/components/_tooltip.scss +76 -1
@@ -14,7 +14,13 @@ export type TooltipSize = 'small' | 'medium' | 'large';
14
14
  export interface TooltipProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, 'color' | 'content'>, Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {
15
15
  /** The tooltip text content. */
16
16
  label?: string;
17
- /** Rich tooltip content (alternative to `label`). */
17
+ /**
18
+ * Rich tooltip content (alternative to `label`), to read rather than use.
19
+ * Nothing in it should take focus or a click: it isn't in the Tab order
20
+ * while hidden, focus there isn't counted as being in the tooltip, and
21
+ * Escape hiding it would drop that focus. For links or buttons, use
22
+ * `HoverCard` or `Popover`.
23
+ */
18
24
  content?: React.ReactNode;
19
25
  /** Position of the tooltip. Default: 'top'. */
20
26
  position?: TooltipPosition;
@@ -22,7 +28,11 @@ export interface TooltipProps extends Omit<React.HTMLAttributes<HTMLSpanElement>
22
28
  color?: TooltipColor;
23
29
  /** Size of the tooltip. */
24
30
  size?: TooltipSize;
25
- /** Force tooltip to be always visible. */
31
+ /**
32
+ * Force tooltip to be always visible. Hover, focus and Escape leave it
33
+ * shown, so Escape goes on to whatever has focus, or to a `Modal` or
34
+ * `Popover` around it.
35
+ */
26
36
  active?: boolean;
27
37
  /** Allow tooltip to wrap to multiple lines. */
28
38
  multiline?: boolean;
@@ -32,16 +42,47 @@ export interface TooltipProps extends Omit<React.HTMLAttributes<HTMLSpanElement>
32
42
  square?: boolean;
33
43
  /** Show dashed underline on trigger. */
34
44
  dashed?: boolean;
35
- /** Delay before showing tooltip (ms). */
45
+ /**
46
+ * How long the pointer or keyboard focus waits on the trigger before the
47
+ * tooltip shows, in milliseconds.
48
+ */
36
49
  delay?: number;
37
- /** Delay in ms before hiding the tooltip. */
50
+ /**
51
+ * How long the tooltip stays after the pointer and focus have left the
52
+ * trigger and the tooltip, in milliseconds. The pointer can move from the
53
+ * trigger onto a shown tooltip without one, across the default gap. A
54
+ * theme that raises `--bulma-tooltip-arrow-size` or
55
+ * `--bulma-tooltip-arrow-margin` widens the gap past what the pointer can
56
+ * cross on its own, so give it a `closeDelay` long enough to cross the
57
+ * rest.
58
+ */
38
59
  closeDelay?: number;
60
+ /**
61
+ * Show the tooltip with a tap on a touch screen. Touch has no hover, so
62
+ * without this a phone or tablet never shows it. With it, a tap on the
63
+ * trigger shows it at once, and it stays until another tap on the trigger
64
+ * or the tooltip, a tap anywhere else, or Escape. The tap still does
65
+ * whatever the trigger does, so turn this on only for a trigger with no
66
+ * tap action of its own. Mouse and pen work the same either way.
67
+ * @defaultValue false
68
+ */
69
+ openOnTap?: boolean;
39
70
  /** Additional classes for the tooltip element. */
40
71
  tooltipClassName?: string;
41
72
  }
42
73
  /**
43
74
  * The `Tooltip` component displays helpful information when users hover over or focus on an element.
44
75
  *
76
+ * Hovering the trigger or giving it keyboard focus shows it. Touch doesn't,
77
+ * unless `openOnTap` is on, and neither does focus a click or a tap brings,
78
+ * so tapping the trigger just does what the trigger does. The pointer can
79
+ * move onto a shown tooltip. Escape hides it wherever focus is, without
80
+ * moving the pointer or focus, and it stays hidden until one of them leaves
81
+ * the trigger and comes back, or, with `openOnTap`, until the next tap on
82
+ * the trigger. The key always goes on too, to whatever has focus or a
83
+ * `Modal` or `Popover` around it, since a tooltip only adds to what's
84
+ * there.
85
+ *
45
86
  * @function
46
87
  * @param {TooltipProps} props - Props for the Tooltip component.
47
88
  * @returns {JSX.Element} The rendered tooltip wrapper.
@@ -39,7 +39,7 @@ export interface ButtonOwnProps extends Omit<BulmaClassesProps, 'color' | 'backg
39
39
  isInverted?: boolean;
40
40
  /** Applies focused styling (visual only). */
41
41
  isFocused?: boolean;
42
- /** Applies active styling (visual only). */
42
+ /** Applies active styling (visual only), with no `aria-pressed`. For a button that stays pressed, use `ToggleButton`. */
43
43
  isActive?: boolean;
44
44
  /** Applies hovered styling (visual only). */
45
45
  isHovered?: boolean;
@@ -0,0 +1,90 @@
1
+ import React from 'react';
2
+ import { type ButtonOwnProps } from './Button.js';
3
+ /**
4
+ * The fills a pressed `ToggleButton` takes: `Button`'s colors, minus `text`
5
+ * and `ghost`, which don't fill, and `white`, `light`, `dark` and `black`.
6
+ * Bulma's color modifier fills the button and drops its border, and those four
7
+ * fill it with about the lightness a plain button already has in one theme or
8
+ * the other, so pressed would look like off without its border.
9
+ */
10
+ export type ToggleButtonColor = 'primary' | 'link' | 'info' | 'success' | 'warning' | 'danger';
11
+ /**
12
+ * Props for the ToggleButton component. The helper props, the other button
13
+ * attributes and the `ref` all land on the `<button>`.
14
+ *
15
+ * Some of `Button`'s props are left out on purpose. `as`, since only a button
16
+ * can be pressed, and `type`, which is always `button`. `isActive`, which is
17
+ * `pressed` here, and `isStatic`, a toggle you couldn't press. And the props
18
+ * that change the fill (`isLight`, `isOutlined`, `isInverted`, `textColor`,
19
+ * `bgColor` and their shades), since each would color the button in both
20
+ * states or turn "on" into a faint tint. `color` takes only the fills that
21
+ * stand out from a plain button: see `ToggleButtonColor`.
22
+ *
23
+ * @extraProp {React.Ref<HTMLButtonElement>} [ref] - Ref forwarded to the `<button>`.
24
+ */
25
+ export interface ToggleButtonProps extends Omit<ButtonOwnProps, 'color' | 'isActive' | 'isStatic' | 'isLight' | 'isOutlined' | 'isInverted' | 'textColor' | 'bgColor' | 'colorShade' | 'backgroundColorShade'>, Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, keyof ButtonOwnProps | 'type' | 'aria-pressed'> {
26
+ /**
27
+ * Whether it's pressed, when you hold the state. Setting it makes the toggle
28
+ * controlled: a click only calls `onPressedChange`, and nothing changes until
29
+ * you pass the new value. Pick this or `defaultPressed` for the button's
30
+ * lifetime.
31
+ */
32
+ pressed?: boolean;
33
+ /** Whether it starts pressed, when it holds its own state. */
34
+ defaultPressed?: boolean;
35
+ /**
36
+ * Called with the new state each time it's toggled, by a click, Enter or
37
+ * Space, whether or not `pressed` is set. An `onClick` that calls
38
+ * `preventDefault()` stops the toggle, and this doesn't fire.
39
+ */
40
+ onPressedChange?: (pressed: boolean) => void;
41
+ /**
42
+ * The fill while it's pressed. Not pressed, it's a plain button, so the fill
43
+ * is the difference people see. `white`, `light`, `dark` and `black` aren't
44
+ * offered, since in one theme or the other each fills about as light or dark
45
+ * as a plain button already is. Of the rest, a pale one such as `warning`
46
+ * changes less than the default `link`.
47
+ */
48
+ color?: ToggleButtonColor;
49
+ /**
50
+ * The label, which stays the same in both states: a screen reader announces
51
+ * whether it's pressed, so "Mute" stays "Mute". An icon can swap, but an
52
+ * icon-only toggle needs an `aria-label` that doesn't. A label that changes
53
+ * ("Play", then "Pause") belongs on a plain `Button`.
54
+ */
55
+ children?: React.ReactNode;
56
+ /**
57
+ * Not accepted: `aria-pressed` follows the state. Declared because JSX lets a
58
+ * hyphenated attribute through when the props don't name it, so leaving it
59
+ * out of the type alone would not reject `aria-pressed="mixed"`.
60
+ * @internal
61
+ */
62
+ 'aria-pressed'?: never;
63
+ }
64
+ /**
65
+ * The `ToggleButton` component renders a button that stays pressed until it's pressed again, and says whether it's on through `aria-pressed`.
66
+ *
67
+ * It's always a `<button type="button">`, so it never submits a form and
68
+ * carries no form value: a setting saved with a form is a `Checkbox` or a
69
+ * `Switch`. A set of toggles that each turn on and off on their own goes in
70
+ * `Buttons hasAddons` with `role="group"` and an `aria-label`. A set where only
71
+ * one can be on is a radio group rather than a row of these: use `Radios`.
72
+ *
73
+ * @function
74
+ * @param {ToggleButtonProps} props - Props for the ToggleButton component.
75
+ * @param {React.Ref<HTMLButtonElement>} ref - Forwarded ref to the `<button>`.
76
+ * @returns {JSX.Element} The rendered toggle button.
77
+ * @see {@link https://www.w3.org/WAI/ARIA/apg/patterns/button/ | APG button pattern}
78
+ *
79
+ * @example
80
+ * <ToggleButton onPressedChange={setMuted}>Mute</ToggleButton>
81
+ *
82
+ * @example
83
+ * <Buttons hasAddons role="group" aria-label="Text style">
84
+ * <ToggleButton aria-label="Bold" pressed={bold} onPressedChange={setBold}>
85
+ * <Icon name="bold" />
86
+ * </ToggleButton>
87
+ * </Buttons>
88
+ */
89
+ export declare const ToggleButton: React.ForwardRefExoticComponent<ToggleButtonProps & React.RefAttributes<HTMLButtonElement>>;
90
+ export default ToggleButton;
@@ -131,7 +131,10 @@ export interface DateTimeInputBaseProps extends Omit<React.InputHTMLAttributes<H
131
131
  audioTick?: boolean;
132
132
  /** Auto-route tactile feedback per wheel tick (vibrate on Android, audio thunk on iOS). Default `false`. */
133
133
  haptics?: boolean;
134
- /** Optional translatable string overrides. */
134
+ /**
135
+ * Optional translatable string overrides. In a 12-hour picker, `ampm`
136
+ * names the AM/PM column, `'AM/PM'` by default.
137
+ */
135
138
  labels?: PickerLabels;
136
139
  }
137
140
  /**
@@ -82,7 +82,7 @@ export interface TimeInputBaseProps extends Omit<React.InputHTMLAttributes<HTMLI
82
82
  size?: 'small' | 'medium' | 'large';
83
83
  /** Render the input with rounded corners. */
84
84
  isRounded?: boolean;
85
- /** Hour format. `'12'` shows an AM/PM toggle. */
85
+ /** Hour format. `'12'` adds an AM/PM column to the wheels. */
86
86
  hourFormat?: HourFormat;
87
87
  /** Show a seconds column. Note: iOS Safari's native time picker UI does not include a seconds wheel; pass `mobileNative={false}` if you need one on iOS. */
88
88
  enableSeconds?: boolean;
@@ -105,7 +105,10 @@ export interface TimeInputBaseProps extends Omit<React.InputHTMLAttributes<HTMLI
105
105
  triggerIcon?: boolean;
106
106
  /** Glyph for the right launcher button. */
107
107
  triggerIconName?: string;
108
- /** Optional translatable string overrides. */
108
+ /**
109
+ * Optional translatable string overrides. In a 12-hour picker, `ampm`
110
+ * names the AM/PM column, `'AM/PM'` by default.
111
+ */
109
112
  labels?: PickerLabels;
110
113
  /** Play a short audible click on each wheel-item crossing. Substitute for haptic feedback on iOS Safari (which has no web haptic API as of May 2026); on Android, `navigator.vibrate(5)` fires automatically regardless. */
111
114
  audioTick?: boolean;
@@ -49,13 +49,24 @@ export interface PickerLabels {
49
49
  hours?: string;
50
50
  minutes?: string;
51
51
  seconds?: string;
52
+ /**
53
+ * Time wheels: names the AM/PM column a 12-hour picker shows.
54
+ * @defaultValue 'AM/PM'
55
+ */
52
56
  ampm?: string;
57
+ /** @deprecated Rendered nowhere: no picker reads it. It goes in the next major. */
53
58
  increaseHours?: string;
59
+ /** @deprecated Rendered nowhere: no picker reads it. It goes in the next major. */
54
60
  decreaseHours?: string;
61
+ /** @deprecated Rendered nowhere: no picker reads it. It goes in the next major. */
55
62
  increaseMinutes?: string;
63
+ /** @deprecated Rendered nowhere: no picker reads it. It goes in the next major. */
56
64
  decreaseMinutes?: string;
65
+ /** @deprecated Rendered nowhere: no picker reads it. It goes in the next major. */
57
66
  increaseSeconds?: string;
67
+ /** @deprecated Rendered nowhere: no picker reads it. It goes in the next major. */
58
68
  decreaseSeconds?: string;
69
+ /** @deprecated Rendered nowhere: no picker reads it. It goes in the next major. */
59
70
  toggleAmPm?: string;
60
71
  chooseTime?: string;
61
72
  chooseDateTime?: string;
@@ -72,5 +83,9 @@ export interface PickerLabels {
72
83
  time?: string;
73
84
  }
74
85
  export declare const DEFAULT_PICKER_LABELS: Required<PickerLabels>;
75
- /** Merge user-supplied label overrides with the defaults. */
86
+ /**
87
+ * Merge user-supplied label overrides with the defaults. A key set to
88
+ * `undefined` keeps its default, so a locale table that is missing a string
89
+ * still names its control instead of leaving it with no accessible name.
90
+ */
76
91
  export declare const mergeLabels: (overrides?: PickerLabels) => Required<PickerLabels>;
@@ -0,0 +1,67 @@
1
+ import type { MouseEvent, PointerEvent, TouchEvent } from 'react';
2
+ /** A point in the viewport, in CSS pixels. */
3
+ export interface ContextMenuPoint {
4
+ x: number;
5
+ y: number;
6
+ }
7
+ /**
8
+ * Options for `useContextMenuTrigger`.
9
+ */
10
+ export interface UseContextMenuTriggerOptions {
11
+ /**
12
+ * Asks to open at `point`. `trigger` is the element the handlers are on,
13
+ * for its document.
14
+ */
15
+ onOpen: (point: ContextMenuPoint, trigger: HTMLElement) => void;
16
+ /** Leave the browser's own menu alone, and start no long press. */
17
+ disabled: boolean;
18
+ }
19
+ /** Handlers for the trigger element. */
20
+ export interface ContextMenuTriggerHandlers {
21
+ onContextMenu: (event: MouseEvent<HTMLElement>) => void;
22
+ onPointerDown: (event: PointerEvent<HTMLElement>) => void;
23
+ onPointerMove: (event: PointerEvent<HTMLElement>) => void;
24
+ onPointerUp: (event: PointerEvent<HTMLElement>) => void;
25
+ onPointerCancel: (event: PointerEvent<HTMLElement>) => void;
26
+ onTouchEnd: (event: TouchEvent<HTMLElement>) => void;
27
+ }
28
+ /** How long a touch or a pen has to stay down to open the menu, in milliseconds. */
29
+ export declare const LONG_PRESS_DELAY = 500;
30
+ /** How far a long press can move before it counts as something else, in CSS pixels. */
31
+ export declare const LONG_PRESS_TOLERANCE = 10;
32
+ /**
33
+ * Decides when a context menu asks to open, and where. It renders nothing,
34
+ * and knows nothing about ARIA or the menu.
35
+ *
36
+ * - **`contextmenu`:** a right-click, and Shift+F10 or the context menu key
37
+ * where the browser fires the event for them, asks to open at the point
38
+ * `contextMenuPoint` works out, and prevents the browser's own menu. Each
39
+ * one asks again, so a right-click elsewhere on the trigger moves it.
40
+ * - **Long press:** a touch or a pen held still on the trigger for
41
+ * `LONG_PRESS_DELAY` fires a `contextmenu` at what it landed on, so it
42
+ * takes the same path, handlers on the way included. Moving more than
43
+ * `LONG_PRESS_TOLERANCE`, lifting, another pointer coming down anywhere in
44
+ * the document, or `pointercancel`, which is the browser taking the
45
+ * gesture for a scroll or a zoom, cancels it, and a mouse never starts
46
+ * one. A pointer that comes down while another is held starts no long
47
+ * press of its own, on this trigger or another, so two fingers on two rows
48
+ * open nothing. While the finger is down, and only then, a listener on the
49
+ * document hears other pointers coming down. Where the
50
+ * browser fires `contextmenu` for a long press itself, the first of the two
51
+ * opens it, and the end of a touch that opened it is prevented, so lifting
52
+ * doesn't click what is under the finger.
53
+ * - **Who opens:** a handler that calls `preventDefault()` on the
54
+ * `contextmenu` before this one keeps it from asking, which is how a
55
+ * trigger's own handler opts out and how the innermost of nested triggers
56
+ * wins. A long press goes to the innermost trigger that takes it. Events
57
+ * from content portaled out of the trigger, which React brings here
58
+ * anyway, are ignored.
59
+ * - **`disabled`:** nothing asks, and the browser's menu shows.
60
+ *
61
+ * Nothing it scheduled runs after the component unmounts.
62
+ *
63
+ * @function useContextMenuTrigger
64
+ * @param options - How to ask to open, and whether to.
65
+ * @returns Handlers for the trigger element.
66
+ */
67
+ export declare function useContextMenuTrigger({ onOpen, disabled, }: UseContextMenuTriggerOptions): ContextMenuTriggerHandlers;
@@ -38,6 +38,14 @@ export interface UseHoverOpenOptions {
38
38
  * @defaultValue false
39
39
  */
40
40
  openOnTap?: boolean;
41
+ /**
42
+ * Whether an Escape that closes it stops there when focus is on the
43
+ * trigger, in the floating element or nowhere, so a `Popover` or `Modal`
44
+ * around it keeps its own. Off, Escape still closes it and always goes on
45
+ * to whatever has focus, as suits content that only adds to the trigger.
46
+ * @defaultValue true
47
+ */
48
+ consumeEscape?: boolean;
41
49
  }
42
50
  /**
43
51
  * Pointer and focus handlers for the element that holds the trigger. The
@@ -48,12 +56,18 @@ export interface UseHoverOpenOptions {
48
56
  */
49
57
  export interface HoverOpenHandlers {
50
58
  onPointerEnter: (event: PointerEvent<HTMLElement>) => void;
59
+ onPointerOver: (event: PointerEvent<HTMLElement>) => void;
51
60
  onPointerLeave: (event: PointerEvent<HTMLElement>) => void;
52
61
  onFocus: (event: FocusEvent<HTMLElement>) => void;
53
62
  onBlur: (event: FocusEvent<HTMLElement>) => void;
54
63
  onPointerDown: (event: PointerEvent<HTMLElement>) => void;
55
64
  onPointerUp: (event: PointerEvent<HTMLElement>) => void;
56
65
  }
66
+ /**
67
+ * Runs the caller's own handler on the element that holds the trigger, then
68
+ * the hook's, so neither replaces the other.
69
+ */
70
+ export declare function chain<E>(own: ((event: E) => void) | undefined, ours: (event: E) => void): (event: E) => void;
57
71
  /**
58
72
  * Decides when a floating element that opens on hover or keyboard focus asks
59
73
  * to open and to close. It renders nothing, and leaves roles, ARIA and
@@ -62,7 +76,8 @@ export interface HoverOpenHandlers {
62
76
  * - The pointer entering, or keyboard focus arriving on the trigger, asks to
63
77
  * open after `openDelay`. A touch pointer doesn't count. Neither does focus
64
78
  * that a press brings to the trigger: it counts when the browser shows it
65
- * as `:focus-visible`.
79
+ * as `:focus-visible`. The pointer arriving from outside a shadow root the
80
+ * trigger renders into counts too, though React sends no enter for it.
66
81
  * - Focus in the floating element holds it open however it got there,
67
82
  * including focus in content it portals elsewhere, such as a nested card.
68
83
  * Whether a click focuses what it lands on is up to the browser: Safari
@@ -73,8 +88,9 @@ export interface HoverOpenHandlers {
73
88
  * else closes it on a timer.
74
89
  * - Escape closes it at once, wherever focus is, and stops there when focus
75
90
  * is in it, on its trigger or nowhere, so a `Popover` or `Modal` around it
76
- * keeps its own. With focus somewhere else, the key goes on after closing
77
- * it. Inside another one, the inner one closes first. Escape before it
91
+ * keeps its own. With focus somewhere else, or with `consumeEscape` off,
92
+ * the key goes on after closing it. Inside another one, the inner one
93
+ * closes first. Escape before it
78
94
  * opens takes the opening back and goes on its way. One key listener per
79
95
  * document serves every hook on it, there only while one of them is open
80
96
  * or on its way to open.
@@ -0,0 +1,57 @@
1
+ import type { KeyboardEvent } from 'react';
2
+ /**
3
+ * Every menu-item role, so an item a caller gives `menuitemcheckbox` or
4
+ * `menuitemradio` (a filter or sort menu) stays reachable.
5
+ */
6
+ export declare const MENU_ITEM_SELECTOR = "[role=\"menuitem\"], [role=\"menuitemcheckbox\"], [role=\"menuitemradio\"]";
7
+ /**
8
+ * Whether a menu item can be reached and activated: it isn't disabled, as a
9
+ * native control or through `aria-disabled="true"`.
10
+ */
11
+ export declare function isEnabledMenuItem(item: Element): boolean;
12
+ /** The items in `menu` the keys reach, in document order. */
13
+ export declare function enabledMenuItems(menu: Element): HTMLElement[];
14
+ /** How long typed characters keep adding up to one search, in milliseconds. */
15
+ export declare const TYPEAHEAD_TIMEOUT = 500;
16
+ /** What `useMenuKeys` returns. */
17
+ export interface MenuKeys {
18
+ /** The `keydown` handler for the `role="menu"` element. */
19
+ onKeyDown: (event: KeyboardEvent<HTMLElement>) => void;
20
+ /**
21
+ * Forgets what has been typed, so the next character starts a new search.
22
+ * Call it when the menu opens, so a search typed just before it last
23
+ * closed doesn't carry over.
24
+ */
25
+ reset: () => void;
26
+ }
27
+ /**
28
+ * Moves through and activates the items of a `role="menu"` element from the
29
+ * keyboard. Put the `onKeyDown` it returns on that element. It reads the
30
+ * items from the element each time, so items that come and go are found,
31
+ * and it renders nothing.
32
+ *
33
+ * It acts only while focus is on the menu element itself or on one of its
34
+ * items, so anything else focusable inside the menu, such as a filter
35
+ * field, keeps its own keys.
36
+ *
37
+ * - Down and Up move to the next and previous enabled item, wrapping, and
38
+ * Home and End go to the first and last. From no item, Down goes to the
39
+ * first and Up to the last.
40
+ * - Enter and Space activate the focused item. A `<button>`, and a link with
41
+ * an `href` for Enter, get the browser's own click, and anything else gets
42
+ * one from here, so its `onClick` runs either way. A key held down
43
+ * activates it once. A key handler on the item that called
44
+ * `preventDefault()` has claimed the key, and nothing else runs it.
45
+ * - A printable character moves to the next enabled item whose text starts
46
+ * with what has been typed. Characters typed within `TYPEAHEAD_TIMEOUT` of
47
+ * each other add up to one search, until `reset` starts a new one.
48
+ *
49
+ * Items are `MENU_ITEM_SELECTOR` matches, and disabled ones are skipped by
50
+ * all of the above. Escape and Tab are left to the menu's owner, since what
51
+ * closing means is up to it, and so is any other key.
52
+ *
53
+ * @function useMenuKeys
54
+ * @returns The `keydown` handler for the menu element, and a way to forget
55
+ * what has been typed.
56
+ */
57
+ export declare function useMenuKeys(): MenuKeys;
@@ -19,6 +19,7 @@ export * from './components/Collapses.js';
19
19
  export * from './components/Tooltip.js';
20
20
  export * from './components/Popover.js';
21
21
  export * from './components/HoverCard.js';
22
+ export * from './components/ContextMenu.js';
22
23
  export * from './components/Steps.js';
23
24
  export * from './components/Sidebar.js';
24
25
  export * from './components/Toast.js';
@@ -30,6 +31,7 @@ export * from './elements/Box.js';
30
31
  export * from './elements/Button.js';
31
32
  export * from './elements/Buttons.js';
32
33
  export * from './elements/LinkButton.js';
34
+ export * from './elements/ToggleButton.js';
33
35
  export * from './elements/Code.js';
34
36
  export * from './elements/Content.js';
35
37
  export * from './elements/Delete.js';