@human-kit/svelte-components 1.0.0-alpha.16 → 1.0.0-alpha.17

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 (27) hide show
  1. package/dist/table/PLAN.md +6 -6
  2. package/dist/table/README.md +4 -2
  3. package/dist/table/body/table-body.svelte +5 -0
  4. package/dist/table/cell/table-cell.svelte +17 -0
  5. package/dist/table/checkbox/README.md +1 -1
  6. package/dist/table/checkbox/table-checkbox.svelte +2 -15
  7. package/dist/table/checkbox-indicator/README.md +1 -1
  8. package/dist/table/column/README.md +11 -11
  9. package/dist/table/column-header-cell/table-column-header-cell.svelte +19 -16
  10. package/dist/table/column-resizer/table-column-resizer-fixed-width-test.svelte +57 -0
  11. package/dist/table/column-resizer/table-column-resizer-fixed-width-test.svelte.d.ts +3 -0
  12. package/dist/table/column-resizer/table-column-resizer-freeze-layout-test.svelte +2 -1
  13. package/dist/table/column-resizer/table-column-resizer-overflow-test.svelte +64 -0
  14. package/dist/table/column-resizer/table-column-resizer-overflow-test.svelte.d.ts +3 -0
  15. package/dist/table/column-resizer/table-column-resizer-padded-container-test.svelte +67 -0
  16. package/dist/table/column-resizer/table-column-resizer-padded-container-test.svelte.d.ts +3 -0
  17. package/dist/table/column-resizer/table-column-resizer-selection-column-test.svelte +2 -1
  18. package/dist/table/column-resizer/table-column-resizer-test.svelte +3 -3
  19. package/dist/table/column-resizer/table-column-resizer-three-column-relative-test.svelte +64 -0
  20. package/dist/table/column-resizer/table-column-resizer-three-column-relative-test.svelte.d.ts +3 -0
  21. package/dist/table/column-resizer/table-column-resizer.svelte +47 -54
  22. package/dist/table/root/README.md +12 -12
  23. package/dist/table/root/context.d.ts +13 -7
  24. package/dist/table/root/context.js +363 -38
  25. package/dist/table/root/table-root.svelte +113 -9
  26. package/dist/table/types.d.ts +4 -4
  27. package/package.json +1 -1
@@ -1,10 +1,12 @@
1
- import { getContext, setContext } from 'svelte';
1
+ import { flushSync, getContext, setContext } from 'svelte';
2
2
  import { writable } from 'svelte/store';
3
3
  const TABLE_KEY = Symbol('table');
4
4
  const TABLE_SECTION_KEY = Symbol('table-section');
5
5
  const TABLE_ROW_KEY = Symbol('table-row');
6
6
  const TABLE_COLUMN_KEY = Symbol('table-column');
7
7
  const TABLE_CELL_KEY = Symbol('table-cell');
