@h-ai/ui 0.1.0-alpha.23 → 0.1.0-alpha.25
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 +23 -5
- package/dist/components/compounds/Card.svelte +1 -1
- package/dist/components/compounds/DataTable.svelte +29 -7
- package/dist/components/compounds/Modal.svelte +79 -57
- package/dist/components/compounds/Pagination.svelte +30 -16
- package/dist/components/primitives/Input.svelte +23 -1
- package/dist/components/primitives/Select.svelte +2 -2
- package/dist/components/scenes/ai/markdown-parse.js +12 -4
- package/dist/components/scenes/ai/table/AiTableEditor.svelte +4 -2
- package/dist/components/scenes/crud/CrudDetailDrawer.svelte +196 -0
- package/dist/components/scenes/crud/CrudDetailDrawer.svelte.d.ts +44 -0
- package/dist/components/scenes/crud/CrudDetailPanel.svelte +23 -11
- package/dist/components/scenes/crud/CrudDetailPanel.svelte.d.ts +2 -1
- package/dist/components/scenes/crud/CrudEditPanel.svelte +28 -12
- package/dist/components/scenes/crud/CrudEditPanel.svelte.d.ts +2 -1
- package/dist/components/scenes/crud/CrudFilterBar.svelte +22 -18
- package/dist/components/scenes/crud/CrudPage.svelte +23 -13
- package/dist/components/scenes/crud/crud-types.d.ts +1 -1
- package/dist/theme-config.d.ts +60 -4
- package/dist/theme-config.js +77 -6
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -288,7 +288,7 @@ components/
|
|
|
288
288
|
>
|
|
289
289
|
> `pagination` 配置分页栏(始终显示):`showSizeChanger`、`pageSizeOptions`、`showJumper`、`showTotal` 默认开启。
|
|
290
290
|
>
|
|
291
|
-
> `density` 配置列表密度:`'normal'`(默认)或 `'compact'
|
|
291
|
+
> `density` 配置列表密度:`'normal'`(默认)或 `'compact'`(紧凑)。它只影响列表行、行内操作、分页与创建/编辑/详情表单元素,不影响页头与筛选栏。
|
|
292
292
|
|
|
293
293
|
## 使用示例
|
|
294
294
|
|
|
@@ -506,24 +506,42 @@ npm install -D @iconify/tailwind4 @iconify-json/tabler
|
|
|
506
506
|
```ts
|
|
507
507
|
import {
|
|
508
508
|
applyTheme, // 应用主题(自动持久化到 localStorage)
|
|
509
|
+
createThemeBootstrapScript, // 生成可注入 app.html 的首屏主题恢复脚本
|
|
509
510
|
getCurrentTheme, // 获取当前主题
|
|
510
511
|
getThemeInitScript, // 返回可注入到 HTML shell 的防闪烁脚本文本
|
|
511
512
|
isDarkTheme, // 检查是否暗色主题
|
|
513
|
+
normalizeHexColor, // 归一化 #RGB / #RRGGBB 主题色
|
|
514
|
+
normalizeThemeId, // 校验主题 ID 并自动回退
|
|
512
515
|
THEME_GROUPS, // 按亮色/暗色分组
|
|
513
516
|
THEMES, // ThemeInfo[] — 15 个精选主题元数据
|
|
514
517
|
} from '@h-ai/ui'
|
|
515
518
|
```
|
|
516
519
|
|
|
517
|
-
|
|
520
|
+
简单场景可以直接使用 `getThemeInitScript()`;如果应用有自定义存储 key、主题色 CSS 变量或语言偏好,推荐在服务端通过 `createThemeBootstrapScript()` 生成脚本后再注入 `app.html`。
|
|
521
|
+
|
|
522
|
+
在 SvelteKit 的 `app.html` 中,可以先放一个占位符:
|
|
518
523
|
|
|
519
524
|
```html
|
|
520
525
|
<head>
|
|
521
|
-
<script>
|
|
522
|
-
(function(){var t='light';try{var s=localStorage.getItem('theme');if(s)t=s}catch{}document.documentElement.setAttribute('data-theme',t)})()
|
|
523
|
-
</script>
|
|
526
|
+
<script>%theme_bootstrap%</script>
|
|
524
527
|
</head>
|
|
525
528
|
```
|
|
526
529
|
|
|
530
|
+
然后在 `hooks.server.ts` 里注入:
|
|
531
|
+
|
|
532
|
+
```ts
|
|
533
|
+
import { createThemeBootstrapScript, DEFAULT_THEME_COLOR_CSS_VAR } from '@h-ai/ui'
|
|
534
|
+
|
|
535
|
+
const themeBootstrapScript = createThemeBootstrapScript({
|
|
536
|
+
storageKey: 'hai-demo-preferences',
|
|
537
|
+
legacyThemeStorageKey: 'hai-demo-theme',
|
|
538
|
+
defaultThemeColor: '#5765f0',
|
|
539
|
+
colorCssVar: DEFAULT_THEME_COLOR_CSS_VAR,
|
|
540
|
+
})
|
|
541
|
+
|
|
542
|
+
transformPageChunk: ({ html }) => html.replace('%theme_bootstrap%', themeBootstrapScript)
|
|
543
|
+
```
|
|
544
|
+
|
|
527
545
|
## 国际化 (i18n)
|
|
528
546
|
|
|
529
547
|
@h-ai/ui 采用**组件内置翻译**模式:
|
|
@@ -57,6 +57,28 @@
|
|
|
57
57
|
|
|
58
58
|
type Column = { key: keyof T | string, label: string, width?: string, align?: 'left' | 'center' | 'right', render?: (item: T) => string }
|
|
59
59
|
|
|
60
|
+
const tableClass = $derived(
|
|
61
|
+
density === 'compact'
|
|
62
|
+
? 'table table-sm w-full text-[12px] [&_thead_th]:px-3 [&_thead_th]:py-2 [&_tbody_td]:px-3 [&_tbody_td]:py-2'
|
|
63
|
+
: 'table w-full text-[13px] [&_thead_th]:px-4 [&_thead_th]:py-2.5 [&_tbody_td]:px-4 [&_tbody_td]:py-3',
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
const headerRowClass = $derived(
|
|
67
|
+
density === 'compact'
|
|
68
|
+
? 'text-[11px] text-base-content/55 border-b border-base-content/6'
|
|
69
|
+
: 'text-[12px] text-base-content/55 border-b border-base-content/6',
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
const rowClass = $derived(
|
|
73
|
+
hoverable
|
|
74
|
+
? 'border-b border-base-content/5 transition-colors hover:bg-base-content/3 last:border-b-0'
|
|
75
|
+
: 'border-b border-base-content/5 last:border-b-0',
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
const actionColumnClass = $derived(density === 'compact' ? 'w-20 text-center font-medium' : 'w-24 text-center font-medium')
|
|
79
|
+
const actionWrapClass = $derived(density === 'compact' ? 'flex items-center justify-center gap-0.5' : 'flex items-center justify-center gap-1')
|
|
80
|
+
const placeholderCellClass = $derived(density === 'compact' ? 'text-center py-6 text-base-content/40' : 'text-center py-8 text-base-content/40')
|
|
81
|
+
|
|
60
82
|
function getValue(item: T, col: Column): string {
|
|
61
83
|
if (col.render) {
|
|
62
84
|
return col.render(item)
|
|
@@ -82,9 +104,9 @@
|
|
|
82
104
|
</script>
|
|
83
105
|
|
|
84
106
|
<div class='overflow-x-auto {className}'>
|
|
85
|
-
<table class=
|
|
107
|
+
<table class={tableClass} class:table-zebra={striped}>
|
|
86
108
|
<thead>
|
|
87
|
-
<tr class=
|
|
109
|
+
<tr class={headerRowClass}>
|
|
88
110
|
{#each columns as col (String(col.key))}
|
|
89
111
|
<th
|
|
90
112
|
style={col.width ? `width: ${col.width}` : ''}
|
|
@@ -94,20 +116,20 @@
|
|
|
94
116
|
</th>
|
|
95
117
|
{/each}
|
|
96
118
|
{#if actions}
|
|
97
|
-
<th class=
|
|
119
|
+
<th class={actionColumnClass}>{uiM('data_table_actions')}</th>
|
|
98
120
|
{/if}
|
|
99
121
|
</tr>
|
|
100
122
|
</thead>
|
|
101
123
|
<tbody>
|
|
102
124
|
{#if loading}
|
|
103
125
|
<tr>
|
|
104
|
-
<td colspan={columns.length + (actions ? 1 : 0)} class=
|
|
126
|
+
<td colspan={columns.length + (actions ? 1 : 0)} class={placeholderCellClass}>
|
|
105
127
|
<span class='loading loading-spinner loading-sm'></span>
|
|
106
128
|
</td>
|
|
107
129
|
</tr>
|
|
108
130
|
{:else if data.length === 0}
|
|
109
131
|
<tr>
|
|
110
|
-
<td colspan={columns.length + (actions ? 1 : 0)} class=
|
|
132
|
+
<td colspan={columns.length + (actions ? 1 : 0)} class={placeholderCellClass}>
|
|
111
133
|
{#if empty}
|
|
112
134
|
{@render empty()}
|
|
113
135
|
{:else}
|
|
@@ -117,13 +139,13 @@
|
|
|
117
139
|
</tr>
|
|
118
140
|
{:else}
|
|
119
141
|
{#each data as item (item[keyField])}
|
|
120
|
-
<tr class={
|
|
142
|
+
<tr class={rowClass}>
|
|
121
143
|
{#each columns as col (String(col.key))}
|
|
122
144
|
<td class={getAlignClass(col.align)}>{getValue(item, col)}</td>
|
|
123
145
|
{/each}
|
|
124
146
|
{#if actions}
|
|
125
147
|
<td>
|
|
126
|
-
<div class=
|
|
148
|
+
<div class={actionWrapClass}>
|
|
127
149
|
{@render actions(item)}
|
|
128
150
|
</div>
|
|
129
151
|
</td>
|
|
@@ -86,6 +86,17 @@
|
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
function handleViewportKeydown(event: KeyboardEvent) {
|
|
90
|
+
if (
|
|
91
|
+
event.target === event.currentTarget
|
|
92
|
+
&& closeOnBackdrop
|
|
93
|
+
&& (event.key === 'Enter' || event.key === ' ')
|
|
94
|
+
) {
|
|
95
|
+
event.preventDefault()
|
|
96
|
+
handleClose()
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
89
100
|
function handleDialogCancel(event: Event) {
|
|
90
101
|
// 原生 dialog 在按下 Escape 时会先触发 cancel;
|
|
91
102
|
// 这里统一接管关闭逻辑,避免浏览器直接 close 后跳过组件自己的 onclose 回调。
|
|
@@ -117,74 +128,76 @@
|
|
|
117
128
|
bind:this={modalElement}
|
|
118
129
|
class='hai-modal'
|
|
119
130
|
oncancel={handleDialogCancel}
|
|
120
|
-
onclick={handleDialogClick}
|
|
121
131
|
>
|
|
122
|
-
<div
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
132
|
+
<div
|
|
133
|
+
class='hai-modal__viewport'
|
|
134
|
+
role='button'
|
|
135
|
+
tabindex='-1'
|
|
136
|
+
aria-label={uiM('common_close')}
|
|
137
|
+
onclick={handleDialogClick}
|
|
138
|
+
onkeydown={handleViewportKeydown}
|
|
139
|
+
>
|
|
140
|
+
<div class={modalBoxClass} style={panelStyle}>
|
|
141
|
+
{#if hasHeader}
|
|
142
|
+
<!-- 顶部栏单独固定,保证滚动时标题和关闭操作始终可见。 -->
|
|
143
|
+
<div class='hai-modal__header flex flex-none items-start justify-between gap-4 px-6 py-5 sm:px-7'>
|
|
144
|
+
{#if header}
|
|
145
|
+
<div class='flex-1 font-semibold text-[1.05rem] text-base-content/92'>
|
|
146
|
+
{@render header()}
|
|
147
|
+
</div>
|
|
148
|
+
{:else if title}
|
|
149
|
+
<h3 class='flex-1 font-semibold text-[1.05rem] tracking-tight text-base-content/92'>
|
|
150
|
+
{title}
|
|
151
|
+
</h3>
|
|
152
|
+
{:else}
|
|
153
|
+
<div class='flex-1'></div>
|
|
154
|
+
{/if}
|
|
155
|
+
|
|
156
|
+
{#if showClose}
|
|
157
|
+
<button
|
|
158
|
+
type='button'
|
|
159
|
+
class='hai-modal__close'
|
|
160
|
+
aria-label={uiM('common_close')}
|
|
161
|
+
onclick={handleClose}
|
|
162
|
+
>
|
|
163
|
+
<svg viewBox='0 0 24 24' class='hai-modal__close-icon' aria-hidden='true'>
|
|
164
|
+
<path
|
|
165
|
+
d='M6 6l12 12M18 6L6 18'
|
|
166
|
+
fill='none'
|
|
167
|
+
stroke='currentColor'
|
|
168
|
+
stroke-width='1.9'
|
|
169
|
+
stroke-linecap='round'
|
|
170
|
+
></path>
|
|
171
|
+
</svg>
|
|
172
|
+
</button>
|
|
173
|
+
{/if}
|
|
174
|
+
</div>
|
|
175
|
+
{/if}
|
|
137
176
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
aria-label={uiM('common_close')}
|
|
143
|
-
onclick={handleClose}
|
|
144
|
-
>
|
|
145
|
-
<svg viewBox='0 0 24 24' class='hai-modal__close-icon' aria-hidden='true'>
|
|
146
|
-
<path
|
|
147
|
-
d='M6 6l12 12M18 6L6 18'
|
|
148
|
-
fill='none'
|
|
149
|
-
stroke='currentColor'
|
|
150
|
-
stroke-width='1.9'
|
|
151
|
-
stroke-linecap='round'
|
|
152
|
-
></path>
|
|
153
|
-
</svg>
|
|
154
|
-
</button>
|
|
177
|
+
<!-- 主体区域独立滚动,避免长内容把头部和底部一起顶走。 -->
|
|
178
|
+
<div class={cn('min-h-0 flex-1 px-6 py-5 sm:px-7', bodyOverflowClass, bodyClass)}>
|
|
179
|
+
{#if children}
|
|
180
|
+
{@render children()}
|
|
155
181
|
{/if}
|
|
156
182
|
</div>
|
|
157
|
-
{/if}
|
|
158
183
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
184
|
+
{#if footer}
|
|
185
|
+
<!-- 底栏固定在面板底部,用于承接确认/取消或状态摘要。 -->
|
|
186
|
+
<div class='hai-modal__footer flex flex-none items-center px-6 py-4 sm:px-7'>
|
|
187
|
+
<div class='flex w-full items-center justify-end gap-3'>
|
|
188
|
+
{@render footer()}
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
163
191
|
{/if}
|
|
164
192
|
</div>
|
|
165
|
-
|
|
166
|
-
{#if footer}
|
|
167
|
-
<!-- 底栏固定在面板底部,用于承接确认/取消或状态摘要。 -->
|
|
168
|
-
<div class='hai-modal__footer flex flex-none items-center bg-base-100 px-6 py-4 sm:px-7'>
|
|
169
|
-
<div class='flex w-full items-center justify-end gap-3'>
|
|
170
|
-
{@render footer()}
|
|
171
|
-
</div>
|
|
172
|
-
</div>
|
|
173
|
-
{/if}
|
|
174
193
|
</div>
|
|
175
194
|
</dialog>
|
|
176
195
|
|
|
177
196
|
<style>
|
|
178
|
-
dialog.hai-modal
|
|
179
|
-
|
|
180
|
-
width: 100vw;
|
|
181
|
-
min-width: 0;
|
|
182
|
-
max-width: none;
|
|
183
|
-
height: 100vh;
|
|
184
|
-
height: 100dvh;
|
|
185
|
-
max-height: none;
|
|
197
|
+
dialog.hai-modal,
|
|
198
|
+
dialog.hai-modal:modal {
|
|
186
199
|
margin: 0;
|
|
187
|
-
padding:
|
|
200
|
+
padding: 0;
|
|
188
201
|
border: 0;
|
|
189
202
|
background: transparent;
|
|
190
203
|
outline: none;
|
|
@@ -192,9 +205,12 @@
|
|
|
192
205
|
box-sizing: border-box;
|
|
193
206
|
}
|
|
194
207
|
|
|
195
|
-
|
|
208
|
+
.hai-modal__viewport {
|
|
209
|
+
position: fixed;
|
|
210
|
+
inset: 0;
|
|
196
211
|
display: grid;
|
|
197
212
|
place-items: center;
|
|
213
|
+
padding: clamp(1rem, 2.6vw, 2rem);
|
|
198
214
|
}
|
|
199
215
|
|
|
200
216
|
dialog.hai-modal::backdrop {
|
|
@@ -220,10 +236,16 @@
|
|
|
220
236
|
}
|
|
221
237
|
|
|
222
238
|
.hai-modal__header {
|
|
239
|
+
/* 头部单独承接面板圆角,避免滚动主体或主题背景把顶部边缘切成直角。 */
|
|
240
|
+
border-top-left-radius: inherit;
|
|
241
|
+
border-top-right-radius: inherit;
|
|
223
242
|
box-shadow: inset 0 -1px 0 color-mix(in srgb, var(--color-base-content) 6%, transparent);
|
|
224
243
|
}
|
|
225
244
|
|
|
226
245
|
.hai-modal__footer {
|
|
246
|
+
/* 底栏去掉独立背景后,继续继承圆角,保证底部阴影和面板外轮廓保持一致。 */
|
|
247
|
+
border-bottom-right-radius: inherit;
|
|
248
|
+
border-bottom-left-radius: inherit;
|
|
227
249
|
box-shadow: inset 0 1px 0 color-mix(in srgb, var(--color-base-content) 6%, transparent);
|
|
228
250
|
border-radius: 0 0 1.4rem 1.4rem;
|
|
229
251
|
}
|
|
@@ -87,6 +87,18 @@
|
|
|
87
87
|
),
|
|
88
88
|
)
|
|
89
89
|
|
|
90
|
+
const controlSize = $derived(size === 'xs' ? 'xs' : 'sm')
|
|
91
|
+
const jumperControlSize = 'xs'
|
|
92
|
+
const wrapperClass = $derived(size === 'xs' ? 'flex flex-wrap items-center justify-center gap-1.5 text-xs' : 'flex flex-wrap items-center justify-center gap-2.5 text-sm')
|
|
93
|
+
const totalClass = $derived(size === 'xs' ? 'text-xs text-base-content/70' : 'text-sm text-base-content/70')
|
|
94
|
+
const sizeChangerClass = $derived(size === 'xs' ? 'w-22 shrink-0' : 'w-24 shrink-0')
|
|
95
|
+
const jumperWrapClass = $derived(size === 'xs' ? 'flex min-w-fit items-center gap-1.5 whitespace-nowrap' : 'flex min-w-fit items-center gap-1.5 whitespace-nowrap')
|
|
96
|
+
const jumperInputClass = $derived(
|
|
97
|
+
size === 'xs'
|
|
98
|
+
? 'w-[2.25rem] shrink-0 [&>.fieldset]:m-0 [&>.fieldset]:min-w-0'
|
|
99
|
+
: 'w-[2.5rem] shrink-0 [&>.fieldset]:m-0 [&>.fieldset]:min-w-0',
|
|
100
|
+
)
|
|
101
|
+
|
|
90
102
|
function goToPage(p: number) {
|
|
91
103
|
if (p >= 1 && p <= totalPages && p !== page) {
|
|
92
104
|
page = p
|
|
@@ -112,17 +124,17 @@
|
|
|
112
124
|
}
|
|
113
125
|
</script>
|
|
114
126
|
|
|
115
|
-
<div class=
|
|
127
|
+
<div class={wrapperClass}>
|
|
116
128
|
{#if showTotal}
|
|
117
|
-
<span class=
|
|
129
|
+
<span class={totalClass}>
|
|
118
130
|
{(labels.total ?? uiM('pagination_total')).replace('{count}', String(total))}
|
|
119
131
|
</span>
|
|
120
132
|
{/if}
|
|
121
133
|
|
|
122
134
|
{#if showSizeChanger}
|
|
123
|
-
<div class=
|
|
135
|
+
<div class={sizeChangerClass}>
|
|
124
136
|
<Select
|
|
125
|
-
size=
|
|
137
|
+
size={controlSize}
|
|
126
138
|
value={String(pageSize)}
|
|
127
139
|
onchange={handlePageSizeChange}
|
|
128
140
|
>
|
|
@@ -167,18 +179,20 @@
|
|
|
167
179
|
</div>
|
|
168
180
|
|
|
169
181
|
{#if showJumper}
|
|
170
|
-
<div class=
|
|
171
|
-
<span class=
|
|
172
|
-
<
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
+
<div class={jumperWrapClass}>
|
|
183
|
+
<span class={totalClass}>{labels.jumpTo ?? uiM('pagination_jump_to')}</span>
|
|
184
|
+
<div class={jumperInputClass}>
|
|
185
|
+
<Input
|
|
186
|
+
type='number'
|
|
187
|
+
size={jumperControlSize}
|
|
188
|
+
class='h-7 w-full rounded-md [&_input]:px-1.5 [&_input]:text-center [&_input]:text-[12px]'
|
|
189
|
+
min={1}
|
|
190
|
+
max={totalPages}
|
|
191
|
+
bind:value={jumperValue}
|
|
192
|
+
onkeydown={(e: KeyboardEvent & { currentTarget: HTMLInputElement }) => e.key === 'Enter' && handleJump()}
|
|
193
|
+
/>
|
|
194
|
+
</div>
|
|
195
|
+
<span class={totalClass}>{labels.page ?? uiM('pagination_page')}</span>
|
|
182
196
|
</div>
|
|
183
197
|
{/if}
|
|
184
198
|
</div>
|
|
@@ -49,6 +49,24 @@
|
|
|
49
49
|
: 'h-10',
|
|
50
50
|
)
|
|
51
51
|
|
|
52
|
+
const inputSpacingClass = $derived(
|
|
53
|
+
size === 'xs'
|
|
54
|
+
? 'px-2.5 text-xs'
|
|
55
|
+
: size === 'sm'
|
|
56
|
+
? 'px-3 text-sm'
|
|
57
|
+
: size === 'lg'
|
|
58
|
+
? 'px-4 text-base'
|
|
59
|
+
: size === 'xl'
|
|
60
|
+
? 'px-4 text-lg'
|
|
61
|
+
: 'px-3 text-sm',
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
const inputTypeClass = $derived(
|
|
65
|
+
type === 'number'
|
|
66
|
+
? '[appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none'
|
|
67
|
+
: '',
|
|
68
|
+
)
|
|
69
|
+
|
|
52
70
|
const containerClass = $derived(
|
|
53
71
|
cn(
|
|
54
72
|
'flex items-center w-full rounded-lg border bg-base-100',
|
|
@@ -114,7 +132,11 @@
|
|
|
114
132
|
{step}
|
|
115
133
|
inputmode={inputmode}
|
|
116
134
|
autocomplete={autocomplete as HTMLInputElement['autocomplete']}
|
|
117
|
-
class=
|
|
135
|
+
class={cn(
|
|
136
|
+
'flex-1 h-full bg-transparent border-none outline-none placeholder:text-base-content/35',
|
|
137
|
+
inputSpacingClass,
|
|
138
|
+
inputTypeClass,
|
|
139
|
+
)}
|
|
118
140
|
bind:value
|
|
119
141
|
oninput={handleInput}
|
|
120
142
|
onchange={handleChange}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
-->
|
|
11
11
|
<script lang='ts' generics="T = string">
|
|
12
12
|
import type { SelectProps } from '../../types.js'
|
|
13
|
-
import { cn,
|
|
13
|
+
import { cn, getSizeClass } from '../../utils.js'
|
|
14
14
|
|
|
15
15
|
let {
|
|
16
16
|
value = $bindable<T>(),
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
const selectClass = $derived(
|
|
33
33
|
cn(
|
|
34
34
|
'select w-full',
|
|
35
|
-
|
|
35
|
+
getSizeClass(size, 'input'),
|
|
36
36
|
error && 'select-error',
|
|
37
37
|
className,
|
|
38
38
|
),
|
|
@@ -24,6 +24,16 @@ function escapeHtml(text) {
|
|
|
24
24
|
.replace(/"/g, '"')
|
|
25
25
|
.replace(/'/g, ''');
|
|
26
26
|
}
|
|
27
|
+
function createTableCellAttrs(cell) {
|
|
28
|
+
const attrs = [];
|
|
29
|
+
if (cell.align) {
|
|
30
|
+
attrs.push(`style="text-align:${cell.align}"`);
|
|
31
|
+
}
|
|
32
|
+
if (cell.text.trim()) {
|
|
33
|
+
attrs.push(`title="${escapeHtml(cell.text)}"`);
|
|
34
|
+
}
|
|
35
|
+
return attrs.length > 0 ? ` ${attrs.join(' ')}` : '';
|
|
36
|
+
}
|
|
27
37
|
/**
|
|
28
38
|
* 创建自定义渲染器对象
|
|
29
39
|
*
|
|
@@ -91,15 +101,13 @@ function createRendererObject(options) {
|
|
|
91
101
|
table(token) {
|
|
92
102
|
let headerHtml = '';
|
|
93
103
|
for (const cell of token.header) {
|
|
94
|
-
|
|
95
|
-
headerHtml += `<th${align}>${this.parser.parseInline(cell.tokens)}</th>`;
|
|
104
|
+
headerHtml += `<th${createTableCellAttrs(cell)}>${this.parser.parseInline(cell.tokens)}</th>`;
|
|
96
105
|
}
|
|
97
106
|
let bodyHtml = '';
|
|
98
107
|
for (const row of token.rows) {
|
|
99
108
|
let rowHtml = '';
|
|
100
109
|
for (const cell of row) {
|
|
101
|
-
|
|
102
|
-
rowHtml += `<td${align}>${this.parser.parseInline(cell.tokens)}</td>`;
|
|
110
|
+
rowHtml += `<td${createTableCellAttrs(cell)}>${this.parser.parseInline(cell.tokens)}</td>`;
|
|
103
111
|
}
|
|
104
112
|
bodyHtml += `<tr>${rowHtml}</tr>`;
|
|
105
113
|
}
|
|
@@ -1097,7 +1097,7 @@
|
|
|
1097
1097
|
<thead>
|
|
1098
1098
|
<tr>
|
|
1099
1099
|
{#each tableColumns as column (column.key)}
|
|
1100
|
-
<th>{column.label}</th>
|
|
1100
|
+
<th title={column.label}>{column.label}</th>
|
|
1101
1101
|
{/each}
|
|
1102
1102
|
{#if editable}
|
|
1103
1103
|
<th class='hai-ai-table-action-head'>{uiM('data_table_actions')}</th>
|
|
@@ -1122,11 +1122,13 @@
|
|
|
1122
1122
|
ondrop={event => handleRowDrop(row.row_id, event)}
|
|
1123
1123
|
>
|
|
1124
1124
|
{#each tableColumns as column (column.key)}
|
|
1125
|
+
{@const cellInputValue = getCellInputValue(row, column.key)}
|
|
1125
1126
|
<td>
|
|
1126
1127
|
<input
|
|
1127
1128
|
class={resolveCellInputClass(column.type)}
|
|
1128
1129
|
type={column.type === 'number' ? 'number' : 'text'}
|
|
1129
|
-
value={
|
|
1130
|
+
value={cellInputValue}
|
|
1131
|
+
title={cellInputValue}
|
|
1130
1132
|
disabled={!editable}
|
|
1131
1133
|
oninput={event => handleCellInput(row.row_id, column, event)}
|
|
1132
1134
|
/>
|