@h-ai/ui 0.1.0-alpha.12 → 0.1.0-alpha.15

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.
@@ -33,9 +33,6 @@
33
33
  parseMarkdown(content, { enableHighlight, showCopyButton, breaks }),
34
34
  )
35
35
 
36
- /** 容器引用 */
37
- let containerEl: HTMLDivElement | undefined = $state()
38
-
39
36
  /**
40
37
  * 事件代理:处理代码块复制按钮点击
41
38
  */
@@ -66,7 +63,6 @@
66
63
  <!-- svelte-ignore a11y_click_events_have_key_events -->
67
64
  <!-- svelte-ignore a11y_no_static_element_interactions -->
68
65
  <div
69
- bind:this={containerEl}
70
66
  class={cn('hai-markdown', className)}
71
67
  onclick={handleClick}
72
68
  >
@@ -65,7 +65,7 @@ export type MarkdownBlockFormatKind = 'heading' | 'bullet';
65
65
  /**
66
66
  * Rich block-style actions used by the current editor toolbar.
67
67
  */
68
- export type MarkdownBlockStyleKind = 'paragraph' | 'heading1' | 'heading2' | 'heading3' | 'heading4';
68
+ export type MarkdownBlockStyleKind = 'paragraph' | 'heading1' | 'heading2' | 'heading3' | 'heading4' | 'orderedList' | 'bulletList';
69
69
  /**
70
70
  * Inline format actions for the editor toolbar.
71
71
  * `highlight` and `link` stay in the public union so older integrations keep compiling.
@@ -75,6 +75,14 @@ export type MarkdownInlineFormatKind = 'bold' | 'italic' | 'strike' | 'underline
75
75
  * Paragraph alignment actions for the editor toolbar.
76
76
  */
77
77
  export type MarkdownTextAlignKind = 'left' | 'center' | 'right' | 'justify';
78
+ export interface MarkdownSelectionSnapshot {
79
+ /** Selection start offset within the plain-text document. */
80
+ start: number;
81
+ /** Selection end offset within the plain-text document. */
82
+ end: number;
83
+ /** Selected plain text, used as a fallback when structure changes. */
84
+ text: string;
85
+ }
78
86
  /**
79
87
  * 颜色面板一次只会修改前景色或背景色中的一个维度。
80
88
  */
@@ -83,6 +91,18 @@ export interface MarkdownColorFormatRequest {
83
91
  target: 'text' | 'background';
84
92
  /** 颜色值为空时表示恢复默认。 */
85
93
  value: string | null;
94
+ /** 当前工具条记住的选中文本,供外层在 DOM 重建后兜底恢复定位。 */
95
+ selectedText?: string;
96
+ /** 当前工具条记住的精确选区偏移,优先用于重复文本场景的恢复。 */
97
+ selectionSnapshot?: MarkdownSelectionSnapshot;
98
+ }
99
+ export interface MarkdownLinkFormatRequest {
100
+ /** 为空时表示移除当前链接。 */
101
+ href: string | null;
102
+ /** 当前工具条记住的精确选区偏移。 */
103
+ selectionSnapshot?: MarkdownSelectionSnapshot;
104
+ /** 当前工具条记住的选中文本,供宿主做文本兜底。 */
105
+ selectedText?: string;
86
106
  }
