@rozie-ui/data-table-solid 0.8.0 → 0.9.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/CHANGELOG.md +17 -0
- package/README.md +4 -3
- package/dist/index.cjs +163 -6
- package/dist/index.d.cts +13 -3
- package/dist/index.d.mts +13 -3
- package/dist/index.mjs +163 -6
- package/dist/source/index.jsx +163 -6
- package/package.json +1 -1
- package/src/DataTable.tsx +192 -12
- package/src/helpers/rangeFill.ts +22 -0
- package/src/themes/base.css +12 -4
- package/src/themes/bootstrap.css +3 -0
- package/src/themes/material.css +3 -0
- package/src/themes/shadcn.css +3 -0
package/src/DataTable.tsx
CHANGED
|
@@ -8,6 +8,7 @@ import { columnRuleKeys } from './helpers/columnRuleUtils';
|
|
|
8
8
|
import { applyUpdater, clamp, focusables } from './helpers/indexMath';
|
|
9
9
|
import { resolveFollow, sameIdSequence } from './helpers/activeFollow';
|
|
10
10
|
import { escapeTsvField, parseTsv, tileGridToBox, tileIndex } from './helpers/tsvGrid';
|
|
11
|
+
import { rangeFillTargets } from './helpers/rangeFill';
|
|
11
12
|
import { replaceRowValue, indexOfRowIn, replaceRowValues } from './helpers/rowValueUtils';
|
|
12
13
|
import { createTable, getCoreRowModel, getSortedRowModel, getFilteredRowModel, getPaginationRowModel, getExpandedRowModel, getGroupedRowModel,
|
|
13
14
|
// Faceted filtering (phase 50 reqs 8-9, D-03). All three are supplied UNCONDITIONALLY
|
|
@@ -131,6 +132,8 @@ __rozieInjectStyle('DataTable-d5dcab4c', `[data-rozie-s-d5dcab4c]:host {
|
|
|
131
132
|
--rdt-border: 1px solid var(--rozie-data-table-border-color, var(--rdt-ds-border-color));
|
|
132
133
|
--rdt-filter-border: 1px solid var(--rozie-data-table-control-border-color, var(--rdt-ds-control-border-color));
|
|
133
134
|
--rdt-filter-radius: var(--rozie-data-table-radius, var(--rdt-ds-radius));
|
|
135
|
+
--rdt-column-border: 1px solid var(--rozie-data-table-column-border-color, var(--rozie-data-table-border-color, var(--rdt-ds-column-border-color)));
|
|
136
|
+
--rdt-pinned-bg: var(--rozie-data-table-pinned-bg, var(--rdt-ds-pinned-bg));
|
|
134
137
|
--rdt-font: var(--rozie-data-table-font, var(--rdt-ds-font));
|
|
135
138
|
--rdt-cell-padding: var(--rozie-data-table-cell-padding, var(--rdt-ds-cell-padding));
|
|
136
139
|
--rdt-header-bg: var(--rozie-data-table-header-bg, var(--rdt-ds-header-bg));
|
|
@@ -152,7 +155,12 @@ __rozieInjectStyle('DataTable-d5dcab4c', `[data-rozie-s-d5dcab4c]:host {
|
|
|
152
155
|
--rdt-page-size-radius: var(--rozie-data-table-radius, var(--rdt-ds-radius));
|
|
153
156
|
}
|
|
154
157
|
.rozie-data-table[data-rozie-s-d5dcab4c] {
|
|
155
|
-
|
|
158
|
+
/* SEPARATE, not collapse. With collapsed borders the browser keeps a sticky cell's shared
|
|
159
|
+
border in the scrolling grid, so a 1px line of scrolled content shows beside pinned
|
|
160
|
+
columns and above the sticky header. Separate borders travel with their cell; zero
|
|
161
|
+
spacing keeps lines 1px because each cell draws only its own sides (see the cell rule). */
|
|
162
|
+
border-collapse: separate;
|
|
163
|
+
border-spacing: 0;
|
|
156
164
|
width: 100%;
|
|
157
165
|
font: var(--rdt-font, 14px system-ui, sans-serif);
|
|
158
166
|
color: var(--rdt-color, inherit);
|
|
@@ -211,8 +219,28 @@ __rozieInjectStyle('DataTable-d5dcab4c', `[data-rozie-s-d5dcab4c]:host {
|
|
|
211
219
|
.rozie-data-table[data-rozie-s-d5dcab4c] .rdt-td[data-rozie-s-d5dcab4c] {
|
|
212
220
|
padding: var(--rdt-cell-padding, 0.5rem 0.75rem);
|
|
213
221
|
text-align: left;
|
|
222
|
+
/* Reset every side, then draw only the row line. Under separate borders each cell owns its
|
|
223
|
+
lines, and the reset keeps a host's plain \`table td { border }\` rule from drawing a second
|
|
224
|
+
set inside the table (this class-scoped rule outranks it). */
|
|
225
|
+
border: 0;
|
|
214
226
|
border-bottom: var(--rdt-border, 1px solid rgba(0, 0, 0, 0.08));
|
|
215
227
|
}
|
|
228
|
+
.rozie-data-table[data-rozie-s-d5dcab4c] .rdt-detail-cell[data-rozie-s-d5dcab4c] {
|
|
229
|
+
border: 0;
|
|
230
|
+
}
|
|
231
|
+
.rozie-data-table.rdt-column-lines[data-rozie-s-d5dcab4c] .rdt-th[data-rozie-s-d5dcab4c],
|
|
232
|
+
.rozie-data-table.rdt-column-lines[data-rozie-s-d5dcab4c] .rdt-td[data-rozie-s-d5dcab4c],
|
|
233
|
+
.rozie-data-table.rdt-column-lines[data-rozie-s-d5dcab4c] .rdt-filter-cell[data-rozie-s-d5dcab4c],
|
|
234
|
+
.rozie-data-table.rdt-column-lines[data-rozie-s-d5dcab4c] .rdt-detail-cell[data-rozie-s-d5dcab4c] {
|
|
235
|
+
border-inline-end: var(--rdt-column-border, 1px solid rgba(0, 0, 0, 0.08));
|
|
236
|
+
}
|
|
237
|
+
.rozie-data-table.rdt-column-lines[data-rozie-s-d5dcab4c] tr[data-rozie-s-d5dcab4c] > [data-rozie-s-d5dcab4c]:first-child:not(.rdt-col-spacer[data-rozie-s-d5dcab4c]),
|
|
238
|
+
.rozie-data-table.rdt-column-lines[data-rozie-s-d5dcab4c] tr[data-rozie-s-d5dcab4c] > .rdt-col-spacer[data-rozie-s-d5dcab4c]:first-child + *[data-rozie-s-d5dcab4c] {
|
|
239
|
+
border-inline-start: var(--rdt-column-border, 1px solid rgba(0, 0, 0, 0.08));
|
|
240
|
+
}
|
|
241
|
+
.rozie-data-table.rdt-column-lines[data-rozie-s-d5dcab4c] .rdt-thead[data-rozie-s-d5dcab4c] > tr[data-rozie-s-d5dcab4c]:first-child > .rdt-th[data-rozie-s-d5dcab4c] {
|
|
242
|
+
border-top: var(--rdt-column-border, 1px solid rgba(0, 0, 0, 0.08));
|
|
243
|
+
}
|
|
216
244
|
.rozie-data-table[data-rozie-s-d5dcab4c] .rdt-thead[data-rozie-s-d5dcab4c] .rdt-th[data-rozie-s-d5dcab4c] {
|
|
217
245
|
font-weight: var(--rdt-header-weight, 600);
|
|
218
246
|
/* OPAQUE default (was rgba(0,0,0,0.03)): a translucent header lets the scrolling body
|
|
@@ -315,8 +343,21 @@ __rozieInjectStyle('DataTable-d5dcab4c', `[data-rozie-s-d5dcab4c]:host {
|
|
|
315
343
|
.rozie-data-table[data-rozie-s-d5dcab4c] .rdt-pinned[data-rozie-s-d5dcab4c] {
|
|
316
344
|
box-sizing: border-box;
|
|
317
345
|
}
|
|
346
|
+
.rozie-data-table[data-rozie-s-d5dcab4c] .rdt-td.rdt-pinned[data-rozie-s-d5dcab4c] {
|
|
347
|
+
background-color: var(--rdt-pinned-bg, #fff);
|
|
348
|
+
}
|
|
349
|
+
.rozie-data-table[data-rozie-s-d5dcab4c] .rdt-group-header[data-rozie-s-d5dcab4c] > .rdt-td.rdt-pinned[data-rozie-s-d5dcab4c] {
|
|
350
|
+
background: linear-gradient(var(--rdt-group-header-bg, rgba(0, 0, 0, 0.025)), var(--rdt-group-header-bg, rgba(0, 0, 0, 0.025))), var(--rdt-pinned-bg, #fff);
|
|
351
|
+
}
|
|
352
|
+
.rozie-data-table[data-rozie-s-d5dcab4c] .rdt-filter-cell.rdt-pinned[data-rozie-s-d5dcab4c] {
|
|
353
|
+
background: linear-gradient(var(--rdt-filter-row-bg, rgba(0, 0, 0, 0.015)), var(--rdt-filter-row-bg, rgba(0, 0, 0, 0.015))), var(--rdt-pinned-bg, #fff);
|
|
354
|
+
}
|
|
355
|
+
.rozie-data-table[data-rozie-s-d5dcab4c] .rdt-td.rdt-pinned.rdt-in-range[data-rozie-s-d5dcab4c] {
|
|
356
|
+
background: linear-gradient(var(--rdt-range-bg, rgba(37, 99, 235, 0.12)), var(--rdt-range-bg, rgba(37, 99, 235, 0.12))), var(--rdt-pinned-bg, #fff);
|
|
357
|
+
}
|
|
318
358
|
.rozie-data-table[data-rozie-s-d5dcab4c] .rdt-filter-cell[data-rozie-s-d5dcab4c] {
|
|
319
359
|
padding: var(--rdt-filter-cell-padding, 0.35rem 0.75rem);
|
|
360
|
+
border: 0;
|
|
320
361
|
border-bottom: var(--rdt-border, 1px solid rgba(0, 0, 0, 0.08));
|
|
321
362
|
}
|
|
322
363
|
.rozie-data-table-wrap[data-rozie-s-d5dcab4c] .rdt-filter-row[data-rozie-s-d5dcab4c] .rdt-col-filter {
|
|
@@ -542,7 +583,9 @@ interface FilterSlotCtx { columnId: string; value: any; uniqueValues: any[]; min
|
|
|
542
583
|
|
|
543
584
|
interface CellSlotCtx { columnId: string; column: TableColumn<any, unknown>; row: any; value: any; }
|
|
544
585
|
|
|
545
|
-
interface EditorSlotCtx { columnId: string; column: TableColumn<any, unknown>; row: any; value: any; commit: (value: any
|
|
586
|
+
interface EditorSlotCtx { columnId: string; column: TableColumn<any, unknown>; row: any; value: any; commit: (value: any, options?: {
|
|
587
|
+
toRange?: boolean;
|
|
588
|
+
}) => void; cancel: () => void; columnLabel: string; autofocus: boolean; rangeSize: number; }
|
|
546
589
|
|
|
547
590
|
interface DataTableProps {
|
|
548
591
|
/**
|
|
@@ -659,6 +702,10 @@ interface DataTableProps {
|
|
|
659
702
|
* Pure-CSS sticky header: the `<thead>` sticks to the top of the scroll container.
|
|
660
703
|
*/
|
|
661
704
|
stickyHeader?: boolean;
|
|
705
|
+
/**
|
|
706
|
+
* Draw a line between columns, plus an outer line at the start of each row and above the header row. The colour follows `--rozie-data-table-border-color` unless `--rozie-data-table-column-border-color` is set.
|
|
707
|
+
*/
|
|
708
|
+
columnLines?: boolean;
|
|
662
709
|
/**
|
|
663
710
|
* `'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.
|
|
664
711
|
*/
|
|
@@ -676,7 +723,7 @@ interface DataTableProps {
|
|
|
676
723
|
*/
|
|
677
724
|
undoLimit?: number;
|
|
678
725
|
/**
|
|
679
|
-
* When `true`, the table holds its current view through its own writes (edit, paste, fill, cut, clear, the boolean toggle, undo and redo): rows keep the membership and order they had before the write and new values render in place, so an edited row does not jump or vanish under an active sort or filter. The view is re-derived (and both mismatch counts reset to 0) when `sorting`, `columnFilters` or `globalFilter` change, when `refreshView()` is called, when the dataset is swapped externally, or when `holdView` is toggled. Paging, page size, column visibility/order/pinning/sizing and expanding rows move through the held rows; a table write or `refreshView()` never changes the page (a sort, filter or grouping change returns to the first page). Falls back to live behaviour (with one console warning) while grouping is active, in `manual` mode, or with tree data (`getSubRows`). Passing a fresh `data` array that did not come from the table (for example reloading from the server after a save) is an external swap: it re-derives the view and clears undo history. Track mismatches with `view-state-change` / `getViewState()`.
|
|
726
|
+
* When `true`, the table holds its current view through its own writes (edit, paste, fill, range fill, cut, clear, the boolean toggle, undo and redo): rows keep the membership and order they had before the write and new values render in place, so an edited row does not jump or vanish under an active sort or filter. The view is re-derived (and both mismatch counts reset to 0) when `sorting`, `columnFilters` or `globalFilter` change, when `refreshView()` is called, when the dataset is swapped externally, or when `holdView` is toggled. Paging, page size, column visibility/order/pinning/sizing and expanding rows move through the held rows; a table write or `refreshView()` never changes the page (a sort, filter or grouping change returns to the first page). Falls back to live behaviour (with one console warning) while grouping is active, in `manual` mode, or with tree data (`getSubRows`). Passing a fresh `data` array that did not come from the table (for example reloading from the server after a save) is an external swap: it re-derives the view and clears undo history. Track mismatches with `view-state-change` / `getViewState()`.
|
|
680
727
|
*/
|
|
681
728
|
holdView?: boolean;
|
|
682
729
|
/**
|
|
@@ -724,7 +771,9 @@ interface DataTableProps {
|
|
|
724
771
|
filterSlot?: (ctx: FilterSlotCtx) => JSX.Element;
|
|
725
772
|
cellSlot?: (ctx: CellSlotCtx) => JSX.Element;
|
|
726
773
|
editorSlot?: (ctx: EditorSlotCtx) => JSX.Element;
|
|
727
|
-
slots?: { [key: `colHeader-${string}`]: ((ctx: { columnId: string; column: TableColumn<any, unknown>; label: string }) => JSX.Element) | undefined; [key: `filter-${string}`]: ((ctx: { columnId: string; value: any; uniqueValues: any[]; minMax: [number, number] | null; columnLabel: string; setFilter: (columnId: string, value: any) => void }) => JSX.Element) | undefined; [key: `cell-${string}`]: ((ctx: { columnId: string; column: TableColumn<any, unknown>; row: any; value: any }) => JSX.Element) | undefined; [key: `editor-${string}`]: ((ctx: { columnId: string; column: TableColumn<any, unknown>; row: any; value: any; commit: (value: any
|
|
774
|
+
slots?: { [key: `colHeader-${string}`]: ((ctx: { columnId: string; column: TableColumn<any, unknown>; label: string }) => JSX.Element) | undefined; [key: `filter-${string}`]: ((ctx: { columnId: string; value: any; uniqueValues: any[]; minMax: [number, number] | null; columnLabel: string; setFilter: (columnId: string, value: any) => void }) => JSX.Element) | undefined; [key: `cell-${string}`]: ((ctx: { columnId: string; column: TableColumn<any, unknown>; row: any; value: any }) => JSX.Element) | undefined; [key: `editor-${string}`]: ((ctx: { columnId: string; column: TableColumn<any, unknown>; row: any; value: any; commit: (value: any, options?: {
|
|
775
|
+
toRange?: boolean;
|
|
776
|
+
}) => void; cancel: () => void; columnLabel: string; autofocus: boolean; rangeSize: number }) => JSX.Element) | undefined; [key: string]: ((...args: any[]) => JSX.Element) | undefined; };
|
|
728
777
|
ref?: (h: DataTableHandle) => void;
|
|
729
778
|
}
|
|
730
779
|
|
|
@@ -770,8 +819,8 @@ export interface DataTableHandle {
|
|
|
770
819
|
}
|
|
771
820
|
|
|
772
821
|
export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
773
|
-
const _merged = mergeProps({ columns: (() => [])() as any[], selectionMode: 'none', manual: false, rowCount: null, pageCount: null, expandable: false, getRowId: null, getSubRows: null, groupable: false, stickyHeader: false, interactionMode: 'table', singleClickEdit: false, undoable: false, undoLimit: 100, holdView: false, virtual: false, estimateRowHeight: 40, autoMeasure: false, maxHeight: '' }, _props);
|
|
774
|
-
const [local, attrs] = splitProps(_merged, ['data', 'columns', 'selectionMode', 'sorting', 'globalFilter', 'columnFilters', 'pagination', 'manual', 'rowCount', 'pageCount', 'expandable', 'expanded', 'getRowId', 'getSubRows', 'groupable', 'grouping', 'rowSelection', 'columnVisibility', 'columnSizing', 'columnOrder', 'columnPinning', 'stickyHeader', 'interactionMode', 'singleClickEdit', 'undoable', 'undoLimit', 'holdView', 'virtual', 'estimateRowHeight', 'autoMeasure', 'maxHeight', 'children', 'ref', 'onActivecellChange', 'onCellEditCommit', 'onExpandChange', 'onFilterChange', 'onGroupChange', 'onHistoryChange', 'onPageChange', 'onPinChange', 'onRangeChange', 'onReorderChange', 'onResizeChange', 'onRowActivate', 'onRowEditCommit', 'onSelectionChange', 'onSortChange', 'onVisibilityChange', 'onViewStateChange', 'onVisibleRangeChange', 'groupBarSlot', 'selectAllSlot', 'placeholderSlot', 'selectCellSlot', 'detailSlot', 'colHeaderSlot', 'filterSlot', 'cellSlot', 'editorSlot', 'slots']);
|
|
822
|
+
const _merged = mergeProps({ columns: (() => [])() as any[], selectionMode: 'none', manual: false, rowCount: null, pageCount: null, expandable: false, getRowId: null, getSubRows: null, groupable: false, stickyHeader: false, columnLines: false, interactionMode: 'table', singleClickEdit: false, undoable: false, undoLimit: 100, holdView: false, virtual: false, estimateRowHeight: 40, autoMeasure: false, maxHeight: '' }, _props);
|
|
823
|
+
const [local, attrs] = splitProps(_merged, ['data', 'columns', 'selectionMode', 'sorting', 'globalFilter', 'columnFilters', 'pagination', 'manual', 'rowCount', 'pageCount', 'expandable', 'expanded', 'getRowId', 'getSubRows', 'groupable', 'grouping', 'rowSelection', 'columnVisibility', 'columnSizing', 'columnOrder', 'columnPinning', 'stickyHeader', 'columnLines', 'interactionMode', 'singleClickEdit', 'undoable', 'undoLimit', 'holdView', 'virtual', 'estimateRowHeight', 'autoMeasure', 'maxHeight', 'children', 'ref', 'onActivecellChange', 'onCellEditCommit', 'onExpandChange', 'onFilterChange', 'onGroupChange', 'onHistoryChange', 'onPageChange', 'onPinChange', 'onRangeChange', 'onReorderChange', 'onResizeChange', 'onRowActivate', 'onRowEditCommit', 'onSelectionChange', 'onSortChange', 'onVisibilityChange', 'onViewStateChange', 'onVisibleRangeChange', 'groupBarSlot', 'selectAllSlot', 'placeholderSlot', 'selectCellSlot', 'detailSlot', 'colHeaderSlot', 'filterSlot', 'cellSlot', 'editorSlot', 'slots']);
|
|
775
824
|
const resolved = () => local.children;
|
|
776
825
|
onMount(() => { local.ref?.({ sortColumn, clearSorting, toggleRowExpanded, expandAll, collapseAll, getExpandedRows, applyGrouping, clearGrouping, getFacetedUniqueValues, getFacetedMinMaxValues, getColumnDefs, toggleAllRows, clearSelection, getSelectedRows, setPage, setRowsPerPage, toggleColumnVisibility, applyColumnOrder, resetColumnSizing, pinColumn, focusCell, getActiveCell, clearActiveCell, scrollToRow, getScrollElement, getRowIndexRelativeToPage, editCell, commitEditing, editRow, getSelectedRange, cut, undo, redo, canUndo, canRedo, clearHistory, refreshView, getViewState }); });
|
|
777
826
|
|
|
@@ -1157,6 +1206,12 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
1157
1206
|
};
|
|
1158
1207
|
document.addEventListener('pointerdown', docPointerDown, true);
|
|
1159
1208
|
}
|
|
1209
|
+
// Range fill: a CAPTURE-phase keydown on the grid root runs before any editor's own keydown
|
|
1210
|
+
// handler — built-in, drop-in or slotted (on Lit the slotted editor's composed path still
|
|
1211
|
+
// passes through the grid root). Registered here in script rather than as a template
|
|
1212
|
+
// `.capture` binding so it is the same code on all six targets. It reads no reactive state
|
|
1213
|
+
// (a mount-time closure would see stale state on React); the commit checks the range.
|
|
1214
|
+
if (gridRoot) gridRoot.addEventListener('keydown', onEditorDropinKeyDownCapture, true);
|
|
1160
1215
|
// WR-04: NO on-mount auto-focus of the entry cell. Auto-focusing here stole focus on
|
|
1161
1216
|
// page load AND was non-deterministic on React/Solid (the entry cell may not be
|
|
1162
1217
|
// committed to the DOM yet at the $onMount microtask). The roving tabindex="0" entry
|
|
@@ -1292,6 +1347,7 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
1292
1347
|
document.removeEventListener('pointerdown', docPointerDown, true);
|
|
1293
1348
|
docPointerDown = null;
|
|
1294
1349
|
}
|
|
1350
|
+
if (gridRoot) gridRoot.removeEventListener('keydown', onEditorDropinKeyDownCapture, true);
|
|
1295
1351
|
if (virtualizerCleanup) virtualizerCleanup();
|
|
1296
1352
|
// Phase 87 87-04: tear down the column virtualizer's scroll-element ResizeObserver too.
|
|
1297
1353
|
// No-op when column windowing was off (cleanup stays null).
|
|
@@ -5840,6 +5896,10 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
5840
5896
|
// any OTHER cell falls through to clearRange() and clears the flag on the way past.
|
|
5841
5897
|
if (rangeTransition) {
|
|
5842
5898
|
rangeTransition = false;
|
|
5899
|
+
} else if (!isHeader && tgt !== cellEl && editingRow() === movedRow && editingCol() === col) {
|
|
5900
|
+
// Focus moving INTO the open editor of this cell (a built-in input, or a slot editor that
|
|
5901
|
+
// focuses itself) is not navigation: keep the range so the editor can commit to it with
|
|
5902
|
+
// Ctrl/Cmd+Enter.
|
|
5843
5903
|
} else if (rangeClickPending && rangeClickPending.isHeader === isHeader && rangeClickPending.r === movedRow && rangeClickPending.c === col) {
|
|
5844
5904
|
rangeClickPending = null;
|
|
5845
5905
|
} else {
|
|
@@ -7207,6 +7267,28 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
7207
7267
|
applyGridToRange(grid, box.r0, box.c0, 'filled');
|
|
7208
7268
|
}
|
|
7209
7269
|
|
|
7270
|
+
// ══ Range fill (Ctrl/Cmd+Enter from an editor; Space on a checkbox range) ════════════════
|
|
7271
|
+
// rangeSize(): how many cells a range fill would write — the range's rows in the ACTIVE
|
|
7272
|
+
// cell's column, or 1 with no range. Exposed to editor slots as `rangeSize` so a custom
|
|
7273
|
+
// editor can say "Apply to 24 cells".
|
|
7274
|
+
function fillTargets() {
|
|
7275
|
+
return rangeFillTargets(normalizedRange(), activeRow(), activeColIndex());
|
|
7276
|
+
}
|
|
7277
|
+
function rangeSize() {
|
|
7278
|
+
return activeIsHeader() ? 1 : fillTargets().size;
|
|
7279
|
+
}
|
|
7280
|
+
|
|
7281
|
+
// fillActiveColumn(value): write ONE value down the active cell's column for every row of the
|
|
7282
|
+
// range, through the same funnel as paste and fill (skip rule, one undo step, one
|
|
7283
|
+
// cell-edit-commit per changed cell, "N of M cells updated"). The value is written AS IS — a
|
|
7284
|
+
// one-column grid of the same value, never stringified — so an array or object survives.
|
|
7285
|
+
function fillActiveColumn(value: any) {
|
|
7286
|
+
const t = fillTargets();
|
|
7287
|
+
const grid = [];
|
|
7288
|
+
for (let i = 0; i < t.size; i++) grid.push([value]);
|
|
7289
|
+
return applyGridToRange(grid, t.top, t.col, 'updated');
|
|
7290
|
+
}
|
|
7291
|
+
|
|
7210
7292
|
// onFillHandlePointerDown: begin a fill-handle drag (req-8 / D-04). The handle sits on the
|
|
7211
7293
|
// range's bottom-right cell; a pointer drag extends the range (reusing setRangeFocus off the
|
|
7212
7294
|
// cell under the pointer) and, on release, value-fills the dragged rectangle. Kept minimal:
|
|
@@ -8029,6 +8111,62 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
8029
8111
|
return true;
|
|
8030
8112
|
}
|
|
8031
8113
|
|
|
8114
|
+
// commitEditToRange(overrideValue): the Ctrl/Cmd+Enter commit. Writes the editor's value to
|
|
8115
|
+
// every selected cell in the editing cell's column through fillActiveColumn (the paste/fill
|
|
8116
|
+
// funnel: skip rule, one undo step, one cell-edit-commit per changed cell, "N of M cells
|
|
8117
|
+
// updated"), then closes the editor and returns focus to the active cell WITHOUT clearing the
|
|
8118
|
+
// range. The value is checked against the editing cell first: if the cell being edited rejects
|
|
8119
|
+
// it, the editor stays open with the error, exactly as a single-cell commit does.
|
|
8120
|
+
function commitEditToRange(overrideValue = undefined) {
|
|
8121
|
+
if (editingRow() < 0) return false;
|
|
8122
|
+
if (committedThisSession) return false;
|
|
8123
|
+
const colId = editingColumnId();
|
|
8124
|
+
if (colId == null) {
|
|
8125
|
+
endEdit();
|
|
8126
|
+
return false;
|
|
8127
|
+
}
|
|
8128
|
+
const rawValue = overrideValue !== undefined ? overrideValue : draftValue();
|
|
8129
|
+
const err = runValidator(colId, coerceCellValue(colId, rawValue), editingRowOriginal());
|
|
8130
|
+
if (err !== true) {
|
|
8131
|
+
setInvalid(err);
|
|
8132
|
+
focusEditorWhenReady(editingRow(), editingCol());
|
|
8133
|
+
return false;
|
|
8134
|
+
}
|
|
8135
|
+
setInvalid('');
|
|
8136
|
+
const focusRow = editingRow();
|
|
8137
|
+
const focusCol = editingCol();
|
|
8138
|
+
const rowOriginal = editingRowOriginal();
|
|
8139
|
+
const rowId = editingRowId();
|
|
8140
|
+
// Same teardown guards as commitEdit: the unmount blur of the editor must not commit again.
|
|
8141
|
+
editTransition = true;
|
|
8142
|
+
committedThisSession = true;
|
|
8143
|
+
const result = fillActiveColumn(rawValue);
|
|
8144
|
+
endEdit();
|
|
8145
|
+
editTransition = false;
|
|
8146
|
+
// Keep the range when focus comes back to the active cell (the selection stays put after a
|
|
8147
|
+
// fill, as in a spreadsheet). Coordinate-matched, like a Shift+Click: if focus lands anywhere
|
|
8148
|
+
// else first (the user clicks another cell, or the focus return gives up), that focusin
|
|
8149
|
+
// clears the range as usual instead of inheriting a stale "keep" flag.
|
|
8150
|
+
rangeClickPending = {
|
|
8151
|
+
isHeader: false,
|
|
8152
|
+
r: focusRow,
|
|
8153
|
+
c: focusCol
|
|
8154
|
+
};
|
|
8155
|
+
if (result && result.changed > 0) {
|
|
8156
|
+
// A write can re-sort or re-filter the rows (holdView off), so follow the active cell's
|
|
8157
|
+
// RECORD rather than its old index — the same B23 path commitEdit uses. A re-order also
|
|
8158
|
+
// clears the range, so the coordinate match above only keeps it when nothing moved.
|
|
8159
|
+
pendingEditFollow = {
|
|
8160
|
+
rowOriginal,
|
|
8161
|
+
rowId,
|
|
8162
|
+
col: focusCol
|
|
8163
|
+
};
|
|
8164
|
+
} else {
|
|
8165
|
+
focusCellWhenReady(focusRow, focusCol);
|
|
8166
|
+
}
|
|
8167
|
+
return true;
|
|
8168
|
+
}
|
|
8169
|
+
|
|
8032
8170
|
// toggleActiveBooleanCell (design doc 2026-07-05, Change 1): the spreadsheet-standard
|
|
8033
8171
|
// single-keystroke boolean toggle. Flips the ACTIVE cell's value and commits it through the
|
|
8034
8172
|
// EXACT SAME write funnel commitEdit uses (replaceRowValue → writeData → single $emit) but
|
|
@@ -8057,6 +8195,14 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
8057
8195
|
const rowId = row.id;
|
|
8058
8196
|
const oldValue = cellValueAt(activeRow(), activeColIndex());
|
|
8059
8197
|
const newValue = !oldValue;
|
|
8198
|
+
// With a range selected, set every selected cell in this column to the active cell's new
|
|
8199
|
+
// value (one value for all, not a per-cell flip) through the paste/fill funnel: invalid cells
|
|
8200
|
+
// skipped, one undo step, one cell-edit-commit per changed cell, "N of M cells updated".
|
|
8201
|
+
if (rangeSize() > 1) {
|
|
8202
|
+
setInvalid('');
|
|
8203
|
+
fillActiveColumn(newValue);
|
|
8204
|
+
return;
|
|
8205
|
+
}
|
|
8060
8206
|
// D-01: same discipline as commitEdit — a rejecting validator blocks the toggle. There is
|
|
8061
8207
|
// no editor to keep open here, so the toggle simply does not apply (no model write).
|
|
8062
8208
|
const err = runValidator(colId, newValue, rowOriginal);
|
|
@@ -8454,6 +8600,12 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
8454
8600
|
committed: boolean;
|
|
8455
8601
|
} | null = null;
|
|
8456
8602
|
|
|
8603
|
+
// rangeCommitIntent: set by the drop-in wrapper's CAPTURE-phase keydown when Ctrl/Cmd+Enter is
|
|
8604
|
+
// pressed while a range is selected, so the slot editor's own commit — which runs during that
|
|
8605
|
+
// same keypress — goes to the whole range. Cleared by the bubble-phase handler and by a
|
|
8606
|
+
// setTimeout(0), so an editor that does not commit during the keypress falls back to one cell.
|
|
8607
|
+
let rangeCommitIntent = false;
|
|
8608
|
+
|
|
8457
8609
|
// ── Per-cell editor draft source (req-6) ──────────────────────────────────────────────
|
|
8458
8610
|
// In single-cell mode every editor binds the shared $data.draftValue. In full-row mode
|
|
8459
8611
|
// (editingRowIndex != null) each editable cell owns its OWN draft keyed by columnId in
|
|
@@ -8477,11 +8629,19 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
8477
8629
|
// reverts the cell (single) or the whole row (row mode). Factory-bound per columnId so the
|
|
8478
8630
|
// row-mode commit targets the right draft key.
|
|
8479
8631
|
function editorCommitFor(colId: any) {
|
|
8480
|
-
return (value: any) => {
|
|
8632
|
+
return (value: any, options: any) => {
|
|
8481
8633
|
if (inRowEdit()) {
|
|
8482
8634
|
setRowDraft$local(colId, value);
|
|
8483
8635
|
return;
|
|
8484
8636
|
}
|
|
8637
|
+
// Range fill: an explicit `commit(v, { toRange: true })`, or any commit made during a
|
|
8638
|
+
// Ctrl/Cmd+Enter keypress (rangeCommitIntent), writes v to every selected cell in this column.
|
|
8639
|
+
const wantsRange = options != null && options.toRange === true || rangeCommitIntent;
|
|
8640
|
+
rangeCommitIntent = false;
|
|
8641
|
+
if (wantsRange && !dropinTabFlush && rangeSize() > 1) {
|
|
8642
|
+
commitEditToRange(value);
|
|
8643
|
+
return;
|
|
8644
|
+
}
|
|
8485
8645
|
// C-10: during the drop-in wrapper's Tab flush, record the outcome and skip the focus
|
|
8486
8646
|
// return — the Tab handler advances focus to the next editor itself.
|
|
8487
8647
|
if (dropinTabFlush) {
|
|
@@ -8593,7 +8753,12 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
8593
8753
|
}
|
|
8594
8754
|
return;
|
|
8595
8755
|
}
|
|
8596
|
-
if (key === 'Enter') {
|
|
8756
|
+
if (key === 'Enter' && (e.ctrlKey || e.metaKey) && rangeSize() > 1) {
|
|
8757
|
+
// Ctrl/Cmd+Enter with a range selected: write the draft to every selected cell in this
|
|
8758
|
+
// column. Plain Enter (below) still commits the active cell only.
|
|
8759
|
+
e.preventDefault();
|
|
8760
|
+
commitEditToRange(undefined);
|
|
8761
|
+
} else if (key === 'Enter') {
|
|
8597
8762
|
e.preventDefault();
|
|
8598
8763
|
commitEdit(undefined);
|
|
8599
8764
|
} else if (key === 'Tab') {
|
|
@@ -8681,8 +8846,23 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
8681
8846
|
// advances. `dropinTabFlush` tells editorCommitFor to report the outcome and skip the focus
|
|
8682
8847
|
// return. A drop-in that does not commit on blur (EditorCheckbox commits on change, so it holds
|
|
8683
8848
|
// nothing pending) falls back to the host-draft path, which is exactly right in that case.
|
|
8849
|
+
// Capture-phase keydown on the grid root (registered at mount): runs BEFORE any editor's own
|
|
8850
|
+
// keydown handler. Ctrl/Cmd+Enter marks the commit an editor makes during this keypress as a
|
|
8851
|
+
// range commit; editorCommitFor decides whether a range is actually selected. The mark expires
|
|
8852
|
+
// after this event, so a commit that comes later (on blur, after an async step) writes one
|
|
8853
|
+
// cell, as usual. Reads NO reactive state: it is a mount-time closure (stale on React).
|
|
8854
|
+
function onEditorDropinKeyDownCapture(e: any) {
|
|
8855
|
+
if (!e) return;
|
|
8856
|
+
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
|
|
8857
|
+
rangeCommitIntent = true;
|
|
8858
|
+
setTimeout(() => {
|
|
8859
|
+
rangeCommitIntent = false;
|
|
8860
|
+
}, 0);
|
|
8861
|
+
}
|
|
8862
|
+
}
|
|
8684
8863
|
function onEditorDropinKeyDown(e: any) {
|
|
8685
8864
|
if (!e) return;
|
|
8865
|
+
rangeCommitIntent = false;
|
|
8686
8866
|
const key = e.key;
|
|
8687
8867
|
if (inRowEdit()) {
|
|
8688
8868
|
if (key === 'Enter') {
|
|
@@ -9209,7 +9389,7 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
9209
9389
|
|
|
9210
9390
|
{<Show when={local.groupable}><div class={"rdt-group-bar-host"} data-rozie-s-d5dcab4c="">
|
|
9211
9391
|
{(_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>}
|
|
9212
|
-
</div></Show>}{<Show when={isWindowed()} fallback={<table aria-rowcount={rozieAttr(gridAriaRowCount())} aria-colcount={rozieAttr(gridAriaColCount())} 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="">
|
|
9392
|
+
</div></Show>}{<Show when={isWindowed()} fallback={<table aria-rowcount={rozieAttr(gridAriaRowCount())} aria-colcount={rozieAttr(gridAriaColCount())} class={"rozie-data-table" + " " + rozieClass({ 'rdt-sticky': local.stickyHeader, 'rdt-column-lines': local.columnLines })} 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="">
|
|
9213
9393
|
<thead class={"rdt-thead"} role="rowgroup" data-rozie-s-d5dcab4c="">
|
|
9214
9394
|
<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="">
|
|
9215
9395
|
<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-pinned': columnPinSide(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()))} aria-colindex={rozieAttr(headerLeafStart(hg(), header()) + 1)} tabIndex={rozieAttr(cellTabindex('__header', headerColIndexOf(hg(), header()), hgLevel()))} aria-sort={rozieAttr(ariaSortFor(header().column.id))} style={parseInlineStyle(thStyle(header()))} data-rozie-s-d5dcab4c="">
|
|
@@ -9261,7 +9441,7 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
9261
9441
|
{_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())}
|
|
9262
9442
|
</span>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="" /></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
|
|
9263
9443
|
{<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="" aria-invalid={rozieAttr(invalidMsg() ? 'true' : null)} 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" }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLSpanElement; target: Element }) => { onEditorDropinKeyDown($event); }} data-rozie-s-d5dcab4c="">
|
|
9264
|
-
{_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 columnLabel() { return headerLabel(cell().column.id); }, 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 columnLabel() { return headerLabel(cell().column.id); }, get autofocus() { return editorAutofocusFor(cell().column.id, rowIndexOf(row())); } }) ?? <input type="text" data-editing-cell="" data-builtin-editor="" aria-invalid={rozieAttr(invalidMsg() ? 'true' : null)} 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="" />}
|
|
9444
|
+
{_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 columnLabel() { return headerLabel(cell().column.id); }, get autofocus() { return editorAutofocusFor(cell().column.id, rowIndexOf(row())); }, get rangeSize() { return inRowEdit() ? 1 : rangeSize(); } }) ?? (_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 columnLabel() { return headerLabel(cell().column.id); }, get autofocus() { return editorAutofocusFor(cell().column.id, rowIndexOf(row())); }, get rangeSize() { return inRowEdit() ? 1 : rangeSize(); } }) ?? <input type="text" data-editing-cell="" data-builtin-editor="" aria-invalid={rozieAttr(invalidMsg() ? 'true' : null)} 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="" />}
|
|
9265
9445
|
</span></Show>}><input type="checkbox" data-editing-cell="" data-builtin-editor="" aria-invalid={rozieAttr(invalidMsg() ? 'true' : null)} 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="" aria-invalid={rozieAttr(invalidMsg() ? 'true' : null)} class={"rdt-cell-editor"} ref={(el) => { rozieSelectValue(el as HTMLSelectElement, () => (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="">
|
|
9266
9446
|
<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>
|
|
9267
9447
|
</select></Show>}><input type="number" data-editing-cell="" data-builtin-editor="" aria-invalid={rozieAttr(invalidMsg() ? 'true' : null)} 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="">
|
|
@@ -9283,7 +9463,7 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
9283
9463
|
</tr></Show>}</>}</Key>
|
|
9284
9464
|
</tbody>
|
|
9285
9465
|
</table>}><div class={"rdt-scroll"} style={parseInlineStyle(rowsWindowed() && local.maxHeight ? 'max-height:' + local.maxHeight + ';overflow:auto;--rozie-data-table-max-height:' + local.maxHeight : 'overflow:auto')} data-rozie-s-d5dcab4c="">
|
|
9286
|
-
<table aria-rowcount={rozieAttr(gridAriaRowCount())} aria-colcount={rozieAttr(gridAriaColCount())} class={"rozie-data-table" + " " + rozieClass({ 'rdt-sticky': local.stickyHeader, 'rdt-col-windowed': colsWindowed() })} 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="">
|
|
9466
|
+
<table aria-rowcount={rozieAttr(gridAriaRowCount())} aria-colcount={rozieAttr(gridAriaColCount())} class={"rozie-data-table" + " " + rozieClass({ 'rdt-sticky': local.stickyHeader, 'rdt-col-windowed': colsWindowed(), 'rdt-column-lines': local.columnLines })} 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="">
|
|
9287
9467
|
<thead class={"rdt-thead"} role="rowgroup" data-rozie-s-d5dcab4c="">
|
|
9288
9468
|
<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="">
|
|
9289
9469
|
|
|
@@ -9338,7 +9518,7 @@ export default function DataTable(_props: DataTableProps): JSX.Element {
|
|
|
9338
9518
|
{_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())}
|
|
9339
9519
|
</span>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="" /></Show>}><span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
|
|
9340
9520
|
{<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="" aria-invalid={rozieAttr(invalidMsg() ? 'true' : null)} 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" }} onKeyDown={($event: KeyboardEvent & { currentTarget: HTMLSpanElement; target: Element }) => { onEditorDropinKeyDown($event); }} data-rozie-s-d5dcab4c="">
|
|
9341
|
-
{_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 columnLabel() { return headerLabel(cell().column.id); }, 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 columnLabel() { return headerLabel(cell().column.id); }, get autofocus() { return editorAutofocusFor(cell().column.id, wr().vi.index); } }) ?? <input type="text" data-editing-cell="" data-builtin-editor="" aria-invalid={rozieAttr(invalidMsg() ? 'true' : null)} 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="" />}
|
|
9521
|
+
{_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 columnLabel() { return headerLabel(cell().column.id); }, get autofocus() { return editorAutofocusFor(cell().column.id, wr().vi.index); }, get rangeSize() { return inRowEdit() ? 1 : rangeSize(); } }) ?? (_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 columnLabel() { return headerLabel(cell().column.id); }, get autofocus() { return editorAutofocusFor(cell().column.id, wr().vi.index); }, get rangeSize() { return inRowEdit() ? 1 : rangeSize(); } }) ?? <input type="text" data-editing-cell="" data-builtin-editor="" aria-invalid={rozieAttr(invalidMsg() ? 'true' : null)} 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="" />}
|
|
9342
9522
|
</span></Show>}><input type="checkbox" data-editing-cell="" data-builtin-editor="" aria-invalid={rozieAttr(invalidMsg() ? 'true' : null)} 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="" aria-invalid={rozieAttr(invalidMsg() ? 'true' : null)} class={"rdt-cell-editor"} ref={(el) => { rozieSelectValue(el as HTMLSelectElement, () => (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="">
|
|
9343
9523
|
<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>
|
|
9344
9524
|
</select></Show>}><input type="number" data-editing-cell="" data-builtin-editor="" aria-invalid={rozieAttr(invalidMsg() ? 'true' : null)} 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="">
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// rangeFill.ts — which cells a range fill writes. Pure, sigil-free.
|
|
2
|
+
//
|
|
3
|
+
// A range fill (Ctrl/Cmd+Enter in an editor, or Space on a checkbox range) writes one value
|
|
4
|
+
// down the ACTIVE cell's column for every row the range covers. Other columns of a
|
|
5
|
+
// multi-column range are left alone: one typed value rarely makes sense in several columns.
|
|
6
|
+
// With no range, or an active cell outside it, the fill is the active cell alone (size 1).
|
|
7
|
+
|
|
8
|
+
export interface FillRange { r0: number; r1: number; c0: number; c1: number }
|
|
9
|
+
export interface FillTargets { top: number; bottom: number; col: number; size: number }
|
|
10
|
+
|
|
11
|
+
const rangeFillTargets = (range: FillRange | null, activeRow: number, activeCol: number): FillTargets => {
|
|
12
|
+
const single = { top: activeRow, bottom: activeRow, col: activeCol, size: 1 }
|
|
13
|
+
if (!range) return single
|
|
14
|
+
const top = Math.min(range.r0, range.r1)
|
|
15
|
+
const bottom = Math.max(range.r0, range.r1)
|
|
16
|
+
const left = Math.min(range.c0, range.c1)
|
|
17
|
+
const right = Math.max(range.c0, range.c1)
|
|
18
|
+
if (activeCol < left || activeCol > right || activeRow < top || activeRow > bottom) return single
|
|
19
|
+
return { top, bottom, col: activeCol, size: bottom - top + 1 }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export { rangeFillTargets }
|
package/src/themes/base.css
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* @rozie-ui/data-table — base token reference.
|
|
3
3
|
*
|
|
4
|
-
* This file is the PUBLIC token reference. It wires each of the
|
|
4
|
+
* This file is the PUBLIC token reference. It wires each of the 20 public
|
|
5
5
|
* `--rozie-data-table-*` tokens, with its default, onto the short internal `--rdt-*`
|
|
6
|
-
* token the component's scoped `<style>` actually reads. The component reads
|
|
6
|
+
* token the component's scoped `<style>` actually reads. The component reads 84 internal `--rdt-*`
|
|
7
7
|
* tokens in total, each with an inline `var(--rdt-x, <fallback>)` default, so the
|
|
8
8
|
* table renders ZERO-CONFIG without importing this file.
|
|
9
9
|
*
|
|
10
10
|
* Scope, stated plainly: remapping the public tokens re-skins the table's primary
|
|
11
11
|
* surfaces — type, borders, background, header, row, cell padding, accent, and the
|
|
12
|
-
* max-height. It does NOT reach every cosmetic value. 58 of the
|
|
12
|
+
* max-height. It does NOT reach every cosmetic value. 58 of the 84 internal tokens
|
|
13
13
|
* have no public counterpart today, including the grid active-cell ring, the range
|
|
14
14
|
* highlight, the fill handle, the invalid-cell outline, the group tokens, the filter
|
|
15
15
|
* row, the per-header column ⋯ menu (its panel is a composed @rozie-ui/popover, so its
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* the element itself), never on the `.rozie-data-table` / `.rozie-data-table-wrap`
|
|
28
28
|
* classes — on that target both of those render inside the component's shadow root,
|
|
29
29
|
* where a document stylesheet cannot reach them. The tokens themselves inherit across
|
|
30
|
-
* the boundary, and the component carries the mapping internally, so every one of the
|
|
30
|
+
* the boundary, and the component carries the mapping internally, so every one of the 20
|
|
31
31
|
* works there.
|
|
32
32
|
*
|
|
33
33
|
* NOTE the split: the STRUCTURAL rules — `border-collapse`, the keyed-r-for
|
|
@@ -75,6 +75,12 @@
|
|
|
75
75
|
--rdt-border: 1px solid var(--rozie-data-table-border-color, rgba(0, 0, 0, 0.08));
|
|
76
76
|
--rdt-filter-border: 1px solid var(--rozie-data-table-control-border-color, rgba(0, 0, 0, 0.2));
|
|
77
77
|
--rdt-filter-radius: var(--rozie-data-table-radius, 4px);
|
|
78
|
+
/* Column lines are off unless the `columnLines` prop is set. Their colour follows the
|
|
79
|
+
row-line colour unless the column token is set, so one variable themes both. */
|
|
80
|
+
--rdt-column-border: 1px solid var(--rozie-data-table-column-border-color, var(--rozie-data-table-border-color, rgba(0, 0, 0, 0.08)));
|
|
81
|
+
/* Pinned body cells are opaque so scrolled content never shows through them. Set this
|
|
82
|
+
to the surface colour behind the table. */
|
|
83
|
+
--rdt-pinned-bg: var(--rozie-data-table-pinned-bg, #fff);
|
|
78
84
|
|
|
79
85
|
/* ── typography / cell metrics ──────────────────────────────────────────── */
|
|
80
86
|
--rdt-font: var(--rozie-data-table-font, 14px system-ui, sans-serif);
|
|
@@ -125,6 +131,8 @@
|
|
|
125
131
|
--rdt-ds-fg: currentColor;
|
|
126
132
|
--rdt-ds-select-accent: #0066cc;
|
|
127
133
|
--rdt-ds-border-color: rgba(0, 0, 0, 0.08);
|
|
134
|
+
--rdt-ds-column-border-color: var(--rdt-ds-border-color);
|
|
135
|
+
--rdt-ds-pinned-bg: #fff;
|
|
128
136
|
--rdt-ds-control-border-color: rgba(0, 0, 0, 0.2);
|
|
129
137
|
--rdt-ds-radius: 4px;
|
|
130
138
|
--rdt-ds-font: 14px system-ui, sans-serif;
|
package/src/themes/bootstrap.css
CHANGED
|
@@ -35,6 +35,8 @@ rozie-data-table {
|
|
|
35
35
|
--rdt-border: 1px solid var(--rozie-data-table-border-color, var(--bs-border-color, #dee2e6));
|
|
36
36
|
--rdt-filter-border: 1px solid var(--rozie-data-table-control-border-color, var(--bs-border-color, #dee2e6));
|
|
37
37
|
--rdt-filter-radius: var(--rozie-data-table-radius, var(--bs-border-radius, 0.375rem));
|
|
38
|
+
--rdt-column-border: 1px solid var(--rozie-data-table-column-border-color, var(--rozie-data-table-border-color, var(--bs-border-color, #dee2e6)));
|
|
39
|
+
--rdt-pinned-bg: var(--rozie-data-table-pinned-bg, var(--bs-body-bg, #fff));
|
|
38
40
|
--rdt-header-bg: var(--rozie-data-table-header-bg, var(--bs-tertiary-bg, #f8f9fa));
|
|
39
41
|
--rdt-resize-grip-color: var(--rozie-data-table-resize-grip-color, var(--bs-border-color, #dee2e6));
|
|
40
42
|
--rdt-resize-grip-active: var(--rozie-data-table-resize-grip-active, var(--rozie-data-table-accent, var(--bs-primary, #0d6efd)));
|
|
@@ -60,6 +62,7 @@ rozie-data-table {
|
|
|
60
62
|
--rdt-ds-fg: var(--bs-body-color, #212529);
|
|
61
63
|
--rdt-ds-select-accent: var(--bs-primary, #0d6efd);
|
|
62
64
|
--rdt-ds-border-color: var(--bs-border-color, #dee2e6);
|
|
65
|
+
--rdt-ds-pinned-bg: var(--bs-body-bg, #fff);
|
|
63
66
|
--rdt-ds-control-border-color: var(--bs-border-color, #dee2e6);
|
|
64
67
|
--rdt-ds-radius: var(--bs-border-radius, 0.375rem);
|
|
65
68
|
--rdt-ds-header-bg: var(--bs-tertiary-bg, #f8f9fa);
|
package/src/themes/material.css
CHANGED
|
@@ -33,6 +33,8 @@ rozie-data-table {
|
|
|
33
33
|
--rdt-color: var(--rozie-data-table-fg, var(--md-sys-color-on-surface, #1d1b20));
|
|
34
34
|
--rdt-select-accent: var(--rozie-data-table-select-accent, var(--rozie-data-table-accent, var(--md-sys-color-primary, #6750a4)));
|
|
35
35
|
--rdt-border: 1px solid var(--rozie-data-table-border-color, var(--md-sys-color-outline-variant, #cac4d0));
|
|
36
|
+
--rdt-column-border: 1px solid var(--rozie-data-table-column-border-color, var(--rozie-data-table-border-color, var(--md-sys-color-outline-variant, #cac4d0)));
|
|
37
|
+
--rdt-pinned-bg: var(--rozie-data-table-pinned-bg, var(--md-sys-color-surface, #fef7ff));
|
|
36
38
|
--rdt-filter-border: 1px solid var(--rozie-data-table-control-border-color, var(--md-sys-color-outline, #79747e));
|
|
37
39
|
--rdt-header-bg: var(--rozie-data-table-header-bg, var(--md-sys-color-surface-container-highest, #e6e0e9));
|
|
38
40
|
--rdt-header-weight: var(--rozie-data-table-header-weight, 500);
|
|
@@ -55,6 +57,7 @@ rozie-data-table {
|
|
|
55
57
|
--rdt-ds-fg: var(--md-sys-color-on-surface, #1d1b20);
|
|
56
58
|
--rdt-ds-select-accent: var(--md-sys-color-primary, #6750a4);
|
|
57
59
|
--rdt-ds-border-color: var(--md-sys-color-outline-variant, #cac4d0);
|
|
60
|
+
--rdt-ds-pinned-bg: var(--md-sys-color-surface, #fef7ff);
|
|
58
61
|
--rdt-ds-control-border-color: var(--md-sys-color-outline, #79747e);
|
|
59
62
|
--rdt-ds-radius: 4px;
|
|
60
63
|
--rdt-ds-header-bg: var(--md-sys-color-surface-container-highest, #e6e0e9);
|
package/src/themes/shadcn.css
CHANGED
|
@@ -39,6 +39,8 @@ rozie-data-table {
|
|
|
39
39
|
--rdt-color: var(--rozie-data-table-fg, hsl(var(--foreground, 222.2 47.4% 11.2%)));
|
|
40
40
|
--rdt-select-accent: var(--rozie-data-table-select-accent, var(--rozie-data-table-accent, hsl(var(--primary, 222.2 47.4% 11.2%))));
|
|
41
41
|
--rdt-border: 1px solid var(--rozie-data-table-border-color, hsl(var(--border, 214.3 31.8% 91.4%)));
|
|
42
|
+
--rdt-column-border: 1px solid var(--rozie-data-table-column-border-color, var(--rozie-data-table-border-color, hsl(var(--border, 214.3 31.8% 91.4%))));
|
|
43
|
+
--rdt-pinned-bg: var(--rozie-data-table-pinned-bg, hsl(var(--background, 0 0% 100%)));
|
|
42
44
|
--rdt-filter-border: 1px solid var(--rozie-data-table-control-border-color, hsl(var(--border, 214.3 31.8% 91.4%)));
|
|
43
45
|
--rdt-filter-radius: var(--rozie-data-table-radius, var(--radius, 0.5rem));
|
|
44
46
|
--rdt-header-bg: var(--rozie-data-table-header-bg, hsl(var(--muted, 210 40% 96.1%)));
|
|
@@ -65,6 +67,7 @@ rozie-data-table {
|
|
|
65
67
|
--rdt-ds-fg: hsl(var(--foreground, 222.2 47.4% 11.2%));
|
|
66
68
|
--rdt-ds-select-accent: hsl(var(--primary, 222.2 47.4% 11.2%));
|
|
67
69
|
--rdt-ds-border-color: hsl(var(--border, 214.3 31.8% 91.4%));
|
|
70
|
+
--rdt-ds-pinned-bg: hsl(var(--background, 0 0% 100%));
|
|
68
71
|
--rdt-ds-control-border-color: hsl(var(--border, 214.3 31.8% 91.4%));
|
|
69
72
|
--rdt-ds-radius: var(--radius, 0.5rem);
|
|
70
73
|
--rdt-ds-header-bg: hsl(var(--muted, 210 40% 96.1%));
|