@assure-one/design-system 1.35.0 → 1.37.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.
Files changed (38) hide show
  1. package/README.md +52 -0
  2. package/codemods/README.md +155 -12
  3. package/codemods/lib/registry.mjs +5 -0
  4. package/codemods/transforms/cm-07-input-size.mjs +104 -0
  5. package/codemods/transforms/cm-08-search-select-to-combobox.mjs +236 -0
  6. package/codemods/transforms/cm-09-side-drawer-to-sheet.mjs +348 -0
  7. package/codemods/transforms/cm-10-date-picker-value-change.mjs +416 -0
  8. package/codemods/transforms/cm-13-tooltip-delay.mjs +99 -0
  9. package/codemods/transforms/cm-14-hidden-mirrors.mjs +2 -2
  10. package/dist/css/components.css +1 -1
  11. package/dist/css/legacy-aliases.css +25 -1
  12. package/dist/css/shadcn.css +2 -2
  13. package/dist/css/tailwind.css +9 -0
  14. package/dist/css/tokens.css +64 -38
  15. package/dist/design-system-provider-C8UlFe52.d.ts +549 -0
  16. package/dist/icons/index.d.ts +2 -2
  17. package/dist/icons/index.js +6 -2
  18. package/dist/icons/index.js.map +1 -1
  19. package/dist/{index-B3OiMlQv.d.ts → index-vztxMSfl.d.ts} +7 -4
  20. package/dist/index.d.ts +2259 -345
  21. package/dist/index.js +7229 -3057
  22. package/dist/index.js.map +1 -1
  23. package/dist/next/index.d.ts +20 -0
  24. package/dist/next/index.js +16 -0
  25. package/dist/next/index.js.map +1 -0
  26. package/dist/styles.css +1 -1
  27. package/dist/{system-lyhOUTpa.d.ts → system-CyAVbHhM.d.ts} +17 -17
  28. package/dist/testing/index.cjs +133 -11
  29. package/dist/testing/index.d.cts +98 -2
  30. package/dist/testing/index.d.ts +98 -2
  31. package/dist/testing/index.js +132 -12
  32. package/dist/tokens/index.d.ts +1 -1
  33. package/dist/tokens/index.js +8 -8
  34. package/dist/tokens/index.js.map +1 -1
  35. package/docs/components.md +606 -0
  36. package/docs/components.registry.json +1604 -0
  37. package/docs/for-ai-agents.md +153 -0
  38. package/package.json +15 -3
@@ -33,7 +33,7 @@ declare const colors: {
33
33
  readonly proDisabled: "#b7a7f3";
34
34
  readonly proLine: "#7b5bd8";
35
35
  readonly fgDisabled: "#a7a7ac";
36
- readonly bgDisabled: "#f3f3f1";
36
+ readonly bgDisabled: "#f1f2f6";
37
37
  readonly successFg: "#1f7a3d";
38
38
  readonly successBg: "#eaf6ee";
39
39
  readonly successLine: "#c5e5cf";
@@ -58,7 +58,7 @@ declare const typography: {
58
58
  readonly semibold: 600;
59
59
  readonly bold: 700;
60
60
  };
61
- readonly ease: "cubic-bezier(0.2, 0.8, 0.2, 1)";
61
+ readonly ease: "cubic-bezier(0.25, 1, 0.5, 1)";
62
62
  };
