@stonecrop/utilities 0.2.24 → 0.2.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/composables/keyboard.js +464 -0
- package/dist/composables/visibility/index.js +47 -0
- package/dist/index.js +8 -0
- package/dist/src/composables/keyboard.d.ts +4 -0
- package/dist/src/composables/keyboard.d.ts.map +1 -0
- package/dist/src/composables/visibility/index.d.ts +23 -0
- package/dist/src/composables/visibility/index.d.ts.map +1 -0
- package/dist/src/index.d.ts +11 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/types/index.d.ts +10 -0
- package/dist/src/types/index.d.ts.map +1 -0
- package/dist/types/index.js +0 -0
- package/dist/utilities/src/tsdoc-metadata.json +11 -0
- package/dist/utilities.d.ts +22 -0
- package/dist/utilities.js +222 -183
- package/dist/utilities.js.map +1 -1
- package/dist/utilities.tsbuildinfo +1 -0
- package/dist/utilities.umd.cjs +1 -1
- package/dist/utilities.umd.cjs.map +1 -1
- package/package.json +22 -23
- package/src/composables/keyboard.ts +1 -1
- package/src/index.ts +8 -3
- package/src/types/index.ts +18 -0
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
import { onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
|
2
|
+
import { useFocusWithin } from '@vueuse/core';
|
|
3
|
+
import { useElementVisibility } from '@/composables/visibility';
|
|
4
|
+
// helper functions
|
|
5
|
+
const isVisible = (element) => {
|
|
6
|
+
let isVisible = useElementVisibility(element).value;
|
|
7
|
+
isVisible = isVisible && element.offsetHeight > 0;
|
|
8
|
+
return isVisible;
|
|
9
|
+
};
|
|
10
|
+
const isFocusable = (element) => {
|
|
11
|
+
return element.tabIndex >= 0;
|
|
12
|
+
};
|
|
13
|
+
// navigation functions
|
|
14
|
+
const getUpCell = (event) => {
|
|
15
|
+
const $target = event.target;
|
|
16
|
+
return _getUpCell($target);
|
|
17
|
+
};
|
|
18
|
+
const _getUpCell = (element) => {
|
|
19
|
+
let $upCell;
|
|
20
|
+
if (element instanceof HTMLTableCellElement) {
|
|
21
|
+
const $prevRow = element.parentElement?.previousElementSibling;
|
|
22
|
+
if ($prevRow) {
|
|
23
|
+
const $prevRowCells = Array.from($prevRow.children);
|
|
24
|
+
const $prevCell = $prevRowCells[element.cellIndex];
|
|
25
|
+
if ($prevCell) {
|
|
26
|
+
$upCell = $prevCell;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
else if (element instanceof HTMLTableRowElement) {
|
|
31
|
+
const $prevRow = element.previousElementSibling;
|
|
32
|
+
if ($prevRow) {
|
|
33
|
+
$upCell = $prevRow;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
// handle other contexts
|
|
38
|
+
}
|
|
39
|
+
if ($upCell && (!isFocusable($upCell) || !isVisible($upCell))) {
|
|
40
|
+
return _getUpCell($upCell);
|
|
41
|
+
}
|
|
42
|
+
return $upCell;
|
|
43
|
+
};
|
|
44
|
+
const getTopCell = (event) => {
|
|
45
|
+
const $target = event.target;
|
|
46
|
+
let $topCell;
|
|
47
|
+
if ($target instanceof HTMLTableCellElement) {
|
|
48
|
+
const $table = $target.parentElement?.parentElement;
|
|
49
|
+
if ($table) {
|
|
50
|
+
const $firstRow = $table.firstElementChild;
|
|
51
|
+
const $navCell = $firstRow.children[$target.cellIndex];
|
|
52
|
+
if ($navCell) {
|
|
53
|
+
$topCell = $navCell;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
else if ($target instanceof HTMLTableRowElement) {
|
|
58
|
+
const $table = $target.parentElement;
|
|
59
|
+
if ($table) {
|
|
60
|
+
const $firstRow = $table.firstElementChild;
|
|
61
|
+
if ($firstRow) {
|
|
62
|
+
$topCell = $firstRow;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
// handle other contexts
|
|
68
|
+
}
|
|
69
|
+
if ($topCell && (!isFocusable($topCell) || !isVisible($topCell))) {
|
|
70
|
+
return _getDownCell($topCell);
|
|
71
|
+
}
|
|
72
|
+
return $topCell;
|
|
73
|
+
};
|
|
74
|
+
const getDownCell = (event) => {
|
|
75
|
+
const $target = event.target;
|
|
76
|
+
return _getDownCell($target);
|
|
77
|
+
};
|
|
78
|
+
const _getDownCell = (element) => {
|
|
79
|
+
let $downCell;
|
|
80
|
+
if (element instanceof HTMLTableCellElement) {
|
|
81
|
+
const $nextRow = element.parentElement?.nextElementSibling;
|
|
82
|
+
if ($nextRow) {
|
|
83
|
+
const $nextRowCells = Array.from($nextRow.children);
|
|
84
|
+
const $nextCell = $nextRowCells[element.cellIndex];
|
|
85
|
+
if ($nextCell) {
|
|
86
|
+
$downCell = $nextCell;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else if (element instanceof HTMLTableRowElement) {
|
|
91
|
+
const $nextRow = element.nextElementSibling;
|
|
92
|
+
if ($nextRow) {
|
|
93
|
+
$downCell = $nextRow;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
// handle other contexts
|
|
98
|
+
}
|
|
99
|
+
if ($downCell && (!isFocusable($downCell) || !isVisible($downCell))) {
|
|
100
|
+
return _getDownCell($downCell);
|
|
101
|
+
}
|
|
102
|
+
return $downCell;
|
|
103
|
+
};
|
|
104
|
+
const getBottomCell = (event) => {
|
|
105
|
+
const $target = event.target;
|
|
106
|
+
let $bottomCell;
|
|
107
|
+
if ($target instanceof HTMLTableCellElement) {
|
|
108
|
+
const $table = $target.parentElement?.parentElement;
|
|
109
|
+
if ($table) {
|
|
110
|
+
const $lastRow = $table.lastElementChild;
|
|
111
|
+
const $navCell = $lastRow.children[$target.cellIndex];
|
|
112
|
+
if ($navCell) {
|
|
113
|
+
$bottomCell = $navCell;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else if ($target instanceof HTMLTableRowElement) {
|
|
118
|
+
const $table = $target.parentElement;
|
|
119
|
+
if ($table) {
|
|
120
|
+
const $lastRow = $table.lastElementChild;
|
|
121
|
+
if ($lastRow) {
|
|
122
|
+
$bottomCell = $lastRow;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
// handle other contexts
|
|
128
|
+
}
|
|
129
|
+
if ($bottomCell && (!isFocusable($bottomCell) || !isVisible($bottomCell))) {
|
|
130
|
+
return _getUpCell($bottomCell);
|
|
131
|
+
}
|
|
132
|
+
return $bottomCell;
|
|
133
|
+
};
|
|
134
|
+
const getPrevCell = (event) => {
|
|
135
|
+
const $target = event.target;
|
|
136
|
+
return _getPrevCell($target);
|
|
137
|
+
};
|
|
138
|
+
const _getPrevCell = (element) => {
|
|
139
|
+
let $prevCell;
|
|
140
|
+
if (element.previousElementSibling) {
|
|
141
|
+
$prevCell = element.previousElementSibling;
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
const $prevRow = element.parentElement?.previousElementSibling;
|
|
145
|
+
$prevCell = $prevRow?.lastElementChild;
|
|
146
|
+
}
|
|
147
|
+
if ($prevCell && (!isFocusable($prevCell) || !isVisible($prevCell))) {
|
|
148
|
+
return _getPrevCell($prevCell);
|
|
149
|
+
}
|
|
150
|
+
return $prevCell;
|
|
151
|
+
};
|
|
152
|
+
const getNextCell = (event) => {
|
|
153
|
+
const $target = event.target;
|
|
154
|
+
return _getNextCell($target);
|
|
155
|
+
};
|
|
156
|
+
const _getNextCell = (element) => {
|
|
157
|
+
let $nextCell;
|
|
158
|
+
if (element.nextElementSibling) {
|
|
159
|
+
$nextCell = element.nextElementSibling;
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
const $nextRow = element.parentElement?.nextElementSibling;
|
|
163
|
+
$nextCell = $nextRow?.firstElementChild;
|
|
164
|
+
}
|
|
165
|
+
if ($nextCell && (!isFocusable($nextCell) || !isVisible($nextCell))) {
|
|
166
|
+
return _getNextCell($nextCell);
|
|
167
|
+
}
|
|
168
|
+
return $nextCell;
|
|
169
|
+
};
|
|
170
|
+
const getFirstCell = (event) => {
|
|
171
|
+
const $target = event.target;
|
|
172
|
+
const $parent = $target.parentElement;
|
|
173
|
+
const $firstCell = $parent.firstElementChild;
|
|
174
|
+
if ($firstCell && (!isFocusable($firstCell) || !isVisible($firstCell))) {
|
|
175
|
+
return _getNextCell($firstCell);
|
|
176
|
+
}
|
|
177
|
+
return $firstCell;
|
|
178
|
+
};
|
|
179
|
+
const getLastCell = (event) => {
|
|
180
|
+
const $target = event.target;
|
|
181
|
+
const $parent = $target.parentElement;
|
|
182
|
+
const $lastCell = $parent.lastElementChild;
|
|
183
|
+
if ($lastCell && (!isFocusable($lastCell) || !isVisible($lastCell))) {
|
|
184
|
+
return _getPrevCell($lastCell);
|
|
185
|
+
}
|
|
186
|
+
return $lastCell;
|
|
187
|
+
};
|
|
188
|
+
const modifierKeys = ['alt', 'control', 'shift', 'meta'];
|
|
189
|
+
const eventKeyMap = {
|
|
190
|
+
ArrowUp: 'up',
|
|
191
|
+
ArrowDown: 'down',
|
|
192
|
+
ArrowLeft: 'left',
|
|
193
|
+
ArrowRight: 'right',
|
|
194
|
+
};
|
|
195
|
+
export const defaultKeypressHandlers = {
|
|
196
|
+
'keydown.up': (event) => {
|
|
197
|
+
const $upCell = getUpCell(event);
|
|
198
|
+
if ($upCell) {
|
|
199
|
+
event.preventDefault();
|
|
200
|
+
event.stopPropagation();
|
|
201
|
+
$upCell.focus();
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
'keydown.down': (event) => {
|
|
205
|
+
const $downCell = getDownCell(event);
|
|
206
|
+
if ($downCell) {
|
|
207
|
+
event.preventDefault();
|
|
208
|
+
event.stopPropagation();
|
|
209
|
+
$downCell.focus();
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
'keydown.left': (event) => {
|
|
213
|
+
const $prevCell = getPrevCell(event);
|
|
214
|
+
// prevent default edit-cell behaviour on first cell
|
|
215
|
+
event.preventDefault();
|
|
216
|
+
event.stopPropagation();
|
|
217
|
+
if ($prevCell) {
|
|
218
|
+
$prevCell.focus();
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
'keydown.right': (event) => {
|
|
222
|
+
const $nextCell = getNextCell(event);
|
|
223
|
+
// prevent default edit-cell behaviour on last cell
|
|
224
|
+
event.preventDefault();
|
|
225
|
+
event.stopPropagation();
|
|
226
|
+
if ($nextCell) {
|
|
227
|
+
$nextCell.focus();
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
'keydown.control.up': (event) => {
|
|
231
|
+
const $topCell = getTopCell(event);
|
|
232
|
+
if ($topCell) {
|
|
233
|
+
event.preventDefault();
|
|
234
|
+
event.stopPropagation();
|
|
235
|
+
$topCell.focus();
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
'keydown.control.down': (event) => {
|
|
239
|
+
const $bottomCell = getBottomCell(event);
|
|
240
|
+
if ($bottomCell) {
|
|
241
|
+
event.preventDefault();
|
|
242
|
+
event.stopPropagation();
|
|
243
|
+
$bottomCell.focus();
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
'keydown.control.left': (event) => {
|
|
247
|
+
const $firstCell = getFirstCell(event);
|
|
248
|
+
if ($firstCell) {
|
|
249
|
+
event.preventDefault();
|
|
250
|
+
event.stopPropagation();
|
|
251
|
+
$firstCell.focus();
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
'keydown.control.right': (event) => {
|
|
255
|
+
const $lastCell = getLastCell(event);
|
|
256
|
+
if ($lastCell) {
|
|
257
|
+
event.preventDefault();
|
|
258
|
+
event.stopPropagation();
|
|
259
|
+
$lastCell.focus();
|
|
260
|
+
}
|
|
261
|
+
},
|
|
262
|
+
'keydown.end': (event) => {
|
|
263
|
+
const $lastCell = getLastCell(event);
|
|
264
|
+
if ($lastCell) {
|
|
265
|
+
event.preventDefault();
|
|
266
|
+
event.stopPropagation();
|
|
267
|
+
$lastCell.focus();
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
'keydown.enter': (event) => {
|
|
271
|
+
const $target = event.target;
|
|
272
|
+
if ($target instanceof HTMLTableCellElement) {
|
|
273
|
+
event.preventDefault();
|
|
274
|
+
event.stopPropagation();
|
|
275
|
+
const $downCell = getDownCell(event);
|
|
276
|
+
if ($downCell) {
|
|
277
|
+
$downCell.focus();
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
// handle other contexts
|
|
282
|
+
}
|
|
283
|
+
},
|
|
284
|
+
'keydown.shift.enter': (event) => {
|
|
285
|
+
const $target = event.target;
|
|
286
|
+
if ($target instanceof HTMLTableCellElement) {
|
|
287
|
+
event.preventDefault();
|
|
288
|
+
event.stopPropagation();
|
|
289
|
+
const $upCell = getUpCell(event);
|
|
290
|
+
if ($upCell) {
|
|
291
|
+
$upCell.focus();
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
// handle other contexts
|
|
296
|
+
}
|
|
297
|
+
},
|
|
298
|
+
'keydown.home': (event) => {
|
|
299
|
+
const $firstCell = getFirstCell(event);
|
|
300
|
+
if ($firstCell) {
|
|
301
|
+
event.preventDefault();
|
|
302
|
+
event.stopPropagation();
|
|
303
|
+
$firstCell.focus();
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
'keydown.tab': (event) => {
|
|
307
|
+
const $nextCell = getNextCell(event);
|
|
308
|
+
if ($nextCell) {
|
|
309
|
+
event.preventDefault();
|
|
310
|
+
event.stopPropagation();
|
|
311
|
+
$nextCell.focus();
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
'keydown.shift.tab': (event) => {
|
|
315
|
+
const $prevCell = getPrevCell(event);
|
|
316
|
+
if ($prevCell) {
|
|
317
|
+
event.preventDefault();
|
|
318
|
+
event.stopPropagation();
|
|
319
|
+
$prevCell.focus();
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
};
|
|
323
|
+
export function useKeyboardNav(options) {
|
|
324
|
+
const getParentElement = (option) => {
|
|
325
|
+
let $parent = null;
|
|
326
|
+
if (option.parent) {
|
|
327
|
+
if (typeof option.parent === 'string') {
|
|
328
|
+
$parent = document.querySelector(option.parent);
|
|
329
|
+
}
|
|
330
|
+
else if (option.parent instanceof HTMLElement) {
|
|
331
|
+
$parent = option.parent;
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
$parent = option.parent.value;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return $parent;
|
|
338
|
+
};
|
|
339
|
+
const getSelectorsFromOption = (option) => {
|
|
340
|
+
// assumes that option.selectors is provided
|
|
341
|
+
const $parent = getParentElement(option);
|
|
342
|
+
let selectors = [];
|
|
343
|
+
if (typeof option.selectors === 'string') {
|
|
344
|
+
selectors = $parent
|
|
345
|
+
? Array.from($parent.querySelectorAll(option.selectors))
|
|
346
|
+
: Array.from(document.querySelectorAll(option.selectors));
|
|
347
|
+
}
|
|
348
|
+
else if (Array.isArray(option.selectors)) {
|
|
349
|
+
for (const element of option.selectors) {
|
|
350
|
+
if (element instanceof HTMLElement) {
|
|
351
|
+
selectors.push(element);
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
selectors.push(element.$el);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
else if (option.selectors instanceof HTMLElement) {
|
|
359
|
+
selectors.push(option.selectors);
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
if (Array.isArray(option.selectors.value)) {
|
|
363
|
+
for (const element of option.selectors.value) {
|
|
364
|
+
if (element instanceof HTMLElement) {
|
|
365
|
+
selectors.push(element);
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
selectors.push(element.$el);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
selectors.push(option.selectors.value);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return selectors;
|
|
377
|
+
};
|
|
378
|
+
const getSelectors = (option) => {
|
|
379
|
+
const $parent = getParentElement(option);
|
|
380
|
+
let selectors = [];
|
|
381
|
+
if (option.selectors) {
|
|
382
|
+
selectors = getSelectorsFromOption(option);
|
|
383
|
+
}
|
|
384
|
+
else if ($parent) {
|
|
385
|
+
// TODO: what should happen if no parent or selectors are provided?
|
|
386
|
+
const $children = Array.from($parent.children);
|
|
387
|
+
selectors = $children.filter(selector => {
|
|
388
|
+
// ignore elements not in the tab order or are not visible
|
|
389
|
+
return isFocusable(selector) && isVisible(selector);
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
return selectors;
|
|
393
|
+
};
|
|
394
|
+
const getEventListener = (option) => {
|
|
395
|
+
return (event) => {
|
|
396
|
+
const activeKey = eventKeyMap[event.key] || event.key.toLowerCase();
|
|
397
|
+
if (modifierKeys.includes(activeKey))
|
|
398
|
+
return; // ignore modifier key presses
|
|
399
|
+
const handlers = option.handlers || defaultKeypressHandlers;
|
|
400
|
+
for (const key of Object.keys(handlers)) {
|
|
401
|
+
const [eventType, ...keys] = key.split('.');
|
|
402
|
+
if (eventType !== 'keydown') {
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
if (keys.includes(activeKey)) {
|
|
406
|
+
const listener = handlers[key];
|
|
407
|
+
// check if the handler has modifiers, and if the modifier is active;
|
|
408
|
+
// this is to ensure exact key-press matches
|
|
409
|
+
const hasModifier = keys.filter(key => modifierKeys.includes(key));
|
|
410
|
+
const isModifierActive = modifierKeys.some(key => {
|
|
411
|
+
const modifierKey = key.charAt(0).toUpperCase() + key.slice(1);
|
|
412
|
+
return event.getModifierState(modifierKey);
|
|
413
|
+
});
|
|
414
|
+
if (hasModifier.length > 0) {
|
|
415
|
+
if (isModifierActive) {
|
|
416
|
+
for (const modifier of modifierKeys) {
|
|
417
|
+
if (keys.includes(modifier)) {
|
|
418
|
+
// docs: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/getModifierState
|
|
419
|
+
const modifierKey = modifier.charAt(0).toUpperCase() + modifier.slice(1);
|
|
420
|
+
if (event.getModifierState(modifierKey)) {
|
|
421
|
+
listener(event);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
if (!isModifierActive) {
|
|
429
|
+
listener(event);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
};
|
|
436
|
+
const watchStopHandlers = [];
|
|
437
|
+
onMounted(() => {
|
|
438
|
+
for (const option of options) {
|
|
439
|
+
const $parent = getParentElement(option);
|
|
440
|
+
const selectors = getSelectors(option);
|
|
441
|
+
const listener = getEventListener(option);
|
|
442
|
+
const listenerElements = $parent
|
|
443
|
+
? [$parent] // watch for focus recursively within the parent element
|
|
444
|
+
: selectors; // watch for focus on each selector element TODO: too much JS?
|
|
445
|
+
for (const element of listenerElements) {
|
|
446
|
+
const { focused } = useFocusWithin(ref(element));
|
|
447
|
+
const stopHandler = watch(focused, value => {
|
|
448
|
+
if (value) {
|
|
449
|
+
element.addEventListener('keydown', listener);
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
element.removeEventListener('keydown', listener);
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
watchStopHandlers.push(stopHandler);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
onBeforeUnmount(() => {
|
|
460
|
+
for (const stopHandler of watchStopHandlers) {
|
|
461
|
+
stopHandler();
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { defaultWindow, unrefElement, useEventListener, } from '@vueuse/core';
|
|
2
|
+
import { ref, watch } from 'vue';
|
|
3
|
+
/**
|
|
4
|
+
* Tracks the visibility of an element within the viewport.
|
|
5
|
+
*
|
|
6
|
+
* Compatibility version to get Stonecrop keyboard navigation to work. This function is a copy of the
|
|
7
|
+
* `useElementVisibility` function from VueUse v9.13.0, with the `IntersectionObserver` API removed.
|
|
8
|
+
*
|
|
9
|
+
* This version uses the `getBoundingClientRect` method to determine if an element is visible
|
|
10
|
+
* in the viewport. This is less performant than the `IntersectionObserver` API, but it is
|
|
11
|
+
* more compatible.
|
|
12
|
+
*
|
|
13
|
+
* Note: the newer versions of the VueUse dependencies imported here are sufficient for this composable.
|
|
14
|
+
* (Last verified: v10.9.0 on May 2, 2024)
|
|
15
|
+
*
|
|
16
|
+
* @see https://v9-13-0.vueuse.org/core/useElementVisibility
|
|
17
|
+
* @param element
|
|
18
|
+
* @param options
|
|
19
|
+
*/
|
|
20
|
+
export function useElementVisibility(element, { window = defaultWindow, scrollTarget } = {}) {
|
|
21
|
+
const elementIsVisible = ref(false);
|
|
22
|
+
const testBounding = () => {
|
|
23
|
+
if (!window)
|
|
24
|
+
return;
|
|
25
|
+
const document = window.document;
|
|
26
|
+
const el = unrefElement(element);
|
|
27
|
+
if (!el) {
|
|
28
|
+
elementIsVisible.value = false;
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
const rect = el.getBoundingClientRect();
|
|
32
|
+
elementIsVisible.value =
|
|
33
|
+
rect.top <= (window.innerHeight || document.documentElement.clientHeight) &&
|
|
34
|
+
rect.left <= (window.innerWidth || document.documentElement.clientWidth) &&
|
|
35
|
+
rect.bottom >= 0 &&
|
|
36
|
+
rect.right >= 0;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
watch(() => unrefElement(element), () => testBounding(), { immediate: true, flush: 'post' });
|
|
40
|
+
if (window) {
|
|
41
|
+
useEventListener(scrollTarget || window, 'scroll', testBounding, {
|
|
42
|
+
capture: false,
|
|
43
|
+
passive: true,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return elementIsVisible;
|
|
47
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { defaultKeypressHandlers, useKeyboardNav } from '@/composables/keyboard';
|
|
2
|
+
/**
|
|
3
|
+
* Install all utility components
|
|
4
|
+
* @param app - Vue app instance
|
|
5
|
+
* @public
|
|
6
|
+
*/
|
|
7
|
+
function install(app /* options */) { }
|
|
8
|
+
export { defaultKeypressHandlers, install, useKeyboardNav };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keyboard.d.ts","sourceRoot":"","sources":["../../../src/composables/keyboard.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,yBAAyB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAuM1E,eAAO,MAAM,uBAAuB,EAAE,gBA6HrC,CAAA;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,yBAAyB,EAAE,QA2IlE"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type ConfigurableWindow, type MaybeComputedElementRef, type MaybeRefOrGetter } from '@vueuse/core';
|
|
2
|
+
export interface UseElementVisibilityOptions extends ConfigurableWindow {
|
|
3
|
+
scrollTarget?: MaybeRefOrGetter<HTMLElement | undefined | null>;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Tracks the visibility of an element within the viewport.
|
|
7
|
+
*
|
|
8
|
+
* Compatibility version to get Stonecrop keyboard navigation to work. This function is a copy of the
|
|
9
|
+
* `useElementVisibility` function from VueUse v9.13.0, with the `IntersectionObserver` API removed.
|
|
10
|
+
*
|
|
11
|
+
* This version uses the `getBoundingClientRect` method to determine if an element is visible
|
|
12
|
+
* in the viewport. This is less performant than the `IntersectionObserver` API, but it is
|
|
13
|
+
* more compatible.
|
|
14
|
+
*
|
|
15
|
+
* Note: the newer versions of the VueUse dependencies imported here are sufficient for this composable.
|
|
16
|
+
* (Last verified: v10.9.0 on May 2, 2024)
|
|
17
|
+
*
|
|
18
|
+
* @see https://v9-13-0.vueuse.org/core/useElementVisibility
|
|
19
|
+
* @param element
|
|
20
|
+
* @param options
|
|
21
|
+
*/
|
|
22
|
+
export declare function useElementVisibility(element: MaybeComputedElementRef, { window, scrollTarget }?: UseElementVisibilityOptions): import("vue").Ref<boolean>;
|
|
23
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/composables/visibility/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,EAC5B,KAAK,gBAAgB,EAIrB,MAAM,cAAc,CAAA;AAGrB,MAAM,WAAW,2BAA4B,SAAQ,kBAAkB;IACtE,YAAY,CAAC,EAAE,gBAAgB,CAAC,WAAW,GAAG,SAAS,GAAG,IAAI,CAAC,CAAA;CAC/D;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,oBAAoB,CACnC,OAAO,EAAE,uBAAuB,EAChC,EAAE,MAAsB,EAAE,YAAY,EAAE,GAAE,2BAAgC,8BAmC1E"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { App } from 'vue';
|
|
2
|
+
import { defaultKeypressHandlers, useKeyboardNav } from '@/composables/keyboard';
|
|
3
|
+
export type { KeypressHandlers, KeyboardNavigationOptions } from '@/types';
|
|
4
|
+
/**
|
|
5
|
+
* Install all utility components
|
|
6
|
+
* @param app - Vue app instance
|
|
7
|
+
* @public
|
|
8
|
+
*/
|
|
9
|
+
declare function install(app: App): void;
|
|
10
|
+
export { defaultKeypressHandlers, install, useKeyboardNav };
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AAEzB,OAAO,EAAE,uBAAuB,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAChF,YAAY,EAAE,gBAAgB,EAAE,yBAAyB,EAAE,MAAM,SAAS,CAAA;AAE1E;;;;GAIG;AACH,iBAAS,OAAO,CAAC,GAAG,EAAE,GAAG,QAAkB;AAE3C,OAAO,EAAE,uBAAuB,EAAE,OAAO,EAAE,cAAc,EAAE,CAAA"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ComponentPublicInstance, Ref } from 'vue';
|
|
2
|
+
export type KeypressHandlers = {
|
|
3
|
+
[key: string]: (ev: KeyboardEvent) => any;
|
|
4
|
+
};
|
|
5
|
+
export type KeyboardNavigationOptions = {
|
|
6
|
+
parent?: string | HTMLElement | Ref<HTMLElement>;
|
|
7
|
+
selectors?: string | HTMLElement | HTMLElement[] | ComponentPublicInstance[] | Ref<HTMLElement> | Ref<HTMLElement[]> | Ref<ComponentPublicInstance[]>;
|
|
8
|
+
handlers?: KeypressHandlers;
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/types/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AAElD,MAAM,MAAM,gBAAgB,GAAG;IAC9B,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,EAAE,EAAE,aAAa,KAAK,GAAG,CAAA;CACzC,CAAA;AAED,MAAM,MAAM,yBAAyB,GAAG;IACvC,MAAM,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,GAAG,CAAC,WAAW,CAAC,CAAA;IAChD,SAAS,CAAC,EACP,MAAM,GACN,WAAW,GACX,WAAW,EAAE,GACb,uBAAuB,EAAE,GACzB,GAAG,CAAC,WAAW,CAAC,GAChB,GAAG,CAAC,WAAW,EAAE,CAAC,GAClB,GAAG,CAAC,uBAAuB,EAAE,CAAC,CAAA;IACjC,QAAQ,CAAC,EAAE,gBAAgB,CAAA;CAC3B,CAAA"}
|
|
File without changes
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// This file is read by tools that parse documentation comments conforming to the TSDoc standard.
|
|
2
|
+
// It should be published with your NPM package. It should not be tracked by Git.
|
|
3
|
+
{
|
|
4
|
+
"tsdocVersion": "0.12",
|
|
5
|
+
"toolPackages": [
|
|
6
|
+
{
|
|
7
|
+
"packageName": "@microsoft/api-extractor",
|
|
8
|
+
"packageVersion": "7.47.0"
|
|
9
|
+
}
|
|
10
|
+
]
|
|
11
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { App } from 'vue';
|
|
2
|
+
import { defaultKeypressHandlers } from '@/composables/keyboard';
|
|
3
|
+
import { KeyboardNavigationOptions } from '@/types';
|
|
4
|
+
import { KeypressHandlers } from '@/types';
|
|
5
|
+
import { useKeyboardNav } from '@/composables/keyboard';
|
|
6
|
+
|
|
7
|
+
export { defaultKeypressHandlers }
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Install all utility components
|
|
11
|
+
* @param app - Vue app instance
|
|
12
|
+
* @public
|
|
13
|
+
*/
|
|
14
|
+
export declare function install(app: App): void;
|
|
15
|
+
|
|
16
|
+
export { KeyboardNavigationOptions }
|
|
17
|
+
|
|
18
|
+
export { KeypressHandlers }
|
|
19
|
+
|
|
20
|
+
export { useKeyboardNav }
|
|
21
|
+
|
|
22
|
+
export { }
|