phlex-forms 0.2.4 → 0.2.5
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +19 -3
- data/lib/forms/checkbox_group.rb +17 -1
- data/lib/forms/field_hint.rb +1 -1
- data/lib/forms/form_control.rb +9 -3
- data/lib/forms/plain/control.rb +2 -2
- data/lib/phlex_forms/builder.rb +29 -1
- data/lib/phlex_forms/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '0038bad89aee72fb6b4e162bfc83dc9afbaadbc2280f3ea23be2985e81774d41'
|
|
4
|
+
data.tar.gz: 9b35b71658c4ffbb14667a79e6e85e4a0437d6953b64921272ae629a443a0ac2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b8ada79a4a87a79096b16f82d000ea0e5620096ebe4b8fff9560ae5e556ff982ff6e13ccb52c77e0d7ea72e0d094055595870468de9c40d9f498350bab399eb4
|
|
7
|
+
data.tar.gz: bce76ac9a71f33cab8c15f6f4b529b636da88d802c4b86ba07ee90948daaa1a2e1c0637f898a11be75e37ab4b8dd7abb8f42ce654a9481907b5cbcc5a299263a
|
data/CHANGELOG.md
CHANGED
|
@@ -16,6 +16,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
16
16
|
Shares one array-valued field name with a leading empty-array hidden field,
|
|
17
17
|
derives the checked set from the model's current value, and renders under both
|
|
18
18
|
themes. `variant:` (`:stack`/`:inline`/`:pill`) is layout-only, no JS.
|
|
19
|
+
- **`checkbox_group` accessible name** (issue #17): the `div[role="group"]` can
|
|
20
|
+
now carry an accessible name/description. HTML/ARIA attributes pass straight
|
|
21
|
+
through to the group, so the bare verb is named with plain `aria:`
|
|
22
|
+
(`aria: { label: "Tags" }` or `aria: { labelledby: "id" }`); through `f.field`
|
|
23
|
+
the Control's own visible `label:` / `hint:` get stable ids and the field wires
|
|
24
|
+
`aria-labelledby` / `aria-describedby` at them (no duplicate markup). Absent a
|
|
25
|
+
name, output is unchanged.
|
|
19
26
|
|
|
20
27
|
### Changed
|
|
21
28
|
|
data/README.md
CHANGED
|
@@ -366,9 +366,11 @@ f.checkbox_group(:tag_ids, Tag.all, value: :id, label: :name)
|
|
|
366
366
|
f.checkbox_group(:tag_ids, Tag.all, value: :id,
|
|
367
367
|
label: ->(t) { t.name.presence || t.slug }, # Symbol method or Proc
|
|
368
368
|
variant: :pill, # :stack (default) | :inline | :pill
|
|
369
|
-
size: :sm
|
|
370
|
-
#
|
|
371
|
-
|
|
369
|
+
size: :sm, # daisyUI checkbox size
|
|
370
|
+
aria: { label: "Tags" }) # names the group for screen readers
|
|
371
|
+
# ...or through field inference (the field's label/hint name the group):
|
|
372
|
+
f.field :tag_ids, as: :checkbox_group, collection: Tag.all, value: :id,
|
|
373
|
+
label: "Tags", hint: "Pick any"
|
|
372
374
|
|
|
373
375
|
f.collection_select(:country_id, Country.all, :id, :name, prompt: "Select…")
|
|
374
376
|
```
|
|
@@ -379,6 +381,20 @@ set comes from the model's current value matched by each item's resolved
|
|
|
379
381
|
`value:` — re-rendering an edit form pre-checks the right boxes. The `:pill`
|
|
380
382
|
variant styles the active chip with Tailwind's `has-[:checked]:` (no JS).
|
|
381
383
|
|
|
384
|
+
A `role="group"` needs an **accessible name** for assistive tech. The verb has
|
|
385
|
+
no bespoke naming option — HTML/ARIA attributes pass straight through to the
|
|
386
|
+
group, so name it with plain `aria:` (`aria: { label: "Tags" }` for a literal
|
|
387
|
+
name, or `aria: { labelledby: "some_id" }` to point at an existing element).
|
|
388
|
+
Through `f.field`, the Control's own visible `label:` / `hint:` name the group
|
|
389
|
+
automatically (the field wires `aria-labelledby` / `aria-describedby` at them).
|
|
390
|
+
Without a name the group renders as before — naming is the caller's call, the
|
|
391
|
+
same posture as Rails' derived form markup.
|
|
392
|
+
|
|
393
|
+
Field ids derive from scope + name (+ value for group items), exactly like
|
|
394
|
+
Rails' `form_with`; the gem does not guarantee page-wide id uniqueness across
|
|
395
|
+
multiple forms for the same model — scope one form (`Form(model:, scope: …)`) to
|
|
396
|
+
disambiguate, as you would in Rails.
|
|
397
|
+
|
|
382
398
|
`Form(model: @item, scope: false)` emits **bare** field names
|
|
383
399
|
(`name="quantity"`) — the shape phlex-reactive row editors and
|
|
384
400
|
`<template>`-cloned rows need. External widgets bind through the public
|
data/lib/forms/checkbox_group.rb
CHANGED
|
@@ -15,6 +15,18 @@ module Forms
|
|
|
15
15
|
# variant: :stack (default) | :inline | :pill — layout only, zero JS
|
|
16
16
|
# size: daisyUI checkbox size modifier (:xs :sm :md :lg :xl)
|
|
17
17
|
#
|
|
18
|
+
# Accessible name (issue #17): `role="group"` needs one so a screen reader
|
|
19
|
+
# announces the group when focus enters a checkbox. The leaf does NOT invent
|
|
20
|
+
# its own naming API — extra attributes pass straight through to the group
|
|
21
|
+
# `div`, so the caller names it with plain HTML/ARIA:
|
|
22
|
+
#
|
|
23
|
+
# f.checkbox_group(:tag_ids, Tag.all, value: :id, aria: { label: "Tags" })
|
|
24
|
+
# f.checkbox_group(:tag_ids, Tag.all, value: :id, aria: { labelledby: "hdr" })
|
|
25
|
+
#
|
|
26
|
+
# Through `f.field`, the builder points the group at the Control's own visible
|
|
27
|
+
# <label>/hint via `aria: { labelledby:, describedby: }` (so the accessible
|
|
28
|
+
# name matches what sighted users see) — same passthrough, no special API.
|
|
29
|
+
#
|
|
18
30
|
# The checked set is passed in pre-resolved by the builder (Field#checkbox_group
|
|
19
31
|
# matches the model's current value by each item's resolved value:), so the
|
|
20
32
|
# component itself stays presentation-only. Each checkbox's markup is delegated
|
|
@@ -44,7 +56,11 @@ module Forms
|
|
|
44
56
|
# convention as collection_check_boxes).
|
|
45
57
|
input(type: "hidden", name: @name, value: "")
|
|
46
58
|
|
|
47
|
-
|
|
59
|
+
# class is the per-checkbox styling seam (see render_checkbox), not a group
|
|
60
|
+
# attribute — everything else the caller passed lands on the group so aria:,
|
|
61
|
+
# data:, id: etc. pass straight through.
|
|
62
|
+
div(class: group_classes, role: "group", "aria-invalid": @error || nil,
|
|
63
|
+
**@attributes.except(:class)) do
|
|
48
64
|
@options.each { |option| item(option) }
|
|
49
65
|
end
|
|
50
66
|
end
|
data/lib/forms/field_hint.rb
CHANGED
data/lib/forms/form_control.rb
CHANGED
|
@@ -6,27 +6,33 @@ module Forms
|
|
|
6
6
|
# workhorse behind the Control-first `f.field` API and the explicit `f.Control`
|
|
7
7
|
# escape hatch.
|
|
8
8
|
class FormControl < Phlex::HTML
|
|
9
|
-
|
|
9
|
+
# label_id:/hint_id: give the label and hint stable ids so a group control
|
|
10
|
+
# (checkbox_group's div[role="group"], which a plain `for`/`id` can't name)
|
|
11
|
+
# can reference them via aria-labelledby / aria-describedby (issue #17).
|
|
12
|
+
def initialize(*modifiers, label: nil, hint: nil, error: nil, for: nil, required: false,
|
|
13
|
+
label_id: nil, hint_id: nil, **options)
|
|
10
14
|
@modifiers = modifiers
|
|
11
15
|
@label = label
|
|
12
16
|
@hint = hint
|
|
13
17
|
@error = error
|
|
14
18
|
@field_id = grab(for:)
|
|
15
19
|
@required = required
|
|
20
|
+
@label_id = label_id
|
|
21
|
+
@hint_id = hint_id
|
|
16
22
|
@options = options
|
|
17
23
|
super()
|
|
18
24
|
end
|
|
19
25
|
|
|
20
26
|
def view_template(&)
|
|
21
27
|
div(class: control_classes, **@options.except(:class)) do
|
|
22
|
-
render Forms::Label.new(text: @label, for: @field_id, required: @required) if @label
|
|
28
|
+
render Forms::Label.new(text: @label, for: @field_id, required: @required, id: @label_id) if @label
|
|
23
29
|
|
|
24
30
|
yield if block_given?
|
|
25
31
|
|
|
26
32
|
if @error
|
|
27
33
|
render Forms::FieldError.new(message: @error)
|
|
28
34
|
elsif @hint
|
|
29
|
-
render Forms::FieldHint.new(text: @hint)
|
|
35
|
+
render Forms::FieldHint.new(text: @hint, id: @hint_id)
|
|
30
36
|
end
|
|
31
37
|
end
|
|
32
38
|
end
|
data/lib/forms/plain/control.rb
CHANGED
|
@@ -7,14 +7,14 @@ module Forms
|
|
|
7
7
|
class Control < Forms::FormControl
|
|
8
8
|
def view_template
|
|
9
9
|
div(**@options.except(:class), class: @options[:class]) do
|
|
10
|
-
render Label.new(text: @label, for: @field_id, required: @required) if @label
|
|
10
|
+
render Label.new(text: @label, for: @field_id, required: @required, id: @label_id) if @label
|
|
11
11
|
|
|
12
12
|
yield if block_given?
|
|
13
13
|
|
|
14
14
|
if @error
|
|
15
15
|
render FieldError.new(message: @error)
|
|
16
16
|
elsif @hint
|
|
17
|
-
render FieldHint.new(text: @hint)
|
|
17
|
+
render FieldHint.new(text: @hint, id: @hint_id)
|
|
18
18
|
end
|
|
19
19
|
end
|
|
20
20
|
end
|
data/lib/phlex_forms/builder.rb
CHANGED
|
@@ -81,11 +81,39 @@ module PhlexForms
|
|
|
81
81
|
options = fo.apply_validations(options)
|
|
82
82
|
choices ||= materialize_choices(inferred.choices)
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
# A checkbox_group renders div[role="group"], which a plain <label for> can't
|
|
85
|
+
# name. Give the Control's label/hint stable ids (control_opts) and point the
|
|
86
|
+
# group at them with a passed-through aria: { labelledby:, describedby: }
|
|
87
|
+
# (group_opts), reusing the Control's own visible chrome (issue #17).
|
|
88
|
+
control_opts, group_opts = group_aria(fo, inferred.as, label_text, hint)
|
|
89
|
+
options = options.merge(group_opts)
|
|
90
|
+
|
|
91
|
+
render fo.control(label: label_text, hint:, required: req, **control_opts) do
|
|
85
92
|
render_field_input(fo, inferred.name, inferred.as, modifiers, choices:, required: req, **options)
|
|
86
93
|
end
|
|
87
94
|
end
|
|
88
95
|
|
|
96
|
+
# For a checkbox_group field: the stable ids to stamp on the Control's
|
|
97
|
+
# label/hint (control_opts), and a group `aria:` hash pointing back at them
|
|
98
|
+
# (group_opts) — passed through to the group div, no special leaf API. Returns
|
|
99
|
+
# [{}, {}] for every other field type (a label associates via for/id, no aria
|
|
100
|
+
# needed) and when neither label nor hint is present.
|
|
101
|
+
def group_aria(fo, as, label_text, hint)
|
|
102
|
+
return [{}, {}] unless as == :checkbox_group
|
|
103
|
+
|
|
104
|
+
control_opts = {}
|
|
105
|
+
aria = {}
|
|
106
|
+
if label_text
|
|
107
|
+
control_opts[:label_id] = "#{fo.field_id}_label"
|
|
108
|
+
aria[:labelledby] = control_opts[:label_id]
|
|
109
|
+
end
|
|
110
|
+
if hint
|
|
111
|
+
control_opts[:hint_id] = "#{fo.field_id}_hint"
|
|
112
|
+
aria[:describedby] = control_opts[:hint_id]
|
|
113
|
+
end
|
|
114
|
+
[control_opts, aria.empty? ? {} : { aria: }]
|
|
115
|
+
end
|
|
116
|
+
|
|
89
117
|
# ------------------------------------------------------------------
|
|
90
118
|
# Layout helpers
|
|
91
119
|
# ------------------------------------------------------------------
|
data/lib/phlex_forms/version.rb
CHANGED