@fusion.dev/helix-headless 0.22.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/custom-elements.json +48397 -26631
  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 +54 -6
  54. package/dist/menu/item/menu-item.js +31 -6
  55. package/dist/menu/root/menu-root.d.ts +23 -0
  56. package/dist/menu/root/menu-root.js +13 -2
  57. package/dist/notifications/group/notification-group.d.ts +129 -0
  58. package/dist/notifications/group/notification-group.js +106 -0
  59. package/dist/notifications/index.d.ts +1 -0
  60. package/dist/notifications/index.js +1 -0
  61. package/dist/notifications/notification/notification.d.ts +39 -13
  62. package/dist/notifications/notification/notification.js +11 -8
  63. package/dist/notifications/root/notifications-root.d.ts +57 -1
  64. package/dist/notifications/root/notifications-root.js +35 -4
  65. package/dist/number-input/root/number-input-root.d.ts +21 -1
  66. package/dist/number-input/root/number-input-root.js +18 -5
  67. package/dist/object-inspector/index.d.ts +1 -0
  68. package/dist/object-inspector/index.js +1 -0
  69. package/dist/object-inspector/root/object-inspector-root.d.ts +183 -0
  70. package/dist/object-inspector/root/object-inspector-root.js +348 -0
  71. package/dist/outline/root/outline-root.d.ts +16 -1
  72. package/dist/outline/root/outline-root.js +27 -9
  73. package/dist/radio/group/radio-group.d.ts +1 -0
  74. package/dist/scatter-chart/index.d.ts +1 -0
  75. package/dist/scatter-chart/index.js +1 -0
  76. package/dist/scatter-chart/root/scatter-chart-root.d.ts +144 -0
  77. package/dist/scatter-chart/root/scatter-chart-root.js +205 -0
  78. package/dist/skip-link/index.d.ts +1 -0
  79. package/dist/skip-link/index.js +1 -0
  80. package/dist/skip-link/root/skip-link-root.d.ts +81 -0
  81. package/dist/skip-link/root/skip-link-root.js +58 -0
  82. package/dist/speed-dial/root/speed-dial-root.d.ts +1 -0
  83. package/dist/speed-dial/root/speed-dial-root.js +6 -2
  84. package/dist/steps/root/steps-root.d.ts +1 -0
  85. package/dist/table/column/table-column.d.ts +79 -0
  86. package/dist/table/column/table-column.js +125 -5
  87. package/dist/table/root/table-root.d.ts +2 -0
  88. package/dist/table/root/table-root.js +12 -3
  89. package/dist/time-picker/root/time-picker-root.d.ts +9 -0
  90. package/dist/time-picker/root/time-picker-root.js +5 -0
  91. package/dist/toast/root/toast-root.d.ts +2 -0
  92. package/dist/tooltip/root/tooltip-root.js +1 -1
  93. package/dist/tour/root/tour-root.d.ts +1 -0
  94. package/dist/tree/item/tree-item.d.ts +7 -4
  95. package/dist/tree/item/tree-item.js +4 -1
  96. package/dist/video/root/video-root.d.ts +23 -1
  97. package/dist/video/root/video-root.js +42 -22
  98. package/dist/virtual-list/root/virtual-list-root.d.ts +10 -6
  99. package/dist/virtual-list/root/virtual-list-root.js +10 -4
  100. package/package.json +5 -5
  101. package/types/jsx.d.ts +4535 -346
  102. package/types/svelte.d.ts +11 -0
  103. package/types/vue.d.ts +737 -39
