@superleapai/flow-ui 2.4.6 → 2.5.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.
@@ -11,7 +11,10 @@
11
11
  "use strict";
12
12
 
13
13
  function getDep(name) {
14
- if (typeof global.FlowUI !== "undefined" && typeof global.FlowUI._getComponent === "function") {
14
+ if (
15
+ typeof global.FlowUI !== "undefined" &&
16
+ typeof global.FlowUI._getComponent === "function"
17
+ ) {
15
18
  var c = global.FlowUI._getComponent(name);
16
19
  if (c) return c;
17
20
  }
@@ -42,7 +45,7 @@
42
45
  '<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>';
43
46
  var X_SVG =
44
47
  '<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>';
45
-
48
+
46
49
  var SEARCH_ICON =
47
50
  '<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>';
48
51
 
@@ -57,7 +60,8 @@
57
60
  "border-error-border hover:border-error-border-hover focus:border-1/2 focus:border-error-border-hover",
58
61
  warning:
59
62
  "border-warning-border hover:border-warning-border-hover focus:border-1/2 focus:border-warning-border-hover",
60
- borderless: "border-none shadow-none rounded-0 bg-fill-quarternary-fill-white",
63
+ borderless:
64
+ "border-none shadow-none rounded-0 bg-fill-quarternary-fill-white",
61
65
  inline:
62
66
  "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",
63
67
  };
@@ -66,7 +70,9 @@
66
70
  large: "px-12 py-8",
67
71
  small: "px-8 py-4",
68
72
  };
69
- var placeholderClass = placeholder ? " text-typography-quaternary-text" : "";
73
+ var placeholderClass = placeholder
74
+ ? " text-typography-quaternary-text"
75
+ : "";
70
76
  var disabledClass = disabled
71
77
  ? " pointer-events-none cursor-not-allowed bg-fill-tertiary-fill-light-gray text-typography-quaternary-text hover:border-border-primary"
72
78
  : "";
