@superleapai/flow-ui 1.0.0

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.
Files changed (40) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/LICENSE +21 -0
  3. package/README.md +451 -0
  4. package/components/alert.js +282 -0
  5. package/components/avatar.js +195 -0
  6. package/components/badge.js +135 -0
  7. package/components/button.js +201 -0
  8. package/components/checkbox.js +254 -0
  9. package/components/currency.js +227 -0
  10. package/components/date-time-picker/date-time-picker-utils.js +253 -0
  11. package/components/date-time-picker/date-time-picker.js +532 -0
  12. package/components/duration/duration-constants.js +46 -0
  13. package/components/duration/duration-utils.js +164 -0
  14. package/components/duration/duration.js +448 -0
  15. package/components/enum-multiselect.js +869 -0
  16. package/components/enum-select.js +831 -0
  17. package/components/enumeration.js +213 -0
  18. package/components/file-input.js +533 -0
  19. package/components/icon.js +200 -0
  20. package/components/input.js +259 -0
  21. package/components/label.js +111 -0
  22. package/components/multiselect.js +351 -0
  23. package/components/phone-input/phone-input.js +392 -0
  24. package/components/phone-input/phone-utils.js +157 -0
  25. package/components/popover.js +240 -0
  26. package/components/radio-group.js +435 -0
  27. package/components/record-multiselect.js +956 -0
  28. package/components/record-select.js +930 -0
  29. package/components/select.js +544 -0
  30. package/components/spinner.js +136 -0
  31. package/components/table.js +335 -0
  32. package/components/textarea.js +114 -0
  33. package/components/time-picker.js +357 -0
  34. package/components/toast.js +343 -0
  35. package/core/flow.js +1729 -0
  36. package/core/superleapClient.js +146 -0
  37. package/dist/output.css +2 -0
  38. package/index.d.ts +458 -0
  39. package/index.js +253 -0
  40. package/package.json +70 -0
