@kiwa-test/component 0.2.0

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.
@@ -0,0 +1,534 @@
1
+ /**
2
+ * Shared types for `@kiwa-test/component` — the 3-integration unified mock
3
+ * harness for Storybook 8 + Playwright Component Testing + Chromatic.
4
+ *
5
+ * v1.16 milestone (Issue #762, sub #763) 新設。 kiwa の思想 =
6
+ * 「1 度書けば Storybook / Playwright CT / Chromatic の 3 SDK 同一挙動で
7
+ * 回せる mock」 を実現するため、 3 統合非依存な共通契約を SSOT として定義する。
8
+ *
9
+ * 各 module (`storybook.ts` / `playwright-ct.ts` / `chromatic.ts`) は本 file
10
+ * の共通型を SDK 固有 shape に変換する薄い層に留める。
11
+ */
12
+ /**
13
+ * どの integration が emit した mock かを識別する。 release-gate 11 軸 dispatch
14
+ * の provider prefix として使う。
15
+ */
16
+ type ComponentProvider = 'storybook' | 'playwright-ct' | 'chromatic';
17
+ /**
18
+ * Storybook 8 の `StoryObj` (CSF3) と互換な最小 shape。 args + play + parameters
19
+ * の 3 field を持ち、 story 登録後に registry から名前指定で resolve できる。
20
+ *
21
+ * Storybook 8 の実際の `StoryObj` は component / render / decorators 等の
22
+ * 追加 field を持つが、 mock harness は「args を解決して play を回して
23
+ * DOM を触る」 core loop だけを模倣する。
24
+ */
25
+ interface StoryObj<TArgs = Record<string, unknown>> {
26
+ /** story 表示名 (未指定時は export 名を使う想定、 mock では登録時に必須)。 */
27
+ name?: string;
28
+ /** default args (registry で resolve 時 override args と shallow merge)。 */
29
+ args?: Partial<TArgs>;
30
+ /**
31
+ * play function — story mount 後に interaction を実行する。 Storybook 8 の
32
+ * PlayFunction と互換な `{ canvasElement, args, step }` を受ける。
33
+ */
34
+ play?: (context: PlayContext<TArgs>) => Promise<void> | void;
35
+ /** story 単位の parameters (chromatic viewport, layout 等)。 */
36
+ parameters?: StoryParameters;
37
+ }
38
+ /** Storybook 8 の `parameters` field の最小 shape。 mock で意味のある値のみ抽出。 */
39
+ interface StoryParameters {
40
+ /** chromatic diffThreshold (0-1)、 viewport 名一覧、 delay ms 等。 */
41
+ chromatic?: {
42
+ /** pixel diff の許容率 (0-1)、 default 0)。 */
43
+ diffThreshold?: number;
44
+ /** capture 時の viewport 名一覧 (ChromaticVisualMock で切替)。 */
45
+ viewports?: string[];
46
+ /** capture 前に待つ ms、 mock では noop。 */
47
+ delay?: number;
48
+ /** true なら chromatic capture 対象外。 */
49
+ disable?: boolean;
50
+ };
51
+ /** a11y addon parameters (rules disable/enable、 mock で violations を模倣)。 */
52
+ a11y?: {
53
+ disable?: boolean;
54
+ /** violation を 1 件模倣、 test で violations 検出を assert できる。 */
55
+ injectViolations?: A11yViolation[];
56
+ };
57
+ /** layout hint (centered / fullscreen / padded)、 mock では未使用。 */
58
+ layout?: 'centered' | 'fullscreen' | 'padded';
59
+ }
60
+ /** Storybook 8 の PlayFunction が受ける context。 mock 互換用に最小 shape のみ。 */
61
+ interface PlayContext<TArgs = Record<string, unknown>> {
62
+ /** mount 済 root element (mock では in-memory DOM の抽象 handle)。 */
63
+ canvasElement: CanvasElement;
64
+ /** resolve 済 args (default + override の merge 後)。 */
65
+ args: TArgs;
66
+ /**
67
+ * step wrapper — Storybook 8 の `step('label', async () => {...})` 互換。
68
+ * mock では single-thread に順次実行、 label を trace に記録する。
69
+ */
70
+ step: (label: string, fn: () => Promise<void> | void) => Promise<void>;
71
+ }
72
+ /**
73
+ * mount 済 root の抽象 handle。 実 DOM ではなく、 in-memory な要素 tree として
74
+ * 扱う。 test は `getByText` / `getByRole` / `querySelector` の subset を通じて
75
+ * 要素を取り出し、 `click` / `fill` / `assert` を実行する。
76
+ */
77
+ interface CanvasElement {
78
+ /** mount 時に render された root node。 */
79
+ root: MockNode;
80
+ /** 要素 lookup helpers (最小 subset)。 */
81
+ getByText(text: string): MockNode;
82
+ getByRole(role: string, options?: {
83
+ name?: string;
84
+ }): MockNode;
85
+ querySelector(selector: string): MockNode | null;
86
+ querySelectorAll(selector: string): MockNode[];
87
+ /** capture 時のセルフ dump (Chromatic に渡す markup 表現)。 */
88
+ toMarkup(): string;
89
+ }
90
+ /**
91
+ * mount 対象の component 表現。 実 React / Vue / Svelte component を直接
92
+ * 扱わず、 純関数 `(args) => MockNode` として抽象化する。 mock harness は
93
+ * framework agnostic — Storybook の render callback / Playwright CT の
94
+ * component 引数 / Chromatic の capture 対象を同じ関数で表現する。
95
+ */
96
+ type ComponentRender<TArgs = Record<string, unknown>> = (args: TArgs) => MockNode;
97
+ /**
98
+ * in-memory な DOM node 表現。 実 DOM を使わず tag / attrs / children /
99
+ * text で tree を組む。 test で `click` / `fill` / `text` 操作を行うため
100
+ * event listener + value state だけ持たせる。
101
+ */
102
+ interface MockNode {
103
+ tag: string;
104
+ attrs: Record<string, string>;
105
+ text?: string;
106
+ children: MockNode[];
107
+ /** input / textarea の value (fill 操作で更新)。 */
108
+ value?: string;
109
+ /** on{event} handlers ({ click: [fn1, fn2], input: [fn3] })。 */
110
+ handlers: Record<string, Array<(event: MockEvent) => void>>;
111
+ /** parent への back reference (querySelector traversal 用)。 */
112
+ parent: MockNode | null;
113
+ }
114
+ /** event handler に渡す minimal event object。 */
115
+ interface MockEvent {
116
+ type: string;
117
+ target: MockNode;
118
+ /** input event 時の value。 */
119
+ value?: string;
120
+ }
121
+ /**
122
+ * a11y violation を 1 件模倣。 実 axe-core の Result と field を揃える。
123
+ * (mock harness では injectViolations で外部から注入して test 対象にする)
124
+ */
125
+ interface A11yViolation {
126
+ id: string;
127
+ impact: 'minor' | 'moderate' | 'serious' | 'critical';
128
+ description: string;
129
+ helpUrl?: string;
130
+ nodes: Array<{
131
+ target: string[];
132
+ html: string;
133
+ }>;
134
+ }
135
+ /**
136
+ * StoryRegistry の 1 entry — story ID → StoryObj + render + meta の bind。
137
+ * Storybook 8 の `Meta` + `StoryObj` を 1 record に flatten した shape。
138
+ */
139
+ interface StoryEntry<TArgs = Record<string, unknown>> {
140
+ id: string;
141
+ title: string;
142
+ storyName: string;
143
+ args: TArgs;
144
+ render: ComponentRender<TArgs>;
145
+ play?: (context: PlayContext<TArgs>) => Promise<void> | void;
146
+ parameters: StoryParameters;
147
+ }
148
+ /**
149
+ * Playwright CT の `mount(component)` が返す ComponentLocator の subset。
150
+ * 実 Playwright CT は `Locator` を返して page 全体を操作するが、 mock は
151
+ * mount 済 canvas を直接返して interact に集中する。
152
+ */
153
+ interface ComponentLocator {
154
+ /** mount 時に生成された canvas。 */
155
+ canvas: CanvasElement;
156
+ /** locator chain の起点 element。 */
157
+ root: MockNode;
158
+ /** テキスト locator の subset。 */
159
+ getByText(text: string): NodeLocator;
160
+ getByRole(role: string, options?: {
161
+ name?: string;
162
+ }): NodeLocator;
163
+ /** element を unmount して event handler を全 clear する。 */
164
+ unmount(): void;
165
+ }
166
+ /**
167
+ * Playwright Locator の subset — click / fill / textContent / count / expect。
168
+ * mock は同一 API で real Playwright test と表面互換にする。
169
+ */
170
+ interface NodeLocator {
171
+ click(): Promise<void>;
172
+ fill(value: string): Promise<void>;
173
+ textContent(): Promise<string | null>;
174
+ count(): Promise<number>;
175
+ /** 対象 node への直接 access (mock 側 assert 用)。 */
176
+ node(): MockNode | null;
177
+ }
178
+ /**
179
+ * Chromatic の 1 baseline entry — story id + viewport + markup + hash。
180
+ * baseline JSON store に永続化される (mock は in-memory Map で代替)。
181
+ */
182
+ interface VisualBaseline {
183
+ storyId: string;
184
+ viewport: string;
185
+ markup: string;
186
+ /** markup の deterministic hash (SHA-256 hex substring)。 */
187
+ hash: string;
188
+ /** baseline 生成時刻 (ms)、 accept workflow で比較に使う。 */
189
+ capturedAt: number;
190
+ }
191
+ /**
192
+ * Chromatic の diff 結果 1 件。 changed=true なら pixel diff 検出、
193
+ * pixelDiffRatio が threshold を超えると status=failed になる。
194
+ */
195
+ interface VisualDiff {
196
+ storyId: string;
197
+ viewport: string;
198
+ baselineHash: string;
199
+ currentHash: string;
200
+ changed: boolean;
201
+ /** markup 差分 (0-1)、 hash 完全一致で 0、 完全不一致で 1。 */
202
+ pixelDiffRatio: number;
203
+ status: 'passed' | 'failed' | 'new';
204
+ /** threshold 判定に使った値 (parameters.chromatic.diffThreshold or default)。 */
205
+ threshold: number;
206
+ }
207
+ /**
208
+ * accept / reject workflow の 1 action。 accept は baseline を current で
209
+ * 置換、 reject は baseline を保持したまま diff status を残す。
210
+ */
211
+ type VisualReviewAction = 'accept' | 'reject';
212
+ interface VisualReviewEntry {
213
+ storyId: string;
214
+ viewport: string;
215
+ action: VisualReviewAction;
216
+ reviewedAt: number;
217
+ }
218
+
219
+ /**
220
+ * MockNode + CanvasElement の生成 / traversal / event 発火 helper 群。
221
+ *
222
+ * 実 DOM (jsdom / happy-dom) を使わない理由 = (1) Storybook 8 / Playwright CT /
223
+ * Chromatic の 3 統合 mock で共通の抽象を提供したい、 (2) framework agnostic
224
+ * (React / Vue / Svelte / Solid) にする、 (3) test 速度と決定性を最優先する。
225
+ *
226
+ * MockNode tree は tag / attrs / text / children で表現、 実 DOM の subset を
227
+ * 網羅する (querySelector 相当の tag / .class / #id / attr selector、 getByText、
228
+ * getByRole)。 fill / click は event handler を synchronously 呼ぶ。
229
+ */
230
+ /** node factory — attrs / children / text / value を任意で与える。 */
231
+ declare function createNode(tag: string, options?: NodeOptions): MockNode;
232
+ interface NodeOptions {
233
+ attrs?: Record<string, string>;
234
+ text?: string;
235
+ value?: string;
236
+ children?: MockNode[];
237
+ on?: Record<string, (event: MockEvent) => void>;
238
+ }
239
+ /** node に child を追加、 parent back reference も更新する。 */
240
+ declare function appendChild(parent: MockNode, child: MockNode): void;
241
+ /** event handler を登録、 同一 event に複数 handler を許容する。 */
242
+ declare function addHandler(node: MockNode, event: string, handler: (event: MockEvent) => void): void;
243
+ /**
244
+ * event を発火する。 実 DOM のような bubble はせず target 単発、 mock harness
245
+ * では component 側で bubble 相当を実装する。 fill 相当は input event を発火。
246
+ */
247
+ declare function fireEvent(node: MockNode, event: MockEvent): void;
248
+ /**
249
+ * CanvasElement 生成 — root node と lookup helpers を wrap する。 mount 完了後
250
+ * story / component test / visual capture の全経路で使う。
251
+ */
252
+ declare function createCanvas(root: MockNode): CanvasElement;
253
+ /**
254
+ * MockNode subtree を deterministic な pseudo-HTML 文字列に変換する。
255
+ * event handler は含めず、 tag / attrs / text / children のみ。
256
+ * Chromatic の hash / markup 保存に使う。
257
+ */
258
+ declare function renderMarkup(node: MockNode): string;
259
+ /** markup 文字列を deterministic SHA-256 hex substring (先頭 16) に変換。 */
260
+ declare function hashMarkup(markup: string): string;
261
+ /**
262
+ * text 一致で最初に見つかった node を返す。 深い node を優先する — root 側の
263
+ * aggregated text ではなく、 実際に text を持つ leaf 相当の node を先に返す
264
+ * (Storybook / Testing Library の getByText 挙動と揃える)。
265
+ */
266
+ declare function findByText(node: MockNode, text: string): MockNode | null;
267
+ /**
268
+ * role 属性 (`role="button"`) or implicit role (button tag) と、 aria-label /
269
+ * text で一致する node を返す。 mock は最小 subset (button / link / heading /
270
+ * textbox / checkbox) のみ。
271
+ */
272
+ declare function findByRole(node: MockNode, role: string, accessibleName?: string): MockNode | null;
273
+ /**
274
+ * querySelector の最小 subset。 tag / .class / #id / [attr=value] / 子孫 (space)
275
+ * 結合子を support する。 実 CSS selector 全対応ではないが、 mock 用途では十分。
276
+ *
277
+ * MockNode tree では canvas.root が component 自体を指すため、 root も検査対象
278
+ * に含める (実 DOM の `document.querySelector` は container を除くが、 mock は
279
+ * component rendering root = 検索対象という semantics に揃える)。
280
+ */
281
+ declare function query(root: MockNode, selector: string, all: boolean): MockNode[];
282
+
283
+ /**
284
+ * Storybook 8 の CSF3 と互換な最小 mock。 real Storybook (SB8) は Meta +
285
+ * StoryObj 単位で `.stories.tsx` を書き、 storybook が collect して registry
286
+ * を作る。 mock はその registry 相当を in-memory Map で表現する。
287
+ *
288
+ * 使い方 ...
289
+ * ```ts
290
+ * const registry = createStoryRegistry();
291
+ * registry.register({
292
+ * title: 'Button',
293
+ * render: (args) => createNode('button', { text: args.label }),
294
+ * stories: {
295
+ * Primary: { args: { label: 'Click me' } },
296
+ * Disabled: { args: { label: 'nope', disabled: true } },
297
+ * },
298
+ * });
299
+ * const canvas = await registry.mount('Button', 'Primary');
300
+ * await registry.play('Button', 'Primary', canvas);
301
+ * ```
302
+ *
303
+ * SB8 の実 API 互換で意識するのは (1) StoryObj.args の merge (2) play の
304
+ * step wrapper (3) parameters.chromatic / parameters.a11y の透過保持
305
+ * の 3 点。 実 SB8 の docs mode / decorators / loaders / addons は mock 対象外
306
+ * (テストが吸収する semantic は絞る)。
307
+ */
308
+ interface StoryMeta<TArgs = Record<string, unknown>> {
309
+ /** Storybook の title (e.g. 'Components/Button')。 */
310
+ title: string;
311
+ /** args → MockNode の render 関数、 framework agnostic。 */
312
+ render: ComponentRender<TArgs>;
313
+ /** meta 単位の default args (story 単位で override 可)。 */
314
+ args?: Partial<TArgs>;
315
+ /** meta 単位の default parameters (story 単位で shallow merge)。 */
316
+ parameters?: StoryParameters;
317
+ /** 登録する story 群、 key が story 名になる。 */
318
+ stories: Record<string, StoryObj<TArgs>>;
319
+ }
320
+ interface StoryMountResult {
321
+ canvas: CanvasElement;
322
+ entry: StoryEntry<Record<string, unknown>>;
323
+ }
324
+ interface StoryPlayResult {
325
+ steps: Array<{
326
+ label: string;
327
+ ok: boolean;
328
+ error?: string;
329
+ }>;
330
+ ok: boolean;
331
+ }
332
+ interface StoryRegistry {
333
+ register<TArgs>(meta: StoryMeta<TArgs>): void;
334
+ list(): StoryEntry[];
335
+ get(title: string, storyName: string): StoryEntry;
336
+ mount(title: string, storyName: string, overrideArgs?: Record<string, unknown>): StoryMountResult;
337
+ play(title: string, storyName: string, canvas: CanvasElement, args?: Record<string, unknown>): Promise<StoryPlayResult>;
338
+ runA11y(title: string, storyName: string, canvas: CanvasElement): {
339
+ violations: A11yViolation[];
340
+ };
341
+ }
342
+ /**
343
+ * Registry を新規作成する。 内部は `Map<storyId, StoryEntry>`、 story id は
344
+ * `title--storyName` (Storybook 8 の SB URL param 互換 lowercase / kebab-case)。
345
+ */
346
+ declare function createStoryRegistry(): StoryRegistry;
347
+
348
+ /**
349
+ * Playwright Component Testing (CT) 互換の最小 mock。 real Playwright CT は
350
+ * `mount(component)` → `Locator` を返し、 `page.getByText().click()` 等で
351
+ * interact する。 mock は同じ API 表面 (mount / getByText / getByRole / click /
352
+ * fill / textContent / count) を持ち、 real vs mock で同じ test を回せる。
353
+ *
354
+ * 実 Playwright CT との差分 = (1) browser process を起動しない (in-memory)、
355
+ * (2) network request の intercept / route は非 support (別の kiwa mock で
356
+ * 対応)、 (3) screenshot は Chromatic 経路に一本化する。
357
+ *
358
+ * 使い方 ...
359
+ * ```ts
360
+ * const ct = createPlaywrightCTMock();
361
+ * const button = ct.mount((args) => createNode('button', { text: args.label, on: { click: args.onClick } }), { label: 'ok', onClick: () => hits++ });
362
+ * await button.getByRole('button', { name: 'ok' }).click();
363
+ * expect(hits).toBe(1);
364
+ * ```
365
+ */
366
+ interface PlaywrightCTMock {
367
+ mount<TArgs>(render: ComponentRender<TArgs>, args: TArgs): ComponentLocator;
368
+ /** mount 済 locator 一覧 (test teardown で全 unmount する時使う)。 */
369
+ activeMounts(): number;
370
+ /** 全 mount を解放する — vitest afterEach 相当の cleanup。 */
371
+ unmountAll(): void;
372
+ }
373
+ /**
374
+ * PlaywrightCTMock を新規作成する。 mount 毎に in-memory canvas を組み、
375
+ * ComponentLocator に wrap して返す。 activeMounts count は resource leak
376
+ * 検出用 (test で unmount 忘れを assert できる)。
377
+ */
378
+ declare function createPlaywrightCTMock(): PlaywrightCTMock;
379
+
380
+ /**
381
+ * Chromatic 型の visual regression mock。 real Chromatic は Storybook 8 を
382
+ * headless で snapshot 撮り、 baseline JSON を server 側に置いて diff を
383
+ * pixel single で計算する。 mock harness は browser を起動せず、 mount 後の
384
+ * MockNode を `renderMarkup` で pseudo-HTML 化 → SHA-256 hash → hash 比較で
385
+ * changed 判定を行う (pixel diff は hash 完全一致 = 0、 不一致 = 1、
386
+ * partial diff の連続値は mock 対象外)。
387
+ *
388
+ * mock で意味のある semantic は 4 つ ...
389
+ * (1) baseline capture (未登録なら status='new'、 hash を保存)
390
+ * (2) current capture + diff (hash 一致で passed、 不一致で failed / changed)
391
+ * (3) diffThreshold の適用 (StoryEntry.parameters.chromatic.diffThreshold)
392
+ * (4) accept / reject workflow (accept で baseline を current で置換)
393
+ *
394
+ * multi viewport support = parameters.chromatic.viewports に列挙された viewport
395
+ * 名を 1 回ずつ capture し、 viewport × storyId 単位で baseline / diff を管理。
396
+ */
397
+ interface ChromaticVisualMock {
398
+ /** baseline / current 群を全 clear。 test 間の isolation 用。 */
399
+ reset(): void;
400
+ /** baseline を明示 seed (test setup で初期状態を作る時使う)。 */
401
+ seedBaseline(input: {
402
+ storyId: string;
403
+ viewport: string;
404
+ markup: string;
405
+ capturedAt?: number;
406
+ }): VisualBaseline;
407
+ /** 1 story × 1 viewport を capture、 baseline 不在なら status='new'。 */
408
+ capture(input: {
409
+ entry: StoryEntry;
410
+ canvas: CanvasElement;
411
+ viewport?: string;
412
+ now?: number;
413
+ }): VisualDiff;
414
+ /**
415
+ * 1 story を parameters.chromatic.viewports 全 viewport で一括 capture。
416
+ * viewports 未指定 or 空なら 'default' viewport 1 件のみ。 disabled story は
417
+ * skip (空配列を返す)。
418
+ */
419
+ captureAll(input: {
420
+ entry: StoryEntry;
421
+ canvas: CanvasElement;
422
+ now?: number;
423
+ }): VisualDiff[];
424
+ /** accept / reject 1 件。 accept は baseline を current 相当で置換する。 */
425
+ review(input: {
426
+ storyId: string;
427
+ viewport: string;
428
+ action: VisualReviewAction;
429
+ reviewedAt?: number;
430
+ }): VisualReviewEntry;
431
+ /** review 履歴一覧 (test で workflow を assert する時使う)。 */
432
+ reviewHistory(): VisualReviewEntry[];
433
+ /** baseline 一覧 (test 用 introspection)。 */
434
+ baselines(): VisualBaseline[];
435
+ }
436
+ /**
437
+ * ChromaticVisualMock を新規作成する。 baseline / current / review を全て
438
+ * in-memory Map で保持し、 reset() で全 clear できる (test isolation 用)。
439
+ */
440
+ declare function createChromaticVisualMock(config?: ChromaticConfig): ChromaticVisualMock;
441
+ interface ChromaticConfig {
442
+ /** viewport 名の default (未指定 story の 1 件 capture 時に使う)。 */
443
+ defaultViewport?: string;
444
+ /** parameters.chromatic.diffThreshold 未指定時の default。 default = 0 (完全一致で pass)。 */
445
+ defaultDiffThreshold?: number;
446
+ /** capturedAt / reviewedAt 用の deterministic time source (test 決定性)。 */
447
+ now?: () => number;
448
+ }
449
+
450
+ /**
451
+ * 5 pattern の共通 component fixture。 Storybook story / Playwright CT mount /
452
+ * Chromatic capture の 3 経路で共有できる framework agnostic renderer。 実 SB /
453
+ * PW CT / Chromatic に持込む時は各 framework (React / Vue / Svelte / Solid) の
454
+ * component として書換えるが、 test だけ回す用途はこの fixture で完結する。
455
+ *
456
+ * 5 pattern の選定理由 = SaaS frontend で頻出する 5 primitive を全 cover する。
457
+ * - Button (interactive、 click event、 disabled state)
458
+ * - Input (controlled、 input event、 label association)
459
+ * - Form (submit event、 field 集約、 validation)
460
+ * - Modal (open/close state、 backdrop click、 escape close)
461
+ * - Card (content wrapping、 title + body、 optional footer)
462
+ */
463
+ interface ButtonArgs {
464
+ label: string;
465
+ disabled?: boolean;
466
+ variant?: 'primary' | 'secondary' | 'danger';
467
+ onClick?: (event: MockEvent) => void;
468
+ }
469
+ /**
470
+ * Button — text label + optional click handler + disabled state。
471
+ * variant は class 属性に反映 (chromatic diff で variant 別 baseline を持てる)。
472
+ */
473
+ declare const buildButton: ComponentRender<ButtonArgs>;
474
+ interface InputArgs {
475
+ id: string;
476
+ label: string;
477
+ value?: string;
478
+ type?: 'text' | 'email' | 'password' | 'number';
479
+ required?: boolean;
480
+ placeholder?: string;
481
+ onChange?: (event: MockEvent) => void;
482
+ }
483
+ /**
484
+ * Input — label + input の pair、 label[for] で id を関連付ける (a11y label
485
+ * rule を pass する)。 onChange は input event で発火。
486
+ */
487
+ declare const buildInput: ComponentRender<InputArgs>;
488
+ interface FormField {
489
+ id: string;
490
+ label: string;
491
+ type?: InputArgs['type'];
492
+ required?: boolean;
493
+ value?: string;
494
+ }
495
+ interface FormArgs {
496
+ title: string;
497
+ fields: FormField[];
498
+ submitLabel?: string;
499
+ onSubmit?: (data: Record<string, string>) => void;
500
+ }
501
+ /**
502
+ * Form — title + 複数 input + submit button。 submit 時に全 field の value を
503
+ * 集めて onSubmit に渡す。 required field 未入力なら submit を発火しない
504
+ * (validation)、 UI 側で「必須」 表示を出す責務。
505
+ */
506
+ declare const buildForm: ComponentRender<FormArgs>;
507
+ interface ModalArgs {
508
+ open: boolean;
509
+ title: string;
510
+ body: string;
511
+ onClose?: () => void;
512
+ closeOnBackdrop?: boolean;
513
+ }
514
+ /**
515
+ * Modal — open=false なら空 div を返す (closed 状態を表現)。 open=true で
516
+ * backdrop + dialog を組む。 backdrop click / close button click で
517
+ * onClose を発火。
518
+ */
519
+ declare const buildModal: ComponentRender<ModalArgs>;
520
+ interface CardArgs {
521
+ title: string;
522
+ body: string;
523
+ footer?: string;
524
+ variant?: 'default' | 'outlined' | 'elevated';
525
+ }
526
+ /**
527
+ * Card — title / body / optional footer の 3 slot。 chromatic diff で variant
528
+ * 別 baseline を持つ用途の代表 pattern。
529
+ */
530
+ declare const buildCard: ComponentRender<CardArgs>;
531
+ /** 全 component の render function を 1 record にまとめる (test 一括登録用)。 */
532
+ declare const componentFixtures: Record<string, ComponentRender<Record<string, unknown>>>;
533
+
534
+ export { type A11yViolation, type ButtonArgs, type CanvasElement, type CardArgs, type ChromaticConfig, type ChromaticVisualMock, type ComponentLocator, type ComponentProvider, type ComponentRender, type FormArgs, type FormField, type InputArgs, type MockEvent, type MockNode, type ModalArgs, type NodeLocator, type NodeOptions, type PlayContext, type PlaywrightCTMock, type StoryEntry, type StoryMeta, type StoryMountResult, type StoryObj, type StoryParameters, type StoryPlayResult, type StoryRegistry, type VisualBaseline, type VisualDiff, type VisualReviewAction, type VisualReviewEntry, addHandler, appendChild, buildButton, buildCard, buildForm, buildInput, buildModal, componentFixtures, createCanvas, createChromaticVisualMock, createNode, createPlaywrightCTMock, createStoryRegistry, findByRole, findByText, fireEvent, hashMarkup, query, renderMarkup };