@codecademy/gamut
72.2.472.2.5-alpha.619ec9.0
dist/Form/SelectDropdown/hooks/useNoOptionsAnnouncement.js+
dist/Form/SelectDropdown/hooks/useNoOptionsAnnouncement.jsNew file+44
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,44 @@
+import { 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.
+ */
+export const useNoOptionsAnnouncement = () => {
+ const [rawAnnouncement, setRawAnnouncement] = useState('');
+ const [announcement, setAnnouncement] = useState('');
+ const [announcementKey, setAnnouncementKey] = useState(0);
+ useEffect(() => {
+ // Applying '' immediately (rather than debounced) guarantees a real DOM
+ // mutation happens before an identical message can be shown again -
+ // aria-live announcements depend on an actual content change to fire.
+ if (!rawAnnouncement) {
+ setAnnouncement('');
+ return;
+ }
+ const timeoutId = setTimeout(() => {
+ setAnnouncement(rawAnnouncement);
+ }, ANNOUNCEMENT_DEBOUNCE_MS);
+ return () => clearTimeout(timeoutId);
+ }, [rawAnnouncement]);
+ useEffect(() => {
+ // Safari/VoiceOver can silently drop a live region's second announcement
+ // when it's only a text mutation on an already-mounted node. Incrementing
+ // this key (used by the caller to key the live region element) forces
+ // React to unmount and recreate the DOM node for every new announcement,
+ // so each one is a genuinely fresh node.
+ if (announcement) {
+ setAnnouncementKey(key => key + 1);
+ }
+ }, [announcement]);
+ const noOptionsMessageComponent = useMemo(() => createNoOptionsMessage(setRawAnnouncement), []);
+ return {
+ noOptionsMessageComponent,
+ announcement,
+ announcementKey
+ };
+};
\ No newline at end of file