@synopackageland/ui 0.21.1 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +1 -0
  3. package/dist/cli.js +0 -0
  4. package/dist/components/app/app-layout/AppLayout.vue.d.ts +13 -2
  5. package/dist/components/composite/about-dialog/AboutDialog.vue.d.ts +1 -1
  6. package/dist/components/composite/adaptive-panel/AdaptivePanel.vue.d.ts +1 -1
  7. package/dist/components/composite/disclosure-row/DisclosureRow.vue.d.ts +39 -0
  8. package/dist/components/composite/record-panel/RecordPanel.vue.d.ts +1 -1
  9. package/dist/components/containers/split-pane/SplitPane.vue.d.ts +1 -1
  10. package/dist/components/feedback/validation-popup/ValidationPopup.vue.d.ts +1 -1
  11. package/dist/components/primitives/button/Button.vue.d.ts +7 -1
  12. package/dist/components/primitives/checkbox/CheckboxGroup.vue.d.ts +1 -1
  13. package/dist/components/primitives/dropdown-menu/types.d.ts +6 -0
  14. package/dist/components/primitives/pagination/Pagination.vue.d.ts +1 -1
  15. package/dist/components/primitives/popover/Popover.vue.d.ts +1 -1
  16. package/dist/components/primitives/progress/Progress.vue.d.ts +1 -1
  17. package/dist/components/primitives/tooltip/tooltipStyle.d.ts +1 -1
  18. package/dist/components/rich-text-editor/RichTextEditor.vue.d.ts +1 -1
  19. package/dist/feedback/StatusSlot.vue.d.ts +1 -1
  20. package/dist/grid-core/theme.d.ts +2 -1
  21. package/dist/grid.js +253 -251
  22. package/dist/index.d.ts +3 -0
  23. package/dist/index.js +14628 -14321
  24. package/dist/json/catalog.d.ts +2 -0
  25. package/dist/json/registry.d.ts +34 -0
  26. package/dist/json-ai-prompt.md +1 -1
  27. package/dist/json-catalog.schema.json +2 -1
  28. package/dist/json-preview/preview.js +9516 -9405
  29. package/dist/json.js +23 -23
  30. package/dist/lib/adaptiveLayout.d.ts +17 -0
  31. package/dist/lib/scroll.d.ts +31 -0
  32. package/dist/lib/typeScale.d.ts +20 -0
  33. package/dist/reka-runtime.json +1 -1
  34. package/dist/theme.css +1 -1
  35. package/dist/themes/dsm7-embedded-theme.css +1 -1
  36. package/dist/themes/dsm7-embedded.css +1 -1
  37. package/dist/themes/dsm7.css +1 -1
  38. package/docs/button-policy.md +19 -2
  39. package/docs/mobile-native.md +191 -34
  40. package/package.json +67 -59
