@h-ai/ui 0.1.0-alpha.34 → 0.1.0-alpha.36

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 (40) hide show
  1. package/README.md +29 -8
  2. package/auto-import.js +2 -2
  3. package/dist/components/compounds/Dropdown.svelte +2 -0
  4. package/dist/components/compounds/Modal.svelte +22 -24
  5. package/dist/components/compounds/Pagination.svelte +15 -7
  6. package/dist/components/compounds/Tabs.svelte +1 -0
  7. package/dist/components/primitives/IconButton.svelte +1 -15
  8. package/dist/components/primitives/Select.svelte +3 -1
  9. package/dist/components/scenes/charts/Chart.svelte +152 -0
  10. package/dist/components/scenes/charts/Chart.svelte.d.ts +5 -0
  11. package/dist/components/scenes/charts/chart-theme.d.ts +23 -0
  12. package/dist/components/scenes/charts/chart-theme.js +44 -0
  13. package/dist/components/scenes/charts/chart-types.d.ts +77 -0
  14. package/dist/components/scenes/charts/chart-types.js +7 -0
  15. package/dist/components/scenes/charts/index.d.ts +8 -0
  16. package/dist/components/scenes/charts/index.js +7 -0
  17. package/dist/components/scenes/charts/renderers/AreaChart.svelte +18 -0
  18. package/dist/components/scenes/charts/renderers/AreaChart.svelte.d.ts +5 -0
  19. package/dist/components/scenes/charts/renderers/BarChart.svelte +18 -0
  20. package/dist/components/scenes/charts/renderers/BarChart.svelte.d.ts +5 -0
  21. package/dist/components/scenes/charts/renderers/LineChart.svelte +571 -0
  22. package/dist/components/scenes/charts/renderers/LineChart.svelte.d.ts +19 -0
  23. package/dist/components/scenes/charts/renderers/PieChart.svelte +18 -0
  24. package/dist/components/scenes/charts/renderers/PieChart.svelte.d.ts +5 -0
  25. package/dist/components/scenes/charts/renderers/ScatterChart.svelte +18 -0
  26. package/dist/components/scenes/charts/renderers/ScatterChart.svelte.d.ts +5 -0
  27. package/dist/components/scenes/crud/CrudPage.svelte +4 -0
  28. package/dist/components/scenes/crud/crud-types.d.ts +4 -0
  29. package/dist/components/scenes/index.d.ts +1 -0
  30. package/dist/components/scenes/index.js +2 -0
  31. package/dist/components/scenes/storage/AvatarUpload.svelte +21 -71
  32. package/dist/components/scenes/storage/FileUpload.svelte +24 -88
  33. package/dist/components/scenes/storage/ImageUpload.svelte +53 -67
  34. package/dist/components/scenes/types.d.ts +26 -18
  35. package/dist/internal/icon-safety.d.ts +7 -0
  36. package/dist/internal/icon-safety.js +33 -0
  37. package/dist/messages/en-US.json +2 -5
  38. package/dist/messages/zh-CN.json +2 -5
  39. package/dist/types.d.ts +7 -0
  40. package/package.json +3 -2
package/README.md CHANGED
@@ -150,7 +150,7 @@ components/
150
150
  | `Spinner` | 加载动画 | `size`, `variant` |
151
151
  | `Progress` | 进度条 | `value`, `max`, `variant`, `striped`, `animated` |
152
152
 
153
- ### 组合组件 Compounds(33 个)
153
+ ### 组合组件 Compounds(36 个)
154
154
 
155
155
  #### 表单
156
156
 
@@ -212,10 +212,10 @@ components/
212
212
  | 组件 | 描述 | 主要属性 |
213
213
  | ------------ | -------- | -------------------------------------------- |
214
214
  | `Tabs` | 标签页 | `items`, `active`, `type`, `size` |
215
- | `Pagination` | 分页(统一 table 风格) | `page`, `total`, `pageSize`, `showTotal`, `showJumper`, `showSizeChanger` |
215
+ | `Pagination` | 分页(统一 table 风格) | `page`, `total`, `pageSize`, `showTotal`, `showJumper`, `showSizeChanger`, `showPageInfo`, `showFirstLast` |
216
216
  | `Breadcrumb` | 面包屑 | `items`, `separator` |
217
217
  | `Steps` | 步骤条 | `items`, `current`, `direction`, `clickable` |
