flexdesk 0.11.1 → 0.12.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.
package/README.md CHANGED
@@ -885,6 +885,39 @@ list.setColumnVisible(3, true);
885
885
  A column sorted on and then hidden keeps the order; its arrow comes back with
886
886
  the column.
887
887
 
888
+ ### DataTable: a select mode (0.12.0)
889
+
890
+ A list whose click opens a row can still let people pick many rows: a toggle
891
+ in the table's own header turns on a select mode, with a checkbox per row and
892
+ a click that ticks instead of opening. A table that sets none of these options
893
+ draws and behaves as in 0.11.
894
+
895
+ ```js
896
+ const list = DataTable.mount(host, {
897
+ headers, rows,
898
+ getRowKey: (row) => row[0], // keeps the ticks across new rows
899
+ onRowActivate: (i, row) => open(row), // not called while selecting
900
+ selectToggle: true,
901
+ onSelectModeChange: (on) => bar.hidden = !on,
902
+ onSelectionChange: (ticked) => bar.count = ticked.length,
903
+ });
904
+ list.setSelectMode(true);
905
+ ```
906
+
907
+ | Option | What it does |
908
+ |---|---|
909
+ | `selectToggle: true` | Draws a narrow leading column whose header holds a toggle button. While the mode is on, every row shows a checkbox and the header a select-all box. Needs a multi-row `selectable` (on `false` or `'single'` it is ignored, with a console warning). |
910
+ | `selectMode: true` | Starts in the mode. Draws the column even without `selectToggle`; the consumer then leaves the mode with `setSelectMode(false)`. |
911
+ | `setSelectMode(on)` / `isSelectMode()` | Turns the mode on or off from code (returns whether it changed), and says whether it is on. The selection is kept either way. |
912
+ | `onSelectModeChange(on)` | Called after every change of the mode: the toggle, Escape, `setSelectMode`. |
913
+ | In the mode | A plain click or Space ticks or unticks one row; Shift-click adds the range from the last row ticked. The arrows move a cursor without changing the ticks (Shift ticks the rows passed). A right-click keeps the ticks. Escape clears them, and a second Escape leaves the mode. Nothing opens a row: `onRowActivate` is not called (`onRowClick` still is, so a consumer that opens rows from it checks `isSelectMode()`). New rows (`setData`) keep the ticks by `getRowKey`. |
914
+ | Select-all | Ticks every row the filters let through, on every page; when all are ticked it clears them. It shows checked, mixed or clear. |
915
+
916
+ The select column is not a data column: it is not in a copy, a CSV,
917
+ `ctx.colIdx` or `hiddenColumns`, and it cannot be resized. It is drawn for the
918
+ table's whole life, so remembered widths stay on their columns when the mode
919
+ flips, and widths saved by a table without it land on the same columns.
920
+
888
921
  ### Scrollbars
889
922
 
890
923
  `installAutoScrollbars(root)` (from `@flexdesk/widgets`) replaces the browser's
