@synopackageland/ui 0.20.3 → 0.20.5

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 (182) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/LICENSES/THIRD_PARTY.md +2 -32
  3. package/LICENSES/ux-foundation.md +83 -0
  4. package/LICENSES/ux-grid-baserow.md +35 -0
  5. package/LICENSES/ux-grid-teable.md +48 -0
  6. package/README.md +3 -0
  7. package/dist/components/app/app-layout/AppLayout.vue.d.ts +52 -5
  8. package/dist/components/app/app-layout/pageTitle.d.ts +24 -0
  9. package/dist/components/app/app-root/AppRoot.vue.d.ts +9 -0
  10. package/dist/components/app/app-tab-bar/AppTabBar.vue.d.ts +1 -1
  11. package/dist/components/app/aside-panel/AsidePanel.vue.d.ts +69 -0
  12. package/dist/components/app/aside-panel/asidePreference.d.ts +28 -0
  13. package/dist/components/app/section-tabs/SectionTabs.vue.d.ts +22 -4
  14. package/dist/components/composite/about-dialog/AboutDialog.vue.d.ts +1 -1
  15. package/dist/components/composite/adaptive-panel/AdaptivePanel.vue.d.ts +61 -0
  16. package/dist/components/composite/connection-topology/ConnectionTopology.vue.d.ts +1 -1
  17. package/dist/components/composite/data-grid/DataGrid.vue.d.ts +19 -1
  18. package/dist/components/composite/data-grid/labels.d.ts +15 -0
  19. package/dist/components/composite/data-grid/listLayout.d.ts +26 -0
  20. package/dist/components/composite/data-table/DataTable.vue.d.ts +33 -7
  21. package/dist/components/composite/file-picker/FilePicker.vue.d.ts +2 -2
  22. package/dist/components/composite/form/FormGroup.vue.d.ts +1 -1
  23. package/dist/components/composite/form/context.d.ts +13 -0
  24. package/dist/components/composite/form-actions/FormActions.vue.d.ts +6 -2
  25. package/dist/components/composite/mention-list/MentionList.vue.d.ts +2 -2
  26. package/dist/components/composite/overflow-toolbar/OverflowToolbar.vue.d.ts +2 -2
  27. package/dist/components/composite/record-panel/RecordPanel.vue.d.ts +91 -0
  28. package/dist/components/composite/record-panel/recordPanelWidth.d.ts +54 -0
  29. package/dist/components/composite/settings-section/SettingsSection.vue.d.ts +1 -1
  30. package/dist/components/composite/summary-card/SummaryCard.vue.d.ts +1 -1
  31. package/dist/components/composite/tag-editor/TagEditor.vue.d.ts +4 -4
  32. package/dist/components/composite/tree-view/TreeView.vue.d.ts +4 -4
  33. package/dist/components/composite/wizard/Wizard.vue.d.ts +2 -2
  34. package/dist/components/composite/wizard/WizardDialog.vue.d.ts +4 -4
  35. package/dist/components/containers/card/Card.vue.d.ts +1 -1
  36. package/dist/components/containers/card/CardField.vue.d.ts +26 -0
  37. package/dist/components/containers/card-grid/CardCover.vue.d.ts +16 -0
  38. package/dist/components/containers/card-grid/CardGrid.vue.d.ts +1 -1
  39. package/dist/components/containers/card-grid/VirtualCardGrid.vue.d.ts +71 -0
  40. package/dist/components/containers/expandable-item/ExpandableItem.vue.d.ts +1 -1
  41. package/dist/components/containers/fieldset/Fieldset.vue.d.ts +1 -1
  42. package/dist/components/containers/list-detail/ListDetail.vue.d.ts +6 -4
  43. package/dist/components/containers/split-pane/SplitPane.vue.d.ts +1 -1
  44. package/dist/components/feedback/notification/Notification.vue.d.ts +1 -1
  45. package/dist/components/feedback/notification/NotificationProvider.vue.d.ts +1 -1
  46. package/dist/components/feedback/validation-popup/ValidationPopup.vue.d.ts +1 -1
  47. package/dist/components/primitives/breadcrumb/Breadcrumb.vue.d.ts +2 -2
  48. package/dist/components/primitives/color-picker/ColorPicker.vue.d.ts +4 -4
  49. package/dist/components/primitives/combobox/Combobox.vue.d.ts +4 -4
  50. package/dist/components/primitives/context-menu/ContextMenu.vue.d.ts +5 -2
  51. package/dist/components/primitives/context-menu/ContextMenuItems.vue.d.ts +4 -5
  52. package/dist/components/primitives/dialog/Dialog.vue.d.ts +16 -8
  53. package/dist/components/primitives/dropdown-menu/DropdownMenu.vue.d.ts +7 -3
  54. package/dist/components/primitives/dropdown-menu/DropdownMenuItems.vue.d.ts +6 -3
  55. package/dist/components/primitives/dropdown-menu/DropdownMenuSheet.vue.d.ts +28 -0
  56. package/dist/components/primitives/input/Input.vue.d.ts +8 -1
  57. package/dist/components/primitives/input/Textarea.vue.d.ts +1 -1
  58. package/dist/components/primitives/input-steppers/InputSteppers.vue.d.ts +1 -1
  59. package/dist/components/primitives/motion/PushTransition.vue.d.ts +38 -0
  60. package/dist/components/primitives/multiple-select/MultipleSelect.vue.d.ts +4 -4
  61. package/dist/components/primitives/pagination/Pagination.vue.d.ts +22 -9
  62. package/dist/components/primitives/pagination/PaginationSheet.vue.d.ts +27 -0
  63. package/dist/components/primitives/pagination/labels.d.ts +6 -0
  64. package/dist/components/primitives/popover/Popover.vue.d.ts +11 -3
  65. package/dist/components/primitives/popover/floating.d.ts +27 -0
  66. package/dist/components/primitives/popover/overlay.d.ts +36 -0
  67. package/dist/components/primitives/segment-control/SegmentControl.vue.d.ts +4 -1
  68. package/dist/components/primitives/segment-control/context.d.ts +3 -0
  69. package/dist/components/primitives/select/Select.vue.d.ts +4 -0
  70. package/dist/components/primitives/select/internal/ComboboxField.vue.d.ts +6 -5
  71. package/dist/components/primitives/select/internal/SelectSheet.vue.d.ts +42 -0
  72. package/dist/components/primitives/select/internal/types.d.ts +2 -0
  73. package/dist/components/primitives/sheet/BottomSheet.vue.d.ts +69 -0
  74. package/dist/components/primitives/sheet/Sheet.vue.d.ts +13 -5
  75. package/dist/components/primitives/sheet/phone.d.ts +7 -0
  76. package/dist/components/primitives/skeleton/SectionSkeleton.vue.d.ts +25 -4
  77. package/dist/components/primitives/skeleton/Skeleton.vue.d.ts +3 -0
  78. package/dist/components/primitives/superbox-select/SuperboxSelect.vue.d.ts +4 -4
  79. package/dist/components/primitives/tag/Tag.vue.d.ts +2 -2
  80. package/dist/components/primitives/toast/ToastProvider.vue.d.ts +0 -2
  81. package/dist/components/primitives/toast/toast.d.ts +21 -1
  82. package/dist/components/primitives/tooltip/Tooltip.vue.d.ts +8 -3
  83. package/dist/components/primitives/tooltip/tooltipStyle.d.ts +11 -1
  84. package/dist/components/rich-text-editor/RichTextEditor.vue.d.ts +1 -1
  85. package/dist/composables/backDismiss.d.ts +19 -0
  86. package/dist/composables/formControl.d.ts +18 -0
  87. package/dist/composables/phone.d.ts +8 -0
  88. package/dist/composables/phoneSurface.d.ts +35 -0
  89. package/dist/composables/softKeyboard.d.ts +26 -4
  90. package/dist/composables/visualViewport.d.ts +31 -0
  91. package/dist/dnd/autoScroll.d.ts +57 -0
  92. package/dist/dnd/dragKit.d.ts +111 -0
  93. package/dist/dnd/index.d.ts +4 -0
  94. package/dist/dnd/layout.d.ts +57 -0
  95. package/dist/dnd/vue.d.ts +16 -0
  96. package/dist/feedback/StatusSlot.vue.d.ts +24 -0
  97. package/dist/feedback/showAfter.d.ts +15 -0
  98. package/dist/feedback/stackMotion.d.ts +13 -0
  99. package/dist/feedback/timing.d.ts +23 -0
  100. package/dist/grid/cells/draw.d.ts +57 -0
  101. package/dist/grid/cells/images.d.ts +5 -0
  102. package/dist/grid/cells/index.d.ts +7 -0
  103. package/dist/grid/cells/number.d.ts +5 -0
  104. package/dist/grid/cells/registry.d.ts +56 -0
  105. package/dist/grid/cells/renderers.d.ts +8 -0
  106. package/dist/grid/cells/status.d.ts +6 -0
  107. package/dist/grid/cells/types.d.ts +200 -0
  108. package/dist/grid/cells/use.d.ts +7 -0
  109. package/dist/grid/clipboard/copy.d.ts +17 -0
  110. package/dist/grid/clipboard/index.d.ts +4 -0
  111. package/dist/grid/clipboard/proposal.d.ts +35 -0
  112. package/dist/grid/clipboard/tsv.d.ts +14 -0
  113. package/dist/grid/clipboard/write.d.ts +21 -0
  114. package/dist/grid/contracts.d.ts +200 -0
  115. package/dist/grid/edit/cell-status.d.ts +33 -0
  116. package/dist/grid/edit/edit-session.d.ts +97 -0
  117. package/dist/grid/edit/use-grid-edit.d.ts +48 -0
  118. package/dist/grid/focus/editor-style.d.ts +67 -0
  119. package/dist/grid/host/GridHost.vue.d.ts +137 -0
  120. package/dist/grid/host/controller.d.ts +219 -0
  121. package/dist/grid/host/dom.d.ts +10 -0
  122. package/dist/grid/index.d.ts +8 -0
  123. package/dist/grid/range/block.d.ts +30 -0
  124. package/dist/grid/range/fill.d.ts +27 -0
  125. package/dist/grid/range/index.d.ts +2 -0
  126. package/dist/grid/theme.d.ts +61 -0
  127. package/dist/grid-core/auto-scroll.d.ts +67 -0
  128. package/dist/grid-core/column-resize.d.ts +43 -0
  129. package/dist/grid-core/config.d.ts +39 -0
  130. package/dist/grid-core/coordinate.d.ts +64 -0
  131. package/dist/grid-core/drag.d.ts +46 -0
  132. package/dist/grid-core/freeze.d.ts +11 -0
  133. package/dist/grid-core/index.d.ts +17 -0
  134. package/dist/grid-core/keyboard.d.ts +34 -0
  135. package/dist/grid-core/keys.d.ts +38 -0
  136. package/dist/grid-core/lru.d.ts +10 -0
  137. package/dist/grid-core/range.d.ts +28 -0
  138. package/dist/grid-core/region.d.ts +73 -0
  139. package/dist/grid-core/renderer/base.d.ts +92 -0
  140. package/dist/grid-core/renderer/layout.d.ts +129 -0
  141. package/dist/grid-core/selection-controller.d.ts +62 -0
  142. package/dist/grid-core/selection.d.ts +35 -0
  143. package/dist/grid-core/theme.d.ts +81 -0
  144. package/dist/grid-core/types.d.ts +178 -0
  145. package/dist/grid.js +6272 -0
  146. package/dist/i18n/text.d.ts +17 -0
  147. package/dist/index.d.ts +39 -4
  148. package/dist/index.js +23037 -14390
  149. package/dist/interaction/editSession.d.ts +49 -0
  150. package/dist/interaction/focus.d.ts +28 -0
  151. package/dist/interaction/gesture.d.ts +49 -0
  152. package/dist/interaction/ime.d.ts +55 -0
  153. package/dist/interaction/index.d.ts +6 -0
  154. package/dist/interaction/outside.d.ts +40 -0
  155. package/dist/interaction/vue.d.ts +21 -0
  156. package/dist/json-ai-prompt.md +1 -1
  157. package/dist/json-preview/preview.js +16780 -11595
  158. package/dist/json.js +10 -10
  159. package/dist/lib/adaptiveLayout.d.ts +32 -2
  160. package/dist/lib/deviceViewport.d.ts +2 -1
  161. package/dist/lib/motion.d.ts +12 -0
  162. package/dist/reka-runtime.json +15 -12
  163. package/dist/rich-text.js +1 -1
  164. package/dist/theme.css +1 -1
  165. package/dist/themes/dsm7-embedded-reset.css +1 -1
  166. package/dist/themes/dsm7-embedded-theme.css +1 -1
  167. package/dist/themes/dsm7-embedded.css +1 -1
  168. package/dist/themes/dsm7.css +1 -1
  169. package/dist/virtual/cardHeight.d.ts +39 -0
  170. package/dist/virtual/galleryColumns.d.ts +11 -0
  171. package/dist/virtual/layoutMotion.d.ts +30 -0
  172. package/docs/adaptive-layout.md +148 -25
  173. package/docs/app-local-modal.md +1 -1
  174. package/docs/callout.md +22 -1
  175. package/docs/layout-rules.md +14 -0
  176. package/docs/list-detail.md +60 -0
  177. package/docs/mobile-native.md +502 -0
  178. package/docs/release-checks.md +28 -0
  179. package/docs/theme-hooks.md +28 -7
  180. package/docs/ux-foundation.md +133 -0
  181. package/docs/ux-grid.md +217 -0
  182. package/package.json +100 -2
