@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.
@@ -0,0 +1,53 @@
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 { rozieAttr, rozieDisplay } from '@rozie/runtime-lit';
5
+ import { repeat } from 'lit/directives/repeat.js';
6
+
7
+ @customElement('rozie-detail-panel')
8
+ export default class DetailPanel extends SignalWatcher(LitElement) {
9
+ static styles = css`
10
+ :host{display:contents}
11
+ `;
12
+
13
+ /**
14
+ * The raw row object (the `#detail` slot scope `row` = `row.original`). This drop-in walks its own enumerable keys and String-coerces each value into a key/value definition list; a null row renders an empty list.
15
+ */
16
+ @property({ type: Object }) row: unknown = null;
17
+
18
+ private _disconnectCleanups: Array<() => void> = [];
19
+ // Re-parenting guard: set true once the deferred teardown has actually
20
+ // run (a genuine un-mount), so a subsequent reconnect knows to re-arm.
21
+ private _rozieTornDown = false;
22
+
23
+ disconnectedCallback(): void {
24
+ super.disconnectedCallback();
25
+ queueMicrotask(() => {
26
+ if (this.isConnected || this._rozieTornDown) return;
27
+ this._rozieTornDown = true;
28
+ for (const fn of this._disconnectCleanups) fn();
29
+ this._disconnectCleanups = [];
30
+ });
31
+ }
32
+
33
+ render() {
34
+ return html`
35
+ <dl class="rdt-detail-panel" data-rozie-s-8f65bdaa>
36
+
37
+ ${repeat<any>(this.entries(), (pair, _idx) => pair.key, (pair, _idx) => html`<div class="rdt-detail-entry" key=${rozieAttr(pair.key)} data-rozie-s-8f65bdaa>
38
+ <dt class="rdt-detail-key" data-rozie-s-8f65bdaa>${rozieDisplay(pair.key)}</dt>
39
+ <dd class="rdt-detail-value" data-rozie-s-8f65bdaa>${rozieDisplay(pair.value)}</dd>
40
+ </div>`)}
41
+ </dl>
42
+ `;
43
+ }
44
+
45
+ entries = () => {
46
+ const r = this.row;
47
+ if (!r) return [];
48
+ return Object.keys(r).map((key: any) => ({
49
+ key,
50
+ value: r[key] == null ? '' : String(r[key])
51
+ }));
52
+ };
53
+ }
@@ -0,0 +1,67 @@
1
+ import { LitElement, css, html } from 'lit';
2
+ import { customElement, property } from 'lit/decorators.js';
3
+ import { SignalWatcher } from '@lit-labs/preact-signals';
4
+
5
+ @customElement('rozie-editor-checkbox')
6
+ export default class EditorCheckbox extends SignalWatcher(LitElement) {
7
+ static styles = css`
8
+ :host{display:contents}
9
+ `;
10
+
11
+ /**
12
+ * The column id (mirrors the `#editor` slot scope). Used as the input `aria-label`.
13
+ */
14
+ @property({ type: String, reflect: true }) columnId: string = '';
15
+ /**
16
+ * The table-core column object (opaque passthrough from the `#editor` slot scope).
17
+ */
18
+ @property({ type: Object }) column: unknown = null;
19
+ /**
20
+ * The consumer's row data object (opaque passthrough from the `#editor` slot scope).
21
+ */
22
+ @property({ type: Object }) row: unknown = null;
23
+ /**
24
+ * The current cell value — coerced to a real boolean via `!!` to seed the checkbox `checked` state.
25
+ */
26
+ @property({ type: Object }) value: unknown = null;
27
+ /**
28
+ * `(value) => void` — commit the cell. This editor immediately commits the boolean checked state on `@change`. Null-guarded at call sites.
29
+ */
30
+ @property({ type: Function }) commit: ((...args: unknown[]) => unknown) | null = null;
31
+ /**
32
+ * `() => void` — revert the edit (Escape). Null-guarded at call sites.
33
+ */
34
+ @property({ type: Function }) cancel: ((...args: unknown[]) => unknown) | null = null;
35
+
36
+ private _disconnectCleanups: Array<() => void> = [];
37
+ // Re-parenting guard: set true once the deferred teardown has actually
38
+ // run (a genuine un-mount), so a subsequent reconnect knows to re-arm.
39
+ private _rozieTornDown = false;
40
+
41
+ disconnectedCallback(): void {
42
+ super.disconnectedCallback();
43
+ queueMicrotask(() => {
44
+ if (this.isConnected || this._rozieTornDown) return;
45
+ this._rozieTornDown = true;
46
+ for (const fn of this._disconnectCleanups) fn();
47
+ this._disconnectCleanups = [];
48
+ });
49
+ }
50
+
51
+ render() {
52
+ return html`
53
+ <input class="rdt-cell-editor" type="checkbox" data-editing-cell="" aria-label=${this.columnId} ?checked=${!!this.value} @change=${($event: Event & { currentTarget: HTMLInputElement; target: HTMLInputElement }) => { this.onChange($event); }} @keydown=${($event: KeyboardEvent & { currentTarget: HTMLInputElement; target: HTMLInputElement }) => { this.onKeydown($event); }} data-rozie-s-3d792482 />
54
+ `;
55
+ }
56
+
57
+ onChange = (e: any) => {
58
+ this.commit && this.commit(!!(e && e.target ? e.target.checked : false));
59
+ };
60
+
61
+ onKeydown = (e: any) => {
62
+ if (e && e.key === 'Escape') {
63
+ e.preventDefault();
64
+ this.cancel && this.cancel();
65
+ }
66
+ };
67
+ }
@@ -0,0 +1,94 @@
1
+ import { LitElement, css, html } from 'lit';
2
+ import { customElement, property } from 'lit/decorators.js';
3
+ import { SignalWatcher, signal } from '@lit-labs/preact-signals';
4
+
5
+ @customElement('rozie-editor-date')
6
+ export default class EditorDate extends SignalWatcher(LitElement) {
7
+ static styles = css`
8
+ :host{display:contents}
9
+ `;
10
+
11
+ /**
12
+ * The column id (mirrors the `#editor` slot scope). Used as the input `aria-label`.
13
+ */
14
+ @property({ type: String, reflect: true }) columnId: string = '';
15
+ /**
16
+ * The table-core column object (opaque passthrough from the `#editor` slot scope).
17
+ */
18
+ @property({ type: Object }) column: unknown = null;
19
+ /**
20
+ * The consumer's row data object (opaque passthrough from the `#editor` slot scope).
21
+ */
22
+ @property({ type: Object }) row: unknown = null;
23
+ /**
24
+ * The current cell value the local draft seeds from (setup-once); String-coerced to an ISO `YYYY-MM-DD` string for the native date input.
25
+ */
26
+ @property({ type: Object }) value: unknown = null;
27
+ /**
28
+ * `(value) => void` — commit the cell with the ISO `YYYY-MM-DD` string (Enter / blur). Null-guarded at call sites.
29
+ */
30
+ @property({ type: Function }) commit: ((...args: unknown[]) => unknown) | null = null;
31
+ /**
32
+ * `() => void` — revert the edit (Escape). Null-guarded at call sites.
33
+ */
34
+ @property({ type: Function }) cancel: ((...args: unknown[]) => unknown) | null = null;
35
+ private _draft = signal('');
36
+
37
+ private _disconnectCleanups: Array<() => void> = [];
38
+ // Re-parenting guard: set true once the deferred teardown has actually
39
+ // run (a genuine un-mount), so a subsequent reconnect knows to re-arm.
40
+ private _rozieTornDown = false;
41
+
42
+ firstUpdated(): void {
43
+ // Seed the draft once from the incoming value (setup-once). A native date input
44
+ // only accepts `YYYY-MM-DD`; normalize null/undefined to ''.
45
+ this._draft.value = this.value != null ? String(this.value) : '';
46
+ }
47
+
48
+ disconnectedCallback(): void {
49
+ super.disconnectedCallback();
50
+ queueMicrotask(() => {
51
+ if (this.isConnected || this._rozieTornDown) return;
52
+ this._rozieTornDown = true;
53
+ for (const fn of this._disconnectCleanups) fn();
54
+ this._disconnectCleanups = [];
55
+ });
56
+ }
57
+
58
+ render() {
59
+ return html`
60
+ <input class="rdt-cell-editor" type="date" data-editing-cell="" aria-label=${this.columnId} .value=${this._draft.value} @input=${($event: InputEvent & { currentTarget: HTMLInputElement; target: HTMLInputElement }) => { this.onInput($event); }} @change=${($event: Event & { currentTarget: HTMLInputElement; target: HTMLInputElement }) => { this.onChange($event); }} @keydown=${($event: KeyboardEvent & { currentTarget: HTMLInputElement; target: HTMLInputElement }) => { this.onKeydown($event); }} @blur=${($event: FocusEvent & { currentTarget: HTMLInputElement; target: HTMLInputElement }) => { this.onBlur(); }} data-rozie-s-7abe1a56 />
61
+ `;
62
+ }
63
+
64
+ onInput = (e: any) => {
65
+ this._draft.value = e && e.target ? e.target.value : '';
66
+ };
67
+
68
+ doCommit = () => {
69
+ // commit the ISO date string the native control already produced.
70
+ this.commit && this.commit(this._draft.value);
71
+ };
72
+
73
+ doCancel = () => {
74
+ this.cancel && this.cancel();
75
+ };
76
+
77
+ onChange = (e: any) => {
78
+ this._draft.value = e && e.target ? e.target.value : '';
79
+ };
80
+
81
+ onKeydown = (e: any) => {
82
+ if (e && e.key === 'Enter') {
83
+ e.preventDefault();
84
+ this.doCommit();
85
+ } else if (e && e.key === 'Escape') {
86
+ e.preventDefault();
87
+ this.doCancel();
88
+ }
89
+ };
90
+
91
+ onBlur = () => {
92
+ this.doCommit();
93
+ };
94
+ }
@@ -0,0 +1,95 @@
1
+ import { LitElement, css, html } from 'lit';
2
+ import { customElement, property } from 'lit/decorators.js';
3
+ import { SignalWatcher, signal } from '@lit-labs/preact-signals';
4
+
5
+ @customElement('rozie-editor-number')
6
+ export default class EditorNumber extends SignalWatcher(LitElement) {
7
+ static styles = css`
8
+ :host{display:contents}
9
+ `;
10
+
11
+ /**
12
+ * The column id (mirrors the `#editor` slot scope). Used as the input `aria-label`.
13
+ */
14
+ @property({ type: String, reflect: true }) columnId: string = '';
15
+ /**
16
+ * The table-core column object (opaque passthrough from the `#editor` slot scope).
17
+ */
18
+ @property({ type: Object }) column: unknown = null;
19
+ /**
20
+ * The consumer's row data object (opaque passthrough from the `#editor` slot scope).
21
+ */
22
+ @property({ type: Object }) row: unknown = null;
23
+ /**
24
+ * The current cell value the local draft string seeds from (setup-once).
25
+ */
26
+ @property({ type: Object }) value: unknown = null;
27
+ /**
28
+ * `(value) => void` — commit the cell. The draft is coerced with `Number()` at commit time; an empty/whitespace or non-numeric draft commits `null` (never `NaN`). Null-guarded at call sites.
29
+ */
30
+ @property({ type: Function }) commit: ((...args: unknown[]) => unknown) | null = null;
31
+ /**
32
+ * `() => void` — revert the edit (Escape). Null-guarded at call sites.
33
+ */
34
+ @property({ type: Function }) cancel: ((...args: unknown[]) => unknown) | null = null;
35
+ private _draft = signal('');
36
+
37
+ private _disconnectCleanups: Array<() => void> = [];
38
+ // Re-parenting guard: set true once the deferred teardown has actually
39
+ // run (a genuine un-mount), so a subsequent reconnect knows to re-arm.
40
+ private _rozieTornDown = false;
41
+
42
+ firstUpdated(): void {
43
+ // Seed the draft string once from the incoming value (setup-once).
44
+ this._draft.value = this.value != null ? String(this.value) : '';
45
+ }
46
+
47
+ disconnectedCallback(): void {
48
+ super.disconnectedCallback();
49
+ queueMicrotask(() => {
50
+ if (this.isConnected || this._rozieTornDown) return;
51
+ this._rozieTornDown = true;
52
+ for (const fn of this._disconnectCleanups) fn();
53
+ this._disconnectCleanups = [];
54
+ });
55
+ }
56
+
57
+ render() {
58
+ return html`
59
+ <input class="rdt-cell-editor" type="number" data-editing-cell="" aria-label=${this.columnId} .value=${this._draft.value} @input=${($event: InputEvent & { currentTarget: HTMLInputElement; target: HTMLInputElement }) => { this.onInput($event); }} @keydown=${($event: KeyboardEvent & { currentTarget: HTMLInputElement; target: HTMLInputElement }) => { this.onKeydown($event); }} @blur=${($event: FocusEvent & { currentTarget: HTMLInputElement; target: HTMLInputElement }) => { this.onBlur(); }} data-rozie-s-b2792b32 />
60
+ `;
61
+ }
62
+
63
+ onInput = (e: any) => {
64
+ this._draft.value = e && e.target ? e.target.value : '';
65
+ };
66
+
67
+ doCommit = () => {
68
+ if (!this.commit) return;
69
+ const raw = this._draft.value;
70
+ if (raw == null || String(raw).trim() === '') {
71
+ this.commit(null);
72
+ return;
73
+ }
74
+ const n = Number(raw);
75
+ this.commit(Number.isNaN(n) ? null : n);
76
+ };
77
+
78
+ doCancel = () => {
79
+ this.cancel && this.cancel();
80
+ };
81
+
82
+ onKeydown = (e: any) => {
83
+ if (e && e.key === 'Enter') {
84
+ e.preventDefault();
85
+ this.doCommit();
86
+ } else if (e && e.key === 'Escape') {
87
+ e.preventDefault();
88
+ this.doCancel();
89
+ }
90
+ };
91
+
92
+ onBlur = () => {
93
+ this.doCommit();
94
+ };
95
+ }
@@ -0,0 +1,99 @@
1
+ import { LitElement, css, html } from 'lit';
2
+ import { customElement, property } from 'lit/decorators.js';
3
+ import { SignalWatcher, signal } from '@lit-labs/preact-signals';
4
+ import { rozieAttr, rozieDisplay } from '@rozie/runtime-lit';
5
+ import { repeat } from 'lit/directives/repeat.js';
6
+
7
+ @customElement('rozie-editor-select')
8
+ export default class EditorSelect extends SignalWatcher(LitElement) {
9
+ static styles = css`
10
+ :host{display:contents}
11
+ `;
12
+
13
+ /**
14
+ * The column id (mirrors the `#editor` slot scope). Used as the select `aria-label`.
15
+ */
16
+ @property({ type: String, reflect: true }) columnId: string = '';
17
+ /**
18
+ * The table-core column object (opaque passthrough from the `#editor` slot scope).
19
+ */
20
+ @property({ type: Object }) column: unknown = null;
21
+ /**
22
+ * The consumer's row data object (opaque passthrough from the `#editor` slot scope).
23
+ */
24
+ @property({ type: Object }) row: unknown = null;
25
+ /**
26
+ * The current cell value the local draft seeds from (setup-once); String-coerced for the `<select>` binding.
27
+ */
28
+ @property({ type: Object }) value: unknown = null;
29
+ /**
30
+ * `(value) => void` — commit the cell with the selected value (Enter / blur). Null-guarded at call sites.
31
+ */
32
+ @property({ type: Function }) commit: ((...args: unknown[]) => unknown) | null = null;
33
+ /**
34
+ * `() => void` — revert the edit (Escape). Null-guarded at call sites.
35
+ */
36
+ @property({ type: Function }) cancel: ((...args: unknown[]) => unknown) | null = null;
37
+ /**
38
+ * The select options — `[{ value, label }]`. Mirrors `<Column editorOptions>`.
39
+ */
40
+ @property({ type: Array }) options: any[] = [];
41
+ private _draft = signal('');
42
+
43
+ private _disconnectCleanups: Array<() => void> = [];
44
+ // Re-parenting guard: set true once the deferred teardown has actually
45
+ // run (a genuine un-mount), so a subsequent reconnect knows to re-arm.
46
+ private _rozieTornDown = false;
47
+
48
+ firstUpdated(): void {
49
+ // Seed the draft once from the incoming value (setup-once). Normalize null/undefined
50
+ // to '' so the <select> binds to a string.
51
+ this._draft.value = this.value != null ? String(this.value) : '';
52
+
53
+ // Picking/arrow-cycling an option updates the draft only — no commit.
54
+ }
55
+
56
+ disconnectedCallback(): void {
57
+ super.disconnectedCallback();
58
+ queueMicrotask(() => {
59
+ if (this.isConnected || this._rozieTornDown) return;
60
+ this._rozieTornDown = true;
61
+ for (const fn of this._disconnectCleanups) fn();
62
+ this._disconnectCleanups = [];
63
+ });
64
+ }
65
+
66
+ render() {
67
+ return html`
68
+ <select class="rdt-cell-editor" data-editing-cell="" aria-label=${this.columnId} .value=${this._draft.value} @change=${($event: Event & { currentTarget: HTMLSelectElement; target: HTMLSelectElement }) => { this.onChange($event); }} @keydown=${($event: KeyboardEvent & { currentTarget: HTMLSelectElement; target: HTMLSelectElement }) => { this.onKeydown($event); }} @blur=${($event: FocusEvent & { currentTarget: HTMLSelectElement; target: HTMLSelectElement }) => { this.onBlur(); }} data-rozie-s-117f1a16>
69
+ ${repeat<any>(this.options, (opt, _idx) => opt.value, (opt, _idx) => html`<option key=${rozieAttr(opt.value)} value=${rozieAttr(opt.value)} data-rozie-s-117f1a16>${rozieDisplay(opt.label)}</option>`)}
70
+ </select>
71
+ `;
72
+ }
73
+
74
+ onChange = (e: any) => {
75
+ this._draft.value = e && e.target ? e.target.value : '';
76
+ };
77
+
78
+ doCommit = () => {
79
+ this.commit && this.commit(this._draft.value);
80
+ };
81
+
82
+ doCancel = () => {
83
+ this.cancel && this.cancel();
84
+ };
85
+
86
+ onKeydown = (e: any) => {
87
+ if (e && e.key === 'Enter') {
88
+ e.preventDefault();
89
+ this.doCommit();
90
+ } else if (e && e.key === 'Escape') {
91
+ e.preventDefault();
92
+ this.doCancel();
93
+ }
94
+ };
95
+
96
+ onBlur = () => {
97
+ this.doCommit();
98
+ };
99
+ }
@@ -0,0 +1,92 @@
1
+ import { LitElement, css, html } from 'lit';
2
+ import { customElement, property } from 'lit/decorators.js';
3
+ import { SignalWatcher, signal } from '@lit-labs/preact-signals';
4
+
5
+ @customElement('rozie-editor-text')
6
+ export default class EditorText extends SignalWatcher(LitElement) {
7
+ static styles = css`
8
+ :host{display:contents}
9
+ `;
10
+
11
+ /**
12
+ * The column id (mirrors the `#editor` slot scope). Used as the input `aria-label` fallback.
13
+ */
14
+ @property({ type: String, reflect: true }) columnId: string = '';
15
+ /**
16
+ * The table-core column object (opaque passthrough from the `#editor` slot scope).
17
+ */
18
+ @property({ type: Object }) column: unknown = null;
19
+ /**
20
+ * The consumer's row data object (opaque passthrough from the `#editor` slot scope).
21
+ */
22
+ @property({ type: Object }) row: unknown = null;
23
+ /**
24
+ * The current cell value the editor seeds its local draft from (setup-once).
25
+ */
26
+ @property({ type: Object }) value: unknown = null;
27
+ /**
28
+ * `(value) => void` — commit the edited cell value (from the `#editor` slot scope). Null-guarded at call sites.
29
+ */
30
+ @property({ type: Function }) commit: ((...args: unknown[]) => unknown) | null = null;
31
+ /**
32
+ * `() => void` — revert the edit and close the editor (from the `#editor` slot scope). Null-guarded at call sites.
33
+ */
34
+ @property({ type: Function }) cancel: ((...args: unknown[]) => unknown) | null = null;
35
+ private _draft = signal('');
36
+
37
+ private _disconnectCleanups: Array<() => void> = [];
38
+ // Re-parenting guard: set true once the deferred teardown has actually
39
+ // run (a genuine un-mount), so a subsequent reconnect knows to re-arm.
40
+ private _rozieTornDown = false;
41
+
42
+ firstUpdated(): void {
43
+ // Seed the draft once at setup from the incoming value (setup-once, NOT in the
44
+ // template). Normalize null/undefined to '' so the input value binds to a string.
45
+ this._draft.value = this.value != null ? String(this.value) : '';
46
+
47
+ // Untyped handler param neutralizes to `any`, so reading e.target.value typechecks
48
+ // ×6 (the global-filter idiom). Never inline `$data.x = $event.target.value`.
49
+ }
50
+
51
+ disconnectedCallback(): void {
52
+ super.disconnectedCallback();
53
+ queueMicrotask(() => {
54
+ if (this.isConnected || this._rozieTornDown) return;
55
+ this._rozieTornDown = true;
56
+ for (const fn of this._disconnectCleanups) fn();
57
+ this._disconnectCleanups = [];
58
+ });
59
+ }
60
+
61
+ render() {
62
+ return html`
63
+ <input class="rdt-cell-editor" type="text" data-editing-cell="" aria-label=${this.columnId} .value=${this._draft.value} @input=${($event: InputEvent & { currentTarget: HTMLInputElement; target: HTMLInputElement }) => { this.onInput($event); }} @keydown=${($event: KeyboardEvent & { currentTarget: HTMLInputElement; target: HTMLInputElement }) => { this.onKeydown($event); }} @blur=${($event: FocusEvent & { currentTarget: HTMLInputElement; target: HTMLInputElement }) => { this.onBlur(); }} data-rozie-s-0d17f43a />
64
+ `;
65
+ }
66
+
67
+ onInput = (e: any) => {
68
+ this._draft.value = e && e.target ? e.target.value : '';
69
+ };
70
+
71
+ doCommit = () => {
72
+ this.commit && this.commit(this._draft.value);
73
+ };
74
+
75
+ doCancel = () => {
76
+ this.cancel && this.cancel();
77
+ };
78
+
79
+ onKeydown = (e: any) => {
80
+ if (e && e.key === 'Enter') {
81
+ e.preventDefault();
82
+ this.doCommit();
83
+ } else if (e && e.key === 'Escape') {
84
+ e.preventDefault();
85
+ this.doCancel();
86
+ }
87
+ };
88
+
89
+ onBlur = () => {
90
+ this.doCommit();
91
+ };
92
+ }
@@ -0,0 +1,89 @@
1
+ import { LitElement, css, html } from 'lit';
2
+ import { customElement, property } from 'lit/decorators.js';
3
+ import { SignalWatcher, signal } from '@lit-labs/preact-signals';
4
+ import { rozieAttr } from '@rozie/runtime-lit';
5
+
6
+ @customElement('rozie-filter-number-range')
7
+ export default class FilterNumberRange extends SignalWatcher(LitElement) {
8
+ static styles = css`
9
+ :host{display:contents}
10
+ `;
11
+
12
+ /**
13
+ * The column id (mirrors the `#filter` slot scope) — used as the filter key and the input `aria-label` base.
14
+ */
15
+ @property({ type: String, reflect: true }) columnId: string = '';
16
+ /**
17
+ * The table-core column object (opaque passthrough from the `#filter` slot scope).
18
+ */
19
+ @property({ type: Object }) column: unknown = null;
20
+ /**
21
+ * The current column filter value (`[min, max]` tuple or null) the two inputs seed from (setup-once).
22
+ */
23
+ @property({ type: Object }) value: unknown = null;
24
+ /**
25
+ * `(columnId, value) => void` — apply the column filter as a `[min, max]` tuple (each side coerced to a Number or `undefined`, so a one-sided range works); both empty clears the filter. Null-guarded at call sites.
26
+ */
27
+ @property({ type: Function }) setFilter: ((...args: unknown[]) => unknown) | null = null;
28
+ /**
29
+ * The faceted `[min, max]` bounds for this column (`[number, number]` or null) — drives the input placeholders only.
30
+ */
31
+ @property({ type: Object }) minMax: unknown = null;
32
+ private _minDraft = signal('');
33
+ private _maxDraft = signal('');
34
+
35
+ private _disconnectCleanups: Array<() => void> = [];
36
+ // Re-parenting guard: set true once the deferred teardown has actually
37
+ // run (a genuine un-mount), so a subsequent reconnect knows to re-arm.
38
+ private _rozieTornDown = false;
39
+
40
+ firstUpdated(): void {
41
+ // Seed both drafts once at setup from the incoming [min,max] tuple (setup-once).
42
+ this._minDraft.value = Array.isArray(this.value) && this.value[0] != null ? String(this.value[0]) : '';
43
+ this._maxDraft.value = Array.isArray(this.value) && this.value[1] != null ? String(this.value[1]) : '';
44
+
45
+ // Untyped handler params neutralize to `any` (the global-filter idiom).
46
+ }
47
+
48
+ disconnectedCallback(): void {
49
+ super.disconnectedCallback();
50
+ queueMicrotask(() => {
51
+ if (this.isConnected || this._rozieTornDown) return;
52
+ this._rozieTornDown = true;
53
+ for (const fn of this._disconnectCleanups) fn();
54
+ this._disconnectCleanups = [];
55
+ });
56
+ }
57
+
58
+ render() {
59
+ return html`
60
+ <span style="display:flex; align-items: center" data-rozie-s-97b2c090>
61
+ <input class="rdt-col-filter" part="col-filter" type="number" aria-label=${rozieAttr(this.columnId + ' min')} placeholder=${rozieAttr(this.minPlaceholder())} .value=${this._minDraft.value} @input=${($event: InputEvent & { currentTarget: HTMLInputElement; target: HTMLInputElement }) => { this.onMinInput($event); }} @change=${($event: Event & { currentTarget: HTMLInputElement; target: HTMLInputElement }) => { this.applyRange(); }} data-rozie-s-97b2c090 />
62
+ <span data-rozie-s-97b2c090> - </span>
63
+ <input class="rdt-col-filter" part="col-filter" type="number" aria-label=${rozieAttr(this.columnId + ' max')} placeholder=${rozieAttr(this.maxPlaceholder())} .value=${this._maxDraft.value} @input=${($event: InputEvent & { currentTarget: HTMLInputElement; target: HTMLInputElement }) => { this.onMaxInput($event); }} @change=${($event: Event & { currentTarget: HTMLInputElement; target: HTMLInputElement }) => { this.applyRange(); }} data-rozie-s-97b2c090 />
64
+ </span>
65
+ `;
66
+ }
67
+
68
+ onMinInput = (e: any) => {
69
+ this._minDraft.value = e && e.target ? e.target.value : '';
70
+ };
71
+
72
+ onMaxInput = (e: any) => {
73
+ this._maxDraft.value = e && e.target ? e.target.value : '';
74
+ };
75
+
76
+ minPlaceholder = () => Array.isArray(this.minMax) && this.minMax[0] != null ? String(this.minMax[0]) : '';
77
+
78
+ maxPlaceholder = () => Array.isArray(this.minMax) && this.minMax[1] != null ? String(this.minMax[1]) : '';
79
+
80
+ applyRange = () => {
81
+ const minNum = this._minDraft.value === '' ? undefined : Number(this._minDraft.value);
82
+ const maxNum = this._maxDraft.value === '' ? undefined : Number(this._maxDraft.value);
83
+ if (minNum === undefined && maxNum === undefined) {
84
+ this.setFilter && this.setFilter(this.columnId, '');
85
+ } else {
86
+ this.setFilter && this.setFilter(this.columnId, [minNum, maxNum]);
87
+ }
88
+ };
89
+ }
@@ -0,0 +1,68 @@
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 { rozieAttr, rozieDisplay } from '@rozie/runtime-lit';
5
+ import { repeat } from 'lit/directives/repeat.js';
6
+
7
+ @customElement('rozie-filter-select')
8
+ export default class FilterSelect extends SignalWatcher(LitElement) {
9
+ static styles = css`
10
+ :host{display:contents}
11
+ `;
12
+
13
+ /**
14
+ * The column id (mirrors the `#filter` slot scope) — used as the filter key and the select `aria-label`.
15
+ */
16
+ @property({ type: String, reflect: true }) columnId: string = '';
17
+ /**
18
+ * The table-core column object (opaque passthrough from the `#filter` slot scope).
19
+ */
20
+ @property({ type: Object }) column: unknown = null;
21
+ /**
22
+ * The current column filter value the select seeds from (String-coerced).
23
+ */
24
+ @property({ type: Object }) value: unknown = null;
25
+ /**
26
+ * `(columnId, value) => void` — apply the column filter on change; the leading empty "All" option clears it. Null-guarded at call sites.
27
+ */
28
+ @property({ type: Function }) setFilter: ((...args: unknown[]) => unknown) | null = null;
29
+ /**
30
+ * The faceted distinct keys for this column (cross-filtered, keys only — no occurrence counts) used to build the `<option>` list.
31
+ */
32
+ @property({ type: Array }) uniqueValues: any[] = [];
33
+
34
+ private _disconnectCleanups: Array<() => void> = [];
35
+ // Re-parenting guard: set true once the deferred teardown has actually
36
+ // run (a genuine un-mount), so a subsequent reconnect knows to re-arm.
37
+ private _rozieTornDown = false;
38
+
39
+ disconnectedCallback(): void {
40
+ super.disconnectedCallback();
41
+ queueMicrotask(() => {
42
+ if (this.isConnected || this._rozieTornDown) return;
43
+ this._rozieTornDown = true;
44
+ for (const fn of this._disconnectCleanups) fn();
45
+ this._disconnectCleanups = [];
46
+ });
47
+ }
48
+
49
+ render() {
50
+ return html`
51
+ <select class="rdt-col-filter" part="col-filter" aria-label=${this.columnId} .value=${this.selectValue()} @change=${($event: Event & { currentTarget: HTMLSelectElement; target: HTMLSelectElement }) => { this.onChange($event); }} data-rozie-s-d75b42b2>
52
+ <option value="" data-rozie-s-d75b42b2>All</option>
53
+ ${repeat<any>(this.uniqueValues, (opt, _idx) => opt, (opt, _idx) => html`<option key=${rozieAttr(opt)} value=${rozieAttr(opt)} data-rozie-s-d75b42b2>${rozieDisplay(opt)}</option>`)}
54
+ </select>
55
+ `;
56
+ }
57
+
58
+ selectValue = () => this.value != null ? String(this.value) : '';
59
+
60
+ onChange = (e: any) => {
61
+ const v = e && e.target ? e.target.value : '';
62
+ if (v === '') {
63
+ this.setFilter && this.setFilter(this.columnId, '');
64
+ } else {
65
+ this.setFilter && this.setFilter(this.columnId, v);
66
+ }
67
+ };
68
+ }