@rozie-ui/data-table-lit 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -231,7 +231,7 @@ render(html`
231
231
  .data=${rows}
232
232
  .filter=${({ columnId, uniqueValues, minMax }) =>
233
233
  columnId === 'category'
234
- ? html`<fieldset>${uniqueValues.map((v) => html`<label><input type="checkbox" /> ${v}</label>`)}</fieldset>`
234
+ ? html`<fieldset>${uniqueValues.map((v: unknown) => html`<label><input type="checkbox" /> ${v}</label>`)}</fieldset>`
235
235
  : html`<input type="range" min=${minMax?.[0]} max=${minMax?.[1]} />`}
236
236
  >
237
237
  <rozie-column field="name" header="Name"></rozie-column>
@@ -262,8 +262,8 @@ const statusOptions = [
262
262
  ];
263
263
 
264
264
  // The #editor slot is the `.editor` property — a function returning a Lit template,
265
- // dispatched by columnId. Pass the slot scope ({ columnId, column, row, value, commit,
266
- // cancel, autofocus }) as element properties; <rozie-editor-select> also takes `.options`.
265
+ // dispatched by columnId. Pass the slot scope ({ columnId, columnLabel, column, row, value,
266
+ // commit, cancel, autofocus }) as element properties; <rozie-editor-select> also takes `.options`.
267
267
  render(html`
268
268
  <rozie-data-table
269
269
  interactionmode="grid"
@@ -273,11 +273,11 @@ render(html`
273
273
  .editor=${({ columnId, column, row, value, commit, cancel, autofocus }) => {
274
274
  const p = { columnId, column, row, value, commit, cancel, autofocus };
275
275
  switch (columnId) {
276
- case 'name': return html`<rozie-editor-text .columnId=${p.columnId} .column=${p.column} .row=${p.row} .value=${p.value} .commit=${p.commit} .cancel=${p.cancel} .autofocus=${p.autofocus}></rozie-editor-text>`;
277
- case 'qty': return html`<rozie-editor-number .columnId=${p.columnId} .column=${p.column} .row=${p.row} .value=${p.value} .commit=${p.commit} .cancel=${p.cancel} .autofocus=${p.autofocus}></rozie-editor-number>`;
278
- case 'status': return html`<rozie-editor-select .columnId=${p.columnId} .column=${p.column} .row=${p.row} .value=${p.value} .commit=${p.commit} .cancel=${p.cancel} .autofocus=${p.autofocus} .options=${statusOptions}></rozie-editor-select>`;
279
- case 'active': return html`<rozie-editor-checkbox .columnId=${p.columnId} .column=${p.column} .row=${p.row} .value=${p.value} .commit=${p.commit} .cancel=${p.cancel} .autofocus=${p.autofocus}></rozie-editor-checkbox>`;
280
- case 'score': return html`<rozie-editor-date .columnId=${p.columnId} .column=${p.column} .row=${p.row} .value=${p.value} .commit=${p.commit} .cancel=${p.cancel} .autofocus=${p.autofocus}></rozie-editor-date>`;
276
+ case 'name': return html`<rozie-editor-text .columnId=${p.columnId} .columnLabel=${p.columnLabel} .column=${p.column} .row=${p.row} .value=${p.value} .commit=${p.commit} .cancel=${p.cancel} .autofocus=${p.autofocus}></rozie-editor-text>`;
277
+ case 'qty': return html`<rozie-editor-number .columnId=${p.columnId} .columnLabel=${p.columnLabel} .column=${p.column} .row=${p.row} .value=${p.value} .commit=${p.commit} .cancel=${p.cancel} .autofocus=${p.autofocus}></rozie-editor-number>`;
278
+ case 'status': return html`<rozie-editor-select .columnId=${p.columnId} .columnLabel=${p.columnLabel} .column=${p.column} .row=${p.row} .value=${p.value} .commit=${p.commit} .cancel=${p.cancel} .autofocus=${p.autofocus} .options=${statusOptions}></rozie-editor-select>`;
279
+ case 'active': return html`<rozie-editor-checkbox .columnId=${p.columnId} .columnLabel=${p.columnLabel} .column=${p.column} .row=${p.row} .value=${p.value} .commit=${p.commit} .cancel=${p.cancel} .autofocus=${p.autofocus}></rozie-editor-checkbox>`;
280
+ case 'score': return html`<rozie-editor-date .columnId=${p.columnId} .columnLabel=${p.columnLabel} .column=${p.column} .row=${p.row} .value=${p.value} .commit=${p.commit} .cancel=${p.cancel} .autofocus=${p.autofocus}></rozie-editor-date>`;
281
281
  default: return null;
282
282
  }
283
283
  }}
@@ -309,8 +309,8 @@ let rows = [
309
309
  let columnFilters: { id: string; value: unknown }[] = [];
310
310
 
311
311
  // The #filter slot is the `.filter` property — a function returning a Lit template,
312
- // dispatched by columnId. Pass the slot scope ({ columnId, value, uniqueValues, minMax,
313
- // setFilter }) as element properties.
312
+ // dispatched by columnId. Pass the slot scope ({ columnId, columnLabel, value, uniqueValues,
313
+ // minMax, setFilter }) as element properties.
314
314
  render(html`
315
315
  <rozie-data-table
316
316
  .data=${rows}
@@ -319,9 +319,9 @@ render(html`
319
319
  .filter=${({ columnId, value, setFilter, uniqueValues, minMax }) => {
320
320
  const p = { columnId, value, setFilter, uniqueValues, minMax };
321
321
  switch (columnId) {
322
- case 'name': return html`<rozie-filter-text .columnId=${p.columnId} .value=${p.value} .setFilter=${p.setFilter}></rozie-filter-text>`;
323
- case 'category': return html`<rozie-filter-select .columnId=${p.columnId} .value=${p.value} .setFilter=${p.setFilter} .uniqueValues=${p.uniqueValues}></rozie-filter-select>`;
324
- case 'price': return html`<rozie-filter-number-range .columnId=${p.columnId} .value=${p.value} .setFilter=${p.setFilter} .minMax=${p.minMax}></rozie-filter-number-range>`;
322
+ case 'name': return html`<rozie-filter-text .columnId=${p.columnId} .columnLabel=${p.columnLabel} .value=${p.value} .setFilter=${p.setFilter}></rozie-filter-text>`;
323
+ case 'category': return html`<rozie-filter-select .columnId=${p.columnId} .columnLabel=${p.columnLabel} .value=${p.value} .setFilter=${p.setFilter} .uniqueValues=${p.uniqueValues}></rozie-filter-select>`;
324
+ case 'price': return html`<rozie-filter-number-range .columnId=${p.columnId} .columnLabel=${p.columnLabel} .value=${p.value} .setFilter=${p.setFilter} .minMax=${p.minMax}></rozie-filter-number-range>`;
325
325
  default: return null;
326
326
  }
327
327
  }}
@@ -395,7 +395,7 @@ render(html`
395
395
  'filter-category': ({ columnId, uniqueValues, setFilter }) => html`
396
396
  <select @change=${(e: Event) => setFilter(columnId, (e.target as HTMLSelectElement).value || null)}>
397
397
  <option value="">All</option>
398
- ${uniqueValues.map((v) => html`<option value=${String(v)}>${String(v)}</option>`)}
398
+ ${uniqueValues.map((v: unknown) => html`<option value=${String(v)}>${String(v)}</option>`)}
399
399
  </select>`,
400
400
  }}
401
401
  >
@@ -431,6 +431,7 @@ import '@rozie-ui/data-table-lit/themes/shadcn.css'; // or material.css, boot
431
431
  | `pageCount` | `Number` | `null` | | | Explicit total page count for `manual` pagination; overrides rowCount-derived count. |
432
432
  | `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. |
433
433
  | `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. |
434
+ | `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. |
434
435
  | `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. |
435
436
  | `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). |
436
437
  | `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. |
@@ -464,7 +465,9 @@ import '@rozie-ui/data-table-lit/themes/shadcn.css'; // or material.css, boot
464
465
  | `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[]`. |
465
466
  | `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. |
466
467
  | `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. |
468
+ | `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. |
467
469
  | `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. |
470
+ | `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'`. |
468
471
  | `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. |
469
472
  | `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. |
470
473
  | `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. |
@@ -531,7 +534,7 @@ if (el) {
531
534
 
532
535
  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.)
533
536
 
534
- The `detail` (expandable rows), `groupBar` (grouping) and `filter` (faceted filtering) scoped slots follow the SAME render-prop convention: on React they are `renderDetail` / `renderGroupBar` / `renderFilter`; on Solid they are `detailSlot` / `groupBarSlot` / `filterSlot`; on Lit they are the `.detail` / `.groupBar` / `.filter` 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`). 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.
537
+ 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`.
535
538
 
536
539
  `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 Lit the family is the additive **`.rozieSlots`** record property — `.rozieSlots=${{ 'cell-price': (ctx) => html`…` }}` — keyed by the exact `<seam>-<id>` string.