@@ -0,0 +1,502 @@
1
+ # Mobile-native rules
2
+
3
+ How the UI SDK behaves on a phone (390×844) and a touch tablet (768×1024).
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
+
26
+
27
+ ## Signals
28
+
29
+ | Signal | Where it comes from | What keys on it |
30
+ |---|---|---|
31
+ | Narrow | the app root is 640px wide or narrower: AppRoot sets `data-ui-presentation="mobile"`, AppLayout `data-ui-layout-mode="mobile"` | layout shape: drawer, sheets (as before) |
32
+ | Phone | narrow **and** `(pointer: coarse)` | phone tokens (type, control height, spacing, rows, header) |
33
+ | Touch | `@media (pointer: coarse)` | 16px fields, 44px tap boxes, tap feedback, touch-tablet control sizes |
34
+ | Hover | `@media (hover: hover)` | every hand-written `:hover` rule in the theme |
35
+
36
+ A touch tablet at 768px gets the touch rules but not the phone tokens. A
37
+ desktop gets neither, so it looks exactly as before. That includes a DSM
38
+ window or browser window narrowed below 640px and used with a mouse: it keeps
39
+ the narrow layout with desktop sizes. The phone tokens need a coarse pointer
40
+ too, because without that gate the 1280 gallery's narrow solo shots changed.
41
+
42
+ Every touch rule is keyed on `[data-ui-app]`, the root every app has (AppRoot).
43
+ Before this change they were keyed on `[data-ui-theme='dsm7']`. Nothing sets
44
+ that attribute (Studio sets its own theme name), so the rules never applied.
45
+
46
+ ## Tokens and base guards
47
+
48
+ ### Type and size tokens
49
+
50
+ Each size is one token, so changing a size changes one line. Phone values are
51
+ set in `src/theme/adaptive.css` (the phone block), touch values in the
52
+ `@media (pointer: coarse)` blocks of `adaptive.css` and `dsm7.css`, and
53
+ desktop values in `dsm7.css`. An explicit `data-ui-density` keeps the app's
54
+ control and grid sizes; it does not change the 16px field rule.
55
+
56
+ | Token | Desktop | Touch tablet | Phone | Used for |
57
+ |---|---|---|---|---|
58
+ | `--ui-font-size` | 13 | 13 | **15** | primary text: list and record titles, field values, body copy, menu items |
59
+ | `--ui-font-size-sm` | 12 | 12 | **13** | secondary text: labels, meta, captions, helper text, badges |
60
+ | `--ui-font-size-xs` (new) | 12 | 12 | 12 | the smallest text anywhere |
61
+ | `--ui-line-height` | 20 | 20 | **22** | body line |
62
+ | `--ui-font-size-title` | 16 | 16 | **17** | header and dialog titles |
63
+ | `--ui-grid-font-size` (new) | 13 | 13 | **14** | grid cell text (not yet read by the canvas grid; see below) |
64
+ | `--ui-font-size-field` (new) | 16 | 16 | 16 | every field on a coarse pointer, to stop iOS focus zoom |
65
+ | `--ui-control-md` | 28 | **36** | **44** | fields, selects, form and sheet buttons |
66
+ | `--ui-control-sm` | 24 | **32** | **36** | inline and toolbar buttons (the tap box comes from `--ui-hit-min`) |
67
+ | `--ui-hit-min` (new) | 0 | **44** | **44** | smallest tap box |
68
+ | `--ui-tap-gap` (new) | 0 | **8** | **8** | room between tap targets; `--ui-icon-button-gap` reads it on touch |
69
+ | `--ui-grid-row-height` / `-header-height` | 28 | **36** | **32** | canvas grid rows (docs/ux-grid.md) |
70
+ | `--ui-list-row-height` (new) | 32 | **44** | **48** | list rows |
71
+ | `--ui-menu-row-height` | 32 | **40** | **48** | menu, select and sheet list rows |
72
+ | `--ui-page-padding-x` / `-y` | 16 (narrow layout 12) | 16 | **16** | page gutter |
73
+ | `--ui-section-gap` | 12 | 12 | **20** | space between sections |
74
+ | `--ui-card-padding` | 20 (Card default) | 20 | **16** | card inset |
75
+ | `--ui-chrome-height` | 44 (web 48, narrow layout 44) | – | **48** | the phone header |
76
+ | `--ui-radius-sheet` (new) | 12 | 12 | 12 | top corners of a bottom sheet |
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` |
78
+ | `--ui-pressed` (new) | 8% of the text colour | same | same | the pressed tint |
79
+
80
+ Density comes from spacing, not smaller text: phone rows are 44–48px, and
81
+ components add no padding beyond the tokens.
82
+
83
+ Phone motion tokens (all read 0ms under reduced motion or `data-ui-motion="off"`):
84
+
85
+ | Token | Value | Use |
86
+ |---|---|---|
87
+ | `--ui-motion-sheet-in` / `-out` | 280 / 200ms | bottom sheet |
88
+ | `--ui-motion-sheet-ease` | `cubic-bezier(0.32, 0.72, 0, 1)` | sheet, push |
89
+ | `--ui-motion-drawer-in` / `-out` | 240 / 180ms | side drawer |
90
+ | `--ui-motion-push` / `-pop` | 280 / 240ms | push navigation |
91
+ | `--ui-motion-spring-back` | 180ms | a sheet released above its close threshold |
92
+ | `--ui-motion-press` | 150ms | pressed tint fading out (it shows at once) |
93
+
94
+ ### Base guards (`preflight.css`)
95
+
96
+ These rules are in the base layer at zero specificity (`:where()`), so any
97
+ component rule overrides them.
98
+
99
+ - No blanket `min-width: 0`. The spec asked for one, but the 1280 gallery
100
+ diff showed it squeezing nowrap rows (Space "wrap off" went one letter per
101
+ line) and a fixed-size brand icon on the desktop. A component whose flex
102
+ child must shrink sets `min-w-0` on it instead (a later change handles the remaining
103
+ width offenders).
104
+ - `overflow-wrap: break-word` on the app root. A word longer than its line
105
+ breaks. We use `break-word`, not `anywhere`, because it only breaks text
106
+ that would overflow and never changes how wide a table column or flex item
107
+ wants to be. Desktop layouts stay the same.
108
+ - `img`, `video` and `iframe` get `max-width: 100%`, and `img` and `video` also
109
+ get `height: auto`. `svg` is left alone so icons keep their size.
110
+ - Don't write `100vw` in components. Use `100%` or the viewport variables from
111
+ the viewport layer. The `check:mobile` script enforces this.
112
+
113
+ ### Tap feedback (`touch.css`)
114
+
115
+ - Buttons, links, labels and every element with a button, tab, option,
116
+ menuitem, switch, checkbox, radio or treeitem role get
117
+ `-webkit-tap-highlight-color: transparent` and `touch-action: manipulation`.
118
+ This removes the grey flash and the 300ms double-tap wait. Pinch zoom still
119
+ works. A component's own `touch-none` overrides it.
120
+ - Pressed tint on a coarse pointer: an inset `--ui-pressed` shadow appears
121
+ while the finger is down and fades over `--ui-motion-press`. Button and
122
+ IconButton draw their own pressed state (`data-variant`), so they're
123
+ skipped. A component that sets its own shadow or `active:` style also keeps
124
+ its own.
125
+ - Hover: every hand-written `:hover` rule in the theme sits inside
126
+ `@media (hover: hover)`, so a tap leaves no stuck hover look. Tailwind's
127
+ `hover:` utilities already do this.
128
+
129
+ ### Hit area (`hit-area.css`)
130
+
131
+ On a coarse pointer, a control smaller than `--ui-hit-min` keeps its drawn
132
+ size, and an invisible `::before` grows its tap box evenly on all sides to the
133
+ minimum:
134
+
135
+ ```css
136
+ inset-block: min(0px, calc((100% - var(--ui-hit-min)) / 2));
137
+ inset-inline: min(0px, calc((100% - var(--ui-hit-min)) / 2));
138
+ ```
139
+
140
+ These controls get the hit area:
141
+
142
+ - **Marked with `data-ui-hit`:** `Button`, `IconButton`
143
+ - **Matched by role or attribute:** `button[role=checkbox]` (Checkbox),
144
+ `button[role=radio]` (Radio, ToggleButton), `button[role=switch]` (Switch),
145
+ `[data-tag][role=button]` (a tappable Tag), `[data-tag-remove]`,
146
+ `[data-ui-password-toggle]`
147
+
148
+ A new tappable control can opt in by adding `data-ui-hit`. When two tap boxes
149
+ overlap, the later control wins the overlap. Clusters keep `--ui-tap-gap`
150
+ between controls. The rule sits in the base layer, so a component that sets
151
+ its own `position` or draws its own `::before` keeps it, and loses the larger
152
+ tap box.
153
+
154
+ ### Not done in this layer
155
+
156
+ - **Hardcoded font sizes:** components that write a literal size, such as
157
+ `text-[13px]` in Checkbox, Switch, Radio, Input and the menu rows, don't
158
+ pick up the 15px phone text until they read `--ui-font-size`. Their owners
159
+ (later changes) switch them over. Fields are already 16px on touch.
160
+ - **Canvas grid:** the grid draws text at fixed sizes from
161
+ `src/grid-core/theme.ts`. Reading `--ui-grid-font-size` needs a change in
162
+ the grid.
163
+ - **Form gap:** `Form` writes its own `--ui-form-gap` inline, from
164
+ `--ui-field-gap`. The 16px phone form gap belongs to Form.
165
+ - **Hover rules in `adaptive.css`:** the brand action, sidebar collapse and
166
+ resize handles still use plain `:hover`. They belong to the shell tasks.
167
+
168
+ ## Floating layer
169
+
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.
171
+
172
+ | Setting | Value |
173
+ |---|---|
174
+ | Boundary | the app window in app scope (`useCollisionBoundary`), else the visual viewport (Reka's root boundary for fixed popups) |
175
+ | Padding | 8px from each edge, plus `--ui-safe-*` (or `env(safe-area-inset-*)`), plus `--ui-kb-inset` at the bottom. Read when the popup opens and again on a visual-viewport resize |
176
+ | Shift | `sticky: 'partial'` |
177
+ | Detached anchor | `hideWhenDetached`: the popup hides once its anchor has left the boundary |
178
+ | Max size | `FLOATING_SIZE_CLASS`: `--reka-popper-available-width` and `-height`. Never `100vw` |
179
+ | Autofocus | none on a coarse pointer (`skipAutoFocusOnTouch` in `overlay.ts`) |
180
+
181
+ ```vue
182
+ <script setup lang="ts">
183
+ import { FLOATING_SIZE_CLASS, useFloating } from '@/components/primitives/popover/floating'
184
+ const floating = useFloating(() => trigger.value?.$el, () => open.value)
185
+ </script>
186
+ <SomeContent v-bind="floating" :class="[ownClasses, FLOATING_SIZE_CLASS]" />
187
+ ```
188
+
189
+ **Portal layer.** `src/theme/floating.css` makes the AppRoot `[data-ui-portals]` layer `position: absolute; inset: 0; overflow: clip; pointer-events: none`. It covers the app root exactly, so app-local dialogs keep the app as their containing block, and nothing in it can add page width or height. Its direct children take presses again; Reka's inline `pointer-events` still wins while a modal surface is open.
190
+
191
+ **Tooltip on touch.** `openOnTap` defaults to on with a coarse pointer: a tap toggles the tooltip and a tap anywhere else closes it. Pass `:open-on-tap="false"` when the tap must only run the trigger's action.
192
+
193
+ **Uses it today:** Popover, Tooltip, OverflowTooltip and all anchored popups listed below.
194
+
195
+ ## All anchored popups
196
+
197
+ Menus (including nested menus and Breadcrumb), Select and the shared ComboboxField,
198
+ DatePicker, ColorPicker, InputSteppers presets, InfoTip, ValidationPopup,
199
+ MentionList and OverflowToolbar now use the floating preset above. SplitButton,
200
+ MultipleSelect and SuperboxSelect inherit it through their shared primitives.
201
+ Menu sublevels inherit the parent's live padding, so an open submenu follows
202
+ safe-area and keyboard changes. Preferred and trigger widths are capped by
203
+ `--reka-popper-available-width`; lists scroll within the available height.
204
+ Opening a searchable Select, calendar or palette on touch does not autofocus
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.
207
+
208
+ ## Toast, notification and hover-only controls
209
+
210
+ `usePhone()` (`src/composables/phone.ts`) is the phone signal in script: the
211
+ enclosing AppRoot is narrow and the pointer is coarse, the same test as the
212
+ phone tokens. A provider sets `data-ui-phone` from it, and the placement rules
213
+ are in `src/feedback/feedback.css`.
214
+
215
+ | | Desktop, tablet, narrow window with a mouse | Phone |
216
+ |---|---|---|
217
+ | Toast | bottom right, 360px, swipe right, a stack | bottom, 8px from each side, above the visible tab bar or the keyboard (`--ui-kb-inset`), whichever is higher, plus `--ui-safe-bottom` and 8px; swipe down; only the newest shows, with a `+N` count of the others still open |
218
+ | Notification | the chosen corner, 360px | top, 8px from each side, below `--ui-safe-top` plus 8px; `alignment` is ignored |
219
+
220
+ - Toasts behind the front one keep their own timers, so a short one can
221
+ expire unseen. Danger toasts stay until closed and come forward in turn.
222
+ - Toast and notification text reads `--ui-font-size` and
223
+ `--ui-font-size-sm` (13/12 on a desktop, 15/13 on a phone). A static
224
+ Notification card no longer has a 320px minimum width.
225
+ - Neither provider writes `100vw` any more.
226
+
227
+ **Hover-only controls.** VirtualCardGrid card actions, the ScheduleTable edit
228
+ pen and the TypoHeader anchor carry `[@media(hover:none)]:opacity-100`, so a
229
+ touch screen shows them at rest. A component that hides a control until hover
230
+ does the same.
231
+
232
+ **Card long press.** On touch, holding a VirtualCardGrid card for 250ms opens
233
+ the card's context menu (`menuItems`) through Reka's long press
234
+ (`ContextMenu` `pressOpenDelay`). The click that ends that press doesn't open
235
+ the card. A card without entries opens no menu. The menu is the plain
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.
@@ -0,0 +1,28 @@
1
+ # Release checks
2
+
3
+ `npm run gates` runs basic release checks. `npm run gates:full` adds browser,
4
+ workbench and registry consumer integrations only when explicitly requested.
5
+ `SPL_FULL_GATE=1` aliases `--full`. `npm run gates -- --plan` prints the exact
6
+ selection without running checks. `pnpm check` and `pnpm check:fast` run basic
7
+ checks through the optional build-isolation wrapper.
8
+
9
+ | Entry point | Default | Explicit full |
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 browser checks, Histoire build, exact-tarball registry consumer smoke |
12
+ | `pnpm check`, `check:fast` | Basic, with optional Docker host build isolation | `SPL_FULL_GATE=1` selects the same full plan |
13
+ | `fast-ci.yml`, composite verify action | Basic | No automatic extended checks |
14
+ | Disabled CI/release workflow templates | Basic, version/tag/ancestry checks, stage, pack and actual archive audit | No implicit browser or registry consumer smoke |
15
+ | `scripts/stage-release.mjs`, `release:stage` | Validate existing dist and copy allowlisted publication inputs | No gate invocation |
16
+ | `scripts/ci-release.mjs preflight/audit/dry-run` | Release metadata, archive checks or credential-free npm validation | No browser/workbench/consumer integration |
17
+ | `scripts/ci-release.mjs smoke`, `release:smoke` | Explicit manual consumer integration | Included in full; release:smoke requires full opt-in |
18
+ | `prepack`, `prepublishOnly` | No hooks defined | No hidden gate invocation |
19
+
20
+ Stage the package only after a successful build/basic check. Preserve version,
21
+ ancestry, immutable-publication, metadata and credential boundaries. Registry
22
+ availability checks needed to publish are distinct from optional consumer tests.
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.
@@ -46,14 +46,26 @@ here stay implementation details and can change in any release.
46
46
  | `data-ui-brand-action` | native button around the default brand when `AppLayout` has a `brand` listener | present |
47
47
  | `data-ui-sidebar-body` | wrapper of the `sidebar` slot | present |
48
48
  | `data-ui-sidebar-footer` | wrapper of the `sidebar-footer` slot | present |
49
- | `data-ui-sidebar-collapse` | the web rail's collapse button | present |
49
+ | `data-ui-sidebar-collapse` | the web sidebar's collapse button, at the end of its head row | present |
50
+ | `data-ui-layout-aside` | the right side region holding the `aside` slot | present; `data-ui-aside-state` is `open`, `collapsed`, `expanded` |
51
+ | `data-ui-aside` | `AppLayout` root, when it has an `aside` slot | `open`, `collapsed`, `expanded` |
52
+ | `data-ui-aside-overlay` | `AppLayout` root | `true` while the open aside floats over the page |
53
+ | `data-ui-aside-resize` | the aside's resize handle | present |
54
+ | `data-ui-aside-resizing` | `AppLayout` root | `true` while the aside's handle is dragged |
55
+ | `data-ui-side-moving` | `AppLayout` root | `true` while the sidebar or the aside changes width |
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`; on a phone the `BottomSheet` dialog) | `data-ui-aside-state` is `open`, `collapsed`, `expanded` |
58
+ | `data-ui-aside-rail` / `data-ui-aside-open` | the 48px rail and its button | present |
59
+ | `data-ui-aside-fab` | the phone button | present |
60
+ | `data-ui-aside-collapse` / `data-ui-aside-expand` / `data-ui-aside-restore` | header buttons | present |
61
+ | `data-ui-aside-body` / `data-ui-aside-composer` | the thread scroller and the composer | present |
50
62
  | `data-ui-mobile-navigation` | the phone bar above the page | present |
