module Coco class Component < ViewComponent::Base include Concerns::AcceptsTagAttributes include Concerns::HasName include Concerns::Translatable include Coco::ComponentHelper include Coco::AlpineHelper def accepts_options? self.class.respond_to?(:accepts_option) end protected def args @_args ||= {} end def args=(new_args) @_args = new_args end def component_args @_component_args ||= {**args} end def call render component_tag(tag_name) do content end end def before_render content unless content_evaluated? tag_attrs[:data] ||= {} tag_attrs[:data][:component] = component_name tag_attrs[:data][:coco] = "✔️" self.class.send(:run_callbacks, :before_render, nil, context: self) if accepts_options? accepted_options.validate_required! accepted_options.flattened_attribute_values.each { tag_attrs[:data][_1] = _2 } end tag_attrs.compact! end def component_tag(tag = nil, **attrs) Tag.new(tag || tag_name || :div, root: true, **merge_tag_attrs(**attrs)) end def merge_tag_attrs(**attrs) merged_attrs = attrs.deep_merge(tag_attrs) classes = class_names(attrs[:class], tag_attrs[:class]) merged_attrs[:class] = classes if classes.present? merged_attrs end class << self def new(**kwargs) # If a `before_initialize` callback returns a hash then # that is used as the new kwargs value which will eventually # be used to initialize the component. kwargs = run_callbacks(:before_initialize, kwargs, reduce: true) obj = super(**kwargs) if obj.instance_of?(Coco::Component) raise "`Coco::Component` must be subclassed before use" end obj.instance_variable_set(:@_args, kwargs) obj.send(:process_tag_attrs, kwargs) obj.send(:merge_option_values, kwargs) if obj.accepts_options? obj.class.send(:run_callbacks, :after_initialize, {}, context: obj) && obj end def before_render(method_name = nil, &callback) callbacks[:before_render].push(method_name || callback) end def after_initialize(method_name = nil, &callback) callbacks[:after_initialize].push(method_name || callback) end def before_initialize(method_name = nil, &callback) callbacks[:before_initialize].push(method_name || callback) end private def run_callbacks(type, args, context: self, reduce: false) callbacks[type].inject(args) do |memo, cb| result = cb.is_a?(Symbol) ? context.send(cb, memo) : context.instance_exec(memo, &cb) (reduce && result.is_a?(Hash) && args.is_a?(Hash)) ? result : memo end end def callbacks @_callbacks ||= { before_render: [], before_initialize: [], after_initialize: [] } end end end end