@synopackageland/ui 0.20.19 → 0.21.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.
Files changed (30) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/components/app/app-layout/AppLayout.vue.d.ts +20 -0
  3. package/dist/components/app/app-layout/PhoneFab.vue.d.ts +18 -0
  4. package/dist/components/app/app-layout/hideOnScroll.d.ts +37 -0
  5. package/dist/components/app/app-layout/iconActionTitles.d.ts +9 -0
  6. package/dist/components/app/app-layout/pageTitle.d.ts +76 -6
  7. package/dist/components/app/app-toolbar/AppToolbar.vue.d.ts +8 -7
  8. package/dist/components/app/aside-panel/AsidePanel.vue.d.ts +1 -1
  9. package/dist/components/app/page-content/PageContent.vue.d.ts +6 -0
  10. package/dist/components/app/page-fab/PageFab.vue.d.ts +12 -0
  11. package/dist/components/app/page-header/PageHeader.vue.d.ts +17 -4
  12. package/dist/components/app/section-tabs/SectionTabs.vue.d.ts +4 -3
  13. package/dist/components/app/settings-page/SettingsPage.vue.d.ts +16 -1
  14. package/dist/components/primitives/motion/PushTransition.vue.d.ts +4 -0
  15. package/dist/grid.js +9 -0
  16. package/dist/i18n/text.d.ts +1 -0
  17. package/dist/index.d.ts +2 -0
  18. package/dist/index.js +13417 -12959
  19. package/dist/json-preview/preview.js +11100 -10639
  20. package/dist/lib/motion.d.ts +1 -1
  21. package/dist/reka-runtime.json +1 -1
  22. package/dist/theme.css +1 -1
  23. package/dist/themes/dsm7-embedded-theme.css +1 -1
  24. package/dist/themes/dsm7-embedded.css +1 -1
  25. package/dist/themes/dsm7.css +1 -1
  26. package/docs/adaptive-layout.md +10 -0
  27. package/docs/button-policy.md +3 -1
  28. package/docs/mobile-native.md +118 -5
  29. package/docs/release-checks.md +1 -1
  30. package/package.json +92 -1
@@ -146,6 +146,16 @@ this in Chromium at 390 and 768 with touch, with Android emulated by a
146
146
  viewport resize and iOS by a stand-in visual viewport. A real-device pass on
147
147
  iOS Safari and Android Chrome is still needed (spec D6).
148
148
 
149
+ ## Phone: one header row, one scroller
150
+
151
+ On a phone (`AppLayout` 640px or narrower, any height) the layout's bar is
152
+ the page's one header row: the page title (or a sub-page's Back and title,
153
+ `PageHeader` `back`, a pushed settings section) and the page's own actions
154
+ in it. A page with a `SettingsPage` or a `SectionTabs fill` scrolls as one
155
+ under the bar (`data-ui-layout-scroll="page"`), so no settings body becomes
156
+ a short scroller with a hidden scrollbar. Details in docs/mobile-native.md,
157
+ "Phone shell".
158
+
149
159
  ## Short screens
150
160
 
