@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,201 @@
1
+ /**
2
+ * Button Component (vanilla JS)
3
+ * Design-system button with variants and sizes via Tailwind.
4
+ * Ref: React Button with cva variants (primary, outline, ghost, link, destructive, dashed, toggle, etc.)
5
+ */
6
+
7
+ (function (global) {
8
+ "use strict";
9
+
10
+ var BASE_CLASS =
11
+ "inline-flex items-center justify-center whitespace-nowrap !text-med-12 transition-colors disabled:pointer-events-none hover:cursor-pointer h-fit";
12
+
13
+ var VARIANTS = {
14
+ primary:
15
+ "shadow-soft-extra-small group bg-primary-base border-1/2 border-primary-base text-typography-invert-text hover:bg-primary-hover active:bg-primary-active disabled:opacity-50",
16
+ outline:
17
+ "shadow-soft-extra-small group bg-fill-quarternary-fill-white border-1/2 border-border-primary text-typography-primary-text hover:bg-fill-tertiary-fill-light-gray active:bg-fill-secondary-fill-gray disabled:opacity-50 disabled:border-fill-secondary-fill-gray",
18
+ ghost:
19
+ "group text-typography-primary-text hover:bg-fill-tertiary-fill-light-gray active:bg-fill-secondary-fill-gray disabled:text-typography-quaternary-text",
20
+ link: "group text-primary-text-base hover:bg-primary-surface active:bg-primary-surface-hover disabled:text-typography-quaternary-text",
21
+ primaryDestructive:
22
+ "shadow-soft-extra-small group bg-error-base text-typography-invert-text border-1/2 border-error-base hover:bg-error-hover active:bg-error-active disabled:opacity-50",
23
+ outlineDestructive:
24
+ "shadow-soft-extra-small group bg-fill-quarternary-fill-white border-1/2 border-error-border text-error-text-base hover:bg-error-surface active:bg-error-surface-hover active:text-error-text-active disabled:opacity-50 disabled:border-fill-secondary-fill-gray disabled:text-typography-quaternary-text",
25
+ ghostDestructive:
26
+ "group text-error-text-base hover:bg-error-surface active:bg-error-surface-hover active:text-error-text-active disabled:opacity-50",
27
+ dashed:
28
+ "group bg-fill-quarternary-fill-white border border-border-primary border-dashed text-typography-secondary-text hover:bg-fill-tertiary-fill-light-gray active:bg-fill-secondary-fill-gray disabled:opacity-50 disabled:border-fill-secondary-fill-gray",
29
+ toggleOff:
30
+ "shadow-soft-extra-small group bg-fill-quarternary-fill-white border-1/2 border-border-primary text-typography-primary-text hover:bg-primary-surface active:bg-primary-surface disabled:opacity-50 disabled:border-fill-secondary-fill-gray",
31
+ toggleOn:
32
+ "shadow-soft-extra-small group bg-primary-surface border-1/2 border-border-primary text-primary-text-base hover:bg-primary-surface active:bg-primary-surface disabled:opacity-50 disabled:border-fill-secondary-fill-gray",
33
+ ghostInline:
34
+ "group text-typography-primary-text disabled:text-typography-quaternary-text hover:bg-transparent active:bg-transparent !px-0 hover:text-typography-secondary-text",
35
+ linkInline:
36
+ "group text-primary-text-base hover:bg-transparent active:bg-transparent disabled:text-typography-quaternary-text !px-0 hover:text-primary-text-active",
37
+ };
38
+
39
+ var SIZES = {
40
+ small: "px-8 py-4 gap-4 rounded-4",
41
+ medium: "px-12 py-6 gap-4 rounded-4",
42
+ default: "px-8 py-4 gap-4 rounded-4",
43
+ large: "px-16 py-8 gap-4 rounded-4",
44
+ };
45
+
46
+ var SHADOW_CLASS = "shadow-soft-extra-small";
47
+
48
+ // Icon-only button variants (same visual, different size treatment)
49
+ var ICON_SIZES = {
50
+ small: "box-content size-16 p-4 rounded-4",
51
+ medium: "box-content size-16 p-6 rounded-4",
52
+ default: "box-content size-16 p-4 rounded-4",
53
+ large: "box-content size-16 p-8 rounded-4",
54
+ };
55
+
56
+ var ICON_BASE = "flex items-center justify-center disabled:pointer-events-none hover:cursor-pointer";
57
+
58
+ function join() {
59
+ return Array.prototype.filter.call(arguments, Boolean).join(" ");
60
+ }
61
+
62
+ /**
63
+ * Create a button element
64
+ * @param {Object} config
65
+ * @param {string} [config.variant] - 'primary' | 'outline' | 'ghost' | 'link' | 'primaryDestructive' | 'outlineDestructive' | 'ghostDestructive' | 'dashed' | 'toggleOff' | 'toggleOn' | 'ghostInline' | 'linkInline'
66
+ * @param {string} [config.size] - 'small' | 'medium' | 'default' | 'large'
67
+ * @param {boolean} [config.isShadow] - add extra shadow
68
+ * @param {string} [config.className] - extra Tailwind/classes
69
+ * @param {boolean} [config.disabled]
70
+ * @param {string} [config.type] - 'button' | 'submit' | 'reset'
71
+ * @param {string} [config.label] - accessible label (aria-label)
72
+ * @param {string} [config.text] - button text (same as children for simple case)
73
+ * @param {string} [config.innerHTML] - raw HTML for content (use for icons + text)
74
+ * @param {HTMLElement|string} [config.startIcon] - element or HTML string for left icon
75
+ * @param {HTMLElement|string} [config.endIcon] - element or HTML string for right icon
76
+ * @param {HTMLElement|string} [config.icon] - single icon for icon-only button (no text)
77
+ * @param {function} [config.onClick] - click handler
78
+ * @returns {HTMLButtonElement}
79
+ */
80
+ function create(config) {
81
+ var opts = config || {};
82
+ var variant = opts.variant || "primary";
83
+ var size = opts.size || "default";
84
+ var isShadow = opts.isShadow === true;
85
+ var className = opts.className || "";
86
+ var disabled = opts.disabled === true;
87
+ var type = opts.type || "button";
88
+ var label = opts.label;
89
+ var text = opts.text;
90
+ var innerHTML = opts.innerHTML;
91
+ var startIcon = opts.startIcon;
92
+ var endIcon = opts.endIcon;
93
+ var iconOnly = opts.icon != null;
94
+ var icon = opts.icon;
95
+ var onClick = opts.onClick;
96
+
97
+ var button = document.createElement("button");
98
+ button.type = type;
99
+ if (disabled) {
100
+ button.disabled = true;
101
+ }
102
+ if (label) {
103
+ button.setAttribute("aria-label", label);
104
+ }
105
+
106
+ var variantClass = VARIANTS[variant] != null ? VARIANTS[variant] : VARIANTS.primary;
107
+ var sizeClass = SIZES[size] != null ? SIZES[size] : SIZES.default;
108
+
109
+ if (iconOnly) {
110
+ var iconVariantClass = VARIANTS[variant] != null ? VARIANTS[variant] : VARIANTS.primary;
111
+ var iconSizeClass = ICON_SIZES[size] != null ? ICON_SIZES[size] : ICON_SIZES.default;
112
+ button.className = join(
113
+ BASE_CLASS,
114
+ ICON_BASE,
115
+ iconVariantClass,
116
+ iconSizeClass,
117
+ isShadow ? SHADOW_CLASS : "",
118
+ className
119
+ );
120
+ appendContent(button, icon);
121
+ } else {
122
+ button.className = join(
123
+ BASE_CLASS,
124
+ variantClass,
125
+ sizeClass,
126
+ isShadow ? SHADOW_CLASS : "",
127
+ className
128
+ );
129
+ if (startIcon) {
130
+ var startSpan = document.createElement("span");
131
+ startSpan.className = "box-content flex size-16 items-center justify-center rounded-4 !p-0";
132
+ appendContent(startSpan, startIcon);
133
+ button.appendChild(startSpan);
134
+ }
135
+ if (innerHTML != null) {
136
+ var frag = document.createElement("span");
137
+ frag.innerHTML = innerHTML;
138
+ while (frag.firstChild) {
139
+ button.appendChild(frag.firstChild);
140
+ }
141
+ } else if (text != null) {
142
+ button.appendChild(document.createTextNode(text));
143
+ }
144
+ if (endIcon) {
145
+ var endSpan = document.createElement("span");
146
+ endSpan.className = "box-content flex size-16 items-center justify-center rounded-4 !p-0";
147
+ appendContent(endSpan, endIcon);
148
+ button.appendChild(endSpan);
149
+ }
150
+ }
151
+
152
+ if (typeof onClick === "function") {
153
+ button.addEventListener("click", onClick);
154
+ }
155
+
156
+ return button;
157
+ }
158
+
159
+ function appendContent(el, content) {
160
+ if (content == null) return;
161
+ if (typeof content === "string") {
162
+ el.insertAdjacentHTML("beforeend", content);
163
+ } else if (content instanceof HTMLElement) {
164
+ el.appendChild(content);
165
+ } else if (content instanceof Node) {
166
+ el.appendChild(content);
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Get Tailwind class string for a button (for use with asChild or custom elements)
172
+ * @param {Object} options - { variant, size, isShadow, iconOnly }
173
+ * @returns {string}
174
+ */
175
+ function getButtonClasses(options) {
176
+ var o = options || {};
177
+ var variant = o.variant || "primary";
178
+ var size = o.size || "default";
179
+ var isShadow = o.isShadow === true;
180
+ var iconOnly = o.iconOnly === true;
181
+ var variantClass = VARIANTS[variant] != null ? VARIANTS[variant] : VARIANTS.primary;
182
+ var sizeClass = iconOnly
183
+ ? (ICON_SIZES[size] != null ? ICON_SIZES[size] : ICON_SIZES.default)
184
+ : (SIZES[size] != null ? SIZES[size] : SIZES.default);
185
+ var base = iconOnly ? join(BASE_CLASS, ICON_BASE) : BASE_CLASS;
186
+ return join(base, variantClass, sizeClass, isShadow ? SHADOW_CLASS : "");
187
+ }
188
+
189
+ var Button = {
190
+ create: create,
191
+ getButtonClasses: getButtonClasses,
192
+ VARIANTS: VARIANTS,
193
+ SIZES: SIZES,
194
+ };
195
+
196
+ if (typeof module !== "undefined" && module.exports) {
197
+ module.exports = Button;
198
+ } else {
199
+ global.Button = Button;
200
+ }
201
+ })(typeof window !== "undefined" ? window : this);
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Checkbox Component (vanilla JS)
3
+ * Design-system checkbox with variants, sizes, indeterminate state, and label support.
4
+ * Ref: React Checkbox with Radix UI primitives and cva variants; no React/Radix/cva dependency.
5
+ */
6
+
7
+ (function (global) {
8
+ "use strict";
9
+
10
+ // Inline SVGs (Tabler-style icons for check and minus)
11
+ var ICONS = {
12
+ check:
13
+ '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>',
14
+ minus:
15
+ '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="12" x2="19" y2="12"/></svg>',
16
+ };
17
+
18
+ // Size-based icon variants
19
+ var ICON_SIZES = {
20
+ small: { width: "12", height: "12" },
21
+ default: { width: "14", height: "14" },
22
+ large: { width: "16", height: "16" },
23
+ };
24
+
25
+ var ALIGN_VARIANTS = {
26
+ left: "justify-start",
27
+ center: "justify-center",
28
+ right: "justify-end",
29
+ };
30
+
31
+ var CHECKBOX_SIZES = {
32
+ small: "size-12",
33
+ default: "size-16",
34
+ large: "size-20",
35
+ };
36
+
37
+ var CHECKBOX_BASE_CLASS =
38
+ "flex items-center justify-center rounded-2 border-1/2 border-borderColor-border-primary bg-fill-quarternary-fill-white p-4 transition-all hover:border-primary-base hover:shadow-primary-focused disabled:cursor-not-allowed disabled:border-borderColor-border-primary disabled:opacity-50 disabled:hover:shadow-none";
39
+
40
+ var CHECKBOX_CHECKED_CLASS =
41
+ "data-checked:border-transparent data-checked:bg-primary-base data-checked:hover:border-primary-base data-checked:hover:shadow-primary-focused data-checked:disabled:border-borderColor-border-primary";
42
+
43
+ var LABEL_BASE_CLASS =
44
+ "cursor-pointer pb-0 text-reg-12 leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70";
45
+
46
+ function join() {
47
+ return Array.prototype.filter.call(arguments, Boolean).join(" ");
48
+ }
49
+
50
+ /**
51
+ * Create a checkbox component
52
+ * @param {Object} config
53
+ * @param {string} [config.id] - checkbox input id (also used for label htmlFor)
54
+ * @param {string} [config.name] - name attribute for the checkbox
55
+ * @param {boolean} [config.checked] - initial checked state
56
+ * @param {boolean} [config.indeterminate] - indeterminate state (shows minus icon)
57
+ * @param {boolean} [config.disabled] - disabled state
58
+ * @param {string} [config.label] - label text
59
+ * @param {boolean} [config.isLabelCaps] - capitalize label
60
+ * @param {string} [config.align] - 'left' | 'center' | 'right' (default: 'center')
61
+ * @param {string} [config.size] - 'small' | 'default' | 'large'
62
+ * @param {string} [config.className] - extra class on wrapper
63
+ * @param {Function} [config.onChange] - change handler (receives checked state)
64
+ * @returns {HTMLElement} wrapper element containing checkbox and optional label
65
+ */
66
+ function create(config) {
67
+ var opts = config || {};
68
+ var id = opts.id || "checkbox-" + Math.random().toString(36).substr(2, 9);
69
+ var name = opts.name;
70
+ var checked = !!opts.checked;
71
+ var indeterminate = !!opts.indeterminate;
72
+ var disabled = !!opts.disabled;
73
+ var label = opts.label;
74
+ var isLabelCaps = !!opts.isLabelCaps;
75
+ var align = opts.align || "center";
76
+ var size = opts.size || "default";
77
+ var className = opts.className || "";
78
+ var onChange = opts.onChange;
79
+
80
+ // Wrapper container
81
+ var wrapper = document.createElement("div");
82
+ wrapper.className = join(
83
+ "flex gap-8 items-center",
84
+ ALIGN_VARIANTS[align] || ALIGN_VARIANTS.center,
85
+ className
86
+ );
87
+
88
+ // Hidden native checkbox (for accessibility and form submission)
89
+ var input = document.createElement("input");
90
+ input.type = "checkbox";
91
+ input.id = id;
92
+ if (name) input.name = name;
93
+ input.checked = checked;
94
+ input.disabled = disabled;
95
+ input.className = "absolute opacity-0 w-0 h-0 peer"; // completely hidden
96
+ input.setAttribute("aria-checked", indeterminate ? "mixed" : checked ? "true" : "false");
97
+ input.style.position = "absolute";
98
+ input.style.opacity = "0";
99
+ input.style.width = "0";
100
+ input.style.height = "0";
101
+ input.style.pointerEvents = "none";
102
+
103
+ // Custom checkbox visual
104
+ var checkboxBox = document.createElement("div");
105
+ checkboxBox.className = join(
106
+ CHECKBOX_BASE_CLASS,
107
+ CHECKBOX_CHECKED_CLASS,
108
+ CHECKBOX_SIZES[size] || CHECKBOX_SIZES.default,
109
+ "cursor-pointer"
110
+ );
111
+ checkboxBox.setAttribute("role", "checkbox");
112
+ checkboxBox.setAttribute("tabindex", disabled ? "-1" : "0");
113
+ checkboxBox.setAttribute("aria-checked", indeterminate ? "mixed" : checked ? "true" : "false");
114
+ if (disabled) {
115
+ checkboxBox.setAttribute("aria-disabled", "true");
116
+ }
117
+
118
+ // Indicator (checkmark or minus icon)
119
+ var indicator = document.createElement("span");
120
+ indicator.className = "flex items-center justify-center text-current";
121
+ updateIndicator(indicator, checked, indeterminate, size);
122
+
123
+ checkboxBox.appendChild(indicator);
124
+
125
+ // Update visual state
126
+ function updateCheckedState() {
127
+ var isChecked = input.checked;
128
+ var isIndeterminate = input.indeterminate;
129
+ checkboxBox.setAttribute("aria-checked", isIndeterminate ? "mixed" : isChecked ? "true" : "false");
130
+ input.setAttribute("aria-checked", isIndeterminate ? "mixed" : isChecked ? "true" : "false");
131
+
132
+ if (isChecked || isIndeterminate) {
133
+ checkboxBox.setAttribute("data-checked", "true");
134
+ checkboxBox.classList.add("border-transparent", "bg-primary-base");
135
+ checkboxBox.classList.remove("border-borderColor-border-primary", "bg-fill-quarternary-fill-white");
136
+ } else {
137
+ checkboxBox.removeAttribute("data-checked");
138
+ checkboxBox.classList.remove("border-transparent", "bg-primary-base");
139
+ checkboxBox.classList.add("border-borderColor-border-primary", "bg-fill-quarternary-fill-white");
140
+ }
141
+
142
+ updateIndicator(indicator, isChecked, isIndeterminate, size);
143
+ }
144
+
145
+ // Toggle on click
146
+ checkboxBox.addEventListener("click", function () {
147
+ if (disabled) return;
148
+ input.indeterminate = false;
149
+ input.checked = !input.checked;
150
+ updateCheckedState();
151
+ if (typeof onChange === "function") {
152
+ onChange(input.checked);
153
+ }
154
+ input.dispatchEvent(new Event("change", { bubbles: true }));
155
+ });
156
+
157
+ // Keyboard support
158
+ checkboxBox.addEventListener("keydown", function (e) {
159
+ if (disabled) return;
160
+ if (e.key === " " || e.key === "Enter") {
161
+ e.preventDefault();
162
+ checkboxBox.click();
163
+ }
164
+ });
165
+
166
+ // Sync with native input changes (for programmatic updates)
167
+ input.addEventListener("change", function () {
168
+ updateCheckedState();
169
+ if (typeof onChange === "function") {
170
+ onChange(input.checked);
171
+ }
172
+ });
173
+
174
+ wrapper.appendChild(input);
175
+ wrapper.appendChild(checkboxBox);
176
+
177
+ // Label
178
+ if (label) {
179
+ var labelEl = document.createElement("label");
180
+ labelEl.htmlFor = id;
181
+ labelEl.className = join(
182
+ LABEL_BASE_CLASS,
183
+ isLabelCaps ? "capitalize" : "",
184
+ disabled ? "text-typography-quaternary-text" : ""
185
+ );
186
+ labelEl.textContent = label;
187
+ wrapper.appendChild(labelEl);
188
+ }
189
+
190
+ // Set initial visual state
191
+ if (indeterminate) {
192
+ input.indeterminate = true;
193
+ }
194
+ updateCheckedState();
195
+
196
+ // Public API
197
+ wrapper.getInput = function () {
198
+ return input;
199
+ };
200
+ wrapper.setChecked = function (value) {
201
+ input.indeterminate = false;
202
+ input.checked = !!value;
203
+ updateCheckedState();
204
+ };
205
+ wrapper.getChecked = function () {
206
+ return input.checked;
207
+ };
208
+ wrapper.setIndeterminate = function (value) {
209
+ input.indeterminate = !!value;
210
+ if (value) {
211
+ input.checked = false; // typically indeterminate means partially checked, not fully checked
212
+ }
213
+ updateCheckedState();
214
+ };
215
+ wrapper.getIndeterminate = function () {
216
+ return input.indeterminate;
217
+ };
218
+ wrapper.setDisabled = function (value) {
219
+ disabled = !!value;
220
+ input.disabled = disabled;
221
+ checkboxBox.setAttribute("tabindex", disabled ? "-1" : "0");
222
+ if (disabled) {
223
+ checkboxBox.setAttribute("aria-disabled", "true");
224
+ } else {
225
+ checkboxBox.removeAttribute("aria-disabled");
226
+ }
227
+ updateCheckedState();
228
+ };
229
+
230
+ return wrapper;
231
+ }
232
+
233
+ function updateIndicator(indicator, checked, indeterminate, size) {
234
+ indicator.innerHTML = "";
235
+ if (checked || indeterminate) {
236
+ var iconSize = ICON_SIZES[size] || ICON_SIZES.default;
237
+ var iconSVG = indeterminate ? ICONS.minus : ICONS.check;
238
+ // Update SVG with size
239
+ iconSVG = iconSVG.replace(/width="\d+"/, 'width="' + iconSize.width + '"');
240
+ iconSVG = iconSVG.replace(/height="\d+"/, 'height="' + iconSize.height + '"');
241
+ indicator.innerHTML = iconSVG;
242
+ }
243
+ }
244
+
245
+ var Checkbox = {
246
+ create: create,
247
+ };
248
+
249
+ if (typeof module !== "undefined" && module.exports) {
250
+ module.exports = Checkbox;
251
+ } else {
252
+ global.Checkbox = Checkbox;
253
+ }
254
+ })(typeof window !== "undefined" ? window : this);
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Currency Component (vanilla JS)
3
+ * Label (currency type, fit-content) + decimal input. Styles/variants match input.js.
4
+ */
5
+
6
+ (function (global) {
7
+ "use strict";
8
+
9
+ // Match input.js WRAPPER_CLASS for style and variants
10
+ var WRAPPER_CLASS = {
11
+ base:
12
+ "group flex items-stretch border-1/2 border-border-primary rounded-4 text-typography-primary-text gap-x-0 w-full transition-all ease-in-out overflow-hidden",
13
+ default:
14
+ "bg-fill-quarternary-fill-white hover:border-primary-base focus-within:border-primary-base",
15
+ error:
16
+ "border-error-base bg-fill-quarternary-fill-white hover:border-error-base focus-within:border-error-base",
17
+ warning:
18
+ "border-warning-base bg-fill-quarternary-fill-white hover:border-warning-base focus-within:border-warning-base",
19
+ success:
20
+ "border-success-base bg-fill-quarternary-fill-white hover:border-success-base focus-within:border-success-base",
21
+ borderless:
22
+ "border-none shadow-none rounded-0 bg-fill-quarternary-fill-white",
23
+ inline:
24
+ "border-transparent shadow-none rounded-0 bg-fill-quarternary-fill-white hover:bg-fill-tertiary-fill-light-gray focus-within:border-transparent focus:bg-fill-tertiary-fill-light-gray focus-within:bg-fill-tertiary-fill-light-gray",
25
+ sizeDefault: "",
26
+ sizeLarge: "",
27
+ sizeSmall: "",
28
+ disabled:
29
+ "cursor-not-allowed border-border-primary bg-fill-tertiary-fill-light-gray text-typography-quaternary-text hover:border-border-primary",
30
+ };
31
+
32
+ // Currency type label: fit-content, separator (border-r) on type only, full height
33
+ var LABEL_CLASS =
34
+ "flex shrink-0 w-fit items-center justify-center border-r-1/2 border-border-primary bg-fill-tertiary-fill-light-gray text-typography-primary-text px-12 text-reg-12";
35
+ var LABEL_SIZE = {
36
+ default: "py-6",
37
+ large: "py-8",
38
+ small: "py-4",
39
+ };
40
+ var INPUT_WRAPPER_CLASS = "flex items-center flex-1 min-w-0 border-0";
41
+ // Input: no separator/border (separator is on type only)
42
+ var INPUT_CLASS = {
43
+ base:
44
+ "w-full min-w-0 border-0 bg-inherit text-start outline-none placeholder:text-typography-quaternary-text focus:bg-inherit hover:bg-inherit focus-visible:outline-none disabled:cursor-not-allowed disabled:text-typography-quaternary-text file:rounded-4 file:border-none file:text-typography-primary-text file:shadow-none file:outline-none !text-reg-13 px-12",
45
+ inline:
46
+ "transition-all focus:bg-fill-tertiary-fill-light-gray group-hover:bg-fill-tertiary-fill-light-gray",
47
+ sizeDefault: "py-6",
48
+ sizeLarge: "py-8",
49
+ sizeSmall: "py-4",
50
+ };
51
+
52
+ var DECIMAL_PLACES = 2;
53
+ var DECIMAL_REGEX = /^-?\d*\.?\d{0,2}$/;
54
+
55
+ function join() {
56
+ return Array.prototype.filter.call(arguments, Boolean).join(" ");
57
+ }
58
+
59
+ /**
60
+ * Parse string to number or null (2 decimal places).
61
+ * @param {string} str
62
+ * @returns {number|null}
63
+ */
64
+ function parseDecimal(str) {
65
+ if (str === "" || str == null) return null;
66
+ var trimmed = String(str).trim();
67
+ if (trimmed === "") return null;
68
+ var n = parseFloat(trimmed);
69
+ return isNaN(n) ? null : Math.round(n * 100) / 100;
70
+ }
71
+
72
+ /**
73
+ * Format number for display (2 decimal places).
74
+ * @param {number|null} num
75
+ * @returns {string}
76
+ */
77
+ function formatDecimal(num) {
78
+ if (num == null || isNaN(num)) return "";
79
+ return Number(num).toFixed(DECIMAL_PLACES);
80
+ }
81
+
82
+ /**
83
+ * Restrict input to decimal with max 2 decimal places.
84
+ * @param {HTMLInputElement} input
85
+ */
86
+ function restrictDecimalInput(input) {
87
+ input.addEventListener("input", function () {
88
+ var val = input.value;
89
+ if (val === "" || val === "-") return;
90
+ if (!DECIMAL_REGEX.test(val)) {
91
+ var neg = val.charAt(0) === "-" ? "-" : "";
92
+ var rest = neg ? val.slice(1) : val;
93
+ var dotIdx = rest.indexOf(".");
94
+ if (dotIdx === -1) {
95
+ input.value = neg + rest.replace(/\D/g, "");
96
+ } else {
97
+ var before = rest.slice(0, dotIdx).replace(/\D/g, "");
98
+ var after = rest.slice(dotIdx + 1).replace(/\D/g, "").slice(0, DECIMAL_PLACES);
99
+ input.value = neg + (after.length ? before + "." + after : before);
100
+ }
101
+ }
102
+ });
103
+ }
104
+
105
+ /**
106
+ * Create a currency field (currency type label fit-content + decimal input).
107
+ * Variants/sizes match input.js.
108
+ * @param {Object} config
109
+ * @param {string} [config.variant] - 'default' | 'error' | 'warning' | 'success' | 'borderless' | 'inline'
110
+ * @param {string} [config.size] - 'small' | 'default' | 'large' (maps to inputSize)
111
+ * @param {string} [config.placeholder]
112
+ * @param {boolean} [config.disabled]
113
+ * @param {string} [config.className]
114
+ * @param {Object} [config.column] - { properties?: { currency?: { currency?: string } }, placeholder?: string }
115
+ * @param {Function} [config.onChange] - (v: number | null) => void
116
+ * @param {number|null} [config.value]
117
+ * @param {boolean} [config.autoFocus]
118
+ * @param {Function} [config.onBlur]
119
+ * @param {string} [config.rootClassName]
120
+ * @returns {HTMLElement} Wrapper (label + input)
121
+ */
122
+ function create(config) {
123
+ var variant = config.variant || "default";
124
+ var size = config.size || "default";
125
+ var disabled = !!config.disabled;
126
+ var className = config.className || "";
127
+ var column = config.column || {};
128
+ var onChange = config.onChange;
129
+ var value = config.value != null ? config.value : null;
130
+ var autoFocus = !!config.autoFocus;
131
+ var onBlur = config.onBlur;
132
+ var placeholder =
133
+ config.placeholder != null
134
+ ? config.placeholder
135
+ : (column.placeholder || "Enter value");
136
+ var rootClassName = config.rootClassName || "";
137
+
138
+ var currencyLabel =
139
+ (column.properties && column.properties.currency && column.properties.currency.currency) ||
140
+ "Currency";
141
+
142
+ var sizeClass = size === "large" ? "large" : size === "small" ? "small" : "default";
143
+
144
+ var wrapper = document.createElement("div");
145
+ wrapper.className = join(
146
+ WRAPPER_CLASS.base,
147
+ WRAPPER_CLASS[variant] != null ? WRAPPER_CLASS[variant] : WRAPPER_CLASS.default,
148
+ disabled ? WRAPPER_CLASS.disabled : "",
149
+ className
150
+ );
151
+ wrapper.setAttribute("data-currency-variant", variant);
152
+
153
+ var labelEl = document.createElement("div");
154
+ labelEl.className = join(LABEL_CLASS, LABEL_SIZE[sizeClass]);
155
+ labelEl.textContent = currencyLabel;
156
+ wrapper.appendChild(labelEl);
157
+
158
+ var inputWrap = document.createElement("div");
159
+ inputWrap.className = INPUT_WRAPPER_CLASS;
160
+
161
+ var input = document.createElement("input");
162
+ input.type = "text";
163
+ input.inputMode = "decimal";
164
+ input.autocomplete = "off";
165
+ input.placeholder = placeholder;
166
+ input.value = value != null ? formatDecimal(value) : "";
167
+ input.disabled = disabled;
168
+ input.className = join(
169
+ INPUT_CLASS.base,
170
+ variant === "inline" ? INPUT_CLASS.inline : "",
171
+ INPUT_CLASS[sizeClass === "large" ? "sizeLarge" : sizeClass === "small" ? "sizeSmall" : "sizeDefault"],
172
+ rootClassName
173
+ );
174
+ if (autoFocus) input.autofocus = true;
175
+
176
+ restrictDecimalInput(input);
177
+
178
+ function notifyChange() {
179
+ var parsed = parseDecimal(input.value);
180
+ if (onChange) onChange(parsed);
181
+ }
182
+
183
+ input.addEventListener("input", notifyChange);
184
+ input.addEventListener("change", notifyChange);
185
+ if (onBlur) input.addEventListener("blur", onBlur);
186
+
187
+ inputWrap.appendChild(input);
188
+ wrapper.appendChild(inputWrap);
189
+
190
+ wrapper.getInput = function () {
191
+ return input;
192
+ };
193
+ wrapper.setValue = function (v) {
194
+ if (v == null || v === "") {
195
+ input.value = "";
196
+ } else {
197
+ input.value = formatDecimal(Number(v));
198
+ }
199
+ };
200
+ wrapper.getValue = function () {
201
+ return parseDecimal(input.value);
202
+ };
203
+ wrapper.setVariant = function (v) {
204
+ variant = v;
205
+ wrapper.setAttribute("data-currency-variant", v);
206
+ wrapper.className = join(
207
+ WRAPPER_CLASS.base,
208
+ WRAPPER_CLASS[variant] != null ? WRAPPER_CLASS[variant] : WRAPPER_CLASS.default,
209
+ disabled ? WRAPPER_CLASS.disabled : "",
210
+ config.className || ""
211
+ );
212
+ };
213
+ wrapper.setDisabled = function (d) {
214
+ disabled = !!d;
215
+ input.disabled = disabled;
216
+ wrapper.classList.toggle("cursor-not-allowed", disabled);
217
+ };
218
+
219
+ return wrapper;
220
+ }
221
+
222
+ global.CurrencyComponent = {
223
+ create: create,
224
+ parseDecimal: parseDecimal,
225
+ formatDecimal: formatDecimal,
226
+ };
227
+ })(typeof window !== "undefined" ? window : this);