@fusion.dev/helix-headless 0.157.0 → 0.159.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 +851 -56
- package/dist/app-header/root/app-header-root.d.ts +59 -12
- package/dist/app-header/root/app-header-root.js +68 -29
- package/dist/button/root/button-root.d.ts +8 -0
- package/dist/button/root/button-root.js +4 -0
- package/dist/button-group/root/button-group-root.d.ts +20 -7
- package/dist/button-group/root/button-group-root.js +30 -5
- package/dist/command/root/command-root.d.ts +19 -5
- package/dist/command/root/command-root.js +14 -4
- package/dist/data-table/root/data-table-root.js +5 -2
- package/dist/element-events.generated.js +7 -1
- package/dist/lightbox/item/lightbox-item.d.ts +7 -1
- package/dist/lightbox/item/lightbox-item.js +15 -2
- package/dist/lightbox/root/lightbox-root.d.ts +8 -4
- package/dist/lightbox/root/lightbox-root.js +9 -1
- package/dist/lightbox/zoom-controller.d.ts +13 -4
- package/dist/lightbox/zoom-controller.js +17 -0
- package/dist/list/item/list-item.d.ts +15 -1
- package/dist/list/item/list-item.js +20 -1
- package/dist/list/root/list-root.d.ts +29 -1
- package/dist/list/root/list-root.js +44 -10
- package/dist/listbox/root/listbox-root.d.ts +19 -2
- package/dist/listbox/root/listbox-root.js +16 -8
- package/dist/map/root/map-root.d.ts +4 -0
- package/dist/map/root/map-root.js +1 -1
- package/dist/menu/item/menu-item.js +1 -1
- package/dist/menu/root/menu-root.d.ts +6 -2
- package/dist/menu/root/menu-root.js +9 -2
- package/dist/number-input/root/number-input-root.d.ts +7 -4
- package/dist/number-input/root/number-input-root.js +3 -3
- package/dist/progress/root/progress-root.js +1 -1
- package/dist/select/root/select-root.d.ts +8 -1
- package/dist/select/root/select-root.js +8 -5
- package/dist/skeleton/root/skeleton-root.d.ts +2 -1
- package/dist/skeleton/root/skeleton-root.js +1 -1
- package/dist/steps/root/steps-root.js +8 -6
- package/dist/steps/step/step.d.ts +9 -0
- package/dist/steps/step/step.js +19 -2
- package/dist/tabs/panel/tab-panel.d.ts +22 -1
- package/dist/tabs/panel/tab-panel.js +22 -1
- package/dist/tabs/root/tabs-root.d.ts +21 -5
- package/dist/tabs/root/tabs-root.js +24 -13
- package/dist/tabs/tab/tab.d.ts +4 -2
- package/dist/tabs/tab/tab.js +3 -1
- package/dist/toast/item/toast-item.d.ts +27 -8
- package/dist/toast/item/toast-item.js +47 -11
- package/dist/toast/root/toast-root.d.ts +24 -1
- package/dist/toast/root/toast-root.js +54 -0
- package/dist/tour/root/tour-root.d.ts +34 -0
- package/dist/tour/root/tour-root.js +92 -3
- package/dist/tour/stop/tour-stop.d.ts +33 -1
- package/dist/tour/stop/tour-stop.js +12 -1
- package/lint.json +1 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +667 -102
- package/types/vue.d.ts +148 -23
|
@@ -1015,7 +1015,7 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
|
|
|
1015
1015
|
: '';
|
|
1016
1016
|
}
|
|
1017
1017
|
}
|
|
1018
|
-
renderCell(column, row, picked, place, stop, header) {
|
|
1018
|
+
renderCell(column, row, picked, place, stop, header, id) {
|
|
1019
1019
|
const edge = edgeOf(column.textAlign);
|
|
1020
1020
|
const placed = header && this.childField ? this.tree().placed.get(row) : undefined;
|
|
1021
1021
|
const style = [
|
|
@@ -1027,6 +1027,7 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
|
|
|
1027
1027
|
return html `<span
|
|
1028
1028
|
part="cell${picked ? ' cell-selected' : ''}${header ? ' cell-header' : ''}"
|
|
1029
1029
|
role=${header ? 'rowheader' : 'gridcell'}
|
|
1030
|
+
id=${id ?? nothing}
|
|
1030
1031
|
tabindex=${stop ? '0' : '-1'}
|
|
1031
1032
|
data-column=${place}
|
|
1032
1033
|
style=${style || nothing}
|
|
@@ -1083,10 +1084,12 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
|
|
|
1083
1084
|
const stop = (place) => index === stopRow && place === stopColumn;
|
|
1084
1085
|
const moveKey = grips ? this.moveKeyOf(row) : '';
|
|
1085
1086
|
const marked = mark?.key === moveKey ? mark.edge : undefined;
|
|
1087
|
+
const header = named ? `row-header-${index}` : undefined;
|
|
1086
1088
|
return html `<div
|
|
1087
1089
|
part="row${held?.key === moveKey ? ' row-dragging' : ''}${marked === 'before' ? ' row-drop-before' : ''}${marked === 'after' ? ' row-drop-after' : ''}"
|
|
1088
1090
|
role="row"
|
|
1089
1091
|
aria-rowindex=${this.rowIndex(index)}
|
|
1092
|
+
aria-labelledby=${header ?? nothing}
|
|
1090
1093
|
aria-selected=${chosen ? String(picked) : nothing}
|
|
1091
1094
|
aria-level=${placed ? placed.level : nothing}
|
|
1092
1095
|
aria-posinset=${placed ? placed.position : nothing}
|
|
@@ -1103,7 +1106,7 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
|
|
|
1103
1106
|
data-column=${this.selectAt}
|
|
1104
1107
|
>${this.renderCheckbox(formatWords(this, 'selectRowLabel', { row: this.rowName(row, key) }), picked, false, (event) => this.toggleRow(key, event))}</span
|
|
1105
1108
|
>`
|
|
1106
|
-
: nothing}${columns.map((column, place) => this.renderCell(column, row, picked, place + offset, stop(place + offset), column === named))}
|
|
1109
|
+
: nothing}${columns.map((column, place) => this.renderCell(column, row, picked, place + offset, stop(place + offset), column === named, column === named ? header : undefined))}
|
|
1107
1110
|
</div>`;
|
|
1108
1111
|
})}`;
|
|
1109
1112
|
}
|
|
@@ -2,7 +2,12 @@ import { registerElementEvents } from '@fusion.dev/helix-core';
|
|
|
2
2
|
registerElementEvents({
|
|
3
3
|
'helix-headless-accordion-item': ['helix-before-close', 'helix-before-open', 'toggle'],
|
|
4
4
|
'helix-headless-announcement': ['helix-dismiss'],
|
|
5
|
-
'helix-headless-app-header': [
|
|
5
|
+
'helix-headless-app-header': [
|
|
6
|
+
'helix-before-close',
|
|
7
|
+
'helix-before-open',
|
|
8
|
+
'helix-scrolled',
|
|
9
|
+
'toggle',
|
|
10
|
+
],
|
|
6
11
|
'helix-headless-area-chart': ['helix-reading-change', 'helix-reading-press'],
|
|
7
12
|
'helix-headless-avatar': ['error', 'load'],
|
|
8
13
|
'helix-headless-bar-chart': ['helix-reading-change', 'helix-reading-press'],
|
|
@@ -244,6 +249,7 @@ registerElementEvents({
|
|
|
244
249
|
'helix-headless-steps': ['helix-activate', 'helix-before-activate', 'helix-select'],
|
|
245
250
|
'helix-headless-switch': ['change', 'helix-before-toggle', 'input'],
|
|
246
251
|
'helix-headless-tab': ['helix-dismiss', 'helix-select'],
|
|
252
|
+
'helix-headless-tab-panel': ['helix-load'],
|
|
247
253
|
'helix-headless-table': [
|
|
248
254
|
'helix-before-reorder',
|
|
249
255
|
'helix-reorder',
|
|
@@ -11,7 +11,8 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
11
11
|
* Each item is a `group` with the role description `slide`, named by its
|
|
12
12
|
* `label` or, without one, by its place in the set, such as "2 of 5", as the
|
|
13
13
|
* APG carousel pattern names a slide. The lightbox supplies both words, so they
|
|
14
|
-
* are translated on the lightbox.
|
|
14
|
+
* are translated on the lightbox. A `description`, while there is one,
|
|
15
|
+
* describes the group, so a screen reader says it with the name.
|
|
15
16
|
*
|
|
16
17
|
* An item that is not current is hidden and stays in the DOM, as a `TabPanel`
|
|
17
18
|
* does. Crawlers can read it, it is in the server-rendered markup, and an
|
|
@@ -56,6 +57,11 @@ export declare class HelixHeadlessLightboxItem extends HelixElement {
|
|
|
56
57
|
* The item's identifier. The root compares its `active` value against it.
|
|
57
58
|
*/
|
|
58
59
|
value: string;
|
|
60
|
+
/** Whether anything is slotted into `description`, which then describes the group. */
|
|
61
|
+
private described;
|
|
62
|
+
/** Reads whether the `description` slot holds anything. */
|
|
63
|
+
private readonly readDescription;
|
|
64
|
+
protected firstUpdated(changed: PropertyValues): void;
|
|
59
65
|
/**
|
|
60
66
|
* Mirrors the shown state where a stylesheet can select on it.
|
|
61
67
|
*
|
|
@@ -6,7 +6,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
6
6
|
};
|
|
7
7
|
import { customElement, HelixElement } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html, nothing } from 'lit';
|
|
9
|
-
import { property } from 'lit/decorators.js';
|
|
9
|
+
import { property, state } from 'lit/decorators.js';
|
|
10
10
|
let HelixHeadlessLightboxItem = class HelixHeadlessLightboxItem extends HelixElement {
|
|
11
11
|
constructor() {
|
|
12
12
|
super(...arguments);
|
|
@@ -15,6 +15,15 @@ let HelixHeadlessLightboxItem = class HelixHeadlessLightboxItem extends HelixEle
|
|
|
15
15
|
this.position = '';
|
|
16
16
|
this.roleLabel = '';
|
|
17
17
|
this.value = '';
|
|
18
|
+
this.described = false;
|
|
19
|
+
this.readDescription = () => {
|
|
20
|
+
const slot = this.renderRoot.querySelector('slot[name="description"]');
|
|
21
|
+
this.described = (slot?.assignedNodes() ?? []).some((node) => node.nodeType === Node.ELEMENT_NODE || (node.textContent ?? '').trim() !== '');
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
firstUpdated(changed) {
|
|
25
|
+
super.firstUpdated(changed);
|
|
26
|
+
this.readDescription();
|
|
18
27
|
}
|
|
19
28
|
willUpdate(changed) {
|
|
20
29
|
super.willUpdate(changed);
|
|
@@ -26,10 +35,11 @@ let HelixHeadlessLightboxItem = class HelixHeadlessLightboxItem extends HelixEle
|
|
|
26
35
|
role="group"
|
|
27
36
|
aria-roledescription=${this.roleLabel || nothing}
|
|
28
37
|
aria-label=${this.label || this.position || nothing}
|
|
38
|
+
aria-describedby=${this.described ? 'description' : nothing}
|
|
29
39
|
?hidden=${!this.current}
|
|
30
40
|
>
|
|
31
41
|
<slot></slot>
|
|
32
|
-
<slot name="description"></slot>
|
|
42
|
+
<slot id="description" name="description" @slotchange=${this.readDescription}></slot>
|
|
33
43
|
</div>`;
|
|
34
44
|
}
|
|
35
45
|
};
|
|
@@ -48,6 +58,9 @@ __decorate([
|
|
|
48
58
|
__decorate([
|
|
49
59
|
property()
|
|
50
60
|
], HelixHeadlessLightboxItem.prototype, "value", void 0);
|
|
61
|
+
__decorate([
|
|
62
|
+
state()
|
|
63
|
+
], HelixHeadlessLightboxItem.prototype, "described", void 0);
|
|
51
64
|
HelixHeadlessLightboxItem = __decorate([
|
|
52
65
|
customElement('helix-headless-lightbox-item')
|
|
53
66
|
], HelixHeadlessLightboxItem);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CommandController, FocusScopeController, HelixElement, SwipeController } from '@fusion.dev/helix-core';
|
|
1
|
+
import { CommandController, FocusScopeController, HelixElement, OpenPhaseController, SwipeController } from '@fusion.dev/helix-core';
|
|
2
2
|
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
3
3
|
declare const HelixHeadlessLightbox_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerActiveInterface) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerOpenInterface) & typeof HelixElement;
|
|
4
4
|
/**
|
|
@@ -25,9 +25,10 @@ declare const HelixHeadlessLightbox_base: (new (...args: any[]) => import("@fusi
|
|
|
25
25
|
* buttons that send `--previous`, `--next` and `--close`.
|
|
26
26
|
*
|
|
27
27
|
* **Set `max-zoom` to let a reader zoom into the shown item**, such as `3`.
|
|
28
|
-
* Two fingers pinch it,
|
|
29
|
-
* about the point
|
|
30
|
-
*
|
|
28
|
+
* Two fingers pinch it, and so does a trackpad pinch or the wheel with Ctrl
|
|
29
|
+
* held, about the point under the pointer. A double click or a double tap
|
|
30
|
+
* zooms all the way in about the point pressed and back out, and once zoomed
|
|
31
|
+
* one pointer drags it about and the wheel moves it. `+` and `-` zoom in and out from the keyboard, and `0` puts it back.
|
|
31
32
|
* The scale and the offset are on the `viewport` part as `--lightbox-zoom`,
|
|
32
33
|
* `--lightbox-pan-x` and `--lightbox-pan-y`, written only while zoomed, and a
|
|
33
34
|
* swipe moves to the next item only while the item is at its own size. With
|
|
@@ -109,6 +110,8 @@ declare const HelixHeadlessLightbox_base: (new (...args: any[]) => import("@fusi
|
|
|
109
110
|
* @cssproperty --lightbox-pan-y - How far the zoomed item is moved down from the middle of the viewport, in pixels. Written only while zoomed.
|
|
110
111
|
* @cssproperty --lightbox-zoom - The scale the shown item is zoomed to, such as `2`. Written only while zoomed.
|
|
111
112
|
*
|
|
113
|
+
* @cssstate entering - From the moment the viewer opens until its entrance has finished.
|
|
114
|
+
* @cssstate exiting - From the moment the viewer closes until its exit has finished.
|
|
112
115
|
* @cssstate fullscreen - While the viewer fills the screen.
|
|
113
116
|
* @cssstate idle - The user has been still long enough for the controls to hide.
|
|
114
117
|
* @cssstate open - While the viewer is open.
|
|
@@ -225,6 +228,7 @@ export declare class HelixHeadlessLightbox extends HelixHeadlessLightbox_base {
|
|
|
225
228
|
private quietZoom;
|
|
226
229
|
protected readonly swipes: SwipeController;
|
|
227
230
|
protected readonly scope: FocusScopeController;
|
|
231
|
+
protected readonly phases: OpenPhaseController;
|
|
228
232
|
private readonly zoomer;
|
|
229
233
|
/** Writes the zoom on the viewport, and nothing at the item's own size. */
|
|
230
234
|
private drawZoom;
|
|
@@ -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 { auditName, CommandController, customElement, DelayController, deepActiveElement, deepContains, drawDismiss, drawDismissGlyph, FocusScopeController, FullscreenController, formatWords, HelixElement, hostAria, IdleController, inlineStep, isRtl, keyClaimed, lockScroll, MixinControllerActive, MixinControllerOpen, resolveActiveItem, SwipeController, swipeStep, unlockScroll, whenHydrated, words, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { auditName, CommandController, customElement, DelayController, deepActiveElement, deepContains, drawDismiss, drawDismissGlyph, FocusScopeController, FullscreenController, formatWords, HelixElement, hostAria, IdleController, inlineStep, isRtl, keyClaimed, lockScroll, MixinControllerActive, MixinControllerOpen, OpenPhaseController, resolveActiveItem, SwipeController, swipeStep, unlockScroll, whenHydrated, words, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { caretLeft, caretRight, minus, plus } from '@fusion.dev/helix-icons';
|
|
9
9
|
import { html, nothing } from 'lit';
|
|
10
10
|
import { property, state } from 'lit/decorators.js';
|
|
@@ -63,6 +63,14 @@ let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerA
|
|
|
63
63
|
],
|
|
64
64
|
surface: () => this.dialog,
|
|
65
65
|
});
|
|
66
|
+
this.phases = new OpenPhaseController(this, {
|
|
67
|
+
mark: {
|
|
68
|
+
entering: (on) => this.toggleState('entering', on),
|
|
69
|
+
exiting: (on) => this.toggleState('exiting', on),
|
|
70
|
+
},
|
|
71
|
+
open: () => this.open,
|
|
72
|
+
surface: () => this.dialog,
|
|
73
|
+
});
|
|
66
74
|
this.zoomer = new ZoomController(this, {
|
|
67
75
|
max: () => (this.open ? this.maxZoom : 1),
|
|
68
76
|
onChange: (scale, pan) => this.drawZoom(scale, pan),
|
|
@@ -12,10 +12,12 @@ export interface ZoomOptions {
|
|
|
12
12
|
* Pinch, double press and drag to zoom into the item a viewer shows, and to
|
|
13
13
|
* move about it once zoomed.
|
|
14
14
|
*
|
|
15
|
-
* Two fingers scale the item about the point between them. A
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
15
|
+
* Two fingers scale the item about the point between them. A trackpad pinch,
|
|
16
|
+
* which the browser sends as a wheel with Ctrl held, and the wheel with Ctrl
|
|
17
|
+
* held scale it about the pointer. A double press, a double click or a double
|
|
18
|
+
* tap, zooms all the way in about the point pressed, and back out again. One
|
|
19
|
+
* pointer dragged across a zoomed item moves it, never so far that an edge
|
|
20
|
+
* comes inside the box, and so does the wheel without Ctrl. What it reports is a scale and an
|
|
19
21
|
* offset from the middle of the box; drawing them is the caller's.
|
|
20
22
|
*/
|
|
21
23
|
export declare class ZoomController implements ReactiveController {
|
|
@@ -58,6 +60,13 @@ export declare class ZoomController implements ReactiveController {
|
|
|
58
60
|
private readonly handleDown;
|
|
59
61
|
private readonly handleMove;
|
|
60
62
|
private readonly handleUp;
|
|
63
|
+
/**
|
|
64
|
+
* A wheel with Ctrl held, which is also how the browser sends a trackpad
|
|
65
|
+
* pinch, scales the item about the pointer. Without Ctrl, a wheel over a
|
|
66
|
+
* zoomed item moves it. Over an item at its own size, a plain wheel is left
|
|
67
|
+
* to the page.
|
|
68
|
+
*/
|
|
69
|
+
private readonly handleWheel;
|
|
61
70
|
/** A double press zooms all the way in about the point, or back out. */
|
|
62
71
|
private readonly handleDouble;
|
|
63
72
|
private release;
|
|
@@ -48,6 +48,21 @@ export class ZoomController {
|
|
|
48
48
|
}
|
|
49
49
|
this.begin();
|
|
50
50
|
};
|
|
51
|
+
this.handleWheel = (event) => {
|
|
52
|
+
if (this.options.max() <= 1 || !this.inside(event)) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const unit = event.deltaMode === 1 ? 16 : event.deltaMode === 2 ? 400 : 1;
|
|
56
|
+
if (event.ctrlKey) {
|
|
57
|
+
event.preventDefault();
|
|
58
|
+
this.zoomTo(this.scale * Math.exp((-event.deltaY * unit) / 250), this.offset(event));
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (this.scale > 1) {
|
|
62
|
+
event.preventDefault();
|
|
63
|
+
this.panBy({ x: -event.deltaX * unit, y: -event.deltaY * unit });
|
|
64
|
+
}
|
|
65
|
+
};
|
|
51
66
|
this.handleDouble = (event) => {
|
|
52
67
|
if (this.options.max() <= 1 || !this.inside(event)) {
|
|
53
68
|
return;
|
|
@@ -69,10 +84,12 @@ export class ZoomController {
|
|
|
69
84
|
hostConnected() {
|
|
70
85
|
this.host.addEventListener('pointerdown', this.handleDown);
|
|
71
86
|
this.host.addEventListener('dblclick', this.handleDouble);
|
|
87
|
+
this.host.addEventListener('wheel', this.handleWheel, { passive: false });
|
|
72
88
|
}
|
|
73
89
|
hostDisconnected() {
|
|
74
90
|
this.host.removeEventListener('pointerdown', this.handleDown);
|
|
75
91
|
this.host.removeEventListener('dblclick', this.handleDouble);
|
|
92
|
+
this.host.removeEventListener('wheel', this.handleWheel);
|
|
76
93
|
this.release();
|
|
77
94
|
}
|
|
78
95
|
zoomTo(scale, at = { x: 0, y: 0 }) {
|
|
@@ -63,6 +63,7 @@ export type ListItemMove = 'dragging' | 'drop-after' | 'drop-before';
|
|
|
63
63
|
* @cssstate dragging - The row is being moved, in a `reorderable` list.
|
|
64
64
|
* @cssstate drop-after - The row being moved goes after this one when it is dropped.
|
|
65
65
|
* @cssstate drop-before - The row being moved goes before this one when it is dropped.
|
|
66
|
+
* @cssstate focus-visible - The row's cell has keyboard focus that the browser shows, in a list with `selection-mode`.
|
|
66
67
|
* @cssstate selected - The row is selected, in a list that selects.
|
|
67
68
|
*/
|
|
68
69
|
export declare class HelixHeadlessListItem extends HelixElement {
|
|
@@ -78,7 +79,9 @@ export declare class HelixHeadlessListItem extends HelixElement {
|
|
|
78
79
|
current?: ListItemCurrent;
|
|
79
80
|
/**
|
|
80
81
|
* Takes the row out of reach in a list with `selection-mode`: the arrow keys
|
|
81
|
-
* pass over it and it cannot be selected or acted on.
|
|
82
|
+
* pass over it and it cannot be selected or acted on. In a list with
|
|
83
|
+
* `disabled-behavior="selection"`, it cannot be selected and is reached and
|
|
84
|
+
* acted on as any row is.
|
|
82
85
|
*/
|
|
83
86
|
disabled: boolean;
|
|
84
87
|
/**
|
|
@@ -121,6 +124,12 @@ export declare class HelixHeadlessListItem extends HelixElement {
|
|
|
121
124
|
* selected, which keeps `aria-selected` off a list that does not select.
|
|
122
125
|
*/
|
|
123
126
|
selected?: boolean | undefined;
|
|
127
|
+
/**
|
|
128
|
+
* Whether `disabled` takes the row out of selection alone, written by a list
|
|
129
|
+
* with `disabled-behavior="selection"`. No attribute: it is coordination
|
|
130
|
+
* between elements, as `focusable` is.
|
|
131
|
+
*/
|
|
132
|
+
selectionOnly: boolean;
|
|
124
133
|
/**
|
|
125
134
|
* Where the link opens. Use it for a row that leaves the site.
|
|
126
135
|
*/
|
|
@@ -137,6 +146,11 @@ export declare class HelixHeadlessListItem extends HelixElement {
|
|
|
137
146
|
private get described();
|
|
138
147
|
/** Whether a list with `selection-mode` holds this row, which makes it a row of a grid. */
|
|
139
148
|
private get grouped();
|
|
149
|
+
/**
|
|
150
|
+
* Says whether the cell shows keyboard focus, as `:focus-visible` does, so a
|
|
151
|
+
* sheet outside can draw the ring on the row by the host.
|
|
152
|
+
*/
|
|
153
|
+
private readonly handleCellFocus;
|
|
140
154
|
/** @internal The cell a list with `selection-mode` moves focus to. */
|
|
141
155
|
get cell(): HTMLElement | null;
|
|
142
156
|
/**
|
|
@@ -16,8 +16,19 @@ let HelixHeadlessListItem = class HelixHeadlessListItem extends HelixElement {
|
|
|
16
16
|
this.disabled = false;
|
|
17
17
|
this.href = '';
|
|
18
18
|
this.rel = '';
|
|
19
|
+
this.selectionOnly = false;
|
|
19
20
|
this.target = '';
|
|
20
21
|
this.value = '';
|
|
22
|
+
this.handleCellFocus = (event) => {
|
|
23
|
+
const cell = event.currentTarget;
|
|
24
|
+
let shown = false;
|
|
25
|
+
try {
|
|
26
|
+
shown = event.type === 'focus' && cell.matches(':focus-visible');
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
}
|
|
30
|
+
this.toggleState('focus-visible', shown);
|
|
31
|
+
};
|
|
21
32
|
}
|
|
22
33
|
get words() {
|
|
23
34
|
return this.childrenKnown ? slottedWords(this, ['']) : '';
|
|
@@ -56,7 +67,8 @@ let HelixHeadlessListItem = class HelixHeadlessListItem extends HelixElement {
|
|
|
56
67
|
super.willUpdate(changed);
|
|
57
68
|
hostAria(this.internals, { role: this.grouped ? 'row' : 'listitem' });
|
|
58
69
|
this.internals.ariaSelected = this.selected === undefined ? null : String(this.selected);
|
|
59
|
-
this.internals.ariaDisabled =
|
|
70
|
+
this.internals.ariaDisabled =
|
|
71
|
+
this.grouped && this.disabled && !this.selectionOnly ? 'true' : null;
|
|
60
72
|
this.internals.ariaCurrent =
|
|
61
73
|
this.current !== undefined && this.href === '' ? this.current : null;
|
|
62
74
|
this.toggleState('selected', this.selected === true);
|
|
@@ -91,6 +103,8 @@ let HelixHeadlessListItem = class HelixHeadlessListItem extends HelixElement {
|
|
|
91
103
|
tabindex=${tabindex}
|
|
92
104
|
aria-labelledby=${labelledby}
|
|
93
105
|
aria-describedby=${describedby}
|
|
106
|
+
@focus=${this.handleCellFocus}
|
|
107
|
+
@blur=${this.handleCellFocus}
|
|
94
108
|
>${content}<slot name="end"></slot>${this.renderGrip()}</div>`
|
|
95
109
|
: html `<div
|
|
96
110
|
part="row${this.current === undefined ? '' : ' row-current'}"
|
|
@@ -98,6 +112,8 @@ let HelixHeadlessListItem = class HelixHeadlessListItem extends HelixElement {
|
|
|
98
112
|
tabindex=${tabindex}
|
|
99
113
|
aria-labelledby=${labelledby}
|
|
100
114
|
aria-describedby=${describedby}
|
|
115
|
+
@focus=${this.handleCellFocus}
|
|
116
|
+
@blur=${this.handleCellFocus}
|
|
101
117
|
><a
|
|
102
118
|
part="link"
|
|
103
119
|
href=${this.href}
|
|
@@ -137,6 +153,9 @@ __decorate([
|
|
|
137
153
|
__decorate([
|
|
138
154
|
property({ attribute: false })
|
|
139
155
|
], HelixHeadlessListItem.prototype, "selected", void 0);
|
|
156
|
+
__decorate([
|
|
157
|
+
property({ attribute: false })
|
|
158
|
+
], HelixHeadlessListItem.prototype, "selectionOnly", void 0);
|
|
140
159
|
__decorate([
|
|
141
160
|
property()
|
|
142
161
|
], HelixHeadlessListItem.prototype, "target", void 0);
|
|
@@ -14,7 +14,9 @@ import { type ListSelectionBehavior, type ListSelectionMode } from '../selection
|
|
|
14
14
|
* `multiple` any number, and `value` holds the selected rows' values,
|
|
15
15
|
* separated by commas. A change asks with `helix-before-change` and tells
|
|
16
16
|
* with `change`. `none` selects nothing, so a press acts on the row. A
|
|
17
|
-
* `disabled` row is passed over.
|
|
17
|
+
* `disabled` row is passed over. With `disabled-behavior="selection"`, a
|
|
18
|
+
* `disabled` row cannot be selected, and is reached and acted on as any row
|
|
19
|
+
* is, as React Aria's GridList has it. Each row is named by its label and described
|
|
18
20
|
* by its description, so the words of the controls in `end` are not read as
|
|
19
21
|
* part of its name.
|
|
20
22
|
*
|
|
@@ -125,6 +127,15 @@ export declare class HelixHeadlessList extends HelixElement {
|
|
|
125
127
|
* refused, in a `reorderable` list.
|
|
126
128
|
*/
|
|
127
129
|
canceledLabel: string;
|
|
130
|
+
/**
|
|
131
|
+
* What `disabled` takes from a row, in a list that selects: `all`, which
|
|
132
|
+
* passes over the row, or `selection`, which keeps the row out of the
|
|
133
|
+
* selection and lets the keys reach it and Enter or a press act on it, as
|
|
134
|
+
* React Aria's GridList takes `disabledBehavior`.
|
|
135
|
+
*
|
|
136
|
+
* Absent is `all`.
|
|
137
|
+
*/
|
|
138
|
+
disabledBehavior?: 'all' | 'selection' | undefined;
|
|
128
139
|
/** What screen readers hear when a row is dropped, in a `reorderable` list. */
|
|
129
140
|
droppedLabel: string;
|
|
130
141
|
/**
|
|
@@ -203,6 +214,10 @@ export declare class HelixHeadlessList extends HelixElement {
|
|
|
203
214
|
* lets the arrow keys carry the selection.
|
|
204
215
|
*/
|
|
205
216
|
selectionBehavior: ListSelectionBehavior;
|
|
217
|
+
/** Whether the row is out of reach of the keys and of actions, past selection. */
|
|
218
|
+
private blocked;
|
|
219
|
+
/** The rows a key can land on that can also be selected. */
|
|
220
|
+
private get selectable();
|
|
206
221
|
/**
|
|
207
222
|
* Makes the list interactive: `single` selects one row, `multiple` any
|
|
208
223
|
* number, and `none` moves and acts without selecting. Unset is a list a
|
|
@@ -235,6 +250,8 @@ export declare class HelixHeadlessList extends HelixElement {
|
|
|
235
250
|
value: string;
|
|
236
251
|
/** The row the reader was last on, which keeps the tab stop. */
|
|
237
252
|
private last;
|
|
253
|
+
/** The rows in order on the last `sync`, so a removed row's place is known. */
|
|
254
|
+
private placed;
|
|
238
255
|
/** The value of the row a Shift range runs from: the last row pressed. */
|
|
239
256
|
private anchor;
|
|
240
257
|
/**
|
|
@@ -295,6 +312,17 @@ export declare class HelixHeadlessList extends HelixElement {
|
|
|
295
312
|
* row a server rendered draws its served markup first.
|
|
296
313
|
*/
|
|
297
314
|
private sync;
|
|
315
|
+
/**
|
|
316
|
+
* Moves the tab stop off a row the page removed, to the next row a key can
|
|
317
|
+
* reach or, at the end, the one before it, and moves focus there where
|
|
318
|
+
* removing the row dropped it to the body.
|
|
319
|
+
*
|
|
320
|
+
* **Focus went to the body**, and the arrow keys did nothing until the
|
|
321
|
+
* reader tabbed back into the list. React Aria's GridList moves focus to a
|
|
322
|
+
* neighbor the same way. Focus anywhere but the body is where you put it,
|
|
323
|
+
* and is left there.
|
|
324
|
+
*/
|
|
325
|
+
private followRemoved;
|
|
298
326
|
/** The row a press or a key came from, read off the event's path. */
|
|
299
327
|
private rowOf;
|
|
300
328
|
/**
|
|
@@ -27,6 +27,7 @@ let HelixHeadlessList = class HelixHeadlessList extends HelixElement {
|
|
|
27
27
|
this.reversed = false;
|
|
28
28
|
this.selectionBehavior = 'toggle';
|
|
29
29
|
this.value = '';
|
|
30
|
+
this.placed = [];
|
|
30
31
|
this.keys = new KeyboardNavigationController(this, {
|
|
31
32
|
items: () => this.reachable.map((row) => this.navigable(row)),
|
|
32
33
|
origin: 'focus',
|
|
@@ -67,12 +68,12 @@ let HelixHeadlessList = class HelixHeadlessList extends HelixElement {
|
|
|
67
68
|
};
|
|
68
69
|
this.handleKeydown = (event) => {
|
|
69
70
|
const row = this.interactive ? this.rowOf(event) : undefined;
|
|
70
|
-
if (row && !row
|
|
71
|
+
if (row && !this.blocked(row) && !event.altKey && /^Page(Down|Up)$/.test(event.key)) {
|
|
71
72
|
event.preventDefault();
|
|
72
73
|
this.page(row, event.key === 'PageDown' ? 1 : -1);
|
|
73
74
|
}
|
|
74
75
|
this.moving = undefined;
|
|
75
|
-
if (!row || row
|
|
76
|
+
if (!row || this.blocked(row) || event.altKey) {
|
|
76
77
|
return;
|
|
77
78
|
}
|
|
78
79
|
const toggle = event.ctrlKey || event.metaKey;
|
|
@@ -85,7 +86,7 @@ let HelixHeadlessList = class HelixHeadlessList extends HelixElement {
|
|
|
85
86
|
}
|
|
86
87
|
if (selectsAll(event) && this.selectionMode === 'multiple') {
|
|
87
88
|
event.preventDefault();
|
|
88
|
-
const every = this.
|
|
89
|
+
const every = this.selectable.map((one) => this.valueFor(one)).join(',');
|
|
89
90
|
this.choose(every === this.value ? null : every);
|
|
90
91
|
}
|
|
91
92
|
else if (event.key === 'Escape' && this.value !== '' && this.selectionMode !== 'none') {
|
|
@@ -111,7 +112,7 @@ let HelixHeadlessList = class HelixHeadlessList extends HelixElement {
|
|
|
111
112
|
if (pressedOwnControl(event, cell && event.composedPath().includes(cell) ? cell : row)) {
|
|
112
113
|
return;
|
|
113
114
|
}
|
|
114
|
-
if (this.selectionMode === 'none') {
|
|
115
|
+
if (this.selectionMode === 'none' || row.disabled) {
|
|
115
116
|
this.act(row);
|
|
116
117
|
}
|
|
117
118
|
else {
|
|
@@ -139,6 +140,12 @@ let HelixHeadlessList = class HelixHeadlessList extends HelixElement {
|
|
|
139
140
|
};
|
|
140
141
|
}
|
|
141
142
|
static { this.words = 'helix-list'; }
|
|
143
|
+
blocked(row) {
|
|
144
|
+
return row.disabled && this.disabledBehavior !== 'selection';
|
|
145
|
+
}
|
|
146
|
+
get selectable() {
|
|
147
|
+
return this.reachable.filter((row) => !row.disabled);
|
|
148
|
+
}
|
|
142
149
|
get interactive() {
|
|
143
150
|
return this.selectionMode !== undefined;
|
|
144
151
|
}
|
|
@@ -146,11 +153,11 @@ let HelixHeadlessList = class HelixHeadlessList extends HelixElement {
|
|
|
146
153
|
return this.knownChildren.filter((child) => child instanceof HelixHeadlessListItem);
|
|
147
154
|
}
|
|
148
155
|
get reachable() {
|
|
149
|
-
return this.interactive ? this.rows.filter((row) => !row
|
|
156
|
+
return this.interactive ? this.rows.filter((row) => !this.blocked(row)) : [];
|
|
150
157
|
}
|
|
151
158
|
navigable(row) {
|
|
152
159
|
return focusItemOf(row, {
|
|
153
|
-
disabled: row
|
|
160
|
+
disabled: this.blocked(row),
|
|
154
161
|
focus: () => row.focusItem(),
|
|
155
162
|
words: row.words,
|
|
156
163
|
});
|
|
@@ -210,20 +217,41 @@ let HelixHeadlessList = class HelixHeadlessList extends HelixElement {
|
|
|
210
217
|
const mode = this.selectionMode;
|
|
211
218
|
const chosen = new Set(commaList(this.value));
|
|
212
219
|
const rows = this.rows;
|
|
220
|
+
if (mode) {
|
|
221
|
+
this.followRemoved(rows);
|
|
222
|
+
}
|
|
223
|
+
this.placed = rows;
|
|
213
224
|
const holder = mode
|
|
214
225
|
? rovingStop(rows, this.last, {
|
|
215
|
-
disabled: (row) => row
|
|
226
|
+
disabled: (row) => this.blocked(row),
|
|
216
227
|
selected: (row) => chosen.has(this.valueFor(row)),
|
|
217
228
|
})
|
|
218
229
|
: undefined;
|
|
219
230
|
for (const row of rows) {
|
|
220
231
|
whenHydrated(row, () => {
|
|
221
232
|
row.focusable = mode ? row === holder : undefined;
|
|
233
|
+
row.selectionOnly = this.disabledBehavior === 'selection';
|
|
222
234
|
row.selected =
|
|
223
235
|
mode === undefined || mode === 'none' ? undefined : chosen.has(this.valueFor(row));
|
|
224
236
|
});
|
|
225
237
|
}
|
|
226
238
|
}
|
|
239
|
+
followRemoved(rows) {
|
|
240
|
+
const last = this.last;
|
|
241
|
+
const at = last ? this.placed.indexOf(last) : -1;
|
|
242
|
+
if (!last || at < 0 || rows.includes(last)) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
const present = new Set(rows);
|
|
246
|
+
const reachable = (row) => present.has(row) && !this.blocked(row);
|
|
247
|
+
const next = this.placed.slice(at + 1).find(reachable) ??
|
|
248
|
+
this.placed.slice(0, at).reverse().find(reachable);
|
|
249
|
+
this.last = next;
|
|
250
|
+
const active = deepActiveElement();
|
|
251
|
+
if (next && (active === null || active === document.body)) {
|
|
252
|
+
whenHydrated(next, () => next.focusItem());
|
|
253
|
+
}
|
|
254
|
+
}
|
|
227
255
|
rowOf(event) {
|
|
228
256
|
return event
|
|
229
257
|
.composedPath()
|
|
@@ -245,7 +273,7 @@ let HelixHeadlessList = class HelixHeadlessList extends HelixElement {
|
|
|
245
273
|
extend,
|
|
246
274
|
mode: this.selectionMode ?? 'none',
|
|
247
275
|
pressed: this.valueFor(row),
|
|
248
|
-
shown: this.
|
|
276
|
+
shown: this.selectable.map((one) => this.valueFor(one)),
|
|
249
277
|
toggle,
|
|
250
278
|
value: this.value,
|
|
251
279
|
};
|
|
@@ -263,7 +291,10 @@ let HelixHeadlessList = class HelixHeadlessList extends HelixElement {
|
|
|
263
291
|
}
|
|
264
292
|
arrive(row, from) {
|
|
265
293
|
const moving = this.moving;
|
|
266
|
-
if (!moving ||
|
|
294
|
+
if (!moving ||
|
|
295
|
+
this.selectionMode === undefined ||
|
|
296
|
+
this.selectionMode === 'none' ||
|
|
297
|
+
row.disabled) {
|
|
267
298
|
return;
|
|
268
299
|
}
|
|
269
300
|
if (moving.extend && this.anchor === undefined && from) {
|
|
@@ -287,7 +318,7 @@ let HelixHeadlessList = class HelixHeadlessList extends HelixElement {
|
|
|
287
318
|
}
|
|
288
319
|
}
|
|
289
320
|
act(row) {
|
|
290
|
-
if (row
|
|
321
|
+
if (this.blocked(row)) {
|
|
291
322
|
return;
|
|
292
323
|
}
|
|
293
324
|
if (row.href !== '') {
|
|
@@ -420,6 +451,9 @@ __decorate([
|
|
|
420
451
|
words,
|
|
421
452
|
property({ attribute: 'canceled-label' })
|
|
422
453
|
], HelixHeadlessList.prototype, "canceledLabel", void 0);
|
|
454
|
+
__decorate([
|
|
455
|
+
property({ attribute: 'disabled-behavior' })
|
|
456
|
+
], HelixHeadlessList.prototype, "disabledBehavior", void 0);
|
|
423
457
|
__decorate([
|
|
424
458
|
words,
|
|
425
459
|
property({ attribute: 'dropped-label' })
|
|
@@ -14,7 +14,9 @@ declare const HelixHeadlessListbox_base: (new (...args: any[]) => import("@fusio
|
|
|
14
14
|
* With `multiple` set, `value` is a comma-separated string, like
|
|
15
15
|
* `Checkbox.Group`'s, because `MixinControllerValue` holds a string. The form
|
|
16
16
|
* receives one entry per selected option, as a native `<select multiple>` sends
|
|
17
|
-
* it, so your server does not split on commas.
|
|
17
|
+
* it, so your server does not split on commas. An option's value may hold a
|
|
18
|
+
* comma: a run of the string that is an option's value is read as that one
|
|
19
|
+
* value, and the form sends it as one entry.
|
|
18
20
|
*
|
|
19
21
|
* Selected and active are separate. The selected option is the value. The
|
|
20
22
|
* active option is where the keyboard is: the arrow keys move it, and
|
|
@@ -185,6 +187,16 @@ export declare class HelixHeadlessListbox extends HelixHeadlessListbox_base {
|
|
|
185
187
|
* Placeholder text for the filter field.
|
|
186
188
|
*/
|
|
187
189
|
filterLabel: string;
|
|
190
|
+
/**
|
|
191
|
+
* Whether an option is kept by the filter: a function given the option's
|
|
192
|
+
* words and the filter, trimmed, that returns `true` to keep it showing, as
|
|
193
|
+
* React Aria's `Autocomplete` takes `filter`. For matching that ignores
|
|
194
|
+
* accents, matches the start of each word, or matches out of order.
|
|
195
|
+
*
|
|
196
|
+
* Absent, an option is kept where its words hold the filter, in any case. A
|
|
197
|
+
* property only, there being no way to write a function in markup.
|
|
198
|
+
*/
|
|
199
|
+
filterMatch?: ((text: string, filter: string) => boolean) | undefined;
|
|
188
200
|
/**
|
|
189
201
|
* The options as data, for a list too long to draw whole: each a value,
|
|
190
202
|
* which is also its words, or a record with a `value`, a `label` and
|
|
@@ -447,7 +459,12 @@ export declare class HelixHeadlessListbox extends HelixHeadlessListbox_base {
|
|
|
447
459
|
private get elements();
|
|
448
460
|
/** The options a filter has left showing. */
|
|
449
461
|
private get shown();
|
|
450
|
-
/**
|
|
462
|
+
/**
|
|
463
|
+
* The chosen values, as an array whether or not `multiple` is set.
|
|
464
|
+
*
|
|
465
|
+
* Read against the options' values, so an option whose value holds a comma
|
|
466
|
+
* is one value. See `chosenValues`.
|
|
467
|
+
*/
|
|
451
468
|
get values(): string[];
|
|
452
469
|
/**
|
|
453
470
|
* Applies a choice, announcing first so a listener can refuse it.
|