@fusion.dev/helix-headless 0.177.0 → 0.177.1
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 +774 -71
- package/dist/button/root/button-root.js +1 -0
- package/dist/chat/composer/chat-composer.d.ts +1 -1
- package/dist/chat/composer/chat-composer.js +1 -1
- package/dist/color-picker/root/color-picker-root.d.ts +10 -0
- package/dist/color-picker/root/color-picker-root.js +20 -13
- package/dist/combobox/root/combobox-root.d.ts +13 -0
- package/dist/combobox/root/combobox-root.js +16 -4
- package/dist/file-input/root/file-input-root.d.ts +3 -2
- package/dist/file-input/root/file-input-root.js +5 -2
- package/dist/pin-input/root/pin-input-root.js +1 -0
- package/dist/time-input/root/time-input-root.d.ts +7 -1
- package/dist/time-input/root/time-input-root.js +6 -3
- package/lint.json +1 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +156 -18
- package/types/vue.d.ts +15 -3
- package/types/words.d.ts +4 -2
|
@@ -49,7 +49,7 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
49
49
|
*
|
|
50
50
|
* @event {HelixSendEvent} helix-send - Fired when the user sends their turn, and cancelable.
|
|
51
51
|
* @event {HelixTypingEvent} helix-typing - The user started or stopped writing, with `typing` saying which, as the composer's `typing` does.
|
|
52
|
-
* @event {Event} input - The user changed the text. Read it from `value`, for a typing indicator or a saved draft.
|
|
52
|
+
* @event {Event} input - The user changed the text. Read it from `value`, for a typing indicator or a saved draft. Bubbles and is composed, as a native field's is.
|
|
53
53
|
*
|
|
54
54
|
* @csspart composer - The whole composer.
|
|
55
55
|
* @csspart end - The box around the `end` slot.
|
|
@@ -41,7 +41,7 @@ let HelixHeadlessChatComposer = class HelixHeadlessChatComposer extends HelixFor
|
|
|
41
41
|
event.stopPropagation();
|
|
42
42
|
this.value = event.target.value;
|
|
43
43
|
this.edited = true;
|
|
44
|
-
this.
|
|
44
|
+
this.announceInput();
|
|
45
45
|
this.markTyping(this.value !== '');
|
|
46
46
|
};
|
|
47
47
|
this.handleSend = () => {
|
|
@@ -633,6 +633,16 @@ export declare class HelixHeadlessColorPicker extends HelixHeadlessColorPicker_b
|
|
|
633
633
|
* is redrawn though the value it settles on is the one drawn before.
|
|
634
634
|
*/
|
|
635
635
|
protected renderFields(color: Color): TemplateResult;
|
|
636
|
+
/**
|
|
637
|
+
* A channel's name, after the picker's own where the picker has one.
|
|
638
|
+
*
|
|
639
|
+
* **Every channel the same way**, as React Aria's color area names its own:
|
|
640
|
+
* "Brand, Saturation" says which picker on a page with two. The square's
|
|
641
|
+
* axes and the wheel took the picker's name, and the hue and opacity ramps
|
|
642
|
+
* and the number fields did not, so two pickers on a page gave two sliders
|
|
643
|
+
* named "Hue", measured in Chromium's tree.
|
|
644
|
+
*/
|
|
645
|
+
protected channelName(channel: string): string;
|
|
636
646
|
/**
|
|
637
647
|
* One of the ramps under the square.
|
|
638
648
|
*
|
|
@@ -407,11 +407,15 @@ let HelixHeadlessColorPicker = class HelixHeadlessColorPicker extends MixinPicke
|
|
|
407
407
|
}
|
|
408
408
|
renderFields(color) {
|
|
409
409
|
return html `<div part="fields">
|
|
410
|
-
${channelFields(this.value, color, this.format ?? 'hex', this.alpha).map((field) =>
|
|
411
|
-
|
|
410
|
+
${channelFields(this.value, color, this.format ?? 'hex', this.alpha).map((field) => {
|
|
411
|
+
const word = this.channelWord(field.channel);
|
|
412
|
+
const name = this.channelName(word);
|
|
413
|
+
return html `<label part="field">
|
|
414
|
+
<span part="field-label">${word}</span>
|
|
412
415
|
<input
|
|
413
416
|
part="field-input"
|
|
414
417
|
type="number"
|
|
418
|
+
aria-label=${name === word ? nothing : name}
|
|
415
419
|
inputmode=${field.step < 1 ? 'decimal' : 'numeric'}
|
|
416
420
|
min=${field.min}
|
|
417
421
|
max=${field.max}
|
|
@@ -421,14 +425,19 @@ let HelixHeadlessColorPicker = class HelixHeadlessColorPicker extends MixinPicke
|
|
|
421
425
|
?readonly=${this.readOnly}
|
|
422
426
|
@input=${(event) => this.handleField(field.channel, event)}
|
|
423
427
|
@change=${(event) => {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
+
event.stopPropagation();
|
|
429
|
+
this.settle();
|
|
430
|
+
this.requestUpdate();
|
|
431
|
+
}}
|
|
428
432
|
/>
|
|
429
|
-
</label
|
|
433
|
+
</label>`;
|
|
434
|
+
})}
|
|
430
435
|
</div>`;
|
|
431
436
|
}
|
|
437
|
+
channelName(channel) {
|
|
438
|
+
const owner = this.getAttribute('aria-label')?.trim() || this.named;
|
|
439
|
+
return owner ? `${owner}, ${channel}` : channel;
|
|
440
|
+
}
|
|
432
441
|
renderChannel(channel, label, max, value, spoken = '') {
|
|
433
442
|
return html `<${this.tag('slider')}
|
|
434
443
|
part="channel"
|
|
@@ -439,7 +448,7 @@ let HelixHeadlessColorPicker = class HelixHeadlessColorPicker extends MixinPicke
|
|
|
439
448
|
tooltips: channel-tooltips"
|
|
440
449
|
data-channel=${channel}
|
|
441
450
|
aria-invalid=${this.marked ? 'true' : nothing}
|
|
442
|
-
label=${label}
|
|
451
|
+
label=${this.channelName(label)}
|
|
443
452
|
max=${max}
|
|
444
453
|
min="0"
|
|
445
454
|
value-text=${spoken || nothing}
|
|
@@ -568,8 +577,6 @@ let HelixHeadlessColorPicker = class HelixHeadlessColorPicker extends MixinPicke
|
|
|
568
577
|
const reversed = square('brightness');
|
|
569
578
|
const hue = hueName({ alpha: 100, brightness: 100, hue: color.hue, saturation: 100 }, this.colorWords, locale);
|
|
570
579
|
const hueSaid = (value) => new Intl.ListFormat(locale, { style: 'short', type: 'unit' }).format([String(value), hue]);
|
|
571
|
-
const owner = this.getAttribute('aria-label')?.trim() || this.named;
|
|
572
|
-
const axisName = (channel) => (owner ? `${owner}, ${channel}` : channel);
|
|
573
580
|
const area = html `<div
|
|
574
581
|
part="grid"
|
|
575
582
|
style="--color-picker-hue: ${color.hue}; --color-picker-saturation: ${color.saturation}%; --color-picker-brightness: ${color.brightness}%"
|
|
@@ -584,7 +591,7 @@ let HelixHeadlessColorPicker = class HelixHeadlessColorPicker extends MixinPicke
|
|
|
584
591
|
type="range"
|
|
585
592
|
min="0"
|
|
586
593
|
max="100"
|
|
587
|
-
aria-label=${
|
|
594
|
+
aria-label=${this.channelName(this.saturationLabel)}
|
|
588
595
|
aria-describedby=${this.describedBy}
|
|
589
596
|
aria-valuetext=${both}
|
|
590
597
|
aria-invalid=${this.marked ? 'true' : nothing}
|
|
@@ -601,7 +608,7 @@ let HelixHeadlessColorPicker = class HelixHeadlessColorPicker extends MixinPicke
|
|
|
601
608
|
max="100"
|
|
602
609
|
tabindex="-1"
|
|
603
610
|
style="writing-mode: vertical-lr; direction: rtl"
|
|
604
|
-
aria-label=${
|
|
611
|
+
aria-label=${this.channelName(this.brightnessLabel)}
|
|
605
612
|
aria-describedby=${this.describedBy}
|
|
606
613
|
aria-valuetext=${reversed}
|
|
607
614
|
aria-invalid=${this.marked ? 'true' : nothing}
|
|
@@ -630,7 +637,7 @@ let HelixHeadlessColorPicker = class HelixHeadlessColorPicker extends MixinPicke
|
|
|
630
637
|
min="0"
|
|
631
638
|
max="360"
|
|
632
639
|
dir="ltr"
|
|
633
|
-
aria-label=${
|
|
640
|
+
aria-label=${this.channelName(this.hueLabel)}
|
|
634
641
|
aria-describedby=${this.describedBy}
|
|
635
642
|
aria-valuetext=${hueSaid(color.hue)}
|
|
636
643
|
aria-invalid=${this.marked ? 'true' : nothing}
|
|
@@ -395,6 +395,8 @@ export declare class HelixHeadlessCombobox extends HelixHeadlessCombobox_base {
|
|
|
395
395
|
* "France", and the form received two entries.
|
|
396
396
|
*/
|
|
397
397
|
private get values();
|
|
398
|
+
/** The values a value holds, read as `values` reads this element's own. */
|
|
399
|
+
private valuesIn;
|
|
398
400
|
/** The answers created here, which no option holds, kept so their commas read as theirs. */
|
|
399
401
|
private readonly created;
|
|
400
402
|
/**
|
|
@@ -577,6 +579,17 @@ export declare class HelixHeadlessCombobox extends HelixHeadlessCombobox_base {
|
|
|
577
579
|
* `result-label` for one where a page still sets it.
|
|
578
580
|
*/
|
|
579
581
|
private countSaid;
|
|
582
|
+
/**
|
|
583
|
+
* Says the option a choice changed, for VoiceOver, which says nothing when
|
|
584
|
+
* `aria-selected` changes: `Beta, selected` for one taken, and its words
|
|
585
|
+
* alone for one let go, as the highlight says an option not chosen.
|
|
586
|
+
*
|
|
587
|
+
* **The option the choice changed, read from the values before and after.**
|
|
588
|
+
* With `multiple` the value is the whole list, and the option was looked up
|
|
589
|
+
* by it, so no option matched and no choice was ever said: measured with
|
|
590
|
+
* Beta chosen, Enter let it go and the live region kept `Beta, selected`.
|
|
591
|
+
*/
|
|
592
|
+
private sayChoice;
|
|
580
593
|
/**
|
|
581
594
|
* Takes what the list announced and stops it there.
|
|
582
595
|
*
|
|
@@ -188,12 +188,12 @@ let HelixHeadlessCombobox = class HelixHeadlessCombobox extends MixinControllerO
|
|
|
188
188
|
this.handleChange = (event) => {
|
|
189
189
|
event.stopPropagation();
|
|
190
190
|
const { value } = event.detail;
|
|
191
|
+
const before = this.values;
|
|
191
192
|
if (!this.requestValue(value)) {
|
|
192
193
|
return;
|
|
193
194
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
this.status.say(formatWords(this, 'selectedLabel', { name: chosen.text }));
|
|
195
|
+
if (onApple()) {
|
|
196
|
+
this.sayChoice(before, this.valuesIn(value));
|
|
197
197
|
}
|
|
198
198
|
this.emptied = false;
|
|
199
199
|
this.typed = '';
|
|
@@ -253,11 +253,14 @@ let HelixHeadlessCombobox = class HelixHeadlessCombobox extends MixinControllerO
|
|
|
253
253
|
return this.partNamed('listbox');
|
|
254
254
|
}
|
|
255
255
|
get values() {
|
|
256
|
+
return this.valuesIn(this.value);
|
|
257
|
+
}
|
|
258
|
+
valuesIn(value) {
|
|
256
259
|
const known = new Set(this.created);
|
|
257
260
|
for (const option of optionsAmong(this.knownChildren)) {
|
|
258
261
|
known.add(option.value);
|
|
259
262
|
}
|
|
260
|
-
return chosenValues(
|
|
263
|
+
return chosenValues(value, known, this.multiple);
|
|
261
264
|
}
|
|
262
265
|
get chosen() {
|
|
263
266
|
const options = optionsAmong(this.knownChildren);
|
|
@@ -460,6 +463,15 @@ let HelixHeadlessCombobox = class HelixHeadlessCombobox extends MixinControllerO
|
|
|
460
463
|
? countLabel(count, this.resultLabel, this.resultsLabel, wordsLocale(this))
|
|
461
464
|
: formatWords(this, 'resultsLabel', { count });
|
|
462
465
|
}
|
|
466
|
+
sayChoice(before, after) {
|
|
467
|
+
const taken = this.multiple ? after.find((one) => !before.includes(one)) : after[0];
|
|
468
|
+
const changed = taken ?? before.find((one) => !after.includes(one));
|
|
469
|
+
const option = this.listbox?.options.find((one) => one.value === changed);
|
|
470
|
+
if (!option) {
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
this.status.say(taken === undefined ? option.text : formatWords(this, 'selectedLabel', { name: option.text }));
|
|
474
|
+
}
|
|
463
475
|
removeLastToken() {
|
|
464
476
|
const last = this.values.at(-1);
|
|
465
477
|
if (last !== undefined) {
|
|
@@ -360,8 +360,9 @@ export declare class HelixHeadlessFileInput extends HelixHeadlessFileInput_base
|
|
|
360
360
|
*
|
|
361
361
|
* Not through `commit`, and that is the point of the split: a reset is the
|
|
362
362
|
* form's decision and there is nothing here for a listener to refuse. What it
|
|
363
|
-
* owes is the report
|
|
364
|
-
* `
|
|
363
|
+
* owes is the report to the form, and no `input` or `change`: a native file
|
|
364
|
+
* input sends neither on `form.reset()`, measured in Chromium, where this
|
|
365
|
+
* sent both.
|
|
365
366
|
*/
|
|
366
367
|
formResetCallback(): void;
|
|
367
368
|
/**
|
|
@@ -224,10 +224,13 @@ let HelixHeadlessFileInput = class HelixHeadlessFileInput extends MixinField(Hel
|
|
|
224
224
|
}
|
|
225
225
|
control.files = holding.files;
|
|
226
226
|
}
|
|
227
|
-
settle() {
|
|
227
|
+
settle(announce = true) {
|
|
228
228
|
this.committed = this.files;
|
|
229
229
|
this.sync();
|
|
230
230
|
this.requestUpdate();
|
|
231
|
+
if (!announce) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
231
234
|
this.announceInput();
|
|
232
235
|
this.announceChange();
|
|
233
236
|
}
|
|
@@ -265,7 +268,7 @@ let HelixHeadlessFileInput = class HelixHeadlessFileInput extends MixinField(Hel
|
|
|
265
268
|
if (this.control) {
|
|
266
269
|
restoreValue(this.control, '');
|
|
267
270
|
}
|
|
268
|
-
this.settle();
|
|
271
|
+
this.settle(false);
|
|
269
272
|
}
|
|
270
273
|
renderMeta(file) {
|
|
271
274
|
return html `<${this.tag('tag')} part="size"
|
|
@@ -133,6 +133,7 @@ let HelixHeadlessPinInput = class HelixHeadlessPinInput extends MixinControllerV
|
|
|
133
133
|
}
|
|
134
134
|
this.placeCaret(true);
|
|
135
135
|
if (applied && this.value === code && code.length === this.count) {
|
|
136
|
+
this.runPendingUpdate();
|
|
136
137
|
this.dispatchEvent(new HelixCompleteEvent({ value: code }));
|
|
137
138
|
this.commit();
|
|
138
139
|
if (this.autosubmit) {
|
|
@@ -277,7 +277,13 @@ export declare class HelixHeadlessTimeInput extends HelixHeadlessTimeInput_base
|
|
|
277
277
|
unavailableMessage: string;
|
|
278
278
|
/**
|
|
279
279
|
* The message for text that is no time, where `bad-input-message` is not
|
|
280
|
-
* set. `{mode}` is `single`, `range`, or `multiple`,
|
|
280
|
+
* set. `{mode}` is `single`, `range`, or `multiple`, `{example}` is a time
|
|
281
|
+
* written as the field writes one, `2:30 PM` in `en-US` and `14:30` in
|
|
282
|
+
* `de-DE`, and `{shape}` is `HH:mm`, or `HH:mm:ss` with `seconds`.
|
|
283
|
+
*
|
|
284
|
+
* **The default names a time the field reads back.** It said "Enter a time
|
|
285
|
+
* as HH:mm." to a reader whose field showed `2:30 PM`, and a pattern token
|
|
286
|
+
* is words for a developer.
|
|
281
287
|
*/
|
|
282
288
|
unreadableMessage: string;
|
|
283
289
|
/** Every time in the value, which may hold more than one. */
|
|
@@ -36,7 +36,7 @@ let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinSegmented
|
|
|
36
36
|
this.timeStyle = 'local';
|
|
37
37
|
this.triggerLabel = 'Choose a time';
|
|
38
38
|
this.unavailableMessage = 'Choose a time that is available.';
|
|
39
|
-
this.unreadableMessage = '{mode, select, single {Enter a time as {
|
|
39
|
+
this.unreadableMessage = '{mode, select, single {Enter a time such as {example}.} other {Enter times such as {example}, separated by commas.}}';
|
|
40
40
|
this.draft = '';
|
|
41
41
|
this.handleTextCommit = (event) => {
|
|
42
42
|
if (this.mode !== 'multiple') {
|
|
@@ -182,10 +182,13 @@ let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinSegmented
|
|
|
182
182
|
}
|
|
183
183
|
validate() {
|
|
184
184
|
const held = this.held;
|
|
185
|
-
const shape = this.seconds ? 'HH:mm:ss' : 'HH:mm';
|
|
186
185
|
if (held.length > 0 && !held.every((one) => this.isTime(one))) {
|
|
187
186
|
return (this.badInputMessage ||
|
|
188
|
-
formatWords(this, 'unreadableMessage', {
|
|
187
|
+
formatWords(this, 'unreadableMessage', {
|
|
188
|
+
example: this.written(this.seconds ? '14:30:15' : '14:30'),
|
|
189
|
+
mode: this.mode ?? 'single',
|
|
190
|
+
shape: this.seconds ? 'HH:mm:ss' : 'HH:mm',
|
|
191
|
+
}));
|
|
189
192
|
}
|
|
190
193
|
const rules = { disableTimes: this.disableTimes, max: this.max, min: this.min };
|
|
191
194
|
const faults = held.map((one) => timeFault(one, rules));
|