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,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
|
-
*
|
|
8
|
-
*
|
|
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
|
-
*
|
|
11
|
-
* -
|
|
12
|
-
* -
|
|
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
|
-
*
|
|
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
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
73
|
-
*
|
|
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
|
-
*
|
|
84
|
-
*
|
|
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
|
|
97
|
-
* belonging to this group.
|
|
180
|
+
* Returns the container element for child item views.
|
|
98
181
|
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
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
|
|
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
|
-
*
|
|
113
|
-
*
|
|
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
|
-
*
|
|
130
|
-
* -
|
|
131
|
-
* -
|
|
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
|
-
* @
|
|
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
|
-
*
|
|
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
|
-
*
|
|
15
|
-
* -
|
|
16
|
-
* -
|
|
17
|
-
* -
|
|
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
|
-
*
|
|
20
|
-
*
|
|
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
|
-
*
|
|
28
|
-
*
|
|
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
|
|
34
|
-
*
|
|
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
|
|
40
|
-
*
|
|
41
|
-
*
|
|
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
|
-
*
|
|
47
|
-
*
|
|
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
|
-
*
|
|
55
|
-
*
|
|
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
|
-
* @
|
|
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
|
|
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
|
-
*
|
|
68
|
-
* -
|
|
69
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
112
|
-
* - `
|
|
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
|
-
*
|
|
122
|
-
* - Toggles
|
|
123
|
-
* - Switches
|
|
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
|
|
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
|
|
260
|
+
* Shows or hides the option's image element.
|
|
138
261
|
*
|
|
139
|
-
*
|
|
140
|
-
* - Toggles
|
|
141
|
-
* -
|
|
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
|
-
*
|
|
151
|
-
*
|
|
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
|
-
*
|
|
161
|
-
*
|
|
303
|
+
* Diffed properties:
|
|
304
|
+
* - `imageWidth`, `imageHeight`, `imageBorderRadius`
|
|
305
|
+
* - `imagePosition`
|
|
306
|
+
* - `labelValign`, `labelHalign`
|
|
162
307
|
*
|
|
163
|
-
*
|
|
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
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
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
|
-
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
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
|
-
*
|
|
361
|
-
*
|
|
362
|
-
*
|
|
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;
|