composite-select 1.0.9 → 1.0.11
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/cjs/CenterAndHeightResizer.cjs +268 -0
- package/dist/cjs/CenterResizer.cjs +195 -0
- package/dist/cjs/composite-select/ContainerManager.html.cjs +650 -0
- package/dist/cjs/composite-select/composite-select.html.cjs +651 -0
- package/dist/cjs/options-section/OptionsSectionManager.html.cjs +362 -0
- package/dist/cjs/options-section/OptionsSectionManagerWebComponent.attributes.html.cjs +346 -0
- package/dist/cjs/options-section/OptionsSectionManagerWebComponent.html.cjs +354 -0
- package/dist/cjs/options-section/OptionsSectionManagerWebComponent.nocssrequest.html.cjs +345 -0
- package/dist/cjs/selected-section/SelectedSectionManager.html.cjs +336 -0
- package/dist/cjs/selected-section/SelectedSectionManager.templates.html.cjs +353 -0
- package/dist/cjs/selected-section/SelectedSectionManagerWebComponent.attributes.html.cjs +353 -0
- package/dist/cjs/selected-section/SelectedSectionManagerWebComponent.html.cjs +335 -0
- package/dist/cjs/selected-section/SelectedSectionManagerWebComponent.nocssrequest.html.cjs +341 -0
- package/dist/esm/CenterAndHeightResizer.js +268 -0
- package/dist/esm/CenterResizer.js +195 -0
- package/dist/types/CenterAndHeightResizer.d.ts +18 -0
- package/dist/types/CenterResizer.d.ts +16 -0
- package/dist/types/composite-select/ContainerManager.html.d.ts +1 -1
- package/dist/types/composite-select/composite-select.html.d.ts +1 -1
- package/dist/types/options-section/OptionsSectionManager.html.d.ts +1 -1
- package/dist/types/options-section/OptionsSectionManagerWebComponent.attributes.html.d.ts +1 -1
- package/dist/types/options-section/OptionsSectionManagerWebComponent.html.d.ts +1 -1
- package/dist/types/options-section/OptionsSectionManagerWebComponent.nocssrequest.html.d.ts +1 -1
- package/dist/types/selected-section/SelectedSectionManager.html.d.ts +1 -1
- package/dist/types/selected-section/SelectedSectionManager.templates.html.d.ts +1 -1
- package/dist/types/selected-section/SelectedSectionManagerWebComponent.attributes.html.d.ts +1 -1
- package/dist/types/selected-section/SelectedSectionManagerWebComponent.html.d.ts +1 -1
- package/dist/types/selected-section/SelectedSectionManagerWebComponent.nocssrequest.html.d.ts +1 -1
- package/package.json +1 -1
- package/js/CenterAndHeightResizer.js +0 -263
- package/js/CenterResizer.js +0 -190
- /package/dist/cjs/composite-select/{CompositeManager.js → CompositeManager.cjs} +0 -0
- /package/dist/cjs/composite-select/{composite-select.js → composite-select.cjs} +0 -0
- /package/dist/cjs/composite-select/{debounce.js → debounce.cjs} +0 -0
- /package/dist/cjs/composite-select/{helpers.js → helpers.cjs} +0 -0
- /package/dist/cjs/composite-select/{react.js → react.cjs} +0 -0
- /package/dist/cjs/container/{ContainerManager.js → ContainerManager.cjs} +0 -0
- /package/dist/cjs/options-section/{OptionsSectionManager.js → OptionsSectionManager.cjs} +0 -0
- /package/dist/cjs/options-section/{options-section.js → options-section.cjs} +0 -0
- /package/dist/cjs/options-section/{react.js → react.cjs} +0 -0
- /package/dist/cjs/selected-section/{SelectedSectionManager.js → SelectedSectionManager.cjs} +0 -0
- /package/dist/cjs/selected-section/{react.js → react.cjs} +0 -0
- /package/dist/cjs/selected-section/{selected-section.js → selected-section.cjs} +0 -0
- /package/dist/cjs/unbind/{clickOutside.js → clickOutside.cjs} +0 -0
|
@@ -0,0 +1,650 @@
|
|
|
1
|
+
import "../CenterAndHeightResizer.js";
|
|
2
|
+
import { CompositeManager } from "./CompositeManager.js";
|
|
3
|
+
import {} from "../types.js";
|
|
4
|
+
import { deduplicateArrayById, sortById, togglePresenceOnTheList, markSelectedByIds } from "./helpers.js";
|
|
5
|
+
import { searchNames as rawSearchNames, getSafeFreeOffset } from "./namesSource.js";
|
|
6
|
+
import debounce from "./debounce.js";
|
|
7
|
+
import { urlStateConfig } from "./urlManager.js";
|
|
8
|
+
const reloadLink = document.getElementById("reload-link");
|
|
9
|
+
if (reloadLink) {
|
|
10
|
+
reloadLink.href = window.location.pathname;
|
|
11
|
+
}
|
|
12
|
+
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
13
|
+
let instanceCounter = 0;
|
|
14
|
+
let globalIdCounter = getSafeFreeOffset();
|
|
15
|
+
const imgData = await fetch("../img/img.json").then((r) => r.json());
|
|
16
|
+
const updateUrlDisplay = (url = window.location.href) => {
|
|
17
|
+
const el = document.getElementById("url-display");
|
|
18
|
+
if (el)
|
|
19
|
+
el.textContent = url;
|
|
20
|
+
};
|
|
21
|
+
const init = (initialSelected = [], states = {}) => {
|
|
22
|
+
instanceCounter++;
|
|
23
|
+
const id = instanceCounter;
|
|
24
|
+
const section = document.createElement("div");
|
|
25
|
+
section.className = "demo-section";
|
|
26
|
+
section.innerHTML = `
|
|
27
|
+
<h2>Instance #${id}</h2>
|
|
28
|
+
<button class="gcp-css white destroy-btn" data-role="destroy">Destroy</button>
|
|
29
|
+
|
|
30
|
+
<div class="resizer-container">
|
|
31
|
+
<center-and-height-resizer
|
|
32
|
+
data-role="resizer"
|
|
33
|
+
left="${states.left || "100px"}"
|
|
34
|
+
center="${states.center || "600px"}"
|
|
35
|
+
height="${states.height || "auto"}"
|
|
36
|
+
style="padding: 12px;"
|
|
37
|
+
>
|
|
38
|
+
<div data-role="container"></div>
|
|
39
|
+
</center-and-height-resizer>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<div class="controls gcp-css" style="margin-bottom: 8px;">
|
|
43
|
+
<div class="controls-label">SelectedSectionManager (Top Section)</div>
|
|
44
|
+
|
|
45
|
+
<div class="gcp-css checkbox-wrapper">
|
|
46
|
+
<div class="checkbox-row">
|
|
47
|
+
<input type="checkbox" id="disabled-sel-${id}" data-role="disabled-sel" ${states.disabledSel ? "checked" : ""}>
|
|
48
|
+
<div class="content-cell"><label for="disabled-sel-${id}">Disabled</label></div>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<div class="gcp-css checkbox-wrapper">
|
|
53
|
+
<div class="checkbox-row">
|
|
54
|
+
<input type="checkbox" id="loading-sel-${id}" data-role="loading-sel" ${states.loadingSel ? "checked" : ""}>
|
|
55
|
+
<div class="content-cell"><label for="loading-sel-${id}">Loading</label></div>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<div class="gcp-css checkbox-wrapper">
|
|
60
|
+
<div class="checkbox-row">
|
|
61
|
+
<input type="checkbox" id="error-sel-${id}" data-role="error-sel" ${states.errorSel ? "checked" : ""}>
|
|
62
|
+
<div class="content-cell"><label for="error-sel-${id}">Error</label></div>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<div class="gcp-css checkbox-wrapper">
|
|
67
|
+
<div class="checkbox-row">
|
|
68
|
+
<input type="checkbox" id="show-input-sel-${id}" data-role="show-input-sel" ${states.showInputSel !== false ? "checked" : ""}>
|
|
69
|
+
<div class="content-cell"><label for="show-input-sel-${id}">Show Input</label></div>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<div class="gcp-css input-wrapper">
|
|
74
|
+
<input type="text" id="label-input-sel-${id}" data-role="label-input-sel" placeholder=" " value="${states.labelSel || ""}">
|
|
75
|
+
<label for="label-input-sel-${id}">Top Label</label>
|
|
76
|
+
<button class="gcp-css white" data-role="focus-btn" style="margin-left: 5px;">Focus</button>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<div class="controls gcp-css">
|
|
81
|
+
<div class="controls-label">OptionsSectionManager (Dropdown Section)</div>
|
|
82
|
+
|
|
83
|
+
<div class="gcp-css checkbox-wrapper">
|
|
84
|
+
<div class="checkbox-row">
|
|
85
|
+
<input type="checkbox" id="disabled-opt-${id}" data-role="disabled-opt" ${states.disabledOpt ? "checked" : ""}>
|
|
86
|
+
<div class="content-cell"><label for="disabled-opt-${id}">Disabled</label></div>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<div class="gcp-css checkbox-wrapper">
|
|
91
|
+
<div class="checkbox-row">
|
|
92
|
+
<input type="checkbox" id="loading-opt-${id}" data-role="loading-opt" ${states.loadingOpt ? "checked" : ""}>
|
|
93
|
+
<div class="content-cell"><label for="loading-opt-${id}">Loading</label></div>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<div class="gcp-css checkbox-wrapper">
|
|
98
|
+
<div class="checkbox-row">
|
|
99
|
+
<input type="checkbox" id="show-filter-opt-${id}" data-role="show-filter-opt" ${states.showFilter !== false ? "checked" : ""}>
|
|
100
|
+
<div class="content-cell"><label for="show-filter-opt-${id}">Show Filter</label></div>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<div class="gcp-css checkbox-wrapper">
|
|
105
|
+
<div class="checkbox-row">
|
|
106
|
+
<input type="checkbox" id="show-footer-opt-${id}" data-role="show-footer-opt" ${states.showFooter !== false ? "checked" : ""}>
|
|
107
|
+
<div class="content-cell"><label for="show-footer-opt-${id}">Show Footer</label></div>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<div class="input-wrapper">
|
|
112
|
+
<select id="position-${id}" data-role="position" class="gcp-css white" required>
|
|
113
|
+
<option value="cover-bottom">cover-bottom</option>
|
|
114
|
+
<option value="bottom">bottom</option>
|
|
115
|
+
<option value="top">top</option>
|
|
116
|
+
<option value="left">left</option>
|
|
117
|
+
<option value="right">right</option>
|
|
118
|
+
</select>
|
|
119
|
+
<label for="position-${id}">Position</label>
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
<div class="gcp-css input-wrapper">
|
|
123
|
+
<input type="text" id="label-input-opt-${id}" data-role="label-input-opt" placeholder=" " value="${states.labelOpt || ""}">
|
|
124
|
+
<label for="label-input-opt-${id}">Dropdown Label</label>
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
<div style="display: flex; gap: 5px; align-items: center; width: 100%; margin-top: 10px;">
|
|
128
|
+
<span style="min-width: 120px;">🔍 <strong>Input & List</strong>:</span>
|
|
129
|
+
<button class="gcp-css white" data-role="opt-focus-btn">Focus Input</button>
|
|
130
|
+
|
|
131
|
+
<div class="gcp-css checkbox-wrapper">
|
|
132
|
+
<div class="checkbox-row">
|
|
133
|
+
<input type="checkbox" id="empty-list-${id}" data-role="empty-list" ${states.emptyList === true ? "checked" : ""}>
|
|
134
|
+
<div class="content-cell"><label for="empty-list-${id}">Empty list</label></div>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
<div style="display: flex; gap: 10px; flex-wrap: wrap; align-items: center; width: 100%; margin-top: 10px;">
|
|
140
|
+
<div style="display: flex; gap: 5px; align-items: center;" data-role="mh-presets-container">
|
|
141
|
+
<span style="min-width: 120px;">📏 <strong>Max Height</strong>:</span>
|
|
142
|
+
<div style="display: flex; gap: 2px;">
|
|
143
|
+
<button class="gcp-css white" data-role="maxheight-down" style="padding: 0 8px; min-width: auto;">▼</button>
|
|
144
|
+
<div class="gcp-css input-wrapper" style="max-width: 150px; margin-bottom: 0;">
|
|
145
|
+
<input type="text" data-role="maxheight-input" id="maxheight-input-${id}" placeholder=" " value="${states.maxHeight || ""}">
|
|
146
|
+
<label for="maxheight-input-${id}">Max height</label>
|
|
147
|
+
</div>
|
|
148
|
+
<button class="gcp-css white" data-role="maxheight-up" style="padding: 0 8px; min-width: auto;">▲</button>
|
|
149
|
+
</div>
|
|
150
|
+
<button data-role="maxheight-btn" class="gcp-css">Set</button>
|
|
151
|
+
<button class="gcp-css white" data-role="mh-preset" data-value="200px">200px</button>
|
|
152
|
+
<button class="gcp-css white" data-role="mh-preset" data-value="300px">300px</button>
|
|
153
|
+
<button class="gcp-css white" data-role="mh-preset" data-value="400px">400px</button>
|
|
154
|
+
<button class="gcp-css white" data-role="mh-preset" data-value="600px">600px</button>
|
|
155
|
+
<button class="gcp-css white" data-role="mh-preset" data-value="reset">Reset</button>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
<div style="display: flex; gap: 5px; align-items: center; width: 100%; margin-top: 10px;">
|
|
160
|
+
<span style="min-width: 120px;">🎨 <strong>Render</strong>:</span>
|
|
161
|
+
<button class="gcp-css white" data-role="opt-render-btn">Set Custom Render</button>
|
|
162
|
+
<button class="gcp-css white" data-role="opt-string-render-btn">Set String Render</button>
|
|
163
|
+
<button class="gcp-css white" data-role="opt-default-render-btn">Set Default Render</button>
|
|
164
|
+
<button class="gcp-css white" data-role="opt-empty-btn">Set Custom Empty</button>
|
|
165
|
+
<button class="gcp-css white" data-role="opt-default-empty-btn">Set Default Empty</button>
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
<div style="width: 100%; margin-top: 10px;">
|
|
170
|
+
<pre data-role="search-val-out" style="display: inline; padding: 2px 5px; margin: 0; background: #eee; width: auto; max-height: none;">-</pre>
|
|
171
|
+
(onChange triggers: <span data-role="onchange-count" style="font-weight: bold;">0</span>,
|
|
172
|
+
onOk triggers: <span data-role="onok-count" style="font-weight: bold;">0</span>,
|
|
173
|
+
onCancel triggers: <span data-role="oncancel-count" style="font-weight: bold;">0</span>,
|
|
174
|
+
onItemPick triggers: <span data-role="onpick-count" style="font-weight: bold;">0</span>,
|
|
175
|
+
onSelectedComponentChange triggers: <span data-role="on-selected-component-change-count" style="font-weight: bold;">0</span>,
|
|
176
|
+
onOptionsComponentChange triggers: <span data-role="on-options-component-change-count" style="font-weight: bold;">0</span>)
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
|
|
180
|
+
<div class="controls gcp-css">
|
|
181
|
+
<div class="controls-label">Templates</div>
|
|
182
|
+
<div data-role="templates-area" style="display: flex; gap: 5px; flex-wrap: wrap; margin-bottom: 8px;"></div>
|
|
183
|
+
</div>
|
|
184
|
+
<div class="controls gcp-css" style="margin-bottom: 8px;">
|
|
185
|
+
<button class="white gcp-css" data-role="custom-render-btn">Set Custom Render Item</button>
|
|
186
|
+
<button class="white gcp-css" data-role="custom-list-render-btn">Set Custom Render List</button>
|
|
187
|
+
<button class="white gcp-css" data-role="reset-templates-btn">Reset Templates</button>
|
|
188
|
+
</div>
|
|
189
|
+
|
|
190
|
+
<pre data-role="dump" style="background:#f8f8f8;padding:10px;border:1px solid #eee;border-radius:4px;font-size:12px;margin:0;overflow:auto;"></pre>
|
|
191
|
+
`;
|
|
192
|
+
const instancesArea = document.getElementById("instances-area");
|
|
193
|
+
instancesArea?.appendChild(section);
|
|
194
|
+
const templatesArea = section.querySelector('[data-role="templates-area"]');
|
|
195
|
+
if (templatesArea) {
|
|
196
|
+
for (const [color, images] of Object.entries(imgData)) {
|
|
197
|
+
images.forEach((img) => {
|
|
198
|
+
const btn = document.createElement("button");
|
|
199
|
+
btn.className = "template-btn white gcp-css";
|
|
200
|
+
btn.dataset.color = color;
|
|
201
|
+
btn.dataset.img = img;
|
|
202
|
+
btn.style.color = color;
|
|
203
|
+
btn.style.padding = "4px 8px";
|
|
204
|
+
btn.style.margin = "0";
|
|
205
|
+
btn.textContent = img;
|
|
206
|
+
templatesArea.appendChild(btn);
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
const container = section.querySelector('[data-role="container"]');
|
|
211
|
+
const resizer = section.querySelector('[data-role="resizer"]');
|
|
212
|
+
const destroyBtn = section.querySelector('[data-role="destroy"]');
|
|
213
|
+
const dump = section.querySelector('[data-role="dump"]');
|
|
214
|
+
const positionSelect = section.querySelector('[data-role="position"]');
|
|
215
|
+
const disabledSelCb = section.querySelector('[data-role="disabled-sel"]');
|
|
216
|
+
const loadingSelCb = section.querySelector('[data-role="loading-sel"]');
|
|
217
|
+
const errorSelCb = section.querySelector('[data-role="error-sel"]');
|
|
218
|
+
const showInputSelCb = section.querySelector('[data-role="show-input-sel"]');
|
|
219
|
+
const labelInputSel = section.querySelector('[data-role="label-input-sel"]');
|
|
220
|
+
const emptyListCb = section.querySelector('[data-role="empty-list"]');
|
|
221
|
+
const focusBtn = section.querySelector('[data-role="focus-btn"]');
|
|
222
|
+
const disabledOptCb = section.querySelector('[data-role="disabled-opt"]');
|
|
223
|
+
const loadingOptCb = section.querySelector('[data-role="loading-opt"]');
|
|
224
|
+
const setShowFilterOptCb = section.querySelector('[data-role="show-filter-opt"]');
|
|
225
|
+
const setShowFooterOptCb = section.querySelector('[data-role="show-footer-opt"]');
|
|
226
|
+
const labelInputOpt = section.querySelector('[data-role="label-input-opt"]');
|
|
227
|
+
const optFocusBtn = section.querySelector('[data-role="opt-focus-btn"]');
|
|
228
|
+
const maxHeightInput = section.querySelector('[data-role="maxheight-input"]');
|
|
229
|
+
const maxHeightBtn = section.querySelector('[data-role="maxheight-btn"]');
|
|
230
|
+
const customRenderBtn = section.querySelector('[data-role="custom-render-btn"]');
|
|
231
|
+
const customListRenderBtn = section.querySelector('[data-role="custom-list-render-btn"]');
|
|
232
|
+
const resetTemplatesBtn = section.querySelector('[data-role="reset-templates-btn"]');
|
|
233
|
+
const optRenderBtn = section.querySelector('[data-role="opt-render-btn"]');
|
|
234
|
+
const optStringRenderBtn = section.querySelector('[data-role="opt-string-render-btn"]');
|
|
235
|
+
const optDefaultRenderBtn = section.querySelector('[data-role="opt-default-render-btn"]');
|
|
236
|
+
const optEmptyBtn = section.querySelector('[data-role="opt-empty-btn"]');
|
|
237
|
+
const optDefaultEmptyBtn = section.querySelector('[data-role="opt-default-empty-btn"]');
|
|
238
|
+
const searchValOut = section.querySelector('[data-role="search-val-out"]');
|
|
239
|
+
const inc = (role) => {
|
|
240
|
+
const el = section.querySelector(`[data-role="${role}"]`);
|
|
241
|
+
if (el) {
|
|
242
|
+
el.textContent = String(parseInt(el.textContent || "0", 10) + 1);
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
const updateDump = (list) => {
|
|
246
|
+
dump.textContent = JSON.stringify(list, null, 2);
|
|
247
|
+
};
|
|
248
|
+
function searchNames(name, num = 10) {
|
|
249
|
+
if (emptyListCb.checked) {
|
|
250
|
+
return [];
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
return rawSearchNames(name, num);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
let mgr;
|
|
257
|
+
const syncUrl = () => {
|
|
258
|
+
const url = new URL(window.location.href);
|
|
259
|
+
urlStateConfig.toUrl(url, id, {
|
|
260
|
+
selected: mgr?.selected.getSelected() ?? [],
|
|
261
|
+
left: resizer.getAttribute("left") || "100px",
|
|
262
|
+
center: resizer.getAttribute("center") || "600px",
|
|
263
|
+
height: resizer.getAttribute("height") || "auto",
|
|
264
|
+
disabledSel: disabledSelCb.checked,
|
|
265
|
+
disabledOpt: disabledOptCb.checked,
|
|
266
|
+
loadingSel: loadingSelCb.checked,
|
|
267
|
+
loadingOpt: loadingOptCb.checked,
|
|
268
|
+
labelSel: labelInputSel.value || "",
|
|
269
|
+
labelOpt: labelInputOpt.value || "",
|
|
270
|
+
errorSel: errorSelCb.checked,
|
|
271
|
+
showInputSel: showInputSelCb.checked,
|
|
272
|
+
showFilter: setShowFilterOptCb.checked,
|
|
273
|
+
showFooter: setShowFooterOptCb.checked,
|
|
274
|
+
position: positionSelect.value,
|
|
275
|
+
filter: mgr?.options.getValue() || "",
|
|
276
|
+
selectedValue: mgr?.selected.propOptions.value || "",
|
|
277
|
+
maxHeight: maxHeightInput.value || "",
|
|
278
|
+
emptyList: emptyListCb.checked,
|
|
279
|
+
});
|
|
280
|
+
window.history.replaceState({}, "", url);
|
|
281
|
+
updateUrlDisplay(url.toString());
|
|
282
|
+
};
|
|
283
|
+
function localDetermineSearch() {
|
|
284
|
+
const showFilter = mgr.options.getShowFilter();
|
|
285
|
+
const position = mgr.container.getPosition();
|
|
286
|
+
if (!showFilter && position === "bottom") {
|
|
287
|
+
return { search: mgr.selected.getValue() || "", popupInput: false };
|
|
288
|
+
}
|
|
289
|
+
return { search: mgr.options.getValue() || "", popupInput: true };
|
|
290
|
+
}
|
|
291
|
+
const onChangeEventFactory = (stopPopupInput) => debounce(async (e, previousValue) => {
|
|
292
|
+
const { search, popupInput } = localDetermineSearch();
|
|
293
|
+
if (popupInput === true) {
|
|
294
|
+
inc("onchange-count");
|
|
295
|
+
searchValOut.textContent = search || "-";
|
|
296
|
+
}
|
|
297
|
+
if (stopPopupInput === popupInput) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
const value = e.target.value;
|
|
301
|
+
if (!popupInput && e.type === "keydown") {
|
|
302
|
+
const key = e.key;
|
|
303
|
+
if (key === "Backspace" && value === "" && mgr.selected.getSelected().length > 0) {
|
|
304
|
+
const selItems = mgr.selected.getSelected();
|
|
305
|
+
selItems.pop();
|
|
306
|
+
mgr.selected.setSelected(selItems);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
if (search === previousValue)
|
|
311
|
+
return;
|
|
312
|
+
mgr.options.setLoading(true);
|
|
313
|
+
mgr.options.setDisabled(true);
|
|
314
|
+
mgr.selected.setLoading(true);
|
|
315
|
+
mgr.selected.setDisabled(true);
|
|
316
|
+
await delay(1000);
|
|
317
|
+
const found = deduplicateArrayById(searchNames(search));
|
|
318
|
+
const sorted = sortById(found);
|
|
319
|
+
mgr.options.setOptions(sorted);
|
|
320
|
+
mgr.options.setDisabled(false);
|
|
321
|
+
mgr.options.setLoading(false);
|
|
322
|
+
mgr.selected.setDisabled(false);
|
|
323
|
+
mgr.selected.setLoading(false);
|
|
324
|
+
syncUrl();
|
|
325
|
+
}, 500);
|
|
326
|
+
mgr = new CompositeManager(container, {
|
|
327
|
+
select: {
|
|
328
|
+
selected: initialSelected,
|
|
329
|
+
label: states.labelSel || "Select Fruit",
|
|
330
|
+
disabled: !!states.disabledSel,
|
|
331
|
+
loading: !!states.loadingSel,
|
|
332
|
+
error: !!states.errorSel,
|
|
333
|
+
showInput: states.showInputSel !== false,
|
|
334
|
+
onFocus: (e) => {
|
|
335
|
+
const { search } = localDetermineSearch();
|
|
336
|
+
const selected = mgr.selected.getSelected();
|
|
337
|
+
const selIds = selected.map((i) => i.id);
|
|
338
|
+
const combined = searchNames(search);
|
|
339
|
+
const options = markSelectedByIds(combined, selIds);
|
|
340
|
+
mgr.options.setOptions(sortById(options));
|
|
341
|
+
mgr.selected.setShowDelete(false);
|
|
342
|
+
mgr.options.setFocus();
|
|
343
|
+
},
|
|
344
|
+
onInputChange: onChangeEventFactory(true),
|
|
345
|
+
onDelete: (id) => {
|
|
346
|
+
let selected = mgr.selected.getSelected();
|
|
347
|
+
selected = selected.filter((i) => String(i.id) !== String(id));
|
|
348
|
+
mgr.selected.setSelected(selected);
|
|
349
|
+
syncUrl();
|
|
350
|
+
},
|
|
351
|
+
onClear: () => {
|
|
352
|
+
mgr.selected.setSelected([]);
|
|
353
|
+
syncUrl();
|
|
354
|
+
},
|
|
355
|
+
onChange: (items) => {
|
|
356
|
+
updateDump(items);
|
|
357
|
+
},
|
|
358
|
+
onComponentChange: (opt, reason) => {
|
|
359
|
+
inc("on-selected-component-change-count");
|
|
360
|
+
disabledSelCb.checked = !!opt.disabled;
|
|
361
|
+
loadingSelCb.checked = !!opt.loading;
|
|
362
|
+
errorSelCb.checked = !!opt.error;
|
|
363
|
+
showInputSelCb.checked = opt.showInput !== false;
|
|
364
|
+
labelInputSel.value = opt.label || "";
|
|
365
|
+
},
|
|
366
|
+
},
|
|
367
|
+
options: {
|
|
368
|
+
label: states.labelOpt || "Search fruits...",
|
|
369
|
+
maxHeight: states.maxHeight || "300px",
|
|
370
|
+
showFilter: !!states.showFilter,
|
|
371
|
+
showFooter: !!states.showFooter,
|
|
372
|
+
disabled: !!states.disabledOpt,
|
|
373
|
+
loading: !!states.loadingOpt,
|
|
374
|
+
value: states.filter || "",
|
|
375
|
+
onInputChange: onChangeEventFactory(false),
|
|
376
|
+
onItemPick: (item) => {
|
|
377
|
+
inc("onpick-count");
|
|
378
|
+
if (mgr.options.getShowFooter()) {
|
|
379
|
+
const options = [...mgr.options.getOptions()];
|
|
380
|
+
const found = options.find((opt) => opt.id === item.id);
|
|
381
|
+
found.selected = !found.selected;
|
|
382
|
+
mgr.options.setOptions(options);
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
const selectedToggled = togglePresenceOnTheList(mgr.selected.getSelected(), item);
|
|
386
|
+
mgr.selected.setSelected(selectedToggled);
|
|
387
|
+
mgr.selected.setShowDelete(true);
|
|
388
|
+
mgr.container.hide();
|
|
389
|
+
}
|
|
390
|
+
syncUrl();
|
|
391
|
+
},
|
|
392
|
+
onCancel: () => {
|
|
393
|
+
inc("oncancel-count");
|
|
394
|
+
mgr.container.hide();
|
|
395
|
+
},
|
|
396
|
+
onOk: () => {
|
|
397
|
+
inc("onok-count");
|
|
398
|
+
// this is where we prepare new list for mgr.selected.setSelected(...)
|
|
399
|
+
// by passing options first
|
|
400
|
+
// this way these from options will win
|
|
401
|
+
const options = deduplicateArrayById([...mgr.options.getOptions(), ...mgr.selected.getSelected()]);
|
|
402
|
+
// now we have to just filter out selected
|
|
403
|
+
const optionsSelected = options.filter((i) => i.selected);
|
|
404
|
+
mgr.selected.setSelected(optionsSelected);
|
|
405
|
+
mgr.container.hide();
|
|
406
|
+
syncUrl();
|
|
407
|
+
},
|
|
408
|
+
onComponentChange: (opt, reason) => {
|
|
409
|
+
inc("on-options-component-change-count");
|
|
410
|
+
setShowFilterOptCb.checked = !!opt.showFilter;
|
|
411
|
+
setShowFooterOptCb.checked = !!opt.showFooter;
|
|
412
|
+
disabledOptCb.checked = !!opt.disabled;
|
|
413
|
+
loadingOptCb.checked = !!opt.loading;
|
|
414
|
+
labelInputOpt.value = opt.label || "";
|
|
415
|
+
},
|
|
416
|
+
},
|
|
417
|
+
container: {
|
|
418
|
+
onClose: () => {
|
|
419
|
+
mgr.selected.setShowDelete(true);
|
|
420
|
+
},
|
|
421
|
+
},
|
|
422
|
+
});
|
|
423
|
+
mgr.container.setPosition(states.position || "cover-bottom");
|
|
424
|
+
const { search } = localDetermineSearch();
|
|
425
|
+
mgr.options.setValue(search);
|
|
426
|
+
const found = deduplicateArrayById(searchNames(search));
|
|
427
|
+
mgr.options.setOptions(sortById(found));
|
|
428
|
+
disabledSelCb.addEventListener("change", () => {
|
|
429
|
+
mgr.selected.setDisabled(disabledSelCb.checked);
|
|
430
|
+
syncUrl();
|
|
431
|
+
});
|
|
432
|
+
loadingSelCb.addEventListener("change", () => {
|
|
433
|
+
mgr.selected.setLoading(loadingSelCb.checked);
|
|
434
|
+
syncUrl();
|
|
435
|
+
});
|
|
436
|
+
errorSelCb.addEventListener("change", () => {
|
|
437
|
+
mgr.selected.setError(errorSelCb.checked);
|
|
438
|
+
syncUrl();
|
|
439
|
+
});
|
|
440
|
+
showInputSelCb.addEventListener("change", () => {
|
|
441
|
+
mgr.selected.setShowInput(showInputSelCb.checked);
|
|
442
|
+
syncUrl();
|
|
443
|
+
});
|
|
444
|
+
emptyListCb.addEventListener("change", () => {
|
|
445
|
+
const { search } = localDetermineSearch();
|
|
446
|
+
const found = deduplicateArrayById(searchNames(search));
|
|
447
|
+
mgr.options.setOptions(sortById(found));
|
|
448
|
+
syncUrl();
|
|
449
|
+
});
|
|
450
|
+
labelInputSel.addEventListener("input", () => {
|
|
451
|
+
mgr.selected.setLabel(labelInputSel.value);
|
|
452
|
+
syncUrl();
|
|
453
|
+
});
|
|
454
|
+
disabledOptCb.addEventListener("change", () => {
|
|
455
|
+
mgr.options.setDisabled(disabledOptCb.checked);
|
|
456
|
+
syncUrl();
|
|
457
|
+
});
|
|
458
|
+
loadingOptCb.addEventListener("change", () => {
|
|
459
|
+
mgr.options.setLoading(loadingOptCb.checked);
|
|
460
|
+
syncUrl();
|
|
461
|
+
});
|
|
462
|
+
setShowFilterOptCb.addEventListener("change", () => {
|
|
463
|
+
mgr.options.setShowFilter(setShowFilterOptCb.checked);
|
|
464
|
+
syncUrl();
|
|
465
|
+
});
|
|
466
|
+
setShowFooterOptCb.addEventListener("change", () => {
|
|
467
|
+
mgr.options.setShowFooter(setShowFooterOptCb.checked);
|
|
468
|
+
syncUrl();
|
|
469
|
+
});
|
|
470
|
+
labelInputOpt.addEventListener("input", () => {
|
|
471
|
+
mgr.options.setLabel(labelInputOpt.value);
|
|
472
|
+
syncUrl();
|
|
473
|
+
});
|
|
474
|
+
optFocusBtn.addEventListener("click", () => mgr.selected.setFocus());
|
|
475
|
+
const setMH = (val) => {
|
|
476
|
+
if (val === "reset") {
|
|
477
|
+
val = undefined;
|
|
478
|
+
}
|
|
479
|
+
maxHeightInput.value = val || "";
|
|
480
|
+
mgr.options.setMaxHeight(val || "");
|
|
481
|
+
syncUrl();
|
|
482
|
+
};
|
|
483
|
+
maxHeightBtn.addEventListener("click", () => setMH(maxHeightInput.value));
|
|
484
|
+
section.querySelectorAll('[data-role="mh-preset"]').forEach((btn) => {
|
|
485
|
+
btn.addEventListener("click", () => setMH(btn.dataset.value));
|
|
486
|
+
});
|
|
487
|
+
const stepMH = (delta) => {
|
|
488
|
+
let current = parseInt(maxHeightInput.value) || 0;
|
|
489
|
+
current += delta;
|
|
490
|
+
if (current < 0)
|
|
491
|
+
current = 0;
|
|
492
|
+
setMH(current + "px");
|
|
493
|
+
};
|
|
494
|
+
section.querySelector('[data-role="maxheight-up"]')?.addEventListener("click", () => stepMH(10));
|
|
495
|
+
section.querySelector('[data-role="maxheight-down"]')?.addEventListener("click", () => stepMH(-10));
|
|
496
|
+
positionSelect.value = states.position || "cover-bottom";
|
|
497
|
+
positionSelect.addEventListener("change", () => {
|
|
498
|
+
mgr.container.setPosition(positionSelect.value);
|
|
499
|
+
syncUrl();
|
|
500
|
+
});
|
|
501
|
+
resizer.addEventListener("onLeft", () => syncUrl());
|
|
502
|
+
resizer.addEventListener("onCenter", () => syncUrl());
|
|
503
|
+
resizer.addEventListener("onHeight", () => syncUrl());
|
|
504
|
+
destroyBtn.addEventListener("click", () => {
|
|
505
|
+
mgr.destroy();
|
|
506
|
+
section.remove();
|
|
507
|
+
});
|
|
508
|
+
if (templatesArea) {
|
|
509
|
+
templatesArea.querySelectorAll(".template-btn").forEach((btn) => {
|
|
510
|
+
btn.addEventListener("click", () => {
|
|
511
|
+
const color = btn.dataset.color;
|
|
512
|
+
const img = btn.dataset.img;
|
|
513
|
+
const newItem = {
|
|
514
|
+
id: globalIdCounter++,
|
|
515
|
+
label: img.split(".")[0],
|
|
516
|
+
color: color,
|
|
517
|
+
img: img,
|
|
518
|
+
};
|
|
519
|
+
const selected = deduplicateArrayById([...mgr.selected.getSelected(), newItem]);
|
|
520
|
+
mgr.selected.setSelected(selected);
|
|
521
|
+
console.log("add custom", newItem, "selected after", mgr.selected.getSelected());
|
|
522
|
+
syncUrl();
|
|
523
|
+
updateDump(selected);
|
|
524
|
+
});
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
customRenderBtn.addEventListener("click", () => {
|
|
528
|
+
mgr.selected.setRenderItem(function (item) {
|
|
529
|
+
const el = document.createElement("div");
|
|
530
|
+
el.className = "element";
|
|
531
|
+
el.dataset.id = String(item.id);
|
|
532
|
+
el.style.border = `2px solid ${item.color || "black"}`;
|
|
533
|
+
el.style.display = "flex";
|
|
534
|
+
el.style.alignItems = "center";
|
|
535
|
+
el.style.gap = "5px";
|
|
536
|
+
el.style.padding = "5px";
|
|
537
|
+
el.style.background = "#fff";
|
|
538
|
+
if (item.img) {
|
|
539
|
+
const img = document.createElement("img");
|
|
540
|
+
img.src = `../img/${item.img}`;
|
|
541
|
+
img.style.width = "20px";
|
|
542
|
+
img.style.height = "20px";
|
|
543
|
+
img.style.objectFit = "contain";
|
|
544
|
+
el.appendChild(img);
|
|
545
|
+
}
|
|
546
|
+
const label = document.createElement("label");
|
|
547
|
+
label.textContent = item.label;
|
|
548
|
+
const del = document.createElement("div");
|
|
549
|
+
del.dataset.remove = String(item.id);
|
|
550
|
+
el.appendChild(label);
|
|
551
|
+
el.appendChild(del);
|
|
552
|
+
return el;
|
|
553
|
+
});
|
|
554
|
+
});
|
|
555
|
+
customListRenderBtn.addEventListener("click", () => {
|
|
556
|
+
mgr.selected.setRenderList(function (selected, defaultRenderList) {
|
|
557
|
+
const elements = defaultRenderList(selected);
|
|
558
|
+
const groups = [];
|
|
559
|
+
for (let i = 0; i < elements.length; i += 3) {
|
|
560
|
+
const groupDiv = document.createElement("div");
|
|
561
|
+
groupDiv.style.border = "1px solid #1a73e8";
|
|
562
|
+
groupDiv.style.borderRadius = "8px";
|
|
563
|
+
groupDiv.style.padding = "8px";
|
|
564
|
+
groupDiv.style.margin = "4px";
|
|
565
|
+
groupDiv.style.display = "flex";
|
|
566
|
+
groupDiv.style.gap = "8px";
|
|
567
|
+
groupDiv.style.flexWrap = "wrap";
|
|
568
|
+
groupDiv.style.background = "#e8f0fe";
|
|
569
|
+
groupDiv.style.boxShadow = "0 2px 5px rgba(0,0,0,0.05)";
|
|
570
|
+
const chunk = elements.slice(i, i + 3);
|
|
571
|
+
chunk.forEach((el) => groupDiv.appendChild(el));
|
|
572
|
+
groups.push(groupDiv);
|
|
573
|
+
}
|
|
574
|
+
return groups;
|
|
575
|
+
});
|
|
576
|
+
});
|
|
577
|
+
resetTemplatesBtn.addEventListener("click", () => {
|
|
578
|
+
mgr.selected.setRenderItem();
|
|
579
|
+
mgr.selected.setRenderList();
|
|
580
|
+
});
|
|
581
|
+
optRenderBtn.addEventListener("click", () => {
|
|
582
|
+
mgr.options.setRenderItem((item) => {
|
|
583
|
+
const el = document.createElement("div");
|
|
584
|
+
el.className = "element";
|
|
585
|
+
el.style.padding = "12px";
|
|
586
|
+
el.style.background = item.selected ? "#fff3e0" : "transparent";
|
|
587
|
+
el.style.color = item.selected ? "#e65100" : "inherit";
|
|
588
|
+
el.dataset.id = String(item.id);
|
|
589
|
+
const icon = document.createElement("span");
|
|
590
|
+
icon.textContent = item.selected ? "🔥 " : "❄️ ";
|
|
591
|
+
icon.style.marginRight = "10px";
|
|
592
|
+
const label = document.createElement("label");
|
|
593
|
+
label.textContent = item.label || "";
|
|
594
|
+
el.appendChild(icon);
|
|
595
|
+
el.appendChild(label);
|
|
596
|
+
return el;
|
|
597
|
+
});
|
|
598
|
+
});
|
|
599
|
+
optStringRenderBtn.addEventListener("click", () => {
|
|
600
|
+
mgr.options.setRenderItem((item) => `
|
|
601
|
+
<div class="element" data-id="${item.id}" style="border: 1px solid #ccc; margin: 2px; border-radius: 20px; padding: 5px 15px; background: ${item.selected ? "#e8f5e9" : "white"}; color: ${item.selected ? "#2e7d32" : "#333"};">
|
|
602
|
+
<span style="font-size: 1.2em; vertical-align: middle;">${item.selected ? "✅" : "⬜"}</span>
|
|
603
|
+
<strong style="margin-left: 10px;">${item.label}</strong>
|
|
604
|
+
<small style="margin-left: auto; opacity: 0.5;">#${item.id}</small>
|
|
605
|
+
</div>
|
|
606
|
+
`);
|
|
607
|
+
});
|
|
608
|
+
optDefaultRenderBtn.addEventListener("click", () => {
|
|
609
|
+
mgr.options.setRenderItem();
|
|
610
|
+
});
|
|
611
|
+
optEmptyBtn.addEventListener("click", () => {
|
|
612
|
+
mgr.options.setRenderEmpty(() => `<div style="padding: 40px; text-align: center; color: #ff5252; font-weight: bold; border: 2px dashed #ff5252; border-radius: 8px;">⚠️ Custom Empty State!</div>`);
|
|
613
|
+
});
|
|
614
|
+
optDefaultEmptyBtn.addEventListener("click", () => {
|
|
615
|
+
mgr.options.setRenderEmpty();
|
|
616
|
+
});
|
|
617
|
+
focusBtn.addEventListener("click", () => mgr.selected.setFocus());
|
|
618
|
+
return mgr;
|
|
619
|
+
};
|
|
620
|
+
const initBtn = document.getElementById("init-btn");
|
|
621
|
+
if (initBtn) {
|
|
622
|
+
initBtn.addEventListener("click", () => init());
|
|
623
|
+
}
|
|
624
|
+
const loadFromUrl = () => {
|
|
625
|
+
const instancesArea = document.getElementById("instances-area");
|
|
626
|
+
if (instancesArea) {
|
|
627
|
+
instancesArea.innerHTML = "";
|
|
628
|
+
}
|
|
629
|
+
instanceCounter = 0;
|
|
630
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
631
|
+
const allIds = urlStateConfig.getAllIds(urlParams);
|
|
632
|
+
if (allIds.length === 0) {
|
|
633
|
+
// Default state if no URL params
|
|
634
|
+
init([], {});
|
|
635
|
+
}
|
|
636
|
+
else {
|
|
637
|
+
allIds.forEach((id) => {
|
|
638
|
+
const state = urlStateConfig.fromUrl(urlParams, id);
|
|
639
|
+
const restored = (state.selected || []).map((e) => {
|
|
640
|
+
e.id = parseInt(e.id, 10);
|
|
641
|
+
return e;
|
|
642
|
+
});
|
|
643
|
+
console.log("restored", restored);
|
|
644
|
+
init(restored, state);
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
updateUrlDisplay();
|
|
648
|
+
};
|
|
649
|
+
window.addEventListener("popstate", loadFromUrl);
|
|
650
|
+
loadFromUrl();
|