@synopackageland/ui 0.20.4 → 0.20.6

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 (73) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/LICENSES/THIRD_PARTY.md +2 -32
  3. package/dist/cli.js +0 -0
  4. package/dist/components/app/app-layout/AppLayout.vue.d.ts +21 -1
  5. package/dist/components/app/app-layout/pageTitle.d.ts +24 -0
  6. package/dist/components/app/app-tab-bar/AppTabBar.vue.d.ts +1 -1
  7. package/dist/components/app/aside-panel/AsidePanel.vue.d.ts +1 -1
  8. package/dist/components/app/section-tabs/SectionTabs.vue.d.ts +22 -4
  9. package/dist/components/composite/about-dialog/AboutDialog.vue.d.ts +1 -1
  10. package/dist/components/composite/adaptive-panel/AdaptivePanel.vue.d.ts +1 -1
  11. package/dist/components/composite/data-grid/DataGrid.vue.d.ts +19 -1
  12. package/dist/components/composite/data-grid/labels.d.ts +15 -0
  13. package/dist/components/composite/data-grid/listLayout.d.ts +26 -0
  14. package/dist/components/composite/data-table/DataTable.vue.d.ts +33 -7
  15. package/dist/components/composite/file-picker/FilePicker.vue.d.ts +3 -2
  16. package/dist/components/composite/form/FormGroup.vue.d.ts +1 -1
  17. package/dist/components/composite/form/context.d.ts +13 -0
  18. package/dist/components/composite/form-actions/FormActions.vue.d.ts +6 -2
  19. package/dist/components/composite/record-panel/RecordPanel.vue.d.ts +20 -6
  20. package/dist/components/composite/settings-section/SettingsSection.vue.d.ts +1 -1
  21. package/dist/components/composite/tag-editor/TagEditor.vue.d.ts +2 -2
  22. package/dist/components/composite/tree-view/TreeView.vue.d.ts +4 -4
  23. package/dist/components/composite/wizard/WizardDialog.vue.d.ts +2 -2
  24. package/dist/components/containers/list-detail/ListDetail.vue.d.ts +5 -3
  25. package/dist/components/feedback/validation-popup/ValidationPopup.vue.d.ts +1 -1
  26. package/dist/components/primitives/color-picker/ColorPicker.vue.d.ts +4 -4
  27. package/dist/components/primitives/combobox/Combobox.vue.d.ts +4 -4
  28. package/dist/components/primitives/context-menu/ContextMenu.vue.d.ts +2 -2
  29. package/dist/components/primitives/date-picker/DatePicker.vue.d.ts +1 -1
  30. package/dist/components/primitives/dialog/Dialog.vue.d.ts +17 -9
  31. package/dist/components/primitives/dropdown-menu/DropdownMenu.vue.d.ts +3 -3
  32. package/dist/components/primitives/dropdown-menu/DropdownMenuSheet.vue.d.ts +28 -0
  33. package/dist/components/primitives/input/Input.vue.d.ts +8 -1
  34. package/dist/components/primitives/input/Textarea.vue.d.ts +1 -1
  35. package/dist/components/primitives/input-steppers/InputSteppers.vue.d.ts +1 -1
  36. package/dist/components/primitives/motion/PushTransition.vue.d.ts +38 -0
  37. package/dist/components/primitives/multiple-select/MultipleSelect.vue.d.ts +4 -4
  38. package/dist/components/primitives/pagination/Pagination.vue.d.ts +22 -9
  39. package/dist/components/primitives/pagination/PaginationSheet.vue.d.ts +27 -0
  40. package/dist/components/primitives/pagination/labels.d.ts +6 -0
  41. package/dist/components/primitives/popover/Popover.vue.d.ts +1 -1
  42. package/dist/components/primitives/select/internal/ComboboxField.vue.d.ts +5 -5
  43. package/dist/components/primitives/select/internal/SelectSheet.vue.d.ts +42 -0
  44. package/dist/components/primitives/sheet/BottomSheet.vue.d.ts +69 -0
  45. package/dist/components/primitives/sheet/Sheet.vue.d.ts +13 -5
  46. package/dist/components/primitives/sheet/phone.d.ts +7 -0
  47. package/dist/components/primitives/superbox-select/SuperboxSelect.vue.d.ts +4 -4
  48. package/dist/components/rich-text-editor/RichTextEditor.vue.d.ts +1 -1
  49. package/dist/components/rich-text-editor/extensions.d.ts +2 -0
  50. package/dist/composables/formControl.d.ts +18 -0
  51. package/dist/composables/phoneSurface.d.ts +35 -0
  52. package/dist/feedback/StatusSlot.vue.d.ts +1 -1
  53. package/dist/grid/host/GridHost.vue.d.ts +1 -1
  54. package/dist/grid.js +358 -259
  55. package/dist/i18n/text.d.ts +11 -0
  56. package/dist/index.d.ts +7 -2
  57. package/dist/index.js +21205 -16551
  58. package/dist/json-preview/preview.js +16913 -12418
  59. package/dist/reka-runtime.json +15 -12
  60. package/dist/rich-text.js +259 -255
  61. package/dist/theme.css +1 -1
  62. package/dist/themes/dsm7-embedded-reset.css +1 -1
  63. package/dist/themes/dsm7-embedded-theme.css +1 -1
  64. package/dist/themes/dsm7-embedded.css +1 -1
  65. package/dist/themes/dsm7.css +1 -1
  66. package/docs/adaptive-layout.md +11 -9
  67. package/docs/app-local-modal.md +1 -1
  68. package/docs/layout-rules.md +14 -0
  69. package/docs/list-detail.md +9 -3
  70. package/docs/mobile-native.md +299 -11
  71. package/docs/release-checks.md +5 -0
  72. package/docs/theme-hooks.md +2 -2
  73. package/package.json +65 -57