151
161
  A screen at most 480px tall (a phone in landscape: 753×279, 667×375,
@@ -47,7 +47,9 @@ are in [dsm-native-metrics.md](dsm-native-metrics.md).
47
47
  5. Inside a page, use `primary` only for the one step that unblocks the page,
48
48
  such as Sign in in a callout; at most one per section.
49
49
  6. Keep the default size. `sm` and `lg` exist for dense rows and touch layouts;
50
- neighbouring buttons share one size.
50
+ neighbouring buttons share one size. On a phone every size is drawn one
51
+ step smaller than the field beside it (md 36, sm 28, lg 44px;
52
+ `--ui-button-*`), with the same 44px tap box; an IconButton keeps its size.
51
53
  7. A disabled button keeps its shape and place. Say why next to it.
52
54
  8. An icon-only action is an `IconButton` with a `label`; it shows as its
53
55
  tooltip and names the button for screen readers.
@@ -64,6 +64,7 @@ control and grid sizes; it does not change the 16px field rule.
64
64
  | `--ui-font-size-field` (new) | 16 | 16 | 16 | every field on a coarse pointer, to stop iOS focus zoom |
65
65
  | `--ui-control-md` | 28 | **44** | **44** | fields, selects, form and sheet buttons |
66
66
  | `--ui-control-sm` | 24 | **32** | **36** | inline and toolbar buttons (the tap box comes from `--ui-hit-min`) |
67
+ | `--ui-button-md` / `-sm` / `-lg` (new) | `--ui-control-*` | `--ui-control-*` | **36 / 28 / 44** | a Button and a SplitButton, one size smaller than the field beside it; the tap box stays 44. An IconButton keeps `--ui-control-*` |
67
68
  | `--ui-hit-min` (new) | 0 | **44** | **44** | smallest tap box |
68
69
  | `--ui-tap-gap` (new) | 0 | **8** | **8** | room between tap targets; `--ui-icon-button-gap` reads it on touch |
69
70
  | `--ui-grid-row-height` / `-header-height` | 28 | **36** | **32** | canvas grid rows (docs/ux-grid.md) |
@@ -391,7 +392,9 @@ view. `PushTransition` draws one pushed page:
391
392
  over `--ui-motion-pop` (240ms), both on `--ui-motion-sheet-ease`. The page
392
393
  under it (`under`) moves 30% left, dims 10% and is `inert` while covered.
393
394
  - Its 48px back header (with `title` or the `title` slot) has Back at the
394
- start, the title in the middle and a `header-actions` slot at the end.
395
+ start, the title in the middle and a `header-actions` slot at the end. In
396
+ a phone `AppLayout` with a `title` it draws no header of its own: the
397
+ layout's bar shows Back, the title and `header-actions` ("Phone shell").
395
398
  - Back, the browser's Back (Android back, the iOS edge swipe, through
396
399
  `useBackDismiss`) and a swipe that starts within 20px of the page's left
397
400
  edge pop it (`pop`). A swipe let go past 35% of the width or faster than
@@ -411,7 +414,7 @@ Where it is used:
411
414
  |---|---|---|
412
415
  | `ListDetail` | the list fills the room; the detail is pushed over it, with Back in place of Close | the split view, unchanged |
413
416
  | `SectionTabs`, five or more sections or `fill` (a settings page) | a grouped list (`group` on each item sets the blocks, 48px rows with a chevron) that pushes the tapped section; `v-model:pushed` | the strip |
414
- | `SectionTabs`, four or fewer sections | a full-width SegmentControl with equal segments | the strip |
417
+ | `SectionTabs`, four or fewer sections | the scrolling underline strip on a touch phone (0.21.0); a full-width SegmentControl with equal segments in a narrow window used with a mouse, or with `phone="segment"` | the strip |
415
418
  | `SectionTabs` with `phone="strip"` | the scrolling strip, for tabs inside a pushed page or a sheet | the strip |
416
419
 
417
420
  `phone` (`auto`, `list`, `segment`, `strip`) overrides the choice. A phone
@@ -460,12 +463,122 @@ driven by a mouse):
460
463
  keeps its heading for screen readers only, and takes no room when it has
461
464
  nothing else to show (no description, meta or actions). There is no large
462
465
  collapsing title.
463
- - **Actions:** `headerActions` (DropdownMenu entries). The first plain entry
464
- is one 44px button, its icon (the label is its accessible name) or else its
465
- label; the rest open from "…". A pick emits `headerAction` with the key.
466
+ - **Actions:** first the page's own actions, then `headerActions`
467
+ (DropdownMenu entries). The first plain `headerActions` entry is one 44px
468
+ button, its icon (the label is its accessible name) or else its label; the
469
+ rest open from "…". A pick emits `headerAction` with the key.
466
470
 
467
471
  Every bar button is a 44px target.
468
472
 
