@fusion.dev/helix-headless 0.22.0 → 0.24.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 +48397 -26631
- package/dist/announcement/root/announcement-root.d.ts +19 -5
- package/dist/area-chart/index.d.ts +1 -0
- package/dist/area-chart/index.js +1 -0
- package/dist/area-chart/root/area-chart-root.d.ts +182 -0
- package/dist/area-chart/root/area-chart-root.js +205 -0
- package/dist/carousel/root/carousel-root.d.ts +1 -0
- package/dist/carousel/root/carousel-root.js +18 -2
- package/dist/chat/index.d.ts +1 -1
- package/dist/chat/message/chat-message.d.ts +7 -0
- package/dist/chat/message/chat-message.js +2 -1
- package/dist/chat/root/chat-root.d.ts +195 -4
- package/dist/chat/root/chat-root.js +316 -23
- package/dist/checkbox-group/root/checkbox-group-root.d.ts +2 -0
- package/dist/checkbox-group/root/checkbox-group-root.js +12 -2
- package/dist/code-block/index.d.ts +1 -0
- package/dist/code-block/index.js +1 -0
- package/dist/code-block/patch.d.ts +62 -0
- package/dist/code-block/patch.js +93 -0
- package/dist/code-block/root/code-block-root.d.ts +82 -17
- package/dist/code-block/root/code-block-root.js +83 -6
- package/dist/color-picker/root/color-picker-root.d.ts +4 -3
- package/dist/color-picker/root/color-picker-root.js +4 -1
- package/dist/data-table/root/data-table-root.d.ts +195 -23
- package/dist/data-table/root/data-table-root.js +308 -27
- package/dist/description-list/index.d.ts +2 -0
- package/dist/description-list/index.js +2 -0
- package/dist/description-list/item/description-list-item.d.ts +39 -0
- package/dist/description-list/item/description-list-item.js +26 -0
- package/dist/description-list/root/description-list-root.d.ts +56 -0
- package/dist/description-list/root/description-list-root.js +21 -0
- package/dist/element-events.generated.js +27 -6
- package/dist/filter-input/chip/filter-chip.d.ts +114 -0
- package/dist/filter-input/chip/filter-chip.js +61 -0
- package/dist/filter-input/field/filter-field.d.ts +128 -0
- package/dist/filter-input/field/filter-field.js +70 -0
- package/dist/filter-input/grammar.d.ts +96 -0
- package/dist/filter-input/grammar.js +133 -0
- package/dist/filter-input/index.d.ts +5 -0
- package/dist/filter-input/index.js +5 -0
- package/dist/filter-input/menu/filter-menu.d.ts +153 -0
- package/dist/filter-input/menu/filter-menu.js +164 -0
- package/dist/filter-input/operator/filter-operator.d.ts +105 -0
- package/dist/filter-input/operator/filter-operator.js +39 -0
- package/dist/filter-input/root/filter-input-root.d.ts +118 -9
- package/dist/filter-input/root/filter-input-root.js +96 -2
- package/dist/format-date/root/format-date-root.js +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/line-chart/root/line-chart-root.d.ts +9 -2
- package/dist/listbox/root/listbox-root.d.ts +8 -7
- package/dist/listbox/root/listbox-root.js +7 -2
- package/dist/menu/item/menu-item.d.ts +54 -6
- package/dist/menu/item/menu-item.js +31 -6
- package/dist/menu/root/menu-root.d.ts +23 -0
- package/dist/menu/root/menu-root.js +13 -2
- package/dist/notifications/group/notification-group.d.ts +129 -0
- package/dist/notifications/group/notification-group.js +106 -0
- package/dist/notifications/index.d.ts +1 -0
- package/dist/notifications/index.js +1 -0
- package/dist/notifications/notification/notification.d.ts +39 -13
- package/dist/notifications/notification/notification.js +11 -8
- package/dist/notifications/root/notifications-root.d.ts +57 -1
- package/dist/notifications/root/notifications-root.js +35 -4
- package/dist/number-input/root/number-input-root.d.ts +21 -1
- package/dist/number-input/root/number-input-root.js +18 -5
- package/dist/object-inspector/index.d.ts +1 -0
- package/dist/object-inspector/index.js +1 -0
- package/dist/object-inspector/root/object-inspector-root.d.ts +183 -0
- package/dist/object-inspector/root/object-inspector-root.js +348 -0
- package/dist/outline/root/outline-root.d.ts +16 -1
- package/dist/outline/root/outline-root.js +27 -9
- package/dist/radio/group/radio-group.d.ts +1 -0
- package/dist/scatter-chart/index.d.ts +1 -0
- package/dist/scatter-chart/index.js +1 -0
- package/dist/scatter-chart/root/scatter-chart-root.d.ts +144 -0
- package/dist/scatter-chart/root/scatter-chart-root.js +205 -0
- package/dist/skip-link/index.d.ts +1 -0
- package/dist/skip-link/index.js +1 -0
- package/dist/skip-link/root/skip-link-root.d.ts +81 -0
- package/dist/skip-link/root/skip-link-root.js +58 -0
- package/dist/speed-dial/root/speed-dial-root.d.ts +1 -0
- package/dist/speed-dial/root/speed-dial-root.js +6 -2
- package/dist/steps/root/steps-root.d.ts +1 -0
- package/dist/table/column/table-column.d.ts +79 -0
- package/dist/table/column/table-column.js +125 -5
- package/dist/table/root/table-root.d.ts +2 -0
- package/dist/table/root/table-root.js +12 -3
- package/dist/time-picker/root/time-picker-root.d.ts +9 -0
- package/dist/time-picker/root/time-picker-root.js +5 -0
- package/dist/toast/root/toast-root.d.ts +2 -0
- package/dist/tooltip/root/tooltip-root.js +1 -1
- package/dist/tour/root/tour-root.d.ts +1 -0
- package/dist/tree/item/tree-item.d.ts +7 -4
- package/dist/tree/item/tree-item.js +4 -1
- package/dist/video/root/video-root.d.ts +23 -1
- package/dist/video/root/video-root.js +42 -22
- package/dist/virtual-list/root/virtual-list-root.d.ts +10 -6
- package/dist/virtual-list/root/virtual-list-root.js +10 -4
- package/package.json +5 -5
- package/types/jsx.d.ts +4535 -346
- package/types/svelte.d.ts +11 -0
- package/types/vue.d.ts +737 -39
|
@@ -4,19 +4,61 @@ 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
|
-
|
|
8
|
-
import {
|
|
7
|
+
var HelixHeadlessChat_1;
|
|
8
|
+
import { customElement, HelixBeforePinEvent, HelixElement, HelixHistoryEvent, hostAria, MixinCollection, requestChange, VirtualizeController, } from '@fusion.dev/helix-core';
|
|
9
|
+
import { nothing } from 'lit';
|
|
9
10
|
import { property, state } from 'lit/decorators.js';
|
|
11
|
+
import { keyed } from 'lit/directives/keyed.js';
|
|
12
|
+
import { repeat } from 'lit/directives/repeat.js';
|
|
13
|
+
import { html } from 'lit/static-html.js';
|
|
14
|
+
import { FAILED_LABEL } from '../message/chat-message.js';
|
|
10
15
|
const REGIONS = ['header', 'actions', 'resume', 'status', 'composer'];
|
|
16
|
+
const settled = (turn) => turn.state !== 'sending' && turn.state !== 'streaming';
|
|
17
|
+
const newsworthy = (turn) => turn.author !== 'me' || turn.state === 'failed';
|
|
18
|
+
const spoken = (message) => [...message.childNodes]
|
|
19
|
+
.filter((node) => !(node.nodeType === 1 && node.hasAttribute('slot')))
|
|
20
|
+
.map((node) => node.textContent ?? '')
|
|
21
|
+
.join('')
|
|
22
|
+
.replace(/\s+/g, ' ')
|
|
23
|
+
.trim();
|
|
11
24
|
let HelixHeadlessChat = class HelixHeadlessChat extends MixinCollection(HelixElement) {
|
|
12
25
|
constructor() {
|
|
13
26
|
super(...arguments);
|
|
14
27
|
this.defaultPinned = true;
|
|
28
|
+
this.history = '';
|
|
29
|
+
this.historyText = 'Loading earlier messages';
|
|
15
30
|
this.label = '';
|
|
16
31
|
this.threshold = 48;
|
|
32
|
+
this.turns = [];
|
|
17
33
|
this.pinnedState = undefined;
|
|
34
|
+
this.announcement = '';
|
|
35
|
+
this.said = 0;
|
|
18
36
|
this.watcher = undefined;
|
|
37
|
+
this.settling = undefined;
|
|
38
|
+
this.grower = undefined;
|
|
39
|
+
this.watched = [];
|
|
40
|
+
this.stuckAt = -1;
|
|
41
|
+
this.anchor = undefined;
|
|
42
|
+
this.asked = false;
|
|
43
|
+
this.opening = true;
|
|
44
|
+
this.newest = undefined;
|
|
45
|
+
this.pending = new Set();
|
|
46
|
+
this.virtualize = new VirtualizeController(this, {
|
|
47
|
+
container: () => (this.fed ? this.log : null),
|
|
48
|
+
count: () => this.turns.length,
|
|
49
|
+
estimate: () => HelixHeadlessChat_1.ESTIMATE,
|
|
50
|
+
rows: () => (this.fed ? this.drawn() : []),
|
|
51
|
+
});
|
|
19
52
|
this.filled = undefined;
|
|
53
|
+
this.handleResize = () => {
|
|
54
|
+
if (this.pinned) {
|
|
55
|
+
this.stick();
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
this.restore();
|
|
59
|
+
}
|
|
60
|
+
this.remember();
|
|
61
|
+
};
|
|
20
62
|
this.handleResume = () => {
|
|
21
63
|
this.resume();
|
|
22
64
|
};
|
|
@@ -24,17 +66,30 @@ let HelixHeadlessChat = class HelixHeadlessChat extends MixinCollection(HelixEle
|
|
|
24
66
|
this.assign();
|
|
25
67
|
this.requestUpdate();
|
|
26
68
|
};
|
|
69
|
+
this.handleSettle = () => {
|
|
70
|
+
this.requestUpdate();
|
|
71
|
+
};
|
|
27
72
|
this.handleScroll = () => {
|
|
28
|
-
const
|
|
29
|
-
if (
|
|
30
|
-
this.
|
|
73
|
+
const log = this.log;
|
|
74
|
+
if (this.pinned && log && log.scrollTop >= this.stuckAt - 1 && !this.atBottom) {
|
|
75
|
+
this.stick();
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
const wanted = this.atBottom;
|
|
79
|
+
if (wanted !== this.pinned) {
|
|
80
|
+
this.requestPinned(wanted);
|
|
81
|
+
}
|
|
31
82
|
}
|
|
83
|
+
this.remember();
|
|
84
|
+
this.reachHistory();
|
|
32
85
|
};
|
|
33
86
|
}
|
|
87
|
+
static { HelixHeadlessChat_1 = this; }
|
|
34
88
|
static { this.shadowRootOptions = {
|
|
35
89
|
...HelixElement.shadowRootOptions,
|
|
36
90
|
slotAssignment: 'manual',
|
|
37
91
|
}; }
|
|
92
|
+
static { this.ESTIMATE = 72; }
|
|
38
93
|
get pinned() {
|
|
39
94
|
return this.pinnedState ?? this.defaultPinned;
|
|
40
95
|
}
|
|
@@ -46,8 +101,11 @@ let HelixHeadlessChat = class HelixHeadlessChat extends MixinCollection(HelixEle
|
|
|
46
101
|
has(name) {
|
|
47
102
|
return this.filled?.has(name) ?? true;
|
|
48
103
|
}
|
|
104
|
+
get fed() {
|
|
105
|
+
return this.turns.length > 0;
|
|
106
|
+
}
|
|
49
107
|
get messages() {
|
|
50
|
-
return
|
|
108
|
+
return this.knownChildren.filter((child) => 'isChatMessage' in child && child.isChatMessage === true);
|
|
51
109
|
}
|
|
52
110
|
get log() {
|
|
53
111
|
return this.renderRoot?.querySelector?.('[part="log"]') ?? null;
|
|
@@ -59,12 +117,49 @@ let HelixHeadlessChat = class HelixHeadlessChat extends MixinCollection(HelixEle
|
|
|
59
117
|
}
|
|
60
118
|
return log.scrollHeight - log.scrollTop - log.clientHeight <= Math.max(0, this.threshold);
|
|
61
119
|
}
|
|
120
|
+
identity(turn) {
|
|
121
|
+
return turn?.key ?? turn;
|
|
122
|
+
}
|
|
123
|
+
drawn() {
|
|
124
|
+
return [...(this.renderRoot?.querySelectorAll?.('[part="turn"]') ?? [])].map((element) => ({ element, index: Number(element.dataset.index) }));
|
|
125
|
+
}
|
|
126
|
+
rendered() {
|
|
127
|
+
if (this.fed) {
|
|
128
|
+
return this.drawn().map(({ element, index }) => ({
|
|
129
|
+
element,
|
|
130
|
+
id: this.identity(this.turns[index]),
|
|
131
|
+
}));
|
|
132
|
+
}
|
|
133
|
+
return this.messages.map((element) => ({ element, id: element }));
|
|
134
|
+
}
|
|
135
|
+
heard() {
|
|
136
|
+
if (this.fed) {
|
|
137
|
+
return this.turns.map((turn) => ({
|
|
138
|
+
author: turn.author,
|
|
139
|
+
failed: FAILED_LABEL,
|
|
140
|
+
id: this.identity(turn),
|
|
141
|
+
state: turn.state,
|
|
142
|
+
text: turn.text,
|
|
143
|
+
}));
|
|
144
|
+
}
|
|
145
|
+
return this.messages.map((message) => ({
|
|
146
|
+
author: message.author,
|
|
147
|
+
failed: message.failedLabel,
|
|
148
|
+
id: message,
|
|
149
|
+
state: message.state,
|
|
150
|
+
text: spoken(message),
|
|
151
|
+
}));
|
|
152
|
+
}
|
|
62
153
|
resume() {
|
|
154
|
+
this.stick();
|
|
155
|
+
this.requestPinned(true);
|
|
156
|
+
}
|
|
157
|
+
stick() {
|
|
63
158
|
const log = this.log;
|
|
64
159
|
if (log) {
|
|
65
160
|
log.scrollTop = log.scrollHeight;
|
|
161
|
+
this.stuckAt = log.scrollTop;
|
|
66
162
|
}
|
|
67
|
-
this.requestPinned(true);
|
|
68
163
|
}
|
|
69
164
|
requestPinned(next) {
|
|
70
165
|
if (next === this.pinned) {
|
|
@@ -87,16 +182,25 @@ let HelixHeadlessChat = class HelixHeadlessChat extends MixinCollection(HelixEle
|
|
|
87
182
|
if (!log?.assign) {
|
|
88
183
|
return;
|
|
89
184
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
for (const name of REGIONS) {
|
|
94
|
-
const given = [...this.children].filter((child) => child.getAttribute('slot') === name);
|
|
95
|
-
root?.querySelector?.(`slot[name="${name}"]`)?.assign?.(...given);
|
|
96
|
-
if (given.length > 0) {
|
|
97
|
-
filled.add(name);
|
|
185
|
+
if (this.assignsByName) {
|
|
186
|
+
for (const turn of this.fed ? [] : this.messages) {
|
|
187
|
+
this.route(turn, 'messages');
|
|
98
188
|
}
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
log.assign(...(this.fed ? [] : this.messages));
|
|
192
|
+
for (const name of REGIONS) {
|
|
193
|
+
root?.querySelector?.(`slot[name="${name}"]`)?.assign?.(...this.given(name));
|
|
99
194
|
}
|
|
195
|
+
}
|
|
196
|
+
given(name) {
|
|
197
|
+
return [...(this.children ?? [])].filter((child) => child.getAttribute('slot') === name);
|
|
198
|
+
}
|
|
199
|
+
countRegions() {
|
|
200
|
+
if (this.children === undefined || this.hydrating) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
const filled = new Set(REGIONS.filter((name) => this.given(name).length > 0));
|
|
100
204
|
const previous = this.filled;
|
|
101
205
|
if (!previous ||
|
|
102
206
|
previous.size !== filled.size ||
|
|
@@ -104,33 +208,201 @@ let HelixHeadlessChat = class HelixHeadlessChat extends MixinCollection(HelixEle
|
|
|
104
208
|
this.filled = filled;
|
|
105
209
|
}
|
|
106
210
|
}
|
|
211
|
+
remember() {
|
|
212
|
+
const log = this.log;
|
|
213
|
+
const turns = this.rendered();
|
|
214
|
+
if (!log || turns.length === 0) {
|
|
215
|
+
this.anchor = undefined;
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
const top = log.getBoundingClientRect().top;
|
|
219
|
+
let low = 0;
|
|
220
|
+
let high = turns.length - 1;
|
|
221
|
+
while (low < high) {
|
|
222
|
+
const middle = (low + high) >> 1;
|
|
223
|
+
if ((turns[middle]?.element.getBoundingClientRect().bottom ?? 0) > top) {
|
|
224
|
+
high = middle;
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
low = middle + 1;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const found = turns[low];
|
|
231
|
+
this.anchor = found
|
|
232
|
+
? { id: found.id, offset: found.element.getBoundingClientRect().top - top }
|
|
233
|
+
: undefined;
|
|
234
|
+
}
|
|
235
|
+
restore() {
|
|
236
|
+
const log = this.log;
|
|
237
|
+
const anchor = this.anchor;
|
|
238
|
+
if (!log || !anchor) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const found = this.rendered().find((turn) => turn.id === anchor.id);
|
|
242
|
+
if (!found) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
const moved = found.element.getBoundingClientRect().top - log.getBoundingClientRect().top - anchor.offset;
|
|
246
|
+
if (Math.abs(moved) >= 1) {
|
|
247
|
+
log.scrollTop += moved;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
reachHistory() {
|
|
251
|
+
const log = this.log;
|
|
252
|
+
if (this.opening || this.history !== 'more' || !log) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (log.scrollTop > Math.max(0, this.threshold)) {
|
|
256
|
+
this.asked = false;
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (!this.asked) {
|
|
260
|
+
this.asked = true;
|
|
261
|
+
this.dispatchEvent(new HelixHistoryEvent());
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
announce() {
|
|
265
|
+
const turns = this.heard();
|
|
266
|
+
if (this.opening) {
|
|
267
|
+
this.newest = turns.at(-1)?.id;
|
|
268
|
+
this.pending = new Set(turns.filter((turn) => !settled(turn)).map((turn) => turn.id));
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
const fresh = this.arrived(turns);
|
|
272
|
+
const words = [];
|
|
273
|
+
for (const turn of turns) {
|
|
274
|
+
if (this.pending.has(turn.id) && settled(turn) && !fresh.includes(turn)) {
|
|
275
|
+
this.pending.delete(turn.id);
|
|
276
|
+
if (newsworthy(turn)) {
|
|
277
|
+
words.push(this.phrase(turn));
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
for (const turn of fresh) {
|
|
282
|
+
if (!settled(turn)) {
|
|
283
|
+
this.pending.add(turn.id);
|
|
284
|
+
}
|
|
285
|
+
else if (newsworthy(turn)) {
|
|
286
|
+
words.push(this.phrase(turn));
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (this.pending.size > 0) {
|
|
290
|
+
const present = new Set(turns.map((turn) => turn.id));
|
|
291
|
+
this.pending = new Set([...this.pending].filter((id) => present.has(id)));
|
|
292
|
+
}
|
|
293
|
+
this.newest = turns.at(-1)?.id;
|
|
294
|
+
if (words.length > 0) {
|
|
295
|
+
this.announcement = words.join(' ');
|
|
296
|
+
this.said += 1;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
arrived(turns) {
|
|
300
|
+
if (this.newest === undefined) {
|
|
301
|
+
return turns.length === 1 ? turns : [];
|
|
302
|
+
}
|
|
303
|
+
const at = turns.findIndex((turn) => turn.id === this.newest);
|
|
304
|
+
return at < 0 ? [] : turns.slice(at + 1);
|
|
305
|
+
}
|
|
306
|
+
phrase(turn) {
|
|
307
|
+
return turn.state === 'failed' ? `${turn.failed}: ${turn.text}` : turn.text;
|
|
308
|
+
}
|
|
309
|
+
watchSizes() {
|
|
310
|
+
if (typeof ResizeObserver === 'undefined') {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
const log = this.log;
|
|
314
|
+
const targets = [...(log ? [log] : []), ...this.rendered().map((turn) => turn.element)];
|
|
315
|
+
if (targets.length === this.watched.length &&
|
|
316
|
+
targets.every((target, index) => target === this.watched[index])) {
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
this.grower ??= new ResizeObserver(this.handleResize);
|
|
320
|
+
this.grower.disconnect();
|
|
321
|
+
for (const target of targets) {
|
|
322
|
+
this.grower.observe(target);
|
|
323
|
+
}
|
|
324
|
+
this.watched = targets;
|
|
325
|
+
}
|
|
107
326
|
connectedCallback() {
|
|
108
327
|
super.connectedCallback();
|
|
109
328
|
this.watcher = new MutationObserver(this.handleSlotChange);
|
|
110
329
|
this.watcher.observe(this, { childList: true });
|
|
330
|
+
this.settling = new MutationObserver(this.handleSettle);
|
|
331
|
+
this.settling.observe(this, { attributeFilter: ['state'], subtree: true });
|
|
332
|
+
this.opening = true;
|
|
333
|
+
requestAnimationFrame(() => {
|
|
334
|
+
this.opening = false;
|
|
335
|
+
this.reachHistory();
|
|
336
|
+
});
|
|
111
337
|
}
|
|
112
338
|
disconnectedCallback() {
|
|
113
339
|
super.disconnectedCallback();
|
|
114
340
|
this.watcher?.disconnect();
|
|
115
341
|
this.watcher = undefined;
|
|
342
|
+
this.settling?.disconnect();
|
|
343
|
+
this.settling = undefined;
|
|
344
|
+
this.grower?.disconnect();
|
|
345
|
+
this.watched = [];
|
|
116
346
|
}
|
|
117
347
|
willUpdate(changed) {
|
|
118
348
|
super.willUpdate(changed);
|
|
119
349
|
hostAria(this.internals, { label: this.label || 'Conversation', role: 'region' });
|
|
350
|
+
this.countRegions();
|
|
351
|
+
if (changed.has('history')) {
|
|
352
|
+
this.asked = false;
|
|
353
|
+
}
|
|
354
|
+
if (changed.has('turns')) {
|
|
355
|
+
this.follow(changed.get('turns'));
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
follow(previous) {
|
|
359
|
+
if (!previous?.length || this.turns.length === 0) {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
const first = this.identity(previous[0]);
|
|
363
|
+
const at = this.turns.findIndex((turn) => this.identity(turn) === first);
|
|
364
|
+
if (at > 0) {
|
|
365
|
+
this.virtualize.shift(at);
|
|
366
|
+
}
|
|
367
|
+
else if (at < 0) {
|
|
368
|
+
this.virtualize.forget();
|
|
369
|
+
}
|
|
120
370
|
}
|
|
121
371
|
updated(changed) {
|
|
122
372
|
super.updated(changed);
|
|
123
373
|
this.assign();
|
|
124
374
|
if (this.pinned) {
|
|
125
|
-
|
|
126
|
-
if (log) {
|
|
127
|
-
log.scrollTop = log.scrollHeight;
|
|
128
|
-
}
|
|
375
|
+
this.stick();
|
|
129
376
|
}
|
|
377
|
+
else {
|
|
378
|
+
this.restore();
|
|
379
|
+
}
|
|
380
|
+
if (this.fed) {
|
|
381
|
+
this.virtualize.measure();
|
|
382
|
+
}
|
|
383
|
+
this.watchSizes();
|
|
384
|
+
this.remember();
|
|
385
|
+
this.reachHistory();
|
|
386
|
+
this.announce();
|
|
387
|
+
}
|
|
388
|
+
renderTurns() {
|
|
389
|
+
const { after, before, first, last } = this.virtualize.window;
|
|
390
|
+
const message = this.tag('chat-message');
|
|
391
|
+
return html `<div part="before" role="presentation" style="block-size: ${before}px; flex: none"></div>
|
|
392
|
+
${repeat(this.turns.slice(first, last), (turn, offset) => turn.key ?? first + offset, (turn, offset) => html `<div part="turn" data-index=${first + offset}>
|
|
393
|
+
<${message}
|
|
394
|
+
author=${turn.author ?? nothing}
|
|
395
|
+
at=${turn.at ?? nothing}
|
|
396
|
+
state=${turn.state ?? nothing}
|
|
397
|
+
>${turn.text}</${message}>
|
|
398
|
+
</div>`)}
|
|
399
|
+
<div part="after" role="presentation" style="block-size: ${after}px; flex: none"></div>`;
|
|
130
400
|
}
|
|
131
401
|
render() {
|
|
132
402
|
const away = !this.pinned;
|
|
403
|
+
const fed = this.fed;
|
|
133
404
|
const titled = this.has('header') || this.has('actions');
|
|
405
|
+
const busy = this.loading || this.history === 'loading';
|
|
134
406
|
return html `<div part="header" ?hidden=${!titled}>
|
|
135
407
|
<slot name="header"></slot>
|
|
136
408
|
<span part="actions" ?hidden=${!this.has('actions')}>
|
|
@@ -140,14 +412,20 @@ let HelixHeadlessChat = class HelixHeadlessChat extends MixinCollection(HelixEle
|
|
|
140
412
|
<div
|
|
141
413
|
part="log"
|
|
142
414
|
role="log"
|
|
143
|
-
aria-live="
|
|
415
|
+
aria-live="off"
|
|
144
416
|
aria-label=${this.label || 'Conversation'}
|
|
145
|
-
aria-busy=${
|
|
417
|
+
aria-busy=${busy ? 'true' : nothing}
|
|
418
|
+
?data-windowed=${fed}
|
|
146
419
|
@scroll=${this.handleScroll}
|
|
147
420
|
>
|
|
421
|
+
${this.history === 'loading'
|
|
422
|
+
? html `<p part="history" role="status">${this.historyText}</p>`
|
|
423
|
+
: nothing}
|
|
424
|
+
${fed ? this.renderTurns() : nothing}
|
|
148
425
|
<slot name="messages" @slotchange=${this.handleSlotChange}></slot>
|
|
149
|
-
${this.renderStatus(this.messages.length > 0)}
|
|
426
|
+
${this.renderStatus(fed || this.messages.length > 0 || !this.childrenKnown)}
|
|
150
427
|
</div>
|
|
428
|
+
<span part="live" role="status">${keyed(this.said, this.announcement)}</span>
|
|
151
429
|
<div part="resume" ?hidden=${!away || !this.has('resume')} @click=${this.handleResume}>
|
|
152
430
|
<slot name="resume"></slot>
|
|
153
431
|
</div>
|
|
@@ -162,6 +440,12 @@ let HelixHeadlessChat = class HelixHeadlessChat extends MixinCollection(HelixEle
|
|
|
162
440
|
__decorate([
|
|
163
441
|
property({ attribute: 'default-pinned', type: Boolean })
|
|
164
442
|
], HelixHeadlessChat.prototype, "defaultPinned", void 0);
|
|
443
|
+
__decorate([
|
|
444
|
+
property()
|
|
445
|
+
], HelixHeadlessChat.prototype, "history", void 0);
|
|
446
|
+
__decorate([
|
|
447
|
+
property({ attribute: 'history-text' })
|
|
448
|
+
], HelixHeadlessChat.prototype, "historyText", void 0);
|
|
165
449
|
__decorate([
|
|
166
450
|
property()
|
|
167
451
|
], HelixHeadlessChat.prototype, "label", void 0);
|
|
@@ -171,13 +455,22 @@ __decorate([
|
|
|
171
455
|
__decorate([
|
|
172
456
|
property({ type: Number })
|
|
173
457
|
], HelixHeadlessChat.prototype, "threshold", void 0);
|
|
458
|
+
__decorate([
|
|
459
|
+
property({ attribute: false })
|
|
460
|
+
], HelixHeadlessChat.prototype, "turns", void 0);
|
|
174
461
|
__decorate([
|
|
175
462
|
state()
|
|
176
463
|
], HelixHeadlessChat.prototype, "pinnedState", void 0);
|
|
464
|
+
__decorate([
|
|
465
|
+
state()
|
|
466
|
+
], HelixHeadlessChat.prototype, "announcement", void 0);
|
|
467
|
+
__decorate([
|
|
468
|
+
state()
|
|
469
|
+
], HelixHeadlessChat.prototype, "said", void 0);
|
|
177
470
|
__decorate([
|
|
178
471
|
state()
|
|
179
472
|
], HelixHeadlessChat.prototype, "filled", void 0);
|
|
180
|
-
HelixHeadlessChat = __decorate([
|
|
473
|
+
HelixHeadlessChat = HelixHeadlessChat_1 = __decorate([
|
|
181
474
|
customElement('helix-headless-chat')
|
|
182
475
|
], HelixHeadlessChat);
|
|
183
476
|
export { HelixHeadlessChat };
|
|
@@ -58,6 +58,8 @@ export declare class HelixHeadlessCheckboxGroup extends HelixHeadlessCheckboxGro
|
|
|
58
58
|
* reports and does not patch up. A box that names itself keeps its own.
|
|
59
59
|
*/
|
|
60
60
|
private sync;
|
|
61
|
+
/** Whether `seed` has had its one chance. */
|
|
62
|
+
private seeded;
|
|
61
63
|
/**
|
|
62
64
|
* Reads the boxes' own state into `value`, once, before anything is pushed.
|
|
63
65
|
*
|
|
@@ -15,6 +15,7 @@ let HelixHeadlessCheckboxGroup = class HelixHeadlessCheckboxGroup extends MixinF
|
|
|
15
15
|
this.cascade = new DisabledCascadeController(this, {
|
|
16
16
|
items: () => this.boxes,
|
|
17
17
|
});
|
|
18
|
+
this.seeded = false;
|
|
18
19
|
this.handleToggle = (event) => {
|
|
19
20
|
const target = event.target;
|
|
20
21
|
const changed = this.boxes.find((box) => box === target);
|
|
@@ -77,11 +78,20 @@ let HelixHeadlessCheckboxGroup = class HelixHeadlessCheckboxGroup extends MixinF
|
|
|
77
78
|
super.willUpdate(changed);
|
|
78
79
|
hostAria(this.internals, { label: this.label, role: 'group' });
|
|
79
80
|
this.internals.ariaRequired = this.required ? 'true' : null;
|
|
81
|
+
if (!this.seeded && this.children !== undefined && !this.hydrating) {
|
|
82
|
+
this.seeded = true;
|
|
83
|
+
if (this.value === '') {
|
|
84
|
+
this.seed();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
80
87
|
}
|
|
81
88
|
firstUpdated(changed) {
|
|
82
89
|
super.firstUpdated(changed);
|
|
83
|
-
if (this.
|
|
84
|
-
this.
|
|
90
|
+
if (!this.seeded && this.children !== undefined) {
|
|
91
|
+
this.seeded = true;
|
|
92
|
+
if (this.value === '') {
|
|
93
|
+
this.seed();
|
|
94
|
+
}
|
|
85
95
|
}
|
|
86
96
|
}
|
|
87
97
|
validate() {
|
package/dist/code-block/index.js
CHANGED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading a unified diff into the lines a reader sees.
|
|
3
|
+
*
|
|
4
|
+
* **This reads a diff and never computes one.** Which lines changed is the
|
|
5
|
+
* answer an algorithm gives, and consumers already have that algorithm where
|
|
6
|
+
* the two versions live, usually on a server: `git diff`, `diff -u`, a patch
|
|
7
|
+
* from a code host's API. Taking its output keeps the algorithm, and whatever it
|
|
8
|
+
* depends on, out of a component library. What is left is punctuation: a header
|
|
9
|
+
* per file, a `@@` per hunk, and a first character per line.
|
|
10
|
+
*
|
|
11
|
+
* **Line numbers come from the hunk headers**, so they are the file's numbers
|
|
12
|
+
* rather than the diff's, which is the only kind a reader can look up. A line
|
|
13
|
+
* that is not in a hunk is not in the output: file headers, `index` lines and
|
|
14
|
+
* `diff --git` are metadata, and a name is taken from them and nothing else.
|
|
15
|
+
*/
|
|
16
|
+
/** One line of a hunk, as the unified format marks it. */
|
|
17
|
+
export interface DiffLine {
|
|
18
|
+
kind: 'added' | 'context' | 'removed';
|
|
19
|
+
/** The number on the old side, absent for a line that only the new side has. */
|
|
20
|
+
old?: number;
|
|
21
|
+
/** The number on the new side, absent for a line that only the old side has. */
|
|
22
|
+
new?: number;
|
|
23
|
+
text: string;
|
|
24
|
+
}
|
|
25
|
+
/** A `@@` header and the lines under it. */
|
|
26
|
+
export interface DiffHunk {
|
|
27
|
+
header: string;
|
|
28
|
+
lines: DiffLine[];
|
|
29
|
+
}
|
|
30
|
+
/** Everything a patch says about one file. */
|
|
31
|
+
export interface DiffFile {
|
|
32
|
+
/** The path, from `+++` or from `---` when the file was deleted. Empty when the patch has none. */
|
|
33
|
+
name: string;
|
|
34
|
+
hunks: DiffHunk[];
|
|
35
|
+
}
|
|
36
|
+
/** One row of a side-by-side view: a line on either side, or nothing there. */
|
|
37
|
+
export interface DiffRow {
|
|
38
|
+
left?: DiffLine | undefined;
|
|
39
|
+
right?: DiffLine | undefined;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The files in a unified diff, each with its hunks.
|
|
43
|
+
*
|
|
44
|
+
* **A hunk is read to the counts its header states**, `@@ -12,7 +12,8 @@` being
|
|
45
|
+
* seven lines of the old file and eight of the new. That is what tells a
|
|
46
|
+
* removed line reading `-- note` from a file header, and a context line whose
|
|
47
|
+
* trailing space an editor stripped from the blank line that ends the patch.
|
|
48
|
+
*
|
|
49
|
+
* Lenient where the format is loose: a patch with no file headers is one
|
|
50
|
+
* nameless file, and `` is a note about the line
|
|
51
|
+
* before it rather than a line of its own, so it is dropped.
|
|
52
|
+
*/
|
|
53
|
+
export declare const parsePatch: (patch: string) => DiffFile[];
|
|
54
|
+
/**
|
|
55
|
+
* A hunk's lines paired for a side-by-side view.
|
|
56
|
+
*
|
|
57
|
+
* Context sits on both sides. A run of removals followed by a run of additions
|
|
58
|
+
* is one change, and its lines are paired in order, the longer run's remainder
|
|
59
|
+
* standing opposite nothing: that is what lets the eye read a changed line
|
|
60
|
+
* across rather than hunting for it further down.
|
|
61
|
+
*/
|
|
62
|
+
export declare const pairLines: (lines: DiffLine[]) => DiffRow[];
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
const HUNK = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
|
|
2
|
+
const path = (line) => {
|
|
3
|
+
const raw = line.slice(4).split('\t')[0]?.trim() ?? '';
|
|
4
|
+
return raw === '/dev/null' ? '' : raw.replace(/^[ab]\//, '');
|
|
5
|
+
};
|
|
6
|
+
const dedent = (text) => {
|
|
7
|
+
const lines = text.split('\n');
|
|
8
|
+
const indents = lines
|
|
9
|
+
.filter((line) => line.trim() !== '')
|
|
10
|
+
.map((line) => /^[ \t]*/.exec(line)?.[0].length ?? 0);
|
|
11
|
+
const common = indents.length === 0 ? 0 : Math.min(...indents);
|
|
12
|
+
return common === 0 ? text : lines.map((line) => line.slice(common)).join('\n');
|
|
13
|
+
};
|
|
14
|
+
export const parsePatch = (patch) => {
|
|
15
|
+
const files = [];
|
|
16
|
+
let file;
|
|
17
|
+
let hunk;
|
|
18
|
+
let before = 0;
|
|
19
|
+
let after = 0;
|
|
20
|
+
let owed = { new: 0, old: 0 };
|
|
21
|
+
let from = '';
|
|
22
|
+
for (const line of dedent(patch.replace(/\r\n?/g, '\n')).split('\n')) {
|
|
23
|
+
if (hunk && (owed.old > 0 || owed.new > 0)) {
|
|
24
|
+
const mark = line[0];
|
|
25
|
+
const text = line.slice(1);
|
|
26
|
+
if (mark === '+') {
|
|
27
|
+
hunk.lines.push({ kind: 'added', new: after++, text });
|
|
28
|
+
owed.new -= 1;
|
|
29
|
+
}
|
|
30
|
+
else if (mark === '-') {
|
|
31
|
+
hunk.lines.push({ kind: 'removed', old: before++, text });
|
|
32
|
+
owed.old -= 1;
|
|
33
|
+
}
|
|
34
|
+
else if (mark === ' ' || line === '') {
|
|
35
|
+
hunk.lines.push({ kind: 'context', new: after++, old: before++, text });
|
|
36
|
+
owed = { new: owed.new - 1, old: owed.old - 1 };
|
|
37
|
+
}
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (line.startsWith('diff --git ')) {
|
|
41
|
+
file = undefined;
|
|
42
|
+
from = '';
|
|
43
|
+
}
|
|
44
|
+
else if (line.startsWith('--- ')) {
|
|
45
|
+
from = path(line);
|
|
46
|
+
}
|
|
47
|
+
else if (line.startsWith('+++ ')) {
|
|
48
|
+
file = { hunks: [], name: path(line) || from };
|
|
49
|
+
files.push(file);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
const header = HUNK.exec(line);
|
|
53
|
+
if (header) {
|
|
54
|
+
if (!file) {
|
|
55
|
+
file = { hunks: [], name: from };
|
|
56
|
+
files.push(file);
|
|
57
|
+
}
|
|
58
|
+
hunk = { header: line, lines: [] };
|
|
59
|
+
file.hunks.push(hunk);
|
|
60
|
+
before = Number(header[1]);
|
|
61
|
+
after = Number(header[3]);
|
|
62
|
+
owed = { new: Number(header[4] ?? 1), old: Number(header[2] ?? 1) };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return files;
|
|
67
|
+
};
|
|
68
|
+
export const pairLines = (lines) => {
|
|
69
|
+
const rows = [];
|
|
70
|
+
let at = 0;
|
|
71
|
+
while (at < lines.length) {
|
|
72
|
+
const line = lines[at];
|
|
73
|
+
if (line.kind === 'context') {
|
|
74
|
+
rows.push({ left: line, right: line });
|
|
75
|
+
at += 1;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const removed = [];
|
|
79
|
+
const added = [];
|
|
80
|
+
while (lines[at]?.kind === 'removed') {
|
|
81
|
+
removed.push(lines[at]);
|
|
82
|
+
at += 1;
|
|
83
|
+
}
|
|
84
|
+
while (lines[at]?.kind === 'added') {
|
|
85
|
+
added.push(lines[at]);
|
|
86
|
+
at += 1;
|
|
87
|
+
}
|
|
88
|
+
for (let row = 0; row < Math.max(removed.length, added.length); row += 1) {
|
|
89
|
+
rows.push({ left: removed[row], right: added[row] });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return rows;
|
|
93
|
+
};
|