@@ -122,15 +128,27 @@
122
128
  function getObjectIconInfo(slug, objectSchema) {
123
129
  var color = "neutral";
124
130
  var iconStr = "IconDatabase";
125
- if (objectSchema && objectSchema.properties && objectSchema.properties.icon_data) {
131
+ if (
132
+ objectSchema &&
133
+ objectSchema.properties &&
134
+ objectSchema.properties.icon_data
135
+ ) {
126
136
  var iconData = objectSchema.properties.icon_data;
127
- if (typeof iconData.color === "string" && iconData.color) color = iconData.color;
128
- if (typeof iconData.icon === "string" && iconData.icon) iconStr = iconData.icon;
137
+ if (typeof iconData.color === "string" && iconData.color)
138
+ color = iconData.color;
139
+ if (typeof iconData.icon === "string" && iconData.icon)
140
+ iconStr = iconData.icon;
129
141
  }
130
- if (!objectSchema || !objectSchema.properties || !objectSchema.properties.icon_data || !objectSchema.properties.icon_data.icon) {
142
+ if (
143
+ !objectSchema ||
144
+ !objectSchema.properties ||
145
+ !objectSchema.properties.icon_data ||
146
+ !objectSchema.properties.icon_data.icon
147
+ ) {
131
148
  if (OBJECT_SLUG_TO_ICON[slug]) {
132
149
  iconStr = OBJECT_SLUG_TO_ICON[slug].iconStr;
133
- if (OBJECT_SLUG_TO_ICON[slug].color) color = OBJECT_SLUG_TO_ICON[slug].color;
150
+ if (OBJECT_SLUG_TO_ICON[slug].color)
151
+ color = OBJECT_SLUG_TO_ICON[slug].color;
134
152
  } else {
135
153
  iconStr = "IconDatabase";
136
154
  color = "neutral";
@@ -153,6 +171,7 @@
153
171
  * @param {string} [config.size] - 'default' | 'large' | 'small'
154
172
  * @param {boolean} [config.canClear] - Show clear button when value is set
155
173
  * @param {number} [config.initialLimit] - Initial fetch limit (default 50)
174
+ * @param {Object} [config.initialFilter] - Optional filter object to merge with search (e.g. { field: "status", operator: "exact", value: "active" } or { and: [...] })
156
175
  * @param {Object} [config.objectSchema] - Optional object type/schema; properties.icon_data { svg?, color? } used for static icon (not used for user; user shows Vivid Avatar)
157
176
  * @returns {HTMLElement} Record select container element
158
177
  */
@@ -161,12 +180,14 @@
161
180
  var objectSlug = config.objectSlug;
162
181
  var objectSchema = config.objectSchema || null;
163
182
  var placeholder = config.placeholder || "Select a record";
164
- var searchPlaceholder = config.searchPlaceholder || "Search " + (objectSlug || "") + "...";
183
+ var searchPlaceholder =
184
+ config.searchPlaceholder || "Search " + (objectSlug || "") + "...";
165
185
  var onChange = config.onChange;
166
186
  var variant = config.variant || "default";
167
187
  var size = config.size || "default";
168
188
  var canClear = !!config.canClear;
169
189
  var initialLimit = config.initialLimit != null ? config.initialLimit : 50;
190
+ var initialFilter = config.initialFilter || null; // Can be array, object, or function returning either
170
191
 
171
192
  var disabled = config.disabled === true;
172
193
  var value =
@@ -214,7 +235,7 @@
214
235
  size,
215
236
  disabled,
216
237
  !value,
217
- canClear && !!value && !disabled
238
+ canClear && !!value && !disabled,
218
239
  );
219
240
  trigger.disabled = disabled;
220
241
  trigger.setAttribute("aria-haspopup", "listbox");
@@ -223,7 +244,8 @@
223
244
  trigger.classList.add("record-select-trigger");
224
245
 
225
246
  var triggerContent = document.createElement("div");
226
- triggerContent.className = "record-select-trigger-content flex items-center gap-8 flex-1 min-w-0";
247
+ triggerContent.className =
248
+ "record-select-trigger-content flex items-center gap-8 flex-1 min-w-0";
227
249
 
228
250
  var triggerIcon = document.createElement("span");
229
251
  triggerIcon.className = "record-select-trigger-icon shrink-0 hidden";
@@ -267,7 +289,8 @@
267
289
  var content = document.createElement("div");
268
290
  content.setAttribute("role", "listbox");
269
291
  content.setAttribute("data-field-id", fieldId);
270
- content.className = "record-select-content max-h-[45vh] overflow-hidden flex flex-col";
292
+ content.className =
293
+ "record-select-content max-h-[45vh] overflow-hidden flex flex-col";
271
294
 
272
295
  var searchWrap = document.createElement("div");
273
296
  searchWrap.className = "py-8 border-b-1/2 border-border-primary";
@@ -298,12 +321,12 @@
298
321
  } else {
299
322
  var fallbackWrapper = document.createElement("div");
300
323
  fallbackWrapper.className = "flex items-center gap-8 px-12";
301
-
324
+
302
325
  var searchIconSpan = document.createElement("span");
303
326
  searchIconSpan.className = "shrink-0 text-typography-tertiary-text";
304
327
  searchIconSpan.innerHTML = SEARCH_ICON;
305
328
  fallbackWrapper.appendChild(searchIconSpan);
306
-
329
+
307
330
  var searchInput = document.createElement("input");
308
331
  searchInput.type = "text";
309
332
  searchInput.className =
@@ -317,21 +340,22 @@
317
340
  content.appendChild(searchWrap);
318
341
 
319
342
  var optionsList = document.createElement("div");
320
- optionsList.className = "overflow-y-auto max-h-[45vh] p-2 w-full rounded-4 bg-fill-quarternary-fill-white record-select-options";
321
-
343
+ optionsList.className =
344
+ "overflow-y-auto max-h-[45vh] p-2 w-full rounded-4 bg-fill-quarternary-fill-white record-select-options";
345
+
322
346
  // Add scroll listener for infinite scroll
323
347
  optionsList.addEventListener("scroll", function () {
324
348
  if (isFetchingMore || !hasMoreRecords) return;
325
349
  var scrollHeight = optionsList.scrollHeight;
326
350
  var scrollTop = optionsList.scrollTop;
327
351
  var clientHeight = optionsList.clientHeight;
328
-
352
+
329
353
  // Trigger load more when scrolled to bottom (with 50px threshold)
330
354
  if (scrollTop + clientHeight >= scrollHeight - 50) {
331
355
  loadMoreRecords();
332
356
  }
333
357
  });
334
-
358
+
335
359
  content.appendChild(optionsList);
336
360
 
337
361
  popover = Popover.create({
@@ -365,11 +389,15 @@
365
389
  }, 0);
366
390
  // Let consumers (e.g. BANT Questions "Add Contact") inject content into the dropdown
367
391
  try {
368
- var doc = global.document || (typeof document !== "undefined" ? document : null);
392
+ var doc =
393
+ global.document ||
394
+ (typeof document !== "undefined" ? document : null);
369
395
  if (doc && typeof global.CustomEvent !== "undefined") {
370
- doc.dispatchEvent(new global.CustomEvent("record-select:opened", {
371
- detail: { fieldId: fieldId, content: content },
372
- }));
396
+ doc.dispatchEvent(
397
+ new global.CustomEvent("record-select:opened", {
398
+ detail: { fieldId: fieldId, content: content },
399
+ }),
400
+ );
373
401
  }
374
402
  } catch (e) {}
375
403
  },
@@ -387,7 +415,8 @@
387
415
  },
388
416
  });
