@h-ai/ui 0.1.0-alpha.34 → 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 (27) hide show
  1. package/README.md +19 -0
  2. package/auto-import.js +2 -2
  3. package/dist/components/primitives/Select.svelte +1 -1
  4. package/dist/components/scenes/charts/Chart.svelte +152 -0
  5. package/dist/components/scenes/charts/Chart.svelte.d.ts +5 -0
  6. package/dist/components/scenes/charts/chart-theme.d.ts +23 -0
  7. package/dist/components/scenes/charts/chart-theme.js +44 -0
  8. package/dist/components/scenes/charts/chart-types.d.ts +77 -0
  9. package/dist/components/scenes/charts/chart-types.js +7 -0
  10. package/dist/components/scenes/charts/index.d.ts +8 -0
  11. package/dist/components/scenes/charts/index.js +7 -0
  12. package/dist/components/scenes/charts/renderers/AreaChart.svelte +18 -0
  13. package/dist/components/scenes/charts/renderers/AreaChart.svelte.d.ts +5 -0
  14. package/dist/components/scenes/charts/renderers/BarChart.svelte +18 -0
  15. package/dist/components/scenes/charts/renderers/BarChart.svelte.d.ts +5 -0
  16. package/dist/components/scenes/charts/renderers/LineChart.svelte +571 -0
  17. package/dist/components/scenes/charts/renderers/LineChart.svelte.d.ts +19 -0
  18. package/dist/components/scenes/charts/renderers/PieChart.svelte +18 -0
  19. package/dist/components/scenes/charts/renderers/PieChart.svelte.d.ts +5 -0
  20. package/dist/components/scenes/charts/renderers/ScatterChart.svelte +18 -0
  21. package/dist/components/scenes/charts/renderers/ScatterChart.svelte.d.ts +5 -0
  22. package/dist/components/scenes/index.d.ts +1 -0
  23. package/dist/components/scenes/index.js +2 -0
  24. package/dist/messages/en-US.json +2 -0
  25. package/dist/messages/zh-CN.json +2 -0
  26. package/dist/types.d.ts +1 -0
  27. package/package.json +3 -2
package/README.md CHANGED
@@ -309,6 +309,25 @@ components/
309
309
  />
