@allxsmith/bestax-bulma 5.18.2 → 5.20.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 (94) 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 +628 -52
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.esm.js +625 -53
  13. package/dist/index.esm.js.map +1 -1
  14. package/dist/types/components/Card.d.ts +2 -0
  15. package/dist/types/components/Carousel.d.ts +8 -2
  16. package/dist/types/components/Modal.d.ts +2 -0
  17. package/dist/types/components/Navbar.d.ts +24 -3
  18. package/dist/types/components/Sidebar.d.ts +2 -0
  19. package/dist/types/components/Steps.d.ts +5 -1
  20. package/dist/types/elements/Button.d.ts +5 -1
  21. package/dist/types/elements/Delete.d.ts +9 -1
  22. package/dist/types/elements/Link.d.ts +7 -1
  23. package/dist/types/elements/Loader.d.ts +39 -0
  24. package/dist/types/elements/Notification.d.ts +29 -3
  25. package/dist/types/elements/Tag.d.ts +7 -1
  26. package/dist/types/elements/Td.d.ts +21 -2
  27. package/dist/types/form/Autocomplete.d.ts +5 -1
  28. package/dist/types/form/Control.d.ts +5 -1
  29. package/dist/types/form/DateInput.d.ts +5 -1
  30. package/dist/types/form/DateInputBase.d.ts +9 -1
  31. package/dist/types/form/DateTimeInput.d.ts +5 -1
  32. package/dist/types/form/DateTimeInputBase.d.ts +11 -4
  33. package/dist/types/form/Input.d.ts +5 -1
  34. package/dist/types/form/Numberinput.d.ts +5 -1
  35. package/dist/types/form/Select.d.ts +5 -1
  36. package/dist/types/form/SelectBase.d.ts +5 -1
  37. package/dist/types/form/Taginput.d.ts +5 -1
  38. package/dist/types/form/TextArea.d.ts +5 -1
  39. package/dist/types/form/TimeInput.d.ts +5 -1
  40. package/dist/types/form/TimeInputBase.d.ts +4 -2
  41. package/dist/types/helpers/Theme.d.ts +6 -1
  42. package/dist/types/helpers/bulmaClassHelpers.d.ts +30 -6
  43. package/dist/types/helpers/shadowDom.d.ts +54 -0
  44. package/dist/types/helpers/statusRegion.d.ts +61 -0
  45. package/dist/types/helpers/useBulmaClasses.d.ts +1 -1
  46. package/dist/types/helpers/useOtherClasses.d.ts +61 -5
  47. package/dist/types/index.d.ts +1 -0
  48. package/dist/types-cjs/components/Card.d.ts +2 -0
  49. package/dist/types-cjs/components/Carousel.d.ts +8 -2
  50. package/dist/types-cjs/components/Modal.d.ts +2 -0
  51. package/dist/types-cjs/components/Navbar.d.ts +24 -3
  52. package/dist/types-cjs/components/Sidebar.d.ts +2 -0
  53. package/dist/types-cjs/components/Steps.d.ts +5 -1
  54. package/dist/types-cjs/elements/Button.d.ts +5 -1
  55. package/dist/types-cjs/elements/Delete.d.ts +9 -1
  56. package/dist/types-cjs/elements/Link.d.ts +7 -1
  57. package/dist/types-cjs/elements/Loader.d.ts +39 -0
  58. package/dist/types-cjs/elements/Notification.d.ts +29 -3
  59. package/dist/types-cjs/elements/Tag.d.ts +7 -1
  60. package/dist/types-cjs/elements/Td.d.ts +21 -2
  61. package/dist/types-cjs/form/Autocomplete.d.ts +5 -1
  62. package/dist/types-cjs/form/Control.d.ts +5 -1
  63. package/dist/types-cjs/form/DateInput.d.ts +5 -1
  64. package/dist/types-cjs/form/DateInputBase.d.ts +9 -1
  65. package/dist/types-cjs/form/DateTimeInput.d.ts +5 -1
  66. package/dist/types-cjs/form/DateTimeInputBase.d.ts +11 -4
  67. package/dist/types-cjs/form/Input.d.ts +5 -1
  68. package/dist/types-cjs/form/Numberinput.d.ts +5 -1
  69. package/dist/types-cjs/form/Select.d.ts +5 -1
  70. package/dist/types-cjs/form/SelectBase.d.ts +5 -1
  71. package/dist/types-cjs/form/Taginput.d.ts +5 -1
  72. package/dist/types-cjs/form/TextArea.d.ts +5 -1
  73. package/dist/types-cjs/form/TimeInput.d.ts +5 -1
  74. package/dist/types-cjs/form/TimeInputBase.d.ts +4 -2
  75. package/dist/types-cjs/helpers/Theme.d.ts +6 -1
  76. package/dist/types-cjs/helpers/bulmaClassHelpers.d.ts +30 -6
  77. package/dist/types-cjs/helpers/shadowDom.d.ts +54 -0
  78. package/dist/types-cjs/helpers/statusRegion.d.ts +61 -0
  79. package/dist/types-cjs/helpers/useBulmaClasses.d.ts +1 -1
  80. package/dist/types-cjs/helpers/useOtherClasses.d.ts +61 -5
  81. package/dist/types-cjs/index.d.ts +1 -0
  82. package/dist/versions/bestax-no-dark-mode.css +1 -1
  83. package/dist/versions/bestax-no-dark-mode.css.map +1 -1
  84. package/dist/versions/bestax-no-helpers-prefixed.css +1 -1
  85. package/dist/versions/bestax-no-helpers-prefixed.css.map +1 -1
  86. package/dist/versions/bestax-no-helpers.css +1 -1
  87. package/dist/versions/bestax-no-helpers.css.map +1 -1
  88. package/dist/versions/bestax-prefixed.css +1 -1
  89. package/dist/versions/bestax-prefixed.css.map +1 -1
  90. package/package.json +1 -1
  91. package/src/scss/elements/_index.scss +1 -0
  92. package/src/scss/elements/_loader.scss +23 -0
  93. package/src/scss/form/_dateinput.scss +36 -10
  94. 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,
