@fusion.dev/helix-headless 0.208.1 → 0.210.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/api.json +1 -1
- package/custom-elements.json +1 -1
- package/dist/canvas/root/canvas-root.d.ts +4 -1
- package/dist/element-events.generated.js +9 -0
- package/dist/graph/root/graph-root.d.ts +11 -10
- package/dist/graph/root/graph-root.js +8 -18
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/legend/root/legend-root.d.ts +9 -15
- package/dist/legend/root/legend-root.js +10 -20
- package/dist/map/root/map-root.d.ts +4 -2
- package/dist/map/root/map-root.js +1 -1
- package/dist/terminal/index.d.ts +3 -0
- package/dist/terminal/index.js +1 -0
- package/dist/terminal/keys.d.ts +14 -0
- package/dist/terminal/keys.js +20 -0
- package/dist/terminal/root/terminal-root.d.ts +489 -0
- package/dist/terminal/root/terminal-root.js +710 -0
- package/dist/toolbar/root/toolbar-root.d.ts +9 -78
- package/dist/toolbar/root/toolbar-root.js +10 -224
- package/examples.json +1 -1
- package/lint.json +1 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +923 -10
- package/types/svelte.d.ts +1 -0
- package/types/vue.d.ts +223 -4
- package/types/words.d.ts +112 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AriaForwardController, HelixElement } from '@fusion.dev/helix-core';
|
|
1
|
+
import { AriaForwardController, HelixElement, ToolbarController } from '@fusion.dev/helix-core';
|
|
2
2
|
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
3
3
|
/**
|
|
4
4
|
* @summary A group of controls that share one tab stop.
|
|
@@ -55,6 +55,10 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
55
55
|
* tab stop while the toolbar's is elsewhere, so Tab stops once in the
|
|
56
56
|
* toolbar. A composite without one keeps its own tab stop.
|
|
57
57
|
*
|
|
58
|
+
* A `<slot>` in the toolbar counts as the controls assigned to it, for a
|
|
59
|
+
* toolbar a component draws in its own shadow root with your controls passed
|
|
60
|
+
* into it: they share its tab stop and its arrows with the component's own.
|
|
61
|
+
*
|
|
58
62
|
* A toolbar inside another toolbar, a Helix one or the page's own
|
|
59
63
|
* `role="toolbar"`, is a `group` of the outer one, as React Aria's is: the
|
|
60
64
|
* outer toolbar's arrows walk through its controls and on past them, and it
|
|
@@ -90,13 +94,6 @@ export declare class HelixHeadlessToolbar extends HelixElement {
|
|
|
90
94
|
label: string;
|
|
91
95
|
/** Carries a host `aria-label` and `aria-labelledby` to the toolbar. */
|
|
92
96
|
protected readonly aria: AriaForwardController;
|
|
93
|
-
/**
|
|
94
|
-
* Whether the stop has been written onto controls this package does not
|
|
95
|
-
* know, which happens on the first focus. From then on, every move of the
|
|
96
|
-
* stop has to write them too, or the one at `0` can be removed or disabled
|
|
97
|
-
* and leave every other one at `-1`.
|
|
98
|
-
*/
|
|
99
|
-
private wroteForeign;
|
|
100
97
|
/**
|
|
101
98
|
* The direction the arrow keys move focus in.
|
|
102
99
|
*/
|
|
@@ -106,67 +103,12 @@ export declare class HelixHeadlessToolbar extends HelixElement {
|
|
|
106
103
|
* wrap around from one end to the other.
|
|
107
104
|
*/
|
|
108
105
|
stopAtEnds: boolean;
|
|
109
|
-
private watcher;
|
|
110
|
-
/**
|
|
111
|
-
* The children that take part in the toolbar, in document order, each with
|
|
112
|
-
* the elements in it that take focus.
|
|
113
|
-
*
|
|
114
|
-
* A test on the element rather than a list of tag names: a toolbar holds
|
|
115
|
-
* anything, including elements this package has never heard of. A child
|
|
116
|
-
* that takes no focus and holds nothing that does is skipped. Client only,
|
|
117
|
-
* there being no tree to walk on the server.
|
|
118
|
-
*/
|
|
119
|
-
private get controls();
|
|
120
|
-
/** The elements in a child's light DOM that take focus. */
|
|
121
|
-
private within;
|
|
122
|
-
/**
|
|
123
|
-
* The stops of a child that takes focus itself: the child, or the members of
|
|
124
|
-
* a composite that owns its tab stop, such as a toggle group, which the
|
|
125
|
-
* toolbar enters at a member and moves the stop of through `focusable`.
|
|
126
|
-
*/
|
|
127
|
-
private inside;
|
|
128
|
-
/**
|
|
129
|
-
* Where focus lands on arriving at a control. A nested composite is entered
|
|
130
|
-
* at the stop it holds, which for a toggle group is the chosen toggle.
|
|
131
|
-
*/
|
|
132
|
-
private entry;
|
|
133
|
-
/** The control a press or a focus came from, by its path. */
|
|
134
|
-
private origin;
|
|
135
|
-
/**
|
|
136
|
-
* Moves the single tab stop to one control.
|
|
137
|
-
*
|
|
138
|
-
* Prefers the control's own `focusable` property to writing `tabindex` on it.
|
|
139
|
-
* The attribute is a change to markup the consumer rendered, which a React
|
|
140
|
-
* consumer's hydration reports and does not patch up; a control that offers
|
|
141
|
-
* the property moves the stop inside its own shadow root instead.
|
|
142
|
-
*
|
|
143
|
-
* `tabindex` stays as the fallback, because a toolbar has to hold controls
|
|
144
|
-
* this package has never heard of.
|
|
145
|
-
*/
|
|
146
|
-
private setTabStop;
|
|
147
106
|
/**
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
* that ran first would find no `focusable`, take the `tabindex` fallback, and
|
|
152
|
-
* write the very attribute this exists to avoid.
|
|
107
|
+
* The one tab stop and the arrow keys, shared with every toolbar a
|
|
108
|
+
* component draws: the controls are the children, a slot among them read as
|
|
109
|
+
* the elements assigned to it.
|
|
153
110
|
*/
|
|
154
|
-
|
|
155
|
-
private readonly handleFocusIn;
|
|
156
|
-
/**
|
|
157
|
-
* Gives the tab stop back to a control that still exists.
|
|
158
|
-
*
|
|
159
|
-
* `controls` excludes anything disabled or hidden, so disabling the control
|
|
160
|
-
* holding the stop drops it out of the list while every other control is
|
|
161
|
-
* already at `-1`, leaving a toolbar the keyboard cannot reach at all. A Save
|
|
162
|
-
* button disabling itself once the form is clean is enough to do it.
|
|
163
|
-
*
|
|
164
|
-
* `foreign` stays false, the same as the first claim, until a focus has
|
|
165
|
-
* written `tabindex="-1"` onto foreign controls. From then on `setTabStop`
|
|
166
|
-
* writes them as well, so removing or disabling the native button that held
|
|
167
|
-
* the stop hands it to the first one left.
|
|
168
|
-
*/
|
|
169
|
-
private readonly restoreTabStop;
|
|
111
|
+
protected readonly tabStop: ToolbarController;
|
|
170
112
|
/**
|
|
171
113
|
* Whether this toolbar sits inside another, which then owns the keys and the
|
|
172
114
|
* tab stop: a toolbar inside a toolbar held the reader's arrows inside it.
|
|
@@ -181,18 +123,7 @@ export declare class HelixHeadlessToolbar extends HelixElement {
|
|
|
181
123
|
* so a reflected default cannot appear on the host after hydration.
|
|
182
124
|
*/
|
|
183
125
|
private get axis();
|
|
184
|
-
/**
|
|
185
|
-
* The toolbar's own keys, from inside a nested composite.
|
|
186
|
-
*
|
|
187
|
-
* In capture, ahead of the composite, which answers every arrow itself and
|
|
188
|
-
* would otherwise hold the reader inside it. Only the keys along the
|
|
189
|
-
* toolbar's axis and Home and End are taken, so the arrows across it still
|
|
190
|
-
* move inside the composite.
|
|
191
|
-
*/
|
|
192
|
-
private readonly handleCompositeKeydown;
|
|
193
|
-
private readonly handleKeydown;
|
|
194
126
|
connectedCallback(): void;
|
|
195
|
-
disconnectedCallback(): void;
|
|
196
127
|
protected firstUpdated(changed: PropertyValues): void;
|
|
197
128
|
protected willUpdate(changed: PropertyValues): void;
|
|
198
129
|
protected render(): TemplateResult;
|
|
@@ -1,44 +1,8 @@
|
|
|
1
1
|
var HelixHeadlessToolbar_1;
|
|
2
2
|
import { __decorate } from "tslib";
|
|
3
|
-
import { AriaForwardController, closestInstance, customElement, HelixElement,
|
|
3
|
+
import { AriaForwardController, closestInstance, customElement, HelixElement, ToolbarController, toolbarAround, words, } from '@fusion.dev/helix-core';
|
|
4
4
|
import { html, nothing } from 'lit';
|
|
5
5
|
import { property } from 'lit/decorators.js';
|
|
6
|
-
const KEYS = new Set(['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End']);
|
|
7
|
-
const ownsTabStop = (node) => 'focusable' in node;
|
|
8
|
-
const NATIVE = 'a[href], button, input, select, textarea, [tabindex], [contenteditable=""], [contenteditable="true"]';
|
|
9
|
-
const TABBABLE = NATIVE.replace('[tabindex]', '[tabindex]:not([tabindex="-1"])');
|
|
10
|
-
const innerStops = new WeakMap();
|
|
11
|
-
const takesFocus = (node) => {
|
|
12
|
-
if (!(node instanceof HTMLElement) ||
|
|
13
|
-
node.hasAttribute('disabled') ||
|
|
14
|
-
node.getAttribute('aria-hidden') === 'true') {
|
|
15
|
-
return false;
|
|
16
|
-
}
|
|
17
|
-
return (node.matches(NATIVE) ||
|
|
18
|
-
ownsTabStop(node) ||
|
|
19
|
-
node.shadowRoot?.delegatesFocus === true ||
|
|
20
|
-
node.shadowRoot?.querySelector(TABBABLE) != null ||
|
|
21
|
-
innerStops.get(node)?.isConnected === true);
|
|
22
|
-
};
|
|
23
|
-
const stopOf = (node) => {
|
|
24
|
-
if (node.hasAttribute('tabindex')) {
|
|
25
|
-
return node;
|
|
26
|
-
}
|
|
27
|
-
const held = innerStops.get(node);
|
|
28
|
-
if (held?.isConnected && held.getRootNode() === node.shadowRoot) {
|
|
29
|
-
return held;
|
|
30
|
-
}
|
|
31
|
-
const inner = node.localName.includes('-')
|
|
32
|
-
? node.shadowRoot?.querySelector(TABBABLE)
|
|
33
|
-
: null;
|
|
34
|
-
if (!inner) {
|
|
35
|
-
return node;
|
|
36
|
-
}
|
|
37
|
-
innerStops.set(node, inner);
|
|
38
|
-
return inner;
|
|
39
|
-
};
|
|
40
|
-
const flattens = (node) => ownsTabStop(node) &&
|
|
41
|
-
node.selectionMode === 'multiple';
|
|
42
6
|
let HelixHeadlessToolbar = class HelixHeadlessToolbar extends HelixElement {
|
|
43
7
|
constructor() {
|
|
44
8
|
super(...arguments);
|
|
@@ -47,177 +11,16 @@ let HelixHeadlessToolbar = class HelixHeadlessToolbar extends HelixElement {
|
|
|
47
11
|
label: true,
|
|
48
12
|
target: () => this.partNamed('toolbar'),
|
|
49
13
|
});
|
|
50
|
-
this.wroteForeign = false;
|
|
51
14
|
this.stopAtEnds = false;
|
|
52
|
-
this.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
this.setTabStop(control);
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
this.restoreTabStop = () => {
|
|
62
|
-
if (this.nested) {
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
const controls = this.controls;
|
|
66
|
-
const held = controls.some((control) => {
|
|
67
|
-
if (control.group) {
|
|
68
|
-
return control.group.focusable;
|
|
69
|
-
}
|
|
70
|
-
const composite = control.stops.length !== 1;
|
|
71
|
-
const element = composite ? control.child : control.stops[0];
|
|
72
|
-
if (ownsTabStop(element)) {
|
|
73
|
-
return element.focusable;
|
|
74
|
-
}
|
|
75
|
-
return composite || stopOf(element).tabIndex === 0;
|
|
76
|
-
});
|
|
77
|
-
if (controls.length > 0 && !held) {
|
|
78
|
-
this.setTabStop(controls[0], false);
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
this.handleCompositeKeydown = (event) => {
|
|
82
|
-
if (this.nested) {
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
const control = this.origin(event);
|
|
86
|
-
if (!control || (control.stops.length === 1 && !control.group)) {
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
this.handleKeydown(event);
|
|
90
|
-
if (event.defaultPrevented) {
|
|
91
|
-
event.stopPropagation();
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
this.handleKeydown = (event) => {
|
|
95
|
-
if (this.nested || !KEYS.has(event.key) || keyClaimed(event)) {
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
const along = inlineStep(this, event.key);
|
|
99
|
-
const forward = this.axis === 'horizontal' ? along === 1 : event.key === 'ArrowDown';
|
|
100
|
-
const back = this.axis === 'horizontal' ? along === -1 : event.key === 'ArrowUp';
|
|
101
|
-
const controls = this.controls;
|
|
102
|
-
const origin = this.origin(event);
|
|
103
|
-
const current = origin ? controls.findIndex((control) => control.child === origin.child) : -1;
|
|
104
|
-
if (current === -1) {
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
let next = current;
|
|
108
|
-
const last = controls.length - 1;
|
|
109
|
-
if (forward) {
|
|
110
|
-
next = this.stopAtEnds ? Math.min(current + 1, last) : (current + 1) % controls.length;
|
|
111
|
-
}
|
|
112
|
-
else if (back) {
|
|
113
|
-
next = this.stopAtEnds
|
|
114
|
-
? Math.max(current - 1, 0)
|
|
115
|
-
: (current - 1 + controls.length) % controls.length;
|
|
116
|
-
}
|
|
117
|
-
else if (event.key === 'Home') {
|
|
118
|
-
next = 0;
|
|
119
|
-
}
|
|
120
|
-
else if (event.key === 'End') {
|
|
121
|
-
next = controls.length - 1;
|
|
122
|
-
}
|
|
123
|
-
else {
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
event.preventDefault();
|
|
127
|
-
const target = controls[next];
|
|
128
|
-
this.setTabStop(target);
|
|
129
|
-
const element = target ? this.entry(target) : undefined;
|
|
130
|
-
if (element && 'focusItem' in element && typeof element.focusItem === 'function') {
|
|
131
|
-
element.focusItem();
|
|
132
|
-
}
|
|
133
|
-
else if (element) {
|
|
134
|
-
;
|
|
135
|
-
(ownsTabStop(element) ? element : stopOf(element)).focus();
|
|
136
|
-
}
|
|
137
|
-
};
|
|
15
|
+
this.tabStop = new ToolbarController(this, {
|
|
16
|
+
arrows: () => this.axis,
|
|
17
|
+
members: () => [...this.children],
|
|
18
|
+
nested: () => this.nested,
|
|
19
|
+
stopAtEnds: () => this.stopAtEnds,
|
|
20
|
+
});
|
|
138
21
|
}
|
|
139
22
|
static { HelixHeadlessToolbar_1 = this; }
|
|
140
23
|
static { this.words = 'helix-toolbar'; }
|
|
141
|
-
get controls() {
|
|
142
|
-
const controls = [];
|
|
143
|
-
for (const child of this.children) {
|
|
144
|
-
if (!(child instanceof HTMLElement) ||
|
|
145
|
-
child.hasAttribute('disabled') ||
|
|
146
|
-
child.getAttribute('aria-hidden') === 'true') {
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
if (takesFocus(child)) {
|
|
150
|
-
const stops = this.inside(child);
|
|
151
|
-
if (stops.length > 1 && flattens(child)) {
|
|
152
|
-
for (const stop of stops) {
|
|
153
|
-
controls.push({ child: stop, group: child, stops: [stop] });
|
|
154
|
-
}
|
|
155
|
-
continue;
|
|
156
|
-
}
|
|
157
|
-
controls.push({ child, stops });
|
|
158
|
-
continue;
|
|
159
|
-
}
|
|
160
|
-
for (const stop of this.within(child)) {
|
|
161
|
-
controls.push({ child: stop, stops: [stop] });
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
return controls;
|
|
165
|
-
}
|
|
166
|
-
within(child) {
|
|
167
|
-
return [...child.querySelectorAll('*')].filter(takesFocus);
|
|
168
|
-
}
|
|
169
|
-
inside(child) {
|
|
170
|
-
if (!ownsTabStop(child)) {
|
|
171
|
-
return [child];
|
|
172
|
-
}
|
|
173
|
-
const members = this.within(child);
|
|
174
|
-
return members.length > 1 ? members : [child];
|
|
175
|
-
}
|
|
176
|
-
entry(control) {
|
|
177
|
-
const { stops } = control;
|
|
178
|
-
if (stops.length === 1) {
|
|
179
|
-
return stops[0];
|
|
180
|
-
}
|
|
181
|
-
return (stops.find((stop) => (ownsTabStop(stop) && stop.focusable) ||
|
|
182
|
-
stop.selected === true) ?? stops[0]);
|
|
183
|
-
}
|
|
184
|
-
origin(event) {
|
|
185
|
-
const path = event.composedPath();
|
|
186
|
-
return this.controls.find((control) => control.stops.some((stop) => path.includes(stop)));
|
|
187
|
-
}
|
|
188
|
-
setTabStop(active, foreign = true) {
|
|
189
|
-
if (foreign) {
|
|
190
|
-
this.wroteForeign = true;
|
|
191
|
-
}
|
|
192
|
-
for (const control of this.controls) {
|
|
193
|
-
if (control.group) {
|
|
194
|
-
control.group.focusable = control.group === active?.group;
|
|
195
|
-
continue;
|
|
196
|
-
}
|
|
197
|
-
if (control.stops.length !== 1) {
|
|
198
|
-
if (ownsTabStop(control.child)) {
|
|
199
|
-
control.child.focusable = control.child === active?.child;
|
|
200
|
-
}
|
|
201
|
-
continue;
|
|
202
|
-
}
|
|
203
|
-
const element = control.stops[0];
|
|
204
|
-
const stop = control.child === active?.child;
|
|
205
|
-
if (ownsTabStop(element)) {
|
|
206
|
-
element.focusable = stop;
|
|
207
|
-
}
|
|
208
|
-
else if (foreign || this.wroteForeign) {
|
|
209
|
-
stopOf(element).tabIndex = stop ? 0 : -1;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
async claimTabStop() {
|
|
214
|
-
if (this.nested) {
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
const pending = [...new Set([...this.children].map((child) => child.localName))].filter((name) => name.includes('-'));
|
|
218
|
-
await Promise.all(pending.map((name) => customElements.whenDefined(name)));
|
|
219
|
-
this.setTabStop(this.controls[0], false);
|
|
220
|
-
}
|
|
221
24
|
get nested() {
|
|
222
25
|
const parent = this.parentElement;
|
|
223
26
|
if (!parent) {
|
|
@@ -231,27 +34,10 @@ let HelixHeadlessToolbar = class HelixHeadlessToolbar extends HelixElement {
|
|
|
231
34
|
connectedCallback() {
|
|
232
35
|
super.connectedCallback();
|
|
233
36
|
this.requestUpdate();
|
|
234
|
-
this.addEventListener('keydown', this.handleCompositeKeydown, true);
|
|
235
|
-
this.addEventListener('keydown', this.handleKeydown);
|
|
236
|
-
this.addEventListener('focusin', this.handleFocusIn);
|
|
237
|
-
this.watcher = new MutationObserver(this.restoreTabStop);
|
|
238
|
-
this.watcher.observe(this, {
|
|
239
|
-
attributeFilter: ['disabled', 'aria-hidden'],
|
|
240
|
-
attributes: true,
|
|
241
|
-
subtree: true,
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
disconnectedCallback() {
|
|
245
|
-
super.disconnectedCallback();
|
|
246
|
-
this.removeEventListener('keydown', this.handleCompositeKeydown, true);
|
|
247
|
-
this.removeEventListener('keydown', this.handleKeydown);
|
|
248
|
-
this.removeEventListener('focusin', this.handleFocusIn);
|
|
249
|
-
this.watcher?.disconnect();
|
|
250
|
-
this.watcher = undefined;
|
|
251
37
|
}
|
|
252
38
|
firstUpdated(changed) {
|
|
253
39
|
super.firstUpdated(changed);
|
|
254
|
-
void this.
|
|
40
|
+
void this.tabStop.claim();
|
|
255
41
|
}
|
|
256
42
|
willUpdate(changed) {
|
|
257
43
|
super.willUpdate(changed);
|
|
@@ -259,7 +45,7 @@ let HelixHeadlessToolbar = class HelixHeadlessToolbar extends HelixElement {
|
|
|
259
45
|
this.toggleState('horizontal', this.axis === 'horizontal');
|
|
260
46
|
this.toggleState('vertical', this.axis === 'vertical');
|
|
261
47
|
if (this.hasUpdated && changed.has('orientation')) {
|
|
262
|
-
for (const child of this.
|
|
48
|
+
for (const child of this.tabStop.members) {
|
|
263
49
|
;
|
|
264
50
|
child.requestUpdate?.();
|
|
265
51
|
}
|
|
@@ -273,7 +59,7 @@ let HelixHeadlessToolbar = class HelixHeadlessToolbar extends HelixElement {
|
|
|
273
59
|
aria-label=${this.label}
|
|
274
60
|
aria-orientation=${nested ? nothing : this.axis}
|
|
275
61
|
>
|
|
276
|
-
<slot @slotchange=${() => this.
|
|
62
|
+
<slot @slotchange=${() => this.tabStop.claim()}></slot>
|
|
277
63
|
</div>`;
|
|
278
64
|
}
|
|
279
65
|
};
|