@allxsmith/bestax-bulma 5.33.1 → 5.34.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 (49) hide show
  1. package/dist/bestax.css +1 -1
  2. package/dist/bestax.css.map +1 -1
  3. package/dist/esm/components/EmptyState.js +139 -0
  4. package/dist/esm/components/EmptyState.js.map +1 -0
  5. package/dist/esm/elements/Buttons.js.map +1 -1
  6. package/dist/esm/elements/SubTitle.js.map +1 -1
  7. package/dist/esm/elements/Title.js.map +1 -1
  8. package/dist/esm/form/Field.js +3 -3
  9. package/dist/esm/form/Field.js.map +1 -1
  10. package/dist/esm/form/FormContext.js +2 -1
  11. package/dist/esm/form/FormContext.js.map +1 -1
  12. package/dist/esm/form/Taginput.js +36 -6
  13. package/dist/esm/form/Taginput.js.map +1 -1
  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 +1151 -989
  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/EmptyState.d.ts +149 -0
  22. package/dist/types/elements/Buttons.d.ts +1 -1
  23. package/dist/types/elements/SubTitle.d.ts +13 -2
  24. package/dist/types/elements/Title.d.ts +11 -2
  25. package/dist/types/form/Field.d.ts +5 -5
  26. package/dist/types/form/FormContext.d.ts +2 -1
  27. package/dist/types/form/Taginput.d.ts +8 -3
  28. package/dist/types/form/useAutoLabelId.d.ts +2 -2
  29. package/dist/types/index.d.ts +1 -0
  30. package/dist/types-cjs/components/EmptyState.d.ts +149 -0
  31. package/dist/types-cjs/elements/Buttons.d.ts +1 -1
  32. package/dist/types-cjs/elements/SubTitle.d.ts +13 -2
  33. package/dist/types-cjs/elements/Title.d.ts +11 -2
  34. package/dist/types-cjs/form/Field.d.ts +5 -5
  35. package/dist/types-cjs/form/FormContext.d.ts +2 -1
  36. package/dist/types-cjs/form/Taginput.d.ts +8 -3
  37. package/dist/types-cjs/form/useAutoLabelId.d.ts +2 -2
  38. package/dist/types-cjs/index.d.ts +1 -0
  39. package/dist/versions/bestax-no-dark-mode.css +1 -1
  40. package/dist/versions/bestax-no-dark-mode.css.map +1 -1
  41. package/dist/versions/bestax-no-helpers-prefixed.css +1 -1
  42. package/dist/versions/bestax-no-helpers-prefixed.css.map +1 -1
  43. package/dist/versions/bestax-no-helpers.css +1 -1
  44. package/dist/versions/bestax-no-helpers.css.map +1 -1
  45. package/dist/versions/bestax-prefixed.css +1 -1
  46. package/dist/versions/bestax-prefixed.css.map +1 -1
  47. package/package.json +1 -1
  48. package/src/scss/components/_emptystate.scss +243 -0
  49. package/src/scss/components/_index.scss +1 -0
package/dist/index.cjs CHANGED
@@ -2841,237 +2841,921 @@ const Dropdown = withSubComponents(DropdownComponent, {
2841
2841
  Divider: DropdownDivider,
2842
2842
  }, 'Dropdown');
2843
2843
 
2844
- // Context to track MenuList nesting level
2845
- const MenuListLevelContext = React.createContext(0);
2844
+ const validButtonColors = [...validColors, 'text', 'ghost'];
2846
2845
  /**
2847
- * The `Menu` component provides Bulma's vertical navigation menu: a simple, accessible sidebar or section menu for your Bulma React UI.
2846
+ * The `Button` component provides a flexible and highly customizable button for your Bulma React UI.
2848
2847
  *
2849
2848
  * @function
2850
- * @param {MenuProps} props - Props for the Menu component.
2851
- * @returns {JSX.Element} The rendered menu.
2852
- * @see {@link https://bulma.io/documentation/components/menu/ | Bulma Menu documentation}
2849
+ * @param {ButtonProps} props - Props for the Button component.
2850
+ * @param {React.Ref} ref - Forwarded ref to the element `as` renders.
2851
+ * @returns {JSX.Element} The rendered button, anchor, or custom element.
2852
+ * @see {@link https://bulma.io/documentation/elements/button/ | Bulma Button documentation}
2853
2853
  */
2854
- const MenuComponent = ({ className, children, ...props }) => {
2855
- const { bulmaHelperClasses, rest } = useBulmaClasses(props);
2854
+ const Button = React.forwardRef(function Button(props, ref) {
2855
+ const { color, size, isLight, isRounded, isLoading, isStatic, isFullwidth, isFullWidth, isOutlined, isInverted, isFocused, isActive, isHovered, isDisabled, className, children, textColor, bgColor, as: Component = 'button', href, onClick, target, rel, ...bulmaProps } = props;
2856
+ const { bulmaHelperClasses, rest } = useBulmaClasses({
2857
+ color: textColor,
2858
+ backgroundColor: bgColor,
2859
+ ...bulmaProps,
2860
+ });
2856
2861
  // Generate Bulma classes with prefix
2857
- const bulmaClasses = usePrefixedClassNames('menu');
2858
- return (jsxRuntime.jsx("aside", { className: classNames(bulmaClasses, bulmaHelperClasses, className), ...rest, children: children }));
2859
- };
2862
+ const bulmaClasses = usePrefixedClassNames('button', {
2863
+ [`is-${color}`]: color &&
2864
+ validButtonColors.includes(color),
2865
+ [`is-${size}`]: size,
2866
+ 'is-outlined': isOutlined,
2867
+ 'is-light': isLight,
2868
+ 'is-loading': isLoading,
2869
+ 'is-static': isStatic,
2870
+ 'is-disabled': isDisabled,
2871
+ 'is-rounded': isRounded,
2872
+ 'is-hovered': isHovered,
2873
+ 'is-focused': isFocused,
2874
+ 'is-active': isActive,
2875
+ 'is-inverted': isInverted,
2876
+ 'is-fullwidth': isFullwidth ?? isFullWidth,
2877
+ });
2878
+ // Combine prefixed Bulma classes with unprefixed user className and prefixed helper classes
2879
+ const buttonClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
2880
+ if (Component !== 'button') {
2881
+ // Strip the form-control attributes a link-like element cannot carry, so a
2882
+ // JavaScript consumer or a spread object doesn't put them on the DOM. The
2883
+ // types no longer allow them through; this is the runtime backstop.
2884
+ //
2885
+ // Only for INTRINSIC tags. A custom component (a router Link, ...) owns its
2886
+ // prop contract, and `ComponentPropsWithoutRef<T>` promises the caller that
2887
+ // its props reach it. Stripping names by their spelling alone broke that
2888
+ // promise silently: `<Button as={Custom} name="x" />` type-checked while
2889
+ // `Custom` never received the `name` it requires.
2890
+ //
2891
+ // `autoFocus` and `type` are deliberately NOT stripped: both are valid on
2892
+ // targets this list was filtering them from. `autoFocus` is a global
2893
+ // attribute (React decides per element whether to honour it, which is not
2894
+ // ours to pre-empt), and `type` on an anchor is the MIME hint that
2895
+ // `ButtonProps<'a'>` now types it as.
2896
+ //
2897
+ // The types already answer this for a TypeScript caller: `ButtonProps<T>`
2898
+ // derives from `ComponentPropsWithoutRef<T>`, so only what the target
2899
+ // accepts can be passed. What is left is a runtime backstop for JavaScript
2900
+ // consumers and spread objects.
2901
+ //
2902
+ // Keep it to the two cases where a stray attribute does something. The
2903
+ // submit-overrides belong to a submit control and never make sense on the
2904
+ // anchor path. `disabled` is the one that is VISIBLE: Bulma styles
2905
+ // `.button[disabled]` (background, border, shadow, opacity), so letting it
2906
+ // through to a `<span>` greys the element out — main stripped it, and not
2907
+ // stripping it would be a silent visual change on a types-only release.
2908
+ //
2909
+ // Everything else (`name`, `value`, `form`) is inert where it does not
2910
+ // belong, and enumerating which tags own it is what kept going wrong:
2911
+ // first all nine names on every non-button tag (so `<Button as="input"
2912
+ // name="query" value="Search">` submitted nothing), then a form-control
2913
+ // allowlist that still lost `formAction` on an input and `value` on an
2914
+ // option. Those now ride on the types instead.
2915
+ //
2916
+ // A custom component is exempt entirely — it owns its prop contract, and
2917
+ // `ComponentPropsWithoutRef<T>` promises the caller its props arrive.
2918
+ //
2919
+ // The elements that own `disabled`, per the HTML spec. A closed set, unlike
2920
+ // the open-ended question of which tag owns which attribute.
2921
+ const SUBMIT_OVERRIDES = [
2922
+ 'formAction',
2923
+ 'formEncType',
2924
+ 'formMethod',
2925
+ 'formNoValidate',
2926
+ 'formTarget',
2927
+ ];
2928
+ const OWNS_DISABLED = [
2929
+ 'button',
2930
+ 'fieldset',
2931
+ 'input',
2932
+ 'optgroup',
2933
+ 'option',
2934
+ 'select',
2935
+ 'textarea',
2936
+ ];
2937
+ // Two independent filters. They are not exclusive — an `<a>` owns neither
2938
+ // the submit-overrides nor `disabled`, so it takes both — and deriving them
2939
+ // from one destructure, or from one if/else, is what broke each of them in
2940
+ // turn: first the overrides came off every intrinsic (an `as="input"` lost
2941
+ // `formAction`), then scoping them to the anchor let `disabled` back onto
2942
+ // it.
2943
+ const forwardedRest = { ...rest };
2944
+ if (typeof Component === 'string' && !isCustomElement(Component)) {
2945
+ // Meaningless anywhere but a submit control, and an anchor is not one.
2946
+ if (Component === 'a') {
2947
+ for (const key of SUBMIT_OVERRIDES)
2948
+ delete forwardedRest[key];
2949
+ }
2950
+ // Visible via Bulma's `.button[disabled]`, so withheld from any element
2951
+ // that does not own it.
2952
+ if (!OWNS_DISABLED.includes(Component))
2953
+ delete forwardedRest.disabled;
2954
+ }
2955
+ return (jsxRuntime.jsx(Component, { ref: ref, className: buttonClasses, href: href, target: target, rel: rel, "aria-disabled": isDisabled, tabIndex: isDisabled ? -1 : undefined, onClick: isDisabled
2956
+ ? // Guarded, because this handler is installed into whatever `as`
2957
+ // renders and a custom component's `onClick` need not be a DOM
2958
+ // event handler at all. One declaring `onClick: (v: string) =>
2959
+ // void` calls this with a string, and an unguarded
2960
+ // `e.preventDefault()` throws `is not a function`. Blocking the
2961
+ // real event is the point; a foreign payload is simply not ours
2962
+ // to act on.
2963
+ (e) => {
2964
+ if (typeof e?.preventDefault === 'function')
2965
+ e.preventDefault();
2966
+ }
2967
+ : onClick, ...forwardedRest, children: children }));
2968
+ }
2969
+ return (jsxRuntime.jsx("button", { ref: ref, className: buttonClasses, disabled: isDisabled, onClick: onClick, ...rest, children: children }));
2970
+ });
2971
+ Button.displayName = 'Button';
2972
+
2860
2973
  /**
2861
- * Bulma Menu label component.
2974
+ * The `LinkButton` component renders a `<button>` that visually looks like text or a link.
2862
2975
  *
2863
2976
  * @function
2864
- * @param {MenuLabelProps} props - Props for the MenuLabel component.
2865
- * @returns {JSX.Element} The rendered menu label.
2977
+ * @param {LinkButtonProps} props - Props for the LinkButton component.
2978
+ * @param {React.Ref} ref - Forwarded ref to the element `as` renders.
2979
+ * @returns {JSX.Element} The rendered link-styled button element.
2980
+ *
2981
+ * @example
2982
+ * // Text variant (default)
2983
+ * <LinkButton onClick={handleClick}>Click me</LinkButton>
2984
+ *
2985
+ * @example
2986
+ * // Underline variant with color
2987
+ * <LinkButton variant="underline" color="primary">Learn more</LinkButton>
2866
2988
  */
2867
- const MenuLabel = ({ className, children, ...props }) => {
2868
- const { bulmaHelperClasses, rest } = useBulmaClasses(props);
2869
- return (jsxRuntime.jsx("p", { className: classNames(usePrefixedClassNames('menu-label'), className, bulmaHelperClasses), ...rest, children: children }));
2870
- };
2989
+ const LinkButton = React.forwardRef(function LinkButton({ variant = 'text', color, className, ...props }, ref) {
2990
+ const buttonColor = variant === 'underline' ? 'text' : variant;
2991
+ const prefixedClasses = usePrefixedClassNames('link-button', color && `link-button-${color}`, variant === 'underline' && 'link-button-underline');
2992
+ return (jsxRuntime.jsx(Button
2993
+ // LinkButton is polymorphic through to Button, which resolves the element
2994
+ // from `as`. TS cannot infer that through the spread below.
2995
+ , {
2996
+ // LinkButton is polymorphic through to Button, which resolves the element
2997
+ // from `as`. TS cannot infer that through the spread below.
2998
+ ref: ref, color: buttonColor, className: classNames(prefixedClasses, className), ...props }));
2999
+ });
3000
+ LinkButton.displayName = 'LinkButton';
3001
+
2871
3002
  /**
2872
- * MenuList applies `menu-list` class only at the top level (not for nested lists).
3003
+ * The `ToggleButton` component renders a button that stays pressed until it's pressed again, and says whether it's on through `aria-pressed`.
3004
+ *
3005
+ * It's always a `<button type="button">`, so it never submits a form and
3006
+ * carries no form value: a setting saved with a form is a `Checkbox` or a
3007
+ * `Switch`. A set of toggles that each turn on and off on their own goes in
3008
+ * `Buttons hasAddons` with `role="group"` and an `aria-label`. A set where only
3009
+ * one can be on is a radio group rather than a row of these: use
3010
+ * `SegmentedControl`, or `Radios` for a choice that's sent with a form.
2873
3011
  *
2874
3012
  * @function
2875
- * @param {MenuListProps} props - Props for the MenuList component.
2876
- * @returns {JSX.Element} The rendered menu list.
2877
- */
2878
- const MenuList = ({ className, children, ...props }) => {
2879
- const level = React.useContext(MenuListLevelContext);
2880
- const { bulmaHelperClasses, rest } = useBulmaClasses(props);
2881
- const ulClass = classNames(className, bulmaHelperClasses, {
2882
- [usePrefixedClassNames('menu-list')]: level === 0,
2883
- });
2884
- // Increment level for nested MenuLists
2885
- return (jsxRuntime.jsx(MenuListLevelContext.Provider, { value: level + 1, children: jsxRuntime.jsx("ul", { className: ulClass || undefined, ...rest, children: children }) }));
2886
- };
2887
- /**
2888
- * `href` alone — annotated so a typo is a compile error rather than a silent
2889
- * no-op, the way both siblings' sets are.
3013
+ * @param {ToggleButtonProps} props - Props for the ToggleButton component.
3014
+ * @param {React.Ref<HTMLButtonElement>} ref - Forwarded ref to the `<button>`.
3015
+ * @returns {JSX.Element} The rendered toggle button.
3016
+ * @see {@link https://www.w3.org/WAI/ARIA/apg/patterns/button/ | APG button pattern}
3017
+ *
3018
+ * @example
3019
+ * <ToggleButton onPressedChange={setMuted}>Mute</ToggleButton>
3020
+ *
3021
+ * @example
3022
+ * <Buttons hasAddons role="group" aria-label="Text style">
3023
+ * <ToggleButton aria-label="Bold" pressed={bold} onPressedChange={setBold}>
3024
+ * <Icon name="bold" />
3025
+ * </ToggleButton>
3026
+ * </Buttons>
2890
3027
  */
