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,18 +1,51 @@
1
-
2
1
  import { LifecycleHookContext, LifecycleHooks, LifecycleState } from "src/ts/types/core/base/lifecycle.type";
3
2
 
4
3
  type LifecycleHookName = keyof LifecycleHooks;
5
4
 
6
5
  /**
7
- * Lightweight lifecycle manager that provides:
8
- * - A finite-state lifecycle machine (`NEW` → `INITIALIZED` → `MOUNTED` → `UPDATED` → `DESTROYED`)
9
- * - A simple hooks system to subscribe to lifecycle events (`onInit`, `onMount`, `onUpdate`, `onDestroy`)
6
+ * Minimal lifecycle finite-state machine (FSM) with a lightweight hook system.
7
+ *
8
+ * ### Responsibility
9
+ * - Provide a **strict**, **guarded** lifecycle FSM:
10
+ * `NEW → INITIALIZED → MOUNTED → UPDATED → DESTROYED`
11
+ * - Provide an in-memory hook registry to observe lifecycle transitions:
12
+ * `onInit`, `onMount`, `onUpdate`, `onDestroy`
13
+ *
14
+ * This class is designed to be extended by core primitives (Model/View/Adapter/Controller)
15
+ * so they share consistent lifecycle semantics without coupling to any rendering runtime.
16
+ *
17
+ * ### FSM & Idempotency
18
+ * - `init()` is **idempotent**: only transitions `NEW → INITIALIZED`; otherwise **no-op**.
19
+ * - `mount()` is **guarded**: only transitions `INITIALIZED → MOUNTED`; otherwise **no-op**.
20
+ * - `update()` is **repeatable** once mounted: allowed in `MOUNTED` and `UPDATED`.
21
+ * It always emits `onUpdate` and keeps state at `UPDATED`.
22
+ * - `destroy()` is **idempotent**: once `DESTROYED`, subsequent calls are **no-op**.
10
23
  *
11
- * Classes in the core (e.g., Model/View) can extend this to standardize initialization,
12
- * mounting, updates, and teardown. Hooks are stored as in-memory callbacks and cleared on destroy.
24
+ * ### Hook semantics
25
+ * - Hooks are stored in a `Set` per hook name:
26
+ * - de-duplicates identical callback references,
27
+ * - preserves insertion order for deterministic execution.
28
+ * - Hook callbacks receive a {@link LifecycleHookContext} containing:
29
+ * - `state` (current state after transition),
30
+ * - `prevState` (state prior to the transition).
31
+ * - Hook exceptions are caught and forwarded to {@link handleHookError},
32
+ * preventing a single subscriber from breaking the lifecycle flow.
33
+ *
34
+ * ### Memory & teardown
35
+ * - All registered hooks are cleared on `destroy()` via {@link clearHooks}.
36
+ * - Post-destroy calls to lifecycle methods do not emit further hooks.
37
+ *
38
+ * @see {@link LifecycleState}
39
+ * @see {@link LifecycleHooks}
40
+ * @see {@link LifecycleHookContext}
13
41
  */
