@allxsmith/bestax-bulma 5.20.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/bestax.css +1 -1
- package/dist/bestax.css.map +1 -1
- package/dist/extras.css +1 -1
- package/dist/extras.css.map +1 -1
- package/dist/index.cjs +658 -86
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +654 -87
- package/dist/index.esm.js.map +1 -1
- package/dist/types/components/Collapse.d.ts +13 -9
- package/dist/types/components/Collapses.d.ts +53 -0
- package/dist/types/components/collapsesContext.d.ts +19 -0
- package/dist/types/helpers/ClientOnly.d.ts +40 -0
- package/dist/types/helpers/inertProps.d.ts +11 -0
- package/dist/types/helpers/portal.d.ts +62 -0
- package/dist/types/helpers/shadowDom.d.ts +3 -1
- package/dist/types/helpers/useFocusTrap.d.ts +74 -0
- package/dist/types/helpers/useIsHydrated.d.ts +5 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types-cjs/components/Collapse.d.ts +13 -9
- package/dist/types-cjs/components/Collapses.d.ts +53 -0
- package/dist/types-cjs/components/collapsesContext.d.ts +19 -0
- package/dist/types-cjs/helpers/ClientOnly.d.ts +40 -0
- package/dist/types-cjs/helpers/inertProps.d.ts +11 -0
- package/dist/types-cjs/helpers/portal.d.ts +62 -0
- package/dist/types-cjs/helpers/shadowDom.d.ts +3 -1
- package/dist/types-cjs/helpers/useFocusTrap.d.ts +74 -0
- package/dist/types-cjs/helpers/useIsHydrated.d.ts +5 -0
- package/dist/types-cjs/index.d.ts +8 -0
- package/dist/versions/bestax-no-dark-mode.css +1 -1
- package/dist/versions/bestax-no-dark-mode.css.map +1 -1
- package/dist/versions/bestax-no-helpers-prefixed.css +1 -1
- package/dist/versions/bestax-no-helpers-prefixed.css.map +1 -1
- package/dist/versions/bestax-no-helpers.css +1 -1
- package/dist/versions/bestax-no-helpers.css.map +1 -1
- package/dist/versions/bestax-prefixed.css +1 -1
- package/dist/versions/bestax-prefixed.css.map +1 -1
- package/package.json +1 -1
- package/src/scss/components/_collapse.scss +13 -4
- package/dist/types/form/_pickerInternals/useFocusTrap.d.ts +0 -7
- package/dist/types-cjs/form/_pickerInternals/useFocusTrap.d.ts +0 -7
package/dist/index.esm.js
CHANGED
|
@@ -1683,12 +1683,12 @@ Avatar.displayName = 'Avatar';
|
|
|
1683
1683
|
* prefixing the fragment's own key — otherwise a fragment sibling of a direct
|
|
1684
1684
|
* child would collide (both ".0") and trigger duplicate-key warnings.
|
|
1685
1685
|
*/
|
|
1686
|
-
function flattenChildren(children, keyPrefix = '') {
|
|
1686
|
+
function flattenChildren$1(children, keyPrefix = '') {
|
|
1687
1687
|
return React.Children.toArray(children).flatMap(child => {
|
|
1688
1688
|
if (!React.isValidElement(child))
|
|
1689
1689
|
return [];
|
|
1690
1690
|
if (child.type === React.Fragment) {
|
|
1691
|
-
return flattenChildren(child.props.children, keyPrefix + child.key);
|
|
1691
|
+
return flattenChildren$1(child.props.children, keyPrefix + child.key);
|
|
1692
1692
|
}
|
|
1693
1693
|
const el = child;
|
|
1694
1694
|
return [
|
|
@@ -1722,7 +1722,7 @@ const AvatarsComponent = ({ className, max, size, shape, spacing = 'md', spaced
|
|
|
1722
1722
|
: undefined;
|
|
1723
1723
|
const surplusClass = usePrefixedClassNames('is-surplus');
|
|
1724
1724
|
const combinedClasses = classNames(avatarsClasses, bulmaHelperClasses, className);
|
|
1725
|
-
const childArray = flattenChildren(children);
|
|
1725
|
+
const childArray = flattenChildren$1(children);
|
|
1726
1726
|
const maxCount = typeof max === 'number' && Number.isInteger(max) && max >= 0
|
|
1727
1727
|
? max
|
|
1728
1728
|
: undefined;
|
|
@@ -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 =
|
|
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,26 +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
|
-
*
|
|
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
|
-
*
|
|
2831
|
-
*
|
|
2832
|
-
*
|
|
2833
|
-
*
|
|
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
|
-
*
|
|
2836
|
-
*
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
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.
|
|
2862
|
+
*
|
|
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>
|
|
2883
|
+
*/
|
|
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));
|
|
2840
2891
|
}
|
|
2841
2892
|
|
|
2842
2893
|
/**
|
|
@@ -2850,7 +2901,7 @@ const activeModalStack = [];
|
|
|
2850
2901
|
* excluded because `focus()` on them is a no-op, which would leave focus
|
|
2851
2902
|
* outside the modal.
|
|
2852
2903
|
*/
|
|
2853
|
-
const FOCUSABLE_SELECTOR
|
|
2904
|
+
const FOCUSABLE_SELECTOR = 'button:not(:disabled), [href], input:not(:disabled):not([type="hidden"]), select:not(:disabled), textarea:not(:disabled), [tabindex]';
|
|
2854
2905
|
/**
|
|
2855
2906
|
* The modal's tab stops, in document order. The selector alone is not the
|
|
2856
2907
|
* tabbable set — `[href]` and `button` match regardless of `tabindex`, so an
|
|
@@ -2859,7 +2910,7 @@ const FOCUSABLE_SELECTOR$1 = 'button:not(:disabled), [href], input:not(:disabled
|
|
|
2859
2910
|
* resolved value, so filtering on it drops every negative index (not just
|
|
2860
2911
|
* `-1`) without a second selector to keep in sync.
|
|
2861
2912
|
*/
|
|
2862
|
-
const getTabbable = (node) => Array.from(node.querySelectorAll(FOCUSABLE_SELECTOR
|
|
2913
|
+
const getTabbable = (node) => Array.from(node.querySelectorAll(FOCUSABLE_SELECTOR)).filter(el => el.tabIndex >= 0);
|
|
2863
2914
|
/**
|
|
2864
2915
|
* Modal.Background - Renders the modal background overlay.
|
|
2865
2916
|
*
|
|
@@ -4255,6 +4306,35 @@ const Loading = ({ active = false, isFullPage = false, size, color, canCancel =
|
|
|
4255
4306
|
return (jsxs("div", { className: combinedClasses, role: "alert", "aria-busy": "true", "aria-label": "Loading", ...rest, children: [jsx("div", { className: combinedOverlayClasses, onClick: handleOverlayClick, "aria-hidden": "true" }), jsxs("div", { className: loadingContentClass, children: [indicator ? (jsx("span", { className: loadingIconCustomClass, children: indicator })) : (jsx("span", { className: combinedIconClasses })), children && jsx("div", { className: loadingTextClass, children: children }), canCancel && (jsx("button", { type: "button", className: loadingCancelClass, onClick: onCancel, "aria-label": "Cancel loading", children: "Cancel" }))] })] }));
|
|
4256
4307
|
};
|
|
4257
4308
|
|
|
4309
|
+
// INTERNAL: deliberately not exported from src/index.ts.
|
|
4310
|
+
/** The major version of the React this bundle is running against. */
|
|
4311
|
+
const runningMajor = () => Number(React.version.split('.')[0]);
|
|
4312
|
+
/**
|
|
4313
|
+
* The prop that makes an element inert, spelled for the React in use, or
|
|
4314
|
+
* nothing when `inert` is false.
|
|
4315
|
+
*
|
|
4316
|
+
* Both majors must render `inert=""`, and each warns on the other's spelling
|
|
4317
|
+
* and then drops the attribute. React 19 treats `inert` as a boolean and
|
|
4318
|
+
* rejects the empty string. React 18 does not know the attribute, so it
|
|
4319
|
+
* passes a string through and rejects `true`.
|
|
4320
|
+
*/
|
|
4321
|
+
const inertProps = (inert, major = runningMajor()) => {
|
|
4322
|
+
if (!inert)
|
|
4323
|
+
return {};
|
|
4324
|
+
// The cast is for React 18's spelling: this package's types are React 19's,
|
|
4325
|
+
// where `inert` is a boolean.
|
|
4326
|
+
return (major >= 19
|
|
4327
|
+
? { inert: true }
|
|
4328
|
+
: { inert: '' });
|
|
4329
|
+
};
|
|
4330
|
+
|
|
4331
|
+
/**
|
|
4332
|
+
* `null` outside a group. A `Collapse` also resets it to `null` for its own
|
|
4333
|
+
* subtree, so a Collapse nested in an item's content keeps its own state
|
|
4334
|
+
* instead of answering to its ancestor's slot in the group.
|
|
4335
|
+
*/
|
|
4336
|
+
const CollapsesItemContext = createContext(null);
|
|
4337
|
+
|
|
4258
4338
|
/**
|
|
4259
4339
|
* The `Collapse` component provides an expandable/collapsible content panel.
|
|
4260
4340
|
*
|
|
@@ -4269,12 +4349,9 @@ const Loading = ({ active = false, isFullPage = false, size, color, canCancel =
|
|
|
4269
4349
|
* </Collapse>
|
|
4270
4350
|
*
|
|
4271
4351
|
* @example
|
|
4272
|
-
* // Controlled collapse
|
|
4352
|
+
* // Controlled collapse, driven by its own trigger
|
|
4273
4353
|
* const [isOpen, setIsOpen] = useState(false);
|
|
4274
|
-
* <Collapse
|
|
4275
|
-
* open={isOpen}
|
|
4276
|
-
* trigger={<button onClick={() => setIsOpen(!isOpen)}>Toggle</button>}
|
|
4277
|
-
* >
|
|
4354
|
+
* <Collapse open={isOpen} onOpenChange={setIsOpen} trigger={<strong>Toggle</strong>}>
|
|
4278
4355
|
* <p>Content</p>
|
|
4279
4356
|
* </Collapse>
|
|
4280
4357
|
*
|
|
@@ -4288,14 +4365,49 @@ const Loading = ({ active = false, isFullPage = false, size, color, canCancel =
|
|
|
4288
4365
|
* <p>Content</p>
|
|
4289
4366
|
* </Collapse>
|
|
4290
4367
|
*/
|
|
4291
|
-
const Collapse = ({ open: controlledOpen, defaultOpen = false, onOpen, onClose, trigger, animation = 'fade', position = 'top', ariaId, bordered, children, className, triggerClassName, contentClassName, ...props }) => {
|
|
4368
|
+
const Collapse = ({ open: controlledOpen, defaultOpen = false, onOpen, onClose, onOpenChange, trigger, animation = 'fade', position = 'top', ariaId, bordered, children, className, triggerClassName, contentClassName, ...props }) => {
|
|
4292
4369
|
const { bulmaHelperClasses, rest } = useBulmaClasses(props);
|
|
4293
4370
|
const contentRef = useRef(null);
|
|
4294
|
-
|
|
4295
|
-
//
|
|
4371
|
+
// Three possible owners of the open state, in order: the caller when `open`
|
|
4372
|
+
// is set, an enclosing Collapses group, and this Collapse itself. A caller's
|
|
4373
|
+
// `open` wins over the group, which is how the group leaves such an item
|
|
4374
|
+
// alone.
|
|
4296
4375
|
const isControlled = controlledOpen !== undefined;
|
|
4376
|
+
const group = useContext(CollapsesItemContext);
|
|
4377
|
+
const groupItem = isControlled ? null : group;
|
|
4297
4378
|
const [internalOpen, setInternalOpen] = useState(defaultOpen);
|
|
4298
|
-
const isOpen =
|
|
4379
|
+
const isOpen = controlledOpen ?? groupItem?.open ?? internalOpen;
|
|
4380
|
+
// A group owns the state of the items it manages, so these props do
|
|
4381
|
+
// nothing there. That is easy to miss, so development says which ones and
|
|
4382
|
+
// what to use instead, once per combination (the way Avatar keys its
|
|
4383
|
+
// warning), and `warnOnce` keeps production silent.
|
|
4384
|
+
if (groupItem) {
|
|
4385
|
+
const ignored = [
|
|
4386
|
+
defaultOpen && 'defaultOpen',
|
|
4387
|
+
onOpen && 'onOpen',
|
|
4388
|
+
onClose && 'onClose',
|
|
4389
|
+
].filter((name) => Boolean(name));
|
|
4390
|
+
if (ignored.length > 0) {
|
|
4391
|
+
const callbacks = ignored.filter(name => name !== 'defaultOpen');
|
|
4392
|
+
warnOnce(`Collapse:ignored-in-Collapses:${ignored.join('+')}`, `[bestax-bulma] <Collapse ${ignored.join(' ')}> inside <Collapses>: ` +
|
|
4393
|
+
[
|
|
4394
|
+
ignored.includes('defaultOpen') &&
|
|
4395
|
+
`defaultOpen has no effect, because the group decides which ` +
|
|
4396
|
+
`items start open. Pass the item's index in the group's ` +
|
|
4397
|
+
`defaultValue instead.`,
|
|
4398
|
+
callbacks.length > 0 &&
|
|
4399
|
+
`${callbacks.join(' and ')} never fire${callbacks.length > 1 ? '' : 's'}, ` +
|
|
4400
|
+
`because the group holds the item's open state. Use the ` +
|
|
4401
|
+
`item's onOpenChange, or the group's onChange, instead.`,
|
|
4402
|
+
]
|
|
4403
|
+
.filter(Boolean)
|
|
4404
|
+
.join(' '));
|
|
4405
|
+
}
|
|
4406
|
+
}
|
|
4407
|
+
// Start from the resolved state rather than `defaultOpen`, so an item that
|
|
4408
|
+
// mounts open through `open` or a group is not first painted (or rendered
|
|
4409
|
+
// on the server) at height 0 under the slide animation.
|
|
4410
|
+
const [height, setHeight] = useState(isOpen ? 'auto' : 0);
|
|
4299
4411
|
// Generate unique ID for accessibility (useId is SSR-safe and stable across
|
|
4300
4412
|
// renders; works in React 18 and 19).
|
|
4301
4413
|
const generatedId = useId();
|
|
@@ -4340,19 +4452,25 @@ const Collapse = ({ open: controlledOpen, defaultOpen = false, onOpen, onClose,
|
|
|
4340
4452
|
// excluded from the deps.
|
|
4341
4453
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
4342
4454
|
}, [isOpen, animation]);
|
|
4343
|
-
//
|
|
4455
|
+
// The trigger reports in every mode, then changes only the state this
|
|
4456
|
+
// Collapse owns. `onOpen`/`onClose` belong to that last case alone.
|
|
4344
4457
|
const handleToggle = useCallback(() => {
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
onClose?.();
|
|
4353
|
-
}
|
|
4458
|
+
const next = !isOpen;
|
|
4459
|
+
onOpenChange?.(next);
|
|
4460
|
+
if (isControlled)
|
|
4461
|
+
return;
|
|
4462
|
+
if (groupItem) {
|
|
4463
|
+
groupItem.setOpen(next);
|
|
4464
|
+
return;
|
|
4354
4465
|
}
|
|
4355
|
-
|
|
4466
|
+
setInternalOpen(next);
|
|
4467
|
+
if (next) {
|
|
4468
|
+
onOpen?.();
|
|
4469
|
+
}
|
|
4470
|
+
else {
|
|
4471
|
+
onClose?.();
|
|
4472
|
+
}
|
|
4473
|
+
}, [isOpen, isControlled, groupItem, onOpenChange, onOpen, onClose]);
|
|
4356
4474
|
// Handle keyboard interaction
|
|
4357
4475
|
const handleKeyDown = useCallback((e) => {
|
|
4358
4476
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
@@ -4393,9 +4511,99 @@ const Collapse = ({ open: controlledOpen, defaultOpen = false, onOpen, onClose,
|
|
|
4393
4511
|
};
|
|
4394
4512
|
}
|
|
4395
4513
|
const triggerElement = (jsx("div", { className: combinedTriggerClasses, onClick: handleToggle, onKeyDown: handleKeyDown, role: "button", tabIndex: 0, "aria-expanded": isOpen, "aria-controls": uniqueId, children: trigger }));
|
|
4396
|
-
const contentElement = (jsx("div", { style: contentWrapperStyle, children: jsx("div", { ref: contentRef, id: uniqueId, className: combinedContentClasses, "aria-hidden": !isOpen, children: children }) }));
|
|
4397
|
-
|
|
4514
|
+
const contentElement = (jsx("div", { style: contentWrapperStyle, children: jsx("div", { ref: contentRef, id: uniqueId, className: combinedContentClasses, "aria-hidden": !isOpen, ...inertProps(!isOpen), children: children }) }));
|
|
4515
|
+
// The group's item context stops here: a Collapse nested in this one's
|
|
4516
|
+
// content is not an item of this one's group.
|
|
4517
|
+
return (jsx(CollapsesItemContext.Provider, { value: null, children: jsx("div", { className: combinedClasses, ...rest, children: position === 'bottom' ? (jsxs(Fragment, { children: [contentElement, triggerElement] })) : (jsxs(Fragment, { children: [triggerElement, contentElement] })) }) }));
|
|
4518
|
+
};
|
|
4519
|
+
|
|
4520
|
+
/** Either mode's value as the list of open indexes. */
|
|
4521
|
+
function toIndexes(value) {
|
|
4522
|
+
if (Array.isArray(value))
|
|
4523
|
+
return value;
|
|
4524
|
+
return typeof value === 'number' ? [value] : [];
|
|
4525
|
+
}
|
|
4526
|
+
/**
|
|
4527
|
+
* Flattens Fragment children (recursively) so each item inside one gets its
|
|
4528
|
+
* own index rather than sharing the fragment's. Children.toArray keys restart
|
|
4529
|
+
* at ".0" inside each fragment, so those children are re-keyed with the
|
|
4530
|
+
* fragment's own key as a prefix, the way Avatars does it.
|
|
4531
|
+
*/
|
|
4532
|
+
function flattenChildren(children, keyPrefix = '') {
|
|
4533
|
+
return React.Children.toArray(children).flatMap(child => {
|
|
4534
|
+
if (!React.isValidElement(child))
|
|
4535
|
+
return [child];
|
|
4536
|
+
if (child.type === React.Fragment) {
|
|
4537
|
+
return flattenChildren(child.props.children, keyPrefix + child.key);
|
|
4538
|
+
}
|
|
4539
|
+
return [
|
|
4540
|
+
keyPrefix
|
|
4541
|
+
? React.cloneElement(child, { key: keyPrefix + child.key })
|
|
4542
|
+
: child,
|
|
4543
|
+
];
|
|
4544
|
+
});
|
|
4545
|
+
}
|
|
4546
|
+
/**
|
|
4547
|
+
* The `Collapses` component groups `Collapse` items into an accordion that keeps one item open at a time, or any number with `multiple`.
|
|
4548
|
+
*
|
|
4549
|
+
* @function
|
|
4550
|
+
* @param {CollapsesProps} props - Props for the Collapses component.
|
|
4551
|
+
* @returns {JSX.Element} The rendered group.
|
|
4552
|
+
*
|
|
4553
|
+
* @example
|
|
4554
|
+
* // Accordion: one item open at a time, the first to start with
|
|
4555
|
+
* <Collapses defaultValue={0}>
|
|
4556
|
+
* <Collapse bordered trigger={<strong>Shipping</strong>}>…</Collapse>
|
|
4557
|
+
* <Collapse bordered trigger={<strong>Returns</strong>}>…</Collapse>
|
|
4558
|
+
* </Collapses>
|
|
4559
|
+
*
|
|
4560
|
+
* @example
|
|
4561
|
+
* // Controlled, with items that open independently
|
|
4562
|
+
* const [open, setOpen] = useState<number[]>([]);
|
|
4563
|
+
* <Collapses multiple value={open} onChange={setOpen}>…</Collapses>
|
|
4564
|
+
*/
|
|
4565
|
+
const CollapsesComponent = ({ multiple = false, value, defaultValue, onChange, seamless = false, className, children, ...props }) => {
|
|
4566
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses({ ...props });
|
|
4567
|
+
// Controlled vs uncontrolled, as in Tabs. State is held as a list of open
|
|
4568
|
+
// indexes in both modes; `multiple` only decides how a change is computed
|
|
4569
|
+
// and which shape `onChange` receives.
|
|
4570
|
+
const isControlled = value !== undefined;
|
|
4571
|
+
const [internalOpen, setInternalOpen] = useState(() => toIndexes(defaultValue));
|
|
4572
|
+
const openIndexes = isControlled ? toIndexes(value) : internalOpen;
|
|
4573
|
+
// Each mode types `onChange` for its own shape, and the value built below
|
|
4574
|
+
// always matches `multiple`, the union's discriminant.
|
|
4575
|
+
const reportChange = onChange;
|
|
4576
|
+
const setItemOpen = (index, open) => {
|
|
4577
|
+
const others = openIndexes.filter(i => i !== index);
|
|
4578
|
+
// Single mode keeps at most one item open, so opening one drops the rest.
|
|
4579
|
+
// Ascending order, whatever order the items were opened in, so the value
|
|
4580
|
+
// reads and compares the same way however the user got there.
|
|
4581
|
+
const next = (open ? (multiple ? [...others, index] : [index]) : others).sort((a, b) => a - b);
|
|
4582
|
+
if (!isControlled)
|
|
4583
|
+
setInternalOpen(next);
|
|
4584
|
+
reportChange?.(multiple ? next : (next[0] ?? null));
|
|
4585
|
+
};
|
|
4586
|
+
const groupClasses = usePrefixedClassNames('collapses', {
|
|
4587
|
+
'is-seamless': seamless,
|
|
4588
|
+
});
|
|
4589
|
+
const combinedClasses = classNames(groupClasses, bulmaHelperClasses, className);
|
|
4590
|
+
const items = [];
|
|
4591
|
+
let index = 0;
|
|
4592
|
+
for (const child of flattenChildren(children)) {
|
|
4593
|
+
if (!React.isValidElement(child)) {
|
|
4594
|
+
items.push(child);
|
|
4595
|
+
continue;
|
|
4596
|
+
}
|
|
4597
|
+
const itemIndex = index;
|
|
4598
|
+
index += 1;
|
|
4599
|
+
items.push(jsx(CollapsesItemContext.Provider, { value: {
|
|
4600
|
+
open: openIndexes.includes(itemIndex),
|
|
4601
|
+
setOpen: open => setItemOpen(itemIndex, open),
|
|
4602
|
+
}, children: child }, child.key));
|
|
4603
|
+
}
|
|
4604
|
+
return (jsx("div", { className: combinedClasses, ...rest, children: items }));
|
|
4398
4605
|
};
|
|
4606
|
+
const Collapses = withSubComponents(CollapsesComponent, { Collapse }, 'Collapses');
|
|
4399
4607
|
|
|
4400
4608
|
/**
|
|
4401
4609
|
* The `Tooltip` component displays helpful information when users hover over or focus on an element.
|
|
@@ -10762,63 +10970,395 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min, max,
|
|
|
10762
10970
|
};
|
|
10763
10971
|
Calendar.displayName = 'Calendar';
|
|
10764
10972
|
|
|
10765
|
-
|
|
10766
|
-
|
|
10767
|
-
|
|
10768
|
-
|
|
10769
|
-
|
|
10770
|
-
|
|
10771
|
-
|
|
10772
|
-
|
|
10773
|
-
|
|
10774
|
-
|
|
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();
|
|
10775
11288
|
useEffect(() => {
|
|
10776
|
-
if (!active)
|
|
11289
|
+
if (!active || !hydrated)
|
|
10777
11290
|
return undefined;
|
|
10778
11291
|
const container = containerRef.current;
|
|
10779
11292
|
if (!container)
|
|
10780
11293
|
return undefined;
|
|
10781
|
-
const
|
|
10782
|
-
|
|
10783
|
-
|
|
10784
|
-
|
|
10785
|
-
|
|
10786
|
-
|
|
10787
|
-
|
|
10788
|
-
|
|
10789
|
-
|
|
10790
|
-
const target = focusables[0] ?? container;
|
|
10791
|
-
target.focus();
|
|
10792
|
-
};
|
|
10793
|
-
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;
|
|
10794
11303
|
const handleKeyDown = (e) => {
|
|
10795
|
-
|
|
11304
|
+
// A trap nested inside this one has already wrapped focus.
|
|
11305
|
+
if (e.key !== 'Tab' || e.defaultPrevented)
|
|
10796
11306
|
return;
|
|
10797
|
-
const
|
|
10798
|
-
if (
|
|
11307
|
+
const stops = findTabStops(container);
|
|
11308
|
+
if (!stops) {
|
|
10799
11309
|
e.preventDefault();
|
|
11310
|
+
container.focus();
|
|
10800
11311
|
return;
|
|
10801
11312
|
}
|
|
10802
|
-
const
|
|
10803
|
-
|
|
10804
|
-
|
|
10805
|
-
|
|
10806
|
-
|
|
10807
|
-
|
|
10808
|
-
|
|
10809
|
-
|
|
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)) {
|
|
10810
11321
|
e.preventDefault();
|
|
10811
|
-
first.focus();
|
|
11322
|
+
(e.shiftKey ? stops.last : stops.first).focus();
|
|
11323
|
+
return;
|
|
10812
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();
|
|
10813
11339
|
};
|
|
10814
11340
|
container.addEventListener('keydown', handleKeyDown);
|
|
11341
|
+
doc.addEventListener('focusin', handleFocusIn, true);
|
|
10815
11342
|
return () => {
|
|
10816
11343
|
container.removeEventListener('keydown', handleKeyDown);
|
|
10817
|
-
|
|
10818
|
-
|
|
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();
|
|
10819
11359
|
}
|
|
10820
11360
|
};
|
|
10821
|
-
}, [active, containerRef, initialFocusRef, restoreFocus]);
|
|
11361
|
+
}, [active, hydrated, containerRef, initialFocusRef, restoreFocus]);
|
|
10822
11362
|
}
|
|
10823
11363
|
|
|
10824
11364
|
const isBrowser = typeof window !== 'undefined';
|
|
@@ -10925,7 +11465,7 @@ const PickerPopover = ({ isOpen, onClose, anchorRef, children, position = 'botto
|
|
|
10925
11465
|
document.addEventListener('keydown', handler);
|
|
10926
11466
|
return () => document.removeEventListener('keydown', handler);
|
|
10927
11467
|
}, [isOpen, closeOnEscape, onClose]);
|
|
10928
|
-
useFocusTrap(panelRef, isOpen && trapFocus);
|
|
11468
|
+
useFocusTrap(panelRef, { active: isOpen && trapFocus });
|
|
10929
11469
|
const panelClass = usePrefixedClassNames('picker-popover', {
|
|
10930
11470
|
'is-active': isOpen,
|
|
10931
11471
|
[`is-${resolved.position}`]: true,
|
|
@@ -14128,6 +14668,33 @@ const Theme = ({ bulmaVars = {}, children, className, isRoot = false, colorMode,
|
|
|
14128
14668
|
return isRoot ? (jsx(Fragment, { children: children })) : (jsx("div", { className: combinedClassName || undefined, style: style, ...rest, children: children }));
|
|
14129
14669
|
};
|
|
14130
14670
|
|
|
14671
|
+
/**
|
|
14672
|
+
* Renders its children only in the browser, after hydration, and a `fallback` until then, so content that depends on browser-only state doesn't cause a hydration mismatch.
|
|
14673
|
+
*
|
|
14674
|
+
* The server render and the hydrating client render both show `fallback`,
|
|
14675
|
+
* which keeps them identical; the children replace it in the commit that
|
|
14676
|
+
* follows. In an app without server rendering the children show on the first
|
|
14677
|
+
* render.
|
|
14678
|
+
*
|
|
14679
|
+
* @function
|
|
14680
|
+
* @param {ClientOnlyProps} props - Props for the ClientOnly component.
|
|
14681
|
+
* @returns The children once hydrated, otherwise the fallback.
|
|
14682
|
+
*
|
|
14683
|
+
* @example
|
|
14684
|
+
* // The server's time zone is not the reader's.
|
|
14685
|
+
* <ClientOnly fallback={<Skeleton variant="lines" lines={1} />}>
|
|
14686
|
+
* {() => (
|
|
14687
|
+
* <p>Times are shown in {Intl.DateTimeFormat().resolvedOptions().timeZone}.</p>
|
|
14688
|
+
* )}
|
|
14689
|
+
* </ClientOnly>
|
|
14690
|
+
*/
|
|
14691
|
+
function ClientOnly({ children, fallback = null }) {
|
|
14692
|
+
const hydrated = useIsHydrated();
|
|
14693
|
+
if (!hydrated)
|
|
14694
|
+
return jsx(Fragment, { children: fallback });
|
|
14695
|
+
return jsx(Fragment, { children: typeof children === 'function' ? children() : children });
|
|
14696
|
+
}
|
|
14697
|
+
|
|
14131
14698
|
/**
|
|
14132
14699
|
* The `Container` component provides a responsive and flexible layout wrapper for your Bulma React UI.
|
|
14133
14700
|
*
|
|
@@ -14474,5 +15041,5 @@ const Section = ({ size, className, children, color, bgColor, textColor, style,
|
|
|
14474
15041
|
return (jsx("section", { className: sectionClasses, style: mergeBulmaStyles(bulmaHelperStyles, style), ...rest, children: children }));
|
|
14475
15042
|
};
|
|
14476
15043
|
|
|
14477
|
-
export { Autocomplete, Avatar, Avatars, Badge, Block, Box, Breadcrumb, Button, Buttons, Card, Carousel, CarouselItem, Cell, Checkbox, Checkboxes, Code, Collapse, 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, 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 };
|
|
14478
15045
|
//# sourceMappingURL=index.esm.js.map
|