@superleapai/flow-ui 2.5.18 → 2.5.19

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.
@@ -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
- if (options.length === 0) {
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 = "No options available";
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
- options.forEach(function (opt) {
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;
@@ -12,6 +12,9 @@
12
12
  var X_SVG =
13
13
  '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" xmlns="http://www.w3.org/2000/svg"><path d="M18 6L6 18M6 6l12 12"/></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, placeholder, hasClear) {
16
19
  var v = variant || "default";
17
20
  var base =
@@ -73,6 +76,7 @@
73
76
  * @param {string} [config.size] - 'default' | 'large' | 'small'
74
77
  * @param {boolean} [config.canClear] - Show clear button when value is set
75
78
  * @param {Function} [config.onClear] - Called when clear is clicked
79
+ * @param {boolean} [config.showSearch] - Show search input (default: false)
76
80
  * @returns {HTMLElement} Select container element
77
81
  */
78
82
  function createCustomSelect(config) {
@@ -84,9 +88,11 @@
84
88
  var size = config.size || "default";
85
89
  var canClear = !!config.canClear;
86
90
  var onClear = config.onClear;
91
+ var showSearch = !!config.showSearch;
87
92
  var popoverWrapperClassName = config.popoverWrapperClassName || "";
88
93
  var onOpenCallback = config.onOpenCallback || null;
89
94
  var dropdownMaxHeightVh = config.dropdownMaxHeightVh != null ? config.dropdownMaxHeightVh : 30;
95
+ var searchQuery = "";
90
96
 
91
97
  var disabled = config.disabled === true;
92
98
  var value =
@@ -199,19 +205,76 @@
199
205
  content.className = "custom-select-content w-full overflow-hidden flex flex-col";
200
206
  content.style.maxHeight = dropdownMaxHeightVh + "vh";
201
207
 
208
+ var searchContainer = document.createElement("div");
209
+ searchContainer.className = showSearch ? "p-8 border-b-1/2 border-border-primary" : "p-8 hidden";
210
+
211
+ var searchInputWrapper = document.createElement("div");
212
+ searchInputWrapper.className = "flex items-center gap-8";
213
+ var searchIconSpan = document.createElement("span");
214
+ searchIconSpan.className = "shrink-0 text-typography-tertiary-text";
215
+ searchIconSpan.innerHTML = SEARCH_ICON;
216
+ searchInputWrapper.appendChild(searchIconSpan);
217
+
218
+ var searchInput = document.createElement("input");
219
+ searchInput.type = "text";
220
+ searchInput.placeholder = "Search...";
221
+ searchInput.className =
222
+ "w-full bg-transparent text-reg-13 text-typography-primary-text placeholder:text-typography-quaternary-text focus:outline-none border-none";
223
+ searchInput.setAttribute("aria-label", "Search options");
224
+ searchInput.addEventListener("input", function (e) {
225
+ searchQuery = e.target.value.trim();
226
+ renderOptions();
227
+ updateOptionsSelection();
228
+ });
229
+ searchInput.addEventListener("click", function (e) {
230
+ e.stopPropagation();
231
+ });
232
+ searchInput.addEventListener("keydown", function (e) {
233
+ e.stopPropagation();
234
+ });
235
+ searchInputWrapper.appendChild(searchInput);
236
+ searchContainer.appendChild(searchInputWrapper);
237
+ content.appendChild(searchContainer);
238
+
202
239
  var optionsList = document.createElement("div");
203
240
  optionsList.className =
204
241
  "overflow-y-auto p-2 w-full rounded-4 bg-fill-quarternary-fill-white";
205
242
  optionsList.style.maxHeight = dropdownMaxHeightVh + "vh";
206
243
 
207
- if (options.length === 0) {
208
- var noOpt = document.createElement("div");
209
- noOpt.className =
210
- "flex h-full min-h-[100px] w-full items-center justify-center p-4 !text-reg-13 text-typography-quaternary-text";
211
- noOpt.textContent = "No options available";
212
- optionsList.appendChild(noOpt);
213
- } else {
214
- options.forEach(function (opt) {
244
+ function getFilteredOptions() {
245
+ if (!showSearch || !searchQuery) return options.slice();
246
+ var normalizedQuery = searchQuery.toLowerCase();
247
+ return options.filter(function (opt) {
248
+ var label =
249
+ opt.label || opt.name || opt.display_name || opt.value || "";
250
+ var optionValue =
251
+ opt.value !== undefined && opt.value !== null
252
+ ? opt.value
253
+ : opt.slug || opt.id || "";
254
+ return (
255
+ String(label).toLowerCase().includes(normalizedQuery) ||
256
+ String(optionValue).toLowerCase().includes(normalizedQuery)
257
+ );
258
+ });
259
+ }
260
+
261
+ function renderOptions() {
262
+ optionsList.innerHTML = "";
263
+ var filteredOptions = getFilteredOptions();
264
+ if (filteredOptions.length === 0) {
265
+ var noOpt = document.createElement("div");
266
+ noOpt.className =
267
+ "flex h-full min-h-[100px] w-full items-center justify-center p-4 !text-reg-13 text-typography-quaternary-text";
268
+ noOpt.textContent =
269
+ options.length === 0
270
+ ? "No options available"
271
+ : searchQuery
272
+ ? "No options found"
273
+ : "No options available";
274
+ optionsList.appendChild(noOpt);
275
+ return;
276
+ }
277
+ filteredOptions.forEach(function (opt) {
215
278
  var optionValue =
216
279
  opt.value !== undefined && opt.value !== null
217
280
  ? opt.value
@@ -250,6 +313,7 @@
250
313
  optionsList.appendChild(option);
251
314
  });
252
315
  }
316
+ renderOptions();
253
317
 
254
318
  content.appendChild(optionsList);
255
319
 
@@ -278,6 +342,8 @@
278
342
  });
279
343
  trigger.setAttribute("aria-expanded", "true");
280
344
  chevron.style.transform = "rotate(180deg)";
345
+ renderOptions();
346
+ updateOptionsSelection();
281
347
  highlightOptionByValue(value);
282
348
  if (popover.panel) {
283
349
  var triggerWidthPx = trigger.offsetWidth + "px";
@@ -285,11 +351,22 @@
285
351
  popover.panel.style.minWidth = triggerWidthPx;
286
352
  popover.panel.style.width = triggerWidthPx;
287
353
  }
354
+ if (showSearch) {
355
+ setTimeout(function () {
356
+ searchInput.focus();
357
+ }, 50);
358
+ }
288
359
  },
289
360
  onClose: function () {
290
361
  trigger.setAttribute("aria-expanded", "false");
291
362
  chevron.style.transform = "";
292
363
  highlightedIndex = -1;
364
+ if (showSearch) {
365
+ searchQuery = "";
366
+ searchInput.value = "";
367
+ renderOptions();
368
+ updateOptionsSelection();
369
+ }
293
370
  },
294
371
  });
295
372
  container.popoverInstance = popover;
@@ -486,53 +563,8 @@
486
563
  container.updateOptions = function (newOptions) {
487
564
  options.length = 0;
488
565
  options.push.apply(options, newOptions);
489
- optionsList.innerHTML = "";
490
-
491
- if (newOptions.length === 0) {
492
- var noOpt = document.createElement("div");
493
- noOpt.className =
494
- "flex h-full min-h-[100px] w-full items-center justify-center p-4 !text-reg-13 text-typography-quaternary-text";
495
- noOpt.textContent = "No options available";
496
- optionsList.appendChild(noOpt);
497
- return;
498
- }
499
-
500
- newOptions.forEach(function (opt) {
501
- var optionValue =
502
- opt.value !== undefined && opt.value !== null
503
- ? opt.value
504
- : opt.slug || opt.id;
505
- var optionLabel =
506
- opt.label || opt.name || opt.display_name || opt.value;
507
- var isSelected = optionValue === value;
508
-
509
- var option = document.createElement("div");
510
- option.setAttribute("role", "option");
511
- option.setAttribute("data-value", optionValue);
512
- option.setAttribute("aria-selected", isSelected);
513
- option.className = join(
514
- "relative flex w-full cursor-pointer select-none items-center gap-8 rounded-2 px-12 py-6 text-reg-13 outline-none first:rounded-t-4 last:rounded-b-4",
515
- "hover:bg-fill-tertiary-fill-light-gray focus:bg-fill-tertiary-fill-light-gray",
516
- isSelected
517
- ? "bg-primary-surface hover:!bg-primary-surface-hover"
518
- : ""
519
- );
520
-
521
- var optionContent = document.createElement("span");
522
- optionContent.className = "flex items-center gap-8 flex-1 truncate";
523
- optionContent.textContent = optionLabel;
524
- option.appendChild(optionContent);
525
-
526
- option.addEventListener("click", function () {
527
- if (disabled) return;
528
- selectOption(optionValue);
529
- });
530
- option.addEventListener("mouseenter", function () {
531
- if (disabled) return;
532
- highlightOption(option);
533
- });
534
- optionsList.appendChild(option);
535
- });
566
+ renderOptions();
567
+ updateOptionsSelection();
536
568
  };
537
569
 
538
570
  container.setDisabled = function (isDisabled) {
package/core/flow.js CHANGED
@@ -277,11 +277,12 @@
277
277
  * @param {Array} config.options - Array of { value, label } objects
278
278
  * @param {boolean} config.required - Whether field is required
279
279
  * @param {Function} config.onChange - Optional change handler
280
+ * @param {boolean} [config.showSearch] - Show local search input in dropdown
280
281
  * @param {boolean} config.disabled - Whether select is disabled
281
282
  * @returns {HTMLElement} Field element
282
283
  */
283
284
  function createSelect(config) {
284
- const { label, fieldId, options = [], required = false, onChange, disabled = false, helpText = null } = config;
285
+ const { label, fieldId, options = [], required = false, onChange, showSearch = false, disabled = false, helpText = null } = config;
285
286
 
286
287
  const field = createFieldWrapper(label, required, helpText);
287
288
 
@@ -295,6 +296,7 @@
295
296
  options,
296
297
  placeholder,
297
298
  value: currentValue,
299
+ showSearch,
298
300
  disabled,
299
301
  onChange: (value) => {
300
302
  set(fieldId, value);
@@ -678,11 +680,12 @@
678
680
  * @param {string} config.variant - 'default' | 'error' | 'warning' | 'borderless' | 'inline'
679
681
  * @param {string} config.size - 'default' | 'large' | 'small'
680
682
  * @param {string} config.type - 'default' (count) | 'tags' (show tags in trigger)
683
+ * @param {boolean} [config.showSearch] - Show local search input in dropdown
681
684
  * @param {boolean} config.disabled - Whether multiselect is disabled
682
685
  * @returns {HTMLElement} Field element
683
686
  */
684
687
  function createMultiSelect(config) {
685
- const { label, fieldId, options = [], required = false, onChange, placeholder, helpText = null, variant, size, type, disabled = false } = config;
688
+ const { label, fieldId, options = [], required = false, onChange, placeholder, helpText = null, variant, size, type, showSearch = false, disabled = false } = config;
686
689
 
687
690
  const field = createFieldWrapper(label, required, helpText);
688
691
 
@@ -697,6 +700,7 @@
697
700
  variant: variant || "default",
698
701
  size: size || "default",
699
702
  type: type || "default",
703
+ showSearch,
700
704
  disabled,
701
705
  onValuesChange: (values) => {
702
706
  set(fieldId, values);