@synopackageland/ui 0.19.3 → 0.19.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 CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.19.5 (2026-11-03)
4
+
5
+ - Fix fullscreen `Dialog` height in an app-local root: the local and floating height caps no longer shorten a fullscreen panel by 24px. `fullscreen="mobile"` keeps the usual caps above the phone breakpoint.
6
+
7
+ ## 0.19.4 (2026-11-02)
8
+
9
+ - `SegmentControl`'s selected fill slides from the old item to the new one in 120ms, like the tab notch; a `variant="sheet"` tab's accent line slides the same way. Only transform and width move, through a `.ui-segment-thumb` shown while it slides (`data-ui-segment-moving`). The first render, a resize and a relabel do not move it, and reduced motion or `data-ui-motion="off"` switches at once.
10
+ - New `MotionSwap` for content switched by a control or a route: `<MotionSwap :value="view">` rises what it holds 4px and fades it in over 180ms each time `value` changes, like a switched tab panel. Not on first render, the old content goes at once, the slot is not remounted, and reduced motion turns it off. Pair it with `SegmentControl`, or wrap an App shell's router view ([README](README.md#segmentcontrol-and-the-views-it-switches)).
11
+ - `AppLayout`'s page content rises in the same way after navigating with its `AppSidebar` (side or top) or `AppTabBar`; the first load does not. Only opacity and a translate move, so the page height and scroll stay put.
12
+ - Fix Escape in an app-local modal (`AppRoot modal-scope="app"`) closing the dialog before an inline editor inside it could cancel its edit: Escape now reaches the focused control first, and an editor that calls `preventDefault()` or `stopPropagation()` keeps the dialog open; the next unconsumed Escape closes the top layer ([app-local modal](docs/app-local-modal.md)).
13
+ - Fix a `DatePicker` field being about 8px taller than a text input beside it: its inline root sat on the text baseline and left a descender gap below, so in a row of fields its label and box sat higher than its neighbours'. The root now aligns to the top of its line.
14
+ - New `AvatarStack`: people as overlapping round avatars (a board card's assignees) with each name in a tooltip, the group named for screen readers, and a "+n" circle past `max` (default 3). The comment avatar is exported as `Avatar`, and both take `initials` (one or two letters) instead of the name's first character.
15
+ - `Dialog` takes a `header` slot: what it holds (a breadcrumb, a menu) fills the header strip beside the close button, and the `title` stays as the dialog's accessible name only. New `maxHeight` caps the panel (default 85vh), and `fullscreen` fills the viewport: `true` always, `'mobile'` at 640px and narrower, with `data-ui-dialog-fullscreen` set.
16
+ - `ImageLightbox` has a Rotate button on pictures: each press turns the picture a quarter turn clockwise for viewing, such as a receipt photographed sideways. The file is unchanged and the next file opens upright; `data-ui-lightbox-rotate` carries `data-turns` while turned.
17
+
3
18
  ## 0.19.3 (2026-11-02)
4
19
 
5
20
  - Fix the library's own text (a `SettingsPage` footer's Reset and Apply, Close, pagination, validation) showing English in an App that names its language only on `AppRoot`'s `lang` or not at all. `AppRoot` takes a `locale` prop and otherwise reads its `lang` attribute; with neither, and no `ConfigProvider locale` above, the text follows DSM's language in a DSM window, then the browser's, then English. `toUiLocale()` and `detectUiLocale()` map DSM codes (`cht`, `jpn`, …) and BCP-47 tags.
package/README.md CHANGED
@@ -521,7 +521,7 @@ Primitives:
521
521
  - Sheet / Drawer — `:modal="false"` overlays the page without blocking it
522
522
  - DatePicker
523
523
  - ToastProvider + `useToast()`
524
- - Tabs / Accordion
524
+ - Tabs / Accordion / MotionSwap (content switched by a control or a route rises in, as a switched tab panel does)
525
525
 
526
526
  DSM-style application building blocks:
527
527
 
