@allxsmith/bestax-bulma 5.34.2 → 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/Autocomplete.js +43 -8
- package/dist/esm/form/Autocomplete.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 +283 -100
- 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/Autocomplete.d.ts +6 -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/Autocomplete.d.ts +6 -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.
|
|
3837
3842
|
/**
|
|
3838
|
-
*
|
|
3839
|
-
*
|
|
3840
|
-
*
|
|
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.
|
|
3841
3846
|
*/
|
|
3842
|
-
const
|
|
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.
|
|
3877
|
+
/**
|
|
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.
|
|
3882
|
+
*/
|
|
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;
|
|
@@ -12646,6 +12774,18 @@ Rate.displayName = 'Rate';
|
|
|
12646
12774
|
// it's handed while that content is on screen because of the user's typing.
|
|
12647
12775
|
const typedNoMatches = [{ id: 'empty' }];
|
|
12648
12776
|
const nothingToAnnounce = [];
|
|
12777
|
+
// A press on an option would take focus from the field, since an option
|
|
12778
|
+
// can't hold it, and nothing would put it back. Cancelling the press keeps
|
|
12779
|
+
// it there, as the ARIA Authoring Practices' combobox does, and the click
|
|
12780
|
+
// still picks the option.
|
|
12781
|
+
const keepFocus = (e) => e.preventDefault();
|
|
12782
|
+
// An IME that's composing owns every key: Enter confirms what it composed,
|
|
12783
|
+
// and the arrows move through its candidates. Safari can say so with
|
|
12784
|
+
// `keyCode` 229 rather than `isComposing`.
|
|
12785
|
+
const isComposing = (e) => e.nativeEvent.isComposing || e.nativeEvent.keyCode === 229;
|
|
12786
|
+
// Whether an option can be picked. The arrows still reach a disabled one, so
|
|
12787
|
+
// a screen reader user finds it.
|
|
12788
|
+
const isEnabled = (item) => typeof item === 'string' || !item.disabled;
|
|
12649
12789
|
/**
|
|
12650
12790
|
* The `Autocomplete` component provides an input field with dropdown suggestions that filter based on user input.
|
|
12651
12791
|
*
|
|
@@ -12805,13 +12945,17 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
12805
12945
|
}, [keepFirst, isActive, filteredData.length, inputValue]);
|
|
12806
12946
|
// Handle keyboard navigation
|
|
12807
12947
|
const handleKeyDown = React.useCallback((e) => {
|
|
12808
|
-
if (disabled)
|
|
12948
|
+
if (disabled || isComposing(e))
|
|
12809
12949
|
return;
|
|
12810
12950
|
switch (e.key) {
|
|
12811
12951
|
case 'ArrowDown':
|
|
12812
12952
|
e.preventDefault();
|
|
12813
12953
|
if (!isActive) {
|
|
12954
|
+
// A closed list opens on its first enabled option, as the ARIA
|
|
12955
|
+
// Authoring Practices' combobox does. Alt+Down Arrow opens it
|
|
12956
|
+
// without a highlight.
|
|
12814
12957
|
setIsActive(true);
|
|
12958
|
+
setHighlightedIndex(e.altKey ? -1 : filteredData.map(isEnabled).indexOf(true));
|
|
12815
12959
|
}
|
|
12816
12960
|
else {
|
|
12817
12961
|
setHighlightedIndex(prev => prev < filteredData.length - 1 ? prev + 1 : prev);
|
|
@@ -12819,13 +12963,30 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
12819
12963
|
break;
|
|
12820
12964
|
case 'ArrowUp':
|
|
12821
12965
|
e.preventDefault();
|
|
12822
|
-
|
|
12966
|
+
if (!isActive) {
|
|
12967
|
+
// And Up Arrow opens it on its last enabled option.
|
|
12968
|
+
setIsActive(true);
|
|
12969
|
+
setHighlightedIndex(filteredData.map(isEnabled).lastIndexOf(true));
|
|
12970
|
+
}
|
|
12971
|
+
else {
|
|
12972
|
+
setHighlightedIndex(prev => (prev > 0 ? prev - 1 : prev));
|
|
12973
|
+
}
|
|
12823
12974
|
break;
|
|
12824
12975
|
case 'Enter':
|
|
12825
|
-
|
|
12826
|
-
|
|
12976
|
+
// With the list open on a highlighted option it can pick, Enter
|
|
12977
|
+
// is that option's. Otherwise it closes the list and goes on, so
|
|
12978
|
+
// a form around the field submits, as it would from any text
|
|
12979
|
+
// field.
|
|
12980
|
+
if (isActive &&
|
|
12981
|
+
highlightedIndex >= 0 &&
|
|
12982
|
+
filteredData[highlightedIndex] &&
|
|
12983
|
+
isEnabled(filteredData[highlightedIndex])) {
|
|
12984
|
+
e.preventDefault();
|
|
12827
12985
|
handleSelect(filteredData[highlightedIndex]);
|
|
12828
12986
|
}
|
|
12987
|
+
else {
|
|
12988
|
+
setIsActive(false);
|
|
12989
|
+
}
|
|
12829
12990
|
break;
|
|
12830
12991
|
case 'Escape':
|
|
12831
12992
|
// Only closing the dropdown claims the key. With it closed, the
|
|
@@ -12836,7 +12997,9 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
12836
12997
|
setHighlightedIndex(-1);
|
|
12837
12998
|
break;
|
|
12838
12999
|
case 'Tab':
|
|
12839
|
-
if (
|
|
13000
|
+
if (isActive &&
|
|
13001
|
+
highlightedIndex >= 0 &&
|
|
13002
|
+
filteredData[highlightedIndex]) {
|
|
12840
13003
|
handleSelect(filteredData[highlightedIndex]);
|
|
12841
13004
|
}
|
|
12842
13005
|
setIsActive(false);
|
|
@@ -12949,17 +13112,17 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
12949
13112
|
return null;
|
|
12950
13113
|
return spokenText(node);
|
|
12951
13114
|
}, []);
|
|
12952
|
-
const autocompleteElement = (jsxRuntime.jsxs("div", { ref: containerRef, className: combinedClasses, ...rest, children: [jsxRuntime.jsxs("div", { className: controlClasses, children: [jsxRuntime.jsx("input", { ref: combinedRef, type: "text", className: inputClasses, id: controlId, value: inputValue, placeholder: placeholder, disabled: disabled, name: name, form: form, required: required, onChange: handleInputChange, onFocus: handleFocus, onKeyDown: handleKeyDown, role: "combobox", "aria-expanded": listOpen, "aria-controls": listOpen ? listboxId : undefined, "aria-activedescendant": activeOptionId, "aria-haspopup": "listbox", "aria-autocomplete": "list", autoComplete: "off" }), showClear && (jsxRuntime.jsx("button", { type: "button", className: iconRightClickableClass, onClick: handleClear, "aria-label": "Clear", children: jsxRuntime.jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", width: "16", height: "16", "aria-hidden": "true", children: [jsxRuntime.jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), jsxRuntime.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })] }) })), loading && (jsxRuntime.jsx("span", { className: iconRightClass, children: jsxRuntime.jsx("span", { className: loaderClass }) }))] }), menuOpen && (jsxRuntime.jsx("div", { className: dropdownMenuClasses, children: jsxRuntime.jsxs("div", { ref: dropdownRef, className: dropdownContentClass, style: { maxHeight: `${maxHeight}px`, overflowY: 'auto' }, onScroll: handleDropdownScroll, children: [header && jsxRuntime.jsx("div", { className: dropdownHeaderClass, children: header }), listOpen ? (jsxRuntime.jsx("div", { id: listboxId, role: "listbox", "aria-labelledby": listLabelId, "aria-label": listLabelId ? undefined : 'Suggestions', children: filteredData.map((item, index) => {
|
|
13115
|
+
const autocompleteElement = (jsxRuntime.jsxs("div", { ref: containerRef, className: combinedClasses, ...rest, children: [jsxRuntime.jsxs("div", { className: controlClasses, children: [jsxRuntime.jsx("input", { ref: combinedRef, type: "text", className: inputClasses, id: controlId, value: inputValue, placeholder: placeholder, disabled: disabled, name: name, form: form, required: required, onChange: handleInputChange, onFocus: handleFocus, onKeyDown: handleKeyDown, role: "combobox", "aria-expanded": listOpen, "aria-controls": listOpen ? listboxId : undefined, "aria-activedescendant": activeOptionId, "aria-haspopup": "listbox", "aria-autocomplete": "list", autoComplete: "off" }), showClear && (jsxRuntime.jsx("button", { type: "button", className: iconRightClickableClass, onClick: handleClear, "aria-label": "Clear", children: jsxRuntime.jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", width: "16", height: "16", "aria-hidden": "true", children: [jsxRuntime.jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), jsxRuntime.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })] }) })), loading && (jsxRuntime.jsx("span", { className: iconRightClass, children: jsxRuntime.jsx("span", { className: loaderClass }) }))] }), menuOpen && (jsxRuntime.jsx("div", { className: dropdownMenuClasses, children: jsxRuntime.jsxs("div", { ref: dropdownRef, className: dropdownContentClass, style: { maxHeight: `${maxHeight}px`, overflowY: 'auto' }, onScroll: handleDropdownScroll, children: [header && jsxRuntime.jsx("div", { className: dropdownHeaderClass, children: header }), listOpen ? (jsxRuntime.jsx("div", { id: listboxId, role: "listbox", "aria-labelledby": listLabelId, "aria-label": listLabelId ? undefined : 'Suggestions', onMouseDown: keepFocus, children: filteredData.map((item, index) => {
|
|
12953
13116
|
const isDisabled = typeof item !== 'string' && item.disabled;
|
|
12954
13117
|
const isHighlighted = index === highlightedIndex;
|
|
12955
13118
|
const itemClasses = prefixedClassNames(classPrefix, 'dropdown-item', {
|
|
12956
13119
|
'is-active': isHighlighted,
|
|
12957
13120
|
'is-disabled': isDisabled,
|
|
12958
13121
|
});
|
|
12959
|
-
return (jsxRuntime.jsx("a", { id: `${listboxId}-${index}`, "data-index": index, className: itemClasses, onClick: () => !isDisabled && handleSelect(item), onMouseEnter: () => !isDisabled && setHighlightedIndex(index), role: "option", "aria-selected": isHighlighted, "aria-disabled": isDisabled, children: itemTemplate
|
|
13122
|
+
return (jsxRuntime.jsx("a", { id: `${listboxId}-${index}`, "data-index": index, className: itemClasses, onClick: () => !isDisabled && handleSelect(item), onMouseEnter: () => !isDisabled && setHighlightedIndex(index), role: "option", "aria-selected": isHighlighted && !isDisabled, "aria-disabled": isDisabled, children: itemTemplate
|
|
12960
13123
|
? itemTemplate(item)
|
|
12961
13124
|
: getDisplayValue(item) }, index));
|
|
12962
|
-
}) })) : (jsxRuntime.jsx("div", { ref: emptyRef, className: emptyItemClasses, children: empty })), footer && jsxRuntime.jsx("div", { className: dropdownFooterClass, children: footer })] }) })), jsxRuntime.jsx(StatusRegion, { items: emptyItems, describe: describeEmpty })] }));
|
|
13125
|
+
}) })) : (jsxRuntime.jsx("div", { ref: emptyRef, className: emptyItemClasses, onMouseDown: keepFocus, children: empty })), footer && jsxRuntime.jsx("div", { className: dropdownFooterClass, children: footer })] }) })), jsxRuntime.jsx(StatusRegion, { items: emptyItems, describe: describeEmpty })] }));
|
|
12963
13126
|
if (!insideField) {
|
|
12964
13127
|
return (jsxRuntime.jsxs(Field, { label: label, labelSize: labelSize, labelProps: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [autocompleteElement, messageEl] }));
|
|
12965
13128
|
}
|
|
@@ -13054,7 +13217,10 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
13054
13217
|
const hasInput = inputValue || (openOnFocus && isActive);
|
|
13055
13218
|
return matchesInput && notAlreadyAdded && hasInput;
|
|
13056
13219
|
});
|
|
13057
|
-
|
|
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;
|
|
13058
13224
|
// keepFirst: auto-highlight first item when dropdown opens
|
|
13059
13225
|
React.useEffect(() => {
|
|
13060
13226
|
if (keepFirst && isActive && filteredData.length > 0) {
|
|
@@ -13068,17 +13234,16 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
13068
13234
|
}
|
|
13069
13235
|
onChange?.(newTags);
|
|
13070
13236
|
}, [controlledValue, onChange]);
|
|
13071
|
-
// Add tag
|
|
13237
|
+
// Add a tag from text typed, pasted or picked from the list. Only
|
|
13238
|
+
// `createTag` turns it into an object.
|
|
13072
13239
|
const addTag = React.useCallback((value) => {
|
|
13073
13240
|
if (disabled || readonly)
|
|
13074
13241
|
return;
|
|
13075
|
-
const
|
|
13076
|
-
const displayValue = typeof tagValue === 'string' ? tagValue : getDisplayValue(tagValue);
|
|
13242
|
+
const displayValue = value.trim();
|
|
13077
13243
|
if (!displayValue)
|
|
13078
13244
|
return;
|
|
13079
|
-
//
|
|
13080
|
-
|
|
13081
|
-
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.
|
|
13082
13247
|
// Check duplicates
|
|
13083
13248
|
if (!allowDuplicates) {
|
|
13084
13249
|
const exists = tags.some(tag => getDisplayValue(tag).toLowerCase() === displayValue.toLowerCase());
|
|
@@ -13095,7 +13260,7 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
13095
13260
|
if (beforeAdding && !beforeAdding(displayValue))
|
|
13096
13261
|
return;
|
|
13097
13262
|
// createTag transformation
|
|
13098
|
-
const finalTag = createTag ? createTag(displayValue) :
|
|
13263
|
+
const finalTag = createTag ? createTag(displayValue) : displayValue;
|
|
13099
13264
|
const newTags = [...tags, finalTag];
|
|
13100
13265
|
updateTags(newTags);
|
|
13101
13266
|
onAdd?.(finalTag);
|
|
@@ -13108,7 +13273,6 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
13108
13273
|
disabled,
|
|
13109
13274
|
readonly,
|
|
13110
13275
|
tags,
|
|
13111
|
-
maxTags,
|
|
13112
13276
|
allowDuplicates,
|
|
13113
13277
|
allowNew,
|
|
13114
13278
|
data,
|
|
@@ -13174,6 +13338,17 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
13174
13338
|
setIsActive(true);
|
|
13175
13339
|
}
|
|
13176
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]);
|
|
13177
13352
|
// Handle click outside
|
|
13178
13353
|
React.useEffect(() => {
|
|
13179
13354
|
if (!isActive)
|
|
@@ -15132,7 +15307,15 @@ const PickerPopover = ({ isOpen, onClose, anchorRef, children, position = 'botto
|
|
|
15132
15307
|
left: resolved.left,
|
|
15133
15308
|
}
|
|
15134
15309
|
: {};
|
|
15135
|
-
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 }));
|
|
15136
15319
|
if (appendToBody && isBrowser) {
|
|
15137
15320
|
return reactDom.createPortal(panel, resolveContainer());
|
|
15138
15321
|
}
|
|
@@ -15546,7 +15729,7 @@ function useSegmentedEntry(params) {
|
|
|
15546
15729
|
// any of its effects, so a focus in between is the one handed back.
|
|
15547
15730
|
const closingRef = React.useRef(false);
|
|
15548
15731
|
const wasOpenRef = React.useRef(isOpen);
|
|
15549
|
-
|
|
15732
|
+
useClientLayoutEffect(() => {
|
|
15550
15733
|
if (wasOpenRef.current && !isOpen) {
|
|
15551
15734
|
closingRef.current = true;
|
|
15552
15735
|
if (!value && isUntouched(text))
|
|
@@ -15576,12 +15759,12 @@ function useSegmentedEntry(params) {
|
|
|
15576
15759
|
setText(formatFn(next, format, locale));
|
|
15577
15760
|
}, [commitValue, isAllowed, formatFn, format, locale, setText]);
|
|
15578
15761
|
// After every segment-driven change, restore the system selection on the
|
|
15579
|
-
// active segment's char range.
|
|
15762
|
+
// active segment's char range. A layout effect runs synchronously before
|
|
15580
15763
|
// paint so the user never sees a stray caret position. We deliberately do
|
|
15581
15764
|
// NOT gate on document.activeElement: when openOnFocus is true the focus
|
|
15582
15765
|
// trap moves focus into the popover, but setSelectionRange on an unfocused
|
|
15583
15766
|
// input is well-defined and the selection becomes visible on refocus.
|
|
15584
|
-
|
|
15767
|
+
useClientLayoutEffect(() => {
|
|
15585
15768
|
if (activeSegmentIdx === null || !segmentMap || !inputRef.current)
|
|
15586
15769
|
return;
|
|
15587
15770
|
const seg = segmentMap.segments[activeSegmentIdx];
|