@rozie-ui/data-table-lit 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,96 @@
1
+ {
2
+ "name": "@rozie-ui/data-table-lit",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "private": false,
6
+ "license": "MIT",
7
+ "description": "Idiomatic Lit headless WAI-ARIA data table (sorting, filtering, pagination, selection, column visibility/resize/reorder/pinning) on @tanstack/table-core — one accessible Rozie source compiled to Lit.",
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
+ "lit"
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/lit"
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
+ "@rozie/runtime-lit": "0.1.3"
49
+ },
50
+ "peerDependencies": {
51
+ "lit": "^3.2",
52
+ "@lit/context": "^1.1",
53
+ "@lit-labs/preact-signals": "^1.0.0",
54
+ "@preact/signals-core": "^1.3.0",
55
+ "@tanstack/table-core": "^8.21",
56
+ "@tanstack/virtual-core": "^3",
57
+ "@floating-ui/dom": "^1.7.2"
58
+ },
59
+ "peerDependenciesMeta": {
60
+ "lit": {
61
+ "optional": false
62
+ },
63
+ "@lit/context": {
64
+ "optional": false
65
+ },
66
+ "@lit-labs/preact-signals": {
67
+ "optional": false
68
+ },
69
+ "@preact/signals-core": {
70
+ "optional": false
71
+ },
72
+ "@tanstack/table-core": {
73
+ "optional": false
74
+ },
75
+ "@tanstack/virtual-core": {
76
+ "optional": false
77
+ },
78
+ "@floating-ui/dom": {
79
+ "optional": false
80
+ }
81
+ },
82
+ "devDependencies": {
83
+ "@floating-ui/dom": "^1.7.2"
84
+ },
85
+ "publishConfig": {
86
+ "access": "public"
87
+ },
88
+ "sideEffects": [
89
+ "*.css",
90
+ "**/*.css"
91
+ ],
92
+ "scripts": {
93
+ "build": "tsdown",
94
+ "typecheck": "tsc --noEmit"
95
+ }
96
+ }
package/src/Column.ts ADDED
@@ -0,0 +1,159 @@
1
+ import { LitElement, css, html } from 'lit';
2
+ import { customElement, property } from 'lit/decorators.js';
3
+ import { SignalWatcher } from '@lit-labs/preact-signals';
4
+ import { ContextConsumer, createContext } from '@lit/context';
5
+
6
+ const __rozieCtx_data_table_columns = createContext(Symbol.for("rozie:data-table:columns"));
7
+
8
+ @customElement('rozie-column')
9
+ export default class Column extends SignalWatcher(LitElement) {
10
+ static styles = css`
11
+ :host{display:contents}
12
+ `;
13
+
14
+ /**
15
+ * 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.
16
+ */
17
+ @property({ type: String, reflect: true }) id: string = '';
18
+ /**
19
+ * The row field this column reads (table-core `accessorKey`). The plain accessor value renders when the `#cell` slot falls through.
20
+ * @example
21
+ * <Column field="email" header="Email" />
22
+ */
23
+ @property({ type: String, reflect: true }) field: string = '';
24
+ /**
25
+ * The header label, rendered when the parent `#colHeader` slot falls through to the plain label.
26
+ */
27
+ @property({ type: String, reflect: true }) header: string = '';
28
+ /**
29
+ * Whether this column participates in click-to-sort. Default `false`. Bind `:sortable="true"` (a bare attr only coerces on Vue+Lit).
30
+ */
31
+ @property({ type: Boolean, reflect: true }) sortable: boolean = false;
32
+ /**
33
+ * Whether this column participates in per-column filtering (the `#filter` slot / faceted filter chrome). Default `false`.
34
+ */
35
+ @property({ type: Boolean, reflect: true }) filterable: boolean = false;
36
+ /**
37
+ * Pin side: `''` (unpinned) | `'left'` | `'right'`. Reserved metadata carried into the parent's column pinning state.
38
+ */
39
+ @property({ type: String, reflect: true }) pinned: string = '';
40
+ /**
41
+ * Optional fixed/initial column width — a CSS length string or a px number.
42
+ */
43
+ @property({ type: String }) width: string | number = '';
44
+ /**
45
+ * 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`.
46
+ */
47
+ @property({ type: Boolean, reflect: true }) expandable: boolean = false;
48
+ /**
49
+ * 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.
50
+ */
51
+ @property({ type: Boolean, reflect: true }) groupable: boolean = true;
52
+ /**
53
+ * 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).
54
+ */
55
+ @property({ type: String }) aggregationFn: string | (((...args: unknown[]) => unknown) | null) = null;
56
+ /**
57
+ * 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).
58
+ */
59
+ @property({ type: Boolean, reflect: true }) editable: boolean = false;
60
+ /**
61
+ * Built-in editor type for this column: `'text'` | `'number'` | `'select'` | `'checkbox'`. Ignored when a custom `#editor` scoped slot handles the column. Default `'text'`.
62
+ */
63
+ @property({ type: String, reflect: true }) editor: string = 'text';
64
+ /**
65
+ * Options for `editor: 'select'` — `[{ value, label }]`. Empty for other editor types.
66
+ */
67
+ @property({ type: Array }) editorOptions: any[] = [];
68
+ /**
69
+ * 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.
70
+ */
71
+ @property({ type: Function }) validate: ((...args: unknown[]) => unknown) | null = null;
72
+ private __rozieFirstUpdateDone = false;
73
+ private __rozieCtxConsumer_data_table_columns = new ContextConsumer(this, { context: __rozieCtx_data_table_columns, subscribe: true });
74
+ private get registry() { return this.__rozieCtxConsumer_data_table_columns.value; }
75
+
76
+ private _disconnectCleanups: Array<() => void> = [];
77
+ // Re-parenting guard: set true once the deferred teardown has actually
78
+ // run (a genuine un-mount), so a subsequent reconnect knows to re-arm.
79
+ private _rozieTornDown = false;
80
+
81
+ firstUpdated(): void {
82
+ this.reg = this.registry;
83
+
84
+ // idempotency flag so a reactive late-context registration (Lit async first paint,
85
+ // REQ-30) and the $onMount registration never double-register the column.
86
+
87
+ this._disconnectCleanups.push((() => {
88
+ if (this.reg) this.reg.unregisterColumn(this.colId());
89
+ }));
90
+
91
+ // register this column's spec. On Lit the injected registry may still be undefined
92
+ // here (REQ-30 async context); the $onUpdate below performs the registration once
93
+ // the value arrives.
94
+ if (this.reg && !this.registered) {
95
+ this.registered = true;
96
+ this.reg.registerColumn(this.colId(), this.buildSpec());
97
+ }
98
+ }
99
+
100
+ updated(changedProperties: Map<string, unknown>): void {
101
+ if (this.__rozieFirstUpdateDone && (changedProperties.has('id') || changedProperties.has('field') || changedProperties.has('header') || changedProperties.has('sortable') || changedProperties.has('filterable') || changedProperties.has('pinned') || changedProperties.has('width') || changedProperties.has('expandable') || changedProperties.has('groupable') || changedProperties.has('aggregationFn') || changedProperties.has('editable') || changedProperties.has('editor') || changedProperties.has('editorOptions') || changedProperties.has('validate'))) { const __watchVal = (() => [this.id, this.field, this.header, this.sortable, this.filterable, this.pinned, this.width, this.expandable, this.groupable, this.aggregationFn, this.editable, this.editor, this.editorOptions, this.validate])(); (() => {
102
+ if (this.reg) this.reg.registerColumn(this.colId(), this.buildSpec());
103
+ })(); }
104
+ this.__rozieFirstUpdateDone = true;
105
+
106
+ if (this.registered) return;
107
+ const live = this.registry;
108
+ if (live == null) return;
109
+ this.reg = live;
110
+ this.registered = true;
111
+ this.reg.registerColumn(this.colId(), this.buildSpec());
112
+ }
113
+
114
+ disconnectedCallback(): void {
115
+ super.disconnectedCallback();
116
+ queueMicrotask(() => {
117
+ if (this.isConnected || this._rozieTornDown) return;
118
+ this._rozieTornDown = true;
119
+ for (const fn of this._disconnectCleanups) fn();
120
+ this._disconnectCleanups = [];
121
+ });
122
+ }
123
+
124
+ render() {
125
+ return html`
126
+
127
+ <div class="rozie-data-table-column" style="display:none" data-rozie-s-289f2d72></div>
128
+ `;
129
+ }
130
+
131
+ reg: any = null;
132
+
133
+ registered = false;
134
+
135
+ colId = () => this.id !== '' ? this.id : this.field;
136
+
137
+ buildSpec = () => ({
138
+ id: this.colId(),
139
+ field: this.field !== '' ? this.field : this.colId(),
140
+ header: this.header,
141
+ sortable: this.sortable,
142
+ filterable: this.filterable,
143
+ pinned: this.pinned,
144
+ width: this.width,
145
+ // Expandable-rows reserved metadata (phase 50, D-04) — carried via the parent registry.
146
+ expandable: this.expandable,
147
+ // Grouping + aggregation metadata (phase 50, reqs 4-7, D-05) — carried via the parent
148
+ // registry; the parent resolves aggregationFn onto the ColumnDef (defensive-wrapping a
149
+ // custom fn) and filters groupableColumns by `groupable`.
150
+ groupable: this.groupable,
151
+ aggregationFn: this.aggregationFn,
152
+ // Editable-cell config (Phase 51) — carried into ColumnDef.meta via the parent
153
+ // registry (the existing per-column metadata path; NO parallel registry).
154
+ editable: this.editable,
155
+ editor: this.editor,
156
+ editorOptions: this.editorOptions,
157
+ validate: this.validate
158
+ });
159
+ }