@fusion.dev/helix-headless 0.169.0 → 0.171.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 +663 -95
- package/dist/chat/composer/chat-composer.d.ts +2 -1
- package/dist/chat/composer/chat-composer.js +7 -1
- package/dist/chat/root/chat-root.d.ts +36 -6
- package/dist/chat/root/chat-root.js +53 -5
- package/dist/code-block/root/code-block-root.d.ts +20 -2
- package/dist/code-block/root/code-block-root.js +29 -8
- package/dist/color-picker/root/color-picker-root.d.ts +50 -7
- package/dist/color-picker/root/color-picker-root.js +62 -1
- package/dist/combobox/root/combobox-root.d.ts +16 -6
- package/dist/combobox/root/combobox-root.js +9 -2
- package/dist/command/root/command-root.d.ts +16 -1
- package/dist/command/root/command-root.js +8 -0
- package/dist/data-table/root/data-table-root.d.ts +40 -5
- package/dist/data-table/root/data-table-root.js +35 -7
- package/dist/filter-input/chip/filter-chip.d.ts +13 -3
- package/dist/filter-input/chip/filter-chip.js +5 -0
- package/dist/filter-input/menu/filter-menu.js +0 -1
- package/dist/filter-input/root/filter-input-root.d.ts +71 -4
- package/dist/filter-input/root/filter-input-root.js +87 -2
- package/dist/format-date/root/format-date-root.d.ts +3 -1
- package/dist/input/root/input-root.d.ts +6 -0
- package/dist/input/root/input-root.js +13 -0
- package/dist/listbox/root/listbox-root.d.ts +6 -0
- package/dist/listbox/root/listbox-root.js +6 -1
- package/dist/number-input/root/number-input-root.d.ts +16 -6
- package/dist/number-input/root/number-input-root.js +15 -4
- package/dist/select/root/select-root.js +2 -0
- package/dist/steps/root/steps-root.d.ts +32 -8
- package/dist/steps/root/steps-root.js +70 -9
- package/dist/steps/step/step.d.ts +9 -1
- package/dist/steps/step/step.js +5 -0
- package/dist/video-playlist/root/video-playlist-root.d.ts +19 -1
- package/dist/video-playlist/root/video-playlist-root.js +14 -3
- package/dist/virtual-list/root/virtual-list-root.d.ts +8 -0
- package/dist/virtual-list/root/virtual-list-root.js +7 -2
- package/lint.json +1 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +510 -126
- package/types/vue.d.ts +109 -26
- package/types/words.d.ts +12 -2
|
@@ -25,7 +25,8 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
25
25
|
* field taking more. A draft that fails them does not send, and the composer
|
|
26
26
|
* takes `:state(invalid)` and `aria-invalid` once the reader has tried to send
|
|
27
27
|
* it, the form was submitted, or the reader edited the field and left it.
|
|
28
|
-
*
|
|
28
|
+
* Sending an empty required composer counts as trying. Tabbing past an empty
|
|
29
|
+
* composer marks nothing.
|
|
29
30
|
*
|
|
30
31
|
* **`typing` says whether the user is writing**, and `helix-typing` is sent
|
|
31
32
|
* each time it changes: when they start, and when they send or stop for
|
|
@@ -69,7 +69,13 @@ let HelixHeadlessChatComposer = class HelixHeadlessChatComposer extends HelixFor
|
|
|
69
69
|
}
|
|
70
70
|
send() {
|
|
71
71
|
const value = this.value.trim();
|
|
72
|
-
if (
|
|
72
|
+
if (this.isDisabled) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
if (value === '') {
|
|
76
|
+
if (this.required) {
|
|
77
|
+
this.touch();
|
|
78
|
+
}
|
|
73
79
|
return false;
|
|
74
80
|
}
|
|
75
81
|
if (!this.internals.validity.valid) {
|
|
@@ -43,8 +43,10 @@ declare const HelixHeadlessChat_base: (new (...args: any[]) => import("@fusion.d
|
|
|
43
43
|
* there when the transcript opened. A turn is said with its author first, such
|
|
44
44
|
* as "Ada: It ships today" (`arrival-label`), where its author can be named.
|
|
45
45
|
*
|
|
46
|
-
* Pass the turns as children or as `turns` data. Do not use both
|
|
47
|
-
*
|
|
46
|
+
* Pass the turns as children or as `turns` data. Do not use both: a chat given
|
|
47
|
+
* both draws every turn from `turns` and then the children, and says so once
|
|
48
|
+
* in the console. Children render from the server and suit most
|
|
49
|
+
* conversations. `turns` draws a long
|
|
48
50
|
* transcript one window at a time, so the log needs a block size of its own.
|
|
49
51
|
*
|
|
50
52
|
* **A conversation is built from three elements.** The chat holds the
|
|
@@ -60,7 +62,10 @@ declare const HelixHeadlessChat_base: (new (...args: any[]) => import("@fusion.d
|
|
|
60
62
|
* it takes them away. Anything slotted into `status` is drawn first.
|
|
61
63
|
*
|
|
62
64
|
* **Keyboard.** The transcript moves between turns the way a feed does. Each
|
|
63
|
-
* turn says its place, such as "3 of 40", windowed or not.
|
|
65
|
+
* turn says its place, such as "3 of 40", windowed or not. The transcript is
|
|
66
|
+
* a tab stop while it holds a turn, and the turn a reader moved to is one
|
|
67
|
+
* 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.
|
|
64
69
|
*
|
|
65
70
|
* | Key | What it does |
|
|
66
71
|
* |---|---|
|
|
@@ -142,6 +147,11 @@ declare const HelixHeadlessChat_base: (new (...args: any[]) => import("@fusion.d
|
|
|
142
147
|
* @csspart status - The status region, outside the log.
|
|
143
148
|
* @csspart turn - The wrapper around the message drawn for one turn from `turns`.
|
|
144
149
|
* @csspart typing - The words that say who is typing, in the status band, while `typing` names someone.
|
|
150
|
+
*
|
|
151
|
+
* @cssstate empty - The conversation has no turns.
|
|
152
|
+
* @cssstate loading - Older turns are loading: `history` is `loading`.
|
|
153
|
+
* @cssstate pinned - The transcript follows its newest turn.
|
|
154
|
+
* @cssstate typing - `typing` names someone on the other side.
|
|
145
155
|
*/
|
|
146
156
|
export declare class HelixHeadlessChat extends HelixHeadlessChat_base {
|
|
147
157
|
/**
|
|
@@ -362,13 +372,16 @@ export declare class HelixHeadlessChat extends HelixHeadlessChat_base {
|
|
|
362
372
|
/** The element that scrolls, once there is one. */
|
|
363
373
|
private get log();
|
|
364
374
|
/**
|
|
365
|
-
* Makes the log a tab stop while it scrolls, and takes the
|
|
366
|
-
*
|
|
375
|
+
* Makes the log a tab stop while it holds a turn or scrolls, and takes the
|
|
376
|
+
* stop away from an empty log.
|
|
367
377
|
*
|
|
368
378
|
* **A reader on a keyboard scrolls the history from the log itself.**
|
|
369
379
|
* Chromium and Firefox make a box that scrolls focusable on their own, and
|
|
370
380
|
* Safari does not, so there the arrow keys could not reach a long history.
|
|
371
|
-
*
|
|
381
|
+
*
|
|
382
|
+
* **And it is where the turns are entered.** Page Down from the log goes to
|
|
383
|
+
* the turn at the top of the view. A log that fits was no stop at all, so a
|
|
384
|
+
* conversation whose turns held no control had no way into them by keyboard.
|
|
372
385
|
*/
|
|
373
386
|
private markScroller;
|
|
374
387
|
/** Whether the reader is within `threshold` of the bottom right now. */
|
|
@@ -491,6 +504,10 @@ export declare class HelixHeadlessChat extends HelixHeadlessChat_base {
|
|
|
491
504
|
connectedCallback(): void;
|
|
492
505
|
disconnectedCallback(): void;
|
|
493
506
|
protected willUpdate(changed: PropertyValues): void;
|
|
507
|
+
/** Whether the chat has said it was given turns both ways. */
|
|
508
|
+
private warnedMixed;
|
|
509
|
+
/** Says once that turns arrived both as children and as `turns`. */
|
|
510
|
+
private warnMixed;
|
|
494
511
|
/**
|
|
495
512
|
* Keeps the window on the same turns when the set changes around them.
|
|
496
513
|
*
|
|
@@ -522,6 +539,8 @@ export declare class HelixHeadlessChat extends HelixHeadlessChat_base {
|
|
|
522
539
|
* and never on a turn nobody moved to.
|
|
523
540
|
*/
|
|
524
541
|
private focusTurn;
|
|
542
|
+
/** Draws turn `at` where it is windowed and not drawn, and visits it. */
|
|
543
|
+
private showTurn;
|
|
525
544
|
/** The turn that is a tab stop because a reader moved to it. */
|
|
526
545
|
private stop;
|
|
527
546
|
/**
|
|
@@ -534,6 +553,17 @@ export declare class HelixHeadlessChat extends HelixHeadlessChat_base {
|
|
|
534
553
|
* `tabindex` you wrote on a turn is left as it is.
|
|
535
554
|
*/
|
|
536
555
|
private visit;
|
|
556
|
+
/** Where this element last scrolled the transcript to show a turn, until the scroll that follows. */
|
|
557
|
+
private placed;
|
|
558
|
+
/**
|
|
559
|
+
* Scrolls the transcript, and nothing around it, until `turn` is in view,
|
|
560
|
+
* and notes the turn at the top as the reader's place. A pinned transcript
|
|
561
|
+
* is left at the bottom, which is where whoever holds the pin wants it.
|
|
562
|
+
*
|
|
563
|
+
* The edge nearest the turn moves the least, and a turn taller than the
|
|
564
|
+
* transcript is shown from its start.
|
|
565
|
+
*/
|
|
566
|
+
private reveal;
|
|
537
567
|
/** The first control in the regions `names` holds, in their order. */
|
|
538
568
|
private firstControlIn;
|
|
539
569
|
/**
|
|
@@ -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, requestChange, UpgradeController, VirtualizeController, whenHydrated, words, wordsConverter, } from '@fusion.dev/helix-core';
|
|
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';
|
|
9
9
|
import { arrowDown } from '@fusion.dev/helix-icons';
|
|
10
10
|
import { nothing } from 'lit';
|
|
11
11
|
import { property, state } from 'lit/decorators.js';
|
|
@@ -97,6 +97,7 @@ let HelixHeadlessChat = class HelixHeadlessChat extends MixinCollection(HelixEle
|
|
|
97
97
|
this.handleResume = () => {
|
|
98
98
|
this.resume();
|
|
99
99
|
};
|
|
100
|
+
this.warnedMixed = false;
|
|
100
101
|
this.handleKeydown = (event) => {
|
|
101
102
|
if (event.defaultPrevented || event.altKey || event.metaKey || event.shiftKey) {
|
|
102
103
|
return;
|
|
@@ -138,6 +139,13 @@ let HelixHeadlessChat = class HelixHeadlessChat extends MixinCollection(HelixEle
|
|
|
138
139
|
};
|
|
139
140
|
this.handleScroll = () => {
|
|
140
141
|
const log = this.log;
|
|
142
|
+
const placed = this.placed;
|
|
143
|
+
this.placed = undefined;
|
|
144
|
+
if (log && placed !== undefined && Math.abs(log.scrollTop - placed) < 1 && !this.pinned) {
|
|
145
|
+
this.remember();
|
|
146
|
+
this.reachHistory();
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
141
149
|
if (this.pinned && log && log.scrollTop >= this.stuckAt - 1 && !this.atBottom) {
|
|
142
150
|
this.stick();
|
|
143
151
|
}
|
|
@@ -183,7 +191,8 @@ let HelixHeadlessChat = class HelixHeadlessChat extends MixinCollection(HelixEle
|
|
|
183
191
|
if (!log) {
|
|
184
192
|
return;
|
|
185
193
|
}
|
|
186
|
-
|
|
194
|
+
const turns = this.fed ? this.turns.length : this.messages.length;
|
|
195
|
+
if (turns > 0 || log.scrollHeight > log.clientHeight) {
|
|
187
196
|
log.tabIndex = 0;
|
|
188
197
|
}
|
|
189
198
|
else {
|
|
@@ -449,6 +458,22 @@ let HelixHeadlessChat = class HelixHeadlessChat extends MixinCollection(HelixEle
|
|
|
449
458
|
if (changed.has('turns')) {
|
|
450
459
|
this.follow(changed.get('turns'));
|
|
451
460
|
}
|
|
461
|
+
this.warnMixed();
|
|
462
|
+
this.toggleState('pinned', this.pinned);
|
|
463
|
+
this.toggleState('typing', this.typing !== '');
|
|
464
|
+
this.toggleState('loading', this.history === 'loading');
|
|
465
|
+
this.toggleState('empty', this.childrenKnown && this.turns.length === 0 && this.messages.length === 0);
|
|
466
|
+
}
|
|
467
|
+
warnMixed() {
|
|
468
|
+
if (this.warnedMixed || this.turns.length === 0 || this.messages.length === 0) {
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
this.warnedMixed = true;
|
|
472
|
+
reportRuntime({
|
|
473
|
+
kind: 'mixed-turns',
|
|
474
|
+
message: `A chat was given ${this.messages.length} turns as children and ${this.turns.length} as \`turns\`, and draws the children after every turn from \`turns\`. Pass one or the other.`,
|
|
475
|
+
tag: this.localName,
|
|
476
|
+
});
|
|
452
477
|
}
|
|
453
478
|
follow(previous) {
|
|
454
479
|
if (!previous?.length || this.turns.length === 0) {
|
|
@@ -526,9 +551,15 @@ let HelixHeadlessChat = class HelixHeadlessChat extends MixinCollection(HelixEle
|
|
|
526
551
|
const count = this.fed ? this.turns.length : this.messages.length;
|
|
527
552
|
const log = this.log;
|
|
528
553
|
const scrolls = log !== null && log !== undefined && log.scrollHeight > log.clientHeight;
|
|
529
|
-
if (at < count - 1 && this.pinned && scrolls) {
|
|
530
|
-
this.
|
|
554
|
+
if (at < count - 1 && this.pinned && scrolls && this.requestPinned(false)) {
|
|
555
|
+
await this.updateComplete;
|
|
556
|
+
}
|
|
557
|
+
await this.showTurn(at);
|
|
558
|
+
if (at === count - 1 && !this.pinned && this.atBottom) {
|
|
559
|
+
this.requestPinned(true);
|
|
531
560
|
}
|
|
561
|
+
}
|
|
562
|
+
async showTurn(at) {
|
|
532
563
|
if (this.fed) {
|
|
533
564
|
const drawn = () => this.renderRoot
|
|
534
565
|
?.querySelector(`[part="turn"][data-index="${at}"]`)
|
|
@@ -559,7 +590,24 @@ let HelixHeadlessChat = class HelixHeadlessChat extends MixinCollection(HelixEle
|
|
|
559
590
|
this.stop = turn;
|
|
560
591
|
}
|
|
561
592
|
}
|
|
562
|
-
turn.focus();
|
|
593
|
+
turn.focus({ preventScroll: true });
|
|
594
|
+
this.reveal(turn);
|
|
595
|
+
}
|
|
596
|
+
reveal(turn) {
|
|
597
|
+
const log = this.log;
|
|
598
|
+
if (!log || this.pinned) {
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
const box = log.getBoundingClientRect();
|
|
602
|
+
const rect = turn.getBoundingClientRect();
|
|
603
|
+
const by = rect.top < box.top || rect.height > box.height
|
|
604
|
+
? rect.top - box.top
|
|
605
|
+
: Math.max(0, rect.bottom - box.bottom);
|
|
606
|
+
if (Math.abs(by) >= 1) {
|
|
607
|
+
log.scrollTop += by;
|
|
608
|
+
this.placed = log.scrollTop;
|
|
609
|
+
}
|
|
610
|
+
this.remember();
|
|
563
611
|
}
|
|
564
612
|
firstControlIn(names) {
|
|
565
613
|
for (const name of names) {
|
|
@@ -18,7 +18,11 @@ import '../../copy-button/index.js';
|
|
|
18
18
|
* from the accessibility tree and left out of a copy. Set `mark-lines` to
|
|
19
19
|
* mark lines a reader should look at, such as `2, 4-6`: the block draws a
|
|
20
20
|
* band behind each run, placed where the lines are laid out, so it follows a
|
|
21
|
-
* highlighter's markup and lines that wrap.
|
|
21
|
+
* highlighter's markup and lines that wrap. `added-lines` and
|
|
22
|
+
* `removed-lines` mark lines the same way as inserted and deleted, as
|
|
23
|
+
* Expressive Code's `ins` and `del` do, in code that is not a diff. The bands
|
|
24
|
+
* are drawn and hidden from screen readers, so say in the text around the
|
|
25
|
+
* block what a marked line means. Set `wrap` to wrap long lines
|
|
22
26
|
* rather than scroll them; a gutter then numbers each line where it starts.
|
|
23
27
|
* Set `wrappable` for a button in the header that turns `wrap` on and off,
|
|
24
28
|
* pressed while the lines wrap.
|
|
@@ -93,6 +97,8 @@ import '../../copy-button/index.js';
|
|
|
93
97
|
* @slot actions - Controls for the block, in the header before the copy button.
|
|
94
98
|
* @slot copied-icon - The copy button's icon after a copy. Defaults to a check mark.
|
|
95
99
|
* @slot copy-icon - The copy button's icon before a copy. Defaults to a clipboard.
|
|
100
|
+
* @slot expand-icon - An icon on the button that shows every line of a capped block, before its words.
|
|
101
|
+
* @slot label - The block's name in the header, such as a file icon beside the file name. Replaces `label` and `language` there, and draws the header.
|
|
96
102
|
*
|
|
97
103
|
* @event {Event} change - A file tab showed its file, whose name `file` now holds.
|
|
98
104
|
* @event {HelixBeforeChangeEvent} helix-before-change - A file tab is about to show its file, with the file's name in `detail.value`. Cancelable: cancel it to keep the file shown.
|
|
@@ -134,7 +140,9 @@ import '../../copy-button/index.js';
|
|
|
134
140
|
* @csspart header - The bar that holds the name and the copy button, shown when there is a name.
|
|
135
141
|
* @csspart hunk - In a diff, a `@@` line saying where the lines under it are.
|
|
136
142
|
* @csspart label - The block's name: its `label`, or its `language` where it has no label.
|
|
137
|
-
* @csspart mark - The band behind a run of lines `mark-lines` names. The block writes where the run is laid out into `--code-block-mark-start`, `--code-block-mark-size`, and `--code-block-mark-inline-size` on it. Hidden from the accessibility tree.
|
|
143
|
+
* @csspart mark - The band behind a run of lines `mark-lines`, `added-lines` or `removed-lines` names. The block writes where the run is laid out into `--code-block-mark-start`, `--code-block-mark-size`, and `--code-block-mark-inline-size` on it. Hidden from the accessibility tree.
|
|
144
|
+
* @csspart mark-added - The band behind a run of lines `added-lines` names. Applied with `mark`.
|
|
145
|
+
* @csspart mark-removed - The band behind a run of lines `removed-lines` names. Applied with `mark`.
|
|
138
146
|
* @csspart marker - In a diff, the `+`, `-`, or space that starts a line.
|
|
139
147
|
* @csspart new - In a split diff, the new version's `<pre>`.
|
|
140
148
|
* @csspart number - In a diff, a line number. In a `wrap` block with `line-numbers`, one line's number in the gutter, which the block makes as tall as the line it numbers through `--code-block-line-size`.
|
|
@@ -158,6 +166,11 @@ export declare class HelixHeadlessCodeBlock extends HelixElement {
|
|
|
158
166
|
* `registerWords('de', { 'helix-code-block': { copyLabel: 'Code kopieren' } })`.
|
|
159
167
|
*/
|
|
160
168
|
static words: string;
|
|
169
|
+
/**
|
|
170
|
+
* The lines to mark as inserted, as numbers and ranges counted from 1, such
|
|
171
|
+
* as `3-4`. Drawn as `mark-lines` is, in the added color. Not for a diff.
|
|
172
|
+
*/
|
|
173
|
+
addedLines: string;
|
|
161
174
|
/**
|
|
162
175
|
* The name of a block that scrolls, which is a tab stop while it does.
|
|
163
176
|
*
|
|
@@ -312,6 +325,11 @@ export declare class HelixHeadlessCodeBlock extends HelixElement {
|
|
|
312
325
|
* `false`.
|
|
313
326
|
*/
|
|
314
327
|
noCopy: boolean;
|
|
328
|
+
/**
|
|
329
|
+
* The lines to mark as deleted, as numbers and ranges counted from 1. Drawn
|
|
330
|
+
* as `mark-lines` is, in the removed color. Not for a diff.
|
|
331
|
+
*/
|
|
332
|
+
removedLines: string;
|
|
315
333
|
/**
|
|
316
334
|
* The words of a button that shows a folded run of unchanged lines, in a
|
|
317
335
|
* diff with `context-lines`. `{count}` is how many.
|
|
@@ -27,6 +27,7 @@ const nameOf = (block) => {
|
|
|
27
27
|
let HelixHeadlessCodeBlock = class HelixHeadlessCodeBlock extends HelixElement {
|
|
28
28
|
constructor() {
|
|
29
29
|
super(...arguments);
|
|
30
|
+
this.addedLines = '';
|
|
30
31
|
this.codeLabel = 'Code';
|
|
31
32
|
this.codeNameLabel = '{code}, {name}';
|
|
32
33
|
this.collapseLabel = 'Show fewer lines';
|
|
@@ -43,6 +44,7 @@ let HelixHeadlessCodeBlock = class HelixHeadlessCodeBlock extends HelixElement {
|
|
|
43
44
|
this.lineNumbers = false;
|
|
44
45
|
this.markLines = '';
|
|
45
46
|
this.noCopy = false;
|
|
47
|
+
this.removedLines = '';
|
|
46
48
|
this.showUnchangedLabel = '{count, plural, one {Show # unchanged line} other {Show # unchanged lines}}';
|
|
47
49
|
this.wrap = false;
|
|
48
50
|
this.wrapLabel = 'Wrap lines';
|
|
@@ -223,7 +225,7 @@ let HelixHeadlessCodeBlock = class HelixHeadlessCodeBlock extends HelixElement {
|
|
|
223
225
|
}
|
|
224
226
|
placeLines() {
|
|
225
227
|
const code = this.renderRoot.querySelector('[part="code"]');
|
|
226
|
-
const marks = [...this.renderRoot.querySelectorAll('[part
|
|
228
|
+
const marks = [...this.renderRoot.querySelectorAll('[part~="mark"]')];
|
|
227
229
|
const numbers = [
|
|
228
230
|
...this.renderRoot.querySelectorAll('[part="gutter"] > [part="number"]'),
|
|
229
231
|
];
|
|
@@ -356,8 +358,13 @@ let HelixHeadlessCodeBlock = class HelixHeadlessCodeBlock extends HelixElement {
|
|
|
356
358
|
catch (error) {
|
|
357
359
|
globalThis.reportError?.(error);
|
|
358
360
|
}
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
+
const marks = this.childrenKnown
|
|
362
|
+
? [
|
|
363
|
+
...lineRuns(this.markLines).map(([first, last]) => html `<span part="mark" aria-hidden="true" data-lines="${first}-${last}"></span>`),
|
|
364
|
+
...lineRuns(this.addedLines).map(([first, last]) => html `<span part="mark mark-added" aria-hidden="true" data-lines="${first}-${last}"></span>`),
|
|
365
|
+
...lineRuns(this.removedLines).map(([first, last]) => html `<span part="mark mark-removed" aria-hidden="true" data-lines="${first}-${last}"></span>`),
|
|
366
|
+
]
|
|
367
|
+
: [];
|
|
361
368
|
const code = highlighted === undefined
|
|
362
369
|
? html `<code part="code">${marks}<slot @slotchange=${() => this.requestUpdate()}></slot></code>`
|
|
363
370
|
: html `<code part="code">${marks}${unsafeHTML(highlighted)}</code><slot
|
|
@@ -376,7 +383,7 @@ let HelixHeadlessCodeBlock = class HelixHeadlessCodeBlock extends HelixElement {
|
|
|
376
383
|
: nothing;
|
|
377
384
|
const name = this.label || this.language;
|
|
378
385
|
const drawn = this.tabbed ? '' : name;
|
|
379
|
-
return html `${this.renderHeader(name, undefined, drawn)}
|
|
386
|
+
return html `${this.renderHeader(name, undefined, drawn, true)}
|
|
380
387
|
<div part="frame">
|
|
381
388
|
<pre id="block" part="block" translate="no">${gutter}${code}</pre>
|
|
382
389
|
${this.headed(drawn) ? nothing : this.renderCopy()}
|
|
@@ -396,16 +403,24 @@ let HelixHeadlessCodeBlock = class HelixHeadlessCodeBlock extends HelixElement {
|
|
|
396
403
|
this.toggleState('expanded', this.expanded);
|
|
397
404
|
}}
|
|
398
405
|
>
|
|
399
|
-
${this.expanded ? this.collapseLabel : this.expandLabel}
|
|
406
|
+
${this.slotted('expand-icon') ? html `<slot name="expand-icon"></slot>` : nothing}${this.expanded ? this.collapseLabel : this.expandLabel}
|
|
400
407
|
</button>`;
|
|
401
408
|
}
|
|
402
409
|
headed(name) {
|
|
403
|
-
return name !== '' ||
|
|
410
|
+
return (name !== '' ||
|
|
411
|
+
(this.wrappable && !this.diff) ||
|
|
412
|
+
this.slotted('actions') ||
|
|
413
|
+
(!this.diff && !this.tabbed && this.slotted('label')));
|
|
404
414
|
}
|
|
405
|
-
renderHeader(name, value, drawn = name) {
|
|
415
|
+
renderHeader(name, value, drawn = name, own = false) {
|
|
406
416
|
const acting = this.slotted('actions');
|
|
417
|
+
const label = own && !this.tabbed && this.slotted('label')
|
|
418
|
+
? html `<span part="label"><slot name="label">${drawn}</slot></span>`
|
|
419
|
+
: drawn
|
|
420
|
+
? html `<span part="label">${drawn}</span>`
|
|
421
|
+
: nothing;
|
|
407
422
|
return this.headed(drawn)
|
|
408
|
-
? html `<div part="header">${
|
|
423
|
+
? html `<div part="header">${label}${acting ? html `<span part="actions"><slot name="actions"></slot></span>` : nothing}${this.renderWrapToggle()}${this.renderCopy(value, name)}</div>`
|
|
409
424
|
: nothing;
|
|
410
425
|
}
|
|
411
426
|
renderWrapToggle() {
|
|
@@ -501,6 +516,9 @@ let HelixHeadlessCodeBlock = class HelixHeadlessCodeBlock extends HelixElement {
|
|
|
501
516
|
})}<slot hidden @slotchange=${() => this.requestUpdate()}></slot>`;
|
|
502
517
|
}
|
|
503
518
|
};
|
|
519
|
+
__decorate([
|
|
520
|
+
property({ attribute: 'added-lines' })
|
|
521
|
+
], HelixHeadlessCodeBlock.prototype, "addedLines", void 0);
|
|
504
522
|
__decorate([
|
|
505
523
|
words,
|
|
506
524
|
property({ attribute: 'code-label' })
|
|
@@ -567,6 +585,9 @@ __decorate([
|
|
|
567
585
|
__decorate([
|
|
568
586
|
property({ attribute: 'no-copy', reflect: true, type: Boolean })
|
|
569
587
|
], HelixHeadlessCodeBlock.prototype, "noCopy", void 0);
|
|
588
|
+
__decorate([
|
|
589
|
+
property({ attribute: 'removed-lines' })
|
|
590
|
+
], HelixHeadlessCodeBlock.prototype, "removedLines", void 0);
|
|
570
591
|
__decorate([
|
|
571
592
|
words,
|
|
572
593
|
property({ attribute: 'show-unchanged-label' })
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type Color, type ColorFormat, type ColorWords, HelixFormElement, KeyboardNavigationController } from '@fusion.dev/helix-core';
|
|
2
|
-
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
1
|
+
import { type Color, type ColorFormat, type ColorWords, HelixFormElement, KeyboardNavigationController, type PickerFailure } from '@fusion.dev/helix-core';
|
|
2
|
+
import { nothing, type PropertyValues, type TemplateResult } from 'lit';
|
|
3
3
|
import '../../button/index.js';
|
|
4
4
|
import '../../button-group/index.js';
|
|
5
5
|
import '../../slider/index.js';
|
|
@@ -82,7 +82,7 @@ declare const HelixHeadlessColorPicker_base: (new (...args: any[]) => import("@f
|
|
|
82
82
|
* @csspart eyedropper-live - The live region of the button inside `eyedropper`, which says its pending label.
|
|
83
83
|
* @csspart eyedropper-spinner - The ring the eyedropper button turns while it is pending.
|
|
84
84
|
* @csspart eyedropper-text - The words on the button inside `eyedropper`, trimmed to their capitals and baseline.
|
|
85
|
-
* @csspart feedback - The message under the picker
|
|
85
|
+
* @csspart feedback - The message under the picker: `feedback`, or what is wrong with the answer once the picker is marked invalid.
|
|
86
86
|
* @csspart field - One channel's number field with its label, in `channel-fields`.
|
|
87
87
|
* @csspart field-input - The number input in a channel's field.
|
|
88
88
|
* @csspart field-label - The name above a channel's number input.
|
|
@@ -321,10 +321,11 @@ export declare class HelixHeadlessColorPicker extends HelixHeadlessColorPicker_b
|
|
|
321
321
|
* the anchor the browser points its bubble at.
|
|
322
322
|
*
|
|
323
323
|
* **Text that is not a color is bad input**, so a form does not submit
|
|
324
|
-
* `#00` from a half-typed value. The picker
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
-
*
|
|
324
|
+
* `#00` from a half-typed value. The picker marks it, and says
|
|
325
|
+
* `badInputMessage` under itself, once the reader leaves the value field or
|
|
326
|
+
* presses Enter, and a missing color once the reader has committed one or a
|
|
327
|
+
* form has refused the picker. Color Input shows its own message against the
|
|
328
|
+
* field the reader typed into.
|
|
328
329
|
*/
|
|
329
330
|
required: boolean;
|
|
330
331
|
/**
|
|
@@ -404,6 +405,48 @@ export declare class HelixHeadlessColorPicker extends HelixHeadlessColorPicker_b
|
|
|
404
405
|
private get hinted();
|
|
405
406
|
/** The ids a control in the picker is described by: the help, then the message. */
|
|
406
407
|
private get describedBy();
|
|
408
|
+
/**
|
|
409
|
+
* Whether the reader is typing into the value field, between the first
|
|
410
|
+
* keystroke and the `change` that ends it.
|
|
411
|
+
*
|
|
412
|
+
* Half-typed text is rarely a color: `#00ff00` passes through `#0` and
|
|
413
|
+
* `#00ff0` on the way. So text that is not a color is refused once the
|
|
414
|
+
* reader leaves the field or presses Enter, and not on every keystroke.
|
|
415
|
+
*/
|
|
416
|
+
private typing;
|
|
417
|
+
/**
|
|
418
|
+
* Text in the value field that is not a color, or undefined while the field
|
|
419
|
+
* holds the value.
|
|
420
|
+
*
|
|
421
|
+
* **Kept in the field and out of the value**, as a native number field keeps
|
|
422
|
+
* "12e": the text stays where the reader can correct it, the value stays the
|
|
423
|
+
* last color, and the picker reports `badInput` until the text is a color or
|
|
424
|
+
* the value moves. Measured before this: typed "nonsense" became the value,
|
|
425
|
+
* so a form read `brand=nonsense` and the square had no color to show.
|
|
426
|
+
*/
|
|
427
|
+
private draft;
|
|
428
|
+
/**
|
|
429
|
+
* A required picker with no color is missing, and text that is not a color
|
|
430
|
+
* is refused, once the reader has finished typing it. See
|
|
431
|
+
* `MixinPickerFeedback.marked`.
|
|
432
|
+
*
|
|
433
|
+
* Measured before this: typed "nonsense" and a required picker its form
|
|
434
|
+
* refused both failed `checkValidity()` while `:state(invalid)` stayed off,
|
|
435
|
+
* the value field had no `aria-invalid`, and nothing was said.
|
|
436
|
+
*/
|
|
437
|
+
protected get failure(): PickerFailure | undefined;
|
|
438
|
+
/**
|
|
439
|
+
* The line under the picker: `feedback`, or, where the picker is marked by
|
|
440
|
+
* its own validation, what is wrong.
|
|
441
|
+
*/
|
|
442
|
+
private get said();
|
|
443
|
+
/**
|
|
444
|
+
* A reset ends any typing and takes back text that was not a color, so the
|
|
445
|
+
* field shows the color the reset put back.
|
|
446
|
+
*/
|
|
447
|
+
formResetCallback(): void;
|
|
448
|
+
/** The line `said` holds, or nothing while it is empty. */
|
|
449
|
+
protected renderPickerFeedback(): TemplateResult | typeof nothing;
|
|
407
450
|
/** Whether a pointer is dragging the square's handle, said as `:state(dragging)`. */
|
|
408
451
|
private dragging;
|
|
409
452
|
/** Whether the eyedropper is open, said as `:state(sampling)`. */
|
|
@@ -56,6 +56,8 @@ let HelixHeadlessColorPicker = class HelixHeadlessColorPicker extends MixinPicke
|
|
|
56
56
|
this.handleNamingSlot = () => {
|
|
57
57
|
this.requestUpdate();
|
|
58
58
|
};
|
|
59
|
+
this.typing = false;
|
|
60
|
+
this.draft = undefined;
|
|
59
61
|
this.dragging = false;
|
|
60
62
|
this.picking = false;
|
|
61
63
|
this.owned = false;
|
|
@@ -68,7 +70,14 @@ let HelixHeadlessColorPicker = class HelixHeadlessColorPicker extends MixinPicke
|
|
|
68
70
|
return;
|
|
69
71
|
}
|
|
70
72
|
const typed = event.target.value;
|
|
73
|
+
this.typing = true;
|
|
71
74
|
const parsed = parseColor(typed);
|
|
75
|
+
if (!parsed && typed.trim() !== '') {
|
|
76
|
+
this.draft = typed;
|
|
77
|
+
this.sync();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
this.draft = undefined;
|
|
72
81
|
if (parsed) {
|
|
73
82
|
this.moved = parsed;
|
|
74
83
|
this.owned = typed !== this.value;
|
|
@@ -261,9 +270,47 @@ let HelixHeadlessColorPicker = class HelixHeadlessColorPicker extends MixinPicke
|
|
|
261
270
|
return this.hint !== '' || this.slottedIn('hint').length > 0;
|
|
262
271
|
}
|
|
263
272
|
get describedBy() {
|
|
264
|
-
const ids = [this.hinted ? 'hint' : '', this.
|
|
273
|
+
const ids = [this.hinted ? 'hint' : '', this.said === '' ? '' : 'feedback'].filter(Boolean);
|
|
265
274
|
return ids.length > 0 ? ids.join(' ') : nothing;
|
|
266
275
|
}
|
|
276
|
+
get failure() {
|
|
277
|
+
if (this.draft !== undefined) {
|
|
278
|
+
return this.typing ? undefined : 'refused';
|
|
279
|
+
}
|
|
280
|
+
if (this.value === '') {
|
|
281
|
+
return this.required ? 'missing' : undefined;
|
|
282
|
+
}
|
|
283
|
+
return parseColor(this.value) === null ? 'refused' : undefined;
|
|
284
|
+
}
|
|
285
|
+
get said() {
|
|
286
|
+
if (this.feedback !== '') {
|
|
287
|
+
return this.feedback;
|
|
288
|
+
}
|
|
289
|
+
if (!this.marked || this.invalid) {
|
|
290
|
+
return '';
|
|
291
|
+
}
|
|
292
|
+
const failure = this.failure;
|
|
293
|
+
return failure === 'refused'
|
|
294
|
+
? this.badInputMessage
|
|
295
|
+
: failure === 'missing'
|
|
296
|
+
? this.valueMissingMessage
|
|
297
|
+
: '';
|
|
298
|
+
}
|
|
299
|
+
formResetCallback() {
|
|
300
|
+
super.formResetCallback();
|
|
301
|
+
this.typing = false;
|
|
302
|
+
if (this.draft !== undefined) {
|
|
303
|
+
this.draft = undefined;
|
|
304
|
+
const field = this.partNamed('value');
|
|
305
|
+
if (field) {
|
|
306
|
+
field.value = this.value;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
renderPickerFeedback() {
|
|
311
|
+
const said = this.said;
|
|
312
|
+
return said === '' ? nothing : html `<p part="feedback" id="feedback">${said}</p>`;
|
|
313
|
+
}
|
|
267
314
|
settle() {
|
|
268
315
|
if (this.unsettled) {
|
|
269
316
|
this.unsettled = false;
|
|
@@ -299,6 +346,10 @@ let HelixHeadlessColorPicker = class HelixHeadlessColorPicker extends MixinPicke
|
|
|
299
346
|
this.reportRequired();
|
|
300
347
|
}
|
|
301
348
|
reportRequired() {
|
|
349
|
+
if (this.draft !== undefined) {
|
|
350
|
+
this.internals.setValidity({ badInput: true }, this.badInputMessage, this.partNamed('value') ?? this.partNamed('grid') ?? undefined);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
302
353
|
if (this.required && this.value === '') {
|
|
303
354
|
this.internals.setValidity({ valueMissing: true }, this.valueMissingMessage, this.partNamed('grid') ?? undefined);
|
|
304
355
|
return;
|
|
@@ -424,6 +475,9 @@ let HelixHeadlessColorPicker = class HelixHeadlessColorPicker extends MixinPicke
|
|
|
424
475
|
this.move({ [channel]: Number(control.value) });
|
|
425
476
|
}
|
|
426
477
|
willUpdate(changed) {
|
|
478
|
+
if (changed.has('value')) {
|
|
479
|
+
this.draft = undefined;
|
|
480
|
+
}
|
|
427
481
|
super.willUpdate(changed);
|
|
428
482
|
const outside = [...(this.internals.labels ?? [])];
|
|
429
483
|
const named = this.named;
|
|
@@ -630,6 +684,7 @@ let HelixHeadlessColorPicker = class HelixHeadlessColorPicker extends MixinPicke
|
|
|
630
684
|
@input=${this.handleValueInput}
|
|
631
685
|
@change=${(event) => {
|
|
632
686
|
event.stopPropagation();
|
|
687
|
+
this.typing = false;
|
|
633
688
|
this.settle();
|
|
634
689
|
}}
|
|
635
690
|
/>`}
|
|
@@ -770,6 +825,12 @@ __decorate([
|
|
|
770
825
|
__decorate([
|
|
771
826
|
state()
|
|
772
827
|
], HelixHeadlessColorPicker.prototype, "swatchAt", void 0);
|
|
828
|
+
__decorate([
|
|
829
|
+
state()
|
|
830
|
+
], HelixHeadlessColorPicker.prototype, "typing", void 0);
|
|
831
|
+
__decorate([
|
|
832
|
+
state()
|
|
833
|
+
], HelixHeadlessColorPicker.prototype, "draft", void 0);
|
|
773
834
|
__decorate([
|
|
774
835
|
state()
|
|
775
836
|
], HelixHeadlessColorPicker.prototype, "dragging", void 0);
|
|
@@ -240,12 +240,13 @@ export declare class HelixHeadlessCombobox extends HelixHeadlessCombobox_base {
|
|
|
240
240
|
/**
|
|
241
241
|
* Whether more than one option can be chosen.
|
|
242
242
|
*
|
|
243
|
-
* The list already does the work: `value`
|
|
244
|
-
* toggles rather than replaces, and the arrows move without
|
|
245
|
-
* this adds is the tags, and keeping the panel open between
|
|
243
|
+
* The list already does the work: `value` is the chosen values joined with
|
|
244
|
+
* commas, a press toggles rather than replaces, and the arrows move without
|
|
245
|
+
* choosing. What this adds is the tags, and keeping the panel open between
|
|
246
|
+
* picks.
|
|
246
247
|
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
248
|
+
* An option's value may hold a comma, and it is read as one value, as
|
|
249
|
+
* `Select` and `Listbox` read it. So may an answer the reader created.
|
|
249
250
|
*
|
|
250
251
|
* **The list is told, and the field is not.** `aria-multiselectable` belongs
|
|
251
252
|
* to `listbox`, `grid`, `tree` and `tablist`, and `combobox` dropped it in
|
|
@@ -385,8 +386,17 @@ export declare class HelixHeadlessCombobox extends HelixHeadlessCombobox_base {
|
|
|
385
386
|
*/
|
|
386
387
|
protected readonly upgradeWatch: UpgradeController;
|
|
387
388
|
protected readonly panel: PopoverController;
|
|
388
|
-
/**
|
|
389
|
+
/**
|
|
390
|
+
* The values chosen, in the order they were chosen, read against the
|
|
391
|
+
* options' values and the answers created here, so a value that holds a
|
|
392
|
+
* comma is one value. See `chosenValues`.
|
|
393
|
+
*
|
|
394
|
+
* Measured before this: choosing `Paris, France` drew two tags, "Paris" and
|
|
395
|
+
* "France", and the form received two entries.
|
|
396
|
+
*/
|
|
389
397
|
private get values();
|
|
398
|
+
/** The answers created here, which no option holds, kept so their commas read as theirs. */
|
|
399
|
+
private readonly created;
|
|
390
400
|
/**
|
|
391
401
|
* The chosen options themselves, for their words.
|
|
392
402
|
*
|