@fusion.dev/helix-headless 0.31.0 → 0.33.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 (62) hide show
  1. package/custom-elements.json +880 -177
  2. package/dist/area-chart/root/area-chart-root.d.ts +8 -0
  3. package/dist/area-chart/root/area-chart-root.js +1 -0
  4. package/dist/bar-chart/root/bar-chart-root.d.ts +8 -0
  5. package/dist/bar-chart/root/bar-chart-root.js +2 -1
  6. package/dist/breadcrumbs/breadcrumb/breadcrumb.js +1 -1
  7. package/dist/carousel/root/carousel-root.d.ts +12 -0
  8. package/dist/carousel/root/carousel-root.js +17 -11
  9. package/dist/chat/composer/chat-composer.d.ts +4 -0
  10. package/dist/chat/root/chat-root.d.ts +17 -0
  11. package/dist/chat/root/chat-root.js +12 -1
  12. package/dist/code-block/root/code-block-root.d.ts +8 -0
  13. package/dist/code-block/root/code-block-root.js +4 -1
  14. package/dist/color-picker/root/color-picker-root.js +2 -1
  15. package/dist/copy-button/root/copy-button-root.d.ts +8 -0
  16. package/dist/copy-button/root/copy-button-root.js +4 -1
  17. package/dist/data-table/root/data-table-root.d.ts +22 -3
  18. package/dist/data-table/root/data-table-root.js +25 -16
  19. package/dist/date-input/root/date-input-root.d.ts +34 -1
  20. package/dist/date-input/root/date-input-root.js +27 -1
  21. package/dist/date-picker/root/date-picker-root.js +6 -8
  22. package/dist/donut-chart/root/donut-chart-root.d.ts +21 -5
  23. package/dist/donut-chart/root/donut-chart-root.js +18 -6
  24. package/dist/drilldown/item/drilldown-item.d.ts +36 -1
  25. package/dist/drilldown/item/drilldown-item.js +30 -3
  26. package/dist/drilldown/root/drilldown-root.js +5 -4
  27. package/dist/heatmap/root/heatmap-root.d.ts +11 -2
  28. package/dist/icon/root/icon-root.d.ts +8 -0
  29. package/dist/icon/root/icon-root.js +5 -0
  30. package/dist/line-chart/root/line-chart-root.d.ts +9 -1
  31. package/dist/line-chart/root/line-chart-root.js +1 -0
  32. package/dist/menu/item/menu-item.js +1 -1
  33. package/dist/menu/root/menu-root.js +5 -4
  34. package/dist/object-inspector/root/object-inspector-root.d.ts +8 -0
  35. package/dist/object-inspector/root/object-inspector-root.js +20 -15
  36. package/dist/pagination/root/pagination-root.js +1 -1
  37. package/dist/pin-input/root/pin-input-root.d.ts +7 -1
  38. package/dist/pin-input/root/pin-input-root.js +5 -7
  39. package/dist/scatter-chart/root/scatter-chart-root.d.ts +20 -0
  40. package/dist/scatter-chart/root/scatter-chart-root.js +15 -9
  41. package/dist/scroll-area/root/scroll-area-root.js +3 -2
  42. package/dist/sparkline/root/sparkline-root.js +1 -0
  43. package/dist/splitter/root/splitter-root.d.ts +11 -0
  44. package/dist/splitter/root/splitter-root.js +16 -16
  45. package/dist/table/cell/table-cell.d.ts +2 -0
  46. package/dist/table/column/table-column.d.ts +50 -5
  47. package/dist/table/column/table-column.js +24 -5
  48. package/dist/table/root/table-root.d.ts +76 -11
  49. package/dist/table/root/table-root.js +90 -3
  50. package/dist/table/row/table-row.d.ts +14 -0
  51. package/dist/table/row/table-row.js +16 -0
  52. package/dist/toolbar/root/toolbar-root.js +6 -5
  53. package/dist/tooltip/root/tooltip-root.d.ts +20 -0
  54. package/dist/tooltip/root/tooltip-root.js +21 -3
  55. package/dist/tour/root/tour-root.js +1 -1
  56. package/dist/tour/stop/tour-stop.d.ts +15 -0
  57. package/dist/tour/stop/tour-stop.js +16 -2
  58. package/dist/tree/root/tree-root.js +4 -3
  59. package/dist/video/root/video-root.js +7 -0
  60. package/package.json +5 -5
  61. package/types/jsx.d.ts +1556 -582
  62. package/types/vue.d.ts +65 -22
