@icen.ai/ui 0.3.0 → 0.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.
- package/dist/behaviors/back-top.mjs +84 -0
- package/dist/behaviors/charts.mjs +188 -0
- package/dist/behaviors/command-palette.mjs +177 -0
- package/dist/behaviors/datatable.mjs +244 -24
- package/dist/behaviors/date-picker.mjs +228 -0
- package/dist/behaviors/input.mjs +170 -1
- package/dist/behaviors/notification.mjs +754 -0
- package/dist/behaviors/select.mjs +151 -22
- package/dist/behaviors/split-pane.mjs +102 -0
- package/dist/behaviors/upload.mjs +165 -14
- package/dist/components/back-top.css +88 -0
- package/dist/components/back-top.mjs +3 -0
- package/dist/components/badge.css +131 -0
- package/dist/components/badge.mjs +2 -0
- package/dist/components/breadcrumb.css +57 -0
- package/dist/components/card.css +68 -0
- package/dist/components/chart-area.css +15 -0
- package/dist/components/chart-area.mjs +4 -0
- package/dist/components/chart-bar.css +79 -0
- package/dist/components/chart-bar.mjs +4 -0
- package/dist/components/chart-gauge.css +38 -0
- package/dist/components/chart-gauge.mjs +4 -0
- package/dist/components/chart-heatmap.css +52 -0
- package/dist/components/chart-heatmap.mjs +4 -0
- package/dist/components/chart-line.css +15 -0
- package/dist/components/chart-line.mjs +4 -0
- package/dist/components/chart-pie.css +26 -0
- package/dist/components/chart-pie.mjs +4 -0
- package/dist/components/chart-radar.css +19 -0
- package/dist/components/chart-radar.mjs +4 -0
- package/dist/components/chart-sparkline.css +14 -0
- package/dist/components/chart-sparkline.mjs +4 -0
- package/dist/components/chart-stack.css +30 -0
- package/dist/components/chart-stack.mjs +4 -0
- package/dist/components/charts.css +7 -207
- package/dist/components/charts.mjs +9 -0
- package/dist/components/command-palette.css +246 -0
- package/dist/components/command-palette.mjs +3 -0
- package/dist/components/content.css +214 -0
- package/dist/components/copy.css +93 -0
- package/dist/components/copy.mjs +3 -0
- package/dist/components/datatable.css +86 -0
- package/dist/components/date-picker.css +244 -0
- package/dist/components/date-picker.mjs +3 -0
- package/dist/components/empty.css +73 -3
- package/dist/components/feedback.css +239 -1
- package/dist/components/form.css +119 -0
- package/dist/components/form.mjs +2 -0
- package/dist/components/input.css +24 -1
- package/dist/components/media.css +144 -1
- package/dist/components/nav.css +99 -0
- package/dist/components/notification.css +387 -0
- package/dist/components/notification.mjs +3 -0
- package/dist/components/panel.css +100 -5
- package/dist/components/pill.css +70 -0
- package/dist/components/scroll-area.css +112 -0
- package/dist/components/scroll-area.mjs +2 -0
- package/dist/components/segmented.css +75 -0
- package/dist/components/select.css +97 -2
- package/dist/components/sidebar.css +118 -0
- package/dist/components/slider.css +113 -0
- package/dist/components/split-pane.css +112 -0
- package/dist/components/split-pane.mjs +3 -0
- package/dist/components/stat.css +129 -6
- package/dist/components/steps.css +97 -0
- package/dist/components/switch.css +142 -0
- package/dist/components/table.css +86 -3
- package/dist/components/tabs.css +122 -1
- package/dist/components/tag.css +164 -0
- package/dist/components/tag.mjs +2 -0
- package/dist/components/toast.css +114 -0
- package/dist/components/toolbar.css +101 -0
- package/dist/components/toolbar.mjs +2 -0
- package/dist/components/upload.css +61 -4
- package/dist/index.mjs +2039 -62
- package/dist/registry.json +200 -24
- package/dist/ui.css +4865 -622
- package/package.json +1 -1
package/dist/behaviors/input.mjs
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
// src/behaviors/input.ts
|
|
2
|
+
var isComposing = false;
|
|
3
|
+
if (typeof document !== "undefined") {
|
|
4
|
+
document.addEventListener("compositionstart", () => {
|
|
5
|
+
isComposing = true;
|
|
6
|
+
});
|
|
7
|
+
document.addEventListener("compositionend", () => {
|
|
8
|
+
isComposing = false;
|
|
9
|
+
});
|
|
10
|
+
}
|
|
2
11
|
function setupClear(wrap) {
|
|
3
12
|
const btn = wrap.querySelector(".input-clear");
|
|
4
|
-
const field = wrap.querySelector("input.input");
|
|
13
|
+
const field = wrap.querySelector("input.input, textarea.textarea");
|
|
5
14
|
if (!btn || !field) return;
|
|
6
15
|
btn.addEventListener("click", () => {
|
|
7
16
|
field.value = "";
|
|
@@ -43,6 +52,140 @@ function setupAutosize(ta) {
|
|
|
43
52
|
ta.addEventListener("input", () => resizeTextarea(ta));
|
|
44
53
|
resizeTextarea(ta);
|
|
45
54
|
}
|
|
55
|
+
function setupCounter(field) {
|
|
56
|
+
const maxAttr = field.getAttribute("maxlength");
|
|
57
|
+
const dataCount = field.getAttribute("data-count");
|
|
58
|
+
if (!maxAttr && dataCount === null) return;
|
|
59
|
+
const max = maxAttr ? Number(maxAttr) : 0;
|
|
60
|
+
let counter = null;
|
|
61
|
+
const wrap = field.closest(".input-wrap, .textarea-wrap");
|
|
62
|
+
if (wrap) {
|
|
63
|
+
counter = wrap.querySelector(".input-counter");
|
|
64
|
+
if (!counter) {
|
|
65
|
+
counter = document.createElement("span");
|
|
66
|
+
counter.className = "input-counter";
|
|
67
|
+
wrap.appendChild(counter);
|
|
68
|
+
if (wrap.classList.contains("input-wrap")) {
|
|
69
|
+
wrap.classList.add("input-wrap--has-counter");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const counterEl = counter;
|
|
74
|
+
if (!counterEl) return;
|
|
75
|
+
function update() {
|
|
76
|
+
const len = field.value.length;
|
|
77
|
+
counterEl.textContent = max > 0 ? `${len} / ${max}` : `${len}`;
|
|
78
|
+
counterEl.classList.remove("is-warn", "is-error");
|
|
79
|
+
if (max > 0) {
|
|
80
|
+
const ratio = len / max;
|
|
81
|
+
if (ratio >= 1) counterEl.classList.add("is-error");
|
|
82
|
+
else if (ratio >= 0.8) counterEl.classList.add("is-warn");
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
field.addEventListener("input", () => {
|
|
86
|
+
if (!isComposing) update();
|
|
87
|
+
});
|
|
88
|
+
update();
|
|
89
|
+
}
|
|
90
|
+
function setupMask(field) {
|
|
91
|
+
const mask = field.getAttribute("data-mask");
|
|
92
|
+
if (!mask) return;
|
|
93
|
+
const maskChars = [];
|
|
94
|
+
for (const ch of mask) maskChars.push(ch);
|
|
95
|
+
function isInputSlot(ch) {
|
|
96
|
+
return ch === "#" || ch === "A" || ch === "*";
|
|
97
|
+
}
|
|
98
|
+
function validate(rawChar, slotType) {
|
|
99
|
+
if (slotType === "#") return /[0-9]/.test(rawChar);
|
|
100
|
+
if (slotType === "A") return /[a-zA-Z]/.test(rawChar);
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
function applyMask(input) {
|
|
104
|
+
let result = "";
|
|
105
|
+
let rawIdx = 0;
|
|
106
|
+
const rawChars = input.split("");
|
|
107
|
+
for (let i = 0; i < maskChars.length && rawIdx < rawChars.length; i++) {
|
|
108
|
+
const slot = maskChars[i];
|
|
109
|
+
if (isInputSlot(slot)) {
|
|
110
|
+
while (rawIdx < rawChars.length && !validate(rawChars[rawIdx], slot)) {
|
|
111
|
+
rawIdx++;
|
|
112
|
+
}
|
|
113
|
+
if (rawIdx < rawChars.length) {
|
|
114
|
+
result += rawChars[rawIdx];
|
|
115
|
+
rawIdx++;
|
|
116
|
+
}
|
|
117
|
+
} else {
|
|
118
|
+
result += slot;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
function extractRaw(formatted) {
|
|
124
|
+
let raw = "";
|
|
125
|
+
for (let i = 0; i < formatted.length && i < maskChars.length; i++) {
|
|
126
|
+
if (isInputSlot(maskChars[i])) raw += formatted[i];
|
|
127
|
+
}
|
|
128
|
+
return raw;
|
|
129
|
+
}
|
|
130
|
+
let lastFormatted = applyMask(field.value);
|
|
131
|
+
field.value = lastFormatted;
|
|
132
|
+
field.setAttribute("data-raw-value", extractRaw(lastFormatted));
|
|
133
|
+
field.addEventListener("input", () => {
|
|
134
|
+
if (isComposing) return;
|
|
135
|
+
const cursorPos = field.selectionStart ?? 0;
|
|
136
|
+
const oldRaw = extractRaw(lastFormatted);
|
|
137
|
+
const newFull = field.value;
|
|
138
|
+
const newRaw = extractRaw(newFull);
|
|
139
|
+
const formatted = applyMask(newRaw);
|
|
140
|
+
field.value = formatted;
|
|
141
|
+
field.setAttribute("data-raw-value", extractRaw(formatted));
|
|
142
|
+
lastFormatted = formatted;
|
|
143
|
+
const rawDelta = newRaw.length - oldRaw.length;
|
|
144
|
+
let targetCursor = cursorPos;
|
|
145
|
+
if (rawDelta > 0) {
|
|
146
|
+
let count = 0;
|
|
147
|
+
for (let i = 0; i < formatted.length; i++) {
|
|
148
|
+
if (isInputSlot(maskChars[i])) {
|
|
149
|
+
count++;
|
|
150
|
+
if (count >= newRaw.length) {
|
|
151
|
+
targetCursor = i + 1;
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
} else if (rawDelta < 0) {
|
|
157
|
+
targetCursor = Math.min(cursorPos, formatted.length);
|
|
158
|
+
}
|
|
159
|
+
field.setSelectionRange(targetCursor, targetCursor);
|
|
160
|
+
});
|
|
161
|
+
field.addEventListener("keydown", (ev) => {
|
|
162
|
+
if (isComposing) return;
|
|
163
|
+
if (ev.ctrlKey || ev.metaKey || ev.altKey) return;
|
|
164
|
+
if (["Backspace", "Delete", "Tab", "Escape", "ArrowLeft", "ArrowRight", "Home", "End", "Enter"].includes(ev.key)) return;
|
|
165
|
+
const currentRaw = extractRaw(field.value);
|
|
166
|
+
const nextSlotIdx = maskChars.findIndex((ch, i) => {
|
|
167
|
+
if (!isInputSlot(ch)) return false;
|
|
168
|
+
return i >= field.value.length || !isInputSlot(maskChars[Math.min(field.value.length, maskChars.length - 1)]);
|
|
169
|
+
});
|
|
170
|
+
const nextSlot = maskChars.find((ch, i) => {
|
|
171
|
+
const filledSlots = maskChars.slice(0, i).filter(isInputSlot).length;
|
|
172
|
+
return isInputSlot(ch) && filledSlots >= currentRaw.length;
|
|
173
|
+
});
|
|
174
|
+
if (nextSlot && !validate(ev.key, nextSlot)) {
|
|
175
|
+
ev.preventDefault();
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
function setupTrim(field) {
|
|
180
|
+
if (!field.hasAttribute("data-trim")) return;
|
|
181
|
+
field.addEventListener("blur", () => {
|
|
182
|
+
const trimmed = field.value.trim();
|
|
183
|
+
if (trimmed !== field.value) {
|
|
184
|
+
field.value = trimmed;
|
|
185
|
+
field.dispatchEvent(new Event("input", { bubbles: true }));
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
}
|
|
46
189
|
function setupOtp(group) {
|
|
47
190
|
const el = group;
|
|
48
191
|
if (el.__icenInputInit) return;
|
|
@@ -57,6 +200,7 @@ function setupOtp(group) {
|
|
|
57
200
|
}
|
|
58
201
|
syncFilled(cell);
|
|
59
202
|
cell.addEventListener("input", () => {
|
|
203
|
+
if (isComposing) return;
|
|
60
204
|
const ch = cell.value.slice(-1);
|
|
61
205
|
cell.value = ch;
|
|
62
206
|
syncFilled(cell);
|
|
@@ -111,6 +255,31 @@ function initInput(root = document) {
|
|
|
111
255
|
if (root instanceof HTMLTextAreaElement && root.matches("[data-autosize]")) textareas.push(root);
|
|
112
256
|
textareas.push(...Array.from(root.querySelectorAll("textarea[data-autosize]")));
|
|
113
257
|
for (const ta of textareas) setupAutosize(ta);
|
|
258
|
+
const countedFields = [];
|
|
259
|
+
if (root instanceof Element) {
|
|
260
|
+
const fields = Array.from(root.querySelectorAll(
|
|
261
|
+
"input[maxlength], input[data-count], textarea[maxlength], textarea[data-count]"
|
|
262
|
+
));
|
|
263
|
+
countedFields.push(...fields);
|
|
264
|
+
if (root instanceof HTMLInputElement || root instanceof HTMLTextAreaElement) {
|
|
265
|
+
if (root.hasAttribute("maxlength") || root.hasAttribute("data-count")) countedFields.push(root);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
for (const f of countedFields) setupCounter(f);
|
|
269
|
+
const maskedInputs = [];
|
|
270
|
+
if (root instanceof Element) {
|
|
271
|
+
maskedInputs.push(...Array.from(root.querySelectorAll("input[data-mask]")));
|
|
272
|
+
if (root instanceof HTMLInputElement && root.hasAttribute("data-mask")) maskedInputs.push(root);
|
|
273
|
+
}
|
|
274
|
+
for (const inp of maskedInputs) setupMask(inp);
|
|
275
|
+
const trimFields = [];
|
|
276
|
+
if (root instanceof Element) {
|
|
277
|
+
trimFields.push(...Array.from(root.querySelectorAll("[data-trim]")));
|
|
278
|
+
if ((root instanceof HTMLInputElement || root instanceof HTMLTextAreaElement) && root.hasAttribute("data-trim")) {
|
|
279
|
+
trimFields.push(root);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
for (const f of trimFields) setupTrim(f);
|
|
114
283
|
const otps = [];
|
|
115
284
|
if (root instanceof Element && root.matches(".otp")) otps.push(root);
|
|
116
285
|
otps.push(...Array.from(root.querySelectorAll(".otp")));
|