@allxsmith/bestax-bulma 5.34.1 → 5.34.2

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.
@@ -5,6 +5,7 @@ import { createPortal } from 'react-dom';
5
5
  import { usePrefixedClassNames, classNames } from '../../helpers/classNames.js';
6
6
  import { useFocusTrap } from '../../helpers/useFocusTrap.js';
7
7
  import { usePortalContainer } from '../../helpers/portal.js';
8
+ import { isEventInside } from '../../helpers/shadowDom.js';
8
9
  import { useAnchoredPosition, marginGap } from '../../helpers/useAnchoredPosition.js';
9
10
 
10
11
  const isBrowser = typeof window !== 'undefined';
@@ -48,13 +49,20 @@ const PickerPopover = ({ isOpen, onClose, anchorRef, children, position = 'botto
48
49
  return () => document.removeEventListener('pointerdown', handler);
49
50
  }, [isOpen, closeOnClickOutside, anchorRef, onClose]);
50
51
  // Escape closes it, and so does Alt+ArrowUp, the combobox's key for it,
51
- // which the calendar and the time wheels inside leave to it.
52
+ // which the calendar and the time wheels inside leave to it. Escape closes
53
+ // it from anywhere, but the key is only the picker's when it was pressed in
54
+ // the panel or the field: then it's marked used, so a Modal or Sidebar
55
+ // around the picker stays open. From anywhere else, such as a dialog opened
56
+ // over the picker, the key goes on to whatever has focus.
52
57
  useEffect(() => {
53
58
  if (!isOpen || !closeOnEscape || !isBrowser)
54
59
  return undefined;
55
60
  const handler = (e) => {
56
61
  if (e.key === 'Escape') {
57
- e.stopPropagation();
62
+ if (isEventInside(e, panelRef.current) ||
63
+ isEventInside(e, anchorRef.current)) {
64
+ e.preventDefault();
65
+ }
58
66
  onClose();
59
67
  }
60
68
  else if (e.altKey && e.key === 'ArrowUp') {
@@ -65,7 +73,7 @@ const PickerPopover = ({ isOpen, onClose, anchorRef, children, position = 'botto
65
73
  };
66
74
  document.addEventListener('keydown', handler);
67
75
  return () => document.removeEventListener('keydown', handler);
68
- }, [isOpen, closeOnEscape, onClose]);
76
+ }, [isOpen, closeOnEscape, anchorRef, onClose]);
69
77
  useFocusTrap(panelRef, {
70
78
  active: isOpen && trapFocus,
71
79
  initialFocusRef,
@@ -1 +1 @@
1
- {"version":3,"file":"PickerPopover.js","sources":["../../../../src/form/_pickerInternals/PickerPopover.tsx"],"sourcesContent":["import React, { useEffect, useMemo, useRef } from 'react';\nimport { createPortal } from 'react-dom';\nimport { classNames, usePrefixedClassNames } from '../../helpers/classNames';\nimport { useFocusTrap } from '../../helpers/useFocusTrap';\nimport { usePortalContainer } from '../../helpers/portal';\nimport {\n marginGap,\n useAnchoredPosition,\n} from '../../helpers/useAnchoredPosition';\nimport { PickerPosition } from './pickerTypes';\n\nexport interface PickerPopoverProps {\n isOpen: boolean;\n onClose: () => void;\n anchorRef: React.RefObject<HTMLElement | null>;\n children: React.ReactNode;\n position?: PickerPosition;\n appendToBody?: boolean;\n className?: string;\n trapFocus?: boolean;\n closeOnClickOutside?: boolean;\n /** Close on Escape and on Alt+ArrowUp. Default `true`. */\n closeOnEscape?: boolean;\n ariaLabel?: string;\n ariaLabelledBy?: string;\n role?: 'dialog' | 'group';\n id?: string;\n /**\n * Where focus goes when the popover closes. Without it, focus goes back to\n * whatever had it when the focus trap turned on, which is read after the\n * panel's content mounts: a calendar that focuses a cell as it mounts\n * would be recorded instead, and that cell leaves with the panel.\n */\n restoreFocusRef?: React.RefObject<HTMLElement | null>;\n /**\n * Selects the element in the panel that takes focus as it opens, such as a\n * calendar's focused date rather than the first button in its header. When\n * nothing matches, or the match can't take focus, the first tab stop does.\n */\n initialFocusSelector?: string;\n}\n\nconst isBrowser = typeof window !== 'undefined';\n\nexport const PickerPopover: React.FC<PickerPopoverProps> = ({\n isOpen,\n onClose,\n anchorRef,\n children,\n position = 'bottom-left',\n appendToBody = false,\n className,\n trapFocus = true,\n closeOnClickOutside = true,\n closeOnEscape = true,\n ariaLabel,\n ariaLabelledBy,\n role = 'dialog',\n id,\n restoreFocusRef,\n initialFocusSelector,\n}) => {\n const panelRef = useRef<HTMLDivElement>(null);\n const resolveContainer = usePortalContainer();\n // Read as the trap turns on, once the panel's content has rendered.\n const initialFocusRef = useMemo(\n () =>\n initialFocusSelector === undefined\n ? undefined\n : {\n get current() {\n return (\n panelRef.current?.querySelector<HTMLElement>(\n initialFocusSelector\n ) ?? null\n );\n },\n },\n [initialFocusSelector]\n );\n const resolved = useAnchoredPosition(anchorRef, panelRef, {\n active: isOpen,\n position,\n fixed: appendToBody,\n // The gap comes from `--bulma-picker-popover-offset` as a margin, in place\n // or portaled, so the coordinates leave it out and `auto` reads it off\n // the panel.\n offset: 0,\n styledGap: marginGap,\n });\n\n useEffect(() => {\n if (!isOpen || !closeOnClickOutside || !isBrowser) return undefined;\n // Listen on `pointerdown` (rather than `mousedown`) so touch starts\n // outside the popover dismiss it on mobile — including swipe-from-outside\n // gestures, where the synthetic mouse event would otherwise lag a few\n // hundred ms behind the actual touch.\n const handler = (e: PointerEvent) => {\n const path = e.composedPath();\n if (panelRef.current && path.includes(panelRef.current)) return;\n if (anchorRef.current && path.includes(anchorRef.current)) return;\n onClose();\n };\n document.addEventListener('pointerdown', handler);\n return () => document.removeEventListener('pointerdown', handler);\n }, [isOpen, closeOnClickOutside, anchorRef, onClose]);\n\n // Escape closes it, and so does Alt+ArrowUp, the combobox's key for it,\n // which the calendar and the time wheels inside leave to it.\n useEffect(() => {\n if (!isOpen || !closeOnEscape || !isBrowser) return undefined;\n const handler = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n e.stopPropagation();\n onClose();\n } else if (e.altKey && e.key === 'ArrowUp') {\n e.preventDefault();\n e.stopPropagation();\n onClose();\n }\n };\n document.addEventListener('keydown', handler);\n return () => document.removeEventListener('keydown', handler);\n }, [isOpen, closeOnEscape, onClose]);\n\n useFocusTrap(panelRef, {\n active: isOpen && trapFocus,\n initialFocusRef,\n restoreFocus: restoreFocusRef ?? true,\n });\n\n const panelClass = usePrefixedClassNames('picker-popover', {\n 'is-active': isOpen,\n [`is-${resolved.position}`]: true,\n 'is-portal': appendToBody,\n });\n\n if (!isOpen) return null;\n\n const style: React.CSSProperties = appendToBody\n ? {\n position: 'fixed',\n top: resolved.top,\n left: resolved.left,\n }\n : {};\n\n const panel = (\n <div\n ref={panelRef}\n id={id}\n role={role}\n aria-modal={role === 'dialog' ? 'false' : undefined}\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledBy}\n tabIndex={-1}\n className={classNames(panelClass, className)}\n style={style}\n >\n {children}\n </div>\n );\n\n if (appendToBody && isBrowser) {\n return createPortal(panel, resolveContainer());\n }\n return panel;\n};\n\nPickerPopover.displayName = 'PickerPopover';\n\nexport default PickerPopover;\n"],"names":["_jsx"],"mappings":"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"}
1
+ {"version":3,"file":"PickerPopover.js","sources":["../../../../src/form/_pickerInternals/PickerPopover.tsx"],"sourcesContent":["import React, { useEffect, useMemo, useRef } from 'react';\nimport { createPortal } from 'react-dom';\nimport { classNames, usePrefixedClassNames } from '../../helpers/classNames';\nimport { useFocusTrap } from '../../helpers/useFocusTrap';\nimport { usePortalContainer } from '../../helpers/portal';\nimport { isEventInside } from '../../helpers/shadowDom';\nimport {\n marginGap,\n useAnchoredPosition,\n} from '../../helpers/useAnchoredPosition';\nimport { PickerPosition } from './pickerTypes';\n\nexport interface PickerPopoverProps {\n isOpen: boolean;\n onClose: () => void;\n anchorRef: React.RefObject<HTMLElement | null>;\n children: React.ReactNode;\n position?: PickerPosition;\n appendToBody?: boolean;\n className?: string;\n trapFocus?: boolean;\n closeOnClickOutside?: boolean;\n /** Close on Escape and on Alt+ArrowUp. Default `true`. */\n closeOnEscape?: boolean;\n ariaLabel?: string;\n ariaLabelledBy?: string;\n role?: 'dialog' | 'group';\n id?: string;\n /**\n * Where focus goes when the popover closes. Without it, focus goes back to\n * whatever had it when the focus trap turned on, which is read after the\n * panel's content mounts: a calendar that focuses a cell as it mounts\n * would be recorded instead, and that cell leaves with the panel.\n */\n restoreFocusRef?: React.RefObject<HTMLElement | null>;\n /**\n * Selects the element in the panel that takes focus as it opens, such as a\n * calendar's focused date rather than the first button in its header. When\n * nothing matches, or the match can't take focus, the first tab stop does.\n */\n initialFocusSelector?: string;\n}\n\nconst isBrowser = typeof window !== 'undefined';\n\nexport const PickerPopover: React.FC<PickerPopoverProps> = ({\n isOpen,\n onClose,\n anchorRef,\n children,\n position = 'bottom-left',\n appendToBody = false,\n className,\n trapFocus = true,\n closeOnClickOutside = true,\n closeOnEscape = true,\n ariaLabel,\n ariaLabelledBy,\n role = 'dialog',\n id,\n restoreFocusRef,\n initialFocusSelector,\n}) => {\n const panelRef = useRef<HTMLDivElement>(null);\n const resolveContainer = usePortalContainer();\n // Read as the trap turns on, once the panel's content has rendered.\n const initialFocusRef = useMemo(\n () =>\n initialFocusSelector === undefined\n ? undefined\n : {\n get current() {\n return (\n panelRef.current?.querySelector<HTMLElement>(\n initialFocusSelector\n ) ?? null\n );\n },\n },\n [initialFocusSelector]\n );\n const resolved = useAnchoredPosition(anchorRef, panelRef, {\n active: isOpen,\n position,\n fixed: appendToBody,\n // The gap comes from `--bulma-picker-popover-offset` as a margin, in place\n // or portaled, so the coordinates leave it out and `auto` reads it off\n // the panel.\n offset: 0,\n styledGap: marginGap,\n });\n\n useEffect(() => {\n if (!isOpen || !closeOnClickOutside || !isBrowser) return undefined;\n // Listen on `pointerdown` (rather than `mousedown`) so touch starts\n // outside the popover dismiss it on mobile — including swipe-from-outside\n // gestures, where the synthetic mouse event would otherwise lag a few\n // hundred ms behind the actual touch.\n const handler = (e: PointerEvent) => {\n const path = e.composedPath();\n if (panelRef.current && path.includes(panelRef.current)) return;\n if (anchorRef.current && path.includes(anchorRef.current)) return;\n onClose();\n };\n document.addEventListener('pointerdown', handler);\n return () => document.removeEventListener('pointerdown', handler);\n }, [isOpen, closeOnClickOutside, anchorRef, onClose]);\n\n // Escape closes it, and so does Alt+ArrowUp, the combobox's key for it,\n // which the calendar and the time wheels inside leave to it. Escape closes\n // it from anywhere, but the key is only the picker's when it was pressed in\n // the panel or the field: then it's marked used, so a Modal or Sidebar\n // around the picker stays open. From anywhere else, such as a dialog opened\n // over the picker, the key goes on to whatever has focus.\n useEffect(() => {\n if (!isOpen || !closeOnEscape || !isBrowser) return undefined;\n const handler = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n if (\n isEventInside(e, panelRef.current) ||\n isEventInside(e, anchorRef.current)\n ) {\n e.preventDefault();\n }\n onClose();\n } else if (e.altKey && e.key === 'ArrowUp') {\n e.preventDefault();\n e.stopPropagation();\n onClose();\n }\n };\n document.addEventListener('keydown', handler);\n return () => document.removeEventListener('keydown', handler);\n }, [isOpen, closeOnEscape, anchorRef, onClose]);\n\n useFocusTrap(panelRef, {\n active: isOpen && trapFocus,\n initialFocusRef,\n restoreFocus: restoreFocusRef ?? true,\n });\n\n const panelClass = usePrefixedClassNames('picker-popover', {\n 'is-active': isOpen,\n [`is-${resolved.position}`]: true,\n 'is-portal': appendToBody,\n });\n\n if (!isOpen) return null;\n\n const style: React.CSSProperties = appendToBody\n ? {\n position: 'fixed',\n top: resolved.top,\n left: resolved.left,\n }\n : {};\n\n const panel = (\n <div\n ref={panelRef}\n id={id}\n role={role}\n aria-modal={role === 'dialog' ? 'false' : undefined}\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledBy}\n tabIndex={-1}\n className={classNames(panelClass, className)}\n style={style}\n >\n {children}\n </div>\n );\n\n if (appendToBody && isBrowser) {\n return createPortal(panel, resolveContainer());\n }\n return panel;\n};\n\nPickerPopover.displayName = 'PickerPopover';\n\nexport default PickerPopover;\n"],"names":["_jsx"],"mappings":";;;;;;;;;;AA2CA,MAAM,SAAS,GAAG,OAAO,MAAM,KAAK,WAAW;MAElC,aAAa,GAAiC,CAAC,EAC1D,MAAM,EACN,OAAO,EACP,SAAS,EACT,QAAQ,EACR,QAAQ,GAAG,aAAa,EACxB,YAAY,GAAG,KAAK,EACpB,SAAS,EACT,SAAS,GAAG,IAAI,EAChB,mBAAmB,GAAG,IAAI,EAC1B,aAAa,GAAG,IAAI,EACpB,SAAS,EACT,cAAc,EACd,IAAI,GAAG,QAAQ,EACf,EAAE,EACF,eAAe,EACf,oBAAoB,GACrB,KAAI;AACH,IAAA,MAAM,QAAQ,GAAG,MAAM,CAAiB,IAAI,CAAC;AAC7C,IAAA,MAAM,gBAAgB,GAAG,kBAAkB,EAAE;;IAE7C,MAAM,eAAe,GAAG,OAAO,CAC7B,MACE,oBAAoB,KAAK;AACvB,UAAE;AACF,UAAE;AACE,YAAA,IAAI,OAAO,GAAA;AACT,gBAAA,QACE,QAAQ,CAAC,OAAO,EAAE,aAAa,CAC7B,oBAAoB,CACrB,IAAI,IAAI;YAEb,CAAC;AACF,SAAA,EACP,CAAC,oBAAoB,CAAC,CACvB;AACD,IAAA,MAAM,QAAQ,GAAG,mBAAmB,CAAC,SAAS,EAAE,QAAQ,EAAE;AACxD,QAAA,MAAM,EAAE,MAAM;QACd,QAAQ;AACR,QAAA,KAAK,EAAE,YAAY;;;;AAInB,QAAA,MAAM,EAAE,CAAC;AACT,QAAA,SAAS,EAAE,SAAS;AACrB,KAAA,CAAC;IAEF,SAAS,CAAC,MAAK;AACb,QAAA,IAAI,CAAC,MAAM,IAAI,CAAC,mBAAmB,IAAI,CAAC,SAAS;AAAE,YAAA,OAAO,SAAS;;;;;AAKnE,QAAA,MAAM,OAAO,GAAG,CAAC,CAAe,KAAI;AAClC,YAAA,MAAM,IAAI,GAAG,CAAC,CAAC,YAAY,EAAE;YAC7B,IAAI,QAAQ,CAAC,OAAO,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC;gBAAE;YACzD,IAAI,SAAS,CAAC,OAAO,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,OAAO,CAAC;gBAAE;AAC3D,YAAA,OAAO,EAAE;AACX,QAAA,CAAC;AACD,QAAA,QAAQ,CAAC,gBAAgB,CAAC,aAAa,EAAE,OAAO,CAAC;QACjD,OAAO,MAAM,QAAQ,CAAC,mBAAmB,CAAC,aAAa,EAAE,OAAO,CAAC;IACnE,CAAC,EAAE,CAAC,MAAM,EAAE,mBAAmB,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;;;;;;;IAQrD,SAAS,CAAC,MAAK;AACb,QAAA,IAAI,CAAC,MAAM,IAAI,CAAC,aAAa,IAAI,CAAC,SAAS;AAAE,YAAA,OAAO,SAAS;AAC7D,QAAA,MAAM,OAAO,GAAG,CAAC,CAAgB,KAAI;AACnC,YAAA,IAAI,CAAC,CAAC,GAAG,KAAK,QAAQ,EAAE;AACtB,gBAAA,IACE,aAAa,CAAC,CAAC,EAAE,QAAQ,CAAC,OAAO,CAAC;oBAClC,aAAa,CAAC,CAAC,EAAE,SAAS,CAAC,OAAO,CAAC,EACnC;oBACA,CAAC,CAAC,cAAc,EAAE;gBACpB;AACA,gBAAA,OAAO,EAAE;YACX;iBAAO,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,GAAG,KAAK,SAAS,EAAE;gBAC1C,CAAC,CAAC,cAAc,EAAE;gBAClB,CAAC,CAAC,eAAe,EAAE;AACnB,gBAAA,OAAO,EAAE;YACX;AACF,QAAA,CAAC;AACD,QAAA,QAAQ,CAAC,gBAAgB,CAAC,SAAS,EAAE,OAAO,CAAC;QAC7C,OAAO,MAAM,QAAQ,CAAC,mBAAmB,CAAC,SAAS,EAAE,OAAO,CAAC;IAC/D,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IAE/C,YAAY,CAAC,QAAQ,EAAE;QACrB,MAAM,EAAE,MAAM,IAAI,SAAS;QAC3B,eAAe;QACf,YAAY,EAAE,eAAe,IAAI,IAAI;AACtC,KAAA,CAAC;AAEF,IAAA,MAAM,UAAU,GAAG,qBAAqB,CAAC,gBAAgB,EAAE;AACzD,QAAA,WAAW,EAAE,MAAM;AACnB,QAAA,CAAC,MAAM,QAAQ,CAAC,QAAQ,CAAA,CAAE,GAAG,IAAI;AACjC,QAAA,WAAW,EAAE,YAAY;AAC1B,KAAA,CAAC;AAEF,IAAA,IAAI,CAAC,MAAM;AAAE,QAAA,OAAO,IAAI;IAExB,MAAM,KAAK,GAAwB;AACjC,UAAE;AACE,YAAA,QAAQ,EAAE,OAAO;YACjB,GAAG,EAAE,QAAQ,CAAC,GAAG;YACjB,IAAI,EAAE,QAAQ,CAAC,IAAI;AACpB;UACD,EAAE;IAEN,MAAM,KAAK,IACTA,GAAA,CAAA,KAAA,EAAA,EACE,GAAG,EAAE,QAAQ,EACb,EAAE,EAAE,EAAE,EACN,IAAI,EAAE,IAAI,EAAA,YAAA,EACE,IAAI,KAAK,QAAQ,GAAG,OAAO,GAAG,SAAS,EAAA,YAAA,EACvC,SAAS,EAAA,iBAAA,EACJ,cAAc,EAC/B,QAAQ,EAAE,EAAE,EACZ,SAAS,EAAE,UAAU,CAAC,UAAU,EAAE,SAAS,CAAC,EAC5C,KAAK,EAAE,KAAK,EAAA,QAAA,EAEX,QAAQ,EAAA,CACL,CACP;AAED,IAAA,IAAI,YAAY,IAAI,SAAS,EAAE;AAC7B,QAAA,OAAO,YAAY,CAAC,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAChD;AACA,IAAA,OAAO,KAAK;AACd;AAEA,aAAa,CAAC,WAAW,GAAG,eAAe;;"}
package/dist/index.cjs CHANGED
@@ -2563,8 +2563,9 @@ const isBrowser$1 = (win, doc) => typeof win !== 'undefined' && typeof doc !== '
2563
2563
  * the menu, so a letter typed on the trigger does nothing. Escape closes the
2564
2564
  * menu and focuses the trigger, and Tab closes it and lets focus move on.
2565
2565
  * Anything focusable in the menu that isn't an item, such as a field, keeps
2566
- * its own keys apart from Escape and Tab. For a panel built around fields,
2567
- * use `Popover`.
2566
+ * its own keys apart from Escape and Tab, though an Escape it calls
2567
+ * `preventDefault()` on in its own `onKeyDown` stays its own and leaves the
2568
+ * menu open. For a panel built around fields, use `Popover`.
2568
2569
  *
2569
2570
  * @function
2570
2571
  * @param {DropdownProps} props - Props for the Dropdown component.
@@ -2741,8 +2742,12 @@ const DropdownComponent = React.forwardRef(function DropdownComponent({ label, c
2741
2742
  // Closing is Dropdown's: Escape and Tab close the menu from anywhere in
2742
2743
  // it, a field included, and an empty or all-disabled menu too. Moving
2743
2744
  // between the items, running them and typeahead are the hook's, which
2744
- // acts only while focus is on the menu or one of its items.
2745
+ // acts only while focus is on the menu or one of its items. An Escape
2746
+ // something in the menu already used, such as a field's own list
2747
+ // closing on it, leaves the menu open for the next one.
2745
2748
  if (e.key === 'Escape') {
2749
+ if (e.defaultPrevented)
2750
+ return;
2746
2751
  e.preventDefault();
2747
2752
  setActive(false);
2748
2753
  onActiveChange?.(false);
@@ -4056,6 +4061,9 @@ const ModalRoot = React.forwardRef(function ModalRoot({ active, isActive, onClos
4056
4061
  };
4057
4062
  }, [isModalActive]);
4058
4063
  // Escape closes; Tab cycles inside the modal. Both only for the topmost one.
4064
+ // The listener is on `window`, where a keydown arrives last, so everything
4065
+ // inside the modal has had the key first: React's handlers, and `document`
4066
+ // listeners added after this one, such as a date picker's.
4059
4067
  React.useEffect(() => {
4060
4068
  if (!isModalActive)
4061
4069
  return undefined;
@@ -4065,11 +4073,15 @@ const ModalRoot = React.forwardRef(function ModalRoot({ active, isActive, onClos
4065
4073
  return;
4066
4074
  }
4067
4075
  if (e.key === 'Escape') {
4068
- if (closeOnEscape)
4076
+ // Something inside already used this Escape, such as a popup that
4077
+ // closed on it, so the modal waits for the next one.
4078
+ if (closeOnEscape && !e.defaultPrevented)
4069
4079
  onClose?.();
4070
4080
  return;
4071
4081
  }
4072
- if (e.key !== 'Tab' || !node)
4082
+ // A trap inside the modal, such as a portaled popup's, already moved
4083
+ // focus on this Tab, so the modal leaves it where it went.
4084
+ if (e.key !== 'Tab' || !node || e.defaultPrevented)
4073
4085
  return;
4074
4086
  // Keep Tab within the modal — `aria-modal` hides the rest of the page
4075
4087
  // from assistive technology, so the keyboard order has to agree.
@@ -4094,8 +4106,8 @@ const ModalRoot = React.forwardRef(function ModalRoot({ active, isActive, onClos
4094
4106
  first.focus();
4095
4107
  }
4096
4108
  };
4097
- document.addEventListener('keydown', handleKeyDown);
4098
- return () => document.removeEventListener('keydown', handleKeyDown);
4109
+ window.addEventListener('keydown', handleKeyDown);
4110
+ return () => window.removeEventListener('keydown', handleKeyDown);
4099
4111
  }, [isModalActive, closeOnEscape, onClose]);
4100
4112
  // Move focus into the modal on open, restore it on close
4101
4113
  React.useEffect(() => {
@@ -5375,17 +5387,18 @@ const Loading = ({ active = false, isFullPage = false, size, color, canCancel =
5375
5387
  onCancel();
5376
5388
  }
5377
5389
  };
5378
- // Handle escape key
5390
+ // Handle escape key. On `window`, as in `Modal`, so a popup on the page has
5391
+ // had the key first, and an Escape it closed on doesn't cancel as well.
5379
5392
  React.useEffect(() => {
5380
5393
  if (!active || !canCancel || !onCancel)
5381
5394
  return;
5382
5395
  const handleKeyDown = (e) => {
5383
- if (e.key === 'Escape') {
5396
+ if (e.key === 'Escape' && !e.defaultPrevented) {
5384
5397
  onCancel();
5385
5398
  }
5386
5399
  };
5387
- document.addEventListener('keydown', handleKeyDown);
5388
- return () => document.removeEventListener('keydown', handleKeyDown);
5400
+ window.addEventListener('keydown', handleKeyDown);
5401
+ return () => window.removeEventListener('keydown', handleKeyDown);
5389
5402
  }, [active, canCancel, onCancel]);
5390
5403
  // Prevent body scroll when full page loading is active. Ref-counted through
5391
5404
  // the shared helper so an overlapping Modal/Dialog/Sidebar doesn't have its
@@ -7795,17 +7808,18 @@ const SidebarComponent = React.forwardRef(({ isOpen, onClose, position = 'left',
7795
7808
  handleClose();
7796
7809
  }
7797
7810
  }, [overlayClose, handleClose]);
7798
- // Handle escape key
7811
+ // Handle escape key. On `window`, as in `Modal`, so everything inside the
7812
+ // sidebar has had the key first, and an Escape it used is left to it.
7799
7813
  React.useEffect(() => {
7800
7814
  if (!isOpen || !escapeClose)
7801
7815
  return;
7802
7816
  const handleKeyDown = (e) => {
7803
- if (e.key === 'Escape') {
7817
+ if (e.key === 'Escape' && !e.defaultPrevented) {
7804
7818
  handleClose();
7805
7819
  }
7806
7820
  };
7807
- document.addEventListener('keydown', handleKeyDown);
7808
- return () => document.removeEventListener('keydown', handleKeyDown);
7821
+ window.addEventListener('keydown', handleKeyDown);
7822
+ return () => window.removeEventListener('keydown', handleKeyDown);
7809
7823
  }, [isOpen, escapeClose, handleClose]);
7810
7824
  // Prevent body scroll when sidebar is open. Ref-counted through the shared
7811
7825
  // helper so an overlapping Modal/Dialog/Loading doesn't unlock underneath.
@@ -8060,17 +8074,19 @@ const Toast = React.forwardRef(({ message, type = 'default', actionType, positio
8060
8074
  document.removeEventListener('click', handleDocumentClick);
8061
8075
  };
8062
8076
  }, [dismissible, isVisible, handleClose]);
8077
+ // On `window`, as in `Modal`, so a popup on the page has had the key
8078
+ // first, and an Escape it closed on doesn't dismiss the toast as well.
8063
8079
  React.useEffect(() => {
8064
- if (!cancelable)
8080
+ if (!cancelable || !isVisible)
8065
8081
  return undefined;
8066
8082
  const handleKeyDown = (e) => {
8067
- if (e.key === 'Escape') {
8083
+ if (e.key === 'Escape' && !e.defaultPrevented) {
8068
8084
  handleClose();
8069
8085
  }
8070
8086
  };
8071
- document.addEventListener('keydown', handleKeyDown);
8072
- return () => document.removeEventListener('keydown', handleKeyDown);
8073
- }, [cancelable, handleClose]);
8087
+ window.addEventListener('keydown', handleKeyDown);
8088
+ return () => window.removeEventListener('keydown', handleKeyDown);
8089
+ }, [cancelable, isVisible, handleClose]);
8074
8090
  const containerClasses = usePrefixedClassNames('toast-container', {
8075
8091
  [`is-${position}`]: true,
8076
8092
  });
@@ -12655,6 +12671,11 @@ const nothingToAnnounce = [];
12655
12671
  * />
12656
12672
  */
12657
12673
  const Autocomplete = React.forwardRef(({ data = [], value: controlledValue, placeholder, field = 'label', clearable = false, openOnFocus = false, keepFirst = false, keepOpen = false, selectOnClickOutside = false, maxHeight = 200, loading = false, disabled = false, checkInfiniteScroll = false, infiniteScrollDistance = 50, color, size, onInput, onSelect, onActiveChange, onInfiniteScroll, itemTemplate, header, footer, empty, name, form, required, id,
12674
+ // Deprecated no-ops, taken here so they stay off the wrapper.
12675
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars -- deprecated no-op
12676
+ selected,
12677
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars -- deprecated no-op
12678
+ dropdown,
12658
12679
  // Field props
12659
12680
  label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName, className, ...props }, ref) => {
12660
12681
  const insideField = useInsideField();
@@ -12807,7 +12828,10 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
12807
12828
  }
12808
12829
  break;
12809
12830
  case 'Escape':
12810
- e.preventDefault();
12831
+ // Only closing the dropdown claims the key. With it closed, the
12832
+ // key goes on to a Modal around the field.
12833
+ if (menuOpen)
12834
+ e.preventDefault();
12811
12835
  setIsActive(false);
12812
12836
  setHighlightedIndex(-1);
12813
12837
  break;
@@ -12818,7 +12842,14 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
12818
12842
  setIsActive(false);
12819
12843
  break;
12820
12844
  }
12821
- }, [disabled, isActive, highlightedIndex, filteredData, handleSelect]);
12845
+ }, [
12846
+ disabled,
12847
+ isActive,
12848
+ menuOpen,
12849
+ highlightedIndex,
12850
+ filteredData,
12851
+ handleSelect,
12852
+ ]);
12822
12853
  // Handle dropdown scroll for infinite scroll
12823
12854
  const handleDropdownScroll = React.useCallback(() => {
12824
12855
  if (!checkInfiniteScroll || !dropdownRef.current)
@@ -13192,6 +13223,10 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
13192
13223
  }
13193
13224
  break;
13194
13225
  case 'Escape':
13226
+ // Closing the suggestions claims the key, so a Modal around the
13227
+ // field stays open. With none showing, the key goes on to it.
13228
+ if (isActive && filteredData.length > 0)
13229
+ e.preventDefault();
13195
13230
  setIsActive(false);
13196
13231
  setHighlightedIndex(-1);
13197
13232
  break;
@@ -15053,13 +15088,20 @@ const PickerPopover = ({ isOpen, onClose, anchorRef, children, position = 'botto
15053
15088
  return () => document.removeEventListener('pointerdown', handler);
15054
15089
  }, [isOpen, closeOnClickOutside, anchorRef, onClose]);
15055
15090
  // Escape closes it, and so does Alt+ArrowUp, the combobox's key for it,
15056
- // which the calendar and the time wheels inside leave to it.
15091
+ // which the calendar and the time wheels inside leave to it. Escape closes
15092
+ // it from anywhere, but the key is only the picker's when it was pressed in
15093
+ // the panel or the field: then it's marked used, so a Modal or Sidebar
15094
+ // around the picker stays open. From anywhere else, such as a dialog opened
15095
+ // over the picker, the key goes on to whatever has focus.
15057
15096
  React.useEffect(() => {
15058
15097
  if (!isOpen || !closeOnEscape || !isBrowser)
15059
15098
  return undefined;
15060
15099
  const handler = (e) => {
15061
15100
  if (e.key === 'Escape') {
15062
- e.stopPropagation();
15101
+ if (isEventInside(e, panelRef.current) ||
15102
+ isEventInside(e, anchorRef.current)) {
15103
+ e.preventDefault();
15104
+ }
15063
15105
  onClose();
15064
15106
  }
15065
15107
  else if (e.altKey && e.key === 'ArrowUp') {
@@ -15070,7 +15112,7 @@ const PickerPopover = ({ isOpen, onClose, anchorRef, children, position = 'botto
15070
15112
  };
15071
15113
  document.addEventListener('keydown', handler);
15072
15114
  return () => document.removeEventListener('keydown', handler);
15073
- }, [isOpen, closeOnEscape, onClose]);
15115
+ }, [isOpen, closeOnEscape, anchorRef, onClose]);
15074
15116
  useFocusTrap(panelRef, {
15075
15117
  active: isOpen && trapFocus,
15076
15118
  initialFocusRef,