87
107
  export interface MarkdownToolbarDownloadAction {
88
108
  /** Action id used to identify the download target. */
@@ -124,6 +144,8 @@ export interface AiDocumentEditorProps {
124
144
  breaks?: boolean;
125
145
  /** Whether to show the left outline panel. */
126
146
  showOutline?: boolean;
147
+ /** Whether to show numbering prefixes in outline items. */
148
+ showOutlineNumbering?: boolean;
127
149
  /** Whether to show the top toolbar. */
128
150
  showToolbar?: boolean;
129
151
  /** Initial outline collapsed state. */
@@ -191,7 +213,11 @@ export interface AiDocumentEditorProps {
191
213
  /** Paragraph alignment handler from the selection toolbar. */
192
214
  onapplyalignment?: (kind: MarkdownTextAlignKind) => void;
193
215
  /** Link add / edit / remove handler from the selection toolbar. */
194
- onapplylink?: (href: string | null) => void;
216
+ onapplylink?: BivariantCallback<[
217
+ href: string | null,
218
+ selectionSnapshot?: MarkdownSelectionSnapshot,
219
+ selectedText?: string
220
+ ], void>;
195
221
  /** Text or background color handler from the selection toolbar. */
196
222
  onapplycolor?: (request: MarkdownColorFormatRequest) => void;
197
223
  /** Selection copy handler for custom behavior. */
@@ -7,7 +7,8 @@ const HEX_COLOR_REGEX = /^#(?:[0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
7
7
  */
8
8
  const ALIGN_VALUE_REGEX = /^(?:left|center|right|justify)$/i;
9
9
  const HAI_ALIGN_BLOCK_REGEX = /^<hai-align\s+value="(left|center|right|justify)">\n?([\s\S]+?)\n?<\/hai-align>\n{0,2}/i;
10
- const HAI_SPAN_INLINE_REGEX = /^<hai-span(?:\s+color="([^"]+)")?(?:\s+bg="([^"]+)")?\s*>([\s\S]+?)<\/hai-span>/i;
10
+ const HAI_SPAN_INLINE_REGEX = /^<hai-span\b([^>]*)>([\s\S]+?)<\/hai-span>/i;
11
+ const HAI_SPAN_ATTR_REGEX = /\b(color|bg)="([^"]+)"/gi;
11
12
  const HAI_UNDERLINE_REGEX = /^\+\+((?:\\.|[\s\S])+?)\+\+/;
12
13
  const HAI_HIGHLIGHT_REGEX = /^==((?:\\.|[\s\S])+?)==/;
13
14
  /**
@@ -55,6 +56,21 @@ function sanitizeAlign(value) {
55
56
  ? normalized
56
57
  : undefined;
57
58
  }
59
+ function parseHaiSpanAttributes(rawAttributes) {
60
+ let color;
61
+ let background;
62
+ for (const match of rawAttributes.matchAll(HAI_SPAN_ATTR_REGEX)) {
63
+ const [, attrName, attrValue] = match;
64
+ if (attrName === 'color') {
65
+ color = sanitizeColor(attrValue) ?? color;
66
+ continue;
67
+ }
68
+ if (attrName === 'bg') {
69
+ background = sanitizeColor(attrValue) ?? background;
70
+ }
71
+ }
72
+ return { color, background };
73
+ }
58
74
  /**
59
75
  * ChatWorkspace 编辑器会把“非标准 Markdown”能力写成自定义标签,
60
76
  * 这里把它们注册为 marked 扩展,保证普通 Markdown 和编辑器增强能力共享同一条渲染链路。
@@ -101,14 +117,13 @@ export function createEditorMarkdownExtensions() {
101
117
  if (!match) {
102
118
  return undefined;
103
119
  }
104
- const color = sanitizeColor(match[1]);
105
- const background = sanitizeColor(match[2]);
120
+ const { color, background } = parseHaiSpanAttributes(match[1] ?? '');
106
121
  return {
107
122
  type: 'haiSpan',
108
123
  raw: match[0],
109
124
  color,
110
125
  background,
111
- tokens: this.lexer.inlineTokens(match[3]),
126
+ tokens: this.lexer.inlineTokens(match[2]),
112
127
  };
113
128
  },
114
129
  renderer(token) {
@@ -79,7 +79,7 @@
79
79
  <Select
80
80
  class='min-w-32'
81
81
  value={String(filterValues[field.id] ?? '')}
82
- onchange={e => handleFilterChange(field.id, (e.target as HTMLSelectElement).value)}
82
+ onchange={value => handleFilterChange(field.id, value)}
83
83
  >
84
84
  <option value="">{uiM('crud_filter_all')}</option>
85
85
  {#each opts as opt}
@@ -13,8 +13,7 @@
13
13
  <script lang='ts'>
14
14
  import type { Snippet } from 'svelte'
15
15
  import type { Size } from '../../../types.js'
16
- import { goto, invalidateAll } from '$app/navigation'
17
- import { page } from '$app/state'
16
+ import type { NavAdapter } from './nav-adapter.js'
18
17
  import { uiM } from '../../../messages.js'
19
18
  import Card from '../../compounds/Card.svelte'
20
19
  import DataTable from '../../compounds/DataTable.svelte'
@@ -26,6 +25,7 @@
26
25
  import CrudDetailDrawer from './CrudDetailDrawer.svelte'
27
26
  import CrudEditDrawer from './CrudEditDrawer.svelte'
28
27
  import CrudFilterBar from './CrudFilterBar.svelte'
28
+ import { createBrowserNavAdapter } from './nav-adapter.js'
29
29
 
30
30
  // ─── 类型定义 ───
31
31
 
@@ -49,7 +49,15 @@
49
49
  order?: number
50
50
  }
51
51
 
52
- type AnyApiFunc = (...args: unknown[]) => Promise<Record<string, unknown> | unknown> | Record<string, unknown> | unknown
52
+ // 使用 method shorthand 语法(双向协变)以兼容来自 @h-ai/kit 的 CrudOperations 类型
53
+ // 详见:https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-6.html#strict-function-types
54
+ type CrudApi = {
55
+ list(params: Record<string, unknown>): Promise<{ items: Record<string, unknown>[], total: number, page: number, pageSize: number }>
56
+ get?(id: string): Promise<Record<string, unknown> | null>
57
+ create?(data: Record<string, unknown>): Promise<Record<string, unknown>>
58
+ update?(id: string, data: Record<string, unknown>): Promise<Record<string, unknown>>
59
+ remove?(id: string): Promise<void>
60
+ }
53
61
 
54
62
  type CrudDef = {
55
63
  resource: {
@@ -60,13 +68,7 @@
60
68
  defaultPageSize?: number
61
69
  searchable?: boolean
62
70
  searchPlaceholder?: string | (() => string)
63
- api: {
64
- list: AnyApiFunc
65
- get?: AnyApiFunc
66
- create?: AnyApiFunc
67
- update?: AnyApiFunc
68
- remove?: AnyApiFunc
69
- }
71
+ api: CrudApi
70
72
  }
71
73
  getListFields: () => FieldDef[]
72
74
  getFilterFields: () => FieldDef[]
@@ -75,13 +77,7 @@
75
77
  getDetailFields: () => FieldDef[]
76
78
  toTableColumns: () => Array<{ key: string, label: string, width?: string, align?: string, render?: (item: Record<string, unknown>) => string }>
77
79
  getDefaultValues: () => Record<string, unknown>
78
- api: {
79
- list: AnyApiFunc
80
- get?: AnyApiFunc
81
- create?: AnyApiFunc
82
- update?: AnyApiFunc
83
- remove?: AnyApiFunc
84
- }
80
+ api: CrudApi
85
81
  }
86
82
 
87
83
  // ─── Props ───
@@ -101,6 +97,7 @@
101
97
  onafterdelete,
102
98
  onerror,
103
99
  basePath = '',
100
+ nav = createBrowserNavAdapter(),
104
101
  class: className = '',
105
102
  }: {
106
103
  crud: CrudDef
@@ -117,6 +114,13 @@
117
114
  onafterdelete?: (item: Record<string, unknown>) => void
118
115
  onerror?: (error: string) => void
119
116
  basePath?: string
117
+ /**
118
+ * 路由适配器:用于读取当前 pathname、同步过滤参数到 URL、列表刷新。
119
+ * 默认使用 `createBrowserNavAdapter()`(基于 `globalThis.location`)。
120
+ * SvelteKit 应用应传入 `createSvelteKitNavAdapter()`(来自 `@h-ai/kit/client`),
121
+ * 以获得客户端跳转 + invalidateAll 体验。
122
+ */
123
+ nav?: NavAdapter
120
124
  class?: string
121
125
  } = $props()
