@rozie-ui/data-table-lit 0.1.0 → 0.2.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/README.md +2 -2
- package/dist/index.cjs +44 -7
- package/dist/index.d.cts +10 -1
- package/dist/index.d.mts +10 -1
- package/dist/index.mjs +44 -7
- package/package.json +7 -9
- package/src/DataTable.ts +48 -12
- package/src/EditorText.ts +17 -2
- package/src/Popover.ts +0 -446
- package/src/internal/middleware.ts +0 -54
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Idiomatic **lit** `DataTable` — a headless, fully-accessible (WAI-ARIA) data t
|
|
|
8
8
|
npm i @rozie-ui/data-table-lit
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
Peer dependencies: `lit + @lit/context + @lit-labs/preact-signals + @preact/signals-core + @tanstack/table-core + @tanstack/virtual-core + @
|
|
11
|
+
Peer dependencies: `lit + @lit/context + @lit-labs/preact-signals + @preact/signals-core + @tanstack/table-core + @tanstack/virtual-core + @rozie-ui/popover-lit`. Install them alongside this package.
|
|
12
12
|
|
|
13
13
|
## Usage
|
|
14
14
|
|
|
@@ -494,5 +494,5 @@ The `detail` (expandable rows), `groupBar` (grouping) and `filter` (faceted filt
|
|
|
494
494
|
| filter | columnId, value, uniqueValues, minMax, setFilter |
|
|
495
495
|
| selectCell | row, checked, toggle |
|
|
496
496
|
| cell | columnId, column, row, value |
|
|
497
|
-
| editor | columnId, column, row, value, commit, cancel |
|
|
497
|
+
| editor | columnId, column, row, value, commit, cancel, autofocus |
|
|
498
498
|
| detail | row |
|
package/dist/index.cjs
CHANGED
|
@@ -8,6 +8,7 @@ let _lit_labs_preact_signals = require("@lit-labs/preact-signals");
|
|
|
8
8
|
let _rozie_runtime_lit = require("@rozie/runtime-lit");
|
|
9
9
|
let _lit_context = require("@lit/context");
|
|
10
10
|
let lit_directives_repeat_js = require("lit/directives/repeat.js");
|
|
11
|
+
require("@rozie-ui/popover-lit");
|
|
11
12
|
let _tanstack_table_core = require("@tanstack/table-core");
|
|
12
13
|
let _tanstack_virtual_core = require("@tanstack/virtual-core");
|
|
13
14
|
//#region \0@oxc-project+runtime@0.127.0/helpers/decorate.js
|
|
@@ -175,6 +176,7 @@ let DataTable = class DataTable extends (0, _lit_labs_preact_signals.SignalWatch
|
|
|
175
176
|
this._draftValue = (0, _lit_labs_preact_signals.signal)(null);
|
|
176
177
|
this._invalidMsg = (0, _lit_labs_preact_signals.signal)("");
|
|
177
178
|
this._editVer = (0, _lit_labs_preact_signals.signal)(0);
|
|
179
|
+
this._editFocusColId = (0, _lit_labs_preact_signals.signal)(null);
|
|
178
180
|
this._editingRowIndex = (0, _lit_labs_preact_signals.signal)(null);
|
|
179
181
|
this._rowDraft = (0, _lit_labs_preact_signals.signal)({});
|
|
180
182
|
this._rangeAnchor = (0, _lit_labs_preact_signals.signal)(null);
|
|
@@ -1054,8 +1056,7 @@ let DataTable = class DataTable extends (0, _lit_labs_preact_signals.SignalWatch
|
|
|
1054
1056
|
const base = this.pinStyle(colId);
|
|
1055
1057
|
if (this.isExpanderColumn(colId) && row && row.depth) {
|
|
1056
1058
|
const pad = "padding-left:" + (.5 + row.depth * 1.25) + "rem";
|
|
1057
|
-
|
|
1058
|
-
return base ? base + sep + pad : pad;
|
|
1059
|
+
return base ? base + pad : pad;
|
|
1059
1060
|
}
|
|
1060
1061
|
return base;
|
|
1061
1062
|
};
|
|
@@ -2476,6 +2477,7 @@ let DataTable = class DataTable extends (0, _lit_labs_preact_signals.SignalWatch
|
|
|
2476
2477
|
};
|
|
2477
2478
|
this.focusEditorWhenReady = (selectAll = true) => {
|
|
2478
2479
|
if (!this.gridRoot) return;
|
|
2480
|
+
if (this._editFocusColId.value != null && this.hasEditorSlot(this._editFocusColId.value)) return;
|
|
2479
2481
|
let attempts = 0;
|
|
2480
2482
|
const tryFocus = () => {
|
|
2481
2483
|
const el = this.gridRoot ? this.gridRoot.querySelector("[data-editing-cell]") : null;
|
|
@@ -2526,6 +2528,7 @@ let DataTable = class DataTable extends (0, _lit_labs_preact_signals.SignalWatch
|
|
|
2526
2528
|
this._draftValue.value = seed != null ? seed : this.cellValueAt(rowIndex, colIndex);
|
|
2527
2529
|
this._activeInControl.value = true;
|
|
2528
2530
|
this._editVer.value = this._editVer.value + 1;
|
|
2531
|
+
this._editFocusColId.value = colId;
|
|
2529
2532
|
this.focusEditorWhenReady(seed == null);
|
|
2530
2533
|
};
|
|
2531
2534
|
this.focusCellWhenReady = (row, col) => {
|
|
@@ -2562,6 +2565,7 @@ let DataTable = class DataTable extends (0, _lit_labs_preact_signals.SignalWatch
|
|
|
2562
2565
|
this._invalidMsg.value = "";
|
|
2563
2566
|
this._activeInControl.value = false;
|
|
2564
2567
|
this._editVer.value = this._editVer.value + 1;
|
|
2568
|
+
this._editFocusColId.value = null;
|
|
2565
2569
|
};
|
|
2566
2570
|
this.endRowEdit = () => {
|
|
2567
2571
|
this._editingRowIndex.value = null;
|
|
@@ -2569,6 +2573,14 @@ let DataTable = class DataTable extends (0, _lit_labs_preact_signals.SignalWatch
|
|
|
2569
2573
|
this._invalidMsg.value = "";
|
|
2570
2574
|
this._activeInControl.value = false;
|
|
2571
2575
|
this._editVer.value = this._editVer.value + 1;
|
|
2576
|
+
this._editFocusColId.value = null;
|
|
2577
|
+
};
|
|
2578
|
+
this.editorAutofocusFor = (colId, rowIndex) => {
|
|
2579
|
+
if (this._editVer.value < 0) return false;
|
|
2580
|
+
if (this._editingRowIndex.value != null) {
|
|
2581
|
+
if (this._editingRowIndex.value !== rowIndex) return false;
|
|
2582
|
+
} else if (this._editingRow.value !== rowIndex) return false;
|
|
2583
|
+
return this._editFocusColId.value != null && this._editFocusColId.value === colId;
|
|
2572
2584
|
};
|
|
2573
2585
|
this.coerceCellValue = (colId, raw) => {
|
|
2574
2586
|
if (this.editorTypeOf(colId) !== "number") return raw;
|
|
@@ -2697,6 +2709,8 @@ let DataTable = class DataTable extends (0, _lit_labs_preact_signals.SignalWatch
|
|
|
2697
2709
|
};
|
|
2698
2710
|
this.focusRowEditorAt = (rowIndex, colIndex) => {
|
|
2699
2711
|
if (!this.gridRoot) return;
|
|
2712
|
+
const colId = this.columnIdAt(rowIndex, colIndex);
|
|
2713
|
+
if (colId != null && this.hasEditorSlot(colId)) return;
|
|
2700
2714
|
let attempts = 0;
|
|
2701
2715
|
const tryFocus = () => {
|
|
2702
2716
|
const cellEl = this.resolveCellEl(String(rowIndex), colIndex);
|
|
@@ -2737,6 +2751,7 @@ let DataTable = class DataTable extends (0, _lit_labs_preact_signals.SignalWatch
|
|
|
2737
2751
|
this._editingRowIndex.value = rowIndex;
|
|
2738
2752
|
this._activeInControl.value = true;
|
|
2739
2753
|
this._editVer.value = this._editVer.value + 1;
|
|
2754
|
+
this._editFocusColId.value = editable[0].colId;
|
|
2740
2755
|
this.focusEditorWhenReady();
|
|
2741
2756
|
};
|
|
2742
2757
|
this.commitRow = () => {
|
|
@@ -2756,6 +2771,8 @@ let DataTable = class DataTable extends (0, _lit_labs_preact_signals.SignalWatch
|
|
|
2756
2771
|
const err = this.runValidator(ec.colId, this.coerceCellValue(ec.colId, draft[ec.colId]), rowOriginal);
|
|
2757
2772
|
if (err !== true) {
|
|
2758
2773
|
this.setInvalid(err);
|
|
2774
|
+
this._editFocusColId.value = ec.colId;
|
|
2775
|
+
this._editVer.value = this._editVer.value + 1;
|
|
2759
2776
|
this.focusRowEditorAt(rowIndex, ec.colIndex);
|
|
2760
2777
|
return false;
|
|
2761
2778
|
}
|
|
@@ -2925,6 +2942,8 @@ let DataTable = class DataTable extends (0, _lit_labs_preact_signals.SignalWatch
|
|
|
2925
2942
|
if (pos < 0) pos = 0;
|
|
2926
2943
|
const len = cols.length;
|
|
2927
2944
|
const nextPos = backward ? (pos - 1 + len) % len : (pos + 1) % len;
|
|
2945
|
+
this._editFocusColId.value = editable[nextPos].colId;
|
|
2946
|
+
this._editVer.value = this._editVer.value + 1;
|
|
2928
2947
|
this.focusRowEditorAt(rowIndex, cols[nextPos]);
|
|
2929
2948
|
};
|
|
2930
2949
|
this.onEditorKeyDown = (e) => {
|
|
@@ -4023,7 +4042,8 @@ ${this.groupable ? lit.html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4
|
|
|
4023
4042
|
row: wr.row.original,
|
|
4024
4043
|
value: this.editorValueFor(cell.column.id),
|
|
4025
4044
|
commit: this.editorCommitFor(cell.column.id),
|
|
4026
|
-
cancel: this.editorCancelFor()
|
|
4045
|
+
cancel: this.editorCancelFor(),
|
|
4046
|
+
autofocus: this.editorAutofocusFor(cell.column.id, wr.vi.index)
|
|
4027
4047
|
}) : lit.html`<slot name="editor" data-rozie-params=${(() => {
|
|
4028
4048
|
try {
|
|
4029
4049
|
return JSON.stringify({
|
|
@@ -4032,7 +4052,8 @@ ${this.groupable ? lit.html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4
|
|
|
4032
4052
|
row: wr.row.original,
|
|
4033
4053
|
value: this.editorValueFor(cell.column.id),
|
|
4034
4054
|
commit: this.editorCommitFor(cell.column.id),
|
|
4035
|
-
cancel: this.editorCancelFor()
|
|
4055
|
+
cancel: this.editorCancelFor(),
|
|
4056
|
+
autofocus: this.editorAutofocusFor(cell.column.id, wr.vi.index)
|
|
4036
4057
|
});
|
|
4037
4058
|
} catch {
|
|
4038
4059
|
return "{}";
|
|
@@ -4314,7 +4335,8 @@ ${this.groupable ? lit.html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4
|
|
|
4314
4335
|
row: row.original,
|
|
4315
4336
|
value: this.editorValueFor(cell.column.id),
|
|
4316
4337
|
commit: this.editorCommitFor(cell.column.id),
|
|
4317
|
-
cancel: this.editorCancelFor()
|
|
4338
|
+
cancel: this.editorCancelFor(),
|
|
4339
|
+
autofocus: this.editorAutofocusFor(cell.column.id, this.rowIndexOf(row))
|
|
4318
4340
|
}) : lit.html`<slot name="editor" data-rozie-params=${(() => {
|
|
4319
4341
|
try {
|
|
4320
4342
|
return JSON.stringify({
|
|
@@ -4323,7 +4345,8 @@ ${this.groupable ? lit.html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4
|
|
|
4323
4345
|
row: row.original,
|
|
4324
4346
|
value: this.editorValueFor(cell.column.id),
|
|
4325
4347
|
commit: this.editorCommitFor(cell.column.id),
|
|
4326
|
-
cancel: this.editorCancelFor()
|
|
4348
|
+
cancel: this.editorCancelFor(),
|
|
4349
|
+
autofocus: this.editorAutofocusFor(cell.column.id, this.rowIndexOf(row))
|
|
4327
4350
|
});
|
|
4328
4351
|
} catch {
|
|
4329
4352
|
return "{}";
|
|
@@ -4796,7 +4819,9 @@ let EditorText = class EditorText extends (0, _lit_labs_preact_signals.SignalWat
|
|
|
4796
4819
|
this.value = null;
|
|
4797
4820
|
this.commit = null;
|
|
4798
4821
|
this.cancel = null;
|
|
4822
|
+
this.autofocus = false;
|
|
4799
4823
|
this._draft = (0, _lit_labs_preact_signals.signal)("");
|
|
4824
|
+
this.__rozieFirstUpdateDone = false;
|
|
4800
4825
|
this._disconnectCleanups = [];
|
|
4801
4826
|
this._rozieTornDown = false;
|
|
4802
4827
|
this.onInput = (e) => {
|
|
@@ -4828,6 +4853,13 @@ let EditorText = class EditorText extends (0, _lit_labs_preact_signals.SignalWat
|
|
|
4828
4853
|
}
|
|
4829
4854
|
firstUpdated() {
|
|
4830
4855
|
this._draft.value = this.value != null ? String(this.value) : "";
|
|
4856
|
+
if (this.autofocus) this._refInputEl?.focus();
|
|
4857
|
+
}
|
|
4858
|
+
updated(changedProperties) {
|
|
4859
|
+
if (this.__rozieFirstUpdateDone && changedProperties.has("autofocus")) ((v) => {
|
|
4860
|
+
if (v) this._refInputEl?.focus();
|
|
4861
|
+
})(this.autofocus);
|
|
4862
|
+
this.__rozieFirstUpdateDone = true;
|
|
4831
4863
|
}
|
|
4832
4864
|
disconnectedCallback() {
|
|
4833
4865
|
super.disconnectedCallback();
|
|
@@ -4846,7 +4878,7 @@ let EditorText = class EditorText extends (0, _lit_labs_preact_signals.SignalWat
|
|
|
4846
4878
|
this.onKeydown($event);
|
|
4847
4879
|
}} @blur=${($event) => {
|
|
4848
4880
|
this.onBlur();
|
|
4849
|
-
}} data-rozie-s-0d17f43a />
|
|
4881
|
+
}} data-rozie-ref="inputEl" data-rozie-s-0d17f43a />
|
|
4850
4882
|
`;
|
|
4851
4883
|
}
|
|
4852
4884
|
};
|
|
@@ -4859,6 +4891,11 @@ __decorate([(0, lit_decorators_js.property)({ type: Object })], EditorText.proto
|
|
|
4859
4891
|
__decorate([(0, lit_decorators_js.property)({ type: Object })], EditorText.prototype, "value", void 0);
|
|
4860
4892
|
__decorate([(0, lit_decorators_js.property)({ type: Function })], EditorText.prototype, "commit", void 0);
|
|
4861
4893
|
__decorate([(0, lit_decorators_js.property)({ type: Function })], EditorText.prototype, "cancel", void 0);
|
|
4894
|
+
__decorate([(0, lit_decorators_js.property)({
|
|
4895
|
+
type: Boolean,
|
|
4896
|
+
reflect: true
|
|
4897
|
+
})], EditorText.prototype, "autofocus", void 0);
|
|
4898
|
+
__decorate([(0, lit_decorators_js.query)("[data-rozie-ref=\"inputEl\"]")], EditorText.prototype, "_refInputEl", void 0);
|
|
4862
4899
|
EditorText = __decorate([(0, lit_decorators_js.customElement)("rozie-editor-text")], EditorText);
|
|
4863
4900
|
var EditorText_default = EditorText;
|
|
4864
4901
|
//#endregion
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import * as _$lit from "lit";
|
|
2
2
|
import { LitElement } from "lit";
|
|
3
|
-
|
|
4
3
|
//#region src/DataTable.d.ts
|
|
5
4
|
declare const DataTable_base: typeof LitElement;
|
|
6
5
|
declare class DataTable extends DataTable_base {
|
|
@@ -159,6 +158,7 @@ declare class DataTable extends DataTable_base {
|
|
|
159
158
|
private _draftValue;
|
|
160
159
|
private _invalidMsg;
|
|
161
160
|
private _editVer;
|
|
161
|
+
private _editFocusColId;
|
|
162
162
|
private _editingRowIndex;
|
|
163
163
|
private _rowDraft;
|
|
164
164
|
private _rangeAnchor;
|
|
@@ -226,6 +226,7 @@ declare class DataTable extends DataTable_base {
|
|
|
226
226
|
value: unknown;
|
|
227
227
|
commit: unknown;
|
|
228
228
|
cancel: unknown;
|
|
229
|
+
autofocus: unknown;
|
|
229
230
|
}) => unknown;
|
|
230
231
|
private _hasSlotDetail;
|
|
231
232
|
private _slotDetailElements;
|
|
@@ -581,6 +582,7 @@ declare class DataTable extends DataTable_base {
|
|
|
581
582
|
indexOfRowIn: (rows: any, rowOriginal: any, rowId: any) => number;
|
|
582
583
|
endEdit: () => void;
|
|
583
584
|
endRowEdit: () => void;
|
|
585
|
+
editorAutofocusFor: (colId: any, rowIndex: any) => boolean;
|
|
584
586
|
coerceCellValue: (colId: any, raw: any) => any;
|
|
585
587
|
commitEdit: (overrideValue?: any, skipFocusReturn?: boolean) => boolean;
|
|
586
588
|
toggleActiveBooleanCell: () => void;
|
|
@@ -778,10 +780,17 @@ declare class EditorText extends EditorText_base {
|
|
|
778
780
|
* `() => void` — revert the edit and close the editor (from the `#editor` slot scope). Null-guarded at call sites.
|
|
779
781
|
*/
|
|
780
782
|
cancel: ((...args: unknown[]) => unknown) | null;
|
|
783
|
+
/**
|
|
784
|
+
* Focus this editor's primary input when true — the host sets it for the one editor that should hold focus; reactive.
|
|
785
|
+
*/
|
|
786
|
+
autofocus: boolean;
|
|
781
787
|
private _draft;
|
|
788
|
+
private _refInputEl;
|
|
789
|
+
private __rozieFirstUpdateDone;
|
|
782
790
|
private _disconnectCleanups;
|
|
783
791
|
private _rozieTornDown;
|
|
784
792
|
firstUpdated(): void;
|
|
793
|
+
updated(changedProperties: Map<string, unknown>): void;
|
|
785
794
|
disconnectedCallback(): void;
|
|
786
795
|
render(): _$lit.TemplateResult<1>;
|
|
787
796
|
onInput: (e: any) => void;
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import * as _$lit from "lit";
|
|
2
2
|
import { LitElement } from "lit";
|
|
3
|
-
|
|
4
3
|
//#region src/DataTable.d.ts
|
|
5
4
|
declare const DataTable_base: typeof LitElement;
|
|
6
5
|
declare class DataTable extends DataTable_base {
|
|
@@ -159,6 +158,7 @@ declare class DataTable extends DataTable_base {
|
|
|
159
158
|
private _draftValue;
|
|
160
159
|
private _invalidMsg;
|
|
161
160
|
private _editVer;
|
|
161
|
+
private _editFocusColId;
|
|
162
162
|
private _editingRowIndex;
|
|
163
163
|
private _rowDraft;
|
|
164
164
|
private _rangeAnchor;
|
|
@@ -226,6 +226,7 @@ declare class DataTable extends DataTable_base {
|
|
|
226
226
|
value: unknown;
|
|
227
227
|
commit: unknown;
|
|
228
228
|
cancel: unknown;
|
|
229
|
+
autofocus: unknown;
|
|
229
230
|
}) => unknown;
|
|
230
231
|
private _hasSlotDetail;
|
|
231
232
|
private _slotDetailElements;
|
|
@@ -581,6 +582,7 @@ declare class DataTable extends DataTable_base {
|
|
|
581
582
|
indexOfRowIn: (rows: any, rowOriginal: any, rowId: any) => number;
|
|
582
583
|
endEdit: () => void;
|
|
583
584
|
endRowEdit: () => void;
|
|
585
|
+
editorAutofocusFor: (colId: any, rowIndex: any) => boolean;
|
|
584
586
|
coerceCellValue: (colId: any, raw: any) => any;
|
|
585
587
|
commitEdit: (overrideValue?: any, skipFocusReturn?: boolean) => boolean;
|
|
586
588
|
toggleActiveBooleanCell: () => void;
|
|
@@ -778,10 +780,17 @@ declare class EditorText extends EditorText_base {
|
|
|
778
780
|
* `() => void` — revert the edit and close the editor (from the `#editor` slot scope). Null-guarded at call sites.
|
|
779
781
|
*/
|
|
780
782
|
cancel: ((...args: unknown[]) => unknown) | null;
|
|
783
|
+
/**
|
|
784
|
+
* Focus this editor's primary input when true — the host sets it for the one editor that should hold focus; reactive.
|
|
785
|
+
*/
|
|
786
|
+
autofocus: boolean;
|
|
781
787
|
private _draft;
|
|
788
|
+
private _refInputEl;
|
|
789
|
+
private __rozieFirstUpdateDone;
|
|
782
790
|
private _disconnectCleanups;
|
|
783
791
|
private _rozieTornDown;
|
|
784
792
|
firstUpdated(): void;
|
|
793
|
+
updated(changedProperties: Map<string, unknown>): void;
|
|
785
794
|
disconnectedCallback(): void;
|
|
786
795
|
render(): _$lit.TemplateResult<1>;
|
|
787
796
|
onInput: (e: any) => void;
|
package/dist/index.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { SignalWatcher, effect, signal, untracked } from "@lit-labs/preact-signa
|
|
|
4
4
|
import { createLitControllableProperty, rozieAttr, rozieDisplay, rozieStyle } from "@rozie/runtime-lit";
|
|
5
5
|
import { ContextConsumer, ContextProvider, createContext } from "@lit/context";
|
|
6
6
|
import { repeat } from "lit/directives/repeat.js";
|
|
7
|
+
import "@rozie-ui/popover-lit";
|
|
7
8
|
import { createTable, getCoreRowModel, getExpandedRowModel, getFacetedMinMaxValues, getFacetedRowModel, getFacetedUniqueValues, getFilteredRowModel, getGroupedRowModel, getPaginationRowModel, getSortedRowModel } from "@tanstack/table-core";
|
|
8
9
|
import { Virtualizer, elementScroll, measureElement, observeElementOffset, observeElementRect } from "@tanstack/virtual-core";
|
|
9
10
|
//#region \0@oxc-project+runtime@0.127.0/helpers/decorate.js
|
|
@@ -171,6 +172,7 @@ let DataTable = class DataTable extends SignalWatcher(LitElement) {
|
|
|
171
172
|
this._draftValue = signal(null);
|
|
172
173
|
this._invalidMsg = signal("");
|
|
173
174
|
this._editVer = signal(0);
|
|
175
|
+
this._editFocusColId = signal(null);
|
|
174
176
|
this._editingRowIndex = signal(null);
|
|
175
177
|
this._rowDraft = signal({});
|
|
176
178
|
this._rangeAnchor = signal(null);
|
|
@@ -1050,8 +1052,7 @@ let DataTable = class DataTable extends SignalWatcher(LitElement) {
|
|
|
1050
1052
|
const base = this.pinStyle(colId);
|
|
1051
1053
|
if (this.isExpanderColumn(colId) && row && row.depth) {
|
|
1052
1054
|
const pad = "padding-left:" + (.5 + row.depth * 1.25) + "rem";
|
|
1053
|
-
|
|
1054
|
-
return base ? base + sep + pad : pad;
|
|
1055
|
+
return base ? base + pad : pad;
|
|
1055
1056
|
}
|
|
1056
1057
|
return base;
|
|
1057
1058
|
};
|
|
@@ -2472,6 +2473,7 @@ let DataTable = class DataTable extends SignalWatcher(LitElement) {
|
|
|
2472
2473
|
};
|
|
2473
2474
|
this.focusEditorWhenReady = (selectAll = true) => {
|
|
2474
2475
|
if (!this.gridRoot) return;
|
|
2476
|
+
if (this._editFocusColId.value != null && this.hasEditorSlot(this._editFocusColId.value)) return;
|
|
2475
2477
|
let attempts = 0;
|
|
2476
2478
|
const tryFocus = () => {
|
|
2477
2479
|
const el = this.gridRoot ? this.gridRoot.querySelector("[data-editing-cell]") : null;
|
|
@@ -2522,6 +2524,7 @@ let DataTable = class DataTable extends SignalWatcher(LitElement) {
|
|
|
2522
2524
|
this._draftValue.value = seed != null ? seed : this.cellValueAt(rowIndex, colIndex);
|
|
2523
2525
|
this._activeInControl.value = true;
|
|
2524
2526
|
this._editVer.value = this._editVer.value + 1;
|
|
2527
|
+
this._editFocusColId.value = colId;
|
|
2525
2528
|
this.focusEditorWhenReady(seed == null);
|
|
2526
2529
|
};
|
|
2527
2530
|
this.focusCellWhenReady = (row, col) => {
|
|
@@ -2558,6 +2561,7 @@ let DataTable = class DataTable extends SignalWatcher(LitElement) {
|
|
|
2558
2561
|
this._invalidMsg.value = "";
|
|
2559
2562
|
this._activeInControl.value = false;
|
|
2560
2563
|
this._editVer.value = this._editVer.value + 1;
|
|
2564
|
+
this._editFocusColId.value = null;
|
|
2561
2565
|
};
|
|
2562
2566
|
this.endRowEdit = () => {
|
|
2563
2567
|
this._editingRowIndex.value = null;
|
|
@@ -2565,6 +2569,14 @@ let DataTable = class DataTable extends SignalWatcher(LitElement) {
|
|
|
2565
2569
|
this._invalidMsg.value = "";
|
|
2566
2570
|
this._activeInControl.value = false;
|
|
2567
2571
|
this._editVer.value = this._editVer.value + 1;
|
|
2572
|
+
this._editFocusColId.value = null;
|
|
2573
|
+
};
|
|
2574
|
+
this.editorAutofocusFor = (colId, rowIndex) => {
|
|
2575
|
+
if (this._editVer.value < 0) return false;
|
|
2576
|
+
if (this._editingRowIndex.value != null) {
|
|
2577
|
+
if (this._editingRowIndex.value !== rowIndex) return false;
|
|
2578
|
+
} else if (this._editingRow.value !== rowIndex) return false;
|
|
2579
|
+
return this._editFocusColId.value != null && this._editFocusColId.value === colId;
|
|
2568
2580
|
};
|
|
2569
2581
|
this.coerceCellValue = (colId, raw) => {
|
|
2570
2582
|
if (this.editorTypeOf(colId) !== "number") return raw;
|
|
@@ -2693,6 +2705,8 @@ let DataTable = class DataTable extends SignalWatcher(LitElement) {
|
|
|
2693
2705
|
};
|
|
2694
2706
|
this.focusRowEditorAt = (rowIndex, colIndex) => {
|
|
2695
2707
|
if (!this.gridRoot) return;
|
|
2708
|
+
const colId = this.columnIdAt(rowIndex, colIndex);
|
|
2709
|
+
if (colId != null && this.hasEditorSlot(colId)) return;
|
|
2696
2710
|
let attempts = 0;
|
|
2697
2711
|
const tryFocus = () => {
|
|
2698
2712
|
const cellEl = this.resolveCellEl(String(rowIndex), colIndex);
|
|
@@ -2733,6 +2747,7 @@ let DataTable = class DataTable extends SignalWatcher(LitElement) {
|
|
|
2733
2747
|
this._editingRowIndex.value = rowIndex;
|
|
2734
2748
|
this._activeInControl.value = true;
|
|
2735
2749
|
this._editVer.value = this._editVer.value + 1;
|
|
2750
|
+
this._editFocusColId.value = editable[0].colId;
|
|
2736
2751
|
this.focusEditorWhenReady();
|
|
2737
2752
|
};
|
|
2738
2753
|
this.commitRow = () => {
|
|
@@ -2752,6 +2767,8 @@ let DataTable = class DataTable extends SignalWatcher(LitElement) {
|
|
|
2752
2767
|
const err = this.runValidator(ec.colId, this.coerceCellValue(ec.colId, draft[ec.colId]), rowOriginal);
|
|
2753
2768
|
if (err !== true) {
|
|
2754
2769
|
this.setInvalid(err);
|
|
2770
|
+
this._editFocusColId.value = ec.colId;
|
|
2771
|
+
this._editVer.value = this._editVer.value + 1;
|
|
2755
2772
|
this.focusRowEditorAt(rowIndex, ec.colIndex);
|
|
2756
2773
|
return false;
|
|
2757
2774
|
}
|
|
@@ -2921,6 +2938,8 @@ let DataTable = class DataTable extends SignalWatcher(LitElement) {
|
|
|
2921
2938
|
if (pos < 0) pos = 0;
|
|
2922
2939
|
const len = cols.length;
|
|
2923
2940
|
const nextPos = backward ? (pos - 1 + len) % len : (pos + 1) % len;
|
|
2941
|
+
this._editFocusColId.value = editable[nextPos].colId;
|
|
2942
|
+
this._editVer.value = this._editVer.value + 1;
|
|
2924
2943
|
this.focusRowEditorAt(rowIndex, cols[nextPos]);
|
|
2925
2944
|
};
|
|
2926
2945
|
this.onEditorKeyDown = (e) => {
|
|
@@ -4019,7 +4038,8 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
|
|
|
4019
4038
|
row: wr.row.original,
|
|
4020
4039
|
value: this.editorValueFor(cell.column.id),
|
|
4021
4040
|
commit: this.editorCommitFor(cell.column.id),
|
|
4022
|
-
cancel: this.editorCancelFor()
|
|
4041
|
+
cancel: this.editorCancelFor(),
|
|
4042
|
+
autofocus: this.editorAutofocusFor(cell.column.id, wr.vi.index)
|
|
4023
4043
|
}) : html`<slot name="editor" data-rozie-params=${(() => {
|
|
4024
4044
|
try {
|
|
4025
4045
|
return JSON.stringify({
|
|
@@ -4028,7 +4048,8 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
|
|
|
4028
4048
|
row: wr.row.original,
|
|
4029
4049
|
value: this.editorValueFor(cell.column.id),
|
|
4030
4050
|
commit: this.editorCommitFor(cell.column.id),
|
|
4031
|
-
cancel: this.editorCancelFor()
|
|
4051
|
+
cancel: this.editorCancelFor(),
|
|
4052
|
+
autofocus: this.editorAutofocusFor(cell.column.id, wr.vi.index)
|
|
4032
4053
|
});
|
|
4033
4054
|
} catch {
|
|
4034
4055
|
return "{}";
|
|
@@ -4310,7 +4331,8 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
|
|
|
4310
4331
|
row: row.original,
|
|
4311
4332
|
value: this.editorValueFor(cell.column.id),
|
|
4312
4333
|
commit: this.editorCommitFor(cell.column.id),
|
|
4313
|
-
cancel: this.editorCancelFor()
|
|
4334
|
+
cancel: this.editorCancelFor(),
|
|
4335
|
+
autofocus: this.editorAutofocusFor(cell.column.id, this.rowIndexOf(row))
|
|
4314
4336
|
}) : html`<slot name="editor" data-rozie-params=${(() => {
|
|
4315
4337
|
try {
|
|
4316
4338
|
return JSON.stringify({
|
|
@@ -4319,7 +4341,8 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
|
|
|
4319
4341
|
row: row.original,
|
|
4320
4342
|
value: this.editorValueFor(cell.column.id),
|
|
4321
4343
|
commit: this.editorCommitFor(cell.column.id),
|
|
4322
|
-
cancel: this.editorCancelFor()
|
|
4344
|
+
cancel: this.editorCancelFor(),
|
|
4345
|
+
autofocus: this.editorAutofocusFor(cell.column.id, this.rowIndexOf(row))
|
|
4323
4346
|
});
|
|
4324
4347
|
} catch {
|
|
4325
4348
|
return "{}";
|
|
@@ -4792,7 +4815,9 @@ let EditorText = class EditorText extends SignalWatcher(LitElement) {
|
|
|
4792
4815
|
this.value = null;
|
|
4793
4816
|
this.commit = null;
|
|
4794
4817
|
this.cancel = null;
|
|
4818
|
+
this.autofocus = false;
|
|
4795
4819
|
this._draft = signal("");
|
|
4820
|
+
this.__rozieFirstUpdateDone = false;
|
|
4796
4821
|
this._disconnectCleanups = [];
|
|
4797
4822
|
this._rozieTornDown = false;
|
|
4798
4823
|
this.onInput = (e) => {
|
|
@@ -4824,6 +4849,13 @@ let EditorText = class EditorText extends SignalWatcher(LitElement) {
|
|
|
4824
4849
|
}
|
|
4825
4850
|
firstUpdated() {
|
|
4826
4851
|
this._draft.value = this.value != null ? String(this.value) : "";
|
|
4852
|
+
if (this.autofocus) this._refInputEl?.focus();
|
|
4853
|
+
}
|
|
4854
|
+
updated(changedProperties) {
|
|
4855
|
+
if (this.__rozieFirstUpdateDone && changedProperties.has("autofocus")) ((v) => {
|
|
4856
|
+
if (v) this._refInputEl?.focus();
|
|
4857
|
+
})(this.autofocus);
|
|
4858
|
+
this.__rozieFirstUpdateDone = true;
|
|
4827
4859
|
}
|
|
4828
4860
|
disconnectedCallback() {
|
|
4829
4861
|
super.disconnectedCallback();
|
|
@@ -4842,7 +4874,7 @@ let EditorText = class EditorText extends SignalWatcher(LitElement) {
|
|
|
4842
4874
|
this.onKeydown($event);
|
|
4843
4875
|
}} @blur=${($event) => {
|
|
4844
4876
|
this.onBlur();
|
|
4845
|
-
}} data-rozie-s-0d17f43a />
|
|
4877
|
+
}} data-rozie-ref="inputEl" data-rozie-s-0d17f43a />
|
|
4846
4878
|
`;
|
|
4847
4879
|
}
|
|
4848
4880
|
};
|
|
@@ -4855,6 +4887,11 @@ __decorate([property({ type: Object })], EditorText.prototype, "row", void 0);
|
|
|
4855
4887
|
__decorate([property({ type: Object })], EditorText.prototype, "value", void 0);
|
|
4856
4888
|
__decorate([property({ type: Function })], EditorText.prototype, "commit", void 0);
|
|
4857
4889
|
__decorate([property({ type: Function })], EditorText.prototype, "cancel", void 0);
|
|
4890
|
+
__decorate([property({
|
|
4891
|
+
type: Boolean,
|
|
4892
|
+
reflect: true
|
|
4893
|
+
})], EditorText.prototype, "autofocus", void 0);
|
|
4894
|
+
__decorate([query("[data-rozie-ref=\"inputEl\"]")], EditorText.prototype, "_refInputEl", void 0);
|
|
4858
4895
|
EditorText = __decorate([customElement("rozie-editor-text")], EditorText);
|
|
4859
4896
|
var EditorText_default = EditorText;
|
|
4860
4897
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rozie-ui/data-table-lit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"src"
|
|
46
46
|
],
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@rozie/runtime-lit": "0.1.
|
|
48
|
+
"@rozie/runtime-lit": "0.1.4"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
51
|
"lit": "^3.2",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"@preact/signals-core": "^1.3.0",
|
|
55
55
|
"@tanstack/table-core": "^8.21",
|
|
56
56
|
"@tanstack/virtual-core": "^3",
|
|
57
|
-
"@
|
|
57
|
+
"@rozie-ui/popover-lit": "^0.1.0"
|
|
58
58
|
},
|
|
59
59
|
"peerDependenciesMeta": {
|
|
60
60
|
"lit": {
|
|
@@ -75,19 +75,17 @@
|
|
|
75
75
|
"@tanstack/virtual-core": {
|
|
76
76
|
"optional": false
|
|
77
77
|
},
|
|
78
|
-
"@
|
|
78
|
+
"@rozie-ui/popover-lit": {
|
|
79
79
|
"optional": false
|
|
80
80
|
}
|
|
81
81
|
},
|
|
82
|
-
"devDependencies": {
|
|
83
|
-
"@floating-ui/dom": "^1.7.2"
|
|
84
|
-
},
|
|
82
|
+
"devDependencies": {},
|
|
85
83
|
"publishConfig": {
|
|
86
84
|
"access": "public"
|
|
87
85
|
},
|
|
88
86
|
"sideEffects": [
|
|
89
|
-
"
|
|
90
|
-
"
|
|
87
|
+
"./dist/index.mjs",
|
|
88
|
+
"./dist/index.cjs"
|
|
91
89
|
],
|
|
92
90
|
"scripts": {
|
|
93
91
|
"build": "tsdown",
|
package/src/DataTable.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { SignalWatcher, effect, signal, untracked } from '@lit-labs/preact-signa
|
|
|
4
4
|
import { createLitControllableProperty, rozieAttr, rozieDisplay, rozieStyle } from '@rozie/runtime-lit';
|
|
5
5
|
import { ContextProvider, createContext } from '@lit/context';
|
|
6
6
|
import { repeat } from 'lit/directives/repeat.js';
|
|
7
|
-
import '
|
|
7
|
+
import '@rozie-ui/popover-lit';
|
|
8
8
|
import { createTable, getCoreRowModel, getSortedRowModel, getFilteredRowModel, getPaginationRowModel, getExpandedRowModel, getGroupedRowModel,
|
|
9
9
|
// Faceted filtering (phase 50 reqs 8-9, D-03). All three are supplied UNCONDITIONALLY
|
|
10
10
|
// (mirrors the expand/group models) — inert until a consumer READS a column facet via the
|
|
@@ -87,6 +87,7 @@ interface RozieEditorSlotCtx {
|
|
|
87
87
|
value: unknown;
|
|
88
88
|
commit: unknown;
|
|
89
89
|
cancel: unknown;
|
|
90
|
+
autofocus: unknown;
|
|
90
91
|
}
|
|
91
92
|
|
|
92
93
|
interface RozieDetailSlotCtx {
|
|
@@ -605,6 +606,7 @@ export default class DataTable extends SignalWatcher(LitElement) {
|
|
|
605
606
|
private _draftValue = signal<any>(null);
|
|
606
607
|
private _invalidMsg = signal('');
|
|
607
608
|
private _editVer = signal(0);
|
|
609
|
+
private _editFocusColId = signal<any>(null);
|
|
608
610
|
private _editingRowIndex = signal<any>(null);
|
|
609
611
|
private _rowDraft = signal<any>({});
|
|
610
612
|
private _rangeAnchor = signal<any>(null);
|
|
@@ -656,7 +658,7 @@ private __rozieCtxProvider_data_table_columns = new ContextProvider(this, { cont
|
|
|
656
658
|
@property({ attribute: false }) cell?: (scope: { columnId: unknown; column: unknown; row: unknown; value: unknown }) => unknown;
|
|
657
659
|
@state() private _hasSlotEditor = false;
|
|
658
660
|
@queryAssignedElements({ slot: 'editor', flatten: true }) private _slotEditorElements!: Element[];
|
|
659
|
-
@property({ attribute: false }) editor?: (scope: { columnId: unknown; column: unknown; row: unknown; value: unknown; commit: unknown; cancel: unknown }) => unknown;
|
|
661
|
+
@property({ attribute: false }) editor?: (scope: { columnId: unknown; column: unknown; row: unknown; value: unknown; commit: unknown; cancel: unknown; autofocus: unknown }) => unknown;
|
|
660
662
|
@state() private _hasSlotDetail = false;
|
|
661
663
|
@queryAssignedElements({ slot: 'detail', flatten: true }) private _slotDetailElements!: Element[];
|
|
662
664
|
@property({ attribute: false }) detail?: (scope: { row: unknown }) => unknown;
|
|
@@ -1229,7 +1231,7 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
|
|
|
1229
1231
|
<span class="rdt-group-count" data-rozie-s-d5dcab4c>${rozieDisplay('(' + this.groupSubRowCount(wr.row) + ')')}</span>
|
|
1230
1232
|
</span>` : this.isEditing(wr.vi.index, this.colIndexOf(wr.row, cell)) ? html`<span style="display:contents" data-rozie-s-d5dcab4c>
|
|
1231
1233
|
${this.hasEditorSlot(cell.column.id) ? html`<span style="display:contents" data-rozie-s-d5dcab4c>
|
|
1232
|
-
${this.editor !== undefined ? this.editor({columnId: cell.column.id, column: cell.column, row: wr.row.original, value: this.editorValueFor(cell.column.id), commit: this.editorCommitFor(cell.column.id), cancel: this.editorCancelFor()}) : html`<slot name="editor" data-rozie-params=${(() => { try { return JSON.stringify({columnId: cell.column.id, column: cell.column, row: wr.row.original, value: this.editorValueFor(cell.column.id), commit: this.editorCommitFor(cell.column.id), cancel: this.editorCancelFor()}); } catch { return '{}'; } })()}></slot>`}
|
|
1234
|
+
${this.editor !== undefined ? this.editor({columnId: cell.column.id, column: cell.column, row: wr.row.original, value: this.editorValueFor(cell.column.id), commit: this.editorCommitFor(cell.column.id), cancel: this.editorCancelFor(), autofocus: this.editorAutofocusFor(cell.column.id, wr.vi.index)}) : html`<slot name="editor" data-rozie-params=${(() => { try { return JSON.stringify({columnId: cell.column.id, column: cell.column, row: wr.row.original, value: this.editorValueFor(cell.column.id), commit: this.editorCommitFor(cell.column.id), cancel: this.editorCancelFor(), autofocus: this.editorAutofocusFor(cell.column.id, wr.vi.index)}); } catch { return '{}'; } })()}></slot>`}
|
|
1233
1235
|
</span>` : this.editorTypeOf(cell.column.id) === 'number' ? html`<input class="rdt-cell-editor" type="number" data-editing-cell="" .value=${this.editorValueFor(cell.column.id)} @input=${($event: InputEvent & { currentTarget: HTMLInputElement; target: HTMLInputElement }) => { this.onCellEditorInput(cell.column.id, $event); }} @keydown=${($event: KeyboardEvent & { currentTarget: HTMLInputElement; target: HTMLInputElement }) => { this.onEditorKeyDown($event); }} @blur=${($event: FocusEvent & { currentTarget: HTMLInputElement; target: HTMLInputElement }) => { this.onEditorBlur($event); }} data-rozie-s-d5dcab4c />` : this.editorTypeOf(cell.column.id) === 'select' ? html`<select class="rdt-cell-editor" data-editing-cell="" .value=${this.editorValueFor(cell.column.id)} @change=${($event: Event & { currentTarget: HTMLSelectElement; target: HTMLSelectElement }) => { this.onCellEditorInput(cell.column.id, $event); }} @keydown=${($event: KeyboardEvent & { currentTarget: HTMLSelectElement; target: HTMLSelectElement }) => { this.onEditorKeyDown($event); }} @blur=${($event: FocusEvent & { currentTarget: HTMLSelectElement; target: HTMLSelectElement }) => { this.onEditorBlur($event); }} data-rozie-s-d5dcab4c>
|
|
1234
1236
|
${repeat<any>(this.editorOptionsOf(cell.column.id), (opt, _idx) => opt.value, (opt, _idx) => html`<option key=${rozieAttr(opt.value)} value=${rozieAttr(opt.value)} data-rozie-s-d5dcab4c>${rozieDisplay(opt.label)}</option>`)}
|
|
1235
1237
|
</select>` : this.editorTypeOf(cell.column.id) === 'checkbox' ? html`<input class="rdt-cell-editor" type="checkbox" data-editing-cell="" ?checked=${this.editorCheckedFor(cell.column.id)} @change=${($event: Event & { currentTarget: HTMLInputElement; target: HTMLInputElement }) => { this.onCellEditorCheckbox(cell.column.id, $event); }} @keydown=${($event: KeyboardEvent & { currentTarget: HTMLInputElement; target: HTMLInputElement }) => { this.onEditorKeyDown($event); }} @blur=${($event: FocusEvent & { currentTarget: HTMLInputElement; target: HTMLInputElement }) => { this.onEditorBlur($event); }} data-rozie-s-d5dcab4c />` : html`<input class="rdt-cell-editor" type="text" data-editing-cell="" .value=${this.editorValueFor(cell.column.id)} @input=${($event: InputEvent & { currentTarget: HTMLInputElement; target: HTMLInputElement }) => { this.onCellEditorInput(cell.column.id, $event); }} @keydown=${($event: KeyboardEvent & { currentTarget: HTMLInputElement; target: HTMLInputElement }) => { this.onEditorKeyDown($event); }} @blur=${($event: FocusEvent & { currentTarget: HTMLInputElement; target: HTMLInputElement }) => { this.onEditorBlur($event); }} data-rozie-s-d5dcab4c />`}</span>` : this.cellIsPlaceholder(cell) ? html`<span style="display:contents" data-rozie-s-d5dcab4c></span>` : html`<span class="rdt-cell-value" data-rozie-s-d5dcab4c>
|
|
@@ -1309,7 +1311,7 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
|
|
|
1309
1311
|
<span class="rdt-group-count" data-rozie-s-d5dcab4c>${rozieDisplay('(' + this.groupSubRowCount(row) + ')')}</span>
|
|
1310
1312
|
</span>` : this.isEditing(this.rowIndexOf(row), this.colIndexOf(row, cell)) ? html`<span style="display:contents" data-rozie-s-d5dcab4c>
|
|
1311
1313
|
${this.hasEditorSlot(cell.column.id) ? html`<span style="display:contents" data-rozie-s-d5dcab4c>
|
|
1312
|
-
${this.editor !== undefined ? this.editor({columnId: cell.column.id, column: cell.column, row: row.original, value: this.editorValueFor(cell.column.id), commit: this.editorCommitFor(cell.column.id), cancel: this.editorCancelFor()}) : html`<slot name="editor" data-rozie-params=${(() => { try { return JSON.stringify({columnId: cell.column.id, column: cell.column, row: row.original, value: this.editorValueFor(cell.column.id), commit: this.editorCommitFor(cell.column.id), cancel: this.editorCancelFor()}); } catch { return '{}'; } })()}></slot>`}
|
|
1314
|
+
${this.editor !== undefined ? this.editor({columnId: cell.column.id, column: cell.column, row: row.original, value: this.editorValueFor(cell.column.id), commit: this.editorCommitFor(cell.column.id), cancel: this.editorCancelFor(), autofocus: this.editorAutofocusFor(cell.column.id, this.rowIndexOf(row))}) : html`<slot name="editor" data-rozie-params=${(() => { try { return JSON.stringify({columnId: cell.column.id, column: cell.column, row: row.original, value: this.editorValueFor(cell.column.id), commit: this.editorCommitFor(cell.column.id), cancel: this.editorCancelFor(), autofocus: this.editorAutofocusFor(cell.column.id, this.rowIndexOf(row))}); } catch { return '{}'; } })()}></slot>`}
|
|
1313
1315
|
</span>` : this.editorTypeOf(cell.column.id) === 'number' ? html`<input class="rdt-cell-editor" type="number" data-editing-cell="" .value=${this.editorValueFor(cell.column.id)} @input=${($event: InputEvent & { currentTarget: HTMLInputElement; target: HTMLInputElement }) => { this.onCellEditorInput(cell.column.id, $event); }} @keydown=${($event: KeyboardEvent & { currentTarget: HTMLInputElement; target: HTMLInputElement }) => { this.onEditorKeyDown($event); }} @blur=${($event: FocusEvent & { currentTarget: HTMLInputElement; target: HTMLInputElement }) => { this.onEditorBlur($event); }} data-rozie-s-d5dcab4c />` : this.editorTypeOf(cell.column.id) === 'select' ? html`<select class="rdt-cell-editor" data-editing-cell="" .value=${this.editorValueFor(cell.column.id)} @change=${($event: Event & { currentTarget: HTMLSelectElement; target: HTMLSelectElement }) => { this.onCellEditorInput(cell.column.id, $event); }} @keydown=${($event: KeyboardEvent & { currentTarget: HTMLSelectElement; target: HTMLSelectElement }) => { this.onEditorKeyDown($event); }} @blur=${($event: FocusEvent & { currentTarget: HTMLSelectElement; target: HTMLSelectElement }) => { this.onEditorBlur($event); }} data-rozie-s-d5dcab4c>
|
|
1314
1316
|
${repeat<any>(this.editorOptionsOf(cell.column.id), (opt, _idx) => opt.value, (opt, _idx) => html`<option key=${rozieAttr(opt.value)} value=${rozieAttr(opt.value)} data-rozie-s-d5dcab4c>${rozieDisplay(opt.label)}</option>`)}
|
|
1315
1317
|
</select>` : this.editorTypeOf(cell.column.id) === 'checkbox' ? html`<input class="rdt-cell-editor" type="checkbox" data-editing-cell="" ?checked=${this.editorCheckedFor(cell.column.id)} @change=${($event: Event & { currentTarget: HTMLInputElement; target: HTMLInputElement }) => { this.onCellEditorCheckbox(cell.column.id, $event); }} @keydown=${($event: KeyboardEvent & { currentTarget: HTMLInputElement; target: HTMLInputElement }) => { this.onEditorKeyDown($event); }} @blur=${($event: FocusEvent & { currentTarget: HTMLInputElement; target: HTMLInputElement }) => { this.onEditorBlur($event); }} data-rozie-s-d5dcab4c />` : html`<input class="rdt-cell-editor" type="text" data-editing-cell="" .value=${this.editorValueFor(cell.column.id)} @input=${($event: InputEvent & { currentTarget: HTMLInputElement; target: HTMLInputElement }) => { this.onCellEditorInput(cell.column.id, $event); }} @keydown=${($event: KeyboardEvent & { currentTarget: HTMLInputElement; target: HTMLInputElement }) => { this.onEditorKeyDown($event); }} @blur=${($event: FocusEvent & { currentTarget: HTMLInputElement; target: HTMLInputElement }) => { this.onEditorBlur($event); }} data-rozie-s-d5dcab4c />`}</span>` : this.cellIsPlaceholder(cell) ? html`<span style="display:contents" data-rozie-s-d5dcab4c></span>` : html`<span class="rdt-cell-value" data-rozie-s-d5dcab4c>
|
|
@@ -2513,15 +2515,9 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
|
|
|
2513
2515
|
bodyCellStyle = (row: any, colId: any) => {
|
|
2514
2516
|
const base = this.pinStyle(colId);
|
|
2515
2517
|
if (this.isExpanderColumn(colId) && row && row.depth) {
|
|
2518
|
+
// Only the expander column indents (the tree affordance lives here).
|
|
2516
2519
|
const pad = 'padding-left:' + (0.5 + row.depth * 1.25) + 'rem';
|
|
2517
|
-
|
|
2518
|
-
// second ';' produces a `;;` empty declaration. Browsers tolerate that in a
|
|
2519
|
-
// raw style attribute (React/Vue/Svelte/Lit) and Solid's cssText path does
|
|
2520
|
-
// too, but Angular's [style] binding parses the string via ɵɵstyleMap and
|
|
2521
|
-
// DROPS the declaration following the empty one — so the depth padding-left
|
|
2522
|
-
// silently vanished on Angular only. Emit a single, well-formed separator.
|
|
2523
|
-
const sep = base.endsWith(';') ? '' : ';';
|
|
2524
|
-
return base ? base + sep + pad : pad;
|
|
2520
|
+
return base ? base + pad : pad;
|
|
2525
2521
|
}
|
|
2526
2522
|
return base;
|
|
2527
2523
|
};
|
|
@@ -4507,6 +4503,9 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
|
|
|
4507
4503
|
|
|
4508
4504
|
focusEditorWhenReady = (selectAll = true) => {
|
|
4509
4505
|
if (!this.gridRoot) return;
|
|
4506
|
+
// Editor-owns-focus contract: when the CURRENT focus target is a #editor drop-in, the host
|
|
4507
|
+
// does NOT reach into its DOM — the drop-in self-focuses via its own autofocus prop.
|
|
4508
|
+
if (this._editFocusColId.value != null && this.hasEditorSlot(this._editFocusColId.value)) return;
|
|
4510
4509
|
let attempts = 0;
|
|
4511
4510
|
const tryFocus = () => {
|
|
4512
4511
|
const el = this.gridRoot ? this.gridRoot.querySelector('[data-editing-cell]') : null;
|
|
@@ -4575,6 +4574,10 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
|
|
|
4575
4574
|
this._draftValue.value = seed != null ? seed : this.cellValueAt(rowIndex, colIndex);
|
|
4576
4575
|
this._activeInControl.value = true;
|
|
4577
4576
|
this._editVer.value = this._editVer.value + 1;
|
|
4577
|
+
// Editor-owns-focus contract (quick 260711-i5m): THIS cell's column is the current
|
|
4578
|
+
// focus target — editorAutofocusFor derives the reactive `autofocus` #editor scope prop
|
|
4579
|
+
// from it. Cleared on endEdit.
|
|
4580
|
+
this._editFocusColId.value = colId;
|
|
4578
4581
|
// B2: a seeded (type-to-edit) entry must NOT select-all — keep the caret after the
|
|
4579
4582
|
// seeded char so subsequent typing appends instead of replacing it.
|
|
4580
4583
|
this.focusEditorWhenReady(seed == null);
|
|
@@ -4614,6 +4617,7 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
|
|
|
4614
4617
|
this._invalidMsg.value = '';
|
|
4615
4618
|
this._activeInControl.value = false;
|
|
4616
4619
|
this._editVer.value = this._editVer.value + 1;
|
|
4620
|
+
this._editFocusColId.value = null;
|
|
4617
4621
|
};
|
|
4618
4622
|
|
|
4619
4623
|
endRowEdit = () => {
|
|
@@ -4622,6 +4626,17 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
|
|
|
4622
4626
|
this._invalidMsg.value = '';
|
|
4623
4627
|
this._activeInControl.value = false;
|
|
4624
4628
|
this._editVer.value = this._editVer.value + 1;
|
|
4629
|
+
this._editFocusColId.value = null;
|
|
4630
|
+
};
|
|
4631
|
+
|
|
4632
|
+
editorAutofocusFor = (colId: any, rowIndex: any) => {
|
|
4633
|
+
if (this._editVer.value < 0) return false;
|
|
4634
|
+
if (this._editingRowIndex.value != null) {
|
|
4635
|
+
if (this._editingRowIndex.value !== rowIndex) return false;
|
|
4636
|
+
} else {
|
|
4637
|
+
if (this._editingRow.value !== rowIndex) return false;
|
|
4638
|
+
}
|
|
4639
|
+
return this._editFocusColId.value != null && this._editFocusColId.value === colId;
|
|
4625
4640
|
};
|
|
4626
4641
|
|
|
4627
4642
|
coerceCellValue = (colId: any, raw: any) => {
|
|
@@ -4811,6 +4826,8 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
|
|
|
4811
4826
|
|
|
4812
4827
|
focusRowEditorAt = (rowIndex: any, colIndex: any) => {
|
|
4813
4828
|
if (!this.gridRoot) return;
|
|
4829
|
+
const colId = this.columnIdAt(rowIndex, colIndex);
|
|
4830
|
+
if (colId != null && this.hasEditorSlot(colId)) return;
|
|
4814
4831
|
let attempts = 0;
|
|
4815
4832
|
const tryFocus = () => {
|
|
4816
4833
|
const cellEl = this.resolveCellEl(String(rowIndex), colIndex);
|
|
@@ -4856,6 +4873,11 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
|
|
|
4856
4873
|
this._editingRowIndex.value = rowIndex;
|
|
4857
4874
|
this._activeInControl.value = true;
|
|
4858
4875
|
this._editVer.value = this._editVer.value + 1;
|
|
4876
|
+
// Editor-owns-focus contract (quick 260711-i5m): the row's FIRST editable column is the
|
|
4877
|
+
// initial focus target — editorAutofocusFor derives the reactive `autofocus` #editor scope
|
|
4878
|
+
// prop from it (a built-in column is also host-focused below via focusEditorWhenReady; a
|
|
4879
|
+
// drop-in column self-focuses via its own $onMount, gated off the host reach-in in Task 3).
|
|
4880
|
+
this._editFocusColId.value = editable[0].colId;
|
|
4859
4881
|
this.focusEditorWhenReady();
|
|
4860
4882
|
};
|
|
4861
4883
|
|
|
@@ -4883,9 +4905,18 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
|
|
|
4883
4905
|
const err = this.runValidator(ec.colId, this.coerceCellValue(ec.colId, draft[ec.colId]), rowOriginal);
|
|
4884
4906
|
if (err !== true) {
|
|
4885
4907
|
this.setInvalid(err);
|
|
4908
|
+
// Editor-owns-focus contract (quick 260711-i5m): the OFFENDING column becomes the new
|
|
4909
|
+
// reactive focus target BEFORE the host-focus call below — a #editor drop-in already
|
|
4910
|
+
// mounted (full-row edit opens every editable cell at once) picks this up via its own
|
|
4911
|
+
// lazy $watch on the `autofocus` scope prop flipping false→true. Bump editVer so the
|
|
4912
|
+
// coarse-render targets (React/Vue/Angular/Svelte) re-derive the slot binding (Solid's
|
|
4913
|
+
// fine-grained accessor re-runs without the bump, but the bump keeps all 6 in lockstep).
|
|
4914
|
+
this._editFocusColId.value = ec.colId;
|
|
4915
|
+
this._editVer.value = this._editVer.value + 1;
|
|
4886
4916
|
// B22: focus the OFFENDING column's editor (the one whose validator rejected), NOT
|
|
4887
4917
|
// unconditionally the first editor (focusEditorWhenReady resolves the first
|
|
4888
4918
|
// [data-editing-cell] in DOM order). ec.colIndex is the offending cell's visible col.
|
|
4919
|
+
// Gated (Task 3) so a #editor drop-in self-focuses instead of a host DOM reach-in.
|
|
4889
4920
|
this.focusRowEditorAt(rowIndex, ec.colIndex);
|
|
4890
4921
|
return false;
|
|
4891
4922
|
}
|
|
@@ -5108,6 +5139,11 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
|
|
|
5108
5139
|
if (pos < 0) pos = 0;
|
|
5109
5140
|
const len = cols.length;
|
|
5110
5141
|
const nextPos = backward ? (pos - 1 + len) % len : (pos + 1) % len;
|
|
5142
|
+
// Editor-owns-focus contract (quick 260711-i5m): the Tab target becomes the new reactive
|
|
5143
|
+
// focus target BEFORE the host-focus call below, so Tab onto an already-mounted #editor
|
|
5144
|
+
// drop-in (row mode) also refocuses it via its own lazy $watch.
|
|
5145
|
+
this._editFocusColId.value = editable[nextPos].colId;
|
|
5146
|
+
this._editVer.value = this._editVer.value + 1;
|
|
5111
5147
|
this.focusRowEditorAt(rowIndex, cols[nextPos]);
|
|
5112
5148
|
};
|
|
5113
5149
|
|
package/src/EditorText.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { LitElement, css, html } from 'lit';
|
|
2
|
-
import { customElement, property } from 'lit/decorators.js';
|
|
2
|
+
import { customElement, property, query } from 'lit/decorators.js';
|
|
3
3
|
import { SignalWatcher, signal } from '@lit-labs/preact-signals';
|
|
4
4
|
|
|
5
5
|
@customElement('rozie-editor-text')
|
|
@@ -32,7 +32,13 @@ export default class EditorText extends SignalWatcher(LitElement) {
|
|
|
32
32
|
* `() => void` — revert the edit and close the editor (from the `#editor` slot scope). Null-guarded at call sites.
|
|
33
33
|
*/
|
|
34
34
|
@property({ type: Function }) cancel: ((...args: unknown[]) => unknown) | null = null;
|
|
35
|
+
/**
|
|
36
|
+
* Focus this editor's primary input when true — the host sets it for the one editor that should hold focus; reactive.
|
|
37
|
+
*/
|
|
38
|
+
@property({ type: Boolean, reflect: true }) autofocus: boolean = false;
|
|
35
39
|
private _draft = signal('');
|
|
40
|
+
@query('[data-rozie-ref="inputEl"]') private _refInputEl!: HTMLElement;
|
|
41
|
+
private __rozieFirstUpdateDone = false;
|
|
36
42
|
|
|
37
43
|
private _disconnectCleanups: Array<() => void> = [];
|
|
38
44
|
// Re-parenting guard: set true once the deferred teardown has actually
|
|
@@ -46,6 +52,15 @@ export default class EditorText extends SignalWatcher(LitElement) {
|
|
|
46
52
|
|
|
47
53
|
// Untyped handler param neutralizes to `any`, so reading e.target.value typechecks
|
|
48
54
|
// ×6 (the global-filter idiom). Never inline `$data.x = $event.target.value`.
|
|
55
|
+
|
|
56
|
+
if (this.autofocus) this._refInputEl?.focus();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
updated(changedProperties: Map<string, unknown>): void {
|
|
60
|
+
if (this.__rozieFirstUpdateDone && (changedProperties.has('autofocus'))) { const __watchVal = (() => this.autofocus)(); ((v: any) => {
|
|
61
|
+
if (v) this._refInputEl?.focus();
|
|
62
|
+
})(__watchVal); }
|
|
63
|
+
this.__rozieFirstUpdateDone = true;
|
|
49
64
|
}
|
|
50
65
|
|
|
51
66
|
disconnectedCallback(): void {
|
|
@@ -60,7 +75,7 @@ export default class EditorText extends SignalWatcher(LitElement) {
|
|
|
60
75
|
|
|
61
76
|
render() {
|
|
62
77
|
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 />
|
|
78
|
+
<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-ref="inputEl" data-rozie-s-0d17f43a />
|
|
64
79
|
`;
|
|
65
80
|
}
|
|
66
81
|
|
package/src/Popover.ts
DELETED
|
@@ -1,446 +0,0 @@
|
|
|
1
|
-
import { LitElement, css, html, nothing } from 'lit';
|
|
2
|
-
import { customElement, property, query, queryAssignedElements, state } from 'lit/decorators.js';
|
|
3
|
-
import { SignalWatcher, effect, untracked } from '@lit-labs/preact-signals';
|
|
4
|
-
import { attachOutsideClickListener, createLitControllableProperty, rozieAttr, rozieListeners, rozieSpread } from '@rozie/runtime-lit';
|
|
5
|
-
// The `offset` AND `arrow` middleware factories are ALIASED on import: both are
|
|
6
|
-
// ALSO author PROP names (`offset`, `arrow`). A bare `offset`/`arrow` shorthand in
|
|
7
|
-
// the buildMiddleware factories object resolves to the PROP — on Vue/Svelte the
|
|
8
|
-
// destructured prop local shadows the import, and on Angular the emitter rewrites
|
|
9
|
-
// the bare shorthand to the prop signal (`offset: this.offset()`, a number) instead
|
|
10
|
-
// of the middleware function (TS2322). Aliasing both severs the import↔prop clash.
|
|
11
|
-
// (The Cropper import-name==component-name class, applied to imports vs PROP names —
|
|
12
|
-
// two collisions, not one.) computePosition/autoUpdate/flip/shift carry no clash.
|
|
13
|
-
import { computePosition, autoUpdate, offset as offsetMiddleware, flip, shift, arrow as arrowMiddleware } from '@floating-ui/dom';
|
|
14
|
-
import { buildMiddleware } from './internal/middleware';
|
|
15
|
-
|
|
16
|
-
// null-lets so the bundled-leaf typeNeutralize pass annotates them `any`:
|
|
17
|
-
// anchorNode/floatingNode/arrowNode hold the resolved ref ELEMENTS (read ONLY in
|
|
18
|
-
// $onMount/handlers, ROZ123). They are deliberately named DIFFERENTLY from the
|
|
19
|
-
// `ref="anchorEl"` / `ref="floatingEl"` / `ref="arrowEl"` template ref names: the
|
|
20
|
-
// React/Svelte emitters declare a `const anchorEl = useRef(...)` for the ref, and a
|
|
21
|
-
// top-level `let anchorEl` hoisted to its own `useRef` would REDECLARE it (TS2451 —
|
|
22
|
-
// the local-name==ref-name self-shadow class, here in its `let X = null; X = $refs.X`
|
|
23
|
-
// variant, which deconflictRefShadows does NOT auto-rewrite since it only fires on the
|
|
24
|
-
// `const X = $refs.X` init shape).
|
|
25
|
-
// stopAutoUpdate is the autoUpdate teardown handle — a TOP-LEVEL `let` so the Solid
|
|
26
|
-
// onMount→onCleanup split (teardown is a separate closure) can still see it.
|
|
27
|
-
// lastFocusedEl (phase 72-06b) holds whatever had DOM focus at the moment a
|
|
28
|
-
// `trigger="click"` popover opened (natively the clicked trigger element itself,
|
|
29
|
-
// since a mousedown focuses a native `<button>` before its `click` fires) —
|
|
30
|
-
// restored on dismissal so Escape/click-outside don't drop focus to `<body>`.
|
|
31
|
-
// Same null-let convention as the others: read/written only in handlers, `any`
|
|
32
|
-
// via typeNeutralize.
|
|
33
|
-
|
|
34
|
-
interface RozieAnchorSlotCtx {
|
|
35
|
-
open: unknown;
|
|
36
|
-
toggle: unknown;
|
|
37
|
-
show: unknown;
|
|
38
|
-
hide: unknown;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
@customElement('rozie-popover')
|
|
42
|
-
export default class Popover extends SignalWatcher(LitElement) {
|
|
43
|
-
static styles = css`
|
|
44
|
-
:host{display:contents}
|
|
45
|
-
.rozie-popover[data-rozie-s-c6cf02ea] {
|
|
46
|
-
display: contents;
|
|
47
|
-
}
|
|
48
|
-
.rozie-popover-anchor[data-rozie-s-c6cf02ea] {
|
|
49
|
-
display: inline-block;
|
|
50
|
-
}
|
|
51
|
-
.rozie-popover-floating[data-rozie-s-c6cf02ea] {
|
|
52
|
-
position: absolute;
|
|
53
|
-
left: 0;
|
|
54
|
-
top: 0;
|
|
55
|
-
z-index: var(--rozie-popover-z, 1000);
|
|
56
|
-
width: max-content;
|
|
57
|
-
max-width: var(--rozie-popover-max-width, calc(100vw - 16px));
|
|
58
|
-
background: var(--rozie-popover-bg, #fff);
|
|
59
|
-
color: var(--rozie-popover-color, inherit);
|
|
60
|
-
border: var(--rozie-popover-border, 1px solid rgba(0, 0, 0, 0.12));
|
|
61
|
-
border-radius: var(--rozie-popover-radius, 8px);
|
|
62
|
-
box-shadow: var(--rozie-popover-shadow, 0 8px 24px rgba(0, 0, 0, 0.12));
|
|
63
|
-
padding: var(--rozie-popover-padding, 8px 12px);
|
|
64
|
-
}
|
|
65
|
-
.rozie-popover-arrow[data-rozie-s-c6cf02ea] {
|
|
66
|
-
position: absolute;
|
|
67
|
-
width: var(--rozie-popover-arrow-size, 8px);
|
|
68
|
-
height: var(--rozie-popover-arrow-size, 8px);
|
|
69
|
-
background: var(--rozie-popover-bg, #fff);
|
|
70
|
-
border: var(--rozie-popover-border, 1px solid rgba(0, 0, 0, 0.12));
|
|
71
|
-
transform: rotate(45deg);
|
|
72
|
-
}
|
|
73
|
-
`;
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Whether the floating content is open. The sole `model: true` prop — two-way bind it (`r-model:open` / `v-model:open` / `bind:open` / `[(open)]`) and Popover writes the new state back whenever the trigger or a dismissal toggles it. Left unbound it falls back to an uncontrolled default.
|
|
77
|
-
*/
|
|
78
|
-
@property({ type: Boolean, attribute: 'open' }) _open_attr: boolean = false;
|
|
79
|
-
private _openControllable = createLitControllableProperty<boolean>({ host: this, eventName: 'open-change', defaultValue: false, initialControlledValue: undefined });
|
|
80
|
-
/**
|
|
81
|
-
* Floating UI placement of the content relative to the anchor — one of `top`/`right`/`bottom`/`left`, each optionally suffixed `-start`/`-end` (e.g. `bottom-start`). With `disableFlip` off, the content may flip to the opposite side when it would overflow the viewport. Reconciled at runtime.
|
|
82
|
-
*/
|
|
83
|
-
@property({ type: String, reflect: true }) placement: string = 'bottom';
|
|
84
|
-
/**
|
|
85
|
-
* How the anchor opens the content: `'click'` toggles on click, `'hover'` opens on pointer-enter and closes on pointer-leave (tooltip-style), `'focus'` opens on focus and closes on blur. Drives both the gesture handlers and the ARIA role (`'hover'`/`'focus'` → tooltip, `'click'` → popover dialog).
|
|
86
|
-
*/
|
|
87
|
-
@property({ type: String, reflect: true }) trigger: string = 'click';
|
|
88
|
-
/**
|
|
89
|
-
* Distance in pixels between the anchor and the floating content (the Floating UI `offset` middleware). Reconciled at runtime.
|
|
90
|
-
*/
|
|
91
|
-
@property({ type: Number, reflect: true }) offset: number = 8;
|
|
92
|
-
/**
|
|
93
|
-
* Disable the Floating UI `flip` middleware. By default the content flips to the opposite side of the anchor when it would overflow the viewport; set this to keep it pinned to `placement` regardless.
|
|
94
|
-
*/
|
|
95
|
-
@property({ type: Boolean, reflect: true }) disableFlip: boolean = false;
|
|
96
|
-
/**
|
|
97
|
-
* Disable the Floating UI `shift` middleware. By default the content shifts along its axis to stay within the viewport; set this to keep it strictly aligned to the anchor.
|
|
98
|
-
*/
|
|
99
|
-
@property({ type: Boolean, reflect: true }) disableShift: boolean = false;
|
|
100
|
-
/**
|
|
101
|
-
* Opt in to a positioned arrow element. When set, Popover renders an arrow `<div>` and runs the Floating UI `arrow` middleware against it so it points at the anchor. Style it via the `--rozie-popover-*` arrow CSS custom properties.
|
|
102
|
-
*/
|
|
103
|
-
@property({ type: Boolean, reflect: true }) arrow: boolean = false;
|
|
104
|
-
/**
|
|
105
|
-
* Disable the control entirely: the trigger no longer opens the content and any open content is suppressed.
|
|
106
|
-
*/
|
|
107
|
-
@property({ type: Boolean, reflect: true }) disabled: boolean = false;
|
|
108
|
-
/**
|
|
109
|
-
* Opt in to modal dialog semantics for a `click` popover. **Off by default:** a click popover is a non-modal, click-outside-dismissable layer, so its panel is rendered role-neutral (the slot content owns its own ARIA role — e.g. a `role="menu"`) and carries NO `aria-modal`. Set `modal` for a genuinely modal dialog popover: the panel then gets `role="dialog"` + `aria-modal="true"`. **Note:** Popover ships no focus trap (it stays a minimal headless primitive); if you set `modal`, provide your own focus containment so the `aria-modal` claim holds. Ignored for `hover`/`focus` triggers (always tooltip-flavored).
|
|
110
|
-
*/
|
|
111
|
-
@property({ type: Boolean, reflect: true }) modal: boolean = false;
|
|
112
|
-
/**
|
|
113
|
-
* Floating UI positioning strategy — 'absolute' (default) or 'fixed'. Use 'fixed' to escape a scrollable/overflow-clipping ancestor (e.g. a sticky table header). Reconciled at runtime.
|
|
114
|
-
*/
|
|
115
|
-
@property({ type: String, reflect: true }) strategy: string = 'absolute';
|
|
116
|
-
@query('[data-rozie-ref="anchorEl"]') private _refAnchorEl!: HTMLElement;
|
|
117
|
-
@query('[data-rozie-ref="floatingEl"]') private _refFloatingEl!: HTMLElement;
|
|
118
|
-
@query('[data-rozie-ref="arrowEl"]') private _refArrowEl!: HTMLElement;
|
|
119
|
-
private __rozieWatchInitial_0 = true;
|
|
120
|
-
private __rozieFirstUpdateDone = false;
|
|
121
|
-
|
|
122
|
-
@state() private _hasSlotAnchor = false;
|
|
123
|
-
@queryAssignedElements({ slot: 'anchor', flatten: true }) private _slotAnchorElements!: Element[];
|
|
124
|
-
@property({ attribute: false }) anchor?: (scope: { open: unknown; toggle: unknown; show: unknown; hide: unknown }) => unknown;
|
|
125
|
-
@state() private _hasSlotDefault = false;
|
|
126
|
-
@queryAssignedElements({ flatten: true }) private _slotDefaultElements!: Element[];
|
|
127
|
-
|
|
128
|
-
private _disconnectCleanups: Array<() => void> = [];
|
|
129
|
-
// Re-parenting guard: set true once the deferred teardown has actually
|
|
130
|
-
// run (a genuine un-mount), so a subsequent reconnect knows to re-arm.
|
|
131
|
-
private _rozieTornDown = false;
|
|
132
|
-
|
|
133
|
-
private _armListeners(): void {
|
|
134
|
-
const _lh0 = ($event: KeyboardEvent) => { if (!(this.open)) return; if ($event.key !== 'Escape') return; ((this.dismiss) as (...args: any[]) => any)($event); };
|
|
135
|
-
document.addEventListener('keydown', _lh0, undefined);
|
|
136
|
-
this._disconnectCleanups.push(() => document.removeEventListener('keydown', _lh0, undefined));
|
|
137
|
-
|
|
138
|
-
const _u1 = attachOutsideClickListener([() => this._refAnchorEl, () => this._refFloatingEl], ($event) => { ((this.dismiss) as (...args: any[]) => any)($event); }, () => (this.open));
|
|
139
|
-
this._disconnectCleanups.push(_u1);
|
|
140
|
-
|
|
141
|
-
{
|
|
142
|
-
const slotEl = this.shadowRoot?.querySelector('slot[name="anchor"]');
|
|
143
|
-
if (slotEl !== null && slotEl !== undefined) {
|
|
144
|
-
const update = () => { this._hasSlotAnchor = this._slotAnchorElements.length > 0; };
|
|
145
|
-
slotEl.addEventListener('slotchange', update);
|
|
146
|
-
// CR-05 fix: push cleanup so the listener is removed on disconnectedCallback.
|
|
147
|
-
this._disconnectCleanups.push(() => slotEl.removeEventListener('slotchange', update));
|
|
148
|
-
update();
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
{
|
|
153
|
-
const slotEl = this.shadowRoot?.querySelector('slot:not([name])');
|
|
154
|
-
if (slotEl !== null && slotEl !== undefined) {
|
|
155
|
-
const update = () => { this._hasSlotDefault = this._slotDefaultElements.length > 0; };
|
|
156
|
-
slotEl.addEventListener('slotchange', update);
|
|
157
|
-
// CR-05 fix: push cleanup so the listener is removed on disconnectedCallback.
|
|
158
|
-
this._disconnectCleanups.push(() => slotEl.removeEventListener('slotchange', update));
|
|
159
|
-
update();
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
connectedCallback(): void {
|
|
165
|
-
// Phase 07.3.1 D-LIT-15 — pre-seed _hasSlot<X> from light DOM so first render isn't deadlocked.
|
|
166
|
-
this._hasSlotAnchor = Array.from(this.children).some((el) => el.getAttribute('slot') === 'anchor');
|
|
167
|
-
this._hasSlotDefault = Array.from(this.children).some((el) => !el.hasAttribute('slot') && (el.nodeType !== 3 || (el.textContent?.trim().length ?? 0) > 0));
|
|
168
|
-
super.connectedCallback();
|
|
169
|
-
if (this.hasUpdated && this._rozieTornDown) { this._rozieTornDown = false; this._armListeners(); }
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
firstUpdated(): void {
|
|
173
|
-
this._armListeners();
|
|
174
|
-
|
|
175
|
-
this._disconnectCleanups.push((() => {
|
|
176
|
-
this.stopTracking();
|
|
177
|
-
}));
|
|
178
|
-
|
|
179
|
-
this._disconnectCleanups.push(effect(() => { const __watchVal = (() => this.open)(); untracked(() => { if (this.__rozieWatchInitial_0) { this.__rozieWatchInitial_0 = false; return; } ((isOpen: any) => {
|
|
180
|
-
if (isOpen && !this.disabled) {
|
|
181
|
-
queueMicrotask(() => {
|
|
182
|
-
if (!this.open || this.disabled) return;
|
|
183
|
-
this.floatingNode = this._refFloatingEl;
|
|
184
|
-
this.arrowNode = this._refArrowEl;
|
|
185
|
-
this.startTracking();
|
|
186
|
-
});
|
|
187
|
-
} else {
|
|
188
|
-
this.stopTracking();
|
|
189
|
-
}
|
|
190
|
-
})(__watchVal); }); }));
|
|
191
|
-
|
|
192
|
-
// $refs read ONLY here (ROZ123). The floating + arrow elements live behind r-if
|
|
193
|
-
// and may be null until open; startTracking re-reads via the watch path.
|
|
194
|
-
this.anchorNode = this._refAnchorEl;
|
|
195
|
-
if (this.open && !this.disabled) {
|
|
196
|
-
// floatingNode is populated by its r-if having rendered; read it lazily inside
|
|
197
|
-
// the watch/handlers too. Position on next tick when it exists.
|
|
198
|
-
this.floatingNode = this._refFloatingEl;
|
|
199
|
-
this.arrowNode = this._refArrowEl;
|
|
200
|
-
this.startTracking();
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
updated(changedProperties: Map<string, unknown>): void {
|
|
205
|
-
if (this.__rozieFirstUpdateDone && (changedProperties.has('placement'))) { const __watchVal = (() => this.placement)(); (() => {
|
|
206
|
-
if (this.open) this.position();
|
|
207
|
-
})(); }
|
|
208
|
-
if (this.__rozieFirstUpdateDone && (changedProperties.has('offset'))) { const __watchVal = (() => this.offset)(); (() => {
|
|
209
|
-
if (this.open) this.position();
|
|
210
|
-
})(); }
|
|
211
|
-
if (this.__rozieFirstUpdateDone && (changedProperties.has('disableFlip'))) { const __watchVal = (() => this.disableFlip)(); (() => {
|
|
212
|
-
if (this.open) this.position();
|
|
213
|
-
})(); }
|
|
214
|
-
if (this.__rozieFirstUpdateDone && (changedProperties.has('disableShift'))) { const __watchVal = (() => this.disableShift)(); (() => {
|
|
215
|
-
if (this.open) this.position();
|
|
216
|
-
})(); }
|
|
217
|
-
if (this.__rozieFirstUpdateDone && (changedProperties.has('strategy'))) { const __watchVal = (() => this.strategy)(); (() => {
|
|
218
|
-
if (this.open) this.position();
|
|
219
|
-
})(); }
|
|
220
|
-
this.__rozieFirstUpdateDone = true;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
disconnectedCallback(): void {
|
|
224
|
-
super.disconnectedCallback();
|
|
225
|
-
queueMicrotask(() => {
|
|
226
|
-
if (this.isConnected || this._rozieTornDown) return;
|
|
227
|
-
this._rozieTornDown = true;
|
|
228
|
-
for (const fn of this._disconnectCleanups) fn();
|
|
229
|
-
this._disconnectCleanups = [];
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
attributeChangedCallback(name: string, old: string | null, value: string | null): void {
|
|
234
|
-
super.attributeChangedCallback(name, old, value);
|
|
235
|
-
if (name === 'open') this._openControllable.notifyAttributeChange(value !== null);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
render() {
|
|
239
|
-
return html`
|
|
240
|
-
<div class="rozie-popover" ${rozieSpread(this.$attrs)} ${rozieListeners(this.$listeners)} data-rozie-s-c6cf02ea>
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
<div class="rozie-popover-anchor" aria-haspopup="dialog" aria-expanded=${!!this.open} aria-describedby=${rozieAttr(this.isTooltip() && this.open ? 'rozie-popover-floating' : null)} @click=${($event: MouseEvent & { currentTarget: HTMLDivElement; target: HTMLDivElement }) => { this.trigger === 'click' && this.onAnchorClick(); }} @pointerenter=${($event: Event & { currentTarget: HTMLDivElement; target: HTMLDivElement }) => { this.trigger === 'hover' && this.onAnchorPointerEnter(); }} @pointerleave=${($event: Event & { currentTarget: HTMLDivElement; target: HTMLDivElement }) => { this.trigger === 'hover' && this.onAnchorPointerLeave(); }} @focusin=${($event: Event & { currentTarget: HTMLDivElement; target: HTMLDivElement }) => { this.trigger === 'focus' && this.onAnchorFocus(); }} @focusout=${($event: Event & { currentTarget: HTMLDivElement; target: HTMLDivElement }) => { this.trigger === 'focus' && this.onAnchorBlur(); }} data-rozie-ref="anchorEl" data-rozie-s-c6cf02ea>
|
|
244
|
-
${this.anchor !== undefined ? this.anchor({open: this.open, toggle: this.toggle, show: this.show, hide: this.hide}) : html`<slot name="anchor" data-rozie-params=${(() => { try { return JSON.stringify({open: this.open}); } catch { return '{}'; } })()} @rozie-anchor-toggle=${($event: CustomEvent) => ((this.toggle) as (...args: any[]) => any)($event.detail)} @rozie-anchor-show=${($event: CustomEvent) => ((this.show) as (...args: any[]) => any)($event.detail)} @rozie-anchor-hide=${($event: CustomEvent) => ((this.hide) as (...args: any[]) => any)($event.detail)}></slot>`}
|
|
245
|
-
</div>
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
${this.open && !this.disabled ? html`<div class="rozie-popover-floating" id="rozie-popover-floating" role=${rozieAttr(this.floatingRole())} aria-modal=${!!(this.floatingRole() === 'dialog')} data-rozie-ref="floatingEl" data-rozie-s-c6cf02ea>
|
|
249
|
-
${this.arrow ? html`<div class="rozie-popover-arrow" data-rozie-ref="arrowEl" data-rozie-s-c6cf02ea></div>` : nothing}<slot></slot>
|
|
250
|
-
</div>` : nothing}</div>
|
|
251
|
-
`;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
anchorNode: any = null;
|
|
255
|
-
|
|
256
|
-
floatingNode: any = null;
|
|
257
|
-
|
|
258
|
-
arrowNode: any = null;
|
|
259
|
-
|
|
260
|
-
stopAutoUpdate: any = null;
|
|
261
|
-
|
|
262
|
-
lastFocusedEl: any = null;
|
|
263
|
-
|
|
264
|
-
deepActiveElement = () => {
|
|
265
|
-
let el = document.activeElement;
|
|
266
|
-
while (el && el.shadowRoot && el.shadowRoot.activeElement) {
|
|
267
|
-
el = el.shadowRoot.activeElement;
|
|
268
|
-
}
|
|
269
|
-
return el;
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
requestOpen = (next: any) => {
|
|
273
|
-
if (this.open === next) return;
|
|
274
|
-
if (next && this.trigger === 'click') {
|
|
275
|
-
this.lastFocusedEl = this.deepActiveElement();
|
|
276
|
-
}
|
|
277
|
-
this._openControllable.write(next);
|
|
278
|
-
this.dispatchEvent(new CustomEvent("change", {
|
|
279
|
-
detail: next,
|
|
280
|
-
bubbles: true,
|
|
281
|
-
composed: true
|
|
282
|
-
}));
|
|
283
|
-
if (!next && this.trigger === 'click' && this.lastFocusedEl && this.lastFocusedEl.isConnected && typeof this.lastFocusedEl.focus === 'function') {
|
|
284
|
-
this.lastFocusedEl.focus();
|
|
285
|
-
}
|
|
286
|
-
if (!next) {
|
|
287
|
-
this.lastFocusedEl = null;
|
|
288
|
-
}
|
|
289
|
-
};
|
|
290
|
-
|
|
291
|
-
applyPosition = (x: any, y: any, middlewareData: any) => {
|
|
292
|
-
if (!this.floatingNode) return;
|
|
293
|
-
this.floatingNode.style.left = x + 'px';
|
|
294
|
-
this.floatingNode.style.top = y + 'px';
|
|
295
|
-
if (this.arrowNode && middlewareData && middlewareData.arrow) {
|
|
296
|
-
const ax = middlewareData.arrow.x;
|
|
297
|
-
const ay = middlewareData.arrow.y;
|
|
298
|
-
this.arrowNode.style.left = ax == null ? '' : ax + 'px';
|
|
299
|
-
this.arrowNode.style.top = ay == null ? '' : ay + 'px';
|
|
300
|
-
}
|
|
301
|
-
};
|
|
302
|
-
|
|
303
|
-
position = () => {
|
|
304
|
-
if (!this.anchorNode || !this.floatingNode) return;
|
|
305
|
-
const middleware = buildMiddleware({
|
|
306
|
-
offset: offsetMiddleware,
|
|
307
|
-
flip,
|
|
308
|
-
shift,
|
|
309
|
-
arrow: arrowMiddleware
|
|
310
|
-
}, {
|
|
311
|
-
offset: this.offset,
|
|
312
|
-
disableFlip: this.disableFlip,
|
|
313
|
-
disableShift: this.disableShift,
|
|
314
|
-
arrow: this.arrow,
|
|
315
|
-
arrowEl: this.arrowNode
|
|
316
|
-
});
|
|
317
|
-
// 'fixed' inline position MUST be written before computePosition measures the
|
|
318
|
-
// floating element's offset parent (fixed vs absolute changes the containing
|
|
319
|
-
// block). Default 'absolute' explicitly CLEARS any inline position instead of
|
|
320
|
-
// writing `position: absolute` — so a never-fixed popover still writes no
|
|
321
|
-
// visible inline position (byte-identical-off preserved: `style.position = ''`
|
|
322
|
-
// is a no-op when the property was never set), while a live `strategy`
|
|
323
|
-
// reconcile (fixed → absolute, see the $watch below) correctly resets the
|
|
324
|
-
// stale inline `fixed` so the stylesheet's `position: absolute` rule re-takes
|
|
325
|
-
// over instead of positioning `fixed` with absolute-computed coordinates
|
|
326
|
-
// (72-REVIEW.md WR-01).
|
|
327
|
-
if (this.strategy === 'fixed') {
|
|
328
|
-
this.floatingNode.style.position = 'fixed';
|
|
329
|
-
} else {
|
|
330
|
-
this.floatingNode.style.position = '';
|
|
331
|
-
}
|
|
332
|
-
let opts: any = null;
|
|
333
|
-
opts = {
|
|
334
|
-
placement: this.placement,
|
|
335
|
-
strategy: this.strategy,
|
|
336
|
-
middleware
|
|
337
|
-
};
|
|
338
|
-
computePosition(this.anchorNode, this.floatingNode, opts).then((result: any) => {
|
|
339
|
-
this.applyPosition(result.x, result.y, result.middlewareData);
|
|
340
|
-
});
|
|
341
|
-
};
|
|
342
|
-
|
|
343
|
-
startTracking = () => {
|
|
344
|
-
if (!this.anchorNode || !this.floatingNode) return;
|
|
345
|
-
if (this.stopAutoUpdate) {
|
|
346
|
-
this.stopAutoUpdate();
|
|
347
|
-
this.stopAutoUpdate = null;
|
|
348
|
-
}
|
|
349
|
-
this.stopAutoUpdate = autoUpdate(this.anchorNode, this.floatingNode, this.position);
|
|
350
|
-
};
|
|
351
|
-
|
|
352
|
-
stopTracking = () => {
|
|
353
|
-
if (this.stopAutoUpdate) {
|
|
354
|
-
this.stopAutoUpdate();
|
|
355
|
-
this.stopAutoUpdate = null;
|
|
356
|
-
}
|
|
357
|
-
};
|
|
358
|
-
|
|
359
|
-
onAnchorClick = () => {
|
|
360
|
-
if (this.disabled) return;
|
|
361
|
-
this.requestOpen(!this.open);
|
|
362
|
-
};
|
|
363
|
-
|
|
364
|
-
onAnchorPointerEnter = () => {
|
|
365
|
-
if (this.disabled) return;
|
|
366
|
-
this.requestOpen(true);
|
|
367
|
-
};
|
|
368
|
-
|
|
369
|
-
onAnchorPointerLeave = () => {
|
|
370
|
-
if (this.disabled) return;
|
|
371
|
-
this.requestOpen(false);
|
|
372
|
-
};
|
|
373
|
-
|
|
374
|
-
onAnchorFocus = () => {
|
|
375
|
-
if (this.disabled) return;
|
|
376
|
-
this.requestOpen(true);
|
|
377
|
-
};
|
|
378
|
-
|
|
379
|
-
onAnchorBlur = () => {
|
|
380
|
-
if (this.disabled) return;
|
|
381
|
-
this.requestOpen(false);
|
|
382
|
-
};
|
|
383
|
-
|
|
384
|
-
dismiss = () => {
|
|
385
|
-
this.requestOpen(false);
|
|
386
|
-
};
|
|
387
|
-
|
|
388
|
-
isTooltip = () => this.trigger === 'hover' || this.trigger === 'focus';
|
|
389
|
-
|
|
390
|
-
floatingRole = () => this.isTooltip() ? 'tooltip' : this.modal ? 'dialog' : undefined;
|
|
391
|
-
|
|
392
|
-
show() {
|
|
393
|
-
if (!this.disabled) this.requestOpen(true);
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
hide() {
|
|
397
|
-
this.requestOpen(false);
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
toggle() {
|
|
401
|
-
if (!this.disabled) this.requestOpen(!this.open);
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
reposition() {
|
|
405
|
-
this.position();
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
get open(): boolean { return this._openControllable.read(); }
|
|
409
|
-
set open(v: boolean) { this._openControllable.notifyPropertyWrite(v); }
|
|
410
|
-
|
|
411
|
-
/**
|
|
412
|
-
* Plan 14-05 — cross-framework attribute fallthrough source. Reads the
|
|
413
|
-
* host custom element's attributes on each call so a consumer-side bound
|
|
414
|
-
* attribute flows through on every render. The `rozieSpread` directive
|
|
415
|
-
* (D-02) does the cross-render diff downstream.
|
|
416
|
-
*
|
|
417
|
-
* Phase 15 follow-up Bug A — declared-prop attribute names are filtered
|
|
418
|
-
* out so `$attrs` returns "rest after declared props" (semantic parity
|
|
419
|
-
* with React/Vue/Svelte/Solid/Angular). Both Lit attribute-naming
|
|
420
|
-
* forms are folded into the skip set: kebab-case for model props
|
|
421
|
-
* (explicit `attribute:`) AND lowercased property name (Lit's default).
|
|
422
|
-
*/
|
|
423
|
-
private get $attrs(): Record<string, string> {
|
|
424
|
-
const __skip = new Set<string>(['open', 'placement', 'trigger', 'offset', 'disable-flip', 'disableflip', 'disable-shift', 'disableshift', 'arrow', 'disabled', 'modal', 'strategy']);
|
|
425
|
-
const out: Record<string, string> = {};
|
|
426
|
-
for (const a of Array.from(this.attributes)) {
|
|
427
|
-
if (__skip.has(a.name)) continue;
|
|
428
|
-
out[a.name] = a.value;
|
|
429
|
-
}
|
|
430
|
-
return out;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
/**
|
|
434
|
-
* Phase 15 D-19 — consumer-passed listener cluster placeholder.
|
|
435
|
-
* Lit attaches event listeners directly on the host element via
|
|
436
|
-
* `addEventListener` (no per-instance prop rest binding), so the
|
|
437
|
-
* runtime value is undefined; the `rozieListeners` directive's
|
|
438
|
-
* nullish coercion (`obj ?? {}`) handles the no-op cleanly.
|
|
439
|
-
* The declaration exists to satisfy `tsc --noEmit` on consumer
|
|
440
|
-
* projects with strict mode — bare `$listeners` in `render()`
|
|
441
|
-
* would otherwise raise TS2304 (Cannot find name).
|
|
442
|
-
*/
|
|
443
|
-
private get $listeners(): Record<string, EventListener> | undefined {
|
|
444
|
-
return undefined;
|
|
445
|
-
}
|
|
446
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* middleware.ts — pure construction of the Floating UI middleware stack from the
|
|
3
|
-
* Popover's first-class props.
|
|
4
|
-
*
|
|
5
|
-
* This is the one piece of BRANCHY engine glue in Popover.rozie (offset always on,
|
|
6
|
-
* flip/shift opt-out via `disableFlip`/`disableShift`, arrow opt-in via `arrow` +
|
|
7
|
-
* an element). Extracted to `src/internal/` so it can be unit-tested in isolation
|
|
8
|
-
* (codegen vendors `src/internal/` into every leaf via copyInternal, excluding
|
|
9
|
-
* `*.test.ts`).
|
|
10
|
-
*
|
|
11
|
-
* It takes the Floating UI middleware FACTORIES as arguments (rather than importing
|
|
12
|
-
* `@floating-ui/dom` itself) so the unit test can pass lightweight stand-ins and
|
|
13
|
-
* assert ORDER + presence without pulling the real engine — and so the vendored
|
|
14
|
-
* copy in each leaf has zero engine import of its own (the leaf's Popover.* owns
|
|
15
|
-
* the single engine import).
|
|
16
|
-
*
|
|
17
|
-
* Floating UI ordering contract: offset → flip → shift → arrow. `offset` first so
|
|
18
|
-
* the gap is measured before collision detection; `arrow` last so it reads the
|
|
19
|
-
* final resolved placement.
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
export interface MiddlewareFactories {
|
|
23
|
-
offset: (value: number) => unknown;
|
|
24
|
-
flip: () => unknown;
|
|
25
|
-
shift: () => unknown;
|
|
26
|
-
arrow: (opts: { element: Element }) => unknown;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface MiddlewareConfig {
|
|
30
|
-
offset: number;
|
|
31
|
-
disableFlip: boolean;
|
|
32
|
-
disableShift: boolean;
|
|
33
|
-
arrow: boolean;
|
|
34
|
-
arrowEl: Element | null;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Build the ordered middleware array for `computePosition`. Pure — no engine
|
|
39
|
-
* import, no DOM read beyond the passed-in arrow element.
|
|
40
|
-
*/
|
|
41
|
-
export function buildMiddleware(
|
|
42
|
-
factories: MiddlewareFactories,
|
|
43
|
-
config: MiddlewareConfig,
|
|
44
|
-
): unknown[] {
|
|
45
|
-
const mw: unknown[] = [factories.offset(config.offset)];
|
|
46
|
-
if (!config.disableFlip) mw.push(factories.flip());
|
|
47
|
-
if (!config.disableShift) mw.push(factories.shift());
|
|
48
|
-
// The arrow middleware needs a real element to position; opt-in only when both
|
|
49
|
-
// the `arrow` prop is set AND the arrow element has mounted.
|
|
50
|
-
if (config.arrow && config.arrowEl) {
|
|
51
|
-
mw.push(factories.arrow({ element: config.arrowEl }));
|
|
52
|
-
}
|
|
53
|
-
return mw;
|
|
54
|
-
}
|