@dsh-workbench/shared 0.2.12 → 0.2.13
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/dist/components/ChoiceGroup.css +53 -3
- package/dist/components/ChoiceGroup.d.ts +5 -0
- package/dist/components/ChoiceGroup.js +15 -5
- package/dist/components/FormRows.css +53 -0
- package/dist/components/FormRows.d.ts +14 -0
- package/dist/components/FormRows.js +11 -0
- package/dist/components/SelectField.css +12 -4
- package/dist/components/SelectField.d.ts +1 -0
- package/dist/components/SelectField.js +9 -7
- package/dist/components/Spinner.css +6 -0
- package/dist/components/Spinner.d.ts +6 -0
- package/dist/components/Spinner.js +7 -0
- package/dist/components/TextField.css +2 -2
- package/dist/host.d.ts +3 -3
- package/dist/host.js +3 -3
- package/dist/index.d.ts +7 -4
- package/dist/index.js +7 -4
- package/dist/lib/form-problem.d.ts +2 -0
- package/dist/lib/form-problem.js +20 -0
- package/package.json +2 -1
- package/src/components/ChoiceGroup.css +53 -3
- package/src/components/ChoiceGroup.test.tsx +88 -0
- package/src/components/ChoiceGroup.tsx +70 -28
- package/src/components/FormRows.css +53 -0
- package/src/components/FormRows.test.tsx +82 -0
- package/src/components/FormRows.tsx +39 -0
- package/src/components/SelectField.css +12 -4
- package/src/components/SelectField.test.tsx +20 -2
- package/src/components/SelectField.tsx +11 -3
- package/src/components/Spinner.css +6 -0
- package/src/components/Spinner.test.tsx +35 -0
- package/src/components/Spinner.tsx +19 -0
- package/src/components/TextField.css +2 -2
- package/src/host.ts +3 -3
- package/src/index.ts +7 -4
- package/src/lib/form-problem.test.ts +76 -0
- package/src/lib/form-problem.ts +22 -0
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
display: flex;
|
|
4
4
|
flex-direction: column;
|
|
5
5
|
gap: var(--wb-len-2);
|
|
6
|
+
background: transparent;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.wb-choiceMenu[data-variant='boxed'] {
|
|
6
10
|
padding: var(--wb-len-4);
|
|
7
11
|
border: 0.5px solid var(--dsw-alias-border-l3);
|
|
8
12
|
border-radius: var(--dsw-radius-md);
|
|
9
|
-
background: transparent;
|
|
10
13
|
}
|
|
11
14
|
|
|
12
15
|
.wb-choiceOption {
|
|
@@ -14,8 +17,8 @@
|
|
|
14
17
|
display: flex;
|
|
15
18
|
align-items: center;
|
|
16
19
|
gap: var(--wb-len-6);
|
|
17
|
-
min-height: var(--wb-len-
|
|
18
|
-
padding: var(--wb-len-
|
|
20
|
+
min-height: var(--wb-len-28);
|
|
21
|
+
padding: var(--wb-len-4) var(--wb-len-8);
|
|
19
22
|
border-radius: var(--dsw-radius-md);
|
|
20
23
|
cursor: pointer;
|
|
21
24
|
font-size: var(--dsw-font-xs-13-font-size);
|
|
@@ -87,12 +90,59 @@
|
|
|
87
90
|
|
|
88
91
|
.wb-choiceLabel {
|
|
89
92
|
flex: 1;
|
|
93
|
+
min-width: 0;
|
|
94
|
+
display: flex;
|
|
95
|
+
flex-direction: column;
|
|
96
|
+
overflow: hidden;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.wb-choiceLabelText {
|
|
100
|
+
overflow: hidden;
|
|
101
|
+
text-overflow: ellipsis;
|
|
102
|
+
white-space: nowrap;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.wb-choiceLabel:has(.wb-choiceDescription) .wb-choiceLabelText {
|
|
106
|
+
overflow: visible;
|
|
107
|
+
white-space: normal;
|
|
108
|
+
overflow-wrap: anywhere;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.wb-choiceDescription {
|
|
112
|
+
font-size: var(--dsw-font-xxs-12-font-size);
|
|
113
|
+
line-height: var(--wb-line-18);
|
|
114
|
+
color: var(--dsw-alias-label-tertiary);
|
|
115
|
+
overflow-wrap: anywhere;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.wb-choiceLoading {
|
|
119
|
+
display: flex;
|
|
120
|
+
align-items: center;
|
|
121
|
+
gap: var(--wb-len-6);
|
|
122
|
+
min-height: var(--wb-len-28);
|
|
123
|
+
padding: var(--wb-len-4) var(--wb-len-8);
|
|
124
|
+
font-size: var(--dsw-font-xs-13-font-size);
|
|
125
|
+
line-height: var(--wb-line-20);
|
|
126
|
+
color: var(--dsw-alias-label-secondary);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.wb-choiceLoadingText {
|
|
90
130
|
min-width: 0;
|
|
91
131
|
overflow: hidden;
|
|
92
132
|
text-overflow: ellipsis;
|
|
93
133
|
white-space: nowrap;
|
|
94
134
|
}
|
|
95
135
|
|
|
136
|
+
.wb-choiceEmpty {
|
|
137
|
+
display: flex;
|
|
138
|
+
align-items: center;
|
|
139
|
+
min-height: var(--wb-len-28);
|
|
140
|
+
padding: var(--wb-len-4) var(--wb-len-8);
|
|
141
|
+
font-size: var(--dsw-font-xs-13-font-size);
|
|
142
|
+
line-height: var(--wb-line-20);
|
|
143
|
+
color: var(--dsw-alias-label-tertiary);
|
|
144
|
+
}
|
|
145
|
+
|
|
96
146
|
.wb-radio {
|
|
97
147
|
appearance: none;
|
|
98
148
|
-webkit-appearance: none;
|
|
@@ -3,12 +3,17 @@ import './ChoiceGroup.css';
|
|
|
3
3
|
export interface ChoiceGroupOption {
|
|
4
4
|
readonly value: string;
|
|
5
5
|
readonly label: ReactNode;
|
|
6
|
+
readonly description?: ReactNode | undefined;
|
|
6
7
|
}
|
|
7
8
|
interface ChoiceGroupCommon {
|
|
8
9
|
readonly label: string;
|
|
9
10
|
readonly name: string;
|
|
10
11
|
readonly options: readonly ChoiceGroupOption[];
|
|
11
12
|
readonly disabled?: boolean | undefined;
|
|
13
|
+
readonly variant?: 'boxed' | 'ghost' | undefined;
|
|
14
|
+
readonly loading?: boolean | undefined;
|
|
15
|
+
readonly loadingText?: ReactNode | undefined;
|
|
16
|
+
readonly emptyText?: ReactNode | undefined;
|
|
12
17
|
}
|
|
13
18
|
export interface ChoiceGroupSingleProps extends ChoiceGroupCommon {
|
|
14
19
|
readonly multi?: false | undefined;
|
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
import { Fragment, h } from '../lib/react.js';
|
|
2
2
|
import { IconCheckOutlineRegular } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
3
|
+
import { Spinner } from './Spinner.js';
|
|
3
4
|
import './ChoiceGroup.css';
|
|
4
5
|
function ChoiceMenu(input) {
|
|
5
|
-
|
|
6
|
+
const body = input.loading ? (h("span", { className: "wb-choiceLoading", role: "status" },
|
|
7
|
+
h(Spinner, { size: 14 }),
|
|
8
|
+
h("span", { className: "wb-choiceLoadingText" }, input.loadingText ?? '…'))) : input.options.length === 0 ? (input.emptyText === undefined ? null : (h("span", { className: "wb-choiceEmpty" }, input.emptyText))) : (input.options.map((option) => {
|
|
6
9
|
const row = input.rowOf(option.value);
|
|
7
10
|
return (h(Fragment, { key: option.value },
|
|
8
11
|
h("label", { className: `wb-choiceOption${row.checked ? ' wb-choiceOptionOn' : ''}` },
|
|
9
12
|
h("input", { className: input.multi ? 'wb-checkbox' : 'wb-radio', type: input.multi ? 'checkbox' : 'radio', name: input.name, checked: row.checked, disabled: input.disabled, onChange: row.onSelect }),
|
|
10
13
|
input.multi ? (h("span", { className: "wb-checkboxBox", "aria-hidden": "true" }, row.checked ? h(IconCheckOutlineRegular, { size: 12 }) : null)) : null,
|
|
11
|
-
h("span", { className: "wb-choiceLabel" },
|
|
12
|
-
|
|
14
|
+
h("span", { className: "wb-choiceLabel" },
|
|
15
|
+
h("span", { className: "wb-choiceLabelText" }, option.label),
|
|
16
|
+
option.description === undefined ? null : (h("span", { className: "wb-choiceDescription" }, option.description))))));
|
|
17
|
+
}));
|
|
18
|
+
return (h("div", { className: "wb-choiceMenu", "data-variant": input.variant, role: input.multi ? 'group' : 'radiogroup', "aria-label": input.label, "aria-busy": input.loading ? true : undefined }, body));
|
|
13
19
|
}
|
|
14
20
|
export function ChoiceGroup(props) {
|
|
15
21
|
const common = {
|
|
@@ -17,11 +23,15 @@ export function ChoiceGroup(props) {
|
|
|
17
23
|
name: props.name,
|
|
18
24
|
options: props.options,
|
|
19
25
|
disabled: props.disabled === true,
|
|
26
|
+
variant: props.variant ?? 'boxed',
|
|
27
|
+
loading: props.loading === true,
|
|
28
|
+
loadingText: props.loadingText,
|
|
29
|
+
emptyText: props.emptyText,
|
|
20
30
|
};
|
|
21
31
|
if (props.multi === true) {
|
|
22
32
|
const values = props.values;
|
|
23
33
|
const onChange = props.onChange;
|
|
24
|
-
return (h(ChoiceMenu, { label: common.label, name: common.name, options: common.options, disabled: common.disabled, multi: true, rowOf: (value) => ({
|
|
34
|
+
return (h(ChoiceMenu, { label: common.label, name: common.name, options: common.options, disabled: common.disabled, multi: true, variant: common.variant, loading: common.loading, loadingText: common.loadingText, emptyText: common.emptyText, rowOf: (value) => ({
|
|
25
35
|
checked: values.includes(value),
|
|
26
36
|
onSelect: () => {
|
|
27
37
|
onChange(values.includes(value)
|
|
@@ -32,7 +42,7 @@ export function ChoiceGroup(props) {
|
|
|
32
42
|
}
|
|
33
43
|
const value = props.value;
|
|
34
44
|
const onChange = props.onChange;
|
|
35
|
-
return (h(ChoiceMenu, { label: common.label, name: common.name, options: common.options, disabled: common.disabled, multi: false, rowOf: (option) => ({
|
|
45
|
+
return (h(ChoiceMenu, { label: common.label, name: common.name, options: common.options, disabled: common.disabled, multi: false, variant: common.variant, loading: common.loading, loadingText: common.loadingText, emptyText: common.emptyText, rowOf: (option) => ({
|
|
36
46
|
checked: option === value,
|
|
37
47
|
onSelect: () => {
|
|
38
48
|
onChange(option);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
.wb-formRows {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
gap: var(--wb-len-12);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.wb-formRow {
|
|
8
|
+
display: grid;
|
|
9
|
+
grid-template-columns: 72px minmax(0, 1fr);
|
|
10
|
+
align-items: center;
|
|
11
|
+
gap: var(--wb-len-8);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.wb-formRow[data-layout='stacked'] {
|
|
15
|
+
grid-template-columns: minmax(0, 1fr);
|
|
16
|
+
align-items: stretch;
|
|
17
|
+
gap: var(--wb-len-6);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.wb-formLabel {
|
|
21
|
+
min-width: 0;
|
|
22
|
+
font-size: var(--dsw-font-xs-13-font-size);
|
|
23
|
+
color: var(--dsw-alias-label-secondary);
|
|
24
|
+
overflow-wrap: anywhere;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.wb-formControl {
|
|
28
|
+
display: flex;
|
|
29
|
+
align-items: center;
|
|
30
|
+
min-width: 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.wb-formHint {
|
|
34
|
+
grid-column: 2;
|
|
35
|
+
font-size: var(--dsw-font-xxs-12-font-size);
|
|
36
|
+
line-height: var(--wb-line-18);
|
|
37
|
+
color: var(--dsw-alias-label-tertiary);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.wb-formProblem {
|
|
41
|
+
grid-column: 2;
|
|
42
|
+
font-size: var(--dsw-font-xxs-12-font-size);
|
|
43
|
+
line-height: var(--wb-line-18);
|
|
44
|
+
color: var(--dsw-alias-state-error-primary);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.wb-formRow:has(.wb-formProblem) .wb-formLabel {
|
|
48
|
+
color: var(--dsw-alias-state-error-primary);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.wb-formRow:has(:disabled) .wb-formLabel {
|
|
52
|
+
opacity: 0.4;
|
|
53
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ReactNode } from '../lib/react.js';
|
|
2
|
+
import './FormRows.css';
|
|
3
|
+
export interface FormRowsProps {
|
|
4
|
+
readonly children?: ReactNode | undefined;
|
|
5
|
+
}
|
|
6
|
+
export declare function FormRows({ children }: FormRowsProps): JSX.Element;
|
|
7
|
+
export interface FormRowProps {
|
|
8
|
+
readonly label: ReactNode;
|
|
9
|
+
readonly hint?: ReactNode | undefined;
|
|
10
|
+
readonly problem?: ReactNode | undefined;
|
|
11
|
+
readonly layout?: 'inline' | 'stacked' | undefined;
|
|
12
|
+
readonly children?: ReactNode | undefined;
|
|
13
|
+
}
|
|
14
|
+
export declare function FormRow({ label, hint, problem, layout, children }: FormRowProps): JSX.Element;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { h } from '../lib/react.js';
|
|
2
|
+
import './FormRows.css';
|
|
3
|
+
export function FormRows({ children }) {
|
|
4
|
+
return h("div", { className: "wb-formRows" }, children);
|
|
5
|
+
}
|
|
6
|
+
export function FormRow({ label, hint, problem, layout = 'inline', children }) {
|
|
7
|
+
return (h("label", { className: "wb-formRow", "data-layout": layout, "data-wb-problem": problem === undefined ? undefined : '' },
|
|
8
|
+
h("span", { className: "wb-formLabel" }, label),
|
|
9
|
+
h("span", { className: "wb-formControl" }, children),
|
|
10
|
+
problem === undefined ? (hint === undefined ? null : (h("span", { className: "wb-formHint" }, hint))) : (h("span", { className: "wb-formProblem" }, problem))));
|
|
11
|
+
}
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
align-items: center;
|
|
5
5
|
gap: var(--wb-len-8);
|
|
6
6
|
width: 100%;
|
|
7
|
-
min-height: var(--wb-len-
|
|
8
|
-
padding: var(--wb-len-
|
|
7
|
+
min-height: var(--wb-len-28);
|
|
8
|
+
padding: var(--wb-len-4) var(--wb-len-10);
|
|
9
9
|
border: 0.5px solid var(--dsw-alias-border-l4);
|
|
10
10
|
border-radius: var(--dsw-radius-md);
|
|
11
11
|
background: var(--dsw-alias-bg-layer-3);
|
|
@@ -21,6 +21,14 @@
|
|
|
21
21
|
box-shadow: inset 0 0 0 0.5px var(--dsw-alias-state-business-primary);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
.wb-selectTrigger[aria-invalid='true'] {
|
|
25
|
+
border-color: var(--dsw-alias-state-error-primary);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.wb-selectTrigger[aria-invalid='true']:focus-visible {
|
|
29
|
+
box-shadow: inset 0 0 0 0.5px var(--dsw-alias-state-error-primary);
|
|
30
|
+
}
|
|
31
|
+
|
|
24
32
|
.wb-selectTrigger:disabled {
|
|
25
33
|
cursor: not-allowed;
|
|
26
34
|
opacity: 0.4;
|
|
@@ -100,8 +108,8 @@
|
|
|
100
108
|
align-items: center;
|
|
101
109
|
gap: var(--wb-len-8);
|
|
102
110
|
width: 100%;
|
|
103
|
-
min-height: var(--wb-len-
|
|
104
|
-
padding: var(--wb-len-
|
|
111
|
+
min-height: var(--wb-len-28);
|
|
112
|
+
padding: var(--wb-len-4) var(--wb-len-10);
|
|
105
113
|
border: 0.5px solid var(--dsw-alias-border-l4);
|
|
106
114
|
border-radius: var(--dsw-radius-md);
|
|
107
115
|
background: var(--dsw-alias-bg-layer-3);
|
|
@@ -34,6 +34,7 @@ export interface SelectFieldProps {
|
|
|
34
34
|
readonly open?: boolean | undefined;
|
|
35
35
|
readonly onOpenChange?: ((open: boolean) => void) | undefined;
|
|
36
36
|
readonly disabled?: boolean | undefined;
|
|
37
|
+
readonly invalid?: boolean | undefined;
|
|
37
38
|
readonly loading?: boolean | undefined;
|
|
38
39
|
readonly loadingText?: string | undefined;
|
|
39
40
|
readonly fallback?: SelectFieldFallback | undefined;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { h, useEffect, useMemo, useRef, useState } from '../lib/react.js';
|
|
2
|
-
import { IconChevronDownOutlineRegular, IconSearchOutlineRegular, Menu,
|
|
2
|
+
import { IconChevronDownOutlineRegular, IconSearchOutlineRegular, Menu, TextShimmer, } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
3
3
|
import { Field } from './Field.js';
|
|
4
4
|
import { TextField } from './TextField.js';
|
|
5
|
+
import { Spinner } from './Spinner.js';
|
|
5
6
|
import './SelectField.css';
|
|
6
7
|
const FOOTER_ID = '__wb-select-footer__';
|
|
7
8
|
const EMPTY_ID = '__wb-select-empty__';
|
|
@@ -17,18 +18,18 @@ function itemOf(option) {
|
|
|
17
18
|
};
|
|
18
19
|
}
|
|
19
20
|
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
|
+
return (h("button", { type: "button", id: args.id, className: args.className, disabled: args.disabled, "aria-haspopup": "listbox", "aria-expanded": args.open, "aria-invalid": args.invalid || undefined, "aria-label": args.ariaLabel, title: args.title, onClick: args.onOpen },
|
|
21
22
|
args.icon === undefined ? null : h("span", { className: "wb-selectIcon" }, args.icon),
|
|
22
23
|
h("span", { className: "wb-selectValue" }, args.value),
|
|
23
24
|
h("span", { className: "wb-selectChevron" },
|
|
24
25
|
h(IconChevronDownOutlineRegular, { size: 14 }))));
|
|
25
26
|
}
|
|
26
27
|
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
|
+
return (h("button", { type: "button", id: args.id, className: `${args.className} wb-selectLoading`, disabled: true, "aria-busy": "true", "aria-invalid": args.invalid || undefined, "aria-label": args.ariaLabel, title: args.loadingText },
|
|
28
29
|
h("span", { className: "wb-selectValue" },
|
|
29
30
|
h(TextShimmer, { active: true }, args.loadingText ?? '…')),
|
|
30
31
|
h("span", { className: "wb-selectChevron" },
|
|
31
|
-
h(
|
|
32
|
+
h(Spinner, { size: 14 }))));
|
|
32
33
|
}
|
|
33
34
|
function searchNode(args) {
|
|
34
35
|
return (h("span", { ref: args.boxRef, className: args.className },
|
|
@@ -154,21 +155,22 @@ function useSelectState(props) {
|
|
|
154
155
|
};
|
|
155
156
|
}
|
|
156
157
|
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 { value, options, onChange, label, hint, icon, renderValue, placeholder, search = true, searchLabel, searchPlaceholder, footer, onFooter, disabled = false, invalid = false, loading = false, loadingText, fallback, selection = 'fill', variant = 'boxed', ariaLabel, title, id, name, className, menuClassName, } = props;
|
|
158
159
|
const state = useSelectState(props);
|
|
159
160
|
const current = options.find((option) => option.value === value);
|
|
160
161
|
const variantClass = variant === 'ghost' ? 'wb-selectGhost' : undefined;
|
|
161
162
|
const triggerClass = mergeClass('wb-selectTrigger', variantClass);
|
|
162
163
|
if (loading) {
|
|
163
|
-
return framed(label, hint, className, loadingNode({ id, className: triggerClass, ariaLabel, loadingText }));
|
|
164
|
+
return framed(label, hint, className, loadingNode({ id, className: triggerClass, invalid, ariaLabel, loadingText }));
|
|
164
165
|
}
|
|
165
166
|
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
|
+
return framed(label, hint, className, h(TextField, { id: id, name: name, className: "wb-selectFallback", invalid: invalid, "aria-label": ariaLabel, value: fallback.value, onChange: (event) => fallback.onChange(event.currentTarget.value) }));
|
|
167
168
|
}
|
|
168
169
|
const trigger = triggerNode({
|
|
169
170
|
id,
|
|
170
171
|
className: triggerClass,
|
|
171
172
|
disabled,
|
|
173
|
+
invalid,
|
|
172
174
|
open: state.open,
|
|
173
175
|
ariaLabel,
|
|
174
176
|
title,
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { StateDot } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
2
|
+
import { h } from '../lib/react.js';
|
|
3
|
+
import './Spinner.css';
|
|
4
|
+
export function Spinner({ size = 14, label }) {
|
|
5
|
+
return (h("span", { className: "wb-spinner", ...(label === undefined ? { 'aria-hidden': true } : { role: 'status', 'aria-label': label }) },
|
|
6
|
+
h(StateDot, { state: "ongoing", size: size })));
|
|
7
|
+
}
|
package/dist/host.d.ts
CHANGED
|
@@ -8,17 +8,17 @@ export * from './lib/text.js';
|
|
|
8
8
|
export * from './lib/ui.js';
|
|
9
9
|
export * from './lib/panel-state.js';
|
|
10
10
|
export * from './api/blocker-table.js';
|
|
11
|
+
export * from './host/browser-find.js';
|
|
12
|
+
export * from './host/browser-read.js';
|
|
11
13
|
export * from './host/cli-error.js';
|
|
12
14
|
export * from './host/data-root.js';
|
|
13
|
-
export * from './host/browser-read.js';
|
|
14
|
-
export * from './host/browser-find.js';
|
|
15
15
|
export * from './host/facts.js';
|
|
16
16
|
export * from './host/git.js';
|
|
17
17
|
export * from './host/http.js';
|
|
18
18
|
export * from './host/long-task.js';
|
|
19
19
|
export * from './host/opencli-runtime.js';
|
|
20
|
-
export * from './host/opencli.js';
|
|
21
20
|
export * from './host/opencli-window.js';
|
|
21
|
+
export * from './host/opencli.js';
|
|
22
22
|
export * from './host/plugin-ledger.js';
|
|
23
23
|
export * from './host/plugin-source.js';
|
|
24
24
|
export * from './host/provision.js';
|
package/dist/host.js
CHANGED
|
@@ -8,17 +8,17 @@ export * from './lib/text.js';
|
|
|
8
8
|
export * from './lib/ui.js';
|
|
9
9
|
export * from './lib/panel-state.js';
|
|
10
10
|
export * from './api/blocker-table.js';
|
|
11
|
+
export * from './host/browser-find.js';
|
|
12
|
+
export * from './host/browser-read.js';
|
|
11
13
|
export * from './host/cli-error.js';
|
|
12
14
|
export * from './host/data-root.js';
|
|
13
|
-
export * from './host/browser-read.js';
|
|
14
|
-
export * from './host/browser-find.js';
|
|
15
15
|
export * from './host/facts.js';
|
|
16
16
|
export * from './host/git.js';
|
|
17
17
|
export * from './host/http.js';
|
|
18
18
|
export * from './host/long-task.js';
|
|
19
19
|
export * from './host/opencli-runtime.js';
|
|
20
|
-
export * from './host/opencli.js';
|
|
21
20
|
export * from './host/opencli-window.js';
|
|
21
|
+
export * from './host/opencli.js';
|
|
22
22
|
export * from './host/plugin-ledger.js';
|
|
23
23
|
export * from './host/plugin-source.js';
|
|
24
24
|
export * from './host/provision.js';
|
package/dist/index.d.ts
CHANGED
|
@@ -7,8 +7,8 @@ export * from './api/facts.js';
|
|
|
7
7
|
export * from './api/host-write.js';
|
|
8
8
|
export * from './api/long-task.js';
|
|
9
9
|
export * from './api/unit-board.js';
|
|
10
|
-
export * from './api/unit.js';
|
|
11
10
|
export * from './api/unit-declaration.js';
|
|
11
|
+
export * from './api/unit.js';
|
|
12
12
|
export * from './api/workbench-client.js';
|
|
13
13
|
export * from './api/workbench-contract.js';
|
|
14
14
|
export * from './components/Activity.js';
|
|
@@ -19,18 +19,20 @@ export * from './components/CardAnchor.js';
|
|
|
19
19
|
export * from './components/CardBoundary.js';
|
|
20
20
|
export * from './components/CardHead.js';
|
|
21
21
|
export * from './components/ChoiceGroup.js';
|
|
22
|
+
export * from './components/Field.js';
|
|
23
|
+
export * from './components/FormRows.js';
|
|
24
|
+
export * from './components/Link.js';
|
|
22
25
|
export * from './components/LongTaskRow.js';
|
|
23
26
|
export * from './components/Note.js';
|
|
24
27
|
export * from './components/ResourceStates.js';
|
|
25
28
|
export * from './components/Rows.js';
|
|
26
29
|
export * from './components/SaveBar.js';
|
|
27
|
-
export * from './components/SettingsSection.js';
|
|
28
30
|
export * from './components/SelectField.js';
|
|
31
|
+
export * from './components/SettingsSection.js';
|
|
32
|
+
export * from './components/Spinner.js';
|
|
29
33
|
export * from './components/Stack.js';
|
|
30
34
|
export * from './components/StateBlock.js';
|
|
31
35
|
export * from './components/TextField.js';
|
|
32
|
-
export * from './components/Field.js';
|
|
33
|
-
export * from './components/Link.js';
|
|
34
36
|
export * from './components/board-module.js';
|
|
35
37
|
export * from './components/icons.js';
|
|
36
38
|
export * from './config/constants.js';
|
|
@@ -41,6 +43,7 @@ export * from './i18n/merge.js';
|
|
|
41
43
|
export * from './i18n/zh.js';
|
|
42
44
|
export * from './lib/blocker-action.js';
|
|
43
45
|
export * from './lib/busy.js';
|
|
46
|
+
export * from './lib/form-problem.js';
|
|
44
47
|
export * from './lib/panel-state.js';
|
|
45
48
|
export * from './lib/react.js';
|
|
46
49
|
export * from './lib/text.js';
|
package/dist/index.js
CHANGED
|
@@ -7,8 +7,8 @@ export * from './api/facts.js';
|
|
|
7
7
|
export * from './api/host-write.js';
|
|
8
8
|
export * from './api/long-task.js';
|
|
9
9
|
export * from './api/unit-board.js';
|
|
10
|
-
export * from './api/unit.js';
|
|
11
10
|
export * from './api/unit-declaration.js';
|
|
11
|
+
export * from './api/unit.js';
|
|
12
12
|
export * from './api/workbench-client.js';
|
|
13
13
|
export * from './api/workbench-contract.js';
|
|
14
14
|
export * from './components/Activity.js';
|
|
@@ -19,18 +19,20 @@ export * from './components/CardAnchor.js';
|
|
|
19
19
|
export * from './components/CardBoundary.js';
|
|
20
20
|
export * from './components/CardHead.js';
|
|
21
21
|
export * from './components/ChoiceGroup.js';
|
|
22
|
+
export * from './components/Field.js';
|
|
23
|
+
export * from './components/FormRows.js';
|
|
24
|
+
export * from './components/Link.js';
|
|
22
25
|
export * from './components/LongTaskRow.js';
|
|
23
26
|
export * from './components/Note.js';
|
|
24
27
|
export * from './components/ResourceStates.js';
|
|
25
28
|
export * from './components/Rows.js';
|
|
26
29
|
export * from './components/SaveBar.js';
|
|
27
|
-
export * from './components/SettingsSection.js';
|
|
28
30
|
export * from './components/SelectField.js';
|
|
31
|
+
export * from './components/SettingsSection.js';
|
|
32
|
+
export * from './components/Spinner.js';
|
|
29
33
|
export * from './components/Stack.js';
|
|
30
34
|
export * from './components/StateBlock.js';
|
|
31
35
|
export * from './components/TextField.js';
|
|
32
|
-
export * from './components/Field.js';
|
|
33
|
-
export * from './components/Link.js';
|
|
34
36
|
export * from './components/board-module.js';
|
|
35
37
|
export * from './components/icons.js';
|
|
36
38
|
export * from './config/constants.js';
|
|
@@ -41,6 +43,7 @@ export * from './i18n/merge.js';
|
|
|
41
43
|
export * from './i18n/zh.js';
|
|
42
44
|
export * from './lib/blocker-action.js';
|
|
43
45
|
export * from './lib/busy.js';
|
|
46
|
+
export * from './lib/form-problem.js';
|
|
44
47
|
export * from './lib/panel-state.js';
|
|
45
48
|
export * from './lib/react.js';
|
|
46
49
|
export * from './lib/text.js';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const PROBLEM_SELECTOR = '[data-wb-problem]';
|
|
2
|
+
const FOCUSABLE_SELECTOR = [
|
|
3
|
+
'input:not([disabled])',
|
|
4
|
+
'select:not([disabled])',
|
|
5
|
+
'textarea:not([disabled])',
|
|
6
|
+
'button:not([disabled])',
|
|
7
|
+
'a[href]',
|
|
8
|
+
'[tabindex]:not([tabindex="-1"])',
|
|
9
|
+
].join(', ');
|
|
10
|
+
export function firstProblemOf(root) {
|
|
11
|
+
return root.querySelector(PROBLEM_SELECTOR);
|
|
12
|
+
}
|
|
13
|
+
export function revealFirstProblem(root) {
|
|
14
|
+
const problem = firstProblemOf(root);
|
|
15
|
+
if (problem === null)
|
|
16
|
+
return false;
|
|
17
|
+
problem.scrollIntoView?.({ block: 'nearest' });
|
|
18
|
+
problem.querySelector(FOCUSABLE_SELECTOR)?.focus?.();
|
|
19
|
+
return true;
|
|
20
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-workbench/shared",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.13",
|
|
4
4
|
"description": "工作台与模块共享的纯代码与契约类型:类型、纯逻辑、哑组件、平台解析。宿主侧的东西从 /host 进(不影响浏览器产物)。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"typecheck": "tsc --noEmit",
|
|
44
44
|
"test": "vitest run",
|
|
45
45
|
"coverage": "vitest run --coverage",
|
|
46
|
+
"barrels": "node scripts/barrels.mjs",
|
|
46
47
|
"selfcheck": "node scripts/check-state.mjs",
|
|
47
48
|
"arch:report": "arch-guard --report-only",
|
|
48
49
|
"arch": "arch-guard"
|
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
display: flex;
|
|
4
4
|
flex-direction: column;
|
|
5
5
|
gap: var(--wb-len-2);
|
|
6
|
+
background: transparent;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.wb-choiceMenu[data-variant='boxed'] {
|
|
6
10
|
padding: var(--wb-len-4);
|
|
7
11
|
border: 0.5px solid var(--dsw-alias-border-l3);
|
|
8
12
|
border-radius: var(--dsw-radius-md);
|
|
9
|
-
background: transparent;
|
|
10
13
|
}
|
|
11
14
|
|
|
12
15
|
.wb-choiceOption {
|
|
@@ -14,8 +17,8 @@
|
|
|
14
17
|
display: flex;
|
|
15
18
|
align-items: center;
|
|
16
19
|
gap: var(--wb-len-6);
|
|
17
|
-
min-height: var(--wb-len-
|
|
18
|
-
padding: var(--wb-len-
|
|
20
|
+
min-height: var(--wb-len-28);
|
|
21
|
+
padding: var(--wb-len-4) var(--wb-len-8);
|
|
19
22
|
border-radius: var(--dsw-radius-md);
|
|
20
23
|
cursor: pointer;
|
|
21
24
|
font-size: var(--dsw-font-xs-13-font-size);
|
|
@@ -87,12 +90,59 @@
|
|
|
87
90
|
|
|
88
91
|
.wb-choiceLabel {
|
|
89
92
|
flex: 1;
|
|
93
|
+
min-width: 0;
|
|
94
|
+
display: flex;
|
|
95
|
+
flex-direction: column;
|
|
96
|
+
overflow: hidden;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.wb-choiceLabelText {
|
|
100
|
+
overflow: hidden;
|
|
101
|
+
text-overflow: ellipsis;
|
|
102
|
+
white-space: nowrap;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.wb-choiceLabel:has(.wb-choiceDescription) .wb-choiceLabelText {
|
|
106
|
+
overflow: visible;
|
|
107
|
+
white-space: normal;
|
|
108
|
+
overflow-wrap: anywhere;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.wb-choiceDescription {
|
|
112
|
+
font-size: var(--dsw-font-xxs-12-font-size);
|
|
113
|
+
line-height: var(--wb-line-18);
|
|
114
|
+
color: var(--dsw-alias-label-tertiary);
|
|
115
|
+
overflow-wrap: anywhere;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.wb-choiceLoading {
|
|
119
|
+
display: flex;
|
|
120
|
+
align-items: center;
|
|
121
|
+
gap: var(--wb-len-6);
|
|
122
|
+
min-height: var(--wb-len-28);
|
|
123
|
+
padding: var(--wb-len-4) var(--wb-len-8);
|
|
124
|
+
font-size: var(--dsw-font-xs-13-font-size);
|
|
125
|
+
line-height: var(--wb-line-20);
|
|
126
|
+
color: var(--dsw-alias-label-secondary);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.wb-choiceLoadingText {
|
|
90
130
|
min-width: 0;
|
|
91
131
|
overflow: hidden;
|
|
92
132
|
text-overflow: ellipsis;
|
|
93
133
|
white-space: nowrap;
|
|
94
134
|
}
|
|
95
135
|
|
|
136
|
+
.wb-choiceEmpty {
|
|
137
|
+
display: flex;
|
|
138
|
+
align-items: center;
|
|
139
|
+
min-height: var(--wb-len-28);
|
|
140
|
+
padding: var(--wb-len-4) var(--wb-len-8);
|
|
141
|
+
font-size: var(--dsw-font-xs-13-font-size);
|
|
142
|
+
line-height: var(--wb-line-20);
|
|
143
|
+
color: var(--dsw-alias-label-tertiary);
|
|
144
|
+
}
|
|
145
|
+
|
|
96
146
|
.wb-radio {
|
|
97
147
|
appearance: none;
|
|
98
148
|
-webkit-appearance: none;
|