122
126
 
@@ -144,7 +148,7 @@
144
148
  let filterValues = $state<Record<string, unknown>>({})
145
149
 
146
150
  // 基础路径
147
- const currentBasePath = $derived(basePath || page.url.pathname)
151
+ const currentBasePath = $derived(basePath || nav.pathname)
148
152
 
149
153
  // ─── 工具函数 ───
150
154
 
@@ -194,7 +198,8 @@
194
198
  }
195
199
 
196
200
  const qs = params.toString()
197
- goto(`${currentBasePath}${qs ? `?${qs}` : ''}`, { invalidateAll: true })
201
+ const url = `${currentBasePath}${qs ? `?${qs}` : ''}`
202
+ void Promise.resolve(nav.navigate(url)).then(() => nav.refresh?.())
198
203
  }
199
204
 
200
205
  function handleSearch(search: string) {
@@ -258,7 +263,7 @@
258
263
  const submitted = (result ?? {}) as Record<string, unknown>
259
264
  closeDrawer()
260
265
  onaftersubmit?.(submitted, 'create')
261
- await invalidateAll()
266
+ await nav.refresh?.()
262
267
  }
263
268
  else if (drawerMode === 'edit' && crud.api.update && selectedItem) {
264
269
  const id = String(selectedItem[keyField])
@@ -266,7 +271,7 @@
266
271
  const submitted = (result ?? {}) as Record<string, unknown>
267
272
  closeDrawer()
268
273
  onaftersubmit?.(submitted, 'edit')
269
- await invalidateAll()
274
+ await nav.refresh?.()
270
275
  }
