@synopackageland/ui 0.9.1 → 0.9.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,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.2 (2026-10-12)
4
+
5
+ One behavior change: while a Dialog floats outside its window (`dialog-bounds="viewport"`), focus that lands on the owning DSM window returns to the top Dialog, so Escape closes that Dialog instead of the window. Everything else is additive: Badge looks the same in the DSM 7 theme.
6
+ 一項行為變化:Dialog 浮出視窗外(`dialog-bounds="viewport"`)期間,落到所屬 DSM 視窗的焦點會回到最上層 Dialog,Esc 因此只關該 Dialog,不再關掉視窗。其餘皆為新增;DSM 7 主題下 Badge 外觀不變。
7
+
8
+ - `ConnectionTopology` draws paths from several sources, each through an optional relay, to one shared target, with no product wording built in. Each link is one of `active`, `pending`, `attention`, `error`, `idle` or `unknown`, shown with colour, a glyph and a word: working links flow dots toward the target, changing ones move slow dashes with a pulsing dot. Lanes are buttons that emit `select-lane` (Enter/Space; arrow keys, Home and End move between lanes); `lane-footer-<id>` and `node-<id>` slots add content. Below 600px of container width, or with `compact`, lanes stack with their own target. `pnpm check:topology` checks it in Chrome.
9
+ - 新增 `ConnectionTopology`:畫出多個來源各自經過(可省略的)中繼、連到同一個目標的路徑,元件本身不含產品用語。每段連線是 `active`、`pending`、`attention`、`error`、`idle`、`unknown` 之一,以顏色、圖示與文字表示:運作中的連線有點點流向目標,變化中的連線是慢速虛線加脈動圓點。每條 lane 是按鈕,發出 `select-lane`(Enter/Space;方向鍵、Home、End 在 lane 間移動);`lane-footer-<id>`、`node-<id>` slot 可加內容。容器寬度小於 600px 或設 `compact` 時改為直向堆疊、每條 lane 各帶目標。`pnpm check:topology` 在 Chrome 中檢查。
10
+ - `StatusDot` is a tone dot; `pulse` ripples a ring out of it. `StepList` is a static numbered list of instructions or checklist items, each with an optional status (`done`, `current`, `pending`, `error`, `warning`) and `#description`/`#action` slots.
11
+ - 新增 `StatusDot`(色調圓點,`pulse` 會向外擴散光圈)與 `StepList`(靜態編號步驟清單,可作操作說明或檢查清單;每項可帶狀態 `done`、`current`、`pending`、`error`、`warning`,並有 `#description`/`#action` slot)。
12
+ - Theme tokens `--ui-{neutral,info,success,warning,danger}-soft` and `-text` give each status tone a soft fill and the text that reads on it; `Badge` now uses them (same colours in the DSM 7 theme), so a theme recolours every tone. New keyframes `ui-flow` and `ui-pulse`; both stop under `prefers-reduced-motion`.
13
+ - 新增主題 token `--ui-{neutral,info,success,warning,danger}-soft` 與 `-text`,為每個狀態色提供淡底色與其上的文字色;`Badge` 改用它們(DSM 7 主題下顏色不變),主題可一次改掉所有色調。新增 keyframe `ui-flow`、`ui-pulse`,在 `prefers-reduced-motion` 下皆停止。
14
+ - **Behavior change.** Fix: with `dialog-bounds="viewport"`, Escape after pressing a floating Dialog no longer closes the whole DSM window. The `activate` handler's `toFront()` moved focus to the window's own anchor, so Escape skipped the Dialog and reached the window's keymap. Focus that lands on the owning window while a Dialog floats now returns to the top Dialog, so Escape closes only that Dialog. Apps need no change.
15
+ - **行為變化。** 修正:`dialog-bounds="viewport"` 下,按過浮出的 Dialog 後再按 Esc,不再關掉整個 DSM 視窗。`activate` 處理中的 `toFront()` 會把焦點移到視窗自己的錨點,Esc 因此繞過 Dialog、由視窗 keymap 處理。現在 Dialog 浮出期間,落到所屬視窗的焦點會回到最上層 Dialog,Esc 只關該 Dialog。App 不需修改。
16
+
3
17
  ## 0.9.1 (2026-10-12)
