diff --git a/.yarnrc b/.yarnrc deleted file mode 100644 index adf41e8f..00000000 --- a/.yarnrc +++ /dev/null @@ -1,5 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -lastUpdateCheck 1763256402231 diff --git a/README.md b/README.md index b233c2d3..b786e982 100644 --- a/README.md +++ b/README.md @@ -1490,38 +1490,38 @@ Generated HTML:
``` @@ -1551,8 +1551,8 @@ Generated HTML: ```html ``` @@ -1649,7 +1649,7 @@ Which outputs: ```html ``` @@ -1670,7 +1670,7 @@ Which outputs: ```html ``` @@ -1689,7 +1689,7 @@ Which outputs: ```html ``` diff --git a/lib/bootstrap_form/components/labels.rb b/lib/bootstrap_form/components/labels.rb index 579957ed..e1e5c801 100644 --- a/lib/bootstrap_form/components/labels.rb +++ b/lib/bootstrap_form/components/labels.rb @@ -18,6 +18,7 @@ def generate_label(id, name, options, custom_label_col, group_layout) options[:class] = label_classes(name, options, custom_label_col, group_layout) options.delete(:class) if options[:class].none? + options[:id] = id.present? ? "#{id}_feedback" : field_id(name, :feedback) if error?(name) && label_errors label(name, label_text(name, options), options.except(:text)) end diff --git a/lib/bootstrap_form/components/validation.rb b/lib/bootstrap_form/components/validation.rb index affba190..8b4ba29d 100644 --- a/lib/bootstrap_form/components/validation.rb +++ b/lib/bootstrap_form/components/validation.rb @@ -61,14 +61,15 @@ def inline_error?(name) error?(name) && inline_errors end - def generate_error(name) + def generate_error(name, id) return unless inline_error?(name) help_text = get_error_messages(name) help_klass = "invalid-feedback" help_tag = :div + id = id.present? ? "#{id}_feedback" : field_id(name, :feedback) - content_tag(help_tag, help_text, class: help_klass) + content_tag(help_tag, help_text, class: help_klass, id:) end def get_error_messages(name) diff --git a/lib/bootstrap_form/form_group.rb b/lib/bootstrap_form/form_group.rb index c244f4b2..aeda4a76 100644 --- a/lib/bootstrap_form/form_group.rb +++ b/lib/bootstrap_form/form_group.rb @@ -23,7 +23,7 @@ def form_group_content_tag(name, field_name, without_field_name, options, html_o html_class = control_specific_class(field_name) html_class = "#{html_class} col-auto g-3" if @layout == :horizontal && options[:skip_inline].blank? tag.div(class: html_class) do - input_with_error(name) do + input_with_error(name, options[:id]) do send(without_field_name, name, options, html_options) end end diff --git a/lib/bootstrap_form/form_group_builder.rb b/lib/bootstrap_form/form_group_builder.rb index 79eff7e5..488a5d87 100644 --- a/lib/bootstrap_form/form_group_builder.rb +++ b/lib/bootstrap_form/form_group_builder.rb @@ -92,7 +92,11 @@ def form_group_css_options(method, html_options, options) # Add control_class; allow it to be overridden by :control_class option control_classes = css_options.delete(:control_class) { control_class } css_options[:class] = safe_join([control_classes, css_options[:class]].compact, " ") - css_options[:class] << " is-invalid" if error?(method) + if error?(method) + css_options[:class] << " is-invalid" + labelledby = options[:id].present? ? "#{options[:id]}_feedback" : field_id(method, :feedback) + css_options[:aria] = { labelledby: } + end css_options[:placeholder] = form_group_placeholder(options, method) if options[:label_as_placeholder] css_options end diff --git a/lib/bootstrap_form/helpers/bootstrap.rb b/lib/bootstrap_form/helpers/bootstrap.rb index 2048c448..0f1422b8 100644 --- a/lib/bootstrap_form/helpers/bootstrap.rb +++ b/lib/bootstrap_form/helpers/bootstrap.rb @@ -34,7 +34,7 @@ def errors_on(name, options={}) hide_attribute_name = options[:hide_attribute_name] || false custom_class = options[:custom_class] || false - tag.div class: custom_class || "invalid-feedback" do + tag.div(class: custom_class || "invalid-feedback", id: field_id(name, :feedback)) do errors = if hide_attribute_name object.errors[name] else @@ -66,20 +66,21 @@ def custom_control(*args, &) end def prepend_and_append_input(name, options, &) + id = options[:id] options = options.extract!(:prepend, :append, :input_group_class).compact input = capture(&) || ActiveSupport::SafeBuffer.new input = attach_input(options, :prepend) + input + attach_input(options, :append) - input << generate_error(name) + input << generate_error(name, id) options.present? && input = tag.div(input, class: ["input-group", options[:input_group_class]].compact) input end - def input_with_error(name, &) + def input_with_error(name, id, &) input = capture(&) - input << generate_error(name) + input << generate_error(name, id) end def input_group_content(content) diff --git a/lib/bootstrap_form/inputs/base.rb b/lib/bootstrap_form/inputs/base.rb index 94833bd9..807dccfa 100644 --- a/lib/bootstrap_form/inputs/base.rb +++ b/lib/bootstrap_form/inputs/base.rb @@ -24,6 +24,7 @@ def bootstrap_field(field_name) def bootstrap_select_group(field_name) define_method(:"#{field_name}_with_bootstrap") do |name, options={}, html_options={}| + options.delete(:id) html_options = html_options.reverse_merge(control_class: "form-select") form_group_builder(name, options, html_options) do form_group_content_tag(name, field_name, "#{field_name}_without_bootstrap", options, html_options) diff --git a/lib/bootstrap_form/inputs/check_box.rb b/lib/bootstrap_form/inputs/check_box.rb index fca776d0..866a9aa6 100644 --- a/lib/bootstrap_form/inputs/check_box.rb +++ b/lib/bootstrap_form/inputs/check_box.rb @@ -13,7 +13,7 @@ def check_box_with_bootstrap(name, options={}, checked_value="1", unchecked_valu content = tag.div(class: check_box_wrapper_class(options), **options[:wrapper].to_h.except(:class)) do html = check_box_without_bootstrap(name, check_box_options(name, options), checked_value, unchecked_value) html << check_box_label(name, options, checked_value, &block) unless options[:skip_label] - html << generate_error(name) if options[:error_message] + html << generate_error(name, options[:id]) if options[:error_message] html end wrapper(content, options) @@ -41,6 +41,10 @@ def check_box_options(name, options) :inline, :label, :label_class, :label_col, :layout, :skip_label, :switch, :wrapper, :wrapper_class) check_box_options[:class] = check_box_classes(name, options) + if error?(name) + labelledby = options[:id].present? ? "#{options[:id]}_feedback" : field_id(name, :feedback) + check_box_options[:aria] = { labelledby: } + end check_box_options.merge!(required_field_options(options, name)) end diff --git a/lib/bootstrap_form/inputs/collection_check_boxes.rb b/lib/bootstrap_form/inputs/collection_check_boxes.rb index 6b18edb7..4c028427 100644 --- a/lib/bootstrap_form/inputs/collection_check_boxes.rb +++ b/lib/bootstrap_form/inputs/collection_check_boxes.rb @@ -9,6 +9,7 @@ module CollectionCheckBoxes included do def collection_check_boxes_with_bootstrap(*args) + args[4]&.delete(:id) html = inputs_collection(*args) do |name, value, options| options[:multiple] = true check_box(name, options, value, nil) diff --git a/lib/bootstrap_form/inputs/collection_radio_buttons.rb b/lib/bootstrap_form/inputs/collection_radio_buttons.rb index be07db35..d6d6d5c5 100644 --- a/lib/bootstrap_form/inputs/collection_radio_buttons.rb +++ b/lib/bootstrap_form/inputs/collection_radio_buttons.rb @@ -9,6 +9,7 @@ module CollectionRadioButtons included do def collection_radio_buttons_with_bootstrap(*args) + args[4]&.delete(:id) inputs_collection(*args) do |name, value, options| radio_button(name, value, options) end diff --git a/lib/bootstrap_form/inputs/radio_button.rb b/lib/bootstrap_form/inputs/radio_button.rb index d78e0e11..9d69395f 100644 --- a/lib/bootstrap_form/inputs/radio_button.rb +++ b/lib/bootstrap_form/inputs/radio_button.rb @@ -14,7 +14,7 @@ def radio_button_with_bootstrap(name, value, *args) tag.div(**wrapper_attributes) do html = radio_button_without_bootstrap(name, value, radio_button_options(name, options)) html << radio_button_label(name, value, options) unless options[:skip_label] - html << generate_error(name) if options[:error_message] + html << generate_error(name, options[:id]) if options[:error_message] html end end @@ -28,6 +28,10 @@ def radio_button_options(name, options) radio_button_options = options.except(:class, :label, :label_class, :error_message, :help, :inline, :hide_label, :skip_label, :wrapper, :wrapper_class) radio_button_options[:class] = radio_button_classes(name, options) + if error?(name) + labelledby = options[:id].present? ? "#{options[:id]}_feedback" : field_id(name, :feedback) + radio_button_options[:aria] = { labelledby: } + end radio_button_options.merge!(required_field_options(options, name)) end diff --git a/lib/bootstrap_form/inputs/time_zone_select.rb b/lib/bootstrap_form/inputs/time_zone_select.rb index 555c2902..e174ce9b 100644 --- a/lib/bootstrap_form/inputs/time_zone_select.rb +++ b/lib/bootstrap_form/inputs/time_zone_select.rb @@ -10,7 +10,7 @@ module TimeZoneSelect def time_zone_select_with_bootstrap(method, priority_zones=nil, options={}, html_options={}) html_options = html_options.reverse_merge(control_class: "form-select") form_group_builder(method, options, html_options) do - input_with_error(method) do + input_with_error(method, options[:id]) do time_zone_select_without_bootstrap(method, priority_zones, options, html_options) end end diff --git a/test/bootstrap_checkbox_test.rb b/test/bootstrap_checkbox_test.rb index 48a8bb94..0428658c 100644 --- a/test/bootstrap_checkbox_test.rb +++ b/test/bootstrap_checkbox_test.rb @@ -543,13 +543,13 @@ class BootstrapCheckboxTest < ActionView::TestCaseBar
'.html_safe - unless @user.errors[:email].empty? - html << tag.div(@user.errors[:email].join(", "), class: "invalid-feedback", - style: "display: block;") - end - html - end - - expected = <<~HTML -Bar
-