@icen.ai/ui 0.5.0 → 0.8.1
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/AGENTS.md +262 -0
- package/README.md +52 -13
- package/dist/base.css +33 -5
- package/dist/behaviors/accordion.d.mts +7 -0
- package/dist/behaviors/accordion.mjs +37 -11
- package/dist/behaviors/ai-chat.d.mts +59 -0
- package/dist/behaviors/ai-chat.mjs +580 -0
- package/dist/behaviors/ai-composer.d.mts +137 -0
- package/dist/behaviors/ai-composer.mjs +2114 -0
- package/dist/behaviors/ai-core.d.mts +156 -0
- package/dist/behaviors/ai-core.mjs +504 -0
- package/dist/behaviors/ai-diff.d.mts +53 -0
- package/dist/behaviors/ai-diff.mjs +413 -0
- package/dist/behaviors/ai-panel.d.mts +101 -0
- package/dist/behaviors/ai-panel.mjs +1087 -0
- package/dist/behaviors/ai-provider.d.mts +198 -0
- package/dist/behaviors/ai-provider.mjs +1061 -0
- package/dist/behaviors/ai-tool.d.mts +26 -0
- package/dist/behaviors/ai-tool.mjs +2173 -0
- package/dist/behaviors/ai-tools.d.mts +84 -0
- package/dist/behaviors/ai-tools.mjs +1762 -0
- package/dist/behaviors/back-top.d.mts +20 -0
- package/dist/behaviors/back-top.mjs +64 -32
- package/dist/behaviors/carousel.d.mts +7 -0
- package/dist/behaviors/carousel.mjs +72 -27
- package/dist/behaviors/charts.d.mts +296 -0
- package/dist/behaviors/charts.mjs +1364 -440
- package/dist/behaviors/command-palette.d.mts +42 -0
- package/dist/behaviors/command-palette.mjs +167 -27
- package/dist/behaviors/context-menu.d.mts +53 -0
- package/dist/behaviors/context-menu.mjs +127 -39
- package/dist/behaviors/controls.d.mts +59 -0
- package/dist/behaviors/controls.mjs +601 -0
- package/dist/behaviors/copy.d.mts +7 -0
- package/dist/behaviors/copy.mjs +41 -15
- package/dist/behaviors/datatable.d.mts +90 -0
- package/dist/behaviors/datatable.mjs +279 -53
- package/dist/behaviors/date-picker.d.mts +4 -0
- package/dist/behaviors/date-picker.mjs +276 -69
- package/dist/behaviors/dropdown.d.mts +8 -0
- package/dist/behaviors/dropdown.mjs +171 -28
- package/dist/behaviors/events.d.mts +330 -0
- package/dist/behaviors/events.mjs +208 -0
- package/dist/behaviors/input.d.mts +4 -0
- package/dist/behaviors/input.mjs +52 -32
- package/dist/behaviors/modal.d.mts +30 -0
- package/dist/behaviors/modal.mjs +87 -16
- package/dist/behaviors/nav.d.mts +7 -0
- package/dist/behaviors/nav.mjs +57 -15
- package/dist/behaviors/notification.d.mts +188 -0
- package/dist/behaviors/notification.mjs +274 -140
- package/dist/behaviors/popover.d.mts +82 -0
- package/dist/behaviors/popover.mjs +92 -5
- package/dist/behaviors/select.d.mts +6 -0
- package/dist/behaviors/select.mjs +287 -45
- package/dist/behaviors/sidebar.d.mts +7 -0
- package/dist/behaviors/sidebar.mjs +44 -13
- package/dist/behaviors/slider.d.mts +7 -0
- package/dist/behaviors/slider.mjs +55 -43
- package/dist/behaviors/split-pane.d.mts +8 -0
- package/dist/behaviors/split-pane.mjs +70 -29
- package/dist/behaviors/tabs.d.mts +9 -0
- package/dist/behaviors/tabs.mjs +102 -26
- package/dist/behaviors/tag-input.d.mts +7 -0
- package/dist/behaviors/tag-input.mjs +66 -17
- package/dist/behaviors/theme.d.mts +36 -0
- package/dist/behaviors/theme.mjs +51 -5
- package/dist/behaviors/toast.d.mts +48 -0
- package/dist/behaviors/toast.mjs +98 -25
- package/dist/behaviors/tree.d.mts +7 -0
- package/dist/behaviors/tree.mjs +39 -15
- package/dist/behaviors/upload.d.mts +4 -0
- package/dist/behaviors/upload.mjs +79 -44
- package/dist/cli.mjs +1 -1
- package/dist/components/ai-chat.css +1037 -0
- package/dist/components/ai-chat.mjs +3 -0
- package/dist/components/ai-composer.mjs +3 -0
- package/dist/components/ai-context.mjs +2 -0
- package/dist/components/ai-diff.css +311 -0
- package/dist/components/ai-diff.mjs +3 -0
- package/dist/components/ai-files.mjs +3 -0
- package/dist/components/ai-message.mjs +3 -0
- package/dist/components/ai-panel.css +682 -0
- package/dist/components/ai-reasoning.mjs +3 -0
- package/dist/components/ai-subagent.mjs +2 -0
- package/dist/components/ai-todo.mjs +2 -0
- package/dist/components/ai-tool-call.mjs +2 -0
- package/dist/components/ai-tool.css +532 -0
- package/dist/components/ai-usage.mjs +3 -0
- package/dist/components/badge.css +5 -9
- package/dist/components/breadcrumb.css +0 -36
- package/dist/components/btn.css +4 -7
- package/dist/components/card.css +3 -6
- package/dist/components/chart-bar.css +10 -0
- package/dist/components/chart-calendar.mjs +4 -0
- package/dist/components/chart-gauge.css +1 -1
- package/dist/components/chart-heatmap.css +40 -0
- package/dist/components/chart-pie.css +0 -6
- package/dist/components/chart-scatter.css +24 -0
- package/dist/components/chart-scatter.mjs +4 -0
- package/dist/components/charts.css +87 -2
- package/dist/components/charts.mjs +1 -0
- package/dist/components/command-palette.css +2 -2
- package/dist/components/content.css +11 -25
- package/dist/components/copy.css +8 -8
- package/dist/components/datatable.css +5 -9
- package/dist/components/date-picker.css +4 -8
- package/dist/components/empty.css +0 -4
- package/dist/components/feedback.css +10 -17
- package/dist/components/form.css +13 -14
- package/dist/components/input.css +2 -2
- package/dist/components/layout.css +36 -36
- package/dist/components/media.css +12 -8
- package/dist/components/menu.css +2 -2
- package/dist/components/modal.css +1 -1
- package/dist/components/nav.css +11 -6
- package/dist/components/notification.css +27 -28
- package/dist/components/pagination.css +4 -2
- package/dist/components/pagination.mjs +1 -0
- package/dist/components/panel.css +16 -16
- package/dist/components/pill.css +15 -15
- package/dist/components/rating.mjs +1 -0
- package/dist/components/segmented.css +14 -6
- package/dist/components/segmented.mjs +1 -0
- package/dist/components/select.css +79 -24
- package/dist/components/sidebar.css +1 -1
- package/dist/components/split-pane.css +0 -1
- package/dist/components/stat.css +14 -18
- package/dist/components/steps.css +15 -5
- package/dist/components/steps.mjs +1 -0
- package/dist/components/switch.css +11 -13
- package/dist/components/switch.mjs +1 -0
- package/dist/components/table.css +46 -47
- package/dist/components/table.mjs +1 -0
- package/dist/components/tabs.css +57 -45
- package/dist/components/tag-input.css +5 -10
- package/dist/components/toast.css +16 -13
- package/dist/components/tooltip.css +2 -2
- package/dist/components/upload.css +0 -15
- package/dist/index.d.mts +39 -0
- package/dist/index.mjs +11243 -3312
- package/dist/registry.json +147 -9
- package/dist/retro-effects.css +23 -32
- package/dist/tokens/colors.css +4 -1
- package/dist/tokens/retro-effects.css +23 -32
- package/dist/tokens/style-profiles.css +24 -2
- package/dist/tokens/typography.css +2 -0
- package/dist/tokens.css +30 -3
- package/dist/ui.css +3420 -645
- package/docs/spec/ai-native.md +662 -0
- package/package.json +15 -5
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export { initInput } from './input.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 接线开关 / 勾选框 / 单选(document 级委托,页面级一次即可,重复调用幂等)。
|
|
5
|
+
* 点击 / Space / Enter 驱动 aria-checked(checkbox 三态循环含初始半选例外;
|
|
6
|
+
* radio 组内互斥),派 icen:switch-change / icen:check-change。
|
|
7
|
+
* 返回销毁函数:摘除委托并复位幂等标记,销毁后可重新 initSwitch。SSR 下返回 no-op。
|
|
8
|
+
*/
|
|
9
|
+
declare const initSwitch: () => void;
|
|
10
|
+
/**
|
|
11
|
+
* 接线数字步进器(document 级委托,重复调用幂等)。
|
|
12
|
+
* .stepper 内 .stepper-btn[data-step] 点击 → .stepper-input 数值 ±step 并 clamp 到
|
|
13
|
+
* min/max,派 icen:stepper-change { el: .stepper 根, value, delta }(触界 delta 为 0)。
|
|
14
|
+
* 返回销毁函数:摘除委托并复位幂等标记,销毁后可重新 initStepper。SSR 下返回 no-op。
|
|
15
|
+
*/
|
|
16
|
+
declare const initStepper: () => void;
|
|
17
|
+
/**
|
|
18
|
+
* 接线分段控件(document 级委托,重复调用幂等)。
|
|
19
|
+
* .segmented 内 .segmented-item 点击 → 组内 .is-active 互斥 + role="radio" 项同步
|
|
20
|
+
* aria-checked,派 icen:segmented-change { el: 容器, value: data-value ?? String(index), index }。
|
|
21
|
+
* 返回销毁函数:摘除委托并复位幂等标记,销毁后可重新 initSegmented。SSR 下返回 no-op。
|
|
22
|
+
*/
|
|
23
|
+
declare const initSegmented: () => void;
|
|
24
|
+
/**
|
|
25
|
+
* 接线可点击步骤条(document 级委托,重复调用幂等)。
|
|
26
|
+
* .steps 内 .step[role="button"] 点击(或聚焦后 Space / Enter)→ 激活点位移
|
|
27
|
+
* (目标 step--process + .is-active,之前 finish、之后 wait),派
|
|
28
|
+
* icen:step-change { step, index }。返回销毁函数:摘除委托并复位幂等标记,
|
|
29
|
+
* 销毁后可重新 initSteps。SSR 下返回 no-op。
|
|
30
|
+
*/
|
|
31
|
+
declare const initSteps: () => void;
|
|
32
|
+
/**
|
|
33
|
+
* 接线星级评分(document 级委托,重复调用幂等)。
|
|
34
|
+
* .rating 内 .rating-star 点击 → 该星及之前 .is-filled(重建选中态,清除残留
|
|
35
|
+
* is-half),.rating-value 存在时回写数值(textContent),派
|
|
36
|
+
* icen:rating-change { el: 容器, value }(1 起算);.rating--readonly 不接线。
|
|
37
|
+
* 返回销毁函数:摘除委托并复位幂等标记,销毁后可重新 initRating。SSR 下返回 no-op。
|
|
38
|
+
*/
|
|
39
|
+
declare const initRating: () => void;
|
|
40
|
+
/**
|
|
41
|
+
* 接线分页(document 级委托,重复调用幂等)。
|
|
42
|
+
* .pagination 内 .page-btn 点击 → .is-active / aria-current 位移并派
|
|
43
|
+
* icen:page-change { page };页码取 data-page > 按钮文本数字 > 序号,上下页按钮按
|
|
44
|
+
* aria-label / data-dir 推断方向(当前页 ±1,目标钮被省略号折叠时只派事件)。
|
|
45
|
+
* 总页数与省略号折叠策略归消费方。返回销毁函数:摘除委托并复位幂等标记,
|
|
46
|
+
* 销毁后可重新 initPagination。SSR 下返回 no-op。
|
|
47
|
+
*/
|
|
48
|
+
declare const initPagination: () => void;
|
|
49
|
+
/**
|
|
50
|
+
* 接线静态表排序(document 级委托,重复调用幂等)。
|
|
51
|
+
* th[data-sort] 点击 → asc→desc→无 循环(data-sort 属性即方向载体,列 key 首次
|
|
52
|
+
* 交互转存 data-sort-key,中性态还原;同表其他表头复位),派
|
|
53
|
+
* icen:sort-change { th, key, dir }。行数据重排归消费方( datatable 场景用
|
|
54
|
+
* behaviors/datatable 的 icen:table-sort-change)。返回销毁函数:摘除委托并
|
|
55
|
+
* 复位幂等标记,销毁后可重新 initTableSort。SSR 下返回 no-op。
|
|
56
|
+
*/
|
|
57
|
+
declare const initTableSort: () => void;
|
|
58
|
+
|
|
59
|
+
export { initPagination, initRating, initSegmented, initStepper, initSteps, initSwitch, initTableSort };
|
|
@@ -0,0 +1,601 @@
|
|
|
1
|
+
// src/behaviors/events.ts
|
|
2
|
+
function emitIcen(source, type, detail) {
|
|
3
|
+
return source.dispatchEvent(
|
|
4
|
+
new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable: true })
|
|
5
|
+
);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// src/behaviors/input.ts
|
|
9
|
+
function trackComposing(target) {
|
|
10
|
+
let composing = false;
|
|
11
|
+
target.addEventListener("compositionstart", () => {
|
|
12
|
+
composing = true;
|
|
13
|
+
});
|
|
14
|
+
target.addEventListener("compositionend", () => {
|
|
15
|
+
composing = false;
|
|
16
|
+
});
|
|
17
|
+
return () => composing;
|
|
18
|
+
}
|
|
19
|
+
function setupClear(wrap) {
|
|
20
|
+
const btn = wrap.querySelector(".input-clear");
|
|
21
|
+
const field = wrap.querySelector("input.input, textarea.textarea");
|
|
22
|
+
if (!btn || !field) return;
|
|
23
|
+
btn.addEventListener("click", () => {
|
|
24
|
+
field.value = "";
|
|
25
|
+
field.dispatchEvent(new Event("input", { bubbles: true }));
|
|
26
|
+
field.focus();
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function setupPasswordToggle(wrap) {
|
|
30
|
+
const btn = wrap.querySelector(".input-pw-toggle");
|
|
31
|
+
const field = wrap.querySelector("input.input");
|
|
32
|
+
if (!btn || !field) return;
|
|
33
|
+
const iconShow = btn.querySelector('[data-icon="show"]');
|
|
34
|
+
const iconHide = btn.querySelector('[data-icon="hide"]');
|
|
35
|
+
btn.setAttribute("aria-label", "\u663E\u793A\u5BC6\u7801");
|
|
36
|
+
btn.addEventListener("click", () => {
|
|
37
|
+
const show = field.type === "password";
|
|
38
|
+
field.type = show ? "text" : "password";
|
|
39
|
+
if (iconShow) iconShow.hidden = show;
|
|
40
|
+
if (iconHide) iconHide.hidden = !show;
|
|
41
|
+
btn.setAttribute("aria-label", show ? "\u9690\u85CF\u5BC6\u7801" : "\u663E\u793A\u5BC6\u7801");
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function resizeTextarea(ta) {
|
|
45
|
+
ta.style.height = "auto";
|
|
46
|
+
const cs = getComputedStyle(ta);
|
|
47
|
+
const lineHeight = parseFloat(cs.lineHeight) || 20;
|
|
48
|
+
const padY = (parseFloat(cs.paddingTop) || 0) + (parseFloat(cs.paddingBottom) || 0);
|
|
49
|
+
const minRows = Number(ta.getAttribute("data-min-rows") ?? "3") || 3;
|
|
50
|
+
const maxAttr = ta.getAttribute("data-max-rows");
|
|
51
|
+
const maxRows = maxAttr ? Number(maxAttr) || 0 : 0;
|
|
52
|
+
const minH = minRows * lineHeight + padY;
|
|
53
|
+
const maxH = maxRows > 0 ? maxRows * lineHeight + padY : Infinity;
|
|
54
|
+
ta.style.height = `${Math.max(minH, Math.min(ta.scrollHeight, maxH))}px`;
|
|
55
|
+
}
|
|
56
|
+
function setupAutosize(ta) {
|
|
57
|
+
const el = ta;
|
|
58
|
+
if (el.__icenInputInit) return;
|
|
59
|
+
el.__icenInputInit = true;
|
|
60
|
+
ta.addEventListener("input", () => resizeTextarea(ta));
|
|
61
|
+
resizeTextarea(ta);
|
|
62
|
+
}
|
|
63
|
+
function setupCounter(field) {
|
|
64
|
+
const el = field;
|
|
65
|
+
if (el.__icenCounterInit) return;
|
|
66
|
+
const maxAttr = field.getAttribute("maxlength");
|
|
67
|
+
const dataCount = field.getAttribute("data-count");
|
|
68
|
+
if (!maxAttr && dataCount === null) return;
|
|
69
|
+
const max = maxAttr ? Number(maxAttr) : 0;
|
|
70
|
+
let counter = null;
|
|
71
|
+
const wrap = field.closest(".input-wrap, .textarea-wrap");
|
|
72
|
+
if (wrap) {
|
|
73
|
+
counter = wrap.querySelector(".input-counter");
|
|
74
|
+
if (!counter) {
|
|
75
|
+
counter = document.createElement("span");
|
|
76
|
+
counter.className = "input-counter";
|
|
77
|
+
wrap.appendChild(counter);
|
|
78
|
+
if (wrap.classList.contains("input-wrap")) {
|
|
79
|
+
wrap.classList.add("input-wrap--has-counter");
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const counterEl = counter;
|
|
84
|
+
if (!counterEl) return;
|
|
85
|
+
el.__icenCounterInit = true;
|
|
86
|
+
const isComposing = trackComposing(field);
|
|
87
|
+
function update() {
|
|
88
|
+
const len = field.value.length;
|
|
89
|
+
counterEl.textContent = max > 0 ? `${len} / ${max}` : `${len}`;
|
|
90
|
+
counterEl.classList.remove("is-warn", "is-error");
|
|
91
|
+
if (max > 0) {
|
|
92
|
+
const ratio = len / max;
|
|
93
|
+
if (ratio >= 1) counterEl.classList.add("is-error");
|
|
94
|
+
else if (ratio >= 0.8) counterEl.classList.add("is-warn");
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
;
|
|
98
|
+
field.addEventListener("input", () => {
|
|
99
|
+
if (!isComposing()) update();
|
|
100
|
+
});
|
|
101
|
+
update();
|
|
102
|
+
}
|
|
103
|
+
function setupMask(field) {
|
|
104
|
+
const el = field;
|
|
105
|
+
if (el.__icenMaskInit) return;
|
|
106
|
+
const mask = field.getAttribute("data-mask");
|
|
107
|
+
if (!mask) return;
|
|
108
|
+
el.__icenMaskInit = true;
|
|
109
|
+
const isComposing = trackComposing(field);
|
|
110
|
+
const maskChars = [];
|
|
111
|
+
for (const ch of mask) maskChars.push(ch);
|
|
112
|
+
function isInputSlot(ch) {
|
|
113
|
+
return ch === "#" || ch === "A" || ch === "*";
|
|
114
|
+
}
|
|
115
|
+
function validate(rawChar, slotType) {
|
|
116
|
+
if (slotType === "#") return /[0-9]/.test(rawChar);
|
|
117
|
+
if (slotType === "A") return /[a-zA-Z]/.test(rawChar);
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
function applyMask(input) {
|
|
121
|
+
let result = "";
|
|
122
|
+
let rawIdx = 0;
|
|
123
|
+
const rawChars = input.split("");
|
|
124
|
+
for (let i = 0; i < maskChars.length && rawIdx < rawChars.length; i++) {
|
|
125
|
+
const slot = maskChars[i];
|
|
126
|
+
if (isInputSlot(slot)) {
|
|
127
|
+
while (rawIdx < rawChars.length && !validate(rawChars[rawIdx], slot)) {
|
|
128
|
+
rawIdx++;
|
|
129
|
+
}
|
|
130
|
+
if (rawIdx < rawChars.length) {
|
|
131
|
+
result += rawChars[rawIdx];
|
|
132
|
+
rawIdx++;
|
|
133
|
+
}
|
|
134
|
+
} else {
|
|
135
|
+
result += slot;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
140
|
+
function extractRaw(formatted) {
|
|
141
|
+
let raw = "";
|
|
142
|
+
for (let i = 0; i < formatted.length && i < maskChars.length; i++) {
|
|
143
|
+
if (isInputSlot(maskChars[i])) raw += formatted[i];
|
|
144
|
+
}
|
|
145
|
+
return raw;
|
|
146
|
+
}
|
|
147
|
+
let lastFormatted = applyMask(field.value);
|
|
148
|
+
field.value = lastFormatted;
|
|
149
|
+
field.setAttribute("data-raw-value", extractRaw(lastFormatted));
|
|
150
|
+
field.addEventListener("input", () => {
|
|
151
|
+
if (isComposing()) return;
|
|
152
|
+
const cursorPos = field.selectionStart ?? 0;
|
|
153
|
+
const oldRaw = extractRaw(lastFormatted);
|
|
154
|
+
const newFull = field.value;
|
|
155
|
+
const newRaw = extractRaw(newFull);
|
|
156
|
+
const formatted = applyMask(newRaw);
|
|
157
|
+
field.value = formatted;
|
|
158
|
+
field.setAttribute("data-raw-value", extractRaw(formatted));
|
|
159
|
+
lastFormatted = formatted;
|
|
160
|
+
const rawDelta = newRaw.length - oldRaw.length;
|
|
161
|
+
let targetCursor = cursorPos;
|
|
162
|
+
if (rawDelta > 0) {
|
|
163
|
+
let count = 0;
|
|
164
|
+
for (let i = 0; i < formatted.length; i++) {
|
|
165
|
+
if (isInputSlot(maskChars[i])) {
|
|
166
|
+
count++;
|
|
167
|
+
if (count >= newRaw.length) {
|
|
168
|
+
targetCursor = i + 1;
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
} else if (rawDelta < 0) {
|
|
174
|
+
targetCursor = Math.min(cursorPos, formatted.length);
|
|
175
|
+
}
|
|
176
|
+
field.setSelectionRange(targetCursor, targetCursor);
|
|
177
|
+
});
|
|
178
|
+
field.addEventListener("keydown", (ev) => {
|
|
179
|
+
if (isComposing()) return;
|
|
180
|
+
if (ev.ctrlKey || ev.metaKey || ev.altKey) return;
|
|
181
|
+
if (["Backspace", "Delete", "Tab", "Escape", "ArrowLeft", "ArrowRight", "Home", "End", "Enter"].includes(ev.key)) return;
|
|
182
|
+
const currentRaw = extractRaw(field.value);
|
|
183
|
+
const nextSlot = maskChars.find((ch, i) => {
|
|
184
|
+
const filledSlots = maskChars.slice(0, i).filter(isInputSlot).length;
|
|
185
|
+
return isInputSlot(ch) && filledSlots >= currentRaw.length;
|
|
186
|
+
});
|
|
187
|
+
if (nextSlot && !validate(ev.key, nextSlot)) {
|
|
188
|
+
ev.preventDefault();
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
function setupTrim(field) {
|
|
193
|
+
if (!field.hasAttribute("data-trim")) return;
|
|
194
|
+
const el = field;
|
|
195
|
+
if (el.__icenTrimInit) return;
|
|
196
|
+
el.__icenTrimInit = true;
|
|
197
|
+
field.addEventListener("blur", () => {
|
|
198
|
+
const trimmed = field.value.trim();
|
|
199
|
+
if (trimmed !== field.value) {
|
|
200
|
+
field.value = trimmed;
|
|
201
|
+
field.dispatchEvent(new Event("input", { bubbles: true }));
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
function setupOtp(group) {
|
|
206
|
+
const el = group;
|
|
207
|
+
if (el.__icenInputInit) return;
|
|
208
|
+
el.__icenInputInit = true;
|
|
209
|
+
const isComposing = trackComposing(group);
|
|
210
|
+
const cells = Array.from(group.querySelectorAll(".otp-cell"));
|
|
211
|
+
const syncFilled = (cell) => {
|
|
212
|
+
cell.classList.toggle("is-filled", cell.value.length > 0);
|
|
213
|
+
};
|
|
214
|
+
cells.forEach((cell, i) => {
|
|
215
|
+
if (!cell.hasAttribute("aria-label")) {
|
|
216
|
+
cell.setAttribute("aria-label", `\u7B2C ${i + 1} \u4F4D\uFF0C\u5171 ${cells.length} \u4F4D`);
|
|
217
|
+
}
|
|
218
|
+
syncFilled(cell);
|
|
219
|
+
cell.addEventListener("input", () => {
|
|
220
|
+
if (isComposing()) return;
|
|
221
|
+
const ch = cell.value.slice(-1);
|
|
222
|
+
cell.value = ch;
|
|
223
|
+
syncFilled(cell);
|
|
224
|
+
if (ch && i < cells.length - 1) cells[i + 1].focus();
|
|
225
|
+
});
|
|
226
|
+
cell.addEventListener("keydown", (ev) => {
|
|
227
|
+
if (ev.key === "Backspace" && !cell.value && i > 0) {
|
|
228
|
+
ev.preventDefault();
|
|
229
|
+
cells[i - 1].focus();
|
|
230
|
+
}
|
|
231
|
+
if (ev.key === "ArrowLeft" && i > 0) {
|
|
232
|
+
ev.preventDefault();
|
|
233
|
+
cells[i - 1].focus();
|
|
234
|
+
}
|
|
235
|
+
if (ev.key === "ArrowRight" && i < cells.length - 1) {
|
|
236
|
+
ev.preventDefault();
|
|
237
|
+
cells[i + 1].focus();
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
cell.addEventListener("paste", (ev) => {
|
|
241
|
+
ev.preventDefault();
|
|
242
|
+
const text = (ev.clipboardData?.getData("text") ?? "").replace(/\s/g, "").slice(0, cells.length - i);
|
|
243
|
+
for (let k = 0; k < text.length; k++) {
|
|
244
|
+
const target = cells[i + k];
|
|
245
|
+
target.value = text[k] ?? "";
|
|
246
|
+
syncFilled(target);
|
|
247
|
+
}
|
|
248
|
+
cells[Math.min(i + text.length, cells.length - 1)].focus();
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
const fireComplete = () => {
|
|
252
|
+
const code = cells.map((c) => c.value).join("");
|
|
253
|
+
if (code.length === cells.length && cells.every((c) => c.value.length > 0)) {
|
|
254
|
+
group.classList.add("is-complete");
|
|
255
|
+
emitIcen(group, "icen:otp-complete", { code });
|
|
256
|
+
group.dispatchEvent(new CustomEvent("otp:complete", { detail: { code }, bubbles: true }));
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
cells.forEach((cell) => cell.addEventListener("input", fireComplete));
|
|
260
|
+
}
|
|
261
|
+
function initInput(root) {
|
|
262
|
+
if (typeof document === "undefined") return;
|
|
263
|
+
const scope = root ?? document;
|
|
264
|
+
const wraps = [];
|
|
265
|
+
if (scope instanceof Element && scope.matches(".input-wrap")) wraps.push(scope);
|
|
266
|
+
wraps.push(...Array.from(scope.querySelectorAll(".input-wrap")));
|
|
267
|
+
for (const w of wraps) {
|
|
268
|
+
const el = w;
|
|
269
|
+
if (el.__icenInputInit) continue;
|
|
270
|
+
el.__icenInputInit = true;
|
|
271
|
+
setupClear(w);
|
|
272
|
+
setupPasswordToggle(w);
|
|
273
|
+
}
|
|
274
|
+
const textareas = [];
|
|
275
|
+
if (scope instanceof HTMLTextAreaElement && scope.matches("[data-autosize]")) textareas.push(scope);
|
|
276
|
+
textareas.push(...Array.from(scope.querySelectorAll("textarea[data-autosize]")));
|
|
277
|
+
for (const ta of textareas) setupAutosize(ta);
|
|
278
|
+
const countedFields = [];
|
|
279
|
+
if (scope instanceof Element) {
|
|
280
|
+
const fields = Array.from(scope.querySelectorAll(
|
|
281
|
+
"input[maxlength], input[data-count], textarea[maxlength], textarea[data-count]"
|
|
282
|
+
));
|
|
283
|
+
countedFields.push(...fields);
|
|
284
|
+
if (scope instanceof HTMLInputElement || scope instanceof HTMLTextAreaElement) {
|
|
285
|
+
if (scope.hasAttribute("maxlength") || scope.hasAttribute("data-count")) countedFields.push(scope);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
for (const f of countedFields) setupCounter(f);
|
|
289
|
+
const maskedInputs = [];
|
|
290
|
+
if (scope instanceof Element) {
|
|
291
|
+
maskedInputs.push(...Array.from(scope.querySelectorAll("input[data-mask]")));
|
|
292
|
+
if (scope instanceof HTMLInputElement && scope.hasAttribute("data-mask")) maskedInputs.push(scope);
|
|
293
|
+
}
|
|
294
|
+
for (const inp of maskedInputs) setupMask(inp);
|
|
295
|
+
const trimFields = [];
|
|
296
|
+
if (scope instanceof Element) {
|
|
297
|
+
trimFields.push(...Array.from(scope.querySelectorAll("[data-trim]")));
|
|
298
|
+
if ((scope instanceof HTMLInputElement || scope instanceof HTMLTextAreaElement) && scope.hasAttribute("data-trim")) {
|
|
299
|
+
trimFields.push(scope);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
for (const f of trimFields) setupTrim(f);
|
|
303
|
+
const otps = [];
|
|
304
|
+
if (scope instanceof Element && scope.matches(".otp")) otps.push(scope);
|
|
305
|
+
otps.push(...Array.from(scope.querySelectorAll(".otp")));
|
|
306
|
+
for (const g of otps) setupOtp(g);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// src/behaviors/controls.ts
|
|
310
|
+
function isDisabled(el) {
|
|
311
|
+
return el instanceof HTMLButtonElement && el.disabled || el.hasAttribute("disabled") || el.getAttribute("aria-disabled") === "true" || el.classList.contains("is-disabled") || el.hasAttribute("data-disabled");
|
|
312
|
+
}
|
|
313
|
+
function isActivateKey(ev) {
|
|
314
|
+
return ev.key === "Enter" || ev.key === " ";
|
|
315
|
+
}
|
|
316
|
+
function createDelegatedInit(handlers) {
|
|
317
|
+
let inited = false;
|
|
318
|
+
return () => {
|
|
319
|
+
if (inited || typeof document === "undefined") return () => {
|
|
320
|
+
};
|
|
321
|
+
inited = true;
|
|
322
|
+
if (handlers.click) document.addEventListener("click", handlers.click);
|
|
323
|
+
if (handlers.keydown) document.addEventListener("keydown", handlers.keydown);
|
|
324
|
+
return () => {
|
|
325
|
+
if (handlers.click) document.removeEventListener("click", handlers.click);
|
|
326
|
+
if (handlers.keydown) document.removeEventListener("keydown", handlers.keydown);
|
|
327
|
+
inited = false;
|
|
328
|
+
};
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
var SWITCH_LIKE = '.switch[role="switch"], .checkbox[role="checkbox"], .radio-item[role="radio"], .radio[role="radio"], .radio-card[role="radio"]';
|
|
332
|
+
var seenCheckboxes = /* @__PURE__ */ new WeakSet();
|
|
333
|
+
function toggleSwitch(el) {
|
|
334
|
+
const checked = el.getAttribute("aria-checked") !== "true";
|
|
335
|
+
el.setAttribute("aria-checked", String(checked));
|
|
336
|
+
emitIcen(el, "icen:switch-change", { el, checked });
|
|
337
|
+
}
|
|
338
|
+
function toggleCheckbox(el) {
|
|
339
|
+
const cur = el.getAttribute("aria-checked") ?? "false";
|
|
340
|
+
const firstSeen = !seenCheckboxes.has(el);
|
|
341
|
+
seenCheckboxes.add(el);
|
|
342
|
+
let next;
|
|
343
|
+
if (cur === "mixed") next = firstSeen ? "false" : "true";
|
|
344
|
+
else next = cur === "true" ? "false" : "mixed";
|
|
345
|
+
el.setAttribute("aria-checked", next);
|
|
346
|
+
const checked = next === "mixed" ? "mixed" : next === "true";
|
|
347
|
+
emitIcen(el, "icen:check-change", { el, checked });
|
|
348
|
+
}
|
|
349
|
+
function selectRadio(el) {
|
|
350
|
+
if (el.getAttribute("aria-checked") === "true") return;
|
|
351
|
+
const group = el.closest('[data-radio-group], .radio-group, [role="radiogroup"], .radio-card-grid');
|
|
352
|
+
const peers = group ? Array.from(group.querySelectorAll('[role="radio"]')).filter(
|
|
353
|
+
(it) => it.closest('[data-radio-group], .radio-group, [role="radiogroup"], .radio-card-grid') === group
|
|
354
|
+
) : [el];
|
|
355
|
+
for (const it of peers) {
|
|
356
|
+
const on = it === el;
|
|
357
|
+
it.classList.toggle("is-selected", on);
|
|
358
|
+
it.setAttribute("aria-checked", String(on));
|
|
359
|
+
}
|
|
360
|
+
emitIcen(el, "icen:check-change", { el, checked: true });
|
|
361
|
+
}
|
|
362
|
+
function activateSwitchLike(el) {
|
|
363
|
+
if (el.matches('.switch[role="switch"]')) toggleSwitch(el);
|
|
364
|
+
else if (el.matches('.checkbox[role="checkbox"]')) toggleCheckbox(el);
|
|
365
|
+
else selectRadio(el);
|
|
366
|
+
}
|
|
367
|
+
function resolveSwitchTarget(t) {
|
|
368
|
+
if (!(t instanceof Element)) return null;
|
|
369
|
+
const direct = t.closest(SWITCH_LIKE);
|
|
370
|
+
if (direct) return isDisabled(direct) ? null : direct;
|
|
371
|
+
const row = t.closest(".checkbox-row");
|
|
372
|
+
if (row && !t.closest("button, a, input, select, textarea, [contenteditable]")) {
|
|
373
|
+
const box = row.querySelector('.checkbox[role="checkbox"]');
|
|
374
|
+
if (box && !isDisabled(box)) return box;
|
|
375
|
+
}
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
function onSwitchClick(ev) {
|
|
379
|
+
const el = resolveSwitchTarget(ev.target);
|
|
380
|
+
if (el) activateSwitchLike(el);
|
|
381
|
+
}
|
|
382
|
+
function onSwitchKeydown(ev) {
|
|
383
|
+
if (!isActivateKey(ev)) return;
|
|
384
|
+
const t = ev.target;
|
|
385
|
+
if (!(t instanceof Element)) return;
|
|
386
|
+
const el = t.closest(SWITCH_LIKE);
|
|
387
|
+
if (!el || isDisabled(el)) return;
|
|
388
|
+
ev.preventDefault();
|
|
389
|
+
activateSwitchLike(el);
|
|
390
|
+
}
|
|
391
|
+
var initSwitch = createDelegatedInit({ click: onSwitchClick, keydown: onSwitchKeydown });
|
|
392
|
+
function stepStepper(btn) {
|
|
393
|
+
const root = btn.closest(".stepper");
|
|
394
|
+
if (!root) return;
|
|
395
|
+
const input = root.querySelector('input.stepper-input, input[type="number"]');
|
|
396
|
+
if (!input || input.disabled) return;
|
|
397
|
+
const step = Number(btn.getAttribute("data-step")) || 1;
|
|
398
|
+
const minAttr = input.min;
|
|
399
|
+
const maxAttr = input.max;
|
|
400
|
+
const min = minAttr !== "" && Number.isFinite(Number(minAttr)) ? Number(minAttr) : -Infinity;
|
|
401
|
+
const max = maxAttr !== "" && Number.isFinite(Number(maxAttr)) ? Number(maxAttr) : Infinity;
|
|
402
|
+
const base = Number.isFinite(Number(input.value)) ? Number(input.value) : 0;
|
|
403
|
+
const next = Number(Math.min(max, Math.max(min, base + step)).toPrecision(12));
|
|
404
|
+
const delta = next - base;
|
|
405
|
+
input.value = String(next);
|
|
406
|
+
emitIcen(root, "icen:stepper-change", { el: root, value: next, delta });
|
|
407
|
+
}
|
|
408
|
+
function onStepperClick(ev) {
|
|
409
|
+
const t = ev.target;
|
|
410
|
+
if (!(t instanceof Element)) return;
|
|
411
|
+
const btn = t.closest(".stepper-btn");
|
|
412
|
+
if (!btn || isDisabled(btn)) return;
|
|
413
|
+
stepStepper(btn);
|
|
414
|
+
}
|
|
415
|
+
var initStepper = createDelegatedInit({ click: onStepperClick });
|
|
416
|
+
function selectSegmentedItem(item) {
|
|
417
|
+
const container = item.closest(".segmented");
|
|
418
|
+
if (!container) return;
|
|
419
|
+
const items = Array.from(container.querySelectorAll(".segmented-item")).filter(
|
|
420
|
+
(it) => it.closest(".segmented") === container
|
|
421
|
+
);
|
|
422
|
+
const index = items.indexOf(item);
|
|
423
|
+
if (index === -1) return;
|
|
424
|
+
if (item.classList.contains("is-active") || item.getAttribute("aria-checked") === "true") return;
|
|
425
|
+
for (const it of items) {
|
|
426
|
+
const on = it === item;
|
|
427
|
+
it.classList.toggle("is-active", on);
|
|
428
|
+
if (it.getAttribute("role") === "radio") it.setAttribute("aria-checked", String(on));
|
|
429
|
+
}
|
|
430
|
+
emitIcen(container, "icen:segmented-change", {
|
|
431
|
+
el: container,
|
|
432
|
+
value: item.getAttribute("data-value") ?? String(index),
|
|
433
|
+
index
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
function onSegmentedClick(ev) {
|
|
437
|
+
const t = ev.target;
|
|
438
|
+
if (!(t instanceof Element)) return;
|
|
439
|
+
const item = t.closest(".segmented-item");
|
|
440
|
+
if (!item || isDisabled(item)) return;
|
|
441
|
+
selectSegmentedItem(item);
|
|
442
|
+
}
|
|
443
|
+
var initSegmented = createDelegatedInit({ click: onSegmentedClick });
|
|
444
|
+
function activateStep(step) {
|
|
445
|
+
const container = step.closest(".steps");
|
|
446
|
+
if (!container) return;
|
|
447
|
+
const list = Array.from(container.querySelectorAll(".step")).filter(
|
|
448
|
+
(s) => s.closest(".steps") === container
|
|
449
|
+
);
|
|
450
|
+
const index = list.indexOf(step);
|
|
451
|
+
if (index === -1) return;
|
|
452
|
+
list.forEach((s, i) => {
|
|
453
|
+
s.classList.toggle("is-active", i === index);
|
|
454
|
+
if (i < index) {
|
|
455
|
+
s.classList.add("step--finish");
|
|
456
|
+
s.classList.remove("step--process", "step--wait");
|
|
457
|
+
} else if (i === index) {
|
|
458
|
+
s.classList.add("step--process");
|
|
459
|
+
s.classList.remove("step--finish", "step--wait", "step--error");
|
|
460
|
+
} else {
|
|
461
|
+
s.classList.add("step--wait");
|
|
462
|
+
s.classList.remove("step--finish", "step--process");
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
emitIcen(container, "icen:step-change", { step, index });
|
|
466
|
+
}
|
|
467
|
+
function onStepsClick(ev) {
|
|
468
|
+
const t = ev.target;
|
|
469
|
+
if (!(t instanceof Element)) return;
|
|
470
|
+
const step = t.closest('.step[role="button"]');
|
|
471
|
+
if (!step || isDisabled(step)) return;
|
|
472
|
+
activateStep(step);
|
|
473
|
+
}
|
|
474
|
+
function onStepsKeydown(ev) {
|
|
475
|
+
if (!isActivateKey(ev)) return;
|
|
476
|
+
const t = ev.target;
|
|
477
|
+
if (!(t instanceof Element)) return;
|
|
478
|
+
const step = t.closest('.step[role="button"]');
|
|
479
|
+
if (!step) return;
|
|
480
|
+
ev.preventDefault();
|
|
481
|
+
activateStep(step);
|
|
482
|
+
}
|
|
483
|
+
var initSteps = createDelegatedInit({ click: onStepsClick, keydown: onStepsKeydown });
|
|
484
|
+
function rateStar(star) {
|
|
485
|
+
const container = star.closest(".rating");
|
|
486
|
+
if (!container || container.classList.contains("rating--readonly")) return;
|
|
487
|
+
const stars = Array.from(container.querySelectorAll(".rating-star")).filter(
|
|
488
|
+
(s) => s.closest(".rating") === container
|
|
489
|
+
);
|
|
490
|
+
const index = stars.indexOf(star);
|
|
491
|
+
if (index === -1) return;
|
|
492
|
+
const value = index + 1;
|
|
493
|
+
stars.forEach((s, i) => {
|
|
494
|
+
s.classList.remove("is-half");
|
|
495
|
+
s.classList.toggle("is-filled", i <= index);
|
|
496
|
+
});
|
|
497
|
+
const valueEl = container.querySelector(".rating-value");
|
|
498
|
+
if (valueEl) valueEl.textContent = String(value);
|
|
499
|
+
emitIcen(container, "icen:rating-change", { el: container, value });
|
|
500
|
+
}
|
|
501
|
+
function onRatingClick(ev) {
|
|
502
|
+
const t = ev.target;
|
|
503
|
+
if (!(t instanceof Element)) return;
|
|
504
|
+
const star = t.closest(".rating-star");
|
|
505
|
+
if (!star || isDisabled(star)) return;
|
|
506
|
+
rateStar(star);
|
|
507
|
+
}
|
|
508
|
+
var initRating = createDelegatedInit({ click: onRatingClick });
|
|
509
|
+
function pageOf(btn, btns) {
|
|
510
|
+
const dp = btn.getAttribute("data-page");
|
|
511
|
+
if (dp != null && dp.trim() !== "" && Number.isFinite(Number(dp))) return Number(dp);
|
|
512
|
+
const text = Number((btn.textContent ?? "").trim());
|
|
513
|
+
if (Number.isFinite(text) && text > 0) return text;
|
|
514
|
+
return btns.indexOf(btn) + 1;
|
|
515
|
+
}
|
|
516
|
+
function dirOf(btn) {
|
|
517
|
+
const dir = (btn.getAttribute("data-dir") ?? "").toLowerCase();
|
|
518
|
+
if (dir === "prev" || dir === "-1" || dir === "previous") return -1;
|
|
519
|
+
if (dir === "next" || dir === "+1" || dir === "1") return 1;
|
|
520
|
+
const label = btn.getAttribute("aria-label");
|
|
521
|
+
if (label && /^上一页$|^上一张$|^向前$|^(prev|previous)$/i.test(label)) return -1;
|
|
522
|
+
if (label && /^下一页$|^下一张$|^向后$|^next$/i.test(label)) return 1;
|
|
523
|
+
return 0;
|
|
524
|
+
}
|
|
525
|
+
function setActivePage(btn, btns) {
|
|
526
|
+
for (const b of btns) {
|
|
527
|
+
const on = b === btn;
|
|
528
|
+
b.classList.toggle("is-active", on);
|
|
529
|
+
if (on) b.setAttribute("aria-current", "page");
|
|
530
|
+
else b.removeAttribute("aria-current");
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
function goPage(btn) {
|
|
534
|
+
const nav = btn.closest(".pagination");
|
|
535
|
+
if (!nav) return;
|
|
536
|
+
const btns = Array.from(nav.querySelectorAll(".page-btn")).filter(
|
|
537
|
+
(b) => b.closest(".pagination") === nav
|
|
538
|
+
);
|
|
539
|
+
const dir = dirOf(btn);
|
|
540
|
+
if (dir !== 0) {
|
|
541
|
+
const curBtn = btns.find(
|
|
542
|
+
(b) => b.classList.contains("is-active") || b.getAttribute("aria-current") === "page"
|
|
543
|
+
);
|
|
544
|
+
const cur = curBtn ? pageOf(curBtn, btns) : 1;
|
|
545
|
+
const target = Math.max(1, cur + dir);
|
|
546
|
+
const targetBtn = btns.find((b) => dirOf(b) === 0 && pageOf(b, btns) === target);
|
|
547
|
+
if (targetBtn) setActivePage(targetBtn, btns);
|
|
548
|
+
emitIcen(nav, "icen:page-change", { page: target });
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
const page = pageOf(btn, btns);
|
|
552
|
+
setActivePage(btn, btns);
|
|
553
|
+
emitIcen(nav, "icen:page-change", { page });
|
|
554
|
+
}
|
|
555
|
+
function onPaginationClick(ev) {
|
|
556
|
+
const t = ev.target;
|
|
557
|
+
if (!(t instanceof Element)) return;
|
|
558
|
+
const btn = t.closest(".page-btn");
|
|
559
|
+
if (!btn || isDisabled(btn)) return;
|
|
560
|
+
goPage(btn);
|
|
561
|
+
}
|
|
562
|
+
var initPagination = createDelegatedInit({ click: onPaginationClick });
|
|
563
|
+
function sortKeyOf(th) {
|
|
564
|
+
const cur = th.getAttribute("data-sort") ?? "";
|
|
565
|
+
return th.getAttribute("data-sort-key") ?? (cur === "asc" || cur === "desc" ? "" : cur);
|
|
566
|
+
}
|
|
567
|
+
function resetSortHeader(th) {
|
|
568
|
+
th.setAttribute("data-sort", sortKeyOf(th));
|
|
569
|
+
}
|
|
570
|
+
function cycleSort(th) {
|
|
571
|
+
const cur = th.getAttribute("data-sort") ?? "";
|
|
572
|
+
const key = sortKeyOf(th);
|
|
573
|
+
if (!th.hasAttribute("data-sort-key") && key !== "") th.setAttribute("data-sort-key", key);
|
|
574
|
+
const dir = cur === "asc" ? "desc" : cur === "desc" ? null : "asc";
|
|
575
|
+
const table = th.closest("table");
|
|
576
|
+
if (table) {
|
|
577
|
+
for (const h of table.querySelectorAll("th[data-sort]")) {
|
|
578
|
+
if (h !== th) resetSortHeader(h);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
th.setAttribute("data-sort", dir ?? key);
|
|
582
|
+
emitIcen(table ?? th, "icen:sort-change", { th, key, dir });
|
|
583
|
+
}
|
|
584
|
+
function onSortClick(ev) {
|
|
585
|
+
const t = ev.target;
|
|
586
|
+
if (!(t instanceof Element)) return;
|
|
587
|
+
const th = t.closest("th[data-sort]");
|
|
588
|
+
if (!th) return;
|
|
589
|
+
cycleSort(th);
|
|
590
|
+
}
|
|
591
|
+
var initTableSort = createDelegatedInit({ click: onSortClick });
|
|
592
|
+
export {
|
|
593
|
+
initInput,
|
|
594
|
+
initPagination,
|
|
595
|
+
initRating,
|
|
596
|
+
initSegmented,
|
|
597
|
+
initStepper,
|
|
598
|
+
initSteps,
|
|
599
|
+
initSwitch,
|
|
600
|
+
initTableSort
|
|
601
|
+
};
|