From 54064530442b53ccf5591f06c8f9dce77b91a34d Mon Sep 17 00:00:00 2001 From: Tropical <42101043+Tropix126@users.noreply.github.com> Date: Sun, 6 Oct 2024 04:05:21 -0400 Subject: [PATCH] feat: add attributes for most AccessKit properties (#882) * feat: add attributes for most AccessKit properties * fix: update components * fix: tests * fix `NodeBuilder` unwrap assumption * fix: Proper support for keyboard navigation for Radio (#880) * fix: Proper incremental redraws for elements with outer or center borders * chore: torin changes * feat: Proper support for keyboard navigation with Radio * fix: Update tests * chore: Update tests * feat: Only focus focusable nodes * chore: Update tests * chore: Update tests * feat: add attributes for most AccessKit properties * fix: update components * fix: tests * revert components changes * fix accessibility nodes not being added to tree * feat: use inner text for `paragraph`/`label` names if none is provided * fix accessibility tests * fmt, lint * reduce out-of-scope changes * fmt * refactor: make `a11y_role` attribute kebab case * lint * fmt again * fix bad accessibility state merge * use `Role::parse` rather than `serde_json` * fix bad role in test * fix role parsing test * update or remove redundant roles from components --------- Co-authored-by: Marc Espin --- crates/components/src/input.rs | 2 +- crates/components/src/network_image.rs | 2 +- .../components/src/scroll_views/scroll_bar.rs | 2 +- .../src/scroll_views/scroll_view.rs | 2 +- .../src/scroll_views/virtual_scroll_view.rs | 2 +- crates/core/src/accessibility/tree.rs | 44 +-- crates/elements/src/definitions.rs | 363 ++++++++++++++++-- crates/hooks/src/use_init_native_platform.rs | 3 - crates/native-core/src/attributes.rs | 234 ++++++++++- crates/state/src/accessibility.rs | 362 +++++++++++++++-- crates/state/src/values/accessibility.rs | 349 +++++++++++++++++ crates/state/src/values/mod.rs | 1 + examples/accessibility.rs | 2 +- 13 files changed, 1265 insertions(+), 103 deletions(-) create mode 100644 crates/state/src/values/accessibility.rs diff --git a/crates/components/src/input.rs b/crates/components/src/input.rs index f29017ab0..3443d18bc 100644 --- a/crates/components/src/input.rs +++ b/crates/components/src/input.rs @@ -217,7 +217,7 @@ pub fn Input( main_align: "center", cursor_reference, a11y_id, - a11y_role: "textInput", + a11y_role: "text-input", a11y_auto_focus: "{auto_focus}", onkeydown, onkeyup, diff --git a/crates/components/src/network_image.rs b/crates/components/src/network_image.rs index 45d096d47..54d60b776 100644 --- a/crates/components/src/network_image.rs +++ b/crates/components/src/network_image.rs @@ -124,7 +124,7 @@ pub fn NetworkImage(props: NetworkImageProps) -> Element { a11y_id, image_data, a11y_role: "image", - a11y_alt: alt + a11y_name: alt }) } else if *status.read() == ImageState::Loading { if let Some(loading_element) = &props.loading { diff --git a/crates/components/src/scroll_views/scroll_bar.rs b/crates/components/src/scroll_views/scroll_bar.rs index 80c0ba7da..e726ca9a2 100644 --- a/crates/components/src/scroll_views/scroll_bar.rs +++ b/crates/components/src/scroll_views/scroll_bar.rs @@ -56,7 +56,7 @@ pub fn ScrollBar( rsx!( rect { overflow: "clip", - a11y_role:"scrollBar", + a11y_role: "scroll-bar", width: "{width}", height: "{height}", offset_x: "{offset_x}", diff --git a/crates/components/src/scroll_views/scroll_view.rs b/crates/components/src/scroll_views/scroll_view.rs index d82d768d7..0f4ac98f9 100644 --- a/crates/components/src/scroll_views/scroll_view.rs +++ b/crates/components/src/scroll_views/scroll_view.rs @@ -372,7 +372,7 @@ pub fn ScrollView( rsx!( rect { - a11y_role:"scrollView", + a11y_role:"scroll-view", overflow: "clip", direction: "horizontal", width, diff --git a/crates/components/src/scroll_views/virtual_scroll_view.rs b/crates/components/src/scroll_views/virtual_scroll_view.rs index 74f9d51ff..2365ed697 100644 --- a/crates/components/src/scroll_views/virtual_scroll_view.rs +++ b/crates/components/src/scroll_views/virtual_scroll_view.rs @@ -447,7 +447,7 @@ pub fn VirtualScrollView< rsx!( rect { - a11y_role:"scrollView", + a11y_role: "scroll-view", overflow: "clip", direction: "horizontal", width: "{width}", diff --git a/crates/core/src/accessibility/tree.rs b/crates/core/src/accessibility/tree.rs index d427b22d2..565de7d68 100644 --- a/crates/core/src/accessibility/tree.rs +++ b/crates/core/src/accessibility/tree.rs @@ -185,7 +185,6 @@ impl AccessibilityTree { { let accessibility_node = Self::create_node(&node_ref, layout_node, node_accessibility_state); - let accessibility_id = node_ref.get_accessibility_id().unwrap(); nodes.push((accessibility_id, accessibility_node)); @@ -337,7 +336,18 @@ impl AccessibilityTree { let transform_state = &*node_ref.get::().unwrap(); let node_type = node_ref.node_type(); - let mut builder = NodeBuilder::new(Role::default()); + let mut builder = match node_type.tag() { + // Make the root accessibility node. + Some(&TagName::Root) => NodeBuilder::new(Role::Window), + + // All other node types will either don't have a builder (but don't support + // accessibility attributes like with `text`) or have their builder made for + // them already. + Some(_) => node_accessibility.builder.clone().unwrap(), + + // Tag-less nodes can't have accessibility state + None => unreachable!(), + }; // Set children let children = node_ref.get_accessibility_children(); @@ -352,6 +362,14 @@ impl AccessibilityTree { y1: area.max_y(), }); + if let NodeType::Element(node) = &*node_type { + if matches!(node.tag, TagName::Label | TagName::Paragraph) && builder.name().is_none() { + if let Some(inner_text) = node_ref.get_inner_texts() { + builder.set_name(inner_text); + } + } + } + // Set focusable action // This will cause assistive technology to offer the user an option // to focus the current element if it supports it. @@ -458,28 +476,6 @@ impl AccessibilityTree { )); } - // Set text value - if let Some(alt) = &node_accessibility.a11y_alt { - builder.set_value(alt.to_owned()); - } else if let Some(value) = node_ref.get_inner_texts() { - builder.set_value(value); - builder.set_role(Role::Label); - } - - // Set name - if let Some(name) = &node_accessibility.a11y_name { - builder.set_name(name.to_owned()); - } - - // Set role - if let Some(role) = node_accessibility.a11y_role { - builder.set_role(role); - } - // Set root role - if node_ref.id() == node_ref.real_dom().root_id() { - builder.set_role(Role::Window); - } - builder.build() } } diff --git a/crates/elements/src/definitions.rs b/crates/elements/src/definitions.rs index e6024b2f1..abeb5c600 100644 --- a/crates/elements/src/definitions.rs +++ b/crates/elements/src/definitions.rs @@ -232,18 +232,81 @@ builder_constructors! { #[doc = include_str!("_docs/attributes/spacing.md")] spacing: String, - a11y_auto_focus: String, - a11y_name: String, - a11y_role:String, - a11y_id: AccessibilityId, - a11y_alt: String, - a11y_focusable: String, canvas_reference: String, layer: String, offset_y: String, offset_x: String, reference: Reference, cursor_reference: CursorReference, + + a11y_id: String, + a11y_focusable: String, + a11y_auto_focus: String, + a11y_name: String, + a11y_description: String, + a11y_value: String, + a11y_access_key: String, + a11y_author_id: String, + a11y_keyboard_shortcut: String, + a11y_language: String, + a11y_placeholder: String, + a11y_role_description: String, + a11y_state_description: String, + a11y_tooltip: String, + a11y_url: String, + a11y_row_index_text: String, + a11y_column_index_text: String, + a11y_scroll_x: String, + a11y_scroll_x_min: String, + a11y_scroll_x_max: String, + a11y_scroll_y: String, + a11y_scroll_y_min: String, + a11y_scroll_y_max: String, + a11y_numeric_value: String, + a11y_min_numeric_value: String, + a11y_max_numeric_value: String, + a11y_numeric_value_step: String, + a11y_numeric_value_jump: String, + a11y_row_count: String, + a11y_column_count: String, + a11y_row_index: String, + a11y_column_index: String, + a11y_row_span: String, + a11y_column_span: String, + a11y_level: String, + a11y_size_of_set: String, + a11y_position_in_set: String, + a11y_color_value: String, + a11y_expanded: String, + a11y_selected: String, + a11y_hovered: String, + a11y_hidden: String, + a11y_linked: String, + a11y_multiselectable: String, + a11y_required: String, + a11y_visited: String, + a11y_busy: String, + a11y_live_atomic: String, + a11y_modal: String, + a11y_touch_transparent: String, + a11y_read_only: String, + a11y_disabled: String, + a11y_is_spelling_error: String, + a11y_is_grammar_error: String, + a11y_is_search_match: String, + a11y_is_suggestion: String, + a11y_role: String, + a11y_invalid: String, + a11y_toggled: String, + a11y_live: String, + a11y_default_action_verb: String, + a11y_orientation: String, + a11y_sort_direction: String, + a11y_current: String, + a11y_auto_complete: String, + a11y_has_popup: String, + a11y_list_style: String, + a11y_vertical_offset: String, }; /// `label` simply let's you display some text. /// @@ -305,12 +368,75 @@ builder_constructors! { opacity: String, layer: String, + + a11y_id: String, a11y_auto_focus: String, - a11y_name: String, - a11y_role:String, - a11y_id: AccessibilityId, - a11y_alt: String, a11y_focusable: String, + a11y_name: String, + a11y_description: String, + a11y_value: String, + a11y_access_key: String, + a11y_author_id: String, + a11y_keyboard_shortcut: String, + a11y_language: String, + a11y_placeholder: String, + a11y_role_description: String, + a11y_state_description: String, + a11y_tooltip: String, + a11y_url: String, + a11y_row_index_text: String, + a11y_column_index_text: String, + a11y_scroll_x: String, + a11y_scroll_x_min: String, + a11y_scroll_x_max: String, + a11y_scroll_y: String, + a11y_scroll_y_min: String, + a11y_scroll_y_max: String, + a11y_numeric_value: String, + a11y_min_numeric_value: String, + a11y_max_numeric_value: String, + a11y_numeric_value_step: String, + a11y_numeric_value_jump: String, + a11y_row_count: String, + a11y_column_count: String, + a11y_row_index: String, + a11y_column_index: String, + a11y_row_span: String, + a11y_column_span: String, + a11y_level: String, + a11y_size_of_set: String, + a11y_position_in_set: String, + a11y_color_value: String, + a11y_expanded: String, + a11y_selected: String, + a11y_hovered: String, + a11y_hidden: String, + a11y_linked: String, + a11y_multiselectable: String, + a11y_required: String, + a11y_visited: String, + a11y_busy: String, + a11y_live_atomic: String, + a11y_modal: String, + a11y_touch_transparent: String, + a11y_read_only: String, + a11y_disabled: String, + a11y_is_spelling_error: String, + a11y_is_grammar_error: String, + a11y_is_search_match: String, + a11y_is_suggestion: String, + a11y_role: String, + a11y_invalid: String, + a11y_toggled: String, + a11y_live: String, + a11y_default_action_verb: String, + a11y_orientation: String, + a11y_sort_direction: String, + a11y_current: String, + a11y_auto_complete: String, + a11y_has_popup: String, + a11y_list_style: String, + a11y_vertical_offset: String, }; /// `paragraph` element let's you build texts with different styles. /// @@ -384,15 +510,79 @@ builder_constructors! { cursor_color: String, cursor_mode: String, cursor_id: String, - a11y_auto_focus: String, - a11y_name: String, - a11y_role:String, - a11y_id: AccessibilityId, - a11y_alt: String, - a11y_focusable: String, + highlights: String, highlight_color: String, highlight_mode: String, + + a11y_id: String, + a11y_focusable: String, + a11y_auto_focus: String, + a11y_name: String, + a11y_description: String, + a11y_value: String, + a11y_access_key: String, + a11y_author_id: String, + a11y_keyboard_shortcut: String, + a11y_language: String, + a11y_placeholder: String, + a11y_role_description: String, + a11y_state_description: String, + a11y_tooltip: String, + a11y_url: String, + a11y_row_index_text: String, + a11y_column_index_text: String, + a11y_scroll_x: String, + a11y_scroll_x_min: String, + a11y_scroll_x_max: String, + a11y_scroll_y: String, + a11y_scroll_y_min: String, + a11y_scroll_y_max: String, + a11y_numeric_value: String, + a11y_min_numeric_value: String, + a11y_max_numeric_value: String, + a11y_numeric_value_step: String, + a11y_numeric_value_jump: String, + a11y_row_count: String, + a11y_column_count: String, + a11y_row_index: String, + a11y_column_index: String, + a11y_row_span: String, + a11y_column_span: String, + a11y_level: String, + a11y_size_of_set: String, + a11y_position_in_set: String, + a11y_color_value: String, + a11y_expanded: String, + a11y_selected: String, + a11y_hovered: String, + a11y_hidden: String, + a11y_linked: String, + a11y_multiselectable: String, + a11y_required: String, + a11y_visited: String, + a11y_busy: String, + a11y_live_atomic: String, + a11y_modal: String, + a11y_touch_transparent: String, + a11y_read_only: String, + a11y_disabled: String, + a11y_is_spelling_error: String, + a11y_is_grammar_error: String, + a11y_is_search_match: String, + a11y_is_suggestion: String, + a11y_role: String, + a11y_invalid: String, + a11y_toggled: String, + a11y_live: String, + a11y_default_action_verb: String, + a11y_orientation: String, + a11y_sort_direction: String, + a11y_current: String, + a11y_auto_complete: String, + a11y_has_popup: String, + a11y_list_style: String, + a11y_vertical_offset: String, }; /// `text` element is simply a text span used for the `paragraph` element. text { @@ -453,11 +643,75 @@ builder_constructors! { image_data: String, image_reference: String, + + a11y_id: String, a11y_auto_focus: String, + a11y_focusable: String, a11y_name: String, - a11y_role:String, - a11y_id: AccessibilityId, - a11y_alt: String, + a11y_description: String, + a11y_value: String, + a11y_access_key: String, + a11y_author_id: String, + a11y_keyboard_shortcut: String, + a11y_language: String, + a11y_placeholder: String, + a11y_role_description: String, + a11y_state_description: String, + a11y_tooltip: String, + a11y_url: String, + a11y_row_index_text: String, + a11y_column_index_text: String, + a11y_scroll_x: String, + a11y_scroll_x_min: String, + a11y_scroll_x_max: String, + a11y_scroll_y: String, + a11y_scroll_y_min: String, + a11y_scroll_y_max: String, + a11y_numeric_value: String, + a11y_min_numeric_value: String, + a11y_max_numeric_value: String, + a11y_numeric_value_step: String, + a11y_numeric_value_jump: String, + a11y_row_count: String, + a11y_column_count: String, + a11y_row_index: String, + a11y_column_index: String, + a11y_row_span: String, + a11y_column_span: String, + a11y_level: String, + a11y_size_of_set: String, + a11y_position_in_set: String, + a11y_color_value: String, + a11y_expanded: String, + a11y_selected: String, + a11y_hovered: String, + a11y_hidden: String, + a11y_linked: String, + a11y_multiselectable: String, + a11y_required: String, + a11y_visited: String, + a11y_busy: String, + a11y_live_atomic: String, + a11y_modal: String, + a11y_touch_transparent: String, + a11y_read_only: String, + a11y_disabled: String, + a11y_is_spelling_error: String, + a11y_is_grammar_error: String, + a11y_is_search_match: String, + a11y_is_suggestion: String, + a11y_role: String, + a11y_invalid: String, + a11y_toggled: String, + a11y_live: String, + a11y_default_action_verb: String, + a11y_orientation: String, + a11y_sort_direction: String, + a11y_current: String, + a11y_auto_complete: String, + a11y_has_popup: String, + a11y_list_style: String, + a11y_vertical_offset: String, }; /// `svg` element let's you display SVG code. /// @@ -492,12 +746,75 @@ builder_constructors! { svg_data: String, svg_content: String, + + a11y_id: String, + a11y_focusable: String, a11y_auto_focus: String, a11y_name: String, - a11y_role:String, - a11y_id: AccessibilityId, - a11y_alt: String, - a11y_focusable: String, + a11y_description: String, + a11y_value: String, + a11y_access_key: String, + a11y_author_id: String, + a11y_keyboard_shortcut: String, + a11y_language: String, + a11y_placeholder: String, + a11y_role_description: String, + a11y_state_description: String, + a11y_tooltip: String, + a11y_url: String, + a11y_row_index_text: String, + a11y_column_index_text: String, + a11y_scroll_x: String, + a11y_scroll_x_min: String, + a11y_scroll_x_max: String, + a11y_scroll_y: String, + a11y_scroll_y_min: String, + a11y_scroll_y_max: String, + a11y_numeric_value: String, + a11y_min_numeric_value: String, + a11y_max_numeric_value: String, + a11y_numeric_value_step: String, + a11y_numeric_value_jump: String, + a11y_row_count: String, + a11y_column_count: String, + a11y_row_index: String, + a11y_column_index: String, + a11y_row_span: String, + a11y_column_span: String, + a11y_level: String, + a11y_size_of_set: String, + a11y_position_in_set: String, + a11y_color_value: String, + a11y_expanded: String, + a11y_selected: String, + a11y_hovered: String, + a11y_hidden: String, + a11y_linked: String, + a11y_multiselectable: String, + a11y_required: String, + a11y_visited: String, + a11y_busy: String, + a11y_live_atomic: String, + a11y_modal: String, + a11y_touch_transparent: String, + a11y_read_only: String, + a11y_disabled: String, + a11y_is_spelling_error: String, + a11y_is_grammar_error: String, + a11y_is_search_match: String, + a11y_is_suggestion: String, + a11y_role: String, + a11y_invalid: String, + a11y_toggled: String, + a11y_live: String, + a11y_default_action_verb: String, + a11y_orientation: String, + a11y_sort_direction: String, + a11y_current: String, + a11y_auto_complete: String, + a11y_has_popup: String, + a11y_list_style: String, + a11y_vertical_offset: String, }; } diff --git a/crates/hooks/src/use_init_native_platform.rs b/crates/hooks/src/use_init_native_platform.rs index bb5dffd64..aa267f673 100644 --- a/crates/hooks/src/use_init_native_platform.rs +++ b/crates/hooks/src/use_init_native_platform.rs @@ -155,7 +155,6 @@ mod test { let focus = use_focus(); rsx!(rect { a11y_id: focus.attribute(), - a11y_role: "genericContainer", width: "100%", height: "50%", }) @@ -223,12 +222,10 @@ mod test { rsx!( rect { a11y_id: focus_1.attribute(), - a11y_role: "genericContainer", a11y_auto_focus: "true", } rect { a11y_id: focus_2.attribute(), - a11y_role: "genericContainer", a11y_auto_focus: "true", } ) diff --git a/crates/native-core/src/attributes.rs b/crates/native-core/src/attributes.rs index f91f42fdc..1e7f2713a 100644 --- a/crates/native-core/src/attributes.rs +++ b/crates/native-core/src/attributes.rs @@ -43,12 +43,6 @@ pub enum AttributeName { PositionLeft, Opacity, Content, - A11YAutoFocus, - A11YName, - A11YFocusable, - A11YRole, - A11YId, - A11YAlt, CanvasReference, Layer, OffsetY, @@ -67,6 +61,160 @@ pub enum AttributeName { SvgData, SvgContent, Spacing, + + // Focus + A11yId, + A11yFocusable, + A11yAutoFocus, + + // Some internal notes about these accessibility attributes: + // + // - These are mostly derived from AccessKit's [`Node`] struct, with minor + // modifications to fit Freya's needs. These modifications are documented. + // + // - Some properties are commented out, meaning they are yet to be implemented. + // This is typically due to it being unclear how to represent these in Freya's + // attribute system (such as the association types, which will likely need + // some kind of ID system). + // + // - Any AccessKit properties that can be automatically calculated from style + // attributes or measured from torin are not included here, and are instead + // added in Freya's [`AccessibilityManager`] struct. + + // Vec associations + // A11yControls, + // A11yDetails, + // A11yDescribedBy, + // A11yFlowTo, + // A11yLabelledBy, + // A11yOwns, + // A11yRadioGroup, + + // NodeId associations + // ActiveDescendant, + // A11yErrorMessage, + // A11yInPageLinkTarget, + // A11yMemberOf, + // A11yNextOnLine, + // A11yPreviousOnLine, + // A11yPopupFor, + + // String + A11yName, + A11yDescription, + A11yValue, + A11yAccessKey, + A11yAuthorId, + // These three attributes are intended for assistive tech that parse MathML, + // which we don't support at the moment anyways. Unlikely to be implemented. + // A11yClassName, + // A11yHtmlTag, + // A11yInnerHtml, + A11yKeyboardShortcut, + A11yLanguage, + A11yPlaceholder, + A11yRoleDescription, + A11yStateDescription, + A11yTooltip, + A11yUrl, + A11yRowIndexText, + A11yColumnIndexText, + + // f64 + A11yScrollX, + A11yScrollXMin, + A11yScrollXMax, + A11yScrollY, + A11yScrollYMin, + A11yScrollYMax, + A11yNumericValue, + A11yMinNumericValue, + A11yMaxNumericValue, + A11yNumericValueStep, + A11yNumericValueJump, + + // usize + A11yRowCount, + A11yColumnCount, + A11yRowIndex, + A11yColumnIndex, + A11yRowSpan, + A11yColumnSpan, + A11yLevel, + A11ySizeOfSet, + A11yPositionInSet, + + // Color + A11yColorValue, + + // TODO: The following two categories are for inline text. They should be implemented + // automatically in [`AccessibilityManager`] based on Skia text measurement on text. + // spans. These really shouldn't be here (they should never have to be manually provided + // as an attribute), but I've left them here as a reminder to implement inline text data. + // + // See AccessKit's documentation for inline text measurements here: + // - + // + // Chromium also has a good writeup on how it measures inline text spans: + // - + + // LengthSlice + // A11yCharacterLengths, + // A11yWordLengths, + + // CoordSlice + // A11yCharacterPositions, + // A11yCharacterWidths, + + // bool + A11yExpanded, + A11ySelected, + + // bitflag + // TODO: This might be able to be determined automatically, + // but i'm not sure what ARIA property it corresponds to + // or its actual purpose. + A11yHovered, + A11yHidden, + A11yLinked, + A11yMultiselectable, + A11yRequired, + A11yVisited, + A11yBusy, + A11yLiveAtomic, + A11yModal, + A11yTouchTransparent, + A11yReadOnly, + A11yDisabled, + A11yIsSpellingError, + A11yIsGrammarError, + A11yIsSearchMatch, + A11yIsSuggestion, + + // Unique enums + A11yRole, + A11yInvalid, + A11yToggled, + A11yLive, + A11yDefaultActionVerb, + A11yOrientation, + A11ySortDirection, + A11yCurrent, // called AriaCurrent in accesskit, but that's a pretty poor name + A11yAutoComplete, + A11yHasPopup, + // This one is kind of weird to include, given it's reflecting a CSS property + // not in Freya for the HTML
    /
  • tags, but it can maybe be useful for + // language-specific semantics. + A11yListStyle, + A11yVerticalOffset, + // Other + // This could probably be inferred from Freya's text editing hook, but it's also + // a little strange in the data it expects. + // A11yTextSelection, + // A11yCustomActions, // Needs a special syntax or custom attribute value' + + // TODO: Some way to specify builtin AccessKit actions, as well as a way to + // handle actions in the form of an event. } impl FromStr for AttributeName { @@ -115,12 +263,6 @@ impl FromStr for AttributeName { "position_left" => Ok(AttributeName::PositionLeft), "opacity" => Ok(AttributeName::Opacity), "content" => Ok(AttributeName::Content), - "a11y_auto_focus" => Ok(AttributeName::A11YAutoFocus), - "a11y_name" => Ok(AttributeName::A11YName), - "a11y_role" => Ok(AttributeName::A11YRole), - "a11y_id" => Ok(AttributeName::A11YId), - "a11y_alt" => Ok(AttributeName::A11YAlt), - "a11y_focusable" => Ok(AttributeName::A11YFocusable), "canvas_reference" => Ok(AttributeName::CanvasReference), "layer" => Ok(AttributeName::Layer), "offset_y" => Ok(AttributeName::OffsetY), @@ -139,6 +281,74 @@ impl FromStr for AttributeName { "svg_data" => Ok(AttributeName::SvgData), "svg_content" => Ok(AttributeName::SvgContent), "spacing" => Ok(AttributeName::Spacing), + "a11y_id" => Ok(AttributeName::A11yId), + "a11y_focusable" => Ok(AttributeName::A11yFocusable), + "a11y_auto_focus" => Ok(AttributeName::A11yAutoFocus), + "a11y_name" => Ok(AttributeName::A11yName), + "a11y_description" => Ok(AttributeName::A11yDescription), + "a11y_value" => Ok(AttributeName::A11yValue), + "a11y_access_key" => Ok(AttributeName::A11yAccessKey), + "a11y_author_id" => Ok(AttributeName::A11yAuthorId), + "a11y_keyboard_shortcut" => Ok(AttributeName::A11yKeyboardShortcut), + "a11y_language" => Ok(AttributeName::A11yLanguage), + "a11y_placeholder" => Ok(AttributeName::A11yPlaceholder), + "a11y_role_description" => Ok(AttributeName::A11yRoleDescription), + "a11y_state_description" => Ok(AttributeName::A11yStateDescription), + "a11y_tooltip" => Ok(AttributeName::A11yTooltip), + "a11y_url" => Ok(AttributeName::A11yUrl), + "a11y_row_index_text" => Ok(AttributeName::A11yRowIndexText), + "a11y_column_index_text" => Ok(AttributeName::A11yColumnIndexText), + "a11y_scroll_x" => Ok(AttributeName::A11yScrollX), + "a11y_scroll_x_min" => Ok(AttributeName::A11yScrollXMin), + "a11y_scroll_x_max" => Ok(AttributeName::A11yScrollXMax), + "a11y_scroll_y" => Ok(AttributeName::A11yScrollY), + "a11y_scroll_y_min" => Ok(AttributeName::A11yScrollYMin), + "a11y_scroll_y_max" => Ok(AttributeName::A11yScrollYMax), + "a11y_numeric_value" => Ok(AttributeName::A11yNumericValue), + "a11y_min_numeric_value" => Ok(AttributeName::A11yMinNumericValue), + "a11y_max_numeric_value" => Ok(AttributeName::A11yMaxNumericValue), + "a11y_numeric_value_step" => Ok(AttributeName::A11yNumericValueStep), + "a11y_numeric_value_jump" => Ok(AttributeName::A11yNumericValueJump), + "a11y_row_count" => Ok(AttributeName::A11yRowCount), + "a11y_column_count" => Ok(AttributeName::A11yColumnCount), + "a11y_row_index" => Ok(AttributeName::A11yRowIndex), + "a11y_column_index" => Ok(AttributeName::A11yColumnIndex), + "a11y_row_span" => Ok(AttributeName::A11yRowSpan), + "a11y_column_span" => Ok(AttributeName::A11yColumnSpan), + "a11y_level" => Ok(AttributeName::A11yLevel), + "a11y_size_of_set" => Ok(AttributeName::A11ySizeOfSet), + "a11y_position_in_set" => Ok(AttributeName::A11yPositionInSet), + "a11y_color_value" => Ok(AttributeName::A11yColorValue), + "a11y_expanded" => Ok(AttributeName::A11yExpanded), + "a11y_selected" => Ok(AttributeName::A11ySelected), + "a11y_hovered" => Ok(AttributeName::A11yHovered), + "a11y_hidden" => Ok(AttributeName::A11yHidden), + "a11y_linked" => Ok(AttributeName::A11yLinked), + "a11y_multiselectable" => Ok(AttributeName::A11yMultiselectable), + "a11y_required" => Ok(AttributeName::A11yRequired), + "a11y_visited" => Ok(AttributeName::A11yVisited), + "a11y_busy" => Ok(AttributeName::A11yBusy), + "a11y_live_atomic" => Ok(AttributeName::A11yLiveAtomic), + "a11y_modal" => Ok(AttributeName::A11yModal), + "a11y_touch_transparent" => Ok(AttributeName::A11yTouchTransparent), + "a11y_read_only" => Ok(AttributeName::A11yReadOnly), + "a11y_disabled" => Ok(AttributeName::A11yDisabled), + "a11y_is_spelling_error" => Ok(AttributeName::A11yIsSpellingError), + "a11y_is_grammar_error" => Ok(AttributeName::A11yIsGrammarError), + "a11y_is_search_match" => Ok(AttributeName::A11yIsSearchMatch), + "a11y_is_suggestion" => Ok(AttributeName::A11yIsSuggestion), + "a11y_role" => Ok(AttributeName::A11yRole), + "a11y_invalid" => Ok(AttributeName::A11yInvalid), + "a11y_toggled" => Ok(AttributeName::A11yToggled), + "a11y_live" => Ok(AttributeName::A11yLive), + "a11y_default_action_verb" => Ok(AttributeName::A11yDefaultActionVerb), + "a11y_orientation" => Ok(AttributeName::A11yOrientation), + "a11y_sort_direction" => Ok(AttributeName::A11ySortDirection), + "a11y_current" => Ok(AttributeName::A11yCurrent), + "a11y_auto_complete" => Ok(AttributeName::A11yAutoComplete), + "a11y_has_popup" => Ok(AttributeName::A11yHasPopup), + "a11y_list_style" => Ok(AttributeName::A11yListStyle), + "a11y_vertical_offset" => Ok(AttributeName::A11yVerticalOffset), _ => Err(format!("{attr} not supported.")), } } diff --git a/crates/state/src/accessibility.rs b/crates/state/src/accessibility.rs index 1fb90b874..2e7ecb261 100644 --- a/crates/state/src/accessibility.rs +++ b/crates/state/src/accessibility.rs @@ -4,13 +4,26 @@ use std::sync::{ }; use accesskit::{ + AriaCurrent, + AutoComplete, + DefaultActionVerb, + HasPopup, + Invalid, + ListStyle, + Live, + NodeBuilder, NodeId as AccessibilityId, + Orientation, Role, + SortDirection, + Toggled, + VerticalOffset, }; use freya_common::{ AccessibilityDirtyNodes, AccessibilityGenerator, }; +use freya_engine::prelude::Color; use freya_native_core::{ attributes::AttributeName, exports::shipyard::Component, @@ -22,6 +35,7 @@ use freya_native_core::{ NodeMaskBuilder, State, }, + tags::TagName, NodeId, SendAnyMap, }; @@ -35,17 +49,15 @@ use crate::{ ParseError, }; -#[derive(Clone, Debug, PartialEq, Eq, Default, Component)] +#[derive(Clone, Debug, PartialEq, Default, Component)] pub struct AccessibilityNodeState { pub closest_accessibility_node_id: Option, pub descencent_accessibility_ids: Vec, pub node_id: NodeId, pub a11y_id: Option, - pub a11y_role: Option, - pub a11y_alt: Option, - pub a11y_name: Option, pub a11y_auto_focus: bool, pub a11y_focusable: Focusable, + pub builder: Option, } impl ParseAttribute for AccessibilityNodeState { @@ -54,47 +66,248 @@ impl ParseAttribute for AccessibilityNodeState { attr: freya_native_core::prelude::OwnedAttributeView, ) -> Result<(), crate::ParseError> { match attr.attribute { - AttributeName::A11YId => { + AttributeName::A11yId => { if let OwnedAttributeValue::Custom(CustomAttributeValues::AccessibilityId(id)) = attr.value { self.a11y_id = Some(*id); - // Enable focus on nodes that pass a custom a11y id if self.a11y_focusable.is_unknown() { self.a11y_focusable = Focusable::Enabled; } } } - AttributeName::A11YRole => { - if let OwnedAttributeValue::Text(attr) = attr.value { - self.a11y_role = Some( - serde_json::from_str::(&format!("\"{attr}\"")) - .map_err(|_| ParseError)?, - ) - } - } - AttributeName::A11YAlt => { + AttributeName::A11yFocusable => { if let OwnedAttributeValue::Text(attr) = attr.value { - self.a11y_alt = Some(attr.to_owned()) - } - } - AttributeName::A11YName => { - if let OwnedAttributeValue::Text(attr) = attr.value { - self.a11y_name = Some(attr.to_owned()) + self.a11y_focusable = Focusable::parse(attr)?; } } - AttributeName::A11YAutoFocus => { + AttributeName::A11yAutoFocus => { if let OwnedAttributeValue::Text(attr) = attr.value { self.a11y_auto_focus = attr.parse().unwrap_or_default() } } - AttributeName::A11YFocusable => { + a11y_attr => { if let OwnedAttributeValue::Text(attr) = attr.value { - self.a11y_focusable = Focusable::parse(attr)?; + if let Some(builder) = self.builder.as_mut() { + match a11y_attr { + AttributeName::A11yName => builder.set_name(attr.clone()), + AttributeName::A11yDescription => builder.set_description(attr.clone()), + AttributeName::A11yValue => builder.set_value(attr.clone()), + AttributeName::A11yAccessKey => builder.set_access_key(attr.clone()), + AttributeName::A11yAuthorId => builder.set_author_id(attr.clone()), + AttributeName::A11yKeyboardShortcut => { + builder.set_keyboard_shortcut(attr.clone()) + } + AttributeName::A11yLanguage => builder.set_language(attr.clone()), + AttributeName::A11yPlaceholder => builder.set_placeholder(attr.clone()), + AttributeName::A11yRoleDescription => { + builder.set_role_description(attr.clone()) + } + AttributeName::A11yStateDescription => { + builder.set_state_description(attr.clone()) + } + AttributeName::A11yTooltip => builder.set_tooltip(attr.clone()), + AttributeName::A11yUrl => builder.set_url(attr.clone()), + AttributeName::A11yRowIndexText => { + builder.set_row_index_text(attr.clone()) + } + AttributeName::A11yColumnIndexText => { + builder.set_column_index_text(attr.clone()) + } + AttributeName::A11yScrollX => { + builder.set_scroll_x(attr.parse().map_err(|_| ParseError)?) + } + AttributeName::A11yScrollXMin => { + builder.set_scroll_x_min(attr.parse().map_err(|_| ParseError)?) + } + AttributeName::A11yScrollXMax => { + builder.set_scroll_x_max(attr.parse().map_err(|_| ParseError)?) + } + AttributeName::A11yScrollY => { + builder.set_scroll_y(attr.parse().map_err(|_| ParseError)?) + } + AttributeName::A11yScrollYMin => { + builder.set_scroll_y_min(attr.parse().map_err(|_| ParseError)?) + } + AttributeName::A11yScrollYMax => { + builder.set_scroll_y_max(attr.parse().map_err(|_| ParseError)?) + } + AttributeName::A11yNumericValue => { + builder.set_numeric_value(attr.parse().map_err(|_| ParseError)?) + } + AttributeName::A11yMinNumericValue => { + builder.set_min_numeric_value(attr.parse().map_err(|_| ParseError)?) + } + AttributeName::A11yMaxNumericValue => { + builder.set_max_numeric_value(attr.parse().map_err(|_| ParseError)?) + } + AttributeName::A11yNumericValueStep => builder + .set_numeric_value_step(attr.parse().map_err(|_| ParseError)?), + AttributeName::A11yNumericValueJump => builder + .set_numeric_value_jump(attr.parse().map_err(|_| ParseError)?), + AttributeName::A11yRowCount => { + builder.set_row_count(attr.parse().map_err(|_| ParseError)?) + } + AttributeName::A11yColumnCount => { + builder.set_column_count(attr.parse().map_err(|_| ParseError)?) + } + AttributeName::A11yRowIndex => { + builder.set_row_index(attr.parse().map_err(|_| ParseError)?) + } + AttributeName::A11yColumnIndex => { + builder.set_column_index(attr.parse().map_err(|_| ParseError)?) + } + AttributeName::A11yRowSpan => { + builder.set_row_span(attr.parse().map_err(|_| ParseError)?) + } + AttributeName::A11yColumnSpan => { + builder.set_column_span(attr.parse().map_err(|_| ParseError)?) + } + AttributeName::A11yLevel => { + builder.set_level(attr.parse().map_err(|_| ParseError)?) + } + AttributeName::A11ySizeOfSet => { + builder.set_size_of_set(attr.parse().map_err(|_| ParseError)?) + } + AttributeName::A11yPositionInSet => { + builder.set_position_in_set(attr.parse().map_err(|_| ParseError)?) + } + AttributeName::A11yColorValue => { + let color = Color::parse(attr)?; + builder.set_color_value( + ((color.a() as u32) << 24) + | ((color.b() as u32) << 16) + | (((color.g() as u32) << 8) + (color.r() as u32)), + ); + } + AttributeName::A11yExpanded => { + builder.set_expanded(attr.parse::().map_err(|_| ParseError)?); + } + AttributeName::A11ySelected => { + builder.set_selected(attr.parse::().map_err(|_| ParseError)?); + } + AttributeName::A11yHovered => { + if attr.parse::().map_err(|_| ParseError)? { + builder.set_hovered(); + } + } + AttributeName::A11yHidden => { + if attr.parse::().map_err(|_| ParseError)? { + builder.set_hidden(); + } + } + AttributeName::A11yLinked => { + if attr.parse::().map_err(|_| ParseError)? { + builder.set_linked(); + } + } + AttributeName::A11yMultiselectable => { + if attr.parse::().map_err(|_| ParseError)? { + builder.set_multiselectable(); + } + } + AttributeName::A11yRequired => { + if attr.parse::().map_err(|_| ParseError)? { + builder.set_required(); + } + } + AttributeName::A11yVisited => { + if attr.parse::().map_err(|_| ParseError)? { + builder.set_visited(); + } + } + AttributeName::A11yBusy => { + if attr.parse::().map_err(|_| ParseError)? { + builder.set_busy(); + } + } + AttributeName::A11yLiveAtomic => { + if attr.parse::().map_err(|_| ParseError)? { + builder.set_live_atomic(); + } + } + AttributeName::A11yModal => { + if attr.parse::().map_err(|_| ParseError)? { + builder.set_modal(); + } + } + AttributeName::A11yTouchTransparent => { + if attr.parse::().map_err(|_| ParseError)? { + builder.set_touch_transparent(); + } + } + AttributeName::A11yReadOnly => { + if attr.parse::().map_err(|_| ParseError)? { + builder.set_read_only(); + } + } + AttributeName::A11yDisabled => { + if attr.parse::().map_err(|_| ParseError)? { + builder.set_disabled(); + } + } + AttributeName::A11yIsSpellingError => { + if attr.parse::().map_err(|_| ParseError)? { + builder.set_is_spelling_error(); + } + } + AttributeName::A11yIsGrammarError => { + if attr.parse::().map_err(|_| ParseError)? { + builder.set_is_grammar_error(); + } + } + AttributeName::A11yIsSearchMatch => { + if attr.parse::().map_err(|_| ParseError)? { + builder.set_is_search_match(); + } + } + AttributeName::A11yIsSuggestion => { + if attr.parse::().map_err(|_| ParseError)? { + builder.set_is_suggestion(); + } + } + AttributeName::A11yRole => { + builder.set_role(Role::parse(attr)?); + } + AttributeName::A11yInvalid => { + builder.set_invalid(Invalid::parse(attr)?); + } + AttributeName::A11yToggled => { + builder.set_toggled(Toggled::parse(attr)?); + } + AttributeName::A11yLive => { + builder.set_live(Live::parse(attr)?); + } + AttributeName::A11yDefaultActionVerb => { + builder.set_default_action_verb(DefaultActionVerb::parse(attr)?); + } + AttributeName::A11yOrientation => { + builder.set_orientation(Orientation::parse(attr)?); + } + AttributeName::A11ySortDirection => { + builder.set_sort_direction(SortDirection::parse(attr)?); + } + AttributeName::A11yCurrent => { + builder.set_aria_current(AriaCurrent::parse(attr)?); + } + AttributeName::A11yAutoComplete => { + builder.set_auto_complete(AutoComplete::parse(attr)?); + } + AttributeName::A11yHasPopup => { + builder.set_has_popup(HasPopup::parse(attr)?); + } + AttributeName::A11yListStyle => { + builder.set_list_style(ListStyle::parse(attr)?); + } + AttributeName::A11yVerticalOffset => { + builder.set_vertical_offset(VerticalOffset::parse(attr)?); + } + _ => {} + } + } } } - _ => {} } Ok(()) @@ -109,14 +322,78 @@ impl State for AccessibilityNodeState { type NodeDependencies = (); - const NODE_MASK: NodeMaskBuilder<'static> = - NodeMaskBuilder::new().with_attrs(AttributeMaskBuilder::Some(&[ - AttributeName::A11YId, - AttributeName::A11YRole, - AttributeName::A11YAlt, - AttributeName::A11YName, - AttributeName::A11YAutoFocus, - ])); + const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new() + .with_attrs(AttributeMaskBuilder::Some(&[ + AttributeName::A11yId, + AttributeName::A11yFocusable, + AttributeName::A11yAutoFocus, + AttributeName::A11yName, + AttributeName::A11yDescription, + AttributeName::A11yValue, + AttributeName::A11yAccessKey, + AttributeName::A11yAuthorId, + AttributeName::A11yKeyboardShortcut, + AttributeName::A11yLanguage, + AttributeName::A11yPlaceholder, + AttributeName::A11yRoleDescription, + AttributeName::A11yStateDescription, + AttributeName::A11yTooltip, + AttributeName::A11yUrl, + AttributeName::A11yRowIndexText, + AttributeName::A11yColumnIndexText, + AttributeName::A11yScrollX, + AttributeName::A11yScrollXMin, + AttributeName::A11yScrollXMax, + AttributeName::A11yScrollY, + AttributeName::A11yScrollYMin, + AttributeName::A11yScrollYMax, + AttributeName::A11yNumericValue, + AttributeName::A11yMinNumericValue, + AttributeName::A11yMaxNumericValue, + AttributeName::A11yNumericValueStep, + AttributeName::A11yNumericValueJump, + AttributeName::A11yRowCount, + AttributeName::A11yColumnCount, + AttributeName::A11yRowIndex, + AttributeName::A11yColumnIndex, + AttributeName::A11yRowSpan, + AttributeName::A11yColumnSpan, + AttributeName::A11yLevel, + AttributeName::A11ySizeOfSet, + AttributeName::A11yPositionInSet, + AttributeName::A11yColorValue, + AttributeName::A11yExpanded, + AttributeName::A11ySelected, + AttributeName::A11yHovered, + AttributeName::A11yHidden, + AttributeName::A11yLinked, + AttributeName::A11yMultiselectable, + AttributeName::A11yRequired, + AttributeName::A11yVisited, + AttributeName::A11yBusy, + AttributeName::A11yLiveAtomic, + AttributeName::A11yModal, + AttributeName::A11yTouchTransparent, + AttributeName::A11yReadOnly, + AttributeName::A11yDisabled, + AttributeName::A11yIsSpellingError, + AttributeName::A11yIsGrammarError, + AttributeName::A11yIsSearchMatch, + AttributeName::A11yIsSuggestion, + AttributeName::A11yRole, + AttributeName::A11yInvalid, + AttributeName::A11yToggled, + AttributeName::A11yLive, + AttributeName::A11yDefaultActionVerb, + AttributeName::A11yOrientation, + AttributeName::A11ySortDirection, + AttributeName::A11yCurrent, + AttributeName::A11yAutoComplete, + AttributeName::A11yHasPopup, + AttributeName::A11yListStyle, + AttributeName::A11yVerticalOffset, + ])) + .with_tag(); fn update<'a>( &mut self, @@ -134,6 +411,18 @@ impl State for AccessibilityNodeState { let mut accessibility = AccessibilityNodeState { node_id: node_view.node_id(), a11y_id: self.a11y_id, + builder: node_view.tag().and_then(|tag| { + match tag { + TagName::Image => Some(NodeBuilder::new(Role::Image)), + TagName::Label => Some(NodeBuilder::new(Role::Label)), + TagName::Paragraph => Some(NodeBuilder::new(Role::Paragraph)), + TagName::Rect => Some(NodeBuilder::new(Role::GenericContainer)), + TagName::Svg => Some(NodeBuilder::new(Role::GraphicsObject)), + TagName::Root => Some(NodeBuilder::new(Role::Window)), + // TODO: make this InlineTextBox and supply computed text span properties + TagName::Text => None, + } + }), ..Default::default() }; @@ -171,8 +460,11 @@ impl State for AccessibilityNodeState { *self = accessibility; if changed { - // Assign an accessibility ID if none was passed but the node has a role - if self.a11y_id.is_none() && self.a11y_role.is_some() { + // Assign an accessibility ID if none was passed but the node has a valid builder + // + // In our case, builder will be `None` if the node's tag cannot be added to accessibility + // tree. + if self.a11y_id.is_none() && self.builder.is_some() { let id = AccessibilityId(accessibility_generator.new_id()); #[cfg(debug_assertions)] tracing::info!("Assigned {id:?} to {:?}", node_view.node_id()); diff --git a/crates/state/src/values/accessibility.rs b/crates/state/src/values/accessibility.rs new file mode 100644 index 000000000..35101fffb --- /dev/null +++ b/crates/state/src/values/accessibility.rs @@ -0,0 +1,349 @@ +use accesskit::{ + AriaCurrent, + AutoComplete, + DefaultActionVerb, + HasPopup, + Invalid, + ListStyle, + Live, + Orientation, + Role, + SortDirection, + Toggled, + VerticalOffset, +}; + +use crate::{ + Parse, + ParseError, +}; + +impl Parse for Role { + fn parse(value: &str) -> Result { + Ok(match value { + "unknown" => Self::Unknown, + "inline-text-box" => Self::InlineTextBox, + "cell" => Self::Cell, + "label" => Self::Label, + "image" => Self::Image, + "link" => Self::Link, + "row" => Self::Row, + "list-item" => Self::ListItem, + "list-marker" => Self::ListMarker, + "tree-item" => Self::TreeItem, + "list-box-option" => Self::ListBoxOption, + "menu-item" => Self::MenuItem, + "menu-list-option" => Self::MenuListOption, + "paragraph" => Self::Paragraph, + "generic-container" => Self::GenericContainer, + "check-box" => Self::CheckBox, + "radio-button" => Self::RadioButton, + "text-input" => Self::TextInput, + "button" => Self::Button, + "default-button" => Self::DefaultButton, + "pane" => Self::Pane, + "row-header" => Self::RowHeader, + "column-header" => Self::ColumnHeader, + "row-group" => Self::RowGroup, + "list" => Self::List, + "table" => Self::Table, + "layout-table-cell" => Self::LayoutTableCell, + "layout-table-row" => Self::LayoutTableRow, + "layout-table" => Self::LayoutTable, + "switch" => Self::Switch, + "menu" => Self::Menu, + "multiline-text-input" => Self::MultilineTextInput, + "search-input" => Self::SearchInput, + "date-input" => Self::DateInput, + "date-time-input" => Self::DateTimeInput, + "week-input" => Self::WeekInput, + "month-input" => Self::MonthInput, + "time-input" => Self::TimeInput, + "email-input" => Self::EmailInput, + "number-input" => Self::NumberInput, + "password-input" => Self::PasswordInput, + "phone-number-input" => Self::PhoneNumberInput, + "url-input" => Self::UrlInput, + "abbr" => Self::Abbr, + "alert" => Self::Alert, + "alert-dialog" => Self::AlertDialog, + "application" => Self::Application, + "article" => Self::Article, + "audio" => Self::Audio, + "banner" => Self::Banner, + "blockquote" => Self::Blockquote, + "canvas" => Self::Canvas, + "caption" => Self::Caption, + "caret" => Self::Caret, + "code" => Self::Code, + "color-well" => Self::ColorWell, + "combo-box" => Self::ComboBox, + "editable-combo-box" => Self::EditableComboBox, + "complementary" => Self::Complementary, + "comment" => Self::Comment, + "content-deletion" => Self::ContentDeletion, + "content-insertion" => Self::ContentInsertion, + "content-info" => Self::ContentInfo, + "definition" => Self::Definition, + "description-list" => Self::DescriptionList, + "description-list-detail" => Self::DescriptionListDetail, + "description-list-term" => Self::DescriptionListTerm, + "details" => Self::Details, + "dialog" => Self::Dialog, + "directory" => Self::Directory, + "disclosure-triangle" => Self::DisclosureTriangle, + "document" => Self::Document, + "embedded-object" => Self::EmbeddedObject, + "emphasis" => Self::Emphasis, + "feed" => Self::Feed, + "figure-caption" => Self::FigureCaption, + "figure" => Self::Figure, + "footer" => Self::Footer, + "footer-as-non-landmark" => Self::FooterAsNonLandmark, + "form" => Self::Form, + "grid" => Self::Grid, + "group" => Self::Group, + "header" => Self::Header, + "header-as-non-landmark" => Self::HeaderAsNonLandmark, + "heading" => Self::Heading, + "iframe" => Self::Iframe, + "iframe-presentational" => Self::IframePresentational, + "ime-candidate" => Self::ImeCandidate, + "keyboard" => Self::Keyboard, + "legend" => Self::Legend, + "line-break" => Self::LineBreak, + "list-box" => Self::ListBox, + "log" => Self::Log, + "main" => Self::Main, + "mark" => Self::Mark, + "marquee" => Self::Marquee, + "math" => Self::Math, + "menu-bar" => Self::MenuBar, + "menu-item-check-box" => Self::MenuItemCheckBox, + "menu-item-radio" => Self::MenuItemRadio, + "menu-list-popup" => Self::MenuListPopup, + "meter" => Self::Meter, + "navigation" => Self::Navigation, + "note" => Self::Note, + "plugin-object" => Self::PluginObject, + "portal" => Self::Portal, + "pre" => Self::Pre, + "progress-indicator" => Self::ProgressIndicator, + "radio-group" => Self::RadioGroup, + "region" => Self::Region, + "root-web-area" => Self::RootWebArea, + "ruby" => Self::Ruby, + "ruby-annotation" => Self::RubyAnnotation, + "scroll-bar" => Self::ScrollBar, + "scroll-view" => Self::ScrollView, + "search" => Self::Search, + "section" => Self::Section, + "slider" => Self::Slider, + "spin-button" => Self::SpinButton, + "splitter" => Self::Splitter, + "status" => Self::Status, + "strong" => Self::Strong, + "suggestion" => Self::Suggestion, + "svg-root" => Self::SvgRoot, + "tab" => Self::Tab, + "tab-list" => Self::TabList, + "tab-panel" => Self::TabPanel, + "term" => Self::Term, + "time" => Self::Time, + "timer" => Self::Timer, + "title-bar" => Self::TitleBar, + "toolbar" => Self::Toolbar, + "tooltip" => Self::Tooltip, + "tree" => Self::Tree, + "tree-grid" => Self::TreeGrid, + "video" => Self::Video, + "web-view" => Self::WebView, + "window" => Self::Window, + "pdf-actionable-highlight" => Self::PdfActionableHighlight, + "pdf-root" => Self::PdfRoot, + "graphics-document" => Self::GraphicsDocument, + "graphics-object" => Self::GraphicsObject, + "graphics-symbol" => Self::GraphicsSymbol, + "doc-abstract" => Self::DocAbstract, + "doc-acknowledgements" => Self::DocAcknowledgements, + "doc-afterword" => Self::DocAfterword, + "doc-appendix" => Self::DocAppendix, + "doc-back-link" => Self::DocBackLink, + "doc-biblio-entry" => Self::DocBiblioEntry, + "doc-bibliography" => Self::DocBibliography, + "doc-biblio-ref" => Self::DocBiblioRef, + "doc-chapter" => Self::DocChapter, + "doc-colophon" => Self::DocColophon, + "doc-conclusion" => Self::DocConclusion, + "doc-cover" => Self::DocCover, + "doc-credit" => Self::DocCredit, + "doc-credits" => Self::DocCredits, + "doc-dedication" => Self::DocDedication, + "doc-endnote" => Self::DocEndnote, + "doc-endnotes" => Self::DocEndnotes, + "doc-epigraph" => Self::DocEpigraph, + "doc-epilogue" => Self::DocEpilogue, + "doc-errata" => Self::DocErrata, + "doc-example" => Self::DocExample, + "doc-footnote" => Self::DocFootnote, + "doc-foreword" => Self::DocForeword, + "doc-glossary" => Self::DocGlossary, + "doc-gloss-ref" => Self::DocGlossRef, + "doc-index" => Self::DocIndex, + "doc-introduction" => Self::DocIntroduction, + "doc-note-ref" => Self::DocNoteRef, + "doc-notice" => Self::DocNotice, + "doc-page-break" => Self::DocPageBreak, + "doc-page-footer" => Self::DocPageFooter, + "doc-page-header" => Self::DocPageHeader, + "doc-page-list" => Self::DocPageList, + "doc-part" => Self::DocPart, + "doc-preface" => Self::DocPreface, + "doc-prologue" => Self::DocPrologue, + "doc-pullquote" => Self::DocPullquote, + "doc-qna" => Self::DocQna, + "doc-subtitle" => Self::DocSubtitle, + "doc-tip" => Self::DocTip, + "doc-toc" => Self::DocToc, + "list-grid" => Self::ListGrid, + "terminal" => Self::Terminal, + _ => Err(ParseError)?, + }) + } +} + +impl Parse for Invalid { + fn parse(value: &str) -> Result { + Ok(match value { + "true" => Invalid::True, + "grammar" => Invalid::Grammar, + "spelling" => Invalid::Spelling, + _ => Err(ParseError)?, + }) + } +} + +impl Parse for Toggled { + fn parse(value: &str) -> Result { + Ok(match value { + "true" => Toggled::True, + "false" => Toggled::False, + "mixed" => Toggled::Mixed, + _ => Err(ParseError)?, + }) + } +} + +impl Parse for Live { + fn parse(value: &str) -> Result { + Ok(match value { + "assertive" => Live::Assertive, + "off" => Live::Off, + "polite" => Live::Polite, + _ => Err(ParseError)?, + }) + } +} + +impl Parse for DefaultActionVerb { + fn parse(value: &str) -> Result { + Ok(match value { + "click" => DefaultActionVerb::Click, + "focus" => DefaultActionVerb::Focus, + "check" => DefaultActionVerb::Check, + "uncheck" => DefaultActionVerb::Uncheck, + "click-ancestor" => DefaultActionVerb::ClickAncestor, + "jump" => DefaultActionVerb::Jump, + "open" => DefaultActionVerb::Open, + "press" => DefaultActionVerb::Press, + "select" => DefaultActionVerb::Select, + "unselect" => DefaultActionVerb::Unselect, + _ => Err(ParseError)?, + }) + } +} + +impl Parse for Orientation { + fn parse(value: &str) -> Result { + Ok(match value { + "horizontal" => Orientation::Horizontal, + "vertical" => Orientation::Vertical, + _ => Err(ParseError)?, + }) + } +} + +impl Parse for SortDirection { + fn parse(value: &str) -> Result { + Ok(match value { + "ascending" => SortDirection::Ascending, + "descending" => SortDirection::Descending, + "other" => SortDirection::Other, + _ => Err(ParseError)?, + }) + } +} + +impl Parse for AriaCurrent { + fn parse(value: &str) -> Result { + Ok(match value { + "false" => AriaCurrent::False, + "true" => AriaCurrent::True, + "page" => AriaCurrent::Page, + "step" => AriaCurrent::Step, + "location" => AriaCurrent::Location, + "date" => AriaCurrent::Date, + "time" => AriaCurrent::Time, + _ => Err(ParseError)?, + }) + } +} + +impl Parse for AutoComplete { + fn parse(value: &str) -> Result { + Ok(match value { + "inline" => AutoComplete::Inline, + "list" => AutoComplete::List, + "both" => AutoComplete::Both, + _ => Err(ParseError)?, + }) + } +} + +impl Parse for HasPopup { + fn parse(value: &str) -> Result { + Ok(match value { + "true" => HasPopup::True, + "menu" => HasPopup::Menu, + "listbox" => HasPopup::Listbox, + "tree" => HasPopup::Tree, + "grid" => HasPopup::Grid, + "dialog" => HasPopup::Dialog, + _ => Err(ParseError)?, + }) + } +} + +impl Parse for ListStyle { + fn parse(value: &str) -> Result { + Ok(match value { + "circle" => ListStyle::Circle, + "disc" => ListStyle::Disc, + "image" => ListStyle::Image, + "numeric" => ListStyle::Numeric, + "square" => ListStyle::Square, + "other" => ListStyle::Other, + _ => Err(ParseError)?, + }) + } +} + +impl Parse for VerticalOffset { + fn parse(value: &str) -> Result { + Ok(match value { + "subscript" => VerticalOffset::Subscript, + "superscript" => VerticalOffset::Superscript, + _ => Err(ParseError)?, + }) + } +} diff --git a/crates/state/src/values/mod.rs b/crates/state/src/values/mod.rs index 4f2b435e4..7cabda902 100644 --- a/crates/state/src/values/mod.rs +++ b/crates/state/src/values/mod.rs @@ -1,3 +1,4 @@ +mod accessibility; mod alignment; mod border; mod color; diff --git a/examples/accessibility.rs b/examples/accessibility.rs index 56f07101c..27aa9d0cc 100644 --- a/examples/accessibility.rs +++ b/examples/accessibility.rs @@ -55,7 +55,7 @@ fn app() -> Element { width: "100%", height: "50%", a11y_role:"label", - a11y_alt: "This is a rectangle", + a11y_name: "This is a rectangle", onclick: move |_| { focus_b.focus(); },