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