@@ -0,0 +1,205 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
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
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { customElement, drawTooltip, extent, formatNumbers, HelixElement, linear, MixinChartSource, seriesIndex, ticks, } from '@fusion.dev/helix-core';
8
+ import { html, nothing, svg } from 'lit';
9
+ import { property } from 'lit/decorators.js';
10
+ import '../../tooltip/index.js';
11
+ const WIDTH = 100;
12
+ const HEIGHT = 100;
13
+ const REACH = 24;
14
+ const coefficient = new Intl.NumberFormat(undefined, {
15
+ maximumFractionDigits: 2,
16
+ minimumFractionDigits: 2,
17
+ signDisplay: 'exceptZero',
18
+ });
19
+ const nice = ([low, high]) => {
20
+ if (low === high) {
21
+ return [low - 1, high + 1];
22
+ }
23
+ const levels = ticks([low, high]);
24
+ const step = levels.length > 1 ? levels[1] - levels[0] : high - low;
25
+ const round = (value) => Number(value.toPrecision(12));
26
+ return [round(Math.floor(low / step) * step), round(Math.ceil(high / step) * step)];
27
+ };
28
+ const correlation = (pairs) => {
29
+ if (pairs.length < 3) {
30
+ return undefined;
31
+ }
32
+ const mean = (values) => values.reduce((sum, one) => sum + one, 0) / values.length;
33
+ const across = mean(pairs.map(([x]) => x));
34
+ const up = mean(pairs.map(([, y]) => y));
35
+ let both = 0;
36
+ let xs = 0;
37
+ let ys = 0;
38
+ for (const [x, y] of pairs) {
39
+ both += (x - across) * (y - up);
40
+ xs += (x - across) ** 2;
41
+ ys += (y - up) ** 2;
42
+ }
43
+ return xs === 0 || ys === 0 ? undefined : both / Math.sqrt(xs * ys);
44
+ };
45
+ let HelixHeadlessScatterChart = class HelixHeadlessScatterChart extends MixinChartSource(HelixElement) {
46
+ constructor() {
47
+ super(...arguments);
48
+ this.correlationLabel = 'correlation';
49
+ this.pointsLabel = 'points';
50
+ this.drawn = [];
51
+ this.nearest = undefined;
52
+ this.handleMove = (event) => {
53
+ if (event.pointerType !== 'mouse' && event.pointerType !== 'pen') {
54
+ return;
55
+ }
56
+ const box = this.renderRoot.querySelector('[part="plot"]')?.getBoundingClientRect();
57
+ if (!box || box.width === 0 || box.height === 0) {
58
+ return;
59
+ }
60
+ const across = event.clientX - box.left;
61
+ const up = event.clientY - box.top;
62
+ let found;
63
+ let best = REACH ** 2;
64
+ for (const point of this.drawn) {
65
+ const distance = ((point.x / WIDTH) * box.width - across) ** 2 + ((point.y / HEIGHT) * box.height - up) ** 2;
66
+ if (distance <= best) {
67
+ best = distance;
68
+ found = point;
69
+ }
70
+ }
71
+ if (found !== this.nearest) {
72
+ this.nearest = found;
73
+ this.pointer.set(found?.row ?? -1);
74
+ this.requestUpdate();
75
+ }
76
+ };
77
+ this.handleLeave = () => {
78
+ this.nearest = undefined;
79
+ };
80
+ }
81
+ connectedCallback() {
82
+ super.connectedCallback();
83
+ this.pointer.axis = 'none';
84
+ this.addEventListener('pointermove', this.handleMove);
85
+ this.addEventListener('pointerleave', this.handleLeave);
86
+ }
87
+ disconnectedCallback() {
88
+ super.disconnectedCallback();
89
+ this.removeEventListener('pointermove', this.handleMove);
90
+ this.removeEventListener('pointerleave', this.handleLeave);
91
+ }
92
+ get rulesX() {
93
+ return this.gridlines !== undefined && this.gridlines !== 'y';
94
+ }
95
+ get rulesY() {
96
+ return this.gridlines !== undefined && this.gridlines !== 'x';
97
+ }
98
+ get field() {
99
+ const rows = this.sourceRows;
100
+ const width = Math.max(0, ...rows.map((row) => row.cells.length));
101
+ const columns = Array.from({ length: width }, (_, column) => column).filter((column) => rows.some((row) => row.cells[column]?.value !== undefined));
102
+ const [across, ...ups] = columns;
103
+ const name = (column) => column === undefined ? '' : (rows.find((row) => row.cells[column])?.cells[column]?.name ?? '');
104
+ const pairs = ups.map((column) => rows.flatMap((row, index) => {
105
+ const x = across === undefined ? undefined : row.cells[across]?.value;
106
+ const y = row.cells[column]?.value;
107
+ return x === undefined || y === undefined ? [] : [[x, y, index]];
108
+ }));
109
+ const all = pairs.flat();
110
+ const x = nice(extent(all.length > 0 ? all.map(([one]) => one) : [0]));
111
+ const y = nice(extent(all.length > 0 ? all.map(([, one]) => one) : [0]));
112
+ const along = linear(x, [0, WIDTH]);
113
+ const up = linear(y, [HEIGHT, 0]);
114
+ return {
115
+ pairs: pairs.map((series) => series.map(([one, two]) => [one, two])),
116
+ points: pairs.flatMap((series, depth) => series.map(([one, two, row]) => ({ depth, row, x: along(one), y: up(two) }))),
117
+ x,
118
+ xName: name(across),
119
+ y,
120
+ yNames: ups.map((column) => name(column)),
121
+ };
122
+ }
123
+ render() {
124
+ const field = this.field;
125
+ this.drawn = field.points;
126
+ if (field.points.length === 0) {
127
+ return html `<div id="helix-chart-plot" part="plot"></div>`;
128
+ }
129
+ const format = formatNumbers('');
130
+ const along = linear(field.x, [0, WIDTH]);
131
+ 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>`)}
135
+ </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
+ ${this.renderSummary(field)}`;
142
+ }
143
+ renderPlot(field, along, up) {
144
+ const at = this.pointer.at;
145
+ return html `<div aria-hidden="true" id="helix-chart-plot" part="plot">
146
+ <svg part="svg"
147
+ preserveAspectRatio="none"
148
+ viewBox="0 0 ${WIDTH} ${HEIGHT}"
149
+ xmlns="http://www.w3.org/2000/svg"
150
+ >
151
+ ${this.rulesX
152
+ ? ticks(field.x).map((level) => svg `<line part="gridline" x1=${along(level)} x2=${along(level)} y1="0" y2=${HEIGHT} />`)
153
+ : nothing}
154
+ ${this.rulesY
155
+ ? ticks(field.y).map((level) => svg `<line part="gridline" x1="0" x2=${WIDTH} y1=${up(level)} y2=${up(level)} />`)
156
+ : nothing}
157
+ </svg>
158
+ ${field.points.map((point) => {
159
+ const index = seriesIndex(point.depth);
160
+ return html `<span
161
+ data-series=${index}
162
+ ?data-active=${point.row === at}
163
+ part="point"
164
+ style=${`--chart-series: var(--chart-series-${index}); --chart-point-x: ${point.x}%; --chart-point-y: ${point.y}%`}
165
+ ></span>`;
166
+ })}
167
+ ${this.renderTooltip()}
168
+ </div>`;
169
+ }
170
+ renderSummary(field) {
171
+ const clauses = field.yNames.map((name, depth) => {
172
+ const pairs = field.pairs[depth] ?? [];
173
+ const r = correlation(pairs);
174
+ const strength = r === undefined ? '' : `, ${this.correlationLabel} ${coefficient.format(r)}`;
175
+ return `${name} / ${field.xName}: ${pairs.length} ${this.pointsLabel}${strength}`;
176
+ });
177
+ const named = this.named ? `${this.named}. ` : '';
178
+ return html `<p part="summary">${named}${clauses.join('; ')}.</p>`;
179
+ }
180
+ renderTooltip() {
181
+ const at = this.pointer.at;
182
+ const point = this.nearest;
183
+ if (at < 0 || !point) {
184
+ return drawTooltip(this.sourceRows, -1, this.tag('tooltip'));
185
+ }
186
+ return drawTooltip(this.sourceRows, at, this.tag('tooltip'), {
187
+ placement: 'top',
188
+ x: `${point.x}%`,
189
+ y: `${point.y}%`,
190
+ });
191
+ }
192
+ };
193
+ __decorate([
194
+ property({ attribute: 'correlation-label' })
195
+ ], HelixHeadlessScatterChart.prototype, "correlationLabel", void 0);
196
+ __decorate([
197
+ property()
198
+ ], HelixHeadlessScatterChart.prototype, "gridlines", void 0);
199
+ __decorate([
200
+ property({ attribute: 'points-label' })
201
+ ], HelixHeadlessScatterChart.prototype, "pointsLabel", void 0);
202
+ HelixHeadlessScatterChart = __decorate([
203
+ customElement('helix-headless-scatter-chart')
204
+ ], HelixHeadlessScatterChart);
205
+ export { HelixHeadlessScatterChart };
@@ -0,0 +1 @@
1
+ export { HelixHeadlessSkipLink } from './root/skip-link-root.js';
@@ -0,0 +1 @@
1
+ export { HelixHeadlessSkipLink } from './root/skip-link-root.js';
@@ -0,0 +1,81 @@
1
+ import { HelixElement } from '@fusion.dev/helix-core';
2
+ import { type TemplateResult } from 'lit';
3
+ /**
4
+ * @summary The first stop on a page, which moves the reader past everything
5
+ * repeated on every page.
6
+ *
7
+ * @description
8
+ * A keyboard reader arriving on a page tabs through the whole header before
9
+ * reaching anything the page is about, and does it again on the next page. This
10
+ * is the control that skips it: first in the document, and pointed at the
11
+ * content.
12
+ *
13
+ * **It moves focus, not only the scroll position.** An in-page link on its own
14
+ * scrolls to its target and leaves focus behind, so the next Tab goes to
15
+ * whatever followed the link, which is the header it was meant to skip. Pressed,
16
+ * this focuses the target. A target that cannot take focus, which is most of
17
+ * them, since `<main>` cannot, is given `tabindex="-1"` for as long as it holds
18
+ * focus and has it taken off again when focus leaves, so the page ends up as the
19
+ * consumer wrote it.
20
+ *
21
+ * **The address is left alone.** The press is handled rather than followed, so
22
+ * no `#main` lands in the URL and no history entry is added, which matters to a
23
+ * router reading the hash. The anchor is still a real anchor with a real
24
+ * `href`, so before any script has run the link does what a plain link does:
25
+ * it scrolls there and moves the point the next Tab starts from.
26
+ *
27
+ * **Hidden until focused is the styled tier's**, being a decision about paint,
28
+ * and on this tier the link is simply a link. A headless consumer hides
29
+ * `::part(link)` while the host is not `:focus-within` and shows it when it is.
30
+ * Hiding with `display: none` or `visibility: hidden` takes it out of the tab
31
+ * order as well, and a skip link nobody can reach is the most common way one is
32
+ * broken.
33
+ *
34
+ * @since 0.1.0
35
+ * @status experimental
36
+ * @category navigation
37
+ * @aliases Skip navigation, Skip to content
38
+ * @documentation https://helix.fusion.dev/components/skip-link
39
+ *
40
+ * @slot - The link's words. Falls back to `label`.
41
+ *
42
+ * @csspart link - The anchor, which is what takes focus.
43
+ */
44
+ export declare class HelixHeadlessSkipLink extends HelixElement {
45
+ /** Focus goes to the anchor inside, so the host is never the focus target. */
46
+ static shadowRootOptions: ShadowRootInit;
47
+ /**
48
+ * Where the reader is sent, as an in-page fragment.
49
+ *
50
+ * `#main` by default, because that is the id the content region almost always
51
+ * has; a page whose content is somewhere else says so. Anything that is not a
52
+ * fragment naming an element on the page is left to the browser, as a plain
53
+ * link.
54
+ */
55
+ href: string;
56
+ /**
57
+ * The link's words, for the common case of plain text.
58
+ *
59
+ * The fallback of the default slot, so slotted content replaces it rather than
60
+ * appearing beside it.
61
+ */
62
+ label: string;
63
+ /** The element `href` names, looked up in the tree this link sits in. */
64
+ private get target();
65
+ /**
66
+ * Focuses the target, lending it a `tabindex` only if it needs one.
67
+ *
68
+ * Tried without one first, so a target that can already take focus, a heading
69
+ * with its own `tabindex` or a control, is never given an attribute it did
70
+ * not have. The loan is repaid on `blur` rather than on the next press: a
71
+ * `tabindex` left on `<main>` makes every click inside it focus the whole
72
+ * region.
73
+ */
74
+ private readonly skip;
75
+ protected render(): TemplateResult;
76
+ }
77
+ declare global {
78
+ interface HTMLElementTagNameMap {
79
+ 'helix-headless-skip-link': HelixHeadlessSkipLink;
80
+ }
81
+ }
@@ -0,0 +1,58 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
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
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { customElement, HelixElement } from '@fusion.dev/helix-core';
8
+ import { html } from 'lit';
9
+ import { property } from 'lit/decorators.js';
10
+ let HelixHeadlessSkipLink = class HelixHeadlessSkipLink extends HelixElement {
11
+ constructor() {
12
+ super(...arguments);
13
+ this.href = '#main';
14
+ this.label = 'Skip to content';
15
+ this.skip = (event) => {
16
+ if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
17
+ return;
18
+ }
19
+ const target = this.target;
20
+ if (!target) {
21
+ return;
22
+ }
23
+ event.preventDefault();
24
+ target.focus();
25
+ if (target.getRootNode().activeElement === target) {
26
+ return;
27
+ }
28
+ target.setAttribute('tabindex', '-1');
29
+ target.addEventListener('blur', () => target.removeAttribute('tabindex'), { once: true });
30
+ target.focus();
31
+ };
32
+ }
33
+ static { this.shadowRootOptions = {
34
+ ...HelixElement.shadowRootOptions,
35
+ delegatesFocus: true,
36
+ }; }
37
+ get target() {
38
+ if (!this.href.startsWith('#') || this.href.length < 2) {
39
+ return null;
40
+ }
41
+ const id = decodeURIComponent(this.href.slice(1));
42
+ const root = this.getRootNode();
43
+ return (root.getElementById?.(id) ?? document.getElementById(id));
44
+ }
45
+ render() {
46
+ return html `<a part="link" href=${this.href} @click=${this.skip}><slot>${this.label}</slot></a>`;
47
+ }
48
+ };
49
+ __decorate([
50
+ property()
51
+ ], HelixHeadlessSkipLink.prototype, "href", void 0);
52
+ __decorate([
53
+ property()
54
+ ], HelixHeadlessSkipLink.prototype, "label", void 0);
55
+ HelixHeadlessSkipLink = __decorate([
56
+ customElement('helix-headless-skip-link')
57
+ ], HelixHeadlessSkipLink);
58
+ export { HelixHeadlessSkipLink };
@@ -38,6 +38,7 @@ declare const HelixHeadlessSpeedDial_base: (new (...args: any[]) => import("@fus
38
38
  *
39
39
  * @event {HelixBeforeCloseEvent} helix-before-close - Fired before it closes, and cancelable.
40
40
  * @event {HelixBeforeOpenEvent} helix-before-open - Fired before it opens, and cancelable.
41
+ * @event {HelixSelectEvent} helix-select - Fired when an action is chosen. Dispatched by the action and listened for here, and the dial closes behind it once it has been heard. Cancelable: refusing it takes the decision and the dial stays open.
41
42
  * @event {Event} toggle - Says the state moved, carrying `newState` and `oldState`.
42
43
  *
43
44
  * @csspart actions - The region the actions are drawn in.
@@ -29,8 +29,12 @@ let HelixHeadlessSpeedDial = class HelixHeadlessSpeedDial extends MixinControlle
29
29
  this.handleTriggerClick = () => {
30
30
  this.requestOpen(!this.open);
31
31
  };
32
- this.handleSelect = () => {
33
- this.close();
32
+ this.handleSelect = (event) => {
33
+ queueMicrotask(() => {
34
+ if (!event.defaultPrevented) {
35
+ this.close();
36
+ }
37
+ });
34
38
  };
35
39
  this.handleKeydown = (event) => {
36
40
  if (event.key !== 'Escape') {
@@ -26,6 +26,7 @@ declare const HelixHeadlessSteps_base: (new (...args: any[]) => import("@fusion.
26
26
  *
27
27
  * @event {HelixActivateEvent} helix-activate - Fired once the current one has moved, so the element already holds it.
28
28
  * @event {HelixBeforeActivateEvent} helix-before-activate - Fired before the active step changes. Cancelable: refusing it takes the decision and nothing moves.
29
+ * @event {HelixSelectEvent} helix-select - Fired when a step is pressed. Dispatched by the step and listened for here, because the root is what a consumer holds. Cancelable: refusing it takes the decision and nothing moves.
29
30
  */
30
31
  export declare class HelixHeadlessSteps extends HelixHeadlessSteps_base {
31
32
  /**
@@ -10,6 +10,16 @@ import { type PropertyValues, type TemplateResult } from 'lit';
10
10
  * `undefined` falls back to the field.
11
11
  */
12
12
  export type CellReader = (row: Readonly<Record<string, unknown>>) => unknown;
13
+ /**
14
+ * A dot path resolved against a row.
15
+ *
16
+ * Written here rather than taken from a library, because the whole of what is
17
+ * needed is "walk the keys and stop at the first thing that is not an object".
18
+ * Returns `undefined` for a path that does not resolve, which the table renders
19
+ * as an empty cell: a missing key is a hole in the data and drawing `undefined`
20
+ * into a table tells a reader the software is broken rather than the row is.
21
+ */
22
+ export declare const at: (row: Readonly<Record<string, unknown>>, path: string) => unknown;
13
23
  /**
14
24
  * @summary One column of a table: its heading, how wide it is, and what it reads.
15
25
  *
@@ -37,6 +47,14 @@ export type CellReader = (row: Readonly<Record<string, unknown>>) => unknown;
37
47
  * item of the table, so it lines up with the cells under it rather than sitting
38
48
  * in a wrapper that has to be kept the same width.
39
49
  *
50
+ * **A heading that does something holds a control that does it.** The host is
51
+ * the column header, and a header is not focusable, so a sortable heading is a
52
+ * `<button>` inside it: the pattern for a sortable table, and the only way a
53
+ * reader on a keyboard reaches the sort at all. A column a reader may resize
54
+ * carries a `separator` on its end edge, moved by dragging it or with the arrow
55
+ * keys, and a column in a table that lets a reader reorder its columns carries
56
+ * a grip the table moves.
57
+ *
40
58
  * @since 0.1.0
41
59
  * @status experimental
42
60
  * @aliases Column header, Th
@@ -44,7 +62,13 @@ export type CellReader = (row: Readonly<Record<string, unknown>>) => unknown;
44
62
  *
45
63
  * @slot - The heading.
46
64
  *
65
+ * @event {HelixBeforeResizeEvent} helix-before-resize - Fired as a reader resizes the column, with the width asked for, and cancelable.
66
+ *
47
67
  * @csspart column - The heading cell.
68
+ * @csspart grip - The handle a reader moves the column by, where the table allows it.
69
+ * @csspart grip-mark - The glyph on it.
70
+ * @csspart resizer - The edge a reader drags to change the width, where the column allows it.
71
+ * @csspart sort - The button a sortable heading is.
48
72
  */
49
73
  export declare class HelixHeadlessTableColumn extends HelixElement {
50
74
  /**
@@ -90,6 +114,15 @@ export declare class HelixHeadlessTableColumn extends HelixElement {
90
114
  * such a column is never sortable however it is marked.
91
115
  */
92
116
  field: string;
117
+ /**
118
+ * Whether a reader may change this column's width.
119
+ *
120
+ * By dragging the edge at the end of the heading, or with the arrow keys once
121
+ * it has focus, a step at a time and four with Shift. The width lands in
122
+ * `width` as pixels, and `helix-before-resize` asks first, so a consumer who
123
+ * stores the widths can take the decision.
124
+ */
125
+ resizable: boolean;
93
126
  /**
94
127
  * Whether a reader may reorder the set by this column.
95
128
  *
@@ -135,6 +168,28 @@ export declare class HelixHeadlessTableColumn extends HelixElement {
135
168
  * not in.
136
169
  */
137
170
  sorted: 'ascending' | 'descending' | undefined;
171
+ /**
172
+ * @internal Whether the table this sits in lets a reader move it, written by
173
+ * that table. Not a consumer's to set: the order is the table's, and a grip
174
+ * on a column the table will not move is a control that does nothing.
175
+ */
176
+ movable: boolean;
177
+ /** How wide the heading drew, for the separator to report. */
178
+ private drawn;
179
+ /** Where a drag of the separator started, while one is running. */
180
+ private resizing;
181
+ /** Watches the heading's width, which changes with the table as well as with a drag. */
182
+ private watcher;
183
+ /**
184
+ * The narrowest a reader can drag a column, in pixels.
185
+ *
186
+ * Three characters and the inset around them. Narrower and the heading has
187
+ * nothing left to be read by, and the separator is a grab target with no
188
+ * column beside it.
189
+ */
190
+ static readonly NARROWEST = 48;
191
+ /** The step the arrow keys move the separator by, in pixels. */
192
+ static readonly STEP = 16;
138
193
  /** The track this column contributes, resolved. */
139
194
  get track(): string;
140
195
  /** Whether a reader can actually reorder by this column. */
@@ -150,6 +205,30 @@ export declare class HelixHeadlessTableColumn extends HelixElement {
150
205
  /** The value this column sorts and filters by, which is not always what it shows. */
151
206
  order(row: Readonly<Record<string, unknown>>): unknown;
152
207
  protected willUpdate(changed: PropertyValues): void;
208
+ /** The words of the heading, for naming the controls on it. */
209
+ private get name();
210
+ connectedCallback(): void;
211
+ private observeHeading;
212
+ disconnectedCallback(): void;
213
+ protected firstUpdated(changed: PropertyValues): void;
214
+ /**
215
+ * Asks for a width, in pixels, no narrower than a column can be read at.
216
+ *
217
+ * Through the same request every controlled value here uses: announced, and
218
+ * applied only where nobody refused it.
219
+ */
220
+ private resizeTo;
221
+ /** Which way a drag or an arrow key grows the column, in this writing direction. */
222
+ private get forward();
223
+ private readonly handleResizeStart;
224
+ private readonly handleResizeMove;
225
+ private readonly handleResizeEnd;
226
+ /**
227
+ * The separator's keys: a step each way, four with Shift, and Home for the
228
+ * narrowest. Arrows follow the writing direction, so the one pointing away
229
+ * from the heading's start always widens it.
230
+ */
231
+ private readonly handleResizeKey;
153
232
  protected render(): TemplateResult;
154
233
  }
155
234
  declare global {
@@ -4,10 +4,13 @@ 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, HelixElement, hostAria } from '@fusion.dev/helix-core';
8
- import { html } from 'lit';
7
+ var HelixHeadlessTableColumn_1;
8
+ import { customElement, HelixBeforeResizeEvent, HelixElement, hostAria, requestChange, } from '@fusion.dev/helix-core';
9
+ import { dotsThreeVertical } from '@fusion.dev/helix-icons';
10
+ import { html, nothing } from 'lit';
9
11
  import { property, state } from 'lit/decorators.js';
10
- const at = (row, path) => {
12
+ import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
13
+ export const at = (row, path) => {
11
14
  if (path === '') {
12
15
  return undefined;
13
16
  }
@@ -30,11 +33,50 @@ let HelixHeadlessTableColumn = class HelixHeadlessTableColumn extends HelixEleme
30
33
  this.cell = undefined;
31
34
  this.compare = undefined;
32
35
  this.field = '';
36
+ this.resizable = false;
33
37
  this.sortable = false;
34
38
  this.width = '';
35
39
  this.isTableColumn = true;
36
40
  this.sorted = undefined;
41
+ this.movable = false;
42
+ this.drawn = 0;
43
+ this.handleResizeStart = (event) => {
44
+ if (event.button !== 0) {
45
+ return;
46
+ }
47
+ event.preventDefault();
48
+ try {
49
+ ;
50
+ event.currentTarget.setPointerCapture(event.pointerId);
51
+ }
52
+ catch {
53
+ }
54
+ this.resizing = { from: event.clientX, start: this.drawn };
55
+ };
56
+ this.handleResizeMove = (event) => {
57
+ if (this.resizing) {
58
+ this.resizeTo(this.resizing.start + (event.clientX - this.resizing.from) * this.forward);
59
+ }
60
+ };
61
+ this.handleResizeEnd = () => {
62
+ this.resizing = undefined;
63
+ };
64
+ this.handleResizeKey = (event) => {
65
+ const step = HelixHeadlessTableColumn_1.STEP * (event.shiftKey ? 4 : 1);
66
+ const grow = event.key === 'ArrowRight' ? this.forward : event.key === 'ArrowLeft' ? -this.forward : 0;
67
+ if (grow !== 0) {
68
+ event.preventDefault();
69
+ this.resizeTo(this.drawn + grow * step);
70
+ }
71
+ else if (event.key === 'Home') {
72
+ event.preventDefault();
73
+ this.resizeTo(HelixHeadlessTableColumn_1.NARROWEST);
74
+ }
75
+ };
37
76
  }
77
+ static { HelixHeadlessTableColumn_1 = this; }
78
+ static { this.NARROWEST = 48; }
79
+ static { this.STEP = 16; }
38
80
  get track() {
39
81
  return this.width || 'minmax(max-content, 1fr)';
40
82
  }
@@ -60,8 +102,77 @@ let HelixHeadlessTableColumn = class HelixHeadlessTableColumn extends HelixEleme
60
102
  this.toggleState(name, on);
61
103
  }
62
104
  }
105
+ get name() {
106
+ return this.textContent?.trim() ?? '';
107
+ }
108
+ connectedCallback() {
109
+ super.connectedCallback();
110
+ if (typeof ResizeObserver !== 'undefined') {
111
+ this.watcher = new ResizeObserver(([entry]) => {
112
+ this.drawn = Math.round(entry?.borderBoxSize?.[0]?.inlineSize ?? 0);
113
+ });
114
+ this.observeHeading();
115
+ }
116
+ }
117
+ observeHeading() {
118
+ const heading = this.renderRoot?.querySelector('[part="column"]');
119
+ if (heading) {
120
+ this.watcher?.observe(heading);
121
+ }
122
+ }
123
+ disconnectedCallback() {
124
+ super.disconnectedCallback();
125
+ this.watcher?.disconnect();
126
+ }
127
+ firstUpdated(changed) {
128
+ super.firstUpdated?.(changed);
129
+ this.observeHeading();
130
+ }
131
+ resizeTo(px) {
132
+ const next = `${Math.max(HelixHeadlessTableColumn_1.NARROWEST, Math.round(px))}px`;
133
+ if (next === this.width) {
134
+ return;
135
+ }
136
+ requestChange({
137
+ apply: (value) => {
138
+ this.width = value;
139
+ },
140
+ event: (value) => new HelixBeforeResizeEvent({ value }),
141
+ host: this,
142
+ next,
143
+ });
144
+ }
145
+ get forward() {
146
+ return getComputedStyle(this).direction === 'rtl' ? -1 : 1;
147
+ }
63
148
  render() {
64
- return html `<span part="column"><slot></slot></span>`;
149
+ const name = this.name;
150
+ return html `<span part="column">${this.movable
151
+ ? html `<button part="grip" type="button" aria-label=${name ? `Move ${name}` : 'Move column'}>
152
+ <svg aria-hidden="true" fill="currentColor" part="grip-mark" viewBox=${dotsThreeVertical.viewBox}>
153
+ ${unsafeSVG(dotsThreeVertical.content)}
154
+ </svg>
155
+ </button>`
156
+ : nothing}${this.canSort
157
+ ? html `<button part="sort" type="button"><slot></slot></button>`
158
+ : html `<slot></slot>`}${this.resizable
159
+ ? html `<span
160
+ part="resizer"
161
+ role="separator"
162
+ tabindex="0"
163
+ aria-label=${name ? `Resize ${name}` : 'Resize column'}
164
+ aria-orientation="vertical"
165
+ aria-valuemin=${HelixHeadlessTableColumn_1.NARROWEST}
166
+ aria-valuemax="4096"
167
+ aria-valuenow=${Math.max(HelixHeadlessTableColumn_1.NARROWEST, this.drawn)}
168
+ aria-valuetext=${`${this.drawn} pixels`}
169
+ @keydown=${this.handleResizeKey}
170
+ @pointerdown=${this.handleResizeStart}
171
+ @pointermove=${this.handleResizeMove}
172
+ @pointerup=${this.handleResizeEnd}
173
+ @pointercancel=${this.handleResizeEnd}
174
+ ></span>`
175
+ : nothing}</span>`;
65
176
  }
66
177
  };
67
178
  __decorate([
@@ -76,6 +187,9 @@ __decorate([
76
187
  __decorate([
77
188
  property()
78
189
  ], HelixHeadlessTableColumn.prototype, "field", void 0);
190
+ __decorate([
191
+ property({ type: Boolean })
192
+ ], HelixHeadlessTableColumn.prototype, "resizable", void 0);
79
193
  __decorate([
80
194
  property({ type: Boolean })
81
195
  ], HelixHeadlessTableColumn.prototype, "sortable", void 0);
@@ -85,7 +199,13 @@ __decorate([
85
199
  __decorate([
86
200
  state()
87
201
  ], HelixHeadlessTableColumn.prototype, "sorted", void 0);
88
- HelixHeadlessTableColumn = __decorate([
202
+ __decorate([
203
+ state()
204
+ ], HelixHeadlessTableColumn.prototype, "movable", void 0);
205
+ __decorate([
206
+ state()
207
+ ], HelixHeadlessTableColumn.prototype, "drawn", void 0);
208
+ HelixHeadlessTableColumn = HelixHeadlessTableColumn_1 = __decorate([
89
209
  customElement('helix-headless-table-column')
90
210
  ], HelixHeadlessTableColumn);
91
211
  export { HelixHeadlessTableColumn };