@fusion.dev/helix-headless 0.229.1 → 0.231.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 (31) hide show
  1. package/api.json +1 -1
  2. package/custom-elements.json +1 -1
  3. package/dist/data-table/events/before-column-toggle.d.ts +14 -0
  4. package/dist/data-table/events/before-column-toggle.js +11 -0
  5. package/dist/data-table/events/column-toggle.d.ts +11 -0
  6. package/dist/data-table/events/column-toggle.js +11 -0
  7. package/dist/data-table/events/details.d.ts +7 -0
  8. package/dist/data-table/events/index.d.ts +6 -2
  9. package/dist/data-table/events/index.js +5 -1
  10. package/dist/data-table/root/data-table-root.d.ts +102 -0
  11. package/dist/data-table/root/data-table-root.js +183 -3
  12. package/dist/element-events.generated.js +2 -0
  13. package/dist/spatial-bar-chart/root/spatial-bar-chart-root.js +9 -9
  14. package/dist/spatial-ribbon-chart/root/spatial-ribbon-chart-root.d.ts +1 -0
  15. package/dist/spatial-ribbon-chart/root/spatial-ribbon-chart-root.js +3 -2
  16. package/dist/spatial-scatter-chart/root/spatial-scatter-chart-root.d.ts +1 -0
  17. package/dist/spatial-scatter-chart/root/spatial-scatter-chart-root.js +3 -2
  18. package/dist/spatial-scatter-chart/view.d.ts +30 -1
  19. package/dist/spatial-scatter-chart/view.js +56 -9
  20. package/dist/spatial-surface-chart/root/spatial-surface-chart-root.d.ts +1 -0
  21. package/dist/spatial-surface-chart/root/spatial-surface-chart-root.js +3 -2
  22. package/dist/spatial-treemap/root/spatial-treemap-root.d.ts +1 -0
  23. package/dist/spatial-treemap/root/spatial-treemap-root.js +3 -2
  24. package/dist/table/column/table-column.d.ts +30 -0
  25. package/dist/table/column/table-column.js +49 -7
  26. package/examples.json +1 -1
  27. package/lint.json +1 -1
  28. package/package.json +5 -5
  29. package/types/jsx.d.ts +146 -1
  30. package/types/vue.d.ts +72 -1
  31. package/types/words.d.ts +43 -0
@@ -0,0 +1,14 @@
1
+ import type { ColumnToggleDetail } from './details.js';
2
+ /**
3
+ * A column is about to be hidden or shown again, from its menu or from
4
+ * `hideColumn` or `showColumn`.
5
+ *
6
+ * Cancelable: cancel it to keep the column as it is, and set `hidden` on the
7
+ * column yourself.
8
+ *
9
+ * @followedBy helix-column-toggle
10
+ */
11
+ export declare class HelixBeforeColumnToggleEvent extends CustomEvent<ColumnToggleDetail> {
12
+ static readonly eventName = "helix-before-column-toggle";
13
+ constructor(detail: ColumnToggleDetail);
14
+ }
@@ -0,0 +1,11 @@
1
+ export class HelixBeforeColumnToggleEvent extends CustomEvent {
2
+ static { this.eventName = 'helix-before-column-toggle'; }
3
+ constructor(detail) {
4
+ super(HelixBeforeColumnToggleEvent.eventName, {
5
+ bubbles: true,
6
+ cancelable: true,
7
+ composed: true,
8
+ detail,
9
+ });
10
+ }
11
+ }
@@ -0,0 +1,11 @@
1
+ import type { ColumnToggleDetail } from './details.js';
2
+ /**
3
+ * A column was hidden or shown again.
4
+ *
5
+ * The notification after `helix-before-column-toggle`, sent when nobody refused
6
+ * it. Not cancelable: the decision was the request's.
7
+ */
8
+ export declare class HelixColumnToggleEvent extends CustomEvent<ColumnToggleDetail> {
9
+ static readonly eventName = "helix-column-toggle";
10
+ constructor(detail: ColumnToggleDetail);
11
+ }
@@ -0,0 +1,11 @@
1
+ export class HelixColumnToggleEvent extends CustomEvent {
2
+ static { this.eventName = 'helix-column-toggle'; }
3
+ constructor(detail) {
4
+ super(HelixColumnToggleEvent.eventName, {
5
+ bubbles: true,
6
+ cancelable: false,
7
+ composed: true,
8
+ detail,
9
+ });
10
+ }
11
+ }
@@ -1,3 +1,10 @@
1
+ /** Detail carried by `helix-before-column-toggle` and `helix-column-toggle`. */
2
+ export interface ColumnToggleDetail {
3
+ /** The `field` of the column. */
4
+ field: string;
5
+ /** Whether the column is hidden after the change. */
6
+ hidden: boolean;
7
+ }
1
8
  /** Detail carried by `helix-before-follow` and `helix-follow`. */
