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.
- package/dist/selective-ui.esm.js +4174 -1237
- package/dist/selective-ui.esm.js.map +1 -1
- package/dist/selective-ui.esm.min.js +2 -2
- package/dist/selective-ui.esm.min.js.br +0 -0
- package/dist/selective-ui.min.js +2 -2
- package/dist/selective-ui.min.js.br +0 -0
- package/dist/selective-ui.umd.js +4175 -1238
- package/dist/selective-ui.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/ts/adapter/mixed-adapter.ts +247 -91
- package/src/ts/components/accessorybox.ts +164 -67
- package/src/ts/components/directive.ts +53 -24
- package/src/ts/components/option-handle.ts +121 -54
- package/src/ts/components/placeholder.ts +70 -32
- package/src/ts/components/popup/empty-state.ts +68 -32
- package/src/ts/components/popup/loading-state.ts +70 -30
- package/src/ts/components/popup/popup.ts +0 -1
- package/src/ts/components/searchbox.ts +185 -46
- package/src/ts/components/selectbox.ts +309 -30
- package/src/ts/core/base/adapter.ts +158 -77
- package/src/ts/core/base/fenwick.ts +147 -0
- package/src/ts/core/base/lifecycle.ts +118 -35
- package/src/ts/core/base/model.ts +94 -36
- package/src/ts/core/base/recyclerview.ts +0 -1
- package/src/ts/core/base/view.ts +54 -23
- package/src/ts/core/base/virtual-recyclerview.ts +360 -278
- package/src/ts/core/model-manager.ts +162 -81
- package/src/ts/core/search-controller.ts +164 -91
- package/src/ts/global.ts +1 -1
- package/src/ts/index.ts +1 -1
- package/src/ts/models/group-model.ts +138 -32
- package/src/ts/models/option-model.ts +184 -48
- package/src/ts/services/dataset-observer.ts +72 -10
- package/src/ts/services/ea-observer.ts +87 -10
- package/src/ts/services/effector.ts +181 -32
- package/src/ts/services/refresher.ts +30 -6
- package/src/ts/services/resize-observer.ts +132 -15
- package/src/ts/services/select-observer.ts +115 -50
- package/src/ts/types/utils/ievents.type.ts +6 -1
- package/src/ts/utils/callback-scheduler.ts +112 -34
- package/src/ts/utils/ievents.ts +91 -29
- package/src/ts/utils/selective.ts +330 -61
- package/src/ts/views/group-view.ts +137 -26
- 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
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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
|
-
*
|
|
12
|
-
*
|
|
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
|
-
/**
|
|
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<
|
|
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
|
-
*
|
|
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
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
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
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* @
|
|
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
|
|
64
|
-
* in the order they were added.
|
|
107
|
+
* Emits a lifecycle hook by executing all registered callbacks for that hook.
|
|
65
108
|
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
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
|
|
152
|
+
* Transitions `NEW → INITIALIZED` and emits `onInit`.
|
|
153
|
+
*
|
|
154
|
+
* Idempotent: **no-op** unless current state is {@link LifecycleState.NEW}.
|
|
91
155
|
*
|
|
92
|
-
*
|
|
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
|
|
168
|
+
* Transitions `INITIALIZED → MOUNTED` and emits `onMount`.
|
|
104
169
|
*
|
|
105
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
119
|
-
*
|
|
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
|
|
209
|
+
* Transitions to `DESTROYED`, emits `onDestroy`, then clears all hook registrations.
|
|
136
210
|
*
|
|
137
|
-
* Idempotent:
|
|
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
|
|
235
|
+
* Checks whether the lifecycle is in the specified state.
|
|
157
236
|
*
|
|
158
|
-
* @param state -
|
|
159
|
-
* @returns
|
|
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
|
|
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
|
|
9
|
-
*
|
|
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
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
|
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
|
-
*
|
|
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
|
|
103
|
+
* Creates a new model instance and initializes lifecycle state.
|
|
55
104
|
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
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
|
|
61
|
-
* @param view - Optional view responsible for rendering
|
|
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
|
-
*
|
|
127
|
+
* Rebinds this model to a new target DOM element and marks the model as updated.
|
|
78
128
|
*
|
|
79
|
-
*
|
|
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
|
-
*
|
|
88
|
-
*
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
*
|
|
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
|
-
*
|
|
96
|
-
*
|
|
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
|
}
|
package/src/ts/core/base/view.ts
CHANGED
|
@@ -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
|
|
7
|
+
* Base View primitive that anchors a mounted DOM structure into a parent container.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
* -
|
|
11
|
-
*
|
|
12
|
-
* -
|
|
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
|
-
*
|
|
16
|
-
* -
|
|
17
|
-
* -
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
38
|
-
*
|
|
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 -
|
|
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
|
|
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
|
-
*
|
|
65
|
-
* -
|
|
66
|
-
* -
|
|
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)) {
|