package/css/flexdesk.css CHANGED
@@ -706,6 +706,82 @@ body.twm-dt-col-resizing {
706
706
  .twm-preview-table tbody tr.twm-dt-row--active > td:first-child {
707
707
  box-shadow: inset 2px 0 0 0 var(--accent-bright, #007acc);
708
708
  }
709
+ /* STORY-0016 `selectToggle` / `selectMode` — the select column. Narrow, never
710
+ * the time column (it is the first child, so the time rules reach it: (0,3,2)
711
+ * here beats them), and the time styling moves to the first column after it,
712
+ * so a table looks as it did before the column was added. Its boxes show only
713
+ * in select mode; the header's toggle always. */
714
+ .twm-preview-table.twm-dt--select tr > .twm-dt-select-cell {
715
+ width: 32px;
716
+ min-width: 0;
717
+ max-width: none;
718
+ padding-left: 6px;
719
+ padding-right: 2px;
720
+ text-align: left;
721
+ color: inherit;
722
+ font-weight: inherit;
723
+ white-space: nowrap;
724
+ }
725
+ .twm-preview-table.twm-dt--select:not(.twm-dt--first-plain) tr > .twm-dt-select-cell + th,
726
+ .twm-preview-table.twm-dt--select:not(.twm-dt--first-plain) tr > .twm-dt-select-cell + td {
727
+ width: 120px;
728
+ min-width: 100px;
729
+ font-weight: 500;
730
+ color: #bbb;
731
+ }
732
+ .twm-preview-table.twm-dt--select:not(.twm-dt--first-plain) tr > th.twm-dt-select-cell + th {
733
+ font-weight: 600;
734
+ color: #aaa;
735
+ }
736
+ .twm-preview-table.twm-dt--select td.twm-dt-select-cell .twm-dt-select-box {
737
+ display: none;
738
+ margin: 0;
739
+ vertical-align: middle;
740
+ cursor: pointer;
741
+ }
742
+ .twm-preview-table.twm-dt--selecting td.twm-dt-select-cell .twm-dt-select-box {
743
+ display: inline-block;
744
+ }
745
+ .twm-preview-table.twm-dt--select th.twm-dt-select-cell .twm-dt-select-all {
746
+ margin: 0 4px 0 0;
747
+ vertical-align: middle;
748
+ cursor: pointer;
749
+ }
750
+ .twm-preview-table.twm-dt--select th.twm-dt-select-cell .twm-dt-select-toggle {
751
+ display: inline-flex;
752
+ align-items: center;
753
+ justify-content: center;
754
+ width: 20px;
755
+ height: 20px;
756
+ margin: -3px 0;
757
+ padding: 0;
758
+ border: 0;
759
+ border-radius: 3px;
760
+ background: transparent;
761
+ color: inherit;
762
+ font-size: 16px;
763
+ line-height: 1;
764
+ vertical-align: middle;
765
+ cursor: pointer;
766
+ opacity: 0.7;
767
+ }
768
+ .twm-preview-table.twm-dt--select th.twm-dt-select-cell .twm-dt-select-toggle:hover,
769
+ .twm-preview-table.twm-dt--select th.twm-dt-select-cell .twm-dt-select-toggle[aria-pressed="true"] {
770
+ opacity: 1;
771
+ background: rgba(255, 255, 255, 0.08);
772
+ }
773
+ .twm-preview-table.twm-dt--select th.twm-dt-select-cell .twm-dt-select-toggle:focus-visible {
774
+ opacity: 1;
775
+ outline: 1px solid var(--accent-bright, #007acc);
776
+ }
777
+ /* In the mode a click ticks: a pointer on every row, and the keyboard's row
778
+ * (the arrows move it, Space ticks it) drawn as an outline. */
779
+ .twm-preview-table.twm-dt--selecting tbody tr:not(.twm-dt-spacer):not(.data-table__empty-row) {
780
+ cursor: pointer;
781
+ }
782
+ .twm-preview-table.twm-dt--selecting tbody tr.twm-dt-row--cursor > td {
783
+ box-shadow: inset 0 1px 0 0 var(--accent-bright, #007acc), inset 0 -1px 0 0 var(--accent-bright, #007acc);
784
+ }
709
785
  /* `rowIcon` — drawn from `data-twm-icon` so the icon's NAME is not cell
710
786
  * text (not in a copy, not in the clipped-cell tooltip). The span carries the
711
787
  * icon font's class; the pseudo-element inherits its font and ligatures. Top
package/dist/charts.js CHANGED
@@ -39,10 +39,10 @@ import {
39
39
  setPlotlySource,
40
40
  uid,
41
41
  updateChart
42
- } from "./chunk-EHHTKD7B.js";
42
+ } from "./chunk-2LEJQD4H.js";
43
43
  import {
44
44
  createRafResizeObserver
45
- } from "./chunk-6CGH73BC.js";
45
+ } from "./chunk-M2L4XW2U.js";
46
46
  import {
47
47
  ManagedWindow
48
48
  } from "./chunk-6JSOVNID.js";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  DataTable,
3
3
  createRafResizeObserver
4
- } from "./chunk-6CGH73BC.js";
4
+ } from "./chunk-M2L4XW2U.js";
5
5
  import {
6
6
  ManagedWindow
7
7
  } from "./chunk-6JSOVNID.js";
@@ -2370,4 +2370,4 @@ export {
2370
2370
  openPlotPopoutWindow,
2371
2371
  openRawTracesWindow
2372
2372
  };
2373
- //# sourceMappingURL=chunk-EHHTKD7B.js.map
2373
+ //# sourceMappingURL=chunk-2LEJQD4H.js.map
@@ -763,8 +763,21 @@ var DataTable = class {
763
763
  hiddenColumns: null,
764
764
  columnChooser: false,
765
765
  onColumnsChange: null,
766
+ // STORY-0016. Off — no select column, as in 0.11 — until set.
767
+ selectToggle: false,
768
+ selectMode: false,
769
+ onSelectModeChange: null,
766
770
  ...config
767
771
  };
772
+ {
773
+ const sel = this.config.selectable;
774
+ const asked = !!this.config.selectToggle || !!this.config.selectMode;
775
+ this._gutter = asked && !!sel && sel !== "single";
776
+ if (asked && !this._gutter) {
777
+ console.warn("[DataTable] selectToggle/selectMode need a multi-row `selectable`; ignored.");
778
+ }
779
+ this._selecting = this._gutter && !!this.config.selectMode;
780
+ }
768
781
  this._expanded = new Set((this.config.expandedRows || []).map((k) => String(k)));
769
782
  this._detailHeights = /* @__PURE__ */ new Map();
770
783
  this._source = null;
@@ -879,8 +892,10 @@ var DataTable = class {
879
892
  }
880
893
  if (blob.colWidths && typeof blob.colWidths === "object") {
881
894
  this._colWidths = {};
895
+ const shift = this._lead().select - (Number(blob.colWidthsLead) || 0);
882
896
  for (const [k, v] of Object.entries(blob.colWidths)) {
883
- this._colWidths[Number(k)] = v;
897
+ const i = Number(k) + shift;
898
+ if (i >= this._lead().select) this._colWidths[i] = v;
884
899
  }
885
900
  this._colWidthsSig = this._colSig();
886
901
  applied = true;
@@ -906,6 +921,9 @@ var DataTable = class {
906
921
  sortAscending: this._state.sortAscending,
907
922
  filters: [...this._state.filters.entries()],
908
923
  colWidths: { ...this._colWidths },
924
+ // STORY-0016. Where the widths start, so a blob read by a table
925
+ // with or without the select column lands on the same columns.
926
+ ...this._gutter ? { colWidthsLead: 1 } : {},
909
927
  // STORY-0010. Only for a table that asked: one that did not writes
910
928
  // the 0.9.1 blob, key for key.
911
929
  ...this.config.persistPage ? { offset: this._state.offset } : {},
@@ -935,18 +953,24 @@ var DataTable = class {
935
953
  /** The cells a drawn row has: the shown columns, and the row number. */
936
954
  _domColCount() {
937
955
  const n = Math.max(0, (this.config.headers?.length || 0) - this._hidden.size);
938
- return n + (this.config.showRowNumbers ? 1 : 0);
956
+ return n + this._lead().all;
957
+ }
958
+ /** STORY-0016. The drawn columns in front of the data: the select column
959
+ * (`select`, 0 or 1, always first) and the row number. */
960
+ _lead() {
961
+ const select = this._gutter ? 1 : 0;
962
+ return { select, all: select + (this.config.showRowNumbers ? 1 : 0) };
939
963
  }
940
964
  /** A data column's drawn index (row number included), or -1 when hidden. */
941
965
  _domIdxOf(colIdx) {
942
966
  if (this._isHidden(colIdx)) return -1;
943
- let dom = this.config.showRowNumbers ? 1 : 0;
967
+ let dom = this._lead().all;
944
968
  for (const h of this._hidden) if (h < colIdx) dom--;
945
969
  return dom + colIdx;
946
970
  }
947
971
  /** A drawn cell's data column, or null for the row number. */
948
972
  _dataIdxOfDom(domIdx) {
949
- let i = domIdx - (this.config.showRowNumbers ? 1 : 0);
973
+ let i = domIdx - this._lead().all;
950
974
  if (i < 0) return null;
951
975
  const n = this.config.headers?.length || 0;
952
976
  for (let c = 0; c < n; c++) {
@@ -993,7 +1017,7 @@ var DataTable = class {
993
1017
  this._setHidden([...next]);
994
1018
  if (kept) {
995
1019
  const widths = {};
996
- if (this.config.showRowNumbers && old[0] != null) widths[0] = old[0];
1020
+ for (let i = 0; i < this._lead().all; i++) if (old[i] != null) widths[i] = old[i];
997
1021
  for (let c = 0; c < n; c++) {
998
1022
  const from = domBefore[c];
999
1023
  const to = this._domIdxOf(c);
@@ -1039,6 +1063,7 @@ var DataTable = class {
1039
1063
  * @param {Object} updates - Partial config updates (headers, rows, etc.)
1040
1064
  */
1041
1065
  setData(updates) {
1066
+ const keepTicks = updates.rows && this._selecting && typeof this.config.getRowKey === "function" && this._state.selected.size ? new Set(this.getSelection().map((i) => this._rowKey(this.config.rows[i], i)).filter((k) => k !== null)) : null;
1042
1067
  if (updates.headers && this._state.filters.size > 0) {
1043
1068
  const oldHeaders = this.config.headers;
1044
1069
  const newHeaders = updates.headers;
@@ -1087,9 +1112,15 @@ var DataTable = class {
1087
1112
  if (Object.prototype.hasOwnProperty.call(updates, "columnTypes") && !updates.rows) {
1088
1113
  this._columnTypes = this._detectColumnTypes();
1089
1114
  }
1115
+ if (keepTicks) {
1116
+ (this.config.rows || []).forEach((r, i) => {
1117
+ if (keepTicks.has(this._rowKey(r, i))) this._state.selected.add(i);
1118
+ });
1119
+ }
1090
1120
  this._processedRows = null;
1091
1121
  this._processedIndexMap = null;
1092
1122
  this.render();
1123
+ if (keepTicks) this._notifySelectionChange();
1093
1124
  }
1094
1125
  /**
1095
1126
  * Show / hide a translucent loading overlay over the table body.
@@ -1414,6 +1445,7 @@ var DataTable = class {
1414
1445
  headerTable.className = this._tableClassName();
1415
1446
  const thead = document.createElement("thead");
1416
1447
  const tr = document.createElement("tr");
1448
+ if (this._gutter) tr.appendChild(this._buildSelectHead());
1417
1449
  if (showRowNumbers) {
1418
1450
  const th = document.createElement("th");
1419
1451
  th.className = "num";
@@ -1922,6 +1954,11 @@ var DataTable = class {
1922
1954
  const { showRowNumbers } = this.config;
1923
1955
  const tr = document.createElement("tr");
1924
1956
  tr.className = "twm-data-table__filter-row";
1957
+ if (this._gutter) {
1958
+ const th = document.createElement("th");
1959
+ th.className = "data-table__filter-cell data-table__filter-cell--empty twm-dt-select-cell";
1960
+ tr.appendChild(th);
1961
+ }
1925
1962
  if (showRowNumbers) {
1926
1963
  const th = document.createElement("th");
1927
1964
  th.className = "data-table__filter-cell data-table__filter-cell--empty";
@@ -2268,7 +2305,7 @@ var DataTable = class {
2268
2305
  if (!row) return null;
2269
2306
  const cells = Array.from(row.children);
2270
2307
  const idx = cells.indexOf(cell);
2271
- return this._hidden.size ? this._dataIdxOfDom(idx) : this.config.showRowNumbers ? idx - 1 : idx;
2308
+ return this._dataIdxOfDom(idx);
2272
2309
  }
2273
2310
  _restoreFilterFocus(colIdx) {
2274
2311
  const filterRow = this._wrapperEl?.querySelector(".twm-data-table__filter-row");
@@ -2740,6 +2777,7 @@ var DataTable = class {
2740
2777
  table.tabIndex = 0;
2741
2778
  const thead = document.createElement("thead");
2742
2779
  const headerRow = document.createElement("tr");
2780
+ if (this._gutter) headerRow.appendChild(this._buildSelectHead());
2743
2781
  if (showRowNumbers) {
2744
2782
  const th = document.createElement("th");
2745
2783
  th.className = "num";
@@ -2898,7 +2936,7 @@ var DataTable = class {
2898
2936
  const colgroup = document.createElement("colgroup");
2899
2937
  for (let i = 0; i < n; i++) {
2900
2938
  const col = document.createElement("col");
2901
- const w = this._colWidths?.[i];
2939
+ const w = i < this._lead().select ? this._selectColWidth() : this._colWidths?.[i];
2902
2940
  if (w != null && this._colWidthsSig === this._colSig()) col.style.width = `${w}px`;
2903
2941
  colgroup.appendChild(col);
2904
2942
  }
@@ -3306,6 +3344,18 @@ var DataTable = class {
3306
3344
  * update than it did on load. */
3307
3345
  _fillRowCells(tr, row, globalIdx, showRowNumbers) {
3308
3346
  tr.replaceChildren();
3347
+ if (this._gutter) {
3348
+ const td = document.createElement("td");
3349
+ td.className = "twm-dt-select-cell";
3350
+ const box = document.createElement("input");
3351
+ box.type = "checkbox";
3352
+ box.className = "twm-dt-select-box";
3353
+ box.tabIndex = -1;
3354
+ box.checked = this._state.selected.has(globalIdx);
3355
+ box.setAttribute("aria-label", "Select row");
3356
+ td.appendChild(box);
3357
+ tr.appendChild(td);
3358
+ }
3309
3359
  if (showRowNumbers) {
3310
3360
  const td = document.createElement("td");
3311
3361
  td.className = "num";
@@ -3418,7 +3468,7 @@ var DataTable = class {
3418
3468
  minWidth: td.style.minWidth,
3419
3469
  maxWidth: td.style.maxWidth
3420
3470
  })) : null;
3421
- const showRowNumbers = this.config.showRowNumbers !== false && tr.firstElementChild?.classList.contains("num");
3471
+ const showRowNumbers = this.config.showRowNumbers !== false && !!tr.querySelector(":scope > td.num");
3422
3472
  this._fillRowCells(tr, row, index, showRowNumbers);
3423
3473
  if (widths) {
3424
3474
  widths.forEach((saved, i) => {
@@ -4575,6 +4625,8 @@ var DataTable = class {
4575
4625
  const names = this.config.readonly ? ["twm-preview-table", "twm-preview-table--readonly"] : ["twm-preview-table"];
4576
4626
  if (this.config.firstColumn === "plain") names.push("twm-dt--first-plain");
4577
4627
  if (this._isClickable()) names.push("twm-dt--clickable");
4628
+ if (this._gutter) names.push("twm-dt--select");
4629
+ if (this._selecting) names.push("twm-dt--selecting");
4578
4630
  return names.join(" ");
4579
4631
  }
4580
4632
  /** `clickable` when it is said; otherwise exactly when rows open. */
@@ -4658,6 +4710,7 @@ var DataTable = class {
4658
4710
  const multi = !!sel && sel !== "single";
4659
4711
  const fire = (tr, ev) => {
4660
4712
  if (!this._wrapperEl || typeof this.config.onRowActivate !== "function") return;
4713
+ if (this._selecting) return;
4661
4714
  try {
4662
4715
  this.config.onRowActivate(tr.__rowIndex, tr.__row, ev);
4663
4716
  } catch (err) {
@@ -5018,7 +5071,7 @@ var DataTable = class {
5018
5071
  */
5019
5072
  autoSizeColumn(domIdx, opts = {}) {
5020
5073
  const natural = this._measureNaturalWidths({ sample: true });
5021
- if (!natural || domIdx < 0 || domIdx >= natural.length) return false;
5074
+ if (!natural || domIdx < this._lead().select || domIdx >= natural.length) return false;
5022
5075
  this._colWidths[domIdx] = this._autoWidth(natural[domIdx], opts);
5023
5076
  this._colWidthsSig = this._colSig();
5024
5077
  this._savePersisted();
@@ -5044,7 +5097,7 @@ var DataTable = class {
5044
5097
  const natural = this._measureNaturalWidths({ sample: true });
5045
5098
  if (!natural) return false;
5046
5099
  this._colWidths = {};
5047
- for (let i = 0; i < natural.length; i++) {
5100
+ for (let i = this._lead().select; i < natural.length; i++) {
5048
5101
  this._colWidths[i] = this._autoWidth(natural[i], opts);
5049
5102
  }
5050
5103
  this._colWidthsSig = this._colSig();
@@ -5076,7 +5129,7 @@ var DataTable = class {
5076
5129
  */
5077
5130
  _allColumnsPinned(n) {
5078
5131
  if (!n || this._colWidthsSig !== this._colSig()) return false;
5079
- for (let i = 0; i < n; i++) {
5132
+ for (let i = this._lead().select; i < n; i++) {
5080
5133
  if (this._colWidths[i] == null) return false;
5081
5134
  }
5082
5135
  return true;
@@ -5101,10 +5154,12 @@ var DataTable = class {
5101
5154
  const wrap = this._tableWrapEl;
5102
5155
  const headerWrap = this._headerWrapEl;
5103
5156
  const avail = wrap ? wrap.clientWidth : 0;
5104
- const firstIdx = this.config.showRowNumbers && n > 1 ? 1 : 0;
5157
+ const lead = this._lead().all;
5158
+ const firstIdx = lead && n > lead ? lead : 0;
5105
5159
  const widths = this._fitColumnWidths(natural, avail, {
5106
5160
  firstIdx,
5107
- overrides: this._colWidths,
5161
+ // STORY-0016. The select column is as wide as its controls, always.
5162
+ overrides: this._gutter ? { ...this._colWidths, 0: this._selectColWidth() } : this._colWidths,
5108
5163
  fit: this.config.columnFit,
5109
5164
  floors: this._filterFloors
5110
5165
  });
@@ -5336,6 +5391,7 @@ var DataTable = class {
5336
5391
  if (!headRow) return;
5337
5392
  [...headRow.children].forEach((th, domIdx) => {
5338
5393
  if (th.querySelector(".twm-dt-col-resizer")) return;
5394
+ if (th.classList.contains("twm-dt-select-cell")) return;
5339
5395
  th.classList.add("twm-dt-col");
5340
5396
  const grip = document.createElement("div");
5341
5397
  grip.className = "twm-dt-col-resizer";
@@ -5508,6 +5564,19 @@ var DataTable = class {
5508
5564
  if (!(was && (event.metaKey || event.ctrlKey))) selected.add(idx);
5509
5565
  else DESELECTING_CLICKS.add(event);
5510
5566
  this._state.anchorIndex = selected.size ? idx : null;
5567
+ } else if (this._selecting) {
5568
+ if (event.shiftKey && this._state.anchorIndex != null) {
5569
+ const order = this._processedIndexMap || this.config.rows.map((_, i) => i);
5570
+ const a = order.indexOf(this._state.anchorIndex);
5571
+ const b = order.indexOf(idx);
5572
+ if (a >= 0 && b >= 0) {
5573
+ for (let i = Math.min(a, b); i <= Math.max(a, b); i++) selected.add(order[i]);
5574
+ } else selected.add(idx);
5575
+ } else if (selected.has(idx)) selected.delete(idx);
5576
+ else selected.add(idx);
5577
+ this._state.anchorIndex = idx;
5578
+ this._navCursor = idx;
5579
+ this._markCursor();
5511
5580
  } else if (event.shiftKey && this._state.anchorIndex != null) {
5512
5581
  const start = Math.min(this._state.anchorIndex, idx);
5513
5582
  const end = Math.max(this._state.anchorIndex, idx);
@@ -5559,7 +5628,7 @@ var DataTable = class {
5559
5628
  if (!order.length) return;
5560
5629
  ev.preventDefault();
5561
5630
  const selected = this._state.selected;
5562
- const cur = this._navCursor != null && selected.has(this._navCursor) ? this._navCursor : this._state.anchorIndex;
5631
+ const cur = this._navCursor != null && (this._selecting || selected.has(this._navCursor)) ? this._navCursor : this._state.anchorIndex;
5563
5632
  let pos = order.indexOf(cur);
5564
5633
  if (ev.key === "Home") pos = 0;
5565
5634
  else if (ev.key === "End") pos = order.length - 1;
@@ -5568,6 +5637,20 @@ var DataTable = class {
5568
5637
  const idx = order[pos];
5569
5638
  this._navCursor = idx;
5570
5639
  const anchorPos = order.indexOf(this._state.anchorIndex);
5640
+ if (this._selecting) {
5641
+ if (ev.shiftKey) {
5642
+ const from = order.indexOf(cur);
5643
+ if (from >= 0) {
5644
+ for (let i = Math.min(from, pos); i <= Math.max(from, pos); i++) selected.add(order[i]);
5645
+ } else selected.add(idx);
5646
+ this._updateRowSelection();
5647
+ this._notifySelectionChange();
5648
+ }
5649
+ this._state.anchorIndex = idx;
5650
+ this._markCursor();
5651
+ this.scrollToRow(idx);
5652
+ return;
5653
+ }
5571
5654
  if (ev.shiftKey && selectable !== "single" && anchorPos !== -1) {
5572
5655
  const [a, b] = anchorPos <= pos ? [anchorPos, pos] : [pos, anchorPos];
5573
5656
  selected.clear();
@@ -5584,6 +5667,25 @@ var DataTable = class {
5584
5667
  table.addEventListener("keydown", onNavKey);
5585
5668
  this._disposers.push(() => table.removeEventListener("keydown", onNavKey));
5586
5669
  }
5670
+ if (this._gutter) {
5671
+ const onSpace = (ev) => {
5672
+ if (!this._selecting || ev.key !== " " || ev.ctrlKey || ev.metaKey || ev.altKey) return;
5673
+ if (isRowControl(ev.target, table)) return;
5674
+ const idx = this._navCursor ?? this._state.anchorIndex;
5675
+ if (idx == null || !this.config.rows[idx]) return;
5676
+ ev.preventDefault();
5677
+ const selected = this._state.selected;
5678
+ if (selected.has(idx)) selected.delete(idx);
5679
+ else selected.add(idx);
5680
+ this._state.anchorIndex = idx;
5681
+ this._updateRowSelection();
5682
+ this._notifySelectionChange();
5683
+ };
5684
+ table.addEventListener("keydown", onSpace);
5685
+ this._disposers.push(() => table.removeEventListener("keydown", onSpace));
5686
+ this._paintSelectHead();
5687
+ this._markCursor();
5688
+ }
5587
5689
  if (typeof this.config.onRowActivate === "function") {
5588
5690
  this._installActivation(tbody, table);
5589
5691
  }
@@ -5625,7 +5727,7 @@ var DataTable = class {
5625
5727
  }
5626
5728
  event.preventDefault();
5627
5729
  event.stopPropagation();
5628
- if (idx !== void 0 && !this._state.selected.has(idx)) {
5730
+ if (idx !== void 0 && !this._state.selected.has(idx) && !this._selecting) {
5629
5731
  this._state.selected.clear();
5630
5732
  this._state.selected.add(idx);
5631
5733
  this._state.anchorIndex = idx;
@@ -5650,20 +5752,7 @@ var DataTable = class {
5650
5752
  event.preventDefault();
5651
5753
  } else if (key === "a" && selectable) {
5652
5754
  event.preventDefault();
5653
- this._state.selected.clear();
5654
- const indexMap = this._processedIndexMap;
5655
- if (indexMap) {
5656
- for (const originalIdx of indexMap) {
5657
- this._state.selected.add(originalIdx);
5658
- }
5659
- } else {
5660
- for (let i = 0; i < this.config.rows.length; i++) {
5661
- this._state.selected.add(i);
5662
- }
5663
- }
5664
- this._state.anchorIndex = this.config.rows.length - 1;
5665
- this._updateRowSelection();
5666
- this._notifySelectionChange();
5755
+ this._selectAllShown();
5667
5756
  } else if (key === "c") {
5668
5757
  event.preventDefault();
5669
5758
  this.copyToClipboard(event.shiftKey ? "csv" : "tsv");
@@ -5671,6 +5760,8 @@ var DataTable = class {
5671
5760
  } else if (event.key === "Escape") {
5672
5761
  if (this._state.selected.size) {
5673
5762
  this.clearSelection();
5763
+ } else if (this._selecting && this.config.selectToggle) {
5764
+ this.setSelectMode(false);
5674
5765
  }
5675
5766
  this._hideContextMenu();
5676
5767
  }
@@ -5684,9 +5775,111 @@ var DataTable = class {
5684
5775
  this._tbodyEl.querySelectorAll("tr").forEach((tr) => {
5685
5776
  const idx = tr.__rowIndex;
5686
5777
  if (idx !== void 0) {
5687
- tr.classList.toggle("selected", this._state.selected.has(idx));
5778
+ const on = this._state.selected.has(idx);
5779
+ tr.classList.toggle("selected", on);
5780
+ const box = this._gutter ? tr.querySelector(":scope > td.twm-dt-select-cell > input") : null;
5781
+ if (box) box.checked = on;
5688
5782
  }
5689
5783
  });
5784
+ this._paintSelectHead();
5785
+ }
5786
+ /* ── STORY-0016: select mode ─────────────────────────────────────── */
5787
+ /** Whether the table is in select mode. */
5788
+ isSelectMode() {
5789
+ return !!this._selecting;
5790
+ }
5791
+ /**
5792
+ * Turn select mode on or off. The ticks are kept either way (a selection
5793
+ * is a selection); the consumer clears it if leaving means "never mind".
5794
+ * Does nothing on a table without the select column.
5795
+ *
5796
+ * @param {boolean} on
5797
+ * @returns {boolean} whether the mode changed
5798
+ */
5799
+ setSelectMode(on) {
5800
+ if (!this._gutter || !!on === this._selecting) return false;
5801
+ this._selecting = !!on;
5802
+ this._navCursor = this._selecting ? this._state.anchorIndex : null;
5803
+ if (this._wrapperEl) this.render();
5804
+ try {
5805
+ this.config.onSelectModeChange?.(this._selecting);
5806
+ } catch (err) {
5807
+ console.error("[DataTable] onSelectModeChange threw", err);
5808
+ }
5809
+ return true;
5810
+ }
5811
+ /** The rows the filters let through, on every page, by original index. */
5812
+ _shownIndices() {
5813
+ if (this._isServerSide()) return this.config.rows.map((_, i) => i);
5814
+ this._getProcessedRows();
5815
+ return this._processedIndexMap ? [...this._processedIndexMap] : this.config.rows.map((_, i) => i);
5816
+ }
5817
+ /** Ctrl+A and the select-all box: every row the filters let through. */
5818
+ _selectAllShown() {
5819
+ this._state.selected.clear();
5820
+ for (const i of this._shownIndices()) this._state.selected.add(i);
5821
+ this._state.anchorIndex = this.config.rows.length - 1;
5822
+ this._updateRowSelection();
5823
+ this._notifySelectionChange();
5824
+ }
5825
+ /** The select column's width: its controls, never a drag. */
5826
+ _selectColWidth() {
5827
+ return this._selecting ? this.config.selectToggle ? 56 : 32 : 32;
5828
+ }
5829
+ /** The select column's header: the toggle and, in the mode, select-all. */
5830
+ _buildSelectHead() {
5831
+ const th = document.createElement("th");
5832
+ th.className = "twm-dt-select-cell twm-dt-select-head";
5833
+ if (this._selecting) {
5834
+ const all = document.createElement("input");
5835
+ all.type = "checkbox";
5836
+ all.className = "twm-dt-select-all";
5837
+ all.setAttribute("aria-label", "Select all rows");
5838
+ all.title = "Select all rows";
5839
+ all.addEventListener("click", (ev) => {
5840
+ ev.stopPropagation();
5841
+ const shown = this._shownIndices();
5842
+ const every = shown.length > 0 && shown.every((i) => this._state.selected.has(i));
5843
+ if (every) this.clearSelection();
5844
+ else this._selectAllShown();
5845
+ });
5846
+ th.appendChild(all);
5847
+ }
5848
+ if (this.config.selectToggle) {
5849
+ const btn = document.createElement("button");
5850
+ btn.type = "button";
5851
+ btn.className = "material-symbols-outlined twm-dt-select-toggle";
5852
+ btn.textContent = this._selecting ? "close" : "checklist";
5853
+ btn.setAttribute("aria-pressed", this._selecting ? "true" : "false");
5854
+ const label = this._selecting ? "Stop selecting" : "Select multiple";
5855
+ btn.setAttribute("aria-label", label);
5856
+ btn.title = label;
5857
+ btn.addEventListener("click", (ev) => {
5858
+ ev.stopPropagation();
5859
+ this.setSelectMode(!this._selecting);
5860
+ });
5861
+ th.appendChild(btn);
5862
+ }
5863
+ th.classList.toggle("twm-dt-select-head--on", this._selecting);
5864
+ return th;
5865
+ }
5866
+ /** The select-all box: checked, mixed or clear, as the ticks are. */
5867
+ _paintSelectHead() {
5868
+ if (!this._gutter || !this._wrapperEl) return;
5869
+ const all = this._wrapperEl.querySelector(".twm-dt-select-all");
5870
+ if (!all) return;
5871
+ const shown = this._shownIndices();
5872
+ const n = shown.filter((i) => this._state.selected.has(i)).length;
5873
+ all.checked = n > 0 && n === shown.length;
5874
+ all.indeterminate = n > 0 && n < shown.length;
5875
+ }
5876
+ /** The keyboard's row in select mode, marked so it can be seen. */
5877
+ _markCursor() {
5878
+ if (!this._tbodyEl) return;
5879
+ for (const tr of this._tbodyEl.children) {
5880
+ if (tr.__rowIndex === void 0) continue;
5881
+ tr.classList.toggle("twm-dt-row--cursor", this._selecting && tr.__rowIndex === this._navCursor);
5882
+ }
5690
5883
  }
5691
5884
  _notifySelectionChange() {
5692
5885
  this.config.onSelectionChange?.(this.getSelection());
@@ -5975,4 +6168,4 @@ export {
5975
6168
  resolvePaging,
5976
6169
  DataTable
5977
6170
  };
5978
- //# sourceMappingURL=chunk-6CGH73BC.js.map
6171
+ //# sourceMappingURL=chunk-M2L4XW2U.js.map