@h-ai/ui 0.1.0-alpha.61 → 0.1.0-alpha.63

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.en.md +93 -8
  2. package/README.ja.md +93 -8
  3. package/README.md +94 -8
  4. package/auto-import.d.ts +3 -0
  5. package/auto-import.js +3 -0
  6. package/dist/components/compounds/Combobox.svelte +19 -6
  7. package/dist/components/compounds/Combobox.svelte.d.ts +2 -2
  8. package/dist/components/compounds/DatePicker.svelte +14 -5
  9. package/dist/components/compounds/DatePicker.svelte.d.ts +2 -2
  10. package/dist/components/compounds/Form.svelte +10 -6
  11. package/dist/components/compounds/FormField.svelte +78 -13
  12. package/dist/components/compounds/FormLayout.svelte +59 -0
  13. package/dist/components/compounds/FormLayout.svelte.d.ts +4 -0
  14. package/dist/components/compounds/TagInput.svelte +1 -1
  15. package/dist/components/compounds/index.d.ts +1 -0
  16. package/dist/components/compounds/index.js +1 -0
  17. package/dist/components/primitives/BareInput.svelte +7 -1
  18. package/dist/components/primitives/Checkbox.svelte +8 -2
  19. package/dist/components/primitives/Input.svelte +22 -11
  20. package/dist/components/primitives/Select.svelte +77 -18
  21. package/dist/components/primitives/Textarea.svelte +27 -13
  22. package/dist/components/primitives/Textarea.svelte.d.ts +1 -1
  23. package/dist/components/scenes/app/FeedbackModal.svelte +29 -19
  24. package/dist/components/scenes/app/FeedbackModal.svelte.d.ts +2 -2
  25. package/dist/components/scenes/crud/CrudEditPanel.svelte +148 -118
  26. package/dist/components/scenes/crud/CrudEditPanel.svelte.d.ts +4 -25
  27. package/dist/components/scenes/crud/CrudPage.svelte +5 -41
  28. package/dist/components/scenes/crud/CrudPage.svelte.d.ts +2 -53
  29. package/dist/components/scenes/crud/crud-controller.svelte.d.ts +4 -1
  30. package/dist/components/scenes/crud/crud-form-validation.d.ts +4 -0
  31. package/dist/components/scenes/crud/crud-form-validation.js +39 -0
  32. package/dist/components/scenes/crud/crud-types.d.ts +9 -42
  33. package/dist/components/scenes/guide/GuideHost.svelte +111 -0
  34. package/dist/components/scenes/guide/GuideHost.svelte.d.ts +16 -0
  35. package/dist/components/scenes/guide/HelpCenter.svelte +94 -0
  36. package/dist/components/scenes/guide/HelpCenter.svelte.d.ts +13 -0
  37. package/dist/components/scenes/guide/index.d.ts +2 -0
  38. package/dist/components/scenes/guide/index.js +2 -0
  39. package/dist/components/scenes/iam/AuthPanel.svelte +55 -47
  40. package/dist/components/scenes/iam/ChangePasswordForm.svelte +57 -59
  41. package/dist/components/scenes/iam/ForgotPasswordForm.svelte +89 -84
  42. package/dist/components/scenes/iam/LoginForm.svelte +134 -132
  43. package/dist/components/scenes/iam/PasswordInput.svelte +25 -7
  44. package/dist/components/scenes/iam/PasswordInput.svelte.d.ts +2 -2
  45. package/dist/components/scenes/iam/RegisterForm.svelte +95 -97
  46. package/dist/components/scenes/iam/ResetPasswordForm.svelte +83 -85
  47. package/dist/components/scenes/iam/UserProfile.svelte +9 -5
  48. package/dist/components/scenes/iam/auth-error-message.d.ts +2 -4
  49. package/dist/components/scenes/index.d.ts +1 -0
  50. package/dist/components/scenes/index.js +1 -0
  51. package/dist/components/scenes/storage/FileUpload.svelte +1 -1
  52. package/dist/components/scenes/storage/ImageUpload.svelte +1 -0
  53. package/dist/components/scenes/types.d.ts +9 -9
  54. package/dist/guide-storage.d.ts +6 -0
  55. package/dist/guide-storage.js +67 -0
  56. package/dist/guide-types.d.ts +78 -0
  57. package/dist/guide-types.js +1 -0
  58. package/dist/guide.d.ts +3 -0
  59. package/dist/guide.js +3 -0
  60. package/dist/guide.svelte.d.ts +8 -0
  61. package/dist/guide.svelte.js +450 -0
  62. package/dist/i18n.svelte.d.ts +2 -1
  63. package/dist/i18n.svelte.js +2 -1
  64. package/dist/index.d.ts +2 -0
  65. package/dist/index.js +2 -0
  66. package/dist/internal/form-context.d.ts +12 -0
  67. package/dist/internal/form-context.js +9 -0
  68. package/dist/internal/form-validation.svelte.d.ts +16 -0
  69. package/dist/internal/form-validation.svelte.js +76 -0
  70. package/dist/internal/locale.d.ts +2 -0
  71. package/dist/internal/locale.js +22 -0
  72. package/dist/messages/en-US.json +30 -1
  73. package/dist/messages/zh-CN.json +30 -1
  74. package/dist/messages.d.ts +2 -1
  75. package/dist/messages.js +6 -4
  76. package/dist/styles/guide.css +88 -0
  77. package/dist/types.d.ts +57 -10
  78. package/package.json +13 -2
  79. package/vite.js +1 -0
package/README.en.md CHANGED
@@ -98,7 +98,7 @@ With auto-import enabled, use components directly in templates without individua
98
98
 
99
99
  > `@h-ai/ui/auto-import` injects all public Svelte components except `toast`, type imports, and `Range` (which conflicts with the DOM `Range` constructor).
100
100
 
