diff --git a/src/jsx.d.ts b/src/jsx.d.ts index 2020bfbe79..49b0d76265 100644 --- a/src/jsx.d.ts +++ b/src/jsx.d.ts @@ -18,6 +18,8 @@ type Defaultize = Pick> : never; +type Booleanish = boolean | 'true' | 'false'; + export namespace JSXInternal { export type LibraryManagedAttributes = Component extends { defaultProps: infer Defaults; @@ -70,6 +72,10 @@ export namespace JSXInternal { subscribe(fn: (value: T) => void): () => void; } + export type Signalish = T | SignalLike; + + export type UnpackSignal = T extends SignalLike ? V : T; + export interface SVGAttributes extends HTMLAttributes { accentHeight?: @@ -1320,9 +1326,302 @@ export namespace JSXInternal { onTransitionEndCapture?: TransitionEventHandler; } + // All the WAI-ARIA 1.1 attributes from https://www.w3.org/TR/wai-aria-1.1/ + export interface AriaAttributes { + /** Identifies the currently active element when DOM focus is on a composite widget, textbox, group, or application. */ + 'aria-activedescendant'?: Signalish; + /** Indicates whether assistive technologies will present all, or only parts of, the changed region based on the change notifications defined by the aria-relevant attribute. */ + 'aria-atomic'?: Signalish; + /** + * Indicates whether inputting text could trigger display of one or more predictions of the user's intended value for an input and specifies how predictions would be + * presented if they are made. + */ + 'aria-autocomplete'?: Signalish< + 'none' | 'inline' | 'list' | 'both' | undefined + >; + /** Indicates an element is being modified and that assistive technologies MAY want to wait until the modifications are complete before exposing them to the user. */ + 'aria-busy'?: Signalish; + /** + * Indicates the current "checked" state of checkboxes, radio buttons, and other widgets. + * @see aria-pressed + * @see aria-selected. + */ + 'aria-checked'?: Signalish; + /** + * Defines the total number of columns in a table, grid, or treegrid. + * @see aria-colindex. + */ + 'aria-colcount'?: Signalish; + /** + * Defines an element's column index or position with respect to the total number of columns within a table, grid, or treegrid. + * @see aria-colcount + * @see aria-colspan. + */ + 'aria-colindex'?: Signalish; + /** + * Defines the number of columns spanned by a cell or gridcell within a table, grid, or treegrid. + * @see aria-colindex + * @see aria-rowspan. + */ + 'aria-colspan'?: Signalish; + /** + * Identifies the element (or elements) whose contents or presence are controlled by the current element. + * @see aria-owns. + */ + 'aria-controls'?: Signalish; + /** Indicates the element that represents the current item within a container or set of related elements. */ + 'aria-current'?: Signalish< + Booleanish | 'page' | 'step' | 'location' | 'date' | 'time' | undefined + >; + /** + * Identifies the element (or elements) that describes the object. + * @see aria-labelledby + */ + 'aria-describedby'?: Signalish; + /** + * Identifies the element that provides a detailed, extended description for the object. + * @see aria-describedby. + */ + 'aria-details'?: Signalish; + /** + * Indicates that the element is perceivable but disabled, so it is not editable or otherwise operable. + * @see aria-hidden + * @see aria-readonly. + */ + 'aria-disabled'?: Signalish; + /** + * Indicates what functions can be performed when a dragged object is released on the drop target. + * @deprecated in ARIA 1.1 + */ + 'aria-dropeffect'?: Signalish< + 'none' | 'copy' | 'execute' | 'link' | 'move' | 'popup' | undefined + >; + /** + * Identifies the element that provides an error message for the object. + * @see aria-invalid + * @see aria-describedby. + */ + 'aria-errormessage'?: Signalish; + /** Indicates whether the element, or another grouping element it controls, is currently expanded or collapsed. */ + 'aria-expanded'?: Signalish; + /** + * Identifies the next element (or elements) in an alternate reading order of content which, at the user's discretion, + * allows assistive technology to override the general default of reading in document source order. + */ + 'aria-flowto'?: Signalish; + /** + * Indicates an element's "grabbed" state in a drag-and-drop operation. + * @deprecated in ARIA 1.1 + */ + 'aria-grabbed'?: Signalish; + /** Indicates the availability and type of interactive popup element, such as menu or dialog, that can be triggered by an element. */ + 'aria-haspopup'?: Signalish< + Booleanish | 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog' | undefined + >; + /** + * Indicates whether the element is exposed to an accessibility API. + * @see aria-disabled. + */ + 'aria-hidden'?: Signalish; + /** + * Indicates the entered value does not conform to the format expected by the application. + * @see aria-errormessage. + */ + 'aria-invalid'?: Signalish; + /** Indicates keyboard shortcuts that an author has implemented to activate or give focus to an element. */ + 'aria-keyshortcuts'?: Signalish; + /** + * Defines a string value that labels the current element. + * @see aria-labelledby. + */ + 'aria-label'?: Signalish; + /** + * Identifies the element (or elements) that labels the current element. + * @see aria-describedby. + */ + 'aria-labelledby'?: Signalish; + /** Defines the hierarchical level of an element within a structure. */ + 'aria-level'?: Signalish; + /** Indicates that an element will be updated, and describes the types of updates the user agents, assistive technologies, and user can expect from the live region. */ + 'aria-live'?: Signalish<'off' | 'assertive' | 'polite' | undefined>; + /** Indicates whether an element is modal when displayed. */ + 'aria-modal'?: Signalish; + /** Indicates whether a text box accepts multiple lines of input or only a single line. */ + 'aria-multiline'?: Signalish; + /** Indicates that the user may select more than one item from the current selectable descendants. */ + 'aria-multiselectable'?: Signalish; + /** Indicates whether the element's orientation is horizontal, vertical, or unknown/ambiguous. */ + 'aria-orientation'?: Signalish<'horizontal' | 'vertical' | undefined>; + /** + * Identifies an element (or elements) in order to define a visual, functional, or contextual parent/child relationship + * between DOM elements where the DOM hierarchy cannot be used to represent the relationship. + * @see aria-controls. + */ + 'aria-owns'?: Signalish; + /** + * Defines a short hint (a word or short phrase) intended to aid the user with data entry when the control has no value. + * A hint could be a sample value or a brief description of the expected format. + */ + 'aria-placeholder'?: Signalish; + /** + * Defines an element's number or position in the current set of listitems or treeitems. Not required if all elements in the set are present in the DOM. + * @see aria-setsize. + */ + 'aria-posinset'?: Signalish; + /** + * Indicates the current "pressed" state of toggle buttons. + * @see aria-checked + * @see aria-selected. + */ + 'aria-pressed'?: Signalish; + /** + * Indicates that the element is not editable, but is otherwise operable. + * @see aria-disabled. + */ + 'aria-readonly'?: Signalish; + /** + * Indicates what notifications the user agent will trigger when the accessibility tree within a live region is modified. + * @see aria-atomic. + */ + 'aria-relevant'?: Signalish< + | 'additions' + | 'additions removals' + | 'additions text' + | 'all' + | 'removals' + | 'removals additions' + | 'removals text' + | 'text' + | 'text additions' + | 'text removals' + | undefined + >; + /** Indicates that user input is required on the element before a form may be submitted. */ + 'aria-required'?: Signalish; + /** Defines a human-readable, author-localized description for the role of an element. */ + 'aria-roledescription'?: Signalish; + /** + * Defines the total number of rows in a table, grid, or treegrid. + * @see aria-rowindex. + */ + 'aria-rowcount'?: Signalish; + /** + * Defines an element's row index or position with respect to the total number of rows within a table, grid, or treegrid. + * @see aria-rowcount + * @see aria-rowspan. + */ + 'aria-rowindex'?: Signalish; + /** + * Defines the number of rows spanned by a cell or gridcell within a table, grid, or treegrid. + * @see aria-rowindex + * @see aria-colspan. + */ + 'aria-rowspan'?: Signalish; + /** + * Indicates the current "selected" state of various widgets. + * @see aria-checked + * @see aria-pressed. + */ + 'aria-selected'?: Signalish; + /** + * Defines the number of items in the current set of listitems or treeitems. Not required if all elements in the set are present in the DOM. + * @see aria-posinset. + */ + 'aria-setsize'?: Signalish; + /** Indicates if items in a table or grid are sorted in ascending or descending order. */ + 'aria-sort'?: Signalish< + 'none' | 'ascending' | 'descending' | 'other' | undefined + >; + /** Defines the maximum allowed value for a range widget. */ + 'aria-valuemax'?: Signalish; + /** Defines the minimum allowed value for a range widget. */ + 'aria-valuemin'?: Signalish; + /** + * Defines the current value for a range widget. + * @see aria-valuetext. + */ + 'aria-valuenow'?: Signalish; + /** Defines the human readable text alternative of aria-valuenow for a range widget. */ + 'aria-valuetext'?: Signalish; + } + + // All the WAI-ARIA 1.1 role attribute values from https://www.w3.org/TR/wai-aria-1.1/#role_definitions + type AriaRole = + | 'alert' + | 'alertdialog' + | 'application' + | 'article' + | 'banner' + | 'button' + | 'cell' + | 'checkbox' + | 'columnheader' + | 'combobox' + | 'complementary' + | 'contentinfo' + | 'definition' + | 'dialog' + | 'directory' + | 'document' + | 'feed' + | 'figure' + | 'form' + | 'grid' + | 'gridcell' + | 'group' + | 'heading' + | 'img' + | 'link' + | 'list' + | 'listbox' + | 'listitem' + | 'log' + | 'main' + | 'marquee' + | 'math' + | 'menu' + | 'menubar' + | 'menuitem' + | 'menuitemcheckbox' + | 'menuitemradio' + | 'navigation' + | 'none' + | 'note' + | 'option' + | 'presentation' + | 'progressbar' + | 'radio' + | 'radiogroup' + | 'region' + | 'row' + | 'rowgroup' + | 'rowheader' + | 'scrollbar' + | 'search' + | 'searchbox' + | 'separator' + | 'slider' + | 'spinbutton' + | 'status' + | 'switch' + | 'tab' + | 'table' + | 'tablist' + | 'tabpanel' + | 'term' + | 'textbox' + | 'timer' + | 'toolbar' + | 'tooltip' + | 'tree' + | 'treegrid' + | 'treeitem' + | 'none presentation'; + export interface HTMLAttributes extends ClassAttributes, - DOMAttributes { + DOMAttributes, + AriaAttributes { // Standard HTML Attributes accept?: string | undefined | SignalLike; acceptCharset?: string | undefined | SignalLike; @@ -1499,149 +1798,7 @@ export namespace JSXInternal { rel?: string | undefined | SignalLike; required?: boolean | undefined | SignalLike; reversed?: boolean | undefined | SignalLike; - role?: - | 'alert' - | 'alertdialog' - | 'application' - | 'article' - | 'banner' - | 'button' - | 'cell' - | 'checkbox' - | 'columnheader' - | 'combobox' - | 'complementary' - | 'contentinfo' - | 'definition' - | 'dialog' - | 'directory' - | 'document' - | 'feed' - | 'figure' - | 'form' - | 'grid' - | 'gridcell' - | 'group' - | 'heading' - | 'img' - | 'link' - | 'list' - | 'listbox' - | 'listitem' - | 'log' - | 'main' - | 'marquee' - | 'math' - | 'menu' - | 'menubar' - | 'menuitem' - | 'menuitemcheckbox' - | 'menuitemradio' - | 'navigation' - | 'none' - | 'note' - | 'option' - | 'presentation' - | 'progressbar' - | 'radio' - | 'radiogroup' - | 'region' - | 'row' - | 'rowgroup' - | 'rowheader' - | 'scrollbar' - | 'search' - | 'searchbox' - | 'separator' - | 'slider' - | 'spinbutton' - | 'status' - | 'switch' - | 'tab' - | 'table' - | 'tablist' - | 'tabpanel' - | 'term' - | 'textbox' - | 'timer' - | 'toolbar' - | 'tooltip' - | 'tree' - | 'treegrid' - | 'treeitem' - | undefined - | SignalLike< - | 'alert' - | 'alertdialog' - | 'application' - | 'article' - | 'banner' - | 'button' - | 'cell' - | 'checkbox' - | 'columnheader' - | 'combobox' - | 'complementary' - | 'contentinfo' - | 'definition' - | 'dialog' - | 'directory' - | 'document' - | 'feed' - | 'figure' - | 'form' - | 'grid' - | 'gridcell' - | 'group' - | 'heading' - | 'img' - | 'link' - | 'list' - | 'listbox' - | 'listitem' - | 'log' - | 'main' - | 'marquee' - | 'math' - | 'menu' - | 'menubar' - | 'menuitem' - | 'menuitemcheckbox' - | 'menuitemradio' - | 'navigation' - | 'none' - | 'note' - | 'option' - | 'presentation' - | 'progressbar' - | 'radio' - | 'radiogroup' - | 'region' - | 'row' - | 'rowgroup' - | 'rowheader' - | 'scrollbar' - | 'search' - | 'searchbox' - | 'separator' - | 'slider' - | 'spinbutton' - | 'status' - | 'switch' - | 'tab' - | 'table' - | 'tablist' - | 'tabpanel' - | 'term' - | 'textbox' - | 'timer' - | 'toolbar' - | 'tooltip' - | 'tree' - | 'treegrid' - | 'treeitem' - | undefined - >; + role?: AriaRole | undefined | SignalLike; rows?: number | undefined | SignalLike; rowSpan?: number | undefined | SignalLike; sandbox?: string | undefined | SignalLike; diff --git a/test/ts/dom-attributes-test.tsx b/test/ts/dom-attributes-test.tsx new file mode 100644 index 0000000000..57fda6949d --- /dev/null +++ b/test/ts/dom-attributes-test.tsx @@ -0,0 +1,47 @@ +import { createElement, Fragment, JSX } from 'preact'; + +function createSignal(value: T): JSX.SignalLike { + return { + value, + peek() { + return value; + }, + subscribe() { + return () => {}; + } + }; +} + +// @ts-expect-error We should correctly type aria attributes like autocomplete +const badAriaValues =
; +const validAriaValues =
; +const undefAriaValues =
; +const noAriaValues =
; + +const signalBadAriaValues = ( + // @ts-expect-error We should correctly type aria attributes like autocomplete +
+); +const signalValidAriaValues = ( +
+); +const signalValidAriaValues2 = ( +
+ )} + /> +); + +const validRole =
; +const fallbackRole =
; + +const booleanishTest = ( + <> +
+
+
+
+
+ +); diff --git a/test/ts/tsconfig.json b/test/ts/tsconfig.json index 36621f34f1..10de880f53 100644 --- a/test/ts/tsconfig.json +++ b/test/ts/tsconfig.json @@ -9,7 +9,12 @@ "types": [], "forceConsistentCasingInFileNames": true, "jsx": "react", - "jsxFactory": "createElement" + "jsxFactory": "createElement", + "jsxFragmentFactory": "Fragment", + "paths": { + "preact": ["../../"], + "preact/*": ["../../*"] + } }, "include": ["./**/*.ts", "./**/*.tsx"] }