selective-ui 1.0.2 → 1.0.4

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.
Files changed (67) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +7 -2
  3. package/dist/selective-ui.css +567 -567
  4. package/dist/selective-ui.css.map +1 -1
  5. package/dist/selective-ui.esm.js +6186 -6047
  6. package/dist/selective-ui.esm.js.map +1 -1
  7. package/dist/selective-ui.esm.min.js +1 -1
  8. package/dist/selective-ui.esm.min.js.br +0 -0
  9. package/dist/selective-ui.min.js +2 -2
  10. package/dist/selective-ui.min.js.br +0 -0
  11. package/dist/selective-ui.umd.js +6186 -6047
  12. package/dist/selective-ui.umd.js.map +1 -1
  13. package/package.json +68 -68
  14. package/src/css/components/accessorybox.css +63 -63
  15. package/src/css/components/directive.css +19 -19
  16. package/src/css/components/empty-state.css +25 -25
  17. package/src/css/components/loading-state.css +25 -25
  18. package/src/css/components/optgroup.css +61 -61
  19. package/src/css/components/option-handle.css +33 -33
  20. package/src/css/components/option.css +129 -129
  21. package/src/css/components/placeholder.css +14 -14
  22. package/src/css/components/popup.css +38 -38
  23. package/src/css/components/searchbox.css +28 -28
  24. package/src/css/components/selectbox.css +53 -53
  25. package/src/css/index.css +74 -74
  26. package/src/js/adapter/mixed-adapter.js +434 -434
  27. package/src/js/components/accessorybox.js +124 -124
  28. package/src/js/components/directive.js +37 -37
  29. package/src/js/components/empty-state.js +67 -67
  30. package/src/js/components/loading-state.js +59 -59
  31. package/src/js/components/option-handle.js +113 -113
  32. package/src/js/components/placeholder.js +56 -56
  33. package/src/js/components/popup.js +470 -470
  34. package/src/js/components/searchbox.js +167 -167
  35. package/src/js/components/selectbox.js +749 -692
  36. package/src/js/core/base/adapter.js +162 -162
  37. package/src/js/core/base/model.js +59 -59
  38. package/src/js/core/base/recyclerview.js +82 -82
  39. package/src/js/core/base/view.js +62 -62
  40. package/src/js/core/model-manager.js +286 -286
  41. package/src/js/core/search-controller.js +603 -521
  42. package/src/js/index.js +136 -136
  43. package/src/js/models/group-model.js +142 -142
  44. package/src/js/models/option-model.js +236 -236
  45. package/src/js/services/dataset-observer.js +73 -73
  46. package/src/js/services/ea-observer.js +87 -87
  47. package/src/js/services/effector.js +403 -403
  48. package/src/js/services/refresher.js +39 -39
  49. package/src/js/services/resize-observer.js +151 -151
  50. package/src/js/services/select-observer.js +60 -60
  51. package/src/js/types/adapter.type.js +32 -32
  52. package/src/js/types/effector.type.js +23 -23
  53. package/src/js/types/ievents.type.js +10 -10
  54. package/src/js/types/libs.type.js +27 -27
  55. package/src/js/types/model.type.js +11 -11
  56. package/src/js/types/recyclerview.type.js +11 -11
  57. package/src/js/types/resize-observer.type.js +18 -18
  58. package/src/js/types/view.group.type.js +12 -12
  59. package/src/js/types/view.option.type.js +14 -14
  60. package/src/js/types/view.type.js +10 -10
  61. package/src/js/utils/guard.js +46 -46
  62. package/src/js/utils/ievents.js +83 -83
  63. package/src/js/utils/istorage.js +60 -60
  64. package/src/js/utils/libs.js +618 -618
  65. package/src/js/utils/selective.js +385 -385
  66. package/src/js/views/group-view.js +102 -102
  67. package/src/js/views/option-view.js +152 -152
