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