@cssints/headless 0.17.0 → 0.19.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 +216 -1
- package/dist/index.d.mts +527 -10
- package/dist/index.mjs +963 -50
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -1497,6 +1497,106 @@ export interface Drawer {
|
|
|
1497
1497
|
* dismissal a non-modal drawer has); closing returns focus to the trigger.
|
|
1498
1498
|
*/
|
|
1499
1499
|
export declare const createDrawer: (options?: DrawerOptions) => Drawer;
|
|
1500
|
+
/** Why a file was refused: the `accept` grammar, or `maxSize`. */
|
|
1501
|
+
export type DropZoneRejectReason = "type" | "size";
|
|
1502
|
+
export interface DropZoneReject {
|
|
1503
|
+
readonly file: File;
|
|
1504
|
+
readonly reason: DropZoneRejectReason;
|
|
1505
|
+
}
|
|
1506
|
+
export interface DropZoneLabels {
|
|
1507
|
+
/** The zone's accessible name (`aria-label` on the `role=group` zone). */
|
|
1508
|
+
readonly zone?: string | undefined;
|
|
1509
|
+
/** The browse button's accessible name; render it visibly, or use it for an icon-only button. */
|
|
1510
|
+
readonly browse?: string | undefined;
|
|
1511
|
+
}
|
|
1512
|
+
export interface DropZoneOptions {
|
|
1513
|
+
/** The HTML `accept` grammar: extensions (`.png`) and MIME types, wildcards included (`image/*`). */
|
|
1514
|
+
readonly accept?: string | undefined;
|
|
1515
|
+
readonly multiple?: boolean | undefined;
|
|
1516
|
+
/** Bytes; a larger file is rejected with `"size"`. */
|
|
1517
|
+
readonly maxSize?: number | undefined;
|
|
1518
|
+
readonly disabled?: boolean | undefined;
|
|
1519
|
+
/** The files that passed, one call per batch; not called when the batch has none. */
|
|
1520
|
+
readonly onFiles: (files: File[]) => void;
|
|
1521
|
+
/** Each refused file, once, with the reason the host shows. */
|
|
1522
|
+
readonly onReject?: ((reject: DropZoneReject) => void) | undefined;
|
|
1523
|
+
readonly labels?: DropZoneLabels | undefined;
|
|
1524
|
+
}
|
|
1525
|
+
/** Spread onto the hidden `<input type=file>`; render it, always. */
|
|
1526
|
+
export interface DropZoneInputProps {
|
|
1527
|
+
/** The id the browse button clicks; generated. */
|
|
1528
|
+
readonly id: string;
|
|
1529
|
+
readonly type: "file";
|
|
1530
|
+
readonly accept: string | undefined;
|
|
1531
|
+
readonly multiple: true | undefined;
|
|
1532
|
+
readonly disabled: true | undefined;
|
|
1533
|
+
readonly hidden: true;
|
|
1534
|
+
readonly onChange: (event: {
|
|
1535
|
+
currentTarget: HTMLInputElement;
|
|
1536
|
+
}) => void;
|
|
1537
|
+
}
|
|
1538
|
+
/** Spread onto the drop target (any element). `dragging()` is the styling hook. */
|
|
1539
|
+
export interface DropZoneZoneProps {
|
|
1540
|
+
readonly role: "group";
|
|
1541
|
+
readonly "aria-label": string | undefined;
|
|
1542
|
+
readonly "aria-disabled": "true" | undefined;
|
|
1543
|
+
readonly onDragEnter: (event: DragEvent) => void;
|
|
1544
|
+
readonly onDragOver: (event: DragEvent) => void;
|
|
1545
|
+
readonly onDragLeave: (event: DragEvent) => void;
|
|
1546
|
+
readonly onDrop: (event: DragEvent) => void;
|
|
1547
|
+
}
|
|
1548
|
+
/** Spread onto a real `<button type="button">`: Enter and Space are the platform's. */
|
|
1549
|
+
export interface DropZoneBrowseProps {
|
|
1550
|
+
readonly type: "button";
|
|
1551
|
+
readonly disabled: true | undefined;
|
|
1552
|
+
readonly "aria-disabled": "true" | undefined;
|
|
1553
|
+
readonly "aria-label": string | undefined;
|
|
1554
|
+
readonly onClick: () => void;
|
|
1555
|
+
}
|
|
1556
|
+
/** Spread onto the live region; render it once, always. */
|
|
1557
|
+
export interface DropZoneStatusProps {
|
|
1558
|
+
readonly role: "status";
|
|
1559
|
+
readonly "aria-live": "polite";
|
|
1560
|
+
readonly "aria-atomic": "true";
|
|
1561
|
+
}
|
|
1562
|
+
export interface DropZone {
|
|
1563
|
+
/** A drag is over the zone; `dragleave` on a child does not clear it. */
|
|
1564
|
+
readonly dragging: Accessor<boolean>;
|
|
1565
|
+
/**
|
|
1566
|
+
* A default sentence for the last batch ("2 files, 1 rejected: too large"),
|
|
1567
|
+
* or `undefined` before one arrived. The host may write its own words from
|
|
1568
|
+
* the same counts.
|
|
1569
|
+
*/
|
|
1570
|
+
readonly message: Accessor<string | undefined>;
|
|
1571
|
+
readonly inputProps: DropZoneInputProps;
|
|
1572
|
+
readonly zoneProps: DropZoneZoneProps;
|
|
1573
|
+
readonly browseProps: DropZoneBrowseProps;
|
|
1574
|
+
readonly statusProps: DropZoneStatusProps;
|
|
1575
|
+
}
|
|
1576
|
+
/**
|
|
1577
|
+
* A file drop zone. There is no APG pattern and no Quint spec for one: the
|
|
1578
|
+
* contract is HTML's file input plus WAI-ARIA's live region, and
|
|
1579
|
+
* `test/dropzone.browser.test.tsx` is its oracle.
|
|
1580
|
+
*
|
|
1581
|
+
* Decisions:
|
|
1582
|
+
* - **The zone is never drag-only.** `browseProps` spreads onto a real
|
|
1583
|
+
* `<button type="button">` that clicks the hidden input, so the picker is the
|
|
1584
|
+
* keyboard path (Enter and Space are the platform's) and a pointer user can
|
|
1585
|
+
* click as well as drag. The drop handlers add nothing the button cannot do.
|
|
1586
|
+
* - **Validation happens once per file**, in the order type then size, and a
|
|
1587
|
+
* refused file reaches `onReject` with the reason the host shows. `accept` is
|
|
1588
|
+
* the HTML grammar (`.` extension, MIME type, `*`); `maxSize` is bytes.
|
|
1589
|
+
* - **Dropping a folder or a `DataTransfer` with no files is a no-op**: no
|
|
1590
|
+
* `onFiles`, no `onReject`, no message, and the previous announcement stays.
|
|
1591
|
+
* - **`dragleave` counts.** A child's `dragleave` bubbles to the zone, so the
|
|
1592
|
+
* state is a depth counter: it clears only when the drag leaves the zone
|
|
1593
|
+
* itself, or on a drop.
|
|
1594
|
+
* - **The zone is `role=group`** so `labels.zone` is exposed; `dragging()` is
|
|
1595
|
+
* the only drag state a skin reads (no `data-dragging`).
|
|
1596
|
+
* - `disabled` marks the zone `aria-disabled`, disables the input natively and
|
|
1597
|
+
* the browse button, and drops every drag before it becomes a batch.
|
|
1598
|
+
*/
|
|
1599
|
+
export declare const createDropZone: (options: DropZoneOptions) => DropZone;
|
|
1500
1600
|
/**
|
|
1501
1601
|
* Options are read lazily (getters); pass a component's `props`.
|
|
1502
1602
|
*/
|
|
@@ -1757,6 +1857,128 @@ export interface Pagination {
|
|
|
1757
1857
|
* Target size is the skin's job: 24x24 CSS px at AA, 44x44 at AAA.
|
|
1758
1858
|
*/
|
|
1759
1859
|
export declare const createPagination: (options: PaginationOptions) => Pagination;
|
|
1860
|
+
/** A point in the viewport's own coordinate space: `0, 0` is its top-left corner. */
|
|
1861
|
+
export interface PanZoomPoint {
|
|
1862
|
+
readonly x: number;
|
|
1863
|
+
readonly y: number;
|
|
1864
|
+
}
|
|
1865
|
+
export interface PanZoomOptions {
|
|
1866
|
+
/** Smallest scale, and where `reset` returns to. Default 1. */
|
|
1867
|
+
readonly min?: number | undefined;
|
|
1868
|
+
/** Largest scale. Default 4. */
|
|
1869
|
+
readonly max?: number | undefined;
|
|
1870
|
+
/** The `+` / `-` / `zoomIn` / `zoomOut` increment. Default 0.25. */
|
|
1871
|
+
readonly step?: number | undefined;
|
|
1872
|
+
/** Starting scale when uncontrolled; ignored while `scale` is set. */
|
|
1873
|
+
readonly defaultScale?: number | undefined;
|
|
1874
|
+
/** Controlled: the host owns the scale and this is the only thing that changes it. */
|
|
1875
|
+
readonly scale?: number | undefined;
|
|
1876
|
+
/**
|
|
1877
|
+
* A request to change the scale, called when the intent differs from the
|
|
1878
|
+
* current value. Uncontrolled it is accepted; controlled the host may ignore it.
|
|
1879
|
+
*/
|
|
1880
|
+
readonly onScaleChange?: (scale: number) => void;
|
|
1881
|
+
/** The pan changed (a drag, a pinch, an arrow key, a zoom about a point, a reset). */
|
|
1882
|
+
readonly onPanChange?: (offset: PanZoomPoint) => void;
|
|
1883
|
+
/**
|
|
1884
|
+
* `"clamp"` (default) keeps the scaled image covering the viewport: the offset
|
|
1885
|
+
* never opens a gap. `"free"` lets it be dragged out of view.
|
|
1886
|
+
*/
|
|
1887
|
+
readonly bounds?: "clamp" | "free" | undefined;
|
|
1888
|
+
readonly labels?: {
|
|
1889
|
+
readonly viewport?: string;
|
|
1890
|
+
} | undefined;
|
|
1891
|
+
}
|
|
1892
|
+
export interface PanZoomViewportProps {
|
|
1893
|
+
readonly tabindex: number;
|
|
1894
|
+
readonly role: "group";
|
|
1895
|
+
readonly "aria-roledescription": string;
|
|
1896
|
+
readonly "aria-label": string;
|
|
1897
|
+
readonly "aria-describedby": string;
|
|
1898
|
+
readonly onKeyDown: (event: KeyboardEvent) => void;
|
|
1899
|
+
readonly onWheel: (event: WheelEvent) => void;
|
|
1900
|
+
readonly onPointerDown: (event: PointerEvent) => void;
|
|
1901
|
+
readonly onPointerMove: (event: PointerEvent) => void;
|
|
1902
|
+
readonly onPointerUp: (event: PointerEvent) => void;
|
|
1903
|
+
readonly onPointerCancel: (event: PointerEvent) => void;
|
|
1904
|
+
readonly onDblClick: (event: MouseEvent) => void;
|
|
1905
|
+
}
|
|
1906
|
+
/**
|
|
1907
|
+
* Spread onto the image. `style` is a getter returning a fresh object, so Solid
|
|
1908
|
+
* reads it inside the tracking scope; `draggable: false` keeps the native image
|
|
1909
|
+
* drag from swallowing the pan gesture.
|
|
1910
|
+
*/
|
|
1911
|
+
export interface PanZoomImageProps {
|
|
1912
|
+
readonly draggable: false;
|
|
1913
|
+
readonly style: {
|
|
1914
|
+
readonly transform: string;
|
|
1915
|
+
};
|
|
1916
|
+
}
|
|
1917
|
+
/** The live value: the current percentage, in a polite status region (`aria-describedby` of the viewport). */
|
|
1918
|
+
export interface PanZoomValueProps {
|
|
1919
|
+
readonly id: string;
|
|
1920
|
+
readonly role: "status";
|
|
1921
|
+
readonly "aria-live": "polite";
|
|
1922
|
+
readonly "aria-atomic": "true";
|
|
1923
|
+
}
|
|
1924
|
+
export interface PanZoom {
|
|
1925
|
+
readonly scale: Accessor<number>;
|
|
1926
|
+
readonly offset: Accessor<PanZoomPoint>;
|
|
1927
|
+
/** Back to `min` and centred. */
|
|
1928
|
+
readonly reset: () => void;
|
|
1929
|
+
readonly zoomIn: () => void;
|
|
1930
|
+
readonly zoomOut: () => void;
|
|
1931
|
+
/** A request; `origin` is the viewport point to keep still (its centre by default). */
|
|
1932
|
+
readonly setScale: (scale: number, origin?: PanZoomPoint) => void;
|
|
1933
|
+
readonly viewportProps: PanZoomViewportProps;
|
|
1934
|
+
readonly imageProps: PanZoomImageProps;
|
|
1935
|
+
readonly valueProps: PanZoomValueProps;
|
|
1936
|
+
/** The human value of `valueProps`: `"150%"`. */
|
|
1937
|
+
readonly value: Accessor<string>;
|
|
1938
|
+
}
|
|
1939
|
+
/**
|
|
1940
|
+
* Pan and zoom for one image in a viewport: scale, offset and the prop objects
|
|
1941
|
+
* for a focusable viewport, the transformed image and a live value.
|
|
1942
|
+
*
|
|
1943
|
+
* **There is no APG pattern for pan-zoom.** The accessible contract is the three
|
|
1944
|
+
* parts this returns: a viewport that takes focus (`role=group`, `tabindex=0`,
|
|
1945
|
+
* `aria-roledescription="zoomable image"`), keyboard operations that stand in
|
|
1946
|
+
* for every pointer gesture (`+` / `=` / `-` zoom, arrows pan, `0` resets), and a
|
|
1947
|
+
* polite live region (`aria-describedby` of the viewport) that carries the
|
|
1948
|
+
* current scale. The pointer gestures are an addition for mouse and touch, never
|
|
1949
|
+
* the only way in. That contract is this module's own: the case it replaces
|
|
1950
|
+
* (kocharin-art-3's `Zoomable`, ~110 lines of pointer maths) had no keyboard path
|
|
1951
|
+
* and no ARIA at all, and `createCanvas` is the wrong model here — an infinite
|
|
1952
|
+
* canvas moves a camera over a world, while this is one image fitted to its
|
|
1953
|
+
* viewport.
|
|
1954
|
+
*
|
|
1955
|
+
* Wheel is zoom-with-a-modifier only: `ctrl` / `meta` (the browser's own pinch
|
|
1956
|
+
* arrives as `ctrl+wheel`) zooms about the cursor; a bare wheel is never
|
|
1957
|
+
* `preventDefault`ed, so the page keeps scrolling. Trackpad gestures differ per
|
|
1958
|
+
* platform and a swallowed wheel is the one thing that is never acceptable.
|
|
1959
|
+
*
|
|
1960
|
+
* Pointer: a drag pans, a second pointer pinches (about its midpoint), a double
|
|
1961
|
+
* click resets. Capture is best-effort (a synthetic pointer cannot be captured).
|
|
1962
|
+
*
|
|
1963
|
+
* `bounds: "clamp"` bounds the offset by half the viewport's scaled overflow, so
|
|
1964
|
+
* the image is expected to fill the viewport at `min` (the usual
|
|
1965
|
+
* `width: 100%; height: 100%` / `object-fit: cover` box). A letterboxed image
|
|
1966
|
+
* smaller than its viewport cannot be held gap-free; give it a viewport of its
|
|
1967
|
+
* own or `bounds: "free"`.
|
|
1968
|
+
*
|
|
1969
|
+
* Request/commit (`rt1p`): uncontrolled by default (`defaultScale`); pass `scale`
|
|
1970
|
+
* + `onScaleChange` to own the value. The offset has no controlled prop, so a
|
|
1971
|
+
* hosting page that rejects a scale request also gets the offset resolved for the
|
|
1972
|
+
* scale it refused — accept the request, or pass `bounds: "free"` and own the
|
|
1973
|
+
* pan elsewhere.
|
|
1974
|
+
*
|
|
1975
|
+
* The host owns the box and the looks: size the viewport, put `touch-action:
|
|
1976
|
+
* none` on it if a touch drag should pan, centre the image
|
|
1977
|
+
* (`transform-origin: 50% 50%` is the default this maths assumes), and hide the
|
|
1978
|
+
* live region visually rather than with `hidden` (a `hidden` live region is not
|
|
1979
|
+
* announced).
|
|
1980
|
+
*/
|
|
1981
|
+
export declare const createPanZoom: (options?: PanZoomOptions) => PanZoom;
|
|
1760
1982
|
export type PopoverPlacement = "bottom-start" | "bottom" | "bottom-end" | "top-start" | "top" | "top-end";
|
|
1761
1983
|
/** A rect in one space, as `getBoundingClientRect` gives it. */
|
|
1762
1984
|
export interface PopoverRect {
|
|
@@ -2027,6 +2249,53 @@ export declare const packSort: (column: number, dir: GridSortDirection) => numbe
|
|
|
2027
2249
|
* snapshot); `rows`/`cols`/`page` are captured once.
|
|
2028
2250
|
*/
|
|
2029
2251
|
export declare const createGrid: (options?: GridOptions) => Grid;
|
|
2252
|
+
/**
|
|
2253
|
+
* Hotkeys: one registry for the chords every app hand-rolls (the docs app's `/`
|
|
2254
|
+
* and Cmd/Ctrl+K, the CMS's Cmd/Ctrl+S), instead of a `keydown` listener per
|
|
2255
|
+
* screen.
|
|
2256
|
+
*
|
|
2257
|
+
* This is a platform utility, not a widget: there is no APG pattern for a
|
|
2258
|
+
* keyboard registry, so no role, no element, no styling, no Quint spec and no
|
|
2259
|
+
* ported scenario. The oracle is `test/hotkeys.browser.test.tsx` in a real
|
|
2260
|
+
* Chromium.
|
|
2261
|
+
*/
|
|
2262
|
+
export interface HotkeyBinding {
|
|
2263
|
+
/**
|
|
2264
|
+
* A chord: the modifiers `mod` (Cmd on macOS, Ctrl elsewhere), `ctrl`, `alt`,
|
|
2265
|
+
* `shift`, `meta`, then exactly one key (`k`, `/`, `?`, `escape`, `enter`,
|
|
2266
|
+
* `arrowup`), joined with `+`; `"mod+k"`, `"shift+?"`, `"/"`.
|
|
2267
|
+
*/
|
|
2268
|
+
readonly keys: string;
|
|
2269
|
+
readonly onTrigger: (event: KeyboardEvent) => void;
|
|
2270
|
+
/** Gates a binding whose precondition is off: it falls through to the next one. */
|
|
2271
|
+
readonly when?: (() => boolean) | undefined;
|
|
2272
|
+
/** `event.preventDefault()` when the binding fires; default true. */
|
|
2273
|
+
readonly preventDefault?: boolean | undefined;
|
|
2274
|
+
/** Fire while the focus is in an input, a textarea or a contenteditable; default false. */
|
|
2275
|
+
readonly allowInEditable?: boolean | undefined;
|
|
2276
|
+
/** Only events from inside this element: one panel's `"mod+s"` over a page's. */
|
|
2277
|
+
readonly scope?: HTMLElement | undefined;
|
|
2278
|
+
}
|
|
2279
|
+
export interface HotkeysOptions {
|
|
2280
|
+
/** Whether `mod` is Cmd; injected so a test does not read the runner's OS. */
|
|
2281
|
+
readonly isApple?: boolean | undefined;
|
|
2282
|
+
}
|
|
2283
|
+
export interface Hotkeys {
|
|
2284
|
+
/** Registers `binding`; the returned unbind is idempotent. The last bound wins. */
|
|
2285
|
+
readonly bind: (binding: HotkeyBinding) => () => void;
|
|
2286
|
+
/** Unbinds all of them and removes the listener; a later `bind` is a no-op. */
|
|
2287
|
+
readonly dispose: () => void;
|
|
2288
|
+
}
|
|
2289
|
+
/**
|
|
2290
|
+
* One `keydown` listener in the capture phase, one list of chords. A chord
|
|
2291
|
+
* fires when its modifiers match exactly, its `when` holds, its scope contains
|
|
2292
|
+
* the event's target, and the focus is not in a text field (unless
|
|
2293
|
+
* `allowInEditable`). The most recently bound match is the only one that fires,
|
|
2294
|
+
* so a modal's `"/"` overrides the page's and unbinding it brings the page's
|
|
2295
|
+
* back; `preventDefault` is on by default for the bindings whose chord the
|
|
2296
|
+
* browser would otherwise act on (Cmd/Ctrl+S, Cmd/Ctrl+K).
|
|
2297
|
+
*/
|
|
2298
|
+
export declare const createHotkeys: (options?: HotkeysOptions) => Hotkeys;
|
|
2030
2299
|
export interface LinkOptions {
|
|
2031
2300
|
readonly href?: string | undefined;
|
|
2032
2301
|
/** Render a native `<a href>` (the default when `href` is set). */
|
|
@@ -2601,6 +2870,76 @@ export interface ResizablePanels {
|
|
|
2601
2870
|
* DOM is checked against it by the differential walk.
|
|
2602
2871
|
*/
|
|
2603
2872
|
export declare const createResizablePanels: (options: ResizablePanelsOptions) => ResizablePanels;
|
|
2873
|
+
export interface ScrollspyEntry {
|
|
2874
|
+
readonly id: string;
|
|
2875
|
+
readonly label: string;
|
|
2876
|
+
}
|
|
2877
|
+
export interface ScrollspyOptions {
|
|
2878
|
+
/** The table of contents, in document order. The heading elements carry these ids. */
|
|
2879
|
+
readonly entries: ReadonlyArray<ScrollspyEntry>;
|
|
2880
|
+
/**
|
|
2881
|
+
* How far below the scrollport's top edge a heading counts as current, in px.
|
|
2882
|
+
* A host with a sticky header passes its height. @default 0
|
|
2883
|
+
*/
|
|
2884
|
+
readonly offset?: number | undefined;
|
|
2885
|
+
/**
|
|
2886
|
+
* The scroll container; the document by default. Read once, when `attach`
|
|
2887
|
+
* runs, so a host that swaps containers attaches again.
|
|
2888
|
+
*/
|
|
2889
|
+
readonly root?: (() => HTMLElement | undefined) | undefined;
|
|
2890
|
+
/** The active id changed. */
|
|
2891
|
+
readonly onChange?: ((id: string | undefined) => void) | undefined;
|
|
2892
|
+
}
|
|
2893
|
+
export interface ScrollspyNavProps {
|
|
2894
|
+
/** The navigation landmark's name; the host overrides it after the spread to translate it. */
|
|
2895
|
+
readonly "aria-label": string;
|
|
2896
|
+
}
|
|
2897
|
+
export interface ScrollspyListProps {
|
|
2898
|
+
readonly role: "list";
|
|
2899
|
+
}
|
|
2900
|
+
export interface ScrollspyItemProps {
|
|
2901
|
+
readonly role: "listitem";
|
|
2902
|
+
}
|
|
2903
|
+
export interface ScrollspyLinkProps {
|
|
2904
|
+
readonly href: string;
|
|
2905
|
+
readonly "aria-current": "location" | undefined;
|
|
2906
|
+
}
|
|
2907
|
+
export interface Scrollspy {
|
|
2908
|
+
/** The current entry's id; `undefined` only when there are no entries. */
|
|
2909
|
+
readonly active: Accessor<string | undefined>;
|
|
2910
|
+
readonly navProps: ScrollspyNavProps;
|
|
2911
|
+
readonly listProps: ScrollspyListProps;
|
|
2912
|
+
readonly itemProps: (id: string) => ScrollspyItemProps;
|
|
2913
|
+
readonly linkProps: (id: string) => ScrollspyLinkProps;
|
|
2914
|
+
/**
|
|
2915
|
+
* DOM wiring: one passive `scroll` listener on the root, which measures the
|
|
2916
|
+
* headings. Call it once mounted and call the teardown it returns on cleanup.
|
|
2917
|
+
*/
|
|
2918
|
+
readonly attach: () => () => void;
|
|
2919
|
+
}
|
|
2920
|
+
/**
|
|
2921
|
+
* A scrollspy: a table of contents whose current entry is the last heading that
|
|
2922
|
+
* has reached the top of the scrollport, marked `aria-current="location"` on
|
|
2923
|
+
* its link (and nowhere else: exactly one link is current).
|
|
2924
|
+
*
|
|
2925
|
+
* **No APG pattern exists for a scrollspy**, so there is no Quint spec and no
|
|
2926
|
+
* ported scenarios: the contract is WAI-ARIA (`aria-current="location"`, the
|
|
2927
|
+
* `list` / `listitem` roles, the navigation landmark and its name) and HTML,
|
|
2928
|
+
* and `test/scrollspy.browser.test.tsx` is its oracle.
|
|
2929
|
+
*
|
|
2930
|
+
* **A scroll listener, not an `IntersectionObserver`.** "The last heading at or
|
|
2931
|
+
* above the offset" is a total order over every heading, while an observer only
|
|
2932
|
+
* reports what crossed its band: a band observer has to rebuild the order from
|
|
2933
|
+
* its enter/leave state, and it misreports after a jump (an anchor click, a
|
|
2934
|
+
* restored scroll position). The listener is passive, the measurements are
|
|
2935
|
+
* reads only (no writes, so no layout thrash), and a docs page has a dozen
|
|
2936
|
+
* headings — the same cost as the listener the page already keeps.
|
|
2937
|
+
*
|
|
2938
|
+
* Smooth scrolling is the host's (`scroll-behavior: smooth`): the links are
|
|
2939
|
+
* plain `#id` targets and nothing here moves the scrollport, so no
|
|
2940
|
+
* `prefers-reduced-motion` branch is needed either.
|
|
2941
|
+
*/
|
|
2942
|
+
export declare const createScrollspy: (options: ScrollspyOptions) => Scrollspy;
|
|
2604
2943
|
export type SliderOrientation = "horizontal" | "vertical";
|
|
2605
2944
|
export type SliderDirection = "ltr" | "rtl";
|
|
2606
2945
|
export interface SliderOptions {
|
|
@@ -2615,6 +2954,11 @@ export interface SliderOptions {
|
|
|
2615
2954
|
readonly defaultValue?: number;
|
|
2616
2955
|
readonly orientation?: SliderOrientation;
|
|
2617
2956
|
readonly direction?: SliderDirection;
|
|
2957
|
+
/**
|
|
2958
|
+
* aria-disabled: the thumb stays the tab stop (the APG pattern's focus
|
|
2959
|
+
* contract) but a key or a rail press changes nothing.
|
|
2960
|
+
*/
|
|
2961
|
+
readonly disabled?: boolean;
|
|
2618
2962
|
/** Base for the generated thumb id. */
|
|
2619
2963
|
readonly id?: string;
|
|
2620
2964
|
/**
|
|
@@ -2631,6 +2975,7 @@ export interface SliderThumbProps {
|
|
|
2631
2975
|
readonly "aria-valuemax": string;
|
|
2632
2976
|
readonly "aria-valuetext": string;
|
|
2633
2977
|
readonly "aria-orientation": SliderOrientation;
|
|
2978
|
+
readonly "aria-disabled": "true" | undefined;
|
|
2634
2979
|
readonly tabindex: number;
|
|
2635
2980
|
readonly onFocus: () => void;
|
|
2636
2981
|
readonly onBlur: () => void;
|
|
@@ -2668,6 +3013,11 @@ export interface Slider {
|
|
|
2668
3013
|
* pass `value` + `onValueChange` to own the value. A key or rail press is a
|
|
2669
3014
|
* request the host may ignore.
|
|
2670
3015
|
*
|
|
3016
|
+
* `disabled` is a widget-level extension of the pattern (the spec lists it
|
|
3017
|
+
* under its deliberate omissions): the thumb keeps its tab stop and announces
|
|
3018
|
+
* `aria-disabled`, and every key and rail press is dropped before it becomes a
|
|
3019
|
+
* request.
|
|
3020
|
+
*
|
|
2671
3021
|
* ponytail: dragging (pointer capture/move) is not modelled — the spec
|
|
2672
3022
|
* deliberately only fixes a discrete rail press.
|
|
2673
3023
|
*/
|
|
@@ -2752,6 +3102,11 @@ export interface SpinbuttonOptions {
|
|
|
2752
3102
|
readonly defaultValue?: number;
|
|
2753
3103
|
/** Wrap around at the bounds instead of clamping. */
|
|
2754
3104
|
readonly wrap?: boolean;
|
|
3105
|
+
/**
|
|
3106
|
+
* aria-disabled plus the native `disabled` on the text field: keys, typing
|
|
3107
|
+
* and both steppers change nothing, and both steppers announce `aria-disabled`.
|
|
3108
|
+
*/
|
|
3109
|
+
readonly disabled?: boolean;
|
|
2755
3110
|
/** Base for the generated input / button ids. */
|
|
2756
3111
|
readonly id?: string;
|
|
2757
3112
|
/**
|
|
@@ -2768,6 +3123,8 @@ export interface SpinbuttonInputProps {
|
|
|
2768
3123
|
readonly "aria-valuemax": string;
|
|
2769
3124
|
readonly "aria-valuetext": string;
|
|
2770
3125
|
readonly "aria-invalid": "true" | undefined;
|
|
3126
|
+
readonly "aria-disabled": "true" | undefined;
|
|
3127
|
+
readonly disabled: boolean;
|
|
2771
3128
|
readonly tabindex: number;
|
|
2772
3129
|
readonly value: string;
|
|
2773
3130
|
readonly onFocus: () => void;
|
|
@@ -2823,6 +3180,12 @@ export interface Spinbutton {
|
|
|
2823
3180
|
* stepper buttons, Enter and blur are requests the host may ignore; typing and
|
|
2824
3181
|
* Escape change no committed value.
|
|
2825
3182
|
*
|
|
3183
|
+
* `disabled` is a widget-level extension of the pattern (the spec models the
|
|
3184
|
+
* steppers' bound only, and lists this under its deliberate omissions): the
|
|
3185
|
+
* field announces `aria-disabled` and carries the native `disabled`, every key,
|
|
3186
|
+
* typed draft and both steppers are dropped before they become a request, and
|
|
3187
|
+
* neither stepper offers itself.
|
|
3188
|
+
*
|
|
2826
3189
|
* ponytail: commit/revert on Enter/blur/Escape is the common convention, not
|
|
2827
3190
|
* APG text.
|
|
2828
3191
|
*/
|
|
@@ -3194,6 +3557,26 @@ export interface TabsOptions {
|
|
|
3194
3557
|
* current value. Uncontrolled it is accepted; controlled the host may ignore it.
|
|
3195
3558
|
*/
|
|
3196
3559
|
readonly onValueChange?: (index: number) => void;
|
|
3560
|
+
/**
|
|
3561
|
+
* Close buttons and the Delete key (default false): the host renders
|
|
3562
|
+
* `closeProps` and removes the tab from `onClose`. The last tab is never
|
|
3563
|
+
* asked to close.
|
|
3564
|
+
*/
|
|
3565
|
+
readonly closable?: boolean | undefined;
|
|
3566
|
+
/**
|
|
3567
|
+
* Inline rename (default false): a double click on a tab starts it, Enter /
|
|
3568
|
+
* blur commit and Escape reverts; the host renames from `onRename`.
|
|
3569
|
+
*/
|
|
3570
|
+
readonly renamable?: boolean | undefined;
|
|
3571
|
+
/**
|
|
3572
|
+
* A tab's label: the close / rename button names and the starting draft of a
|
|
3573
|
+
* rename, read lazily (a getter is fine) so a rename follows.
|
|
3574
|
+
*/
|
|
3575
|
+
readonly label?: ((index: number) => string | undefined) | undefined;
|
|
3576
|
+
/** A request to remove a tab; the primitive does not own the list. */
|
|
3577
|
+
readonly onClose?: ((index: number) => void) | undefined;
|
|
3578
|
+
/** A request to rename a tab with a non-empty label. */
|
|
3579
|
+
readonly onRename?: ((index: number, label: string) => void) | undefined;
|
|
3197
3580
|
}
|
|
3198
3581
|
/** Spread onto the element with `role=tab` (a native `<button>`). */
|
|
3199
3582
|
export interface TabProps {
|
|
@@ -3221,21 +3604,63 @@ export interface TabpanelProps {
|
|
|
3221
3604
|
readonly tabindex: 0 | undefined;
|
|
3222
3605
|
readonly hidden: boolean;
|
|
3223
3606
|
}
|
|
3607
|
+
/**
|
|
3608
|
+
* Spread onto the tab's close button. `tabindex=-1` keeps it out of the
|
|
3609
|
+
* tablist's tab order (a tablist's stops are its tabs); the Delete key is the
|
|
3610
|
+
* keyboard path.
|
|
3611
|
+
*/
|
|
3612
|
+
export interface TabCloseProps {
|
|
3613
|
+
readonly "aria-label": string;
|
|
3614
|
+
readonly tabindex: -1;
|
|
3615
|
+
readonly onClick: () => void;
|
|
3616
|
+
}
|
|
3617
|
+
/** Spread onto the inline `<input>` a renaming tab renders instead of its label. */
|
|
3618
|
+
export interface TabRenameInputProps {
|
|
3619
|
+
readonly type: "text";
|
|
3620
|
+
readonly value: string;
|
|
3621
|
+
readonly "aria-label": string;
|
|
3622
|
+
readonly onInput: (event: InputEvent) => void;
|
|
3623
|
+
readonly onKeyDown: (event: KeyboardEvent) => void;
|
|
3624
|
+
readonly onBlur: () => void;
|
|
3625
|
+
}
|
|
3626
|
+
/** The rename part of one tab: put `onDblClick` on the tab, `inputProps` in it. */
|
|
3627
|
+
export interface TabRenameProps {
|
|
3628
|
+
readonly onDblClick: () => void;
|
|
3629
|
+
/** Whether this tab is the one being renamed. */
|
|
3630
|
+
readonly editing: Accessor<boolean>;
|
|
3631
|
+
/** Start renaming `index` (e.g. a tab the host has just added). */
|
|
3632
|
+
readonly begin: (index: number) => void;
|
|
3633
|
+
/** Commit `label` to the tab being edited; an empty label cancels. */
|
|
3634
|
+
readonly commit: (label: string) => void;
|
|
3635
|
+
readonly cancel: () => void;
|
|
3636
|
+
readonly inputProps: TabRenameInputProps;
|
|
3637
|
+
}
|
|
3224
3638
|
export interface Tabs {
|
|
3639
|
+
/** The tab count; read from `items` on every read, so a host that shrinks its list stays in step. */
|
|
3225
3640
|
readonly items: number;
|
|
3226
3641
|
readonly selected: Accessor<number>;
|
|
3227
3642
|
/** Tab holding (or last holding) DOM focus; -1 when focus is outside. */
|
|
3228
3643
|
readonly focused: Accessor<number>;
|
|
3644
|
+
/** The tab being renamed inline, `null` when none. */
|
|
3645
|
+
readonly editing: Accessor<number | null>;
|
|
3229
3646
|
/** Programmatic selection request; ignores disabled tabs. */
|
|
3230
3647
|
readonly select: (index: number) => void;
|
|
3231
3648
|
/** Programmatic selection request (may point at a disabled tab, like base-ui). */
|
|
3232
3649
|
readonly setSelected: (index: number) => void;
|
|
3233
3650
|
/** Programmatic focus (does not select). */
|
|
3234
3651
|
readonly focusTab: (index: number) => void;
|
|
3652
|
+
/**
|
|
3653
|
+
* The tab that takes focus once `onClose(index)`'s removal lands: the tab
|
|
3654
|
+
* that slid into `index`, or the previous one when the last tab closed. Call
|
|
3655
|
+
* it before the host removes the tab.
|
|
3656
|
+
*/
|
|
3657
|
+
readonly focusAfterClose: (index: number) => number;
|
|
3235
3658
|
readonly keyDown: (key: string) => void;
|
|
3236
3659
|
readonly tablistProps: TablistProps;
|
|
3237
3660
|
readonly tabProps: (index: number) => TabProps;
|
|
3238
3661
|
readonly tabpanelProps: (index: number) => TabpanelProps;
|
|
3662
|
+
readonly closeProps: (index: number) => TabCloseProps;
|
|
3663
|
+
readonly renameProps: (index: number) => TabRenameProps;
|
|
3239
3664
|
}
|
|
3240
3665
|
/**
|
|
3241
3666
|
* APG Tabs (https://www.w3.org/WAI/ARIA/apg/patterns/tabs/): state plus prop
|
|
@@ -3250,6 +3675,14 @@ export interface Tabs {
|
|
|
3250
3675
|
* Request/commit (map ticket `rt1p`): uncontrolled by default
|
|
3251
3676
|
* (`defaultSelected`); pass `value` + `onValueChange` to own the selection.
|
|
3252
3677
|
* Programmatic methods (`select`, `setSelected`, `keyDown`) are requests too.
|
|
3678
|
+
*
|
|
3679
|
+
* Close and rename are an **extension** (bean `cssints-gdv2`): the APG spec
|
|
3680
|
+
* models a `deletable` flavour but neither a callback nor a rename, and this
|
|
3681
|
+
* primitive owns no list. `closable` / `renamable` turn on the props; `onClose`
|
|
3682
|
+
* / `onRename` ask the host, which removes or renames. Focus after a close goes
|
|
3683
|
+
* to the tab that slid into the closed one's place, or to the previous tab when
|
|
3684
|
+
* the last one closed; closing the last remaining tab is refused, as the spec
|
|
3685
|
+
* refuses an empty tablist.
|
|
3253
3686
|
*/
|
|
3254
3687
|
export declare const createTabs: (options?: TabsOptions) => Tabs;
|
|
3255
3688
|
/**
|
|
@@ -3267,10 +3700,16 @@ export declare const createTabs: (options?: TabsOptions) => Tabs;
|
|
|
3267
3700
|
* may ignore. The zoom window (`viewStart`/`viewEnd`) is not the controlled
|
|
3268
3701
|
* value and stays uncontrolled.
|
|
3269
3702
|
*
|
|
3270
|
-
*
|
|
3271
|
-
*
|
|
3272
|
-
* `
|
|
3273
|
-
*
|
|
3703
|
+
* The fixed 5-span tree is the demo fixture (the differential walk's oracle).
|
|
3704
|
+
* A real consumer passes `rows`: span data on one shared axis, laid out by
|
|
3705
|
+
* `barProps` as percentages of the rows' extent. No roles are borrowed from
|
|
3706
|
+
* APG: a trace waterfall is a list of spans in time, so the rows mode is
|
|
3707
|
+
* `role="list"` / `role="listitem"` (with `aria-level` for the depth) and the
|
|
3708
|
+
* current row is `aria-current="true"`, which every element may carry.
|
|
3709
|
+
*
|
|
3710
|
+
* Roving tabindex only — a consumer must call `focus()` on the row whose
|
|
3711
|
+
* `tabindex` is 0 after arrow navigation (the tests' fixture does this with an
|
|
3712
|
+
* effect; the rows mode's span rows are reached by tabbing to the current one).
|
|
3274
3713
|
*/
|
|
3275
3714
|
/** One span row: a fraction of the parent span on a shared 0..1 axis. */
|
|
3276
3715
|
export interface TimelineRow {
|
|
@@ -3353,12 +3792,90 @@ export interface Timeline {
|
|
|
3353
3792
|
readonly rowProps: (id: number) => TimelineRowProps;
|
|
3354
3793
|
readonly rulerProps: TimelineRulerProps;
|
|
3355
3794
|
}
|
|
3356
|
-
/**
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
*/
|
|
3361
|
-
|
|
3795
|
+
/** One span of a real trace: a point on a shared axis, labelled, at a depth. */
|
|
3796
|
+
export interface TimelineSpan {
|
|
3797
|
+
readonly id: string;
|
|
3798
|
+
readonly label: string;
|
|
3799
|
+
/** Offset on the axis, in the axis' unit; `end` is the same axis, not a duration. */
|
|
3800
|
+
readonly start: number;
|
|
3801
|
+
readonly end: number;
|
|
3802
|
+
/** Nesting level, 0 for a root span. */
|
|
3803
|
+
readonly depth?: number;
|
|
3804
|
+
readonly status?: "ok" | "error" | "unset";
|
|
3805
|
+
}
|
|
3806
|
+
export interface TimelineAxis {
|
|
3807
|
+
/** The unit the numbers are in; the ruler labels its ticks with it. */
|
|
3808
|
+
readonly unit?: "ms" | "us" | "s";
|
|
3809
|
+
}
|
|
3810
|
+
/** The rows mode: real span data, read lazily like every other option. */
|
|
3811
|
+
export interface TimelineRowsOptions {
|
|
3812
|
+
readonly rows: ReadonlyArray<TimelineSpan>;
|
|
3813
|
+
readonly axis?: TimelineAxis | undefined;
|
|
3814
|
+
/** Controlled: the host owns the selection. */
|
|
3815
|
+
readonly selected?: string | undefined;
|
|
3816
|
+
/** A request to change the selection the host may ignore. */
|
|
3817
|
+
readonly onSelect?: ((id: string) => void) | undefined;
|
|
3818
|
+
}
|
|
3819
|
+
export interface TimelineTick {
|
|
3820
|
+
readonly value: number;
|
|
3821
|
+
readonly left: number;
|
|
3822
|
+
readonly label: string;
|
|
3823
|
+
}
|
|
3824
|
+
export interface TimelineListProps {
|
|
3825
|
+
readonly role: "list";
|
|
3826
|
+
readonly "aria-label": string;
|
|
3827
|
+
}
|
|
3828
|
+
export interface TimelineSpanRowProps {
|
|
3829
|
+
readonly role: "listitem";
|
|
3830
|
+
/** `depth + 1`: the nesting a flat list cannot show in the DOM. */
|
|
3831
|
+
readonly "aria-level": number;
|
|
3832
|
+
readonly "aria-current": "true" | undefined;
|
|
3833
|
+
/** A styling hook: the status the span ended with. */
|
|
3834
|
+
readonly "data-status": "ok" | "error" | "unset";
|
|
3835
|
+
readonly tabindex: number;
|
|
3836
|
+
readonly onClick: () => void;
|
|
3837
|
+
readonly onKeyDown: (event: KeyboardEvent) => void;
|
|
3838
|
+
}
|
|
3839
|
+
export interface TimelineBarProps {
|
|
3840
|
+
readonly "aria-hidden": "true";
|
|
3841
|
+
/** `left` / `width` are percentages of the extent; `--depth` is the row's depth. */
|
|
3842
|
+
readonly style: {
|
|
3843
|
+
readonly left: string;
|
|
3844
|
+
readonly width: string;
|
|
3845
|
+
readonly "--depth": string;
|
|
3846
|
+
};
|
|
3847
|
+
}
|
|
3848
|
+
export interface TimelineLabelProps {
|
|
3849
|
+
/** `--depth`, so the host indents the label (`padding-inline-start: calc(var(--depth) * 1rem)`) instead of nesting it. */
|
|
3850
|
+
readonly style: {
|
|
3851
|
+
readonly "--depth": string;
|
|
3852
|
+
};
|
|
3853
|
+
}
|
|
3854
|
+
export interface TimelineRulerRowsProps {
|
|
3855
|
+
readonly role: "presentation";
|
|
3856
|
+
readonly "aria-hidden": "true";
|
|
3857
|
+
/** Ticks of a nice step across the extent; render them as children. */
|
|
3858
|
+
readonly ticks: ReadonlyArray<TimelineTick>;
|
|
3859
|
+
}
|
|
3860
|
+
export interface TimelineRows {
|
|
3861
|
+
/** `{ start, end }` across the rows; `{ 0, 0 }` when there are none. */
|
|
3862
|
+
readonly extent: () => {
|
|
3863
|
+
readonly start: number;
|
|
3864
|
+
readonly end: number;
|
|
3865
|
+
};
|
|
3866
|
+
readonly depthOf: (id: string) => number;
|
|
3867
|
+
readonly listProps: TimelineListProps;
|
|
3868
|
+
readonly rowProps: (id: string) => TimelineSpanRowProps;
|
|
3869
|
+
readonly barProps: (id: string) => TimelineBarProps;
|
|
3870
|
+
readonly labelProps: (id: string) => TimelineLabelProps;
|
|
3871
|
+
readonly rulerProps: TimelineRulerRowsProps;
|
|
3872
|
+
/** Programmatic selection request; a controlled host may ignore it. */
|
|
3873
|
+
readonly select: (id: string) => void;
|
|
3874
|
+
readonly selectedId: Accessor<string | undefined>;
|
|
3875
|
+
}
|
|
3876
|
+
/** The demo five-span tree (`rows` absent) or real span data (`rows` given). */
|
|
3877
|
+
export declare function createTimeline(options: TimelineRowsOptions): TimelineRows;
|
|
3878
|
+
export declare function createTimeline(options?: TimelineOptions): Timeline;
|
|
3362
3879
|
/** A toast's urgency; `error` is the only tone that announces assertively. */
|
|
3363
3880
|
export type ToastTone = "info" | "success" | "error";
|
|
3364
3881
|
export interface ToastOptions {
|