@synopackageland/ui 0.9.12 → 0.10.2

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,28 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.10.2 (2026-10-16)
4
+
5
+ - A `PageHeader` inside `PageContent` no longer gets the section gap on top of its own bottom padding: the title sits 12 px above the content in a DSM window again (16 px on a phone, 24 px in the web layout), not 24 px.
6
+ - Buttons in `SettingsSection` `#footer`, `Card` `#footer`, `Panel` `#fbar`, `Sheet` `#footer` and `AppActionBar` become `action` automatically, like `FormActions`, `Footbar`, `Dialog` and `Wizard` footers. An explicit `kind` still wins.
7
+ - **Behavior change:** `Tag` `removable` now defaults to `false`. DSM status and label tags are display-only, so the remove control must be opted into with `removable`; a Tag that relied on the old default loses its ×.
8
+ - Visible changes for apps: less space under page titles, and these footers draw capsule buttons. An app can drop the `kind="action"` it added to such footers, and the `:removable="false"` it set on Tags.
9
+
10
+ 繁中摘要:PageContent 裡的 PageHeader 不再把段落間距疊在自己的下方 padding 上,DSM 視窗中標題到內容回到 12px(手機 16px、web 版面 24px)。SettingsSection `#footer`、Card `#footer`、Panel `#fbar`、Sheet `#footer`、AppActionBar 的按鈕跟 FormActions/Footbar/Dialog/Wizard 頁尾一樣自動用 action;明確寫的 `kind` 仍優先。**行為變更:**Tag 的 `removable` 預設改為 `false`(DSM 狀態/標籤 Tag 只供顯示),需要移除鈕時要明確寫 `removable`;依賴舊預設的 Tag 會失去 ×。App 可刪掉自己在這些頁尾加的 `kind="action"`,以及 Tag 上的 `:removable="false"`。
11
+
12
+ ## 0.10.1 (2026-10-16)
13
+
14
+ - Components follow measured DSM 7 metrics (see `docs/dsm-native-metrics.md`).
15
+ - `Button` takes `kind="tool" | "action"` (default `tool`): tool buttons are 28 px, 3 px radius, borderless grey; action buttons are pills with an 80 px minimum width (120 px for primary). Buttons inside `Dialog` and `Wizard` footers, `FormActions` and `Footbar` become `action` automatically.
16
+ - `Dialog` without `width` sizes to its content between 420 px and 760 px; `maxWidth` raises the cap. Header, body and footer use DSM sizes. A dialog with no title and no close button uses the message-box layout. `MessageBox` also sizes to content.
17
+ - Native scrollbars inside the UI root use the DSM style: thin, rounded, faint until hovered.
18
+ - `Surface` takes `variant="inset"` for a borderless grey block inside a card; the default border is subtler. `Card` titles are bold.
19
+ - `PageContent` spaces its direct children with `--ui-section-gap`, so pages need no hand-written margins.
20
+ - Radius tokens (`sm` 3, `md` 4, `lg` 5, `dialog` 8, `pill`) replace hard-coded values; menus and popovers use `md`, menu rows `sm`.
21
+ - On a phone-width DSM window a dialog is a bottom sheet as wide as the window, also when it sizes to its content.
22
+ - Visible changes for apps: footer buttons get wider, dialogs without `width` resize to content, and hand-written margins under `PageContent` add to the new gap.
23
+
24
+ 繁中摘要:元件依實測 DSM 7 數值對齊。Button 分 tool(小圓角灰底)與 action(膠囊、最小寬 80/120px),Dialog/Wizard 頁尾等自動用 action;Dialog 未指定寬度時依內容在 420–760px 之間,`maxWidth` 可放寬;捲軸改 DSM 樣式;Surface 新增 inset 灰底區塊;Card 標題粗體;PageContent 自動段落間距;圓角統一為 token。手機寬度的 DSM 視窗中,Dialog(含依內容決定寬度者)以與視窗同寬的底部面板呈現。App 升級後頁尾按鈕變寬、Dialog 寬度隨內容、PageContent 下手寫 margin 會與新間距疊加。
25
+
3
26
  ## 0.9.12 (2026-10-16)
4
27
 
5
28
  - Fix InfoTip first-click toggling inside AppRoot app modal scope when an ancestor focuses the trigger during pointerdown capture. Keyboard focus still opens the bubble; pointer clicks and adaptive touch toggle it once. AppRoot focus ownership and modal isolation remain intact.
