@superleapai/flow-ui 2.5.11 → 2.5.13
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 +29 -6
- package/components/record-select.js +28 -5
- 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
|
@@ -113,6 +113,24 @@
|
|
|
113
113
|
return global.Icon;
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Get display name for the object from ObjectType: display_singular_name, display_plural_name, or slug.
|
|
118
|
+
* @param {{ display_singular_name?: string, display_plural_name?: string, slug?: string } | null} objectSchema - optional ObjectType
|
|
119
|
+
* @param {string} slug - objectSlug fallback
|
|
120
|
+
* @param {'singular'|'plural'} kind - which name to return
|
|
121
|
+
* @returns {string}
|
|
122
|
+
*/
|
|
123
|
+
function getObjectDisplayName(objectSchema, slug, kind) {
|
|
124
|
+
var s = objectSchema;
|
|
125
|
+
var str = function (v) {
|
|
126
|
+
return typeof v === "string" && v ? v : "";
|
|
127
|
+
};
|
|
128
|
+
if (kind === "singular") {
|
|
129
|
+
return str(s && s.display_singular_name) || str(s && s.slug) || slug || "";
|
|
130
|
+
}
|
|
131
|
+
return str(s && s.display_plural_name) || str(s && s.display_singular_name) || str(s && s.slug) || slug || "";
|
|
132
|
+
}
|
|
133
|
+
|
|
116
134
|
/**
|
|
117
135
|
* Get iconStr and color for the object: from objectSchema.properties.icon_data first, else OBJECT_SLUG_TO_ICON, else IconDatabase fallback.
|
|
118
136
|
* @param {string} slug - objectSlug
|
|
@@ -155,16 +173,21 @@
|
|
|
155
173
|
* @param {number} [config.initialLimit] - Initial fetch limit (default 50)
|
|
156
174
|
* @param {Array<string>} [config.displayFields] - Fields to display as secondary info (e.g. ["email", "phone"])
|
|
157
175
|
* @param {Object} [config.initialFilter] - Optional filter object to merge with search (e.g. { field: "status", operator: "exact", value: "active" } or { and: [...] })
|
|
158
|
-
* @param {Object} [config.objectSchema] - Optional
|
|
176
|
+
* @param {Object} [config.objectSchema] - Optional ObjectType; display_plural_name/display_singular_name/slug used for placeholder/search/summary label; properties.icon_data { icon?, color? } for static icon (not used for user; user shows Vivid Avatar)
|
|
159
177
|
* @returns {HTMLElement} Record multiselect container element
|
|
160
178
|
*/
|
|
161
179
|
function createRecordMultiSelect(config) {
|
|
162
180
|
var fieldId = config.fieldId;
|
|
163
181
|
var objectSlug = config.objectSlug;
|
|
164
182
|
var objectSchema = config.objectSchema || null;
|
|
165
|
-
var
|
|
166
|
-
var
|
|
167
|
-
|
|
183
|
+
var displayPlural = getObjectDisplayName(objectSchema, objectSlug, "plural");
|
|
184
|
+
var placeholder =
|
|
185
|
+
config.placeholder ||
|
|
186
|
+
(displayPlural ? "Select " + displayPlural : "Select records");
|
|
187
|
+
var searchPlaceholder =
|
|
188
|
+
config.searchPlaceholder ||
|
|
189
|
+
"Search " + (displayPlural || objectSlug || "") + "...";
|
|
190
|
+
var summaryLabel = config.label || (displayPlural || "selected");
|
|
168
191
|
var onValuesChange = config.onValuesChange;
|
|
169
192
|
var variant = config.variant || "default";
|
|
170
193
|
var size = config.size || "default";
|
|
@@ -320,7 +343,7 @@
|
|
|
320
343
|
},
|
|
321
344
|
});
|
|
322
345
|
var inputEl = searchInputWrapper.getInput();
|
|
323
|
-
if (inputEl) inputEl.setAttribute("aria-label", "Search records");
|
|
346
|
+
if (inputEl) inputEl.setAttribute("aria-label", "Search " + (displayPlural || "records"));
|
|
324
347
|
searchInputEl = inputEl;
|
|
325
348
|
searchWrap.appendChild(searchInputWrapper);
|
|
326
349
|
} else {
|
|
@@ -337,7 +360,7 @@
|
|
|
337
360
|
searchInput.className =
|
|
338
361
|
"w-full bg-transparent text-reg-13 text-typography-primary-text placeholder:text-typography-quaternary-text focus:outline-none border-none";
|
|
339
362
|
searchInput.placeholder = searchPlaceholder;
|
|
340
|
-
searchInput.setAttribute("aria-label", "Search records");
|
|
363
|
+
searchInput.setAttribute("aria-label", "Search " + (displayPlural || "records"));
|
|
341
364
|
searchInputEl = searchInput;
|
|
342
365
|
fallbackWrapper.appendChild(searchInput);
|
|
343
366
|
searchWrap.appendChild(fallbackWrapper);
|
|
@@ -121,6 +121,24 @@
|
|
|
121
121
|
return global.Icon;
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Get display name for the object from ObjectType: display_singular_name, display_plural_name, or slug.
|
|
126
|
+
* @param {{ display_singular_name?: string, display_plural_name?: string, slug?: string } | null} objectSchema - optional ObjectType
|
|
127
|
+
* @param {string} slug - objectSlug fallback
|
|
128
|
+
* @param {'singular'|'plural'} kind - which name to return
|
|
129
|
+
* @returns {string}
|
|
130
|
+
*/
|
|
131
|
+
function getObjectDisplayName(objectSchema, slug, kind) {
|
|
132
|
+
var s = objectSchema;
|
|
133
|
+
var str = function (v) {
|
|
134
|
+
return typeof v === "string" && v ? v : "";
|
|
135
|
+
};
|
|
136
|
+
if (kind === "singular") {
|
|
137
|
+
return str(s && s.display_singular_name) || str(s && s.slug) || slug || "";
|
|
138
|
+
}
|
|
139
|
+
return str(s && s.display_plural_name) || str(s && s.display_singular_name) || str(s && s.slug) || slug || "";
|
|
140
|
+
}
|
|
141
|
+
|
|
124
142
|
/**
|
|
125
143
|
* Get iconStr and color for the object: from objectSchema.properties.icon_data first, else OBJECT_SLUG_TO_ICON, else IconDatabase fallback.
|
|
126
144
|
* @param {string} slug - objectSlug
|
|
@@ -174,16 +192,21 @@
|
|
|
174
192
|
* @param {boolean} [config.canClear] - Show clear button when value is set
|
|
175
193
|
* @param {number} [config.initialLimit] - Initial fetch limit (default 50)
|
|
176
194
|
* @param {Object} [config.initialFilter] - Optional filter object to merge with search (e.g. { field: "status", operator: "exact", value: "active" } or { and: [...] })
|
|
177
|
-
* @param {Object} [config.objectSchema] - Optional
|
|
195
|
+
* @param {Object} [config.objectSchema] - Optional ObjectType; display_singular_name/display_plural_name/slug used for placeholder/search text; properties.icon_data { icon?, color? } for static icon (not used for user; user shows Vivid Avatar)
|
|
178
196
|
* @returns {HTMLElement} Record select container element
|
|
179
197
|
*/
|
|
180
198
|
function createRecordSelect(config) {
|
|
181
199
|
var fieldId = config.fieldId;
|
|
182
200
|
var objectSlug = config.objectSlug;
|
|
183
201
|
var objectSchema = config.objectSchema || null;
|
|
184
|
-
var
|
|
202
|
+
var displaySingular = getObjectDisplayName(objectSchema, objectSlug, "singular");
|
|
203
|
+
var displayPlural = getObjectDisplayName(objectSchema, objectSlug, "plural");
|
|
204
|
+
var placeholder =
|
|
205
|
+
config.placeholder ||
|
|
206
|
+
(displaySingular ? "Select a " + displaySingular : "Select a record");
|
|
185
207
|
var searchPlaceholder =
|
|
186
|
-
config.searchPlaceholder ||
|
|
208
|
+
config.searchPlaceholder ||
|
|
209
|
+
"Search " + (displayPlural || objectSlug || "") + "...";
|
|
187
210
|
var onChange = config.onChange;
|
|
188
211
|
var variant = config.variant || "default";
|
|
189
212
|
var size = config.size || "default";
|
|
@@ -317,7 +340,7 @@
|
|
|
317
340
|
},
|
|
318
341
|
});
|
|
319
342
|
var inputEl = searchInputWrapper.getInput();
|
|
320
|
-
if (inputEl) inputEl.setAttribute("aria-label", "Search records");
|
|
343
|
+
if (inputEl) inputEl.setAttribute("aria-label", "Search " + (displayPlural || "records"));
|
|
321
344
|
searchInputEl = inputEl;
|
|
322
345
|
searchWrap.appendChild(searchInputWrapper);
|
|
323
346
|
} else {
|
|
@@ -334,7 +357,7 @@
|
|
|
334
357
|
searchInput.className =
|
|
335
358
|
"w-full bg-transparent text-reg-13 text-typography-primary-text placeholder:text-typography-quaternary-text focus:outline-none border-none";
|
|
336
359
|
searchInput.placeholder = searchPlaceholder;
|
|
337
|
-
searchInput.setAttribute("aria-label", "Search records");
|
|
360
|
+
searchInput.setAttribute("aria-label", "Search " + (displayPlural || "records"));
|
|
338
361
|
searchInputEl = searchInput;
|
|
339
362
|
fallbackWrapper.appendChild(searchInput);
|
|
340
363
|
searchWrap.appendChild(fallbackWrapper);
|
|
@@ -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) {
|