@icen.ai/ui 0.8.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.
Files changed (106) hide show
  1. package/AGENTS.md +15 -9
  2. package/README.md +8 -3
  3. package/dist/base.css +4 -3
  4. package/dist/behaviors/accordion.d.mts +5 -2
  5. package/dist/behaviors/accordion.mjs +36 -11
  6. package/dist/behaviors/ai-chat.d.mts +3 -1
  7. package/dist/behaviors/ai-chat.mjs +66 -43
  8. package/dist/behaviors/ai-composer.d.mts +28 -3
  9. package/dist/behaviors/ai-composer.mjs +291 -209
  10. package/dist/behaviors/ai-core.d.mts +9 -4
  11. package/dist/behaviors/ai-core.mjs +21 -8
  12. package/dist/behaviors/ai-diff.d.mts +2 -2
  13. package/dist/behaviors/ai-diff.mjs +172 -41
  14. package/dist/behaviors/ai-panel.d.mts +11 -8
  15. package/dist/behaviors/ai-panel.mjs +185 -110
  16. package/dist/behaviors/ai-provider.mjs +54 -48
  17. package/dist/behaviors/ai-tool.d.mts +12 -4
  18. package/dist/behaviors/ai-tool.mjs +311 -263
  19. package/dist/behaviors/ai-tools.d.mts +9 -6
  20. package/dist/behaviors/ai-tools.mjs +259 -155
  21. package/dist/behaviors/back-top.mjs +16 -1
  22. package/dist/behaviors/carousel.d.mts +5 -2
  23. package/dist/behaviors/carousel.mjs +53 -26
  24. package/dist/behaviors/charts.d.mts +68 -22
  25. package/dist/behaviors/charts.mjs +221 -150
  26. package/dist/behaviors/command-palette.d.mts +40 -6
  27. package/dist/behaviors/command-palette.mjs +143 -15
  28. package/dist/behaviors/context-menu.d.mts +21 -4
  29. package/dist/behaviors/context-menu.mjs +122 -38
  30. package/dist/behaviors/controls.d.mts +59 -0
  31. package/dist/behaviors/controls.mjs +601 -0
  32. package/dist/behaviors/copy.d.mts +5 -2
  33. package/dist/behaviors/copy.mjs +37 -12
  34. package/dist/behaviors/datatable.d.mts +12 -0
  35. package/dist/behaviors/datatable.mjs +226 -43
  36. package/dist/behaviors/date-picker.d.mts +2 -2
  37. package/dist/behaviors/date-picker.mjs +171 -32
  38. package/dist/behaviors/dropdown.d.mts +6 -2
  39. package/dist/behaviors/dropdown.mjs +164 -25
  40. package/dist/behaviors/events.d.mts +330 -0
  41. package/dist/behaviors/events.mjs +208 -0
  42. package/dist/behaviors/input.mjs +25 -12
  43. package/dist/behaviors/modal.d.mts +24 -5
  44. package/dist/behaviors/modal.mjs +76 -10
  45. package/dist/behaviors/nav.d.mts +1 -1
  46. package/dist/behaviors/nav.mjs +33 -7
  47. package/dist/behaviors/notification.d.mts +20 -4
  48. package/dist/behaviors/notification.mjs +53 -73
  49. package/dist/behaviors/popover.d.mts +39 -3
  50. package/dist/behaviors/popover.mjs +90 -4
  51. package/dist/behaviors/select.d.mts +4 -2
  52. package/dist/behaviors/select.mjs +161 -24
  53. package/dist/behaviors/sidebar.d.mts +5 -2
  54. package/dist/behaviors/sidebar.mjs +43 -13
  55. package/dist/behaviors/slider.d.mts +5 -2
  56. package/dist/behaviors/slider.mjs +54 -43
  57. package/dist/behaviors/split-pane.d.mts +6 -2
  58. package/dist/behaviors/split-pane.mjs +62 -21
  59. package/dist/behaviors/tabs.d.mts +3 -2
  60. package/dist/behaviors/tabs.mjs +65 -14
  61. package/dist/behaviors/tag-input.d.mts +5 -2
  62. package/dist/behaviors/tag-input.mjs +61 -21
  63. package/dist/behaviors/theme.d.mts +13 -2
  64. package/dist/behaviors/theme.mjs +48 -4
  65. package/dist/behaviors/toast.d.mts +30 -8
  66. package/dist/behaviors/toast.mjs +74 -7
  67. package/dist/behaviors/tree.d.mts +5 -2
  68. package/dist/behaviors/tree.mjs +38 -15
  69. package/dist/behaviors/upload.mjs +21 -23
  70. package/dist/cli.mjs +1 -1
  71. package/dist/components/ai-panel.css +1 -1
  72. package/dist/components/ai-tool-call.mjs +1 -1
  73. package/dist/components/badge.css +1 -1
  74. package/dist/components/breadcrumb.css +0 -32
  75. package/dist/components/chart-calendar.mjs +1 -2
  76. package/dist/components/chart-pie.css +0 -6
  77. package/dist/components/charts.css +1 -1
  78. package/dist/components/content.css +3 -16
  79. package/dist/components/datatable.css +1 -1
  80. package/dist/components/date-picker.css +1 -1
  81. package/dist/components/notification.css +2 -3
  82. package/dist/components/pagination.css +3 -1
  83. package/dist/components/pagination.mjs +1 -0
  84. package/dist/components/rating.mjs +1 -0
  85. package/dist/components/segmented.css +9 -1
  86. package/dist/components/segmented.mjs +1 -1
  87. package/dist/components/select.css +67 -4
  88. package/dist/components/steps.css +13 -3
  89. package/dist/components/steps.mjs +1 -0
  90. package/dist/components/switch.css +3 -1
  91. package/dist/components/switch.mjs +1 -0
  92. package/dist/components/table.mjs +1 -0
  93. package/dist/components/tabs.css +23 -0
  94. package/dist/components/tag-input.css +1 -6
  95. package/dist/components/upload.css +0 -15
  96. package/dist/index.d.mts +12 -10
  97. package/dist/index.mjs +2700 -1460
  98. package/dist/registry.json +42 -13
  99. package/dist/retro-effects.css +3 -16
  100. package/dist/tokens/retro-effects.css +3 -16
  101. package/dist/tokens/style-profiles.css +3 -2
  102. package/dist/tokens/typography.css +2 -0
  103. package/dist/tokens.css +5 -2
  104. package/dist/ui.css +138 -98
  105. package/docs/spec/ai-native.md +7 -4
  106. package/package.json +1 -1
