@allxsmith/bestax-bulma 5.18.1 → 5.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/dist/bestax.css +1 -1
  2. package/dist/bestax.css.map +1 -1
  3. package/dist/constants.cjs +67 -6
  4. package/dist/constants.cjs.map +1 -1
  5. package/dist/constants.d.cts +30 -6
  6. package/dist/constants.esm.js +65 -7
  7. package/dist/constants.esm.js.map +1 -1
  8. package/dist/extras.css +1 -1
  9. package/dist/extras.css.map +1 -1
  10. package/dist/index.cjs +733 -85
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.esm.js +731 -86
  13. package/dist/index.esm.js.map +1 -1
  14. package/dist/types/components/Avatar.d.ts +3 -0
  15. package/dist/types/components/Card.d.ts +2 -0
  16. package/dist/types/components/Carousel.d.ts +8 -2
  17. package/dist/types/components/Dropdown.d.ts +6 -1
  18. package/dist/types/components/Menu.d.ts +6 -1
  19. package/dist/types/components/Modal.d.ts +2 -0
  20. package/dist/types/components/Navbar.d.ts +24 -3
  21. package/dist/types/components/Sidebar.d.ts +2 -0
  22. package/dist/types/components/Steps.d.ts +5 -1
  23. package/dist/types/components/Toast.d.ts +15 -4
  24. package/dist/types/elements/Delete.d.ts +9 -1
  25. package/dist/types/elements/Link.d.ts +7 -1
  26. package/dist/types/elements/Notification.d.ts +45 -7
  27. package/dist/types/elements/Tag.d.ts +7 -1
  28. package/dist/types/elements/Td.d.ts +21 -2
  29. package/dist/types/form/DateInputBase.d.ts +9 -1
  30. package/dist/types/form/DateTimeInputBase.d.ts +11 -4
  31. package/dist/types/form/TimeInputBase.d.ts +4 -2
  32. package/dist/types/helpers/Theme.d.ts +6 -1
  33. package/dist/types/helpers/bulmaClassHelpers.d.ts +30 -6
  34. package/dist/types/helpers/buttonType.d.ts +19 -0
  35. package/dist/types/helpers/positionStacks.d.ts +33 -0
  36. package/dist/types/helpers/shadowDom.d.ts +54 -0
  37. package/dist/types/helpers/statusRegion.d.ts +61 -0
  38. package/dist/types/helpers/useBulmaClasses.d.ts +1 -1
  39. package/dist/types/helpers/useOtherClasses.d.ts +61 -5
  40. package/dist/types-cjs/components/Avatar.d.ts +3 -0
  41. package/dist/types-cjs/components/Card.d.ts +2 -0
  42. package/dist/types-cjs/components/Carousel.d.ts +8 -2
  43. package/dist/types-cjs/components/Dropdown.d.ts +6 -1
  44. package/dist/types-cjs/components/Menu.d.ts +6 -1
  45. package/dist/types-cjs/components/Modal.d.ts +2 -0
  46. package/dist/types-cjs/components/Navbar.d.ts +24 -3
  47. package/dist/types-cjs/components/Sidebar.d.ts +2 -0
  48. package/dist/types-cjs/components/Steps.d.ts +5 -1
  49. package/dist/types-cjs/components/Toast.d.ts +15 -4
  50. package/dist/types-cjs/elements/Delete.d.ts +9 -1
  51. package/dist/types-cjs/elements/Link.d.ts +7 -1
  52. package/dist/types-cjs/elements/Notification.d.ts +45 -7
  53. package/dist/types-cjs/elements/Tag.d.ts +7 -1
  54. package/dist/types-cjs/elements/Td.d.ts +21 -2
  55. package/dist/types-cjs/form/DateInputBase.d.ts +9 -1
  56. package/dist/types-cjs/form/DateTimeInputBase.d.ts +11 -4
  57. package/dist/types-cjs/form/TimeInputBase.d.ts +4 -2
  58. package/dist/types-cjs/helpers/Theme.d.ts +6 -1
  59. package/dist/types-cjs/helpers/bulmaClassHelpers.d.ts +30 -6
  60. package/dist/types-cjs/helpers/buttonType.d.ts +19 -0
  61. package/dist/types-cjs/helpers/positionStacks.d.ts +33 -0
  62. package/dist/types-cjs/helpers/shadowDom.d.ts +54 -0
  63. package/dist/types-cjs/helpers/statusRegion.d.ts +61 -0
  64. package/dist/types-cjs/helpers/useBulmaClasses.d.ts +1 -1
  65. package/dist/types-cjs/helpers/useOtherClasses.d.ts +61 -5
  66. package/dist/versions/bestax-no-dark-mode.css +1 -1
  67. package/dist/versions/bestax-no-dark-mode.css.map +1 -1
  68. package/dist/versions/bestax-no-helpers-prefixed.css +1 -1
  69. package/dist/versions/bestax-no-helpers-prefixed.css.map +1 -1
  70. package/dist/versions/bestax-no-helpers.css +1 -1
  71. package/dist/versions/bestax-no-helpers.css.map +1 -1
  72. package/dist/versions/bestax-prefixed.css +1 -1
  73. package/dist/versions/bestax-prefixed.css.map +1 -1
  74. package/package.json +1 -1
  75. package/src/scss/form/_dateinput.scss +36 -10
  76. package/src/scss/form/_timeinput.scss +30 -9