101
- Pages using a subset of components, such as administration screens, can import public component-directory entry points directly. Import theme utilities from the lightweight `@h-ai/ui/theme` entry so theme consumers do not load the root entry's AI and chart components. Check the installed package's exports; do not import its internal `src/` or `dist/` paths.
101
+ Pages using a subset of components, such as administration screens, can import public component-directory entry points directly. Import theme utilities from the lightweight `@h-ai/ui/theme` entry so theme consumers do not load the root entry's AI and chart components. Use `@h-ai/ui/i18n` for locale utilities in stores without loading components or tour CSS. Check the installed package's exports; do not import its internal `src/` or `dist/` paths.
102
102
 
103
103
  The preprocessor recognizes semicolon-free, multiline and aliased imports without injecting components already bound locally. Type-only imports do not replace runtime components.
104
104
 
@@ -166,16 +166,42 @@ components/
166
166
 
167
167
  For `BareInput` with `type="file"`, read `event.currentTarget.files` in `onchange` and leave the file path `value` unbound. Other input types support `bind:value`.
168
168
 
169
- ### Compounds (36)
169
+ ### Compounds (37)
170
170
 
171
171
  #### Forms
172
172
 
173
173
  | Component | Description | Key props |
174
174
  | ----------- | -------------- | -------------------------------------------- |
175
- | `Form` | Form container | `loading`, `disabled`, `onsubmit` |
176
- | `FormField` | Form field | `label`, `error`, `hint`, `required` |
175
+ | `Form` | Form container | `columns`, `labelPosition`, `labelWidth`, `loading`, `disabled`, `onsubmit` |
176
+ | `FormField` | Form field | `label`, `for`, `colSpan`, `labelPosition`, `error`, `hint`, `required` |
177
+ | `FormLayout` | Form layout | `columns`, `labelPosition`, `labelWidth` |
177
178
  | `TagInput` | Tag input | `tags`, `maxTags`, `allowDuplicates`, `size` |
178
179
 
180
+ #### Form layouts and validation
181
+
182
+ `Form` and `FormLayout` (for native `<form>` elements) share `columns={1 | 2}`, `labelPosition="top" | "left"` and `labelWidth` (default `8rem`). Defaults are one column and stacked labels. Two columns collapse below a container width of `36rem`; inline labels stack below `28rem`.
183
+
184
+ `FormField.colSpan` defaults to `'auto'`: textareas, file/image uploads, TagInput and multiple Combobox controls span both columns. Use `1` or `2` to override, or mark a custom wide control with `data-form-wide`. Non-field content, including headings, error summaries and actions, spans the full row. Fields may override `labelPosition` / `labelWidth`.
185
+
186
+ Labels connect to nested UI controls automatically; use `<FormField for="control-id">` for explicit IDs or native controls. Errors and hints use `aria-describedby`, invalid controls use `aria-invalid`, and field errors appear once. `label` / `error` / `hint` accept strings or deferred translation functions.
187
+
188
+ `Input`, `Textarea`, `Select` and `PasswordInput` provide Chinese/English native validation messages. `validationMessage` overrides failures without keeping valid input invalid. Sync locale with the UI `setGlobalLocale` / `createLocaleStore` to update visible validation errors immediately. `Textarea` supports `minlength` / `maxlength`; `Select` supports `name`, required validation and FormData serialization.
189
+
190
+ `LoginForm`, `RegisterForm`, `ChangePasswordForm`, `ForgotPasswordForm`, `ResetPasswordForm`, `AuthPanel`, `UserProfile` and `FeedbackModal` accept the same layout options. Pass them through `CrudPage.form` or directly to `CrudEditPanel`. CRUD fields support `colSpan` and deferred `validation.message`. Buttons and Enter both validate before submission; corrections clear errors. Server validation remains required.
191
+
192
+ ```svelte
193
+ <!-- m、save、name、notes 为应用自己的翻译、提交回调和状态 -->
194
+ <Form columns={2} labelPosition="left" labelWidth="6rem" onsubmit={save}>
195
+ <FormField label={() => m.name()} required>
196
+ <Input name="name" bind:value={name} required />
197
+ </FormField>
198
+ <FormField label={() => m.notes()}>
199
+ <Textarea name="notes" bind:value={notes} />
200
+ </FormField>
201
+ <Button type="submit">{m.save()}</Button>
202
+ </Form>
203
+ ```
204
+
179
205
  #### Feedback
180
206
 
181
207
  | Component | Description | Key props |
@@ -248,7 +274,14 @@ For `BareInput` with `type="file"`, read `event.currentTarget.files` in `onchang
248
274
  | ------------ | ----------- | ----------------------------------------------- |
249
275
  | `PageHeader` | Page header | `title`, `description`; supports `actions` slot |
250
276
 
251
- ### Scenes (34)
277
+ ### Scenes (36)
278
+
279
+ #### Guide (2)
280
+
281
+ | Component | Description | Key props |
282
+ | --- | --- | --- |
283
+ | `GuideHost` | First-use invitations, tours and help dialog host | `guide`, `locale`, `scope`, `autoStart` |
284
+ | `HelpCenter` | Standalone searchable help center | `articles`, `tours`, `locale`, `selectedId`, `onstart` |
252
285
 
253
286
  #### App (7)
254
287
 
@@ -524,7 +557,7 @@ Defaults match `@h-ai/crypto`: `SM4` for encrypted inputs, `SM3` for hashes, and
524
557
  crud={roleCrud}
525
558
  {data}
526
559
  permissions={{ create: true, update: true, delete: true }}
527
- form={{ variant: 'modal', modalSize: 'lg' }}
560
+ form={{ variant: 'modal', modalSize: '3xl', columns: 2, labelPosition: 'top' }}
528
561
  pagination={{ showSizeChanger: true, showJumper: true, pageSizeOptions: [10, 20, 50] }}
529
562
  density='compact'
530
563
  detailColumns={['name']}
@@ -691,7 +724,7 @@ Client locale state management, automatically synchronized with the `@h-ai/core`
691
724
 
692
725
  ```svelte
