selective-ui 1.2.4 → 1.2.6

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/README.md +7 -0
  2. package/dist/selective-ui.css +64 -58
  3. package/dist/selective-ui.css.map +1 -1
  4. package/dist/selective-ui.esm.js +4396 -1344
  5. package/dist/selective-ui.esm.js.map +1 -1
  6. package/dist/selective-ui.esm.min.js +2 -2
  7. package/dist/selective-ui.esm.min.js.br +0 -0
  8. package/dist/selective-ui.min.css +1 -1
  9. package/dist/selective-ui.min.css.br +0 -0
  10. package/dist/selective-ui.min.js +2 -2
  11. package/dist/selective-ui.min.js.br +0 -0
  12. package/dist/selective-ui.umd.js +4401 -1345
  13. package/dist/selective-ui.umd.js.map +1 -1
  14. package/package.json +3 -3
  15. package/src/css/components/accessorybox.css +1 -1
  16. package/src/css/components/directive.css +2 -2
  17. package/src/css/components/option-handle.css +4 -4
  18. package/src/css/components/placeholder.css +1 -1
  19. package/src/css/components/popup/empty-state.css +3 -3
  20. package/src/css/components/popup/loading-state.css +3 -3
  21. package/src/css/components/popup/popup.css +5 -5
  22. package/src/css/components/searchbox.css +2 -2
  23. package/src/css/components/selectbox.css +7 -7
  24. package/src/css/views/group-view.css +8 -8
  25. package/src/css/views/option-view.css +22 -22
  26. package/src/ts/adapter/mixed-adapter.ts +248 -92
  27. package/src/ts/components/accessorybox.ts +170 -73
  28. package/src/ts/components/directive.ts +55 -26
  29. package/src/ts/components/option-handle.ts +127 -60
  30. package/src/ts/components/placeholder.ts +73 -35
  31. package/src/ts/components/popup/empty-state.ts +71 -35
  32. package/src/ts/components/popup/loading-state.ts +73 -33
  33. package/src/ts/components/popup/popup.ts +19 -39
  34. package/src/ts/components/searchbox.ts +189 -50
  35. package/src/ts/components/selectbox.ts +401 -40
  36. package/src/ts/core/base/adapter.ts +160 -79
  37. package/src/ts/core/base/fenwick.ts +147 -0
  38. package/src/ts/core/base/lifecycle.ts +118 -35
  39. package/src/ts/core/base/model.ts +94 -36
  40. package/src/ts/core/base/recyclerview.ts +0 -1
  41. package/src/ts/core/base/view.ts +54 -23
  42. package/src/ts/core/base/virtual-recyclerview.ts +365 -283
  43. package/src/ts/core/model-manager.ts +172 -92
  44. package/src/ts/core/search-controller.ts +166 -93
  45. package/src/ts/global.ts +26 -5
  46. package/src/ts/index.ts +22 -3
  47. package/src/ts/models/group-model.ts +138 -32
  48. package/src/ts/models/option-model.ts +197 -53
  49. package/src/ts/services/dataset-observer.ts +72 -10
  50. package/src/ts/services/ea-observer.ts +87 -10
  51. package/src/ts/services/effector.ts +181 -32
  52. package/src/ts/services/refresher.ts +32 -7
  53. package/src/ts/services/resize-observer.ts +136 -19
  54. package/src/ts/services/select-observer.ts +115 -50
  55. package/src/ts/types/core/base/view.type.ts +3 -3
  56. package/src/ts/types/core/base/virtual-recyclerview.type.ts +1 -1
  57. package/src/ts/types/plugins/plugin.type.ts +46 -0
  58. package/src/ts/types/utils/ievents.type.ts +6 -1
  59. package/src/ts/types/utils/istorage.type.ts +8 -4
  60. package/src/ts/types/utils/libs.type.ts +2 -2
  61. package/src/ts/types/utils/selective.type.ts +14 -1
  62. package/src/ts/utils/callback-scheduler.ts +115 -37
  63. package/src/ts/utils/ievents.ts +91 -29
  64. package/src/ts/utils/libs.ts +41 -65
  65. package/src/ts/utils/selective.ts +412 -79
  66. package/src/ts/views/group-view.ts +142 -31
  67. package/src/ts/views/option-view.ts +272 -60
