@allxsmith/bestax-bulma 5.35.1 → 5.36.1
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/EmptyState.js +2 -2
- package/dist/esm/components/EmptyState.js.map +1 -1
- package/dist/esm/components/Menu.js +9 -2
- package/dist/esm/components/Menu.js.map +1 -1
- package/dist/esm/components/Timeline.js +150 -0
- package/dist/esm/components/Timeline.js.map +1 -0
- package/dist/esm/form/Autocomplete.js +2 -2
- package/dist/esm/form/Autocomplete.js.map +1 -1
- package/dist/esm/form/Taginput.js +64 -55
- package/dist/esm/form/Taginput.js.map +1 -1
- package/dist/esm/form/useAutoLabelId.js +3 -3
- package/dist/esm/form/useAutoLabelId.js.map +1 -1
- package/dist/extras.css +1 -1
- package/dist/extras.css.map +1 -1
- package/dist/index.cjs +904 -748
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +1 -0
- package/dist/index.esm.js.map +1 -1
- package/dist/types/components/Menu.d.ts +5 -0
- package/dist/types/components/Timeline.d.ts +130 -0
- package/dist/types/form/Taginput.d.ts +12 -5
- package/dist/types/index.d.ts +1 -0
- package/dist/types-cjs/components/Menu.d.ts +5 -0
- package/dist/types-cjs/components/Timeline.d.ts +130 -0
- package/dist/types-cjs/form/Taginput.d.ts +12 -5
- package/dist/types-cjs/index.d.ts +1 -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/_index.scss +2 -0
- package/src/scss/components/_loading.scss +26 -0
- package/src/scss/components/_menu.scss +37 -0
- package/src/scss/components/_timeline.scss +371 -0
- package/src/scss/form/_checkbox.scss +14 -14
- package/src/scss/form/_radio.scss +12 -12
- package/src/scss/form/_switch.scss +21 -21
package/dist/index.cjs
CHANGED
|
@@ -3473,7 +3473,7 @@ const EmptyStateActions = ({ className, ...props }) => {
|
|
|
3473
3473
|
};
|
|
3474
3474
|
EmptyStateActions.displayName = 'EmptyState.Actions';
|
|
3475
3475
|
/** Whether a shorthand prop was given something to render. */
|
|
3476
|
-
const isGiven = (node) => node !== undefined &&
|
|
3476
|
+
const isGiven$1 = (node) => node !== undefined &&
|
|
3477
3477
|
node !== null &&
|
|
3478
3478
|
typeof node !== 'boolean' &&
|
|
3479
3479
|
node !== '';
|
|
@@ -3520,7 +3520,7 @@ const EmptyStateRoot = React.forwardRef(({ className, icon, title, titleAs = 'h2
|
|
|
3520
3520
|
});
|
|
3521
3521
|
// The props' parts first, each only when it's given, then the children
|
|
3522
3522
|
// exactly as they were written: nothing here looks at them.
|
|
3523
|
-
return (jsxRuntime.jsxs("div", { ref: ref, className: classNames(rootClass, bulmaHelperClasses, className), ...rest, children: [isGiven(icon) && (jsxRuntime.jsx(EmptyStateIndicator, { children: typeof icon === 'string' ? jsxRuntime.jsx(Icon, { name: icon }) : icon })), isGiven(title) && (jsxRuntime.jsx(EmptyStateTitle, { as: titleAs, children: title })), isGiven(description) && (jsxRuntime.jsx(EmptyStateDescription, { children: description })), children] }));
|
|
3523
|
+
return (jsxRuntime.jsxs("div", { ref: ref, className: classNames(rootClass, bulmaHelperClasses, className), ...rest, children: [isGiven$1(icon) && (jsxRuntime.jsx(EmptyStateIndicator, { children: typeof icon === 'string' ? jsxRuntime.jsx(Icon, { name: icon }) : icon })), isGiven$1(title) && (jsxRuntime.jsx(EmptyStateTitle, { as: titleAs, children: title })), isGiven$1(description) && (jsxRuntime.jsx(EmptyStateDescription, { children: description })), children] }));
|
|
3524
3524
|
});
|
|
3525
3525
|
const EmptyState = withSubComponents(EmptyStateRoot, {
|
|
3526
3526
|
Indicator: EmptyStateIndicator,
|
|
@@ -3559,6 +3559,10 @@ const MenuLabel = ({ className, children, ...props }) => {
|
|
|
3559
3559
|
/**
|
|
3560
3560
|
* MenuList applies `menu-list` class only at the top level (not for nested lists).
|
|
3561
3561
|
*
|
|
3562
|
+
* Every level renders `role="list"`, which Safari needs: WebKit stops
|
|
3563
|
+
* exposing a `<ul>` as a list once its markers are hidden, and Bulma's reset
|
|
3564
|
+
* hides them.
|
|
3565
|
+
*
|
|
3562
3566
|
* @function
|
|
3563
3567
|
* @param {MenuListProps} props - Props for the MenuList component.
|
|
3564
3568
|
* @returns {JSX.Element} The rendered menu list.
|
|
@@ -3569,8 +3573,11 @@ const MenuList = ({ className, children, ...props }) => {
|
|
|
3569
3573
|
const ulClass = classNames(className, bulmaHelperClasses, {
|
|
3570
3574
|
[usePrefixedClassNames('menu-list')]: level === 0,
|
|
3571
3575
|
});
|
|
3572
|
-
// Increment level for nested MenuLists
|
|
3573
|
-
|
|
3576
|
+
// Increment level for nested MenuLists. role="list" restates the <ul>'s
|
|
3577
|
+
// own role, for Safari: WebKit stops exposing a list as one once its
|
|
3578
|
+
// markers are hidden, as Bulma's reset hides them, unless the role is set
|
|
3579
|
+
// explicitly. After the spread, so a caller's role can't take it away.
|
|
3580
|
+
return (jsxRuntime.jsx(MenuListLevelContext.Provider, { value: level + 1, children: jsxRuntime.jsx("ul", { className: ulClass || undefined, ...rest, role: "list", children: children }) }));
|
|
3574
3581
|
};
|
|
3575
3582
|
/**
|
|
3576
3583
|
* `href` alone — annotated so a typo is a compile error rather than a silent
|
|
@@ -7834,214 +7841,635 @@ const StepsComponent = ({ value = 0, items, size, color, hasMarker = true, anima
|
|
|
7834
7841
|
const Steps = withSubComponents(StepsComponent, { Step }, 'Steps');
|
|
7835
7842
|
|
|
7836
7843
|
/**
|
|
7837
|
-
*
|
|
7838
|
-
*
|
|
7839
|
-
*
|
|
7840
|
-
* @param {SidebarProps} props - Props for the Sidebar component.
|
|
7841
|
-
* @param {React.Ref<HTMLElement>} ref - Forwarded ref to the sidebar element.
|
|
7842
|
-
* @returns {JSX.Element | null} The rendered sidebar component.
|
|
7843
|
-
*
|
|
7844
|
-
* @example
|
|
7845
|
-
* // Basic sidebar
|
|
7846
|
-
* const [isOpen, setIsOpen] = useState(false);
|
|
7847
|
-
* <Sidebar isOpen={isOpen} onClose={() => setIsOpen(false)}>
|
|
7848
|
-
* <Menu>...</Menu>
|
|
7849
|
-
* </Sidebar>
|
|
7850
|
-
*
|
|
7851
|
-
* @example
|
|
7852
|
-
* // Right-side sidebar with custom width
|
|
7853
|
-
* <Sidebar
|
|
7854
|
-
* isOpen={isOpen}
|
|
7855
|
-
* onClose={() => setIsOpen(false)}
|
|
7856
|
-
* position="right"
|
|
7857
|
-
* width="320px"
|
|
7858
|
-
* >
|
|
7859
|
-
* <div>Panel content</div>
|
|
7860
|
-
* </Sidebar>
|
|
7861
|
-
*/
|
|
7862
|
-
const SidebarComponent = React.forwardRef(({ isOpen, onClose, position = 'left', width = '260px', isFullwidth, fullWidth, overlay = true, overlayClose = true, escapeClose = true, canCancel = true, children, inline = false, className, style, ...props }, ref) => {
|
|
7863
|
-
const { bulmaHelperClasses, rest } = useBulmaClasses(props);
|
|
7864
|
-
const resolveContainer = usePortalContainer();
|
|
7865
|
-
const { portalContainer } = useConfig();
|
|
7866
|
-
// Where the configured default puts the sidebar. An `inline` one doesn't
|
|
7867
|
-
// portal, so a change to the default moves nothing.
|
|
7868
|
-
const defaultTarget = inline ? undefined : portalContainer;
|
|
7869
|
-
const sidebarRef = React.useRef(null);
|
|
7870
|
-
// Cleanup returned by a consumer's callback ref on attach, held until detach.
|
|
7871
|
-
const consumerCleanupRef = React.useRef(null);
|
|
7872
|
-
const resolvedFullwidth = isFullwidth ?? fullWidth ?? false;
|
|
7873
|
-
// Close handler
|
|
7874
|
-
const handleClose = React.useCallback(() => {
|
|
7875
|
-
if (canCancel && onClose) {
|
|
7876
|
-
onClose();
|
|
7877
|
-
}
|
|
7878
|
-
}, [canCancel, onClose]);
|
|
7879
|
-
// Handle overlay click
|
|
7880
|
-
const handleOverlayClick = React.useCallback(() => {
|
|
7881
|
-
if (overlayClose) {
|
|
7882
|
-
handleClose();
|
|
7883
|
-
}
|
|
7884
|
-
}, [overlayClose, handleClose]);
|
|
7885
|
-
// Read when Escape arrives, so the key listener stays put while
|
|
7886
|
-
// `onClose` changes identity, as an inline one does on every render of
|
|
7887
|
-
// the parent.
|
|
7888
|
-
const handleCloseRef = useLatestRef(handleClose);
|
|
7889
|
-
// Joins the stack of open overlays, so the sidebar answers Escape only
|
|
7890
|
-
// while it's the topmost overlay, and keeps it from those under it.
|
|
7891
|
-
const isTopmost = useOverlayStack(isOpen);
|
|
7892
|
-
const closesOnEscape = escapeClose && canCancel && Boolean(onClose);
|
|
7893
|
-
// Handle escape key. On `window`, as in `Modal`, so everything inside the
|
|
7894
|
-
// sidebar has had the key first, and an Escape it used is left to it. One
|
|
7895
|
-
// the sidebar closes on is marked used, so nothing under it acts on it.
|
|
7896
|
-
React.useEffect(() => {
|
|
7897
|
-
if (!isOpen || !closesOnEscape)
|
|
7898
|
-
return;
|
|
7899
|
-
const handleKeyDown = (e) => {
|
|
7900
|
-
if (e.key !== 'Escape' || e.defaultPrevented || !isTopmost())
|
|
7901
|
-
return;
|
|
7902
|
-
e.preventDefault();
|
|
7903
|
-
handleCloseRef.current();
|
|
7904
|
-
};
|
|
7905
|
-
window.addEventListener('keydown', handleKeyDown);
|
|
7906
|
-
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
7907
|
-
}, [isOpen, closesOnEscape, handleCloseRef, isTopmost]);
|
|
7908
|
-
// Prevent body scroll when sidebar is open. Ref-counted through the shared
|
|
7909
|
-
// helper so an overlapping Modal/Dialog/Loading doesn't unlock underneath.
|
|
7910
|
-
useScrollLock(isOpen && overlay);
|
|
7911
|
-
// Focus trap (basic - focus sidebar when opened). A different
|
|
7912
|
-
// `defaultTarget` remounts the portaled sidebar, so it focuses the new
|
|
7913
|
-
// nodes then too.
|
|
7914
|
-
React.useEffect(() => {
|
|
7915
|
-
if (isOpen) {
|
|
7916
|
-
const element = sidebarRef.current;
|
|
7917
|
-
/* istanbul ignore else: the aside renders whenever the sidebar is open, so its ref is set before this runs */
|
|
7918
|
-
if (element) {
|
|
7919
|
-
// Focus first focusable element or sidebar itself
|
|
7920
|
-
const focusable = element.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
|
|
7921
|
-
if (focusable) {
|
|
7922
|
-
focusable.focus();
|
|
7923
|
-
}
|
|
7924
|
-
else {
|
|
7925
|
-
element.focus();
|
|
7926
|
-
}
|
|
7927
|
-
}
|
|
7928
|
-
}
|
|
7929
|
-
}, [isOpen, defaultTarget]);
|
|
7930
|
-
// Use combined ref
|
|
7931
|
-
const combinedRef = React.useCallback((node) => {
|
|
7932
|
-
sidebarRef.current =
|
|
7933
|
-
node;
|
|
7934
|
-
if (typeof ref === 'function') {
|
|
7935
|
-
// React 19 lets a callback ref return a cleanup function and detaches by
|
|
7936
|
-
// running it instead of calling the ref with `null`; React 18 discards the
|
|
7937
|
-
// return value entirely. Returning it from here would be a React-19-only
|
|
7938
|
-
// contract, so we hold the cleanup and run it ourselves on detach — the
|
|
7939
|
-
// same shape as `Dropdown`, `Modal`, `Dialog`, `Toast` and `Carousel`.
|
|
7940
|
-
// The form controls that merge a forwarded ref still discard the cleanup
|
|
7941
|
-
// and detach with `ref(null)`, so this is not yet library-wide — grep
|
|
7942
|
-
// `typeof ref === 'function'` for the current split.
|
|
7943
|
-
if (node === null) {
|
|
7944
|
-
const consumerCleanup = consumerCleanupRef.current;
|
|
7945
|
-
consumerCleanupRef.current = null;
|
|
7946
|
-
if (consumerCleanup) {
|
|
7947
|
-
consumerCleanup();
|
|
7948
|
-
}
|
|
7949
|
-
else {
|
|
7950
|
-
ref(null);
|
|
7951
|
-
}
|
|
7952
|
-
return;
|
|
7953
|
-
}
|
|
7954
|
-
const cleanup = ref(node);
|
|
7955
|
-
consumerCleanupRef.current =
|
|
7956
|
-
typeof cleanup === 'function' ? cleanup : null;
|
|
7957
|
-
}
|
|
7958
|
-
else if (ref) {
|
|
7959
|
-
ref.current = node;
|
|
7960
|
-
}
|
|
7961
|
-
}, [ref]);
|
|
7962
|
-
// Generate classes
|
|
7963
|
-
const sidebarClasses = usePrefixedClassNames('sidebar', {
|
|
7964
|
-
'is-active': isOpen,
|
|
7965
|
-
[`is-${position}`]: position,
|
|
7966
|
-
'is-fullwidth': resolvedFullwidth,
|
|
7967
|
-
});
|
|
7968
|
-
const backgroundClass = usePrefixedClassNames('sidebar-background', {
|
|
7969
|
-
'is-active': isOpen,
|
|
7970
|
-
});
|
|
7971
|
-
const contentClass = usePrefixedClassNames('sidebar-content');
|
|
7972
|
-
const combinedClasses = classNames(sidebarClasses, bulmaHelperClasses, className);
|
|
7973
|
-
// Custom style for width
|
|
7974
|
-
const sidebarStyle = {
|
|
7975
|
-
...style,
|
|
7976
|
-
'--bulma-sidebar-width': resolvedFullwidth ? '100%' : width,
|
|
7977
|
-
};
|
|
7978
|
-
const sidebarContent = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [overlay && (jsxRuntime.jsx("div", { className: backgroundClass, onClick: handleOverlayClick, "aria-hidden": "true" })), jsxRuntime.jsx("aside", { ref: combinedRef, className: combinedClasses, style: sidebarStyle, role: "dialog", "aria-modal": overlay ? 'true' : undefined, "aria-hidden": !isOpen, tabIndex: -1, ...rest, children: jsxRuntime.jsx("div", { className: contentClass, children: children }) })] }));
|
|
7979
|
-
if (inline) {
|
|
7980
|
-
return sidebarContent;
|
|
7981
|
-
}
|
|
7982
|
-
// Render to portal for proper stacking
|
|
7983
|
-
if (typeof document !== 'undefined') {
|
|
7984
|
-
return reactDom.createPortal(sidebarContent, resolveContainer());
|
|
7985
|
-
}
|
|
7986
|
-
return null;
|
|
7987
|
-
});
|
|
7988
|
-
/**
|
|
7989
|
-
* Container for the sidebar header.
|
|
7990
|
-
*
|
|
7991
|
-
* @function
|
|
7992
|
-
* @param {SidebarHeaderProps} props - Props for the SidebarHeader component.
|
|
7993
|
-
* @returns {JSX.Element} The rendered sidebar header.
|
|
7844
|
+
* How long after an item appears its announcement is written, in ms. The wait
|
|
7845
|
+
* lets a screen reader register a region the container has only just mounted,
|
|
7846
|
+
* and items shown in quick succession are written, and read out, together.
|
|
7994
7847
|
*/
|
|
7995
|
-
const
|
|
7996
|
-
const headerClass = usePrefixedClassNames('sidebar-header');
|
|
7997
|
-
return (jsxRuntime.jsx("div", { className: classNames(headerClass, className), ...props, children: children }));
|
|
7998
|
-
};
|
|
7848
|
+
const announceDelay = 100;
|
|
7999
7849
|
/**
|
|
8000
|
-
*
|
|
8001
|
-
*
|
|
8002
|
-
*
|
|
8003
|
-
*
|
|
8004
|
-
*
|
|
7850
|
+
* How long an announcement stays in the region once written, in ms. That's
|
|
7851
|
+
* long enough for a screen reader to pick it up. Clearing it after keeps a
|
|
7852
|
+
* second copy of the item's text from sitting in the page, where someone
|
|
7853
|
+
* reading through the page, or a test looking the text up, would find it
|
|
7854
|
+
* again.
|
|
8005
7855
|
*/
|
|
8006
|
-
const
|
|
8007
|
-
|
|
8008
|
-
|
|
7856
|
+
const announcementLifetime = 1000;
|
|
7857
|
+
const noAnnouncements = [];
|
|
7858
|
+
const nothingWritten = [];
|
|
7859
|
+
const noAnnouncementsYet = {
|
|
7860
|
+
key: 0,
|
|
7861
|
+
waiting: noAnnouncements,
|
|
7862
|
+
written: nothingWritten,
|
|
8009
7863
|
};
|
|
8010
|
-
|
|
8011
|
-
|
|
8012
|
-
|
|
8013
|
-
|
|
8014
|
-
|
|
8015
|
-
|
|
8016
|
-
|
|
8017
|
-
|
|
8018
|
-
|
|
8019
|
-
|
|
7864
|
+
// Hides the region with inline styles rather than a class, so it needs no
|
|
7865
|
+
// stylesheet, including the helper classes a modular Bulma build can leave
|
|
7866
|
+
// out. Clipping it, rather than using `display` or `visibility`, keeps it in
|
|
7867
|
+
// the accessibility tree. The declarations match the `extras-sr-only` mixin,
|
|
7868
|
+
// plus `clip-path`, which replaces the deprecated `clip`. The negative margin
|
|
7869
|
+
// pulls the 1px box back inside the page, so it doesn't add scrollable
|
|
7870
|
+
// overflow. Exported for other live regions that must work without a
|
|
7871
|
+
// stylesheet, such as a range calendar's.
|
|
7872
|
+
const visuallyHidden = {
|
|
7873
|
+
position: 'absolute',
|
|
7874
|
+
width: '1px',
|
|
7875
|
+
height: '1px',
|
|
7876
|
+
padding: 0,
|
|
7877
|
+
margin: '-1px',
|
|
7878
|
+
overflow: 'hidden',
|
|
7879
|
+
clip: 'rect(0, 0, 0, 0)',
|
|
7880
|
+
clipPath: 'inset(50%)',
|
|
7881
|
+
whiteSpace: 'nowrap',
|
|
7882
|
+
border: 0,
|
|
8020
7883
|
};
|
|
8021
7884
|
/**
|
|
8022
|
-
*
|
|
7885
|
+
* Works out what a status region says. Each item is announced once each time
|
|
7886
|
+
* it appears, so one that goes and comes back is announced again. Its
|
|
7887
|
+
* announcement is written `announceDelay` after it appears, or after the last
|
|
7888
|
+
* item to join it when several appear in quick succession, alongside whatever
|
|
7889
|
+
* the region still says. Once written, it stays for `announcementLifetime`,
|
|
7890
|
+
* whatever is written after it and whether or not its item goes in the
|
|
7891
|
+
* meantime. An item that goes before its announcement is written isn't
|
|
7892
|
+
* announced.
|
|
8023
7893
|
*
|
|
8024
|
-
*
|
|
8025
|
-
*
|
|
8026
|
-
*
|
|
8027
|
-
|
|
8028
|
-
|
|
8029
|
-
const bodyClass = usePrefixedClassNames('sidebar-body');
|
|
8030
|
-
return (jsxRuntime.jsx("div", { className: classNames(bodyClass, className), ...props, children: children }));
|
|
8031
|
-
};
|
|
8032
|
-
/**
|
|
8033
|
-
* Footer area of the sidebar.
|
|
7894
|
+
* Nothing is written in the commit that mounts the region or the item. A
|
|
7895
|
+
* screen reader reliably announces a polite live region whose content changes
|
|
7896
|
+
* after the region is already in the page, and not always one that arrives
|
|
7897
|
+
* with its content. By the time `describe` runs, an item's rendered text can
|
|
7898
|
+
* be read.
|
|
8034
7899
|
*
|
|
8035
|
-
* @function
|
|
8036
|
-
* @param
|
|
8037
|
-
* @
|
|
7900
|
+
* @function useAnnouncements
|
|
7901
|
+
* @param items - The items to announce, in the order they were shown.
|
|
7902
|
+
* @param describe - The text to announce for an item, or nothing to skip it.
|
|
7903
|
+
* It runs once the item is on screen, and it must keep its identity between
|
|
7904
|
+
* renders (a module-level function or a `useCallback`), or every render
|
|
7905
|
+
* re-checks the items.
|
|
7906
|
+
* @returns The announcements to render, in the order they were written.
|
|
8038
7907
|
*/
|
|
8039
|
-
|
|
8040
|
-
|
|
8041
|
-
|
|
8042
|
-
|
|
8043
|
-
//
|
|
8044
|
-
|
|
7908
|
+
function useAnnouncements(items, describe) {
|
|
7909
|
+
// The ids of the items on screen as of the last change, every one of which
|
|
7910
|
+
// has been looked at for an announcement already.
|
|
7911
|
+
const onScreenIdsRef = React.useRef(new Set());
|
|
7912
|
+
// Clears that haven't run yet. They outlive the batch that started them, so
|
|
7913
|
+
// they're only stopped when the region unmounts.
|
|
7914
|
+
const clearTimersRef = React.useRef(new Set());
|
|
7915
|
+
const [announcements, setAnnouncements] = React.useState(noAnnouncementsYet);
|
|
7916
|
+
// Runs after the commit that puts an item on screen, which is the first
|
|
7917
|
+
// point its rendered text can be read.
|
|
7918
|
+
React.useEffect(() => {
|
|
7919
|
+
const announced = onScreenIdsRef.current;
|
|
7920
|
+
const appeared = items.filter(item => !announced.has(item.id));
|
|
7921
|
+
// Items that have gone drop out here too, so one that comes back has
|
|
7922
|
+
// appeared again.
|
|
7923
|
+
onScreenIdsRef.current = new Set(items.map(item => item.id));
|
|
7924
|
+
const next = [];
|
|
7925
|
+
for (const item of appeared) {
|
|
7926
|
+
const text = describe(item);
|
|
7927
|
+
if (text)
|
|
7928
|
+
next.push({ id: item.id, text });
|
|
7929
|
+
}
|
|
7930
|
+
if (next.length > 0) {
|
|
7931
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect -- an item's rendered text can only be read once it's on screen
|
|
7932
|
+
setAnnouncements(current => ({
|
|
7933
|
+
...current,
|
|
7934
|
+
key: current.key + 1,
|
|
7935
|
+
// Announcements still waiting go out with the new ones, apart from
|
|
7936
|
+
// one whose item came back before it was written, which the new one
|
|
7937
|
+
// replaces. What's already written stays put.
|
|
7938
|
+
waiting: [
|
|
7939
|
+
...current.waiting.filter(waiting => !next.some(({ id }) => id === waiting.id)),
|
|
7940
|
+
...next,
|
|
7941
|
+
],
|
|
7942
|
+
}));
|
|
7943
|
+
}
|
|
7944
|
+
}, [items, describe]);
|
|
7945
|
+
// The write only touches the batch it was started for: a newer batch can
|
|
7946
|
+
// be set before the render that cancels this timer. Its clear is started
|
|
7947
|
+
// by the write itself, so it's timed from the write without waiting on a
|
|
7948
|
+
// render, and a newer batch arriving doesn't cut short what's written.
|
|
7949
|
+
const { key } = announcements;
|
|
7950
|
+
React.useEffect(() => {
|
|
7951
|
+
if (key === noAnnouncementsYet.key)
|
|
7952
|
+
return undefined;
|
|
7953
|
+
const clearTimers = clearTimersRef.current;
|
|
7954
|
+
const write = setTimeout(() => {
|
|
7955
|
+
// Whether an item is on screen is decided here, once. An item that
|
|
7956
|
+
// went before its announcement was due isn't announced, and one that
|
|
7957
|
+
// goes after keeps its announcement for the rest of its lifetime, when
|
|
7958
|
+
// the region holds the only copy of its text rather than a second one.
|
|
7959
|
+
const onScreen = onScreenIdsRef.current;
|
|
7960
|
+
setAnnouncements(current => current.key === key
|
|
7961
|
+
? {
|
|
7962
|
+
...current,
|
|
7963
|
+
waiting: noAnnouncements,
|
|
7964
|
+
written: [
|
|
7965
|
+
...current.written,
|
|
7966
|
+
...current.waiting
|
|
7967
|
+
.filter(announcement => onScreen.has(announcement.id))
|
|
7968
|
+
.map(announcement => ({ ...announcement, batch: key })),
|
|
7969
|
+
],
|
|
7970
|
+
}
|
|
7971
|
+
: current);
|
|
7972
|
+
// eslint-disable-next-line @eslint-react/web-api-no-leaked-timeout -- it has to outlive this effect when a newer batch arrives, so the unmount effect below clears it
|
|
7973
|
+
const clear = setTimeout(() => {
|
|
7974
|
+
clearTimers.delete(clear);
|
|
7975
|
+
setAnnouncements(current => {
|
|
7976
|
+
const written = current.written.filter(announcement => announcement.batch !== key);
|
|
7977
|
+
return written.length === current.written.length
|
|
7978
|
+
? current
|
|
7979
|
+
: { ...current, written };
|
|
7980
|
+
});
|
|
7981
|
+
}, announcementLifetime);
|
|
7982
|
+
clearTimers.add(clear);
|
|
7983
|
+
}, announceDelay);
|
|
7984
|
+
return () => clearTimeout(write);
|
|
7985
|
+
}, [key]);
|
|
7986
|
+
React.useEffect(() => {
|
|
7987
|
+
const clearTimers = clearTimersRef.current;
|
|
7988
|
+
return () => {
|
|
7989
|
+
clearTimers.forEach(clearTimeout);
|
|
7990
|
+
clearTimers.clear();
|
|
7991
|
+
};
|
|
7992
|
+
}, []);
|
|
7993
|
+
return announcements.written;
|
|
7994
|
+
}
|
|
7995
|
+
/**
|
|
7996
|
+
* Reads an element out roughly the way a screen reader would: its text, with
|
|
7997
|
+
* an element's `aria-label`, or an image's `alt`, standing in for what's
|
|
7998
|
+
* inside it, a break between block-level elements, and parts that are
|
|
7999
|
+
* `aria-hidden`, `hidden` or inline-styled `display: none` left out. It isn't
|
|
8000
|
+
* the full accessible name computation, so `aria-labelledby`, CSS-generated
|
|
8001
|
+
* content, content hidden by a stylesheet and other ways of naming or hiding
|
|
8002
|
+
* content aren't followed.
|
|
8003
|
+
*
|
|
8004
|
+
* @function spokenText
|
|
8005
|
+
* @param element - The element to read.
|
|
8006
|
+
* @returns Its text, with runs of whitespace collapsed and the ends trimmed.
|
|
8007
|
+
*/
|
|
8008
|
+
function spokenText(element) {
|
|
8009
|
+
return readAloud(element).replace(/\s+/g, ' ').trim();
|
|
8010
|
+
}
|
|
8011
|
+
// Elements a browser displays as blocks by default, and `br`. A screen reader
|
|
8012
|
+
// pauses between them, so their text shouldn't run into the text beside them.
|
|
8013
|
+
// They're known by tag rather than by computed `display`, which would depend
|
|
8014
|
+
// on a stylesheet and on layout.
|
|
8015
|
+
const blockLevelTags = new Set([
|
|
8016
|
+
'ADDRESS',
|
|
8017
|
+
'ARTICLE',
|
|
8018
|
+
'ASIDE',
|
|
8019
|
+
'BLOCKQUOTE',
|
|
8020
|
+
'BR',
|
|
8021
|
+
'CAPTION',
|
|
8022
|
+
'DD',
|
|
8023
|
+
'DETAILS',
|
|
8024
|
+
'DIALOG',
|
|
8025
|
+
'DIV',
|
|
8026
|
+
'DL',
|
|
8027
|
+
'DT',
|
|
8028
|
+
'FIELDSET',
|
|
8029
|
+
'FIGCAPTION',
|
|
8030
|
+
'FIGURE',
|
|
8031
|
+
'FOOTER',
|
|
8032
|
+
'FORM',
|
|
8033
|
+
'H1',
|
|
8034
|
+
'H2',
|
|
8035
|
+
'H3',
|
|
8036
|
+
'H4',
|
|
8037
|
+
'H5',
|
|
8038
|
+
'H6',
|
|
8039
|
+
'HEADER',
|
|
8040
|
+
'HGROUP',
|
|
8041
|
+
'HR',
|
|
8042
|
+
'LEGEND',
|
|
8043
|
+
'LI',
|
|
8044
|
+
'MAIN',
|
|
8045
|
+
'MENU',
|
|
8046
|
+
'NAV',
|
|
8047
|
+
'OL',
|
|
8048
|
+
'P',
|
|
8049
|
+
'PRE',
|
|
8050
|
+
'SEARCH',
|
|
8051
|
+
'SECTION',
|
|
8052
|
+
'SUMMARY',
|
|
8053
|
+
'TABLE',
|
|
8054
|
+
'TBODY',
|
|
8055
|
+
'TD',
|
|
8056
|
+
'TFOOT',
|
|
8057
|
+
'TH',
|
|
8058
|
+
'THEAD',
|
|
8059
|
+
'TR',
|
|
8060
|
+
'UL',
|
|
8061
|
+
]);
|
|
8062
|
+
/**
|
|
8063
|
+
* Whether an element is left out of what a screen reader reads.
|
|
8064
|
+
*
|
|
8065
|
+
* @function isHidden
|
|
8066
|
+
* @param element - The element to check.
|
|
8067
|
+
* @returns True for `aria-hidden="true"`, the `hidden` attribute, and an
|
|
8068
|
+
* inline `display: none`, on an HTML, SVG or any other element with an
|
|
8069
|
+
* inline style.
|
|
8070
|
+
*/
|
|
8071
|
+
const isHidden = (element) => element.getAttribute('aria-hidden') === 'true' ||
|
|
8072
|
+
element.hasAttribute('hidden') ||
|
|
8073
|
+
element.style?.display === 'none';
|
|
8074
|
+
/**
|
|
8075
|
+
* The text `spokenText` reads for an element, before its whitespace is
|
|
8076
|
+
* tidied.
|
|
8077
|
+
*
|
|
8078
|
+
* @function readAloud
|
|
8079
|
+
* @param element - The element to read.
|
|
8080
|
+
* @returns Its text.
|
|
8081
|
+
*/
|
|
8082
|
+
function readAloud(element) {
|
|
8083
|
+
if (isHidden(element))
|
|
8084
|
+
return '';
|
|
8085
|
+
const name = element.getAttribute('aria-label') ||
|
|
8086
|
+
(element.tagName === 'IMG' ? element.getAttribute('alt') : null);
|
|
8087
|
+
const text = name ||
|
|
8088
|
+
Array.from(element.childNodes, child => {
|
|
8089
|
+
if (child instanceof Element)
|
|
8090
|
+
return readAloud(child);
|
|
8091
|
+
return child.nodeType === Node.TEXT_NODE ? child.textContent : '';
|
|
8092
|
+
}).join('');
|
|
8093
|
+
// A name stands in for the element's content, and a block's content stands
|
|
8094
|
+
// apart from what's around it, so both are padded to keep them from running
|
|
8095
|
+
// into the neighboring text. An inline run, like `<span>Up</span>` then
|
|
8096
|
+
// `<span>loaded</span>`, still reads as one word.
|
|
8097
|
+
return name || blockLevelTags.has(element.tagName) ? ` ${text} ` : text;
|
|
8098
|
+
}
|
|
8099
|
+
/**
|
|
8100
|
+
* A visually hidden, polite `status` live region that a container announces
|
|
8101
|
+
* its items through. The container renders it from the moment it mounts,
|
|
8102
|
+
* whether or not it has anything to show, so the region is already in the
|
|
8103
|
+
* page when an announcement is written into it.
|
|
8104
|
+
*
|
|
8105
|
+
* The announcements are this component's own state, so writing and clearing
|
|
8106
|
+
* them re-renders the region and leaves the container's items alone. Each one
|
|
8107
|
+
* is its own keyed node, so a re-render that leaves the announcements alone
|
|
8108
|
+
* leaves the region's content alone too, and nothing is read out twice.
|
|
8109
|
+
*
|
|
8110
|
+
* @function StatusRegion
|
|
8111
|
+
* @param props - The items to announce, and the text to announce for each.
|
|
8112
|
+
* @returns The region.
|
|
8113
|
+
*/
|
|
8114
|
+
function StatusRegion({ items, describe, }) {
|
|
8115
|
+
const announcements = useAnnouncements(items, describe);
|
|
8116
|
+
// `role="status"` makes a region atomic unless it says otherwise, which
|
|
8117
|
+
// reads the whole region on every change. Each announcement is its own
|
|
8118
|
+
// node, and only additions are relevant by default, so with atomic off a
|
|
8119
|
+
// screen reader reads just what was written, and removing one announcement
|
|
8120
|
+
// doesn't read out the ones still there. A node is keyed by its batch as
|
|
8121
|
+
// well as its item, since an item that comes back can be written again
|
|
8122
|
+
// while what it said before is still there.
|
|
8123
|
+
return (jsxRuntime.jsx("div", { role: "status", "aria-live": "polite", "aria-atomic": "false", style: visuallyHidden, children: announcements.map(announcement => (jsxRuntime.jsx("div", { children: announcement.text }, `${announcement.batch}:${announcement.id}`))) }));
|
|
8124
|
+
}
|
|
8125
|
+
|
|
8126
|
+
const validTimelineColors = [
|
|
8127
|
+
'primary',
|
|
8128
|
+
'link',
|
|
8129
|
+
'info',
|
|
8130
|
+
'success',
|
|
8131
|
+
'warning',
|
|
8132
|
+
'danger',
|
|
8133
|
+
];
|
|
8134
|
+
const validTimelineTitleElements = ['h2', 'h3', 'h4', 'h5', 'h6', 'p'];
|
|
8135
|
+
// An item outside a Timeline is a record item with an h2 title.
|
|
8136
|
+
const TimelineItemContext = React.createContext({
|
|
8137
|
+
titleAs: 'h2',
|
|
8138
|
+
});
|
|
8139
|
+
/** Whether a prop was given something to render. */
|
|
8140
|
+
const isGiven = (node) => node !== undefined &&
|
|
8141
|
+
node !== null &&
|
|
8142
|
+
typeof node !== 'boolean' &&
|
|
8143
|
+
node !== '';
|
|
8144
|
+
const isTimelineColor = (color) => !!color && validTimelineColors.includes(color);
|
|
8145
|
+
/**
|
|
8146
|
+
* One event: a marker on the rail, then its title, its time and anything else about it.
|
|
8147
|
+
*
|
|
8148
|
+
* Outside a `Timeline` it's a record item with an `h2` title, though the
|
|
8149
|
+
* styles are registered on `.timeline`, so the rail and the marker need one.
|
|
8150
|
+
*
|
|
8151
|
+
* @function
|
|
8152
|
+
* @param {TimelineItemProps} props - Props for Timeline.Item.
|
|
8153
|
+
* @param {React.Ref<HTMLLIElement>} ref - Forwarded ref to the `<li>`.
|
|
8154
|
+
* @returns {JSX.Element} The rendered event.
|
|
8155
|
+
*
|
|
8156
|
+
* @example
|
|
8157
|
+
* <Timeline.Item title="Shipped" time="14 October 2026" dateTime="2026-10-14">
|
|
8158
|
+
* Left the warehouse in Leeds.
|
|
8159
|
+
* </Timeline.Item>
|
|
8160
|
+
*/
|
|
8161
|
+
const TimelineItem = React.forwardRef(({ className, title, time, dateTime, icon, color, textColor, bgColor, children, ...props }, ref) => {
|
|
8162
|
+
const { titleAs: TitleTag, state, label } = React.useContext(TimelineItemContext);
|
|
8163
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
8164
|
+
color: textColor,
|
|
8165
|
+
backgroundColor: bgColor,
|
|
8166
|
+
...props,
|
|
8167
|
+
});
|
|
8168
|
+
const itemClass = usePrefixedClassNames('timeline-item', {
|
|
8169
|
+
[`is-${state}`]: !!state,
|
|
8170
|
+
[`is-${color}`]: isTimelineColor(color),
|
|
8171
|
+
});
|
|
8172
|
+
const markerClass = usePrefixedClassNames('timeline-marker', {
|
|
8173
|
+
'is-icon': isGiven(icon),
|
|
8174
|
+
});
|
|
8175
|
+
const contentClass = usePrefixedClassNames('timeline-content');
|
|
8176
|
+
const titleClass = usePrefixedClassNames('timeline-title');
|
|
8177
|
+
const timeClass = usePrefixedClassNames('timeline-time');
|
|
8178
|
+
// The state in words, read after the title so it's in the heading too:
|
|
8179
|
+
// "Packed completed". The current item has none, since aria-current
|
|
8180
|
+
// says it. The styles are inline, so the words stay hidden in a modular
|
|
8181
|
+
// build that leaves out Bulma's helper classes.
|
|
8182
|
+
const words = label ? (jsxRuntime.jsx("span", { style: visuallyHidden, children: ` ${label}` })) : null;
|
|
8183
|
+
return (jsxRuntime.jsxs("li", { ref: ref, className: classNames(itemClass, bulmaHelperClasses, className), ...rest, ...(state && {
|
|
8184
|
+
'aria-current': state === 'active' ? 'step' : undefined,
|
|
8185
|
+
}), children: [jsxRuntime.jsx("div", { className: markerClass, "aria-hidden": "true", children: isGiven(icon) &&
|
|
8186
|
+
(typeof icon === 'string' ? jsxRuntime.jsx(Icon, { name: icon }) : icon) }), jsxRuntime.jsxs("div", { className: contentClass, children: [isGiven(title) ? (jsxRuntime.jsxs(TitleTag, { className: titleClass, children: [title, words] })) : (words), isGiven(time) &&
|
|
8187
|
+
(dateTime ? (jsxRuntime.jsx("time", { className: timeClass, dateTime: dateTime, children: time })) : (jsxRuntime.jsx("span", { className: timeClass, children: time }))), children] })] }));
|
|
8188
|
+
});
|
|
8189
|
+
TimelineItem.displayName = 'Timeline.Item';
|
|
8190
|
+
/**
|
|
8191
|
+
* The `Timeline` component renders a list of events in the order they happened, each with a marker on a line down the side.
|
|
8192
|
+
*
|
|
8193
|
+
* By default it's a record, an order's history, a ticket's activity or a
|
|
8194
|
+
* changelog, and it marks nothing. With `active` it marks progress, for
|
|
8195
|
+
* something that moves along on its own and is reported to the user: a
|
|
8196
|
+
* parcel, a deployment, a claim. That reads oldest first, and `pending` ends
|
|
8197
|
+
* it on what's under way. A journey the user moves through themselves, like
|
|
8198
|
+
* a wizard or a checkout, is `Steps`.
|
|
8199
|
+
*
|
|
8200
|
+
* It's an `<ol role="list">`, so it stays a list in Safari with its numbers
|
|
8201
|
+
* hidden, and each title is a heading at the level `titleAs` sets. The
|
|
8202
|
+
* markers are decoration and hidden from assistive tech, so the states are
|
|
8203
|
+
* read as words: `aria-current="step"` on the current item, and "completed",
|
|
8204
|
+
* "upcoming" or "in progress" after the other titles, in the language the
|
|
8205
|
+
* label props set. Nothing here is a control or a live region, so a timeline
|
|
8206
|
+
* that changes on screen keeps a `role="status"` region beside it.
|
|
8207
|
+
*
|
|
8208
|
+
* @function
|
|
8209
|
+
* @param {TimelineProps} props - Props for the Timeline component.
|
|
8210
|
+
* @param {React.Ref<HTMLOListElement>} ref - Forwarded ref to the `<ol>`.
|
|
8211
|
+
* @returns {JSX.Element} The rendered timeline.
|
|
8212
|
+
*
|
|
8213
|
+
* @example
|
|
8214
|
+
* <Timeline titleAs="h3">
|
|
8215
|
+
* <Timeline.Item title="Delivered" time="16 October 2026" dateTime="2026-10-16" />
|
|
8216
|
+
* <Timeline.Item title="Order placed" time="12 October 2026" dateTime="2026-10-12" />
|
|
8217
|
+
* </Timeline>
|
|
8218
|
+
*
|
|
8219
|
+
* @example
|
|
8220
|
+
* <Timeline titleAs="h3" active={1} pending="Out for delivery">
|
|
8221
|
+
* <Timeline.Item title="Order placed" />
|
|
8222
|
+
* <Timeline.Item title="Shipped" />
|
|
8223
|
+
* </Timeline>
|
|
8224
|
+
*/
|
|
8225
|
+
const TimelineRoot = React.forwardRef(({ className, children, active, pending, align = 'start', titleAs = 'h2', color, completedLabel = 'completed', upcomingLabel = 'upcoming', pendingLabel = 'in progress', textColor, bgColor, ...props }, ref) => {
|
|
8226
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
8227
|
+
color: textColor,
|
|
8228
|
+
backgroundColor: bgColor,
|
|
8229
|
+
...props,
|
|
8230
|
+
});
|
|
8231
|
+
const rootClass = usePrefixedClassNames('timeline', {
|
|
8232
|
+
'is-end': align === 'end',
|
|
8233
|
+
'is-alternate': align === 'alternate',
|
|
8234
|
+
[`is-${color}`]: isTimelineColor(color),
|
|
8235
|
+
});
|
|
8236
|
+
const level = validTimelineTitleElements.includes(titleAs) ? titleAs : 'h2';
|
|
8237
|
+
const labels = {
|
|
8238
|
+
completed: completedLabel,
|
|
8239
|
+
active: undefined,
|
|
8240
|
+
upcoming: upcomingLabel,
|
|
8241
|
+
pending: pendingLabel,
|
|
8242
|
+
};
|
|
8243
|
+
const contextFor = (state) => ({
|
|
8244
|
+
titleAs: level,
|
|
8245
|
+
state,
|
|
8246
|
+
label: state && labels[state],
|
|
8247
|
+
});
|
|
8248
|
+
// A fraction counts as the item it has reached, so one item is current.
|
|
8249
|
+
const reached = typeof active === 'number' ? Math.floor(active) : undefined;
|
|
8250
|
+
const stateAt = (index) => {
|
|
8251
|
+
if (reached === undefined)
|
|
8252
|
+
return undefined;
|
|
8253
|
+
if (index === reached)
|
|
8254
|
+
return 'active';
|
|
8255
|
+
return index < reached ? 'completed' : 'upcoming';
|
|
8256
|
+
};
|
|
8257
|
+
// Each child gets its state through context rather than a clone, so an
|
|
8258
|
+
// app's own component around a Timeline.Item still gets it. Fragments
|
|
8259
|
+
// and arrays are opened up first, so each item inside one counts.
|
|
8260
|
+
return (jsxRuntime.jsxs("ol", { ref: ref, className: classNames(rootClass, bulmaHelperClasses, className), ...rest, role: "list", children: [flattenChildren(children).map((child, index) => (jsxRuntime.jsx(TimelineItemContext.Provider, { value: contextFor(stateAt(index)), children: child }, React.isValidElement(child) ? child.key : index))), isGiven(pending) && (jsxRuntime.jsx(TimelineItemContext.Provider, { value: contextFor('pending'), children: jsxRuntime.jsx(TimelineItem, { title: pending }) }))] }));
|
|
8261
|
+
});
|
|
8262
|
+
const Timeline = withSubComponents(TimelineRoot, { Item: TimelineItem }, 'Timeline');
|
|
8263
|
+
|
|
8264
|
+
/**
|
|
8265
|
+
* The `Sidebar` component provides a slide-out navigation panel that appears from the left or right side of the screen.
|
|
8266
|
+
*
|
|
8267
|
+
* @function
|
|
8268
|
+
* @param {SidebarProps} props - Props for the Sidebar component.
|
|
8269
|
+
* @param {React.Ref<HTMLElement>} ref - Forwarded ref to the sidebar element.
|
|
8270
|
+
* @returns {JSX.Element | null} The rendered sidebar component.
|
|
8271
|
+
*
|
|
8272
|
+
* @example
|
|
8273
|
+
* // Basic sidebar
|
|
8274
|
+
* const [isOpen, setIsOpen] = useState(false);
|
|
8275
|
+
* <Sidebar isOpen={isOpen} onClose={() => setIsOpen(false)}>
|
|
8276
|
+
* <Menu>...</Menu>
|
|
8277
|
+
* </Sidebar>
|
|
8278
|
+
*
|
|
8279
|
+
* @example
|
|
8280
|
+
* // Right-side sidebar with custom width
|
|
8281
|
+
* <Sidebar
|
|
8282
|
+
* isOpen={isOpen}
|
|
8283
|
+
* onClose={() => setIsOpen(false)}
|
|
8284
|
+
* position="right"
|
|
8285
|
+
* width="320px"
|
|
8286
|
+
* >
|
|
8287
|
+
* <div>Panel content</div>
|
|
8288
|
+
* </Sidebar>
|
|
8289
|
+
*/
|
|
8290
|
+
const SidebarComponent = React.forwardRef(({ isOpen, onClose, position = 'left', width = '260px', isFullwidth, fullWidth, overlay = true, overlayClose = true, escapeClose = true, canCancel = true, children, inline = false, className, style, ...props }, ref) => {
|
|
8291
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses(props);
|
|
8292
|
+
const resolveContainer = usePortalContainer();
|
|
8293
|
+
const { portalContainer } = useConfig();
|
|
8294
|
+
// Where the configured default puts the sidebar. An `inline` one doesn't
|
|
8295
|
+
// portal, so a change to the default moves nothing.
|
|
8296
|
+
const defaultTarget = inline ? undefined : portalContainer;
|
|
8297
|
+
const sidebarRef = React.useRef(null);
|
|
8298
|
+
// Cleanup returned by a consumer's callback ref on attach, held until detach.
|
|
8299
|
+
const consumerCleanupRef = React.useRef(null);
|
|
8300
|
+
const resolvedFullwidth = isFullwidth ?? fullWidth ?? false;
|
|
8301
|
+
// Close handler
|
|
8302
|
+
const handleClose = React.useCallback(() => {
|
|
8303
|
+
if (canCancel && onClose) {
|
|
8304
|
+
onClose();
|
|
8305
|
+
}
|
|
8306
|
+
}, [canCancel, onClose]);
|
|
8307
|
+
// Handle overlay click
|
|
8308
|
+
const handleOverlayClick = React.useCallback(() => {
|
|
8309
|
+
if (overlayClose) {
|
|
8310
|
+
handleClose();
|
|
8311
|
+
}
|
|
8312
|
+
}, [overlayClose, handleClose]);
|
|
8313
|
+
// Read when Escape arrives, so the key listener stays put while
|
|
8314
|
+
// `onClose` changes identity, as an inline one does on every render of
|
|
8315
|
+
// the parent.
|
|
8316
|
+
const handleCloseRef = useLatestRef(handleClose);
|
|
8317
|
+
// Joins the stack of open overlays, so the sidebar answers Escape only
|
|
8318
|
+
// while it's the topmost overlay, and keeps it from those under it.
|
|
8319
|
+
const isTopmost = useOverlayStack(isOpen);
|
|
8320
|
+
const closesOnEscape = escapeClose && canCancel && Boolean(onClose);
|
|
8321
|
+
// Handle escape key. On `window`, as in `Modal`, so everything inside the
|
|
8322
|
+
// sidebar has had the key first, and an Escape it used is left to it. One
|
|
8323
|
+
// the sidebar closes on is marked used, so nothing under it acts on it.
|
|
8324
|
+
React.useEffect(() => {
|
|
8325
|
+
if (!isOpen || !closesOnEscape)
|
|
8326
|
+
return;
|
|
8327
|
+
const handleKeyDown = (e) => {
|
|
8328
|
+
if (e.key !== 'Escape' || e.defaultPrevented || !isTopmost())
|
|
8329
|
+
return;
|
|
8330
|
+
e.preventDefault();
|
|
8331
|
+
handleCloseRef.current();
|
|
8332
|
+
};
|
|
8333
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
8334
|
+
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
8335
|
+
}, [isOpen, closesOnEscape, handleCloseRef, isTopmost]);
|
|
8336
|
+
// Prevent body scroll when sidebar is open. Ref-counted through the shared
|
|
8337
|
+
// helper so an overlapping Modal/Dialog/Loading doesn't unlock underneath.
|
|
8338
|
+
useScrollLock(isOpen && overlay);
|
|
8339
|
+
// Focus trap (basic - focus sidebar when opened). A different
|
|
8340
|
+
// `defaultTarget` remounts the portaled sidebar, so it focuses the new
|
|
8341
|
+
// nodes then too.
|
|
8342
|
+
React.useEffect(() => {
|
|
8343
|
+
if (isOpen) {
|
|
8344
|
+
const element = sidebarRef.current;
|
|
8345
|
+
/* istanbul ignore else: the aside renders whenever the sidebar is open, so its ref is set before this runs */
|
|
8346
|
+
if (element) {
|
|
8347
|
+
// Focus first focusable element or sidebar itself
|
|
8348
|
+
const focusable = element.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
|
|
8349
|
+
if (focusable) {
|
|
8350
|
+
focusable.focus();
|
|
8351
|
+
}
|
|
8352
|
+
else {
|
|
8353
|
+
element.focus();
|
|
8354
|
+
}
|
|
8355
|
+
}
|
|
8356
|
+
}
|
|
8357
|
+
}, [isOpen, defaultTarget]);
|
|
8358
|
+
// Use combined ref
|
|
8359
|
+
const combinedRef = React.useCallback((node) => {
|
|
8360
|
+
sidebarRef.current =
|
|
8361
|
+
node;
|
|
8362
|
+
if (typeof ref === 'function') {
|
|
8363
|
+
// React 19 lets a callback ref return a cleanup function and detaches by
|
|
8364
|
+
// running it instead of calling the ref with `null`; React 18 discards the
|
|
8365
|
+
// return value entirely. Returning it from here would be a React-19-only
|
|
8366
|
+
// contract, so we hold the cleanup and run it ourselves on detach — the
|
|
8367
|
+
// same shape as `Dropdown`, `Modal`, `Dialog`, `Toast` and `Carousel`.
|
|
8368
|
+
// The form controls that merge a forwarded ref still discard the cleanup
|
|
8369
|
+
// and detach with `ref(null)`, so this is not yet library-wide — grep
|
|
8370
|
+
// `typeof ref === 'function'` for the current split.
|
|
8371
|
+
if (node === null) {
|
|
8372
|
+
const consumerCleanup = consumerCleanupRef.current;
|
|
8373
|
+
consumerCleanupRef.current = null;
|
|
8374
|
+
if (consumerCleanup) {
|
|
8375
|
+
consumerCleanup();
|
|
8376
|
+
}
|
|
8377
|
+
else {
|
|
8378
|
+
ref(null);
|
|
8379
|
+
}
|
|
8380
|
+
return;
|
|
8381
|
+
}
|
|
8382
|
+
const cleanup = ref(node);
|
|
8383
|
+
consumerCleanupRef.current =
|
|
8384
|
+
typeof cleanup === 'function' ? cleanup : null;
|
|
8385
|
+
}
|
|
8386
|
+
else if (ref) {
|
|
8387
|
+
ref.current = node;
|
|
8388
|
+
}
|
|
8389
|
+
}, [ref]);
|
|
8390
|
+
// Generate classes
|
|
8391
|
+
const sidebarClasses = usePrefixedClassNames('sidebar', {
|
|
8392
|
+
'is-active': isOpen,
|
|
8393
|
+
[`is-${position}`]: position,
|
|
8394
|
+
'is-fullwidth': resolvedFullwidth,
|
|
8395
|
+
});
|
|
8396
|
+
const backgroundClass = usePrefixedClassNames('sidebar-background', {
|
|
8397
|
+
'is-active': isOpen,
|
|
8398
|
+
});
|
|
8399
|
+
const contentClass = usePrefixedClassNames('sidebar-content');
|
|
8400
|
+
const combinedClasses = classNames(sidebarClasses, bulmaHelperClasses, className);
|
|
8401
|
+
// Custom style for width
|
|
8402
|
+
const sidebarStyle = {
|
|
8403
|
+
...style,
|
|
8404
|
+
'--bulma-sidebar-width': resolvedFullwidth ? '100%' : width,
|
|
8405
|
+
};
|
|
8406
|
+
const sidebarContent = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [overlay && (jsxRuntime.jsx("div", { className: backgroundClass, onClick: handleOverlayClick, "aria-hidden": "true" })), jsxRuntime.jsx("aside", { ref: combinedRef, className: combinedClasses, style: sidebarStyle, role: "dialog", "aria-modal": overlay ? 'true' : undefined, "aria-hidden": !isOpen, tabIndex: -1, ...rest, children: jsxRuntime.jsx("div", { className: contentClass, children: children }) })] }));
|
|
8407
|
+
if (inline) {
|
|
8408
|
+
return sidebarContent;
|
|
8409
|
+
}
|
|
8410
|
+
// Render to portal for proper stacking
|
|
8411
|
+
if (typeof document !== 'undefined') {
|
|
8412
|
+
return reactDom.createPortal(sidebarContent, resolveContainer());
|
|
8413
|
+
}
|
|
8414
|
+
return null;
|
|
8415
|
+
});
|
|
8416
|
+
/**
|
|
8417
|
+
* Container for the sidebar header.
|
|
8418
|
+
*
|
|
8419
|
+
* @function
|
|
8420
|
+
* @param {SidebarHeaderProps} props - Props for the SidebarHeader component.
|
|
8421
|
+
* @returns {JSX.Element} The rendered sidebar header.
|
|
8422
|
+
*/
|
|
8423
|
+
const SidebarHeader = ({ className, children, ...props }) => {
|
|
8424
|
+
const headerClass = usePrefixedClassNames('sidebar-header');
|
|
8425
|
+
return (jsxRuntime.jsx("div", { className: classNames(headerClass, className), ...props, children: children }));
|
|
8426
|
+
};
|
|
8427
|
+
/**
|
|
8428
|
+
* Title text inside the header.
|
|
8429
|
+
*
|
|
8430
|
+
* @function
|
|
8431
|
+
* @param {SidebarTitleProps} props - Props for the SidebarTitle component.
|
|
8432
|
+
* @returns {JSX.Element} The rendered sidebar title.
|
|
8433
|
+
*/
|
|
8434
|
+
const SidebarTitle = ({ className, children, ...props }) => {
|
|
8435
|
+
const titleClass = usePrefixedClassNames('sidebar-title');
|
|
8436
|
+
return (jsxRuntime.jsx("p", { className: classNames(titleClass, className), ...props, children: children }));
|
|
8437
|
+
};
|
|
8438
|
+
/**
|
|
8439
|
+
* Close button for the sidebar.
|
|
8440
|
+
*
|
|
8441
|
+
* @function
|
|
8442
|
+
* @param {SidebarCloseProps} props - Props for the SidebarClose component.
|
|
8443
|
+
* @returns {JSX.Element} The rendered close button.
|
|
8444
|
+
*/
|
|
8445
|
+
const SidebarClose = ({ className, children, ...props }) => {
|
|
8446
|
+
const closeClass = usePrefixedClassNames('sidebar-close');
|
|
8447
|
+
return (jsxRuntime.jsx("button", { className: classNames(closeClass, className), ...props, "aria-label": props['aria-label'] || 'Close', type: buttonType(props.type), children: children }));
|
|
8448
|
+
};
|
|
8449
|
+
/**
|
|
8450
|
+
* Main content area of the sidebar.
|
|
8451
|
+
*
|
|
8452
|
+
* @function
|
|
8453
|
+
* @param {SidebarBodyProps} props - Props for the SidebarBody component.
|
|
8454
|
+
* @returns {JSX.Element} The rendered sidebar body.
|
|
8455
|
+
*/
|
|
8456
|
+
const SidebarBody = ({ className, children, ...props }) => {
|
|
8457
|
+
const bodyClass = usePrefixedClassNames('sidebar-body');
|
|
8458
|
+
return (jsxRuntime.jsx("div", { className: classNames(bodyClass, className), ...props, children: children }));
|
|
8459
|
+
};
|
|
8460
|
+
/**
|
|
8461
|
+
* Footer area of the sidebar.
|
|
8462
|
+
*
|
|
8463
|
+
* @function
|
|
8464
|
+
* @param {SidebarFooterProps} props - Props for the SidebarFooter component.
|
|
8465
|
+
* @returns {JSX.Element} The rendered sidebar footer.
|
|
8466
|
+
*/
|
|
8467
|
+
const SidebarFooter = ({ className, children, ...props }) => {
|
|
8468
|
+
const footerClass = usePrefixedClassNames('sidebar-footer');
|
|
8469
|
+
return (jsxRuntime.jsx("div", { className: classNames(footerClass, className), ...props, children: children }));
|
|
8470
|
+
};
|
|
8471
|
+
// Attach static subcomponents
|
|
8472
|
+
const Sidebar = withSubComponents(SidebarComponent, {
|
|
8045
8473
|
Header: SidebarHeader,
|
|
8046
8474
|
Title: SidebarTitle,
|
|
8047
8475
|
Close: SidebarClose,
|
|
@@ -9180,208 +9608,25 @@ const Box = ({ className, textColor, color, bgColor, hasShadow = true, children,
|
|
|
9180
9608
|
const Code = ({ className, textColor, bgColor, children, ...props }) => {
|
|
9181
9609
|
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
9182
9610
|
color: textColor,
|
|
9183
|
-
backgroundColor: bgColor,
|
|
9184
|
-
...props,
|
|
9185
|
-
});
|
|
9186
|
-
const bulmaClasses = usePrefixedClassNames();
|
|
9187
|
-
const codeClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
|
|
9188
|
-
return (jsxRuntime.jsx("code", { className: codeClasses || undefined, ...rest, children: children }));
|
|
9189
|
-
};
|
|
9190
|
-
|
|
9191
|
-
// Valid size modifiers for the content class
|
|
9192
|
-
const validSizes = ['small', 'medium', 'large'];
|
|
9193
|
-
/**
|
|
9194
|
-
* The `Content` component applies Bulma’s typographic styles to its children, enhancing the appearance of HTML elements like paragraphs, headings, lists, and tables.
|
|
9195
|
-
*
|
|
9196
|
-
* @function
|
|
9197
|
-
* @param {ContentProps} props - Props for the Content component.
|
|
9198
|
-
* @returns {JSX.Element} The rendered content block.
|
|
9199
|
-
* @see {@link https://bulma.io/documentation/elements/content/ | Bulma Content documentation}
|
|
9200
|
-
*/
|
|
9201
|
-
const Content = ({ className, textColor, color, bgColor, size, children, ...props }) => {
|
|
9202
|
-
/**
|
|
9203
|
-
* Generates Bulma helper classes and separates out remaining props.
|
|
9204
|
-
*/
|
|
9205
|
-
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
9206
|
-
color: textColor ?? color,
|
|
9207
|
-
backgroundColor: bgColor,
|
|
9208
|
-
...props,
|
|
9209
|
-
});
|
|
9210
|
-
const bulmaClasses = usePrefixedClassNames('content', {
|
|
9211
|
-
[`is-${size}`]: size && size !== 'normal' && validSizes.includes(size),
|
|
9212
|
-
});
|
|
9213
|
-
const contentClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
|
|
9214
|
-
return (jsxRuntime.jsx("div", { className: contentClasses, ...rest, children: children }));
|
|
9215
|
-
};
|
|
9216
|
-
|
|
9217
|
-
/**
|
|
9218
|
-
* The `Delete` component provides a Bulma-styled close/delete button for dismissing modals, notifications, tags, messages, and more.
|
|
9219
|
-
*
|
|
9220
|
-
* It renders `type="button"`, so a delete button inside a form does not submit it.
|
|
9221
|
-
*
|
|
9222
|
-
* @function
|
|
9223
|
-
* @param {DeleteProps} props - Props for the Delete component.
|
|
9224
|
-
* @returns {JSX.Element} The rendered delete button.
|
|
9225
|
-
* @see {@link https://bulma.io/documentation/elements/delete/ | Bulma Delete documentation}
|
|
9226
|
-
*/
|
|
9227
|
-
const Delete = ({ className, textColor, color, bgColor, onClick, size, ariaLabel, disabled = false, ...props }) => {
|
|
9228
|
-
/**
|
|
9229
|
-
* Generates Bulma helper classes and separates out remaining props.
|
|
9230
|
-
*/
|
|
9231
|
-
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
9232
|
-
color: textColor ?? color,
|
|
9233
|
-
backgroundColor: bgColor,
|
|
9234
|
-
...props,
|
|
9235
|
-
});
|
|
9236
|
-
const bulmaClasses = usePrefixedClassNames('delete', {
|
|
9237
|
-
[`is-${size}`]: size,
|
|
9238
|
-
'is-disabled': disabled,
|
|
9239
|
-
});
|
|
9240
|
-
const classes = classNames(bulmaClasses, bulmaHelperClasses, className);
|
|
9241
|
-
return (jsxRuntime.jsx("button", { className: classes, onClick: onClick, disabled: disabled, ...rest, "aria-label": rest['aria-label'] || ariaLabel || 'Close', type: buttonType(rest.type) }));
|
|
9242
|
-
};
|
|
9243
|
-
|
|
9244
|
-
/**
|
|
9245
|
-
* The `Divider` component renders a styled horizontal rule (`<hr>`) element with Bulma helper class integration.
|
|
9246
|
-
*
|
|
9247
|
-
* @function
|
|
9248
|
-
* @param {DividerProps} props - Props for the Divider component.
|
|
9249
|
-
* @returns {JSX.Element} The rendered hr element.
|
|
9250
|
-
*/
|
|
9251
|
-
const Divider = ({ className, bgColor, ...props }) => {
|
|
9252
|
-
/**
|
|
9253
|
-
* Generates Bulma helper classes and separates out remaining props.
|
|
9254
|
-
*/
|
|
9255
|
-
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
9256
|
-
backgroundColor: bgColor,
|
|
9257
|
-
...props,
|
|
9258
|
-
});
|
|
9259
|
-
const bulmaClasses = usePrefixedClassNames();
|
|
9260
|
-
const dividerClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
|
|
9261
|
-
return jsxRuntime.jsx("hr", { className: dividerClasses || undefined, ...rest });
|
|
9262
|
-
};
|
|
9263
|
-
|
|
9264
|
-
/**
|
|
9265
|
-
* The `Emphasis` component renders a styled `<em>` element with Bulma helper class integration.
|
|
9266
|
-
*
|
|
9267
|
-
* @function
|
|
9268
|
-
* @param {EmphasisProps} props - Props for the Emphasis component.
|
|
9269
|
-
* @returns {JSX.Element} The rendered em element.
|
|
9270
|
-
*/
|
|
9271
|
-
const Emphasis = ({ className, textColor, bgColor, children, ...props }) => {
|
|
9272
|
-
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
9273
|
-
color: textColor,
|
|
9274
|
-
backgroundColor: bgColor,
|
|
9275
|
-
...props,
|
|
9276
|
-
});
|
|
9277
|
-
const bulmaClasses = usePrefixedClassNames();
|
|
9278
|
-
const emphasisClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
|
|
9279
|
-
return (jsxRuntime.jsx("em", { className: emphasisClasses || undefined, ...rest, children: children }));
|
|
9280
|
-
};
|
|
9281
|
-
|
|
9282
|
-
/**
|
|
9283
|
-
* FigureCaption component for rendering a styled figcaption element.
|
|
9284
|
-
*
|
|
9285
|
-
* A FigureCaption wraps the HTML `<figcaption>` element with Bulma helper class integration.
|
|
9286
|
-
* Use it inside Figure components to provide captions for images or other media.
|
|
9287
|
-
*
|
|
9288
|
-
* @function
|
|
9289
|
-
* @param {FigureCaptionProps} props - Props for the FigureCaption component.
|
|
9290
|
-
* @returns {JSX.Element} The rendered figcaption element.
|
|
9291
|
-
*/
|
|
9292
|
-
const FigureCaption = ({ className, textColor, bgColor, children, ...props }) => {
|
|
9293
|
-
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
9294
|
-
color: textColor,
|
|
9295
|
-
backgroundColor: bgColor,
|
|
9296
|
-
...props,
|
|
9297
|
-
});
|
|
9298
|
-
const bulmaClasses = usePrefixedClassNames();
|
|
9299
|
-
const captionClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
|
|
9300
|
-
return (jsxRuntime.jsx("figcaption", { className: captionClasses || undefined, ...rest, children: children }));
|
|
9301
|
-
};
|
|
9302
|
-
/**
|
|
9303
|
-
* The `Figure` component renders a styled `<figure>` element with Bulma helper class integration.
|
|
9304
|
-
*
|
|
9305
|
-
* @function
|
|
9306
|
-
* @param {FigureProps} props - Props for the Figure component.
|
|
9307
|
-
* @returns {JSX.Element} The rendered figure element.
|
|
9308
|
-
*/
|
|
9309
|
-
const FigureComponent = ({ className, textColor, bgColor, children, ...props }) => {
|
|
9310
|
-
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
9311
|
-
color: textColor,
|
|
9312
|
-
backgroundColor: bgColor,
|
|
9313
|
-
...props,
|
|
9314
|
-
});
|
|
9315
|
-
const bulmaClasses = usePrefixedClassNames();
|
|
9316
|
-
const figureClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
|
|
9317
|
-
return (jsxRuntime.jsx("figure", { className: figureClasses || undefined, ...rest, children: children }));
|
|
9318
|
-
};
|
|
9319
|
-
/**
|
|
9320
|
-
* Figure component with Caption subcomponent.
|
|
9321
|
-
*
|
|
9322
|
-
* @example
|
|
9323
|
-
* <Figure>
|
|
9324
|
-
* <img src="image.jpg" alt="Description" />
|
|
9325
|
-
* <Figure.Caption>Image caption text</Figure.Caption>
|
|
9326
|
-
* </Figure>
|
|
9327
|
-
*/
|
|
9328
|
-
const Figure = withSubComponents(FigureComponent, {
|
|
9329
|
-
Caption: FigureCaption,
|
|
9330
|
-
}, 'Figure');
|
|
9331
|
-
|
|
9332
|
-
/**
|
|
9333
|
-
* Distinguishes an `IconProps` object from a plain custom node (an inline SVG, a
|
|
9334
|
-
* `react-icons` component, …) in a slot that accepts either — `Control`'s
|
|
9335
|
-
* `iconLeft`/`iconRight`, `IconText`'s `iconProps`.
|
|
9336
|
-
*
|
|
9337
|
-
* One copy, imported by both consumers: two hand-maintained copies, one per
|
|
9338
|
-
* consumer, is how the deprecated `icon` path came to crash both (#663).
|
|
9339
|
-
*
|
|
9340
|
-
* Its own module rather than `Icon.tsx` because `src/index.ts` wildcard-exports
|
|
9341
|
-
* that file, and everything published there is public forever. `polymorphic.ts`
|
|
9342
|
-
* makes the same call for `isCustomElement`: an internal discriminator is not
|
|
9343
|
-
* something to support for the life of the package.
|
|
9344
|
-
*
|
|
9345
|
-
* It tests each member's VALUE, not merely its key, and only after everything
|
|
9346
|
-
* React owns is out of the way. `'icon' in value` was enough
|
|
9347
|
-
* to claim a Font Awesome `IconDefinition` — `{ prefix, iconName, icon: [w, h, …,
|
|
9348
|
-
* path] }` — whose `icon` is a path array rather than a class string. That object
|
|
9349
|
-
* is not a renderable node either, so the honest outcome is the one this slot has
|
|
9350
|
-
* always had for it: the node branch, and React's own "Objects are not valid as a
|
|
9351
|
-
* React child". Claiming it here instead rendered a blank container that still
|
|
9352
|
-
* reserved layout.
|
|
9353
|
-
*
|
|
9354
|
-
* A member added to `IconProps` needs a line here, and a missed one is a crash
|
|
9355
|
-
* rather than a fallthrough. That is the cost of a slot taking either a props
|
|
9356
|
-
* object or a node; keeping the guard in one place is what keeps the cost to a
|
|
9357
|
-
* single line.
|
|
9358
|
-
*/
|
|
9359
|
-
function isIconProps(value) {
|
|
9360
|
-
if (typeof value !== 'object' || value === null)
|
|
9361
|
-
return false;
|
|
9362
|
-
// Anything React owns. `isValidElement` covers elements only; a PORTAL is a
|
|
9363
|
-
// valid `ReactNode` that fails it and carries a `children` key, so the member
|
|
9364
|
-
// test below claimed it and `Control` spread it into `<Icon>` as props —
|
|
9365
|
-
// rendering the portal's child locally instead of in its container. Every
|
|
9366
|
-
// React internal carries `$$typeof`, so testing for it covers lazy, context
|
|
9367
|
-
// and whatever React adds next, rather than portals alone.
|
|
9368
|
-
if (React.isValidElement(value) || '$$typeof' in value)
|
|
9369
|
-
return false;
|
|
9370
|
-
const candidate = value;
|
|
9371
|
-
return (typeof candidate.name === 'string' ||
|
|
9372
|
-
typeof candidate.icon === 'string' ||
|
|
9373
|
-
'children' in candidate);
|
|
9374
|
-
}
|
|
9611
|
+
backgroundColor: bgColor,
|
|
9612
|
+
...props,
|
|
9613
|
+
});
|
|
9614
|
+
const bulmaClasses = usePrefixedClassNames();
|
|
9615
|
+
const codeClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
|
|
9616
|
+
return (jsxRuntime.jsx("code", { className: codeClasses || undefined, ...rest, children: children }));
|
|
9617
|
+
};
|
|
9375
9618
|
|
|
9619
|
+
// Valid size modifiers for the content class
|
|
9620
|
+
const validSizes = ['small', 'medium', 'large'];
|
|
9376
9621
|
/**
|
|
9377
|
-
* The `
|
|
9622
|
+
* The `Content` component applies Bulma’s typographic styles to its children, enhancing the appearance of HTML elements like paragraphs, headings, lists, and tables.
|
|
9378
9623
|
*
|
|
9379
9624
|
* @function
|
|
9380
|
-
* @param {
|
|
9381
|
-
* @returns {JSX.Element} The rendered
|
|
9382
|
-
* @see {@link https://bulma.io/documentation/elements/
|
|
9625
|
+
* @param {ContentProps} props - Props for the Content component.
|
|
9626
|
+
* @returns {JSX.Element} The rendered content block.
|
|
9627
|
+
* @see {@link https://bulma.io/documentation/elements/content/ | Bulma Content documentation}
|
|
9383
9628
|
*/
|
|
9384
|
-
const
|
|
9629
|
+
const Content = ({ className, textColor, color, bgColor, size, children, ...props }) => {
|
|
9385
9630
|
/**
|
|
9386
9631
|
* Generates Bulma helper classes and separates out remaining props.
|
|
9387
9632
|
*/
|
|
@@ -9390,23 +9635,24 @@ const IconTextComponent = ({ className, textColor, color, bgColor, iconProps, ch
|
|
|
9390
9635
|
backgroundColor: bgColor,
|
|
9391
9636
|
...props,
|
|
9392
9637
|
});
|
|
9393
|
-
const bulmaClasses = usePrefixedClassNames('
|
|
9394
|
-
|
|
9395
|
-
|
|
9396
|
-
|
|
9397
|
-
|
|
9638
|
+
const bulmaClasses = usePrefixedClassNames('content', {
|
|
9639
|
+
[`is-${size}`]: size && size !== 'normal' && validSizes.includes(size),
|
|
9640
|
+
});
|
|
9641
|
+
const contentClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
|
|
9642
|
+
return (jsxRuntime.jsx("div", { className: contentClasses, ...rest, children: children }));
|
|
9398
9643
|
};
|
|
9399
|
-
const IconText = withSubComponents(IconTextComponent, { Icon }, 'IconText');
|
|
9400
9644
|
|
|
9401
9645
|
/**
|
|
9402
|
-
* The `
|
|
9646
|
+
* The `Delete` component provides a Bulma-styled close/delete button for dismissing modals, notifications, tags, messages, and more.
|
|
9647
|
+
*
|
|
9648
|
+
* It renders `type="button"`, so a delete button inside a form does not submit it.
|
|
9403
9649
|
*
|
|
9404
9650
|
* @function
|
|
9405
|
-
* @param {
|
|
9406
|
-
* @returns {JSX.Element} The rendered
|
|
9407
|
-
* @see {@link https://bulma.io/documentation/elements/
|
|
9651
|
+
* @param {DeleteProps} props - Props for the Delete component.
|
|
9652
|
+
* @returns {JSX.Element} The rendered delete button.
|
|
9653
|
+
* @see {@link https://bulma.io/documentation/elements/delete/ | Bulma Delete documentation}
|
|
9408
9654
|
*/
|
|
9409
|
-
const
|
|
9655
|
+
const Delete = ({ className, textColor, color, bgColor, onClick, size, ariaLabel, disabled = false, ...props }) => {
|
|
9410
9656
|
/**
|
|
9411
9657
|
* Generates Bulma helper classes and separates out remaining props.
|
|
9412
9658
|
*/
|
|
@@ -9415,310 +9661,209 @@ const Image = ({ as, className, textColor, color, bgColor, size, isRounded, isRe
|
|
|
9415
9661
|
backgroundColor: bgColor,
|
|
9416
9662
|
...props,
|
|
9417
9663
|
});
|
|
9418
|
-
const bulmaClasses = usePrefixedClassNames('
|
|
9664
|
+
const bulmaClasses = usePrefixedClassNames('delete', {
|
|
9419
9665
|
[`is-${size}`]: size,
|
|
9420
|
-
'
|
|
9666
|
+
'is-disabled': disabled,
|
|
9421
9667
|
});
|
|
9422
|
-
const
|
|
9423
|
-
|
|
9424
|
-
// If not, use <figure> for aspect ratios or children, <div> otherwise.
|
|
9425
|
-
let Tag;
|
|
9426
|
-
if (as) {
|
|
9427
|
-
Tag = as;
|
|
9428
|
-
}
|
|
9429
|
-
else if (size && typeof size === 'string' && size.includes('by')) {
|
|
9430
|
-
Tag = 'figure';
|
|
9431
|
-
}
|
|
9432
|
-
else {
|
|
9433
|
-
Tag = 'div';
|
|
9434
|
-
}
|
|
9435
|
-
const roundedClass = usePrefixedClassNames('is-rounded');
|
|
9436
|
-
const content = children ? (children) : (jsxRuntime.jsx("img", { className: classNames({ [roundedClass]: isRounded }) || undefined, src: src, alt: alt, ...(isRetina && src ? { srcSet: `${src} 2x` } : {}) }));
|
|
9437
|
-
return (jsxRuntime.jsx(Tag, { className: imageClasses, ...rest, children: content }));
|
|
9668
|
+
const classes = classNames(bulmaClasses, bulmaHelperClasses, className);
|
|
9669
|
+
return (jsxRuntime.jsx("button", { className: classes, onClick: onClick, disabled: disabled, ...rest, "aria-label": rest['aria-label'] || ariaLabel || 'Close', type: buttonType(rest.type) }));
|
|
9438
9670
|
};
|
|
9439
9671
|
|
|
9440
9672
|
/**
|
|
9441
|
-
*
|
|
9442
|
-
*
|
|
9443
|
-
*
|
|
9673
|
+
* The `Divider` component renders a styled horizontal rule (`<hr>`) element with Bulma helper class integration.
|
|
9674
|
+
*
|
|
9675
|
+
* @function
|
|
9676
|
+
* @param {DividerProps} props - Props for the Divider component.
|
|
9677
|
+
* @returns {JSX.Element} The rendered hr element.
|
|
9444
9678
|
*/
|
|
9445
|
-
const
|
|
9679
|
+
const Divider = ({ className, bgColor, ...props }) => {
|
|
9680
|
+
/**
|
|
9681
|
+
* Generates Bulma helper classes and separates out remaining props.
|
|
9682
|
+
*/
|
|
9683
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
9684
|
+
backgroundColor: bgColor,
|
|
9685
|
+
...props,
|
|
9686
|
+
});
|
|
9687
|
+
const bulmaClasses = usePrefixedClassNames();
|
|
9688
|
+
const dividerClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
|
|
9689
|
+
return jsxRuntime.jsx("hr", { className: dividerClasses || undefined, ...rest });
|
|
9690
|
+
};
|
|
9691
|
+
|
|
9446
9692
|
/**
|
|
9447
|
-
*
|
|
9448
|
-
*
|
|
9449
|
-
*
|
|
9450
|
-
*
|
|
9451
|
-
*
|
|
9693
|
+
* The `Emphasis` component renders a styled `<em>` element with Bulma helper class integration.
|
|
9694
|
+
*
|
|
9695
|
+
* @function
|
|
9696
|
+
* @param {EmphasisProps} props - Props for the Emphasis component.
|
|
9697
|
+
* @returns {JSX.Element} The rendered em element.
|
|
9452
9698
|
*/
|
|
9453
|
-
const
|
|
9454
|
-
const
|
|
9455
|
-
|
|
9456
|
-
|
|
9457
|
-
|
|
9458
|
-
|
|
9459
|
-
|
|
9460
|
-
|
|
9461
|
-
|
|
9462
|
-
// stylesheet, including the helper classes a modular Bulma build can leave
|
|
9463
|
-
// out. Clipping it, rather than using `display` or `visibility`, keeps it in
|
|
9464
|
-
// the accessibility tree. The declarations match the `extras-sr-only` mixin,
|
|
9465
|
-
// plus `clip-path`, which replaces the deprecated `clip`. The negative margin
|
|
9466
|
-
// pulls the 1px box back inside the page, so it doesn't add scrollable
|
|
9467
|
-
// overflow. Exported for other live regions that must work without a
|
|
9468
|
-
// stylesheet, such as a range calendar's.
|
|
9469
|
-
const visuallyHidden = {
|
|
9470
|
-
position: 'absolute',
|
|
9471
|
-
width: '1px',
|
|
9472
|
-
height: '1px',
|
|
9473
|
-
padding: 0,
|
|
9474
|
-
margin: '-1px',
|
|
9475
|
-
overflow: 'hidden',
|
|
9476
|
-
clip: 'rect(0, 0, 0, 0)',
|
|
9477
|
-
clipPath: 'inset(50%)',
|
|
9478
|
-
whiteSpace: 'nowrap',
|
|
9479
|
-
border: 0,
|
|
9699
|
+
const Emphasis = ({ className, textColor, bgColor, children, ...props }) => {
|
|
9700
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
9701
|
+
color: textColor,
|
|
9702
|
+
backgroundColor: bgColor,
|
|
9703
|
+
...props,
|
|
9704
|
+
});
|
|
9705
|
+
const bulmaClasses = usePrefixedClassNames();
|
|
9706
|
+
const emphasisClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
|
|
9707
|
+
return (jsxRuntime.jsx("em", { className: emphasisClasses || undefined, ...rest, children: children }));
|
|
9480
9708
|
};
|
|
9709
|
+
|
|
9481
9710
|
/**
|
|
9482
|
-
*
|
|
9483
|
-
* it appears, so one that goes and comes back is announced again. Its
|
|
9484
|
-
* announcement is written `announceDelay` after it appears, or after the last
|
|
9485
|
-
* item to join it when several appear in quick succession, alongside whatever
|
|
9486
|
-
* the region still says. Once written, it stays for `announcementLifetime`,
|
|
9487
|
-
* whatever is written after it and whether or not its item goes in the
|
|
9488
|
-
* meantime. An item that goes before its announcement is written isn't
|
|
9489
|
-
* announced.
|
|
9711
|
+
* FigureCaption component for rendering a styled figcaption element.
|
|
9490
9712
|
*
|
|
9491
|
-
*
|
|
9492
|
-
*
|
|
9493
|
-
* after the region is already in the page, and not always one that arrives
|
|
9494
|
-
* with its content. By the time `describe` runs, an item's rendered text can
|
|
9495
|
-
* be read.
|
|
9713
|
+
* A FigureCaption wraps the HTML `<figcaption>` element with Bulma helper class integration.
|
|
9714
|
+
* Use it inside Figure components to provide captions for images or other media.
|
|
9496
9715
|
*
|
|
9497
|
-
* @function
|
|
9498
|
-
* @param
|
|
9499
|
-
* @
|
|
9500
|
-
* It runs once the item is on screen, and it must keep its identity between
|
|
9501
|
-
* renders (a module-level function or a `useCallback`), or every render
|
|
9502
|
-
* re-checks the items.
|
|
9503
|
-
* @returns The announcements to render, in the order they were written.
|
|
9716
|
+
* @function
|
|
9717
|
+
* @param {FigureCaptionProps} props - Props for the FigureCaption component.
|
|
9718
|
+
* @returns {JSX.Element} The rendered figcaption element.
|
|
9504
9719
|
*/
|
|
9505
|
-
|
|
9506
|
-
|
|
9507
|
-
|
|
9508
|
-
|
|
9509
|
-
|
|
9510
|
-
|
|
9511
|
-
const
|
|
9512
|
-
const
|
|
9513
|
-
|
|
9514
|
-
|
|
9515
|
-
React.useEffect(() => {
|
|
9516
|
-
const announced = onScreenIdsRef.current;
|
|
9517
|
-
const appeared = items.filter(item => !announced.has(item.id));
|
|
9518
|
-
// Items that have gone drop out here too, so one that comes back has
|
|
9519
|
-
// appeared again.
|
|
9520
|
-
onScreenIdsRef.current = new Set(items.map(item => item.id));
|
|
9521
|
-
const next = [];
|
|
9522
|
-
for (const item of appeared) {
|
|
9523
|
-
const text = describe(item);
|
|
9524
|
-
if (text)
|
|
9525
|
-
next.push({ id: item.id, text });
|
|
9526
|
-
}
|
|
9527
|
-
if (next.length > 0) {
|
|
9528
|
-
// eslint-disable-next-line react-hooks/set-state-in-effect -- an item's rendered text can only be read once it's on screen
|
|
9529
|
-
setAnnouncements(current => ({
|
|
9530
|
-
...current,
|
|
9531
|
-
key: current.key + 1,
|
|
9532
|
-
// Announcements still waiting go out with the new ones, apart from
|
|
9533
|
-
// one whose item came back before it was written, which the new one
|
|
9534
|
-
// replaces. What's already written stays put.
|
|
9535
|
-
waiting: [
|
|
9536
|
-
...current.waiting.filter(waiting => !next.some(({ id }) => id === waiting.id)),
|
|
9537
|
-
...next,
|
|
9538
|
-
],
|
|
9539
|
-
}));
|
|
9540
|
-
}
|
|
9541
|
-
}, [items, describe]);
|
|
9542
|
-
// The write only touches the batch it was started for: a newer batch can
|
|
9543
|
-
// be set before the render that cancels this timer. Its clear is started
|
|
9544
|
-
// by the write itself, so it's timed from the write without waiting on a
|
|
9545
|
-
// render, and a newer batch arriving doesn't cut short what's written.
|
|
9546
|
-
const { key } = announcements;
|
|
9547
|
-
React.useEffect(() => {
|
|
9548
|
-
if (key === noAnnouncementsYet.key)
|
|
9549
|
-
return undefined;
|
|
9550
|
-
const clearTimers = clearTimersRef.current;
|
|
9551
|
-
const write = setTimeout(() => {
|
|
9552
|
-
// Whether an item is on screen is decided here, once. An item that
|
|
9553
|
-
// went before its announcement was due isn't announced, and one that
|
|
9554
|
-
// goes after keeps its announcement for the rest of its lifetime, when
|
|
9555
|
-
// the region holds the only copy of its text rather than a second one.
|
|
9556
|
-
const onScreen = onScreenIdsRef.current;
|
|
9557
|
-
setAnnouncements(current => current.key === key
|
|
9558
|
-
? {
|
|
9559
|
-
...current,
|
|
9560
|
-
waiting: noAnnouncements,
|
|
9561
|
-
written: [
|
|
9562
|
-
...current.written,
|
|
9563
|
-
...current.waiting
|
|
9564
|
-
.filter(announcement => onScreen.has(announcement.id))
|
|
9565
|
-
.map(announcement => ({ ...announcement, batch: key })),
|
|
9566
|
-
],
|
|
9567
|
-
}
|
|
9568
|
-
: current);
|
|
9569
|
-
// eslint-disable-next-line @eslint-react/web-api-no-leaked-timeout -- it has to outlive this effect when a newer batch arrives, so the unmount effect below clears it
|
|
9570
|
-
const clear = setTimeout(() => {
|
|
9571
|
-
clearTimers.delete(clear);
|
|
9572
|
-
setAnnouncements(current => {
|
|
9573
|
-
const written = current.written.filter(announcement => announcement.batch !== key);
|
|
9574
|
-
return written.length === current.written.length
|
|
9575
|
-
? current
|
|
9576
|
-
: { ...current, written };
|
|
9577
|
-
});
|
|
9578
|
-
}, announcementLifetime);
|
|
9579
|
-
clearTimers.add(clear);
|
|
9580
|
-
}, announceDelay);
|
|
9581
|
-
return () => clearTimeout(write);
|
|
9582
|
-
}, [key]);
|
|
9583
|
-
React.useEffect(() => {
|
|
9584
|
-
const clearTimers = clearTimersRef.current;
|
|
9585
|
-
return () => {
|
|
9586
|
-
clearTimers.forEach(clearTimeout);
|
|
9587
|
-
clearTimers.clear();
|
|
9588
|
-
};
|
|
9589
|
-
}, []);
|
|
9590
|
-
return announcements.written;
|
|
9591
|
-
}
|
|
9720
|
+
const FigureCaption = ({ className, textColor, bgColor, children, ...props }) => {
|
|
9721
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
9722
|
+
color: textColor,
|
|
9723
|
+
backgroundColor: bgColor,
|
|
9724
|
+
...props,
|
|
9725
|
+
});
|
|
9726
|
+
const bulmaClasses = usePrefixedClassNames();
|
|
9727
|
+
const captionClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
|
|
9728
|
+
return (jsxRuntime.jsx("figcaption", { className: captionClasses || undefined, ...rest, children: children }));
|
|
9729
|
+
};
|
|
9592
9730
|
/**
|
|
9593
|
-
*
|
|
9594
|
-
* an element's `aria-label`, or an image's `alt`, standing in for what's
|
|
9595
|
-
* inside it, a break between block-level elements, and parts that are
|
|
9596
|
-
* `aria-hidden`, `hidden` or inline-styled `display: none` left out. It isn't
|
|
9597
|
-
* the full accessible name computation, so `aria-labelledby`, CSS-generated
|
|
9598
|
-
* content, content hidden by a stylesheet and other ways of naming or hiding
|
|
9599
|
-
* content aren't followed.
|
|
9731
|
+
* The `Figure` component renders a styled `<figure>` element with Bulma helper class integration.
|
|
9600
9732
|
*
|
|
9601
|
-
* @function
|
|
9602
|
-
* @param
|
|
9603
|
-
* @returns
|
|
9733
|
+
* @function
|
|
9734
|
+
* @param {FigureProps} props - Props for the Figure component.
|
|
9735
|
+
* @returns {JSX.Element} The rendered figure element.
|
|
9604
9736
|
*/
|
|
9605
|
-
|
|
9606
|
-
|
|
9607
|
-
|
|
9608
|
-
|
|
9609
|
-
|
|
9610
|
-
|
|
9611
|
-
|
|
9612
|
-
const
|
|
9613
|
-
|
|
9614
|
-
|
|
9615
|
-
'ASIDE',
|
|
9616
|
-
'BLOCKQUOTE',
|
|
9617
|
-
'BR',
|
|
9618
|
-
'CAPTION',
|
|
9619
|
-
'DD',
|
|
9620
|
-
'DETAILS',
|
|
9621
|
-
'DIALOG',
|
|
9622
|
-
'DIV',
|
|
9623
|
-
'DL',
|
|
9624
|
-
'DT',
|
|
9625
|
-
'FIELDSET',
|
|
9626
|
-
'FIGCAPTION',
|
|
9627
|
-
'FIGURE',
|
|
9628
|
-
'FOOTER',
|
|
9629
|
-
'FORM',
|
|
9630
|
-
'H1',
|
|
9631
|
-
'H2',
|
|
9632
|
-
'H3',
|
|
9633
|
-
'H4',
|
|
9634
|
-
'H5',
|
|
9635
|
-
'H6',
|
|
9636
|
-
'HEADER',
|
|
9637
|
-
'HGROUP',
|
|
9638
|
-
'HR',
|
|
9639
|
-
'LEGEND',
|
|
9640
|
-
'LI',
|
|
9641
|
-
'MAIN',
|
|
9642
|
-
'MENU',
|
|
9643
|
-
'NAV',
|
|
9644
|
-
'OL',
|
|
9645
|
-
'P',
|
|
9646
|
-
'PRE',
|
|
9647
|
-
'SEARCH',
|
|
9648
|
-
'SECTION',
|
|
9649
|
-
'SUMMARY',
|
|
9650
|
-
'TABLE',
|
|
9651
|
-
'TBODY',
|
|
9652
|
-
'TD',
|
|
9653
|
-
'TFOOT',
|
|
9654
|
-
'TH',
|
|
9655
|
-
'THEAD',
|
|
9656
|
-
'TR',
|
|
9657
|
-
'UL',
|
|
9658
|
-
]);
|
|
9737
|
+
const FigureComponent = ({ className, textColor, bgColor, children, ...props }) => {
|
|
9738
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
9739
|
+
color: textColor,
|
|
9740
|
+
backgroundColor: bgColor,
|
|
9741
|
+
...props,
|
|
9742
|
+
});
|
|
9743
|
+
const bulmaClasses = usePrefixedClassNames();
|
|
9744
|
+
const figureClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
|
|
9745
|
+
return (jsxRuntime.jsx("figure", { className: figureClasses || undefined, ...rest, children: children }));
|
|
9746
|
+
};
|
|
9659
9747
|
/**
|
|
9660
|
-
*
|
|
9748
|
+
* Figure component with Caption subcomponent.
|
|
9661
9749
|
*
|
|
9662
|
-
* @
|
|
9663
|
-
*
|
|
9664
|
-
*
|
|
9665
|
-
*
|
|
9666
|
-
*
|
|
9750
|
+
* @example
|
|
9751
|
+
* <Figure>
|
|
9752
|
+
* <img src="image.jpg" alt="Description" />
|
|
9753
|
+
* <Figure.Caption>Image caption text</Figure.Caption>
|
|
9754
|
+
* </Figure>
|
|
9667
9755
|
*/
|
|
9668
|
-
const
|
|
9669
|
-
|
|
9670
|
-
|
|
9756
|
+
const Figure = withSubComponents(FigureComponent, {
|
|
9757
|
+
Caption: FigureCaption,
|
|
9758
|
+
}, 'Figure');
|
|
9759
|
+
|
|
9671
9760
|
/**
|
|
9672
|
-
*
|
|
9673
|
-
*
|
|
9761
|
+
* Distinguishes an `IconProps` object from a plain custom node (an inline SVG, a
|
|
9762
|
+
* `react-icons` component, …) in a slot that accepts either — `Control`'s
|
|
9763
|
+
* `iconLeft`/`iconRight`, `IconText`'s `iconProps`.
|
|
9674
9764
|
*
|
|
9675
|
-
*
|
|
9676
|
-
*
|
|
9677
|
-
*
|
|
9765
|
+
* One copy, imported by both consumers: two hand-maintained copies, one per
|
|
9766
|
+
* consumer, is how the deprecated `icon` path came to crash both (#663).
|
|
9767
|
+
*
|
|
9768
|
+
* Its own module rather than `Icon.tsx` because `src/index.ts` wildcard-exports
|
|
9769
|
+
* that file, and everything published there is public forever. `polymorphic.ts`
|
|
9770
|
+
* makes the same call for `isCustomElement`: an internal discriminator is not
|
|
9771
|
+
* something to support for the life of the package.
|
|
9772
|
+
*
|
|
9773
|
+
* It tests each member's VALUE, not merely its key, and only after everything
|
|
9774
|
+
* React owns is out of the way. `'icon' in value` was enough
|
|
9775
|
+
* to claim a Font Awesome `IconDefinition` — `{ prefix, iconName, icon: [w, h, …,
|
|
9776
|
+
* path] }` — whose `icon` is a path array rather than a class string. That object
|
|
9777
|
+
* is not a renderable node either, so the honest outcome is the one this slot has
|
|
9778
|
+
* always had for it: the node branch, and React's own "Objects are not valid as a
|
|
9779
|
+
* React child". Claiming it here instead rendered a blank container that still
|
|
9780
|
+
* reserved layout.
|
|
9781
|
+
*
|
|
9782
|
+
* A member added to `IconProps` needs a line here, and a missed one is a crash
|
|
9783
|
+
* rather than a fallthrough. That is the cost of a slot taking either a props
|
|
9784
|
+
* object or a node; keeping the guard in one place is what keeps the cost to a
|
|
9785
|
+
* single line.
|
|
9678
9786
|
*/
|
|
9679
|
-
function
|
|
9680
|
-
if (
|
|
9681
|
-
return
|
|
9682
|
-
|
|
9683
|
-
|
|
9684
|
-
|
|
9685
|
-
|
|
9686
|
-
|
|
9687
|
-
|
|
9688
|
-
|
|
9689
|
-
|
|
9690
|
-
|
|
9691
|
-
|
|
9692
|
-
|
|
9693
|
-
|
|
9694
|
-
return name || blockLevelTags.has(element.tagName) ? ` ${text} ` : text;
|
|
9787
|
+
function isIconProps(value) {
|
|
9788
|
+
if (typeof value !== 'object' || value === null)
|
|
9789
|
+
return false;
|
|
9790
|
+
// Anything React owns. `isValidElement` covers elements only; a PORTAL is a
|
|
9791
|
+
// valid `ReactNode` that fails it and carries a `children` key, so the member
|
|
9792
|
+
// test below claimed it and `Control` spread it into `<Icon>` as props —
|
|
9793
|
+
// rendering the portal's child locally instead of in its container. Every
|
|
9794
|
+
// React internal carries `$$typeof`, so testing for it covers lazy, context
|
|
9795
|
+
// and whatever React adds next, rather than portals alone.
|
|
9796
|
+
if (React.isValidElement(value) || '$$typeof' in value)
|
|
9797
|
+
return false;
|
|
9798
|
+
const candidate = value;
|
|
9799
|
+
return (typeof candidate.name === 'string' ||
|
|
9800
|
+
typeof candidate.icon === 'string' ||
|
|
9801
|
+
'children' in candidate);
|
|
9695
9802
|
}
|
|
9803
|
+
|
|
9696
9804
|
/**
|
|
9697
|
-
*
|
|
9698
|
-
* its items through. The container renders it from the moment it mounts,
|
|
9699
|
-
* whether or not it has anything to show, so the region is already in the
|
|
9700
|
-
* page when an announcement is written into it.
|
|
9805
|
+
* The `IconText` component provides a Bulma-styled horizontal arrangement of one or more `Icon` components and optional text.
|
|
9701
9806
|
*
|
|
9702
|
-
*
|
|
9703
|
-
*
|
|
9704
|
-
*
|
|
9705
|
-
*
|
|
9807
|
+
* @function
|
|
9808
|
+
* @param {IconTextProps} props - Props for the IconText component.
|
|
9809
|
+
* @returns {JSX.Element} The rendered icon text element.
|
|
9810
|
+
* @see {@link https://bulma.io/documentation/elements/icon/#icon-text | Bulma IconText documentation}
|
|
9811
|
+
*/
|
|
9812
|
+
const IconTextComponent = ({ className, textColor, color, bgColor, iconProps, children, items, ...props }) => {
|
|
9813
|
+
/**
|
|
9814
|
+
* Generates Bulma helper classes and separates out remaining props.
|
|
9815
|
+
*/
|
|
9816
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
9817
|
+
color: textColor ?? color,
|
|
9818
|
+
backgroundColor: bgColor,
|
|
9819
|
+
...props,
|
|
9820
|
+
});
|
|
9821
|
+
const bulmaClasses = usePrefixedClassNames('icon-text');
|
|
9822
|
+
const iconTextClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
|
|
9823
|
+
return (jsxRuntime.jsx("span", { className: iconTextClasses, ...rest, children: items ? (items.map((item, index) => (jsxRuntime.jsxs(React.Fragment, { children: [item.iconProps &&
|
|
9824
|
+
(isIconProps(item.iconProps) ? (jsxRuntime.jsx(Icon, { ...item.iconProps })) : (jsxRuntime.jsx(Icon, { children: item.iconProps }))), item.text && jsxRuntime.jsx("span", { children: item.text })] }, index)))) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [iconProps &&
|
|
9825
|
+
(isIconProps(iconProps) ? (jsxRuntime.jsx(Icon, { ...iconProps })) : (jsxRuntime.jsx(Icon, { children: iconProps }))), children && jsxRuntime.jsx("span", { children: children })] })) }));
|
|
9826
|
+
};
|
|
9827
|
+
const IconText = withSubComponents(IconTextComponent, { Icon }, 'IconText');
|
|
9828
|
+
|
|
9829
|
+
/**
|
|
9830
|
+
* The `Image` component wraps images, iframes, or custom content in a Bulma-styled container, supporting fixed sizes, aspect ratios, rounded corners, retina images, and all Bulma helper props for color and spacing.
|
|
9706
9831
|
*
|
|
9707
|
-
* @function
|
|
9708
|
-
* @param props -
|
|
9709
|
-
* @returns The
|
|
9832
|
+
* @function
|
|
9833
|
+
* @param {ImageProps} props - Props for the Image component.
|
|
9834
|
+
* @returns {JSX.Element} The rendered image element.
|
|
9835
|
+
* @see {@link https://bulma.io/documentation/elements/image/ | Bulma Image documentation}
|
|
9710
9836
|
*/
|
|
9711
|
-
|
|
9712
|
-
|
|
9713
|
-
|
|
9714
|
-
|
|
9715
|
-
|
|
9716
|
-
|
|
9717
|
-
|
|
9718
|
-
|
|
9719
|
-
|
|
9720
|
-
|
|
9721
|
-
}
|
|
9837
|
+
const Image = ({ as, className, textColor, color, bgColor, size, isRounded, isRetina, src, alt, children, ...props }) => {
|
|
9838
|
+
/**
|
|
9839
|
+
* Generates Bulma helper classes and separates out remaining props.
|
|
9840
|
+
*/
|
|
9841
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
9842
|
+
color: textColor ?? color,
|
|
9843
|
+
backgroundColor: bgColor,
|
|
9844
|
+
...props,
|
|
9845
|
+
});
|
|
9846
|
+
const bulmaClasses = usePrefixedClassNames('image', {
|
|
9847
|
+
[`is-${size}`]: size,
|
|
9848
|
+
'has-ratio': size && typeof size === 'string' && size.includes('by'),
|
|
9849
|
+
});
|
|
9850
|
+
const imageClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
|
|
9851
|
+
// Default tag logic: if "as" is provided, use it.
|
|
9852
|
+
// If not, use <figure> for aspect ratios or children, <div> otherwise.
|
|
9853
|
+
let Tag;
|
|
9854
|
+
if (as) {
|
|
9855
|
+
Tag = as;
|
|
9856
|
+
}
|
|
9857
|
+
else if (size && typeof size === 'string' && size.includes('by')) {
|
|
9858
|
+
Tag = 'figure';
|
|
9859
|
+
}
|
|
9860
|
+
else {
|
|
9861
|
+
Tag = 'div';
|
|
9862
|
+
}
|
|
9863
|
+
const roundedClass = usePrefixedClassNames('is-rounded');
|
|
9864
|
+
const content = children ? (children) : (jsxRuntime.jsx("img", { className: classNames({ [roundedClass]: isRounded }) || undefined, src: src, alt: alt, ...(isRetina && src ? { srcSet: `${src} 2x` } : {}) }));
|
|
9865
|
+
return (jsxRuntime.jsx(Tag, { className: imageClasses, ...rest, children: content }));
|
|
9866
|
+
};
|
|
9722
9867
|
|
|
9723
9868
|
/**
|
|
9724
9869
|
* Keys pressed together or one after another, in the outer `<kbd>` HTML gives a combination, with the separator between each pair as text.
|
|
@@ -11332,9 +11477,9 @@ function useAutoLabelId({ label, id, labelProps, rendersLabel, hasInput = true,
|
|
|
11332
11477
|
? (labelProps?.id ?? `${generatedId}-label`)
|
|
11333
11478
|
: undefined;
|
|
11334
11479
|
// Inactive with a label still means an own Field may render it (pickers'
|
|
11335
|
-
// inline mode
|
|
11336
|
-
//
|
|
11337
|
-
//
|
|
11480
|
+
// inline mode). The explicit `htmlFor: undefined` tells Field the
|
|
11481
|
+
// association is owned here, so it must not generate one that would
|
|
11482
|
+
// dangle (#495 presence semantics).
|
|
11338
11483
|
const fieldLabelProps = active
|
|
11339
11484
|
? ownLabelId
|
|
11340
11485
|
? { ...ownLabelProps, id: ownLabelId }
|
|
@@ -12781,7 +12926,7 @@ const keepFocus = (e) => e.preventDefault();
|
|
|
12781
12926
|
// An IME that's composing owns every key: Enter confirms what it composed,
|
|
12782
12927
|
// and the arrows move through its candidates. Safari can say so with
|
|
12783
12928
|
// `keyCode` 229 rather than `isComposing`.
|
|
12784
|
-
const isComposing = (e) => e.nativeEvent.isComposing || e.nativeEvent.keyCode === 229;
|
|
12929
|
+
const isComposing$1 = (e) => e.nativeEvent.isComposing || e.nativeEvent.keyCode === 229;
|
|
12785
12930
|
// Whether an option can be picked. The arrows still reach a disabled one, so
|
|
12786
12931
|
// a screen reader user finds it.
|
|
12787
12932
|
const isEnabled = (item) => typeof item === 'string' || !item.disabled;
|
|
@@ -12944,7 +13089,7 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
12944
13089
|
}, [keepFirst, isActive, filteredData.length, inputValue]);
|
|
12945
13090
|
// Handle keyboard navigation
|
|
12946
13091
|
const handleKeyDown = React.useCallback((e) => {
|
|
12947
|
-
if (disabled || isComposing(e))
|
|
13092
|
+
if (disabled || isComposing$1(e))
|
|
12948
13093
|
return;
|
|
12949
13094
|
switch (e.key) {
|
|
12950
13095
|
case 'ArrowDown':
|
|
@@ -13129,6 +13274,10 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
13129
13274
|
});
|
|
13130
13275
|
Autocomplete.displayName = 'Autocomplete';
|
|
13131
13276
|
|
|
13277
|
+
// An IME that's composing owns every key: Enter confirms what it composed,
|
|
13278
|
+
// the arrows move through its candidates, and Escape cancels it. Safari can
|
|
13279
|
+
// say so with `keyCode` 229 rather than `isComposing` (#1102).
|
|
13280
|
+
const isComposing = (e) => e.nativeEvent.isComposing || e.nativeEvent.keyCode === 229;
|
|
13132
13281
|
/**
|
|
13133
13282
|
* The `Taginput` component provides a tag/chip input field for managing multiple tags.
|
|
13134
13283
|
*
|
|
@@ -13177,7 +13326,9 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
13177
13326
|
const [inputValue, setInputValue] = React.useState('');
|
|
13178
13327
|
const [isActive, setIsActive] = React.useState(false);
|
|
13179
13328
|
const [highlightedIndex, setHighlightedIndex] = React.useState(-1);
|
|
13180
|
-
const
|
|
13329
|
+
const baseId = React.useId();
|
|
13330
|
+
const listboxId = `${baseId}-listbox`;
|
|
13331
|
+
const counterId = `${baseId}-counter`;
|
|
13181
13332
|
// With `data` the input offers suggestions, so it's a combobox, even
|
|
13182
13333
|
// while none match. Without it there's never a list, so it stays a plain
|
|
13183
13334
|
// text field.
|
|
@@ -13186,16 +13337,13 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
13186
13337
|
// Use controlled or internal tags
|
|
13187
13338
|
const tags = controlledValue !== undefined ? controlledValue : internalTags;
|
|
13188
13339
|
const isMaxReached = maxTags !== undefined && tags.length >= maxTags;
|
|
13189
|
-
//
|
|
13190
|
-
//
|
|
13191
|
-
// points `aria-labelledby` at the label that names the input, so with
|
|
13192
|
-
// suggestions it needs that label's id.
|
|
13340
|
+
// The suggestion list points `aria-labelledby` at the label that names
|
|
13341
|
+
// the input, so with suggestions it needs that label's id.
|
|
13193
13342
|
const { controlId, fieldLabelProps, labelled, labelId: listLabelId, } = useAutoLabelId({
|
|
13194
13343
|
label,
|
|
13195
13344
|
id,
|
|
13196
13345
|
labelProps,
|
|
13197
|
-
rendersLabel: ownField
|
|
13198
|
-
hasInput: !isMaxReached,
|
|
13346
|
+
rendersLabel: ownField,
|
|
13199
13347
|
needsLabelId: hasSuggestions,
|
|
13200
13348
|
});
|
|
13201
13349
|
// Get display value from tag
|
|
@@ -13216,9 +13364,8 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
13216
13364
|
const hasInput = inputValue || (openOnFocus && isActive);
|
|
13217
13365
|
return matchesInput && notAlreadyAdded && hasInput;
|
|
13218
13366
|
});
|
|
13219
|
-
// At the tag limit the
|
|
13220
|
-
//
|
|
13221
|
-
// the limit (#1087).
|
|
13367
|
+
// At the tag limit the list stays closed, since it could only offer tags
|
|
13368
|
+
// past the limit (#1087).
|
|
13222
13369
|
const listOpen = isActive && !isMaxReached && filteredData.length > 0;
|
|
13223
13370
|
// keepFirst: auto-highlight first item when dropdown opens
|
|
13224
13371
|
React.useEffect(() => {
|
|
@@ -13233,36 +13380,42 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
13233
13380
|
}
|
|
13234
13381
|
onChange?.(newTags);
|
|
13235
13382
|
}, [controlledValue, onChange]);
|
|
13236
|
-
// Add
|
|
13237
|
-
//
|
|
13238
|
-
|
|
13383
|
+
// Add tags from text typed, pasted or picked from the list. Each one is
|
|
13384
|
+
// checked against the tags before it, this batch's included, so a paste
|
|
13385
|
+
// of several adds them all in one change (#1101). Only `createTag` turns
|
|
13386
|
+
// text into an object.
|
|
13387
|
+
const addTags = React.useCallback((values) => {
|
|
13239
13388
|
if (disabled || readonly)
|
|
13240
13389
|
return;
|
|
13241
|
-
|
|
13242
|
-
|
|
13243
|
-
|
|
13244
|
-
|
|
13245
|
-
|
|
13246
|
-
|
|
13247
|
-
|
|
13248
|
-
const
|
|
13249
|
-
if (
|
|
13250
|
-
|
|
13390
|
+
let next = tags;
|
|
13391
|
+
const added = [];
|
|
13392
|
+
for (const value of values) {
|
|
13393
|
+
// A paste can bring more tags than there's room for, and at the
|
|
13394
|
+
// limit the read-only input still takes Enter.
|
|
13395
|
+
if (maxTags !== undefined && next.length >= maxTags)
|
|
13396
|
+
break;
|
|
13397
|
+
const displayValue = value.trim();
|
|
13398
|
+
if (!displayValue)
|
|
13399
|
+
continue;
|
|
13400
|
+
const lower = displayValue.toLowerCase();
|
|
13401
|
+
// Duplicates, data and the dropdown filter all compare
|
|
13402
|
+
// case-insensitively.
|
|
13403
|
+
if (!allowDuplicates &&
|
|
13404
|
+
next.some(tag => getDisplayValue(tag).toLowerCase() === lower))
|
|
13405
|
+
continue;
|
|
13406
|
+
// Without allowNew, only a suggestion can become a tag.
|
|
13407
|
+
if (!allowNew && !data.some(d => d.toLowerCase() === lower))
|
|
13408
|
+
continue;
|
|
13409
|
+
if (beforeAdding && !beforeAdding(displayValue))
|
|
13410
|
+
continue;
|
|
13411
|
+
const tag = createTag ? createTag(displayValue) : displayValue;
|
|
13412
|
+
next = [...next, tag];
|
|
13413
|
+
added.push(tag);
|
|
13251
13414
|
}
|
|
13252
|
-
|
|
13253
|
-
// case-insensitively, consistent with the dropdown filter and the
|
|
13254
|
-
// duplicate check above.
|
|
13255
|
-
if (!allowNew &&
|
|
13256
|
-
!data.some(d => d.toLowerCase() === displayValue.toLowerCase()))
|
|
13257
|
-
return;
|
|
13258
|
-
// beforeAdding validation
|
|
13259
|
-
if (beforeAdding && !beforeAdding(displayValue))
|
|
13415
|
+
if (added.length === 0)
|
|
13260
13416
|
return;
|
|
13261
|
-
|
|
13262
|
-
|
|
13263
|
-
const newTags = [...tags, finalTag];
|
|
13264
|
-
updateTags(newTags);
|
|
13265
|
-
onAdd?.(finalTag);
|
|
13417
|
+
updateTags(next);
|
|
13418
|
+
added.forEach(tag => onAdd?.(tag));
|
|
13266
13419
|
setInputValue('');
|
|
13267
13420
|
if (!keepOpen) {
|
|
13268
13421
|
setIsActive(false);
|
|
@@ -13272,6 +13425,7 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
13272
13425
|
disabled,
|
|
13273
13426
|
readonly,
|
|
13274
13427
|
tags,
|
|
13428
|
+
maxTags,
|
|
13275
13429
|
allowDuplicates,
|
|
13276
13430
|
allowNew,
|
|
13277
13431
|
data,
|
|
@@ -13300,7 +13454,7 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
13300
13454
|
if (confirmKeys.includes(lastChar) && lastChar !== 'Enter') {
|
|
13301
13455
|
const valueWithoutKey = newValue.slice(0, -1).trim();
|
|
13302
13456
|
if (valueWithoutKey) {
|
|
13303
|
-
|
|
13457
|
+
addTags([valueWithoutKey]);
|
|
13304
13458
|
}
|
|
13305
13459
|
return;
|
|
13306
13460
|
}
|
|
@@ -13309,7 +13463,7 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
13309
13463
|
if (newValue && data.length > 0) {
|
|
13310
13464
|
setIsActive(true);
|
|
13311
13465
|
}
|
|
13312
|
-
}, [confirmKeys,
|
|
13466
|
+
}, [confirmKeys, addTags, onTyping, data.length]);
|
|
13313
13467
|
// Handle paste
|
|
13314
13468
|
const handlePaste = React.useCallback((e) => {
|
|
13315
13469
|
if (onPasteSeparators.length === 0)
|
|
@@ -13320,17 +13474,12 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
13320
13474
|
if (!hasSeparator)
|
|
13321
13475
|
return;
|
|
13322
13476
|
e.preventDefault();
|
|
13323
|
-
// Build a
|
|
13324
|
-
|
|
13477
|
+
// Build a character class from all separators. A `-` is escaped
|
|
13478
|
+
// too, since between two others it would make a range.
|
|
13479
|
+
const escapedSeparators = onPasteSeparators.map(s => s.replace(/[-.*+?^${}()|[\]\\]/g, '\\$&'));
|
|
13325
13480
|
const regex = new RegExp(`[${escapedSeparators.join('')}]`);
|
|
13326
|
-
|
|
13327
|
-
|
|
13328
|
-
.map(s => s.trim())
|
|
13329
|
-
.filter(Boolean);
|
|
13330
|
-
for (const part of parts) {
|
|
13331
|
-
addTag(part);
|
|
13332
|
-
}
|
|
13333
|
-
}, [onPasteSeparators, addTag]);
|
|
13481
|
+
addTags(pastedText.split(regex));
|
|
13482
|
+
}, [onPasteSeparators, addTags]);
|
|
13334
13483
|
// Handle focus
|
|
13335
13484
|
const handleFocus = React.useCallback(() => {
|
|
13336
13485
|
if (openOnFocus && data.length > 0 && !disabled) {
|
|
@@ -13363,16 +13512,16 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
13363
13512
|
}, [isActive]);
|
|
13364
13513
|
// Handle keyboard
|
|
13365
13514
|
const handleKeyDown = React.useCallback((e) => {
|
|
13366
|
-
if (disabled || readonly)
|
|
13515
|
+
if (disabled || readonly || isComposing(e))
|
|
13367
13516
|
return;
|
|
13368
13517
|
// Handle confirm keys
|
|
13369
13518
|
if (confirmKeys.includes(e.key)) {
|
|
13370
13519
|
e.preventDefault();
|
|
13371
13520
|
if (highlightedIndex >= 0 && filteredData[highlightedIndex]) {
|
|
13372
|
-
|
|
13521
|
+
addTags([filteredData[highlightedIndex]]);
|
|
13373
13522
|
}
|
|
13374
13523
|
else if (inputValue.trim()) {
|
|
13375
|
-
|
|
13524
|
+
addTags([inputValue]);
|
|
13376
13525
|
}
|
|
13377
13526
|
return;
|
|
13378
13527
|
}
|
|
@@ -13399,7 +13548,7 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
13399
13548
|
case 'Escape':
|
|
13400
13549
|
// Closing the suggestions claims the key, so a Modal around the
|
|
13401
13550
|
// field stays open. With none showing, the key goes on to it.
|
|
13402
|
-
if (
|
|
13551
|
+
if (listOpen)
|
|
13403
13552
|
e.preventDefault();
|
|
13404
13553
|
setIsActive(false);
|
|
13405
13554
|
setHighlightedIndex(-1);
|
|
@@ -13411,9 +13560,10 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
13411
13560
|
confirmKeys,
|
|
13412
13561
|
highlightedIndex,
|
|
13413
13562
|
filteredData,
|
|
13414
|
-
|
|
13563
|
+
addTags,
|
|
13415
13564
|
inputValue,
|
|
13416
13565
|
isActive,
|
|
13566
|
+
listOpen,
|
|
13417
13567
|
data.length,
|
|
13418
13568
|
removeOnKeys,
|
|
13419
13569
|
tags.length,
|
|
@@ -13499,9 +13649,13 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
13499
13649
|
e.stopPropagation();
|
|
13500
13650
|
removeTag(index);
|
|
13501
13651
|
}, "aria-label": ariaCloseLabel || `Remove ${displayVal}` }))] }, index));
|
|
13502
|
-
}),
|
|
13652
|
+
}), jsxRuntime.jsx("input", { ref: combinedRef, type: "text", className: inputClasses, id: controlId, value: inputValue, placeholder: tags.length === 0 ? placeholder : undefined, disabled: disabled,
|
|
13653
|
+
// Read-only at the tag limit rather than gone, so focus stays
|
|
13654
|
+
// put and a label keeps naming it, and Backspace still makes
|
|
13655
|
+
// room (#1103).
|
|
13656
|
+
readOnly: readonly || isMaxReached, maxLength: maxlength, onChange: handleInputChange, onFocus: handleFocus, onKeyDown: handleKeyDown, onPaste: handlePaste, ...(hasSuggestions && comboboxProps), "aria-label": labelled ? undefined : 'Add tag', "aria-describedby": showCounter ? counterId : undefined })] }), loading && (jsxRuntime.jsx("span", { className: iconRightClass, children: jsxRuntime.jsx("span", { className: loaderClass }) }))] }), listOpen && (jsxRuntime.jsx("div", { className: dropdownMenuClasses, children: jsxRuntime.jsx("div", { ref: dropdownRef, id: listboxId, className: dropdownContentClasses, role: "listbox", "aria-labelledby": listLabelId, "aria-label": listLabelId ? undefined : 'Suggestions', children: filteredData.map((item, index) => (jsxRuntime.jsx("a", { id: optionId(index), className: pcn('dropdown-item', {
|
|
13503
13657
|
'is-active': index === highlightedIndex,
|
|
13504
|
-
}), onClick: () =>
|
|
13658
|
+
}), onClick: () => addTags([item]), onMouseEnter: () => setHighlightedIndex(index), role: "option", "aria-selected": optionId(index) === activeOptionId, children: item }, index))) }) })), showCounter && (jsxRuntime.jsx("small", { id: counterId, className: counterClasses, children: counterText })), name &&
|
|
13505
13659
|
tags.map((tag, i) => (jsxRuntime.jsx("input", { type: "hidden", name: name, value: getDisplayValue(tag), form: form }, `tag-input-${i}`)))] }));
|
|
13506
13660
|
if (ownField) {
|
|
13507
13661
|
return (jsxRuntime.jsxs(Field, { label: label, labelSize: labelSize, labelProps: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [taginputElement, messageEl] }));
|
|
@@ -19964,6 +20118,8 @@ exports.Thead = Thead;
|
|
|
19964
20118
|
exports.Theme = Theme;
|
|
19965
20119
|
exports.TimeInput = TimeInput;
|
|
19966
20120
|
exports.TimeInputBase = TimeInputBase;
|
|
20121
|
+
exports.Timeline = Timeline;
|
|
20122
|
+
exports.TimelineItem = TimelineItem;
|
|
19967
20123
|
exports.Title = Title;
|
|
19968
20124
|
exports.Toast = Toast;
|
|
19969
20125
|
exports.ToastContainer = ToastContainer;
|