Class: LocoMotion::ComponentConfig

Inherits:
Object
  • Object
show all
Includes:
LocoMotion::Concerns::InspectableComponent
Defined in:
lib/loco_motion/component_config.rb

Overview

Holds the per-part CSS, HTML, tag name, and modifier configuration that every component builds from its constructor keyword arguments. Each component owns one ComponentConfig instance (via BaseComponent#config) that tracks the default and user-supplied values for every part declared with define_part, and merges them together when the part is rendered.

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from LocoMotion::Concerns::InspectableComponent

#build_inspect_string

Constructor Details

#initialize(component, **kws) ⇒ ComponentConfig

Create a new config for the given component instance and process its keyword arguments into per-part defaults.

Parameters:

  • component (LocoMotion::BaseComponent)

    The component instance this config belongs to.

  • kws (Hash)

    a customizable set of options

Options Hash (**kws):

  • modifiers (Array<Symbol>)

    The component's modifiers (e.g. :primary), validated against the component's valid_modifiers.

  • modifier (Symbol)

    Singular alias for passing a single modifier; merged into modifiers.

  • size (Symbol)

    The component's size, if it declares valid_sizes.

  • _css (part)

    [String, Array] CSS classes to add to the given part (e.g. wrapper_css).

  • _html (part)

    [Hash] HTML attributes to deep-merge onto the given part (e.g. wrapper_html).

  • _aria (part)

    [Hash] aria-* attributes to deep-merge onto the given part, nested under html[:aria] (e.g. wrapper_aria).

  • _data (part)

    [Hash] data-* attributes to deep-merge onto the given part, nested under html[:data] (e.g. wrapper_data).

  • _tag_name (part)

    [String, Symbol] The HTML tag to render the given part as (e.g. wrapper_tag_name).



47
48
49
50
51
52
53
54
55
56
57
# File 'lib/loco_motion/component_config.rb', line 47

def initialize(component, **kws)
  @component = component
  @options = kws

  @parts = {}
  @modifiers = (kws[:modifiers] || [kws[:modifier]]).compact
  @size = kws[:size]

  build
  validate
end

Instance Attribute Details

#componentObject (readonly)

Returns the value of attribute component.



14
15
16
# File 'lib/loco_motion/component_config.rb', line 14

def component
  @component
end

#modifiersObject (readonly)

Returns the value of attribute modifiers.



14
15
16
# File 'lib/loco_motion/component_config.rb', line 14

def modifiers
  @modifiers
end

#optionsObject (readonly)

Returns the value of attribute options.



14
15
16
# File 'lib/loco_motion/component_config.rb', line 14

def options
  @options
end

#partsObject (readonly)

Returns the value of attribute parts.



14
15
16
# File 'lib/loco_motion/component_config.rb', line 14

def parts
  @parts
end

#sizeObject (readonly)

Returns the value of attribute size.



14
15
16
# File 'lib/loco_motion/component_config.rb', line 14

def size
  @size
end

Instance Method Details

#add_aria(part_name, aria) ⇒ Object

Adds default aria-* attributes to the requested component part. This is a convenience wrapper around #add_html that nests the given hash under the aria: key, so add_aria(:component, label: "Save") renders aria-label="Save".

Parameters:

  • part_name (Symbol)

    The name of the part to update.

  • aria (Hash)

    The aria-* attributes to deep-merge onto the part.



209
210
211
# File 'lib/loco_motion/component_config.rb', line 209

def add_aria(part_name, aria)
  add_html(part_name, { aria: aria }) if aria
end

#add_css(part_name, css) ⇒ Object

Adds default CSS to the requested component part.

Parameters:

  • part_name (Symbol)

    The name of the part to update.

  • css (String, Array<String>)

    The CSS classes to add.



186
187
188
# File 'lib/loco_motion/component_config.rb', line 186

def add_css(part_name, css)
  @parts[part_name][:default_css] << css if css
end

#add_data(part_name, data) ⇒ Object

Adds default data-* attributes to the requested component part. This is a convenience wrapper around #add_html that nests the given hash under the data: key, so add_data(:component, foo: "bar") renders data-foo="bar".

Parameters:

  • part_name (Symbol)

    The name of the part to update.

  • data (Hash)

    The data-* attributes to deep-merge onto the part.



222
223
224
# File 'lib/loco_motion/component_config.rb', line 222

def add_data(part_name, data)
  add_html(part_name, { data: data }) if data
end

#add_html(part_name, html) ⇒ Object

Adds default HTML to the requested component part.

Parameters:

  • part_name (Symbol)

    The name of the part to update.

  • html (Hash)

    The HTML attributes to deep-merge onto the part.



196
197
198
# File 'lib/loco_motion/component_config.rb', line 196

def add_html(part_name, html)
  @parts[part_name][:default_html] = @parts[part_name][:default_html].deep_merge(html) if html
end

#add_stimulus_controller(part_name, controller_name) ⇒ Object

Add a default Stimulus (Javascript) controller to the requested component part.

Parameters:

  • part_name (Symbol)

    The name of the part to update.

  • controller_name (String)

    The name of the Stimulus controller to add.



234
235
236
237
238
# File 'lib/loco_motion/component_config.rb', line 234

def add_stimulus_controller(part_name, controller_name)
  @parts[part_name] ||= {}
  @parts[part_name][:default_stimulus_controllers] ||= []
  @parts[part_name][:default_stimulus_controllers] << controller_name
end

#buildObject

Populate #parts with the default and user-supplied CSS, HTML, tag name, and Stimulus controllers for every part the component declares, then layer in the component-level shorthand options (see #merge_user_options!).



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/loco_motion/component_config.rb', line 65

def build
  # Allow users to pass css/html for a specific part (i.e. modal_dialog)
  @component.component_parts.each do |part, defaults|
    @parts[part] = {
      default_css: [],
      default_html: {},
      default_tag_name: defaults[:tag_name] || :div,
      default_stimulus_controllers: [],

      user_css: @options["#{part}_css".to_sym] || [],
      user_html: @options["#{part}_html".to_sym] || {},
      user_tag_name: @options["#{part}_tag_name".to_sym],
      user_stimulus_controllers: @options["#{part}_controllers".to_sym] || []
    }

    # Allow users to pass `{part}_aria` / `{part}_data` as a shorthand for
    # nesting `aria:` / `data:` hashes inside `{part}_html`.
    part_aria = @options["#{part}_aria".to_sym]
    part_data = @options["#{part}_data".to_sym]

    @parts[part][:user_html] = @parts[part][:user_html].deep_merge(aria: part_aria) if part_aria
    @parts[part][:user_html] = @parts[part][:user_html].deep_merge(data: part_data) if part_data
  end

  # Allow users to pass some shortened attributes for the component part
  merge_user_options!(**@options)
end

#get_part(part_name) ⇒ Object

Returns the part for the request part name or an empty hash if none was found.

Parameters:

  • part_name (Symbol)

    The name of the part to look up.



165
166
167
# File 'lib/loco_motion/component_config.rb', line 165

def get_part(part_name)
  @parts[part_name] || {}
end

#inspectObject

Builds a formatted inspect string (via LocoMotion::Concerns::InspectableComponent) showing the options, parts, modifiers, and size.



292
293
294
# File 'lib/loco_motion/component_config.rb', line 292

def inspect
  build_inspect_string("options", "parts", "modifiers", "size", suffix: ">")
end

#merge_user_options!(**kws) ⇒ Object

Add specific component user options if they pass shortened attributes.

Parameters:

  • kws (Hash)

    a customizable set of options

Options Hash (**kws):

  • tag_name (String, Symbol)

    The HTML tag to render the component part as.

  • css (String, Array<String>)

    CSS classes to add to the component part.

  • html (Hash)

    HTML attributes to deep-merge onto the component part.

  • aria (Hash)

    aria-* attributes to deep-merge onto the component part, nested under html[:aria].

  • data (Hash)

    data-* attributes to deep-merge onto the component part, nested under html[:data].

  • controller (String)

    A single Stimulus controller to add to the component part.

  • controllers (Array<String>)

    Stimulus controllers to add to the component part.



117
118
119
120
121
122
123
124
125
# File 'lib/loco_motion/component_config.rb', line 117

def merge_user_options!(**kws)
  @parts[:component][:user_tag_name] = kws[:tag_name] if kws[:tag_name]
  @parts[:component][:user_css].push(kws[:css]) if kws[:css]
  @parts[:component][:user_html].deep_merge!(kws[:html]) if kws[:html]
  @parts[:component][:user_html].deep_merge!(aria: kws[:aria]) if kws[:aria]
  @parts[:component][:user_html].deep_merge!(data: kws[:data]) if kws[:data]
  @parts[:component][:user_stimulus_controllers].push(kws[:controller]) if kws[:controller]
  @parts[:component][:user_stimulus_controllers].push(kws[:controllers]) if kws[:controllers]
end

#set_tag_name(part_name, tag_name) ⇒ Object

Sets the default tag name for the requested component part.

Parameters:

  • part_name (Symbol)

    The name of the part to update.

  • tag_name (String, Symbol)

    The default HTML tag to render the part as.



176
177
178
# File 'lib/loco_motion/component_config.rb', line 176

def set_tag_name(part_name, tag_name)
  @parts[part_name][:default_tag_name] = tag_name if tag_name
end

#smart_merge!(**kws) ⇒ Object

Merge additional options into the defaults config by combining the new options with the existing options, rather than overwriting (where possible).

HTML will be deep merged, CSS will be appended, and tag_name will be overwritten.

Parameters:

  • kws (Hash)

    Keyword arguments using the same per-part {part}_*` shorthand pattern as {#initialize} (e.g. wrapper_css, wrapper_html`), plus the component-level options handled by #merge_user_options!.



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/loco_motion/component_config.rb', line 140

