selective-ui 1.2.1 → 1.2.3
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 +498 -467
- 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 +499 -468
- 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 +50 -54
- package/src/ts/components/accessorybox.ts +51 -25
- 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 +94 -109
- package/src/ts/components/searchbox.ts +14 -14
- package/src/ts/components/selectbox.ts +25 -29
- package/src/ts/core/base/adapter.ts +19 -19
- 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 +55 -52
- package/src/ts/core/model-manager.ts +61 -54
- package/src/ts/core/search-controller.ts +69 -71
- 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 +27 -27
- package/src/ts/services/refresher.ts +1 -1
- package/src/ts/services/resize-observer.ts +29 -29
- package/src/ts/services/select-observer.ts +27 -34
- 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 +41 -21
- 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
package/package.json
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
display: none;
|
|
11
11
|
position: fixed;
|
|
12
12
|
z-index: 9999;
|
|
13
|
-
overflow:
|
|
13
|
+
overflow: hidden;
|
|
14
14
|
flex-direction: column;
|
|
15
15
|
|
|
16
16
|
height: 0px;
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
border-style: var(--seui-popup-border-style);
|
|
23
23
|
border-width: var(--seui-popup-border-width);
|
|
24
24
|
border-radius: var(--seui-popup-border-radius);
|
|
25
|
+
|
|
26
|
+
transition: none;
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
.selective-ui-options-container {
|
|
@@ -12,26 +12,26 @@ import { Libs } from "../utils/libs";
|
|
|
12
12
|
* @extends {Adapter<GroupModel|OptionModel>}
|
|
13
13
|
*/
|
|
14
14
|
export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
15
|
-
isMultiple = false;
|
|
15
|
+
public isMultiple = false;
|
|
16
16
|
|
|
17
|
-
private
|
|
18
|
-
private
|
|
17
|
+
private visibilityChangedCallbacks: Array<(stats: VisibilityStats) => void> = [];
|
|
18
|
+
private currentHighlightIndex = -1;
|
|
19
19
|
|
|
20
|
-
private
|
|
20
|
+
private selectedItemSingle: OptionModel | null = null;
|
|
21
21
|
|
|
22
|
-
groups: GroupModel[] = [];
|
|
22
|
+
public groups: GroupModel[] = [];
|
|
23
23
|
|
|
24
|
-
flatOptions: OptionModel[] = [];
|
|
24
|
+
public flatOptions: OptionModel[] = [];
|
|
25
25
|
|
|
26
|
-
constructor(items: MixedItem[] = []) {
|
|
26
|
+
public constructor(items: MixedItem[] = []) {
|
|
27
27
|
super(items);
|
|
28
|
-
this.
|
|
28
|
+
this.buildFlatStructure();
|
|
29
29
|
|
|
30
30
|
Libs.callbackScheduler.on(`sche_vis_${this.adapterKey}`, () => {
|
|
31
31
|
const visibleCount = this.flatOptions.filter((item) => item.visible).length;
|
|
32
32
|
const totalCount = this.flatOptions.length;
|
|
33
33
|
|
|
34
|
-
this.
|
|
34
|
+
this.visibilityChangedCallbacks.forEach((callback) => {
|
|
35
35
|
callback({
|
|
36
36
|
visibleCount,
|
|
37
37
|
totalCount,
|
|
@@ -46,7 +46,7 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
46
46
|
/**
|
|
47
47
|
* Build flat list of all options for navigation
|
|
48
48
|
*/
|
|
49
|
-
private
|
|
49
|
+
private buildFlatStructure(): void {
|
|
50
50
|
this.flatOptions = [];
|
|
51
51
|
this.groups = [];
|
|
52
52
|
|
|
@@ -84,9 +84,9 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
84
84
|
item.position = position;
|
|
85
85
|
|
|
86
86
|
if (item instanceof GroupModel) {
|
|
87
|
-
this.
|
|
87
|
+
this.handleGroupView(item, viewer as GroupView, position);
|
|
88
88
|
} else if (item instanceof OptionModel) {
|
|
89
|
-
this.
|
|
89
|
+
this.handleOptionView(item, viewer as OptionView, position);
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
item.isInit = true;
|
|
@@ -100,7 +100,7 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
100
100
|
* @param {GroupView} groupView - The view instance that renders the group in the UI.
|
|
101
101
|
* @param {number} position - The position (index) of the group within a list.
|
|
102
102
|
*/
|
|
103
|
-
private
|
|
103
|
+
private handleGroupView(groupModel: GroupModel, groupView: GroupView, position: number): void {
|
|
104
104
|
super.onViewHolder(groupModel, groupView, position);
|
|
105
105
|
groupModel.view = groupView;
|
|
106
106
|
|
|
@@ -134,7 +134,7 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
134
134
|
optionViewer = new OptionView(itemsContainer);
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
this.
|
|
137
|
+
this.handleOptionView(optionModel, optionViewer, idx);
|
|
138
138
|
optionModel.isInit = true;
|
|
139
139
|
});
|
|
140
140
|
|
|
@@ -150,7 +150,7 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
150
150
|
* @param {OptionView} optionViewer - The view instance that renders the option in the UI.
|
|
151
151
|
* @param {number} position - The index of this option within its group's item list.
|
|
152
152
|
*/
|
|
153
|
-
private
|
|
153
|
+
private handleOptionView(optionModel: OptionModel, optionViewer: OptionView, position: number): void {
|
|
154
154
|
optionViewer.isMultiple = this.isMultiple;
|
|
155
155
|
optionViewer.hasImage = optionModel.hasImage;
|
|
156
156
|
|
|
@@ -180,23 +180,19 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
180
180
|
optionViewer.view.tags.LabelContent.innerHTML = optionModel.text;
|
|
181
181
|
|
|
182
182
|
if (!optionModel.isInit) {
|
|
183
|
-
optionViewer.view.tags.OptionView.addEventListener("click", (ev: MouseEvent) => {
|
|
183
|
+
optionViewer.view.tags.OptionView.addEventListener("click", async (ev: MouseEvent) => {
|
|
184
184
|
ev.stopPropagation();
|
|
185
185
|
ev.preventDefault();
|
|
186
186
|
|
|
187
187
|
if (this.isSkipEvent) return;
|
|
188
188
|
|
|
189
189
|
if (this.isMultiple) {
|
|
190
|
-
this.changingProp("select");
|
|
191
|
-
|
|
192
|
-
optionModel.selected = !optionModel.selected;
|
|
193
|
-
}, 5);
|
|
190
|
+
await this.changingProp("select");
|
|
191
|
+
optionModel.selected = !optionModel.selected;
|
|
194
192
|
} else if (optionModel.selected !== true) {
|
|
195
|
-
this.changingProp("select");
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
optionModel.selected = true;
|
|
199
|
-
}, 5);
|
|
193
|
+
await this.changingProp("select");
|
|
194
|
+
if (this.selectedItemSingle) this.selectedItemSingle.selected = false;
|
|
195
|
+
optionModel.selected = true;
|
|
200
196
|
}
|
|
201
197
|
});
|
|
202
198
|
|
|
@@ -212,18 +208,18 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
212
208
|
});
|
|
213
209
|
|
|
214
210
|
optionModel.onInternalSelected((_evtToken: IEventCallback, _el: OptionModel, selected: boolean) => {
|
|
215
|
-
if (selected) this.
|
|
211
|
+
if (selected) this.selectedItemSingle = optionModel;
|
|
216
212
|
this.changeProp("selected_internal");
|
|
217
213
|
});
|
|
218
214
|
|
|
219
215
|
optionModel.onVisibilityChanged((_evtToken: IEventCallback, model: OptionModel, _visible: boolean) => {
|
|
220
216
|
model.group?.updateVisibility();
|
|
221
|
-
this.
|
|
217
|
+
this.notifyVisibilityChanged();
|
|
222
218
|
});
|
|
223
219
|
}
|
|
224
220
|
|
|
225
221
|
if (optionModel.selected) {
|
|
226
|
-
this.
|
|
222
|
+
this.selectedItemSingle = optionModel;
|
|
227
223
|
optionModel.selectedNonTrigger = true;
|
|
228
224
|
}
|
|
229
225
|
}
|
|
@@ -233,11 +229,11 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
233
229
|
*
|
|
234
230
|
* @param {Array<GroupModel|OptionModel>} items - The new collection of items to be displayed.
|
|
235
231
|
*/
|
|
236
|
-
override setItems(items: MixedItem[]): void {
|
|
237
|
-
this.changingProp("items", items);
|
|
232
|
+
override async setItems(items: MixedItem[]): Promise<void> {
|
|
233
|
+
await this.changingProp("items", items);
|
|
238
234
|
this.items = items;
|
|
239
|
-
this.
|
|
240
|
-
this.changeProp("items", items);
|
|
235
|
+
this.buildFlatStructure();
|
|
236
|
+
await this.changeProp("items", items);
|
|
241
237
|
}
|
|
242
238
|
|
|
243
239
|
/**
|
|
@@ -245,8 +241,8 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
245
241
|
*
|
|
246
242
|
* @param {Array<GroupModel|OptionModel>} items - The new collection of items to sync.
|
|
247
243
|
*/
|
|
248
|
-
override syncFromSource(items: MixedItem[]): void {
|
|
249
|
-
this.setItems(items);
|
|
244
|
+
override async syncFromSource(items: MixedItem[]): Promise<void> {
|
|
245
|
+
await this.setItems(items);
|
|
250
246
|
}
|
|
251
247
|
|
|
252
248
|
/**
|
|
@@ -257,7 +253,7 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
257
253
|
*/
|
|
258
254
|
override updateData(items: MixedItem[]): void {
|
|
259
255
|
this.items = items;
|
|
260
|
-
this.
|
|
256
|
+
this.buildFlatStructure();
|
|
261
257
|
}
|
|
262
258
|
|
|
263
259
|
/**
|
|
@@ -265,7 +261,7 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
265
261
|
*
|
|
266
262
|
* @returns {OptionModel[]} - An array of selected option items from the flat list.
|
|
267
263
|
*/
|
|
268
|
-
getSelectedItems(): OptionModel[] {
|
|
264
|
+
public getSelectedItems(): OptionModel[] {
|
|
269
265
|
return this.flatOptions.filter((item) => item.selected);
|
|
270
266
|
}
|
|
271
267
|
|
|
@@ -274,7 +270,7 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
274
270
|
*
|
|
275
271
|
* @returns {OptionModel|undefined} - The first selected option or undefined if none are selected.
|
|
276
272
|
*/
|
|
277
|
-
getSelectedItem(): OptionModel | undefined {
|
|
273
|
+
public getSelectedItem(): OptionModel | undefined {
|
|
278
274
|
return this.flatOptions.find((item) => item.selected);
|
|
279
275
|
}
|
|
280
276
|
|
|
@@ -283,7 +279,7 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
283
279
|
*
|
|
284
280
|
* @param {boolean} isChecked - If true, select all; if false, deselect all.
|
|
285
281
|
*/
|
|
286
|
-
checkAll(isChecked: boolean): void {
|
|
282
|
+
public checkAll(isChecked: boolean): void {
|
|
287
283
|
if (!this.isMultiple) return;
|
|
288
284
|
this.flatOptions.forEach((item) => {
|
|
289
285
|
item.selected = isChecked;
|
|
@@ -296,22 +292,22 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
296
292
|
* @param {(stats: {visibleCount:number,totalCount:number,hasVisible:boolean,isEmpty:boolean}) => void} callback
|
|
297
293
|
* - Function to invoke when visibility stats change.
|
|
298
294
|
*/
|
|
299
|
-
onVisibilityChanged(callback: (stats: VisibilityStats) => void): void {
|
|
300
|
-
this.
|
|
295
|
+
public onVisibilityChanged(callback: (stats: VisibilityStats) => void): void {
|
|
296
|
+
this.visibilityChangedCallbacks.push(callback);
|
|
301
297
|
}
|
|
302
298
|
|
|
303
299
|
/**
|
|
304
300
|
* Notifies all registered visibility-change callbacks with up-to-date statistics.
|
|
305
301
|
* Computes visible and total counts, then emits aggregated state.
|
|
306
302
|
*/
|
|
307
|
-
private
|
|
303
|
+
private notifyVisibilityChanged(): void {
|
|
308
304
|
Libs.callbackScheduler.run(`sche_vis_${this.adapterKey}`);
|
|
309
305
|
}
|
|
310
306
|
|
|
311
307
|
/**
|
|
312
308
|
* Computes and returns current visibility statistics for options.
|
|
313
309
|
*/
|
|
314
|
-
getVisibilityStats(): VisibilityStats {
|
|
310
|
+
public getVisibilityStats(): VisibilityStats {
|
|
315
311
|
const visibleCount = this.flatOptions.filter((item) => item.visible).length;
|
|
316
312
|
const totalCount = this.flatOptions.length;
|
|
317
313
|
|
|
@@ -326,7 +322,7 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
326
322
|
/**
|
|
327
323
|
* Resets the highlight to the first visible option (index 0).
|
|
328
324
|
*/
|
|
329
|
-
resetHighlight(): void {
|
|
325
|
+
public resetHighlight(): void {
|
|
330
326
|
this.setHighlight(0);
|
|
331
327
|
}
|
|
332
328
|
|
|
@@ -336,12 +332,12 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
336
332
|
* @param {number} direction - Increment (+1) or decrement (-1) of the current visible index.
|
|
337
333
|
* @param {boolean} [isScrollToView=true] - Whether to scroll the highlighted item into view.
|
|
338
334
|
*/
|
|
339
|
-
navigate(direction: number, isScrollToView: boolean = true): void {
|
|
335
|
+
public navigate(direction: number, isScrollToView: boolean = true): void {
|
|
340
336
|
const visibleOptions = this.flatOptions.filter((opt) => opt.visible);
|
|
341
337
|
if (visibleOptions.length === 0) return;
|
|
342
338
|
|
|
343
339
|
let currentVisibleIndex = visibleOptions.findIndex(
|
|
344
|
-
(opt) => opt === this.flatOptions[this.
|
|
340
|
+
(opt) => opt === this.flatOptions[this.currentHighlightIndex]
|
|
345
341
|
);
|
|
346
342
|
if (currentVisibleIndex === -1) currentVisibleIndex = -1;
|
|
347
343
|
|
|
@@ -359,9 +355,9 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
359
355
|
* Triggers a click on the currently highlighted and visible option to select it.
|
|
360
356
|
* No-op if nothing is highlighted or the highlighted item is not visible.
|
|
361
357
|
*/
|
|
362
|
-
selectHighlighted(): void {
|
|
363
|
-
if (this.
|
|
364
|
-
const item = this.flatOptions[this.
|
|
358
|
+
public selectHighlighted(): void {
|
|
359
|
+
if (this.currentHighlightIndex > -1 && this.flatOptions[this.currentHighlightIndex]) {
|
|
360
|
+
const item = this.flatOptions[this.currentHighlightIndex];
|
|
365
361
|
if (item.visible) {
|
|
366
362
|
const viewEl = item.view?.getView?.();
|
|
367
363
|
if (viewEl) viewEl.click();
|
|
@@ -376,7 +372,7 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
376
372
|
* @param {number|OptionModel} target - Flat index or the specific OptionModel to highlight.
|
|
377
373
|
* @param {boolean} [isScrollToView=true] - Whether to scroll the highlighted item into view.
|
|
378
374
|
*/
|
|
379
|
-
setHighlight(target: number | OptionModel, isScrollToView: boolean = true): void {
|
|
375
|
+
public setHighlight(target: number | OptionModel, isScrollToView: boolean = true): void {
|
|
380
376
|
let index = 0;
|
|
381
377
|
|
|
382
378
|
if (typeof target === "number") {
|
|
@@ -388,8 +384,8 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
388
384
|
index = 0;
|
|
389
385
|
}
|
|
390
386
|
|
|
391
|
-
if (this.
|
|
392
|
-
this.flatOptions[this.
|
|
387
|
+
if (this.currentHighlightIndex > -1 && this.flatOptions[this.currentHighlightIndex]) {
|
|
388
|
+
this.flatOptions[this.currentHighlightIndex].highlighted = false;
|
|
393
389
|
}
|
|
394
390
|
|
|
395
391
|
for (let i = index; i < this.flatOptions.length; i++) {
|
|
@@ -397,7 +393,7 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
397
393
|
if (!item?.visible) continue;
|
|
398
394
|
|
|
399
395
|
item.highlighted = true;
|
|
400
|
-
this.
|
|
396
|
+
this.currentHighlightIndex = i;
|
|
401
397
|
|
|
402
398
|
if (isScrollToView) {
|
|
403
399
|
const el = item.view?.getView?.();
|
|
@@ -417,11 +413,11 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
417
413
|
* Hook invoked whenever the highlight changes.
|
|
418
414
|
* Override to handle UI side effects (e.g., ARIA announcement, focus sync).
|
|
419
415
|
*/
|
|
420
|
-
onHighlightChange(index: number, id?: string): void { }
|
|
416
|
+
public onHighlightChange(index: number, id?: string): void { }
|
|
421
417
|
|
|
422
418
|
/**
|
|
423
419
|
* Hook invoked when a group's collapsed state changes.
|
|
424
420
|
* Override to handle side effects like analytics or layout adjustments.
|
|
425
421
|
*/
|
|
426
|
-
onCollapsedChange(model: GroupModel, collapsed: boolean): void { }
|
|
422
|
+
public onCollapsedChange(model: GroupModel, collapsed: boolean): void { }
|
|
427
423
|
}
|
|
@@ -11,24 +11,26 @@ import { Libs } from "../utils/libs";
|
|
|
11
11
|
* @class
|
|
12
12
|
*/
|
|
13
13
|
export class AccessoryBox {
|
|
14
|
-
nodeMounted: MountViewResult<any> | null = null;
|
|
14
|
+
private nodeMounted: MountViewResult<any> | null = null;
|
|
15
15
|
|
|
16
|
-
node: HTMLDivElement | null = null;
|
|
16
|
+
private node: HTMLDivElement | null = null;
|
|
17
17
|
|
|
18
|
-
options: SelectiveOptions | null = null;
|
|
18
|
+
private options: SelectiveOptions | null = null;
|
|
19
19
|
|
|
20
|
-
selectUIMask: HTMLDivElement | null = null;
|
|
20
|
+
private selectUIMask: HTMLDivElement | null = null;
|
|
21
21
|
|
|
22
|
-
parentMask: HTMLDivElement | null = null;
|
|
22
|
+
private parentMask: HTMLDivElement | null = null;
|
|
23
23
|
|
|
24
|
-
modelManager: ModelManager<MixedItem, MixedAdapter> | null = null;
|
|
24
|
+
private modelManager: ModelManager<MixedItem, MixedAdapter> | null = null;
|
|
25
|
+
|
|
26
|
+
private modelDatas: OptionModel[] = [];
|
|
25
27
|
|
|
26
28
|
/**
|
|
27
29
|
* Initializes the accessory box with optional configuration and immediately calls init() if provided.
|
|
28
30
|
*
|
|
29
31
|
* @param {object|null} options - Configuration options for the accessory box (e.g., layout and behavior).
|
|
30
32
|
*/
|
|
31
|
-
constructor(options: SelectiveOptions | null = null) {
|
|
33
|
+
public constructor(options: SelectiveOptions | null = null) {
|
|
32
34
|
if (options) this.init(options);
|
|
33
35
|
}
|
|
34
36
|
|
|
@@ -38,7 +40,7 @@ export class AccessoryBox {
|
|
|
38
40
|
*
|
|
39
41
|
* @param {SelectiveOptions} options - Configuration object for the accessory box.
|
|
40
42
|
*/
|
|
41
|
-
init(options: SelectiveOptions): void {
|
|
43
|
+
private init(options: SelectiveOptions): void {
|
|
42
44
|
this.nodeMounted = Libs.mountNode({
|
|
43
45
|
AccessoryBox: {
|
|
44
46
|
tag: {
|
|
@@ -60,7 +62,7 @@ export class AccessoryBox {
|
|
|
60
62
|
*
|
|
61
63
|
* @param {HTMLDivElement} selectUIMask - The overlay/mask element of the main Select UI.
|
|
62
64
|
*/
|
|
63
|
-
setRoot(selectUIMask: HTMLDivElement): void {
|
|
65
|
+
public setRoot(selectUIMask: HTMLDivElement): void {
|
|
64
66
|
this.selectUIMask = selectUIMask;
|
|
65
67
|
this.parentMask = selectUIMask.parentElement as HTMLDivElement | null;
|
|
66
68
|
|
|
@@ -71,8 +73,14 @@ export class AccessoryBox {
|
|
|
71
73
|
* Inserts the accessory box before or after the Select UI mask depending on the configured accessoryStyle.
|
|
72
74
|
* Keeps the accessory box aligned relative to the parent mask.
|
|
73
75
|
*/
|
|
74
|
-
refreshLocation(): void {
|
|
75
|
-
if (
|
|
76
|
+
public refreshLocation(): void {
|
|
77
|
+
if (
|
|
78
|
+
!this.parentMask ||
|
|
79
|
+
!this.node ||
|
|
80
|
+
!this.selectUIMask ||
|
|
81
|
+
!this.options
|
|
82
|
+
)
|
|
83
|
+
return;
|
|
76
84
|
|
|
77
85
|
const ref =
|
|
78
86
|
this.options.accessoryStyle === "top"
|
|
@@ -87,7 +95,9 @@ export class AccessoryBox {
|
|
|
87
95
|
*
|
|
88
96
|
* @param {ModelManager} modelManager - The model manager controlling option state.
|
|
89
97
|
*/
|
|
90
|
-
setModelManager(
|
|
98
|
+
public setModelManager(
|
|
99
|
+
modelManager: ModelManager<MixedItem, MixedAdapter> | null,
|
|
100
|
+
): void {
|
|
91
101
|
this.modelManager = modelManager;
|
|
92
102
|
}
|
|
93
103
|
|
|
@@ -97,14 +107,11 @@ export class AccessoryBox {
|
|
|
97
107
|
*
|
|
98
108
|
* @param {OptionModel[]} modelDatas - List of option models to render as accessory items.
|
|
99
109
|
*/
|
|
100
|
-
setModelData(modelDatas: OptionModel[]): void {
|
|
110
|
+
public setModelData(modelDatas: OptionModel[]): void {
|
|
101
111
|
if (!this.node || !this.options) return;
|
|
102
|
-
|
|
103
112
|
this.node.replaceChildren();
|
|
104
113
|
|
|
105
114
|
if (modelDatas.length > 0 && this.options.multiple) {
|
|
106
|
-
this.node.classList.remove("hide");
|
|
107
|
-
|
|
108
115
|
modelDatas.forEach((modelData) => {
|
|
109
116
|
Libs.mountNode(
|
|
110
117
|
{
|
|
@@ -118,12 +125,12 @@ export class AccessoryBox {
|
|
|
118
125
|
role: "button",
|
|
119
126
|
ariaLabel: `${this.options!.textAccessoryDeselect}${modelData.textContent}`,
|
|
120
127
|
title: `${this.options!.textAccessoryDeselect}${modelData.textContent}`,
|
|
121
|
-
onclick: (evt: MouseEvent) => {
|
|
128
|
+
onclick: async (evt: MouseEvent) => {
|
|
122
129
|
evt.preventDefault();
|
|
123
|
-
this.modelManager?.triggerChanging?.(
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
130
|
+
await this.modelManager?.triggerChanging?.(
|
|
131
|
+
"select",
|
|
132
|
+
);
|
|
133
|
+
modelData.selected = false;
|
|
127
134
|
},
|
|
128
135
|
},
|
|
129
136
|
},
|
|
@@ -137,13 +144,32 @@ export class AccessoryBox {
|
|
|
137
144
|
},
|
|
138
145
|
},
|
|
139
146
|
},
|
|
140
|
-
this.node
|
|
147
|
+
this.node,
|
|
141
148
|
);
|
|
142
149
|
});
|
|
143
|
-
}
|
|
144
|
-
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
modelDatas = [];
|
|
145
153
|
}
|
|
146
154
|
|
|
155
|
+
this.modelDatas = modelDatas;
|
|
156
|
+
this.refreshDisplay();
|
|
147
157
|
iEvents.trigger(window, "resize");
|
|
148
158
|
}
|
|
149
|
-
|
|
159
|
+
|
|
160
|
+
private refreshDisplay(): void {
|
|
161
|
+
if (this.options?.accessoryVisible && this.modelDatas.length > 0 && this.options.multiple) {
|
|
162
|
+
this.show();
|
|
163
|
+
} else {
|
|
164
|
+
this.hide();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private show(): void {
|
|
169
|
+
this.node.classList.remove("hide");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private hide(): void {
|
|
173
|
+
this.node.classList.add("hide");
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -8,14 +8,14 @@ export class Directive {
|
|
|
8
8
|
node: HTMLElement;
|
|
9
9
|
|
|
10
10
|
constructor() {
|
|
11
|
-
this.node = this.
|
|
11
|
+
this.node = this.init();
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Represents a directive button element used to toggle dropdown state.
|
|
16
16
|
* Initializes a clickable node with appropriate ARIA attributes for accessibility.
|
|
17
17
|
*/
|
|
18
|
-
private
|
|
18
|
+
private init(): HTMLElement {
|
|
19
19
|
// Libs.nodeCreator returns Element, but this node is always an HTMLElement in practice.
|
|
20
20
|
return Libs.nodeCreator({
|
|
21
21
|
node: "div",
|
|
@@ -30,7 +30,7 @@ export class Directive {
|
|
|
30
30
|
*
|
|
31
31
|
* @param {boolean} value - If true, adds the "drop-down" class; otherwise removes it.
|
|
32
32
|
*/
|
|
33
|
-
setDropdown(value: boolean): void {
|
|
33
|
+
public setDropdown(value: boolean): void {
|
|
34
34
|
this.node.classList.toggle("drop-down", !!value);
|
|
35
35
|
}
|
|
36
36
|
}
|
|
@@ -6,15 +6,15 @@ import { Libs } from "../utils/libs";
|
|
|
6
6
|
* @class
|
|
7
7
|
*/
|
|
8
8
|
export class EmptyState {
|
|
9
|
-
node: HTMLDivElement | null = null;
|
|
9
|
+
public node: HTMLDivElement | null = null;
|
|
10
10
|
|
|
11
|
-
options: SelectiveOptions | null = null;
|
|
11
|
+
public options: SelectiveOptions | null = null;
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Represents an empty state component that displays a message when no data or search results are available.
|
|
15
15
|
* Provides methods to show/hide the state and check its visibility.
|
|
16
16
|
*/
|
|
17
|
-
constructor(options: SelectiveOptions | null = null) {
|
|
17
|
+
public constructor(options: SelectiveOptions | null = null) {
|
|
18
18
|
if (options) this.init(options);
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -23,7 +23,7 @@ export class EmptyState {
|
|
|
23
23
|
*
|
|
24
24
|
* @param {object} options - Configuration object containing text for "no data" and "not found" states.
|
|
25
25
|
*/
|
|
26
|
-
init(options: SelectiveOptions): void {
|
|
26
|
+
private init(options: SelectiveOptions): void {
|
|
27
27
|
this.options = options;
|
|
28
28
|
|
|
29
29
|
this.node = Libs.nodeCreator({
|
|
@@ -40,7 +40,7 @@ export class EmptyState {
|
|
|
40
40
|
* @param {"notfound" | "nodata"} [type="nodata"] - Determines which message to show:
|
|
41
41
|
* "notfound" for search results not found, "nodata" for no available data.
|
|
42
42
|
*/
|
|
43
|
-
show(type: EmptyStateType = "nodata"): void {
|
|
43
|
+
public show(type: EmptyStateType = "nodata"): void {
|
|
44
44
|
if (!this.node || !this.options) return;
|
|
45
45
|
|
|
46
46
|
const text = type === "notfound" ? this.options.textNotFound : this.options.textNoData;
|
|
@@ -52,7 +52,7 @@ export class EmptyState {
|
|
|
52
52
|
/**
|
|
53
53
|
* Hides the empty state element by adding the "hide" class.
|
|
54
54
|
*/
|
|
55
|
-
hide(): void {
|
|
55
|
+
public hide(): void {
|
|
56
56
|
if (!this.node) return;
|
|
57
57
|
this.node.classList.add("hide");
|
|
58
58
|
}
|
|
@@ -62,7 +62,7 @@ export class EmptyState {
|
|
|
62
62
|
*
|
|
63
63
|
* @returns {boolean} - True if visible, false otherwise.
|
|
64
64
|
*/
|
|
65
|
-
get isVisible(): boolean {
|
|
65
|
+
public get isVisible(): boolean {
|
|
66
66
|
return !!this.node && !this.node.classList.contains("hide");
|
|
67
67
|
}
|
|
68
68
|
}
|
|
@@ -5,15 +5,15 @@ import { Libs } from "../utils/libs";
|
|
|
5
5
|
* @class
|
|
6
6
|
*/
|
|
7
7
|
export class LoadingState {
|
|
8
|
-
node: HTMLDivElement | null = null;
|
|
8
|
+
public node: HTMLDivElement | null = null;
|
|
9
9
|
|
|
10
|
-
options: SelectiveOptions | null = null;
|
|
10
|
+
public options: SelectiveOptions | null = null;
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Represents a loading state component that displays a loading message during data fetch or processing.
|
|
14
14
|
* Provides methods to show/hide the state and check its visibility.
|
|
15
15
|
*/
|
|
16
|
-
constructor(options: SelectiveOptions | null = null) {
|
|
16
|
+
public constructor(options: SelectiveOptions | null = null) {
|
|
17
17
|
if (options) this.init(options);
|
|
18
18
|
}
|
|
19
19
|
|
|
@@ -22,7 +22,7 @@ export class LoadingState {
|
|
|
22
22
|
*
|
|
23
23
|
* @param {object} options - Configuration object containing text for the loading message.
|
|
24
24
|
*/
|
|
25
|
-
init(options: SelectiveOptions): void {
|
|
25
|
+
private init(options: SelectiveOptions): void {
|
|
26
26
|
this.options = options;
|
|
27
27
|
|
|
28
28
|
this.node = Libs.nodeCreator({
|
|
@@ -39,7 +39,7 @@ export class LoadingState {
|
|
|
39
39
|
*
|
|
40
40
|
* @param {boolean} hasItems - If true, applies a "small" style for compact display.
|
|
41
41
|
*/
|
|
42
|
-
show(hasItems: boolean): void {
|
|
42
|
+
public show(hasItems: boolean): void {
|
|
43
43
|
if (!this.node || !this.options) return;
|
|
44
44
|
|
|
45
45
|
this.node.textContent = this.options.textLoading;
|
|
@@ -50,7 +50,7 @@ export class LoadingState {
|
|
|
50
50
|
/**
|
|
51
51
|
* Hides the loading state element by adding the "hide" class.
|
|
52
52
|
*/
|
|
53
|
-
hide(): void {
|
|
53
|
+
public hide(): void {
|
|
54
54
|
if (!this.node) return;
|
|
55
55
|
this.node.classList.add("hide");
|
|
56
56
|
}
|
|
@@ -60,7 +60,7 @@ export class LoadingState {
|
|
|
60
60
|
*
|
|
61
61
|
* @returns {boolean} - True if visible, false otherwise.
|
|
62
62
|
*/
|
|
63
|
-
get isVisible(): boolean {
|
|
63
|
+
public get isVisible(): boolean {
|
|
64
64
|
return !!this.node && !this.node.classList.contains("hide");
|
|
65
65
|
}
|
|
66
66
|
}
|