@fusion.dev/helix-headless 0.147.0 → 0.148.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/custom-elements.json +4512 -790
- package/dist/accordion/item/accordion-item.d.ts +5 -2
- package/dist/accordion/item/accordion-item.js +11 -7
- package/dist/accordion/root/accordion-root.js +4 -6
- package/dist/announcement/root/announcement-root.d.ts +56 -5
- package/dist/announcement/root/announcement-root.js +124 -21
- package/dist/area-chart/root/area-chart-root.js +3 -4
- package/dist/badge/count.d.ts +14 -0
- package/dist/badge/count.js +20 -0
- package/dist/badge/root/badge-root.js +8 -22
- package/dist/blockquote/root/blockquote-root.d.ts +66 -8
- package/dist/blockquote/root/blockquote-root.js +85 -8
- package/dist/breadcrumbs/breadcrumb/breadcrumb.js +2 -2
- package/dist/button-group/arrows.d.ts +14 -0
- package/dist/button-group/arrows.js +10 -0
- package/dist/button-group/root/button-group-root.d.ts +141 -3
- package/dist/button-group/root/button-group-root.js +142 -2
- package/dist/canvas/edge-pan.d.ts +61 -0
- package/dist/canvas/edge-pan.js +63 -0
- package/dist/canvas/node/canvas-node.d.ts +12 -2
- package/dist/canvas/node/canvas-node.js +6 -2
- package/dist/canvas/root/canvas-document.d.ts +10 -0
- package/dist/canvas/root/canvas-document.js +1 -0
- package/dist/canvas/root/canvas-root.d.ts +107 -5
- package/dist/canvas/root/canvas-root.js +140 -36
- package/dist/canvas/snap.d.ts +42 -0
- package/dist/canvas/snap.js +32 -0
- package/dist/carousel/root/carousel-root.d.ts +4 -3
- package/dist/carousel/root/carousel-root.js +2 -6
- package/dist/chat/root/chat-root.d.ts +13 -3
- package/dist/chat/root/chat-root.js +2 -6
- package/dist/checkbox-group/root/checkbox-group-root.d.ts +13 -0
- package/dist/checkbox-group/root/checkbox-group-root.js +18 -10
- package/dist/checkbox-group/select-all.d.ts +11 -0
- package/dist/checkbox-group/select-all.js +5 -0
- package/dist/code-block/root/code-block-root.d.ts +8 -6
- package/dist/code-block/root/code-block-root.js +4 -3
- package/dist/color-picker/root/color-picker-root.js +6 -12
- package/dist/color-picker/wheel.d.ts +11 -0
- package/dist/color-picker/wheel.js +9 -0
- package/dist/combobox/root/combobox-root.d.ts +70 -11
- package/dist/combobox/root/combobox-root.js +68 -18
- package/dist/command/root/command-root.d.ts +4 -3
- package/dist/data-table/root/data-table-root.js +17 -44
- package/dist/description-list/item/description-list-item.d.ts +4 -0
- package/dist/description-list/item/description-list-item.js +7 -1
- package/dist/divider/root/divider-root.d.ts +45 -16
- package/dist/divider/root/divider-root.js +28 -4
- package/dist/drilldown/item/drilldown-item.js +3 -4
- package/dist/element-events.generated.js +1 -0
- package/dist/empty/root/empty-root.d.ts +17 -3
- package/dist/empty/root/empty-root.js +29 -5
- package/dist/file-input/root/file-input-root.js +3 -4
- package/dist/file-input/status.js +2 -1
- package/dist/filter-input/chip/filter-chip.js +2 -3
- package/dist/format-date/fields.d.ts +20 -0
- package/dist/format-date/fields.js +14 -0
- package/dist/format-date/options.d.ts +2 -1
- package/dist/format-date/options.js +9 -38
- package/dist/format-date/root/format-date-root.d.ts +35 -3
- package/dist/format-date/root/format-date-root.js +24 -32
- package/dist/graph/root/graph-root.d.ts +23 -5
- package/dist/graph/root/graph-root.js +17 -20
- package/dist/heatmap/root/heatmap-root.d.ts +12 -0
- package/dist/heatmap/root/heatmap-root.js +26 -11
- package/dist/image/root/image-root.d.ts +11 -0
- package/dist/kbd/root/kbd-root.js +1 -2
- package/dist/legend/root/legend-root.js +4 -17
- package/dist/list/root/list-root.js +8 -22
- package/dist/listbox/root/listbox-root.d.ts +119 -24
- package/dist/listbox/root/listbox-root.js +130 -132
- package/dist/map/marker/map-marker.d.ts +2 -1
- package/dist/map/root/map-root.d.ts +40 -2
- package/dist/map/root/map-root.js +64 -23
- package/dist/megamenu/root/megamenu-root.js +4 -11
- package/dist/menu/root/menu-root.d.ts +2 -2
- package/dist/navigation/root/navigation-root.js +5 -8
- package/dist/notifications/group/notification-group.d.ts +8 -1
- package/dist/notifications/group/notification-group.js +12 -6
- package/dist/notifications/notification/notification.d.ts +12 -1
- package/dist/notifications/notification/notification.js +3 -1
- package/dist/notifications/root/notifications-root.d.ts +41 -5
- package/dist/notifications/root/notifications-root.js +27 -7
- package/dist/object-inspector/root/object-inspector-root.d.ts +5 -1
- package/dist/object-inspector/root/object-inspector-root.js +21 -11
- package/dist/pagination/size/pagination-size.d.ts +1 -0
- package/dist/pagination/size/pagination-size.js +1 -1
- package/dist/pin-input/code.d.ts +23 -0
- package/dist/pin-input/code.js +25 -0
- package/dist/pin-input/root/pin-input-root.js +9 -23
- package/dist/qr-code/root/qr-code-root.d.ts +14 -3
- package/dist/qr-code/root/qr-code-root.js +19 -5
- package/dist/scatter-chart/measures.d.ts +22 -0
- package/dist/scatter-chart/measures.js +11 -0
- package/dist/scatter-chart/root/scatter-chart-root.d.ts +2 -6
- package/dist/scatter-chart/root/scatter-chart-root.js +6 -19
- package/dist/select/root/select-root.d.ts +56 -8
- package/dist/select/root/select-root.js +33 -9
- package/dist/speed-dial/group/speed-dial-group.d.ts +48 -0
- package/dist/speed-dial/group/speed-dial-group.js +43 -0
- package/dist/speed-dial/index.d.ts +1 -0
- package/dist/speed-dial/index.js +1 -0
- package/dist/speed-dial/item/speed-dial-item.d.ts +26 -1
- package/dist/speed-dial/item/speed-dial-item.js +41 -3
- package/dist/speed-dial/long-press.d.ts +50 -0
- package/dist/speed-dial/long-press.js +48 -0
- package/dist/speed-dial/root/speed-dial-root.d.ts +80 -1
- package/dist/speed-dial/root/speed-dial-root.js +114 -5
- package/dist/splitter/root/splitter-root.d.ts +7 -3
- package/dist/splitter/root/splitter-root.js +16 -3
- package/dist/table/cell/table-cell.d.ts +8 -1
- package/dist/table/cell/table-cell.js +6 -2
- package/dist/table/grid-controller.d.ts +117 -0
- package/dist/table/grid-controller.js +293 -0
- package/dist/table/root/table-root.d.ts +47 -3
- package/dist/table/root/table-root.js +81 -24
- package/dist/table/row/table-row.d.ts +13 -1
- package/dist/table/row/table-row.js +20 -6
- package/dist/tag/group/tag-group.js +6 -15
- package/dist/time-picker/root/time-picker-root.d.ts +1 -0
- package/dist/time-picker/root/time-picker-root.js +3 -0
- package/dist/toast/item/toast-item.d.ts +6 -3
- package/dist/toast/item/toast-item.js +1 -0
- package/dist/toast/root/toast-root.d.ts +13 -1
- package/dist/toast/root/toast-root.js +20 -14
- package/dist/toggle/group/toggle-group.js +3 -5
- package/dist/toolbar/root/toolbar-root.d.ts +31 -0
- package/dist/toolbar/root/toolbar-root.js +39 -8
- package/dist/tooltip/root/tooltip-root.js +1 -2
- package/dist/video/root/video-root.d.ts +11 -0
- package/dist/video/root/video-root.js +15 -1
- package/dist/virtual-list/root/virtual-list-root.js +6 -18
- package/lint.json +1 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +2005 -266
- package/types/svelte.d.ts +1 -0
- package/types/vue.d.ts +338 -62
- package/dist/kbd/keys.d.ts +0 -29
- package/dist/kbd/keys.js +0 -52
- package/dist/tooltip/focus-target.d.ts +0 -6
- package/dist/tooltip/focus-target.js +0 -9
|
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { customElement, fillTemplate, formatNumbers, HelixActivateEvent, HelixBeforeChangeEvent, HelixBeforeToggleEvent, HelixElement, HelixMapClusterEvent, HelixMapPanEvent, HelixMapPartChangeEvent, HelixMapReadyEvent, HelixMapRotateEvent, HelixMapTiltEvent, HelixMapZoomEvent, HelixToggleEvent, hostAria, KeyboardNavigationController, MixinViewport, requestChange, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { customElement, fillTemplate, focusItemOf, formatNumbers, HelixActivateEvent, HelixBeforeChangeEvent, HelixBeforeToggleEvent, HelixElement, HelixMapClusterEvent, HelixMapPanEvent, HelixMapPartChangeEvent, HelixMapReadyEvent, HelixMapRotateEvent, HelixMapTiltEvent, HelixMapZoomEvent, HelixToggleEvent, hostAria, KeyboardNavigationController, MixinViewport, requestChange, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { buildings, cloudRain, dotsNine, leaf, mountains, path, polygon, stack, target, } from '@fusion.dev/helix-icons';
|
|
9
9
|
import { html, nothing } from 'lit';
|
|
10
10
|
import { property, state } from 'lit/decorators.js';
|
|
@@ -89,15 +89,13 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
|
|
|
89
89
|
};
|
|
90
90
|
this.stop = 0;
|
|
91
91
|
this.keys = new KeyboardNavigationController(this, {
|
|
92
|
-
items: () => this.markers.map((marker, at) => ({
|
|
93
|
-
contains: (node) => node !== null && (marker.contains(node) || marker.shadowRoot?.contains(node) === true),
|
|
92
|
+
items: () => this.markers.map((marker, at) => focusItemOf(marker, {
|
|
94
93
|
disabled: marker.gatheredBy !== undefined || !placed(marker),
|
|
95
|
-
|
|
94
|
+
focus: () => {
|
|
96
95
|
this.stop = at;
|
|
97
96
|
marker.focus();
|
|
98
97
|
},
|
|
99
|
-
|
|
100
|
-
selected: false,
|
|
98
|
+
owns: (node) => node !== null && (marker.contains(node) || marker.shadowRoot?.contains(node) === true),
|
|
101
99
|
})),
|
|
102
100
|
origin: 'focus',
|
|
103
101
|
selects: () => false,
|
|
@@ -105,10 +103,26 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
|
|
|
105
103
|
this.handleFocusIn = (event) => {
|
|
106
104
|
const marker = this.markerOf(event);
|
|
107
105
|
const at = marker ? this.markers.indexOf(marker) : -1;
|
|
106
|
+
this.focused = at === -1 ? undefined : marker;
|
|
108
107
|
if (at !== -1) {
|
|
109
108
|
this.stop = at;
|
|
110
109
|
}
|
|
111
110
|
};
|
|
111
|
+
this.handleFocusOut = (event) => {
|
|
112
|
+
const marker = this.markerOf(event);
|
|
113
|
+
if (!marker || marker !== this.focused) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (event.relatedTarget) {
|
|
117
|
+
this.focused = undefined;
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
queueMicrotask(() => {
|
|
121
|
+
if (marker.isConnected && this.focused === marker) {
|
|
122
|
+
this.focused = undefined;
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
};
|
|
112
126
|
this.handleClick = (event) => {
|
|
113
127
|
const marker = this.markerOf(event);
|
|
114
128
|
if (marker) {
|
|
@@ -267,6 +281,23 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
|
|
|
267
281
|
.composedPath()
|
|
268
282
|
.find((one) => one.isMapMarker === true);
|
|
269
283
|
}
|
|
284
|
+
refocus(stop) {
|
|
285
|
+
const gone = this.focused;
|
|
286
|
+
if (!gone || this.markers.includes(gone)) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
this.focused = undefined;
|
|
290
|
+
const active = document.activeElement;
|
|
291
|
+
if (active !== null && active !== document.body) {
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
if (stop) {
|
|
295
|
+
stop.focus();
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
this.partNamed('region')?.focus();
|
|
299
|
+
}
|
|
300
|
+
}
|
|
270
301
|
activate(marker) {
|
|
271
302
|
if (marker.gathered.length > 1) {
|
|
272
303
|
marker.dispatchEvent(new HelixMapClusterEvent({ labels: [...marker.gathered] }));
|
|
@@ -290,6 +321,7 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
|
|
|
290
321
|
this.addEventListener(HelixMapPartChangeEvent.eventName, this.handlePartChange);
|
|
291
322
|
this.addEventListener('click', this.handleClick);
|
|
292
323
|
this.addEventListener('focusin', this.handleFocusIn);
|
|
324
|
+
this.addEventListener('focusout', this.handleFocusOut);
|
|
293
325
|
this.addEventListener('keydown', this.handleMarkerKeydown);
|
|
294
326
|
this.addEventListener('keyup', this.handleMarkerKeyup);
|
|
295
327
|
this.hiding = new MutationObserver(() => this.requestUpdate());
|
|
@@ -304,6 +336,7 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
|
|
|
304
336
|
this.removeEventListener(HelixMapPartChangeEvent.eventName, this.handlePartChange);
|
|
305
337
|
this.removeEventListener('click', this.handleClick);
|
|
306
338
|
this.removeEventListener('focusin', this.handleFocusIn);
|
|
339
|
+
this.removeEventListener('focusout', this.handleFocusOut);
|
|
307
340
|
this.removeEventListener('keydown', this.handleMarkerKeydown);
|
|
308
341
|
this.removeEventListener('keyup', this.handleMarkerKeyup);
|
|
309
342
|
}
|
|
@@ -331,6 +364,7 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
|
|
|
331
364
|
for (const marker of markers) {
|
|
332
365
|
marker.current = marker === stop;
|
|
333
366
|
}
|
|
367
|
+
this.refocus(stop);
|
|
334
368
|
if (changed.has('cluster')) {
|
|
335
369
|
this.dispatchEvent(new HelixMapPartChangeEvent({ kind: 'place' }));
|
|
336
370
|
}
|
|
@@ -371,9 +405,11 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
|
|
|
371
405
|
}}
|
|
372
406
|
@keydown=${this.handleMenuKeydown}
|
|
373
407
|
>
|
|
374
|
-
<
|
|
375
|
-
|
|
376
|
-
|
|
408
|
+
<slot name="layers-icon">
|
|
409
|
+
<svg aria-hidden="true" fill="currentColor" height="16" viewBox=${stack.viewBox} width="16">
|
|
410
|
+
${unsafeSVG(stack.content)}
|
|
411
|
+
</svg>
|
|
412
|
+
</slot>
|
|
377
413
|
</button>
|
|
378
414
|
<div
|
|
379
415
|
part="layers-menu"
|
|
@@ -431,38 +467,40 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
|
|
|
431
467
|
return html `<div part="compass">
|
|
432
468
|
${rotate
|
|
433
469
|
? html `<button part="rotate-left" aria-label=${this.rotateLeftLabel} type="button" @click=${() => this.rotate(-ROTATE_STEP)}>
|
|
434
|
-
|
|
470
|
+
<slot name="rotate-left-icon">↺</slot>
|
|
435
471
|
</button>`
|
|
436
472
|
: nothing}
|
|
437
473
|
${pan
|
|
438
474
|
? html `<button part="pan-up" aria-label=${this.panLabel('up')} type="button" @click=${() => this.pan('up')}>
|
|
439
|
-
|
|
475
|
+
<slot name="pan-up-icon">${this.panGlyph('up')}</slot>
|
|
440
476
|
</button>`
|
|
441
477
|
: nothing}
|
|
442
478
|
${rotate
|
|
443
479
|
? html `<button part="rotate-right" aria-label=${this.rotateRightLabel} type="button" @click=${() => this.rotate(ROTATE_STEP)}>
|
|
444
|
-
|
|
480
|
+
<slot name="rotate-right-icon">↻</slot>
|
|
445
481
|
</button>`
|
|
446
482
|
: nothing}
|
|
447
483
|
${pan
|
|
448
484
|
? html `<button part="pan-start" aria-label=${this.panLabel('start')} type="button" @click=${() => this.pan('start')}>
|
|
449
|
-
|
|
485
|
+
<slot name="pan-start-icon">${this.panGlyph('start')}</slot>
|
|
450
486
|
</button>`
|
|
451
487
|
: nothing}
|
|
452
488
|
<button part="face-north" aria-label=${this.faceNorthLabel} type="button" style=${`--map-needle-turn: ${-this.bearing}deg`} @click=${() => this.faceNorth()}>
|
|
453
|
-
<
|
|
454
|
-
<
|
|
455
|
-
|
|
456
|
-
|
|
489
|
+
<slot name="face-north-icon">
|
|
490
|
+
<svg part="needle" aria-hidden="true" viewBox="0 0 16 16">
|
|
491
|
+
<path data-end="north" d="M8 1.5 10.5 8h-5Z" />
|
|
492
|
+
<path data-end="south" d="M8 14.5 5.5 8h5Z" />
|
|
493
|
+
</svg>
|
|
494
|
+
</slot>
|
|
457
495
|
</button>
|
|
458
496
|
${pan
|
|
459
497
|
? html `<button part="pan-end" aria-label=${this.panLabel('end')} type="button" @click=${() => this.pan('end')}>
|
|
460
|
-
|
|
498
|
+
<slot name="pan-end-icon">${this.panGlyph('end')}</slot>
|
|
461
499
|
</button>`
|
|
462
500
|
: nothing}
|
|
463
501
|
${pan
|
|
464
502
|
? html `<button part="pan-down" aria-label=${this.panLabel('down')} type="button" @click=${() => this.pan('down')}>
|
|
465
|
-
|
|
503
|
+
<slot name="pan-down-icon">${this.panGlyph('down')}</slot>
|
|
466
504
|
</button>`
|
|
467
505
|
: nothing}
|
|
468
506
|
</div>`;
|
|
@@ -486,7 +524,7 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
|
|
|
486
524
|
type="button"
|
|
487
525
|
@click=${this.toggleFullscreen}
|
|
488
526
|
>
|
|
489
|
-
|
|
527
|
+
<slot name="fullscreen-icon">⤢</slot>
|
|
490
528
|
</button>`
|
|
491
529
|
: nothing}
|
|
492
530
|
${menu}
|
|
@@ -498,15 +536,15 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
|
|
|
498
536
|
type="button"
|
|
499
537
|
@click=${() => this.tiltTo(this.pitch > 0 ? 0 : TILTED)}
|
|
500
538
|
>
|
|
501
|
-
3D
|
|
539
|
+
<slot name="tilt-icon">3D</slot>
|
|
502
540
|
</button>`
|
|
503
541
|
: nothing}
|
|
504
542
|
${zoom
|
|
505
543
|
? html `<button part="zoom-in" aria-label=${this.zoomInLabel} type="button" @click=${() => this.zoom(1)}>
|
|
506
|
-
|
|
544
|
+
<slot name="zoom-in-icon">+</slot>
|
|
507
545
|
</button>
|
|
508
546
|
<button part="zoom-out" aria-label=${this.zoomOutLabel} type="button" @click=${() => this.zoom(-1)}>
|
|
509
|
-
|
|
547
|
+
<slot name="zoom-out-icon">−</slot>
|
|
510
548
|
</button>`
|
|
511
549
|
: nothing}
|
|
512
550
|
${this.drawsCompass ? this.renderCompass(pan, rotate) : nothing}
|
|
@@ -558,6 +596,9 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
|
|
|
558
596
|
hostAria(this.internals, { label: this.label });
|
|
559
597
|
this.toggleState('fullscreen', this.filled);
|
|
560
598
|
this.toggleState('idle', this.resting);
|
|
599
|
+
this.toggleState('layers-open', this.layersOpen);
|
|
600
|
+
this.toggleState('rotated', Math.round(this.bearing) % 360 !== 0);
|
|
601
|
+
this.toggleState('tilted', Math.round(this.pitch) !== 0);
|
|
561
602
|
}
|
|
562
603
|
renderLayers() {
|
|
563
604
|
const layers = this.layers.filter((layer) => this.shown(layer));
|
|
@@ -5,7 +5,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
7
|
var HelixHeadlessMegamenu_1;
|
|
8
|
-
import { anchorElement, customElement, deepContains, focusableElements, HelixElement, KeyboardNavigationController, MixinControllerOpen, PopoverController, tellDisclosure, } from '@fusion.dev/helix-core';
|
|
8
|
+
import { anchorElement, customElement, deepContains, focusableElements, focusItemOf, HelixElement, KeyboardNavigationController, MixinControllerOpen, PopoverController, tellDisclosure, } from '@fusion.dev/helix-core';
|
|
9
9
|
import { html, nothing } from 'lit';
|
|
10
10
|
import { property, state } from 'lit/decorators.js';
|
|
11
11
|
import { HandOver } from '../hand-over.js';
|
|
@@ -30,16 +30,9 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
|
|
|
30
30
|
});
|
|
31
31
|
this.keys = new KeyboardNavigationController(this, {
|
|
32
32
|
items: () => !this.parentElement?.shadowRoot
|
|
33
|
-
? HelixHeadlessMegamenu_1.bar
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return node === control || control.contains(node);
|
|
37
|
-
},
|
|
38
|
-
disabled: false,
|
|
39
|
-
focusItem: () => megamenu.trigger.focus(),
|
|
40
|
-
select: () => { },
|
|
41
|
-
selected: false,
|
|
42
|
-
}))
|
|
33
|
+
? HelixHeadlessMegamenu_1.bar
|
|
34
|
+
.alongside(this)
|
|
35
|
+
.map((megamenu) => focusItemOf(megamenu.trigger))
|
|
43
36
|
: [],
|
|
44
37
|
origin: 'focus',
|
|
45
38
|
reserved: () => ['ArrowDown', 'ArrowUp'],
|
|
@@ -25,8 +25,8 @@ declare const HelixHeadlessMenu_base: (new (...args: any[]) => import("@fusion.d
|
|
|
25
25
|
* onto your element after it upgrades causes a hydration mismatch.
|
|
26
26
|
*
|
|
27
27
|
* Group rows under a heading with `Menu.Section`, which is a `group` named by
|
|
28
|
-
* its heading. Between runs of rows, a `<helix-divider
|
|
29
|
-
*
|
|
28
|
+
* its heading. Between runs of rows, a `<helix-divider>` is a separator a
|
|
29
|
+
* screen reader announces.
|
|
30
30
|
*
|
|
31
31
|
* Keys: Enter, Space or Down Arrow on the trigger opens the menu on its first
|
|
32
32
|
* row, and Up Arrow on its last. In the menu, Up and Down Arrow move between
|
|
@@ -4,23 +4,20 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { AriaForwardController, customElement, focusableElements, HelixElement, IndicatorController, KeyboardNavigationController, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { AriaForwardController, customElement, focusableElements, focusItemOf, HelixElement, IndicatorController, KeyboardNavigationController, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
10
|
const FOCUSABLE = 'a[href], button, input, select, textarea, [tabindex]:not([tabindex="-1"])';
|
|
11
11
|
const navigable = (item) => {
|
|
12
12
|
const control = () => item.navigationControl ??
|
|
13
13
|
(item.matches(FOCUSABLE) ? item : focusableElements(item)[0]);
|
|
14
|
-
return {
|
|
15
|
-
|
|
14
|
+
return focusItemOf(item, {
|
|
15
|
+
focus: () => control()?.focus(),
|
|
16
|
+
owns: (node) => {
|
|
16
17
|
const target = control();
|
|
17
18
|
return target !== undefined && node !== null && (node === target || target.contains(node));
|
|
18
19
|
},
|
|
19
|
-
|
|
20
|
-
focusItem: () => control()?.focus(),
|
|
21
|
-
select: () => { },
|
|
22
|
-
selected: false,
|
|
23
|
-
};
|
|
20
|
+
});
|
|
24
21
|
};
|
|
25
22
|
let HelixHeadlessNavigation = class HelixHeadlessNavigation extends HelixElement {
|
|
26
23
|
constructor() {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HelixElement } from '@fusion.dev/helix-core';
|
|
1
|
+
import { ChildrenController, HelixElement } from '@fusion.dev/helix-core';
|
|
2
2
|
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
3
3
|
import type { HelixHeadlessNotification } from '../notification/notification.js';
|
|
4
4
|
declare const HelixHeadlessNotificationGroup_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerOpenInterface) & typeof HelixElement;
|
|
@@ -134,6 +134,13 @@ export declare class HelixHeadlessNotificationGroup extends HelixHeadlessNotific
|
|
|
134
134
|
* is anything behind it. The one row is the evidence.
|
|
135
135
|
*/
|
|
136
136
|
private assign;
|
|
137
|
+
/**
|
|
138
|
+
* Hears a row arrive after the render. The slot is assigned by hand, so a
|
|
139
|
+
* new row is in no slot and sends no `slotchange` to say it came. The feed
|
|
140
|
+
* the group is in is asked to update as well, since it counts the group's
|
|
141
|
+
* rows and does not watch inside the group.
|
|
142
|
+
*/
|
|
143
|
+
protected readonly childWatch: ChildrenController;
|
|
137
144
|
private readonly handleToggle;
|
|
138
145
|
protected render(): TemplateResult;
|
|
139
146
|
}
|
|
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { customElement, HelixElement, hostAria, MixinControllerOpen } from '@fusion.dev/helix-core';
|
|
7
|
+
import { assignSlots, ChildrenController, customElement, HelixElement, hostAria, MixinControllerOpen, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html, nothing } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
10
|
const TRIGGER_ID = 'trigger';
|
|
@@ -16,6 +16,10 @@ let HelixHeadlessNotificationGroup = class HelixHeadlessNotificationGroup extend
|
|
|
16
16
|
this.collapsible = false;
|
|
17
17
|
this.label = '';
|
|
18
18
|
this.isNotificationGroup = true;
|
|
19
|
+
this.childWatch = new ChildrenController(this, () => {
|
|
20
|
+
this.requestUpdate();
|
|
21
|
+
this.parentElement?.requestUpdate?.();
|
|
22
|
+
});
|
|
19
23
|
this.handleToggle = () => {
|
|
20
24
|
this.toggleOpen();
|
|
21
25
|
};
|
|
@@ -58,11 +62,13 @@ let HelixHeadlessNotificationGroup = class HelixHeadlessNotificationGroup extend
|
|
|
58
62
|
}
|
|
59
63
|
return;
|
|
60
64
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
assignSlots(this.renderRoot, this.children, (child) => {
|
|
66
|
+
const row = child;
|
|
67
|
+
if (showing.includes(row)) {
|
|
68
|
+
return '';
|
|
69
|
+
}
|
|
70
|
+
return items.includes(row) || !child.hasAttribute('slot') ? 'folded' : undefined;
|
|
71
|
+
});
|
|
66
72
|
}
|
|
67
73
|
render() {
|
|
68
74
|
const counted = this.counted;
|
|
@@ -53,6 +53,16 @@ declare const HelixHeadlessNotification_base: (new (...args: any[]) => import("@
|
|
|
53
53
|
* @csspart label - What happened, on the first line.
|
|
54
54
|
* @csspart media - The leading slot's box.
|
|
55
55
|
* @csspart time - When it happened, formatted for people to read.
|
|
56
|
+
* @csspart time-day - Format Date's `day`, forwarded for a time drawn as fields. An elapsed time draws none.
|
|
57
|
+
* @csspart time-day-period - Format Date's `day-period`, forwarded for a time drawn as fields. An elapsed time draws none.
|
|
58
|
+
* @csspart time-era - Format Date's `era`, forwarded for a time drawn as fields. An elapsed time draws none.
|
|
59
|
+
* @csspart time-hour - Format Date's `hour`, forwarded for a time drawn as fields. An elapsed time draws none.
|
|
60
|
+
* @csspart time-minute - Format Date's `minute`, forwarded for a time drawn as fields. An elapsed time draws none.
|
|
61
|
+
* @csspart time-month - Format Date's `month`, forwarded for a time drawn as fields. An elapsed time draws none.
|
|
62
|
+
* @csspart time-second - Format Date's `second`, forwarded for a time drawn as fields. An elapsed time draws none.
|
|
63
|
+
* @csspart time-time-zone-name - Format Date's `time-zone-name`, forwarded for a time drawn as fields. An elapsed time draws none.
|
|
64
|
+
* @csspart time-weekday - Format Date's `weekday`, forwarded for a time drawn as fields. An elapsed time draws none.
|
|
65
|
+
* @csspart time-year - Format Date's `year`, forwarded for a time drawn as fields. An elapsed time draws none.
|
|
56
66
|
* @csspart trailing - The column holding the time and the controls.
|
|
57
67
|
*
|
|
58
68
|
* @cssstate actions - While the actions slot holds something, so the words above make room for it.
|
|
@@ -210,7 +220,8 @@ export declare class HelixHeadlessNotification extends HelixHeadlessNotification
|
|
|
210
220
|
* already named for its role: the child publishes `time`, the role here is
|
|
211
221
|
* `time`, and naming both would leave `::part(time)` matching two elements.
|
|
212
222
|
* What a consumer wants to reach is the `<time>`, so that is what the name
|
|
213
|
-
* reaches.
|
|
223
|
+
* reaches. Format Date's field parts go on under `time-`, as every composed
|
|
224
|
+
* child's do, though an elapsed time draws no fields.
|
|
214
225
|
*/
|
|
215
226
|
private renderTime;
|
|
216
227
|
protected render(): TemplateResult;
|
|
@@ -103,7 +103,9 @@ let HelixHeadlessNotification = class HelixHeadlessNotification extends MixinDis
|
|
|
103
103
|
renderTime() {
|
|
104
104
|
const stamp = this.tag('format-date');
|
|
105
105
|
return html `<${stamp}
|
|
106
|
-
exportparts="time
|
|
106
|
+
exportparts="time, day: time-day, day-period: time-day-period, era: time-era, hour: time-hour,
|
|
107
|
+
minute: time-minute, month: time-month, second: time-second,
|
|
108
|
+
time-zone-name: time-time-zone-name, weekday: time-weekday, year: time-year"
|
|
107
109
|
elapsed
|
|
108
110
|
date=${this.at}
|
|
109
111
|
locale=${this.locale}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HelixElement } from '@fusion.dev/helix-core';
|
|
1
|
+
import { ChildrenController, HelixElement } from '@fusion.dev/helix-core';
|
|
2
2
|
import { nothing, type PropertyValues, type TemplateResult } from 'lit';
|
|
3
3
|
import type { HelixHeadlessNotificationGroup } from '../group/notification-group.js';
|
|
4
4
|
import type { HelixHeadlessNotification } from '../notification/notification.js';
|
|
@@ -35,6 +35,11 @@ declare const HelixHeadlessNotifications_base: (new (...args: any[]) => import("
|
|
|
35
35
|
* nothing more. While the feed is loading the button stays, so focus stays on
|
|
36
36
|
* it, and it is `aria-disabled` and sends nothing.
|
|
37
37
|
*
|
|
38
|
+
* **`load-error` says a page could not be fetched**, such as "Could not load
|
|
39
|
+
* notifications": it shows under the rows there are, in the `error` part, in
|
|
40
|
+
* place of the empty and loading lines, and a screen reader hears it. Clear it
|
|
41
|
+
* when you ask again.
|
|
42
|
+
*
|
|
38
43
|
* @since 0.1.0
|
|
39
44
|
* @status stable
|
|
40
45
|
* @category feedback
|
|
@@ -43,6 +48,7 @@ declare const HelixHeadlessNotifications_base: (new (...args: any[]) => import("
|
|
|
43
48
|
*
|
|
44
49
|
* @slot - The notifications.
|
|
45
50
|
* @slot empty - Replaces the words of `empty-text` in the `empty` part, such as a picture and a line of your own. The live region still says `empty-text`.
|
|
51
|
+
* @slot error - Replaces the words of `load-error` in the `error` part, such as a line with a retry button. The live region still says `load-error`.
|
|
46
52
|
* @slot footer - Below the list, for a link to everything.
|
|
47
53
|
* @slot header - Above the list, for a title and the mark-all control.
|
|
48
54
|
* @slot loading - Replaces the words of `loading-text` in the `loading` part, such as a loader. The live region still says `loading-text`.
|
|
@@ -52,15 +58,17 @@ declare const HelixHeadlessNotifications_base: (new (...args: any[]) => import("
|
|
|
52
58
|
* @event {HelixReadEvent} helix-read - Fired by an item when it is marked read or unread, and by the feed when everything is marked read. Cancelable.
|
|
53
59
|
*
|
|
54
60
|
* @csspart empty - Shown when there is nothing in the feed.
|
|
61
|
+
* @csspart error - Shown while `load-error` is set, under any rows there are, in place of the empty and loading lines.
|
|
55
62
|
* @csspart footer - The region below the list.
|
|
56
63
|
* @csspart header - The region above the list.
|
|
57
64
|
* @csspart list - The list itself.
|
|
58
|
-
* @csspart live - A copy of the empty and
|
|
65
|
+
* @csspart live - A copy of the empty, loading and error messages, in a live region that screen readers announce when they change. Visually hidden in the styled tier.
|
|
59
66
|
* @csspart loading - Shown while the feed is loading.
|
|
60
67
|
* @csspart mark-all - The control that marks everything read.
|
|
61
68
|
* @csspart more - The button after the list that asks for more, while `load-more` is set. It is `aria-disabled` while the feed is loading.
|
|
62
69
|
*
|
|
63
|
-
* @cssstate empty - While the feed holds no notification and is not loading. Never on the server, which cannot count the rows.
|
|
70
|
+
* @cssstate empty - While the feed holds no notification and is not loading, and `load-error` is not set. Never on the server, which cannot count the rows.
|
|
71
|
+
* @cssstate error - While `load-error` is set.
|
|
64
72
|
* @cssstate loading - While `loading` is set.
|
|
65
73
|
* @cssstate media - While a notification in the list holds something in its media slot, so every row keeps that column.
|
|
66
74
|
*/
|
|
@@ -79,6 +87,15 @@ export declare class HelixHeadlessNotifications extends HelixHeadlessNotificatio
|
|
|
79
87
|
* The feed's name, read by screen readers.
|
|
80
88
|
*/
|
|
81
89
|
label: string;
|
|
90
|
+
/**
|
|
91
|
+
* What the feed says when a page of it could not be loaded, such as after a
|
|
92
|
+
* request to a server fails. Empty, null or no attribute is no error, so
|
|
93
|
+
* removing the attribute clears it.
|
|
94
|
+
*
|
|
95
|
+
* Set, it shows under the rows there are, in place of `empty-text` and
|
|
96
|
+
* `loading-text`, and a screen reader hears it. Clear it when you ask again.
|
|
97
|
+
*/
|
|
98
|
+
loadError: string | null;
|
|
82
99
|
/**
|
|
83
100
|
* Whether to draw a button after the list that asks for the next page, by
|
|
84
101
|
* sending `helix-load-more`. Take it off once there is nothing more.
|
|
@@ -186,6 +203,13 @@ export declare class HelixHeadlessNotifications extends HelixHeadlessNotificatio
|
|
|
186
203
|
markEveryoneRead(): number;
|
|
187
204
|
/** Items arrive from the light DOM, which changes no property here. */
|
|
188
205
|
private readonly handleSlotChange;
|
|
206
|
+
/**
|
|
207
|
+
* Hears a row arrive after the render. The list slot is assigned by hand, so
|
|
208
|
+
* a new row is in no slot and sends no `slotchange`: without this it was
|
|
209
|
+
* drawn nowhere and an emptied feed kept saying it was empty. A row added to
|
|
210
|
+
* a group is the group's to hear, and the group asks the feed to count again.
|
|
211
|
+
*/
|
|
212
|
+
protected readonly childWatch: ChildrenController;
|
|
189
213
|
/**
|
|
190
214
|
* An item's own read event, heard on the way past, so the count follows.
|
|
191
215
|
*
|
|
@@ -225,10 +249,22 @@ export declare class HelixHeadlessNotifications extends HelixHeadlessNotificatio
|
|
|
225
249
|
disconnectedCallback(): void;
|
|
226
250
|
protected willUpdate(changed: PropertyValues): void;
|
|
227
251
|
/**
|
|
228
|
-
* The empty or the loading line, each around a slot of the same
|
|
229
|
-
* fallback is the words, so a page can put its own in.
|
|
252
|
+
* The error, the empty or the loading line, each around a slot of the same
|
|
253
|
+
* name whose fallback is the words, so a page can put its own in. The error
|
|
254
|
+
* shows under the rows there are, since a page that failed is news even to
|
|
255
|
+
* a feed that holds some.
|
|
230
256
|
*/
|
|
231
257
|
protected renderStatus(hasContent: boolean): TemplateResult | typeof nothing;
|
|
258
|
+
/**
|
|
259
|
+
* The error before the shared words, which it wins over.
|
|
260
|
+
*
|
|
261
|
+
* **One region whose words change**, as the shared one is. The error was a
|
|
262
|
+
* second template, so setting it put a new region in the tree with its
|
|
263
|
+
* words already in it, which a reader may not hear.
|
|
264
|
+
*/
|
|
265
|
+
protected renderLive(hasContent: boolean): TemplateResult;
|
|
266
|
+
/** Whether `load-error` holds words, which a removed attribute does not. */
|
|
267
|
+
private get failed();
|
|
232
268
|
/**
|
|
233
269
|
* Says whether anything in the feed has media, so every item can hold the
|
|
234
270
|
* column open whether or not it has one of its own.
|
|
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { customElement, deepContains, focusableElements, HelixElement, HelixLoadMoreEvent, HelixReadEvent, hostAria, MixinCollection, markRegion, rovingStop, whenHydrated, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { assignSlots, ChildrenController, customElement, deepContains, focusableElements, HelixElement, HelixLoadMoreEvent, HelixReadEvent, hostAria, MixinCollection, markRegion, rovingStop, whenHydrated, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html, nothing } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
10
|
import { feedStep } from '../feed-keys.js';
|
|
@@ -12,6 +12,7 @@ let HelixHeadlessNotifications = class HelixHeadlessNotifications extends MixinC
|
|
|
12
12
|
constructor() {
|
|
13
13
|
super(...arguments);
|
|
14
14
|
this.label = '';
|
|
15
|
+
this.loadError = '';
|
|
15
16
|
this.loadMore = false;
|
|
16
17
|
this.loadMoreLabel = 'Load more';
|
|
17
18
|
this.markAll = false;
|
|
@@ -20,6 +21,7 @@ let HelixHeadlessNotifications = class HelixHeadlessNotifications extends MixinC
|
|
|
20
21
|
this.assign();
|
|
21
22
|
this.requestUpdate();
|
|
22
23
|
};
|
|
24
|
+
this.childWatch = new ChildrenController(this, this.handleSlotChange);
|
|
23
25
|
this.handleRead = () => {
|
|
24
26
|
queueMicrotask(() => this.requestUpdate());
|
|
25
27
|
};
|
|
@@ -138,11 +140,8 @@ let HelixHeadlessNotifications = class HelixHeadlessNotifications extends MixinC
|
|
|
138
140
|
}
|
|
139
141
|
return;
|
|
140
142
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const slot = root?.querySelector?.(`slot[name="${name}"]`);
|
|
144
|
-
slot?.assign?.(...[...this.children].filter((child) => child.getAttribute('slot') === name));
|
|
145
|
-
}
|
|
143
|
+
const rows = this.rows;
|
|
144
|
+
assignSlots(root, this.children, (child) => rows.includes(child) ? 'items' : child.getAttribute('slot') === 'items' ? '' : undefined);
|
|
146
145
|
}
|
|
147
146
|
get reachable() {
|
|
148
147
|
return this.items.filter((row) => !('assignedSlot' in row) || row.assignedSlot !== null);
|
|
@@ -178,10 +177,15 @@ let HelixHeadlessNotifications = class HelixHeadlessNotifications extends MixinC
|
|
|
178
177
|
willUpdate(changed) {
|
|
179
178
|
super.willUpdate(changed);
|
|
180
179
|
hostAria(this.internals, { label: this.label || 'Notifications', role: 'region' });
|
|
180
|
+
const failed = this.failed;
|
|
181
|
+
this.toggleState('error', failed);
|
|
181
182
|
this.toggleState('loading', this.loading);
|
|
182
|
-
this.toggleState('empty', !this.loading && this.childrenKnown && this.items.length === 0);
|
|
183
|
+
this.toggleState('empty', !this.loading && !failed && this.childrenKnown && this.items.length === 0);
|
|
183
184
|
}
|
|
184
185
|
renderStatus(hasContent) {
|
|
186
|
+
if (this.failed) {
|
|
187
|
+
return html `<p part="error"><slot name="error">${this.loadError}</slot></p>`;
|
|
188
|
+
}
|
|
185
189
|
if (!this.loading && hasContent) {
|
|
186
190
|
return nothing;
|
|
187
191
|
}
|
|
@@ -189,6 +193,19 @@ let HelixHeadlessNotifications = class HelixHeadlessNotifications extends MixinC
|
|
|
189
193
|
? html `<p part="loading"><slot name="loading">${this.loadingText}</slot></p>`
|
|
190
194
|
: html `<p part="empty"><slot name="empty">${this.emptyText}</slot></p>`;
|
|
191
195
|
}
|
|
196
|
+
renderLive(hasContent) {
|
|
197
|
+
const said = this.failed
|
|
198
|
+
? this.loadError
|
|
199
|
+
: this.loading
|
|
200
|
+
? this.loadingText
|
|
201
|
+
: hasContent
|
|
202
|
+
? this.announced
|
|
203
|
+
: this.emptyText;
|
|
204
|
+
return html `<span part="live" role="status">${said}</span>`;
|
|
205
|
+
}
|
|
206
|
+
get failed() {
|
|
207
|
+
return Boolean(this.loadError);
|
|
208
|
+
}
|
|
192
209
|
markMedia() {
|
|
193
210
|
this.toggleState('media', this.items.some((item) => item.querySelector(':scope > [slot="media"]') !== null));
|
|
194
211
|
}
|
|
@@ -248,6 +265,9 @@ let HelixHeadlessNotifications = class HelixHeadlessNotifications extends MixinC
|
|
|
248
265
|
__decorate([
|
|
249
266
|
property()
|
|
250
267
|
], HelixHeadlessNotifications.prototype, "label", void 0);
|
|
268
|
+
__decorate([
|
|
269
|
+
property({ attribute: 'load-error' })
|
|
270
|
+
], HelixHeadlessNotifications.prototype, "loadError", void 0);
|
|
251
271
|
__decorate([
|
|
252
272
|
property({ attribute: 'load-more', type: Boolean })
|
|
253
273
|
], HelixHeadlessNotifications.prototype, "loadMore", void 0);
|
|
@@ -351,7 +351,7 @@ export declare class HelixHeadlessObjectInspector extends HelixElement {
|
|
|
351
351
|
* box, or in the window where the box does not scroll, less one, so the row
|
|
352
352
|
* at one edge lands at the other.
|
|
353
353
|
*/
|
|
354
|
-
private
|
|
354
|
+
private pageStep;
|
|
355
355
|
/** A press opens or closes the row it lands on, once that row is the active one. */
|
|
356
356
|
private handleRowClick;
|
|
357
357
|
/** Text typed in the filter field, asked for before the tree narrows to it. */
|
|
@@ -380,6 +380,10 @@ export declare class HelixHeadlessObjectInspector extends HelixElement {
|
|
|
380
380
|
private renderRow;
|
|
381
381
|
/** Whether a row of the tree holds focus. */
|
|
382
382
|
private hasFocusedRow;
|
|
383
|
+
/** Where the row holding focus is among the visible rows, or nothing while none does. */
|
|
384
|
+
private focusedIndex;
|
|
385
|
+
/** Focus on the tree itself, its stop while the active row is not drawn, goes on to that row. */
|
|
386
|
+
private readonly handleTreeFocus;
|
|
383
387
|
/**
|
|
384
388
|
* A row that takes focus some other way than the keys, from a script or a
|
|
385
389
|
* screen reader, becomes the active row, so focus and the keys agree.
|