@h-ai/ui 0.1.0-alpha.33 → 0.1.0-alpha.35

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 (45) hide show
  1. package/README.md +24 -5
  2. package/auto-import.js +2 -2
  3. package/dist/components/compounds/Combobox.svelte +12 -4
  4. package/dist/components/compounds/Combobox.svelte.d.ts +4 -1
  5. package/dist/components/compounds/DatePicker.svelte +19 -4
  6. package/dist/components/compounds/DatePicker.svelte.d.ts +4 -1
  7. package/dist/components/compounds/TagInput.svelte +36 -31
  8. package/dist/components/control-size.d.ts +18 -0
  9. package/dist/components/control-size.js +18 -0
  10. package/dist/components/primitives/Input.svelte +4 -25
  11. package/dist/components/primitives/Select.svelte +4 -10
  12. package/dist/components/scenes/ai/AiDocumentEditor.svelte +203 -22
  13. package/dist/components/scenes/ai/document-download.js +19 -2
  14. package/dist/components/scenes/ai/document-parse.d.ts +25 -2
  15. package/dist/components/scenes/ai/document-parse.js +137 -11
  16. package/dist/components/scenes/ai/index.d.ts +1 -1
  17. package/dist/components/scenes/ai/mermaid-host-sync.d.ts +34 -0
  18. package/dist/components/scenes/ai/mermaid-host-sync.js +35 -0
  19. package/dist/components/scenes/ai/mermaid-render.d.ts +5 -0
  20. package/dist/components/scenes/ai/mermaid-render.js +7 -0
  21. package/dist/components/scenes/charts/Chart.svelte +152 -0
  22. package/dist/components/scenes/charts/Chart.svelte.d.ts +5 -0
  23. package/dist/components/scenes/charts/chart-theme.d.ts +23 -0
  24. package/dist/components/scenes/charts/chart-theme.js +44 -0
  25. package/dist/components/scenes/charts/chart-types.d.ts +77 -0
  26. package/dist/components/scenes/charts/chart-types.js +7 -0
  27. package/dist/components/scenes/charts/index.d.ts +8 -0
  28. package/dist/components/scenes/charts/index.js +7 -0
  29. package/dist/components/scenes/charts/renderers/AreaChart.svelte +18 -0
  30. package/dist/components/scenes/charts/renderers/AreaChart.svelte.d.ts +5 -0
  31. package/dist/components/scenes/charts/renderers/BarChart.svelte +18 -0
  32. package/dist/components/scenes/charts/renderers/BarChart.svelte.d.ts +5 -0
  33. package/dist/components/scenes/charts/renderers/LineChart.svelte +571 -0
  34. package/dist/components/scenes/charts/renderers/LineChart.svelte.d.ts +19 -0
  35. package/dist/components/scenes/charts/renderers/PieChart.svelte +18 -0
  36. package/dist/components/scenes/charts/renderers/PieChart.svelte.d.ts +5 -0
  37. package/dist/components/scenes/charts/renderers/ScatterChart.svelte +18 -0
  38. package/dist/components/scenes/charts/renderers/ScatterChart.svelte.d.ts +5 -0
  39. package/dist/components/scenes/iam/PasswordInput.svelte +4 -15
  40. package/dist/components/scenes/index.d.ts +1 -0
  41. package/dist/components/scenes/index.js +2 -0
  42. package/dist/messages/en-US.json +2 -0
  43. package/dist/messages/zh-CN.json +2 -0
  44. package/dist/types.d.ts +1 -0
  45. package/package.json +3 -2