package/README.md CHANGED
@@ -257,7 +257,12 @@ const open = ref(false)
257
257
  </template>
258
258
  ```
259
259
 
260
- `width` (default `520px`) is the panel's maximum width. `height` or
260
+ Without `width` the dialog sizes to its content between
261
+ `--ui-dialog-min-width` (420px) and `--ui-dialog-max-width` (760px, or the
262
+ `max-width` prop); a long paragraph wraps at the maximum. `width` sets a fixed
263
+ width, capped to the viewport. A dialog with no title and no close button is a
264
+ question and takes DSM's message-box layout. Buttons in the `footer` slot are
265
+ capsule `action` buttons (`kind="tool"` opts one out). `height` or
261
266
  `min-height` (for example `min-height="420px"`) keeps a panel with little
262
267
  content from collapsing. In a DSM window, `<AppRoot dialog-bounds="viewport">`
263
268
  lets dialogs be dragged out of the window, within the browser viewport. See
@@ -318,6 +323,14 @@ import { AppLayout, Button, PageHeader } from '@synopackageland/ui'
318
323
 
319
324
  `AppLayout` also accepts `sidebar`, `toolbar`, and `footer` slots.
320
325
 
326
+ `PageContent` stacks its direct children (a search row, a toolbar, cards) at
327
+ `--ui-section-gap`, so a page needs no margins between them. Under a
328
+ `PageHeader` the space is the header's own bottom padding (12px in a DSM
329
+ window), with no section gap added.
330
+
331
+ To set a block apart inside a card, use `<Surface variant="inset">`. DSM draws it as a
332
+ faint grey block with no border of its own, at 12px × 16px padding.
333
+
321
334
  ## Theme override
322
335
 
323
336
  Components use semantic CSS tokens. Import your override stylesheet after the library stylesheet to customize branding:
@@ -10,7 +10,7 @@ type __VLS_Props = {
10
10
  /** Bound with `v-model:open` for controlled collapse. */
11
11
  open?: boolean;
12
12
  };
13
- declare var __VLS_6: {}, __VLS_8: {}, __VLS_10: {}, __VLS_12: {}, __VLS_14: {}, __VLS_21: {}, __VLS_23: {};
13
+ declare var __VLS_6: {}, __VLS_8: {}, __VLS_10: {}, __VLS_12: {}, __VLS_14: {}, __VLS_21: {}, __VLS_29: {};
14
14
  type __VLS_Slots = {} & {
15
15
  title?: (props: typeof __VLS_6) => any;
16
16
  } & {
@@ -24,7 +24,7 @@ type __VLS_Slots = {} & {
24
24
  } & {
25
25
  default?: (props: typeof __VLS_21) => any;
26
26
  } & {
27
- footer?: (props: typeof __VLS_23) => any;
27
+ footer?: (props: typeof __VLS_29) => any;
28
28
  };
29
29
  declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
30
30
  "update:open": (value: boolean) => any;
@@ -13,7 +13,7 @@ declare function toNextStep(): Promise<void>;
13
13
  declare function toPreviousStep(): Promise<void>;
14
14
  declare function finish(): Promise<void>;
15
15
  declare function cancel(): void;
16
- declare var __VLS_1: {}, __VLS_3: {
16
+ declare var __VLS_1: {}, __VLS_9: {
17
17
  nextStep: typeof toNextStep;
18
18
  previousStep: typeof toPreviousStep;
19
19
  finish: typeof finish;
@@ -25,7 +25,7 @@ declare var __VLS_1: {}, __VLS_3: {
25
25
  type __VLS_Slots = {} & {
26
26
  default?: (props: typeof __VLS_1) => any;
27
27
  } & {
28
- footer?: (props: typeof __VLS_3) => any;
28
+ footer?: (props: typeof __VLS_9) => any;
29
29
  };
30
30
  declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {
31
31
  activeStepKey: import('vue').ComputedRef<string>;
@@ -14,7 +14,7 @@ type __VLS_Props = {
14
14
  /** Shows a skeleton in place of the body and footer while they load. */
15
15
  loading?: boolean;
16
16
  };
17
- declare var __VLS_8: {}, __VLS_10: {}, __VLS_22: {}, __VLS_24: {}, __VLS_26: {};
17
+ declare var __VLS_8: {}, __VLS_10: {}, __VLS_22: {}, __VLS_24: {}, __VLS_32: {};
18
18
  type __VLS_Slots = {} & {
19
19
  title?: (props: typeof __VLS_8) => any;
20
20
  } & {
@@ -24,17 +24,17 @@ type __VLS_Slots = {} & {
24
24
  } & {
25
25
  default?: (props: typeof __VLS_24) => any;
26
26
  } & {
27
- footer?: (props: typeof __VLS_26) => any;
27
+ footer?: (props: typeof __VLS_32) => any;
28
28
  };
29
29
  declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
30
30
  "update:expanded": (value: boolean) => any;
31
31
  }, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
32
32
  "onUpdate:expanded"?: ((value: boolean) => any) | undefined;
33
33
  }>, {
34
+ hasHeader: boolean;
34
35
  expandable: boolean;
35
36
  expanded: boolean;
36
37
  layoutType: "regular" | "compact";
37
- hasHeader: boolean;
38
38
  footerAlign: "start" | "end" | "between";
39
39
  }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
40
40
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
@@ -18,7 +18,7 @@ type __VLS_Props = {
18
18
  /** Make the default body slot scroll when its content overflows. */
19
19
  scrollable?: boolean;
20
20
  };
21
- declare var __VLS_1: {}, __VLS_3: {}, __VLS_5: {}, __VLS_7: {}, __VLS_9: {}, __VLS_18: {};
21
+ declare var __VLS_1: {}, __VLS_3: {}, __VLS_5: {}, __VLS_7: {}, __VLS_15: {}, __VLS_24: {};
22
22
  type __VLS_Slots = {} & {
23
23
  tbar?: (props: typeof __VLS_1) => any;
24
24
  } & {
@@ -28,9 +28,9 @@ type __VLS_Slots = {} & {
28
28
  } & {
29
29
  default?: (props: typeof __VLS_7) => any;
30
30
  } & {
31
- fbar?: (props: typeof __VLS_9) => any;
31
+ fbar?: (props: typeof __VLS_15) => any;
32
32
  } & {
33
- 'spin-inner'?: (props: typeof __VLS_18) => any;
33
+ 'spin-inner'?: (props: typeof __VLS_24) => any;
34
34
  };
35
35
  declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {
36
36
  getScrollEl: () => HTMLElement | null;
@@ -1,9 +1,16 @@
1
1
  import { ClickHandlers } from '../../../composables/pendingClick';
2
+ import { ButtonKind } from '../../../composables/buttonKind';
2
3
  type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger';
3
4
  type ButtonSize = 'sm' | 'md' | 'lg';
4
5
  type __VLS_Props = {
5
6
  variant?: ButtonVariant;
6
7
  size?: ButtonSize;
8
+ /**
9
+ * `tool` (toolbar and in-form commands) or `action` (a footer's capsule
10
+ * with a minimum width). Defaults to what the surrounding footer provides,
11
+ * else `tool`.
12
+ */
13
+ kind?: ButtonKind;
7
14
  loading?: boolean;
8
15
  disabled?: boolean;
9
16
  type?: 'button' | 'submit' | 'reset';
@@ -0,0 +1,17 @@
1
+ import { ButtonKind } from '../../../composables/buttonKind';
2
+ type __VLS_Props = {
3
+ kind: ButtonKind;
4
+ };
5
+ declare var __VLS_1: {};
6
+ type __VLS_Slots = {} & {
7
+ default?: (props: typeof __VLS_1) => any;
8
+ };
9
+ declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
10
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
11
+ declare const _default: typeof __VLS_export;
12
+ export default _default;
13
+ type __VLS_WithSlots<T, S> = T & {
14
+ new (): {
15
+ $slots: S;
16
+ };
17
+ };
@@ -3,7 +3,10 @@ type __VLS_Props = {
3
3
  defaultOpen?: boolean;
4
4
  title?: string;
5
5
  description?: string;
6
+ /** A fixed width, capped to the viewport. Without it the dialog sizes to its content. */
6
7
  width?: string;
8
+ /** The widest a content-sized dialog grows (default `--ui-dialog-max-width`). */
9
+ maxWidth?: string;
7
10
  /** A fixed panel height, capped to the available height. */
8
11
  height?: string;
9
12
  /** Keeps the panel from shrinking with short content, capped to the available height. */
@@ -12,13 +15,13 @@ type __VLS_Props = {
12
15
  /** Hides the header close affordance for fixed-flow dialogs that own their dismiss path. */
13
16
  showClose?: boolean;
14
17
  };
15
- declare var __VLS_14: {}, __VLS_71: {}, __VLS_73: {};
18
+ declare var __VLS_14: {}, __VLS_71: {}, __VLS_79: {};
16
19
  type __VLS_Slots = {} & {
17
20
  trigger?: (props: typeof __VLS_14) => any;
18
21
  } & {
19
22
  default?: (props: typeof __VLS_71) => any;
20
23
  } & {
21
- footer?: (props: typeof __VLS_73) => any;
24
+ footer?: (props: typeof __VLS_79) => any;
22
25
  };
23
26
  declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
24
27
  escapeKeyDown: (event: KeyboardEvent) => any;
@@ -35,7 +38,6 @@ declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {}, {}, {},
35
38
  onCloseAutoFocus?: ((event: Event) => any) | undefined;
36
39
  "onUpdate:open"?: ((value: boolean) => any) | undefined;
37
40
  }>, {
38
- width: string;
39
41
  defaultOpen: boolean;
40
42
  open: boolean;
41
43
  showClose: boolean;
@@ -9,9 +9,9 @@ type __VLS_Props = {
9
9
  };
10
10
  declare const __VLS_export: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
11
11
  size: IconSize;
12
+ kind: "folder" | "file";
12
13
  name: string;
13
14
  open: boolean;
14
- kind: "folder" | "file";
15
15
  }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
16
16
  declare const _default: typeof __VLS_export;
17
17
  export default _default;
@@ -8,13 +8,13 @@ type __VLS_Props = {
8
8
  description?: string;
9
9
  closeLabel?: string;
10
10
  };
11
- declare var __VLS_14: {}, __VLS_62: {}, __VLS_64: {};
11
+ declare var __VLS_14: {}, __VLS_62: {}, __VLS_70: {};
12
12
  type __VLS_Slots = {} & {
13
13
  trigger?: (props: typeof __VLS_14) => any;
14
14
  } & {
15
15
  default?: (props: typeof __VLS_62) => any;
16
16
  } & {
17
- footer?: (props: typeof __VLS_64) => any;
17
+ footer?: (props: typeof __VLS_70) => any;
18
18
  };
19
19
  declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
20
20
  escapeKeyDown: (event: KeyboardEvent) => any;
@@ -1,4 +1,7 @@
1
1
  type __VLS_Props = {
2
+ /** `'inset'` is a grey block inside a card, with no border of its own. */
3
+ variant?: 'default' | 'inset';
4
+ /** Defaults to `'md'`; an inset area defaults to DSM's 12px by 16px. */
2
5
  padding?: 'none' | 'sm' | 'md' | 'lg';
3
6
  border?: boolean;
4
7
  raised?: boolean;
@@ -8,6 +11,7 @@ type __VLS_Slots = {} & {
8
11
  default?: (props: typeof __VLS_1) => any;
9
12
  };
10
13
  declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
14
+ variant: "default" | "inset";
11
15
  padding: "none" | "sm" | "md" | "lg";
12
16
  border: boolean;
13
17
  raised: boolean;
@@ -10,7 +10,10 @@ export interface TagVisual {
10
10
  type __VLS_Props = {
11
11
  label?: string;
12
12
  appearance?: TagAppearance;
13
- /** Shows the remove control; the caller handles the actual removal. */
13
+ /**
14
+ * Shows the remove control; the caller handles the actual removal. Off by
15
+ * default: DSM status and label tags are display-only.
16
+ */
14
17
  removable?: boolean;
15
18
  selected?: boolean;
16
19
  disabled?: boolean;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * DSM draws two button shapes: a `tool` button (toolbar, in-form commands) with
3
+ * the small control radius, and an `action` button (dialog, wizard and page
4
+ * footers) as a capsule with a minimum width.
5
+ */
6
+ export type ButtonKind = 'tool' | 'action';
7
+ /** Makes `kind` the default for every Button rendered below the caller. */
8
+ export declare function provideButtonKind(kind: ButtonKind): void;
9
+ export declare function injectButtonKind(): ButtonKind;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export { default as Button } from './components/primitives/button/Button.vue';
2
2
  export { default as IconButton } from './components/primitives/button/IconButton.vue';
3
+ export type { ButtonKind } from './composables/buttonKind';
3
4
  export { default as Input } from './components/primitives/input/Input.vue';
4
5
  export { default as Textarea } from './components/primitives/input/Textarea.vue';
5
6
  export { default as InputGroup } from './components/primitives/input-group/InputGroup.vue';