@synopackageland/ui 0.20.3 → 0.20.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/LICENSES/ux-foundation.md +83 -0
- package/LICENSES/ux-grid-baserow.md +35 -0
- package/LICENSES/ux-grid-teable.md +48 -0
- package/README.md +3 -0
- package/dist/cli.js +0 -0
- package/dist/components/app/app-layout/AppLayout.vue.d.ts +31 -4
- package/dist/components/app/app-root/AppRoot.vue.d.ts +9 -0
- package/dist/components/app/aside-panel/AsidePanel.vue.d.ts +69 -0
- package/dist/components/app/aside-panel/asidePreference.d.ts +28 -0
- package/dist/components/composite/adaptive-panel/AdaptivePanel.vue.d.ts +61 -0
- package/dist/components/composite/connection-topology/ConnectionTopology.vue.d.ts +1 -1
- package/dist/components/composite/file-picker/FilePicker.vue.d.ts +1 -1
- package/dist/components/composite/mention-list/MentionList.vue.d.ts +2 -2
- package/dist/components/composite/overflow-toolbar/OverflowToolbar.vue.d.ts +2 -2
- package/dist/components/composite/record-panel/RecordPanel.vue.d.ts +77 -0
- package/dist/components/composite/record-panel/recordPanelWidth.d.ts +54 -0
- package/dist/components/composite/summary-card/SummaryCard.vue.d.ts +1 -1
- package/dist/components/composite/tag-editor/TagEditor.vue.d.ts +2 -2
- package/dist/components/composite/tree-view/TreeView.vue.d.ts +1 -1
- package/dist/components/composite/wizard/Wizard.vue.d.ts +2 -2
- package/dist/components/composite/wizard/WizardDialog.vue.d.ts +2 -2
- package/dist/components/containers/card/Card.vue.d.ts +1 -1
- package/dist/components/containers/card/CardField.vue.d.ts +26 -0
- package/dist/components/containers/card-grid/CardCover.vue.d.ts +16 -0
- package/dist/components/containers/card-grid/CardGrid.vue.d.ts +1 -1
- package/dist/components/containers/card-grid/VirtualCardGrid.vue.d.ts +71 -0
- package/dist/components/containers/expandable-item/ExpandableItem.vue.d.ts +1 -1
- package/dist/components/containers/fieldset/Fieldset.vue.d.ts +1 -1
- package/dist/components/containers/list-detail/ListDetail.vue.d.ts +1 -1
- package/dist/components/containers/split-pane/SplitPane.vue.d.ts +1 -1
- package/dist/components/feedback/notification/Notification.vue.d.ts +1 -1
- package/dist/components/feedback/notification/NotificationProvider.vue.d.ts +1 -1
- package/dist/components/primitives/breadcrumb/Breadcrumb.vue.d.ts +2 -2
- package/dist/components/primitives/context-menu/ContextMenu.vue.d.ts +3 -0
- package/dist/components/primitives/context-menu/ContextMenuItems.vue.d.ts +4 -5
- package/dist/components/primitives/dialog/Dialog.vue.d.ts +2 -2
- package/dist/components/primitives/dropdown-menu/DropdownMenu.vue.d.ts +4 -0
- package/dist/components/primitives/dropdown-menu/DropdownMenuItems.vue.d.ts +6 -3
- package/dist/components/primitives/popover/Popover.vue.d.ts +10 -2
- package/dist/components/primitives/popover/floating.d.ts +27 -0
- package/dist/components/primitives/popover/overlay.d.ts +36 -0
- package/dist/components/primitives/segment-control/SegmentControl.vue.d.ts +4 -1
- package/dist/components/primitives/segment-control/context.d.ts +3 -0
- package/dist/components/primitives/select/Select.vue.d.ts +4 -0
- package/dist/components/primitives/select/internal/ComboboxField.vue.d.ts +1 -0
- package/dist/components/primitives/select/internal/types.d.ts +2 -0
- package/dist/components/primitives/skeleton/SectionSkeleton.vue.d.ts +25 -4
- package/dist/components/primitives/skeleton/Skeleton.vue.d.ts +3 -0
- package/dist/components/primitives/tag/Tag.vue.d.ts +2 -2
- package/dist/components/primitives/toast/ToastProvider.vue.d.ts +0 -2
- package/dist/components/primitives/toast/toast.d.ts +21 -1
- package/dist/components/primitives/tooltip/Tooltip.vue.d.ts +8 -3
- package/dist/components/primitives/tooltip/tooltipStyle.d.ts +11 -1
- package/dist/composables/backDismiss.d.ts +19 -0
- package/dist/composables/phone.d.ts +8 -0
- package/dist/composables/softKeyboard.d.ts +26 -4
- package/dist/composables/visualViewport.d.ts +31 -0
- package/dist/dnd/autoScroll.d.ts +57 -0
- package/dist/dnd/dragKit.d.ts +111 -0
- package/dist/dnd/index.d.ts +4 -0
- package/dist/dnd/layout.d.ts +57 -0
- package/dist/dnd/vue.d.ts +16 -0
- package/dist/feedback/StatusSlot.vue.d.ts +24 -0
- package/dist/feedback/showAfter.d.ts +15 -0
- package/dist/feedback/stackMotion.d.ts +13 -0
- package/dist/feedback/timing.d.ts +23 -0
- package/dist/grid/cells/draw.d.ts +57 -0
- package/dist/grid/cells/images.d.ts +5 -0
- package/dist/grid/cells/index.d.ts +7 -0
- package/dist/grid/cells/number.d.ts +5 -0
- package/dist/grid/cells/registry.d.ts +56 -0
- package/dist/grid/cells/renderers.d.ts +8 -0
- package/dist/grid/cells/status.d.ts +6 -0
- package/dist/grid/cells/types.d.ts +200 -0
- package/dist/grid/cells/use.d.ts +7 -0
- package/dist/grid/clipboard/copy.d.ts +17 -0
- package/dist/grid/clipboard/index.d.ts +4 -0
- package/dist/grid/clipboard/proposal.d.ts +35 -0
- package/dist/grid/clipboard/tsv.d.ts +14 -0
- package/dist/grid/clipboard/write.d.ts +21 -0
- package/dist/grid/contracts.d.ts +200 -0
- package/dist/grid/edit/cell-status.d.ts +33 -0
- package/dist/grid/edit/edit-session.d.ts +97 -0
- package/dist/grid/edit/use-grid-edit.d.ts +48 -0
- package/dist/grid/focus/editor-style.d.ts +67 -0
- package/dist/grid/host/GridHost.vue.d.ts +137 -0
- package/dist/grid/host/controller.d.ts +219 -0
- package/dist/grid/host/dom.d.ts +10 -0
- package/dist/grid/index.d.ts +8 -0
- package/dist/grid/range/block.d.ts +30 -0
- package/dist/grid/range/fill.d.ts +27 -0
- package/dist/grid/range/index.d.ts +2 -0
- package/dist/grid/theme.d.ts +61 -0
- package/dist/grid-core/auto-scroll.d.ts +67 -0
- package/dist/grid-core/column-resize.d.ts +43 -0
- package/dist/grid-core/config.d.ts +39 -0
- package/dist/grid-core/coordinate.d.ts +64 -0
- package/dist/grid-core/drag.d.ts +46 -0
- package/dist/grid-core/freeze.d.ts +11 -0
- package/dist/grid-core/index.d.ts +17 -0
- package/dist/grid-core/keyboard.d.ts +34 -0
- package/dist/grid-core/keys.d.ts +38 -0
- package/dist/grid-core/lru.d.ts +10 -0
- package/dist/grid-core/range.d.ts +28 -0
- package/dist/grid-core/region.d.ts +73 -0
- package/dist/grid-core/renderer/base.d.ts +92 -0
- package/dist/grid-core/renderer/layout.d.ts +129 -0
- package/dist/grid-core/selection-controller.d.ts +62 -0
- package/dist/grid-core/selection.d.ts +35 -0
- package/dist/grid-core/theme.d.ts +81 -0
- package/dist/grid-core/types.d.ts +178 -0
- package/dist/grid.js +6173 -0
- package/dist/i18n/text.d.ts +6 -0
- package/dist/index.d.ts +32 -2
- package/dist/index.js +17647 -13630
- package/dist/interaction/editSession.d.ts +49 -0
- package/dist/interaction/focus.d.ts +28 -0
- package/dist/interaction/gesture.d.ts +49 -0
- package/dist/interaction/ime.d.ts +55 -0
- package/dist/interaction/index.d.ts +6 -0
- package/dist/interaction/outside.d.ts +40 -0
- package/dist/interaction/vue.d.ts +21 -0
- package/dist/json-ai-prompt.md +1 -1
- package/dist/json-preview/preview.js +8523 -7814
- package/dist/json.js +10 -10
- package/dist/lib/adaptiveLayout.d.ts +32 -2
- package/dist/lib/deviceViewport.d.ts +2 -1
- package/dist/lib/motion.d.ts +12 -0
- package/dist/reka-runtime.json +1 -1
- package/dist/theme.css +1 -1
- package/dist/themes/dsm7-embedded-theme.css +1 -1
- package/dist/themes/dsm7-embedded.css +1 -1
- package/dist/themes/dsm7.css +1 -1
- package/dist/virtual/cardHeight.d.ts +39 -0
- package/dist/virtual/galleryColumns.d.ts +11 -0
- package/dist/virtual/layoutMotion.d.ts +30 -0
- package/docs/adaptive-layout.md +137 -16
- package/docs/callout.md +22 -1
- package/docs/list-detail.md +54 -0
- package/docs/mobile-native.md +214 -0
- package/docs/release-checks.md +23 -0
- package/docs/theme-hooks.md +28 -7
- package/docs/ux-foundation.md +133 -0
- package/docs/ux-grid.md +217 -0
- package/package.json +94 -3
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** Card box metrics in px (layout-spec §2.6). VirtualCardGrid and CardField draw with these. */
|
|
2
|
+
export declare const CARD_METRICS: {
|
|
3
|
+
readonly border: 1;
|
|
4
|
+
readonly padBlock: 8;
|
|
5
|
+
readonly padInline: 12;
|
|
6
|
+
/** One title line (14/20). */
|
|
7
|
+
readonly titleLine: 20;
|
|
8
|
+
/** Space under the title, and between fields. */
|
|
9
|
+
readonly gap: 8;
|
|
10
|
+
/** Field name row: 16 px icon, 12 px muted text. */
|
|
11
|
+
readonly label: 16;
|
|
12
|
+
/** Between the name row and the value row. */
|
|
13
|
+
readonly labelGap: 4;
|
|
14
|
+
};
|
|
15
|
+
/** Value-row heights in px for the common field kinds (interaction-spec §5.1). */
|
|
16
|
+
export declare const CARD_VALUE_HEIGHT: {
|
|
17
|
+
/** One line of 13/20 text, a checkbox or a 20 px select chip. */
|
|
18
|
+
readonly line: 20;
|
|
19
|
+
/** Long text, clamped to two lines. */
|
|
20
|
+
readonly longText: 40;
|
|
21
|
+
/** A 24 px link chip. */
|
|
22
|
+
readonly chip: 24;
|
|
23
|
+
/** A row of 28 px attachment thumbnails. */
|
|
24
|
+
readonly attachment: 28;
|
|
25
|
+
};
|
|
26
|
+
export type CardHeightInput = {
|
|
27
|
+
/** Value-row height of each visible field in order, in px; see CARD_VALUE_HEIGHT. */
|
|
28
|
+
fields: readonly number[];
|
|
29
|
+
/** Cover box height in px; 0 when the view shows no cover. */
|
|
30
|
+
cover?: number;
|
|
31
|
+
/** Whether the card shows its title row. */
|
|
32
|
+
title?: boolean;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* The height of every card in a view, computed from its configuration so the
|
|
36
|
+
* grid knows its rows before any record or image arrives. Each field is a name
|
|
37
|
+
* row, a value row and the gap after it; the last gap is not counted.
|
|
38
|
+
*/
|
|
39
|
+
export declare function cardHeight({ fields, cover, title }: CardHeightInput): number;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Container width (px) at which the gallery gains its 2nd…6th column (layout-spec §2.6). */
|
|
2
|
+
export declare const GALLERY_BREAKPOINTS: readonly [640, 768, 1024, 1280, 1536];
|
|
3
|
+
/** Gallery columns for a container `width` px wide; 0 while the width is unknown. */
|
|
4
|
+
export declare function galleryColumns(width: number): number;
|
|
5
|
+
/**
|
|
6
|
+
* Item indices from `startRow` to `endRow` (inclusive) in a grid of `columns`,
|
|
7
|
+
* clipped to `count`: `[start, end)`.
|
|
8
|
+
*/
|
|
9
|
+
export declare function rowItems(startRow: number, endRow: number, columns: number, count: number): [number, number];
|
|
10
|
+
/** The index a grid arrow, Home/End or Page key moves focus to from `index`; null for other keys. */
|
|
11
|
+
export declare function cardKeyTarget(key: string, index: number, columns: number, count: number, pageRows: number, ctrl?: boolean): number | null;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Ref } from 'vue';
|
|
2
|
+
/**
|
|
3
|
+
* Sanctioned layout motion: a side region (AI panel, sidebar rail) is changing
|
|
4
|
+
* the width of the main area on purpose. Width-dependent layouts inside main,
|
|
5
|
+
* such as the gallery column count, hold still while it is active and settle
|
|
6
|
+
* once when it ends (layout-spec §3.3).
|
|
7
|
+
*/
|
|
8
|
+
export type LayoutMotion = {
|
|
9
|
+
/** True from the first begin until the last motion has ended. */
|
|
10
|
+
readonly active: Readonly<Ref<boolean>>;
|
|
11
|
+
/**
|
|
12
|
+
* Starts a motion and returns its end. Calling the end again does nothing,
|
|
13
|
+
* so an interrupted motion may end from several paths. Under reduced motion
|
|
14
|
+
* nothing starts.
|
|
15
|
+
*/
|
|
16
|
+
begin: (el?: Element | null) => () => void;
|
|
17
|
+
/**
|
|
18
|
+
* Begins a motion for the CSS transition `el` is about to run and ends it on
|
|
19
|
+
* `transitionend`, on a `transitioncancel` that no new transition replaces,
|
|
20
|
+
* or after the transition's own duration when neither event arrives. Calling
|
|
21
|
+
* it again for the same element while it moves restarts the wait instead of
|
|
22
|
+
* adding a second motion. Call it right after changing the property.
|
|
23
|
+
*/
|
|
24
|
+
track: (el: Element) => void;
|
|
25
|
+
};
|
|
26
|
+
export declare function createLayoutMotion(): LayoutMotion;
|
|
27
|
+
/** Gives the components below a motion of their own, for a layout that only moves its own region. */
|
|
28
|
+
export declare function provideLayoutMotion(motion?: LayoutMotion): LayoutMotion;
|
|
29
|
+
/** The nearest provided motion, else the document-wide one. */
|
|
30
|
+
export declare function useLayoutMotion(): LayoutMotion;
|
package/docs/adaptive-layout.md
CHANGED
|
@@ -6,8 +6,8 @@ The same AppRoot + AppLayout supports three presentations:
|
|
|
6
6
|
| AppLayout container width | Presentation | Layout |
|
|
7
7
|
| --- | --- | --- |
|
|
8
8
|
| 1–640px | `mobile` | Slide-out accessible navigation, tight page gutters, wrapped header actions, narrow-table cards |
|
|
9
|
-
| 641–
|
|
10
|
-
|
|
|
9
|
+
| 641–959px (any width above 640 without `wide-layout="web"`) | `window` | Original dense DSM window interior, same as before |
|
|
10
|
+
| 960px or wider, with `wide-layout="web"` | `web` | Full page: 48px chrome rows, collapsible and resizable sidebar (it starts as the 48px rail at 960–1199), page content flush beside the sidebar at DSM window padding (no page card), lifted cards on flat pages |
|
|
11
11
|
|
|
12
12
|
A maximized DSM window is still a DSM window: without `wide-layout="web"`,
|
|
13
13
|
AppLayout stays in `window` mode at any width above 640px. Only a full-page
|
|
@@ -41,15 +41,21 @@ scales it down, and AppLayout never sees a phone width. When the app owns the
|
|
|
41
41
|
whole page, call `ensureDeviceViewport()` once and opt into `web`; leave both
|
|
42
42
|
off inside a DSM desktop window:
|
|
43
43
|
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const restoreViewport = fullPage ? ensureDeviceViewport() : () => {}
|
|
50
|
-
// <AppLayout :wide-layout="fullPage ? 'web' : 'window'" sidebar-storage-key="myapp">
|
|
44
|
+
```vue
|
|
45
|
+
<!-- window.js passes whether DSM launched the AppWindow standalone
|
|
46
|
+
(appWindow.isStandaloneMainWindow?.() or _S('standalone')). -->
|
|
47
|
+
<AppRoot :full-page="fullPage">
|
|
48
|
+
<AppLayout :wide-layout="fullPage ? 'web' : 'window'" sidebar-storage-key="myapp">
|
|
51
49
|
```
|
|
52
50
|
|
|
51
|
+
`AppRoot full-page` calls `ensureDeviceViewport()` itself and restores the
|
|
52
|
+
page on unmount; a host without an `AppRoot` can call it directly. The
|
|
53
|
+
viewport it writes is
|
|
54
|
+
`width=device-width, initial-scale=1, viewport-fit=cover, interactive-widget=resizes-content`
|
|
55
|
+
(a device-width viewport already on the page keeps its keys and gains only the
|
|
56
|
+
missing two). Zoom is never locked; fields avoid iOS focus zoom by their 16px
|
|
57
|
+
text instead.
|
|
58
|
+
|
|
53
59
|
A standalone AppWindow should also drop its desktop `minWidth` (for example
|
|
54
60
|
`minWidth: fullPage ? 320 : 700`) so a phone-width page can reach mobile mode.
|
|
55
61
|
|
|
@@ -59,12 +65,66 @@ for a viewport change. `ensureDeviceViewport()` therefore fires one `resize`
|
|
|
59
65
|
on the next frame after it changes the viewport (and after it restores it), so
|
|
60
66
|
the window narrows to the phone's width.
|
|
61
67
|
|
|
68
|
+
## Viewport, keyboard and Back on a phone
|
|
69
|
+
|
|
70
|
+
A full-page `AppRoot` is the page: it is `100dvh` tall, `html` and `body`
|
|
71
|
+
never scroll and have no overscroll (so no pull-to-reload), and only scrollers
|
|
72
|
+
inside the app scroll. There is no document scroll for iOS Safari to push up
|
|
73
|
+
behind the keyboard. Only use `full-page` when nothing else shares the page
|
|
74
|
+
(a Portal that hides DSM's banner, or a standalone launch).
|
|
75
|
+
|
|
76
|
+
Every `AppRoot`, full page or not, publishes the visual viewport on its root.
|
|
77
|
+
The variables are read-only and updated once per frame:
|
|
78
|
+
|
|
79
|
+
| Variable | Value |
|
|
80
|
+
|---|---|
|
|
81
|
+
| `--ui-vvh` | the visual viewport height (what is on screen above the keyboard) |
|
|
82
|
+
| `--ui-vv-top` | how far iOS has panned the visual viewport down |
|
|
83
|
+
| `--ui-kb-inset` | the height the keyboard covers at the bottom of the layout: the iOS keyboard; 0 on Android, where `interactive-widget=resizes-content` shrinks the layout itself |
|
|
84
|
+
| `data-ui-keyboard="open"` | a text field has focus and the viewport lost at least 60px to the keyboard (smaller changes are the URL bar) |
|
|
85
|
+
|
|
86
|
+
Rules for surfaces:
|
|
87
|
+
|
|
88
|
+
- A bar pinned to the bottom (sticky form actions, a composer, a footer) uses
|
|
89
|
+
`bottom: var(--ui-kb-inset, 0px)`; one formula works on iOS and Android.
|
|
90
|
+
- A scroller with a sticky footer sets `--ui-scroll-footer: <footer height>`.
|
|
91
|
+
While the keyboard is open, the scroller holding the focused field gets
|
|
92
|
+
`data-ui-kb-room`: its `scroll-padding-bottom` becomes keyboard, footer and
|
|
93
|
+
16px, and on iOS it gets a spacer so the last field can scroll above the
|
|
94
|
+
keyboard. The field is scrolled into view inside that scroller, never by
|
|
95
|
+
scrolling the window. A container that aligns its own fields (the reka
|
|
96
|
+
Drawer keyboard provider) opts out with `data-ui-keyboard-reveal="off"`.
|
|
97
|
+
- Programmatic focus uses `focusWithoutScroll(element)`:
|
|
98
|
+
`focus({ preventScroll: true })`, then the same in-scroller reveal.
|
|
99
|
+
- `useSoftKeyboard()` is true at once when a text field gains focus on a touch
|
|
100
|
+
device, and stays true only if the viewport confirms a keyboard within
|
|
101
|
+
800ms; with a hardware keyboard it turns false again.
|
|
102
|
+
|
|
103
|
+
`useBackDismiss(open, close, { enabled })` gives an open phone sheet, drawer
|
|
104
|
+
or pushed page one `history.state` entry (the URL does not change; a router's
|
|
105
|
+
state keys are copied through). The Android back button or the iOS edge swipe
|
|
106
|
+
calls `close` for the top surface only; closing by code removes the entry.
|
|
107
|
+
Pass `enabled: false` where the surface is a popover or inside a DSM window.
|
|
108
|
+
|
|
109
|
+
`node scripts/check-keyboard.mjs [out-dir]` (after `pnpm build`) checks all of
|
|
110
|
+
this in Chromium at 390 and 768 with touch, with Android emulated by a
|
|
111
|
+
viewport resize and iOS by a stand-in visual viewport. A real-device pass on
|
|
112
|
+
iOS Safari and Android Chrome is still needed (spec D6).
|
|
113
|
+
|
|
62
114
|
## Web presentation sidebar
|
|
63
115
|
|
|
64
|
-
In `web`, the sidebar
|
|
65
|
-
on its right edge:
|
|
116
|
+
In `web`, the sidebar's 48px head row ends with a collapse button, and a
|
|
117
|
+
resize handle sits on its right edge:
|
|
118
|
+
|
|
119
|
+
- At 960–1199px the sidebar starts as the rail. Opening it there lasts until
|
|
120
|
+
the container leaves that band and is not stored, so the wide-screen
|
|
121
|
+
preference stays as the user left it.
|
|
122
|
+
- Only the sidebar's width moves (`--ui-motion-panel`, ease-in-out); its rows
|
|
123
|
+
keep the target width inside, so labels never re-wrap while it opens or
|
|
124
|
+
closes. Nothing moves before the first paint (the stored width and collapse
|
|
125
|
+
are read during setup) or while the handle is dragged.
|
|
66
126
|
|
|
67
|
-
- Collapsed, AppSidebar shows icons only; each label becomes a right-side
|
|
127
|
+
- Collapsed (48px rail), the head row keeps only the expand button and AppSidebar shows icons only; each label becomes a right-side
|
|
68
128
|
tooltip and stays the row's accessible name. Badges move to the icon corner.
|
|
69
129
|
- The handle (`role="separator"`) resizes between 200 and 360px (default 248px)
|
|
70
130
|
by dragging, or with Arrow Left/Right (16px), Home and End when focused.
|
|
@@ -79,14 +139,14 @@ on its right edge:
|
|
|
79
139
|
|
|
80
140
|
A full page has no DSM title bar, so AppLayout shows the app's logo and name:
|
|
81
141
|
at the top of the sidebar of a `wide-layout="web"` page at any width above a
|
|
82
|
-
phone's (
|
|
142
|
+
phone's (not in the collapsed rail, whose head row holds the expand button), and in the phone bar
|
|
83
143
|
and at the top of the drawer. A DSM window shows neither, since its title bar
|
|
84
144
|
already does.
|
|
85
145
|
|
|
86
146
|
- `brand-name` and `brand-icon` (a URL) name the app. Without them AppLayout
|
|
87
147
|
uses the page's `document.title` and `<link rel="icon">`; with none of these
|
|
88
148
|
there is no brand row. The `#brand` slot replaces the default content.
|
|
89
|
-
- The `#sidebar-footer` slot
|
|
149
|
+
- The `#sidebar-footer` slot is the sidebar's 48px foot in web and the
|
|
90
150
|
bottom of the phone drawer (not in a DSM window). `AppSidebarUser`
|
|
91
151
|
(`name`, optional `detail`) shows the signed-in account as an initial avatar;
|
|
92
152
|
collapsed, only the avatar remains, with a tooltip.
|
|
@@ -100,6 +160,67 @@ already does.
|
|
|
100
160
|
</AppLayout>
|
|
101
161
|
```
|
|
102
162
|
|
|
163
|
+
## Aside: the right side region
|
|
164
|
+
|
|
165
|
+
The `#aside` slot is a region at the right edge of the shell, full height,
|
|
166
|
+
for a work panel such as an assistant (`AsidePanel` draws the panel; the
|
|
167
|
+
layout only sizes and moves the region). `v-model:aside` is its state:
|
|
168
|
+
|
|
169
|
+
| State | Region width | Page |
|
|
170
|
+
| --- | --- | --- |
|
|
171
|
+
| `collapsed` (default) | the 48px rail | takes the rest |
|
|
172
|
+
| `open` | `v-model:aside-width` (380, 320–560; the handle on its start edge, Arrow keys, Home and End) | takes the rest |
|
|
173
|
+
| `open` that would leave the page under 480px, or outside `web` | the 48px rail; the panel floats over the page (`overlay`) | unchanged |
|
|
174
|
+
| `expanded` | the whole content area beside the sidebar | `inert`, kept mounted |
|
|
175
|
+
|
|
176
|
+
On a phone the region has no width; its content places itself (a button and a
|
|
177
|
+
bottom sheet). The slot receives `state`, `overlay`, `width` and
|
|
178
|
+
`presentation`; the region sets `--ui-aside-open-width` so the panel can keep
|
|
179
|
+
its width while the region moves. Dragging writes the width on release.
|
|
180
|
+
|
|
181
|
+
### AsidePanel
|
|
182
|
+
|
|
183
|
+
`AsidePanel` is the slot's content. It reads the state from the layout (no
|
|
184
|
+
props to forward) and writes it back through `v-model:aside`:
|
|
185
|
+
|
|
186
|
+
```vue
|
|
187
|
+
<script setup lang="ts">
|
|
188
|
+
import { AppLayout, AsidePanel, useAsidePreference } from '@synopackageland/ui'
|
|
189
|
+
// Read during setup, so the first paint has the stored state; the app picks the scope.
|
|
190
|
+
const { state, width } = useAsidePreference('studio.aside', 'editor', { state: 'open' })
|
|
191
|
+
</script>
|
|
192
|
+
|
|
193
|
+
<AppLayout v-model:aside="state" v-model:aside-width="width" wide-layout="web">
|
|
194
|
+
...
|
|
195
|
+
<template #aside>
|
|
196
|
+
<AsidePanel :title="t('IT assistant')" :icon="Sparkles" shortcut="Ctrl+J">
|
|
197
|
+
<template #tabs>…a SegmentControl size="sm", only with more than one surface…</template>
|
|
198
|
+
<template #actions>…a ⋯ menu…</template>
|
|
199
|
+
…the thread…
|
|
200
|
+
<template #composer>…the composer…</template>
|
|
201
|
+
</AsidePanel>
|
|
202
|
+
</template>
|
|
203
|
+
</AppLayout>
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
| Mode | When | Panel |
|
|
207
|
+
| --- | --- | --- |
|
|
208
|
+
| docked | `web`, the page keeps 480px | keeps `--ui-aside-open-width` and slides by `translate` while the region's width moves (`--ui-motion-panel`, ease-in-out both ways); the rail button fades in 150ms into a collapse |
|
|
209
|
+
| floating | an open panel the page has no room for, or a 641–959 container | slides over the page from the rail (in `panel` ease-out, out `normal` ease-in); the region keeps its 48px |
|
|
210
|
+
| sheet | a phone (≤640) | a 48px button at the end corner (above the tab bar and the safe area) opens a 90% bottom sheet; `expanded` is full screen; the scrim, Escape and a downward swipe on the header close it; the page is inert |
|
|
211
|
+
|
|
212
|
+
- The panel is never unmounted: collapsed, it is `inert` and `aria-hidden`, and the thread's scroll and the composer's draft stay. The page is `inert` while the panel is expanded or a sheet is up.
|
|
213
|
+
- Focus moves to the composer's first field on open (not on touch, `initial-focus` changes it) and back to the rail button or the phone button on collapse.
|
|
214
|
+
- Escape: docked and expanded leave it to the content (stop a reply). A floating panel with focus inside, or a sheet, collapses on Escape after any layer opened above it (a menu, a popover) has closed.
|
|
215
|
+
- No transition runs before the first paint has settled. The state needs no `transitionend`: only a floating panel's exit waits for one, with the `normal` token as its deadline, and a new state change or reduced motion ends it at once.
|
|
216
|
+
- Labels: "Open {name}", "Collapse {name}", "Expand {name}" and "Restore {name}" with the title, in the nine locales; `open-label`, `collapse-label`, `expand-label` and `restore-label` replace them. The app binds its shortcut and calls the exposed `toggle()`.
|
|
217
|
+
- `useAsidePreference(key, scope, defaults)` keeps `{"v":1,"<scope>":{"state","width"}}` in localStorage, written on each change (the width only when a drag ends); other fields in a scope's entry are kept. `readAsidePreference` reads it synchronously for a pre-paint script.
|
|
218
|
+
|
|
219
|
+
Both side regions share one motion signal: `inject(APP_SIDE_MOTION_KEY)` is
|
|
220
|
+
true while the sidebar or the aside changes width (the shell also carries
|
|
221
|
+
`data-ui-side-moving`). Content that measures itself (a canvas grid, a chart,
|
|
222
|
+
a column count) waits for false and then measures once.
|
|
223
|
+
|
|
103
224
|
## Usage
|
|
104
225
|
|
|
105
226
|
```vue
|
|
@@ -124,7 +245,7 @@ import {
|
|
|
124
245
|
```
|
|
125
246
|
|
|
126
247
|
- `AppLayout` defaults to `wide-layout="window"`. Set `wide-layout="web"` on
|
|
127
|
-
a full-page Portal to enable the full-page layout at
|
|
248
|
+
a full-page Portal to enable the full-page layout at 960px or wider.
|
|
128
249
|
- `AppLayout` defaults to `responsive=true`. Set `:responsive="false"`
|
|
129
250
|
only if the host application intentionally implements its own complete responsive shell.
|
|
130
251
|
- `PageContent surface="auto"` puts the page straight on the content area: flush
|
|
@@ -235,7 +356,7 @@ any ancestor. Light defaults and the dark values the workbench uses:
|
|
|
235
356
|
| `--ui-app-web-surface` | `var(--ui-surface)` | `var(--ui-surface)` (`#1f2832`) |
|
|
236
357
|
| `--ui-app-web-border` | `var(--ui-border)` | `#34404d` |
|
|
237
358
|
| `--ui-app-web-shadow` | `var(--ui-shadow-card)` | `var(--ui-shadow-card)` |
|
|
238
|
-
| `--ui-web-sidebar-collapsed-width` | `
|
|
359
|
+
| `--ui-web-sidebar-collapsed-width` | `48px` | same |
|
|
239
360
|
|
|
240
361
|
`AppLayout navigation="top"` puts the navigation in a bar above the page in the
|
|
241
362
|
window and web presentations (a phone keeps the drawer); its colours and the
|
package/docs/callout.md
CHANGED
|
@@ -6,7 +6,7 @@ has its own component, and none of them is a full-width banner.
|
|
|
6
6
|
|
|
7
7
|
| The message | Use | Shape |
|
|
8
8
|
| --- | --- | --- |
|
|
9
|
-
| A passing result: "Settings applied", "The check passed", "Copied" | `useToast()` | A toast
|
|
9
|
+
| A passing result: "Settings applied", "The check passed", "Copied" | `useToast()` | A toast at the bottom right: success fades after 3 s, info and warning after 5 s (at least 5 s with an action), an error stays until closed. Do not leave a Callout behind for it. |
|
|
10
10
|
| Something the user has to act on: a missing setting, a failed connection, a sign-in that waits | `<Callout>` | One-line title, at most one line of text, one action. |
|
|
11
11
|
| A risk or a limit of a feature | `<Callout variant="inline">` | One short sentence; longer text in `#details` behind "Learn more", or a link. |
|
|
12
12
|
| A state of the page that fits its toolbar row: "Two devices are offline", "Read only" | `<Callout variant="hint">` | The icon and a few words on one line; the details in a tooltip. |
|
|
@@ -160,6 +160,27 @@ and without an action, light and dark, and on a settings page:
|
|
|
160
160
|
toast.success('Settings applied', 'Added port 6690')
|
|
161
161
|
```
|
|
162
162
|
|
|
163
|
+
- Work in progress keeps one toast id from start to result. A pending toast
|
|
164
|
+
shows only if the work takes longer than 300 ms, has no timer, and turns
|
|
165
|
+
into its result in place; an error's Retry can turn it back into pending:
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
const id = toast.pending('Deleting')
|
|
169
|
+
try {
|
|
170
|
+
await remove()
|
|
171
|
+
toast.update(id, { title: 'Deleted', tone: 'success', actionLabel: 'Undo', onAction: restore })
|
|
172
|
+
} catch {
|
|
173
|
+
toast.update(id, { title: 'Could not delete', tone: 'danger', actionLabel: 'Retry', onAction: retry })
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
An action click closes the toast unless its handler updated it. Timers
|
|
178
|
+
pause while the pointer is over the stack or focus is in it; F8 moves
|
|
179
|
+
focus to the stack, so its actions are reachable by keyboard.
|
|
180
|
+
- A status that belongs to a place on the page (a form's "Saving" / "Saved",
|
|
181
|
+
a field's error with Retry) goes in a `StatusSlot`: a fixed box rendered
|
|
182
|
+
from the start and empty while idle, so the status never pushes content.
|
|
183
|
+
|
|
163
184
|
- Put a machine error code in `code`, not in the text.
|
|
164
185
|
- Stack at most two Callouts in one section. More means the page should show
|
|
165
186
|
the state in its content (a `Badge`, a `KeyValueRow`) instead.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# ListDetail
|
|
2
|
+
|
|
3
|
+
A list with the selected item's detail beside it, as DSM's own list pages show
|
|
4
|
+
one (a Recycle Bin page with the selected shared folder's oldest items, a
|
|
5
|
+
Packages page with the selected package's advice).
|
|
6
|
+
|
|
7
|
+
```vue
|
|
8
|
+
<ListDetail :title="selected?.name" storage-key="recycle" @close="selectedKeys = []">
|
|
9
|
+
<DataGrid v-model:selected-keys="selectedKeys" :columns="columns" :rows="rows" row-key="id" single-select footer />
|
|
10
|
+
<template v-if="selected" #detail>
|
|
11
|
+
<p>{{ selected.summary }}</p>
|
|
12
|
+
<DataGrid :fill="false" :columns="itemColumns" :rows="items" row-key="id" footer />
|
|
13
|
+
</template>
|
|
14
|
+
</ListDetail>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
- The default slot is the list; it takes the room the pane leaves and keeps at
|
|
18
|
+
least `list-min-width` (360px).
|
|
19
|
+
- The pane shows while the `detail` slot is given (a `v-if` on the slot opens
|
|
20
|
+
and closes it), or while `open` is true. With `title` it has a header with the
|
|
21
|
+
title and a Close button that emits `close`; `:closable="false"` drops the
|
|
22
|
+
button and a `header` slot replaces the title.
|
|
23
|
+
- The pane is white with a 1px border and no shadow. It is not a `Surface`, so
|
|
24
|
+
an `auto` page stays white when it opens and only the selected row is
|
|
25
|
+
tinted, even when another App built with an older release shares the DSM
|
|
26
|
+
document ([adaptive layout](adaptive-layout.md)).
|
|
27
|
+
- The separator between list and pane is dragged with the pointer
|
|
28
|
+
(`col-resize` cursor) or moved with the keyboard: ArrowLeft widens the pane
|
|
29
|
+
by 16px and ArrowRight narrows it (64px with Shift), Home makes it as wide
|
|
30
|
+
as the list allows and End as narrow as `min-width` (280px). A double click
|
|
31
|
+
goes back to the default. It is a `role="separator"` with
|
|
32
|
+
`aria-orientation="vertical"`, `aria-controls` naming the pane, and the
|
|
33
|
+
pane's width in px as `aria-valuenow` between `aria-valuemin` and
|
|
34
|
+
`aria-valuemax`.
|
|
35
|
+
- `default-width` is the width before the user sets one: a share of the room
|
|
36
|
+
(default `0.4`, kept between 400px and 640px) or a width in px.
|
|
37
|
+
- The width the user sets is remembered in `localStorage` under
|
|
38
|
+
`syno-pkg-land-ui:detail-width:<app>:<storage-key>`, where `<app>` is the
|
|
39
|
+
DSM package (or the page's host and directory) as for
|
|
40
|
+
[closed callouts](callout.md). Without `storage-key` the App's panes share
|
|
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.
|
|
44
|
+
- In the pane, give tables column widths that fit the default width and dates
|
|
45
|
+
in a one-line short format; a table wider than the pane scrolls sideways.
|
|
46
|
+
|
|
47
|
+
Hooks: `data-ui-list-detail` (with `data-open` and `data-dragging`),
|
|
48
|
+
`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
|
|
50
|
+
`--ui-list-detail-width`.
|
|
51
|
+
|
|
52
|
+
| Default width (1320px window) | Dragged wider | Wide window (1860px) |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
|  |  |  |
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# Mobile-native rules
|
|
2
|
+
|
|
3
|
+
How the UI SDK behaves on a phone (390×844) and a touch tablet (768×1024).
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
## Signals
|
|
7
|
+
|
|
8
|
+
| Signal | Where it comes from | What keys on it |
|
|
9
|
+
|---|---|---|
|
|
10
|
+
| 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) |
|
|
11
|
+
| Phone | narrow **and** `(pointer: coarse)` | phone tokens (type, control height, spacing, rows, header) |
|
|
12
|
+
| Touch | `@media (pointer: coarse)` | 16px fields, 44px tap boxes, tap feedback, touch-tablet control sizes |
|
|
13
|
+
| Hover | `@media (hover: hover)` | every hand-written `:hover` rule in the theme |
|
|
14
|
+
|
|
15
|
+
A touch tablet at 768px gets the touch rules but not the phone tokens. A
|
|
16
|
+
desktop gets neither, so it looks exactly as before. That includes a DSM
|
|
17
|
+
window or browser window narrowed below 640px and used with a mouse: it keeps
|
|
18
|
+
the narrow layout with desktop sizes. The phone tokens need a coarse pointer
|
|
19
|
+
too, because without that gate the 1280 gallery's narrow solo shots changed.
|
|
20
|
+
|
|
21
|
+
Every touch rule is keyed on `[data-ui-app]`, the root every app has (AppRoot).
|
|
22
|
+
Before this change they were keyed on `[data-ui-theme='dsm7']`. Nothing sets
|
|
23
|
+
that attribute (Studio sets its own theme name), so the rules never applied.
|
|
24
|
+
|
|
25
|
+
## Tokens and base guards (M-1)
|
|
26
|
+
|
|
27
|
+
### Type and size tokens
|
|
28
|
+
|
|
29
|
+
Each size is one token, so changing a size changes one line. Phone values are
|
|
30
|
+
set in `src/theme/adaptive.css` (the phone block), touch values in the
|
|
31
|
+
`@media (pointer: coarse)` blocks of `adaptive.css` and `dsm7.css`, and
|
|
32
|
+
desktop values in `dsm7.css`. An explicit `data-ui-density` keeps the app's
|
|
33
|
+
control and grid sizes; it does not change the 16px field rule.
|
|
34
|
+
|
|
35
|
+
| Token | Desktop | Touch tablet | Phone | Used for |
|
|
36
|
+
|---|---|---|---|---|
|
|
37
|
+
| `--ui-font-size` | 13 | 13 | **15** | primary text: list and record titles, field values, body copy, menu items |
|
|
38
|
+
| `--ui-font-size-sm` | 12 | 12 | **13** | secondary text: labels, meta, captions, helper text, badges |
|
|
39
|
+
| `--ui-font-size-xs` (new) | 12 | 12 | 12 | the smallest text anywhere |
|
|
40
|
+
| `--ui-line-height` | 20 | 20 | **22** | body line |
|
|
41
|
+
| `--ui-font-size-title` | 16 | 16 | **17** | header and dialog titles |
|
|
42
|
+
| `--ui-grid-font-size` (new) | 13 | 13 | **14** | grid cell text (not yet read by the canvas grid; see below) |
|
|
43
|
+
| `--ui-font-size-field` (new) | 16 | 16 | 16 | every field on a coarse pointer, to stop iOS focus zoom |
|
|
44
|
+
| `--ui-control-md` | 28 | **36** | **44** | fields, selects, form and sheet buttons |
|
|
45
|
+
| `--ui-control-sm` | 24 | **32** | **36** | inline and toolbar buttons (the tap box comes from `--ui-hit-min`) |
|
|
46
|
+
| `--ui-hit-min` (new) | 0 | **44** | **44** | smallest tap box |
|
|
47
|
+
| `--ui-tap-gap` (new) | 0 | **8** | **8** | room between tap targets; `--ui-icon-button-gap` reads it on touch |
|
|
48
|
+
| `--ui-grid-row-height` / `-header-height` | 28 | **36** | **32** | canvas grid rows (docs/ux-grid.md) |
|
|
49
|
+
| `--ui-list-row-height` (new) | 32 | **44** | **48** | list rows |
|
|
50
|
+
| `--ui-menu-row-height` | 32 | **40** | **48** | menu, select and sheet list rows |
|
|
51
|
+
| `--ui-page-padding-x` / `-y` | 16 (narrow layout 12) | 16 | **16** | page gutter |
|
|
52
|
+
| `--ui-section-gap` | 12 | 12 | **20** | space between sections |
|
|
53
|
+
| `--ui-card-padding` | 20 (Card default) | 20 | **16** | card inset |
|
|
54
|
+
| `--ui-chrome-height` | 44 (web 48, narrow layout 44) | – | **48** | the phone header |
|
|
55
|
+
| `--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) |
|
|
57
|
+
| `--ui-pressed` (new) | 8% of the text colour | same | same | the pressed tint |
|
|
58
|
+
|
|
59
|
+
Density comes from spacing, not smaller text: phone rows are 44–48px, and
|
|
60
|
+
components add no padding beyond the tokens.
|
|
61
|
+
|
|
62
|
+
Phone motion tokens (all read 0ms under reduced motion or `data-ui-motion="off"`):
|
|
63
|
+
|
|
64
|
+
| Token | Value | Use |
|
|
65
|
+
|---|---|---|
|
|
66
|
+
| `--ui-motion-sheet-in` / `-out` | 280 / 200ms | bottom sheet |
|
|
67
|
+
| `--ui-motion-sheet-ease` | `cubic-bezier(0.32, 0.72, 0, 1)` | sheet, push |
|
|
68
|
+
| `--ui-motion-drawer-in` / `-out` | 240 / 180ms | side drawer |
|
|
69
|
+
| `--ui-motion-push` / `-pop` | 280 / 240ms | push navigation |
|
|
70
|
+
| `--ui-motion-spring-back` | 180ms | a sheet released above its close threshold |
|
|
71
|
+
| `--ui-motion-press` | 150ms | pressed tint fading out (it shows at once) |
|
|
72
|
+
|
|
73
|
+
### Base guards (`preflight.css`)
|
|
74
|
+
|
|
75
|
+
These rules are in the base layer at zero specificity (`:where()`), so any
|
|
76
|
+
component rule overrides them.
|
|
77
|
+
|
|
78
|
+
- No blanket `min-width: 0`. The spec asked for one, but the 1280 gallery
|
|
79
|
+
diff showed it squeezing nowrap rows (Space "wrap off" went one letter per
|
|
80
|
+
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
|
|
82
|
+
width offenders).
|
|
83
|
+
- `overflow-wrap: break-word` on the app root. A word longer than its line
|
|
84
|
+
breaks. We use `break-word`, not `anywhere`, because it only breaks text
|
|
85
|
+
that would overflow and never changes how wide a table column or flex item
|
|
86
|
+
wants to be. Desktop layouts stay the same.
|
|
87
|
+
- `img`, `video` and `iframe` get `max-width: 100%`, and `img` and `video` also
|
|
88
|
+
get `height: auto`. `svg` is left alone so icons keep their size.
|
|
89
|
+
- Don't write `100vw` in components. Use `100%` or the viewport variables from
|
|
90
|
+
M-2. The `check:mobile` script (M-12) enforces this.
|
|
91
|
+
|
|
92
|
+
### Tap feedback (`touch.css`)
|
|
93
|
+
|
|
94
|
+
- Buttons, links, labels and every element with a button, tab, option,
|
|
95
|
+
menuitem, switch, checkbox, radio or treeitem role get
|
|
96
|
+
`-webkit-tap-highlight-color: transparent` and `touch-action: manipulation`.
|
|
97
|
+
This removes the grey flash and the 300ms double-tap wait. Pinch zoom still
|
|
98
|
+
works. A component's own `touch-none` overrides it.
|
|
99
|
+
- Pressed tint on a coarse pointer: an inset `--ui-pressed` shadow appears
|
|
100
|
+
while the finger is down and fades over `--ui-motion-press`. Button and
|
|
101
|
+
IconButton draw their own pressed state (`data-variant`), so they're
|
|
102
|
+
skipped. A component that sets its own shadow or `active:` style also keeps
|
|
103
|
+
its own.
|
|
104
|
+
- Hover: every hand-written `:hover` rule in the theme sits inside
|
|
105
|
+
`@media (hover: hover)`, so a tap leaves no stuck hover look. Tailwind's
|
|
106
|
+
`hover:` utilities already do this.
|
|
107
|
+
|
|
108
|
+
### Hit area (`hit-area.css`)
|
|
109
|
+
|
|
110
|
+
On a coarse pointer, a control smaller than `--ui-hit-min` keeps its drawn
|
|
111
|
+
size, and an invisible `::before` grows its tap box evenly on all sides to the
|
|
112
|
+
minimum:
|
|
113
|
+
|
|
114
|
+
```css
|
|
115
|
+
inset-block: min(0px, calc((100% - var(--ui-hit-min)) / 2));
|
|
116
|
+
inset-inline: min(0px, calc((100% - var(--ui-hit-min)) / 2));
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
These controls get the hit area:
|
|
120
|
+
|
|
121
|
+
- **Marked with `data-ui-hit`:** `Button`, `IconButton`
|
|
122
|
+
- **Matched by role or attribute:** `button[role=checkbox]` (Checkbox),
|
|
123
|
+
`button[role=radio]` (Radio, ToggleButton), `button[role=switch]` (Switch),
|
|
124
|
+
`[data-tag][role=button]` (a tappable Tag), `[data-tag-remove]`,
|
|
125
|
+
`[data-ui-password-toggle]`
|
|
126
|
+
|
|
127
|
+
A new tappable control can opt in by adding `data-ui-hit`. When two tap boxes
|
|
128
|
+
overlap, the later control wins the overlap. Clusters keep `--ui-tap-gap`
|
|
129
|
+
between controls. The rule sits in the base layer, so a component that sets
|
|
130
|
+
its own `position` or draws its own `::before` keeps it, and loses the larger
|
|
131
|
+
tap box.
|
|
132
|
+
|
|
133
|
+
### Not done in this layer
|
|
134
|
+
|
|
135
|
+
- **Hardcoded font sizes:** components that write a literal size, such as
|
|
136
|
+
`text-[13px]` in Checkbox, Switch, Radio, Input and the menu rows, don't
|
|
137
|
+
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.
|
|
139
|
+
- **Canvas grid:** the grid draws text at fixed sizes from
|
|
140
|
+
`src/grid-core/theme.ts`. Reading `--ui-grid-font-size` needs a change in
|
|
141
|
+
the grid.
|
|
142
|
+
- **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).
|
|
144
|
+
- **Hover rules in `adaptive.css`:** the brand action, sidebar collapse and
|
|
145
|
+
resize handles still use plain `:hover`. They belong to the shell tasks.
|
|
146
|
+
|
|
147
|
+
## Floating layer (M-3a)
|
|
148
|
+
|
|
149
|
+
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
|
+
|
|
151
|
+
| Setting | Value |
|
|
152
|
+
|---|---|
|
|
153
|
+
| Boundary | the app window in app scope (`useCollisionBoundary`), else the visual viewport (Reka's root boundary for fixed popups) |
|
|
154
|
+
| 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 |
|
|
155
|
+
| Shift | `sticky: 'partial'` |
|
|
156
|
+
| Detached anchor | `hideWhenDetached`: the popup hides once its anchor has left the boundary |
|
|
157
|
+
| Max size | `FLOATING_SIZE_CLASS`: `--reka-popper-available-width` and `-height`. Never `100vw` |
|
|
158
|
+
| Autofocus | none on a coarse pointer (`skipAutoFocusOnTouch` in `overlay.ts`) |
|
|
159
|
+
|
|
160
|
+
```vue
|
|
161
|
+
<script setup lang="ts">
|
|
162
|
+
import { FLOATING_SIZE_CLASS, useFloating } from '@/components/primitives/popover/floating'
|
|
163
|
+
const floating = useFloating(() => trigger.value?.$el, () => open.value)
|
|
164
|
+
</script>
|
|
165
|
+
<SomeContent v-bind="floating" :class="[ownClasses, FLOATING_SIZE_CLASS]" />
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**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.
|
|
169
|
+
|
|
170
|
+
**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.
|
|
171
|
+
|
|
172
|
+
**Uses it today:** Popover, Tooltip, OverflowTooltip and all anchored popups listed below.
|
|
173
|
+
|
|
174
|
+
## All anchored popups (M-3b)
|
|
175
|
+
|
|
176
|
+
Menus (including nested menus and Breadcrumb), Select and the shared ComboboxField,
|
|
177
|
+
DatePicker, ColorPicker, InputSteppers presets, InfoTip, ValidationPopup,
|
|
178
|
+
MentionList and OverflowToolbar now use the floating preset above. SplitButton,
|
|
179
|
+
MultipleSelect and SuperboxSelect inherit it through their shared primitives.
|
|
180
|
+
Menu sublevels inherit the parent's live padding, so an open submenu follows
|
|
181
|
+
safe-area and keyboard changes. Preferred and trigger widths are capped by
|
|
182
|
+
`--reka-popper-available-width`; lists scroll within the available height.
|
|
183
|
+
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.
|
|
185
|
+
|
|
186
|
+
## Toast, notification and hover-only controls (M-11)
|
|
187
|
+
|
|
188
|
+
`usePhone()` (`src/composables/phone.ts`) is the phone signal in script: the
|
|
189
|
+
enclosing AppRoot is narrow and the pointer is coarse, the same test as the
|
|
190
|
+
phone tokens. A provider sets `data-ui-phone` from it, and the placement rules
|
|
191
|
+
are in `src/feedback/feedback.css`.
|
|
192
|
+
|
|
193
|
+
| | Desktop, tablet, narrow window with a mouse | Phone |
|
|
194
|
+
|---|---|---|
|
|
195
|
+
| 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 |
|
|
196
|
+
| Notification | the chosen corner, 360px | top, 8px from each side, below `--ui-safe-top` plus 8px; `alignment` is ignored |
|
|
197
|
+
|
|
198
|
+
- Toasts behind the front one keep their own timers, so a short one can
|
|
199
|
+
expire unseen. Danger toasts stay until closed and come forward in turn.
|
|
200
|
+
- Toast and notification text reads `--ui-font-size` and
|
|
201
|
+
`--ui-font-size-sm` (13/12 on a desktop, 15/13 on a phone). A static
|
|
202
|
+
Notification card no longer has a 320px minimum width.
|
|
203
|
+
- Neither provider writes `100vw` any more.
|
|
204
|
+
|
|
205
|
+
**Hover-only controls.** VirtualCardGrid card actions, the ScheduleTable edit
|
|
206
|
+
pen and the TypoHeader anchor carry `[@media(hover:none)]:opacity-100`, so a
|
|
207
|
+
touch screen shows them at rest. A component that hides a control until hover
|
|
208
|
+
does the same.
|
|
209
|
+
|
|
210
|
+
**Card long press.** On touch, holding a VirtualCardGrid card for 250ms opens
|
|
211
|
+
the card's context menu (`menuItems`) through Reka's long press
|
|
212
|
+
(`ContextMenu` `pressOpenDelay`). The click that ends that press doesn't open
|
|
213
|
+
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.
|
|
@@ -0,0 +1,23 @@
|
|
|
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.
|