@@ -0,0 +1,831 @@
1
+ /**
2
+ * EnumSelect Component (vanilla JS, Tailwind)
3
+ * A select component that fetches options from SuperLeap SDK based on object column metadata.
4
+ * Supports dependent fields, URL-based enums, and search functionality.
5
+ *
6
+ * Usage:
7
+ * var enumSelect = EnumSelect.create({
8
+ * fieldId: 'status',
9
+ * objectSlug: 'accounts',
10
+ * columnSlug: 'status',
11
+ * value: 'active',
12
+ * onChange: function(value) { console.log('Selected:', value); },
13
+ * placeholder: 'Select status',
14
+ * variant: 'default',
15
+ * size: 'default',
16
+ * disabled: false,
17
+ * currentRecordData: { ... }, // Optional: for dependent fields
18
+ * canClear: true,
19
+ * onClear: function() { console.log('Cleared'); }
20
+ * });
21
+ */
22
+
23
+ (function (global) {
24
+ "use strict";
25
+
26
+ var CHEVRON_SVG =
27
+ '<svg width="16" height="16" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"/></svg>';
28
+
29
+ var X_SVG =
30
+ '<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>';
31
+
32
+ var SEARCH_ICON =
33
+ '<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>';
34
+
35
+ function triggerClasses(variant, size, disabled, placeholder, hasClear) {
36
+ var v = variant || "default";
37
+ var base =
38
+ "w-full items-center justify-between rounded-4 border-1/2 bg-fill-quarternary-fill-white text-typography-primary-text !text-reg-13 focus:outline-none flex h-full truncate hover:cursor-pointer ";
39
+ var variantClasses = {
40
+ default:
41
+ "border-border-primary hover:border-primary-border focus:border-1/2 focus:border-primary-border",
42
+ error:
43
+ "border-error-border hover:border-error-border-hover focus:border-1/2 focus:border-error-border-hover",
44
+ warning:
45
+ "border-warning-border hover:border-warning-border-hover focus:border-1/2 focus:border-warning-border-hover",
46
+ borderless: "border-none shadow-none rounded-0 bg-fill-quarternary-fill-white",
47
+ inline:
48
+ "focus:border-transparent border border-transparent shadow-none rounded-0 bg-fill-quarternary-fill-white hover:bg-fill-tertiary-fill-light-gray hover:border-transparent",
49
+ };
50
+ var sizeClasses = {
51
+ default: "px-12 py-6",
52
+ large: "px-12 py-8",
53
+ small: "px-8 py-4",
54
+ };
55
+ var placeholderClass = placeholder ? " text-typography-quaternary-text" : "";
56
+ var disabledClass = disabled
57
+ ? " pointer-events-none cursor-not-allowed bg-fill-tertiary-fill-light-gray text-typography-quaternary-text hover:border-border-primary"
58
+ : "";
59
+ var clearPadding = hasClear ? " pr-24" : "";
60
+ return (
61
+ base +
62
+ (variantClasses[v] || variantClasses.default) +
63
+ " " +
64
+ (sizeClasses[size] || sizeClasses.default) +
65
+ placeholderClass +
66
+ disabledClass +
67
+ clearPadding
68
+ );
69
+ }
70
+
71
+ function join() {
72
+ return Array.prototype.filter.call(arguments, Boolean).join(" ");
73
+ }
74
+
75
+ /** Resolve client: use FlowUI._getComponent when bundle has captured globals, else global.superleapClient */
76
+ function getClient() {
77
+ if (global.FlowUI && typeof global.FlowUI._getComponent === "function") {
78
+ var c = global.FlowUI._getComponent("superleapClient");
79
+ if (c) return c;
80
+ }
81
+ return global.superleapClient;
82
+ }
83
+
84
+ /** Resolve Icon component for badge_config icon_color / start_icon */
85
+ function getIcon() {
86
+ if (global.FlowUI && typeof global.FlowUI._getComponent === "function") {
87
+ var c = global.FlowUI._getComponent("Icon");
88
+ if (c) return c;
89
+ }
90
+ return global.Icon;
91
+ }
92
+
93
+ /**
94
+ * Create an enum-based select component
95
+ * @param {Object} config
96
+ * @param {string} config.fieldId - Field ID for state management
97
+ * @param {string} config.objectSlug - Object slug (e.g., 'accounts')
98
+ * @param {string} config.columnSlug - Column slug (e.g., 'status')
99
+ * @param {string} config.placeholder - Placeholder text
100
+ * @param {string} config.value - Current value
101
+ * @param {Function} config.onChange - Change handler
102
+ * @param {boolean} config.disabled - Whether select is disabled
103
+ * @param {string} [config.variant] - 'default' | 'error' | 'warning' | 'borderless' | 'inline'
104
+ * @param {string} [config.size] - 'default' | 'large' | 'small'
105
+ * @param {boolean} [config.canClear] - Show clear button when value is set
106
+ * @param {Function} [config.onClear] - Called when clear is clicked
107
+ * @param {Object} [config.currentRecordData] - Current record data for dependent fields
108
+ * @param {boolean} [config.showSearch] - Show search input (default: true if more than 10 options)
109
+ * @param {Function} [config.onLoad] - Called when options are loaded
110
+ * @param {Function} [config.onError] - Called when error occurs
111
+ * @returns {HTMLElement} Select container element
112
+ */
113
+ function createEnumSelect(config) {
114
+ var fieldId = config.fieldId;
115
+ var objectSlug = config.objectSlug;
116
+ var columnSlug = config.columnSlug;
117
+ var placeholder = config.placeholder || "Select an option";
118
+ var onChange = config.onChange;
119
+ var variant = config.variant || "default";
120
+ var size = config.size || "default";
121
+ var canClear = !!config.canClear;
122
+ var onClear = config.onClear;
123
+ var currentRecordData = config.currentRecordData || {};
124
+ var showSearch = config.showSearch;
125
+ var onLoad = config.onLoad;
126
+ var onError = config.onError;
127
+
128
+ var disabled = config.disabled === true;
129
+ var value =
130
+ config.value !== undefined && config.value !== null ? config.value : "";
131
+
132
+ // State
133
+ var options = [];
134
+ var columnData = null;
135
+ var isLoading = true;
136
+ var error = null;
137
+ var searchQuery = "";
138
+ var popover = null;
139
+ var usePopover = !!(global.Popover && typeof global.Popover.create === "function");
140
+ var isOpen = false;
141
+
142
+ var container = document.createElement("div");
143
+ container.className = "enum-select relative w-full group";
144
+ container.setAttribute("data-field-id", fieldId);
145
+
146
+ var triggerWrapper = document.createElement("span");
147
+ triggerWrapper.className =
148
+ "select-trigger-wrapper relative flex w-full items-center justify-between gap-8";
149
+
150
+ var trigger = document.createElement("button");
151
+ trigger.type = "button";
152
+ trigger.className = triggerClasses(variant, size, disabled, !value, canClear && !!value && !disabled);
153
+ trigger.disabled = disabled;
154
+ trigger.setAttribute("aria-haspopup", "listbox");
155
+ trigger.setAttribute("aria-expanded", "false");
156
+ trigger.setAttribute("aria-label", placeholder);
157
+
158
+ var triggerText = document.createElement("div");
159
+ triggerText.className = "truncate text-inherit";
160
+ triggerText.textContent = placeholder;
161
+ trigger.appendChild(triggerText);
162
+
163
+ var chevron = document.createElement("span");
164
+ chevron.className =
165
+ "ml-4 box-content flex size-16 items-center justify-center shrink-0 transition-transform duration-200 group-[.open]:rotate-180";
166
+ chevron.innerHTML = CHEVRON_SVG;
167
+ chevron.setAttribute("aria-hidden", "true");
168
+
169
+ var showChevron = variant !== "inline" || !value;
170
+ if (showChevron) {
171
+ trigger.appendChild(chevron);
172
+ }
173
+
174
+ triggerWrapper.appendChild(trigger);
175
+
176
+ // Create clear button (will be shown/hidden dynamically)
177
+ var clearBtn = document.createElement("button");
178
+ clearBtn.type = "button";
179
+ clearBtn.className =
180
+ "rounded-full absolute right-12 top-1/2 -translate-y-1/2 bg-transparent p-0 text-typography-tertiary-text hover:text-typography-secondary-text focus:outline-none";
181
+ clearBtn.innerHTML = X_SVG;
182
+ clearBtn.setAttribute("aria-label", "Clear selection");
183
+ clearBtn.style.display = canClear && value && !disabled ? "" : "none";
184
+ clearBtn.addEventListener("click", function (e) {
185
+ e.stopPropagation();
186
+ value = "";
187
+ triggerText.textContent = placeholder;
188
+ trigger.className = triggerClasses(
189
+ variant,
190
+ size,
191
+ disabled,
192
+ true,
193
+ false
194
+ );
195
+
196
+ // Update chevron visibility for inline variant
197
+ var shouldShowChevron = variant !== "inline" || !value;
198
+ if (shouldShowChevron && !chevron.parentNode) {
199
+ trigger.appendChild(chevron);
200
+ } else if (!shouldShowChevron && chevron.parentNode) {
201
+ chevron.parentNode.removeChild(chevron);
202
+ }
203
+
204
+ updateClearButton();
205
+ renderOptions();
206
+
207
+ if (onClear) onClear();
208
+ if (onChange) onChange("");
209
+ });
210
+ triggerWrapper.appendChild(clearBtn);
211
+
212
+ container.appendChild(triggerWrapper);
213
+
214
+ // Helper to update clear button visibility
215
+ function updateClearButton() {
216
+ if (clearBtn) {
217
+ clearBtn.style.display = canClear && value && !disabled ? "" : "none";
218
+ }
219
+ }
220
+
221
+ // Create dropdown content
222
+ var content = document.createElement("div");
223
+ content.setAttribute("role", "listbox");
224
+ var contentBase = "w-full min-w-[200px]";
225
+ if (!usePopover) {
226
+ contentBase += " absolute left-0 right-0 z-50 max-h-[30vh] overflow-hidden rounded-4 bg-fill-quarternary-fill-white shadow-default-medium border-1/2 border-border-primary opacity-0 invisible transition-all duration-150 ease-out group-[.open]:opacity-100 group-[.open]:visible top-full mt-1 -translate-y-1 group-[.open]:translate-y-0 flex flex-col";
227
+ } else {
228
+ contentBase += " max-h-[30vh] overflow-hidden flex flex-col";
229
+ }
230
+ content.className = contentBase;
231
+
232
+ // Search input (using InputComponent like phone-input)
233
+ var searchContainer = document.createElement("div");
234
+ searchContainer.className = "py-2 border-b-1/2 border-border-primary hidden";
235
+
236
+ var searchInputWrapper = null;
237
+ var searchInput = null;
238
+ if (global.InputComponent && typeof global.InputComponent.create === "function") {
239
+ searchInputWrapper = global.InputComponent.create({
240
+ variant: "borderless",
241
+ inputSize: "small",
242
+ type: "text",
243
+ placeholder: "Search...",
244
+ value: "",
245
+ startIcon: SEARCH_ICON,
246
+ className: "!border-0 !p-0",
247
+ onInput: function () {
248
+ searchQuery = searchInputWrapper.getValue().trim();
249
+ renderOptions();
250
+ },
251
+ });
252
+ var inputEl = searchInputWrapper.getInput();
253
+ if (inputEl) {
254
+ inputEl.setAttribute("aria-label", "Search options");
255
+ inputEl.addEventListener("click", function (e) {
256
+ e.stopPropagation();
257
+ });
258
+ searchInput = inputEl;
259
+ }
260
+ searchContainer.appendChild(searchInputWrapper);
261
+ } else {
262
+ // Fallback to native input with search icon
263
+ var fallbackWrapper = document.createElement("div");
264
+ fallbackWrapper.className = "flex items-center gap-8";
265
+
266
+ var searchIconSpan = document.createElement("span");
267
+ searchIconSpan.className = "shrink-0 text-typography-tertiary-text";
268
+ searchIconSpan.innerHTML = SEARCH_ICON;
269
+ fallbackWrapper.appendChild(searchIconSpan);
270
+
271
+ var nativeSearchInput = document.createElement("input");
272
+ nativeSearchInput.type = "text";
273
+ nativeSearchInput.placeholder = "Search...";
274
+ nativeSearchInput.className =
275
+ "w-full bg-transparent text-reg-13 text-typography-primary-text placeholder:text-typography-quaternary-text focus:outline-none border-none";
276
+ nativeSearchInput.addEventListener("input", function (e) {
277
+ searchQuery = e.target.value.trim();
278
+ renderOptions();
279
+ });
280
+ nativeSearchInput.addEventListener("click", function (e) {
281
+ e.stopPropagation();
282
+ });
283
+ searchInput = nativeSearchInput;
284
+ fallbackWrapper.appendChild(nativeSearchInput);
285
+ searchContainer.appendChild(fallbackWrapper);
286
+ }
287
+
288
+ // Options list
289
+ var optionsList = document.createElement("div");
290
+ optionsList.className =
291
+ "overflow-y-auto max-h-[30vh] p-2 w-full rounded-4 bg-fill-quarternary-fill-white flex-1 min-h-0";
292
+
293
+ content.appendChild(searchContainer);
294
+ content.appendChild(optionsList);
295
+
296
+ if (!usePopover) {
297
+ container.appendChild(content);
298
+ }
299
+
300
+ var highlightedIndex = -1;
301
+
302
+ function showLoading() {
303
+ optionsList.innerHTML = "";
304
+ var loading = document.createElement("div");
305
+ loading.className =
306
+ "flex h-full min-h-[100px] w-full items-center justify-center p-4 !text-reg-13 text-typography-quaternary-text";
307
+ loading.textContent = "Loading options...";
308
+ optionsList.appendChild(loading);
309
+ }
310
+
311
+ function showError(message) {
312
+ optionsList.innerHTML = "";
313
+ var errorDiv = document.createElement("div");
314
+ errorDiv.className =
315
+ "flex h-full min-h-[100px] w-full items-center justify-center p-4 !text-reg-13 text-error-text";
316
+ errorDiv.textContent = message || "Error loading options";
317
+ optionsList.appendChild(errorDiv);
318
+ }
319
+
320
+ function getFilteredOptions() {
321
+ if (!searchQuery.trim()) return options;
322
+
323
+ var normalizedQuery = searchQuery.toLowerCase();
324
+ return options.filter(function (option) {
325
+ var matchesDisplayName = option.display_name
326
+ .toLowerCase()
327
+ .includes(normalizedQuery);
328
+ var matchesSlug = String(option.slug || "")
329
+ .toLowerCase()
330
+ .includes(normalizedQuery);
331
+ return matchesDisplayName || matchesSlug;
332
+ });
333
+ }
334
+
335
+ function renderOptions() {
336
+ optionsList.innerHTML = "";
337
+ var filteredOptions = getFilteredOptions();
338
+
339
+ // Update search visibility
340
+ var shouldShowSearch = showSearch !== undefined ? showSearch : options.length > 10;
341
+ searchContainer.className = shouldShowSearch
342
+ ? "p-8 border-b-1/2 border-border-primary "
343
+ : "p-8 hidden";
344
+
345
+ if (filteredOptions.length === 0) {
346
+ var noOpt = document.createElement("div");
347
+ noOpt.className =
348
+ "flex h-full min-h-[100px] w-full items-center justify-center p-4 !text-reg-13 text-typography-quaternary-text";
349
+ noOpt.textContent = searchQuery.trim()
350
+ ? "No options found"
351
+ : "No options available";
352
+ optionsList.appendChild(noOpt);
353
+ return;
354
+ }
355
+
356
+ filteredOptions.forEach(function (opt) {
357
+ var optionValue = opt.slug || opt.display_name;
358
+ var optionLabel = opt.display_name;
359
+ var isSelected = optionValue === value;
360
+ var isArchived = opt.is_active === 0 || opt.is_active === false;
361
+
362
+ var option = document.createElement("div");
363
+ option.setAttribute("role", "option");
364
+ option.setAttribute("data-value", optionValue);
365
+ option.setAttribute("aria-selected", isSelected);
366
+ option.className = join(
367
+ "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",
368
+ "hover:bg-fill-tertiary-fill-light-gray focus:bg-fill-tertiary-fill-light-gray",
369
+ isSelected
370
+ ? "bg-primary-surface hover:!bg-primary-surface-hover"
371
+ : ""
372
+ );
373
+
374
+ var optionContent = document.createElement("span");
375
+ optionContent.className = "flex items-center gap-8 flex-1 truncate";
376
+
377
+ var label = document.createElement("span");
378
+ label.textContent = optionLabel;
379
+
380
+ var badgeConfig = opt.badge_config;
381
+ if (badgeConfig && (badgeConfig.icon_color || badgeConfig.start_icon)) {
382
+ var IconComponent = getIcon();
383
+ if (IconComponent && typeof IconComponent.createIconOrColor === "function") {
384
+ var iconEl = IconComponent.createIconOrColor({
385
+ start_icon: badgeConfig.start_icon,
386
+ icon_color: badgeConfig.icon_color,
387
+ className: (badgeConfig.class_name || "") + " shrink-0",
388
+ });
389
+ if (iconEl) optionContent.appendChild(iconEl);
390
+ }
391
+ }
392
+
393
+ optionContent.appendChild(label);
394
+ option.appendChild(optionContent);
395
+
396
+ // Add archived badge (inline, after label)
397
+ if (isArchived) {
398
+ var archivedBadge = document.createElement("span");
399
+ archivedBadge.className =
400
+ "ml-8 px-6 py-2 text-xs rounded-2 bg-info-surface-hover text-info-text-base border-1/2 border-info-border-base";
401
+ archivedBadge.textContent = "Archived";
402
+ option.appendChild(archivedBadge);
403
+ }
404
+
405
+ option.addEventListener("click", function (e) {
406
+ e.stopPropagation();
407
+ if (disabled) return;
408
+ selectOption(optionValue, optionLabel);
409
+ });
410
+ option.addEventListener("mouseenter", function () {
411
+ if (disabled) return;
412
+ highlightOption(option);
413
+ });
414
+
415
+ optionsList.appendChild(option);
416
+ });
417
+ }
418
+
419
+
420
+ function openDropdown() {
421
+ if (disabled) return;
422
+ if (popover) {
423
+ // Close other open selects
424
+ document
425
+ .querySelectorAll(".enum-select, .custom-select, .record-select")
426
+ .forEach(function (other) {
427
+ if (other !== container && other.popoverInstance) {
428
+ other.popoverInstance.hide();
429
+ }
430
+ });
431
+ popover.show();
432
+ trigger.setAttribute("aria-expanded", "true");
433
+ highlightOptionByValue(value);
434
+ if (searchInput) {
435
+ setTimeout(function () {
436
+ searchInput.focus();
437
+ }, 50);
438
+ }
439
+ return;
440
+ }
441
+ // Fallback when Popover not available
442
+ document
443
+ .querySelectorAll(".enum-select.open")
444
+ .forEach(function (other) {
445
+ if (other !== container) {
446
+ other.classList.remove("open");
447
+ var t = other.querySelector("button");
448
+ if (t) t.setAttribute("aria-expanded", "false");
449
+ }
450
+ });
451
+ isOpen = true;
452
+ container.classList.add("open");
453
+ trigger.setAttribute("aria-expanded", "true");
454
+ highlightOptionByValue(value);
455
+ if (searchInput) {
456
+ setTimeout(function () {
457
+ searchInput.focus();
458
+ }, 50);
459
+ }
460
+ }
461
+
462
+ function closeDropdown() {
463
+ if (popover) {
464
+ popover.hide();
465
+ } else {
466
+ isOpen = false;
467
+ container.classList.remove("open");
468
+ }
469
+ trigger.setAttribute("aria-expanded", "false");
470
+ highlightedIndex = -1;
471
+ clearSearch();
472
+ renderOptions();
473
+ }
474
+
475
+ function toggleDropdown() {
476
+ var isVisible = popover
477
+ ? popover.element && popover.element.classList.contains("visible")
478
+ : isOpen;
479
+ if (isVisible) {
480
+ closeDropdown();
481
+ } else {
482
+ openDropdown();
483
+ }
484
+ }
485
+
486
+ function selectOption(optionValue, optionLabel) {
487
+ value = optionValue;
488
+ triggerText.textContent = optionLabel;
489
+ trigger.className = triggerClasses(
490
+ variant,
491
+ size,
492
+ disabled,
493
+ false,
494
+ canClear && !!value && !disabled
495
+ );
496
+
497
+ // Update chevron visibility for inline variant
498
+ var shouldShowChevron = variant !== "inline" || !value;
499
+ if (shouldShowChevron && !chevron.parentNode) {
500
+ trigger.appendChild(chevron);
501
+ } else if (!shouldShowChevron && chevron.parentNode) {
502
+ chevron.parentNode.removeChild(chevron);
503
+ }
504
+
505
+ updateClearButton();
506
+ renderOptions();
507
+ closeDropdown();
508
+ if (onChange) onChange(optionValue);
509
+ }
510
+
511
+ function clearSearch() {
512
+ searchQuery = "";
513
+ if (searchInputWrapper) searchInputWrapper.setValue("");
514
+ else if (searchInput) searchInput.value = "";
515
+ }
516
+
517
+ function highlightOption(el) {
518
+ optionsList.querySelectorAll("[role=option]").forEach(function (o) {
519
+ o.classList.remove("bg-fill-tertiary-fill-light-gray");
520
+ });
521
+ if (!el.classList.contains("bg-primary-surface")) {
522
+ el.classList.add("bg-fill-tertiary-fill-light-gray");
523
+ }
524
+ highlightedIndex = Array.from(optionsList.children).indexOf(el);
525
+ }
526
+
527
+ function highlightOptionByValue(val) {
528
+ var opt = optionsList.querySelector('[data-value="' + val + '"]');
529
+ if (opt) {
530
+ highlightOption(opt);
531
+ scrollToOption(opt);
532
+ }
533
+ }
534
+
535
+ function scrollToOption(opt) {
536
+ if (!opt) return;
537
+ var cr = optionsList.getBoundingClientRect();
538
+ var top = opt.offsetTop;
539
+ var bottom = top + opt.offsetHeight;
540
+ var st = optionsList.scrollTop;
541
+ var sb = st + cr.height;
542
+ if (top < st) optionsList.scrollTop = top - 8;
543
+ else if (bottom > sb) optionsList.scrollTop = bottom - cr.height + 8;
544
+ }
545
+
546
+ trigger.addEventListener("keydown", function (e) {
547
+ if (disabled) return;
548
+ var isVisible = popover && popover.element && popover.element.classList.contains("visible");
549
+ switch (e.key) {
550
+ case "Enter":
551
+ case " ":
552
+ e.preventDefault();
553
+ toggleDropdown();
554
+ break;
555
+ case "ArrowDown":
556
+ e.preventDefault();
557
+ if (!isVisible) openDropdown();
558
+ else navigateOptions(1);
559
+ break;
560
+ case "ArrowUp":
561
+ e.preventDefault();
562
+ if (!isVisible) openDropdown();
563
+ else navigateOptions(-1);
564
+ break;
565
+ case "Escape":
566
+ if (isVisible) {
567
+ e.preventDefault();
568
+ closeDropdown();
569
+ }
570
+ break;
571
+ }
572
+ });
573
+
574
+ function navigateOptions(dir) {
575
+ var list = Array.from(optionsList.children).filter(function (n) {
576
+ return n.getAttribute("role") === "option";
577
+ });
578
+ if (list.length === 0) return;
579
+ highlightedIndex += dir;
580
+ if (highlightedIndex < 0) highlightedIndex = list.length - 1;
581
+ else if (highlightedIndex >= list.length) highlightedIndex = 0;
582
+ var opt = list[highlightedIndex];
583
+ highlightOption(opt);
584
+ scrollToOption(opt);
585
+ }
586
+
587
+ // Initialize Popover
588
+ function initializePopover() {
589
+ if (!usePopover) {
590
+ return;
591
+ }
592
+ popover = global.Popover.create({
593
+ trigger: trigger,
594
+ content: content,
595
+ placement: "bottom",
596
+ align: "start",
597
+ closeOnClickOutside: true,
598
+ onClose: function () {
599
+ trigger.setAttribute("aria-expanded", "false");
600
+ chevron.style.transform = "";
601
+ highlightedIndex = -1;
602
+ clearSearch();
603
+ renderOptions();
604
+ },
605
+ onOpen: function () {
606
+ trigger.setAttribute("aria-expanded", "true");
607
+ chevron.style.transform = "rotate(180deg)";
608
+ },
609
+ bodyClassName: "p-0 overflow-hidden",
610
+ panelClassName: "min-w-[var(--trigger-width)] overflow-hidden",
611
+ });
612
+
613
+ // Store popover instance for cleanup
614
+ container.popoverInstance = popover;
615
+
616
+ // Set CSS variable for trigger width
617
+ var triggerWidth = trigger.offsetWidth;
618
+ if (popover.panel) {
619
+ popover.panel.style.setProperty("--trigger-width", triggerWidth + "px");
620
+ popover.panel.style.minWidth = triggerWidth + "px";
621
+ }
622
+ }
623
+
624
+ // Load options from SDK
625
+ function loadOptions() {
626
+ var client = getClient();
627
+ if (!client || (typeof client.isAvailable === "function" && !client.isAvailable())) {
628
+ error = "SuperLeap SDK not initialized";
629
+ showError(error);
630
+ if (onError) onError(error);
631
+ return;
632
+ }
633
+
634
+ showLoading();
635
+
636
+ var sdk = typeof client.getSdk === "function" ? client.getSdk() : null;
637
+ if (!sdk) {
638
+ error = "Failed to get SDK instance";
639
+ showError(error);
640
+ if (onError) onError(error);
641
+ return;
642
+ }
643
+
644
+ var model = sdk.model(objectSlug);
645
+
646
+ model
647
+ .getField(columnSlug)
648
+ .then(function (field) {
649
+ if (!field) {
650
+ throw new Error("Field '" + columnSlug + "' not found in object '" + objectSlug + "'");
651
+ }
652
+
653
+ columnData = field;
654
+
655
+ // Check if it's a select/stage field with enum_group
656
+ if (!field.enumGroup || !field.enumGroup.values) {
657
+ throw new Error("Field '" + columnSlug + "' does not have enum options");
658
+ }
659
+
660
+ var enumGroup = field.enumGroup;
661
+ var allOptions = enumGroup.values || [];
662
+
663
+ // Handle dependent fields
664
+ if (columnData.is_dependent_field && enumGroup.properties && enumGroup.properties.parent_column_slug) {
665
+ var parentSlug = enumGroup.properties.parent_column_slug;
666
+ var parentValue = currentRecordData[parentSlug];
667
+
668
+ if (!parentValue) {
669
+ options = [];
670
+ placeholder = "Select " + parentSlug + " first";
671
+ renderOptions();
672
+ isLoading = false;
673
+ return;
674
+ }
675
+
676
+ // Filter options based on dependent_enum_slugs
677
+ options = allOptions.filter(function (option) {
678
+ if (!option.dependent_enum_slugs) return false;
679
+ if (Array.isArray(parentValue)) {
680
+ return parentValue.some(function (val) {
681
+ return option.dependent_enum_slugs.includes(val);
682
+ });
683
+ }
684
+ return option.dependent_enum_slugs.includes(parentValue);
685
+ });
686
+ } else {
687
+ // Filter active options (or include archived if it's the selected value)
688
+ options = allOptions.filter(function (option) {
689
+ return option.is_active === 1 || option.is_active === true || option.slug === value;
690
+ });
691
+ }
692
+
693
+ // Sort by rank
694
+ options.sort(function (a, b) {
695
+ var rankA = a.rank !== undefined && a.rank !== null ? a.rank : 999999;
696
+ var rankB = b.rank !== undefined && b.rank !== null ? b.rank : 999999;
697
+ return rankA - rankB;
698
+ });
699
+
700
+ isLoading = false;
701
+ renderOptions();
702
+
703
+ // Update trigger text if value is set
704
+ if (value) {
705
+ var selectedOption = options.find(function (opt) {
706
+ return opt.slug === value;
707
+ });
708
+ if (selectedOption) {
709
+ triggerText.textContent = selectedOption.display_name;
710
+ trigger.className = triggerClasses(
711
+ variant,
712
+ size,
713
+ disabled,
714
+ false,
715
+ canClear && !!value && !disabled
716
+ );
717
+
718
+ // Update chevron visibility for inline variant
719
+ var shouldShowChevron = variant !== "inline" || !value;
720
+ if (shouldShowChevron && !chevron.parentNode) {
721
+ trigger.appendChild(chevron);
722
+ } else if (!shouldShowChevron && chevron.parentNode) {
723
+ chevron.parentNode.removeChild(chevron);
724
+ }
725
+
726
+ updateClearButton();
727
+ }
728
+ }
729
+
730
+ if (onLoad) onLoad(options);
731
+ })
732
+ .catch(function (err) {
733
+ error = err.message || "Failed to load options";
734
+ console.error("EnumSelect error:", error);
735
+ showError(error);
736
+ isLoading = false;
737
+ if (onError) onError(error);
738
+ });
739
+ }
740
+
741
+ // Initialize
742
+ initializePopover();
743
+ loadOptions();
744
+
745
+ if (!usePopover) {
746
+ trigger.addEventListener("click", function (e) {
747
+ e.preventDefault();
748
+ e.stopPropagation();
749
+ if (disabled) return;
750
+ toggleDropdown();
751
+ });
752
+ document.addEventListener("click", function (e) {
753
+ if (isOpen && !container.contains(e.target)) closeDropdown();
754
+ });
755
+ document.addEventListener("keydown", function (e) {
756
+ if (e.key === "Escape" && isOpen) closeDropdown();
757
+ });
758
+ }
759
+
760
+ // Public API
761
+ container.updateValue = function (newValue) {
762
+ value = newValue !== undefined && newValue !== null ? newValue : "";
763
+ var sel = options.find(function (opt) {
764
+ return opt.slug === value;
765
+ });
766
+ triggerText.textContent = sel ? sel.display_name : placeholder;
767
+ trigger.className = triggerClasses(
768
+ variant,
769
+ size,
770
+ disabled,
771
+ !value,
772
+ canClear && !!value && !disabled
773
+ );
774
+
775
+ // Update chevron visibility for inline variant
776
+ var shouldShowChevron = variant !== "inline" || !value;
777
+ if (shouldShowChevron && !chevron.parentNode) {
778
+ trigger.appendChild(chevron);
779
+ } else if (!shouldShowChevron && chevron.parentNode) {
780
+ chevron.parentNode.removeChild(chevron);
781
+ }
782
+
783
+ updateClearButton();
784
+ renderOptions();
785
+ };
786
+
787
+ container.updateRecordData = function (newRecordData) {
788
+ currentRecordData = newRecordData || {};
789
+ loadOptions();
790
+ };
791
+
792
+ container.setDisabled = function (isDisabled) {
793
+ disabled = !!isDisabled;
794
+ trigger.disabled = disabled;
795
+ trigger.className = triggerClasses(
796
+ variant,
797
+ size,
798
+ disabled,
799
+ !value,
800
+ canClear && !!value && !disabled
801
+ );
802
+ updateClearButton();
803
+ var isVisible = popover && popover.element && popover.element.classList.contains("visible");
804
+ if (disabled && (isVisible || isOpen)) closeDropdown();
805
+ };
806
+
807
+ container.reload = function () {
808
+ loadOptions();
809
+ };
810
+
811
+ container.getOptions = function () {
812
+ return options;
813
+ };
814
+
815
+ container.getColumnData = function () {
816
+ return columnData;
817
+ };
818
+
819
+ container.destroy = function () {
820
+ if (popover) {
821
+ popover.destroy();
822
+ }
823
+ };
824
+
825
+ return container;
826
+ }
827
+
828
+ global.EnumSelect = {
829
+ create: createEnumSelect,
830
+ };
831
+ })(typeof window !== "undefined" ? window : this);