@superleapai/flow-ui 2.5.18 → 2.5.20
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/CHANGELOG.md +2 -2
- package/LICENSE +1 -1
- package/README.md +38 -38
- package/components/enum-multiselect.js +119 -56
- package/components/enum-select.js +90 -48
- package/components/file-input.js +111 -49
- package/components/multiselect.js +70 -3
- package/components/select.js +87 -55
- package/core/bridge.js +4 -6
- package/core/crm.js +17 -17
- package/core/flow.js +236 -61
- package/core/superleapClient.js +2 -2
- package/dist/superleap-flow.min.js +2 -2
- package/index.d.ts +33 -20
- package/index.js +41 -30
- package/package.json +2 -2
package/components/file-input.js
CHANGED
|
@@ -10,16 +10,23 @@
|
|
|
10
10
|
// Tabler-style inline SVGs (no external deps) – match React IconFile, IconCopy, IconEye, IconX
|
|
11
11
|
var ICONS = {
|
|
12
12
|
file: '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/></svg>',
|
|
13
|
-
fileImage:
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
fileImage:
|
|
14
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/></svg>',
|
|
15
|
+
filePdf:
|
|
16
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M9 12h6"/><path d="M9 16h6"/><path d="M9 8h1"/></svg>',
|
|
17
|
+
fileVideo:
|
|
18
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m22 8-6 4 6 4V8Z"/><rect width="14" height="12" x="2" y="6" rx="2" ry="2"/></svg>',
|
|
19
|
+
fileAudio:
|
|
20
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/></svg>',
|
|
21
|
+
fileSpreadsheet:
|
|
22
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M8 12h8"/><path d="M8 16h8"/><path d="M8 8h8"/></svg>',
|
|
23
|
+
fileText:
|
|
24
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M10 9H8"/><path d="M16 13H8"/><path d="M16 17H8"/></svg>',
|
|
19
25
|
copy: '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>',
|
|
20
26
|
eye: '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/></svg>',
|
|
21
27
|
x: '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>',
|
|
22
|
-
loader:
|
|
28
|
+
loader:
|
|
29
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="animate-spin"><path d="M21 12a9 9 0 1 1-6.219-8.56"/></svg>',
|
|
23
30
|
};
|
|
24
31
|
|
|
25
32
|
var IMAGE_EXT = /\.(jpe?g|png|gif|webp|bmp|svg|ico|avif)(\?|$)/i;
|
|
@@ -80,10 +87,15 @@
|
|
|
80
87
|
}
|
|
81
88
|
|
|
82
89
|
if (!baseUrl) {
|
|
83
|
-
throw new Error(
|
|
90
|
+
throw new Error(
|
|
91
|
+
"Superleap client not initialized. Call superleapClient.init({ baseUrl, apiKey }) first.",
|
|
92
|
+
);
|
|
84
93
|
}
|
|
85
94
|
|
|
86
|
-
const fullUrl = uploadUrl.startsWith("http")
|
|
95
|
+
const fullUrl = uploadUrl.startsWith("http")
|
|
96
|
+
? uploadUrl
|
|
97
|
+
: baseUrl.replace(/\/$/, "") +
|
|
98
|
+
(uploadUrl.startsWith("/") ? uploadUrl : "/" + uploadUrl);
|
|
87
99
|
|
|
88
100
|
const headers = {};
|
|
89
101
|
if (apiKey) {
|
|
@@ -103,14 +115,14 @@
|
|
|
103
115
|
|
|
104
116
|
const result = await response.json();
|
|
105
117
|
console.log("[S3FileUpload] Upload response:");
|
|
106
|
-
|
|
118
|
+
|
|
107
119
|
const url = result?.data?.url;
|
|
108
|
-
|
|
120
|
+
|
|
109
121
|
if (!url) {
|
|
110
122
|
console.error("[S3FileUpload] No URL found in response:", result);
|
|
111
123
|
throw new Error("Upload response did not contain a file URL");
|
|
112
124
|
}
|
|
113
|
-
|
|
125
|
+
|
|
114
126
|
return {
|
|
115
127
|
url: url,
|
|
116
128
|
};
|
|
@@ -135,8 +147,7 @@
|
|
|
135
147
|
* @returns {HTMLElement} Field element
|
|
136
148
|
*/
|
|
137
149
|
var UPLOAD_WRAPPER_CLASS = {
|
|
138
|
-
base:
|
|
139
|
-
"group relative flex w-full items-center justify-between border-1/2 rounded-4 text-typography-primary-text w-full transition-all ease-in-out focus-within:outline-none group-has-[:disabled]:cursor-not-allowed group-has-[:disabled]:border-border-primary group-has-[:disabled]:bg-fill-tertiary-fill-light-gray group-has-[:disabled]:text-typography-quaternary-text group-has-[:disabled]:hover:border-border-primary",
|
|
150
|
+
base: "group relative flex w-full items-center justify-between border-1/2 rounded-4 text-typography-primary-text w-full transition-all ease-in-out focus-within:outline-none group-has-[:disabled]:cursor-not-allowed group-has-[:disabled]:border-border-primary group-has-[:disabled]:bg-fill-tertiary-fill-light-gray group-has-[:disabled]:text-typography-quaternary-text group-has-[:disabled]:hover:border-border-primary",
|
|
140
151
|
default:
|
|
141
152
|
"border-border-primary bg-fill-quarternary-fill-white hover:border-primary-base focus-within:border-primary-base",
|
|
142
153
|
error:
|
|
@@ -205,23 +216,29 @@
|
|
|
205
216
|
UPLOAD_WRAPPER_CLASS.base,
|
|
206
217
|
UPLOAD_WRAPPER_CLASS[currentVariant] || UPLOAD_WRAPPER_CLASS.default,
|
|
207
218
|
sizeClass,
|
|
208
|
-
className
|
|
219
|
+
className,
|
|
209
220
|
);
|
|
210
221
|
}
|
|
211
222
|
applyWrapperClasses();
|
|
212
223
|
uploadWrapper.setAttribute("data-file-input-variant", currentVariant);
|
|
213
224
|
uploadWrapper.setAttribute("data-file-input-size", currentInputSize);
|
|
214
|
-
uploadWrapper.setAttribute(
|
|
225
|
+
uploadWrapper.setAttribute(
|
|
226
|
+
"data-disabled",
|
|
227
|
+
disabledState ? "true" : "false",
|
|
228
|
+
);
|
|
215
229
|
|
|
216
230
|
// Left content (button + status) – pointer-events-none so overlay input receives clicks
|
|
217
231
|
const leftContent = document.createElement("div");
|
|
218
|
-
leftContent.className =
|
|
232
|
+
leftContent.className =
|
|
233
|
+
"pointer-events-none flex min-w-0 flex-1 items-center gap-8 truncate";
|
|
219
234
|
|
|
220
235
|
var disabledChildClasses =
|
|
221
236
|
"group-has-[:disabled]:text-typography-quaternary-text group-has-[:disabled]:bg-fill-tertiary-fill-light-gray group-has-[:disabled]:hover:bg-fill-tertiary-fill-light-gray";
|
|
222
237
|
var statusTextBaseClass = "text-reg-13 min-w-0 flex-1 truncate";
|
|
223
|
-
var statusTextDisabledClass =
|
|
224
|
-
|
|
238
|
+
var statusTextDisabledClass =
|
|
239
|
+
" group-has-[:disabled]:text-typography-quaternary-text";
|
|
240
|
+
const useButtonComponent =
|
|
241
|
+
global.Button && typeof global.Button.create === "function";
|
|
225
242
|
const initialButtonText = multiple ? "Choose files" : "Choose a file";
|
|
226
243
|
const btn = useButtonComponent
|
|
227
244
|
? global.Button.create({
|
|
@@ -241,7 +258,10 @@
|
|
|
241
258
|
|
|
242
259
|
// Status text: "No files chosen" (quaternary) or "X file(s) selected"
|
|
243
260
|
const statusText = document.createElement("p");
|
|
244
|
-
statusText.className =
|
|
261
|
+
statusText.className =
|
|
262
|
+
statusTextBaseClass +
|
|
263
|
+
" text-typography-quaternary-text" +
|
|
264
|
+
statusTextDisabledClass;
|
|
245
265
|
|
|
246
266
|
// Hidden file input – overlays row, high z-index so it receives clicks
|
|
247
267
|
const input = document.createElement("input");
|
|
@@ -250,7 +270,9 @@
|
|
|
250
270
|
function applyInputClasses() {
|
|
251
271
|
input.className =
|
|
252
272
|
inputBaseClass +
|
|
253
|
-
(disabledState
|
|
273
|
+
(disabledState
|
|
274
|
+
? " !pointer-events-none !cursor-not-allowed"
|
|
275
|
+
: " cursor-pointer");
|
|
254
276
|
}
|
|
255
277
|
applyInputClasses();
|
|
256
278
|
input.multiple = multiple;
|
|
@@ -294,7 +316,8 @@
|
|
|
294
316
|
function makeCopyBtn(fileUrl) {
|
|
295
317
|
const btn = document.createElement("button");
|
|
296
318
|
btn.type = "button";
|
|
297
|
-
btn.className =
|
|
319
|
+
btn.className =
|
|
320
|
+
"shrink-0 rounded-2 p-4 text-typography-quaternary-text transition-colors hover:text-typography-primary-text focus:outline-none";
|
|
298
321
|
btn.innerHTML = ICONS.copy;
|
|
299
322
|
btn.setAttribute("aria-label", "Copy URL");
|
|
300
323
|
btn.addEventListener("click", function (e) {
|
|
@@ -309,7 +332,7 @@
|
|
|
309
332
|
if (global.FlowUI && global.FlowUI.showToast) {
|
|
310
333
|
global.FlowUI.showToast("Copy failed", "error");
|
|
311
334
|
}
|
|
312
|
-
}
|
|
335
|
+
},
|
|
313
336
|
);
|
|
314
337
|
});
|
|
315
338
|
return btn;
|
|
@@ -318,7 +341,8 @@
|
|
|
318
341
|
function makeViewBtn(fileUrl) {
|
|
319
342
|
const btn = document.createElement("button");
|
|
320
343
|
btn.type = "button";
|
|
321
|
-
btn.className =
|
|
344
|
+
btn.className =
|
|
345
|
+
"shrink-0 rounded-2 p-4 text-typography-quaternary-text transition-colors hover:text-typography-primary-text focus:outline-none";
|
|
322
346
|
btn.innerHTML = ICONS.eye;
|
|
323
347
|
btn.setAttribute("aria-label", "View file");
|
|
324
348
|
btn.addEventListener("click", function (e) {
|
|
@@ -331,7 +355,8 @@
|
|
|
331
355
|
function makeRemoveBtn(onRemove) {
|
|
332
356
|
const btn = document.createElement("button");
|
|
333
357
|
btn.type = "button";
|
|
334
|
-
btn.className =
|
|
358
|
+
btn.className =
|
|
359
|
+
"shrink-0 rounded-2 p-4 text-error-text-base transition-colors hover:bg-error-surface focus:outline-none";
|
|
335
360
|
btn.innerHTML = ICONS.x;
|
|
336
361
|
btn.setAttribute("aria-label", "Remove file");
|
|
337
362
|
btn.addEventListener("click", function (e) {
|
|
@@ -348,7 +373,8 @@
|
|
|
348
373
|
|
|
349
374
|
uploadedFiles.forEach(function (file, index) {
|
|
350
375
|
var nameEl = document.createElement("p");
|
|
351
|
-
nameEl.className =
|
|
376
|
+
nameEl.className =
|
|
377
|
+
"max-w-[100px] truncate text-reg-10 text-typography-secondary-text";
|
|
352
378
|
nameEl.textContent = file.name;
|
|
353
379
|
nameEl.title = file.url;
|
|
354
380
|
|
|
@@ -356,12 +382,14 @@
|
|
|
356
382
|
actionsWrap.className = "flex items-center gap-2 shrink-0";
|
|
357
383
|
actionsWrap.appendChild(makeCopyBtn(file.url));
|
|
358
384
|
actionsWrap.appendChild(makeViewBtn(file.url));
|
|
359
|
-
actionsWrap.appendChild(
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
385
|
+
actionsWrap.appendChild(
|
|
386
|
+
makeRemoveBtn(function () {
|
|
387
|
+
uploadedFiles.splice(index, 1);
|
|
388
|
+
saveToState();
|
|
389
|
+
renderFilesList();
|
|
390
|
+
updateStatus();
|
|
391
|
+
}),
|
|
392
|
+
);
|
|
365
393
|
|
|
366
394
|
var badge;
|
|
367
395
|
if (useBadge) {
|
|
@@ -373,12 +401,15 @@
|
|
|
373
401
|
className: "text-typography-primary-text",
|
|
374
402
|
});
|
|
375
403
|
var iconWraps = badge.querySelectorAll(".flex.size-16");
|
|
376
|
-
if (iconWraps.length >= 1)
|
|
404
|
+
if (iconWraps.length >= 1)
|
|
405
|
+
iconWraps[0].classList.add("text-typography-secondary-text");
|
|
377
406
|
} else {
|
|
378
407
|
badge = document.createElement("div");
|
|
379
|
-
badge.className =
|
|
408
|
+
badge.className =
|
|
409
|
+
"inline-flex items-center gap-4 rounded-4 border-1/2 border-border-primary bg-fill-tertiary-fill-light-gray px-8 py-2 text-med-12 text-typography-primary-text";
|
|
380
410
|
var iconWrap = document.createElement("span");
|
|
381
|
-
iconWrap.className =
|
|
411
|
+
iconWrap.className =
|
|
412
|
+
"flex size-16 shrink-0 items-center justify-center text-typography-secondary-text";
|
|
382
413
|
iconWrap.innerHTML = getFileTypeIcon(file.name || file.url);
|
|
383
414
|
badge.appendChild(iconWrap);
|
|
384
415
|
badge.appendChild(nameEl);
|
|
@@ -396,7 +427,8 @@
|
|
|
396
427
|
|
|
397
428
|
uploadingFiles.forEach(function (item) {
|
|
398
429
|
var nameEl = document.createElement("p");
|
|
399
|
-
nameEl.className =
|
|
430
|
+
nameEl.className =
|
|
431
|
+
"max-w-[100px] truncate text-reg-10 text-typography-secondary-text";
|
|
400
432
|
nameEl.textContent = item.name;
|
|
401
433
|
|
|
402
434
|
var badge;
|
|
@@ -408,12 +440,15 @@
|
|
|
408
440
|
className: "text-typography-primary-text",
|
|
409
441
|
});
|
|
410
442
|
var iconWraps = badge.querySelectorAll(".flex.size-16");
|
|
411
|
-
if (iconWraps.length >= 1)
|
|
443
|
+
if (iconWraps.length >= 1)
|
|
444
|
+
iconWraps[0].classList.add("text-typography-secondary-text");
|
|
412
445
|
} else {
|
|
413
446
|
badge = document.createElement("div");
|
|
414
|
-
badge.className =
|
|
447
|
+
badge.className =
|
|
448
|
+
"inline-flex items-center gap-4 rounded-4 border-1/2 border-border-primary bg-fill-tertiary-fill-light-gray px-8 py-2 text-med-12";
|
|
415
449
|
var iconWrap = document.createElement("span");
|
|
416
|
-
iconWrap.className =
|
|
450
|
+
iconWrap.className =
|
|
451
|
+
"flex size-16 shrink-0 items-center justify-center text-typography-secondary-text";
|
|
417
452
|
iconWrap.innerHTML = ICONS.loader;
|
|
418
453
|
badge.appendChild(iconWrap);
|
|
419
454
|
badge.appendChild(nameEl);
|
|
@@ -443,13 +478,22 @@
|
|
|
443
478
|
|
|
444
479
|
if (!filesChosen) {
|
|
445
480
|
statusText.textContent = "No files chosen";
|
|
446
|
-
statusText.className =
|
|
481
|
+
statusText.className =
|
|
482
|
+
statusTextBaseClass +
|
|
483
|
+
" text-typography-quaternary-text" +
|
|
484
|
+
statusTextDisabledClass;
|
|
447
485
|
} else if (uploadingCount > 0) {
|
|
448
486
|
statusText.textContent = "Uploading…";
|
|
449
|
-
statusText.className =
|
|
487
|
+
statusText.className =
|
|
488
|
+
statusTextBaseClass +
|
|
489
|
+
" text-typography-quaternary-text" +
|
|
490
|
+
statusTextDisabledClass;
|
|
450
491
|
} else {
|
|
451
492
|
statusText.textContent = `${uploadedCount} file${uploadedCount !== 1 ? "s" : ""} selected`;
|
|
452
|
-
statusText.className =
|
|
493
|
+
statusText.className =
|
|
494
|
+
statusTextBaseClass +
|
|
495
|
+
" text-typography-primary-text" +
|
|
496
|
+
statusTextDisabledClass;
|
|
453
497
|
}
|
|
454
498
|
|
|
455
499
|
endIconSlot.style.display = uploadingCount > 0 ? "flex" : "none";
|
|
@@ -486,7 +530,9 @@
|
|
|
486
530
|
return fileExtension === type.toLowerCase();
|
|
487
531
|
}
|
|
488
532
|
if (type.includes("/")) {
|
|
489
|
-
return
|
|
533
|
+
return (
|
|
534
|
+
fileType === type || fileType.startsWith(type.split("/")[0] + "/")
|
|
535
|
+
);
|
|
490
536
|
}
|
|
491
537
|
return false;
|
|
492
538
|
});
|
|
@@ -517,9 +563,13 @@
|
|
|
517
563
|
const result = await uploadFileToS3(file, isPrivate);
|
|
518
564
|
const fileUrl = result?.url;
|
|
519
565
|
if (!fileUrl) {
|
|
520
|
-
throw new Error(
|
|
566
|
+
throw new Error(
|
|
567
|
+
`No URL returned from upload for file "${file.name}"`,
|
|
568
|
+
);
|
|
521
569
|
}
|
|
522
|
-
console.log(
|
|
570
|
+
console.log(
|
|
571
|
+
`[S3FileUpload] File uploaded successfully: ${file.name} -> ${fileUrl}`,
|
|
572
|
+
);
|
|
523
573
|
|
|
524
574
|
uploadingFiles = uploadingFiles.filter((f) => f.file !== file);
|
|
525
575
|
uploadedFiles.push({ url: fileUrl, name: file.name });
|
|
@@ -533,7 +583,10 @@
|
|
|
533
583
|
renderUploadingList();
|
|
534
584
|
updateStatus();
|
|
535
585
|
if (global.FlowUI && global.FlowUI.showToast) {
|
|
536
|
-
global.FlowUI.showToast(
|
|
586
|
+
global.FlowUI.showToast(
|
|
587
|
+
`Failed to upload "${file.name}": ${error.message}`,
|
|
588
|
+
"error",
|
|
589
|
+
);
|
|
537
590
|
}
|
|
538
591
|
}
|
|
539
592
|
}
|
|
@@ -541,10 +594,16 @@
|
|
|
541
594
|
// Handle file selection
|
|
542
595
|
input.addEventListener("change", async function (e) {
|
|
543
596
|
const files = Array.from(e.target.files || []);
|
|
544
|
-
if (files.length === 0) {
|
|
597
|
+
if (files.length === 0) {
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
545
600
|
|
|
546
601
|
// Check max files limit
|
|
547
|
-
if (
|
|
602
|
+
if (
|
|
603
|
+
multiple &&
|
|
604
|
+
maxFiles &&
|
|
605
|
+
uploadedFiles.length + files.length > maxFiles
|
|
606
|
+
) {
|
|
548
607
|
const message = `You can only upload ${maxFiles} file${maxFiles !== 1 ? "s" : ""}`;
|
|
549
608
|
if (global.FlowUI && global.FlowUI.showToast) {
|
|
550
609
|
global.FlowUI.showToast(message, "error");
|
|
@@ -591,7 +650,10 @@
|
|
|
591
650
|
input.disabled = disabledState;
|
|
592
651
|
applyInputClasses();
|
|
593
652
|
applyWrapperClasses();
|
|
594
|
-
uploadWrapper.setAttribute(
|
|
653
|
+
uploadWrapper.setAttribute(
|
|
654
|
+
"data-disabled",
|
|
655
|
+
disabledState ? "true" : "false",
|
|
656
|
+
);
|
|
595
657
|
};
|
|
596
658
|
|
|
597
659
|
field.setVariant = function (v) {
|
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
var CHECK_SVG =
|
|
13
13
|
'<svg width="14" height="14" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M11.4669 3.72684C11.7558 3.41574 12.2442 3.41574 12.5331 3.72684C12.822 4.03795 12.822 4.53753 12.5331 4.84863L6.81767 10.6736C6.52329 10.9901 6.05308 10.9901 5.7587 10.6736L2.46685 7.3463C2.17795 7.03519 2.17795 6.53561 2.46685 6.2245C2.75575 5.9134 3.24395 5.9134 3.53285 6.2245L6.28822 9.05351L11.4669 3.72684Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"/></svg>';
|
|
14
14
|
|
|
15
|
+
var SEARCH_ICON =
|
|
16
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>';
|
|
17
|
+
|
|
15
18
|
function triggerClasses(variant, size, disabled, isEmpty) {
|
|
16
19
|
var v = variant || "default";
|
|
17
20
|
var base =
|
|
@@ -80,6 +83,7 @@
|
|
|
80
83
|
* @param {boolean} [config.disabled] - Whether multiselect is disabled
|
|
81
84
|
* @param {string} [config.variant] - 'default' | 'error' | 'warning' | 'borderless' | 'inline'
|
|
82
85
|
* @param {string} [config.size] - 'default' | 'large' | 'small'
|
|
86
|
+
* @param {boolean} [config.showSearch] - Show search input (default: false)
|
|
83
87
|
* @returns {HTMLElement} MultiSelect container element
|
|
84
88
|
*/
|
|
85
89
|
function createMultiSelect(config) {
|
|
@@ -90,7 +94,9 @@
|
|
|
90
94
|
var onValuesChange = config.onValuesChange;
|
|
91
95
|
var variant = config.variant || "default";
|
|
92
96
|
var size = config.size || "default";
|
|
97
|
+
var showSearch = !!config.showSearch;
|
|
93
98
|
var disabled = config.disabled === true;
|
|
99
|
+
var searchQuery = "";
|
|
94
100
|
|
|
95
101
|
var values = Array.isArray(config.value)
|
|
96
102
|
? config.value.slice()
|
|
@@ -156,6 +162,35 @@
|
|
|
156
162
|
content.setAttribute("aria-multiselectable", "true");
|
|
157
163
|
content.className = "custom-multiselect-content w-full max-h-[30vh] overflow-hidden flex flex-col";
|
|
158
164
|
|
|
165
|
+
var searchContainer = document.createElement("div");
|
|
166
|
+
searchContainer.className = showSearch ? "p-8 border-b-1/2 border-border-primary" : "p-8 hidden";
|
|
167
|
+
var searchInputWrapper = document.createElement("div");
|
|
168
|
+
searchInputWrapper.className = "flex items-center gap-8";
|
|
169
|
+
var searchIconSpan = document.createElement("span");
|
|
170
|
+
searchIconSpan.className = "shrink-0 text-typography-tertiary-text";
|
|
171
|
+
searchIconSpan.innerHTML = SEARCH_ICON;
|
|
172
|
+
searchInputWrapper.appendChild(searchIconSpan);
|
|
173
|
+
var searchInput = document.createElement("input");
|
|
174
|
+
searchInput.type = "text";
|
|
175
|
+
searchInput.placeholder = "Search...";
|
|
176
|
+
searchInput.className =
|
|
177
|
+
"w-full bg-transparent text-reg-13 text-typography-primary-text placeholder:text-typography-quaternary-text focus:outline-none border-none";
|
|
178
|
+
searchInput.setAttribute("aria-label", "Search options");
|
|
179
|
+
searchInput.addEventListener("input", function (e) {
|
|
180
|
+
searchQuery = e.target.value.trim();
|
|
181
|
+
buildOptionsList();
|
|
182
|
+
syncOptionStates();
|
|
183
|
+
});
|
|
184
|
+
searchInput.addEventListener("click", function (e) {
|
|
185
|
+
e.stopPropagation();
|
|
186
|
+
});
|
|
187
|
+
searchInput.addEventListener("keydown", function (e) {
|
|
188
|
+
e.stopPropagation();
|
|
189
|
+
});
|
|
190
|
+
searchInputWrapper.appendChild(searchInput);
|
|
191
|
+
searchContainer.appendChild(searchInputWrapper);
|
|
192
|
+
content.appendChild(searchContainer);
|
|
193
|
+
|
|
159
194
|
var optionsList = document.createElement("div");
|
|
160
195
|
optionsList.className =
|
|
161
196
|
"overflow-y-auto max-h-[30vh] p-2 w-full rounded-4 bg-fill-quarternary-fill-white";
|
|
@@ -196,17 +231,36 @@
|
|
|
196
231
|
});
|
|
197
232
|
}
|
|
198
233
|
|
|
234
|
+
function getFilteredOptions() {
|
|
235
|
+
if (!showSearch || !searchQuery) return options.slice();
|
|
236
|
+
var normalizedQuery = searchQuery.toLowerCase();
|
|
237
|
+
return options.filter(function (opt) {
|
|
238
|
+
var optionValue = getOptionValue(opt);
|
|
239
|
+
var optionLabel = getOptionLabel(opt);
|
|
240
|
+
return (
|
|
241
|
+
String(optionLabel || "").toLowerCase().includes(normalizedQuery) ||
|
|
242
|
+
String(optionValue || "").toLowerCase().includes(normalizedQuery)
|
|
243
|
+
);
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
199
247
|
function buildOptionsList() {
|
|
200
248
|
optionsList.innerHTML = "";
|
|
201
|
-
|
|
249
|
+
var filteredOptions = getFilteredOptions();
|
|
250
|
+
if (filteredOptions.length === 0) {
|
|
202
251
|
var noOpt = document.createElement("div");
|
|
203
252
|
noOpt.className =
|
|
204
253
|
"flex h-full min-h-[100px] w-full items-center justify-center p-4 !text-reg-13 text-typography-quaternary-text";
|
|
205
|
-
noOpt.textContent =
|
|
254
|
+
noOpt.textContent =
|
|
255
|
+
options.length === 0
|
|
256
|
+
? "No options available"
|
|
257
|
+
: searchQuery
|
|
258
|
+
? "No options found"
|
|
259
|
+
: "No options available";
|
|
206
260
|
optionsList.appendChild(noOpt);
|
|
207
261
|
return;
|
|
208
262
|
}
|
|
209
|
-
|
|
263
|
+
filteredOptions.forEach(function (opt) {
|
|
210
264
|
var optionValue = getOptionValue(opt);
|
|
211
265
|
var optionLabel = getOptionLabel(opt);
|
|
212
266
|
var selected = isSelected(optionValue);
|
|
@@ -278,16 +332,29 @@
|
|
|
278
332
|
});
|
|
279
333
|
trigger.setAttribute("aria-expanded", "true");
|
|
280
334
|
chevron.style.transform = "rotate(180deg)";
|
|
335
|
+
buildOptionsList();
|
|
336
|
+
syncOptionStates();
|
|
281
337
|
if (popover.panel) {
|
|
282
338
|
var triggerWidthPx = trigger.offsetWidth + "px";
|
|
283
339
|
popover.panel.style.setProperty("--trigger-width", triggerWidthPx);
|
|
284
340
|
popover.panel.style.minWidth = triggerWidthPx;
|
|
285
341
|
popover.panel.style.width = triggerWidthPx;
|
|
286
342
|
}
|
|
343
|
+
if (showSearch) {
|
|
344
|
+
setTimeout(function () {
|
|
345
|
+
searchInput.focus();
|
|
346
|
+
}, 50);
|
|
347
|
+
}
|
|
287
348
|
},
|
|
288
349
|
onClose: function () {
|
|
289
350
|
trigger.setAttribute("aria-expanded", "false");
|
|
290
351
|
chevron.style.transform = "";
|
|
352
|
+
if (showSearch) {
|
|
353
|
+
searchQuery = "";
|
|
354
|
+
searchInput.value = "";
|
|
355
|
+
buildOptionsList();
|
|
356
|
+
syncOptionStates();
|
|
357
|
+
}
|
|
291
358
|
},
|
|
292
359
|
});
|
|
293
360
|
container.popoverInstance = popover;
|