demarches-normaliennes/app/components/dsfr/input_errorable.rb
mfo 4041bfa160
Merge pull request #10317 from mfo/US/aria-labelled-by-error-message
ETQ Usager, je souhaite que mon screen reader verbalise les erreurs sur les champs unique contenu dans un `fieldset`
2024-04-18 08:53:12 +00:00

154 lines
4.1 KiB
Ruby

module Dsfr
module InputErrorable
extend ActiveSupport::Concern
included do
delegate :object, to: :@form
delegate :errors, to: :object
renders_one :hint
def dsfr_group_classname
if dsfr_champ_container == :fieldset
'fr-fieldset'
elsif dsfr_input_classname.present? # non fillable element
"#{dsfr_input_classname}-group"
end
end
def input_group_error_class_names
return {} if dsfr_group_classname.nil?
{
"#{dsfr_group_classname}--error" => errors_on_attribute?,
"#{dsfr_group_classname}--valid" => !errors_on_attribute? && errors_on_another_attribute?
}
end
def errors_on_attribute?
errors.has_key?(attribute_or_rich_body)
end
# errors helpers
def error_full_messages
errors.full_messages_for(attribute_or_rich_body)
end
def fieldset_error_opts
if dsfr_champ_container == :fieldset && errors_on_attribute?
{ aria: { labelledby: "#{describedby_id} #{object.labelledby_id}" } }
else
{}
end
end
private
# lookup for edge case from `form.rich_text_area`
# rich text uses _rich_#{attribute}, but it is saved on #{attribute}, as well as error messages
def attribute_or_rich_body
case @input_type
when :rich_text_area
@attribute.to_s.sub(/\Arich_/, '').to_sym
else
@attribute
end
end
def fr_fieldset?
!['fr-input', 'fr-radio', 'fr-select'].include?(dsfr_input_classname)
end
def input_error_class_names
{
"#{dsfr_input_classname}--error": errors_on_attribute?
}
end
def input_error_opts
{
aria: {
describedby: describedby_id
}
}
end
def input_opts(other_opts = {})
@opts = @opts.deep_merge!(other_opts)
@opts[:class] = class_names(map_array_to_hash_with_true(@opts[:class])
.merge({
'fr-password__input': password?,
'fr-input': true,
'fr-mb-0': true
}.merge(input_error_class_names)))
if errors_on_attribute?
@opts.deep_merge!(aria: { describedby: describedby_id })
elsif hintable?
@opts.deep_merge!(aria: { describedby: hint_id })
end
if @required
@opts[:required] = true
end
if email?
@opts.deep_merge!(data: {
action: "blur->email-input#checkEmail",
'email-input-target': 'input'
})
end
if autoresize?
@opts.deep_merge!(data: { controller: 'autoresize' })
end
@opts
end
def errors_on_another_attribute?
!errors.empty?
end
def map_array_to_hash_with_true(array_or_string_or_nil)
Array(array_or_string_or_nil).to_h { [_1, true] }
end
def hint
get_slot(:hint).presence || default_hint
end
def default_hint
if I18n.exists?("activerecord.attributes.#{object.class.name.underscore}.hints.#{@attribute}")
I18n.t("activerecord.attributes.#{object.class.name.underscore}.hints.#{@attribute}")
elsif I18n.exists?("activerecord.attributes.#{object.class.name.underscore}.hints.#{@attribute}_html")
I18n.t("activerecord.attributes.#{object.class.name.underscore}.hints.#{@attribute}_html").html_safe
end
end
def password?
false
end
def email?
false
end
def autoresize?
false
end
def hintable?
false
end
def hint?
return true if get_slot(:hint).present?
maybe_hint = I18n.exists?("activerecord.attributes.#{object.class.name.underscore}.hints.#{@attribute}")
maybe_hint_html = I18n.exists?("activerecord.attributes.#{object.class.name.underscore}.hints.#{@attribute}_html")
maybe_hint || maybe_hint_html
end
end
end
end