# frozen_string_literal: true require 'active_support' require 'active_support/notifications' require 'active_support/concern' require 'active_support/core_ext/string/inflections' require 'html_attrs' class StimulusAttrs < HtmlAttrs # rubocop:disable Style/Documentation VERSION = '0.1.0' require_relative 'stimulus_attrs/identifier_scope' require_relative 'stimulus_attrs/helpers' require_relative 'stimulus_attrs/default_identifier' alias merge smart_merge def self.controller(identifier_as_arg = nil, values: nil, classes: nil, actions: nil, params: nil, outlets: nil, identifier: nil) identifier ||= identifier_as_arg raise ArgumentError, 'identifier is required' unless identifier controller_referenced(identifier) result = new(data: { controller: identifier }) result = result.merge(self.values(**values, identifier: identifier)) if values result = result.merge(self.classes(**classes, identifier: identifier)) if classes result = result.merge(self.actions(**actions, identifier: identifier)) if actions result = result.merge(self.params(**params, identifier: identifier)) if params result = result.merge(self.outlets(**outlets, identifier: identifier)) if outlets result end def self.target(target_name, identifier:) new(data: { "#{identifier}-target" => target_name.to_s.underscore.camelcase(:lower) }) end def self.values(identifier:, **values) new(data: values.transform_keys { |k| "#{identifier}-#{k.to_s.underscore.dasherize}-value" }) end def self.classes(identifier:, **classes) new(data: classes.transform_keys { |k| "#{identifier}-#{k.to_s.underscore.dasherize}-class" }) end # `descriptor` is what stimulus calls things that go inside data-action: https://stimulus.hotwired.dev/reference/actions#descriptors def self.actions(descriptor = nil, identifier: nil, params: nil, **descriptors) if descriptor # Keep the default descriptors near the start of the action list descriptors = { nil => [descriptor] + Array(descriptors.delete(nil)) }.merge(descriptors) end action = descriptors.map do |event, methods| Array(methods).map do |method| method = method.to_s prepend_identifier = !method.include?('#') raise ArgumentError, 'identifier is required' if prepend_identifier && !identifier method = method.to_s.underscore.camelcase(:lower) if prepend_identifier method = "#{identifier}##{method}" if prepend_identifier event ? "#{event}->#{method}" : method end end.flatten.join(' ') result = new(data: { action: action }) result = result.merge(self.params(**params, identifier: identifier)) if params.present? result end def self.params(identifier:, **params) new(data: params.transform_keys { |k| "#{identifier}-#{k.to_s.underscore.dasherize}-param" }) end def self.outlets(identifier:, **outlets) new(data: outlets.transform_keys { |k| "#{identifier}-#{k.to_s.underscore.dasherize}-outlet" }) end def self.with_identifier(identifier, &block) scope = IdentifierScope.new(identifier) yield scope if block scope end def self.controller_referenced(identifier) return unless instrumentation_enabled? ActiveSupport::Notifications.instrument('controller_referenced.stimulus_attrs', identifier: identifier) end def self.instrumentation_enabled? !!@instrumentation_enabled end def self.instrumentation_enabled=(value) @instrumentation_enabled = value end end