@@ -1,286 +1,286 @@
1
- import { GroupModel } from "../models/group-model";
2
- import { OptionModel } from "../models/option-model";
3
- import { Adapter } from "./base/adapter";
4
-
5
- /**
6
- * @template {ModelContract<any, any>} TModel
7
- * @template {Adapter} TAdapter
8
- */
9
- export class ModelManager {
10
- /** @type {Array<GroupModel|OptionModel>} */
11
- #privModelList = [];
12
-
13
- /** @type {new (...args: any[]) => TAdapter} */
14
- #privAdapter;
15
-
16
- /** @type {TAdapter} */
17
- #privAdapterHandle;
18
-
19
- /** @type {new (...args: any[]) => RecyclerViewContract<TAdapter>} */
20
- #privRecyclerView;
21
-
22
- /** @type {RecyclerViewContract<TAdapter>} */
23
- #privRecyclerViewHandle;
24
-
25
- options = null;
26
-
27
- /**
28
- * Constructs a ModelManager with configuration options used by created models and components.
29
- *
30
- * @param {object} options - Configuration object passed to GroupModel/OptionModel and view infrastructure.
31
- */
32
- constructor(options) {
33
- this.options = options;
34
- }
35
-
36
- /**
37
- * Registers the adapter class to be used for rendering and managing models.
38
- *
39
- * @param {new (...args: any[]) => TAdapter} adapter - The adapter constructor (class) to instantiate.
40
- */
41
- setupAdapter(adapter) {
42
- this.#privAdapter = adapter;
43
- }
44
-
45
- /**
46
- * Registers the RecyclerView class responsible for hosting and updating item views.
47
- *
48
- * @param {new (...args: any[]) => RecyclerViewContract<TAdapter>} recyclerView - The recycler view constructor.
49
- */
50
- setupRecyclerView(recyclerView) {
51
- this.#privRecyclerView = recyclerView;
52
- }
53
-
54
- /**
55
- * Builds model instances (GroupModel/OptionModel) from raw <optgroup>/<option> elements.
56
- * Preserves grouping relationships and returns the structured list.
57
- *
58
- * @param {Array<HTMLOptGroupElement|HTMLOptionElement>} modelData - Parsed DOM elements from the source <select>.
59
- * @returns {Array<GroupModel|OptionModel>} - The ordered list of group and option models.
60
- */
61
- createModelResources(modelData) {
62
- this.#privModelList = [];
63
- let currentGroup = null;
64
-
65
- modelData.forEach(data => {
66
- if (data.tagName === "OPTGROUP") {
67
- currentGroup = new GroupModel(this.options, data);
68
- this.#privModelList.push(currentGroup);
69
- }
70
- else if (data.tagName === "OPTION") {
71
- const optionModel = new OptionModel(this.options, /** @type {HTMLOptionElement} */ (data));
72
-
73
- if (data["__parentGroup"] && currentGroup &&
74
- data["__parentGroup"] === currentGroup.targetElement) {
75
- currentGroup.addItem(optionModel);
76
- optionModel.group = currentGroup;
77
- } else {
78
- this.#privModelList.push(optionModel);
79
- currentGroup = null;
80
- }
81
- }
82
- });
83
-
84
- return this.#privModelList;
85
- }
86
-
87
- /**
88
- * Replaces the current model list with new data and syncs it into the adapter,
89
- * then refreshes the view to reflect changes.
90
- *
91
- * @param {Array<HTMLOptGroupElement|HTMLOptionElement>} modelData - New source elements to rebuild models from.
92
- */
93
- replace(modelData) {
94
- this.createModelResources(modelData);
95
-
96
- if (this.#privAdapterHandle) {
97
- this.#privAdapterHandle.syncFromSource(this.#privModelList);
98
- }
99
-
100
- this.refresh();
101
- }
102
-
103
- /**
104
- * Requests a view refresh if an adapter has been initialized,
105
- * typically used after external updates to model data.
106
- */
107
- notify() {
108
- if (!this.#privAdapterHandle) return;
109
-
110
- this.refresh();
111
- }
112
-
113
- /**
114
- * Initializes adapter and recycler view instances, attaches them to a container element,
115
- * and applies optional configuration overrides for adapter and recyclerView.
116
- *
117
- * @param {HTMLElement} viewElement - The container element where items will be rendered.
118
- * @param {object} [adapterOpt={}] - Optional properties to merge into the adapter instance.
119
- * @param {object} [recyclerViewOpt={}] - Optional properties to merge into the recycler view instance.
120
- */
121
- load(viewElement, adapterOpt = {}, recyclerViewOpt = {}) {
122
- this.#privAdapterHandle = new this.#privAdapter(this.#privModelList);
123
- Object.assign(this.#privAdapterHandle, adapterOpt);
124
-
125
- this.#privRecyclerViewHandle = new this.#privRecyclerView(viewElement);
126
- this.#privRecyclerViewHandle.setAdapter(this.#privAdapterHandle);
127
- Object.assign(this.#privRecyclerViewHandle, recyclerViewOpt);
128
- }
129
-
130
- /**
131
- * Diffs existing models against new <optgroup>/<option> data to update in place:
132
- * reuses existing models when possible, updates positions and group membership,
133
- * removes stale views, and notifies adapter and listeners about updates.
134
- *
135
- * @param {Array<HTMLOptGroupElement|HTMLOptionElement>} modelData - Fresh DOM elements reflecting the latest state.
136
- */
137
- update(modelData) {
138
- const oldModels = this.#privModelList;
139
- const newModels = [];
140
-
141
- const oldGroupMap = new Map();
142
- const oldOptionMap = new Map();
143
-
144
- oldModels.forEach(model => {
145
- if (model instanceof GroupModel) {
146
- oldGroupMap.set(model.label, model);
147
- } else if (model instanceof OptionModel) {
148
- oldOptionMap.set(model.value, model);
149
- }
150
- });
151
-
152
- let currentGroup = null;
153
- let position = 0;
154
-
155
- modelData.forEach((data, index) => {
156
- if (data.tagName === "OPTGROUP") {
157
- let dataVset = /** @type {HTMLOptGroupElement} */ (data);
158
- const existingGroup = oldGroupMap.get(dataVset.label);
159
-
160
- if (existingGroup) {
161
- existingGroup.update(dataVset);
162
- existingGroup.position = position;
163
- existingGroup.items = [];
164
- currentGroup = existingGroup;
165
- newModels.push(existingGroup);
166
- oldGroupMap.delete(dataVset.label);
167
- } else {
168
- currentGroup = new GroupModel(this.options, dataVset);
169
- currentGroup.position = position;
170
- newModels.push(currentGroup);
171
- }
172
- position++;
173
- }
174
- else if (data.tagName === "OPTION") {
175
- let dataVset = /** @type {HTMLOptionElement} */ (data);
176
- const existingOption = oldOptionMap.get(dataVset.value);
177
-
178
- if (existingOption) {
179
- existingOption.update(dataVset);
180
- existingOption.position = position;
181
-
182
- if (dataVset["__parentGroup"] && currentGroup) {
183
- currentGroup.addItem(existingOption);
184
- existingOption.group = currentGroup;
185
- } else {
186
- existingOption.group = null;
187
- newModels.push(existingOption);
188
- }
189
-
190
- oldOptionMap.delete(dataVset.value);
191
- } else {
192
- const newOption = new OptionModel(this.options, dataVset);
193
- newOption.position = position;
194
-
195
- if (dataVset["__parentGroup"] && currentGroup) {
196
- currentGroup.addItem(newOption);
197
- newOption.group = currentGroup;
198
- } else {
199
- newModels.push(newOption);
200
- }
201
- }
202
- position++;
203
- }
204
- });
205
-
206
- oldGroupMap.forEach(removedGroup => {
207
- if (removedGroup.view) {
208
- removedGroup.view.getView()?.remove();
209
- }
210
- });
211
-
212
- oldOptionMap.forEach(removedOption => {
213
- if (removedOption.view) {
214
- removedOption.view.getView()?.remove();
215
- }
216
- });
217
-
218
- this.#privModelList = newModels;
219
-
220
- if (this.#privAdapterHandle) {
221
- this.#privAdapterHandle.updateData(this.#privModelList);
222
- }
223
-
224
- this.onUpdated();
225
- this.refresh();
226
- }
227
-
228
- /**
229
- * Hook invoked after the manager completes an update or refresh cycle.
230
- * Override to run side effects (e.g., layout adjustments or analytics).
231
- */
232
- onUpdated() { }
233
-
234
- /**
235
- * Instructs the adapter to temporarily skip event handling (e.g., during batch updates).
236
- *
237
- * @param {boolean} value - True to skip events; false to restore normal behavior.
238
- */
239
- skipEvent(value) {
240
- this.#privAdapterHandle.isSkipEvent = value;
241
- }
242
-
243
- /**
244
- * Re-renders the recycler view if present and invokes the post-refresh hook.
245
- * No-op if the recycler view is not initialized.
246
- */
247
- refresh() {
248
- if (!this.#privRecyclerViewHandle) return;
249
- this.#privRecyclerViewHandle.refresh();
250
- this.onUpdated();
251
- }
252
-
253
- /**
254
- * Returns handles to the current resources, including the model list,
255
- * adapter instance, and recycler view instance.
256
- *
257
- * @returns {{modelList: (GroupModel|OptionModel)[], adapter: TAdapter, recyclerView: RecyclerViewContract<TAdapter>}}
258
- */
259
- getResources() {
260
- return {
261
- modelList: this.#privModelList,
262
- adapter: this.#privAdapterHandle,
263
- recyclerView: this.#privRecyclerViewHandle
264
- };
265
- }
266
-
267
- /**
268
- * Triggers the adapter's pre-change pipeline for a named event,
269
- * enabling observers to react before a change is applied.
270
- *
271
- * @param {string} event_name - The event or property name (e.g., "items", "select").
272
- */
273
- triggerChanging(event_name) {
274
- this.#privAdapterHandle.changingProp(event_name);
275
- }
276
-
277
- /**
278
- * Triggers the adapter's post-change pipeline for a named event,
279
- * notifying observers after a change has been applied.
280
- *
281
- * @param {string} event_name - The event or property name (e.g., "items", "select").
282
- */
283
- triggerChanged(event_name) {
284
- this.#privAdapterHandle.changeProp(event_name);
285
- }
286
- }
1
+ import { GroupModel } from "../models/group-model";
2
+ import { OptionModel } from "../models/option-model";
3
+ import { Adapter } from "./base/adapter";
4
+
5
+ /**
6
+ * @template {ModelContract<any, any>} TModel
7
+ * @template {Adapter} TAdapter
8
+ */
9
+ export class ModelManager {
10
+ /** @type {Array<GroupModel|OptionModel>} */
11
+ #privModelList = [];
12
+
13
+ /** @type {new (...args: any[]) => TAdapter} */
14
+ #privAdapter;
15
+
16
+ /** @type {TAdapter} */
17
+ #privAdapterHandle;
18
+
19
+ /** @type {new (...args: any[]) => RecyclerViewContract<TAdapter>} */
20
+ #privRecyclerView;
21
+
22
+ /** @type {RecyclerViewContract<TAdapter>} */
23
+ #privRecyclerViewHandle;
24
+
25
+ options = null;
26
+
27
+ /**
28
+ * Constructs a ModelManager with configuration options used by created models and components.
29
+ *
30
+ * @param {object} options - Configuration object passed to GroupModel/OptionModel and view infrastructure.
31
+ */
32
+ constructor(options) {
33
+ this.options = options;
34
+ }
35
+
36
+ /**
37
+ * Registers the adapter class to be used for rendering and managing models.
38
+ *
39
+ * @param {new (...args: any[]) => TAdapter} adapter - The adapter constructor (class) to instantiate.
40
+ */
41
+ setupAdapter(adapter) {
42
+ this.#privAdapter = adapter;
43
+ }
44
+
45
+ /**
46
+ * Registers the RecyclerView class responsible for hosting and updating item views.
47
+ *
48
+ * @param {new (...args: any[]) => RecyclerViewContract<TAdapter>} recyclerView - The recycler view constructor.
49
+ */
50
+ setupRecyclerView(recyclerView) {
51
+ this.#privRecyclerView = recyclerView;
52
+ }
53
+
54
+ /**
55
+ * Builds model instances (GroupModel/OptionModel) from raw <optgroup>/<option> elements.
56
+ * Preserves grouping relationships and returns the structured list.
57
+ *
58
+ * @param {Array<HTMLOptGroupElement|HTMLOptionElement>} modelData - Parsed DOM elements from the source <select>.
59
+ * @returns {Array<GroupModel|OptionModel>} - The ordered list of group and option models.
60
+ */
61
+ createModelResources(modelData) {
62
+ this.#privModelList = [];
63
+ let currentGroup = null;
64
+
65
+ modelData.forEach(data => {
66
+ if (data.tagName === "OPTGROUP") {
67
+ currentGroup = new GroupModel(this.options, data);
68
+ this.#privModelList.push(currentGroup);
69
+ }
70
+ else if (data.tagName === "OPTION") {
71
+ const optionModel = new OptionModel(this.options, /** @type {HTMLOptionElement} */ (data));
72
+
73
+ if (data["__parentGroup"] && currentGroup &&
74
+ data["__parentGroup"] === currentGroup.targetElement) {
75
+ currentGroup.addItem(optionModel);
76
+ optionModel.group = currentGroup;
77
+ } else {
78
+ this.#privModelList.push(optionModel);
79
+ currentGroup = null;
80
+ }
81
+ }
82
+ });
83
+
84
+ return this.#privModelList;
85
+ }
86
+
87
+ /**
88
+ * Replaces the current model list with new data and syncs it into the adapter,
89
+ * then refreshes the view to reflect changes.
90
+ *
91
+ * @param {Array<HTMLOptGroupElement|HTMLOptionElement>} modelData - New source elements to rebuild models from.
92
+ */
93
+ replace(modelData) {
94
+ this.createModelResources(modelData);
95
+
96
+ if (this.#privAdapterHandle) {
97
+ this.#privAdapterHandle.syncFromSource(this.#privModelList);
98
+ }
99
+
100
+ this.refresh();
101
+ }
102
+
103
+ /**
104
+ * Requests a view refresh if an adapter has been initialized,
105
+ * typically used after external updates to model data.
106
+ */
107
+ notify() {
108
+ if (!this.#privAdapterHandle) return;
109
+
110
+ this.refresh();
111
+ }
112
+
113
+ /**
114
+ * Initializes adapter and recycler view instances, attaches them to a container element,
115
+ * and applies optional configuration overrides for adapter and recyclerView.
116
+ *
117
+ * @param {HTMLElement} viewElement - The container element where items will be rendered.
118
+ * @param {object} [adapterOpt={}] - Optional properties to merge into the adapter instance.
119
+ * @param {object} [recyclerViewOpt={}] - Optional properties to merge into the recycler view instance.
120
+ */
121
+ load(viewElement, adapterOpt = {}, recyclerViewOpt = {}) {
122
+ this.#privAdapterHandle = new this.#privAdapter(this.#privModelList);
123
+ Object.assign(this.#privAdapterHandle, adapterOpt);
124
+
125
+ this.#privRecyclerViewHandle = new this.#privRecyclerView(viewElement);
126
+ this.#privRecyclerViewHandle.setAdapter(this.#privAdapterHandle);
127
+ Object.assign(this.#privRecyclerViewHandle, recyclerViewOpt);
128
+ }
129
+
130
+ /**
131
+ * Diffs existing models against new <optgroup>/<option> data to update in place:
132
+ * reuses existing models when possible, updates positions and group membership,
133
+ * removes stale views, and notifies adapter and listeners about updates.
134
+ *
135
+ * @param {Array<HTMLOptGroupElement|HTMLOptionElement>} modelData - Fresh DOM elements reflecting the latest state.
136
+ */
137
+ update(modelData) {
138
+ const oldModels = this.#privModelList;
139
+ const newModels = [];
140
+
141
+ const oldGroupMap = new Map();
142
+ const oldOptionMap = new Map();
143
+
144
+ oldModels.forEach(model => {
145
+ if (model instanceof GroupModel) {
146
+ oldGroupMap.set(model.label, model);
147
+ } else if (model instanceof OptionModel) {
148
+ oldOptionMap.set(model.value, model);
149
+ }
150
+ });
151
+
152
+ let currentGroup = null;
153
+ let position = 0;
154
+
155
+ modelData.forEach((data, index) => {
156
+ if (data.tagName === "OPTGROUP") {
157
+ let dataVset = /** @type {HTMLOptGroupElement} */ (data);
158
+ const existingGroup = oldGroupMap.get(dataVset.label);
159
+
160
+ if (existingGroup) {
161
+ existingGroup.update(dataVset);
162
+ existingGroup.position = position;
163
+ existingGroup.items = [];
164
+ currentGroup = existingGroup;
165
+ newModels.push(existingGroup);
166
+ oldGroupMap.delete(dataVset.label);
167
+ } else {
168
+ currentGroup = new GroupModel(this.options, dataVset);
169
+ currentGroup.position = position;
170
+ newModels.push(currentGroup);
171
+ }
172
+ position++;
173
+ }
174
+ else if (data.tagName === "OPTION") {
175
+ let dataVset = /** @type {HTMLOptionElement} */ (data);
176
+ const existingOption = oldOptionMap.get(dataVset.value);
177
+
178
+ if (existingOption) {
179
+ existingOption.update(dataVset);
180
+ existingOption.position = position;
181
+
182
+ if (dataVset["__parentGroup"] && currentGroup) {
183
+ currentGroup.addItem(existingOption);
184
+ existingOption.group = currentGroup;
185
+ } else {
186
+ existingOption.group = null;
187
+ newModels.push(existingOption);
188
+ }
189
+
190
+ oldOptionMap.delete(dataVset.value);
191
+ } else {
192
+ const newOption = new OptionModel(this.options, dataVset);
193
+ newOption.position = position;
194
+
195
+ if (dataVset["__parentGroup"] && currentGroup) {
196
+ currentGroup.addItem(newOption);
197
+ newOption.group = currentGroup;
198
+ } else {
199
+ newModels.push(newOption);
200
+ }
201
+ }
202
+ position++;
203
+ }
204
+ });
205
+
206
+ oldGroupMap.forEach(removedGroup => {
207
+ if (removedGroup.view) {
208
+ removedGroup.view.getView()?.remove();
209
+ }
210
+ });
211
+
212
+ oldOptionMap.forEach(removedOption => {
213
+ if (removedOption.view) {
214
+ removedOption.view.getView()?.remove();
215
+ }
216
+ });
217
+
218
+ this.#privModelList = newModels;
219
+
220
+ if (this.#privAdapterHandle) {
221
+ this.#privAdapterHandle.updateData(this.#privModelList);
222
+ }
223
+
224
+ this.onUpdated();
225
+ this.refresh();
226
+ }
227
+
228
+ /**
229
+ * Hook invoked after the manager completes an update or refresh cycle.
230
+ * Override to run side effects (e.g., layout adjustments or analytics).
231
+ */
232
+ onUpdated() { }
233
+
234
+ /**
235
+ * Instructs the adapter to temporarily skip event handling (e.g., during batch updates).
236
+ *
237
+ * @param {boolean} value - True to skip events; false to restore normal behavior.
238
+ */
239
+ skipEvent(value) {
240
+ this.#privAdapterHandle.isSkipEvent = value;
241
+ }
242
+
243
+ /**
244
+ * Re-renders the recycler view if present and invokes the post-refresh hook.
245
+ * No-op if the recycler view is not initialized.
246
+ */
247
+ refresh() {
248
+ if (!this.#privRecyclerViewHandle) return;
249
+ this.#privRecyclerViewHandle.refresh();
250
+ this.onUpdated();
251
+ }
252
+
253
+ /**
254
+ * Returns handles to the current resources, including the model list,
255
+ * adapter instance, and recycler view instance.
256
+ *
257
+ * @returns {{modelList: (GroupModel|OptionModel)[], adapter: TAdapter, recyclerView: RecyclerViewContract<TAdapter>}}
258
+ */
259
+ getResources() {
260
+ return {
261
+ modelList: this.#privModelList,
262
+ adapter: this.#privAdapterHandle,
263
+ recyclerView: this.#privRecyclerViewHandle
264
+ };
265
+ }
266
+
267
+ /**
268
+ * Triggers the adapter's pre-change pipeline for a named event,
269
+ * enabling observers to react before a change is applied.
270
+ *
271
+ * @param {string} event_name - The event or property name (e.g., "items", "select").
272
+ */
273
+ triggerChanging(event_name) {
274
+ this.#privAdapterHandle.changingProp(event_name);
275
+ }
276
+
277
+ /**
278
+ * Triggers the adapter's post-change pipeline for a named event,
279
+ * notifying observers after a change has been applied.
280
+ *
281
+ * @param {string} event_name - The event or property name (e.g., "items", "select").
282
+ */
283
+ triggerChanged(event_name) {
284
+ this.#privAdapterHandle.changeProp(event_name);
285
+ }
286
+ }