@cssints/headless 0.15.0 → 0.18.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/README.md +191 -1
- package/dist/index.d.mts +531 -1
- package/dist/index.mjs +850 -34
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -1374,7 +1374,9 @@ export declare class ConfirmPendingError extends Error {
|
|
|
1374
1374
|
* Nesting: confirms from different `createConfirm` calls may be open together;
|
|
1375
1375
|
* only the last one opened gets Escape and Tab (and Escape does not reach a
|
|
1376
1376
|
* dialog it sits in), and the scroll lock is counted: the first takes it, the
|
|
1377
|
-
* last to close restores what `overflow` was.
|
|
1377
|
+
* last to close restores what `overflow` was. `createDrawer` claims the same
|
|
1378
|
+
* page-wide stack and lock, so a confirm over a drawer (or the reverse) nests
|
|
1379
|
+
* the same way. One confirm still has one request.
|
|
1378
1380
|
*
|
|
1379
1381
|
* One request at a time: a second `confirm()` while one is pending is rejected,
|
|
1380
1382
|
* not queued. Queued, a double click on "Delete" would ask twice and a stale
|
|
@@ -1382,6 +1384,119 @@ export declare class ConfirmPendingError extends Error {
|
|
|
1382
1384
|
* answered `false` when the owner is disposed.
|
|
1383
1385
|
*/
|
|
1384
1386
|
export declare const createConfirm: (options?: ConfirmOptions) => Confirm;
|
|
1387
|
+
/** The edge a drawer occupies. `side()` reads the option at call time. */
|
|
1388
|
+
export type DrawerSide = "left" | "right" | "bottom";
|
|
1389
|
+
export interface DrawerOptions {
|
|
1390
|
+
/** Base for the generated trigger / content / backdrop ids. */
|
|
1391
|
+
readonly id?: string | undefined;
|
|
1392
|
+
/** Controlled: the host owns the open state and this is the only thing that changes it. */
|
|
1393
|
+
readonly open?: boolean | undefined;
|
|
1394
|
+
/** Uncontrolled starting state; ignored while `open` is set. */
|
|
1395
|
+
readonly defaultOpen?: boolean | undefined;
|
|
1396
|
+
/**
|
|
1397
|
+
* The edge the panel is anchored to. @default "right"
|
|
1398
|
+
*
|
|
1399
|
+
* Presentation only: a `bottom` panel is a sheet, `left` / `right` is a side
|
|
1400
|
+
* rail, and the same drawer is one API either way.
|
|
1401
|
+
*/
|
|
1402
|
+
readonly side?: DrawerSide | undefined;
|
|
1403
|
+
/**
|
|
1404
|
+
* Whether the panel is modal: `aria-modal`, the Tab cycle, the scroll lock and
|
|
1405
|
+
* the backdrop. @default true
|
|
1406
|
+
*
|
|
1407
|
+
* This is the switch a breakpoint flips: below it a panel is a sheet and
|
|
1408
|
+
* modal, above it a side rail may not be. It is read on every attribute read,
|
|
1409
|
+
* so the host may change it while the drawer is open (crossing the
|
|
1410
|
+
* breakpoint) and the backdrop, `aria-modal` and the lock follow.
|
|
1411
|
+
*/
|
|
1412
|
+
readonly modal?: boolean | undefined;
|
|
1413
|
+
/**
|
|
1414
|
+
* The width in px at which the host switches `side` and `modal` (a sheet
|
|
1415
|
+
* below it, a dialog above). **Not read here**: the primitive has no viewport
|
|
1416
|
+
* to measure and no `matchMedia`, so it only documents the pattern, and the
|
|
1417
|
+
* host watches the breakpoint and passes `modal` / `side`. A host that wants
|
|
1418
|
+
* the sheet drag behaviour composes CSS scroll snap around this, as
|
|
1419
|
+
* [docs/bottom-sheet.md](../docs/bottom-sheet.md) lays out.
|
|
1420
|
+
*/
|
|
1421
|
+
readonly breakpoint?: number | undefined;
|
|
1422
|
+
/** A backdrop click or an outside `pointerdown` closes the drawer. @default true */
|
|
1423
|
+
readonly dismissable?: boolean | undefined;
|
|
1424
|
+
/** Id of the element naming the panel (`aria-labelledby`); a dialog without a name is unusable. */
|
|
1425
|
+
readonly labelledby?: string | undefined;
|
|
1426
|
+
/** A request to change the open state, called when the intent differs. */
|
|
1427
|
+
readonly onOpenChange?: ((open: boolean) => void) | undefined;
|
|
1428
|
+
}
|
|
1429
|
+
export interface DrawerTriggerProps {
|
|
1430
|
+
readonly id: string;
|
|
1431
|
+
readonly "aria-haspopup": "dialog";
|
|
1432
|
+
readonly "aria-expanded": "true" | "false";
|
|
1433
|
+
readonly "aria-controls": string;
|
|
1434
|
+
readonly onClick: () => void;
|
|
1435
|
+
readonly onKeyDown: (event: KeyboardEvent) => void;
|
|
1436
|
+
}
|
|
1437
|
+
export interface DrawerBackdropProps {
|
|
1438
|
+
readonly id: string;
|
|
1439
|
+
/** Always hidden when the drawer is closed or not modal: a non-modal drawer keeps the page clickable. */
|
|
1440
|
+
readonly hidden: boolean;
|
|
1441
|
+
readonly onClick: () => void;
|
|
1442
|
+
}
|
|
1443
|
+
export interface DrawerContentProps {
|
|
1444
|
+
readonly id: string;
|
|
1445
|
+
readonly role: "dialog";
|
|
1446
|
+
readonly "aria-modal": "true" | undefined;
|
|
1447
|
+
readonly "aria-labelledby": string | undefined;
|
|
1448
|
+
readonly tabindex: -1;
|
|
1449
|
+
readonly hidden: boolean;
|
|
1450
|
+
readonly onKeyDown: (event: KeyboardEvent) => void;
|
|
1451
|
+
}
|
|
1452
|
+
/** A bottom sheet's grab handle. Decorative: there is no drag, so it says nothing. */
|
|
1453
|
+
export interface DrawerHandleProps {
|
|
1454
|
+
readonly "aria-hidden": "true";
|
|
1455
|
+
}
|
|
1456
|
+
export interface Drawer {
|
|
1457
|
+
readonly open: Accessor<boolean>;
|
|
1458
|
+
readonly side: Accessor<DrawerSide>;
|
|
1459
|
+
readonly triggerProps: DrawerTriggerProps;
|
|
1460
|
+
readonly backdropProps: DrawerBackdropProps;
|
|
1461
|
+
readonly contentProps: DrawerContentProps;
|
|
1462
|
+
readonly handleProps: DrawerHandleProps;
|
|
1463
|
+
}
|
|
1464
|
+
/**
|
|
1465
|
+
* The shell for a drawer / bottom sheet: an edge-anchored panel over the
|
|
1466
|
+
* Dialog (Modal) semantics (https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/),
|
|
1467
|
+
* with the same shell as `createConfirm` — focus into the panel on open and back
|
|
1468
|
+
* to the trigger on close, Tab cycling while modal, Escape read from the
|
|
1469
|
+
* document (so it closes wherever focus is), a counted scroll lock and an
|
|
1470
|
+
* outside dismissal.
|
|
1471
|
+
*
|
|
1472
|
+
* **Compose, do not add a sheet primitive.** [docs/bottom-sheet.md](../docs/bottom-sheet.md)
|
|
1473
|
+
* settled it: the semantics are Dialog (Modal), the position, the slide and the
|
|
1474
|
+
* snap points are the host's CSS (native scroll snap), and the panel that both
|
|
1475
|
+
* need is this shell. `side` and `modal` are the whole presentation API: a sheet
|
|
1476
|
+
* is `{ side: "bottom", modal: true }`, a side rail is `{ side: "right" }` and a
|
|
1477
|
+
* non-modal rail is `{ side: "right", modal: false }`.
|
|
1478
|
+
*
|
|
1479
|
+
* **No drag-to-dismiss.** A drag is not a dialog interaction, it is a scroll
|
|
1480
|
+
* snap the platform runs off the main thread, and it belongs to the host's
|
|
1481
|
+
* scroll-snap CSS (see bottom-sheet.md: `scroll-snap-type: y mandatory`, snap
|
|
1482
|
+
* markers, `scrollsnapchange`). Mirroring a native snap in JS would be the
|
|
1483
|
+
* gesture re-implementation that the platform research forbids, and a pointer
|
|
1484
|
+
* gesture would exist on top of the snap that already moved the panel. So the
|
|
1485
|
+
* handle is decoration (`aria-hidden`, no pointer handlers): the ways out are
|
|
1486
|
+
* the backdrop, Escape, the close control the host puts in the panel, and the
|
|
1487
|
+
* trigger. Swipe-to-dismiss is a separate decision with its own oracle.
|
|
1488
|
+
*
|
|
1489
|
+
* The panel is edge-anchored, so there is **no `attach`**: nothing is measured
|
|
1490
|
+
* and no placement is computed. Focus and Tab find the panel through
|
|
1491
|
+
* `contentProps.id`, like `createConfirm` does, so the host only spreads
|
|
1492
|
+
* attributes onto its own elements.
|
|
1493
|
+
*
|
|
1494
|
+
* Not checked against a Quint spec: the dialog is, and this is its shell, so
|
|
1495
|
+
* `test/drawer.browser.test.tsx` is the oracle. A pointerdown outside the panel
|
|
1496
|
+
* closes it when `dismissable` (that is the backdrop click too, and the only
|
|
1497
|
+
* dismissal a non-modal drawer has); closing returns focus to the trigger.
|
|
1498
|
+
*/
|
|
1499
|
+
export declare const createDrawer: (options?: DrawerOptions) => Drawer;
|
|
1385
1500
|
/**
|
|
1386
1501
|
* Options are read lazily (getters); pass a component's `props`.
|
|
1387
1502
|
*/
|
|
@@ -1642,6 +1757,128 @@ export interface Pagination {
|
|
|
1642
1757
|
* Target size is the skin's job: 24x24 CSS px at AA, 44x44 at AAA.
|
|
1643
1758
|
*/
|
|
1644
1759
|
export declare const createPagination: (options: PaginationOptions) => Pagination;
|
|
1760
|
+
/** A point in the viewport's own coordinate space: `0, 0` is its top-left corner. */
|
|
1761
|
+
export interface PanZoomPoint {
|
|
1762
|
+
readonly x: number;
|
|
1763
|
+
readonly y: number;
|
|
1764
|
+
}
|
|
1765
|
+
export interface PanZoomOptions {
|
|
1766
|
+
/** Smallest scale, and where `reset` returns to. Default 1. */
|
|
1767
|
+
readonly min?: number | undefined;
|
|
1768
|
+
/** Largest scale. Default 4. */
|
|
1769
|
+
readonly max?: number | undefined;
|
|
1770
|
+
/** The `+` / `-` / `zoomIn` / `zoomOut` increment. Default 0.25. */
|
|
1771
|
+
readonly step?: number | undefined;
|
|
1772
|
+
/** Starting scale when uncontrolled; ignored while `scale` is set. */
|
|
1773
|
+
readonly defaultScale?: number | undefined;
|
|
1774
|
+
/** Controlled: the host owns the scale and this is the only thing that changes it. */
|
|
1775
|
+
readonly scale?: number | undefined;
|
|
1776
|
+
/**
|
|
1777
|
+
* A request to change the scale, called when the intent differs from the
|
|
1778
|
+
* current value. Uncontrolled it is accepted; controlled the host may ignore it.
|
|
1779
|
+
*/
|
|
1780
|
+
readonly onScaleChange?: (scale: number) => void;
|
|
1781
|
+
/** The pan changed (a drag, a pinch, an arrow key, a zoom about a point, a reset). */
|
|
1782
|
+
readonly onPanChange?: (offset: PanZoomPoint) => void;
|
|
1783
|
+
/**
|
|
1784
|
+
* `"clamp"` (default) keeps the scaled image covering the viewport: the offset
|
|
1785
|
+
* never opens a gap. `"free"` lets it be dragged out of view.
|
|
1786
|
+
*/
|
|
1787
|
+
readonly bounds?: "clamp" | "free" | undefined;
|
|
1788
|
+
readonly labels?: {
|
|
1789
|
+
readonly viewport?: string;
|
|
1790
|
+
} | undefined;
|
|
1791
|
+
}
|
|
1792
|
+
export interface PanZoomViewportProps {
|
|
1793
|
+
readonly tabindex: number;
|
|
1794
|
+
readonly role: "group";
|
|
1795
|
+
readonly "aria-roledescription": string;
|
|
1796
|
+
readonly "aria-label": string;
|
|
1797
|
+
readonly "aria-describedby": string;
|
|
1798
|
+
readonly onKeyDown: (event: KeyboardEvent) => void;
|
|
1799
|
+
readonly onWheel: (event: WheelEvent) => void;
|
|
1800
|
+
readonly onPointerDown: (event: PointerEvent) => void;
|
|
1801
|
+
readonly onPointerMove: (event: PointerEvent) => void;
|
|
1802
|
+
readonly onPointerUp: (event: PointerEvent) => void;
|
|
1803
|
+
readonly onPointerCancel: (event: PointerEvent) => void;
|
|
1804
|
+
readonly onDblClick: (event: MouseEvent) => void;
|
|
1805
|
+
}
|
|
1806
|
+
/**
|
|
1807
|
+
* Spread onto the image. `style` is a getter returning a fresh object, so Solid
|
|
1808
|
+
* reads it inside the tracking scope; `draggable: false` keeps the native image
|
|
1809
|
+
* drag from swallowing the pan gesture.
|
|
1810
|
+
*/
|
|
1811
|
+
export interface PanZoomImageProps {
|
|
1812
|
+
readonly draggable: false;
|
|
1813
|
+
readonly style: {
|
|
1814
|
+
readonly transform: string;
|
|
1815
|
+
};
|
|
1816
|
+
}
|
|
1817
|
+
/** The live value: the current percentage, in a polite status region (`aria-describedby` of the viewport). */
|
|
1818
|
+
export interface PanZoomValueProps {
|
|
1819
|
+
readonly id: string;
|
|
1820
|
+
readonly role: "status";
|
|
1821
|
+
readonly "aria-live": "polite";
|
|
1822
|
+
readonly "aria-atomic": "true";
|
|
1823
|
+
}
|
|
1824
|
+
export interface PanZoom {
|
|
1825
|
+
readonly scale: Accessor<number>;
|
|
1826
|
+
readonly offset: Accessor<PanZoomPoint>;
|
|
1827
|
+
/** Back to `min` and centred. */
|
|
1828
|
+
readonly reset: () => void;
|
|
1829
|
+
readonly zoomIn: () => void;
|
|
1830
|
+
readonly zoomOut: () => void;
|
|
1831
|
+
/** A request; `origin` is the viewport point to keep still (its centre by default). */
|
|
1832
|
+
readonly setScale: (scale: number, origin?: PanZoomPoint) => void;
|
|
1833
|
+
readonly viewportProps: PanZoomViewportProps;
|
|
1834
|
+
readonly imageProps: PanZoomImageProps;
|
|
1835
|
+
readonly valueProps: PanZoomValueProps;
|
|
1836
|
+
/** The human value of `valueProps`: `"150%"`. */
|
|
1837
|
+
readonly value: Accessor<string>;
|
|
1838
|
+
}
|
|
1839
|
+
/**
|
|
1840
|
+
* Pan and zoom for one image in a viewport: scale, offset and the prop objects
|
|
1841
|
+
* for a focusable viewport, the transformed image and a live value.
|
|
1842
|
+
*
|
|
1843
|
+
* **There is no APG pattern for pan-zoom.** The accessible contract is the three
|
|
1844
|
+
* parts this returns: a viewport that takes focus (`role=group`, `tabindex=0`,
|
|
1845
|
+
* `aria-roledescription="zoomable image"`), keyboard operations that stand in
|
|
1846
|
+
* for every pointer gesture (`+` / `=` / `-` zoom, arrows pan, `0` resets), and a
|
|
1847
|
+
* polite live region (`aria-describedby` of the viewport) that carries the
|
|
1848
|
+
* current scale. The pointer gestures are an addition for mouse and touch, never
|
|
1849
|
+
* the only way in. That contract is this module's own: the case it replaces
|
|
1850
|
+
* (kocharin-art-3's `Zoomable`, ~110 lines of pointer maths) had no keyboard path
|
|
1851
|
+
* and no ARIA at all, and `createCanvas` is the wrong model here — an infinite
|
|
1852
|
+
* canvas moves a camera over a world, while this is one image fitted to its
|
|
1853
|
+
* viewport.
|
|
1854
|
+
*
|
|
1855
|
+
* Wheel is zoom-with-a-modifier only: `ctrl` / `meta` (the browser's own pinch
|
|
1856
|
+
* arrives as `ctrl+wheel`) zooms about the cursor; a bare wheel is never
|
|
1857
|
+
* `preventDefault`ed, so the page keeps scrolling. Trackpad gestures differ per
|
|
1858
|
+
* platform and a swallowed wheel is the one thing that is never acceptable.
|
|
1859
|
+
*
|
|
1860
|
+
* Pointer: a drag pans, a second pointer pinches (about its midpoint), a double
|
|
1861
|
+
* click resets. Capture is best-effort (a synthetic pointer cannot be captured).
|
|
1862
|
+
*
|
|
1863
|
+
* `bounds: "clamp"` bounds the offset by half the viewport's scaled overflow, so
|
|
1864
|
+
* the image is expected to fill the viewport at `min` (the usual
|
|
1865
|
+
* `width: 100%; height: 100%` / `object-fit: cover` box). A letterboxed image
|
|
1866
|
+
* smaller than its viewport cannot be held gap-free; give it a viewport of its
|
|
1867
|
+
* own or `bounds: "free"`.
|
|
1868
|
+
*
|
|
1869
|
+
* Request/commit (`rt1p`): uncontrolled by default (`defaultScale`); pass `scale`
|
|
1870
|
+
* + `onScaleChange` to own the value. The offset has no controlled prop, so a
|
|
1871
|
+
* hosting page that rejects a scale request also gets the offset resolved for the
|
|
1872
|
+
* scale it refused — accept the request, or pass `bounds: "free"` and own the
|
|
1873
|
+
* pan elsewhere.
|
|
1874
|
+
*
|
|
1875
|
+
* The host owns the box and the looks: size the viewport, put `touch-action:
|
|
1876
|
+
* none` on it if a touch drag should pan, centre the image
|
|
1877
|
+
* (`transform-origin: 50% 50%` is the default this maths assumes), and hide the
|
|
1878
|
+
* live region visually rather than with `hidden` (a `hidden` live region is not
|
|
1879
|
+
* announced).
|
|
1880
|
+
*/
|
|
1881
|
+
export declare const createPanZoom: (options?: PanZoomOptions) => PanZoom;
|
|
1645
1882
|
export type PopoverPlacement = "bottom-start" | "bottom" | "bottom-end" | "top-start" | "top" | "top-end";
|
|
1646
1883
|
/** A rect in one space, as `getBoundingClientRect` gives it. */
|
|
1647
1884
|
export interface PopoverRect {
|
|
@@ -1912,6 +2149,53 @@ export declare const packSort: (column: number, dir: GridSortDirection) => numbe
|
|
|
1912
2149
|
* snapshot); `rows`/`cols`/`page` are captured once.
|
|
1913
2150
|
*/
|
|
1914
2151
|
export declare const createGrid: (options?: GridOptions) => Grid;
|
|
2152
|
+
/**
|
|
2153
|
+
* Hotkeys: one registry for the chords every app hand-rolls (the docs app's `/`
|
|
2154
|
+
* and Cmd/Ctrl+K, the CMS's Cmd/Ctrl+S), instead of a `keydown` listener per
|
|
2155
|
+
* screen.
|
|
2156
|
+
*
|
|
2157
|
+
* This is a platform utility, not a widget: there is no APG pattern for a
|
|
2158
|
+
* keyboard registry, so no role, no element, no styling, no Quint spec and no
|
|
2159
|
+
* ported scenario. The oracle is `test/hotkeys.browser.test.tsx` in a real
|
|
2160
|
+
* Chromium.
|
|
2161
|
+
*/
|
|
2162
|
+
export interface HotkeyBinding {
|
|
2163
|
+
/**
|
|
2164
|
+
* A chord: the modifiers `mod` (Cmd on macOS, Ctrl elsewhere), `ctrl`, `alt`,
|
|
2165
|
+
* `shift`, `meta`, then exactly one key (`k`, `/`, `?`, `escape`, `enter`,
|
|
2166
|
+
* `arrowup`), joined with `+`; `"mod+k"`, `"shift+?"`, `"/"`.
|
|
2167
|
+
*/
|
|
2168
|
+
readonly keys: string;
|
|
2169
|
+
readonly onTrigger: (event: KeyboardEvent) => void;
|
|
2170
|
+
/** Gates a binding whose precondition is off: it falls through to the next one. */
|
|
2171
|
+
readonly when?: (() => boolean) | undefined;
|
|
2172
|
+
/** `event.preventDefault()` when the binding fires; default true. */
|
|
2173
|
+
readonly preventDefault?: boolean | undefined;
|
|
2174
|
+
/** Fire while the focus is in an input, a textarea or a contenteditable; default false. */
|
|
2175
|
+
readonly allowInEditable?: boolean | undefined;
|
|
2176
|
+
/** Only events from inside this element: one panel's `"mod+s"` over a page's. */
|
|
2177
|
+
readonly scope?: HTMLElement | undefined;
|
|
2178
|
+
}
|
|
2179
|
+
export interface HotkeysOptions {
|
|
2180
|
+
/** Whether `mod` is Cmd; injected so a test does not read the runner's OS. */
|
|
2181
|
+
readonly isApple?: boolean | undefined;
|
|
2182
|
+
}
|
|
2183
|
+
export interface Hotkeys {
|
|
2184
|
+
/** Registers `binding`; the returned unbind is idempotent. The last bound wins. */
|
|
2185
|
+
readonly bind: (binding: HotkeyBinding) => () => void;
|
|
2186
|
+
/** Unbinds all of them and removes the listener; a later `bind` is a no-op. */
|
|
2187
|
+
readonly dispose: () => void;
|
|
2188
|
+
}
|
|
2189
|
+
/**
|
|
2190
|
+
* One `keydown` listener in the capture phase, one list of chords. A chord
|
|
2191
|
+
* fires when its modifiers match exactly, its `when` holds, its scope contains
|
|
2192
|
+
* the event's target, and the focus is not in a text field (unless
|
|
2193
|
+
* `allowInEditable`). The most recently bound match is the only one that fires,
|
|
2194
|
+
* so a modal's `"/"` overrides the page's and unbinding it brings the page's
|
|
2195
|
+
* back; `preventDefault` is on by default for the bindings whose chord the
|
|
2196
|
+
* browser would otherwise act on (Cmd/Ctrl+S, Cmd/Ctrl+K).
|
|
2197
|
+
*/
|
|
2198
|
+
export declare const createHotkeys: (options?: HotkeysOptions) => Hotkeys;
|
|
1915
2199
|
export interface LinkOptions {
|
|
1916
2200
|
readonly href?: string | undefined;
|
|
1917
2201
|
/** Render a native `<a href>` (the default when `href` is set). */
|
|
@@ -2486,6 +2770,76 @@ export interface ResizablePanels {
|
|
|
2486
2770
|
* DOM is checked against it by the differential walk.
|
|
2487
2771
|
*/
|
|
2488
2772
|
export declare const createResizablePanels: (options: ResizablePanelsOptions) => ResizablePanels;
|
|
2773
|
+
export interface ScrollspyEntry {
|
|
2774
|
+
readonly id: string;
|
|
2775
|
+
readonly label: string;
|
|
2776
|
+
}
|
|
2777
|
+
export interface ScrollspyOptions {
|
|
2778
|
+
/** The table of contents, in document order. The heading elements carry these ids. */
|
|
2779
|
+
readonly entries: ReadonlyArray<ScrollspyEntry>;
|
|
2780
|
+
/**
|
|
2781
|
+
* How far below the scrollport's top edge a heading counts as current, in px.
|
|
2782
|
+
* A host with a sticky header passes its height. @default 0
|
|
2783
|
+
*/
|
|
2784
|
+
readonly offset?: number | undefined;
|
|
2785
|
+
/**
|
|
2786
|
+
* The scroll container; the document by default. Read once, when `attach`
|
|
2787
|
+
* runs, so a host that swaps containers attaches again.
|
|
2788
|
+
*/
|
|
2789
|
+
readonly root?: (() => HTMLElement | undefined) | undefined;
|
|
2790
|
+
/** The active id changed. */
|
|
2791
|
+
readonly onChange?: ((id: string | undefined) => void) | undefined;
|
|
2792
|
+
}
|
|
2793
|
+
export interface ScrollspyNavProps {
|
|
2794
|
+
/** The navigation landmark's name; the host overrides it after the spread to translate it. */
|
|
2795
|
+
readonly "aria-label": string;
|
|
2796
|
+
}
|
|
2797
|
+
export interface ScrollspyListProps {
|
|
2798
|
+
readonly role: "list";
|
|
2799
|
+
}
|
|
2800
|
+
export interface ScrollspyItemProps {
|
|
2801
|
+
readonly role: "listitem";
|
|
2802
|
+
}
|
|
2803
|
+
export interface ScrollspyLinkProps {
|
|
2804
|
+
readonly href: string;
|
|
2805
|
+
readonly "aria-current": "location" | undefined;
|
|
2806
|
+
}
|
|
2807
|
+
export interface Scrollspy {
|
|
2808
|
+
/** The current entry's id; `undefined` only when there are no entries. */
|
|
2809
|
+
readonly active: Accessor<string | undefined>;
|
|
2810
|
+
readonly navProps: ScrollspyNavProps;
|
|
2811
|
+
readonly listProps: ScrollspyListProps;
|
|
2812
|
+
readonly itemProps: (id: string) => ScrollspyItemProps;
|
|
2813
|
+
readonly linkProps: (id: string) => ScrollspyLinkProps;
|
|
2814
|
+
/**
|
|
2815
|
+
* DOM wiring: one passive `scroll` listener on the root, which measures the
|
|
2816
|
+
* headings. Call it once mounted and call the teardown it returns on cleanup.
|
|
2817
|
+
*/
|
|
2818
|
+
readonly attach: () => () => void;
|
|
2819
|
+
}
|
|
2820
|
+
/**
|
|
2821
|
+
* A scrollspy: a table of contents whose current entry is the last heading that
|
|
2822
|
+
* has reached the top of the scrollport, marked `aria-current="location"` on
|
|
2823
|
+
* its link (and nowhere else: exactly one link is current).
|
|
2824
|
+
*
|
|
2825
|
+
* **No APG pattern exists for a scrollspy**, so there is no Quint spec and no
|
|
2826
|
+
* ported scenarios: the contract is WAI-ARIA (`aria-current="location"`, the
|
|
2827
|
+
* `list` / `listitem` roles, the navigation landmark and its name) and HTML,
|
|
2828
|
+
* and `test/scrollspy.browser.test.tsx` is its oracle.
|
|
2829
|
+
*
|
|
2830
|
+
* **A scroll listener, not an `IntersectionObserver`.** "The last heading at or
|
|
2831
|
+
* above the offset" is a total order over every heading, while an observer only
|
|
2832
|
+
* reports what crossed its band: a band observer has to rebuild the order from
|
|
2833
|
+
* its enter/leave state, and it misreports after a jump (an anchor click, a
|
|
2834
|
+
* restored scroll position). The listener is passive, the measurements are
|
|
2835
|
+
* reads only (no writes, so no layout thrash), and a docs page has a dozen
|
|
2836
|
+
* headings — the same cost as the listener the page already keeps.
|
|
2837
|
+
*
|
|
2838
|
+
* Smooth scrolling is the host's (`scroll-behavior: smooth`): the links are
|
|
2839
|
+
* plain `#id` targets and nothing here moves the scrollport, so no
|
|
2840
|
+
* `prefers-reduced-motion` branch is needed either.
|
|
2841
|
+
*/
|
|
2842
|
+
export declare const createScrollspy: (options: ScrollspyOptions) => Scrollspy;
|
|
2489
2843
|
export type SliderOrientation = "horizontal" | "vertical";
|
|
2490
2844
|
export type SliderDirection = "ltr" | "rtl";
|
|
2491
2845
|
export interface SliderOptions {
|
|
@@ -2797,6 +3151,182 @@ export interface Switch {
|
|
|
2797
3151
|
* pass `value` + `onValueChange` to own the state.
|
|
2798
3152
|
*/
|
|
2799
3153
|
export declare const createSwitch: (options?: SwitchOptions) => Switch;
|
|
3154
|
+
/**
|
|
3155
|
+
* A data table on Solid 2: sortable column headers and row selection, as state
|
|
3156
|
+
* plus getter-backed attribute objects; no elements, no styling.
|
|
3157
|
+
*
|
|
3158
|
+
* **The host renders a native `<table>`**: `<caption>`, `<thead>`, `<tbody>`,
|
|
3159
|
+
* `<th scope="col">` and `<td>` carry the semantics, so the primitive adds no
|
|
3160
|
+
* `role` and no `aria-rowindex` — only what a native table cannot say itself
|
|
3161
|
+
* (which column is sorted, whether a row is selected, the mixed select-all
|
|
3162
|
+
* state, and the totals a windowed table hides).
|
|
3163
|
+
*
|
|
3164
|
+
* **Sorting is the host's data.** The primitive carries the sort descriptor and
|
|
3165
|
+
* announces it (`aria-sort`); it never sorts. The host maps `sort()` onto its
|
|
3166
|
+
* own array before rendering `rows`.
|
|
3167
|
+
*
|
|
3168
|
+
* Composes the two state models the package already has — `grid-sort.qnt` and
|
|
3169
|
+
* `grid-selection.qnt` — without their navigation: a table is a grid with a
|
|
3170
|
+
* `th[scope]` header row, and the cells keep their own focus.
|
|
3171
|
+
*
|
|
3172
|
+
* Request/commit (map ticket `rt1p`): the sort and the selection are
|
|
3173
|
+
* uncontrolled by default (`defaultSort` / `defaultSelected`) and can be owned
|
|
3174
|
+
* by the host with `sort` + `onSortChange` / `selected` + `onSelectionChange`.
|
|
3175
|
+
* `null` as `sort` is "no column sorted" (in a controlled host); `undefined` is
|
|
3176
|
+
* "uncontrolled". `toggleRow`, `toggleAll` and the header button are requests:
|
|
3177
|
+
* a controlled host may ignore them.
|
|
3178
|
+
*/
|
|
3179
|
+
export type TableSortDirection = "ascending" | "descending";
|
|
3180
|
+
export type TableSelectionMode = "none" | "single" | "multiple";
|
|
3181
|
+
/**
|
|
3182
|
+
* `"tri-state"`: ascending -> descending -> unsorted, so a table can be put back
|
|
3183
|
+
* in its own order (the bean's requirement). `"toggle"`: ascending <-> descending,
|
|
3184
|
+
* never unsorted, which is what the APG grid example (and `grid-sort.qnt`) does.
|
|
3185
|
+
*/
|
|
3186
|
+
export type TableSortCycle = "tri-state" | "toggle";
|
|
3187
|
+
export interface TableSort {
|
|
3188
|
+
readonly key: string;
|
|
3189
|
+
readonly direction: TableSortDirection;
|
|
3190
|
+
}
|
|
3191
|
+
export interface TableColumn {
|
|
3192
|
+
/** Identity of the column: what `cellProps` and `headerCellProps` are asked for. */
|
|
3193
|
+
readonly key: string;
|
|
3194
|
+
/** Defaults to `true`; `false` makes the header inert (no sort button, no `aria-sort`). */
|
|
3195
|
+
readonly sortable?: boolean | undefined;
|
|
3196
|
+
/**
|
|
3197
|
+
* A layout hint for the host's skin (right-aligned, tabular figures). HTML has
|
|
3198
|
+
* no attribute for it, so the primitive carries nothing.
|
|
3199
|
+
*/
|
|
3200
|
+
readonly numeric?: boolean | undefined;
|
|
3201
|
+
/** The `abbr` of the header cell: the short form a screen reader uses. */
|
|
3202
|
+
readonly abbr?: string | undefined;
|
|
3203
|
+
}
|
|
3204
|
+
export interface TableLabels {
|
|
3205
|
+
/** The table's accessible name; a host that renders a `<caption>` leaves it out. */
|
|
3206
|
+
readonly table?: string | undefined;
|
|
3207
|
+
/** Defaults to "Select all rows". */
|
|
3208
|
+
readonly selectAll?: string | undefined;
|
|
3209
|
+
/** Defaults to `Select row <id>`. */
|
|
3210
|
+
readonly selectRow?: ((id: string) => string) | undefined;
|
|
3211
|
+
}
|
|
3212
|
+
export interface TableOptions {
|
|
3213
|
+
readonly columns: ReadonlyArray<TableColumn>;
|
|
3214
|
+
/**
|
|
3215
|
+
* The row ids the host renders, in render order. Ids, not a count: the sort
|
|
3216
|
+
* reorders the host's data and a selection must survive that, while an index
|
|
3217
|
+
* would then name a different row. The row's visible content stays the host's.
|
|
3218
|
+
*/
|
|
3219
|
+
readonly rows: ReadonlyArray<string>;
|
|
3220
|
+
readonly selection?: TableSelectionMode | undefined;
|
|
3221
|
+
readonly sortCycle?: TableSortCycle | undefined;
|
|
3222
|
+
/** Controlled: the host owns the sort and this is the only thing that changes it; `null` is "unsorted". */
|
|
3223
|
+
readonly sort?: TableSort | null | undefined;
|
|
3224
|
+
/** Uncontrolled starting sort; ignored while `sort` is set. */
|
|
3225
|
+
readonly defaultSort?: TableSort | undefined;
|
|
3226
|
+
/** A request to re-sort (`undefined` = unsorted); uncontrolled it is accepted. */
|
|
3227
|
+
readonly onSortChange?: ((sort: TableSort | undefined) => void) | undefined;
|
|
3228
|
+
/** Controlled: the host owns the selected row ids and this is the only thing that changes them. */
|
|
3229
|
+
readonly selected?: ReadonlyArray<string> | undefined;
|
|
3230
|
+
/** Uncontrolled starting selection; ignored while `selected` is set. */
|
|
3231
|
+
readonly defaultSelected?: ReadonlyArray<string> | undefined;
|
|
3232
|
+
/** A request to change the selection; uncontrolled it is accepted. */
|
|
3233
|
+
readonly onSelectionChange?: ((selected: ReadonlyArray<string>) => void) | undefined;
|
|
3234
|
+
/**
|
|
3235
|
+
* The total row count when the host renders a window of `rows`
|
|
3236
|
+
* (`aria-rowcount`); absent on a table that renders all of them.
|
|
3237
|
+
*/
|
|
3238
|
+
readonly rowCount?: number | undefined;
|
|
3239
|
+
/**
|
|
3240
|
+
* The total column count when the host renders a window of `columns`
|
|
3241
|
+
* (`aria-colcount` and `aria-colindex`); absent on a table that renders all.
|
|
3242
|
+
*/
|
|
3243
|
+
readonly columnCount?: number | undefined;
|
|
3244
|
+
readonly labels?: TableLabels | undefined;
|
|
3245
|
+
/** Base for generated ids. */
|
|
3246
|
+
readonly id?: string | undefined;
|
|
3247
|
+
}
|
|
3248
|
+
/** Spread onto `<table>`; the host renders the table, the head and the body. */
|
|
3249
|
+
export interface TableProps {
|
|
3250
|
+
/** `labels.table`; leave it out when the host renders a caption. */
|
|
3251
|
+
readonly "aria-label": string | undefined;
|
|
3252
|
+
/** Only while the host windows its rows. */
|
|
3253
|
+
readonly "aria-rowcount": number | undefined;
|
|
3254
|
+
/** Only while the host windows its columns. */
|
|
3255
|
+
readonly "aria-colcount": number | undefined;
|
|
3256
|
+
}
|
|
3257
|
+
/** Spread onto `<caption>`. */
|
|
3258
|
+
export interface TableCaptionProps {
|
|
3259
|
+
readonly id: string;
|
|
3260
|
+
}
|
|
3261
|
+
/** Spread onto `<th scope="col">` in the header row. */
|
|
3262
|
+
export interface TableHeaderCellProps {
|
|
3263
|
+
readonly scope: "col";
|
|
3264
|
+
/** The sorted column only; an unsorted one carries nothing (ARIA's default). */
|
|
3265
|
+
readonly "aria-sort": TableSortDirection | undefined;
|
|
3266
|
+
readonly abbr: string | undefined;
|
|
3267
|
+
}
|
|
3268
|
+
/** Spread onto the `<button type="button">` inside a sortable `<th>`. */
|
|
3269
|
+
export interface TableHeaderButtonProps {
|
|
3270
|
+
readonly type: "button";
|
|
3271
|
+
readonly onClick: () => void;
|
|
3272
|
+
}
|
|
3273
|
+
/** Spread onto `<tr>` in the body. */
|
|
3274
|
+
export interface TableRowProps {
|
|
3275
|
+
readonly "aria-selected": "true" | "false" | undefined;
|
|
3276
|
+
/** Selects the row; `undefined` when nothing is selectable. */
|
|
3277
|
+
readonly onClick: (() => void) | undefined;
|
|
3278
|
+
}
|
|
3279
|
+
/** Spread onto a `<td>`; `aria-colindex` only while the host windows its columns. */
|
|
3280
|
+
export interface TableCellProps {
|
|
3281
|
+
readonly "aria-colindex": number | undefined;
|
|
3282
|
+
}
|
|
3283
|
+
/**
|
|
3284
|
+
* Spread onto the select-all control: a `role=checkbox` element (not a native
|
|
3285
|
+
* `input`, which spells the mixed state as a DOM property, not an attribute).
|
|
3286
|
+
*/
|
|
3287
|
+
export interface TableSelectAllProps {
|
|
3288
|
+
readonly role: "checkbox";
|
|
3289
|
+
readonly "aria-checked": "true" | "false" | "mixed";
|
|
3290
|
+
readonly "aria-label": string;
|
|
3291
|
+
readonly tabindex: 0;
|
|
3292
|
+
readonly onClick: () => void;
|
|
3293
|
+
readonly onKeyDown: (event: KeyboardEvent) => void;
|
|
3294
|
+
}
|
|
3295
|
+
/** Spread onto a row's selection control. */
|
|
3296
|
+
export interface TableRowCheckboxProps {
|
|
3297
|
+
readonly role: "checkbox";
|
|
3298
|
+
readonly "aria-checked": "true" | "false";
|
|
3299
|
+
readonly "aria-label": string;
|
|
3300
|
+
readonly tabindex: 0;
|
|
3301
|
+
/** Selects the row and stops there: the control sits inside the row, whose click would select again. */
|
|
3302
|
+
readonly onClick: (event: MouseEvent) => void;
|
|
3303
|
+
readonly onKeyDown: (event: KeyboardEvent) => void;
|
|
3304
|
+
}
|
|
3305
|
+
export interface Table {
|
|
3306
|
+
readonly selectionMode: Accessor<TableSelectionMode>;
|
|
3307
|
+
/** The sort descriptor, or `undefined` when nothing is sorted. */
|
|
3308
|
+
readonly sort: Accessor<TableSort | undefined>;
|
|
3309
|
+
readonly selected: Accessor<ReadonlyArray<string>>;
|
|
3310
|
+
readonly isSelected: (id: string) => boolean;
|
|
3311
|
+
readonly tableProps: TableProps;
|
|
3312
|
+
readonly captionProps: TableCaptionProps;
|
|
3313
|
+
readonly headerCellProps: (key: string) => TableHeaderCellProps;
|
|
3314
|
+
/** `undefined` for a column that is not sortable — render no button there. */
|
|
3315
|
+
readonly headerButtonProps: (key: string) => TableHeaderButtonProps | undefined;
|
|
3316
|
+
readonly rowProps: (id: string) => TableRowProps;
|
|
3317
|
+
readonly cellProps: (rowId: string, key: string) => TableCellProps;
|
|
3318
|
+
/** `undefined` unless `selection` is `"multiple"`: one control over one selection is meaningless. */
|
|
3319
|
+
readonly selectAllProps: TableSelectAllProps | undefined;
|
|
3320
|
+
/** `undefined` while `selection` is `"none"`. */
|
|
3321
|
+
readonly rowCheckboxProps: (id: string) => TableRowCheckboxProps | undefined;
|
|
3322
|
+
readonly toggleAll: () => void;
|
|
3323
|
+
readonly toggleRow: (id: string) => void;
|
|
3324
|
+
}
|
|
3325
|
+
/**
|
|
3326
|
+
* Every option is read when an attribute is read, so pass props/getters, never a
|
|
3327
|
+
* destructured snapshot. `id` is captured once: it is the base of the caption id.
|
|
3328
|
+
*/
|
|
3329
|
+
export declare const createTable: (options: TableOptions) => Table;
|
|
2800
3330
|
/** The HTML `autocomplete` tokens worth naming; the attribute also takes an arbitrary token. */
|
|
2801
3331
|
export type AutoCompleteToken = "off" | "on" | "name" | "given-name" | "family-name" | "username" | "email" | "tel" | "url" | "organization" | "street-address" | "postal-code" | "current-password" | "new-password" | "one-time-code" | "nickname";
|
|
2802
3332
|
export interface TextInputOptions extends FieldOptions {
|