@fusion.dev/helix-headless 0.218.0 → 0.220.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 (84) hide show
  1. package/api.json +1 -1
  2. package/custom-elements.json +1 -1
  3. package/dist/breadcrumbs/breadcrumb/breadcrumb.d.ts +13 -2
  4. package/dist/breadcrumbs/breadcrumb/breadcrumb.js +19 -9
  5. package/dist/breadcrumbs/root/breadcrumbs-root.d.ts +16 -1
  6. package/dist/breadcrumbs/root/breadcrumbs-root.js +6 -0
  7. package/dist/button/root/button-root.d.ts +33 -0
  8. package/dist/button/root/button-root.js +13 -3
  9. package/dist/canvas/root/canvas-root.d.ts +11 -0
  10. package/dist/canvas/root/canvas-root.js +1 -0
  11. package/dist/chat/composer/chat-composer.d.ts +221 -6
  12. package/dist/chat/composer/chat-composer.js +249 -19
  13. package/dist/chat/message/chat-message.d.ts +7 -0
  14. package/dist/chat/message/chat-message.js +2 -1
  15. package/dist/chat/root/chat-root.d.ts +1 -0
  16. package/dist/chat/root/chat-root.js +1 -1
  17. package/dist/code-editor/root/code-editor-root.d.ts +48 -51
  18. package/dist/code-editor/root/code-editor-root.js +67 -249
  19. package/dist/copy-button/root/copy-button-root.js +3 -3
  20. package/dist/device-screen/root/device-screen-root.d.ts +11 -0
  21. package/dist/device-screen/root/device-screen-root.js +22 -10
  22. package/dist/dialog/root/dialog-root.js +6 -1
  23. package/dist/drilldown/item/drilldown-item.js +1 -1
  24. package/dist/element-events.generated.js +4 -1
  25. package/dist/filter-input/root/filter-input-root.d.ts +4 -3
  26. package/dist/filter-input/root/filter-input-root.js +1 -20
  27. package/dist/find/index.d.ts +1 -0
  28. package/dist/find/index.js +1 -0
  29. package/dist/find/root/find-root.d.ts +314 -0
  30. package/dist/find/root/find-root.js +577 -0
  31. package/dist/graph/root/graph-root.d.ts +11 -0
  32. package/dist/graph/root/graph-root.js +4 -1
  33. package/dist/index.d.ts +1 -0
  34. package/dist/index.js +1 -0
  35. package/dist/input/root/input-root.d.ts +6 -0
  36. package/dist/input/root/input-root.js +7 -0
  37. package/dist/lightbox/root/lightbox-root.d.ts +4 -1
  38. package/dist/lightbox/root/lightbox-root.js +10 -8
  39. package/dist/listbox/root/listbox-root.d.ts +21 -0
  40. package/dist/listbox/root/listbox-root.js +37 -2
  41. package/dist/log/group/log-group.d.ts +18 -3
  42. package/dist/log/group/log-group.js +31 -4
  43. package/dist/log/root/log-root.d.ts +13 -3
  44. package/dist/log/root/log-root.js +5 -2
  45. package/dist/map/root/map-root.d.ts +11 -0
  46. package/dist/map/root/map-root.js +28 -12
  47. package/dist/menu/root/menu-root.d.ts +8 -0
  48. package/dist/menu/root/menu-root.js +22 -1
  49. package/dist/menu/section/menu-section.d.ts +10 -0
  50. package/dist/menu/section/menu-section.js +10 -0
  51. package/dist/meter/index.d.ts +1 -0
  52. package/dist/meter/index.js +1 -0
  53. package/dist/meter/root/meter-root.d.ts +16 -1
  54. package/dist/meter/root/meter-root.js +27 -11
  55. package/dist/meter/segment/meter-segment.d.ts +74 -0
  56. package/dist/meter/segment/meter-segment.js +47 -0
  57. package/dist/scroll-area/root/scroll-area-root.d.ts +20 -3
  58. package/dist/scroll-area/root/scroll-area-root.js +23 -4
  59. package/dist/spatial-bar-chart/bars.d.ts +125 -5
  60. package/dist/spatial-bar-chart/bars.js +194 -17
  61. package/dist/spatial-bar-chart/root/spatial-bar-chart-root.d.ts +148 -6
  62. package/dist/spatial-bar-chart/root/spatial-bar-chart-root.js +285 -16
  63. package/dist/splitter/root/splitter-root.js +1 -0
  64. package/dist/tabs/root/tabs-root.d.ts +4 -0
  65. package/dist/tabs/tab/tab.d.ts +1 -0
  66. package/dist/tabs/tab/tab.js +2 -2
  67. package/dist/tag/root/tag-root.d.ts +1 -1
  68. package/dist/terminal/root/terminal-root.d.ts +60 -32
  69. package/dist/terminal/root/terminal-root.js +82 -178
  70. package/dist/toolbar/root/toolbar-root.d.ts +4 -1
  71. package/dist/tooltip/root/tooltip-root.d.ts +23 -1
  72. package/dist/tooltip/root/tooltip-root.js +28 -5
  73. package/dist/tour/root/tour-root.d.ts +1 -0
  74. package/dist/tour/root/tour-root.js +4 -1
  75. package/dist/tour/stop/tour-stop.js +1 -0
  76. package/dist/tree/item/tree-item.d.ts +11 -0
  77. package/dist/tree/item/tree-item.js +19 -1
  78. package/examples.json +1 -1
  79. package/lint.json +1 -1
  80. package/package.json +5 -5
  81. package/types/jsx.d.ts +2222 -118
  82. package/types/svelte.d.ts +2 -0
  83. package/types/vue.d.ts +468 -19
  84. package/types/words.d.ts +96 -1