@@ -18,10 +18,40 @@ function ensureContainer() {
18
18
  }
19
19
  var KIND_CLASS = {
20
20
  ok: "toast--success",
21
+ success: "toast--success",
21
22
  err: "toast--error",
23
+ error: "toast--error",
22
24
  warn: "toast--warning",
23
25
  info: "toast--info"
24
26
  };
27
+ var CLOSE_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>';
28
+ function stripUnsafeSvg(el) {
29
+ for (const attr of Array.from(el.attributes)) {
30
+ if (attr.name.toLowerCase().startsWith("on")) el.removeAttribute(attr.name);
31
+ }
32
+ for (const child of Array.from(el.children)) {
33
+ const tag = child.tagName.toLowerCase();
34
+ if (tag === "script" || tag === "foreignobject") child.remove();
35
+ else stripUnsafeSvg(child);
36
+ }
37
+ }
38
+ function sanitizeSvgIcon(source) {
39
+ if (!source.trimStart().startsWith("<svg")) return null;
40
+ try {
41
+ const doc = new DOMParser().parseFromString(source, "image/svg+xml");
42
+ const svg = doc.documentElement;
43
+ if (!svg || svg.tagName.toLowerCase() !== "svg") return null;
44
+ stripUnsafeSvg(svg);
45
+ return document.importNode(svg, true);
46
+ } catch {
47
+ return null;
48
+ }
49
+ }
50
+ var closeSvgCache = null;
51
+ function closeSvg() {
52
+ closeSvgCache ??= sanitizeSvgIcon(CLOSE_SVG);
53
+ return closeSvgCache ? closeSvgCache.cloneNode(true) : null;
54
+ }
25
55
  function scheduleRemove(el) {
26
56
  el.classList.add("is-leaving");
27
57
  let done = false;
@@ -48,12 +78,23 @@ function trimStack(container) {
48
78
  }
49
79
  }
