composite-select 1.0.3 → 1.0.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/README.md +4 -0
- package/composition/list-up-down-navi/ListManager.css +203 -0
- package/package.json +1 -1
- package/commitlint.config.js +0 -3
- package/composition/composite-select/CompositeManager.js +0 -43
- package/composition/composite-select/composite-select.js +0 -199
- package/composition/composite-select/debounce.js +0 -10
- package/composition/composite-select/helpers.js +0 -96
- package/composition/composite-select/react.js +0 -189
- package/composition/container/ContainerManager.js +0 -76
- package/composition/options-section/OptionsSectionManager.js +0 -486
- package/composition/options-section/options-section.js +0 -245
- package/composition/options-section/react.js +0 -90
- package/composition/selected-section/SelectedSectionManager.js +0 -336
- package/composition/selected-section/react.js +0 -91
- package/composition/selected-section/selected-section.js +0 -207
- package/composition/unbind/clickOutside.js +0 -17
- package/diff/coreBundle.patch +0 -13
- package/diff/recorderApp.patch +0 -13
- package/madooei.tar.gz +0 -0
- package/release.config.js +0 -3
- package/test/lib.d.ts +0 -6
- package/test/lib.js +0 -30
package/README.md
CHANGED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
@keyframes spin {
|
|
2
|
+
from {
|
|
3
|
+
transform: rotate(0deg);
|
|
4
|
+
}
|
|
5
|
+
to {
|
|
6
|
+
transform: rotate(360deg);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.options {
|
|
11
|
+
flex: 1;
|
|
12
|
+
overflow-y: auto;
|
|
13
|
+
min-height: 0;
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-direction: column;
|
|
16
|
+
gap: 2px;
|
|
17
|
+
padding: 8px;
|
|
18
|
+
background: #fff;
|
|
19
|
+
border: 1px solid #ccc;
|
|
20
|
+
border-radius: 4px;
|
|
21
|
+
|
|
22
|
+
.element {
|
|
23
|
+
cursor: pointer;
|
|
24
|
+
padding: 8px 12px;
|
|
25
|
+
border-left: 3px solid transparent;
|
|
26
|
+
transition: background-color 150ms;
|
|
27
|
+
display: flex;
|
|
28
|
+
align-items: center;
|
|
29
|
+
font-size: 14px;
|
|
30
|
+
color: #3c4043;
|
|
31
|
+
user-select: none;
|
|
32
|
+
|
|
33
|
+
& > * {
|
|
34
|
+
pointer-events: none;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
&:hover {
|
|
38
|
+
background: rgba(0, 0, 0, 0.04);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
&.selected {
|
|
42
|
+
background: rgba(26, 115, 232, 0.08);
|
|
43
|
+
border-left-color: #1a73e8;
|
|
44
|
+
color: #1a73e8;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
&.highlighted {
|
|
48
|
+
outline: 2px solid #1a73e8;
|
|
49
|
+
outline-offset: -2px;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.empty-msg {
|
|
54
|
+
padding: 24px;
|
|
55
|
+
text-align: center;
|
|
56
|
+
color: #5f6368;
|
|
57
|
+
font-style: italic;
|
|
58
|
+
font-size: 14px;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.filter {
|
|
63
|
+
flex-shrink: 0;
|
|
64
|
+
padding: 12px 12px 8px;
|
|
65
|
+
display: flex;
|
|
66
|
+
align-items: center;
|
|
67
|
+
gap: 8px;
|
|
68
|
+
border: 1px solid #ccc;
|
|
69
|
+
border-bottom: none;
|
|
70
|
+
border-radius: 4px 4px 0 0;
|
|
71
|
+
background: #fff;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.input-wrapper {
|
|
75
|
+
position: relative;
|
|
76
|
+
display: grid;
|
|
77
|
+
flex: 1;
|
|
78
|
+
|
|
79
|
+
&::after {
|
|
80
|
+
content: "";
|
|
81
|
+
grid-area: 1 / 1;
|
|
82
|
+
border-radius: 4px;
|
|
83
|
+
box-shadow: 0 0 0 1px #99999b;
|
|
84
|
+
transition: box-shadow 150ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
85
|
+
pointer-events: none;
|
|
86
|
+
z-index: 0;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
&:focus-within::after {
|
|
90
|
+
box-shadow: 0 0 0 2px #1a73e8;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
label {
|
|
94
|
+
grid-area: 1 / 1;
|
|
95
|
+
align-self: start;
|
|
96
|
+
justify-self: start;
|
|
97
|
+
margin-left: 7px;
|
|
98
|
+
margin-top: 9px;
|
|
99
|
+
font-size: 16px;
|
|
100
|
+
line-height: 1;
|
|
101
|
+
color: #5f6368;
|
|
102
|
+
pointer-events: none;
|
|
103
|
+
transition:
|
|
104
|
+
transform 150ms,
|
|
105
|
+
font-size 150ms,
|
|
106
|
+
color 150ms,
|
|
107
|
+
margin-top 150ms;
|
|
108
|
+
background: #fff;
|
|
109
|
+
padding: 0 4px;
|
|
110
|
+
z-index: 3;
|
|
111
|
+
transform: translateY(0);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
input {
|
|
115
|
+
grid-area: 1 / 1;
|
|
116
|
+
width: 100%;
|
|
117
|
+
border: none;
|
|
118
|
+
background: transparent;
|
|
119
|
+
padding: 8px 12px;
|
|
120
|
+
padding-right: 36px;
|
|
121
|
+
font-size: 16px;
|
|
122
|
+
font-family: inherit;
|
|
123
|
+
outline: none;
|
|
124
|
+
box-sizing: border-box;
|
|
125
|
+
z-index: 2;
|
|
126
|
+
|
|
127
|
+
&::placeholder {
|
|
128
|
+
color: transparent;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
&:not(:placeholder-shown) + label,
|
|
132
|
+
&:focus + label {
|
|
133
|
+
margin-top: 0;
|
|
134
|
+
transform: translateY(-50%);
|
|
135
|
+
font-size: 12px;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
&:focus + label {
|
|
139
|
+
color: #1a73e8;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.spinner {
|
|
145
|
+
position: absolute;
|
|
146
|
+
right: 10px;
|
|
147
|
+
top: 50%;
|
|
148
|
+
margin-top: -11px;
|
|
149
|
+
width: 22px;
|
|
150
|
+
height: 22px;
|
|
151
|
+
background-image: url("data:image/svg+xml,%3Csvg fill='%231a73e8' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12,4a8,8,0,0,1,7.89,6.7A1.53,1.53,0,0,0,21.38,12h0a1.5,1.5,0,0,0,1.48-1.75,11,11,0,0,0-21.72,0A1.5,1.5,0,0,0,2.62,12h0a1.53,1.53,0,0,0,1.49-1.3A8,8,0,0,1,12,4Z'/%3E%3C/svg%3E");
|
|
152
|
+
background-repeat: no-repeat;
|
|
153
|
+
background-size: contain;
|
|
154
|
+
animation: spin 0.75s linear infinite;
|
|
155
|
+
display: none;
|
|
156
|
+
flex-shrink: 0;
|
|
157
|
+
z-index: 4;
|
|
158
|
+
|
|
159
|
+
&.loading {
|
|
160
|
+
display: block;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.gcp-css {
|
|
165
|
+
font-family: inherit;
|
|
166
|
+
font-size: 14px;
|
|
167
|
+
font-weight: 500;
|
|
168
|
+
height: 36px;
|
|
169
|
+
padding: 0 16px;
|
|
170
|
+
border-radius: 4px;
|
|
171
|
+
cursor: pointer;
|
|
172
|
+
transition:
|
|
173
|
+
background-color 150ms,
|
|
174
|
+
box-shadow 150ms;
|
|
175
|
+
display: inline-flex;
|
|
176
|
+
align-items: center;
|
|
177
|
+
justify-content: center;
|
|
178
|
+
border: 1px solid transparent;
|
|
179
|
+
outline: none;
|
|
180
|
+
box-sizing: border-box;
|
|
181
|
+
background-color: #1a73e8;
|
|
182
|
+
color: #fff;
|
|
183
|
+
margin-right: 8px;
|
|
184
|
+
margin-bottom: 8px;
|
|
185
|
+
|
|
186
|
+
&:hover {
|
|
187
|
+
background-color: #1765cc;
|
|
188
|
+
box-shadow:
|
|
189
|
+
0 1px 2px 0 rgba(66, 133, 244, 0.3),
|
|
190
|
+
0 1px 3px 1px rgba(66, 133, 244, 0.15);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
&.white {
|
|
194
|
+
background-color: transparent;
|
|
195
|
+
color: #1a73e8;
|
|
196
|
+
border-color: #ccc;
|
|
197
|
+
|
|
198
|
+
&:hover {
|
|
199
|
+
background-color: rgba(26, 115, 232, 0.04);
|
|
200
|
+
box-shadow: none;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
package/package.json
CHANGED
package/commitlint.config.js
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { ContainerManager } from "../container/ContainerManager.js";
|
|
2
|
-
import { OptionsSectionManager } from "../options-section/OptionsSectionManager.js";
|
|
3
|
-
import { SelectedSectionManager } from "../selected-section/SelectedSectionManager.js";
|
|
4
|
-
import { clickOutside } from "../unbind/clickOutside.js";
|
|
5
|
-
export class CompositeManager {
|
|
6
|
-
container;
|
|
7
|
-
selected;
|
|
8
|
-
options;
|
|
9
|
-
_unbindClickOutside;
|
|
10
|
-
_parent;
|
|
11
|
-
constructor(parent, options = {}) {
|
|
12
|
-
this._parent = parent;
|
|
13
|
-
this.container = new ContainerManager(this._parent, options.container);
|
|
14
|
-
{
|
|
15
|
-
const target = this.container.getTarget();
|
|
16
|
-
const div = document.createElement("div");
|
|
17
|
-
target.appendChild(div);
|
|
18
|
-
this.selected = new SelectedSectionManager(div, options.select);
|
|
19
|
-
this.selected.getSubscriber().bind("onFocus", (e) => {
|
|
20
|
-
this.container.show();
|
|
21
|
-
this.options.setFocus();
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
{
|
|
25
|
-
const popover = this.container.getPopover();
|
|
26
|
-
const div = document.createElement("div");
|
|
27
|
-
popover.appendChild(div);
|
|
28
|
-
this.options = new OptionsSectionManager(div, options.options);
|
|
29
|
-
}
|
|
30
|
-
this._unbindClickOutside = clickOutside([this.container.getParent()], () => {
|
|
31
|
-
this.container.hide();
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
destroy() {
|
|
35
|
-
this._unbindClickOutside();
|
|
36
|
-
this.options.destroy();
|
|
37
|
-
if (typeof this.selected.destroy === "function") {
|
|
38
|
-
this.selected.destroy();
|
|
39
|
-
}
|
|
40
|
-
this.container.destroy();
|
|
41
|
-
this._parent.innerHTML = "";
|
|
42
|
-
}
|
|
43
|
-
}
|
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
import { CompositeManager } from "./CompositeManager.js";
|
|
2
|
-
/**
|
|
3
|
-
* CompositeSelect web component — wraps CompositeManager.
|
|
4
|
-
*
|
|
5
|
-
* Does NOT use shadow DOM because ContainerManager creates a <div popover>
|
|
6
|
-
* whose positioning CSS classes (cover-bottom, bottom, etc. from popover.css)
|
|
7
|
-
* are global and cannot penetrate shadow DOM boundaries.
|
|
8
|
-
* The host page must load the required CSS via <link> tags, exactly as ContainerManager.html does.
|
|
9
|
-
*
|
|
10
|
-
* Attribute prefixes:
|
|
11
|
-
* selected-* → SelectedSectionManager properties / events
|
|
12
|
-
* options-* → OptionsSectionManager properties / events
|
|
13
|
-
* container-* → ContainerManager properties / events
|
|
14
|
-
*/
|
|
15
|
-
export class CompositeSelect extends HTMLElement {
|
|
16
|
-
_manager = null;
|
|
17
|
-
_mountPoint;
|
|
18
|
-
static get observedAttributes() {
|
|
19
|
-
return [
|
|
20
|
-
// selected-* attributes
|
|
21
|
-
"selected-selected",
|
|
22
|
-
"selected-show-input",
|
|
23
|
-
"selected-value",
|
|
24
|
-
"selected-label",
|
|
25
|
-
"selected-disabled",
|
|
26
|
-
"selected-error",
|
|
27
|
-
"selected-loading",
|
|
28
|
-
"selected-show-delete",
|
|
29
|
-
// options-* attributes
|
|
30
|
-
"options-options",
|
|
31
|
-
"options-loading",
|
|
32
|
-
"options-value",
|
|
33
|
-
"options-label",
|
|
34
|
-
"options-disabled",
|
|
35
|
-
"options-max-height",
|
|
36
|
-
"options-show-footer",
|
|
37
|
-
"options-show-filter",
|
|
38
|
-
// container-* attributes
|
|
39
|
-
"container-position",
|
|
40
|
-
"container-offset",
|
|
41
|
-
];
|
|
42
|
-
}
|
|
43
|
-
constructor() {
|
|
44
|
-
super();
|
|
45
|
-
// Light DOM — no shadow DOM (popover CSS must be global)
|
|
46
|
-
this._mountPoint = document.createElement("div");
|
|
47
|
-
}
|
|
48
|
-
connectedCallback() {
|
|
49
|
-
if (!this._mountPoint.parentNode) {
|
|
50
|
-
this.appendChild(this._mountPoint);
|
|
51
|
-
}
|
|
52
|
-
if (this._manager)
|
|
53
|
-
return;
|
|
54
|
-
const hasBoolAttr = (name, defaultVal) => this.hasAttribute(name) ? this.getAttribute(name) !== "false" : defaultVal;
|
|
55
|
-
// ── DEBUG: dump all boolean attrs at init time ────────────────────────────
|
|
56
|
-
const _bools = {
|
|
57
|
-
"selected-disabled": { hasAttr: this.hasAttribute("selected-disabled"), attrVal: this.getAttribute("selected-disabled"), resolved: hasBoolAttr("selected-disabled", false) },
|
|
58
|
-
"selected-error": { hasAttr: this.hasAttribute("selected-error"), attrVal: this.getAttribute("selected-error"), resolved: hasBoolAttr("selected-error", false) },
|
|
59
|
-
"selected-loading": { hasAttr: this.hasAttribute("selected-loading"), attrVal: this.getAttribute("selected-loading"), resolved: hasBoolAttr("selected-loading", false) },
|
|
60
|
-
"selected-show-input": { hasAttr: this.hasAttribute("selected-show-input"), attrVal: this.getAttribute("selected-show-input"), resolved: hasBoolAttr("selected-show-input", true) },
|
|
61
|
-
"selected-show-delete": { hasAttr: this.hasAttribute("selected-show-delete"), attrVal: this.getAttribute("selected-show-delete"), resolved: hasBoolAttr("selected-show-delete", true) },
|
|
62
|
-
"options-loading": { hasAttr: this.hasAttribute("options-loading"), attrVal: this.getAttribute("options-loading"), resolved: hasBoolAttr("options-loading", false) },
|
|
63
|
-
"options-disabled": { hasAttr: this.hasAttribute("options-disabled"), attrVal: this.getAttribute("options-disabled"), resolved: hasBoolAttr("options-disabled", false) },
|
|
64
|
-
"options-show-footer": { hasAttr: this.hasAttribute("options-show-footer"), attrVal: this.getAttribute("options-show-footer"), resolved: hasBoolAttr("options-show-footer", true) },
|
|
65
|
-
"options-show-filter": { hasAttr: this.hasAttribute("options-show-filter"), attrVal: this.getAttribute("options-show-filter"), resolved: hasBoolAttr("options-show-filter", true) },
|
|
66
|
-
};
|
|
67
|
-
for (const [attr, info] of Object.entries(_bools)) {
|
|
68
|
-
console.log(`[composite-select][connectedCallback] ${attr}: hasAttr=type >${typeof info.hasAttr}< value >${info.hasAttr}< attrVal=type >${typeof info.attrVal}< value >${info.attrVal}< resolved=type >${typeof info.resolved}< value >${info.resolved}<`);
|
|
69
|
-
}
|
|
70
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
71
|
-
this._manager = new CompositeManager(this._mountPoint, {
|
|
72
|
-
select: {
|
|
73
|
-
selected: this._parseJSON(this.getAttribute("selected-selected")) ?? [],
|
|
74
|
-
showInput: hasBoolAttr("selected-show-input", true),
|
|
75
|
-
value: this.getAttribute("selected-value") || "",
|
|
76
|
-
label: this.getAttribute("selected-label") || "",
|
|
77
|
-
disabled: hasBoolAttr("selected-disabled", false),
|
|
78
|
-
error: hasBoolAttr("selected-error", false),
|
|
79
|
-
loading: hasBoolAttr("selected-loading", false),
|
|
80
|
-
showDelete: hasBoolAttr("selected-show-delete", true),
|
|
81
|
-
},
|
|
82
|
-
options: {
|
|
83
|
-
options: this._parseJSON(this.getAttribute("options-options")) ?? [],
|
|
84
|
-
loading: hasBoolAttr("options-loading", false),
|
|
85
|
-
value: this.getAttribute("options-value") || "",
|
|
86
|
-
label: this.getAttribute("options-label") || "",
|
|
87
|
-
disabled: hasBoolAttr("options-disabled", false),
|
|
88
|
-
maxHeight: this.getAttribute("options-max-height") || undefined,
|
|
89
|
-
showFooter: hasBoolAttr("options-show-footer", true),
|
|
90
|
-
showFilter: hasBoolAttr("options-show-filter", true),
|
|
91
|
-
},
|
|
92
|
-
container: {},
|
|
93
|
-
});
|
|
94
|
-
if (this.getAttribute("container-position")) {
|
|
95
|
-
this._manager.container.setPosition(this.getAttribute("container-position"));
|
|
96
|
-
}
|
|
97
|
-
if (this.getAttribute("container-offset")) {
|
|
98
|
-
this._manager.container.setOffset(this.getAttribute("container-offset"));
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
disconnectedCallback() {
|
|
102
|
-
this._manager?.destroy();
|
|
103
|
-
this._manager = null;
|
|
104
|
-
}
|
|
105
|
-
attributeChangedCallback(name, _oldValue, newValue) {
|
|
106
|
-
if (!this._manager)
|
|
107
|
-
return;
|
|
108
|
-
const isTrue = newValue !== null && newValue !== "false";
|
|
109
|
-
// ── DEBUG: dump bool attrs as they change ─────────────────────────────────
|
|
110
|
-
const _boolAttrs = new Set(["selected-disabled", "selected-error", "selected-loading", "selected-show-input", "selected-show-delete", "options-loading", "options-disabled", "options-show-footer", "options-show-filter"]);
|
|
111
|
-
if (_boolAttrs.has(name)) {
|
|
112
|
-
console.log(`[composite-select][attributeChangedCallback] ${name}: isTrue=type >${typeof isTrue}< value >${isTrue}< newValue=type >${typeof newValue}< value >${newValue}< oldValue=type >${typeof _oldValue}< value >${_oldValue}<`);
|
|
113
|
-
}
|
|
114
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
115
|
-
switch (name) {
|
|
116
|
-
// ── selected ─────────────────────────────────────────────────────────
|
|
117
|
-
case "selected-selected": {
|
|
118
|
-
const parsed = this._parseJSON(newValue);
|
|
119
|
-
if (parsed !== undefined) {
|
|
120
|
-
this._manager.selected.setSelected(parsed);
|
|
121
|
-
}
|
|
122
|
-
break;
|
|
123
|
-
}
|
|
124
|
-
case "selected-show-input":
|
|
125
|
-
this._manager.selected.setShowInput(isTrue);
|
|
126
|
-
break;
|
|
127
|
-
case "selected-value":
|
|
128
|
-
this._manager.selected.setValue(newValue);
|
|
129
|
-
break;
|
|
130
|
-
case "selected-label":
|
|
131
|
-
this._manager.selected.setLabel(newValue);
|
|
132
|
-
break;
|
|
133
|
-
case "selected-disabled":
|
|
134
|
-
this._manager.selected.setDisabled(isTrue);
|
|
135
|
-
break;
|
|
136
|
-
case "selected-error":
|
|
137
|
-
this._manager.selected.setError(isTrue);
|
|
138
|
-
break;
|
|
139
|
-
case "selected-loading":
|
|
140
|
-
this._manager.selected.setLoading(isTrue);
|
|
141
|
-
break;
|
|
142
|
-
case "selected-show-delete":
|
|
143
|
-
this._manager.selected.setShowDelete(isTrue);
|
|
144
|
-
break;
|
|
145
|
-
// ── options ──────────────────────────────────────────────────────────
|
|
146
|
-
case "options-options": {
|
|
147
|
-
const parsed = this._parseJSON(newValue);
|
|
148
|
-
if (parsed !== undefined) {
|
|
149
|
-
this._manager.options.setOptions(parsed);
|
|
150
|
-
}
|
|
151
|
-
break;
|
|
152
|
-
}
|
|
153
|
-
case "options-loading":
|
|
154
|
-
this._manager.options.setLoading(isTrue);
|
|
155
|
-
break;
|
|
156
|
-
case "options-value":
|
|
157
|
-
this._manager.options.setValue(newValue);
|
|
158
|
-
break;
|
|
159
|
-
case "options-label":
|
|
160
|
-
this._manager.options.setLabel(newValue);
|
|
161
|
-
break;
|
|
162
|
-
case "options-disabled":
|
|
163
|
-
this._manager.options.setDisabled(isTrue);
|
|
164
|
-
break;
|
|
165
|
-
case "options-max-height":
|
|
166
|
-
this._manager.options.setMaxHeight(newValue);
|
|
167
|
-
break;
|
|
168
|
-
case "options-show-footer":
|
|
169
|
-
this._manager.options.setShowFooter(isTrue);
|
|
170
|
-
break;
|
|
171
|
-
case "options-show-filter":
|
|
172
|
-
this._manager.options.setShowFilter(isTrue);
|
|
173
|
-
break;
|
|
174
|
-
// ── container ────────────────────────────────────────────────────────
|
|
175
|
-
case "container-position":
|
|
176
|
-
this._manager.container.setPosition(newValue);
|
|
177
|
-
break;
|
|
178
|
-
case "container-offset":
|
|
179
|
-
this._manager.container.setOffset(newValue);
|
|
180
|
-
break;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
// ─── Accessor ─────────────────────────────────────────────────────────────
|
|
184
|
-
getManager() {
|
|
185
|
-
return this._manager;
|
|
186
|
-
}
|
|
187
|
-
_parseJSON(val) {
|
|
188
|
-
if (!val)
|
|
189
|
-
return undefined;
|
|
190
|
-
try {
|
|
191
|
-
return JSON.parse(val);
|
|
192
|
-
}
|
|
193
|
-
catch (e) {
|
|
194
|
-
console.error(`CompositeSelect: failed to parse JSON:`, val, e);
|
|
195
|
-
return undefined;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
customElements.define("composite-select", CompositeSelect);
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Toggles an item in the selected list: adds if missing, removes if present.
|
|
3
|
-
*/
|
|
4
|
-
export function togglePresenceOnTheList(selected, item) {
|
|
5
|
-
const tmp = [...selected];
|
|
6
|
-
const newList = tmp.filter((i) => String(i.id) !== String(item.id));
|
|
7
|
-
if (newList.length === tmp.length) {
|
|
8
|
-
tmp.push(item);
|
|
9
|
-
return tmp;
|
|
10
|
-
}
|
|
11
|
-
return newList;
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Resolves a list of IDs into full option objects and merges them into the current selected list (seed).
|
|
15
|
-
* This function is designed to preserve any existing items in the selected list (including "extra" items
|
|
16
|
-
* that might not exist in the current options array) while ensuring the final result is unique and deduplicated by ID.
|
|
17
|
-
*/
|
|
18
|
-
export function selectedFindDeduplicatedInOptionsByIds(options, ids, seed) {
|
|
19
|
-
let tmp = [];
|
|
20
|
-
if (seed) {
|
|
21
|
-
tmp = [...seed];
|
|
22
|
-
}
|
|
23
|
-
ids.forEach((id) => {
|
|
24
|
-
const found = tmp.some((i) => String(i.id) === String(id));
|
|
25
|
-
if (!found) {
|
|
26
|
-
tmp.push(options.find((o) => String(o.id) === String(id)));
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
tmp = deduplicateArrayById(tmp);
|
|
30
|
-
return tmp;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Updates the 'selected' flag on each option based on whether its ID exists in the provided selected list.
|
|
34
|
-
*/
|
|
35
|
-
export function markSelectedByIds(options, selectedIds) {
|
|
36
|
-
if (selectedIds.length === 0) {
|
|
37
|
-
return [...options];
|
|
38
|
-
}
|
|
39
|
-
return options.map((option) => {
|
|
40
|
-
const opt = { ...option };
|
|
41
|
-
opt.selected = selectedIds.includes(option.id);
|
|
42
|
-
return opt;
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Returns a new array with duplicate items removed based on their 'id' property.
|
|
47
|
-
* Interesting trick,
|
|
48
|
-
* if you know you will have more than one instance of object by the same id then pass these which you want to keep first.
|
|
49
|
-
* It is usefull in certain circumstances:
|
|
50
|
-
* For example when onFocus is fired we can unselect objects from selected by placing
|
|
51
|
-
* [...options, ...selected] in this order
|
|
52
|
-
* then unselected options will win over selected
|
|
53
|
-
* and then after that we can filter just these which are selected still and update manager.selected.setSelected()
|
|
54
|
-
* this way we will unselect
|
|
55
|
-
*/
|
|
56
|
-
export function deduplicateArrayById(arr) {
|
|
57
|
-
return arr.filter((item, index) => arr.findIndex((i) => String(i.id) === String(item.id)) === index);
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Sorts a list of elements by their ID string value.
|
|
61
|
-
*/
|
|
62
|
-
export function sortById(list, asc = true) {
|
|
63
|
-
return [...list].sort((a, b) => {
|
|
64
|
-
const aId = String(a.id);
|
|
65
|
-
const bId = String(b.id);
|
|
66
|
-
if (aId < bId) {
|
|
67
|
-
return asc ? -1 : 1;
|
|
68
|
-
}
|
|
69
|
-
if (aId > bId) {
|
|
70
|
-
return asc ? 1 : -1;
|
|
71
|
-
}
|
|
72
|
-
return 0;
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Wraps matches of search terms in the provided text with a highlight span.
|
|
77
|
-
* Supports multi-word search and case-insensitive matching.
|
|
78
|
-
*/
|
|
79
|
-
export function markSearchWithSpan(text, search) {
|
|
80
|
-
if (!search || !search.trim()) {
|
|
81
|
-
return text;
|
|
82
|
-
}
|
|
83
|
-
// Split by whitespace, deduplicate, and remove empty strings
|
|
84
|
-
const words = Array.from(new Set(search.trim().split(/\s+/).filter(Boolean)));
|
|
85
|
-
if (words.length === 0) {
|
|
86
|
-
return text;
|
|
87
|
-
}
|
|
88
|
-
// Escape special regex characters in each word
|
|
89
|
-
const escapedWords = words.map((word) => word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
90
|
-
// Sort by length descending to match longest words first (greedy matching)
|
|
91
|
-
escapedWords.sort((a, b) => b.length - a.length);
|
|
92
|
-
// Create a regex to match any of the words, case-insensitively, globally
|
|
93
|
-
const regex = new RegExp(`(${escapedWords.join("|")})`, "gi");
|
|
94
|
-
// Replace matches with the highlighted span
|
|
95
|
-
return text.replace(regex, '<span class="highlight">$1</span>');
|
|
96
|
-
}
|