473
+ **One header row.** The bar is the page's only header row on a phone:
474
+
475
+ - **Page actions join the title.** The `#actions` of the `PageHeader` whose
476
+ title is in the bar, the `#header-actions` of a pushed page and the
477
+ `#actions` of an `AppToolbar` in the layout's `toolbar` slot render in the
478
+ bar (`[data-ui-mobile-page-actions]`), after the title, instead of a row of
479
+ their own. A Button with an icon is drawn as its icon, 44px, with its label
480
+ as its accessible name and its tooltip; a Button with text only stays text
481
+ (the primary colour); a primary or danger Button keeps its fill, one
482
+ size smaller. Keep one primary per area. An `AppToolbar` whose only content
483
+ was its actions then takes no row. Actions that do not fit beside the title
484
+ (the title would be under 80px, or the row would overflow) stay in the
485
+ page's own header row; `headerActions` "…" is the overflow for more.
486
+ - **A detail page is one row.** `PageHeader` `back` (and `@back`) makes the
487
+ page a sub-page: on a phone the bar leads with Back, shows this page's
488
+ title, whatever AppLayout's `title` says, and holds its actions. Back calls
489
+ `@back`; the App pops its own route. Wider, the header leads its own title
490
+ with a Back button. Use it instead of a hand-drawn Back row above the page.
491
+ - **A pushed section is one row.** A `PushTransition` with a `title` inside
492
+ the layout (a settings section pushed from a `SectionTabs` list) takes the
493
+ bar over the same way while it is shown: Back pops it, and its own 48px
494
+ back header is not drawn. The list under it keeps the page's title.
495
+
496
+ **The page scrolls as one.** On a phone, a page holding a `SettingsPage` or
497
+ a `SectionTabs fill` scrolls in the area under the bar, at any height: the
498
+ settings body takes its content's height instead of a scroller of its own,
499
+ and every element between them (an App shell's fill wrappers too) takes its
500
+ content's height. The bar stays, a sticky `PageHeader` and section strip
501
+ stick under it, and Reset / Apply stick at the bottom (a short page keeps
502
+ them at the bottom of the screen). The root carries
503
+ `data-ui-layout-scroll="page"`. A DataGrid or another page that scrolls
504
+ itself keeps its layout.
505
+
506
+ ### Native phone shell (0.21.0)
507
+
508
+ A *touch phone* is an `AppLayout` 640px or narrower under a coarse pointer
509
+ (the root carries `data-ui-phone`). A narrow DSM window driven by a mouse is
510
+ the phone layout without these additions, and wider layouts are unchanged.
511
+
512
+ **Bar.** One 48px row: Back on a sub-page, else the menu button, else the
513
+ App's icon (`brand-icon`) on a root page; the title, start-aligned beside
514
+ anything else; at most two trailing icons, the rest in "…". There is no brand
515
+ row. Tabs for the page (`SectionTabs` with four or fewer sections) are the
516
+ scrolling underline strip right under the bar; `phone="segment"` keeps the
517
+ SegmentControl. `AppTabBar` labels are 12px under 24px icons.
518
+
519
+ **Page actions on a FAB.** By default (`AppLayout` `phone-actions="fab"`)
520
+ the page's actions (PageHeader `#actions`, a pushed page's
521
+ `#header-actions`, AppToolbar `#actions`) render in a floating action button
522
+ at the bottom right, 16px from the edges and above the tab bar. No App
523
+ change: the same slot moves.
524
+
525
+ - One action: the FAB is that action (56px, its icon only, its label as the
526
+ accessible name). A tap runs it. It is the area's one primary: a danger
527
+ action stays red.
528
+ - Two or more: the FAB (`+`, "Page actions") opens a speed dial: the actions
529
+ as labelled 48px pills over a scrim, staggered in. Focus moves to the first
530
+ and stays in the dial; Escape, Back, a press on the scrim or the FAB close
531
+ it and focus returns to the FAB. A pick runs the action and closes the dial;
532
+ an action that opens a menu closes it once the menu closes. The page behind
533
+ is inert while it is open. Reduced motion shows and hides it at once.
534
+ - Rare or destructive items belong in `headerActions` ("…" in the bar), not
535
+ in the page actions.
536
+ - `phone-actions` on `AppLayout`, and on a `PageHeader`, `AppToolbar` or
537
+ `PushTransition` for one page: `fab` (default), `bar` (icons in the bar, as
538
+ in 0.20.20) or `inline` (the page's own header row).
539
+ - `PageFab` picks the FAB explicitly: `<PageFab><Button variant="primary">…</Button></PageFab>`
540
+ inside the page's actions. On a touch phone its content is the FAB and the
541
+ page's other actions go to the bar; elsewhere it renders in place.
542
+ - A page whose primary action is in a footer bar (a `SettingsPage` footer, a
543
+ sticky `FormActions`) keeps its actions in the bar: the bottom already has a
544
+ primary. Content gets `--ui-fab-room` (88px) of bottom padding so the last
545
+ row is never under the FAB. Toasts are not moved above the FAB.
546
+
547
+ **Hide on scroll.** On by default (`AppLayout` `hide-on-scroll`). Scrolling
548
+ the page down 16px or more slides the bar, the tab bar, the FAB and a clean
549
+ settings footer out (`data-ui-bars-hidden`, `all` when the page scrolls under
550
+ the bar, `bottom` when the document scrolls); any scroll up of 4px, the top of
551
+ the page and a scroll that stops at its end bring them back. Safe areas are
552
+ kept, and they move in `--ui-motion-bars` (200ms; 0 with reduced motion). They
553
+ never hide while the speed dial, the drawer, a sheet or a dialog is open or a
554
+ text field has focus, and focus moving into a bar shows them. A page shorter
555
+ than the bars' room never hides them.
556
+
557
+ - Opt out for the App: `<AppLayout :hide-on-scroll="false">`; for one page:
558
+ `<PageContent :hide-on-scroll="false">`. Both keep the settings footer too.
559
+ - **Settings footer.** A `SettingsPage` footer hides with the bars only while
560
+ the form is clean: never while it is dirty, invalid or saving, so Apply is
561
+ in reach after every edit. Set `dirty`, `invalid` and `saving` on the
562
+ SettingsPage, or let it read them: clean while the footer's primary button
563
+ (every button, without a primary) is disabled, invalid while a field has
564
+ `aria-invalid="true"`, saving while a footer button is loading. A clean
565
+ footer stays at rest: it slides away only with the bars, so Reset / Apply
566
+ never appear or vanish under the finger while the user edits.
567
+
568
+ **Footer hints.** In any footer bar (`data-ui-footer-bar`: SettingsPage,
569
+ Dialog, Sheet, BottomSheet, Card, Panel, Wizard, AppActionBar) a hint or
570
+ status beside the buttons ("Enter a Custom ASR API key") takes a full-width
571
+ line of its own above them on a touch phone, wrapping if it must. A hint is an
572
+ element with text and no control in it; wrap bare text in a `<span>`.
573
+
574
+ **Inside a host shell.** The Package Land mobile shell draws no chrome around
575
+ a registered phone App: the App's bar is the only top bar and its tab bar the
576
+ only bottom bar, edge to edge. A host that draws its own bars sets
577
+ `--ui-host-inset-top/-right/-bottom/-left` on the App's root (or `:root`);
578
+ they replace `env(safe-area-inset-*)` in `--ui-safe-*`, so the bar, the tab
579
+ bar and the FAB clear the host's chrome instead of stacking under it. Android
580
+ Back closes the speed dial first, then pops the App's history.
581
+
469
582
  The navigation drawer is Reka's `Drawer` with `swipe-direction="left"`,
470
583
  portalled like `BottomSheet` (into the AppRoot in app scope). It is 85% of
471
584
  the screen, up to 320px, and mounted only while open. A drag left follows the
@@ -8,7 +8,7 @@ checks through the optional build-isolation wrapper.
8
8
 
9
9
  | Entry point | Default | Explicit full |
10
10
  | --- | --- | --- |
11
- | `npm run gates`, `scripts/gates.mjs` | Internal-content checks, build, lint, typecheck, unit tests, notices, isolated pack and tarball content audit | Adds Chromium installation, responsive/adaptive/date-picker/JSON/host/switch/layout/InfoTip/motion/focus-restore browser checks, Histoire build, exact-tarball registry consumer smoke |
11
+ | `npm run gates`, `scripts/gates.mjs` | Internal-content checks, build, lint, typecheck, unit tests, notices, isolated pack and tarball content audit | Adds Chromium installation, responsive/adaptive/date-picker/JSON/host/switch/layout/InfoTip/motion/short-viewport/phone-layout/focus-restore browser checks, Histoire build, exact-tarball registry consumer smoke |
12
12
  | `pnpm check`, `check:fast` | Basic, with optional Docker host build isolation | `SPL_FULL_GATE=1` selects the same full plan |
13
13
  | `fast-ci.yml`, composite verify action | Basic | No automatic extended checks |
14
14
  | Disabled CI/release workflow templates | Basic, version/tag/ancestry checks, stage, pack and actual archive audit | No implicit browser or registry consumer smoke |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synopackageland/ui",
3
- "version": "0.20.19",
3
+ "version": "0.21.0",
4
4
  "description": "Shared Vue 3 UI component library with a DSM 7 inspired application theme",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -60,6 +60,62 @@
60
60
  "./themes/dsm7-embedded-reset.css": "./dist/themes/dsm7-embedded-reset.css",
61
61
  "./themes/dsm7-embedded-theme.css": "./dist/themes/dsm7-embedded-theme.css"
62
62
  },
