@allxsmith/bestax-bulma 5.22.0 → 5.23.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.
package/dist/index.esm.js CHANGED
@@ -2254,10 +2254,12 @@ function getActiveElementInTree(node) {
2254
2254
  * opener sits in. A closed shadow root cannot be entered, so focus inside one
2255
2255
  * reads as its host.
2256
2256
  *
2257
+ * @param doc - The document to start from: the component's own, when it may
2258
+ * render into one other than the page's (an iframe's). Defaults to `document`.
2257
2259
  * @returns The focused element, or `null` when the document has none.
2258
2260
  */
2259
- function getDeepestActiveElement() {
2260
- let active = document.activeElement;
2261
+ function getDeepestActiveElement(doc = document) {
2262
+ let active = doc.activeElement;
2261
2263
  while (active?.shadowRoot?.activeElement) {
2262
2264
  active = active.shadowRoot.activeElement;
2263
2265
  }
@@ -2794,6 +2796,32 @@ function useScrollLock(active) {
2794
2796
  }, [active]);
2795
2797
  }
2796
2798
 
2799
+ // "Are we past hydration?" as a store that never changes: React reads the
2800
+ // server snapshot both while server-rendering and while hydrating, and the
2801
+ // client snapshot from the first post-hydration render onward.
2802
+ const subscribeToNothing = () => () => { };
2803
+ const getClientSnapshot = () => true;
2804
+ const getServerSnapshot = () => false;
2805
+ /**
2806
+ * Reports whether the component has passed hydration.
2807
+ *
2808
+ * Returns `false` during server rendering and during the hydrating client
2809
+ * render, then `true` from the commit that follows. Use it to defer
2810
+ * DOM-only behaviour (such as portalling) past hydration so the first client
2811
+ * render matches the server markup instead of tripping hydration recovery.
2812
+ *
2813
+ * Only a component that hydrates ever sees `false` on the client. One that
2814
+ * mounts later (after a route change, or in an app with no server rendering)
2815
+ * gets `true` on its first render, so it pays for no extra render. It
2816
+ * subscribes to nothing, so no later render flips it back.
2817
+ *
2818
+ * @function useIsHydrated
2819
+ * @returns `true` once the hydrating render has completed, otherwise `false`.
2820
+ */
2821
+ function useIsHydrated() {
2822
+ return useSyncExternalStore(subscribeToNothing, getClientSnapshot, getServerSnapshot);
2823
+ }
2824
+
2797
2825
  /**
2798
2826
  * Resolves a portal target: an `HTMLElement` is used directly, a non-empty
2799
2827
  * `string` is treated as a `document.querySelector` selector (falling back to
@@ -2817,31 +2845,49 @@ function resolvePortalContainer(container) {
2817
2845
  }
2818
2846
  return container;
2819
2847
  }
2820
-
2821
- // "Are we past hydration?" as a store that never changes: React reads the
2822
- // server snapshot both while server-rendering and while hydrating, and the
2823
- // client snapshot from the first post-hydration render onward.
2824
- const subscribeToNothing = () => () => { };
2825
- const getClientSnapshot = () => true;
2826
- const getServerSnapshot = () => false;
2827
2848
  /**
2828
- * Reports whether the component has passed hydration.
2849
+ * Renders its children into another part of the page, `document.body` unless `container` says otherwise, so floating content escapes an ancestor's `overflow`, `transform` or stacking context.
2829
2850
  *
2830
- * Returns `false` during server rendering and during the hydrating client
2831
- * render, then `true` from the commit that follows. Use it to defer
2832
- * DOM-only behaviour (such as portalling) past hydration so the first client
2833
- * render matches the server markup instead of tripping hydration recovery.
2851
+ * React's server renderer can't render a portal, so `Portal` renders nothing
2852
+ * on the server and during hydration, and mounts its children in the commit
2853
+ * that follows. The first client render then matches the server markup. In an app
2854
+ * without server rendering it portals on the first render.
2834
2855
  *
2835
- * Only a component that hydrates ever sees `false` on the client. One that
2836
- * mounts later (after a route change, or in an app with no server rendering)
2837
- * gets `true` on its first render, so it pays for no extra render. It
2838
- * subscribes to nothing, so no later render flips it back.
2856
+ * React context, such as `ConfigProvider`'s class prefix, reaches the
2857
+ * portaled children, and their React events bubble through the React tree.
2858
+ * CSS inheritance follows the DOM instead: a scoped (non-`isRoot`) `Theme`
2859
+ * sets its variables on a wrapper element the portaled content has left, so
2860
+ * that content sees the page-level theme. Point `container` at an element
2861
+ * inside the themed area to keep it.
2839
2862
  *
2840
- * @function useIsHydrated
2841
- * @returns `true` once the hydrating render has completed, otherwise `false`.
2863
+ * Tab order and focus follow the DOM too. Content portaled from inside a
2864
+ * focus trap is no longer inside the trapped element, so the trap neither
2865
+ * reaches it nor holds focus there: render nested overlays inside the
2866
+ * trapped element, or give them a trap of their own.
2867
+ *
2868
+ * @function
2869
+ * @param {PortalProps} props - Props for the Portal component.
2870
+ * @returns The portal, the children in place when `disabled`, or nothing
2871
+ * before hydration.
2872
+ *
2873
+ * @example
2874
+ * <Portal>
2875
+ * <div className="box">Rendered at the end of document.body</div>
2876
+ * </Portal>
2877
+ *
2878
+ * @example
2879
+ * // Into a dedicated overlay root
2880
+ * <Portal container="#overlays">
2881
+ * <CommandPalette />
2882
+ * </Portal>
2842
2883
  */
