@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.
- package/CHANGELOG.md +35 -0
- package/LICENSES/THIRD_PARTY.md +2 -32
- 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/components/app/app-layout/AppLayout.vue.d.ts +52 -5
- package/dist/components/app/app-layout/pageTitle.d.ts +24 -0
- package/dist/components/app/app-root/AppRoot.vue.d.ts +9 -0
- package/dist/components/app/app-tab-bar/AppTabBar.vue.d.ts +1 -1
- 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/app/section-tabs/SectionTabs.vue.d.ts +22 -4
- package/dist/components/composite/about-dialog/AboutDialog.vue.d.ts +1 -1
- 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/data-grid/DataGrid.vue.d.ts +19 -1
- package/dist/components/composite/data-grid/labels.d.ts +15 -0
- package/dist/components/composite/data-grid/listLayout.d.ts +26 -0
- package/dist/components/composite/data-table/DataTable.vue.d.ts +33 -7
- package/dist/components/composite/file-picker/FilePicker.vue.d.ts +2 -2
- package/dist/components/composite/form/FormGroup.vue.d.ts +1 -1
- package/dist/components/composite/form/context.d.ts +13 -0
- package/dist/components/composite/form-actions/FormActions.vue.d.ts +6 -2
- 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 +91 -0
- package/dist/components/composite/record-panel/recordPanelWidth.d.ts +54 -0
- package/dist/components/composite/settings-section/SettingsSection.vue.d.ts +1 -1
- package/dist/components/composite/summary-card/SummaryCard.vue.d.ts +1 -1
- package/dist/components/composite/tag-editor/TagEditor.vue.d.ts +4 -4
- package/dist/components/composite/tree-view/TreeView.vue.d.ts +4 -4
- package/dist/components/composite/wizard/Wizard.vue.d.ts +2 -2
- package/dist/components/composite/wizard/WizardDialog.vue.d.ts +4 -4
- 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 +6 -4
- 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/feedback/validation-popup/ValidationPopup.vue.d.ts +1 -1
- package/dist/components/primitives/breadcrumb/Breadcrumb.vue.d.ts +2 -2
- package/dist/components/primitives/color-picker/ColorPicker.vue.d.ts +4 -4
- package/dist/components/primitives/combobox/Combobox.vue.d.ts +4 -4
- package/dist/components/primitives/context-menu/ContextMenu.vue.d.ts +5 -2
- package/dist/components/primitives/context-menu/ContextMenuItems.vue.d.ts +4 -5
- package/dist/components/primitives/dialog/Dialog.vue.d.ts +16 -8
- package/dist/components/primitives/dropdown-menu/DropdownMenu.vue.d.ts +7 -3
- package/dist/components/primitives/dropdown-menu/DropdownMenuItems.vue.d.ts +6 -3
- package/dist/components/primitives/dropdown-menu/DropdownMenuSheet.vue.d.ts +28 -0
- package/dist/components/primitives/input/Input.vue.d.ts +8 -1
- package/dist/components/primitives/input/Textarea.vue.d.ts +1 -1
- package/dist/components/primitives/input-steppers/InputSteppers.vue.d.ts +1 -1
- package/dist/components/primitives/motion/PushTransition.vue.d.ts +38 -0
- package/dist/components/primitives/multiple-select/MultipleSelect.vue.d.ts +4 -4
- package/dist/components/primitives/pagination/Pagination.vue.d.ts +22 -9
- package/dist/components/primitives/pagination/PaginationSheet.vue.d.ts +27 -0
- package/dist/components/primitives/pagination/labels.d.ts +6 -0
- package/dist/components/primitives/popover/Popover.vue.d.ts +11 -3
- 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 +6 -5
- package/dist/components/primitives/select/internal/SelectSheet.vue.d.ts +42 -0
- package/dist/components/primitives/select/internal/types.d.ts +2 -0
- package/dist/components/primitives/sheet/BottomSheet.vue.d.ts +69 -0
- package/dist/components/primitives/sheet/Sheet.vue.d.ts +13 -5
- package/dist/components/primitives/sheet/phone.d.ts +7 -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/superbox-select/SuperboxSelect.vue.d.ts +4 -4
- 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/components/rich-text-editor/RichTextEditor.vue.d.ts +1 -1
- package/dist/composables/backDismiss.d.ts +19 -0
- package/dist/composables/formControl.d.ts +18 -0
- package/dist/composables/phone.d.ts +8 -0
- package/dist/composables/phoneSurface.d.ts +35 -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 +6272 -0
- package/dist/i18n/text.d.ts +17 -0
- package/dist/index.d.ts +39 -4
- package/dist/index.js +23037 -14390
- 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 +16780 -11595
- 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 +15 -12
- package/dist/rich-text.js +1 -1
- package/dist/theme.css +1 -1
- package/dist/themes/dsm7-embedded-reset.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 +148 -25
- package/docs/app-local-modal.md +1 -1
- package/docs/callout.md +22 -1
- package/docs/layout-rules.md +14 -0
- package/docs/list-detail.md +60 -0
- package/docs/mobile-native.md +502 -0
- package/docs/release-checks.md +28 -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 +100 -2
|
@@ -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
|
|
@@ -149,20 +270,22 @@ import {
|
|
|
149
270
|
remove a child Card's frame or a PageHeader's own vertical padding. The JSON
|
|
150
271
|
`Page` accepts the same value. SDK `AppShell` consumers need that package to
|
|
151
272
|
expose/forward the option to its internal `PageContent`.
|
|
152
|
-
- `DataTable` defaults to `mobile-view="auto"`, switching to
|
|
153
|
-
|
|
273
|
+
- `DataTable` defaults to `mobile-view="auto"`, switching to a list (a row per
|
|
274
|
+
item: title, subtitle, trailing column; see docs/mobile-native.md) in the
|
|
275
|
+
**App's mobile presentation**. A narrow table within an ordinary
|
|
154
276
|
700px/800px DSM Window stays a table, even if its content area is below
|
|
155
277
|
640px. When no AppRoot/AppLayout provides a presentation, DataTable falls
|
|
156
|
-
back to its own width (640px or less).
|
|
157
|
-
|
|
158
|
-
|
|
278
|
+
back to its own width (640px or less). `mobile-view="table"` retains horizontal
|
|
279
|
+
scrolling for dense/interactive data; `cards` shows every column as fully
|
|
280
|
+
labeled cards at every size.
|
|
159
281
|
Explicit column `width` values (CSS pixels or percentages) use fixed table
|
|
160
282
|
tracks; columns without a width share the remaining space. Long text wraps
|
|
161
283
|
inside those tracks. Tables without widths retain automatic sizing.
|
|
162
|
-
- `DataGrid`
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
for very complex tables rather than degrading a
|
|
284
|
+
- `DataGrid` shows the same phone list in the App's mobile presentation
|
|
285
|
+
(`mobile-view="auto"`): long press selects, and sorting and columns move into
|
|
286
|
+
a sheet. `mobile-view="table"` keeps the table, which scrolls horizontally.
|
|
287
|
+
Use a mobile alternative for very complex tables rather than degrading a
|
|
288
|
+
grid's editing model.
|
|
166
289
|
- `ResponsiveView` renders a `mobile`, `window`, or `web` slot when available.
|
|
167
290
|
The `default` slot is the fallback for all unprovided modes, so features aren't
|
|
168
291
|
accidentally hidden. Only the active variant is mounted.
|
|
@@ -235,7 +358,7 @@ any ancestor. Light defaults and the dark values the workbench uses:
|
|
|
235
358
|
| `--ui-app-web-surface` | `var(--ui-surface)` | `var(--ui-surface)` (`#1f2832`) |
|
|
236
359
|
| `--ui-app-web-border` | `var(--ui-border)` | `#34404d` |
|
|
237
360
|
| `--ui-app-web-shadow` | `var(--ui-shadow-card)` | `var(--ui-shadow-card)` |
|
|
238
|
-
| `--ui-web-sidebar-collapsed-width` | `
|
|
361
|
+
| `--ui-web-sidebar-collapsed-width` | `48px` | same |
|
|
239
362
|
|
|
240
363
|
`AppLayout navigation="top"` puts the navigation in a bar above the page in the
|
|
241
364
|
window and web presentations (a phone keeps the drawer); its colours and the
|
package/docs/app-local-modal.md
CHANGED
|
@@ -146,7 +146,7 @@ root and inside the active panel, or use the library's supported popup component
|
|
|
146
146
|
|
|
147
147
|
## Delivery and patch maintenance
|
|
148
148
|
|
|
149
|
-
Reka 2.
|
|
149
|
+
Reka 2.11.0 is an exact development dependency, bundled into the UI library's
|
|
150
150
|
ESM dist. It is no longer a consumer runtime dependency. Vue remains an external
|
|
151
151
|
peer, and the bundled code uses that same Vue instance. Consumers receive the
|
|
152
152
|
body-style fix by rebuilding, without a package-manager patch or app code change.
|
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.
|
package/docs/layout-rules.md
CHANGED
|
@@ -72,6 +72,20 @@ The setting is a form value, saved with the page's Apply action. It does not nav
|
|
|
72
72
|
- A settings page has one Apply. Put Reset then Apply in `SettingsPage`'s `#footer`, at the bottom-right, as Control Panel does. Keep the footer while the page has settings to apply, so nothing moves when a draft changes. Do not put a Save between the fields of a section.
|
|
73
73
|
- A button that belongs to a field (Allowed websites, Trusted Proxies, Advanced Settings) is a grey `Button variant="secondary"` in the flow: in the `FormGroup` it depends on, after the field it acts on (`#after`), or under the section's description.
|
|
74
74
|
|
|
75
|
+
## Choosing tabs on a phone
|
|
76
|
+
|
|
77
|
+
Phone layout follows the app container (≤640px) and a coarse pointer, not the browser window alone. A 768px touch tablet keeps its wider layout and still gets 44px tap targets.
|
|
78
|
+
|
|
79
|
+
| What changes | Use | Phone behaviour |
|
|
80
|
+
|---|---|---|
|
|
81
|
+
| 2–4 short views of the same content | `SegmentControl` | Full width, equal segments, 36px minimum visual height and 44px hit area; translated labels wrap and may grow the row |
|
|
82
|
+
| Many local views or long navigation labels | `Tabs` | Horizontal strip with edge fades; selection scrolls into view without moving the page; labels stay on one line |
|
|
83
|
+
| Up to 5 top-level app destinations | `AppTabBar` | Bottom navigation; additional destinations belong under More |
|
|
84
|
+
| Settings sections or 5+ page sections | `SectionTabs` / `SettingsPage` | Grouped list and push navigation on phone; retain the section structure |
|
|
85
|
+
| A saved form choice | `RadioGroup` | Labelled choices, not navigation tabs |
|
|
86
|
+
|
|
87
|
+
`SegmentControl variant="sheet"` follows the same phone sizing. More than four existing segments remain horizontally scrollable with fades; use `Tabs` for new many-view navigation. `size="sm"` stays compact on desktop and follows phone sizing on a phone. Icon-only segments require `aria-label`; use visible text when a touch user needs the label to choose. Never place a second bottom tab bar inside a page.
|
|
88
|
+
|
|
75
89
|
## Tables
|
|
76
90
|
|
|
77
91
|
- Use `CellText` for a cell with a main text and secondary information (size, time, path): `text`, `secondary`, optional `icon`, `layout="stacked"` (default) or `"inline"`. It truncates both and shows the full text in a tooltip only when cut. Do not join strings or add margins.
|
|
@@ -0,0 +1,60 @@
|
|
|
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
|
+
- At 640px of room or less (a phone) the list fills the room and the detail
|
|
43
|
+
is a page pushed over it ([push navigation](mobile-native.md#push-navigation)):
|
|
44
|
+
it slides in from the right, the list moves 30% left and dims, and a 48px
|
|
45
|
+
header shows Back and the title in place of Close. Back, the browser's Back
|
|
46
|
+
and a swipe from the detail's left edge pop it, which emits `close`. The
|
|
47
|
+
separator is hidden. Give the ListDetail a height to fill so the list and
|
|
48
|
+
the detail each scroll on their own; without one the detail sets the height.
|
|
49
|
+
- In the pane, give tables column widths that fit the default width and dates
|
|
50
|
+
in a one-line short format; a table wider than the pane scrolls sideways.
|
|
51
|
+
|
|
52
|
+
Hooks: `data-ui-list-detail` (with `data-open` and `data-dragging`),
|
|
53
|
+
`data-ui-list-detail-list`, `data-ui-list-detail-separator`,
|
|
54
|
+
`data-ui-list-detail-pane`, `data-ui-list-detail-close`, `data-push` on the
|
|
55
|
+
root while the detail is pushed; the pane's width is
|
|
56
|
+
`--ui-list-detail-width`.
|
|
57
|
+
|
|
58
|
+
| Default width (1320px window) | Dragged wider | Wide window (1860px) |
|
|
59
|
+
|---|---|---|
|
|
60
|
+
|  |  |  |
|