@@ -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"`
165
+ *
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.
66
171
  *
67
- * The Proxy intercepts property assignments and:
68
- * - Updates internal state
69
- * - Applies targeted DOM changes when the view is already rendered
172
+ * Postcondition:
173
+ * - {@link config} and {@link configProxy} are initialized.
174
+ * - Transitions `NEW INITIALIZED` via `this.init()`.
70
175
  *
71
- * No DOM mutations occur before the initial render.
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,49 +231,88 @@ 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
- (this.configProxy as OptionConfig).isMultiple = !!value;
246
+ this.configProxy.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.
261
+ *
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.
138
265
  *
139
- * When rendered:
140
- * - Toggles related CSS classes
141
- * - Creates or removes the `<img>` element dynamically
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
- (this.configProxy as OptionConfig).hasImage = !!value;
274
+ this.configProxy.hasImage = !!value;
145
275
  }
146
276
 
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).
302
+ *
303
+ * Diffed properties:
304
+ * - `imageWidth`, `imageHeight`, `imageBorderRadius`
305
+ * - `imagePosition`
306
+ * - `labelValign`, `labelHalign`
159
307
  *
160
- * Only properties whose values differ from the current state
161
- * are assigned to the Proxy and processed.
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.
162
312
  *
163
- * @param config - Partial configuration patch
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,12 +345,33 @@ 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.mountNode}.
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
- const viewClass: string[] = ["selective-ui-option-view"];
374
+ const viewClass: string[] = ["seui-option-view"];
202
375
  const opt_id = Libs.randomString(7);
203
376
  const inputID = `option_${opt_id}`;
204
377
 
@@ -245,7 +418,7 @@ export class OptionView extends View<OptionViewTags> {
245
418
  },
246
419
  };
247
420
 
248
- this.view = Libs.mountView<OptionViewTags>({
421
+ this.view = Libs.mountNode<OptionViewResult>({
249
422
  OptionView: {
250
423
  tag: {
251
424
  node: "div",
@@ -257,7 +430,7 @@ export class OptionView extends View<OptionViewTags> {
257
430
  },
258
431
  child: childStructure,
259
432
  },
260
- }) as OptionViewResult;
433
+ });
261
434
 
262
435
  this.parent!.appendChild(this.view.view);
263
436
  this.isRendered = true;
@@ -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,
@@ -281,8 +478,8 @@ export class OptionView extends View<OptionViewTags> {
281
478
  if (!v || !v.view) return;
282
479
 
283
480
  const root = v.view;
284
- const input = v.tags?.OptionInput as HTMLInputElement | undefined;
285
- const label = v.tags?.OptionLabel as HTMLLabelElement | undefined;
481
+ const input = v.tags?.OptionInput;
482
+ const label = v.tags?.OptionLabel;
286
483
 
287
484
  switch (prop) {
288
485
  case "isMultiple": {
@@ -307,7 +504,7 @@ export class OptionView extends View<OptionViewTags> {
307
504
  .replace(/image-(top|right|bottom|left)/g, "")
308
505
  .trim();
309
506
 
310
- const img = v.tags?.OptionImage as HTMLImageElement | null | undefined;
507
+ const img = v.tags?.OptionImage;
311
508
  img?.remove();
312
509
  v.tags.OptionImage = null;
313
510
  }
@@ -327,7 +524,7 @@ export class OptionView extends View<OptionViewTags> {
327
524
  case "imageWidth":
328
525
  case "imageHeight":
329
526
  case "imageBorderRadius": {
330
- const img = v.tags?.OptionImage as HTMLImageElement | null | undefined;
527
+ const img = v.tags?.OptionImage;
331
528
  if (img) {
332
529
  const styleProp =
333
530
  prop === "imageWidth" ? "width" :
@@ -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;
@@ -368,7 +580,7 @@ export class OptionView extends View<OptionViewTags> {
368
580
  if (v.tags?.OptionImage) return;
369
581
 
370
582
  const root = v.view;
371
- const label = v.tags?.OptionLabel as HTMLLabelElement | undefined;
583
+ const label = v.tags?.OptionLabel;
372
584
 
373
585
  const image = document.createElement("img");
374
586
  image.className = "option-image";