@wistia/vhs 6.0.2-beta.3abc3475.31abda7 → 6.0.2-beta.3c90e751.554d424

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.js CHANGED
@@ -1,6 +1,6 @@
1
1
 
2
2
  /*
3
- * @license @wistia/vhs v6.0.2-beta.3abc3475.31abda7
3
+ * @license @wistia/vhs v6.0.2-beta.3c90e751.554d424
4
4
  *
5
5
  * Copyright (c) 2021-2026, Wistia, Inc. and its affiliates.
6
6
  *
@@ -8,9 +8,8 @@
8
8
  */
9
9
 
10
10
  import { AnimatePresence, MotionConfig, motion } from "motion/react";
11
- import { LiveAnnouncer, LiveMessage } from "react-aria-live";
12
11
  import { hasKey, isArray, isBoolean, isEmptyString, isFunction, isNil, isNonEmptyArray, isNonEmptyString, isNotNil, isNotUndefined, isNumber, isString, isUndefined } from "@wistia/type-guards";
13
- import { Children, Fragment, cloneElement, createContext, forwardRef, isValidElement, memo, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
12
+ import { Children, Fragment, cloneElement, createContext, forwardRef, isValidElement, memo, use, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
14
13
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
15
14
  import { createPortal } from "react-dom";
16
15
  import { ThemeProvider, createGlobalStyle, css, keyframes, styled, useTheme as useTheme$1 } from "styled-components";
@@ -110,6 +109,123 @@ const useThemeColor = (colorName = null) => {
110
109
  return getColor(colors, colorName);
111
110
  };
112
111
  //#endregion
112
+ //#region src/providers/AriaLiveProvider/AriaLiveContext.tsx
113
+ const warnMissingProvider = () => {
114
+ console.warn("Announcement failed: render VHSProvider (or AriaLiveProvider) above this component");
115
+ };
116
+ const AriaLiveContext = createContext({ announce: warnMissingProvider });
117
+ //#endregion
118
+ //#region src/components/ScreenReaderOnly/ScreenReaderOnly.tsx
119
+ const screenReaderOnlyStyle = css`
120
+ border: 0;
121
+ clip-path: inset(50%);
122
+ height: 1px;
123
+ overflow: hidden;
124
+ padding: 0;
125
+ position: absolute;
126
+ white-space: nowrap;
127
+ width: 1px;
128
+ `;
129
+ const VisuallyHidden = styled.div`
130
+ ${screenReaderOnlyStyle}
131
+ `;
132
+ const VisuallyHiddenButFocusable = styled.div`
133
+ &:not(:focus-within) {
134
+ ${screenReaderOnlyStyle}
135
+ }
136
+ `;
137
+ const ScreenReaderOnly = ({ text, children, focusable = false, ...otherProps }) => {
138
+ const accessibleText = isNotNil(text) ? text : children;
139
+ if (focusable) return /* @__PURE__ */ jsx(VisuallyHiddenButFocusable, {
140
+ ...otherProps,
141
+ children: accessibleText
142
+ });
143
+ return /* @__PURE__ */ jsx(VisuallyHidden, {
144
+ ...otherProps,
145
+ children: accessibleText
146
+ });
147
+ };
148
+ ScreenReaderOnly.displayName = "ScreenReaderOnly_VHS";
149
+ //#endregion
150
+ //#region src/providers/AriaLiveProvider/AriaLiveProvider.tsx
151
+ const ANNOUNCEMENT_CLEAR_DELAY_MS = 5e3;
152
+ const HiddenRegion = styled.div`
153
+ ${screenReaderOnlyStyle}
154
+ margin: -1px;
155
+ `;
156
+ const LiveRegion = ({ announcements, politeness }) => /* @__PURE__ */ jsx(HiddenRegion, {
157
+ "aria-live": politeness,
158
+ role: "log",
159
+ children: announcements.filter((announcement) => announcement.politeness === politeness).map((announcement) => /* @__PURE__ */ jsx("div", { children: announcement.message }, announcement.id))
160
+ });
161
+ const AriaLiveProvider = ({ children }) => {
162
+ const [announcements, setAnnouncements] = useState([]);
163
+ const nextId = useRef(0);
164
+ const pendingKeys = useRef(/* @__PURE__ */ new Set());
165
+ useEffect(() => {
166
+ pendingKeys.current.clear();
167
+ });
168
+ useEffect(() => {
169
+ if (announcements.length === 0) return;
170
+ const soonest = Math.min(...announcements.map((announcement) => announcement.expiresAt));
171
+ const timer = setTimeout(() => {
172
+ const now = Date.now();
173
+ setAnnouncements((current) => current.filter((announcement) => announcement.expiresAt > now));
174
+ }, Math.max(soonest - Date.now(), 0));
175
+ return () => {
176
+ clearTimeout(timer);
177
+ };
178
+ }, [announcements]);
179
+ const announce = useCallback((message, politeness = "polite") => {
180
+ if (!isNonEmptyString(message)) return;
181
+ const key = `${politeness}:${message}`;
182
+ if (pendingKeys.current.has(key)) return;
183
+ pendingKeys.current.add(key);
184
+ const id = nextId.current;
185
+ nextId.current += 1;
186
+ const expiresAt = Date.now() + ANNOUNCEMENT_CLEAR_DELAY_MS;
187
+ setAnnouncements((current) => [...current, {
188
+ expiresAt,
189
+ id,
190
+ message,
191
+ politeness
192
+ }]);
193
+ }, []);
194
+ const value = useMemo(() => ({ announce }), [announce]);
195
+ return /* @__PURE__ */ jsxs(AriaLiveContext, {
196
+ value,
197
+ children: [children, /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(LiveRegion, {
198
+ announcements,
199
+ politeness: "assertive"
200
+ }), /* @__PURE__ */ jsx(LiveRegion, {
201
+ announcements,
202
+ politeness: "polite"
203
+ })] })]
204
+ });
205
+ };
206
+ //#endregion
207
+ //#region src/private/hooks/useAriaLive/useAriaLive.ts
208
+ const useAriaLive = () => use(AriaLiveContext);
209
+ //#endregion
210
+ //#region src/private/components/LiveAnnouncement/LiveAnnouncement.tsx
211
+ /**
212
+ * Announces `message` to screen readers when it mounts and each time `message` changes.
213
+ */
214
+ const LiveAnnouncement = ({ message, politeness = "polite" }) => {
215
+ const { announce } = useAriaLive();
216
+ const announcedMessage = useRef(null);
217
+ useEffect(() => {
218
+ if (announcedMessage.current === message) return;
219
+ announcedMessage.current = message;
220
+ announce(message, politeness);
221
+ }, [
222
+ announce,
223
+ message,
224
+ politeness
225
+ ]);
226
+ return null;
227
+ };
228
+ //#endregion
113
229
  //#region src/private/helpers/coerceToBoolean/coerceToBoolean.ts