63
+ "scripts": {
64
+ "check:docs": "node --test scripts/check-doc-versions.test.mjs && node scripts/check-doc-versions.mjs",
65
+ "dev": "vite build --watch",
66
+ "build": "vite build && vite build --mode json && vite build --mode rich-text && vite build --mode grid && vite build --mode cli && vite build --mode preview && vite build --mode embedded && node scripts/postbuild.mjs",
67
+ "lint": "eslint .",
68
+ "typecheck": "vue-tsc --noEmit -p tsconfig.json",
69
+ "test": "vitest run",
70
+ "test:watch": "vitest",
71
+ "test:changed": "echo \"test:changed skips the tests the changes since ${SYNOPKGLAND_GATE_BASE:-origin/master} do not reach (vitest --changed); pnpm check runs them all\" && vitest run --changed ${SYNOPKGLAND_GATE_BASE:-origin/master}",
72
+ "story": "histoire dev",
73
+ "story:build": "histoire build",
74
+ "check:host": "node scripts/dsm-host-shots.mjs",
75
+ "check:switch": "node scripts/check-switch.mjs",
76
+ "check:tabs": "node scripts/check-tabs.mjs",
77
+ "check:mobile": "node scripts/check-mobile-overflow.mjs",
78
+ "check:ui-patch": "node scripts/check-ui-patch.mjs",
79
+ "check:narrow": "node scripts/check-narrow-controls.mjs",
80
+ "check:drawer-form": "node scripts/check-drawer-form.mjs",
81
+ "check:responsive": "node scripts/check-responsive.mjs",
82
+ "check:adaptive": "node scripts/check-adaptive.mjs",
83
+ "check:sidebar-focus": "node scripts/check-sidebar-focus.mjs",
84
+ "check:json-render": "node scripts/check-json-render.mjs",
85
+ "check:motion": "node scripts/check-motion.mjs",
86
+ "check:topology": "node scripts/check-topology.mjs",
87
+ "check:wizard": "node scripts/check-wizard.mjs",
88
+ "check:edges": "node scripts/check-edges.mjs",
89
+ "check:data-grid": "node scripts/check-data-grid.mjs",
90
+ "check:truncation": "node scripts/check-truncation.mjs",
91
+ "check:link": "node scripts/check-link.mjs",
92
+ "check:text-select": "node scripts/check-text-select.mjs",
93
+ "check:date-picker": "node scripts/check-date-picker.mjs",
94
+ "check:full-page": "node scripts/check-full-page.mjs",
95
+ "check:short-viewport": "node scripts/check-short-viewport.mjs",
96
+ "check:phone-layout": "node scripts/check-phone-layout.mjs",
97
+ "check:focus-restore": "node scripts/check-focus-restore.mjs",
98
+ "check:detail-surface": "node scripts/check-detail-surface.mjs",
99
+ "check:internal": "node --test scripts/check-internal-content.test.mjs scripts/check-npm-metadata.test.mjs && node scripts/check-internal-content.mjs --repo .",
100
+ "check:npm-metadata": "node scripts/check-npm-metadata.mjs",
101
+ "check:package": "node scripts/check-package-content.mjs",
102
+ "check:public-api-consumer": "node scripts/check-public-api-consumer.mjs",
103
+ "notices:update": "node scripts/third-party-notices.mjs --write",
104
+ "notices:check": "node scripts/third-party-notices.mjs --check",
105
+ "check": "bash scripts/docker-check.sh \"npm run gates\"",
106
+ "check:fast": "bash scripts/docker-check.sh \"npm run gates\"",
107
+ "check:local-browser": "node scripts/app-local-browser.mjs",
108
+ "check:infotip": "node scripts/check-infotip.mjs",
109
+ "release:stage": "node scripts/stage-release.mjs",
110
+ "check:polish": "bash scripts/docker-check.sh --build --out .polish-shots 'node scripts/check-polish.mjs'",
111
+ "check:acceptance": "node scripts/check-acceptance.mjs",
112
+ "check:palette": "node scripts/check-palette.mjs",
113
+ "gates": "node scripts/gates.mjs",
114
+ "gates:full": "node scripts/gates.mjs --full",
115
+ "check:layout": "node scripts/check-layout.mjs",
116
+ "release:smoke": "node scripts/release-smoke.mjs",
117
+ "check:browser-install": "playwright install chromium"
118
+ },
63
119
  "peerDependencies": {
64
120
  "vue": "^3.5.0"
65
121
  },
