@allxsmith/bestax-bulma 5.35.1 → 5.36.1

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