@allxsmith/bestax-bulma 5.34.3 → 5.35.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/esm/components/Loading.js +19 -6
- package/dist/esm/components/Loading.js.map +1 -1
- package/dist/esm/components/Menu.js +5 -1
- package/dist/esm/components/Menu.js.map +1 -1
- package/dist/esm/components/Modal.js +22 -31
- package/dist/esm/components/Modal.js.map +1 -1
- package/dist/esm/components/Sidebar.js +19 -6
- package/dist/esm/components/Sidebar.js.map +1 -1
- package/dist/esm/components/Steps.js +14 -13
- package/dist/esm/components/Steps.js.map +1 -1
- package/dist/esm/components/Toast.js +18 -7
- package/dist/esm/components/Toast.js.map +1 -1
- package/dist/esm/components/Tooltip.js +3 -3
- package/dist/esm/components/Tooltip.js.map +1 -1
- package/dist/esm/elements/Loader.js +2 -1
- package/dist/esm/elements/Loader.js.map +1 -1
- package/dist/esm/form/Checkbox.js +37 -4
- package/dist/esm/form/Checkbox.js.map +1 -1
- package/dist/esm/form/Slider.js +5 -4
- package/dist/esm/form/Slider.js.map +1 -1
- package/dist/esm/form/Taginput.js +21 -9
- package/dist/esm/form/Taginput.js.map +1 -1
- package/dist/esm/form/_pickerInternals/PickerPopover.js +9 -1
- package/dist/esm/form/_pickerInternals/PickerPopover.js.map +1 -1
- package/dist/esm/form/_pickerInternals/useSegmentedEntry.js +5 -4
- package/dist/esm/form/_pickerInternals/useSegmentedEntry.js.map +1 -1
- package/dist/esm/helpers/overlayStack.js +49 -0
- package/dist/esm/helpers/overlayStack.js.map +1 -0
- package/dist/esm/helpers/useLatestRef.js +33 -0
- package/dist/esm/helpers/useLatestRef.js.map +1 -0
- package/dist/extras.css +1 -1
- package/dist/extras.css.map +1 -1
- package/dist/index.cjs +240 -92
- package/dist/index.cjs.map +1 -1
- package/dist/types/components/Loading.d.ts +13 -3
- package/dist/types/components/Menu.d.ts +14 -1
- package/dist/types/components/Modal.d.ts +7 -4
- package/dist/types/components/Sidebar.d.ts +8 -3
- package/dist/types/components/Steps.d.ts +27 -5
- package/dist/types/components/Toast.d.ts +5 -2
- package/dist/types/elements/Loader.d.ts +2 -1
- package/dist/types/form/Checkbox.d.ts +11 -0
- package/dist/types/form/Taginput.d.ts +5 -1
- package/dist/types/helpers/overlayStack.d.ts +15 -0
- package/dist/types/helpers/useLatestRef.d.ts +21 -0
- package/dist/types-cjs/components/Loading.d.ts +13 -3
- package/dist/types-cjs/components/Menu.d.ts +14 -1
- package/dist/types-cjs/components/Modal.d.ts +7 -4
- package/dist/types-cjs/components/Sidebar.d.ts +8 -3
- package/dist/types-cjs/components/Steps.d.ts +27 -5
- package/dist/types-cjs/components/Toast.d.ts +5 -2
- package/dist/types-cjs/elements/Loader.d.ts +2 -1
- package/dist/types-cjs/form/Checkbox.d.ts +11 -0
- package/dist/types-cjs/form/Taginput.d.ts +5 -1
- package/dist/types-cjs/helpers/overlayStack.d.ts +15 -0
- package/dist/types-cjs/helpers/useLatestRef.d.ts +21 -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/_steps.scss +96 -38
- package/src/scss/elements/_loader.scss +20 -0
- package/src/scss/form/_checkbox.scss +28 -1
package/dist/index.cjs
CHANGED
|
@@ -3581,6 +3581,10 @@ const STRIP_FROM_NON_LINK = { href: true };
|
|
|
3581
3581
|
/**
|
|
3582
3582
|
* MenuItem supports `as` prop for custom link components, e.g., react-router-dom Link.
|
|
3583
3583
|
*
|
|
3584
|
+
* Give each item an `href`, a router link through `as`, or `as="button"`, so
|
|
3585
|
+
* the keyboard can reach it: an `<a>` with no `href` isn't a link, and Tab
|
|
3586
|
+
* skips it.
|
|
3587
|
+
*
|
|
3584
3588
|
* @function
|
|
3585
3589
|
* @param {MenuItemProps} props - Props for the MenuItem component.
|
|
3586
3590
|
* @param {React.Ref} ref - Forwarded ref to the inner element `as` renders.
|
|
@@ -3623,7 +3627,7 @@ const MenuItem = React.forwardRef(function MenuItem(itemProps, ref) {
|
|
|
3623
3627
|
labelChildren.push(child);
|
|
3624
3628
|
}
|
|
3625
3629
|
});
|
|
3626
|
-
return (jsxRuntime.jsxs("li", { className: className, "data-testid": testId, style: style, id: id, title: title, role: role, tabIndex: tabIndex, children: [jsxRuntime.jsx(Component, { ref: ref, className: itemClass || undefined, ...linkProps, ...(Component === 'button'
|
|
3630
|
+
return (jsxRuntime.jsxs("li", { className: className, "data-testid": testId, style: style, id: id, title: title, role: role, tabIndex: tabIndex, children: [jsxRuntime.jsx(Component, { ref: ref, className: itemClass || undefined, "aria-current": active ? 'page' : undefined, ...linkProps, ...(Component === 'button'
|
|
3627
3631
|
? { type: buttonType(linkProps.type) }
|
|
3628
3632
|
: {}), children: labelChildren }), nestedMenuLists] }));
|
|
3629
3633
|
});
|
|
@@ -3834,12 +3838,85 @@ function Portal({ children, container, disabled = false }) {
|
|
|
3834
3838
|
return reactDom.createPortal(children, resolveContainer(container));
|
|
3835
3839
|
}
|
|
3836
3840
|
|
|
3841
|
+
// INTERNAL: deliberately not exported from src/index.ts.
|
|
3842
|
+
/**
|
|
3843
|
+
* `useLayoutEffect` in the browser, `useEffect` on the server. A server
|
|
3844
|
+
* render runs neither, and React 18 warns about a layout effect there, so the
|
|
3845
|
+
* server is handed the plain one.
|
|
3846
|
+
*/
|
|
3847
|
+
const useClientLayoutEffect = typeof document === 'undefined' ? React.useEffect : React.useLayoutEffect;
|
|
3848
|
+
|
|
3849
|
+
// INTERNAL: deliberately not exported from src/index.ts.
|
|
3850
|
+
/**
|
|
3851
|
+
* A ref holding the value from the latest commit, for a listener that is
|
|
3852
|
+
* added once and calls whatever callback it was given most recently.
|
|
3853
|
+
*
|
|
3854
|
+
* A listener whose effect lists a callback prop is taken off and added again
|
|
3855
|
+
* each time that callback changes, and an inline one changes on every render
|
|
3856
|
+
* of its parent. React can render between two listeners for the same
|
|
3857
|
+
* keypress, and the browser runs neither a listener taken off mid-event nor
|
|
3858
|
+
* one added mid-event, so that keypress never reaches it (#1088). Reading the
|
|
3859
|
+
* callback from here keeps the listener where it is.
|
|
3860
|
+
*
|
|
3861
|
+
* It's updated before paint on each commit, so a listener reads the props of
|
|
3862
|
+
* the render that's on screen.
|
|
3863
|
+
*
|
|
3864
|
+
* @function useLatestRef
|
|
3865
|
+
* @param value - The value to keep current, usually a callback prop.
|
|
3866
|
+
* @returns A ref whose `current` is `value` as of the latest commit.
|
|
3867
|
+
*/
|
|
3868
|
+
function useLatestRef(value) {
|
|
3869
|
+
const ref = React.useRef(value);
|
|
3870
|
+
useClientLayoutEffect(() => {
|
|
3871
|
+
ref.current = value;
|
|
3872
|
+
});
|
|
3873
|
+
return ref;
|
|
3874
|
+
}
|
|
3875
|
+
|
|
3876
|
+
// INTERNAL: deliberately not exported from src/index.ts.
|
|
3837
3877
|
/**
|
|
3838
|
-
*
|
|
3839
|
-
*
|
|
3840
|
-
*
|
|
3878
|
+
* The open overlays that take part, in the order they opened. Only the last
|
|
3879
|
+
* one answers Escape, so one keypress closes one layer. It's touched in
|
|
3880
|
+
* effects and key handlers, never while rendering, so a server render leaves
|
|
3881
|
+
* it alone.
|
|
3841
3882
|
*/
|
|
3842
|
-
const
|
|
3883
|
+
const openOverlays = [];
|
|
3884
|
+
/**
|
|
3885
|
+
* Holds this overlay's place in the stack of open overlays while `inStack`
|
|
3886
|
+
* is true, from the effect after it opens until the one after it closes.
|
|
3887
|
+
* Opening again puts it back on top.
|
|
3888
|
+
*
|
|
3889
|
+
* Returns a check for whether nothing in the stack sits above this overlay,
|
|
3890
|
+
* to call when a key arrives. For an overlay in the stack, that's whether it
|
|
3891
|
+
* is the topmost one. For one that isn't, such as a Toast, it's whether no
|
|
3892
|
+
* overlay is open at all.
|
|
3893
|
+
*
|
|
3894
|
+
* @function useOverlayStack
|
|
3895
|
+
* @param inStack - Whether this overlay is open and takes part.
|
|
3896
|
+
* @returns A check for whether nothing in the stack is above this overlay.
|
|
3897
|
+
*/
|
|
3898
|
+
function useOverlayStack(inStack) {
|
|
3899
|
+
const tokenRef = React.useRef({});
|
|
3900
|
+
React.useEffect(() => {
|
|
3901
|
+
if (!inStack)
|
|
3902
|
+
return undefined;
|
|
3903
|
+
const token = tokenRef.current;
|
|
3904
|
+
openOverlays.push(token);
|
|
3905
|
+
// Takes out this effect's own push. Should it ever be gone already, the
|
|
3906
|
+
// stack is left as it is: splicing at -1 would take the topmost overlay
|
|
3907
|
+
// instead.
|
|
3908
|
+
return () => {
|
|
3909
|
+
const index = openOverlays.indexOf(token);
|
|
3910
|
+
if (index !== -1)
|
|
3911
|
+
openOverlays.splice(index, 1);
|
|
3912
|
+
};
|
|
3913
|
+
}, [inStack]);
|
|
3914
|
+
return React.useCallback(() => {
|
|
3915
|
+
const top = openOverlays[openOverlays.length - 1];
|
|
3916
|
+
return top === undefined || top === tokenRef.current;
|
|
3917
|
+
}, []);
|
|
3918
|
+
}
|
|
3919
|
+
|
|
3843
3920
|
/**
|
|
3844
3921
|
* Controls the modal can hand focus to. Disabled and hidden controls are
|
|
3845
3922
|
* excluded because `focus()` on them is a no-op, which would leave focus
|
|
@@ -3998,8 +4075,8 @@ const ModalRoot = React.forwardRef(function ModalRoot({ active, isActive, onClos
|
|
|
3998
4075
|
// Cleanup returned by a consumer's callback ref on attach, held until detach.
|
|
3999
4076
|
const consumerCleanupRef = React.useRef(null);
|
|
4000
4077
|
// The forwarded ref and the internal one must both see the node: focus
|
|
4001
|
-
// management
|
|
4002
|
-
//
|
|
4078
|
+
// management and the Tab trap read `modalRootRef`, while consumers expect
|
|
4079
|
+
// their own ref to resolve.
|
|
4003
4080
|
const combinedRef = React.useCallback((node) => {
|
|
4004
4081
|
modalRootRef.current =
|
|
4005
4082
|
node;
|
|
@@ -4030,7 +4107,6 @@ const ModalRoot = React.forwardRef(function ModalRoot({ active, isActive, onClos
|
|
|
4030
4107
|
}
|
|
4031
4108
|
}, [ref]);
|
|
4032
4109
|
const previouslyFocusedRef = React.useRef(null);
|
|
4033
|
-
const stackTokenRef = React.useRef({});
|
|
4034
4110
|
const generatedTitleId = React.useId();
|
|
4035
4111
|
// A portal has no server-rendered counterpart, so the server render and the
|
|
4036
4112
|
// hydrating render have to stay inline and only move into the portal once
|
|
@@ -4048,35 +4124,31 @@ const ModalRoot = React.forwardRef(function ModalRoot({ active, isActive, onClos
|
|
|
4048
4124
|
// to it moves nothing, so it mustn't re-run the effects either.
|
|
4049
4125
|
const defaultTarget = isPortaled && portal === true ? portalContainer : undefined;
|
|
4050
4126
|
useScrollLock(isModalActive && lockScroll);
|
|
4051
|
-
//
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
};
|
|
4062
|
-
}, [isModalActive]);
|
|
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.
|
|
4127
|
+
// Read when Escape arrives, so the key listener stays put while `onClose`
|
|
4128
|
+
// changes identity, as an inline one does on every render of the parent.
|
|
4129
|
+
const onCloseRef = useLatestRef(onClose);
|
|
4130
|
+
// Joins the stack of open overlays, so the keyboard handling below only
|
|
4131
|
+
// applies while this modal is the topmost overlay.
|
|
4132
|
+
const isTopmost = useOverlayStack(isModalActive);
|
|
4133
|
+
// Escape closes; Tab cycles inside the modal. Both only for the topmost
|
|
4134
|
+
// overlay. The listener is on `window`, where a keydown arrives last, so
|
|
4135
|
+
// everything inside the modal has had the key first: React's handlers, and
|
|
4136
|
+
// `document` listeners added after this one, such as a date picker's.
|
|
4067
4137
|
React.useEffect(() => {
|
|
4068
4138
|
if (!isModalActive)
|
|
4069
4139
|
return undefined;
|
|
4070
4140
|
const handleKeyDown = (e) => {
|
|
4071
4141
|
const node = modalRootRef.current;
|
|
4072
|
-
if (
|
|
4142
|
+
if (!isTopmost())
|
|
4073
4143
|
return;
|
|
4074
|
-
}
|
|
4075
4144
|
if (e.key === 'Escape') {
|
|
4076
4145
|
// Something inside already used this Escape, such as a popup that
|
|
4077
|
-
// closed on it, so the modal waits for the next one.
|
|
4078
|
-
|
|
4079
|
-
|
|
4146
|
+
// closed on it, so the modal waits for the next one. One the modal
|
|
4147
|
+
// closes on is marked used, so nothing under it acts on it as well.
|
|
4148
|
+
if (closeOnEscape && onCloseRef.current && !e.defaultPrevented) {
|
|
4149
|
+
e.preventDefault();
|
|
4150
|
+
onCloseRef.current();
|
|
4151
|
+
}
|
|
4080
4152
|
return;
|
|
4081
4153
|
}
|
|
4082
4154
|
// A trap inside the modal, such as a portaled popup's, already moved
|
|
@@ -4108,7 +4180,7 @@ const ModalRoot = React.forwardRef(function ModalRoot({ active, isActive, onClos
|
|
|
4108
4180
|
};
|
|
4109
4181
|
window.addEventListener('keydown', handleKeyDown);
|
|
4110
4182
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
4111
|
-
}, [isModalActive, closeOnEscape,
|
|
4183
|
+
}, [isModalActive, closeOnEscape, onCloseRef, isTopmost]);
|
|
4112
4184
|
// Move focus into the modal on open, restore it on close
|
|
4113
4185
|
React.useEffect(() => {
|
|
4114
4186
|
if (!isModalActive)
|
|
@@ -5105,14 +5177,6 @@ const Tabs = withSubComponents(TabsComponent, {
|
|
|
5105
5177
|
Content: TabsContent,
|
|
5106
5178
|
}, 'Tabs');
|
|
5107
5179
|
|
|
5108
|
-
// INTERNAL: deliberately not exported from src/index.ts.
|
|
5109
|
-
/**
|
|
5110
|
-
* `useLayoutEffect` in the browser, `useEffect` on the server. A server
|
|
5111
|
-
* render runs neither, and React 18 warns about a layout effect there, so the
|
|
5112
|
-
* server is handed the plain one.
|
|
5113
|
-
*/
|
|
5114
|
-
const useClientLayoutEffect = typeof document === 'undefined' ? React.useEffect : React.useLayoutEffect;
|
|
5115
|
-
|
|
5116
5180
|
// Not exported, so it stays out of the public API that `index.ts` re-exports.
|
|
5117
5181
|
const SegmentedControlContext = React.createContext(null);
|
|
5118
5182
|
/** Sorts mounted segments into the order they sit in the page. */
|
|
@@ -5387,19 +5451,30 @@ const Loading = ({ active = false, isFullPage = false, size, color, canCancel =
|
|
|
5387
5451
|
onCancel();
|
|
5388
5452
|
}
|
|
5389
5453
|
};
|
|
5454
|
+
// Read when Escape arrives, so the key listener stays put while `onCancel`
|
|
5455
|
+
// changes identity, as an inline one does on every render of the parent.
|
|
5456
|
+
const onCancelRef = useLatestRef(onCancel);
|
|
5457
|
+
const hasOnCancel = Boolean(onCancel);
|
|
5458
|
+
// A full-page loading covers everything under it, so it joins the stack of
|
|
5459
|
+
// open overlays and takes Escape from them while it's on top. One inside a
|
|
5460
|
+
// container covers only that, so it stays out and waits, as a Toast does,
|
|
5461
|
+
// until no overlay is open.
|
|
5462
|
+
const isTopmost = useOverlayStack(active && isFullPage);
|
|
5390
5463
|
// 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.
|
|
5464
|
+
// had the key first, and an Escape it closed on doesn't cancel as well. One
|
|
5465
|
+
// that cancels is marked used, so nothing under it acts on it too.
|
|
5392
5466
|
React.useEffect(() => {
|
|
5393
|
-
if (!active || !canCancel || !
|
|
5467
|
+
if (!active || !canCancel || !hasOnCancel)
|
|
5394
5468
|
return;
|
|
5395
5469
|
const handleKeyDown = (e) => {
|
|
5396
|
-
if (e.key
|
|
5397
|
-
|
|
5398
|
-
|
|
5470
|
+
if (e.key !== 'Escape' || e.defaultPrevented || !isTopmost())
|
|
5471
|
+
return;
|
|
5472
|
+
e.preventDefault();
|
|
5473
|
+
onCancelRef.current?.();
|
|
5399
5474
|
};
|
|
5400
5475
|
window.addEventListener('keydown', handleKeyDown);
|
|
5401
5476
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
5402
|
-
}, [active, canCancel,
|
|
5477
|
+
}, [active, canCancel, hasOnCancel, onCancelRef, isTopmost]);
|
|
5403
5478
|
// Prevent body scroll when full page loading is active. Ref-counted through
|
|
5404
5479
|
// the shared helper so an overlapping Modal/Dialog/Sidebar doesn't have its
|
|
5405
5480
|
// lock cleared when this one releases.
|
|
@@ -6672,10 +6747,9 @@ const Tooltip = ({ label, content, position = 'top', color, size, active: always
|
|
|
6672
6747
|
openOnTap,
|
|
6673
6748
|
});
|
|
6674
6749
|
// Auto-placement and overflow correction
|
|
6675
|
-
|
|
6750
|
+
useClientLayoutEffect(() => {
|
|
6676
6751
|
const isAuto = position === 'auto';
|
|
6677
6752
|
if (!isAuto) {
|
|
6678
|
-
// eslint-disable-next-line react-hooks/set-state-in-effect -- layout measurement effect resolves placement
|
|
6679
6753
|
setResolvedPosition(position);
|
|
6680
6754
|
}
|
|
6681
6755
|
const showing = isVisible || alwaysActive;
|
|
@@ -7657,23 +7731,21 @@ const Step = ({ isActive = false, isCompleted = false, label, icon, clickable =
|
|
|
7657
7731
|
const stepsMarkerClass = usePrefixedClassNames('steps-marker');
|
|
7658
7732
|
const stepsContentClass = usePrefixedClassNames('steps-content');
|
|
7659
7733
|
const stepsTitleClass = usePrefixedClassNames('steps-title');
|
|
7660
|
-
const handleClick = () => {
|
|
7661
|
-
if (clickable && onClick) {
|
|
7662
|
-
onClick();
|
|
7663
|
-
}
|
|
7664
|
-
};
|
|
7665
|
-
const handleKeyDown = (e) => {
|
|
7666
|
-
if (clickable && onClick && (e.key === 'Enter' || e.key === ' ')) {
|
|
7667
|
-
e.preventDefault();
|
|
7668
|
-
onClick();
|
|
7669
|
-
}
|
|
7670
|
-
};
|
|
7671
7734
|
const resolvedCompletedIcon = completedIcon === undefined ? '\u2713' : completedIcon;
|
|
7672
7735
|
const markerContent = icon ||
|
|
7673
7736
|
(isCompleted && resolvedCompletedIcon
|
|
7674
7737
|
? resolvedCompletedIcon
|
|
7675
7738
|
: (stepNumber ?? null));
|
|
7676
|
-
|
|
7739
|
+
const title = label || children;
|
|
7740
|
+
const current = isActive ? 'step' : undefined;
|
|
7741
|
+
// The step stays a list item, and a real button inside it does the
|
|
7742
|
+
// clicking, so the list keeps its items and the button brings Enter, Space
|
|
7743
|
+
// and focus with it. A button holds only phrasing content, so its parts
|
|
7744
|
+
// are spans.
|
|
7745
|
+
if (clickable) {
|
|
7746
|
+
return (jsxRuntime.jsx("li", { className: stepClasses, ...rest, children: jsxRuntime.jsxs("button", { type: "button", className: stepsLinkClass, onClick: () => onClick?.(), "aria-current": current, children: [jsxRuntime.jsx("span", { className: stepsMarkerClass, children: markerContent }), title && (jsxRuntime.jsx("span", { className: stepsContentClass, children: jsxRuntime.jsx("span", { className: stepsTitleClass, children: title }) }))] }) }));
|
|
7747
|
+
}
|
|
7748
|
+
return (jsxRuntime.jsx("li", { className: stepClasses, "aria-current": current, ...rest, children: jsxRuntime.jsxs("div", { className: stepsLinkClass, children: [jsxRuntime.jsx("span", { className: stepsMarkerClass, children: markerContent }), title && (jsxRuntime.jsx("div", { className: stepsContentClass, children: jsxRuntime.jsx("p", { className: stepsTitleClass, children: title }) }))] }) }));
|
|
7677
7749
|
};
|
|
7678
7750
|
/**
|
|
7679
7751
|
* The `Steps` component provides a multi-step progress indicator for wizard flows, checkout processes, or any multi-step workflow.
|
|
@@ -7754,7 +7826,10 @@ const StepsComponent = ({ value = 0, items, size, color, hasMarker = true, anima
|
|
|
7754
7826
|
}
|
|
7755
7827
|
return null;
|
|
7756
7828
|
};
|
|
7757
|
-
|
|
7829
|
+
// role="list" restates the <ul>'s own role, for Safari: WebKit stops
|
|
7830
|
+
// exposing a list as one once its markers are hidden, as they are here,
|
|
7831
|
+
// unless the role is set explicitly.
|
|
7832
|
+
return (jsxRuntime.jsxs("div", { className: combinedClasses, ...rest, children: [jsxRuntime.jsx("ul", { className: listClasses, role: "list", children: renderSteps() }), hasNavigation && (jsxRuntime.jsxs("div", { className: stepsNavigationClass, children: [jsxRuntime.jsx("button", { type: "button", className: prevButtonClass, disabled: value === 0, onClick: onPrev ?? (() => onStepClick?.(value - 1)), children: prevLabel ?? 'Previous' }), jsxRuntime.jsx("button", { type: "button", className: nextButtonClass, disabled: value === totalSteps - 1, onClick: onNext ?? (() => onStepClick?.(value + 1)), children: nextLabel ?? 'Next' })] }))] }));
|
|
7758
7833
|
};
|
|
7759
7834
|
// Attach Step as static property
|
|
7760
7835
|
const Steps = withSubComponents(StepsComponent, { Step }, 'Steps');
|
|
@@ -7808,19 +7883,29 @@ const SidebarComponent = React.forwardRef(({ isOpen, onClose, position = 'left',
|
|
|
7808
7883
|
handleClose();
|
|
7809
7884
|
}
|
|
7810
7885
|
}, [overlayClose, handleClose]);
|
|
7886
|
+
// Read when Escape arrives, so the key listener stays put while
|
|
7887
|
+
// `onClose` changes identity, as an inline one does on every render of
|
|
7888
|
+
// the parent.
|
|
7889
|
+
const handleCloseRef = useLatestRef(handleClose);
|
|
7890
|
+
// Joins the stack of open overlays, so the sidebar answers Escape only
|
|
7891
|
+
// while it's the topmost overlay, and keeps it from those under it.
|
|
7892
|
+
const isTopmost = useOverlayStack(isOpen);
|
|
7893
|
+
const closesOnEscape = escapeClose && canCancel && Boolean(onClose);
|
|
7811
7894
|
// 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.
|
|
7895
|
+
// sidebar has had the key first, and an Escape it used is left to it. One
|
|
7896
|
+
// the sidebar closes on is marked used, so nothing under it acts on it.
|
|
7813
7897
|
React.useEffect(() => {
|
|
7814
|
-
if (!isOpen || !
|
|
7898
|
+
if (!isOpen || !closesOnEscape)
|
|
7815
7899
|
return;
|
|
7816
7900
|
const handleKeyDown = (e) => {
|
|
7817
|
-
if (e.key
|
|
7818
|
-
|
|
7819
|
-
|
|
7901
|
+
if (e.key !== 'Escape' || e.defaultPrevented || !isTopmost())
|
|
7902
|
+
return;
|
|
7903
|
+
e.preventDefault();
|
|
7904
|
+
handleCloseRef.current();
|
|
7820
7905
|
};
|
|
7821
7906
|
window.addEventListener('keydown', handleKeyDown);
|
|
7822
7907
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
7823
|
-
}, [isOpen,
|
|
7908
|
+
}, [isOpen, closesOnEscape, handleCloseRef, isTopmost]);
|
|
7824
7909
|
// Prevent body scroll when sidebar is open. Ref-counted through the shared
|
|
7825
7910
|
// helper so an overlapping Modal/Dialog/Loading doesn't unlock underneath.
|
|
7826
7911
|
useScrollLock(isOpen && overlay);
|
|
@@ -7830,6 +7915,7 @@ const SidebarComponent = React.forwardRef(({ isOpen, onClose, position = 'left',
|
|
|
7830
7915
|
React.useEffect(() => {
|
|
7831
7916
|
if (isOpen) {
|
|
7832
7917
|
const element = sidebarRef.current;
|
|
7918
|
+
/* istanbul ignore else: the aside renders whenever the sidebar is open, so its ref is set before this runs */
|
|
7833
7919
|
if (element) {
|
|
7834
7920
|
// Focus first focusable element or sidebar itself
|
|
7835
7921
|
const focusable = element.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
|
|
@@ -8038,14 +8124,19 @@ const Toast = React.forwardRef(({ message, type = 'default', actionType, positio
|
|
|
8038
8124
|
onAction?.();
|
|
8039
8125
|
handleClose();
|
|
8040
8126
|
}, [onAction, handleClose]);
|
|
8127
|
+
// Read when the timer runs out, a click lands outside or Escape arrives,
|
|
8128
|
+
// so the timer and the listeners stay put while `onClose` changes
|
|
8129
|
+
// identity, as an inline one does on every render of the parent. An
|
|
8130
|
+
// inline one from `ToastContainer` changes whenever another toast shows.
|
|
8131
|
+
const handleCloseRef = useLatestRef(handleClose);
|
|
8041
8132
|
React.useEffect(() => {
|
|
8042
8133
|
if (indefinite || duration === 0 || isPaused)
|
|
8043
8134
|
return undefined;
|
|
8044
8135
|
const timer = setTimeout(() => {
|
|
8045
|
-
|
|
8136
|
+
handleCloseRef.current();
|
|
8046
8137
|
}, duration);
|
|
8047
8138
|
return () => clearTimeout(timer);
|
|
8048
|
-
}, [duration, indefinite, isPaused,
|
|
8139
|
+
}, [duration, indefinite, isPaused, handleCloseRef]);
|
|
8049
8140
|
const handleMouseEnter = React.useCallback(() => {
|
|
8050
8141
|
if (pauseOnHover) {
|
|
8051
8142
|
setIsPaused(true);
|
|
@@ -8063,7 +8154,7 @@ const Toast = React.forwardRef(({ message, type = 'default', actionType, positio
|
|
|
8063
8154
|
return undefined;
|
|
8064
8155
|
const handleDocumentClick = (e) => {
|
|
8065
8156
|
if (toastRef.current && !isEventInside(e, toastRef.current)) {
|
|
8066
|
-
|
|
8157
|
+
handleCloseRef.current();
|
|
8067
8158
|
}
|
|
8068
8159
|
};
|
|
8069
8160
|
const raf = requestAnimationFrame(() => {
|
|
@@ -8073,20 +8164,24 @@ const Toast = React.forwardRef(({ message, type = 'default', actionType, positio
|
|
|
8073
8164
|
cancelAnimationFrame(raf);
|
|
8074
8165
|
document.removeEventListener('click', handleDocumentClick);
|
|
8075
8166
|
};
|
|
8076
|
-
}, [dismissible, isVisible,
|
|
8167
|
+
}, [dismissible, isVisible, handleCloseRef]);
|
|
8168
|
+
// A toast is a notification rather than a layer, so it never joins the
|
|
8169
|
+
// stack of open overlays. Asking the stack tells it whether one is open:
|
|
8170
|
+
// an Escape while one is belongs to the overlay, and the toast waits.
|
|
8171
|
+
const isTopmost = useOverlayStack(false);
|
|
8077
8172
|
// On `window`, as in `Modal`, so a popup on the page has had the key
|
|
8078
8173
|
// first, and an Escape it closed on doesn't dismiss the toast as well.
|
|
8079
8174
|
React.useEffect(() => {
|
|
8080
8175
|
if (!cancelable || !isVisible)
|
|
8081
8176
|
return undefined;
|
|
8082
8177
|
const handleKeyDown = (e) => {
|
|
8083
|
-
if (e.key === 'Escape' && !e.defaultPrevented) {
|
|
8084
|
-
|
|
8178
|
+
if (e.key === 'Escape' && !e.defaultPrevented && isTopmost()) {
|
|
8179
|
+
handleCloseRef.current();
|
|
8085
8180
|
}
|
|
8086
8181
|
};
|
|
8087
8182
|
window.addEventListener('keydown', handleKeyDown);
|
|
8088
8183
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
8089
|
-
}, [cancelable, isVisible,
|
|
8184
|
+
}, [cancelable, isVisible, handleCloseRef, isTopmost]);
|
|
8090
8185
|
const containerClasses = usePrefixedClassNames('toast-container', {
|
|
8091
8186
|
[`is-${position}`]: true,
|
|
8092
8187
|
});
|
|
@@ -9754,7 +9849,8 @@ const ListItem = ({ className, textColor, bgColor, children, ...props }) => {
|
|
|
9754
9849
|
*
|
|
9755
9850
|
* The ring is `1em` square, so `textSize` scales it. It is exposed as an
|
|
9756
9851
|
* indeterminate `progressbar` named by `ariaLabel`, and it stops spinning
|
|
9757
|
-
* under `prefers-reduced-motion: reduce` while staying drawn.
|
|
9852
|
+
* under `prefers-reduced-motion: reduce` while staying drawn. In forced
|
|
9853
|
+
* colors its two open sides take the page's color, so it stays open.
|
|
9758
9854
|
*
|
|
9759
9855
|
* @function
|
|
9760
9856
|
* @param {LoaderProps} props - Props for the Loader component.
|
|
@@ -10835,11 +10931,39 @@ const checkboxSizes = ['small', 'normal', 'medium', 'large'];
|
|
|
10835
10931
|
* <Checkbox value="vue">Vue</Checkbox>
|
|
10836
10932
|
* </Checkboxes>
|
|
10837
10933
|
*/
|
|
10838
|
-
const Checkbox = React.forwardRef(({ color, size, className, children, textColor, disabled, name, value, checked, defaultChecked, onChange, ...props }, ref) => {
|
|
10934
|
+
const Checkbox = React.forwardRef(({ color, size, className, children, textColor, disabled, name, value, checked, defaultChecked, indeterminate, onChange, ...props }, ref) => {
|
|
10839
10935
|
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
10840
10936
|
color: textColor,
|
|
10841
10937
|
...props,
|
|
10842
10938
|
});
|
|
10939
|
+
// The input's own ref, merged with the forwarded one. A callback ref's
|
|
10940
|
+
// return value is handed back, so React 19 still runs a cleanup it
|
|
10941
|
+
// returns, as it did when the ref went straight to the input.
|
|
10942
|
+
const inputRef = React.useRef(null);
|
|
10943
|
+
const setRefs = React.useCallback((node) => {
|
|
10944
|
+
inputRef.current = node;
|
|
10945
|
+
if (typeof ref === 'function')
|
|
10946
|
+
return ref(node);
|
|
10947
|
+
if (ref)
|
|
10948
|
+
ref.current = node;
|
|
10949
|
+
}, [ref]);
|
|
10950
|
+
// `indeterminate` is a DOM property with no attribute, so it's set on the
|
|
10951
|
+
// input rather than rendered. A click clears it, so this runs after every
|
|
10952
|
+
// render, not only when the prop changes. Unset, it's left alone, so a
|
|
10953
|
+
// ref that sets the property itself still works. `ownsIndeterminateRef`
|
|
10954
|
+
// remembers that this component set it, so a prop that goes back to unset
|
|
10955
|
+
// clears the dash once instead of leaving it on.
|
|
10956
|
+
const ownsIndeterminateRef = React.useRef(false);
|
|
10957
|
+
useClientLayoutEffect(() => {
|
|
10958
|
+
if (indeterminate !== undefined) {
|
|
10959
|
+
ownsIndeterminateRef.current = true;
|
|
10960
|
+
inputRef.current.indeterminate = indeterminate;
|
|
10961
|
+
}
|
|
10962
|
+
else if (ownsIndeterminateRef.current) {
|
|
10963
|
+
ownsIndeterminateRef.current = false;
|
|
10964
|
+
inputRef.current.indeterminate = false;
|
|
10965
|
+
}
|
|
10966
|
+
});
|
|
10843
10967
|
// Inherit name + selection state from a surrounding <Checkboxes> group.
|
|
10844
10968
|
// Local props always win over the group (explicit > implicit).
|
|
10845
10969
|
const group = useCheckboxesGroup();
|
|
@@ -10854,6 +10978,10 @@ const Checkbox = React.forwardRef(({ color, size, className, children, textColor
|
|
|
10854
10978
|
// the controlled/uncontrolled React warning.
|
|
10855
10979
|
const effectiveDefaultChecked = groupManaged ? undefined : defaultChecked;
|
|
10856
10980
|
const handleChange = React.useCallback((e) => {
|
|
10981
|
+
// The click that got here cleared the mixed state. Put it back now,
|
|
10982
|
+
// since a click that changes no state renders nothing to do it.
|
|
10983
|
+
if (indeterminate !== undefined)
|
|
10984
|
+
e.target.indeterminate = indeterminate;
|
|
10857
10985
|
// Local onChange always fires.
|
|
10858
10986
|
onChange?.(e);
|
|
10859
10987
|
// Dispatch to the group additionally — toggle this value's
|
|
@@ -10868,7 +10996,7 @@ const Checkbox = React.forwardRef(({ color, size, className, children, textColor
|
|
|
10868
10996
|
: currentArr.filter(v => v !== valStr);
|
|
10869
10997
|
group.onChange(next);
|
|
10870
10998
|
}
|
|
10871
|
-
}, [onChange, group, value]);
|
|
10999
|
+
}, [onChange, group, value, indeterminate]);
|
|
10872
11000
|
const mainClass = usePrefixedClassNames('styled-checkbox', 'checkbox', {
|
|
10873
11001
|
[`is-${color}`]: color && checkboxColors.includes(color),
|
|
10874
11002
|
[`is-${size}`]: size && checkboxSizes.includes(size),
|
|
@@ -10876,7 +11004,7 @@ const Checkbox = React.forwardRef(({ color, size, className, children, textColor
|
|
|
10876
11004
|
const checkboxClass = classNames(mainClass, bulmaHelperClasses, className);
|
|
10877
11005
|
const checkClass = usePrefixedClassNames('check');
|
|
10878
11006
|
const controlLabelClass = usePrefixedClassNames('control-label');
|
|
10879
|
-
return (jsxRuntime.jsxs("label", { className: checkboxClass, children: [jsxRuntime.jsx("input", { ref:
|
|
11007
|
+
return (jsxRuntime.jsxs("label", { className: checkboxClass, children: [jsxRuntime.jsx("input", { ref: setRefs, type: "checkbox", disabled: disabled, name: effectiveName, value: value, checked: effectiveChecked, defaultChecked: effectiveDefaultChecked, onChange: handleChange, ...rest }), jsxRuntime.jsx("span", { className: checkClass }), children && jsxRuntime.jsx("span", { className: controlLabelClass, children: children })] }));
|
|
10880
11008
|
});
|
|
10881
11009
|
Checkbox.displayName = 'Checkbox';
|
|
10882
11010
|
|
|
@@ -12029,7 +12157,7 @@ const Slider = React.forwardRef((props, ref) => {
|
|
|
12029
12157
|
}
|
|
12030
12158
|
return 0;
|
|
12031
12159
|
}, []);
|
|
12032
|
-
|
|
12160
|
+
useClientLayoutEffect(() => {
|
|
12033
12161
|
if (isVertical)
|
|
12034
12162
|
return;
|
|
12035
12163
|
const wrapper = wrapperRef.current;
|
|
@@ -12043,7 +12171,7 @@ const Slider = React.forwardRef((props, ref) => {
|
|
|
12043
12171
|
setNudgeX(computeNudge(el, wrapper, pct, thumbPx));
|
|
12044
12172
|
}
|
|
12045
12173
|
});
|
|
12046
|
-
|
|
12174
|
+
useClientLayoutEffect(() => {
|
|
12047
12175
|
if (isVertical || !range)
|
|
12048
12176
|
return;
|
|
12049
12177
|
const wrapper = wrapperRef.current;
|
|
@@ -12057,7 +12185,7 @@ const Slider = React.forwardRef((props, ref) => {
|
|
|
12057
12185
|
}
|
|
12058
12186
|
});
|
|
12059
12187
|
// Detect vertical tooltip overflow: flip to left if no space on right
|
|
12060
|
-
|
|
12188
|
+
useClientLayoutEffect(() => {
|
|
12061
12189
|
if (!isVertical)
|
|
12062
12190
|
return;
|
|
12063
12191
|
const wrapper = wrapperRef.current;
|
|
@@ -13089,7 +13217,10 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
13089
13217
|
const hasInput = inputValue || (openOnFocus && isActive);
|
|
13090
13218
|
return matchesInput && notAlreadyAdded && hasInput;
|
|
13091
13219
|
});
|
|
13092
|
-
|
|
13220
|
+
// At the tag limit the input isn't rendered, so the list isn't either.
|
|
13221
|
+
// Left open it would be a listbox with no combobox, offering tags past
|
|
13222
|
+
// the limit (#1087).
|
|
13223
|
+
const listOpen = isActive && !isMaxReached && filteredData.length > 0;
|
|
13093
13224
|
// keepFirst: auto-highlight first item when dropdown opens
|
|
13094
13225
|
React.useEffect(() => {
|
|
13095
13226
|
if (keepFirst && isActive && filteredData.length > 0) {
|
|
@@ -13103,17 +13234,16 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
13103
13234
|
}
|
|
13104
13235
|
onChange?.(newTags);
|
|
13105
13236
|
}, [controlledValue, onChange]);
|
|
13106
|
-
// Add tag
|
|
13237
|
+
// Add a tag from text typed, pasted or picked from the list. Only
|
|
13238
|
+
// `createTag` turns it into an object.
|
|
13107
13239
|
const addTag = React.useCallback((value) => {
|
|
13108
13240
|
if (disabled || readonly)
|
|
13109
13241
|
return;
|
|
13110
|
-
const
|
|
13111
|
-
const displayValue = typeof tagValue === 'string' ? tagValue : getDisplayValue(tagValue);
|
|
13242
|
+
const displayValue = value.trim();
|
|
13112
13243
|
if (!displayValue)
|
|
13113
13244
|
return;
|
|
13114
|
-
//
|
|
13115
|
-
|
|
13116
|
-
return;
|
|
13245
|
+
// No maxTags check: only the input and the list call this, neither
|
|
13246
|
+
// renders at the limit, and each call adds at most one tag.
|
|
13117
13247
|
// Check duplicates
|
|
13118
13248
|
if (!allowDuplicates) {
|
|
13119
13249
|
const exists = tags.some(tag => getDisplayValue(tag).toLowerCase() === displayValue.toLowerCase());
|
|
@@ -13130,7 +13260,7 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
13130
13260
|
if (beforeAdding && !beforeAdding(displayValue))
|
|
13131
13261
|
return;
|
|
13132
13262
|
// createTag transformation
|
|
13133
|
-
const finalTag = createTag ? createTag(displayValue) :
|
|
13263
|
+
const finalTag = createTag ? createTag(displayValue) : displayValue;
|
|
13134
13264
|
const newTags = [...tags, finalTag];
|
|
13135
13265
|
updateTags(newTags);
|
|
13136
13266
|
onAdd?.(finalTag);
|
|
@@ -13143,7 +13273,6 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
13143
13273
|
disabled,
|
|
13144
13274
|
readonly,
|
|
13145
13275
|
tags,
|
|
13146
|
-
maxTags,
|
|
13147
13276
|
allowDuplicates,
|
|
13148
13277
|
allowNew,
|
|
13149
13278
|
data,
|
|
@@ -13209,6 +13338,17 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
13209
13338
|
setIsActive(true);
|
|
13210
13339
|
}
|
|
13211
13340
|
}, [openOnFocus, data.length, disabled]);
|
|
13341
|
+
// Reaching the limit dismisses the list the way a click outside does, so
|
|
13342
|
+
// making room later doesn't bring it back unasked. It's an effect, not a
|
|
13343
|
+
// step in addTag, because a click on a suggestion also bubbles to the
|
|
13344
|
+
// container, whose focus on the input reopens the list in that same
|
|
13345
|
+
// event, and because a controlled value can reach the limit too.
|
|
13346
|
+
React.useEffect(() => {
|
|
13347
|
+
if (isMaxReached) {
|
|
13348
|
+
setIsActive(false);
|
|
13349
|
+
setHighlightedIndex(-1);
|
|
13350
|
+
}
|
|
13351
|
+
}, [isMaxReached]);
|
|
13212
13352
|
// Handle click outside
|
|
13213
13353
|
React.useEffect(() => {
|
|
13214
13354
|
if (!isActive)
|
|
@@ -15167,7 +15307,15 @@ const PickerPopover = ({ isOpen, onClose, anchorRef, children, position = 'botto
|
|
|
15167
15307
|
left: resolved.left,
|
|
15168
15308
|
}
|
|
15169
15309
|
: {};
|
|
15170
|
-
const panel = (jsxRuntime.jsx("div", { ref: panelRef, id: id, role: role, "aria-modal": role === 'dialog' ? 'false' : undefined, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, tabIndex: -1, className: classNames(panelClass, className), style: style,
|
|
15310
|
+
const panel = (jsxRuntime.jsx("div", { ref: panelRef, id: id, role: role, "aria-modal": role === 'dialog' ? 'false' : undefined, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, tabIndex: -1, className: classNames(panelClass, className), style: style,
|
|
15311
|
+
// An Escape pressed in the panel is the picker's, and the listener
|
|
15312
|
+
// above claims it. A Dropdown menu around the picker hears Escape in a
|
|
15313
|
+
// React handler, which runs before that listener, so the panel claims
|
|
15314
|
+
// it here too, where React gets to it first.
|
|
15315
|
+
onKeyDown: e => {
|
|
15316
|
+
if (closeOnEscape && e.key === 'Escape')
|
|
15317
|
+
e.preventDefault();
|
|
15318
|
+
}, children: children }));
|
|
15171
15319
|
if (appendToBody && isBrowser) {
|
|
15172
15320
|
return reactDom.createPortal(panel, resolveContainer());
|
|
15173
15321
|
}
|
|
@@ -15581,7 +15729,7 @@ function useSegmentedEntry(params) {
|
|
|
15581
15729
|
// any of its effects, so a focus in between is the one handed back.
|
|
15582
15730
|
const closingRef = React.useRef(false);
|
|
15583
15731
|
const wasOpenRef = React.useRef(isOpen);
|
|
15584
|
-
|
|
15732
|
+
useClientLayoutEffect(() => {
|
|
15585
15733
|
if (wasOpenRef.current && !isOpen) {
|
|
15586
15734
|
closingRef.current = true;
|
|
15587
15735
|
if (!value && isUntouched(text))
|
|
@@ -15611,12 +15759,12 @@ function useSegmentedEntry(params) {
|
|
|
15611
15759
|
setText(formatFn(next, format, locale));
|
|
15612
15760
|
}, [commitValue, isAllowed, formatFn, format, locale, setText]);
|
|
15613
15761
|
// After every segment-driven change, restore the system selection on the
|
|
15614
|
-
// active segment's char range.
|
|
15762
|
+
// active segment's char range. A layout effect runs synchronously before
|
|
15615
15763
|
// paint so the user never sees a stray caret position. We deliberately do
|
|
15616
15764
|
// NOT gate on document.activeElement: when openOnFocus is true the focus
|
|
15617
15765
|
// trap moves focus into the popover, but setSelectionRange on an unfocused
|
|
15618
15766
|
// input is well-defined and the selection becomes visible on refocus.
|
|
15619
|
-
|
|
15767
|
+
useClientLayoutEffect(() => {
|
|
15620
15768
|
if (activeSegmentIdx === null || !segmentMap || !inputRef.current)
|
|
15621
15769
|
return;
|
|
15622
15770
|
const seg = segmentMap.segments[activeSegmentIdx];
|