@fusion.dev/helix-headless 0.24.0 → 0.25.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 +1891 -4
- package/dist/avatar/group/avatar-group.d.ts +1 -0
- package/dist/avatar/group/avatar-group.js +5 -1
- package/dist/avatar/root/avatar-root.d.ts +1 -0
- package/dist/avatar/root/avatar-root.js +11 -0
- package/dist/carousel/root/carousel-root.d.ts +2 -0
- package/dist/carousel/root/carousel-root.js +5 -2
- package/dist/combobox/root/combobox-root.d.ts +1 -0
- package/dist/combobox/root/combobox-root.js +6 -1
- package/dist/command/root/command-root.d.ts +7 -0
- package/dist/command/root/command-root.js +10 -0
- package/dist/format-date/root/format-date-root.js +4 -1
- package/dist/image/root/image-root.d.ts +9 -0
- package/dist/image/root/image-root.js +16 -3
- package/dist/kbd/root/kbd-root.d.ts +9 -2
- package/dist/kbd/root/kbd-root.js +7 -2
- package/dist/option/root/option-root.d.ts +17 -0
- package/dist/option/root/option-root.js +4 -0
- package/dist/qr-code/root/qr-code-root.d.ts +3 -1
- package/dist/qr-code/root/qr-code-root.js +11 -1
- package/dist/rating/root/rating-root.d.ts +3 -0
- package/dist/rating/root/rating-root.js +9 -0
- package/dist/select/root/select-root.d.ts +8 -0
- package/dist/select/root/select-root.js +8 -6
- package/dist/steps/root/steps-root.js +5 -2
- package/dist/tour/root/tour-root.d.ts +13 -0
- package/dist/tour/root/tour-root.js +43 -18
- package/dist/video/root/video-root.js +4 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +1 -0
- package/types/vue.d.ts +1 -0
|
@@ -72,6 +72,7 @@ export declare class HelixHeadlessAvatarGroup extends HelixElement {
|
|
|
72
72
|
/** Said once per element, because a re-render is not a second mistake. */
|
|
73
73
|
private warned;
|
|
74
74
|
protected willUpdate(changed: PropertyValues): void;
|
|
75
|
+
protected firstUpdated(changed: PropertyValues): void;
|
|
75
76
|
protected updated(changed: PropertyValues): void;
|
|
76
77
|
protected render(): TemplateResult;
|
|
77
78
|
}
|
|
@@ -15,7 +15,7 @@ let HelixHeadlessAvatarGroup = class HelixHeadlessAvatarGroup extends HelixEleme
|
|
|
15
15
|
this.warned = false;
|
|
16
16
|
}
|
|
17
17
|
get avatars() {
|
|
18
|
-
return
|
|
18
|
+
return this.knownChildren.filter((child) => child instanceof HTMLElement);
|
|
19
19
|
}
|
|
20
20
|
get overflow() {
|
|
21
21
|
const limit = this.max ?? 0;
|
|
@@ -26,6 +26,10 @@ let HelixHeadlessAvatarGroup = class HelixHeadlessAvatarGroup extends HelixEleme
|
|
|
26
26
|
super.willUpdate(changed);
|
|
27
27
|
hostAria(this.internals, { label: this.label, role: 'group' });
|
|
28
28
|
}
|
|
29
|
+
firstUpdated(changed) {
|
|
30
|
+
super.firstUpdated(changed);
|
|
31
|
+
this.finishHydration();
|
|
32
|
+
}
|
|
29
33
|
updated(changed) {
|
|
30
34
|
super.updated(changed);
|
|
31
35
|
const limit = this.max ?? 0;
|
|
@@ -53,6 +53,7 @@ export declare class HelixHeadlessAvatar extends HelixElement {
|
|
|
53
53
|
private readonly handleError;
|
|
54
54
|
private readonly handleLoad;
|
|
55
55
|
protected willUpdate(changed: PropertyValues): void;
|
|
56
|
+
protected firstUpdated(changed: PropertyValues): void;
|
|
56
57
|
protected render(): TemplateResult;
|
|
57
58
|
/**
|
|
58
59
|
* The mark shown where no image loaded, behind a seam a tier can move.
|
|
@@ -41,6 +41,17 @@ let HelixHeadlessAvatar = class HelixHeadlessAvatar extends HelixElement {
|
|
|
41
41
|
role: this.name === '' ? '' : 'img',
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
|
+
firstUpdated(changed) {
|
|
45
|
+
super.firstUpdated(changed);
|
|
46
|
+
const image = this.serverRendered ? this.renderRoot.querySelector('img') : null;
|
|
47
|
+
if (image?.complete) {
|
|
48
|
+
const broken = image.naturalWidth === 0;
|
|
49
|
+
if (broken) {
|
|
50
|
+
this.failed = true;
|
|
51
|
+
}
|
|
52
|
+
setTimeout(() => this.relayNativeEvent(new Event(broken ? 'error' : 'load')));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
44
55
|
render() {
|
|
45
56
|
if (this.src !== '' && !this.failed) {
|
|
46
57
|
return html `<img
|
|
@@ -112,6 +112,8 @@ export declare class HelixHeadlessCarousel extends HelixElement {
|
|
|
112
112
|
interval: number;
|
|
113
113
|
/** The slide in view, whoever set it, and `undefined` until somebody has. */
|
|
114
114
|
private indexState;
|
|
115
|
+
/** How many panes were drawn at the last update, to scroll when the first are. */
|
|
116
|
+
private scrolledFor;
|
|
115
117
|
/**
|
|
116
118
|
* Accessible name for the carousel.
|
|
117
119
|
*/
|
|
@@ -16,6 +16,7 @@ let HelixHeadlessCarousel = class HelixHeadlessCarousel extends HelixElement {
|
|
|
16
16
|
this.defaultIndex = 0;
|
|
17
17
|
this.interval = 5000;
|
|
18
18
|
this.indexState = undefined;
|
|
19
|
+
this.scrolledFor = 0;
|
|
19
20
|
this.label = 'Carousel';
|
|
20
21
|
this.loop = false;
|
|
21
22
|
this.rotating = false;
|
|
@@ -207,11 +208,13 @@ let HelixHeadlessCarousel = class HelixHeadlessCarousel extends HelixElement {
|
|
|
207
208
|
}
|
|
208
209
|
updated(changed) {
|
|
209
210
|
super.updated(changed);
|
|
210
|
-
|
|
211
|
+
const count = this.slides.length;
|
|
212
|
+
const drawn = this.renderRoot.querySelectorAll('[part="slide"]').length;
|
|
213
|
+
if (changed.has('index') || (this.scrolledFor === 0 && drawn > 0)) {
|
|
211
214
|
this.scrollToIndex();
|
|
212
215
|
}
|
|
216
|
+
this.scrolledFor = drawn;
|
|
213
217
|
this.runTimer();
|
|
214
|
-
const count = this.slides.length;
|
|
215
218
|
if (count > 0 && this.index > count - 1) {
|
|
216
219
|
this.goTo(count - 1);
|
|
217
220
|
}
|
|
@@ -298,6 +298,7 @@ export declare class HelixHeadlessCombobox extends HelixHeadlessCombobox_base {
|
|
|
298
298
|
* component: a form that received "jav" got the filter, not an option.
|
|
299
299
|
*/
|
|
300
300
|
private sync;
|
|
301
|
+
protected firstUpdated(changed: PropertyValues): void;
|
|
301
302
|
protected willUpdate(changed: PropertyValues): void;
|
|
302
303
|
protected updated(changed: PropertyValues): void;
|
|
303
304
|
/**
|
|
@@ -10,6 +10,7 @@ import { nothing } from 'lit';
|
|
|
10
10
|
import { property, state } from 'lit/decorators.js';
|
|
11
11
|
import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
|
|
12
12
|
import { html } from 'lit/static-html.js';
|
|
13
|
+
import { optionsAmong } from '../../option/root/option-root.js';
|
|
13
14
|
import '../../listbox/index.js';
|
|
14
15
|
import '../../tag/index.js';
|
|
15
16
|
const isInsertion = (event) => !event.inputType?.startsWith('delete');
|
|
@@ -138,7 +139,7 @@ let HelixHeadlessCombobox = class HelixHeadlessCombobox extends MixinControllerO
|
|
|
138
139
|
return this.value === '' ? [] : this.value.split(',');
|
|
139
140
|
}
|
|
140
141
|
get chosen() {
|
|
141
|
-
const options = this.
|
|
142
|
+
const options = optionsAmong(this.knownChildren);
|
|
142
143
|
const known = options.length > 0;
|
|
143
144
|
const words = new Map(options.map((option) => [option.value, option.text]));
|
|
144
145
|
return this.values
|
|
@@ -214,6 +215,10 @@ let HelixHeadlessCombobox = class HelixHeadlessCombobox extends MixinControllerO
|
|
|
214
215
|
this.internals.setFormValue(this.value === '' ? null : this.value);
|
|
215
216
|
this.reportRequired(this.value === '', 'Choose one to continue.', 'control');
|
|
216
217
|
}
|
|
218
|
+
firstUpdated(changed) {
|
|
219
|
+
super.firstUpdated(changed);
|
|
220
|
+
this.finishHydration();
|
|
221
|
+
}
|
|
217
222
|
willUpdate(changed) {
|
|
218
223
|
super.willUpdate(changed);
|
|
219
224
|
this.internals.ariaExpanded = this.open ? 'true' : 'false';
|
|
@@ -86,6 +86,11 @@ export declare class HelixHeadlessCommand extends HelixHeadlessCommand_base {
|
|
|
86
86
|
* child element, which is the slot, so `:not(:has(*))` on it is never true
|
|
87
87
|
* and an empty footer would draw its rule and its padding over nothing. Steps
|
|
88
88
|
* was caught by the same thing.
|
|
89
|
+
*
|
|
90
|
+
* Read from the children before each render, and heard from the slot when
|
|
91
|
+
* they change. The slot alone left the first render without its footer and a
|
|
92
|
+
* second to follow, and a hydrated palette without one for good: a
|
|
93
|
+
* declarative shadow root reports its slots before anything listens.
|
|
89
94
|
*/
|
|
90
95
|
private footed;
|
|
91
96
|
private get listbox();
|
|
@@ -144,6 +149,8 @@ export declare class HelixHeadlessCommand extends HelixHeadlessCommand_base {
|
|
|
144
149
|
private readonly swallowOptionSelect;
|
|
145
150
|
connectedCallback(): void;
|
|
146
151
|
disconnectedCallback(): void;
|
|
152
|
+
protected willUpdate(changed: PropertyValues): void;
|
|
153
|
+
protected firstUpdated(changed: PropertyValues): void;
|
|
147
154
|
protected updated(changed: PropertyValues): void;
|
|
148
155
|
/**
|
|
149
156
|
* The surface the palette opens in, behind a seam the styled tier can move.
|
|
@@ -95,6 +95,16 @@ let HelixHeadlessCommand = class HelixHeadlessCommand extends MixinControllerOpe
|
|
|
95
95
|
this.removeEventListener('click', this.handleAlternate, { capture: true });
|
|
96
96
|
document.removeEventListener('keydown', this.handleHotkey);
|
|
97
97
|
}
|
|
98
|
+
willUpdate(changed) {
|
|
99
|
+
super.willUpdate(changed);
|
|
100
|
+
if (this.childrenKnown) {
|
|
101
|
+
this.footed = this.knownChildren.some((child) => child.slot === 'footer');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
firstUpdated(changed) {
|
|
105
|
+
super.firstUpdated(changed);
|
|
106
|
+
this.finishHydration();
|
|
107
|
+
}
|
|
98
108
|
updated(changed) {
|
|
99
109
|
super.updated?.(changed);
|
|
100
110
|
if (!changed.has('open')) {
|
|
@@ -73,11 +73,14 @@ let HelixHeadlessFormatDate = class HelixHeadlessFormatDate extends HelixElement
|
|
|
73
73
|
}
|
|
74
74
|
firstUpdated(changed) {
|
|
75
75
|
super.firstUpdated(changed);
|
|
76
|
-
this.
|
|
76
|
+
this.finishHydration();
|
|
77
77
|
this.watch();
|
|
78
78
|
}
|
|
79
79
|
willUpdate(changed) {
|
|
80
80
|
super.willUpdate(changed);
|
|
81
|
+
if (this.childrenKnown) {
|
|
82
|
+
this.read();
|
|
83
|
+
}
|
|
81
84
|
if (changed.has('date') || changed.has('elapsed') || changed.has('locale')) {
|
|
82
85
|
this.tick();
|
|
83
86
|
}
|
|
@@ -150,6 +150,15 @@ export declare class HelixHeadlessImage extends HelixElement {
|
|
|
150
150
|
* image it has, so deciding `error` here calls every picture broken the moment
|
|
151
151
|
* it renders. Failure has an event of its own and that event is reliable, so
|
|
152
152
|
* this is only here to catch the success that happened too early to hear.
|
|
153
|
+
*
|
|
154
|
+
* **Except for a picture a server sent**, which a real browser's parser has
|
|
155
|
+
* already fetched: finished with no pixels there is a failure, and its `error`
|
|
156
|
+
* fired before anything could listen, so the placeholder stayed up over a
|
|
157
|
+
* picture that was never coming.
|
|
158
|
+
*
|
|
159
|
+
* **Once per picture.** The first update asks from `firstUpdated` and from
|
|
160
|
+
* `updated`, and a picture already finished answered both, so a page heard
|
|
161
|
+
* every cached `load`, and every `error` a server's page had, twice.
|
|
153
162
|
*/
|
|
154
163
|
private settle;
|
|
155
164
|
protected willUpdate(changed: PropertyValues): void;
|
|
@@ -34,14 +34,27 @@ let HelixHeadlessImage = class HelixHeadlessImage extends HelixElement {
|
|
|
34
34
|
this.relayNativeEvent(event);
|
|
35
35
|
return;
|
|
36
36
|
}
|
|
37
|
-
|
|
37
|
+
const send = () => {
|
|
38
|
+
this.dispatchEvent(new Event(name, { bubbles: false, cancelable: false, composed: false }));
|
|
39
|
+
};
|
|
40
|
+
if (this.serverRendered) {
|
|
41
|
+
setTimeout(send);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
send();
|
|
45
|
+
}
|
|
38
46
|
}
|
|
39
47
|
settle() {
|
|
40
48
|
const image = this.renderRoot?.querySelector('img');
|
|
41
|
-
if (this.src === '' || !image?.complete
|
|
49
|
+
if (this.src === '' || this.status !== 'loading' || !image?.complete) {
|
|
42
50
|
return;
|
|
43
51
|
}
|
|
44
|
-
|
|
52
|
+
if (image.naturalWidth > 0) {
|
|
53
|
+
this.announce('loaded');
|
|
54
|
+
}
|
|
55
|
+
else if (this.serverRendered) {
|
|
56
|
+
this.announce('error');
|
|
57
|
+
}
|
|
45
58
|
}
|
|
46
59
|
willUpdate(changed) {
|
|
47
60
|
super.willUpdate(changed);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { HelixElement } from '@fusion.dev/helix-core';
|
|
2
|
-
import { type TemplateResult } from 'lit';
|
|
2
|
+
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
3
3
|
/**
|
|
4
4
|
* @summary A keyboard key or chord, set inline with text.
|
|
5
5
|
*
|
|
@@ -57,7 +57,13 @@ export declare class HelixHeadlessKbd extends HelixElement {
|
|
|
57
57
|
connectedCallback(): void;
|
|
58
58
|
disconnectedCallback(): void;
|
|
59
59
|
protected updated(changed: Map<string, unknown>): void;
|
|
60
|
-
/**
|
|
60
|
+
/**
|
|
61
|
+
* The chord as this reader's keyboard labels it.
|
|
62
|
+
*
|
|
63
|
+
* As a server labels it while hydrating what one sent: it has no keyboard to
|
|
64
|
+
* ask and serves the words, and a render that differs is a text Lit records
|
|
65
|
+
* and never writes, so a Mac kept Ctrl. The update after hydration asks.
|
|
66
|
+
*/
|
|
61
67
|
private get label();
|
|
62
68
|
private listen;
|
|
63
69
|
private deafen;
|
|
@@ -72,6 +78,7 @@ export declare class HelixHeadlessKbd extends HelixElement {
|
|
|
72
78
|
private get printable();
|
|
73
79
|
/** Whether a press is this chord, modifiers and all. */
|
|
74
80
|
private matches_;
|
|
81
|
+
protected firstUpdated(changed: PropertyValues): void;
|
|
75
82
|
protected render(): TemplateResult;
|
|
76
83
|
}
|
|
77
84
|
declare global {
|
|
@@ -105,13 +105,14 @@ let HelixHeadlessKbd = class HelixHeadlessKbd extends HelixElement {
|
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
get label() {
|
|
108
|
-
const
|
|
108
|
+
const apple = !this.hydrating && onApple();
|
|
109
|
+
const names = apple ? APPLE : ELSEWHERE;
|
|
109
110
|
const parts = this.keys
|
|
110
111
|
.split('+')
|
|
111
112
|
.map((part) => part.trim())
|
|
112
113
|
.filter((part) => part !== '')
|
|
113
114
|
.map((part) => names[part.toLowerCase()] ?? (part.length === 1 ? part.toUpperCase() : part));
|
|
114
|
-
return parts.join(
|
|
115
|
+
return parts.join(apple ? '' : '+');
|
|
115
116
|
}
|
|
116
117
|
listen() {
|
|
117
118
|
if (!this.within || this.keys === '') {
|
|
@@ -157,6 +158,10 @@ let HelixHeadlessKbd = class HelixHeadlessKbd extends HelixElement {
|
|
|
157
158
|
? true
|
|
158
159
|
: !press.metaKey));
|
|
159
160
|
}
|
|
161
|
+
firstUpdated(changed) {
|
|
162
|
+
super.firstUpdated(changed);
|
|
163
|
+
this.finishHydration();
|
|
164
|
+
}
|
|
160
165
|
render() {
|
|
161
166
|
return html `<kbd part="kbd">${this.keys === '' ? html `<slot></slot>` : this.label}</kbd>`;
|
|
162
167
|
}
|
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
import { HelixElement } from '@fusion.dev/helix-core';
|
|
2
2
|
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
3
|
+
/**
|
|
4
|
+
* The options among some elements, reaching one level into a group.
|
|
5
|
+
*
|
|
6
|
+
* What a control holding options reads its words from, off its own children
|
|
7
|
+
* rather than off the listbox they are slotted into: the slot exists only once a
|
|
8
|
+
* render has made it, so the listbox has none on a first render, and on a page a
|
|
9
|
+
* server rendered the `slotchange` that would correct it fires before anything
|
|
10
|
+
* listens.
|
|
11
|
+
*
|
|
12
|
+
* **An option by its shape, not by `instanceof`.** A tag is defined once, by
|
|
13
|
+
* whichever copy of this class registered it first, and a second copy in the
|
|
14
|
+
* same document tests against a class no element on the page has: every option
|
|
15
|
+
* failed it, and a select said "Choose" over its value. Two copies are a
|
|
16
|
+
* consumer with a duplicated dependency, or the browser specs, where each
|
|
17
|
+
* family's bundle carries its own and they share one page.
|
|
18
|
+
*/
|
|
19
|
+
export declare const optionsAmong: (elements: readonly Element[]) => HelixHeadlessOption[];
|
|
3
20
|
/**
|
|
4
21
|
* @summary One choice in a listbox.
|
|
5
22
|
*
|
|
@@ -7,6 +7,10 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
import { customElement, HelixElement, HelixSelectEvent } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html, nothing } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
|
+
export const optionsAmong = (elements) => {
|
|
11
|
+
const isOption = (element) => 'text' in element && 'value' in element && 'selected' in element;
|
|
12
|
+
return elements.flatMap((element) => isOption(element) ? [element] : [...element.children].filter(isOption));
|
|
13
|
+
};
|
|
10
14
|
let HelixHeadlessOption = class HelixHeadlessOption extends HelixElement {
|
|
11
15
|
constructor() {
|
|
12
16
|
super(...arguments);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { HelixElement } from '@fusion.dev/helix-core';
|
|
2
|
-
import { type TemplateResult } from 'lit';
|
|
2
|
+
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
3
3
|
/** How much of the code can be lost and still read. */
|
|
4
4
|
export type QrCorrection = 'H' | 'L' | 'M' | 'Q';
|
|
5
5
|
/** How the modules are drawn. */
|
|
@@ -81,6 +81,8 @@ export declare class HelixHeadlessQrCode extends HelixElement {
|
|
|
81
81
|
value: string;
|
|
82
82
|
/** Whether anything is slotted into the middle. */
|
|
83
83
|
private logo;
|
|
84
|
+
protected willUpdate(changed: PropertyValues): void;
|
|
85
|
+
protected firstUpdated(changed: PropertyValues): void;
|
|
84
86
|
/** The level actually used, which the slot can raise. */
|
|
85
87
|
private get level();
|
|
86
88
|
private renderCode;
|
|
@@ -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
|
import { customElement, HelixElement } from '@fusion.dev/helix-core';
|
|
8
|
-
import { html, nothing, svg } from 'lit';
|
|
8
|
+
import { html, nothing, svg, } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
10
|
import { encode } from 'uqr';
|
|
11
11
|
const isFinder = (x, y, size) => {
|
|
@@ -67,6 +67,16 @@ let HelixHeadlessQrCode = class HelixHeadlessQrCode extends HelixElement {
|
|
|
67
67
|
this.value = '';
|
|
68
68
|
this.logo = false;
|
|
69
69
|
}
|
|
70
|
+
willUpdate(changed) {
|
|
71
|
+
super.willUpdate(changed);
|
|
72
|
+
if (this.childrenKnown) {
|
|
73
|
+
this.logo = this.knownChildren.some((child) => child.slot === 'logo');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
firstUpdated(changed) {
|
|
77
|
+
super.firstUpdated(changed);
|
|
78
|
+
this.finishHydration();
|
|
79
|
+
}
|
|
70
80
|
get level() {
|
|
71
81
|
return this.correction ?? (this.logo ? 'H' : 'M');
|
|
72
82
|
}
|
|
@@ -111,6 +111,8 @@ export declare class HelixHeadlessRating extends HelixHeadlessRating_base {
|
|
|
111
111
|
* ignored.
|
|
112
112
|
*/
|
|
113
113
|
private markSource;
|
|
114
|
+
/** Whether `markSource` has had its first answer from the children. */
|
|
115
|
+
private markRead;
|
|
114
116
|
/**
|
|
115
117
|
* What the pointer is over, which is shown instead of the score while it is.
|
|
116
118
|
*/
|
|
@@ -194,6 +196,7 @@ export declare class HelixHeadlessRating extends HelixHeadlessRating_base {
|
|
|
194
196
|
connectedCallback(): void;
|
|
195
197
|
disconnectedCallback(): void;
|
|
196
198
|
protected updated(changed: PropertyValues): void;
|
|
199
|
+
protected willUpdate(changed: PropertyValues): void;
|
|
197
200
|
protected firstUpdated(changed: PropertyValues): void;
|
|
198
201
|
protected render(): TemplateResult;
|
|
199
202
|
/**
|
|
@@ -18,6 +18,7 @@ let HelixHeadlessRating = class HelixHeadlessRating extends MixinControllerValue
|
|
|
18
18
|
this.step = 1;
|
|
19
19
|
this.valueText = '';
|
|
20
20
|
this.markSource = undefined;
|
|
21
|
+
this.markRead = false;
|
|
21
22
|
this.previewing = undefined;
|
|
22
23
|
this.keys = new KeyboardNavigationController(this, {
|
|
23
24
|
items: () => this.options,
|
|
@@ -157,8 +158,16 @@ let HelixHeadlessRating = class HelixHeadlessRating extends MixinControllerValue
|
|
|
157
158
|
super.updated?.(changed);
|
|
158
159
|
this.sync();
|
|
159
160
|
}
|
|
161
|
+
willUpdate(changed) {
|
|
162
|
+
super.willUpdate(changed);
|
|
163
|
+
if (!this.markRead && this.childrenKnown) {
|
|
164
|
+
this.markRead = true;
|
|
165
|
+
this.markSource = this.knownChildren.find((child) => child.slot === 'mark');
|
|
166
|
+
}
|
|
167
|
+
}
|
|
160
168
|
firstUpdated(changed) {
|
|
161
169
|
super.firstUpdated?.(changed);
|
|
170
|
+
this.finishHydration();
|
|
162
171
|
this.sync();
|
|
163
172
|
}
|
|
164
173
|
render() {
|
|
@@ -124,6 +124,13 @@ export declare class HelixHeadlessSelect extends HelixHeadlessSelect_base {
|
|
|
124
124
|
* Read off the options rather than stored, because the words are the
|
|
125
125
|
* consumer's content and storing a copy is storing something that goes stale
|
|
126
126
|
* the moment they change it.
|
|
127
|
+
*
|
|
128
|
+
* **Off this element's own children, not the listbox's**, which reach the
|
|
129
|
+
* listbox through a slot and so only after a render has made it: read there,
|
|
130
|
+
* the first render showed the placeholder over a chosen value and waited on a
|
|
131
|
+
* `slotchange`, which a declarative shadow root fires before anything listens,
|
|
132
|
+
* so a hydrated select kept "Choose" for good. Nothing on a server or while
|
|
133
|
+
* hydrating what one sent, which drew the placeholder too.
|
|
127
134
|
*/
|
|
128
135
|
private get display();
|
|
129
136
|
/** Closes the list and puts focus back where it came from. */
|
|
@@ -133,6 +140,7 @@ export declare class HelixHeadlessSelect extends HelixHeadlessSelect_base {
|
|
|
133
140
|
/** Reports the answer and the verdict on it. */
|
|
134
141
|
private sync;
|
|
135
142
|
protected willUpdate(changed: PropertyValues): void;
|
|
143
|
+
protected firstUpdated(changed: PropertyValues): void;
|
|
136
144
|
protected updated(changed: PropertyValues): void;
|
|
137
145
|
/**
|
|
138
146
|
* The list the trigger opens, behind a seam the styled tier can move.
|
|
@@ -10,6 +10,7 @@ import { nothing } from 'lit';
|
|
|
10
10
|
import { property } from 'lit/decorators.js';
|
|
11
11
|
import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
|
|
12
12
|
import { html } from 'lit/static-html.js';
|
|
13
|
+
import { optionsAmong } from '../../option/root/option-root.js';
|
|
13
14
|
import '../../listbox/index.js';
|
|
14
15
|
let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(MixinControllerValue(MixinField(HelixFormElement))) {
|
|
15
16
|
constructor() {
|
|
@@ -69,7 +70,7 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
|
|
|
69
70
|
}
|
|
70
71
|
get display() {
|
|
71
72
|
const chosen = new Set(this.value === '' ? [] : this.value.split(','));
|
|
72
|
-
const words = (this.
|
|
73
|
+
const words = optionsAmong(this.knownChildren)
|
|
73
74
|
.filter((option) => chosen.has(option.value))
|
|
74
75
|
.map((option) => option.text);
|
|
75
76
|
return words.length > 0 ? words.join(', ') : this.placeholder;
|
|
@@ -89,6 +90,10 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
|
|
|
89
90
|
super.willUpdate(changed);
|
|
90
91
|
hostAria(this.internals, { label: this.label });
|
|
91
92
|
}
|
|
93
|
+
firstUpdated(changed) {
|
|
94
|
+
super.firstUpdated(changed);
|
|
95
|
+
this.finishHydration();
|
|
96
|
+
}
|
|
92
97
|
updated(changed) {
|
|
93
98
|
super.updated(changed);
|
|
94
99
|
this.sync();
|
|
@@ -122,11 +127,8 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
|
|
|
122
127
|
@change=${this.stopInner}
|
|
123
128
|
@input=${this.stopInner}
|
|
124
129
|
>
|
|
125
|
-
<!--
|
|
126
|
-
|
|
127
|
-
select with a value came up showing its placeholder. Assignment
|
|
128
|
-
changes no property, so nothing else would schedule the update
|
|
129
|
-
that fixes it. -->
|
|
130
|
+
<!-- An option arriving or leaving changes no property, so this is what
|
|
131
|
+
schedules the update that rereads the trigger's words. -->
|
|
130
132
|
<slot @slotchange=${() => this.requestUpdate()}></slot>
|
|
131
133
|
</${this.tag('listbox')}>`;
|
|
132
134
|
}
|
|
@@ -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, MixinControllerActive, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { customElement, HelixElement, hostAria, MixinControllerActive, whenHydrated, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
10
|
let HelixHeadlessSteps = class HelixHeadlessSteps extends MixinControllerActive(HelixElement) {
|
|
@@ -28,7 +28,10 @@ let HelixHeadlessSteps = class HelixHeadlessSteps extends MixinControllerActive(
|
|
|
28
28
|
const steps = this.steps;
|
|
29
29
|
const value = this.resolveActive(steps)?.value ?? '';
|
|
30
30
|
for (const step of steps) {
|
|
31
|
-
|
|
31
|
+
const current = step.value === value;
|
|
32
|
+
whenHydrated(step, () => {
|
|
33
|
+
step.current = current;
|
|
34
|
+
});
|
|
32
35
|
}
|
|
33
36
|
}
|
|
34
37
|
connectedCallback() {
|
|
@@ -90,6 +90,15 @@ export declare class HelixHeadlessTour extends HelixElement {
|
|
|
90
90
|
* Where the light falls, read from the current target.
|
|
91
91
|
*/
|
|
92
92
|
private spot;
|
|
93
|
+
/**
|
|
94
|
+
* Watches the lit target's own box, which moves the light without this
|
|
95
|
+
* element updating or the window changing: a label whose font arrives late,
|
|
96
|
+
* words a page rewrites. Measured on a server's page, the spotlight sat two
|
|
97
|
+
* pixels inside each side of a button that widened once its font loaded.
|
|
98
|
+
*/
|
|
99
|
+
private sizer;
|
|
100
|
+
/** What `sizer` is watching, so a render that changed nothing re-observes nothing. */
|
|
101
|
+
private sized;
|
|
93
102
|
/** The stops, in the order they were written. */
|
|
94
103
|
get stops(): HelixHeadlessTourStop[];
|
|
95
104
|
/** How many stops there are. */
|
|
@@ -124,8 +133,12 @@ export declare class HelixHeadlessTour extends HelixElement {
|
|
|
124
133
|
* localised the tour.
|
|
125
134
|
*/
|
|
126
135
|
private settle;
|
|
136
|
+
/** The current stop's target while the run is open, which is what the light falls on. */
|
|
137
|
+
private get lit();
|
|
127
138
|
/** Reads the current target's box, which is where the light falls. */
|
|
128
139
|
private light;
|
|
140
|
+
/** Keeps `sizer` on the lit target, and on nothing once there is none. */
|
|
141
|
+
private watch;
|
|
129
142
|
protected render(): TemplateResult;
|
|
130
143
|
}
|
|
131
144
|
declare global {
|
|
@@ -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, HelixBeforeActivateEvent, HelixBeforeCloseEvent, HelixCompleteEvent, HelixElement, hostAria, requestChange, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { customElement, HelixBeforeActivateEvent, HelixBeforeCloseEvent, HelixCompleteEvent, HelixElement, hostAria, requestChange, whenHydrated, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html } from 'lit';
|
|
9
9
|
import { property, queryAssignedElements, state } from 'lit/decorators.js';
|
|
10
10
|
let HelixHeadlessTour = class HelixHeadlessTour extends HelixElement {
|
|
@@ -19,6 +19,8 @@ let HelixHeadlessTour = class HelixHeadlessTour extends HelixElement {
|
|
|
19
19
|
this.open = false;
|
|
20
20
|
this.skipLabel = '';
|
|
21
21
|
this.spotlightPadding = 8;
|
|
22
|
+
this.sizer = undefined;
|
|
23
|
+
this.sized = undefined;
|
|
22
24
|
this.handleAsk = (event) => {
|
|
23
25
|
const what = event.detail?.what;
|
|
24
26
|
if (what === 'next') {
|
|
@@ -96,6 +98,9 @@ let HelixHeadlessTour = class HelixHeadlessTour extends HelixElement {
|
|
|
96
98
|
this.ownerDocument.addEventListener('keydown', this.handleKeydown);
|
|
97
99
|
globalThis.addEventListener('resize', this.handleViewport, { passive: true });
|
|
98
100
|
globalThis.addEventListener('scroll', this.handleViewport, { capture: true, passive: true });
|
|
101
|
+
if (this.hasUpdated) {
|
|
102
|
+
this.watch(this.lit);
|
|
103
|
+
}
|
|
99
104
|
}
|
|
100
105
|
disconnectedCallback() {
|
|
101
106
|
super.disconnectedCallback();
|
|
@@ -103,6 +108,8 @@ let HelixHeadlessTour = class HelixHeadlessTour extends HelixElement {
|
|
|
103
108
|
this.ownerDocument.removeEventListener('keydown', this.handleKeydown);
|
|
104
109
|
globalThis.removeEventListener('resize', this.handleViewport);
|
|
105
110
|
globalThis.removeEventListener('scroll', this.handleViewport, { capture: true });
|
|
111
|
+
this.sizer?.disconnect();
|
|
112
|
+
this.sized = undefined;
|
|
106
113
|
}
|
|
107
114
|
willUpdate(changed) {
|
|
108
115
|
super.willUpdate(changed);
|
|
@@ -117,28 +124,34 @@ let HelixHeadlessTour = class HelixHeadlessTour extends HelixElement {
|
|
|
117
124
|
const stops = this.stops;
|
|
118
125
|
const at = Math.max(0, Math.min(this.index, stops.length - 1));
|
|
119
126
|
stops.forEach((stop, index) => {
|
|
120
|
-
stop
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
127
|
+
whenHydrated(stop, () => {
|
|
128
|
+
stop.current = this.open && index === at;
|
|
129
|
+
stop.first = index === 0;
|
|
130
|
+
stop.last = index === stops.length - 1;
|
|
131
|
+
if (this.backLabel !== '') {
|
|
132
|
+
stop.backLabel = this.backLabel;
|
|
133
|
+
}
|
|
134
|
+
if (this.nextLabel !== '') {
|
|
135
|
+
stop.nextLabel = this.nextLabel;
|
|
136
|
+
}
|
|
137
|
+
if (this.doneLabel !== '') {
|
|
138
|
+
stop.doneLabel = this.doneLabel;
|
|
139
|
+
}
|
|
140
|
+
if (this.skipLabel !== '') {
|
|
141
|
+
stop.skipLabel = this.skipLabel;
|
|
142
|
+
}
|
|
143
|
+
});
|
|
135
144
|
});
|
|
136
145
|
}
|
|
137
|
-
|
|
146
|
+
get lit() {
|
|
138
147
|
const stops = this.stops;
|
|
139
|
-
|
|
148
|
+
return this.open
|
|
140
149
|
? stops[Math.max(0, Math.min(this.index, stops.length - 1))]?.target
|
|
141
150
|
: undefined;
|
|
151
|
+
}
|
|
152
|
+
light() {
|
|
153
|
+
const target = this.lit;
|
|
154
|
+
this.watch(target);
|
|
142
155
|
if (!target || typeof target.getBoundingClientRect !== 'function') {
|
|
143
156
|
this.spot = undefined;
|
|
144
157
|
return;
|
|
@@ -160,6 +173,18 @@ let HelixHeadlessTour = class HelixHeadlessTour extends HelixElement {
|
|
|
160
173
|
this.spot = next;
|
|
161
174
|
}
|
|
162
175
|
}
|
|
176
|
+
watch(target) {
|
|
177
|
+
const next = target ?? undefined;
|
|
178
|
+
if (next === this.sized || typeof ResizeObserver === 'undefined') {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
this.sizer ??= new ResizeObserver(() => this.light());
|
|
182
|
+
this.sizer.disconnect();
|
|
183
|
+
if (next) {
|
|
184
|
+
this.sizer.observe(next);
|
|
185
|
+
}
|
|
186
|
+
this.sized = next;
|
|
187
|
+
}
|
|
163
188
|
render() {
|
|
164
189
|
const spot = this.spot;
|
|
165
190
|
return html `${spot === undefined
|