@bndynet/vue-site 1.5.0 → 1.8.0

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 CHANGED
@@ -38,6 +38,7 @@ You can also create the configuration manually:
38
38
  import { defineConfig } from '@bndynet/vue-site'
39
39
 
40
40
  export default defineConfig({
41
+ name: 'My Project',
41
42
  title: 'My Project',
42
43
  favicon: '/favicon.ico',
43
44
  nav: [
@@ -100,10 +101,11 @@ Add `"dev": "vue-site dev"` (or `vs dev`) in `package.json` scripts if you like.
100
101
 
101
102
  | Property | Description |
102
103
  |----------|-------------|
103
- | `title` | Site title (sidebar + tab). `LocalizedString` |
104
+ | `name` | Brand name shown next to the logo (header + sidebar). `LocalizedString`. Optional — omitted or empty means no brand text is rendered |
105
+ | `title` | Browser tab title. `LocalizedString`. Optional — omitted or empty means the document title is left unchanged |
104
106
  | `nav` | `NavItem[]` |
105
107
  | `navPosition` | Optional navigation placement: `'top'` forces the top/tiered layout, `'sidebar'` forces the full nav tree into the sidebar. Omit to keep automatic behavior based on nav depth |
106
- | `defaultPath` | Path the site opens at; `/` and unknown paths redirect here. Must match a registered route (a `nav` item's resolved path or a `pages` entry's `path`). Defaults to the first top-level `nav` item |
108
+ | `defaultPath` | Path the site opens at; `/` and unknown paths redirect here. Must match a registered route (a `nav` item's resolved path or a `pages` entry's `path`). Defaults to the first registered page in navigation order, then the first visible standalone page |
107
109
  | `favicon` | Browser tab icon URL. Use a public asset path, remote URL, or imported asset URL |
108
110
  | `logo` | Logo URL or imported image |
109
111
  | `theme` | See `ThemeConfig` below; set to `false` to disable theming (hides the switcher, forces a fixed `light` palette, no localStorage persistence) |
@@ -231,6 +233,20 @@ export default defineConfig({
231
233
  })
232
234
  ```
233
235
 
236
+ ### Responsive helpers
237
+
238
+ Wide content — such as an Element Plus `el-table` or a multi-column `el-form` — can overflow on
239
+ small screens. Wrap it in a `.site-responsive-scroll` container to give it a horizontal scrollbar
240
+ (with momentum scrolling on touch and a visible scrollbar hint) instead of overflowing the page:
241
+
242
+ ```vue
243
+ <template>
244
+ <div class="site-responsive-scroll">
245
+ <el-table :data="rows">…</el-table>
246
+ </div>
247
+ </template>
248
+ ```
249
+
234
250
  ### Icon registry
235
251
 
236
252
  The CLI scans configured Lucide icon names in `nav`, `links`, `i18n.locales`, and `theme.extraThemes`
@@ -265,13 +281,84 @@ Built-in themes are `light`, `dark`, plus the always-on extras `sepia` and `ocea
265
281
  | `palettes` | — | Partial overrides for built-in light/dark only |
266
282
  | `extraThemes` | — | Extra themes: `id`, `label`, `icon`, optional `basedOn`, `palette`; reuse a built-in id (`sepia`/`ocean`) to override it. Import `builtinThemePalettes` for full defaults |
267
283
 
284
+ ### Semantic color tokens
285
+
286
+ Every theme is driven by semantic `--color-*` CSS variables. Shared Element Plus variables are
287
+ mapped to them in `element-plus-theme.css`, so common controls and surfaces follow the active theme.
288
+ Some components also have component-specific variables; those may need additional mapping when
289
+ adding new Element Plus components. Built-in palettes define seed colors and derive related values
290
+ with `color-mix()` / `var()`, so `builtinThemePalettes` may contain CSS expressions rather than
291
+ literal colors.
292
+
293
+ | Token | Purpose |
294
+ |-------|---------|
295
+ | `--color-primary` | Brand / primary (`--el-color-primary` and its tints) |
296
+ | `--color-primary-contrast` | Text color on filled primary controls; defaults to the theme background |
297
+ | `--color-primary-hover` | Hover color for filled primary controls; derived from primary and text colors |
298
+ | `--color-success` | Success |
299
+ | `--color-warning` | Warning |
300
+ | `--color-danger` | Danger / error (`--el-color-error` reuses it) |
301
+ | `--color-info` | Info |
302
+ | `--color-surface-raised` | Elevated surfaces: popovers, dropdowns, dialogs, masks |
303
+ | `--color-overlay` | Modal / loading overlay |
304
+
305
+ Define just these base colors in a custom theme; the framework derives Element Plus's
306
+ `light-3/5/7/8/9` and `dark-2` shades with `color-mix()`:
307
+
308
+ ```typescript
309
+ theme: {
310
+ extraThemes: [
311
+ {
312
+ id: 'forest',
313
+ label: 'Forest',
314
+ basedOn: 'light',
315
+ palette: {
316
+ '--color-primary': '#2f855a',
317
+ '--color-surface-raised': '#f7faf8',
318
+ },
319
+ },
320
+ ],
321
+ }
322
+ ```
323
+
324
+ If a custom primary/background pair has low contrast, set `--color-primary-contrast` explicitly.
325
+ It is used for text on filled primary buttons, tags, and selected button groups.
326
+
327
+ ### Overriding any token
328
+
329
+ Every token can be overridden, not just the base colors. Overrides are applied in three levels,
330
+ from lowest to highest precedence:
331
+
332
+ 1. `palettes.light` / `palettes.dark`, or an `extraThemes[].palette` — merged onto the built-in
333
+ palette for that theme.
334
+ 2. `colors` — applied globally after the active palette, so it wins across every theme.
335
+ 3. Runtime `document.documentElement.style.setProperty('--color-…', …)`.
336
+
337
+ Overriding a **seed** color (e.g. `--color-border`) also updates the derived tokens that
338
+ reference it (`--color-sidebar-border`, `--color-code-border`, `--color-table-border`,
339
+ `--color-blockquote-border`). Overriding a **derived** token affects only that token. Prefer
340
+ overriding seeds when the value should adapt across themes.
341
+
342
+ ```typescript
343
+ theme: {
344
+ colors: {
345
+ '--color-theme-switch-hover': 'rgba(0, 0, 0, 0.08)',
346
+ },
347
+ palettes: {
348
+ light: {
349
+ '--color-sidebar-bg': '#f0f2f5',
350
+ },
351
+ },
352
+ }
353
+ ```
354
+
268
355
  ## Shell actions (`shell.actions`)
269
356
 
270
357
  Use `shell.actions` to render app-specific Vue components in the active layout's global toolbar.
271
- In top/tiered navigation they appear at the end of the top header; in sidebar-only navigation they
272
- appear in the sidebar footer after the built-in links, locale switcher, and theme switcher. This is
273
- the recommended place for a signed-in user avatar, notification button, help menu, or tenant
274
- switcher.
358
+ On mobile, they appear at the right side of the mobile header. On desktop, they appear at the end
359
+ of the top header in tiered navigation, or in the sidebar footer after the built-in controls in
360
+ sidebar-only navigation. This is the recommended place for a signed-in user avatar, notification
361
+ button, help menu, or tenant switcher.
275
362
 
276
363
  ```typescript
277
364
  export default defineConfig({
@@ -307,8 +394,8 @@ Set `align` to `'left'`, `'center'`, or `'right'` for sidebar-footer placement;
307
394
  ## Internationalization (`i18n`)
308
395
 
309
396
  Set `i18n` to enable multi-language support. The framework adds a locale switcher to the header,
310
- resolves every `LocalizedString` field (`title`, `nav[].label`, `footer`, `links[].title`) against
311
- the active locale, and exposes the current locale via `useLocale()` / `useLocalize()`.
397
+ resolves every `LocalizedString` field (`name`, `title`, `nav[].label`, `footer`, `links[].title`)
398
+ against the active locale, and exposes the current locale via `useLocale()` / `useLocalize()`.
312
399
 
313
400
  ```typescript
314
401
  import { defineConfig } from '@bndynet/vue-site'
@@ -321,7 +408,8 @@ export default defineConfig({
321
408
  ],
322
409
  defaultLocale: 'en',
323
410
  },
324
- title: { en: 'My Site', zh: '我的站点' }, // a LocalizedString
411
+ name: { en: 'My Site', zh: '我的站点' }, // brand text next to the logo
412
+ title: { en: 'My Site', zh: '我的站点' }, // browser-tab title
325
413
  footer: { en: '© 2026', zh: '© 2026 版权所有' },
326
414
  nav: [
327
415
  { label: { en: 'Home', zh: '首页' }, icon: 'home', page: '../README.md' },
@@ -423,8 +511,8 @@ base file → first entry).
423
511
 
424
512
  | Property | Default | Description |
425
513
  |----------|---------|-------------|
426
- | `locales` | discovered `locales/*.json` | `{ code, label, icon? }[]` — supported languages, in display order. Optional: derived from the auto-loaded file names (with built-in labels) when omitted. First entry is the fallback |
427
- | `defaultLocale` | `locales[0].code` | Initial locale when nothing is stored and detection finds no match |
514
+ | `locales` | discovered `locales/*.json` | `{ code, label, icon? }[]` — supported languages, in display order. Optional in CLI mode; without the CLI, codes are derived from `i18n.messages` keys, or from `defaultLocale` / `en` if omitted. First entry is the fallback |
515
+ | `defaultLocale` | first resolved locale, or `en` | Initial locale when nothing is stored and browser detection finds no match |
428
516
  | `detectBrowser` | `true` | Detect the initial locale from `navigator.language(s)` on first visit |
429
517
  | `storageKey` | `vue-site-locale` | localStorage key for the chosen locale |
430
518
  | `messages` | auto-loaded from `locales/<code>.json` | `Record<LocaleCode, Record<string, string>>` — extra/override translations, merged over the auto-loaded files and built-in UI strings |
@@ -439,7 +527,7 @@ no `messages` field) and you don't even have to list the languages.
439
527
  ```jsonc
440
528
  // locales/en.json — nested groups (recommended), flattened to dotted ids
441
529
  {
442
- "site": { "title": "My Site" },
530
+ "site": { "name": "My Site", "title": "My Site" },
443
531
  "nav": { "home": "Home", "guide": "Guide" }
444
532
  }
445
533
  ```
@@ -447,7 +535,7 @@ no `messages` field) and you don't even have to list the languages.
447
535
  ```jsonc
448
536
  // locales/zh.json
449
537
  {
450
- "site": { "title": "我的站点" },
538
+ "site": { "name": "我的站点", "title": "我的站点" },
451
539
  "nav": { "home": "首页", "guide": "指南" }
452
540
  }
453
541
  ```
@@ -469,6 +557,7 @@ export default defineConfig({
469
557
  ],
470
558
  defaultLocale: 'en',
471
559
  },
560
+ name: tk('site.name'),
472
561
  title: tk('site.title'),
473
562
  nav: [
474
563
  { label: tk('nav.home'), icon: 'home', page: '../README.md' },
@@ -491,9 +580,12 @@ mixed freely — use `tk()` for shared/centrally managed text and an inline map
491
580
  relative to the config directory. `code` is the file name (a `LocaleCode` like `en` or `zh-TW`).
492
581
  - An explicit `i18n.messages` is still supported and **overrides** auto-loaded keys (per id); an
493
582
  explicit `i18n.locales` controls the label/icon/order. Both are optional.
494
- - Auto-discovery is a **CLI** feature. If you embed the library yourself (calling `createSiteApp`
495
- without the `vue-site` CLI), pass `i18n.messages` directly — e.g. build it from JSON with explicit
496
- imports (avoid `import.meta.glob` inside `site.config.ts`, which the CLI pre-loads in Node).
583
+ - Auto-discovery of `locales/<code>.json` is a **CLI** feature. If you embed the library yourself
584
+ (calling `createSiteApp` without the `vue-site` CLI), pass `i18n.messages` directly — e.g. build it
585
+ from JSON with explicit imports (avoid `import.meta.glob` inside `site.config.ts`, which the CLI
586
+ pre-loads in Node). When `i18n.locales` is omitted, the library derives locale codes from the
587
+ `messages` keys and uses those codes as display labels. For localized content without a messages
588
+ catalog, provide `locales` explicitly so the locale switcher has the intended options and labels.
497
589
 
498
590
  ### Localizing in your own pages
499
591
 
@@ -607,7 +699,7 @@ Notes:
607
699
  | Property | Description |
608
700
  |----------|-------------|
609
701
  | `port` | Dev server port |
610
- | `outDir` | Build output (relative to site root; default `{folder}-dist`) |
702
+ | `outDir` | Build output (relative to site root; default `dist`) |
611
703
  | `customElements` | Tag prefixes for custom elements (e.g. `['chat-', 'i-']`) |
612
704
  | `watchPackages` | Local packages — see [env.watchPackages](#envwatchpackages) |
613
705
  | `vite` | Vite overrides (not `root`); framework merges aliases, `server.fs.allow`, `build.outDir`, etc. |
@@ -654,11 +746,11 @@ app.mount('#app')
654
746
 
655
747
  Use a top-level `await` in your entry (or an async IIFE): `createSiteApp` is async and **awaits** `configureApp` when it returns a `Promise`. If you set optional `bootstrap` in config, that module loads before the app is created; if you omit `bootstrap`, that step is skipped.
656
748
 
657
- Exports: `createSiteApp`, `defineConfig`, `useTheme`, `useSiteConfig`, `getSiteConfig`, `useLocale`, `useLocalize`, `tk`, `resolveLocalized`, `resolveField`, `resolveMessage`, `mergeCatalog`, `flattenMessages`, `isMessageRef`, `localizedPage`, `builtinMessages`, `themeRefKey`, `localeRefKey`. Types: `SiteConfig`, `SiteEnvConfig`, `SiteViteConfig`, `SiteCustomConfig`, `SiteExternalLink`, `ShellConfig`, `ShellAction`, `ShellActionLoader`, `NavItem`, `StandalonePage`, `PageLayout`, `AuthRule`, `AuthContext`, `AuthConfig`, `RouterConfig`, `ThemeConfig`, `ThemeOption`, `ThemePaletteVars`, `ResolvedNavItem`, `I18nConfig`, `LocaleOption`, `LocaleCode`, `LocalizedString`, `IconRegistry`, `MessageRef`, `MessageTree`, `MessageCatalog`, `PageLoader`, `LocalizedPageOptions`.
749
+ Exports: `createSiteApp`, `defineConfig`, `useTheme`, `useMobileViewport`, `useSiteConfig`, `getSiteConfig`, `useLocale`, `useLocalize`, `tk`, `resolveLocalized`, `resolveField`, `resolveMessage`, `mergeCatalog`, `flattenMessages`, `isMessageRef`, `localizedPage`, `builtinMessages`, `themeRefKey`, `localeRefKey`. Types: `SiteConfig`, `SiteEnvConfig`, `SiteViteConfig`, `SiteCustomConfig`, `SiteExternalLink`, `ShellConfig`, `ShellAction`, `ShellActionLoader`, `NavItem`, `StandalonePage`, `PageLayout`, `AuthRule`, `AuthContext`, `AuthConfig`, `RouterConfig`, `ThemeConfig`, `ThemeOption`, `ThemePaletteVars`, `ResolvedNavItem`, `I18nConfig`, `LocaleOption`, `LocaleCode`, `LocalizedString`, `IconRegistry`, `MessageRef`, `MessageTree`, `MessageCatalog`, `PageLoader`, `LocalizedPageOptions`.
658
750
 
659
751
  ### Theme in Vue pages (`useTheme`)
660
752
 
661
- Import `useTheme` from `@bndynet/vue-site`. It returns a reactive `theme` ref (the active theme id, e.g. `light`, `dark`, or an `extraThemes[].id`) plus `setTheme` and `toggleTheme`. The root layout also sets `document.documentElement` attribute `data-theme` and applies CSS variables, so you can style with `var(--color-*)` without JavaScript.
753
+ Import `useTheme` from `@bndynet/vue-site`. It returns a reactive `theme` ref (the active theme id, e.g. `light`, `dark`, or an `extraThemes[].id`) plus `setTheme` and `toggleTheme`. The root layout also sets `document.documentElement` attribute `data-theme`, applies CSS variables, and updates `color-scheme` so native browser controls and scrollbars use the active theme's light or dark scheme.
662
754
 
663
755
  The theme ref is **provided** by `createSiteApp()` (same Vue runtime as your app), so `watch(theme, …)` works reliably even when another tool would otherwise load a second copy of `vue` from nested `node_modules`.
664
756
 
@@ -681,6 +773,28 @@ watch(theme, (next, prev) => {
681
773
 
682
774
  `watch` only runs when the theme **changes** after your component is set up (for example after the user clicks the theme control). It does not run on the initial value unless you pass `{ immediate: true }`.
683
775
 
776
+ ### Responsive viewport state (`useMobileViewport`)
777
+
778
+ Import `useMobileViewport` when component behavior needs to react to the same mobile breakpoint as
779
+ the site layout. It returns a read-only ref that is `true` at viewport widths of 840 CSS pixels or
780
+ narrower. The layout provides one shared ref, so components do not need to create their own media
781
+ query listeners. For visual-only changes, prefer CSS media queries.
782
+
783
+ ```vue
784
+ <script setup lang="ts">
785
+ import { useMobileViewport } from '@bndynet/vue-site'
786
+
787
+ const isMobileViewport = useMobileViewport()
788
+ </script>
789
+
790
+ <template>
791
+ <button v-if="isMobileViewport">Open compact menu</button>
792
+ </template>
793
+ ```
794
+
795
+ Call the composable from a Vue component rendered inside the site layout. It throws a `[vue-site]`
796
+ error when used outside that context.
797
+
684
798
  ## Upgrade
685
799
 
686
800
  ```bash
@@ -693,7 +807,7 @@ npm update @bndynet/vue-site
693
807
  git clone https://github.com/bndynet/vue-site.git && cd vue-site
694
808
  npm install
695
809
  npm run dev # watch-build lib + example site
696
- npm run build # `dist/` + `example/example-dist`
810
+ npm run build # `dist/` + `example/dist`
697
811
  ```
698
812
 
699
813
  ### Using a local build in another project
package/README.zh.md CHANGED
@@ -100,7 +100,7 @@ npx vue-site build --base /app/
100
100
  | `title` | 站点标题(侧边栏 + 标签页)。`LocalizedString` |
101
101
  | `nav` | `NavItem[]` |
102
102
  | `navPosition` | 可选的导航位置:`'top'` 强制使用顶部/分层导航,`'sidebar'` 强制将完整导航树放在侧边栏。不传时保持基于导航深度的自动行为 |
103
- | `defaultPath` | 站点打开时的路径;`/` 和未知路径会重定向到此。必须匹配一个已注册的路由(某个 `nav` 条目解析后的路径,或某个 `pages` 条目的 `path`)。默认为第一个顶级 `nav` 条目 |
103
+ | `defaultPath` | 站点打开时的路径;`/` 和未知路径会重定向到此。必须匹配一个已注册的路由(某个 `nav` 条目解析后的路径,或某个 `pages` 条目的 `path`)。默认选择导航顺序中的第一个已注册页面;如果导航中没有页面,则选择第一个可见的独立页面 |
104
104
  | `favicon` | 浏览器标签页图标 URL。可使用 public 资源路径、远程 URL 或导入后的资源 URL |
105
105
  | `logo` | Logo 的 URL 或导入的图片 |
106
106
  | `theme` | 见下方 `ThemeConfig`;设为 `false` 可禁用主题(隐藏切换器、强制使用固定的 `light` 调色板、不进行 localStorage 持久化) |
@@ -222,6 +222,20 @@ export default defineConfig({
222
222
  })
223
223
  ```
224
224
 
225
+ ### 响应式辅助
226
+
227
+ 宽内容——例如 Element Plus 的 `el-table` 或多列 `el-form`——在小屏幕上可能溢出。把它包进一个
228
+ `.site-responsive-scroll` 容器,即可获得横向滚动条(触屏惯性滚动 + 可见的滚动条提示),而不是
229
+ 溢出页面:
230
+
231
+ ```vue
232
+ <template>
233
+ <div class="site-responsive-scroll">
234
+ <el-table :data="rows">…</el-table>
235
+ </div>
236
+ </template>
237
+ ```
238
+
225
239
  ### `ThemeConfig`
226
240
 
227
241
  内置主题为 `light`、`dark`,外加始终可用的额外主题 `sepia` 和 `ocean`(在每个站点的切换器中都会显示)。在 `SiteConfig` 上设置 `theme: false` 可完全禁用主题。
@@ -233,6 +247,73 @@ export default defineConfig({
233
247
  | `palettes` | — | 仅针对内置 light/dark 的部分覆盖 |
234
248
  | `extraThemes` | — | 额外主题:`id`、`label`、`icon`,可选 `basedOn`、`palette`;复用内置 id(`sepia`/`ocean`)即可覆盖它。导入 `builtinThemePalettes` 获取完整默认值 |
235
249
 
250
+ ### 语义化颜色变量
251
+
252
+ 每个主题都由语义化的 `--color-*` CSS 变量驱动。Element Plus 的通用变量在
253
+ `element-plus-theme.css` 中映射到这些变量,因此常见控件和表面会跟随当前主题。部分组件
254
+ 还有专属变量;新增 Element Plus 组件时,可能需要补充对应映射。内置调色板定义种子色,
255
+ 其余值通过 `color-mix()` / `var()` 派生,因此 `builtinThemePalettes` 中可能包含 CSS 表达式,
256
+ 而不只是字面色值。
257
+
258
+ | 变量 | 用途 |
259
+ |------|------|
260
+ | `--color-primary` | 品牌/主色(映射到 `--el-color-primary` 及其色阶) |
261
+ | `--color-primary-contrast` | 主色填充控件上的文字色,默认取当前主题背景色 |
262
+ | `--color-primary-hover` | 主色填充控件的悬停色,由主色和文字色派生 |
263
+ | `--color-success` | 成功 |
264
+ | `--color-warning` | 警告 |
265
+ | `--color-danger` | 危险/错误(`--el-color-error` 复用该值) |
266
+ | `--color-info` | 信息 |
267
+ | `--color-surface-raised` | 浮层表面:下拉、对话框、遮罩背景 |
268
+ | `--color-overlay` | 弹窗 / 加载遮罩 |
269
+
270
+ 自定义主题只需定义这些基础色,框架会用 `color-mix()` 自动派生 Element Plus 的
271
+ `light-3/5/7/8/9` 与 `dark-2` 色阶:
272
+
273
+ ```typescript
274
+ theme: {
275
+ extraThemes: [
276
+ {
277
+ id: 'forest',
278
+ label: 'Forest',
279
+ basedOn: 'light',
280
+ palette: {
281
+ '--color-primary': '#2f855a',
282
+ '--color-surface-raised': '#f7faf8',
283
+ },
284
+ },
285
+ ],
286
+ }
287
+ ```
288
+
289
+ 如果自定义主色与背景的对比度不足,可以显式设置 `--color-primary-contrast`。它用于主色按钮、
290
+ 深色标签和选中按钮组等填充控件上的文字。
291
+
292
+ ### 覆盖任意 token
293
+
294
+ 不只是基础色,**任何 token 都可以覆盖**。覆盖按三级优先级从低到高应用:
295
+
296
+ 1. `palettes.light` / `palettes.dark`,或 `extraThemes[].palette` —— 合并到该主题的内置调色板。
297
+ 2. `colors` —— 在激活调色板之后全局应用,对所有主题生效。
298
+ 3. 运行时直接调用 `document.documentElement.style.setProperty('--color-…', …)`。
299
+
300
+ 覆盖 **种子色**(如 `--color-border`)会同时更新引用它的派生 token(`--color-sidebar-border`、
301
+ `--color-code-border`、`--color-table-border`、`--color-blockquote-border`);覆盖 **派生 token**
302
+ 则只影响它本身。如果希望颜色能随主题自动适配,优先覆盖种子色。
303
+
304
+ ```typescript
305
+ theme: {
306
+ colors: {
307
+ '--color-theme-switch-hover': 'rgba(0, 0, 0, 0.08)',
308
+ },
309
+ palettes: {
310
+ light: {
311
+ '--color-sidebar-bg': '#f0f2f5',
312
+ },
313
+ },
314
+ }
315
+ ```
316
+
236
317
  ## 国际化(`i18n`)
237
318
 
238
319
  设置 `i18n` 以启用多语言支持。框架会在头部添加语言切换器,
@@ -340,8 +421,8 @@ export default defineConfig({
340
421
 
341
422
  | 属性 | 默认值 | 说明 |
342
423
  |----------|---------|-------------|
343
- | `locales` | 自动发现的 `locales/*.json` | `{ code, label, icon? }[]` —— 支持的语言,按显示顺序排列。可选:省略时从自动加载的文件名派生(使用内置标签)。第一个条目为回退项 |
344
- | `defaultLocale` | `locales[0].code` | 当没有存储值且检测未命中时使用的初始语言 |
424
+ | `locales` | 自动发现的 `locales/*.json` | `{ code, label, icon? }[]` —— 支持的语言,按显示顺序排列。CLI 模式下可省略;不使用 CLI 时,省略后从 `i18n.messages` 的键派生,若也没有则使用 `defaultLocale` 或 `en`。第一个条目为回退项 |
425
+ | `defaultLocale` | 第一个解析出的语言,或 `en` | 当没有存储值且检测未命中时使用的初始语言 |
345
426
  | `detectBrowser` | `true` | 首次访问时从 `navigator.language(s)` 检测初始语言 |
346
427
  | `storageKey` | `vue-site-locale` | 用于存储所选语言的 localStorage 键 |
347
428
  | `messages` | 自动从 `locales/<code>.json` 加载 | `Record<LocaleCode, Record<string, string>>` —— 额外/覆盖的翻译,会合并到自动加载的文件和内置 UI 字符串之上 |
@@ -407,9 +488,11 @@ export default defineConfig({
407
488
  `code` 即文件名(一个 `LocaleCode`,如 `en` 或 `zh-TW`)。
408
489
  - 仍支持显式的 `i18n.messages`,并会**覆盖**自动加载的键(按 id);显式的 `i18n.locales`
409
490
  控制 label/icon/顺序。两者均为可选。
410
- - 自动发现是 **CLI** 特性。如果你自行嵌入该库(不使用 `vue-site` CLI 而直接调用 `createSiteApp`),
411
- 请直接传入 `i18n.messages` —— 例如用显式 import 从 JSON 构建(避免在 `site.config.ts` 中使用
412
- `import.meta.glob`,CLI 会在 Node 中预加载它)。
491
+ - 自动发现 `locales/<code>.json` 是 **CLI** 特性。如果你自行嵌入该库(不使用 `vue-site` CLI 而直接调用
492
+ `createSiteApp`),请直接传入 `i18n.messages` —— 例如用显式 import 从 JSON 构建(避免在
493
+ `site.config.ts` 中使用 `import.meta.glob`,CLI 会在 Node 中预加载它)。省略 `i18n.locales` 时,库会
494
+ 从 `messages` 的键派生语言代码,并直接用代码作为显示名称。若本地化内容没有消息目录,请显式提供
495
+ `locales`,以设置语言切换器的选项和名称。
413
496
 
414
497
  ### 在你自己的页面中本地化
415
498
 
@@ -590,14 +673,15 @@ app.mount('#app')
590
673
  返回 `Promise` 时会 **await** 它。如果你在配置中设置了可选的 `bootstrap`,该模块会在应用创建前加载;
591
674
  如果省略 `bootstrap`,则跳过该步骤。
592
675
 
593
- 导出:`createSiteApp`、`defineConfig`、`useTheme`、`useSiteConfig`、`getSiteConfig`、`useLocale`、`useLocalize`、`tk`、`resolveLocalized`、`resolveField`、`resolveMessage`、`mergeCatalog`、`flattenMessages`、`isMessageRef`、`localizedPage`、`builtinMessages`、`themeRefKey`、`localeRefKey`。类型:`SiteConfig`、`SiteEnvConfig`、`SiteViteConfig`、`SiteCustomConfig`、`SiteExternalLink`、`NavItem`、`StandalonePage`、`PageLayout`、`AuthRule`、`AuthContext`、`AuthConfig`、`RouterConfig`、`ThemeConfig`、`ThemeOption`、`ThemePaletteVars`、`ResolvedNavItem`、`I18nConfig`、`LocaleOption`、`LocaleCode`、`LocalizedString`、`MessageRef`、`MessageTree`、`MessageCatalog`、`PageLoader`、`LocalizedPageOptions`。
676
+ 导出:`createSiteApp`、`defineConfig`、`useTheme`、`useMobileViewport`、`useSiteConfig`、`getSiteConfig`、`useLocale`、`useLocalize`、`tk`、`resolveLocalized`、`resolveField`、`resolveMessage`、`mergeCatalog`、`flattenMessages`、`isMessageRef`、`localizedPage`、`builtinMessages`、`themeRefKey`、`localeRefKey`。类型:`SiteConfig`、`SiteEnvConfig`、`SiteViteConfig`、`SiteCustomConfig`、`SiteExternalLink`、`NavItem`、`StandalonePage`、`PageLayout`、`AuthRule`、`AuthContext`、`AuthConfig`、`RouterConfig`、`ThemeConfig`、`ThemeOption`、`ThemePaletteVars`、`ResolvedNavItem`、`I18nConfig`、`LocaleOption`、`LocaleCode`、`LocalizedString`、`MessageRef`、`MessageTree`、`MessageCatalog`、`PageLoader`、`LocalizedPageOptions`。
594
677
 
595
678
  ### 在 Vue 页面中使用主题(`useTheme`)
596
679
 
597
680
  从 `@bndynet/vue-site` 导入 `useTheme`。它返回一个响应式的 `theme` ref(当前激活的主题 id,
598
681
  如 `light`、`dark` 或某个 `extraThemes[].id`),外加 `setTheme` 和 `toggleTheme`。
599
682
  根布局还会在 `document.documentElement` 上设置 `data-theme` 属性并应用 CSS 变量,
600
- 因此你可以用 `var(--color-*)` 来设置样式,无需 JavaScript。
683
+ 因此你可以用 `var(--color-*)` 来设置样式,无需 JavaScript。它还会更新 `color-scheme`,
684
+ 让浏览器原生控件和滚动条跟随当前主题的亮色或暗色模式。
601
685
 
602
686
  theme ref 由 `createSiteApp()` **提供**(与你的应用同一个 Vue 运行时),所以 `watch(theme, …)`
603
687
  能可靠工作,即使其他工具可能会从嵌套的 `node_modules` 加载第二份 `vue`。
@@ -622,6 +706,26 @@ watch(theme, (next, prev) => {
622
706
  `watch` 仅在你的组件初始化之后主题**发生变化**时运行(例如用户点击主题控件之后)。
623
707
  除非你传入 `{ immediate: true }`,否则它不会在初始值上运行。
624
708
 
709
+ ### 响应式视口状态(`useMobileViewport`)
710
+
711
+ 当组件行为需要和站点布局使用相同的移动端断点时,导入 `useMobileViewport`。它返回一个只读 ref;
712
+ 视口宽度不超过 840 CSS 像素时值为 `true`。站点布局提供的是共享 ref,因此组件无需各自创建媒体查询监听器。
713
+ 如果只是调整视觉样式,优先使用 CSS 媒体查询。
714
+
715
+ ```vue
716
+ <script setup lang="ts">
717
+ import { useMobileViewport } from '@bndynet/vue-site'
718
+
719
+ const isMobileViewport = useMobileViewport()
720
+ </script>
721
+
722
+ <template>
723
+ <button v-if="isMobileViewport">打开紧凑菜单</button>
724
+ </template>
725
+ ```
726
+
727
+ 请在站点布局内部渲染的 Vue 组件中调用此 composable。在该上下文之外调用时,会抛出 `[vue-site]` 错误。
728
+
625
729
  ## 升级
626
730
 
627
731
  ```bash
package/bin/vue-site.mjs CHANGED
@@ -129,6 +129,7 @@ const configCandidates = [
129
129
  const initSiteConfig = `import { defineConfig } from '@bndynet/vue-site'
130
130
 
131
131
  export default defineConfig({
132
+ name: 'My Site',
132
133
  title: 'My Site',
133
134
  // Public client configuration. Never place secrets here.
134
135
  custom: {
@@ -227,6 +228,7 @@ function resolveSiteConfig(cliConfig) {
227
228
  'Run `npx vue-site init` to create starter files, or create site.config.ts manually:\n\n' +
228
229
  ' import type { SiteConfig } from \'@bndynet/vue-site\'\n\n' +
229
230
  ' export default {\n' +
231
+ ' name: \'My Site\',\n' +
230
232
  ' title: \'My Site\',\n' +
231
233
  ' nav: [\n' +
232
234
  ' { label: \'Home\', icon: \'home\', page: () => import(\'./README.md?raw\') },\n' +
@@ -443,7 +445,7 @@ function buildHtmlShell(scriptTag, siteConfig) {
443
445
  <html lang="en">
444
446
  <head>
445
447
  <meta charset="UTF-8" />
446
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
448
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
447
449
  ${buildFaviconLink(siteConfig)} <title></title>
448
450
  </head>
449
451
  <body>
@@ -1057,7 +1059,7 @@ async function buildViteConfig(options = {}) {
1057
1059
  },
1058
1060
  },
1059
1061
  build: {
1060
- outDir: resolve(cwd, outDir || `${basename(cwd)}-dist`),
1062
+ outDir: resolve(cwd, outDir || 'dist'),
1061
1063
  emptyOutDir: true,
1062
1064
  },
1063
1065
  ...(cliBase != null && { base: cliBase }),
@@ -1,2 +1,6 @@
1
- declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, HTMLDivElement>;
1
+ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {
2
+ menuButton: HTMLButtonElement;
3
+ mobileDrawer: HTMLElement;
4
+ drawerCloseButton: HTMLButtonElement;
5
+ }, HTMLDivElement>;
2
6
  export default _default;
@@ -6,5 +6,6 @@ declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {
6
6
  }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {
7
7
  detailsRef: HTMLDetailsElement;
8
8
  summaryRef: HTMLElement;
9
+ menuRef: HTMLDivElement;
9
10
  }, HTMLDetailsElement>;
10
11
  export default _default;
@@ -1,6 +1,14 @@
1
1
  import { ResolvedNavItem } from '../types';
2
2
  type __VLS_Props = {
3
3
  item: ResolvedNavItem;
4
+ collapsed?: boolean;
5
+ hoverExpand?: boolean;
4
6
  };
5
- declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, HTMLDivElement>;
7
+ declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
8
+ collapsed: boolean;
9
+ hoverExpand: boolean;
10
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {
11
+ trigger: HTMLButtonElement;
12
+ popup: HTMLDivElement;
13
+ }, HTMLDivElement>;
6
14
  export default _default;
@@ -2,8 +2,10 @@ import { ResolvedNavItem } from '../types';
2
2
  type __VLS_Props = {
3
3
  item: ResolvedNavItem;
4
4
  indent?: boolean;
5
+ collapsed?: boolean;
5
6
  };
6
7
  declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
7
8
  indent: boolean;
9
+ collapsed: boolean;
8
10
  }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
9
11
  export default _default;
@@ -1,2 +1,13 @@
1
- declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, HTMLElement>;
1
+ import { ResolvedNavItem } from '../types';
2
+ type __VLS_Props = {
3
+ /** Optional navigation tree override, used by the mobile drawer. */
4
+ items?: ResolvedNavItem[];
5
+ /** Whether desktop navigation is shown as an icon rail. */
6
+ collapsed?: boolean;
7
+ };
8
+ declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
9
+ "toggle-collapse": () => any;
10
+ }, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
11
+ "onToggle-collapse"?: (() => any) | undefined;
12
+ }>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, HTMLElement>;
2
13
  export default _default;
@@ -6,5 +6,6 @@ declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {
6
6
  }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {
7
7
  detailsRef: HTMLDetailsElement;
8
8
  summaryRef: HTMLElement;
9
+ menuRef: HTMLDivElement;
9
10
  }, HTMLDetailsElement>;
10
11
  export default _default;
@@ -0,0 +1,36 @@
1
+ import { Ref } from 'vue';
2
+ /**
3
+ * Breakpoint in CSS pixels below which the site switches to the mobile layout.
4
+ *
5
+ * CSS `@media (max-width: ...)` rules cannot read this value (media queries do not
6
+ * support `var()`), so any change here must also update the matching media queries in
7
+ * `src/styles/layout.css`, `src/styles/markdown.css`, `src/components/SideNav.vue`,
8
+ * and `src/components/ShellActions.vue`. Those files carry a `keep in sync` comment.
9
+ */
10
+ export declare const MOBILE_BREAKPOINT_PX = 840;
11
+ /** Provide the active viewport state to components rendered by the site layout. */
12
+ export declare function provideMobileViewport(isMobileViewport: Ref<boolean>): void;
13
+ /**
14
+ * Return the site's reactive mobile viewport state.
15
+ *
16
+ * The value is `true` when the viewport is {@link MOBILE_BREAKPOINT_PX} CSS pixels wide or
17
+ * narrower. It is shared with the responsive site layout and updates when the viewport crosses
18
+ * that breakpoint.
19
+ *
20
+ * This composable must be called from a Vue component rendered inside `createSiteApp()`.
21
+ * The returned ref is read-only; use CSS media queries for presentation-only changes.
22
+ *
23
+ * @example
24
+ * ```vue
25
+ * <script setup lang="ts">
26
+ * import { useMobileViewport } from '@bndynet/vue-site'
27
+ *
28
+ * const isMobileViewport = useMobileViewport()
29
+ * </script>
30
+ *
31
+ * <template>
32
+ * <button v-if="isMobileViewport">Open compact menu</button>
33
+ * </template>
34
+ * ```
35
+ */
36
+ export declare function useMobileViewport(): Readonly<Ref<boolean>>;
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@ export { createSiteApp } from './create-app';
3
3
  export { useTheme, themeRefKey } from './composables/useTheme';
4
4
  export { useLocale, localeRefKey } from './composables/useLocale';
5
5
  export { useLocalize } from './composables/useLocalize';
6
+ export { useMobileViewport } from './composables/useMobileViewport';
6
7
  export { resolveLocalized, resolveField, resolveMessage, mergeCatalog, flattenMessages, tk, isMessageRef, localizedPage } from './i18n-utils';
7
8
  export type { LocalizedPageOptions, MessageCatalog } from './i18n-utils';
8
9
  export { builtinMessages } from './i18n-messages';