@@ -2009,7 +2102,7 @@ const CardHeaderIcon = ({ className, children, color, bgColor, textColor, ...pro
2009
2102
  backgroundColor: bgColor,
2010
2103
  ...props,
2011
2104
  });
2012
- 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 }));
2013
2106
  };
2014
2107
  /**
2015
2108
  * Card image compound component. Wraps children in a `.card-image` element.
@@ -2107,6 +2200,72 @@ const Card = withSubComponents(CardComponent, {
2107
2200
  FooterItem: CardFooterItem,
2108
2201
  }, 'Card');
2109
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
+
2110
2269
  /**
2111
2270
  * Checks if code is running in a browser environment.
2112
2271
  * @deprecated Dropdown's internal SSR guard, exported by accident; it will
@@ -2187,8 +2346,7 @@ const DropdownComponent = React.forwardRef(function DropdownComponent({ label, c
2187
2346
  if (!isBrowser$1(window, document))
2188
2347
  return;
2189
2348
  const handleClick = (e) => {
2190
- /* istanbul ignore next: dropdownRef.current is never null while the listener is attached */
2191
- if (!dropdownRef.current?.contains(e.target)) {
2349
+ if (!isEventInside(e, dropdownRef.current)) {
2192
2350
  setActive(false);
2193
2351
  onActiveChange?.(false);
2194
2352
  }
@@ -2204,10 +2362,16 @@ const DropdownComponent = React.forwardRef(function DropdownComponent({ label, c
2204
2362
  setActive(newActive);
2205
2363
  onActiveChange?.(newActive);
2206
2364
  };
2207
- const handleMenuClick = () => {
2365
+ const handleMenuClick = (e) => {
2208
2366
  if (closeOnClick) {
2209
2367
  setActive(false);
2210
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
+ }
2211
2375
  }
2212
2376
  };
2213
2377
  const getMenuItems = () => {
@@ -2268,6 +2432,11 @@ const DropdownComponent = React.forwardRef(function DropdownComponent({ label, c
2268
2432
  case 'Enter':
2269
2433
  case ' ':
2270
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;
2271
2440
  if (!active) {
2272
2441
  pendingFocusRef.current = 'first';
2273
2442
  setActive(true);
@@ -2305,7 +2474,7 @@ const DropdownComponent = React.forwardRef(function DropdownComponent({ label, c
2305
2474
  const items = getMenuItems();
2306
2475
  if (!items.length)
2307
2476
  return;
2308
- const currentIndex = items.indexOf(document.activeElement);
2477
+ const currentIndex = items.indexOf(getActiveElementInTree(e.currentTarget));
2309
2478
  switch (e.key) {
2310
2479
  case 'ArrowDown': {
2311
2480
  e.preventDefault();
@@ -2793,7 +2962,7 @@ const ModalCard = withSubComponents(ModalCardComponent, {
2793
2962
  */
2794
2963
  const ModalClose = ({ className, size = 'large', variant = 'delete', ...props }) => {
2795
2964
  const classes = classNames(usePrefixedClassNames(variant === 'delete' ? 'delete' : 'modal-close', variant === 'floating' && size && { [`is-${size}`]: true }), className);
2796
- 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) }));
2797
2966
  };
2798
2967
  /**
2799
2968
  * The `Modal` component provides an empty, accessible overlay for arbitrary content — for a ready-made confirm or alert, reach for `Dialog` instead.
@@ -2910,7 +3079,7 @@ const ModalRoot = React.forwardRef(function ModalRoot({ active, isActive, onClos
2910
3079
  // Keep Tab within the modal — `aria-modal` hides the rest of the page
2911
3080
  // from assistive technology, so the keyboard order has to agree.
2912
3081
  const focusable = getTabbable(node);
2913
- const activeElement = document.activeElement;
3082
+ const activeElement = getActiveElementInTree(node);
2914
3083
  if (focusable.length === 0) {
2915
3084
  e.preventDefault();
2916
3085
  node.focus();
@@ -2937,7 +3106,10 @@ const ModalRoot = React.forwardRef(function ModalRoot({ active, isActive, onClos
2937
3106
  React.useEffect(() => {
2938
3107
  if (!isModalActive)
2939
3108
  return undefined;
2940
- 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();
2941
3113
  const node = modalRootRef.current;
2942
3114
  const focusable = node ? getTabbable(node)[0] : undefined;
2943
3115
  (focusable ?? node)?.focus();
@@ -2945,7 +3117,7 @@ const ModalRoot = React.forwardRef(function ModalRoot({ active, isActive, onClos
2945
3117
  // Only hand focus back if this modal still owns it: closing a background
2946
3118
  // modal must not pull focus out of one that is still open on top. A
2947
3119
  // removed subtree leaves focus on <body>, which still counts as ours.
2948
- const activeElement = document.activeElement;
3120
+ const activeElement = getActiveElementInTree(node);
2949
3121
  if (activeElement &&
2950
3122
  activeElement !== document.body &&
2951
3123
  !node?.contains(activeElement)) {
@@ -3063,7 +3235,7 @@ const NavbarItem = React.forwardRef(function NavbarItem(itemProps, ref) {
3063
3235
  });
3064
3236
  return (jsxRuntime.jsx(Component, { ref: ref, className: classNames(usePrefixedClassNames('navbar-item', {
3065
3237
  'is-active': active,
3066
- }), bulmaHelperClasses, className), ...rest, children: children }));
3238
+ }), bulmaHelperClasses, className), ...rest, ...(Component === 'button' ? { type: buttonType(rest.type) } : {}), children: children }));
3067
3239
  });
3068
3240
  NavbarItem.displayName = 'NavbarItem';
3069
3241
  /**
@@ -3079,9 +3251,9 @@ const NavbarBurger = React.forwardRef(function NavbarBurger({ className, active,
3079
3251
  color: textColor ?? color,
3080
3252
  ...props,
3081
3253
  });
3082
- 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', {
3083
3255
  'is-active': active,
3084
- }), 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] }));
3085
3257
  });
3086
3258
  NavbarBurger.displayName = 'NavbarBurger';
3087
3259
  /**
@@ -3211,7 +3383,7 @@ const NavbarLink = React.forwardRef(function NavbarLink(linkProps, ref) {
3211
3383
  tabIndex: 0,
3212
3384
  onClick: handleClick,
3213
3385
  }),
3214
- }), children: children }));
3386
+ }), ...(Component === 'button' ? { type: buttonType(rest.type) } : {}), children: children }));
3215
3387
  });
3216
3388
  NavbarLink.displayName = 'NavbarLink';
3217
3389
  /**
@@ -4535,7 +4707,7 @@ const StepsComponent = ({ value = 0, items, size, color, hasMarker = true, anima
4535
4707
  }
4536
4708
  return null;
4537
4709
  };
4538
- 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' })] }))] }));
4539
4711
  };
4540
4712
  // Attach Step as static property
4541
4713
  const Steps = withSubComponents(StepsComponent, { Step }, 'Steps');
@@ -4704,7 +4876,7 @@ const SidebarTitle = ({ className, children, ...props }) => {
4704
4876
  */
4705
4877
  const SidebarClose = ({ className, children, ...props }) => {
4706
4878
  const closeClass = usePrefixedClassNames('sidebar-close');
4707
- 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 }));
4708
4880
  };
4709
4881
  /**
4710
4882
  * Main content area of the sidebar.
@@ -4834,7 +5006,7 @@ const Toast = React.forwardRef(({ message, type = 'default', actionType, positio
4834
5006
  if (!dismissible || !isVisible)
4835
5007
  return undefined;
4836
5008
  const handleDocumentClick = (e) => {
4837
- if (toastRef.current && !toastRef.current.contains(e.target)) {
5009
+ if (toastRef.current && !isEventInside(e, toastRef.current)) {
4838
5010
  handleClose();
4839
5011
  }
4840
5012
  };
@@ -5637,7 +5809,8 @@ const Carousel = React.forwardRef(({ value: controlledValue, autoplay = false, i
5637
5809
  // Keyboard navigation
5638
5810
  React.useEffect(() => {
5639
5811
  const handleKeyDown = (e) => {
5640
- if (!carouselRef.current?.contains(document.activeElement))
5812
+ const node = carouselRef.current;
5813
+ if (!node?.contains(getActiveElementInTree(node)))
5641
5814
  return;
5642
5815
  switch (e.key) {
5643
5816
  case 'ArrowLeft':
@@ -5770,7 +5943,7 @@ const Carousel = React.forwardRef(({ value: controlledValue, autoplay = false, i
5770
5943
  transition: isDragging || skipTransition
5771
5944
  ? 'none'
5772
5945
  : 'transform 0.3s ease-in-out',
5773
- }, 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', {
5774
5947
  'is-active': index === activeIndex,
5775
5948
  }), onClick: () => goToSlide(index), role: "tab", "aria-selected": index === activeIndex, "aria-label": `Go to slide ${index + 1}` }, index))) }))] }));
5776
5949
  });
@@ -6078,12 +6251,14 @@ const Content = ({ className, textColor, color, bgColor, size, children, ...prop
6078
6251
  /**
6079
6252
  * The `Delete` component provides a Bulma-styled close/delete button for dismissing modals, notifications, tags, messages, and more.
6080
6253
  *
6254
+ * It renders `type="button"`, so a delete button inside a form does not submit it.
6255
+ *
6081
6256
  * @function
6082
6257
  * @param {DeleteProps} props - Props for the Delete component.
6083
6258
  * @returns {JSX.Element} The rendered delete button.
6084
6259
  * @see {@link https://bulma.io/documentation/elements/delete/ | Bulma Delete documentation}
6085
6260
  */
6086
- 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 }) => {
6087
6262
  /**
6088
6263
  * Generates Bulma helper classes and separates out remaining props.
6089
6264
  */
@@ -6097,7 +6272,7 @@ const Delete = ({ className, textColor, color, bgColor, onClick, size, ariaLabel
6097
6272
  'is-disabled': disabled,
6098
6273
  });
6099
6274
  const classes = classNames(bulmaClasses, bulmaHelperClasses, className);
6100
- 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) }));
6101
6276
  };
6102
6277
 
6103
6278
  /**
@@ -6319,7 +6494,7 @@ const Link = React.forwardRef(function Link(linkProps, ref) {
6319
6494
  'is-active': isActive,
6320
6495
  });
6321
6496
  const linkClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
6322
- 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 }));
6323
6498
  });
6324
6499
  Link.displayName = 'Link';
6325
6500
 
@@ -6344,6 +6519,310 @@ const ListItem = ({ className, textColor, bgColor, children, ...props }) => {
6344
6519
  return (jsxRuntime.jsx("li", { className: itemClasses || undefined, ...rest, children: children }));
6345
6520
  };
6346
6521
 
6522
+ /**
6523
+ * The `Loader` component renders Bulma's `.loader`, a small spinning ring for inline loading states.
6524
+ *
6525
+ * The ring is `1em` square, so `textSize` scales it. It is exposed as an
6526
+ * indeterminate `progressbar` named by `ariaLabel`, and it stops spinning
6527
+ * under `prefers-reduced-motion: reduce` while staying drawn.
6528
+ *
6529
+ * @function
6530
+ * @param {LoaderProps} props - Props for the Loader component.
6531
+ * @param {React.Ref<HTMLSpanElement>} ref - Forwarded ref to the loader element.
6532
+ * @returns {JSX.Element} The rendered loader element.
6533
+ *
6534
+ * @example
6535
+ * <Loader />
6536
+ *
6537
+ * @example
6538
+ * <Loader textSize="3" ariaLabel="Saving" />
6539
+ */
6540
+ const Loader = React.forwardRef(({ className, ariaLabel = 'Loading', ...props }, ref) => {
6541
+ const { bulmaHelperClasses, rest } = useBulmaClasses(props);
6542
+ const loaderClass = usePrefixedClassNames('loader');
6543
+ // role="progressbar", not role="status": a status region mounted together
6544
+ // with its text is not reliably announced, so the loader claims only what
6545
+ // it can deliver, a named indicator found by reading the page. No
6546
+ // aria-valuenow, because the progress is indeterminate.
6547
+ return (jsxRuntime.jsx("span", { ref: ref, className: classNames(loaderClass, bulmaHelperClasses, className), role: "progressbar", "aria-label": ariaLabel, ...rest }));
6548
+ });
6549
+ Loader.displayName = 'Loader';
6550
+
6551
+ /**
6552
+ * How long after an item appears its announcement is written, in ms. The wait
6553
+ * lets a screen reader register a region the container has only just mounted,
6554
+ * and items shown in quick succession are written, and read out, together.
6555
+ */
6556
+ const announceDelay = 100;
6557
+ /**
6558
+ * How long an announcement stays in the region once written, in ms. That's
6559
+ * long enough for a screen reader to pick it up. Clearing it after keeps a
6560
+ * second copy of the item's text from sitting in the page, where someone
6561
+ * reading through the page, or a test looking the text up, would find it
6562
+ * again.
6563
+ */
6564
+ const announcementLifetime = 1000;
6565
+ const noAnnouncements = [];
6566
+ const nothingWritten = [];
6567
+ const noAnnouncementsYet = {
6568
+ key: 0,
6569
+ waiting: noAnnouncements,
6570
+ written: nothingWritten,
6571
+ };
6572
+ // Hides the region with inline styles rather than a class, so it needs no
6573
+ // stylesheet, including the helper classes a modular Bulma build can leave
6574
+ // out. Clipping it, rather than using `display` or `visibility`, keeps it in
6575
+ // the accessibility tree. The declarations match the `extras-sr-only` mixin,
6576
+ // plus `clip-path`, which replaces the deprecated `clip`. The negative margin
6577
+ // pulls the 1px box back inside the page, so it doesn't add scrollable
6578
+ // overflow.
6579
+ const visuallyHidden = {
6580
+ position: 'absolute',
6581
+ width: '1px',
6582
+ height: '1px',
6583
+ padding: 0,
6584
+ margin: '-1px',
6585
+ overflow: 'hidden',
6586
+ clip: 'rect(0, 0, 0, 0)',
6587
+ clipPath: 'inset(50%)',
6588
+ whiteSpace: 'nowrap',
6589
+ border: 0,
6590
+ };
6591
+ /**
6592
+ * Works out what a status region says. Each item is announced once. Its
6593
+ * announcement is written `announceDelay` after it appears, or after the last
6594
+ * item to join it when several appear in quick succession, alongside whatever
6595
+ * the region still says. Once written, it stays for `announcementLifetime`,
6596
+ * whatever is written after it and whether or not its item goes in the
6597
+ * meantime. An item that goes before its announcement is written isn't
6598
+ * announced.
6599
+ *
6600
+ * Nothing is written in the commit that mounts the region or the item. A
6601
+ * screen reader reliably announces a polite live region whose content changes
6602
+ * after the region is already in the page, and not always one that arrives
6603
+ * with its content. By the time `describe` runs, an item's rendered text can
6604
+ * be read.
6605
+ *
6606
+ * @function useAnnouncements
6607
+ * @param items - The items to announce, in the order they were shown.
6608
+ * @param describe - The text to announce for an item, or nothing to skip it.
6609
+ * It runs once the item is on screen, and it must keep its identity between
6610
+ * renders (a module-level function or a `useCallback`), or every render
6611
+ * re-checks the items.
6612
+ * @returns The announcements to render, in the order they were written.
6613
+ */
6614
+ function useAnnouncements(items, describe) {
6615
+ // The ids of the items on screen as of the last change, every one of which
6616
+ // has been looked at for an announcement already.
6617
+ const onScreenIdsRef = React.useRef(new Set());
6618
+ // Clears that haven't run yet. They outlive the batch that started them, so
6619
+ // they're only stopped when the region unmounts.
6620
+ const clearTimersRef = React.useRef(new Set());
6621
+ const [announcements, setAnnouncements] = React.useState(noAnnouncementsYet);
6622
+ // Runs after the commit that puts an item on screen, which is the first
6623
+ // point its rendered text can be read.
6624
+ React.useEffect(() => {
6625
+ const announced = onScreenIdsRef.current;
6626
+ const appeared = items.filter(item => !announced.has(item.id));
6627
+ // Items that have gone drop out here too. Ids are never reused, so
6628
+ // forgetting them can't bring an old item back.
6629
+ onScreenIdsRef.current = new Set(items.map(item => item.id));
6630
+ const next = [];
6631
+ for (const item of appeared) {
6632
+ const text = describe(item);
6633
+ if (text)
6634
+ next.push({ id: item.id, text });
6635
+ }
6636
+ if (next.length > 0) {
6637
+ // eslint-disable-next-line react-hooks/set-state-in-effect -- an item's rendered text can only be read once it's on screen
6638
+ setAnnouncements(current => ({
6639
+ ...current,
6640
+ key: current.key + 1,
6641
+ // Announcements still waiting go out with the new ones. What's
6642
+ // already written stays put.
6643
+ waiting: [...current.waiting, ...next],
6644
+ }));
6645
+ }
6646
+ }, [items, describe]);
6647
+ // The write only touches the batch it was started for: a newer batch can
6648
+ // be set before the render that cancels this timer. Its clear is started
6649
+ // by the write itself, so it's timed from the write without waiting on a
6650
+ // render, and a newer batch arriving doesn't cut short what's written.
6651
+ const { key } = announcements;
6652
+ React.useEffect(() => {
6653
+ if (key === noAnnouncementsYet.key)
6654
+ return undefined;
6655
+ const clearTimers = clearTimersRef.current;
6656
+ const write = setTimeout(() => {
6657
+ // Whether an item is on screen is decided here, once. An item that
6658
+ // went before its announcement was due isn't announced, and one that
6659
+ // goes after keeps its announcement for the rest of its lifetime, when
6660
+ // the region holds the only copy of its text rather than a second one.
6661
+ const onScreen = onScreenIdsRef.current;
6662
+ setAnnouncements(current => current.key === key
6663
+ ? {
6664
+ ...current,
6665
+ waiting: noAnnouncements,
6666
+ written: [
6667
+ ...current.written,
6668
+ ...current.waiting
6669
+ .filter(announcement => onScreen.has(announcement.id))
6670
+ .map(announcement => ({ ...announcement, batch: key })),
6671
+ ],
6672
+ }
6673
+ : current);
6674
+ // 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
6675
+ const clear = setTimeout(() => {
6676
+ clearTimers.delete(clear);
6677
+ setAnnouncements(current => {
6678
+ const written = current.written.filter(announcement => announcement.batch !== key);
6679
+ return written.length === current.written.length
6680
+ ? current
6681
+ : { ...current, written };
6682
+ });
6683
+ }, announcementLifetime);
6684
+ clearTimers.add(clear);
6685
+ }, announceDelay);
6686
+ return () => clearTimeout(write);
6687
+ }, [key]);
6688
+ React.useEffect(() => {
6689
+ const clearTimers = clearTimersRef.current;
6690
+ return () => {
6691
+ clearTimers.forEach(clearTimeout);
6692
+ clearTimers.clear();
6693
+ };
6694
+ }, []);
6695
+ return announcements.written;
6696
+ }
6697
+ /**
6698
+ * Reads an element out roughly the way a screen reader would: its text, with
6699
+ * an element's `aria-label`, or an image's `alt`, standing in for what's
6700
+ * inside it, a break between block-level elements, and parts that are
6701
+ * `aria-hidden`, `hidden` or inline-styled `display: none` left out. It isn't
6702
+ * the full accessible name computation, so `aria-labelledby`, CSS-generated
6703
+ * content, content hidden by a stylesheet and other ways of naming or hiding
6704
+ * content aren't followed.
6705
+ *
6706
+ * @function spokenText
6707
+ * @param element - The element to read.
6708
+ * @returns Its text, with runs of whitespace collapsed and the ends trimmed.
6709
+ */
6710
+ function spokenText(element) {
6711
+ return readAloud(element).replace(/\s+/g, ' ').trim();
6712
+ }
6713
+ // Elements a browser displays as blocks by default, and `br`. A screen reader
6714
+ // pauses between them, so their text shouldn't run into the text beside them.
6715
+ // They're known by tag rather than by computed `display`, which would depend
6716
+ // on a stylesheet and on layout.
6717
+ const blockLevelTags = new Set([
6718
+ 'ADDRESS',
6719
+ 'ARTICLE',
6720
+ 'ASIDE',
6721
+ 'BLOCKQUOTE',
6722
+ 'BR',
6723
+ 'CAPTION',
6724
+ 'DD',
6725
+ 'DETAILS',
6726
+ 'DIALOG',
6727
+ 'DIV',
6728
+ 'DL',
6729
+ 'DT',
6730
+ 'FIELDSET',
6731
+ 'FIGCAPTION',
6732
+ 'FIGURE',
6733
+ 'FOOTER',
6734
+ 'FORM',
6735
+ 'H1',
6736
+ 'H2',
6737
+ 'H3',
6738
+ 'H4',
6739
+ 'H5',
6740
+ 'H6',
6741
+ 'HEADER',
6742
+ 'HGROUP',
6743
+ 'HR',
6744
+ 'LEGEND',
6745
+ 'LI',
6746
+ 'MAIN',
6747
+ 'MENU',
6748
+ 'NAV',
6749
+ 'OL',
6750
+ 'P',
6751
+ 'PRE',
6752
+ 'SEARCH',
6753
+ 'SECTION',
6754
+ 'SUMMARY',
6755
+ 'TABLE',
6756
+ 'TBODY',
6757
+ 'TD',
6758
+ 'TFOOT',
6759
+ 'TH',
6760
+ 'THEAD',
6761
+ 'TR',
6762
+ 'UL',
6763
+ ]);
6764
+ /**
6765
+ * Whether an element is left out of what a screen reader reads.
6766
+ *
6767
+ * @function isHidden
6768
+ * @param element - The element to check.
6769
+ * @returns True for `aria-hidden="true"`, the `hidden` attribute, and an
6770
+ * inline `display: none`, on an HTML, SVG or any other element with an
6771
+ * inline style.
6772
+ */
6773
+ const isHidden = (element) => element.getAttribute('aria-hidden') === 'true' ||
6774
+ element.hasAttribute('hidden') ||
6775
+ element.style?.display === 'none';
6776
+ /**
6777
+ * The text `spokenText` reads for an element, before its whitespace is
6778
+ * tidied.
6779
+ *
6780
+ * @function readAloud
6781
+ * @param element - The element to read.
6782
+ * @returns Its text.
6783
+ */
6784
+ function readAloud(element) {
6785
+ if (isHidden(element))
6786
+ return '';
6787
+ const name = element.getAttribute('aria-label') ||
6788
+ (element.tagName === 'IMG' ? element.getAttribute('alt') : null);
6789
+ const text = name ||
6790
+ Array.from(element.childNodes, child => {
6791
+ if (child instanceof Element)
6792
+ return readAloud(child);
6793
+ return child.nodeType === Node.TEXT_NODE ? child.textContent : '';
6794
+ }).join('');
6795
+ // A name stands in for the element's content, and a block's content stands
6796
+ // apart from what's around it, so both are padded to keep them from running
6797
+ // into the neighboring text. An inline run, like `<span>Up</span>` then
6798
+ // `<span>loaded</span>`, still reads as one word.
6799
+ return name || blockLevelTags.has(element.tagName) ? ` ${text} ` : text;
6800
+ }
6801
+ /**
6802
+ * A visually hidden, polite `status` live region that a programmatic
6803
+ * container announces its items through. The container renders it from the
6804
+ * moment it mounts, whether or not it has anything to show, so the region is
6805
+ * already in the page when an announcement is written into it.
6806
+ *
6807
+ * The announcements are this component's own state, so writing and clearing
6808
+ * them re-renders the region and leaves the container's items alone. Each one
6809
+ * is its own keyed node, so a re-render that leaves the announcements alone
6810
+ * leaves the region's content alone too, and nothing is read out twice.
6811
+ *
6812
+ * @function StatusRegion
6813
+ * @param props - The items to announce, and the text to announce for each.
6814
+ * @returns The region.
6815
+ */
6816
+ function StatusRegion({ items, describe, }) {
6817
+ const announcements = useAnnouncements(items, describe);
6818
+ // `role="status"` makes a region atomic unless it says otherwise, which
6819
+ // reads the whole region on every change. Each announcement is its own
6820
+ // node, and only additions are relevant by default, so with atomic off a
6821
+ // screen reader reads just what was written, and removing one announcement
6822
+ // doesn't read out the ones still there.
6823
+ 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))) }));
6824
+ }
6825
+
6347
6826
  /**
6348
6827
  * The `Notification` component is a Bulma-styled alert/message area for providing feedback, warnings, or information to users.
6349
6828
  *
@@ -6367,7 +6846,7 @@ const Notification = ({ className, color, textColor, isLight, hasDelete, onDelet
6367
6846
  });
6368
6847
  const deleteClasses = usePrefixedClassNames('delete');
6369
6848
  const notificationClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
6370
- return (jsxRuntime.jsxs("div", { className: notificationClasses, ...rest, children: [hasDelete && (jsxRuntime.jsx("button", { className: deleteClasses, onClick: onDelete, "aria-label": "Close notification" })), children] }));
6849
+ return (jsxRuntime.jsxs("div", { className: notificationClasses, ...rest, children: [hasDelete && (jsxRuntime.jsx("button", { type: "button", className: deleteClasses, onClick: onDelete, "aria-label": "Close notification" })), children] }));
6371
6850
  };
6372
6851
  let notificationId = 0;
6373
6852
  const notificationListeners = new Set();
@@ -6386,9 +6865,9 @@ const getServerNotifications = () => noNotifications;
6386
6865
  // updates alone, so notifications raised before it mounted still appear. It is
6387
6866
  // replaced rather than mutated, and only when listeners are notified, because
6388
6867
  // useSyncExternalStore needs the same array back between changes. An empty
6389
- // list is `noNotifications` itself, so a container that hydrates with nothing
6390
- // to show reads the same snapshot the server did and has no reason to render
6391
- // again.
6868
+ // list is `noNotifications` itself, so an empty store reads the same snapshot
6869
+ // on the client as on the server, and a notify that leaves nothing showing
6870
+ // when nothing was showing doesn't render the container again.
6392
6871
  let visibleNotifications = noNotifications;
6393
6872
  const getVisibleNotifications = () => visibleNotifications;
6394
6873
  const notifyNotificationListeners = () => {
@@ -6499,20 +6978,32 @@ const notification = {
6499
6978
  return () => notificationListeners.delete(listener);
6500
6979
  },
6501
6980
  };
6981
+ /**
6982
+ * Whether a notification of this color announces itself as an alert, rather
6983
+ * than politely through NotificationContainer's status region.
6984
+ *
6985
+ * @function isUrgentColor
6986
+ * @param {NotificationOptions['color']} color - The notification's color.
6987
+ * @returns {boolean} True for `danger` and `warning`.
6988
+ */
6989
+ const isUrgentColor = (color) => color === 'danger' || color === 'warning';
6502
6990
  /**
6503
6991
  * Single auto-dismissing notification item used by NotificationContainer.
6504
6992
  *
6505
6993
  * @function
6506
- * @param {{ instance: NotificationInstance; onClose: (id: string) => void }} props - Component props.
6994
+ * @param {{ instance: NotificationInstance; onClose: (id: string) => void; registerMessage: (id: string, node: HTMLElement | null) => void }} props - Component props.
6507
6995
  * @returns {JSX.Element} The rendered notification item.
6508
6996
  */