@@ -583,9 +583,48 @@ on the strip (`nav`).
583
583
  ```
584
584
 
585
585
  Navigation-only `SectionTabs` usage still works. For content managed separately
586
- by the caller, add `class="ui-tab-panel"` to a panel keyed by the selected value
587
- to use the same short fade. These effects and the loading ring respect the
588
- system's reduced-motion preference.
586
+ by the caller, wrap it in `MotionSwap` (below) for the same short rise. These
587
+ effects and the loading ring respect the system's reduced-motion preference.
588
+
589
+ ### SegmentControl and the views it switches
590
+
591
+ When the selection changes, `SegmentControl`'s selected fill (the accent line
592
+ of a `variant="sheet"` tab) slides from the old item to the new one in 120ms,
593
+ like the tab notch. The first render, a resize and a relabel do not move it;
594
+ under reduced motion or `data-ui-motion="off"` it switches at once.
595
+
596
+ Pair the control with its views through `MotionSwap`, keyed by the same value.
597
+ A view switched to rises 4px and fades in over 180ms, like a switched tab
598
+ panel; the view shown first does not animate, the old view goes at once, and
599
+ the slot is not remounted by `MotionSwap` itself:
600
+
601
+ ```vue
602
+ <SegmentControl v-model="view" aria-label="Health views">
603
+ <SegmentItem value="security">Security</SegmentItem>
604
+ <SegmentItem value="storage">Storage</SegmentItem>
605
+ </SegmentControl>
606
+ <MotionSwap :value="view">
607
+ <SecurityView v-if="view === 'security'" />
608
+ <StorageView v-else />
609
+ </MotionSwap>
610
+ ```
611
+
612
+ `AppLayout` does the same for its page: navigating with its `AppSidebar` (side
613
+ or top) or `AppTabBar` rises the page content in; the first load does not. An
614
+ App with its own shell wraps its router view instead (mount the App after
615
+ `router.isReady()`, so the first route is not a change):
616
+
617
+ ```vue
618
+ <RouterView v-slot="{ Component, route }">
619
+ <MotionSwap :value="route.path" class="min-h-0 flex-1">
620
+ <component :is="Component" />
621
+ </MotionSwap>
622
+ </RouterView>
623
+ ```
624
+
625
+ Only opacity and a translate move, so the page height and scroll position stay
626
+ put. While it rises (180ms) the content is a containing block for
627
+ `position: fixed` descendants.
589
628
 
590
629
  ### StatTile: counts that filter
591
630
 
@@ -684,7 +723,7 @@ already matched them. With `maxlength` the box counts characters near and over
684
723
  the limit and never cuts text.
685
724
 
686
725
  `FileThumbs` shows a few thumbnails and "+N", `ImageLightbox` shows files full
687
- size (arrow keys and swipes), and `UploadQueue` lists uploads with progress,
726
+ size (arrow keys and swipes; Rotate turns a picture for viewing), and `UploadQueue` lists uploads with progress,
688
727
  retry and cancel. `useUploadQueue(upload, { concurrency, retries })` runs the
689
728
  uploads it shows. `FilePicker` takes pasted files with `paste`.
690
729
 
@@ -2,12 +2,14 @@ type __VLS_Props = {
2
2
  name: string;
3
3
  id?: string;
4
4
  src?: string;
5
+ initials?: string;
5
6
  size?: 'sm' | 'md';
6
7
  };
7
8
  declare const __VLS_export: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
8
9
  size: "sm" | "md";
9
10
  id: string;
10
11
  src: string;
12
+ initials: string;
11
13
  }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
12
14
  declare const _default: typeof __VLS_export;
13
15
  export default _default;
@@ -0,0 +1,14 @@
1
+ import { AvatarPerson } from './types';
2
+ type __VLS_Props = {
3
+ people: AvatarPerson[];
4
+ max?: number;
5
+ label?: string;
6
+ size?: 'sm' | 'md';
7
+ };
8
+ declare const __VLS_export: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
9
+ label: string;
10
+ size: "sm" | "md";
11
+ max: number;
12
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
13
+ declare const _default: typeof __VLS_export;
14
+ export default _default;
@@ -0,0 +1,7 @@
1
+ /** One person in an AvatarStack: the name shows in the tooltip; `id` keeps the colour, `src` is a picture. */
2
+ export type AvatarPerson = {
3
+ name: string;
4
+ id?: string;
5
+ src?: string;
6
+ initials?: string;
7
+ };
@@ -14,6 +14,10 @@ type __VLS_Props = {
14
14
  height?: string;
15
15
  /** Keeps the panel from shrinking with short content, capped to the available height. */
16
16
  minHeight?: string;
17
+ /** The tallest the panel grows before its body scrolls (default 85vh), capped to the available height. */
18
+ maxHeight?: string;
19
+ /** Fills the viewport: `true` always, `'mobile'` only on a phone-width screen (640px and narrower), for a dialog that is a page there. */
20
+ fullscreen?: boolean | 'mobile';
17
21
  closeLabel?: string;
18
22
  /** Hides the header close affordance for fixed-flow dialogs that own their dismiss path. */
19
23
  showClose?: boolean;
@@ -26,13 +30,15 @@ type __VLS_Props = {
26
30
  /** The mask over the app behind: `dim` (default), `light` (a faint tint, as DSM leaves the window behind a wizard) or `none`. It blocks presses in each case. */
27
31
  scrim?: 'dim' | 'light' | 'none';
28
32
  };
29
- declare var __VLS_14: {}, __VLS_73: {}, __VLS_81: {};
33
+ declare var __VLS_14: {}, __VLS_62: {}, __VLS_75: {}, __VLS_83: {};
30
34
  type __VLS_Slots = {} & {
31
35
  trigger?: (props: typeof __VLS_14) => any;
32
36
  } & {
33
- default?: (props: typeof __VLS_73) => any;
37
+ header?: (props: typeof __VLS_62) => any;
34
38
  } & {
35
- footer?: (props: typeof __VLS_81) => any;
39
+ default?: (props: typeof __VLS_75) => any;
40
+ } & {
41
+ footer?: (props: typeof __VLS_83) => any;
36
42
  };
37
43
  declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
38
44
  escapeKeyDown: (event: KeyboardEvent) => any;
@@ -0,0 +1,21 @@
1
+ type __VLS_Props = {
2
+ /** What is shown; each change plays the rise-in. Usually the control's `v-model` or the route path. */
3
+ value: unknown;
4
+ /** Wrapper element. */
5
+ as?: string;
6
+ };
7
+ declare var __VLS_9: {};
8
+ type __VLS_Slots = {} & {
9
+ default?: (props: typeof __VLS_9) => any;
10
+ };
11
+ declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
12
+ as: string;
13
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
14
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
15
+ declare const _default: typeof __VLS_export;
16
+ export default _default;
17
+ type __VLS_WithSlots<T, S> = T & {
18
+ new (): {
19
+ $slots: S;
20
+ };
21
+ };
@@ -25,6 +25,7 @@ export declare const englishUiText: {
25
25
  readonly "Links start with http://, https:// or mailto:": "Links start with http://, https:// or mailto:";
26
26
  readonly "Mention someone": "Mention someone";
27
27
  readonly "Next file": "Next file";
28
+ readonly Rotate: "Rotate";
28
29
  readonly "Numbered list": "Numbered list";
29
30
  readonly "Open {name}": "Open {name}";
30
31
  readonly "Previous file": "Previous file";
package/dist/index.d.ts CHANGED
@@ -100,6 +100,7 @@ export { default as Pagination } from './components/primitives/pagination/Pagina
100
100
  export type { PaginationBeforeSwitchPage, PaginationLabels, PaginationPageSizeOption, PaginationSwitch, PaginationTotalTextMode, } from './components/primitives/pagination/Pagination.vue';
101
101
  export { default as SegmentControl } from './components/primitives/segment-control/SegmentControl.vue';
102
102
  export { default as SegmentItem } from './components/primitives/segment-control/SegmentItem.vue';
103
+ export { default as MotionSwap } from './components/primitives/motion/MotionSwap.vue';
103
104
  export { default as SearchField } from './components/composite/search-field/SearchField.vue';
104
105
  export { default as SettingsRow } from './components/composite/settings-row/SettingsRow.vue';
105
106
  export { default as FormActions } from './components/composite/form-actions/FormActions.vue';
@@ -140,6 +141,9 @@ export type { FacetGroup, FacetSelection, FacetValue } from './components/compos
140
141
  export { default as TagEditor } from './components/composite/tag-editor/TagEditor.vue';
141
142
  export { default as CompletionToggle } from './components/composite/completion-toggle/CompletionToggle.vue';
142
143
  export { default as CommentItem } from './components/composite/comment-item/CommentItem.vue';
144
+ export { default as Avatar } from './components/composite/comment-item/PersonInitials.vue';
145
+ export { default as AvatarStack } from './components/primitives/avatar/AvatarStack.vue';
146
+ export type { AvatarPerson } from './components/primitives/avatar/types';
143
147
  export { mentionParts } from './lib/mentions';
144
148
  export { default as MentionList } from './components/composite/mention-list/MentionList.vue';
145
149
  export type { MentionCandidate, MentionPart } from './lib/mentions';