selective-ui 1.2.4 → 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 (44) hide show
  1. package/dist/selective-ui.esm.js +4174 -1237
  2. package/dist/selective-ui.esm.js.map +1 -1
  3. package/dist/selective-ui.esm.min.js +2 -2
  4. package/dist/selective-ui.esm.min.js.br +0 -0
  5. package/dist/selective-ui.min.js +2 -2
  6. package/dist/selective-ui.min.js.br +0 -0
  7. package/dist/selective-ui.umd.js +4175 -1238
  8. package/dist/selective-ui.umd.js.map +1 -1
  9. package/package.json +1 -1
  10. package/src/ts/adapter/mixed-adapter.ts +247 -91
  11. package/src/ts/components/accessorybox.ts +164 -67
  12. package/src/ts/components/directive.ts +53 -24
  13. package/src/ts/components/option-handle.ts +121 -54
  14. package/src/ts/components/placeholder.ts +70 -32
  15. package/src/ts/components/popup/empty-state.ts +68 -32
  16. package/src/ts/components/popup/loading-state.ts +70 -30
  17. package/src/ts/components/popup/popup.ts +0 -1
  18. package/src/ts/components/searchbox.ts +185 -46
  19. package/src/ts/components/selectbox.ts +309 -30
  20. package/src/ts/core/base/adapter.ts +158 -77
  21. package/src/ts/core/base/fenwick.ts +147 -0
  22. package/src/ts/core/base/lifecycle.ts +118 -35
  23. package/src/ts/core/base/model.ts +94 -36
  24. package/src/ts/core/base/recyclerview.ts +0 -1
  25. package/src/ts/core/base/view.ts +54 -23
  26. package/src/ts/core/base/virtual-recyclerview.ts +360 -278
  27. package/src/ts/core/model-manager.ts +162 -81
  28. package/src/ts/core/search-controller.ts +164 -91
  29. package/src/ts/global.ts +1 -1
  30. package/src/ts/index.ts +1 -1
  31. package/src/ts/models/group-model.ts +138 -32
  32. package/src/ts/models/option-model.ts +184 -48
  33. package/src/ts/services/dataset-observer.ts +72 -10
  34. package/src/ts/services/ea-observer.ts +87 -10
  35. package/src/ts/services/effector.ts +181 -32
  36. package/src/ts/services/refresher.ts +30 -6
  37. package/src/ts/services/resize-observer.ts +132 -15
  38. package/src/ts/services/select-observer.ts +115 -50
  39. package/src/ts/types/utils/ievents.type.ts +6 -1
  40. package/src/ts/utils/callback-scheduler.ts +112 -34
  41. package/src/ts/utils/ievents.ts +91 -29
  42. package/src/ts/utils/selective.ts +330 -61
  43. package/src/ts/views/group-view.ts +137 -26
  44. package/src/ts/views/option-view.ts +262 -50
@@ -1,32 +1,97 @@
1
-
2
1
  import { View } from "../core/base/view";
3
2
  import { Libs } from "../utils/libs";
4
3
  import type { GroupViewTags, GroupViewResult } from "../types/views/view.group.type";
5
4
 
6
5
  /**
7
- * View implementation responsible for rendering and managing
8
- * a grouped collection of selectable items.
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).
9
32
  *
10
- * The group consists of:
11
- * - A header element (label)
12
- * - A container holding child item views
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.
13
48
  *
14
49
  * @extends View<GroupViewTags>
50
+ * @template GroupViewTags - Type descriptor for the group's DOM structure.
51
+ * @see {@link GroupViewResult}
52
+ * @see {@link View}
15
53
  */