2891
- const STRIP_FROM_NON_LINK = { href: true };
3028
+ const ToggleButton = React.forwardRef(function ToggleButton({ pressed: controlledPressed, defaultPressed = false, onPressedChange, color = 'link', isLoading, 'aria-busy': ariaBusy, 'aria-disabled': ariaDisabled, className, onClick, ...props }, ref) {
3029
+ const isControlled = controlledPressed !== undefined;
3030
+ const [internalPressed, setInternalPressed] = React.useState(defaultPressed);
3031
+ const pressed = controlledPressed ?? internalPressed;
3032
+ // Prefixed like Button's own classes, which a hand-written
3033
+ // `className="is-selected"` could not be under ConfigProvider classPrefix.
3034
+ // Bulma lifts `is-selected` above its neighbours in `.buttons.has-addons`.
3035
+ const selectedClass = usePrefixedClassNames({ 'is-selected': pressed });
3036
+ // The caller's onClick runs first, so a `preventDefault()` there vetoes
3037
+ // the toggle, as it does in Radix, MUI and Zag. Enter and Space reach here
3038
+ // through the button's own click, and a disabled button gets no click.
3039
+ //
3040
+ // A loading one gets the click but drops it. Bulma's spinner only stops a
3041
+ // pointer, so without this a key flipped a toggle the mouse couldn't.
3042
+ // `disabled` would stop both, but it takes focus away mid-save.
3043
+ const handleClick = (event) => {
3044
+ if (isLoading)
3045
+ return;
3046
+ onClick?.(event);
3047
+ if (event.defaultPrevented)
3048
+ return;
3049
+ const next = !pressed;
3050
+ onPressedChange?.(next);
3051
+ if (!isControlled)
3052
+ setInternalPressed(next);
3053
+ };
3054
+ // The props are spread first, so what a toggle depends on wins over
3055
+ // anything a JavaScript caller or a spread object passes along: it stays
3056
+ // a button, never submits, reports the state it's in, and says it's busy
3057
+ // and won't respond while it's loading. A caller's own `aria-busy` or
3058
+ // `aria-disabled` stays when it isn't loading.
3059
+ return (jsxRuntime.jsx(Button, { ...props, ref: ref, as: "button", type: "button", isLoading: isLoading, "aria-busy": isLoading || ariaBusy, "aria-disabled": isLoading || ariaDisabled, "aria-pressed": pressed, color: pressed ? color : undefined, className: classNames(selectedClass, className), onClick: handleClick }));
3060
+ });
3061
+ ToggleButton.displayName = 'ToggleButton';
3062
+
3063
+ const validButtonsSizes = ['small', 'medium', 'large'];
2892
3064
  /**
2893
- * MenuItem supports `as` prop for custom link components, e.g., react-router-dom Link.
3065
+ * The `Buttons` component lets you group multiple `Button` elements together with Bulma's spacing, alignment, and add-on features.
2894
3066
  *
2895
3067
  * @function
2896
- * @param {MenuItemProps} props - Props for the MenuItem component.
2897
- * @param {React.Ref} ref - Forwarded ref to the inner element `as` renders.
2898
- * @returns {JSX.Element} The rendered menu item.
3068
+ * @param {ButtonsProps} props - Props for the Buttons component.
3069
+ * @returns {JSX.Element} The rendered group of buttons.
3070
+ * @see {@link https://bulma.io/documentation/elements/button/#group | Bulma Button Group documentation}
2899
3071
  */