4
18
 
5
19
  Two behavior changes: a Dialog without `width` is 520px wide inside `AppRoot`, and Breadcrumb's last level no longer emits `click-item`.
package/README.md CHANGED
@@ -328,6 +328,19 @@ Components use semantic CSS tokens. Import your override stylesheet after the li
328
328
  }
329
329
  ```
330
330
 
331
+ Status tones come in pairs, a soft fill and the text that reads on it:
332
+ `--ui-{neutral,info,success,warning,danger}-soft` and `-text`. `Badge`,
333
+ `StatusDot`, `StepList` and `ConnectionTopology` use only these and the base
334
+ tones (`--ui-success`, …), so a dark theme overrides the pairs once:
335
+
336
+ ```css
337
+ [data-ui-theme='dark'] {
338
+ --ui-success-soft: #173222;
339
+ --ui-success-text: #6fdc8a;
340
+ /* … the other tones, and the surface and text tokens */
341
+ }
342
+ ```
343
+
331
344
  Density can be switched per application subtree:
332
345
 
333
346
  ```html
@@ -344,7 +357,7 @@ Primitives:
344
357
  - Input / Textarea
345
358
  - Checkbox / RadioGroup / Switch
346
359
  - Select
347
- - Badge / Separator / Spinner / LoadingMask / LoadingArea / Progress / Skeleton / Surface
360
+ - Badge / StatusDot / Separator / Spinner / LoadingMask / LoadingArea / Progress / Skeleton / Surface
348
361
  - SplitButton
349
362
  - Tooltip / Popover / DropdownMenu / ContextMenu / Dialog
350
363
  - Sheet / Drawer
@@ -356,7 +369,8 @@ DSM-style application building blocks:
356
369
 
357
370
  - SearchField
358
371
  - SettingsRow / SettingsSection
359
- - Callout / EmptyState
372
+ - Callout / EmptyState / StepList
373
+ - ConnectionTopology
360
374
  - DataTable / DataGrid
361
375
  - TreeView
362
376
  - FilePicker
@@ -385,6 +399,54 @@ by the caller, add `class="ui-tab-panel"` to a panel keyed by the selected value
385
399
  to use the same short fade. These effects and the loading ring respect the
386
400
  system's reduced-motion preference.
387
401
 
402
+ ### Status: StatusDot, StepList, ConnectionTopology
403
+
404
+ `StatusDot` is an 8px tone dot; `pulse` ripples a ring out of it for a state
405
+ that is still changing. It is decorative unless it has a `label`.
406
+
407
+ `StepList` is a static numbered list (`items: { title, description?, status? }[]`)
408
+ for instructions or a checklist. `status` (`done`, `current`, `pending`, `error`,
409
+ `warning`) recolours the marker and is read out to assistive technology;
410
+ `#description` and `#action` scoped slots add content per step. It does not
411
+ navigate; use `Wizard` for that.
412
+
413
+ `ConnectionTopology` draws paths from several sources, each through an optional
414
+ relay, to one shared target. It knows nothing about any product: the caller
415
+ maps its own states to six link states.
416
+
417
+ ```vue
418
+ <ConnectionTopology
419
+ aria-label="Connections"
420
+ :target="{ id: 'server', label: 'Server', caption: 'office-01', icon: Server }"
421
+ :lanes="[{
422
+ id: 'relay',
423
+ source: { id: 'browser', label: 'Any browser', icon: Globe },
424
+ via: { id: 'relay', label: 'Edge relay', icon: Cloud },
425
+ links: [{ state: 'active', label: 'Reachable' }, { state: 'active', label: 'Running' }],
426
+ }]"
427
+ @select-lane="open"
428
+ >
429
+ <template #lane-footer-relay>https://office.example.com</template>
430
+ </ConnectionTopology>
431
+ ```
432
+
433
+ - **States.** `active` (green, dots flow toward the target), `pending` (amber,
434
+ slow dashes and a pulsing dot), `attention` (amber, waits for the user),
435
+ `error` (red), `idle` (grey dashes), `unknown` (grey dots). Every state but
436
+ `idle` has a glyph and every link a word, so colour is never the only cue;
437
+ `link.icon` replaces the glyph (a key for a sign-in, say). A missing `label`
438
+ shows the localized state word (`text` overrides them); `label: ''` hides it.
439
+ - **Keyboard and screen readers.** Each lane is a button named after its nodes
440
+ and link states; Enter or Space emits `select-lane`, the arrow keys, Home and
441
+ End move between lanes. `selectable: false` renders plain rows.
442
+ `lane-footer-<id>` content sits below the lane button, so it may hold its own
443
+ buttons and links; `node-<id>` replaces a node's content.
444
+ - **Layout.** Below 600px of container width (or with `compact`), each lane
445
+ stacks with its own copy of the target and the link words move to a caption
446
+ line. With no lanes, one idle line shows `empty-label`.
447
+ - **Motion.** The keyframes `ui-flow` and `ui-pulse` stop under
448
+ `prefers-reduced-motion`; a working link then turns solid with an arrow.
449
+
388
450
  `FilePicker` owns file selection/validation/drop-zone UI only; applications remain responsible for upload transport.