271
276
  }
272
277
  catch (e) {
@@ -311,7 +316,7 @@
311
316
  closeDrawer()
312
317
  }
313
318
  onafterdelete?.(deleted)
314
- await invalidateAll()
319
+ await nav.refresh?.()
315
320
  }
316
321
  catch (e) {
317
322
  const msg = e instanceof Error ? e.message : uiM('crud_delete_failed')
@@ -1,5 +1,6 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  import type { Size } from '../../../types.js';
3
+ import type { NavAdapter } from './nav-adapter.js';
3
4
  type FieldDef = {
4
5
  id: string;
5
6
  label: string | (() => string);
@@ -31,7 +32,18 @@ type FieldDef = {
31
32
  defaultValue?: unknown;
32
33
  order?: number;
33
34
  };
34
- type AnyApiFunc = (...args: unknown[]) => Promise<Record<string, unknown> | unknown> | Record<string, unknown> | unknown;
35
+ type CrudApi = {
36
+ list(params: Record<string, unknown>): Promise<{
37
+ items: Record<string, unknown>[];
38
+ total: number;
39
+ page: number;
40
+ pageSize: number;
41
+ }>;
42
+ get?(id: string): Promise<Record<string, unknown> | null>;
43
+ create?(data: Record<string, unknown>): Promise<Record<string, unknown>>;
44
+ update?(id: string, data: Record<string, unknown>): Promise<Record<string, unknown>>;
45
+ remove?(id: string): Promise<void>;
46
+ };
35
47
  type CrudDef = {
36
48
  resource: {
37
49
  name: string;
@@ -41,13 +53,7 @@ type CrudDef = {
41
53
  defaultPageSize?: number;
42
54
  searchable?: boolean;
43
55
  searchPlaceholder?: string | (() => string);
44
- api: {
45
- list: AnyApiFunc;
46
- get?: AnyApiFunc;
47
- create?: AnyApiFunc;
48
- update?: AnyApiFunc;
49
- remove?: AnyApiFunc;
50
- };
56
+ api: CrudApi;
51
57
  };
52
58
  getListFields: () => FieldDef[];
53
59
  getFilterFields: () => FieldDef[];
@@ -62,13 +68,7 @@ type CrudDef = {
62
68
  render?: (item: Record<string, unknown>) => string;
63
69
  }>;
64
70
  getDefaultValues: () => Record<string, unknown>;
65
- api: {
66
- list: AnyApiFunc;
67
- get?: AnyApiFunc;
68
- create?: AnyApiFunc;
69
- update?: AnyApiFunc;
70
- remove?: AnyApiFunc;
71
- };
71
+ api: CrudApi;
72
72
  };
73
73
  type $$ComponentProps = {
74
74
  crud: CrudDef;
@@ -95,6 +95,13 @@ type $$ComponentProps = {
95
95
  onafterdelete?: (item: Record<string, unknown>) => void;
96
96
  onerror?: (error: string) => void;
97
97
  basePath?: string;
98
+ /**
99
+ * 路由适配器:用于读取当前 pathname、同步过滤参数到 URL、列表刷新。
100
+ * 默认使用 `createBrowserNavAdapter()`(基于 `globalThis.location`)。
101
+ * SvelteKit 应用应传入 `createSvelteKitNavAdapter()`(来自 `@h-ai/kit/client`),
102
+ * 以获得客户端跳转 + invalidateAll 体验。
103
+ */
104
+ nav?: NavAdapter;
98
105
  class?: string;
99
106
  };
100
107
  /**
@@ -10,3 +10,5 @@ export { default as CrudDetailDrawer } from './CrudDetailDrawer.svelte';
10
10
  export { default as CrudEditDrawer } from './CrudEditDrawer.svelte';
11
11
  export { default as CrudFilterBar } from './CrudFilterBar.svelte';
12
12
  export { default as CrudPage } from './CrudPage.svelte';
13
+ export type { NavAdapter } from './nav-adapter.js';
14
+ export { createBrowserNavAdapter } from './nav-adapter.js';
@@ -9,3 +9,4 @@ export { default as CrudDetailDrawer } from './CrudDetailDrawer.svelte';
9
9
  export { default as CrudEditDrawer } from './CrudEditDrawer.svelte';
10
10
  export { default as CrudFilterBar } from './CrudFilterBar.svelte';
11
11
  export { default as CrudPage } from './CrudPage.svelte';
12
+ export { createBrowserNavAdapter } from './nav-adapter.js';
@@ -0,0 +1,36 @@
1
+ /**
2
+ * @h-ai/ui — CRUD 导航适配器
3
+ *
4
+ * `CrudPage` 通过 `nav` prop 接入宿主路由系统,避免对任何具体框架(SvelteKit、
5
+ * 自实现 hash router 等)形成强依赖。
6
+ *
7
+ * - SvelteKit 应用:传入 `createSvelteKitNavAdapter()`(从 `@h-ai/kit/client` 导入)。
8
+ * - 纯浏览器 / Tauri / Capacitor:传入自定义实现或使用默认 `createBrowserNavAdapter()`。
9
+ *
10
+ * @module nav-adapter
11
+ */
12
+ /**
13
+ * 路由适配器接口。`CrudPage` 通过此接口完成:
14
+ * - 读取当前 pathname(作为列表 URL 的 basePath 兜底)
15
+ * - 调用 `navigate(url)` 同步搜索 / 过滤 / 分页参数到地址栏
16
+ * - 调用 `refresh()` 在写操作后重新拉取列表数据
17
+ */
18
+ export interface NavAdapter {
19
+ /** 当前页面路径(不含 query / hash),用于推导 `basePath`。 */
20
+ readonly pathname: string;
21
+ /** 跳转到指定 URL(可带 query),通常用于更新 URL 上的搜索/分页参数。 */
22
+ readonly navigate: (url: string) => void | Promise<void>;
23
+ /** 写操作完成后触发列表刷新;未提供时由调用方自行通过 `onaftersubmit` / `onafterdelete` 处理。 */
24
+ readonly refresh?: () => void | Promise<void>;
25
+ }
26
+ /**
27
+ * 创建基于浏览器 `globalThis.location` 的默认导航适配器。
28
+ *
29
+ * 适用场景:纯 SPA(无 SvelteKit)、Tauri、Capacitor 等可以直接读写 `location` 的环境。
30
+ *
31
+ * - `pathname` 读 `globalThis.location?.pathname`,SSR 下安全降级为 `''`。
32
+ * - `navigate(url)` 调 `globalThis.location.assign(url)`;如需客户端路由(hash router 等),
33
+ * 宿主应自行实现一个 `NavAdapter` 传入。
34
+ * - 不提供 `refresh`:浏览器默认导航本身会触发页面加载,调用方需在导航前/后重新拉取数据。
35
+ */
36
+ export declare function createBrowserNavAdapter(): NavAdapter;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @h-ai/ui — CRUD 导航适配器
3
+ *
4
+ * `CrudPage` 通过 `nav` prop 接入宿主路由系统,避免对任何具体框架(SvelteKit、
5
+ * 自实现 hash router 等)形成强依赖。
6
+ *
7
+ * - SvelteKit 应用:传入 `createSvelteKitNavAdapter()`(从 `@h-ai/kit/client` 导入)。
8
+ * - 纯浏览器 / Tauri / Capacitor:传入自定义实现或使用默认 `createBrowserNavAdapter()`。
9
+ *
10
+ * @module nav-adapter
11
+ */
12
+ /**
13
+ * 创建基于浏览器 `globalThis.location` 的默认导航适配器。
14
+ *
15
+ * 适用场景:纯 SPA(无 SvelteKit)、Tauri、Capacitor 等可以直接读写 `location` 的环境。
16
+ *
17
+ * - `pathname` 读 `globalThis.location?.pathname`,SSR 下安全降级为 `''`。
18
+ * - `navigate(url)` 调 `globalThis.location.assign(url)`;如需客户端路由(hash router 等),
19
+ * 宿主应自行实现一个 `NavAdapter` 传入。
20
+ * - 不提供 `refresh`:浏览器默认导航本身会触发页面加载,调用方需在导航前/后重新拉取数据。
21
+ */
22
+ export function createBrowserNavAdapter() {
23
+ return {
24
+ get pathname() {
25
+ return globalThis.location?.pathname ?? '';
26
+ },
27
+ navigate(url) {
28
+ globalThis.location?.assign(url);
29
+ },
30
+ };
31
+ }
@@ -11,9 +11,7 @@
11
11
  <script lang='ts'>
12
12
  import type { Size } from '../../../types.js'
13
13
  import { uiM } from '../../../messages.js'
14
- import BareButton from '../../primitives/BareButton.svelte'
15
- import IconButton from '../../primitives/IconButton.svelte'
16
- import Input from '../../primitives/Input.svelte'
14
+ import { BareButton, IconButton, Input } from '../../primitives/index.js'
17
15
 
18
16
  interface Props {
19
17
  /** 原始值 */
@@ -29,7 +27,7 @@
29
27
  /** 是否显示加密结果 */
30
28
  showEncrypted?: boolean
31
29
  /** 加密算法 */
32
- algorithm?: string
30
+ algorithm?: 'SM2' | 'SM4'
33
31
  /** 自定义类名 */
34
32
  class?: string
35
33
  /** 加密函数 */
@@ -13,7 +13,7 @@ interface Props {
13
13
  /** 是否显示加密结果 */
14
14
  showEncrypted?: boolean;
15
15
  /** 加密算法 */
16
- algorithm?: string;
16
+ algorithm?: 'SM2' | 'SM4';
17
17
  /** 自定义类名 */
18
18
  class?: string;
19
19
  /** 加密函数 */
@@ -12,8 +12,7 @@
12
12
  import type { HashDisplayProps } from '../types.js'
13
13
  import { uiM } from '../../../messages.js'
14
14
  import { cn } from '../../../utils.js'
15
- import Badge from '../../primitives/Badge.svelte'
16
- import IconButton from '../../primitives/IconButton.svelte'
15
+ import { Badge, IconButton } from '../../primitives/index.js'
17
16
 
18
17
  const {
19
18
  value = '',
@@ -12,9 +12,7 @@
12
12
  import type { SignatureDisplayProps } from '../types.js'
13
13
  import { uiM } from '../../../messages.js'
14
14
  import { cn } from '../../../utils.js'
15
- import Badge from '../../primitives/Badge.svelte'
16
-
17
- import IconButton from '../../primitives/IconButton.svelte'
15
+ import { Badge, IconButton } from '../../primitives/index.js'
18
16
 
19
17
  const {
20
18
  signature = '',
@@ -144,6 +144,7 @@
144
144
  const file = e.currentTarget.files?.[0]
145
145
  if (file) {
146
146
  onavatarchange?.(file)
147
+ e.currentTarget.value = ''
147
148
  }
148
149
  }
149
150
  </script>
@@ -170,9 +171,14 @@
170
171
  type='file'
171
172
  class='file-input file-input-sm w-full max-w-xs'
172
173
  accept='image/*'
174
+ disabled={loading}
173
175
  onchange={handleAvatarChange}
174
176
  />
175
- <p class='text-xs text-base-content/60 mt-1'>{uiM('user_profile_avatar_hint')}</p>
177
+ {#if errors.avatar}
178
+ <p class='text-xs text-error mt-1' role='alert'>{errors.avatar}</p>
179
+ {:else}
180
+ <p class='text-xs text-base-content/60 mt-1'>{uiM('user_profile_avatar_hint')}</p>
181
+ {/if}
176
182
  </div>
177
183
  {/if}
178
184
  </div>
@@ -499,7 +499,7 @@ export interface EncryptedInputProps {
499
499
  /** 是否显示加密结果 */
500
500
  showEncrypted?: boolean;
501
501
  /** 加密算法 */
502
- algorithm?: 'SM2' | 'SM4' | 'AES';
502
+ algorithm?: 'SM2' | 'SM4';
503
503
  /** 加密函数 */
504
504
  onencrypt?: (value: string) => Promise<string>;
505
505
  /** 自定义类名 */
@@ -520,7 +520,7 @@ export interface HashDisplayProps {
520
520
  /** 哈希值 */
521
521
  value?: string;
522
522
  /** 算法 */
523
- algorithm?: 'SM3' | 'SHA256' | 'MD5';
523
+ algorithm?: 'SM3';
524
524
  /** 标签 */
525
525
  label?: string;
526
526
  /** 是否可复制 */
@@ -560,7 +560,7 @@ export interface SignatureDisplayProps {
560
560
  /** 公钥 */
561
561
  publicKey?: string;
562
562
  /** 算法 */
563
- algorithm?: 'SM2' | 'RSA' | 'ECDSA';
563
+ algorithm?: 'SM2';
564
564
  /** 验证状态 */
565
565
  verified?: boolean;
566
566
  /** 是否显示公钥 */
@@ -210,6 +210,8 @@
210
210
  "markdown_format_heading_3": "Heading 3",
211
211
  "markdown_format_heading_4": "Heading 4",
212
212
  "markdown_format_bullet": "Bullets",
213
+ "markdown_format_ordered_list": "Ordered list",
214
+ "markdown_format_bullet_list": "Bullet list",
213
215
  "markdown_format_bold": "Bold",
214
216
  "markdown_format_strike": "Strike",
215
217
  "markdown_format_italic": "Italic",
@@ -224,6 +226,7 @@
224
226
  "markdown_align_justify": "Justify",
225
227
  "markdown_text_color": "Text color",
226
228
  "markdown_background_color": "Background color",
229
+ "markdown_background_none": "No background",
227
230
  "markdown_reset_default": "Reset default",
228
231
  "markdown_color_dialog_label": "Text and background color",
229
232
  "markdown_link_dialog_label": "Edit link",
@@ -231,6 +234,8 @@
231
234
  "markdown_link_placeholder": "Paste or type a link",
232
235
  "markdown_link_apply": "Apply link",
233
236
  "markdown_link_remove": "Remove link",
237
+ "markdown_link_open": "Open link",
238
+ "markdown_link_edit": "Edit link",
234
239
  "markdown_copy_selection": "Copy",
235
240
  "markdown_annotation": "Annotate",
236
241
  "markdown_run_preview": "Run Preview",
@@ -210,6 +210,8 @@
210
210
  "markdown_format_heading_3": "三级标题",
211
211
  "markdown_format_heading_4": "四级标题",
212
212
  "markdown_format_bullet": "列表",
213
+ "markdown_format_ordered_list": "有序列表",
214
+ "markdown_format_bullet_list": "无序列表",
213
215
  "markdown_format_bold": "加粗",
214
216
  "markdown_format_strike": "删除线",
215
217
  "markdown_format_italic": "斜体",
@@ -224,6 +226,7 @@
224
226
  "markdown_align_justify": "两端对齐",
225
227
  "markdown_text_color": "字体颜色",
226
228
  "markdown_background_color": "背景颜色",
229
+ "markdown_background_none": "无背景",
227
230
  "markdown_reset_default": "恢复默认",
228
231
  "markdown_color_dialog_label": "文本和背景颜色",
229
232
  "markdown_link_dialog_label": "编辑链接",
@@ -231,6 +234,8 @@
231
234
  "markdown_link_placeholder": "输入或粘贴链接地址",
232
235
  "markdown_link_apply": "应用链接",
233
236
  "markdown_link_remove": "移除链接",
237
+ "markdown_link_open": "打开链接",
238
+ "markdown_link_edit": "修改链接",
234
239
  "markdown_copy_selection": "复制",
235
240
  "markdown_annotation": "注释",
236
241
  "markdown_run_preview": "运行预览",
@@ -103,3 +103,31 @@
103
103
  overflow-y: auto;
104
104
  -webkit-overflow-scrolling: touch;
105
105
  }
106
+
107
+ /* ─── 主流移动端 App 壳布局 ─── */
108
+ .hai-mobile-viewport {
109
+ min-height: 100dvh;
110
+ background:
111
+ radial-gradient(circle at top left, color-mix(in srgb, hsl(var(--p)) 12%, transparent), transparent 32rem),
112
+ hsl(var(--b2));
113
+ }
114
+
115
+ .hai-mobile-shell {
116
+ width: 100%;
117
+ min-height: 100dvh;
118
+ margin-inline: auto;
119
+ display: flex;
120
+ flex-direction: column;
121
+ background: hsl(var(--b1));
122
+ box-shadow:
123
+ 0 0 0 1px color-mix(in srgb, currentColor 6%, transparent),
124
+ 0 24px 72px rgb(15 23 42 / 10%);
125
+ }
126
+
127
+ .hai-mobile-main {
128
+ flex: 1 1 auto;
129
+ min-height: 0;
130
+ overflow-y: auto;
131
+ overscroll-behavior: contain;
132
+ -webkit-overflow-scrolling: touch;
133
+ }
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.12",
4
+ "version": "0.1.0-alpha.15",
5
5
  "description": "Hai Framework UI component library for Svelte 5 Runes.",
6
6
  "license": "Apache-2.0",
7
7
  "svelte": "./dist/index.js",
@@ -33,7 +33,7 @@
33
33
  "access": "public"
34
34
  },
35
35
  "peerDependencies": {
36
- "svelte": "^5.53.5"
36
+ "svelte": "^5.55.5"
37
37
  },
38
38
  "dependencies": {
39
39
  "@internationalized/date": "^3.8.0",
@@ -44,7 +44,7 @@
44
44
  "marked": "^18.0.0",
45
45
  "shiki": "^4.0.2",
46
46
  "tailwind-merge": "^3.5.0",
47
- "@h-ai/core": "0.1.0-alpha.12"
47
+ "@h-ai/core": "0.1.0-alpha.15"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@sveltejs/package": "^2.5.7",