8
+ const IS_BROWSER = typeof window !== 'undefined';
9
+ export const DEFAULT_TABLE_COLUMN_MIN_WIDTH = 60;
8
10
  export function createTableContext(options = {}) {
9
11
  let selectionMode = options.selectionMode ?? 'none';
10
12
  let selectionBehavior = options.selectionBehavior ?? 'toggle';
@@ -20,15 +22,18 @@ export function createTableContext(options = {}) {
20
22
  const disabledKeys = new Set(options.disabledKeys ?? []);
21
23
  const hiddenColumnIds = new Set(options.initialHiddenColumns ?? []);
22
24
  let resizingColumnId = null;
25
+ let resizeSession = null;
23
26
  let suppressNextHeaderClick = false;
24
27
  const columns = new Map();
25
28
  const columnIds = new Map();
26
29
  const columnOrder = [];
27
30
  const columnsWithResizers = new Set();
31
+ let resizerLayoutReady = false;
28
32
  const columnWidths = new Map(options.initialColumnWidths ?? []);
29
33
  const rows = new Map();
30
34
  const headerRowOrder = [];
31
35
  const bodyRowOrder = [];
36
+ let bodyRowsInitialized = false;
32
37
  const cells = new Map();
33
38
  const cellOrder = [];
34
39
  let orderedRowTokensCache = {
@@ -39,6 +44,7 @@ export function createTableContext(options = {}) {
39
44
  let visibleOrderedColumnTokensCache = null;
40
45
  let columnWidthsCache = null;
41
46
  let visibleColumnWidthsCache = null;
47
+ let resolvedVisibleColumnWidthsCache = null;
42
48
  let navigableCellsCache = null;
43
49
  let rowsWithCellsCache = null;
44
50
  const layoutVersion = writable(0);
@@ -60,11 +66,20 @@ export function createTableContext(options = {}) {
60
66
  visibleOrderedColumnTokensCache = null;
61
67
  columnWidthsCache = null;
62
68
  visibleColumnWidthsCache = null;
69
+ resolvedVisibleColumnWidthsCache = null;
63
70
  navigableCellsCache = null;
64
71
  rowsWithCellsCache = null;
65
72
  }
66
73
  let layoutNotifyScheduled = false;
67
74
  let widthNotifyScheduled = false;
75
+ function syncResizerLayoutReady(nextReady) {
76
+ if (resizerLayoutReady === nextReady)
77
+ return;
78
+ resizerLayoutReady = nextReady;
79
+ invalidateLayoutCaches();
80
+ layoutVersion.update((value) => value + 1);
81
+ notifyWidth();
82
+ }
68
83
  function notifyLayout() {
69
84
  invalidateLayoutCaches();
70
85
  if (!layoutNotifyScheduled) {
@@ -88,6 +103,7 @@ export function createTableContext(options = {}) {
88
103
  function notifyWidth() {
89
104
  columnWidthsCache = null;
90
105
  visibleColumnWidthsCache = null;
106
+ resolvedVisibleColumnWidthsCache = null;
91
107
  if (!widthNotifyScheduled) {
92
108
  widthNotifyScheduled = true;
93
109
  queueMicrotask(() => {
@@ -96,22 +112,49 @@ export function createTableContext(options = {}) {
96
112
  });
97
113
  }
98
114
  }
115
+ function notifyWidthImmediately() {
116
+ columnWidthsCache = null;
117
+ visibleColumnWidthsCache = null;
118
+ resolvedVisibleColumnWidthsCache = null;
119
+ flushSync(() => {
120
+ widthVersion.update((value) => value + 1);
121
+ });
122
+ }
99
123
  function notifyResize() {
100
124
  resizeVersion.update((value) => value + 1);
101
125
  }
102
- function normalizeColumnWidth(width) {
126
+ function parseColumnWidth(width) {
103
127
  if (typeof width === 'number') {
104
- return Number.isFinite(width) ? width : undefined;
128
+ return Number.isFinite(width) ? { unit: 'px', value: width } : undefined;
105
129
  }
106
130
  if (typeof width === 'string') {
107
- const match = width.trim().match(/^(\d+(?:\.\d+)?)px$/i);
131
+ const match = width.trim().match(/^(\d+(?:\.\d+)?)(px|%|fr)$/i);
108
132
  if (!match)
109
133
  return undefined;
110
134
  const next = Number(match[1]);
111
- return Number.isFinite(next) ? next : undefined;
135
+ if (!Number.isFinite(next))
136
+ return undefined;
137
+ const unit = match[2].toLowerCase();
138
+ return { unit, value: next };
112
139
  }
113
140
  return undefined;
114
141
  }
142
+ function normalizeColumnWidth(width) {
143
+ const parsed = parseColumnWidth(width);
144
+ if (!parsed)
145
+ return undefined;
146
+ if (parsed.unit === 'px') {
147
+ return typeof width === 'number' ? width : `${parsed.value}px`;
148
+ }
149
+ if (parsed.unit === '%') {
150
+ return `${parsed.value}%`;
151
+ }
152
+ return `${parsed.value}fr`;
153
+ }
154
+ function isRelativeColumnWidth(width) {
155
+ const parsed = parseColumnWidth(width);
156
+ return parsed ? parsed.unit !== 'px' : false;
157
+ }
115
158
  function getColumnRegistrationById(columnId) {
116
159
  const token = columnIds.get(columnId);
117
160
  return token ? columns.get(token) : undefined;
@@ -125,9 +168,61 @@ export function createTableContext(options = {}) {
125
168
  function getColumnMaxWidth(columnId) {
126
169
  return getColumnRegistrationById(columnId)?.maxWidth;
127
170
  }
171
+ function getFixedColumnWidthSpec(columnId) {
172
+ return normalizeColumnWidth(getColumnRegistrationById(columnId)?.width);
173
+ }
174
+ function getManagedColumnWidthSpec(columnId) {
175
+ return normalizeColumnWidth(columnWidths.get(columnId));
176
+ }
177
+ function getDefaultColumnWidthSpec(columnId) {
178
+ return normalizeColumnWidth(getColumnRegistrationById(columnId)?.defaultWidth);
179
+ }
180
+ function hasAuthoredColumnWidthSpec(columnId) {
181
+ return (getFixedColumnWidthSpec(columnId) !== undefined ||
182
+ getManagedColumnWidthSpec(columnId) !== undefined ||
183
+ getDefaultColumnWidthSpec(columnId) !== undefined);
184
+ }
185
+ function getEffectiveColumnWidthSpec(columnId) {
186
+ const fixedWidth = getFixedColumnWidthSpec(columnId);
187
+ if (fixedWidth !== undefined) {
188
+ return fixedWidth;
189
+ }
190
+ const managedWidth = getManagedColumnWidthSpec(columnId);
191
+ if (managedWidth !== undefined) {
192
+ return managedWidth;
193
+ }
194
+ const defaultWidth = getDefaultColumnWidthSpec(columnId);
195
+ if (defaultWidth !== undefined) {
196
+ return defaultWidth;
197
+ }
198
+ return resizerLayoutReady && columnsWithResizers.size > 0 ? '1fr' : undefined;
199
+ }
200
+ function getMeasuredTableWidth() {
201
+ const tableCell = Array.from(cells.values()).find((cell) => cell.element)?.element;
202
+ const tableElement = tableCell?.closest('table');
203
+ const tableWidth = tableElement?.getBoundingClientRect().width;
204
+ const containerElement = tableElement?.parentElement;
205
+ const containerClientWidth = containerElement?.clientWidth;
206
+ const containerStyle = containerElement ? getComputedStyle(containerElement) : undefined;
207
+ const containerPaddingLeft = Number.parseFloat(containerStyle?.paddingLeft ?? '0');
208
+ const containerPaddingRight = Number.parseFloat(containerStyle?.paddingRight ?? '0');
209
+ const containerWidth = containerClientWidth !== undefined && Number.isFinite(containerClientWidth)
210
+ ? containerClientWidth - containerPaddingLeft - containerPaddingRight
211
+ : undefined;
212
+ // Use the container width as the stable reference for fr/% resolution.
213
+ // Using Math.max(table, container) causes a feedback loop during resize:
214
+ // the table grows → measurement returns more → fr columns get more space →
215
+ // the table grows further. The container width is stable and represents
216
+ // the actual available space the table should fill.
217
+ const width = containerWidth ?? tableWidth;
218
+ if (width === undefined || width <= 0 || !Number.isFinite(width)) {
219
+ return undefined;
220
+ }
221
+ return Math.round(width);
222
+ }
128
223
  function clampColumnWidth(columnId, width) {
129
224
  const registration = getColumnRegistrationById(columnId);
130
- const minWidth = registration?.minWidth ?? 75;
225
+ const minWidth = registration?.minWidth ?? DEFAULT_TABLE_COLUMN_MIN_WIDTH;
131
226
  const maxWidth = registration?.maxWidth;
132
227
  let next = Math.round(width);
133
228
  if (Number.isNaN(next) || !Number.isFinite(next)) {
@@ -141,9 +236,7 @@ export function createTableContext(options = {}) {
141
236
  }
142
237
  function hasResizableColumns() {
143
238
  for (const column of columns.values()) {
144
- if (isColumnHidden(column.id))
145
- continue;
146
- if (columnsWithResizers.has(column.token))
239
+ if (isColumnResizable(column.id))
147
240
  return true;
148
241
  }
149
242
  return false;
@@ -152,12 +245,26 @@ export function createTableContext(options = {}) {
152
245
  if (columnsWithResizers.has(columnToken))
153
246
  return;
154
247
  columnsWithResizers.add(columnToken);
155
- notifyLayout();
248
+ if (IS_BROWSER) {
249
+ syncResizerLayoutReady(true);
250
+ return;
251
+ }
252
+ resizerLayoutReady = false;
253
+ invalidateLayoutCaches();
254
+ layoutVersion.update((value) => value + 1);
255
+ notifyWidth();
156
256
  }
157
257
  function unregisterColumnResizer(columnToken) {
158
258
  if (!columnsWithResizers.delete(columnToken))
159
259
  return;
160
- notifyLayout();
260
+ if (IS_BROWSER) {
261
+ syncResizerLayoutReady(columnsWithResizers.size > 0);
262
+ return;
263
+ }
264
+ resizerLayoutReady = false;
265
+ invalidateLayoutCaches();
266
+ layoutVersion.update((value) => value + 1);
267
+ notifyWidth();
161
268
  }
162
269
  function sameColumnMetadata(left, right) {
163
270
  return (left.token === right.token &&
@@ -271,15 +378,72 @@ export function createTableContext(options = {}) {
271
378
  return getColumnRegistrationById(columnId)?.textValue;
272
379
  }
273
380
  function getColumnWidth(columnId) {
274
- const managedWidth = columnWidths.get(columnId);
275
- if (managedWidth !== undefined) {
276
- return clampColumnWidth(columnId, managedWidth);
381
+ const parsed = parseColumnWidth(getEffectiveColumnWidthSpec(columnId));
382
+ if (!parsed)
383
+ return undefined;
384
+ if (!isColumnHidden(columnId)) {
385
+ if (parsed.unit !== 'px') {
386
+ return undefined;
387
+ }
388
+ return getResolvedVisibleColumnWidths().get(columnId);
277
389
  }
278
- const registration = getColumnRegistrationById(columnId);
279
- if (!registration)
390
+ return parsed?.unit === 'px' ? clampColumnWidth(columnId, parsed.value) : undefined;
391
+ }
392
+ function formatCssLength(length) {
393
+ const rounded = Math.round(length * 1000) / 1000;
394
+ return Number.isInteger(rounded) ? `${rounded}` : `${rounded}`;
395
+ }
396
+ function getColumnWidthStyle(columnId) {
397
+ const resolvedWidth = getColumnWidth(columnId);
398
+ if (resolvedWidth !== undefined) {
399
+ return `${resolvedWidth}px`;
400
+ }
401
+ const parsed = parseColumnWidth(getEffectiveColumnWidthSpec(columnId));
402
+ if (!parsed)
403
+ return undefined;
404
+ if (parsed.unit === 'px') {
405
+ return `${clampColumnWidth(columnId, parsed.value)}px`;
406
+ }
407
+ if (parsed.unit === '%') {
408
+ return `${parsed.value}%`;
409
+ }
410
+ let fixedPxTotal = 0;
411
+ let percentTotal = 0;
412
+ let totalFr = 0;
413
+ for (const token of getVisibleOrderedColumnTokens()) {
414
+ const column = columns.get(token);
415
+ if (!column)
416
+ continue;
417
+ const spec = parseColumnWidth(getEffectiveColumnWidthSpec(column.id));
418
+ if (!spec)
419
+ continue;
420
+ if (spec.unit === 'px') {
421
+ fixedPxTotal += clampColumnWidth(column.id, spec.value);
422
+ continue;
423
+ }
424
+ if (spec.unit === '%') {
425
+ percentTotal += spec.value;
426
+ continue;
427
+ }
428
+ totalFr += spec.value;
429
+ }
430
+ if (totalFr <= 0)
280
431
  return undefined;
281
- const nextWidth = normalizeColumnWidth(registration.width) ?? normalizeColumnWidth(registration.defaultWidth);
282
- return nextWidth !== undefined ? clampColumnWidth(columnId, nextWidth) : undefined;
432
+ const frShare = parsed.value / totalFr;
433
+ const availableWidthTerms = ['100%'];
434
+ if (fixedPxTotal > 0) {
435
+ availableWidthTerms.push(`${formatCssLength(fixedPxTotal)}px`);
436
+ }
437
+ if (percentTotal > 0) {
438
+ availableWidthTerms.push(`${formatCssLength(percentTotal)}%`);
439
+ }
440
+ const availableWidthExpression = availableWidthTerms.length === 1
441
+ ? availableWidthTerms[0]
442
+ : `(${availableWidthTerms.join(' - ')})`;
443
+ if (frShare === 1) {
444
+ return `calc(${availableWidthExpression})`;
445
+ }
446
+ return `calc(${availableWidthExpression} * ${formatCssLength(frShare)})`;
283
447
  }
284
448
  function isColumnResizable(columnId) {
285
449
  const column = getColumnRegistrationById(columnId);
@@ -297,7 +461,7 @@ export function createTableContext(options = {}) {
297
461
  const column = columns.get(token);
298
462
  if (!column)
299
463
  continue;
300
- const width = getColumnWidth(column.id);
464
+ const width = getManagedColumnWidthSpec(column.id);
301
465
  if (width !== undefined) {
302
466
  widths.set(column.id, width);
303
467
  }
@@ -317,6 +481,63 @@ export function createTableContext(options = {}) {
317
481
  visibleColumnWidthsCache = widths;
318
482
  return widths;
319
483
  }
484
+ function hasRelativeVisibleColumnWidths() {
485
+ for (const token of getVisibleOrderedColumnTokens()) {
486
+ const column = columns.get(token);
487
+ if (!column)
488
+ continue;
489
+ if (isRelativeColumnWidth(getEffectiveColumnWidthSpec(column.id))) {
490
+ return true;
491
+ }
492
+ }
493
+ return false;
494
+ }
495
+ function getResolvedVisibleColumnWidths() {
496
+ if (resolvedVisibleColumnWidthsCache)
497
+ return resolvedVisibleColumnWidthsCache;
498
+ const widths = new Map();
499
+ const flexibleColumns = [];
500
+ const tableWidth = getMeasuredTableWidth();
501
+ let remainingWidth = tableWidth;
502
+ let totalFr = 0;
503
+ for (const token of getVisibleOrderedColumnTokens()) {
504
+ const column = columns.get(token);
505
+ if (!column)
506
+ continue;
507
+ const parsed = parseColumnWidth(getEffectiveColumnWidthSpec(column.id));
508
+ if (!parsed)
509
+ continue;
510
+ if (parsed.unit === 'px') {
511
+ const nextWidth = clampColumnWidth(column.id, parsed.value);
512
+ widths.set(column.id, nextWidth);
513
+ if (remainingWidth !== undefined) {
514
+ remainingWidth -= nextWidth;
515
+ }
516
+ continue;
517
+ }
518
+ if (parsed.unit === '%') {
519
+ if (tableWidth === undefined)
520
+ continue;
521
+ const nextWidth = clampColumnWidth(column.id, (tableWidth * parsed.value) / 100);
522
+ widths.set(column.id, nextWidth);
523
+ if (remainingWidth !== undefined) {
524
+ remainingWidth -= nextWidth;
525
+ }
526
+ continue;
527
+ }
528
+ flexibleColumns.push({ columnId: column.id, fr: parsed.value });
529
+ totalFr += parsed.value;
530
+ }
531
+ if (tableWidth !== undefined && flexibleColumns.length > 0 && totalFr > 0) {
532
+ const distributableWidth = Math.max(remainingWidth ?? tableWidth, 0);
533
+ for (const entry of flexibleColumns) {
534
+ const nextWidth = clampColumnWidth(entry.columnId, (distributableWidth * entry.fr) / totalFr);
535
+ widths.set(entry.columnId, nextWidth);
536
+ }
537
+ }
538
+ resolvedVisibleColumnWidthsCache = widths;
539
+ return widths;
540
+ }
320
541
  function getMeasuredHeaderWidth(columnToken) {
321
542
  const headerCell = Array.from(cells.values()).find((cell) => cell.section === 'header' && cell.columnToken === columnToken && cell.element);
322
543
  const width = headerCell?.element?.getBoundingClientRect().width;
@@ -325,42 +546,91 @@ export function createTableContext(options = {}) {
325
546
  }
326
547
  return Math.round(width);
327
548
  }
328
- function freezeColumnWidthsFromLayout() {
329
- const next = new Map();
330
- let changed = false;
331
- for (const token of columnOrder) {
549
+ function getResizableRelativeTailColumnId(activeColumnId) {
550
+ return getVisibleOrderedColumnTokens()
551
+ .map((token) => columns.get(token))
552
+ .filter((column) => !!column)
553
+ .filter((column) => {
554
+ if (column.id === activeColumnId)
555
+ return false;
556
+ if (getFixedColumnWidthSpec(column.id) !== undefined)
557
+ return false;
558
+ return isRelativeColumnWidth(getEffectiveColumnWidthSpec(column.id));
559
+ })
560
+ .at(-1)?.id;
561
+ }
562
+ function prepareColumnWidthsForResize(activeColumnId) {
563
+ const next = new Map(columnWidths);
564
+ const baselineWidths = new Map();
565
+ const preservedFlexibleColumnId = getResizableRelativeTailColumnId(activeColumnId);
566
+ const preservedFlexibleEffectiveWidth = preservedFlexibleColumnId
567
+ ? normalizeColumnWidth(getEffectiveColumnWidthSpec(preservedFlexibleColumnId))
568
+ : undefined;
569
+ const measuredTableWidth = getMeasuredTableWidth();
570
+ for (const token of getVisibleOrderedColumnTokens()) {
332
571
  const column = columns.get(token);
333
572
  if (!column)
334
573
  continue;
574
+ if (getFixedColumnWidthSpec(column.id) !== undefined)
575
+ continue;
335
576
  const measuredWidth = getMeasuredHeaderWidth(token);
336
- const resolvedWidth = measuredWidth ?? getColumnWidth(column.id);
577
+ const resolvedWidth = getColumnWidth(column.id) ?? measuredWidth;
337
578
  if (resolvedWidth === undefined)
338
579
  continue;
339
- const clampedWidth = clampColumnWidth(column.id, resolvedWidth);
340
- next.set(column.id, clampedWidth);
341
- if (columnWidths.get(column.id) !== clampedWidth) {
342
- changed = true;
580
+ const frozenWidth = clampColumnWidth(column.id, resolvedWidth);
581
+ next.set(column.id, frozenWidth);
582
+ baselineWidths.set(column.id, frozenWidth);
583
+ }
584
+ let changed = next.size !== columnWidths.size;
585
+ if (!changed) {
586
+ for (const [columnId, width] of next) {
587
+ if (columnWidths.get(columnId) !== width) {
588
+ changed = true;
589
+ break;
590
+ }
343
591
  }
344
592
  }
345
- if (!changed || next.size === 0)
593
+ if (!changed)
346
594
  return;
347
595
  columnWidths.clear();
348
596
  for (const [columnId, width] of next) {
349
597
  columnWidths.set(columnId, width);
350
598
  }
351
599
  columnWidthsCache = null;
600
+ visibleColumnWidthsCache = null;
601
+ const baselineTotalWidth = Array.from(baselineWidths.values()).reduce((total, current) => total + current, 0);
602
+ resizeSession = {
603
+ activeColumnId: activeColumnId,
604
+ flexibleTailColumnId: preservedFlexibleColumnId,
605
+ flexibleTailRestoreWidth: preservedFlexibleColumnId && isRelativeColumnWidth(preservedFlexibleEffectiveWidth)
606
+ ? preservedFlexibleEffectiveWidth
607
+ : undefined,
608
+ baselineWidths,
609
+ baselineTableWidth: measuredTableWidth ?? baselineTotalWidth,
610
+ baselineTotalWidth
611
+ };
352
612
  options.onColumnWidthsChange?.(getColumnWidths());
353
613
  notifyWidth();
354
614
  }
615
+ function shouldPrepareColumnWidthsForResize(columnId) {
616
+ if (getFixedColumnWidthSpec(columnId) !== undefined)
617
+ return false;
618
+ if (!hasRelativeVisibleColumnWidths())
619
+ return false;
620
+ return resizeSession?.activeColumnId !== columnId;
621
+ }
355
622
  function setColumnWidths(widths) {
356
623
  const next = new Map();
624
+ const incomingWidths = widths ? new Map(widths) : undefined;
357
625
  for (const token of columnOrder) {
358
626
  const column = columns.get(token);
359
627
  if (!column)
360
628
  continue;
361
- const incomingWidth = widths ? new Map(widths).get(column.id) : undefined;
629
+ if (getFixedColumnWidthSpec(column.id) !== undefined)
630
+ continue;
631
+ const incomingWidth = normalizeColumnWidth(incomingWidths?.get(column.id));
362
632
  if (incomingWidth !== undefined) {
363
- next.set(column.id, clampColumnWidth(column.id, incomingWidth));
633
+ next.set(column.id, incomingWidth);
364
634
  }
365
635
  }
366
636
  columnWidths.clear();
@@ -370,13 +640,36 @@ export function createTableContext(options = {}) {
370
640
  notifyWidth();
371
641
  }
372
642
  function setColumnWidth(columnId, width) {
643
+ if (getFixedColumnWidthSpec(columnId) !== undefined)
644
+ return;
373
645
  if (!isColumnResizable(columnId))
374
646
  return;
647
+ if (shouldPrepareColumnWidthsForResize(columnId)) {
648
+ prepareColumnWidthsForResize(columnId);
649
+ }
375
650
  const nextWidth = clampColumnWidth(columnId, width);
376
- if (columnWidths.get(columnId) === nextWidth)
651
+ const currentWidth = columnWidths.get(columnId);
652
+ if (resizeSession?.activeColumnId === columnId &&
653
+ resizeSession.flexibleTailColumnId !== undefined) {
654
+ const baselineActiveWidth = resizeSession.baselineWidths.get(columnId);
655
+ const baselineTailWidth = resizeSession.baselineWidths.get(resizeSession.flexibleTailColumnId);
656
+ if (baselineActiveWidth !== undefined && baselineTailWidth !== undefined) {
657
+ const widthDelta = nextWidth - baselineActiveWidth;
658
+ const overflowWidth = Math.max(resizeSession.baselineTotalWidth - resizeSession.baselineTableWidth, 0);
659
+ const tailTargetWidth = widthDelta >= 0
660
+ ? baselineTailWidth - widthDelta
661
+ : baselineTailWidth + Math.max(-widthDelta - overflowWidth, 0);
662
+ const tailWidth = clampColumnWidth(resizeSession.flexibleTailColumnId, tailTargetWidth);
663
+ if (columnWidths.get(resizeSession.flexibleTailColumnId) !== tailWidth) {
664
+ columnWidths.set(resizeSession.flexibleTailColumnId, tailWidth);
665
+ }
666
+ }
667
+ }
668
+ if (currentWidth === nextWidth)
377
669
  return;
378
670
  columnWidths.set(columnId, nextWidth);
379
671
  columnWidthsCache = null;
672
+ visibleColumnWidthsCache = null;
380
673
  options.onColumnWidthsChange?.(getColumnWidths());
381
674
  notifyWidth();
382
675
  }
@@ -502,9 +795,7 @@ export function createTableContext(options = {}) {
502
795
  notifyWidth();
503
796
  }
504
797
  function measureIntrinsicElementWidth(cell) {
505
- const target = cell.querySelector('[data-table-header-content]') ??
506
- cell.firstElementChild ??
507
- cell;
798
+ const target = cell;
508
799
  const clone = target.cloneNode(true);
509
800
  for (const separator of clone.querySelectorAll('[role="separator"]')) {
510
801
  separator.remove();
@@ -563,9 +854,7 @@ export function createTableContext(options = {}) {
563
854
  function startColumnResize(columnId) {
564
855
  if (!isColumnResizable(columnId) || resizingColumnId === columnId)
565
856
  return;
566
- if (getVisibleColumnWidths().size < getVisibleColumnCount()) {
567
- freezeColumnWidthsFromLayout();
568
- }
857
+ resizeSession = null;
569
858
  resizingColumnId = columnId;
570
859
  options.onColumnResizeStart?.(columnId);
571
860
  notifyResize();
@@ -573,8 +862,24 @@ export function createTableContext(options = {}) {
573
862
  function endColumnResize() {
574
863
  if (!resizingColumnId)
575
864
  return;
865
+ let restoredFlexibleTail = false;
866
+ if (resizeSession?.flexibleTailColumnId !== undefined &&
867
+ resizeSession.activeColumnId !== resizeSession.flexibleTailColumnId &&
868
+ resizeSession.flexibleTailRestoreWidth !== undefined) {
869
+ const tailColumnId = resizeSession.flexibleTailColumnId;
870
+ columnWidths.set(tailColumnId, resizeSession.flexibleTailRestoreWidth);
871
+ columnWidthsCache = null;
872
+ visibleColumnWidthsCache = null;
873
+ resolvedVisibleColumnWidthsCache = null;
874
+ restoredFlexibleTail = true;
875
+ }
576
876
  resizingColumnId = null;
877
+ if (restoredFlexibleTail) {
878
+ options.onColumnWidthsChange?.(getColumnWidths());
879
+ }
880
+ resizeSession = null;
577
881
  options.onColumnResizeEnd?.(getColumnWidths());
882
+ notifyWidthImmediately();
578
883
  notifyResize();
579
884
  }
580
885
  function suppressHeaderClickOnce() {
@@ -606,6 +911,7 @@ export function createTableContext(options = {}) {
606
911
  if (targetOrder && !targetOrder.includes(row.token)) {
607
912
  targetOrder.push(row.token);
608
913
  }
914
+ notifySelection();
609
915
  notifyLayout();
610
916
  }
611
917
  function unregisterRow(token) {
@@ -633,8 +939,19 @@ export function createTableContext(options = {}) {
633
939
  notifyFocus();
634
940
  }
635
941
  }
942
+ notifySelection();
636
943
  notifyLayout();
637
944
  }
945
+ function markBodyRowsInitialized() {
946
+ if (bodyRowsInitialized)
947
+ return;
948
+ const optimisticHasSelectableRows = selectionMode === 'multiple' || selectedKeys.size > 0;
949
+ bodyRowsInitialized = true;
950
+ const actualHasSelectableRows = getOrderedSelectableRowIds().length > 0 || selectedKeys.size > 0;
951
+ if (optimisticHasSelectableRows !== actualHasSelectableRows) {
952
+ notifySelection();
953
+ }
954
+ }
638
955
  function getBodyRowCount() {
639
956
  return getOrderedRowTokens('body').length;
640
957
  }
@@ -719,6 +1036,9 @@ export function createTableContext(options = {}) {
719
1036
  return 'some';
720
1037
  }
721
1038
  function hasSelectableRows() {
1039
+ if (!bodyRowsInitialized) {
1040
+ return selectionMode === 'multiple' || selectedKeys.size > 0;
1041
+ }
722
1042
  return getOrderedSelectableRowIds().length > 0 || selectedKeys.size > 0;
723
1043
  }
724
1044
  function isRowFocused(token) {
@@ -1465,12 +1785,16 @@ export function createTableContext(options = {}) {
1465
1785
  getVisibleColumnIndexByToken,
1466
1786
  getColumnTextValue,
1467
1787
  getColumnWidth,
1788
+ getColumnWidthStyle,
1789
+ hasAuthoredColumnWidthSpec,
1468
1790
  getColumnMinWidth,
1469
1791
  getColumnMaxWidth,
1470
1792
  isColumnHidden,
1471
1793
  isColumnResizable,
1472
1794
  getColumnWidths,
1473
1795
  getVisibleColumnWidths,
1796
+ getResolvedVisibleColumnWidths,
1797
+ hasRelativeVisibleColumnWidths,
1474
1798
  setColumnWidths,
1475
1799
  setColumnWidth,
1476
1800
  setHiddenColumns,
@@ -1482,6 +1806,7 @@ export function createTableContext(options = {}) {
1482
1806
  hasResizableColumns,
1483
1807
  registerRow,
1484
1808
  unregisterRow,
1809
+ markBodyRowsInitialized,
1485
1810
  getHeaderRowCount,
1486
1811
  getBodyRowCount,
1487
1812
  isRowSelected,