50
80
  function show(message, kind = "info", opts = {}) {
51
- if (!isBrowser()) return;
52
- const { ms, actionLabel, onAction } = opts;
81
+ if (!isBrowser()) return { dismiss() {
82
+ } };
83
+ const { ms, actionLabel, onAction, icon, closable, progress } = opts;
53
84
  const container = ensureContainer();
54
85
  const el = document.createElement("div");
55
86
  el.className = `toast ${KIND_CLASS[kind]}`;
56
- el.setAttribute("role", kind === "err" ? "alert" : "status");
87
+ el.setAttribute("role", kind === "err" || kind === "error" ? "alert" : "status");
88
+ if (icon) {
89
+ const svg = sanitizeSvgIcon(icon);
90
+ if (svg) {
91
+ const iconWrap = document.createElement("span");
92
+ iconWrap.className = "toast-icon";
93
+ iconWrap.setAttribute("aria-hidden", "true");
94
+ iconWrap.appendChild(svg);
95
+ el.appendChild(iconWrap);
96
+ }
97
+ }
57
98
  const msgNode = document.createElement("span");
58
99
  msgNode.className = "toast-msg";
59
100
  msgNode.textContent = message;
@@ -66,16 +107,39 @@ function show(message, kind = "info", opts = {}) {
66
107
  actionBtn.textContent = actionLabel;
67
108
  el.appendChild(actionBtn);
68
109
  }
69
- container.appendChild(el);
70
- trimStack(container);
110
+ const duration = ms ?? (actionLabel ? ACTION_MS : DEFAULT_MS);
111
+ let timer = 0;
71
112
  let removed = false;
72
113
  const remove = () => {
73
114
  if (removed) return;
74
115
  removed = true;
116
+ if (timer) window.clearTimeout(timer);
75
117
  scheduleRemove(el);
76
118
  };
77
- const duration = ms ?? (actionLabel ? ACTION_MS : DEFAULT_MS);
78
- const timer = window.setTimeout(remove, duration);
119
+ if (closable) {
120
+ const closeBtn = document.createElement("button");
121
+ closeBtn.type = "button";
122
+ closeBtn.className = "toast-close";
123
+ closeBtn.setAttribute("aria-label", "\u5173\u95ED");
124
+ const svg = closeSvg();
125
+ if (svg) closeBtn.appendChild(svg);
126
+ else closeBtn.textContent = "\xD7";
127
+ closeBtn.addEventListener("click", remove);
128
+ el.appendChild(closeBtn);
129
+ }
130
+ if (progress) {
131
+ const bar = document.createElement("div");
132
+ bar.className = "toast-progress";
133
+ bar.setAttribute("aria-hidden", "true");
134
+ const fill = document.createElement("div");
135
+ fill.className = "toast-progress-fill";
136
+ fill.style.animationDuration = `${duration}ms`;
137
+ bar.appendChild(fill);
138
+ el.appendChild(bar);
139
+ }
140
+ container.appendChild(el);
141
+ trimStack(container);
142
+ timer = window.setTimeout(remove, duration);
79
143
  if (actionBtn && onAction) {
80
144
  actionBtn.addEventListener("click", () => {
81
145
  window.clearTimeout(timer);
@@ -84,11 +148,14 @@ function show(message, kind = "info", opts = {}) {
84
148
  remove();
85
149
  });
86
150
  }
151
+ return { dismiss: remove };
87
152
  }
88
153
  var toast = Object.assign(
89
154
  (message, kind = "info", ms) => show(message, kind, { ms }),
90
155
  {
156
+ success: (message, ms) => show(message, "success", { ms }),
91
157
  ok: (message, ms) => show(message, "ok", { ms }),
158
+ error: (message, ms) => show(message, "error", { ms }),
92
159
  err: (message, ms) => show(message, "err", { ms }),
93
160
  warn: (message, ms) => show(message, "warn", { ms }),
94
161
  info: (message, ms) => show(message, "info", { ms }),
@@ -1,4 +1,7 @@
1
- /** 为 root 下每棵 .tree 初始化(root 自身是 .tree 也算)。 */
2
- declare function initTree(root?: ParentNode): void;
1
+ /**
2
+ * 为 root 下每棵 .tree 初始化(root 自身是 .tree 也算)。
3
+ * 返回销毁函数:移除本次挂上的容器监听并复位幂等标记(销毁后可重新 init)。SSR 下返回 no-op。
4
+ */
5
+ declare function initTree(root?: ParentNode): () => void;
3
6
 
4
7
  export { initTree };
@@ -1,12 +1,19 @@
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
+
1
8
  // src/behaviors/tree.ts
2
9
  function setup(tree) {
3
10
  const el = tree;
4
- if (el.__icenTreeInit) return;
11
+ if (el.__icenTreeInit) return void 0;
5
12
  el.__icenTreeInit = true;
6
13
  tree.querySelectorAll(".tree-node").forEach((node) => {
7
14
  if (!node.hasAttribute("tabindex")) node.tabIndex = 0;
8
15
  });
9
- tree.addEventListener("click", (ev) => {
16
+ const onClick = (ev) => {
10
17
  const target = ev.target;
11
18
  if (!(target instanceof Element)) return;
12
19
  const node = target.closest(".tree-node");
@@ -28,6 +35,7 @@ function setup(tree) {
28
35
  toggle?.classList.add("is-collapsed");
29
36
  toggle?.setAttribute("aria-expanded", "false");
30
37
  }
38
+ emitIcen(tree, "icen:tree-toggle", { node: li, open: !collapsed });
31
39
  return;
32
40
  }
33
41
  if (childUl) return;
@@ -37,12 +45,9 @@ function setup(tree) {
37
45
  });
38
46
  node.classList.add("is-selected");
39
47
  node.setAttribute("aria-selected", "true");
40
- tree.dispatchEvent(new CustomEvent("icen:tree-select", {
41
- bubbles: true,
42
- detail: { node }
43
- }));
44
- });
45
- tree.addEventListener("keydown", (ev) => {
48
+ emitIcen(tree, "icen:tree-select", { node });
49
+ };
50
+ const onKeyDown = (ev) => {
46
51
  const kev = ev;
47
52
  const target = kev.target;
48
53
  if (!(target instanceof Element)) return;
@@ -71,6 +76,7 @@ function setup(tree) {
71
76
  childUl.removeAttribute("hidden");
72
77
  node.querySelector(".tree-toggle")?.setAttribute("aria-expanded", "true");
73
78
  node.querySelector(".tree-toggle")?.classList.remove("is-collapsed");
79
+ if (li) emitIcen(tree, "icen:tree-toggle", { node: li, open: true });
74
80
  }
75
81
  break;
76
82
  }
@@ -82,19 +88,36 @@ function setup(tree) {
82
88
  childUl.setAttribute("hidden", "");
83
89
  node.querySelector(".tree-toggle")?.setAttribute("aria-expanded", "false");
84
90
  node.querySelector(".tree-toggle")?.classList.add("is-collapsed");
91
+ if (li) emitIcen(tree, "icen:tree-toggle", { node: li, open: false });
85
92
  }
86
93
  break;
87
94
  }
88
95
  }
89
- });
96
+ };
97
+ tree.addEventListener("click", onClick);
98
+ tree.addEventListener("keydown", onKeyDown);
99
+ return () => {
100
+ tree.removeEventListener("click", onClick);
101
+ tree.removeEventListener("keydown", onKeyDown);
102
+ el.__icenTreeInit = false;
103
+ };
90
104
  }
