@rozie-ui/data-table-solid 0.3.2 → 0.5.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/src/DataTable.tsx CHANGED
@@ -35,7 +35,35 @@ import { Virtualizer, elementScroll, observeElementRect, observeElementOffset, m
35
35
  // snapshot (the rete stale-closure anti-pattern — a top-level $computed/useCallback
36
36
  // freezes the table at the empty-initial state on React).
37
37
 
38
- __rozieInjectStyle('DataTable-d5dcab4c', `.rozie-data-table[data-rozie-s-d5dcab4c] {
38
+ __rozieInjectStyle('DataTable-d5dcab4c', `[data-rozie-s-d5dcab4c]:host {
39
+ --rdt-font: var(--rozie-data-table-font);
40
+ --rdt-color: var(--rozie-data-table-fg);
41
+ --rdt-cell-padding: var(--rozie-data-table-cell-padding);
42
+ --rdt-border: 1px solid var(--rozie-data-table-border-color);
43
+ --rdt-header-bg: var(--rozie-data-table-header-bg);
44
+ --rdt-header-weight: var(--rozie-data-table-header-weight);
45
+ --rdt-sort-ind-opacity: var(--rozie-data-table-sort-indicator-opacity);
46
+ --rdt-filter-border: 1px solid var(--rozie-data-table-control-border-color);
47
+ --rdt-filter-radius: var(--rozie-data-table-radius);
48
+ --rdt-page-btn-border: 1px solid var(--rozie-data-table-control-border-color);
49
+ --rdt-page-btn-radius: var(--rozie-data-table-radius);
50
+ --rdt-page-btn-disabled-opacity: var(--rozie-data-table-disabled-opacity);
51
+ --rdt-page-size-border: 1px solid var(--rozie-data-table-control-border-color);
52
+ --rdt-page-size-radius: var(--rozie-data-table-radius);
53
+ --rdt-resize-grip-color: var(--rozie-data-table-resize-grip-color);
54
+ --rdt-resize-grip-active: var(--rozie-data-table-resize-grip-active);
55
+ --rdt-pin-btn-border: 1px solid var(--rozie-data-table-control-border-color);
56
+ --rdt-pin-btn-radius: var(--rozie-data-table-radius);
57
+ --rdt-pin-btn-active-bg: var(--rozie-data-table-pin-active-bg);
58
+ --rdt-colvis-summary-border: 1px solid var(--rozie-data-table-control-border-color);
59
+ --rdt-colvis-summary-radius: var(--rozie-data-table-radius);
60
+ --rdt-colvis-menu-border: 1px solid var(--rozie-data-table-border-color);
61
+ --rdt-colvis-menu-radius: var(--rozie-data-table-radius);
62
+ --rdt-colvis-menu-bg: var(--rozie-data-table-menu-bg);
63
+ --rdt-colvis-menu-shadow: var(--rozie-data-table-menu-shadow);
64
+ --rdt-select-accent: var(--rozie-data-table-select-accent);
65
+ }
66
+ .rozie-data-table[data-rozie-s-d5dcab4c] {
39
67
  border-collapse: collapse;
40
68
  width: 100%;
41
69
  font: var(--rdt-font, 14px system-ui, sans-serif);
@@ -353,21 +381,21 @@ interface GroupBarSlotCtx { grouping: any; groupableColumns: any; applyGrouping:
353
381
 
354
382
  interface SelectAllSlotCtx { checked: any; indeterminate: any; toggle: any; }
355
383
 
384
+ interface SelectCellSlotCtx { row: any; checked: any; toggle: any; }
385
+
386
+ interface DetailSlotCtx { row: any; }
387
+
356
388
  interface ColHeaderSlotCtx { columnId: any; column: any; label: any; }
357
389
 
358
390
  interface FilterSlotCtx { columnId: any; value: any; uniqueValues: any; minMax: any; setFilter: any; }
359
391
 
360
- interface SelectCellSlotCtx { row: any; checked: any; toggle: any; }
361
-
362
392
  interface CellSlotCtx { columnId: any; column: any; row: any; value: any; }
363
393
 
364
394
  interface EditorSlotCtx { columnId: any; column: any; row: any; value: any; commit: any; cancel: any; autofocus: any; }
365
395
 
366
- interface DetailSlotCtx { row: any; }
367
-
368
396
  interface DataTableProps {
369
397
  /**
370
- * The row data — `model: true`, so a committed cell/row edit writes a **fresh** array back through `r-model:data` (uncontrolled fallback `dataDefault`). A stable reference per Rozie's setup-once model — fed directly into table-core (never map/cloned in the watcher).
398
+ * The row data — `model: true`, so a committed cell/row edit writes a **fresh** array back through the two-way `data` binding (uncontrolled fallback `dataDefault`). A stable reference per Rozie's setup-once model — fed directly into table-core (never map/cloned in the watcher).
371
399
  * @example
372
400
  * <DataTable data={rows()} onDataChange={setRows} columns={cols} />
373
401
  */
@@ -375,7 +403,7 @@ interface DataTableProps {
375
403
  defaultData?: any[];
376
404
  onDataChange?: (data: any[]) => void;
377
405
  /**
378
- * Config-array column fallback (lower precedence than `<Column>` children). Each entry: `{ id?, field, header?, sortable?, filterable?, pinned?, width? }`. Columns may come from this array, from `<Column>` children, or both (id-keyed last-write-wins union).
406
+ * Config-array column fallback (lower precedence than `<Column>` children). Each entry: `{ id?, field, header?, sortable?, filterable?, pinned?, width? }`. Columns may come from this array, from `<Column>` children, or both (id-keyed last-write-wins union). A `pinned` value of `'left'` or `'right'` is applied once as the table's initial `columnPinning` state — identical to `<Column pinned>` — which also REORDERS that column, since table-core orders visible cells `[left-pinned, center, right-pinned]`. A consumer who has already expressed a pin (an initial two-way `columnPinning` model value or an interactive pin) owns the slice and this declaration is ignored.
379
407
  */
380
408
  columns?: any[];
381
409
  /**
@@ -419,7 +447,7 @@ interface DataTableProps {
419
447
  */
420
448
  pageCount?: (number) | null;
421
449
  /**
422
- * Opt-in **expandable rows**. When `true`, a leading chevron expander column auto-injects (after the select column) and `getExpandedRowModel` activates; default `false` is byte-identical-off. Every row can expand to reveal a `#detail` panel unless `getSubRows` is supplied (then only rows with children expand). Bind `:expandable="true"` (a bare attr only coerces on Vue+Lit).
450
+ * Opt-in **expandable rows**. When `true`, a leading chevron expander column auto-injects (after the select column) and `getExpandedRowModel` activates; default `false` is byte-identical-off. Every row can expand to reveal a `#detail` panel unless `getSubRows` is supplied (then only rows with children expand). Pass `expandable` as a real boolean — a valueless attribute only coerces to `true` on Vue and Lit.
423
451
  */
424
452
  expandable?: boolean;
425
453
  /**
@@ -437,7 +465,7 @@ interface DataTableProps {
437
465
  */
438
466
  groupable?: boolean;
439
467
  /**
440
- * `GroupingState` — an ordered `string[]` of column ids (multi-column → nested groups, e.g. `['region','category']`). An empty/unbound list is ungrouped (byte-identical-off). Group-header rows are collapsible (they ride the expand model). Surfaces through `group-change`; uncontrolled fallback (`$data.groupingDefault`, default `[]`) when unbound — the default is `null` (mirroring `expanded`) so the uncontrolled fallback is reachable and the grouping auto-expand default can activate when a consumer applies grouping without binding `r-model:grouping` (a non-null `[]` default would short-circuit it). All reads are null-guarded, so table-core still receives an array.
468
+ * `GroupingState` — an ordered `string[]` of column ids (multi-column → nested groups, e.g. `['region','category']`). An empty/unbound list is ungrouped (byte-identical-off). Group-header rows are collapsible (they ride the expand model). Surfaces through `group-change`; uncontrolled fallback (`$data.groupingDefault`, default `[]`) when unbound — the default is `null` (mirroring `expanded`) so the uncontrolled fallback is reachable and the grouping auto-expand default can activate when a consumer applies grouping without binding the two-way `grouping` model (a non-null `[]` default would short-circuit it). All reads are null-guarded, so table-core still receives an array.
441
469
  */
442
470
  grouping?: (any[]) | null;
443
471
  defaultGrouping?: (any[]) | null;
@@ -477,7 +505,7 @@ interface DataTableProps {
477
505
  */
478
506
  stickyHeader?: boolean;
479
507
  /**
480
- * `'table'` (default, row-oriented, byte-behaviorally identical to a plain accessible table) | `'grid'` (GA since Phase 63) — lights up the full WAI-ARIA **[grid interaction mode](/components/data-table-grid-mode)**: `role="grid"`, a roving single tab-stop, 2-D APG arrow-key cell navigation, range selection, and clipboard support.
508
+ * `'table'` (default, row-oriented, byte-behaviorally identical to a plain accessible table) | `'grid'` — lights up the full WAI-ARIA **[grid interaction mode](/components/data-table-grid-mode)**: `role="grid"`, a roving single tab-stop, 2-D APG arrow-key cell navigation, range selection, and clipboard support.
481
509
  */
482
510
  interactionMode?: string;
483
511
  /**
@@ -527,13 +555,13 @@ interface DataTableProps {
527
555
  children?: JSX.Element;
528
556
  groupBarSlot?: (ctx: GroupBarSlotCtx) => JSX.Element;
529
557
  selectAllSlot?: (ctx: SelectAllSlotCtx) => JSX.Element;
558
+ selectCellSlot?: (ctx: SelectCellSlotCtx) => JSX.Element;
559
+ detailSlot?: (ctx: DetailSlotCtx) => JSX.Element;
530
560
  colHeaderSlot?: (ctx: ColHeaderSlotCtx) => JSX.Element;
531
561
  filterSlot?: (ctx: FilterSlotCtx) => JSX.Element;
532
- selectCellSlot?: (ctx: SelectCellSlotCtx) => JSX.Element;
533
562
  cellSlot?: (ctx: CellSlotCtx) => JSX.Element;
534
563
  editorSlot?: (ctx: EditorSlotCtx) => JSX.Element;
535
- detailSlot?: (ctx: DetailSlotCtx) => JSX.Element;
536
- slots?: Record<string, (ctx: any) => JSX.Element>;
564
+ slots?: { [key: `colHeader-${string}`]: ((ctx: { columnId: any; column: any; label: any }) => JSX.Element) | undefined; [key: `filter-${string}`]: ((ctx: { columnId: any; value: any; uniqueValues: any; minMax: any; setFilter: any }) => JSX.Element) | undefined; [key: `cell-${string}`]: ((ctx: { columnId: any; column: any; row: any; value: any }) => JSX.Element) | undefined; [key: `editor-${string}`]: ((ctx: { columnId: any; column: any; row: any; value: any; commit: any; cancel: any; autofocus: any }) => JSX.Element) | undefined; [key: string]: ((...args: any[]) => JSX.Element) | undefined; };
537
565
  ref?: (h: DataTableHandle) => void;
538
566
  }
539
567
 
@@ -653,6 +681,13 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
653
681
  // a one-way `:data`) has a base array to whole-array-replace. currentData() then sources
654
682
  // the bound prop when controlled, this fallback otherwise.
655
683
  setDataDefault(data() || []);
684
+ // Apply any per-column `pinned` declaration as the INITIAL pinning state BEFORE createTable
685
+ // reads currentState() below, so the first row model table-core builds is already correctly
686
+ // ordered (getVisibleCells() returns [left-pinned, center, right-pinned]) rather than
687
+ // re-ordering a render later. Covers the `:columns` config-array form, which is available
688
+ // now; `<Column>` children register during mount and are covered by the re-feed watch's own
689
+ // call. One-shot and self-latching — see seedColumnPinning() in stateAssembly.rzts.
690
+ seedColumnPinning();
656
691
  // Build the table instance HERE so the closures below capture the live `table`.
657
692
  table = createTable({
658
693
  // Plain value (NOT a `get data()` getter): an object-literal getter rebinds
@@ -928,6 +963,31 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
928
963
  if (!w) {
929
964
  console.warn('[rozie-data-table] virtual is on for columns but the scroll container has no bounded width; set a CSS width on an ancestor so the column window can be measured');
930
965
  }
966
+ // COLUMN-AXIS FIRST-WINDOW BUMP (quick 260908-vcy). The mount-kick above fires right
967
+ // after `new Virtualizer(...)` — BEFORE colVirtualizer has measured, and before the
968
+ // spacer effects even exist. Any fine-grained subscriber created inside that measure
969
+ // gap takes colPadLeft()/colPadRight()'s `if (!items.length) return 0` early return,
970
+ // which reads ONLY windowVer + editVer and never reaches the live path's reads
971
+ // (forcedColumns()/columnSize()/column state). Its dependency set is therefore those
972
+ // two counters alone — and without this bump nothing ever touches them again, so the
973
+ // subscriber stays frozen at 0 for the lifetime of the component. Measured on Svelte:
974
+ // the FIRST header row's trailing spacer froze at 0px while its siblings (created
975
+ // after measurement, on the live path) tracked correctly; under table-layout:fixed the
976
+ // first row alone establishes column widths, so that one cell collapsed the whole
977
+ // table's scrollWidth from 9000 to 1350. The five other targets re-render wholesale
978
+ // (Solid re-creates these rows), which masked it.
979
+ //
980
+ // The ROW axis has always been safe here only by accident: remeasureWindow() above
981
+ // already routes through bumpWindowVer() on this same post-first-frame callback. This
982
+ // is the column axis's missing equivalent — not a Svelte workaround (the emitted
983
+ // Svelte binding is a correct reactive template_effect; the dependency set is what was
984
+ // wrong), so it is fixed once, in the shared engine's consumer, for all six targets.
985
+ //
986
+ // Routed through bumpWindowVer() rather than a raw `$data.windowVer = $data.windowVer + 1`
987
+ // to keep 87-10's microtask coalescing AND to avoid manufacturing the ROZ138
988
+ // false positive that a second in-body counter self-read would trip (see the
989
+ // mount-kick's own comment above).
990
+ bumpWindowVer();
931
991
  }
932
992
  };
933
993
  if (typeof requestAnimationFrame === 'function') requestAnimationFrame(() => requestAnimationFrame(afterFirstFrame));else setTimeout(afterFirstFrame, 0);
@@ -962,8 +1022,8 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
962
1022
  maybeClearHistoryOnExternalSwap();
963
1023
  if (!table) return;
964
1024
  // Phase 51 req-4: track currentData() (the bound prop OR the uncontrolled
965
- // $data.dataDefault) so a committed edit re-feeds on Lit whether or not r-model:data is
966
- // bound. Compare by reference AND length so a same-length single-cell edit (fresh array,
1025
+ // $data.dataDefault) so a committed edit re-feeds on Lit whether or not the two-way
1026
+ // `data` model is bound. Compare by reference AND length so a same-length single-cell edit (fresh array,
967
1027
  // identical length) still re-feeds.
968
1028
  const d = currentData() || [];
969
1029
  if (d === lastData && d.length === lastDataLen) return;
@@ -987,6 +1047,12 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
987
1047
  // (Consumers memoize the array as with $props.data/$props.sorting; the uncontrolled
988
1048
  // <Column>-children path leaves $props.columns undefined — a stable no-op getter.)
989
1049
  local.columns, colReg()])(), (v) => untrack(() => (() => {
1050
+ // Seed BEFORE the re-feed so the pinning this call may write is part of the state object
1051
+ // table-core receives on this same tick (rather than landing a frame late and re-ordering
1052
+ // getVisibleCells() a render after the columns appear). One-shot and self-latching — see
1053
+ // seedColumnPinning() in stateAssembly.rzts. This is the `<Column>`-children entry point:
1054
+ // those register during mount, so the $onMount call below can find zero columns.
1055
+ seedColumnPinning();
990
1056
  reFeed();
991
1057
  maybeClearHistoryOnExternalSwap();
992
1058
  })()), { defer: true }));
@@ -1149,6 +1215,95 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
1149
1215
  };
1150
1216
  }
1151
1217
 
1218
+ // seedColumnPinning(): apply the per-column `pinned` declaration ('left' | 'right') — from the
1219
+ // `:columns` config array or a `<Column pinned="...">` child — as the INITIAL columnPinning
1220
+ // state, exactly once.
1221
+ //
1222
+ // Until now that field was carried onto every ColumnDef by columnBuilders.rzts and then read by
1223
+ // nobody: `column.getIsPinned()` returned false for a column declared `pinned="left"`, so the
1224
+ // documented per-column API was inert while `r-model:columnPinning` was the only mechanism that
1225
+ // actually pinned anything.
1226
+ //
1227
+ // Three invariants, each load-bearing:
1228
+ // 1. ONE-SHOT, LATCHED ON THE OUTCOME — never merely on "we ran". The latch flips only when
1229
+ // a seed is actually applied, or when a consumer pin is observed; a call that finds no
1230
+ // declared pins leaves it DOWN and will look again on the next re-feed. Latching on the
1231
+ // first call that saw any column at all was wrong, and Lit proved it: `<Column>` children
1232
+ // register INCREMENTALLY there, one re-feed per child, so the first call sees only
1233
+ // `name` — no pins declared yet — and a run-once latch would disable the seed
1234
+ // permanently before `amount pinned="right"` ever registered. The other five targets
1235
+ // batch all children into a single flush, which hid the race entirely. Once a seed HAS
1236
+ // been applied the latch holds, so a subsequent interactive UNPIN is never re-seeded —
1237
+ // which is the whole reason this is not folded into effectiveColumnPinning().
1238
+ // 2. ONLY WHEN NOTHING IS PINNED YET. Note this is NOT an `$props.columnPinning == null`
1239
+ // check: the prop carries `default: () => ({ left: [], right: [] })`, so it is never null
1240
+ // and a bound-vs-unbound test is impossible here. The meaningful question is whether the
1241
+ // consumer has actually expressed a pin — if they have, they own the slice and the
1242
+ // per-column declaration must not fight it; if the state is empty, `pinned` IS the
1243
+ // initial state and applying it is the whole point.
1244
+ // 3. NO CHROME IDS. The select/expander rail is injected by effectiveColumnPinning() on read
1245
+ // and stripped by writeColumnPinning() on write; the persisted state must never contain
1246
+ // them. Seeding only real column ids keeps that discipline.
1247
+ //
1248
+ // Writes BOTH the uncontrolled holder and `$model.columnPinning` so a consumer with a two-way
1249
+ // binding sees the pinning that is actually in effect rather than a stale empty object — but
1250
+ // does NOT emit `pin-change`: this is initial state derived from the consumer's own markup, not
1251
+ // a user action, and firing a change event for it on mount would be noise.
1252
+ //
1253
+ // Called from $onMount (covers the `:columns` config-array form, known at mount) AND from the
1254
+ // re-feed watch (covers `<Column>` children, which register during mount, and a runtime
1255
+ // `:columns` swap that arrives before any pin interaction). The `!defs.length` bail is what
1256
+ // makes the mount call harmless when columns are not known yet — the latch stays down and the
1257
+ // next re-feed seeds instead.
1258
+ let pinSeedApplied = false;
1259
+ function seedColumnPinning(): void {
1260
+ if (pinSeedApplied) return;
1261
+ // Invariant 2: bail if the consumer already pinned something (either an initial bound value
1262
+ // or an interaction that beat us here). Read the same source order effectiveColumnPinning()
1263
+ // does, minus the chrome rail it injects on read — the rail is structural, not a consumer pin.
1264
+ const live = columnPinning() != null ? columnPinning() : columnPinningDefault();
1265
+ const isRealPin = (id: string) => id !== SELECT_COL_ID && id !== EXPANDER_COL_ID;
1266
+ const pinnedAlready = (live && live.left ? live.left : []).filter(isRealPin).length > 0 || (live && live.right ? live.right : []).filter(isRealPin).length > 0;
1267
+ if (pinnedAlready) {
1268
+ pinSeedApplied = true;
1269
+ return;
1270
+ }
1271
+ const defs = columnDefs();
1272
+ if (!defs.length) return;
1273
+ const left: string[] = [];
1274
+ const right: string[] = [];
1275
+ for (let i = 0; i < defs.length; i++) {
1276
+ const def = defs[i];
1277
+ if (!def) continue;
1278
+ if (def.pinned === 'left') left.push(def.id);else if (def.pinned === 'right') right.push(def.id);
1279
+ }
1280
+ // Invariant 1: no declared pins in the column set we can currently SEE is not evidence there
1281
+ // are none — on Lit the set is still filling in. Leave the latch down and try again on the
1282
+ // next re-feed; the only exits are "seeded" (below) and "consumer already pinned" (above).
1283
+ if (!left.length && !right.length) return;
1284
+ pinSeedApplied = true;
1285
+ // Nothing was pinned (invariant 2 above), so this is a plain assignment, not a merge — the
1286
+ // only ids that could be in `live` are the structural chrome rail, which must never be
1287
+ // persisted (invariant 3). Writes the uncontrolled holder AND the two-way model so both
1288
+ // binding modes observe the pinning that is actually in effect; deliberately does NOT route
1289
+ // through writeColumnPinning(), whose job is to emit `pin-change` for real user actions.
1290
+ // Annotated `any` for the same reason currentState() is (see its comment below): the
1291
+ // uncontrolled holder's initial value is the empty literal `{ left: [], right: [] }`, so the
1292
+ // emitted per-target state holder infers `{ left: never[]; right: never[] }` and a genuinely
1293
+ // populated `{ left: string[]; right: string[] }` will not assign to it under the strict
1294
+ // bundled-leaf tsc (react TS2345 on setColumnPinningDefault, solid TS2769, lit TS2322 — one
1295
+ // per target, all the same never[] inference). The runtime shape is correct; this is the
1296
+ // established idiom here rather than widening three per-leaf strict baselines for it.
1297
+ // writeColumnPinning() never hits this because its payload flows through `strip()`, which
1298
+ // returns `any[]`.
1299
+ const seeded: any = {
1300
+ left,
1301
+ right
1302
+ };
1303
+ setColumnPinningDefault(seeded);
1304
+ setColumnPinning(seeded);
1305
+ }
1306
+
1152
1307
  // Assemble the live state object from bound r-model slices (?? uncontrolled fallback).
1153
1308
  // All NINE slices are wired (each ?? its own $data.<slice>Default). table-core reads
1154
1309
  // this whole object as `state`. Return type annotated `any`: the inferred object-literal
@@ -1826,9 +1981,42 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
1826
1981
  function onColumnPinningChangeCb(updater: any) {
1827
1982
  writeColumnPinning(applyUpdater(updater, currentState().columnPinning));
1828
1983
  }
1984
+ // columnSizingInfo is the ONLY slice table-core drives as a rapid, multi-call gesture: a resize
1985
+ // drag fires this updater once on pointerdown (seeding startOffset/startSize/isResizingColumn)
1986
+ // and then again on EVERY mousemove, each call reading the values the previous one wrote.
1987
+ //
1988
+ // That read-your-own-last-write requirement is what makes a plain `$data` read wrong here on
1989
+ // React. These callbacks are handed to createTable in $onMount, so on React the closure binds a
1990
+ // MOUNT-TIME `$data.columnSizingInfo` and never sees its own writes. Measured on the shipped
1991
+ // leaf (quick 260909, instrumented build): pointerdown correctly wrote
1992
+ // `{ startOffset: 163, isResizingColumn: 'col0' }`, and then every single mousemove still saw
1993
+ // `startOffset: null, isResizingColumn: false` — so it computed deltaOffset from 0 instead of
1994
+ // 163 (179, 195, 211… instead of 16, 32, 48…) AND wrote `isResizingColumn: false` back, wiping
1995
+ // the gesture on the first move. Net effect: column resizing did nothing at all on React while
1996
+ // working on the other five. It was masked because the one VR case that drives a real drag was
1997
+ // `test.fixme`'d on React as a presumed harness limitation (87-05 Task 3) — a real CDP drag
1998
+ // with intermediate steps fails there too, which is what proved it a product bug.
1999
+ //
2000
+ // Why not just read `currentState().columnSizingInfo` like the eleven siblings above: that
2001
+ // idiom relies on the re-feed $watch re-passing fresh callbacks into setOptions, and that watch
2002
+ // fires on data/column changes — NOT on every render, and never mid-gesture. It is sufficient
2003
+ // for one-shot updaters (a sort toggle, a selection click) and insufficient here, where
2004
+ // table-core issues a burst of updaters between any two re-feeds.
2005
+ //
2006
+ // Fix: a synchronous module-scope mirror, written on the same tick as the state. Reads prefer
2007
+ // the mirror, so each updater in a gesture composes on the previous one's result regardless of
2008
+ // when the host framework commits. Behaviourally identical on the five targets whose state read
2009
+ // was already synchronous (mirror and state agree), and the slice is transient gesture state
2010
+ // owned entirely by table-core — nothing else writes it — so the mirror cannot drift.
2011
+ // Same trick as the `committedThisSession` commit latch (editCellLifecycle.rzts), for the same
2012
+ // reason: a sync latch survives React's async window.
2013
+ let columnSizingInfoSync: any = null;
1829
2014
  function onColumnSizingInfoChangeCb(updater: any) {
1830
- const next = applyUpdater(updater, columnSizingInfo());
1831
- setColumnSizingInfo(next != null ? next : columnSizingInfo());
2015
+ const base = columnSizingInfoSync != null ? columnSizingInfoSync : columnSizingInfo();
2016
+ const next = applyUpdater(updater, base);
2017
+ if (next == null) return;
2018
+ columnSizingInfoSync = next;
2019
+ setColumnSizingInfo(next);
1832
2020
  }
1833
2021
 
1834
2022
  // ══ Vertical row windowing (phase 53, req-1/2/3/6/9/10) — the virtual-core bridge ════════
@@ -3102,20 +3290,6 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
3102
3290
  const m = editMetaOf(colId);
3103
3291
  return m && m.editorOptions != null ? m.editorOptions : [];
3104
3292
  }
3105
- // hasEditorSlot: this column routes through the consumer's #editor scoped slot (req-2)
3106
- // — true only when the column declared editor='custom' AND the consumer actually
3107
- // provided an #editor slot. Falls through to the built-in editor otherwise (e.g. a
3108
- // column marked 'custom' with no slot supplied degrades to the text editor, never blank).
3109
- function hasEditorSlot(colId: any) {
3110
- return editorTypeOf(colId) === 'custom' && !!(_props.editorSlot ?? _props.slots?.["editor"]);
3111
- }
3112
-
3113
- // hasFilterSlot: the consumer supplied a #filter scoped slot, so it OWNS the per-column
3114
- // filter UI (re-added in 72-05 alongside the dedicated filter row's `<slot name="filter">`
3115
- // host — see the 72-03 removal note in that plan's SUMMARY for why this was briefly gone).
3116
- function hasFilterSlot() {
3117
- return !!(_props.filterSlot ?? _props.slots?.["filter"]);
3118
- }
3119
3293
  function columnIsFilterable(colId: any) {
3120
3294
  const d = defFor(colId);
3121
3295
  return !!(d && d.filterable);
@@ -3126,13 +3300,27 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
3126
3300
  }
3127
3301
 
3128
3302
  // ── Column-management chrome (req-8/9/10/11) ────────────────────────────────────────
3129
- // Live header width (px) for a column — drives the <th> :style width binding. Reads the
3130
- // table-core column size (post-mount) with a fallback to undefined (auto width).
3131
- function headerWidth(colId: any) {
3132
- if (tick() < 0 || !table) return null;
3133
- const col = table.getColumn(colId);
3134
- if (!col) return null;
3135
- const w = col.getSize();
3303
+ // Live header width (px) for a <th> — drives the :style width binding. Takes the table-core
3304
+ // HEADER object (not a column id / Column lookup) and reads header.getSize() — deliberately,
3305
+ // NOT column.getSize(). table-core's own ColumnSizing feature (createHeader, always present —
3306
+ // it is in table-core's builtInFeatures, never opt-in) gives Header.getSize() a recursive
3307
+ // implementation: a LEAF header returns its own column's getSize() (identical to the old
3308
+ // column.getSize() path — byte-identical output there), but a GROUP header (one with
3309
+ // subHeaders, e.g. a multi-level `columns:` entry from buildConfigDef) SUMS every descendant
3310
+ // leaf column's getSize() instead of returning the group's own meaningless single-column
3311
+ // default (150px, table-core's defaultColumnSizing.size — group ColumnDefs never set `size`).
3312
+ // Before this fix, a colspan-N grouped <th> asked the browser for ONE column's worth of width
3313
+ // (150px) while spanning N columns; under `table-layout:fixed` the browser then divides that
3314
+ // single declared width ACROSS all N spanned <th>s (150/5 = 30px for this codebase's 5-column
3315
+ // "Group A" fixture) — a 5x collapse of the actual rendered column width, independent of (and
3316
+ // invisible to) columnSize()/the column virtualizer's windowing math, which reads
3317
+ // table.getVisibleLeafColumns()[i].getSize() directly and was never affected. Root cause +
3318
+ // fix confirmed via .planning/debug/datatable-d13-column-axis-all-targets.md and previously
3319
+ // documented as "Gap 1" in .planning/phases/87-.../deferred-items.md (the identical grouped-
3320
+ // header/colspan mismatch, found independently during 87-05/87-06 authoring and deferred).
3321
+ function headerWidth(header: any) {
3322
+ if (tick() < 0 || !header || typeof header.getSize !== 'function') return null;
3323
+ const w = header.getSize();
3136
3324
  return w != null && w > 0 ? w + 'px' : null;
3137
3325
  }
3138
3326
 
@@ -3249,15 +3437,18 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
3249
3437
  }
3250
3438
  return '';
3251
3439
  }
3252
- // Combined inline style for a <th> (width + pin) and a <td> (pin). Plain string concat —
3253
- // uniform on all 6, no bound-object trap. zIndex=2 (see pinStyle) so a pinned header cell
3254
- // — which hosts the ⋯ menu's floating content — always stacks above the pinned filter-row
3255
- // cell for the same column (zIndex=1, its own default).
3256
- function thStyle(colId: any) {
3440
+ // Combined inline style for a <th> (width + pin). Plain string concat — uniform on all 6, no
3441
+ // bound-object trap. zIndex=2 (see pinStyle) so a pinned header cell — which hosts the ⋯ menu's
3442
+ // floating content — always stacks above the pinned filter-row cell for the same column
3443
+ // (zIndex=1, its own default). Takes the HEADER object (not a bare colId) so headerWidth() can
3444
+ // read header.getSize() — the group-aware size (see headerWidth's own comment). pinStyle still
3445
+ // keys off the plain column id (pinning is a leaf-column-only concern; a grouped header never
3446
+ // pins as a unit in this codebase's model).
3447
+ function thStyle(header: any) {
3257
3448
  let s = '';
3258
- const w = headerWidth(colId);
3449
+ const w = headerWidth(header);
3259
3450
  if (w) s += 'width:' + w + ';';
3260
- s += pinStyle(colId, 2);
3451
+ s += pinStyle(header && header.column ? header.column.id : null, 2);
3261
3452
  return s;
3262
3453
  }
3263
3454
 
@@ -5719,8 +5910,25 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
5719
5910
  fillEdgeScrollRaf = null;
5720
5911
  }
5721
5912
  }
5722
- function cellIndexFromPoint(clientX: any, clientY: any) {
5723
- if (typeof document === 'undefined' || !document.elementFromPoint) return null;
5913
+ // FILL_HITTEST_PROBE_RADIUS_PX / FILL_HITTEST_PROBE_STEP_PX (hardening, authorized alongside the
5914
+ // D-13 grouped-header width fix — .planning/debug/datatable-d13-column-axis-all-targets.md):
5915
+ // resolveCellAt() below hit-tests document.elementFromPoint at ONE exact pixel — structurally
5916
+ // fragile against a scrolling DOM by construction: any narrow column transiting past that fixed
5917
+ // screen point (the edge-auto-scroll rAF loop in fillDrag re-probes the SAME fixed point every
5918
+ // frame while gridScrollEl's content slides underneath it) has only a single-frame-scale window
5919
+ // to be caught, and a probe pixel landing exactly on a border/rounding edge between two adjacent
5920
+ // `<td>`/`<th>`s can miss both. cellIndexFromPoint widens the miss case ONLY: it tries the exact
5921
+ // point first (byte-identical to the pre-hardening behavior on a hit — this is not a behavior
5922
+ // change to the common case) and, only if that misses, fans out a small nearest-first horizontal
5923
+ // search before giving up. Vertical-only misses are not probed (rows are >=24px tall by design —
5924
+ // FILL_EDGE_SCROLL_PX itself — so a row is never narrower than a single scroll step; only COLUMN
5925
+ // width has no such floor, hence a horizontal-only widening).
5926
+ const FILL_HITTEST_PROBE_RADIUS_PX = 8;
5927
+ const FILL_HITTEST_PROBE_STEP_PX = 2;
5928
+
5929
+ // resolveCellAt: the exact-point hit-test (identical logic to the pre-hardening
5930
+ // cellIndexFromPoint body) extracted so it can be re-tried at nearby x-offsets below.
5931
+ function resolveCellAt(clientX: any, clientY: any) {
5724
5932
  let el = document.elementFromPoint(clientX, clientY);
5725
5933
  // Pierce OPEN shadow roots (Lit): document.elementFromPoint retargets to the shadow HOST, so
5726
5934
  // a drag over the Lit data-table's shadow content would otherwise resolve the host (no cell)
@@ -5745,6 +5953,22 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
5745
5953
  c
5746
5954
  };
5747
5955
  }
5956
+ function cellIndexFromPoint(clientX: any, clientY: any) {
5957
+ if (typeof document === 'undefined' || !document.elementFromPoint) return null;
5958
+ const direct = resolveCellAt(clientX, clientY);
5959
+ if (direct) return direct;
5960
+ // Widened nearest-cell search: only reached when the exact point misses. Fans outward
5961
+ // (nearest-first, alternating +/-x) up to FILL_HITTEST_PROBE_RADIUS_PX before giving up —
5962
+ // catches a narrow column that transited past the fixed probe point on this exact frame
5963
+ // without changing which cell wins on an ordinary (non-edge-case) hit.
5964
+ for (let d = FILL_HITTEST_PROBE_STEP_PX; d <= FILL_HITTEST_PROBE_RADIUS_PX; d += FILL_HITTEST_PROBE_STEP_PX) {
5965
+ const right = resolveCellAt(clientX + d, clientY);
5966
+ if (right) return right;
5967
+ const left = resolveCellAt(clientX - d, clientY);
5968
+ if (left) return left;
5969
+ }
5970
+ return null;
5971
+ }
5748
5972
  function onFillHandlePointerDown(e: any) {
5749
5973
  if (!e) return;
5750
5974
  if (e.preventDefault) e.preventDefault();
@@ -6069,39 +6293,73 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
6069
6293
  return row ? row.id : null;
6070
6294
  }
6071
6295
 
6296
+ // resolveEditFocusCellEl(rowIndex, colIndex): the DOM cell element for an edit-focus
6297
+ // target, given an EXPLICIT index pair — NEVER a $data re-read inside this function or its
6298
+ // caller's poll closure. Every call site already knows this pair SYNCHRONOUSLY at the
6299
+ // moment it calls (beginEdit's own rowIndex/colIndex params; commitEdit's stable, already-
6300
+ // settled $data.editingRow/editingCol; beginRowEdit's rowIndex + the target column's
6301
+ // editable[].colIndex from editableColumnsForRow) — threading them through as PARAMETERS,
6302
+ // mirroring focusRowEditorAt's already-proven resolveCellEl(String(rowIndex), colIndex)
6303
+ // shape, closes the React batched-setState staleness class 88-05-SUMMARY.md documented
6304
+ // (REACT_STALE_GATE_SINGLE_CELL_BUG): the OLD gate read $data.editFocusColId SYNCHRONOUSLY,
6305
+ // in the SAME tick beginEdit's own setState call set it, and React's batching meant that
6306
+ // read saw the PRE-edit (null) value. A plain function argument threaded through — never a
6307
+ // reactive re-read — is not subject to that race, uniformly on all 6 targets.
6308
+ function resolveEditFocusCellEl(rowIndex: any, colIndex: any) {
6309
+ if (rowIndex == null || colIndex == null || rowIndex < 0 || colIndex < 0) return null;
6310
+ return resolveCellEl(String(rowIndex), colIndex);
6311
+ }
6312
+
6072
6313
  // Focus the freshly-mounted editor (Pitfall 1, ROZ123): after beginEdit flips the editing
6073
6314
  // state, the editor <input> does not exist until the framework commits the r-if branch
6074
- // (React setState async; Solid/Lit/Svelte next reactive tick). Poll for the
6075
- // [data-editing-cell] element off gridRoot for ~30 frames — the five fast targets resolve
6076
- // on attempt 1, React retries across its async commit. NEVER read $refs eagerly.
6315
+ // (React setState async; Solid/Lit/Svelte next reactive tick). Poll for the target CELL's
6316
+ // marked built-in editor for ~30 frames — the five fast targets resolve on attempt 1, React
6317
+ // retries across its async commit. NEVER read $refs eagerly.
6077
6318
  // B2: selectAll gates the post-focus el.select(). Select-all is right when entering
6078
6319
  // edit IN PLACE (F2/Enter/click/row-edit/validation-reject — no seeded char, the user
6079
6320
  // retypes), but WRONG on a type-to-edit entry where a printable key already seeded the
6080
6321
  // draft (selecting the seeded char makes the next keystroke replace it: Zeta → eta).
6081
6322
  // beginEdit threads `seed == null` so a seeded entry skips the select and the caret sits
6082
6323
  // AFTER the seeded char; every other caller keeps the default select-all.
6083
- // Editor-owns-focus contract (quick 260711-i5m): REVERTS the g52 shadow-piercing helper
6084
- // (commit 5fa30045) that recursed into descendant shadow roots. Built-in editors are
6085
- // host-DOM — the plain direct query resolves them on all 6 targets (no shadow to cross). A
6086
- // #editor DROP-IN now owns its OWN focus via the reactive `autofocus` prop (EditorText's
6087
- // $onMount + lazy $watch), so the host never needs to reach across a Lit drop-in's nested
6088
- // shadow root at all — see the !hasEditorSlot gate below, which skips the host focus call
6089
- // entirely for a drop-in target.
6090
- function focusEditorWhenReady(selectAll = true) {
6324
+ // D-01/D-02 (88-07): REPLACES the deleted presence-check early-return gate AND the
6325
+ // gridRoot-wide `[data-editing-cell]` first-match query with a column-scoped, marker-driven
6326
+ // poll. Resolves the TARGET cell first (resolveEditFocusCellEl, mirroring
6327
+ // focusRowEditorAt's already-shipped two-step resolve), then queries for an editor carrying
6328
+ // BOTH `data-editing-cell` and the host-owned `data-builtin-editor` marker WITHIN that one
6329
+ // cell — "no marker found" (a drop-in target, D-01) bails immediately rather than reaching
6330
+ // into DOM the host does not own; "no editor at all yet" keeps polling (the framework hasn't
6331
+ // committed the r-if branch). This closes the D-02 wrong-column hole the old grid-wide query
6332
+ // left open (every editable cell mounts an editor at once in row-edit mode) AND fixes the
6333
+ // React staleness race documented above, as a side effect of no longer reading
6334
+ // $data.editFocusColId anywhere in this function.
6335
+ function focusEditorWhenReady(rowIndex: any, colIndex: any, selectAll = true) {
6091
6336
  if (!gridRoot) return;
6092
- // Editor-owns-focus contract: when the CURRENT focus target is a #editor drop-in, the host
6093
- // does NOT reach into its DOM — the drop-in self-focuses via its own autofocus prop.
6094
- if (editFocusColId() != null && hasEditorSlot(editFocusColId())) return;
6095
6337
  let attempts = 0;
6096
6338
  const tryFocus = () => {
6097
- const el = gridRoot ? gridRoot.querySelector('[data-editing-cell]') : null;
6339
+ const cellEl = resolveEditFocusCellEl(rowIndex, colIndex);
6340
+ const el = cellEl && cellEl.querySelector ? cellEl.querySelector('[data-editing-cell][data-builtin-editor]') : null;
6341
+ if (!el) {
6342
+ // Editor-owns-focus contract (quick 260711-i5m, D-01): if the cell ALREADY holds an
6343
+ // editor that carries only the base marker, it's a consumer drop-in — it owns its own
6344
+ // focus (EditorText's own $onMount + reactive `autofocus`). Bail immediately, zero
6345
+ // wasted frames, rather than burning the retry ceiling on DOM the host does not own.
6346
+ const anyEditor = cellEl && cellEl.querySelector ? cellEl.querySelector('[data-editing-cell]') : null;
6347
+ if (anyEditor) return;
6348
+ attempts = attempts + 1;
6349
+ if (attempts >= 30) return;
6350
+ if (typeof requestAnimationFrame === 'function') requestAnimationFrame(tryFocus);else setTimeout(tryFocus, 16);
6351
+ return;
6352
+ }
6098
6353
  // Do NOT stomp focus a later interaction already placed in a DIFFERENT column's editor of
6099
6354
  // this row: focusEditorWhenReady only needs to get focus INTO the (first) freshly-mounted
6100
6355
  // editor; if focus already sits in another editable cell, a late rAF re-focus would steal it
6101
6356
  // back to the first editor and break row-mode Tab containment (the non-deterministic B21
6102
6357
  // focus-theft). Compare the OWNING cell's data-col-index (NOT node identity) so a stale
6103
6358
  // SAME-column editor node on Solid's node-replacing re-render still resolves as the target —
6104
- // a genuinely dropped focus is still recovered.
6359
+ // a genuinely dropped focus is still recovered. Stays BROAD on `data-editing-cell` (NOT
6360
+ // narrowed to the builtin marker, D-02): focus validly placed inside a drop-in must still
6361
+ // count as "already placed" — narrowing this check would let a late rAF poll steal focus
6362
+ // back from a drop-in that legitimately holds it.
6105
6363
  const ae = gridRoot && gridRoot.getRootNode ? gridRoot.getRootNode().activeElement : null;
6106
6364
  if (ae && el && ae !== el && ae.closest && gridRoot.contains(ae) && ae.hasAttribute && ae.hasAttribute('data-editing-cell')) {
6107
6365
  const aeCell = ae.closest('[data-grid-cell]');
@@ -6110,18 +6368,12 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
6110
6368
  const elCol = elCell ? elCell.getAttribute('data-col-index') : null;
6111
6369
  if (aeCol != null && aeCol !== elCol) return;
6112
6370
  }
6113
- if (el) {
6114
- el.focus();
6115
- if (selectAll && el.select) {
6116
- try {
6117
- el.select();
6118
- } catch (e: any) {}
6119
- }
6120
- return;
6371
+ el.focus();
6372
+ if (selectAll && el.select) {
6373
+ try {
6374
+ el.select();
6375
+ } catch (e: any) {}
6121
6376
  }
6122
- attempts = attempts + 1;
6123
- if (attempts >= 30) return;
6124
- if (typeof requestAnimationFrame === 'function') requestAnimationFrame(tryFocus);else setTimeout(tryFocus, 16);
6125
6377
  };
6126
6378
  if (typeof requestAnimationFrame === 'function') requestAnimationFrame(tryFocus);else setTimeout(tryFocus, 0);
6127
6379
  }
@@ -6176,8 +6428,10 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
6176
6428
  // from it. Cleared on endEdit.
6177
6429
  setEditFocusColId(colId);
6178
6430
  // B2: a seeded (type-to-edit) entry must NOT select-all — keep the caret after the
6179
- // seeded char so subsequent typing appends instead of replacing it.
6180
- focusEditorWhenReady(seed == null);
6431
+ // seeded char so subsequent typing appends instead of replacing it. rowIndex/colIndex
6432
+ // are THIS call's own params — passed through directly (D-02), never re-read off
6433
+ // $data.editingRow/editingCol inside the poll.
6434
+ focusEditorWhenReady(rowIndex, colIndex, seed == null);
6181
6435
  }
6182
6436
 
6183
6437
  // Return focus to a body cell AFTER the editor unmounts (commit/cancel). The display↔
@@ -6192,6 +6446,21 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
6192
6446
  const tryFocus = () => {
6193
6447
  const el = resolveCellEl(String(row), col);
6194
6448
  if (el) {
6449
+ // B-05 / F-07: do NOT stomp focus a later interaction has already placed on a DIFFERENT
6450
+ // grid cell. This poll runs up to 30 frames after commit/cancel, so without this guard a
6451
+ // user (or a test) who moves to the next cell in that window gets yanked BACK to the cell
6452
+ // they just left — and because the steal depends on rAF timing it presents as a flake that
6453
+ // rotates across targets rather than as a reproducible bug. Measured 2026-09-12: the
6454
+ // data-table-dropins editor case failed with the active cell pinned to the PREVIOUS column
6455
+ // (want 0/2, active 0/1; want 0/1, active 0/0) with no editor open.
6456
+ // focusEditorWhenReady already carries the equivalent guard against the same class of
6457
+ // theft; this is its missing twin. Compare the OWNING CELL (not node identity) so a
6458
+ // node-replacing re-render on Solid still resolves a genuinely dropped focus as recoverable.
6459
+ const ae = gridRoot && gridRoot.getRootNode ? gridRoot.getRootNode().activeElement : null;
6460
+ if (ae && ae !== el && gridRoot.contains && gridRoot.contains(ae) && ae.closest) {
6461
+ const aeCell = ae.closest('[data-grid-cell]');
6462
+ if (aeCell && aeCell !== el) return;
6463
+ }
6195
6464
  el.focus();
6196
6465
  return;
6197
6466
  }
@@ -6291,8 +6560,10 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
6291
6560
  const err = runValidator(colId, newValue, rowOriginal);
6292
6561
  if (err !== true) {
6293
6562
  // D-01: reject — keep the editor open, announce, re-trap focus, NEVER write the model.
6563
+ // $data.editingRow/editingCol are STABLE here (set by a prior beginEdit call, not this
6564
+ // same tick) — safe to read directly, unlike the deleted $data.editFocusColId gate.
6294
6565
  setInvalid(err);
6295
- focusEditorWhenReady();
6566
+ focusEditorWhenReady(editingRow(), editingCol());
6296
6567
  return false;
6297
6568
  }
6298
6569
  setInvalid('');
@@ -6453,22 +6724,22 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
6453
6724
 
6454
6725
  // B21/B22: focus the row-mode editor at a given VISIBLE col index. In full-row edit every
6455
6726
  // editable cell is already mounted as an editor, so this resolves the cell off gridRoot and
6456
- // focuses its [data-editing-cell] control. Bounded rAF-poll (mirrors focusEditorWhenReady)
6457
- // so a React re-render that recreates the input across the focus call still lands it. select-
6458
- // all on text/number editors (a no-op try/catch on select/checkbox).
6459
- // Editor-owns-focus contract (quick 260711-i5m): when the TARGET column is a #editor
6460
- // drop-in, the host does NOT reach into its DOM (early return, before starting the rAF poll
6461
- // at all) — the drop-in self-focuses via its own reactive `autofocus` prop, which the caller
6462
- // (commitRow's B22 reject path / rowEditTab) already flips via $data.editFocusColId. Built-in
6463
- // columns are unaffected (hasEditorSlot is false for them) — unchanged host direct-focus.
6727
+ // focuses its [data-editing-cell][data-builtin-editor] control. Bounded rAF-poll (mirrors
6728
+ // focusEditorWhenReady) so a React re-render that recreates the input across the focus call
6729
+ // still lands it. select-all on text/number editors (a no-op try/catch on select/checkbox).
6730
+ // D-01/D-02 (88-07): the old columnIdAt + presence-check early-return gate is DELETED — the
6731
+ // host-owned data-builtin-editor marker replaces it. If the resolved cell already holds an
6732
+ // editor carrying ONLY the base data-editing-cell marker (no builtin marker), that's a
6733
+ // consumer drop-in — bail immediately, it owns its own focus (EditorText's own $onMount +
6734
+ // reactive `autofocus`, which the caller already flips via $data.editFocusColId). This
6735
+ // two-step resolve (resolveCellEl THEN a scoped query within that one cell) was ALREADY
6736
+ // column-scoped and correct — it is the shape focusEditorWhenReady's D-02 rework adopts.
6464
6737
  function focusRowEditorAt(rowIndex: any, colIndex: any) {
6465
6738
  if (!gridRoot) return;
6466
- const colId = columnIdAt(rowIndex, colIndex);
6467
- if (colId != null && hasEditorSlot(colId)) return;
6468
6739
  let attempts = 0;
6469
6740
  const tryFocus = () => {
6470
6741
  const cellEl = resolveCellEl(String(rowIndex), colIndex);
6471
- const ed = cellEl && cellEl.querySelector ? cellEl.querySelector('[data-editing-cell]') : null;
6742
+ const ed = cellEl && cellEl.querySelector ? cellEl.querySelector('[data-editing-cell][data-builtin-editor]') : null;
6472
6743
  if (ed) {
6473
6744
  ed.focus();
6474
6745
  if (ed.select) {
@@ -6478,6 +6749,11 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
6478
6749
  }
6479
6750
  return;
6480
6751
  }
6752
+ // No marked built-in editor yet in this cell: a drop-in mounted there owns its own
6753
+ // focus — bail immediately (D-02) rather than burning the retry ceiling on DOM the
6754
+ // host does not own. No editor at all yet just means "keep polling."
6755
+ const any = cellEl && cellEl.querySelector ? cellEl.querySelector('[data-editing-cell]') : null;
6756
+ if (any) return;
6481
6757
  attempts = attempts + 1;
6482
6758
  if (attempts >= 30) return;
6483
6759
  if (typeof requestAnimationFrame === 'function') requestAnimationFrame(tryFocus);else setTimeout(tryFocus, 16);
@@ -6524,9 +6800,11 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
6524
6800
  // Editor-owns-focus contract (quick 260711-i5m): the row's FIRST editable column is the
6525
6801
  // initial focus target — editorAutofocusFor derives the reactive `autofocus` #editor scope
6526
6802
  // prop from it (a built-in column is also host-focused below via focusEditorWhenReady; a
6527
- // drop-in column self-focuses via its own $onMount, gated off the host reach-in in Task 3).
6803
+ // drop-in column self-focuses via its own $onMount, gated off the host reach-in via D-01's
6804
+ // marker). editable[0].colIndex is THIS call's own already-resolved value (D-02) — passed
6805
+ // through directly, never re-read off $data.editFocusColId inside the poll.
6528
6806
  setEditFocusColId(editable[0].colId);
6529
- focusEditorWhenReady();
6807
+ focusEditorWhenReady(rowIndex, editable[0].colIndex);
6530
6808
  }
6531
6809
 
6532
6810
  // commitRow(): validate EVERY edited column (D-01 — keep the row open if ANY fails: set
@@ -7317,23 +7595,23 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
7317
7595
 
7318
7596
 
7319
7597
  {<Show when={local.groupable}><div class={"rdt-group-bar-host"} data-rozie-s-d5dcab4c="">
7320
- {(_props.groupBarSlot ?? _props.slots?.['groupBar'])?.({ grouping: groupingKeys(), groupableColumns: groupableColumns(), applyGrouping, clearGrouping }) ?? <Key each={groupingKeys() as readonly any[]} by={(gk) => gk}>{(gk) => <span class={"rdt-group-token"} data-group-token="" data-rozie-s-d5dcab4c="">{rozieDisplay(gk())}</span>}</Key>}
7598
+ {(_props.groupBarSlot ?? _props.slots?.['groupBar'])?.({ get grouping() { return groupingKeys(); }, get groupableColumns() { return groupableColumns(); }, applyGrouping, clearGrouping }) ?? <Key each={groupingKeys() as readonly any[]} by={(gk) => gk}>{(gk) => <span class={"rdt-group-token"} data-group-token="" data-rozie-s-d5dcab4c="">{rozieDisplay(gk())}</span>}</Key>}
7321
7599
  </div></Show>}{<Show when={isWindowed()} fallback={<table aria-rowcount={rozieAttr(gridAriaRowCount())} class={"rozie-data-table" + " " + rozieClass({ 'rdt-sticky': local.stickyHeader })} role={rozieAttr(tableRole())} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLTableElement; target: Element }) => { onGridKeyDown($event); }} onFocusIn={($event: FocusEvent & { currentTarget: HTMLTableElement; target: Element }) => { syncActiveFromEvent($event); }} onFocusOut={($event: FocusEvent & { currentTarget: HTMLTableElement; target: Element }) => { onGridFocusOut($event); }} onMouseDown={($event: MouseEvent & { currentTarget: HTMLTableElement; target: Element }) => { onGridMouseDown($event); }} onDblClick={($event: MouseEvent & { currentTarget: HTMLTableElement; target: Element }) => { onGridDblClick($event); }} onClick={($event: MouseEvent & { currentTarget: HTMLTableElement; target: Element }) => { onGridClick($event); }} data-rozie-s-d5dcab4c="">
7322
7600
  <thead class={"rdt-thead"} role="rowgroup" data-rozie-s-d5dcab4c="">
7323
7601
  <Key each={headerGroups() as readonly any[]} by={(hg) => hg.id}>{(hg, hgLevel) => <tr class={"rdt-tr"} role="row" aria-rowindex={rozieAttr(hgLevel() + 1)} data-rozie-s-d5dcab4c="">
7324
- <Key each={hg().headers as readonly any[]} by={(header) => header.id}>{(header) => <th class={"rdt-th" + " " + rozieClass({ 'rdt-select-th': isSelectColumn(header().column.id), 'rdt-expander-th': isExpanderColumn(header().column.id), 'rdt-th-resizing': columnIsResizing(header().column.id), 'rdt-cell-active': isActiveCell('__header', headerColIndexOf(hg(), header()), hgLevel()) })} role="columnheader" data-col={rozieAttr(header().column.id)} data-grid-cell="" data-row="__header" data-header-level={rozieAttr(hgLevel())} colSpan={rozieAttr(header().colSpan > 1 ? header().colSpan : null)} data-col-index={rozieAttr(headerColIndexOf(hg(), header()))} tabIndex={rozieAttr(cellTabindex('__header', headerColIndexOf(hg(), header()), hgLevel()))} aria-sort={rozieAttr(ariaSortFor(header().column.id))} style={parseInlineStyle(thStyle(header().column.id))} data-rozie-s-d5dcab4c="">
7602
+ <Key each={hg().headers as readonly any[]} by={(header) => header.id}>{(header) => <th class={"rdt-th" + " " + rozieClass({ 'rdt-select-th': isSelectColumn(header().column.id), 'rdt-expander-th': isExpanderColumn(header().column.id), 'rdt-th-resizing': columnIsResizing(header().column.id), 'rdt-cell-active': isActiveCell('__header', headerColIndexOf(hg(), header()), hgLevel()) })} role="columnheader" data-col={rozieAttr(header().column.id)} data-grid-cell="" data-row="__header" data-header-level={rozieAttr(hgLevel())} colSpan={rozieAttr(header().colSpan > 1 ? header().colSpan : null)} data-col-index={rozieAttr(headerColIndexOf(hg(), header()))} tabIndex={rozieAttr(cellTabindex('__header', headerColIndexOf(hg(), header()), hgLevel()))} aria-sort={rozieAttr(ariaSortFor(header().column.id))} style={parseInlineStyle(thStyle(header()))} data-rozie-s-d5dcab4c="">
7325
7603
 
7326
7604
 
7327
7605
  {<Show when={isSelectColumn(header().column.id)} fallback={<Show when={isExpanderColumn(header().column.id)} fallback={<span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
7328
7606
 
7329
7607
  {<Show when={header().column.getCanSort && header().column.getCanSort()} fallback={<span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
7330
7608
  <span class={"rdt-header-label"} data-rozie-s-d5dcab4c="">
7331
- {(_props.colHeaderSlot ?? _props.slots?.['colHeader'])?.({ columnId: header().column.id, column: header().column, label: headerLabel(header().column.id) }) ?? rozieDisplay(headerLabel(header().column.id))}
7609
+ {_props.slots?.[`colHeader-${header().column.id}`]?.({ get columnId() { return header().column.id; }, get column() { return header().column; }, get label() { return headerLabel(header().column.id); } }) ?? (_props.colHeaderSlot ?? _props.slots?.['colHeader'])?.({ get columnId() { return header().column.id; }, get column() { return header().column; }, get label() { return headerLabel(header().column.id); } }) ?? rozieDisplay(headerLabel(header().column.id))}
7332
7610
  </span>
7333
7611
  </span>}><button type="button" class={"rdt-sort-btn"} onClick={($event: MouseEvent & { currentTarget: HTMLButtonElement; target: Element }) => { onHeaderSort(header().column.id, $event); }} data-rozie-s-d5dcab4c="">
7334
7612
 
7335
7613
  <span class={"rdt-header-label"} data-rozie-s-d5dcab4c="">
7336
- {(_props.colHeaderSlot ?? _props.slots?.['colHeader'])?.({ columnId: header().column.id, column: header().column, label: headerLabel(header().column.id) }) ?? rozieDisplay(headerLabel(header().column.id))}
7614
+ {_props.slots?.[`colHeader-${header().column.id}`]?.({ get columnId() { return header().column.id; }, get column() { return header().column; }, get label() { return headerLabel(header().column.id); } }) ?? (_props.colHeaderSlot ?? _props.slots?.['colHeader'])?.({ get columnId() { return header().column.id; }, get column() { return header().column; }, get label() { return headerLabel(header().column.id); } }) ?? rozieDisplay(headerLabel(header().column.id))}
7337
7615
  </span>
7338
7616
  <span class={"rdt-sort-ind"} aria-hidden="true" data-rozie-s-d5dcab4c="">{rozieDisplay(sortIndicator(header().column.id))}</span>
7339
7617
  </button></Show>}<Popover trigger="click" placement="bottom-end" strategy="fixed" offset={4} data-rozie-s-d5dcab4c="" anchorSlot={() => (<>
@@ -7348,15 +7626,15 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
7348
7626
 
7349
7627
  <button type="button" aria-label={rozieAttr('Resize ' + headerLabel(header().column.id))} class={"rdt-resize-handle"} onPointerDown={($event: PointerEvent & { currentTarget: HTMLButtonElement; target: Element }) => { onResizeStart(header().column.id, $event); }} onTouchStart={($event: TouchEvent & { currentTarget: HTMLButtonElement; target: Element }) => { onResizeStart(header().column.id, $event); }} data-rozie-s-d5dcab4c=""><span class={"rdt-resize-grip"} aria-hidden="true" data-rozie-s-d5dcab4c="" /></button>
7350
7628
  </span>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="" /></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
7351
- {(_props.selectAllSlot ?? _props.slots?.['selectAll'])?.({ checked: isAllRowsSelected(), indeterminate: isSomeRowsSelected(), toggle: onToggleAllRows }) ?? <Show when={local.selectionMode === 'multiple'}><input type="checkbox" aria-label="Select all rows" class={"rdt-select-all"} checked={isAllRowsSelected()} onChange={($event: Event & { currentTarget: HTMLInputElement; target: Element }) => { onToggleAllRows($event); }} data-rozie-s-d5dcab4c="" /></Show>}
7629
+ {(_props.selectAllSlot ?? _props.slots?.['selectAll'])?.({ get checked() { return isAllRowsSelected(); }, get indeterminate() { return isSomeRowsSelected(); }, get toggle() { return onToggleAllRows; } }) ?? <Show when={local.selectionMode === 'multiple'}><input type="checkbox" aria-label="Select all rows" class={"rdt-select-all"} checked={isAllRowsSelected()} onChange={($event: Event & { currentTarget: HTMLInputElement; target: Element }) => { onToggleAllRows($event); }} data-rozie-s-d5dcab4c="" /></Show>}
7352
7630
  </span></Show>}</th>}</Key>
7353
7631
  </tr>}</Key>
7354
7632
 
7355
7633
  {<Show when={hasAnyFilterableColumn()}><tr class={"rdt-filter-row"} data-rozie-s-d5dcab4c="">
7356
7634
  <Key each={headerGroups()[headerGroups().length - 1].headers as readonly any[]} by={(header) => header.id}>{(header) => <th class={"rdt-filter-cell"} role="presentation" style={parseInlineStyle(pinStyle(header().column.id))} data-rozie-s-d5dcab4c="">
7357
7635
  {<Show when={isSelectColumn(header().column.id)} fallback={<Show when={isExpanderColumn(header().column.id)} fallback={<span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
7358
- {<Show when={columnIsFilterable(header().column.id) && !hasFilterSlot()}><input type="text" aria-label={rozieAttr('Filter ' + headerLabel(header().column.id))} class={"rdt-col-filter"} value={columnFilterValue(header().column.id)} onInput={($event: InputEvent & { currentTarget: HTMLInputElement; target: Element }) => { onColumnFilterInput(header().column.id, $event); }} onClick={($event: MouseEvent & { currentTarget: HTMLInputElement; target: Element }) => { stopEvent($event); }} data-rozie-s-d5dcab4c="" /></Show>}{<Show when={columnIsFilterable(header().column.id)}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
7359
- {(_props.filterSlot ?? _props.slots?.['filter'])?.({ columnId: header().column.id, value: columnFilterValue(header().column.id), uniqueValues: getFacetedUniqueValues(header().column.id), minMax: getFacetedMinMaxValues(header().column.id), setFilter: setColumnFilter })}
7636
+ {<Show when={columnIsFilterable(header().column.id)}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
7637
+ {_props.slots?.[`filter-${header().column.id}`]?.({ get columnId() { return header().column.id; }, get value() { return columnFilterValue(header().column.id); }, get uniqueValues() { return getFacetedUniqueValues(header().column.id); }, get minMax() { return getFacetedMinMaxValues(header().column.id); }, get setFilter() { return setColumnFilter; } }) ?? (_props.filterSlot ?? _props.slots?.['filter'])?.({ get columnId() { return header().column.id; }, get value() { return columnFilterValue(header().column.id); }, get uniqueValues() { return getFacetedUniqueValues(header().column.id); }, get minMax() { return getFacetedMinMaxValues(header().column.id); }, get setFilter() { return setColumnFilter; } }) ?? <input type="text" aria-label={rozieAttr('Filter ' + headerLabel(header().column.id))} class={"rdt-col-filter"} value={columnFilterValue(header().column.id)} onInput={($event: InputEvent & { currentTarget: HTMLInputElement; target: Element }) => { onColumnFilterInput(header().column.id, $event); }} onClick={($event: MouseEvent & { currentTarget: HTMLInputElement; target: Element }) => { stopEvent($event); }} data-rozie-s-d5dcab4c="" />}
7360
7638
  </span></Show>}</span>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="" /></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="" /></Show>}</th>}</Key>
7361
7639
  </tr></Show>}</thead>
7362
7640
 
@@ -7367,27 +7645,27 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
7367
7645
  <Key each={visibleCellsFor(row()) as readonly any[]} by={(cell) => cell.id}>{(cell) => <td class={"rdt-td" + " " + rozieClass({ 'rdt-select-td': isSelectColumn(cell().column.id), 'rdt-expander-td': isExpanderColumn(cell().column.id), 'rdt-in-range': inRange(rowIndexOf(row()), colIndexOf(row(), cell())), 'rdt-cell-active': isActiveCell(String(rowIndexOf(row())), colIndexOf(row(), cell())) })} role={rozieAttr(cellRole())} data-col={rozieAttr(cell().column.id)} data-grid-cell="" data-row={rozieAttr(rowIndexOf(row()))} data-col-index={rozieAttr(colIndexOf(row(), cell()))} tabIndex={rozieAttr(cellTabindex(String(rowIndexOf(row())), colIndexOf(row(), cell())))} style={parseInlineStyle(bodyCellStyle(row(), cell().column.id))} aria-invalid={rozieAttr(cellAriaInvalid(rowIndexOf(row()), colIndexOf(row(), cell())))} data-in-range={rozieAttr(inRange(rowIndexOf(row()), colIndexOf(row(), cell())) ? 'true' : null)} data-agg-cell={rozieAttr(cellIsAggregated(cell()) ? cell().column.id : null)} data-rozie-s-d5dcab4c="">
7368
7646
 
7369
7647
  {<Show when={isExpanderColumn(cell().column.id)} fallback={<Show when={isSelectColumn(cell().column.id)} fallback={<Show when={cellIsGrouped(cell())} fallback={<Show when={isEditing(rowIndexOf(row()), colIndexOf(row(), cell()))} fallback={<Show when={cellIsPlaceholder(cell())} fallback={<span class={"rdt-cell-value"} data-rozie-s-d5dcab4c="">
7370
- {(_props.cellSlot ?? _props.slots?.['cell'])?.({ columnId: cell().column.id, column: cell().column, row: cellSlotRow(row()), value: cell().getValue() }) ?? rozieDisplay(cell().getValue())}
7648
+ {_props.slots?.[`cell-${cell().column.id}`]?.({ get columnId() { return cell().column.id; }, get column() { return cell().column; }, get row() { return cellSlotRow(row()); }, get value() { return cell().getValue(); } }) ?? (_props.cellSlot ?? _props.slots?.['cell'])?.({ get columnId() { return cell().column.id; }, get column() { return cell().column; }, get row() { return cellSlotRow(row()); }, get value() { return cell().getValue(); } }) ?? rozieDisplay(cell().getValue())}
7371
7649
  </span>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="" /></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
7372
- {<Show when={hasEditorSlot(cell().column.id)} fallback={<Show when={editorTypeOf(cell().column.id) === 'number'} fallback={<Show when={editorTypeOf(cell().column.id) === 'select'} fallback={<Show when={editorTypeOf(cell().column.id) === 'checkbox'} fallback={<input type="text" data-editing-cell="" class={"rdt-cell-editor"} value={editorValueFor(cell().column.id)} onInput={($event: InputEvent & { currentTarget: HTMLInputElement; target: Element }) => { onCellEditorInput(cell().column.id, $event); }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorKeyDown($event); }} onBlur={($event: FocusEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorBlur($event); }} data-rozie-s-d5dcab4c="" />}><input type="checkbox" data-editing-cell="" class={"rdt-cell-editor"} checked={editorCheckedFor(cell().column.id)} onChange={($event: Event & { currentTarget: HTMLInputElement; target: Element }) => { onCellEditorCheckbox(cell().column.id, $event); }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorKeyDown($event); }} onBlur={($event: FocusEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorBlur($event); }} data-rozie-s-d5dcab4c="" /></Show>}><select data-editing-cell="" class={"rdt-cell-editor"} value={editorValueFor(cell().column.id)} onChange={($event: Event & { currentTarget: HTMLSelectElement; target: Element }) => { onCellEditorInput(cell().column.id, $event); }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLSelectElement; target: Element }) => { onEditorKeyDown($event); }} onBlur={($event: FocusEvent & { currentTarget: HTMLSelectElement; target: Element }) => { onEditorBlur($event); }} data-rozie-s-d5dcab4c="">
7650
+ {<Show when={editorTypeOf(cell().column.id) === 'number'} fallback={<Show when={editorTypeOf(cell().column.id) === 'select'} fallback={<Show when={editorTypeOf(cell().column.id) === 'checkbox'} fallback={<Show when={editorTypeOf(cell().column.id) === 'custom'} fallback={<input type="text" data-editing-cell="" data-builtin-editor="" class={"rdt-cell-editor"} value={editorValueFor(cell().column.id)} onInput={($event: InputEvent & { currentTarget: HTMLInputElement; target: Element }) => { onCellEditorInput(cell().column.id, $event); }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorKeyDown($event); }} onBlur={($event: FocusEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorBlur($event); }} data-rozie-s-d5dcab4c="" />}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
7651
+ {_props.slots?.[`editor-${cell().column.id}`]?.({ get columnId() { return cell().column.id; }, get column() { return cell().column; }, get row() { return cellSlotRow(row()); }, get value() { return editorValueFor(cell().column.id); }, get commit() { return editorCommitFor(cell().column.id); }, get cancel() { return editorCancelFor(); }, get autofocus() { return editorAutofocusFor(cell().column.id, rowIndexOf(row())); } }) ?? (_props.editorSlot ?? _props.slots?.['editor'])?.({ get columnId() { return cell().column.id; }, get column() { return cell().column; }, get row() { return cellSlotRow(row()); }, get value() { return editorValueFor(cell().column.id); }, get commit() { return editorCommitFor(cell().column.id); }, get cancel() { return editorCancelFor(); }, get autofocus() { return editorAutofocusFor(cell().column.id, rowIndexOf(row())); } }) ?? <input type="text" data-editing-cell="" data-builtin-editor="" class={"rdt-cell-editor"} value={editorValueFor(cell().column.id)} onInput={($event: InputEvent & { currentTarget: HTMLInputElement; target: Element }) => { onCellEditorInput(cell().column.id, $event); }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorKeyDown($event); }} onBlur={($event: FocusEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorBlur($event); }} data-rozie-s-d5dcab4c="" />}
7652
+ </span></Show>}><input type="checkbox" data-editing-cell="" data-builtin-editor="" class={"rdt-cell-editor"} checked={editorCheckedFor(cell().column.id)} onChange={($event: Event & { currentTarget: HTMLInputElement; target: Element }) => { onCellEditorCheckbox(cell().column.id, $event); }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorKeyDown($event); }} onBlur={($event: FocusEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorBlur($event); }} data-rozie-s-d5dcab4c="" /></Show>}><select data-editing-cell="" data-builtin-editor="" class={"rdt-cell-editor"} value={editorValueFor(cell().column.id)} onChange={($event: Event & { currentTarget: HTMLSelectElement; target: Element }) => { onCellEditorInput(cell().column.id, $event); }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLSelectElement; target: Element }) => { onEditorKeyDown($event); }} onBlur={($event: FocusEvent & { currentTarget: HTMLSelectElement; target: Element }) => { onEditorBlur($event); }} data-rozie-s-d5dcab4c="">
7373
7653
  <Key each={editorOptionsOf(cell().column.id) as readonly any[]} by={(opt) => opt.value}>{(opt) => <option value={rozieAttr(opt().value)} data-rozie-s-d5dcab4c="">{rozieDisplay(opt().label)}</option>}</Key>
7374
- </select></Show>}><input type="number" data-editing-cell="" class={"rdt-cell-editor"} value={editorValueFor(cell().column.id)} onInput={($event: InputEvent & { currentTarget: HTMLInputElement; target: Element }) => { onCellEditorInput(cell().column.id, $event); }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorKeyDown($event); }} onBlur={($event: FocusEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorBlur($event); }} data-rozie-s-d5dcab4c="" /></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
7375
- {(_props.editorSlot ?? _props.slots?.['editor'])?.({ columnId: cell().column.id, column: cell().column, row: cellSlotRow(row()), value: editorValueFor(cell().column.id), commit: editorCommitFor(cell().column.id), cancel: editorCancelFor(), autofocus: editorAutofocusFor(cell().column.id, rowIndexOf(row())) })}
7376
- </span></Show>}</span></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
7654
+ </select></Show>}><input type="number" data-editing-cell="" data-builtin-editor="" class={"rdt-cell-editor"} value={editorValueFor(cell().column.id)} onInput={($event: InputEvent & { currentTarget: HTMLInputElement; target: Element }) => { onCellEditorInput(cell().column.id, $event); }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorKeyDown($event); }} onBlur={($event: FocusEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorBlur($event); }} data-rozie-s-d5dcab4c="" /></Show>}</span></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
7377
7655
  <button type="button" data-expander="" aria-expanded={!!rowIsExpanded(row())} aria-label={rozieAttr(rowIsExpanded(row()) ? 'Collapse group' : 'Expand group')} class={"rdt-expander rdt-group-toggle"} onClick={($event: MouseEvent & { currentTarget: HTMLButtonElement; target: Element }) => { onToggleExpand(row(), $event); }} data-rozie-s-d5dcab4c="">{rozieDisplay(rowIsExpanded(row()) ? '▾' : '▸')}</button>
7378
7656
  <span class={"rdt-group-value"} data-rozie-s-d5dcab4c="">
7379
- {(_props.cellSlot ?? _props.slots?.['cell'])?.({ columnId: cell().column.id, column: cell().column, row: cellSlotRow(row()), value: cell().getValue() }) ?? rozieDisplay(cell().getValue())}
7657
+ {_props.slots?.[`cell-${cell().column.id}`]?.({ get columnId() { return cell().column.id; }, get column() { return cell().column; }, get row() { return cellSlotRow(row()); }, get value() { return cell().getValue(); } }) ?? (_props.cellSlot ?? _props.slots?.['cell'])?.({ get columnId() { return cell().column.id; }, get column() { return cell().column; }, get row() { return cellSlotRow(row()); }, get value() { return cell().getValue(); } }) ?? rozieDisplay(cell().getValue())}
7380
7658
  </span>
7381
7659
  <span class={"rdt-group-count"} data-rozie-s-d5dcab4c="">{rozieDisplay('(' + groupSubRowCount(row()) + ')')}</span>
7382
7660
  </span></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
7383
- {(_props.selectCellSlot ?? _props.slots?.['selectCell'])?.({ row: cellSlotRow(row()), checked: rowIsSelected(row()), toggle: e => onToggleRow(row(), e) }) ?? <input type="checkbox" aria-label="Select row" class={"rdt-select-row"} checked={rowIsSelected(row())} onChange={($event: Event & { currentTarget: HTMLInputElement; target: Element }) => { onToggleRow(row(), $event); }} data-rozie-s-d5dcab4c="" />}
7661
+ {(_props.selectCellSlot ?? _props.slots?.['selectCell'])?.({ get row() { return cellSlotRow(row()); }, get checked() { return rowIsSelected(row()); }, toggle: e => onToggleRow(row(), e) }) ?? <input type="checkbox" aria-label="Select row" class={"rdt-select-row"} checked={rowIsSelected(row())} onChange={($event: Event & { currentTarget: HTMLInputElement; target: Element }) => { onToggleRow(row(), $event); }} data-rozie-s-d5dcab4c="" />}
7384
7662
  </span></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
7385
7663
  {<Show when={rowCanExpand(row())}><button type="button" data-expander="" aria-expanded={!!rowIsExpanded(row())} aria-label={rozieAttr(rowIsExpanded(row()) ? 'Collapse row' : 'Expand row')} class={"rdt-expander"} onClick={($event: MouseEvent & { currentTarget: HTMLButtonElement; target: Element }) => { onToggleExpand(row(), $event); }} data-rozie-s-d5dcab4c="">{rozieDisplay(rowIsExpanded(row()) ? '▾' : '▸')}</button></Show>}</span></Show>}{<Show when={isFillHandleCell(rowIndexOf(row()), colIndexOf(row(), cell()))}><span data-fill-handle="" data-testid="fill-handle" aria-hidden="true" class={"rdt-fill-handle"} onPointerDown={($event: PointerEvent & { currentTarget: HTMLSpanElement; target: Element }) => { onFillHandlePointerDown($event); }} data-rozie-s-d5dcab4c="" /></Show>}</td>}</Key>
7386
7664
  </tr>
7387
7665
 
7388
7666
  {<Show when={rowShowsDetail(row())}><tr class={"rdt-detail-row"} role="row" data-detail-row={rozieAttr(row().id)} data-rozie-s-d5dcab4c="">
7389
7667
  <td class={"rdt-detail-cell"} colSpan={rozieAttr(visibleColCount())} data-rozie-s-d5dcab4c="">
7390
- {(_props.detailSlot ?? _props.slots?.['detail'])?.({ row: row().original })}
7668
+ {(_props.detailSlot ?? _props.slots?.['detail'])?.({ get row() { return row().original; } })}
7391
7669
  </td>
7392
7670
  </tr></Show>}</>}</Key>
7393
7671
  </tbody>
@@ -7396,15 +7674,15 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
7396
7674
  <thead class={"rdt-thead"} role="rowgroup" data-rozie-s-d5dcab4c="">
7397
7675
  <Key each={headerGroups() as readonly any[]} by={(hg) => hg.id}>{(hg, hgLevel) => <tr class={"rdt-tr"} role="row" aria-rowindex={rozieAttr(hgLevel() + 1)} data-rozie-s-d5dcab4c="">
7398
7676
 
7399
- {<Show when={colsWindowed()}><th class={"rdt-col-spacer"} aria-hidden="true" style={parseInlineStyle('width:' + colPadLeft() + 'px;padding:0;border:0')} data-rozie-s-d5dcab4c="" /></Show>}<Key each={windowedHeadersFor(hg(), hgLevel()) as readonly any[]} by={(wh) => wh.header.id}>{(wh) => <th class={"rdt-th" + " " + rozieClass({ 'rdt-select-th': isSelectColumn(wh().header.column.id), 'rdt-expander-th': isExpanderColumn(wh().header.column.id), 'rdt-th-resizing': columnIsResizing(wh().header.column.id), 'rdt-cell-active': isActiveCell('__header', headerColIndexOf(hg(), wh().header), hgLevel()) })} role="columnheader" data-col={rozieAttr(wh().header.column.id)} data-grid-cell="" data-row="__header" data-header-level={rozieAttr(hgLevel())} colSpan={rozieAttr(wh().span > 1 ? wh().span : null)} data-col-index={rozieAttr(headerColIndexOf(hg(), wh().header))} tabIndex={rozieAttr(cellTabindex('__header', headerColIndexOf(hg(), wh().header), hgLevel()))} aria-sort={rozieAttr(ariaSortFor(wh().header.column.id))} style={parseInlineStyle(thStyle(wh().header.column.id))} data-rozie-s-d5dcab4c="">
7677
+ {<Show when={colsWindowed()}><th class={"rdt-col-spacer"} aria-hidden="true" style={parseInlineStyle('width:' + colPadLeft() + 'px;padding:0;border:0')} data-rozie-s-d5dcab4c="" /></Show>}<Key each={windowedHeadersFor(hg(), hgLevel()) as readonly any[]} by={(wh) => wh.header.id}>{(wh) => <th class={"rdt-th" + " " + rozieClass({ 'rdt-select-th': isSelectColumn(wh().header.column.id), 'rdt-expander-th': isExpanderColumn(wh().header.column.id), 'rdt-th-resizing': columnIsResizing(wh().header.column.id), 'rdt-cell-active': isActiveCell('__header', headerColIndexOf(hg(), wh().header), hgLevel()) })} role="columnheader" data-col={rozieAttr(wh().header.column.id)} data-grid-cell="" data-row="__header" data-header-level={rozieAttr(hgLevel())} colSpan={rozieAttr(wh().span > 1 ? wh().span : null)} data-col-index={rozieAttr(headerColIndexOf(hg(), wh().header))} tabIndex={rozieAttr(cellTabindex('__header', headerColIndexOf(hg(), wh().header), hgLevel()))} aria-sort={rozieAttr(ariaSortFor(wh().header.column.id))} style={parseInlineStyle(thStyle(wh().header))} data-rozie-s-d5dcab4c="">
7400
7678
  {<Show when={isSelectColumn(wh().header.column.id)} fallback={<Show when={isExpanderColumn(wh().header.column.id)} fallback={<span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
7401
7679
  {<Show when={wh().header.column.getCanSort && wh().header.column.getCanSort()} fallback={<span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
7402
7680
  <span class={"rdt-header-label"} data-rozie-s-d5dcab4c="">
7403
- {(_props.colHeaderSlot ?? _props.slots?.['colHeader'])?.({ columnId: wh().header.column.id, column: wh().header.column, label: headerLabel(wh().header.column.id) }) ?? rozieDisplay(headerLabel(wh().header.column.id))}
7681
+ {_props.slots?.[`colHeader-${wh().header.column.id}`]?.({ get columnId() { return wh().header.column.id; }, get column() { return wh().header.column; }, get label() { return headerLabel(wh().header.column.id); } }) ?? (_props.colHeaderSlot ?? _props.slots?.['colHeader'])?.({ get columnId() { return wh().header.column.id; }, get column() { return wh().header.column; }, get label() { return headerLabel(wh().header.column.id); } }) ?? rozieDisplay(headerLabel(wh().header.column.id))}
7404
7682
  </span>
7405
7683
  </span>}><button type="button" class={"rdt-sort-btn"} onClick={($event: MouseEvent & { currentTarget: HTMLButtonElement; target: Element }) => { onHeaderSort(wh().header.column.id, $event); }} data-rozie-s-d5dcab4c="">
7406
7684
  <span class={"rdt-header-label"} data-rozie-s-d5dcab4c="">
7407
- {(_props.colHeaderSlot ?? _props.slots?.['colHeader'])?.({ columnId: wh().header.column.id, column: wh().header.column, label: headerLabel(wh().header.column.id) }) ?? rozieDisplay(headerLabel(wh().header.column.id))}
7685
+ {_props.slots?.[`colHeader-${wh().header.column.id}`]?.({ get columnId() { return wh().header.column.id; }, get column() { return wh().header.column; }, get label() { return headerLabel(wh().header.column.id); } }) ?? (_props.colHeaderSlot ?? _props.slots?.['colHeader'])?.({ get columnId() { return wh().header.column.id; }, get column() { return wh().header.column; }, get label() { return headerLabel(wh().header.column.id); } }) ?? rozieDisplay(headerLabel(wh().header.column.id))}
7408
7686
  </span>
7409
7687
  <span class={"rdt-sort-ind"} aria-hidden="true" data-rozie-s-d5dcab4c="">{rozieDisplay(sortIndicator(wh().header.column.id))}</span>
7410
7688
  </button></Show>}<Popover trigger="click" placement="bottom-end" strategy="fixed" offset={4} data-rozie-s-d5dcab4c="" anchorSlot={() => (<>
@@ -7418,7 +7696,7 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
7418
7696
  </div></Popover>
7419
7697
  <button type="button" aria-label={rozieAttr('Resize ' + headerLabel(wh().header.column.id))} class={"rdt-resize-handle"} onPointerDown={($event: PointerEvent & { currentTarget: HTMLButtonElement; target: Element }) => { onResizeStart(wh().header.column.id, $event); }} onTouchStart={($event: TouchEvent & { currentTarget: HTMLButtonElement; target: Element }) => { onResizeStart(wh().header.column.id, $event); }} data-rozie-s-d5dcab4c=""><span class={"rdt-resize-grip"} aria-hidden="true" data-rozie-s-d5dcab4c="" /></button>
7420
7698
  </span>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="" /></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
7421
- {(_props.selectAllSlot ?? _props.slots?.['selectAll'])?.({ checked: isAllRowsSelected(), indeterminate: isSomeRowsSelected(), toggle: onToggleAllRows }) ?? <Show when={local.selectionMode === 'multiple'}><input type="checkbox" aria-label="Select all rows" class={"rdt-select-all"} checked={isAllRowsSelected()} onChange={($event: Event & { currentTarget: HTMLInputElement; target: Element }) => { onToggleAllRows($event); }} data-rozie-s-d5dcab4c="" /></Show>}
7699
+ {(_props.selectAllSlot ?? _props.slots?.['selectAll'])?.({ get checked() { return isAllRowsSelected(); }, get indeterminate() { return isSomeRowsSelected(); }, get toggle() { return onToggleAllRows; } }) ?? <Show when={local.selectionMode === 'multiple'}><input type="checkbox" aria-label="Select all rows" class={"rdt-select-all"} checked={isAllRowsSelected()} onChange={($event: Event & { currentTarget: HTMLInputElement; target: Element }) => { onToggleAllRows($event); }} data-rozie-s-d5dcab4c="" /></Show>}
7422
7700
  </span></Show>}</th>}</Key>
7423
7701
 
7424
7702
  {<Show when={colsWindowed()}><th class={"rdt-col-spacer"} aria-hidden="true" style={parseInlineStyle('width:' + colPadRight() + 'px;padding:0;border:0')} data-rozie-s-d5dcab4c="" /></Show>}</tr>}</Key>
@@ -7426,8 +7704,8 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
7426
7704
  {<Show when={hasAnyFilterableColumn()}><tr class={"rdt-filter-row"} data-rozie-s-d5dcab4c="">
7427
7705
  {<Show when={colsWindowed()}><th class={"rdt-col-spacer"} aria-hidden="true" style={parseInlineStyle('width:' + colPadLeft() + 'px;padding:0;border:0')} data-rozie-s-d5dcab4c="" /></Show>}<Key each={windowedHeadersFor(headerGroups()[headerGroups().length - 1], headerGroups().length - 1) as readonly any[]} by={(wh) => wh.header.id}>{(wh) => <th class={"rdt-filter-cell"} role="presentation" data-col={rozieAttr(wh().header.column.id)} style={parseInlineStyle(pinStyle(wh().header.column.id))} data-rozie-s-d5dcab4c="">
7428
7706
  {<Show when={isSelectColumn(wh().header.column.id)} fallback={<Show when={isExpanderColumn(wh().header.column.id)} fallback={<span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
7429
- {<Show when={columnIsFilterable(wh().header.column.id) && !hasFilterSlot()}><input type="text" aria-label={rozieAttr('Filter ' + headerLabel(wh().header.column.id))} class={"rdt-col-filter"} value={columnFilterValue(wh().header.column.id)} onInput={($event: InputEvent & { currentTarget: HTMLInputElement; target: Element }) => { onColumnFilterInput(wh().header.column.id, $event); }} onClick={($event: MouseEvent & { currentTarget: HTMLInputElement; target: Element }) => { stopEvent($event); }} data-rozie-s-d5dcab4c="" /></Show>}{<Show when={columnIsFilterable(wh().header.column.id)}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
7430
- {(_props.filterSlot ?? _props.slots?.['filter'])?.({ columnId: wh().header.column.id, value: columnFilterValue(wh().header.column.id), uniqueValues: getFacetedUniqueValues(wh().header.column.id), minMax: getFacetedMinMaxValues(wh().header.column.id), setFilter: setColumnFilter })}
7707
+ {<Show when={columnIsFilterable(wh().header.column.id)}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
7708
+ {_props.slots?.[`filter-${wh().header.column.id}`]?.({ get columnId() { return wh().header.column.id; }, get value() { return columnFilterValue(wh().header.column.id); }, get uniqueValues() { return getFacetedUniqueValues(wh().header.column.id); }, get minMax() { return getFacetedMinMaxValues(wh().header.column.id); }, get setFilter() { return setColumnFilter; } }) ?? (_props.filterSlot ?? _props.slots?.['filter'])?.({ get columnId() { return wh().header.column.id; }, get value() { return columnFilterValue(wh().header.column.id); }, get uniqueValues() { return getFacetedUniqueValues(wh().header.column.id); }, get minMax() { return getFacetedMinMaxValues(wh().header.column.id); }, get setFilter() { return setColumnFilter; } }) ?? <input type="text" aria-label={rozieAttr('Filter ' + headerLabel(wh().header.column.id))} class={"rdt-col-filter"} value={columnFilterValue(wh().header.column.id)} onInput={($event: InputEvent & { currentTarget: HTMLInputElement; target: Element }) => { onColumnFilterInput(wh().header.column.id, $event); }} onClick={($event: MouseEvent & { currentTarget: HTMLInputElement; target: Element }) => { stopEvent($event); }} data-rozie-s-d5dcab4c="" />}
7431
7709
  </span></Show>}</span>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="" /></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="" /></Show>}</th>}</Key>
7432
7710
  {<Show when={colsWindowed()}><th class={"rdt-col-spacer"} aria-hidden="true" style={parseInlineStyle('width:' + colPadRight() + 'px;padding:0;border:0')} data-rozie-s-d5dcab4c="" /></Show>}</tr></Show>}</thead>
7433
7711
 
@@ -7443,20 +7721,20 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
7443
7721
  {<Show when={colsWindowed()}><td class={"rdt-col-spacer"} aria-hidden="true" style={parseInlineStyle('width:' + colPadLeft() + 'px;padding:0;border:0')} data-rozie-s-d5dcab4c="" /></Show>}<Key each={windowedCells(wr().row) as readonly any[]} by={(cell) => cell.id}>{(cell) => <td class={"rdt-td" + " " + rozieClass({ 'rdt-select-td': isSelectColumn(cell().column.id), 'rdt-expander-td': isExpanderColumn(cell().column.id), 'rdt-in-range': inRange(wr().vi.index, colIndexOf(wr().row, cell())), 'rdt-cell-active': isActiveCell(String(wr().vi.index), colIndexOf(wr().row, cell())) })} role={rozieAttr(cellRole())} data-col={rozieAttr(cell().column.id)} data-grid-cell="" data-row={rozieAttr(wr().vi.index)} data-col-index={rozieAttr(colIndexOf(wr().row, cell()))} tabIndex={rozieAttr(cellTabindex(String(wr().vi.index), colIndexOf(wr().row, cell())))} style={parseInlineStyle(bodyCellStyle(wr().row, cell().column.id))} aria-invalid={rozieAttr(cellAriaInvalid(wr().vi.index, colIndexOf(wr().row, cell())))} data-in-range={rozieAttr(inRange(wr().vi.index, colIndexOf(wr().row, cell())) ? 'true' : null)} data-agg-cell={rozieAttr(cellIsAggregated(cell()) ? cell().column.id : null)} data-rozie-s-d5dcab4c="">
7444
7722
 
7445
7723
  {<Show when={isExpanderColumn(cell().column.id)} fallback={<Show when={isSelectColumn(cell().column.id)} fallback={<Show when={cellIsGrouped(cell())} fallback={<Show when={isEditing(wr().vi.index, colIndexOf(wr().row, cell()))} fallback={<Show when={cellIsPlaceholder(cell())} fallback={<span class={"rdt-cell-value"} data-rozie-s-d5dcab4c="">
7446
- {(_props.cellSlot ?? _props.slots?.['cell'])?.({ columnId: cell().column.id, column: cell().column, row: cellSlotRow(wr().row), value: cell().getValue() }) ?? rozieDisplay(cell().getValue())}
7724
+ {_props.slots?.[`cell-${cell().column.id}`]?.({ get columnId() { return cell().column.id; }, get column() { return cell().column; }, get row() { return cellSlotRow(wr().row); }, get value() { return cell().getValue(); } }) ?? (_props.cellSlot ?? _props.slots?.['cell'])?.({ get columnId() { return cell().column.id; }, get column() { return cell().column; }, get row() { return cellSlotRow(wr().row); }, get value() { return cell().getValue(); } }) ?? rozieDisplay(cell().getValue())}
7447
7725
  </span>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="" /></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
7448
- {<Show when={hasEditorSlot(cell().column.id)} fallback={<Show when={editorTypeOf(cell().column.id) === 'number'} fallback={<Show when={editorTypeOf(cell().column.id) === 'select'} fallback={<Show when={editorTypeOf(cell().column.id) === 'checkbox'} fallback={<input type="text" data-editing-cell="" class={"rdt-cell-editor"} value={editorValueFor(cell().column.id)} onInput={($event: InputEvent & { currentTarget: HTMLInputElement; target: Element }) => { onCellEditorInput(cell().column.id, $event); }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorKeyDown($event); }} onBlur={($event: FocusEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorBlur($event); }} data-rozie-s-d5dcab4c="" />}><input type="checkbox" data-editing-cell="" class={"rdt-cell-editor"} checked={editorCheckedFor(cell().column.id)} onChange={($event: Event & { currentTarget: HTMLInputElement; target: Element }) => { onCellEditorCheckbox(cell().column.id, $event); }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorKeyDown($event); }} onBlur={($event: FocusEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorBlur($event); }} data-rozie-s-d5dcab4c="" /></Show>}><select data-editing-cell="" class={"rdt-cell-editor"} value={editorValueFor(cell().column.id)} onChange={($event: Event & { currentTarget: HTMLSelectElement; target: Element }) => { onCellEditorInput(cell().column.id, $event); }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLSelectElement; target: Element }) => { onEditorKeyDown($event); }} onBlur={($event: FocusEvent & { currentTarget: HTMLSelectElement; target: Element }) => { onEditorBlur($event); }} data-rozie-s-d5dcab4c="">
7726
+ {<Show when={editorTypeOf(cell().column.id) === 'number'} fallback={<Show when={editorTypeOf(cell().column.id) === 'select'} fallback={<Show when={editorTypeOf(cell().column.id) === 'checkbox'} fallback={<Show when={editorTypeOf(cell().column.id) === 'custom'} fallback={<input type="text" data-editing-cell="" data-builtin-editor="" class={"rdt-cell-editor"} value={editorValueFor(cell().column.id)} onInput={($event: InputEvent & { currentTarget: HTMLInputElement; target: Element }) => { onCellEditorInput(cell().column.id, $event); }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorKeyDown($event); }} onBlur={($event: FocusEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorBlur($event); }} data-rozie-s-d5dcab4c="" />}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
7727
+ {_props.slots?.[`editor-${cell().column.id}`]?.({ get columnId() { return cell().column.id; }, get column() { return cell().column; }, get row() { return cellSlotRow(wr().row); }, get value() { return editorValueFor(cell().column.id); }, get commit() { return editorCommitFor(cell().column.id); }, get cancel() { return editorCancelFor(); }, get autofocus() { return editorAutofocusFor(cell().column.id, wr().vi.index); } }) ?? (_props.editorSlot ?? _props.slots?.['editor'])?.({ get columnId() { return cell().column.id; }, get column() { return cell().column; }, get row() { return cellSlotRow(wr().row); }, get value() { return editorValueFor(cell().column.id); }, get commit() { return editorCommitFor(cell().column.id); }, get cancel() { return editorCancelFor(); }, get autofocus() { return editorAutofocusFor(cell().column.id, wr().vi.index); } }) ?? <input type="text" data-editing-cell="" data-builtin-editor="" class={"rdt-cell-editor"} value={editorValueFor(cell().column.id)} onInput={($event: InputEvent & { currentTarget: HTMLInputElement; target: Element }) => { onCellEditorInput(cell().column.id, $event); }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorKeyDown($event); }} onBlur={($event: FocusEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorBlur($event); }} data-rozie-s-d5dcab4c="" />}
7728
+ </span></Show>}><input type="checkbox" data-editing-cell="" data-builtin-editor="" class={"rdt-cell-editor"} checked={editorCheckedFor(cell().column.id)} onChange={($event: Event & { currentTarget: HTMLInputElement; target: Element }) => { onCellEditorCheckbox(cell().column.id, $event); }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorKeyDown($event); }} onBlur={($event: FocusEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorBlur($event); }} data-rozie-s-d5dcab4c="" /></Show>}><select data-editing-cell="" data-builtin-editor="" class={"rdt-cell-editor"} value={editorValueFor(cell().column.id)} onChange={($event: Event & { currentTarget: HTMLSelectElement; target: Element }) => { onCellEditorInput(cell().column.id, $event); }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLSelectElement; target: Element }) => { onEditorKeyDown($event); }} onBlur={($event: FocusEvent & { currentTarget: HTMLSelectElement; target: Element }) => { onEditorBlur($event); }} data-rozie-s-d5dcab4c="">
7449
7729
  <Key each={editorOptionsOf(cell().column.id) as readonly any[]} by={(opt) => opt.value}>{(opt) => <option value={rozieAttr(opt().value)} data-rozie-s-d5dcab4c="">{rozieDisplay(opt().label)}</option>}</Key>
7450
- </select></Show>}><input type="number" data-editing-cell="" class={"rdt-cell-editor"} value={editorValueFor(cell().column.id)} onInput={($event: InputEvent & { currentTarget: HTMLInputElement; target: Element }) => { onCellEditorInput(cell().column.id, $event); }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorKeyDown($event); }} onBlur={($event: FocusEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorBlur($event); }} data-rozie-s-d5dcab4c="" /></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
7451
- {(_props.editorSlot ?? _props.slots?.['editor'])?.({ columnId: cell().column.id, column: cell().column, row: cellSlotRow(wr().row), value: editorValueFor(cell().column.id), commit: editorCommitFor(cell().column.id), cancel: editorCancelFor(), autofocus: editorAutofocusFor(cell().column.id, wr().vi.index) })}
7452
- </span></Show>}</span></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
7730
+ </select></Show>}><input type="number" data-editing-cell="" data-builtin-editor="" class={"rdt-cell-editor"} value={editorValueFor(cell().column.id)} onInput={($event: InputEvent & { currentTarget: HTMLInputElement; target: Element }) => { onCellEditorInput(cell().column.id, $event); }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorKeyDown($event); }} onBlur={($event: FocusEvent & { currentTarget: HTMLInputElement; target: Element }) => { onEditorBlur($event); }} data-rozie-s-d5dcab4c="" /></Show>}</span></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
7453
7731
  <button type="button" data-expander="" aria-expanded={!!rowIsExpanded(wr().row)} aria-label={rozieAttr(rowIsExpanded(wr().row) ? 'Collapse group' : 'Expand group')} class={"rdt-expander rdt-group-toggle"} onClick={($event: MouseEvent & { currentTarget: HTMLButtonElement; target: Element }) => { onToggleExpand(wr().row, $event); }} data-rozie-s-d5dcab4c="">{rozieDisplay(rowIsExpanded(wr().row) ? '▾' : '▸')}</button>
7454
7732
  <span class={"rdt-group-value"} data-rozie-s-d5dcab4c="">
7455
- {(_props.cellSlot ?? _props.slots?.['cell'])?.({ columnId: cell().column.id, column: cell().column, row: cellSlotRow(wr().row), value: cell().getValue() }) ?? rozieDisplay(cell().getValue())}
7733
+ {_props.slots?.[`cell-${cell().column.id}`]?.({ get columnId() { return cell().column.id; }, get column() { return cell().column; }, get row() { return cellSlotRow(wr().row); }, get value() { return cell().getValue(); } }) ?? (_props.cellSlot ?? _props.slots?.['cell'])?.({ get columnId() { return cell().column.id; }, get column() { return cell().column; }, get row() { return cellSlotRow(wr().row); }, get value() { return cell().getValue(); } }) ?? rozieDisplay(cell().getValue())}
7456
7734
  </span>
7457
7735
  <span class={"rdt-group-count"} data-rozie-s-d5dcab4c="">{rozieDisplay('(' + groupSubRowCount(wr().row) + ')')}</span>
7458
7736
  </span></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
7459
- {(_props.selectCellSlot ?? _props.slots?.['selectCell'])?.({ row: cellSlotRow(wr().row), checked: rowIsSelected(wr().row), toggle: e => onToggleRow(wr().row, e) }) ?? <input type="checkbox" aria-label="Select row" class={"rdt-select-row"} checked={rowIsSelected(wr().row)} onChange={($event: Event & { currentTarget: HTMLInputElement; target: Element }) => { onToggleRow(wr().row, $event); }} data-rozie-s-d5dcab4c="" />}
7737
+ {(_props.selectCellSlot ?? _props.slots?.['selectCell'])?.({ get row() { return cellSlotRow(wr().row); }, get checked() { return rowIsSelected(wr().row); }, toggle: e => onToggleRow(wr().row, e) }) ?? <input type="checkbox" aria-label="Select row" class={"rdt-select-row"} checked={rowIsSelected(wr().row)} onChange={($event: Event & { currentTarget: HTMLInputElement; target: Element }) => { onToggleRow(wr().row, $event); }} data-rozie-s-d5dcab4c="" />}
7460
7738
  </span></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
7461
7739
  {<Show when={rowCanExpand(wr().row)}><button type="button" data-expander="" aria-expanded={!!rowIsExpanded(wr().row)} aria-label={rozieAttr(rowIsExpanded(wr().row) ? 'Collapse row' : 'Expand row')} class={"rdt-expander"} onClick={($event: MouseEvent & { currentTarget: HTMLButtonElement; target: Element }) => { onToggleExpand(wr().row, $event); }} data-rozie-s-d5dcab4c="">{rozieDisplay(rowIsExpanded(wr().row) ? '▾' : '▸')}</button></Show>}</span></Show>}{<Show when={isFillHandleCell(wr().vi.index, colIndexOf(wr().row, cell()))}><span data-fill-handle="" data-testid="fill-handle" aria-hidden="true" class={"rdt-fill-handle"} onPointerDown={($event: PointerEvent & { currentTarget: HTMLSpanElement; target: Element }) => { onFillHandlePointerDown($event); }} data-rozie-s-d5dcab4c="" /></Show>}</td>}</Key>
7462
7740
 
@@ -7464,7 +7742,7 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
7464
7742
 
7465
7743
  {<Show when={rowShowsDetail(wr().row)}><tr class={"rdt-detail-row"} role="row" data-detail-row={rozieAttr(wr().row.id)} data-rozie-s-d5dcab4c="">
7466
7744
  <td class={"rdt-detail-cell"} colSpan={rozieAttr(windowedColSpan())} data-rozie-s-d5dcab4c="">
7467
- {(_props.detailSlot ?? _props.slots?.['detail'])?.({ row: wr().row.original })}
7745
+ {(_props.detailSlot ?? _props.slots?.['detail'])?.({ get row() { return wr().row.original; } })}
7468
7746
  </td>
7469
7747
  </tr></Show>}</>}</Key>
7470
7748