package/CHANGELOG.md CHANGED
@@ -2,6 +2,30 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.23.0 (2026-11-07)
6
+
7
+ Compact phone bars and native phone density. A DSM window and desktop are unchanged. No App change is required, except that among several `headerActions` only an entry marked `primary: true` is a bar button (below). Details in docs/mobile-native.md.
8
+
9
+ - **Thin bars, like X.** The phone bar is one 44px row (plus its rule) with a 16px title and a 24px App icon; it still hides on scroll and comes back on a scroll up. The tabs strip under it is drawn 40px (`--ui-section-tab-height`) with a 44px tap box and sticks under the bar, and to the top of the screen while the bars are hidden. The tab bar is icon only: a 48px row (`--ui-tab-bar-height`) plus the safe area, a 1px rule and no shadow, 24px icons (`--ui-tab-bar-icon-size`) in tabs at least 44px square, the current tab in the primary colour with a heavier stroke, badges kept. A tab's label is its accessible name and shows above it on a long press or after a 500ms hover. The FAB, toasts, the bottom accessory and fill pages follow the new heights
10
+ - **Phone density.** On a touch phone: 14px body text (12px secondary, 15px card titles, 16px titles), 12px page margins, section gaps and card insets, 44px list and menu rows, buttons drawn 36px (28 small, 40 large) inside a 44px tap box, 32px small controls; fields stay 44px. A Card's header is one 44px row with no gap under it, a ListItem row is 10px top and bottom, and a surface inside a card's body draws no frame or inset of its own, so cards never nest padding
11
+ - **Type tokens everywhere, zoom and tap areas.** Components carry no literal font sizes; status, help and hint text read `--ui-font-size-sm`. The canvas grid draws cells at `--ui-grid-font-size`, and new `readTypeScale(el)` / `canvasFont()` give an App's own canvas or chart the same tokens. Rows, footers and bars have a minimum height only, so two lines or larger text make them taller; a SettingsSection header grows too. A text button never shrinks in its row. New `check:text-zoom` renders the phone scenes at 390x844 and at 200% zoom and fails on sideways overflow, cut text, or any bar button, tab, text button, DisclosureRow or icon button without its own 44px tap square
12
+ - **One "…" in the phone bar.** A page whose own menu is in the bar (a drill-down page's "…") showed a second "…" for `headerActions` (an App shell's account entry). The `headerActions` now join the end of the page's menu after a separator, and pick through `headerAction` as before; without such a menu they keep a "…" of their own. A `headerActions` entry is no longer promoted to a bar button because it is first: mark it `primary: true` (new on `DropdownMenuEntry`). A sole entry with an icon (an App shell's account) stays that icon
13
+ - **New `Button variant="text"`** for a short action in a line of text (Change, Edit): primary-coloured text with no frame or fill, dimmed while pressed, grey while disabled, a 44px tap box on touch without growing the line, and `chevron` for a trailing ›. docs/button-policy.md says when to use it and that labels never end in "..."
14
+ - **New `DisclosureRow`:** an iOS settings row (label, value, ›) that is one button (or a link with `href`), at least 44px high on a phone, with hover, pressed and focus states
15
+ - **Fill pages reach the bottom with the bars hidden.** While the bars are hidden on a fill page, the room after its last row is only the safe area. New `--ui-page-end-space` (24px) carries it; an App shell that pads its page end reads it
16
+ - **New `--ui-sticky-top-offset`** on PageContent: the height of what sticks at its top (the covered padding, a sticky PageHeader, the phone's tabs strip). Every element in the page body takes it as `scroll-margin-top`, so `scrollIntoView` and focus land below the stuck rows; a row an App makes sticky uses it instead of a hardcoded 44 or 96px
17
+ - **New scroll helpers** `nearestScroller(el)`, `scrollIntoViewWithinScroller(el, options)` and `scrollToStart(el)`, which scroll the element that actually scrolls on a phone, in a DSM window or on a desktop, honour scroll margins and move at once under reduced motion
18
+ - `check:phone-layout` adds a home page (a primary Start meeting button, an inline Change, an upload card, a recent list) and a form page with DisclosureRows; the toast expectation follows the 48px tab bar
19
+
20
+ ## 0.22.0 (2026-11-07)
21
+
22
+ Native phone shell follow-ups. No App change is required; a DSM window and desktop are unchanged apart from the new slot.
23
+
24
+ - **Fill pages give the freed room to their list.** On a fill page (its content scrolls itself), the FAB's 88px room stayed under the content's scroller while the FAB was hidden on scroll, leaving a blank band at the bottom when the bars slid away. PageContent now marks a page whose outer element does not scroll (`height="fill"`, or an App shell that fills it) with `data-ui-page-fills`; there the room goes while the FAB is hidden on scroll or by the keyboard, so the scroller reaches the bars' edge, and comes back with the FAB. A page that scrolls as a whole keeps the room at its end, so its length never changes under the finger. No room is kept while no FAB is drawn
25
+ - **New `#accessory` slot on `AppLayout`.** A bar across the bottom for something the App is running (a recording bar, a mini player). On a phone it docks right above the tab bar, stays while the bars hide on scroll (sliding into the tab bar's place, in `--ui-motion-bars`) and hides while the keyboard is up. The layout measures it (`--ui-bottom-accessory-height`) and lifts the FAB, toasts, an AsidePanel's phone button and the page's end above it; an App overrides no tab-bar or FAB token. Elsewhere it is the column's bottom row. An empty slot draws nothing
26
+ - **Sticky rows in a padded page scroller.** When an App shell makes the PageContent its page's scroller and pads its top, a stuck row (a group heading, a grid header) stopped that padding below the top and the rows scrolling under it showed in the strip above. PageContent now covers that strip in the page header's colour (`[data-ui-page-scroll-cover]`, `--ui-page-scroll-pad`); the content starts where it did and a `top: 0` sticky row needs no offset
27
+ - `check:phone-layout` adds a feed page with sticky group headings, as a page that scrolls and as a fill page, with and without a bottom accessory and with a toast, at 390x844, 360x800 and 390x664 with touch, in a DSM window and on desktop, in English and Traditional Chinese: the covered strip above a stuck heading, the fill page's list reaching the bottom with the bars hidden, the accessory docked above the tab bar and sliding into its place, and the FAB and toast above it. New story "App / Phone shell accessory"
28
+
5
29
  ## 0.21.1 (2026-11-07)
6
30
 
7
31
  Native phone shell fixes. No App change is required; pages without a FAB, a DSM window and desktop are unchanged.
package/README.md CHANGED
@@ -537,6 +537,7 @@ DSM-style application building blocks:
537
537
 
538
538
  - SearchField
539
539
  - SettingsRow / SettingsSection
540
+ - DisclosureRow (a whole clickable row: label, value, ›; see [docs/button-policy.md](docs/button-policy.md))
540
541
  - SettingsPage / Form / FormItem / FormGroup / FormNote / InfoBox / FormActions / Fieldset (DSM-native settings structure, see [docs/layout-rules.md](docs/layout-rules.md))
541
542
  - Callout (see [docs/callout.md](docs/callout.md) for when to use a Callout, a toast or an inline note) / EmptyState / StepList
542
543
  - ConnectionTopology
package/dist/cli.js CHANGED
File without changes
@@ -34,8 +34,11 @@ type __VLS_Props = {
34
34
  /** The phone bar leads with a Back button (emits `back`) instead of the menu button, for a pushed page. */
35
35
  back?: boolean;
36
36
  /**
37
- * The phone bar's actions: the first is one button (its icon, or else its
38
- * label); the rest open from "…". A pick emits `headerAction` with the key.
37
+ * The phone bar's actions. They open from the bar's one "…": the page's
38
+ * own menu when it is in the bar (after a separator), else a "…" of their
39
+ * own. An entry marked `primary`, or a sole entry with an icon, is a button
40
+ * (its icon, or else its label) while the page puts no actions in the bar.
41
+ * A pick emits `headerAction`.
39
42
  */
40
43
  headerActions?: DropdownMenuEntry[];
41
44
  /**
@@ -65,6 +68,14 @@ type __VLS_Slots = {
65
68
  footer?: () => unknown;
66
69
  /** A phone's bottom tab bar (AppTabBar), shown only in the mobile presentation. */
67
70
  tabbar?: () => unknown;
71
+ /**
72
+ * A bar across the bottom of the page that stays while the App runs
73
+ * something (a recording bar, a mini player). On a phone it docks right
74
+ * above the tab bar, stays while the bars hide on scroll (sliding into the
75
+ * tab bar's place) and hides while the keyboard is up; the FAB, toasts and
76
+ * the page's end move above it. Elsewhere it is the column's bottom row.
77
+ */
78
+ accessory?: () => unknown;
68
79
  /**
69
80
  * The right side region. `overlay` is true when the open aside floats over
70
81
  * the page (the page would be narrower than 480px, or the layout is not
@@ -60,9 +60,9 @@ declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {
60
60
  "onCheck-result"?: ((info: AboutVersionInfo | null) => any) | undefined;
61
61
  "onCheck-error"?: ((error: unknown) => any) | undefined;
62
62
  }>, {
63
- open: boolean;
64
63
  width: string;
65
64
  version: string;
65
+ open: boolean;
66
66
  defaultOpen: boolean;
67
67
  tagline: string;
68
68
  iconSrc: string;
@@ -41,8 +41,8 @@ declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {}, {}, {},
41
41
  }, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
42
42
  "onUpdate:open"?: ((value: boolean) => any) | undefined;
43
43
  }>, {
44
- open: boolean;
45
44
  width: string;
45
+ open: boolean;
46
46
  side: "top" | "right" | "bottom" | "left";
47
47
  align: "start" | "center" | "end";
48
48
  motion: OverlayMotion;
@@ -0,0 +1,39 @@
1
+ type __VLS_Props = {
2
+ label?: string;
3
+ /** The current value, in secondary text before the chevron. */
4
+ value?: string | number;
5
+ /** A second line under the label. */
6
+ description?: string;
7
+ /** Renders a link instead of a button. */
8
+ href?: string;
9
+ target?: string;
10
+ disabled?: boolean;
11
+ /** `false` hides the chevron (a row that acts in place). */
12
+ chevron?: boolean;
13
+ };
14
+ declare var __VLS_10: {}, __VLS_12: {}, __VLS_14: {}, __VLS_16: {};
15
+ type __VLS_Slots = {} & {
16
+ leading?: (props: typeof __VLS_10) => any;
17
+ } & {
18
+ default?: (props: typeof __VLS_12) => any;
19
+ } & {
20
+ description?: (props: typeof __VLS_14) => any;
21
+ } & {
22
+ value?: (props: typeof __VLS_16) => any;
23
+ };
24
+ declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
25
+ click: (event: MouseEvent) => any;
26
+ }, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
27
+ onClick?: ((event: MouseEvent) => any) | undefined;
28
+ }>, {
29
+ disabled: boolean;
30
+ chevron: boolean;
31
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
32
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
33
+ declare const _default: typeof __VLS_export;
34
+ export default _default;
35
+ type __VLS_WithSlots<T, S> = T & {
36
+ new (): {
37
+ $slots: S;
38
+ };
39
+ };
@@ -67,8 +67,8 @@ declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {
67
67
  onResizeEnd?: ((value: number) => any) | undefined;
68
68
  onPrevious?: (() => any) | undefined;
69
69
  }>, {
70
- open: boolean;
71
70
  width: number;
71
+ open: boolean;
72
72
  initialFocus: "field" | "panel" | "none";
73
73
  minWidth: number;
74
74
  compactBelow: number;
@@ -31,8 +31,8 @@ declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {
31
31
  "onUpdate:modelValue"?: ((value: number) => any) | undefined;
32
32
  }>, {
33
33
  label: string;
34
- modelValue: number;
35
34
  orientation: "horizontal" | "vertical";
35
+ modelValue: number;
36
36
  gap: number;
37
37
  storageKey: string;
38
38
  defaultSize: number;
@@ -24,8 +24,8 @@ declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {
24
24
  }, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
25
25
  "onUpdate:open"?: ((value: boolean) => any) | undefined;
26
26
  }>, {
27
- open: boolean;
28
27
  offset: number;
28
+ open: boolean;
29
29
  side: "top" | "right" | "bottom" | "left";
30
30
  align: "start" | "center" | "end";
31
31
  defaultOpen: boolean;
@@ -1,6 +1,6 @@
1
1
  import { ClickHandlers } from '../../../composables/pendingClick';
2
2
  import { ButtonKind } from '../../../composables/buttonKind';
3
- type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger' | 'danger-outline';
3
+ type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger' | 'danger-outline' | 'text';
4
4
  type ButtonSize = 'sm' | 'md' | 'lg';
5
5
  type __VLS_Props = {
6
6
  variant?: ButtonVariant;
@@ -14,6 +14,11 @@ type __VLS_Props = {
14
14
  kind?: ButtonKind;
15
15
  loading?: boolean;
16
16
  disabled?: boolean;
17
+ /**
18
+ * A trailing › that says the action opens a next level (a picker, a page).
19
+ * Meant for `variant="text"`.
20
+ */
21
+ chevron?: boolean;
17
22
  type?: 'button' | 'submit' | 'reset';
18
23
  /** A listener that returns a promise shows the loading state until it settles. */
19
24
  onClick?: ClickHandlers;
@@ -28,6 +33,7 @@ declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {}, {}, {},
28
33
  variant: ButtonVariant;
29
34
  loading: boolean;
30
35
  disabled: boolean;
36
+ chevron: boolean;
31
37
  }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
32
38
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
33
39
  declare const _default: typeof __VLS_export;
@@ -16,8 +16,8 @@ declare const __VLS_export: import('vue').DefineComponent<__VLS_Props, {}, {}, {
16
16
  "onUpdate:modelValue"?: ((value: string[]) => any) | undefined;
17
17
  }>, {
18
18
  disabled: boolean;
19
- modelValue: string[];
20
19
  orientation: "vertical" | "horizontal";
20
+ modelValue: string[];
21
21
  }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
22
22
  declare const _default: typeof __VLS_export;
23
23
  export default _default;
@@ -16,6 +16,12 @@ export type DropdownMenuSubEntry = {
16
16
  submenu?: boolean;
17
17
  /** Nested entries render as a submenu. */
18
18
  children?: DropdownMenuEntry[];
19
+ /**
20
+ * In AppLayout's `headerActions`: this entry is the phone bar's own button
21
+ * (its icon, or else its label) instead of a row in "…". Only the first
22
+ * such entry, and only while the page puts no actions in the bar.
23
+ */
24
+ primary?: boolean;
19
25
  };
20
26
  export type DropdownMenuCheckboxEntry = {
21
27
  type: 'checkbox';
@@ -91,9 +91,9 @@ declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {
91
91
  "onUpdate:pageSize"?: ((pageSize: number) => any) | undefined;
92
92
  onChangePage?: ((payload: PaginationSwitch) => any) | undefined;
93
93
  }>, {
94
- modelValue: number;
95
94
  end: string | number;
96
95
  mode: "normal" | "less" | "phone";
96
+ modelValue: number;
97
97
  labels: Partial<PaginationLabels>;
98
98
  start: string | number;
99
99
  hasNext: boolean;
@@ -23,8 +23,8 @@ declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {}, {}, {},
23
23
  onCloseAutoFocus?: ((event: Event) => any) | undefined;
24
24
  "onUpdate:open"?: ((value: boolean) => any) | undefined;
25
25
  }>, {
26
- open: boolean;
27
26
  width: string;
27
+ open: boolean;
28
28
  side: "top" | "right" | "bottom" | "left";
29
29
  align: "start" | "center" | "end";
30
30
  motion: OverlayMotion;
@@ -13,8 +13,8 @@ type __VLS_Props = {
13
13
  };
14
14
  declare const __VLS_export: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
15
15
  size: "md" | "sm";
16
- value: number;
17
16
  max: number;
17
+ value: number;
18
18
  indeterminate: boolean;
19
19
  tone: "primary" | "success" | "warning" | "danger";
20
20
  }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
@@ -1,4 +1,4 @@
1
- export declare const TOOLTIP_CONTENT_CLASS = "z-[1450] max-w-[min(18rem,var(--reka-popper-available-width,18rem))] rounded-[var(--ui-radius-sm)] bg-[var(--ui-tooltip-bg)] px-3 py-1.5 text-[12px] leading-4 text-[var(--ui-tooltip-fg)] whitespace-pre-line shadow-[var(--ui-shadow-menu)] [overflow-wrap:anywhere] animate-[ui-fade-in_var(--ui-motion-fast)_var(--ui-motion-ease)]";
1
+ export declare const TOOLTIP_CONTENT_CLASS = "z-[1450] max-w-[min(18rem,var(--reka-popper-available-width,18rem))] rounded-[var(--ui-radius-sm)] bg-[var(--ui-tooltip-bg)] px-3 py-1.5 text-[length:var(--ui-font-size-sm)] leading-4 text-[var(--ui-tooltip-fg)] whitespace-pre-line shadow-[var(--ui-shadow-menu)] [overflow-wrap:anywhere] animate-[ui-fade-in_var(--ui-motion-fast)_var(--ui-motion-ease)]";
2
2
  /** Gap between a tooltip and its trigger. */
3
3
  export declare const TOOLTIP_OFFSET = 6;
4
4
  /** The first tooltip waits this long (interaction-spec §1.1). */
@@ -68,8 +68,8 @@ declare const __VLS_export: import('vue').DefineComponent<{
68
68
  variant: "compact" | "full";
69
69
  loading: boolean;
70
70
  disabled: boolean;
71
- invalid: boolean;
72
71
  id: string;
72
+ invalid: boolean;
73
73
  modelValue: string;
74
74
  placeholder: string;
75
75
  ariaLabel: string;
@@ -14,9 +14,9 @@ declare const __VLS_export: import('vue').DefineComponent<__VLS_Props, {}, {}, {
14
14
  }, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
15
15
  onRetry?: (() => any) | undefined;
16
16
  }>, {
17
- message: string;
18
17
  height: string;
19
18
  width: string;
19
+ message: string;
20
20
  delay: number;
21
21
  state: "idle" | "pending" | "saved" | "error";
22
22
  }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
@@ -59,7 +59,8 @@ export declare const defaultGridTheme: IGridTheme;
59
59
  export declare const GRID_THEME_TOKENS: Record<Exclude<IGridThemeColorKey, 'themeKey' | 'fontFamily'>, string>;
60
60
  export declare const GRID_FONT_TOKENS: {
61
61
  readonly fontFamily: "--ui-font-sans";
62
- readonly fontSizeSM: "--ui-font-size";
62
+ /** Cell text: the grid's own size (14px on a phone), else the body size. */
63
+ readonly fontSizeSM: "--ui-grid-font-size";
63
64
  readonly fontSizeXS: "--ui-font-size-sm";
64
65
  };
65
66
  /** Looks up a CSS custom property, returning an empty string or undefined when it is not set. */