@rozie-ui/data-table-solid 0.2.6 → 0.3.1

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.
@@ -0,0 +1,35 @@
1
+ // columnDefUtils.ts — pure column-def helpers extracted from columnBuilders.rzts
2
+ // (D-22, Phase 87 plan 87-01). Sigil-free, self-contained: no $props/$data/$emit/
3
+ // $computed/$onMount/$refs/$el/$expose/$watch, and no cross-partial host-symbol
4
+ // calls. Moved verbatim (bodies + explanatory comments intact) — behavior-neutral
5
+ // extraction, proven by dist-parity zero drift + the data-table VR gate.
6
+
7
+ // Prototype-safe id-keyed column resolution (T-48-PP): the `:columns` config array is
8
+ // applied FIRST (lower precedence), then the <Column> registry OVERRIDES by id (LWW).
9
+ // byId is a null-prototype object so a consumer column id of "__proto__"/"constructor"
10
+ // cannot pollute Object.prototype. Returns the table-core ColumnDef[]. (No per-column
11
+ // render callbacks — cells render via the single #cell/#header scoped slot on this
12
+ // component, dispatched by columnId; <Column> carries metadata only.)
13
+ const isSafeKey = (k: any) => k !== '__proto__' && k !== 'constructor' && k !== 'prototype'
14
+
15
+ // wrapAggregationFn (phase 50 req-5, D-05, threat T-50-04): resolve a per-column
16
+ // aggregationFn straight onto the ColumnDef (no component-side switch — RESEARCH
17
+ // anti-pattern). A built-in NAME string ('sum'/'min'/'max'/'extent'/'mean'/'median'/
18
+ // 'unique'/'uniqueCount'/'count') passes through verbatim — table-core resolves it from its
19
+ // built-in `aggregationFns` map. A CUSTOM function `(columnId, leafRows, childRows) => any`
20
+ // is DEFENSIVELY WRAPPED (the runValidator precedent): a consumer fn runs per group, so a
21
+ // throw is coerced to `undefined` and can never crash getGroupedRowModel (DoS guard).
22
+ // Anything else → undefined (no aggregation; the cell renders as a placeholder).
23
+ const wrapAggregationFn = (fn: any) => {
24
+ if (typeof fn === 'string') return fn
25
+ if (typeof fn !== 'function') return undefined
26
+ return (columnId: any, leafRows: any, childRows: any) => {
27
+ try {
28
+ return fn(columnId, leafRows, childRows)
29
+ } catch (err) {
30
+ return undefined
31
+ }
32
+ }
33
+ }
34
+
35
+ export { isSafeKey, wrapAggregationFn }
@@ -0,0 +1,25 @@
1
+ // indexMath.ts — pure index-math + DOM-query helpers extracted from gridFocusNav.rzts /
2
+ // writeFunnels.rzts (D-22, Phase 87 plan 87-01). Sigil-free, self-contained: no
3
+ // $props/$data/$emit/$computed/$onMount/$refs/$el/$expose/$watch, and no cross-partial
4
+ // host-symbol calls. Moved verbatim (bodies + explanatory comments intact) —
5
+ // behavior-neutral extraction, proven by dist-parity zero drift + the data-table VR gate.
6
+ // `focusables(cellEl)` reads the DOM but receives its element as a parameter and touches
7
+ // no sigil and no component ref, so it is framework-agnostic and belongs here alongside
8
+ // `clamp` — its querySelectorAll selector string and its `!n.disabled` filter are kept
9
+ // byte-identical to the pre-extraction body.
10
+
11
+ const clamp = (v: any, lo: any, hi: any) => (v < lo ? lo : (v > hi ? hi : v))
12
+
13
+ // The focusable descendants of a cell (non-disabled), in DOM order. Pure DOM — uniform ×6.
14
+ const focusables = (cellEl: any) => {
15
+ if (!cellEl || !cellEl.querySelectorAll) return []
16
+ const list = Array.prototype.slice.call(
17
+ cellEl.querySelectorAll('button,[href],input,select,textarea,[tabindex]:not([tabindex="-1"])')
18
+ )
19
+ return list.filter((n) => !n.disabled)
20
+ }
21
+
22
+ const applyUpdater = (updater: any, current: any) =>
23
+ (typeof updater === 'function' ? updater(current) : updater)
24
+
25
+ export { clamp, focusables, applyUpdater }
@@ -0,0 +1,62 @@
1
+ // rowValueUtils.ts — pure row-value helpers extracted from editCellLifecycle.rzts /
2
+ // editRowLifecycle.rzts (D-22, Phase 87 plan 87-01). Sigil-free, self-contained: no
3
+ // $props/$data/$emit/$computed/$onMount/$refs/$el/$expose/$watch, and no cross-partial
4
+ // host-symbol calls. Moved verbatim (bodies + explanatory comments intact) —
5
+ // behavior-neutral extraction, proven by dist-parity zero drift + the data-table VR gate.
6
+
7
+ // replaceRowValue: build a FRESH array with ONE row object replaced (the column's field
8
+ // set to the new value); the rest share by reference (the family immutable whole-array
9
+ // replace — in-place mutation is silently dropped on React/Solid/Angular/Lit). rowIndex
10
+ // is over currentData() (== the visible model order for the non-virtual, unsorted/
11
+ // unfiltered single-cell case; the row id is carried for the commit payload).
12
+ const replaceRowValue = (rows: any, rowIndex: any, field: any, value: any) => {
13
+ const src = rows || []
14
+ const out = []
15
+ for (let i = 0; i < src.length; i++) {
16
+ if (i === rowIndex) {
17
+ // WR-03: own-property spread, NOT `for (const k in orig)` which walks the prototype chain
18
+ // and would copy inherited enumerable props of typed/class-instance row objects.
19
+ out.push({ ...(src[i] || {}), [field]: value })
20
+ } else {
21
+ out.push(src[i])
22
+ }
23
+ }
24
+ return out
25
+ }
26
+
27
+ // replaceRowValues: like replaceRowValue but applies a MAP of field→value to ONE row object
28
+ // in a single fresh-array replace (req-6 — the whole-row commit is ONE write, not per cell).
29
+ const replaceRowValues = (rows: any, rowIndex: any, fieldValues: any) => {
30
+ const src = rows || []
31
+ const fv = fieldValues || {}
32
+ const out = []
33
+ for (let i = 0; i < src.length; i++) {
34
+ if (i === rowIndex) {
35
+ // WR-03: own-property spread (orig then the field→value map), NOT a `for..in`
36
+ // prototype-walking copy. Spread copies own enumerable props only.
37
+ out.push({ ...(src[i] || {}), ...fv })
38
+ } else {
39
+ out.push(src[i])
40
+ }
41
+ }
42
+ return out
43
+ }
44
+
45
+ // B23: the index of a committed row WITHIN a given (fresh) visible-model array, resolved by
46
+ // row IDENTITY. table-core's default getRowId is source-index-based, so a row's id is stable
47
+ // across a re-sort (only its VISIBLE position moves); a committed edit replaces the row object
48
+ // via a fresh spread (the `original` reference changes), so match by `id` FIRST, `original`
49
+ // only as a fallback. Returns -1 when the row filtered out of the view. PURE (the caller passes
50
+ // the FRESH row list — refreshRowModel's just-pulled `nextRows`, never the React-stale state).
51
+ const indexOfRowIn = (rows: any, rowOriginal: any, rowId: any) => {
52
+ const list = rows || []
53
+ for (let i = 0; i < list.length; i++) {
54
+ const r = list[i]
55
+ if (!r) continue
56
+ if (rowId != null && r.id === rowId) return i
57
+ if (rowOriginal != null && r.original === rowOriginal) return i
58
+ }
59
+ return -1
60
+ }
61
+
62
+ export { replaceRowValue, replaceRowValues, indexOfRowIn }
@@ -0,0 +1,115 @@
1
+ // tsvGrid.ts — pure TSV/clipboard-grid helpers extracted from clipboardFill.rzts
2
+ // (D-22, Phase 87 plan 87-01). Sigil-free, self-contained: no $props/$data/$emit/
3
+ // $computed/$onMount/$refs/$el/$expose/$watch, and no cross-partial host-symbol
4
+ // calls. Moved verbatim (bodies + explanatory comments intact) — behavior-neutral
5
+ // extraction, proven by dist-parity zero drift + the data-table VR gate.
6
+
7
+ // B10: escape a TSV field per the spreadsheet convention — a field containing a tab, a CR/LF,
8
+ // or a double-quote is wrapped in double-quotes with internal quotes DOUBLED; an ordinary
9
+ // field is emitted verbatim. parseTsv() unescapes symmetrically, so a cell carrying a tab /
10
+ // newline / quote round-trips without smearing into adjacent cells (T-63-03-02).
11
+ const escapeTsvField = (s: any) => {
12
+ if (s.indexOf('\t') >= 0 || s.indexOf('\n') >= 0 || s.indexOf('\r') >= 0 || s.indexOf('"') >= 0) {
13
+ return '"' + s.replace(/"/g, '""') + '"'
14
+ }
15
+ return s
16
+ }
17
+
18
+ // parseTsv(text): a TSV string → string[][] (rows of cells). Tolerates \r\n; a trailing
19
+ // newline does not add a phantom empty row. Pure — produces plain string DATA only (T-51-01:
20
+ // the cells are NEVER eval'd / interpolated into a selector / rendered as markup).
21
+ const parseTsv = (text: any) => {
22
+ const str = text != null ? String(text) : ''
23
+ // CR-03: length guard BEFORE the parse — an empty string is a no-op, and a pathologically
24
+ // large clipboard payload (>2M chars) is rejected outright (DoS-shaped input) before the
25
+ // single-pass scan allocates a cell-per-character grid.
26
+ if (str === '' || str.length > 2000000) return []
27
+ // B10: a quote-aware single-pass state machine (replaces the naive split, which corrupted a
28
+ // cell containing a tab/newline). A field that OPENS with a double-quote is "quoted": tabs,
29
+ // newlines, and doubled quotes ("") inside it are literal content until the closing quote;
30
+ // an unquoted field ends at the next tab/newline. CR/LF and CRLF all delimit a row.
31
+ const rows = []
32
+ let row = []
33
+ let field = ''
34
+ let inQuotes = false
35
+ let i = 0
36
+ const n = str.length
37
+ while (i < n) {
38
+ const ch = str[i]
39
+ if (inQuotes) {
40
+ if (ch === '"') {
41
+ if (i + 1 < n && str[i + 1] === '"') { field = field + '"'; i = i + 2; continue }
42
+ inQuotes = false; i = i + 1; continue
43
+ }
44
+ field = field + ch; i = i + 1; continue
45
+ }
46
+ if (ch === '"' && field === '') { inQuotes = true; i = i + 1; continue }
47
+ if (ch === '\t') { row.push(field); field = ''; i = i + 1; continue }
48
+ if (ch === '\r') {
49
+ if (i + 1 < n && str[i + 1] === '\n') i = i + 1
50
+ row.push(field); field = ''; rows.push(row); row = []; i = i + 1; continue
51
+ }
52
+ if (ch === '\n') { row.push(field); field = ''; rows.push(row); row = []; i = i + 1; continue }
53
+ field = field + ch; i = i + 1
54
+ }
55
+ // Flush the trailing field + row.
56
+ row.push(field)
57
+ rows.push(row)
58
+ // Drop a single trailing empty row (a TSV that ends with a newline → a phantom [''] row).
59
+ if (rows.length > 1) {
60
+ const last = rows[rows.length - 1]
61
+ if (last.length === 1 && last[0] === '') rows.pop()
62
+ }
63
+ return rows
64
+ }
65
+
66
+ // C3: tile a parsed clipboard `grid` (string[][]) to fill a destination `box` — the spreadsheet
67
+ // paste-into-range semantics. The target rectangle is the MAX of the box dims and the source
68
+ // dims per axis, so a SMALLER clipboard TILES across a LARGER selection (a single 1×1 cell fills
69
+ // the whole range; a 2×2 block repeats — tiled[dr][dc] = src[dr % srcRows][dc % srcCols]), while a
70
+ // clipboard LARGER than the selection pastes its full block from the top-left (preserving the
71
+ // no-range "clipboard-sized block at the active cell" behavior — a 1×1 destBox + a 1×N clipboard
72
+ // yields the full 1×N block, byte-for-byte the prior path). Pure — returns a fresh grid; applies
73
+ // nothing. A ragged/short source row defaults the missing cell to '' (coerced per column on write).
74
+ const tileGridToBox = (grid: any, box: any) => {
75
+ const srcRows = grid.length
76
+ // srcCols is the MAX row width across ALL rows (not grid[0].length): a RAGGED clipboard
77
+ // (a later row WIDER than the first, e.g. TSV "a\tb\nc\td\te") would otherwise never read
78
+ // the extra column and silently drop those cells. A row SHORTER than srcCols tiles its
79
+ // missing cells as '' (the `v != null ? v : ''` coercion below), never undefined.
80
+ let srcCols = 0
81
+ for (let i = 0; i < srcRows; i++) {
82
+ const w = grid[i] && grid[i].length ? grid[i].length : 0
83
+ if (w > srcCols) srcCols = w
84
+ }
85
+ if (srcRows <= 0 || srcCols <= 0) return grid
86
+ const boxRows = (box.r1 - box.r0) + 1
87
+ const boxCols = (box.c1 - box.c0) + 1
88
+ const rows = boxRows > srcRows ? boxRows : srcRows
89
+ const cols = boxCols > srcCols ? boxCols : srcCols
90
+ const out = []
91
+ for (let r = 0; r < rows; r++) {
92
+ const srcLine = grid[r % srcRows] || []
93
+ const line = []
94
+ for (let c = 0; c < cols; c++) {
95
+ const v = srcLine[c % srcCols]
96
+ line.push(v != null ? v : '')
97
+ }
98
+ out.push(line)
99
+ }
100
+ return out
101
+ }
102
+
103
+ // tileIndex(i, lo, hi): map an index into the inclusive [lo,hi] source span by TILING (repeat
104
+ // the source block), handling indices below lo (negative offset) correctly. A 1-wide source
105
+ // (lo===hi) always returns lo. Used by fillRange to resolve, per target cell, WHICH source
106
+ // cell it copies — so each column copies its OWN source value down its OWN column.
107
+ const tileIndex = (i: any, lo: any, hi: any) => {
108
+ const span = (hi - lo) + 1
109
+ if (span <= 1) return lo
110
+ let k = (i - lo) % span
111
+ if (k < 0) k = k + span
112
+ return lo + k
113
+ }
114
+
115
+ export { escapeTsvField, parseTsv, tileGridToBox, tileIndex }