310
310
  ```
311
311
 
312
+ #### Charts 图表
313
+
314
+ `Chart type='line'` 默认透传 LayerChart 原生折线图;需要监控看板常见的“分段线 + 圆点 + 最近点 hover + 竖向参考线”时,使用 `lineVariant='segmented-point'`:
315
+
316
+ ```svelte
317
+ <Chart
318
+ type='line'
319
+ data={trendData}
320
+ x='label'
321
+ y={['rateLimit', 'circuitBreak']}
322
+ series={trendSeries}
323
+ lineVariant='segmented-point'
324
+ tooltipMode='nearest'
325
+ showCrosshair
326
+ legend
327
+ grid
328
+ />
329
+ ```
330
+
312
331
  #### CRUD 场景(5 个)
313
332
 
314
333
  | 组件 | 描述 | 主要属性 / 能力 |
package/auto-import.js CHANGED
@@ -232,9 +232,9 @@ function autoImportHaiUi() {
232
232
  return { code: content }
233
233
  }
234
234
 
235
- // 跳过 @h-ai/ui 包自身的文件,避免重复导入
235
+ // 自动导入只服务应用源码;第三方依赖和 @h-ai/ui 自身必须保持原样,避免改写依赖内部的同名组件。
236
236
  const normalizedPath = filename.replace(WINDOWS_PATH_SEPARATOR_REGEX, '/')
237
- if (normalizedPath.includes('/packages/ui/') || normalizedPath.includes('/@h-ai/ui/')) {
237
+ if (normalizedPath.includes('/node_modules/') || normalizedPath.includes('/packages/ui/') || normalizedPath.includes('/@h-ai/ui/')) {
238
238
  return { code: content }
239
239
  }
240
240
 
@@ -264,7 +264,7 @@
264
264
  {/if}
265
265
 
266
266
  <!-- 右侧图标区域 -->
267
- <div class='flex items-center pr-2 shrink-0'>
267
+ <div class='flex items-center shrink-0'>
268
268
  {#if showClear}
269
269
  <button
270
270
  type='button'
@@ -0,0 +1,152 @@
1
+ <!--
2
+ =============================================================================
3
+ @h-ai/ui - Chart 组件
4
+ =============================================================================
5
+ 基于 LayerChart 的统一图表入口,仅处理 type 路由、主题色默认值和基础状态。
6
+
7
+ 使用 Svelte 5 Runes ($props, $derived)
8
+ =============================================================================
9
+ -->
10
+ <script lang='ts'>
11
+ import type { ChartProps, ChartSeries, ChartType } from './chart-types.js'
12
+ import { uiM } from '../../../messages.js'
13
+ import { cn, getDataAttributes } from '../../../utils.js'
14
+ import { DAISYUI_CHART_COLORS, resolveChartColors } from './chart-theme.js'
15
+ import AreaChart from './renderers/AreaChart.svelte'
16
+ import BarChart from './renderers/BarChart.svelte'
17
+ import LineChart from './renderers/LineChart.svelte'
18
+ import PieChart from './renderers/PieChart.svelte'
19
+ import ScatterChart from './renderers/ScatterChart.svelte'
20
+ import 'layerchart/daisyui-5.css'
21
+
22
+ const {
23
+ type,
24
+ data = [],
25
+ x,
26
+ y,
27
+ series,
28
+ orientation,
29
+ size = 'md',
30
+ title = '',
31
+ description = '',
32
+ loading = false,
33
+ empty = false,
34
+ class: className = '',
35
+ ...restProps
36
+ }: ChartProps = $props()
37
+
38
+ const dataAttributes = $derived(getDataAttributes(restProps))
39
+
40
+ const chartSizeHeights: Record<NonNullable<ChartProps['size']>, number> = {
41
+ sm: 200,
42
+ md: 300,
43
+ lg: 400,
44
+ }
45
+
46
+ const cartesianChartTypes = new Set<ChartType>(['bar', 'line', 'area'])
47
+
48
+ /** 图表可视高度优先尊重 LayerChart 原生 height,未传时使用 @h-ai/ui 的 size 预设。 */
49
+ const chartHeight = $derived(
50
+ typeof restProps.height === 'number' ? restProps.height : chartSizeHeights[size],
51
+ )
52
+
53
+ const isEmpty = $derived(empty || data.length === 0)
54
+ const hasHeader = $derived(Boolean(title || description))
55
+
56
+ const rootClass = $derived(
57
+ cn(
58
+ 'hai-chart flex flex-col gap-3',
59
+ className,
60
+ ),
61
+ )
62
+
63
+ const viewportClass = $derived(
64
+ cn(
65
+ 'hai-chart-viewport min-w-0',
66
+ (loading || isEmpty) && 'flex items-center justify-center rounded-lg border border-dashed border-base-300 bg-base-200/30',
67
+ ),
68
+ )
69
+
70
+ /**
71
+ * 当调用方只传 y 数组时,为常见笛卡尔图自动补 series。
72
+ * 这让 `<Chart y={['a','b']}>` 仍然得到多系列和主题色,同时不影响显式传入的 LayerChart series。
73
+ */
74
+ const normalizedSeries = $derived.by<ChartSeries[] | undefined>(() => {
75
+ if (series) {
76
+ if (!cartesianChartTypes.has(type))
77
+ return series
78
+
79
+ return series.map(item => ({
80
+ ...item,
81
+ value: item.value ?? item.key,
82
+ }))
83
+ }
84
+
85
+ if (!cartesianChartTypes.has(type) || !Array.isArray(y))
86
+ return undefined
87
+
88
+ return y.map(key => ({
89
+ key,
90
+ label: key,
91
+ value: key,
92
+ }))
93
+ })
94
+
95
+ const resolvedSeries = $derived(resolveChartColors(normalizedSeries))
96
+
97
+ /** 饼图使用统一 x/y API 时,将 x 映射为 key/label,y 的第一项映射为 value。 */
98
+ const pieValue = $derived(Array.isArray(y) ? y[0] : y)
99
+
100
+ const cartesianLayerProps = $derived({
101
+ data,
102
+ height: chartHeight,
103
+ cRange: DAISYUI_CHART_COLORS,
104
+ ...restProps,
105
+ orientation,
106
+ series: resolvedSeries,
107
+ x,
108
+ y,
109
+ })
110
+
111
+ const pieLayerProps = $derived({
112
+ data,
113
+ height: chartHeight,
114
+ key: x,
115
+ label: x,
116
+ value: pieValue,
117
+ cRange: DAISYUI_CHART_COLORS,
118
+ ...restProps,
119
+ series: resolvedSeries,
120
+ })
121
+ </script>
122
+
123
+ <div {...dataAttributes} class={rootClass}>
124
+ {#if hasHeader}
125
+ <div class='space-y-1'>
126
+ {#if title}
127
+ <h3 class='text-sm font-semibold text-base-content'>{title}</h3>
128
+ {/if}
129
+ {#if description}
130
+ <p class='text-xs leading-relaxed text-base-content/60'>{description}</p>
131
+ {/if}
132
+ </div>
133
+ {/if}
134
+
135
+ <div class={viewportClass} style={`height: ${chartHeight}px;`}>
136
+ {#if loading}
137
+ <span class='loading loading-spinner loading-md text-primary' aria-label={uiM('chart_loading')}></span>
138
+ {:else if isEmpty}
139
+ <p class='text-sm text-base-content/50'>{uiM('chart_empty')}</p>
140
+ {:else if type === 'bar'}
141
+ <BarChart {...cartesianLayerProps} />
142
+ {:else if type === 'line'}
143
+ <LineChart {...cartesianLayerProps} />
144
+ {:else if type === 'area'}
145
+ <AreaChart {...cartesianLayerProps} />
146
+ {:else if type === 'pie'}
147
+ <PieChart {...pieLayerProps} />
148
+ {:else if type === 'scatter'}
149
+ <ScatterChart {...cartesianLayerProps} />
150
+ {/if}
151
+ </div>
152
+ </div>
@@ -0,0 +1,5 @@
1
+ import type { ChartProps } from './chart-types.js';
2
+ import 'layerchart/daisyui-5.css';
3
+ declare const Chart: import("svelte").Component<ChartProps, {}, "">;
4
+ type Chart = ReturnType<typeof Chart>;
5
+ export default Chart;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @h-ai/ui — 图表主题色
3
+ *
4
+ * 颜色保持 CSS 变量引用,避免在 JS 中读取主题并让 DaisyUI 主题切换自然生效。
5
+ * @module chart-theme
6
+ */
7
+ import type { ChartSeries } from './chart-types.js';
8
+ export declare const DAISYUI_CHART_THEME_COLORS: {
9
+ readonly primary: "var(--color-primary)";
10
+ readonly secondary: "var(--color-secondary)";
11
+ readonly accent: "var(--color-accent)";
12
+ readonly success: "var(--color-success)";
13
+ readonly warning: "var(--color-warning)";
14
+ readonly error: "var(--color-error)";
15
+ readonly info: "var(--color-info)";
16
+ };
17
+ /** DaisyUI 图表主题色按系列分配的稳定顺序。 */
18
+ export declare const DAISYUI_CHART_COLORS: readonly ["var(--color-primary)", "var(--color-secondary)", "var(--color-accent)", "var(--color-success)", "var(--color-warning)", "var(--color-error)", "var(--color-info)"];
19
+ /**
20
+ * 为缺少 color 的系列补齐 DaisyUI 主题色。
21
+ * 返回新数组,避免修改调用方传入的 series 对象。
22
+ */
23
+ export declare function resolveChartColors<TSeries extends ChartSeries>(series?: readonly TSeries[]): TSeries[] | undefined;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * @h-ai/ui — 图表主题色
3
+ *
4
+ * 颜色保持 CSS 变量引用,避免在 JS 中读取主题并让 DaisyUI 主题切换自然生效。
5
+ * @module chart-theme
6
+ */
7
+ export const DAISYUI_CHART_THEME_COLORS = {
8
+ // primary 用作第一条主数据系列,跟随当前 DaisyUI 5 主题主色。
9
+ primary: 'var(--color-primary)',
10
+ // secondary 用作第二条系列,和 primary 拉开视觉层级。
11
+ secondary: 'var(--color-secondary)',
12
+ // accent 用作第三条系列,补充主题中的强调色。
13
+ accent: 'var(--color-accent)',
14
+ // success 用作正向或增长类系列,也作为常规调色板的一员。
15
+ success: 'var(--color-success)',
16
+ // warning 用作需要注意的系列,维持 DaisyUI 语义一致性。
17
+ warning: 'var(--color-warning)',
18
+ // error 用作异常或下降类系列,也可作为高对比补充色。
19
+ error: 'var(--color-error)',
20
+ // info 用作信息类系列,补足多系列图表的第七种主题色。
21
+ info: 'var(--color-info)',
22
+ };
23
+ /** DaisyUI 图表主题色按系列分配的稳定顺序。 */
24
+ export const DAISYUI_CHART_COLORS = [
25
+ DAISYUI_CHART_THEME_COLORS.primary,
26
+ DAISYUI_CHART_THEME_COLORS.secondary,
27
+ DAISYUI_CHART_THEME_COLORS.accent,
28
+ DAISYUI_CHART_THEME_COLORS.success,
29
+ DAISYUI_CHART_THEME_COLORS.warning,
30
+ DAISYUI_CHART_THEME_COLORS.error,
31
+ DAISYUI_CHART_THEME_COLORS.info,
32
+ ];
33
+ /**
34
+ * 为缺少 color 的系列补齐 DaisyUI 主题色。
35
+ * 返回新数组,避免修改调用方传入的 series 对象。
36
+ */
37
+ export function resolveChartColors(series) {
38
+ if (!series)
39
+ return undefined;
40
+ return series.map((item, index) => ({
41
+ ...item,
42
+ color: item.color ?? DAISYUI_CHART_COLORS[index % DAISYUI_CHART_COLORS.length],
43
+ }));
44
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * @h-ai/ui — 图表组件类型定义
3
+ *
4
+ * 统一 Chart 只定义 @h-ai/ui 自身需要的轻量语义,其他字段通过索引签名透传给 LayerChart。
5
+ * @module chart-types
6
+ */
7
+ import type { DataAttributes } from '../../../types.js';
8
+ /** 图表数据单元允许的原子值,和 LayerChart 常见 accessor 输入保持一致。 */
9
+ export type ChartDataValue = string | number | Date;
10
+ /** 图表数据项;每个字段都可被 x/y/key/value 等 accessor 引用。 */
11
+ export type ChartDataItem = Record<string, ChartDataValue>;
12
+ /** LayerChart accessor 的常用子集,用于 series.value 等透传配置。 */
13
+ export type ChartAccessor = string | number | ((item: ChartDataItem) => unknown) | ChartAccessor[] | null | undefined;
14
+ /** 统一 Chart 支持的高阶图表类型。 */
15
+ export type ChartType = 'bar' | 'line' | 'area' | 'pie' | 'scatter';
16
+ /** Chart 预设高度档位;具体像素值在 Chart.svelte 中固定映射。 */
17
+ export type ChartSize = 'sm' | 'md' | 'lg';
18
+ /** 折线图渲染变体;default 保持 LayerChart 原生渲染,segmented-point 提供分段线 + 圆点样式。 */
19
+ export type ChartLineVariant = 'default' | 'segmented-point';
20
+ /** 折线图 tooltip 触发方式;nearest 会在绘图区内吸附最近数据点。 */
21
+ export type ChartTooltipMode = 'default' | 'nearest';
22
+ export interface ChartSeries {
23
+ /** 系列稳定标识;宽表数据通常对应数据项中的数值字段名。 */
24
+ key: string;
25
+ /** 系列展示名称;不传时 LayerChart 会按 key 或 value accessor 回退。 */
26
+ label?: string;
27
+ /** 系列颜色;不传时由 @h-ai/ui 按 DaisyUI 主题色顺序补齐。 */
28
+ color?: string;
29
+ /** LayerChart 原生 value accessor,用于覆盖默认按 key 取值的行为。 */
30
+ value?: ChartAccessor;
31
+ /** LayerChart 原生独立数据源,用于 long format 或每个系列不同数据的场景。 */
32
+ data?: ChartDataItem[];
33
+ /** 系列初始可见状态;透传给 LayerChart legend/series 状态管理。 */
34
+ selected?: boolean;
35
+ /** 传给 LayerChart 内部 mark 组件的原生 props,例如 Bars/Spline/Arc/Points props。 */
36
+ props?: Record<string, unknown>;
37
+ /** 单系列最大值;主要用于饼图等需要上限语义的 LayerChart 配置。 */
38
+ maxValue?: number;
39
+ /** 保留 LayerChart 新增 series 字段的透传能力,避免薄包装层限制底层 API。 */
40
+ [key: string]: unknown;
41
+ }
42
+ export interface ChartProps extends DataAttributes {
43
+ /** 选择内部 LayerChart 高阶组件;必填。 */
44
+ type: ChartType;
45
+ /** 图表数据源;空数组会触发内置空态。 */
46
+ data: ChartDataItem[];
47
+ /** x 轴字段名;饼图中默认映射为 key 和 label。 */
48
+ x: string;
49
+ /** y 轴字段名;传数组时会自动生成多系列配置。 */
50
+ y: string | string[];
51
+ /** 系列配置;未指定 color 的项会按 DaisyUI 主题色补齐。 */
52
+ series?: ChartSeries[];
53
+ /** LayerChart 原生 orientation;主要用于柱状图和折线图方向控制。 */
54
+ orientation?: 'horizontal' | 'vertical';
55
+ /** 折线图渲染变体;仅 type='line' 时生效,默认透传 LayerChart。 */
56
+ lineVariant?: ChartLineVariant;
57
+ /** 折线图 tooltip 触发方式;nearest 会在整块绘图区内吸附最近点。 */
58
+ tooltipMode?: ChartTooltipMode;
59
+ /** 折线图是否显示随 hover 变化的竖向参考线;仅 segmented-point 变体生效。 */
60
+ showCrosshair?: boolean;
61
+ /** 折线图是否显示数据点圆点;仅 segmented-point 变体生效,默认显示。 */
62
+ showPoints?: boolean;
63
+ /** @h-ai/ui 预设尺寸;未显式传 LayerChart height 时映射为固定高度。 */
64
+ size?: ChartSize;
65
+ /** 图表标题;不传时不渲染标题区域。 */
66
+ title?: string;
67
+ /** 图表说明;不传时不渲染说明文本。 */
68
+ description?: string;
69
+ /** 加载状态;为 true 时仅显示 DaisyUI loading spinner。 */
70
+ loading?: boolean;
71
+ /** 强制空态;为 true 或 data 为空时显示空数据提示。 */
72
+ empty?: boolean;
73
+ /** 外层容器类名;用于布局,不会覆盖 LayerChart 内部配置。 */
74
+ class?: string;
75
+ /** 其他所有字段会原样透传给当前 type 对应的 LayerChart 组件。 */
76
+ [key: string]: unknown;
77
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @h-ai/ui — 图表组件类型定义
3
+ *
4
+ * 统一 Chart 只定义 @h-ai/ui 自身需要的轻量语义,其他字段通过索引签名透传给 LayerChart。
5
+ * @module chart-types
6
+ */
7
+ export {};
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @h-ai/ui — 图表场景组件
3
+ *
4
+ * 仅导出统一 Chart 入口和公共类型;内部 renderer 不对外暴露。
5
+ * @module index
6
+ */
7
+ export type { ChartDataItem, ChartLineVariant, ChartProps, ChartSeries, ChartSize, ChartTooltipMode, ChartType } from './chart-types.js';
8
+ export { default as Chart } from './Chart.svelte';
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @h-ai/ui — 图表场景组件
3
+ *
4
+ * 仅导出统一 Chart 入口和公共类型;内部 renderer 不对外暴露。
5
+ * @module index
6
+ */
7
+ export { default as Chart } from './Chart.svelte';
@@ -0,0 +1,18 @@
1
+ <!--
2
+ =============================================================================
3
+ @h-ai/ui - AreaChart 内部渲染器
4
+ =============================================================================
5
+ 对 LayerChart AreaChart 的薄包装,仅做类型收窄和统一导入路径。
6
+ 不对外暴露;由 Chart.svelte 按 type='area' 内部路由调用。
7
+ =============================================================================
8
+ -->
9
+ <script lang='ts'>
10
+ import type { AreaChartProps } from 'layerchart'
11
+ import type { ChartDataItem } from '../chart-types.js'
12
+ import { AreaChart as LayerAreaChart } from 'layerchart'
13
+
14
+ /** 透传给 LayerChart AreaChart 的全部原生属性。 */
15
+ const { ...props }: AreaChartProps<ChartDataItem> = $props()
16
+ </script>
17
+
18
+ <LayerAreaChart {...props} />
@@ -0,0 +1,5 @@
1
+ import type { AreaChartProps } from 'layerchart';
2
+ import type { ChartDataItem } from '../chart-types.js';
3
+ declare const AreaChart: import("svelte").Component<AreaChartProps<ChartDataItem>, {}, "">;
4
+ type AreaChart = ReturnType<typeof AreaChart>;
5
+ export default AreaChart;
@@ -0,0 +1,18 @@
1
+ <!--
2
+ =============================================================================
3
+ @h-ai/ui - BarChart 内部渲染器
4
+ =============================================================================
5
+ 对 LayerChart BarChart 的薄包装,仅做类型收窄和统一导入路径。
6
+ 不对外暴露;由 Chart.svelte 按 type='bar' 内部路由调用。
7
+ =============================================================================
8
+ -->
9
+ <script lang='ts'>
10
+ import type { BarChartProps } from 'layerchart'
11
+ import type { ChartDataItem } from '../chart-types.js'
12
+ import { BarChart as LayerBarChart } from 'layerchart'
13
+
14
+ /** 透传给 LayerChart BarChart 的全部原生属性。 */
15
+ const { ...props }: BarChartProps<ChartDataItem> = $props()
16
+ </script>
17
+
18
+ <LayerBarChart {...props} />
@@ -0,0 +1,5 @@
1
+ import type { BarChartProps } from 'layerchart';
2
+ import type { ChartDataItem } from '../chart-types.js';
3
+ declare const BarChart: import("svelte").Component<BarChartProps<ChartDataItem>, {}, "">;
4
+ type BarChart = ReturnType<typeof BarChart>;
5
+ export default BarChart;
@@ -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;
@@ -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';
@@ -8,6 +8,8 @@
8
8
  export * from './ai/index.js';
9
9
  // App 应用级场景组件
10
10
  export * from './app/index.js';
11
+ // Chart 图表场景组件
12
+ export * from './charts/index.js';
11
13
  // CRUD 场景组件
12
14
  export * from './crud/index.js';
13
15
  // Crypto 场景组件
@@ -25,6 +25,8 @@
25
25
  "combobox_remove": "Remove",
26
26
  "data_table_actions": "Actions",
27
27
  "data_table_empty": "No data",
28
+ "chart_loading": "Loading chart",
29
+ "chart_empty": "No data",
28
30
  "confirm_title": "Confirm",
29
31
  "confirm_message": "Are you sure you want to proceed?",
30
32
  "confirm_ok": "Confirm",
@@ -25,6 +25,8 @@
25
25
  "combobox_remove": "移除",
26
26
  "data_table_actions": "操作",
27
27
  "data_table_empty": "暂无数据",
28
+ "chart_loading": "图表加载中",
29
+ "chart_empty": "暂无数据",
28
30
  "confirm_title": "确认",
29
31
  "confirm_message": "确定要继续吗?",
30
32
  "confirm_ok": "确认",
package/dist/types.d.ts CHANGED
@@ -12,6 +12,7 @@ export type DataAttributeValue = string | number | boolean | null | undefined;
12
12
  export interface DataAttributes {
13
13
  [key: `data-${string}`]: DataAttributeValue;
14
14
  }
15
+ export type { ChartDataItem, ChartProps, ChartSeries, ChartSize, ChartType } from './components/scenes/charts/chart-types.js';
15
16
  /**
16
17
  * 基础变体类型
17
18
  */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@h-ai/ui",
3
3
  "type": "module",
4
- "version": "0.1.0-alpha.34",
4
+ "version": "0.1.0-alpha.35",
5
5
  "description": "Hai Framework UI component library for Svelte 5 Runes.",
6
6
  "license": "Apache-2.0",
7
7
  "svelte": "./dist/index.js",
@@ -47,11 +47,12 @@
47
47
  "bits-ui": "^2.18.1",
48
48
  "html2canvas": "^1.4.1",
49
49
  "jspdf": "^4.2.1",
50
+ "layerchart": "2.0.1",
50
51
  "marked": "^18.0.0",
51
52
  "mermaid": "^11.12.0",
52
53
  "shiki": "^4.0.2",
53
54
  "tailwind-merge": "^3.5.0",
54
- "@h-ai/core": "0.1.0-alpha.34"
55
+ "@h-ai/core": "0.1.0-alpha.35"
55
56
  },
56
57
  "devDependencies": {
57
58
  "@sveltejs/package": "^2.5.7",