@rozie-ui/data-table-solid 0.1.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/package.json ADDED
@@ -0,0 +1,85 @@
1
+ {
2
+ "name": "@rozie-ui/data-table-solid",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "private": false,
6
+ "license": "MIT",
7
+ "description": "Idiomatic Solid headless WAI-ARIA data table (sorting, filtering, pagination, selection, column visibility/resize/reorder/pinning) on @tanstack/table-core — one accessible Rozie source compiled to Solid.",
8
+ "keywords": [
9
+ "rozie",
10
+ "rozie-ui",
11
+ "data-table",
12
+ "table",
13
+ "datagrid",
14
+ "tanstack-table",
15
+ "table-core",
16
+ "headless",
17
+ "accessibility",
18
+ "aria",
19
+ "component",
20
+ "solid"
21
+ ],
22
+ "author": "One Learning Community (https://github.com/One-Learning-Community)",
23
+ "homepage": "https://github.com/One-Learning-Community/rozie.js#readme",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/One-Learning-Community/rozie.js.git",
27
+ "directory": "packages/ui/data-table/packages/solid"
28
+ },
29
+ "bugs": {
30
+ "url": "https://github.com/One-Learning-Community/rozie.js/issues"
31
+ },
32
+ "main": "./dist/index.cjs",
33
+ "module": "./dist/index.mjs",
34
+ "types": "./dist/index.d.mts",
35
+ "exports": {
36
+ ".": {
37
+ "types": "./dist/index.d.mts",
38
+ "import": "./dist/index.mjs",
39
+ "require": "./dist/index.cjs"
40
+ },
41
+ "./themes/*": "./src/themes/*"
42
+ },
43
+ "files": [
44
+ "dist",
45
+ "src"
46
+ ],
47
+ "dependencies": {
48
+ "@solid-primitives/keyed": "^1.5.3",
49
+ "@rozie/runtime-solid": "0.1.3"
50
+ },
51
+ "peerDependencies": {
52
+ "solid-js": "^1.8",
53
+ "@tanstack/table-core": "^8.21",
54
+ "@tanstack/virtual-core": "^3",
55
+ "@floating-ui/dom": "^1.7.2"
56
+ },
57
+ "peerDependenciesMeta": {
58
+ "solid-js": {
59
+ "optional": false
60
+ },
61
+ "@tanstack/table-core": {
62
+ "optional": false
63
+ },
64
+ "@tanstack/virtual-core": {
65
+ "optional": false
66
+ },
67
+ "@floating-ui/dom": {
68
+ "optional": false
69
+ }
70
+ },
71
+ "devDependencies": {
72
+ "@floating-ui/dom": "^1.7.2"
73
+ },
74
+ "publishConfig": {
75
+ "access": "public"
76
+ },
77
+ "sideEffects": [
78
+ "*.css",
79
+ "**/*.css"
80
+ ],
81
+ "scripts": {
82
+ "build": "tsdown",
83
+ "typecheck": "tsc --noEmit"
84
+ }
85
+ }
package/src/Column.tsx ADDED
@@ -0,0 +1,149 @@
1
+ import type { JSX } from 'solid-js';
2
+ import { createEffect, mergeProps, on, onCleanup, onMount, splitProps, untrack, useContext } from 'solid-js';
3
+ import { rozieContext } from '@rozie/runtime-solid';
4
+
5
+ interface ColumnProps {
6
+ /**
7
+ * The column id. Optional — defaults to `field` when omitted. Used as the key in the id-keyed registry union and in the `#cell` / `#colHeader` slot dispatch.
8
+ */
9
+ id?: string;
10
+ /**
11
+ * The row field this column reads (table-core `accessorKey`). The plain accessor value renders when the `#cell` slot falls through.
12
+ * @example
13
+ * <Column field="email" header="Email" />
14
+ */
15
+ field?: string;
16
+ /**
17
+ * The header label, rendered when the parent `#colHeader` slot falls through to the plain label.
18
+ */
19
+ header?: string;
20
+ /**
21
+ * Whether this column participates in click-to-sort. Default `false`. Bind `:sortable="true"` (a bare attr only coerces on Vue+Lit).
22
+ */
23
+ sortable?: boolean;
24
+ /**
25
+ * Whether this column participates in per-column filtering (the `#filter` slot / faceted filter chrome). Default `false`.
26
+ */
27
+ filterable?: boolean;
28
+ /**
29
+ * Pin side: `''` (unpinned) | `'left'` | `'right'`. Reserved metadata carried into the parent's column pinning state.
30
+ */
31
+ pinned?: string;
32
+ /**
33
+ * Optional fixed/initial column width — a CSS length string or a px number.
34
+ */
35
+ width?: string | number;
36
+ /**
37
+ * Reserved per-column metadata flagging participation in the expand affordance. The expander chevron is its own auto-injected leading column on `<DataTable expandable>`, so this is forward-compat metadata, not the toggle host. Default `false`.
38
+ */
39
+ expandable?: boolean;
40
+ /**
41
+ * Whether this column is offered to the headless `#groupBar` as a grouping target. Defaults `true` (opt-OUT via `:groupable="false"`); this only filters the groupable-columns list. Whether grouping is engaged is driven by the parent's `grouping` model, never this flag.
42
+ */
43
+ groupable?: boolean;
44
+ /**
45
+ * The table-core aggregation for this column inside a group-header cell. Either a built-in name string — `'sum'` | `'min'` | `'max'` | `'extent'` | `'mean'` | `'median'` | `'unique'` | `'uniqueCount'` | `'count'` — or a custom function `(columnId, leafRows, childRows) => any` (defensively wrapped by the parent so a throw cannot crash grouping). Null → no aggregation (the group-header cell renders as a placeholder).
46
+ */
47
+ aggregationFn?: (string | ((...args: unknown[]) => unknown)) | null;
48
+ /**
49
+ * Whether this column's cells are editable (opt-in). Default `false` → the column is read-only and the display↔editor branch never mounts an editor. Bind `:editable="true"` (a bare attr only coerces on Vue+Lit).
50
+ */
51
+ editable?: boolean;
52
+ /**
53
+ * Built-in editor type for this column: `'text'` | `'number'` | `'select'` | `'checkbox'`. Ignored when a custom `#editor` scoped slot handles the column. Default `'text'`.
54
+ */
55
+ editor?: string;
56
+ /**
57
+ * Options for `editor: 'select'` — `[{ value, label }]`. Empty for other editor types.
58
+ */
59
+ editorOptions?: any[];
60
+ /**
61
+ * Synchronous per-column validator `(value, row) => true | string`. A string return is the error message (the editor stays open and the aria-live region announces it). Null → no validation. The parent wraps it defensively against a thrown/non-bool/non-string return.
62
+ */
63
+ validate?: ((...args: unknown[]) => unknown) | null;
64
+ }
65
+
66
+ export default function Column(_props: ColumnProps): JSX.Element {
67
+ const _merged = mergeProps({ id: '', field: '', header: '', sortable: false, filterable: false, pinned: '', width: '', expandable: false, groupable: true, aggregationFn: null, editable: false, editor: 'text', editorOptions: (() => [])(), validate: null }, _props);
68
+ const [local, attrs] = splitProps(_merged, ['id', 'field', 'header', 'sortable', 'filterable', 'pinned', 'width', 'expandable', 'groupable', 'aggregationFn', 'editable', 'editor', 'editorOptions', 'validate']);
69
+
70
+ const registry = useContext(rozieContext("data-table:columns"));
71
+ onMount(() => {
72
+ const _cleanup = (() => {
73
+ // register this column's spec. On Lit the injected registry may still be undefined
74
+ // here (REQ-30 async context); the $onUpdate below performs the registration once
75
+ // the value arrives.
76
+ if (reg && !registered) {
77
+ registered = true;
78
+ reg.registerColumn(colId(), buildSpec());
79
+ }
80
+ })() as unknown;
81
+ if (_cleanup) onCleanup(_cleanup as () => void);
82
+ onCleanup(() => {
83
+ if (reg) reg.unregisterColumn(colId());
84
+ });
85
+ });
86
+ createEffect(() => {
87
+ if (registered) return;
88
+ const live = registry;
89
+ if (live == null) return;
90
+ reg = live;
91
+ registered = true;
92
+ reg.registerColumn(colId(), buildSpec());
93
+ });
94
+ createEffect(on(() => (() => [local.id, local.field, local.header, local.sortable, local.filterable, local.pinned, local.width, local.expandable, local.groupable, local.aggregationFn, local.editable, local.editor, local.editorOptions, local.validate])(), (v) => untrack(() => (() => {
95
+ if (reg) reg.registerColumn(colId(), buildSpec());
96
+ })()), { defer: true }));
97
+
98
+ // $inject is typed `unknown` (Phase 36 D-4: no rich type synthesis yet), which the
99
+ // STRICT BUNDLED-LEAF tsc rejects on `.registerColumn(...)` (TS2339). The .rozie-native
100
+ // fix is the null-let → `any` typeNeutralize idiom: alias the injected API through a
101
+ // MODULE-SCOPE `let reg = null` (typeNeutralize types it `any`). Module-scope (not
102
+ // hook-local) so the alias is in scope from the Solid teardown — which the Solid
103
+ // emitter hoists into a sibling onCleanup() OUTSIDE the mount closure. ZERO emitter
104
+ // change.
105
+ let reg: any = null;
106
+ reg = registry;
107
+
108
+ // idempotency flag so a reactive late-context registration (Lit async first paint,
109
+ // REQ-30) and the $onMount registration never double-register the column.
110
+ let registered = false;
111
+
112
+ // the column SPEC builder — shared by the $onMount register and the late-context
113
+ // $onUpdate below. Carries METADATA ONLY (no cell/header render callbacks — D-A moved
114
+ // per-cell rendering to the parent's #cell/#header scoped slot, dispatched by columnId).
115
+ function colId() {
116
+ return local.id !== '' ? local.id : local.field;
117
+ }
118
+ function buildSpec() {
119
+ return {
120
+ id: colId(),
121
+ field: local.field !== '' ? local.field : colId(),
122
+ header: local.header,
123
+ sortable: local.sortable,
124
+ filterable: local.filterable,
125
+ pinned: local.pinned,
126
+ width: local.width,
127
+ // Expandable-rows reserved metadata (phase 50, D-04) — carried via the parent registry.
128
+ expandable: local.expandable,
129
+ // Grouping + aggregation metadata (phase 50, reqs 4-7, D-05) — carried via the parent
130
+ // registry; the parent resolves aggregationFn onto the ColumnDef (defensive-wrapping a
131
+ // custom fn) and filters groupableColumns by `groupable`.
132
+ groupable: local.groupable,
133
+ aggregationFn: local.aggregationFn,
134
+ // Editable-cell config (Phase 51) — carried into ColumnDef.meta via the parent
135
+ // registry (the existing per-column metadata path; NO parallel registry).
136
+ editable: local.editable,
137
+ editor: local.editor,
138
+ editorOptions: local.editorOptions,
139
+ validate: local.validate
140
+ };
141
+ }
142
+
143
+ return (
144
+ <>
145
+
146
+ <div class={"rozie-data-table-column"} style={{ display: "none" }} data-rozie-s-289f2d72="" />
147
+ </>
148
+ );
149
+ }