693
726
  <script>
694
- import { createLocaleStore, setGlobalLocale } from '@h-ai/ui'
727
+ import { createLocaleStore, setGlobalLocale } from '@h-ai/ui/i18n'
695
728
  import { setLocale } from '$lib/paraglide/runtime'
696
729
 
697
730
  const localeStore = createLocaleStore()
@@ -722,7 +755,7 @@ import {
722
755
  isLocaleSupported, // 检查 locale 是否支持
723
756
  resolveLocale, // 解析 locale(支持回退)
724
757
  setGlobalLocale, // 设置全局 locale(同步 @h-ai/core)
725
- } from '@h-ai/ui'
758
+ } from '@h-ai/ui/i18n'
726
759
  ```
727
760
 
728
761
  ## Configuration driven authentication
@@ -756,6 +789,58 @@ AuthPanel disables native form controls during SSR and enables input after clien
756
789
 
757
790
  [完整 IAM 配置 / IAM configuration](../iam/README.en.md)
758
791
 
792
+ ## Onboarding and help center
793
+
794
+ Import `createGuide`, `GuideHost` and `HelpCenter` from `@h-ai/ui/guide`. No init/close is required. The tour engine loads when a tour starts; controls follow the current theme and include Chinese/English translations. Business text uses application i18n callbacks.
795
+
796
+ The same entry exports `resolveGuideText(text?)` to evaluate strings or i18n callbacks; host components resolve text automatically.
797
+
798
+ Create the controller inside a layout component, register tours with id/title/steps and optional route/version, then mount `<GuideHost {guide} {locale} scope={user.id} autoStart='overview' />`. Add `data-guide` to existing target components. Supply the application's NavAdapter for SPA navigation.
799
+
800
+ ```svelte
801
+ <script lang='ts'>
802
+ import { createGuide, GuideHost } from '@h-ai/ui/guide'
803
+
804
+ const guide = createGuide({
805
+ nav,
806
+ persistKey: 'my-app-guide',
807
+ tours: [{
808
+ id: 'users',
809
+ version: 1,
810
+ route: '/users',
811
+ title: () => m.guide_users_title(),
812
+ description: () => m.guide_users_description(),
813
+ steps: [{
814
+ target: 'users-create',
815
+ title: () => m.guide_create_title(),
816
+ description: () => m.guide_create_description(),
817
+ }],
818
+ }],
819
+ articles: [{
820
+ id: 'users-help',
821
+ title: () => m.guide_users_title(),
822
+ content: () => m.guide_users_markdown(),
823
+ tourId: 'users',
824
+ }],
825
+ })
826
+ </script>
827
+
828
+ <GuideHost {guide} {locale} scope={user.id} autoStart='users' />
829
+ <Button onclick={() => guide.openHelp()}>{m.help()}</Button>
830
+ <!-- 在目标页面为现有按钮添加锚点,无需额外包装。 -->
831
+ <Button data-guide='users-create' onclick={createUser}>{m.create_user()}</Button>
832
+ ```
833
+
834
+ `start(id)` returns Promise<boolean> and always allows replay; failure returns false and sets error. `startIfNeeded(id)` returns boolean and offers a first-use invitation. `openHelp(articleId?)` opens searchable Markdown help; `stop()` cancels without marking completion; `reset(id)` clears the current user's preference.
835
+
836
+ The close button, Escape, and overlay clicks cancel pending highlighting and clear `busy` even before the first positioning frame completes. A pending `start()` resolves to false without recording completion.
837
+
838
+ Persistence supports cookie (default), localStorage and memory, with an in-instance fallback when storage is restricted. Preferences are isolated by user and version. Later delays reminders for one day; Cookie preferences expire after one year. The default full-page navigation uses a short-lived sessionStorage record to start on the destination page.
839
+
840
+ Use `() => m.key()` for localized content and pass the current locale. Steps render as plain text; help uses the existing safe Markdown renderer with raw HTML disabled. Targets wait up to 3000ms by default; before(signal) prepares tabs or drawers, optional skips nonessential missing targets, and missing required targets stop the tour. Unmounting cancels pending work. Never share controllers across SSR users.
841
+
842
+ HelpCenter can also render a standalone page using articles, optional tours, locale, selectedId and onstart. The admin-console example and CLI fullstack Web/App/Desktop include first-use invitations, help and profile tours by default. See the [complete integration guide](../cli/templates/skills/hai-ui/guide.md).
843
+
759
844
  ## Other exports
760
845
 
761
846
  ```ts