16
54
  export class GroupView extends View<GroupViewTags> {
17
55
 
18
56
  /**
19
57
  * Strongly-typed reference to the mounted group view structure.
20
- * Will be null until the view has been mounted.
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
21
68
  */
22
69
  public view: GroupViewResult | null = null;
23
70
 
24
71
  /**
25
72
  * Mounts the group view into the DOM.
26
73
  *
27
- * Creates the group container, header, and items wrapper,
28
- * applies required ARIA attributes, and appends the root
29
- * element to the parent container.
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).
30
95
  */
31
96
  public override mount(): void {
32
97
  const group_id = Libs.randomString(7);
@@ -67,10 +132,19 @@ export class GroupView extends View<GroupViewTags> {
67
132
  }
68
133
 
69
134
  /**
70
- * Called when the view needs to be updated.
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()`.
71
140
  *
72
- * Currently performs a lightweight refresh by updating
73
- * the group header label.
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
74
148
  */
75
149
  public override update(): void {
76
150
  this.updateLabel();
@@ -80,8 +154,18 @@ export class GroupView extends View<GroupViewTags> {
80
154
  /**
81
155
  * Updates the text content of the group header.
82
156
  *
83
- * @param label - The new label to display.
84
- * If null, the existing label is preserved.
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).
165
+ *
166
+ * @public
167
+ * @param {string | null} [label=null] - New label to display; `null` preserves current label.
168
+ * @returns {void}
85
169
  */
86
170
  public updateLabel(label: string | null = null): void {
87
171
  if (!this.view) return;
@@ -93,11 +177,15 @@ export class GroupView extends View<GroupViewTags> {
93
177
  }
94
178
 
95
179
  /**
96
- * Returns the container element that holds all item/option views
97
- * belonging to this group.
180
+ * Returns the container element for child item views.
98
181
  *
99
- * @returns The HTMLDivElement used as the items container.
100
- * @throws {Error} If the view has not been mounted yet.
182
+ * Usage:
183
+ * - Caller appends `OptionView` or other child views to this container.
184
+ * - Container provides semantic grouping (`role="group"`).
185
+ *
186
+ * @public
187
+ * @returns {HTMLDivElement} The items container element.
188
+ * @throws {Error} If the view has not been mounted yet ({@link view} is `null`).
101
189
  */
102
190
  public getItemsContainer(): HTMLDivElement {
103
191
  if (!this.view) {
@@ -107,10 +195,22 @@ export class GroupView extends View<GroupViewTags> {
107
195
  }
108
196
 
109
197
  /**
110
- * Updates the visibility of the group based on its children.
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).
111
211
  *
112
- * If all child items are hidden, the entire group
113
- * will be hidden as well.
212
+ * @public
213
+ * @returns {void}
114
214
  */
115
215
  public updateVisibility(): void {
116
216
  if (!this.view) return;
@@ -126,11 +226,22 @@ export class GroupView extends View<GroupViewTags> {
126
226
  /**
127
227
  * Sets the collapsed/expanded state of the group.
128
228
  *
129
- * This updates both:
130
- * - Visual state (CSS classes)
131
- * - Accessibility state (ARIA attributes)
229
+ * State updates:
230
+ * - **CSS**: Toggles `"collapsed"` class on root container.
231
+ * - **ARIA**: Sets `aria-expanded` attribute on header (`"true"` or `"false"`).
232
+ *
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).
132
241
  *
133
- * @param collapsed - True to collapse the group, false to expand it.
242
+ * @public
243
+ * @param {boolean} collapsed - `true` to collapse the group; `false` to expand.
244
+ * @returns {void}
134
245
  */
135
246
  public setCollapsed(collapsed: boolean): void {
136
247
  if (!this.view) return;
@@ -1,4 +1,3 @@
1
-
2
1
  import { View } from "../core/base/view";
3
2
  import { Libs } from "../utils/libs";
4
3
  import type {
@@ -9,52 +8,144 @@ import type {
9
8
  } from "../types/views/view.option.type";
10
9
 
11
10
  /**
12
- * View implementation for a single selectable option.
11
+ * OptionView
12
+ *
13
+ * View implementation for a single selectable option with reactive configuration.
14
+ *
15
+ * ### Responsibility
16
+ * - Renders an option with input (radio/checkbox) + optional image + label.
17
+ * - Supports **reactive configuration** via Proxy-based change tracking.
18
+ * - Applies **incremental DOM updates** for configuration changes (no full re-render).
19
+ * - Manages input type switching (radio ↔ checkbox) based on selection mode.
20
+ * - Dynamically creates/removes image elements when {@link hasImage} changes.
21
+ *
22
+ * ### Structure
23
+ * ```
24
+ * OptionView (root, role="option")
25
+ * ├─ OptionInput (<input type="radio|checkbox">)
26
+ * ├─ OptionImage (<img>, conditional)
27
+ * └─ OptionLabel (<label>)
28
+ * └─ LabelContent (<div>)
29
+ * ```
30
+ *
31
+ * ### Lifecycle (View-based FSM)
32
+ * - **Construction**: Calls {@link initialize}, sets up config Proxy, transitions `NEW → INITIALIZED`.
33
+ * - **{@link mount}**: Creates DOM structure based on current config, transitions `INITIALIZED → MOUNTED`.
34
+ * - **Reactive updates**: After mount, config changes trigger {@link applyPartialChange} (targeted DOM updates).
35
+ * - **{@link destroy}**: Removes DOM nodes, transitions to `DESTROYED`.
36
+ *
37
+ * ### Reactive configuration strategy
38
+ * - **{@link config}**: Internal target object (should not be mutated directly).
39
+ * - **{@link configProxy}**: Proxy wrapper; assignments trigger {@link applyPartialChange}.
40
+ * - **{@link isRendered}**: Gates partial updates (no DOM changes before initial {@link mount}).
41
+ * - **Batch updates**: {@link optionConfig} setter applies multiple changes efficiently (only diffed properties).
13
42
  *
14
- * An option may consist of:
15
- * - An input element (radio or checkbox)
16
- * - An optional image
17
- * - A label container
43
+ * ### Partial update semantics
44
+ * - **`isMultiple`**: Toggles `"multiple"` class, switches input `type` (radio checkbox).
45
+ * - **`hasImage`**: Toggles `"has-image"` class, creates/removes `<img>` element.
46
+ * - **`imagePosition`**: Replaces `image-{position}` class (top/right/bottom/left).
47
+ * - **`imageWidth/Height/BorderRadius`**: Mutates `<img>` inline styles.
48
+ * - **`labelValign/Halign`**: Replaces label alignment classes.
18
49
  *
19
- * This view supports reactive configuration changes via a Proxy,
20
- * allowing partial DOM updates without fully re-rendering the view.
50
+ * ### Image lifecycle
51
+ * - Created on-demand via {@link createImage} when `hasImage = true`.
52
+ * - Removed via `remove()` when `hasImage = false`.
53
+ * - Reference stored in `view.tags.OptionImage` (nulled after removal).
54
+ *
55
+ * ### Accessibility
56
+ * - Root: `role="option"`, `aria-selected="false"` (managed externally), `tabindex="-1"`.
57
+ * - Input: Associated with label via unique `id` / `htmlFor`.
58
+ * - Label: Clickable, triggers input selection.
59
+ *
60
+ * ### DOM side effects
61
+ * - {@link mount} creates and appends full structure.
62
+ * - {@link applyPartialChange} mutates classes, attributes, styles, or child nodes.
63
+ * - {@link createImage} inserts `<img>` element.
64
+ * - Setters ({@link isMultiple}, {@link hasImage}) trigger Proxy → DOM updates.
65
+ *
66
+ * ### No-op / Idempotency
67
+ * - {@link applyPartialChange} is no-op if view not mounted (early return guard).
68
+ * - {@link createImage} is no-op if image already exists.
69
+ * - {@link optionConfig} setter only assigns diffed properties (avoids redundant Proxy triggers).
70
+ * - Safe to call setters multiple times with same value (Proxy guards against no-op updates).
21
71
  *
22
72
  * @extends View<OptionViewTags>
73
+ * @template OptionViewTags - Type descriptor for the option's DOM structure.
74
+ * @see {@link OptionViewResult}
75
+ * @see {@link OptionConfig}
76
+ * @see {@link View}
23
77
  */
24
78
  export class OptionView extends View<OptionViewTags> {
25
79
 
26
80
  /**
27
- * Reference to the mounted option view.
28
- * Set during `onMount`; null before render.
81
+ * Strongly-typed reference to the mounted option view structure.
82
+ *
83
+ * Structure:
84
+ * - **view**: Root container element.
85
+ * - **tags**: Named references to input, image (conditional), label, label content.
86
+ *
87
+ * Lifecycle:
88
+ * - `null` until {@link mount} completes.
89
+ * - Cleared during {@link destroy}.
90
+ *
91
+ * @public
29
92
  */
30
93
  public view: OptionViewResult | null = null;
31
94
 
32
95
  /**
33
- * Internal configuration state used as the Proxy target.
34
- * Should not be mutated directly.
96
+ * Internal configuration object (Proxy target).
97
+ *
98
+ * Lifecycle:
99
+ * - Initialized during {@link initialize} with default values.
100
+ * - Mutated via {@link configProxy} Proxy trap.
101
+ *
102
+ * Notes:
103
+ * - **Should not be mutated directly**; use {@link configProxy} or typed setters.
104
+ * - Contains default values for layout, image, and alignment.
105
+ *
106
+ * @private
35
107
  */
36
108
  private config: OptionConfig | null = null;
37
109
 
38
110
  /**
39
- * Proxy wrapper around `config`.
40
- * Assigning properties on this object triggers incremental
41
- * DOM updates once the view has been rendered.
111
+ * Reactive Proxy wrapper around {@link config}.
112
+ *
113
+ * Behavior:
114
+ * - Intercepts property assignments via `set` trap.
115
+ * - Triggers {@link applyPartialChange} for diffed values when {@link isRendered} is `true`.
116
+ * - Prevents redundant DOM updates when value hasn't changed.
117
+ *
118
+ * Usage:
119
+ * - Accessed via {@link optionConfig} getter or typed setters ({@link isMultiple}, {@link hasImage}).
120
+ *
121
+ * @private
42
122
  */
43
123
  private configProxy: OptionConfig | null = null;
44
124
 
45
125
  /**
46
- * Indicates whether the initial render has been completed.
47
- * Partial DOM updates are skipped until this becomes true.
126
+ * Flag indicating whether the initial render has completed.
127
+ *
128
+ * Lifecycle:
129
+ * - `false` until {@link mount} finishes.
130
+ * - `true` afterward (enables partial updates).
131
+ *
132
+ * Purpose:
133
+ * - Gates {@link applyPartialChange} to prevent DOM mutations before structure exists.
134
+ *
135
+ * @private
48
136
  */
49
137
  private isRendered = false;
50
138
 
51
139
  /**
52
140
  * Creates a new OptionView bound to the given parent element.
53
141
  *
54
- * Initializes the internal configuration and sets up
55
- * a reactive Proxy to track and apply configuration changes.
142
+ * Initialization flow:
143
+ * 1. Calls `super(parent)` (View base constructor).
144
+ * 2. Calls {@link initialize} to set up config and Proxy.
145
+ * 3. Transitions `NEW → INITIALIZED` via `this.init()` inside {@link initialize}.
56
146
  *
57
- * @param parent - The container element that will host this option view.
147
+ * @public
148
+ * @param {HTMLElement} parent - Container element that will host this option view.
58
149
  */
59
150
  public constructor(parent: HTMLElement) {
60
151
  super(parent);
@@ -62,13 +153,31 @@ export class OptionView extends View<OptionViewTags> {
62
153
  }
63
154
 
64
155
  /**
65
- * Initializes the default configuration object and wraps it in a Proxy.
156
+ * Initializes the default configuration and sets up reactive Proxy.
157
+ *
158
+ * Configuration defaults:
159
+ * - `isMultiple`: `false` (radio mode)
160
+ * - `hasImage`: `false` (no image)
161
+ * - `imagePosition`: `"right"`
162
+ * - `imageWidth/Height`: `"60px"`
163
+ * - `imageBorderRadius`: `"4px"`
164
+ * - `labelValign/Halign`: `"center"` / `"left"`
66
165
  *
67
- * The Proxy intercepts property assignments and:
68
- * - Updates internal state
69
- * - Applies targeted DOM changes when the view is already rendered
166
+ * Proxy behavior:
167
+ * - **`set` trap**: Compares old vs new value; if different:
168
+ * 1. Updates {@link config} target.
169
+ * 2. Calls {@link applyPartialChange} if {@link isRendered} is `true`.
170
+ * - Returns `true` to indicate success.
70
171
  *
71
- * No DOM mutations occur before the initial render.
172
+ * Postcondition:
173
+ * - {@link config} and {@link configProxy} are initialized.
174
+ * - Transitions `NEW → INITIALIZED` via `this.init()`.
175
+ *
176
+ * Notes:
177
+ * - No DOM mutations occur until {@link mount} is called.
178
+ *
179
+ * @public
180
+ * @returns {void}
72
181
  */
73
182
  public initialize(): void {
74
183
  const self = this;
@@ -108,8 +217,12 @@ export class OptionView extends View<OptionViewTags> {
108
217
  /**
109
218
  * Indicates whether the option supports multiple selection.
110
219
  *
111
- * - `false`: single selection (radio)
112
- * - `true`: multiple selection (checkbox)
220
+ * Semantics:
221
+ * - `false`: Single selection mode (radio input).
222
+ * - `true`: Multiple selection mode (checkbox input).
223
+ *
224
+ * @public
225
+ * @returns {boolean} Current selection mode.
113
226
  */
114
227
  public get isMultiple(): boolean {
115
228
  return this.config!.isMultiple;
@@ -118,27 +231,44 @@ export class OptionView extends View<OptionViewTags> {
118
231
  /**
119
232
  * Enables or disables multiple selection mode.
120
233
  *
121
- * When rendered:
122
- * - Toggles the `multiple` CSS class on the root element
123
- * - Switches the input type between `radio` and `checkbox`
234
+ * Side effects (when rendered):
235
+ * - Toggles `"multiple"` CSS class on root element.
236
+ * - Switches input `type` attribute (`"radio"` `"checkbox"`).
237
+ *
238
+ * Notes:
239
+ * - Assignments trigger Proxy → {@link applyPartialChange}.
240
+ * - No-op if value hasn't changed (Proxy guards).
241
+ *
242
+ * @public
243
+ * @param {boolean} value - `true` for multiple selection; `false` for single.
124
244
  */
125
245
  public set isMultiple(value: boolean) {
126
246
  (this.configProxy as OptionConfig).isMultiple = !!value;
127
247
  }
128
248
 
129
249
  /**
130
- * Indicates whether the option displays an image next to the label.
250
+ * Indicates whether the option displays an image.
251
+ *
252
+ * @public
253
+ * @returns {boolean} `true` if image is visible; `false` otherwise.
131
254
  */
132
255
  public get hasImage(): boolean {
133
256
  return this.config!.hasImage;
134
257
  }
135
258
 
136
259
  /**
137
- * Shows or hides the image block.
260
+ * Shows or hides the option's image element.
138
261
  *
139
- * When rendered:
140
- * - Toggles related CSS classes
141
- * - Creates or removes the `<img>` element dynamically
262
+ * Side effects (when rendered):
263
+ * - **`true`**: Toggles `"has-image"` class, adds `image-{position}` class, calls {@link createImage}.
264
+ * - **`false`**: Removes `"has-image"` and `image-*` classes, removes `<img>` element, nulls reference.
265
+ *
266
+ * Notes:
267
+ * - Assignments trigger Proxy → {@link applyPartialChange}.
268
+ * - Image is created on-demand (not pre-rendered).
269
+ *
270
+ * @public
271
+ * @param {boolean} value - `true` to show image; `false` to hide.
142
272
  */
143
273
  public set hasImage(value: boolean) {
144
274
  (this.configProxy as OptionConfig).hasImage = !!value;
@@ -147,20 +277,42 @@ export class OptionView extends View<OptionViewTags> {
147
277
  /**
148
278
  * Provides reactive access to the full option configuration.
149
279
  *
150
- * Mutating properties on this object triggers incremental
151
- * DOM updates when the view has already been rendered.
280
+ * Usage:
281
+ * - **Getter**: Returns {@link configProxy} for direct property access.
282
+ * - **Setter**: Applies batch configuration changes (see setter docs).
283
+ *
284
+ * Notes:
285
+ * - Mutating properties on the returned object triggers incremental DOM updates.
286
+ * - Safe to read/write after {@link initialize} completes.
287
+ *
288
+ * @public
289
+ * @returns {OptionConfig} Reactive configuration Proxy.
152
290
  */
153
291
  public get optionConfig(): OptionConfig {
154
292
  return this.configProxy as OptionConfig;
155
293
  }
156
294
 
157
295
  /**
158
- * Applies a batch of configuration changes.
296
+ * Applies a batch of configuration changes efficiently.
297
+ *
298
+ * Optimization strategy:
299
+ * 1. Compares each incoming property against current {@link config} value.
300
+ * 2. Builds a `changes` object containing **only diffed properties**.
301
+ * 3. Assigns `changes` to {@link configProxy} via `Object.assign` (triggers Proxy traps).
159
302
  *
160
- * Only properties whose values differ from the current state
161
- * are assigned to the Proxy and processed.
303
+ * Diffed properties:
304
+ * - `imageWidth`, `imageHeight`, `imageBorderRadius`
305
+ * - `imagePosition`
306
+ * - `labelValign`, `labelHalign`
162
307
  *
163
- * @param config - Partial configuration patch
308
+ * Notes:
309
+ * - No-op if `config` is `null`, or no properties differ.
310
+ * - Prevents redundant Proxy triggers for unchanged values.
311
+ * - Each changed property triggers {@link applyPartialChange} individually.
312
+ *
313
+ * @public
314
+ * @param {OptionConfigPatch | null} config - Partial configuration patch; `null` is no-op.
315
+ * @returns {void}
164
316
  */
165
317
  public set optionConfig(config: OptionConfigPatch | null) {
166
318
  if (!config || !this.configProxy || !this.config) return;
@@ -193,9 +345,30 @@ export class OptionView extends View<OptionViewTags> {
193
345
  /**
194
346
  * Performs the initial render of the option view.
195
347
  *
196
- * Builds the DOM structure based on the current configuration,
197
- * assigns CSS classes and ARIA attributes, mounts the view via
198
- * `Libs.mountView`, and enables reactive updates afterward.
348
+ * Rendering flow:
349
+ * 1. Generates unique option ID (7-character random string).
350
+ * 2. Builds CSS classes based on current {@link config} (`multiple`, `has-image`, `image-{position}`).
351
+ * 3. Constructs child structure:
352
+ * - **OptionInput**: `<input type="radio|checkbox">` with unique ID.
353
+ * - **OptionImage** (conditional): `<img>` with inline styles (width/height/borderRadius).
354
+ * - **OptionLabel**: `<label htmlFor="{inputID}">` with alignment classes.
355
+ * - **LabelContent**: `<div>` (content placeholder).
356
+ * 4. Creates DOM via {@link Libs.mountView}.
357
+ * 5. Appends root to {@link parent}.
358
+ * 6. Sets {@link isRendered} to `true` (enables reactive updates).
359
+ * 7. Transitions `INITIALIZED → MOUNTED` via `super.mount()`.
360
+ *
361
+ * Accessibility setup:
362
+ * - Root: `role="option"`, `aria-selected="false"`, `tabindex="-1"`.
363
+ * - Input/Label association via `id` / `htmlFor`.
364
+ *
365
+ * Postcondition:
366
+ * - {@link view} is populated with typed DOM references.
367
+ * - Reactive updates are now enabled.
368
+ *
369
+ * @public
370
+ * @returns {void}
371
+ * @override
199
372
  */
200
373
  public override mount(): void {
201
374
  const viewClass: string[] = ["selective-ui-option-view"];
@@ -268,9 +441,33 @@ export class OptionView extends View<OptionViewTags> {
268
441
  /**
269
442
  * Applies a targeted DOM update for a single configuration change.
270
443
  *
271
- * Updates only the affected parts of the view
272
- * (classes, attributes, styles, or child nodes)
273
- * without triggering a full re-render.
444
+ * Implementation strategy:
445
+ * - Retrieves DOM references from {@link view}.
446
+ * - Switches on `prop` to determine update type.
447
+ * - Mutates **only** the affected DOM nodes (classes, attributes, styles, or child structure).
448
+ *
449
+ * Update rules:
450
+ * - **`isMultiple`**: Toggle `"multiple"` class, switch input `type` (radio ↔ checkbox).
451
+ * - **`hasImage`**: Toggle `"has-image"` class, create/remove `<img>` element, manage `image-*` classes.
452
+ * - **`imagePosition`**: Replace `image-{position}` class (top/right/bottom/left).
453
+ * - **`imageWidth/Height/BorderRadius`**: Mutate `<img>` inline styles.
454
+ * - **`labelValign/Halign`**: Replace label alignment classes.
455
+ *
456
+ * No-op conditions:
457
+ * - If {@link view} is `null` (not mounted yet).
458
+ * - If affected element doesn't exist (e.g., image removed).
459
+ *
460
+ * Notes:
461
+ * - Called by Proxy `set` trap when {@link isRendered} is `true`.
462
+ * - Avoids full re-render; updates are incremental and efficient.
463
+ * - `oldValue` parameter is unused (reserved for future diffing logic).
464
+ *
465
+ * @private
466
+ * @template K - Key of {@link OptionConfig}.
467
+ * @param {K} prop - Property name that changed.
468
+ * @param {OptionConfig[K]} newValue - New value for the property.
469
+ * @param {OptionConfig[K]} oldValue - Previous value (currently unused).
470
+ * @returns {void}
274
471
  */
275
472
  private applyPartialChange<K extends keyof OptionConfig>(
276
473
  prop: K,
@@ -357,9 +554,24 @@ export class OptionView extends View<OptionViewTags> {
357
554
  /**
358
555
  * Creates and inserts the `<img>` element for the option on demand.
359
556
  *
360
- * The image is styled using the current configuration and is inserted
361
- * before the label when possible. If an image already exists, no action
362
- * is taken.
557
+ * Creation flow:
558
+ * 1. Checks if image already exists (early return if present).
559
+ * 2. Creates `<img>` element with:
560
+ * - Class: `"option-image"`
561
+ * - Inline styles: `width`, `height`, `borderRadius` from {@link config}.
562
+ * 3. Inserts image before {@link OptionLabel} (if label exists), otherwise appends to root.
563
+ * 4. Stores reference in `view.tags.OptionImage`.
564
+ *
565
+ * No-op conditions:
566
+ * - If {@link view} is `null` (not mounted yet).
567
+ * - If image already exists in `view.tags.OptionImage`.
568
+ *
569
+ * Notes:
570
+ * - Called by {@link applyPartialChange} when `hasImage` transitions to `true`.
571
+ * - Insertion order ensures proper layout (image before label).
572
+ *
573
+ * @private
574
+ * @returns {void}
363
575
  */
364
576
  private createImage(): void {
365
577
  const v = this.view;