@dsh-workbench/shared 0.2.11 → 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/MockStrip.css +5 -1
- 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 -5
- package/dist/index.js +7 -5
- 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 -5
- package/src/lib/form-problem.test.ts +76 -0
- package/src/lib/form-problem.ts +22 -0
- package/src/components/MockStrip.css +0 -45
- package/src/components/MockStrip.tsx +0 -41
package/src/host.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/src/index.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,19 +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
|
-
export * from './components/MockStrip.js';
|
|
24
26
|
export * from './components/Note.js';
|
|
25
27
|
export * from './components/ResourceStates.js';
|
|
26
28
|
export * from './components/Rows.js';
|
|
27
29
|
export * from './components/SaveBar.js';
|
|
28
|
-
export * from './components/SettingsSection.js';
|
|
29
30
|
export * from './components/SelectField.js';
|
|
31
|
+
export * from './components/SettingsSection.js';
|
|
32
|
+
export * from './components/Spinner.js';
|
|
30
33
|
export * from './components/Stack.js';
|
|
31
34
|
export * from './components/StateBlock.js';
|
|
32
35
|
export * from './components/TextField.js';
|
|
33
|
-
export * from './components/Field.js';
|
|
34
|
-
export * from './components/Link.js';
|
|
35
36
|
export * from './components/board-module.js';
|
|
36
37
|
export * from './components/icons.js';
|
|
37
38
|
export * from './config/constants.js';
|
|
@@ -42,6 +43,7 @@ export * from './i18n/merge.js';
|
|
|
42
43
|
export * from './i18n/zh.js';
|
|
43
44
|
export * from './lib/blocker-action.js';
|
|
44
45
|
export * from './lib/busy.js';
|
|
46
|
+
export * from './lib/form-problem.js';
|
|
45
47
|
export * from './lib/panel-state.js';
|
|
46
48
|
export * from './lib/react.js';
|
|
47
49
|
export * from './lib/text.js';
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { firstProblemOf, revealFirstProblem } from './form-problem.js';
|
|
3
|
+
|
|
4
|
+
function rootWith(problem: unknown): { readonly root: ParentNode; readonly queries: string[] } {
|
|
5
|
+
const queries: string[] = [];
|
|
6
|
+
const root = {
|
|
7
|
+
querySelector: (selector: string) => {
|
|
8
|
+
queries.push(selector);
|
|
9
|
+
return selector === '[data-wb-problem]' ? problem : null;
|
|
10
|
+
},
|
|
11
|
+
} as unknown as ParentNode;
|
|
12
|
+
return { root, queries };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface ProblemStub {
|
|
16
|
+
readonly element: Element;
|
|
17
|
+
readonly queries: string[];
|
|
18
|
+
readonly scrollIntoView: ReturnType<typeof vi.fn>;
|
|
19
|
+
readonly focus: ReturnType<typeof vi.fn>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function problemStub(options: { readonly scroll: boolean }): ProblemStub {
|
|
23
|
+
const queries: string[] = [];
|
|
24
|
+
const scrollIntoView = vi.fn();
|
|
25
|
+
const focus = vi.fn();
|
|
26
|
+
const element = {
|
|
27
|
+
scrollIntoView,
|
|
28
|
+
querySelector: (selector: string) => {
|
|
29
|
+
queries.push(selector);
|
|
30
|
+
return { focus };
|
|
31
|
+
},
|
|
32
|
+
} as unknown as Element;
|
|
33
|
+
if (!options.scroll) {
|
|
34
|
+
delete (element as { scrollIntoView?: unknown }).scrollIntoView;
|
|
35
|
+
}
|
|
36
|
+
return { element, queries, scrollIntoView, focus };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe('firstProblemOf / revealFirstProblem', () => {
|
|
40
|
+
it('没有错误行就返回 null / false,什么也不碰', () => {
|
|
41
|
+
const empty = rootWith(null);
|
|
42
|
+
const missing = problemStub({ scroll: true });
|
|
43
|
+
|
|
44
|
+
expect(firstProblemOf(empty.root)).toBeNull();
|
|
45
|
+
expect(revealFirstProblem(rootWith(null).root)).toBe(false);
|
|
46
|
+
expect(missing.focus).not.toHaveBeenCalled();
|
|
47
|
+
expect(missing.scrollIntoView).not.toHaveBeenCalled();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('第一处 [data-wb-problem] 就是它', () => {
|
|
51
|
+
const problem = problemStub({ scroll: true });
|
|
52
|
+
const found = rootWith(problem.element);
|
|
53
|
+
|
|
54
|
+
expect(firstProblemOf(found.root)).toBe(problem.element);
|
|
55
|
+
expect(found.queries).toEqual(['[data-wb-problem]']);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('带进视口 + 聚焦里面第一个可聚焦控件,返回 true', () => {
|
|
59
|
+
const problem = problemStub({ scroll: true });
|
|
60
|
+
|
|
61
|
+
expect(revealFirstProblem(rootWith(problem.element).root)).toBe(true);
|
|
62
|
+
expect(problem.scrollIntoView).toHaveBeenCalledTimes(1);
|
|
63
|
+
expect(problem.scrollIntoView).toHaveBeenCalledWith({ block: 'nearest' });
|
|
64
|
+
expect(problem.queries).toHaveLength(1);
|
|
65
|
+
expect(problem.queries[0]).toContain('input:not([disabled])');
|
|
66
|
+
expect(problem.focus).toHaveBeenCalledTimes(1);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('没有 scrollIntoView 也不炸(jsdom 缺它):照样聚焦、照样返回 true', () => {
|
|
70
|
+
const problem = problemStub({ scroll: false });
|
|
71
|
+
|
|
72
|
+
expect(() => revealFirstProblem(rootWith(problem.element).root)).not.toThrow();
|
|
73
|
+
expect(revealFirstProblem(rootWith(problem.element).root)).toBe(true);
|
|
74
|
+
expect(problem.focus).toHaveBeenCalledTimes(2);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const PROBLEM_SELECTOR = '[data-wb-problem]';
|
|
2
|
+
|
|
3
|
+
const FOCUSABLE_SELECTOR = [
|
|
4
|
+
'input:not([disabled])',
|
|
5
|
+
'select:not([disabled])',
|
|
6
|
+
'textarea:not([disabled])',
|
|
7
|
+
'button:not([disabled])',
|
|
8
|
+
'a[href]',
|
|
9
|
+
'[tabindex]:not([tabindex="-1"])',
|
|
10
|
+
].join(', ');
|
|
11
|
+
|
|
12
|
+
export function firstProblemOf(root: ParentNode): Element | null {
|
|
13
|
+
return root.querySelector(PROBLEM_SELECTOR);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function revealFirstProblem(root: ParentNode): boolean {
|
|
17
|
+
const problem = firstProblemOf(root);
|
|
18
|
+
if (problem === null) return false;
|
|
19
|
+
problem.scrollIntoView?.({ block: 'nearest' });
|
|
20
|
+
problem.querySelector<HTMLElement>(FOCUSABLE_SELECTOR)?.focus?.();
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
.wb-mockStrip {
|
|
3
|
-
display: flex;
|
|
4
|
-
flex-wrap: wrap;
|
|
5
|
-
gap: var(--wb-len-4);
|
|
6
|
-
padding: var(--wb-len-8) var(--wb-len-12);
|
|
7
|
-
border-bottom: 0.5px solid var(--dsw-alias-border-l2);
|
|
8
|
-
flex: none;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
.wb-mockNote {
|
|
12
|
-
flex: 1 0 100%;
|
|
13
|
-
font-size: var(--wb-font-10);
|
|
14
|
-
line-height: var(--wb-line-14);
|
|
15
|
-
color: var(--dsw-alias-label-tertiary);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
.wb-mockChip {
|
|
19
|
-
flex: none;
|
|
20
|
-
padding: var(--wb-len-2) var(--wb-len-7);
|
|
21
|
-
border: 0.5px solid var(--dsw-alias-border-l2);
|
|
22
|
-
border-radius: var(--wb-radius-pill);
|
|
23
|
-
corner-shape: round;
|
|
24
|
-
background: transparent;
|
|
25
|
-
font-size: var(--wb-font-10);
|
|
26
|
-
line-height: var(--wb-line-14);
|
|
27
|
-
color: var(--dsw-alias-label-secondary);
|
|
28
|
-
cursor: pointer;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
.wb-mockChip:hover {
|
|
32
|
-
color: var(--dsw-alias-label-primary);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
.wb-mockChipOn {
|
|
36
|
-
border-color: transparent;
|
|
37
|
-
background: var(--dsw-alias-brand-primary);
|
|
38
|
-
color: var(--dsw-alias-label-primary-foreground);
|
|
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
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { h } from '../lib/react.js';
|
|
2
|
-
import './MockStrip.css';
|
|
3
|
-
|
|
4
|
-
export function MockStrip({
|
|
5
|
-
mocks,
|
|
6
|
-
index,
|
|
7
|
-
onPick,
|
|
8
|
-
note,
|
|
9
|
-
real,
|
|
10
|
-
}: {
|
|
11
|
-
readonly mocks: readonly { readonly id: string; readonly label: string }[];
|
|
12
|
-
readonly index: number;
|
|
13
|
-
readonly onPick: (next: number) => void;
|
|
14
|
-
readonly note?: string;
|
|
15
|
-
readonly real?: { readonly label: string; readonly active: boolean; readonly onPick: () => void } | undefined;
|
|
16
|
-
}) {
|
|
17
|
-
return (
|
|
18
|
-
<div className="wb-mockStrip">
|
|
19
|
-
<span className="wb-mockNote">{note ?? '夹具:只切状态,不发请求'}</span>
|
|
20
|
-
{real === undefined ? null : (
|
|
21
|
-
<button
|
|
22
|
-
type="button"
|
|
23
|
-
className={`wb-mockChip${real.active ? ' wb-mockChipOn' : ''}`}
|
|
24
|
-
onClick={real.onPick}
|
|
25
|
-
>
|
|
26
|
-
{real.label}
|
|
27
|
-
</button>
|
|
28
|
-
)}
|
|
29
|
-
{mocks.map((mock, at) => (
|
|
30
|
-
<button
|
|
31
|
-
key={mock.id}
|
|
32
|
-
type="button"
|
|
33
|
-
className={`wb-mockChip${at === index ? ' wb-mockChipOn' : ''}`}
|
|
34
|
-
onClick={() => onPick(at)}
|
|
35
|
-
>
|
|
36
|
-
{mock.label}
|
|
37
|
-
</button>
|
|
38
|
-
))}
|
|
39
|
-
</div>
|
|
40
|
-
);
|
|
41
|
-
}
|