@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.
Files changed (78) hide show
  1. package/dist/behaviors/back-top.mjs +84 -0
  2. package/dist/behaviors/charts.mjs +188 -0
  3. package/dist/behaviors/command-palette.mjs +177 -0
  4. package/dist/behaviors/datatable.mjs +244 -24
  5. package/dist/behaviors/date-picker.mjs +228 -0
  6. package/dist/behaviors/input.mjs +170 -1
  7. package/dist/behaviors/notification.mjs +754 -0
  8. package/dist/behaviors/select.mjs +151 -22
  9. package/dist/behaviors/split-pane.mjs +102 -0
  10. package/dist/behaviors/upload.mjs +165 -14
  11. package/dist/components/back-top.css +88 -0
  12. package/dist/components/back-top.mjs +3 -0
  13. package/dist/components/badge.css +131 -0
  14. package/dist/components/badge.mjs +2 -0
  15. package/dist/components/breadcrumb.css +57 -0
  16. package/dist/components/card.css +68 -0
  17. package/dist/components/chart-area.css +15 -0
  18. package/dist/components/chart-area.mjs +4 -0
  19. package/dist/components/chart-bar.css +79 -0
  20. package/dist/components/chart-bar.mjs +4 -0
  21. package/dist/components/chart-gauge.css +38 -0
  22. package/dist/components/chart-gauge.mjs +4 -0
  23. package/dist/components/chart-heatmap.css +52 -0
  24. package/dist/components/chart-heatmap.mjs +4 -0
  25. package/dist/components/chart-line.css +15 -0
  26. package/dist/components/chart-line.mjs +4 -0
  27. package/dist/components/chart-pie.css +26 -0
  28. package/dist/components/chart-pie.mjs +4 -0
  29. package/dist/components/chart-radar.css +19 -0
  30. package/dist/components/chart-radar.mjs +4 -0
  31. package/dist/components/chart-sparkline.css +14 -0
  32. package/dist/components/chart-sparkline.mjs +4 -0
  33. package/dist/components/chart-stack.css +30 -0
  34. package/dist/components/chart-stack.mjs +4 -0
  35. package/dist/components/charts.css +7 -207
  36. package/dist/components/charts.mjs +9 -0
  37. package/dist/components/command-palette.css +246 -0
  38. package/dist/components/command-palette.mjs +3 -0
  39. package/dist/components/content.css +214 -0
  40. package/dist/components/copy.css +93 -0
  41. package/dist/components/copy.mjs +3 -0
  42. package/dist/components/datatable.css +86 -0
  43. package/dist/components/date-picker.css +244 -0
  44. package/dist/components/date-picker.mjs +3 -0
  45. package/dist/components/empty.css +73 -3
  46. package/dist/components/feedback.css +239 -1
  47. package/dist/components/form.css +119 -0
  48. package/dist/components/form.mjs +2 -0
  49. package/dist/components/input.css +24 -1
  50. package/dist/components/media.css +144 -1
  51. package/dist/components/nav.css +99 -0
  52. package/dist/components/notification.css +387 -0
  53. package/dist/components/notification.mjs +3 -0
  54. package/dist/components/panel.css +100 -5
  55. package/dist/components/pill.css +70 -0
  56. package/dist/components/scroll-area.css +112 -0
  57. package/dist/components/scroll-area.mjs +2 -0
  58. package/dist/components/segmented.css +75 -0
  59. package/dist/components/select.css +97 -2
  60. package/dist/components/sidebar.css +118 -0
  61. package/dist/components/slider.css +113 -0
  62. package/dist/components/split-pane.css +112 -0
  63. package/dist/components/split-pane.mjs +3 -0
  64. package/dist/components/stat.css +129 -6
  65. package/dist/components/steps.css +97 -0
  66. package/dist/components/switch.css +142 -0
  67. package/dist/components/table.css +86 -3
  68. package/dist/components/tabs.css +122 -1
  69. package/dist/components/tag.css +164 -0
  70. package/dist/components/tag.mjs +2 -0
  71. package/dist/components/toast.css +114 -0
  72. package/dist/components/toolbar.css +101 -0
  73. package/dist/components/toolbar.mjs +2 -0
  74. package/dist/components/upload.css +61 -4
  75. package/dist/index.mjs +2039 -62
  76. package/dist/registry.json +200 -24
  77. package/dist/ui.css +4865 -622
  78. package/package.json +1 -1
@@ -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")));