@ringcentral/spring-base 1.11.2 → 1.12.0
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/chunks/index.js +234 -0
- package/chunks/twMerge.js +18 -2
- package/hooks.js +88 -32
- package/index.js +2 -1
- package/liveAnnouncer.js +1 -0
- package/package.json +4 -4
- package/src/hooks/index.d.ts +1 -0
- package/src/hooks/useAnnouncer/useAnnouncer.d.ts +5 -2
- package/src/hooks/useGlobalAnnouncer/index.d.ts +1 -0
- package/src/hooks/useGlobalAnnouncer/useGlobalAnnouncer.d.ts +5 -0
- package/src/hooks/useId/useId.d.ts +3 -1
- package/src/hooks/useKeyboardMoveFocus/useOnlyOneFocusable.d.ts +4 -1
- package/src/index.d.ts +1 -0
- package/src/liveAnnouncer/LiveAnnouncer.d.ts +25 -0
- package/src/liveAnnouncer/announce.d.ts +4 -0
- package/src/liveAnnouncer/index.d.ts +3 -0
- package/src/liveAnnouncer/types.d.ts +8 -0
- package/src/liveAnnouncer/visuallyHidden.d.ts +3 -0
package/chunks/index.js
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
const LIVE_ANNOUNCER_ATTR = 'data-live-announcer';
|
|
2
|
+
const visuallyHiddenStyleProperties = {
|
|
3
|
+
border: '0',
|
|
4
|
+
clip: 'rect(0 0 0 0)',
|
|
5
|
+
clipPath: 'inset(50%)',
|
|
6
|
+
height: '1px',
|
|
7
|
+
margin: '-1px',
|
|
8
|
+
overflow: 'hidden',
|
|
9
|
+
padding: '0',
|
|
10
|
+
position: 'absolute',
|
|
11
|
+
width: '1px',
|
|
12
|
+
whiteSpace: 'nowrap',
|
|
13
|
+
};
|
|
14
|
+
const applyVisuallyHiddenStyles = (el) => {
|
|
15
|
+
Object.assign(el.style, visuallyHiddenStyleProperties);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
class RepeatStringText {
|
|
19
|
+
last = {};
|
|
20
|
+
usedZwsp = {
|
|
21
|
+
assertive: false,
|
|
22
|
+
polite: false,
|
|
23
|
+
};
|
|
24
|
+
next(politeness, message) {
|
|
25
|
+
const useZwsp = this.last[politeness] === message ? !this.usedZwsp[politeness] : false;
|
|
26
|
+
this.last[politeness] = message;
|
|
27
|
+
this.usedZwsp[politeness] = useZwsp;
|
|
28
|
+
return useZwsp ? `\u200B${message}` : message;
|
|
29
|
+
}
|
|
30
|
+
reset(politeness) {
|
|
31
|
+
const channels = politeness
|
|
32
|
+
? [politeness]
|
|
33
|
+
: ['assertive', 'polite'];
|
|
34
|
+
for (const channel of channels) {
|
|
35
|
+
this.last[channel] = undefined;
|
|
36
|
+
this.usedZwsp[channel] = false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
class LiveAnnouncer {
|
|
41
|
+
node = null;
|
|
42
|
+
assertiveLog = null;
|
|
43
|
+
politeLog = null;
|
|
44
|
+
doc;
|
|
45
|
+
repeatText = new RepeatStringText();
|
|
46
|
+
ready = false;
|
|
47
|
+
pending = [];
|
|
48
|
+
firstDelayTimerId = null;
|
|
49
|
+
constructor(doc) {
|
|
50
|
+
this.doc = doc;
|
|
51
|
+
if (typeof doc === 'undefined' || !doc.body) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
this.node = doc.createElement('div');
|
|
55
|
+
this.node.setAttribute(LIVE_ANNOUNCER_ATTR, 'true');
|
|
56
|
+
applyVisuallyHiddenStyles(this.node);
|
|
57
|
+
this.assertiveLog = this.createLog('assertive');
|
|
58
|
+
this.node.appendChild(this.assertiveLog);
|
|
59
|
+
this.politeLog = this.createLog('polite');
|
|
60
|
+
this.node.appendChild(this.politeLog);
|
|
61
|
+
doc.body.prepend(this.node);
|
|
62
|
+
}
|
|
63
|
+
isAttached() {
|
|
64
|
+
return Boolean(this.node?.isConnected);
|
|
65
|
+
}
|
|
66
|
+
createLog(ariaLive) {
|
|
67
|
+
const node = this.doc.createElement('div');
|
|
68
|
+
node.setAttribute('role', 'log');
|
|
69
|
+
node.setAttribute('aria-live', ariaLive);
|
|
70
|
+
node.setAttribute('aria-relevant', 'additions');
|
|
71
|
+
return node;
|
|
72
|
+
}
|
|
73
|
+
destroy() {
|
|
74
|
+
this.cancelFirstDelay();
|
|
75
|
+
if (!this.node) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const parent = this.node.parentNode;
|
|
79
|
+
if (parent) {
|
|
80
|
+
parent.removeChild(this.node);
|
|
81
|
+
}
|
|
82
|
+
this.node = null;
|
|
83
|
+
this.assertiveLog = null;
|
|
84
|
+
this.politeLog = null;
|
|
85
|
+
this.repeatText.reset();
|
|
86
|
+
}
|
|
87
|
+
schedule(message, politeness = 'assertive', timeout = LIVEREGION_TIMEOUT_DELAY) {
|
|
88
|
+
if (this.ready || isTestAnnounceEnvironment()) {
|
|
89
|
+
this.announce(message, politeness, timeout);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
this.pending.push({ message, politeness, timeout });
|
|
93
|
+
if (this.firstDelayTimerId != null) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const win = this.doc.defaultView ?? globalThis;
|
|
97
|
+
this.firstDelayTimerId = win.setTimeout(() => {
|
|
98
|
+
this.firstDelayTimerId = null;
|
|
99
|
+
this.ready = true;
|
|
100
|
+
const queued = this.pending;
|
|
101
|
+
this.pending = [];
|
|
102
|
+
for (const item of queued) {
|
|
103
|
+
this.announce(item.message, item.politeness, item.timeout);
|
|
104
|
+
}
|
|
105
|
+
}, FIRST_ANNOUNCE_DELAY_MS);
|
|
106
|
+
}
|
|
107
|
+
announce(message, politeness = 'assertive', timeout = LIVEREGION_TIMEOUT_DELAY) {
|
|
108
|
+
if (!this.node) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const node = this.doc.createElement('div');
|
|
112
|
+
if (typeof message === 'object') {
|
|
113
|
+
node.setAttribute('role', 'img');
|
|
114
|
+
node.setAttribute('aria-labelledby', message['aria-labelledby']);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
node.textContent = this.repeatText.next(politeness, message);
|
|
118
|
+
}
|
|
119
|
+
if (politeness === 'assertive') {
|
|
120
|
+
this.assertiveLog?.appendChild(node);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
this.politeLog?.appendChild(node);
|
|
124
|
+
}
|
|
125
|
+
if (message !== '') {
|
|
126
|
+
const win = this.doc.defaultView ?? globalThis;
|
|
127
|
+
win.setTimeout(() => {
|
|
128
|
+
node.remove();
|
|
129
|
+
}, timeout);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
clear(politeness) {
|
|
133
|
+
this.pending = politeness
|
|
134
|
+
? this.pending.filter((item) => item.politeness !== politeness)
|
|
135
|
+
: [];
|
|
136
|
+
if (!this.node) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if ((!politeness || politeness === 'assertive') && this.assertiveLog) {
|
|
140
|
+
this.assertiveLog.innerHTML = '';
|
|
141
|
+
this.repeatText.reset('assertive');
|
|
142
|
+
}
|
|
143
|
+
if ((!politeness || politeness === 'polite') && this.politeLog) {
|
|
144
|
+
this.politeLog.innerHTML = '';
|
|
145
|
+
this.repeatText.reset('polite');
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
cancelFirstDelay() {
|
|
149
|
+
if (this.firstDelayTimerId != null) {
|
|
150
|
+
const win = this.doc.defaultView ?? globalThis;
|
|
151
|
+
win.clearTimeout(this.firstDelayTimerId);
|
|
152
|
+
this.firstDelayTimerId = null;
|
|
153
|
+
}
|
|
154
|
+
this.pending = [];
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const LIVEREGION_TIMEOUT_DELAY = 7000;
|
|
158
|
+
const FIRST_ANNOUNCE_DELAY_MS = 100;
|
|
159
|
+
const announcersByDocument = new WeakMap();
|
|
160
|
+
const getLiveAnnouncer = (doc) => {
|
|
161
|
+
return announcersByDocument.get(doc) ?? null;
|
|
162
|
+
};
|
|
163
|
+
const ensureLiveAnnouncer = (doc) => {
|
|
164
|
+
const existing = announcersByDocument.get(doc);
|
|
165
|
+
if (existing?.isAttached()) {
|
|
166
|
+
return existing;
|
|
167
|
+
}
|
|
168
|
+
existing?.destroy();
|
|
169
|
+
const announcer = new LiveAnnouncer(doc);
|
|
170
|
+
announcersByDocument.set(doc, announcer);
|
|
171
|
+
return announcer;
|
|
172
|
+
};
|
|
173
|
+
const clearLiveAnnouncerRegistry = (doc) => {
|
|
174
|
+
if (doc) {
|
|
175
|
+
const announcer = announcersByDocument.get(doc);
|
|
176
|
+
announcer?.destroy();
|
|
177
|
+
announcersByDocument.delete(doc);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
const isTestAnnounceEnvironment = () => {
|
|
182
|
+
const globalObj = globalThis;
|
|
183
|
+
if (typeof globalObj.IS_REACT_ACT_ENVIRONMENT === 'boolean') {
|
|
184
|
+
return globalObj.IS_REACT_ACT_ENVIRONMENT;
|
|
185
|
+
}
|
|
186
|
+
return typeof jest !== 'undefined';
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const resolveDocument = (doc) => {
|
|
190
|
+
if (doc) {
|
|
191
|
+
return doc;
|
|
192
|
+
}
|
|
193
|
+
if (typeof document !== 'undefined') {
|
|
194
|
+
return document;
|
|
195
|
+
}
|
|
196
|
+
return undefined;
|
|
197
|
+
};
|
|
198
|
+
const resolveTimeoutAndDocument = (timeoutOrOptions) => {
|
|
199
|
+
if (typeof timeoutOrOptions === 'number') {
|
|
200
|
+
return { timeout: timeoutOrOptions };
|
|
201
|
+
}
|
|
202
|
+
if (timeoutOrOptions && typeof timeoutOrOptions === 'object') {
|
|
203
|
+
return {
|
|
204
|
+
timeout: timeoutOrOptions.timeout ?? LIVEREGION_TIMEOUT_DELAY,
|
|
205
|
+
document: timeoutOrOptions.document,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
return { timeout: LIVEREGION_TIMEOUT_DELAY };
|
|
209
|
+
};
|
|
210
|
+
function announce(message, politeness = 'assertive', timeoutOrOptions) {
|
|
211
|
+
const { timeout, document: optionDocument } = resolveTimeoutAndDocument(timeoutOrOptions);
|
|
212
|
+
const doc = resolveDocument(optionDocument);
|
|
213
|
+
if (!doc?.body) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const liveAnnouncer = ensureLiveAnnouncer(doc);
|
|
217
|
+
liveAnnouncer.schedule(message, politeness, timeout);
|
|
218
|
+
}
|
|
219
|
+
function clearAnnouncer(politeness, doc) {
|
|
220
|
+
const target = resolveDocument(doc);
|
|
221
|
+
if (!target) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
getLiveAnnouncer(target)?.clear(politeness);
|
|
225
|
+
}
|
|
226
|
+
function destroyAnnouncer(doc) {
|
|
227
|
+
const target = resolveDocument(doc);
|
|
228
|
+
if (!target) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
clearLiveAnnouncerRegistry(target);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export { LIVEREGION_TIMEOUT_DELAY as L, announce as a, LIVE_ANNOUNCER_ATTR as b, clearAnnouncer as c, destroyAnnouncer as d, applyVisuallyHiddenStyles as e };
|
package/chunks/twMerge.js
CHANGED
|
@@ -109,6 +109,9 @@ const createAriaHideOutsideController = (targets, options = {}) => {
|
|
|
109
109
|
}
|
|
110
110
|
};
|
|
111
111
|
const hide = (node) => {
|
|
112
|
+
if (hiddenNodes.has(node)) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
112
115
|
const mechanism = getHideMechanism(node);
|
|
113
116
|
const refCountMap = getRefCountMap(currentWindow, mechanism);
|
|
114
117
|
const refCount = refCountMap.get(node) ?? 0;
|
|
@@ -214,10 +217,23 @@ const createAriaHideOutsideController = (targets, options = {}) => {
|
|
|
214
217
|
targetNodes.add(target);
|
|
215
218
|
visibleNodes.add(target);
|
|
216
219
|
const path = getTargetPath(target);
|
|
217
|
-
|
|
218
|
-
|
|
220
|
+
const restoredAncestorIndexes = [];
|
|
221
|
+
for (let index = 0; index < path.length; index++) {
|
|
222
|
+
if (index > 0 && hiddenNodes.has(path[index])) {
|
|
223
|
+
restoredAncestorIndexes.push(index);
|
|
224
|
+
}
|
|
225
|
+
restoreHiddenNode(path[index]);
|
|
219
226
|
}
|
|
220
227
|
targetPaths.set(target, path);
|
|
228
|
+
for (const index of restoredAncestorIndexes) {
|
|
229
|
+
const ancestor = path[index];
|
|
230
|
+
const pathChild = path[index - 1];
|
|
231
|
+
for (const child of ancestor.children) {
|
|
232
|
+
if (child !== pathChild) {
|
|
233
|
+
walk(child, true);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
221
237
|
};
|
|
222
238
|
const removeTarget = (target) => {
|
|
223
239
|
if (!targetNodes.delete(target)) {
|
package/hooks.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { unstable_useEventCallback, unstable_ownerDocument, unstable_Timeout, unstable_useForkRef } from '@mui/utils';
|
|
2
2
|
export { unstable_useControlled as useControlled } from '@mui/utils';
|
|
3
3
|
import * as React from 'react';
|
|
4
|
-
import { useState, useRef, useEffect, useCallback, useLayoutEffect, useMemo } from 'react';
|
|
4
|
+
import React__default, { useState, useRef, useEffect, useCallback, useLayoutEffect, useMemo } from 'react';
|
|
5
5
|
import { useWindowContext, useResponsiveContext, getMatchDetail } from './contexts.js';
|
|
6
|
+
import { b as LIVE_ANNOUNCER_ATTR, e as applyVisuallyHiddenStyles, a as announce } from './chunks/index.js';
|
|
6
7
|
import uniqueId from 'lodash/uniqueId';
|
|
7
8
|
import { useButton as useButton$1 } from '@mui/base/useButton';
|
|
8
9
|
import { c as combineProps, d as createAriaHideOutsideController, g as getRefElement, l as logInDev, o as ownerWindow } from './chunks/twMerge.js';
|
|
@@ -128,7 +129,7 @@ function useResultRef(cb) {
|
|
|
128
129
|
}
|
|
129
130
|
|
|
130
131
|
const DEFAULT_PREFIX = 'spring-id-';
|
|
131
|
-
function
|
|
132
|
+
function useLegacyId(id, prefixMode) {
|
|
132
133
|
const { current: resultId } = useResultRef(() => {
|
|
133
134
|
if (id) {
|
|
134
135
|
if (prefixMode) {
|
|
@@ -140,27 +141,40 @@ function useId(id, prefixMode) {
|
|
|
140
141
|
});
|
|
141
142
|
return resultId;
|
|
142
143
|
}
|
|
144
|
+
const reactUseId = React__default.useId;
|
|
145
|
+
function useReactId(id, prefixMode) {
|
|
146
|
+
const reactId = reactUseId();
|
|
147
|
+
if (id) {
|
|
148
|
+
return prefixMode ? `${id}-${reactId}` : id;
|
|
149
|
+
}
|
|
150
|
+
return `${DEFAULT_PREFIX}${reactId}`;
|
|
151
|
+
}
|
|
152
|
+
const useId = reactUseId ? useReactId : useLegacyId;
|
|
143
153
|
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const useAnnouncer = (id) => {
|
|
154
|
+
const isUseAnnouncerOptions = (value) => {
|
|
155
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
156
|
+
};
|
|
157
|
+
function useAnnouncer(idOrOptions, maybeOptions) {
|
|
158
|
+
const id = typeof idOrOptions === 'string' ? idOrOptions : undefined;
|
|
159
|
+
const options = isUseAnnouncerOptions(idOrOptions)
|
|
160
|
+
? idOrOptions
|
|
161
|
+
: maybeOptions;
|
|
162
|
+
const politeness = options?.politeness ?? 'assertive';
|
|
154
163
|
const _id = useId(id ? `rc-announcer-${id}` : 'rc-announcer', !id);
|
|
155
164
|
const { document: targetDocument, externalWindow, globalThis: contextGlobal, } = useWindowContext();
|
|
156
165
|
const win = externalWindow ?? contextGlobal;
|
|
157
166
|
const ref = useRef(null);
|
|
158
167
|
const lastMessageRef = useRef();
|
|
159
168
|
const pendingTimerRef = useRef(null);
|
|
169
|
+
const pendingRemovalRef = useRef(null);
|
|
160
170
|
const clearPendingTimer = useCallback(() => {
|
|
161
171
|
pendingTimerRef.current?.();
|
|
162
172
|
pendingTimerRef.current = null;
|
|
163
173
|
}, []);
|
|
174
|
+
const clearPendingRemoval = useCallback(() => {
|
|
175
|
+
pendingRemovalRef.current?.();
|
|
176
|
+
pendingRemovalRef.current = null;
|
|
177
|
+
}, []);
|
|
164
178
|
useEffect(() => {
|
|
165
179
|
const currentDocument = targetDocument ??
|
|
166
180
|
(typeof contextGlobal.document !== 'undefined'
|
|
@@ -169,18 +183,30 @@ const useAnnouncer = (id) => {
|
|
|
169
183
|
if (!currentDocument?.body) {
|
|
170
184
|
return undefined;
|
|
171
185
|
}
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
el
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
186
|
+
const currentEl = ref.current;
|
|
187
|
+
const canReuseCurrentEl = currentEl?.ownerDocument === currentDocument &&
|
|
188
|
+
currentEl.id === _id &&
|
|
189
|
+
currentDocument.body.contains(currentEl);
|
|
190
|
+
const el = canReuseCurrentEl
|
|
191
|
+
? currentEl
|
|
192
|
+
: currentDocument.createElement('div');
|
|
193
|
+
if (canReuseCurrentEl) {
|
|
194
|
+
clearPendingRemoval();
|
|
195
|
+
el.setAttribute('aria-live', politeness);
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
ref.current = el;
|
|
199
|
+
el.id = _id;
|
|
200
|
+
el.setAttribute('aria-live', politeness);
|
|
201
|
+
el.setAttribute('aria-atomic', 'true');
|
|
202
|
+
el.setAttribute('aria-hidden', 'false');
|
|
203
|
+
el.setAttribute(LIVE_ANNOUNCER_ATTR, 'true');
|
|
204
|
+
applyVisuallyHiddenStyles(el);
|
|
205
|
+
currentDocument.body.appendChild(el);
|
|
206
|
+
}
|
|
181
207
|
return () => {
|
|
182
208
|
clearPendingTimer();
|
|
183
|
-
win.setTimeout(() => {
|
|
209
|
+
const timerId = win.setTimeout(() => {
|
|
184
210
|
const body = currentDocument.body;
|
|
185
211
|
if (body.contains(el)) {
|
|
186
212
|
body.removeChild(el);
|
|
@@ -188,9 +214,24 @@ const useAnnouncer = (id) => {
|
|
|
188
214
|
if (el === ref.current) {
|
|
189
215
|
ref.current = null;
|
|
190
216
|
}
|
|
217
|
+
if (pendingRemovalRef.current === cancelRemoval) {
|
|
218
|
+
pendingRemovalRef.current = null;
|
|
219
|
+
}
|
|
191
220
|
});
|
|
221
|
+
const cancelRemoval = () => {
|
|
222
|
+
win.clearTimeout(timerId);
|
|
223
|
+
};
|
|
224
|
+
pendingRemovalRef.current = cancelRemoval;
|
|
192
225
|
};
|
|
193
|
-
}, [
|
|
226
|
+
}, [
|
|
227
|
+
_id,
|
|
228
|
+
politeness,
|
|
229
|
+
targetDocument,
|
|
230
|
+
contextGlobal,
|
|
231
|
+
win,
|
|
232
|
+
clearPendingTimer,
|
|
233
|
+
clearPendingRemoval,
|
|
234
|
+
]);
|
|
194
235
|
return useCallback((message) => {
|
|
195
236
|
const el = ref.current;
|
|
196
237
|
if (!el)
|
|
@@ -221,7 +262,20 @@ const useAnnouncer = (id) => {
|
|
|
221
262
|
};
|
|
222
263
|
pendingTimerRef.current = cancelTimer;
|
|
223
264
|
}, [clearPendingTimer, win]);
|
|
224
|
-
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function useGlobalAnnouncer() {
|
|
268
|
+
const { document: targetDocument } = useWindowContext();
|
|
269
|
+
return useCallback((message, politeness = 'assertive', timeoutOrOptions) => {
|
|
270
|
+
const timeout = typeof timeoutOrOptions === 'number'
|
|
271
|
+
? timeoutOrOptions
|
|
272
|
+
: timeoutOrOptions?.timeout;
|
|
273
|
+
announce(message, politeness, {
|
|
274
|
+
timeout,
|
|
275
|
+
document: targetDocument,
|
|
276
|
+
});
|
|
277
|
+
}, [targetDocument]);
|
|
278
|
+
}
|
|
225
279
|
|
|
226
280
|
function useButton(parameters) {
|
|
227
281
|
const muiButtonResult = useButton$1(parameters);
|
|
@@ -287,9 +341,9 @@ const useAudio = (init) => {
|
|
|
287
341
|
return audio;
|
|
288
342
|
});
|
|
289
343
|
useEffect(() => {
|
|
344
|
+
const audio = audioRef.current;
|
|
290
345
|
return () => {
|
|
291
|
-
|
|
292
|
-
audioRef.current = null;
|
|
346
|
+
audio.srcObject = null;
|
|
293
347
|
};
|
|
294
348
|
}, []);
|
|
295
349
|
return audioRef.current;
|
|
@@ -1123,13 +1177,14 @@ const useKeyboardMoveFocus = ({ infinite, options = [], focusedIndexRef, onFocus
|
|
|
1123
1177
|
};
|
|
1124
1178
|
|
|
1125
1179
|
const MOVE_ATTR_KEY = 'data-move-focused';
|
|
1180
|
+
const MOVE_INDEX_ATTR_KEY = 'data-move-index';
|
|
1126
1181
|
const useOnlyOneFocusable = ({ focusedIndexRef, containerRef, }) => {
|
|
1127
|
-
const moveFocusedId = useId(
|
|
1128
|
-
const getIndexElm = (toIndex) => containerRef.current?.querySelector(`[${moveFocusedId}="${toIndex}"]`);
|
|
1182
|
+
const moveFocusedId = useId();
|
|
1183
|
+
const getIndexElm = (toIndex) => containerRef.current?.querySelector(`[${MOVE_ATTR_KEY}="${moveFocusedId}"][${MOVE_INDEX_ATTR_KEY}="${toIndex}"]`);
|
|
1129
1184
|
const setIndexTabIndex = (toIndex) => {
|
|
1130
1185
|
const toElm = getIndexElm(toIndex);
|
|
1131
1186
|
if (toElm) {
|
|
1132
|
-
const prevElm = containerRef.current?.querySelector(`[${moveFocusedId}][tabindex="0"]`);
|
|
1187
|
+
const prevElm = containerRef.current?.querySelector(`[${MOVE_ATTR_KEY}="${moveFocusedId}"][tabindex="0"]`);
|
|
1133
1188
|
prevElm?.setAttribute('tabindex', '-1');
|
|
1134
1189
|
toElm.setAttribute('tabindex', '0');
|
|
1135
1190
|
}
|
|
@@ -1144,13 +1199,14 @@ const useOnlyOneFocusable = ({ focusedIndexRef, containerRef, }) => {
|
|
|
1144
1199
|
setIndexTabIndex,
|
|
1145
1200
|
getItemProps: (index, options) => {
|
|
1146
1201
|
return combineProps({
|
|
1147
|
-
[
|
|
1202
|
+
[MOVE_ATTR_KEY]: moveFocusedId,
|
|
1203
|
+
[MOVE_INDEX_ATTR_KEY]: String(index),
|
|
1148
1204
|
tabIndex: index === focusedIndexRef.current ? 0 : -1,
|
|
1149
1205
|
onFocus: () => {
|
|
1150
1206
|
focusedIndexRef.current = index;
|
|
1151
1207
|
setIndexTabIndex(index);
|
|
1152
1208
|
},
|
|
1153
|
-
}, options);
|
|
1209
|
+
}, options ?? {});
|
|
1154
1210
|
},
|
|
1155
1211
|
};
|
|
1156
1212
|
};
|
|
@@ -1966,4 +2022,4 @@ const useVirtuosoExternalWindow = () => {
|
|
|
1966
2022
|
return undefined;
|
|
1967
2023
|
};
|
|
1968
2024
|
|
|
1969
|
-
export { createGlobalListener, globalListenerEventMap, isElmEqualOrContain, isTouchEvent, moveIndexInTwoDimension, teardown, useA11yKeyEvent, useActive, useAnnouncer, useAriaHideOutside, useAudio, useBodyScrollLock, useButton, useChange, useClickAway, useDebounce, useDepsChange, useEventCallback, useEventListener, useEver, useFocusVisible, useFontLoaded, useForceUpdate, useForkRef, useGlobalListener, useHiddenTabindex, useHover, useHoverDirty, useId, useImgLoaded, useInterval, useIsFocusVisible, useKeyDownOnce, useKeyboardMoveFocus, useLongPress, useMountState, useOnLayoutReRender, useOnReRender, useOnlyOneFocusable, useOverflow, usePrevious, useRefState, useResizeObserver, useResponsiveMatch, useResultRef, useRetry, useSleep, useTabOut, useTestRunnerDetection, useThrottle, useTimeout, useTouchMouseEvent, useVirtuosoExternalWindow
|
|
2025
|
+
export { createGlobalListener, globalListenerEventMap, isElmEqualOrContain, isTouchEvent, moveIndexInTwoDimension, teardown, useA11yKeyEvent, useActive, useAnnouncer, useAriaHideOutside, useAudio, useBodyScrollLock, useButton, useChange, useClickAway, useDebounce, useDepsChange, useEventCallback, useEventListener, useEver, useFocusVisible, useFontLoaded, useForceUpdate, useForkRef, useGlobalAnnouncer, useGlobalListener, useHiddenTabindex, useHover, useHoverDirty, useId, useImgLoaded, useInterval, useIsFocusVisible, useKeyDownOnce, useKeyboardMoveFocus, useLongPress, useMountState, useOnLayoutReRender, useOnReRender, useOnlyOneFocusable, useOverflow, usePrevious, useRefState, useResizeObserver, useResponsiveMatch, useResultRef, useRetry, useSleep, useTabOut, useTestRunnerDetection, useThrottle, useTimeout, useTouchMouseEvent, useVirtuosoExternalWindow };
|
package/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export { configure, rcConfiguration } from './config.js';
|
|
2
2
|
export { RcResponsiveContext, WindowContext, breakpointList, getMatchDetail, useResponsiveContext, useWindowContext } from './contexts.js';
|
|
3
|
-
export { createGlobalListener, globalListenerEventMap, isElmEqualOrContain, isTouchEvent, moveIndexInTwoDimension, teardown, useA11yKeyEvent, useActive, useAnnouncer, useAriaHideOutside, useAudio, useBodyScrollLock, useButton, useChange, useClickAway, useDebounce, useDepsChange, useEventCallback, useEventListener, useEver, useFocusVisible, useFontLoaded, useForceUpdate, useForkRef, useGlobalListener, useHiddenTabindex, useHover, useHoverDirty, useId, useImgLoaded, useInterval, useIsFocusVisible, useKeyDownOnce, useKeyboardMoveFocus, useLongPress, useMountState, useOnLayoutReRender, useOnReRender, useOnlyOneFocusable, useOverflow, usePrevious, useRefState, useResizeObserver, useResponsiveMatch, useResultRef, useRetry, useSleep, useTabOut, useTestRunnerDetection, useThrottle, useTimeout, useTouchMouseEvent, useVirtuosoExternalWindow
|
|
3
|
+
export { createGlobalListener, globalListenerEventMap, isElmEqualOrContain, isTouchEvent, moveIndexInTwoDimension, teardown, useA11yKeyEvent, useActive, useAnnouncer, useAriaHideOutside, useAudio, useBodyScrollLock, useButton, useChange, useClickAway, useDebounce, useDepsChange, useEventCallback, useEventListener, useEver, useFocusVisible, useFontLoaded, useForceUpdate, useForkRef, useGlobalAnnouncer, useGlobalListener, useHiddenTabindex, useHover, useHoverDirty, useId, useImgLoaded, useInterval, useIsFocusVisible, useKeyDownOnce, useKeyboardMoveFocus, useLongPress, useMountState, useOnLayoutReRender, useOnReRender, useOnlyOneFocusable, useOverflow, usePrevious, useRefState, useResizeObserver, useResponsiveMatch, useResultRef, useRetry, useSleep, useTabOut, useTestRunnerDetection, useThrottle, useTimeout, useTouchMouseEvent, useVirtuosoExternalWindow } from './hooks.js';
|
|
4
4
|
export { unstable_ownerDocument as ownerDocument, unstable_useControlled as useControlled } from '@mui/utils';
|
|
5
|
+
export { L as LIVEREGION_TIMEOUT_DELAY, a as announce, c as clearAnnouncer, d as destroyAnnouncer } from './chunks/index.js';
|
|
5
6
|
export { a11yKeyboard, capitalize, checkDefaultPrevented, checkElementName, clearWindowSelection, getSelectionPosition, getTitle, globalStateClasses, isDeleteKeyboardEvent, isSuiElement, isUrl, isWebKit154, omit, outerSize, pick, preloadImg, removeClassName, runKeyHandler, setSelectionPosition, useClasses } from './utils.js';
|
|
6
7
|
export { a as ariaHideOutside, b as combineClasses, c as combineProps, g as getRefElement, i as isRef, l as logInDev, o as ownerWindow, t as twMerge } from './chunks/twMerge.js';
|
|
7
8
|
import 'react';
|
package/liveAnnouncer.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { L as LIVEREGION_TIMEOUT_DELAY, a as announce, c as clearAnnouncer, d as destroyAnnouncer } from './chunks/index.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ringcentral/spring-base",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.0",
|
|
4
4
|
"author": "RingCentral",
|
|
5
5
|
"description": "Core utilities, hooks, and foundational functionality for Spring UI components, providing essential building blocks for React applications",
|
|
6
6
|
"main": "./index.js",
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
"type-fest": "^2.12.2"
|
|
17
17
|
},
|
|
18
18
|
"peerDependencies": {
|
|
19
|
-
"@types/react": "
|
|
20
|
-
"react": "
|
|
21
|
-
"react-dom": "
|
|
19
|
+
"@types/react": "^17.0.3 || ^18.0.0 || ^19.0.0",
|
|
20
|
+
"react": "^17.0.2 || ^18.0.0 || ^19.0.0",
|
|
21
|
+
"react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0"
|
|
22
22
|
},
|
|
23
23
|
"sideEffects": false,
|
|
24
24
|
"license": "MIT",
|
package/src/hooks/index.d.ts
CHANGED
|
@@ -1,2 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
1
|
+
import type { AnnouncerPoliteness } from '../../liveAnnouncer';
|
|
2
|
+
export type UseAnnouncerOptions = {
|
|
3
|
+
politeness?: AnnouncerPoliteness;
|
|
4
|
+
};
|
|
5
|
+
export declare function useAnnouncer(idOrOptions?: string | UseAnnouncerOptions, maybeOptions?: UseAnnouncerOptions): (message?: string) => void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './useGlobalAnnouncer';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AnnounceMessage, AnnouncerPoliteness } from '../../liveAnnouncer/types';
|
|
2
|
+
export type GlobalAnnounceOptions = {
|
|
3
|
+
timeout?: number;
|
|
4
|
+
};
|
|
5
|
+
export declare function useGlobalAnnouncer(): (message: AnnounceMessage, politeness?: AnnouncerPoliteness, timeoutOrOptions?: number | GlobalAnnounceOptions) => void;
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
|
+
import type { HTMLDataAttribute } from '../../typings';
|
|
2
3
|
export type UseOnlyOneFocusableParams = {
|
|
3
4
|
focusedIndexRef: React.MutableRefObject<number>;
|
|
4
5
|
containerRef: React.RefObject<HTMLElement>;
|
|
5
6
|
};
|
|
7
|
+
type MoveFocusedItemProps = Omit<React.HTMLAttributes<HTMLElement>, 'children'> & HTMLDataAttribute;
|
|
6
8
|
export declare const useOnlyOneFocusable: ({ focusedIndexRef, containerRef, }: UseOnlyOneFocusableParams) => {
|
|
7
9
|
moveFocusedId: string;
|
|
8
10
|
focusIndex: (toIndex: number) => void;
|
|
9
11
|
setIndexTabIndex: (toIndex: number) => void;
|
|
10
|
-
getItemProps: (index: number, options?: Omit<React.HTMLAttributes<HTMLElement>, 'children'>) =>
|
|
12
|
+
getItemProps: (index: number, options?: Omit<React.HTMLAttributes<HTMLElement>, 'children'>) => NonNullable<MoveFocusedItemProps>;
|
|
11
13
|
};
|
|
14
|
+
export {};
|
package/src/index.d.ts
CHANGED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { AnnounceMessage, AnnouncerPoliteness } from './types';
|
|
2
|
+
export declare class LiveAnnouncer {
|
|
3
|
+
node: HTMLElement | null;
|
|
4
|
+
assertiveLog: HTMLElement | null;
|
|
5
|
+
politeLog: HTMLElement | null;
|
|
6
|
+
private readonly doc;
|
|
7
|
+
private readonly repeatText;
|
|
8
|
+
private ready;
|
|
9
|
+
private pending;
|
|
10
|
+
private firstDelayTimerId;
|
|
11
|
+
constructor(doc: Document);
|
|
12
|
+
isAttached(): boolean;
|
|
13
|
+
createLog(ariaLive: AnnouncerPoliteness): HTMLElement;
|
|
14
|
+
destroy(): void;
|
|
15
|
+
schedule(message: AnnounceMessage, politeness?: AnnouncerPoliteness, timeout?: number): void;
|
|
16
|
+
announce(message: AnnounceMessage, politeness?: AnnouncerPoliteness, timeout?: number): void;
|
|
17
|
+
clear(politeness?: AnnouncerPoliteness): void;
|
|
18
|
+
private cancelFirstDelay;
|
|
19
|
+
}
|
|
20
|
+
export declare const LIVEREGION_TIMEOUT_DELAY = 7000;
|
|
21
|
+
export declare const FIRST_ANNOUNCE_DELAY_MS = 100;
|
|
22
|
+
export declare const getLiveAnnouncer: (doc: Document) => LiveAnnouncer | null;
|
|
23
|
+
export declare const ensureLiveAnnouncer: (doc: Document) => LiveAnnouncer;
|
|
24
|
+
export declare const clearLiveAnnouncerRegistry: (doc?: Document) => void;
|
|
25
|
+
export declare const isTestAnnounceEnvironment: () => boolean;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { AnnounceMessage, AnnounceOptions, AnnouncerPoliteness } from './types';
|
|
2
|
+
export declare function announce(message: AnnounceMessage, politeness?: AnnouncerPoliteness, timeoutOrOptions?: number | AnnounceOptions): void;
|
|
3
|
+
export declare function clearAnnouncer(politeness?: AnnouncerPoliteness, doc?: Document): void;
|
|
4
|
+
export declare function destroyAnnouncer(doc?: Document): void;
|