@@ -1,10 +1,16 @@
1
1
  import { __decorate } from "tslib";
2
- import { AriaForwardController, CHART_PLOT, customElement, depthOrder, drawTooltip, formatWords, HelixElement, HelixPlotChangeEvent, HelixPlotTurnEvent, HelixReadingChangeEvent, HelixReadingPressEvent, isRtl, MixinChartSource, MixinChartStatus, spokenReading, words, } from '@fusion.dev/helix-core';
2
+ import { AriaForwardController, CHART_PLOT, customElement, drawTooltip, formatWords, HelixElement, HelixPlotChangeEvent, HelixPlotTurnEvent, HelixReadingChangeEvent, HelixReadingPressEvent, isRtl, MixinChartSource, MixinChartStatus, motionPaused, reducedMotion, shortNumber, spokenReading, words, } from '@fusion.dev/helix-core';
3
3
  import { html, nothing, svg, } from 'lit';
4
4
  import { property, state } from 'lit/decorators.js';
5
5
  import '../../tooltip/index.js';
6
- import { boundsOf, boxView, DragTurn, drawBox, SPATIAL_VIEW, turned, } from '../../spatial-scatter-chart/view.js';
7
- import { barsDrawn, barsOf } from '../bars.js';
6
+ import { boundsOf, boxView, DragTurn, drawBox, SPATIAL_VIEW, TICK_SIZE, turned, } from '../../spatial-scatter-chart/view.js';
7
+ import { barsDrawn, barsOf, climbing, floorOf, grownAt, growTime, heightened, namesOf, paintOrder, rowFits, tallestTimes, widthOf, } from '../bars.js';
8
+ const barSizeOf = (style) => {
9
+ const size = Number.parseFloat(style.columnGap);
10
+ return Number.isFinite(size) && size > 0 ? size : undefined;
11
+ };
12
+ const WORD_LINES = 3.6;
13
+ const CLIMB_SHARE = 0.25;
8
14
  const TURNS = {
9
15
  ArrowDown: { across: 0, up: -1 },
10
16
  ArrowLeft: { across: -1, up: 0 },
@@ -12,12 +18,22 @@ const TURNS = {
12
18
  ArrowUp: { across: 0, up: 1 },
13
19
  };
14
20
  const SERIES = 24;
21
+ const toned = (tone) => tone && /^[a-z][a-z0-9-]*$/.test(tone) ? tone : undefined;
22
+ const around = (frame, points) => {
23
+ const xs = [frame.x, frame.x + frame.width, ...points.map((point) => point.x)];
24
+ const ys = [frame.y, frame.y + frame.height, ...points.map((point) => point.y)];
25
+ const [left, top] = [Math.min(...xs), Math.min(...ys)];
26
+ return { height: Math.max(...ys) - top, width: Math.max(...xs) - left, x: left, y: top };
27
+ };
15
28
  let HelixHeadlessSpatialBarChart = class HelixHeadlessSpatialBarChart extends MixinChartStatus(MixinChartSource(HelixElement)) {
16
29
  constructor() {
17
30
  super(...arguments);
31
+ this.grow = false;
32
+ this.growAgain = false;
18
33
  this.noBox = false;
19
34
  this.openLabel = '{category}, {series}: no value';
20
35
  this.plotLabel = '3D bar chart';
36
+ this.stacked = false;
21
37
  this.summaryLabel = '{count, plural, one {# bar} other {# bars}} in {series, plural, one {# series} other {# series}} over {categories, plural, one {# category} other {# categories}}, from {low} to {high}.';
22
38
  this.rendered = false;
23
39
  this.camera = SPATIAL_VIEW;
@@ -25,6 +41,8 @@ let HelixHeadlessSpatialBarChart = class HelixHeadlessSpatialBarChart extends Mi
25
41
  this.camera = next(this.camera);
26
42
  }, () => this.requestUpdate());
27
43
  this.told = -1;
44
+ this.grew = false;
45
+ this.fresh = new Map();
28
46
  this.aria = new AriaForwardController(this, {
29
47
  target: () => this.partNamed('plot'),
30
48
  });
@@ -71,8 +89,14 @@ let HelixHeadlessSpatialBarChart = class HelixHeadlessSpatialBarChart extends Mi
71
89
  get field() {
72
90
  const rows = this.sourceRows;
73
91
  const columns = this.plottedColumns;
74
- if (this.memo?.rows !== rows || this.memo.columns !== columns) {
75
- this.memo = { columns, field: barsOf(rows, columns), rows };
92
+ const series = this.series;
93
+ if (this.memo?.rows !== rows ||
94
+ this.memo.columns !== columns ||
95
+ this.memo.series !== series ||
96
+ this.memo.stacked !== this.stacked) {
97
+ const tones = series.map((one) => new Map(one.points.map((point) => [point.at, toned(point.tone)])));
98
+ const field = barsOf(rows, columns, this.stacked, (row, depth) => tones[depth]?.get(row));
99
+ this.memo = { columns, field, rows, series, stacked: this.stacked };
76
100
  }
77
101
  return this.memo.field;
78
102
  }
@@ -99,10 +123,158 @@ let HelixHeadlessSpatialBarChart = class HelixHeadlessSpatialBarChart extends Mi
99
123
  }
100
124
  willUpdate(changed) {
101
125
  super.willUpdate(changed);
102
- this.pointer.grid = Math.max(1, this.field.series.length);
126
+ const field = this.field;
127
+ this.pointer.grid = Math.max(1, field.series.length);
128
+ const labels = new Set(field.categories);
129
+ const moves = typeof IntersectionObserver === 'function' &&
130
+ typeof requestAnimationFrame === 'function' &&
131
+ !reducedMotion() &&
132
+ !motionPaused();
133
+ if (this.grow && !this.grew && field.readings > 0) {
134
+ this.grew = true;
135
+ this.growth = moves ? {} : undefined;
136
+ }
137
+ else if (this.grow && this.grew && field.readings > 0 && this.drawnLabels && moves) {
138
+ const added = [...labels].filter((label) => !this.drawnLabels?.has(label));
139
+ if (added.length > 0 && !this.growth) {
140
+ const since = performance.now();
141
+ added.forEach((label, index) => {
142
+ this.fresh.set(label, { count: added.length, index, since });
143
+ });
144
+ this.followFresh();
145
+ }
146
+ }
147
+ if (field.readings > 0) {
148
+ this.drawnLabels = labels;
149
+ }
150
+ }
151
+ followFresh() {
152
+ const frame = () => {
153
+ const now = performance.now();
154
+ for (const [label, one] of this.fresh) {
155
+ if (now - one.since >= growTime(one.count)) {
156
+ this.fresh.delete(label);
157
+ }
158
+ }
159
+ this.requestUpdate();
160
+ if (this.fresh.size > 0) {
161
+ requestAnimationFrame(frame);
162
+ }
163
+ };
164
+ requestAnimationFrame(frame);
165
+ }
166
+ disconnectedCallback() {
167
+ super.disconnectedCallback();
168
+ this.sizes?.disconnect();
169
+ this.sizes = undefined;
170
+ this.measured = undefined;
171
+ this.seen?.disconnect();
172
+ this.seen = undefined;
173
+ this.gone?.disconnect();
174
+ this.gone = undefined;
175
+ }
176
+ measure() {
177
+ const box = this.partNamed('fallback');
178
+ if (!box || box === this.measured || typeof ResizeObserver !== 'function') {
179
+ return;
180
+ }
181
+ this.measured = box;
182
+ this.sizes?.disconnect();
183
+ this.sizes = new ResizeObserver(([entry]) => {
184
+ const { height, width } = entry?.contentRect ?? { height: 0, width: 0 };
185
+ const style = getComputedStyle(box);
186
+ const font = Number.parseFloat(style.fontSize) || 16;
187
+ const bar = barSizeOf(style);
188
+ const was = this.room;
189
+ if (was?.width !== width || was.height !== height || was.font !== font || was.bar !== bar) {
190
+ this.room =
191
+ height > 0 && width > 0
192
+ ? { font, height, width, ...(bar && bar > 0 ? { bar } : {}) }
193
+ : undefined;
194
+ }
195
+ });
196
+ this.sizes.observe(box);
197
+ }
198
+ get fits() {
199
+ const box = this.partNamed('fallback');
200
+ if (!box || !this.variant || typeof getComputedStyle !== 'function') {
201
+ return undefined;
202
+ }
203
+ const style = getComputedStyle(box);
204
+ const bar = barSizeOf(style);
205
+ const width = box.clientWidth;
206
+ return bar && width > 0 ? rowFits(width, bar) : undefined;
207
+ }
208
+ sized(read, projection, layout, room) {
209
+ const first = barsDrawn(read, projection, layout)[0];
210
+ if (!first || !room.bar) {
211
+ return undefined;
212
+ }
213
+ const frameOf = (field, grown) => {
214
+ const floor = this.noBox ? [] : floorOf(field, projection, layout);
215
+ const drawn = barsDrawn(field, projection, layout, grown);
216
+ return boundsOf([...drawn.flatMap((bar) => bar.corners), ...floor.flatMap((one) => one.corners)], 0);
217
+ };
218
+ const unit = Math.max(widthOf(first) / room.bar, frameOf(read, () => 0).width / Math.max(1, room.width - room.font * 2));
219
+ const field = climbing(read, room.height * CLIMB_SHARE * unit, frameOf(read, () => 0).width);
220
+ const low = frameOf(field, () => 0);
221
+ const high = frameOf(field);
222
+ const tall = (room.height - room.font * WORD_LINES) * unit;
223
+ const rises = high.height - low.height;
224
+ const fills = rises > 0 ? Math.max(0.05, (tall - low.height) / rises) : 1;
225
+ const times = Math.min(fills, tallestTimes(field, projection, barsDrawn(field, projection, layout)));
226
+ return { field: heightened(field, times), unit };
227
+ }
228
+ watchGrowth() {
229
+ if (!this.growth || this.growth.since !== undefined || this.seen) {
230
+ return;
231
+ }
232
+ this.seen = new IntersectionObserver(([entry]) => {
233
+ if (!entry?.isIntersecting || !this.growth) {
234
+ return;
235
+ }
236
+ this.seen?.disconnect();
237
+ this.seen = undefined;
238
+ const since = performance.now();
239
+ this.growth = { since };
240
+ const length = growTime(this.field.categories.length);
241
+ const frame = () => {
242
+ if (this.growth?.since !== since) {
243
+ return;
244
+ }
245
+ if (performance.now() - since >= length) {
246
+ this.growth = undefined;
247
+ this.watchExit();
248
+ return;
249
+ }
250
+ this.requestUpdate();
251
+ requestAnimationFrame(frame);
252
+ };
253
+ requestAnimationFrame(frame);
254
+ }, { threshold: 0.4 });
255
+ this.seen.observe(this);
256
+ }
257
+ watchExit() {
258
+ if (!this.growAgain || this.gone) {
259
+ return;
260
+ }
261
+ this.gone = new IntersectionObserver(([entry]) => {
262
+ if (!entry || entry.isIntersecting) {
263
+ return;
264
+ }
265
+ this.gone?.disconnect();
266
+ this.gone = undefined;
267
+ this.fresh.clear();
268
+ this.growth = {};
269
+ });
270
+ this.gone.observe(this);
103
271
  }
104
272
  connectedCallback() {
105
273
  super.connectedCallback();
274
+ if (this.hasUpdated) {
275
+ this.measure();
276
+ this.watchGrowth();
277
+ }
106
278
  this.pointer.axis = 'none';
107
279
  this.pointer.onReading = (heard, detail) => {
108
280
  const cell = this.field.cells[detail.index];
@@ -116,6 +288,8 @@ let HelixHeadlessSpatialBarChart = class HelixHeadlessSpatialBarChart extends Mi
116
288
  }
117
289
  updated(changed) {
118
290
  super.updated(changed);
291
+ this.measure();
292
+ this.watchGrowth();
119
293
  const field = this.field;
120
294
  if (field !== this.last) {
121
295
  this.last = field;
@@ -156,14 +330,85 @@ let HelixHeadlessSpatialBarChart = class HelixHeadlessSpatialBarChart extends Mi
156
330
  </div>
157
331
  ${readable ? this.renderSummary(field) : this.renderStatus()}${this.renderSonify()}${this.renderTable()}`;
158
332
  }
159
- renderFallback(field, at) {
160
- const view = boxView(field.axes, this.camera, [], this.noBox);
161
- const bars = barsDrawn(field, view.projection);
333
+ renderFallback(read, at) {
334
+ const layout = this.variant ?? 'grid';
335
+ const lined = layout === 'grid' ? undefined : layout;
336
+ const bare = this.noBox || lined !== undefined;
337
+ const view = boxView(read.axes, this.camera, [], bare);
338
+ const room = this.room;
339
+ const sized = lined && room?.bar ? this.sized(read, view.projection, lined, room) : undefined;
340
+ const field = sized?.field ?? read;
341
+ const full = barsDrawn(field, view.projection, layout);
342
+ const growth = this.growth;
343
+ const since = growth?.since;
344
+ const elapsed = since === undefined ? 0 : performance.now() - since;
345
+ const categories = field.categories.length;
346
+ const now = performance.now();
347
+ const bars = growth
348
+ ? barsDrawn(field, view.projection, layout, (one) => grownAt(one.across, categories, elapsed))
349
+ : this.fresh.size > 0
350
+ ? barsDrawn(field, view.projection, layout, (one) => {
351
+ const added = this.fresh.get(one.label);
352
+ return added ? grownAt(added.index, added.count, now - added.since) : 1;
353
+ })
354
+ : full;
355
+ const floor = lined && !this.noBox ? floorOf(field, view.projection, lined) : [];
162
356
  const active = bars.find((bar) => bar.index === at);
163
357
  const cell = field.cells[at];
164
- const bounds = this.drag.frame(this.noBox
165
- ? boundsOf(bars.flatMap((bar) => bar.corners), 0.1)
166
- : view.bounds, view.on([0, 0, 0]));
358
+ const base = bare
359
+ ? boundsOf([...full.flatMap((bar) => bar.corners), ...floor.flatMap((one) => one.corners)], 0.1)
360
+ : view.bounds;
361
+ const pixelOf = (frame) => sized
362
+ ? sized.unit
363
+ : room
364
+ ? 1 / Math.min(room.width / frame.width, room.height / frame.height)
365
+ : undefined;
366
+ const sizeOf = (frame) => {
367
+ const pixel = pixelOf(frame);
368
+ return room && pixel !== undefined ? room.font * pixel : TICK_SIZE;
369
+ };
370
+ const wordsAt = (size) => {
371
+ const names = floor.length > 0 ? namesOf(field, floor, size) : [];
372
+ const values = full.flatMap((bar) => {
373
+ const one = field.cells[bar.index];
374
+ const text = one?.tone ? this.sourceRows[one.row]?.cells[one.column]?.text : undefined;
375
+ return one && text
376
+ ? [
377
+ {
378
+ index: bar.index,
379
+ text,
380
+ tone: one.tone,
381
+ x: bar.drawn.peak.x,
382
+ y: bar.drawn.crown - size,
383
+ },
384
+ ]
385
+ : [];
386
+ });
387
+ const extents = [...names, ...values].flatMap((word) => {
388
+ const half = (word.text.length * size * 0.62) / 2;
389
+ return [
390
+ { x: word.x - half, y: word.y - size * 0.7 },
391
+ { x: word.x + half, y: word.y + size * 0.7 },
392
+ ];
393
+ });
394
+ return { frame: around(base, extents), names, values };
395
+ };
396
+ const first = wordsAt(sizeOf(base));
397
+ const size = sizeOf(first.frame);
398
+ const { frame: fits, names, values } = wordsAt(size);
399
+ const wide = sized && room ? room.width * sized.unit : 0;
400
+ const high = sized && room ? room.height * sized.unit : 0;
401
+ const placed = sized && fits.width <= wide + 1e-9 && fits.height <= high + 1e-9
402
+ ? {
403
+ height: high,
404
+ width: wide,
405
+ x: fits.x + (fits.width - wide) / 2,
406
+ y: fits.y + (fits.height - high) / 2,
407
+ }
408
+ : fits;
409
+ const bounds = this.drag.frame(placed, view.on([0, 0, 0]));
410
+ const pixel = pixelOf(bounds);
411
+ const font = Math.round(size * 10_000) / 10_000;
167
412
  return html `<div aria-hidden="true" dir="ltr" ?hidden=${this.rendered} part="fallback">
168
413
  <svg
169
414
  part="drawing"
@@ -176,15 +421,21 @@ let HelixHeadlessSpatialBarChart = class HelixHeadlessSpatialBarChart extends Mi
176
421
  @pointerleave=${this.onDrawingLeave}
177
422
  @pointermove=${this.onDrawingMove}
178
423
  @pointerup=${this.onDrawingTap}
179
- >${this.noBox ? nothing : drawBox(view)}${depthOrder(view.projection, bars).map((bar) => svg `${this.renderBar(field, bar, at)}${bar === active ? svg `<path d=${active.drawn.outline} part="ring"></path>` : nothing}`)}${active
180
- ? svg `<path d=${active.drawn.outline} part="ring-behind"></path><foreignObject height="0.001" overflow="visible" width="0.001" x=${active.drawn.peak.x} y=${active.drawn.peak.y}>${this.renderOwnTooltip(cell?.row)}</foreignObject>`
424
+ >${bars.some((bar) => field.cells[bar.index]?.tone)
425
+ ? svg `<defs><filter height="160%" id="helix-spatial-bar-chart-glow" width="500%" x="-200%" y="-30%"><feGaussianBlur stdDeviation=${(pixel ?? 0.006) * 8}></feGaussianBlur></filter></defs>`
426
+ : nothing}${bare ? nothing : drawBox(view)}${floor.map((plate) => svg `<path d=${plate.d} part="floor"></path>`)}${names.map((name) => svg `<text data-tone=${name.tone ?? nothing} dominant-baseline="central" font-size=${font} part="tick" style="${name.tone ? `--chart-series: var(--spatial-bar-chart-tone-${name.tone})` : ''}" text-anchor="middle" x=${shortNumber(name.x)} y=${shortNumber(name.y)}>${name.text}</text>`)}${paintOrder(view.projection, bars).map((bar) => svg `${field.cells[bar.index]?.tone ? svg `<path d=${bar.drawn.outline} fill="none" filter="url(#helix-spatial-bar-chart-glow)" part="glow" style="--chart-series: var(--spatial-bar-chart-tone-${field.cells[bar.index]?.tone})"></path>` : nothing}${this.renderBar(field, bar, at)}${bar === active ? svg `<path d=${active.drawn.outline} part="ring"></path>` : nothing}`)}${values.map((value) => {
427
+ const crown = bars.find((bar) => bar.index === value.index)?.drawn.crown ?? value.y + size;
428
+ return svg `<text data-tone=${value.tone ?? nothing} dominant-baseline="central" font-size=${font} part="value" text-anchor="middle" x=${shortNumber(value.x)} y=${shortNumber(crown - size)}>${value.text}</text>`;
429
+ })}${active
430
+ ? svg `<path d=${active.drawn.outline} part="ring-behind"></path><foreignObject height="0.001" overflow="visible" width="0.001" x=${active.drawn.peak.x} y=${active.drawn.crown}>${this.renderOwnTooltip(cell?.row)}</foreignObject>`
181
431
  : nothing}</svg>
182
432
  </div>`;
183
433
  }
184
434
  renderBar(field, bar, at) {
185
- const depth = field.cells[bar.index]?.depth ?? 0;
435
+ const one = field.cells[bar.index];
436
+ const series = ((one?.depth ?? 0) % SERIES) + 1;
186
437
  const short = (value) => Math.round(value * 1000) / 1000;
187
- return svg `<g data-index=${bar.index} part="bar${bar.index === at ? ' bar-active' : ''}" style="--chart-series: var(--chart-series-${(depth % SERIES) + 1})">${bar.drawn.faces.map((face) => svg `<path d=${face.d} part="face" style="--spatial-bar-chart-shade: ${short(face.darken)}"></path>`)}</g>`;
438
+ return svg `<g data-index=${bar.index} data-tone=${one?.tone ?? nothing} part="bar${bar.index === at ? ' bar-active' : ''}" style="--chart-series: ${one?.tone ? `var(--spatial-bar-chart-tone-${one.tone}, var(--chart-series-${series}))` : `var(--chart-series-${series})`}">${bar.drawn.faces.map((face) => svg `<path d=${face.d} part="face" style="--spatial-bar-chart-shade: ${short(face.darken)}"></path>`)}</g>`;
188
439
  }
189
440
  renderLive(field, at) {
190
441
  const cell = field.cells[at];
@@ -232,6 +483,12 @@ let HelixHeadlessSpatialBarChart = class HelixHeadlessSpatialBarChart extends Mi
232
483
  return html `<p id="helix-chart-summary" part="summary">${said}</p>`;
233
484
  }
234
485
  };
486
+ __decorate([
487
+ property({ reflect: true, type: Boolean })
488
+ ], HelixHeadlessSpatialBarChart.prototype, "grow", void 0);
489
+ __decorate([
490
+ property({ attribute: 'grow-again', reflect: true, type: Boolean })
491
+ ], HelixHeadlessSpatialBarChart.prototype, "growAgain", void 0);
235
492
  __decorate([
236
493
  property({ attribute: 'no-box', reflect: true, type: Boolean })
237
494
  ], HelixHeadlessSpatialBarChart.prototype, "noBox", void 0);
@@ -243,10 +500,16 @@ __decorate([
243
500
  words,
244
501
  property({ attribute: 'plot-label' })
245
502
  ], HelixHeadlessSpatialBarChart.prototype, "plotLabel", void 0);
503
+ __decorate([
504
+ property({ reflect: true, type: Boolean })
505
+ ], HelixHeadlessSpatialBarChart.prototype, "stacked", void 0);
246
506
  __decorate([
247
507
  words,
248
508
  property({ attribute: 'summary-label' })
249
509
  ], HelixHeadlessSpatialBarChart.prototype, "summaryLabel", void 0);
510
+ __decorate([
511
+ property({ reflect: true })
512
+ ], HelixHeadlessSpatialBarChart.prototype, "variant", void 0);
250
513
  __decorate([
251
514
  state()
252
515
  ], HelixHeadlessSpatialBarChart.prototype, "rendered", void 0);
@@ -256,6 +519,12 @@ __decorate([
256
519
  __decorate([
257
520
  state()
258
521
  ], HelixHeadlessSpatialBarChart.prototype, "place", void 0);
522
+ __decorate([
523
+ state()
524
+ ], HelixHeadlessSpatialBarChart.prototype, "room", void 0);
525
+ __decorate([
526
+ state()
527
+ ], HelixHeadlessSpatialBarChart.prototype, "growth", void 0);
259
528
  HelixHeadlessSpatialBarChart = __decorate([
260
529
  customElement('helix-headless-spatial-bar-chart')
261
530
  ], HelixHeadlessSpatialBarChart);
@@ -20,6 +20,7 @@ let HelixHeadlessSplitter = class HelixHeadlessSplitter extends HelixElement {
20
20
  set collapsed(next) {
21
21
  const previous = this.collapsed;
22
22
  this.collapsedState = next ?? 'none';
23
+ this.reached = undefined;
23
24
  this.requestUpdate('collapsed', previous);
24
25
  }
25
26
  toPercent(value, fallback) {
@@ -60,6 +60,10 @@ declare const HelixHeadlessTabs_base: (new (...args: any[]) => import("@fusion.d
60
60
  *
61
61
  * A tab with `dismissible` can be closed: see `Tabs.Tab`.
62
62
  *
63
+ * **Panels are optional.** A set with none is a strip whose tabs stand for
64
+ * something the page shows elsewhere: the keys, the selection, `active` and
65
+ * `helix-activate` work the same, and no tab names a panel it controls.
66
+ *
63
67
  * The `indicator` part moves to the selected tab and carries its place and
64
68
  * size, so a mark of your own can be drawn there: style the part, or slot an
65
69
  * element into `indicator`, which sits inside the part and reads the same
@@ -34,6 +34,7 @@ declare const HelixHeadlessTab_base: (new (...args: any[]) => import("@fusion.de
34
34
  *
35
35
  * @csspart dismiss - The mark a pointer presses to close a `dismissible` tab.
36
36
  * @csspart dismiss-mark - The glyph on that mark, where none is slotted.
37
+ * @csspart label - The slot the tab's label is in.
37
38
  * @csspart tab - The tab itself.
38
39
  *
39
40
  * @cssstate selected - While it is the tab selected.
@@ -88,7 +88,7 @@ let HelixHeadlessTab = class HelixHeadlessTab extends MixinDismissible(MixinSele
88
88
  @click=${this.handleLinkClick}
89
89
  @keydown=${this.handleKeydown}
90
90
  >
91
- <slot></slot>${this.renderDismiss()}
91
+ <slot part="label"></slot>${this.renderDismiss()}
92
92
  </a>`;
93
93
  }
94
94
  return html `<button
@@ -102,7 +102,7 @@ let HelixHeadlessTab = class HelixHeadlessTab extends MixinDismissible(MixinSele
102
102
  @click=${() => this.select()}
103
103
  @keydown=${this.handleKeydown}
104
104
  >
105
- <slot></slot>${this.renderDismiss()}
105
+ <slot part="label"></slot>${this.renderDismiss()}
106
106
  </button>`;
107
107
  }
108
108
  };
@@ -42,7 +42,7 @@ declare const HelixHeadlessTag_base: (new (...args: any[]) => import("@fusion.de
42
42
  *
43
43
  * @slot - The tag's label.
44
44
  * @slot dismiss-icon - Replaces the glyph on the remove button.
45
- * @slot start - The category the label belongs to, shown before it in a heavier weight.
45
+ * @slot start - The category the label belongs to, shown before it in a heavier weight, or an icon.
46
46
  *
47
47
  * @event {HelixDismissEvent} helix-dismiss - The remove button was pressed. The tag does not remove itself, so remove it in response. Cancelable.
48
48
  *
@@ -1,7 +1,9 @@
1
1
  import { HelixElement, type TerminalResizeDetail } from '@fusion.dev/helix-core';
2
2
  import { nothing, type PropertyValues, type TemplateResult } from 'lit';
3
3
  import '../../button/index.js';
4
+ import '../../find/index.js';
4
5
  import '../../icon-button/index.js';
6
+ import '../../tooltip/index.js';
5
7
  import '../../toolbar/index.js';
6
8
  /** How a search through a terminal's output moves. */
7
9
  export interface TerminalFindOptions {
@@ -163,6 +165,7 @@ export interface TerminalSink {
163
165
  * @slot restart-icon - Replaces the glyph inside the Restart button.
164
166
  *
165
167
  * @event {HelixCopyEvent} helix-copy - The copy control or its key put the selection on the clipboard.
168
+ * @event {HelixFoundEvent} helix-found - The find bar searched or moved among the matches, with `count` and the current match's `index`, counting from 0, or -1 for none.
166
169
  * @event {HelixTerminalBellEvent} helix-terminal-bell - The program rang the bell. The frame flashes once.
167
170
  * @event {HelixTerminalInputEvent} helix-terminal-input - The reader typed or pasted, with `data` encoded for the program. Write it to the process. Not sent while `readonly`, or once `exit-code` is set.
168
171
  * @event {HelixTerminalLinkEvent} helix-terminal-link - A link in the output was activated, with its `href`. Cancelable: cancel it to open the link yourself. Left alone, a web address opens in a new tab.
@@ -184,23 +187,23 @@ export interface TerminalSink {
184
187
  * @csspart copy-spinner - The ring the copy button turns while it is pending.
185
188
  * @csspart copy-text - The box the copy button's icon sits in.
186
189
  * @csspart exit-actions - The `exit-actions` slot, beside Restart in the status line.
187
- * @csspart find - The button that opens and closes the find bar. Expanded while it is open.
188
- * @csspart find-actions - The `find-actions` slot, first in the find bar's toolbar.
189
- * @csspart find-bar - The bar under the header where the reader searches the output.
190
- * @csspart find-button - The find button's inner button.
190
+ * @csspart find - The find bar under the header, where the reader searches the output: a Find of its own.
191
+ * @csspart find-actions - The `find-actions` slot, in the find bar's toolbar.
192
+ * @csspart find-bar - The find bar's strip: the field, the count, and the toolbar.
191
193
  * @csspart find-close - The button that closes the find bar.
192
194
  * @csspart find-close-button - The find bar's close button's inner button.
193
195
  * @csspart find-close-live - The find bar's close button's live region, which speaks while the button is pending.
194
196
  * @csspart find-close-spinner - The ring the find bar's close button turns while it is pending.
195
197
  * @csspart find-close-text - The box the find bar's close button's icon sits in.
196
- * @csspart find-controls - The toolbar at the end of the find bar: your find actions, then the buttons that move between matches and close the bar.
198
+ * @csspart find-controls - The toolbar at the end of the find bar: the options, your find actions, and the buttons that move between matches and close the bar.
197
199
  * @csspart find-controls-toolbar - The box inside the find bar's toolbar that lays its buttons out.
198
200
  * @csspart find-count - Which match is current and how many there are, such as "3 of 12 matches".
199
201
  * @csspart find-field - The search field in the find bar.
200
- * @csspart find-live - The find button's live region, which speaks while the button is pending.
202
+ * @csspart find-live - The find bar's live region, which says the count as it changes.
201
203
  * @csspart find-match-case - The button that matches case in the find bar. Pressed while it does.
202
204
  * @csspart find-match-case-button - The match case button's inner button.
203
205
  * @csspart find-match-case-live - The match case button's live region, which speaks while the button is pending.
206
+ * @csspart find-match-case-pressed - The match case button while case has to match, beside `find-match-case`.
204
207
  * @csspart find-match-case-spinner - The ring the match case button turns while it is pending.
205
208
  * @csspart find-match-case-text - The box the match case button's icon sits in.
206
209
  * @csspart find-next - The button that moves to the next match.
@@ -216,13 +219,34 @@ export interface TerminalSink {
216
219
  * @csspart find-regex - The button that reads the find field as a regular expression. Pressed while it does.
217
220
  * @csspart find-regex-button - The regular expression button's inner button.
218
221
  * @csspart find-regex-live - The regular expression button's live region, which speaks while the button is pending.
222
+ * @csspart find-regex-pressed - The regular expression button while the field reads as one, beside `find-regex`.
219
223
  * @csspart find-regex-spinner - The ring the regular expression button turns while it is pending.
220
224
  * @csspart find-regex-text - The box the regular expression button's icon sits in.
221
- * @csspart find-spinner - The ring the find button turns while it is pending.
222
- * @csspart find-text - The box the find button's icon sits in.
225
+ * @csspart find-replace - The find bar's Replace button, which a terminal does not draw, since its output cannot be changed.
226
+ * @csspart find-replace-all - The find bar's Replace all button, which a terminal does not draw.
227
+ * @csspart find-replace-all-button - The Replace all button's inner button.
228
+ * @csspart find-replace-all-live - The Replace all button's live region, which speaks while the button is pending.
229
+ * @csspart find-replace-all-spinner - The ring the Replace all button turns while it is pending.
230
+ * @csspart find-replace-all-text - The box the Replace all button's words sits in.
231
+ * @csspart find-replace-button - The Replace button's inner button.
232
+ * @csspart find-replace-field - The find bar's replace field, which a terminal does not draw.
233
+ * @csspart find-replace-live - The Replace button's live region, which speaks while the button is pending.
234
+ * @csspart find-replace-spinner - The ring the Replace button turns while it is pending.
235
+ * @csspart find-replace-text - The box the Replace button's words sits in.
236
+ * @csspart find-replace-toggle - The button that shows the find bar's replace row, which a terminal does not draw.
237
+ * @csspart find-replace-toggle-button - The replace toggle's inner button.
238
+ * @csspart find-replace-toggle-live - The replace toggle's live region, which speaks while the button is pending.
239
+ * @csspart find-replace-toggle-spinner - The ring the replace toggle turns while it is pending.
240
+ * @csspart find-replace-toggle-text - The box the replace toggle's icon sits in.
241
+ * @csspart find-toggle - The button in the header that opens and closes the find bar. Expanded while it is open.
242
+ * @csspart find-toggle-button - The find button's inner button.
243
+ * @csspart find-toggle-live - The find button's live region, which speaks while the button is pending.
244
+ * @csspart find-toggle-spinner - The ring the find button turns while it is pending.
245
+ * @csspart find-toggle-text - The box the find button's icon sits in.
223
246
  * @csspart find-whole-word - The button that matches whole words only in the find bar. Pressed while it does.
224
247
  * @csspart find-whole-word-button - The whole word button's inner button.
225
248
  * @csspart find-whole-word-live - The whole word button's live region, which speaks while the button is pending.
249
+ * @csspart find-whole-word-pressed - The whole word button while only whole words match, beside `find-whole-word`.
226
250
  * @csspart find-whole-word-spinner - The ring the whole word button turns while it is pending.
227
251
  * @csspart find-whole-word-text - The box the whole word button's icon sits in.
228
252
  * @csspart fullscreen - The button that makes the terminal fill the screen. Pressed while it does.
@@ -240,6 +264,16 @@ export interface TerminalSink {
240
264
  * @csspart restart-spinner - The ring the Restart button turns while it is pending.
241
265
  * @csspart restart-text - The Restart button's label.
242
266
  * @csspart status - The line under the terminal that says the process exited, or that Tab moves focus.
267
+ * @csspart tooltip-arrow - The arrow of the tooltip that names each button, forwarded from `Tooltip`.
268
+ * @csspart tooltip-arrow-bottom - The arrow while the bubble is below its button, on the bubble's top edge. Set together with `tooltip-arrow`.
269
+ * @csspart tooltip-arrow-left - The arrow while the bubble is left of its button, on the bubble's right edge. Set together with `tooltip-arrow`.
270
+ * @csspart tooltip-arrow-right - The arrow while the bubble is right of its button, on the bubble's left edge. Set together with `tooltip-arrow`.
271
+ * @csspart tooltip-arrow-top - The arrow while the bubble is above its button, on the bubble's bottom edge. Set together with `tooltip-arrow`.
272
+ * @csspart tooltip-surface - The bubble of the tooltip that names each button, forwarded from `Tooltip`.
273
+ * @csspart tooltip-surface-bottom - The bubble while it is below its button. Set together with `tooltip-surface`.
274
+ * @csspart tooltip-surface-left - The bubble while it is left of its button. Set together with `tooltip-surface`.
275
+ * @csspart tooltip-surface-right - The bubble while it is right of its button. Set together with `tooltip-surface`.
276
+ * @csspart tooltip-surface-top - The bubble while it is above its button. Set together with `tooltip-surface`.
243
277
  * @csspart viewport - The box the terminal is drawn in.
244
278
  *
245
279
  * @cssproperty --terminal-active-match-background-color - The ground of the current match of a search.
@@ -374,16 +408,12 @@ export declare class HelixHeadlessTerminal extends HelixElement {
374
408
  tabMovesFocusLabel: string;
375
409
  /** What screen readers hear when Tab goes back to typing in the terminal. */
376
410
  tabTypesLabel: string;
377
- /** Whether the find bar is open. */
411
+ /** Whether the find bar is open, as it last said. */
378
412
  private findOpen;
379
- /** What the find field holds. */
380
- private query;
381
- /** The current match, counting from `0`, or `-1` for none. */
382
- private matchIndex;
383
- /** How many matches the search found, or `undefined` before one ran. */
384
- private matchCount;
385
- /** Whether the regular expression in the field cannot be read, which the count says instead. */
386
- private invalid;
413
+ /** The find bar drawn now, and how the renderer reports a search to it. */
414
+ private find;
415
+ /** The options the renderer last searched with, which a search with others ends first. */
416
+ private searchedWith;
387
417
  /** Whether Tab moves focus, after Control+M. */
388
418
  private tabMovesFocus;
389
419
  /** Whether any text is selected in the grid. */
@@ -457,8 +487,6 @@ export declare class HelixHeadlessTerminal extends HelixElement {
457
487
  private say;
458
488
  /** Whether `controls`, or the resting controls, names a control the renderer can answer. */
459
489
  private draws;
460
- /** The find bar's count, as the reader reads it. */
461
- private get countText();
462
490
  /** The chord that switches Tab, as a reader says it. */
463
491
  private get tabKey();
464
492
  /**
@@ -479,16 +507,15 @@ export declare class HelixHeadlessTerminal extends HelixElement {
479
507
  * a key the frame takes never reaches the program.
480
508
  */
481
509
  private readonly handleKeys;
482
- /** Opens the find bar and puts focus in its field, searching again for what it holds. */
510
+ /** Opens the find bar and puts focus in its field. */
483
511
  private openFind;
484
- /** Closes the find bar, takes its highlights off, and returns focus to the terminal. */
485
- private closeFind;
486
- /** Searches for what the field holds, toward the bottom or with `backward` toward the top. */
487
- private search;
488
- /** Turns one of the find bar's options on or off, and searches again with it, staying on a match that still matches. */
489
- private toggleOption;
490
- private readonly handleFindInput;
491
- private readonly handleFindKeys;
512
+ /**
513
+ * The renderer, as the find bar searches through it. The renderer reports
514
+ * the matches through `found`, once it has counted them.
515
+ */
516
+ private readonly finder;
517
+ /** The find bar opened or closed, which the find button shows. */
518
+ private readonly handleFindToggle;
492
519
  /** Copies the selection, and says so. */
493
520
  private copySelection;
494
521
  private readonly handleActions;
@@ -501,6 +528,8 @@ export declare class HelixHeadlessTerminal extends HelixElement {
501
528
  */
502
529
  protected willUpdate(changed: PropertyValues<this>): void;
503
530
  protected updated(changed: PropertyValues<this>): void;
531
+ /** Hands the find bar drawn now the renderer to search through, once per bar. */
532
+ private attachFind;
504
533
  connectedCallback(): void;
505
534
  disconnectedCallback(): void;
506
535
  /** Whether any built-in control is drawn. */
@@ -515,10 +544,9 @@ export declare class HelixHeadlessTerminal extends HelixElement {
515
544
  */
516
545
  protected renderControls(): TemplateResult;
517
546
  /**
518
- * The find bar, kept in the tree while closed so its field keeps what was
519
- * typed. The field stands before the toolbar and outside it, because a
520
- * toolbar leaves the arrows to a text field, and the buttons in one with it
521
- * would be out of their reach.
547
+ * The find bar, a Find of the frame's own that searches through the
548
+ * renderer, kept in the tree while closed so its field keeps what was
549
+ * typed. The frame takes the find key itself, so the bar takes none.
522
550
  */
523
551
  protected renderFind(): TemplateResult | typeof nothing;
524
552
  /**