pixel-react 1.22.5 → 1.22.6
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/lib/components/AutoTruncateText/AutoTruncateText.js +38 -30
- package/lib/components/AutoTruncateText/AutoTruncateText.js.map +1 -1
- package/lib/components/AutoTruncateText/resizeObserverService.d.ts +10 -2
- package/lib/components/AutoTruncateText/resizeObserverService.js +19 -12
- package/lib/components/AutoTruncateText/resizeObserverService.js.map +1 -1
- package/lib/components/HighlightText/HighlightText.d.ts +2 -2
- package/lib/components/HighlightText/HighlightText.js +144 -13
- package/lib/components/HighlightText/HighlightText.js.map +1 -1
- package/lib/components/HighlightText/index.d.ts +1 -0
- package/lib/components/HighlightText/types.d.ts +46 -0
- package/lib/components/Icon/iconList.js +2 -4
- package/lib/components/Icon/iconList.js.map +1 -1
- package/lib/components/MenuOption/MenuOption.js +22 -31
- package/lib/components/MenuOption/MenuOption.js.map +1 -1
- package/lib/components/Tooltip/Tooltip.js +20 -26
- package/lib/components/Tooltip/Tooltip.js.map +1 -1
- package/lib/components/TooltipService/TooltipService.js +219 -109
- package/lib/components/TooltipService/TooltipService.js.map +1 -1
- package/lib/hooks/useFocusTrap.d.ts +29 -0
- package/lib/hooks/useFocusTrap.js +97 -0
- package/lib/hooks/useFocusTrap.js.map +1 -0
- package/lib/hooks/useInteractiveAncestor.d.ts +7 -0
- package/lib/hooks/useInteractiveAncestor.js +18 -0
- package/lib/hooks/useInteractiveAncestor.js.map +1 -0
- package/lib/index.d.ts +50 -175
- package/lib/index.js +9 -27
- package/lib/index.js.map +1 -1
- package/lib/styles.css +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/utils/a11y/a11y.d.ts +28 -0
- package/lib/utils/a11y/a11y.js +62 -0
- package/lib/utils/a11y/a11y.js.map +1 -0
- package/lib/utils/truncateText/truncateText.js +29 -15
- package/lib/utils/truncateText/truncateText.js.map +1 -1
- package/package.json +119 -129
- package/lib/components/RichTextEditor/DynamicValue.d.ts +0 -8
- package/lib/components/RichTextEditor/DynamicValue.js +0 -73
- package/lib/components/RichTextEditor/DynamicValue.js.map +0 -1
- package/lib/components/RichTextEditor/DynamicValueList.d.ts +0 -11
- package/lib/components/RichTextEditor/DynamicValueList.js +0 -49
- package/lib/components/RichTextEditor/DynamicValueList.js.map +0 -1
- package/lib/components/RichTextEditor/ImagePopover.d.ts +0 -10
- package/lib/components/RichTextEditor/ImagePopover.js +0 -99
- package/lib/components/RichTextEditor/ImagePopover.js.map +0 -1
- package/lib/components/RichTextEditor/LinkPopover.d.ts +0 -8
- package/lib/components/RichTextEditor/LinkPopover.js +0 -98
- package/lib/components/RichTextEditor/LinkPopover.js.map +0 -1
- package/lib/components/RichTextEditor/RichTextEditor.d.ts +0 -5
- package/lib/components/RichTextEditor/RichTextEditor.js +0 -216
- package/lib/components/RichTextEditor/RichTextEditor.js.map +0 -1
- package/lib/components/RichTextEditor/Toolbar.d.ts +0 -12
- package/lib/components/RichTextEditor/Toolbar.js +0 -186
- package/lib/components/RichTextEditor/Toolbar.js.map +0 -1
- package/lib/components/RichTextEditor/constants.d.ts +0 -4
- package/lib/components/RichTextEditor/constants.js +0 -33
- package/lib/components/RichTextEditor/constants.js.map +0 -1
- package/lib/components/RichTextEditor/fileAttachment.d.ts +0 -23
- package/lib/components/RichTextEditor/fileAttachment.js +0 -143
- package/lib/components/RichTextEditor/fileAttachment.js.map +0 -1
- package/lib/components/RichTextEditor/imageUpload.d.ts +0 -6
- package/lib/components/RichTextEditor/imageUpload.js +0 -29
- package/lib/components/RichTextEditor/imageUpload.js.map +0 -1
- package/lib/components/RichTextEditor/index.d.ts +0 -1
- package/lib/components/RichTextEditor/index.js +0 -2
- package/lib/components/RichTextEditor/index.js.map +0 -1
- package/lib/components/RichTextEditor/pasteFormatting.d.ts +0 -18
- package/lib/components/RichTextEditor/pasteFormatting.js +0 -167
- package/lib/components/RichTextEditor/pasteFormatting.js.map +0 -1
- package/lib/components/RichTextEditor/types.d.ts +0 -167
- package/lib/components/RichTextEditor/types.js +0 -2
- package/lib/components/RichTextEditor/types.js.map +0 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { useEffect } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import tippy, { hideAll } from 'tippy.js';
|
|
3
3
|
import 'tippy.js/dist/tippy.css';
|
|
4
4
|
import './TooltipService.scss';
|
|
5
|
+
const TOOLTIP_TARGET = '[data-tooltip], [data-tooltip-id], [data-tooltip-overflow]';
|
|
5
6
|
const VALID_PLACEMENTS = new Set([
|
|
6
7
|
'auto',
|
|
7
8
|
'auto-start',
|
|
@@ -19,6 +20,30 @@ const VALID_PLACEMENTS = new Set([
|
|
|
19
20
|
'left-start',
|
|
20
21
|
'left-end',
|
|
21
22
|
]);
|
|
23
|
+
const TOOLTIP_PROPS = {
|
|
24
|
+
arrow: false,
|
|
25
|
+
theme: 'ff-global-tooltip',
|
|
26
|
+
appendTo: () => document.body,
|
|
27
|
+
allowHTML: true,
|
|
28
|
+
popperOptions: {
|
|
29
|
+
modifiers: [
|
|
30
|
+
{
|
|
31
|
+
name: 'preventOverflow',
|
|
32
|
+
options: {
|
|
33
|
+
altAxis: true,
|
|
34
|
+
padding: 8,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: 'flip',
|
|
39
|
+
options: {
|
|
40
|
+
padding: 8,
|
|
41
|
+
fallbackPlacements: ['top', 'right', 'left'],
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
};
|
|
22
47
|
const getTooltipContent = (reference) => {
|
|
23
48
|
const contentNode = document.createElement('div');
|
|
24
49
|
// Accept template content via id
|
|
@@ -45,16 +70,34 @@ const getTooltipContent = (reference) => {
|
|
|
45
70
|
return contentNode;
|
|
46
71
|
}
|
|
47
72
|
}
|
|
48
|
-
|
|
73
|
+
// An overflow-only element without its own text shows the text it cuts off
|
|
74
|
+
const rawText = reference.getAttribute('data-tooltip') ||
|
|
75
|
+
(reference.hasAttribute('data-tooltip-overflow')
|
|
76
|
+
? reference.textContent
|
|
77
|
+
: '') ||
|
|
78
|
+
'';
|
|
49
79
|
if (!rawText.trim())
|
|
50
80
|
return '';
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
81
|
+
// `data-tooltip-multiline` keeps the text's line breaks (\n), e.g. one
|
|
82
|
+
// `name: value` per line; otherwise all whitespace collapses to single spaces
|
|
83
|
+
const multiline = reference.hasAttribute('data-tooltip-multiline');
|
|
84
|
+
const text = multiline
|
|
85
|
+
? rawText
|
|
86
|
+
.split(/\r?\n/)
|
|
87
|
+
.map((line) => line.replace(/\s+/g, ' ').trim())
|
|
88
|
+
.filter(Boolean)
|
|
89
|
+
.join('\n')
|
|
90
|
+
: rawText
|
|
91
|
+
.replace(/\\n/g, ' ')
|
|
92
|
+
.replace(/\n/g, ' ')
|
|
93
|
+
.replace(/\s+/g, ' ')
|
|
94
|
+
.trim();
|
|
56
95
|
if (!text)
|
|
57
96
|
return '';
|
|
97
|
+
if (multiline) {
|
|
98
|
+
contentNode.style.whiteSpace = 'pre-line';
|
|
99
|
+
contentNode.style.textAlign = 'left';
|
|
100
|
+
}
|
|
58
101
|
const isPureHTML = text.trim().startsWith('<') && text.trim().endsWith('>');
|
|
59
102
|
const hasHTML = /<(?:[a-z][\s\S]*>|\/[a-z0-9]+>)|&[#a-zA-Z0-9]+;/i.test(text);
|
|
60
103
|
const allowHTML = reference.hasAttribute('data-tooltip-allowhtml');
|
|
@@ -79,118 +122,185 @@ const getTooltipContent = (reference) => {
|
|
|
79
122
|
}
|
|
80
123
|
return contentNode;
|
|
81
124
|
};
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
125
|
+
// One-line ellipsis cut-off, read only when the element is hovered
|
|
126
|
+
const isClipped = (element) => element.scrollWidth > element.clientWidth + 0.5;
|
|
127
|
+
// The innermost element with something to show. A `data-tooltip-overflow`
|
|
128
|
+
// element counts only while its text is cut off; otherwise the next one out is tried.
|
|
129
|
+
const findTooltipTarget = (start) => {
|
|
130
|
+
let element = start.closest(TOOLTIP_TARGET);
|
|
131
|
+
while (element) {
|
|
132
|
+
const overflowOnly = element.hasAttribute('data-tooltip-overflow');
|
|
133
|
+
if ((!overflowOnly || isClipped(element)) &&
|
|
134
|
+
getTooltipContent(element) !== '') {
|
|
135
|
+
return element;
|
|
136
|
+
}
|
|
137
|
+
element = element.parentElement?.closest(TOOLTIP_TARGET) ?? null;
|
|
138
|
+
}
|
|
139
|
+
return null;
|
|
140
|
+
};
|
|
141
|
+
// Tippy's own ARIA, except where it would be wrong: a hoverable tooltip is not
|
|
142
|
+
// interactive, so it still describes its element and sets no aria-expanded;
|
|
143
|
+
// and text shown only because an ellipsis cuts it off repeats what the element
|
|
144
|
+
// already says, so it is not added as a description
|
|
145
|
+
const getAria = (reference) => {
|
|
146
|
+
if (reference.hasAttribute('data-tooltip-interactive'))
|
|
147
|
+
return {};
|
|
148
|
+
const repeatsOwnText = reference.hasAttribute('data-tooltip-overflow') &&
|
|
149
|
+
!reference.hasAttribute('data-tooltip') &&
|
|
150
|
+
!reference.hasAttribute('data-tooltip-id');
|
|
151
|
+
if (!repeatsOwnText && !reference.hasAttribute('data-tooltip-hoverable')) {
|
|
152
|
+
return {};
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
aria: { expanded: false, content: repeatsOwnText ? null : 'describedby' },
|
|
156
|
+
};
|
|
157
|
+
};
|
|
158
|
+
const getPlacement = (reference) => {
|
|
159
|
+
// placement attribute for tooltip
|
|
160
|
+
const normalizedPlacement = reference
|
|
161
|
+
.getAttribute('data-tooltip-placement')
|
|
162
|
+
?.trim()
|
|
163
|
+
.toLowerCase();
|
|
164
|
+
return normalizedPlacement && VALID_PLACEMENTS.has(normalizedPlacement)
|
|
165
|
+
? normalizedPlacement
|
|
166
|
+
: 'bottom';
|
|
167
|
+
};
|
|
168
|
+
/**
|
|
169
|
+
* Shows at most one tooltip at a time. The instance is created when its element
|
|
170
|
+
* is hovered or focused and destroyed when it hides, so nothing is kept for
|
|
171
|
+
* elements that were hovered and later removed (e.g. virtualized rows).
|
|
172
|
+
*/
|
|
173
|
+
const startTooltips = () => {
|
|
174
|
+
let active = null;
|
|
175
|
+
// Closed with Escape: stays hidden until the pointer or focus leaves it
|
|
176
|
+
let dismissed = null;
|
|
177
|
+
// While a tooltip shows: refresh it when its attributes change, and drop it
|
|
178
|
+
// when its element leaves the page
|
|
179
|
+
const activeObserver = new MutationObserver((records) => {
|
|
180
|
+
if (!active)
|
|
181
|
+
return;
|
|
182
|
+
if (!active.reference.isConnected) {
|
|
183
|
+
destroyActive();
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (records.some((record) => record.type === 'attributes')) {
|
|
187
|
+
const content = getTooltipContent(active.reference);
|
|
188
|
+
if (content === '') {
|
|
189
|
+
destroyActive();
|
|
88
190
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
},
|
|
117
|
-
content(reference) {
|
|
118
|
-
return getTooltipContent(reference);
|
|
119
|
-
},
|
|
120
|
-
onShow(tippyInstance) {
|
|
121
|
-
const { reference } = tippyInstance;
|
|
122
|
-
if (!document.body.contains(reference)) {
|
|
123
|
-
return false;
|
|
124
|
-
}
|
|
125
|
-
const latestContent = getTooltipContent(reference);
|
|
126
|
-
// If no tooltip content is currently available, prevent showing.
|
|
127
|
-
if (latestContent === '') {
|
|
191
|
+
else {
|
|
192
|
+
active.setContent(content);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
const destroyActive = () => {
|
|
197
|
+
activeObserver.disconnect();
|
|
198
|
+
active?.destroy();
|
|
199
|
+
active = null;
|
|
200
|
+
};
|
|
201
|
+
const show = (reference) => {
|
|
202
|
+
destroyActive();
|
|
203
|
+
// Another copy of this service (e.g. from a second bundle) already handles it
|
|
204
|
+
if (reference._tippy)
|
|
205
|
+
return;
|
|
206
|
+
active = tippy(reference, {
|
|
207
|
+
...TOOLTIP_PROPS,
|
|
208
|
+
content: getTooltipContent(reference),
|
|
209
|
+
placement: getPlacement(reference),
|
|
210
|
+
// This is specific tooltip attributefor interactive. A hoverable
|
|
211
|
+
// tooltip uses it only so the pointer can move onto it (WCAG 1.4.13).
|
|
212
|
+
interactive: reference.hasAttribute('data-tooltip-interactive') ||
|
|
213
|
+
reference.hasAttribute('data-tooltip-hoverable'),
|
|
214
|
+
...getAria(reference),
|
|
215
|
+
showOnCreate: true,
|
|
216
|
+
onShow(instance) {
|
|
217
|
+
if (!instance.reference.isConnected)
|
|
128
218
|
return false;
|
|
129
|
-
}
|
|
130
|
-
// This is specific tooltip attributefor interactive
|
|
131
|
-
const isInteractive = tippyInstance.reference.hasAttribute('data-tooltip-interactive');
|
|
132
|
-
// placement attribute for tooltip
|
|
133
|
-
const rawPlacement = tippyInstance.reference.getAttribute('data-tooltip-placement');
|
|
134
|
-
const normalizedPlacement = rawPlacement?.trim().toLowerCase();
|
|
135
|
-
const placement = normalizedPlacement && VALID_PLACEMENTS.has(normalizedPlacement)
|
|
136
|
-
? normalizedPlacement
|
|
137
|
-
: 'bottom';
|
|
138
|
-
tippyInstance.setProps({
|
|
139
|
-
interactive: isInteractive,
|
|
140
|
-
placement,
|
|
141
|
-
content: latestContent,
|
|
142
|
-
});
|
|
143
|
-
},
|
|
144
|
-
onCreate(tippyInstance) {
|
|
145
|
-
const attrObserver = new MutationObserver(() => {
|
|
146
|
-
if (tippyInstance.state.isVisible) {
|
|
147
|
-
const newContent = getTooltipContent(tippyInstance.reference);
|
|
148
|
-
if (newContent === '') {
|
|
149
|
-
tippyInstance.hide();
|
|
150
|
-
}
|
|
151
|
-
else {
|
|
152
|
-
tippyInstance.setContent(newContent);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
});
|
|
156
|
-
attrObserver.observe(tippyInstance.reference, {
|
|
157
|
-
attributes: true,
|
|
158
|
-
attributeFilter: ['data-tooltip', 'data-tooltip-id'],
|
|
159
|
-
});
|
|
160
|
-
tippyInstance._attrObserver = attrObserver;
|
|
161
|
-
const parentEl = tippyInstance.reference.parentElement;
|
|
162
|
-
if (parentEl) {
|
|
163
|
-
const domObserver = new MutationObserver(() => {
|
|
164
|
-
if (!document.body.contains(tippyInstance.reference)) {
|
|
165
|
-
tippyInstance.hide();
|
|
166
|
-
tippyInstance.destroy();
|
|
167
|
-
}
|
|
168
|
-
});
|
|
169
|
-
domObserver.observe(parentEl, { childList: true });
|
|
170
|
-
tippyInstance._domObserver = domObserver;
|
|
171
|
-
}
|
|
172
|
-
},
|
|
173
|
-
onDestroy(tippyInstance) {
|
|
174
|
-
tippyInstance._attrObserver?.disconnect();
|
|
175
|
-
tippyInstance._domObserver?.disconnect();
|
|
176
219
|
},
|
|
177
|
-
onMount(
|
|
178
|
-
const isExplicitlyInteractive =
|
|
220
|
+
onMount(instance) {
|
|
221
|
+
const isExplicitlyInteractive = instance.reference.hasAttribute('data-tooltip-interactive');
|
|
179
222
|
if (!isExplicitlyInteractive) {
|
|
180
|
-
const tippyBox =
|
|
223
|
+
const tippyBox = instance.popper.querySelector('.tippy-box');
|
|
181
224
|
if (tippyBox && tippyBox.scrollHeight > tippyBox.clientHeight) {
|
|
182
|
-
|
|
225
|
+
instance.setProps({ interactive: true });
|
|
183
226
|
}
|
|
184
227
|
}
|
|
185
228
|
},
|
|
229
|
+
onHidden(instance) {
|
|
230
|
+
if (instance === active) {
|
|
231
|
+
destroyActive();
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
instance.destroy();
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
activeObserver.observe(reference, {
|
|
239
|
+
attributes: true,
|
|
240
|
+
attributeFilter: ['data-tooltip', 'data-tooltip-id'],
|
|
186
241
|
});
|
|
242
|
+
activeObserver.observe(document.body, { childList: true, subtree: true });
|
|
243
|
+
};
|
|
244
|
+
const handlePointerOrFocus = (event) => {
|
|
245
|
+
const { target } = event;
|
|
246
|
+
if (!(target instanceof Element))
|
|
247
|
+
return;
|
|
248
|
+
// Moving into an interactive tooltip keeps it open
|
|
249
|
+
if (active?.popper.contains(target))
|
|
250
|
+
return;
|
|
251
|
+
if (dismissed && !dismissed.contains(target))
|
|
252
|
+
dismissed = null;
|
|
253
|
+
// Nothing new under the pointer: the shown tooltip hides itself on mouseleave
|
|
254
|
+
const closest = target.closest(TOOLTIP_TARGET);
|
|
255
|
+
if (!closest || closest === active?.reference)
|
|
256
|
+
return;
|
|
257
|
+
const reference = findTooltipTarget(closest);
|
|
258
|
+
if (reference &&
|
|
259
|
+
reference !== active?.reference &&
|
|
260
|
+
reference !== dismissed) {
|
|
261
|
+
show(reference);
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
// Escape hides the tooltip without moving the pointer or focus (WCAG 1.4.13)
|
|
265
|
+
const hideOnEscape = (event) => {
|
|
266
|
+
if (event.key !== 'Escape' || !active)
|
|
267
|
+
return;
|
|
268
|
+
dismissed = active.reference;
|
|
269
|
+
destroyActive();
|
|
270
|
+
};
|
|
271
|
+
const hideTooltipsOnScroll = (event) => {
|
|
272
|
+
const target = event.target;
|
|
273
|
+
if (target instanceof Element && target.closest('[data-tippy-root]')) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
hideAll({ duration: 0 });
|
|
277
|
+
destroyActive();
|
|
278
|
+
};
|
|
279
|
+
document.addEventListener('mouseover', handlePointerOrFocus);
|
|
280
|
+
document.addEventListener('focusin', handlePointerOrFocus);
|
|
281
|
+
document.addEventListener('keydown', hideOnEscape);
|
|
282
|
+
window.addEventListener('scroll', hideTooltipsOnScroll, true);
|
|
283
|
+
return () => {
|
|
284
|
+
document.removeEventListener('mouseover', handlePointerOrFocus);
|
|
285
|
+
document.removeEventListener('focusin', handlePointerOrFocus);
|
|
286
|
+
document.removeEventListener('keydown', hideOnEscape);
|
|
287
|
+
window.removeEventListener('scroll', hideTooltipsOnScroll, true);
|
|
288
|
+
destroyActive();
|
|
289
|
+
};
|
|
290
|
+
};
|
|
291
|
+
// Every mounted TooltipService shares one set of listeners
|
|
292
|
+
let mountedServices = 0;
|
|
293
|
+
let stopTooltips = null;
|
|
294
|
+
const TooltipService = () => {
|
|
295
|
+
useEffect(() => {
|
|
296
|
+
mountedServices += 1;
|
|
297
|
+
if (mountedServices === 1)
|
|
298
|
+
stopTooltips = startTooltips();
|
|
187
299
|
return () => {
|
|
188
|
-
|
|
189
|
-
if (
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
else {
|
|
193
|
-
instance.destroy();
|
|
300
|
+
mountedServices -= 1;
|
|
301
|
+
if (mountedServices === 0) {
|
|
302
|
+
stopTooltips?.();
|
|
303
|
+
stopTooltips = null;
|
|
194
304
|
}
|
|
195
305
|
};
|
|
196
306
|
}, []);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TooltipService.js","sourceRoot":"","sources":["../../../src/components/TooltipService/TooltipService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAElC,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"TooltipService.js","sourceRoot":"","sources":["../../../src/components/TooltipService/TooltipService.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { RefObject } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Remembers the element that had focus when `isOpen` became true, read during
|
|
4
|
+
* render — before children with autoFocus take focus — so it can be restored.
|
|
5
|
+
*/
|
|
6
|
+
export declare const useOpenerElement: (isOpen: boolean) => import("react").MutableRefObject<HTMLElement | null>;
|
|
7
|
+
interface FocusTrapOptions {
|
|
8
|
+
active: boolean;
|
|
9
|
+
/** Element to focus on activation; defaults to the first focusable child. */
|
|
10
|
+
initialFocusRef?: RefObject<HTMLElement>;
|
|
11
|
+
/** Return focus to the element that was focused before activation. */
|
|
12
|
+
restoreFocus?: boolean;
|
|
13
|
+
/** Explicit element to return focus to (see useOpenerElement). */
|
|
14
|
+
returnFocusRef?: RefObject<HTMLElement | null>;
|
|
15
|
+
/**
|
|
16
|
+
* Keep Tab/Shift+Tab inside the container (modal). When false, focus only
|
|
17
|
+
* moves in on activation and back out afterwards (non-modal panel).
|
|
18
|
+
*/
|
|
19
|
+
trapTab?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export declare const isTopMostFocusTrap: (container: HTMLElement | null) => boolean;
|
|
22
|
+
export declare const getTopMostFocusTrap: () => HTMLElement | null;
|
|
23
|
+
/**
|
|
24
|
+
* Moves keyboard focus into a dialog when it opens, optionally keeps it there
|
|
25
|
+
* while it is modal, and restores it to the trigger afterwards — WCAG 2.4.3
|
|
26
|
+
* Focus Order and 2.1.2 No Keyboard Trap (focus is released on close).
|
|
27
|
+
*/
|
|
28
|
+
declare const useFocusTrap: (containerRef: RefObject<HTMLElement>, { active, initialFocusRef, restoreFocus, trapTab, returnFocusRef, }: FocusTrapOptions) => void;
|
|
29
|
+
export default useFocusTrap;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
import { getFocusableElements } from '../utils/a11y/a11y';
|
|
3
|
+
/**
|
|
4
|
+
* Remembers the element that had focus when `isOpen` became true, read during
|
|
5
|
+
* render — before children with autoFocus take focus — so it can be restored.
|
|
6
|
+
*/
|
|
7
|
+
export const useOpenerElement = (isOpen) => {
|
|
8
|
+
const openerRef = useRef(null);
|
|
9
|
+
const wasOpenRef = useRef(false);
|
|
10
|
+
if (isOpen && !wasOpenRef.current && typeof document !== 'undefined') {
|
|
11
|
+
const focused = document.activeElement;
|
|
12
|
+
openerRef.current =
|
|
13
|
+
focused instanceof HTMLElement && focused !== document.body
|
|
14
|
+
? focused
|
|
15
|
+
: null;
|
|
16
|
+
}
|
|
17
|
+
wasOpenRef.current = isOpen;
|
|
18
|
+
return openerRef;
|
|
19
|
+
};
|
|
20
|
+
// Traps are stacked so only the top-most dialog handles Tab and Escape.
|
|
21
|
+
const trapStack = [];
|
|
22
|
+
export const isTopMostFocusTrap = (container) => !!container && trapStack[trapStack.length - 1] === container;
|
|
23
|
+
export const getTopMostFocusTrap = () => trapStack[trapStack.length - 1] ?? null;
|
|
24
|
+
/**
|
|
25
|
+
* Moves keyboard focus into a dialog when it opens, optionally keeps it there
|
|
26
|
+
* while it is modal, and restores it to the trigger afterwards — WCAG 2.4.3
|
|
27
|
+
* Focus Order and 2.1.2 No Keyboard Trap (focus is released on close).
|
|
28
|
+
*/
|
|
29
|
+
const useFocusTrap = (containerRef, { active, initialFocusRef, restoreFocus = true, trapTab = true, returnFocusRef, }) => {
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
const container = containerRef.current;
|
|
32
|
+
if (!active || !container)
|
|
33
|
+
return undefined;
|
|
34
|
+
const focusedNow = document.activeElement instanceof HTMLElement
|
|
35
|
+
? document.activeElement
|
|
36
|
+
: null;
|
|
37
|
+
// Prefer the opener captured at render; otherwise whatever had focus,
|
|
38
|
+
// unless that is already inside the container (e.g. an autoFocus field).
|
|
39
|
+
const previouslyFocused = returnFocusRef?.current ??
|
|
40
|
+
(focusedNow && !container.contains(focusedNow) ? focusedNow : null);
|
|
41
|
+
if (trapTab)
|
|
42
|
+
trapStack.push(container);
|
|
43
|
+
// Deferred so children using autoFocus win, and portal content is laid out.
|
|
44
|
+
const frame = requestAnimationFrame(() => {
|
|
45
|
+
if (container.contains(document.activeElement))
|
|
46
|
+
return;
|
|
47
|
+
const target = initialFocusRef?.current ??
|
|
48
|
+
getFocusableElements(container)[0] ??
|
|
49
|
+
container;
|
|
50
|
+
target.focus({ preventScroll: true });
|
|
51
|
+
});
|
|
52
|
+
const handleKeyDown = (event) => {
|
|
53
|
+
if (event.key !== 'Tab' ||
|
|
54
|
+
// Composite widgets (e.g. a spreadsheet) may use Tab internally.
|
|
55
|
+
event.defaultPrevented ||
|
|
56
|
+
!isTopMostFocusTrap(container)) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const focusable = getFocusableElements(container);
|
|
60
|
+
const first = focusable[0];
|
|
61
|
+
const last = focusable[focusable.length - 1];
|
|
62
|
+
if (!first || !last) {
|
|
63
|
+
event.preventDefault();
|
|
64
|
+
container.focus({ preventScroll: true });
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const activeElement = document.activeElement;
|
|
68
|
+
const focusIsOutside = !container.contains(activeElement);
|
|
69
|
+
if (event.shiftKey && (activeElement === first || focusIsOutside)) {
|
|
70
|
+
event.preventDefault();
|
|
71
|
+
last.focus();
|
|
72
|
+
}
|
|
73
|
+
else if (!event.shiftKey && (activeElement === last || focusIsOutside)) {
|
|
74
|
+
event.preventDefault();
|
|
75
|
+
first.focus();
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
if (trapTab)
|
|
79
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
80
|
+
return () => {
|
|
81
|
+
cancelAnimationFrame(frame);
|
|
82
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
83
|
+
const index = trapStack.lastIndexOf(container);
|
|
84
|
+
if (index !== -1)
|
|
85
|
+
trapStack.splice(index, 1);
|
|
86
|
+
// Only restore when focus would otherwise be lost (inside the closing
|
|
87
|
+
// container or already on <body>), never steal it from elsewhere.
|
|
88
|
+
const focused = document.activeElement;
|
|
89
|
+
const focusWouldBeLost = !focused || focused === document.body || container.contains(focused);
|
|
90
|
+
if (restoreFocus && focusWouldBeLost && previouslyFocused?.isConnected) {
|
|
91
|
+
previouslyFocused.focus({ preventScroll: true });
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}, [active, containerRef, initialFocusRef, restoreFocus, trapTab, returnFocusRef]);
|
|
95
|
+
};
|
|
96
|
+
export default useFocusTrap;
|
|
97
|
+
//# sourceMappingURL=useFocusTrap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useFocusTrap.js","sourceRoot":"","sources":["../../src/hooks/useFocusTrap.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { RefObject } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* True when the element is rendered inside a control that already handles
|
|
4
|
+
* keyboard activation, so the element should not add its own tab stop.
|
|
5
|
+
*/
|
|
6
|
+
declare const useInteractiveAncestor: (ref: RefObject<HTMLElement>, enabled: boolean) => boolean;
|
|
7
|
+
export default useInteractiveAncestor;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { useLayoutEffect, useState } from 'react';
|
|
2
|
+
import { hasInteractiveAncestor } from '../utils/a11y/a11y';
|
|
3
|
+
/**
|
|
4
|
+
* True when the element is rendered inside a control that already handles
|
|
5
|
+
* keyboard activation, so the element should not add its own tab stop.
|
|
6
|
+
*/
|
|
7
|
+
const useInteractiveAncestor = (ref, enabled) => {
|
|
8
|
+
const [isNested, setIsNested] = useState(false);
|
|
9
|
+
useLayoutEffect(() => {
|
|
10
|
+
if (!enabled)
|
|
11
|
+
return;
|
|
12
|
+
const nested = hasInteractiveAncestor(ref.current);
|
|
13
|
+
setIsNested((previous) => (previous === nested ? previous : nested));
|
|
14
|
+
}, [enabled, ref]);
|
|
15
|
+
return enabled && isNested;
|
|
16
|
+
};
|
|
17
|
+
export default useInteractiveAncestor;
|
|
18
|
+
//# sourceMappingURL=useInteractiveAncestor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useInteractiveAncestor.js","sourceRoot":"","sources":["../../src/hooks/useInteractiveAncestor.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAa,eAAe,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAC7D,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAE5D;;;GAGG;AACH,MAAM,sBAAsB,GAAG,CAC7B,GAA2B,EAC3B,OAAgB,EAChB,EAAE;IACF,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAEhD,eAAe,CAAC,GAAG,EAAE;QACnB,IAAI,CAAC,OAAO;YAAE,OAAO;QACrB,MAAM,MAAM,GAAG,sBAAsB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACnD,WAAW,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IACvE,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;IAEnB,OAAO,OAAO,IAAI,QAAQ,CAAC;AAC7B,CAAC,CAAC;AAEF,eAAe,sBAAsB,CAAC"}
|