@allxsmith/bestax-bulma 5.32.0 → 5.33.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/bestax.css +1 -1
  2. package/dist/bestax.css.map +1 -1
  3. package/dist/esm/components/Avatars.js +4 -21
  4. package/dist/esm/components/Avatars.js.map +1 -1
  5. package/dist/esm/components/Collapses.js +1 -20
  6. package/dist/esm/components/Collapses.js.map +1 -1
  7. package/dist/esm/elements/Kbd.js +86 -0
  8. package/dist/esm/elements/Kbd.js.map +1 -0
  9. package/dist/esm/form/Autocomplete.js +59 -14
  10. package/dist/esm/form/Autocomplete.js.map +1 -1
  11. package/dist/esm/helpers/flattenChildren.js +35 -0
  12. package/dist/esm/helpers/flattenChildren.js.map +1 -0
  13. package/dist/esm/helpers/statusRegion.js +19 -12
  14. package/dist/esm/helpers/statusRegion.js.map +1 -1
  15. package/dist/extras.css +1 -1
  16. package/dist/extras.css.map +1 -1
  17. package/dist/index.cjs +254 -134
  18. package/dist/index.cjs.map +1 -1
  19. package/dist/index.esm.js +1 -0
  20. package/dist/index.esm.js.map +1 -1
  21. package/dist/types/elements/Kbd.d.ts +100 -0
  22. package/dist/types/form/Autocomplete.d.ts +18 -3
  23. package/dist/types/helpers/flattenChildren.d.ts +16 -0
  24. package/dist/types/helpers/statusRegion.d.ts +4 -4
  25. package/dist/types/index.d.ts +1 -0
  26. package/dist/types-cjs/elements/Kbd.d.ts +100 -0
  27. package/dist/types-cjs/form/Autocomplete.d.ts +18 -3
  28. package/dist/types-cjs/helpers/flattenChildren.d.ts +16 -0
  29. package/dist/types-cjs/helpers/statusRegion.d.ts +4 -4
  30. package/dist/types-cjs/index.d.ts +1 -0
  31. package/dist/versions/bestax-no-dark-mode.css +1 -1
  32. package/dist/versions/bestax-no-dark-mode.css.map +1 -1
  33. package/dist/versions/bestax-no-helpers-prefixed.css +1 -1
  34. package/dist/versions/bestax-no-helpers-prefixed.css.map +1 -1
  35. package/dist/versions/bestax-no-helpers.css +1 -1
  36. package/dist/versions/bestax-no-helpers.css.map +1 -1
  37. package/dist/versions/bestax-prefixed.css +1 -1
  38. package/dist/versions/bestax-prefixed.css.map +1 -1
  39. package/package.json +1 -1
  40. package/src/scss/elements/_index.scss +1 -0
  41. package/src/scss/elements/_kbd.scss +86 -0
package/dist/index.cjs CHANGED
@@ -1762,26 +1762,36 @@ const Avatar = React.forwardRef(function Avatar(avatarProps, ref) {
1762
1762
  });
1763
1763
  Avatar.displayName = 'Avatar';
1764
1764
 
1765
+ // INTERNAL: deliberately not exported from src/index.ts.
1765
1766
  /**
1766
- * Flattens Fragment children (recursively) into a flat element array so they
1767
- * participate in max clamping and size/shape injection. Children.toArray keys
1768
- * restart at ".0" inside each fragment, so fragment children are re-keyed by
1769
- * prefixing the fragment's own key — otherwise a fragment sibling of a direct
1770
- * child would collide (both ".0") and trigger duplicate-key warnings.
1767
+ * A component's children as a flat list, with each Fragment opened up
1768
+ * (recursively) so the items inside one count one by one rather than as the
1769
+ * Fragment. `Children.toArray` already drops `null`, `undefined` and
1770
+ * booleans and flattens arrays, but it keeps a Fragment whole, so an empty
1771
+ * one would still count as an item and two items in one would count as one.
1772
+ *
1773
+ * `Children.toArray` keys restart at ".0" inside each Fragment, so the items
1774
+ * in one are re-keyed with the Fragment's own key as a prefix. Otherwise an
1775
+ * item in a Fragment and a sibling outside it could share a key.
1776
+ *
1777
+ * Text and numbers pass through as they are, so a caller that wants only
1778
+ * elements filters with `React.isValidElement`.
1771
1779
  */