@@ -46,6 +46,17 @@ declare const HelixHeadlessScatterChart_base: typeof HelixElement & (new (...arg
46
46
  * a `<span>` placed by percentage over it, which is how `LineChart` draws its
47
47
  * termini and for the same reason.
48
48
  *
49
+ * **Left to right on every page, the one chart here that is.** A bar or a line
50
+ * chart's across is the order its readings are named in, which follows the
51
+ * line of text and turns with it. Here it is a measure, and a measure has a
52
+ * direction of its own: more is to the right in every script, as a number line
53
+ * is. So the plot, its axes and the names at their ends sit in a `plane` part
54
+ * that carries `dir="ltr"`, which holds the y axis on the left and the points,
55
+ * rules, ticks, pointer and tooltip mark where a left-to-right page puts them,
56
+ * with no stylesheet asked to know. The names carry `dir="auto"`, so one written
57
+ * in Arabic or Hebrew still reads its own way; the summary is outside the plane
58
+ * and the bubble is told the page's direction, both being words.
59
+ *
49
60
  * @since 0.1.0
50
61
  * @status experimental
51
62
  * @category charts
@@ -54,6 +65,7 @@ declare const HelixHeadlessScatterChart_base: typeof HelixElement & (new (...arg
54
65
  *
55
66
  * @csspart gridline - One rule across the plot, under `gridlines`.
56
67
  * @csspart mark - The invisible point the tooltip is anchored to.
68
+ * @csspart plane - The plot, both axes and their names, left to right on every page. The grid they are laid out in.
57
69
  * @csspart plot - The drawing.
58
70
  * @csspart point - One row, as a point.
59
71
  * @csspart point-active - A point of the row a pointer is asking about, beside `point`.
@@ -62,9 +74,17 @@ declare const HelixHeadlessScatterChart_base: typeof HelixElement & (new (...arg
62
74
  * @csspart tooltip - The bubble, forwarded from `Tooltip`.
63
75
  * @csspart tooltip-anchor - The `Tooltip` element itself.
64
76
  * @csspart tooltip-arrow - The bubble's point, forwarded from `Tooltip`.
77
+ * @csspart tooltip-arrow-bottom - The point while the bubble is below the reading, so on its top edge, beside `tooltip-arrow`.
78
+ * @csspart tooltip-arrow-left - The point while the bubble is left of the reading, so on its right edge, beside `tooltip-arrow`.
79
+ * @csspart tooltip-arrow-right - The point while the bubble is right of the reading, so on its left edge, beside `tooltip-arrow`.
80
+ * @csspart tooltip-arrow-top - The point while the bubble is above the reading, so on its bottom edge, beside `tooltip-arrow`.
81
+ * @csspart tooltip-bottom - The bubble while it is below the reading, beside `tooltip`.
65
82
  * @csspart tooltip-cell - One column's name and value inside the tooltip.
66
83
  * @csspart tooltip-label - The row's own name inside the tooltip.
84
+ * @csspart tooltip-left - The bubble while it is left of the reading, beside `tooltip`.
67
85
  * @csspart tooltip-name - A column's name inside the tooltip.
86
+ * @csspart tooltip-right - The bubble while it is right of the reading, beside `tooltip`.
87
+ * @csspart tooltip-top - The bubble while it is above the reading, beside `tooltip`.
68
88
  * @csspart tooltip-value - A column's value inside the tooltip.
69
89
  * @csspart x-axis - The levels measured across, under the plot.
70
90
  * @csspart x-name - What is measured across.
@@ -124,20 +124,24 @@ let HelixHeadlessScatterChart = class HelixHeadlessScatterChart extends MixinCha
124
124
  const field = this.field;
125
125
  this.drawn = field.points;
126
126
  if (field.points.length === 0) {
127
- return html `<div id="helix-chart-plot" part="plot"></div>`;
127
+ return html `<div dir="ltr" part="plane">
128
+ <div id="helix-chart-plot" part="plot"></div>
129
+ </div>`;
128
130
  }
129
131
  const format = formatNumbers('');
130
132
  const along = linear(field.x, [0, WIDTH]);
131
133
  const up = linear(field.y, [HEIGHT, 0]);
132
- return html `${this.renderPlot(field, along, up)}
133
- <div aria-hidden="true" part="x-axis">
134
- ${ticks(field.x).map((level) => html `<span part="x-tick" style="--chart-tick: ${along(level)}%">${format(level)}</span>`)}
134
+ return html `<div dir="ltr" part="plane">
135
+ ${this.renderPlot(field, along, up)}
136
+ <div aria-hidden="true" part="x-axis">
137
+ ${ticks(field.x).map((level) => html `<span part="x-tick" style="--chart-tick: ${along(level)}%">${format(level)}</span>`)}
138
+ </div>
139
+ <div aria-hidden="true" part="y-axis">
140
+ ${ticks(field.y).map((level) => html `<span part="y-tick" style="--chart-tick: ${up(level)}%">${format(level)}</span>`)}
141
+ </div>
142
+ <span aria-hidden="true" dir="auto" part="x-name">${field.xName}</span>
143
+ <span aria-hidden="true" dir="auto" part="y-name">${field.yNames.join(', ')}</span>
135
144
  </div>
136
- <div aria-hidden="true" part="y-axis">
137
- ${ticks(field.y).map((level) => html `<span part="y-tick" style="--chart-tick: ${up(level)}%">${format(level)}</span>`)}
138
- </div>
139
- <span aria-hidden="true" part="x-name">${field.xName}</span>
140
- <span aria-hidden="true" part="y-name">${field.yNames.join(', ')}</span>
141
145
  ${this.renderSummary(field)}`;
142
146
  }
143
147
  renderPlot(field, along, up) {
@@ -184,6 +188,8 @@ let HelixHeadlessScatterChart = class HelixHeadlessScatterChart extends MixinCha
184
188
  return drawTooltip(this.sourceRows, -1, this.tag('tooltip'));
185
189
  }
186
190
  return drawTooltip(this.sourceRows, at, this.tag('tooltip'), {
191
+ dir: getComputedStyle(this).direction === 'rtl' ? 'rtl' : 'ltr',
192
+ fixed: true,
187
193
  placement: 'top',
188
194
  x: `${point.x}%`,
189
195
  y: `${point.y}%`,
@@ -27,10 +27,11 @@ let HelixHeadlessScrollArea = class HelixHeadlessScrollArea extends HelixElement
27
27
  if (box.scrollTop + box.clientHeight < box.scrollHeight - slack) {
28
28
  found.push('block-end');
29
29
  }
30
- if (box.scrollLeft > slack) {
30
+ const along = Math.abs(box.scrollLeft);
31
+ if (along > slack) {
31
32
  found.push('inline-start');
32
33
  }
33
- if (box.scrollLeft + box.clientWidth < box.scrollWidth - slack) {
34
+ if (along + box.clientWidth < box.scrollWidth - slack) {
34
35
  found.push('inline-end');
35
36
  }
36
37
  this.more = found;
@@ -60,6 +60,7 @@ let HelixHeadlessSparkline = class HelixHeadlessSparkline extends HelixElement {
60
60
  return html `<svg
61
61
  aria-hidden=${this.label === '' ? 'true' : nothing}
62
62
  aria-label=${this.label === '' ? nothing : this.label}
63
+ data-mirror
63
64
  part="svg"
64
65
  preserveAspectRatio="none"
65
66
  role=${this.label === '' ? nothing : 'img'}
@@ -332,6 +332,17 @@ export declare class HelixHeadlessSplitter extends HelixElement {
332
332
  * only place in a splitter where a second axis means anything.
333
333
  */
334
334
  private readonly handleKeydown;
335
+ /**
336
+ * Which way a key moves the divider, as a sign on `position`, or `0` for a key
337
+ * that is not along its axis.
338
+ *
339
+ * **The arrow says where the divider goes on screen**, the way the window
340
+ * splitter pattern words it: left moves a side-by-side divider to the left.
341
+ * `position` is the start pane's share, and the start pane is on the right of
342
+ * a right-to-left page, so there the left arrow grows it. It used to shrink
343
+ * it on every page, which on a right-to-left one sent the divider right.
344
+ */
345
+ private travel;
335
346
  /** Puts the divider back where the markup put it. */
336
347
  private reset;
337
348
  private readonly handleDoubleClick;
@@ -5,7 +5,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
7
  var HelixHeadlessSplitter_1;
8
- import { capturePointer, closestInstance, customElement, HelixBeforeCollapseEvent, HelixBeforeResizeEvent, HelixElement, requestChange, } from '@fusion.dev/helix-core';
8
+ import { capturePointer, closestInstance, customElement, HelixBeforeCollapseEvent, HelixBeforeResizeEvent, HelixElement, inlineStep, requestChange, } from '@fusion.dev/helix-core';
9
9
  import { html } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
11
11
  const STACK_BELOW = 512;
@@ -68,14 +68,10 @@ let HelixHeadlessSplitter = HelixHeadlessSplitter_1 = class HelixHeadlessSplitte
68
68
  this.settle(this.reached);
69
69
  };
70
70
  this.handleKeydown = (event) => {
71
- const back = this.vertical ? 'ArrowUp' : 'ArrowLeft';
72
- const forward = this.vertical ? 'ArrowDown' : 'ArrowRight';
71
+ const toward = this.travel(event.key);
73
72
  let next;
74
- if (event.key === back) {
75
- next = this.position - this.step;
76
- }
77
- else if (event.key === forward) {
78
- next = this.position + this.step;
73
+ if (toward !== 0) {
74
+ next = this.position + toward * this.step;
79
75
  }
80
76
  else if (event.key === 'Home') {
81
77
  next = this.minPercent;
@@ -171,15 +167,13 @@ let HelixHeadlessSplitter = HelixHeadlessSplitter_1 = class HelixHeadlessSplitte
171
167
  return;
172
168
  }
173
169
  const inner = crossing.by;
174
- const mineBack = this.vertical ? 'ArrowUp' : 'ArrowLeft';
175
- const mineForward = this.vertical ? 'ArrowDown' : 'ArrowRight';
176
- const theirsBack = inner.vertical ? 'ArrowUp' : 'ArrowLeft';
177
- const theirsForward = inner.vertical ? 'ArrowDown' : 'ArrowRight';
178
- if (event.key === mineBack || event.key === mineForward) {
179
- this.moveTo(this.position + (event.key === mineBack ? -this.step : this.step), true);
170
+ const mine = this.travel(event.key);
171
+ const theirs = inner.travel(event.key);
172
+ if (mine !== 0) {
173
+ this.moveTo(this.position + mine * this.step, true);
180
174
  }
181
- else if (event.key === theirsBack || event.key === theirsForward) {
182
- inner.moveTo(inner.position + (event.key === theirsBack ? -inner.step : inner.step), true);
175
+ else if (theirs !== 0) {
176
+ inner.moveTo(inner.position + theirs * inner.step, true);
183
177
  }
184
178
  else {
185
179
  return;
@@ -332,6 +326,12 @@ let HelixHeadlessSplitter = HelixHeadlessSplitter_1 = class HelixHeadlessSplitte
332
326
  });
333
327
  }
334
328
  }
329
+ travel(key) {
330
+ if (!this.vertical) {
331
+ return inlineStep(this, key);
332
+ }
333
+ return key === 'ArrowDown' ? 1 : key === 'ArrowUp' ? -1 : 0;
334
+ }
335
335
  reset() {
336
336
  const home = this.clamp(this.defaultPosition);
337
337
  this.move(home);
@@ -18,6 +18,8 @@ import { type PropertyValues, type TemplateResult } from 'lit';
18
18
  * @documentation https://helix.fusion.dev/components/table
19
19
  *
20
20
  * @slot - The cell's content.
21
+ *
22
+ * @cssproperty --table-cell-align - The edge the column this cell sits under is set to, `start`, `center` or `end`, and unset under a column that sets none. Written by the table around it, since the cell is the consumer's markup and nothing is written onto it; read by the sheet that draws the cell, where `justify-content` and `text-align` both take it as it comes and both follow the writing direction.
21
23
  */
22
24
  export declare class HelixHeadlessTableCell extends HelixElement {
23
25
  /**
@@ -47,6 +47,15 @@ export declare const at: (row: Readonly<Record<string, unknown>>, path: string)
47
47
  * item of the table, so it lines up with the cells under it rather than sitting
48
48
  * in a wrapper that has to be kept the same width.
49
49
  *
50
+ * **`hidden` takes the column out of the grid**, its heading and its track
51
+ * both, so the columns after it keep their own widths and every row keeps its
52
+ * cells under their headings. `hidden="until-found"` keeps its track, the
53
+ * heading being left drawn, as every Helix element leaves one with that value.
54
+ * `helix-data-table` draws the cells, so it leaves out the ones under a hidden
55
+ * column. A `helix-table`'s cells are markup the consumer wrote, so hide them
56
+ * with the column, as removing a column means removing its cells: a row with a
57
+ * cell more than the table has tracks wraps into the next.
58
+ *
50
59
  * **A heading that does something holds a control that does it.** The host is
51
60
  * the column header, and a header is not focusable, so a sortable heading is a
52
61
  * `<button>` inside it: the pattern for a sortable table, and the only way a
@@ -79,9 +88,23 @@ export declare class HelixHeadlessTableColumn extends HelixElement {
79
88
  *
80
89
  * `end` is what a column of numbers wants, so digits of the same magnitude sit
81
90
  * above each other. Left unset the cell inherits the table's own alignment,
82
- * which is `start` for text in either writing direction.
91
+ * which is `start` for text in either writing direction. `helix-data-table`
92
+ * aligns the cells it draws. A `helix-table`'s cells are markup the consumer
93
+ * wrote, so the table writes nothing onto them: it publishes the edge to each
94
+ * cell under this column as `--table-cell-align`, which `@fusion.dev/helix`
95
+ * reads. The heading is this element's own, and `@fusion.dev/helix` sets it to
96
+ * the same edge in either table. `end` is the inline end, so in a
97
+ * right-to-left table it is the left.
98
+ *
99
+ * **Reflected, because the heading is drawn by a stylesheet and has no other
100
+ * way to learn it.** Set as a property, which is how React sets it, an
101
+ * unreflected `align` left no attribute to select on, so the heading stayed at
102
+ * the start over a column of digits set to the end. It is the author's
103
+ * declaration and nothing moves it afterwards, so an attribute cannot come to
104
+ * disagree with the element, which is the reason `open` records a custom
105
+ * state instead. No default, so an unset column writes nothing onto its host.
83
106
  */
84
- align: 'center' | 'end' | 'start' | '';
107
+ align?: 'center' | 'end' | 'start';
85
108
  /**
86
109
  * How to read the cell, where the field alone will not do.
87
110
  *
@@ -132,14 +155,24 @@ export declare class HelixHeadlessTableColumn extends HelixElement {
132
155
  * Ignored without a `field`, and silently: a column with nothing to sort by
133
156
  * has no order to offer, and refusing loudly would mean an actions column
134
157
  * carrying `sortable` breaks a table rather than doing nothing.
158
+ *
159
+ * **Reflected, because a stylesheet selects on it.** `@fusion.dev/helix`
160
+ * gives a data table's sortable headings a pointer through
161
+ * `::slotted(helix-table-column[sortable])`, and set as a property, which is
162
+ * how React sets it, an unreflected `sortable` left no attribute to match.
163
+ * `false` reflects as no attribute at all, so an unset column writes nothing
164
+ * onto its host.
135
165
  */
136
166
  sortable: boolean;
137
167
  /**
138
168
  * The track this column takes, as CSS.
139
169
  *
140
170
  * A single track value, so anything valid in `grid-template-columns` works:
141
- * `16rem`, `auto`, `minmax(0, 2fr)`. Left unset the column shares what is
142
- * left over equally, as `minmax(max-content, 1fr)`.
171
+ * `16rem`, `auto`, `minmax(0, 2fr)`. Left unset the column takes the track
172
+ * in its place in a `helix-table`'s `columns`, where that table sets one, and
173
+ * otherwise shares what is left over equally, as `minmax(max-content, 1fr)`.
174
+ * Set here, it wins over that list, which is what lets a reader resize a
175
+ * column in a table that set one.
143
176
  *
144
177
  * **The floor is `max-content`, never `0` and never `min-content`.** A zero
145
178
  * floor lets a track shrink under its own content: at 375 the columns squeeze
@@ -183,6 +216,8 @@ export declare class HelixHeadlessTableColumn extends HelixElement {
183
216
  private resizing;
184
217
  /** Watches the heading's width, which changes with the table as well as with a drag. */
185
218
  private watcher;
219
+ /** Watches `hidden`, which is the platform's attribute and no property of this element's. */
220
+ private hiding;
186
221
  /**
187
222
  * The narrowest a reader can drag a column, in pixels.
188
223
  *
@@ -195,6 +230,13 @@ export declare class HelixHeadlessTableColumn extends HelixElement {
195
230
  static readonly STEP = 16;
196
231
  /** The track this column contributes, resolved. */
197
232
  get track(): string;
233
+ /**
234
+ * @internal Whether this column takes a track, read by the table that holds
235
+ * it. Not a consumer's to read: `hidden` is the setting. Read off the
236
+ * attribute the way `HelixElement` hides a host, so the table drops a track
237
+ * exactly when the heading stops being drawn.
238
+ */
239
+ get shown(): boolean;
198
240
  /** Whether a reader can actually reorder by this column. */
199
241
  get canSort(): boolean;
200
242
  /**
@@ -214,6 +256,9 @@ export declare class HelixHeadlessTableColumn extends HelixElement {
214
256
  private observeHeading;
215
257
  disconnectedCallback(): void;
216
258
  protected firstUpdated(changed: PropertyValues): void;
259
+ protected updated(changed: PropertyValues): void;
260
+ /** Asks whatever holds this column to render, since it reads the column when it does. */
261
+ private tellHolder;
217
262
  /**
218
263
  * Asks for a width, in pixels, no narrower than a column can be read at.
219
264
  *
@@ -221,7 +266,7 @@ export declare class HelixHeadlessTableColumn extends HelixElement {
221
266
  * applied only where nobody refused it.
222
267
  */
223
268
  private resizeTo;
224
- /** Which way a drag or an arrow key grows the column, in this writing direction. */
269
+ /** Which way a drag grows the column, in this writing direction. */
225
270
  private get forward();
226
271
  private readonly handleResizeStart;
227
272
  private readonly handleResizeMove;
@@ -5,7 +5,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
7
  var HelixHeadlessTableColumn_1;
8
- import { customElement, HelixBeforeResizeEvent, HelixElement, hostAria, requestChange, } from '@fusion.dev/helix-core';
8
+ import { customElement, HelixBeforeResizeEvent, HelixElement, hostAria, inlineStep, requestChange, } from '@fusion.dev/helix-core';
9
9
  import { dotsThreeVertical } from '@fusion.dev/helix-icons';
10
10
  import { html, nothing } from 'lit';
11
11
  import { property, state } from 'lit/decorators.js';
@@ -29,7 +29,6 @@ export const at = (row, path) => {
29
29
  let HelixHeadlessTableColumn = class HelixHeadlessTableColumn extends HelixElement {
30
30
  constructor() {
31
31
  super(...arguments);
32
- this.align = '';
33
32
  this.cell = undefined;
34
33
  this.compare = undefined;
35
34
  this.field = '';
@@ -63,7 +62,7 @@ let HelixHeadlessTableColumn = class HelixHeadlessTableColumn extends HelixEleme
63
62
  };
64
63
  this.handleResizeKey = (event) => {
65
64
  const step = HelixHeadlessTableColumn_1.STEP * (event.shiftKey ? 4 : 1);
66
- const grow = event.key === 'ArrowRight' ? this.forward : event.key === 'ArrowLeft' ? -this.forward : 0;
65
+ const grow = inlineStep(this, event.key);
67
66
  if (grow !== 0) {
68
67
  event.preventDefault();
69
68
  this.resizeTo(this.drawn + grow * step);
@@ -80,6 +79,10 @@ let HelixHeadlessTableColumn = class HelixHeadlessTableColumn extends HelixEleme
80
79
  get track() {
81
80
  return this.width || 'minmax(max-content, 1fr)';
82
81
  }
82
+ get shown() {
83
+ const hidden = this.getAttribute('hidden');
84
+ return hidden === null || hidden === 'until-found';
85
+ }
83
86
  get canSort() {
84
87
  return this.sortable && this.field !== '';
85
88
  }
@@ -109,6 +112,10 @@ let HelixHeadlessTableColumn = class HelixHeadlessTableColumn extends HelixEleme
109
112
  });
110
113
  this.observeHeading();
111
114
  }
115
+ if (typeof MutationObserver !== 'undefined') {
116
+ this.hiding = new MutationObserver(() => this.tellHolder());
117
+ this.hiding.observe(this, { attributeFilter: ['hidden'] });
118
+ }
112
119
  }
113
120
  observeHeading() {
114
121
  const heading = this.partNamed('column');
@@ -119,11 +126,23 @@ let HelixHeadlessTableColumn = class HelixHeadlessTableColumn extends HelixEleme
119
126
  disconnectedCallback() {
120
127
  super.disconnectedCallback();
121
128
  this.watcher?.disconnect();
129
+ this.hiding?.disconnect();
130
+ this.hiding = undefined;
122
131
  }
123
132
  firstUpdated(changed) {
124
133
  super.firstUpdated?.(changed);
125
134
  this.observeHeading();
126
135
  }
136
+ updated(changed) {
137
+ super.updated(changed);
138
+ if (changed.has('align') || changed.has('width')) {
139
+ this.tellHolder();
140
+ }
141
+ }
142
+ tellHolder() {
143
+ ;
144
+ this.parentElement?.requestUpdate?.();
145
+ }
127
146
  resizeTo(px) {
128
147
  const next = `${Math.max(HelixHeadlessTableColumn_1.NARROWEST, Math.round(px))}px`;
129
148
  if (next === this.width) {
@@ -172,7 +191,7 @@ let HelixHeadlessTableColumn = class HelixHeadlessTableColumn extends HelixEleme
172
191
  }
173
192
  };
174
193
  __decorate([
175
- property()
194
+ property({ reflect: true })
176
195
  ], HelixHeadlessTableColumn.prototype, "align", void 0);
177
196
  __decorate([
178
197
  property({ attribute: false })
@@ -187,7 +206,7 @@ __decorate([
187
206
  property({ type: Boolean })
188
207
  ], HelixHeadlessTableColumn.prototype, "resizable", void 0);
189
208
  __decorate([
190
- property({ type: Boolean })
209
+ property({ reflect: true, type: Boolean })
191
210
  ], HelixHeadlessTableColumn.prototype, "sortable", void 0);
192
211
  __decorate([
193
212
  property()
@@ -39,15 +39,24 @@ declare const HelixHeadlessTable_base: (new (...args: any[]) => import("@fusion.
39
39
  */
40
40
  export declare class HelixHeadlessTable extends HelixHeadlessTable_base {
41
41
  /**
42
- * Column track list, as CSS. Deprecated: declare columns instead.
42
+ * The column widths as one track list, written once on the table.
43
43
  *
44
- * Kept for a table that has no `helix-table-column` children, and ignored the
45
- * moment it has one. A raw track list is the right shape for the value and
46
- * the wrong place for it: the headings and the widths were two halves of one
47
- * fact held in two places, and nothing checked that they agreed. Worse, the
48
- * value is CSS, so handing it headings by mistake produced an invalid track
49
- * list, which a grid ignores in silence: the table collapsed to one column
50
- * and said nothing. Columns carry both halves now.
44
+ * With `helix-table-column` children, each track sizes the column in its
45
+ * place, a hidden column counted, so hiding one takes its track with it and
46
+ * the rest keep theirs. The columns decide how many tracks there are and this
47
+ * list only how wide: a track past the last column is dropped, and a column
48
+ * past the last track shares what is left. With no columns declared it is
49
+ * the whole track list. Tracks count as the grid counts them, so
50
+ * `minmax(0, 1fr)` is one and `repeat(2, 8rem)` is two, and one the grid
51
+ * would refuse leaves its column at the default rather than taking the table
52
+ * down to one column.
53
+ *
54
+ * **A column's own `width` wins over its track here.** The width is written
55
+ * on the column it sizes, which is the more particular statement, and it is
56
+ * where a resize lands: a reader dragging a column's edge writes `width`, so
57
+ * were this list to win, the drag would move the separator and leave the
58
+ * track where the list put it. This is the table's default for its columns,
59
+ * and each column may say otherwise.
51
60
  */
52
61
  columns: string;
53
62
  /**
@@ -60,12 +69,29 @@ export declare class HelixHeadlessTable extends HelixHeadlessTable_base {
60
69
  * The track list, from the columns if there are any.
61
70
  *
62
71
  * Read from the DOM rather than kept, so adding or removing a column needs
63
- * nothing else to be told. Falls back to the `columns` string for a table
64
- * written before columns existed, and to a single track for one that declares
65
- * neither.
72
+ * nothing else to be told. A hidden column takes no track: left in, the
73
+ * header row had a heading fewer than the grid had tracks, the first cell
74
+ * filled the gap, and every row after it began a column late. Falls back to
75
+ * the `columns` string for a table that declares no columns, and to a single
76
+ * track for one that declares neither.
66
77
  */
67
78
  private get template();
68
79
  private readonly handleSlotChange;
80
+ /**
81
+ * Watches the light DOM for rows and columns arriving, leaving and moving.
82
+ *
83
+ * **A manually assigned slot does not announce them**, the same gap `Chat`
84
+ * and `Carousel` watch for. Under `slotAssignment: 'manual'` only `assign`
85
+ * changes an assignment, so a row appended after the first render reached no
86
+ * slot and drew nowhere, and a column moved to another place lost its
87
+ * assignment on the way out and never got it back: measured in Chromium, both
88
+ * `assignedSlot` null a render later. A table whose definition loaded before
89
+ * its markup was parsed drew no columns at all, having rendered before any
90
+ * arrived.
91
+ */
92
+ private watcher;
93
+ connectedCallback(): void;
94
+ disconnectedCallback(): void;
69
95
  /**
70
96
  * Nothing is assigned by name, so the columns can be put in a row.
71
97
  *
@@ -88,6 +114,45 @@ export declare class HelixHeadlessTable extends HelixHeadlessTable_base {
88
114
  /** The rows: every child that is not a column, which is what the body slot is assigned. */
89
115
  protected get slotted(): Element[];
90
116
  private assign;
117
+ /**
118
+ * The rules handing each column's `align` to the cells under it. One sheet for
119
+ * every row this table holds, rewritten in place, so a column changing its
120
+ * edge reaches every row in one write.
121
+ */
122
+ private alignment;
123
+ /** What `alignment` was last written with, so a render that changed no edge writes nothing. */
124
+ private aligned;
125
+ /**
126
+ * Tells every row which edge each of its cells lines up against.
127
+ *
128
+ * **A rule in each row's shadow root, keyed on position and written by this
129
+ * table.** The cells are the consumer's markup, so nothing may be written onto
130
+ * them, and each of the other ways fails somewhere a table goes:
131
+ *
132
+ * - This element's own sheet cannot reach them. `::slotted()` matches what is
133
+ * assigned here, which is the rows, and never a row's children.
134
+ * - A property per column published here, `--table-align-2: end`, still needs
135
+ * a rule that knows which column a cell is in, and a fixed ladder of
136
+ * `:nth-child` rules in a static sheet is a ceiling on the column count.
137
+ * - A custom state set on each cell by script is right only until a cell
138
+ * moves, and a cell moving inside a row changes no child list this element
139
+ * watches.
140
+ *
141
+ * `:nth-child` is matched by the browser against wherever a cell is now, so a
142
+ * cell that moves takes its new column's edge with no script at all; a column
143
+ * that moves rewrites the rules on the render the watcher asks for; and a
144
+ * column hidden along with its cells keeps its place in both counts, since
145
+ * `:nth-child` counts a hidden sibling the way `tableColumns` counts a hidden
146
+ * column. The row is where they go because only the shadow root holding a
147
+ * slot can style what is assigned to it.
148
+ *
149
+ * **A custom property and nothing else**, which is how this tier publishes a
150
+ * fact: the edge is the author's declaration, and what lining up means, a
151
+ * `justify-content` or a `text-align`, belongs to whoever draws the cell.
152
+ * `initial` on every cell first, so a table nested in an aligned cell does not
153
+ * take the outer column's edge for columns of its own that set none.
154
+ */
155
+ private alignCells;
91
156
  protected updated(changed: PropertyValues): void;
92
157
  protected render(): TemplateResult;
93
158
  }
@@ -7,6 +7,56 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  import { customElement, HelixElement, MixinCollection } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
+ const EDGES = new Set(['center', 'end', 'start']);
11
+ const tracksOf = (list, wanted) => {
12
+ const words = [];
13
+ let depth = 0;
14
+ let word = '';
15
+ for (const char of list.trim()) {
16
+ if (char === '(' || char === '[') {
17
+ depth += 1;
18
+ }
19
+ else if (char === ')' || char === ']') {
20
+ depth -= 1;
21
+ }
22
+ if (depth === 0 && /\s/.test(char)) {
23
+ if (word) {
24
+ words.push(word);
25
+ }
26
+ word = '';
27
+ }
28
+ else {
29
+ word += char;
30
+ }
31
+ }
32
+ if (word) {
33
+ words.push(word);
34
+ }
35
+ const valid = (track) => typeof CSS === 'undefined' || CSS.supports('grid-template-columns', track);
36
+ const tracks = [];
37
+ let names = '';
38
+ for (const one of words) {
39
+ if (tracks.length >= wanted) {
40
+ break;
41
+ }
42
+ if (one.startsWith('[')) {
43
+ names += `${one} `;
44
+ continue;
45
+ }
46
+ const repeat = /^repeat\(\s*(\d+)\s*,(.*)\)$/is.exec(one);
47
+ if (repeat) {
48
+ const inner = tracksOf(repeat[2] ?? '', wanted);
49
+ for (let time = 0; time < Number(repeat[1]) && tracks.length < wanted; time += 1) {
50
+ tracks.push(...inner.map((track, index) => (time === 0 && index === 0 ? names + track : track)));
51
+ }
52
+ }
53
+ else if (!/^repeat\(/i.test(one)) {
54
+ tracks.push(valid(names + one) ? names + one : '');
55
+ }
56
+ names = '';
57
+ }
58
+ return tracks.slice(0, wanted);
59
+ };
10
60
  let HelixHeadlessTable = class HelixHeadlessTable extends MixinCollection(HelixElement) {
11
61
  constructor() {
12
62
  super(...arguments);
@@ -15,16 +65,32 @@ let HelixHeadlessTable = class HelixHeadlessTable extends MixinCollection(HelixE
15
65
  this.handleSlotChange = () => {
16
66
  this.requestUpdate();
17
67
  };
68
+ this.watcher = undefined;
69
+ this.alignment = undefined;
70
+ this.aligned = '';
18
71
  }
19
72
  get tableColumns() {
20
73
  return this.knownChildren.filter((child) => 'isTableColumn' in child && child.isTableColumn === true);
21
74
  }
22
75
  get template() {
23
76
  const declared = this.tableColumns;
24
- if (declared.length > 0) {
25
- return declared.map((column) => column.track).join(' ');
77
+ if (declared.length === 0) {
78
+ return this.columns;
26
79
  }
27
- return this.columns;
80
+ const listed = tracksOf(this.columns, declared.length);
81
+ return declared
82
+ .flatMap((column, index) => column.shown ? [column.width ? column.track : listed[index] || column.track] : [])
83
+ .join(' ');
84
+ }
85
+ connectedCallback() {
86
+ super.connectedCallback();
87
+ this.watcher = new MutationObserver(() => this.requestUpdate());
88
+ this.watcher.observe(this, { childList: true });
89
+ }
90
+ disconnectedCallback() {
91
+ super.disconnectedCallback();
92
+ this.watcher?.disconnect();
93
+ this.watcher = undefined;
28
94
  }
29
95
  static { this.shadowRootOptions = {
30
96
  ...HelixElement.shadowRootOptions,
@@ -49,9 +115,30 @@ let HelixHeadlessTable = class HelixHeadlessTable extends MixinCollection(HelixE
49
115
  head.assign(...this.tableColumns);
50
116
  body.assign(...this.slotted);
51
117
  }
118
+ alignCells() {
119
+ if (typeof CSSStyleSheet === 'undefined' || !('replaceSync' in CSSStyleSheet.prototype)) {
120
+ return;
121
+ }
122
+ const rules = this.tableColumns.flatMap((column, index) => column.align && EDGES.has(column.align)
123
+ ? [`::slotted(:nth-child(${index + 1})) { --table-cell-align: ${column.align}; }`]
124
+ : []);
125
+ const text = ['::slotted(*) { --table-cell-align: initial; }', ...rules].join('\n');
126
+ this.alignment ??= new CSSStyleSheet();
127
+ if (text !== this.aligned) {
128
+ this.alignment.replaceSync(text);
129
+ this.aligned = text;
130
+ }
131
+ for (const row of this.slotted) {
132
+ if ('isTableRow' in row) {
133
+ ;
134
+ row.alignCells(this.alignment);
135
+ }
136
+ }
137
+ }
52
138
  updated(changed) {
53
139
  super.updated(changed);
54
140
  this.assign();
141
+ this.alignCells();
55
142
  }
56
143
  render() {
57
144
  const template = this.template;