@h-ai/ui 0.1.0-alpha.45 → 0.1.0-alpha.47
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 +6 -5
- package/dist/components/compounds/DataTable.svelte +21 -2
- package/dist/components/compounds/DataTable.svelte.d.ts +2 -0
- package/dist/components/compounds/Drawer.svelte +121 -2
- package/dist/components/scenes/crud/CrudDetailPanel.svelte +15 -1
- package/dist/components/scenes/crud/CrudDetailPanel.svelte.d.ts +2 -0
- package/dist/components/scenes/crud/CrudEditPanel.svelte +16 -1
- package/dist/components/scenes/crud/CrudEditPanel.svelte.d.ts +2 -0
- package/dist/components/scenes/crud/CrudPage.svelte +14 -0
- package/dist/components/scenes/crud/CrudPage.svelte.d.ts +2 -0
- package/dist/components/scenes/crud/crud-types.d.ts +4 -0
- package/dist/messages/en-US.json +1 -0
- package/dist/messages/zh-CN.json +1 -0
- package/dist/types.d.ts +4 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -172,7 +172,7 @@ components/
|
|
|
172
172
|
| 组件 | 描述 | 主要属性 |
|
|
173
173
|
| --------- | ------ | ------------------------------------------------------- |
|
|
174
174
|
| `Modal` | 模态框 | `open`, `title`, `size`, `radius`, `bodyOverflow`, `showClose` |
|
|
175
|
-
| `Drawer` | 抽屉 | `open`, `position`, `title`, `size`
|
|
175
|
+
| `Drawer` | 抽屉 | `open`, `position`, `title`, `size`, `width`, `resizable`, `widthStorageKey` |
|
|
176
176
|
| `Confirm` | 确认框 | `open`, `title`, `message`, `variant`, `onconfirm` |
|
|
177
177
|
| `Popover` | 弹出层 | `open`, `position`, `trigger`, `offset` |
|
|
178
178
|
|
|
@@ -334,13 +334,13 @@ components/
|
|
|
334
334
|
|
|
335
335
|
| 组件 | 描述 | 主要属性 / 能力 |
|
|
336
336
|
| -------------------- | ------------ | ---------------- |
|
|
337
|
-
| `CrudPage` | CRUD 主页面 | `crud`, `data`, `permissions`, `form`, `pagination`, `density`, `showHeader`, `toolbarStyle`, `toolbarLeading`, `toolbarActions`, `card`, `tableCell`, `onrowclick`, `sortableColumns`, `nav` |
|
|
337
|
+
| `CrudPage` | CRUD 主页面 | `crud`, `data`, `permissions`, `form`, `pagination`, `density`, `detailColumns`, `showHeader`, `toolbarStyle`, `toolbarLeading`, `toolbarActions`, `card`, `tableCell`, `onrowclick`, `sortableColumns`, `nav` |
|
|
338
338
|
| `CrudFilterBar` | 过滤工具栏(搜索 + 多类型过滤 + 重置) | `filterFields`, `searchValue`, `onsearch`, `onfilterchange`, `onreset` |
|
|
339
339
|
| `CrudDetailPanel` | 详情面板(抽屉/弹窗) | `open`, `item`, `fields`, `variant`, `drawerWidth`, `modalSize` |
|
|
340
340
|
| `CrudEditPanel` | 编辑面板(抽屉/弹窗) | `open`, `fields`, `variant`, `drawerWidth`, `modalSize`, `onsubmit` |
|
|
341
341
|
| `CrudDeleteConfirm` | 删除确认框 | `open`, `loading`, `onconfirm` |
|
|
342
342
|
|
|
343
|
-
> `CrudPage` 默认使用 `TableToolbar` 图标弹层工具栏,也可通过 `toolbarStyle='filter-bar'` 切回平铺筛选栏;`toolbarLeading` 用于注入左侧标题或说明,配合 `showHeader={false}` 可避免重复标题;`toolbarActions` 注入右侧业务操作,`card` 插槽可将列表项替换为卡片视图,`tableCell` 可按行和列 key 渲染交互式单元格内容(例如 Tooltip),`sortableColumns` 控制服务端排序字段。过滤工具栏支持 `select`、`boolean`、`number`、`date`、`date-range`、`text`。
|
|
343
|
+
> `CrudPage` 默认使用 `TableToolbar` 图标弹层工具栏,也可通过 `toolbarStyle='filter-bar'` 切回平铺筛选栏;`detailColumns={['title']}` 可把标题等指定列绑定为详情入口;`toolbarLeading` 用于注入左侧标题或说明,配合 `showHeader={false}` 可避免重复标题;`toolbarActions` 注入右侧业务操作,`card` 插槽可将列表项替换为卡片视图,`tableCell` 可按行和列 key 渲染交互式单元格内容(例如 Tooltip),`sortableColumns` 控制服务端排序字段。过滤工具栏支持 `select`、`boolean`、`number`、`date`、`date-range`、`text`。
|
|
344
344
|
>
|
|
345
345
|
> 页面根容器会占满父级可用高度。表格无论是否有数据都会撑满剩余空间,仅数据区域滚动;表头固定在数据区顶部,分页栏固定在列表卡片底部。
|
|
346
346
|
>
|
|
@@ -355,7 +355,7 @@ components/
|
|
|
355
355
|
> `CrudPage` 通过 `form` 配置新建/编辑的展示形式:
|
|
356
356
|
>
|
|
357
357
|
> - `form.variant`:`'drawer'`(抽屉,默认)或 `'modal'`(弹出窗口)。
|
|
358
|
-
> - 抽屉:`form.drawerSize` 选预设尺寸,`form.drawerWidth` 传任意 CSS
|
|
358
|
+
> - 抽屉:`form.drawerSize` 选预设尺寸,`form.drawerWidth` 传任意 CSS 宽度(优先级更高);默认可拖动左侧边缘调整宽度,并按资源记忆,`form.drawerResizable=false` 可关闭。
|
|
359
359
|
> - 弹窗:`form.modalSize` 选预设尺寸,`form.modalWidth` / `form.modalHeight` 传任意 CSS 尺寸。
|
|
360
360
|
>
|
|
361
361
|
> `pagination` 配置分页栏(始终显示):`showSizeChanger`、`pageSizeOptions`、`showJumper`、`showTotal` 默认开启;紧凑视图可用 `showPageInfo` 关闭页码文案、`showFirstLast` 关闭首页 / 末页按钮。
|
|
@@ -488,12 +488,13 @@ components/
|
|
|
488
488
|
form={{ variant: 'modal', modalSize: 'lg' }}
|
|
489
489
|
pagination={{ showSizeChanger: true, showJumper: true, pageSizeOptions: [10, 20, 50] }}
|
|
490
490
|
density='compact'
|
|
491
|
+
detailColumns={['name']}
|
|
491
492
|
sortableColumns={[{ key: 'createdAt', label: '创建时间' }]}
|
|
492
493
|
{nav}
|
|
493
494
|
/>
|
|
494
495
|
```
|
|
495
496
|
|
|
496
|
-
> 抽屉形式可改用 `form={{ variant: 'drawer', drawerWidth: '40rem' }}
|
|
497
|
+
> 抽屉形式可改用 `form={{ variant: 'drawer', drawerWidth: '40rem' }}`;用户拖动后的宽度会覆盖初始宽度并按资源记忆。
|
|
497
498
|
|
|
498
499
|
### 登录页面
|
|
499
500
|
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
@prop {Snippet<[T, string]>} cell - 单元格内容插槽
|
|
9
9
|
@prop {Snippet<[T]>} actions - 操作按钮插槽
|
|
10
10
|
@prop {(item: T) => void} onrowclick - 行点击回调
|
|
11
|
+
@prop {(item: T) => void} columns[].onclick - 指定列的单元格点击回调
|
|
11
12
|
@prop {Snippet} empty - 空状态插槽
|
|
12
13
|
@prop {boolean} loading - 是否加载中
|
|
13
14
|
@prop {boolean} striped - 是否斑马纹
|
|
@@ -54,7 +55,7 @@
|
|
|
54
55
|
...restProps
|
|
55
56
|
}: {
|
|
56
57
|
data: T[]
|
|
57
|
-
columns: { key: keyof T | string, label: string, width?: string, align?: 'left' | 'center' | 'right', sortable?: boolean, render?: (item: T) => string }[]
|
|
58
|
+
columns: { key: keyof T | string, label: string, width?: string, align?: 'left' | 'center' | 'right', sortable?: boolean, render?: (item: T) => string, onclick?: (item: T) => void }[]
|
|
58
59
|
keyField: keyof T
|
|
59
60
|
/** 自定义单元格内容;参数依次为行记录和列 key。 */
|
|
60
61
|
cell?: Snippet<[T, string]>
|
|
@@ -91,6 +92,8 @@
|
|
|
91
92
|
sortable?: boolean
|
|
92
93
|
/** 自定义单元格格式化函数。 */
|
|
93
94
|
render?: (item: T) => string
|
|
95
|
+
/** 点击当前列单元格时的回调。 */
|
|
96
|
+
onclick?: (item: T) => void
|
|
94
97
|
}
|
|
95
98
|
|
|
96
99
|
// ─── 排序状态 ───
|
|
@@ -341,7 +344,23 @@
|
|
|
341
344
|
<tr class='{rowClass} {onrowclick ? 'cursor-pointer' : ''}' onclick={() => onrowclick?.(item)}>
|
|
342
345
|
{#each columns as col (String(col.key))}
|
|
343
346
|
<td style={getColumnStyle(col)} class={getAlignClass(col.align)}>
|
|
344
|
-
{#if
|
|
347
|
+
{#if col.onclick}
|
|
348
|
+
<button
|
|
349
|
+
type='button'
|
|
350
|
+
class='block min-w-0 max-w-full cursor-pointer rounded-sm text-inherit hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary'
|
|
351
|
+
style={getContentStyle(col)}
|
|
352
|
+
onclick={(event) => {
|
|
353
|
+
event.stopPropagation()
|
|
354
|
+
col.onclick?.(item)
|
|
355
|
+
}}
|
|
356
|
+
>
|
|
357
|
+
{#if cell}
|
|
358
|
+
{@render cell(item, String(col.key))}
|
|
359
|
+
{:else}
|
|
360
|
+
<span class='block min-w-0 truncate' title={getValue(item, col)}>{getValue(item, col)}</span>
|
|
361
|
+
{/if}
|
|
362
|
+
</button>
|
|
363
|
+
{:else if cell}
|
|
345
364
|
<div class='min-w-0' style={getContentStyle(col)}>
|
|
346
365
|
{@render cell(item, String(col.key))}
|
|
347
366
|
</div>
|
|
@@ -10,6 +10,7 @@ declare function $$render<T>(): {
|
|
|
10
10
|
align?: "left" | "center" | "right";
|
|
11
11
|
sortable?: boolean;
|
|
12
12
|
render?: (item: T) => string;
|
|
13
|
+
onclick?: (item: T) => void;
|
|
13
14
|
}[];
|
|
14
15
|
keyField: keyof T;
|
|
15
16
|
/** 自定义单元格内容;参数依次为行记录和列 key。 */
|
|
@@ -59,6 +60,7 @@ interface $$IsomorphicComponent {
|
|
|
59
60
|
* @prop {Snippet<[T, string]>} cell - 单元格内容插槽
|
|
60
61
|
* @prop {Snippet<[T]>} actions - 操作按钮插槽
|
|
61
62
|
* @prop {(item: T) => void} onrowclick - 行点击回调
|
|
63
|
+
* @prop {(item: T) => void} columns[].onclick - 指定列的单元格点击回调
|
|
62
64
|
* @prop {Snippet} empty - 空状态插槽
|
|
63
65
|
* @prop {boolean} loading - 是否加载中
|
|
64
66
|
* @prop {boolean} striped - 是否斑马纹
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
-->
|
|
11
11
|
<script lang='ts'>
|
|
12
12
|
import type { DataAttributes, DrawerProps } from '../../types.js'
|
|
13
|
+
import { onDestroy, onMount } from 'svelte'
|
|
14
|
+
import { readStoredValue, writeStoredValue } from '../../internal/browser-safety.js'
|
|
13
15
|
import { uiM } from '../../messages.js'
|
|
14
16
|
import { cn, generateId, getDataAttributes } from '../../utils.js'
|
|
15
17
|
import IconButton from '../primitives/IconButton.svelte'
|
|
@@ -21,6 +23,8 @@
|
|
|
21
23
|
position = 'right',
|
|
22
24
|
size = 'md',
|
|
23
25
|
width,
|
|
26
|
+
resizable = false,
|
|
27
|
+
widthStorageKey,
|
|
24
28
|
closeOnBackdrop = true,
|
|
25
29
|
showClose = true,
|
|
26
30
|
class: className = '',
|
|
@@ -31,6 +35,16 @@
|
|
|
31
35
|
|
|
32
36
|
const dataAttributes = $derived(getDataAttributes(restProps))
|
|
33
37
|
const id = generateId('drawer')
|
|
38
|
+
/** 用户拖动后的像素宽度;优先于 width 与 size。 */
|
|
39
|
+
let resizedWidth = $state<number | null>(null)
|
|
40
|
+
/** 与响应式渲染解耦的同步宽度,确保 mouseup 时持久化最后一次 mousemove 的值。 */
|
|
41
|
+
let resizeWidthForStorage: number | null = null
|
|
42
|
+
/** 当前拖动起点的指针横坐标。 */
|
|
43
|
+
let resizeStartX = 0
|
|
44
|
+
/** 当前拖动起点的抽屉宽度。 */
|
|
45
|
+
let resizeStartWidth = 0
|
|
46
|
+
/** 拖动期间临时保存页面原有的文本选择样式。 */
|
|
47
|
+
let previousUserSelect = ''
|
|
34
48
|
|
|
35
49
|
const sizeMap = {
|
|
36
50
|
'xs': 'w-60',
|
|
@@ -45,7 +59,15 @@
|
|
|
45
59
|
|
|
46
60
|
// width 优先:传入自定义 CSS 宽度时通过 inline style 覆盖预设宽度类
|
|
47
61
|
const hasCustomWidth = $derived(typeof width === 'string' && width.trim().length > 0)
|
|
48
|
-
const widthStyle = $derived(
|
|
62
|
+
const widthStyle = $derived(
|
|
63
|
+
resizedWidth !== null
|
|
64
|
+
? `width: ${resizedWidth}px; max-width: calc(100vw - 1rem)`
|
|
65
|
+
: hasCustomWidth
|
|
66
|
+
? `width: ${width!.trim()}; max-width: 100vw`
|
|
67
|
+
: undefined,
|
|
68
|
+
)
|
|
69
|
+
/** 纵向抽屉没有横向宽度拖动语义。 */
|
|
70
|
+
const canResize = $derived(resizable && (position === 'left' || position === 'right'))
|
|
49
71
|
|
|
50
72
|
const drawerClass = $derived(
|
|
51
73
|
cn(
|
|
@@ -62,7 +84,7 @@
|
|
|
62
84
|
|
|
63
85
|
const contentClass = $derived(
|
|
64
86
|
cn(
|
|
65
|
-
'menu bg-base-200 text-base-content min-h-full p-4',
|
|
87
|
+
'menu relative z-10 bg-base-200 text-base-content min-h-full p-4',
|
|
66
88
|
!hasCustomWidth && sizeMap[size],
|
|
67
89
|
className,
|
|
68
90
|
),
|
|
@@ -78,6 +100,92 @@
|
|
|
78
100
|
handleClose()
|
|
79
101
|
}
|
|
80
102
|
}
|
|
103
|
+
|
|
104
|
+
/** 将宽度限制在可视区域内,并保留移动端可关闭抽屉的边距。 */
|
|
105
|
+
function clampWidth(nextWidth: number): number {
|
|
106
|
+
const maximum = Math.max(0, globalThis.innerWidth - 16)
|
|
107
|
+
const minimum = Math.min(280, maximum)
|
|
108
|
+
return Math.round(Math.min(Math.max(nextWidth, minimum), maximum))
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** 根据抽屉方向计算拖动宽度。 */
|
|
112
|
+
function handleResizeMove(event: MouseEvent) {
|
|
113
|
+
const delta = position === 'right'
|
|
114
|
+
? resizeStartX - event.clientX
|
|
115
|
+
: event.clientX - resizeStartX
|
|
116
|
+
const nextWidth = clampWidth(resizeStartWidth + delta)
|
|
117
|
+
resizeWidthForStorage = nextWidth
|
|
118
|
+
resizedWidth = nextWidth
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** 结束拖动并在配置了 key 时记忆最终宽度。 */
|
|
122
|
+
function finishResize() {
|
|
123
|
+
if (typeof document === 'undefined')
|
|
124
|
+
return
|
|
125
|
+
|
|
126
|
+
globalThis.removeEventListener('mousemove', handleResizeMove)
|
|
127
|
+
globalThis.removeEventListener('mouseup', finishResize)
|
|
128
|
+
document.documentElement.style.userSelect = previousUserSelect
|
|
129
|
+
if (widthStorageKey && resizeWidthForStorage !== null) {
|
|
130
|
+
writeStoredValue(widthStorageKey, String(resizeWidthForStorage))
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** 从抽屉可见边缘开始宽度拖动。 */
|
|
135
|
+
function startResize(event: MouseEvent) {
|
|
136
|
+
if (!canResize)
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
const drawer = event.currentTarget instanceof HTMLElement
|
|
140
|
+
? event.currentTarget.parentElement
|
|
141
|
+
: null
|
|
142
|
+
if (!drawer)
|
|
143
|
+
return
|
|
144
|
+
|
|
145
|
+
event.preventDefault()
|
|
146
|
+
resizeStartX = event.clientX
|
|
147
|
+
resizeStartWidth = drawer.getBoundingClientRect().width
|
|
148
|
+
resizeWidthForStorage = resizeStartWidth
|
|
149
|
+
previousUserSelect = document.documentElement.style.userSelect
|
|
150
|
+
document.documentElement.style.userSelect = 'none'
|
|
151
|
+
globalThis.addEventListener('mousemove', handleResizeMove)
|
|
152
|
+
globalThis.addEventListener('mouseup', finishResize, { once: true })
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** 键盘左右键以固定步长调整宽度,并立即持久化。 */
|
|
156
|
+
function handleResizeKeydown(event: KeyboardEvent) {
|
|
157
|
+
if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight')
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
const drawer = event.currentTarget instanceof HTMLElement
|
|
161
|
+
? event.currentTarget.parentElement
|
|
162
|
+
: null
|
|
163
|
+
if (!drawer)
|
|
164
|
+
return
|
|
165
|
+
|
|
166
|
+
event.preventDefault()
|
|
167
|
+
const edgeDelta = event.key === 'ArrowLeft' ? -16 : 16
|
|
168
|
+
const nextWidth = clampWidth(drawer.getBoundingClientRect().width + (position === 'right' ? -edgeDelta : edgeDelta))
|
|
169
|
+
resizeWidthForStorage = nextWidth
|
|
170
|
+
resizedWidth = nextWidth
|
|
171
|
+
if (widthStorageKey) {
|
|
172
|
+
writeStoredValue(widthStorageKey, String(nextWidth))
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
onMount(() => {
|
|
177
|
+
if (!canResize || !widthStorageKey)
|
|
178
|
+
return
|
|
179
|
+
|
|
180
|
+
const storedWidth = Number(readStoredValue(widthStorageKey))
|
|
181
|
+
if (Number.isFinite(storedWidth) && storedWidth > 0) {
|
|
182
|
+
const nextWidth = clampWidth(storedWidth)
|
|
183
|
+
resizeWidthForStorage = nextWidth
|
|
184
|
+
resizedWidth = nextWidth
|
|
185
|
+
}
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
onDestroy(finishResize)
|
|
81
189
|
</script>
|
|
82
190
|
|
|
83
191
|
<div {...dataAttributes} class={drawerClass}>
|
|
@@ -94,6 +202,17 @@
|
|
|
94
202
|
></div>
|
|
95
203
|
|
|
96
204
|
<div class={contentClass} style={widthStyle}>
|
|
205
|
+
{#if canResize}
|
|
206
|
+
<button
|
|
207
|
+
type='button'
|
|
208
|
+
aria-label={uiM('drawer_resize')}
|
|
209
|
+
data-drawer-resize-handle
|
|
210
|
+
class='absolute inset-y-0 z-20 hidden w-2 cursor-col-resize touch-none border-0 bg-transparent p-0 md:block {position === 'right' ? 'left-0' : 'right-0'}'
|
|
211
|
+
onmousedown={startResize}
|
|
212
|
+
onkeydown={handleResizeKeydown}
|
|
213
|
+
></button>
|
|
214
|
+
{/if}
|
|
215
|
+
|
|
97
216
|
<div class='flex items-center justify-between mb-4'>
|
|
98
217
|
{#if title}
|
|
99
218
|
<h3 class='text-lg font-bold'>{title}</h3>
|
|
@@ -35,6 +35,8 @@
|
|
|
35
35
|
variant = 'drawer' as CrudFormVariant,
|
|
36
36
|
size = '2xl' as Size,
|
|
37
37
|
drawerWidth,
|
|
38
|
+
drawerResizable = true,
|
|
39
|
+
drawerWidthStorageKey,
|
|
38
40
|
modalSize = '2xl' as Size | 'full',
|
|
39
41
|
modalWidth,
|
|
40
42
|
modalHeight,
|
|
@@ -53,6 +55,8 @@
|
|
|
53
55
|
variant?: CrudFormVariant
|
|
54
56
|
size?: Size
|
|
55
57
|
drawerWidth?: string
|
|
58
|
+
drawerResizable?: boolean
|
|
59
|
+
drawerWidthStorageKey?: string
|
|
56
60
|
modalSize?: Size | 'full'
|
|
57
61
|
modalWidth?: string
|
|
58
62
|
modalHeight?: string
|
|
@@ -195,7 +199,17 @@
|
|
|
195
199
|
{/snippet}
|
|
196
200
|
</Modal>
|
|
197
201
|
{:else}
|
|
198
|
-
<Drawer
|
|
202
|
+
<Drawer
|
|
203
|
+
bind:open
|
|
204
|
+
{title}
|
|
205
|
+
class={className}
|
|
206
|
+
position='right'
|
|
207
|
+
{size}
|
|
208
|
+
width={drawerWidth}
|
|
209
|
+
resizable={drawerResizable}
|
|
210
|
+
widthStorageKey={drawerWidthStorageKey}
|
|
211
|
+
onclose={handleClose}
|
|
212
|
+
>
|
|
199
213
|
{#if item}
|
|
200
214
|
<div class={drawerBodyClass}>
|
|
201
215
|
{@render detailBody()}
|
|
@@ -41,6 +41,8 @@
|
|
|
41
41
|
variant = 'drawer' as CrudFormVariant,
|
|
42
42
|
size = '2xl' as Size,
|
|
43
43
|
drawerWidth,
|
|
44
|
+
drawerResizable = true,
|
|
45
|
+
drawerWidthStorageKey,
|
|
44
46
|
modalSize = '2xl' as Size | 'full',
|
|
45
47
|
modalWidth,
|
|
46
48
|
modalHeight,
|
|
@@ -62,6 +64,8 @@
|
|
|
62
64
|
variant?: CrudFormVariant
|
|
63
65
|
size?: Size
|
|
64
66
|
drawerWidth?: string
|
|
67
|
+
drawerResizable?: boolean
|
|
68
|
+
drawerWidthStorageKey?: string
|
|
65
69
|
modalSize?: Size | 'full'
|
|
66
70
|
modalWidth?: string
|
|
67
71
|
modalHeight?: string
|
|
@@ -273,7 +277,18 @@
|
|
|
273
277
|
{/snippet}
|
|
274
278
|
</Modal>
|
|
275
279
|
{:else}
|
|
276
|
-
<Drawer
|
|
280
|
+
<Drawer
|
|
281
|
+
bind:open
|
|
282
|
+
{title}
|
|
283
|
+
class={className}
|
|
284
|
+
position='right'
|
|
285
|
+
{size}
|
|
286
|
+
width={drawerWidth}
|
|
287
|
+
resizable={drawerResizable}
|
|
288
|
+
widthStorageKey={drawerWidthStorageKey}
|
|
289
|
+
onclose={handleClose}
|
|
290
|
+
closeOnBackdrop={false}
|
|
291
|
+
>
|
|
277
292
|
<form onsubmit={handleSubmit} class={drawerFormClass}>
|
|
278
293
|
{@render formFields()}
|
|
279
294
|
</form>
|
|
@@ -140,6 +140,7 @@
|
|
|
140
140
|
pagination = {},
|
|
141
141
|
density = 'normal',
|
|
142
142
|
rowClickDetail = true,
|
|
143
|
+
detailColumns = [],
|
|
143
144
|
showHeader = true,
|
|
144
145
|
listItemActions,
|
|
145
146
|
tableCell,
|
|
@@ -175,6 +176,8 @@
|
|
|
175
176
|
density?: CrudDensity
|
|
176
177
|
/** 点击详情图标是否打开详情面板。 */
|
|
177
178
|
rowClickDetail?: boolean
|
|
179
|
+
/** 点击后打开详情的列 key。 */
|
|
180
|
+
detailColumns?: string[]
|
|
178
181
|
/** 是否显示默认页面标题栏;使用 toolbarLeading 承载标题时可设为 false。 */
|
|
179
182
|
showHeader?: boolean
|
|
180
183
|
/** 每行额外操作按钮插槽。 */
|
|
@@ -265,6 +268,10 @@
|
|
|
265
268
|
const formDrawerSize = $derived(form.drawerSize ?? '2xl')
|
|
266
269
|
/** 抽屉自定义宽度。 */
|
|
267
270
|
const formDrawerWidth = $derived(form.drawerWidth)
|
|
271
|
+
/** 是否允许拖动并记忆抽屉宽度。 */
|
|
272
|
+
const formDrawerResizable = $derived(form.drawerResizable !== false)
|
|
273
|
+
/** 同一资源的详情与编辑抽屉共享宽度记忆。 */
|
|
274
|
+
const formDrawerWidthStorageKey = $derived(`hai-ui:crud:${crud.resource.name}:drawer-width`)
|
|
268
275
|
/** 弹窗尺寸预设。 */
|
|
269
276
|
const formModalSize = $derived(form.modalSize ?? '2xl')
|
|
270
277
|
/** 弹窗自定义宽度。 */
|
|
@@ -622,6 +629,9 @@
|
|
|
622
629
|
render: col.render
|
|
623
630
|
? (item: Record<string, unknown>) => col.render!(item)
|
|
624
631
|
: undefined,
|
|
632
|
+
onclick: detailColumns.includes(col.key)
|
|
633
|
+
? (item: Record<string, unknown>) => openDetail(item)
|
|
634
|
+
: undefined,
|
|
625
635
|
})),
|
|
626
636
|
)
|
|
627
637
|
</script>
|
|
@@ -795,6 +805,8 @@
|
|
|
795
805
|
variant={formVariant}
|
|
796
806
|
size={formDrawerSize}
|
|
797
807
|
drawerWidth={formDrawerWidth}
|
|
808
|
+
drawerResizable={formDrawerResizable}
|
|
809
|
+
drawerWidthStorageKey={formDrawerWidthStorageKey}
|
|
798
810
|
modalSize={formModalSize}
|
|
799
811
|
modalWidth={formModalWidth}
|
|
800
812
|
modalHeight={formModalHeight}
|
|
@@ -815,6 +827,8 @@
|
|
|
815
827
|
variant={formVariant}
|
|
816
828
|
size={formDrawerSize}
|
|
817
829
|
drawerWidth={formDrawerWidth}
|
|
830
|
+
drawerResizable={formDrawerResizable}
|
|
831
|
+
drawerWidthStorageKey={formDrawerWidthStorageKey}
|
|
818
832
|
modalSize={formModalSize}
|
|
819
833
|
modalWidth={formModalWidth}
|
|
820
834
|
modalHeight={formModalHeight}
|
|
@@ -142,6 +142,8 @@ type $$ComponentProps = {
|
|
|
142
142
|
density?: CrudDensity;
|
|
143
143
|
/** 点击详情图标是否打开详情面板。 */
|
|
144
144
|
rowClickDetail?: boolean;
|
|
145
|
+
/** 点击后打开详情的列 key。 */
|
|
146
|
+
detailColumns?: string[];
|
|
145
147
|
/** 是否显示默认页面标题栏;使用 toolbarLeading 承载标题时可设为 false。 */
|
|
146
148
|
showHeader?: boolean;
|
|
147
149
|
/** 每行额外操作按钮插槽。 */
|
|
@@ -27,6 +27,8 @@ export interface CrudFormConfig {
|
|
|
27
27
|
drawerSize?: Size;
|
|
28
28
|
/** 抽屉自定义宽度 CSS 值,优先级高于 drawerSize(variant='drawer') */
|
|
29
29
|
drawerWidth?: string;
|
|
30
|
+
/** 是否允许拖动并按资源记忆抽屉宽度(variant='drawer',默认 true) */
|
|
31
|
+
drawerResizable?: boolean;
|
|
30
32
|
/** 弹窗尺寸预设(variant='modal',默认 '2xl') */
|
|
31
33
|
modalSize?: Size | 'full';
|
|
32
34
|
/** 弹窗自定义宽度 CSS 值(variant='modal') */
|
|
@@ -156,6 +158,8 @@ export interface CrudPageProps<T = Record<string, unknown>> extends DataAttribut
|
|
|
156
158
|
density?: CrudDensity;
|
|
157
159
|
/** 列表行点击是否打开详情(默认 true) */
|
|
158
160
|
rowClickDetail?: boolean;
|
|
161
|
+
/** 点击后打开详情的列 key;只让标题等主标识列可点击时使用 */
|
|
162
|
+
detailColumns?: string[];
|
|
159
163
|
/** 是否显示默认页面标题栏;需要把标题放进工具栏左侧时设为 false。 */
|
|
160
164
|
showHeader?: boolean;
|
|
161
165
|
/** 列表行操作按钮插槽 */
|
package/dist/messages/en-US.json
CHANGED
package/dist/messages/zh-CN.json
CHANGED
package/dist/types.d.ts
CHANGED
|
@@ -487,6 +487,10 @@ export interface DrawerProps extends DataAttributes {
|
|
|
487
487
|
size?: Size;
|
|
488
488
|
/** 自定义宽度(任意合法 CSS 宽度值,优先级高于 size 预设) */
|
|
489
489
|
width?: string;
|
|
490
|
+
/** 是否允许拖动抽屉边缘调整宽度;仅 left/right 位置生效 */
|
|
491
|
+
resizable?: boolean;
|
|
492
|
+
/** 宽度持久化 key;设置后会从 localStorage 恢复并记忆拖动后的像素宽度 */
|
|
493
|
+
widthStorageKey?: string;
|
|
490
494
|
/** 是否可通过点击遮罩关闭 */
|
|
491
495
|
closeOnBackdrop?: boolean;
|
|
492
496
|
/** 是否显示关闭按钮 */
|
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.
|
|
4
|
+
"version": "0.1.0-alpha.47",
|
|
5
5
|
"description": "Hai Framework UI component library for Svelte 5 Runes.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"svelte": "./dist/index.js",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"mermaid": "^11.12.0",
|
|
53
53
|
"shiki": "^4.0.2",
|
|
54
54
|
"tailwind-merge": "^3.5.0",
|
|
55
|
-
"@h-ai/core": "0.1.0-alpha.
|
|
55
|
+
"@h-ai/core": "0.1.0-alpha.47"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@sveltejs/package": "^2.5.7",
|