@@ -270,20 +270,22 @@ import {
270
270
  remove a child Card's frame or a PageHeader's own vertical padding. The JSON
271
271
  `Page` accepts the same value. SDK `AppShell` consumers need that package to
272
272
  expose/forward the option to its internal `PageContent`.
273
- - `DataTable` defaults to `mobile-view="auto"`, switching to accessible labeled
274
- cards in the **App's mobile presentation**. A narrow table within an ordinary
273
+ - `DataTable` defaults to `mobile-view="auto"`, switching to a list (a row per
274
+ item: title, subtitle, trailing column; see docs/mobile-native.md) in the
275
+ **App's mobile presentation**. A narrow table within an ordinary
275
276
  700px/800px DSM Window stays a table, even if its content area is below
276
277
  640px. When no AppRoot/AppLayout provides a presentation, DataTable falls
277
- back to its own width (640px or less). It preserves *all* columns,
278
- custom `cell-*` slots and actions. `mobile-view="table"` retains horizontal
279
- scrolling for dense/interactive data; `cards` forces cards at every size.
278
+ back to its own width (640px or less). `mobile-view="table"` retains horizontal
279
+ scrolling for dense/interactive data; `cards` shows every column as fully
280
+ labeled cards at every size.
280
281
  Explicit column `width` values (CSS pixels or percentages) use fixed table
281
282
  tracks; columns without a width share the remaining space. Long text wraps
282
283
  inside those tracks. Tables without widths retain automatic sizing.
283
- - `DataGrid` retains its desktop semantics (sorting, editing, selection,
284
- keyboard navigation, reorder) and horizontally scrolls at narrow widths.
285
- Avoid silently removing columns or disabling actions. Use a mobile alternative
286
- for very complex tables rather than degrading a grid's editing model.
284
+ - `DataGrid` shows the same phone list in the App's mobile presentation
285
+ (`mobile-view="auto"`): long press selects, and sorting and columns move into
286
+ a sheet. `mobile-view="table"` keeps the table, which scrolls horizontally.
287
+ Use a mobile alternative for very complex tables rather than degrading a
288
+ grid's editing model.
287
289
  - `ResponsiveView` renders a `mobile`, `window`, or `web` slot when available.
288
290
  The `default` slot is the fallback for all unprovided modes, so features aren't
289
291
  accidentally hidden. Only the active variant is mounted.
@@ -146,7 +146,7 @@ root and inside the active panel, or use the library's supported popup component
146
146
 
147
147
  ## Delivery and patch maintenance
148
148
 
149
- Reka 2.10.5 is an exact development dependency, bundled into the UI library's
149
+ Reka 2.11.0 is an exact development dependency, bundled into the UI library's
150
150
  ESM dist. It is no longer a consumer runtime dependency. Vue remains an external
151
151
  peer, and the bundled code uses that same Vue instance. Consumers receive the
152
152
  body-style fix by rebuilding, without a package-manager patch or app code change.
@@ -72,6 +72,20 @@ The setting is a form value, saved with the page's Apply action. It does not nav
72
72
  - A settings page has one Apply. Put Reset then Apply in `SettingsPage`'s `#footer`, at the bottom-right, as Control Panel does. Keep the footer while the page has settings to apply, so nothing moves when a draft changes. Do not put a Save between the fields of a section.
73
73
  - A button that belongs to a field (Allowed websites, Trusted Proxies, Advanced Settings) is a grey `Button variant="secondary"` in the flow: in the `FormGroup` it depends on, after the field it acts on (`#after`), or under the section's description.
74
74
 
75
+ ## Choosing tabs on a phone
76
+
77
+ Phone layout follows the app container (≤640px) and a coarse pointer, not the browser window alone. A 768px touch tablet keeps its wider layout and still gets 44px tap targets.
78
+
79
+ | What changes | Use | Phone behaviour |
80
+ |---|---|---|
81
+ | 2–4 short views of the same content | `SegmentControl` | Full width, equal segments, 36px minimum visual height and 44px hit area; translated labels wrap and may grow the row |
82
+ | Many local views or long navigation labels | `Tabs` | Horizontal strip with edge fades; selection scrolls into view without moving the page; labels stay on one line |
83
+ | Up to 5 top-level app destinations | `AppTabBar` | Bottom navigation; additional destinations belong under More |
84
+ | Settings sections or 5+ page sections | `SectionTabs` / `SettingsPage` | Grouped list and push navigation on phone; retain the section structure |
85
+ | A saved form choice | `RadioGroup` | Labelled choices, not navigation tabs |
86
+
87
+ `SegmentControl variant="sheet"` follows the same phone sizing. More than four existing segments remain horizontally scrollable with fades; use `Tabs` for new many-view navigation. `size="sm"` stays compact on desktop and follows phone sizing on a phone. Icon-only segments require `aria-label`; use visible text when a touch user needs the label to choose. Never place a second bottom tab bar inside a page.
88
+
75
89
  ## Tables
76
90
 
77
91
  - Use `CellText` for a cell with a main text and secondary information (size, time, path): `text`, `secondary`, optional `icon`, `layout="stacked"` (default) or `"inline"`. It truncates both and shows the full text in a tooltip only when cut. Do not join strings or add margins.
@@ -39,14 +39,20 @@ Packages page with the selected package's advice).
39
39
  DSM package (or the page's host and directory) as for
40
40
  [closed callouts](callout.md). Without `storage-key` the App's panes share
41
41
  one width. An unreadable or blocked storage falls back to the default.
42
- - Narrower than 640px of room the pane goes above the list, full width, and
43
- the separator is hidden.
42
+ - At 640px of room or less (a phone) the list fills the room and the detail
43
+ is a page pushed over it ([push navigation](mobile-native.md#push-navigation)):
44
+ it slides in from the right, the list moves 30% left and dims, and a 48px
45
+ header shows Back and the title in place of Close. Back, the browser's Back
46
+ and a swipe from the detail's left edge pop it, which emits `close`. The
47
+ separator is hidden. Give the ListDetail a height to fill so the list and
48
+ the detail each scroll on their own; without one the detail sets the height.
44
49
  - In the pane, give tables column widths that fit the default width and dates
45
50
  in a one-line short format; a table wider than the pane scrolls sideways.
46
51
 
47
52
  Hooks: `data-ui-list-detail` (with `data-open` and `data-dragging`),
48
53
  `data-ui-list-detail-list`, `data-ui-list-detail-separator`,
49
- `data-ui-list-detail-pane`, `data-ui-list-detail-close`; the pane's width is
54
+ `data-ui-list-detail-pane`, `data-ui-list-detail-close`, `data-push` on the
55
+ root while the detail is pushed; the pane's width is
50
56
  `--ui-list-detail-width`.
51
57
 
52
58
  | Default width (1320px window) | Dragged wider | Wide window (1860px) |
@@ -2,6 +2,27 @@
2
2
 
3
3
  How the UI SDK behaves on a phone (390×844) and a touch tablet (768×1024).
4
4
 
5
+ ## Width containment and the release gate
6
+
7
+ Typography tables scroll horizontally within their parent, including a flex
8
+ or grid parent. Wizard footer groups wrap within the wizard; stage topology
9
+ nodes shrink with the available width. Topology card tracks shrink rather
10
+ than force their container wider. FormGroup uses a 12px indent level on a
11
+ touch phone and 28px elsewhere; an explicit CSS length keeps its value.
12
+ Settings switch rows retain a 44px touch target, and a Slider's value bubble
13
+ stays inside its track at both ends. Heading permalinks use the shared touch
14
+ hit area. Text fields inside a BottomSheet are at least 44px tall on touch
15
+ tablets as well as phones. Long Select values ellipsize within the trigger.
16
+ Numeric preset rows are at least 44px tall on touch. The rich text toolbar
17
+ counts its 44px touch buttons when deciding which tools fit; the rest move
18
+ into the existing More menu.
19
+
20
+ `pnpm check:mobile` checks the gallery at both phone and tablet sizes. It is
21
+ a blocking step of `gates:full`; the basic gate selection stays the same.
22
+ Any remaining audit exceptions are bounded in
23
+ `scripts/fixtures/mobile-overflow-allow.json`, with an audit reference and a
24
+ reason. Normal checks never add exceptions.
25
+
5
26
 
6
27
  ## Signals
7
28
 
@@ -22,7 +43,7 @@ Every touch rule is keyed on `[data-ui-app]`, the root every app has (AppRoot).
22
43
  Before this change they were keyed on `[data-ui-theme='dsm7']`. Nothing sets
23
44
  that attribute (Studio sets its own theme name), so the rules never applied.
24
45
 
25
- ## Tokens and base guards (M-1)
46
+ ## Tokens and base guards
26
47
 
27
48
  ### Type and size tokens
28
49
 
@@ -53,7 +74,7 @@ control and grid sizes; it does not change the 16px field rule.
53
74
  | `--ui-card-padding` | 20 (Card default) | 20 | **16** | card inset |
54
75
  | `--ui-chrome-height` | 44 (web 48, narrow layout 44) | – | **48** | the phone header |
55
76
  | `--ui-radius-sheet` (new) | 12 | 12 | 12 | top corners of a bottom sheet |
56
- | `--ui-safe-top/right/bottom/left` (new) | `env(safe-area-inset-*, 0px)` | same | same | notch and home indicator; non-zero only once the viewport meta has `viewport-fit=cover` (M-2) |
77
+ | `--ui-safe-top/right/bottom/left` (new) | `env(safe-area-inset-*, 0px)` | same | same | notch and home indicator; non-zero only once the viewport meta has `viewport-fit=cover` |
57
78
  | `--ui-pressed` (new) | 8% of the text colour | same | same | the pressed tint |
58
79
 
59
80
  Density comes from spacing, not smaller text: phone rows are 44–48px, and
@@ -78,7 +99,7 @@ component rule overrides them.
78
99
  - No blanket `min-width: 0`. The spec asked for one, but the 1280 gallery
79
100
  diff showed it squeezing nowrap rows (Space "wrap off" went one letter per
80
101
  line) and a fixed-size brand icon on the desktop. A component whose flex
81
- child must shrink sets `min-w-0` on it instead (M-13 handles the remaining
102
+ child must shrink sets `min-w-0` on it instead (a later change handles the remaining
82
103
  width offenders).
83
104
  - `overflow-wrap: break-word` on the app root. A word longer than its line
84
105
  breaks. We use `break-word`, not `anywhere`, because it only breaks text
@@ -87,7 +108,7 @@ component rule overrides them.
87
108
  - `img`, `video` and `iframe` get `max-width: 100%`, and `img` and `video` also
88
109
  get `height: auto`. `svg` is left alone so icons keep their size.
89
110
  - Don't write `100vw` in components. Use `100%` or the viewport variables from
90
- M-2. The `check:mobile` script (M-12) enforces this.
111
+ the viewport layer. The `check:mobile` script enforces this.
91
112
 
92
113
  ### Tap feedback (`touch.css`)
93
114
 
@@ -135,16 +156,16 @@ tap box.
135
156
  - **Hardcoded font sizes:** components that write a literal size, such as
136
157
  `text-[13px]` in Checkbox, Switch, Radio, Input and the menu rows, don't
137
158
  pick up the 15px phone text until they read `--ui-font-size`. Their owners
138
- (M-5, M-10, M-13) switch them over. Fields are already 16px on touch.
159
+ (later changes) switch them over. Fields are already 16px on touch.
139
160
  - **Canvas grid:** the grid draws text at fixed sizes from
140
161
  `src/grid-core/theme.ts`. Reading `--ui-grid-font-size` needs a change in
141
162
  the grid.
142
163
  - **Form gap:** `Form` writes its own `--ui-form-gap` inline, from
143
- `--ui-field-gap`. The 16px phone form gap belongs to Form (M-10).
164
+ `--ui-field-gap`. The 16px phone form gap belongs to Form.
144
165
  - **Hover rules in `adaptive.css`:** the brand action, sidebar collapse and
145
166
  resize handles still use plain `:hover`. They belong to the shell tasks.
146
167
 
147
- ## Floating layer (M-3a)
168
+ ## Floating layer
148
169
 
149
170
  Every anchored popup is placed by one preset, `src/components/primitives/popover/floating.ts`, on Reka's popper (floating-ui flip, shift, size and hide). A component binds it on its Reka `*Content` and never picks its own collision props.
150
171
 
@@ -171,7 +192,7 @@ const floating = useFloating(() => trigger.value?.$el, () => open.value)
171
192
 
172
193
  **Uses it today:** Popover, Tooltip, OverflowTooltip and all anchored popups listed below.
173
194
 
174
- ## All anchored popups (M-3b)
195
+ ## All anchored popups
175
196
 
176
197
  Menus (including nested menus and Breadcrumb), Select and the shared ComboboxField,
177
198
  DatePicker, ColorPicker, InputSteppers presets, InfoTip, ValidationPopup,
@@ -181,9 +202,10 @@ Menu sublevels inherit the parent's live padding, so an open submenu follows
181
202
  safe-area and keyboard changes. Preferred and trigger widths are capped by
182
203
  `--reka-popper-available-width`; lists scroll within the available height.
183
204
  Opening a searchable Select, calendar or palette on touch does not autofocus
184
- an input or swatch. Phone bottom sheets and inline validation remain M-5 work.
205
+ an input or swatch. On a phone, pickers and long menus open as bottom sheets instead
206
+ (see Pickers and menus on a phone); inline validation comes later.
185
207
 
186
- ## Toast, notification and hover-only controls (M-11)
208
+ ## Toast, notification and hover-only controls
187
209
 
188
210
  `usePhone()` (`src/composables/phone.ts`) is the phone signal in script: the
189
211
  enclosing AppRoot is narrow and the pointer is coarse, the same test as the
@@ -211,4 +233,270 @@ does the same.
211
233
  the card's context menu (`menuItems`) through Reka's long press
212
234
  (`ContextMenu` `pressOpenDelay`). The click that ends that press doesn't open
213
235
  the card. A card without entries opens no menu. The menu is the plain
214
- context menu for now; it becomes a phone sheet through M-5's rule.
236
+ context menu for now; it becomes a phone sheet once pickers move to sheets.
237
+
238
+ ## Forms and inputs
239
+
240
+ The keyboard depends on each field's type, so these are component defaults,
241
+ not tokens.
242
+
243
+ ### Keyboard hints by type
244
+
245
+ `Input` sets these from its `type`, and `SearchField` from `search`. An
246
+ attribute of the same name on the component overrides each one.
247
+
248
+ | `type` | Native type | `inputmode` | Other hints |
249
+ |---|---|---|---|
250
+ | `email` | email | email | `autocapitalize=off spellcheck=false` |
251
+ | `tel` | tel | tel | – |
252
+ | `url` | url | url | `autocapitalize=off` |
253
+ | `integer` (new) | text | numeric | – |
254
+ | `decimal` (new) | text | decimal | – |
255
+ | `password` | password | – | `autocapitalize=off spellcheck=false` (still right once revealed) |
256
+ | `search`, `SearchField` | search | – | `enterkeyhint=search` |
257
+ | `text` and others | as given | – | – |
258
+
259
+ Use `integer` or `decimal` for numbers typed on a phone. They open the number
260
+ keyboard and skip the native number field's spinner and value parsing.
261
+ `type="number"` stays the native field. `Input` and `SearchField` read
262
+ `--ui-font-size` (15px on a phone), and every field is 16px on a coarse
263
+ pointer (see Tokens and base guards). The clear button (`data-ui-hit`) and the password toggle get the
264
+ 44px tap box.
265
+
266
+ ### Next and Done
267
+
268
+ Inside a `Form`, each `Input` that is the primary control of a `FormItem`
269
+ joins the form's field order, which is document order, taken when the field
270
+ mounts. Disabled and read-only fields are skipped. The last field gets
271
+ `enterkeyhint=done` and the others get `next`. On a coarse pointer, Enter on a
272
+ `next` field moves focus to the next field with `focusWithoutScroll`, so the
273
+ page never jumps. It does not submit the form. With a mouse, Enter keeps the
274
+ browser's behaviour. A caller's own `enterkeyhint` turns both off for that
275
+ field. A `SearchField` keeps `search` and is not part of the order. Other
276
+ controls (`InputSteppers`, `Textarea`) are not in the order yet.
277
+
278
+ `useFormControl({ keyboard: { type, element, active } })` is how a text
279
+ control opts in.
280
+
281
+ ### Phone form layout
282
+
283
+ - `Form` with `spacing="auto"` puts 16px between items on a phone. Below
284
+ 400px it already stacks labels over fields.
285
+ - `FormActions` is sticky by default on a phone (narrow and coarse,
286
+ `usePhone()`, exported). Elsewhere it stays off unless you set `sticky`, and
287
+ `:sticky="false"` turns it off on a phone too. A sticky bar sits at
288
+ `bottom: var(--ui-kb-inset, 0px)`, so it rides above the iOS keyboard, and
289
+ it sets `--ui-scroll-footer` on its scroller so a revealed field clears it.
290
+ On a phone its bottom padding is `max(12px, safe-bottom)`, or 8px while the
291
+ keyboard is open.
292
+ - On a phone, `FormActions` buttons share the row equally, and a single
293
+ button is full width. Put the primary button last so it sits on the right.
294
+
295
+ ## BottomSheet
296
+
297
+ `BottomSheet` is the one bottom sheet, built on Reka's `Drawer` (reka-ui
298
+ 2.11.0). It has a 36×4 grabber in a 20px zone, a 48px header with the title
299
+ and a 44px close button, a body that scrolls with `overscroll-behavior:
300
+ contain`, a pinned footer and safe-area padding. The top corners use
301
+ `--ui-radius-sheet`.
302
+
303
+ - `height`: `auto` (content, up to 92%), `large` (92%; the strip of page left
304
+ above it closes the sheet when tapped), `full` (the whole screen, below the
305
+ top safe area), or a CSS height capped at 92%. `snap-points` passes through
306
+ to the Drawer.
307
+ - A drag follows the finger. Letting go past 30% of the sheet or faster than
308
+ 0.5 px/ms closes it; otherwise it springs back over
309
+ `--ui-motion-spring-back` (180ms). The two thresholds are hash-checked
310
+ patch hunks on Reka (`build/reka-patches.json`); Reka's own defaults are
311
+ 40px and 0.3 px/ms. A drag that starts in the body moves the sheet only when
312
+ the body is scrolled to the top.
313
+ - The Drawer's keyboard provider publishes `--drawer-keyboard-inset` on the
314
+ viewport. The viewport pads its bottom by it, so the sheet and its footer
315
+ sit above the keyboard and the heights shrink instead of the sheet moving
316
+ off the screen.
317
+ - In 280ms on `--ui-motion-sheet-ease`, out 200ms, the scrim
318
+ (`--ui-drawer-backdrop`) fading with it. Reduced motion uses an 80ms fade.
319
+ - `confirm-close` (set while a form is dirty) asks "Discard changes?" in a
320
+ nested sheet before a swipe, Back, Escape, an outside press or the close
321
+ button closes it. Nested sheets stack, and each closes only itself.
322
+ - On a phone, Back closes the top sheet (`useBackDismiss`); `back-dismiss`
323
+ overrides that.
324
+
325
+ `Dialog` on a phone (an app 640px or narrower) renders as a `BottomSheet`:
326
+ `auto`, or `full` for `fullscreen` and for a form with more than 3 fields. The
327
+ CSS-only dialog-as-sheet in `adaptive.css` is gone. `Sheet side="bottom"` is
328
+ a `BottomSheet` everywhere; without `size` it fits its content. Left and right
329
+ sheets are unchanged, and their footer keeps clear of the home indicator.
330
+ an input or swatch. On a phone, pickers and long menus open as bottom sheets instead
331
+ (see Pickers and menus on a phone); inline validation comes later.
332
+
333
+ ## Push navigation
334
+
335
+ On a phone a detail is a page pushed over the page it came from, not a split
336
+ view. `PushTransition` draws one pushed page:
337
+
338
+ - It slides in from the right over `--ui-motion-push` (280ms) and pops back
339
+ over `--ui-motion-pop` (240ms), both on `--ui-motion-sheet-ease`. The page
340
+ under it (`under`) moves 30% left, dims 10% and is `inert` while covered.
341
+ - Its 48px back header (with `title` or the `title` slot) has Back at the
342
+ start, the title in the middle and a `header-actions` slot at the end.
343
+ - Back, the browser's Back (Android back, the iOS edge swipe, through
344
+ `useBackDismiss`) and a swipe that starts within 20px of the page's left
345
+ edge pop it (`pop`). A swipe let go past 35% of the width or faster than
346
+ 0.5px/ms pops; a shorter one springs back over `--ui-motion-spring-back`.
347
+ - Reduced motion or `data-ui-motion="off"`: no slide or parallax, a fade that
348
+ the zero durations make instant.
349
+ - With `enabled` off (a split view, a desktop) it shows and hides at once and
350
+ leaves the page under it alone.
351
+ - The covered page leaves the flow (absolute, clipped to the pushed page) and
352
+ the pushed page stays in flow, so a container with no height takes the
353
+ pushed page's height. The container is `position: relative` with
354
+ `overflow-x: clip`, so the page off to the right never widens the document.
355
+
356
+ Where it is used:
357
+
358
+ | Component | Phone (640px or less) | Wider |
359
+ |---|---|---|
360
+ | `ListDetail` | the list fills the room; the detail is pushed over it, with Back in place of Close | the split view, unchanged |
361
+ | `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 |
362
+ | `SectionTabs`, four or fewer sections | a full-width SegmentControl with equal segments | the strip |
363
+ | `SectionTabs` with `phone="strip"` | the scrolling strip, for tabs inside a pushed page or a sheet | the strip |
364
+
365
+ `phone` (`auto`, `list`, `segment`, `strip`) overrides the choice. A phone
366
+ is the App's presentation (`mobile`), set by AppRoot or AppLayout. The
367
+ `#actions` slot sits above the section list. A `SettingsPage` inside a pushed
368
+ section keeps its sticky footer.
369
+
370
+ ## RecordPanel and AsidePanel on the BottomSheet
371
+
372
+ Both panels use `BottomSheet` on a phone, so they share its grabber, drag,
373
+ Back, keyboard inset, scrim and motion. Neither has swipe code of its own.
374
+
375
+ - `RecordPanel` compact (a host 640px or narrower, or too narrow for a 320px
376
+ panel beside 240px of view) is a `large` sheet (92%). The record's title
377
+ names it, and the `header` slot sits next to the 44px close button.
378
+ `has-previous` and `has-next` add a footer with full-width Previous record
379
+ and Next record buttons (`@previous`, `@next`). The footer stays above the
380
+ keyboard and the home indicator. `secondary-open` pushes the comments over
381
+ the body with `PushTransition`, titled by `secondary-label`. Its Back (the
382
+ button, the edge swipe or the browser's) closes that page before the sheet
383
+ and emits `update:secondary-open`. Close reasons are `escape`,
384
+ `button`, `outside` and `swipe` (a drag or Back).
385
+ - `AsidePanel` on a phone is a `large` sheet with `snap-points` `[0.5, 0.92]`.
386
+ `expanded` makes it `full`. Its expand button is 44px. The phone button
387
+ hides while it is open and while a record sheet is open.
388
+ - Neither panel remounts the caller's content. The header, body, thread and
389
+ composer render once and move between the docked panel, the sheet and a
390
+ hidden holder. A switch between docked and sheet (a rotation or a resize)
391
+ keeps fields, focus and caret. A collapsed assistant keeps its draft and
392
+ thread scroll.
393
+
394
+
395
+ ## Phone shell
396
+
397
+ On a phone (`AppLayout` 640px or narrower) the layout draws one bar above
398
+ the page, `--ui-chrome-height` tall (48px on a phone, 44px in a narrow window
399
+ driven by a mouse):
400
+
401
+ - **Leading:** the menu button, or a Back button when `back` is set (it
402
+ emits `back`; the app pops its own route). While an `AppTabBar` More tab
403
+ opens the drawer, the menu button steps aside.
404
+ - **Title:** the `title` prop, else the title of the page's `PageHeader`
405
+ (the last one mounted in the layout's content column), else the brand. 17px
406
+ semibold, one line, cut with an ellipsis; centred while nothing follows it,
407
+ start-aligned beside actions. A `PageHeader` whose title moves into the bar
408
+ keeps its heading for screen readers only, and takes no room when it has
409
+ nothing else to show (no description, meta or actions). There is no large
410
+ collapsing title.
411
+ - **Actions:** `headerActions` (DropdownMenu entries). The first plain entry
412
+ is one 44px button, its icon (the label is its accessible name) or else its
413
+ label; the rest open from "…". A pick emits `headerAction` with the key.
414
+
415
+ Every bar button is a 44px target.
416
+
417
+ The navigation drawer is Reka's `Drawer` with `swipe-direction="left"`,
418
+ portalled like `BottomSheet` (into the AppRoot in app scope). It is 85% of
419
+ the screen, up to 320px, and mounted only while open. A drag left follows the
420
+ finger and closes past 30% or above 0.5 px/ms, otherwise it springs back; it
421
+ moves in 240ms and out 180ms (`--ui-motion-drawer-in/-out`) with the scrim
422
+ fading, and reduced motion uses an 80ms fade. Escape, a press on the scrim, a
423
+ link in it and Back (`useBackDismiss`) close it; focus goes to its close
424
+ button and returns to what opened it. The page keeps its width.
425
+
426
+ `AppTabBar` hides while the keyboard is open (`useSoftKeyboard`, confirmed by
427
+ the visual viewport), pads by `--ui-safe-bottom`, and shows at most five
428
+ slots: with more tabs, the fifth is More, which opens a menu of the rest (and
429
+ the drawer, inside an AppLayout with a sidebar). More is marked current when
430
+ the current tab is in it.
431
+
432
+ ## Pickers and menus on a phone
433
+
434
+ In an app 640px wide or narrower (the same signal that turns a Dialog into a
435
+ BottomSheet), a popup that holds a list, a search, a form, more than five
436
+ actions or a submenu opens as a `BottomSheet` instead of a popover. A touch
437
+ tablet and a desktop window keep popovers. One composable makes the choice
438
+ for every picker: `usePhoneSurface(content)` returns `popover` or `sheet`,
439
+ and `menuContent(items)` counts a menu's first-level actions (separators do
440
+ not count) and finds submenus. Each component keeps one open state, and only
441
+ one surface shows it.
442
+
443
+ | Component | Phone sheet |
444
+ |---|---|
445
+ | Select, Combobox, MultipleSelect, SuperboxSelect | Titled with the field's label; 48px rows; a search pinned above the list (when the field searches or creates) that is not focused on open. From the first search until the sheet closes it keeps 60% of the screen, so the sheet does not jump as results shrink. The field itself is read-only, so a tap opens the sheet instead of the keyboard. A multiple select toggles rows and closes on Done; a free label is added from an `Add "…"` row or Enter |
446
+ | DatePicker | The calendar with 44px days and 16px time selects, and Done in the footer. A date-only pick still closes it |
447
+ | DropdownMenu, ContextMenu | More than five actions, or any submenu: an action sheet with 48px rows, danger rows in red and a Cancel row. A submenu pushes inside the sheet with a back row. Checkbox rows toggle in place; any other pick closes the sheet. Five actions or fewer stay a popover |
448
+ | ColorPicker | The palette with 32px swatches and 44px Reset and Custom buttons |
449
+
450
+ Escape, a swipe down and Back each close only the top sheet, and an Escape
451
+ that ends an IME composition in the search closes nothing (the BottomSheet's
452
+ guard). `AdaptivePanel` already follows this rule for its own content: a form
453
+ panel, a sheet once its host is 640px or narrower.
454
+
455
+ ## Pagination and the older tables
456
+
457
+ These key on the narrow signal (an app 640px or narrower, `usePhonePresentation`),
458
+ because they change layout, not size. Sizes still come from the tokens, so a
459
+ narrow window used with a mouse keeps desktop-sized controls.
460
+
461
+ **Pagination.** The pager becomes `‹ Page 1 of N ›`: previous and next are
462
+ `--ui-control-md` icon buttons (44px on a phone), the total text sits on the
463
+ left, and there are no first, last or page-number buttons. Tapping the label
464
+ opens a `BottomSheet` with a page field (`type="integer"`, `enterkeyhint=go`)
465
+ and, when `items-per-page` is set, the page sizes as 48px rows. A single page
466
+ without sizes shows the label as plain text. An endless pager shows
467
+ `‹ Page 5 ›`, and its sheet only has the sizes. Above 640px the pager is
468
+ unchanged.
469
+
470
+ **DataGrid and DataTable.** `mobile-view="auto"` (the default) shows a list
471
+ instead of the table. Each row is at least `--ui-list-row-height` (48px on a
472
+ phone): the title column on the first line, up to two columns joined with `·`
473
+ on the second, and trailing columns at the end. Columns place themselves with
474
+ `list`:
475
+
476
+ | `list` | Place |
477
+ |---|---|
478
+ | unset | the first column is the title, the next two the subtitle; an `actions` column trails |
479
+ | `title` | the first line |
480
+ | `subtitle` | the second line; marked columns replace the unmarked ones |
481
+ | `trailing` | the end of the row, e.g. a status badge |
482
+ | `none` | not in the list |
483
+
484
+ The same `cell-*` slots render in the list. `mobile-view="table"` keeps the
485
+ table; DataTable's `cards` keeps the fully labeled cards.
486
+
487
+ DataGrid only:
488
+
489
+ - **Select mode.** With `selectable`, a 500ms long press on a row selects it
490
+ (`onLongPress` from VueUse, cancelled by a 10px move or a scroll). Having a
491
+ selection is select mode: each row gets a checkbox with a 44px tap box, a tap
492
+ toggles the row, and a bottom bar replaces the footer with select all, the
493
+ count, the `selection-actions` slot (`keys`, `rows`, `clear`) and Cancel,
494
+ which clears the selection. Outside select mode a tap is `rowClick`.
495
+ - **View options.** With a sortable column or `column-chooser`, a bar above the
496
+ list shows the sort and a button that opens a sheet: sort by (a tap on the
497
+ sorted column flips it, as the header does) and the columns to show, with
498
+ Reset columns.
499
+ - **Footer.** The phone `Pagination` (one page per step) with the item count
500
+ and refresh.
501
+ - Groups and expandable rows work in the list. Row drag reorder, column
502
+ resizing and column reorder are table-only.
@@ -21,3 +21,8 @@ Stage the package only after a successful build/basic check. Preserve version,
21
21
  ancestry, immutable-publication, metadata and credential boundaries. Registry
22
22
  availability checks needed to publish are distinct from optional consumer tests.
23
23
  No full-gate stamp is required for publication.
24
+
25
+ The full plan includes `check:mobile` at 390×844 and 768×1024. A mobile
26
+ failure returns a nonzero status and blocks that plan, like the other checks.
27
+ The mobile allow-list records only bounded audit exceptions with reasons;
28
+ normal runs never add exceptions.
@@ -54,9 +54,9 @@ here stay implementation details and can change in any release.
54
54
  | `data-ui-aside-resizing` | `AppLayout` root | `true` while the aside's handle is dragged |
55
55
  | `data-ui-side-moving` | `AppLayout` root | `true` while the sidebar or the aside changes width |
56
56
  | `data-ui-aside-panel-root` | `AsidePanel` root inside the aside region | `data-ui-aside-mode` is `docked`, `floating`, `sheet`; `data-ui-aside-floating` while the panel floats or slides away from floating |
57
- | `data-ui-aside-panel` | the panel (`complementary`, or `dialog` as a sheet) | `data-ui-aside-state` is `open`, `collapsed`, `expanded` |
57
+ | `data-ui-aside-panel` | the panel (`complementary`; on a phone the `BottomSheet` dialog) | `data-ui-aside-state` is `open`, `collapsed`, `expanded` |
58
58
  | `data-ui-aside-rail` / `data-ui-aside-open` | the 48px rail and its button | present |
59
- | `data-ui-aside-fab` / `data-ui-aside-scrim` | the phone button and the sheet's scrim | present |
59
+ | `data-ui-aside-fab` | the phone button | present |
60
60
  | `data-ui-aside-collapse` / `data-ui-aside-expand` / `data-ui-aside-restore` | header buttons | present |
61
61
  | `data-ui-aside-body` / `data-ui-aside-composer` | the thread scroller and the composer | present |
62
62
  | `data-ui-mobile-navigation` | the phone bar above the page | present |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synopackageland/ui",
3
- "version": "0.20.4",
3
+ "version": "0.20.6",
4
4
  "description": "Shared Vue 3 UI component library with a DSM 7 inspired application theme",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -61,6 +61,59 @@
61
61
  "./themes/dsm7-embedded-reset.css": "./dist/themes/dsm7-embedded-reset.css",
62
62
  "./themes/dsm7-embedded-theme.css": "./dist/themes/dsm7-embedded-theme.css"
63
63
  },
64
+ "scripts": {
65
+ "check:docs": "node --test scripts/check-doc-versions.test.mjs && node scripts/check-doc-versions.mjs",
66
+ "dev": "vite build --watch",
67
+ "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",
68
+ "lint": "eslint .",
69
+ "typecheck": "vue-tsc --noEmit -p tsconfig.json",
70
+ "test": "vitest run",
71
+ "test:watch": "vitest",
72
+ "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}",
73
+ "story": "histoire dev",
74
+ "story:build": "histoire build",
75
+ "check:host": "node scripts/dsm-host-shots.mjs",
76
+ "check:switch": "node scripts/check-switch.mjs",
77
+ "check:tabs": "node scripts/check-tabs.mjs",
78
+ "check:mobile": "node scripts/check-mobile-overflow.mjs",
79
+ "check:ui-patch": "node scripts/check-ui-patch.mjs",
80
+ "check:narrow": "node scripts/check-narrow-controls.mjs",
81
+ "check:drawer-form": "node scripts/check-drawer-form.mjs",
82
+ "check:responsive": "node scripts/check-responsive.mjs",
83
+ "check:adaptive": "node scripts/check-adaptive.mjs",
84
+ "check:sidebar-focus": "node scripts/check-sidebar-focus.mjs",
85
+ "check:json-render": "node scripts/check-json-render.mjs",
86
+ "check:motion": "node scripts/check-motion.mjs",
87
+ "check:topology": "node scripts/check-topology.mjs",
88
+ "check:wizard": "node scripts/check-wizard.mjs",
89
+ "check:edges": "node scripts/check-edges.mjs",
90
+ "check:data-grid": "node scripts/check-data-grid.mjs",
91
+ "check:truncation": "node scripts/check-truncation.mjs",
92
+ "check:link": "node scripts/check-link.mjs",
93
+ "check:text-select": "node scripts/check-text-select.mjs",
94
+ "check:date-picker": "node scripts/check-date-picker.mjs",
95
+ "check:full-page": "node scripts/check-full-page.mjs",
96
+ "check:detail-surface": "node scripts/check-detail-surface.mjs",
97
+ "check:internal": "node --test scripts/check-internal-content.test.mjs scripts/check-npm-metadata.test.mjs && node scripts/check-internal-content.mjs --repo .",
98
+ "check:npm-metadata": "node scripts/check-npm-metadata.mjs",
99
+ "check:package": "node scripts/check-package-content.mjs",
100
+ "check:ux-foundation-consumer": "node scripts/check-ux-foundation-consumer.mjs",
101
+ "notices:update": "node scripts/third-party-notices.mjs --write",
102
+ "notices:check": "node scripts/third-party-notices.mjs --check",
103
+ "check": "bash scripts/docker-check.sh \"npm run gates\"",
104
+ "check:fast": "bash scripts/docker-check.sh \"npm run gates\"",
105
+ "check:local-browser": "node scripts/app-local-browser.mjs",
106
+ "check:infotip": "node scripts/check-infotip.mjs",
107
+ "release:stage": "node scripts/stage-release.mjs",
108
+ "check:polish": "bash scripts/docker-check.sh --build --out .polish-shots 'node scripts/check-polish.mjs'",
109
+ "check:acceptance": "node scripts/check-acceptance.mjs",
110
+ "check:palette": "node scripts/check-palette.mjs",
111
+ "gates": "node scripts/gates.mjs",
112
+ "gates:full": "node scripts/gates.mjs --full",
113
+ "check:layout": "node scripts/check-layout.mjs",
114
+ "release:smoke": "node scripts/release-smoke.mjs",
115
+ "check:browser-install": "playwright install chromium"
116
+ },
64
117
  "peerDependencies": {
65
118
  "vue": "^3.5.0"
66
119
  },
@@ -95,7 +148,7 @@
95
148
  "histoire": "1.0.0-beta.1",
96
149
  "jsdom": "30.1.1",
97
150
  "playwright": "1.58.2",
98
- "reka-ui": "2.10.5",
151
+ "reka-ui": "2.11.0",
99
152
  "tailwindcss": "4.3.3",
100
153
  "typescript": "5.9.3",
101
154
  "typescript-eslint": "8.71.0",
@@ -111,64 +164,19 @@
111
164
  "engines": {
112
165
  "node": ">=20"
113
166
  },
167
+ "packageManager": "pnpm@10.33.2",
168
+ "pnpm": {
169
+ "onlyBuiltDependencies": [
170
+ "esbuild",
171
+ "vue-demi"
172
+ ]
173
+ },
114
174
  "config": {
115
175
  "bundledRuntime": {
116
176
  "@tanstack/vue-virtual": "3.13.39",
117
177
  "@vueuse/core": "14.4.0",
118
- "reka-ui": "2.10.5"
178
+ "reka-ui": "2.11.0"
119
179
  }
120
180
  },
121
- "homepage": "https://www.varvis.me/docs/",
122
- "scripts": {
123
- "check:docs": "node --test scripts/check-doc-versions.test.mjs && node scripts/check-doc-versions.mjs",
124
- "dev": "vite build --watch",
125
- "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",
126
- "lint": "eslint .",
127
- "typecheck": "vue-tsc --noEmit -p tsconfig.json",
128
- "test": "vitest run",
129
- "test:watch": "vitest",
130
- "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}",
131
- "story": "histoire dev",
132
- "story:build": "histoire build",
133
- "check:host": "node scripts/dsm-host-shots.mjs",
134
- "check:switch": "node scripts/check-switch.mjs",
135
- "check:tabs": "node scripts/check-tabs.mjs",
136
- "check:mobile": "node scripts/check-mobile-overflow.mjs",
137
- "check:narrow": "node scripts/check-narrow-controls.mjs",
138
- "check:drawer-form": "node scripts/check-drawer-form.mjs",
139
- "check:responsive": "node scripts/check-responsive.mjs",
140
- "check:adaptive": "node scripts/check-adaptive.mjs",
141
- "check:sidebar-focus": "node scripts/check-sidebar-focus.mjs",
142
- "check:json-render": "node scripts/check-json-render.mjs",
143
- "check:motion": "node scripts/check-motion.mjs",
144
- "check:topology": "node scripts/check-topology.mjs",
145
- "check:wizard": "node scripts/check-wizard.mjs",
146
- "check:edges": "node scripts/check-edges.mjs",
147
- "check:data-grid": "node scripts/check-data-grid.mjs",
148
- "check:truncation": "node scripts/check-truncation.mjs",
149
- "check:link": "node scripts/check-link.mjs",
150
- "check:text-select": "node scripts/check-text-select.mjs",
151
- "check:date-picker": "node scripts/check-date-picker.mjs",
152
- "check:full-page": "node scripts/check-full-page.mjs",
153
- "check:detail-surface": "node scripts/check-detail-surface.mjs",
154
- "check:internal": "node --test scripts/check-internal-content.test.mjs scripts/check-npm-metadata.test.mjs && node scripts/check-internal-content.mjs --repo .",
155
- "check:npm-metadata": "node scripts/check-npm-metadata.mjs",
156
- "check:package": "node scripts/check-package-content.mjs",
157
- "check:ux-foundation-consumer": "node scripts/check-ux-foundation-consumer.mjs",
158
- "notices:update": "node scripts/third-party-notices.mjs --write",
159
- "notices:check": "node scripts/third-party-notices.mjs --check",
160
- "check": "bash scripts/docker-check.sh \"npm run gates\"",
161
- "check:fast": "bash scripts/docker-check.sh \"npm run gates\"",
162
- "check:local-browser": "node scripts/app-local-browser.mjs",
163
- "check:infotip": "node scripts/check-infotip.mjs",
164
- "release:stage": "node scripts/stage-release.mjs",
165
- "check:polish": "bash scripts/docker-check.sh --build --out .polish-shots 'node scripts/check-polish.mjs'",
166
- "check:acceptance": "node scripts/check-acceptance.mjs",
167
- "check:palette": "node scripts/check-palette.mjs",
168
- "gates": "node scripts/gates.mjs",
169
- "gates:full": "node scripts/gates.mjs --full",
170
- "check:layout": "node scripts/check-layout.mjs",
171
- "release:smoke": "node scripts/release-smoke.mjs",
172
- "check:browser-install": "playwright install chromium"
173
- }
174
- }
181
+ "homepage": "https://www.varvis.me/docs/"
182
+ }