@fusion.dev/helix-headless 0.171.0 → 0.172.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 +439 -65
- package/dist/chat/composer/chat-composer.d.ts +28 -1
- package/dist/chat/composer/chat-composer.js +22 -3
- package/dist/chat/root/chat-root.d.ts +38 -2
- package/dist/chat/root/chat-root.js +38 -1
- package/dist/color-input/root/color-input-root.d.ts +15 -1
- package/dist/color-input/root/color-input-root.js +19 -1
- package/dist/color-picker/root/color-picker-root.d.ts +5 -1
- package/dist/color-picker/root/color-picker-root.js +1 -1
- package/dist/data-table/root/data-table-root.d.ts +5 -1
- package/dist/data-table/root/data-table-root.js +1 -0
- package/dist/dialog/root/dialog-root.js +1 -0
- package/dist/drawer/root/drawer-root.d.ts +4 -0
- package/dist/drawer/root/drawer-root.js +1 -0
- package/dist/element-events.generated.js +1 -0
- package/dist/lightbox/root/lightbox-root.js +1 -0
- package/dist/listbox/root/listbox-root.d.ts +2 -1
- package/dist/listbox/root/listbox-root.js +3 -2
- package/dist/menu/item/menu-item.d.ts +2 -0
- package/dist/menu/item/menu-item.js +2 -0
- package/dist/menu/root/menu-root.d.ts +73 -12
- package/dist/menu/root/menu-root.js +103 -7
- package/dist/popover/root/popover-root.d.ts +59 -6
- package/dist/popover/root/popover-root.js +51 -3
- package/dist/select/root/select-root.d.ts +2 -2
- package/dist/select/root/select-root.js +2 -2
- package/dist/textarea/root/textarea-root.d.ts +8 -0
- package/dist/textarea/root/textarea-root.js +16 -0
- package/lint.json +1 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +312 -30
- package/types/vue.d.ts +59 -8
- package/types/words.d.ts +8 -0
|
@@ -28,6 +28,11 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
28
28
|
* Sending an empty required composer counts as trying. Tabbing past an empty
|
|
29
29
|
* composer marks nothing.
|
|
30
30
|
*
|
|
31
|
+
* **`feedback` is a message of your own about the draft**, such as one your
|
|
32
|
+
* server sent back. While it is set, the composer is invalid at once, does
|
|
33
|
+
* not send or submit, shows the message under the field, and a screen reader
|
|
34
|
+
* hears it as it appears. Clear it to let the draft go.
|
|
35
|
+
*
|
|
31
36
|
* **`typing` says whether the user is writing**, and `helix-typing` is sent
|
|
32
37
|
* each time it changes: when they start, and when they send or stop for
|
|
33
38
|
* `typing-timeout` milliseconds. Pass it on to show "is typing" to the other
|
|
@@ -48,13 +53,14 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
48
53
|
*
|
|
49
54
|
* @csspart composer - The whole composer.
|
|
50
55
|
* @csspart end - The box around the `end` slot.
|
|
56
|
+
* @csspart feedback - The `feedback` message, under the field while it is set.
|
|
51
57
|
* @csspart field - The text field.
|
|
52
58
|
* @csspart mark - The icon inside the send button.
|
|
53
59
|
* @csspart send - The send button.
|
|
54
60
|
* @csspart start - The box around the `start` slot.
|
|
55
61
|
* @csspart tools - The row under the field.
|
|
56
62
|
*
|
|
57
|
-
* @cssstate invalid - The draft fails `required`, `minlength`, `maxlength`, or a custom error, and the reader has tried to send or submit it, or edited the field and left it.
|
|
63
|
+
* @cssstate invalid - The draft fails `required`, `minlength`, `maxlength`, or a custom error, and the reader has tried to send or submit it, or edited the field and left it. Or `feedback` is set.
|
|
58
64
|
*/
|
|
59
65
|
export declare class HelixHeadlessChatComposer extends HelixFormElement {
|
|
60
66
|
/**
|
|
@@ -62,6 +68,12 @@ export declare class HelixHeadlessChatComposer extends HelixFormElement {
|
|
|
62
68
|
* `registerWords('de', { 'helix-chat-composer': { sendLabel: 'Senden' } })`.
|
|
63
69
|
*/
|
|
64
70
|
static words: string;
|
|
71
|
+
/**
|
|
72
|
+
* A message about the draft, shown under the field. While set, the composer
|
|
73
|
+
* is invalid, does not send, and does not submit, as a field's `feedback`
|
|
74
|
+
* does, and the message is announced as it appears.
|
|
75
|
+
*/
|
|
76
|
+
feedback: string;
|
|
65
77
|
/**
|
|
66
78
|
* The field's name, read by screen readers.
|
|
67
79
|
*/
|
|
@@ -123,6 +135,17 @@ export declare class HelixHeadlessChatComposer extends HelixFormElement {
|
|
|
123
135
|
private writing;
|
|
124
136
|
/** Whether the reader has tried to send or submit the draft, or edited it and left. */
|
|
125
137
|
private touched;
|
|
138
|
+
/**
|
|
139
|
+
* Whether the reader has typed in the field since it last held a fresh
|
|
140
|
+
* draft: since it rendered, since a send cleared it, or since a reset.
|
|
141
|
+
*
|
|
142
|
+
* **A send clears the field, and the field still sends `change` as it is
|
|
143
|
+
* left**, because the platform compares the text with what it held when it
|
|
144
|
+
* was focused. Measured in Chromium before this: a required composer sent
|
|
145
|
+
* "hi", cleared, and on Tab showed itself invalid over an empty draft
|
|
146
|
+
* nobody had written.
|
|
147
|
+
*/
|
|
148
|
+
private edited;
|
|
126
149
|
/** Whether the composer shows that its draft is wrong. */
|
|
127
150
|
private shownInvalid;
|
|
128
151
|
/** The pause after which the user has stopped writing. */
|
|
@@ -147,10 +170,14 @@ export declare class HelixHeadlessChatComposer extends HelixFormElement {
|
|
|
147
170
|
* back to. `value` does not reflect, so its attribute stays as written.
|
|
148
171
|
*/
|
|
149
172
|
private get declared();
|
|
173
|
+
/** @internal `feedback` marks the composer invalid, as a field's does. */
|
|
174
|
+
protected get validityFeedback(): string;
|
|
150
175
|
/** Puts back the draft the markup declared when the form is reset. */
|
|
151
176
|
formResetCallback(): void;
|
|
152
177
|
/** The reader has had a go at the draft, so whether it is wrong can be shown. */
|
|
153
178
|
private readonly touch;
|
|
179
|
+
/** The field's `change`, which counts only after the reader typed in it. */
|
|
180
|
+
private readonly handleChange;
|
|
154
181
|
/** Brings back the draft when the browser restores the form. */
|
|
155
182
|
formStateRestoreCallback(state: string | File | FormData | null): void;
|
|
156
183
|
/**
|
|
@@ -13,6 +13,7 @@ import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
|
|
|
13
13
|
let HelixHeadlessChatComposer = class HelixHeadlessChatComposer extends HelixFormElement {
|
|
14
14
|
constructor() {
|
|
15
15
|
super(...arguments);
|
|
16
|
+
this.feedback = '';
|
|
16
17
|
this.label = 'Message';
|
|
17
18
|
this.placeholder = '';
|
|
18
19
|
this.required = false;
|
|
@@ -24,15 +25,22 @@ let HelixHeadlessChatComposer = class HelixHeadlessChatComposer extends HelixFor
|
|
|
24
25
|
this.isChatComposer = true;
|
|
25
26
|
this.writing = false;
|
|
26
27
|
this.touched = false;
|
|
28
|
+
this.edited = false;
|
|
27
29
|
this.shownInvalid = false;
|
|
28
30
|
this.pause = new DelayController(this, { renders: false });
|
|
29
31
|
this.touch = () => {
|
|
30
32
|
this.touched = true;
|
|
31
33
|
this.requestUpdate();
|
|
32
34
|
};
|
|
35
|
+
this.handleChange = () => {
|
|
36
|
+
if (this.edited) {
|
|
37
|
+
this.touch();
|
|
38
|
+
}
|
|
39
|
+
};
|
|
33
40
|
this.handleInput = (event) => {
|
|
34
41
|
event.stopPropagation();
|
|
35
42
|
this.value = event.target.value;
|
|
43
|
+
this.edited = true;
|
|
36
44
|
this.dispatchEvent(new Event('input'));
|
|
37
45
|
this.markTyping(this.value !== '');
|
|
38
46
|
};
|
|
@@ -87,16 +95,21 @@ let HelixHeadlessChatComposer = class HelixHeadlessChatComposer extends HelixFor
|
|
|
87
95
|
}
|
|
88
96
|
this.value = '';
|
|
89
97
|
this.touched = false;
|
|
98
|
+
this.edited = false;
|
|
90
99
|
this.markTyping(false);
|
|
91
100
|
return true;
|
|
92
101
|
}
|
|
93
102
|
get declared() {
|
|
94
103
|
return this.getAttribute('value') ?? '';
|
|
95
104
|
}
|
|
105
|
+
get validityFeedback() {
|
|
106
|
+
return this.feedback;
|
|
107
|
+
}
|
|
96
108
|
formResetCallback() {
|
|
97
109
|
super.formResetCallback();
|
|
98
110
|
this.value = this.declared;
|
|
99
111
|
this.touched = false;
|
|
112
|
+
this.edited = false;
|
|
100
113
|
this.requestUpdate();
|
|
101
114
|
}
|
|
102
115
|
formStateRestoreCallback(state) {
|
|
@@ -117,7 +130,7 @@ let HelixHeadlessChatComposer = class HelixHeadlessChatComposer extends HelixFor
|
|
|
117
130
|
if (field?.validity) {
|
|
118
131
|
this.internals.setValidity(field.validity, field.validationMessage, field);
|
|
119
132
|
}
|
|
120
|
-
const invalid = this.touched && !this.internals.validity.valid;
|
|
133
|
+
const invalid = this.feedback !== '' || (this.touched && !this.internals.validity.valid);
|
|
121
134
|
this.toggleState('invalid', invalid);
|
|
122
135
|
this.internals.ariaInvalid = invalid ? 'true' : 'false';
|
|
123
136
|
if (invalid !== this.shownInvalid) {
|
|
@@ -166,8 +179,9 @@ let HelixHeadlessChatComposer = class HelixHeadlessChatComposer extends HelixFor
|
|
|
166
179
|
minlength=${this.minLength ?? nothing}
|
|
167
180
|
maxlength=${this.maxLength ?? nothing}
|
|
168
181
|
aria-invalid=${this.shownInvalid ? 'true' : nothing}
|
|
182
|
+
aria-describedby=${this.feedback === '' ? nothing : 'feedback'}
|
|
169
183
|
?disabled=${this.isDisabled}
|
|
170
|
-
@change=${this.
|
|
184
|
+
@change=${this.handleChange}
|
|
171
185
|
@input=${this.handleInput}
|
|
172
186
|
@keydown=${this.handleKey}
|
|
173
187
|
></textarea>
|
|
@@ -176,9 +190,14 @@ let HelixHeadlessChatComposer = class HelixHeadlessChatComposer extends HelixFor
|
|
|
176
190
|
<span part="end"><slot name="end"></slot></span>
|
|
177
191
|
${this.renderSend()}
|
|
178
192
|
</div>
|
|
179
|
-
</div
|
|
193
|
+
</div>${this.feedback === ''
|
|
194
|
+
? nothing
|
|
195
|
+
: html `<p part="feedback" id="feedback" role="alert">${this.feedback}</p>`}`;
|
|
180
196
|
}
|
|
181
197
|
};
|
|
198
|
+
__decorate([
|
|
199
|
+
property()
|
|
200
|
+
], HelixHeadlessChatComposer.prototype, "feedback", void 0);
|
|
182
201
|
__decorate([
|
|
183
202
|
words,
|
|
184
203
|
property()
|
|
@@ -37,6 +37,13 @@ declare const HelixHeadlessChat_base: (new (...args: any[]) => import("@fusion.d
|
|
|
37
37
|
* `loading` while you fetch, then prepend the older turns. The turn the user
|
|
38
38
|
* was reading stays in place on screen.
|
|
39
39
|
*
|
|
40
|
+
* The transcript is a `log`, the role for content added in order where the
|
|
41
|
+
* newest matters most, and it answers the APG feed's keys. The `feed` role
|
|
42
|
+
* reads its articles as a list from the top, with no newest end to stay
|
|
43
|
+
* pinned to, so the log keeps the role a conversation has. While `loading` is
|
|
44
|
+
* set or older turns load, the log carries `aria-busy`, as the APG asks of a
|
|
45
|
+
* feed.
|
|
46
|
+
*
|
|
40
47
|
* The log is `aria-live="off"`. A separate status region announces each new
|
|
41
48
|
* turn once, when it has finished arriving. It skips the user's own turns
|
|
42
49
|
* unless they failed to send, turns loaded as history, and the turns already
|
|
@@ -49,6 +56,12 @@ declare const HelixHeadlessChat_base: (new (...args: any[]) => import("@fusion.d
|
|
|
49
56
|
* conversations. `turns` draws a long
|
|
50
57
|
* transcript one window at a time, so the log needs a block size of its own.
|
|
51
58
|
*
|
|
59
|
+
* The log scrolls on this tier. It carries `data-scroller`, and `core` ships
|
|
60
|
+
* `[data-scroller] { overflow: auto }` into every shadow root, so pinning,
|
|
61
|
+
* history and the window work with no stylesheet. You still need to give the
|
|
62
|
+
* log a block size, such as `helix-headless-chat::part(log) { block-size:
|
|
63
|
+
* 20rem }`.
|
|
64
|
+
*
|
|
52
65
|
* **A conversation is built from three elements.** The chat holds the
|
|
53
66
|
* transcript, a `Chat.Message` stands for each turn, and a `Chat.Composer` sits
|
|
54
67
|
* in the `composer` slot. When the user sends, `helix-send` reaches the chat:
|
|
@@ -65,7 +78,9 @@ declare const HelixHeadlessChat_base: (new (...args: any[]) => import("@fusion.d
|
|
|
65
78
|
* turn says its place, such as "3 of 40", windowed or not. The transcript is
|
|
66
79
|
* a tab stop while it holds a turn, and the turn a reader moved to is one
|
|
67
80
|
* too. Moving to an older turn unpins a transcript that scrolls and scrolls
|
|
68
|
-
* the turn into view, and moving to the newest pins it again.
|
|
81
|
+
* the turn into view, and moving to the newest pins it again. When the turn
|
|
82
|
+
* holding focus is removed, focus moves to the turn that took its place, or to
|
|
83
|
+
* the one before it where it was the last.
|
|
69
84
|
*
|
|
70
85
|
* | Key | What it does |
|
|
71
86
|
* |---|---|
|
|
@@ -120,7 +135,7 @@ declare const HelixHeadlessChat_base: (new (...args: any[]) => import("@fusion.d
|
|
|
120
135
|
* @csspart indicator - The three dots before the words that say who is typing, which the styled tier animates as a message's are. Hidden from screen readers.
|
|
121
136
|
* @csspart live - The region where screen readers announce each turn once it has arrived.
|
|
122
137
|
* @csspart loading - Shown while the history loads.
|
|
123
|
-
* @csspart log - The transcript, which scrolls.
|
|
138
|
+
* @csspart log - The transcript, which scrolls. On this tier, it needs only a block size.
|
|
124
139
|
* @csspart message - The message drawn for a turn from `turns`, forwarded whole.
|
|
125
140
|
* @csspart message-actions - The controls on a turn from `turns`, beside its time.
|
|
126
141
|
* @csspart message-avatar - The avatar box of a turn from `turns`.
|
|
@@ -518,6 +533,27 @@ export declare class HelixHeadlessChat extends HelixHeadlessChat_base {
|
|
|
518
533
|
*/
|
|
519
534
|
private follow;
|
|
520
535
|
protected updated(changed: PropertyValues): void;
|
|
536
|
+
/** The turn focus is in, by its place and its identity, once a reader is in one. */
|
|
537
|
+
private held;
|
|
538
|
+
/** Notes the turn focus went into, or that it went somewhere else. */
|
|
539
|
+
private readonly handleFocusIn;
|
|
540
|
+
/**
|
|
541
|
+
* Forgets the turn once focus moves to something outside the transcript.
|
|
542
|
+
* A move to nothing is kept, since removing the turn that held focus is
|
|
543
|
+
* one.
|
|
544
|
+
*/
|
|
545
|
+
private readonly handleFocusOut;
|
|
546
|
+
/**
|
|
547
|
+
* Moves focus to a neighbor where removing the turn that held it dropped
|
|
548
|
+
* focus to the body: the turn that took its place, or the one before it
|
|
549
|
+
* where it was the last.
|
|
550
|
+
*
|
|
551
|
+
* **Focus went to the body**, and Page Down did nothing until the reader
|
|
552
|
+
* tabbed back into the transcript. List and Virtual List move focus to a
|
|
553
|
+
* neighbor the same way, as React Aria's GridList does. Focus anywhere but
|
|
554
|
+
* the body is where you put it, and is left there.
|
|
555
|
+
*/
|
|
556
|
+
private followRemoved;
|
|
521
557
|
/**
|
|
522
558
|
* Hands each turn written as a child the words it is named with, once it
|
|
523
559
|
* has hydrated, so its first render is the server's.
|
|
@@ -5,7 +5,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
7
|
var HelixHeadlessChat_1;
|
|
8
|
-
import { ChildrenController, customElement, drawIcon, focusableElements, formatWords, HelixBeforePinEvent, HelixElement, HelixHistoryEvent, HelixPinEvent, hostAria, MixinCollection, onUnlessFalse, reportRuntime, requestChange, UpgradeController, VirtualizeController, whenHydrated, words, wordsConverter, } from '@fusion.dev/helix-core';
|
|
8
|
+
import { ChildrenController, customElement, deepActiveElement, drawIcon, focusableElements, formatWords, HelixBeforePinEvent, HelixElement, HelixHistoryEvent, HelixPinEvent, hostAria, MixinCollection, onUnlessFalse, reportRuntime, requestChange, UpgradeController, VirtualizeController, whenHydrated, words, wordsConverter, } from '@fusion.dev/helix-core';
|
|
9
9
|
import { arrowDown } from '@fusion.dev/helix-icons';
|
|
10
10
|
import { nothing } from 'lit';
|
|
11
11
|
import { property, state } from 'lit/decorators.js';
|
|
@@ -98,6 +98,21 @@ let HelixHeadlessChat = class HelixHeadlessChat extends MixinCollection(HelixEle
|
|
|
98
98
|
this.resume();
|
|
99
99
|
};
|
|
100
100
|
this.warnedMixed = false;
|
|
101
|
+
this.handleFocusIn = (event) => {
|
|
102
|
+
const at = this.turnFrom(event.composedPath());
|
|
103
|
+
if (at === undefined || at < 0) {
|
|
104
|
+
this.held = undefined;
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const id = this.fed ? this.identity(this.turns[at]) : this.messages[at];
|
|
108
|
+
this.held = { at, id };
|
|
109
|
+
};
|
|
110
|
+
this.handleFocusOut = (event) => {
|
|
111
|
+
const to = event.relatedTarget;
|
|
112
|
+
if (to instanceof Node && !this.contains(to) && !this.renderRoot?.contains?.(to)) {
|
|
113
|
+
this.held = undefined;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
101
116
|
this.handleKeydown = (event) => {
|
|
102
117
|
if (event.defaultPrevented || event.altKey || event.metaKey || event.shiftKey) {
|
|
103
118
|
return;
|
|
@@ -423,6 +438,8 @@ let HelixHeadlessChat = class HelixHeadlessChat extends MixinCollection(HelixEle
|
|
|
423
438
|
connectedCallback() {
|
|
424
439
|
super.connectedCallback();
|
|
425
440
|
this.addEventListener('keydown', this.handleKeydown);
|
|
441
|
+
this.addEventListener('focusin', this.handleFocusIn);
|
|
442
|
+
this.addEventListener('focusout', this.handleFocusOut);
|
|
426
443
|
this.settling = new MutationObserver(this.handleSettle);
|
|
427
444
|
this.settling.observe(this, { attributeFilter: ['state'], subtree: true });
|
|
428
445
|
this.opening = true;
|
|
@@ -436,6 +453,8 @@ let HelixHeadlessChat = class HelixHeadlessChat extends MixinCollection(HelixEle
|
|
|
436
453
|
disconnectedCallback() {
|
|
437
454
|
super.disconnectedCallback();
|
|
438
455
|
this.removeEventListener('keydown', this.handleKeydown);
|
|
456
|
+
this.removeEventListener('focusin', this.handleFocusIn);
|
|
457
|
+
this.removeEventListener('focusout', this.handleFocusOut);
|
|
439
458
|
this.settling?.disconnect();
|
|
440
459
|
this.settling = undefined;
|
|
441
460
|
this.grower?.disconnect();
|
|
@@ -506,6 +525,23 @@ let HelixHeadlessChat = class HelixHeadlessChat extends MixinCollection(HelixEle
|
|
|
506
525
|
this.remember();
|
|
507
526
|
this.reachHistory();
|
|
508
527
|
this.announce();
|
|
528
|
+
this.followRemoved();
|
|
529
|
+
}
|
|
530
|
+
followRemoved() {
|
|
531
|
+
const held = this.held;
|
|
532
|
+
if (held === undefined) {
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
const ids = this.fed ? this.turns.map((turn) => this.identity(turn)) : this.messages;
|
|
536
|
+
if (ids.includes(held.id)) {
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
this.held = undefined;
|
|
540
|
+
const active = deepActiveElement();
|
|
541
|
+
if (ids.length === 0 || (active !== null && active !== document.body)) {
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
void this.focusTurn(Math.min(held.at, ids.length - 1));
|
|
509
545
|
}
|
|
510
546
|
tellTurns() {
|
|
511
547
|
const messages = this.messages;
|
|
@@ -670,6 +706,7 @@ let HelixHeadlessChat = class HelixHeadlessChat extends MixinCollection(HelixEle
|
|
|
670
706
|
part="log"
|
|
671
707
|
role="log"
|
|
672
708
|
aria-live="off"
|
|
709
|
+
data-scroller
|
|
673
710
|
aria-label=${this.label || this.fallbackLabel}
|
|
674
711
|
aria-busy=${busy ? 'true' : nothing}
|
|
675
712
|
?data-windowed=${fed}
|
|
@@ -31,7 +31,8 @@ declare const HelixHeadlessColorInput_base: (new (...args: any[]) => import("@fu
|
|
|
31
31
|
*
|
|
32
32
|
* The trigger and ArrowDown open the panel with focus in the square. Tab moves
|
|
33
33
|
* between the panel's controls without leaving it, and Escape closes it and
|
|
34
|
-
* returns focus to the
|
|
34
|
+
* returns focus to the control that opened it: the trigger after a press, and
|
|
35
|
+
* the field after ArrowDown.
|
|
35
36
|
*
|
|
36
37
|
* The component sets `--color-input-swatch`, so do not set it yourself. It
|
|
37
38
|
* holds the resolved color, so the styled tier can paint the swatch without
|
|
@@ -305,6 +306,19 @@ export declare class HelixHeadlessColorInput extends HelixHeadlessColorInput_bas
|
|
|
305
306
|
*/
|
|
306
307
|
protected sync(): void;
|
|
307
308
|
protected updated(changed: PropertyValues): void;
|
|
309
|
+
/** Whether the trigger's press opened the panel that is open now. */
|
|
310
|
+
private pressedOpen;
|
|
311
|
+
/**
|
|
312
|
+
* The trigger's press, which notes that it opened the panel, so Escape hands
|
|
313
|
+
* focus back to the trigger.
|
|
314
|
+
*/
|
|
315
|
+
private readonly handlePress;
|
|
316
|
+
/**
|
|
317
|
+
* Hands focus back to the control that opened the panel: the trigger after
|
|
318
|
+
* its press, which is the APG date picker dialog's rule, and the text after
|
|
319
|
+
* ArrowDown from it.
|
|
320
|
+
*/
|
|
321
|
+
protected returnFocus(): void;
|
|
308
322
|
/**
|
|
309
323
|
* The picker the field opens, behind a seam the styled tier can move.
|
|
310
324
|
*
|
|
@@ -55,6 +55,12 @@ let HelixHeadlessColorInput = class HelixHeadlessColorInput extends MixinPickerF
|
|
|
55
55
|
this.handleSampleEnd = () => {
|
|
56
56
|
this.requestOpen(true);
|
|
57
57
|
};
|
|
58
|
+
this.pressedOpen = false;
|
|
59
|
+
this.handlePress = () => {
|
|
60
|
+
const opening = !this.open;
|
|
61
|
+
this.handleTriggerClick();
|
|
62
|
+
this.pressedOpen = opening && this.open;
|
|
63
|
+
};
|
|
58
64
|
}
|
|
59
65
|
static { this.words = 'helix-color-input'; }
|
|
60
66
|
requestValue(next) {
|
|
@@ -117,6 +123,18 @@ let HelixHeadlessColorInput = class HelixHeadlessColorInput extends MixinPickerF
|
|
|
117
123
|
if (changed.has('value') && parseColor(this.value)) {
|
|
118
124
|
this.settled = this.value;
|
|
119
125
|
}
|
|
126
|
+
if (changed.has('open') && !this.open) {
|
|
127
|
+
this.pressedOpen = false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
returnFocus() {
|
|
131
|
+
const trigger = this.pressedOpen ? this.partNamed('trigger') : null;
|
|
132
|
+
this.pressedOpen = false;
|
|
133
|
+
if (trigger) {
|
|
134
|
+
trigger.focus();
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
super.returnFocus();
|
|
120
138
|
}
|
|
121
139
|
renderPicker() {
|
|
122
140
|
return html `<${this.tag('color-picker')}
|
|
@@ -204,7 +222,7 @@ let HelixHeadlessColorInput = class HelixHeadlessColorInput extends MixinPickerF
|
|
|
204
222
|
: 'swatch-value'
|
|
205
223
|
: nothing}
|
|
206
224
|
?disabled=${this.isDisabled}
|
|
207
|
-
@click=${this.
|
|
225
|
+
@click=${this.handlePress}
|
|
208
226
|
>
|
|
209
227
|
<span
|
|
210
228
|
part="preview"
|
|
@@ -445,7 +445,11 @@ export declare class HelixHeadlessColorPicker extends HelixHeadlessColorPicker_b
|
|
|
445
445
|
* field shows the color the reset put back.
|
|
446
446
|
*/
|
|
447
447
|
formResetCallback(): void;
|
|
448
|
-
/**
|
|
448
|
+
/**
|
|
449
|
+
* The line `said` holds, or nothing while it is empty. `role="alert"`, as
|
|
450
|
+
* every field's feedback line has, so the message is announced when it
|
|
451
|
+
* appears.
|
|
452
|
+
*/
|
|
449
453
|
protected renderPickerFeedback(): TemplateResult | typeof nothing;
|
|
450
454
|
/** Whether a pointer is dragging the square's handle, said as `:state(dragging)`. */
|
|
451
455
|
private dragging;
|
|
@@ -309,7 +309,7 @@ let HelixHeadlessColorPicker = class HelixHeadlessColorPicker extends MixinPicke
|
|
|
309
309
|
}
|
|
310
310
|
renderPickerFeedback() {
|
|
311
311
|
const said = this.said;
|
|
312
|
-
return said === '' ? nothing : html `<p part="feedback" id="feedback">${said}</p>`;
|
|
312
|
+
return said === '' ? nothing : html `<p part="feedback" id="feedback" role="alert">${said}</p>`;
|
|
313
313
|
}
|
|
314
314
|
settle() {
|
|
315
315
|
if (this.unsettled) {
|
|
@@ -208,7 +208,7 @@ declare const HelixHeadlessDataTable_base: (new (...args: any[]) => import("@fus
|
|
|
208
208
|
* @csspart select - The cell that holds a row's checkbox.
|
|
209
209
|
* @csspart select-all - The heading that holds the checkbox that selects every row.
|
|
210
210
|
* @csspart select-selected - Added to `select` in a selected row.
|
|
211
|
-
* @csspart table - The grid.
|
|
211
|
+
* @csspart table - The grid. While `windowed`, the box that scrolls, which needs only a block size on this tier.
|
|
212
212
|
* @csspart toggle - The mark in a row header that a press opens and closes its row by, in a tree grid.
|
|
213
213
|
* @csspart toggle-mark - The caret in `toggle`. It points along the line while the row is closed and down while it is open.
|
|
214
214
|
* @csspart toggle-open - Added to `toggle` while its row is open.
|
|
@@ -586,6 +586,10 @@ export declare class HelixHeadlessDataTable extends HelixHeadlessDataTable_base
|
|
|
586
586
|
* Off by default. Windowing costs a scroll listener, a measurement per row
|
|
587
587
|
* drawn, and a `block-size` on the scrolling box, and a table of twenty rows
|
|
588
588
|
* should pay none of them.
|
|
589
|
+
*
|
|
590
|
+
* While windowed, the `table` part scrolls on this tier: it carries
|
|
591
|
+
* `data-scroller`, and `core` ships `[data-scroller] { overflow: auto }` into
|
|
592
|
+
* every shadow root. You still need to give it a block size.
|
|
589
593
|
*/
|
|
590
594
|
windowed: boolean;
|
|
591
595
|
private sortState;
|
|
@@ -1228,6 +1228,7 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
|
|
|
1228
1228
|
part="table"
|
|
1229
1229
|
role="rowgroup"
|
|
1230
1230
|
aria-busy=${this.loading ? 'true' : nothing}
|
|
1231
|
+
?data-scroller=${this.windowed}
|
|
1231
1232
|
style=${template ? `--data-table-template: ${template}` : nothing}
|
|
1232
1233
|
@scroll=${this.handleScroll}
|
|
1233
1234
|
>
|
|
@@ -74,6 +74,10 @@ declare const HelixHeadlessDrawer_base: (new (...args: any[]) => import("@fusion
|
|
|
74
74
|
* <helix-headless-drawer id="nav">…</helix-headless-drawer>
|
|
75
75
|
* ```
|
|
76
76
|
*
|
|
77
|
+
* From its first press, a button that opens the drawer this way says whether
|
|
78
|
+
* the drawer is open: a Helix button through `expanded`, and any other
|
|
79
|
+
* element with `aria-expanded`.
|
|
80
|
+
*
|
|
77
81
|
* @since 0.1.0
|
|
78
82
|
* @status stable
|
|
79
83
|
* @category overlays
|
|
@@ -19,6 +19,7 @@ let HelixHeadlessDrawer = class HelixHeadlessDrawer extends MixinControllerOpen(
|
|
|
19
19
|
'--show': () => this.requestOpen(true),
|
|
20
20
|
'--toggle': () => this.toggleOpen(),
|
|
21
21
|
},
|
|
22
|
+
expanded: () => this.open,
|
|
22
23
|
});
|
|
23
24
|
this.dismissible = false;
|
|
24
25
|
this.dismissLabel = 'Close';
|
|
@@ -196,6 +196,7 @@ registerElementEvents({
|
|
|
196
196
|
'helix-headless-map-terrain': ['helix-map-part-change'],
|
|
197
197
|
'helix-headless-megamenu': ['helix-before-close', 'helix-before-open', 'toggle'],
|
|
198
198
|
'helix-headless-menu': [
|
|
199
|
+
'change',
|
|
199
200
|
'helix-before-close',
|
|
200
201
|
'helix-before-open',
|
|
201
202
|
'helix-filter',
|
|
@@ -394,7 +394,8 @@ export declare class HelixHeadlessListbox extends HelixHeadlessListbox_base {
|
|
|
394
394
|
* sounded the same. Said when the filter changes and the count with it, so
|
|
395
395
|
* a keystroke that leaves the same options says nothing new. An empty filter
|
|
396
396
|
* says nothing, being the list as it was given; no matches is `empty-text`,
|
|
397
|
-
* which the region says in place of this.
|
|
397
|
+
* which the region says in place of this. A load that ends says the count
|
|
398
|
+
* too, whatever the filter.
|
|
398
399
|
*/
|
|
399
400
|
private tell;
|
|
400
401
|
/**
|
|
@@ -260,13 +260,14 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
|
|
|
260
260
|
if (!this.hasUpdated) {
|
|
261
261
|
return;
|
|
262
262
|
}
|
|
263
|
-
|
|
263
|
+
const loaded = changed.get('loading') === true && !this.loading;
|
|
264
|
+
if (!changed.has('filter') && !loaded) {
|
|
264
265
|
this.tellActive(changed);
|
|
265
266
|
return;
|
|
266
267
|
}
|
|
267
268
|
const count = this.shown.length;
|
|
268
269
|
this.announced =
|
|
269
|
-
this.filter.trim() && count
|
|
270
|
+
(this.filter.trim() || loaded) && count
|
|
270
271
|
? this.resultLabel
|
|
271
272
|
? countLabel(count, this.resultLabel, this.resultsLabel, wordsLocale(this))
|
|
272
273
|
: formatWords(this, 'resultsLabel', { count })
|
|
@@ -81,6 +81,8 @@ import '../../kbd/index.js';
|
|
|
81
81
|
* @csspart surface - The panel a submenu opens in beside the row, under the menu's `submenus="flyout"`.
|
|
82
82
|
*
|
|
83
83
|
* @cssstate checked - The row is checked.
|
|
84
|
+
* @cssstate disabled - The row is disabled.
|
|
85
|
+
* @cssstate expanded - Under `submenus="flyout"`, the row's submenu is open beside it.
|
|
84
86
|
* @cssstate highlighted - In a menu with `filterable`, the arrow keys or the pointer reached the row while focus stays in the filter field.
|
|
85
87
|
* @cssstate submenu-mark - Under `submenus="flyout"`, a row in this row's submenu can be ticked or opens a submenu, so the submenu keeps its trailing column.
|
|
86
88
|
* @cssstate submenu-start - Under `submenus="flyout"`, a row in this row's submenu has content before its label, so the submenu keeps its leading column.
|
|
@@ -93,6 +93,8 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
|
|
|
93
93
|
super.willUpdate(changed);
|
|
94
94
|
this.toggleState('checked', this.checked);
|
|
95
95
|
this.toggleState('highlighted', this.virtual && this.highlighted);
|
|
96
|
+
this.toggleState('disabled', this.disabled);
|
|
97
|
+
this.toggleState('expanded', this.expanded);
|
|
96
98
|
if (!this.childrenKnown) {
|
|
97
99
|
return;
|
|
98
100
|
}
|