package/README.ja.md CHANGED
@@ -98,7 +98,7 @@ export default defineConfig({
98
98
 
99
99
  > `toast`・型 import・DOM の `Range` と衝突する `Range` を除き、公開 Svelte コンポーネントは `@h-ai/ui/auto-import` で自動注入できます。
100
100
 
101
- 管理画面など一部のコンポーネントを使うページは、公開コンポーネントディレクトリーの入口から直接 import できます。テーマツールは軽量な `@h-ai/ui/theme` から読み込み、テーマの利用だけでルート入口の AI・グラフコンポーネントを読み込まないようにします。インストール済みパッケージの exports を確認し、内部 `src/`・`dist/` は import しないでください。
101
+ 管理画面など一部のコンポーネントを使うページは、公開コンポーネントディレクトリーの入口から直接 import できます。テーマツールは軽量な `@h-ai/ui/theme` から読み込み、テーマの利用だけでルート入口の AI・グラフコンポーネントを読み込まないようにします。ストアの言語ツールは `@h-ai/ui/i18n` から読み込み、コンポーネントやガイド CSS を読み込みません。インストール済みパッケージの exports を確認し、内部 `src/`・`dist/` は import しないでください。
102
102
 
103
103
  自動 import はセミコロンなし、複数行、別名の import を認識し、既存のローカル名を重複注入しません。型のみの import は実行時コンポーネントの代わりにはなりません。
104
104
 
@@ -166,16 +166,42 @@ components/
166
166
 
167
167
  `BareInput` の `type="file"` では `onchange` の `event.currentTarget.files` を読み取り、ファイルパスの `value` はバインドしません。他の入力型では `bind:value` を使用できます。
168
168
 
169
- ### Compounds:複合コンポーネント(36)
169
+ ### Compounds:複合コンポーネント(37)
170
170
 
171
171
  #### フォーム
172
172
 
173
173
  | コンポーネント | 説明 | 主なプロパティ |
174
174
  | -------------- | ------------------ | -------------------------------------------- |
175
- | `Form` | フォームコンテナー | `loading`, `disabled`, `onsubmit` |
176
- | `FormField` | フォーム項目 | `label`, `error`, `hint`, `required` |
175
+ | `Form` | フォームコンテナー | `columns`, `labelPosition`, `labelWidth`, `loading`, `disabled`, `onsubmit` |
176
+ | `FormField` | フォーム項目 | `label`, `for`, `colSpan`, `labelPosition`, `error`, `hint`, `required` |
177
+ | `FormLayout` | フォーム配置 | `columns`, `labelPosition`, `labelWidth` |
177
178
  | `TagInput` | タグ入力 | `tags`, `maxTags`, `allowDuplicates`, `size` |
178
179
 
180
+ #### フォームのレイアウトと検証
181
+
182
+ `Form` とネイティブ `<form>` 内で使う `FormLayout` は、`columns={1 | 2}`、`labelPosition="top" | "left"`、`labelWidth`(既定 `8rem`)を共有します。既定は1列・ラベルを上に配置します。コンテナー幅が `36rem` 未満では1列、`28rem` 未満では横並びラベルも縦並びになります。
183
+
184
+ `FormField.colSpan` の既定は `'auto'`。Textarea、ファイル・画像アップロード、TagInput、複数選択 Combobox は2列を占めます。`1` / `2` で指定でき、独自の幅広コントロールには `data-form-wide` を付けます。見出し、エラー概要、操作ボタンなどの非 FormField 要素は行全体を使用します。項目ごとに `labelPosition` / `labelWidth` を上書きできます。
185
+
186
+ ラベルは内部 UI コントロールに自動で関連付けられます。明示 ID やネイティブ要素は `<FormField for="control-id">` を使います。エラーとヒントは `aria-describedby`、無効な要素は `aria-invalid` で通知し、エラーは重複表示しません。`label` / `error` / `hint` は文字列または翻訳関数を受け取ります。
187
+
188
+ `Input`、`Textarea`、`Select`、`PasswordInput` のネイティブ検証は中国語・英語に対応します。`validationMessage` は失敗時だけ適用され、有効な入力を無効にし続けません。UI の `setGlobalLocale` / `createLocaleStore` で言語を同期すると表示中のエラーも更新されます。`Textarea` は `minlength` / `maxlength`、`Select` は `name`・必須検証・FormData に対応します。
189
+
190
+ `LoginForm`、`RegisterForm`、`ChangePasswordForm`、`ForgotPasswordForm`、`ResetPasswordForm`、`AuthPanel`、`UserProfile`、`FeedbackModal` は同じレイアウト設定に対応します。`CrudPage.form` または `CrudEditPanel` に渡します。CRUD 項目は `colSpan` と翻訳関数の `validation.message` に対応し、ボタンと Enter のどちらも送信前に検証します。入力修正でエラーが消えます。サーバー側の検証も必要です。
191
+
192
+ ```svelte
193
+ <!-- m、save、name、notes 为应用自己的翻译、提交回调和状态 -->
194
+ <Form columns={2} labelPosition="left" labelWidth="6rem" onsubmit={save}>
195
+ <FormField label={() => m.name()} required>
196
+ <Input name="name" bind:value={name} required />
197
+ </FormField>
198
+ <FormField label={() => m.notes()}>
199
+ <Textarea name="notes" bind:value={notes} />
200
+ </FormField>
201
+ <Button type="submit">{m.save()}</Button>
202
+ </Form>
203
+ ```
204
+
179
205
  #### フィードバック
180
206
 
181
207
  | コンポーネント | 説明 | 主なプロパティ |
@@ -248,7 +274,14 @@ components/
248
274
  | -------------- | -------------- | ---------------------------------------------- |
249
275
  | `PageHeader` | ページヘッダー | `title`, `description`、`actions` スロット対応 |
250
276
 
251
- ### Scenes:用途別コンポーネント(34)
277
+ ### Scenes:用途別コンポーネント(36)
278
+
279
+ #### Guide:案内・ヘルプ(2)
280
+
281
+ | コンポーネント | 説明 | 主なプロパティ |
282
+ | --- | --- | --- |
283
+ | `GuideHost` | 初回案内・ガイド・ヘルプダイアログ | `guide`, `locale`, `scope`, `autoStart` |
284
+ | `HelpCenter` | 独立して使える検索付きヘルプ | `articles`, `tours`, `locale`, `selectedId`, `onstart` |
252
285
 
253
286
  #### App:アプリ全体(7)
254
287
 
@@ -524,7 +557,7 @@ components/
524
557
  crud={roleCrud}
525
558
  {data}
526
559
  permissions={{ create: true, update: true, delete: true }}
527
- form={{ variant: 'modal', modalSize: 'lg' }}
560
+ form={{ variant: 'modal', modalSize: '3xl', columns: 2, labelPosition: 'top' }}
528
561
  pagination={{ showSizeChanger: true, showJumper: true, pageSizeOptions: [10, 20, 50] }}
529
562
  density='compact'
530
563
  detailColumns={['name']}
@@ -691,7 +724,7 @@ transformPageChunk: ({ html }) => html.replace('%theme_bootstrap%', themeBootstr
691
724
 
692
725
  ```svelte
693
726
  <script>
694
- import { createLocaleStore, setGlobalLocale } from '@h-ai/ui'
727
+ import { createLocaleStore, setGlobalLocale } from '@h-ai/ui/i18n'
695
728
  import { setLocale } from '$lib/paraglide/runtime'
696
729
 
697
730
  const localeStore = createLocaleStore()
@@ -722,7 +755,7 @@ import {
722
755
  isLocaleSupported, // 检查 locale 是否支持
723
756
  resolveLocale, // 解析 locale(支持回退)
724
757
  setGlobalLocale, // 设置全局 locale(同步 @h-ai/core)
725
- } from '@h-ai/ui'
758
+ } from '@h-ai/ui/i18n'
726
759
  ```
727
760
 
728
761
  ## 設定による認証画面
@@ -756,6 +789,58 @@ AuthPanel は SSR 中のフォーム操作を無効にし、クライアント
756
789
 
757
790
  [完整 IAM 配置 / IAM configuration](../iam/README.ja.md)
758
791
 
792
+ ## 初回ガイドとヘルプセンター
793
+
794
+ `@h-ai/ui/guide` から `createGuide`、`GuideHost`、`HelpCenter` をインポートします。init/close は不要です。ガイド開始時にエンジンを読み込み、既存テーマと中国語・英語の共通文言を使用します。業務内容はアプリの翻訳関数を使います。
795
+
796
+ 同じ入口の `resolveGuideText(text?)` は文字列や i18n コールバックを評価します。ホストコンポーネントは文言を自動で解析します。
797
+
798
+ レイアウト内でコントローラーを作成し、id/title/steps と任意の route/version を登録します。`<GuideHost {guide} {locale} scope={user.id} autoStart='overview' />` を配置し、対象コンポーネントに data-guide を付けます。SPA は既存の NavAdapter を提供してください。
799
+
800
+ ```svelte
801
+ <script lang='ts'>
802
+ import { createGuide, GuideHost } from '@h-ai/ui/guide'
803
+
804
+ const guide = createGuide({
805
+ nav,
806
+ persistKey: 'my-app-guide',
807
+ tours: [{
808
+ id: 'users',
809
+ version: 1,
810
+ route: '/users',
811
+ title: () => m.guide_users_title(),
812
+ description: () => m.guide_users_description(),
813
+ steps: [{
814
+ target: 'users-create',
815
+ title: () => m.guide_create_title(),
816
+ description: () => m.guide_create_description(),
817
+ }],
818
+ }],
819
+ articles: [{
820
+ id: 'users-help',
821
+ title: () => m.guide_users_title(),
822
+ content: () => m.guide_users_markdown(),
823
+ tourId: 'users',
824
+ }],
825
+ })
826
+ </script>
827
+
828
+ <GuideHost {guide} {locale} scope={user.id} autoStart='users' />
829
+ <Button onclick={() => guide.openHelp()}>{m.help()}</Button>
830
+ <!-- 在目标页面为现有按钮添加锚点,无需额外包装。 -->
831
+ <Button data-guide='users-create' onclick={createUser}>{m.create_user()}</Button>
832
+ ```
833
+
834
+ start(id) は Promise<boolean> を返し、手動で再表示できます。失敗時は false と error を設定します。startIfNeeded(id) は初回案内の有無を boolean で返します。openHelp(articleId?) は検索付き Markdown ヘルプを開き、stop() は完了として記録せず終了し、reset(id) は現在のユーザーの状態を削除します。
835
+
836
+ 閉じるボタン、Escape、オーバーレイのクリックは、最初の位置決めフレームが完了する前でも待機と `busy` 状態を解除します。開始中の `start()` は false を返し、完了として記録しません。
837
+
838
+ 保存先は cookie(既定)、localStorage、memory です。保存が制限された環境ではインスタンス内のメモリーに戻ります。ユーザーとバージョンごとに状態を分け、後で表示は一日延期、Cookie は一年保存します。既定のページ遷移は短期 sessionStorage 記録で移動先から開始します。
839
+
840
+ 文言は `() => m.key()` で遅延評価し、現在の locale を渡します。説明はプレーンテキスト、ヘルプは安全な MarkdownRenderer を使用し、生 HTML を無効にします。要素の待機は既定で 3000ms。before(signal) でタブ等を準備し、optional の要素のみ省略できます。必要な要素がなければ終了し、アンマウント時に処理を解除します。SSR ユーザー間でコントローラーを共有しないでください。
841
+
842
+ HelpCenter は articles、任意の tours、locale、selectedId、onstart で独立ページにも配置できます。admin-console と CLI fullstack Web/App/Desktop は初回案内、ヘルプ、プロフィールガイドを標準搭載します。[詳細ガイド](../cli/templates/skills/hai-ui/guide.md)を参照してください。
843
+
759
844
  ## その他のエクスポート
760
845
 
761
846
  ```ts
package/README.md CHANGED
@@ -98,7 +98,7 @@ export default defineConfig({
98
98
 
99
99
  > 除 `toast`、类型导入与 `Range`(会和 DOM `Range` 构造器冲突)外,其余公开 Svelte 组件都可通过 `@h-ai/ui/auto-import` 自动注入。
100
100
 
101
- 管理后台等只使用部分组件的页面,可直接使用公开组件目录入口;主题工具通过轻量 `@h-ai/ui/theme` 导入,避免主题消费加载根入口中的 AI、图表等组件。组件路径以已安装版本的 exports 为准,不导入包内 `src/` 或 `dist/`。
101
+ 管理后台等只使用部分组件的页面,可直接使用公开组件目录入口;主题工具通过轻量 `@h-ai/ui/theme` 导入,避免主题消费加载根入口中的 AI、图表等组件。状态层的语言工具使用 `@h-ai/ui/i18n`,不加载组件与引导 CSS。组件路径以已安装版本的 exports 为准,不导入包内 `src/` 或 `dist/`。
102
102
 
103
103
  自动导入器识别无分号、跨行及带别名的显式导入,已绑定的组件名称不会重复注入;仅类型导入不会替代运行时组件。
104
104
 
@@ -166,16 +166,42 @@ components/
166
166
 
167
167
  `BareInput` 的 `file` 类型通过 `onchange` 读取 `event.currentTarget.files`,不绑定文件路径 `value`;其他输入类型可使用 `bind:value`。
168
168
 
169
- ### 组合组件 Compounds(36 个)
169
+ ### 组合组件 Compounds(37 个)
170
170
 
171
171
  #### 表单
172
172
 
173
173
  | 组件 | 描述 | 主要属性 |
174
174
  | ----------- | -------- | -------------------------------------------- |
175
- | `Form` | 表单容器 | `loading`, `disabled`, `onsubmit` |
176
- | `FormField` | 表单字段 | `label`, `error`, `hint`, `required` |
175
+ | `Form` | 表单容器 | `columns`, `labelPosition`, `labelWidth`, `loading`, `disabled`, `onsubmit` |
176
+ | `FormField` | 表单字段 | `label`, `for`, `colSpan`, `labelPosition`, `error`, `hint`, `required` |
177
+ | `FormLayout` | 表单布局 | `columns`, `labelPosition`, `labelWidth` |
177
178
  | `TagInput` | 标签输入 | `tags`, `maxTags`, `allowDuplicates`, `size` |
178
179
 
180
+ #### 表单布局与校验
181
+
182
+ `Form` 和可嵌入原生 `<form>` 的 `FormLayout` 共用 `columns={1 | 2}`、`labelPosition="top" | "left"`、`labelWidth`(默认 `8rem`)。默认单列、标签独立成行;双列在容器不足 `36rem` 时收为单列,同行标签在容器不足 `28rem` 时改为上下排列。
183
+
184
+ `FormField.colSpan` 默认 `'auto'`:多行文本、文件/图片上传、TagInput 和多选 Combobox 自动占两列;`1` / `2` 显式覆盖。自定义宽控件标注 `data-form-wide`。标题、错误摘要、按钮及其他非 FormField 内容自动独占一行。字段可单独覆盖 `labelPosition` / `labelWidth`。
185
+
186
+ 标签自动关联内部 UI 控件;已有控件 ID 或原生控件使用 `<FormField for="control-id">`。错误/提示通过 `aria-describedby` 关联,错误控件带 `aria-invalid`,只显示一份字段提示。`label` / `error` / `hint` 支持字符串或延迟翻译函数。
187
+
188
+ `Input`、`Textarea`、`Select` 和 `PasswordInput` 的原生校验使用内置中英文提示;`validationMessage` 只覆盖失败提示,不让有效输入一直无效。应用通过 UI 的 `setGlobalLocale` / `createLocaleStore` 同步语言,已显示的校验错误即时更新。`Textarea` 支持 `minlength` / `maxlength`,`Select` 支持 `name`、必填校验和 FormData 序列化。
189
+
190
+ `LoginForm`、`RegisterForm`、`ChangePasswordForm`、`ForgotPasswordForm`、`ResetPasswordForm`、`AuthPanel`、`UserProfile` 和 `FeedbackModal` 接受同一组布局参数。`CrudPage` 通过 `form` 传入;`CrudEditPanel` 直接接收。CRUD 字段支持 `colSpan`,`validation.message` 支持翻译函数;按钮与回车均先校验,修正后错误消失,客户端校验不能替代服务端校验。
191
+
192
+ ```svelte
193
+ <!-- m、save、name、notes 为应用自己的翻译、提交回调和状态 -->
194
+ <Form columns={2} labelPosition="left" labelWidth="6rem" onsubmit={save}>
195
+ <FormField label={() => m.name()} required>
196
+ <Input name="name" bind:value={name} required />
197
+ </FormField>
198
+ <FormField label={() => m.notes()}>
199
+ <Textarea name="notes" bind:value={notes} />
200
+ </FormField>
201
+ <Button type="submit">{m.save()}</Button>
202
+ </Form>
203
+ ```
204
+
179
205
  #### 反馈
180
206
 
181
207
  | 组件 | 描述 | 主要属性 |
@@ -248,7 +274,14 @@ components/
248
274
  | ------------ | -------- | ------------------------------------------- |
249
275
  | `PageHeader` | 页面头部 | `title`, `description`,支持 `actions` 插槽 |
250
276
 
251
- ### 场景组件 Scenes(34 个)
277
+ ### 场景组件 Scenes(36 个)
278
+
279
+ #### Guide 引导与帮助(2 个)
280
+
281
+ | 组件 | 描述 | 主要属性 |
282
+ | --- | --- | --- |
283
+ | `GuideHost` | 首次邀请、页面导览与帮助弹窗宿主 | `guide`, `locale`, `scope`, `autoStart` |
284
+ | `HelpCenter` | 可独立使用的帮助中心 | `articles`, `tours`, `locale`, `selectedId`, `onstart` |
252
285
 
253
286
  #### App 应用级(7 个)
254
287
 
@@ -523,7 +556,7 @@ components/
523
556
  crud={roleCrud}
524
557
  {data}
525
558
  permissions={{ create: true, update: true, delete: true }}
526
- form={{ variant: 'modal', modalSize: 'lg' }}
559
+ form={{ variant: 'modal', modalSize: '3xl', columns: 2, labelPosition: 'top' }}
527
560
  pagination={{ showSizeChanger: true, showJumper: true, pageSizeOptions: [10, 20, 50] }}
528
561
  density='compact'
529
562
  detailColumns={['name']}
@@ -721,7 +754,7 @@ AuthPanel 在 SSR 阶段禁用原生表单控件,客户端挂载并绑定事
721
754
 
722
755
  ```svelte
723
756
  <script>
724
- import { createLocaleStore, setGlobalLocale } from '@h-ai/ui'
757
+ import { createLocaleStore, setGlobalLocale } from '@h-ai/ui/i18n'
725
758
  import { setLocale } from '$lib/paraglide/runtime'
726
759
 
727
760
  const localeStore = createLocaleStore()
@@ -752,7 +785,7 @@ import {
752
785
  isLocaleSupported, // 检查 locale 是否支持
753
786
  resolveLocale, // 解析 locale(支持回退)
754
787
  setGlobalLocale, // 设置全局 locale(同步 @h-ai/core)
755
- } from '@h-ai/ui'
788
+ } from '@h-ai/ui/i18n'
756
789
  ```
757
790
 
758
791
 
@@ -773,6 +806,59 @@ import {
773
806
  上例的 subscribe 为应用自身的 $state,应用包装 register 动作校验/提交扩展业务数据;如需单独组合表单,使用 `<RegisterForm> {#snippet extraFields()}...{/snippet} </RegisterForm>`。扩展里的按钮须为 `type="button"`,避免误提交。
774
807
 
775
808
 
809
+ ## 新手引导与帮助中心
810
+
811
+ `@h-ai/ui/guide` 提供 `createGuide`、`GuideHost`、`HelpCenter` 和相关类型。无需 init/close;引擎在开始导览时按需加载,浮层复用当前主题。组件内置中英文按钮、进度和错误提示,业务内容使用应用 i18n。
812
+
813
+ 同一入口的 `resolveGuideText(text?)` 可解析字符串或 i18n 回调;宿主组件已自动解析文案。
814
+
815
+ 下例放在应用布局中;`nav` 是应用现有 NavAdapter,`locale`、`user` 与翻译函数 `m` 来自应用,翻译 key 需在应用消息中定义。目标按钮放在 `/users` 页面。
816
+
817
+ ```svelte
818
+ <script lang='ts'>
819
+ import { createGuide, GuideHost } from '@h-ai/ui/guide'
820
+
821
+ const guide = createGuide({
822
+ nav,
823
+ persistKey: 'my-app-guide',
824
+ tours: [{
825
+ id: 'users',
826
+ version: 1,
827
+ route: '/users',
828
+ title: () => m.guide_users_title(),
829
+ description: () => m.guide_users_description(),
830
+ steps: [{
831
+ target: 'users-create',
832
+ title: () => m.guide_create_title(),
833
+ description: () => m.guide_create_description(),
834
+ }],
835
+ }],
836
+ articles: [{
837
+ id: 'users-help',
838
+ title: () => m.guide_users_title(),
839
+ content: () => m.guide_users_markdown(),
840
+ tourId: 'users',
841
+ }],
842
+ })
843
+ </script>
844
+
845
+ <GuideHost {guide} {locale} scope={user.id} autoStart='users' />
846
+ <Button onclick={() => guide.openHelp()}>{m.help()}</Button>
847
+ <!-- 在目标页面为现有按钮添加锚点,无需额外包装。 -->
848
+ <Button data-guide='users-create' onclick={createUser}>{m.create_user()}</Button>
849
+ ```
850
+
851
+ - `start(id)` 返回 `Promise<boolean>`,允许手动重看;失败返回 false 并设置 `error`。`startIfNeeded(id)` 返回 boolean 并显示首次邀请,通常由 autoStart 处理。
852
+ - `openHelp(articleId?)` 打开帮助,`stop()` 取消导览,`reset(id)` 删除当前用户的偏好。完成、稍后和不再提醒分别记录;稍后暂缓一天,Cookie 默认保存一年。
853
+ - 关闭按钮、Escape 和遮罩点击在首帧定位期间也会取消等待、清除 `busy`;启动中的 `start()` 返回 false,不记录完成。
854
+ - `persistence` 支持 cookie(默认)、localStorage、memory;受限存储回退到实例内存。scope 使用非敏感用户标识,version 变化会重新提示。
855
+ - 通过已有 NavAdapter 接入 SvelteKit、SPA 或原生 WebView;默认完整导航使用 sessionStorage 保存短期启动记录。宿主只在浏览器挂载后读取记录,SSR 实例不得跨用户共享。
856
+ - 目标等待默认 3000ms,可用 waitTimeout 调整。before(signal) 可展开页签/抽屉;必要元素缺失停止,可选步骤使用 optional 跳过。引导不会自动提交业务表单。
857
+ - 业务文案用函数延迟求值,并传入当前 locale。步骤内容按纯文本显示;帮助正文复用安全 MarkdownRenderer,原始 HTML 关闭。
858
+ - HelpCenter 可独立放入帮助页面,props 为 articles、tours(可选)、locale、selectedId 和 onstart;支持类别标签、标题/正文搜索和关联导览。
859
+
860
+ admin-console 示例和 CLI fullstack Web/App/Desktop 默认包含帮助入口、首次邀请及个人资料导览。完整配置和验证场景见 [引导指南](../cli/templates/skills/hai-ui/guide.md)。
861
+
776
862
  ## 其他导出
777
863
 
778
864
  ```ts
package/auto-import.d.ts CHANGED
@@ -16,6 +16,8 @@ type AutoImportHaiUiPreprocessor = {
16
16
  export function autoImportHaiUi(): AutoImportHaiUiPreprocessor
17
17
 
18
18
  declare global {
19
+ const GuideHost: typeof import('@h-ai/ui')['GuideHost']
20
+ const HelpCenter: typeof import('@h-ai/ui')['HelpCenter']
19
21
  // primitives
20
22
  const Avatar: typeof import('@h-ai/ui')['Avatar']
21
23
  const Badge: typeof import('@h-ai/ui')['Badge']
@@ -56,6 +58,7 @@ declare global {
56
58
  const Empty: typeof import('@h-ai/ui')['Empty']
57
59
  const Form: typeof import('@h-ai/ui')['Form']
58
60
  const FormField: typeof import('@h-ai/ui')['FormField']
61
+ const FormLayout: typeof import('@h-ai/ui')['FormLayout']
59
62
  const InfiniteScroll: typeof import('@h-ai/ui')['InfiniteScroll']
60
63
  const MessageContainer: typeof import('@h-ai/ui')['MessageContainer']
61
64
  const MessageBoxContainer: typeof import('@h-ai/ui')['MessageBoxContainer']
package/auto-import.js CHANGED
@@ -49,6 +49,7 @@ const UI_COMPONENTS = new Set([
49
49
  'Empty',
50
50
  'Form',
51
51
  'FormField',
52
+ 'FormLayout',
52
53
  'InfiniteScroll',
53
54
  'MessageContainer',
54
55
  'MessageBoxContainer',
@@ -77,6 +78,8 @@ const UI_COMPONENTS = new Set([
77
78
  'ThemeColorPicker',
78
79
  'ThemeSelector',
79
80
  'ThemeToggle',
81
+ 'GuideHost',
82
+ 'HelpCenter',
80
83
  // scenes - iam
81
84
  'AuthShell',
82
85
  'AuthPanel',
@@ -34,8 +34,10 @@
34
34
  />
35
35
  -->
36
36
  <script lang='ts'>
37
- import type { DataAttributes, Size } from '../../types.js'
37
+ import type { DataAttributes, FormText, Size } from '../../types.js'
38
38
  import { Combobox } from 'bits-ui'
39
+ import { getFormFieldContext } from '../../internal/form-context.js'
40
+ import { resolveFormText } from '../../internal/form-validation.svelte.js'
39
41
  import { uiM } from '../../messages.js'
40
42
  import { cn, getDataAttributes } from '../../utils.js'
41
43
  import { getFormControlSizeClasses } from '../control-size.js'
@@ -67,7 +69,7 @@
67
69
  /** 是否禁用 */
68
70
  disabled?: boolean
69
71
  /** 错误消息 */
70
- error?: string
72
+ error?: FormText
71
73
  /** 表单标签 */
72
74
  label?: string
73
75
  /** 自定义类名 */
@@ -91,6 +93,11 @@
91
93
  }: Props & DataAttributes = $props()
92
94
 
93
95
  const dataAttributes = $derived(getDataAttributes(restProps))
96
+ const field = getFormFieldContext()
97
+ const uniqueId = $props.id()
98
+ const messageId = `${uniqueId}-message`
99
+ const localError = $derived(resolveFormText(error))
100
+ const effectiveError = $derived(field?.error || localError)
94
101
  const sizeClasses = $derived(getFormControlSizeClasses(size))
95
102
  // 为未提供 value 时设置初始默认值(仅在初始化时执行一次)
96
103
  // 统一以空字符串初始化,避免在初始化阶段捕获 multiple 的初始值。
@@ -101,7 +108,7 @@
101
108
 
102
109
  // ─── 统一视觉样式(与 Select 对齐:rounded-lg / 柔和边框 / focus ring) ───
103
110
  const controlStateClass = $derived(
104
- error
111
+ effectiveError
105
112
  ? 'border-error/60 focus-within:ring-2 focus-within:ring-error/15'
106
113
  : 'border-base-content/15 hover:border-base-content/25 focus-within:border-primary/50 focus-within:ring-2 focus-within:ring-primary/10',
107
114
  )
@@ -200,7 +207,7 @@
200
207
  }
201
208
  </script>
202
209
 
203
- <div {...dataAttributes} class='fieldset w-full {className}'>
210
+ <div {...dataAttributes} class='fieldset w-full {className}' data-form-wide={multiple ? '' : undefined}>
204
211
  {#if fieldLabel}
205
212
  <legend class='fieldset-legend font-medium'>{fieldLabel}</legend>
206
213
  {/if}
@@ -243,6 +250,9 @@
243
250
  {/each}
244
251
 
245
252
  <Combobox.Input
253
+ id={field?.controlId}
254
+ aria-invalid={effectiveError ? true : undefined}
255
+ aria-describedby={[field?.messageId, localError && !field?.error ? messageId : ''].filter(Boolean).join(' ') || undefined}
246
256
  bind:ref={inputRef}
247
257
  class={cn('flex-1 min-w-20 bg-transparent py-0.5 outline-none placeholder:text-base-content/40', sizeClasses.spacing)}
248
258
  placeholder={selectedOptions.length === 0 ? placeholder : ''}
@@ -312,6 +322,9 @@
312
322
  >
313
323
  <div class='relative'>
314
324
  <Combobox.Input
325
+ id={field?.controlId}
326
+ aria-invalid={effectiveError ? true : undefined}
327
+ aria-describedby={[field?.messageId, localError && !field?.error ? messageId : ''].filter(Boolean).join(' ') || undefined}
315
328
  bind:ref={inputRef}
316
329
  class={cn(
317
330
  'w-full rounded-lg border bg-base-100 pr-9 outline-none transition-[border-color,box-shadow] duration-150 placeholder:text-base-content/40',
@@ -372,7 +385,7 @@
372
385
  </Combobox.Root>
373
386
  {/if}
374
387
 
375
- {#if error}
376
- <span class='fieldset-label text-error'>{error}</span>
388
+ {#if localError && !field?.error}
389
+ <p id={messageId} class='mt-1.5 text-xs text-error leading-relaxed break-words' aria-live='polite'>{localError}</p>
377
390
  {/if}
378
391
  </div>
@@ -1,4 +1,4 @@
1
- import type { DataAttributes, Size } from '../../types.js';
1
+ import type { DataAttributes, FormText, Size } from '../../types.js';
2
2
  import { Combobox } from 'bits-ui';
3
3
  /** 选项定义 */
4
4
  interface Option {
@@ -25,7 +25,7 @@ interface Props {
25
25
  /** 是否禁用 */
26
26
  disabled?: boolean;
27
27
  /** 错误消息 */
28
- error?: string;
28
+ error?: FormText;
29
29
  /** 表单标签 */
30
30
  label?: string;
31
31
  /** 自定义类名 */