@dsh-workbench/shared 0.2.10 → 0.2.12

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 (90) hide show
  1. package/dist/api/dsh-client.d.ts +4 -1
  2. package/dist/components/Activity.css +1 -0
  3. package/dist/components/Card.css +4 -4
  4. package/dist/components/CardAnchor.d.ts +7 -0
  5. package/dist/components/CardAnchor.js +8 -0
  6. package/dist/components/CardAnchor.module.css +3 -0
  7. package/dist/components/CardHead.css +1 -5
  8. package/dist/components/ChoiceGroup.css +11 -2
  9. package/dist/components/Field.css +4 -0
  10. package/dist/components/MockStrip.css +11 -1
  11. package/dist/components/Note.css +4 -4
  12. package/dist/components/SelectField.css +156 -0
  13. package/dist/components/SelectField.d.ts +57 -0
  14. package/dist/components/SelectField.js +236 -0
  15. package/dist/components/SettingsSection.css +2 -2
  16. package/dist/components/StateDot.d.ts +5 -0
  17. package/dist/components/StateDot.js +10 -0
  18. package/dist/components/TextField.css +5 -0
  19. package/dist/components/icons.d.ts +1 -0
  20. package/dist/components/icons.js +13 -0
  21. package/dist/host/__tests__/fakes.d.ts +41 -0
  22. package/dist/host/__tests__/fakes.js +76 -0
  23. package/dist/host/__tests__/preflight-fixtures.d.ts +19 -0
  24. package/dist/host/__tests__/preflight-fixtures.js +47 -0
  25. package/dist/host/context-inject.d.ts +24 -0
  26. package/dist/host/context-inject.js +74 -0
  27. package/dist/host/events.d.ts +9 -0
  28. package/dist/host/events.js +70 -0
  29. package/dist/host/mount-units.d.ts +52 -0
  30. package/dist/host/mount-units.js +186 -0
  31. package/dist/host/preflight.d.ts +58 -0
  32. package/dist/host/preflight.js +233 -0
  33. package/dist/host/probe-cache.d.ts +6 -0
  34. package/dist/host/probe-cache.js +19 -0
  35. package/dist/host/probe-libs.d.ts +45 -0
  36. package/dist/host/probe-libs.js +61 -0
  37. package/dist/host/probe-units.d.ts +17 -0
  38. package/dist/host/probe-units.js +102 -0
  39. package/dist/host/probe-verdict.d.ts +7 -0
  40. package/dist/host/probe-verdict.js +143 -0
  41. package/dist/host/tool-approval.d.ts +2 -0
  42. package/dist/host/tool-approval.js +1 -1
  43. package/dist/host/tool-kit.js +2 -2
  44. package/dist/host/tool-scope.d.ts +32 -0
  45. package/dist/host/tool-scope.js +84 -0
  46. package/dist/host/unit-readiness.d.ts +7 -0
  47. package/dist/host/unit-readiness.js +5 -0
  48. package/dist/host/unit-registry.d.ts +9 -0
  49. package/dist/host/unit-registry.js +33 -0
  50. package/dist/host/version-range.d.ts +2 -0
  51. package/dist/host/version-range.js +73 -0
  52. package/dist/host.d.ts +1 -0
  53. package/dist/host.js +1 -0
  54. package/dist/i18n/en.js +1 -1
  55. package/dist/i18n/index.d.ts +1 -1
  56. package/dist/i18n/zh.d.ts +1 -1
  57. package/dist/i18n/zh.js +1 -1
  58. package/dist/index.d.ts +2 -1
  59. package/dist/index.js +2 -1
  60. package/dist/lib/react.d.ts +2 -0
  61. package/dist/lib/react.js +2 -0
  62. package/package.json +1 -1
  63. package/src/api/dsh-client.ts +5 -1
  64. package/src/components/Activity.css +1 -0
  65. package/src/components/Card.css +4 -4
  66. package/src/components/Card.test.tsx +46 -0
  67. package/src/components/CardAnchor.module.css +3 -0
  68. package/src/components/CardAnchor.test.tsx +36 -0
  69. package/src/components/CardAnchor.tsx +20 -0
  70. package/src/components/CardHead.css +1 -5
  71. package/src/components/ChoiceGroup.css +11 -2
  72. package/src/components/Field.css +4 -0
  73. package/src/components/Note.css +4 -4
  74. package/src/components/SelectField.css +156 -0
  75. package/src/components/SelectField.test.tsx +238 -0
  76. package/src/components/SelectField.tsx +486 -0
  77. package/src/components/SettingsSection.css +2 -2
  78. package/src/components/TextField.css +5 -0
  79. package/src/components/icons.tsx +19 -0
  80. package/src/host/tool-approval.ts +2 -1
  81. package/src/host/tool-kit.ts +2 -2
  82. package/src/host.ts +1 -0
  83. package/src/i18n/en.ts +1 -1
  84. package/src/i18n/zh.ts +1 -1
  85. package/src/index.ts +2 -1
  86. package/src/lib/react.ts +3 -0
  87. package/dist/lib/lint-probe.d.ts +0 -1
  88. package/dist/lib/lint-probe.js +0 -6
  89. package/src/components/MockStrip.css +0 -39
  90. package/src/components/MockStrip.tsx +0 -41
