@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.
- package/CHANGELOG.md +65 -0
- package/LICENSE +21 -0
- package/README.md +451 -0
- package/components/alert.js +282 -0
- package/components/avatar.js +195 -0
- package/components/badge.js +135 -0
- package/components/button.js +201 -0
- package/components/checkbox.js +254 -0
- package/components/currency.js +227 -0
- package/components/date-time-picker/date-time-picker-utils.js +253 -0
- package/components/date-time-picker/date-time-picker.js +532 -0
- package/components/duration/duration-constants.js +46 -0
- package/components/duration/duration-utils.js +164 -0
- package/components/duration/duration.js +448 -0
- package/components/enum-multiselect.js +869 -0
- package/components/enum-select.js +831 -0
- package/components/enumeration.js +213 -0
- package/components/file-input.js +533 -0
- package/components/icon.js +200 -0
- package/components/input.js +259 -0
- package/components/label.js +111 -0
- package/components/multiselect.js +351 -0
- package/components/phone-input/phone-input.js +392 -0
- package/components/phone-input/phone-utils.js +157 -0
- package/components/popover.js +240 -0
- package/components/radio-group.js +435 -0
- package/components/record-multiselect.js +956 -0
- package/components/record-select.js +930 -0
- package/components/select.js +544 -0
- package/components/spinner.js +136 -0
- package/components/table.js +335 -0
- package/components/textarea.js +114 -0
- package/components/time-picker.js +357 -0
- package/components/toast.js +343 -0
- package/core/flow.js +1729 -0
- package/core/superleapClient.js +146 -0
- package/dist/output.css +2 -0
- package/index.d.ts +458 -0
- package/index.js +253 -0
- 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);
|