114
230
  /**
115
231
  * Converts a boolean string value `value` into a boolean. If passed
@@ -1618,9 +1734,9 @@ const ButtonContent = ({ children, icon, iconName, iconPosition, isLoading, size
1618
1734
  return /* @__PURE__ */ jsxs(Fragment$1, { children: [isLoading ? /* @__PURE__ */ jsxs(ButtonLoadingContainer, {
1619
1735
  $textAlign: textAlign,
1620
1736
  "data-testid": "button-content-loading",
1621
- children: [/* @__PURE__ */ jsx(LiveMessage, {
1622
- "aria-live": "assertive",
1623
- message: "Button has been disabled while action is in progress"
1737
+ children: [/* @__PURE__ */ jsx(LiveAnnouncement, {
1738
+ message: "Button has been disabled while action is in progress",
1739
+ politeness: "assertive"
1624
1740
  }), /* @__PURE__ */ jsx(Icon, {
1625
1741
  size: iconSizeMap$2[size],
1626
1742
  type: "spinner"
@@ -2090,9 +2206,9 @@ const Toast = memo(({ actionInterceptor, actionLabel = defaultActionLabel, color
2090
2206
  ...otherProps,
2091
2207
  children: [/* @__PURE__ */ jsxs(ToastContent, {
2092
2208
  "data-testid": "toast-message",
2093
- children: [/* @__PURE__ */ jsx(LiveMessage, {
2094
- "aria-live": ariaLive,
2095
- message
2209
+ children: [/* @__PURE__ */ jsx(LiveAnnouncement, {
2210
+ message,
2211
+ politeness: ariaLive
2096
2212
  }), message]
2097
2213
  }), /* @__PURE__ */ jsx(Button, {
2098
2214
  colorOverride: accentColor,
@@ -2644,7 +2760,7 @@ const DEFAULT_THEME = {};
2644
2760
  const VHSProvider = ({ children, theme = DEFAULT_THEME, globalStylesIncluded = true, skipAnimations = false, Link }) => {
2645
2761
  return /* @__PURE__ */ jsxs(VHSThemeProvider, {
2646
2762
  theme,
2647
- children: [globalStylesIncluded ? /* @__PURE__ */ jsx(GlobalStyle$1, {}) : null, /* @__PURE__ */ jsx(LiveAnnouncer, { children: /* @__PURE__ */ jsx(MotionConfig, {
2763
+ children: [globalStylesIncluded ? /* @__PURE__ */ jsx(GlobalStyle$1, {}) : null, /* @__PURE__ */ jsx(AriaLiveProvider, { children: /* @__PURE__ */ jsx(MotionConfig, {
2648
2764
  reducedMotion: skipAnimations ? "always" : "user",
2649
2765
  children: /* @__PURE__ */ jsx(WrapperProvider, {
2650
2766
  value: { Link },
@@ -2654,38 +2770,6 @@ const VHSProvider = ({ children, theme = DEFAULT_THEME, globalStylesIncluded = t
2654
2770
  });
2655
2771
  };
2656
2772
  //#endregion
2657
- //#region src/components/ScreenReaderOnly/ScreenReaderOnly.tsx
2658
- const screenReaderOnlyStyle = css`
2659
- border: 0;
2660
- clip-path: inset(50%);
2661
- height: 1px;
2662
- overflow: hidden;
2663
- padding: 0;
2664
- position: absolute;
2665
- white-space: nowrap;
2666
- width: 1px;
2667
- `;
2668
- const VisuallyHidden = styled.div`
2669
- ${screenReaderOnlyStyle}
2670
- `;
2671
- const VisuallyHiddenButFocusable = styled.div`
2672
- &:not(:focus-within) {
2673
- ${screenReaderOnlyStyle}
2674
- }
2675
- `;
2676
- const ScreenReaderOnly = ({ text, children, focusable = false, ...otherProps }) => {
2677
- const accessibleText = isNotNil(text) ? text : children;
2678
- if (focusable) return /* @__PURE__ */ jsx(VisuallyHiddenButFocusable, {
2679
- ...otherProps,
2680
- children: accessibleText
2681
- });
2682
- return /* @__PURE__ */ jsx(VisuallyHidden, {
2683
- ...otherProps,
2684
- children: accessibleText
2685
- });
2686
- };
2687
- ScreenReaderOnly.displayName = "ScreenReaderOnly_VHS";
2688
- //#endregion
2689
2773
  //#region src/components/Truncate/Truncate.tsx
2690
2774
  const Truncate = ({ children, maxChars, terminator = "...", breakOnWords = false }) => {
2691
2775
  if (isNil(children)) return null;