@@ -79,9 +135,44 @@
79
135
  "@tiptap/vue-3": "3.32.1",
80
136
  "zod": "^4.6.5"
81
137
  },
138
+ "devDependencies": {
139
+ "@eslint/js": "10.0.1",
140
+ "@histoire/plugin-vue": "1.0.0-beta.1",
141
+ "@tailwindcss/vite": "4.3.3",
142
+ "@tanstack/vue-virtual": "3.13.39",
143
+ "@vitejs/plugin-vue": "6.0.9",
144
+ "@vue/test-utils": "2.5.1",
145
+ "@vueuse/core": "14.4.0",
146
+ "eslint": "10.11.0",
147
+ "eslint-plugin-vue": "10.11.1",
148
+ "flexsearch": "0.8.212",
149
+ "globals": "17.12.0",
150
+ "histoire": "1.0.0-beta.1",
151
+ "jsdom": "30.1.1",
152
+ "playwright": "1.58.2",
153
+ "reka-ui": "2.11.0",
154
+ "tailwindcss": "4.3.3",
155
+ "typescript": "5.9.3",
156
+ "typescript-eslint": "8.71.0",
157
+ "vite": "7.3.6",
158
+ "vite-plugin-dts": "5.1.1",
159
+ "vitest": "5.0.2",
160
+ "vscode-oniguruma": "2.0.1",
161
+ "vscode-textmate": "9.3.2",
162
+ "vue": "3.5.43",
163
+ "vue-eslint-parser": "10.4.1",
164
+ "vue-tsc": "3.3.11"
165
+ },
82
166
  "engines": {
83
167
  "node": ">=20"
84
168
  },
169
+ "packageManager": "pnpm@10.33.2",
170
+ "pnpm": {
171
+ "onlyBuiltDependencies": [
172
+ "esbuild",
173
+ "vue-demi"
174
+ ]
175
+ },
85
176
  "config": {
86
177
  "bundledRuntime": {
87
178
  "@tanstack/vue-virtual": "3.13.39",