218
- | `Dropdown` | 下拉菜单 | `items`, `trigger`, `position` |
218
+ | `Dropdown` | 下拉菜单 | `items`, `trigger`, `triggerClass`, `position` |
219
219
  | `Tooltip` | 提示 | `content`, `position`, `delay` |
220
220
 
221
221
  #### 状态占位
@@ -232,7 +232,7 @@ components/
232
232
  | ------------ | -------- | ------------------------------------------- |
233
233
  | `PageHeader` | 页面头部 | `title`, `description`,支持 `actions` 插槽 |
234
234
 
235
- ### 场景组件 Scenes(33 个)
235
+ ### 场景组件 Scenes(34 个)
236
236
 
237
237
  #### App 应用级(7 个)
238
238
 
@@ -264,11 +264,13 @@ components/
264
264
 
265
265
  | 组件 | 描述 | 主要属性 |
266
266
  | -------------- | -------- | -------------------------------------------------------- |
267
- | `FileUpload` | 文件上传 | `accept`, `maxSize`, `maxFiles`, `multiple`, `uploadUrl` |
268
- | `ImageUpload` | 图片上传 | `value`, `accept`, `maxSize`, `aspectRatio` |
269
- | `AvatarUpload` | 头像上传 | `value`, `size`, `maxSize`, `fallback` |
267
+ | `FileUpload` | 文件上传 | `accept`, `maxSize`, `maxFiles`, `multiple`, `uploadHandler` |
268
+ | `ImageUpload` | 图片上传 | `value`, `accept`, `maxSize`, `aspectRatio`, `uploadHandler` |
269
+ | `AvatarUpload` | 头像上传 | `value`, `size`, `maxSize`, `fallback`, `uploadHandler` |
270
270
  | `FileList` | 文件列表 | `files`, `layout`, `showDelete`, `showDownload` |
271
271
 
272
+ 上传组件不内置 presign、请求头或 PUT/POST 协议。应用层通过 `uploadHandler(file, { signal, onProgress })` 调用自己的 service,并返回 `{ url?, response? }`;图片和头像上传需要返回可展示的 `url`。未提供 handler 时,`FileUpload` 只选择文件,图片/头像组件只生成本地预览。
273
+
272
274
  #### Crypto 加密展示(3 个)
273
275
 
274
276
  | 组件 | 描述 | 主要属性 |
@@ -309,6 +311,25 @@ components/
309
311
  />