package/dist/index.cjs CHANGED
@@ -429,10 +429,59 @@ const validViewports = [
429
429
  */
430
430
  const validFloats = ['left', 'right'];
431
431
  /**
432
- * Valid Bulma overflow classes.
433
- * @example 'clipped'
432
+ * Valid Bulma overflow values for one axis, taken by `overflowX` and
433
+ * `overflowY`. Each renders `is-overflow-x-<value>` or `is-overflow-y-<value>`.
434
+ * @example 'auto', 'hidden', 'scroll'
434
435
  */
435
- const validOverflows = ['clipped'];
436
+ const validAxisOverflows = [
437
+ 'auto',
438
+ 'clip',
439
+ 'hidden',
440
+ 'scroll',
441
+ 'visible',
442
+ ];
443
+ /**
444
+ * Valid Bulma overflow values for `overflow`.
445
+ *
446
+ * `clipped` renders `is-clipped`, the helper `overflow` started with. Every
447
+ * other value renders `is-overflow-<value>`.
448
+ * @example 'clipped', 'auto', 'hidden'
449
+ */
450
+ const validOverflows = ['clipped', ...validAxisOverflows];
451
+ /**
452
+ * Valid Bulma position values, taken by `pos`. Each renders
453
+ * `is-position-<value>`.
454
+ * @example 'relative', 'absolute', 'sticky'
455
+ */
456
+ const validPositions = [
457
+ 'absolute',
458
+ 'fixed',
459
+ 'relative',
460
+ 'static',
461
+ 'sticky',
462
+ ];
463
+ /**
464
+ * Valid Bulma aspect ratios, taken by `aspectRatio`. Each renders
465
+ * `is-aspect-ratio-<value>`.
466
+ * @example '1by1', '4by3', '16by9'
467
+ */
468
+ const validAspectRatios = [
469
+ '1by1',
470
+ '5by4',
471
+ '4by3',
472
+ '3by2',
473
+ '5by3',
474
+ '16by9',
475
+ '2by1',
476
+ '3by1',
477
+ '4by5',
478
+ '3by4',
479
+ '2by3',
480
+ '3by5',
481
+ '9by16',
482
+ '1by2',
483
+ '1by3',
484
+ ];
436
485
  /**
437
486
  * Valid Bulma interaction classes.
438
487
  * @example 'unselectable', 'clickable'
@@ -460,10 +509,19 @@ const cursorClasses = {
460
509
  help: 'is-cursor-help',
461
510
  };
462
511
  /**
463
- * Valid Bulma border-radius helper classes.
464
- * @example 'radiusless'
512
+ * Valid Bulma border-radius helper values.
513
+ *
514
+ * `radiusless` renders `is-radiusless` and removes the radius. The sizes
515
+ * render `has-radius-<value>` and set one from Bulma's radius scale.
516
+ * @example 'radiusless', 'small', 'rounded'
465
517
  */
466
- const validRadii = ['radiusless'];
518
+ const validRadii = [
519
+ 'radiusless',
520
+ 'small',
521
+ 'normal',
522
+ 'large',
523
+ 'rounded',
524
+ ];
467
525
  /**
468
526
  * Valid Bulma shadow helper classes.
469
527
  * @example 'shadowless'
@@ -924,7 +982,7 @@ const useFlexboxClasses = (props) => {
924
982
  /**
925
983
  * A hook that generates miscellaneous Bulma helper classes (float, overflow,
926
984
  * overlay, interaction, cursor, radius, shadow, responsive, skeleton,
927
- * clearfix, relative, and full height).
985
+ * clearfix, position, full height, and aspect ratio).
928
986
  *
929
987
  * @function useOtherClasses
930
988
  * @param props - Miscellaneous Bulma helper props.
@@ -935,15 +993,32 @@ const useFlexboxClasses = (props) => {
935
993
  */
936
994
  const useOtherClasses = (props) => {
937
995
  const { classPrefix } = useConfig();
938
- const { float, overflow, overlay, interaction, cursor, radius, shadow, responsive, skeleton, clearfix, relative, fullHeight, } = props;
996
+ const { float, overflow, overflowX, overflowY, overlay, interaction, cursor, radius, shadow, responsive, skeleton, clearfix, pos, relative, fullHeight, aspectRatio, } = props;
939
997
  return React.useMemo(() => {
940
998
  const { classes, addPrefixedClass, addClassNoViewport } = createBulmaClassHelpers(classPrefix);
941
999
  // Other Helpers (no viewport support)
942
1000
  if (float) {
943
1001
  addClassNoViewport('is-pulled', float, validFloats);
944
1002
  }
945
- if (overflow) {
946
- addClassNoViewport('is', overflow, validOverflows);
1003
+ // Bulma's overflow helpers are all `!important` at the same specificity
1004
+ // and ordered by value rather than by axis, so a both-axes class beside an
1005
+ // axis class would be settled by which value comes later in the
1006
+ // stylesheet. Once an axis prop is set, both axes are written per axis
1007
+ // instead: the axis prop wins its own, and `overflow` fills in the other,
1008
+ // `clipped` being `overflow: hidden`.
1009
+ const axisX = validAxisOverflows.find(v => v === overflowX);
1010
+ const axisY = validAxisOverflows.find(v => v === overflowY);
1011
+ if (axisX || axisY) {
1012
+ const rest = overflow === 'clipped' ? 'hidden' : overflow;
1013
+ addClassNoViewport('is-overflow-x', axisX ?? rest, validAxisOverflows);
1014
+ addClassNoViewport('is-overflow-y', axisY ?? rest, validAxisOverflows);
1015
+ }
1016
+ else if (overflow === 'clipped') {
1017
+ // The older helper, which keeps its own class.
1018
+ addPrefixedClass('is-clipped');
1019
+ }
1020
+ else {
1021
+ addClassNoViewport('is-overflow', overflow, validAxisOverflows);
947
1022
  }
948
1023
  if (overlay) {
949
1024
  addPrefixedClass('is-overlay');
@@ -954,8 +1029,13 @@ const useOtherClasses = (props) => {
954
1029
  if (cursor && validCursors.includes(cursor)) {
955
1030
  addPrefixedClass(cursorClasses[cursor]);
956
1031
  }
957
- if (radius) {
958
- addClassNoViewport('is', radius, validRadii);
1032
+ // `radiusless` is an `is-` helper that removes the radius; the sizes are
1033
+ // Bulma's `has-radius-` helpers.
1034
+ if (radius === 'radiusless') {
1035
+ addPrefixedClass('is-radiusless');
1036
+ }
1037
+ else {
1038
+ addClassNoViewport('has-radius', radius, validRadii);
959
1039
  }
960
1040
  if (shadow) {
961
1041
  addClassNoViewport('is', shadow, validShadows);
@@ -971,19 +1051,26 @@ const useOtherClasses = (props) => {
971
1051
  if (clearfix) {
972
1052
  addPrefixedClass('is-clearfix');
973
1053
  }
974
- // Position Relative Helper
975
- if (relative) {
1054
+ // Position Helpers. A valid `pos` decides the position on its own, so a
1055
+ // `relative` beside it cannot add a second, conflicting one.
1056
+ if (pos && validPositions.includes(pos)) {
1057
+ addPrefixedClass(`is-position-${pos}`);
1058
+ }
1059
+ else if (relative) {
976
1060
  addPrefixedClass('is-relative');
977
1061
  }
978
1062
  // Full Height Helper
979
1063
  if (fullHeight) {
980
1064
  addPrefixedClass('is-full-height');
981
1065
  }
1066
+ addClassNoViewport('is-aspect-ratio', aspectRatio, validAspectRatios);
982
1067
  return classNames(classes);
983
1068
  }, [
984
1069
  classPrefix,
985
1070
  float,
986
1071
  overflow,
1072
+ overflowX,
1073
+ overflowY,
987
1074
  overlay,
988
1075
  interaction,
989
1076
  cursor,
@@ -992,8 +1079,10 @@ const useOtherClasses = (props) => {
992
1079
  responsive,
993
1080
  skeleton,
994
1081
  clearfix,
1082
+ pos,
995
1083
  relative,
996
1084
  fullHeight,
1085
+ aspectRatio,
997
1086
  ]);
998
1087
  };
999
1088
 
@@ -1024,7 +1113,7 @@ const useOtherClasses = (props) => {
1024
1113
  * // rest: { className: 'custom-class' }
1025
1114
  */
1026
1115
  const useBulmaClasses = (props) => {
1027
- const { color, backgroundColor, colorShade, backgroundColorShade, m, mt, mr, mb, ml, mx, my, p, pt, pr, pb, pl, px, py, textSize, textAlign, textTransform, textWeight, fontFamily, display, visibility, flexDirection, flexWrap, justifyContent, alignContent, alignItems, alignSelf, flexGrow, flexShrink, float, overflow, overlay, interaction, cursor, radius, shadow, responsive, viewport, displayMobile, displayTablet, displayTabletOnly, displayTouch, displayDesktop, displayDesktopOnly, displayWidescreen, displayWidescreenOnly, displayFullhd, textSizeMobile, textSizeTablet, textSizeDesktop, textSizeWidescreen, textSizeFullhd, textAlignMobile, textAlignTablet, textAlignDesktop, textAlignWidescreen, textAlignFullhd, visibilityMobile, visibilityTablet, visibilityTabletOnly, visibilityTouch, visibilityDesktop, visibilityDesktopOnly, visibilityWidescreen, visibilityWidescreenOnly, visibilityFullhd, skeleton, clearfix, relative, fullHeight, ...rest } = props;
1116
+ const { color, backgroundColor, colorShade, backgroundColorShade, m, mt, mr, mb, ml, mx, my, p, pt, pr, pb, pl, px, py, textSize, textAlign, textTransform, textWeight, fontFamily, display, visibility, flexDirection, flexWrap, justifyContent, alignContent, alignItems, alignSelf, flexGrow, flexShrink, float, overflow, overflowX, overflowY, overlay, interaction, cursor, radius, shadow, responsive, viewport, displayMobile, displayTablet, displayTabletOnly, displayTouch, displayDesktop, displayDesktopOnly, displayWidescreen, displayWidescreenOnly, displayFullhd, textSizeMobile, textSizeTablet, textSizeDesktop, textSizeWidescreen, textSizeFullhd, textAlignMobile, textAlignTablet, textAlignDesktop, textAlignWidescreen, textAlignFullhd, visibilityMobile, visibilityTablet, visibilityTabletOnly, visibilityTouch, visibilityDesktop, visibilityDesktopOnly, visibilityWidescreen, visibilityWidescreenOnly, visibilityFullhd, skeleton, clearfix, pos, relative, fullHeight, aspectRatio, ...rest } = props;
1028
1117
  const colorClasses = useColorClasses({
1029
1118
  color,
1030
1119
  colorShade,
@@ -1112,6 +1201,8 @@ const useBulmaClasses = (props) => {
1112
1201
  const otherClasses = useOtherClasses({
1113
1202
  float,
1114
1203
  overflow,
1204
+ overflowX,
1205
+ overflowY,
1115
1206
  overlay,
1116
1207
  interaction,
1117
1208
  cursor,
@@ -1120,8 +1211,10 @@ const useBulmaClasses = (props) => {
1120
1211
  responsive,
1121
1212
  skeleton,
1122
1213
  clearfix,
1214
+ pos,
1123
1215
  relative,
1124
1216
  fullHeight,
1217
+ aspectRatio,
1125
1218
  });
1126
1219
  const bulmaHelperClasses = React.useMemo(() => classNames(colorClasses, spacingClasses, typographyClasses, visibilityClasses, flexboxClasses, otherClasses), [
1127
1220
  colorClasses,
@@ -1237,6 +1330,27 @@ const ColumnsComponent = ({ className, textColor, color: _fieldColor, bgColor, i
1237
1330
  };
1238
1331
  const Columns = withSubComponents(ColumnsComponent, { Column }, 'Columns');
1239
1332
 
1333
+ /**
1334
+ * The `type` to render on a `<button>` a component defaults to `button`: the
1335
+ * caller's own value when it is `button`, `submit` or `reset`, and `button`
1336
+ * otherwise.
1337
+ *
1338
+ * A missing `type` is not the only way a button ends up submitting the form
1339
+ * around it. HTML reads an invalid value, such as the anchor's MIME-type
1340
+ * `type="text/html"`, as submit too, and a props spread carrying
1341
+ * `type: undefined` removes the attribute outright. Both can arrive through a
1342
+ * spread or an untyped caller the component's own types never see, so this
1343
+ * checks the value rather than trusting where it came from.
1344
+ *
1345
+ * Call it AFTER the forwarded props are spread and pass it the forwarded
1346
+ * `type`, so its result is the attribute that renders.
1347
+ */
1348
+ function buttonType(given) {
1349
+ return given === 'submit' || given === 'reset' || given === 'button'
1350
+ ? given
1351
+ : 'button';
1352
+ }
1353
+
1240
1354
  /**
1241
1355
  * Whether an `as` target is a custom element rather than a built-in tag.
1242
1356
  *
@@ -1534,7 +1648,7 @@ const Avatar = React.forwardRef(function Avatar(avatarProps, ref) {
1534
1648
  // The decorative opt-out never applies to an interactive avatar — a link or
1535
1649
  // button must always expose an accessible name.
1536
1650
  const isDecorative = alt === '' && !isInteractive;
1537
- const a11yProps = showImage
1651
+ const a11yDefaults = showImage
1538
1652
  ? isInteractive && !accessibleName
1539
1653
  ? // The img alt normally names the control; with no alt/name (an API
1540
1654
  // returning only a photo URL) the link/button would be nameless.
@@ -1546,10 +1660,18 @@ const Avatar = React.forwardRef(function Avatar(avatarProps, ref) {
1546
1660
  ...(isInteractive ? {} : { role: 'img' }),
1547
1661
  'aria-label': accessibleName || name || 'Avatar',
1548
1662
  };
1549
- // A clickable avatar inside a form must not submit it; default the native
1550
- // button type (an explicit type passed through rest still wins).
1551
- const buttonTypeProps = Tag === 'button' ? { type: 'button' } : {};
1552
- 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 => {
1663
+ // Each default yields to a value the caller passed, and is applied after
1664
+ // `rest` reading through it rather than spread before it. React treats an
1665
+ // `undefined` attribute as "remove it", and a spread carrying the key with no
1666
+ // value is how that arrives, so a default spread first was erased by it: an
1667
+ // `aria-label: undefined` left a button avatar nameless, and `role` and
1668
+ // `aria-hidden` went the same way. The button `type` below has the same
1669
+ // shape (#690).
1670
+ const a11yProps = Object.fromEntries(Object.entries(a11yDefaults).map(([key, fallback]) => [
1671
+ key,
1672
+ rest[key] ?? fallback,
1673
+ ]));
1674
+ return (jsxRuntime.jsxs(Tag, { ref: ref, className: combinedClasses, style: { ...sizeStyle, ...style }, ...linkProps, ...rest, ...a11yProps, ...(Tag === 'button' ? { type: buttonType(rest.type) } : {}), children: [showImage && (jsxRuntime.jsx("img", { ...imageProps, ref: imgRef, src: src, alt: accessibleName ?? '', onError: e => {
1553
1675
  imageProps?.onError?.(e);
1554
1676
  setErroredSrc(src);
1555
1677
  } }, src)), showInitials && (jsxRuntime.jsx("span", { className: initialsClass, children: resolvedInitials })), showIcon && icon, showDefaultIcon && jsxRuntime.jsx(DefaultAvatarIcon, {})] }));
@@ -1980,7 +2102,7 @@ const CardHeaderIcon = ({ className, children, color, bgColor, textColor, ...pro
1980
2102
  backgroundColor: bgColor,
1981
2103
  ...props,
1982
2104
  });
1983
- return (jsxRuntime.jsx("button", { className: classNames(usePrefixedClassNames('card-header-icon'), bulmaHelperClasses, className), "aria-label": props['aria-label'] || 'more options', ...rest, children: children }));
2105
+ return (jsxRuntime.jsx("button", { className: classNames(usePrefixedClassNames('card-header-icon'), bulmaHelperClasses, className), ...rest, "aria-label": rest['aria-label'] || 'more options', type: buttonType(rest.type), children: children }));
1984
2106
  };
1985
2107
  /**
1986
2108
  * Card image compound component. Wraps children in a `.card-image` element.
@@ -2049,7 +2171,6 @@ const CardFooterItem = ({ as = 'span', className, children, color, bgColor, text
2049
2171
  }
2050
2172
  const forwarded = omitAttrs(rest, STRIP_FROM_NON_ANCHOR$1);
2051
2173
  if (as === 'button') {
2052
- const forwardedType = forwarded.type;
2053
2174
  return (jsxRuntime.jsx("button", { className: itemClasses, ...forwarded,
2054
2175
  // A footer item button must not submit an enclosing form by default —
2055
2176
  // `<button>` defaults to `type="submit"`, and a Save/Cancel action row
@@ -2059,13 +2180,9 @@ const CardFooterItem = ({ as = 'span', className, children, color, bgColor, text
2059
2180
  // typed here as the `<a>` MIME string (`STRIP_FROM_NON_ANCHOR` above
2060
2181
  // says why it is not withheld), so `as="button" type="text/html"`
2061
2182
  // compiles, and HTML's INVALID-value default for a button's `type` is
2062
- // submit too. So anything that is not one of the three native button
2063
- // types falls back to `button` rather than being forwarded.
2064
- type: forwardedType === 'submit' ||
2065
- forwardedType === 'reset' ||
2066
- forwardedType === 'button'
2067
- ? forwardedType
2068
- : 'button', children: children }));
2183
+ // submit too. `buttonType` keeps `button`, `submit` and `reset` and
2184
+ // turns anything else into `button`.
2185
+ type: buttonType(forwarded.type), children: children }));
2069
2186
  }
2070
2187
  return (jsxRuntime.jsx("span", { className: itemClasses, ...omitAttrs(forwarded, STRIP_FROM_NON_BUTTON), children: children }));
2071
2188
  };
@@ -2083,6 +2200,72 @@ const Card = withSubComponents(CardComponent, {
2083
2200
  FooterItem: CardFooterItem,
2084
2201
  }, 'Card');
2085
2202
 
2203
+ /**
2204
+ * Event and focus checks that still hold when a component renders inside a
2205
+ * shadow root.
2206
+ *
2207
+ * Code outside a shadow root cannot see into it. A listener on `document`
2208
+ * reads an event from inside one with its `target` set to the shadow host,
2209
+ * and `document.activeElement` names the host instead of the element that has
2210
+ * focus. A component asking either question from `document` then takes a
2211
+ * click on its own menu for an outside click, or cannot tell which of its
2212
+ * items is focused. A web component or a sandboxed preview puts a component
2213
+ * in exactly that position.
2214
+ *
2215
+ * Focus comes in two questions, and each has its own function here. "Is focus
2216
+ * inside me, and on which of my elements?" is `getActiveElementInTree`. "Which
2217
+ * element has focus, wherever it is?", the one to record before moving focus
2218
+ * away and restore to later, is `getDeepestActiveElement`.
2219
+ */
2220
+ /**
2221
+ * Whether `event` happened inside `node`. The event's composed path still
2222
+ * holds the element that was really clicked, where a `document` listener
2223
+ * reads the shadow host as the event's `target`.
2224
+ *
2225
+ * @param event - An event read by a listener outside `node`, usually on `document`.
2226
+ * @param node - The component's element, or nothing while it is unmounted.
2227
+ * @returns True when `node` is on the event's path.
2228
+ */
2229
+ function isEventInside(event, node) {
2230
+ return !!node && event.composedPath().includes(node);
2231
+ }
2232
+ /**
2233
+ * The focused element as `node`'s own tree sees it: its shadow root's
2234
+ * `activeElement` when `node` is inside one, `document.activeElement` when it
2235
+ * is not. The result is comparable with `node` and its descendants, which
2236
+ * live in that same tree, so use it to ask whether focus is inside `node`.
2237
+ * Focus inside a shadow root nested within `node` reads as that root's host,
2238
+ * which `node` contains.
2239
+ *
2240
+ * A shadow root with nothing focused inside reports `null`, and a detached
2241
+ * node has no document above it. Both fall back to `document.activeElement`,
2242
+ * so focus elsewhere on the page still reads as outside `node`.
2243
+ *
2244
+ * @param node - An element of the component, or nothing while it is unmounted.
2245
+ * @returns The focused element in `node`'s tree, or the document's.
2246
+ */
2247
+ function getActiveElementInTree(node) {
2248
+ const root = node?.getRootNode();
2249
+ return root?.activeElement ?? document.activeElement;
2250
+ }
2251
+ /**
2252
+ * The element that really has focus, followed down from
2253
+ * `document.activeElement` through every open shadow root on the way. Use it
2254
+ * to record where focus was before moving it, so it can be restored to that
2255
+ * element later: the tree a component renders into need not be the tree its
2256
+ * opener sits in. A closed shadow root cannot be entered, so focus inside one
2257
+ * reads as its host.
2258
+ *
2259
+ * @returns The focused element, or `null` when the document has none.
2260
+ */
2261
+ function getDeepestActiveElement() {
2262
+ let active = document.activeElement;
2263
+ while (active?.shadowRoot?.activeElement) {
2264
+ active = active.shadowRoot.activeElement;
2265
+ }
2266
+ return active;
2267
+ }
2268
+
2086
2269
  /**
2087
2270
  * Checks if code is running in a browser environment.
2088
2271
  * @deprecated Dropdown's internal SSR guard, exported by accident; it will
@@ -2163,8 +2346,7 @@ const DropdownComponent = React.forwardRef(function DropdownComponent({ label, c
2163
2346
  if (!isBrowser$1(window, document))
2164
2347
  return;
2165
2348
  const handleClick = (e) => {
2166
- /* istanbul ignore next: dropdownRef.current is never null while the listener is attached */
2167
- if (!dropdownRef.current?.contains(e.target)) {
2349
+ if (!isEventInside(e, dropdownRef.current)) {
2168
2350
  setActive(false);
2169
2351
  onActiveChange?.(false);
2170
2352
  }
@@ -2180,10 +2362,16 @@ const DropdownComponent = React.forwardRef(function DropdownComponent({ label, c
2180
2362
  setActive(newActive);
2181
2363
  onActiveChange?.(newActive);
2182
2364
  };
2183
- const handleMenuClick = () => {
2365
+ const handleMenuClick = (e) => {
2184
2366
  if (closeOnClick) {
2185
2367
  setActive(false);
2186
2368
  onActiveChange?.(false);
2369
+ // Closing hides the item that has focus, and the browser drops focus
2370
+ // to the page when that happens. Hand it back to the trigger, as
2371
+ // Escape does. An item that moved focus somewhere else keeps it there.
2372
+ if (e.currentTarget.contains(getActiveElementInTree(e.currentTarget))) {
2373
+ triggerRef.current?.focus();
2374
+ }
2187
2375
  }
2188
2376
  };
2189
2377
  const getMenuItems = () => {
@@ -2244,6 +2432,11 @@ const DropdownComponent = React.forwardRef(function DropdownComponent({ label, c
2244
2432
  case 'Enter':
2245
2433
  case ' ':
2246
2434
  e.preventDefault();
2435
+ // A held key sends a keydown per auto-repeat. Running an item hands
2436
+ // focus back here, so without this a held Enter would reopen the
2437
+ // menu and run whichever item it focused next.
2438
+ if (e.repeat)
2439
+ break;
2247
2440
  if (!active) {
2248
2441
  pendingFocusRef.current = 'first';
2249
2442
  setActive(true);
@@ -2281,7 +2474,7 @@ const DropdownComponent = React.forwardRef(function DropdownComponent({ label, c
2281
2474
  const items = getMenuItems();
2282
2475
  if (!items.length)
2283
2476
  return;
2284
- const currentIndex = items.indexOf(document.activeElement);
2477
+ const currentIndex = items.indexOf(getActiveElementInTree(e.currentTarget));
2285
2478
  switch (e.key) {
2286
2479
  case 'ArrowDown': {
2287
2480
  e.preventDefault();
@@ -2402,9 +2595,7 @@ const DropdownItem = ((itemProps) => {
2402
2595
  // carrying `role: undefined` would otherwise erase the default, and an
2403
2596
  // item with no role drops out of the menu and its arrow-key order.
2404
2597
  role: forwarded.role ?? 'menuitem', ...(Component === 'button'
2405
- ? {
2406
- type: forwarded.type ?? 'button',
2407
- }
2598
+ ? { type: buttonType(forwarded.type) }
2408
2599
  : {}), children: children }));
2409
2600
  });
2410
2601
  /**
@@ -2513,7 +2704,9 @@ const MenuItem = React.forwardRef(function MenuItem(itemProps, ref) {
2513
2704
  labelChildren.push(child);
2514
2705
  }
2515
2706
  });
2516
- 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, children: labelChildren }), nestedMenuLists] }));
2707
+ 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'
2708
+ ? { type: buttonType(linkProps.type) }
2709
+ : {}), children: labelChildren }), nestedMenuLists] }));
2517
2710
  });
2518
2711
  MenuItem.displayName = 'MenuItem';
2519
2712
  // Attach static subcomponents
@@ -2769,7 +2962,7 @@ const ModalCard = withSubComponents(ModalCardComponent, {
2769
2962
  */
2770
2963
  const ModalClose = ({ className, size = 'large', variant = 'delete', ...props }) => {
2771
2964
  const classes = classNames(usePrefixedClassNames(variant === 'delete' ? 'delete' : 'modal-close', variant === 'floating' && size && { [`is-${size}`]: true }), className);
2772
- return (jsxRuntime.jsx("button", { className: classes, "aria-label": "close", type: "button", ...props }));
2965
+ return (jsxRuntime.jsx("button", { className: classes, ...props, "aria-label": props['aria-label'] || 'close', type: buttonType(props.type) }));
2773
2966
  };
2774
2967
  /**
2775
2968
  * The `Modal` component provides an empty, accessible overlay for arbitrary content — for a ready-made confirm or alert, reach for `Dialog` instead.
@@ -2886,7 +3079,7 @@ const ModalRoot = React.forwardRef(function ModalRoot({ active, isActive, onClos
2886
3079
  // Keep Tab within the modal — `aria-modal` hides the rest of the page
2887
3080
  // from assistive technology, so the keyboard order has to agree.
2888
3081
  const focusable = getTabbable(node);
2889
- const activeElement = document.activeElement;
3082
+ const activeElement = getActiveElementInTree(node);
2890
3083
  if (focusable.length === 0) {
2891
3084
  e.preventDefault();
2892
3085
  node.focus();
@@ -2913,7 +3106,10 @@ const ModalRoot = React.forwardRef(function ModalRoot({ active, isActive, onClos
2913
3106
  React.useEffect(() => {
2914
3107
  if (!isModalActive)
2915
3108
  return undefined;
2916
- previouslyFocusedRef.current = document.activeElement;
3109
+ // The element that had focus, wherever it sits: a portaled modal renders
3110
+ // under `document.body` while its opener can be inside a shadow root.
3111
+ previouslyFocusedRef.current =
3112
+ getDeepestActiveElement();
2917
3113
  const node = modalRootRef.current;
2918
3114
  const focusable = node ? getTabbable(node)[0] : undefined;
2919
3115
  (focusable ?? node)?.focus();
@@ -2921,7 +3117,7 @@ const ModalRoot = React.forwardRef(function ModalRoot({ active, isActive, onClos
2921
3117
  // Only hand focus back if this modal still owns it: closing a background
2922
3118
  // modal must not pull focus out of one that is still open on top. A
2923
3119
  // removed subtree leaves focus on <body>, which still counts as ours.
2924
- const activeElement = document.activeElement;
3120
+ const activeElement = getActiveElementInTree(node);
2925
3121
  if (activeElement &&
2926
3122
  activeElement !== document.body &&
2927
3123
  !node?.contains(activeElement)) {
@@ -3039,7 +3235,7 @@ const NavbarItem = React.forwardRef(function NavbarItem(itemProps, ref) {
3039
3235
  });
3040
3236
  return (jsxRuntime.jsx(Component, { ref: ref, className: classNames(usePrefixedClassNames('navbar-item', {
3041
3237
  'is-active': active,
3042
- }), bulmaHelperClasses, className), ...rest, children: children }));
3238
+ }), bulmaHelperClasses, className), ...rest, ...(Component === 'button' ? { type: buttonType(rest.type) } : {}), children: children }));
3043
3239
  });
3044
3240
  NavbarItem.displayName = 'NavbarItem';
3045
3241
  /**
@@ -3055,9 +3251,9 @@ const NavbarBurger = React.forwardRef(function NavbarBurger({ className, active,
3055
3251
  color: textColor ?? color,
3056
3252
  ...props,
3057
3253
  });
3058
- return (jsxRuntime.jsxs("button", { ref: ref, type: "button", className: classNames(usePrefixedClassNames('navbar-burger', {
3254
+ return (jsxRuntime.jsxs("button", { ref: ref, className: classNames(usePrefixedClassNames('navbar-burger', {
3059
3255
  'is-active': active,
3060
- }), bulmaHelperClasses, className), "aria-label": props['aria-label'] || 'menu', "aria-expanded": props['aria-expanded'] ?? !!active, ...rest, children: [jsxRuntime.jsx("span", { "aria-hidden": "true" }), jsxRuntime.jsx("span", { "aria-hidden": "true" }), jsxRuntime.jsx("span", { "aria-hidden": "true" }), jsxRuntime.jsx("span", { "aria-hidden": "true" }), children] }));
3256
+ }), bulmaHelperClasses, className), ...rest, "aria-label": rest['aria-label'] || 'menu', "aria-expanded": rest['aria-expanded'] ?? !!active, type: buttonType(rest.type), children: [jsxRuntime.jsx("span", { "aria-hidden": "true" }), jsxRuntime.jsx("span", { "aria-hidden": "true" }), jsxRuntime.jsx("span", { "aria-hidden": "true" }), jsxRuntime.jsx("span", { "aria-hidden": "true" }), children] }));
3061
3257
  });
3062
3258
  NavbarBurger.displayName = 'NavbarBurger';
3063
3259
  /**
@@ -3187,7 +3383,7 @@ const NavbarLink = React.forwardRef(function NavbarLink(linkProps, ref) {
3187
3383
  tabIndex: 0,
3188
3384
  onClick: handleClick,
3189
3385
  }),
3190
- }), children: children }));
3386
+ }), ...(Component === 'button' ? { type: buttonType(rest.type) } : {}), children: children }));
3191
3387
  });
3192
3388
  NavbarLink.displayName = 'NavbarLink';
3193
3389
  /**
@@ -4511,7 +4707,7 @@ const StepsComponent = ({ value = 0, items, size, color, hasMarker = true, anima
4511
4707
  }
4512
4708
  return null;
4513
4709
  };
4514
- return (jsxRuntime.jsxs("div", { className: combinedClasses, ...rest, children: [jsxRuntime.jsx("ul", { className: listClasses, children: renderSteps() }), hasNavigation && (jsxRuntime.jsxs("div", { className: stepsNavigationClass, children: [jsxRuntime.jsx("button", { className: prevButtonClass, disabled: value === 0, onClick: onPrev ?? (() => onStepClick?.(value - 1)), children: prevLabel ?? 'Previous' }), jsxRuntime.jsx("button", { className: nextButtonClass, disabled: value === totalSteps - 1, onClick: onNext ?? (() => onStepClick?.(value + 1)), children: nextLabel ?? 'Next' })] }))] }));
4710
+ return (jsxRuntime.jsxs("div", { className: combinedClasses, ...rest, children: [jsxRuntime.jsx("ul", { className: listClasses, children: renderSteps() }), hasNavigation && (jsxRuntime.jsxs("div", { className: stepsNavigationClass, children: [jsxRuntime.jsx("button", { type: "button", className: prevButtonClass, disabled: value === 0, onClick: onPrev ?? (() => onStepClick?.(value - 1)), children: prevLabel ?? 'Previous' }), jsxRuntime.jsx("button", { type: "button", className: nextButtonClass, disabled: value === totalSteps - 1, onClick: onNext ?? (() => onStepClick?.(value + 1)), children: nextLabel ?? 'Next' })] }))] }));
4515
4711
  };
4516
4712
  // Attach Step as static property
4517
4713
  const Steps = withSubComponents(StepsComponent, { Step }, 'Steps');
@@ -4680,7 +4876,7 @@ const SidebarTitle = ({ className, children, ...props }) => {
4680
4876
  */
4681
4877
  const SidebarClose = ({ className, children, ...props }) => {
4682
4878
  const closeClass = usePrefixedClassNames('sidebar-close');
4683
- return (jsxRuntime.jsx("button", { type: "button", className: classNames(closeClass, className), "aria-label": "Close", ...props, children: children }));
4879
+ return (jsxRuntime.jsx("button", { className: classNames(closeClass, className), ...props, "aria-label": props['aria-label'] || 'Close', type: buttonType(props.type), children: children }));
4684
4880
  };
4685
4881
  /**
4686
4882
  * Main content area of the sidebar.
@@ -4713,6 +4909,48 @@ const Sidebar = withSubComponents(SidebarComponent, {
4713
4909
  Footer: SidebarFooter,
4714
4910
  }, 'Sidebar');
4715
4911
 
4912
+ /**
4913
+ * Splits the items a container shows into one stack per position in use. An
4914
+ * item goes to its own position when it has one and to the container's
4915
+ * otherwise. A position with no items gets no stack.
4916
+ *
4917
+ * Stacks come back in `order`, so their order in the page follows the screen
4918
+ * rather than which position happened to be used first, and a stack keeps its
4919
+ * place while the others come and go. A position missing from `order` sorts
4920
+ * after the known ones.
4921
+ *
4922
+ * @function groupIntoPositionStacks
4923
+ * @param items - The items the container shows, in the order they were shown.
4924
+ * @param positionOf - Reads an item's own position, if it has one.
4925
+ * @param containerPosition - Where an item without a position of its own goes.
4926
+ * @param order - Every known position, in the order stacks should render.
4927
+ * @returns One stack per position in use.
4928
+ */
4929
+ function groupIntoPositionStacks(items, positionOf, containerPosition, order) {
4930
+ const byPosition = new Map();
4931
+ for (const item of items) {
4932
+ const position = positionOf(item) ?? containerPosition;
4933
+ const stack = byPosition.get(position);
4934
+ if (stack) {
4935
+ stack.push(item);
4936
+ }
4937
+ else {
4938
+ byPosition.set(position, [item]);
4939
+ }
4940
+ }
4941
+ const rank = (position) => {
4942
+ const index = order.indexOf(position);
4943
+ return index === -1 ? order.length : index;
4944
+ };
4945
+ // Array.prototype.sort is stable, so unknown positions keep the order they
4946
+ // were first used in.
4947
+ return Array.from(byPosition, ([position, stackItems]) => ({
4948
+ key: position === containerPosition ? 'container' : `at-${position}`,
4949
+ position,
4950
+ items: stackItems,
4951
+ })).sort((a, b) => rank(a.position) - rank(b.position));
4952
+ }
4953
+
4716
4954
  /**
4717
4955
  * The `Toast` component provides brief notification messages with optional action and cancel buttons.
4718
4956
  *
@@ -4768,7 +5006,7 @@ const Toast = React.forwardRef(({ message, type = 'default', actionType, positio
4768
5006
  if (!dismissible || !isVisible)
4769
5007
  return undefined;
4770
5008
  const handleDocumentClick = (e) => {
4771
- if (toastRef.current && !toastRef.current.contains(e.target)) {
5009
+ if (toastRef.current && !isEventInside(e, toastRef.current)) {
4772
5010
  handleClose();
4773
5011
  }
4774
5012
  };
@@ -4989,25 +5227,38 @@ const toast = {
4989
5227
  return () => toastListeners.delete(listener);
4990
5228
  },
4991
5229
  };
5230
+ // The order ToastContainer renders its stacks in: across the top of the
5231
+ // screen, then across the bottom.
5232
+ const toastStackOrder = [
5233
+ 'top-left',
5234
+ 'top-center',
5235
+ 'top-right',
5236
+ 'bottom-left',
5237
+ 'bottom-center',
5238
+ 'bottom-right',
5239
+ ];
4992
5240
  /**
4993
5241
  * Container component for rendering programmatic toasts.
4994
- * Place once at your app root to enable the toast API.
5242
+ * Place once at your app root to enable the toast API. A toast shown with a
5243
+ * `position` appears there, and one shown without goes to the container's
5244
+ * `position`.
4995
5245
  *
4996
5246
  * @function
4997
5247
  * @param {{ position?: ToastPosition }} props - Container props.
4998
5248
  * @returns {JSX.Element | null} The rendered toast container, or null if empty.
4999
5249
  */
5000
- const ToastContainer = ({ position = 'top-right', }) => {
5250
+ const ToastContainer = ({ position = 'top-right' }) => {
5001
5251
  // Starts from the toasts already showing instead of an empty list, then
5002
5252
  // follows changes.
5003
5253
  const toastList = React.useSyncExternalStore(toast.subscribe, getVisibleToasts, getServerToasts);
5004
5254
  if (typeof document === 'undefined' || toastList.length === 0) {
5005
5255
  return null;
5006
5256
  }
5007
- return reactDom.createPortal(jsxRuntime.jsx("div", { className: `toast-container is-${position}`, children: toastList.map(t => {
5257
+ const stacks = groupIntoPositionStacks(toastList, t => t.props.position, position, toastStackOrder);
5258
+ return reactDom.createPortal(stacks.map(stack => (jsxRuntime.jsx("div", { className: `toast-container is-${stack.position}`, children: stack.items.map(t => {
5008
5259
  const { queue: _queue, ...toastProps } = t.props;
5009
- return (jsxRuntime.jsx(Toast, { ...toastProps, position: position, onClose: () => toast.close(t.id) }, t.id));
5010
- }) }), document.body);
5260
+ return (jsxRuntime.jsx(Toast, { ...toastProps, position: stack.position, onClose: () => toast.close(t.id) }, t.id));
5261
+ }) }, stack.key))), document.body);
5011
5262
  };
5012
5263
 
5013
5264
  /**
@@ -5558,7 +5809,8 @@ const Carousel = React.forwardRef(({ value: controlledValue, autoplay = false, i
5558
5809
  // Keyboard navigation
5559
5810
  React.useEffect(() => {
5560
5811
  const handleKeyDown = (e) => {
5561
- if (!carouselRef.current?.contains(document.activeElement))
5812
+ const node = carouselRef.current;
5813
+ if (!node?.contains(getActiveElementInTree(node)))
5562
5814
  return;
5563
5815
  switch (e.key) {
5564
5816
  case 'ArrowLeft':
@@ -5691,7 +5943,7 @@ const Carousel = React.forwardRef(({ value: controlledValue, autoplay = false, i
5691
5943
  transition: isDragging || skipTransition
5692
5944
  ? 'none'
5693
5945
  : 'transform 0.3s ease-in-out',
5694
- }, onTransitionEnd: handleTransitionEnd, children: renderSlides() }), arrow && itemCount > 1 && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(Button, { className: carouselArrowPrevClass, onClick: goToPrev, isDisabled: !canGoPrev, "aria-label": "Previous slide", children: iconPrev ? (jsxRuntime.jsx(Icon, { name: iconPrev, library: iconLibrary, variant: iconVariant, size: iconSize, features: iconFeatures })) : (jsxRuntime.jsx(DefaultPrevIcon, {})) }), jsxRuntime.jsx(Button, { className: carouselArrowNextClass, onClick: goToNext, isDisabled: !canGoNext, "aria-label": "Next slide", children: iconNext ? (jsxRuntime.jsx(Icon, { name: iconNext, library: iconLibrary, variant: iconVariant, size: iconSize, features: iconFeatures })) : (jsxRuntime.jsx(DefaultNextIcon, {})) })] }))] }), indicator && itemCount > 1 && (jsxRuntime.jsx("div", { className: carouselIndicatorClass, role: "tablist", children: items.map((_, index) => (jsxRuntime.jsx(Button, { className: prefixedClassNames(classPrefix, 'indicator-item', {
5946
+ }, onTransitionEnd: handleTransitionEnd, children: renderSlides() }), arrow && itemCount > 1 && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(Button, { type: "button", className: carouselArrowPrevClass, onClick: goToPrev, isDisabled: !canGoPrev, "aria-label": "Previous slide", children: iconPrev ? (jsxRuntime.jsx(Icon, { name: iconPrev, library: iconLibrary, variant: iconVariant, size: iconSize, features: iconFeatures })) : (jsxRuntime.jsx(DefaultPrevIcon, {})) }), jsxRuntime.jsx(Button, { type: "button", className: carouselArrowNextClass, onClick: goToNext, isDisabled: !canGoNext, "aria-label": "Next slide", children: iconNext ? (jsxRuntime.jsx(Icon, { name: iconNext, library: iconLibrary, variant: iconVariant, size: iconSize, features: iconFeatures })) : (jsxRuntime.jsx(DefaultNextIcon, {})) })] }))] }), indicator && itemCount > 1 && (jsxRuntime.jsx("div", { className: carouselIndicatorClass, role: "tablist", children: items.map((_, index) => (jsxRuntime.jsx(Button, { type: "button", className: prefixedClassNames(classPrefix, 'indicator-item', {
5695
5947
  'is-active': index === activeIndex,
5696
5948
  }), onClick: () => goToSlide(index), role: "tab", "aria-selected": index === activeIndex, "aria-label": `Go to slide ${index + 1}` }, index))) }))] }));
5697
5949
  });
@@ -5999,12 +6251,14 @@ const Content = ({ className, textColor, color, bgColor, size, children, ...prop
5999
6251
  /**
6000
6252
  * The `Delete` component provides a Bulma-styled close/delete button for dismissing modals, notifications, tags, messages, and more.
6001
6253
  *
6254
+ * It renders `type="button"`, so a delete button inside a form does not submit it.
6255
+ *
6002
6256
  * @function
6003
6257
  * @param {DeleteProps} props - Props for the Delete component.
6004
6258
  * @returns {JSX.Element} The rendered delete button.
6005
6259
  * @see {@link https://bulma.io/documentation/elements/delete/ | Bulma Delete documentation}
6006
6260
  */
6007
- const Delete = ({ className, textColor, color, bgColor, onClick, size, ariaLabel = 'Close', disabled = false, ...props }) => {
6261
+ const Delete = ({ className, textColor, color, bgColor, onClick, size, ariaLabel, disabled = false, ...props }) => {
6008
6262
  /**
6009
6263
  * Generates Bulma helper classes and separates out remaining props.
6010
6264
  */
@@ -6018,7 +6272,7 @@ const Delete = ({ className, textColor, color, bgColor, onClick, size, ariaLabel
6018
6272
  'is-disabled': disabled,
6019
6273
  });
6020
6274
  const classes = classNames(bulmaClasses, bulmaHelperClasses, className);
6021
- return (jsxRuntime.jsx("button", { className: classes, onClick: onClick, "aria-label": ariaLabel, disabled: disabled, type: "button", ...rest }));
6275
+ return (jsxRuntime.jsx("button", { className: classes, onClick: onClick, disabled: disabled, ...rest, "aria-label": rest['aria-label'] || ariaLabel || 'Close', type: buttonType(rest.type) }));
6022
6276
  };
6023
6277
 
6024
6278
  /**
@@ -6240,7 +6494,7 @@ const Link = React.forwardRef(function Link(linkProps, ref) {
6240
6494
  'is-active': isActive,
6241
6495
  });
6242
6496
  const linkClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
6243
- return (jsxRuntime.jsx(Component, { ref: ref, className: linkClasses || undefined, ...rest, children: children }));
6497
+ return (jsxRuntime.jsx(Component, { ref: ref, className: linkClasses || undefined, ...rest, ...(Component === 'button' ? { type: buttonType(rest.type) } : {}), children: children }));
6244
6498
  });
6245
6499
  Link.displayName = 'Link';
6246
6500
 
@@ -6265,6 +6519,281 @@ const ListItem = ({ className, textColor, bgColor, children, ...props }) => {
6265
6519
  return (jsxRuntime.jsx("li", { className: itemClasses || undefined, ...rest, children: children }));
6266
6520
  };
6267
6521
 
6522
+ /**
6523
+ * How long after an item appears its announcement is written, in ms. The wait
6524
+ * lets a screen reader register a region the container has only just mounted,
6525
+ * and items shown in quick succession are written, and read out, together.
6526
+ */
6527
+ const announceDelay = 100;
6528
+ /**
6529
+ * How long an announcement stays in the region once written, in ms. That's
6530
+ * long enough for a screen reader to pick it up. Clearing it after keeps a
6531
+ * second copy of the item's text from sitting in the page, where someone
6532
+ * reading through the page, or a test looking the text up, would find it
6533
+ * again.
6534
+ */
6535
+ const announcementLifetime = 1000;
6536
+ const noAnnouncements = [];
6537
+ const nothingWritten = [];
6538
+ const noAnnouncementsYet = {
6539
+ key: 0,
6540
+ waiting: noAnnouncements,
6541
+ written: nothingWritten,
6542
+ };
6543
+ // Hides the region with inline styles rather than a class, so it needs no
6544
+ // stylesheet, including the helper classes a modular Bulma build can leave
6545
+ // out. Clipping it, rather than using `display` or `visibility`, keeps it in
6546
+ // the accessibility tree. The declarations match the `extras-sr-only` mixin,
6547
+ // plus `clip-path`, which replaces the deprecated `clip`. The negative margin
6548
+ // pulls the 1px box back inside the page, so it doesn't add scrollable
6549
+ // overflow.
6550
+ const visuallyHidden = {
6551
+ position: 'absolute',
6552
+ width: '1px',
6553
+ height: '1px',
6554
+ padding: 0,
6555
+ margin: '-1px',
6556
+ overflow: 'hidden',
6557
+ clip: 'rect(0, 0, 0, 0)',
6558
+ clipPath: 'inset(50%)',
6559
+ whiteSpace: 'nowrap',
6560
+ border: 0,
6561
+ };
6562
+ /**
6563
+ * Works out what a status region says. Each item is announced once. Its
6564
+ * announcement is written `announceDelay` after it appears, or after the last
6565
+ * item to join it when several appear in quick succession, alongside whatever
6566
+ * the region still says. Once written, it stays for `announcementLifetime`,
6567
+ * whatever is written after it and whether or not its item goes in the
6568
+ * meantime. An item that goes before its announcement is written isn't
6569
+ * announced.
6570
+ *
6571
+ * Nothing is written in the commit that mounts the region or the item. A
6572
+ * screen reader reliably announces a polite live region whose content changes
6573
+ * after the region is already in the page, and not always one that arrives
6574
+ * with its content. By the time `describe` runs, an item's rendered text can
6575
+ * be read.
6576
+ *
6577
+ * @function useAnnouncements
6578
+ * @param items - The items to announce, in the order they were shown.
6579
+ * @param describe - The text to announce for an item, or nothing to skip it.
6580
+ * It runs once the item is on screen, and it must keep its identity between
6581
+ * renders (a module-level function or a `useCallback`), or every render
6582
+ * re-checks the items.
6583
+ * @returns The announcements to render, in the order they were written.
6584
+ */
6585
+ function useAnnouncements(items, describe) {
6586
+ // The ids of the items on screen as of the last change, every one of which
6587
+ // has been looked at for an announcement already.
6588
+ const onScreenIdsRef = React.useRef(new Set());
6589
+ // Clears that haven't run yet. They outlive the batch that started them, so
6590
+ // they're only stopped when the region unmounts.
6591
+ const clearTimersRef = React.useRef(new Set());
6592
+ const [announcements, setAnnouncements] = React.useState(noAnnouncementsYet);
6593
+ // Runs after the commit that puts an item on screen, which is the first
6594
+ // point its rendered text can be read.
6595
+ React.useEffect(() => {
6596
+ const announced = onScreenIdsRef.current;
6597
+ const appeared = items.filter(item => !announced.has(item.id));
6598
+ // Items that have gone drop out here too. Ids are never reused, so
6599
+ // forgetting them can't bring an old item back.
6600
+ onScreenIdsRef.current = new Set(items.map(item => item.id));
6601
+ const next = [];
6602
+ for (const item of appeared) {
6603
+ const text = describe(item);
6604
+ if (text)
6605
+ next.push({ id: item.id, text });
6606
+ }
6607
+ if (next.length > 0) {
6608
+ // eslint-disable-next-line react-hooks/set-state-in-effect -- an item's rendered text can only be read once it's on screen
6609
+ setAnnouncements(current => ({
6610
+ ...current,
6611
+ key: current.key + 1,
6612
+ // Announcements still waiting go out with the new ones. What's
6613
+ // already written stays put.
6614
+ waiting: [...current.waiting, ...next],
6615
+ }));
6616
+ }
6617
+ }, [items, describe]);
6618
+ // The write only touches the batch it was started for: a newer batch can
6619
+ // be set before the render that cancels this timer. Its clear is started
6620
+ // by the write itself, so it's timed from the write without waiting on a
6621
+ // render, and a newer batch arriving doesn't cut short what's written.
6622
+ const { key } = announcements;
6623
+ React.useEffect(() => {
6624
+ if (key === noAnnouncementsYet.key)
6625
+ return undefined;
6626
+ const clearTimers = clearTimersRef.current;
6627
+ const write = setTimeout(() => {
6628
+ // Whether an item is on screen is decided here, once. An item that
6629
+ // went before its announcement was due isn't announced, and one that
6630
+ // goes after keeps its announcement for the rest of its lifetime, when
6631
+ // the region holds the only copy of its text rather than a second one.
6632
+ const onScreen = onScreenIdsRef.current;
6633
+ setAnnouncements(current => current.key === key
6634
+ ? {
6635
+ ...current,
6636
+ waiting: noAnnouncements,
6637
+ written: [
6638
+ ...current.written,
6639
+ ...current.waiting
6640
+ .filter(announcement => onScreen.has(announcement.id))
6641
+ .map(announcement => ({ ...announcement, batch: key })),
6642
+ ],
6643
+ }
6644
+ : current);
6645
+ // eslint-disable-next-line @eslint-react/web-api-no-leaked-timeout -- it has to outlive this effect when a newer batch arrives, so the unmount effect below clears it
6646
+ const clear = setTimeout(() => {
6647
+ clearTimers.delete(clear);
6648
+ setAnnouncements(current => {
6649
+ const written = current.written.filter(announcement => announcement.batch !== key);
6650
+ return written.length === current.written.length
6651
+ ? current
6652
+ : { ...current, written };
6653
+ });
6654
+ }, announcementLifetime);
6655
+ clearTimers.add(clear);
6656
+ }, announceDelay);
6657
+ return () => clearTimeout(write);
6658
+ }, [key]);
6659
+ React.useEffect(() => {
6660
+ const clearTimers = clearTimersRef.current;
6661
+ return () => {
6662
+ clearTimers.forEach(clearTimeout);
6663
+ clearTimers.clear();
6664
+ };
6665
+ }, []);
6666
+ return announcements.written;
6667
+ }
6668
+ /**
6669
+ * Reads an element out roughly the way a screen reader would: its text, with
6670
+ * an element's `aria-label`, or an image's `alt`, standing in for what's
6671
+ * inside it, a break between block-level elements, and parts that are
6672
+ * `aria-hidden`, `hidden` or inline-styled `display: none` left out. It isn't
6673
+ * the full accessible name computation, so `aria-labelledby`, CSS-generated
6674
+ * content, content hidden by a stylesheet and other ways of naming or hiding
6675
+ * content aren't followed.
6676
+ *
6677
+ * @function spokenText
6678
+ * @param element - The element to read.
6679
+ * @returns Its text, with runs of whitespace collapsed and the ends trimmed.
6680
+ */
6681
+ function spokenText(element) {
6682
+ return readAloud(element).replace(/\s+/g, ' ').trim();
6683
+ }
6684
+ // Elements a browser displays as blocks by default, and `br`. A screen reader
6685
+ // pauses between them, so their text shouldn't run into the text beside them.
6686
+ // They're known by tag rather than by computed `display`, which would depend
6687
+ // on a stylesheet and on layout.
6688
+ const blockLevelTags = new Set([
6689
+ 'ADDRESS',
6690
+ 'ARTICLE',
6691
+ 'ASIDE',
6692
+ 'BLOCKQUOTE',
6693
+ 'BR',
6694
+ 'CAPTION',
6695
+ 'DD',
6696
+ 'DETAILS',
6697
+ 'DIALOG',
6698
+ 'DIV',
6699
+ 'DL',
6700
+ 'DT',
6701
+ 'FIELDSET',
6702
+ 'FIGCAPTION',
6703
+ 'FIGURE',
6704
+ 'FOOTER',
6705
+ 'FORM',
6706
+ 'H1',
6707
+ 'H2',
6708
+ 'H3',
6709
+ 'H4',
6710
+ 'H5',
6711
+ 'H6',
6712
+ 'HEADER',
6713
+ 'HGROUP',
6714
+ 'HR',
6715
+ 'LEGEND',
6716
+ 'LI',
6717
+ 'MAIN',
6718
+ 'MENU',
6719
+ 'NAV',
6720
+ 'OL',
6721
+ 'P',
6722
+ 'PRE',
6723
+ 'SEARCH',
6724
+ 'SECTION',
6725
+ 'SUMMARY',
6726
+ 'TABLE',
6727
+ 'TBODY',
6728
+ 'TD',
6729
+ 'TFOOT',
6730
+ 'TH',
6731
+ 'THEAD',
6732
+ 'TR',
6733
+ 'UL',
6734
+ ]);
6735
+ /**
6736
+ * Whether an element is left out of what a screen reader reads.
6737
+ *
6738
+ * @function isHidden
6739
+ * @param element - The element to check.
6740
+ * @returns True for `aria-hidden="true"`, the `hidden` attribute, and an
6741
+ * inline `display: none`, on an HTML, SVG or any other element with an
6742
+ * inline style.
6743
+ */
6744
+ const isHidden = (element) => element.getAttribute('aria-hidden') === 'true' ||
6745
+ element.hasAttribute('hidden') ||
6746
+ element.style?.display === 'none';
6747
+ /**
6748
+ * The text `spokenText` reads for an element, before its whitespace is
6749
+ * tidied.
6750
+ *
6751
+ * @function readAloud
6752
+ * @param element - The element to read.
6753
+ * @returns Its text.
6754
+ */
6755
+ function readAloud(element) {
6756
+ if (isHidden(element))
6757
+ return '';
6758
+ const name = element.getAttribute('aria-label') ||
6759
+ (element.tagName === 'IMG' ? element.getAttribute('alt') : null);
6760
+ const text = name ||
6761
+ Array.from(element.childNodes, child => {
6762
+ if (child instanceof Element)
6763
+ return readAloud(child);
6764
+ return child.nodeType === Node.TEXT_NODE ? child.textContent : '';
6765
+ }).join('');
6766
+ // A name stands in for the element's content, and a block's content stands
6767
+ // apart from what's around it, so both are padded to keep them from running
6768
+ // into the neighboring text. An inline run, like `<span>Up</span>` then
6769
+ // `<span>loaded</span>`, still reads as one word.
6770
+ return name || blockLevelTags.has(element.tagName) ? ` ${text} ` : text;
6771
+ }
6772
+ /**
6773
+ * A visually hidden, polite `status` live region that a programmatic
6774
+ * container announces its items through. The container renders it from the
6775
+ * moment it mounts, whether or not it has anything to show, so the region is
6776
+ * already in the page when an announcement is written into it.
6777
+ *
6778
+ * The announcements are this component's own state, so writing and clearing
6779
+ * them re-renders the region and leaves the container's items alone. Each one
6780
+ * is its own keyed node, so a re-render that leaves the announcements alone
6781
+ * leaves the region's content alone too, and nothing is read out twice.
6782
+ *
6783
+ * @function StatusRegion
6784
+ * @param props - The items to announce, and the text to announce for each.
6785
+ * @returns The region.
6786
+ */
6787
+ function StatusRegion({ items, describe, }) {
6788
+ const announcements = useAnnouncements(items, describe);
6789
+ // `role="status"` makes a region atomic unless it says otherwise, which
6790
+ // reads the whole region on every change. Each announcement is its own
6791
+ // node, and only additions are relevant by default, so with atomic off a
6792
+ // screen reader reads just what was written, and removing one announcement
6793
+ // doesn't read out the ones still there.
6794
+ return (jsxRuntime.jsx("div", { role: "status", "aria-live": "polite", "aria-atomic": "false", style: visuallyHidden, children: announcements.map(announcement => (jsxRuntime.jsx("div", { children: announcement.text }, announcement.id))) }));
6795
+ }
6796
+
6268
6797
  /**
6269
6798
  * The `Notification` component is a Bulma-styled alert/message area for providing feedback, warnings, or information to users.
6270
6799
  *
@@ -6288,7 +6817,7 @@ const Notification = ({ className, color, textColor, isLight, hasDelete, onDelet
6288
6817
  });
6289
6818
  const deleteClasses = usePrefixedClassNames('delete');
6290
6819
  const notificationClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
6291
- return (jsxRuntime.jsxs("div", { className: notificationClasses, ...rest, children: [hasDelete && (jsxRuntime.jsx("button", { className: deleteClasses, onClick: onDelete, "aria-label": "Close notification" })), children] }));
6820
+ return (jsxRuntime.jsxs("div", { className: notificationClasses, ...rest, children: [hasDelete && (jsxRuntime.jsx("button", { type: "button", className: deleteClasses, onClick: onDelete, "aria-label": "Close notification" })), children] }));
6292
6821
  };
6293
6822
  let notificationId = 0;
6294
6823
  const notificationListeners = new Set();
@@ -6307,9 +6836,9 @@ const getServerNotifications = () => noNotifications;
6307
6836
  // updates alone, so notifications raised before it mounted still appear. It is
6308
6837
  // replaced rather than mutated, and only when listeners are notified, because
6309
6838
  // useSyncExternalStore needs the same array back between changes. An empty
6310
- // list is `noNotifications` itself, so a container that hydrates with nothing
6311
- // to show reads the same snapshot the server did and has no reason to render
6312
- // again.
6839
+ // list is `noNotifications` itself, so an empty store reads the same snapshot
6840
+ // on the client as on the server, and a notify that leaves nothing showing
6841
+ // when nothing was showing doesn't render the container again.
6313
6842
  let visibleNotifications = noNotifications;
6314
6843
  const getVisibleNotifications = () => visibleNotifications;
6315
6844
  const notifyNotificationListeners = () => {
@@ -6420,20 +6949,32 @@ const notification = {
6420
6949
  return () => notificationListeners.delete(listener);
6421
6950
  },
6422
6951
  };
6952
+ /**
6953
+ * Whether a notification of this color announces itself as an alert, rather
6954
+ * than politely through NotificationContainer's status region.
6955
+ *
6956
+ * @function isUrgentColor
6957
+ * @param {NotificationOptions['color']} color - The notification's color.
6958
+ * @returns {boolean} True for `danger` and `warning`.
6959
+ */
6960
+ const isUrgentColor = (color) => color === 'danger' || color === 'warning';
6423
6961
  /**
6424
6962
  * Single auto-dismissing notification item used by NotificationContainer.
6425
6963
  *
6426
6964
  * @function
6427
- * @param {{ instance: NotificationInstance; onClose: (id: string) => void }} props - Component props.
6965
+ * @param {{ instance: NotificationInstance; onClose: (id: string) => void; registerMessage: (id: string, node: HTMLElement | null) => void }} props - Component props.
6428
6966
  * @returns {JSX.Element} The rendered notification item.
6429
6967
  */
6430
- const NotificationItem = ({ instance, onClose }) => {
6968
+ const NotificationItem = ({ instance, onClose, registerMessage }) => {
6431
6969
  const { message, color, isLight, duration = 3000, hasDelete = true, indefinite = false, pauseOnHover = true, } = instance.options;
6432
6970
  const [isPaused, setIsPaused] = React.useState(false);
6433
- const urgent = color === 'danger' || color === 'warning';
6971
+ const urgent = isUrgentColor(color);
6434
6972
  const handleClose = React.useCallback(() => {
6435
6973
  onClose(instance.id);
6436
6974
  }, [onClose, instance.id]);
6975
+ const messageRef = React.useCallback((node) => {
6976
+ registerMessage(instance.id, node);
6977
+ }, [registerMessage, instance.id]);
6437
6978
  // Auto-close timer
6438
6979
  React.useEffect(() => {
6439
6980
  if (indefinite || duration === 0 || isPaused)
@@ -6449,27 +6990,30 @@ const NotificationItem = ({ instance, onClose }) => {
6449
6990
  if (pauseOnHover)
6450
6991
  setIsPaused(false);
6451
6992
  }, [pauseOnHover]);
6452
- return (jsxRuntime.jsx(Notification, { color: color, isLight: isLight, hasDelete: hasDelete, onDelete: handleClose, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, style: { pointerEvents: 'auto' }, children: urgent ? (jsxRuntime.jsx("span", { role: "alert", "aria-live": "assertive", children: message })) : (jsxRuntime.jsx("span", { role: "status", "aria-live": "polite", children: message })) }));
6453
- };
6993
+ return (jsxRuntime.jsx(Notification, { color: color, isLight: isLight, hasDelete: hasDelete, onDelete: handleClose, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, style: { pointerEvents: 'auto' }, children: urgent ? (jsxRuntime.jsx("span", { role: "alert", "aria-live": "assertive", children: message })) : (jsxRuntime.jsx("span", { ref: messageRef, children: message })) }));
6994
+ };
6995
+ // The order NotificationContainer renders its stacks in: across the top of the
6996
+ // screen, then across the bottom.
6997
+ const notificationStackOrder = [
6998
+ 'top-left',
6999
+ 'top',
7000
+ 'top-right',
7001
+ 'bottom-left',
7002
+ 'bottom',
7003
+ 'bottom-right',
7004
+ ];
6454
7005
  /**
6455
- * Container component for rendering programmatic notifications.
6456
- * Place once at your app root to enable the notification API.
7006
+ * Inline style that fixes a stack of notifications to its place on the screen.
6457
7007
  *
6458
7008
  * @function
6459
- * @param {{ position?: NotificationPosition }} props - Container props.
6460
- * @returns {JSX.Element | null} The rendered notification container, or null if empty.
7009
+ * @param {NotificationPosition} position - Where the stack sits.
7010
+ * @returns {React.CSSProperties} The stack's style.
6461
7011
  */
6462
- const NotificationContainer = ({ position = 'top-right' }) => {
6463
- // Starts from the notifications already showing instead of an empty list,
6464
- // then follows changes.
6465
- const items = React.useSyncExternalStore(notification.subscribe, getVisibleNotifications, getServerNotifications);
6466
- if (typeof document === 'undefined' || items.length === 0) {
6467
- return null;
6468
- }
7012
+ const notificationStackStyle = (position) => {
6469
7013
  const isBottom = position.startsWith('bottom');
6470
7014
  const isCenter = position === 'top' || position === 'bottom';
6471
7015
  const isRight = position.endsWith('right');
6472
- const containerStyle = {
7016
+ return {
6473
7017
  position: 'fixed',
6474
7018
  zIndex: 100,
6475
7019
  display: 'flex',
@@ -6485,7 +7029,71 @@ const NotificationContainer = ({ position = 'top-right' }) => {
6485
7029
  ? { right: 0, alignItems: 'flex-end' }
6486
7030
  : { left: 0, alignItems: 'flex-start' }),
6487
7031
  };
6488
- return reactDom.createPortal(jsxRuntime.jsx("div", { style: containerStyle, children: items.map(item => (jsxRuntime.jsx(NotificationItem, { instance: item, onClose: notification.close }, item.id))) }), document.body);
7032
+ };
7033
+ /**
7034
+ * Container component for rendering programmatic notifications.
7035
+ * Place once at your app root to enable the notification API. A notification
7036
+ * shown with a `position` appears there, and one shown without goes to the
7037
+ * container's `position`, so the container renders a stack for each position
7038
+ * in use.
7039
+ *
7040
+ * It keeps a visually hidden `role="status"` live region in the page from the
7041
+ * moment it mounts, even while nothing is showing, and announces every
7042
+ * notification other than `danger` and `warning` through it, a moment after
7043
+ * the notification appears. Those notifications carry no `role="status"` of
7044
+ * their own, so `getByRole('status')` finds the region, not the notification.
7045
+ * The text stays in the region briefly, even if the notification closes in
7046
+ * the meantime, and while both are up the page holds two copies of it. A
7047
+ * polite notification that closes before its announcement is written, a
7048
+ * moment after it appears, isn't announced at all. `danger` and `warning`
7049
+ * notifications announce themselves as assertive alerts. The region is hidden
7050
+ * with inline styles, so it needs no stylesheet.
7051
+ *
7052
+ * @function
7053
+ * @param {{ position?: NotificationPosition }} props - Container props.
7054
+ * @returns {JSX.Element | null} The rendered notification container, or null on the server and while hydrating.
7055
+ */
7056
+ const NotificationContainer = ({ position = 'top-right' }) => {
7057
+ // Starts from the notifications already showing instead of an empty list,
7058
+ // then follows changes.
7059
+ const items = React.useSyncExternalStore(notification.subscribe, getVisibleNotifications, getServerNotifications);
7060
+ // A portal needs a document, and it has no server-rendered counterpart, so
7061
+ // the server render and the hydrating one render nothing, and the container
7062
+ // portals in from the render after hydration.
7063
+ const isHydrated = useIsHydrated();
7064
+ // The notifications the status region announces. Danger and warning
7065
+ // announce themselves.
7066
+ const politeItems = React.useMemo(() => items.filter(item => !isUrgentColor(item.options.color)), [items]);
7067
+ // Each polite notification's message element, so the text the region
7068
+ // announces is the text on screen, whatever the message renders.
7069
+ const messageNodesRef = React.useRef(new Map());
7070
+ const registerMessage = React.useCallback((id, node) => {
7071
+ if (node) {
7072
+ messageNodesRef.current.set(id, node);
7073
+ }
7074
+ else {
7075
+ messageNodesRef.current.delete(id);
7076
+ }
7077
+ }, []);
7078
+ // StatusRegion only describes polite notifications that are on screen, and
7079
+ // each of those registered its message element when it mounted. The
7080
+ // fallback is for a notification announced without being rendered, should
7081
+ // the container ever render fewer than it announces: a plain-text message
7082
+ // still says itself, and anything else is skipped.
7083
+ const describe = React.useCallback((item) => {
7084
+ const node = messageNodesRef.current.get(item.id);
7085
+ /* istanbul ignore if: every polite notification on screen registers its message element when it mounts */
7086
+ if (!node) {
7087
+ const { message } = item.options;
7088
+ return typeof message === 'string' ? message : null;
7089
+ }
7090
+ return spokenText(node);
7091
+ }, []);
7092
+ if (typeof document === 'undefined' || !isHydrated) {
7093
+ return null;
7094
+ }
7095
+ const stacks = groupIntoPositionStacks(items, item => item.options.position, position, notificationStackOrder);
7096
+ return reactDom.createPortal(jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [stacks.map(stack => (jsxRuntime.jsx("div", { style: notificationStackStyle(stack.position), children: stack.items.map(item => (jsxRuntime.jsx(NotificationItem, { instance: item, onClose: notification.close, registerMessage: registerMessage }, item.id))) }, stack.key))), jsxRuntime.jsx(StatusRegion, { items: politeItems, describe: describe })] }), document.body);
6489
7097
  };
6490
7098
 
6491
7099
  /**
@@ -6737,7 +7345,26 @@ const Tfoot = ({ className, children, ...props }) => {
6737
7345
  return (jsxRuntime.jsx("tfoot", { className: tfootClasses || undefined, ...rest, children: children }));
6738
7346
  };
6739
7347
 
6740
- /** Valid Bulma color values for table cells. */
7348
+ /**
7349
+ * The values the table `color` prop accepts, as a readonly tuple.
7350
+ *
7351
+ * `TableColor` is typed from it, and `Tr`, `Th` and `Td` all take a
7352
+ * `TableColor`, so the tuple and those props list the same values. Map over
7353
+ * it to build a color picker, or check a value that arrives at runtime before
7354
+ * passing it in: the components add no color class for a value outside the
7355
+ * tuple.
7356
+ *
7357
+ * @example
7358
+ * import { Tr, Td, validTableColors } from '@allxsmith/bestax-bulma';
7359
+ *
7360
+ * <Tr>
7361
+ * {validTableColors.map(color => (
7362
+ * <Td key={color} color={color}>
7363
+ * {color}
7364
+ * </Td>
7365
+ * ))}
7366
+ * </Tr>;
7367
+ */
6741
7368
  const validTableColors = [
6742
7369
  'primary',
6743
7370
  'link',
@@ -6894,7 +7521,7 @@ const Tag = ({ className, color, size, isLight, isRounded, isDelete, isHoverable
6894
7521
  });
6895
7522
  const tagClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
6896
7523
  if (isDelete) {
6897
- return (jsxRuntime.jsx("button", { className: tagClasses, onClick: onDelete, "aria-label": "Delete tag", ...rest }));
7524
+ return (jsxRuntime.jsx("button", { className: tagClasses, onClick: onDelete, ...rest, "aria-label": rest['aria-label'] || 'Delete tag', type: buttonType(rest.type) }));
6898
7525
  }
6899
7526
  return (jsxRuntime.jsx("span", { className: tagClasses, ...rest, children: children }));
6900
7527
  };
@@ -9076,8 +9703,7 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
9076
9703
  if (!isActive)
9077
9704
  return undefined;
9078
9705
  const handleClickOutside = (e) => {
9079
- if (containerRef.current &&
9080
- !containerRef.current.contains(e.target)) {
9706
+ if (containerRef.current && !isEventInside(e, containerRef.current)) {
9081
9707
  setIsActive(false);
9082
9708
  setHighlightedIndex(-1);
9083
9709
  }
@@ -13237,9 +13863,18 @@ const bulmaVarPropMap = Object.fromEntries(bulmaCssVars
13237
13863
  * real 0 keeps what
13238
13864
  * people see and makes it valid CSS. Keyed by the helper values, so adding
13239
13865
  * one means saying what it writes.
13866
+ *
13867
+ * The sizes write nothing and only add their class. Their classes read the
13868
+ * radius variables, so `normal` would point `--bulma-radius` at itself, which
13869
+ * is invalid and computes to 0, and `rounded` would turn every control inside
13870
+ * the Theme into a pill.
13240
13871
  */
13241
13872
  const radiusHelperVars = {
13242
13873
  radiusless: '0',
13874
+ small: undefined,
13875
+ normal: undefined,
13876
+ large: undefined,
13877
+ rounded: undefined,
13243
13878
  };
13244
13879
  /**
13245
13880
  * What `radius` writes to `--bulma-radius` on a Theme, or `undefined` when it
@@ -13266,7 +13901,7 @@ const themeRadiusVar = (radius) => {
13266
13901
  warnOnce('Theme:radius-variable', `[bestax-bulma] <Theme radius="${radius}">: setting --bulma-radius ` +
13267
13902
  'through the radius prop is deprecated and will stop working in a ' +
13268
13903
  'future major version. On Theme, as on every other component, radius ' +
13269
- `is the "${validRadii.join('", "')}" helper. Set the variable with ` +
13904
+ `is the border radius helper ("${validRadii.join('", "')}"). Set the variable with ` +
13270
13905
  `bulmaVars={{ '--bulma-radius': '${radius}' }} instead.`);
13271
13906
  return radius;
13272
13907
  };
@@ -13352,6 +13987,16 @@ const Theme = ({ bulmaVars = {}, children, className, isRoot = false, colorMode,
13352
13987
  const radiusHelper = validRadii.includes(radius)
13353
13988
  ? radius
13354
13989
  : undefined;
13990
+ // A radius size only adds its class, and a root Theme renders no wrapper to
13991
+ // carry one, so on `isRoot` it does nothing at all. Say so, as the other
13992
+ // radius routes that do not do what they look like do.
13993
+ if (isRoot && radiusHelper && radiusHelperVars[radiusHelper] === undefined) {
13994
+ warnOnce('Theme:root-radius-size', `[bestax-bulma] <Theme isRoot radius="${radiusHelper}">: a root Theme ` +
13995
+ `has no wrapper element for has-radius-${radiusHelper}, and the ` +
13996
+ 'radius sizes set no variable, so this does nothing. To round one ' +
13997
+ `element, put radius="${radiusHelper}" on it. To change the radius ` +
13998
+ "across the page, set bulmaVars={{ '--bulma-radius': '…' }} instead.");
13999
+ }
13355
14000
  // Extract Bulma variable props from restProps
13356
14001
  const { bulmaVarProps, otherProps } = React.useMemo(() => {
13357
14002
  const varProps = {};
@@ -13980,6 +14625,8 @@ exports.validAlignContents = validAlignContents;
13980
14625
  exports.validAlignItems = validAlignItems;
13981
14626
  exports.validAlignSelfs = validAlignSelfs;
13982
14627
  exports.validAlignments = validAlignments$1;
14628
+ exports.validAspectRatios = validAspectRatios;
14629
+ exports.validAxisOverflows = validAxisOverflows;
13983
14630
  exports.validColorShades = validColorShades;
13984
14631
  exports.validColors = validColors;
13985
14632
  exports.validCursors = validCursors;
@@ -13992,6 +14639,7 @@ exports.validFontFamilies = validFontFamilies;
13992
14639
  exports.validInteractions = validInteractions;
13993
14640
  exports.validJustifyContents = validJustifyContents;
13994
14641
  exports.validOverflows = validOverflows;
14642
+ exports.validPositions = validPositions;
13995
14643
  exports.validRadii = validRadii;
13996
14644
  exports.validResponsives = validResponsives;
13997
14645
  exports.validSchemeColors = validSchemeColors;