@cssints/headless 0.18.0 → 0.20.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 +190 -2
- package/dist/index.d.mts +768 -236
- package/dist/index.mjs +1417 -507
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -1030,6 +1030,94 @@ export interface Checkbox {
|
|
|
1030
1030
|
* pass `value` + `onValueChange` to own the state.
|
|
1031
1031
|
*/
|
|
1032
1032
|
export declare const createCheckbox: (options?: CheckboxOptions) => Checkbox;
|
|
1033
|
+
export interface FieldOptions {
|
|
1034
|
+
/** Base of every id the field makes (`<id>-control`, `-label`, `-hint`, `-error`). */
|
|
1035
|
+
readonly id?: string | undefined;
|
|
1036
|
+
readonly required?: boolean | undefined;
|
|
1037
|
+
/**
|
|
1038
|
+
* `true` marks the control invalid; a string does too and is the error message
|
|
1039
|
+
* (unless `error` is set). `false` forces valid: an `error` is then not shown,
|
|
1040
|
+
* so a host can pass `touched() && failed()` and keep the message ready.
|
|
1041
|
+
* Left out, the field is invalid exactly when it has an error message.
|
|
1042
|
+
*/
|
|
1043
|
+
readonly invalid?: boolean | string | undefined;
|
|
1044
|
+
/** Help text; absent (or empty) means the hint element is hidden and not referenced. */
|
|
1045
|
+
readonly hint?: string | undefined;
|
|
1046
|
+
/** The error message; absent (or empty) means no error element. */
|
|
1047
|
+
readonly error?: string | undefined;
|
|
1048
|
+
/**
|
|
1049
|
+
* Ids of elements the host owns, appended to `aria-describedby` after the
|
|
1050
|
+
* field's own hint and error: a form-level error that belongs to several
|
|
1051
|
+
* controls ("the passwords do not match" names both). One id or a list;
|
|
1052
|
+
* empty strings are dropped, and the attribute is absent when nothing is left.
|
|
1053
|
+
* The host keeps that element rendered, and decides whether it is hidden.
|
|
1054
|
+
*/
|
|
1055
|
+
readonly describedby?: string | readonly string[] | undefined;
|
|
1056
|
+
}
|
|
1057
|
+
/** Spread onto the form control (`input`, `textarea`, `select`, or a custom widget). */
|
|
1058
|
+
export interface FieldControlProps {
|
|
1059
|
+
readonly id: string;
|
|
1060
|
+
/** The ids of the hint and the error that are shown, hint first, then the host's `describedby`; absent when there are none. */
|
|
1061
|
+
readonly "aria-describedby": string | undefined;
|
|
1062
|
+
readonly "aria-invalid": "true" | undefined;
|
|
1063
|
+
/** For a control that is not native (a native one has `required`). */
|
|
1064
|
+
readonly "aria-required": "true" | undefined;
|
|
1065
|
+
readonly required: true | undefined;
|
|
1066
|
+
}
|
|
1067
|
+
export interface FieldLabelProps {
|
|
1068
|
+
readonly id: string;
|
|
1069
|
+
readonly for: string;
|
|
1070
|
+
}
|
|
1071
|
+
/** Spread onto the hint and the error element; render both, always. */
|
|
1072
|
+
export interface FieldMessageProps {
|
|
1073
|
+
readonly id: string;
|
|
1074
|
+
readonly hidden: boolean;
|
|
1075
|
+
}
|
|
1076
|
+
/** The error element also announces itself: `role="alert"` while it is shown. */
|
|
1077
|
+
export interface FieldErrorProps extends FieldMessageProps {
|
|
1078
|
+
readonly role: "alert" | undefined;
|
|
1079
|
+
}
|
|
1080
|
+
export interface Field {
|
|
1081
|
+
readonly invalid: () => boolean;
|
|
1082
|
+
/** The hint, or `undefined` when there is none. */
|
|
1083
|
+
readonly hint: () => string | undefined;
|
|
1084
|
+
/** The error shown: `undefined` unless the field is invalid and has a message. */
|
|
1085
|
+
readonly error: () => string | undefined;
|
|
1086
|
+
/**
|
|
1087
|
+
* The ids `aria-describedby` lists now: hint, then error (never an id that is
|
|
1088
|
+
* hidden), then the host's `describedby`.
|
|
1089
|
+
*/
|
|
1090
|
+
readonly describedIds: () => readonly string[];
|
|
1091
|
+
readonly controlProps: FieldControlProps;
|
|
1092
|
+
readonly labelProps: FieldLabelProps;
|
|
1093
|
+
readonly hintProps: FieldMessageProps;
|
|
1094
|
+
readonly errorProps: FieldErrorProps;
|
|
1095
|
+
}
|
|
1096
|
+
/**
|
|
1097
|
+
* The ARIA contract of a form field: a label, a control, a hint and an error.
|
|
1098
|
+
* There is no APG pattern for it (APG has no "form field"; the nearest is the
|
|
1099
|
+
* label and description rules of the accessible name computation), so the
|
|
1100
|
+
* contract comes from WAI-ARIA 1.2 (`aria-describedby`, `aria-invalid`,
|
|
1101
|
+
* `aria-required`) and HTML (`label[for]`, `required`), and from the
|
|
1102
|
+
* majority of Base UI Field, react-aria TextField and ariakit FormControl,
|
|
1103
|
+
* which list the description and the error message together. It is not checked
|
|
1104
|
+
* against a Quint spec; `test/field.browser.test.tsx` is its oracle.
|
|
1105
|
+
*
|
|
1106
|
+
* Decisions: the error does not replace the hint, both are listed, hint first
|
|
1107
|
+
* (the hint is what tells the user how to fix the error); an error implies
|
|
1108
|
+
* invalid and `invalid: false` overrides it; a hidden message is never
|
|
1109
|
+
* referenced, so `aria-describedby` has no dangling id. The error element is
|
|
1110
|
+
* `role="alert"` only while it is shown (a hidden element announces nothing, and
|
|
1111
|
+
* one that appears is announced); the hint is never an alert.
|
|
1112
|
+
*
|
|
1113
|
+
* `describedby` lists host-owned ids after the field's own: one refusal that
|
|
1114
|
+
* belongs to two controls is one form-level element (with its own `role`), named
|
|
1115
|
+
* by both. The field cannot know whether that element is shown: the host
|
|
1116
|
+
* passes the id only while it is.
|
|
1117
|
+
*
|
|
1118
|
+
* Options are read when an attribute is read: pass getters, never destructure.
|
|
1119
|
+
*/
|
|
1120
|
+
export declare const createField: (options?: FieldOptions) => Field;
|
|
1033
1121
|
export type CheckboxGroupFocus = "none" | "parent" | "c0" | "c1" | "c2";
|
|
1034
1122
|
export type ParentChecked = "true" | "false" | "mixed";
|
|
1035
1123
|
export interface CheckboxGroupOptions<T = unknown> {
|
|
@@ -1043,6 +1131,22 @@ export interface CheckboxGroupOptions<T = unknown> {
|
|
|
1043
1131
|
readonly parent?: boolean;
|
|
1044
1132
|
/** Remember the last mixed combination for the controller's third activation. */
|
|
1045
1133
|
readonly remember?: boolean;
|
|
1134
|
+
/** Base of the group's ids (`<id>-control`, `-error`); a unique one when absent. */
|
|
1135
|
+
readonly id?: string;
|
|
1136
|
+
/** The group's name, as `aria-label`: a `role="group"` needs one. */
|
|
1137
|
+
readonly label?: string | undefined;
|
|
1138
|
+
/** The id of the element that names the group (`aria-labelledby`); wins over `label`. */
|
|
1139
|
+
readonly labelledby?: string | undefined;
|
|
1140
|
+
/** Ids of elements the host owns that describe the group, after the group's own error id. */
|
|
1141
|
+
readonly describedby?: string | undefined;
|
|
1142
|
+
/**
|
|
1143
|
+
* `true` marks the group invalid; a string does too and is the error message
|
|
1144
|
+
* (unless `error` is set). `false` forces valid. Left out, the group is invalid
|
|
1145
|
+
* exactly when it has an error message. Same contract as `createField`.
|
|
1146
|
+
*/
|
|
1147
|
+
readonly invalid?: boolean | string | undefined;
|
|
1148
|
+
/** The error message; absent (or empty) means no error element. */
|
|
1149
|
+
readonly error?: string | undefined;
|
|
1046
1150
|
/**
|
|
1047
1151
|
* A request to change the checked subset, called when the intent differs from
|
|
1048
1152
|
* the current value. Uncontrolled it is accepted; controlled the host may
|
|
@@ -1070,9 +1174,18 @@ export interface CheckboxGroupChildProps {
|
|
|
1070
1174
|
readonly onFocus: () => void;
|
|
1071
1175
|
}
|
|
1072
1176
|
export interface CheckboxGroupProps {
|
|
1177
|
+
/** The group element: a host's label can name it, and a controller can `aria-controls` it. */
|
|
1178
|
+
readonly id: string;
|
|
1073
1179
|
readonly role: "group";
|
|
1180
|
+
readonly "aria-label": string | undefined;
|
|
1181
|
+
readonly "aria-labelledby": string | undefined;
|
|
1182
|
+
/** The error id while one is shown, then the host's `describedby`. */
|
|
1183
|
+
readonly "aria-describedby": string | undefined;
|
|
1184
|
+
readonly "aria-invalid": "true" | undefined;
|
|
1074
1185
|
readonly onKeyDown: (event: KeyboardEvent) => void;
|
|
1075
1186
|
}
|
|
1187
|
+
/** The error element: render it always, it is `hidden` while there is no error. */
|
|
1188
|
+
export type CheckboxGroupErrorProps = FieldErrorProps;
|
|
1076
1189
|
export interface CheckboxGroup<T = unknown> {
|
|
1077
1190
|
readonly children: Accessor<ReadonlyArray<boolean>>;
|
|
1078
1191
|
readonly memory: Accessor<ReadonlyArray<boolean>>;
|
|
@@ -1080,6 +1193,14 @@ export interface CheckboxGroup<T = unknown> {
|
|
|
1080
1193
|
readonly focused: Accessor<CheckboxGroupFocus>;
|
|
1081
1194
|
readonly parentChecked: Accessor<ParentChecked>;
|
|
1082
1195
|
readonly checkedValues: () => ReadonlyArray<T>;
|
|
1196
|
+
/** The error shown: `undefined` unless the group is invalid and has a message. */
|
|
1197
|
+
readonly error: () => string | undefined;
|
|
1198
|
+
readonly invalid: () => boolean;
|
|
1199
|
+
/**
|
|
1200
|
+
* The ids `aria-describedby` lists now: the error (never while it is hidden),
|
|
1201
|
+
* then the host's `describedby`.
|
|
1202
|
+
*/
|
|
1203
|
+
readonly describedIds: () => readonly string[];
|
|
1083
1204
|
readonly click: (target: CheckboxGroupFocus) => void;
|
|
1084
1205
|
readonly keySpace: () => void;
|
|
1085
1206
|
readonly keyTab: () => void;
|
|
@@ -1088,6 +1209,7 @@ export interface CheckboxGroup<T = unknown> {
|
|
|
1088
1209
|
readonly setRemember: (value: boolean) => void;
|
|
1089
1210
|
readonly setChildren: (children: ReadonlyArray<boolean>) => void;
|
|
1090
1211
|
readonly groupProps: CheckboxGroupProps;
|
|
1212
|
+
readonly errorProps: CheckboxGroupErrorProps;
|
|
1091
1213
|
readonly parentProps: CheckboxGroupParentProps;
|
|
1092
1214
|
readonly childProps: (index: 0 | 1 | 2) => CheckboxGroupChildProps;
|
|
1093
1215
|
}
|
|
@@ -1103,7 +1225,17 @@ export interface CheckboxGroup<T = unknown> {
|
|
|
1103
1225
|
* pass `value` + `onValueChange` to own the checked subset. The checked set is
|
|
1104
1226
|
* packed internally so it rides the scalar `createControllable`.
|
|
1105
1227
|
*
|
|
1106
|
-
*
|
|
1228
|
+
* The name, the description and the error are `createField`'s contract on the
|
|
1229
|
+
* `role="group"` element (`aria-label` / `aria-labelledby`, `aria-describedby`,
|
|
1230
|
+
* `aria-invalid`, an error element that is `role="alert"` while it is shown), so
|
|
1231
|
+
* a group reads like the fields around it. The group is not a `<label for>`
|
|
1232
|
+
* target: a host names a visible label element's id with `labelledby`, or passes
|
|
1233
|
+
* `label` without one.
|
|
1234
|
+
*
|
|
1235
|
+
* Behaviour is checked against `specs/apg/checkbox-group.qnt` by the bridge. That
|
|
1236
|
+
* model leaves labelling out on purpose ("shift+Tab, accessible names,
|
|
1237
|
+
* fieldset/legend or role=group labelling" in its header), so the field contract
|
|
1238
|
+
* is checked by plain assertions in `test/checkbox-group.browser.test.tsx`.
|
|
1107
1239
|
*/
|
|
1108
1240
|
export declare const createCheckboxGroup: <T = unknown>(options: CheckboxGroupOptions<T>) => CheckboxGroup<T>;
|
|
1109
1241
|
/**
|
|
@@ -1294,6 +1426,215 @@ export interface DialogModal {
|
|
|
1294
1426
|
* are requests the host may ignore.
|
|
1295
1427
|
*/
|
|
1296
1428
|
export declare const createDialogModal: (options?: DialogModalOptions) => DialogModal;
|
|
1429
|
+
export interface StatusOptions {
|
|
1430
|
+
/** Rendered on `statusProps`; generated when absent. */
|
|
1431
|
+
readonly id?: string | undefined;
|
|
1432
|
+
}
|
|
1433
|
+
/** Spread onto the `role=status` live region. Render it once, always. */
|
|
1434
|
+
export interface StatusProps {
|
|
1435
|
+
readonly id: string;
|
|
1436
|
+
readonly role: "status";
|
|
1437
|
+
readonly "aria-live": "polite";
|
|
1438
|
+
readonly "aria-atomic": "true";
|
|
1439
|
+
}
|
|
1440
|
+
export interface Status {
|
|
1441
|
+
/** The text currently shown, or `undefined` before the first `show`. */
|
|
1442
|
+
readonly message: Accessor<string | undefined>;
|
|
1443
|
+
/** Writes the message, replacing the previous one; nothing is queued. */
|
|
1444
|
+
readonly show: (text: string) => void;
|
|
1445
|
+
readonly clear: () => void;
|
|
1446
|
+
readonly statusProps: StatusProps;
|
|
1447
|
+
}
|
|
1448
|
+
/**
|
|
1449
|
+
* APG Alert's polite sibling: one `role=status` live region whose text the host
|
|
1450
|
+
* replaces ("Link copied", "Saved"). It is not a queue: a second message shows
|
|
1451
|
+
* in place of the first, so a burst reads as the last state, not as N alerts.
|
|
1452
|
+
* The region is rendered once and stays mounted, which is what makes a change
|
|
1453
|
+
* of its text announced.
|
|
1454
|
+
*
|
|
1455
|
+
* A status is never for errors: an error that needs attention now is
|
|
1456
|
+
* `createAlert` (`role=alert`, assertive), and a message with a title, a
|
|
1457
|
+
* description and a timeout is `createToast`.
|
|
1458
|
+
*/
|
|
1459
|
+
export declare const createStatus: (options?: StatusOptions) => Status;
|
|
1460
|
+
/** The HTML `autocomplete` tokens worth naming; the attribute also takes an arbitrary token. */
|
|
1461
|
+
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";
|
|
1462
|
+
export interface TextInputOptions extends FieldOptions {
|
|
1463
|
+
readonly type?: "text" | "email" | "tel" | "url" | "search" | "password" | undefined;
|
|
1464
|
+
/** The HTML token list: the browser's own autofill is the accessible path. */
|
|
1465
|
+
readonly autoComplete?: AutoCompleteToken | undefined;
|
|
1466
|
+
readonly spellCheck?: boolean | undefined;
|
|
1467
|
+
readonly autoCapitalize?: "none" | "sentences" | "words" | "characters" | undefined;
|
|
1468
|
+
readonly inputMode?: "none" | "text" | "tel" | "url" | "email" | "numeric" | "decimal" | "search" | undefined;
|
|
1469
|
+
readonly maxLength?: number | undefined;
|
|
1470
|
+
readonly readOnly?: boolean | undefined;
|
|
1471
|
+
readonly disabled?: boolean | undefined;
|
|
1472
|
+
readonly name?: string | undefined;
|
|
1473
|
+
readonly dir?: "ltr" | "rtl" | "auto" | undefined;
|
|
1474
|
+
readonly placeholder?: string | undefined;
|
|
1475
|
+
/** Change and input both land here; the host decides what a keystroke means. */
|
|
1476
|
+
readonly onValueChange?: ((value: string) => void) | undefined;
|
|
1477
|
+
}
|
|
1478
|
+
export interface TextAreaOptions extends TextInputOptions {
|
|
1479
|
+
readonly rows?: number | undefined;
|
|
1480
|
+
readonly wrap?: "soft" | "hard" | "off" | undefined;
|
|
1481
|
+
readonly resize?: "none" | "vertical" | "horizontal" | "both" | undefined;
|
|
1482
|
+
}
|
|
1483
|
+
export interface TextControlProps {
|
|
1484
|
+
readonly id: string;
|
|
1485
|
+
readonly "aria-describedby": string | undefined;
|
|
1486
|
+
readonly "aria-invalid": "true" | undefined;
|
|
1487
|
+
readonly "aria-required": "true" | undefined;
|
|
1488
|
+
readonly required: true | undefined;
|
|
1489
|
+
readonly type: TextInputOptions["type"];
|
|
1490
|
+
readonly autoComplete: AutoCompleteToken | undefined;
|
|
1491
|
+
readonly spellcheck: "true" | "false" | undefined;
|
|
1492
|
+
readonly autocapitalize: TextInputOptions["autoCapitalize"];
|
|
1493
|
+
readonly inputmode: TextInputOptions["inputMode"];
|
|
1494
|
+
readonly maxlength: number | undefined;
|
|
1495
|
+
readonly readOnly: true | undefined;
|
|
1496
|
+
readonly disabled: true | undefined;
|
|
1497
|
+
readonly name: string | undefined;
|
|
1498
|
+
readonly dir: "ltr" | "rtl" | "auto" | undefined;
|
|
1499
|
+
readonly placeholder: string | undefined;
|
|
1500
|
+
readonly onInput: (event: {
|
|
1501
|
+
currentTarget: {
|
|
1502
|
+
value: string;
|
|
1503
|
+
};
|
|
1504
|
+
}) => void;
|
|
1505
|
+
}
|
|
1506
|
+
export interface TextAreaControlProps extends TextControlProps {
|
|
1507
|
+
readonly rows: number | undefined;
|
|
1508
|
+
readonly wrap: TextAreaOptions["wrap"];
|
|
1509
|
+
readonly style: {
|
|
1510
|
+
readonly resize: "none" | "vertical" | "horizontal" | "both";
|
|
1511
|
+
} | undefined;
|
|
1512
|
+
}
|
|
1513
|
+
/** The field and the control, one object: every prop the element needs. */
|
|
1514
|
+
export interface TextInput extends Field {
|
|
1515
|
+
readonly controlProps: TextControlProps;
|
|
1516
|
+
}
|
|
1517
|
+
export interface TextArea extends Field {
|
|
1518
|
+
readonly controlProps: TextAreaControlProps;
|
|
1519
|
+
}
|
|
1520
|
+
/**
|
|
1521
|
+
* A text input with the field contract attached: label, hint, error (announced),
|
|
1522
|
+
* `aria-describedby` and `aria-invalid` come from `createField`; this adds the
|
|
1523
|
+
* props a host forgets — autofill tokens, spellcheck, inputmode, maxlength, the
|
|
1524
|
+
* disabled/readOnly pair. Platform: native (`input` is its own APG-equivalent;
|
|
1525
|
+
* no primitive re-implements text editing, which is why there is no key
|
|
1526
|
+
* handling here).
|
|
1527
|
+
*
|
|
1528
|
+
* Both `required` and `aria-required` are emitted: the native attribute is what
|
|
1529
|
+
* the browser enforces, the ARIA one is what a custom widget reading the same
|
|
1530
|
+
* props needs. A host that validates itself passes `noValidate` on the form
|
|
1531
|
+
* rather than dropping the attribute.
|
|
1532
|
+
*/
|
|
1533
|
+
export declare const createTextInput: (options?: TextInputOptions) => TextInput;
|
|
1534
|
+
/**
|
|
1535
|
+
* The same contract for a textarea, plus rows, wrap and resize. `resize` is a
|
|
1536
|
+
* style because CSS has no attribute for it; `none` is the value a layout that
|
|
1537
|
+
* owns its own height wants.
|
|
1538
|
+
*/
|
|
1539
|
+
export declare const createTextArea: (options?: TextAreaOptions) => TextArea;
|
|
1540
|
+
/** One command the host offers; `group` only picks the heading the host renders. */
|
|
1541
|
+
export interface PaletteCommand {
|
|
1542
|
+
readonly id: string;
|
|
1543
|
+
readonly label: string;
|
|
1544
|
+
readonly group?: string | undefined;
|
|
1545
|
+
readonly keywords?: ReadonlyArray<string> | undefined;
|
|
1546
|
+
readonly run: () => void | Promise<void>;
|
|
1547
|
+
}
|
|
1548
|
+
/** A command that survived the filter, in host order. */
|
|
1549
|
+
export interface PaletteResult {
|
|
1550
|
+
readonly id: string;
|
|
1551
|
+
readonly label: string;
|
|
1552
|
+
readonly group: string | undefined;
|
|
1553
|
+
}
|
|
1554
|
+
/** Every string the host puts on screen; override what you translate. */
|
|
1555
|
+
export interface CommandPaletteLabels {
|
|
1556
|
+
/** The dialog's name; `titleProps` heads it and the listbox is named by it. */
|
|
1557
|
+
readonly title: string;
|
|
1558
|
+
readonly placeholder: string;
|
|
1559
|
+
readonly empty: string;
|
|
1560
|
+
/** The result count the polite region announces, once per query. */
|
|
1561
|
+
readonly results: (count: number) => string;
|
|
1562
|
+
}
|
|
1563
|
+
export interface CommandPaletteOptions {
|
|
1564
|
+
readonly commands: ReadonlyArray<PaletteCommand>;
|
|
1565
|
+
/** Controlled: the host owns the open state and this is the only thing that changes it. */
|
|
1566
|
+
readonly open?: boolean | undefined;
|
|
1567
|
+
/** A request to change the open state, called when the intent differs. */
|
|
1568
|
+
readonly onOpenChange?: ((open: boolean) => void) | undefined;
|
|
1569
|
+
/** The `createHotkeys` chord `attach` binds; `"mod+k"` by default. */
|
|
1570
|
+
readonly hotkey?: string | undefined;
|
|
1571
|
+
readonly labels?: Partial<CommandPaletteLabels> | undefined;
|
|
1572
|
+
}
|
|
1573
|
+
export interface CommandPalette {
|
|
1574
|
+
readonly open: Accessor<boolean>;
|
|
1575
|
+
readonly openPalette: () => void;
|
|
1576
|
+
readonly closePalette: () => void;
|
|
1577
|
+
readonly query: Accessor<string>;
|
|
1578
|
+
readonly setQuery: (text: string) => void;
|
|
1579
|
+
/** Index into `results()`; `-1` = the search field holds the highlight. */
|
|
1580
|
+
readonly active: Accessor<number>;
|
|
1581
|
+
readonly setActive: (index: number) => void;
|
|
1582
|
+
readonly results: Accessor<ReadonlyArray<PaletteResult>>;
|
|
1583
|
+
readonly labels: CommandPaletteLabels;
|
|
1584
|
+
/** The live region's text; render it inside `statusProps`. */
|
|
1585
|
+
readonly status: Accessor<string | undefined>;
|
|
1586
|
+
readonly inputProps: TextControlProps & {
|
|
1587
|
+
readonly role: "searchbox";
|
|
1588
|
+
readonly value: string;
|
|
1589
|
+
readonly "aria-controls": string;
|
|
1590
|
+
readonly "aria-activedescendant": string | undefined;
|
|
1591
|
+
readonly onKeyDown: (event: KeyboardEvent) => void;
|
|
1592
|
+
};
|
|
1593
|
+
readonly listProps: {
|
|
1594
|
+
readonly id: string;
|
|
1595
|
+
readonly role: "listbox";
|
|
1596
|
+
readonly "aria-labelledby": string;
|
|
1597
|
+
/** A pointer click keeps the focus in the search field. */
|
|
1598
|
+
readonly onMouseDown: (event: MouseEvent) => void;
|
|
1599
|
+
};
|
|
1600
|
+
readonly optionProps: (id: string) => {
|
|
1601
|
+
readonly id: string;
|
|
1602
|
+
readonly role: "option";
|
|
1603
|
+
readonly "aria-selected": "true" | "false";
|
|
1604
|
+
readonly tabindex: -1;
|
|
1605
|
+
readonly onClick: () => void;
|
|
1606
|
+
readonly onMouseEnter: () => void;
|
|
1607
|
+
};
|
|
1608
|
+
readonly emptyProps: {
|
|
1609
|
+
readonly hidden: boolean;
|
|
1610
|
+
};
|
|
1611
|
+
readonly statusProps: StatusProps;
|
|
1612
|
+
readonly dialogProps: DialogContentProps;
|
|
1613
|
+
readonly backdropProps: DialogBackdropProps;
|
|
1614
|
+
readonly titleProps: {
|
|
1615
|
+
readonly id: string;
|
|
1616
|
+
};
|
|
1617
|
+
/** Runs the active command and closes the palette; a no-op with no highlight. */
|
|
1618
|
+
readonly runActive: () => void | Promise<void>;
|
|
1619
|
+
/** Binds the hotkey on the document; call once and call the teardown on cleanup. */
|
|
1620
|
+
readonly attach: () => () => void;
|
|
1621
|
+
}
|
|
1622
|
+
/**
|
|
1623
|
+
* A command palette: `createDialogModal` (a modal dialog, announced as one),
|
|
1624
|
+
* `createTextInput` (a `searchbox`, not a combobox — choosing runs a command,
|
|
1625
|
+
* it does not complete the field) and one filtered list. Focus stays in the
|
|
1626
|
+
* field; `aria-activedescendant` names the highlighted option, and ↓ on the
|
|
1627
|
+
* last / ↑ on the first return the highlight to the field. Home/End are menu
|
|
1628
|
+
* keys only while the field is empty, so the caret keeps them while typing.
|
|
1629
|
+
* Filtering is a plain case-insensitive substring match on the label and the
|
|
1630
|
+
* keywords, no fuzzy scoring: a palette is short and stable, and ranking
|
|
1631
|
+
* (recency, scopes) is a policy the host owns.
|
|
1632
|
+
*
|
|
1633
|
+
* The host's: rendering and grouping (the `group` field, headings, the CSS),
|
|
1634
|
+
* async commands (a rejected `run` is the host's to report), and any close
|
|
1635
|
+
* other than Escape / a command. `runActive` closes before it runs.
|
|
1636
|
+
*/
|
|
1637
|
+
export declare const createCommandPalette: (options: CommandPaletteOptions) => CommandPalette;
|
|
1297
1638
|
export interface ConfirmOptions {
|
|
1298
1639
|
readonly id?: string | undefined;
|
|
1299
1640
|
/** Called with `true` when a request opens the dialog and `false` when it settles. */
|
|
@@ -1497,6 +1838,106 @@ export interface Drawer {
|
|
|
1497
1838
|
* dismissal a non-modal drawer has); closing returns focus to the trigger.
|
|
1498
1839
|
*/
|
|
1499
1840
|
export declare const createDrawer: (options?: DrawerOptions) => Drawer;
|
|
1841
|
+
/** Why a file was refused: the `accept` grammar, or `maxSize`. */
|
|
1842
|
+
export type DropZoneRejectReason = "type" | "size";
|
|
1843
|
+
export interface DropZoneReject {
|
|
1844
|
+
readonly file: File;
|
|
1845
|
+
readonly reason: DropZoneRejectReason;
|
|
1846
|
+
}
|
|
1847
|
+
export interface DropZoneLabels {
|
|
1848
|
+
/** The zone's accessible name (`aria-label` on the `role=group` zone). */
|
|
1849
|
+
readonly zone?: string | undefined;
|
|
1850
|
+
/** The browse button's accessible name; render it visibly, or use it for an icon-only button. */
|
|
1851
|
+
readonly browse?: string | undefined;
|
|
1852
|
+
}
|
|
1853
|
+
export interface DropZoneOptions {
|
|
1854
|
+
/** The HTML `accept` grammar: extensions (`.png`) and MIME types, wildcards included (`image/*`). */
|
|
1855
|
+
readonly accept?: string | undefined;
|
|
1856
|
+
readonly multiple?: boolean | undefined;
|
|
1857
|
+
/** Bytes; a larger file is rejected with `"size"`. */
|
|
1858
|
+
readonly maxSize?: number | undefined;
|
|
1859
|
+
readonly disabled?: boolean | undefined;
|
|
1860
|
+
/** The files that passed, one call per batch; not called when the batch has none. */
|
|
1861
|
+
readonly onFiles: (files: File[]) => void;
|
|
1862
|
+
/** Each refused file, once, with the reason the host shows. */
|
|
1863
|
+
readonly onReject?: ((reject: DropZoneReject) => void) | undefined;
|
|
1864
|
+
readonly labels?: DropZoneLabels | undefined;
|
|
1865
|
+
}
|
|
1866
|
+
/** Spread onto the hidden `<input type=file>`; render it, always. */
|
|
1867
|
+
export interface DropZoneInputProps {
|
|
1868
|
+
/** The id the browse button clicks; generated. */
|
|
1869
|
+
readonly id: string;
|
|
1870
|
+
readonly type: "file";
|
|
1871
|
+
readonly accept: string | undefined;
|
|
1872
|
+
readonly multiple: true | undefined;
|
|
1873
|
+
readonly disabled: true | undefined;
|
|
1874
|
+
readonly hidden: true;
|
|
1875
|
+
readonly onChange: (event: {
|
|
1876
|
+
currentTarget: HTMLInputElement;
|
|
1877
|
+
}) => void;
|
|
1878
|
+
}
|
|
1879
|
+
/** Spread onto the drop target (any element). `dragging()` is the styling hook. */
|
|
1880
|
+
export interface DropZoneZoneProps {
|
|
1881
|
+
readonly role: "group";
|
|
1882
|
+
readonly "aria-label": string | undefined;
|
|
1883
|
+
readonly "aria-disabled": "true" | undefined;
|
|
1884
|
+
readonly onDragEnter: (event: DragEvent) => void;
|
|
1885
|
+
readonly onDragOver: (event: DragEvent) => void;
|
|
1886
|
+
readonly onDragLeave: (event: DragEvent) => void;
|
|
1887
|
+
readonly onDrop: (event: DragEvent) => void;
|
|
1888
|
+
}
|
|
1889
|
+
/** Spread onto a real `<button type="button">`: Enter and Space are the platform's. */
|
|
1890
|
+
export interface DropZoneBrowseProps {
|
|
1891
|
+
readonly type: "button";
|
|
1892
|
+
readonly disabled: true | undefined;
|
|
1893
|
+
readonly "aria-disabled": "true" | undefined;
|
|
1894
|
+
readonly "aria-label": string | undefined;
|
|
1895
|
+
readonly onClick: () => void;
|
|
1896
|
+
}
|
|
1897
|
+
/** Spread onto the live region; render it once, always. */
|
|
1898
|
+
export interface DropZoneStatusProps {
|
|
1899
|
+
readonly role: "status";
|
|
1900
|
+
readonly "aria-live": "polite";
|
|
1901
|
+
readonly "aria-atomic": "true";
|
|
1902
|
+
}
|
|
1903
|
+
export interface DropZone {
|
|
1904
|
+
/** A drag is over the zone; `dragleave` on a child does not clear it. */
|
|
1905
|
+
readonly dragging: Accessor<boolean>;
|
|
1906
|
+
/**
|
|
1907
|
+
* A default sentence for the last batch ("2 files, 1 rejected: too large"),
|
|
1908
|
+
* or `undefined` before one arrived. The host may write its own words from
|
|
1909
|
+
* the same counts.
|
|
1910
|
+
*/
|
|
1911
|
+
readonly message: Accessor<string | undefined>;
|
|
1912
|
+
readonly inputProps: DropZoneInputProps;
|
|
1913
|
+
readonly zoneProps: DropZoneZoneProps;
|
|
1914
|
+
readonly browseProps: DropZoneBrowseProps;
|
|
1915
|
+
readonly statusProps: DropZoneStatusProps;
|
|
1916
|
+
}
|
|
1917
|
+
/**
|
|
1918
|
+
* A file drop zone. There is no APG pattern and no Quint spec for one: the
|
|
1919
|
+
* contract is HTML's file input plus WAI-ARIA's live region, and
|
|
1920
|
+
* `test/dropzone.browser.test.tsx` is its oracle.
|
|
1921
|
+
*
|
|
1922
|
+
* Decisions:
|
|
1923
|
+
* - **The zone is never drag-only.** `browseProps` spreads onto a real
|
|
1924
|
+
* `<button type="button">` that clicks the hidden input, so the picker is the
|
|
1925
|
+
* keyboard path (Enter and Space are the platform's) and a pointer user can
|
|
1926
|
+
* click as well as drag. The drop handlers add nothing the button cannot do.
|
|
1927
|
+
* - **Validation happens once per file**, in the order type then size, and a
|
|
1928
|
+
* refused file reaches `onReject` with the reason the host shows. `accept` is
|
|
1929
|
+
* the HTML grammar (`.` extension, MIME type, `*`); `maxSize` is bytes.
|
|
1930
|
+
* - **Dropping a folder or a `DataTransfer` with no files is a no-op**: no
|
|
1931
|
+
* `onFiles`, no `onReject`, no message, and the previous announcement stays.
|
|
1932
|
+
* - **`dragleave` counts.** A child's `dragleave` bubbles to the zone, so the
|
|
1933
|
+
* state is a depth counter: it clears only when the drag leaves the zone
|
|
1934
|
+
* itself, or on a drop.
|
|
1935
|
+
* - **The zone is `role=group`** so `labels.zone` is exposed; `dragging()` is
|
|
1936
|
+
* the only drag state a skin reads (no `data-dragging`).
|
|
1937
|
+
* - `disabled` marks the zone `aria-disabled`, disables the input natively and
|
|
1938
|
+
* the browse button, and drops every drag before it becomes a batch.
|
|
1939
|
+
*/
|
|
1940
|
+
export declare const createDropZone: (options: DropZoneOptions) => DropZone;
|
|
1500
1941
|
/**
|
|
1501
1942
|
* Options are read lazily (getters); pass a component's `props`.
|
|
1502
1943
|
*/
|
|
@@ -1558,120 +1999,32 @@ export interface Feed {
|
|
|
1558
1999
|
readonly ctrlEnd: () => void;
|
|
1559
2000
|
readonly ctrlHome: () => void;
|
|
1560
2001
|
/** Scroll/position asked for more: `aria-busy` goes true while in flight. */
|
|
1561
|
-
readonly loadStart: () => void;
|
|
1562
|
-
/** A batch arrived; a request to grow the loaded count. */
|
|
1563
|
-
readonly loadFinish: (count: number) => void;
|
|
1564
|
-
readonly loadFail: () => void;
|
|
1565
|
-
readonly setSizeMode: (mode: "loaded" | "total") => void;
|
|
1566
|
-
readonly setTotalKnown: (known: boolean) => void;
|
|
1567
|
-
readonly feedProps: FeedProps;
|
|
1568
|
-
readonly articleProps: (posinset: number) => FeedArticleProps;
|
|
1569
|
-
readonly beforeProps: FeedSentinelProps;
|
|
1570
|
-
readonly afterProps: FeedSentinelProps;
|
|
1571
|
-
}
|
|
1572
|
-
/**
|
|
1573
|
-
* APG Feed (https://www.w3.org/WAI/ARIA/apg/patterns/feed/): state plus prop
|
|
1574
|
-
* objects for the feed container, its articles and the two outside focus
|
|
1575
|
-
* sentinels. Focus reaching the last loaded article triggers a load; `aria-busy`
|
|
1576
|
-
* brackets it; Page Up/Down move by article; Control+End/Home leave the feed.
|
|
1577
|
-
* No styling, no elements. Checked against `specs/apg/feed.qnt`.
|
|
1578
|
-
*
|
|
1579
|
-
* Platform: **library** — focusgroup V2 `feed` is explainer-only, so the load
|
|
1580
|
-
* trigger, busy bracketing and Page Up/Down / Control+End/Home keys are ours.
|
|
1581
|
-
*
|
|
1582
|
-
* Request/commit (map ticket `rt1p`): uncontrolled by default (`defaultValue`);
|
|
1583
|
-
* pass `value` + `onValueChange` to own the loaded count. `loadFinish` is the
|
|
1584
|
-
* request; `loadStart` / `loadFail` are adapter lifecycle events.
|
|
1585
|
-
*/
|
|
1586
|
-
export declare const createFeed: (options?: FeedOptions) => Feed;
|
|
1587
|
-
export interface FieldOptions {
|
|
1588
|
-
/** Base of every id the field makes (`<id>-control`, `-label`, `-hint`, `-error`). */
|
|
1589
|
-
readonly id?: string | undefined;
|
|
1590
|
-
readonly required?: boolean | undefined;
|
|
1591
|
-
/**
|
|
1592
|
-
* `true` marks the control invalid; a string does too and is the error message
|
|
1593
|
-
* (unless `error` is set). `false` forces valid: an `error` is then not shown,
|
|
1594
|
-
* so a host can pass `touched() && failed()` and keep the message ready.
|
|
1595
|
-
* Left out, the field is invalid exactly when it has an error message.
|
|
1596
|
-
*/
|
|
1597
|
-
readonly invalid?: boolean | string | undefined;
|
|
1598
|
-
/** Help text; absent (or empty) means the hint element is hidden and not referenced. */
|
|
1599
|
-
readonly hint?: string | undefined;
|
|
1600
|
-
/** The error message; absent (or empty) means no error element. */
|
|
1601
|
-
readonly error?: string | undefined;
|
|
1602
|
-
/**
|
|
1603
|
-
* Ids of elements the host owns, appended to `aria-describedby` after the
|
|
1604
|
-
* field's own hint and error: a form-level error that belongs to several
|
|
1605
|
-
* controls ("the passwords do not match" names both). One id or a list;
|
|
1606
|
-
* empty strings are dropped, and the attribute is absent when nothing is left.
|
|
1607
|
-
* The host keeps that element rendered, and decides whether it is hidden.
|
|
1608
|
-
*/
|
|
1609
|
-
readonly describedby?: string | readonly string[] | undefined;
|
|
1610
|
-
}
|
|
1611
|
-
/** Spread onto the form control (`input`, `textarea`, `select`, or a custom widget). */
|
|
1612
|
-
export interface FieldControlProps {
|
|
1613
|
-
readonly id: string;
|
|
1614
|
-
/** The ids of the hint and the error that are shown, hint first, then the host's `describedby`; absent when there are none. */
|
|
1615
|
-
readonly "aria-describedby": string | undefined;
|
|
1616
|
-
readonly "aria-invalid": "true" | undefined;
|
|
1617
|
-
/** For a control that is not native (a native one has `required`). */
|
|
1618
|
-
readonly "aria-required": "true" | undefined;
|
|
1619
|
-
readonly required: true | undefined;
|
|
1620
|
-
}
|
|
1621
|
-
export interface FieldLabelProps {
|
|
1622
|
-
readonly id: string;
|
|
1623
|
-
readonly for: string;
|
|
1624
|
-
}
|
|
1625
|
-
/** Spread onto the hint and the error element; render both, always. */
|
|
1626
|
-
export interface FieldMessageProps {
|
|
1627
|
-
readonly id: string;
|
|
1628
|
-
readonly hidden: boolean;
|
|
1629
|
-
}
|
|
1630
|
-
/** The error element also announces itself: `role="alert"` while it is shown. */
|
|
1631
|
-
export interface FieldErrorProps extends FieldMessageProps {
|
|
1632
|
-
readonly role: "alert" | undefined;
|
|
1633
|
-
}
|
|
1634
|
-
export interface Field {
|
|
1635
|
-
readonly invalid: () => boolean;
|
|
1636
|
-
/** The hint, or `undefined` when there is none. */
|
|
1637
|
-
readonly hint: () => string | undefined;
|
|
1638
|
-
/** The error shown: `undefined` unless the field is invalid and has a message. */
|
|
1639
|
-
readonly error: () => string | undefined;
|
|
1640
|
-
/**
|
|
1641
|
-
* The ids `aria-describedby` lists now: hint, then error (never an id that is
|
|
1642
|
-
* hidden), then the host's `describedby`.
|
|
1643
|
-
*/
|
|
1644
|
-
readonly describedIds: () => readonly string[];
|
|
1645
|
-
readonly controlProps: FieldControlProps;
|
|
1646
|
-
readonly labelProps: FieldLabelProps;
|
|
1647
|
-
readonly hintProps: FieldMessageProps;
|
|
1648
|
-
readonly errorProps: FieldErrorProps;
|
|
1649
|
-
}
|
|
1650
|
-
/**
|
|
1651
|
-
* The ARIA contract of a form field: a label, a control, a hint and an error.
|
|
1652
|
-
* There is no APG pattern for it (APG has no "form field"; the nearest is the
|
|
1653
|
-
* label and description rules of the accessible name computation), so the
|
|
1654
|
-
* contract comes from WAI-ARIA 1.2 (`aria-describedby`, `aria-invalid`,
|
|
1655
|
-
* `aria-required`) and HTML (`label[for]`, `required`), and from the
|
|
1656
|
-
* majority of Base UI Field, react-aria TextField and ariakit FormControl,
|
|
1657
|
-
* which list the description and the error message together. It is not checked
|
|
1658
|
-
* against a Quint spec; `test/field.browser.test.tsx` is its oracle.
|
|
1659
|
-
*
|
|
1660
|
-
* Decisions: the error does not replace the hint, both are listed, hint first
|
|
1661
|
-
* (the hint is what tells the user how to fix the error); an error implies
|
|
1662
|
-
* invalid and `invalid: false` overrides it; a hidden message is never
|
|
1663
|
-
* referenced, so `aria-describedby` has no dangling id. The error element is
|
|
1664
|
-
* `role="alert"` only while it is shown (a hidden element announces nothing, and
|
|
1665
|
-
* one that appears is announced); the hint is never an alert.
|
|
2002
|
+
readonly loadStart: () => void;
|
|
2003
|
+
/** A batch arrived; a request to grow the loaded count. */
|
|
2004
|
+
readonly loadFinish: (count: number) => void;
|
|
2005
|
+
readonly loadFail: () => void;
|
|
2006
|
+
readonly setSizeMode: (mode: "loaded" | "total") => void;
|
|
2007
|
+
readonly setTotalKnown: (known: boolean) => void;
|
|
2008
|
+
readonly feedProps: FeedProps;
|
|
2009
|
+
readonly articleProps: (posinset: number) => FeedArticleProps;
|
|
2010
|
+
readonly beforeProps: FeedSentinelProps;
|
|
2011
|
+
readonly afterProps: FeedSentinelProps;
|
|
2012
|
+
}
|
|
2013
|
+
/**
|
|
2014
|
+
* APG Feed (https://www.w3.org/WAI/ARIA/apg/patterns/feed/): state plus prop
|
|
2015
|
+
* objects for the feed container, its articles and the two outside focus
|
|
2016
|
+
* sentinels. Focus reaching the last loaded article triggers a load; `aria-busy`
|
|
2017
|
+
* brackets it; Page Up/Down move by article; Control+End/Home leave the feed.
|
|
2018
|
+
* No styling, no elements. Checked against `specs/apg/feed.qnt`.
|
|
1666
2019
|
*
|
|
1667
|
-
*
|
|
1668
|
-
*
|
|
1669
|
-
* by both. The field cannot know whether that element is shown: the host
|
|
1670
|
-
* passes the id only while it is.
|
|
2020
|
+
* Platform: **library** — focusgroup V2 `feed` is explainer-only, so the load
|
|
2021
|
+
* trigger, busy bracketing and Page Up/Down / Control+End/Home keys are ours.
|
|
1671
2022
|
*
|
|
1672
|
-
*
|
|
2023
|
+
* Request/commit (map ticket `rt1p`): uncontrolled by default (`defaultValue`);
|
|
2024
|
+
* pass `value` + `onValueChange` to own the loaded count. `loadFinish` is the
|
|
2025
|
+
* request; `loadStart` / `loadFail` are adapter lifecycle events.
|
|
1673
2026
|
*/
|
|
1674
|
-
export declare const
|
|
2027
|
+
export declare const createFeed: (options?: FeedOptions) => Feed;
|
|
1675
2028
|
export interface PaginationLabels {
|
|
1676
2029
|
/** The navigation landmark's name; defaults to "Pagination". */
|
|
1677
2030
|
readonly nav?: string | undefined;
|
|
@@ -2196,6 +2549,107 @@ export interface Hotkeys {
|
|
|
2196
2549
|
* browser would otherwise act on (Cmd/Ctrl+S, Cmd/Ctrl+K).
|
|
2197
2550
|
*/
|
|
2198
2551
|
export declare const createHotkeys: (options?: HotkeysOptions) => Hotkeys;
|
|
2552
|
+
/** How a drag turns the knob: the angle around the centre, a straight slide, or whichever the drag starts as. */
|
|
2553
|
+
export type KnobGesture = "spin" | "slide" | "both";
|
|
2554
|
+
export interface KnobLabels {
|
|
2555
|
+
/** The knob's accessible name (`aria-label`); "Knob" by default. */
|
|
2556
|
+
readonly knob?: string | undefined;
|
|
2557
|
+
}
|
|
2558
|
+
export interface KnobOptions {
|
|
2559
|
+
readonly min?: number | undefined;
|
|
2560
|
+
readonly max?: number | undefined;
|
|
2561
|
+
readonly step?: number | undefined;
|
|
2562
|
+
/** Controlled: the host owns the value and this is the only thing that changes it. */
|
|
2563
|
+
readonly value?: number | undefined;
|
|
2564
|
+
/** Starting value when uncontrolled (the middle of the range by default). */
|
|
2565
|
+
readonly defaultValue?: number | undefined;
|
|
2566
|
+
/**
|
|
2567
|
+
* `false` (default): clamp at `min` / `max`. `true`: a continuous dial — the
|
|
2568
|
+
* value wraps around (past `max` comes back at `min`), so a full turn of the
|
|
2569
|
+
* skin is a full cycle.
|
|
2570
|
+
*/
|
|
2571
|
+
readonly wrap?: boolean | undefined;
|
|
2572
|
+
/** Which drag turns the knob; "both" (default) locks per drag from the first movement. */
|
|
2573
|
+
readonly gesture?: KnobGesture | undefined;
|
|
2574
|
+
readonly labels?: KnobLabels | undefined;
|
|
2575
|
+
/** The spoken value (`aria-valuetext`); `String` by default. */
|
|
2576
|
+
readonly format?: ((value: number) => string) | undefined;
|
|
2577
|
+
/** Base for the generated value region id. */
|
|
2578
|
+
readonly id?: string | undefined;
|
|
2579
|
+
/**
|
|
2580
|
+
* A request to change the value, called when the intent differs from the
|
|
2581
|
+
* current value. Uncontrolled it is accepted; controlled the host may ignore it.
|
|
2582
|
+
*/
|
|
2583
|
+
readonly onValueChange?: ((value: number) => void) | undefined;
|
|
2584
|
+
}
|
|
2585
|
+
/** Spread onto the knob element, the focusable control. */
|
|
2586
|
+
export interface KnobProps {
|
|
2587
|
+
readonly role: "slider";
|
|
2588
|
+
readonly tabindex: number;
|
|
2589
|
+
readonly "aria-valuemin": string;
|
|
2590
|
+
readonly "aria-valuemax": string;
|
|
2591
|
+
readonly "aria-valuenow": string;
|
|
2592
|
+
readonly "aria-valuetext": string;
|
|
2593
|
+
readonly "aria-label": string;
|
|
2594
|
+
readonly onKeyDown: (event: KeyboardEvent) => void;
|
|
2595
|
+
readonly onPointerDown: (event: PointerEvent) => void;
|
|
2596
|
+
readonly onPointerMove: (event: PointerEvent) => void;
|
|
2597
|
+
readonly onPointerUp: (event: PointerEvent) => void;
|
|
2598
|
+
readonly onPointerCancel: (event: PointerEvent) => void;
|
|
2599
|
+
readonly onWheel: (event: WheelEvent) => void;
|
|
2600
|
+
}
|
|
2601
|
+
/** Spread onto the live value; render it once, always (hide it visually, never with `hidden`). */
|
|
2602
|
+
export interface KnobValueProps {
|
|
2603
|
+
readonly id: string;
|
|
2604
|
+
readonly role: "status";
|
|
2605
|
+
readonly "aria-live": "polite";
|
|
2606
|
+
}
|
|
2607
|
+
export interface Knob {
|
|
2608
|
+
readonly value: Accessor<number>;
|
|
2609
|
+
/** A request, like a key or a drag; `setValue` never bypasses a controlled `value`. */
|
|
2610
|
+
readonly setValue: (value: number) => void;
|
|
2611
|
+
/**
|
|
2612
|
+
* The fraction of a full turn the skin rotates by for `value`, 0..1 (multiply
|
|
2613
|
+
* by 360 for degrees). Clamped to 0..1, or the wrapped fraction when `wrap`.
|
|
2614
|
+
*/
|
|
2615
|
+
readonly percentOf: (value: number) => number;
|
|
2616
|
+
/** A request to move by `delta` value units (a key, a wheel, a host button). */
|
|
2617
|
+
readonly stepBy: (delta: number) => void;
|
|
2618
|
+
readonly knobProps: KnobProps;
|
|
2619
|
+
readonly valueProps: KnobValueProps;
|
|
2620
|
+
}
|
|
2621
|
+
/**
|
|
2622
|
+
* A virtual knob after GarageBand's (https://jherrm.github.io/knobs/): one
|
|
2623
|
+
* control turned three ways — spun around its centre, or slid vertically or
|
|
2624
|
+
* horizontally, one gesture locked per drag. Plain Solid signals, no elements,
|
|
2625
|
+
* no styling. **No ARIA role exists for a dial**, so the knob is a `role=slider`
|
|
2626
|
+
* with `aria-valuenow` / `aria-valuetext`; the value the host renders in
|
|
2627
|
+
* `valueProps` is the live announcement (`role=status`), and a numeric field
|
|
2628
|
+
* must sit next to the dial, because a dial is never the only control.
|
|
2629
|
+
*
|
|
2630
|
+
* Request/commit (map ticket `rt1p`): uncontrolled by default (`defaultValue`);
|
|
2631
|
+
* pass `value` + `onValueChange` to own the value. A key, a wheel, a drag and
|
|
2632
|
+
* `setValue`/`stepBy` are requests the host may ignore.
|
|
2633
|
+
*
|
|
2634
|
+
* Gestures (`gesture`, default "both"), decided once on the first meaningful
|
|
2635
|
+
* movement so a drag never switches modes:
|
|
2636
|
+
* - a movement that stays on one axis (the other axis under `SLIDE_VARIANCE` of
|
|
2637
|
+
* it) locks a slide — the reference's "slide in a straight line";
|
|
2638
|
+
* - any other movement is a turn around the centre: the angle the pointer
|
|
2639
|
+
* covers is the value, and the farther from the centre the finer it is;
|
|
2640
|
+
* - `gesture: "spin"` / `"slide"` disable the other.
|
|
2641
|
+
*
|
|
2642
|
+
* A vertical slide turns the knob the way the grabbed side would turn a real
|
|
2643
|
+
* one: down on the right half is clockwise (up in value), down on the left half
|
|
2644
|
+
* counter-clockwise. A horizontal slide uses the same left/right side (the
|
|
2645
|
+
* reference picks top/bottom for this one; one rule for both is simpler to
|
|
2646
|
+
* learn). `wrap` makes the dial continuous instead of clamped.
|
|
2647
|
+
*
|
|
2648
|
+
* ponytail: no momentum/inertia and no `disabled` — the reference has a
|
|
2649
|
+
* deceleration tail, and no host here has asked for one; add inertia in the
|
|
2650
|
+
* host's rAF loop if a look needs it.
|
|
2651
|
+
*/
|
|
2652
|
+
export declare const createKnob: (options?: KnobOptions) => Knob;
|
|
2199
2653
|
export interface LinkOptions {
|
|
2200
2654
|
readonly href?: string | undefined;
|
|
2201
2655
|
/** Render a native `<a href>` (the default when `href` is set). */
|
|
@@ -2351,8 +2805,12 @@ export declare const createListbox: (options: ListboxOptions) => Listbox;
|
|
|
2351
2805
|
* dismiss and Esc; `focusgroup` gives the menubar's roving tabindex. The
|
|
2352
2806
|
* library owns the menu keys, typeahead, submenus and checkable state.
|
|
2353
2807
|
*
|
|
2354
|
-
*
|
|
2355
|
-
*
|
|
2808
|
+
* Request/commit (map ticket `rt1p`): the open state, and the checked items of
|
|
2809
|
+
* the checkable variants, are controllable. Uncontrolled by default
|
|
2810
|
+
* (`defaultOpen` / `defaultValue`); pass `open` / `value` (+ `onOpenChange` /
|
|
2811
|
+
* `onCheckedValueChange`) to own them. Every opening and closing intent — the
|
|
2812
|
+
* trigger's click and keys, Escape, an item's activation, Tab, and the
|
|
2813
|
+
* programmatic `openMenu` / `closeMenu` — is a request the host may ignore.
|
|
2356
2814
|
*/
|
|
2357
2815
|
export type MenuItemKind = "item" | "checkbox" | "radio";
|
|
2358
2816
|
export interface MenuItemInput {
|
|
@@ -2365,6 +2823,9 @@ export type MenuVariant = "plain" | "checkbox-items" | "radio-items" | "mixed";
|
|
|
2365
2823
|
export interface MenuOptions {
|
|
2366
2824
|
readonly items: ReadonlyArray<string | MenuItemInput>;
|
|
2367
2825
|
readonly variant?: MenuVariant;
|
|
2826
|
+
/** Controlled: the host owns the open state and this is the only thing that opens or closes it. */
|
|
2827
|
+
readonly open?: boolean | undefined;
|
|
2828
|
+
/** Uncontrolled starting state; ignored while `open` is set. */
|
|
2368
2829
|
readonly defaultOpen?: boolean;
|
|
2369
2830
|
/**
|
|
2370
2831
|
* `outside` (menu button): focus can sit outside the widget, so keys are
|
|
@@ -2380,7 +2841,7 @@ export interface MenuOptions {
|
|
|
2380
2841
|
* Controlled: the host owns the checked items. Packed as
|
|
2381
2842
|
* `checkboxItem0 * 5 + radioChoice` (1-based), matching `menu-checkable.qnt`'s
|
|
2382
2843
|
* `checked(wrap, choice)`; pass `undefined` for uncontrolled. The open state
|
|
2383
|
-
*
|
|
2844
|
+
* has its own `open` pair.
|
|
2384
2845
|
*/
|
|
2385
2846
|
readonly value?: number | undefined;
|
|
2386
2847
|
/** The uncontrolled packed checked state at mount. */
|
|
@@ -2390,6 +2851,10 @@ export interface MenuOptions {
|
|
|
2390
2851
|
/** Item indices disabled (focusable, not activatable). */
|
|
2391
2852
|
readonly disabled?: ReadonlyArray<number>;
|
|
2392
2853
|
readonly id?: string;
|
|
2854
|
+
/**
|
|
2855
|
+
* A request to change the open state, called when the intent differs from the
|
|
2856
|
+
* current one. Uncontrolled it is accepted; controlled the host may ignore it.
|
|
2857
|
+
*/
|
|
2393
2858
|
readonly onOpenChange?: (open: boolean) => void;
|
|
2394
2859
|
readonly onItemClick?: (index: number) => void;
|
|
2395
2860
|
readonly onCheckedChange?: (checked: boolean, index: number) => void;
|
|
@@ -2854,6 +3319,11 @@ export interface SliderOptions {
|
|
|
2854
3319
|
readonly defaultValue?: number;
|
|
2855
3320
|
readonly orientation?: SliderOrientation;
|
|
2856
3321
|
readonly direction?: SliderDirection;
|
|
3322
|
+
/**
|
|
3323
|
+
* aria-disabled: the thumb stays the tab stop (the APG pattern's focus
|
|
3324
|
+
* contract) but a key or a rail press changes nothing.
|
|
3325
|
+
*/
|
|
3326
|
+
readonly disabled?: boolean;
|
|
2857
3327
|
/** Base for the generated thumb id. */
|
|
2858
3328
|
readonly id?: string;
|
|
2859
3329
|
/**
|
|
@@ -2870,6 +3340,7 @@ export interface SliderThumbProps {
|
|
|
2870
3340
|
readonly "aria-valuemax": string;
|
|
2871
3341
|
readonly "aria-valuetext": string;
|
|
2872
3342
|
readonly "aria-orientation": SliderOrientation;
|
|
3343
|
+
readonly "aria-disabled": "true" | undefined;
|
|
2873
3344
|
readonly tabindex: number;
|
|
2874
3345
|
readonly onFocus: () => void;
|
|
2875
3346
|
readonly onBlur: () => void;
|
|
@@ -2907,6 +3378,11 @@ export interface Slider {
|
|
|
2907
3378
|
* pass `value` + `onValueChange` to own the value. A key or rail press is a
|
|
2908
3379
|
* request the host may ignore.
|
|
2909
3380
|
*
|
|
3381
|
+
* `disabled` is a widget-level extension of the pattern (the spec lists it
|
|
3382
|
+
* under its deliberate omissions): the thumb keeps its tab stop and announces
|
|
3383
|
+
* `aria-disabled`, and every key and rail press is dropped before it becomes a
|
|
3384
|
+
* request.
|
|
3385
|
+
*
|
|
2910
3386
|
* ponytail: dragging (pointer capture/move) is not modelled — the spec
|
|
2911
3387
|
* deliberately only fixes a discrete rail press.
|
|
2912
3388
|
*/
|
|
@@ -2991,6 +3467,11 @@ export interface SpinbuttonOptions {
|
|
|
2991
3467
|
readonly defaultValue?: number;
|
|
2992
3468
|
/** Wrap around at the bounds instead of clamping. */
|
|
2993
3469
|
readonly wrap?: boolean;
|
|
3470
|
+
/**
|
|
3471
|
+
* aria-disabled plus the native `disabled` on the text field: keys, typing
|
|
3472
|
+
* and both steppers change nothing, and both steppers announce `aria-disabled`.
|
|
3473
|
+
*/
|
|
3474
|
+
readonly disabled?: boolean;
|
|
2994
3475
|
/** Base for the generated input / button ids. */
|
|
2995
3476
|
readonly id?: string;
|
|
2996
3477
|
/**
|
|
@@ -3007,6 +3488,8 @@ export interface SpinbuttonInputProps {
|
|
|
3007
3488
|
readonly "aria-valuemax": string;
|
|
3008
3489
|
readonly "aria-valuetext": string;
|
|
3009
3490
|
readonly "aria-invalid": "true" | undefined;
|
|
3491
|
+
readonly "aria-disabled": "true" | undefined;
|
|
3492
|
+
readonly disabled: boolean;
|
|
3010
3493
|
readonly tabindex: number;
|
|
3011
3494
|
readonly value: string;
|
|
3012
3495
|
readonly onFocus: () => void;
|
|
@@ -3062,41 +3545,16 @@ export interface Spinbutton {
|
|
|
3062
3545
|
* stepper buttons, Enter and blur are requests the host may ignore; typing and
|
|
3063
3546
|
* Escape change no committed value.
|
|
3064
3547
|
*
|
|
3548
|
+
* `disabled` is a widget-level extension of the pattern (the spec models the
|
|
3549
|
+
* steppers' bound only, and lists this under its deliberate omissions): the
|
|
3550
|
+
* field announces `aria-disabled` and carries the native `disabled`, every key,
|
|
3551
|
+
* typed draft and both steppers are dropped before they become a request, and
|
|
3552
|
+
* neither stepper offers itself.
|
|
3553
|
+
*
|
|
3065
3554
|
* ponytail: commit/revert on Enter/blur/Escape is the common convention, not
|
|
3066
3555
|
* APG text.
|
|
3067
3556
|
*/
|
|
3068
3557
|
export declare const createSpinbutton: (options?: SpinbuttonOptions) => Spinbutton;
|
|
3069
|
-
export interface StatusOptions {
|
|
3070
|
-
/** Rendered on `statusProps`; generated when absent. */
|
|
3071
|
-
readonly id?: string | undefined;
|
|
3072
|
-
}
|
|
3073
|
-
/** Spread onto the `role=status` live region. Render it once, always. */
|
|
3074
|
-
export interface StatusProps {
|
|
3075
|
-
readonly id: string;
|
|
3076
|
-
readonly role: "status";
|
|
3077
|
-
readonly "aria-live": "polite";
|
|
3078
|
-
readonly "aria-atomic": "true";
|
|
3079
|
-
}
|
|
3080
|
-
export interface Status {
|
|
3081
|
-
/** The text currently shown, or `undefined` before the first `show`. */
|
|
3082
|
-
readonly message: Accessor<string | undefined>;
|
|
3083
|
-
/** Writes the message, replacing the previous one; nothing is queued. */
|
|
3084
|
-
readonly show: (text: string) => void;
|
|
3085
|
-
readonly clear: () => void;
|
|
3086
|
-
readonly statusProps: StatusProps;
|
|
3087
|
-
}
|
|
3088
|
-
/**
|
|
3089
|
-
* APG Alert's polite sibling: one `role=status` live region whose text the host
|
|
3090
|
-
* replaces ("Link copied", "Saved"). It is not a queue: a second message shows
|
|
3091
|
-
* in place of the first, so a burst reads as the last state, not as N alerts.
|
|
3092
|
-
* The region is rendered once and stays mounted, which is what makes a change
|
|
3093
|
-
* of its text announced.
|
|
3094
|
-
*
|
|
3095
|
-
* A status is never for errors: an error that needs attention now is
|
|
3096
|
-
* `createAlert` (`role=alert`, assertive), and a message with a title, a
|
|
3097
|
-
* description and a timeout is `createToast`.
|
|
3098
|
-
*/
|
|
3099
|
-
export declare const createStatus: (options?: StatusOptions) => Status;
|
|
3100
3558
|
export interface SwitchOptions {
|
|
3101
3559
|
/** Controlled: the host owns the state and this is the only thing that changes it. */
|
|
3102
3560
|
readonly value?: boolean | undefined;
|
|
@@ -3327,86 +3785,6 @@ export interface Table {
|
|
|
3327
3785
|
* destructured snapshot. `id` is captured once: it is the base of the caption id.
|
|
3328
3786
|
*/
|
|
3329
3787
|
export declare const createTable: (options: TableOptions) => Table;
|
|
3330
|
-
/** The HTML `autocomplete` tokens worth naming; the attribute also takes an arbitrary token. */
|
|
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";
|
|
3332
|
-
export interface TextInputOptions extends FieldOptions {
|
|
3333
|
-
readonly type?: "text" | "email" | "tel" | "url" | "search" | "password" | undefined;
|
|
3334
|
-
/** The HTML token list: the browser's own autofill is the accessible path. */
|
|
3335
|
-
readonly autoComplete?: AutoCompleteToken | undefined;
|
|
3336
|
-
readonly spellCheck?: boolean | undefined;
|
|
3337
|
-
readonly autoCapitalize?: "none" | "sentences" | "words" | "characters" | undefined;
|
|
3338
|
-
readonly inputMode?: "none" | "text" | "tel" | "url" | "email" | "numeric" | "decimal" | "search" | undefined;
|
|
3339
|
-
readonly maxLength?: number | undefined;
|
|
3340
|
-
readonly readOnly?: boolean | undefined;
|
|
3341
|
-
readonly disabled?: boolean | undefined;
|
|
3342
|
-
readonly name?: string | undefined;
|
|
3343
|
-
readonly dir?: "ltr" | "rtl" | "auto" | undefined;
|
|
3344
|
-
readonly placeholder?: string | undefined;
|
|
3345
|
-
/** Change and input both land here; the host decides what a keystroke means. */
|
|
3346
|
-
readonly onValueChange?: ((value: string) => void) | undefined;
|
|
3347
|
-
}
|
|
3348
|
-
export interface TextAreaOptions extends TextInputOptions {
|
|
3349
|
-
readonly rows?: number | undefined;
|
|
3350
|
-
readonly wrap?: "soft" | "hard" | "off" | undefined;
|
|
3351
|
-
readonly resize?: "none" | "vertical" | "horizontal" | "both" | undefined;
|
|
3352
|
-
}
|
|
3353
|
-
export interface TextControlProps {
|
|
3354
|
-
readonly id: string;
|
|
3355
|
-
readonly "aria-describedby": string | undefined;
|
|
3356
|
-
readonly "aria-invalid": "true" | undefined;
|
|
3357
|
-
readonly "aria-required": "true" | undefined;
|
|
3358
|
-
readonly required: true | undefined;
|
|
3359
|
-
readonly type: TextInputOptions["type"];
|
|
3360
|
-
readonly autoComplete: AutoCompleteToken | undefined;
|
|
3361
|
-
readonly spellcheck: "true" | "false" | undefined;
|
|
3362
|
-
readonly autocapitalize: TextInputOptions["autoCapitalize"];
|
|
3363
|
-
readonly inputmode: TextInputOptions["inputMode"];
|
|
3364
|
-
readonly maxlength: number | undefined;
|
|
3365
|
-
readonly readOnly: true | undefined;
|
|
3366
|
-
readonly disabled: true | undefined;
|
|
3367
|
-
readonly name: string | undefined;
|
|
3368
|
-
readonly dir: "ltr" | "rtl" | "auto" | undefined;
|
|
3369
|
-
readonly placeholder: string | undefined;
|
|
3370
|
-
readonly onInput: (event: {
|
|
3371
|
-
currentTarget: {
|
|
3372
|
-
value: string;
|
|
3373
|
-
};
|
|
3374
|
-
}) => void;
|
|
3375
|
-
}
|
|
3376
|
-
export interface TextAreaControlProps extends TextControlProps {
|
|
3377
|
-
readonly rows: number | undefined;
|
|
3378
|
-
readonly wrap: TextAreaOptions["wrap"];
|
|
3379
|
-
readonly style: {
|
|
3380
|
-
readonly resize: "none" | "vertical" | "horizontal" | "both";
|
|
3381
|
-
} | undefined;
|
|
3382
|
-
}
|
|
3383
|
-
/** The field and the control, one object: every prop the element needs. */
|
|
3384
|
-
export interface TextInput extends Field {
|
|
3385
|
-
readonly controlProps: TextControlProps;
|
|
3386
|
-
}
|
|
3387
|
-
export interface TextArea extends Field {
|
|
3388
|
-
readonly controlProps: TextAreaControlProps;
|
|
3389
|
-
}
|
|
3390
|
-
/**
|
|
3391
|
-
* A text input with the field contract attached: label, hint, error (announced),
|
|
3392
|
-
* `aria-describedby` and `aria-invalid` come from `createField`; this adds the
|
|
3393
|
-
* props a host forgets — autofill tokens, spellcheck, inputmode, maxlength, the
|
|
3394
|
-
* disabled/readOnly pair. Platform: native (`input` is its own APG-equivalent;
|
|
3395
|
-
* no primitive re-implements text editing, which is why there is no key
|
|
3396
|
-
* handling here).
|
|
3397
|
-
*
|
|
3398
|
-
* Both `required` and `aria-required` are emitted: the native attribute is what
|
|
3399
|
-
* the browser enforces, the ARIA one is what a custom widget reading the same
|
|
3400
|
-
* props needs. A host that validates itself passes `noValidate` on the form
|
|
3401
|
-
* rather than dropping the attribute.
|
|
3402
|
-
*/
|
|
3403
|
-
export declare const createTextInput: (options?: TextInputOptions) => TextInput;
|
|
3404
|
-
/**
|
|
3405
|
-
* The same contract for a textarea, plus rows, wrap and resize. `resize` is a
|
|
3406
|
-
* style because CSS has no attribute for it; `none` is the value a layout that
|
|
3407
|
-
* owns its own height wants.
|
|
3408
|
-
*/
|
|
3409
|
-
export declare const createTextArea: (options?: TextAreaOptions) => TextArea;
|
|
3410
3788
|
export type TabsOrientation = "horizontal" | "vertical";
|
|
3411
3789
|
export type TabsActivation = "automatic" | "manual";
|
|
3412
3790
|
/**
|
|
@@ -3433,6 +3811,26 @@ export interface TabsOptions {
|
|
|
3433
3811
|
* current value. Uncontrolled it is accepted; controlled the host may ignore it.
|
|
3434
3812
|
*/
|
|
3435
3813
|
readonly onValueChange?: (index: number) => void;
|
|
3814
|
+
/**
|
|
3815
|
+
* Close buttons and the Delete key (default false): the host renders
|
|
3816
|
+
* `closeProps` and removes the tab from `onClose`. The last tab is never
|
|
3817
|
+
* asked to close.
|
|
3818
|
+
*/
|
|
3819
|
+
readonly closable?: boolean | undefined;
|
|
3820
|
+
/**
|
|
3821
|
+
* Inline rename (default false): a double click on a tab starts it, Enter /
|
|
3822
|
+
* blur commit and Escape reverts; the host renames from `onRename`.
|
|
3823
|
+
*/
|
|
3824
|
+
readonly renamable?: boolean | undefined;
|
|
3825
|
+
/**
|
|
3826
|
+
* A tab's label: the close / rename button names and the starting draft of a
|
|
3827
|
+
* rename, read lazily (a getter is fine) so a rename follows.
|
|
3828
|
+
*/
|
|
3829
|
+
readonly label?: ((index: number) => string | undefined) | undefined;
|
|
3830
|
+
/** A request to remove a tab; the primitive does not own the list. */
|
|
3831
|
+
readonly onClose?: ((index: number) => void) | undefined;
|
|
3832
|
+
/** A request to rename a tab with a non-empty label. */
|
|
3833
|
+
readonly onRename?: ((index: number, label: string) => void) | undefined;
|
|
3436
3834
|
}
|
|
3437
3835
|
/** Spread onto the element with `role=tab` (a native `<button>`). */
|
|
3438
3836
|
export interface TabProps {
|
|
@@ -3460,21 +3858,63 @@ export interface TabpanelProps {
|
|
|
3460
3858
|
readonly tabindex: 0 | undefined;
|
|
3461
3859
|
readonly hidden: boolean;
|
|
3462
3860
|
}
|
|
3861
|
+
/**
|
|
3862
|
+
* Spread onto the tab's close button. `tabindex=-1` keeps it out of the
|
|
3863
|
+
* tablist's tab order (a tablist's stops are its tabs); the Delete key is the
|
|
3864
|
+
* keyboard path.
|
|
3865
|
+
*/
|
|
3866
|
+
export interface TabCloseProps {
|
|
3867
|
+
readonly "aria-label": string;
|
|
3868
|
+
readonly tabindex: -1;
|
|
3869
|
+
readonly onClick: () => void;
|
|
3870
|
+
}
|
|
3871
|
+
/** Spread onto the inline `<input>` a renaming tab renders instead of its label. */
|
|
3872
|
+
export interface TabRenameInputProps {
|
|
3873
|
+
readonly type: "text";
|
|
3874
|
+
readonly value: string;
|
|
3875
|
+
readonly "aria-label": string;
|
|
3876
|
+
readonly onInput: (event: InputEvent) => void;
|
|
3877
|
+
readonly onKeyDown: (event: KeyboardEvent) => void;
|
|
3878
|
+
readonly onBlur: () => void;
|
|
3879
|
+
}
|
|
3880
|
+
/** The rename part of one tab: put `onDblClick` on the tab, `inputProps` in it. */
|
|
3881
|
+
export interface TabRenameProps {
|
|
3882
|
+
readonly onDblClick: () => void;
|
|
3883
|
+
/** Whether this tab is the one being renamed. */
|
|
3884
|
+
readonly editing: Accessor<boolean>;
|
|
3885
|
+
/** Start renaming `index` (e.g. a tab the host has just added). */
|
|
3886
|
+
readonly begin: (index: number) => void;
|
|
3887
|
+
/** Commit `label` to the tab being edited; an empty label cancels. */
|
|
3888
|
+
readonly commit: (label: string) => void;
|
|
3889
|
+
readonly cancel: () => void;
|
|
3890
|
+
readonly inputProps: TabRenameInputProps;
|
|
3891
|
+
}
|
|
3463
3892
|
export interface Tabs {
|
|
3893
|
+
/** The tab count; read from `items` on every read, so a host that shrinks its list stays in step. */
|
|
3464
3894
|
readonly items: number;
|
|
3465
3895
|
readonly selected: Accessor<number>;
|
|
3466
3896
|
/** Tab holding (or last holding) DOM focus; -1 when focus is outside. */
|
|
3467
3897
|
readonly focused: Accessor<number>;
|
|
3898
|
+
/** The tab being renamed inline, `null` when none. */
|
|
3899
|
+
readonly editing: Accessor<number | null>;
|
|
3468
3900
|
/** Programmatic selection request; ignores disabled tabs. */
|
|
3469
3901
|
readonly select: (index: number) => void;
|
|
3470
3902
|
/** Programmatic selection request (may point at a disabled tab, like base-ui). */
|
|
3471
3903
|
readonly setSelected: (index: number) => void;
|
|
3472
3904
|
/** Programmatic focus (does not select). */
|
|
3473
3905
|
readonly focusTab: (index: number) => void;
|
|
3906
|
+
/**
|
|
3907
|
+
* The tab that takes focus once `onClose(index)`'s removal lands: the tab
|
|
3908
|
+
* that slid into `index`, or the previous one when the last tab closed. Call
|
|
3909
|
+
* it before the host removes the tab.
|
|
3910
|
+
*/
|
|
3911
|
+
readonly focusAfterClose: (index: number) => number;
|
|
3474
3912
|
readonly keyDown: (key: string) => void;
|
|
3475
3913
|
readonly tablistProps: TablistProps;
|
|
3476
3914
|
readonly tabProps: (index: number) => TabProps;
|
|
3477
3915
|
readonly tabpanelProps: (index: number) => TabpanelProps;
|
|
3916
|
+
readonly closeProps: (index: number) => TabCloseProps;
|
|
3917
|
+
readonly renameProps: (index: number) => TabRenameProps;
|
|
3478
3918
|
}
|
|
3479
3919
|
/**
|
|
3480
3920
|
* APG Tabs (https://www.w3.org/WAI/ARIA/apg/patterns/tabs/): state plus prop
|
|
@@ -3489,6 +3929,14 @@ export interface Tabs {
|
|
|
3489
3929
|
* Request/commit (map ticket `rt1p`): uncontrolled by default
|
|
3490
3930
|
* (`defaultSelected`); pass `value` + `onValueChange` to own the selection.
|
|
3491
3931
|
* Programmatic methods (`select`, `setSelected`, `keyDown`) are requests too.
|
|
3932
|
+
*
|
|
3933
|
+
* Close and rename are an **extension** (bean `cssints-gdv2`): the APG spec
|
|
3934
|
+
* models a `deletable` flavour but neither a callback nor a rename, and this
|
|
3935
|
+
* primitive owns no list. `closable` / `renamable` turn on the props; `onClose`
|
|
3936
|
+
* / `onRename` ask the host, which removes or renames. Focus after a close goes
|
|
3937
|
+
* to the tab that slid into the closed one's place, or to the previous tab when
|
|
3938
|
+
* the last one closed; closing the last remaining tab is refused, as the spec
|
|
3939
|
+
* refuses an empty tablist.
|
|
3492
3940
|
*/
|
|
3493
3941
|
export declare const createTabs: (options?: TabsOptions) => Tabs;
|
|
3494
3942
|
/**
|
|
@@ -3506,10 +3954,16 @@ export declare const createTabs: (options?: TabsOptions) => Tabs;
|
|
|
3506
3954
|
* may ignore. The zoom window (`viewStart`/`viewEnd`) is not the controlled
|
|
3507
3955
|
* value and stays uncontrolled.
|
|
3508
3956
|
*
|
|
3509
|
-
*
|
|
3510
|
-
*
|
|
3511
|
-
* `
|
|
3512
|
-
*
|
|
3957
|
+
* The fixed 5-span tree is the demo fixture (the differential walk's oracle).
|
|
3958
|
+
* A real consumer passes `rows`: span data on one shared axis, laid out by
|
|
3959
|
+
* `barProps` as percentages of the rows' extent. No roles are borrowed from
|
|
3960
|
+
* APG: a trace waterfall is a list of spans in time, so the rows mode is
|
|
3961
|
+
* `role="list"` / `role="listitem"` (with `aria-level` for the depth) and the
|
|
3962
|
+
* current row is `aria-current="true"`, which every element may carry.
|
|
3963
|
+
*
|
|
3964
|
+
* Roving tabindex only — a consumer must call `focus()` on the row whose
|
|
3965
|
+
* `tabindex` is 0 after arrow navigation (the tests' fixture does this with an
|
|
3966
|
+
* effect; the rows mode's span rows are reached by tabbing to the current one).
|
|
3513
3967
|
*/
|
|
3514
3968
|
/** One span row: a fraction of the parent span on a shared 0..1 axis. */
|
|
3515
3969
|
export interface TimelineRow {
|
|
@@ -3592,12 +4046,90 @@ export interface Timeline {
|
|
|
3592
4046
|
readonly rowProps: (id: number) => TimelineRowProps;
|
|
3593
4047
|
readonly rulerProps: TimelineRulerProps;
|
|
3594
4048
|
}
|
|
3595
|
-
/**
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
*/
|
|
3600
|
-
|
|
4049
|
+
/** One span of a real trace: a point on a shared axis, labelled, at a depth. */
|
|
4050
|
+
export interface TimelineSpan {
|
|
4051
|
+
readonly id: string;
|
|
4052
|
+
readonly label: string;
|
|
4053
|
+
/** Offset on the axis, in the axis' unit; `end` is the same axis, not a duration. */
|
|
4054
|
+
readonly start: number;
|
|
4055
|
+
readonly end: number;
|
|
4056
|
+
/** Nesting level, 0 for a root span. */
|
|
4057
|
+
readonly depth?: number;
|
|
4058
|
+
readonly status?: "ok" | "error" | "unset";
|
|
4059
|
+
}
|
|
4060
|
+
export interface TimelineAxis {
|
|
4061
|
+
/** The unit the numbers are in; the ruler labels its ticks with it. */
|
|
4062
|
+
readonly unit?: "ms" | "us" | "s";
|
|
4063
|
+
}
|
|
4064
|
+
/** The rows mode: real span data, read lazily like every other option. */
|
|
4065
|
+
export interface TimelineRowsOptions {
|
|
4066
|
+
readonly rows: ReadonlyArray<TimelineSpan>;
|
|
4067
|
+
readonly axis?: TimelineAxis | undefined;
|
|
4068
|
+
/** Controlled: the host owns the selection. */
|
|
4069
|
+
readonly selected?: string | undefined;
|
|
4070
|
+
/** A request to change the selection the host may ignore. */
|
|
4071
|
+
readonly onSelect?: ((id: string) => void) | undefined;
|
|
4072
|
+
}
|
|
4073
|
+
export interface TimelineTick {
|
|
4074
|
+
readonly value: number;
|
|
4075
|
+
readonly left: number;
|
|
4076
|
+
readonly label: string;
|
|
4077
|
+
}
|
|
4078
|
+
export interface TimelineListProps {
|
|
4079
|
+
readonly role: "list";
|
|
4080
|
+
readonly "aria-label": string;
|
|
4081
|
+
}
|
|
4082
|
+
export interface TimelineSpanRowProps {
|
|
4083
|
+
readonly role: "listitem";
|
|
4084
|
+
/** `depth + 1`: the nesting a flat list cannot show in the DOM. */
|
|
4085
|
+
readonly "aria-level": number;
|
|
4086
|
+
readonly "aria-current": "true" | undefined;
|
|
4087
|
+
/** A styling hook: the status the span ended with. */
|
|
4088
|
+
readonly "data-status": "ok" | "error" | "unset";
|
|
4089
|
+
readonly tabindex: number;
|
|
4090
|
+
readonly onClick: () => void;
|
|
4091
|
+
readonly onKeyDown: (event: KeyboardEvent) => void;
|
|
4092
|
+
}
|
|
4093
|
+
export interface TimelineBarProps {
|
|
4094
|
+
readonly "aria-hidden": "true";
|
|
4095
|
+
/** `left` / `width` are percentages of the extent; `--depth` is the row's depth. */
|
|
4096
|
+
readonly style: {
|
|
4097
|
+
readonly left: string;
|
|
4098
|
+
readonly width: string;
|
|
4099
|
+
readonly "--depth": string;
|
|
4100
|
+
};
|
|
4101
|
+
}
|
|
4102
|
+
export interface TimelineLabelProps {
|
|
4103
|
+
/** `--depth`, so the host indents the label (`padding-inline-start: calc(var(--depth) * 1rem)`) instead of nesting it. */
|
|
4104
|
+
readonly style: {
|
|
4105
|
+
readonly "--depth": string;
|
|
4106
|
+
};
|
|
4107
|
+
}
|
|
4108
|
+
export interface TimelineRulerRowsProps {
|
|
4109
|
+
readonly role: "presentation";
|
|
4110
|
+
readonly "aria-hidden": "true";
|
|
4111
|
+
/** Ticks of a nice step across the extent; render them as children. */
|
|
4112
|
+
readonly ticks: ReadonlyArray<TimelineTick>;
|
|
4113
|
+
}
|
|
4114
|
+
export interface TimelineRows {
|
|
4115
|
+
/** `{ start, end }` across the rows; `{ 0, 0 }` when there are none. */
|
|
4116
|
+
readonly extent: () => {
|
|
4117
|
+
readonly start: number;
|
|
4118
|
+
readonly end: number;
|
|
4119
|
+
};
|
|
4120
|
+
readonly depthOf: (id: string) => number;
|
|
4121
|
+
readonly listProps: TimelineListProps;
|
|
4122
|
+
readonly rowProps: (id: string) => TimelineSpanRowProps;
|
|
4123
|
+
readonly barProps: (id: string) => TimelineBarProps;
|
|
4124
|
+
readonly labelProps: (id: string) => TimelineLabelProps;
|
|
4125
|
+
readonly rulerProps: TimelineRulerRowsProps;
|
|
4126
|
+
/** Programmatic selection request; a controlled host may ignore it. */
|
|
4127
|
+
readonly select: (id: string) => void;
|
|
4128
|
+
readonly selectedId: Accessor<string | undefined>;
|
|
4129
|
+
}
|
|
4130
|
+
/** The demo five-span tree (`rows` absent) or real span data (`rows` given). */
|
|
4131
|
+
export declare function createTimeline(options: TimelineRowsOptions): TimelineRows;
|
|
4132
|
+
export declare function createTimeline(options?: TimelineOptions): Timeline;
|
|
3601
4133
|
/** A toast's urgency; `error` is the only tone that announces assertively. */
|
|
3602
4134
|
export type ToastTone = "info" | "success" | "error";
|
|
3603
4135
|
export interface ToastOptions {
|