310
312
  ```
311
313
 
314
+ #### Charts 图表
315
+
316
+ `Chart type='line'` 默认透传 LayerChart 原生折线图;需要监控看板常见的“分段线 + 圆点 + 最近点 hover + 竖向参考线”时,使用 `lineVariant='segmented-point'`:
317
+
318
+ ```svelte
319
+ <Chart
320
+ type='line'
321
+ data={trendData}
322
+ x='label'
323
+ y={['rateLimit', 'circuitBreak']}
324
+ series={trendSeries}
325
+ lineVariant='segmented-point'
326
+ tooltipMode='nearest'
327
+ showCrosshair
328
+ legend
329
+ grid
330
+ />
331
+ ```
332
+
312
333
  #### CRUD 场景(5 个)
313
334
 
314
335
  | 组件 | 描述 | 主要属性 / 能力 |
@@ -333,7 +354,7 @@ components/
333
354
  > - 抽屉:`form.drawerSize` 选预设尺寸,`form.drawerWidth` 传任意 CSS 宽度(优先级更高)。
334
355
  > - 弹窗:`form.modalSize` 选预设尺寸,`form.modalWidth` / `form.modalHeight` 传任意 CSS 尺寸。
335
356
  >
336
- > `pagination` 配置分页栏(始终显示):`showSizeChanger`、`pageSizeOptions`、`showJumper`、`showTotal` 默认开启。
357
+ > `pagination` 配置分页栏(始终显示):`showSizeChanger`、`pageSizeOptions`、`showJumper`、`showTotal` 默认开启;紧凑视图可用 `showPageInfo` 关闭页码文案、`showFirstLast` 关闭首页 / 末页按钮。
337
358
  >
338
359
  > `density` 配置列表密度:`'normal'`(默认)或 `'compact'`(紧凑)。它只影响列表行、行内操作、分页与创建/编辑/详情表单元素,不影响页头与筛选栏。
339
360
 
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
 
@@ -13,6 +13,7 @@
13
13
  position = 'bottom',
14
14
  align = 'start',
15
15
  class: className = '',
16
+ triggerClass = '',
16
17
  onselect,
17
18
  children,
18
19
  ...restProps
@@ -61,6 +62,7 @@
61
62
  <BareButton
62
63
  tabindex={0}
63
64
  role='button'
65
+ class={triggerClass}
64
66
  onclick={toggleDropdown}
65
67
  onkeydown={e => e.key === 'Enter' && toggleDropdown()}
66
68
  >
@@ -28,8 +28,6 @@
28
28
  }: ModalProps & DataAttributes = $props()
29
29
 
30
30
  const dataAttributes = $derived(getDataAttributes(restProps))
31
- // modalElement 持有 overlay 容器引用;不再依赖原生 `<dialog>`,改用 fixed + z-index 叠加。
32
- let modalElement: HTMLDivElement | undefined = $state()
33
31
 
34
32
  // 标题区仅在显式提供标题或自定义 header 时出现;
35
33
  // 仅需关闭按钮的场景改为右上角悬浮按钮,避免业务侧额外占一整行头部。
@@ -93,27 +91,9 @@
93
91
  onclose?.()
94
92
  }
95
93
 
96
- function handleDialogClick(event: MouseEvent) {
97
- if (event.target === event.currentTarget && closeOnBackdrop) {
98
- handleClose()
99
- }
100
- }
101
-
102
- function handleViewportKeydown(event: KeyboardEvent) {
103
- if (
104
- event.target === event.currentTarget
105
- && closeOnBackdrop
106
- && (event.key === 'Enter' || event.key === ' ')
107
- ) {
108
- event.preventDefault()
109
- handleClose()
110
- }
111
- }
112
-
113
94
  </script>
114
95
 
115
96
  <div {...dataAttributes}
116
- bind:this={modalElement}
117
97
  class='hai-modal'
118
98
  style:display={open ? '' : 'none'}
119
99
  aria-hidden={open ? undefined : 'true'}
@@ -124,11 +104,19 @@
124
104
  >
125
105
  <div
126
106
  class='hai-modal__viewport'
127
- tabindex='-1'
128
- aria-label={uiM('common_close')}
129
- onclick={handleDialogClick}
130
- onkeydown={handleViewportKeydown}
131
107
  >
108
+ {#if closeOnBackdrop}
109
+ <button
110
+ type='button'
111
+ class='hai-modal__backdrop'
112
+ aria-label={uiM('common_close')}
113
+ tabindex='-1'
114
+ onclick={handleClose}
115
+ ></button>
116
+ {:else}
117
+ <div class='hai-modal__backdrop' aria-hidden='true'></div>
118
+ {/if}
119
+
132
120
  <div class={modalBoxClass} style={panelStyle}>
133
121
  {#if hasFloatingClose}
134
122
  <button
@@ -230,12 +218,22 @@
230
218
  place-items: center;
231
219
  padding: clamp(1rem, 2.6vw, 2rem);
232
220
  pointer-events: auto;
221
+ }
222
+
223
+ .hai-modal__backdrop {
224
+ position: absolute;
225
+ inset: 0;
226
+ width: 100%;
227
+ height: 100%;
228
+ padding: 0;
229
+ border: 0;
233
230
  background: rgb(15 23 42 / 0.26);
234
231
  background: color-mix(in srgb, var(--color-base-content) 15%, transparent);
235
232
  backdrop-filter: blur(2px);
236
233
  }
237
234
 
238
235
  .hai-modal__panel {
236
+ z-index: 1;
239
237
  width: var(--hai-modal-width, min(100%, 44rem));
240
238
  max-width: min(100%, calc(100vw - clamp(2rem, 5.2vw, 4rem)));
241
239
  height: var(--hai-modal-height, auto);
@@ -24,6 +24,8 @@
24
24
  showTotal = true,
25
25
  showJumper = false,
26
26
  showSizeChanger = false,
27
+ showPageInfo = true,
28
+ showFirstLast = true,
27
29
  pageSizeOptions = [10, 20, 50, 100],
28
30
  labels = {},
29
31
  class: className = '',
@@ -113,21 +115,27 @@
113
115
  </div>
114
116
  {/if}
115
117
 
116
- <span class={tableTextClass}>{pageInfoText}</span>
118
+ {#if showPageInfo}
119
+ <span class={tableTextClass}>{pageInfoText}</span>
120
+ {/if}
117
121
 
118
122
  <div class='flex items-center gap-1'>
119
- <BareButton class={navBtnClass} disabled={page === 1} onclick={() => goToPage(1)} ariaLabel='First page'>
120
- <span class={cn('icon-[tabler--chevrons-left]', navIconClass)}></span>
121
- </BareButton>
123
+ {#if showFirstLast}
124
+ <BareButton class={navBtnClass} disabled={page === 1} onclick={() => goToPage(1)} ariaLabel='First page'>
125
+ <span class={cn('icon-[tabler--chevrons-left]', navIconClass)}></span>
126
+ </BareButton>
127
+ {/if}
122
128
  <BareButton class={navBtnClass} disabled={page === 1} onclick={() => goToPage(page - 1)} ariaLabel='Previous page'>
123
129
  <span class={cn('icon-[tabler--chevron-left]', navIconClass)}></span>
124
130
  </BareButton>
125
131
  <BareButton class={navBtnClass} disabled={page === totalPages} onclick={() => goToPage(page + 1)} ariaLabel='Next page'>
126
132
  <span class={cn('icon-[tabler--chevron-right]', navIconClass)}></span>
127
133
  </BareButton>
128
- <BareButton class={navBtnClass} disabled={page === totalPages} onclick={() => goToPage(totalPages)} ariaLabel='Last page'>
129
- <span class={cn('icon-[tabler--chevrons-right]', navIconClass)}></span>
130
- </BareButton>
134
+ {#if showFirstLast}
135
+ <BareButton class={navBtnClass} disabled={page === totalPages} onclick={() => goToPage(totalPages)} ariaLabel='Last page'>
136
+ <span class={cn('icon-[tabler--chevrons-right]', navIconClass)}></span>
137
+ </BareButton>
138
+ {/if}
131
139
  </div>
132
140
 
133
141
  {#if showJumper}
@@ -62,6 +62,7 @@
62
62
  {#each items as item (item.key)}
63
63
  <BareButton
64
64
  role='tab'
65
+ ariaSelected={active === item.key}
65
66
  class={cn('tab', active === item.key && 'tab-active')}
66
67
  disabled={item.disabled}
67
68
  onclick={() => handleSelect(item.key)}
@@ -9,11 +9,9 @@
9
9
  -->
10
10
  <script lang='ts'>
11
11
  import type { DataAttributes, IconButtonProps } from '../../types.js'
12
+ import { sanitizeInlineSvg } from '../../internal/icon-safety.js'
12
13
  import { cn, getDataAttributes, getSizeClass, getVariantClass } from '../../utils.js'
13
14
 
14
- const SAFE_INLINE_SVG_REGEX = /^<svg[\s\S]*<\/svg>$/i
15
- const UNSAFE_INLINE_SVG_REGEX = /<\s*(?:script|foreignObject|iframe|object|embed|link|meta|img)\b|\son[a-z]+\s*=|(?:href|xlink:href)\s*=\s*['"]\s*(?:javascript:|vbscript:|data:text\/html)/i
16
-
17
15
  const {
18
16
  icon,
19
17
  label = '',
@@ -72,18 +70,6 @@
72
70
  onclick?.(e)
73
71
  }
74
72
 
75
- function sanitizeInlineSvg(markup: string): string {
76
- const trimmed = markup.trim()
77
- if (!trimmed) {
78
- return ''
79
- }
80
-
81
- if (!SAFE_INLINE_SVG_REGEX.test(trimmed) || UNSAFE_INLINE_SVG_REGEX.test(trimmed)) {
82
- return ''
83
- }
84
-
85
- return trimmed
86
- }
87
73
  </script>
88
74
 
89
75
  {#if tooltip}
@@ -112,6 +112,8 @@
112
112
  document.addEventListener('click', handleClickOutside)
113
113
  return () => document.removeEventListener('click', handleClickOutside)
114
114
  }
115
+
116
+ return undefined
115
117
  })
116
118
 
117
119
  // 计算下拉层位置(fixed,基于触发器视口坐标;空间不足时向上翻转)
@@ -264,7 +266,7 @@
264
266
  {/if}
265
267
 
266
268
  <!-- 右侧图标区域 -->
267
- <div class='flex items-center pr-2 shrink-0'>
269
+ <div class='flex items-center shrink-0'>
268
270
  {#if showClear}
269
271
  <button
270
272
  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;