@rozie-ui/data-table-solid 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -10
- package/dist/index.cjs +3162 -1095
- package/dist/index.d.cts +69 -1
- package/dist/index.d.mts +69 -1
- package/dist/index.mjs +3086 -1019
- package/dist/source/index.jsx +6377 -0
- package/package.json +8 -3
- package/src/Column.tsx +1 -1
- package/src/DataTable.tsx +1233 -135
- package/src/EditorCheckbox.tsx +38 -4
- package/src/EditorDate.tsx +79 -8
- package/src/EditorNumber.tsx +52 -6
- package/src/EditorSelect.tsx +51 -6
- package/src/EditorText.tsx +34 -5
- package/src/FilterNumberRange.tsx +79 -12
- package/src/FilterSelect.tsx +18 -3
- package/src/FilterText.tsx +48 -9
- package/src/GroupBar.tsx +75 -3
- package/src/helpers/columnDefUtils.ts +73 -1
- package/src/helpers/dateValue.ts +55 -0
- package/src/index.ts +1 -1
- package/src/themes/base.css +96 -75
- package/src/themes/bootstrap.css +59 -34
- package/src/themes/material.css +53 -32
- package/src/themes/shadcn.css +58 -33
package/README.md
CHANGED
|
@@ -95,9 +95,13 @@ export function Demo() {
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
// TOKEN form — the same height via the CSS custom property (the prop wins when
|
|
98
|
-
// both are set; the token is the fallback).
|
|
99
|
-
//
|
|
100
|
-
//
|
|
98
|
+
// both are set; the token is the fallback). DataTable takes no style/class prop, so
|
|
99
|
+
// set the token in your stylesheet on the table's own wrap element, scoped by a
|
|
100
|
+
// parent class (this also outranks the base.css default). estimateRowHeight tunes the seed:
|
|
101
|
+
// .orders .rozie-data-table-wrap { --rozie-data-table-max-height: 400px; }
|
|
102
|
+
// <div class="orders">
|
|
103
|
+
// <DataTable data={rows} virtual estimateRowHeight={48} />
|
|
104
|
+
// </div>
|
|
101
105
|
```
|
|
102
106
|
|
|
103
107
|
### Editable cells (inline edit + validation)
|
|
@@ -252,7 +256,7 @@ export function Demo() {
|
|
|
252
256
|
filterSlot={({ columnId, uniqueValues, minMax }) =>
|
|
253
257
|
columnId === 'category' ? (
|
|
254
258
|
<fieldset>
|
|
255
|
-
{uniqueValues.map((v) => <label><input type="checkbox" /> {String(v)}</label>)}
|
|
259
|
+
{uniqueValues.map((v: unknown) => <label><input type="checkbox" /> {String(v)}</label>)}
|
|
256
260
|
</fieldset>
|
|
257
261
|
) : (
|
|
258
262
|
<input type="range" min={minMax?.[0]} max={minMax?.[1]} />
|
|
@@ -433,7 +437,7 @@ export function Demo() {
|
|
|
433
437
|
'filter-category': ({ columnId, uniqueValues, setFilter }) => (
|
|
434
438
|
<select onChange={(e) => setFilter(columnId, e.currentTarget.value || null)}>
|
|
435
439
|
<option value="">All</option>
|
|
436
|
-
{uniqueValues.map((v) => <option value={String(v)}>{String(v)}</option>)}
|
|
440
|
+
{uniqueValues.map((v: unknown) => <option value={String(v)}>{String(v)}</option>)}
|
|
437
441
|
</select>
|
|
438
442
|
),
|
|
439
443
|
}}
|
|
@@ -471,6 +475,7 @@ import '@rozie-ui/data-table-solid/themes/shadcn.css'; // or material.css, bo
|
|
|
471
475
|
| `pageCount` | `Number` | `null` | | | Explicit total page count for `manual` pagination; overrides rowCount-derived count. |
|
|
472
476
|
| `expandable` | `Boolean` | `false` | | | 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. |
|
|
473
477
|
| `expanded` | `any` | `null` | ✓ | | `ExpandedState` — `{ [rowId]: true }`, or the `true` literal after `expandAll` (declared `type: [Object, Boolean]`). Multi-expand (multiple rows open at once). Surfaces through `expand-change`; uncontrolled fallback (`$data.expandedDefault`) when unbound — the default is `null` so the uncontrolled fallback AND the grouping auto-expand default are reachable (a non-null default would short-circuit them). When grouping is active and `expanded` is untouched, group subtrees auto-expand. |
|
|
478
|
+
| `getRowId` | `Function` | `null` | | | Row identity `(originalRow, index, parentRow?) => string \| number` — the key `rowSelection`, `expanded` and the per-row caches use. Default null → table-core keys rows by position (`"0"`, `"1"`, … and `"0.1"` for sub-rows), so inserting or removing rows above a selected or expanded row moves that state onto whatever row now sits at its index. Supply it whenever rows can be added or removed while state is held (server push, live lists); the result is coerced to a string. |
|
|
474
479
|
| `getSubRows` | `Function` | `null` | | | Table-level child-row accessor `(originalRow, index) => TData[] \| undefined` that drives nested sub-rows. When supplied (with `expandable`), table-core flattens the hierarchy and the expand seam reveals depth-indented child rows. Null → the `#detail` scoped slot is the expand mode. |
|
|
475
480
|
| `groupable` | `Boolean` | `false` | | | Opt-in gate for the **headless `#groupBar`** host region. Default `false` is byte-identical-off. `getGroupedRowModel` is wired unconditionally (inert when `grouping` is empty), so grouping is driven by the `grouping` model; this flag only gates the consumer-facing group-bar surface (the component ships **no** built-in drag UI). |
|
|
476
481
|
| `grouping` | `Array` | `null` | ✓ | | `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. |
|
|
@@ -504,7 +509,9 @@ import '@rozie-ui/data-table-solid/themes/shadcn.css'; // or material.css, bo
|
|
|
504
509
|
| `reorder-change` | Fired when the column order changes. Column reorder is programmatic only — an `applyColumnOrder` call or a two-way `columnOrder` write; the component ships NO drag or menu affordance for it. Payload is the fresh `ColumnOrderState` `string[]`. |
|
|
505
510
|
| `pin-change` | Fired when a column is pinned/unpinned (the per-header pin buttons or a `pinColumn` call). Payload is the fresh `ColumnPinningState` `{ left: string[], right: string[] }` object. |
|
|
506
511
|
| `history-change` | Fired when undo/redo AVAILABILITY changes (one unified grid-wide undo/redo stack, not scoped per-cell or per-row) — EDGE-triggered (only when `canUndo`/`canRedo` actually flip, not on every recorded edit) so a toolbar can enable/disable its buttons without polling. Only meaningful when `undoable` is `true`. Payload is `{ canUndo, canRedo }` booleans. No `history` model prop exists, so there is no model-prop==emit-name duplicate-identifier collision. |
|
|
512
|
+
| `visible-range-change` | Fired when the RENDERED row window changes under row windowing (`virtual`) — the window the table actually renders, overscan included — and once after mount. Payload `{ start, end }` over the full row space (`end` exclusive; with lazy rows that space is `rowCount` long). Deduped: a scroll that does not move the window emits nothing. Pair with `manual` + `rowCount` and sparse `data` to fetch the rows the viewport needs. |
|
|
507
513
|
| `activecell-change` | Fired (grid interaction mode only) whenever the active cell moves — by keyboard navigation or a `focusCell` call. One-way notification; payload is `{ rowIndex, colIndex }` integers over the visible model. Fires on every move including arrow keypresses. |
|
|
514
|
+
| `row-activate` | Fired when the user opens a row (list-style "open this item" — oinbox's email thread). A plain click on a body data row, outside any control in the clicked cell (the selection checkbox, the expander, links, inputs, drop-in widgets and an open editor do NOT activate), in both interaction modes; not a shift/ctrl/meta click (selection gestures), the end of a drag-select, or a `singleClickEdit` click on an editable grid cell (that opens the editor). In grid mode also Enter on a non-editable cell that has no controls of its own (Enter on an editable cell still begins the edit; Enter on a cell with controls still enters them). Group-header and detail rows never activate. Payload is `{ row, index, trigger }` — `row` the original data object, `index` its position in the rendered model (page-relative; the full model when virtual), `trigger` `'click'` or `'keyboard'`. |
|
|
508
515
|
| `range-change` | Fired whenever the rectangular cell-range selection changes — extended by `Shift+Arrow` / `Shift+Click` from the single `extendRange`/`setRangeFocus` call sites (React multi-emit dedup). One-way notification (the range is NOT a `model:true` slice — the model:true count stays at 12, leaving the Angular multi-model-CVA condition untouched); it is a SEPARATE layer from the row-selection slice and the two never corrupt each other. Payload is `getSelectedRange()` → `{ anchor, focus }` where each corner is a `{ rowIndex, colIndex }` index pair over the visible model (integers only — no row data, no DOM node), or `{ anchor: null, focus: null }` when no range is set. |
|
|
509
516
|
| `cell-edit-commit` | Fired when an editable cell commits a NEW, validated value — once per committed cell, from the single `commitEdit` call site (a rejected/invalid commit fires nothing). Pairs with the two-way `data` write (one fresh-array replace per commit). Payload is `{ rowId, columnId, oldValue, newValue }`. `cancel`/`Escape` and a validation failure emit nothing. |
|
|
510
517
|
| `row-edit-commit` | Fired when a FULL-ROW edit commits — once per row save, from the single `commitRow` call site (React multi-emit dedup). A row enters full-row edit via `Shift+F2` or the `editRow` verb (every editable cell edits at once); one save validates every edited cell (a single failure blocks the whole row and emits nothing) then writes the two-way `data` model ONCE (a single fresh-array row-object replace with all changes applied) and fires this event ONCE. `Escape` reverts the whole row as a unit and emits nothing. Payload is `{ rowId, changes }` where `changes` is `[{ columnId, oldValue, newValue }]` for ONLY the columns whose value actually changed. |
|
|
@@ -538,6 +545,8 @@ Beyond props, the component exposes imperative methods (declared once in the Roz
|
|
|
538
545
|
| `focusCell` | Move + focus the active cell (grid interaction mode) — `focusCell(rowIndex, colIndex)`. `rowIndex` is the ABSOLUTE display-order position in `getPrePaginationRowModel().rows` (filter+sort+expand applied, BEFORE pagination/windowing), in BOTH paginated and virtual modes: a paginated `focusCell(abs)` switches to `Math.floor(abs/pageSize)` then focuses; a virtual `focusCell(abs)` scrolls-then-focuses. Args coerced to integers + clamped to bounds. Fires `activecell-change` with the absolute `rowIndex`. (Named `focusCell`, not `focus`: a bare `focus` verb shadows the inherited `HTMLElement.focus` on Lit — ROZ137.) |
|
|
539
546
|
| `getActiveCell` | Return the current active-cell position — `getActiveCell()` → `{ rowIndex, colIndex, isHeader }`. For a body cell `rowIndex` is the ABSOLUTE display-order index (matching `focusCell` / `activecell-change`) and `isHeader` is `false`; for a header-active cell `rowIndex` is `null` and `isHeader` is `true` (integers only — no row data, no DOM node). |
|
|
540
547
|
| `clearActiveCell` | Reset the roving active-cell position to the entry cell and exit interaction mode — `clearActiveCell()`. The next Tab-in re-enters at the entry cell. (Named `clearActiveCell`, not `clear`: distinct from the listbox `clear` selection verb.) |
|
|
548
|
+
| `scrollToRow` | Scroll a row into view on a `virtual` table — `scrollToRow(index, options?)` where `options` mirrors TanStack virtual-core's own `ScrollToOptions` (`{ align?: 'start'|'center'|'end'|'auto', behavior?: 'auto'|'smooth'|'instant' }`) and is forwarded UNCHANGED to `virtualizer.scrollToIndex`. INDEPENDENT of grid-mode focus: never moves the roving active cell and never fires `activecell-change`, callable in either `interactionMode`. `index` is coerced to an integer (never range-clamped here — virtual-core's own `scrollToIndex` already clamps to `[0, options.count-1]`). A no-op when rows are not windowed (`virtual` off, or `'columns'`-only). |
|
|
549
|
+
| `getScrollElement` | Return the scroll-container DOM node the row and column virtualizer(s) observe — `getScrollElement()` → `HTMLElement | null`. The supported replacement for reaching into the internal `.rdt-scroll` class selector. Returns `null` when nothing is windowed. |
|
|
541
550
|
| `getRowIndexRelativeToPage` | Convert an ABSOLUTE display-order row index (the `focusCell` / `getActiveCell` / `activecell-change` space) to the PAGE-RELATIVE index — `getRowIndexRelativeToPage(absRow?)` → `number`. With no argument it converts the current active cell (`abs - pageIndex*pageSize`). In virtual mode (no pagination) it returns the absolute index unchanged. Mirrors MUI `getRowIndexRelativeToVisibleRows`. (Named `getRowIndexRelativeToPage`: collision-clean against the verb/event/prop and Lit ROZ137 reserved sets.) |
|
|
542
551
|
| `editCell` | Programmatically open the editor on a cell — `editCell(rowIndex, colIndex)`, addressed by index over the visible model (args coerced to integers + clamped). No-op on a non-editable cell. (Named `editCell`, not `edit`: collision-clean against the verb/event/prop and Lit ROZ137 reserved sets.) |
|
|
543
552
|
| `commitEditing` | Programmatically commit the open editor — `commitEditing()`. Runs the column validator; on success writes the two-way `data` model and fires one `cell-edit-commit`; on a validation failure keeps the editor open. No-op when no cell is editing. (Named `commitEditing`, not `commit`.) |
|
|
@@ -568,7 +577,7 @@ const cats = handle?.getFacetedUniqueValues('category'); // getFacetedMinMaxValu
|
|
|
568
577
|
|
|
569
578
|
All rendering slots live on the parent `<DataTable>` (a `<Column>` carries metadata only). The `cell` / `colHeader` slots are single renderers dispatched by `columnId` — switch on it to vary the render per column; a column the slot does not render shows the plain accessor value. (On React/Solid these are render-prop props — `renderCell` / `renderColHeader` / `cellSlot` / `colHeaderSlot`; on Lit they are the `.cell` / `.colHeader` properties — the documented cross-framework divergence.)
|
|
570
579
|
|
|
571
|
-
The `detail` (expandable rows), `groupBar` (grouping)
|
|
580
|
+
The `detail` (expandable rows), `groupBar` (grouping), `filter` (faceted filtering) and `placeholder` (lazy-loading skeleton) scoped slots follow the SAME render-prop convention: on React they are `renderDetail` / `renderGroupBar` / `renderFilter` / `renderPlaceholder`; on Solid they are `detailSlot` / `groupBarSlot` / `filterSlot` / `placeholderSlot`; on Lit they are the `.detail` / `.groupBar` / `.filter` / `.placeholder` properties — the documented React render-prop edge (per the cross-framework compatibility bar). On Vue / Svelte / Angular they are ordinary named scoped slots (`#detail` / `#groupBar` / `#filter` / `#placeholder`). The `groupBar` and `filter` slots are HEADLESS — the component ships NO built-in group-bar / facet control, so the consumer builds the UI purely from the exposed slot props. `placeholder` DOES ship a default (a skeleton bar) — fill it only to replace that default with your own loading affordance; it renders for a `virtual` + `manual` + `rowCount` row not yet present in `data`.
|
|
572
581
|
|
|
573
582
|
`cell`, `colHeader`, `filter` and `editor` each also have a **per-column slot family** — a statically-named fill per `columnId` (`cell-price`, `colHeader-price`, `filter-category`, `editor-score`, …) that takes precedence OVER the matching generic slot above it: `<seam>-<id>` → the generic `<seam>` slot → the built-in render. `editor-<id>` (and `editor`) only render on a column declared `editor="custom"`; `filter-<id>` (and `filter`) only render on a column declared `filterable`; `cell-<id>` / `colHeader-<id>` have no gate. The auto-injected row-select / row-expander chrome columns never reach any family — they are excluded structurally. On Solid the family is the additive **`slots`** record prop — `slots={{ 'cell-price': (ctx) => … }}` — keyed by the exact `<seam>-<id>` string.
|
|
574
583
|
|
|
@@ -580,12 +589,13 @@ A misspelled family key does not fail to compile — the emitted type carries a
|
|
|
580
589
|
| groupBar | grouping, groupableColumns, applyGrouping, clearGrouping |
|
|
581
590
|
| selectAll | checked, indeterminate, toggle |
|
|
582
591
|
| colHeader-<id> | columnId, column, label |
|
|
583
|
-
| filter-<id> | columnId, value, uniqueValues, minMax, setFilter |
|
|
592
|
+
| filter-<id> | columnId, value, uniqueValues, minMax, columnLabel, setFilter |
|
|
593
|
+
| placeholder | index, columnId |
|
|
584
594
|
| selectCell | row, checked, toggle |
|
|
585
595
|
| cell-<id> | columnId, column, row, value |
|
|
586
|
-
| editor-<id> | columnId, column, row, value, commit, cancel, autofocus |
|
|
596
|
+
| editor-<id> | columnId, column, row, value, commit, cancel, columnLabel, autofocus |
|
|
587
597
|
| detail | row |
|
|
588
598
|
| colHeader | columnId, column, label |
|
|
589
|
-
| filter | columnId, value, uniqueValues, minMax, setFilter |
|
|
599
|
+
| filter | columnId, value, uniqueValues, minMax, columnLabel, setFilter |
|
|
590
600
|
| cell | columnId, column, row, value |
|
|
591
|
-
| editor | columnId, column, row, value, commit, cancel, autofocus |
|
|
601
|
+
| editor | columnId, column, row, value, commit, cancel, columnLabel, autofocus |
|