cssints 0.15.0 → 0.17.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.
- package/docs/API.md +89 -0
- package/package.json +2 -2
package/docs/API.md
CHANGED
|
@@ -1377,6 +1377,95 @@ createPopover(options?: {
|
|
|
1377
1377
|
|
|
1378
1378
|
`attach` is the DOM contract: on open it measures and places the content (and again on resize, scroll and a `ResizeObserver`), focuses the content's first focusable element (else the content, which is `tabindex="-1"`), and on close returns the focus to the trigger unless the close came from an outside `pointerdown` — that one leaves the focus where the page put it. The page behind stays interactive: there is no `inert`, no scroll lock and no Tab cycle, and the content is not a menu (no roving focus: `createMenu` in a popover owns its items). The rects and the viewport passed to `position` must share a space; `attach` measures in client space and writes page space for `position: absolute`.
|
|
1379
1379
|
|
|
1380
|
+
### Headless: `createDrawer` (`@cssints/headless`)
|
|
1381
|
+
|
|
1382
|
+
The drawer / sheet shell: an edge-anchored panel over Dialog (Modal), with the focus, Escape, scroll-lock and backdrop behaviour of `createConfirm`. **No APG pattern exists for a drawer or a bottom sheet**; [packages/headless/docs/bottom-sheet.md](../packages/headless/docs/bottom-sheet.md) settles that the library composes instead of adding a sheet primitive (the semantics are Dialog, the position and the snap points are native CSS), and this is the shell that decision left to exist. Its oracle is `packages/headless/test/drawer.browser.test.tsx`.
|
|
1383
|
+
|
|
1384
|
+
```ts skip
|
|
1385
|
+
createDrawer(options?: {
|
|
1386
|
+
id?: string | undefined;
|
|
1387
|
+
open?: boolean | undefined; // controlled: the host owns the state
|
|
1388
|
+
defaultOpen?: boolean | undefined;
|
|
1389
|
+
side?: "left" | "right" | "bottom" | undefined; // "right"; presentation only
|
|
1390
|
+
modal?: boolean | undefined; // true; read at every attribute read (the breakpoint switch)
|
|
1391
|
+
breakpoint?: number | undefined; // px; documented only: the host watches it and passes side / modal
|
|
1392
|
+
dismissable?: boolean | undefined; // true; a backdrop click or an outside pointerdown
|
|
1393
|
+
labelledby?: string | undefined; // the panel needs a name
|
|
1394
|
+
onOpenChange?(open: boolean): void;
|
|
1395
|
+
}): {
|
|
1396
|
+
open(): boolean;
|
|
1397
|
+
side(): "left" | "right" | "bottom";
|
|
1398
|
+
triggerProps: {
|
|
1399
|
+
id;
|
|
1400
|
+
"aria-haspopup": "dialog";
|
|
1401
|
+
"aria-expanded": "true" | "false";
|
|
1402
|
+
"aria-controls": string;
|
|
1403
|
+
onClick(): void; // toggles
|
|
1404
|
+
onKeyDown(event: KeyboardEvent): void; // Enter / Space open
|
|
1405
|
+
};
|
|
1406
|
+
backdropProps: { id; hidden: boolean; onClick(): void }; // always hidden when closed or not modal
|
|
1407
|
+
contentProps: {
|
|
1408
|
+
id;
|
|
1409
|
+
role: "dialog";
|
|
1410
|
+
"aria-modal": "true" | undefined; // by modal, and undefined while closed
|
|
1411
|
+
"aria-labelledby": string | undefined;
|
|
1412
|
+
tabindex: -1;
|
|
1413
|
+
hidden: boolean;
|
|
1414
|
+
onKeyDown(event: KeyboardEvent): void; // Escape closes
|
|
1415
|
+
};
|
|
1416
|
+
handleProps: { "aria-hidden": "true" }; // a bottom sheet's grab handle; decorative
|
|
1417
|
+
};
|
|
1418
|
+
```
|
|
1419
|
+
|
|
1420
|
+
`side` and `modal` are the whole presentation API: a sheet is `{ side: "bottom", modal: true }`, a side rail is `{ side: "right" }`, and a non-modal rail is `{ side: "right", modal: false }`. `modal` is read at attribute-read time, so a host that watches the breakpoint may flip it while the drawer is open and the backdrop, `aria-modal` and the lock follow; `breakpoint` itself is not read, because the primitive has no viewport and the host owns the `matchMedia`.
|
|
1421
|
+
|
|
1422
|
+
A modal drawer is `aria-modal="true"` with a backdrop, focus moves to the panel's first focusable on open (else the panel, `tabindex="-1"`) and back to the trigger on close, Tab and Shift+Tab cycle inside it, `overflow: hidden` is set on `document.documentElement` (counted, and shared with `createConfirm`, so a confirm over a drawer nests), and Escape is read from the document in the capture phase, so it closes wherever the focus is. A non-modal drawer keeps the page's scroll, focus and clicks: no `aria-modal`, no backdrop, no lock, no Tab trap; an outside `pointerdown` still dismisses it when `dismissable`, and Escape still closes it. `dismissable: false` pins the drawer against the backdrop and the outside pointerdown only.
|
|
1423
|
+
|
|
1424
|
+
**No drag-to-dismiss and no `attach`.** A drag would be a JS re-implementation of the native scroll snap the host writes (see bottom-sheet.md), so `handleProps` is decoration with no pointer handlers, and the ways out are the backdrop, Escape, a close control in the panel and the trigger. The panel is edge-anchored, so nothing is measured: focus and the Tab cycle find it through `contentProps.id`.
|
|
1425
|
+
|
|
1426
|
+
### Headless: `createTable` (`@cssints/headless`)
|
|
1427
|
+
|
|
1428
|
+
A data table on a **native `<table>`**: sortable column headers and row selection, as state and attribute objects. It has no spec of its own; it composes the two state models the package already has (`grid-sort.qnt`, `grid-selection.qnt`) without the grid's navigation, so `packages/headless/test/table.browser.test.tsx` is its oracle. The host renders `<table>`, `<caption>`, `<thead>`, `<tbody>`, `<th scope="col">` and `<td>`: the primitive adds no `role`, only what the elements cannot say themselves.
|
|
1429
|
+
|
|
1430
|
+
```ts skip
|
|
1431
|
+
createTable(options: {
|
|
1432
|
+
columns: ReadonlyArray<{ key: string; sortable?: boolean; numeric?: boolean; abbr?: string | undefined }>; // sortable defaults true
|
|
1433
|
+
rows: ReadonlyArray<string>; // row ids in render order; the host sorts its own data
|
|
1434
|
+
selection?: "none" | "single" | "multiple" | undefined; // "none"
|
|
1435
|
+
sortCycle?: "tri-state" | "toggle" | undefined; // "tri-state": ascending -> descending -> unsorted
|
|
1436
|
+
sort?: TableSort | null | undefined; // controlled; null = unsorted, undefined = uncontrolled
|
|
1437
|
+
defaultSort?: TableSort | undefined;
|
|
1438
|
+
onSortChange?(sort: TableSort | undefined): void;
|
|
1439
|
+
selected?: ReadonlyArray<string> | undefined; // controlled
|
|
1440
|
+
defaultSelected?: ReadonlyArray<string> | undefined;
|
|
1441
|
+
onSelectionChange?(selected: ReadonlyArray<string>): void;
|
|
1442
|
+
rowCount?: number | undefined; // only when the host windows its rows
|
|
1443
|
+
columnCount?: number | undefined; // only when it windows its columns
|
|
1444
|
+
labels?: { table?: string | undefined; selectAll?: string | undefined; selectRow?: ((id: string) => string) | undefined } | undefined;
|
|
1445
|
+
id?: string | undefined;
|
|
1446
|
+
}): {
|
|
1447
|
+
selectionMode(): "none" | "single" | "multiple";
|
|
1448
|
+
sort(): TableSort | undefined;
|
|
1449
|
+
selected(): ReadonlyArray<string>;
|
|
1450
|
+
isSelected(id: string): boolean;
|
|
1451
|
+
tableProps: { "aria-label"; "aria-rowcount"; "aria-colcount" }; // the counts only while windowed
|
|
1452
|
+
captionProps: { id };
|
|
1453
|
+
headerCellProps(key: string): { scope: "col"; "aria-sort": "ascending" | "descending" | undefined; abbr };
|
|
1454
|
+
headerButtonProps(key: string): { type: "button"; onClick } | undefined; // undefined when not sortable
|
|
1455
|
+
rowProps(id: string): { "aria-selected"; onClick };
|
|
1456
|
+
cellProps(rowId: string, key: string): { "aria-colindex" }; // only while windowed
|
|
1457
|
+
selectAllProps: { role: "checkbox"; "aria-checked": "true" | "false" | "mixed"; "aria-label"; tabindex: 0; onClick; onKeyDown } | undefined;
|
|
1458
|
+
rowCheckboxProps(id: string): { role: "checkbox"; "aria-checked"; "aria-label"; tabindex: 0; onClick; onKeyDown } | undefined;
|
|
1459
|
+
toggleAll(): void;
|
|
1460
|
+
toggleRow(id: string): void;
|
|
1461
|
+
};
|
|
1462
|
+
```
|
|
1463
|
+
|
|
1464
|
+
`aria-sort` is carried by the sorted column only; a sortable header that is not sorted has none (ARIA's "not sorted" default), and a `sortable: false` column gets no button from `headerButtonProps` at all, so it is not a tab stop. The header button cycles `ascending` -> `descending` -> unsorted by default (so the table can be put back in its own order) and `ascending` <-> `descending` with `sortCycle: "toggle"` (what the APG grid example does); a new column always starts `ascending`, and only one is sorted. Sorting is the host's data: the primitive carries the descriptor and announces it, it never reorders a row.
|
|
1465
|
+
|
|
1466
|
+
Selection is over ids, so it survives the host's re-sort. `rowProps` carries `aria-selected` and a click that selects; `toggleRow` / `toggleAll` are the programmatic path, and the row's checkbox stops its click from reaching the row so the two controls never toggle the same row twice. `selectAllProps` and `rowCheckboxProps` are `undefined` unless `selection` is `"multiple"` (one control over one selection is meaningless), and the select-all is a `role="checkbox"` rather than a native `input` because the mixed state a screen reader must hear is `aria-checked="mixed"`, not the DOM-only `indeterminate` property. It toggles the rows the table renders: a windowed table toggles its window and keeps the rest of the selection. A table that renders all of its rows and columns carries no `aria-rowcount` / `aria-colcount` (the markup already says it); pass them, and every cell gets its `aria-colindex`, only when the host windows. `numeric` is a layout hint for the host's skin: HTML has no attribute for it.
|
|
1467
|
+
|
|
1468
|
+
f812d87e (headless: a data table with sortable headers and row selection)
|
|
1380
1469
|
### Publishing a library (decided 2026-10-02, cssints-0l3f)
|
|
1381
1470
|
|
|
1382
1471
|
A package written on cssints ships JavaScript that still has the attribute, and its `.d.ts`. The app that installs it evaluates the package with its own engine: one sheet, the app's browserslist and themes, and a token, a kernel or an `@property` of the package is made once however many modules use it. Nothing is declared: the engine recognises a file by `with { type: "cssints" }`, the same marker as in app code.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cssints",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.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.
|
|
75
|
+
"@cssints/css-grammar": "^0.17.0",
|
|
76
76
|
"culori": "^4.0.2",
|
|
77
77
|
"effect": "4.0.6",
|
|
78
78
|
"happy-dom": "^20.14.5",
|