Module: Dommy::Internal::AriaState
- Defined in:
- lib/dommy/internal/aria_state.rb
Overview
Computes the ARIA state/property set an accessibility tree exposes for an element, given its already-computed role. Returns a compact hash holding only the states meaningful for that role, in a fixed key order so an ARIA snapshot serializes deterministically:
checked, disabled, expanded, level, pressed, readonly, required, selected
Each value is true, false, "mixed", or (for level) an Integer. A
state native to the host language (an 's checked/disabled, an
Constant Summary collapse
- LEVEL_ROLES =
Roles that expose aria-level / a heading level in the snapshot.
%w[heading listitem row treeitem].freeze
- CHECKABLE =
%w[checkbox radio menuitemcheckbox menuitemradio switch treeitem].freeze
- SELECTABLE =
%w[option tab row gridcell treeitem columnheader rowheader].freeze
- READONLY_ROLES =
%w[textbox searchbox spinbutton combobox gridcell columnheader rowheader].freeze
- REQUIRED_ROLES =
%w[textbox searchbox spinbutton combobox listbox radiogroup checkbox].freeze
- DISABLEABLE_TAGS =
Form controls whose
disabledcontent attribute is meaningful but which do not (all) expose a reflecteddisabledIDL property in Dommy. %w[button fieldset input optgroup option select textarea].freeze
Class Method Summary collapse
- .add(states, key, value) ⇒ Object
- .aria_true?(element, attribute) ⇒ Boolean
-
.checked_state(element) ⇒ Object
checkbox/radio: an indeterminate native control is "mixed"; otherwise the native checkedness, else the tri-state aria-checked.
- .compute(element, role) ⇒ Object
-
.disabled_state(element) ⇒ Object
disabled / readonly / required are binary (no "[disabled=false]"): return true or nil so the caller omits the key when absent.
- .dropdown?(select) ⇒ Boolean
-
.effective_single_selection(select) ⇒ Object
The single selected option: the last bearing
selected, or — only for a drop-down (not multiple and no size>1) — the first option when none is. -
.expanded_state(element) ⇒ Object
Expanded comes only from aria-expanded.
- .multiple_select?(select) ⇒ Boolean
- .native_checkbox_radio?(element) ⇒ Boolean
-
.native_disabled?(element) ⇒ Boolean
Native disabledness: the reflected property when present (input/option/ optgroup), else the content attribute on a disable-able control.
- .native_option?(element) ⇒ Boolean
-
.option_selected?(option) ⇒ Boolean
An
- .readonly_state(element) ⇒ Object
- .required_state(element) ⇒ Object
- .same_node?(first, second) ⇒ Boolean
- .selected_state(element) ⇒ Object
- .tag?(element, name) ⇒ Boolean
- .tristate(element, attribute) ⇒ Object
Class Method Details
.add(states, key, value) ⇒ Object
43 44 45 |
# File 'lib/dommy/internal/aria_state.rb', line 43 def add(states, key, value) states[key] = value unless value.nil? end |
.aria_true?(element, attribute) ⇒ Boolean
140 |
# File 'lib/dommy/internal/aria_state.rb', line 140 def aria_true?(element, attribute) = element.get_attribute(attribute).to_s.casecmp?("true") |
.checked_state(element) ⇒ Object
checkbox/radio: an indeterminate native control is "mixed"; otherwise the native checkedness, else the tri-state aria-checked.
49 50 51 52 53 54 |
# File 'lib/dommy/internal/aria_state.rb', line 49 def checked_state(element) return "mixed" if element.respond_to?(:indeterminate) && element.indeterminate return element.checked if native_checkbox_radio?(element) tristate(element, "aria-checked") end |
.compute(element, role) ⇒ Object
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
# File 'lib/dommy/internal/aria_state.rb', line 26 def compute(element, role) states = {} add(states, :checked, checked_state(element)) if CHECKABLE.include?(role) add(states, :disabled, disabled_state(element)) add(states, :expanded, (element)) # The level (hN tag or aria-level) is reported for the roles that # support it, even when an explicit role overrides the heading role # (<h2 role=listitem> is `listitem [level=2]`, but <h2 role=tablist> has # no level). add(states, :level, LEVEL_ROLES.include?(role) ? AriaRole.heading_level(element) : nil) add(states, :pressed, tristate(element, "aria-pressed")) if role == "button" add(states, :readonly, readonly_state(element)) if READONLY_ROLES.include?(role) add(states, :required, required_state(element)) if REQUIRED_ROLES.include?(role) add(states, :selected, selected_state(element)) if SELECTABLE.include?(role) states end |
.disabled_state(element) ⇒ Object
disabled / readonly / required are binary (no "[disabled=false]"): return true or nil so the caller omits the key when absent.
112 113 114 |
# File 'lib/dommy/internal/aria_state.rb', line 112 def disabled_state(element) true if native_disabled?(element) || aria_true?(element, "aria-disabled") end |
.dropdown?(select) ⇒ Boolean
91 92 93 |
# File 'lib/dommy/internal/aria_state.rb', line 91 def dropdown?(select) !multiple_select?(select) && select.get_attribute("size").to_s.to_i <= 1 end |
.effective_single_selection(select) ⇒ Object
The single selected option: the last bearing selected, or — only for a
drop-down (not multiple and no size>1) — the first option when none is.
83 84 85 86 87 88 89 |
# File 'lib/dommy/internal/aria_state.rb', line 83 def effective_single_selection(select) = select.query_selector_all("option").to_a marked = .reverse_each.find { |option| option.has_attribute?("selected") } return marked if marked dropdown?(select) ? .first : nil end |
.expanded_state(element) ⇒ Object
Expanded comes only from aria-expanded. A native
102 103 104 |
# File 'lib/dommy/internal/aria_state.rb', line 102 def (element) tristate(element, "aria-expanded") end |
.multiple_select?(select) ⇒ Boolean
77 78 79 |
# File 'lib/dommy/internal/aria_state.rb', line 77 def multiple_select?(select) select.respond_to?(:multiple) && select.multiple end |
.native_checkbox_radio?(element) ⇒ Boolean
142 143 144 145 |
# File 'lib/dommy/internal/aria_state.rb', line 142 def native_checkbox_radio?(element) element.respond_to?(:checked) && tag?(element, "input") && %w[checkbox radio].include?(element.get_attribute("type").to_s.downcase) end |
.native_disabled?(element) ⇒ Boolean
Native disabledness: the reflected property when present (input/option/ optgroup), else the content attribute on a disable-able control.
118 119 120 121 122 |
# File 'lib/dommy/internal/aria_state.rb', line 118 def native_disabled?(element) return element.disabled if element.respond_to?(:disabled) DISABLEABLE_TAGS.include?(element.local_name.to_s.downcase) && element.has_attribute?("disabled") end |
.native_option?(element) ⇒ Boolean
147 |
# File 'lib/dommy/internal/aria_state.rb', line 147 def native_option?(element) = element.respond_to?(:selected) && tag?(element, "option") |
.option_selected?(option) ⇒ Boolean
An
66 67 68 69 70 71 72 73 74 75 |
# File 'lib/dommy/internal/aria_state.rb', line 66 def option_selected?(option) select = option.respond_to?(:closest) ? option.closest("select") : nil return !!option.selected unless select # Only `multiple` allows multiple selection — each marked option is # selected. Otherwise the select is single-selection (drop-down or a # size>1 list box): the last marked option wins. return option.has_attribute?("selected") if multiple_select?(select) same_node?(option, effective_single_selection(select)) end |
.readonly_state(element) ⇒ Object
124 125 126 |
# File 'lib/dommy/internal/aria_state.rb', line 124 def readonly_state(element) true if (element.respond_to?(:readonly) && element.readonly) || aria_true?(element, "aria-readonly") end |
.required_state(element) ⇒ Object
128 129 130 |
# File 'lib/dommy/internal/aria_state.rb', line 128 def required_state(element) true if (element.respond_to?(:required) && element.required) || aria_true?(element, "aria-required") end |
.same_node?(first, second) ⇒ Boolean
95 96 97 |
# File 'lib/dommy/internal/aria_state.rb', line 95 def same_node?(first, second) !!(first && second && first.__dommy_backend_node__ == second.__dommy_backend_node__) end |
.selected_state(element) ⇒ Object
56 57 58 59 60 |
# File 'lib/dommy/internal/aria_state.rb', line 56 def selected_state(element) return option_selected?(element) if native_option?(element) tristate(element, "aria-selected") end |
.tag?(element, name) ⇒ Boolean
148 |
# File 'lib/dommy/internal/aria_state.rb', line 148 def tag?(element, name) = element.local_name.to_s.casecmp?(name) |
.tristate(element, attribute) ⇒ Object
132 133 134 135 136 137 138 |
# File 'lib/dommy/internal/aria_state.rb', line 132 def tristate(element, attribute) case element.get_attribute(attribute).to_s.downcase when "true" then true when "false" then false when "mixed" then "mixed" end end |