@superleapai/flow-ui 2.5.10 → 2.5.12
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/components/record-multiselect.js +31 -16
- package/components/record-select.js +27 -14
- package/components/switch.js +185 -0
- package/core/flow.js +50 -0
- package/dist/output.css +1 -1
- package/dist/superleap-flow.min.js +2 -2
- package/index.js +2 -0
- package/package.json +1 -1
|
@@ -346,20 +346,20 @@
|
|
|
346
346
|
|
|
347
347
|
var optionsList = document.createElement("div");
|
|
348
348
|
optionsList.className = "overflow-y-auto max-h-[45vh] p-2 w-full rounded-4 bg-fill-quarternary-fill-white record-multiselect-options";
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
349
|
+
|
|
350
|
+
var loadMoreSentinel = null;
|
|
351
|
+
var loadMoreObserver =
|
|
352
|
+
typeof IntersectionObserver !== "undefined"
|
|
353
|
+
? new IntersectionObserver(
|
|
354
|
+
function (entries) {
|
|
355
|
+
if (!entries.length || !entries[0].isIntersecting) return;
|
|
356
|
+
if (isFetchingMore || !hasMoreRecords) return;
|
|
357
|
+
loadMoreRecords();
|
|
358
|
+
},
|
|
359
|
+
{ root: optionsList, rootMargin: "50px", threshold: 0 },
|
|
360
|
+
)
|
|
361
|
+
: null;
|
|
362
|
+
|
|
363
363
|
content.appendChild(optionsList);
|
|
364
364
|
|
|
365
365
|
var Popover = getDep("Popover");
|
|
@@ -619,11 +619,17 @@
|
|
|
619
619
|
opt.remove();
|
|
620
620
|
});
|
|
621
621
|
|
|
622
|
-
|
|
622
|
+
if (loadMoreSentinel && loadMoreObserver) {
|
|
623
|
+
loadMoreObserver.unobserve(loadMoreSentinel);
|
|
624
|
+
loadMoreSentinel = null;
|
|
625
|
+
}
|
|
626
|
+
var oldStates = optionsList.querySelectorAll(
|
|
627
|
+
".record-multiselect-loading, .record-multiselect-empty, .record-multiselect-sentinel",
|
|
628
|
+
);
|
|
623
629
|
oldStates.forEach(function (el) {
|
|
624
630
|
el.remove();
|
|
625
631
|
});
|
|
626
|
-
|
|
632
|
+
|
|
627
633
|
filteredRecords.forEach(function (rec) {
|
|
628
634
|
var recordId = rec.id || rec.value;
|
|
629
635
|
var recordLabel = rec.name || rec.label || rec.value;
|
|
@@ -738,6 +744,15 @@
|
|
|
738
744
|
|
|
739
745
|
if (isFetchingMore) {
|
|
740
746
|
showLoadingMore();
|
|
747
|
+
} else if (hasMoreRecords && filteredRecords.length > 0 && loadMoreObserver) {
|
|
748
|
+
// Sentinel: when visible (short list or user scrolled to bottom), load more automatically
|
|
749
|
+
var sentinel = document.createElement("div");
|
|
750
|
+
sentinel.className = "record-multiselect-sentinel";
|
|
751
|
+
sentinel.setAttribute("aria-hidden", "true");
|
|
752
|
+
sentinel.style.cssText = "height:1px;visibility:hidden;pointer-events:none;";
|
|
753
|
+
optionsList.appendChild(sentinel);
|
|
754
|
+
loadMoreSentinel = sentinel;
|
|
755
|
+
loadMoreObserver.observe(sentinel);
|
|
741
756
|
}
|
|
742
757
|
}
|
|
743
758
|
|
|
@@ -345,18 +345,18 @@
|
|
|
345
345
|
optionsList.className =
|
|
346
346
|
"overflow-y-auto max-h-[45vh] p-2 w-full rounded-4 bg-fill-quarternary-fill-white record-select-options";
|
|
347
347
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
348
|
+
var loadMoreSentinel = null;
|
|
349
|
+
var loadMoreObserver =
|
|
350
|
+
typeof IntersectionObserver !== "undefined"
|
|
351
|
+
? new IntersectionObserver(
|
|
352
|
+
function (entries) {
|
|
353
|
+
if (!entries.length || !entries[0].isIntersecting) return;
|
|
354
|
+
if (isFetchingMore || !hasMoreRecords) return;
|
|
355
|
+
loadMoreRecords();
|
|
356
|
+
},
|
|
357
|
+
{ root: optionsList, rootMargin: "50px", threshold: 0 },
|
|
358
|
+
)
|
|
359
|
+
: null;
|
|
360
360
|
|
|
361
361
|
content.appendChild(optionsList);
|
|
362
362
|
|
|
@@ -694,9 +694,13 @@
|
|
|
694
694
|
opt.remove();
|
|
695
695
|
});
|
|
696
696
|
|
|
697
|
-
// Remove old loading/empty states
|
|
697
|
+
// Remove old loading/empty states and sentinel (unobserve first)
|
|
698
|
+
if (loadMoreSentinel && loadMoreObserver) {
|
|
699
|
+
loadMoreObserver.unobserve(loadMoreSentinel);
|
|
700
|
+
loadMoreSentinel = null;
|
|
701
|
+
}
|
|
698
702
|
var oldStates = optionsList.querySelectorAll(
|
|
699
|
-
".record-select-loading, .record-select-empty",
|
|
703
|
+
".record-select-loading, .record-select-empty, .record-select-sentinel",
|
|
700
704
|
);
|
|
701
705
|
oldStates.forEach(function (el) {
|
|
702
706
|
el.remove();
|
|
@@ -778,6 +782,15 @@
|
|
|
778
782
|
// Add loading more indicator at the bottom if fetching
|
|
779
783
|
if (isFetchingMore) {
|
|
780
784
|
showLoadingMore();
|
|
785
|
+
} else if (hasMoreRecords && filteredRecords.length > 0 && loadMoreObserver) {
|
|
786
|
+
// Sentinel: when visible (short list or user scrolled to bottom), load more automatically
|
|
787
|
+
var sentinel = document.createElement("div");
|
|
788
|
+
sentinel.className = "record-select-sentinel";
|
|
789
|
+
sentinel.setAttribute("aria-hidden", "true");
|
|
790
|
+
sentinel.style.cssText = "height:1px;visibility:hidden;pointer-events:none;";
|
|
791
|
+
optionsList.appendChild(sentinel);
|
|
792
|
+
loadMoreSentinel = sentinel;
|
|
793
|
+
loadMoreObserver.observe(sentinel);
|
|
781
794
|
}
|
|
782
795
|
}
|
|
783
796
|
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Switch Component (vanilla JS)
|
|
3
|
+
* Design-system switch with track + thumb, size variants, and accessibility.
|
|
4
|
+
* Ref: React Switch with Radix UI primitives and cva variants; no React/Radix/cva dependency.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
(function (global) {
|
|
8
|
+
"use strict";
|
|
9
|
+
|
|
10
|
+
var SWITCH_BASE_CLASS =
|
|
11
|
+
"cursor-pointer flex items-center rounded-128 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-[0.65]";
|
|
12
|
+
|
|
13
|
+
var SWITCH_TRACK_CLASS = {
|
|
14
|
+
checked: "bg-primary-base",
|
|
15
|
+
unchecked: "bg-fill-primary-fill-dark-gray",
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
var SWITCH_SIZES = {
|
|
19
|
+
default: "h-[22px] w-44 min-w-44",
|
|
20
|
+
small: "h-16 w-28 min-w-28",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
var THUMB_BASE_CLASS =
|
|
24
|
+
"pointer-events-none shadow-soft-large block bg-fill-quarternary-fill-white rounded-128 ring-0 transition-transform duration-200 ease-in-out";
|
|
25
|
+
|
|
26
|
+
var THUMB_SIZES = {
|
|
27
|
+
default: "size-16",
|
|
28
|
+
small: "size-12",
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Thumb translate X (px) so visibility doesn't rely on data-state variants
|
|
32
|
+
var THUMB_TRANSLATE = {
|
|
33
|
+
default: { unchecked: 3, checked: 24 },
|
|
34
|
+
small: { unchecked: 8, checked: 14 },
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
function join() {
|
|
38
|
+
return Array.prototype.filter.call(arguments, Boolean).join(" ");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Create a switch component
|
|
43
|
+
* @param {Object} config
|
|
44
|
+
* @param {string} [config.id] - input id (for form/label association)
|
|
45
|
+
* @param {string} [config.name] - name attribute for the hidden input
|
|
46
|
+
* @param {boolean} [config.checked] - initial checked state
|
|
47
|
+
* @param {boolean} [config.disabled] - disabled state
|
|
48
|
+
* @param {string} [config.size] - 'default' | 'small'
|
|
49
|
+
* @param {string} [config.className] - extra class on root (track)
|
|
50
|
+
* @param {Function} [config.onChange] - change handler (receives checked state)
|
|
51
|
+
* @returns {HTMLElement} root element (track) containing thumb and hidden input
|
|
52
|
+
*/
|
|
53
|
+
function create(config) {
|
|
54
|
+
var opts = config || {};
|
|
55
|
+
var id = opts.id || "switch-" + Math.random().toString(36).substr(2, 9);
|
|
56
|
+
var name = opts.name;
|
|
57
|
+
var checked = !!opts.checked;
|
|
58
|
+
var disabled = !!opts.disabled;
|
|
59
|
+
var size = opts.size || "default";
|
|
60
|
+
var className = opts.className || "";
|
|
61
|
+
var onChange = opts.onChange;
|
|
62
|
+
|
|
63
|
+
var sizeKey = size === "small" ? "small" : "default";
|
|
64
|
+
|
|
65
|
+
// Hidden native checkbox (accessibility + form submission)
|
|
66
|
+
var input = document.createElement("input");
|
|
67
|
+
input.type = "checkbox";
|
|
68
|
+
input.id = id;
|
|
69
|
+
if (name) input.name = name;
|
|
70
|
+
input.checked = checked;
|
|
71
|
+
input.disabled = disabled;
|
|
72
|
+
input.className = "absolute opacity-0 w-0 h-0 peer";
|
|
73
|
+
input.setAttribute("aria-checked", checked ? "true" : "false");
|
|
74
|
+
input.style.position = "absolute";
|
|
75
|
+
input.style.opacity = "0";
|
|
76
|
+
input.style.width = "0";
|
|
77
|
+
input.style.height = "0";
|
|
78
|
+
input.style.pointerEvents = "none";
|
|
79
|
+
input.setAttribute("tabindex", "-1");
|
|
80
|
+
|
|
81
|
+
// Root = track (switch container)
|
|
82
|
+
var root = document.createElement("button");
|
|
83
|
+
root.type = "button";
|
|
84
|
+
root.className = join(
|
|
85
|
+
SWITCH_BASE_CLASS,
|
|
86
|
+
SWITCH_SIZES[sizeKey],
|
|
87
|
+
className
|
|
88
|
+
);
|
|
89
|
+
root.setAttribute("role", "switch");
|
|
90
|
+
root.setAttribute("aria-checked", checked ? "true" : "false");
|
|
91
|
+
root.setAttribute("tabindex", disabled ? "-1" : "0");
|
|
92
|
+
if (disabled) root.setAttribute("aria-disabled", "true");
|
|
93
|
+
|
|
94
|
+
// Thumb
|
|
95
|
+
var thumb = document.createElement("span");
|
|
96
|
+
thumb.className = join(THUMB_BASE_CLASS, THUMB_SIZES[sizeKey]);
|
|
97
|
+
thumb.setAttribute("aria-hidden", "true");
|
|
98
|
+
|
|
99
|
+
root.appendChild(thumb);
|
|
100
|
+
|
|
101
|
+
function updateRootDataState(el, isChecked) {
|
|
102
|
+
el.setAttribute("data-state", isChecked ? "checked" : "unchecked");
|
|
103
|
+
el.setAttribute("aria-checked", isChecked ? "true" : "false");
|
|
104
|
+
el.classList.remove(SWITCH_TRACK_CLASS.checked, SWITCH_TRACK_CLASS.unchecked);
|
|
105
|
+
el.classList.add(isChecked ? SWITCH_TRACK_CLASS.checked : SWITCH_TRACK_CLASS.unchecked);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function updateThumbPosition(el, isChecked) {
|
|
109
|
+
var t = THUMB_TRANSLATE[sizeKey] || THUMB_TRANSLATE.default;
|
|
110
|
+
var x = isChecked ? t.checked : t.unchecked;
|
|
111
|
+
el.style.transform = "translateX(" + x + "px)";
|
|
112
|
+
el.setAttribute("data-state", isChecked ? "checked" : "unchecked");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function updateVisualState() {
|
|
116
|
+
var isChecked = input.checked;
|
|
117
|
+
updateRootDataState(root, isChecked);
|
|
118
|
+
updateThumbPosition(thumb, isChecked);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
updateVisualState();
|
|
122
|
+
|
|
123
|
+
root.addEventListener("click", function (e) {
|
|
124
|
+
e.preventDefault();
|
|
125
|
+
if (disabled) return;
|
|
126
|
+
input.checked = !input.checked;
|
|
127
|
+
updateVisualState();
|
|
128
|
+
if (typeof onChange === "function") onChange(input.checked);
|
|
129
|
+
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
root.addEventListener("keydown", function (e) {
|
|
133
|
+
if (disabled) return;
|
|
134
|
+
if (e.key === " " || e.key === "Enter") {
|
|
135
|
+
e.preventDefault();
|
|
136
|
+
root.click();
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
input.addEventListener("change", function () {
|
|
141
|
+
updateVisualState();
|
|
142
|
+
if (typeof onChange === "function") onChange(input.checked);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Mount: parent gets both (input can be visually hidden next to root or in a form)
|
|
146
|
+
var wrapper = document.createElement("div");
|
|
147
|
+
wrapper.className = "relative inline-flex";
|
|
148
|
+
wrapper.appendChild(input);
|
|
149
|
+
wrapper.appendChild(root);
|
|
150
|
+
|
|
151
|
+
// Public API
|
|
152
|
+
wrapper.getInput = function () {
|
|
153
|
+
return input;
|
|
154
|
+
};
|
|
155
|
+
wrapper.getRoot = function () {
|
|
156
|
+
return root;
|
|
157
|
+
};
|
|
158
|
+
wrapper.setChecked = function (value) {
|
|
159
|
+
input.checked = !!value;
|
|
160
|
+
updateVisualState();
|
|
161
|
+
};
|
|
162
|
+
wrapper.getChecked = function () {
|
|
163
|
+
return input.checked;
|
|
164
|
+
};
|
|
165
|
+
wrapper.setDisabled = function (value) {
|
|
166
|
+
disabled = !!value;
|
|
167
|
+
input.disabled = disabled;
|
|
168
|
+
root.setAttribute("tabindex", disabled ? "-1" : "0");
|
|
169
|
+
if (disabled) root.setAttribute("aria-disabled", "true");
|
|
170
|
+
else root.removeAttribute("aria-disabled");
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
return wrapper;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
var Switch = {
|
|
177
|
+
create: create,
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
if (typeof module !== "undefined" && module.exports) {
|
|
181
|
+
module.exports = Switch;
|
|
182
|
+
} else {
|
|
183
|
+
global.Switch = Switch;
|
|
184
|
+
}
|
|
185
|
+
})(typeof window !== "undefined" ? window : this);
|
package/core/flow.js
CHANGED
|
@@ -1443,6 +1443,55 @@
|
|
|
1443
1443
|
return field;
|
|
1444
1444
|
}
|
|
1445
1445
|
|
|
1446
|
+
/**
|
|
1447
|
+
* Create a switch field with label and optional help text (same pattern as createCheckbox).
|
|
1448
|
+
* Layout: justify-between, items-center. Supports orientation 'horizontal' | 'vertical'.
|
|
1449
|
+
* @param {Object} config - { label, fieldId, checked, disabled, helpText, size, orientation, required, onChange }
|
|
1450
|
+
* @returns {HTMLElement} Field wrapper containing switch
|
|
1451
|
+
*/
|
|
1452
|
+
function createSwitch(config) {
|
|
1453
|
+
const {
|
|
1454
|
+
label,
|
|
1455
|
+
fieldId,
|
|
1456
|
+
checked = false,
|
|
1457
|
+
disabled = false,
|
|
1458
|
+
helpText = null,
|
|
1459
|
+
size = "default",
|
|
1460
|
+
orientation = "horizontal",
|
|
1461
|
+
required = false,
|
|
1462
|
+
onChange,
|
|
1463
|
+
} = config;
|
|
1464
|
+
|
|
1465
|
+
const field = createFieldWrapper(label, required, helpText);
|
|
1466
|
+
field.setAttribute("data-field-id", fieldId);
|
|
1467
|
+
|
|
1468
|
+
if (orientation === "vertical") {
|
|
1469
|
+
field.className = "field flex flex-col gap-2 items-start";
|
|
1470
|
+
} else {
|
|
1471
|
+
field.className = "field flex justify-between items-center";
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
if (getComponent("Switch") && getComponent("Switch").create) {
|
|
1475
|
+
const currentValue = get(fieldId);
|
|
1476
|
+
const switchEl = getComponent("Switch").create({
|
|
1477
|
+
id: fieldId,
|
|
1478
|
+
name: fieldId,
|
|
1479
|
+
checked: currentValue !== undefined ? currentValue : checked,
|
|
1480
|
+
disabled,
|
|
1481
|
+
size,
|
|
1482
|
+
onChange: (isChecked) => {
|
|
1483
|
+
set(fieldId, isChecked);
|
|
1484
|
+
if (onChange) onChange(isChecked);
|
|
1485
|
+
},
|
|
1486
|
+
});
|
|
1487
|
+
switchEl._fieldId = fieldId;
|
|
1488
|
+
field.appendChild(switchEl);
|
|
1489
|
+
return field;
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
return field;
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1446
1495
|
// ============================================================================
|
|
1447
1496
|
// STEPPER COMPONENT
|
|
1448
1497
|
// ============================================================================
|
|
@@ -1867,6 +1916,7 @@
|
|
|
1867
1916
|
createPhoneInput,
|
|
1868
1917
|
createCheckbox,
|
|
1869
1918
|
createCheckboxGroup,
|
|
1919
|
+
createSwitch,
|
|
1870
1920
|
|
|
1871
1921
|
// Button (delegates to Button component when available; resolved at call time via getComponent)
|
|
1872
1922
|
createButton: function (config) {
|