2900
- const MenuItem = React.forwardRef(function MenuItem(itemProps, ref) {
2901
- const { className, children, active, as: Component = 'a', 'data-testid': testId, style, id, title, role, tabIndex, ...rest } = itemProps;
2902
- const { bulmaHelperClasses, rest: forwarded } = useBulmaClasses(rest);
2903
- // `href` reaches an anchor or a custom component (which owns its own prop
2904
- // contract), but not another intrinsic tag — `<span href>` is invalid HTML.
2905
- // The same rule Avatar applies through `isLinkLike` and Button through its
2906
- // intrinsic-only strip. Before #641 `href` was an own prop re-applied only
2907
- // for `as="a"`; deriving it from `as` must not quietly widen that.
2908
- const isLinkLike = Component === 'a' ||
2909
- typeof Component !== 'string' ||
2910
- isCustomElement(Component);
2911
- // `href` ALONE. `Dropdown.Item` strips every anchor-only attribute except
2912
- // `type`; `Level.Item` strips all of them, plus `rel`.
2913
- //
2914
- // Not because nothing else can arrive: `as` here is an open `React.ElementType`,
2915
- // and a non-fresh spread skips excess-property checking, so a typed caller
2916
- // CAN deliver `target` or `download` and they are forwarded. The set stays at
2917
- // `href` because widening it would be wrong for an open `as` — `referrerPolicy`
2918
- // on `as="img"`, `target` on `as="form"` and `media` on `as="source"` are all
2919
- // legal, and a flat strip would delete them. Getting that right needs a
2920
- // per-attribute-per-element rule, which `bestax-migrate` already models in
2921
- // `LINK_ATTR_ELEMENTS`.
2922
- const linkProps = isLinkLike
2923
- ? forwarded
2924
- : omitAttrs(forwarded, STRIP_FROM_NON_LINK);
2925
- const itemClass = classNames({ [usePrefixedClassNames('is-active')]: active }, bulmaHelperClasses);
2926
- // Split children into label and nested MenuList(s)
2927
- const labelChildren = [];
2928
- const nestedMenuLists = [];
2929
- React.Children.forEach(children, child => {
2930
- if (React.isValidElement(child) && child.type === MenuList) {
2931
- nestedMenuLists.push(child);
2932
- }
2933
- else {
2934
- labelChildren.push(child);
2935
- }
3072
+ const ButtonsComponent = ({ className, textColor, color, bgColor, isCentered, isRight, hasAddons, size, children, ...props }) => {
3073
+ const buttonsClasses = usePrefixedClassNames('buttons', {
3074
+ 'is-centered': isCentered,
3075
+ 'is-right': isRight,
3076
+ 'has-addons': hasAddons,
3077
+ [`are-${size}`]: size && validButtonsSizes.includes(size),
2936
3078
  });
2937
- return (jsxRuntime.jsxs("li", { className: className, "data-testid": testId, style: style, id: id, title: title, role: role, tabIndex: tabIndex, children: [jsxRuntime.jsx(Component, { ref: ref, className: itemClass || undefined, ...linkProps, ...(Component === 'button'
2938
- ? { type: buttonType(linkProps.type) }
2939
- : {}), children: labelChildren }), nestedMenuLists] }));
2940
- });
2941
- MenuItem.displayName = 'MenuItem';
2942
- // Attach static subcomponents
2943
- const Menu = withSubComponents(MenuComponent, {
2944
- Label: MenuLabel,
2945
- List: MenuList,
2946
- Item: MenuItem,
2947
- }, 'Menu');
2948
-
2949
- const validMessageSizes = ['small', 'medium', 'large'];
2950
- /**
2951
- * The `Message` component provides Bulma's flexible notice/message box for your Bulma React UI.
2952
- *
2953
- * @function
2954
- * @param {MessageProps} props - Props for the Message component.
2955
- * @returns {JSX.Element} The rendered message.
2956
- * @see {@link https://bulma.io/documentation/components/message/ | Bulma Message documentation}
2957
- */
2958
- const MessageComponent = ({ className, title, textColor, color, bgColor, size, onClose, children, ...props }) => {
2959
- const { classPrefix } = useConfig();
3079
+ /**
3080
+ * Generates Bulma helper classes and separates out remaining props.
3081
+ */
2960
3082
  const { bulmaHelperClasses, rest } = useBulmaClasses({
2961
- color: textColor,
3083
+ color: textColor ?? color,
2962
3084
  backgroundColor: bgColor,
2963
3085
  ...props,
2964
3086
  });
2965
- // Generate Bulma classes with prefix
2966
- const bulmaClasses = usePrefixedClassNames('message', {
2967
- [`is-${color}`]: color,
2968
- [`is-${size}`]: size && validMessageSizes.includes(size),
2969
- });
2970
- const deleteClass = usePrefixedClassNames('delete');
2971
- const messageClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
2972
- return (jsxRuntime.jsxs("article", { className: messageClasses, ...rest, "data-testid": "message", children: [(title || onClose) && (jsxRuntime.jsxs("div", { className: prefixedClassNames(classPrefix, 'message-header'), children: [title && jsxRuntime.jsx("span", { children: title }), onClose && (jsxRuntime.jsx("button", { className: deleteClass, "aria-label": "delete", onClick: onClose, type: "button", "data-testid": "message-close" }))] })), children && (jsxRuntime.jsx("div", { className: prefixedClassNames(classPrefix, 'message-body'), "data-testid": "message-body", children: children }))] }));
3087
+ const combinedClasses = classNames(buttonsClasses, className, bulmaHelperClasses);
3088
+ return (jsxRuntime.jsx("div", { className: combinedClasses, ...rest, children: children }));
2973
3089
  };
2974
- /**
2975
- * Message header compound component. Renders a `.message-header` element.
2976
- *
2977
- * @function
2978
- * @param {MessageHeaderProps} props - Props for the MessageHeader component.
2979
- * @returns {JSX.Element} The rendered message header.
2980
- */
2981
- const MessageHeader = ({ className, children, ...props }) => (jsxRuntime.jsx("div", { className: classNames(usePrefixedClassNames('message-header'), className), ...props, children: children }));
2982
- /**
2983
- * Message body compound component. Renders a `.message-body` element.
2984
- *
2985
- * @function
2986
- * @param {MessageBodyProps} props - Props for the MessageBody component.
2987
- * @returns {JSX.Element} The rendered message body.
2988
- */
2989
- const MessageBody = ({ className, children, ...props }) => (jsxRuntime.jsx("div", { className: classNames(usePrefixedClassNames('message-body'), className), ...props, children: children }));
2990
- // Attach compound components
2991
- const Message = withSubComponents(MessageComponent, {
2992
- Header: MessageHeader,
2993
- Body: MessageBody,
2994
- }, 'Message');
3090
+ const Buttons = withSubComponents(ButtonsComponent, {
3091
+ Button,
3092
+ LinkButton,
3093
+ ToggleButton,
3094
+ }, 'Buttons');
2995
3095
 
2996
- // Ref-counted across every caller (e.g. a Dialog rendering its own Modal)
2997
- // so overlapping/nested locks don't fight over which one restores `overflow`.
2998
- // Every overlay that locks body scroll — Modal, Dialog, Sidebar, Loading —
2999
- // goes through here; a component setting `document.body.style.overflow`
3000
- // itself would clear a lock another overlay still needs.
3001
- let lockCount = 0;
3002
- let originalOverflow = '';
3003
3096
  /**
3004
- * Locks (and ref-counted-ly unlocks) `document.body` scrolling while `active`
3005
- * is true. Safe to call from multiple components at once — the body scroll
3006
- * is only restored once every active caller has released its lock.
3097
+ * Strips a redundant leading library prefix from an icon name (e.g. `fa-check` -> `check`
3098
+ * for the `fa` library, `mdi-account` -> `account` for `mdi`), so `name` behaves the same
3099
+ * whether or not the caller includes the prefix. Font Awesome and Material Design Icons docs
3100
+ * conventionally refer to icons with their class prefix (`fa-check`), which otherwise gets
3101
+ * doubled by `getIconClasses` (producing `fa-fa-check`, matching no glyph).
3007
3102
  *
3008
- * @function useScrollLock
3009
- * @param active - Whether this caller wants the body scroll locked.
3103
+ * @param {string} name - The icon name, possibly including a redundant library prefix.
3104
+ * @param {IconLibrary} library - The icon library the name will be rendered with.
3105
+ * @returns {string} The icon name with any redundant leading library prefix removed.
3010
3106
  */
3011
- function useScrollLock(active) {
3012
- React.useEffect(() => {
3013
- if (!active)
3014
- return undefined;
3015
- lockCount++;
3016
- if (lockCount === 1) {
3017
- originalOverflow = document.body.style.overflow;
3018
- document.body.style.overflow = 'hidden';
3019
- }
3020
- return () => {
3021
- lockCount--;
3022
- if (lockCount === 0) {
3023
- document.body.style.overflow = originalOverflow;
3024
- }
3025
- };
3026
- }, [active]);
3107
+ function stripRedundantLibraryPrefix(name, library) {
3108
+ if (!name) {
3109
+ return name;
3110
+ }
3111
+ if (library === 'fa' && name.startsWith('fa-')) {
3112
+ return name.substring(3);
3113
+ }
3114
+ if (library === 'mdi' && name.startsWith('mdi-')) {
3115
+ return name.substring(4);
3116
+ }
3117
+ return name;
3027
3118
  }
3028
-
3029
- // "Are we past hydration?" as a store that never changes: React reads the
3030
- // server snapshot both while server-rendering and while hydrating, and the
3031
- // client snapshot from the first post-hydration render onward.
3032
- const subscribeToNothing = () => () => { };
3033
- const getClientSnapshot = () => true;
3034
- const getServerSnapshot = () => false;
3035
3119
  /**
3036
- * Reports whether the component has passed hydration.
3037
- *
3038
- * Returns `false` during server rendering and during the hydrating client
3039
- * render, then `true` from the commit that follows. Use it to defer
3040
- * DOM-only behaviour (such as portalling) past hydration so the first client
3041
- * render matches the server markup instead of tripping hydration recovery.
3042
- *
3043
- * Only a component that hydrates ever sees `false` on the client. One that
3044
- * mounts later (after a route change, or in an app with no server rendering)
3045
- * gets `true` on its first render, so it pays for no extra render. It
3046
- * subscribes to nothing, so no later render flips it back.
3120
+ * Gets the correct classes for the icon element based on the library and features.
3047
3121
  *
3048
- * @function useIsHydrated
3049
- * @returns `true` once the hydrating render has completed, otherwise `false`.
3122
+ * @param {IconLibrary} library - The icon library.
3123
+ * @param {string} name - The icon name.
3124
+ * @param {string} [variant] - Icon style variant (e.g., 'solid', 'outlined', 'rounded').
3125
+ * @param {string | string[]} [features] - Additional library-specific modifiers.
3126
+ * @returns {string} The combined class string for the icon.
3050
3127
  */
3051
- function useIsHydrated() {
3052
- return React.useSyncExternalStore(subscribeToNothing, getClientSnapshot, getServerSnapshot);
3128
+ function getIconClasses(library, name, variant, features) {
3129
+ let baseClass;
3130
+ let iconClass;
3131
+ const featureList = Array.isArray(features)
3132
+ ? features
3133
+ : features
3134
+ ? [features]
3135
+ : [];
3136
+ switch (library) {
3137
+ case 'fa': {
3138
+ // Font Awesome: use variant as style ('solid' -> 'fas', 'regular' -> 'far', etc.)
3139
+ const styleMap = {
3140
+ solid: 'fas',
3141
+ regular: 'far',
3142
+ brands: 'fab',
3143
+ light: 'fal',
3144
+ duotone: 'fad',
3145
+ thin: 'fat',
3146
+ };
3147
+ const faStyle = variant ? styleMap[variant] || variant : 'fas';
3148
+ baseClass = faStyle;
3149
+ iconClass = `fa-${name}`;
3150
+ return [baseClass, iconClass, ...featureList].join(' ');
3151
+ }
3152
+ case 'mdi':
3153
+ // Material Design Icons: no variants, just features
3154
+ baseClass = 'mdi';
3155
+ iconClass = `mdi-${name}`;
3156
+ return [baseClass, iconClass, ...featureList].join(' ');
3157
+ case 'material-icons': {
3158
+ // Google Material Icons: map variants to full class names
3159
+ const styleVariants = {
3160
+ filled: 'material-icons',
3161
+ outlined: 'material-icons-outlined',
3162
+ round: 'material-icons-round',
3163
+ sharp: 'material-icons-sharp',
3164
+ };
3165
+ baseClass = variant
3166
+ ? styleVariants[variant] || `material-icons-${variant}`
3167
+ : 'material-icons';
3168
+ return [baseClass, ...featureList].join(' ');
3169
+ }
3170
+ case 'material-symbols': {
3171
+ // Google Material Symbols: map variants to full class names
3172
+ const styleVariants = {
3173
+ outlined: 'material-symbols-outlined',
3174
+ rounded: 'material-symbols-rounded',
3175
+ sharp: 'material-symbols-sharp',
3176
+ };
3177
+ baseClass = variant
3178
+ ? styleVariants[variant] || `material-symbols-${variant}`
3179
+ : 'material-symbols-outlined';
3180
+ return [baseClass, ...featureList].join(' ');
3181
+ }
3182
+ default:
3183
+ // fallback: just icon name and features
3184
+ return [name, ...featureList].join(' ');
3185
+ }
3053
3186
  }
3054
-
3055
3187
  /**
3056
- * Resolves a portal target: an `HTMLElement` is used directly, a non-empty
3057
- * `string` is treated as a `document.querySelector` selector (falling back to
3058
- * `document.body` when it matches nothing), and any falsy value — `undefined`
3059
- * or the empty string — resolves to `document.body`.
3060
- *
3061
- * The empty string is deliberately treated as "no target" rather than passed
3062
- * through: `document.querySelector('')` throws a `SyntaxError`, and callers
3063
- * building a selector from state can easily hand us `''`.
3188
+ * The `Icon` component is a Bulma-styled wrapper for displaying icons from various libraries (Font Awesome, Material Design Icons, Ionicons, Google Material Icons, Material Symbols, etc.).
3064
3189
  *
3065
- * @function resolvePortalContainer
3066
- * @param container - The requested portal target, if any.
3067
- * @returns The resolved DOM node to portal into.
3190
+ * @function
3191
+ * @param {IconProps} props - Props for the Icon component.
3192
+ * @returns {JSX.Element} The rendered icon element.
3193
+ * @see {@link https://bulma.io/documentation/elements/icon/ | Bulma Icon documentation}
3068
3194
  */
3069
- function resolvePortalContainer(container) {
3070
- if (!container) {
3071
- return document.body;
3072
- }
3073
- if (typeof container === 'string') {
3074
- return (document.querySelector(container) ?? document.body);
3195
+ const Icon = ({ className, textColor, bgColor, name, library, variant, features, libraryFeatures, // Deprecated but maintained for backward compatibility
3196
+ size, ariaLabel, style, icon, // Capture and exclude the deprecated 'icon' prop from DOM
3197
+ color: _color, // Exclude 'color' prop if passed directly
3198
+ containerClassName, children, ...restProps }) => {
3199
+ // Get the default icon library from context, fallback to 'fa' if not set
3200
+ const defaultLibrary = useIconLibrary();
3201
+ /**
3202
+ * Generates Bulma helper classes and separates out remaining props.
3203
+ * Note: variant, features, and libraryFeatures are excluded from props spread
3204
+ */
3205
+ const { bulmaHelperClasses, rest } = useBulmaClasses({
3206
+ color: textColor,
3207
+ backgroundColor: bgColor,
3208
+ ...restProps,
3209
+ });
3210
+ // Hoisted unconditionally to respect rules-of-hooks; the branches below
3211
+ // pick which result to consume.
3212
+ const defaultIconClasses = usePrefixedClassNames('icon', {
3213
+ [`is-${size}`]: size,
3214
+ });
3215
+ const sizeModifierClass = usePrefixedClassNames(size ? `is-${size}` : undefined);
3216
+ const bulmaClasses = containerClassName
3217
+ ? containerClassName
3218
+ : defaultIconClasses;
3219
+ const iconContainerClasses = classNames(bulmaClasses, containerClassName && size ? sizeModifierClass : undefined, bulmaHelperClasses, className);
3220
+ // A named icon is an image, whose name is its label alone, so a ligature inside
3221
+ // never leaks into it. An unnamed one is decorative and hidden outright. Spread
3222
+ // before `rest`, so a caller's own `role` or `aria-hidden` still wins.
3223
+ const named = !!ariaLabel || !!restProps['aria-label'] || !!restProps['aria-labelledby'];
3224
+ // A caller's own `role` or `tabIndex` on an unnamed icon means it isn't decoration (an
3225
+ // icon made a button, say), so it isn't hidden either: `aria-hidden` on something
3226
+ // focusable is a failure of its own.
3227
+ const a11yProps = named
3228
+ ? { role: 'img', 'aria-label': ariaLabel || undefined }
3229
+ : restProps.role !== undefined || restProps.tabIndex !== undefined
3230
+ ? {}
3231
+ : { 'aria-hidden': true };
3232
+ if (children !== undefined) {
3233
+ // `IconChildrenProps`: render the caller's node (an inline SVG, a `react-icons`
3234
+ // component, …) in place of a class-based glyph. Library/variant/features don't apply.
3235
+ return (jsxRuntime.jsx("span", { className: iconContainerClasses, ...a11yProps, style: style, ...rest, children: children }));
3236
+ }
3237
+ // `name` is guaranteed once `children` is absent UNLESS the caller is on the deprecated
3238
+ // `icon` path, which `IconDeprecatedProps` declares with `name?: undefined`.
3239
+ // (`IconChildrenProps['children']` excludes `undefined`, so `children={undefined}` can't
3240
+ // slip past into this branch.) The cast covers that third member, and the `!name && icon`
3241
+ // fallback below is what fills it in.
3242
+ let finalName = name;
3243
+ if (!name && icon) {
3244
+ // If icon prop is provided instead of name, try to parse it
3245
+ // e.g., "mdi mdi-rocket-launch" -> "rocket-launch"
3246
+ if (typeof icon === 'string') {
3247
+ const parts = icon.split(' ');
3248
+ const lastPart = parts[parts.length - 1];
3249
+ if (lastPart.startsWith('mdi-')) {
3250
+ finalName = lastPart.substring(4); // Remove "mdi-" prefix
3251
+ }
3252
+ else if (lastPart.startsWith('fa-')) {
3253
+ finalName = lastPart.substring(3); // Remove "fa-" prefix
3254
+ }
3255
+ else {
3256
+ finalName = lastPart;
3257
+ }
3258
+ }
3259
+ }
3260
+ const finalLibrary = library || defaultLibrary || 'fa';
3261
+ // Normalize a redundant leading library prefix (e.g. "fa-check" -> "check" when the
3262
+ // library is 'fa'), so `name` behaves identically with or without the prefix.
3263
+ finalName = stripRedundantLibraryPrefix(finalName, finalLibrary);
3264
+ if (!finalName) {
3265
+ // No glyph to name. Reachable through the public type only as `icon=""`, and otherwise
3266
+ // by a plain-JS caller (or one that casts). Building a class off an absent name produced
3267
+ // a bogus `fa-undefined` glyph; render the bare container instead, matching the
3268
+ // `children` branch.
3269
+ return (jsxRuntime.jsx("span", { className: iconContainerClasses, ...a11yProps, style: style, ...rest }));
3270
+ }
3271
+ // Backward compatibility: if libraryFeatures is provided, parse it for variant and features
3272
+ let finalVariant = variant;
3273
+ let finalFeatures = features;
3274
+ if (libraryFeatures && !variant && !features) {
3275
+ const legacyFeatures = Array.isArray(libraryFeatures)
3276
+ ? libraryFeatures
3277
+ : [libraryFeatures];
3278
+ // For Font Awesome, extract style from features
3279
+ if (finalLibrary === 'fa') {
3280
+ const faStyle = legacyFeatures.find(f => [
3281
+ 'fas',
3282
+ 'far',
3283
+ 'fab',
3284
+ 'fal',
3285
+ 'fad',
3286
+ 'fat',
3287
+ 'solid',
3288
+ 'regular',
3289
+ 'brands',
3290
+ 'light',
3291
+ 'duotone',
3292
+ 'thin',
3293
+ ].includes(f));
3294
+ if (faStyle) {
3295
+ finalVariant = faStyle;
3296
+ finalFeatures = legacyFeatures.filter(f => f !== faStyle);
3297
+ }
3298
+ else {
3299
+ finalFeatures = legacyFeatures;
3300
+ }
3301
+ }
3302
+ // For Material Icons/Symbols, extract style variant
3303
+ else if (finalLibrary === 'material-icons' ||
3304
+ finalLibrary === 'material-symbols') {
3305
+ const styleVariants = finalLibrary === 'material-icons'
3306
+ ? ['filled', 'outlined', 'round', 'sharp']
3307
+ : ['outlined', 'rounded', 'sharp'];
3308
+ const styleVariant = legacyFeatures.find(f => styleVariants.includes(f));
3309
+ if (styleVariant) {
3310
+ finalVariant = styleVariant;
3311
+ finalFeatures = legacyFeatures.filter(f => f !== styleVariant);
3312
+ }
3313
+ else {
3314
+ finalFeatures = legacyFeatures;
3315
+ }
3316
+ }
3317
+ // For others, all features go to finalFeatures
3318
+ else {
3319
+ finalFeatures = legacyFeatures;
3320
+ }
3321
+ }
3322
+ // Handle web components vs CSS-based icons
3323
+ if (finalLibrary === 'ion') {
3324
+ // For Ionicons, handle variant in the name
3325
+ let ionName = finalName;
3326
+ if (finalVariant === 'outline') {
3327
+ ionName = `${finalName}-outline`;
3328
+ }
3329
+ else if (finalVariant === 'sharp') {
3330
+ ionName = `${finalName}-sharp`;
3331
+ }
3332
+ return (jsxRuntime.jsx("span", { className: iconContainerClasses, ...a11yProps, style: style, ...rest, children: jsxRuntime.jsx("ion-icon", { name: ionName }) }));
3333
+ }
3334
+ // Legacy CSS-based icons
3335
+ const iClasses = getIconClasses(finalLibrary, finalName, finalVariant, finalFeatures);
3336
+ // Material Icons and Material Symbols use text content, not CSS classes for the icon name
3337
+ if (finalLibrary === 'material-icons' ||
3338
+ finalLibrary === 'material-symbols') {
3339
+ return (jsxRuntime.jsx("span", { className: iconContainerClasses, ...a11yProps, style: style, ...rest, children: jsxRuntime.jsx("i", { className: iClasses, children: finalName }) }));
3340
+ }
3341
+ return (jsxRuntime.jsx("span", { className: iconContainerClasses, ...a11yProps, style: style, ...rest, children: jsxRuntime.jsx("i", { className: iClasses }) }));
3342
+ };
3343
+
3344
+ /**
3345
+ * The `Paragraph` component renders a styled `<p>` element with Bulma helper class integration.
3346
+ *
3347
+ * @function
3348
+ * @param {ParagraphProps} props - Props for the Paragraph component.
3349
+ * @returns {JSX.Element} The rendered paragraph element.
3350
+ */
3351
+ const Paragraph = ({ className, textColor, bgColor, children, ...props }) => {
3352
+ /**
3353
+ * Generates Bulma helper classes and separates out remaining props.
3354
+ */
3355
+ const { bulmaHelperClasses, rest } = useBulmaClasses({
3356
+ color: textColor,
3357
+ backgroundColor: bgColor,
3358
+ ...props,
3359
+ });
3360
+ const bulmaClasses = usePrefixedClassNames();
3361
+ const paragraphClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
3362
+ return (jsxRuntime.jsx("p", { className: paragraphClasses || undefined, ...rest, children: children }));
3363
+ };
3364
+
3365
+ const validTitleSizes = ['1', '2', '3', '4', '5', '6'];
3366
+ const validTitleElements = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p'];
3367
+ /**
3368
+ * The `Title` component renders a Bulma-styled title (heading), supporting sizes `1`-`6`, spacing, and rendering as any heading or paragraph element (`h1`-`h6`, `p`).
3369
+ *
3370
+ * @function
3371
+ * @param {TitleProps} props - Props for the Title component.
3372
+ * @returns {JSX.Element} The rendered title element.
3373
+ * @see {@link https://bulma.io/documentation/elements/title/ | Bulma Title documentation}
3374
+ */
3375
+ const Title = ({ className, size, isSpaced, as = 'h1', hasSkeleton, textColor, bgColor, children, ...props }) => {
3376
+ /**
3377
+ * Generates Bulma helper classes and separates out remaining props.
3378
+ */
3379
+ const { bulmaHelperClasses, rest } = useBulmaClasses({
3380
+ color: textColor,
3381
+ backgroundColor: bgColor,
3382
+ ...props,
3383
+ });
3384
+ // Validate 'as' prop at runtime
3385
+ const element = validTitleElements.includes(as) ? as : 'h1';
3386
+ // Validate 'size' prop at runtime (numeric sizes normalize to strings)
3387
+ const normalized = size == null ? undefined : String(size);
3388
+ const validSize = normalized && validTitleSizes.includes(normalized)
3389
+ ? normalized
3390
+ : undefined;
3391
+ const bulmaClasses = usePrefixedClassNames('title', {
3392
+ [`is-${validSize}`]: validSize,
3393
+ 'is-spaced': isSpaced,
3394
+ 'has-skeleton': hasSkeleton,
3395
+ });
3396
+ const titleClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
3397
+ // Determine the tag based on 'element' and 'validSize'
3398
+ const Tag = element === 'p' ? 'p' : validSize ? `h${validSize}` : element;
3399
+ return (jsxRuntime.jsx(Tag, { className: titleClasses, ...rest, children: children }));
3400
+ };
3401
+
3402
+ const validEmptyStateColors = [
3403
+ 'primary',
3404
+ 'link',
3405
+ 'info',
3406
+ 'success',
3407
+ 'warning',
3408
+ 'danger',
3409
+ ];
3410
+ const validEmptyStateHeadings = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
3411
+ const validEmptyStateSizes = ['small', 'medium', 'large'];
3412
+ /**
3413
+ * The circle the picture sits in, hidden from assistive tech whatever is in it.
3414
+ *
3415
+ * @function
3416
+ * @param {EmptyStateIndicatorProps} props - Props for EmptyState.Indicator.
3417
+ * @returns {JSX.Element} The rendered indicator.
3418
+ */
3419
+ const EmptyStateIndicator = ({ className, textColor, bgColor, children, ...props }) => {
3420
+ const { bulmaHelperClasses, rest } = useBulmaClasses({
3421
+ color: textColor,
3422
+ backgroundColor: bgColor,
3423
+ ...props,
3424
+ });
3425
+ const indicatorClass = usePrefixedClassNames('empty-state-indicator');
3426
+ // Hidden after the rest of the props, so nothing passed in can expose the
3427
+ // picture: the title says what it means.
3428
+ return (jsxRuntime.jsx("div", { className: classNames(indicatorClass, bulmaHelperClasses, className), ...rest, "aria-hidden": "true", children: children }));
3429
+ };
3430
+ EmptyStateIndicator.displayName = 'EmptyState.Indicator';
3431
+ /**
3432
+ * The heading, a Bulma `Title` at the level `as` sets, `h2` by default, and at the EmptyState's size.
3433
+ *
3434
+ * @function
3435
+ * @param {EmptyStateTitleProps} props - Props for EmptyState.Title.
3436
+ * @returns {JSX.Element} The rendered heading.
3437
+ */
3438
+ const EmptyStateTitle = ({ as = 'h2', className, ...props }) => {
3439
+ // `size` isn't in the type, but on `Title` it picks the tag, so it's kept
3440
+ // off even when it's passed anyway. The level is `as` alone.
3441
+ const { size: _size, ...rest } = props;
3442
+ const titleClass = usePrefixedClassNames('empty-state-title');
3443
+ const level = validEmptyStateHeadings.includes(as) ? as : 'h2';
3444
+ return (jsxRuntime.jsx(Title, { as: level, className: classNames(titleClass, className), ...rest }));
3445
+ };
3446
+ EmptyStateTitle.displayName = 'EmptyState.Title';
3447
+ /**
3448
+ * A sentence or two under the title, in the color of the text around it.
3449
+ *
3450
+ * @function
3451
+ * @param {EmptyStateDescriptionProps} props - Props for EmptyState.Description.
3452
+ * @returns {JSX.Element} The rendered description.
3453
+ */
3454
+ const EmptyStateDescription = ({ className, ...props }) => {
3455
+ const descriptionClass = usePrefixedClassNames('empty-state-description');
3456
+ return (jsxRuntime.jsx(Paragraph, { className: classNames(descriptionClass, className), ...props }));
3457
+ };
3458
+ EmptyStateDescription.displayName = 'EmptyState.Description';
3459
+ /**
3460
+ * A row of buttons, centered under the text or at the start of the line beside the indicator.
3461
+ *
3462
+ * @function
3463
+ * @param {EmptyStateActionsProps} props - Props for EmptyState.Actions.
3464
+ * @returns {JSX.Element} The rendered row of buttons.
3465
+ */
3466
+ const EmptyStateActions = ({ className, ...props }) => {
3467
+ const actionsClass = usePrefixedClassNames('empty-state-actions');
3468
+ return jsxRuntime.jsx(Buttons, { className: classNames(actionsClass, className), ...props });
3469
+ };
3470
+ EmptyStateActions.displayName = 'EmptyState.Actions';
3471
+ /** Whether a shorthand prop was given something to render. */
3472
+ const isGiven = (node) => node !== undefined &&
3473
+ node !== null &&
3474
+ typeof node !== 'boolean' &&
3475
+ node !== '';
3476
+ /**
3477
+ * The `EmptyState` component renders the block an app shows where content would be: an empty list, a search with no results, or a first-run screen.
3478
+ *
3479
+ * It's a column of a picture in a circle, a heading, a line of text and the
3480
+ * actions, with one layout whether it's written with the `icon`, `title` and
3481
+ * `description` props or with the parts. The children always render as
3482
+ * written, after the parts the props render.
3483
+ *
3484
+ * It has no role and no live region. It arrives already filled and goes away
3485
+ * when results come back, so it can't announce a search that matched
3486
+ * nothing: keep a `role="status"` region beside the search field for that,
3487
+ * in the page the whole time. While the data loads, show a `Skeleton` or a
3488
+ * `Loader` instead, so "No projects yet" doesn't flash first, and show a
3489
+ * load that failed in a `Message` or a `Notification`.
3490
+ *
3491
+ * @function
3492
+ * @param {EmptyStateProps} props - Props for the EmptyState component.
3493
+ * @param {React.Ref<HTMLDivElement>} ref - Forwarded ref to the root `<div>`.
3494
+ * @returns {JSX.Element} The rendered empty state.
3495
+ *
3496
+ * @example
3497
+ * <EmptyState
3498
+ * icon="folder-open"
3499
+ * title="No projects yet"
3500
+ * description="Projects you create or join show up here."
3501
+ * >
3502
+ * <Button color="primary">New project</Button>
3503
+ * </EmptyState>
3504
+ */
3505
+ const EmptyStateRoot = React.forwardRef(({ className, icon, title, titleAs = 'h2', description, children, color, variant = 'light', size = 'normal', align = 'center', textColor, bgColor, ...props }, ref) => {
3506
+ const { bulmaHelperClasses, rest } = useBulmaClasses({
3507
+ color: textColor,
3508
+ backgroundColor: bgColor,
3509
+ ...props,
3510
+ });
3511
+ const rootClass = usePrefixedClassNames('empty-state', {
3512
+ [`is-${size}`]: validEmptyStateSizes.includes(size),
3513
+ 'is-left': align === 'left',
3514
+ [`is-${color}`]: !!color && validEmptyStateColors.includes(color),
3515
+ 'is-filled': variant === 'filled',
3516
+ });
3517
+ // The props' parts first, each only when it's given, then the children
3518
+ // exactly as they were written: nothing here looks at them.
3519
+ 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] }));
3520
+ });
3521
+ const EmptyState = withSubComponents(EmptyStateRoot, {
3522
+ Indicator: EmptyStateIndicator,
3523
+ Title: EmptyStateTitle,
3524
+ Description: EmptyStateDescription,
3525
+ Actions: EmptyStateActions,
3526
+ }, 'EmptyState');
3527
+
3528
+ // Context to track MenuList nesting level
3529
+ const MenuListLevelContext = React.createContext(0);
3530
+ /**
3531
+ * The `Menu` component provides Bulma's vertical navigation menu: a simple, accessible sidebar or section menu for your Bulma React UI.
3532
+ *
3533
+ * @function
3534
+ * @param {MenuProps} props - Props for the Menu component.
3535
+ * @returns {JSX.Element} The rendered menu.
3536
+ * @see {@link https://bulma.io/documentation/components/menu/ | Bulma Menu documentation}
3537
+ */
3538
+ const MenuComponent = ({ className, children, ...props }) => {
3539
+ const { bulmaHelperClasses, rest } = useBulmaClasses(props);
3540
+ // Generate Bulma classes with prefix
3541
+ const bulmaClasses = usePrefixedClassNames('menu');
3542
+ return (jsxRuntime.jsx("aside", { className: classNames(bulmaClasses, bulmaHelperClasses, className), ...rest, children: children }));
3543
+ };
3544
+ /**
3545
+ * Bulma Menu label component.
3546
+ *
3547
+ * @function
3548
+ * @param {MenuLabelProps} props - Props for the MenuLabel component.
3549
+ * @returns {JSX.Element} The rendered menu label.
3550
+ */
3551
+ const MenuLabel = ({ className, children, ...props }) => {
3552
+ const { bulmaHelperClasses, rest } = useBulmaClasses(props);
3553
+ return (jsxRuntime.jsx("p", { className: classNames(usePrefixedClassNames('menu-label'), className, bulmaHelperClasses), ...rest, children: children }));
3554
+ };
3555
+ /**
3556
+ * MenuList applies `menu-list` class only at the top level (not for nested lists).
3557
+ *
3558
+ * @function
3559
+ * @param {MenuListProps} props - Props for the MenuList component.
3560
+ * @returns {JSX.Element} The rendered menu list.
3561
+ */
3562
+ const MenuList = ({ className, children, ...props }) => {
3563
+ const level = React.useContext(MenuListLevelContext);
3564
+ const { bulmaHelperClasses, rest } = useBulmaClasses(props);
3565
+ const ulClass = classNames(className, bulmaHelperClasses, {
3566
+ [usePrefixedClassNames('menu-list')]: level === 0,
3567
+ });
3568
+ // Increment level for nested MenuLists
3569
+ return (jsxRuntime.jsx(MenuListLevelContext.Provider, { value: level + 1, children: jsxRuntime.jsx("ul", { className: ulClass || undefined, ...rest, children: children }) }));
3570
+ };
3571
+ /**
3572
+ * `href` alone — annotated so a typo is a compile error rather than a silent
3573
+ * no-op, the way both siblings' sets are.
3574
+ */
3575
+ const STRIP_FROM_NON_LINK = { href: true };
3576
+ /**
3577
+ * MenuItem supports `as` prop for custom link components, e.g., react-router-dom Link.
3578
+ *
3579
+ * @function
3580
+ * @param {MenuItemProps} props - Props for the MenuItem component.
3581
+ * @param {React.Ref} ref - Forwarded ref to the inner element `as` renders.
3582
+ * @returns {JSX.Element} The rendered menu item.
3583
+ */
3584
+ const MenuItem = React.forwardRef(function MenuItem(itemProps, ref) {
3585
+ const { className, children, active, as: Component = 'a', 'data-testid': testId, style, id, title, role, tabIndex, ...rest } = itemProps;
3586
+ const { bulmaHelperClasses, rest: forwarded } = useBulmaClasses(rest);
3587
+ // `href` reaches an anchor or a custom component (which owns its own prop
3588
+ // contract), but not another intrinsic tag — `<span href>` is invalid HTML.
3589
+ // The same rule Avatar applies through `isLinkLike` and Button through its
3590
+ // intrinsic-only strip. Before #641 `href` was an own prop re-applied only
3591
+ // for `as="a"`; deriving it from `as` must not quietly widen that.
3592
+ const isLinkLike = Component === 'a' ||
3593
+ typeof Component !== 'string' ||
3594
+ isCustomElement(Component);
3595
+ // `href` ALONE. `Dropdown.Item` strips every anchor-only attribute except
3596
+ // `type`; `Level.Item` strips all of them, plus `rel`.
3597
+ //
3598
+ // Not because nothing else can arrive: `as` here is an open `React.ElementType`,
3599
+ // and a non-fresh spread skips excess-property checking, so a typed caller
3600
+ // CAN deliver `target` or `download` and they are forwarded. The set stays at
3601
+ // `href` because widening it would be wrong for an open `as` — `referrerPolicy`
3602
+ // on `as="img"`, `target` on `as="form"` and `media` on `as="source"` are all
3603
+ // legal, and a flat strip would delete them. Getting that right needs a
3604
+ // per-attribute-per-element rule, which `bestax-migrate` already models in
3605
+ // `LINK_ATTR_ELEMENTS`.
3606
+ const linkProps = isLinkLike
3607
+ ? forwarded
3608
+ : omitAttrs(forwarded, STRIP_FROM_NON_LINK);
3609
+ const itemClass = classNames({ [usePrefixedClassNames('is-active')]: active }, bulmaHelperClasses);
3610
+ // Split children into label and nested MenuList(s)
3611
+ const labelChildren = [];
3612
+ const nestedMenuLists = [];
3613
+ React.Children.forEach(children, child => {
3614
+ if (React.isValidElement(child) && child.type === MenuList) {
3615
+ nestedMenuLists.push(child);
3616
+ }
3617
+ else {
3618
+ labelChildren.push(child);
3619
+ }
3620
+ });
3621
+ return (jsxRuntime.jsxs("li", { className: className, "data-testid": testId, style: style, id: id, title: title, role: role, tabIndex: tabIndex, children: [jsxRuntime.jsx(Component, { ref: ref, className: itemClass || undefined, ...linkProps, ...(Component === 'button'
3622
+ ? { type: buttonType(linkProps.type) }
3623
+ : {}), children: labelChildren }), nestedMenuLists] }));
3624
+ });
3625
+ MenuItem.displayName = 'MenuItem';
3626
+ // Attach static subcomponents
3627
+ const Menu = withSubComponents(MenuComponent, {
3628
+ Label: MenuLabel,
3629
+ List: MenuList,
3630
+ Item: MenuItem,
3631
+ }, 'Menu');
3632
+
3633
+ const validMessageSizes = ['small', 'medium', 'large'];
3634
+ /**
3635
+ * The `Message` component provides Bulma's flexible notice/message box for your Bulma React UI.
3636
+ *
3637
+ * @function
3638
+ * @param {MessageProps} props - Props for the Message component.
3639
+ * @returns {JSX.Element} The rendered message.
3640
+ * @see {@link https://bulma.io/documentation/components/message/ | Bulma Message documentation}
3641
+ */
3642
+ const MessageComponent = ({ className, title, textColor, color, bgColor, size, onClose, children, ...props }) => {
3643
+ const { classPrefix } = useConfig();
3644
+ const { bulmaHelperClasses, rest } = useBulmaClasses({
3645
+ color: textColor,
3646
+ backgroundColor: bgColor,
3647
+ ...props,
3648
+ });
3649
+ // Generate Bulma classes with prefix
3650
+ const bulmaClasses = usePrefixedClassNames('message', {
3651
+ [`is-${color}`]: color,
3652
+ [`is-${size}`]: size && validMessageSizes.includes(size),
3653
+ });
3654
+ const deleteClass = usePrefixedClassNames('delete');
3655
+ const messageClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
3656
+ return (jsxRuntime.jsxs("article", { className: messageClasses, ...rest, "data-testid": "message", children: [(title || onClose) && (jsxRuntime.jsxs("div", { className: prefixedClassNames(classPrefix, 'message-header'), children: [title && jsxRuntime.jsx("span", { children: title }), onClose && (jsxRuntime.jsx("button", { className: deleteClass, "aria-label": "delete", onClick: onClose, type: "button", "data-testid": "message-close" }))] })), children && (jsxRuntime.jsx("div", { className: prefixedClassNames(classPrefix, 'message-body'), "data-testid": "message-body", children: children }))] }));
3657
+ };
3658
+ /**
3659
+ * Message header compound component. Renders a `.message-header` element.
3660
+ *
3661
+ * @function
3662
+ * @param {MessageHeaderProps} props - Props for the MessageHeader component.
3663
+ * @returns {JSX.Element} The rendered message header.
3664
+ */
3665
+ const MessageHeader = ({ className, children, ...props }) => (jsxRuntime.jsx("div", { className: classNames(usePrefixedClassNames('message-header'), className), ...props, children: children }));
3666
+ /**
3667
+ * Message body compound component. Renders a `.message-body` element.
3668
+ *
3669
+ * @function
3670
+ * @param {MessageBodyProps} props - Props for the MessageBody component.
3671
+ * @returns {JSX.Element} The rendered message body.
3672
+ */
3673
+ const MessageBody = ({ className, children, ...props }) => (jsxRuntime.jsx("div", { className: classNames(usePrefixedClassNames('message-body'), className), ...props, children: children }));
3674
+ // Attach compound components
3675
+ const Message = withSubComponents(MessageComponent, {
3676
+ Header: MessageHeader,
3677
+ Body: MessageBody,
3678
+ }, 'Message');
3679
+
3680
+ // Ref-counted across every caller (e.g. a Dialog rendering its own Modal)
3681
+ // so overlapping/nested locks don't fight over which one restores `overflow`.
3682
+ // Every overlay that locks body scroll — Modal, Dialog, Sidebar, Loading —
3683
+ // goes through here; a component setting `document.body.style.overflow`
3684
+ // itself would clear a lock another overlay still needs.
3685
+ let lockCount = 0;
3686
+ let originalOverflow = '';
3687
+ /**
3688
+ * Locks (and ref-counted-ly unlocks) `document.body` scrolling while `active`
3689
+ * is true. Safe to call from multiple components at once — the body scroll
3690
+ * is only restored once every active caller has released its lock.
3691
+ *
3692
+ * @function useScrollLock
3693
+ * @param active - Whether this caller wants the body scroll locked.
3694
+ */
3695
+ function useScrollLock(active) {
3696
+ React.useEffect(() => {
3697
+ if (!active)
3698
+ return undefined;
3699
+ lockCount++;
3700
+ if (lockCount === 1) {
3701
+ originalOverflow = document.body.style.overflow;
3702
+ document.body.style.overflow = 'hidden';
3703
+ }
3704
+ return () => {
3705
+ lockCount--;
3706
+ if (lockCount === 0) {
3707
+ document.body.style.overflow = originalOverflow;
3708
+ }
3709
+ };
3710
+ }, [active]);
3711
+ }
3712
+
3713
+ // "Are we past hydration?" as a store that never changes: React reads the
3714
+ // server snapshot both while server-rendering and while hydrating, and the
3715
+ // client snapshot from the first post-hydration render onward.
3716
+ const subscribeToNothing = () => () => { };
3717
+ const getClientSnapshot = () => true;
3718
+ const getServerSnapshot = () => false;
3719
+ /**
3720
+ * Reports whether the component has passed hydration.
3721
+ *
3722
+ * Returns `false` during server rendering and during the hydrating client
3723
+ * render, then `true` from the commit that follows. Use it to defer
3724
+ * DOM-only behaviour (such as portalling) past hydration so the first client
3725
+ * render matches the server markup instead of tripping hydration recovery.
3726
+ *
3727
+ * Only a component that hydrates ever sees `false` on the client. One that
3728
+ * mounts later (after a route change, or in an app with no server rendering)
3729
+ * gets `true` on its first render, so it pays for no extra render. It
3730
+ * subscribes to nothing, so no later render flips it back.
3731
+ *
3732
+ * @function useIsHydrated
3733
+ * @returns `true` once the hydrating render has completed, otherwise `false`.
3734
+ */
3735
+ function useIsHydrated() {
3736
+ return React.useSyncExternalStore(subscribeToNothing, getClientSnapshot, getServerSnapshot);
3737
+ }
3738
+
3739
+ /**
3740
+ * Resolves a portal target: an `HTMLElement` is used directly, a non-empty
3741
+ * `string` is treated as a `document.querySelector` selector (falling back to
3742
+ * `document.body` when it matches nothing), and any falsy value — `undefined`
3743
+ * or the empty string — resolves to `document.body`.
3744
+ *
3745
+ * The empty string is deliberately treated as "no target" rather than passed
3746
+ * through: `document.querySelector('')` throws a `SyntaxError`, and callers
3747
+ * building a selector from state can easily hand us `''`.
3748
+ *
3749
+ * @function resolvePortalContainer
3750
+ * @param container - The requested portal target, if any.
3751
+ * @returns The resolved DOM node to portal into.
3752
+ */
3753
+ function resolvePortalContainer(container) {
3754
+ if (!container) {
3755
+ return document.body;
3756
+ }
3757
+ if (typeof container === 'string') {
3758
+ return (document.querySelector(container) ?? document.body);
3075
3759
  }
3076
3760
  return container;
3077
3761
  }
@@ -3819,428 +4503,180 @@ function whole(count, fallback) {
3819
4503
  }
3820
4504
  function range(from, to) {
3821
4505
  return Array.from({ length: Math.max(to - from + 1, 0) }, (_, i) => from + i);
3822
- }
3823
- /**
3824
- * The pages to show for `current` of `total`: the first and last
3825
- * `boundaries`, `siblings` on each side of the current one, and a gap for
3826
- * each run left out. A gap of a single page shows that page instead, and the
3827
- * window around the current page slides inward near either end, so the row
3828
- * keeps its length as the page moves.
3829
- */
3830
- function pageItems(total, current, siblings, boundaries) {
3831
- const windowSize = siblings * 2 + 1;
3832
- // Every page fits in the room the boundary pages, the window and two gaps take.
3833
- if (total <= boundaries * 2 + windowSize + 2)
3834
- return range(1, total);
3835
- const lowest = boundaries + 2;
3836
- const highest = total - boundaries - 1;
3837
- const start = Math.min(Math.max(current - siblings, lowest), highest - windowSize + 1);
3838
- const end = start + windowSize - 1;
3839
- const gap = (from, to, name) => to === from ? [from] : [name];
3840
- return [
3841
- ...range(1, boundaries),
3842
- ...gap(boundaries + 1, start - 1, 'start-gap'),
3843
- ...range(start, end),
3844
- ...gap(end + 1, total - boundaries, 'end-gap'),
3845
- ...range(total - boundaries + 1, total),
3846
- ];
3847
- }
3848
- /**
3849
- * "Previous" navigation button.
3850
- *
3851
- * @function
3852
- * @param {PaginationPreviousNextProps} props - Props for the PaginationPrevious component.
3853
- * @returns {JSX.Element} The rendered previous button.
3854
- */
3855
- const PaginationPrevious = ({ className, disabled, children, ...props }) => (jsxRuntime.jsx("a", { className: classNames(usePrefixedClassNames('pagination-previous', {
3856
- 'is-disabled': disabled,
3857
- }), className), "aria-disabled": disabled, tabIndex: disabled ? -1 : 0, ...props, onClick: disabled
3858
- ? e => {
3859
- e.preventDefault();
3860
- e.stopPropagation();
3861
- }
3862
- : props.onClick, children: children }));
3863
- /**
3864
- * "Next" navigation button.
3865
- *
3866
- * @function
3867
- * @param {PaginationPreviousNextProps} props - Props for the PaginationNext component.
3868
- * @returns {JSX.Element} The rendered next button.
3869
- */
3870
- const PaginationNext = ({ className, disabled, children, ...props }) => (jsxRuntime.jsx("a", { className: classNames(usePrefixedClassNames('pagination-next', { 'is-disabled': disabled }), className), "aria-disabled": disabled, tabIndex: disabled ? -1 : 0, ...props, onClick: disabled
3871
- ? e => {
3872
- e.preventDefault();
3873
- e.stopPropagation();
3874
- }
3875
- : props.onClick, children: children }));
3876
- /**
3877
- * The `Pagination` component provides a flexible, composable Bulma pagination navigation for your Bulma React UI.
3878
- *
3879
- * @function
3880
- * @param {PaginationProps} props - Props for the Pagination component.
3881
- * @returns {JSX.Element} The rendered pagination.
3882
- * @see {@link https://bulma.io/documentation/components/pagination/ | Bulma Pagination documentation}
3883
- */
3884
- const PaginationComponent = ({ color, textColor, bgColor, size, align, rounded, total, current, onPageChange, siblingCount = 1, boundaryCount = 1, previousLabel = 'Previous', nextLabel = 'Next', disabled, getPageHref, getPageLabel, className, children, ...props }) => {
3885
- const [kept, setKept] = React.useState(1);
3886
- warnDeprecatedColorProp('Pagination', color, 'Use the textColor / bgColor helper props instead.');
3887
- const { bulmaHelperClasses, rest } = useBulmaClasses({
3888
- color: textColor,
3889
- backgroundColor: bgColor,
3890
- ...props,
3891
- });
3892
- // Generate Bulma classes with prefix
3893
- const bulmaClasses = usePrefixedClassNames('pagination', {
3894
- [`is-${color}`]: color,
3895
- [`is-${size}`]: size,
3896
- [`is-${align}`]: align,
3897
- 'is-rounded': rounded,
3898
- });
3899
- const paginationClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
3900
- // A child that renders nothing (`{show && …}` when false) is no children.
3901
- const fromTotal = total !== undefined && React.Children.toArray(children).length === 0;
3902
- let content = children;
3903
- if (fromTotal) {
3904
- const pages = Number.isFinite(total) ? Math.max(Math.floor(total), 0) : 0;
3905
- // A current page that's no number reads as the first.
3906
- const page = Math.min(Math.max(whole(current ?? kept, 1), 1), Math.max(pages, 1));
3907
- const choose = (next) => {
3908
- if (next === page)
3909
- return;
3910
- if (current === undefined)
3911
- setKept(next);
3912
- onPageChange?.(next);
3913
- };
3914
- /**
3915
- * What a link to `target` is given, beside what it renders. With no
3916
- * `getPageHref` it's a button; a link that's `off` goes nowhere.
3917
- */
3918
- const linkTo = (target, off) => ({
3919
- href: off || !getPageHref ? undefined : getPageHref(target),
3920
- role: getPageHref ? undefined : 'button',
3921
- // A link that's off is disabled too, and the parts drop its clicks.
3922
- onClick: () => choose(target),
3923
- onKeyDown: getPageHref
3924
- ? undefined
3925
- : (e) => {
3926
- if (off || (e.key !== 'Enter' && e.key !== ' '))
3927
- return;
3928
- e.preventDefault();
3929
- choose(target);
3930
- },
3931
- });
3932
- const atStart = disabled || page <= 1;
3933
- const atEnd = disabled || page >= pages;
3934
- content = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(PaginationPrevious, { disabled: atStart, ...linkTo(page - 1, atStart), children: previousLabel }), jsxRuntime.jsx(PaginationNext, { disabled: atEnd, ...linkTo(page + 1, atEnd), children: nextLabel }), jsxRuntime.jsx(PaginationList, { children: pageItems(pages, page, whole(siblingCount, 1), whole(boundaryCount, 1)).map(item => typeof item === 'number' ? (jsxRuntime.jsx(PaginationLink, { "aria-label": getPageLabel ? getPageLabel(item) : `Page ${item}`, active: item === page, disabled: disabled, ...linkTo(item, Boolean(disabled)), children: item }, item)) : (jsxRuntime.jsx(PaginationEllipsis, {}, item))) })] }));
3935
- }
3936
- return (jsxRuntime.jsx("nav", { className: paginationClasses, role: "navigation", "aria-label": "pagination", ...rest, children: content }));
3937
- };
3938
- /**
3939
- * Container for page links and ellipsis.
3940
- *
3941
- * @function
3942
- * @param {PaginationListProps} props - Props for the PaginationList component.
3943
- * @returns {JSX.Element} The rendered pagination list.
3944
- */
3945
- const PaginationList = ({ className, textColor, bgColor, children, ...props }) => {
3946
- const { bulmaHelperClasses, rest } = useBulmaClasses({
3947
- color: textColor,
3948
- backgroundColor: bgColor,
3949
- ...props,
3950
- });
3951
- return (jsxRuntime.jsx("ul", { className: classNames(usePrefixedClassNames('pagination-list'), bulmaHelperClasses, className), ...rest, children: children }));
3952
- };
3953
- /**
3954
- * Page number or navigation link.
3955
- *
3956
- * @function
3957
- * @param {PaginationLinkProps} props - Props for the PaginationLink component.
3958
- * @returns {JSX.Element} The rendered pagination link.
3959
- */
3960
- const PaginationLink = ({ className, textColor, bgColor, active, disabled, onClick, children, ...props }) => {
3961
- const { bulmaHelperClasses, rest } = useBulmaClasses({
3962
- color: textColor,
3963
- backgroundColor: bgColor,
3964
- ...props,
3965
- });
3966
- const handleClick = (e) => {
3967
- if (disabled) {
3968
- e.preventDefault();
3969
- e.stopPropagation();
3970
- return;
3971
- }
3972
- if (onClick) {
3973
- onClick(e);
3974
- }
3975
- };
3976
- return (jsxRuntime.jsx("li", { children: jsxRuntime.jsx("a", { className: classNames(usePrefixedClassNames('pagination-link', {
3977
- 'is-current': active,
3978
- 'is-disabled': disabled,
3979
- }), bulmaHelperClasses, className), "aria-current": active ? 'page' : undefined, "aria-disabled": disabled, tabIndex: disabled ? -1 : 0, onClick: handleClick, ...rest, children: children }) }));
3980
- };
3981
- /**
3982
- * Ellipsis separator.
3983
- *
3984
- * @function
3985
- * @param {React.LiHTMLAttributes<HTMLLIElement>} props - Standard li props.
3986
- * @returns {JSX.Element} The rendered ellipsis.
3987
- */
3988
- const PaginationEllipsis = props => (jsxRuntime.jsx("li", { children: jsxRuntime.jsx("span", { className: usePrefixedClassNames('pagination-ellipsis'), ...props, children: "\u2026" }) }));
3989
- const Pagination = withSubComponents(PaginationComponent, {
3990
- Link: PaginationLink,
3991
- List: PaginationList,
3992
- Ellipsis: PaginationEllipsis,
3993
- Previous: PaginationPrevious,
3994
- Next: PaginationNext,
3995
- }, 'Pagination');
3996
-
3997
- /**
3998
- * Strips a redundant leading library prefix from an icon name (e.g. `fa-check` -> `check`
3999
- * for the `fa` library, `mdi-account` -> `account` for `mdi`), so `name` behaves the same
4000
- * whether or not the caller includes the prefix. Font Awesome and Material Design Icons docs
4001
- * conventionally refer to icons with their class prefix (`fa-check`), which otherwise gets
4002
- * doubled by `getIconClasses` (producing `fa-fa-check`, matching no glyph).
4003
- *
4004
- * @param {string} name - The icon name, possibly including a redundant library prefix.
4005
- * @param {IconLibrary} library - The icon library the name will be rendered with.
4006
- * @returns {string} The icon name with any redundant leading library prefix removed.
4007
- */
4008
- function stripRedundantLibraryPrefix(name, library) {
4009
- if (!name) {
4010
- return name;
4011
- }
4012
- if (library === 'fa' && name.startsWith('fa-')) {
4013
- return name.substring(3);
4014
- }
4015
- if (library === 'mdi' && name.startsWith('mdi-')) {
4016
- return name.substring(4);
4017
- }
4018
- return name;
4019
- }
4020
- /**
4021
- * Gets the correct classes for the icon element based on the library and features.
4022
- *
4023
- * @param {IconLibrary} library - The icon library.
4024
- * @param {string} name - The icon name.
4025
- * @param {string} [variant] - Icon style variant (e.g., 'solid', 'outlined', 'rounded').
4026
- * @param {string | string[]} [features] - Additional library-specific modifiers.
4027
- * @returns {string} The combined class string for the icon.
4028
- */
4029
- function getIconClasses(library, name, variant, features) {
4030
- let baseClass;
4031
- let iconClass;
4032
- const featureList = Array.isArray(features)
4033
- ? features
4034
- : features
4035
- ? [features]
4036
- : [];
4037
- switch (library) {
4038
- case 'fa': {
4039
- // Font Awesome: use variant as style ('solid' -> 'fas', 'regular' -> 'far', etc.)
4040
- const styleMap = {
4041
- solid: 'fas',
4042
- regular: 'far',
4043
- brands: 'fab',
4044
- light: 'fal',
4045
- duotone: 'fad',
4046
- thin: 'fat',
4047
- };
4048
- const faStyle = variant ? styleMap[variant] || variant : 'fas';
4049
- baseClass = faStyle;
4050
- iconClass = `fa-${name}`;
4051
- return [baseClass, iconClass, ...featureList].join(' ');
4052
- }
4053
- case 'mdi':
4054
- // Material Design Icons: no variants, just features
4055
- baseClass = 'mdi';
4056
- iconClass = `mdi-${name}`;
4057
- return [baseClass, iconClass, ...featureList].join(' ');
4058
- case 'material-icons': {
4059
- // Google Material Icons: map variants to full class names
4060
- const styleVariants = {
4061
- filled: 'material-icons',
4062
- outlined: 'material-icons-outlined',
4063
- round: 'material-icons-round',
4064
- sharp: 'material-icons-sharp',
4065
- };
4066
- baseClass = variant
4067
- ? styleVariants[variant] || `material-icons-${variant}`
4068
- : 'material-icons';
4069
- return [baseClass, ...featureList].join(' ');
4506
+ }
4507
+ /**
4508
+ * The pages to show for `current` of `total`: the first and last
4509
+ * `boundaries`, `siblings` on each side of the current one, and a gap for
4510
+ * each run left out. A gap of a single page shows that page instead, and the
4511
+ * window around the current page slides inward near either end, so the row
4512
+ * keeps its length as the page moves.
4513
+ */
4514
+ function pageItems(total, current, siblings, boundaries) {
4515
+ const windowSize = siblings * 2 + 1;
4516
+ // Every page fits in the room the boundary pages, the window and two gaps take.
4517
+ if (total <= boundaries * 2 + windowSize + 2)
4518
+ return range(1, total);
4519
+ const lowest = boundaries + 2;
4520
+ const highest = total - boundaries - 1;
4521
+ const start = Math.min(Math.max(current - siblings, lowest), highest - windowSize + 1);
4522
+ const end = start + windowSize - 1;
4523
+ const gap = (from, to, name) => to === from ? [from] : [name];
4524
+ return [
4525
+ ...range(1, boundaries),
4526
+ ...gap(boundaries + 1, start - 1, 'start-gap'),
4527
+ ...range(start, end),
4528
+ ...gap(end + 1, total - boundaries, 'end-gap'),
4529
+ ...range(total - boundaries + 1, total),
4530
+ ];
4531
+ }
4532
+ /**
4533
+ * "Previous" navigation button.
4534
+ *
4535
+ * @function
4536
+ * @param {PaginationPreviousNextProps} props - Props for the PaginationPrevious component.
4537
+ * @returns {JSX.Element} The rendered previous button.
4538
+ */
4539
+ const PaginationPrevious = ({ className, disabled, children, ...props }) => (jsxRuntime.jsx("a", { className: classNames(usePrefixedClassNames('pagination-previous', {
4540
+ 'is-disabled': disabled,
4541
+ }), className), "aria-disabled": disabled, tabIndex: disabled ? -1 : 0, ...props, onClick: disabled
4542
+ ? e => {
4543
+ e.preventDefault();
4544
+ e.stopPropagation();
4070
4545
  }
4071
- case 'material-symbols': {
4072
- // Google Material Symbols: map variants to full class names
4073
- const styleVariants = {
4074
- outlined: 'material-symbols-outlined',
4075
- rounded: 'material-symbols-rounded',
4076
- sharp: 'material-symbols-sharp',
4077
- };
4078
- baseClass = variant
4079
- ? styleVariants[variant] || `material-symbols-${variant}`
4080
- : 'material-symbols-outlined';
4081
- return [baseClass, ...featureList].join(' ');
4546
+ : props.onClick, children: children }));
4547
+ /**
4548
+ * "Next" navigation button.
4549
+ *
4550
+ * @function
4551
+ * @param {PaginationPreviousNextProps} props - Props for the PaginationNext component.
4552
+ * @returns {JSX.Element} The rendered next button.
4553
+ */
4554
+ const PaginationNext = ({ className, disabled, children, ...props }) => (jsxRuntime.jsx("a", { className: classNames(usePrefixedClassNames('pagination-next', { 'is-disabled': disabled }), className), "aria-disabled": disabled, tabIndex: disabled ? -1 : 0, ...props, onClick: disabled
4555
+ ? e => {
4556
+ e.preventDefault();
4557
+ e.stopPropagation();
4082
4558
  }
4083
- default:
4084
- // fallback: just icon name and features
4085
- return [name, ...featureList].join(' ');
4086
- }
4087
- }
4559
+ : props.onClick, children: children }));
4088
4560
  /**
4089
- * The `Icon` component is a Bulma-styled wrapper for displaying icons from various libraries (Font Awesome, Material Design Icons, Ionicons, Google Material Icons, Material Symbols, etc.).
4561
+ * The `Pagination` component provides a flexible, composable Bulma pagination navigation for your Bulma React UI.
4090
4562
  *
4091
4563
  * @function
4092
- * @param {IconProps} props - Props for the Icon component.
4093
- * @returns {JSX.Element} The rendered icon element.
4094
- * @see {@link https://bulma.io/documentation/elements/icon/ | Bulma Icon documentation}
4564
+ * @param {PaginationProps} props - Props for the Pagination component.
4565
+ * @returns {JSX.Element} The rendered pagination.
4566
+ * @see {@link https://bulma.io/documentation/components/pagination/ | Bulma Pagination documentation}
4095
4567
  */
4096
- const Icon = ({ className, textColor, bgColor, name, library, variant, features, libraryFeatures, // Deprecated but maintained for backward compatibility
4097
- size, ariaLabel, style, icon, // Capture and exclude the deprecated 'icon' prop from DOM
4098
- color: _color, // Exclude 'color' prop if passed directly
4099
- containerClassName, children, ...restProps }) => {
4100
- // Get the default icon library from context, fallback to 'fa' if not set
4101
- const defaultLibrary = useIconLibrary();
4102
- /**
4103
- * Generates Bulma helper classes and separates out remaining props.
4104
- * Note: variant, features, and libraryFeatures are excluded from props spread
4105
- */
4568
+ const PaginationComponent = ({ color, textColor, bgColor, size, align, rounded, total, current, onPageChange, siblingCount = 1, boundaryCount = 1, previousLabel = 'Previous', nextLabel = 'Next', disabled, getPageHref, getPageLabel, className, children, ...props }) => {
4569
+ const [kept, setKept] = React.useState(1);
4570
+ warnDeprecatedColorProp('Pagination', color, 'Use the textColor / bgColor helper props instead.');
4106
4571
  const { bulmaHelperClasses, rest } = useBulmaClasses({
4107
4572
  color: textColor,
4108
4573
  backgroundColor: bgColor,
4109
- ...restProps,
4574
+ ...props,
4110
4575
  });
4111
- // Hoisted unconditionally to respect rules-of-hooks; the branches below
4112
- // pick which result to consume.
4113
- const defaultIconClasses = usePrefixedClassNames('icon', {
4576
+ // Generate Bulma classes with prefix
4577
+ const bulmaClasses = usePrefixedClassNames('pagination', {
4578
+ [`is-${color}`]: color,
4114
4579
  [`is-${size}`]: size,
4580
+ [`is-${align}`]: align,
4581
+ 'is-rounded': rounded,
4115
4582
  });
4116
- const sizeModifierClass = usePrefixedClassNames(size ? `is-${size}` : undefined);
4117
- const bulmaClasses = containerClassName
4118
- ? containerClassName
4119
- : defaultIconClasses;
4120
- const iconContainerClasses = classNames(bulmaClasses, containerClassName && size ? sizeModifierClass : undefined, bulmaHelperClasses, className);
4121
- // A named icon is an image, whose name is its label alone, so a ligature inside
4122
- // never leaks into it. An unnamed one is decorative and hidden outright. Spread
4123
- // before `rest`, so a caller's own `role` or `aria-hidden` still wins.
4124
- const named = !!ariaLabel || !!restProps['aria-label'] || !!restProps['aria-labelledby'];
4125
- // A caller's own `role` or `tabIndex` on an unnamed icon means it isn't decoration (an
4126
- // icon made a button, say), so it isn't hidden either: `aria-hidden` on something
4127
- // focusable is a failure of its own.
4128
- const a11yProps = named
4129
- ? { role: 'img', 'aria-label': ariaLabel || undefined }
4130
- : restProps.role !== undefined || restProps.tabIndex !== undefined
4131
- ? {}
4132
- : { 'aria-hidden': true };
4133
- if (children !== undefined) {
4134
- // `IconChildrenProps`: render the caller's node (an inline SVG, a `react-icons`
4135
- // component, …) in place of a class-based glyph. Library/variant/features don't apply.
4136
- return (jsxRuntime.jsx("span", { className: iconContainerClasses, ...a11yProps, style: style, ...rest, children: children }));
4137
- }
4138
- // `name` is guaranteed once `children` is absent UNLESS the caller is on the deprecated
4139
- // `icon` path, which `IconDeprecatedProps` declares with `name?: undefined`.
4140
- // (`IconChildrenProps['children']` excludes `undefined`, so `children={undefined}` can't
4141
- // slip past into this branch.) The cast covers that third member, and the `!name && icon`
4142
- // fallback below is what fills it in.
4143
- let finalName = name;
4144
- if (!name && icon) {
4145
- // If icon prop is provided instead of name, try to parse it
4146
- // e.g., "mdi mdi-rocket-launch" -> "rocket-launch"
4147
- if (typeof icon === 'string') {
4148
- const parts = icon.split(' ');
4149
- const lastPart = parts[parts.length - 1];
4150
- if (lastPart.startsWith('mdi-')) {
4151
- finalName = lastPart.substring(4); // Remove "mdi-" prefix
4152
- }
4153
- else if (lastPart.startsWith('fa-')) {
4154
- finalName = lastPart.substring(3); // Remove "fa-" prefix
4155
- }
4156
- else {
4157
- finalName = lastPart;
4158
- }
4159
- }
4160
- }
4161
- const finalLibrary = library || defaultLibrary || 'fa';
4162
- // Normalize a redundant leading library prefix (e.g. "fa-check" -> "check" when the
4163
- // library is 'fa'), so `name` behaves identically with or without the prefix.
4164
- finalName = stripRedundantLibraryPrefix(finalName, finalLibrary);
4165
- if (!finalName) {
4166
- // No glyph to name. Reachable through the public type only as `icon=""`, and otherwise
4167
- // by a plain-JS caller (or one that casts). Building a class off an absent name produced
4168
- // a bogus `fa-undefined` glyph; render the bare container instead, matching the
4169
- // `children` branch.
4170
- return (jsxRuntime.jsx("span", { className: iconContainerClasses, ...a11yProps, style: style, ...rest }));
4171
- }
4172
- // Backward compatibility: if libraryFeatures is provided, parse it for variant and features
4173
- let finalVariant = variant;
4174
- let finalFeatures = features;
4175
- if (libraryFeatures && !variant && !features) {
4176
- const legacyFeatures = Array.isArray(libraryFeatures)
4177
- ? libraryFeatures
4178
- : [libraryFeatures];
4179
- // For Font Awesome, extract style from features
4180
- if (finalLibrary === 'fa') {
4181
- const faStyle = legacyFeatures.find(f => [
4182
- 'fas',
4183
- 'far',
4184
- 'fab',
4185
- 'fal',
4186
- 'fad',
4187
- 'fat',
4188
- 'solid',
4189
- 'regular',
4190
- 'brands',
4191
- 'light',
4192
- 'duotone',
4193
- 'thin',
4194
- ].includes(f));
4195
- if (faStyle) {
4196
- finalVariant = faStyle;
4197
- finalFeatures = legacyFeatures.filter(f => f !== faStyle);
4198
- }
4199
- else {
4200
- finalFeatures = legacyFeatures;
4201
- }
4202
- }
4203
- // For Material Icons/Symbols, extract style variant
4204
- else if (finalLibrary === 'material-icons' ||
4205
- finalLibrary === 'material-symbols') {
4206
- const styleVariants = finalLibrary === 'material-icons'
4207
- ? ['filled', 'outlined', 'round', 'sharp']
4208
- : ['outlined', 'rounded', 'sharp'];
4209
- const styleVariant = legacyFeatures.find(f => styleVariants.includes(f));
4210
- if (styleVariant) {
4211
- finalVariant = styleVariant;
4212
- finalFeatures = legacyFeatures.filter(f => f !== styleVariant);
4213
- }
4214
- else {
4215
- finalFeatures = legacyFeatures;
4216
- }
4217
- }
4218
- // For others, all features go to finalFeatures
4219
- else {
4220
- finalFeatures = legacyFeatures;
4221
- }
4583
+ const paginationClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
4584
+ // A child that renders nothing (`{show && …}` when false) is no children.
4585
+ const fromTotal = total !== undefined && React.Children.toArray(children).length === 0;
4586
+ let content = children;
4587
+ if (fromTotal) {
4588
+ const pages = Number.isFinite(total) ? Math.max(Math.floor(total), 0) : 0;
4589
+ // A current page that's no number reads as the first.
4590
+ const page = Math.min(Math.max(whole(current ?? kept, 1), 1), Math.max(pages, 1));
4591
+ const choose = (next) => {
4592
+ if (next === page)
4593
+ return;
4594
+ if (current === undefined)
4595
+ setKept(next);
4596
+ onPageChange?.(next);
4597
+ };
4598
+ /**
4599
+ * What a link to `target` is given, beside what it renders. With no
4600
+ * `getPageHref` it's a button; a link that's `off` goes nowhere.
4601
+ */
4602
+ const linkTo = (target, off) => ({
4603
+ href: off || !getPageHref ? undefined : getPageHref(target),
4604
+ role: getPageHref ? undefined : 'button',
4605
+ // A link that's off is disabled too, and the parts drop its clicks.
4606
+ onClick: () => choose(target),
4607
+ onKeyDown: getPageHref
4608
+ ? undefined
4609
+ : (e) => {
4610
+ if (off || (e.key !== 'Enter' && e.key !== ' '))
4611
+ return;
4612
+ e.preventDefault();
4613
+ choose(target);
4614
+ },
4615
+ });
4616
+ const atStart = disabled || page <= 1;
4617
+ const atEnd = disabled || page >= pages;
4618
+ content = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(PaginationPrevious, { disabled: atStart, ...linkTo(page - 1, atStart), children: previousLabel }), jsxRuntime.jsx(PaginationNext, { disabled: atEnd, ...linkTo(page + 1, atEnd), children: nextLabel }), jsxRuntime.jsx(PaginationList, { children: pageItems(pages, page, whole(siblingCount, 1), whole(boundaryCount, 1)).map(item => typeof item === 'number' ? (jsxRuntime.jsx(PaginationLink, { "aria-label": getPageLabel ? getPageLabel(item) : `Page ${item}`, active: item === page, disabled: disabled, ...linkTo(item, Boolean(disabled)), children: item }, item)) : (jsxRuntime.jsx(PaginationEllipsis, {}, item))) })] }));
4222
4619
  }
4223
- // Handle web components vs CSS-based icons
4224
- if (finalLibrary === 'ion') {
4225
- // For Ionicons, handle variant in the name
4226
- let ionName = finalName;
4227
- if (finalVariant === 'outline') {
4228
- ionName = `${finalName}-outline`;
4620
+ return (jsxRuntime.jsx("nav", { className: paginationClasses, role: "navigation", "aria-label": "pagination", ...rest, children: content }));
4621
+ };
4622
+ /**
4623
+ * Container for page links and ellipsis.
4624
+ *
4625
+ * @function
4626
+ * @param {PaginationListProps} props - Props for the PaginationList component.
4627
+ * @returns {JSX.Element} The rendered pagination list.
4628
+ */
4629
+ const PaginationList = ({ className, textColor, bgColor, children, ...props }) => {
4630
+ const { bulmaHelperClasses, rest } = useBulmaClasses({
4631
+ color: textColor,
4632
+ backgroundColor: bgColor,
4633
+ ...props,
4634
+ });
4635
+ return (jsxRuntime.jsx("ul", { className: classNames(usePrefixedClassNames('pagination-list'), bulmaHelperClasses, className), ...rest, children: children }));
4636
+ };
4637
+ /**
4638
+ * Page number or navigation link.
4639
+ *
4640
+ * @function
4641
+ * @param {PaginationLinkProps} props - Props for the PaginationLink component.
4642
+ * @returns {JSX.Element} The rendered pagination link.
4643
+ */
4644
+ const PaginationLink = ({ className, textColor, bgColor, active, disabled, onClick, children, ...props }) => {
4645
+ const { bulmaHelperClasses, rest } = useBulmaClasses({
4646
+ color: textColor,
4647
+ backgroundColor: bgColor,
4648
+ ...props,
4649
+ });
4650
+ const handleClick = (e) => {
4651
+ if (disabled) {
4652
+ e.preventDefault();
4653
+ e.stopPropagation();
4654
+ return;
4229
4655
  }
4230
- else if (finalVariant === 'sharp') {
4231
- ionName = `${finalName}-sharp`;
4656
+ if (onClick) {
4657
+ onClick(e);
4232
4658
  }
4233
- return (jsxRuntime.jsx("span", { className: iconContainerClasses, ...a11yProps, style: style, ...rest, children: jsxRuntime.jsx("ion-icon", { name: ionName }) }));
4234
- }
4235
- // Legacy CSS-based icons
4236
- const iClasses = getIconClasses(finalLibrary, finalName, finalVariant, finalFeatures);
4237
- // Material Icons and Material Symbols use text content, not CSS classes for the icon name
4238
- if (finalLibrary === 'material-icons' ||
4239
- finalLibrary === 'material-symbols') {
4240
- return (jsxRuntime.jsx("span", { className: iconContainerClasses, ...a11yProps, style: style, ...rest, children: jsxRuntime.jsx("i", { className: iClasses, children: finalName }) }));
4241
- }
4242
- return (jsxRuntime.jsx("span", { className: iconContainerClasses, ...a11yProps, style: style, ...rest, children: jsxRuntime.jsx("i", { className: iClasses }) }));
4659
+ };
4660
+ return (jsxRuntime.jsx("li", { children: jsxRuntime.jsx("a", { className: classNames(usePrefixedClassNames('pagination-link', {
4661
+ 'is-current': active,
4662
+ 'is-disabled': disabled,
4663
+ }), bulmaHelperClasses, className), "aria-current": active ? 'page' : undefined, "aria-disabled": disabled, tabIndex: disabled ? -1 : 0, onClick: handleClick, ...rest, children: children }) }));
4243
4664
  };
4665
+ /**
4666
+ * Ellipsis separator.
4667
+ *
4668
+ * @function
4669
+ * @param {React.LiHTMLAttributes<HTMLLIElement>} props - Standard li props.
4670
+ * @returns {JSX.Element} The rendered ellipsis.
4671
+ */
4672
+ const PaginationEllipsis = props => (jsxRuntime.jsx("li", { children: jsxRuntime.jsx("span", { className: usePrefixedClassNames('pagination-ellipsis'), ...props, children: "\u2026" }) }));
4673
+ const Pagination = withSubComponents(PaginationComponent, {
4674
+ Link: PaginationLink,
4675
+ List: PaginationList,
4676
+ Ellipsis: PaginationEllipsis,
4677
+ Previous: PaginationPrevious,
4678
+ Next: PaginationNext,
4679
+ }, 'Pagination');
4244
4680
 
4245
4681
  /**
4246
4682
  * The `Panel` component implements Bulma's versatile panel block for React.
@@ -4587,204 +5023,75 @@ const Tab = ({ index, disabled, icon, iconLibrary, iconVariant, iconSize = 'smal
4587
5023
  if (target) {
4588
5024
  e.preventDefault();
4589
5025
  target.focus();
4590
- }
4591
- };
4592
- const handleFocus = (e) => {
4593
- onFocus?.(e);
4594
- ctx?.setFocusedTab(index);
4595
- };
4596
- const handleBlur = (e) => {
4597
- onBlur?.(e);
4598
- if (!tabListOf(e.currentTarget).contains(e.relatedTarget)) {
4599
- ctx?.setFocusedTab(null);
4600
- }
4601
- };
4602
- return (jsxRuntime.jsx("li", { className: classNames(activeClass, className) || undefined, role: "tab", id: tabId, "aria-selected": isActive, "aria-controls": ctx?.panelId(index), "aria-disabled": disabled || undefined, tabIndex: tabIndex, onKeyDown: handleKeyDown, onFocus: handleFocus, onBlur: handleBlur, ...props, children: jsxRuntime.jsxs("a", { ref: anchorRef, onClick: handleClick, "aria-disabled": disabled || undefined, children: [icon && (jsxRuntime.jsx(Icon, { name: icon, library: iconLibrary, variant: iconVariant, size: iconSize, features: iconFeatures })), children && jsxRuntime.jsx("span", { children: children })] }) }));
4603
- };
4604
- /**
4605
- * Each tab; accepts `active`, `onClick`, etc.
4606
- *
4607
- * @function
4608
- * @param {TabItemProps} props - Props for the TabItem component.
4609
- * @returns {JSX.Element} The rendered tab item.
4610
- * @deprecated Use `Tabs.Tab` with an `index` prop instead.
4611
- */
4612
- const TabItem = ({ active, className, children, onClick, ...props }) => (jsxRuntime.jsx("li", { className: classNames({ [usePrefixedClassNames('is-active')]: active }, className) ||
4613
- undefined, onClick: onClick, ...props, children: children }));
4614
- /**
4615
- * Container for tab content panels. No custom props beyond `children` and standard `<div>` HTML attributes. Applies the `.tabs-content` class.
4616
- *
4617
- * @function
4618
- * @param {TabsContentProps} props - Props for the TabsContent component.
4619
- * @returns {JSX.Element} The rendered tabs content wrapper.
4620
- */
4621
- const TabsContentComponent = ({ className, children, ...props }) => {
4622
- const contentClass = usePrefixedClassNames('tabs-content');
4623
- return (jsxRuntime.jsx("div", { className: classNames(contentClass, className), ...props, children: children }));
4624
- };
4625
- /**
4626
- * Individual content panel. Shows/hides based on active tab from context.
4627
- *
4628
- * @function
4629
- * @param {TabContentItemProps} props - Props for the TabContentItem component.
4630
- * @returns {JSX.Element} The rendered tab content panel.
4631
- */
4632
- const TabContentItem = ({ index, className, children, id, ...props }) => {
4633
- const ctx = useTabsContext();
4634
- const isActive = ctx ? ctx.activeTab === index : false;
4635
- const itemClass = usePrefixedClassNames('tabs-content-item', {
4636
- 'is-active': isActive,
4637
- });
4638
- const panelId = id ?? (ctx ? `${ctx.baseId}-panel-${index}` : undefined);
4639
- const registerPanel = ctx?.registerPanel;
4640
- React.useEffect(() => {
4641
- if (!registerPanel || panelId === undefined)
4642
- return undefined;
4643
- return registerPanel({ index, id: panelId });
4644
- }, [registerPanel, index, panelId]);
4645
- return (jsxRuntime.jsx("div", { className: classNames(itemClass, className), role: "tabpanel", id: panelId, "aria-labelledby": ctx?.tabId(index), "aria-hidden": !isActive, ...props, children: children }));
4646
- };
4647
- // ---------------------------------------------------------------------------
4648
- // Static property attachment
4649
- // ---------------------------------------------------------------------------
4650
- const TabsContent = withSubComponents(TabsContentComponent, {
4651
- Item: TabContentItem,
4652
- });
4653
- const Tabs = withSubComponents(TabsComponent, {
4654
- List: TabList,
4655
- Tab,
4656
- Item: TabItem,
4657
- Content: TabsContent,
4658
- }, 'Tabs');
4659
-
4660
- const validButtonColors = [...validColors, 'text', 'ghost'];
4661
- /**
4662
- * The `Button` component provides a flexible and highly customizable button for your Bulma React UI.
4663
- *
4664
- * @function
4665
- * @param {ButtonProps} props - Props for the Button component.
4666
- * @param {React.Ref} ref - Forwarded ref to the element `as` renders.
4667
- * @returns {JSX.Element} The rendered button, anchor, or custom element.
4668
- * @see {@link https://bulma.io/documentation/elements/button/ | Bulma Button documentation}
4669
- */
4670
- const Button = React.forwardRef(function Button(props, ref) {
4671
- const { color, size, isLight, isRounded, isLoading, isStatic, isFullwidth, isFullWidth, isOutlined, isInverted, isFocused, isActive, isHovered, isDisabled, className, children, textColor, bgColor, as: Component = 'button', href, onClick, target, rel, ...bulmaProps } = props;
4672
- const { bulmaHelperClasses, rest } = useBulmaClasses({
4673
- color: textColor,
4674
- backgroundColor: bgColor,
4675
- ...bulmaProps,
4676
- });
4677
- // Generate Bulma classes with prefix
4678
- const bulmaClasses = usePrefixedClassNames('button', {
4679
- [`is-${color}`]: color &&
4680
- validButtonColors.includes(color),
4681
- [`is-${size}`]: size,
4682
- 'is-outlined': isOutlined,
4683
- 'is-light': isLight,
4684
- 'is-loading': isLoading,
4685
- 'is-static': isStatic,
4686
- 'is-disabled': isDisabled,
4687
- 'is-rounded': isRounded,
4688
- 'is-hovered': isHovered,
4689
- 'is-focused': isFocused,
4690
- 'is-active': isActive,
4691
- 'is-inverted': isInverted,
4692
- 'is-fullwidth': isFullwidth ?? isFullWidth,
4693
- });
4694
- // Combine prefixed Bulma classes with unprefixed user className and prefixed helper classes
4695
- const buttonClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
4696
- if (Component !== 'button') {
4697
- // Strip the form-control attributes a link-like element cannot carry, so a
4698
- // JavaScript consumer or a spread object doesn't put them on the DOM. The
4699
- // types no longer allow them through; this is the runtime backstop.
4700
- //
4701
- // Only for INTRINSIC tags. A custom component (a router Link, ...) owns its
4702
- // prop contract, and `ComponentPropsWithoutRef<T>` promises the caller that
4703
- // its props reach it. Stripping names by their spelling alone broke that
4704
- // promise silently: `<Button as={Custom} name="x" />` type-checked while
4705
- // `Custom` never received the `name` it requires.
4706
- //
4707
- // `autoFocus` and `type` are deliberately NOT stripped: both are valid on
4708
- // targets this list was filtering them from. `autoFocus` is a global
4709
- // attribute (React decides per element whether to honour it, which is not
4710
- // ours to pre-empt), and `type` on an anchor is the MIME hint that
4711
- // `ButtonProps<'a'>` now types it as.
4712
- //
4713
- // The types already answer this for a TypeScript caller: `ButtonProps<T>`
4714
- // derives from `ComponentPropsWithoutRef<T>`, so only what the target
4715
- // accepts can be passed. What is left is a runtime backstop for JavaScript
4716
- // consumers and spread objects.
4717
- //
4718
- // Keep it to the two cases where a stray attribute does something. The
4719
- // submit-overrides belong to a submit control and never make sense on the
4720
- // anchor path. `disabled` is the one that is VISIBLE: Bulma styles
4721
- // `.button[disabled]` (background, border, shadow, opacity), so letting it
4722
- // through to a `<span>` greys the element out — main stripped it, and not
4723
- // stripping it would be a silent visual change on a types-only release.
4724
- //
4725
- // Everything else (`name`, `value`, `form`) is inert where it does not
4726
- // belong, and enumerating which tags own it is what kept going wrong:
4727
- // first all nine names on every non-button tag (so `<Button as="input"
4728
- // name="query" value="Search">` submitted nothing), then a form-control
4729
- // allowlist that still lost `formAction` on an input and `value` on an
4730
- // option. Those now ride on the types instead.
4731
- //
4732
- // A custom component is exempt entirely — it owns its prop contract, and
4733
- // `ComponentPropsWithoutRef<T>` promises the caller its props arrive.
4734
- //
4735
- // The elements that own `disabled`, per the HTML spec. A closed set, unlike
4736
- // the open-ended question of which tag owns which attribute.
4737
- const SUBMIT_OVERRIDES = [
4738
- 'formAction',
4739
- 'formEncType',
4740
- 'formMethod',
4741
- 'formNoValidate',
4742
- 'formTarget',
4743
- ];
4744
- const OWNS_DISABLED = [
4745
- 'button',
4746
- 'fieldset',
4747
- 'input',
4748
- 'optgroup',
4749
- 'option',
4750
- 'select',
4751
- 'textarea',
4752
- ];
4753
- // Two independent filters. They are not exclusive — an `<a>` owns neither
4754
- // the submit-overrides nor `disabled`, so it takes both — and deriving them
4755
- // from one destructure, or from one if/else, is what broke each of them in
4756
- // turn: first the overrides came off every intrinsic (an `as="input"` lost
4757
- // `formAction`), then scoping them to the anchor let `disabled` back onto
4758
- // it.
4759
- const forwardedRest = { ...rest };
4760
- if (typeof Component === 'string' && !isCustomElement(Component)) {
4761
- // Meaningless anywhere but a submit control, and an anchor is not one.
4762
- if (Component === 'a') {
4763
- for (const key of SUBMIT_OVERRIDES)
4764
- delete forwardedRest[key];
4765
- }
4766
- // Visible via Bulma's `.button[disabled]`, so withheld from any element
4767
- // that does not own it.
4768
- if (!OWNS_DISABLED.includes(Component))
4769
- delete forwardedRest.disabled;
4770
- }
4771
- return (jsxRuntime.jsx(Component, { ref: ref, className: buttonClasses, href: href, target: target, rel: rel, "aria-disabled": isDisabled, tabIndex: isDisabled ? -1 : undefined, onClick: isDisabled
4772
- ? // Guarded, because this handler is installed into whatever `as`
4773
- // renders and a custom component's `onClick` need not be a DOM
4774
- // event handler at all. One declaring `onClick: (v: string) =>
4775
- // void` calls this with a string, and an unguarded
4776
- // `e.preventDefault()` throws `is not a function`. Blocking the
4777
- // real event is the point; a foreign payload is simply not ours
4778
- // to act on.
4779
- (e) => {
4780
- if (typeof e?.preventDefault === 'function')
4781
- e.preventDefault();
4782
- }
4783
- : onClick, ...forwardedRest, children: children }));
4784
- }
4785
- return (jsxRuntime.jsx("button", { ref: ref, className: buttonClasses, disabled: isDisabled, onClick: onClick, ...rest, children: children }));
5026
+ }
5027
+ };
5028
+ const handleFocus = (e) => {
5029
+ onFocus?.(e);
5030
+ ctx?.setFocusedTab(index);
5031
+ };
5032
+ const handleBlur = (e) => {
5033
+ onBlur?.(e);
5034
+ if (!tabListOf(e.currentTarget).contains(e.relatedTarget)) {
5035
+ ctx?.setFocusedTab(null);
5036
+ }
5037
+ };
5038
+ return (jsxRuntime.jsx("li", { className: classNames(activeClass, className) || undefined, role: "tab", id: tabId, "aria-selected": isActive, "aria-controls": ctx?.panelId(index), "aria-disabled": disabled || undefined, tabIndex: tabIndex, onKeyDown: handleKeyDown, onFocus: handleFocus, onBlur: handleBlur, ...props, children: jsxRuntime.jsxs("a", { ref: anchorRef, onClick: handleClick, "aria-disabled": disabled || undefined, children: [icon && (jsxRuntime.jsx(Icon, { name: icon, library: iconLibrary, variant: iconVariant, size: iconSize, features: iconFeatures })), children && jsxRuntime.jsx("span", { children: children })] }) }));
5039
+ };
5040
+ /**
5041
+ * Each tab; accepts `active`, `onClick`, etc.
5042
+ *
5043
+ * @function
5044
+ * @param {TabItemProps} props - Props for the TabItem component.
5045
+ * @returns {JSX.Element} The rendered tab item.
5046
+ * @deprecated Use `Tabs.Tab` with an `index` prop instead.
5047
+ */
5048
+ const TabItem = ({ active, className, children, onClick, ...props }) => (jsxRuntime.jsx("li", { className: classNames({ [usePrefixedClassNames('is-active')]: active }, className) ||
5049
+ undefined, onClick: onClick, ...props, children: children }));
5050
+ /**
5051
+ * Container for tab content panels. No custom props beyond `children` and standard `<div>` HTML attributes. Applies the `.tabs-content` class.
5052
+ *
5053
+ * @function
5054
+ * @param {TabsContentProps} props - Props for the TabsContent component.
5055
+ * @returns {JSX.Element} The rendered tabs content wrapper.
5056
+ */
5057
+ const TabsContentComponent = ({ className, children, ...props }) => {
5058
+ const contentClass = usePrefixedClassNames('tabs-content');
5059
+ return (jsxRuntime.jsx("div", { className: classNames(contentClass, className), ...props, children: children }));
5060
+ };
5061
+ /**
5062
+ * Individual content panel. Shows/hides based on active tab from context.
5063
+ *
5064
+ * @function
5065
+ * @param {TabContentItemProps} props - Props for the TabContentItem component.
5066
+ * @returns {JSX.Element} The rendered tab content panel.
5067
+ */
5068
+ const TabContentItem = ({ index, className, children, id, ...props }) => {
5069
+ const ctx = useTabsContext();
5070
+ const isActive = ctx ? ctx.activeTab === index : false;
5071
+ const itemClass = usePrefixedClassNames('tabs-content-item', {
5072
+ 'is-active': isActive,
5073
+ });
5074
+ const panelId = id ?? (ctx ? `${ctx.baseId}-panel-${index}` : undefined);
5075
+ const registerPanel = ctx?.registerPanel;
5076
+ React.useEffect(() => {
5077
+ if (!registerPanel || panelId === undefined)
5078
+ return undefined;
5079
+ return registerPanel({ index, id: panelId });
5080
+ }, [registerPanel, index, panelId]);
5081
+ return (jsxRuntime.jsx("div", { className: classNames(itemClass, className), role: "tabpanel", id: panelId, "aria-labelledby": ctx?.tabId(index), "aria-hidden": !isActive, ...props, children: children }));
5082
+ };
5083
+ // ---------------------------------------------------------------------------
5084
+ // Static property attachment
5085
+ // ---------------------------------------------------------------------------
5086
+ const TabsContent = withSubComponents(TabsContentComponent, {
5087
+ Item: TabContentItem,
4786
5088
  });
4787
- Button.displayName = 'Button';
5089
+ const Tabs = withSubComponents(TabsComponent, {
5090
+ List: TabList,
5091
+ Tab,
5092
+ Item: TabItem,
5093
+ Content: TabsContent,
5094
+ }, 'Tabs');
4788
5095
 
4789
5096
  // INTERNAL: deliberately not exported from src/index.ts.
4790
5097
  /**
@@ -8753,129 +9060,6 @@ const Box = ({ className, textColor, color, bgColor, hasShadow = true, children,
8753
9060
  return (jsxRuntime.jsx("div", { className: boxClasses, style: mergeBulmaStyles(bulmaHelperStyles, style), ...rest, children: children }));
8754
9061
  };
8755
9062
 
8756
- /**
8757
- * The `LinkButton` component renders a `<button>` that visually looks like text or a link.
8758
- *
8759
- * @function
8760
- * @param {LinkButtonProps} props - Props for the LinkButton component.
8761
- * @param {React.Ref} ref - Forwarded ref to the element `as` renders.
8762
- * @returns {JSX.Element} The rendered link-styled button element.
8763
- *
8764
- * @example
8765
- * // Text variant (default)
8766
- * <LinkButton onClick={handleClick}>Click me</LinkButton>
8767
- *
8768
- * @example
8769
- * // Underline variant with color
8770
- * <LinkButton variant="underline" color="primary">Learn more</LinkButton>
8771
- */
8772
- const LinkButton = React.forwardRef(function LinkButton({ variant = 'text', color, className, ...props }, ref) {
8773
- const buttonColor = variant === 'underline' ? 'text' : variant;
8774
- const prefixedClasses = usePrefixedClassNames('link-button', color && `link-button-${color}`, variant === 'underline' && 'link-button-underline');
8775
- return (jsxRuntime.jsx(Button
8776
- // LinkButton is polymorphic through to Button, which resolves the element
8777
- // from `as`. TS cannot infer that through the spread below.
8778
- , {
8779
- // LinkButton is polymorphic through to Button, which resolves the element
8780
- // from `as`. TS cannot infer that through the spread below.
8781
- ref: ref, color: buttonColor, className: classNames(prefixedClasses, className), ...props }));
8782
- });
8783
- LinkButton.displayName = 'LinkButton';
8784
-
8785
- /**
8786
- * The `ToggleButton` component renders a button that stays pressed until it's pressed again, and says whether it's on through `aria-pressed`.
8787
- *
8788
- * It's always a `<button type="button">`, so it never submits a form and
8789
- * carries no form value: a setting saved with a form is a `Checkbox` or a
8790
- * `Switch`. A set of toggles that each turn on and off on their own goes in
8791
- * `Buttons hasAddons` with `role="group"` and an `aria-label`. A set where only
8792
- * one can be on is a radio group rather than a row of these: use
8793
- * `SegmentedControl`, or `Radios` for a choice that's sent with a form.
8794
- *
8795
- * @function
8796
- * @param {ToggleButtonProps} props - Props for the ToggleButton component.
8797
- * @param {React.Ref<HTMLButtonElement>} ref - Forwarded ref to the `<button>`.
8798
- * @returns {JSX.Element} The rendered toggle button.
8799
- * @see {@link https://www.w3.org/WAI/ARIA/apg/patterns/button/ | APG button pattern}
8800
- *
8801
- * @example
8802
- * <ToggleButton onPressedChange={setMuted}>Mute</ToggleButton>
8803
- *
8804
- * @example
8805
- * <Buttons hasAddons role="group" aria-label="Text style">
8806
- * <ToggleButton aria-label="Bold" pressed={bold} onPressedChange={setBold}>
8807
- * <Icon name="bold" />
8808
- * </ToggleButton>
8809
- * </Buttons>
8810
- */
8811
- const ToggleButton = React.forwardRef(function ToggleButton({ pressed: controlledPressed, defaultPressed = false, onPressedChange, color = 'link', isLoading, 'aria-busy': ariaBusy, 'aria-disabled': ariaDisabled, className, onClick, ...props }, ref) {
8812
- const isControlled = controlledPressed !== undefined;
8813
- const [internalPressed, setInternalPressed] = React.useState(defaultPressed);
8814
- const pressed = controlledPressed ?? internalPressed;
8815
- // Prefixed like Button's own classes, which a hand-written
8816
- // `className="is-selected"` could not be under ConfigProvider classPrefix.
8817
- // Bulma lifts `is-selected` above its neighbours in `.buttons.has-addons`.
8818
- const selectedClass = usePrefixedClassNames({ 'is-selected': pressed });
8819
- // The caller's onClick runs first, so a `preventDefault()` there vetoes
8820
- // the toggle, as it does in Radix, MUI and Zag. Enter and Space reach here
8821
- // through the button's own click, and a disabled button gets no click.
8822
- //
8823
- // A loading one gets the click but drops it. Bulma's spinner only stops a
8824
- // pointer, so without this a key flipped a toggle the mouse couldn't.
8825
- // `disabled` would stop both, but it takes focus away mid-save.
8826
- const handleClick = (event) => {
8827
- if (isLoading)
8828
- return;
8829
- onClick?.(event);
8830
- if (event.defaultPrevented)
8831
- return;
8832
- const next = !pressed;
8833
- onPressedChange?.(next);
8834
- if (!isControlled)
8835
- setInternalPressed(next);
8836
- };
8837
- // The props are spread first, so what a toggle depends on wins over
8838
- // anything a JavaScript caller or a spread object passes along: it stays
8839
- // a button, never submits, reports the state it's in, and says it's busy
8840
- // and won't respond while it's loading. A caller's own `aria-busy` or
8841
- // `aria-disabled` stays when it isn't loading.
8842
- return (jsxRuntime.jsx(Button, { ...props, ref: ref, as: "button", type: "button", isLoading: isLoading, "aria-busy": isLoading || ariaBusy, "aria-disabled": isLoading || ariaDisabled, "aria-pressed": pressed, color: pressed ? color : undefined, className: classNames(selectedClass, className), onClick: handleClick }));
8843
- });
8844
- ToggleButton.displayName = 'ToggleButton';
8845
-
8846
- const validButtonsSizes = ['small', 'medium', 'large'];
8847
- /**
8848
- * The `Buttons` component lets you group multiple `Button` elements together with Bulma's spacing, alignment, and add-on features.
8849
- *
8850
- * @function
8851
- * @param {ButtonsProps} props - Props for the Buttons component.
8852
- * @returns {JSX.Element} The rendered group of buttons.
8853
- * @see {@link https://bulma.io/documentation/elements/button/#group | Bulma Button Group documentation}
8854
- */
8855
- const ButtonsComponent = ({ className, textColor, color, bgColor, isCentered, isRight, hasAddons, size, children, ...props }) => {
8856
- const buttonsClasses = usePrefixedClassNames('buttons', {
8857
- 'is-centered': isCentered,
8858
- 'is-right': isRight,
8859
- 'has-addons': hasAddons,
8860
- [`are-${size}`]: size && validButtonsSizes.includes(size),
8861
- });
8862
- /**
8863
- * Generates Bulma helper classes and separates out remaining props.
8864
- */
8865
- const { bulmaHelperClasses, rest } = useBulmaClasses({
8866
- color: textColor ?? color,
8867
- backgroundColor: bgColor,
8868
- ...props,
8869
- });
8870
- const combinedClasses = classNames(buttonsClasses, className, bulmaHelperClasses);
8871
- return (jsxRuntime.jsx("div", { className: combinedClasses, ...rest, children: children }));
8872
- };
8873
- const Buttons = withSubComponents(ButtonsComponent, {
8874
- Button,
8875
- LinkButton,
8876
- ToggleButton,
8877
- }, 'Buttons');
8878
-
8879
9063
  /**
8880
9064
  * The `Code` component renders a styled `<code>` element with Bulma helper class integration.
8881
9065
  *
@@ -9904,27 +10088,6 @@ const OrderedListComponent = ({ className, textColor, bgColor, children, ...prop
9904
10088
  };
9905
10089
  const OrderedList = withSubComponents(OrderedListComponent, { Item: ListItem }, 'OrderedList');
9906
10090
 
9907
- /**
9908
- * The `Paragraph` component renders a styled `<p>` element with Bulma helper class integration.
9909
- *
9910
- * @function
9911
- * @param {ParagraphProps} props - Props for the Paragraph component.
9912
- * @returns {JSX.Element} The rendered paragraph element.
9913
- */
9914
- const Paragraph = ({ className, textColor, bgColor, children, ...props }) => {
9915
- /**
9916
- * Generates Bulma helper classes and separates out remaining props.
9917
- */
9918
- const { bulmaHelperClasses, rest } = useBulmaClasses({
9919
- color: textColor,
9920
- backgroundColor: bgColor,
9921
- ...props,
9922
- });
9923
- const bulmaClasses = usePrefixedClassNames();
9924
- const paragraphClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
9925
- return (jsxRuntime.jsx("p", { className: paragraphClasses || undefined, ...rest, children: children }));
9926
- };
9927
-
9928
10091
  /**
9929
10092
  * The `Pre` component renders a styled `<pre>` element with Bulma helper class integration.
9930
10093
  *
@@ -10337,43 +10500,6 @@ isMultiline, size, children, ...props }) => {
10337
10500
  };
10338
10501
  const Tags = withSubComponents(TagsComponent, { Tag }, 'Tags');
10339
10502
 
10340
- const validTitleSizes = ['1', '2', '3', '4', '5', '6'];
10341
- const validTitleElements = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p'];
10342
- /**
10343
- * The `Title` component renders a Bulma-styled title (heading), supporting sizes `1`-`6`, spacing, and rendering as any heading or paragraph element (`h1`-`h6`, `p`).
10344
- *
10345
- * @function
10346
- * @param {TitleProps} props - Props for the Title component.
10347
- * @returns {JSX.Element} The rendered title element.
10348
- * @see {@link https://bulma.io/documentation/elements/title/ | Bulma Title documentation}
10349
- */
10350
- const Title = ({ className, size, isSpaced, as = 'h1', hasSkeleton, textColor, bgColor, children, ...props }) => {
10351
- /**
10352
- * Generates Bulma helper classes and separates out remaining props.
10353
- */
10354
- const { bulmaHelperClasses, rest } = useBulmaClasses({
10355
- color: textColor,
10356
- backgroundColor: bgColor,
10357
- ...props,
10358
- });
10359
- // Validate 'as' prop at runtime
10360
- const element = validTitleElements.includes(as) ? as : 'h1';
10361
- // Validate 'size' prop at runtime (numeric sizes normalize to strings)
10362
- const normalized = size == null ? undefined : String(size);
10363
- const validSize = normalized && validTitleSizes.includes(normalized)
10364
- ? normalized
10365
- : undefined;
10366
- const bulmaClasses = usePrefixedClassNames('title', {
10367
- [`is-${validSize}`]: validSize,
10368
- 'is-spaced': isSpaced,
10369
- 'has-skeleton': hasSkeleton,
10370
- });
10371
- const titleClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
10372
- // Determine the tag based on 'element' and 'validSize'
10373
- const Tag = element === 'p' ? 'p' : validSize ? `h${validSize}` : element;
10374
- return (jsxRuntime.jsx(Tag, { className: titleClasses, ...rest, children: children }));
10375
- };
10376
-
10377
10503
  /**
10378
10504
  * The `UnorderedList` component renders a styled unordered list (`<ul>`) element with Bulma helper class integration.
10379
10505
  *
@@ -10568,7 +10694,8 @@ const FieldLabelForContext = React.createContext({});
10568
10694
  * renders with, if it has one. An unlabeled Field passes both through
10569
10695
  * together, so a control in the inner Field of a horizontal layout can tell
10570
10696
  * the outer label names it, and point at that label by its id, as a range
10571
- * Slider's thumbs (#981) and Autocomplete's suggestion list (#998) do. The
10697
+ * Slider's thumbs (#981) and Autocomplete's and Taginput's suggestion lists
10698
+ * (#998, #1082) do. The
10572
10699
  * `htmlFor` is only ever compared with a control's own id, never adopted, so
10573
10700
  * it hands out no id for a control. It keeps the generated id after the
10574
10701
  * label drops a `for` nothing took (#1004), which no control's id matches
@@ -10823,9 +10950,9 @@ Control.displayName = 'Control';
10823
10950
  * `id`, or give the horizontal `Field` a `label` prop, which renders this
10824
10951
  * column and its `<label>` for you. A `<label>` you put here names the
10825
10952
  * control through its `htmlFor` alone: a range `Slider`'s thumbs and an
10826
- * `Autocomplete`'s suggestion list point `aria-labelledby` at a `Field`'s
10827
- * label only when its `label` prop renders it and it names the control.
10828
- * Where it would not name the control on its own, as in an inner `Field`,
10953
+ * `Autocomplete`'s or `Taginput`'s suggestion list point `aria-labelledby` at
10954
+ * a `Field`'s label only when its `label` prop renders it and it names the
10955
+ * control. Where it would not name the control on its own, as in an inner `Field`,
10829
10956
  * pass `labelProps={{ htmlFor, id }}` with a matching `id` on the control.
10830
10957
  *
10831
10958
  * @function
@@ -12834,7 +12961,7 @@ Autocomplete.displayName = 'Autocomplete';
12834
12961
  */
12835
12962
  const Taginput = React.forwardRef(({
12836
12963
  // Field props
12837
- label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName, value: controlledValue, defaultValue = [], data = [], placeholder, field = 'label', allowNew = true, allowDuplicates = false, openOnFocus = false, removeOnKeys = true, confirmKeys = ['Enter', ','], closable = true, attached = false, maxTags, maxlength, disabled = false, readonly = false, rounded = false, ellipsis = false, hasCounter = true, onPasteSeparators = [','], beforeAdding, createTag, keepFirst = false, keepOpen = true, loading = false, ariaCloseLabel, icon, iconLibrary: iconLibraryProp, iconVariant, iconFeatures, color, tagColor, size, onChange, onAdd, onRemove, onTyping, tagTemplate, name, form, id, className, ...props }, ref) => {
12964
+ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName, value: controlledValue, defaultValue = [], data: dataProp, placeholder, field = 'label', allowNew = true, allowDuplicates = false, openOnFocus = false, removeOnKeys = true, confirmKeys = ['Enter', ','], closable = true, attached = false, maxTags, maxlength, disabled = false, readonly = false, rounded = false, ellipsis = false, hasCounter = true, onPasteSeparators = [','], beforeAdding, createTag, keepFirst = false, keepOpen = true, loading = false, ariaCloseLabel, icon, iconLibrary: iconLibraryProp, iconVariant, iconFeatures, color, tagColor, size, onChange, onAdd, onRemove, onTyping, tagTemplate, name, form, id, className, ...props }, ref) => {
12838
12965
  const insideField = useInsideField();
12839
12966
  const insideControl = useInsideControl();
12840
12967
  const ownField = rendersOwnField('Taginput', {
@@ -12857,17 +12984,26 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
12857
12984
  const [inputValue, setInputValue] = React.useState('');
12858
12985
  const [isActive, setIsActive] = React.useState(false);
12859
12986
  const [highlightedIndex, setHighlightedIndex] = React.useState(-1);
12987
+ const listboxId = `${React.useId()}-listbox`;
12988
+ // With `data` the input offers suggestions, so it's a combobox, even
12989
+ // while none match. Without it there's never a list, so it stays a plain
12990
+ // text field.
12991
+ const hasSuggestions = dataProp !== undefined;
12992
+ const data = dataProp ?? [];
12860
12993
  // Use controlled or internal tags
12861
12994
  const tags = controlledValue !== undefined ? controlledValue : internalTags;
12862
12995
  const isMaxReached = maxTags !== undefined && tags.length >= maxTags;
12863
12996
  // At the tag limit the text input is not rendered, so there is nothing to
12864
- // wire a label to, its own or a surrounding Field's.
12865
- const { controlId, fieldLabelProps, labelled } = useAutoLabelId({
12997
+ // wire a label to, its own or a surrounding Field's. The suggestion list
12998
+ // points `aria-labelledby` at the label that names the input, so with
12999
+ // suggestions it needs that label's id.
13000
+ const { controlId, fieldLabelProps, labelled, labelId: listLabelId, } = useAutoLabelId({
12866
13001
  label,
12867
13002
  id,
12868
13003
  labelProps,
12869
13004
  rendersLabel: ownField && !isMaxReached,
12870
13005
  hasInput: !isMaxReached,
13006
+ needsLabelId: hasSuggestions,
12871
13007
  });
12872
13008
  // Get display value from tag
12873
13009
  const getDisplayValue = (tag) => {
@@ -12887,6 +13023,7 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
12887
13023
  const hasInput = inputValue || (openOnFocus && isActive);
12888
13024
  return matchesInput && notAlreadyAdded && hasInput;
12889
13025
  });
13026
+ const listOpen = isActive && filteredData.length > 0;
12890
13027
  // keepFirst: auto-highlight first item when dropdown opens
12891
13028
  React.useEffect(() => {
12892
13029
  if (keepFirst && isActive && filteredData.length > 0) {
@@ -13127,15 +13264,35 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
13127
13264
  [`is-${messageColor}`]: !!messageColor,
13128
13265
  });
13129
13266
  const messageEl = message ? jsxRuntime.jsx("p", { className: helpClass, children: message }) : null;
13267
+ // The combobox controls its listbox and points at the highlighted option,
13268
+ // the way Autocomplete's does. The listbox takes its name from the label
13269
+ // that names the combobox, the one this renders or a surrounding Field's,
13270
+ // and keeps a fallback name when no label names it or the one that does
13271
+ // has no id to point at. While no suggestion matches there's no listbox,
13272
+ // so the combobox is collapsed and points at nothing. It skips a
13273
+ // highlighted suggestion that's blank once trimmed, since that one can't
13274
+ // become a tag, and the option it points at is the one marked selected.
13275
+ const optionId = (index) => `${listboxId}-${index}`;
13276
+ const activeOptionId = listOpen && filteredData[highlightedIndex]?.trim()
13277
+ ? optionId(highlightedIndex)
13278
+ : undefined;
13279
+ const comboboxProps = {
13280
+ role: 'combobox',
13281
+ 'aria-expanded': listOpen,
13282
+ 'aria-controls': listOpen ? listboxId : undefined,
13283
+ 'aria-activedescendant': activeOptionId,
13284
+ 'aria-haspopup': 'listbox',
13285
+ 'aria-autocomplete': 'list',
13286
+ };
13130
13287
  const taginputElement = (jsxRuntime.jsxs("div", { ref: containerRef, className: combinedClasses, onClick: handleContainerClick, ...rest, children: [jsxRuntime.jsxs("div", { className: containerClasses, children: [icon && (jsxRuntime.jsx(Icon, { name: icon, library: resolvedLibrary, variant: iconVariant, features: iconFeatures, containerClassName: pcn('icon', 'is-left') })), jsxRuntime.jsxs("div", { className: tagsClasses, children: [tags.map((tag, index) => {
13131
13288
  const displayVal = getDisplayValue(tag);
13132
13289
  return (jsxRuntime.jsxs("span", { className: tagClasses, children: [tagTemplate ? (tagTemplate(tag)) : ellipsis ? (jsxRuntime.jsx("span", { title: displayVal, children: displayVal })) : (displayVal), closable && !disabled && !readonly && (jsxRuntime.jsx("button", { type: "button", className: pcn('delete', 'is-small'), onClick: e => {
13133
13290
  e.stopPropagation();
13134
13291
  removeTag(index);
13135
13292
  }, "aria-label": ariaCloseLabel || `Remove ${displayVal}` }))] }, index));
13136
- }), !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, "aria-label": labelled ? undefined : 'Add tag' }))] }), loading && (jsxRuntime.jsx("span", { className: iconRightClass, children: jsxRuntime.jsx("span", { className: loaderClass }) }))] }), isActive && filteredData.length > 0 && (jsxRuntime.jsx("div", { className: dropdownMenuClasses, children: jsxRuntime.jsx("div", { ref: dropdownRef, className: dropdownContentClasses, role: "listbox", children: filteredData.map((item, index) => (jsxRuntime.jsx("a", { className: pcn('dropdown-item', {
13293
+ }), !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', {
13137
13294
  'is-active': index === highlightedIndex,
13138
- }), onClick: () => addTag(item), onMouseEnter: () => setHighlightedIndex(index), role: "option", "aria-selected": index === highlightedIndex, children: item }, index))) }) })), showCounter && jsxRuntime.jsx("small", { className: counterClasses, children: counterText }), name &&
13295
+ }), 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 &&
13139
13296
  tags.map((tag, i) => (jsxRuntime.jsx("input", { type: "hidden", name: name, value: getDisplayValue(tag), form: form }, `tag-input-${i}`)))] }));
13140
13297
  if (ownField) {
13141
13298
  return (jsxRuntime.jsxs(Field, { label: label, labelSize: labelSize, labelProps: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [taginputElement, messageEl] }));
@@ -19463,6 +19620,11 @@ exports.Dropdown = Dropdown;
19463
19620
  exports.DropdownDivider = DropdownDivider;
19464
19621
  exports.DropdownItem = DropdownItem;
19465
19622
  exports.Emphasis = Emphasis;
19623
+ exports.EmptyState = EmptyState;
19624
+ exports.EmptyStateActions = EmptyStateActions;
19625
+ exports.EmptyStateDescription = EmptyStateDescription;
19626
+ exports.EmptyStateIndicator = EmptyStateIndicator;
19627
+ exports.EmptyStateTitle = EmptyStateTitle;
19466
19628
  exports.Field = Field;
19467
19629
  exports.FieldBody = FieldBody;
19468
19630
  exports.FieldLabel = FieldLabel;