@@ -0,0 +1,571 @@
1
+ <!--
2
+ =============================================================================
3
+ @h-ai/ui - LineChart 内部渲染器
4
+ =============================================================================
5
+ 对 LayerChart LineChart 的薄包装,仅做类型收窄和统一导入路径。
6
+ 不对外暴露;由 Chart.svelte 按 type='line' 内部路由调用。
7
+ =============================================================================
8
+ -->
9
+ <script lang='ts'>
10
+ import type { LineChartProps } from 'layerchart'
11
+ import type {
12
+ ChartAccessor,
13
+ ChartDataItem,
14
+ ChartLineVariant,
15
+ ChartSeries,
16
+ ChartTooltipMode,
17
+ } from '../chart-types.js'
18
+ import { LineChart as LayerLineChart } from 'layerchart'
19
+
20
+ interface SegmentedLinePoint {
21
+ /** 横轴展示标签,来源于 Chart 的 x accessor。 */
22
+ label: string
23
+ /** 当前系列在该横轴位置上的数值。 */
24
+ value: number
25
+ }
26
+
27
+ interface SegmentedLineSeries {
28
+ /** 系列稳定标识,优先来自 ChartSeries.key。 */
29
+ key: string
30
+ /** tooltip 和图例展示文案。 */
31
+ label: string
32
+ /** 折线、圆点和 tooltip 色标共用颜色。 */
33
+ color: string
34
+ /** 当前系列按 data 顺序解析后的点。 */
35
+ points: SegmentedLinePoint[]
36
+ /** 是否以虚线展示,适合辅助系列。 */
37
+ dashed: boolean
38
+ }
39
+
40
+ interface SegmentedLineCoordinate extends SegmentedLinePoint {
41
+ /** SVG viewBox 中的横向坐标。 */
42
+ x: number
43
+ /** SVG viewBox 中的纵向坐标。 */
44
+ y: number
45
+ }
46
+
47
+ interface SegmentedLineTooltipItem {
48
+ /** tooltip 中展示的系列名称。 */
49
+ label: string
50
+ /** tooltip 中展示的当前数值。 */
51
+ value: number
52
+ /** tooltip 色标,与系列颜色一致。 */
53
+ color: string
54
+ }
55
+
56
+ interface SegmentedLineTooltip {
57
+ /** 当前吸附数据点在 SVG 坐标系内的横向位置。 */
58
+ x: number
59
+ /** tooltip 横向定位百分比,适配响应式容器宽度。 */
60
+ leftPercent: number
61
+ /** tooltip 纵向定位百分比,来自首个系列当前点。 */
62
+ topPercent: number
63
+ /** 当前 hover 的横轴标签。 */
64
+ label: string
65
+ /** 当前横轴位置下每个系列的值。 */
66
+ items: SegmentedLineTooltipItem[]
67
+ }
68
+
69
+ interface Props extends LineChartProps<ChartDataItem> {
70
+ /** 折线图渲染变体;default 保持 LayerChart 原生渲染。 */
71
+ lineVariant?: ChartLineVariant
72
+ /** tooltip 触发方式;nearest 会在绘图区内吸附最近数据点。 */
73
+ tooltipMode?: ChartTooltipMode
74
+ /** segmented-point 变体是否显示竖向参考线。 */
75
+ showCrosshair?: boolean
76
+ /** segmented-point 变体是否显示圆点。 */
77
+ showPoints?: boolean
78
+ /** LayerChart 原生 legend 配置;segmented-point 中 true 会渲染轻量图例。 */
79
+ legend?: boolean | Record<string, unknown>
80
+ /** LayerChart 原生 grid 配置;segmented-point 中 truthy 会渲染水平网格。 */
81
+ grid?: boolean | Record<string, unknown>
82
+ }
83
+
84
+ const {
85
+ lineVariant = 'default',
86
+ tooltipMode = 'default',
87
+ showCrosshair = false,
88
+ showPoints = true,
89
+ legend = false,
90
+ grid = false,
91
+ data = [],
92
+ x,
93
+ y,
94
+ series,
95
+ height = 300,
96
+ ...props
97
+ }: Props = $props()
98
+
99
+ /** segmented-point 使用固定 viewBox 保持点、线和 tooltip 命中区在不同高度下比例一致。 */
100
+ const segmentedViewBox = {
101
+ width: 420,
102
+ height: 132,
103
+ plotLeft: 36,
104
+ plotRight: 390,
105
+ plotTop: 14,
106
+ plotBottom: 98,
107
+ labelY: 124,
108
+ }
109
+
110
+ let activeTooltip = $state<SegmentedLineTooltip | null>(null)
111
+
112
+ /** 当前渲染分支是否启用 @h-ai/ui 的分段折线变体。 */
113
+ const useSegmentedLine = $derived(lineVariant === 'segmented-point')
114
+
115
+ /** 解析后的可绘制系列;默认 LayerChart 分支不会读取该派生值。 */
116
+ const segmentedSeries = $derived(resolveSegmentedSeries(data, x, y, series))
117
+
118
+ /** Y 轴最大值兜底为 1,避免全零或空数据时除零。 */
119
+ const segmentedMax = $derived(getSegmentedLineMax(segmentedSeries))
120
+
121
+ /** 第一条系列的坐标用于横轴标签和 hover 命中基准,多系列共用同一 x 轴。 */
122
+ const primaryCoordinates = $derived(getSegmentedLineCoordinates(segmentedSeries[0]?.points ?? [], segmentedMax))
123
+
124
+ /**
125
+ * 从数据项中读取指定 accessor 的值。
126
+ * 支持函数、字符串键和数字键三种 accessor 形式。
127
+ * @param item - 原始数据对象
128
+ * @param accessor - 字段访问器(函数 / 字符串 / 数字)
129
+ * @returns 读取到的原始值,无法识别时返回 undefined
130
+ */
131
+ function readAccessorValue(item: ChartDataItem, accessor: ChartAccessor): unknown {
132
+ if (typeof accessor === 'function')
133
+ return accessor(item)
134
+
135
+ if (typeof accessor === 'string' || typeof accessor === 'number')
136
+ return item[String(accessor)]
137
+
138
+ return undefined
139
+ }
140
+
141
+ /**
142
+ * 将 accessor 读取到的值格式化为用于展示的字符串标签。
143
+ * Date 类型转为 ISO 字符串,字符串/数字直接转字符串,其余返回 fallback。
144
+ * @param value - 原始值
145
+ * @param fallback - 无法识别时的回退文案
146
+ * @returns 格式化后的标签字符串
147
+ */
148
+ function formatAccessorLabel(value: unknown, fallback: string): string {
149
+ if (value instanceof Date)
150
+ return value.toISOString()
151
+
152
+ if (typeof value === 'string' || typeof value === 'number')
153
+ return `${value}`
154
+
155
+ return fallback
156
+ }
157
+
158
+ /**
159
+ * 将任意值安全转换为有限数字。
160
+ * 非数字或 Infinity/NaN 均兜底为 0,避免后续除法或坐标计算出错。
161
+ * @param value - 待转换的原始值
162
+ * @returns 有限数字,无法转换时返回 0
163
+ */
164
+ function toFiniteNumber(value: unknown): number {
165
+ const numberValue = typeof value === 'number' ? value : Number(value)
166
+
167
+ return Number.isFinite(numberValue) ? numberValue : 0
168
+ }
169
+
170
+ /**
171
+ * 获取系列配置中的值访问器,优先使用 value,回退到 key。
172
+ * @param item - 系列配置对象
173
+ * @returns 适用于 readAccessorValue 的 accessor
174
+ */
175
+ function getSeriesAccessor(item: ChartSeries): ChartAccessor {
176
+ return item.value ?? item.key
177
+ }
178
+
179
+ /**
180
+ * 获取系列的展示标签,未配置时回退到 fallback。
181
+ * @param item - 系列配置对象
182
+ * @param fallback - 未配置 label 时使用的默认文案
183
+ * @returns tooltip / 图例中显示的文案
184
+ */
185
+ function getSeriesLabel(item: ChartSeries, fallback: string): string {
186
+ return item.label ?? fallback
187
+ }
188
+
189
+ /**
190
+ * 获取系列的渲染颜色。
191
+ * 优先使用 series 配置中的 color,未配置时按索引循环分配主题色变量。
192
+ * @param item - 系列配置对象
193
+ * @param index - 系列在列表中的索引,用于循环选色
194
+ * @returns CSS 颜色值或变量
195
+ */
196
+ function getSeriesColor(item: ChartSeries, index: number): string {
197
+ return item.color ?? `var(--color-${['primary', 'secondary', 'accent', 'success', 'warning', 'error', 'info'][index % 7]})`
198
+ }
199
+
200
+ /**
201
+ * 判断系列是否应以虚线渲染。
202
+ * 支持 ChartSeries.dashed 顶层布尔值和 props.dashed 两种配置方式。
203
+ * @param item - 系列配置对象
204
+ * @returns true 表示该系列使用虚线
205
+ */
206
+ function getSeriesDashed(item: ChartSeries): boolean {
207
+ return item.dashed === true || (typeof item.props === 'object' && item.props !== null && item.props.dashed === true)
208
+ }
209
+
210
+ /**
211
+ * 将 y 轴配置和可选的 series 数组统一为标准 ChartSeries 列表。
212
+ * 若已提供 chartSeries 则直接返回其副本;
213
+ * 否则根据 yAccessor 自动推导单系列或多系列配置。
214
+ * @param yAccessor - y 轴字段配置(字符串 / 数组 / 函数)
215
+ * @param chartSeries - 可选的显式系列配置
216
+ * @returns 标准化的 ChartSeries 数组
217
+ */
218
+ function normalizeSeries(yAccessor: Props['y'], chartSeries?: readonly ChartSeries[]): ChartSeries[] {
219
+ if (chartSeries && chartSeries.length > 0)
220
+ return [...chartSeries]
221
+
222
+ if (Array.isArray(yAccessor)) {
223
+ return yAccessor.map((accessor, index) => ({
224
+ key: typeof accessor === 'string' ? accessor : `series-${index}`,
225
+ label: typeof accessor === 'string' ? accessor : `series-${index + 1}`,
226
+ value: accessor,
227
+ }))
228
+ }
229
+
230
+ return [{
231
+ key: typeof yAccessor === 'string' ? yAccessor : 'value',
232
+ label: typeof yAccessor === 'string' ? yAccessor : 'value',
233
+ value: yAccessor,
234
+ }]
235
+ }
236
+
237
+ /**
238
+ * 将原始数据和系列配置解析为 segmented-point 变体所需的 SegmentedLineSeries 数组。
239
+ * 每个系列包含 key、label、color、dashed 以及按 x 轴顺序排列的 points。
240
+ * @param items - 原始数据数组
241
+ * @param xAccessor - x 轴字段访问器
242
+ * @param yAccessor - y 轴字段配置
243
+ * @param chartSeries - 可选的显式系列配置
244
+ * @returns 解析后的 SegmentedLineSeries 列表
245
+ */
246
+ function resolveSegmentedSeries(items: readonly ChartDataItem[], xAccessor: Props['x'], yAccessor: Props['y'], chartSeries?: readonly ChartSeries[]): SegmentedLineSeries[] {
247
+ return normalizeSeries(yAccessor, chartSeries).map((item, seriesIndex) => {
248
+ const accessor = getSeriesAccessor(item)
249
+
250
+ return {
251
+ key: item.key,
252
+ label: getSeriesLabel(item, item.key),
253
+ color: getSeriesColor(item, seriesIndex),
254
+ dashed: getSeriesDashed(item),
255
+ points: items.map((datum, dataIndex) => ({
256
+ label: formatAccessorLabel(readAccessorValue(datum, xAccessor), `${dataIndex + 1}`),
257
+ value: toFiniteNumber(readAccessorValue(datum, accessor)),
258
+ })),
259
+ }
260
+ })
261
+ }
262
+
263
+ /**
264
+ * 计算所有系列中的最大数据值,作为 Y 轴上限。
265
+ * 全零或空数据时兜底为 1,避免后续坐标计算中出现除零。
266
+ * @param items - SegmentedLineSeries 列表
267
+ * @returns Y 轴最大值(至少为 1)
268
+ */
269
+ function getSegmentedLineMax(items: SegmentedLineSeries[]): number {
270
+ const maxValue = Math.max(...items.flatMap(item => item.points.map(point => point.value)))
271
+
272
+ return maxValue > 0 ? maxValue : 1
273
+ }
274
+
275
+ /**
276
+ * 将数据点从逻辑值映射为 SVG viewBox 坐标。
277
+ * x 方向按点在序列中的位置均匀分布,y 方向按 maxValue 做线性缩放。
278
+ * @param points - 一个系列的数据点列表
279
+ * @param maxValue - Y 轴最大值,用于归一化
280
+ * @returns 带有 SVG 坐标的 SegmentedLineCoordinate 数组
281
+ */
282
+ function getSegmentedLineCoordinates(points: SegmentedLinePoint[], maxValue: number): SegmentedLineCoordinate[] {
283
+ const width = segmentedViewBox.plotRight - segmentedViewBox.plotLeft
284
+ const height = segmentedViewBox.plotBottom - segmentedViewBox.plotTop
285
+ const lastIndex = Math.max(points.length - 1, 1)
286
+
287
+ return points.map((point, index) => ({
288
+ ...point,
289
+ x: segmentedViewBox.plotLeft + (width * index) / lastIndex,
290
+ y: segmentedViewBox.plotTop + height - (point.value / maxValue) * height,
291
+ }))
292
+ }
293
+
294
+ /**
295
+ * 将有序坐标点转换为 SVG path 的 d 属性段(M ... L ... 格式)。
296
+ * 相邻两点生成一条线段,用于渲染折线。
297
+ * @param points - 已计算坐标的点序列
298
+ * @returns SVG path d 属性字符串数组,每项对应一段线段
299
+ */
300
+ function getSegmentedLineSegments(points: SegmentedLineCoordinate[]): string[] {
301
+ return points.slice(1).map((point, index) => {
302
+ const previous = points[index]
303
+ if (!previous)
304
+ return ''
305
+
306
+ return `M ${previous.x} ${previous.y} L ${point.x} ${point.y}`
307
+ }).filter(Boolean)
308
+ }
309
+
310
+ /**
311
+ * 显示 segmented-point 变体的 tooltip。
312
+ * 计算 tooltip 的定位百分比并收集所有系列在该横轴位置的数值。
313
+ * @param point - 当前吸附的坐标点
314
+ * @param pointIndex - 该点在坐标数组中的索引,用于从各系列取值
315
+ */
316
+ function showSegmentedTooltip(point: SegmentedLineCoordinate, pointIndex: number): void {
317
+ activeTooltip = {
318
+ x: point.x,
319
+ leftPercent: (point.x / segmentedViewBox.width) * 100,
320
+ topPercent: (Math.max(segmentedViewBox.plotTop, point.y - 10) / segmentedViewBox.height) * 100,
321
+ label: point.label,
322
+ items: segmentedSeries.map(item => ({
323
+ label: item.label,
324
+ value: item.points[pointIndex]?.value ?? 0,
325
+ color: item.color,
326
+ })),
327
+ }
328
+ }
329
+
330
+ /**
331
+ * 处理指针在命中区域上的移动事件。
332
+ * 仅在 tooltipMode === 'nearest' 时生效,计算指针在 SVG 中的等效坐标,
333
+ * 找到最近的横轴数据点并触发 tooltip 显示。
334
+ * @param event - 原生 PointerEvent
335
+ */
336
+ function handleSegmentedPointerMove(event: PointerEvent): void {
337
+ if (tooltipMode !== 'nearest')
338
+ return
339
+
340
+ if (primaryCoordinates.length === 0)
341
+ return
342
+
343
+ const bounds = event.currentTarget instanceof SVGRectElement
344
+ ? event.currentTarget.getBoundingClientRect()
345
+ : null
346
+ if (!bounds || bounds.width === 0)
347
+ return
348
+
349
+ const plotWidth = segmentedViewBox.plotRight - segmentedViewBox.plotLeft
350
+ const pointerX = ((event.clientX - bounds.left) / bounds.width) * plotWidth + segmentedViewBox.plotLeft
351
+ const nearestIndex = primaryCoordinates.reduce((bestIndex, point, index) => {
352
+ const currentDistance = Math.abs(point.x - pointerX)
353
+ const bestDistance = Math.abs((primaryCoordinates[bestIndex]?.x ?? point.x) - pointerX)
354
+
355
+ return currentDistance < bestDistance ? index : bestIndex
356
+ }, 0)
357
+ const nearestPoint = primaryCoordinates[nearestIndex]
358
+ if (nearestPoint)
359
+ showSegmentedTooltip(nearestPoint, nearestIndex)
360
+ }
361
+
362
+ /**
363
+ * 隐藏 segmented-point 变体的 tooltip。
364
+ * 在指针离开命中区域时调用,将 activeTooltip 置为 null。
365
+ */
366
+ function hideSegmentedTooltip(): void {
367
+ activeTooltip = null
368
+ }
369
+ </script>
370
+
371
+ {#if useSegmentedLine}
372
+ <div class='hai-segmented-line-chart' style={`height: ${height}px;`}>
373
+ <svg viewBox={`0 0 ${segmentedViewBox.width} ${segmentedViewBox.height}`} role='img'>
374
+ {#if grid}
375
+ {#each [18, 58, 98] as gridY (gridY)}
376
+ <line class='hai-segmented-line-chart__grid' x1={segmentedViewBox.plotLeft} x2={segmentedViewBox.plotRight} y1={gridY} y2={gridY}></line>
377
+ {/each}
378
+ {/if}
379
+
380
+ {#if showCrosshair && activeTooltip}
381
+ <line class='hai-segmented-line-chart__crosshair' x1={activeTooltip.x} x2={activeTooltip.x} y1={segmentedViewBox.plotTop} y2='108'></line>
382
+ {/if}
383
+
384
+ {#each segmentedSeries as item (item.key)}
385
+ {@const coordinates = getSegmentedLineCoordinates(item.points, segmentedMax)}
386
+ {#each getSegmentedLineSegments(coordinates) as segment (segment)}
387
+ <path
388
+ class='hai-segmented-line-chart__segment'
389
+ d={segment}
390
+ stroke={item.color}
391
+ stroke-dasharray={item.dashed ? '6 5' : undefined}
392
+ ></path>
393
+ {/each}
394
+ {#if showPoints}
395
+ {#each coordinates as point (point.label)}
396
+ <circle
397
+ class='hai-segmented-line-chart__point'
398
+ cx={point.x}
399
+ cy={point.y}
400
+ r='3.8'
401
+ fill={item.color}
402
+ ></circle>
403
+ {/each}
404
+ {/if}
405
+ {/each}
406
+
407
+ {#each primaryCoordinates as point (point.label)}
408
+ <text class='hai-segmented-line-chart__label' x={point.x} y={segmentedViewBox.labelY}>{point.label}</text>
409
+ {/each}
410
+
411
+ <rect
412
+ class='hai-segmented-line-chart__hit-area'
413
+ x={segmentedViewBox.plotLeft}
414
+ y='10'
415
+ width={segmentedViewBox.plotRight - segmentedViewBox.plotLeft}
416
+ height='104'
417
+ role='presentation'
418
+ onpointerenter={handleSegmentedPointerMove}
419
+ onpointermove={handleSegmentedPointerMove}
420
+ onpointerleave={hideSegmentedTooltip}
421
+ ></rect>
422
+ </svg>
423
+
424
+ {#if legend}
425
+ <div class='hai-segmented-line-chart__legend'>
426
+ {#each segmentedSeries as item (item.key)}
427
+ <span>
428
+ <i style={`background: ${item.color};`}></i>
429
+ {item.label}
430
+ </span>
431
+ {/each}
432
+ </div>
433
+ {/if}
434
+
435
+ {#if activeTooltip}
436
+ <div
437
+ class='hai-segmented-line-chart__tooltip'
438
+ style={`left: ${activeTooltip.leftPercent}%; top: ${activeTooltip.topPercent}%;`}
439
+ >
440
+ <strong>{activeTooltip.label}</strong>
441
+ {#each activeTooltip.items as item (`${item.label}-${item.color}`)}
442
+ <span>
443
+ <i style={`background: ${item.color};`}></i>
444
+ {item.label}: {item.value}
445
+ </span>
446
+ {/each}
447
+ </div>
448
+ {/if}
449
+ </div>
450
+ {:else}
451
+ <LayerLineChart {...props} {data} {x} {y} {series} {height} {legend} {grid} />
452
+ {/if}
453
+
454
+ <style>
455
+ .hai-segmented-line-chart {
456
+ position: relative;
457
+ min-width: 0;
458
+ overflow: visible;
459
+ }
460
+
461
+ .hai-segmented-line-chart svg {
462
+ display: block;
463
+ width: 100%;
464
+ height: 100%;
465
+ }
466
+
467
+ .hai-segmented-line-chart__grid {
468
+ stroke: color-mix(in oklab, var(--color-base-content) 16%, transparent);
469
+ stroke-width: 1;
470
+ }
471
+
472
+ .hai-segmented-line-chart__crosshair {
473
+ stroke: color-mix(in oklab, var(--color-primary) 54%, transparent);
474
+ stroke-dasharray: 4 4;
475
+ stroke-width: 1.25;
476
+ }
477
+
478
+ .hai-segmented-line-chart__segment {
479
+ fill: none;
480
+ stroke-linecap: round;
481
+ stroke-linejoin: round;
482
+ stroke-width: 3;
483
+ }
484
+
485
+ .hai-segmented-line-chart__point {
486
+ cursor: pointer;
487
+ stroke: var(--color-base-100);
488
+ stroke-width: 2.4;
489
+ }
490
+
491
+ .hai-segmented-line-chart__point:hover {
492
+ stroke-width: 3.2;
493
+ }
494
+
495
+ .hai-segmented-line-chart__label {
496
+ fill: color-mix(in oklab, var(--color-base-content) 62%, transparent);
497
+ font-size: 0.72rem;
498
+ font-weight: 700;
499
+ text-anchor: middle;
500
+ }
501
+
502
+ .hai-segmented-line-chart__hit-area {
503
+ fill: transparent;
504
+ cursor: crosshair;
505
+ pointer-events: all;
506
+ }
507
+
508
+ .hai-segmented-line-chart__legend {
509
+ position: absolute;
510
+ right: 0.4rem;
511
+ bottom: 1.8rem;
512
+ display: inline-flex;
513
+ flex-wrap: wrap;
514
+ justify-content: flex-end;
515
+ gap: 0.65rem;
516
+ color: color-mix(in oklab, var(--color-base-content) 62%, transparent);
517
+ font-size: 0.72rem;
518
+ font-weight: 750;
519
+ pointer-events: none;
520
+ }
521
+
522
+ .hai-segmented-line-chart__legend span {
523
+ display: inline-flex;
524
+ align-items: center;
525
+ gap: 0.3rem;
526
+ }
527
+
528
+ .hai-segmented-line-chart__legend i {
529
+ width: 0.85rem;
530
+ height: 0.18rem;
531
+ border-radius: 999px;
532
+ }
533
+
534
+ .hai-segmented-line-chart__tooltip {
535
+ position: absolute;
536
+ z-index: 3;
537
+ display: grid;
538
+ min-width: 7rem;
539
+ gap: 0.28rem;
540
+ border: 1px solid color-mix(in oklab, var(--color-base-content) 14%, transparent);
541
+ border-radius: 0.35rem;
542
+ padding: 0.48rem 0.58rem;
543
+ color: var(--color-base-content);
544
+ background: color-mix(in oklab, var(--color-base-100) 96%, var(--color-base-200) 4%);
545
+ box-shadow: 0 0.7rem 1.8rem color-mix(in oklab, #0f172a 14%, transparent);
546
+ font-size: 0.74rem;
547
+ line-height: 1.2;
548
+ pointer-events: none;
549
+ transform: translate(-50%, -100%);
550
+ }
551
+
552
+ .hai-segmented-line-chart__tooltip strong {
553
+ font-size: 0.72rem;
554
+ font-weight: 850;
555
+ }
556
+
557
+ .hai-segmented-line-chart__tooltip span {
558
+ display: inline-flex;
559
+ align-items: center;
560
+ gap: 0.32rem;
561
+ white-space: nowrap;
562
+ color: color-mix(in oklab, var(--color-base-content) 62%, transparent);
563
+ font-weight: 750;
564
+ }
565
+
566
+ .hai-segmented-line-chart__tooltip i {
567
+ width: 0.62rem;
568
+ height: 0.62rem;
569
+ border-radius: 999px;
570
+ }
571
+ </style>
@@ -0,0 +1,19 @@
1
+ import type { LineChartProps } from 'layerchart';
2
+ import type { ChartDataItem, ChartLineVariant, ChartTooltipMode } from '../chart-types.js';
3
+ interface Props extends LineChartProps<ChartDataItem> {
4
+ /** 折线图渲染变体;default 保持 LayerChart 原生渲染。 */
5
+ lineVariant?: ChartLineVariant;
6
+ /** tooltip 触发方式;nearest 会在绘图区内吸附最近数据点。 */
7
+ tooltipMode?: ChartTooltipMode;
8
+ /** segmented-point 变体是否显示竖向参考线。 */
9
+ showCrosshair?: boolean;
10
+ /** segmented-point 变体是否显示圆点。 */
11
+ showPoints?: boolean;
12
+ /** LayerChart 原生 legend 配置;segmented-point 中 true 会渲染轻量图例。 */
13
+ legend?: boolean | Record<string, unknown>;
14
+ /** LayerChart 原生 grid 配置;segmented-point 中 truthy 会渲染水平网格。 */
15
+ grid?: boolean | Record<string, unknown>;
16
+ }
17
+ declare const LineChart: import("svelte").Component<Props, {}, "">;
18
+ type LineChart = ReturnType<typeof LineChart>;
19
+ export default LineChart;
@@ -0,0 +1,18 @@
1
+ <!--
2
+ =============================================================================
3
+ @h-ai/ui - PieChart 内部渲染器
4
+ =============================================================================
5
+ 对 LayerChart PieChart 的薄包装,仅做类型收窄和统一导入路径。
6
+ 不对外暴露;由 Chart.svelte 按 type='pie' 内部路由调用。
7
+ =============================================================================
8
+ -->
9
+ <script lang='ts'>
10
+ import type { PieChartProps } from 'layerchart'
11
+ import type { ChartDataItem } from '../chart-types.js'
12
+ import { PieChart as LayerPieChart } from 'layerchart'
13
+
14
+ /** 透传给 LayerChart PieChart 的全部原生属性。 */
15
+ const { ...props }: PieChartProps<ChartDataItem> = $props()
16
+ </script>
17
+
18
+ <LayerPieChart {...props} />
@@ -0,0 +1,5 @@
1
+ import type { PieChartProps } from 'layerchart';
2
+ import type { ChartDataItem } from '../chart-types.js';
3
+ declare const PieChart: import("svelte").Component<PieChartProps<ChartDataItem>, {}, "">;
4
+ type PieChart = ReturnType<typeof PieChart>;
5
+ export default PieChart;
@@ -0,0 +1,18 @@
1
+ <!--
2
+ =============================================================================
3
+ @h-ai/ui - ScatterChart 内部渲染器
4
+ =============================================================================
5
+ 对 LayerChart ScatterChart 的薄包装,仅做类型收窄和统一导入路径。
6
+ 不对外暴露;由 Chart.svelte 按 type='scatter' 内部路由调用。
7
+ =============================================================================
8
+ -->
9
+ <script lang='ts'>
10
+ import type { ScatterChartProps } from 'layerchart'
11
+ import type { ChartDataItem } from '../chart-types.js'
12
+ import { ScatterChart as LayerScatterChart } from 'layerchart'
13
+
14
+ /** 透传给 LayerChart ScatterChart 的全部原生属性。 */
15
+ const { ...props }: ScatterChartProps<ChartDataItem> = $props()
16
+ </script>
17
+
18
+ <LayerScatterChart {...props} />
@@ -0,0 +1,5 @@
1
+ import type { ScatterChartProps } from 'layerchart';
2
+ import type { ChartDataItem } from '../chart-types.js';
3
+ declare const ScatterChart: import("svelte").Component<ScatterChartProps<ChartDataItem>, {}, "">;
4
+ type ScatterChart = ReturnType<typeof ScatterChart>;
5
+ export default ScatterChart;
@@ -7,6 +7,7 @@
7
7
  import type { DataAttributes, Size } from '../../../types.js'
8
8
  import { uiM } from '../../../messages.js'
9
9
  import { cn, getDataAttributes } from '../../../utils.js'
10
+ import { getFormControlSizeClasses } from '../../control-size.js'
10
11
  import BareButton from '../../primitives/BareButton.svelte'
11
12
  import BareInput from '../../primitives/BareInput.svelte'
12
13
  import Progress from '../../primitives/Progress.svelte'
@@ -76,19 +77,7 @@
76
77
 
77
78
  const dataAttributes = $derived(getDataAttributes(restProps))
78
79
  let showPassword = $state(false)
79
-
80
- // 容器高度(与 Input 组件一致)
81
- const containerHeight = $derived(
82
- size === 'xs'
83
- ? 'h-8'
84
- : size === 'sm'
85
- ? 'h-9'
86
- : size === 'lg'
87
- ? 'h-12'
88
- : size === 'xl'
89
- ? 'h-14'
90
- : 'h-10',
91
- )
80
+ const sizeClasses = $derived(getFormControlSizeClasses(size))
92
81
 
93
82
  // 图标大小
94
83
  const iconSize = $derived(
@@ -152,7 +141,7 @@
152
141
  <!-- 输入框容器 -->
153
142
  <div class={cn(
154
143
  'flex items-center w-full rounded-lg border bg-base-100',
155
- containerHeight,
144
+ sizeClasses.height,
156
145
  error
157
146
  ? 'border-error/60 focus-within:ring-2 focus-within:ring-error/15'
158
147
  : 'border-base-content/15 focus-within:border-primary/50 focus-within:ring-2 focus-within:ring-primary/10',
@@ -163,7 +152,7 @@
163
152
  <BareInput
164
153
  type={showPassword ? 'text' : 'password'}
165
154
  {id}
166
- class='flex-1 h-full px-3 bg-transparent border-none outline-none text-sm placeholder:text-base-content/35'
155
+ class={cn('flex-1 h-full bg-transparent border-none outline-none placeholder:text-base-content/35', sizeClasses.spacing)}
167
156
  bind:value
168
157
  bind:inputRef={inputRef}
169
158
  {placeholder}
@@ -6,6 +6,7 @@
6
6
  */
7
7
  export * from './ai/index.js';
8
8
  export * from './app/index.js';
9
+ export * from './charts/index.js';
9
10
  export * from './crud/index.js';
10
11
  export * from './crypto/index.js';
11
12
  export * from './error/index.js';