389
417
 
390
- if (clearBtn) clearBtn.style.display = canClear && value && !disabled ? "" : "none";
418
+ if (clearBtn)
419
+ clearBtn.style.display = canClear && value && !disabled ? "" : "none";
391
420
 
392
421
  function setValue(newVal) {
393
422
  value = newVal !== undefined && newVal !== null ? newVal : "";
@@ -401,16 +430,18 @@
401
430
 
402
431
  function updateTriggerDisplay() {
403
432
  if (selectedRecord) {
404
- triggerText.textContent = selectedRecord.name || selectedRecord.label || value;
433
+ triggerText.textContent =
434
+ selectedRecord.name || selectedRecord.label || value;
405
435
  trigger.classList.remove("placeholder");
406
436
  trigger.className = triggerClasses(
407
437
  variant,
408
438
  size,
409
439
  disabled,
410
440
  false,
411
- canClear && !!value && !disabled
441
+ canClear && !!value && !disabled,
412
442
  );
413
- triggerIcon.className = "record-select-trigger-icon shrink-0 flex items-center justify-center size-20 rounded-4 overflow-hidden";
443
+ triggerIcon.className =
444
+ "record-select-trigger-icon shrink-0 flex items-center justify-center size-20 rounded-4 overflow-hidden";
414
445
  triggerIcon.innerHTML = "";
415
446
  if (objectSlug === STANDARD_OBJECT_SLUGS_USERS) {
416
447
  var Avatar = getAvatar();
@@ -422,7 +453,9 @@
422
453
  });
423
454
  triggerIcon.appendChild(vividEl);
424
455
  } else {
425
- renderStaticIconPlaceholder(selectedRecord.name || selectedRecord.label);
456
+ renderStaticIconPlaceholder(
457
+ selectedRecord.name || selectedRecord.label,
458
+ );
426
459
  }
427
460
  } else {
428
461
  renderStaticObjectIcon();
@@ -435,12 +468,13 @@
435
468
  size,
436
469
  disabled,
437
470
  true,
438
- canClear && !!value && !disabled
471
+ canClear && !!value && !disabled,
439
472
  );