def smart_merge!(**kws)
  @component.component_parts.each_key do |part|
    set_tag_name(part, kws["#{part}_tag_name".to_sym])
    add_css(part, kws["#{part}_css".to_sym])
    add_html(part, kws["#{part}_html".to_sym])
    add_aria(part, kws["#{part}_aria".to_sym])
    add_data(part, kws["#{part}_data".to_sym])

    controllers = kws["#{part}_controllers".to_sym] || []

    controllers.each do |controller_name|
      add_stimulus_controller(part, controller_name)
    end
  end

  # Make sure to merge any user-provided options as well
  merge_user_options!(**kws)
end

#to_hObject

Render a Hash version of the config.



278
279
280
281
282
283
284
285
# File 'lib/loco_motion/component_config.rb', line 278

def to_h
  {
    options: @options,
    parts: @parts,
    modifiers: @modifiers,
    size: @size
  }
end

#valid_partsObject

Return a list of valid parts for the component.



271
272
273
# File 'lib/loco_motion/component_config.rb', line 271

def valid_parts
  @parts.keys
end

#validateObject

Validate the component config and throw errors if there are issues.



243
244
245
# File 'lib/loco_motion/component_config.rb', line 243

def validate
  validate_modifiers
end

#validate_modifiersObject

Validate that all of the modifiers are correct.



250
251
252
253
254
255
256
257
# File 'lib/loco_motion/component_config.rb', line 250

def validate_modifiers
  # Check to make sure they have passed a valid / defined modifier
  (@modifiers || []).each do |modifier|
    if modifier.present? && !@component.valid_modifiers.include?(modifier)
      raise LocoMotion::InvalidModifierError.new(modifier, @component)
    end
  end
end

#validate_part(part_name) ⇒ Object

Validates that the requested part is valid for the component.

Parameters:

  • part_name (Symbol)

    The name of the part to validate.

Raises:



264
265
266
# File 'lib/loco_motion/component_config.rb', line 264

def validate_part(part_name)
  raise LocoMotion::UnknownPartError.new(part_name, @component) unless valid_parts.include?(part_name)
end