389
451
 
390
452
  ### DataGrid
@@ -443,6 +505,7 @@ pnpm check # lint, typecheck, tests, and production build
443
505
  pnpm story:build # build the component workbench
444
506
  pnpm check:host # after pnpm build: DSM host simulation screenshots (Chrome)
445
507
  pnpm check:switch # after pnpm build: Switch knob geometry and click toggling (Chrome)
508
+ pnpm check:topology # after pnpm build: ConnectionTopology motion, keyboard and narrow layout (Chrome)
446
509
  ```
447
510
 
448
511
  `pnpm check:host` serves `tests/dsm-host` and screenshots each overlay scene in
@@ -457,6 +520,13 @@ embedded theme (also under `host-simulation.css`), checks in headless Chrome tha
457
520
  inside and centred in the track and that a real mouse click toggles an enabled switch. Screenshots
458
521
  are left in `.switch-shots/`.
459
522
 
523
+ `pnpm check:topology` renders every link state wide, on dark tokens, at 360px and
524
+ empty, on the standalone and the embedded theme. In headless Chrome it checks
525
+ that working links flow and changing ones pulse, that reduced motion stills
526
+ them, that the arrow keys and Enter select a lane with a visible focus ring, and
527
+ that the narrow layout stacks without overflow. Screenshots are left in
528
+ `.topology-shots/`.
529
+
460
530
  In the workbench, open `DSM 7 / App Interior` and `DSM 7 / Advanced Components`
461
531
  for reference compositions. See [SPEC.md](./SPEC.md) for architecture and
462
532
  contribution rules, and [src/index.ts](./src/index.ts) for public exports.
@@ -0,0 +1,40 @@
1
+ import { LinkState, TopologyLane, TopologyNode } from './types';
2
+ /**
3
+ * Paths from several sources, each through an optional relay, to one shared
4
+ * target. Each link shows its state with colour, a glyph and text; working
5
+ * links flow toward the target. Every lane is a button that emits
6
+ * `select-lane`. Below 600px of container width the lanes stack compactly.
7
+ */
8
+ type __VLS_Props = {
9
+ target: TopologyNode;
10
+ lanes: TopologyLane[];
11
+ ariaLabel?: string;
12
+ /** Always use the stacked narrow layout. */
13
+ compact?: boolean;
14
+ /** Render lanes as buttons that emit `select-lane`. */
15
+ selectable?: boolean;
16
+ emptyLabel?: string;
17
+ /** State words for links without a label. */
18
+ text?: Partial<Record<LinkState, string>>;
19
+ };
20
+ type __VLS_Slots = Record<`node-${string}`, (props: {
21
+ node: TopologyNode;
22
+ }) => unknown> & Record<`lane-footer-${string}`, (props: {
23
+ lane: TopologyLane;
24
+ }) => unknown>;
25
+ declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
26
+ "select-lane": (id: string) => any;
27
+ }, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
28
+ "onSelect-lane"?: ((id: string) => any) | undefined;
29
+ }>, {
30
+ compact: boolean;
31
+ selectable: boolean;
32
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
33
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
34
+ declare const _default: typeof __VLS_export;
35
+ export default _default;
36
+ type __VLS_WithSlots<T, S> = T & {
37
+ new (): {
38
+ $slots: S;
39
+ };
40
+ };
@@ -0,0 +1,8 @@
1
+ import { TopologyLink } from './types';
2
+ type __VLS_Props = {
3
+ link: TopologyLink;
4
+ text: string;
5
+ };
6
+ declare const __VLS_export: 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>;
7
+ declare const _default: typeof __VLS_export;
8
+ export default _default;
@@ -0,0 +1,19 @@
1
+ import { TopologyNode } from './types';
2
+ type __VLS_Props = {
3
+ node: TopologyNode;
4
+ };
5
+ declare var __VLS_1: {
6
+ node: TopologyNode;
7
+ };
8
+ type __VLS_Slots = {} & {
9
+ default?: (props: typeof __VLS_1) => 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<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
12
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
13
+ declare const _default: typeof __VLS_export;
14
+ export default _default;
15
+ type __VLS_WithSlots<T, S> = T & {
16
+ new (): {
17
+ $slots: S;
18
+ };
19
+ };
@@ -0,0 +1,30 @@
1
+ import { Component } from 'vue';
2
+ /**
3
+ * `active` works, `pending` is still changing, `attention` waits for the user,
4
+ * `error` failed, `idle` is not set up or turned off, `unknown` cannot be told.
5
+ */
6
+ export type LinkState = 'active' | 'pending' | 'attention' | 'error' | 'idle' | 'unknown';
7
+ export interface TopologyNode {
8
+ id: string;
9
+ label: string;
10
+ caption?: string;
11
+ icon?: Component;
12
+ }
13
+ export interface TopologyLink {
14
+ state: LinkState;
15
+ /** Short visible text; the state word stands in when it is missing, and an
16
+ * empty string shows none (the lane's accessible name keeps the state word). */
17
+ label?: string;
18
+ /** Replaces the state's glyph, e.g. a key for a sign-in. */
19
+ icon?: Component;
20
+ }
21
+ export interface TopologyLane {
22
+ id: string;
23
+ /** The far end, e.g. the people or devices that connect. */
24
+ source: TopologyNode;
25
+ /** The relay in between; without it the lane is a direct path. */
26
+ via?: TopologyNode;
27
+ /** Source to relay, then relay to the shared target. */
28
+ links: [TopologyLink, TopologyLink];
29
+ disabled?: boolean;
30
+ }
@@ -229,10 +229,10 @@ declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {
229
229
  draggable: boolean | DataGridDraggable;
230
230
  page: number;
231
231
  pageSize: number;
232
+ selectable: boolean;
232
233
  rowKey: string;
233
234
  defaultSort: DataGridSort | null;
234
235
  selectedKeys: string[];
235
- selectable: boolean;
236
236
  selectOnRowClick: boolean;
237
237
  singleSelect: boolean;
238
238
  localSort: boolean;
@@ -0,0 +1,29 @@
1
+ import { StepListItem, StepStatus } from './types';
2
+ /**
3
+ * A static, numbered list of steps: instructions to follow or a checklist
4
+ * whose items each carry a status. It does not navigate; see Wizard for that.
5
+ */
6
+ type __VLS_Props = {
7
+ items: StepListItem[];
8
+ /** Status words read to assistive technology. */
9
+ text?: Partial<Record<Exclude<StepStatus, 'pending'>, string>>;
10
+ };
11
+ type __VLS_Slots = {
12
+ description?: (props: {
13
+ item: StepListItem;
14
+ index: number;
15
+ }) => unknown;
16
+ action?: (props: {
17
+ item: StepListItem;
18
+ index: number;
19
+ }) => unknown;
20
+ };
21
+ 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>;
22
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
23
+ declare const _default: typeof __VLS_export;
24
+ export default _default;
25
+ type __VLS_WithSlots<T, S> = T & {
26
+ new (): {
27
+ $slots: S;
28
+ };
29
+ };
@@ -0,0 +1,7 @@
1
+ export type StepStatus = 'done' | 'current' | 'pending' | 'error' | 'warning';
2
+ export interface StepListItem {
3
+ title: string;
4
+ description?: string;
5
+ /** Defaults to `pending`. */
6
+ status?: StepStatus;
7
+ }
@@ -0,0 +1,13 @@
1
+ type __VLS_Props = {
2
+ tone?: 'neutral' | 'info' | 'success' | 'warning' | 'danger';
3
+ /** Ripple a ring out of the dot, for a state that is still changing. */
4
+ pulse?: boolean;
5
+ /** Names the state for assistive technology; without it the dot is decorative. */
6
+ label?: string;
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
+ tone: "neutral" | "info" | "success" | "warning" | "danger";
10
+ pulse: boolean;
11
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
12
+ declare const _default: typeof __VLS_export;
13
+ export default _default;
@@ -43,6 +43,15 @@ export declare const englishUiText: {
43
43
  readonly Info: "Info";
44
44
  readonly Warning: "Warning";
45
45
  readonly Error: "Error";
46
+ readonly Completed: "Completed";
47
+ readonly "Current step": "Current step";
48
+ readonly Connected: "Connected";
49
+ readonly Connecting: "Connecting";
50
+ readonly "Needs attention": "Needs attention";
51
+ readonly Failed: "Failed";
52
+ readonly "Not set up": "Not set up";
53
+ readonly Unknown: "Unknown";
54
+ readonly "No connections": "No connections";
46
55
  readonly Reset: "Reset";
47
56
  readonly Custom: "Custom";
48
57
  readonly Color: "Color";
package/dist/index.d.ts CHANGED
@@ -19,6 +19,7 @@ export { default as MultipleSelect } from './components/primitives/multiple-sele
19
19
  export { default as SuperboxSelect } from './components/primitives/superbox-select/SuperboxSelect.vue';
20
20
  export type { SelectAllowCreate, SelectCreateOptions, SelectOptionGroup, SelectOptions, SelectOptionsRecord, SelectSearch, } from './components/primitives/select/internal/types';
21
21
  export { default as Badge } from './components/primitives/badge/Badge.vue';
22
+ export { default as StatusDot } from './components/primitives/status-dot/StatusDot.vue';
22
23
  export { default as Separator } from './components/primitives/separator/Separator.vue';
23
24
  export { default as Spinner } from './components/primitives/spinner/Spinner.vue';
24
25
  export { default as LoadingMask } from './components/primitives/loading/LoadingMask.vue';
@@ -103,6 +104,10 @@ export { default as SettingsSection } from './components/composite/settings-sect
103
104
  export { default as Footbar } from './components/composite/footbar/Footbar.vue';
104
105
  export type { FootbarButton, FootbarButtonRegion, FootbarButtonSize, FootbarButtonVariant, } from './components/composite/footbar/types';
105
106
  export { default as Callout } from './components/composite/callout/Callout.vue';
107
+ export { default as StepList } from './components/composite/step-list/StepList.vue';
108
+ export type { StepListItem, StepStatus } from './components/composite/step-list/types';
109
+ export { default as ConnectionTopology } from './components/composite/connection-topology/ConnectionTopology.vue';
110
+ export type { LinkState, TopologyLane, TopologyLink, TopologyNode } from './components/composite/connection-topology/types';
106
111
  export { default as EmptyState } from './components/composite/empty-state/EmptyState.vue';
107
112
  export { default as DataTable } from './components/composite/data-table/DataTable.vue';
108
113
  export type { DataTableColumn } from './components/composite/data-table/DataTable.vue';