440
473
  triggerIcon.className = "record-select-trigger-icon shrink-0 hidden";
441
474
  triggerIcon.innerHTML = "";
442
475
  }
443
- if (clearBtn) clearBtn.style.display = canClear && value && !disabled ? "" : "none";
476
+ if (clearBtn)
477
+ clearBtn.style.display = canClear && value && !disabled ? "" : "none";
444
478
  }
445
479
 
446
480
  function renderStaticObjectIcon() {
@@ -496,18 +530,39 @@
496
530
  var fields = ["id", "name"];
497
531
  var actualLimit = limit || initialLimit;
498
532
  var offset = page ? (page - 1) * actualLimit : 0;
499
-
533
+
500
534
  try {
501
535
  if (model && typeof model.select === "function") {
502
536
  var q = model.select.apply(model, fields);
537
+ var filters = [];
538
+ var resolvedFilter =
539
+ typeof initialFilter === "function"
540
+ ? initialFilter()
541
+ : initialFilter;
542
+ console.log(
543
+ "[RecordSelect] initialFilter:",
544
+ resolvedFilter,
545
+ "| search:",
546
+ search,
547
+ );
548
+ if (resolvedFilter) {
549
+ filters = filters.concat(
550
+ Array.isArray(resolvedFilter) ? resolvedFilter : [resolvedFilter],
551
+ );
552
+ }
503
553
  if (search && search.trim()) {
504
- q = q.filterBy({
554
+ filters.push({
505
555
  or: [
506
556
  { field: "name", operator: "contains", value: search.trim() },
507
557
  { field: "id", operator: "eq", value: search.trim() },
508
558
  ],
509
559
  });
510
560
  }
561
+ if (filters.length > 0) {
562
+ q = q.filterBy(
563
+ filters.length === 1 ? filters[0] : { and: filters },
564
+ );
565
+ }
511
566
  var orderBy = ["name"];
512
567
  if (objectSlug === "account") orderBy.push("-ParentId");
513
568
  return q
@@ -570,7 +625,12 @@
570
625
  updateTriggerDisplay();
571
626
  })
572
627
  .catch(function () {
573
- selectedRecord = { id: value, value: value, name: value, label: value };
628
+ selectedRecord = {
629
+ id: value,
630
+ value: value,
631
+ name: value,
632
+ label: value,
633
+ };
574
634
  updateTriggerDisplay();
575
635
  });
576
636
  }
@@ -581,7 +641,9 @@
581
641
  loadWrap.className =
582
642
  "flex flex-row items-center justify-center gap-8 py-12 px-12 w-full text-reg-12 text-typography-quaternary-text record-select-loading";
583
643
  if (Spinner && typeof Spinner.create === "function") {
584
- loadWrap.appendChild(Spinner.create({ size: "small", text: "Loading..." }));
644
+ loadWrap.appendChild(
645
+ Spinner.create({ size: "small", text: "Loading..." }),
646
+ );
585
647
  } else {
586
648
  var loadText = document.createElement("span");
587
649
  loadText.textContent = "Loading...";
@@ -589,12 +651,12 @@
589
651
  }
590
652
  optionsList.appendChild(loadWrap);
591
653
  }
592
-
654
+
593
655
  function showLoadingMore() {
594
656
  // Remove existing loading more indicator
595
657
  var existing = optionsList.querySelector(".record-select-loading-more");
596
658
  if (existing) existing.remove();
597
-
659
+
598
660
  var loadWrap = document.createElement("div");
599
661
  loadWrap.className =
600
662
  "flex flex-row items-center justify-center gap-8 py-8 px-12 w-full text-reg-12 text-typography-quaternary-text record-select-loading-more";
@@ -607,9 +669,11 @@
607
669
  }
608
670
  optionsList.appendChild(loadWrap);
609
671
  }
