@human-kit/svelte-components 1.0.0-alpha.13 → 1.0.0-alpha.14

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.
@@ -8,6 +8,9 @@ const TABLE_CELL_KEY = Symbol('table-cell');
8
8
  export function createTableContext(options = {}) {
9
9
  let selectionMode = options.selectionMode ?? 'none';
10
10
  let selectionBehavior = options.selectionBehavior ?? 'toggle';
11
+ let disabledBehavior = options.disabledBehavior ?? 'all';
12
+ let disallowEmptySelection = options.disallowEmptySelection ?? false;
13
+ let onRowAction = options.onRowAction;
11
14
  let sortDescriptor = options.initialSortDescriptor;
12
15
  let focusedCellKey = null;
13
16
  let focusedRowTarget = null;
@@ -35,6 +38,7 @@ export function createTableContext(options = {}) {
35
38
  let orderedColumnTokensCache = null;
36
39
  let visibleOrderedColumnTokensCache = null;
37
40
  let columnWidthsCache = null;
41
+ let visibleColumnWidthsCache = null;
38
42
  let navigableCellsCache = null;
39
43
  let rowsWithCellsCache = null;
40
44
  const layoutVersion = writable(0);
@@ -44,6 +48,7 @@ export function createTableContext(options = {}) {
44
48
  const widthVersion = writable(0);
45
49
  const resizeVersion = writable(0);
46
50
  const instanceCounters = new Map();
51
+ const selectionUnavailableDescriptionId = createInstanceToken('selection-unavailable');
47
52
  function createInstanceToken(prefix) {
48
53
  const nextCount = (instanceCounters.get(prefix) ?? 0) + 1;
49
54
  instanceCounters.set(prefix, nextCount);
@@ -54,6 +59,7 @@ export function createTableContext(options = {}) {
54
59
  orderedColumnTokensCache = null;
55
60
  visibleOrderedColumnTokensCache = null;
56
61
  columnWidthsCache = null;
62
+ visibleColumnWidthsCache = null;
57
63
  navigableCellsCache = null;
58
64
  rowsWithCellsCache = null;
59
65
  }
@@ -81,6 +87,7 @@ export function createTableContext(options = {}) {
81
87
  }
82
88
  function notifyWidth() {
83
89
  columnWidthsCache = null;
90
+ visibleColumnWidthsCache = null;
84
91
  if (!widthNotifyScheduled) {
85
92
  widthNotifyScheduled = true;
86
93
  queueMicrotask(() => {
@@ -300,12 +307,15 @@ export function createTableContext(options = {}) {
300
307
  return widths;
301
308
  }
302
309
  function getVisibleColumnWidths() {
310
+ if (visibleColumnWidthsCache)
311
+ return visibleColumnWidthsCache;
303
312
  const widths = new Map();
304
313
  for (const [columnId, width] of getColumnWidths()) {
305
314
  if (isColumnHidden(columnId))
306
315
  continue;
307
316
  widths.set(columnId, width);
308
317
  }
318
+ visibleColumnWidthsCache = widths;
309
319
  return widths;
310
320
  }
311
321
  function getMeasuredHeaderWidth(columnToken) {
@@ -444,6 +454,33 @@ export function createTableContext(options = {}) {
444
454
  focusedRowTarget = null;
445
455
  notifyFocus();
446
456
  }
457
+ function reconcileFocusAfterDisabledStateChange() {
458
+ if (focusedCellKey) {
459
+ const focusedCell = cells.get(focusedCellKey);
460
+ if (focusedCell &&
461
+ focusedCell.section === 'body' &&
462
+ isRowDisabled(rows.get(focusedCell.rowToken)?.id, rows.get(focusedCell.rowToken)?.disabled)) {
463
+ moveFocus('down');
464
+ const nextFocusedCell = cells.get(focusedCellKey ?? '');
465
+ if (nextFocusedCell?.section === 'body' &&
466
+ isRowDisabled(rows.get(nextFocusedCell.rowToken)?.id, rows.get(nextFocusedCell.rowToken)?.disabled)) {
467
+ moveFocus('up');
468
+ }
469
+ }
470
+ }
471
+ if (focusedRowTarget) {
472
+ const focusedRow = rows.get(focusedRowTarget.rowToken);
473
+ if (focusedRow && isRowDisabled(focusedRow.id, focusedRow.disabled)) {
474
+ const nextToken = getFocusableBodyRowToken('start');
475
+ if (nextToken) {
476
+ setFocusedRow(nextToken, focusedRowTarget.edge);
477
+ }
478
+ else {
479
+ setFocusedRow(null);
480
+ }
481
+ }
482
+ }
483
+ }
447
484
  function setHiddenColumns(columnIds) {
448
485
  const next = new Set(columnIds ?? []);
449
486
  let changed = next.size !== hiddenColumnIds.size;
@@ -635,19 +672,28 @@ export function createTableContext(options = {}) {
635
672
  const row = rows.get(token);
636
673
  if (!row?.id)
637
674
  continue;
638
- if (disabledKeys.has(row.id) || row.disabled)
675
+ if (isRowSelectionDisabled(row.id, row.disabled))
639
676
  continue;
640
677
  rowIds.push(row.id);
641
678
  }
642
679
  return rowIds;
643
680
  }
644
- function isRowDisabled(id, localDisabled = false) {
681
+ function isRowSelectionDisabled(id, localDisabled = false) {
645
682
  if (localDisabled)
646
683
  return true;
647
684
  if (id === undefined)
648
685
  return false;
649
686
  return disabledKeys.has(id);
650
687
  }
688
+ function isRowDisabled(id, localDisabled = false) {
689
+ return disabledBehavior === 'all' && isRowSelectionDisabled(id, localDisabled);
690
+ }
691
+ function isRowActionDisabled(id, localDisabled = false) {
692
+ return disabledBehavior === 'all' && isRowSelectionDisabled(id, localDisabled);
693
+ }
694
+ function isRowActionable(id, localDisabled = false) {
695
+ return Boolean(onRowAction) && id !== undefined && !isRowActionDisabled(id, localDisabled);
696
+ }
651
697
  function isRowSelected(id) {
652
698
  if (id === undefined)
653
699
  return false;
@@ -919,12 +965,25 @@ export function createTableContext(options = {}) {
919
965
  return true;
920
966
  }
921
967
  function setSelectedKeys(next, anchor) {
968
+ const previousSelectedKeys = new Set(selectedKeys);
922
969
  selectedKeys =
923
970
  selectionMode === 'none'
924
971
  ? new Set()
925
972
  : selectionMode === 'single' && next.size > 1
926
973
  ? new Set([next.values().next().value])
927
974
  : next;
975
+ if (selectionMode !== 'none' && disallowEmptySelection && selectedKeys.size === 0) {
976
+ const fallbackKey = (anchor !== undefined && anchor !== null && !isRowSelectionDisabled(anchor)
977
+ ? anchor
978
+ : previousSelectedKeys.values().next().value) ??
979
+ getFocusedRowId() ??
980
+ getOrderedSelectableRowIds()[0];
981
+ if (fallbackKey !== undefined &&
982
+ fallbackKey !== null &&
983
+ !isRowSelectionDisabled(fallbackKey)) {
984
+ selectedKeys = new Set([fallbackKey]);
985
+ }
986
+ }
928
987
  const fallbackAnchor = selectedKeys.values().next().value ?? null;
929
988
  if (anchor === undefined) {
930
989
  selectionAnchorKey = fallbackAnchor;
@@ -933,18 +992,35 @@ export function createTableContext(options = {}) {
933
992
  selectionAnchorKey = anchor === null || selectedKeys.has(anchor) ? anchor : fallbackAnchor;
934
993
  }
935
994
  function replaceSelectionWithRow(id) {
936
- if (id === undefined || disabledKeys.has(id))
995
+ if (id === undefined || isRowSelectionDisabled(id))
996
+ return;
997
+ const next = new Set([id]);
998
+ if (hasSameSelection(selectedKeys, next)) {
999
+ setSelectedKeys(next, id);
1000
+ notifySelection();
937
1001
  return;
938
- setSelectedKeys(new Set([id]), id);
1002
+ }
1003
+ setSelectedKeys(next, id);
939
1004
  emitSelectionChange();
940
1005
  }
1006
+ function applySelectionChange(next, anchor) {
1007
+ const previousSelection = new Set(selectedKeys);
1008
+ const previousAnchor = selectionAnchorKey;
1009
+ setSelectedKeys(next, anchor);
1010
+ if (!hasSameSelection(previousSelection, selectedKeys)) {
1011
+ emitSelectionChange();
1012
+ return;
1013
+ }
1014
+ if (!hasSameSelection(next, selectedKeys) || previousAnchor !== selectionAnchorKey) {
1015
+ notifySelection();
1016
+ }
1017
+ }
941
1018
  function toggleSelectionForRow(id) {
942
- if (id === undefined || disabledKeys.has(id))
1019
+ if (id === undefined || isRowSelectionDisabled(id))
943
1020
  return;
944
1021
  if (selectionMode === 'single') {
945
1022
  const wasSelected = selectedKeys.has(id);
946
- setSelectedKeys(selectionBehavior === 'toggle' && wasSelected ? new Set() : new Set([id]), selectionBehavior === 'toggle' && wasSelected ? null : id);
947
- emitSelectionChange();
1023
+ applySelectionChange(selectionBehavior === 'toggle' && wasSelected ? new Set() : new Set([id]), selectionBehavior === 'toggle' && wasSelected ? null : id);
948
1024
  return;
949
1025
  }
950
1026
  const next = new Set(selectedKeys);
@@ -954,11 +1030,10 @@ export function createTableContext(options = {}) {
954
1030
  else {
955
1031
  next.add(id);
956
1032
  }
957
- setSelectedKeys(next, id);
958
- emitSelectionChange();
1033
+ applySelectionChange(next, id);
959
1034
  }
960
1035
  function extendSelectionToRow(id, anchorOverride) {
961
- if (id === undefined || disabledKeys.has(id))
1036
+ if (id === undefined || isRowSelectionDisabled(id))
962
1037
  return;
963
1038
  if (selectionMode !== 'multiple') {
964
1039
  replaceSelectionWithRow(id);
@@ -979,11 +1054,18 @@ export function createTableContext(options = {}) {
979
1054
  }
980
1055
  const start = Math.min(anchorIndex, targetIndex);
981
1056
  const end = Math.max(anchorIndex, targetIndex);
982
- setSelectedKeys(new Set(orderedIds.slice(start, end + 1)), anchor);
983
- emitSelectionChange();
1057
+ applySelectionChange(new Set(orderedIds.slice(start, end + 1)), anchor);
984
1058
  }
985
- function pressRow(id, interaction = {}) {
986
- if (selectionMode === 'none' || id === undefined || disabledKeys.has(id))
1059
+ function performRowAction(id) {
1060
+ if (!onRowAction || id === undefined || isRowActionDisabled(id))
1061
+ return;
1062
+ onRowAction(id);
1063
+ }
1064
+ function hasActiveSelection() {
1065
+ return selectedKeys.size > 0;
1066
+ }
1067
+ function pressRowSelection(id, interaction = {}) {
1068
+ if (selectionMode === 'none' || id === undefined || isRowSelectionDisabled(id))
987
1069
  return;
988
1070
  if (selectionBehavior === 'replace' && selectionMode === 'multiple') {
989
1071
  if (interaction.shiftKey) {
@@ -999,6 +1081,55 @@ export function createTableContext(options = {}) {
999
1081
  }
1000
1082
  toggleSelectionForRow(id);
1001
1083
  }
1084
+ function pressRow(id, source, interaction = {}, localDisabled = false) {
1085
+ if (id === undefined)
1086
+ return;
1087
+ if (isRowDisabled(id, localDisabled))
1088
+ return;
1089
+ if (source === 'keyboard-enter') {
1090
+ if (onRowAction) {
1091
+ performRowAction(id);
1092
+ return;
1093
+ }
1094
+ pressRowSelection(id, interaction);
1095
+ return;
1096
+ }
1097
+ if (source === 'keyboard-space') {
1098
+ if (interaction.shiftKey) {
1099
+ extendSelectionToRow(id);
1100
+ return;
1101
+ }
1102
+ if (interaction.ctrlKey || interaction.metaKey) {
1103
+ toggleSelectionForRow(id);
1104
+ return;
1105
+ }
1106
+ toggleRowSelection(id);
1107
+ return;
1108
+ }
1109
+ if (source === 'pointer-double') {
1110
+ if (onRowAction && selectionMode !== 'none' && selectionBehavior === 'replace') {
1111
+ performRowAction(id);
1112
+ }
1113
+ return;
1114
+ }
1115
+ if (!onRowAction) {
1116
+ pressRowSelection(id, interaction);
1117
+ return;
1118
+ }
1119
+ if (selectionMode === 'none') {
1120
+ performRowAction(id);
1121
+ return;
1122
+ }
1123
+ if (selectionBehavior === 'replace') {
1124
+ pressRowSelection(id, interaction);
1125
+ return;
1126
+ }
1127
+ if (hasActiveSelection()) {
1128
+ pressRowSelection(id, interaction);
1129
+ return;
1130
+ }
1131
+ performRowAction(id);
1132
+ }
1002
1133
  function moveFocus(direction, interaction = {}) {
1003
1134
  const rowMap = getRowsWithCells();
1004
1135
  const currentCoord = getFocusedCoord();
@@ -1166,12 +1297,11 @@ export function createTableContext(options = {}) {
1166
1297
  notifySelection();
1167
1298
  }
1168
1299
  function toggleRowSelection(id) {
1169
- if (selectionMode === 'none' || id === undefined || disabledKeys.has(id))
1300
+ if (selectionMode === 'none' || id === undefined || isRowSelectionDisabled(id))
1170
1301
  return;
1171
1302
  if (selectionMode === 'single') {
1172
1303
  const wasSelected = selectedKeys.has(id);
1173
- setSelectedKeys(wasSelected ? new Set() : new Set([id]), wasSelected ? null : id);
1174
- emitSelectionChange();
1304
+ applySelectionChange(wasSelected ? new Set() : new Set([id]), wasSelected ? null : id);
1175
1305
  return;
1176
1306
  }
1177
1307
  const next = new Set(selectedKeys);
@@ -1181,8 +1311,7 @@ export function createTableContext(options = {}) {
1181
1311
  else {
1182
1312
  next.add(id);
1183
1313
  }
1184
- setSelectedKeys(next, id);
1185
- emitSelectionChange();
1314
+ applySelectionChange(next, id);
1186
1315
  }
1187
1316
  function selectAllRows() {
1188
1317
  if (selectionMode !== 'multiple')
@@ -1190,7 +1319,7 @@ export function createTableContext(options = {}) {
1190
1319
  const next = new Set();
1191
1320
  for (const token of getOrderedRowTokens('body')) {
1192
1321
  const row = rows.get(token);
1193
- if (!row?.id || disabledKeys.has(row.id) || row.disabled)
1322
+ if (!row?.id || isRowSelectionDisabled(row.id, row.disabled))
1194
1323
  continue;
1195
1324
  next.add(row.id);
1196
1325
  }
@@ -1200,14 +1329,21 @@ export function createTableContext(options = {}) {
1200
1329
  function deselectAllRows() {
1201
1330
  if (selectedKeys.size === 0)
1202
1331
  return;
1203
- setSelectedKeys(new Set(), null);
1204
- emitSelectionChange();
1332
+ applySelectionChange(new Set(), null);
1205
1333
  }
1206
1334
  function setSelection(keys) {
1335
+ const previousSelection = new Set(selectedKeys);
1336
+ const previousAnchor = selectionAnchorKey;
1207
1337
  const next = new Set(keys);
1208
1338
  const preservedAnchor = selectionAnchorKey !== null && next.has(selectionAnchorKey) ? selectionAnchorKey : undefined;
1209
1339
  setSelectedKeys(next, preservedAnchor);
1210
- notifySelection();
1340
+ if (!hasSameSelection(previousSelection, selectedKeys)) {
1341
+ emitSelectionChange();
1342
+ return;
1343
+ }
1344
+ if (previousAnchor !== selectionAnchorKey) {
1345
+ notifySelection();
1346
+ }
1211
1347
  }
1212
1348
  function setSelectionMode(mode) {
1213
1349
  const previousSelectedKeys = new Set(selectedKeys);
@@ -1225,6 +1361,23 @@ export function createTableContext(options = {}) {
1225
1361
  selectionBehavior = behavior;
1226
1362
  notifySelection();
1227
1363
  }
1364
+ function setDisabledBehavior(behavior) {
1365
+ disabledBehavior = behavior;
1366
+ invalidateLayoutCaches();
1367
+ reconcileFocusAfterDisabledStateChange();
1368
+ notifyLayout();
1369
+ notifySelection();
1370
+ }
1371
+ function setDisallowEmptySelection(disallow) {
1372
+ disallowEmptySelection = disallow;
1373
+ const previousSelection = new Set(selectedKeys);
1374
+ setSelectedKeys(new Set(selectedKeys), selectionAnchorKey);
1375
+ if (!hasSameSelection(previousSelection, selectedKeys)) {
1376
+ emitSelectionChange();
1377
+ return;
1378
+ }
1379
+ notifySelection();
1380
+ }
1228
1381
  function setDisabledKeys(keys) {
1229
1382
  disabledKeys.clear();
1230
1383
  if (keys) {
@@ -1232,6 +1385,13 @@ export function createTableContext(options = {}) {
1232
1385
  disabledKeys.add(key);
1233
1386
  }
1234
1387
  }
1388
+ invalidateLayoutCaches();
1389
+ reconcileFocusAfterDisabledStateChange();
1390
+ notifyLayout();
1391
+ notifySelection();
1392
+ }
1393
+ function setRowActionHandler(handler) {
1394
+ onRowAction = handler;
1235
1395
  notifySelection();
1236
1396
  }
1237
1397
  function setSortDescriptor(descriptor) {
@@ -1240,7 +1400,7 @@ export function createTableContext(options = {}) {
1240
1400
  notifySort();
1241
1401
  }
1242
1402
  function isColumnSortable(columnId) {
1243
- return Array.from(columns.values()).some((column) => column.id === columnId && column.allowsSorting);
1403
+ return getColumnRegistrationById(columnId)?.allowsSorting ?? false;
1244
1404
  }
1245
1405
  function toggleSort(columnId) {
1246
1406
  if (!isColumnSortable(columnId))
@@ -1273,6 +1433,15 @@ export function createTableContext(options = {}) {
1273
1433
  get selectionBehavior() {
1274
1434
  return selectionBehavior;
1275
1435
  },
1436
+ get disabledBehavior() {
1437
+ return disabledBehavior;
1438
+ },
1439
+ get disallowEmptySelection() {
1440
+ return disallowEmptySelection;
1441
+ },
1442
+ get selectionUnavailableDescriptionId() {
1443
+ return selectionUnavailableDescriptionId;
1444
+ },
1276
1445
  disabledKeys,
1277
1446
  get focusedCellKey() {
1278
1447
  return focusedCellKey;
@@ -1321,6 +1490,9 @@ export function createTableContext(options = {}) {
1321
1490
  isRowFocusTarget,
1322
1491
  getRowFocusEdge,
1323
1492
  isRowDisabled,
1493
+ isRowSelectionDisabled,
1494
+ isRowActionDisabled,
1495
+ isRowActionable,
1324
1496
  hasSelectableRows,
1325
1497
  getSelectionCheckboxState,
1326
1498
  registerCell,
@@ -1347,7 +1519,10 @@ export function createTableContext(options = {}) {
1347
1519
  setSelection,
1348
1520
  setSelectionMode,
1349
1521
  setSelectionBehavior,
1522
+ setDisabledBehavior,
1523
+ setDisallowEmptySelection,
1350
1524
  setDisabledKeys,
1525
+ setRowActionHandler,
1351
1526
  setSortDescriptor,
1352
1527
  toggleSort,
1353
1528
  isColumnSortable,
@@ -17,6 +17,8 @@
17
17
  import {
18
18
  createTableContext,
19
19
  setTableContext,
20
+ type TableDisabledBehavior,
21
+ type TableRowActionHandler,
20
22
  type TableContext,
21
23
  type TableSelectionBehavior,
22
24
  type TableSelectionKey,
@@ -31,6 +33,8 @@
31
33
  type TableRootProps = Omit<HTMLAttributes<HTMLTableElement>, 'children'> & {
32
34
  selectionMode?: TableSelectionMode;
33
35
  selectionBehavior?: TableSelectionBehavior;
36
+ disabledBehavior?: TableDisabledBehavior;
37
+ disallowEmptySelection?: boolean;
34
38
  hiddenColumns?: Iterable<string>;
35
39
  defaultHiddenColumns?: Iterable<string>;
36
40
  selectedKeys?: Iterable<TableSelectionKey>;
@@ -40,6 +44,7 @@
40
44
  columnWidths?: Map<string, number>;
41
45
  defaultColumnWidths?: Iterable<readonly [string, number]>;
42
46
  disabledKeys?: Iterable<TableSelectionKey>;
47
+ onRowAction?: TableRowActionHandler;
43
48
  onSelectionChange?: (keys: Set<TableSelectionKey>) => void;
44
49
  onSortChange?: (descriptor: TableSortDescriptor | undefined) => void;
45
50
  onColumnWidthsChange?: (widths: Map<string, number>) => void;
@@ -55,6 +60,8 @@
55
60
  let {
56
61
  selectionMode = 'none',
57
62
  selectionBehavior = 'toggle',
63
+ disabledBehavior = 'all',
64
+ disallowEmptySelection = false,
58
65
  hiddenColumns = $bindable(),
59
66
  defaultHiddenColumns,
60
67
  selectedKeys = $bindable(),
@@ -66,6 +73,7 @@
66
73
  'aria-label': ariaLabel,
67
74
  'aria-labelledby': ariaLabelledby,
68
75
  disabledKeys,
76
+ onRowAction,
69
77
  onSelectionChange,
70
78
  onSortChange,
71
79
  onColumnWidthsChange,
@@ -94,11 +102,14 @@
94
102
  createTableContext({
95
103
  selectionMode: (() => selectionMode)(),
96
104
  selectionBehavior: (() => selectionBehavior)(),
105
+ disabledBehavior: (() => disabledBehavior)(),
106
+ disallowEmptySelection: (() => disallowEmptySelection)(),
97
107
  initialHiddenColumns: (() => hiddenColumns ?? defaultHiddenColumns)(),
98
108
  initialSelectedKeys: (() => selectedKeys ?? defaultSelectedKeys)(),
99
109
  initialSortDescriptor: (() => sortDescriptor ?? defaultSortDescriptor)(),
100
110
  initialColumnWidths: (() => columnWidths ?? defaultColumnWidths)(),
101
111
  disabledKeys: (() => disabledKeys)(),
112
+ onRowAction: (() => onRowAction)(),
102
113
  onHiddenColumnsChange: (columnIds) => {
103
114
  pendingControlledHiddenColumns = [...columnIds];
104
115
  hiddenColumns = [...columnIds];
@@ -270,10 +281,22 @@
270
281
  ctx.setSelectionBehavior(selectionBehavior);
271
282
  });
272
283
 
284
+ $effect(() => {
285
+ ctx.setDisabledBehavior(disabledBehavior);
286
+ });
287
+
288
+ $effect(() => {
289
+ ctx.setDisallowEmptySelection(disallowEmptySelection);
290
+ });
291
+
273
292
  $effect(() => {
274
293
  ctx.setDisabledKeys(disabledKeys);
275
294
  });
276
295
 
296
+ $effect(() => {
297
+ ctx.setRowActionHandler(onRowAction);
298
+ });
299
+
277
300
  $effect(() => {
278
301
  if (selectedKeys !== undefined) {
279
302
  const nextSelection = parseSelection(selectedKeys);
@@ -388,6 +411,7 @@
388
411
  aria-multiselectable={selectionMode === 'multiple' ? true : undefined}
389
412
  data-selection-mode={selectionMode}
390
413
  data-selection-behavior={selectionBehavior}
414
+ data-disabled-behavior={disabledBehavior}
391
415
  data-focus-within={focusWithin || undefined}
392
416
  data-focus-visible={focusVisible || undefined}
393
417
  onfocusin={handleFocusIn}
@@ -408,3 +432,9 @@
408
432
  style="position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;"
409
433
  >{sortAnnouncement}</span
410
434
  >
435
+
436
+ <span
437
+ id={ctx.selectionUnavailableDescriptionId}
438
+ style="position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;"
439
+ >Selection unavailable for this row.</span
440
+ >
@@ -1,9 +1,11 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  import type { HTMLAttributes } from 'svelte/elements';
3
- import { type TableContext, type TableSelectionBehavior, type TableSelectionKey, type TableSelectionMode, type TableSortDescriptor } from './context';
3
+ import { type TableDisabledBehavior, type TableRowActionHandler, type TableContext, type TableSelectionBehavior, type TableSelectionKey, type TableSelectionMode, type TableSortDescriptor } from './context';
4
4
  type TableRootProps = Omit<HTMLAttributes<HTMLTableElement>, 'children'> & {
5
5
  selectionMode?: TableSelectionMode;
6
6
  selectionBehavior?: TableSelectionBehavior;
7
+ disabledBehavior?: TableDisabledBehavior;
8
+ disallowEmptySelection?: boolean;
7
9
  hiddenColumns?: Iterable<string>;
8
10
  defaultHiddenColumns?: Iterable<string>;
9
11
  selectedKeys?: Iterable<TableSelectionKey>;
@@ -13,6 +15,7 @@ type TableRootProps = Omit<HTMLAttributes<HTMLTableElement>, 'children'> & {
13
15
  columnWidths?: Map<string, number>;
14
16
  defaultColumnWidths?: Iterable<readonly [string, number]>;
15
17
  disabledKeys?: Iterable<TableSelectionKey>;
18
+ onRowAction?: TableRowActionHandler;
16
19
  onSelectionChange?: (keys: Set<TableSelectionKey>) => void;
17
20
  onSortChange?: (descriptor: TableSortDescriptor | undefined) => void;
18
21
  onColumnWidthsChange?: (widths: Map<string, number>) => void;
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { Table } from '../index';
3
3
  import type {
4
+ TableDisabledBehavior,
4
5
  TableSelectionBehavior,
5
6
  TableSelectionKey,
6
7
  TableSelectionMode,
@@ -25,11 +26,15 @@
25
26
  ariaLabelledby?: string;
26
27
  selectionMode?: TableSelectionMode;
27
28
  selectionBehavior?: TableSelectionBehavior;
29
+ disabledBehavior?: TableDisabledBehavior;
30
+ disallowEmptySelection?: boolean;
28
31
  hiddenColumns?: string[];
29
32
  defaultHiddenColumns?: string[];
30
33
  disabledKeys?: Iterable<TableSelectionKey>;
31
34
  initialSelectedKeys?: Iterable<TableSelectionKey>;
32
35
  initialSortDescriptor?: TableSortDescriptor;
36
+ onRowAction?: (id: TableSelectionKey) => void;
37
+ onSelectionChange?: (keys: Set<TableSelectionKey>) => void;
33
38
  showSelectionModeToggle?: boolean;
34
39
  showSingleSelectionModeToggle?: boolean;
35
40
  showSortClearButton?: boolean;
@@ -42,11 +47,15 @@
42
47
  ariaLabelledby,
43
48
  selectionMode = $bindable<TableSelectionMode>('multiple'),
44
49
  selectionBehavior = 'toggle',
50
+ disabledBehavior = 'all',
51
+ disallowEmptySelection = false,
45
52
  hiddenColumns = $bindable<string[] | undefined>(),
46
53
  defaultHiddenColumns,
47
54
  disabledKeys,
48
55
  initialSelectedKeys,
49
56
  initialSortDescriptor,
57
+ onRowAction,
58
+ onSelectionChange,
50
59
  showSelectionModeToggle = false,
51
60
  showSingleSelectionModeToggle = false,
52
61
  showSortClearButton = false,
@@ -59,6 +68,8 @@
59
68
  let currentSortDescriptor = $state<TableSortDescriptor | undefined>(
60
69
  (() => initialSortDescriptor)()
61
70
  );
71
+ let rowActionLog = $state<string[]>([]);
72
+ let eventLog = $state<string[]>([]);
62
73
 
63
74
  const renderedRows = $derived.by(() => {
64
75
  const nextRows = [...rows];
@@ -71,6 +82,18 @@
71
82
  return String(left).localeCompare(String(right)) * direction;
72
83
  });
73
84
  });
85
+
86
+ function handleSelectionChange(keys: Set<TableSelectionKey>) {
87
+ currentSelectedKeys = new Set(keys);
88
+ eventLog = [...eventLog, `selection:${JSON.stringify([...keys])}`];
89
+ onSelectionChange?.(new Set(keys));
90
+ }
91
+
92
+ function handleRowAction(id: TableSelectionKey) {
93
+ rowActionLog = [...rowActionLog, String(id)];
94
+ eventLog = [...eventLog, `action:${String(id)}`];
95
+ onRowAction?.(id);
96
+ }
74
97
  </script>
75
98
 
76
99
  <Table.Root
@@ -78,11 +101,15 @@
78
101
  aria-labelledby={ariaLabelledby}
79
102
  {selectionMode}
80
103
  {selectionBehavior}
104
+ {disabledBehavior}
105
+ {disallowEmptySelection}
81
106
  bind:hiddenColumns
82
107
  {defaultHiddenColumns}
83
108
  bind:selectedKeys={currentSelectedKeys}
84
109
  bind:sortDescriptor={currentSortDescriptor}
85
110
  {disabledKeys}
111
+ onRowAction={handleRowAction}
112
+ onSelectionChange={handleSelectionChange}
86
113
  class="table-root"
87
114
  >
88
115
  <Table.Header>
@@ -157,6 +184,8 @@
157
184
  {/if}
158
185
 
159
186
  <output data-testid="selected-keys">{JSON.stringify([...currentSelectedKeys])}</output>
187
+ <output data-testid="row-action-log">{JSON.stringify(rowActionLog)}</output>
188
+ <output data-testid="event-log">{JSON.stringify(eventLog)}</output>
160
189
  <output data-testid="sort-descriptor"
161
190
  >{currentSortDescriptor
162
191
  ? `${currentSortDescriptor.column}:${currentSortDescriptor.direction}`
@@ -1,4 +1,4 @@
1
- import type { TableSelectionBehavior, TableSelectionKey, TableSelectionMode, TableSortDescriptor } from './context';
1
+ import type { TableDisabledBehavior, TableSelectionBehavior, TableSelectionKey, TableSelectionMode, TableSortDescriptor } from './context';
2
2
  type DemoRow = {
3
3
  id: string;
4
4
  email: string;
@@ -10,11 +10,15 @@ type TableTestProps = {
10
10
  ariaLabelledby?: string;
11
11
  selectionMode?: TableSelectionMode;
12
12
  selectionBehavior?: TableSelectionBehavior;
13
+ disabledBehavior?: TableDisabledBehavior;
14
+ disallowEmptySelection?: boolean;
13
15
  hiddenColumns?: string[];
14
16
  defaultHiddenColumns?: string[];
15
17
  disabledKeys?: Iterable<TableSelectionKey>;
16
18
  initialSelectedKeys?: Iterable<TableSelectionKey>;
17
19
  initialSortDescriptor?: TableSortDescriptor;
20
+ onRowAction?: (id: TableSelectionKey) => void;
21
+ onSelectionChange?: (keys: Set<TableSelectionKey>) => void;
18
22
  showSelectionModeToggle?: boolean;
19
23
  showSingleSelectionModeToggle?: boolean;
20
24
  showSortClearButton?: boolean;