cssints 0.14.0 → 0.15.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 (2) hide show
  1. package/docs/API.md +43 -0
  2. package/package.json +2 -2
package/docs/API.md CHANGED
@@ -1291,6 +1291,49 @@ createConfirm(options?: { id?: string | undefined; onOpenChange?(open: boolean):
1291
1291
 
1292
1292
  `confirm()` resolves `true` on confirm and `false` on cancel, Escape and a backdrop click. A second request while one is pending rejects with `ConfirmPendingError` (it is not queued). Focus goes to the cancel button on open and back to the element that had it when `confirm()` was called. While it is open the dialog has a shell: Tab and Shift+Tab cycle inside it, `overflow: hidden` is set on `document.documentElement` (restored on close; counted, so nested confirms restore it once), and Escape is read from the document in the capture phase, so it works with the focus outside the dialog. Confirms from different `createConfirm` calls may be open together: the last opened gets Escape and Tab. The default labels stay English; a translating host passes `confirmLabel` and `cancelLabel` with each request.
1293
1293
 
1294
+ ### Headless: `createToast` and `createStatus` (`@cssints/headless`)
1295
+
1296
+ Two live regions with no APG pattern and no Quint spec: `createStatus` is one polite message whose text is replaced, `createToast` queues toasts whose countdown pauses. Their oracle is `packages/headless/test/toast.browser.test.tsx`.
1297
+
1298
+ ```ts skip
1299
+ createStatus(options?: { id?: string | undefined }): {
1300
+ message(): string | undefined;
1301
+ show(text: string): void; // replaces the text; nothing is queued
1302
+ clear(): void;
1303
+ statusProps: { id; role: "status"; "aria-live": "polite"; "aria-atomic": "true" };
1304
+ };
1305
+
1306
+ createToast(options?: {
1307
+ timeout?: number | undefined; // ms, 5000; 0 dismisses on the next tick
1308
+ max?: number | undefined; // visible toasts, 3; a new one past it drops the oldest
1309
+ pauseOnHover?: boolean | undefined; // true; focus always pauses
1310
+ labels?: { region?: string | undefined } | undefined; // "Notifications"
1311
+ onDismiss?(id: string): void; // every departure: timeout, dismiss, dismissAll, ceiling
1312
+ }): {
1313
+ toasts(): readonly ToastItem[]; // oldest first
1314
+ show(input: {
1315
+ title: string;
1316
+ description?: string | undefined;
1317
+ tone?: "info" | "success" | "error" | undefined; // "info"
1318
+ }): string; // the id
1319
+ dismiss(id: string): void;
1320
+ dismissAll(): void;
1321
+ regionProps: { role: "region"; "aria-label": string }; // the F6 target
1322
+ toastProps(id: string): {
1323
+ role: "alert" | "status"; // "alert" for tone "error", "status" otherwise
1324
+ "aria-atomic": "true";
1325
+ onMouseEnter(): void;
1326
+ onMouseLeave(): void;
1327
+ onFocusIn(): void;
1328
+ onFocusOut(): void;
1329
+ };
1330
+ };
1331
+ ```
1332
+
1333
+ `createStatus` renders one `role=status` region that the host keeps mounted; `show()` replaces its text, so a burst of "Link copied" / "Saved" reads as the last state, not as a queue. A status is never for errors: an error is `createAlert` (`role=alert`), and a titled message with a timeout is a toast.
1334
+
1335
+ A toast is its own live region inside the `role=region` landmark: `role=alert` for `tone: "error"` (assertive) and `role=status` otherwise (polite). The host renders the `title` and `description` handed to `show` inside that element, so the toast's own text is the announcement — `aria-atomic` reads all of it, a close button's label included, so keep a toast to its title and description and give a close button no more than an icon with a label. The role sits on the toast rather than on one container per tone: the container is the `region` landmark, so it cannot also be the live region, and a role per toast needs no second region from the host. One timer per toast honours `timeout`; hover pauses it when `pauseOnHover` is true and focus always pauses, and both resume with the time that was left rather than restarting the deadline. `max` drops the oldest toast past the ceiling, and `onDismiss` fires for every toast that leaves. F6 into the region is the host's handler (hotkeys are a separate bean, cssints-h0d5): give `regionProps` an element with `tabindex="-1"` and focus it, remembering where focus was.
1336
+
1294
1337
  ### Headless: `createPopover` (`@cssints/headless`)
1295
1338
 
1296
1339
  **No APG pattern exists** for a popover (APG has Dialog, which is modal), so it has no Quint spec and no ported scenarios: `packages/headless/test/popover.browser.test.tsx` is its oracle. The behaviour is copied from Base UI's Popover (a trigger click toggles, Enter / Space / ArrowDown open, an outside `pointerdown` light-dismisses without stealing focus) and React Aria's `usePopover` (focus into the content on open and back to the trigger on close, `role="dialog"` without `aria-modal`, a placement that flips at the viewport edge).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cssints",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "description": "CSS in TypeScript: typed css.* calls compiled to atomic CSS at build time (a Vite plugin)",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -72,7 +72,7 @@
72
72
  }
73
73
  },
74
74
  "dependencies": {
75
- "@cssints/css-grammar": "^0.14.0",
75
+ "@cssints/css-grammar": "^0.15.0",
76
76
  "culori": "^4.0.2",
77
77
  "effect": "4.0.6",
78
78
  "happy-dom": "^20.14.5",