@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,956 @@
1
+ /**
2
+ * Record MultiSelect Component (superleap-flow)
3
+ * Multi-record select with search; same trigger/content styling as MultiSelect.
4
+ * Uses: Popover (dropdown), InputComponent (search), Spinner (loading), Avatar (vivid for user), Icon (object icons).
5
+ * Fetches records via superleapClient.getSdk().model(objectSlug).
6
+ * For objectSlug === 'user' shows Vivid Avatar; otherwise static icon from schema or object map (database fallback).
7
+ * Does not use icon from backend payload.
8
+ */
9
+
10
+ (function (global) {
11
+ "use strict";
12
+
13
+ var Popover = global.Popover;
14
+ var InputComponent = global.InputComponent;
15
+ var Spinner = global.Spinner;
16
+
17
+ /** When objectSlug === user, show Vivid Avatar (name-based color) instead of static icon */
18
+ var STANDARD_OBJECT_SLUGS_USERS = "user";
19
+
20
+ /** Object slug -> Icon component iconStr + color (Tabler icon names). Used when objectSchema.properties.icon_data not provided. */
21
+ var OBJECT_SLUG_TO_ICON = {
22
+ team: { iconStr: "IconUsers", color: "primary" },
23
+ role: { iconStr: "IconShield", color: "info" },
24
+ iframe: { iconStr: "IconLayout", color: "neutral" },
25
+ lead: { iconStr: "IconUser", color: "primary" },
26
+ opportunity: { iconStr: "IconCurrencyDollar", color: "success" },
27
+ call_log: { iconStr: "IconPhone", color: "info" },
28
+ communication: { iconStr: "IconMessage", color: "primary" },
29
+ history_field: { iconStr: "IconClock", color: "neutral" },
30
+ email: { iconStr: "IconMail", color: "info" },
31
+ };
32
+
33
+ var CHEVRON_SVG =
34
+ '<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>';
35
+
36
+ var CHECK_SVG =
37
+ '<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>';
38
+
39
+ var SEARCH_ICON =
40
+ '<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>';
41
+
42
+ function triggerClasses(variant, size, disabled, isEmpty) {
43
+ var v = variant || "default";
44
+ var base =
45
+ "w-full items-center justify-between rounded-4 border-1/2 bg-fill-quarternary-fill-white !text-reg-13 focus:outline-none flex h-full truncate hover:cursor-pointer ";
46
+ var variantClasses = {
47
+ default:
48
+ "border-border-primary hover:border-primary-border focus:border-1/2 focus:border-primary-border",
49
+ error:
50
+ "border-error-border hover:border-error-border-hover focus:border-1/2 focus:border-error-border-hover",
51
+ warning:
52
+ "border-warning-border hover:border-warning-border-hover focus:border-1/2 focus:border-warning-border-hover",
53
+ borderless: "border-none shadow-none rounded-0 bg-fill-quarternary-fill-white",
54
+ inline:
55
+ "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",
56
+ };
57
+ var sizeClasses = {
58
+ default: "px-12 py-6",
59
+ large: "px-12 py-8",
60
+ small: "px-8 py-4",
61
+ };
62
+ var textColorClass = isEmpty ? " text-typography-quaternary-text" : " text-typography-primary-text";
63
+ var disabledClass = disabled
64
+ ? " pointer-events-none cursor-not-allowed bg-fill-tertiary-fill-light-gray text-typography-quaternary-text hover:border-border-primary"
65
+ : "";
66
+ return (
67
+ base +
68
+ (variantClasses[v] || variantClasses.default) +
69
+ " " +
70
+ (sizeClasses[size] || sizeClasses.default) +
71
+ textColorClass +
72
+ disabledClass
73
+ );
74
+ }
75
+
76
+ function join() {
77
+ return Array.prototype.filter.call(arguments, Boolean).join(" ");
78
+ }
79
+
80
+ /** Resolve client: use FlowUI._getComponent when bundle has captured globals, else global.superleapClient */
81
+ function getClient() {
82
+ if (global.FlowUI && typeof global.FlowUI._getComponent === "function") {
83
+ var c = global.FlowUI._getComponent("superleapClient");
84
+ if (c) return c;
85
+ }
86
+ return global.superleapClient;
87
+ }
88
+
89
+ /** Resolve Avatar component for Vivid Avatar when objectSlug === user */
90
+ function getAvatar() {
91
+ if (global.FlowUI && typeof global.FlowUI._getComponent === "function") {
92
+ var c = global.FlowUI._getComponent("Avatar");
93
+ if (c) return c;
94
+ }
95
+ return global.Avatar;
96
+ }
97
+
98
+ /** Resolve Icon component for object/record icons */
99
+ function getIcon() {
100
+ if (global.FlowUI && typeof global.FlowUI._getComponent === "function") {
101
+ var c = global.FlowUI._getComponent("Icon");
102
+ if (c) return c;
103
+ }
104
+ return global.Icon;
105
+ }
106
+
107
+ /**
108
+ * Get iconStr and color for the object: from objectSchema.properties.icon_data first, else OBJECT_SLUG_TO_ICON, else IconDatabase fallback.
109
+ * @param {string} slug - objectSlug
110
+ * @param {{ properties?: { icon_data?: { icon?: string, color?: string } } } | null} objectSchema - optional schema (ObjectType)
111
+ * @returns {{ iconStr: string, color: string }}
112
+ */
113
+ function getObjectIconInfo(slug, objectSchema) {
114
+ var color = "neutral";
115
+ var iconStr = "IconDatabase";
116
+ if (objectSchema && objectSchema.properties && objectSchema.properties.icon_data) {
117
+ var iconData = objectSchema.properties.icon_data;
118
+ if (typeof iconData.color === "string" && iconData.color) color = iconData.color;
119
+ if (typeof iconData.icon === "string" && iconData.icon) iconStr = iconData.icon;
120
+ }
121
+ if (!objectSchema || !objectSchema.properties || !objectSchema.properties.icon_data || !objectSchema.properties.icon_data.icon) {
122
+ if (OBJECT_SLUG_TO_ICON[slug]) {
123
+ iconStr = OBJECT_SLUG_TO_ICON[slug].iconStr;
124
+ if (OBJECT_SLUG_TO_ICON[slug].color) color = OBJECT_SLUG_TO_ICON[slug].color;
125
+ } else {
126
+ iconStr = "IconDatabase";
127
+ color = "neutral";
128
+ }
129
+ }
130
+ return { iconStr: iconStr, color: color || "neutral" };
131
+ }
132
+
133
+ /**
134
+ * Create a record multiselect component
135
+ * @param {Object} config
136
+ * @param {string} config.fieldId - Field ID for state management
137
+ * @param {string} config.objectSlug - Object slug (e.g. "account", "opportunity")
138
+ * @param {string} [config.placeholder] - Placeholder text
139
+ * @param {string} [config.searchPlaceholder] - Search input placeholder
140
+ * @param {string} [config.label] - Label for summary when items selected (e.g. "selected" -> "3 selected")
141
+ * @param {Array} config.value - Current selected values (array of record ids)
142
+ * @param {Function} config.onValuesChange - Change handler (values: string[], records?: Array) => void
143
+ * @param {boolean} [config.disabled] - Whether select is disabled
144
+ * @param {string} [config.variant] - 'default' | 'error' | 'warning' | 'borderless' | 'inline'
145
+ * @param {string} [config.size] - 'default' | 'large' | 'small'
146
+ * @param {number} [config.initialLimit] - Initial fetch limit (default 50)
147
+ * @param {Array<string>} [config.displayFields] - Fields to display as secondary info (e.g. ["email", "phone"])
148
+ * @param {Object} [config.objectSchema] - Optional object type/schema; properties.icon_data { icon?, color? } used for static icon (not used for user; user shows Vivid Avatar)
149
+ * @returns {HTMLElement} Record multiselect container element
150
+ */
151
+ function createRecordMultiSelect(config) {
152
+ var fieldId = config.fieldId;
153
+ var objectSlug = config.objectSlug;
154
+ var objectSchema = config.objectSchema || null;
155
+ var placeholder = config.placeholder || "Select records";
156
+ var searchPlaceholder = config.searchPlaceholder || "Search " + (objectSlug || "") + "...";
157
+ var summaryLabel = config.label || "selected";
158
+ var onValuesChange = config.onValuesChange;
159
+ var variant = config.variant || "default";
160
+ var size = config.size || "default";
161
+ var initialLimit = config.initialLimit != null ? config.initialLimit : 50;
162
+ var displayFields = config.displayFields || [];
163
+
164
+ var disabled = config.disabled === true;
165
+ var values = Array.isArray(config.value)
166
+ ? config.value.slice()
167
+ : Array.isArray(config.values)
168
+ ? config.values.slice()
169
+ : [];
170
+
171
+ if (!objectSlug) {
172
+ var errEl = document.createElement("div");
173
+ errEl.className = "text-reg-13 text-typography-quaternary-text";
174
+ errEl.textContent = "Record multiselect: objectSlug is required.";
175
+ return errEl;
176
+ }
177
+
178
+ var container = document.createElement("div");
179
+ container.className = "record-multiselect relative w-full group";
180
+ container.setAttribute("data-field-id", fieldId);
181
+ container.setAttribute("data-object-slug", objectSlug);
182
+
183
+ var selectedRecords = [];
184
+ var allRecords = [];
185
+ var filteredRecords = [];
186
+ var isOpen = false;
187
+ var searchTerm = "";
188
+ var searchDebounceTimer = null;
189
+ var usePopover = Popover && typeof Popover.create === "function";
190
+ var popover = null;
191
+ var hasMoreRecords = true;
192
+ var currentPage = 1;
193
+ var isFetchingMore = false;
194
+ var totalFetched = 0;
195
+ var contentBase = "record-multiselect-content min-w-[8rem] ";
196
+ if (!usePopover) {
197
+ contentBase += "absolute left-0 right-0 z-50 max-h-[30vh] overflow-hidden rounded-4 bg-fill-quarternary-fill-white shadow-default-medium opacity-0 invisible transition-all duration-150 ease-out group-[.open]:opacity-100 group-[.open]:visible ";
198
+ }
199
+
200
+ // Trigger wrapper + button
201
+ var triggerWrapper = document.createElement("span");
202
+ triggerWrapper.className =
203
+ "multiselect-trigger-wrapper relative flex w-full items-center justify-between gap-8";
204
+
205
+ var trigger = document.createElement("button");
206
+ trigger.type = "button";
207
+ trigger.className = triggerClasses(variant, size, disabled, values.length === 0);
208
+ trigger.disabled = disabled;
209
+ trigger.setAttribute("aria-haspopup", "listbox");
210
+ trigger.setAttribute("aria-expanded", "false");
211
+ trigger.setAttribute("aria-multiselectable", "true");
212
+ trigger.setAttribute("aria-label", placeholder);
213
+ trigger.classList.add("record-multiselect-trigger");
214
+
215
+ var triggerContent = document.createElement("div");
216
+ triggerContent.className = "record-multiselect-trigger-content flex items-center gap-8 flex-1 min-w-0";
217
+
218
+ var triggerIcon = document.createElement("span");
219
+ triggerIcon.className = "record-multiselect-trigger-icon shrink-0 hidden";
220
+ triggerContent.appendChild(triggerIcon);
221
+
222
+ var triggerText = document.createElement("div");
223
+ triggerText.className = "truncate text-inherit record-multiselect-value";
224
+ triggerContent.appendChild(triggerText);
225
+
226
+ function renderStaticObjectIcon() {
227
+ var IconComponent = getIcon();
228
+ if (!IconComponent || typeof IconComponent.create !== "function") return;
229
+ var info = getObjectIconInfo(objectSlug, objectSchema);
230
+ var el = IconComponent.create({
231
+ iconStr: info.iconStr,
232
+ color: info.color,
233
+ fallbackIconStr: "IconDatabase",
234
+ defaultIcon: true,
235
+ className: "size-20 shrink-0",
236
+ });
237
+ if (el) triggerIcon.appendChild(el);
238
+ }
239
+
240
+ function renderStaticIconPlaceholder(label) {
241
+ var initial = (label && String(label).charAt(0).toUpperCase()) || "?";
242
+ var div = document.createElement("div");
243
+ div.className =
244
+ "size-20 rounded-full bg-primary-surface text-primary-text flex items-center justify-center text-reg-12 font-semibold";
245
+ div.textContent = initial;
246
+ triggerIcon.appendChild(div);
247
+ }
248
+
249
+ function renderTriggerContent() {
250
+ triggerText.textContent = values.length === 0 ? placeholder : values.length + " " + summaryLabel;
251
+ triggerIcon.innerHTML = "";
252
+ if (values.length === 0) {
253
+ triggerIcon.className = "record-multiselect-trigger-icon shrink-0 hidden";
254
+ } else {
255
+ triggerIcon.className = "record-multiselect-trigger-icon shrink-0 flex items-center justify-center size-20 rounded-4 overflow-hidden";
256
+ if (objectSlug === STANDARD_OBJECT_SLUGS_USERS) {
257
+ var Avatar = getAvatar();
258
+ if (Avatar && typeof Avatar.createVivid === "function") {
259
+ var firstRecord = selectedRecords.length > 0 ? selectedRecords[0] : null;
260
+ var label = firstRecord && (firstRecord.name || firstRecord.label);
261
+ var vividEl = Avatar.createVivid({
262
+ name: label || "?",
263
+ size: "small",
264
+ shape: "circle",
265
+ });
266
+ triggerIcon.appendChild(vividEl);
267
+ } else {
268
+ renderStaticIconPlaceholder(selectedRecords.length > 0 && (selectedRecords[0].name || selectedRecords[0].label));
269
+ }
270
+ } else {
271
+ renderStaticObjectIcon();
272
+ }
273
+ }
274
+ }
275
+ renderTriggerContent();
276
+
277
+ trigger.appendChild(triggerContent);
278
+
279
+ var chevron = document.createElement("span");
280
+ chevron.className =
281
+ "ml-4 box-content flex size-16 items-center justify-center shrink-0 transition-transform duration-200 group-[.open]:rotate-180";
282
+ chevron.innerHTML = CHEVRON_SVG;
283
+ chevron.setAttribute("aria-hidden", "true");
284
+ trigger.appendChild(chevron);
285
+
286
+ triggerWrapper.appendChild(trigger);
287
+ container.appendChild(triggerWrapper);
288
+
289
+ // Dropdown content: search + list
290
+ var content = document.createElement("div");
291
+ content.setAttribute("role", "listbox");
292
+ content.setAttribute("aria-multiselectable", "true");
293
+ content.className = contentBase + (usePopover ? "max-h-[30vh] overflow-hidden flex flex-col" : "top-full mt-1 -translate-y-1 group-[.open]:translate-y-0");
294
+
295
+ var searchWrap = document.createElement("div");
296
+ searchWrap.className = "p-8 pb-4 border-b-1/2 border-border-primary ";
297
+ var searchInputEl;
298
+ var searchInputWrapper = null;
299
+ if (InputComponent && typeof InputComponent.create === "function") {
300
+ searchInputWrapper = InputComponent.create({
301
+ variant: "borderless",
302
+ inputSize: "small",
303
+ type: "text",
304
+ placeholder: searchPlaceholder,
305
+ value: "",
306
+ startIcon: SEARCH_ICON,
307
+ className: "!border-0 !p-0",
308
+ onInput: function () {
309
+ searchTerm = searchInputWrapper.getValue().trim();
310
+ if (!searchTerm) {
311
+ loadInitialAndRender();
312
+ return;
313
+ }
314
+ debouncedSearch();
315
+ },
316
+ });
317
+ var inputEl = searchInputWrapper.getInput();
318
+ if (inputEl) inputEl.setAttribute("aria-label", "Search records");
319
+ searchInputEl = inputEl;
320
+ searchWrap.appendChild(searchInputWrapper);
321
+ } else {
322
+ var fallbackWrapper = document.createElement("div");
323
+ fallbackWrapper.className = "flex items-center gap-8";
324
+
325
+ var searchIconSpan = document.createElement("span");
326
+ searchIconSpan.className = "shrink-0 text-typography-tertiary-text";
327
+ searchIconSpan.innerHTML = SEARCH_ICON;
328
+ fallbackWrapper.appendChild(searchIconSpan);
329
+
330
+ var searchInput = document.createElement("input");
331
+ searchInput.type = "text";
332
+ searchInput.className =
333
+ "w-full bg-transparent text-reg-13 text-typography-primary-text placeholder:text-typography-quaternary-text focus:outline-none border-none";
334
+ searchInput.placeholder = searchPlaceholder;
335
+ searchInput.setAttribute("aria-label", "Search records");
336
+ searchInputEl = searchInput;
337
+ fallbackWrapper.appendChild(searchInput);
338
+ searchWrap.appendChild(fallbackWrapper);
339
+ }
340
+ content.appendChild(searchWrap);
341
+
342
+ var optionsList = document.createElement("div");
343
+ optionsList.className = "overflow-y-auto max-h-[200px] p-2 w-full rounded-4 bg-fill-quarternary-fill-white record-multiselect-options";
344
+
345
+ // Add scroll listener for infinite scroll
346
+ optionsList.addEventListener("scroll", function () {
347
+ if (isFetchingMore || !hasMoreRecords) return;
348
+ var scrollHeight = optionsList.scrollHeight;
349
+ var scrollTop = optionsList.scrollTop;
350
+ var clientHeight = optionsList.clientHeight;
351
+
352
+ // Trigger load more when scrolled to bottom (with 50px threshold)
353
+ if (scrollTop + clientHeight >= scrollHeight - 50) {
354
+ loadMoreRecords();
355
+ }
356
+ });
357
+
358
+ content.appendChild(optionsList);
359
+
360
+ if (!usePopover) {
361
+ container.appendChild(content);
362
+ }
363
+
364
+ if (usePopover) {
365
+ popover = Popover.create({
366
+ trigger: trigger,
367
+ content: content,
368
+ placement: "bottom",
369
+ align: "start",
370
+ closeOnClickOutside: true,
371
+ bodyClassName: "p-0 overflow-hidden",
372
+ panelClassName: "max-h-[30vh] overflow-hidden",
373
+ onOpen: function () {
374
+ if (disabled) {
375
+ popover.hide();
376
+ return;
377
+ }
378
+ document.querySelectorAll(".custom-select.open, .record-select.open, .custom-multiselect.open, .record-multiselect.open").forEach(function (other) {
379
+ if (other !== container) {
380
+ other.classList.remove("open");
381
+ var t = other.querySelector("button, .custom-select-trigger, .record-select-trigger, .multiselect-trigger-wrapper button, .record-multiselect-trigger");
382
+ if (t) t.setAttribute("aria-expanded", "false");
383
+ }
384
+ });
385
+ isOpen = true;
386
+ container.classList.add("open");
387
+ trigger.setAttribute("aria-expanded", "true");
388
+ searchTerm = "";
389
+ if (searchInputWrapper) searchInputWrapper.setValue("");
390
+ else if (searchInputEl) searchInputEl.value = "";
391
+ content.style.minWidth = trigger.offsetWidth + "px";
392
+ loadInitialAndRender();
393
+ setTimeout(function () {
394
+ if (searchInputEl) searchInputEl.focus();
395
+ }, 0);
396
+ },
397
+ onClose: function () {
398
+ isOpen = false;
399
+ container.classList.remove("open");
400
+ trigger.setAttribute("aria-expanded", "false");
401
+ searchTerm = "";
402
+ if (searchInputWrapper) searchInputWrapper.setValue("");
403
+ else if (searchInputEl) searchInputEl.value = "";
404
+ if (searchDebounceTimer) {
405
+ clearTimeout(searchDebounceTimer);
406
+ searchDebounceTimer = null;
407
+ }
408
+ },
409
+ });
410
+ }
411
+
412
+ function isSelected(recordId) {
413
+ return values.some(function (v) {
414
+ return v === recordId || String(v) === String(recordId);
415
+ });
416
+ }
417
+
418
+ function toggleValue(recordId) {
419
+ var idx = values.findIndex(function (v) {
420
+ return v === recordId || String(v) === String(recordId);
421
+ });
422
+ if (idx >= 0) {
423
+ values.splice(idx, 1);
424
+ selectedRecords = selectedRecords.filter(function (r) {
425
+ return (r.id || r.value) !== recordId;
426
+ });
427
+ } else {
428
+ values.push(recordId);
429
+ var rec = allRecords.find(function (r) {
430
+ return (r.id || r.value) === recordId;
431
+ });
432
+ if (rec) selectedRecords.push(rec);
433
+ }
434
+ renderTriggerContent();
435
+ trigger.className = triggerClasses(variant, size, disabled, values.length === 0);
436
+ syncOptionStates();
437
+ if (onValuesChange) onValuesChange(values.slice(), selectedRecords.slice());
438
+ }
439
+
440
+ function syncOptionStates() {
441
+ optionsList.querySelectorAll("[role=option]").forEach(function (optEl) {
442
+ var dv = optEl.getAttribute("data-value");
443
+ var selected = values.some(function (v) {
444
+ return v === dv || String(v) === dv || (dv === "false" && v === false) || (dv === "true" && v === true);
445
+ });
446
+ optEl.setAttribute("aria-selected", selected);
447
+ var checkEl = optEl.querySelector(".multiselect-option-check");
448
+ if (checkEl) checkEl.style.visibility = selected ? "visible" : "hidden";
449
+ optEl.classList.toggle("bg-primary-surface", selected);
450
+ optEl.classList.toggle("hover:!bg-primary-surface-hover", selected);
451
+ optEl.classList.toggle("hover:bg-fill-tertiary-fill-light-gray", !selected);
452
+ });
453
+ }
454
+
455
+ function fetchRecords(search, limit, page) {
456
+ var client = getClient();
457
+ if (!client || typeof client.getSdk !== "function") {
458
+ return Promise.resolve({ records: [], hasMore: false });
459
+ }
460
+ var sdk = client.getSdk();
461
+ if (!sdk) return Promise.resolve({ records: [], hasMore: false });
462
+ var model = sdk.model(objectSlug);
463
+ var fields = ["id", "name"].concat(displayFields || []);
464
+ var actualLimit = limit || initialLimit;
465
+ var offset = page ? (page - 1) * actualLimit : 0;
466
+
467
+ try {
468
+ if (model && typeof model.select === "function") {
469
+ var q = model.select.apply(model, fields);
470
+ if (search && search.trim()) {
471
+ q = q.filterBy({
472
+ or: [
473
+ { field: "name", operator: "contains", value: search.trim() },
474
+ { field: "id", operator: "eq", value: search.trim() },
475
+ ],
476
+ });
477
+ }
478
+ var orderBy = ["name"];
479
+ if (objectSlug === "account") orderBy.push("-ParentId");
480
+ return q
481
+ .limit(actualLimit)
482
+ .offset(offset)
483
+ .orderBy.apply(q, orderBy)
484
+ .cache({ ttl: 2 * 60 * 1000 })
485
+ .then(function (records) {
486
+ var mappedRecords = records.map(function (r) {
487
+ var d = r.toJSON ? r.toJSON() : r;
488
+ var iconUrl = d.icon || d.logo || d.icon_url || d.Icon__c || d.Logo__c || null;
489
+ var displayLabels = displayFields ? displayFields.map(function (field) {
490
+ return d[field] || "";
491
+ }) : [];
492
+ return {
493
+ id: d.id,
494
+ value: d.id,
495
+ name: d.name || d.id,
496
+ label: d.name || d.id,
497
+ icon: iconUrl,
498
+ displayLabels: displayLabels,
499
+ };
500
+ });
501
+ var hasMore = mappedRecords.length >= actualLimit;
502
+ return { records: mappedRecords, hasMore: hasMore };
503
+ });
504
+ }
505
+ } catch (e) {
506
+ console.error("[RecordMultiSelect] fetchRecords error:", e);
507
+ }
508
+ return Promise.resolve({ records: [], hasMore: false });
509
+ }
510
+
511
+ function loadSelectedRecords() {
512
+ if (!values || values.length === 0) {
513
+ renderTriggerContent();
514
+ return;
515
+ }
516
+ var client = getClient();
517
+ if (!client || typeof client.getSdk !== "function") {
518
+ renderTriggerContent();
519
+ return;
520
+ }
521
+ var sdk = client.getSdk();
522
+ if (!sdk) {
523
+ renderTriggerContent();
524
+ return;
525
+ }
526
+ var model = sdk.model(objectSlug);
527
+ var fields = ["id", "name"].concat(displayFields || []);
528
+ model
529
+ .select.apply(model, fields)
530
+ .filterBy({
531
+ and: [
532
+ { field: "id", operator: "in", value: values },
533
+ ],
534
+ })
535
+ .limit(values.length)
536
+ .then(function (records) {
537
+ selectedRecords = records.map(function (r) {
538
+ var d = r.toJSON ? r.toJSON() : r;
539
+ var iconUrl = d.icon || d.logo || d.icon_url || d.Icon__c || d.Logo__c || null;
540
+ var displayLabels = displayFields ? displayFields.map(function (field) {
541
+ return d[field] || "";
542
+ }) : [];
543
+ return {
544
+ id: d.id,
545
+ value: d.id,
546
+ name: d.name || d.id,
547
+ label: d.name || d.id,
548
+ icon: iconUrl,
549
+ displayLabels: displayLabels,
550
+ };
551
+ });
552
+ renderTriggerContent();
553
+ })
554
+ .catch(function () {
555
+ selectedRecords = values.map(function (v) {
556
+ return { id: v, value: v, name: v, label: v, icon: null, displayLabels: [] };
557
+ });
558
+ renderTriggerContent();
559
+ });
560
+ }
561
+
562
+ function showLoading() {
563
+ optionsList.innerHTML = "";
564
+ var loadWrap = document.createElement("div");
565
+ loadWrap.className =
566
+ "flex flex-row items-center justify-center gap-8 py-12 px-12 w-full text-reg-12 text-typography-quaternary-text record-multiselect-loading";
567
+ if (Spinner && typeof Spinner.create === "function") {
568
+ loadWrap.appendChild(Spinner.create({ size: "small", text: "Loading..." }));
569
+ } else {
570
+ var loadText = document.createElement("span");
571
+ loadText.textContent = "Loading...";
572
+ loadWrap.appendChild(loadText);
573
+ }
574
+ optionsList.appendChild(loadWrap);
575
+ }
576
+
577
+ function showLoadingMore() {
578
+ var existing = optionsList.querySelector(".record-multiselect-loading-more");
579
+ if (existing) existing.remove();
580
+
581
+ var loadWrap = document.createElement("div");
582
+ loadWrap.className =
583
+ "flex flex-row items-center justify-center gap-8 py-8 px-12 w-full text-reg-12 text-typography-quaternary-text record-multiselect-loading-more";
584
+ if (Spinner && typeof Spinner.create === "function") {
585
+ loadWrap.appendChild(Spinner.create({ size: "small" }));
586
+ } else {
587
+ var loadText = document.createElement("span");
588
+ loadText.textContent = "Loading more...";
589
+ loadWrap.appendChild(loadText);
590
+ }
591
+ optionsList.appendChild(loadWrap);
592
+ }
593
+
594
+ function removeLoadingMore() {
595
+ var loadingMore = optionsList.querySelector(".record-multiselect-loading-more");
596
+ if (loadingMore) loadingMore.remove();
597
+ }
598
+
599
+ function showEmpty(message) {
600
+ optionsList.innerHTML = "";
601
+ var empty = document.createElement("div");
602
+ empty.className =
603
+ "w-full justify-center px-12 py-6 text-center text-reg-12 text-typography-quaternary-text record-multiselect-empty";
604
+ empty.textContent = message || "No records found";
605
+ optionsList.appendChild(empty);
606
+ }
607
+
608
+ function renderOptions() {
609
+ var existingOptions = optionsList.querySelectorAll("[role=option]");
610
+ existingOptions.forEach(function (opt) {
611
+ opt.remove();
612
+ });
613
+
614
+ var oldStates = optionsList.querySelectorAll(".record-multiselect-loading, .record-multiselect-empty");
615
+ oldStates.forEach(function (el) {
616
+ el.remove();
617
+ });
618
+
619
+ filteredRecords.forEach(function (rec) {
620
+ var recordId = rec.id || rec.value;
621
+ var recordLabel = rec.name || rec.label || rec.value;
622
+ var selected = isSelected(recordId);
623
+
624
+ var option = document.createElement("div");
625
+ option.setAttribute("role", "option");
626
+ option.setAttribute("data-value", recordId);
627
+ option.setAttribute("aria-selected", selected);
628
+ option.className = join(
629
+ "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",
630
+ "hover:bg-fill-tertiary-fill-light-gray focus:bg-fill-tertiary-fill-light-gray",
631
+ selected ? "bg-primary-surface hover:!bg-primary-surface-hover" : ""
632
+ );
633
+
634
+ var optContent = document.createElement("span");
635
+ optContent.className = "flex flex-col flex-1 truncate min-w-0";
636
+
637
+ var primaryRow = document.createElement("div");
638
+ primaryRow.className = "flex items-center gap-8 truncate";
639
+
640
+ if (objectSlug === STANDARD_OBJECT_SLUGS_USERS) {
641
+ var Avatar = getAvatar();
642
+ if (Avatar && typeof Avatar.createVivid === "function") {
643
+ var avatarEl = Avatar.createVivid({
644
+ name: recordLabel || "",
645
+ size: "small",
646
+ shape: "circle",
647
+ });
648
+ primaryRow.appendChild(avatarEl);
649
+ } else {
650
+ var plUser = document.createElement("span");
651
+ plUser.className =
652
+ "size-20 rounded-full bg-primary-surface text-primary-text flex items-center justify-center text-reg-12 font-semibold shrink-0";
653
+ plUser.textContent = (recordLabel || "?").charAt(0).toUpperCase();
654
+ primaryRow.appendChild(plUser);
655
+ }
656
+ } else {
657
+ var IconComponent = getIcon();
658
+ if (IconComponent && typeof IconComponent.create === "function") {
659
+ var iconInfo = getObjectIconInfo(objectSlug, objectSchema);
660
+ var iconEl = IconComponent.create({
661
+ iconStr: iconInfo.iconStr,
662
+ color: iconInfo.color,
663
+ fallbackIconStr: "IconDatabase",
664
+ defaultIcon: true,
665
+ className: "size-20 shrink-0",
666
+ });
667
+ if (iconEl) primaryRow.appendChild(iconEl);
668
+ } else {
669
+ var plOther = document.createElement("span");
670
+ plOther.className =
671
+ "size-20 rounded-4 flex items-center justify-center shrink-0 bg-neutral-surface-hover text-neutral-text-base";
672
+ plOther.textContent = "?";
673
+ primaryRow.appendChild(plOther);
674
+ }
675
+ }
676
+
677
+ var labelSpan = document.createElement("span");
678
+ labelSpan.className = "truncate";
679
+ labelSpan.textContent = recordLabel;
680
+ primaryRow.appendChild(labelSpan);
681
+ optContent.appendChild(primaryRow);
682
+
683
+ // Display secondary fields
684
+ if (rec.displayLabels && rec.displayLabels.length > 0 && rec.displayLabels.some(function (l) { return l; })) {
685
+ var secondaryRow = document.createElement("div");
686
+ secondaryRow.className = "flex items-center gap-4 truncate text-reg-10 text-typography-tertiary-text mt-2 ml-28";
687
+ rec.displayLabels.forEach(function (label, index) {
688
+ if (label) {
689
+ var secondaryLabel = document.createElement("span");
690
+ secondaryLabel.className = "truncate";
691
+ secondaryLabel.textContent = label;
692
+ secondaryRow.appendChild(secondaryLabel);
693
+ if (index < rec.displayLabels.length - 1 && rec.displayLabels[index + 1]) {
694
+ var separator = document.createElement("span");
695
+ separator.textContent = " • ";
696
+ secondaryRow.appendChild(separator);
697
+ }
698
+ }
699
+ });
700
+ optContent.appendChild(secondaryRow);
701
+ }
702
+
703
+ option.appendChild(optContent);
704
+
705
+ var checkSpan = document.createElement("span");
706
+ checkSpan.className = "multiselect-option-check ml-auto flex size-14 shrink-0 items-center justify-center";
707
+ checkSpan.innerHTML = CHECK_SVG;
708
+ checkSpan.style.visibility = selected ? "visible" : "hidden";
709
+ option.appendChild(checkSpan);
710
+
711
+ option.addEventListener("click", function (e) {
712
+ e.stopPropagation();
713
+ if (disabled) return;
714
+ toggleValue(recordId);
715
+ });
716
+ option.addEventListener("mouseenter", function () {
717
+ if (disabled) return;
718
+ optionsList.querySelectorAll("[role=option]").forEach(function (o) {
719
+ if (o !== option && !isSelected(o.getAttribute("data-value"))) {
720
+ o.classList.remove("bg-fill-tertiary-fill-light-gray");
721
+ }
722
+ });
723
+ if (!option.classList.contains("bg-primary-surface")) {
724
+ option.classList.add("bg-fill-tertiary-fill-light-gray");
725
+ }
726
+ });
727
+
728
+ optionsList.appendChild(option);
729
+ });
730
+
731
+ if (isFetchingMore) {
732
+ showLoadingMore();
733
+ }
734
+ }
735
+
736
+ function loadInitialAndRender() {
737
+ showLoading();
738
+ currentPage = 1;
739
+ hasMoreRecords = true;
740
+ totalFetched = 0;
741
+
742
+ fetchRecords(searchTerm, initialLimit, 1).then(function (result) {
743
+ allRecords = result.records;
744
+ filteredRecords = result.records;
745
+ hasMoreRecords = result.hasMore;
746
+ totalFetched = result.records.length;
747
+ currentPage = 1;
748
+
749
+ if (filteredRecords.length === 0) {
750
+ showEmpty(searchTerm ? "No results found" : "No records available");
751
+ } else {
752
+ renderOptions();
753
+ }
754
+ }).catch(function () {
755
+ showEmpty("Failed to load records");
756
+ hasMoreRecords = false;
757
+ });
758
+ }
759
+
760
+ function loadMoreRecords() {
761
+ if (isFetchingMore || !hasMoreRecords) return;
762
+
763
+ isFetchingMore = true;
764
+ currentPage += 1;
765
+ showLoadingMore();
766
+
767
+ fetchRecords(searchTerm, initialLimit, currentPage).then(function (result) {
768
+ isFetchingMore = false;
769
+ removeLoadingMore();
770
+
771
+ if (result.records.length > 0) {
772
+ allRecords = allRecords.concat(result.records);
773
+ filteredRecords = filteredRecords.concat(result.records);
774
+ totalFetched += result.records.length;
775
+ hasMoreRecords = result.hasMore;
776
+ renderOptions();
777
+ } else {
778
+ hasMoreRecords = false;
779
+ }
780
+ }).catch(function (err) {
781
+ console.error("[RecordMultiSelect] loadMoreRecords error:", err);
782
+ isFetchingMore = false;
783
+ removeLoadingMore();
784
+ hasMoreRecords = false;
785
+ });
786
+ }
787
+
788
+ function debouncedSearch() {
789
+ if (searchDebounceTimer) clearTimeout(searchDebounceTimer);
790
+ searchDebounceTimer = setTimeout(function () {
791
+ searchDebounceTimer = null;
792
+ showLoading();
793
+ currentPage = 1;
794
+ hasMoreRecords = true;
795
+ totalFetched = 0;
796
+
797
+ fetchRecords(searchTerm, initialLimit, 1).then(function (result) {
798
+ allRecords = result.records;
799
+ filteredRecords = result.records;
800
+ hasMoreRecords = result.hasMore;
801
+ totalFetched = result.records.length;
802
+
803
+ if (result.records.length === 0) {
804
+ showEmpty("No results found");
805
+ } else {
806
+ renderOptions();
807
+ }
808
+ }).catch(function () {
809
+ showEmpty("Search failed");
810
+ hasMoreRecords = false;
811
+ });
812
+ }, 500);
813
+ }
814
+
815
+ function openDropdown() {
816
+ if (disabled) return;
817
+ if (usePopover && popover) {
818
+ popover.show();
819
+ return;
820
+ }
821
+ document
822
+ .querySelectorAll(".custom-select.open, .record-select.open, .custom-multiselect.open, .record-multiselect.open")
823
+ .forEach(function (other) {
824
+ if (other !== container) {
825
+ other.classList.remove("open");
826
+ var t = other.querySelector(
827
+ "button, .custom-select-trigger, .record-select-trigger, .multiselect-trigger-wrapper button, .record-multiselect-trigger"
828
+ );
829
+ if (t) t.setAttribute("aria-expanded", "false");
830
+ }
831
+ });
832
+ isOpen = true;
833
+ container.classList.add("open");
834
+ trigger.setAttribute("aria-expanded", "true");
835
+ searchTerm = "";
836
+ if (searchInputWrapper) searchInputWrapper.setValue("");
837
+ else if (searchInputEl) searchInputEl.value = "";
838
+ loadInitialAndRender();
839
+ setTimeout(function () {
840
+ if (searchInputEl) searchInputEl.focus();
841
+ }, 0);
842
+ updatePosition();
843
+ }
844
+
845
+ function closeDropdown() {
846
+ if (usePopover && popover) {
847
+ popover.hide();
848
+ return;
849
+ }
850
+ isOpen = false;
851
+ container.classList.remove("open");
852
+ trigger.setAttribute("aria-expanded", "false");
853
+ searchTerm = "";
854
+ if (searchInputWrapper) searchInputWrapper.setValue("");
855
+ else if (searchInputEl) searchInputEl.value = "";
856
+ if (searchDebounceTimer) {
857
+ clearTimeout(searchDebounceTimer);
858
+ searchDebounceTimer = null;
859
+ }
860
+ }
861
+
862
+ function updatePosition() {
863
+ if (usePopover) return;
864
+ var rect = trigger.getBoundingClientRect();
865
+ var vh = window.innerHeight;
866
+ var below = vh - rect.bottom;
867
+ var above = rect.top;
868
+ if (below < 200 && above > below) {
869
+ content.className = contentBase + "bottom-full mb-1 translate-y-1 group-[.open]:translate-y-0";
870
+ } else {
871
+ content.className = contentBase + "top-full mt-1 -translate-y-1 group-[.open]:translate-y-0";
872
+ }
873
+ }
874
+
875
+ if (!searchInputWrapper && searchInputEl) {
876
+ searchInputEl.addEventListener("input", function () {
877
+ searchTerm = this.value.trim();
878
+ if (!searchTerm) {
879
+ loadInitialAndRender();
880
+ return;
881
+ }
882
+ debouncedSearch();
883
+ });
884
+ }
885
+ if (searchInputEl) {
886
+ searchInputEl.addEventListener("keydown", function (e) {
887
+ e.stopPropagation();
888
+ });
889
+ }
890
+
891
+ if (usePopover && popover) {
892
+ trigger.addEventListener("keydown", function (e) {
893
+ if (disabled) return;
894
+ if (e.key === "Enter" || e.key === " ") {
895
+ e.preventDefault();
896
+ if (isOpen) popover.hide();
897
+ else popover.show();
898
+ }
899
+ });
900
+ } else {
901
+ trigger.addEventListener("click", function (e) {
902
+ e.preventDefault();
903
+ e.stopPropagation();
904
+ if (isOpen) closeDropdown();
905
+ else openDropdown();
906
+ });
907
+ trigger.addEventListener("keydown", function (e) {
908
+ if (disabled) return;
909
+ if (e.key === "Enter" || e.key === " ") {
910
+ e.preventDefault();
911
+ if (isOpen) closeDropdown();
912
+ else openDropdown();
913
+ }
914
+ if (e.key === "Escape" && isOpen) {
915
+ e.preventDefault();
916
+ closeDropdown();
917
+ }
918
+ });
919
+ document.addEventListener("click", function (e) {
920
+ if (isOpen && !container.contains(e.target)) closeDropdown();
921
+ });
922
+ document.addEventListener("keydown", function (e) {
923
+ if (e.key === "Escape" && isOpen) closeDropdown();
924
+ });
925
+ }
926
+
927
+ container.updateValues = function (newValues) {
928
+ values = Array.isArray(newValues) ? newValues.slice() : [];
929
+ loadSelectedRecords();
930
+ };
931
+
932
+ container.setDisabled = function (isDisabled) {
933
+ disabled = !!isDisabled;
934
+ trigger.disabled = disabled;
935
+ trigger.className = triggerClasses(variant, size, disabled, values.length === 0);
936
+ if (disabled && isOpen) closeDropdown();
937
+ };
938
+
939
+ container.getValues = function () {
940
+ return values.slice();
941
+ };
942
+
943
+ container.getSelectedRecords = function () {
944
+ return selectedRecords.slice();
945
+ };
946
+
947
+ if (values && values.length > 0) loadSelectedRecords();
948
+ else renderTriggerContent();
949
+
950
+ return container;
951
+ }
952
+
953
+ global.RecordMultiSelect = {
954
+ create: createRecordMultiSelect,
955
+ };
956
+ })(typeof window !== "undefined" ? window : this);