@codecademy/gamut
72.3.072.3.1-alpha.59371c.0
dist/Form/SelectDropdown/hooks/useNoOptionsAnnouncement.js+
dist/Form/SelectDropdown/hooks/useNoOptionsAnnouncement.jsNew file+39
Index: package/dist/Form/SelectDropdown/hooks/useNoOptionsAnnouncement.js
===================================================================
--- package/dist/Form/SelectDropdown/hooks/useNoOptionsAnnouncement.js
+++ package/dist/Form/SelectDropdown/hooks/useNoOptionsAnnouncement.js
@@ -0,0 +1,39 @@
+import { useCallback, useEffect, useMemo, useState } from 'react';
+import { createNoOptionsMessage } from '../elements';
+const ANNOUNCEMENT_DEBOUNCE_MS = 400;
+/*
+ Announces react-select's "no options" menu text (its default "No options",
+ or a custom `validationMessage`) to screen readers via a standalone live
+ region. react-select's own live region only fires when its `options` prop
+ is non-empty, so it never speaks the "no options" state - this fills that
+ gap, including while a consumer is mid-fetch with an empty `options` array.
+
+ The caller must render `announcement` inside a live-region element that
+ stays mounted for the component's lifetime (no key-based remounting).
+ VoiceOver/Safari only tracks a live region it discovered on first mount -
+ swapping in a new DOM node for every announcement makes it stop hearing
+ updates after the first one. Repeat (even identical) announcements are
+ instead produced by clearing to '' immediately, then setting the real text
+ after a debounce, so the same node gets a genuine, detectable mutation.
+*/
+export const useNoOptionsAnnouncement = () => {
+ const [rawAnnouncement, setRawAnnouncement] = useState('');
+ const [announcement, setAnnouncement] = useState('');
+ useEffect(() => {
+ if (!rawAnnouncement) {
+ setAnnouncement('');
+ return;
+ }
+ const timeoutId = setTimeout(() => {
+ setAnnouncement(rawAnnouncement);
+ }, ANNOUNCEMENT_DEBOUNCE_MS);
+ return () => clearTimeout(timeoutId);
+ }, [rawAnnouncement]);
+ const noOptionsMessageComponent = useMemo(() => createNoOptionsMessage(setRawAnnouncement), []);
+ const clearAnnouncement = useCallback(() => setRawAnnouncement(''), []);
+ return {
+ noOptionsMessageComponent,
+ announcement,
+ clearAnnouncement
+ };
+};
\ No newline at end of file