2
9
  export interface FollowDetail {
3
10
  /** The path, query, and hash the link names, ready for a router. */
@@ -1,9 +1,13 @@
1
+ import { HelixBeforeColumnToggleEvent } from './before-column-toggle.js';
1
2
  import { HelixBeforeFollowEvent } from './before-follow.js';
3
+ import { HelixColumnToggleEvent } from './column-toggle.js';
2
4
  import { HelixFollowEvent } from './follow.js';
3
- export type { FollowDetail } from './details.js';
4
- export { HelixBeforeFollowEvent, HelixFollowEvent };
5
+ export type { ColumnToggleDetail, FollowDetail } from './details.js';
6
+ export { HelixBeforeColumnToggleEvent, HelixBeforeFollowEvent, HelixColumnToggleEvent, HelixFollowEvent, };
5
7
  declare const registry: {
8
+ readonly HelixBeforeColumnToggleEvent: typeof HelixBeforeColumnToggleEvent;
6
9
  readonly HelixBeforeFollowEvent: typeof HelixBeforeFollowEvent;
10
+ readonly HelixColumnToggleEvent: typeof HelixColumnToggleEvent;
7
11
  readonly HelixFollowEvent: typeof HelixFollowEvent;
8
12
  };
9
13
  type Registry = typeof registry;
@@ -1,9 +1,13 @@
1
1
  import { registerEventNames } from '@fusion.dev/helix-core';
2
+ import { HelixBeforeColumnToggleEvent } from './before-column-toggle.js';
2
3
  import { HelixBeforeFollowEvent } from './before-follow.js';
4
+ import { HelixColumnToggleEvent } from './column-toggle.js';
3
5
  import { HelixFollowEvent } from './follow.js';
4
- export { HelixBeforeFollowEvent, HelixFollowEvent };
6
+ export { HelixBeforeColumnToggleEvent, HelixBeforeFollowEvent, HelixColumnToggleEvent, HelixFollowEvent, };
5
7
  const registry = {
8
+ HelixBeforeColumnToggleEvent,
6
9
  HelixBeforeFollowEvent,
10
+ HelixColumnToggleEvent,
7
11
  HelixFollowEvent,
8
12
  };
9
13
  registerEventNames(Object.values(registry).map((one) => one.eventName));
@@ -2,6 +2,8 @@ import { type DataRow, HelixElement } from '@fusion.dev/helix-core';
2
2
  import { type PropertyValues, type TemplateResult } from 'lit';
3
3
  import type { HelixHeadlessTableColumn } from '../../table/column/table-column.js';
4
4
  import '../../checkbox/index.js';
5
+ import '../../divider/index.js';
6
+ import '../../menu/index.js';
5
7
  import '../../pagination/index.js';
6
8
  declare const HelixHeadlessDataTable_base: (new (...args: any[]) => import("@fusion.dev/helix-core").CollectionElement) & typeof HelixElement;
7
9
  /**
@@ -124,6 +126,25 @@ declare const HelixHeadlessDataTable_base: (new (...args: any[]) => import("@fus
124
126
  * is written to `order` as a list of fields. Your markup is not rewritten: the
125
127
  * table assigns the headings to its header in that order.
126
128
  *
129
+ * **`menu` on a column adds a menu to its heading.** A small button after the
130
+ * sort chevrons opens it, and a press anywhere else on the heading still sorts.
131
+ * The rows depend on the column:
132
+ *
133
+ * - Sort ascending and Sort descending, where the column is `sortable`.
134
+ * - Move left and Move right, where the table is `reorderable`.
135
+ * - Hide column, which is disabled on the last column showing.
136
+ * - Show hidden columns, once the menu has hidden a column.
137
+ *
138
+ * Sorting and moving ask first with `helix-before-sort` and
139
+ * `helix-before-reorder`, as a press on the heading or a drag of the grip does.
140
+ * Hiding asks with `helix-before-column-toggle`, then sets `hidden` on the column
141
+ * and fires `helix-column-toggle`. Your own column picker can call `hideColumn`
142
+ * and `showColumn`. Show hidden columns restores only the columns that went
143
+ * through them, so a column you hid with `hidden` stays hidden.
144
+ *
145
+ * In the grid, Alt+Down on a heading opens its menu, and F2 and Tab reach the
146
+ * button.
147
+ *
127
148
  * **`reorderable-rows` lets people move rows.** A column of grips leads each
128
149
  * row, named by `move-row-label`. Drag a grip to move its row, and the table
129
150
  * scrolls near an edge. Or press Space or Enter on a grip, or click it, to pick
@@ -172,12 +193,14 @@ declare const HelixHeadlessDataTable_base: (new (...args: any[]) => import("@fus
172
193
  * @slot loading - Content shown while the rows load, such as a spinner. Falls back to `loading-text`, which the live region still says.
173
194
  * @slot toolbar - Content above the grid, such as a search field or actions.
174
195
  *
196
+ * @event {HelixBeforeColumnToggleEvent} helix-before-column-toggle - A column is about to be hidden or shown, with its `field` and `hidden`. Cancelable: cancel it to keep the column as it is.
175
197
  * @event {HelixBeforeFilterEvent} helix-before-filter - The search query is about to change. Cancelable: cancel it to keep the current query.
176
198
  * @event {HelixBeforeFollowEvent} helix-before-follow - A press on a link in a cell is about to load its page, in this tab and on this site. Cancelable: cancel it to keep the page, and hand `detail.href` to your router.
177
199
  * @event {HelixBeforeNavigateEvent} helix-before-navigate - Another page was requested. Cancelable: cancel it to stay on the current page.
178
200
  * @event {HelixBeforeReorderEvent} helix-before-reorder - A row or a column is about to move. For a row, with `reorderable-rows`, the event carries every row's key in the new order; for a column, `kind` is `column` and `value` holds every field in the new order. Cancelable: cancel it to keep the row or the column where it is.
179
201
  * @event {HelixBeforeSelectEvent} helix-before-select - The selection is about to change. The event carries every selected key, separated by commas. Cancelable: cancel it to keep the current selection.
180
202
  * @event {HelixBeforeSortEvent} helix-before-sort - A different sort order was requested. Cancelable: cancel it to keep the current order.
203
+ * @event {HelixColumnToggleEvent} helix-column-toggle - Fired after a column is hidden or shown, with its `field` and `hidden`.
181
204
  * @event {HelixExpandEvent} helix-expand - Fired after a row opens or closes, with every open row's key, separated by commas, which `expanded` also holds.
182
205
  * @event {HelixFilterEvent} helix-filter - Fired after the search query changes, with the new query.
183
206
  * @event {HelixFollowEvent} helix-follow - Fired as the browser loads the page a link in a cell names, with its path.
@@ -211,6 +234,28 @@ declare const HelixHeadlessDataTable_base: (new (...args: any[]) => import("@fus
211
234
  * @csspart head - The row that holds the column headings. It is `display: contents`, so it takes no space.
212
235
  * @csspart live - A copy of the empty and loading messages, which screen readers announce when they change. Visually hidden in the styled tier.
213
236
  * @csspart loading - Shown while the rows are loading.
237
+ * @csspart menu-back - Forwarded from the column's menu, on the column. Not shown in the table, whose menu has no submenu.
238
+ * @csspart menu-back-mark - Forwarded from the column's menu, on the column. Not shown in the table, whose menu has no submenu.
239
+ * @csspart menu-divider - The line between groups of rows in a column's menu, on the column.
240
+ * @csspart menu-divider-content - Forwarded from the divider, on the column. Not shown in the table.
241
+ * @csspart menu-divider-line - The line inside `menu-divider`, on the column.
242
+ * @csspart menu-empty - Forwarded from the column's menu, on the column. Not shown in the table, whose menu always has a row.
243
+ * @csspart menu-entry - One row of a column's menu, on the column.
244
+ * @csspart menu-entry-end - The trailing column of a row in a column's menu, on the column.
245
+ * @csspart menu-entry-item - The row inside `menu-entry`, on the column.
246
+ * @csspart menu-entry-mark - The check mark on the sort row that is current, on the column.
247
+ * @csspart menu-entry-shortcut - Forwarded from the row, on the column. Not shown in the table.
248
+ * @csspart menu-entry-shortcut-kbd - Forwarded from the row, on the column. Not shown in the table.
249
+ * @csspart menu-entry-shortcut-key - Forwarded from the row, on the column. Not shown in the table.
250
+ * @csspart menu-entry-shortcut-separator - Forwarded from the row, on the column. Not shown in the table.
251
+ * @csspart menu-entry-start - The leading column of a row in a column's menu, on the column.
252
+ * @csspart menu-entry-surface - Forwarded from the row, on the column. Not shown in the table.
253
+ * @csspart menu-header - Forwarded from the column's menu, on the column. Not shown in the table, whose menu has no submenu.
254
+ * @csspart menu-live - Forwarded from the column's menu, on the column. Not shown in the table, whose menu has no loading or empty text.
255
+ * @csspart menu-loading - Forwarded from the column's menu, on the column. Not shown in the table.
256
+ * @csspart menu-search-field - Forwarded from the column's menu, on the column. Not shown in the table.
257
+ * @csspart menu-surface - The panel a column's menu opens in, on the column: `helix-table-column::part(menu-surface)`.
258
+ * @csspart menu-title - Forwarded from the column's menu, on the column. Not shown in the table, whose menu has no submenu.
214
259
  * @csspart more - The row after the last one that holds the button for more rows, while `load-more` is set.
215
260
  * @csspart more-button - The button that asks for more rows.
216
261
  * @csspart more-cell - The cell of the `more` row, which spans every column.
@@ -396,6 +441,8 @@ export declare class HelixHeadlessDataTable extends HelixHeadlessDataTable_base
396
441
  * replaced with the page it jumps to.
397
442
  */
398
443
  gapLabel: string;
444
+ /** The name of the row in a column's menu that hides the column. */
445
+ hideColumnLabel: string;
399
446
  /**
400
447
  * The grid's name, read by screen readers.
401
448
  */
@@ -434,6 +481,10 @@ export declare class HelixHeadlessDataTable extends HelixHeadlessDataTable_base
434
481
  * would go, counting from 1, and `{count}` the number of rows on the page.
435
482
  */
436
483
  movedLabel: string;
484
+ /** The name of the row in a column's menu that moves the column left. */
485
+ moveLeftLabel: string;
486
+ /** The name of the row in a column's menu that moves the column right. */
487
+ moveRightLabel: string;
437
488
  /**
438
489
  * The name of a row's grip, with `reorderable-rows`. `{row}` is replaced with
439
490
  * the row's name.
@@ -610,6 +661,8 @@ export declare class HelixHeadlessDataTable extends HelixHeadlessDataTable_base
610
661
  * the first column that has a field, or with its key.
611
662
  */
612
663
  selectRowLabel: string;
664
+ /** The name of the row in a column's menu that brings back the columns it hid. */
665
+ showColumnsLabel: string;
613
666
  /**
614
667
  * The sort order, as `field:direction`.
615
668
  *
@@ -620,6 +673,10 @@ export declare class HelixHeadlessDataTable extends HelixHeadlessDataTable_base
620
673
  */
621
674
  get sort(): string;
622
675
  set sort(next: string);
676
+ /** The name of the row in a column's menu that sorts smallest first. */
677
+ sortAscendingLabel: string;
678
+ /** The name of the row in a column's menu that sorts largest first. */
679
+ sortDescendingLabel: string;
623
680
  /**
624
681
  * What screen readers hear when the table is sorted. `{column}` is the
625
682
  * column's heading and `{direction}` is `ascending` or `descending`, so each
@@ -1168,6 +1225,47 @@ export declare class HelixHeadlessDataTable extends HelixHeadlessDataTable_base
1168
1225
  * told about being one they cannot see yet.
1169
1226
  */
1170
1227
  private announceSort;
1228
+ /** The columns the menu or `hideColumn` hid, which Show hidden columns brings back. */
1229
+ private readonly hiddenByTable;
1230
+ /** The fields in `hiddenByTable` still hidden, since a page can show one itself. */
1231
+ private get restorable();
1232
+ /** Everything a column's menu draws from the table, so a change draws it again. */
1233
+ private columnMenuFacts;
1234
+ /**
1235
+ * Draws a column's menu around the button the column draws.
1236
+ *
1237
+ * In the column's shadow root and from here, so the menu has its trigger beside
1238
+ * it and only a page with a `DataTable` carries the menu's code. Every event a
1239
+ * menu sends stops here: `helix-select` from a row is no row selection.
1240
+ */
1241
+ private readonly drawColumnMenu;
1242
+ /** A menu's own event, which is no event of this table's. */
1243
+ private readonly stopMenuEvent;
1244
+ /** The two sort rows show the table's sort, so a press never checks one itself. */
1245
+ private readonly keepMenuCheck;
1246
+ /**
1247
+ * Does what a menu row says.
1248
+ *
1249
+ * A microtask on, because the menu closes a microtask after `helix-select`
1250
+ * and returns focus to the button. Hide then has a focus to move away from the
1251
+ * button it removes.
1252
+ */
1253
+ private chooseInColumnMenu;
1254
+ /**
1255
+ * Hides a column, as Hide column in its menu does.
1256
+ *
1257
+ * Public for the same reason `toggleSort` is: your own column picker needs a
1258
+ * way to ask. Asks with `helix-before-column-toggle`, and keeps the last column
1259
+ * showing. Returns whether the column was hidden.
1260
+ */
1261
+ hideColumn(field: string): boolean;
1262
+ /**
1263
+ * Shows a hidden column. Asks with `helix-before-column-toggle`, and returns
1264
+ * whether the column was shown.
1265
+ */
1266
+ showColumn(field: string): boolean;
1267
+ /** Asks for a column to be hidden or shown. */
1268
+ private requestHidden;
1171
1269
  /** Columns arrive from the light DOM, which changes no property here. */
1172
1270
  private readonly handleSlotChange;
1173
1271
  /**
@@ -1383,6 +1481,10 @@ export declare class HelixHeadlessDataTable extends HelixHeadlessDataTable_base
1383
1481
  * whether it took the key, and the arrows move between cells where it did not.
1384
1482
  */
1385
1483
  private treeKey;
1484
+ /** Whether the event came from a heading's menu, its button included. */
1485
+ private inColumnMenu;
1486
+ /** Alt+Down on a heading opens its menu. */
1487
+ private openColumnMenuFrom;
1386
1488
  /** Enter, Space and F2 on a cell. */
1387
1489
  private activate;
1388
1490
  /** The keys the grid takes while focus is on a control inside a cell. */
@@ -10,8 +10,11 @@ import { html } from 'lit/static-html.js';
10
10
  import { edgeOf } from '../../table/edge.js';
11
11
  import { tracksOf } from '../../table/tracks.js';
12
12
  import '../../checkbox/index.js';
13
+ import '../../divider/index.js';
14
+ import '../../menu/index.js';
15
+ import { HelixHeadlessMenu } from '../../menu/root/menu-root.js';
13
16
  import '../../pagination/index.js';
14
- import { HelixBeforeFollowEvent, HelixFollowEvent } from '../events/index.js';
17
+ import { HelixBeforeColumnToggleEvent, HelixBeforeFollowEvent, HelixColumnToggleEvent, HelixFollowEvent, } from '../events/index.js';
15
18
  import { toggledOpen } from '../keys.js';
16
19
  import { grow } from '../tree.js';
17
20
  const CONTROL = [
@@ -44,12 +47,15 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
44
47
  this.errorText = '';
45
48
  this.estimate = 32;
46
49
  this.gapLabel = 'Skip to page {page}';
50
+ this.hideColumnLabel = 'Hide column';
47
51
  this.label = '';
48
52
  this.loadMore = false;
49
53
  this.loadMoreLabel = 'Load more';
50
54
  this.locale = '';
51
55
  this.mode = 'client';
52
56
  this.movedLabel = '{name} moved to position {position} of {count}';
57
+ this.moveLeftLabel = 'Move left';
58
+ this.moveRightLabel = 'Move right';
53
59
  this.moveRowLabel = 'Move {row}';
54
60
  this.nextLabel = 'Next page';
55
61
  this.pageLabel = 'Page {page}';
@@ -67,6 +73,9 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
67
73
  this.selectedLabel = '{count} selected';
68
74
  this.selectionBehavior = 'toggle';
69
75
  this.selectRowLabel = 'Select {row}';
76
+ this.showColumnsLabel = 'Show hidden columns';
77
+ this.sortAscendingLabel = 'Sort ascending';
78
+ this.sortDescendingLabel = 'Sort descending';
70
79
  this.sortedLabel = '{direction, select, ascending {Sorted by {column}, ascending} descending {Sorted by {column}, descending} other {Sorted by {column}}}';
71
80
  this.total = 0;
72
81
  this.unsortedLabel = 'Not sorted';
@@ -170,6 +179,58 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
170
179
  this.heardSort = undefined;
171
180
  this.heardSelected = undefined;
172
181
  this.heardFilter = undefined;
182
+ this.hiddenByTable = new Set();
183
+ this.drawColumnMenu = (column, button) => {
184
+ const menu = this.tag('menu');
185
+ const item = this.tag('menu-item');
186
+ const divider = this.tag('divider');
187
+ const shown = this.shownFields;
188
+ const at = shown.indexOf(column.field);
189
+ const left = inlineStep(this, 'ArrowLeft');
190
+ const row = (value, label, disabled = false, sorted) => html `<${item}
191
+ part="menu-entry"
192
+ exportparts="end: menu-entry-end, item: menu-entry-item, mark: menu-entry-mark, shortcut: menu-entry-shortcut,
193
+ shortcut-kbd: menu-entry-shortcut-kbd, shortcut-key: menu-entry-shortcut-key,
194
+ shortcut-separator: menu-entry-shortcut-separator, start: menu-entry-start, surface: menu-entry-surface"
195
+ value=${value}
196
+ ?disabled=${disabled}
197
+ ?exclusive=${sorted !== undefined}
198
+ .checked=${sorted ?? false}
199
+ >${label}</${item}>`;
200
+ const groups = [
201
+ column.canSort
202
+ ? html `${row('ascending', this.sortAscendingLabel, false, column.sorted === 'ascending')}${row('descending', this.sortDescendingLabel, false, column.sorted === 'descending')}`
203
+ : nothing,
204
+ column.movable
205
+ ? html `${row('left', this.moveLeftLabel, shown[at + left] === undefined)}${row('right', this.moveRightLabel, shown[at - left] === undefined)}`
206
+ : nothing,
207
+ html `${row('hide', this.hideColumnLabel, shown.length <= 1)}${this.restorable.length > 0 ? row('restore', this.showColumnsLabel) : nothing}`,
208
+ ].filter((group) => group !== nothing);
209
+ return html `<${menu}
210
+ exportparts="back: menu-back, back-mark: menu-back-mark, empty: menu-empty, header: menu-header,
211
+ live: menu-live, loading: menu-loading, search-field: menu-search-field, surface: menu-surface,
212
+ title: menu-title"
213
+ @helix-before-check=${this.keepMenuCheck}
214
+ @helix-before-close=${this.stopMenuEvent}
215
+ @helix-before-open=${this.stopMenuEvent}
216
+ @helix-before-select=${this.stopMenuEvent}
217
+ @helix-select=${(event) => {
218
+ event.stopPropagation();
219
+ this.chooseInColumnMenu(column, event.detail.value);
220
+ }}
221
+ >
222
+ ${button}${groups.map((group, index) => html `${index > 0
223
+ ? html `<${divider} part="menu-divider" exportparts="content: menu-divider-content, line: menu-divider-line"></${divider}>`
224
+ : nothing}${group}`)}
225
+ </${menu}>`;
226
+ };
227
+ this.stopMenuEvent = (event) => {
228
+ event.stopPropagation();
229
+ };
230
+ this.keepMenuCheck = (event) => {
231
+ event.preventDefault();
232
+ event.stopPropagation();
233
+ };
173
234
  this.handleSlotChange = () => {
174
235
  this.requestUpdate();
175
236
  };
@@ -266,7 +327,7 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
266
327
  this.chooseOnly(place.row);
267
328
  };
268
329
  this.handleHeadClick = (event) => {
269
- if (this.partIn(event, 'grip') || this.partIn(event, 'resizer')) {
330
+ if (this.partIn(event, 'grip') || this.partIn(event, 'resizer') || this.inColumnMenu(event)) {
270
331
  return;
271
332
  }
272
333
  const column = this.columnFrom(event);
@@ -296,7 +357,12 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
296
357
  }
297
358
  };
298
359
  this.handleGridKey = (event) => {
299
- if (event.defaultPrevented || event.altKey) {
360
+ if (event.defaultPrevented ||
361
+ (this.inColumnMenu(event) && !this.partIn(event, 'menu-button'))) {
362
+ return;
363
+ }
364
+ if (event.altKey) {
365
+ this.openColumnMenuFrom(event);
300
366
  return;
301
367
  }
302
368
  const place = this.placeOf(event);
@@ -1094,6 +1160,7 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
1094
1160
  for (const column of this.tableColumns) {
1095
1161
  whenHydrated(column, () => {
1096
1162
  column.movable = this.reorderable && column.field !== '';
1163
+ column.menuDrawer = column.field === '' ? undefined : this.drawColumnMenu;
1097
1164
  column.gridCell = true;
1098
1165
  column.tabStop = shown.indexOf(column) + offset === stop;
1099
1166
  column.sorted =
@@ -1106,9 +1173,87 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
1106
1173
  android && column.sorted
1107
1174
  ? this.sortSaid(column.textContent?.trim() || column.field, column.sorted)
1108
1175
  : '';
1176
+ column.menuFacts = column.menu ? this.columnMenuFacts(column) : '';
1109
1177
  });
1110
1178
  }
1111
1179
  }
1180
+ get restorable() {
1181
+ return this.tableColumns
1182
+ .filter((column) => !column.shown && this.hiddenByTable.has(column.field))
1183
+ .map((column) => column.field);
1184
+ }
1185
+ columnMenuFacts(column) {
1186
+ return [
1187
+ this.shownFields.indexOf(column.field),
1188
+ this.shownFields.length,
1189
+ this.restorable.length,
1190
+ inlineStep(this, 'ArrowLeft'),
1191
+ this.sortAscendingLabel,
1192
+ this.sortDescendingLabel,
1193
+ this.moveLeftLabel,
1194
+ this.moveRightLabel,
1195
+ this.hideColumnLabel,
1196
+ this.showColumnsLabel,
1197
+ ].join('|');
1198
+ }
1199
+ chooseInColumnMenu(column, value) {
1200
+ queueMicrotask(() => {
1201
+ const left = inlineStep(this, 'ArrowLeft');
1202
+ if (value === 'ascending' || value === 'descending') {
1203
+ const next = format({
1204
+ direction: value === 'ascending' ? 'asc' : 'desc',
1205
+ field: column.field,
1206
+ });
1207
+ if (next !== this.sort) {
1208
+ this.requestSort(next);
1209
+ }
1210
+ }
1211
+ else if (value === 'left' || value === 'right') {
1212
+ this.moveColumn(column.field, value === 'left' ? left : -left);
1213
+ }
1214
+ else if (value === 'restore') {
1215
+ for (const field of this.restorable) {
1216
+ this.showColumn(field);
1217
+ }
1218
+ }
1219
+ else if (value === 'hide') {
1220
+ const at = this.shownColumns.indexOf(column);
1221
+ if (this.hideColumn(column.field)) {
1222
+ this.moveTo(-1, this.lead + Math.min(at, this.shownColumns.length - 1));
1223
+ }
1224
+ }
1225
+ });
1226
+ }
1227
+ hideColumn(field) {
1228
+ return this.requestHidden(field, true);
1229
+ }
1230
+ showColumn(field) {
1231
+ return this.requestHidden(field, false);
1232
+ }
1233
+ requestHidden(field, hidden) {
1234
+ const column = this.tableColumns.find((one) => one.field === field);
1235
+ if (!column || field === '' || column.shown !== hidden) {
1236
+ return false;
1237
+ }
1238
+ if (hidden && this.shownFields.length <= 1) {
1239
+ return false;
1240
+ }
1241
+ return requestChange({
1242
+ apply: (value) => {
1243
+ column.hidden = value;
1244
+ if (value) {
1245
+ this.hiddenByTable.add(field);
1246
+ }
1247
+ else {
1248
+ this.hiddenByTable.delete(field);
1249
+ }
1250
+ this.dispatchEvent(new HelixColumnToggleEvent({ field, hidden: value }));
1251
+ },
1252
+ event: (value) => new HelixBeforeColumnToggleEvent({ field, hidden: value }),
1253
+ host: this,
1254
+ next: hidden,
1255
+ });
1256
+ }
1112
1257
  renderCell(column, row, picked, place, stop, header, id) {
1113
1258
  const edge = edgeOf(column.textAlign);
1114
1259
  const placed = header && this.childField ? this.tree().placed.get(row) : undefined;
@@ -1556,6 +1701,17 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
1556
1701
  }
1557
1702
  return false;
1558
1703
  }
1704
+ inColumnMenu(event) {
1705
+ return (this.columnFrom(event) !== undefined &&
1706
+ event.composedPath().some((node) => node instanceof HelixHeadlessMenu));
1707
+ }
1708
+ openColumnMenuFrom(event) {
1709
+ const column = event.key === 'ArrowDown' ? this.columnFrom(event) : undefined;
1710
+ if (column?.drawsMenu) {
1711
+ event.preventDefault();
1712
+ column.openMenu();
1713
+ }
1714
+ }
1559
1715
  activate(event, place) {
1560
1716
  const offset = this.lead;
1561
1717
  const heading = place.row === -1 ? this.shownColumns[place.column - offset] : undefined;
@@ -1694,6 +1850,10 @@ __decorate([
1694
1850
  words,
1695
1851
  property({ attribute: 'gap-label' })
1696
1852
  ], HelixHeadlessDataTable.prototype, "gapLabel", void 0);
1853
+ __decorate([
1854
+ words,
1855
+ property({ attribute: 'hide-column-label' })
1856
+ ], HelixHeadlessDataTable.prototype, "hideColumnLabel", void 0);
1697
1857
  __decorate([
1698
1858
  property()
1699
1859
  ], HelixHeadlessDataTable.prototype, "label", void 0);
@@ -1714,6 +1874,14 @@ __decorate([
1714
1874
  words,
1715
1875
  property({ attribute: 'moved-label' })
1716
1876
  ], HelixHeadlessDataTable.prototype, "movedLabel", void 0);
1877
+ __decorate([
1878
+ words,
1879
+ property({ attribute: 'move-left-label' })
1880
+ ], HelixHeadlessDataTable.prototype, "moveLeftLabel", void 0);
1881
+ __decorate([
1882
+ words,
1883
+ property({ attribute: 'move-right-label' })
1884
+ ], HelixHeadlessDataTable.prototype, "moveRightLabel", void 0);
1717
1885
  __decorate([
1718
1886
  words,
1719
1887
  property({ attribute: 'move-row-label' })
@@ -1800,9 +1968,21 @@ __decorate([
1800
1968
  words,
1801
1969
  property({ attribute: 'select-row-label' })
1802
1970
  ], HelixHeadlessDataTable.prototype, "selectRowLabel", void 0);
1971
+ __decorate([
1972
+ words,
1973
+ property({ attribute: 'show-columns-label' })
1974
+ ], HelixHeadlessDataTable.prototype, "showColumnsLabel", void 0);
1803
1975
  __decorate([
1804
1976
  property()
1805
1977
  ], HelixHeadlessDataTable.prototype, "sort", null);
1978
+ __decorate([
1979
+ words,
1980
+ property({ attribute: 'sort-ascending-label' })
1981
+ ], HelixHeadlessDataTable.prototype, "sortAscendingLabel", void 0);
1982
+ __decorate([
1983
+ words,
1984
+ property({ attribute: 'sort-descending-label' })
1985
+ ], HelixHeadlessDataTable.prototype, "sortDescendingLabel", void 0);
1806
1986
  __decorate([
1807
1987
  words,
1808
1988
  property({ attribute: 'sorted-label' })
@@ -103,12 +103,14 @@ registerElementEvents({
103
103
  ],
104
104
  'helix-headless-copy-button': ['helix-copy', 'helix-copy-error', 'helix-press'],
105
105
  'helix-headless-data-table': [
106
+ 'helix-before-column-toggle',
106
107
  'helix-before-filter',
107
108
  'helix-before-follow',
108
109
  'helix-before-navigate',
109
110
  'helix-before-reorder',
110
111
  'helix-before-select',
111
112
  'helix-before-sort',
113
+ 'helix-column-toggle',
112
114
  'helix-expand',
113
115
  'helix-filter',
114
116
  'helix-follow',
@@ -3,7 +3,7 @@ import { AriaForwardController, CHART_PLOT, customElement, drawTooltip, formatWo
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, TICK_SIZE, turned, } from '../../spatial-scatter-chart/view.js';
6
+ import { boundsOf, boxView, DragTurn, drawBox, SPATIAL_VIEW, TICK_SIZE, turned, wordsFitted, } from '../../spatial-scatter-chart/view.js';
7
7
  import { barsDrawn, barsOf, climbing, floorOf, grownAt, growTime, heightened, namesOf, paintOrder, rowFits, tallestTimes, widthOf, } from '../bars.js';
8
8
  const barSizeOf = (style) => {
9
9
  const size = Number.parseFloat(style.columnGap);
@@ -334,7 +334,7 @@ let HelixHeadlessSpatialBarChart = class HelixHeadlessSpatialBarChart extends Mi
334
334
  const layout = this.layout ?? 'grid';
335
335
  const lined = layout === 'grid' ? undefined : layout;
336
336
  const bare = this.noBox || lined !== undefined;
337
- const view = boxView(read.axes, this.camera, [], bare);
337
+ const view = wordsFitted(this.room, (words) => boxView(read.axes, this.camera, [], bare, words));
338
338
  const room = this.room;
339
339
  const sized = lined && room?.bar ? this.sized(read, view.projection, lined, room) : undefined;
340
340
  const field = sized?.field ?? read;
@@ -358,11 +358,10 @@ let HelixHeadlessSpatialBarChart = class HelixHeadlessSpatialBarChart extends Mi
358
358
  const base = bare
359
359
  ? boundsOf([...full.flatMap((bar) => bar.corners), ...floor.flatMap((one) => one.corners)], 0.1)
360
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;
361
+ const pixelOf = (frame) => {
362
+ const fitted = room && 1 / Math.min(room.width / frame.width, room.height / frame.height);
363
+ return sized ? Math.max(sized.unit, fitted ?? 0) : fitted;
364
+ };
366
365
  const sizeOf = (frame) => {
367
366
  const pixel = pixelOf(frame);
368
367
  return room && pixel !== undefined ? room.font * pixel : TICK_SIZE;
@@ -393,8 +392,9 @@ let HelixHeadlessSpatialBarChart = class HelixHeadlessSpatialBarChart extends Mi
393
392
  });
394
393
  return { frame: around(base, extents), names, values };
395
394
  };
396
- const first = wordsAt(sizeOf(base));
397
- const size = sizeOf(first.frame);
395
+ let size = sizeOf(wordsAt(sizeOf(base)).frame);
396
+ size = sizeOf(wordsAt(size).frame);
397
+ size = sizeOf(wordsAt(size).frame);
398
398
  const { frame: fits, names, values } = wordsAt(size);
399
399
  const wide = sized && room ? room.width * sized.unit : 0;
400
400
  const high = sized && room ? room.height * sized.unit : 0;
@@ -143,6 +143,7 @@ export declare class HelixHeadlessSpatialRibbonChart extends HelixHeadlessSpatia
143
143
  /** Where the chart's own drawing is seen from, which a drag or Shift with an arrow turns. */
144
144
  private camera;
145
145
  /** A drag across the chart's own drawing, which turns its view. */
146
+ private readonly plotRoom;
146
147
  private readonly drag;
147
148
  /** Where the renderer draws the reading, which the tooltip points at. */
148
149
  private place;
@@ -3,7 +3,7 @@ import { AriaForwardController, CHART_PLOT, customElement, drawTooltip, formatWo
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, nearestTo, SPATIAL_VIEW, turned, } from '../../spatial-scatter-chart/view.js';
6
+ import { boundsOf, boxView, DragTurn, drawBox, nearestTo, PlotRoomWatch, SPATIAL_VIEW, turned, wordsFitted, } from '../../spatial-scatter-chart/view.js';
7
7
  import { ribbonsDrawn, ribbonsOf } from '../ribbons.js';
8
8
  const TURNS = {
9
9
  ArrowDown: { across: 0, up: -1 },
@@ -21,6 +21,7 @@ let HelixHeadlessSpatialRibbonChart = class HelixHeadlessSpatialRibbonChart exte
21
21
  this.summaryLabel = '{count, plural, one {# reading} other {# readings}} in {series, plural, one {# series} other {# series}} over {places, plural, one {# place} other {# places}}, from {low} to {high}.';
22
22
  this.rendered = false;
23
23
  this.camera = SPATIAL_VIEW;
24
+ this.plotRoom = new PlotRoomWatch(this);
24
25
  this.drag = new DragTurn((next) => {
25
26
  this.camera = next(this.camera);
26
27
  }, () => this.requestUpdate());
@@ -153,7 +154,7 @@ let HelixHeadlessSpatialRibbonChart = class HelixHeadlessSpatialRibbonChart exte
153
154
  ${readable ? this.renderSummary(field) : this.renderStatus()}${this.renderSonify()}${this.renderTable()}`;
154
155
  }
155
156
  renderFallback(field, at) {
156
- const view = boxView(field.axes, this.camera, [], this.noBox);
157
+ const view = wordsFitted(this.plotRoom.room, (words) => boxView(field.axes, this.camera, [], this.noBox, words));
157
158
  const ribbons = ribbonsDrawn(field, view.projection).sort((one, other) => one.depth - other.depth);
158
159
  const places = ribbons.flatMap((ribbon) => ribbon.readings);
159
160
  this.placed = places;
@@ -141,6 +141,7 @@ export declare class HelixHeadlessSpatialScatterChart extends HelixHeadlessSpati
141
141
  /** Where the chart's own drawing is seen from, which a drag or Shift with an arrow turns. */
142
142
  private camera;
143
143
  /** A drag across the chart's own drawing, which turns its view. */
144
+ private readonly plotRoom;
144
145
  private readonly drag;
145
146
  /** Each point where the chart's own drawing put it, which the pointer finds. */
146
147
  private placed;