@ringcentral/spring-base 1.11.1 → 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.
@@ -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
@@ -7,12 +7,16 @@ import { extendTailwindMerge } from 'tailwind-merge';
7
7
  const ownerWindow = unstable_ownerWindow;
8
8
 
9
9
  const ARIA_HIDDEN = 'aria-hidden';
10
- const REF_COUNT_MAP = '__SUI_ARIA_HIDE_OUTSIDE_REF_COUNT_MAP__';
11
- const getRefCountMap = (window) => {
12
- if (!window[REF_COUNT_MAP]) {
13
- window[REF_COUNT_MAP] = new WeakMap();
10
+ const TOP_LAYER_ATTR = 'data-sui-top-layer';
11
+ const alwaysVisibleSelector = `[data-live-announcer="true"], [${TOP_LAYER_ATTR}]`;
12
+ const ARIA_HIDDEN_REF_COUNT_MAP = '__SUI_ARIA_HIDE_OUTSIDE_REF_COUNT_MAP__';
13
+ const INERT_REF_COUNT_MAP = '__SUI_ARIA_HIDE_OUTSIDE_INERT_REF_COUNT_MAP__';
14
+ const getRefCountMap = (window, mechanism) => {
15
+ const key = mechanism === 'inert' ? INERT_REF_COUNT_MAP : ARIA_HIDDEN_REF_COUNT_MAP;
16
+ if (!window[key]) {
17
+ window[key] = new WeakMap();
14
18
  }
15
- return window[REF_COUNT_MAP];
19
+ return window[key];
16
20
  };
17
21
  const OBSERVER_STACK = '__SUI_ARIA_HIDE_OUTSIDE_OBSERVER_STACK__';
18
22
  const getObserverStack = (window) => {
@@ -21,109 +25,332 @@ const getObserverStack = (window) => {
21
25
  }
22
26
  return window[OBSERVER_STACK];
23
27
  };
24
- const isHTMLOrSVGElement = (node) => {
25
- return 'dataset' in node;
26
- };
27
28
  const isElement = (node) => {
28
- return node.nodeType === Node.ELEMENT_NODE;
29
+ return (typeof node === 'object' &&
30
+ node != null &&
31
+ 'nodeType' in node &&
32
+ node.nodeType === 1);
33
+ };
34
+ const isAlwaysVisibleNode = (node) => {
35
+ return (node.getAttribute('data-live-announcer') === 'true' ||
36
+ node.hasAttribute(TOP_LAYER_ATTR));
29
37
  };
30
- const ariaHideOutside = (targets, root = document.body) => {
31
- const currentWindow = ownerWindow(root);
38
+ const createAriaHideOutsideController = (targets, options = {}) => {
39
+ const isLegacyRoot = isElement(options);
40
+ const configuredRoot = isLegacyRoot
41
+ ? options
42
+ : options.root;
43
+ const currentWindow = ownerWindow(configuredRoot ?? targets[0]);
32
44
  if (!currentWindow) {
33
- return () => { };
45
+ return {
46
+ addTarget: () => { },
47
+ removeTarget: () => { },
48
+ dispose: () => { },
49
+ };
34
50
  }
51
+ const root = configuredRoot ?? currentWindow.document.body;
52
+ const shouldUseInert = !isLegacyRoot &&
53
+ options.shouldUseInert &&
54
+ 'inert' in currentWindow.HTMLElement.prototype;
35
55
  const visibleNodes = new Set(targets);
56
+ const targetNodes = new Set(targets);
57
+ const automaticVisibleNodes = new Set();
36
58
  const hiddenNodes = new Set();
37
- const refCountMap = getRefCountMap(currentWindow);
38
- const observerStack = getObserverStack(currentWindow);
39
- const walker = currentWindow.document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, {
40
- acceptNode(node) {
41
- if (isHTMLOrSVGElement(node) &&
42
- node.dataset['liveAnnouncer'] === 'true') {
43
- visibleNodes.add(node);
59
+ const targetPaths = new Map();
60
+ const getTargetPath = (target) => {
61
+ const path = [];
62
+ let node = target;
63
+ while (node && root.contains(node)) {
64
+ path.push(node);
65
+ if (node === root) {
66
+ break;
44
67
  }
45
- if (visibleNodes.has(node) ||
46
- (node.parentElement &&
47
- hiddenNodes.has(node.parentElement) &&
48
- node.parentElement.getAttribute('role') !== 'row')) {
49
- return NodeFilter.FILTER_REJECT;
50
- }
51
- if (targets.some((target) => node.contains(target))) {
52
- return NodeFilter.FILTER_SKIP;
53
- }
54
- return NodeFilter.FILTER_ACCEPT;
55
- },
56
- });
68
+ node = node.parentElement;
69
+ }
70
+ return path;
71
+ };
72
+ for (const target of targets) {
73
+ targetPaths.set(target, getTargetPath(target));
74
+ }
75
+ const observerStack = getObserverStack(currentWindow);
76
+ const getHideMechanism = (node) => shouldUseInert && node instanceof currentWindow.HTMLElement
77
+ ? 'inert'
78
+ : 'aria-hidden';
79
+ const getHidden = (node, mechanism) => mechanism === 'inert'
80
+ ? node.inert
81
+ : node.getAttribute(ARIA_HIDDEN) === 'true';
82
+ const setHidden = (node, mechanism, hidden) => {
83
+ if (mechanism === 'inert') {
84
+ node.inert = hidden;
85
+ }
86
+ else if (hidden) {
87
+ node.setAttribute(ARIA_HIDDEN, 'true');
88
+ }
89
+ else {
90
+ node.removeAttribute(ARIA_HIDDEN);
91
+ }
92
+ };
93
+ const restoreHiddenNode = (node) => {
94
+ if (!hiddenNodes.delete(node)) {
95
+ return;
96
+ }
97
+ const mechanism = getHideMechanism(node);
98
+ const refCountMap = getRefCountMap(currentWindow, mechanism);
99
+ const count = refCountMap.get(node);
100
+ if (typeof count !== 'number') {
101
+ return;
102
+ }
103
+ if (count === 1) {
104
+ setHidden(node, mechanism, false);
105
+ refCountMap.delete(node);
106
+ }
107
+ else {
108
+ refCountMap.set(node, count - 1);
109
+ }
110
+ };
57
111
  const hide = (node) => {
112
+ if (hiddenNodes.has(node)) {
113
+ return;
114
+ }
115
+ const mechanism = getHideMechanism(node);
116
+ const refCountMap = getRefCountMap(currentWindow, mechanism);
58
117
  const refCount = refCountMap.get(node) ?? 0;
59
- if (node.getAttribute(ARIA_HIDDEN) === 'true' && refCount === 0) {
118
+ if (getHidden(node, mechanism) && refCount === 0) {
60
119
  return;
61
120
  }
62
121
  if (refCount === 0) {
63
- node.setAttribute(ARIA_HIDDEN, 'true');
122
+ setHidden(node, mechanism, true);
64
123
  }
65
124
  hiddenNodes.add(node);
66
125
  refCountMap.set(node, refCount + 1);
67
126
  };
127
+ const addAlwaysVisibleNode = (node) => {
128
+ automaticVisibleNodes.add(node);
129
+ visibleNodes.add(node);
130
+ };
131
+ const addAlwaysVisibleNodes = (node) => {
132
+ if (isAlwaysVisibleNode(node)) {
133
+ addAlwaysVisibleNode(node);
134
+ }
135
+ for (const alwaysVisibleNode of node.querySelectorAll(alwaysVisibleSelector)) {
136
+ addAlwaysVisibleNode(alwaysVisibleNode);
137
+ }
138
+ };
139
+ const removeDisconnectedAlwaysVisibleNodes = (node) => {
140
+ for (const alwaysVisibleNode of automaticVisibleNodes) {
141
+ if (!node.contains(alwaysVisibleNode) ||
142
+ root.contains(alwaysVisibleNode)) {
143
+ continue;
144
+ }
145
+ automaticVisibleNodes.delete(alwaysVisibleNode);
146
+ if (!targetNodes.has(alwaysVisibleNode)) {
147
+ visibleNodes.delete(alwaysVisibleNode);
148
+ }
149
+ }
150
+ };
151
+ const containsNode = (nodes, node) => {
152
+ for (const candidate of nodes) {
153
+ if (candidate.contains(node)) {
154
+ return true;
155
+ }
156
+ }
157
+ return false;
158
+ };
159
+ const containsVisibleNode = (node, nodes) => {
160
+ for (const visibleNode of nodes) {
161
+ if (node.contains(visibleNode)) {
162
+ return true;
163
+ }
164
+ }
165
+ return false;
166
+ };
167
+ const isCoveredByHiddenNode = (node) => {
168
+ let ancestor = node.parentElement;
169
+ while (ancestor && root.contains(ancestor)) {
170
+ if (hiddenNodes.has(ancestor)) {
171
+ return !(ancestor === node.parentElement &&
172
+ ancestor.getAttribute('role') === 'row');
173
+ }
174
+ ancestor = ancestor.parentElement;
175
+ }
176
+ return false;
177
+ };
178
+ const shouldAdoptHiddenNode = (node) => {
179
+ return (root.contains(node) &&
180
+ !hiddenNodes.has(node) &&
181
+ !containsNode(visibleNodes, node) &&
182
+ !containsVisibleNode(node, visibleNodes) &&
183
+ !isCoveredByHiddenNode(node));
184
+ };
185
+ const acceptNode = (node) => {
186
+ if (visibleNodes.has(node) ||
187
+ (node.parentElement &&
188
+ hiddenNodes.has(node.parentElement) &&
189
+ node.parentElement.getAttribute('role') !== 'row')) {
190
+ return currentWindow.NodeFilter.FILTER_REJECT;
191
+ }
192
+ if (containsVisibleNode(node, visibleNodes)) {
193
+ return currentWindow.NodeFilter.FILTER_SKIP;
194
+ }
195
+ return currentWindow.NodeFilter.FILTER_ACCEPT;
196
+ };
197
+ const walk = (walkRoot, includeRoot) => {
198
+ addAlwaysVisibleNodes(walkRoot);
199
+ const rootFilter = acceptNode(walkRoot);
200
+ if (includeRoot && rootFilter === currentWindow.NodeFilter.FILTER_ACCEPT) {
201
+ hide(walkRoot);
202
+ }
203
+ if (rootFilter === currentWindow.NodeFilter.FILTER_REJECT) {
204
+ return;
205
+ }
206
+ const walker = currentWindow.document.createTreeWalker(walkRoot, currentWindow.NodeFilter.SHOW_ELEMENT, { acceptNode });
207
+ let node = walker.nextNode();
208
+ while (node != null) {
209
+ hide(node);
210
+ node = walker.nextNode();
211
+ }
212
+ };
213
+ const addTarget = (target) => {
214
+ if (!root.contains(target)) {
215
+ return;
216
+ }
217
+ targetNodes.add(target);
218
+ visibleNodes.add(target);
219
+ const path = getTargetPath(target);
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]);
226
+ }
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
+ }
237
+ };
238
+ const removeTarget = (target) => {
239
+ if (!targetNodes.delete(target)) {
240
+ return;
241
+ }
242
+ if (!automaticVisibleNodes.has(target)) {
243
+ visibleNodes.delete(target);
244
+ }
245
+ const targetPath = targetPaths.get(target);
246
+ targetPaths.delete(target);
247
+ const connectedAncestor = targetPath?.find((node) => root.contains(node));
248
+ if (!connectedAncestor) {
249
+ return;
250
+ }
251
+ let walkRoot = connectedAncestor;
252
+ while (walkRoot.parentElement &&
253
+ walkRoot.parentElement !== root &&
254
+ !containsVisibleNode(walkRoot.parentElement, visibleNodes)) {
255
+ walkRoot = walkRoot.parentElement;
256
+ }
257
+ walk(walkRoot, true);
258
+ };
68
259
  if (observerStack.length) {
69
260
  observerStack[observerStack.length - 1].disconnect();
70
261
  }
71
- let node = walker.nextNode();
72
- while (node != null) {
73
- hide(node);
74
- node = walker.nextNode();
75
- }
76
- const observer = new currentWindow.MutationObserver((changes) => {
262
+ walk(root, false);
263
+ const processChanges = (changes) => {
77
264
  for (const change of changes) {
78
- if (change.type !== 'childList' || change.addedNodes.length === 0) {
265
+ if (change.type !== 'childList') {
79
266
  continue;
80
267
  }
81
- if (![...visibleNodes, ...hiddenNodes].some((node) => node.contains(change.target))) {
268
+ for (const node of change.removedNodes) {
269
+ if (isElement(node)) {
270
+ removeDisconnectedAlwaysVisibleNodes(node);
271
+ }
272
+ }
273
+ if (!change.target.isConnected || change.addedNodes.length === 0) {
274
+ continue;
275
+ }
276
+ if (!containsNode(visibleNodes, change.target) &&
277
+ !containsNode(hiddenNodes, change.target)) {
82
278
  for (const node of change.addedNodes) {
83
- if (isHTMLOrSVGElement(node) &&
84
- node.dataset['liveAnnouncer'] === 'true') {
85
- visibleNodes.add(node);
86
- }
87
- else if (isElement(node)) {
88
- hide(node);
279
+ if (isElement(node) && root.contains(node)) {
280
+ walk(node, true);
89
281
  }
90
282
  }
91
283
  }
92
284
  }
93
- });
285
+ };
286
+ const observer = new currentWindow.MutationObserver(processChanges);
94
287
  observer.observe(root, { childList: true, subtree: true });
95
288
  const observerWrapper = {
96
289
  observe() {
97
290
  observer.observe(root, { childList: true, subtree: true });
98
291
  },
292
+ resume(releasedHiddenNodes, releasedAlwaysVisibleNodes) {
293
+ for (const node of releasedAlwaysVisibleNodes) {
294
+ if (root.contains(node)) {
295
+ addAlwaysVisibleNode(node);
296
+ }
297
+ }
298
+ for (const node of releasedHiddenNodes) {
299
+ if (shouldAdoptHiddenNode(node)) {
300
+ hide(node);
301
+ }
302
+ }
303
+ observerWrapper.observe();
304
+ },
99
305
  disconnect() {
100
306
  observer.disconnect();
101
307
  },
102
308
  };
103
309
  observerStack.push(observerWrapper);
104
- return () => {
105
- observer.disconnect();
106
- for (const node of hiddenNodes) {
107
- const count = refCountMap.get(node);
108
- if (count === 1) {
109
- node.removeAttribute(ARIA_HIDDEN);
110
- refCountMap.delete(node);
111
- }
112
- else if (typeof count === 'number') {
113
- refCountMap.set(node, count - 1);
114
- }
310
+ let isCleanedUp = false;
311
+ const dispose = () => {
312
+ if (isCleanedUp) {
313
+ return;
115
314
  }
315
+ isCleanedUp = true;
316
+ const pendingChanges = observer.takeRecords();
317
+ observer.disconnect();
318
+ processChanges(pendingChanges);
116
319
  if (observerWrapper === observerStack[observerStack.length - 1]) {
117
320
  observerStack.pop();
118
- if (observerStack.length) {
119
- observerStack[observerStack.length - 1].observe();
321
+ const previousObserver = observerStack[observerStack.length - 1];
322
+ if (previousObserver?.resume) {
323
+ previousObserver.resume(hiddenNodes, automaticVisibleNodes);
324
+ }
325
+ else {
326
+ previousObserver?.observe();
120
327
  }
121
328
  }
122
329
  else {
123
330
  observerStack.splice(observerStack.indexOf(observerWrapper), 1);
124
331
  }
332
+ for (const node of [...hiddenNodes]) {
333
+ restoreHiddenNode(node);
334
+ }
335
+ targetPaths.clear();
336
+ targetNodes.clear();
337
+ automaticVisibleNodes.clear();
338
+ };
339
+ return {
340
+ addTarget: (target) => {
341
+ if (!isCleanedUp) {
342
+ addTarget(target);
343
+ }
344
+ },
345
+ removeTarget: (target) => {
346
+ if (!isCleanedUp) {
347
+ removeTarget(target);
348
+ }
349
+ },
350
+ dispose,
125
351
  };
126
352
  };
353
+ const ariaHideOutside = (targets, options = {}) => createAriaHideOutsideController(targets, options).dispose;
127
354
 
128
355
  const combineProps = (baseProps, newProps, coverPropKeys) => innerCombineProps({
129
356
  baseProps,
@@ -273,4 +500,4 @@ const twMerge = extendTailwindMerge({
273
500
  },
274
501
  });
275
502
 
276
- export { ariaHideOutside as a, combineClasses as b, combineProps as c, getRefElement as g, isRef as i, logInDev as l, ownerWindow as o, twMerge as t };
503
+ export { ariaHideOutside as a, combineClasses as b, combineProps as c, createAriaHideOutsideController as d, getRefElement as g, isRef as i, logInDev as l, ownerWindow as o, twMerge as t };
package/hooks.js CHANGED
@@ -1,11 +1,12 @@
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
- import { c as combineProps, a as ariaHideOutside, g as getRefElement, l as logInDev, o as ownerWindow } from './chunks/twMerge.js';
9
+ import { c as combineProps, d as createAriaHideOutsideController, g as getRefElement, l as logInDev, o as ownerWindow } from './chunks/twMerge.js';
9
10
  import './config.js';
10
11
  import 'lodash/omit';
11
12
  import 'lodash/pick';
@@ -128,7 +129,7 @@ function useResultRef(cb) {
128
129
  }
129
130
 
130
131
  const DEFAULT_PREFIX = 'spring-id-';
131
- function useId(id, prefixMode) {
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 visuallyHiddenStyles = `
145
- clip-path: inset(100%);
146
- clip: rect(1px, 1px, 1px, 1px);
147
- height: 1px;
148
- overflow: hidden;
149
- position: absolute;
150
- white-space: nowrap;
151
- width: 1px;
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 el = currentDocument.createElement('div');
173
- ref.current = el;
174
- el.id = _id;
175
- el.setAttribute('aria-live', 'assertive');
176
- el.setAttribute('aria-atomic', 'true');
177
- el.setAttribute('aria-hidden', 'false');
178
- el.setAttribute('data-live-announcer', 'true');
179
- el.setAttribute('style', visuallyHiddenStyles);
180
- currentDocument.body.appendChild(el);
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
- }, [_id, targetDocument, contextGlobal, win, clearPendingTimer]);
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);
@@ -241,18 +295,42 @@ function useButton(parameters) {
241
295
  }
242
296
 
243
297
  const useAriaHideOutside = (props = {}) => {
244
- const { enable = true } = props;
245
- const { externalWindow, document } = useWindowContext();
298
+ const { enable = true, shouldUseInert = false } = props;
299
+ const { document } = useWindowContext();
246
300
  const ariaHideOutsideRef = useRef(null);
247
- useEffect(() => {
301
+ const branchNodesRef = useRef(new Set());
302
+ const controllerRef = useRef();
303
+ const setAriaHideOutsideTarget = useCallback((node, isActive) => {
304
+ if (isActive) {
305
+ branchNodesRef.current.add(node);
306
+ controllerRef.current?.addTarget(node);
307
+ }
308
+ else {
309
+ branchNodesRef.current.delete(node);
310
+ controllerRef.current?.removeTarget(node);
311
+ }
312
+ }, []);
313
+ useLayoutEffect(() => {
248
314
  if (enable && ariaHideOutsideRef.current) {
249
- return ariaHideOutside([ariaHideOutsideRef.current], document?.body);
315
+ const controller = createAriaHideOutsideController([ariaHideOutsideRef.current, ...branchNodesRef.current], {
316
+ root: document?.body,
317
+ shouldUseInert,
318
+ });
319
+ controllerRef.current = controller;
320
+ return () => {
321
+ if (controllerRef.current === controller) {
322
+ controllerRef.current = undefined;
323
+ }
324
+ controller.dispose();
325
+ };
250
326
  }
327
+ controllerRef.current = undefined;
251
328
  return undefined;
252
- }, [enable, externalWindow, document?.body]);
329
+ }, [enable, shouldUseInert, document?.body]);
253
330
  return {
254
331
  ariaHideOutsideRef: ariaHideOutsideRef,
255
332
  ariaHideEnabled: enable,
333
+ setAriaHideOutsideTarget,
256
334
  };
257
335
  };
258
336
 
@@ -263,9 +341,9 @@ const useAudio = (init) => {
263
341
  return audio;
264
342
  });
265
343
  useEffect(() => {
344
+ const audio = audioRef.current;
266
345
  return () => {
267
- audioRef.current.srcObject = null;
268
- audioRef.current = null;
346
+ audio.srcObject = null;
269
347
  };
270
348
  }, []);
271
349
  return audioRef.current;
@@ -1099,13 +1177,14 @@ const useKeyboardMoveFocus = ({ infinite, options = [], focusedIndexRef, onFocus
1099
1177
  };
1100
1178
 
1101
1179
  const MOVE_ATTR_KEY = 'data-move-focused';
1180
+ const MOVE_INDEX_ATTR_KEY = 'data-move-index';
1102
1181
  const useOnlyOneFocusable = ({ focusedIndexRef, containerRef, }) => {
1103
- const moveFocusedId = useId(MOVE_ATTR_KEY, true);
1104
- 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}"]`);
1105
1184
  const setIndexTabIndex = (toIndex) => {
1106
1185
  const toElm = getIndexElm(toIndex);
1107
1186
  if (toElm) {
1108
- const prevElm = containerRef.current?.querySelector(`[${moveFocusedId}][tabindex="0"]`);
1187
+ const prevElm = containerRef.current?.querySelector(`[${MOVE_ATTR_KEY}="${moveFocusedId}"][tabindex="0"]`);
1109
1188
  prevElm?.setAttribute('tabindex', '-1');
1110
1189
  toElm.setAttribute('tabindex', '0');
1111
1190
  }
@@ -1120,13 +1199,14 @@ const useOnlyOneFocusable = ({ focusedIndexRef, containerRef, }) => {
1120
1199
  setIndexTabIndex,
1121
1200
  getItemProps: (index, options) => {
1122
1201
  return combineProps({
1123
- [moveFocusedId]: index,
1202
+ [MOVE_ATTR_KEY]: moveFocusedId,
1203
+ [MOVE_INDEX_ATTR_KEY]: String(index),
1124
1204
  tabIndex: index === focusedIndexRef.current ? 0 : -1,
1125
1205
  onFocus: () => {
1126
1206
  focusedIndexRef.current = index;
1127
1207
  setIndexTabIndex(index);
1128
1208
  },
1129
- }, options);
1209
+ }, options ?? {});
1130
1210
  },
1131
1211
  };
1132
1212
  };
@@ -1942,4 +2022,4 @@ const useVirtuosoExternalWindow = () => {
1942
2022
  return undefined;
1943
2023
  };
1944
2024
 
1945
- 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, visuallyHiddenStyles };
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, visuallyHiddenStyles } from './hooks.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, 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';
@@ -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.11.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": ">=17.0.3",
20
- "react": ">=17.0.2",
21
- "react-dom": ">=17.0.2"
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",
@@ -1,6 +1,7 @@
1
1
  export * from './useA11yKeyEvent';
2
2
  export * from './useActive';
3
3
  export * from './useAnnouncer';
4
+ export * from './useGlobalAnnouncer';
4
5
  export * from './useButton';
5
6
  export * from './useAriaHideOutside';
6
7
  export * from './useAudio';
@@ -1,2 +1,5 @@
1
- export declare const visuallyHiddenStyles = "\n clip-path: inset(100%);\n clip: rect(1px, 1px, 1px, 1px);\n height: 1px;\n overflow: hidden;\n position: absolute;\n white-space: nowrap;\n width: 1px;\n";
2
- export declare const useAnnouncer: (id?: string) => (message?: string) => void;
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;
@@ -1,8 +1,10 @@
1
1
  /// <reference types="react" />
2
2
  export type UseAriaHideOutsideProps = {
3
3
  enable?: boolean;
4
+ shouldUseInert?: boolean;
4
5
  };
5
6
  export declare const useAriaHideOutside: (props?: UseAriaHideOutsideProps) => {
6
7
  ariaHideOutsideRef: import("react").RefObject<HTMLElement>;
7
8
  ariaHideEnabled: boolean;
9
+ setAriaHideOutsideTarget: (node: HTMLElement, isActive: boolean) => void;
8
10
  };
@@ -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 +1,3 @@
1
- export declare function useId(id?: string, prefixMode?: boolean): string;
1
+ declare function useReactId(id?: string, prefixMode?: boolean): string;
2
+ export declare const useId: typeof useReactId;
3
+ export {};
@@ -10,7 +10,7 @@ export type UseKeyboardMoveFocusParams<T> = {
10
10
  endSearchDebounceTime?: number;
11
11
  } & Pick<MoveIndexInTwoDimensionalOptions, 'infinite'>;
12
12
  export declare const useKeyboardMoveFocus: <T>({ infinite, options, focusedIndexRef, onFocusedIndexChange, getOptionDisabled, getOptionSearchText, columns, endSearchDebounceTime, }: UseKeyboardMoveFocusParams<T>) => {
13
- getPrevFocusableOption: (index?: number, isInfinite?: any) => number;
14
- getNextFocusableOption: (index?: number, isInfinite?: any) => number;
13
+ getPrevFocusableOption: (index?: number, isInfinite?: boolean) => number;
14
+ getNextFocusableOption: (index?: number, isInfinite?: boolean) => number;
15
15
  onKeyFocusedIndexHandle: (event: React.KeyboardEvent<any>) => void;
16
16
  };
@@ -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'>) => Omit<import("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
@@ -1,5 +1,6 @@
1
1
  export * from './config';
2
2
  export * from './contexts';
3
3
  export * from './hooks';
4
+ export * from './liveAnnouncer';
4
5
  export * from './typings';
5
6
  export * from './utils';
@@ -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;
@@ -0,0 +1,3 @@
1
+ export type { AnnounceMessage, AnnounceOptions, AnnouncerPoliteness, } from './types';
2
+ export { announce, clearAnnouncer, destroyAnnouncer } from './announce';
3
+ export { LIVEREGION_TIMEOUT_DELAY } from './LiveAnnouncer';
@@ -0,0 +1,8 @@
1
+ export type AnnouncerPoliteness = 'assertive' | 'polite';
2
+ export type AnnounceMessage = string | {
3
+ 'aria-labelledby': string;
4
+ };
5
+ export type AnnounceOptions = {
6
+ timeout?: number;
7
+ document?: Document;
8
+ };
@@ -0,0 +1,3 @@
1
+ export declare const LIVE_ANNOUNCER_ATTR = "data-live-announcer";
2
+ export declare const visuallyHiddenStyleProperties: Partial<CSSStyleDeclaration>;
3
+ export declare const applyVisuallyHiddenStyles: (el: HTMLElement) => void;
@@ -1 +1,11 @@
1
- export declare const ariaHideOutside: (targets: Element[], root?: HTMLElement) => () => void;
1
+ export type AriaHideOutsideController = {
2
+ addTarget: (target: Element) => void;
3
+ removeTarget: (target: Element) => void;
4
+ dispose: () => void;
5
+ };
6
+ export type AriaHideOutsideOptions = {
7
+ root?: Element;
8
+ shouldUseInert?: boolean;
9
+ };
10
+ export declare const createAriaHideOutsideController: (targets: Element[], options?: AriaHideOutsideOptions | Element) => AriaHideOutsideController;
11
+ export declare const ariaHideOutside: (targets: Element[], options?: AriaHideOutsideOptions | Element) => () => void;
@@ -1,5 +1,6 @@
1
1
  export * from './a11yKeyboardCode';
2
- export * from './ariaHideOutside';
2
+ export { ariaHideOutside } from './ariaHideOutside';
3
+ export type { AriaHideOutsideOptions } from './ariaHideOutside';
3
4
  export * from './capitalize';
4
5
  export * from './checkDefaultPrevented';
5
6
  export * from './checkElementName';