1772
- function flattenChildren$1(children, keyPrefix = '') {
1780
+ function flattenChildren(children, keyPrefix = '') {
1773
1781
  return React.Children.toArray(children).flatMap(child => {
1774
1782
  if (!React.isValidElement(child))
1775
- return [];
1783
+ return [child];
1776
1784
  if (child.type === React.Fragment) {
1777
- return flattenChildren$1(child.props.children, keyPrefix + child.key);
1785
+ return flattenChildren(child.props.children, keyPrefix + child.key);
1778
1786
  }
1779
- const el = child;
1780
1787
  return [
1781
- keyPrefix ? React.cloneElement(el, { key: keyPrefix + el.key }) : el,
1788
+ keyPrefix
1789
+ ? React.cloneElement(child, { key: keyPrefix + child.key })
1790
+ : child,
1782
1791
  ];
1783
1792
  });
1784
1793
  }
1794
+
1785
1795
  /**
1786
1796
  * The `Avatars` component renders an overlapping/stacked group of `Avatar`s, the "members" list pattern.
1787
1797
  *
@@ -1808,7 +1818,9 @@ const AvatarsComponent = ({ className, max, size, shape, spacing = 'md', spaced
1808
1818
  : undefined;
1809
1819
  const surplusClass = usePrefixedClassNames('is-surplus');
1810
1820
  const combinedClasses = classNames(avatarsClasses, bulmaHelperClasses, className);
1811
- const childArray = flattenChildren$1(children);
1821
+ // Fragments are opened up, so the avatars inside one count toward `max` and
1822
+ // take the group's size and shape one by one. Text between them is dropped.
1823
+ const childArray = flattenChildren(children).filter((child) => React.isValidElement(child));
1812
1824
  const maxCount = typeof max === 'number' && Number.isInteger(max) && max >= 0
1813
1825
  ? max
1814
1826
  : undefined;
@@ -5295,26 +5307,6 @@ function toIndexes(value) {
5295
5307
  return value;
5296
5308
  return typeof value === 'number' ? [value] : [];
5297
5309
  }
5298
- /**
5299
- * Flattens Fragment children (recursively) so each item inside one gets its
5300
- * own index rather than sharing the fragment's. Children.toArray keys restart
5301
- * at ".0" inside each fragment, so those children are re-keyed with the
5302
- * fragment's own key as a prefix, the way Avatars does it.
5303
- */
5304
- function flattenChildren(children, keyPrefix = '') {
5305
- return React.Children.toArray(children).flatMap(child => {
5306
- if (!React.isValidElement(child))
5307
- return [child];
5308
- if (child.type === React.Fragment) {
5309
- return flattenChildren(child.props.children, keyPrefix + child.key);
5310
- }
5311
- return [
5312
- keyPrefix
5313
- ? React.cloneElement(child, { key: keyPrefix + child.key })
5314
- : child,
5315
- ];
5316
- });
5317
- }
5318
5310
  /**
5319
5311
  * The `Collapses` component groups `Collapse` items into an accordion that keeps one item open at a time, or any number with `multiple`.
5320
5312
  *
@@ -9151,83 +9143,6 @@ const Image = ({ as, className, textColor, color, bgColor, size, isRounded, isRe
9151
9143
  return (jsxRuntime.jsx(Tag, { className: imageClasses, ...rest, children: content }));
9152
9144
  };
9153
9145
 
9154
- /**
9155
- * The `Link` component renders a styled anchor (`<a>`) element with Bulma helper class integration.
9156
- *
9157
- * @function
9158
- * @param {LinkProps} props - Props for the Link component.
9159
- * @param {React.Ref} ref - Forwarded ref to the element `as` renders.
9160
- * @returns {JSX.Element} The rendered anchor, or whatever `as` names.
9161
- * @see {@link https://bulma.io/documentation/elements/content/ | Bulma Content documentation}
9162
- */
9163
- const Link = React.forwardRef(function Link(linkProps, ref) {
9164
- const { className, textColor, bgColor, isActive, as: Component = 'a', children, ...props } = linkProps;
9165
- /**
9166
- * Generates Bulma helper classes and separates out remaining props.
9167
- */
9168
- const { bulmaHelperClasses, rest } = useBulmaClasses({
9169
- color: textColor,
9170
- backgroundColor: bgColor,
9171
- ...props,
9172
- });
9173
- const bulmaClasses = usePrefixedClassNames({
9174
- 'is-active': isActive,
9175
- });
9176
- const linkClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
9177
- return (jsxRuntime.jsx(Component, { ref: ref, className: linkClasses || undefined, ...rest, ...(Component === 'button' ? { type: buttonType(rest.type) } : {}), children: children }));
9178
- });
9179
- Link.displayName = 'Link';
9180
-
9181
- /**
9182
- * The `ListItem` component renders a styled list item (`<li>`) element with Bulma helper class integration.
9183
- *
9184
- * @function
9185
- * @param {ListItemProps} props - Props for the ListItem component.
9186
- * @returns {JSX.Element} The rendered li element.
9187
- */
9188
- const ListItem = ({ className, textColor, bgColor, children, ...props }) => {
9189
- /**
9190
- * Generates Bulma helper classes and separates out remaining props.
9191
- */
9192
- const { bulmaHelperClasses, rest } = useBulmaClasses({
9193
- color: textColor,
9194
- backgroundColor: bgColor,
9195
- ...props,
9196
- });
9197
- const bulmaClasses = usePrefixedClassNames();
9198
- const itemClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
9199
- return (jsxRuntime.jsx("li", { className: itemClasses || undefined, ...rest, children: children }));
9200
- };
9201
-
9202
- /**
9203
- * The `Loader` component renders Bulma's `.loader`, a small spinning ring for inline loading states.
9204
- *
9205
- * The ring is `1em` square, so `textSize` scales it. It is exposed as an
9206
- * indeterminate `progressbar` named by `ariaLabel`, and it stops spinning
9207
- * under `prefers-reduced-motion: reduce` while staying drawn.
9208
- *
9209
- * @function
9210
- * @param {LoaderProps} props - Props for the Loader component.
9211
- * @param {React.Ref<HTMLSpanElement>} ref - Forwarded ref to the loader element.
9212
- * @returns {JSX.Element} The rendered loader element.
9213
- *
9214
- * @example
9215
- * <Loader />
9216
- *
9217
- * @example
9218
- * <Loader textSize="3" ariaLabel="Saving" />
9219
- */
9220
- const Loader = React.forwardRef(({ className, ariaLabel = 'Loading', ...props }, ref) => {
9221
- const { bulmaHelperClasses, rest } = useBulmaClasses(props);
9222
- const loaderClass = usePrefixedClassNames('loader');
9223
- // role="progressbar", not role="status": a status region mounted together
9224
- // with its text is not reliably announced, so the loader claims only what
9225
- // it can deliver, a named indicator found by reading the page. No
9226
- // aria-valuenow, because the progress is indeterminate.
9227
- return (jsxRuntime.jsx("span", { ref: ref, className: classNames(loaderClass, bulmaHelperClasses, className), role: "progressbar", "aria-label": ariaLabel, ...rest }));
9228
- });
9229
- Loader.displayName = 'Loader';
9230
-
9231
9146
  /**
9232
9147
  * How long after an item appears its announcement is written, in ms. The wait
9233
9148
  * lets a screen reader register a region the container has only just mounted,
@@ -9270,7 +9185,8 @@ const visuallyHidden = {
9270
9185
  border: 0,
9271
9186
  };
9272
9187
  /**
9273
- * Works out what a status region says. Each item is announced once. Its
9188
+ * Works out what a status region says. Each item is announced once each time
9189
+ * it appears, so one that goes and comes back is announced again. Its
9274
9190
  * announcement is written `announceDelay` after it appears, or after the last
9275
9191
  * item to join it when several appear in quick succession, alongside whatever
9276
9192
  * the region still says. Once written, it stays for `announcementLifetime`,
@@ -9305,8 +9221,8 @@ function useAnnouncements(items, describe) {
9305
9221
  React.useEffect(() => {
9306
9222
  const announced = onScreenIdsRef.current;
9307
9223
  const appeared = items.filter(item => !announced.has(item.id));
9308
- // Items that have gone drop out here too. Ids are never reused, so
9309
- // forgetting them can't bring an old item back.
9224
+ // Items that have gone drop out here too, so one that comes back has
9225
+ // appeared again.
9310
9226
  onScreenIdsRef.current = new Set(items.map(item => item.id));
9311
9227
  const next = [];
9312
9228
  for (const item of appeared) {
@@ -9319,9 +9235,13 @@ function useAnnouncements(items, describe) {
9319
9235
  setAnnouncements(current => ({
9320
9236
  ...current,
9321
9237
  key: current.key + 1,
9322
- // Announcements still waiting go out with the new ones. What's
9323
- // already written stays put.
9324
- waiting: [...current.waiting, ...next],
9238
+ // Announcements still waiting go out with the new ones, apart from
9239
+ // one whose item came back before it was written, which the new one
9240
+ // replaces. What's already written stays put.
9241
+ waiting: [
9242
+ ...current.waiting.filter(waiting => !next.some(({ id }) => id === waiting.id)),
9243
+ ...next,
9244
+ ],
9325
9245
  }));
9326
9246
  }
9327
9247
  }, [items, describe]);
@@ -9480,10 +9400,10 @@ function readAloud(element) {
9480
9400
  return name || blockLevelTags.has(element.tagName) ? ` ${text} ` : text;
9481
9401
  }
9482
9402
  /**
9483
- * A visually hidden, polite `status` live region that a programmatic
9484
- * container announces its items through. The container renders it from the
9485
- * moment it mounts, whether or not it has anything to show, so the region is
9486
- * already in the page when an announcement is written into it.
9403
+ * A visually hidden, polite `status` live region that a container announces
9404
+ * its items through. The container renders it from the moment it mounts,
9405
+ * whether or not it has anything to show, so the region is already in the
9406
+ * page when an announcement is written into it.
9487
9407
  *
9488
9408
  * The announcements are this component's own state, so writing and clearing
9489
9409
  * them re-renders the region and leaves the container's items alone. Each one
@@ -9500,10 +9420,164 @@ function StatusRegion({ items, describe, }) {
9500
9420
  // reads the whole region on every change. Each announcement is its own
9501
9421
  // node, and only additions are relevant by default, so with atomic off a
9502
9422
  // screen reader reads just what was written, and removing one announcement
9503
- // doesn't read out the ones still there.
9504
- 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))) }));
9423
+ // doesn't read out the ones still there. A node is keyed by its batch as
9424
+ // well as its item, since an item that comes back can be written again
9425
+ // while what it said before is still there.
9426
+ 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.batch}:${announcement.id}`))) }));
9505
9427
  }
9506
9428
 
9429
+ /**
9430
+ * Keys pressed together or one after another, in the outer `<kbd>` HTML gives a combination, with the separator between each pair as text.
9431
+ *
9432
+ * It doesn't wrap in the middle and isn't drawn as a key itself. It sets no
9433
+ * font size, so the keys in it are the size of a key on its own.
9434
+ *
9435
+ * @function
9436
+ * @param {KbdGroupProps} props - Props for Kbd.Group.
9437
+ * @param {React.Ref<HTMLElement>} ref - Forwarded ref to the outer `<kbd>`.
9438
+ * @returns {JSX.Element} The rendered combination.
9439
+ *
9440
+ * @example
9441
+ * <Kbd.Group>
9442
+ * <Kbd>Ctrl</Kbd>
9443
+ * <Kbd>Shift</Kbd>
9444
+ * <Kbd>P</Kbd>
9445
+ * </Kbd.Group>
9446
+ *
9447
+ * @example
9448
+ * <Kbd.Group separator="then">
9449
+ * <Kbd>G</Kbd>
9450
+ * <Kbd>I</Kbd>
9451
+ * </Kbd.Group>
9452
+ */
9453
+ const KbdGroup = React.forwardRef(({ className, textColor, separator = '+', children, ...props }, ref) => {
9454
+ const { bulmaHelperClasses, rest } = useBulmaClasses({
9455
+ color: textColor,
9456
+ ...props,
9457
+ });
9458
+ const groupClass = usePrefixedClassNames('kbd-group');
9459
+ const separatorClass = usePrefixedClassNames('kbd-separator');
9460
+ // Null, undefined and booleans drop out, and Fragments are opened up, so
9461
+ // an empty Fragment leaves no separator behind and the keys in one are
9462
+ // separated like the rest. The spaces are part of the text, so a screen
9463
+ // reader reads "Ctrl plus S" rather than one run-together word, whatever
9464
+ // the CSS does.
9465
+ const keys = flattenChildren(children);
9466
+ return (jsxRuntime.jsx("kbd", { ref: ref, className: classNames(groupClass, bulmaHelperClasses, className), ...rest, children: keys.map((key, index) => (jsxRuntime.jsxs(React.Fragment, { children: [index > 0 && (jsxRuntime.jsx("span", { className: separatorClass, children: ` ${separator} ` })), key] }, React.isValidElement(key) && key.key != null ? key.key : index))) }));
9467
+ });
9468
+ KbdGroup.displayName = 'Kbd.Group';
9469
+ /**
9470
+ * The `Kbd` component renders a `<kbd>` drawn as a keycap, for a key the reader presses.
9471
+ *
9472
+ * It only shows the key: it doesn't listen for it. The key takes its colors
9473
+ * from the text it sits in, and its size in `em`, so it fits a paragraph, a
9474
+ * title or a filled button as it is, and `textSize` sizes it outright. For a
9475
+ * combination or a sequence, put the keys in a `Kbd.Group`. What tells
9476
+ * assistive tech a control has a shortcut is `aria-keyshortcuts` on that
9477
+ * control, with `Kbd` as the hint people see next to it.
9478
+ *
9479
+ * @function
9480
+ * @param {KbdProps} props - Props for the Kbd component.
9481
+ * @param {React.Ref<HTMLElement>} ref - Forwarded ref to the `<kbd>`.
9482
+ * @returns {JSX.Element} The rendered key.
9483
+ *
9484
+ * @example
9485
+ * <Kbd>Esc</Kbd>
9486
+ *
9487
+ * @example
9488
+ * <Kbd label="Command">⌘</Kbd>
9489
+ */
9490
+ const KbdRoot = React.forwardRef(({ className, textColor, bgColor, label, children, ...props }, ref) => {
9491
+ const { bulmaHelperClasses, rest } = useBulmaClasses({
9492
+ color: textColor,
9493
+ backgroundColor: bgColor,
9494
+ ...props,
9495
+ });
9496
+ const kbdClass = usePrefixedClassNames('kbd');
9497
+ // The name is text rather than an aria-label, which ARIA in HTML
9498
+ // prohibits on kbd. Its styles are inline, so it stays hidden in a
9499
+ // modular build that leaves out Bulma's helper classes.
9500
+ return (jsxRuntime.jsx("kbd", { ref: ref, className: classNames(kbdClass, bulmaHelperClasses, className), ...rest, children: label ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("span", { "aria-hidden": "true", children: children }), jsxRuntime.jsx("span", { style: visuallyHidden, children: label })] })) : (children) }));
9501
+ });
9502
+ const Kbd = withSubComponents(KbdRoot, { Group: KbdGroup }, 'Kbd');
9503
+
9504
+ /**
9505
+ * The `Link` component renders a styled anchor (`<a>`) element with Bulma helper class integration.
9506
+ *
9507
+ * @function
9508
+ * @param {LinkProps} props - Props for the Link component.
9509
+ * @param {React.Ref} ref - Forwarded ref to the element `as` renders.
9510
+ * @returns {JSX.Element} The rendered anchor, or whatever `as` names.
9511
+ * @see {@link https://bulma.io/documentation/elements/content/ | Bulma Content documentation}
9512
+ */
9513
+ const Link = React.forwardRef(function Link(linkProps, ref) {
9514
+ const { className, textColor, bgColor, isActive, as: Component = 'a', children, ...props } = linkProps;
9515
+ /**
9516
+ * Generates Bulma helper classes and separates out remaining props.
9517
+ */
9518
+ const { bulmaHelperClasses, rest } = useBulmaClasses({
9519
+ color: textColor,
9520
+ backgroundColor: bgColor,
9521
+ ...props,
9522
+ });
9523
+ const bulmaClasses = usePrefixedClassNames({
9524
+ 'is-active': isActive,
9525
+ });
9526
+ const linkClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
9527
+ return (jsxRuntime.jsx(Component, { ref: ref, className: linkClasses || undefined, ...rest, ...(Component === 'button' ? { type: buttonType(rest.type) } : {}), children: children }));
9528
+ });
9529
+ Link.displayName = 'Link';
9530
+
9531
+ /**
9532
+ * The `ListItem` component renders a styled list item (`<li>`) element with Bulma helper class integration.
9533
+ *
9534
+ * @function
9535
+ * @param {ListItemProps} props - Props for the ListItem component.
9536
+ * @returns {JSX.Element} The rendered li element.
9537
+ */
9538
+ const ListItem = ({ className, textColor, bgColor, children, ...props }) => {
9539
+ /**
9540
+ * Generates Bulma helper classes and separates out remaining props.
9541
+ */
9542
+ const { bulmaHelperClasses, rest } = useBulmaClasses({
9543
+ color: textColor,
9544
+ backgroundColor: bgColor,
9545
+ ...props,
9546
+ });
9547
+ const bulmaClasses = usePrefixedClassNames();
9548
+ const itemClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
9549
+ return (jsxRuntime.jsx("li", { className: itemClasses || undefined, ...rest, children: children }));
9550
+ };
9551
+
9552
+ /**
9553
+ * The `Loader` component renders Bulma's `.loader`, a small spinning ring for inline loading states.
9554
+ *
9555
+ * The ring is `1em` square, so `textSize` scales it. It is exposed as an
9556
+ * indeterminate `progressbar` named by `ariaLabel`, and it stops spinning
9557
+ * under `prefers-reduced-motion: reduce` while staying drawn.
9558
+ *
9559
+ * @function
9560
+ * @param {LoaderProps} props - Props for the Loader component.
9561
+ * @param {React.Ref<HTMLSpanElement>} ref - Forwarded ref to the loader element.
9562
+ * @returns {JSX.Element} The rendered loader element.
9563
+ *
9564
+ * @example
9565
+ * <Loader />
9566
+ *
9567
+ * @example
9568
+ * <Loader textSize="3" ariaLabel="Saving" />
9569
+ */
9570
+ const Loader = React.forwardRef(({ className, ariaLabel = 'Loading', ...props }, ref) => {
9571
+ const { bulmaHelperClasses, rest } = useBulmaClasses(props);
9572
+ const loaderClass = usePrefixedClassNames('loader');
9573
+ // role="progressbar", not role="status": a status region mounted together
9574
+ // with its text is not reliably announced, so the loader claims only what
9575
+ // it can deliver, a named indicator found by reading the page. No
9576
+ // aria-valuenow, because the progress is indeterminate.
9577
+ return (jsxRuntime.jsx("span", { ref: ref, className: classNames(loaderClass, bulmaHelperClasses, className), role: "progressbar", "aria-label": ariaLabel, ...rest }));
9578
+ });
9579
+ Loader.displayName = 'Loader';
9580
+
9507
9581
  /**
9508
9582
  * The `Notification` component is a Bulma-styled alert/message area for providing feedback, warnings, or information to users.
9509
9583
  *
@@ -12425,6 +12499,10 @@ const Rate = React.forwardRef(({ label, labelSize, labelProps, horizontal, messa
12425
12499
  });
12426
12500
  Rate.displayName = 'Rate';
12427
12501
 
12502
+ // What the status region announces: the `empty` content, as the one item
12503
+ // it's handed while that content is on screen because of the user's typing.
12504
+ const typedNoMatches = [{ id: 'empty' }];
12505
+ const nothingToAnnounce = [];
12428
12506
  /**
12429
12507
  * The `Autocomplete` component provides an input field with dropdown suggestions that filter based on user input.
12430
12508
  *
@@ -12469,6 +12547,13 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
12469
12547
  const dropdownRef = React.useRef(null);
12470
12548
  const [internalValue, setInternalValue] = React.useState('');
12471
12549
  const [isActive, setIsActive] = React.useState(false);
12550
+ // Whether the user has typed since the list opened, since only typing
12551
+ // gets the `empty` content announced. Closing the list resets it (during
12552
+ // render, React's pattern for adjusting state when other state changes),
12553
+ // so opening the list again without typing stays quiet.
12554
+ const [typed, setTyped] = React.useState(false);
12555
+ if (typed && !isActive)
12556
+ setTyped(false);
12472
12557
  const [highlightedIndex, setHighlightedIndex] = React.useState(-1);
12473
12558
  const listboxId = `${React.useId()}-listbox`;
12474
12559
  // Use controlled or internal value
@@ -12487,6 +12572,11 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
12487
12572
  const displayValue = getDisplayValue(item);
12488
12573
  return displayValue.toLowerCase().includes(inputValue.toLowerCase());
12489
12574
  });
12575
+ // The dropdown shows the options or, when nothing matches, the `empty`
12576
+ // content. The listbox holds only options, so it's on screen only while
12577
+ // there are some.
12578
+ const menuOpen = isActive && (filteredData.length > 0 || !!empty);
12579
+ const listOpen = isActive && filteredData.length > 0;
12490
12580
  // Handle input change
12491
12581
  const handleInputChange = React.useCallback((e) => {
12492
12582
  const newValue = e.target.value;
@@ -12494,6 +12584,7 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
12494
12584
  setInternalValue(newValue);
12495
12585
  }
12496
12586
  onInput?.(newValue);
12587
+ setTyped(true);
12497
12588
  if (!isActive && newValue) {
12498
12589
  setIsActive(true);
12499
12590
  }
@@ -12644,7 +12735,7 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
12644
12735
  });
12645
12736
  const controlClasses = usePrefixedClassNames('control', 'is-expanded', 'has-icons-right');
12646
12737
  const dropdownMenuClasses = usePrefixedClassNames('dropdown-menu', {
12647
- 'is-active': isActive && (filteredData.length > 0 || !!empty),
12738
+ 'is-active': menuOpen,
12648
12739
  });
12649
12740
  const dropdownContentClass = usePrefixedClassNames('dropdown-content');
12650
12741
  const dropdownHeaderClass = usePrefixedClassNames('dropdown-header');
@@ -12666,24 +12757,51 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
12666
12757
  // The listbox takes its name from the label that names the combobox, the
12667
12758
  // one this renders or a surrounding Field's, so the two say the same
12668
12759
  // thing. When no label names the combobox, or the one that does has no
12669
- // id to point at, the list keeps a fallback name.
12670
- const listOpen = isActive && (filteredData.length > 0 || !!empty);
12760
+ // id to point at, the list keeps a fallback name. While the dropdown
12761
+ // shows only the `empty` content there's no listbox, so the combobox is
12762
+ // collapsed and points at nothing.
12671
12763
  const activeOptionId = listOpen &&
12672
12764
  highlightedIndex >= 0 &&
12673
12765
  highlightedIndex < filteredData.length
12674
12766
  ? `${listboxId}-${highlightedIndex}`
12675
12767
  : undefined;
12676
- const autocompleteElement = (jsxRuntime.jsxs("div", { ref: containerRef, className: combinedClasses, ...rest, children: [jsxRuntime.jsxs("div", { className: controlClasses, children: [jsxRuntime.jsx("input", { ref: combinedRef, type: "text", className: inputClasses, id: controlId, value: inputValue, placeholder: placeholder, disabled: disabled, name: name, form: form, required: required, onChange: handleInputChange, onFocus: handleFocus, onKeyDown: handleKeyDown, role: "combobox", "aria-expanded": listOpen, "aria-controls": listOpen ? listboxId : undefined, "aria-activedescendant": activeOptionId, "aria-haspopup": "listbox", "aria-autocomplete": "list", autoComplete: "off" }), showClear && (jsxRuntime.jsx("button", { type: "button", className: iconRightClickableClass, onClick: handleClear, "aria-label": "Clear", children: jsxRuntime.jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", width: "16", height: "16", "aria-hidden": "true", children: [jsxRuntime.jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), jsxRuntime.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })] }) })), loading && (jsxRuntime.jsx("span", { className: iconRightClass, children: jsxRuntime.jsx("span", { className: loaderClass }) }))] }), listOpen && (jsxRuntime.jsx("div", { className: dropdownMenuClasses, children: jsxRuntime.jsxs("div", { ref: dropdownRef, className: dropdownContentClass, style: { maxHeight: `${maxHeight}px`, overflowY: 'auto' }, id: listboxId, role: "listbox", "aria-labelledby": listLabelId, "aria-label": listLabelId ? undefined : 'Suggestions', onScroll: handleDropdownScroll, children: [header && jsxRuntime.jsx("div", { className: dropdownHeaderClass, children: header }), filteredData.length > 0 ? (filteredData.map((item, index) => {
12677
- const isDisabled = typeof item !== 'string' && item.disabled;
12678
- const isHighlighted = index === highlightedIndex;
12679
- const itemClasses = prefixedClassNames(classPrefix, 'dropdown-item', {
12680
- 'is-active': isHighlighted,
12681
- 'is-disabled': isDisabled,
12682
- });
12683
- return (jsxRuntime.jsx("a", { id: `${listboxId}-${index}`, "data-index": index, className: itemClasses, onClick: () => !isDisabled && handleSelect(item), onMouseEnter: () => !isDisabled && setHighlightedIndex(index), role: "option", "aria-selected": isHighlighted, "aria-disabled": isDisabled, children: itemTemplate
12684
- ? itemTemplate(item)
12685
- : getDisplayValue(item) }, index));
12686
- })) : (jsxRuntime.jsx("div", { className: emptyItemClasses, children: empty })), footer && jsxRuntime.jsx("div", { className: dropdownFooterClass, children: footer })] }) }))] }));
12768
+ // A polite status region, in the page from the start so it's there
12769
+ // before anything is written into it, announces the `empty` content
12770
+ // when the user's typing leaves nothing that matches what's in the
12771
+ // field. It follows what's on screen, so results that come in after the
12772
+ // typing count too. It's said once each time that happens, not again on
12773
+ // each keystroke while nothing matches. An empty field isn't a search,
12774
+ // so it stays quiet then, even with no data. While `loading`, or a
12775
+ // loading Control around it, says results are on their way, it waits,
12776
+ // and it's said once loading ends with nothing matching.
12777
+ const emptyItems = typed &&
12778
+ menuOpen &&
12779
+ !listOpen &&
12780
+ !!inputValue &&
12781
+ !loading &&
12782
+ !controlLoading
12783
+ ? typedNoMatches
12784
+ : nothingToAnnounce;
12785
+ // The region reads the text on screen, whatever `empty` renders.
12786
+ const emptyRef = React.useRef(null);
12787
+ const describeEmpty = React.useCallback(() => {
12788
+ const node = emptyRef.current;
12789
+ /* istanbul ignore if: it's only announced while it's on screen */
12790
+ if (!node)
12791
+ return null;
12792
+ return spokenText(node);
12793
+ }, []);
12794
+ const autocompleteElement = (jsxRuntime.jsxs("div", { ref: containerRef, className: combinedClasses, ...rest, children: [jsxRuntime.jsxs("div", { className: controlClasses, children: [jsxRuntime.jsx("input", { ref: combinedRef, type: "text", className: inputClasses, id: controlId, value: inputValue, placeholder: placeholder, disabled: disabled, name: name, form: form, required: required, onChange: handleInputChange, onFocus: handleFocus, onKeyDown: handleKeyDown, role: "combobox", "aria-expanded": listOpen, "aria-controls": listOpen ? listboxId : undefined, "aria-activedescendant": activeOptionId, "aria-haspopup": "listbox", "aria-autocomplete": "list", autoComplete: "off" }), showClear && (jsxRuntime.jsx("button", { type: "button", className: iconRightClickableClass, onClick: handleClear, "aria-label": "Clear", children: jsxRuntime.jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", width: "16", height: "16", "aria-hidden": "true", children: [jsxRuntime.jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), jsxRuntime.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })] }) })), loading && (jsxRuntime.jsx("span", { className: iconRightClass, children: jsxRuntime.jsx("span", { className: loaderClass }) }))] }), menuOpen && (jsxRuntime.jsx("div", { className: dropdownMenuClasses, children: jsxRuntime.jsxs("div", { ref: dropdownRef, className: dropdownContentClass, style: { maxHeight: `${maxHeight}px`, overflowY: 'auto' }, onScroll: handleDropdownScroll, children: [header && jsxRuntime.jsx("div", { className: dropdownHeaderClass, children: header }), listOpen ? (jsxRuntime.jsx("div", { id: listboxId, role: "listbox", "aria-labelledby": listLabelId, "aria-label": listLabelId ? undefined : 'Suggestions', children: filteredData.map((item, index) => {
12795
+ const isDisabled = typeof item !== 'string' && item.disabled;
12796
+ const isHighlighted = index === highlightedIndex;
12797
+ const itemClasses = prefixedClassNames(classPrefix, 'dropdown-item', {
12798
+ 'is-active': isHighlighted,
12799
+ 'is-disabled': isDisabled,
12800
+ });
12801
+ return (jsxRuntime.jsx("a", { id: `${listboxId}-${index}`, "data-index": index, className: itemClasses, onClick: () => !isDisabled && handleSelect(item), onMouseEnter: () => !isDisabled && setHighlightedIndex(index), role: "option", "aria-selected": isHighlighted, "aria-disabled": isDisabled, children: itemTemplate
12802
+ ? itemTemplate(item)
12803
+ : getDisplayValue(item) }, index));
12804
+ }) })) : (jsxRuntime.jsx("div", { ref: emptyRef, className: emptyItemClasses, children: empty })), footer && jsxRuntime.jsx("div", { className: dropdownFooterClass, children: footer })] }) })), jsxRuntime.jsx(StatusRegion, { items: emptyItems, describe: describeEmpty })] }));
12687
12805
  if (!insideField) {
12688
12806
  return (jsxRuntime.jsxs(Field, { label: label, labelSize: labelSize, labelProps: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [autocompleteElement, messageEl] }));
12689
12807
  }
@@ -19362,6 +19480,8 @@ exports.IconText = IconText;
19362
19480
  exports.Image = Image;
19363
19481
  exports.Input = Input;
19364
19482
  exports.InputBase = InputBase;
19483
+ exports.Kbd = Kbd;
19484
+ exports.KbdGroup = KbdGroup;
19365
19485
  exports.Level = Level;
19366
19486
  exports.LevelItem = LevelItem;
19367
19487
  exports.LevelLeft = LevelLeft;