cssints 0.24.0 → 0.26.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 +124 -25
- package/package.json +2 -2
package/docs/API.md
CHANGED
|
@@ -1223,27 +1223,27 @@ export const typo = mdi("clos"); // error: not an icon of mdi
|
|
|
1223
1223
|
|
|
1224
1224
|
More plugins on `scope()`, each a package of its own with its rules and limits in its README. Import them with the attribute, like `cssints`.
|
|
1225
1225
|
|
|
1226
|
-
| Package | Members
|
|
1227
|
-
| ----------------------------------------------------------- |
|
|
1228
|
-
| [`@cssints/fluid`](../packages/fluid/README.md) | `fluid(min, max, { from?, to?, zoom? })`, `fluid.container(...)`
|
|
1229
|
-
| [`@cssints/layout`](../packages/layout/README.md) | `stack()`, `cluster()`, `sidebar()`, `switcher()`, `cover()`, `autoGrid()`
|
|
1230
|
-
| [`@cssints/fonts`](../packages/fonts/README.md) | `font(family, { provider, ... })` → `.family()`, `.trim()`
|
|
1231
|
-
| [`@cssints/reset`](../packages/reset/README.md) | `reset({ media?, prose? })`
|
|
1232
|
-
| [`@cssints/tokens`](../packages/tokens/README.md) | `/open-props`, `/radix`, `/flexoki`, `/reasonable-colors`, `/madoromi`
|
|
1233
|
-
| [`@cssints/gradient`](../packages/gradient/README.md) | `gradient(kind, direction, from, to, easing?, stops?)`
|
|
1234
|
-
| [`@cssints/gradient-2d`](../packages/gradient-2d/README.md) | `meshGradient(mesh)`, `freeformGradient(points)`
|
|
1235
|
-
| [`@cssints/color-names`](../packages/color-names/README.md) | `named(name)`, `colorNames.<sector>(name)`, `/nearest`
|
|
1236
|
-
| [`@cssints/motion`](../packages/motion/README.md) | `motion().preset().duration().delay().easing()`
|
|
1237
|
-
| [`@cssints/squircle`](../packages/squircle/README.md) | `squircle().radius()`
|
|
1238
|
-
| [`@cssints/reveal`](../packages/reveal/README.md) | `reveal().preset().range().duration().easing()`, `appear().preset().range().duration().easing().inOut()`
|
|
1239
|
-
| [`@cssints/stuck`](../packages/stuck/README.md) | `sticky().top()`, `stuck(styles)`, `stuck("bottom")(styles)`
|
|
1240
|
-
| [`@cssints/balance`](../packages/balance/README.md) | `balance()`, `pretty()`
|
|
1241
|
-
| [`@cssints/masonry`](../packages/masonry/README.md) | `masonry().columns().gap()`
|
|
1242
|
-
| [`@cssints/placeholder`](../packages/placeholder/README.md) | `placeholder(path, { mode? })`
|
|
1243
|
-
| [`@cssints/ramp`](../packages/ramp/README.md) | `ramp(base, { mode, steps \| ratios, background })`, `overrides(ramp)`
|
|
1244
|
-
| [`@cssints/rybitten`](../packages/rybitten/README.md) | `color(hue, { cube })`, `wheel(steps, { cube })`, `shades(hue, lightness)`, `overrides(group)`
|
|
1245
|
-
| [`@cssints/skeleton`](../packages/skeleton/README.md) | `skeleton().radius().color().highlight()`
|
|
1246
|
-
| [`@cssints/reactive`](../packages/reactive/README.md) | `state()`, `toggle()`, `set()`, `value()`, `checked()`, `persist()`, `on()`, `bind()`, `when()`, `inside()` | State as custom properties, events as data on the element: writes only in events, conditions and DOM output in plain CSS, wired by a 1.
|
|
1226
|
+
| Package | Members | What it does |
|
|
1227
|
+
| ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1228
|
+
| [`@cssints/fluid`](../packages/fluid/README.md) | `fluid(min, max, { from?, to?, zoom? })`, `fluid.container(...)` | A `clamp()` that grows between two widths, computed at build time; a range that fails WCAG 1.4.4 is a build error |
|
|
1229
|
+
| [`@cssints/layout`](../packages/layout/README.md) | `stack()`, `cluster()`, `sidebar()`, `switcher()`, `cover()`, `autoGrid()` | Every Layout primitives as chain members |
|
|
1230
|
+
| [`@cssints/fonts`](../packages/fonts/README.md) | `font(family, { provider, ... })` → `.family()`, `.trim()` | `@font-face` and a metric-matched fallback face; cap-height trimming by `text-box`, else capsize's margins |
|
|
1231
|
+
| [`@cssints/reset`](../packages/reset/README.md) | `reset({ media?, prose? })` | A modern reset in a top-level `@layer reset` declared before the engine's, below every style; `@cssints/reset/<baseline group>` keeps only the declarations of that group |
|
|
1232
|
+
| [`@cssints/tokens`](../packages/tokens/README.md) | `/open-props`, `/radix`, `/flexoki`, `/reasonable-colors`, `/madoromi` | DTCG data for `createTokens` |
|
|
1233
|
+
| [`@cssints/gradient`](../packages/gradient/README.md) | `gradient(kind, direction, from, to, easing?, stops?)` | Eased colour stops mixed in oklab at build time |
|
|
1234
|
+
| [`@cssints/gradient-2d`](../packages/gradient-2d/README.md) | `meshGradient(mesh)`, `freeformGradient(points)` | `mesh-gradient()` / `freeform-gradient()` where the browser has them, else a runtime's canvas (this package's algorithms; lines and shapes not yet) |
|
|
1235
|
+
| [`@cssints/color-names`](../packages/color-names/README.md) | `named(name)`, `colorNames.<sector>(name)`, `/nearest` | The Color Name List (31,939 names) as hex colours; the ten sector members keep an editor's completion short, `nearest()` is the build-time reverse lookup |
|
|
1236
|
+
| [`@cssints/motion`](../packages/motion/README.md) | `motion().preset().duration().delay().easing()` | Animation presets over `keyframes()`, with reduced motion |
|
|
1237
|
+
| [`@cssints/squircle`](../packages/squircle/README.md) | `squircle().radius()` | `corner-shape: squircle`, else a CSS `mask` of the same shape |
|
|
1238
|
+
| [`@cssints/reveal`](../packages/reveal/README.md) | `reveal().preset().range().duration().easing()`, `appear().preset().range().duration().easing().inOut()` | motion's entrance presets on `animation-timeline: view()` (reveal) or on `timeline-trigger` + `animation-trigger` (appear); for other targets a script plays it once on entry (delivered by the runtime) |
|
|
1239
|
+
| [`@cssints/stuck`](../packages/stuck/README.md) | `sticky().top()`, `stuck(styles)`, `stuck("bottom")(styles)` | Styles inside a sticky element while it is stuck, by `@container scroll-state()`; for other targets an `IntersectionObserver` (delivered by the runtime) |
|
|
1240
|
+
| [`@cssints/balance`](../packages/balance/README.md) | `balance()`, `pretty()` | `text-wrap: balance` / `pretty`; for targets without balance a script balances the lines (delivered by the runtime) |
|
|
1241
|
+
| [`@cssints/masonry`](../packages/masonry/README.md) | `masonry().columns().gap()` | `display: grid-lanes`, else CSS `columns` (top to bottom per column) |
|
|
1242
|
+
| [`@cssints/placeholder`](../packages/placeholder/README.md) | `placeholder(path, { mode? })` | An `<image>` from a JPEG or PNG read at build time: a gradient of its colours laid out as the image, or a tiny thumbhash PNG |
|
|
1243
|
+
| [`@cssints/ramp`](../packages/ramp/README.md) | `ramp(base, { mode, steps \| ratios, background })`, `overrides(ramp)` | An OKLCH palette as DTCG data for `createTokens`; the contrast mode meets each ratio against its background |
|
|
1244
|
+
| [`@cssints/rybitten`](../packages/rybitten/README.md) | `color(hue, { cube })`, `wheel(steps, { cube })`, `shades(hue, lightness)`, `overrides(group)` | RYBitten's red-yellow-blue colours (36 historical cubes) as DTCG data for `createTokens`: a hue, a wheel of hues, a hue at several lightnesses |
|
|
1245
|
+
| [`@cssints/skeleton`](../packages/skeleton/README.md) | `skeleton().radius().color().highlight()` | Loading placeholders whose shimmer is in phase across the page (one clock on `:root`), static under reduced motion |
|
|
1246
|
+
| [`@cssints/reactive`](../packages/reactive/README.md) | `state()`, `toggle()`, `set()`, `value()`, `checked()`, `persist()`, `equals()`, `either()`, `on()`, `emit()`, `bind()`, `when()`, `inside()` | State as custom properties, events as data on the element: writes only in events, conditions and DOM output in plain CSS, wired by a 1.4 kB runtime shipped to every target; `value()`/`checked()` take the field's value, calls of `on()`, `bind()` or `state()` in one `cn()` append, `persist(value, key)` keeps a state across a reload, and `bind()` takes a source, a comparison (`equals()`), a choice of two literals (`either()`) or a literal — the one expression the runtime evaluates, because an attribute on the element cannot be computed in CSS (2026-10-31, cssints-ac3o). `emit(name)` is the one hand-off to JavaScript: `on({ submit: emit("save") })` writes no state and dispatches a bubbling `reactive:save` from the element the native event fired on, with `{ event, owner }` in its detail, which `listen(target, name, handler)` from `@cssints/reactive/runtime` hears and whose disposer it returns (2026-10-31, cssints-4s8d) |
|
|
1247
1247
|
|
|
1248
1248
|
### Headless: `createField` and `createConfirm` (`@cssints/headless`)
|
|
1249
1249
|
|
|
@@ -1478,7 +1478,7 @@ Close and rename are an **extension** (bean `cssints-gdv2`, the docs playground'
|
|
|
1478
1478
|
|
|
1479
1479
|
```ts skip
|
|
1480
1480
|
createTabs(options?: {
|
|
1481
|
-
...; // the plain tabs: items, value, defaultSelected, orientation, activation, disabled, focusablePanels, id, tabId, panelId, onValueChange
|
|
1481
|
+
...; // the plain tabs: items, value, defaultSelected, orientation, dir, activation, disabled, focusablePanels, id, tabId, panelId, onValueChange
|
|
1482
1482
|
closable?: boolean; // false: closeProps and the Delete key; the host removes from onClose
|
|
1483
1483
|
renamable?: boolean; // false: a double click edits the label inline; the host renames from onRename
|
|
1484
1484
|
label?(index: number): string | undefined; // the tab's name: the buttons' labels and the rename draft
|
|
@@ -1503,6 +1503,7 @@ createTabs(options?: {
|
|
|
1503
1503
|
- **Close.** `closeProps(index)` is the tab's close button: `aria-label="Close <label>"` and `tabindex=-1`, because a tablist's tab stops are its tabs; the keyboard path is Delete on the focused tab. Focus after a close goes to the tab that slid into the closed one's place, or to the previous tab when the last one closed: `focusAfterClose(index)` names that index in the shortened list (call it before the host removes the tab). Closing the last remaining tab is refused, as the spec refuses an empty tablist; a closed tab that was selected asks for the tab that takes the focus.
|
|
1504
1504
|
- **Rename.** A double click begins; the tab renders `inputProps` instead of its label. Enter commits, Escape reverts, blur commits, and a label that trims to nothing cancels. While editing, the input owns the keys: the tablist's roving does not move. `begin(index)` is the programmatic start, for a tab the host has just added (append it, then `focusTab(index)` and `begin(index)`).
|
|
1505
1505
|
- **Ids.** `tabId(index)` and `panelId(index)` replace the generated `${id}-tab-<index>` / `${id}-tabpanel-<index>`, and `aria-controls` / `aria-labelledby` follow them. Use them when the host's markup or a deep link already names a panel (`#logs`): the alternative is a second id on the same element, which no attribute can carry.
|
|
1506
|
+
- **Direction.** `dir: "rtl"` swaps ArrowLeft / ArrowRight on a horizontal tablist (a vertical one keeps Up / Down); the host still sets `dir` on its element for the visual order. The model `tabs.qnt` is ltr only, so the ported `direction` scenarios stay skipped; `packages/headless/test/tabs-requirements.browser.test.tsx` holds the swap.
|
|
1506
1507
|
- **The host's list.** `items` is read from the option on every read, so the bounds, the roving tab stop and `firstEnabled` follow a host that shrinks its list. `label` is read lazily too, so a renamed tab renames its buttons with it.
|
|
1507
1508
|
- The spec models a `deletable` flavour (Delete, focus to the next tab, the last refused) but neither a rename nor the callbacks, so the browser test, not the model, holds the extension.
|
|
1508
1509
|
|
|
@@ -1592,7 +1593,105 @@ createCommandPalette(options: {
|
|
|
1592
1593
|
};
|
|
1593
1594
|
```
|
|
1594
1595
|
|
|
1595
|
-
Focus stays in the field and `aria-activedescendant` names the highlighted option: ↓ on the last and ↑ on the first return the highlight to the field, and ↓/↑ from there re-enter at the first/last row. Home/End move the caret while the field has text and are list keys only while it is empty. Enter runs the highlight, Escape closes through the dialog, Tab is not intercepted. Filtering is a plain case-insensitive substring match over `label` and `keywords` — no fuzzy scoring, because a palette is short and ranking (recents, scopes) is the host's policy. `results()` keeps host order; `group` is what the host groups by; `emptyProps.hidden` and `labels.empty` are the empty state; the count is written to `statusProps` once per query, not on every arrow. The query and highlight reset on close, focus returns to the element that had it, and `runActive()` closes before `run()` so a command may open its own UI. Host's: rendering, grouping and CSS; async commands and their errors; every visible string via `labels`; any close other than Escape or a command. The
|
|
1596
|
+
Focus stays in the field and `aria-activedescendant` names the highlighted option: ↓ on the last and ↑ on the first return the highlight to the field, and ↓/↑ from there re-enter at the first/last row. Home/End move the caret while the field has text and are list keys only while it is empty. Enter runs the highlight, Escape closes through the dialog, Tab is not intercepted. Filtering is a plain case-insensitive substring match over `label` and `keywords` — no fuzzy scoring, because a palette is short and ranking (recents, scopes) is the host's policy. `results()` keeps host order; `group` is what the host groups by; `emptyProps.hidden` and `labels.empty` are the empty state; the count is written to `statusProps` once per query, not on every arrow. The query and highlight reset on close, focus returns to the element that had it, and `runActive()` closes before `run()` so a command may open its own UI. Host's: rendering, grouping and CSS; async commands and their errors; every visible string via `labels`; any close other than Escape or a command. The date picker is a later composition on the same research; the filterable menu is the next section.
|
|
1597
|
+
|
|
1598
|
+
### Headless: `createFilterableMenu` (`@cssints/headless`)
|
|
1599
|
+
|
|
1600
|
+
The second **composition**, not a new primitive and not a new spec: `createDialogModal` + `createTextInput` + `createStatus` with one filtered `role=menu`, per Base UI's [Building a filterable menu that works for everyone](https://base-ui.com/blog/building-a-filterable-menu) (settled in `docs/research/compositions-2026-10.md`). Its oracle is `packages/headless/test/filterable-menu.browser.test.tsx`. The structure is a role promise: choosing a row runs a command, so the popup is a `role=dialog` whose trigger says `aria-haspopup="dialog"`, the field is `input[type=text][role=searchbox]` with no `aria-autocomplete`, `aria-haspopup` or `aria-expanded`, and the list is a `role=menu`.
|
|
1601
|
+
|
|
1602
|
+
```ts skip
|
|
1603
|
+
createFilterableMenu(options: {
|
|
1604
|
+
items:
|
|
1605
|
+
| ReadonlyArray<{
|
|
1606
|
+
id: string;
|
|
1607
|
+
label: string;
|
|
1608
|
+
group?: string | undefined; // the heading the host renders
|
|
1609
|
+
submenu?: ReadonlyArray<{ id: string; label: string; run?(): void | Promise<void> }>;
|
|
1610
|
+
run(): void | Promise<void>;
|
|
1611
|
+
}>
|
|
1612
|
+
| (() => ReadonlyArray<FilterableMenuItem>);
|
|
1613
|
+
open?: boolean | undefined; // controlled: the host owns the open state
|
|
1614
|
+
onOpenChange?(open: boolean): void;
|
|
1615
|
+
labels?: Partial<{
|
|
1616
|
+
placeholder: string; // "Filter actions"
|
|
1617
|
+
empty: string; // "No actions found"; the host renders it
|
|
1618
|
+
count(count: number): string; // "5 actions"
|
|
1619
|
+
clear: string; // "Clear"; the clear button's name
|
|
1620
|
+
}> | undefined;
|
|
1621
|
+
id?: string | undefined;
|
|
1622
|
+
}): {
|
|
1623
|
+
open(): boolean;
|
|
1624
|
+
openMenu(): void;
|
|
1625
|
+
closeMenu(): void; // closes every level; Escape pops one first
|
|
1626
|
+
query(): string;
|
|
1627
|
+
setQuery(text: string): void;
|
|
1628
|
+
active(): string | null; // the highlighted row, or null while the field holds it
|
|
1629
|
+
setActive(id: string | null): void;
|
|
1630
|
+
results(submenuId?: string): ReadonlyArray<{
|
|
1631
|
+
id: string;
|
|
1632
|
+
label: string;
|
|
1633
|
+
group: string | undefined;
|
|
1634
|
+
hasSubmenu: boolean;
|
|
1635
|
+
}>;
|
|
1636
|
+
labels: FilterableMenuLabels;
|
|
1637
|
+
status(): string | undefined;
|
|
1638
|
+
// triggerProps (aria-haspopup="dialog", needs nothing from the field),
|
|
1639
|
+
// inputProps, menuProps, groupProps(name), groupLabelProps(name),
|
|
1640
|
+
// itemProps(id), submenuTriggerProps(id), submenuProps(id), emptyProps
|
|
1641
|
+
// ({ hidden, text }), statusProps, clearProps (tabindex -1), dialogProps,
|
|
1642
|
+
// backdropProps and titleProps: the attribute objects.
|
|
1643
|
+
};
|
|
1644
|
+
```
|
|
1645
|
+
|
|
1646
|
+
Focus stays in the field and `aria-activedescendant` names the highlighted row; `data-highlighted` sits on the field while the highlight is there. ↓ on the last row and ↑ on the first return it to the field, and ↓/↑ from the field enter at the first/last row. Home/End are menu keys only while the query is empty **and** a row is highlighted; otherwise they move the caret. Enter runs the row or enters its submenu. **Tab closes and leaves** (not prevented: the dialog is hidden on the next task, after focus moved, so the menu never traps; a native `showModal()` wrapper keeps its own trap and moves first). **Escape pops one submenu level** and closes at the top level; Backspace on an empty submenu field goes back too.
|
|
1647
|
+
|
|
1648
|
+
A submenu is a level: each level keeps its query and highlight and there is one field, so Enter on a trigger moves the field to that level at once, hover shows the level without moving focus, and coming back never loses the caret (`submenuProps` is the pointer-into-the-submenu hook; `results(triggerId)` gives a submenu's rows for a panel the host draws). Filtering is a case-insensitive substring on the label; typing inside an IME composition updates the text but filters only on `compositionend` (`event.isComposing` is honoured on the keyboard too). `groupProps` / `groupLabelProps` (`aria-hidden`: the group is already named through `aria-labelledby`) are the groups; `emptyProps.hidden` + `emptyProps.text` are the empty state; the count is one polite write per query change, not per arrow; `clearProps` has `tabindex: -1`, so Clear is a pointer affordance and not a tab stop. The host mounts the dialog and field only while `open()`; `triggerProps` names `aria-haspopup="dialog"` and needs nothing from the field, so the trigger works before it exists. Host's: rendering the groups and the submenu geometry, CSS, async commands and their errors and closing, and every visible string through `labels`.
|
|
1649
|
+
Focus stays in the field and `aria-activedescendant` names the highlighted option: ↓ on the last and ↑ on the first return the highlight to the field, and ↓/↑ from there re-enter at the first/last row. Home/End move the caret while the field has text and are list keys only while it is empty. Enter runs the highlight, Escape closes through the dialog, Tab is not intercepted. Filtering is a plain case-insensitive substring match over `label` and `keywords` — no fuzzy scoring, because a palette is short and ranking (recents, scopes) is the host's policy. `results()` keeps host order; `group` is what the host groups by; `emptyProps.hidden` and `labels.empty` are the empty state; the count is written to `statusProps` once per query, not on every arrow. The query and highlight reset on close, focus returns to the element that had it, and `runActive()` closes before `run()` so a command may open its own UI. Host's: rendering, grouping and CSS; async commands and their errors; every visible string via `labels`; any close other than Escape or a command. The filterable menu is a later composition on the same research.
|
|
1650
|
+
|
|
1651
|
+
### Headless: `createDatePicker` (`@cssints/headless`)
|
|
1652
|
+
|
|
1653
|
+
The second **composition**, not a new primitive and not a new spec: `createPopover` (a non-modal trigger and popup) + `createGrid` (a 6x7 month) + `createStatus` (the selected day). Its oracle is `packages/headless/test/date-picker.browser.test.tsx`. The popup is not modal on purpose — Tab leaves it, nothing behind it is `inert` — and the calendar maths is integer day numbers (`Date.UTC(y, m, d) / 86_400_000`), not a date library.
|
|
1654
|
+
|
|
1655
|
+
```ts skip
|
|
1656
|
+
createDatePicker(options: {
|
|
1657
|
+
value?: string | undefined; // controlled, ISO YYYY-MM-DD
|
|
1658
|
+
defaultValue?: string | undefined;
|
|
1659
|
+
onValueChange?(iso: string): void; // a request; a controlled host may ignore it
|
|
1660
|
+
min?: string | undefined; // inclusive
|
|
1661
|
+
max?: string | undefined;
|
|
1662
|
+
today?: string | undefined; // injected; the local date by default
|
|
1663
|
+
open?: boolean | undefined; // controlled
|
|
1664
|
+
onOpenChange?(open: boolean): void;
|
|
1665
|
+
labels?: Partial<{
|
|
1666
|
+
trigger: string; // "Choose date"
|
|
1667
|
+
previousMonth: string; // "Previous month"
|
|
1668
|
+
nextMonth: string; // "Next month"
|
|
1669
|
+
month(year: number, month: number): string; // 0-11; "October 2026"
|
|
1670
|
+
weekdays: ReadonlyArray<string>; // 7, Sunday first (getUTCDay order)
|
|
1671
|
+
day(iso: string): string; // the announcement; the ISO string by default
|
|
1672
|
+
}> | undefined;
|
|
1673
|
+
id?: string | undefined;
|
|
1674
|
+
firstDayOfWeek?: number | undefined; // 0 = Sunday .. 6 = Saturday; 1
|
|
1675
|
+
}): {
|
|
1676
|
+
value(): string | undefined;
|
|
1677
|
+
open(): boolean;
|
|
1678
|
+
openPicker(): void;
|
|
1679
|
+
closePicker(): void;
|
|
1680
|
+
month(): { year: number; month: number }; // 0-11
|
|
1681
|
+
setMonth(year: number, month: number): void; // the roving day at the value, today or the 1st
|
|
1682
|
+
today(): string;
|
|
1683
|
+
select(iso: string): void; // a no-op outside min/max; the host closes
|
|
1684
|
+
monthDays(): ReadonlyArray<ReadonlyArray<string>>; // 6 rows of 7 ISO dates, neighbours included
|
|
1685
|
+
labels: DatePickerLabels;
|
|
1686
|
+
status(): string | undefined;
|
|
1687
|
+
contentStyle(): { position: "absolute"; top: string; left: string }; // the popup's last measure, in page space
|
|
1688
|
+
// statusProps, triggerProps, popoverProps, headerProps, previousProps,
|
|
1689
|
+
// nextProps, gridProps, weekdayProps(index) and cellProps(iso): the attribute objects.
|
|
1690
|
+
attach(trigger: HTMLElement, content: HTMLElement): () => void; // the popover wiring; returns the teardown
|
|
1691
|
+
};
|
|
1692
|
+
```
|
|
1693
|
+
|
|
1694
|
+
Every date is an ISO `YYYY-MM-DD`, read as a UTC midnight; `month()` and `labels.month` use 0-11. `monthDays()` is **six weeks around the shown month**, so a row can start in the previous month and end in the next one: every cell is a real focusable date (a blank cannot hold the roving focus), and a cursor that leaves the month pages the grid to where it landed. `cellProps(iso)` marks the chosen day `aria-selected`, `today` `aria-current="date"`, a day outside `min`/`max` `aria-disabled`, and gives `tabindex=0` to the roving day only: the selected day, else today when it is in the shown month, else the first day the bounds allow. Keys on the focused day: ←/→ a day, ↑/↓ a week, Home/End the week's first/last day, PageUp/PageDown a month, Shift+PageUp/PageDown a year (the day clamps, 29 February included), Enter/Space selects and closes, Escape closes without changing the value, Tab leaves. The month arrows page the month and move the roving tab stop; the DOM focus stays on the arrow, so a host that wants the next key to land on a day moves it after the cells re-rendered (the kit's nav buttons do). `attach()` is the popover's light dismissal and focus return, plus focus into the roving day on open; `contentStyle()` is its placement — the same page-space `position: absolute` box `createPopover` gives, so a popup that escapes a positioned or clipping ancestor is placed by the initial containing block, not by the trigger's parent. The host renders the cells, the CSS and the trigger's visible text; a segmented text field is a host composition of `createField` + `createSpinbutton` over the same day numbers, not part of this API.
|
|
1596
1695
|
|
|
1597
1696
|
### Headless: `createPanZoom` (`@cssints/headless`)
|
|
1598
1697
|
|
|
@@ -1642,7 +1741,7 @@ The host owns the box and the look: the viewport's size and `overflow: hidden`,
|
|
|
1642
1741
|
|
|
1643
1742
|
### Headless: `createMenu` (`@cssints/headless`)
|
|
1644
1743
|
|
|
1645
|
-
The APG menu family (`createMenu` for the menu button and the checkable menus, `createMenubar` for the roving bar) as state and attribute objects. Its behaviour is `specs/apg/menu-button.qnt` and `menu-
|
|
1744
|
+
The APG menu family (`createMenu` for the menu button and the checkable menus, `createMenubar` for the roving bar) as state and attribute objects. Its behaviour is `specs/apg/menu-button.qnt`, `menu-checkable.qnt` and `menu-menubar.qnt`, and `packages/headless/test/menu-button.browser.test.tsx` and `packages/headless/test/menu.browser.test.tsx` replay the generated models, ported scenarios and seeded differential walks against it.
|
|
1646
1745
|
|
|
1647
1746
|
```ts skip
|
|
1648
1747
|
createMenu(options: {
|
|
@@ -1952,7 +2051,7 @@ createKnob(options?: {
|
|
|
1952
2051
|
|
|
1953
2052
|
### Headless: `createDropZone` (`@cssints/headless`)
|
|
1954
2053
|
|
|
1955
|
-
A file drop target. **There is no APG pattern
|
|
2054
|
+
A file drop target. **There is no APG pattern for a drop zone** — the contract is HTML's file input plus WAI-ARIA's live region — but the drag depth and the last delivery change over time, so they have a machine (`packages/apg-specs/specs/patterns/dropzone.qnt`). `packages/headless/test/dropzone.browser.test.tsx` (real Chromium) is the DOM oracle; `dropzone-spec.browser.test.tsx` replays the spec's scenarios and the differential walk, and a property test checks the per-file decision (`accept`, `maxSize`, `maxFiles`). It is the replacement for the bare `<input type="file">` fields of cms-admin's image field and kocharin's dashboard upload.
|
|
1956
2055
|
|
|
1957
2056
|
```ts skip
|
|
1958
2057
|
createDropZone(options: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cssints",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.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.26.0",
|
|
76
76
|
"culori": "^4.0.2",
|
|
77
77
|
"effect": "4.0.6",
|
|
78
78
|
"happy-dom": "^20.14.5",
|