@allxsmith/bestax-bulma 5.33.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.
package/dist/index.cjs CHANGED
@@ -9185,7 +9185,8 @@ const visuallyHidden = {
9185
9185
  border: 0,
9186
9186
  };
9187
9187
  /**
9188
- * 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
9189
9190
  * announcement is written `announceDelay` after it appears, or after the last
9190
9191
  * item to join it when several appear in quick succession, alongside whatever
9191
9192
  * the region still says. Once written, it stays for `announcementLifetime`,
@@ -9220,8 +9221,8 @@ function useAnnouncements(items, describe) {
9220
9221
  React.useEffect(() => {
9221
9222
  const announced = onScreenIdsRef.current;
9222
9223
  const appeared = items.filter(item => !announced.has(item.id));
9223
- // Items that have gone drop out here too. Ids are never reused, so
9224
- // 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.
9225
9226
  onScreenIdsRef.current = new Set(items.map(item => item.id));
9226
9227
  const next = [];
9227
9228
  for (const item of appeared) {
@@ -9234,9 +9235,13 @@ function useAnnouncements(items, describe) {
9234
9235
  setAnnouncements(current => ({
9235
9236
  ...current,
9236
9237
  key: current.key + 1,
9237
- // Announcements still waiting go out with the new ones. What's
9238
- // already written stays put.
9239
- 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
+ ],
9240
9245
  }));
9241
9246
  }
9242
9247
  }, [items, describe]);
@@ -9395,10 +9400,10 @@ function readAloud(element) {
9395
9400
  return name || blockLevelTags.has(element.tagName) ? ` ${text} ` : text;
9396
9401
  }
9397
9402
  /**
9398
- * A visually hidden, polite `status` live region that a programmatic
9399
- * container announces its items through. The container renders it from the
9400
- * moment it mounts, whether or not it has anything to show, so the region is
9401
- * 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.
9402
9407
  *
9403
9408
  * The announcements are this component's own state, so writing and clearing
9404
9409
  * them re-renders the region and leaves the container's items alone. Each one
@@ -9415,8 +9420,10 @@ function StatusRegion({ items, describe, }) {
9415
9420
  // reads the whole region on every change. Each announcement is its own
9416
9421
  // node, and only additions are relevant by default, so with atomic off a
9417
9422
  // screen reader reads just what was written, and removing one announcement
9418
- // doesn't read out the ones still there.
9419
- 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}`))) }));
9420
9427
  }
9421
9428
 
9422
9429
  /**
@@ -12492,6 +12499,10 @@ const Rate = React.forwardRef(({ label, labelSize, labelProps, horizontal, messa
12492
12499
  });
12493
12500
  Rate.displayName = 'Rate';
12494
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 = [];
12495
12506
  /**
12496
12507
  * The `Autocomplete` component provides an input field with dropdown suggestions that filter based on user input.
12497
12508
  *
@@ -12536,6 +12547,13 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
12536
12547
  const dropdownRef = React.useRef(null);
12537
12548
  const [internalValue, setInternalValue] = React.useState('');
12538
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);
12539
12557
  const [highlightedIndex, setHighlightedIndex] = React.useState(-1);
12540
12558
  const listboxId = `${React.useId()}-listbox`;
12541
12559
  // Use controlled or internal value
@@ -12554,6 +12572,11 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
12554
12572
  const displayValue = getDisplayValue(item);
12555
12573
  return displayValue.toLowerCase().includes(inputValue.toLowerCase());
12556
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;
12557
12580
  // Handle input change
12558
12581
  const handleInputChange = React.useCallback((e) => {
12559
12582
  const newValue = e.target.value;
@@ -12561,6 +12584,7 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
12561
12584
  setInternalValue(newValue);
12562
12585
  }
12563
12586
  onInput?.(newValue);
12587
+ setTyped(true);
12564
12588
  if (!isActive && newValue) {
12565
12589
  setIsActive(true);
12566
12590
  }
@@ -12711,7 +12735,7 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
12711
12735
  });
12712
12736
  const controlClasses = usePrefixedClassNames('control', 'is-expanded', 'has-icons-right');
12713
12737
  const dropdownMenuClasses = usePrefixedClassNames('dropdown-menu', {
12714
- 'is-active': isActive && (filteredData.length > 0 || !!empty),
12738
+ 'is-active': menuOpen,
12715
12739
  });
12716
12740
  const dropdownContentClass = usePrefixedClassNames('dropdown-content');
12717
12741
  const dropdownHeaderClass = usePrefixedClassNames('dropdown-header');
@@ -12733,24 +12757,51 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
12733
12757
  // The listbox takes its name from the label that names the combobox, the
12734
12758
  // one this renders or a surrounding Field's, so the two say the same
12735
12759
  // thing. When no label names the combobox, or the one that does has no
12736
- // id to point at, the list keeps a fallback name.
12737
- 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.
12738
12763
  const activeOptionId = listOpen &&
12739
12764
  highlightedIndex >= 0 &&
12740
12765
  highlightedIndex < filteredData.length
12741
12766
  ? `${listboxId}-${highlightedIndex}`
12742
12767
  : undefined;
12743
- 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) => {
12744
- const isDisabled = typeof item !== 'string' && item.disabled;
12745
- const isHighlighted = index === highlightedIndex;
12746
- const itemClasses = prefixedClassNames(classPrefix, 'dropdown-item', {
12747
- 'is-active': isHighlighted,
12748
- 'is-disabled': isDisabled,
12749
- });
12750
- 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
12751
- ? itemTemplate(item)
12752
- : getDisplayValue(item) }, index));
12753
- })) : (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 })] }));
12754
12805
  if (!insideField) {
12755
12806
  return (jsxRuntime.jsxs(Field, { label: label, labelSize: labelSize, labelProps: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [autocompleteElement, messageEl] }));
12756
12807
  }