610
-
672
+
611
673
  function removeLoadingMore() {
612
- var loadingMore = optionsList.querySelector(".record-select-loading-more");
674
+ var loadingMore = optionsList.querySelector(
675
+ ".record-select-loading-more",
676
+ );
613
677
  if (loadingMore) loadingMore.remove();
614
678
  }
615
679
 
@@ -628,13 +692,15 @@
628
692
  existingOptions.forEach(function (opt) {
629
693
  opt.remove();
630
694
  });
631
-
695
+
632
696
  // Remove old loading/empty states
633
- var oldStates = optionsList.querySelectorAll(".record-select-loading, .record-select-empty");
697
+ var oldStates = optionsList.querySelectorAll(
698
+ ".record-select-loading, .record-select-empty",
699
+ );
634
700
  oldStates.forEach(function (el) {
635
701
  el.remove();
636
702
  });
637
-
703
+
638
704
  filteredRecords.forEach(function (rec) {
639
705
  var optionValue = rec.id || rec.value;
640
706
  var optionLabel = rec.name || rec.label || rec.value;
@@ -649,7 +715,7 @@
649
715
  "hover:bg-fill-tertiary-fill-light-gray focus:bg-fill-tertiary-fill-light-gray",
650
716
  isSelected
651
717
  ? "bg-primary-surface hover:!bg-primary-surface-hover"
652
- : ""
718
+ : "",
653
719
  );
654
720
 
655
721
  var optContent = document.createElement("span");
@@ -707,7 +773,7 @@
707
773
 
708
774
  optionsList.appendChild(option);
709
775
  });
710
-
776
+
711
777
  // Add loading more indicator at the bottom if fetching
712
778
  if (isFetchingMore) {
713
779
  showLoadingMore();
@@ -729,60 +795,71 @@
729
795
  currentPage = 1;
730
796
  hasMoreRecords = true;
731
797
  totalFetched = 0;
732
-
733
- fetchRecords(searchTerm, initialLimit, 1).then(function (result) {
734
- allRecords = result.records;
735
- filteredRecords = result.records;
736
- hasMoreRecords = result.hasMore;
737
- totalFetched = result.records.length;
738
- currentPage = 1;
739
-
740
- if (value && !result.records.some(function (r) { return (r.id || r.value) === value; })) {
741
- loadSelectedRecord();
742
- } else if (value && result.records.length) {
743
- var sel = result.records.find(function (r) { return (r.id || r.value) === value; });
744
- if (sel) selectedRecord = sel;
745
- updateTriggerDisplay();
746
- }
747
- if (filteredRecords.length === 0) {
748
- showEmpty(searchTerm ? "No results found" : "No records available");
749
- } else {
750
- renderOptions();
751
- }
752
- scheduleUpdatePosition();
753
- }).catch(function () {
754
- showEmpty("Failed to load records");
755
- hasMoreRecords = false;
756
- scheduleUpdatePosition();
757
- });
798
+
799
+ fetchRecords(searchTerm, initialLimit, 1)
800
+ .then(function (result) {
801
+ allRecords = result.records;
802
+ filteredRecords = result.records;
803
+ hasMoreRecords = result.hasMore;
804
+ totalFetched = result.records.length;
805
+ currentPage = 1;
806
+
807
+ if (
808
+ value &&
809
+ !result.records.some(function (r) {
810
+ return (r.id || r.value) === value;
811
+ })
812
+ ) {
813
+ loadSelectedRecord();
814
+ } else if (value && result.records.length) {
815
+ var sel = result.records.find(function (r) {
816
+ return (r.id || r.value) === value;
817
+ });
818
+ if (sel) selectedRecord = sel;
819
+ updateTriggerDisplay();
820
+ }
821
+ if (filteredRecords.length === 0) {
822
+ showEmpty(searchTerm ? "No results found" : "No records available");
823
+ } else {
824
+ renderOptions();
825
+ }
826
+ scheduleUpdatePosition();
827
+ })
828
+ .catch(function () {
829
+ showEmpty("Failed to load records");
830
+ hasMoreRecords = false;
831
+ scheduleUpdatePosition();
832
+ });
758
833
  }