14
42
  export class Lifecycle {
15
- /** Current lifecycle state */
43
+ /**
44
+ * Current lifecycle state.
45
+ *
46
+ * Starts at {@link LifecycleState.NEW} and transitions through the FSM via
47
+ * {@link init}, {@link mount}, {@link update}, {@link destroy}.
48
+ */
16
49
  protected state: LifecycleState = LifecycleState.NEW;
17
50
 
18
51
  /**
@@ -21,12 +54,18 @@ export class Lifecycle {
21
54
  * Uses a Set per hook to:
22
55
  * - Avoid duplicate registrations
23
56
  * - Preserve insertion order for deterministic execution
57
+ *
58
+ * @remarks
59
+ * This map is initialized with keys for all supported hooks in the constructor.
60
+ * Callbacks are cleared on {@link destroy}.
24
61
  */
25
- private hooks: Map<LifecycleHookName, Set<Function>> = new Map();
62
+ private hooks: Map<LifecycleHookName, Set<(ctx: LifecycleHookContext) => void>> = new Map();
26
63
 
27
64
  /**
28
65
  * Constructs the lifecycle manager and pre-registers hook containers.
29
- * No hooks are executed during construction.
66
+ *
67
+ * No hooks are executed during construction; consumers must call
68
+ * {@link init}, {@link mount}, {@link update}, or {@link destroy}.
30
69
  */
31
70
  constructor() {
32
71
  this.hooks.set("onInit", new Set());
@@ -38,11 +77,14 @@ export class Lifecycle {
38
77
  /**
39
78
  * Subscribes a callback to a lifecycle hook.
40
79
  *
41
- * @param hook - The lifecycle hook name to listen to
42
- * @param fn - The callback to execute when the hook is emitted
43
- * @returns `this` for chaining
80
+ * Hook callbacks are invoked in insertion order. Duplicate callback references are ignored
81
+ * due to Set semantics.
82
+ *
83
+ * @param {LifecycleHookName} hook - Hook name to subscribe to.
84
+ * @param {(ctx: LifecycleHookContext) => void} fn - Callback invoked when the hook is emitted.
85
+ * @returns {this} The current instance (chainable).
44
86
  */
45
- on(hook: LifecycleHookName, fn: () => void): this {
87
+ on(hook: LifecycleHookName, fn: (ctx: LifecycleHookContext) => void): this {
46
88
  this.hooks.get(hook)!.add(fn);
47
89
  return this;
48
90
  }
@@ -50,22 +92,31 @@ export class Lifecycle {
50
92
  /**
51
93
  * Unsubscribes a previously registered callback from a lifecycle hook.
52
94
  *
53
- * @param hook - The lifecycle hook name
54
- * @param fn - The callback to remove
55
- * @returns `this` for chaining
95
+ * Safe to call even if the callback was never registered (no-op).
96
+ *
97
+ * @param {LifecycleHookName} hook - Hook name to unsubscribe from.
98
+ * @param {(ctx: LifecycleHookContext) => void} fn - Callback to remove.
99
+ * @returns {this} The current instance (chainable).
56
100
  */
57
- off(hook: LifecycleHookName, fn: () => void): this {
101
+ off(hook: LifecycleHookName, fn: (ctx: LifecycleHookContext) => void): this {
58
102
  this.hooks.get(hook)!.delete(fn);
59
103
  return this;
60
104
  }
61
105
 
62
106
  /**
63
- * Emits a lifecycle hook, executing all registered callbacks
64
- * in the order they were added.
107
+ * Emits a lifecycle hook by executing all registered callbacks for that hook.
65
108
  *
66
- * @param hook - The lifecycle hook to emit
67
- * @internal Prefer using the public lifecycle methods (`init/mount/update/destroy`)
68
- * which call `emit` at the correct times.
109
+ * Execution model:
110
+ * - Callbacks run in insertion order.
111
+ * - Errors thrown by callbacks are caught and forwarded to {@link handleHookError}.
112
+ *
113
+ * @param {LifecycleHookName} hook - The hook to emit.
114
+ * @param {LifecycleState} prevState - The state prior to the transition.
115
+ * @returns {void}
116
+ *
117
+ * @internal
118
+ * Prefer invoking the public lifecycle methods ({@link init}, {@link mount}, {@link update}, {@link destroy})
119
+ * which call `emit()` at the correct time and enforce FSM guards.
69
120
  */
70
121
  protected emit(hook: LifecycleHookName, prevState: LifecycleState): void {
71
122
  const ctx: LifecycleHookContext = {
@@ -82,14 +133,28 @@ export class Lifecycle {
82
133
  }
83
134
  }
84
135
 
136
+ /**
137
+ * Handles errors thrown by lifecycle hook callbacks.
138
+ *
139
+ * Default behavior logs to `console.error` with a hook-scoped prefix.
140
+ * Subclasses may override to integrate with application logging/telemetry.
141
+ *
142
+ * @param {unknown} error - Error thrown by a hook callback.
143
+ * @param {LifecycleHookName} hook - Hook name during which the error occurred.
144
+ * @returns {void}
145
+ * @protected
146
+ */
85
147
  protected handleHookError(error: unknown, hook: LifecycleHookName): void {
86
148
  console.error(`[Lifecycle:${hook}]`, error);
87
149
  }
88
150
 
89
151
  /**
90
- * Transitions from `NEW``INITIALIZED` and emits `onInit`.
152
+ * Transitions `NEW → INITIALIZED` and emits `onInit`.
153
+ *
154
+ * Idempotent: **no-op** unless current state is {@link LifecycleState.NEW}.
91
155
  *
92
- * Safe to call multiple times; subsequent calls are no-ops unless current state is `NEW`.
156
+ * @returns {void}
157
+ * @see {@link LifecycleHooks.onInit}
93
158
  */
94
159
  init(): void {
95
160
  if (this.state !== LifecycleState.NEW) return;
@@ -100,9 +165,12 @@ export class Lifecycle {
100
165
  }
101
166
 
102
167
  /**
103
- * Transitions from `INITIALIZED``MOUNTED` and emits `onMount`.
168
+ * Transitions `INITIALIZED → MOUNTED` and emits `onMount`.
104
169
  *
105
- * No-ops if current state is not `INITIALIZED` (guards against invalid order).
170
+ * Guarded: **no-op** unless current state is {@link LifecycleState.INITIALIZED}.
171
+ *
172
+ * @returns {void}
173
+ * @see {@link LifecycleHooks.onMount}
106
174
  */
107
175
  mount(): void {
108
176
  if (this.state !== LifecycleState.INITIALIZED) return;
@@ -113,10 +181,16 @@ export class Lifecycle {
113
181
  }
114
182
 
115
183
  /**
116
- * Transitions from `MOUNTED` `UPDATED` and emits `onUpdate`.
184
+ * Emits `onUpdate` and transitions to/keeps state `UPDATED`.
185
+ *
186
+ * Allowed states:
187
+ * - `MOUNTED` → `UPDATED`
188
+ * - `UPDATED` → `UPDATED` (repeatable updates still emit)
189
+ *
190
+ * Guarded: **no-op** unless current state is `MOUNTED` or `UPDATED`.
117
191
  *
118
- * No-ops if not currently `MOUNTED`. Subsequent `update()` calls will keep the state
119
- * at `UPDATED` while still emitting `onUpdate`.
192
+ * @returns {void}
193
+ * @see {@link LifecycleHooks.onUpdate}
120
194
  */
121
195
  update(): void {
122
196
  if (
@@ -132,9 +206,12 @@ export class Lifecycle {
132
206
  }
133
207
 
134
208
  /**
135
- * Transitions to `DESTROYED` and emits `onDestroy`, then clears all hooks.
209
+ * Transitions to `DESTROYED`, emits `onDestroy`, then clears all hook registrations.
136
210
  *
137
- * Idempotent: calling `destroy()` multiple times after destruction has no effect.
211
+ * Idempotent: **no-op** if already {@link LifecycleState.DESTROYED}.
212
+ *
213
+ * @returns {void}
214
+ * @see {@link LifecycleHooks.onDestroy}
138
215
  */
139
216
  destroy(): void {
140
217
  if (this.state === LifecycleState.DESTROYED) return;
@@ -147,16 +224,18 @@ export class Lifecycle {
147
224
 
148
225
  /**
149
226
  * Returns the current lifecycle state.
227
+ *
228
+ * @returns {LifecycleState} Current FSM state.
150
229
  */
151
230
  getState(): LifecycleState {
152
231
  return this.state;
153
232
  }
154
233
 
155
234
  /**
156
- * Checks if the lifecycle is in the specified state.
235
+ * Checks whether the lifecycle is in the specified state.
157
236
  *
158
- * @param state - The state to compare against
159
- * @returns True if the current state matches; otherwise false
237
+ * @param {LifecycleState} state - State to compare against.
238
+ * @returns {boolean} `true` if current state matches; otherwise `false`.
160
239
  */
161
240
  is(state: LifecycleState): boolean {
162
241
  return this.state === state;
@@ -165,7 +244,11 @@ export class Lifecycle {
165
244
  /**
166
245
  * Clears all registered lifecycle hooks.
167
246
  *
168
- * Called automatically during `destroy()`.
247
+ * Called automatically during {@link destroy}. After clearing, the hook containers remain
248
+ * allocated (map keys persist) but contain no subscribers.
249
+ *
250
+ * @returns {void}
251
+ * @private
169
252
  */
170
253
  private clearHooks(): void {
171
254
  for (const set of this.hooks.values()) {
@@ -1,19 +1,43 @@
1
-
2
1
  import { ModelContract } from "src/ts/types/core/base/model.type";
3
2
  import { ViewContract } from "src/ts/types/core/base/view.type";
4
3
  import { Lifecycle } from "./lifecycle";
5
4
  import { LifecycleState } from "src/ts/types/core/base/lifecycle.type";
6
5
 
7
6
  /**
8
- * Base Model class that connects a target DOM element with its corresponding View.
9
- * Handles lifecycle, state, and synchronization between model, view, and DOM.
7
+ * Base model primitive that binds a domain object to a target DOM element and an optional View.
8
+ *
9
+ * This class is the **Model** part of the library's Model/View separation:
10
+ * - The **Model** owns references to the authoritative DOM source (`targetElement`) and configuration (`options`).
11
+ * - The **View** (if attached) owns rendering and DOM event wiring for the model.
12
+ * - Higher-level infrastructure (e.g., Adapter / RecyclerView) orchestrates when models are created,
13
+ * bound to views, and updated.
14
+ *
15
+ * ### Lifecycle (Strict FSM)
16
+ * - Constructor calls {@link Lifecycle.init} immediately, transitioning `NEW → INITIALIZED`.
17
+ * - This base model does not call `mount()` by itself; mounting is typically handled by the View layer.
18
+ * - {@link updateTarget} triggers {@link Lifecycle.update}, which emits `onUpdate` lifecycle hooks in
19
+ * `MOUNTED/UPDATED` states (and is guarded otherwise).
20
+ * - {@link destroy} transitions to `DESTROYED`, clears references, and destroys the associated view.
21
+ *
22
+ * ### Idempotency / No-ops
23
+ * - {@link destroy} is idempotent once in {@link LifecycleState.DESTROYED}.
24
+ * - {@link updateTarget} is safe to call multiple times; consumers should treat repeated assignments
25
+ * as a no-op when the target does not change (this base class does not compare equality).
10
26
  *
11
- * @template TTarget - The HTML element this model is bound to
12
- * @template TTags - A map of named HTML elements used by the view
13
- * @template TView - The view implementation associated with this model
14
- * @template TOptions - Configuration options for the model
27
+ * ### Ownership & side effects
28
+ * - This model **owns** its `view` reference and will call `view.destroy()` during {@link destroy}.
29
+ * - The model itself does not mutate the DOM, except reading from `targetElement` (e.g., {@link value}).
30
+ * Any DOM side effects are expected to live in the View implementation.
31
+ *
32
+ * @template TTarget - The DOM element type this model is bound to (e.g., HTMLOptionElement).
33
+ * @template TTags - Named element map used by the view (view-specific DOM handles).
34
+ * @template TView - View implementation associated with this model.
35
+ * @template TOptions - Configuration/options type carried by the model.
15
36
  *
16
37
  * @implements {ModelContract<TTarget, TView>}
38
+ * @extends Lifecycle
39
+ * @see {@link ViewContract}
40
+ * @see {@link LifecycleState}
17
41
  */
18
42
  export class Model<
19
43
  TTarget extends HTMLElement,
@@ -22,43 +46,69 @@ export class Model<
22
46
  TOptions = unknown
23
47
  > extends Lifecycle implements ModelContract<TTarget, TView> {
24
48
 
25
- /** The underlying DOM element associated with this model */
49
+ /**
50
+ * The currently bound target DOM element.
51
+ *
52
+ * This element typically represents the source-of-truth node in the host DOM (e.g., a native `<option>`).
53
+ * May be replaced via {@link updateTarget} during reconciliation.
54
+ */
26
55
  public targetElement: TTarget | null = null;
27
56
 
28
- /** Configuration options provided at construction time */
57
+ /**
58
+ * Configuration options supplied at construction time.
59
+ * Stored as-is and intended to be consumed by subclasses and/or the view layer.
60
+ */
29
61
  public options: TOptions;
30
62
 
31
- /** View instance responsible for rendering this model */
63
+ /**
64
+ * View instance responsible for rendering this model.
65
+ *
66
+ * Ownership: this model will destroy the view on {@link destroy}.
67
+ * The view may be attached/assigned by external orchestrators (Adapter/RecyclerView) after construction.
68
+ */
32
69
  public view: TView | null = null;
33
70
 
34
- /** Position index of the model (used for ordering or tracking) */
71
+ /**
72
+ * Position index used by list infrastructure for ordering/tracking.
73
+ * Semantics are library-specific (e.g., top-level index or adapter position).
74
+ */
35
75
  public position = -1;
36
76
 
37
- /** Indicates whether the model has been initialized */
77
+ /**
78
+ * Indicates whether this model has completed its initial binding step.
79
+ * Typically set by the adapter/view binding layer to prevent duplicate listener wiring.
80
+ */
38
81
  public isInit = false;
39
82
 
40
- /** Indicates whether the model has been destroyed/removed */
83
+ /**
84
+ * Indicates whether this model has been removed/destroyed from the active dataset.
85
+ * Set to `true` during {@link destroy}.
86
+ */
41
87
  public isRemoved = false;
42
88
 
43
89
  /**
44
- * Returns the current value of the bound target element.
90
+ * Returns the current "value" associated with the bound target element.
91
+ *
92
+ * Implementation note:
93
+ * - Reads from the target element's `"value"` attribute via `getAttribute("value")`.
94
+ * - Returns `null` when no target is bound or the attribute is not present.
45
95
  *
46
- * - For single-value elements, this is usually a string
47
- * - For multi-value elements, this may be an array depending on usage
96
+ * @returns {string | null | string[]} The value representation of the target element.
48
97
  */
49
98
  public get value(): string | null | string[] {
50
99
  return this.targetElement?.getAttribute("value") ?? null;
51
100
  }
52
101
 
53
102
  /**
54
- * Creates a new Model instance.
103
+ * Creates a new model instance and initializes lifecycle state.
55
104
  *
56
- * Initializes the model with configuration options and optionally binds
57
- * it to a target DOM element and a view.
105
+ * - Captures {@link options}.
106
+ * - Optionally binds an initial {@link targetElement} and {@link view}.
107
+ * - Calls {@link Lifecycle.init} immediately (`NEW → INITIALIZED`).
58
108
  *
59
- * @param options - Configuration options for the model
60
- * @param targetElement - Optional DOM element to bind to this model
61
- * @param view - Optional view responsible for rendering the model
109
+ * @param {TOptions} options - Configuration options for the model.
110
+ * @param {TTarget | null} [targetElement=null] - Optional DOM element to bind.
111
+ * @param {TView | null} [view=null] - Optional view responsible for rendering this model.
62
112
  */
63
113
  public constructor(
64
114
  options: TOptions,
@@ -74,9 +124,18 @@ export class Model<
74
124
  }
75
125
 
76
126
  /**
77
- * Updates the bound target element and triggers the update lifecycle.
127
+ * Rebinds this model to a new target DOM element and marks the model as updated.
78
128
  *
79
- * @param targetElement - The new DOM element to associate with this model
129
+ * Typical usage:
130
+ * - Reconciliation when the underlying DOM node is replaced (e.g., `<option>` node recreated).
131
+ * - Keeping model identity stable while swapping its backing DOM node.
132
+ *
133
+ * Side effects:
134
+ * - Assigns {@link targetElement}.
135
+ * - Calls {@link Lifecycle.update} (guarded by lifecycle state).
136
+ *
137
+ * @param {TTarget | null} targetElement - The new DOM element to associate with this model.
138
+ * @returns {void}
80
139
  */
81
140
  public updateTarget(targetElement: TTarget | null): void {
82
141
  this.targetElement = targetElement;
@@ -84,20 +143,19 @@ export class Model<
84
143
  }
85
144
 
86
145
  /**
87
- * Hook executed when the model is updated.
88
- * Intended to be overridden by subclasses.
89
- */
90
- public onUpdate() { }
91
-
92
- /**
93
- * Destroys the model and releases all references.
146
+ * Destroys this model and releases owned resources.
147
+ *
148
+ * Behavior:
149
+ * - Idempotent once lifecycle is {@link LifecycleState.DESTROYED}.
150
+ * - Clears {@link targetElement}.
151
+ * - Destroys the associated {@link view} (if present) and clears the reference.
152
+ * - Marks {@link isRemoved} as `true`.
153
+ * - Calls {@link Lifecycle.destroy} to transition to `DESTROYED` and clear hooks.
94
154
  *
95
- * - Unbinds the target element
96
- * - Removes the associated view from the DOM
97
- * - Marks the model as removed
98
- * - Triggers lifecycle cleanup
155
+ * @returns {void}
156
+ * @override
99
157
  */
100
- public override destroy() {
158
+ public override destroy(): void {
101
159
  if (this.is(LifecycleState.DESTROYED)) {
102
160
  return;
103
161
  }
@@ -114,7 +114,6 @@ export class RecyclerView<
114
114
  return;
115
115
  }
116
116
 
117
- this.adapter?.destroy?.();
118
117
  this.viewElement = null;
119
118
  this.adapter = null;
120
119
 
@@ -4,40 +4,66 @@ import { Lifecycle } from "./lifecycle";
4
4
  import { LifecycleState } from "src/ts/types/core/base/lifecycle.type";
5
5
 
6
6
  /**
7
- * Base View class that anchors a mounted DOM structure into a parent container.
7
+ * Base View primitive that anchors a mounted DOM structure into a parent container.
8
8
  *
9
- * Responsibilities:
10
- * - Hold a reference to the parent container (`parent`)
11
- * - Store the mounted structure (`view`) returned by a mounting helper
12
- * - Provide a safe getter for the root element (`getView()`)
13
- * - Participate in the standard lifecycle (`init` → `mount` → `update` → `destroy`)
9
+ * This class is the **View** part of the library's Model/View separation:
10
+ * - A View is responsible for owning/manipulating DOM nodes and exposing typed handles (`tags`)
11
+ * for efficient updates.
12
+ * - A View is typically created/managed by an Adapter/RecyclerView layer and assigned back to a Model.
14
13
  *
15
- * Typical usage:
16
- * - Subclasses set `this.view` inside `onMount()` using a mounting utility
17
- * - Then call `this.parent!.appendChild(this.view.view)`
14
+ * ### Responsibility
15
+ * - Hold a reference to the host container (`parent`) where the view's root element is attached.
16
+ * - Store the mounted structure (`view`) produced by a mount utility (root element + typed tag map).
17
+ * - Provide a safe root accessor ({@link getView}) for downstream code (e.g., scrolling, a11y, styling).
18
18
  *
19
- * @template TTags - A map of tag names to their corresponding HTMLElement instances.
19
+ * ### Lifecycle (Strict FSM)
20
+ * - Constructor calls {@link Lifecycle.init} immediately (`NEW → INITIALIZED`).
21
+ * - Mounting is performed by subclasses / infrastructure:
22
+ * - populate {@link view} (usually during a subclass mount hook),
23
+ * - append `view.view` to {@link parent},
24
+ * - then transition to `MOUNTED` via {@link Lifecycle.mount} (typically done by the base/framework).
25
+ * - {@link destroy} transitions to `DESTROYED` and removes the root element from the DOM.
26
+ *
27
+ * ### Idempotency / No-ops
28
+ * - {@link destroy} is idempotent once in {@link LifecycleState.DESTROYED}.
29
+ * - {@link getView} throws if the view is not yet mounted (i.e., {@link view} is unset).
30
+ *
31
+ * ### DOM side effects / Ownership
32
+ * - Owns the root element produced by the mount helper and removes it on {@link destroy}.
33
+ * - Does not automatically append the root node; external orchestrators (Adapter/RecyclerView) control attachment.
34
+ *
35
+ * @template TTags - Map of tag names to their corresponding HTMLElement instances.
20
36
  * @implements {ViewContract<TTags>}
37
+ * @extends Lifecycle
38
+ * @see {@link MountViewResult}
39
+ * @see {@link ViewContract}
40
+ * @see {@link LifecycleState}
21
41
  */
22
42
  export class View<TTags extends Record<string, HTMLElement>> extends Lifecycle implements ViewContract<TTags> {
23
-
24
- /** The parent DOM element into which this view is rendered. */
43
+ /**
44
+ * Host container element into which this view's root element is rendered/attached.
45
+ *
46
+ * This reference is captured at construction time and cleared on {@link destroy}.
47
+ */
25
48
  public parent: HTMLElement | null = null;
26
49
 
27
50
  /**
28
- * Mounted result containing:
51
+ * Mounted view result containing:
29
52
  * - `view`: the root element of this view
30
- * - `tags`: a strongly-typed map of child elements
53
+ * - `tags`: a strongly-typed map of child elements for fast access
54
+ *
55
+ * This is expected to be assigned by subclasses (or a mount helper) before {@link getView} is called.
31
56
  */
32
57
  public view: MountViewResult<TTags> | null = null;
33
58
 
34
59
  /**
35
- * Creates a View bound to the specified parent container.
60
+ * Creates a View bound to the specified parent container and initializes lifecycle state.
36
61
  *
37
- * Note: Subclasses should assign `this.view` during `onMount()` before
38
- * attempting to access `getView()` or manipulate the root element.
62
+ * Notes:
63
+ * - This base constructor **does not** perform DOM mounting or attachment.
64
+ * - Subclasses typically assign {@link view} during their mount step, then append `view.view` to {@link parent}.
39
65
  *
40
- * @param parent - The parent element into which this view will render.
66
+ * @param {HTMLElement} parent - Host element into which this view will render.
41
67
  */
42
68
  public constructor(parent: HTMLElement) {
43
69
  super();
@@ -48,8 +74,8 @@ export class View<TTags extends Record<string, HTMLElement>> extends Lifecycle i
48
74
  /**
49
75
  * Returns the root HTMLElement of the mounted view.
50
76
  *
51
- * @returns The root element produced by the mounting helper.
52
- * @throws {Error} If the view has not been mounted or `this.view` is not set.
77
+ * @returns {HTMLElement} The root element produced by the mounting helper.
78
+ * @throws {Error} If {@link view} is not set or the view has not been mounted yet.
53
79
  */
54
80
  public getView(): HTMLElement {
55
81
  if (!this.view?.view) {
@@ -61,9 +87,14 @@ export class View<TTags extends Record<string, HTMLElement>> extends Lifecycle i
61
87
  /**
62
88
  * Destroys the view and releases DOM references.
63
89
  *
64
- * - Removes the root element from the DOM (if present)
65
- * - Clears references to `parent` and `view`
66
- * - Ends the lifecycle via `super.destroy()`
90
+ * Behavior:
91
+ * - Idempotent: returns early if already in {@link LifecycleState.DESTROYED}.
92
+ * - Removes the root element from the DOM (if present).
93
+ * - Clears references to {@link parent} and {@link view}.
94
+ * - Completes teardown by calling {@link Lifecycle.destroy}.
95
+ *
96
+ * @returns {void}
97
+ * @override
67
98
  */
68
99
  public override destroy(): void {
69
100
  if (this.is(LifecycleState.DESTROYED)) {