@@ -93,10 +93,13 @@ export interface DshLayoutPanels {
93
93
  export interface DshShortcutCatalog {
94
94
  readonly catalog: unknown;
95
95
  }
96
+ export interface DshSessionStartOptions {
97
+ readonly agentPreset?: string;
98
+ }
96
99
  export interface DshUiWorkspace {
97
100
  openWorkspace(workspaceId: string): Promise<unknown>;
98
101
  openSession(sessionId: string): void;
99
- startSession(workspaceId: string): void;
102
+ startSession(workspaceId: string, options?: DshSessionStartOptions): void;
100
103
  pickDirectory(): Promise<string | null>;
101
104
  }
102
105
  export interface DshWorkspaceWritten {
@@ -8,6 +8,7 @@
8
8
  }
9
9
 
10
10
  .wb-activityBlock:first-child {
11
+ padding-top: 0;
11
12
  border-top: none;
12
13
  }
13
14
 
@@ -12,9 +12,9 @@
12
12
  display: flex;
13
13
  flex-direction: column;
14
14
  gap: var(--wb-len-12);
15
- padding: var(--wb-len-12);
15
+ padding: var(--wb-len-12) var(--wb-card-gutter);
16
+ font-size: var(--dsw-font-xxs-12-font-size);
16
17
  }
17
- .wb-cardBody > :not([data-wb-bleed]) {
18
- padding-left: var(--wb-len-8);
19
- padding-right: var(--wb-len-8);
18
+ .wb-cardBleed {
19
+ margin-inline: calc(-1 * var(--wb-card-gutter));
20
20
  }
@@ -0,0 +1,7 @@
1
+ import type { ReactNode } from '../lib/react.js';
2
+ export declare function cardIdOf(unit: string | null): string | undefined;
3
+ export interface CardAnchorProps {
4
+ readonly unit: string;
5
+ readonly children?: ReactNode | undefined;
6
+ }
7
+ export declare function CardAnchor({ unit, children }: CardAnchorProps): JSX.Element;
@@ -0,0 +1,8 @@
1
+ import { h } from '../lib/react.js';
2
+ import styles from './CardAnchor.module.css';
3
+ export function cardIdOf(unit) {
4
+ return unit === null || unit === '' ? undefined : `wb-card-${unit}`;
5
+ }
6
+ export function CardAnchor({ unit, children }) {
7
+ return (h("div", { id: cardIdOf(unit), className: styles.anchor }, children));
8
+ }
@@ -0,0 +1,3 @@
1
+ .anchor {
2
+ display: contents;
3
+ }
@@ -4,7 +4,7 @@
4
4
  align-items: center;
5
5
  gap: var(--wb-len-6);
6
6
  flex: none;
7
- padding: var(--wb-len-8) var(--wb-len-10) var(--wb-len-8) var(--wb-len-20);
7
+ padding: var(--wb-len-8) var(--wb-len-10) var(--wb-len-8) var(--wb-card-gutter);
8
8
  border-bottom: 0.5px solid var(--dsw-alias-border-l2);
9
9
  background: var(--dsw-alias-bg-layer-2);
10
10
  }
@@ -86,7 +86,3 @@
86
86
  .wb-cardGear:hover:not(:disabled) {
87
87
  color: var(--dsw-alias-label-primary);
88
88
  }
