@fusion.dev/helix-headless 0.172.0 → 0.174.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 +912 -60
- package/dist/button-group/root/button-group-root.d.ts +30 -0
- package/dist/button-group/root/button-group-root.js +50 -4
- package/dist/canvas/root/canvas-root.d.ts +7 -0
- package/dist/canvas/root/canvas-root.js +7 -0
- package/dist/copy-button/root/copy-button-root.d.ts +43 -3
- package/dist/copy-button/root/copy-button-root.js +49 -3
- package/dist/element-events.generated.js +2 -0
- package/dist/graph/root/graph-root.d.ts +22 -0
- package/dist/graph/root/graph-root.js +21 -5
- package/dist/list/root/list-root.d.ts +6 -1
- package/dist/list/root/list-root.js +3 -2
- package/dist/outline/root/outline-root.d.ts +57 -0
- package/dist/outline/root/outline-root.js +98 -29
- package/dist/speed-dial/root/speed-dial-root.d.ts +14 -0
- package/dist/speed-dial/root/speed-dial-root.js +34 -2
- package/dist/splitter/root/splitter-root.d.ts +25 -2
- package/dist/splitter/root/splitter-root.js +19 -4
- package/dist/tag/group/tag-group.d.ts +8 -1
- package/dist/tag/group/tag-group.js +8 -0
- package/dist/tag/root/tag-root.d.ts +12 -0
- package/dist/tag/root/tag-root.js +12 -0
- package/dist/time-picker/root/time-picker-root.d.ts +14 -0
- package/dist/time-picker/root/time-picker-root.js +8 -1
- package/dist/timeline/item/timeline-item.d.ts +5 -1
- package/dist/timeline/item/timeline-item.js +9 -0
- package/dist/timeline/root/timeline-root.d.ts +23 -0
- package/dist/timeline/root/timeline-root.js +17 -3
- package/dist/tooltip/root/tooltip-root.d.ts +10 -0
- package/dist/tooltip/root/tooltip-root.js +14 -2
- package/dist/virtual-list/reorder/index.d.ts +1 -0
- package/dist/virtual-list/reorder/index.js +4 -0
- package/dist/virtual-list/reorder/virtual-list-reorder.d.ts +86 -0
- package/dist/virtual-list/reorder/virtual-list-reorder.js +193 -0
- package/dist/virtual-list/root/virtual-list-root.d.ts +49 -11
- package/dist/virtual-list/root/virtual-list-root.js +10 -1
- package/lint.json +1 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +568 -18
- package/types/vue.d.ts +115 -11
- package/types/words.d.ts +14 -0
|
@@ -40,6 +40,22 @@ import { type Branch } from '../levels.js';
|
|
|
40
40
|
* headings are far apart, none is in the band and no section is marked, and
|
|
41
41
|
* when they are close together, several are in the band at once.
|
|
42
42
|
*
|
|
43
|
+
* **Several sections at once.** `marks="visible"` marks the link of every
|
|
44
|
+
* section with part of it on screen, from the reading line to the bottom of
|
|
45
|
+
* what the sections scroll in, as Zag's table of contents marks its active
|
|
46
|
+
* ids. The mark then runs from the first of those links to the last, and
|
|
47
|
+
* `marked` lists their ids in page order.
|
|
48
|
+
*
|
|
49
|
+
* **Going to a section from code.** `scrollToSection(id)` scrolls to the
|
|
50
|
+
* section as a press on its link does, with `behavior`, and marks its link at
|
|
51
|
+
* once.
|
|
52
|
+
*
|
|
53
|
+
* **Styling a link by its state.** Each link the outline finds carries
|
|
54
|
+
* `data-depth`, its depth in the outline from `1`, nested lists and
|
|
55
|
+
* `data-level` alike, and `aria-current="location"` while its section is
|
|
56
|
+
* marked, so `a[aria-current]` and `a[data-depth="2"]` are the hooks for a
|
|
57
|
+
* rule of yours.
|
|
58
|
+
*
|
|
43
59
|
* @since 0.1.0
|
|
44
60
|
* @status stable
|
|
45
61
|
* @category navigation
|
|
@@ -109,6 +125,12 @@ export declare class HelixHeadlessOutline extends HelixElement {
|
|
|
109
125
|
* slot names it in place of this.
|
|
110
126
|
*/
|
|
111
127
|
label: string;
|
|
128
|
+
/**
|
|
129
|
+
* Which links are marked. `reading` marks the one section being read, the
|
|
130
|
+
* last whose heading has passed the reading line. `visible` marks every
|
|
131
|
+
* section with part of it on screen, and the mark runs across their links.
|
|
132
|
+
*/
|
|
133
|
+
marks: 'reading' | 'visible';
|
|
112
134
|
/**
|
|
113
135
|
* How far below the top of the scroll container a heading counts as reached,
|
|
114
136
|
* in pixels. The scroll container is the window or the panel the sections
|
|
@@ -130,6 +152,14 @@ export declare class HelixHeadlessOutline extends HelixElement {
|
|
|
130
152
|
* than saying it in a sentence nobody reads.
|
|
131
153
|
*/
|
|
132
154
|
get current(): string;
|
|
155
|
+
/**
|
|
156
|
+
* The ids of the sections whose links are marked, in page order: the one
|
|
157
|
+
* `current` names, or under `marks="visible"` every section on screen.
|
|
158
|
+
* Read-only, for the reason `current` gives.
|
|
159
|
+
*/
|
|
160
|
+
get marked(): string[];
|
|
161
|
+
/** What `marked` reports, which only `markTo` writes. */
|
|
162
|
+
private markedIds;
|
|
133
163
|
/**
|
|
134
164
|
* What `current` reports, which only `mark` writes.
|
|
135
165
|
*
|
|
@@ -183,6 +213,12 @@ export declare class HelixHeadlessOutline extends HelixElement {
|
|
|
183
213
|
* wherever the page says the top of the readable area is.
|
|
184
214
|
*/
|
|
185
215
|
private mark;
|
|
216
|
+
/**
|
|
217
|
+
* The sections with part of them on screen, under `marks="visible"`: each
|
|
218
|
+
* runs from its own target to the next one's, and the last to the end of the
|
|
219
|
+
* page.
|
|
220
|
+
*/
|
|
221
|
+
private onScreen;
|
|
186
222
|
/** Where the destination is set, whether measured or asked for. */
|
|
187
223
|
private markTo;
|
|
188
224
|
/**
|
|
@@ -293,6 +329,18 @@ export declare class HelixHeadlessOutline extends HelixElement {
|
|
|
293
329
|
* handled is not ours, and `auto` is the whole point of `auto`.
|
|
294
330
|
*/
|
|
295
331
|
private readonly handleClick;
|
|
332
|
+
/**
|
|
333
|
+
* Scrolls to the section with this id, as a press on its link does, with
|
|
334
|
+
* `behavior`, and marks its link at once. The address and focus stay where
|
|
335
|
+
* they are. Returns whether the outline has a link to that section.
|
|
336
|
+
*/
|
|
337
|
+
scrollToSection(id: string): boolean;
|
|
338
|
+
/**
|
|
339
|
+
* Marks the section a reader asked for, and holds the mark there until the
|
|
340
|
+
* page stops moving. Scrolls to it where `scroll` says this element does
|
|
341
|
+
* the scrolling, and returns whether it did.
|
|
342
|
+
*/
|
|
343
|
+
private travel;
|
|
296
344
|
/**
|
|
297
345
|
* Watches every section, and re-marks whenever any of them crosses anything.
|
|
298
346
|
*
|
|
@@ -312,6 +360,15 @@ export declare class HelixHeadlessOutline extends HelixElement {
|
|
|
312
360
|
* life of the page.
|
|
313
361
|
*/
|
|
314
362
|
private readonly handleSlotChange;
|
|
363
|
+
/**
|
|
364
|
+
* Writes each link's depth in the outline, from 1, as `data-depth`: the
|
|
365
|
+
* list items around it inside the outline, or for a flat run, its place in
|
|
366
|
+
* the lists `data-level` makes of it.
|
|
367
|
+
*
|
|
368
|
+
* On the consumer's links, as `aria-current` is, and only after the first
|
|
369
|
+
* render, where a framework has hydrated them.
|
|
370
|
+
*/
|
|
371
|
+
private writeDepths;
|
|
315
372
|
disconnectedCallback(): void;
|
|
316
373
|
/**
|
|
317
374
|
* Stops waiting for a scroll to land, both halves of the wait.
|
|
@@ -17,11 +17,14 @@ let HelixHeadlessOutline = class HelixHeadlessOutline extends HelixElement {
|
|
|
17
17
|
active: () => this.links.find((link) => link.hasAttribute('aria-current')),
|
|
18
18
|
indicator: () => this.partNamed('indicator'),
|
|
19
19
|
surface: () => this.partNamed('nav'),
|
|
20
|
+
through: () => this.links.findLast((link) => link.hasAttribute('aria-current')),
|
|
20
21
|
});
|
|
21
22
|
this.behavior = 'auto';
|
|
22
23
|
this.follow = false;
|
|
23
24
|
this.label = 'On this page';
|
|
25
|
+
this.marks = 'reading';
|
|
24
26
|
this.offset = 0;
|
|
27
|
+
this.markedIds = [];
|
|
25
28
|
this.reading = '';
|
|
26
29
|
this.frame = 0;
|
|
27
30
|
this.handleScroll = () => {
|
|
@@ -42,34 +45,18 @@ let HelixHeadlessOutline = class HelixHeadlessOutline extends HelixElement {
|
|
|
42
45
|
if (!section) {
|
|
43
46
|
return;
|
|
44
47
|
}
|
|
45
|
-
this.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
this.arrive = arrive;
|
|
54
|
-
document.addEventListener('scrollend', arrive, { capture: true, once: true });
|
|
55
|
-
this.landed = setTimeout(arrive, 1200);
|
|
56
|
-
if (this.behavior === 'auto') {
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
event.preventDefault();
|
|
60
|
-
const reduced = globalThis.matchMedia?.('(prefers-reduced-motion: reduce)').matches === true;
|
|
61
|
-
section.target.scrollIntoView({
|
|
62
|
-
behavior: reduced && this.behavior === 'smooth' ? 'instant' : this.behavior,
|
|
63
|
-
block: 'start',
|
|
64
|
-
});
|
|
65
|
-
globalThis.history?.pushState(null, '', link?.getAttribute('href') ?? '');
|
|
66
|
-
const target = section.target;
|
|
67
|
-
if (!target.hasAttribute('tabindex')) {
|
|
68
|
-
target.setAttribute('tabindex', '-1');
|
|
48
|
+
if (this.travel(section, this.behavior !== 'auto')) {
|
|
49
|
+
event.preventDefault();
|
|
50
|
+
globalThis.history?.pushState(null, '', link?.getAttribute('href') ?? '');
|
|
51
|
+
const target = section.target;
|
|
52
|
+
if (!target.hasAttribute('tabindex')) {
|
|
53
|
+
target.setAttribute('tabindex', '-1');
|
|
54
|
+
}
|
|
55
|
+
target.focus({ preventScroll: true });
|
|
69
56
|
}
|
|
70
|
-
target.focus({ preventScroll: true });
|
|
71
57
|
};
|
|
72
58
|
this.handleSlotChange = () => {
|
|
59
|
+
this.writeDepths();
|
|
73
60
|
this.watch();
|
|
74
61
|
this.indicator.place();
|
|
75
62
|
};
|
|
@@ -87,6 +74,9 @@ let HelixHeadlessOutline = class HelixHeadlessOutline extends HelixElement {
|
|
|
87
74
|
get current() {
|
|
88
75
|
return this.reading;
|
|
89
76
|
}
|
|
77
|
+
get marked() {
|
|
78
|
+
return [...this.markedIds];
|
|
79
|
+
}
|
|
90
80
|
get links() {
|
|
91
81
|
const slots = [
|
|
92
82
|
...(this.renderRoot?.querySelectorAll?.('slot[data-link], nav > slot:not([role])') ?? []),
|
|
@@ -127,20 +117,34 @@ let HelixHeadlessOutline = class HelixHeadlessOutline extends HelixElement {
|
|
|
127
117
|
const origin = page ? 0 : scroller.getBoundingClientRect().top;
|
|
128
118
|
const passed = sections.filter(({ target }) => target.getBoundingClientRect().top - origin - this.offset <= 1);
|
|
129
119
|
const active = this.atEnd ? sections.at(-1) : (passed.at(-1) ?? sections[0]);
|
|
130
|
-
this.
|
|
120
|
+
const shown = this.marks === 'visible' ? this.onScreen(sections, origin, scroller) : [];
|
|
121
|
+
this.markTo(shown.length > 0 ? shown : active ? [active] : []);
|
|
131
122
|
}
|
|
132
|
-
|
|
123
|
+
onScreen(sections, origin, scroller) {
|
|
124
|
+
const line = origin + this.offset;
|
|
125
|
+
const bottom = origin + (scroller === this.page ? this.page.clientHeight : scroller.clientHeight);
|
|
126
|
+
const tops = sections.map(({ target }) => target.getBoundingClientRect().top);
|
|
127
|
+
return sections.filter((_, at) => {
|
|
128
|
+
const end = tops[at + 1] ?? Number.POSITIVE_INFINITY;
|
|
129
|
+
return (tops[at] ?? 0) < bottom - 1 && end > line + 1;
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
markTo(marked) {
|
|
133
|
+
const on = new Set(marked.map(({ target }) => target));
|
|
133
134
|
for (const { link, target } of this.sections) {
|
|
134
|
-
if (target
|
|
135
|
+
if (on.has(target)) {
|
|
135
136
|
link.setAttribute('aria-current', 'location');
|
|
136
137
|
}
|
|
137
138
|
else {
|
|
138
139
|
link.removeAttribute('aria-current');
|
|
139
140
|
}
|
|
140
141
|
}
|
|
142
|
+
const active = marked[0];
|
|
141
143
|
const next = active?.target.id ?? '';
|
|
142
|
-
|
|
144
|
+
const ids = marked.map(({ target }) => target.id);
|
|
145
|
+
if (next !== this.reading || ids.join(' ') !== this.markedIds.join(' ')) {
|
|
143
146
|
this.reading = next;
|
|
147
|
+
this.markedIds = ids;
|
|
144
148
|
this.indicator.place();
|
|
145
149
|
if (this.follow && active) {
|
|
146
150
|
this.reveal(active.link);
|
|
@@ -173,6 +177,36 @@ let HelixHeadlessOutline = class HelixHeadlessOutline extends HelixElement {
|
|
|
173
177
|
element.style.scrollMarginBlockStart = this.offset > 0 ? `${this.offset}px` : '';
|
|
174
178
|
}
|
|
175
179
|
}
|
|
180
|
+
scrollToSection(id) {
|
|
181
|
+
const section = this.sections.find(({ target }) => target.id === id);
|
|
182
|
+
if (!section) {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
this.travel(section, true);
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
travel(section, scroll) {
|
|
189
|
+
this.landing = section;
|
|
190
|
+
this.markTo([section]);
|
|
191
|
+
this.unland();
|
|
192
|
+
const arrive = () => {
|
|
193
|
+
this.unland();
|
|
194
|
+
this.landing = undefined;
|
|
195
|
+
this.mark();
|
|
196
|
+
};
|
|
197
|
+
this.arrive = arrive;
|
|
198
|
+
document.addEventListener('scrollend', arrive, { capture: true, once: true });
|
|
199
|
+
this.landed = setTimeout(arrive, 1200);
|
|
200
|
+
if (!scroll) {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
const reduced = globalThis.matchMedia?.('(prefers-reduced-motion: reduce)').matches === true;
|
|
204
|
+
section.target.scrollIntoView({
|
|
205
|
+
behavior: reduced && this.behavior === 'smooth' ? 'instant' : this.behavior,
|
|
206
|
+
block: 'start',
|
|
207
|
+
});
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
176
210
|
watch() {
|
|
177
211
|
this.observer?.disconnect();
|
|
178
212
|
if (this.sections.length === 0) {
|
|
@@ -188,6 +222,35 @@ let HelixHeadlessOutline = class HelixHeadlessOutline extends HelixElement {
|
|
|
188
222
|
this.reach();
|
|
189
223
|
this.mark();
|
|
190
224
|
}
|
|
225
|
+
writeDepths() {
|
|
226
|
+
const links = this.links;
|
|
227
|
+
const items = new Map();
|
|
228
|
+
for (const link of links) {
|
|
229
|
+
let count = 0;
|
|
230
|
+
for (let node = link.parentElement; node && node !== this; node = node.parentElement) {
|
|
231
|
+
count += node.localName === 'li' ? 1 : 0;
|
|
232
|
+
}
|
|
233
|
+
items.set(link, count);
|
|
234
|
+
}
|
|
235
|
+
const flat = links.filter((link) => items.get(link) === 0);
|
|
236
|
+
const depth = new Map();
|
|
237
|
+
const walk = (branches, at) => {
|
|
238
|
+
for (const branch of branches) {
|
|
239
|
+
const link = flat[branch.index];
|
|
240
|
+
if (link) {
|
|
241
|
+
depth.set(link, at);
|
|
242
|
+
}
|
|
243
|
+
walk(branch.children, at + 1);
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
walk(nest(flat.map((link) => levelOf(link.getAttribute('data-level')))), 1);
|
|
247
|
+
for (const link of links) {
|
|
248
|
+
const at = String(depth.get(link) ?? items.get(link) ?? 1);
|
|
249
|
+
if (link.getAttribute('data-depth') !== at) {
|
|
250
|
+
link.setAttribute('data-depth', at);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
191
254
|
disconnectedCallback() {
|
|
192
255
|
super.disconnectedCallback();
|
|
193
256
|
this.childWatch?.disconnect();
|
|
@@ -258,9 +321,12 @@ let HelixHeadlessOutline = class HelixHeadlessOutline extends HelixElement {
|
|
|
258
321
|
updated(changed) {
|
|
259
322
|
super.updated(changed);
|
|
260
323
|
this.assign();
|
|
324
|
+
this.writeDepths();
|
|
261
325
|
this.nameByHeading();
|
|
262
326
|
if (changed.has('offset')) {
|
|
263
327
|
this.reach();
|
|
328
|
+
}
|
|
329
|
+
if (changed.has('offset') || changed.has('marks')) {
|
|
264
330
|
this.mark();
|
|
265
331
|
}
|
|
266
332
|
if (!this.observer) {
|
|
@@ -295,6 +361,9 @@ __decorate([
|
|
|
295
361
|
words,
|
|
296
362
|
property()
|
|
297
363
|
], HelixHeadlessOutline.prototype, "label", void 0);
|
|
364
|
+
__decorate([
|
|
365
|
+
property()
|
|
366
|
+
], HelixHeadlessOutline.prototype, "marks", void 0);
|
|
298
367
|
__decorate([
|
|
299
368
|
property({ type: Number })
|
|
300
369
|
], HelixHeadlessOutline.prototype, "offset", void 0);
|
|
@@ -200,8 +200,22 @@ export declare class HelixHeadlessSpeedDial extends HelixHeadlessSpeedDial_base
|
|
|
200
200
|
* attribute the consumer's server never sent is a mismatch whether or not it
|
|
201
201
|
* moves afterwards, measured in both load orders, and a trigger that says it
|
|
202
202
|
* is expanded is already saying it has something to expand.
|
|
203
|
+
*
|
|
204
|
+
* **A plain trigger once a reader has reached it.** A `<button>` of yours has
|
|
205
|
+
* no shadow root, so it takes `aria-haspopup="menu"` and `aria-expanded` as
|
|
206
|
+
* attributes, written only once focus or a pointer has reached it. That is
|
|
207
|
+
* after hydration, which is the only time React compares them. A trigger
|
|
208
|
+
* that carries either attribute itself is yours to keep up to date.
|
|
203
209
|
*/
|
|
204
210
|
private announce;
|
|
211
|
+
/** The plain trigger this dial has written its state on, so it can be taken back. */
|
|
212
|
+
private toldPlain;
|
|
213
|
+
/** Whether a reader has reached the trigger, by focus or a pointer, which is after hydration. */
|
|
214
|
+
private reached;
|
|
215
|
+
/** Takes the state off a plain trigger that is no longer this dial's. */
|
|
216
|
+
private untellPlain;
|
|
217
|
+
/** A reader reaching the trigger, which lets a plain one be told its state. */
|
|
218
|
+
private readonly handleReach;
|
|
205
219
|
/** The control the dial has described, so the description can be taken back. */
|
|
206
220
|
private described;
|
|
207
221
|
/**
|
|
@@ -139,6 +139,13 @@ let HelixHeadlessSpeedDial = class HelixHeadlessSpeedDial extends MixinControlle
|
|
|
139
139
|
this.close();
|
|
140
140
|
event.preventDefault();
|
|
141
141
|
};
|
|
142
|
+
this.reached = false;
|
|
143
|
+
this.handleReach = (event) => {
|
|
144
|
+
if (!this.reached && event.composedPath().includes(this.trigger)) {
|
|
145
|
+
this.reached = true;
|
|
146
|
+
this.announce();
|
|
147
|
+
}
|
|
148
|
+
};
|
|
142
149
|
this.handleTriggerFocusIn = () => {
|
|
143
150
|
if (this.pressHeld) {
|
|
144
151
|
this.describeTrigger(true);
|
|
@@ -186,6 +193,8 @@ let HelixHeadlessSpeedDial = class HelixHeadlessSpeedDial extends MixinControlle
|
|
|
186
193
|
this.addEventListener('click', this.handleHeldClick, true);
|
|
187
194
|
this.addEventListener('pointerenter', this.handlePointerEnter);
|
|
188
195
|
this.addEventListener('pointerleave', this.handlePointerLeave);
|
|
196
|
+
this.addEventListener('focusin', this.handleReach);
|
|
197
|
+
this.addEventListener('pointerover', this.handleReach);
|
|
189
198
|
}
|
|
190
199
|
disconnectedCallback() {
|
|
191
200
|
super.disconnectedCallback();
|
|
@@ -199,14 +208,37 @@ let HelixHeadlessSpeedDial = class HelixHeadlessSpeedDial extends MixinControlle
|
|
|
199
208
|
this.removeEventListener('click', this.handleHeldClick, true);
|
|
200
209
|
this.removeEventListener('pointerenter', this.handlePointerEnter);
|
|
201
210
|
this.removeEventListener('pointerleave', this.handlePointerLeave);
|
|
211
|
+
this.removeEventListener('focusin', this.handleReach);
|
|
212
|
+
this.removeEventListener('pointerover', this.handleReach);
|
|
202
213
|
this.stay();
|
|
203
214
|
}
|
|
204
215
|
announce() {
|
|
205
216
|
const trigger = this.trigger;
|
|
206
|
-
if (trigger === this.partNamed('trigger')) {
|
|
217
|
+
if (trigger === this.partNamed('trigger') || trigger === this) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (trigger !== this.toldPlain) {
|
|
221
|
+
this.untellPlain();
|
|
222
|
+
}
|
|
223
|
+
if (trigger.localName.includes('-')) {
|
|
224
|
+
tellDisclosure(trigger, this.open, 'menu');
|
|
207
225
|
return;
|
|
208
226
|
}
|
|
209
|
-
|
|
227
|
+
if (trigger !== this.toldPlain) {
|
|
228
|
+
if (!this.reached ||
|
|
229
|
+
trigger.hasAttribute('aria-expanded') ||
|
|
230
|
+
trigger.hasAttribute('aria-haspopup')) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
this.toldPlain = trigger;
|
|
234
|
+
}
|
|
235
|
+
trigger.setAttribute('aria-haspopup', 'menu');
|
|
236
|
+
trigger.setAttribute('aria-expanded', String(this.open));
|
|
237
|
+
}
|
|
238
|
+
untellPlain() {
|
|
239
|
+
this.toldPlain?.removeAttribute('aria-haspopup');
|
|
240
|
+
this.toldPlain?.removeAttribute('aria-expanded');
|
|
241
|
+
this.toldPlain = undefined;
|
|
210
242
|
}
|
|
211
243
|
describeTrigger(light = false) {
|
|
212
244
|
const trigger = this.trigger;
|
|
@@ -28,7 +28,11 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
28
28
|
* back to the position where the collapsing gesture began. A drag records that position
|
|
29
29
|
* on press, and every other way of collapsing records it in `settle`.
|
|
30
30
|
* `collapsed-size` keeps a sliver of the pane visible, such as a tab strip.
|
|
31
|
-
* Collapsing overrides `min` and `max`.
|
|
31
|
+
* Collapsing overrides `min` and `max`. `collapsible` says which panes can be
|
|
32
|
+
* collapsed at all, as Zag's panels each say.
|
|
33
|
+
*
|
|
34
|
+
* `min` is the least the start pane keeps and `end-min` the least the end pane
|
|
35
|
+
* keeps, each a percentage or a CSS length such as `16rem`.
|
|
32
36
|
*
|
|
33
37
|
* Set `primary` to the pane that keeps its size when the splitter's own size
|
|
34
38
|
* changes, such as a sidebar beside a window being resized. The other pane
|
|
@@ -49,7 +53,7 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
49
53
|
* | Left, Right | Moves a side-by-side divider by `step`. On a right-to-left page, Left moves it toward the end. |
|
|
50
54
|
* | Up, Down | Moves a stacked divider by `step`. |
|
|
51
55
|
* | Home, End | Moves the divider to its bounds. |
|
|
52
|
-
* | Enter | Collapses the start pane, and opens it again. |
|
|
56
|
+
* | Enter | Collapses the start pane, or the end pane where `collapsible="end"`, and opens it again. |
|
|
53
57
|
* | F6 | Moves focus from one pane to the first control in the other, and from the divider to the start pane. |
|
|
54
58
|
*
|
|
55
59
|
* @since 0.1.0
|
|
@@ -119,6 +123,15 @@ export declare class HelixHeadlessSplitter extends HelixElement {
|
|
|
119
123
|
* consumer's to set.
|
|
120
124
|
*/
|
|
121
125
|
collapsedSize: number;
|
|
126
|
+
/**
|
|
127
|
+
* Which panes can be collapsed, as Zag's panels each take `collapsible`:
|
|
128
|
+
* `start`, `end`, `both`, or `none`. A pane that cannot be collapsed stops
|
|
129
|
+
* at the edge without shutting. `Enter` collapses the end pane where only
|
|
130
|
+
* the end pane can be collapsed, and the start pane otherwise.
|
|
131
|
+
*
|
|
132
|
+
* Unset is `both`, and the attribute stays absent until you write it.
|
|
133
|
+
*/
|
|
134
|
+
collapsible?: 'both' | 'end' | 'none' | 'start';
|
|
122
135
|
/**
|
|
123
136
|
* The name of the handle where two dividers cross, read by screen readers.
|
|
124
137
|
*/
|
|
@@ -141,6 +154,14 @@ export declare class HelixHeadlessSplitter extends HelixElement {
|
|
|
141
154
|
* Where the divider starts, when `position` is not set.
|
|
142
155
|
*/
|
|
143
156
|
defaultPosition: number;
|
|
157
|
+
/**
|
|
158
|
+
* The least the end pane keeps, in the units `min` takes: a percentage, or
|
|
159
|
+
* a CSS length such as `16rem` resolved against the splitter's box. The
|
|
160
|
+
* divider stops that far from the end, whatever `max` allows, so `min` and
|
|
161
|
+
* `end-min` hold a minimum for each pane, as Zag's panels each take
|
|
162
|
+
* `minSize`.
|
|
163
|
+
*/
|
|
164
|
+
endMin: number | string;
|
|
144
165
|
/**
|
|
145
166
|
* The divider's name, read by screen readers.
|
|
146
167
|
*
|
|
@@ -403,6 +424,8 @@ export declare class HelixHeadlessSplitter extends HelixElement {
|
|
|
403
424
|
* latches on any drag that runs off the end.
|
|
404
425
|
*/
|
|
405
426
|
private shutAt;
|
|
427
|
+
/** Whether `collapsible` lets this pane be collapsed. */
|
|
428
|
+
private canCollapse;
|
|
406
429
|
/** Pulled between the bounds, which are the ends unless a consumer moved them. */
|
|
407
430
|
private clamp;
|
|
408
431
|
/**
|
|
@@ -55,7 +55,7 @@ let HelixHeadlessSplitter = class HelixHeadlessSplitter extends HelixElement {
|
|
|
55
55
|
return this.toPercent(this.min, 0);
|
|
56
56
|
}
|
|
57
57
|
get maxPercent() {
|
|
58
|
-
return this.toPercent(this.max, 100);
|
|
58
|
+
return Math.min(this.toPercent(this.max, 100), 100 - this.toPercent(this.endMin, 0));
|
|
59
59
|
}
|
|
60
60
|
get position() {
|
|
61
61
|
const shut = this.collapsed;
|
|
@@ -141,10 +141,14 @@ let HelixHeadlessSplitter = class HelixHeadlessSplitter extends HelixElement {
|
|
|
141
141
|
}
|
|
142
142
|
shutAt(position) {
|
|
143
143
|
const size = this.sliver;
|
|
144
|
-
if (position <= size) {
|
|
144
|
+
if (position <= size && this.canCollapse('start')) {
|
|
145
145
|
return 'start';
|
|
146
146
|
}
|
|
147
|
-
return position >= 100 - size ? 'end' : undefined;
|
|
147
|
+
return position >= 100 - size && this.canCollapse('end') ? 'end' : undefined;
|
|
148
|
+
}
|
|
149
|
+
canCollapse(pane) {
|
|
150
|
+
const collapsible = this.collapsible ?? 'both';
|
|
151
|
+
return collapsible === 'both' || collapsible === pane;
|
|
148
152
|
}
|
|
149
153
|
clamp(next) {
|
|
150
154
|
const min = Math.max(0, Math.min(100, this.minPercent));
|
|
@@ -250,6 +254,10 @@ let HelixHeadlessSplitter = class HelixHeadlessSplitter extends HelixElement {
|
|
|
250
254
|
}
|
|
251
255
|
toggleCollapse() {
|
|
252
256
|
const shut = this.collapsed;
|
|
257
|
+
const pane = this.collapsible === 'end' ? 'end' : 'start';
|
|
258
|
+
if (!shut && !this.canCollapse(pane)) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
253
261
|
const opened = shut ? undefined : this.position;
|
|
254
262
|
requestChange({
|
|
255
263
|
apply: (value) => {
|
|
@@ -266,7 +274,7 @@ let HelixHeadlessSplitter = class HelixHeadlessSplitter extends HelixElement {
|
|
|
266
274
|
},
|
|
267
275
|
event: (value) => new HelixBeforeCollapseEvent({ value }),
|
|
268
276
|
host: this,
|
|
269
|
-
next: shut ? '' :
|
|
277
|
+
next: shut ? '' : pane,
|
|
270
278
|
});
|
|
271
279
|
}
|
|
272
280
|
reset() {
|
|
@@ -280,6 +288,7 @@ let HelixHeadlessSplitter = class HelixHeadlessSplitter extends HelixElement {
|
|
|
280
288
|
this.cornerLabel = 'Resize both';
|
|
281
289
|
this.cornerValueLabel = '{across} across, {down} down';
|
|
282
290
|
this.defaultPosition = 50;
|
|
291
|
+
this.endMin = 0;
|
|
283
292
|
this.label = 'Resize panes';
|
|
284
293
|
this.aria = new AriaForwardController(this, {
|
|
285
294
|
label: true,
|
|
@@ -665,6 +674,9 @@ __decorate([
|
|
|
665
674
|
__decorate([
|
|
666
675
|
property({ attribute: 'collapsed-size', type: Number })
|
|
667
676
|
], HelixHeadlessSplitter.prototype, "collapsedSize", void 0);
|
|
677
|
+
__decorate([
|
|
678
|
+
property()
|
|
679
|
+
], HelixHeadlessSplitter.prototype, "collapsible", void 0);
|
|
668
680
|
__decorate([
|
|
669
681
|
words,
|
|
670
682
|
property({ attribute: 'corner-label' })
|
|
@@ -679,6 +691,9 @@ __decorate([
|
|
|
679
691
|
__decorate([
|
|
680
692
|
property({ attribute: 'default-position', type: Number })
|
|
681
693
|
], HelixHeadlessSplitter.prototype, "defaultPosition", void 0);
|
|
694
|
+
__decorate([
|
|
695
|
+
property({ attribute: 'end-min' })
|
|
696
|
+
], HelixHeadlessSplitter.prototype, "endMin", void 0);
|
|
682
697
|
__decorate([
|
|
683
698
|
words,
|
|
684
699
|
property()
|
|
@@ -22,7 +22,8 @@ export type TagSelectionMode = SelectionMode;
|
|
|
22
22
|
* not disabled. `selection-behavior="replace"` selects the tag a reader
|
|
23
23
|
* presses or moves to alone, as a file list does, and Control or Command
|
|
24
24
|
* with a press or Space adds or takes out one tag. `disallow-empty-selection`
|
|
25
|
-
* keeps one tag selected once one is
|
|
25
|
+
* keeps one tag selected once one is, and Escape lets go of the selection
|
|
26
|
+
* otherwise. `value` holds the
|
|
26
27
|
* selected tags' values, separated by commas, and a change asks with
|
|
27
28
|
* `helix-before-change` and tells with `change`. Enter on a tag with an
|
|
28
29
|
* `href` follows its link. A `disabled` tag is passed over. A selected tag
|
|
@@ -40,6 +41,12 @@ export type TagSelectionMode = SelectionMode;
|
|
|
40
41
|
* Where only disabled tags are left, the first of them holds the tab stop and
|
|
41
42
|
* takes focus, so the reader stays in the set.
|
|
42
43
|
*
|
|
44
|
+
* **Typing new tags.** A field where people type tags and press Enter to add
|
|
45
|
+
* each one is Combobox with `multiple` and `allows-custom-value`, or
|
|
46
|
+
* `creatable` to ask first with `helix-create`. It draws each value as a tag,
|
|
47
|
+
* Backspace on the empty field removes the last one, and the arrow keys move
|
|
48
|
+
* between them.
|
|
49
|
+
*
|
|
43
50
|
* @since 0.1.0
|
|
44
51
|
* @status stable
|
|
45
52
|
* @aliases Tag list, Chip group, Token list
|
|
@@ -46,6 +46,14 @@ let HelixHeadlessTagGroup = class HelixHeadlessTagGroup extends HelixElement {
|
|
|
46
46
|
if (event.ctrlKey || event.metaKey) {
|
|
47
47
|
return;
|
|
48
48
|
}
|
|
49
|
+
if (event.key === 'Escape') {
|
|
50
|
+
if (this.selectionMode && this.written !== '' && !this.disallowEmptySelection) {
|
|
51
|
+
event.preventDefault();
|
|
52
|
+
event.stopPropagation();
|
|
53
|
+
this.ask('');
|
|
54
|
+
}
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
49
57
|
if (MOVES.has(event.key)) {
|
|
50
58
|
const to = this.last;
|
|
51
59
|
if (multiple && event.shiftKey && to && to !== tag) {
|
|
@@ -28,6 +28,12 @@ declare const HelixHeadlessTag_base: (new (...args: any[]) => import("@fusion.de
|
|
|
28
28
|
* selected. Its remove button leaves the tab order there, Delete and Backspace
|
|
29
29
|
* doing its job from the row.
|
|
30
30
|
*
|
|
31
|
+
* **Styling a tag by what is happening to it.** `:state(selected)` while it is
|
|
32
|
+
* selected, `:state(focus-visible)` while its cell shows keyboard focus,
|
|
33
|
+
* `[disabled]` while it is out of reach, and the host's own `:hover` and
|
|
34
|
+
* `:active` under the pointer, as React Aria's `data-hovered` and
|
|
35
|
+
* `data-pressed` are.
|
|
36
|
+
*
|
|
31
37
|
* @since 0.1.0
|
|
32
38
|
* @status stable
|
|
33
39
|
* @category markers
|
|
@@ -47,6 +53,7 @@ declare const HelixHeadlessTag_base: (new (...args: any[]) => import("@fusion.de
|
|
|
47
53
|
* @csspart link - The link around the label, under `href`.
|
|
48
54
|
* @csspart start - The category before the label.
|
|
49
55
|
*
|
|
56
|
+
* @cssstate focus-visible - The tag's cell has keyboard focus that the browser shows, in a `Tag.Group`.
|
|
50
57
|
* @cssstate selected - The tag is selected in a `Tag.Group` that selects.
|
|
51
58
|
*/
|
|
52
59
|
export declare class HelixHeadlessTag extends HelixHeadlessTag_base {
|
|
@@ -131,6 +138,11 @@ export declare class HelixHeadlessTag extends HelixHeadlessTag_base {
|
|
|
131
138
|
*/
|
|
132
139
|
private get removeName();
|
|
133
140
|
protected firstUpdated(changed: PropertyValues): void;
|
|
141
|
+
/**
|
|
142
|
+
* Says whether the cell shows keyboard focus, as `:focus-visible` does, so a
|
|
143
|
+
* sheet outside can draw the ring on the tag by the host.
|
|
144
|
+
*/
|
|
145
|
+
private readonly handleCellFocus;
|
|
134
146
|
protected render(): TemplateResult;
|
|
135
147
|
/** The label, inside a link under `href`, behind a seam a tier can move. */
|
|
136
148
|
protected renderLabel(): TemplateResult;
|
|
@@ -15,6 +15,16 @@ let HelixHeadlessTag = class HelixHeadlessTag extends MixinDismissible(HelixElem
|
|
|
15
15
|
this.dismissTabStop = true;
|
|
16
16
|
this.href = '';
|
|
17
17
|
this.value = '';
|
|
18
|
+
this.handleCellFocus = (event) => {
|
|
19
|
+
const cell = event.currentTarget;
|
|
20
|
+
let shown = false;
|
|
21
|
+
try {
|
|
22
|
+
shown = event.type === 'focus' && cell.matches(':focus-visible');
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
}
|
|
26
|
+
this.toggleState('focus-visible', shown);
|
|
27
|
+
};
|
|
18
28
|
}
|
|
19
29
|
static { this.words = 'helix-tag'; }
|
|
20
30
|
get words() {
|
|
@@ -66,6 +76,8 @@ let HelixHeadlessTag = class HelixHeadlessTag extends MixinDismissible(HelixElem
|
|
|
66
76
|
part="cell"
|
|
67
77
|
role="gridcell"
|
|
68
78
|
tabindex=${this.focusable ? '0' : '-1'}
|
|
79
|
+
@focus=${this.handleCellFocus}
|
|
80
|
+
@blur=${this.handleCellFocus}
|
|
69
81
|
>${content}</span
|
|
70
82
|
>`
|
|
71
83
|
: content;
|
|
@@ -469,6 +469,20 @@ export declare class HelixHeadlessTimePicker extends HelixHeadlessTimePicker_bas
|
|
|
469
469
|
* The validation message for a time that `step` skips.
|
|
470
470
|
*/
|
|
471
471
|
stepMismatchMessage: string;
|
|
472
|
+
/**
|
|
473
|
+
* What each time in the list shows: a function given the time as `HH:mm`,
|
|
474
|
+
* or `HH:mm:ss` with `seconds`, and the time as the row writes it, that
|
|
475
|
+
* returns text, an element or a Lit template.
|
|
476
|
+
*
|
|
477
|
+
* For the seats left at a time, or a caption under it, as React Aria's
|
|
478
|
+
* `ListBoxItem` takes children. Content with `slot="start"`, `slot="end"`
|
|
479
|
+
* or `slot="description"` goes to that slot of the row, and a description
|
|
480
|
+
* is read after the time. The row is still named by the written time, and
|
|
481
|
+
* typeahead still finds it by that. Return the written time with your
|
|
482
|
+
* content where it should still show. A property only, there being no way
|
|
483
|
+
* to write a function in markup. The wheels draw their own figures.
|
|
484
|
+
*/
|
|
485
|
+
timeContent?: ((time: string, written: string) => unknown) | undefined;
|
|
472
486
|
/**
|
|
473
487
|
* The validation message for a time that `disable-times` bars.
|
|
474
488
|
*/
|
|
@@ -480,6 +480,8 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinPickerF
|
|
|
480
480
|
${this.times.map((seconds) => {
|
|
481
481
|
const at = this.valueFor(seconds);
|
|
482
482
|
const between = this.between(at);
|
|
483
|
+
const written = writeClock(at, this.clock, this.seconds);
|
|
484
|
+
const drawn = this.timeContent;
|
|
483
485
|
return html `<${this.tag('option')}
|
|
484
486
|
part="option${between ? ' option-between' : ''}"
|
|
485
487
|
exportparts="description: option-description, item: option-item,
|
|
@@ -489,7 +491,9 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinPickerF
|
|
|
489
491
|
?disabled=${this.barred(at)}
|
|
490
492
|
?selected=${this.held.includes(at)}
|
|
491
493
|
data-between=${between ? '' : nothing}
|
|
492
|
-
|
|
494
|
+
aria-label=${drawn ? written : nothing}
|
|
495
|
+
text-value=${drawn ? written : nothing}
|
|
496
|
+
>${drawn ? drawn(at, written) : written}</${this.tag('option')}
|
|
493
497
|
>`;
|
|
494
498
|
})}
|
|
495
499
|
<slot name="empty" slot="empty">${this.emptyText}</slot>
|
|
@@ -600,6 +604,9 @@ __decorate([
|
|
|
600
604
|
words,
|
|
601
605
|
property({ attribute: 'step-mismatch-message' })
|
|
602
606
|
], HelixHeadlessTimePicker.prototype, "stepMismatchMessage", void 0);
|
|
607
|
+
__decorate([
|
|
608
|
+
property({ attribute: false })
|
|
609
|
+
], HelixHeadlessTimePicker.prototype, "timeContent", void 0);
|
|
603
610
|
__decorate([
|
|
604
611
|
words,
|
|
605
612
|
property({ attribute: 'unavailable-message' })
|