63
63
  declare const radii: {
64
64
  readonly btn: "7px";
@@ -69,14 +69,14 @@ declare const radii: {
69
69
  readonly full: "9999px";
70
70
  };
71
71
  declare const shadows: {
72
- readonly quiet: "0 1px 2px rgba(16, 20, 36, 0.05)";
73
- readonly card: "0 1px 2px rgba(16, 20, 36, 0.04), 0 4px 12px -4px rgba(16, 20, 36, 0.08)";
74
- readonly pop: "0 0 0 1px rgba(16, 20, 36, 0.03), 0 2px 4px -1px rgba(16, 20, 36, 0.06), 0 12px 28px -6px rgba(16, 20, 36, 0.14)";
72
+ readonly quiet: "0 1px 1px rgba(16, 20, 36, 0.03), 0 1px 3px rgba(16, 20, 36, 0.05)";
73
+ readonly card: "0 1px 2px rgba(16, 20, 36, 0.04), 0 2px 4px -2px rgba(16, 20, 36, 0.04), 0 8px 16px -6px rgba(16, 20, 36, 0.08)";
74
+ readonly pop: "0 0 0 1px rgba(16, 20, 36, 0.04), 0 2px 4px -1px rgba(16, 20, 36, 0.05), 0 6px 12px -3px rgba(16, 20, 36, 0.07), 0 16px 32px -8px rgba(16, 20, 36, 0.12)";
75
75
  readonly focusRing: "0 0 0 2px #ffffff, 0 0 0 4px #6c42f8";
76
76
  };
77
77
  declare const overlays: {
78
- readonly hover: "rgba(10, 10, 10, 0.03)";
79
- readonly active: "rgba(10, 10, 10, 0.05)";
78
+ readonly hover: "rgba(16, 20, 36, 0.04)";
79
+ readonly active: "rgba(16, 20, 36, 0.07)";
80
80
  };
81
81
  declare const layout: {
82
82
  readonly shellSidebarWidth: "200px";
@@ -115,7 +115,7 @@ declare const surfaces: {
115
115
  };
116
116
  declare const motion: {
117
117
  readonly ease: {
118
- readonly outQuart: "cubic-bezier(0.2, 0.8, 0.2, 1)";
118
+ readonly outQuart: "cubic-bezier(0.25, 1, 0.5, 1)";
119
119
  readonly out: "cubic-bezier(0.16, 1, 0.3, 1)";
120
120
  readonly in: "cubic-bezier(0.7, 0, 0.84, 0)";
121
121
  readonly inOut: "cubic-bezier(0.65, 0, 0.35, 1)";
@@ -195,7 +195,7 @@ declare const reference: {
195
195
  readonly proDisabled: "#b7a7f3";
196
196
  readonly proLine: "#7b5bd8";
197
197
  readonly fgDisabled: "#a7a7ac";
198
- readonly bgDisabled: "#f3f3f1";
198
+ readonly bgDisabled: "#f1f2f6";
199
199
  readonly successFg: "#1f7a3d";
200
200
  readonly successBg: "#eaf6ee";
201
201
  readonly successLine: "#c5e5cf";
@@ -220,7 +220,7 @@ declare const reference: {
220
220
  readonly semibold: 600;
221
221
  readonly bold: 700;
222
222
  };
223
- readonly ease: "cubic-bezier(0.2, 0.8, 0.2, 1)";
223
+ readonly ease: "cubic-bezier(0.25, 1, 0.5, 1)";
224
224
  };
225
225
  readonly radii: {
226
226
  readonly btn: "7px";
@@ -231,14 +231,14 @@ declare const reference: {
231
231
  readonly full: "9999px";
232
232
  };
233
233
  readonly shadows: {
234
- readonly quiet: "0 1px 2px rgba(16, 20, 36, 0.05)";
235
- readonly card: "0 1px 2px rgba(16, 20, 36, 0.04), 0 4px 12px -4px rgba(16, 20, 36, 0.08)";
236
- readonly pop: "0 0 0 1px rgba(16, 20, 36, 0.03), 0 2px 4px -1px rgba(16, 20, 36, 0.06), 0 12px 28px -6px rgba(16, 20, 36, 0.14)";
234
+ readonly quiet: "0 1px 1px rgba(16, 20, 36, 0.03), 0 1px 3px rgba(16, 20, 36, 0.05)";
235
+ readonly card: "0 1px 2px rgba(16, 20, 36, 0.04), 0 2px 4px -2px rgba(16, 20, 36, 0.04), 0 8px 16px -6px rgba(16, 20, 36, 0.08)";
236
+ readonly pop: "0 0 0 1px rgba(16, 20, 36, 0.04), 0 2px 4px -1px rgba(16, 20, 36, 0.05), 0 6px 12px -3px rgba(16, 20, 36, 0.07), 0 16px 32px -8px rgba(16, 20, 36, 0.12)";
237
237
  readonly focusRing: "0 0 0 2px #ffffff, 0 0 0 4px #6c42f8";
238
238
  };
239
239
  readonly overlays: {
240
- readonly hover: "rgba(10, 10, 10, 0.03)";
241
- readonly active: "rgba(10, 10, 10, 0.05)";
240
+ readonly hover: "rgba(16, 20, 36, 0.04)";
241
+ readonly active: "rgba(16, 20, 36, 0.07)";
242
242
  };
243
243
  readonly layout: {
244
244
  readonly shellSidebarWidth: "200px";
@@ -247,7 +247,7 @@ declare const reference: {
247
247
  };
248
248
  readonly motion: {
249
249
  readonly ease: {
250
- readonly outQuart: "cubic-bezier(0.2, 0.8, 0.2, 1)";
250
+ readonly outQuart: "cubic-bezier(0.25, 1, 0.5, 1)";
251
251
  readonly out: "cubic-bezier(0.16, 1, 0.3, 1)";
252
252
  readonly in: "cubic-bezier(0.7, 0, 0.84, 0)";
253
253
  readonly inOut: "cubic-bezier(0.65, 0, 0.35, 1)";
@@ -556,4 +556,4 @@ declare const systemTokens: {
556
556
  };
557
557
  type SystemTokens = typeof systemTokens;
558
558
 
559
- export { type ColorName as C, type ReferenceTokens as R, type SystemTokens as S, reference as a, breakpoints as b, colors as c, spacing as d, surfaces as e, systemTokens as f, iconSizes as i, layout as l, motion as m, overlays as o, radii as r, shadows as s, typography as t, zIndex as z };
559
+ export { type ColorName as C, type ReferenceTokens as R, type SystemTokens as S, reference as a, spacing as b, colors as c, surfaces as d, systemTokens as e, breakpoints as f, iconSizes as i, layout as l, motion as m, overlays as o, radii as r, shadows as s, typography as t, zIndex as z };
@@ -312,14 +312,28 @@ function accessibleText(element) {
312
312
  function isDisabled(element) {
313
313
  return element.hasAttribute("disabled") || element.getAttribute("aria-disabled") === "true" || element.hasAttribute("data-disabled");
314
314
  }
315
+ function isQueries(target) {
316
+ return typeof target.getByRole === "function";
317
+ }
315
318
  function press(target, key) {
316
319
  const view = target.ownerDocument.defaultView ?? window;
317
320
  const init = { key, code: key === " " ? "Space" : key, bubbles: true, cancelable: true };
318
321
  target.dispatchEvent(new view.KeyboardEvent("keydown", init));
319
322
  target.dispatchEvent(new view.KeyboardEvent("keyup", init));
320
323
  }
324
+ async function run(fn) {
325
+ const actEnvironment = globalThis.IS_REACT_ACT_ENVIRONMENT;
326
+ if (actEnvironment) {
327
+ await react.act(async () => {
328
+ await fn();
329
+ });
330
+ return;
331
+ }
332
+ await fn();
333
+ await new Promise((resolve) => setTimeout(resolve, 0));
334
+ }
321
335
  async function flush() {
322
- await react.act(async () => {
336
+ await run(async () => {
323
337
  await Promise.resolve();
324
338
  });
325
339
  }
@@ -332,7 +346,7 @@ async function waitUntil(find, maxFlushes) {
332
346
  return null;
333
347
  }
334
348
  function resolveTrigger(target, name, role) {
335
- if (typeof target.getByRole === "function") {
349
+ if (isQueries(target)) {
336
350
  if (name === void 0)
337
351
  throw new Error(`A ${role} name is required when passing a query object.`);
338
352
  return target.getByRole(role, { name });
@@ -350,8 +364,33 @@ function popupOf(trigger, role) {
350
364
  }
351
365
  return null;
352
366
  }
367
+ function isTextControl(element) {
368
+ const tag = element.tagName;
369
+ return tag === "TEXTAREA" || tag === "INPUT" && !/^(checkbox|radio|file|button|submit|reset|image|hidden)$/i.test(
370
+ element.type
371
+ );
372
+ }
373
+ async function typeInto(control, text2, commit) {
374
+ const view = control.ownerDocument.defaultView ?? window;
375
+ const proto = control.tagName === "TEXTAREA" ? view.HTMLTextAreaElement.prototype : view.HTMLInputElement.prototype;
376
+ const setter = Object.getOwnPropertyDescriptor(proto, "value")?.set;
377
+ await run(() => {
378
+ control.focus();
379
+ if (setter) setter.call(control, text2);
380
+ else control.value = text2;
381
+ control.dispatchEvent(new view.Event("input", { bubbles: true }));
382
+ });
383
+ if (commit) {
384
+ await run(() => {
385
+ control.blur();
386
+ });
387
+ }
388
+ await flush();
389
+ }
390
+
391
+ // src/testing/interactions.ts
353
392
  async function selectOption(target, ...rest) {
354
- const byQuery = typeof target.getByRole === "function";
393
+ const byQuery = isQueries(target);
355
394
  const label = byQuery ? rest[0] : void 0;
356
395
  const option = byQuery ? rest[1] : rest[0];
357
396
  const settings = (byQuery ? rest[2] : rest[1]) ?? {};
@@ -363,7 +402,7 @@ async function selectOption(target, ...rest) {
363
402
  );
364
403
  }
365
404
  if (trigger.getAttribute("aria-expanded") !== "true") {
366
- await react.act(async () => {
405
+ await run(() => {
367
406
  trigger.focus();
368
407
  press(trigger, "Enter");
369
408
  });
@@ -378,16 +417,16 @@ async function selectOption(target, ...rest) {
378
417
  const wanted = all.find((el) => matches(accessibleText(el), option));
379
418
  if (!wanted) {
380
419
  const available = all.map((el) => JSON.stringify(accessibleText(el))).join(", ");
381
- await react.act(async () => press(listbox, "Escape"));
420
+ await run(() => press(listbox, "Escape"));
382
421
  throw new Error(
383
422
  `selectOption: no option matches ${describe(option)}. Options: ${available || "(none)"}.`
384
423
  );
385
424
  }
386
425
  if (isDisabled(wanted)) {
387
- await react.act(async () => press(listbox, "Escape"));
426
+ await run(() => press(listbox, "Escape"));
388
427
  throw new Error(`selectOption: the option ${describe(option)} is disabled.`);
389
428
  }
390
- await react.act(async () => {
429
+ await run(() => {
391
430
  wanted.focus();
392
431
  press(wanted, "Enter");
393
432
  });
@@ -396,7 +435,7 @@ async function selectOption(target, ...rest) {
396
435
  throw new Error(`selectOption: the list stayed open after choosing ${describe(option)}.`);
397
436
  }
398
437
  async function openMenu(target, ...rest) {
399
- const byQuery = typeof target.getByRole === "function";
438
+ const byQuery = isQueries(target);
400
439
  const name = byQuery ? rest[0] : void 0;
401
440
  const settings = (byQuery ? rest[1] : rest[0]) ?? {};
402
441
  const maxFlushes = settings.maxFlushes ?? 20;
@@ -407,7 +446,7 @@ async function openMenu(target, ...rest) {
407
446
  );
408
447
  }
409
448
  if (trigger.getAttribute("aria-expanded") !== "true") {
410
- await react.act(async () => {
449
+ await run(() => {
411
450
  trigger.focus();
412
451
  press(trigger, "ArrowDown");
413
452
  });
@@ -445,21 +484,104 @@ async function openMenu(target, ...rest) {
445
484
  const item = getItem(itemName);
446
485
  if (isDisabled(item))
447
486
  throw new Error(`openMenu: the item ${describe(itemName)} is disabled.`);
448
- await react.act(async () => {
487
+ await run(() => {
449
488
  item.focus();
450
489
  press(item, "Enter");
451
490
  });
452
491
  await waitUntil(() => menu.isConnected ? null : true, maxFlushes);
453
492
  },
454
493
  async close() {
455
- await react.act(async () => press(menu, "Escape"));
494
+ await run(() => press(menu, "Escape"));
456
495
  await waitClosed("Escape");
457
496
  }
458
497
  };
459
498
  }
460
499
 
500
+ // src/testing/helpers/fill-field.ts
501
+ var TEXT_ROLES = ["textbox", "spinbutton", "combobox"];
502
+ function resolveByLabel(queries, label) {
503
+ const errors = [];
504
+ for (const role of TEXT_ROLES) {
505
+ try {
506
+ return queries.getByRole(role, { name: label });
507
+ } catch (error) {
508
+ errors.push(error instanceof Error ? error.message : String(error));
509
+ }
510
+ }
511
+ throw new Error(
512
+ `fillField: no text control is labelled ${describe(label)} (looked for roles ${TEXT_ROLES.join(", ")}).
513
+ ${errors[0] ?? ""}`
514
+ );
515
+ }
516
+ async function fillField(target, ...rest) {
517
+ const byQuery = isQueries(target);
518
+ const label = byQuery ? rest[0] : void 0;
519
+ const value = byQuery ? rest[1] : rest[0];
520
+ const settings = (byQuery ? rest[2] : rest[1]) ?? {};
521
+ const control = byQuery ? resolveByLabel(target, label) : target;
522
+ const name = label ?? accessibleText(control);
523
+ if (!isTextControl(control)) {
524
+ throw new Error(
525
+ `fillField: ${describe(name)} is a <${control.tagName.toLowerCase()}>, not a text input or textarea. Use selectOption for a Select and pickDate for a DatePicker.`
526
+ );
527
+ }
528
+ if (isDisabled(control)) throw new Error(`fillField: the control ${describe(name)} is disabled.`);
529
+ if (control.readOnly || control.getAttribute("aria-readonly") === "true") {
530
+ throw new Error(`fillField: the control ${describe(name)} is read-only.`);
531
+ }
532
+ await typeInto(control, value, settings.commit ?? true);
533
+ }
534
+
535
+ // src/testing/helpers/pick-date.ts
536
+ var ISO = /^(\d{4})-(\d{2})-(\d{2})$/;
537
+ var DEFAULT_PATTERN = "MM/DD/YYYY";
538
+ function looksLikePattern(text2) {
539
+ return !!text2 && /YYYY/.test(text2) && /MM/.test(text2) === /DD/.test(text2);
540
+ }
541
+ function formatIso(iso, pattern) {
542
+ const match = ISO.exec(iso);
543
+ if (!match) {
544
+ throw new Error(
545
+ `pickDate: expected an ISO date (YYYY-MM-DD), received ${JSON.stringify(iso)}.`
546
+ );
547
+ }
548
+ const [, year, month, day] = match;
549
+ return pattern.replace("YYYY", year).replace("MM", month).replace("DD", day);
550
+ }
551
+ async function pickDate(target, ...rest) {
552
+ const byQuery = isQueries(target);
553
+ const label = byQuery ? rest[0] : void 0;
554
+ const iso = byQuery ? rest[1] : rest[0];
555
+ const settings = (byQuery ? rest[2] : rest[1]) ?? {};
556
+ const input = byQuery ? target.getByRole("textbox", { name: label }) : target;
557
+ const name = label ?? accessibleText(input);
558
+ if (!isTextControl(input) || input.tagName !== "INPUT") {
559
+ throw new Error(
560
+ `pickDate: ${describe(name)} is not a DatePicker text input (a <${input.tagName.toLowerCase()}>).`
561
+ );
562
+ }
563
+ if (isDisabled(input))
564
+ throw new Error(`pickDate: the date picker ${describe(name)} is disabled.`);
565
+ if (input.readOnly) throw new Error(`pickDate: the date picker ${describe(name)} is read-only.`);
566
+ const placeholder = input.getAttribute("placeholder");
567
+ const pattern = settings.dateFormat ?? (looksLikePattern(placeholder) ? placeholder : DEFAULT_PATTERN);
568
+ const yearOnly = !/MM|DD/.test(pattern);
569
+ let display = "";
570
+ if (iso !== "") {
571
+ display = yearOnly ? formatIso(iso, "YYYY") : formatIso(iso, pattern);
572
+ }
573
+ await typeInto(input, display, true);
574
+ if (input.value !== display) {
575
+ throw new Error(
576
+ `pickDate: typed ${JSON.stringify(display)} into ${describe(name)} but it shows ${JSON.stringify(input.value)}. The picker's pattern is probably not ${JSON.stringify(pattern)}; pass { dateFormat } with the pattern the picker uses.`
577
+ );
578
+ }
579
+ }
580
+
461
581
  exports.createToastRecorder = createToastRecorder;
582
+ exports.fillField = fillField;
462
583
  exports.installDomPolyfills = installDomPolyfills;
463
584
  exports.openMenu = openMenu;
585
+ exports.pickDate = pickDate;
464
586
  exports.selectOption = selectOption;
465
587
  exports.withAssureDesignSystem = withAssureDesignSystem;
@@ -182,11 +182,22 @@ interface RoleQueries {
182
182
  }
183
183
  /** Text matcher: exact text (after trimming and collapsing whitespace) or a pattern. @experimental */
184
184
  type TextMatch = string | RegExp;
185
- /** Options for `selectOption` and `openMenu`. @experimental */
185
+ /** Options for the interaction helpers. @experimental */
186
186
  interface InteractionOptions {
187
187
  /** How many render passes to wait for the popup to appear or close. Default 20. */
188
188
  maxFlushes?: number;
189
189
  }
190
+
191
+ /**
192
+ * Interaction helpers that drive design-system components the way a keyboard
193
+ * user does, so tests do not encode Radix internals (ADR-014).
194
+ *
195
+ * They use only the DOM (roles, `aria-*`, keyboard events) and React's `act`,
196
+ * so they work with any renderer and any query library. This repository tests
197
+ * them against the real components; if a component's markup changes, those
198
+ * tests fail here rather than in a consumer.
199
+ */
200
+
190
201
  /**
191
202
  * Open a design-system `Select` and choose an option by its visible text.
192
203
  *
@@ -250,4 +261,89 @@ declare function openMenu(trigger: HTMLElement, options?: InteractionOptions): P
250
261
  */
251
262
  declare function openMenu(queries: RoleQueries, name: TextMatch, options?: InteractionOptions): Promise<MenuHandle>;
252
263
 
253
- export { type AssureJestOptions, type DomPolyfill, type DomPolyfillOptions, type InteractionOptions, type JestConfigInput, type JestConfigLike, type MenuHandle, type RecordedToast, type RecordedToastVariant, type RoleQueries, type TextMatch, type ToastRecorder, type ToastRecorderOptions, type WithAssureDesignSystem, createToastRecorder, installDomPolyfills, openMenu, selectOption, withAssureDesignSystem };
264
+ /**
265
+ * `fillField` — type a value into a design-system text control found by its
266
+ * Field label (ADR-014, W4-26).
267
+ */
268
+
269
+ /** Options for `fillField`. @experimental */
270
+ interface FillFieldOptions {
271
+ /**
272
+ * Blur the control after typing so controls that commit on blur
273
+ * (`NumberInput` formatting) settle. Default `true`; pass `false` to keep
274
+ * focus, for example before pressing Enter yourself.
275
+ */
276
+ commit?: boolean;
277
+ }
278
+ /**
279
+ * Type `value` into a text control — `Input`, `Textarea`, `SearchInput`,
280
+ * `NumberInput`, a `DatePicker`'s text input — replacing what it holds.
281
+ *
282
+ * ```ts
283
+ * await fillField(screen, "Full name", "Ada Lovelace"); // control named by its Field label
284
+ * await fillField(screen.getByRole("textbox", { name: "Notes" }), "Late filing");
285
+ * ```
286
+ *
287
+ * The control is found by its accessible name, which inside a `<Field>` is
288
+ * the `Label` text. React's `onChange` fires once with the new value; the
289
+ * control's own masking or formatting applies as it would for a user. Throws
290
+ * when the control is disabled or read-only, or when nothing is labelled
291
+ * `label`.
292
+ *
293
+ * @experimental
294
+ */
295
+ declare function fillField(control: HTMLElement, value: string, options?: FillFieldOptions): Promise<void>;
296
+ /**
297
+ * Type `value` into the text control whose accessible name is `label`, found
298
+ * through `queries` (for example Testing Library's `screen`).
299
+ *
300
+ * @experimental
301
+ */
302
+ declare function fillField(queries: RoleQueries, label: TextMatch, value: string, options?: FillFieldOptions): Promise<void>;
303
+
304
+ /**
305
+ * `pickDate` — set a design-system `DatePicker` to an ISO date by typing into
306
+ * its masked text input (ADR-014, W4-26).
307
+ */
308
+
309
+ /** Options for `pickDate`. @experimental */
310
+ interface PickDateOptions {
311
+ /**
312
+ * The picker's display pattern — `MM`, `DD` and `YYYY` around literal
313
+ * separators, as passed to `DatePicker`'s `dateFormat` — when the helper
314
+ * cannot read it from the input's placeholder (a custom `placeholder`).
315
+ * Default: the placeholder, else `"MM/DD/YYYY"`.
316
+ */
317
+ dateFormat?: string;
318
+ }
319
+ /**
320
+ * Set a `DatePicker` to `iso` (`YYYY-MM-DD`) by typing the date into its text
321
+ * input in the picker's own display pattern. `""` clears it.
322
+ *
323
+ * ```ts
324
+ * await pickDate(screen, "Due date", "2025-03-14"); // input named by its Field label
325
+ * await pickDate(screen.getByRole("textbox", { name: "Due date" }), "2025-03-14");
326
+ * await pickDate(screen, "Fällig am", "2025-03-14", { dateFormat: "DD.MM.YYYY" });
327
+ * ```
328
+ *
329
+ * The pattern is read from the input's placeholder, which `DatePicker` sets to
330
+ * its pattern (`MM/DD/YYYY` for `en-US`, `DD.MM.YYYY` for `de-DE`, a custom
331
+ * `dateFormat`); pass `dateFormat` when the picker has its own placeholder. A
332
+ * year picker (`picker="year"`, placeholder `YYYY`) receives the year. The
333
+ * helper checks that the input shows the typed date afterwards, so a picker
334
+ * that masked it differently — a pattern mismatch — throws instead of
335
+ * submitting a wrong value. `onValueChange` / `onChange` fire and a `name`d
336
+ * picker updates its form value, as for a real user.
337
+ *
338
+ * @experimental
339
+ */
340
+ declare function pickDate(input: HTMLElement, iso: string, options?: PickDateOptions): Promise<void>;
341
+ /**
342
+ * Set the `DatePicker` whose text input has the accessible name `label`
343
+ * (found through `queries`, for example Testing Library's `screen`) to `iso`.
344
+ *
345
+ * @experimental
346
+ */
347
+ declare function pickDate(queries: RoleQueries, label: TextMatch, iso: string, options?: PickDateOptions): Promise<void>;
348
+
349
+ export { type AssureJestOptions, type DomPolyfill, type DomPolyfillOptions, type FillFieldOptions, type InteractionOptions, type JestConfigInput, type JestConfigLike, type MenuHandle, type PickDateOptions, type RecordedToast, type RecordedToastVariant, type RoleQueries, type TextMatch, type ToastRecorder, type ToastRecorderOptions, type WithAssureDesignSystem, createToastRecorder, fillField, installDomPolyfills, openMenu, pickDate, selectOption, withAssureDesignSystem };
@@ -182,11 +182,22 @@ interface RoleQueries {
182
182
  }
183
183
  /** Text matcher: exact text (after trimming and collapsing whitespace) or a pattern. @experimental */
184
184
  type TextMatch = string | RegExp;
185
- /** Options for `selectOption` and `openMenu`. @experimental */
185
+ /** Options for the interaction helpers. @experimental */
186
186
  interface InteractionOptions {
187
187
  /** How many render passes to wait for the popup to appear or close. Default 20. */
188
188
  maxFlushes?: number;
189
189
  }
190
+
191
+ /**
192
+ * Interaction helpers that drive design-system components the way a keyboard
193
+ * user does, so tests do not encode Radix internals (ADR-014).
194
+ *
195
+ * They use only the DOM (roles, `aria-*`, keyboard events) and React's `act`,
196
+ * so they work with any renderer and any query library. This repository tests
197
+ * them against the real components; if a component's markup changes, those
198
+ * tests fail here rather than in a consumer.
199
+ */
200
+
190
201
  /**
191
202
  * Open a design-system `Select` and choose an option by its visible text.
192
203
  *
@@ -250,4 +261,89 @@ declare function openMenu(trigger: HTMLElement, options?: InteractionOptions): P
250
261
  */
251
262
  declare function openMenu(queries: RoleQueries, name: TextMatch, options?: InteractionOptions): Promise<MenuHandle>;
252
263
 
253
- export { type AssureJestOptions, type DomPolyfill, type DomPolyfillOptions, type InteractionOptions, type JestConfigInput, type JestConfigLike, type MenuHandle, type RecordedToast, type RecordedToastVariant, type RoleQueries, type TextMatch, type ToastRecorder, type ToastRecorderOptions, type WithAssureDesignSystem, createToastRecorder, installDomPolyfills, openMenu, selectOption, withAssureDesignSystem };
264
+ /**
265
+ * `fillField` — type a value into a design-system text control found by its
266
+ * Field label (ADR-014, W4-26).
267
+ */
268
+
269
+ /** Options for `fillField`. @experimental */
270
+ interface FillFieldOptions {
271
+ /**
272
+ * Blur the control after typing so controls that commit on blur
273
+ * (`NumberInput` formatting) settle. Default `true`; pass `false` to keep
274
+ * focus, for example before pressing Enter yourself.
275
+ */
276
+ commit?: boolean;
277
+ }
278
+ /**
279
+ * Type `value` into a text control — `Input`, `Textarea`, `SearchInput`,
280
+ * `NumberInput`, a `DatePicker`'s text input — replacing what it holds.
281
+ *
282
+ * ```ts
283
+ * await fillField(screen, "Full name", "Ada Lovelace"); // control named by its Field label
284
+ * await fillField(screen.getByRole("textbox", { name: "Notes" }), "Late filing");
285
+ * ```
286
+ *
287
+ * The control is found by its accessible name, which inside a `<Field>` is
288
+ * the `Label` text. React's `onChange` fires once with the new value; the
289
+ * control's own masking or formatting applies as it would for a user. Throws
290
+ * when the control is disabled or read-only, or when nothing is labelled
291
+ * `label`.
292
+ *
293
+ * @experimental
294
+ */
295
+ declare function fillField(control: HTMLElement, value: string, options?: FillFieldOptions): Promise<void>;
296
+ /**
297
+ * Type `value` into the text control whose accessible name is `label`, found
298
+ * through `queries` (for example Testing Library's `screen`).
299
+ *
300
+ * @experimental
301
+ */
302
+ declare function fillField(queries: RoleQueries, label: TextMatch, value: string, options?: FillFieldOptions): Promise<void>;
303
+
304
+ /**
305
+ * `pickDate` — set a design-system `DatePicker` to an ISO date by typing into
306
+ * its masked text input (ADR-014, W4-26).
307
+ */
308
+
309
+ /** Options for `pickDate`. @experimental */
310
+ interface PickDateOptions {
311
+ /**
312
+ * The picker's display pattern — `MM`, `DD` and `YYYY` around literal
313
+ * separators, as passed to `DatePicker`'s `dateFormat` — when the helper
314
+ * cannot read it from the input's placeholder (a custom `placeholder`).
315
+ * Default: the placeholder, else `"MM/DD/YYYY"`.
316
+ */
317
+ dateFormat?: string;
318
+ }
319
+ /**
320
+ * Set a `DatePicker` to `iso` (`YYYY-MM-DD`) by typing the date into its text
321
+ * input in the picker's own display pattern. `""` clears it.
322
+ *
323
+ * ```ts
324
+ * await pickDate(screen, "Due date", "2025-03-14"); // input named by its Field label
325
+ * await pickDate(screen.getByRole("textbox", { name: "Due date" }), "2025-03-14");
326
+ * await pickDate(screen, "Fällig am", "2025-03-14", { dateFormat: "DD.MM.YYYY" });
327
+ * ```
328
+ *
329
+ * The pattern is read from the input's placeholder, which `DatePicker` sets to
330
+ * its pattern (`MM/DD/YYYY` for `en-US`, `DD.MM.YYYY` for `de-DE`, a custom
331
+ * `dateFormat`); pass `dateFormat` when the picker has its own placeholder. A
332
+ * year picker (`picker="year"`, placeholder `YYYY`) receives the year. The
333
+ * helper checks that the input shows the typed date afterwards, so a picker
334
+ * that masked it differently — a pattern mismatch — throws instead of
335
+ * submitting a wrong value. `onValueChange` / `onChange` fire and a `name`d
336
+ * picker updates its form value, as for a real user.
337
+ *
338
+ * @experimental
339
+ */
340
+ declare function pickDate(input: HTMLElement, iso: string, options?: PickDateOptions): Promise<void>;
341
+ /**
342
+ * Set the `DatePicker` whose text input has the accessible name `label`
343
+ * (found through `queries`, for example Testing Library's `screen`) to `iso`.
344
+ *
345
+ * @experimental
346
+ */
347
+ declare function pickDate(queries: RoleQueries, label: TextMatch, iso: string, options?: PickDateOptions): Promise<void>;
348
+
349
+ export { type AssureJestOptions, type DomPolyfill, type DomPolyfillOptions, type FillFieldOptions, type InteractionOptions, type JestConfigInput, type JestConfigLike, type MenuHandle, type PickDateOptions, type RecordedToast, type RecordedToastVariant, type RoleQueries, type TextMatch, type ToastRecorder, type ToastRecorderOptions, type WithAssureDesignSystem, createToastRecorder, fillField, installDomPolyfills, openMenu, pickDate, selectOption, withAssureDesignSystem };