selective-ui 1.2.3 → 1.2.5

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 (59) hide show
  1. package/dist/selective-ui.css.map +1 -1
  2. package/dist/selective-ui.esm.js +5462 -1043
  3. package/dist/selective-ui.esm.js.map +1 -1
  4. package/dist/selective-ui.esm.min.js +2 -2
  5. package/dist/selective-ui.esm.min.js.br +0 -0
  6. package/dist/selective-ui.min.js +2 -2
  7. package/dist/selective-ui.min.js.br +0 -0
  8. package/dist/selective-ui.umd.js +5463 -1044
  9. package/dist/selective-ui.umd.js.map +1 -1
  10. package/package.json +1 -1
  11. package/src/ts/adapter/mixed-adapter.ts +312 -65
  12. package/src/ts/components/accessorybox.ts +248 -28
  13. package/src/ts/components/directive.ts +91 -11
  14. package/src/ts/components/option-handle.ts +191 -28
  15. package/src/ts/components/placeholder.ts +111 -16
  16. package/src/ts/components/popup/empty-state.ts +162 -0
  17. package/src/ts/components/popup/loading-state.ts +160 -0
  18. package/src/ts/components/{popup.ts → popup/popup.ts} +167 -71
  19. package/src/ts/components/searchbox.ts +225 -20
  20. package/src/ts/components/selectbox.ts +498 -120
  21. package/src/ts/core/base/adapter.ts +200 -53
  22. package/src/ts/core/base/fenwick.ts +147 -0
  23. package/src/ts/core/base/lifecycle.ts +258 -0
  24. package/src/ts/core/base/model.ts +120 -31
  25. package/src/ts/core/base/recyclerview.ts +55 -18
  26. package/src/ts/core/base/view.ts +87 -19
  27. package/src/ts/core/base/virtual-recyclerview.ts +475 -202
  28. package/src/ts/core/model-manager.ts +166 -85
  29. package/src/ts/core/search-controller.ts +236 -38
  30. package/src/ts/global.ts +6 -6
  31. package/src/ts/index.ts +6 -6
  32. package/src/ts/models/group-model.ts +159 -32
  33. package/src/ts/models/option-model.ts +213 -54
  34. package/src/ts/services/dataset-observer.ts +72 -10
  35. package/src/ts/services/ea-observer.ts +92 -15
  36. package/src/ts/services/effector.ts +181 -32
  37. package/src/ts/services/refresher.ts +30 -6
  38. package/src/ts/services/resize-observer.ts +132 -15
  39. package/src/ts/services/select-observer.ts +115 -50
  40. package/src/ts/types/components/searchbox.type.ts +1 -1
  41. package/src/ts/types/core/base/adapter.type.ts +2 -1
  42. package/src/ts/types/core/base/lifecycle.type.ts +62 -0
  43. package/src/ts/types/core/base/model.type.ts +3 -1
  44. package/src/ts/types/core/base/recyclerview.type.ts +2 -8
  45. package/src/ts/types/core/base/view.type.ts +36 -24
  46. package/src/ts/types/utils/ievents.type.ts +6 -1
  47. package/src/ts/utils/callback-scheduler.ts +112 -34
  48. package/src/ts/utils/ievents.ts +91 -29
  49. package/src/ts/utils/istorage.ts +1 -1
  50. package/src/ts/utils/selective.ts +474 -88
  51. package/src/ts/views/group-view.ts +170 -21
  52. package/src/ts/views/option-view.ts +349 -68
  53. package/src/ts/components/empty-state.ts +0 -68
  54. package/src/ts/components/loading-state.ts +0 -66
  55. /package/src/css/components/{empty-state.css → popup/empty-state.css} +0 -0
  56. /package/src/css/components/{loading-state.css → popup/loading-state.css} +0 -0
  57. /package/src/css/components/{popup.css → popup/popup.css} +0 -0
  58. /package/src/css/{components/optgroup.css → views/group-view.css} +0 -0
  59. /package/src/css/{components/option.css → views/option-view.css} +0 -0
@@ -3,16 +3,97 @@ import { Libs } from "../utils/libs";
3
3
  import type { GroupViewTags, GroupViewResult } from "../types/views/view.group.type";
4
4
 
