selective-ui 1.0.2 → 1.0.4
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/LICENSE +21 -21
- package/README.md +7 -2
- package/dist/selective-ui.css +567 -567
- package/dist/selective-ui.css.map +1 -1
- package/dist/selective-ui.esm.js +6186 -6047
- package/dist/selective-ui.esm.js.map +1 -1
- package/dist/selective-ui.esm.min.js +1 -1
- 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 +6186 -6047
- package/dist/selective-ui.umd.js.map +1 -1
- package/package.json +68 -68
- package/src/css/components/accessorybox.css +63 -63
- package/src/css/components/directive.css +19 -19
- package/src/css/components/empty-state.css +25 -25
- package/src/css/components/loading-state.css +25 -25
- package/src/css/components/optgroup.css +61 -61
- package/src/css/components/option-handle.css +33 -33
- package/src/css/components/option.css +129 -129
- package/src/css/components/placeholder.css +14 -14
- package/src/css/components/popup.css +38 -38
- package/src/css/components/searchbox.css +28 -28
- package/src/css/components/selectbox.css +53 -53
- package/src/css/index.css +74 -74
- package/src/js/adapter/mixed-adapter.js +434 -434
- package/src/js/components/accessorybox.js +124 -124
- package/src/js/components/directive.js +37 -37
- package/src/js/components/empty-state.js +67 -67
- package/src/js/components/loading-state.js +59 -59
- package/src/js/components/option-handle.js +113 -113
- package/src/js/components/placeholder.js +56 -56
- package/src/js/components/popup.js +470 -470
- package/src/js/components/searchbox.js +167 -167
- package/src/js/components/selectbox.js +749 -692
- package/src/js/core/base/adapter.js +162 -162
- package/src/js/core/base/model.js +59 -59
- package/src/js/core/base/recyclerview.js +82 -82
- package/src/js/core/base/view.js +62 -62
- package/src/js/core/model-manager.js +286 -286
- package/src/js/core/search-controller.js +603 -521
- package/src/js/index.js +136 -136
- package/src/js/models/group-model.js +142 -142
- package/src/js/models/option-model.js +236 -236
- package/src/js/services/dataset-observer.js +73 -73
- package/src/js/services/ea-observer.js +87 -87
- package/src/js/services/effector.js +403 -403
- package/src/js/services/refresher.js +39 -39
- package/src/js/services/resize-observer.js +151 -151
- package/src/js/services/select-observer.js +60 -60
- package/src/js/types/adapter.type.js +32 -32
- package/src/js/types/effector.type.js +23 -23
- package/src/js/types/ievents.type.js +10 -10
- package/src/js/types/libs.type.js +27 -27
- package/src/js/types/model.type.js +11 -11
- package/src/js/types/recyclerview.type.js +11 -11
- package/src/js/types/resize-observer.type.js +18 -18
- package/src/js/types/view.group.type.js +12 -12
- package/src/js/types/view.option.type.js +14 -14
- package/src/js/types/view.type.js +10 -10
- package/src/js/utils/guard.js +46 -46
- package/src/js/utils/ievents.js +83 -83
- package/src/js/utils/istorage.js +60 -60
- package/src/js/utils/libs.js +618 -618
- package/src/js/utils/selective.js +385 -385
- package/src/js/views/group-view.js +102 -102
- package/src/js/views/option-view.js +152 -152
|
@@ -1,103 +1,103 @@
|
|
|
1
|
-
import { View } from "../core/base/view";
|
|
2
|
-
import { Libs } from "../utils/libs";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* @extends {View<GroupViewTags>}
|
|
6
|
-
*/
|
|
7
|
-
export class GroupView extends View {
|
|
8
|
-
/** @type {GroupViewResult} */
|
|
9
|
-
view;
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Renders the group view structure (header + items container), sets ARIA attributes,
|
|
13
|
-
* and appends the root element to the parent container.
|
|
14
|
-
*/
|
|
15
|
-
render() {
|
|
16
|
-
const group_id = Libs.randomString(7);
|
|
17
|
-
|
|
18
|
-
this.view = Libs.mountView({
|
|
19
|
-
GroupView: {
|
|
20
|
-
tag: {
|
|
21
|
-
node: "div",
|
|
22
|
-
classList: ["selective-ui-group"],
|
|
23
|
-
role: "group",
|
|
24
|
-
ariaLabelledby: `seui-${group_id}-header`,
|
|
25
|
-
id: `seui-${group_id}-group`
|
|
26
|
-
},
|
|
27
|
-
child: {
|
|
28
|
-
GroupHeader: {
|
|
29
|
-
tag: {
|
|
30
|
-
node: "div",
|
|
31
|
-
classList: ["selective-ui-group-header"],
|
|
32
|
-
role: "presentation",
|
|
33
|
-
id: `seui-${group_id}-header`
|
|
34
|
-
}
|
|
35
|
-
},
|
|
36
|
-
GroupItems: {
|
|
37
|
-
tag: {
|
|
38
|
-
node: "div",
|
|
39
|
-
classList: ["selective-ui-group-items"],
|
|
40
|
-
role: "group"
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
this.parent.appendChild(this.view.view);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Performs a lightweight refresh of the view (currently updates the header label).
|
|
52
|
-
*/
|
|
53
|
-
update() {
|
|
54
|
-
this.updateLabel();
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Updates the group header text content if a label is provided.
|
|
59
|
-
*
|
|
60
|
-
* @param {string|null} [label=null] - The new label to display; if null, keeps current.
|
|
61
|
-
*/
|
|
62
|
-
updateLabel(label = null) {
|
|
63
|
-
const headerEl = this.view.tags.GroupHeader;
|
|
64
|
-
if (label !== null) {
|
|
65
|
-
headerEl.textContent = label;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Returns the container element that holds all option/item views in this group.
|
|
71
|
-
*
|
|
72
|
-
* @returns {HTMLDivElement} - The items container element.
|
|
73
|
-
*/
|
|
74
|
-
getItemsContainer() {
|
|
75
|
-
return this.view.tags.GroupItems;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Toggles the group's visibility based on whether any child item is visible.
|
|
80
|
-
* Hides the entire group when all children are hidden.
|
|
81
|
-
*/
|
|
82
|
-
updateVisibility() {
|
|
83
|
-
const items = this.view.tags.GroupItems;
|
|
84
|
-
const visibleItems = Array.from(items.children)
|
|
85
|
-
.filter(child => !child.classList.contains("hide"));
|
|
86
|
-
|
|
87
|
-
const isVisible = visibleItems.length > 0;
|
|
88
|
-
this.view.view.classList.toggle("hide", !isVisible);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Sets the collapsed state on the group and updates ARIA attributes accordingly.
|
|
93
|
-
*
|
|
94
|
-
* @param {boolean} collapsed - True to collapse; false to expand.
|
|
95
|
-
*/
|
|
96
|
-
setCollapsed(collapsed) {
|
|
97
|
-
this.view.view.classList.toggle("collapsed", collapsed);
|
|
98
|
-
this.view.tags.GroupHeader.setAttribute(
|
|
99
|
-
"aria-expanded",
|
|
100
|
-
collapsed ? "false" : "true"
|
|
101
|
-
);
|
|
102
|
-
}
|
|
1
|
+
import { View } from "../core/base/view";
|
|
2
|
+
import { Libs } from "../utils/libs";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @extends {View<GroupViewTags>}
|
|
6
|
+
*/
|
|
7
|
+
export class GroupView extends View {
|
|
8
|
+
/** @type {GroupViewResult} */
|
|
9
|
+
view;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Renders the group view structure (header + items container), sets ARIA attributes,
|
|
13
|
+
* and appends the root element to the parent container.
|
|
14
|
+
*/
|
|
15
|
+
render() {
|
|
16
|
+
const group_id = Libs.randomString(7);
|
|
17
|
+
|
|
18
|
+
this.view = Libs.mountView({
|
|
19
|
+
GroupView: {
|
|
20
|
+
tag: {
|
|
21
|
+
node: "div",
|
|
22
|
+
classList: ["selective-ui-group"],
|
|
23
|
+
role: "group",
|
|
24
|
+
ariaLabelledby: `seui-${group_id}-header`,
|
|
25
|
+
id: `seui-${group_id}-group`
|
|
26
|
+
},
|
|
27
|
+
child: {
|
|
28
|
+
GroupHeader: {
|
|
29
|
+
tag: {
|
|
30
|
+
node: "div",
|
|
31
|
+
classList: ["selective-ui-group-header"],
|
|
32
|
+
role: "presentation",
|
|
33
|
+
id: `seui-${group_id}-header`
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
GroupItems: {
|
|
37
|
+
tag: {
|
|
38
|
+
node: "div",
|
|
39
|
+
classList: ["selective-ui-group-items"],
|
|
40
|
+
role: "group"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
this.parent.appendChild(this.view.view);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Performs a lightweight refresh of the view (currently updates the header label).
|
|
52
|
+
*/
|
|
53
|
+
update() {
|
|
54
|
+
this.updateLabel();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Updates the group header text content if a label is provided.
|
|
59
|
+
*
|
|
60
|
+
* @param {string|null} [label=null] - The new label to display; if null, keeps current.
|
|
61
|
+
*/
|
|
62
|
+
updateLabel(label = null) {
|
|
63
|
+
const headerEl = this.view.tags.GroupHeader;
|
|
64
|
+
if (label !== null) {
|
|
65
|
+
headerEl.textContent = label;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Returns the container element that holds all option/item views in this group.
|
|
71
|
+
*
|
|
72
|
+
* @returns {HTMLDivElement} - The items container element.
|
|
73
|
+
*/
|
|
74
|
+
getItemsContainer() {
|
|
75
|
+
return this.view.tags.GroupItems;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Toggles the group's visibility based on whether any child item is visible.
|
|
80
|
+
* Hides the entire group when all children are hidden.
|
|
81
|
+
*/
|
|
82
|
+
updateVisibility() {
|
|
83
|
+
const items = this.view.tags.GroupItems;
|
|
84
|
+
const visibleItems = Array.from(items.children)
|
|
85
|
+
.filter(child => !child.classList.contains("hide"));
|
|
86
|
+
|
|
87
|
+
const isVisible = visibleItems.length > 0;
|
|
88
|
+
this.view.view.classList.toggle("hide", !isVisible);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Sets the collapsed state on the group and updates ARIA attributes accordingly.
|
|
93
|
+
*
|
|
94
|
+
* @param {boolean} collapsed - True to collapse; false to expand.
|
|
95
|
+
*/
|
|
96
|
+
setCollapsed(collapsed) {
|
|
97
|
+
this.view.view.classList.toggle("collapsed", collapsed);
|
|
98
|
+
this.view.tags.GroupHeader.setAttribute(
|
|
99
|
+
"aria-expanded",
|
|
100
|
+
collapsed ? "false" : "true"
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
103
|
}
|
|
@@ -1,153 +1,153 @@
|
|
|
1
|
-
|
|
2
|
-
import { View } from "../core/base/view";
|
|
3
|
-
import { Libs } from "../utils/libs";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* @extends {View<OptionViewTags>}
|
|
7
|
-
*/
|
|
8
|
-
export class OptionView extends View {
|
|
9
|
-
/** @type {OptionViewResult} */
|
|
10
|
-
view;
|
|
11
|
-
|
|
12
|
-
isMultiple = false;
|
|
13
|
-
hasImage = false;
|
|
14
|
-
optionConfig = null;
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Renders the option view DOM structure (input, optional image, label),
|
|
18
|
-
* sets ARIA attributes/IDs, mounts into parent, and applies initial config.
|
|
19
|
-
*/
|
|
20
|
-
render() {
|
|
21
|
-
const viewClass = ["selective-ui-option-view"];
|
|
22
|
-
const opt_id = Libs.randomString(7);
|
|
23
|
-
const inputID = `option_${opt_id}`;
|
|
24
|
-
|
|
25
|
-
if (this.isMultiple) {
|
|
26
|
-
viewClass.push("multiple");
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
if (this.hasImage) {
|
|
30
|
-
viewClass.push("has-image");
|
|
31
|
-
viewClass.push(`image-${this.optionConfig?.imagePosition}`);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const childStructure = {
|
|
35
|
-
OptionInput: {
|
|
36
|
-
tag: {
|
|
37
|
-
node: "input",
|
|
38
|
-
type: this.isMultiple ? "checkbox" : "radio",
|
|
39
|
-
classList: "allow-choice",
|
|
40
|
-
id: inputID
|
|
41
|
-
}
|
|
42
|
-
},
|
|
43
|
-
...(this.hasImage && {
|
|
44
|
-
OptionImage: {
|
|
45
|
-
tag: {
|
|
46
|
-
node: "img",
|
|
47
|
-
classList: "option-image",
|
|
48
|
-
style: {
|
|
49
|
-
width: this.optionConfig?.imageWidth || "60px",
|
|
50
|
-
height: this.optionConfig?.imageHeight || "60px",
|
|
51
|
-
borderRadius: this.optionConfig?.imageBorderRadius || "4px"
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}),
|
|
56
|
-
OptionLabel: {
|
|
57
|
-
tag: {
|
|
58
|
-
node: "label",
|
|
59
|
-
htmlFor: inputID,
|
|
60
|
-
classList: [
|
|
61
|
-
`align-vertical-${this.optionConfig?.labelValign}`,
|
|
62
|
-
`align-horizontal-${this.optionConfig?.labelHalign}`
|
|
63
|
-
]
|
|
64
|
-
},
|
|
65
|
-
child: {
|
|
66
|
-
LabelContent: { tag: { node: "div" } }
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
this.view = Libs.mountView({
|
|
72
|
-
OptionView: {
|
|
73
|
-
tag: {
|
|
74
|
-
node: "div",
|
|
75
|
-
id: `seui-${opt_id}-option`,
|
|
76
|
-
classList: viewClass,
|
|
77
|
-
role: "option",
|
|
78
|
-
ariaSelected: "false",
|
|
79
|
-
tabIndex: "-1"
|
|
80
|
-
},
|
|
81
|
-
child: childStructure
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
this.parent.appendChild(this.view.view);
|
|
86
|
-
|
|
87
|
-
this.applyConfigToDOM();
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Refreshes the option view by reapplying configuration (classes, alignments, image styles).
|
|
92
|
-
*/
|
|
93
|
-
update() {
|
|
94
|
-
this.applyConfigToDOM();
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Applies current configuration to the DOM in a minimal, fast way:
|
|
99
|
-
* - Set root/label classes in a single assignment (less DOM churn),
|
|
100
|
-
* - Ensure input type matches selection mode,
|
|
101
|
-
* - Create/remove image element only when needed, update its styles.
|
|
102
|
-
*/
|
|
103
|
-
applyConfigToDOM() {
|
|
104
|
-
const v = this.view;
|
|
105
|
-
if (!v || !v.view) return;
|
|
106
|
-
|
|
107
|
-
const root = v.view;
|
|
108
|
-
const input = v.tags?.OptionInput;
|
|
109
|
-
const label = v.tags?.OptionLabel;
|
|
110
|
-
const isMultiple = !!this.isMultiple;
|
|
111
|
-
const hasImage = !!this.hasImage;
|
|
112
|
-
const imagePos = this.optionConfig?.imagePosition || 'right';
|
|
113
|
-
const imageWidth = this.optionConfig?.imageWidth || '60px';
|
|
114
|
-
const imageHeight = this.optionConfig?.imageHeight || '60px';
|
|
115
|
-
const imageRadius = this.optionConfig?.imageBorderRadius || '4px';
|
|
116
|
-
const vAlign = this.optionConfig?.labelValign || 'center';
|
|
117
|
-
const hAlign = this.optionConfig?.labelHalign || 'left';
|
|
118
|
-
|
|
119
|
-
const rootClasses = ['selective-ui-option-view'];
|
|
120
|
-
if (isMultiple) rootClasses.push('multiple');
|
|
121
|
-
if (hasImage) {
|
|
122
|
-
rootClasses.push('has-image', `image-${imagePos}`);
|
|
123
|
-
}
|
|
124
|
-
root.className = rootClasses.join(' ');
|
|
125
|
-
|
|
126
|
-
if (input) {
|
|
127
|
-
const desiredType = isMultiple ? 'checkbox' : 'radio';
|
|
128
|
-
if (input.type !== desiredType) input.type = desiredType;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (label) {
|
|
132
|
-
label.className = `align-vertical-${vAlign} align-horizontal-${hAlign}`;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
let image = v.tags?.OptionImage;
|
|
136
|
-
if (hasImage) {
|
|
137
|
-
if (!image) {
|
|
138
|
-
image = document.createElement('img');
|
|
139
|
-
image.className = 'option-image';
|
|
140
|
-
if (label && label.parentElement) root.insertBefore(image, label);
|
|
141
|
-
else root.appendChild(image);
|
|
142
|
-
v.tags.OptionImage = image;
|
|
143
|
-
}
|
|
144
|
-
const style = image.style;
|
|
145
|
-
style.width = imageWidth;
|
|
146
|
-
style.height = imageHeight;
|
|
147
|
-
style.borderRadius = imageRadius;
|
|
148
|
-
} else if (image) {
|
|
149
|
-
image.remove();
|
|
150
|
-
v.tags.OptionImage = null;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
1
|
+
|
|
2
|
+
import { View } from "../core/base/view";
|
|
3
|
+
import { Libs } from "../utils/libs";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @extends {View<OptionViewTags>}
|
|
7
|
+
*/
|
|
8
|
+
export class OptionView extends View {
|
|
9
|
+
/** @type {OptionViewResult} */
|
|
10
|
+
view;
|
|
11
|
+
|
|
12
|
+
isMultiple = false;
|
|
13
|
+
hasImage = false;
|
|
14
|
+
optionConfig = null;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Renders the option view DOM structure (input, optional image, label),
|
|
18
|
+
* sets ARIA attributes/IDs, mounts into parent, and applies initial config.
|
|
19
|
+
*/
|
|
20
|
+
render() {
|
|
21
|
+
const viewClass = ["selective-ui-option-view"];
|
|
22
|
+
const opt_id = Libs.randomString(7);
|
|
23
|
+
const inputID = `option_${opt_id}`;
|
|
24
|
+
|
|
25
|
+
if (this.isMultiple) {
|
|
26
|
+
viewClass.push("multiple");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (this.hasImage) {
|
|
30
|
+
viewClass.push("has-image");
|
|
31
|
+
viewClass.push(`image-${this.optionConfig?.imagePosition}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const childStructure = {
|
|
35
|
+
OptionInput: {
|
|
36
|
+
tag: {
|
|
37
|
+
node: "input",
|
|
38
|
+
type: this.isMultiple ? "checkbox" : "radio",
|
|
39
|
+
classList: "allow-choice",
|
|
40
|
+
id: inputID
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
...(this.hasImage && {
|
|
44
|
+
OptionImage: {
|
|
45
|
+
tag: {
|
|
46
|
+
node: "img",
|
|
47
|
+
classList: "option-image",
|
|
48
|
+
style: {
|
|
49
|
+
width: this.optionConfig?.imageWidth || "60px",
|
|
50
|
+
height: this.optionConfig?.imageHeight || "60px",
|
|
51
|
+
borderRadius: this.optionConfig?.imageBorderRadius || "4px"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}),
|
|
56
|
+
OptionLabel: {
|
|
57
|
+
tag: {
|
|
58
|
+
node: "label",
|
|
59
|
+
htmlFor: inputID,
|
|
60
|
+
classList: [
|
|
61
|
+
`align-vertical-${this.optionConfig?.labelValign}`,
|
|
62
|
+
`align-horizontal-${this.optionConfig?.labelHalign}`
|
|
63
|
+
]
|
|
64
|
+
},
|
|
65
|
+
child: {
|
|
66
|
+
LabelContent: { tag: { node: "div" } }
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
this.view = Libs.mountView({
|
|
72
|
+
OptionView: {
|
|
73
|
+
tag: {
|
|
74
|
+
node: "div",
|
|
75
|
+
id: `seui-${opt_id}-option`,
|
|
76
|
+
classList: viewClass,
|
|
77
|
+
role: "option",
|
|
78
|
+
ariaSelected: "false",
|
|
79
|
+
tabIndex: "-1"
|
|
80
|
+
},
|
|
81
|
+
child: childStructure
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
this.parent.appendChild(this.view.view);
|
|
86
|
+
|
|
87
|
+
this.applyConfigToDOM();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Refreshes the option view by reapplying configuration (classes, alignments, image styles).
|
|
92
|
+
*/
|
|
93
|
+
update() {
|
|
94
|
+
this.applyConfigToDOM();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Applies current configuration to the DOM in a minimal, fast way:
|
|
99
|
+
* - Set root/label classes in a single assignment (less DOM churn),
|
|
100
|
+
* - Ensure input type matches selection mode,
|
|
101
|
+
* - Create/remove image element only when needed, update its styles.
|
|
102
|
+
*/
|
|
103
|
+
applyConfigToDOM() {
|
|
104
|
+
const v = this.view;
|
|
105
|
+
if (!v || !v.view) return;
|
|
106
|
+
|
|
107
|
+
const root = v.view;
|
|
108
|
+
const input = v.tags?.OptionInput;
|
|
109
|
+
const label = v.tags?.OptionLabel;
|
|
110
|
+
const isMultiple = !!this.isMultiple;
|
|
111
|
+
const hasImage = !!this.hasImage;
|
|
112
|
+
const imagePos = this.optionConfig?.imagePosition || 'right';
|
|
113
|
+
const imageWidth = this.optionConfig?.imageWidth || '60px';
|
|
114
|
+
const imageHeight = this.optionConfig?.imageHeight || '60px';
|
|
115
|
+
const imageRadius = this.optionConfig?.imageBorderRadius || '4px';
|
|
116
|
+
const vAlign = this.optionConfig?.labelValign || 'center';
|
|
117
|
+
const hAlign = this.optionConfig?.labelHalign || 'left';
|
|
118
|
+
|
|
119
|
+
const rootClasses = ['selective-ui-option-view'];
|
|
120
|
+
if (isMultiple) rootClasses.push('multiple');
|
|
121
|
+
if (hasImage) {
|
|
122
|
+
rootClasses.push('has-image', `image-${imagePos}`);
|
|
123
|
+
}
|
|
124
|
+
root.className = rootClasses.join(' ');
|
|
125
|
+
|
|
126
|
+
if (input) {
|
|
127
|
+
const desiredType = isMultiple ? 'checkbox' : 'radio';
|
|
128
|
+
if (input.type !== desiredType) input.type = desiredType;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (label) {
|
|
132
|
+
label.className = `align-vertical-${vAlign} align-horizontal-${hAlign}`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
let image = v.tags?.OptionImage;
|
|
136
|
+
if (hasImage) {
|
|
137
|
+
if (!image) {
|
|
138
|
+
image = document.createElement('img');
|
|
139
|
+
image.className = 'option-image';
|
|
140
|
+
if (label && label.parentElement) root.insertBefore(image, label);
|
|
141
|
+
else root.appendChild(image);
|
|
142
|
+
v.tags.OptionImage = image;
|
|
143
|
+
}
|
|
144
|
+
const style = image.style;
|
|
145
|
+
style.width = imageWidth;
|
|
146
|
+
style.height = imageHeight;
|
|
147
|
+
style.borderRadius = imageRadius;
|
|
148
|
+
} else if (image) {
|
|
149
|
+
image.remove();
|
|
150
|
+
v.tags.OptionImage = null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
153
|
}
|