@allxsmith/bestax-bulma 5.15.0 → 5.15.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs.js CHANGED
@@ -1189,6 +1189,21 @@ const ColumnsComponent = ({ className, textColor, color: _fieldColor, bgColor, i
1189
1189
  };
1190
1190
  const Columns = withSubComponents(ColumnsComponent, { Column }, 'Columns');
1191
1191
 
1192
+ /**
1193
+ * Whether an `as` target is a custom element rather than a built-in tag.
1194
+ *
1195
+ * HTML requires a custom element's name to contain a hyphen, and no built-in
1196
+ * element name has one, so this is exact rather than a heuristic. It matters
1197
+ * because a custom element is an intrinsic STRING — `typeof as === 'string'` is
1198
+ * true — while its props are whatever a consumer declared through
1199
+ * `React.JSX.IntrinsicElements`, the way this package declares `<ion-icon>`.
1200
+ * A runtime backstop that filters built-in attributes must leave those alone,
1201
+ * or it strips props the derived type just promised to forward.
1202
+ */
1203
+ function isCustomElement(as) {
1204
+ return typeof as === 'string' && as.includes('-');
1205
+ }
1206
+
1192
1207
  const avatarColors = [
1193
1208
  'primary',
1194
1209
  'link',
@@ -1257,6 +1272,7 @@ function DefaultAvatarIcon() {
1257
1272
  *
1258
1273
  * @function
1259
1274
  * @param {AvatarProps} props - Props for the Avatar component.
1275
+ * @param {React.Ref} ref - Forwarded ref to the element `as` renders.
1260
1276
  * @returns {JSX.Element} The rendered avatar element.
1261
1277
  *
1262
1278
  * @example
@@ -1264,7 +1280,8 @@ function DefaultAvatarIcon() {
1264
1280
  * @example
1265
1281
  * <Avatar name="Grace Hopper" />
1266
1282
  */
1267
- const Avatar = ({ className, src, alt, name, initials, icon, size, shape = 'circle', color, as, href, target, rel, imageProps, style, ...props }) => {
1283
+ const Avatar = React.forwardRef(function Avatar(avatarProps, ref) {
1284
+ const { className, src, alt, name, initials, icon, size, shape = 'circle', color, as, href, target, rel, imageProps, style, ...props } = avatarProps;
1268
1285
  // Tracks the src that failed to load. A src change clears the latch during
1269
1286
  // render (React's "reset state when props change" pattern) so a previously
1270
1287
  // failed src is retried when switched back to. The img is additionally
@@ -1316,10 +1333,10 @@ const Avatar = ({ className, src, alt, name, initials, icon, size, shape = 'circ
1316
1333
  // decorative (aria-hidden) and it could render nameless.
1317
1334
  const isInteractive = Tag === 'a' ||
1318
1335
  Tag === 'button' ||
1319
- (typeof Tag !== 'string' && href != null);
1336
+ ((typeof Tag !== 'string' || isCustomElement(Tag)) && href != null);
1320
1337
  // Only forward link attributes when rendering an anchor or a custom (non-DOM)
1321
1338
  // component; a plain `as="div"` must not receive a stray `href`/`target`/`rel`.
1322
- const isLinkLike = Tag === 'a' || typeof Tag !== 'string';
1339
+ const isLinkLike = Tag === 'a' || typeof Tag !== 'string' || isCustomElement(Tag);
1323
1340
  const linkProps = isLinkLike ? { href, target, rel } : {};
1324
1341
  // alt coalesces with ?? (not ||) so an explicit alt="" survives as the
1325
1342
  // standard decorative marker instead of being overridden by name.
@@ -1342,11 +1359,11 @@ const Avatar = ({ className, src, alt, name, initials, icon, size, shape = 'circ
1342
1359
  // A clickable avatar inside a form must not submit it; default the native
1343
1360
  // button type (an explicit type passed through rest still wins).
1344
1361
  const buttonTypeProps = Tag === 'button' ? { type: 'button' } : {};
1345
- return (jsxRuntime.jsxs(Tag, { className: combinedClasses, style: { ...sizeStyle, ...style }, ...buttonTypeProps, ...linkProps, ...a11yProps, ...rest, children: [showImage && (jsxRuntime.jsx("img", { ...imageProps, ref: imgRef, src: src, alt: accessibleName ?? '', onError: e => {
1362
+ return (jsxRuntime.jsxs(Tag, { ref: ref, className: combinedClasses, style: { ...sizeStyle, ...style }, ...buttonTypeProps, ...linkProps, ...a11yProps, ...rest, children: [showImage && (jsxRuntime.jsx("img", { ...imageProps, ref: imgRef, src: src, alt: accessibleName ?? '', onError: e => {
1346
1363
  imageProps?.onError?.(e);
1347
1364
  setErroredSrc(src);
1348
1365
  } }, src)), showInitials && (jsxRuntime.jsx("span", { className: initialsClass, children: resolvedInitials })), showIcon && icon, showDefaultIcon && jsxRuntime.jsx(DefaultAvatarIcon, {})] }));
1349
- };
1366
+ });
1350
1367
  Avatar.displayName = 'Avatar';
1351
1368
 
1352
1369
  /**
@@ -1985,6 +2002,33 @@ const DropdownComponent = React.forwardRef(function DropdownComponent({ label, c
1985
2002
  const dropdownClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
1986
2003
  return (jsxRuntime.jsxs("div", { className: dropdownClasses, ref: setRefs, id: id, "data-testid": "dropdown-root", ...rest, children: [jsxRuntime.jsx("div", { className: usePrefixedClassNames('dropdown-trigger'), children: jsxRuntime.jsxs("button", { ref: triggerRef, className: buttonClass, "aria-haspopup": "true", "aria-controls": id ? `${id}-menu` : undefined, "aria-expanded": active, onClick: handleToggle, onKeyDown: handleTriggerKeyDown, disabled: disabled, type: "button", children: [jsxRuntime.jsx("span", { children: label }), jsxRuntime.jsx("span", { className: usePrefixedClassNames('icon', 'is-small'), "aria-hidden": "true", children: jsxRuntime.jsx("i", { className: "fas fa-angle-down" }) })] }) }), jsxRuntime.jsx("div", { className: classNames(usePrefixedClassNames('dropdown-menu'), menuClassName), id: id ? `${id}-menu` : undefined, role: "menu", "data-testid": "dropdown-menu", onKeyDown: handleMenuKeyDown, children: jsxRuntime.jsx("div", { className: usePrefixedClassNames('dropdown-content'), onClick: handleMenuClick, tabIndex: -1, children: children }) })] }));
1987
2004
  });
2005
+ /**
2006
+ * The anchor-only attributes, withheld from a `<div>` or a `<button>`.
2007
+ *
2008
+ * Mirrors `LINK_ATTRS` in `bestax-migrate/src/sources/_shared/polymorphic.ts`,
2009
+ * which drops the same set when a migration lands one on a non-anchor. `rel` is
2010
+ * absent from both on purpose: React declares it on `HTMLAttributes<T>` — every
2011
+ * element — so withholding it would diverge from React's own typing, the same
2012
+ * call #641 recorded for `Navbar.Link`.
2013
+ */
2014
+ const LINK_ONLY_ATTRS = [
2015
+ 'href',
2016
+ 'target',
2017
+ 'download',
2018
+ 'hrefLang',
2019
+ 'ping',
2020
+ 'referrerPolicy',
2021
+ 'media',
2022
+ ];
2023
+ /** `props` without any attribute only an anchor can carry. */
2024
+ function omitLinkAttrs(props) {
2025
+ const out = {};
2026
+ for (const [key, value] of Object.entries(props)) {
2027
+ if (!LINK_ONLY_ATTRS.includes(key))
2028
+ out[key] = value;
2029
+ }
2030
+ return out;
2031
+ }
1988
2032
  /**
1989
2033
  * Bulma Dropdown item.
1990
2034
  *
@@ -1992,12 +2036,34 @@ const DropdownComponent = React.forwardRef(function DropdownComponent({ label, c
1992
2036
  * @param {DropdownItemProps} props - Props for the DropdownItem component.
1993
2037
  * @returns {JSX.Element} The rendered dropdown item.
1994
2038
  */
1995
- const DropdownItem = ({ children, active, className, as: Component = 'a', ...props }) => {
2039
+ const DropdownItem = ((itemProps) => {
2040
+ const { children, active, className, as: Component = 'a', ...props } = itemProps;
1996
2041
  const { bulmaHelperClasses, rest } = useBulmaClasses(props);
2042
+ // The anchor's attributes reach an anchor and nothing else — `<div href>` and
2043
+ // `<button target>` are invalid HTML. The same rule Menu applies, for the same
2044
+ // reason: the type now derives these from `as`, and deriving them must not
2045
+ // quietly widen where they land. The type stops a direct caller; this stops a
2046
+ // plain-JS one, a loose `{...props}` spread, and the genericity a wrapping HOC
2047
+ // erases.
2048
+ //
2049
+ // The whole link set, not `href` alone. Menu strips only `href` because its
2050
+ // props never gained the rest; here they all arrive together under `as="a"`,
2051
+ // and `bestax-migrate` already removes exactly this set (`LINK_ATTRS`) when it
2052
+ // migrates onto a non-anchor — so stripping less would leave the codemod
2053
+ // stricter than the component it migrates to.
2054
+ //
2055
+ // Menu's condition also admits a custom component and a custom element, which
2056
+ // own their prop contracts. `as` is closed to three intrinsic tags here, so
2057
+ // neither can arrive and the anchor test is the whole rule.
2058
+ const forwarded = Component === 'a' ? rest : omitLinkAttrs(rest);
1997
2059
  return (jsxRuntime.jsx(Component, { className: classNames(usePrefixedClassNames('dropdown-item', {
1998
2060
  'is-active': active,
1999
- }), bulmaHelperClasses, className), tabIndex: 0, role: "menuitem", "data-testid": "dropdown-item", ...rest, children: children }));
2000
- };
2061
+ }), bulmaHelperClasses, className), tabIndex: 0, role: "menuitem", "data-testid": "dropdown-item", ...forwarded, ...(Component === 'button'
2062
+ ? {
2063
+ type: forwarded.type ?? 'button',
2064
+ }
2065
+ : {}), children: children }));
2066
+ });
2001
2067
  /**
2002
2068
  * Bulma Dropdown divider.
2003
2069
  *
@@ -2059,13 +2125,23 @@ const MenuList = ({ className, children, ...props }) => {
2059
2125
  *
2060
2126
  * @function
2061
2127
  * @param {MenuItemProps} props - Props for the MenuItem component.
2128
+ * @param {React.Ref} ref - Forwarded ref to the inner element `as` renders.
2062
2129
  * @returns {JSX.Element} The rendered menu item.
2063
2130
  */
2064
- const MenuItem = ({ className, children, active, href, as: Component = 'a', 'data-testid': testId, ...rest }) => {
2065
- const { bulmaHelperClasses, rest: bulmaRest } = useBulmaClasses(rest);
2131
+ const MenuItem = React.forwardRef(function MenuItem(itemProps, ref) {
2132
+ const { className, children, active, as: Component = 'a', 'data-testid': testId, style, id, title, role, tabIndex, ...rest } = itemProps;
2133
+ const { bulmaHelperClasses, rest: forwarded } = useBulmaClasses(rest);
2134
+ // `href` reaches an anchor or a custom component (which owns its own prop
2135
+ // contract), but not another intrinsic tag — `<span href>` is invalid HTML.
2136
+ // The same rule Avatar applies through `isLinkLike` and Button through its
2137
+ // intrinsic-only strip. Before #641 `href` was an own prop re-applied only
2138
+ // for `as="a"`; deriving it from `as` must not quietly widen that.
2139
+ const isLinkLike = Component === 'a' ||
2140
+ typeof Component !== 'string' ||
2141
+ isCustomElement(Component);
2142
+ const { href: _href, ...withoutHref } = forwarded;
2143
+ const linkProps = isLinkLike ? forwarded : withoutHref;
2066
2144
  const itemClass = classNames({ [usePrefixedClassNames('is-active')]: active }, bulmaHelperClasses);
2067
- // Standard <li> props
2068
- const { style, id, title, role, tabIndex, ...linkProps } = bulmaRest;
2069
2145
  // Split children into label and nested MenuList(s)
2070
2146
  const labelChildren = [];
2071
2147
  const nestedMenuLists = [];
@@ -2077,15 +2153,9 @@ const MenuItem = ({ className, children, active, href, as: Component = 'a', 'dat
2077
2153
  labelChildren.push(child);
2078
2154
  }
2079
2155
  });
2080
- // href/to should go to the link component
2081
- if (Component === 'a' && href) {
2082
- linkProps.href = href;
2083
- }
2084
- if (Object.prototype.hasOwnProperty.call(rest, 'to')) {
2085
- linkProps.to = rest.to;
2086
- }
2087
- return (jsxRuntime.jsxs("li", { className: className, "data-testid": testId, style: style, id: id, title: title, role: role, tabIndex: tabIndex, children: [jsxRuntime.jsx(Component, { className: itemClass, ...linkProps, children: labelChildren }), nestedMenuLists] }));
2088
- };
2156
+ 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, ...linkProps, children: labelChildren }), nestedMenuLists] }));
2157
+ });
2158
+ MenuItem.displayName = 'MenuItem';
2089
2159
  // Attach static subcomponents
2090
2160
  const Menu = withSubComponents(MenuComponent, {
2091
2161
  Label: MenuLabel,
@@ -2597,18 +2667,21 @@ const NavbarBrand = ({ className, children, textColor, ...props }) => {
2597
2667
  *
2598
2668
  * @function
2599
2669
  * @param {NavbarItemProps} props - Props for the NavbarItem component.
2670
+ * @param {React.Ref} ref - Forwarded ref to the element `as` renders.
2600
2671
  * @returns {JSX.Element} The rendered item.
2601
2672
  */
2602
- const NavbarItem = ({ className, as: Component = 'a', active, textColor, bgColor, children, ...props }) => {
2673
+ const NavbarItem = React.forwardRef(function NavbarItem(itemProps, ref) {
2674
+ const { className, as: Component = 'a', active, textColor, bgColor, children, ...props } = itemProps;
2603
2675
  const { bulmaHelperClasses, rest } = useBulmaClasses({
2604
2676
  color: textColor,
2605
2677
  backgroundColor: bgColor,
2606
2678
  ...props,
2607
2679
  });
2608
- return (jsxRuntime.jsx(Component, { className: classNames(usePrefixedClassNames('navbar-item', {
2680
+ return (jsxRuntime.jsx(Component, { ref: ref, className: classNames(usePrefixedClassNames('navbar-item', {
2609
2681
  'is-active': active,
2610
2682
  }), bulmaHelperClasses, className), ...rest, children: children }));
2611
- };
2683
+ });
2684
+ NavbarItem.displayName = 'NavbarItem';
2612
2685
  /**
2613
2686
  * Responsive menu toggle (mobile)
2614
2687
  *
@@ -2672,10 +2745,11 @@ const NavbarEnd = ({ className, children, ...props }) => {
2672
2745
  *
2673
2746
  * @function
2674
2747
  * @param {NavbarLinkProps} props - Props for the NavbarLink component.
2675
- * @param {React.Ref<HTMLAnchorElement | HTMLButtonElement>} ref - Forwarded ref to the rendered link or button element.
2748
+ * @param {React.Ref} ref - Forwarded ref to the element `as` renders.
2676
2749
  * @returns {JSX.Element} The rendered navbar link.
2677
2750
  */
2678
- const NavbarLink = React.forwardRef(function NavbarLink({ className, as: Component = 'a', arrowless, textColor, bgColor, children, ...props }, ref) {
2751
+ const NavbarLink = React.forwardRef(function NavbarLink(linkProps, ref) {
2752
+ const { className, as: Component = 'a', arrowless, textColor, bgColor, children, ...props } = linkProps;
2679
2753
  const { bulmaHelperClasses, rest } = useBulmaClasses({
2680
2754
  color: textColor,
2681
2755
  backgroundColor: bgColor,
@@ -2684,8 +2758,16 @@ const NavbarLink = React.forwardRef(function NavbarLink({ className, as: Compone
2684
2758
  const dropdownContext = React.useContext(NavbarDropdownContext);
2685
2759
  const hasHref = rest.href !== undefined;
2686
2760
  const isNativeInteractive = Component === 'button' || hasHref;
2761
+ // Guarded like Button's disabled blocker: these run on whatever `as` renders,
2762
+ // and a custom target's callback need not take a DOM event. One declaring
2763
+ // `onKeyDown: (k: {key: string}) => void` reaches `e.preventDefault()` and
2764
+ // throws. Composing the caller's handler is unconditional; acting on the
2765
+ // event is not ours to do when the payload is foreign.
2766
+ const isDomEvent = (e) => typeof e?.preventDefault === 'function';
2687
2767
  const handleKeyDown = (e) => {
2688
2768
  rest.onKeyDown?.(e);
2769
+ if (!isDomEvent(e))
2770
+ return;
2689
2771
  if (!dropdownContext || e.defaultPrevented)
2690
2772
  return;
2691
2773
  if (e.key === 'Enter' || e.key === ' ') {
@@ -2704,6 +2786,8 @@ const NavbarLink = React.forwardRef(function NavbarLink({ className, as: Compone
2704
2786
  // click semantics (navigation / the caller's handler).
2705
2787
  const handleClick = (e) => {
2706
2788
  rest.onClick?.(e);
2789
+ if (!isDomEvent(e))
2790
+ return;
2707
2791
  if (!dropdownContext || isNativeInteractive || e.defaultPrevented)
2708
2792
  return;
2709
2793
  dropdownContext.toggle();
@@ -3095,10 +3179,11 @@ containerClassName, children, ...restProps }) => {
3095
3179
  // component, …) in place of a class-based glyph. Library/variant/features don't apply.
3096
3180
  return (jsxRuntime.jsx("span", { className: iconContainerClasses, "aria-label": ariaLabel, style: style, ...rest, children: children }));
3097
3181
  }
3098
- // `name` is guaranteed once `children` is absent (`IconProps` is a discriminated union of
3099
- // the two, and `IconChildrenProps['children']` excludes `undefined` so `children={undefined}`
3100
- // can't slip past into this branch) — the cast only matters for legacy callers that bypass
3101
- // the type and rely solely on the deprecated `icon` prop below.
3182
+ // `name` is guaranteed once `children` is absent UNLESS the caller is on the deprecated
3183
+ // `icon` path, which `IconDeprecatedProps` declares with `name?: undefined`.
3184
+ // (`IconChildrenProps['children']` excludes `undefined`, so `children={undefined}` can't
3185
+ // slip past into this branch.) The cast covers that third member, and the `!name && icon`
3186
+ // fallback below is what fills it in.
3102
3187
  let finalName = name;
3103
3188
  if (!name && icon) {
3104
3189
  // If icon prop is provided instead of name, try to parse it
@@ -3122,9 +3207,10 @@ containerClassName, children, ...restProps }) => {
3122
3207
  // library is 'fa'), so `name` behaves identically with or without the prefix.
3123
3208
  finalName = stripRedundantLibraryPrefix(finalName, finalLibrary);
3124
3209
  if (!finalName) {
3125
- // No glyph to name. Unreachable through the public type, but a plain-JS caller (or one
3126
- // that casts) can land here, and building a class off an absent name produced a bogus
3127
- // `fa-undefined` glyph. Render the bare container instead, matching the `children` branch.
3210
+ // No glyph to name. Reachable through the public type only as `icon=""`, and otherwise
3211
+ // by a plain-JS caller (or one that casts). Building a class off an absent name produced
3212
+ // a bogus `fa-undefined` glyph; render the bare container instead, matching the
3213
+ // `children` branch.
3128
3214
  return (jsxRuntime.jsx("span", { className: iconContainerClasses, "aria-label": ariaLabel, style: style, ...rest }));
3129
3215
  }
3130
3216
  // Backward compatibility: if libraryFeatures is provided, parse it for variant and features
@@ -4693,15 +4779,16 @@ const validButtonColors = [...validColors, 'text', 'ghost'];
4693
4779
  *
4694
4780
  * @function
4695
4781
  * @param {ButtonProps} props - Props for the Button component.
4696
- * @param {React.Ref<HTMLButtonElement | HTMLAnchorElement>} ref - Forwarded ref to the rendered button or anchor element.
4697
- * @returns {JSX.Element} The rendered button or anchor element.
4782
+ * @param {React.Ref} ref - Forwarded ref to the element `as` renders.
4783
+ * @returns {JSX.Element} The rendered button, anchor, or custom element.
4698
4784
  * @see {@link https://bulma.io/documentation/elements/button/ | Bulma Button documentation}
4699
4785
  */
4700
- const Button = React.forwardRef(function Button({ 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, ...props }, ref) {
4786
+ const Button = React.forwardRef(function Button(props, ref) {
4787
+ 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;
4701
4788
  const { bulmaHelperClasses, rest } = useBulmaClasses({
4702
4789
  color: textColor,
4703
4790
  backgroundColor: bgColor,
4704
- ...props,
4791
+ ...bulmaProps,
4705
4792
  });
4706
4793
  // Generate Bulma classes with prefix
4707
4794
  const bulmaClasses = usePrefixedClassNames('button', {
@@ -4723,13 +4810,93 @@ const Button = React.forwardRef(function Button({ color, size, isLight, isRounde
4723
4810
  // Combine prefixed Bulma classes with unprefixed user className and prefixed helper classes
4724
4811
  const buttonClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
4725
4812
  if (Component !== 'button') {
4726
- // Create anchor-specific props by excluding button-specific ones, so
4727
- // native/custom link-like elements (an <a>, a router Link, ...) don't
4728
- // receive button-only HTML attributes.
4729
- const { type: _type, disabled: _disabled, form: _form, formAction: _formAction, formEncType: _formEncType, formMethod: _formMethod, formNoValidate: _formNoValidate, formTarget: _formTarget, name: _name, value: _value, autoFocus: _autoFocus, ...anchorRest } = rest;
4813
+ // Strip the form-control attributes a link-like element cannot carry, so a
4814
+ // JavaScript consumer or a spread object doesn't put them on the DOM. The
4815
+ // types no longer allow them through; this is the runtime backstop.
4816
+ //
4817
+ // Only for INTRINSIC tags. A custom component (a router Link, ...) owns its
4818
+ // prop contract, and `ComponentPropsWithoutRef<T>` promises the caller that
4819
+ // its props reach it. Stripping names by their spelling alone broke that
4820
+ // promise silently: `<Button as={Custom} name="x" />` type-checked while
4821
+ // `Custom` never received the `name` it requires.
4822
+ //
4823
+ // `autoFocus` and `type` are deliberately NOT stripped: both are valid on
4824
+ // targets this list was filtering them from. `autoFocus` is a global
4825
+ // attribute (React decides per element whether to honour it, which is not
4826
+ // ours to pre-empt), and `type` on an anchor is the MIME hint that
4827
+ // `ButtonProps<'a'>` now types it as.
4828
+ //
4829
+ // The types already answer this for a TypeScript caller: `ButtonProps<T>`
4830
+ // derives from `ComponentPropsWithoutRef<T>`, so only what the target
4831
+ // accepts can be passed. What is left is a runtime backstop for JavaScript
4832
+ // consumers and spread objects.
4833
+ //
4834
+ // Keep it to the two cases where a stray attribute does something. The
4835
+ // submit-overrides belong to a submit control and never make sense on the
4836
+ // anchor path. `disabled` is the one that is VISIBLE: Bulma styles
4837
+ // `.button[disabled]` (background, border, shadow, opacity), so letting it
4838
+ // through to a `<span>` greys the element out — main stripped it, and not
4839
+ // stripping it would be a silent visual change on a types-only release.
4840
+ //
4841
+ // Everything else (`name`, `value`, `form`) is inert where it does not
4842
+ // belong, and enumerating which tags own it is what kept going wrong:
4843
+ // first all nine names on every non-button tag (so `<Button as="input"
4844
+ // name="query" value="Search">` submitted nothing), then a form-control
4845
+ // allowlist that still lost `formAction` on an input and `value` on an
4846
+ // option. Those now ride on the types instead.
4847
+ //
4848
+ // A custom component is exempt entirely — it owns its prop contract, and
4849
+ // `ComponentPropsWithoutRef<T>` promises the caller its props arrive.
4850
+ //
4851
+ // The elements that own `disabled`, per the HTML spec. A closed set, unlike
4852
+ // the open-ended question of which tag owns which attribute.
4853
+ const SUBMIT_OVERRIDES = [
4854
+ 'formAction',
4855
+ 'formEncType',
4856
+ 'formMethod',
4857
+ 'formNoValidate',
4858
+ 'formTarget',
4859
+ ];
4860
+ const OWNS_DISABLED = [
4861
+ 'button',
4862
+ 'fieldset',
4863
+ 'input',
4864
+ 'optgroup',
4865
+ 'option',
4866
+ 'select',
4867
+ 'textarea',
4868
+ ];
4869
+ // Two independent filters. They are not exclusive — an `<a>` owns neither
4870
+ // the submit-overrides nor `disabled`, so it takes both — and deriving them
4871
+ // from one destructure, or from one if/else, is what broke each of them in
4872
+ // turn: first the overrides came off every intrinsic (an `as="input"` lost
4873
+ // `formAction`), then scoping them to the anchor let `disabled` back onto
4874
+ // it.
4875
+ const forwardedRest = { ...rest };
4876
+ if (typeof Component === 'string' && !isCustomElement(Component)) {
4877
+ // Meaningless anywhere but a submit control, and an anchor is not one.
4878
+ if (Component === 'a') {
4879
+ for (const key of SUBMIT_OVERRIDES)
4880
+ delete forwardedRest[key];
4881
+ }
4882
+ // Visible via Bulma's `.button[disabled]`, so withheld from any element
4883
+ // that does not own it.
4884
+ if (!OWNS_DISABLED.includes(Component))
4885
+ delete forwardedRest.disabled;
4886
+ }
4730
4887
  return (jsxRuntime.jsx(Component, { ref: ref, className: buttonClasses, href: href, target: target, rel: rel, "aria-disabled": isDisabled, tabIndex: isDisabled ? -1 : undefined, onClick: isDisabled
4731
- ? (e) => e.preventDefault()
4732
- : onClick, ...anchorRest, children: children }));
4888
+ ? // Guarded, because this handler is installed into whatever `as`
4889
+ // renders and a custom component's `onClick` need not be a DOM
4890
+ // event handler at all. One declaring `onClick: (v: string) =>
4891
+ // void` calls this with a string, and an unguarded
4892
+ // `e.preventDefault()` throws `is not a function`. Blocking the
4893
+ // real event is the point; a foreign payload is simply not ours
4894
+ // to act on.
4895
+ (e) => {
4896
+ if (typeof e?.preventDefault === 'function')
4897
+ e.preventDefault();
4898
+ }
4899
+ : onClick, ...forwardedRest, children: children }));
4733
4900
  }
4734
4901
  return (jsxRuntime.jsx("button", { ref: ref, className: buttonClasses, disabled: isDisabled, onClick: onClick, ...rest, children: children }));
4735
4902
  });
@@ -5118,7 +5285,8 @@ function usePrefersReducedMotion() {
5118
5285
  * ))}
5119
5286
  * </Reveal>
5120
5287
  */
5121
- const Reveal = ({ animation = 'fade-up', delay = 0, duration = 600, threshold = 0.15, once = true, as: Component = 'div', cascade = false, cascadeInterval = 80, className, style, children, ...props }) => {
5288
+ function RevealImpl(revealProps) {
5289
+ const { animation = 'fade-up', delay = 0, duration = 600, threshold = 0.15, once = true, as: Component = 'div', cascade = false, cascadeInterval = 80, className, style, children, ...props } = revealProps;
5122
5290
  const { bulmaHelperClasses, rest } = useBulmaClasses(props);
5123
5291
  const classPrefix = useClassPrefix();
5124
5292
  const prefersReducedMotion = usePrefersReducedMotion();
@@ -5195,12 +5363,16 @@ const Reveal = ({ animation = 'fade-up', delay = 0, duration = 600, threshold =
5195
5363
  return (jsxRuntime.jsx("span", { className: itemAnimationClasses, style: itemStyle, children: child }, index));
5196
5364
  })
5197
5365
  : children;
5198
- // Scroll observation needs a real DOM node. Intrinsic tags ('div',
5199
- // 'section', ...) always accept a ref directly. A custom component passed
5200
- // via `as` (Section, Card, ...) is rendered by this library as a plain
5201
- // React.FC with no ref forwarding, so the ref (and the animation classes
5202
- // that depend on it) go on a plain wrapper `div` instead, with `Component`
5203
- // rendered inside it.
5366
+ // Scroll observation needs a DOM node this component OWNS — the observer and
5367
+ // the animation classes both key off it. An intrinsic tag is that node, so
5368
+ // the ref goes straight on it. A component passed via `as` is not: whatever
5369
+ // it renders is its own business, and it may render several elements or
5370
+ // none. So it gets wrapped in an observed `div`.
5371
+ //
5372
+ // Not a statement about ref forwarding. Several components in this library
5373
+ // forward refs (#641 added four more), and that changes nothing here — a
5374
+ // forwarded ref would still point at the target's node rather than at one
5375
+ // this component controls.
5204
5376
  if (typeof Component === 'string') {
5205
5377
  // A plain createElement call sidesteps the combinatorial JSX prop types
5206
5378
  // for `ref` across every possible intrinsic tag `as` could be.
@@ -5212,7 +5384,9 @@ const Reveal = ({ animation = 'fade-up', delay = 0, duration = 600, threshold =
5212
5384
  }, content);
5213
5385
  }
5214
5386
  return (jsxRuntime.jsx("div", { ref: setNode, className: combinedClasses, style: wrapperStyle, children: jsxRuntime.jsx(Component, { ...rest, children: content }) }));
5215
- };
5387
+ }
5388
+ const Reveal = RevealImpl;
5389
+ Reveal.displayName = 'Reveal';
5216
5390
 
5217
5391
  /**
5218
5392
  * The `Block` component renders a simple container with Bulma's `.block` class, adding vertical margin between sections of content.
@@ -5265,7 +5439,7 @@ const Box = ({ className, textColor, color, bgColor, hasShadow = true, children,
5265
5439
  *
5266
5440
  * @function
5267
5441
  * @param {LinkButtonProps} props - Props for the LinkButton component.
5268
- * @param {React.Ref<HTMLButtonElement | HTMLAnchorElement>} ref - Forwarded ref to the rendered button or anchor element.
5442
+ * @param {React.Ref} ref - Forwarded ref to the element `as` renders.
5269
5443
  * @returns {JSX.Element} The rendered link-styled button element.
5270
5444
  *
5271
5445
  * @example
@@ -5279,7 +5453,13 @@ const Box = ({ className, textColor, color, bgColor, hasShadow = true, children,
5279
5453
  const LinkButton = React.forwardRef(function LinkButton({ variant = 'text', color, className, ...props }, ref) {
5280
5454
  const buttonColor = variant === 'underline' ? 'text' : variant;
5281
5455
  const prefixedClasses = usePrefixedClassNames('link-button', color && `link-button-${color}`, variant === 'underline' && 'link-button-underline');
5282
- return (jsxRuntime.jsx(Button, { ref: ref, color: buttonColor, className: classNames(prefixedClasses, className), ...props }));
5456
+ return (jsxRuntime.jsx(Button
5457
+ // LinkButton is polymorphic through to Button, which resolves the element
5458
+ // from `as`. TS cannot infer that through the spread below.
5459
+ , {
5460
+ // LinkButton is polymorphic through to Button, which resolves the element
5461
+ // from `as`. TS cannot infer that through the spread below.
5462
+ ref: ref, color: buttonColor, className: classNames(prefixedClasses, className), ...props }));
5283
5463
  });
5284
5464
  LinkButton.displayName = 'LinkButton';
5285
5465
 
@@ -5473,15 +5653,49 @@ const Figure = withSubComponents(FigureComponent, {
5473
5653
  }, 'Figure');
5474
5654
 
5475
5655
  /**
5476
- * Distinguishes an `IconProps` object from a plain custom node (an inline SVG, a `react-icons`
5477
- * component, …) passed to a slot that accepts either.
5656
+ * Distinguishes an `IconProps` object from a plain custom node (an inline SVG, a
5657
+ * `react-icons` component, …) in a slot that accepts either — `Control`'s
5658
+ * `iconLeft`/`iconRight`, `IconText`'s `iconProps`.
5659
+ *
5660
+ * One copy, imported by both consumers: two hand-maintained copies, one per
5661
+ * consumer, is how the deprecated `icon` path came to crash both (#663).
5662
+ *
5663
+ * Its own module rather than `Icon.tsx` because `src/index.ts` wildcard-exports
5664
+ * that file, and everything published there is public forever. `polymorphic.ts`
5665
+ * makes the same call for `isCustomElement`: an internal discriminator is not
5666
+ * something to support for the life of the package.
5667
+ *
5668
+ * It tests each member's VALUE, not merely its key, and only after everything
5669
+ * React owns is out of the way. `'icon' in value` was enough
5670
+ * to claim a Font Awesome `IconDefinition` — `{ prefix, iconName, icon: [w, h, …,
5671
+ * path] }` — whose `icon` is a path array rather than a class string. That object
5672
+ * is not a renderable node either, so the honest outcome is the one this slot has
5673
+ * always had for it: the node branch, and React's own "Objects are not valid as a
5674
+ * React child". Claiming it here instead rendered a blank container that still
5675
+ * reserved layout.
5676
+ *
5677
+ * A member added to `IconProps` needs a line here, and a missed one is a crash
5678
+ * rather than a fallthrough. That is the cost of a slot taking either a props
5679
+ * object or a node; keeping the guard in one place is what keeps the cost to a
5680
+ * single line.
5478
5681
  */
5479
- function isIconProps$1(value) {
5480
- return (typeof value === 'object' &&
5481
- value !== null &&
5482
- !React.isValidElement(value) &&
5483
- ('name' in value || 'children' in value));
5682
+ function isIconProps(value) {
5683
+ if (typeof value !== 'object' || value === null)
5684
+ return false;
5685
+ // Anything React owns. `isValidElement` covers elements only; a PORTAL is a
5686
+ // valid `ReactNode` that fails it and carries a `children` key, so the member
5687
+ // test below claimed it and `Control` spread it into `<Icon>` as props —
5688
+ // rendering the portal's child locally instead of in its container. Every
5689
+ // React internal carries `$$typeof`, so testing for it covers lazy, context
5690
+ // and whatever React adds next, rather than portals alone.
5691
+ if (React.isValidElement(value) || '$$typeof' in value)
5692
+ return false;
5693
+ const candidate = value;
5694
+ return (typeof candidate.name === 'string' ||
5695
+ typeof candidate.icon === 'string' ||
5696
+ 'children' in candidate);
5484
5697
  }
5698
+
5485
5699
  /**
5486
5700
  * The `IconText` component provides a Bulma-styled horizontal arrangement of one or more `Icon` components and optional text.
5487
5701
  *
@@ -5502,8 +5716,8 @@ const IconTextComponent = ({ className, textColor, color, bgColor, iconProps, ch
5502
5716
  const bulmaClasses = usePrefixedClassNames('icon-text');
5503
5717
  const iconTextClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
5504
5718
  return (jsxRuntime.jsx("span", { className: iconTextClasses, ...rest, children: items ? (items.map((item, index) => (jsxRuntime.jsxs(React.Fragment, { children: [item.iconProps &&
5505
- (isIconProps$1(item.iconProps) ? (jsxRuntime.jsx(Icon, { ...item.iconProps })) : (jsxRuntime.jsx(Icon, { children: item.iconProps }))), item.text && jsxRuntime.jsx("span", { children: item.text })] }, index)))) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [iconProps &&
5506
- (isIconProps$1(iconProps) ? (jsxRuntime.jsx(Icon, { ...iconProps })) : (jsxRuntime.jsx(Icon, { children: iconProps }))), children && jsxRuntime.jsx("span", { children: children })] })) }));
5719
+ (isIconProps(item.iconProps) ? (jsxRuntime.jsx(Icon, { ...item.iconProps })) : (jsxRuntime.jsx(Icon, { children: item.iconProps }))), item.text && jsxRuntime.jsx("span", { children: item.text })] }, index)))) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [iconProps &&
5720
+ (isIconProps(iconProps) ? (jsxRuntime.jsx(Icon, { ...iconProps })) : (jsxRuntime.jsx(Icon, { children: iconProps }))), children && jsxRuntime.jsx("span", { children: children })] })) }));
5507
5721
  };
5508
5722
  const IconText = withSubComponents(IconTextComponent, { Icon }, 'IconText');
5509
5723
 
@@ -5551,10 +5765,12 @@ const Image = ({ as, className, textColor, color, bgColor, size, isRounded, isRe
5551
5765
  *
5552
5766
  * @function
5553
5767
  * @param {LinkProps} props - Props for the Link component.
5554
- * @returns {JSX.Element} The rendered anchor element.
5768
+ * @param {React.Ref} ref - Forwarded ref to the element `as` renders.
5769
+ * @returns {JSX.Element} The rendered anchor, or whatever `as` names.
5555
5770
  * @see {@link https://bulma.io/documentation/elements/content/ | Bulma Content documentation}
5556
5771
  */
5557
- const Link = ({ className, textColor, bgColor, isActive, as: Component = 'a', children, ...props }) => {
5772
+ const Link = React.forwardRef(function Link(linkProps, ref) {
5773
+ const { className, textColor, bgColor, isActive, as: Component = 'a', children, ...props } = linkProps;
5558
5774
  /**
5559
5775
  * Generates Bulma helper classes and separates out remaining props.
5560
5776
  */
@@ -5567,8 +5783,9 @@ const Link = ({ className, textColor, bgColor, isActive, as: Component = 'a', ch
5567
5783
  'is-active': isActive,
5568
5784
  });
5569
5785
  const linkClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
5570
- return (jsxRuntime.jsx(Component, { className: linkClasses || undefined, ...rest, children: children }));
5571
- };
5786
+ return (jsxRuntime.jsx(Component, { ref: ref, className: linkClasses || undefined, ...rest, children: children }));
5787
+ });
5788
+ Link.displayName = 'Link';
5572
5789
 
5573
5790
  /**
5574
5791
  * The `ListItem` component renders a styled list item (`<li>`) element with Bulma helper class integration.
@@ -6414,16 +6631,6 @@ const Checkbox = React.forwardRef(({ color, size, className, children, textColor
6414
6631
  });
6415
6632
  Checkbox.displayName = 'Checkbox';
6416
6633
 
6417
- /**
6418
- * Distinguishes an `IconProps` object from a plain custom node (an inline SVG, a `react-icons`
6419
- * component, …) passed to a slot that accepts either.
6420
- */
6421
- function isIconProps(value) {
6422
- return (typeof value === 'object' &&
6423
- value !== null &&
6424
- !React.isValidElement(value) &&
6425
- ('name' in value || 'children' in value));
6426
- }
6427
6634
  const allowedColors = [...validColors, 'inherit', 'current'];
6428
6635
  /**
6429
6636
  * The `Control` component is a Bulma-styled wrapper for form controls (`Input`, `Select`, `TextArea`, etc.), supporting icons (left/right), loading state, expansion, size, and Bulma helper props for layout and color.