51
63
  | `data-ui-mobile-menu` | the phone bar's menu (hamburger) button | present |
52
64
  | `data-ui-mobile-sidebar-header` | the drawer's header row | present |
53
65
  | `data-ui-mobile-backdrop` | the drawer's backdrop | present |
54
66
  | `data-ui-app-toolbar` | `AppToolbar` | present |
55
67
  | `data-ui-page-header` | `PageHeader` | present |
56
- | `data-ui-page-header-meta`, `data-ui-page-header-actions` | `PageHeader`'s meta and actions areas | present |
68
+ | `data-ui-page-header-meta`, `data-ui-page-header-actions`, `data-ui-page-header-description` | `PageHeader`'s meta, actions and description | present |
57
69
  | `data-page-surface` | `PageContent` | `auto`, `card`, `flat`, `none` |
58
70
  | `data-ui-motion` | set by the app on any ancestor (an `AppRoot`, `<html>`) | `off`: the library's motion is instant inside it, as under reduced motion ([Motion](#motion)) |
59
71
 
@@ -95,6 +107,7 @@ here stay implementation details and can change in any release.
95
107
  | `data-menu-description` | a `DropdownMenu` entry's second line | present |
96
108
  | `data-ui-segment-count` | a `SegmentItem`'s count pill | present |