2843
- function useIsHydrated() {
2844
- return useSyncExternalStore(subscribeToNothing, getClientSnapshot, getServerSnapshot);
2884
+ function Portal({ children, container, disabled = false }) {
2885
+ const hydrated = useIsHydrated();
2886
+ if (disabled)
2887
+ return jsx(Fragment, { children: children });
2888
+ if (!hydrated)
2889
+ return null;
2890
+ return createPortal(children, resolvePortalContainer(container));
2845
2891
  }
2846
2892
 
2847
2893
  /**
@@ -2855,7 +2901,7 @@ const activeModalStack = [];
2855
2901
  * excluded because `focus()` on them is a no-op, which would leave focus
2856
2902
  * outside the modal.
2857
2903
  */
2858
- const FOCUSABLE_SELECTOR$1 = 'button:not(:disabled), [href], input:not(:disabled):not([type="hidden"]), select:not(:disabled), textarea:not(:disabled), [tabindex]';
2904
+ const FOCUSABLE_SELECTOR = 'button:not(:disabled), [href], input:not(:disabled):not([type="hidden"]), select:not(:disabled), textarea:not(:disabled), [tabindex]';
2859
2905
  /**
2860
2906
  * The modal's tab stops, in document order. The selector alone is not the
2861
2907
  * tabbable set — `[href]` and `button` match regardless of `tabindex`, so an
@@ -2864,7 +2910,7 @@ const FOCUSABLE_SELECTOR$1 = 'button:not(:disabled), [href], input:not(:disabled
2864
2910
  * resolved value, so filtering on it drops every negative index (not just
2865
2911
  * `-1`) without a second selector to keep in sync.
2866
2912
  */
2867
- const getTabbable = (node) => Array.from(node.querySelectorAll(FOCUSABLE_SELECTOR$1)).filter(el => el.tabIndex >= 0);
2913
+ const getTabbable = (node) => Array.from(node.querySelectorAll(FOCUSABLE_SELECTOR)).filter(el => el.tabIndex >= 0);
2868
2914
  /**
2869
2915
  * Modal.Background - Renders the modal background overlay.
2870
2916
  *
@@ -10924,63 +10970,395 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min, max,
10924
10970
  };
10925
10971
  Calendar.displayName = 'Calendar';
10926
10972
 
10927
- const FOCUSABLE_SELECTOR = [
10928
- 'a[href]',
10929
- 'button:not([disabled])',
10930
- 'input:not([disabled]):not([type="hidden"])',
10931
- 'select:not([disabled])',
10932
- 'textarea:not([disabled])',
10933
- '[tabindex]:not([tabindex="-1"])',
10934
- ].join(',');
10935
- function useFocusTrap(containerRef, active, options = {}) {
10936
- const { initialFocusRef, restoreFocus = true } = options;
10973
+ /**
10974
+ * Whether `el` is editable while its parent is not, which makes it an editing
10975
+ * host: the one tab stop an editable region gets. `contenteditable="false"`
10976
+ * turns editing off, and a missing or unknown value inherits the parent's.
10977
+ */
10978
+ function isEditingHost(el) {
10979
+ const editable = (node) => {
10980
+ const value = node.getAttribute('contenteditable')?.toLowerCase();
10981
+ if (value === undefined)
10982
+ return null;
10983
+ if (value === '' || value === 'true' || value === 'plaintext-only') {
10984
+ return true;
10985
+ }
10986
+ return value === 'false' ? false : null;
10987
+ };
10988
+ if (editable(el) !== true)
10989
+ return false;
10990
+ for (let node = el.parentElement; node; node = node.parentElement) {
10991
+ const state = editable(node);
10992
+ if (state !== null)
10993
+ return !state;
10994
+ }
10995
+ return true;
10996
+ }
10997
+ /** Whether `el` is the summary of its `<details>`: the first one, not a stray. */
10998
+ const isDetailsSummary = (el) => el.parentElement?.localName === 'details' &&
10999
+ Array.from(el.parentElement.children).find(child => child.localName === 'summary') === el;
11000
+ /**
11001
+ * Whether the browser puts `el` in the tab order, before anything that hides
11002
+ * or disables it. The `tabIndex` property can't answer this on its own: it
11003
+ * can read `-1` for an editable region or an `<embed>` that Tab still visits,
11004
+ * and `0` for a `<video>` without controls that it doesn't. So a valid
11005
+ * `tabindex` attribute decides, and otherwise the element's kind does.
11006
+ */
11007
+ function isTabbableKind(el) {
11008
+ const tabindex = el.getAttribute('tabindex');
11009
+ if (tabindex !== null && /^\s*[+-]?\d/.test(tabindex)) {
11010
+ return el.tabIndex >= 0;
11011
+ }
11012
+ switch (el.localName) {
11013
+ case 'a':
11014
+ case 'area':
11015
+ return el.hasAttribute('href');
11016
+ case 'input':
11017
+ return el.type !== 'hidden';
11018
+ case 'button':
11019
+ case 'select':
11020
+ case 'textarea':
11021
+ case 'iframe':
11022
+ case 'embed':
11023
+ case 'object':
11024
+ return true;
11025
+ case 'audio':
11026
+ case 'video':
11027
+ return el.hasAttribute('controls');
11028
+ case 'summary':
11029
+ return isDetailsSummary(el);
11030
+ default:
11031
+ return isEditingHost(el);
11032
+ }
11033
+ }
11034
+ /**
11035
+ * The node's parent in the flattened tree, the one the browser renders and
11036
+ * tabs through: the slot it is assigned to, else its parent, else the host of
11037
+ * the shadow root it tops, and nothing above the document. A closed shadow
11038
+ * root hides its slots (`assignedSlot` reads `null` under one), so a child of
11039
+ * its host goes straight to the host, whether a slot takes it or not.
11040
+ */
11041
+ const flatParent = (node) => node.assignedSlot ??
11042
+ node.parentElement ??
11043
+ node.parentNode?.host ??
11044
+ null;
11045
+ /**
11046
+ * The element's children in the flattened tree, in the order they render: a
11047
+ * host's shadow root content, a slot's assigned elements (or its fallback
11048
+ * content when nothing is assigned), anything else's own children. A host
11049
+ * whose shadow root is closed has no root to read, so its own children stand
11050
+ * in, including any that no slot takes and that aren't rendered.
11051
+ */
11052
+ function flatChildren(el) {
11053
+ if (el.shadowRoot)
11054
+ return Array.from(el.shadowRoot.children);
11055
+ if (el.localName === 'slot') {
11056
+ const assigned = el.assignedNodes();
11057
+ if (assigned.length > 0) {
11058
+ return assigned.filter((node) => node.nodeType === Node.ELEMENT_NODE);
11059
+ }
11060
+ }
11061
+ return Array.from(el.children);
11062
+ }
11063
+ const isShown = (el) => !el.hasAttribute('inert') && getComputedStyle(el).display !== 'none';
11064
+ const isVisible = (el) => {
11065
+ const { visibility } = getComputedStyle(el);
11066
+ return visibility !== 'hidden' && visibility !== 'collapse';
11067
+ };
11068
+ /**
11069
+ * Whether nothing from `el` up to `top` hides it: `inert`, `display: none`
11070
+ * (not inherited, so each element is checked) or a closed `<details>` it
11071
+ * isn't the summary of. `top` itself isn't checked, and `null` walks to the
11072
+ * top of the page.
11073
+ */
11074
+ function isShownUpTo(el, top) {
11075
+ let below = null;
11076
+ for (let node = el; node && node !== top; node = flatParent(node)) {
11077
+ if (!isShown(node))
11078
+ return false;
11079
+ if (below &&
11080
+ node.localName === 'details' &&
11081
+ !node.hasAttribute('open') &&
11082
+ !isDetailsSummary(below)) {
11083
+ return false;
11084
+ }
11085
+ below = node;
11086
+ }
11087
+ return true;
11088
+ }
11089
+ /**
11090
+ * The images that use the image map `area` belongs to. An image names its
11091
+ * map with `usemap="#…"`, matching the map's `name` or `id`.
11092
+ */
11093
+ function imagesUsingMapOf(area) {
11094
+ const map = area.closest('map');
11095
+ if (!map)
11096
+ return [];
11097
+ const names = [map.getAttribute('name'), map.id].filter(name => name);
11098
+ return Array.from(area.getRootNode().querySelectorAll('img[usemap]')).filter(img => {
11099
+ const ref = img.getAttribute('usemap');
11100
+ return ref.startsWith('#') && names.includes(ref.slice(1));
11101
+ });
11102
+ }
11103
+ /**
11104
+ * Whether an image map area is shown. An area has no box of its own: the
11105
+ * HTML rendering rules give it `display: none`, though a browser may not
11106
+ * report that. So it counts where an image using its map is shown, as long
11107
+ * as nothing makes it inert.
11108
+ */
11109
+ function isAreaShown(area, container) {
11110
+ for (let node = area; node && node !== container; node = flatParent(node)) {
11111
+ if (node.hasAttribute('inert'))
11112
+ return false;
11113
+ }
11114
+ return imagesUsingMapOf(area).some(img => isShownUpTo(img, null) && isVisible(img));
11115
+ }
11116
+ /**
11117
+ * Whether Tab can land on `el`, one of the kinds it visits. A disabled control
11118
+ * (including one inside a disabled `<fieldset>`), an inert subtree and
11119
+ * anything not rendered are skipped. `visibility` is inherited, so the
11120
+ * element's own computed value covers its ancestors.
11121
+ */
11122
+ function isRenderedAndEnabled(el, container) {
11123
+ if (el.matches(':disabled'))
11124
+ return false;
11125
+ if (el.localName === 'area')
11126
+ return isAreaShown(el, container);
11127
+ return isShownUpTo(el, container) && isVisible(el);
11128
+ }
11129
+ /** A radio button with a name, which shares one tab stop with its group. */
11130
+ const isGroupedRadio = (el) => el.localName === 'input' &&
11131
+ el.type === 'radio' &&
11132
+ el.name !== '';
11133
+ /** The flattened-tree ancestors of `node` below the container, outermost first, then `node`. */
11134
+ function flatChain(node, container) {
11135
+ const chain = [];
11136
+ for (let n = node; n && n !== container; n = flatParent(n)) {
11137
+ chain.unshift(n);
11138
+ }
11139
+ return chain;
11140
+ }
11141
+ /**
11142
+ * Whether `node` is inside the container in the flattened tree: in it, in a
11143
+ * shadow root it holds, or slotted into it. A child of a closed-root host
11144
+ * counts as inside wherever the host is, as its slot can't be seen.
11145
+ */
11146
+ function inside(container, node) {
11147
+ for (let n = node; n; n = flatParent(n)) {
11148
+ if (n === container)
11149
+ return true;
11150
+ }
11151
+ return false;
11152
+ }
11153
+ /**
11154
+ * `true` when Tab reaches `b` after `a`, which is their order in the
11155
+ * flattened tree: a shadow host comes before its shadow root's content, and
11156
+ * slotted content comes where its slot is. A closed root's slots can't be
11157
+ * seen, so its host's children keep their place in the light DOM. Two
11158
+ * flattened-tree siblings are always in one DOM tree, so their document
11159
+ * position decides between them.
11160
+ */
11161
+ function follows(container, a, b) {
11162
+ const chainA = flatChain(a, container);
11163
+ const chainB = flatChain(b, container);
11164
+ for (let i = 0; i < Math.max(chainA.length, chainB.length); i++) {
11165
+ const [x, y] = [chainA[i], chainB[i]];
11166
+ if (x === y)
11167
+ continue;
11168
+ if (!x)
11169
+ return true;
11170
+ if (!y)
11171
+ return false;
11172
+ return Boolean(x.compareDocumentPosition(y) & Node.DOCUMENT_POSITION_FOLLOWING);
11173
+ }
11174
+ return false;
11175
+ }
11176
+ /**
11177
+ * Every element of a tabbable kind under `el` in the flattened tree, depth
11178
+ * first, which is the order Tab visits them. Under a host whose shadow root
11179
+ * is closed, that takes in the host's own children instead, rendered or not.
11180
+ */
11181
+ function collectCandidates(el, out = []) {
11182
+ for (const child of flatChildren(el)) {
11183
+ if (isTabbableKind(child))
11184
+ out.push(child);
11185
+ collectCandidates(child, out);
11186
+ }
11187
+ return out;
11188
+ }
11189
+ /**
11190
+ * The first and last tab stops inside `container`, or `null` when there are
11191
+ * none. Only the ends matter for wrapping, so the style checks run from each
11192
+ * end until they find a stop rather than over every candidate.
11193
+ *
11194
+ * A radio group is one stop, the way the browser treats it: its checked
11195
+ * button, or its first when none is checked. Counting every button would make
11196
+ * the group's last one an end, and Tab from the checked one would leave.
11197
+ */
11198
+ function findTabStops(container) {
11199
+ const candidates = collectCandidates(container);
11200
+ const checked = new Map();
11201
+ const usable = (el) => {
11202
+ let ok = checked.get(el);
11203
+ if (ok === undefined) {
11204
+ ok = isRenderedAndEnabled(el, container);
11205
+ checked.set(el, ok);
11206
+ }
11207
+ return ok;
11208
+ };
11209
+ const groupStop = (radio) => {
11210
+ const group = candidates.filter((el) => isGroupedRadio(el) &&
11211
+ el.name === radio.name &&
11212
+ el.form === radio.form &&
11213
+ el.getRootNode() === radio.getRootNode());
11214
+ return group.find(el => el.checked && usable(el)) ?? group.find(usable);
11215
+ };
11216
+ const isStop = (el) => usable(el) && (!isGroupedRadio(el) || groupStop(el) === el);
11217
+ const first = candidates.find(isStop);
11218
+ if (!first)
11219
+ return null;
11220
+ let last = first;
11221
+ for (let i = candidates.length - 1; candidates[i] !== first; i--) {
11222
+ if (isStop(candidates[i])) {
11223
+ last = candidates[i];
11224
+ break;
11225
+ }
11226
+ }
11227
+ return {
11228
+ first,
11229
+ last,
11230
+ stopFor: node => (isGroupedRadio(node) && groupStop(node)) || node,
11231
+ };
11232
+ }
11233
+ /**
11234
+ * The element that has focus in the container's own document (an iframe's,
11235
+ * when it renders into one), inside open shadow roots too. Focus inside a
11236
+ * closed one reads as its host, which the comparisons above place like any
11237
+ * other element. Browsers fall back to <body> when nothing has
11238
+ * focus. jsdom, where consumers run their tests, reports nothing once focus
11239
+ * was inside a shadow root that was removed, so this falls back for it.
11240
+ */
11241
+ const focusedElement = (doc) => getDeepestActiveElement(doc) ?? doc.body;
11242
+ /**
11243
+ * Keeps keyboard focus inside a container while it is active: focus moves in
11244
+ * when the trap turns on, Tab and Shift+Tab wrap between the first and last
11245
+ * tab stops, and focus goes back where it came from when the trap turns off.
11246
+ *
11247
+ * Tab stops are what the browser's Tab visits, in document order: links,
11248
+ * form controls, frames, media with controls, a `<details>` summary, editable
11249
+ * regions, anything with `tabIndex={0}`, and the content of open shadow
11250
+ * roots. Hidden, disabled, inert and `tabIndex={-1}` elements are skipped,
11251
+ * and a radio group counts once. Give the container `tabIndex={-1}` so it can
11252
+ * hold focus itself when nothing inside can. The trap listens on the
11253
+ * container, so a trap nested inside another handles Tab first and the outer
11254
+ * one stands aside.
11255
+ *
11256
+ * The trap is about Tab. If a Tab it let through still takes focus out (a
11257
+ * browser can leave out a stop the page has, such as links when its settings
11258
+ * say to), it sends focus back to the other end. A pointer or a script moving
11259
+ * focus out is left alone, because pulling focus back from a click is modal
11260
+ * behaviour, and a modal also has to block the page behind it, which `Modal`
11261
+ * does. Content the container renders through a portal lives elsewhere in
11262
+ * the DOM, so it is outside the trap: render nested overlays inside the
11263
+ * container, or give them a trap of their own.
11264
+ *
11265
+ * The container has to be in the DOM when the trap turns on. The trap waits
11266
+ * for hydration, so a container that only appears once the page has
11267
+ * hydrated, the way portaled content does, still has it attach in the commit
11268
+ * where the container appears.
11269
+ *
11270
+ * @function useFocusTrap
11271
+ * @param containerRef - Ref to the element focus stays inside.
11272
+ * @param options - When the trap is on, and where focus starts and ends.
11273
+ *
11274
+ * @example
11275
+ * const panelRef = useRef<HTMLDivElement>(null);
11276
+ * useFocusTrap(panelRef, { active: isOpen });
11277
+ * return isOpen ? (
11278
+ * <div ref={panelRef} role="dialog" aria-label="Filters" tabIndex={-1}>
11279
+ * …
11280
+ * </div>
11281
+ * ) : null;
11282
+ */
11283
+ function useFocusTrap(containerRef, options = {}) {
11284
+ const { active = true, initialFocusRef, restoreFocus = true } = options;
11285
+ // Portaled content has no server-rendered counterpart, so a portaled
11286
+ // container only exists from the commit after hydration.
11287
+ const hydrated = useIsHydrated();
10937
11288
  useEffect(() => {
10938
- if (!active)
11289
+ if (!active || !hydrated)
10939
11290
  return undefined;
10940
11291
  const container = containerRef.current;
10941
11292
  if (!container)
10942
11293
  return undefined;
10943
- const previouslyFocused = typeof document !== 'undefined'
10944
- ? document.activeElement
10945
- : null;
10946
- const focusInitial = () => {
10947
- if (initialFocusRef?.current) {
10948
- initialFocusRef.current.focus();
10949
- return;
10950
- }
10951
- const focusables = Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR)).filter(el => !el.hasAttribute('disabled'));
10952
- const target = focusables[0] ?? container;
10953
- target.focus();
10954
- };
10955
- focusInitial();
11294
+ const doc = container.ownerDocument;
11295
+ const opener = focusedElement(doc);
11296
+ (initialFocusRef?.current ??
11297
+ findTabStops(container)?.first ??
11298
+ container).focus();
11299
+ // Where focus goes if a Tab the trap let the browser handle takes it out,
11300
+ // held until that Tab has finished moving focus.
11301
+ let wrapTo = null;
11302
+ let wrapTimer;
10956
11303
  const handleKeyDown = (e) => {
10957
- if (e.key !== 'Tab')
11304
+ // A trap nested inside this one has already wrapped focus.
11305
+ if (e.key !== 'Tab' || e.defaultPrevented)
10958
11306
  return;
10959
- const focusables = Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR)).filter(el => !el.hasAttribute('disabled'));
10960
- if (focusables.length === 0) {
11307
+ const stops = findTabStops(container);
11308
+ if (!stops) {
10961
11309
  e.preventDefault();
11310
+ container.focus();
10962
11311
  return;
10963
11312
  }
10964
- const first = focusables[0];
10965
- const last = focusables[focusables.length - 1];
10966
- const activeEl = document.activeElement;
10967
- if (e.shiftKey && activeEl === first) {
11313
+ const current = stops.stopFor(focusedElement(doc));
11314
+ // Wrap whenever the browser has no stop left in that direction inside
11315
+ // the container. Comparing positions rather than testing `=== first`
11316
+ // also covers focus resting on the container or on an element outside
11317
+ // the tab order, which the browser would otherwise walk straight out of.
11318
+ if (e.shiftKey
11319
+ ? !follows(container, stops.first, current)
11320
+ : !follows(container, current, stops.last)) {
10968
11321
  e.preventDefault();
10969
- last.focus();
10970
- }
10971
- else if (!e.shiftKey && activeEl === last) {
10972
- e.preventDefault();
10973
- first.focus();
11322
+ (e.shiftKey ? stops.last : stops.first).focus();
11323
+ return;
10974
11324
  }
11325
+ wrapTo = e.shiftKey ? stops.last : stops.first;
11326
+ clearTimeout(wrapTimer);
11327
+ wrapTimer = setTimeout(() => {
11328
+ wrapTo = null;
11329
+ });
11330
+ };
11331
+ // The browser moves focus as soon as the keydown is over, so focus that
11332
+ // lands outside while `wrapTo` is set got there by that Tab.
11333
+ const handleFocusIn = (e) => {
11334
+ if (!wrapTo || inside(container, e.composedPath()[0]))
11335
+ return;
11336
+ const target = wrapTo;
11337
+ wrapTo = null;
11338
+ target.focus();
10975
11339
  };
10976
11340
  container.addEventListener('keydown', handleKeyDown);
11341
+ doc.addEventListener('focusin', handleFocusIn, true);
10977
11342
  return () => {
10978
11343
  container.removeEventListener('keydown', handleKeyDown);
10979
- if (restoreFocus && previouslyFocused?.focus) {
10980
- previouslyFocused.focus();
11344
+ doc.removeEventListener('focusin', handleFocusIn, true);
11345
+ clearTimeout(wrapTimer);
11346
+ let target = restoreFocus ? opener : null;
11347
+ // `null` is an object too, and a caller without types can pass it.
11348
+ if (restoreFocus && typeof restoreFocus === 'object') {
11349
+ // Read now rather than when the trap turned on: a trigger that
11350
+ // re-mounted in between has a new node.
11351
+ // eslint-disable-next-line react-hooks/exhaustive-deps
11352
+ target = restoreFocus.current;
11353
+ }
11354
+ // Hand focus back only while the trap still holds it: inside the
11355
+ // container, or dropped to <body> because the container left the page.
11356
+ const now = focusedElement(doc);
11357
+ if (target && (now === doc.body || inside(container, now))) {
11358
+ target.focus();
10981
11359
  }
10982
11360
  };
10983
- }, [active, containerRef, initialFocusRef, restoreFocus]);
11361
+ }, [active, hydrated, containerRef, initialFocusRef, restoreFocus]);
10984
11362
  }
