@fusion.dev/helix-headless 0.23.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.
Files changed (102) hide show
  1. package/custom-elements.json +48376 -26638
  2. package/dist/announcement/root/announcement-root.d.ts +19 -5
  3. package/dist/area-chart/index.d.ts +1 -0
  4. package/dist/area-chart/index.js +1 -0
  5. package/dist/area-chart/root/area-chart-root.d.ts +182 -0
  6. package/dist/area-chart/root/area-chart-root.js +205 -0
  7. package/dist/carousel/root/carousel-root.d.ts +1 -0
  8. package/dist/carousel/root/carousel-root.js +18 -2
  9. package/dist/chat/index.d.ts +1 -1
  10. package/dist/chat/message/chat-message.d.ts +7 -0
  11. package/dist/chat/message/chat-message.js +2 -1
  12. package/dist/chat/root/chat-root.d.ts +195 -4
  13. package/dist/chat/root/chat-root.js +316 -23
  14. package/dist/checkbox-group/root/checkbox-group-root.d.ts +2 -0
  15. package/dist/checkbox-group/root/checkbox-group-root.js +12 -2
  16. package/dist/code-block/index.d.ts +1 -0
  17. package/dist/code-block/index.js +1 -0
  18. package/dist/code-block/patch.d.ts +62 -0
  19. package/dist/code-block/patch.js +93 -0
  20. package/dist/code-block/root/code-block-root.d.ts +82 -17
  21. package/dist/code-block/root/code-block-root.js +83 -6
  22. package/dist/color-picker/root/color-picker-root.d.ts +4 -3
  23. package/dist/color-picker/root/color-picker-root.js +4 -1
  24. package/dist/data-table/root/data-table-root.d.ts +195 -23
  25. package/dist/data-table/root/data-table-root.js +308 -27
  26. package/dist/description-list/index.d.ts +2 -0
  27. package/dist/description-list/index.js +2 -0
  28. package/dist/description-list/item/description-list-item.d.ts +39 -0
  29. package/dist/description-list/item/description-list-item.js +26 -0
  30. package/dist/description-list/root/description-list-root.d.ts +56 -0
  31. package/dist/description-list/root/description-list-root.js +21 -0
  32. package/dist/element-events.generated.js +27 -6
  33. package/dist/filter-input/chip/filter-chip.d.ts +114 -0
  34. package/dist/filter-input/chip/filter-chip.js +61 -0
  35. package/dist/filter-input/field/filter-field.d.ts +128 -0
  36. package/dist/filter-input/field/filter-field.js +70 -0
  37. package/dist/filter-input/grammar.d.ts +96 -0
  38. package/dist/filter-input/grammar.js +133 -0
  39. package/dist/filter-input/index.d.ts +5 -0
  40. package/dist/filter-input/index.js +5 -0
  41. package/dist/filter-input/menu/filter-menu.d.ts +153 -0
  42. package/dist/filter-input/menu/filter-menu.js +164 -0
  43. package/dist/filter-input/operator/filter-operator.d.ts +105 -0
  44. package/dist/filter-input/operator/filter-operator.js +39 -0
  45. package/dist/filter-input/root/filter-input-root.d.ts +118 -9
  46. package/dist/filter-input/root/filter-input-root.js +96 -2
  47. package/dist/format-date/root/format-date-root.js +1 -1
  48. package/dist/index.d.ts +5 -0
  49. package/dist/index.js +5 -0
  50. package/dist/line-chart/root/line-chart-root.d.ts +9 -2
  51. package/dist/listbox/root/listbox-root.d.ts +8 -7
  52. package/dist/listbox/root/listbox-root.js +7 -2
  53. package/dist/menu/item/menu-item.d.ts +31 -6
  54. package/dist/menu/item/menu-item.js +18 -5
  55. package/dist/menu/root/menu-root.js +7 -2
  56. package/dist/notifications/group/notification-group.d.ts +129 -0
  57. package/dist/notifications/group/notification-group.js +106 -0
  58. package/dist/notifications/index.d.ts +1 -0
  59. package/dist/notifications/index.js +1 -0
  60. package/dist/notifications/notification/notification.d.ts +39 -13
  61. package/dist/notifications/notification/notification.js +11 -8
  62. package/dist/notifications/root/notifications-root.d.ts +57 -1
  63. package/dist/notifications/root/notifications-root.js +35 -4
  64. package/dist/number-input/root/number-input-root.d.ts +21 -1
  65. package/dist/number-input/root/number-input-root.js +18 -5
  66. package/dist/object-inspector/index.d.ts +1 -0
  67. package/dist/object-inspector/index.js +1 -0
  68. package/dist/object-inspector/root/object-inspector-root.d.ts +183 -0
  69. package/dist/object-inspector/root/object-inspector-root.js +348 -0
  70. package/dist/outline/root/outline-root.d.ts +16 -1
  71. package/dist/outline/root/outline-root.js +27 -9
  72. package/dist/radio/group/radio-group.d.ts +1 -0
  73. package/dist/scatter-chart/index.d.ts +1 -0
  74. package/dist/scatter-chart/index.js +1 -0
  75. package/dist/scatter-chart/root/scatter-chart-root.d.ts +144 -0
  76. package/dist/scatter-chart/root/scatter-chart-root.js +205 -0
  77. package/dist/skip-link/index.d.ts +1 -0
  78. package/dist/skip-link/index.js +1 -0
  79. package/dist/skip-link/root/skip-link-root.d.ts +81 -0
  80. package/dist/skip-link/root/skip-link-root.js +58 -0
  81. package/dist/speed-dial/root/speed-dial-root.d.ts +1 -0
  82. package/dist/speed-dial/root/speed-dial-root.js +6 -2
  83. package/dist/steps/root/steps-root.d.ts +1 -0
  84. package/dist/table/column/table-column.d.ts +79 -0
  85. package/dist/table/column/table-column.js +125 -5
  86. package/dist/table/root/table-root.d.ts +2 -0
  87. package/dist/table/root/table-root.js +12 -3
  88. package/dist/time-picker/root/time-picker-root.d.ts +9 -0
  89. package/dist/time-picker/root/time-picker-root.js +5 -0
  90. package/dist/toast/root/toast-root.d.ts +2 -0
  91. package/dist/tooltip/root/tooltip-root.js +1 -1
  92. package/dist/tour/root/tour-root.d.ts +1 -0
  93. package/dist/tree/item/tree-item.d.ts +7 -4
  94. package/dist/tree/item/tree-item.js +4 -1
  95. package/dist/video/root/video-root.d.ts +23 -1
  96. package/dist/video/root/video-root.js +42 -22
  97. package/dist/virtual-list/root/virtual-list-root.d.ts +10 -6
  98. package/dist/virtual-list/root/virtual-list-root.js +10 -4
  99. package/package.json +5 -5
  100. package/types/jsx.d.ts +4535 -346
  101. package/types/svelte.d.ts +11 -0
  102. 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
- import { customElement, HelixBeforePinEvent, HelixElement, hostAria, MixinCollection, requestChange, } from '@fusion.dev/helix-core';
8
- import { html, nothing } from 'lit';
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 wanted = this.atBottom;
29
- if (wanted !== this.pinned) {
30
- this.requestPinned(wanted);
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 [...(this.children ?? [])].filter((child) => 'isChatMessage' in child && child.isChatMessage === true);
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
- const messages = this.messages;
91
- log.assign(...messages);
92
- const filled = new Set();
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
- const log = this.log;
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="polite"
415
+ aria-live="off"
144
416
  aria-label=${this.label || 'Conversation'}
145
- aria-busy=${this.loading ? 'true' : nothing}
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.value === '') {
84
- this.seed();
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() {
@@ -1 +1,2 @@
1
+ export * from './patch.js';
1
2
  export * from './root/code-block-root.js';
@@ -1 +1,2 @@
1
+ export * from './patch.js';
1
2
  export * from './root/code-block-root.js';
@@ -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
+ };