97
109
  | `data-ui-segment-control` | `SegmentControl` | `segment`, `sheet` |
110
+ | `data-ui-segment-size` | `SegmentControl` with `size="sm"` | `sm` |
98
111
  | `data-ui-segment-moving` | `SegmentControl` while its selection slides (`.ui-segment-thumb` is shown, the new item's own fill waits) | present |
99
112
  | `data-ui-swap` | `MotionSwap` and `AppLayout`'s `main` | present |
100
113
  | `data-ui-swapped` | the same, after the first change: plays the rise-in | present |
@@ -103,7 +116,7 @@ here stay implementation details and can change in any release.
103
116
  | `data-ui-stat-tile` | `StatTile` (`data-tone`; a filter tile also `data-state` `on` / `off`) | present |
104
117
  | `data-ui-stat-tile-label`, `-value`, `-unit`, `-note` | `StatTile` parts | present |
105
118
  | `data-ui-stat-tile-group` | `StatTileGroup` | present |
106
- | `data-ui-page-toolbar`, `-start`, `-actions` | `PageToolbar` and its two sides | present |
119
+ | `data-ui-page-toolbar`, `-start`, `-actions` | `PageToolbar` and its two sides | present; the start side carries `data-overflow-start` and `data-overflow-end` while it continues past an edge |
107
120
  | `data-ui-summary-card` | `SummaryCard` (`data-state` `loading`, `error`, `ready`; `data-wide`) | present |
108
121
  | `data-ui-summary-card-title`, `-state`, `-figures`, `-figure`, `-body`, `-note`, `-error` | `SummaryCard` parts | present |
109
122
  | `data-ui-skeleton` | `SectionSkeleton` | `lines`, `figures`, `rows` (parts `data-ui-skeleton-line`, `-figure`, `-row`) |
@@ -188,14 +201,22 @@ does not animate its rows.
188
201
 
189
202
  | Token | Value | Use |
190
203
  | --- | --- | --- |
191
- | `--ui-motion-fast` | 120ms | colour and state changes, the tab notch and segment fill sliding, menus opening, anything closing |
192
- | `--ui-motion-normal` | 180ms | dialogs opening, a new row, a tab panel, switched content and pages, the loading mask |
204
+ | `--ui-motion-fast` | 150ms | colour and state changes, the tab notch and segment fill sliding, menus opening, a dialog closing |
205
+ | `--ui-motion-normal` | 200ms | dialogs opening, a new row, a tab panel, switched content and pages, the loading mask, a card's hover shadow |
193
206
  | `--ui-motion-slow` | 240ms | height, notifications and toasts arriving, a sheet sliding in |
207
+ | `--ui-motion-panel` | 300ms | a side region: the web sidebar and its rail, an aside, a record panel |
208
+ | `--ui-motion-exit` | 100ms | a popup, menu or tooltip closing |
209
+ | `--ui-motion-stack` | 200ms (`--ui-motion-stack-ease`) | a toast stack closing up |
210
+ | `--ui-motion-flash` | 1600ms | the tint on something changed elsewhere |
211
+ | `--ui-skeleton-pulse` | 1200ms (`--ui-skeleton-pulse-ease`) | one skeleton pulse |
212
+ | `--ui-motion-drag` | 200ms, `cubic-bezier(0.2, 0, 0, 1)` (`--ui-motion-drag-ease`) | siblings making room while dragging |
213
+ | `--ui-motion-drag-settle` | 260ms, `cubic-bezier(0.2, 1, 0.1, 1)` (`--ui-motion-drag-settle-ease`) | a dropped item settling |
214
+ | `--ui-motion-ease` | `cubic-bezier(0.4, 0, 0.2, 1)` | colour and hover fades |
194
215
  | `--ui-motion-ease-out` | `cubic-bezier(0.22, 1, 0.36, 1)` | arriving |
195
216
  | `--ui-motion-ease-in` | `cubic-bezier(0.4, 0, 1, 1)` | leaving |
196
- | `--ui-motion-ease-in-out` | `cubic-bezier(0.4, 0, 0.2, 1)` | height between two resting states |
217
+ | `--ui-motion-ease-in-out` | `cubic-bezier(0.4, 0, 0.2, 1)` | height or width between two resting states |
197
218
 
198
- Under `prefers-reduced-motion: reduce` all of it is instant. To turn it off
219
+ Under `prefers-reduced-motion: reduce` all of it is instant, and every duration token reads 0ms, so script that reads them waits for nothing either. To turn it off
199
220
  for part of a page, or the whole App, set `data-ui-motion="off"` on any
200
221
  ancestor:
201
222