10985
11363
 
10986
11364
  const isBrowser = typeof window !== 'undefined';
@@ -11087,7 +11465,7 @@ const PickerPopover = ({ isOpen, onClose, anchorRef, children, position = 'botto
11087
11465
  document.addEventListener('keydown', handler);
11088
11466
  return () => document.removeEventListener('keydown', handler);
11089
11467
  }, [isOpen, closeOnEscape, onClose]);
11090
- useFocusTrap(panelRef, isOpen && trapFocus);
11468
+ useFocusTrap(panelRef, { active: isOpen && trapFocus });
11091
11469
  const panelClass = usePrefixedClassNames('picker-popover', {
11092
11470
  'is-active': isOpen,
11093
11471
  [`is-${resolved.position}`]: true,
@@ -14663,5 +15041,5 @@ const Section = ({ size, className, children, color, bgColor, textColor, style,
14663
15041
  return (jsx("section", { className: sectionClasses, style: mergeBulmaStyles(bulmaHelperStyles, style), ...rest, children: children }));
14664
15042
  };
14665
15043
 
14666
- export { Autocomplete, Avatar, Avatars, Badge, Block, Box, Breadcrumb, Button, Buttons, Card, Carousel, CarouselItem, Cell, Checkbox, Checkboxes, ClientOnly, Code, Collapse, Collapses, Column, Columns, ConfigProvider, Container, Content, Control, DateInput, DateInputBase, DateTimeInput, DateTimeInputBase, Delete, Dialog, DialogContainer, Divider, Dropdown, DropdownDivider, DropdownItem, Emphasis, Field, FieldBody, FieldLabel, Figure, File, Footer, Grid, Hero, HeroBody, HeroFoot, HeroHead, Icon, IconText, Image, Input, InputBase, Level, LevelItem, LevelLeft, LevelRight, Link, LinkButton, ListItem, Loader, Loading, Media, MediaContent, MediaLeft, MediaRight, Menu, MenuItem, MenuLabel, MenuList, Message, Modal, Navbar, NavbarBrand, NavbarBurger, NavbarDivider, NavbarDropdown, NavbarDropdownMenu, NavbarEnd, NavbarItem, NavbarLink, NavbarMenu, NavbarStart, Notification, NotificationContainer, NumberInput, Numberinput, OrderedList, Pagination, PaginationEllipsis, PaginationLink, PaginationList, PaginationNext, PaginationPrevious, Panel, PanelBlock, PanelButtonBlock, PanelCheckboxBlock, PanelHeading, PanelIcon, PanelInputBlock, PanelTabs, Paragraph, Pre, Progress, Radio, Radios, Rate, Reveal, Section, Select, SelectBase, Sidebar, Skeleton, Slider, Span, Step, Steps, Strong, SubTitle, Switch, Tab, TabContentItem, TabItem, TabList, Table, Tabs, TabsContent, Tag, TagInput, Taginput, Tags, Tbody, Td, TextArea, TextAreaBase, Tfoot, Th, Thead, Theme, TimeInput, TimeInputBase, Title, Toast, ToastContainer, Tooltip, Tr, UnorderedList, checkboxColors, checkboxSizes, classNames, createPrefixedClassNames, dialog, isBrowser$1 as isBrowser, mergeBulmaStyles, notification, prefixedClassNames, radioColors, radioSizes, switchColors, switchSizes, toast, useBulmaClasses, useClassPrefix, useColorClasses, useColorStyles, useConfig, useFlexboxClasses, useIconLibrary, useInsideControl, useInsideField, useIsHydrated, useOtherClasses, usePrefixedClass, usePrefixedClassNames, useSpacingClasses, useTypographyClasses, useVisibilityClasses, validAlignContents, validAlignItems, validAlignSelfs, validAlignments$1 as validAlignments, validAspectRatios, validAxisOverflows, validColorShades, validColors, validCursors, validDisplays, validFlexDirections, validFlexGrowShrink, validFlexWraps, validFloats, validFontFamilies, validInteractions, validJustifyContents, validOverflows, validPositions, validRadii, validResponsives, validSchemeColors, validShadows, validSizes$1 as validSizes, validTableColors, validTextSizes, validTextTransforms, validTextWeights, validViewports, validVisibilities };
15044
+ export { Autocomplete, Avatar, Avatars, Badge, Block, Box, Breadcrumb, Button, Buttons, Card, Carousel, CarouselItem, Cell, Checkbox, Checkboxes, ClientOnly, Code, Collapse, Collapses, Column, Columns, ConfigProvider, Container, Content, Control, DateInput, DateInputBase, DateTimeInput, DateTimeInputBase, Delete, Dialog, DialogContainer, Divider, Dropdown, DropdownDivider, DropdownItem, Emphasis, Field, FieldBody, FieldLabel, Figure, File, Footer, Grid, Hero, HeroBody, HeroFoot, HeroHead, Icon, IconText, Image, Input, InputBase, Level, LevelItem, LevelLeft, LevelRight, Link, LinkButton, ListItem, Loader, Loading, Media, MediaContent, MediaLeft, MediaRight, Menu, MenuItem, MenuLabel, MenuList, Message, Modal, Navbar, NavbarBrand, NavbarBurger, NavbarDivider, NavbarDropdown, NavbarDropdownMenu, NavbarEnd, NavbarItem, NavbarLink, NavbarMenu, NavbarStart, Notification, NotificationContainer, NumberInput, Numberinput, OrderedList, Pagination, PaginationEllipsis, PaginationLink, PaginationList, PaginationNext, PaginationPrevious, Panel, PanelBlock, PanelButtonBlock, PanelCheckboxBlock, PanelHeading, PanelIcon, PanelInputBlock, PanelTabs, Paragraph, Portal, Pre, Progress, Radio, Radios, Rate, Reveal, Section, Select, SelectBase, Sidebar, Skeleton, Slider, Span, Step, Steps, Strong, SubTitle, Switch, Tab, TabContentItem, TabItem, TabList, Table, Tabs, TabsContent, Tag, TagInput, Taginput, Tags, Tbody, Td, TextArea, TextAreaBase, Tfoot, Th, Thead, Theme, TimeInput, TimeInputBase, Title, Toast, ToastContainer, Tooltip, Tr, UnorderedList, checkboxColors, checkboxSizes, classNames, createPrefixedClassNames, dialog, isBrowser$1 as isBrowser, mergeBulmaStyles, notification, prefixedClassNames, radioColors, radioSizes, switchColors, switchSizes, toast, useBulmaClasses, useClassPrefix, useColorClasses, useColorStyles, useConfig, useFlexboxClasses, useFocusTrap, useIconLibrary, useInsideControl, useInsideField, useIsHydrated, useOtherClasses, usePrefixedClass, usePrefixedClassNames, useSpacingClasses, useTypographyClasses, useVisibilityClasses, validAlignContents, validAlignItems, validAlignSelfs, validAlignments$1 as validAlignments, validAspectRatios, validAxisOverflows, validColorShades, validColors, validCursors, validDisplays, validFlexDirections, validFlexGrowShrink, validFlexWraps, validFloats, validFontFamilies, validInteractions, validJustifyContents, validOverflows, validPositions, validRadii, validResponsives, validSchemeColors, validShadows, validSizes$1 as validSizes, validTableColors, validTextSizes, validTextTransforms, validTextWeights, validViewports, validVisibilities };
14667
15045
  //# sourceMappingURL=index.esm.js.map