demarches-normaliennes/app/components/dsfr/input_errorable.rb

151 lines
4 KiB
Ruby

# frozen_string_literal: true
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? && object.public_send(attribute).present?
}
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 react_input_opts(other_opts = {})
input_opts(other_opts, true)
end
def input_opts(other_opts = {}, react = false)
@opts = @opts.deep_merge!(other_opts)
@opts[react ? :class_name : :class] = class_names(map_array_to_hash_with_true(@opts[:class])
.merge({
'fr-password__input': password?,
'fr-input': !react,
'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[react ? :is_required : :required] = true
end
if email?
@opts.deep_merge!(data: {
action: "blur->email-input#checkEmail",
'email-input-target': 'input'
})
end
@opts.deep_merge!(data: { controller: token_list(@opts.dig(:data, :controller), 'autoresize' => autoresize?) })
@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 hint? = hint.present?
def password?
false
end
def email?
false
end
def autoresize?
false
end
def hintable?
false
end
end
end
end