Module: Odin::Forms::Accessibility

Defined in:
lib/odin/forms/accessibility.rb

Overview

Pure helpers for generating accessible markup and computing WCAG contrast.

Class Method Summary collapse

Class Method Details

.contrast_ratio(fg, bg) ⇒ Object

── WCAG contrast ──────────────────────────────────────────────────────



64
65
66
67
68
69
70
# File 'lib/odin/forms/accessibility.rb', line 64

def contrast_ratio(fg, bg)
  l1 = relative_luminance(fg)
  l2 = relative_luminance(bg)
  lighter = [l1, l2].max
  darker = [l1, l2].min
  (lighter + 0.05) / (darker + 0.05)
end

.field_aria_attrs(element, page_index) ⇒ Object

ARIA and id attributes for a field element.



20
21
22
23
24
25
26
27
# File 'lib/odin/forms/accessibility.rb', line 20

def field_aria_attrs(element, page_index)
  attrs = {
    "id" => generate_field_id(element.name, page_index),
    "aria-label" => element[:"aria-label"] || element[:label],
  }
  attrs["aria-required"] = "true" if element[:required]
  attrs
end

.field_group_html(_group_name, legend, content) ⇒ Object

Wrap content in a <fieldset>/<legend> for grouped controls.



30
31
32
33
34
35
# File 'lib/odin/forms/accessibility.rb', line 30

def field_group_html(_group_name, legend, content)
  %(<fieldset class="odin-form-fieldset">) +
    %(<legend class="odin-form-legend">#{legend}</legend>) +
    content +
    "</fieldset>"
end

.field_label_html(label, input_id) ⇒ Object

A <label> associated with the given input id.



15
16
17
# File 'lib/odin/forms/accessibility.rb', line 15

def field_label_html(label, input_id)
  %(<label for="#{input_id}" class="odin-form-label">#{label}</label>)
end

.generate_field_id(element_name, page_index) ⇒ Object

Unique, stable HTML element id for a field.



10
11
12
# File 'lib/odin/forms/accessibility.rb', line 10

def generate_field_id(element_name, page_index)
  "odin-field-#{page_index}-#{element_name}"
end

.linearize(channel) ⇒ Object



77
78
79
80
# File 'lib/odin/forms/accessibility.rb', line 77

def linearize(channel)
  srgb = channel / 255.0
  srgb <= 0.04045 ? srgb / 12.92 : (((srgb + 0.055) / 1.055)**2.4)
end

.meets_contrast_aa(fg, bg, font_size) ⇒ Object



72
73
74
75
# File 'lib/odin/forms/accessibility.rb', line 72

def meets_contrast_aa(fg, bg, font_size)
  ratio = contrast_ratio(fg, bg)
  font_size >= 18 ? ratio >= 3.0 : ratio >= 4.5
end

.parse_hex(hex) ⇒ Object

Raises:

  • (ArgumentError)


82
83
84
85
86
87
# File 'lib/odin/forms/accessibility.rb', line 82

def parse_hex(hex)
  clean = hex.start_with?("#") ? hex[1..] : hex
  raise ArgumentError, %(Invalid hex colour: "#{hex}") unless clean.match?(/\A[0-9a-fA-F]{6}\z/)

  [clean[0, 2].to_i(16), clean[2, 2].to_i(16), clean[4, 2].to_i(16)]
end

.relative_luminance(hex) ⇒ Object



89
90
91
92
# File 'lib/odin/forms/accessibility.rb', line 89

def relative_luminance(hex)
  r, g, b = parse_hex(hex)
  (0.2126 * linearize(r)) + (0.7152 * linearize(g)) + (0.0722 * linearize(b))
end

Skip-navigation link targeting the form content anchor.



38
39
40
41
42
# File 'lib/odin/forms/accessibility.rb', line 38

def skip_link_html(form_title)
  %(<a class="odin-form-sr-only odin-form-skip" href="#odin-form-content">) +
    "Skip to #{form_title}" +
    "</a>"
end

.sr_only_html(text) ⇒ Object

Visually-hidden text that remains announced to screen readers.



45
46
47
# File 'lib/odin/forms/accessibility.rb', line 45

def sr_only_html(text)
  %(<span class="odin-form-sr-only">#{text}</span>)
end

.tab_order_sort(elements) ⇒ Object

Field elements only, sorted by reading order (top-to-bottom, left-to-right).



50
51
52
53
54
55
56
57
58
59
60
# File 'lib/odin/forms/accessibility.rb', line 50

def tab_order_sort(elements)
  elements.select(&:field?).sort do |a, b|
    ay = a[:y] || 0
    by = b[:y] || 0
    if ay != by
      ay <=> by
    else
      (a[:x] || 0) <=> (b[:x] || 0)
    end
  end
end