537
540
 
@@ -543,12 +546,13 @@ A misspelled family key does not fail to compile — the emitted type carries a
543
546
  | groupBar | grouping, groupableColumns, applyGrouping, clearGrouping |
544
547
  | selectAll | checked, indeterminate, toggle |
545
548
  | colHeader-<id> | columnId, column, label |
546
- | filter-<id> | columnId, value, uniqueValues, minMax, setFilter |
549
+ | filter-<id> | columnId, value, uniqueValues, minMax, columnLabel, setFilter |
550
+ | placeholder | index, columnId |
547
551
  | selectCell | row, checked, toggle |
548
552
  | cell-<id> | columnId, column, row, value |
549
- | editor-<id> | columnId, column, row, value, commit, cancel, autofocus |
553
+ | editor-<id> | columnId, column, row, value, commit, cancel, columnLabel, autofocus |
550
554
  | detail | row |
551
555
  | colHeader | columnId, column, label |
552
- | filter | columnId, value, uniqueValues, minMax, setFilter |
556
+ | filter | columnId, value, uniqueValues, minMax, columnLabel, setFilter |
553
557
  | cell | columnId, column, row, value |
554
- | editor | columnId, column, row, value, commit, cancel, autofocus |
558
+ | editor | columnId, column, row, value, commit, cancel, columnLabel, autofocus |