6509
- const NotificationItem = ({ instance, onClose }) => {
6997
+ const NotificationItem = ({ instance, onClose, registerMessage }) => {
6510
6998
  const { message, color, isLight, duration = 3000, hasDelete = true, indefinite = false, pauseOnHover = true, } = instance.options;
6511
6999
  const [isPaused, setIsPaused] = React.useState(false);
6512
- const urgent = color === 'danger' || color === 'warning';
7000
+ const urgent = isUrgentColor(color);
6513
7001
  const handleClose = React.useCallback(() => {
6514
7002
  onClose(instance.id);
6515
7003
  }, [onClose, instance.id]);
7004
+ const messageRef = React.useCallback((node) => {
7005
+ registerMessage(instance.id, node);
7006
+ }, [registerMessage, instance.id]);
6516
7007
  // Auto-close timer
6517
7008
  React.useEffect(() => {
6518
7009
  if (indefinite || duration === 0 || isPaused)
@@ -6528,7 +7019,7 @@ const NotificationItem = ({ instance, onClose }) => {
6528
7019
  if (pauseOnHover)
6529
7020
  setIsPaused(false);
6530
7021
  }, [pauseOnHover]);
6531
- 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 })) }));
7022
+ 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 })) }));
6532
7023
  };
6533
7024
  // The order NotificationContainer renders its stacks in: across the top of the