5
5
  /**
6
- * @extends {View<GroupViewTags>}
6
+ * GroupView
7
+ *
8
+ * View implementation for rendering grouped collections of selectable items.
9
+ *
10
+ * ### Responsibility
11
+ * - Renders a semantic group structure: header (label) + items container.
12
+ * - Manages group-level visibility based on child item state.
13
+ * - Supports collapse/expand interactions with accessibility annotations.
14
+ * - Provides typed access to DOM structure via {@link view}.
15
+ *
16
+ * ### Structure
17
+ * ```
18
+ * GroupView (root)
19
+ * ├─ GroupHeader (label, role="presentation")
20
+ * └─ GroupItems (container, role="group")
21
+ * ```
22
+ *
23
+ * ### Lifecycle (View-based FSM)
24
+ * - **Construction**: Accepts parent container, transitions `NEW → INITIALIZED`.
25
+ * - **{@link mount}**: Creates DOM structure, appends to parent, transitions `INITIALIZED → MOUNTED`.
26
+ * - **{@link update}**: Refreshes group header label, transitions `MOUNTED → UPDATED → MOUNTED`.
27
+ * - **{@link destroy}**: Removes DOM nodes, transitions to `DESTROYED`.
28
+ *
29
+ * ### Visibility semantics
30
+ * - {@link updateVisibility} hides the entire group when all child items are hidden.
31
+ * - Checks for `"hide"` class on children (does not inspect `display` or `visibility` styles).
32
+ *
33
+ * ### Accessibility
34
+ * - Root container: `role="group"`, `aria-labelledby` points to header.
35
+ * - Header: `role="presentation"`, unique ID for labeling.
36
+ * - Items container: `role="group"` (nested group).
37
+ * - Collapse state: `aria-expanded` attribute on header (managed by {@link setCollapsed}).
38
+ *
39
+ * ### DOM side effects
40
+ * - {@link mount} creates and appends DOM structure.
41
+ * - {@link updateLabel} mutates header `textContent`.
42
+ * - {@link setCollapsed} toggles CSS classes and ARIA attributes.
43
+ * - {@link updateVisibility} toggles `"hide"` class on root.
44
+ *
45
+ * ### No-op / Idempotency
46
+ * - {@link updateLabel}, {@link updateVisibility}, {@link setCollapsed} are no-ops if not mounted (early return guards).
47
+ * - Safe to call multiple times without side effects beyond DOM state updates.
48
+ *
49
+ * @extends View<GroupViewTags>
50
+ * @template GroupViewTags - Type descriptor for the group's DOM structure.
51
+ * @see {@link GroupViewResult}
52
+ * @see {@link View}
7
53
  */