91
105
  function initTree(root) {
92
- if (typeof document === "undefined") return;
93
- const scope = root ?? document;
94
- const trees = [];
95
- if (scope instanceof Element && scope.matches(".tree")) trees.push(scope);
96
- trees.push(...Array.from(scope.querySelectorAll(".tree")));
97
- for (const t of trees) setup(t);
106
+ const cleanups = [];
107
+ if (typeof document !== "undefined") {
108
+ const scope = root ?? document;
109
+ const trees = [];
110
+ if (scope instanceof Element && scope.matches(".tree")) trees.push(scope);
111
+ trees.push(...Array.from(scope.querySelectorAll(".tree")));
112
+ for (const t of trees) {
113
+ const cleanup = setup(t);
114
+ if (cleanup) cleanups.push(cleanup);
115
+ }
116
+ }
117
+ return () => {
118
+ for (const fn of cleanups) fn();
119
+ cleanups.length = 0;
120
+ };
98
121
  }
99
122
  export {
100
123
  initTree
@@ -1,3 +1,10 @@
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
+
1
8
  // src/behaviors/upload.ts
2
9
  function formatSize(bytes) {
3
10
  if (bytes < 1024) return `${bytes} B`;
@@ -34,9 +41,9 @@ function sameFile(a, b) {
34
41
  function setup(zone) {
35
42
  const el = zone;
36
43
  if (el.__icenUploadInit) return;
37
- el.__icenUploadInit = true;
38
44
  const input = zone.querySelector('input[type="file"]');
39
45
  if (!input) return;
46
+ el.__icenUploadInit = true;
40
47
  const hasList = zone.hasAttribute("data-upload-list");
41
48
  const maxSize = Number(zone.getAttribute("data-max-size")) || 0;
42
49
  const maxFiles = Number(zone.getAttribute("data-max-files")) || 0;
@@ -118,7 +125,7 @@ function setup(zone) {
118
125
  removeBtn.addEventListener("click", (ev) => {
119
126
  ev.stopPropagation();
120
127
  currentFiles = currentFiles.filter((f) => f !== file);
121
- zone.dispatchEvent(new CustomEvent("icen:upload-remove", { bubbles: true, detail: { file } }));
128
+ emitIcen(zone, "icen:upload-remove", { file });
122
129
  renderList();
123
130
  syncInputFiles();
124
131
  });
@@ -126,10 +133,10 @@ function setup(zone) {
126
133
  list.appendChild(li);
127
134
  }
128
135
  }
129
- function syncInputFiles() {
136
+ function setInputFiles(files) {
130
137
  try {
131
138
  const dt = new DataTransfer();
132
- for (const f of currentFiles) dt.items.add(f);
139
+ for (const f of files) dt.items.add(f);
133
140
  syncing = true;
134
141
  input.files = dt.files;
135
142
  input.dispatchEvent(new Event("change", { bubbles: true }));
@@ -138,6 +145,9 @@ function setup(zone) {
138
145
  syncing = false;
139
146
  }
140
147
  }
148
+ function syncInputFiles() {
149
+ setInputFiles(currentFiles);
150
+ }
141
151
  function validate(file) {
142
152
  if (accept && !isAccepted(file, accept)) return "\u6587\u4EF6\u7C7B\u578B\u4E0D\u652F\u6301";
143
153
  if (maxSize > 0 && file.size > maxSize) return `\u6587\u4EF6\u8D85\u8FC7 ${formatSize(maxSize)}`;
@@ -148,7 +158,7 @@ function setup(zone) {
148
158
  for (const file of files) {
149
159
  const reason = validate(file);
150
160
  if (reason) {
151
- zone.dispatchEvent(new CustomEvent("icen:upload-error", { bubbles: true, detail: { file, reason } }));
161
+ emitIcen(zone, "icen:upload-error", { file, reason });
152
162
  continue;
153
163
  }
154
164
  valid.push(file);
@@ -164,10 +174,7 @@ function setup(zone) {
164
174
  if (maxFiles > 0) {
165
175
  const remaining = maxFiles - currentFiles.length;
166
176
  if (remaining <= 0) {
167
- zone.dispatchEvent(new CustomEvent("icen:upload-error", {
168
- bubbles: true,
169
- detail: { file: null, reason: `\u6700\u591A ${maxFiles} \u4E2A\u6587\u4EF6` }
170
- }));
177
+ emitIcen(zone, "icen:upload-error", { file: null, reason: `\u6700\u591A ${maxFiles} \u4E2A\u6587\u4EF6` });
171
178
  return;
172
179
  }
173
180
  if (fresh.length > remaining) fresh.length = remaining;
@@ -175,10 +182,10 @@ function setup(zone) {
175
182
  currentFiles.push(...fresh);
176
183
  renderList();
177
184
  syncInputFiles();
178
- zone.dispatchEvent(new CustomEvent("icen:upload", { bubbles: true, detail: { files: fresh } }));
185
+ emitIcen(zone, "icen:upload", { files: fresh });
179
186
  return;
180
187
  }
181
- zone.dispatchEvent(new CustomEvent("icen:upload", { bubbles: true, detail: { files: valid } }));
188
+ emitIcen(zone, "icen:upload", { files: valid });
182
189
  }
183
190
  const openPicker = () => input.click();
184
191
  zone.addEventListener("click", (ev) => {
@@ -189,6 +196,8 @@ function setup(zone) {
189
196
  });
190
197
  zone.addEventListener("keydown", (ev) => {
191
198
  if (ev.key === "Enter" || ev.key === " ") {
199
+ const tgt = ev.target;
200
+ if (tgt instanceof Element && tgt.closest(".upload-list")) return;
192
201
  ev.preventDefault();
193
202
  openPicker();
194
203
  }
@@ -215,18 +224,7 @@ function setup(zone) {
215
224
  const files = ev.dataTransfer?.files;
216
225
  if (!files || files.length === 0) return;
217
226
  handleFiles(Array.from(files));
218
- if (!hasList) {
219
- try {
220
- const dt = new DataTransfer();
221
- for (const f of Array.from(files)) dt.items.add(f);
222
- syncing = true;
223
- input.files = dt.files;
224
- input.dispatchEvent(new Event("change", { bubbles: true }));
225
- } catch {
226
- } finally {
227
- syncing = false;
228
- }
229
- }
227
+ if (!hasList) setInputFiles(Array.from(files));
230
228
  });
231
229
  }
232
230
  function initUpload(root) {
package/dist/cli.mjs CHANGED
@@ -7,7 +7,7 @@
7
7
  * 用法:
8
8
  * bunx --bun @icen.ai/ui add <slug...> 打印引入行(多个 slug 空格分隔)
9
9
  * bunx --bun @icen.ai/ui list 列出全部组件 slug
10
- * 数据源:同目录 registry.json(构建期由 scripts/slugs.mjs 生成)。
10
+ * 数据源:同目录 registry.json(构建期由 scripts/build-css.ts 写入,映射数据源为 scripts/slugs.mjs)。
11
11
  */
12
12
  import { readFileSync } from 'node:fs';
13
13
 
@@ -399,7 +399,7 @@
399
399
  user-select: none;
400
400
  }
401
401
 
402
- /* 大号变体(常驻工具栏/头部用) */
402
+ /* 大号变体(消费方变体,常驻头部大号环;behavior 不产出,由使用方手写标记) */
403
403
  .ai-usage-ring--lg {
404
404
  width: 44px;
405
405
  height: 44px;
@@ -1,2 +1,2 @@
1
1
  // @icen.ai/ui — kit: ai-tool-call(CSS + behavior 一行入口;构建产物,勿手改)
2
- import './ai-tool-call.css';
2
+ import './ai-tool.css';
@@ -22,7 +22,7 @@
22
22
  * </span>
23
23
  *
24
24
  * 变体:--dot 纯圆点 / --sm 小号 / --lg 大号 / --bottom 底部角
25
- * 语义色:--success(在线)/ --warning / --error / --accent
25
+ * 语义色:--success(在线)/ --warning / --error / --info / --accent
26
26
  */
27
27
 
28
28
  .badge-host {
@@ -105,38 +105,6 @@
105
105
  background: var(--token-fill-1);
106
106
  }
107
107
 
108
- /* ── 溢出下拉(behavior 可选:点击 … 展开被折叠的中间路径) ── */
109
- .breadcrumb-dropdown {
110
- position: relative;
111
- }
112
- .breadcrumb-dropdown-menu {
113
- position: absolute;
114
- top: calc(100% + 4px);
115
- left: 0;
116
- min-width: 160px;
117
- padding: 4px;
118
- background: var(--token-card-strong);
119
- border: 1px solid var(--token-outline);
120
- border-radius: var(--radius-md);
121
- box-shadow: var(--token-shadow-overlay);
122
- z-index: var(--z-popover);
123
- animation: icen-pop-in var(--icen-tx-fast);
124
- }
125
- .breadcrumb-dropdown-menu[hidden] { display: none; }
126
- .breadcrumb-dropdown-menu a {
127
- display: block;
128
- padding: 4px var(--density-space-sm);
129
- font-size: var(--density-font-size-sm);
130
- color: var(--token-text-muted);
131
- text-decoration: none;
132
- border-radius: var(--radius-sm);
133
- transition: background-color var(--icen-tx-fast), color var(--icen-tx-fast);
134
- }
135
- .breadcrumb-dropdown-menu a:hover {
136
- background: var(--token-fill-1);
137
- color: var(--token-text);
138
- }
139
-
140
108
  /* ── 面包屑分隔符变体 ── */
141
109
  .breadcrumb--slash .breadcrumb-sep::after { content: '/'; }
142
110
  .breadcrumb--arrow .breadcrumb-sep::after { content: '→'; }
@@ -1,5 +1,4 @@
1
1
  // @icen.ai/ui — kit: chart-calendar(CSS + behavior 一行入口;构建产物,勿手改)
2
- import './chart-calendar.css';
3
- import './charts.css';
4
2
  import './chart-heatmap.css';
3
+ import './charts.css';
5
4
  export { renderCalendar } from '../behaviors/charts.mjs';
@@ -18,9 +18,3 @@
18
18
  height: 132px;
19
19
  flex-shrink: 0;
20
20
  }
21
- .chart-donut-center {
22
- font-variant-numeric: tabular-nums;
23
- text-anchor: middle;
24
- fill: var(--token-text);
25
- font-weight: 600;
26
- }
@@ -78,7 +78,7 @@
78
78
  font-variant-numeric: tabular-nums;
79
79
  }
80
80
 
81
- /* ── 调色盘:多系列图(donut/stack/line/radar)由 behavior 选用 ── */
81
+ /* ── 调色盘:手写 markup 调色盘;behavior 走 charts.ts PALETTE_VARS 内联同值(两处值需人工同步) ── */
82
82
  .chart-tone-1 { --chart-tone: var(--token-accent); }
83
83
  .chart-tone-2 { --chart-tone: var(--token-accent-cold); }
84
84
  .chart-tone-3 { --chart-tone: var(--token-success); }
@@ -3,6 +3,9 @@
3
3
  *
4
4
  * 包含:kbd(按键)/ code-inline(行内代码)/ list(分隔列表)/ accordion(手风琴,behaviors/accordion 配套)/
5
5
  * timeline(时间线)/ desc(描述列表)/ tree(树形,behaviors/tree 配套)
6
+ * markup API 变体(正文"增强"段,behavior 不产出、由手写 markup 消费):
7
+ * .accordion--cards / .code-inline--success|--warning|--error /
8
+ * .timeline--right|--alternate|--sm / .timeline-extra / .timeline-dot--sm 及 dot 语义色(--error 等)
6
9
  * 规格源自 opengal src/presentation/ui/data/exhibit.tsx 与 data/display.tsx。
7
10
  */
8
11
 
@@ -531,22 +534,6 @@
531
534
  background: var(--token-outline-soft);
532
535
  }
533
536
 
534
- /* 树节点复选框(checkbox tree) */
535
- .tree-checkbox {
536
- width: 14px;
537
- height: 14px;
538
- flex-shrink: 0;
539
- margin-right: 2px;
540
- }
541
-
542
- /* 树拖拽视觉态 */
543
- .tree-node.is-dragging {
544
- opacity: 0.5;
545
- }
546
- .tree-node.is-drop-target {
547
- box-shadow: inset 0 -2px var(--token-accent);
548
- }
549
-
550
537
  /* 树搜索高亮 */
551
538
  .tree-label mark {
552
539
  background: color-mix(in srgb, var(--token-warning) 30%, transparent);
@@ -418,7 +418,7 @@
418
418
  background: color-mix(in srgb, var(--token-accent-subtle) 82%, var(--token-fill-1));
419
419
  }
420
420
 
421
- /* ── 行状态类(rowClassName 产出) ── */
421
+ /* ── 行状态类(rowClassName 产出;--danger/--warning/--success 同族语义行底色) ── */
422
422
  .dt-row--danger { background: color-mix(in srgb, var(--token-error) 6%, transparent) !important; }
423
423
  .dt-row--danger:hover { background: color-mix(in srgb, var(--token-error) 10%, transparent) !important; }
424
424
  .dt-row--warning { background: color-mix(in srgb, var(--token-warning) 6%, transparent) !important; }
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * 契约:
5
5
  * <div class="date-picker" data-date-picker data-date-picker-input="id">
6
- * <button class="date-picker-trigger" type="button" aria-haspopup="dialog">
6
+ * <button class="date-picker-trigger [--sm|--lg]" type="button" aria-haspopup="dialog">
7
7
  * <span class="date-picker-value">2026-07-29</span>
8
8
  * <svg class="date-picker-icon">…日历 svg…</svg>
9
9
  * </button>
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * 完整契约(由 behavior 渲染):
11
11
  * <div class="notification-container" role="region" aria-label="通知" data-position="top-right">
12
- * <div class="notification [notification--success|warning|error|info]
12
+ * <div class="notification [notification--success|warning|error]
13
13
  * [.is-priority] [.is-unread] [.is-progress] [.is-clickable] [.is-paused]
14
14
  * [.has-avatar]" role="alert|button">
15
15
  * <span class="notification-icon">…svg 或 img.notification-avatar…</span>
@@ -81,7 +81,7 @@
81
81
  .notification.notification--success { border-left-color: var(--token-success); }
82
82
  .notification.notification--warning { border-left-color: var(--token-warning); }
83
83
  .notification.notification--error { border-left-color: var(--token-error); }
84
- .notification.notification--info { border-left-color: var(--token-info); }
84
+ /* info 为基类默认配色(基类左侧边即 --token-info),无修饰类 */
85
85
 
86
86
  /* ── 优先级:高优先级强调(左侧加粗 + 更强阴影)── */
87
87
  .notification.is-priority {
@@ -138,7 +138,6 @@
138
138
  .notification--success .notification-icon { color: var(--token-success); }
139
139
  .notification--warning .notification-icon { color: var(--token-warning); }
140
140
  .notification--error .notification-icon { color: var(--token-error); }
141
- .notification--info .notification-icon { color: var(--token-info); }
142
141
 
143
142
  /* 头像(圆形图) */
144
143
  .notification-avatar {
@@ -10,7 +10,9 @@
10
10
  * <button class="page-btn motion" aria-label="下一页">›</button>
11
11
  * </nav>
12
12
  *
13
- * 翻页逻辑由消费方实现,这里只提供静态样式契约。
13
+ * 点击接线由 behaviors/controls.ts 的 initPagination() 提供(.page-btn 激活位移 +
14
+ * icen:page-change,上下页按钮按 aria-label / data-dir 推断方向);总页数与省略号
15
+ * 折叠策略由消费方维护,这里只提供样式契约。
14
16
  */
15
17
 
16
18
  .pagination {
@@ -1,2 +1,3 @@
1
1
  // @icen.ai/ui — kit: pagination(CSS + behavior 一行入口;构建产物,勿手改)
2
2
  import './pagination.css';
3
+ export { initPagination } from '../behaviors/controls.mjs';
@@ -1,2 +1,3 @@
1
1
  // @icen.ai/ui — kit: rating(CSS + behavior 一行入口;构建产物,勿手改)
2
2
  import './media.css';
3
+ export { initRating } from '../behaviors/controls.mjs';
@@ -28,7 +28,15 @@
28
28
  * …
29
29
  * </div>
30
30
  *
31
- * segmented/toggle 是纯 CSS 状态组件(.is-active);stepper/otp 由 behaviors/input.ts 接线。
31
+ * segmented / stepper 由 behaviors/controls.ts 接线(initSegmented / initStepper,
32
+ * 派 icen:segmented-change / icen:stepper-change);otp 由 behaviors/input.ts 的
33
+ * initInput 接线;toggle-group 为纯 CSS 状态组件(.is-active,使用方自行驱动)。
34
+ *
35
+ * 增强段(正文"增强"段):
36
+ * 变体:segmented--block / segmented--ghost / toggle-group--block / toggle-group--ghost
37
+ * 槽位:stepper-unit(步进器单位后缀)/ otp-separator(OTP 分组分隔符)
38
+ * 状态类(消费方施加):.otp.is-complete(behavior 接线中)/ .otp.is-error /
39
+ * .segmented.is-error / .segmented.is-disabled / .toggle-group.is-error / .toggle-group.is-disabled
32
40
  */
33
41
 
34
42
  /* ── 分段控件(排他单选) ── */
@@ -1,3 +1,3 @@
1
1
  // @icen.ai/ui — kit: segmented(CSS + behavior 一行入口;构建产物,勿手改)
2
2
  import './segmented.css';
3
- export { initInput } from '../behaviors/input.mjs';
3
+ export { initInput, initSegmented, initStepper } from '../behaviors/controls.mjs';