6534
7025
  // screen, then across the bottom.
@@ -6575,19 +7066,63 @@ const notificationStackStyle = (position) => {
6575
7066
  * container's `position`, so the container renders a stack for each position
6576
7067
  * in use.
6577
7068
  *
7069
+ * It keeps a visually hidden `role="status"` live region in the page from the
7070
+ * moment it mounts, even while nothing is showing, and announces every
7071
+ * notification other than `danger` and `warning` through it, a moment after
7072
+ * the notification appears. Those notifications carry no `role="status"` of
7073
+ * their own, so `getByRole('status')` finds the region, not the notification.
7074
+ * The text stays in the region briefly, even if the notification closes in
7075
+ * the meantime, and while both are up the page holds two copies of it. A
7076
+ * polite notification that closes before its announcement is written, a
7077
+ * moment after it appears, isn't announced at all. `danger` and `warning`
7078
+ * notifications announce themselves as assertive alerts. The region is hidden
7079
+ * with inline styles, so it needs no stylesheet.
7080
+ *
6578
7081
  * @function
6579
7082
  * @param {{ position?: NotificationPosition }} props - Container props.
6580
- * @returns {JSX.Element | null} The rendered notification container, or null if empty.
7083
+ * @returns {JSX.Element | null} The rendered notification container, or null on the server and while hydrating.
6581
7084
  */
6582
7085
  const NotificationContainer = ({ position = 'top-right' }) => {
6583
7086
  // Starts from the notifications already showing instead of an empty list,
6584
7087
  // then follows changes.
6585
7088
  const items = React.useSyncExternalStore(notification.subscribe, getVisibleNotifications, getServerNotifications);
6586
- if (typeof document === 'undefined' || items.length === 0) {
7089
+ // A portal needs a document, and it has no server-rendered counterpart, so
7090
+ // the server render and the hydrating one render nothing, and the container
7091
+ // portals in from the render after hydration.
7092
+ const isHydrated = useIsHydrated();
7093
+ // The notifications the status region announces. Danger and warning
7094
+ // announce themselves.
7095
+ const politeItems = React.useMemo(() => items.filter(item => !isUrgentColor(item.options.color)), [items]);
7096
+ // Each polite notification's message element, so the text the region
7097
+ // announces is the text on screen, whatever the message renders.
7098
+ const messageNodesRef = React.useRef(new Map());
7099
+ const registerMessage = React.useCallback((id, node) => {
7100
+ if (node) {
7101
+ messageNodesRef.current.set(id, node);
7102
+ }
7103
+ else {
7104
+ messageNodesRef.current.delete(id);
7105
+ }
7106
+ }, []);
7107
+ // StatusRegion only describes polite notifications that are on screen, and
7108
+ // each of those registered its message element when it mounted. The
7109
+ // fallback is for a notification announced without being rendered, should
7110
+ // the container ever render fewer than it announces: a plain-text message
7111
+ // still says itself, and anything else is skipped.
7112
+ const describe = React.useCallback((item) => {
7113
+ const node = messageNodesRef.current.get(item.id);
7114
+ /* istanbul ignore if: every polite notification on screen registers its message element when it mounts */
7115
+ if (!node) {
7116
+ const { message } = item.options;
7117
+ return typeof message === 'string' ? message : null;
7118
+ }
7119
+ return spokenText(node);
7120
+ }, []);
7121
+ if (typeof document === 'undefined' || !isHydrated) {
6587
7122
  return null;
6588
7123
  }
6589
7124
  const stacks = groupIntoPositionStacks(items, item => item.options.position, position, notificationStackOrder);
6590
- return reactDom.createPortal(stacks.map(stack => (jsxRuntime.jsx("div", { style: notificationStackStyle(stack.position), children: stack.items.map(item => (jsxRuntime.jsx(NotificationItem, { instance: item, onClose: notification.close }, item.id))) }, stack.key))), document.body);
7125
+ 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);
6591
7126
  };
6592
7127
 
6593
7128
  /**
@@ -6839,7 +7374,26 @@ const Tfoot = ({ className, children, ...props }) => {
6839
7374
  return (jsxRuntime.jsx("tfoot", { className: tfootClasses || undefined, ...rest, children: children }));
6840
7375
  };
6841
7376
 
6842
- /** Valid Bulma color values for table cells. */
7377
+ /**
7378
+ * The values the table `color` prop accepts, as a readonly tuple.
7379
+ *
7380
+ * `TableColor` is typed from it, and `Tr`, `Th` and `Td` all take a
7381
+ * `TableColor`, so the tuple and those props list the same values. Map over
7382
+ * it to build a color picker, or check a value that arrives at runtime before
7383
+ * passing it in: the components add no color class for a value outside the
7384
+ * tuple.
7385
+ *
7386
+ * @example
7387
+ * import { Tr, Td, validTableColors } from '@allxsmith/bestax-bulma';
7388
+ *
7389
+ * <Tr>
7390
+ * {validTableColors.map(color => (
7391
+ * <Td key={color} color={color}>
7392
+ * {color}
7393
+ * </Td>
7394
+ * ))}
7395
+ * </Tr>;
7396
+ */
6843
7397
  const validTableColors = [
6844
7398
  'primary',
6845
7399
  'link',
@@ -6996,7 +7550,7 @@ const Tag = ({ className, color, size, isLight, isRounded, isDelete, isHoverable
6996
7550
  });
6997
7551
  const tagClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
6998
7552
  if (isDelete) {
6999
- return (jsxRuntime.jsx("button", { className: tagClasses, onClick: onDelete, "aria-label": "Delete tag", ...rest }));
7553
+ return (jsxRuntime.jsx("button", { className: tagClasses, onClick: onDelete, ...rest, "aria-label": rest['aria-label'] || 'Delete tag', type: buttonType(rest.type) }));
7000
7554
  }
7001
7555
  return (jsxRuntime.jsx("span", { className: tagClasses, ...rest, children: children }));
7002
7556
  };
@@ -9178,8 +9732,7 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
9178
9732
  if (!isActive)
9179
9733
  return undefined;
9180
9734
  const handleClickOutside = (e) => {
9181
- if (containerRef.current &&
9182
- !containerRef.current.contains(e.target)) {
9735
+ if (containerRef.current && !isEventInside(e, containerRef.current)) {
9183
9736
  setIsActive(false);
9184
9737
  setHighlightedIndex(-1);
9185
9738
  }
@@ -13339,9 +13892,18 @@ const bulmaVarPropMap = Object.fromEntries(bulmaCssVars
13339
13892
  * real 0 keeps what
13340
13893
  * people see and makes it valid CSS. Keyed by the helper values, so adding
13341
13894
  * one means saying what it writes.
13895
+ *
13896
+ * The sizes write nothing and only add their class. Their classes read the
13897
+ * radius variables, so `normal` would point `--bulma-radius` at itself, which
13898
+ * is invalid and computes to 0, and `rounded` would turn every control inside
13899
+ * the Theme into a pill.
13342
13900
  */
13343
13901
  const radiusHelperVars = {
13344
13902
  radiusless: '0',
13903
+ small: undefined,
13904
+ normal: undefined,
13905
+ large: undefined,
13906
+ rounded: undefined,
13345
13907
  };
13346
13908
  /**
13347
13909
  * What `radius` writes to `--bulma-radius` on a Theme, or `undefined` when it
@@ -13368,7 +13930,7 @@ const themeRadiusVar = (radius) => {
13368
13930
  warnOnce('Theme:radius-variable', `[bestax-bulma] <Theme radius="${radius}">: setting --bulma-radius ` +
13369
13931
  'through the radius prop is deprecated and will stop working in a ' +
13370
13932
  'future major version. On Theme, as on every other component, radius ' +
13371
- `is the "${validRadii.join('", "')}" helper. Set the variable with ` +
13933
+ `is the border radius helper ("${validRadii.join('", "')}"). Set the variable with ` +
13372
13934
  `bulmaVars={{ '--bulma-radius': '${radius}' }} instead.`);
13373
13935
  return radius;
13374
13936
  };
@@ -13454,6 +14016,16 @@ const Theme = ({ bulmaVars = {}, children, className, isRoot = false, colorMode,
13454
14016
  const radiusHelper = validRadii.includes(radius)
13455
14017
  ? radius
13456
14018
  : undefined;
14019
+ // A radius size only adds its class, and a root Theme renders no wrapper to
14020
+ // carry one, so on `isRoot` it does nothing at all. Say so, as the other
14021
+ // radius routes that do not do what they look like do.
14022
+ if (isRoot && radiusHelper && radiusHelperVars[radiusHelper] === undefined) {
14023
+ warnOnce('Theme:root-radius-size', `[bestax-bulma] <Theme isRoot radius="${radiusHelper}">: a root Theme ` +
14024
+ `has no wrapper element for has-radius-${radiusHelper}, and the ` +
14025
+ 'radius sizes set no variable, so this does nothing. To round one ' +
14026
+ `element, put radius="${radiusHelper}" on it. To change the radius ` +
14027
+ "across the page, set bulmaVars={{ '--bulma-radius': '…' }} instead.");
14028
+ }
13457
14029
  // Extract Bulma variable props from restProps
13458
14030
  const { bulmaVarProps, otherProps } = React.useMemo(() => {
13459
14031
  const varProps = {};
@@ -13962,6 +14534,7 @@ exports.LevelRight = LevelRight;
13962
14534
  exports.Link = Link;
13963
14535
  exports.LinkButton = LinkButton;
13964
14536
  exports.ListItem = ListItem;
14537
+ exports.Loader = Loader;
13965
14538
  exports.Loading = Loading;
13966
14539
  exports.Media = Media;
13967
14540
  exports.MediaContent = MediaContent;
@@ -14082,6 +14655,8 @@ exports.validAlignContents = validAlignContents;
14082
14655
  exports.validAlignItems = validAlignItems;
14083
14656
  exports.validAlignSelfs = validAlignSelfs;
14084
14657
  exports.validAlignments = validAlignments$1;
14658
+ exports.validAspectRatios = validAspectRatios;
14659
+ exports.validAxisOverflows = validAxisOverflows;
14085
14660
  exports.validColorShades = validColorShades;
14086
14661
  exports.validColors = validColors;
14087
14662
  exports.validCursors = validCursors;
@@ -14094,6 +14669,7 @@ exports.validFontFamilies = validFontFamilies;
14094
14669
  exports.validInteractions = validInteractions;
14095
14670
  exports.validJustifyContents = validJustifyContents;
14096
14671
  exports.validOverflows = validOverflows;
14672
+ exports.validPositions = validPositions;
14097
14673
  exports.validRadii = validRadii;
14098
14674
  exports.validResponsives = validResponsives;
14099
14675
  exports.validSchemeColors = validSchemeColors;