8
54
  export class GroupView extends View<GroupViewTags> {
55
+
56
+ /**
57
+ * Strongly-typed reference to the mounted group view structure.
58
+ *
59
+ * Structure:
60
+ * - **view**: Root container element.
61
+ * - **tags**: Named references to header and items container.
62
+ *
63
+ * Lifecycle:
64
+ * - `null` until {@link mount} completes.
65
+ * - Cleared during {@link destroy}.
66
+ *
67
+ * @public
68
+ */
9
69
  public view: GroupViewResult | null = null;
10
70
 
11
71
  /**
12
- * Renders the group view structure (header + items container), sets ARIA attributes,
13
- * and appends the root element to the parent container.
72
+ * Mounts the group view into the DOM.
73
+ *
74
+ * Creation flow:
75
+ * 1. Generates unique group ID (7-character random string).
76
+ * 2. Creates DOM structure via {@link Libs.mountView}:
77
+ * - Root: `<div role="group" aria-labelledby="seui-{id}-header">`
78
+ * - Header: `<div role="presentation" id="seui-{id}-header">`
79
+ * - Items: `<div role="group">` (nested group for child items)
80
+ * 3. Appends root to {@link parent} container.
81
+ * 4. Transitions `INITIALIZED → MOUNTED` via `super.mount()`.
82
+ *
83
+ * Accessibility setup:
84
+ * - Root `aria-labelledby` associates group with header text.
85
+ * - Header `role="presentation"` hides it from navigation (purely visual label).
86
+ * - Items container `role="group"` creates semantic boundary for children.
87
+ *
88
+ * Postcondition:
89
+ * - {@link view} is populated with typed DOM references.
90
+ *
91
+ * @public
92
+ * @returns {void}
93
+ * @override
94
+ * @throws {Error} If {@link parent} is null (should never occur due to base `View` constructor).
14
95
  */
15
- public render(): void {
96
+ public override mount(): void {
16
97
  const group_id = Libs.randomString(7);
17
98
 
18
99
  this.view = Libs.mountView<GroupViewTags>({
@@ -44,63 +125,131 @@ export class GroupView extends View<GroupViewTags> {
44
125
  },
45
126
  }) as GroupViewResult;
46
127
 
47
- // Parent is guaranteed by base constructor.
128
+ // Parent is guaranteed to exist by the base View constructor.
48
129
  this.parent!.appendChild(this.view.view);
130
+
131
+ super.mount();
49
132
  }
50
133
 
51
134
  /**
52
- * Performs a lightweight refresh of the view (currently updates the header label).
135
+ * Updates the group view in response to state changes.
136
+ *
137
+ * Behavior:
138
+ * - Refreshes the group header label via {@link updateLabel}.
139
+ * - Transitions `MOUNTED → UPDATED → MOUNTED` via `super.update()`.
140
+ *
141
+ * Notes:
142
+ * - Currently performs only label refresh; extend for additional update logic.
143
+ * - Does **not** update visibility or collapse state automatically.
144
+ *
145
+ * @public
146
+ * @returns {void}
147
+ * @override
53
148
  */
54
- public update(): void {
149
+ public override update(): void {
55
150
  this.updateLabel();
151
+ super.update();
56
152
  }
57
153
 
58
154
  /**
59
- * Updates the group header text content if a label is provided.
155
+ * Updates the text content of the group header.
156
+ *
157
+ * Behavior:
158
+ * - No-op if not mounted ({@link view} is `null`).
159
+ * - If `label` is `null`, preserves existing header text.
160
+ * - Otherwise, replaces header `textContent` with new label.
161
+ *
162
+ * Notes:
163
+ * - Does **not** escape HTML (uses `textContent`, not `innerHTML`).
164
+ * - Safe to call multiple times with same value (idempotent).
60
165
  *
61
- * @param {string|null} [label=null] - The new label to display; if null, keeps current.
166
+ * @public
167
+ * @param {string | null} [label=null] - New label to display; `null` preserves current label.
168
+ * @returns {void}
62
169
  */
63
170
  public updateLabel(label: string | null = null): void {
64
171
  if (!this.view) return;
172
+
65
173
  const headerEl = this.view.tags.GroupHeader;
66
- if (label !== null) headerEl.textContent = label;
174
+ if (label !== null) {
175
+ headerEl.textContent = label;
176
+ }
67
177
  }
68
178
 
69
179
  /**
70
- * Returns the container element that holds all option/item views in this group.
180
+ * Returns the container element for child item views.
181
+ *
182
+ * Usage:
183
+ * - Caller appends `OptionView` or other child views to this container.
184
+ * - Container provides semantic grouping (`role="group"`).
71
185
  *
72
- * @returns {HTMLDivElement} - The items container element.
186
+ * @public
187
+ * @returns {HTMLDivElement} The items container element.
188
+ * @throws {Error} If the view has not been mounted yet ({@link view} is `null`).
73
189
  */
74
190
  public getItemsContainer(): HTMLDivElement {
75
- if (!this.view) throw new Error("GroupView is not rendered.");
191
+ if (!this.view) {
192
+ throw new Error("GroupView has not been rendered.");
193
+ }
76
194
  return this.view.tags.GroupItems;
77
195
  }
78
196
 
79
197
  /**
80
- * Toggles the group's visibility based on whether any child item is visible.
81
- * Hides the entire group when all children are hidden.
198
+ * Updates the group's visibility based on child item state.
199
+ *
200
+ * Visibility rules:
201
+ * - Iterates through direct children of the items container.
202
+ * - Counts children **without** the `"hide"` CSS class.
203
+ * - Toggles `"hide"` class on root container:
204
+ * - **Added** if all children are hidden (zero visible).
205
+ * - **Removed** if any child is visible.
206
+ *
207
+ * Notes:
208
+ * - No-op if not mounted ({@link view} is `null`).
209
+ * - Only checks for `"hide"` class; does **not** inspect `display` or `visibility` styles.
210
+ * - Safe to call repeatedly (idempotent based on current child state).
211
+ *
212
+ * @public
213
+ * @returns {void}
82
214
  */
83
215
  public updateVisibility(): void {
84
216
  if (!this.view) return;
85
217
 
86
218
  const items = this.view.tags.GroupItems;
87
219
  const visibleItems = Array.from(items.children).filter(
88
- (child) => !child.classList.contains("hide")
220
+ child => !child.classList.contains("hide")
89
221
  );
90
222
 
91
- const isVisible = visibleItems.length > 0;
92
- this.view.view.classList.toggle("hide", !isVisible);
223
+ this.view.view.classList.toggle("hide", visibleItems.length === 0);
93
224
  }
94
225
 
95
226
  /**
96
- * Sets the collapsed state on the group and updates ARIA attributes accordingly.
227
+ * Sets the collapsed/expanded state of the group.
228
+ *
229
+ * State updates:
230
+ * - **CSS**: Toggles `"collapsed"` class on root container.
231
+ * - **ARIA**: Sets `aria-expanded` attribute on header (`"true"` or `"false"`).
97
232
  *
98
- * @param {boolean} collapsed - True to collapse; false to expand.
233
+ * Visual effects:
234
+ * - CSS class typically controls item container visibility (via stylesheet).
235
+ * - ARIA attribute communicates state to assistive technologies.
236
+ *
237
+ * Notes:
238
+ * - No-op if not mounted ({@link view} is `null`).
239
+ * - Does **not** animate or transition; relies on CSS for presentation.
240
+ * - Safe to call with same value repeatedly (idempotent).
241
+ *
242
+ * @public
243
+ * @param {boolean} collapsed - `true` to collapse the group; `false` to expand.
244
+ * @returns {void}
99
245
  */
100
246
  public setCollapsed(collapsed: boolean): void {
101
247
  if (!this.view) return;
102
248
 
103
249
  this.view.view.classList.toggle("collapsed", collapsed);
104
- this.view.tags.GroupHeader.setAttribute("aria-expanded", collapsed ? "false" : "true");
250
+ this.view.tags.GroupHeader.setAttribute(
251
+ "aria-expanded",
252
+ collapsed ? "false" : "true"
253
+ );
105
254
  }
106
255
  }