selective-ui 1.2.1 → 1.2.2
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/dist/selective-ui.css +3 -1
- package/dist/selective-ui.css.map +1 -1
- package/dist/selective-ui.esm.js +435 -405
- package/dist/selective-ui.esm.js.map +1 -1
- package/dist/selective-ui.esm.min.js +2 -2
- package/dist/selective-ui.esm.min.js.br +0 -0
- package/dist/selective-ui.min.css +1 -1
- package/dist/selective-ui.min.css.br +0 -0
- package/dist/selective-ui.min.js +2 -2
- package/dist/selective-ui.min.js.br +0 -0
- package/dist/selective-ui.umd.js +436 -406
- package/dist/selective-ui.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/css/components/popup.css +3 -1
- package/src/ts/adapter/mixed-adapter.ts +40 -40
- package/src/ts/components/accessorybox.ts +49 -21
- package/src/ts/components/directive.ts +3 -3
- package/src/ts/components/empty-state.ts +7 -7
- package/src/ts/components/loading-state.ts +7 -7
- package/src/ts/components/option-handle.ts +17 -17
- package/src/ts/components/placeholder.ts +12 -12
- package/src/ts/components/popup.ts +93 -108
- package/src/ts/components/searchbox.ts +14 -14
- package/src/ts/components/selectbox.ts +25 -22
- package/src/ts/core/base/adapter.ts +12 -12
- package/src/ts/core/base/model.ts +12 -13
- package/src/ts/core/base/recyclerview.ts +7 -7
- package/src/ts/core/base/view.ts +6 -6
- package/src/ts/core/base/virtual-recyclerview.ts +50 -50
- package/src/ts/core/model-manager.ts +53 -53
- package/src/ts/core/search-controller.ts +69 -69
- package/src/ts/models/group-model.ts +21 -21
- package/src/ts/models/option-model.ts +30 -30
- package/src/ts/services/dataset-observer.ts +17 -17
- package/src/ts/services/ea-observer.ts +21 -21
- package/src/ts/services/effector.ts +25 -25
- package/src/ts/services/refresher.ts +1 -1
- package/src/ts/services/resize-observer.ts +29 -29
- package/src/ts/services/select-observer.ts +29 -29
- package/src/ts/types/components/popup.type.ts +15 -0
- package/src/ts/types/utils/istorage.type.ts +1 -1
- package/src/ts/utils/callback-scheduler.ts +4 -4
- package/src/ts/utils/ievents.ts +4 -4
- package/src/ts/utils/istorage.ts +5 -5
- package/src/ts/utils/libs.ts +38 -29
- package/src/ts/utils/selective.ts +11 -11
- package/src/ts/views/group-view.ts +7 -7
- package/src/ts/views/option-view.ts +51 -51
|
@@ -5,28 +5,28 @@ import type { GroupViewTags } from "../types/views/view.group.type";
|
|
|
5
5
|
import { GroupView } from "../views/group-view";
|
|
6
6
|
import { OptionModel } from "./option-model";
|
|
7
7
|
import type { IEventCallback } from "../types/utils/ievents.type";
|
|
8
|
-
import {
|
|
8
|
+
import { SelectiveOptions } from "../types/utils/selective.type";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* @extends {Model<HTMLOptGroupElement, GroupViewTags, GroupView>}
|
|
12
12
|
*/
|
|
13
|
-
export class GroupModel extends Model<HTMLOptGroupElement, GroupViewTags, GroupView,
|
|
14
|
-
label = "";
|
|
13
|
+
export class GroupModel extends Model<HTMLOptGroupElement, GroupViewTags, GroupView, SelectiveOptions> {
|
|
14
|
+
public label = "";
|
|
15
15
|
|
|
16
|
-
items: OptionModel[] = [];
|
|
16
|
+
public items: OptionModel[] = [];
|
|
17
17
|
|
|
18
|
-
collapsed = false;
|
|
18
|
+
public collapsed = false;
|
|
19
19
|
|
|
20
|
-
private
|
|
20
|
+
private privOnCollapsedChanged: Array<(evtToken: IEventCallback, model: GroupModel, collapsed: boolean) => void> = [];
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Initializes a group model with options and an optional <optgroup> target.
|
|
24
24
|
* Reads the label and collapsed state from the target element's attributes/dataset.
|
|
25
25
|
*
|
|
26
|
-
* @param {
|
|
26
|
+
* @param {SelectiveOptions} options - Configuration for the model.
|
|
27
27
|
* @param {HTMLOptGroupElement} [targetElement] - The source <optgroup> element.
|
|
28
28
|
*/
|
|
29
|
-
constructor(options:
|
|
29
|
+
public constructor(options: SelectiveOptions, targetElement?: HTMLOptGroupElement) {
|
|
30
30
|
super(options, targetElement ?? null, null);
|
|
31
31
|
|
|
32
32
|
if (targetElement) {
|
|
@@ -40,7 +40,7 @@ export class GroupModel extends Model<HTMLOptGroupElement, GroupViewTags, GroupV
|
|
|
40
40
|
*
|
|
41
41
|
* @type {string[]}
|
|
42
42
|
*/
|
|
43
|
-
get value(): string[] {
|
|
43
|
+
public get value(): string[] {
|
|
44
44
|
return this.items.map((item) => item.value);
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -49,7 +49,7 @@ export class GroupModel extends Model<HTMLOptGroupElement, GroupViewTags, GroupV
|
|
|
49
49
|
*
|
|
50
50
|
* @type {OptionModel[]}
|
|
51
51
|
*/
|
|
52
|
-
get selectedItems(): OptionModel[] {
|
|
52
|
+
public get selectedItems(): OptionModel[] {
|
|
53
53
|
return this.items.filter((item) => item.selected);
|
|
54
54
|
}
|
|
55
55
|
|
|
@@ -58,7 +58,7 @@ export class GroupModel extends Model<HTMLOptGroupElement, GroupViewTags, GroupV
|
|
|
58
58
|
*
|
|
59
59
|
* @type {OptionModel[]}
|
|
60
60
|
*/
|
|
61
|
-
get visibleItems(): OptionModel[] {
|
|
61
|
+
public get visibleItems(): OptionModel[] {
|
|
62
62
|
return this.items.filter((item) => item.visible);
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -67,7 +67,7 @@ export class GroupModel extends Model<HTMLOptGroupElement, GroupViewTags, GroupV
|
|
|
67
67
|
*
|
|
68
68
|
* @type {boolean}
|
|
69
69
|
*/
|
|
70
|
-
get hasVisibleItems(): boolean {
|
|
70
|
+
public get hasVisibleItems(): boolean {
|
|
71
71
|
return this.visibleItems.length > 0;
|
|
72
72
|
}
|
|
73
73
|
|
|
@@ -76,7 +76,7 @@ export class GroupModel extends Model<HTMLOptGroupElement, GroupViewTags, GroupV
|
|
|
76
76
|
*
|
|
77
77
|
* @param {HTMLOptGroupElement} targetElement - The updated <optgroup> element.
|
|
78
78
|
*/
|
|
79
|
-
update(targetElement: HTMLOptGroupElement): void {
|
|
79
|
+
public update(targetElement: HTMLOptGroupElement): void {
|
|
80
80
|
this.label = targetElement.label;
|
|
81
81
|
this.view?.updateLabel(this.label);
|
|
82
82
|
}
|
|
@@ -85,7 +85,7 @@ export class GroupModel extends Model<HTMLOptGroupElement, GroupViewTags, GroupV
|
|
|
85
85
|
* Hook invoked when the target element reference changes.
|
|
86
86
|
* Updates the view's label and collapsed state to keep UI in sync.
|
|
87
87
|
*/
|
|
88
|
-
onTargetChanged(): void {
|
|
88
|
+
public onTargetChanged(): void {
|
|
89
89
|
if (this.view) {
|
|
90
90
|
this.view.updateLabel(this.label);
|
|
91
91
|
this.view.setCollapsed(this.collapsed);
|
|
@@ -97,18 +97,18 @@ export class GroupModel extends Model<HTMLOptGroupElement, GroupViewTags, GroupV
|
|
|
97
97
|
*
|
|
98
98
|
* @param {(evtToken: IEventCallback, model: GroupModel, collapsed: boolean) => void} callback - Listener for collapse changes.
|
|
99
99
|
*/
|
|
100
|
-
onCollapsedChanged(callback: (evtToken: IEventCallback, model: GroupModel, collapsed: boolean) => void): void {
|
|
101
|
-
this.
|
|
100
|
+
public onCollapsedChanged(callback: (evtToken: IEventCallback, model: GroupModel, collapsed: boolean) => void): void {
|
|
101
|
+
this.privOnCollapsedChanged.push(callback);
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
/**
|
|
105
105
|
* Toggles the group's collapsed state, updates the view, and notifies registered listeners.
|
|
106
106
|
*/
|
|
107
|
-
toggleCollapse(): void {
|
|
107
|
+
public toggleCollapse(): void {
|
|
108
108
|
this.collapsed = !this.collapsed;
|
|
109
109
|
this.view?.setCollapsed(this.collapsed);
|
|
110
110
|
|
|
111
|
-
iEvents.callEvent<[GroupModel, boolean]>([this, this.collapsed], ...this.
|
|
111
|
+
iEvents.callEvent<[GroupModel, boolean]>([this, this.collapsed], ...this.privOnCollapsedChanged);
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
/**
|
|
@@ -116,7 +116,7 @@ export class GroupModel extends Model<HTMLOptGroupElement, GroupViewTags, GroupV
|
|
|
116
116
|
*
|
|
117
117
|
* @param {OptionModel} optionModel - The option to add.
|
|
118
118
|
*/
|
|
119
|
-
addItem(optionModel: OptionModel): void {
|
|
119
|
+
public addItem(optionModel: OptionModel): void {
|
|
120
120
|
this.items.push(optionModel);
|
|
121
121
|
optionModel.group = this;
|
|
122
122
|
}
|
|
@@ -126,7 +126,7 @@ export class GroupModel extends Model<HTMLOptGroupElement, GroupViewTags, GroupV
|
|
|
126
126
|
*
|
|
127
127
|
* @param {OptionModel} optionModel - The option to remove.
|
|
128
128
|
*/
|
|
129
|
-
removeItem(optionModel: OptionModel): void {
|
|
129
|
+
public removeItem(optionModel: OptionModel): void {
|
|
130
130
|
const index = this.items.indexOf(optionModel);
|
|
131
131
|
if (index > -1) {
|
|
132
132
|
this.items.splice(index, 1);
|
|
@@ -138,7 +138,7 @@ export class GroupModel extends Model<HTMLOptGroupElement, GroupViewTags, GroupV
|
|
|
138
138
|
* Updates the group's visibility in the view, typically based on children visibility.
|
|
139
139
|
* No-ops if the view is not initialized.
|
|
140
140
|
*/
|
|
141
|
-
updateVisibility(): void {
|
|
141
|
+
public updateVisibility(): void {
|
|
142
142
|
this.view?.updateVisibility();
|
|
143
143
|
}
|
|
144
144
|
}
|
|
@@ -12,17 +12,17 @@ import { SelectiveOptions } from "../types/utils/selective.type";
|
|
|
12
12
|
* @extends {Model<HTMLOptionElement, OptionViewTags, OptionView, SelectiveOptions>}
|
|
13
13
|
*/
|
|
14
14
|
export class OptionModel extends Model<HTMLOptionElement, OptionViewTags, OptionView, SelectiveOptions> {
|
|
15
|
-
private
|
|
15
|
+
private privOnSelected: Array<(evtToken: IEventCallback, el: OptionModel, selected: boolean) => void> = [];
|
|
16
16
|
|
|
17
|
-
private
|
|
17
|
+
private privOnInternalSelected: Array<(evtToken: IEventCallback, el: OptionModel, selected: boolean) => void> = [];
|
|
18
18
|
|
|
19
|
-
private
|
|
19
|
+
private privOnVisibilityChanged: Array<(evtToken: IEventCallback, model: OptionModel, visible: boolean) => void> = [];
|
|
20
20
|
|
|
21
21
|
private _visible = true;
|
|
22
22
|
|
|
23
23
|
private _highlighted = false;
|
|
24
24
|
|
|
25
|
-
group: GroupModel | null = null;
|
|
25
|
+
public group: GroupModel | null = null;
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* Constructs a Model instance with configuration options and optional bindings to a target element and view.
|
|
@@ -32,7 +32,7 @@ export class OptionModel extends Model<HTMLOptionElement, OptionViewTags, Option
|
|
|
32
32
|
* @param {HTMLOptionElement|null} [targetElement=null] - The underlying element (e.g., <option> or group node).
|
|
33
33
|
* @param {OptionView|null} [view=null] - The associated view responsible for rendering the model.
|
|
34
34
|
*/
|
|
35
|
-
constructor(options: SelectiveOptions, targetElement: HTMLOptionElement | null = null, view: OptionView | null = null) {
|
|
35
|
+
public constructor(options: SelectiveOptions, targetElement: HTMLOptionElement | null = null, view: OptionView | null = null) {
|
|
36
36
|
super(options, targetElement, view);
|
|
37
37
|
|
|
38
38
|
(async () => {
|
|
@@ -45,7 +45,7 @@ export class OptionModel extends Model<HTMLOptionElement, OptionViewTags, Option
|
|
|
45
45
|
*
|
|
46
46
|
* @type {string}
|
|
47
47
|
*/
|
|
48
|
-
get imageSrc(): string {
|
|
48
|
+
public get imageSrc(): string {
|
|
49
49
|
return this.dataset?.imgsrc || this.dataset?.image || "";
|
|
50
50
|
}
|
|
51
51
|
|
|
@@ -54,7 +54,7 @@ export class OptionModel extends Model<HTMLOptionElement, OptionViewTags, Option
|
|
|
54
54
|
*
|
|
55
55
|
* @type {boolean}
|
|
56
56
|
*/
|
|
57
|
-
get hasImage(): boolean {
|
|
57
|
+
public get hasImage(): boolean {
|
|
58
58
|
return !!this.imageSrc;
|
|
59
59
|
}
|
|
60
60
|
|
|
@@ -63,7 +63,7 @@ export class OptionModel extends Model<HTMLOptionElement, OptionViewTags, Option
|
|
|
63
63
|
*
|
|
64
64
|
* @type {string}
|
|
65
65
|
*/
|
|
66
|
-
get value(): string {
|
|
66
|
+
public get value(): string {
|
|
67
67
|
return this.targetElement?.value ?? "";
|
|
68
68
|
}
|
|
69
69
|
|
|
@@ -72,7 +72,7 @@ export class OptionModel extends Model<HTMLOptionElement, OptionViewTags, Option
|
|
|
72
72
|
*
|
|
73
73
|
* @type {boolean}
|
|
74
74
|
*/
|
|
75
|
-
get selected(): boolean {
|
|
75
|
+
public get selected(): boolean {
|
|
76
76
|
return !!this.targetElement?.selected;
|
|
77
77
|
}
|
|
78
78
|
|
|
@@ -82,9 +82,9 @@ export class OptionModel extends Model<HTMLOptionElement, OptionViewTags, Option
|
|
|
82
82
|
*
|
|
83
83
|
* @type {boolean}
|
|
84
84
|
*/
|
|
85
|
-
set selected(value: boolean) {
|
|
85
|
+
public set selected(value: boolean) {
|
|
86
86
|
this.selectedNonTrigger = value;
|
|
87
|
-
iEvents.callEvent<[OptionModel, boolean]>([this, value], ...this.
|
|
87
|
+
iEvents.callEvent<[OptionModel, boolean]>([this, value], ...this.privOnSelected);
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
/**
|
|
@@ -92,7 +92,7 @@ export class OptionModel extends Model<HTMLOptionElement, OptionViewTags, Option
|
|
|
92
92
|
*
|
|
93
93
|
* @type {boolean}
|
|
94
94
|
*/
|
|
95
|
-
get visible(): boolean {
|
|
95
|
+
public get visible(): boolean {
|
|
96
96
|
return this._visible;
|
|
97
97
|
}
|
|
98
98
|
|
|
@@ -101,14 +101,14 @@ export class OptionModel extends Model<HTMLOptionElement, OptionViewTags, Option
|
|
|
101
101
|
*
|
|
102
102
|
* @type {boolean}
|
|
103
103
|
*/
|
|
104
|
-
set visible(value: boolean) {
|
|
104
|
+
public set visible(value: boolean) {
|
|
105
105
|
if (this._visible === value) return;
|
|
106
106
|
this._visible = value;
|
|
107
107
|
|
|
108
108
|
const viewEl = this.view?.getView?.();
|
|
109
109
|
if (viewEl) viewEl.classList.toggle("hide", !value);
|
|
110
110
|
|
|
111
|
-
iEvents.callEvent<[OptionModel, boolean]>([this, value], ...this.
|
|
111
|
+
iEvents.callEvent<[OptionModel, boolean]>([this, value], ...this.privOnVisibilityChanged);
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
/**
|
|
@@ -116,7 +116,7 @@ export class OptionModel extends Model<HTMLOptionElement, OptionViewTags, Option
|
|
|
116
116
|
*
|
|
117
117
|
* @type {boolean}
|
|
118
118
|
*/
|
|
119
|
-
get selectedNonTrigger(): boolean {
|
|
119
|
+
public get selectedNonTrigger(): boolean {
|
|
120
120
|
return this.selected;
|
|
121
121
|
}
|
|
122
122
|
|
|
@@ -126,7 +126,7 @@ export class OptionModel extends Model<HTMLOptionElement, OptionViewTags, Option
|
|
|
126
126
|
*
|
|
127
127
|
* @type {boolean}
|
|
128
128
|
*/
|
|
129
|
-
set selectedNonTrigger(value: boolean) {
|
|
129
|
+
public set selectedNonTrigger(value: boolean) {
|
|
130
130
|
const input = this.view?.view?.tags?.OptionInput;
|
|
131
131
|
const viewEl = this.view?.getView?.();
|
|
132
132
|
|
|
@@ -140,7 +140,7 @@ export class OptionModel extends Model<HTMLOptionElement, OptionViewTags, Option
|
|
|
140
140
|
|
|
141
141
|
if (this.targetElement) this.targetElement.selected = value;
|
|
142
142
|
|
|
143
|
-
iEvents.callEvent<[OptionModel, boolean]>([this, value], ...this.
|
|
143
|
+
iEvents.callEvent<[OptionModel, boolean]>([this, value], ...this.privOnInternalSelected);
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
/**
|
|
@@ -149,7 +149,7 @@ export class OptionModel extends Model<HTMLOptionElement, OptionViewTags, Option
|
|
|
149
149
|
*
|
|
150
150
|
* @type {string}
|
|
151
151
|
*/
|
|
152
|
-
get text(): string {
|
|
152
|
+
public get text(): string {
|
|
153
153
|
const raw = this.dataset?.mask ?? this.targetElement?.text ?? "";
|
|
154
154
|
const translated = Libs.tagTranslate(raw);
|
|
155
155
|
return this.options.allowHtml ? translated : Libs.stripHtml(translated);
|
|
@@ -161,18 +161,18 @@ export class OptionModel extends Model<HTMLOptionElement, OptionViewTags, Option
|
|
|
161
161
|
*
|
|
162
162
|
* @type {string}
|
|
163
163
|
*/
|
|
164
|
-
get textContent(): string {
|
|
164
|
+
public get textContent(): string {
|
|
165
165
|
return this.options.allowHtml ? Libs.stripHtml(this.text).trim() : this.text.trim();
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
-
textToFind: string;
|
|
168
|
+
public textToFind: string;
|
|
169
169
|
|
|
170
170
|
/**
|
|
171
171
|
* Returns the dataset object of the underlying <option> element, or an empty object.
|
|
172
172
|
*
|
|
173
173
|
* @type {DOMStringMap|Record<string, string>}
|
|
174
174
|
*/
|
|
175
|
-
get dataset(): DOMStringMap {
|
|
175
|
+
public get dataset(): DOMStringMap {
|
|
176
176
|
return this.targetElement?.dataset ?? ({} as DOMStringMap);
|
|
177
177
|
}
|
|
178
178
|
|
|
@@ -181,7 +181,7 @@ export class OptionModel extends Model<HTMLOptionElement, OptionViewTags, Option
|
|
|
181
181
|
*
|
|
182
182
|
* @type {boolean}
|
|
183
183
|
*/
|
|
184
|
-
get highlighted(): boolean {
|
|
184
|
+
public get highlighted(): boolean {
|
|
185
185
|
return this._highlighted;
|
|
186
186
|
}
|
|
187
187
|
|
|
@@ -191,7 +191,7 @@ export class OptionModel extends Model<HTMLOptionElement, OptionViewTags, Option
|
|
|
191
191
|
*
|
|
192
192
|
* @type {boolean}
|
|
193
193
|
*/
|
|
194
|
-
set highlighted(value: boolean) {
|
|
194
|
+
public set highlighted(value: boolean) {
|
|
195
195
|
const val = !!value;
|
|
196
196
|
const viewEl = this.view?.getView?.();
|
|
197
197
|
|
|
@@ -204,8 +204,8 @@ export class OptionModel extends Model<HTMLOptionElement, OptionViewTags, Option
|
|
|
204
204
|
*
|
|
205
205
|
* @param {(evtToken: IEventCallback, el: OptionModel, selected: boolean) => void} callback - Selection listener.
|
|
206
206
|
*/
|
|
207
|
-
onSelected(callback: (evtToken: IEventCallback, el: OptionModel, selected: boolean) => void): void {
|
|
208
|
-
this.
|
|
207
|
+
public onSelected(callback: (evtToken: IEventCallback, el: OptionModel, selected: boolean) => void): void {
|
|
208
|
+
this.privOnSelected.push(callback);
|
|
209
209
|
}
|
|
210
210
|
|
|
211
211
|
/**
|
|
@@ -213,8 +213,8 @@ export class OptionModel extends Model<HTMLOptionElement, OptionViewTags, Option
|
|
|
213
213
|
*
|
|
214
214
|
* @param {(evtToken: IEventCallback, el: OptionModel, selected: boolean) => void} callback - Internal selection listener.
|
|
215
215
|
*/
|
|
216
|
-
onInternalSelected(callback: (evtToken: IEventCallback, el: OptionModel, selected: boolean) => void): void {
|
|
217
|
-
this.
|
|
216
|
+
public onInternalSelected(callback: (evtToken: IEventCallback, el: OptionModel, selected: boolean) => void): void {
|
|
217
|
+
this.privOnInternalSelected.push(callback);
|
|
218
218
|
}
|
|
219
219
|
|
|
220
220
|
/**
|
|
@@ -222,8 +222,8 @@ export class OptionModel extends Model<HTMLOptionElement, OptionViewTags, Option
|
|
|
222
222
|
*
|
|
223
223
|
* @param {(evtToken: IEventCallback, model: OptionModel, visible: boolean) => void} callback - Visibility listener.
|
|
224
224
|
*/
|
|
225
|
-
onVisibilityChanged(callback: (evtToken: IEventCallback, model: OptionModel, visible: boolean) => void): void {
|
|
226
|
-
this.
|
|
225
|
+
public onVisibilityChanged(callback: (evtToken: IEventCallback, model: OptionModel, visible: boolean) => void): void {
|
|
226
|
+
this.privOnVisibilityChanged.push(callback);
|
|
227
227
|
}
|
|
228
228
|
|
|
229
229
|
/**
|
|
@@ -231,7 +231,7 @@ export class OptionModel extends Model<HTMLOptionElement, OptionViewTags, Option
|
|
|
231
231
|
* Updates label content (HTML or text), image src/alt if present,
|
|
232
232
|
* and synchronizes initial selected state to the view.
|
|
233
233
|
*/
|
|
234
|
-
onTargetChanged(): void {
|
|
234
|
+
public onTargetChanged(): void {
|
|
235
235
|
this.textToFind = Libs.string2normalize(this.textContent.toLowerCase());
|
|
236
236
|
if (!this.view) return;
|
|
237
237
|
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
* @class
|
|
3
3
|
*/
|
|
4
4
|
export class DatasetObserver {
|
|
5
|
-
private
|
|
5
|
+
private observer: MutationObserver;
|
|
6
6
|
|
|
7
|
-
private
|
|
7
|
+
private element: HTMLElement;
|
|
8
8
|
|
|
9
|
-
private
|
|
9
|
+
private debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Observes data-* attribute changes on a target element and debounces notifications.
|
|
@@ -14,10 +14,10 @@ export class DatasetObserver {
|
|
|
14
14
|
*
|
|
15
15
|
* @param {HTMLElement} element - The element whose dataset (data-* attributes) will be observed.
|
|
16
16
|
*/
|
|
17
|
-
constructor(element: HTMLElement) {
|
|
18
|
-
this.
|
|
17
|
+
public constructor(element: HTMLElement) {
|
|
18
|
+
this.element = element;
|
|
19
19
|
|
|
20
|
-
this.
|
|
20
|
+
this.observer = new MutationObserver((mutations: MutationRecord[]) => {
|
|
21
21
|
let datasetChanged = false;
|
|
22
22
|
|
|
23
23
|
for (const mutation of mutations) {
|
|
@@ -29,14 +29,14 @@ export class DatasetObserver {
|
|
|
29
29
|
|
|
30
30
|
if (!datasetChanged) return;
|
|
31
31
|
|
|
32
|
-
if (this.
|
|
33
|
-
this.
|
|
34
|
-
this.onChanged({ ...this.
|
|
32
|
+
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
33
|
+
this.debounceTimer = setTimeout(() => {
|
|
34
|
+
this.onChanged({ ...this.element.dataset });
|
|
35
35
|
}, 50);
|
|
36
36
|
});
|
|
37
37
|
|
|
38
38
|
element.addEventListener("dataset:changed", () => {
|
|
39
|
-
this.onChanged({ ...this.
|
|
39
|
+
this.onChanged({ ...this.element.dataset });
|
|
40
40
|
});
|
|
41
41
|
}
|
|
42
42
|
|
|
@@ -44,8 +44,8 @@ export class DatasetObserver {
|
|
|
44
44
|
* Starts observing the element for attribute changes, including old values.
|
|
45
45
|
* Uses MutationObserver to track updates to data-* attributes.
|
|
46
46
|
*/
|
|
47
|
-
connect(): void {
|
|
48
|
-
this.
|
|
47
|
+
public connect(): void {
|
|
48
|
+
this.observer.observe(this.element, {
|
|
49
49
|
attributes: true,
|
|
50
50
|
attributeOldValue: true,
|
|
51
51
|
});
|
|
@@ -58,16 +58,16 @@ export class DatasetObserver {
|
|
|
58
58
|
* @param {Record<string, string>} dataset - A shallow copy of the element's current dataset.
|
|
59
59
|
*/
|
|
60
60
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
61
|
-
onChanged(dataset: Record<string, string>): void {
|
|
61
|
+
public onChanged(dataset: Record<string, string>): void {
|
|
62
62
|
// override
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
66
|
* Stops observing the element and clears any pending debounce timers.
|
|
67
67
|
*/
|
|
68
|
-
disconnect(): void {
|
|
69
|
-
if (this.
|
|
70
|
-
this.
|
|
71
|
-
this.
|
|
68
|
+
public disconnect(): void {
|
|
69
|
+
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
70
|
+
this.debounceTimer = null;
|
|
71
|
+
this.observer.disconnect();
|
|
72
72
|
}
|
|
73
73
|
}
|
|
@@ -2,26 +2,26 @@
|
|
|
2
2
|
* @class
|
|
3
3
|
*/
|
|
4
4
|
export class ElementAdditionObserver<T extends Element = Element> {
|
|
5
|
-
private
|
|
5
|
+
private isActive = false;
|
|
6
6
|
|
|
7
|
-
private
|
|
7
|
+
private observer: MutationObserver | null = null;
|
|
8
8
|
|
|
9
|
-
private
|
|
9
|
+
private actions: Array<(el: T) => void> = [];
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Registers a callback to be invoked whenever a matching element is detected being added to the DOM.
|
|
13
13
|
*
|
|
14
14
|
* @param {(el: T) => void} action - Function executed with the newly added element.
|
|
15
15
|
*/
|
|
16
|
-
onDetect(action: (el: T) => void): void {
|
|
17
|
-
this.
|
|
16
|
+
public onDetect(action: (el: T) => void): void {
|
|
17
|
+
this.actions.push(action);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Clears all previously registered detection callbacks.
|
|
22
22
|
*/
|
|
23
|
-
clearDetect(): void {
|
|
24
|
-
this.
|
|
23
|
+
public clearDetect(): void {
|
|
24
|
+
this.actions = [];
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/**
|
|
@@ -30,15 +30,15 @@ export class ElementAdditionObserver<T extends Element = Element> {
|
|
|
30
30
|
*
|
|
31
31
|
* @param {string} tag - The tag name to watch for (e.g., "select", "div").
|
|
32
32
|
*/
|
|
33
|
-
start(tag: string): void {
|
|
34
|
-
if (this.
|
|
33
|
+
public start(tag: string): void {
|
|
34
|
+
if (this.isActive) return;
|
|
35
35
|
|
|
36
|
-
this.
|
|
36
|
+
this.isActive = true;
|
|
37
37
|
|
|
38
38
|
const upperTag = tag.toUpperCase();
|
|
39
39
|
const lowerTag = tag.toLowerCase();
|
|
40
40
|
|
|
41
|
-
this.
|
|
41
|
+
this.observer = new MutationObserver((mutations: MutationRecord[]) => {
|
|
42
42
|
for (const mutation of mutations) {
|
|
43
43
|
mutation.addedNodes.forEach((node) => {
|
|
44
44
|
if (node.nodeType !== 1) return;
|
|
@@ -46,16 +46,16 @@ export class ElementAdditionObserver<T extends Element = Element> {
|
|
|
46
46
|
const subnode = node as HTMLElement;
|
|
47
47
|
|
|
48
48
|
if (subnode.tagName === upperTag) {
|
|
49
|
-
this.
|
|
49
|
+
this.handle(subnode as unknown as T);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
const matches = subnode.querySelectorAll(lowerTag);
|
|
53
|
-
matches.forEach((el) => this.
|
|
53
|
+
matches.forEach((el) => this.handle(el as unknown as T));
|
|
54
54
|
});
|
|
55
55
|
}
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
-
this.
|
|
58
|
+
this.observer.observe(document.body, {
|
|
59
59
|
childList: true,
|
|
60
60
|
subtree: true,
|
|
61
61
|
});
|
|
@@ -65,12 +65,12 @@ export class ElementAdditionObserver<T extends Element = Element> {
|
|
|
65
65
|
* Stops observing for element additions and releases internal resources.
|
|
66
66
|
* No-ops if the observer is not active.
|
|
67
67
|
*/
|
|
68
|
-
stop(): void {
|
|
69
|
-
if (!this.
|
|
68
|
+
public stop(): void {
|
|
69
|
+
if (!this.isActive) return;
|
|
70
70
|
|
|
71
|
-
this.
|
|
72
|
-
this.
|
|
73
|
-
this.
|
|
71
|
+
this.isActive = false;
|
|
72
|
+
this.observer?.disconnect();
|
|
73
|
+
this.observer = null;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
/**
|
|
@@ -78,7 +78,7 @@ export class ElementAdditionObserver<T extends Element = Element> {
|
|
|
78
78
|
*
|
|
79
79
|
* @param {T} element - The element that was detected as added to the DOM.
|
|
80
80
|
*/
|
|
81
|
-
private
|
|
82
|
-
this.
|
|
81
|
+
private handle(element: T): void {
|
|
82
|
+
this.actions.forEach((action) => action(element));
|
|
83
83
|
}
|
|
84
84
|
}
|