@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.
Files changed (40) hide show
  1. package/dist/components/ChoiceGroup.css +53 -3
  2. package/dist/components/ChoiceGroup.d.ts +5 -0
  3. package/dist/components/ChoiceGroup.js +15 -5
  4. package/dist/components/FormRows.css +53 -0
  5. package/dist/components/FormRows.d.ts +14 -0
  6. package/dist/components/FormRows.js +11 -0
  7. package/dist/components/MockStrip.css +5 -1
  8. package/dist/components/SelectField.css +12 -4
  9. package/dist/components/SelectField.d.ts +1 -0
  10. package/dist/components/SelectField.js +9 -7
  11. package/dist/components/Spinner.css +6 -0
  12. package/dist/components/Spinner.d.ts +6 -0
  13. package/dist/components/Spinner.js +7 -0
  14. package/dist/components/TextField.css +2 -2
  15. package/dist/host.d.ts +3 -3
  16. package/dist/host.js +3 -3
  17. package/dist/index.d.ts +7 -5
  18. package/dist/index.js +7 -5
  19. package/dist/lib/form-problem.d.ts +2 -0
  20. package/dist/lib/form-problem.js +20 -0
  21. package/package.json +2 -1
  22. package/src/components/ChoiceGroup.css +53 -3
  23. package/src/components/ChoiceGroup.test.tsx +88 -0
  24. package/src/components/ChoiceGroup.tsx +70 -28
  25. package/src/components/FormRows.css +53 -0
  26. package/src/components/FormRows.test.tsx +82 -0
  27. package/src/components/FormRows.tsx +39 -0
  28. package/src/components/SelectField.css +12 -4
  29. package/src/components/SelectField.test.tsx +20 -2
  30. package/src/components/SelectField.tsx +11 -3
  31. package/src/components/Spinner.css +6 -0
  32. package/src/components/Spinner.test.tsx +35 -0
  33. package/src/components/Spinner.tsx +19 -0
  34. package/src/components/TextField.css +2 -2
  35. package/src/host.ts +3 -3
  36. package/src/index.ts +7 -5
  37. package/src/lib/form-problem.test.ts +76 -0
  38. package/src/lib/form-problem.ts +22 -0
  39. package/src/components/MockStrip.css +0 -45
  40. 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
- }