759
-
834
+
760
835
  function loadMoreRecords() {
761
836
  if (isFetchingMore || !hasMoreRecords) return;
762
-
837
+
763
838
  isFetchingMore = true;
764
839
  currentPage += 1;
765
840
  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 {
841
+
842
+ fetchRecords(searchTerm, initialLimit, currentPage)
843
+ .then(function (result) {
844
+ isFetchingMore = false;
845
+ removeLoadingMore();
846
+
847
+ if (result.records.length > 0) {
848
+ allRecords = allRecords.concat(result.records);
849
+ filteredRecords = filteredRecords.concat(result.records);
850
+ totalFetched += result.records.length;
851
+ hasMoreRecords = result.hasMore;
852
+ renderOptions();
853
+ } else {
854
+ hasMoreRecords = false;
855
+ }
856
+ })
857
+ .catch(function (err) {
858
+ console.error("[RecordSelect] loadMoreRecords error:", err);
859
+ isFetchingMore = false;
860
+ removeLoadingMore();
778
861
  hasMoreRecords = false;
779
- }
780
- }).catch(function (err) {
781
- console.error("[RecordSelect] loadMoreRecords error:", err);
782
- isFetchingMore = false;
783
- removeLoadingMore();
784
- hasMoreRecords = false;
785
- });
862
+ });
786
863
  }
787
864
 
788
865
  function debouncedSearch() {
@@ -793,24 +870,26 @@
793
870
  currentPage = 1;
794
871
  hasMoreRecords = true;
795
872
  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
- scheduleUpdatePosition();
809
- }).catch(function () {
810
- showEmpty("Search failed");
811
- hasMoreRecords = false;
812
- scheduleUpdatePosition();
813
- });
873
+
874
+ fetchRecords(searchTerm, initialLimit, 1)
875
+ .then(function (result) {
876
+ allRecords = result.records;
877
+ filteredRecords = result.records;
878
+ hasMoreRecords = result.hasMore;
879
+ totalFetched = result.records.length;
880
+
881
+ if (result.records.length === 0) {
882
+ showEmpty("No results found");
883
+ } else {
884
+ renderOptions();
885
+ }
886
+ scheduleUpdatePosition();
887
+ })
888
+ .catch(function () {
889
+ showEmpty("Search failed");
890
+ hasMoreRecords = false;
891
+ scheduleUpdatePosition();
892
+ });
814
893
  }, 500);
815
894
  }
816
895
 
@@ -860,7 +939,7 @@
860
939
  size,
861
940
  disabled,
862
941
  !value,
863
- canClear && !!value && !disabled
942
+ canClear && !!value && !disabled,
864
943
  );
865
944
  if (disabled && isOpen) closeDropdown();
866
945
  };
package/core/flow.js CHANGED
@@ -591,6 +591,81 @@
591
591
  return field;
592
592
  }
593
593
 
