@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.
- package/README.md +29 -8
- package/auto-import.js +2 -2
- package/dist/components/compounds/Dropdown.svelte +2 -0
- package/dist/components/compounds/Modal.svelte +22 -24
- package/dist/components/compounds/Pagination.svelte +15 -7
- package/dist/components/compounds/Tabs.svelte +1 -0
- package/dist/components/primitives/IconButton.svelte +1 -15
- package/dist/components/primitives/Select.svelte +3 -1
- package/dist/components/scenes/charts/Chart.svelte +152 -0
- package/dist/components/scenes/charts/Chart.svelte.d.ts +5 -0
- package/dist/components/scenes/charts/chart-theme.d.ts +23 -0
- package/dist/components/scenes/charts/chart-theme.js +44 -0
- package/dist/components/scenes/charts/chart-types.d.ts +77 -0
- package/dist/components/scenes/charts/chart-types.js +7 -0
- package/dist/components/scenes/charts/index.d.ts +8 -0
- package/dist/components/scenes/charts/index.js +7 -0
- package/dist/components/scenes/charts/renderers/AreaChart.svelte +18 -0
- package/dist/components/scenes/charts/renderers/AreaChart.svelte.d.ts +5 -0
- package/dist/components/scenes/charts/renderers/BarChart.svelte +18 -0
- package/dist/components/scenes/charts/renderers/BarChart.svelte.d.ts +5 -0
- package/dist/components/scenes/charts/renderers/LineChart.svelte +571 -0
- package/dist/components/scenes/charts/renderers/LineChart.svelte.d.ts +19 -0
- package/dist/components/scenes/charts/renderers/PieChart.svelte +18 -0
- package/dist/components/scenes/charts/renderers/PieChart.svelte.d.ts +5 -0
- package/dist/components/scenes/charts/renderers/ScatterChart.svelte +18 -0
- package/dist/components/scenes/charts/renderers/ScatterChart.svelte.d.ts +5 -0
- package/dist/components/scenes/crud/CrudPage.svelte +4 -0
- package/dist/components/scenes/crud/crud-types.d.ts +4 -0
- package/dist/components/scenes/index.d.ts +1 -0
- package/dist/components/scenes/index.js +2 -0
- package/dist/components/scenes/storage/AvatarUpload.svelte +21 -71
- package/dist/components/scenes/storage/FileUpload.svelte +24 -88
- package/dist/components/scenes/storage/ImageUpload.svelte +53 -67
- package/dist/components/scenes/types.d.ts +26 -18
- package/dist/internal/icon-safety.d.ts +7 -0
- package/dist/internal/icon-safety.js +33 -0
- package/dist/messages/en-US.json +2 -5
- package/dist/messages/zh-CN.json +2 -5
- package/dist/types.d.ts +7 -0
- 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(
|
|
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(
|
|
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`, `
|
|
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
|
-
//
|
|
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
|
-
|
|
118
|
+
{#if showPageInfo}
|
|
119
|
+
<span class={tableTextClass}>{pageInfoText}</span>
|
|
120
|
+
{/if}
|
|
117
121
|
|
|
118
122
|
<div class='flex items-center gap-1'>
|
|
119
|
-
|
|
120
|
-
<
|
|
121
|
-
|
|
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
|
-
|
|
129
|
-
<
|
|
130
|
-
|
|
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}
|
|
@@ -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
|
|
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,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,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,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;
|