89
-
90
- .wb-cardRefresh.wb-spinning svg {
91
- animation: wb-spin var(--wb-dur-0-9) linear infinite;
92
- }
@@ -62,7 +62,7 @@
62
62
  }
63
63
 
64
64
  .wb-checkbox:disabled + .wb-checkboxBox {
65
- opacity: 0.5;
65
+ cursor: default;
66
66
  }
67
67
 
68
68
  .wb-checkbox:focus-visible + .wb-checkboxBox {
@@ -75,6 +75,16 @@
75
75
  background: var(--dsw-alias-interactive-bg-hover);
76
76
  }
77
77
 
78
+ .wb-choiceOption:has(:disabled) {
79
+ cursor: default;
80
+ opacity: 0.5;
81
+ }
82
+
83
+ .wb-choiceOption:has(:disabled),
84
+ .wb-choiceOption:has(:disabled):hover {
85
+ background: transparent;
86
+ }
87
+
78
88
  .wb-choiceLabel {
79
89
  flex: 1;
80
90
  min-width: 0;
@@ -115,7 +125,6 @@
115
125
 
116
126
  .wb-radio:disabled {
117
127
  cursor: default;
118
- opacity: 0.5;
119
128
  }
120
129
 
121
130
  .wb-radio:focus-visible {
@@ -14,3 +14,7 @@
14
14
  line-height: var(--wb-line-18);
15
15
  color: var(--dsw-alias-label-tertiary);
16
16
  }
17
+
18
+ .wb-field:has(:disabled) .wb-fieldLabel {
19
+ opacity: 0.4;
20
+ }
@@ -36,4 +36,14 @@
36
36
  border-color: transparent;
37
37
  background: var(--dsw-alias-brand-primary);
38
38
  color: var(--dsw-alias-label-primary-foreground);
39
- }
39
+ }
40
+
41
+ .wb-mockChipOn:hover {
42
+ border-color: transparent;
43
+ background: var(--dsw-alias-brand-primary);
44
+ color: var(--dsw-alias-label-primary-foreground);
45
+ }
46
+
47
+ .wb-panelBody.wb-panelMany .wb-mockStrip {
48
+ display: none;
49
+ }
@@ -1,14 +1,14 @@
1
1
 
2
2
  .wb-note {
3
3
  margin: 0;
4
- font-size: var(--dsw-font-xxxs-11-font-size);
5
- line-height: var(--wb-line-16);
4
+ font-size: var(--dsw-font-xxs-12-font-size);
5
+ line-height: var(--wb-line-18);
6
6
  color: var(--dsw-alias-label-tertiary);
7
7
  }
8
8
  .wb-problem {
9
9
  margin: 0;
10
- font-size: var(--dsw-font-xxxs-11-font-size);
11
- line-height: var(--wb-line-16);
10
+ font-size: var(--dsw-font-xxs-12-font-size);
11
+ line-height: var(--wb-line-18);
12
12
  color: var(--dsw-alias-state-error-primary);
13
13
  overflow-wrap: anywhere;
14
14
  }