594
+ /**
595
+ * Create a card select field (uses CardSelect component when available, else radio fallback)
596
+ * @param {Object} config - Configuration object
597
+ * @param {string} config.label - Field label
598
+ * @param {string} config.fieldId - State key for this field
599
+ * @param {Array} config.options - Array of { value, label, description?, icon?, disabled? }
600
+ * @param {boolean} [config.required] - Whether field is required
601
+ * @param {Function} [config.onChange] - Optional change handler (receives selected value)
602
+ * @param {string} [config.helpText] - Optional help text for tooltip
603
+ * @param {boolean} [config.disabled] - Whether all cards are disabled
604
+ * @param {string} [config.className] - Extra CSS class on card container
605
+ * @returns {HTMLElement} Field wrapper element
606
+ */
607
+ function createCardSelect(config) {
608
+ const { label, fieldId, options = [], required = false, onChange, helpText = null, disabled = false, className } = config;
609
+
610
+ const field = createFieldWrapper(label, required, helpText);
611
+
612
+ if (getComponent("CardSelect") && getComponent("CardSelect").create) {
613
+ const currentValue = get(fieldId);
614
+ const cardSelectEl = getComponent("CardSelect").create({
615
+ name: fieldId,
616
+ options: options.map((opt) => ({
617
+ value: opt.value,
618
+ label: opt.label || opt.value,
619
+ description: opt.description,
620
+ icon: opt.icon,
621
+ disabled: opt.disabled,
622
+ })),
623
+ value: currentValue,
624
+ disabled,
625
+ className,
626
+ onChange: (value) => {
627
+ set(fieldId, value);
628
+ if (onChange) onChange(value);
629
+ },
630
+ });
631
+ cardSelectEl._fieldId = fieldId;
632
+ field.appendChild(cardSelectEl);
633
+ return field;
634
+ }
635
+
636
+ // Fallback: native radio buttons
637
+ const radioGroup = document.createElement("div");
638
+ radioGroup.className = "card-select-fallback";
639
+
640
+ options.forEach((opt) => {
641
+ const wrapper = document.createElement("div");
642
+ wrapper.className = "card-option";
643
+
644
+ const radio = document.createElement("input");
645
+ radio.type = "radio";
646
+ radio.name = fieldId;
647
+ radio.value = opt.value;
648
+ radio.id = `${fieldId}-${opt.value}`;
649
+ radio.checked = get(fieldId) === opt.value;
650
+ radio.disabled = disabled || !!opt.disabled;
651
+ radio.addEventListener("change", () => {
652
+ set(fieldId, opt.value);
653
+ if (onChange) { onChange(opt.value); }
654
+ });
655
+
656
+ const radioLabel = document.createElement("label");
657
+ radioLabel.htmlFor = `${fieldId}-${opt.value}`;
658
+ radioLabel.textContent = opt.label || opt.value;
659
+
660
+ wrapper.appendChild(radio);
661
+ wrapper.appendChild(radioLabel);
662
+ radioGroup.appendChild(wrapper);
663
+ });
664
+
665
+ field.appendChild(radioGroup);
666
+ return field;
667
+ }
668
+
594
669
  /**
595
670
  * Create a multi-select field (uses MultiSelect component when available, else checkbox group)
596
671
  * @param {Object} config - Configuration object
@@ -700,6 +775,7 @@
700
775
  size,
701
776
  canClear,
702
777
  initialLimit,
778
+ initialFilter,
703
779
  helpText = null,
704
780
  } = config;
705
781
 
@@ -719,6 +795,7 @@
719
795
  size: size || "default",
720
796
  canClear: !!canClear,
721
797
  initialLimit,
798
+ initialFilter,
722
799
  onChange: (value, record) => {
723
800
  set(fieldId, value);
724
801
  if (onChange) onChange(value, record);
@@ -768,6 +845,7 @@
768
845
  variant,
769
846
  size,
770
847
  initialLimit,
848
+ initialFilter,
771
849
  displayFields,
772
850
  helpText = null,
773
851
  } = config;
@@ -786,6 +864,7 @@
786
864
  variant: variant || "default",
787
865
  size: size || "default",
788
866
  initialLimit,
867
+ initialFilter,
789
868
  displayFields: displayFields || [],
790
869
  onValuesChange: (values, records) => {
791
870
  set(fieldId, values);
@@ -1737,6 +1816,7 @@
1737
1816
  createTimePicker,
1738
1817
  createDateTimePicker,
1739
1818
  createRadioGroup,
1819
+ createCardSelect,
1740
1820
  createMultiSelect,
1741
1821
  createRecordSelect,
1742
1822
  createRecordMultiSelect,