@@ -0,0 +1,156 @@
1
+ :where(.wb-selectTrigger) {
2
+ box-sizing: border-box;
3
+ display: flex;
4
+ align-items: center;
5
+ gap: var(--wb-len-8);
6
+ width: 100%;
7
+ min-height: var(--wb-len-32);
8
+ padding: var(--wb-len-6) var(--wb-len-12);
9
+ border: 0.5px solid var(--dsw-alias-border-l4);
10
+ border-radius: var(--dsw-radius-md);
11
+ background: var(--dsw-alias-bg-layer-3);
12
+ font: var(--dsw-font-xs-13);
13
+ color: var(--dsw-alias-label-primary);
14
+ text-align: left;
15
+ cursor: pointer;
16
+ }
17
+
18
+ .wb-selectTrigger:focus-visible {
19
+ outline: none;
20
+ border-color: var(--dsw-alias-state-business-primary);
21
+ box-shadow: inset 0 0 0 0.5px var(--dsw-alias-state-business-primary);
22
+ }
23
+
24
+ .wb-selectTrigger:disabled {
25
+ cursor: not-allowed;
26
+ opacity: 0.4;
27
+ }
28
+
29
+ .wb-selectLoading:disabled {
30
+ cursor: default;
31
+ opacity: 1;
32
+ }
33
+
34
+ .wb-selectGhost {
35
+ gap: var(--wb-gap-xs);
36
+ min-height: 0;
37
+ height: var(--wb-len-34);
38
+ padding: 0 var(--wb-len-8);
39
+ border: none;
40
+ background: transparent;
41
+ font-size: var(--wb-font-row);
42
+ }
43
+
44
+ .wb-selectGhost:hover:not(:disabled) {
45
+ background: var(--dsw-alias-interactive-bg-hover);
46
+ }
47
+
48
+ .wb-selectGhost:focus-visible {
49
+ outline: var(--dsw-focus-ring-width) solid
50
+ var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));
51
+ outline-offset: -2px;
52
+ border-color: transparent;
53
+ box-shadow: none;
54
+ }
55
+
56
+ .wb-selectAnchor {
57
+ display: block;
58
+ width: 100%;
59
+ }
60
+
61
+ .wb-selectIcon {
62
+ flex: none;
63
+ display: inline-flex;
64
+ align-items: center;
65
+ justify-content: center;
66
+ width: var(--wb-len-16);
67
+ height: var(--wb-len-20);
68
+ color: var(--dsw-alias-label-tertiary);
69
+ }
70
+
71
+ .wb-selectValue {
72
+ flex: 1;
73
+ min-width: 0;
74
+ display: flex;
75
+ align-items: baseline;
76
+ gap: var(--wb-len-6);
77
+ line-height: var(--wb-line-20);
78
+ overflow: hidden;
79
+ }
80
+
81
+ .wb-selectPlaceholder {
82
+ color: var(--dsw-alias-label-tertiary);
83
+ }
84
+
85
+ .wb-selectChevron {
86
+ flex: none;
87
+ display: inline-flex;
88
+ color: var(--dsw-alias-label-caption);
89
+ }
90
+
91
+ .wb-selectMenu {
92
+ min-width: var(--wb-len-240);
93
+ max-width: var(--wb-len-360);
94
+ max-height: min(var(--wb-len-320), calc(100vh - var(--wb-len-24)));
95
+ }
96
+
97
+ :where(.wb-selectSearch) {
98
+ box-sizing: border-box;
99
+ display: flex;
100
+ align-items: center;
101
+ gap: var(--wb-len-8);
102
+ width: 100%;
103
+ min-height: var(--wb-len-32);
104
+ padding: var(--wb-len-6) var(--wb-len-12);
105
+ border: 0.5px solid var(--dsw-alias-border-l4);
106
+ border-radius: var(--dsw-radius-md);
107
+ background: var(--dsw-alias-bg-layer-3);
108
+ }
109
+
110
+ .wb-selectSearch:focus-within {
111
+ border-color: var(--dsw-alias-state-business-primary);
112
+ box-shadow: inset 0 0 0 0.5px var(--dsw-alias-state-business-primary);
113
+ }
114
+
115
+ .wb-selectSearch.wb-selectGhost {
116
+ gap: var(--wb-gap-xs);
117
+ min-height: 0;
118
+ height: var(--wb-len-34);
119
+ padding: 0 var(--wb-len-8);
120
+ border: 0.5px solid var(--dsw-alias-border-l3);
121
+ background: var(--dsw-alias-bg-layer-2);
122
+ }
123
+
124
+ .wb-selectSearch.wb-selectGhost:focus-within {
125
+ border-color: var(--dsw-alias-state-business-primary);
126
+ box-shadow: none;
127
+ }
128
+
129
+ .wb-selectSearchIcon {
130
+ flex: none;
131
+ display: inline-flex;
132
+ color: var(--dsw-alias-label-tertiary);
133
+ }
134
+
135
+ .wb-textField.wb-selectSearchInput {
136
+ flex: 1;
137
+ min-width: 0;
138
+ min-height: 0;
139
+ padding: 0;
140
+ border: none;
141
+ background: transparent;
142
+ }
143
+
144
+ .wb-textField.wb-selectSearchInput:focus {
145
+ box-shadow: none;
146
+ }
147
+
148
+ .wb-selectFallback {
149
+ width: 100%;
150
+ }
151
+
152
+ .wb-selectHint {
153
+ font-size: var(--dsw-font-xxs-12-font-size);
154
+ line-height: var(--wb-line-18);
155
+ color: var(--dsw-alias-label-tertiary);
156
+ }
@@ -0,0 +1,57 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { MenuEntry } from '@deepseek-ai/dsh-client-ui-primitives';
3
+ import './SelectField.css';
4
+ export interface SelectFieldOption {
5
+ readonly value: string;
6
+ readonly label: ReactNode;
7
+ readonly searchText?: string | undefined;
8
+ readonly icon?: ReactNode | undefined;
9
+ readonly group?: string | undefined;
10
+ readonly disabled?: boolean | undefined;
11
+ }
12
+ export interface SelectFieldFallback {
13
+ readonly value: string;
14
+ readonly onChange: (next: string) => void;
15
+ }
16
+ export interface SelectFieldFooter {
17
+ readonly label: ReactNode;
18
+ readonly icon?: ReactNode | undefined;
19
+ }
20
+ export interface SelectFieldProps {
21
+ readonly value: string;
22
+ readonly options: readonly SelectFieldOption[];
23
+ readonly onChange: (value: string) => void;
24
+ readonly label?: ReactNode | undefined;
25
+ readonly hint?: ReactNode | undefined;
26
+ readonly icon?: ReactNode | undefined;
27
+ readonly renderValue?: ((current: SelectFieldOption | undefined) => ReactNode) | undefined;
28
+ readonly placeholder?: ReactNode | undefined;
29
+ readonly search?: boolean | undefined;
30
+ readonly searchLabel?: string | undefined;
31
+ readonly searchPlaceholder?: string | undefined;
32
+ readonly filter?: ((option: SelectFieldOption, query: string) => boolean) | undefined;
33
+ readonly emptyText?: string | ((query: string) => string) | undefined;
34
+ readonly open?: boolean | undefined;
35
+ readonly onOpenChange?: ((open: boolean) => void) | undefined;
36
+ readonly disabled?: boolean | undefined;
37
+ readonly loading?: boolean | undefined;
38
+ readonly loadingText?: string | undefined;
39
+ readonly fallback?: SelectFieldFallback | undefined;
40
+ readonly footer?: SelectFieldFooter | undefined;
41
+ readonly onFooter?: (() => void) | undefined;
42
+ readonly selection?: 'check' | 'fill' | undefined;
43
+ readonly variant?: 'ghost' | 'boxed' | undefined;
44
+ readonly ariaLabel?: string | undefined;
45
+ readonly title?: string | undefined;
46
+ readonly id?: string | undefined;
47
+ readonly name?: string | undefined;
48
+ readonly className?: string | undefined;
49
+ readonly menuClassName?: string | undefined;
50
+ }
51
+ export declare function searchTextOf(option: SelectFieldOption): string;
52
+ export declare function defaultSelectFilter(option: SelectFieldOption, query: string): boolean;
53
+ export declare function filterSelectOptions(options: readonly SelectFieldOption[], query: string, filter?: (option: SelectFieldOption, query: string) => boolean): readonly SelectFieldOption[];
54
+ export declare function selectCursorValue(shown: readonly SelectFieldOption[], cursor: number | null): string | null;
55
+ export declare function stepSelectCursor(shown: readonly SelectFieldOption[], cursor: number | null, step: 1 | -1): number | null;
56
+ export declare function selectEntries(shown: readonly SelectFieldOption[], emptyText: string | undefined): readonly MenuEntry[];
57
+ export declare function SelectField(props: SelectFieldProps): ReactNode;
@@ -0,0 +1,236 @@
1
+ import { h, useEffect, useMemo, useRef, useState } from '../lib/react.js';
2
+ import { IconChevronDownOutlineRegular, IconSearchOutlineRegular, Menu, StateDot, TextShimmer, } from '@deepseek-ai/dsh-client-ui-primitives';
3
+ import { Field } from './Field.js';
4
+ import { TextField } from './TextField.js';
5
+ import './SelectField.css';
6
+ const FOOTER_ID = '__wb-select-footer__';
7
+ const EMPTY_ID = '__wb-select-empty__';
8
+ function mergeClass(...parts) {
9
+ return parts.filter((one) => one !== undefined && one !== '').join(' ');
10
+ }
11
+ function itemOf(option) {
12
+ return {
13
+ id: option.value,
14
+ label: option.label,
15
+ ...(option.icon === undefined ? {} : { icon: option.icon }),
16
+ ...(option.disabled === true ? { disabled: true } : {}),
17
+ };
18
+ }
19
+ function triggerNode(args) {
20
+ return (h("button", { type: "button", id: args.id, className: args.className, disabled: args.disabled, "aria-haspopup": "listbox", "aria-expanded": args.open, "aria-label": args.ariaLabel, title: args.title, onClick: args.onOpen },
21
+ args.icon === undefined ? null : h("span", { className: "wb-selectIcon" }, args.icon),
22
+ h("span", { className: "wb-selectValue" }, args.value),
23
+ h("span", { className: "wb-selectChevron" },
24
+ h(IconChevronDownOutlineRegular, { size: 14 }))));
25
+ }
26
+ function loadingNode(args) {
27
+ return (h("button", { type: "button", id: args.id, className: `${args.className} wb-selectLoading`, disabled: true, "aria-busy": "true", "aria-label": args.ariaLabel, title: args.loadingText },
28
+ h("span", { className: "wb-selectValue" },
29
+ h(TextShimmer, { active: true }, args.loadingText ?? '…')),
30
+ h("span", { className: "wb-selectChevron" },
31
+ h(StateDot, { state: "ongoing", size: 14 }))));
32
+ }
33
+ function searchNode(args) {
34
+ return (h("span", { ref: args.boxRef, className: args.className },
35
+ h("span", { className: "wb-selectSearchIcon" },
36
+ h(IconSearchOutlineRegular, { size: 14 })),
37
+ h(TextField, { id: args.id, name: args.name, className: "wb-selectSearchInput", "aria-label": args.ariaLabel, placeholder: args.placeholder, value: args.query, onChange: (event) => args.onQuery(event.currentTarget.value), onKeyDown: (event) => {
38
+ if (event.nativeEvent.isComposing)
39
+ return;
40
+ args.onKey(event);
41
+ } })));
42
+ }
43
+ function menuNode(args) {
44
+ return (h(Menu, { open: args.open, portal: true, className: "wb-selectAnchor", listClassName: args.listClassName, anchor: args.anchor, align: "start", side: "bottom", selection: args.selection, selectedId: args.selectedId, items: args.items, footer: args.footer, onSelect: args.onPick, onClose: args.onCancel }));
45
+ }
46
+ function framed(label, hint, className, control) {
47
+ const inner = label === undefined
48
+ ? hint === undefined
49
+ ? control
50
+ : (h("span", { className: "wb-select" },
51
+ control,
52
+ h("span", { className: "wb-selectHint" }, hint)))
53
+ : (h(Field, { label: label, hint: hint }, control));
54
+ return className === undefined ? inner : h("div", { className: className }, inner);
55
+ }
56
+ export function searchTextOf(option) {
57
+ if (option.searchText !== undefined)
58
+ return option.searchText;
59
+ return typeof option.label === 'string' ? option.label : '';
60
+ }
61
+ export function defaultSelectFilter(option, query) {
62
+ const needle = query.trim().toLowerCase();
63
+ if (needle === '')
64
+ return true;
65
+ return searchTextOf(option).toLowerCase().includes(needle);
66
+ }
67
+ export function filterSelectOptions(options, query, filter = defaultSelectFilter) {
68
+ return options.filter((option) => filter(option, query));
69
+ }
70
+ export function selectCursorValue(shown, cursor) {
71
+ if (cursor === null || shown.length === 0)
72
+ return null;
73
+ return shown[Math.min(cursor, shown.length - 1)]?.value ?? null;
74
+ }
75
+ export function stepSelectCursor(shown, cursor, step) {
76
+ if (shown.length === 0)
77
+ return null;
78
+ if (cursor === null)
79
+ return step === 1 ? 0 : shown.length - 1;
80
+ return (cursor + step + shown.length) % shown.length;
81
+ }
82
+ export function selectEntries(shown, emptyText) {
83
+ if (shown.length === 0) {
84
+ return emptyText === undefined ? [] : [{ type: 'label', id: EMPTY_ID, text: emptyText }];
85
+ }
86
+ const out = [];
87
+ let group = null;
88
+ for (const option of shown) {
89
+ if (option.group !== undefined && option.group !== group) {
90
+ out.push({ type: 'label', id: `__wb-select-group-${String(out.length)}`, text: option.group });
91
+ group = option.group;
92
+ }
93
+ out.push(itemOf(option));
94
+ }
95
+ return out;
96
+ }
97
+ function useSelectState(props) {
98
+ const { options, filter = defaultSelectFilter, emptyText, open: openProp, onOpenChange } = props;
99
+ const [openState, setOpenState] = useState(false);
100
+ const [query, setQuery] = useState('');
101
+ const [cursor, setCursor] = useState(null);
102
+ const boxRef = useRef(null);
103
+ const triggerRef = useRef(null);
104
+ const wasOpenRef = useRef(false);
105
+ const pickedRef = useRef(false);
106
+ const closedByUsRef = useRef(false);
107
+ const open = openProp ?? openState;
108
+ const setOpen = (next) => {
109
+ if (openProp === undefined)
110
+ setOpenState(next);
111
+ onOpenChange?.(next);
112
+ };
113
+ useEffect(() => {
114
+ if (!open || props.search === false)
115
+ return;
116
+ boxRef.current?.querySelector('input')?.focus();
117
+ }, [open, props.search]);
118
+ useEffect(() => {
119
+ if (open) {
120
+ wasOpenRef.current = true;
121
+ return;
122
+ }
123
+ if (!wasOpenRef.current)
124
+ return;
125
+ wasOpenRef.current = false;
126
+ const closedByUs = closedByUsRef.current;
127
+ const picked = pickedRef.current;
128
+ closedByUsRef.current = false;
129
+ pickedRef.current = false;
130
+ if (!closedByUs || picked)
131
+ return;
132
+ triggerRef.current?.focus();
133
+ }, [open]);
134
+ const shown = useMemo(() => filterSelectOptions(options, query, filter), [options, query, filter]);
135
+ const close = (picked) => {
136
+ closedByUsRef.current = true;
137
+ pickedRef.current = picked;
138
+ setOpen(false);
139
+ setQuery('');
140
+ setCursor(null);
141
+ };
142
+ return {
143
+ open,
144
+ setOpen,
145
+ query,
146
+ cursor,
147
+ setCursor,
148
+ setQuery,
149
+ shown,
150
+ close,
151
+ boxRef,
152
+ triggerRef,
153
+ empty: typeof emptyText === 'function' ? emptyText(query) : emptyText,
154
+ };
155
+ }
156
+ export function SelectField(props) {
157
+ const { value, options, onChange, label, hint, icon, renderValue, placeholder, search = true, searchLabel, searchPlaceholder, footer, onFooter, disabled = false, loading = false, loadingText, fallback, selection = 'fill', variant = 'boxed', ariaLabel, title, id, name, className, menuClassName, } = props;
158
+ const state = useSelectState(props);
159
+ const current = options.find((option) => option.value === value);
160
+ const variantClass = variant === 'ghost' ? 'wb-selectGhost' : undefined;
161
+ const triggerClass = mergeClass('wb-selectTrigger', variantClass);
162
+ if (loading) {
163
+ return framed(label, hint, className, loadingNode({ id, className: triggerClass, ariaLabel, loadingText }));
164
+ }
165
+ if (fallback !== undefined && options.length === 0) {
166
+ return framed(label, hint, className, h(TextField, { id: id, name: name, className: "wb-selectFallback", "aria-label": ariaLabel, value: fallback.value, onChange: (event) => fallback.onChange(event.currentTarget.value) }));
167
+ }
168
+ const trigger = triggerNode({
169
+ id,
170
+ className: triggerClass,
171
+ disabled,
172
+ open: state.open,
173
+ ariaLabel,
174
+ title,
175
+ icon,
176
+ value: current === undefined ? (h("span", { className: "wb-selectPlaceholder" }, placeholder ?? '—')) : renderValue === undefined ? (current.label) : (renderValue(current)),
177
+ onOpen: () => state.setOpen(true),
178
+ });
179
+ const anchor = state.open && search
180
+ ? searchNode({
181
+ boxRef: state.boxRef,
182
+ className: mergeClass('wb-selectSearch', variantClass),
183
+ id,
184
+ name,
185
+ ariaLabel: searchLabel,
186
+ placeholder: searchPlaceholder,
187
+ query: state.query,
188
+ onQuery: (next) => {
189
+ state.setQuery(next);
190
+ state.setCursor(null);
191
+ },
192
+ onKey: (event) => {
193
+ if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
194
+ event.stopPropagation();
195
+ event.preventDefault();
196
+ state.setCursor((at) => stepSelectCursor(state.shown, at, event.key === 'ArrowDown' ? 1 : -1));
197
+ return;
198
+ }
199
+ if (event.key !== 'Enter')
200
+ return;
201
+ event.stopPropagation();
202
+ const pick = state.shown[Math.min(state.cursor ?? 0, state.shown.length - 1)];
203
+ if (pick === undefined)
204
+ return;
205
+ state.close(true);
206
+ onChange(pick.value);
207
+ },
208
+ })
209
+ : trigger;
210
+ return framed(label, hint, className, menuNode({
211
+ open: state.open,
212
+ listClassName: mergeClass('wb-selectMenu', menuClassName),
213
+ anchor,
214
+ selection,
215
+ selectedId: selectCursorValue(state.shown, state.cursor) ?? undefined,
216
+ items: selectEntries(state.shown, state.empty),
217
+ footer: footer === undefined
218
+ ? undefined
219
+ : [
220
+ {
221
+ id: FOOTER_ID,
222
+ label: footer.label,
223
+ ...(footer.icon === undefined ? {} : { icon: footer.icon }),
224
+ },
225
+ ],
226
+ onPick: (picked) => {
227
+ state.close(true);
228
+ if (picked === FOOTER_ID) {
229
+ onFooter?.();
230
+ return;
231
+ }
232
+ onChange(picked);
233
+ },
234
+ onCancel: () => state.close(false),
235
+ }));
236
+ }
@@ -3,7 +3,7 @@ section.wb-settings {
3
3
  display: flex;
4
4
  flex-direction: column;
5
5
  gap: var(--wb-len-12);
6
- padding: 0;
6
+ padding: var(--wb-len-12) var(--wb-len-16);
7
7
  border: 0;
8
8
  border-radius: var(--dsw-radius-md);
9
9
  background: var(--dsw-alias-settings-card-fill);
@@ -19,7 +19,7 @@ section.wb-settings {
19
19
  overflow: hidden;
20
20
  text-overflow: ellipsis;
21
21
  white-space: nowrap;
22
- font-size: var(--dsw-font-base-16-font-size);
22
+ font-size: var(--dsw-font-s-strong-14-font-size);
23
23
  line-height: var(--wb-line-24);
24
24
  font-weight: 500;
25
25
  color: var(--dsw-alias-label-primary);
@@ -0,0 +1,5 @@
1
+ export type DotState = 'ongoing' | 'warning' | 'done' | 'idle' | string;
2
+ export declare function StateDot({ state, size }: {
3
+ state: DotState;
4
+ size: number;
5
+ }): JSX.Element;
@@ -0,0 +1,10 @@
1
+ import { h } from '../lib/react.js';
2
+ export function StateDot({ state, size }) {
3
+ if (state === 'ongoing') {
4
+ return (h("svg", { className: "wb-loading", viewBox: "0 0 24 24", width: size, height: size, "aria-hidden": "true", style: { display: 'block' } },
5
+ h("g", { className: "wb-loadingMotion" },
6
+ h("circle", { className: "wb-loadingTrack", cx: 12, cy: 12, r: 9.5 }),
7
+ h("circle", { className: "wb-loadingArc", cx: 12, cy: 12, r: 9.5 }))));
8
+ }
9
+ return (h("span", { className: "wb-stateDot", "data-state": state, style: { width: size, height: size }, "aria-hidden": "true" }));
10
+ }
@@ -13,6 +13,11 @@
13
13
  box-shadow: inset 0 0 0 0.5px var(--dsw-alias-state-business-primary);
14
14
  }
15
15
 
16
+ .wb-textField:disabled {
17
+ cursor: not-allowed;
18
+ opacity: 0.4;
19
+ }
20
+
16
21
  .wb-textField[aria-invalid='true'] {
17
22
  border-color: var(--dsw-alias-state-error-primary);
18
23
  }
@@ -6,4 +6,5 @@ export declare function IconBrandMark({ size }: IconProps): JSX.Element;
6
6
  export declare function IconOpencli({ size }: IconProps): JSX.Element;
7
7
  export declare function IconNpm({ size }: IconProps): JSX.Element;
8
8
  export declare function IconModules({ size }: IconProps): JSX.Element;
9
+ export declare function IconModuleMark({ size }: IconProps): JSX.Element;
9
10
  export declare function IconPanelScreen({ size }: IconProps): JSX.Element;