Module: Ruflet::UI::ControlFactory
- Defined in:
- lib/ruflet_ui/ruflet/ui/control_factory.rb
Constant Summary collapse
- CLASS_MAP =
Services::RufletServices::CLASS_MAP .merge(Controls::RufletControls::CLASS_MAP) .freeze
- EXTENSION_CLASS_MAP =
{}
- CONSTRUCTOR_KEYWORDS_CACHE =
{}
- COMMON_ATTRIBUTES =
%i[flip ref transform].freeze
- ATTRIBUTE_OVERRIDES =
{ "expansionpanellist" => %i[auto_scroll on_scroll scroll scroll_interval], "ruflet_app" => %i[assets_dir on_python_output], "rufletapp" => %i[assets_dir on_python_output], "image" => %i[ align animate_align animate_margin animate_offset animate_opacity animate_position animate_rotation animate_scale animate_size aspect_ratio badge bottom col disabled expand expand_loose left margin offset on_animation_end on_size_change opacity right rotate rtl scale size_change_interval tooltip top visible ], "navigationrail" => %i[pin_leading_to_top pin_trailing_to_bottom scrollable], "responsiverow" => %i[auto_scroll on_scroll scroll scroll_interval], "text" => %i[ align animate_align animate_margin animate_offset animate_opacity animate_position animate_rotation animate_scale animate_size aspect_ratio badge bottom col disabled expand expand_loose height left margin offset on_animation_end on_size_change opacity right rtl scale size_change_interval tooltip top visible width ] }.freeze
Class Method Summary collapse
- .build(type, id: nil, **props) ⇒ Object
- .constructor_keywords(klass) ⇒ Object
-
.dsl_receiver?(receiver) ⇒ Boolean
Where an unregistered extension helper is still meant to build a control: a script's top level, and objects the framework owns.
-
.known_control?(name) ⇒ Boolean
Whether a name resolves to a control, including extensions registered at runtime.
- .normalize_common_value(value) ⇒ Object
- .normalize_constructor_props(klass, type, props) ⇒ Object
- .register_extension(type, klass) ⇒ Object
Class Method Details
.build(type, id: nil, **props) ⇒ Object
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/ruflet_ui/ruflet/ui/control_factory.rb', line 60 def build(type, id: nil, **props) normalized_type = type.to_s.downcase if ENV["RUFLET_DEBUG"] == "1" && normalized_type == "floatingactionbutton" Kernel.warn("[factory] type=#{normalized_type} id=#{id.inspect} props=#{props.inspect}") end klass = EXTENSION_CLASS_MAP[normalized_type] || CLASS_MAP[normalized_type] if klass normalized_props, supplemental_props = normalize_constructor_props(klass, normalized_type, props) if ENV["RUFLET_DEBUG"] == "1" && normalized_type == "floatingactionbutton" Kernel.warn("[factory] normalized_props=#{normalized_props.inspect}") end control = klass.new(id: id, **normalized_props) supplemental_props.each do |key, value| if key.to_s.start_with?("on_") && value.respond_to?(:call) control.attach_handler(key.to_s.delete_prefix("on_"), value) next end next if key == :ref control.props[key.to_s] = normalize_common_value(value) end return control end Control.new(type: normalized_type, id: id, **props) end |
.constructor_keywords(klass) ⇒ Object
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/ruflet_ui/ruflet/ui/control_factory.rb', line 104 def constructor_keywords(klass) return CONSTRUCTOR_KEYWORDS_CACHE[klass] if CONSTRUCTOR_KEYWORDS_CACHE.key?(klass) keywords = if klass.const_defined?(:KEYWORDS) klass::KEYWORDS else klass.instance_method(:initialize).parameters .select { |kind, _| kind == :key || kind == :keyreq } .map { |_, name| name } .reject { |name| name == :id } end CONSTRUCTOR_KEYWORDS_CACHE[klass] = keywords.freeze rescue StandardError [] end |
.dsl_receiver?(receiver) ⇒ Boolean
Where an unregistered extension helper is still meant to build a control: a script's top level, and objects the framework owns. Anything else asking for an unknown method is asking by accident.
51 52 53 54 55 56 57 58 |
# File 'lib/ruflet_ui/ruflet/ui/control_factory.rb', line 51 def dsl_receiver?(receiver) return true if receiver.equal?(TOPLEVEL_BINDING.receiver) name = receiver.class.name name.is_a?(String) && name.start_with?("Ruflet::") rescue StandardError false end |
.known_control?(name) ⇒ Boolean
Whether a name resolves to a control, including extensions registered at runtime. Used to decide when a missing method is a DSL call rather than something Ruby expected to raise.
43 44 45 46 |
# File 'lib/ruflet_ui/ruflet/ui/control_factory.rb', line 43 def known_control?(name) key = name.to_s.downcase EXTENSION_CLASS_MAP.key?(key) || CLASS_MAP.key?(key) end |
.normalize_common_value(value) ⇒ Object
136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
# File 'lib/ruflet_ui/ruflet/ui/control_factory.rb', line 136 def normalize_common_value(value) case value when Hash value.each_with_object({}) do |(key, nested), result| result[key.to_s] = normalize_common_value(nested) end when Array value.map { |nested| normalize_common_value(nested) } when Symbol value.to_s else value end end |
.normalize_constructor_props(klass, type, props) ⇒ Object
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
# File 'lib/ruflet_ui/ruflet/ui/control_factory.rb', line 87 def normalize_constructor_props(klass, type, props) keywords = constructor_keywords(klass) return [props, {}] if keywords.empty? && ATTRIBUTE_OVERRIDES[type].nil? allowed = keywords.map(&:to_s) mapped = props.each_with_object({}) { |(k, v), out| out[k.to_sym] = v } if mapped.key?("value") && !allowed.include?("value") && allowed.include?("text") && !mapped.key?("text") mapped["text"] = mapped.delete("value") end if mapped.key?(:value) && !allowed.include?("value") && allowed.include?("text") && !mapped.key?(:text) mapped[:text] = mapped.delete(:value) end supplemental_names = COMMON_ATTRIBUTES + ATTRIBUTE_OVERRIDES.fetch(type, []) supplemental = mapped.slice(*supplemental_names) [mapped.reject { |key, _| supplemental_names.include?(key) }, supplemental] end |
.register_extension(type, klass) ⇒ Object
121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/ruflet_ui/ruflet/ui/control_factory.rb', line 121 def register_extension(type, klass) key = type.to_s.downcase raise ArgumentError, "Extension control type cannot be empty" if key.empty? raise ArgumentError, "Extension control must inherit Ruflet::Control" unless klass <= Ruflet::Control existing = EXTENSION_CLASS_MAP[key] if existing && existing != klass raise ArgumentError, "Extension control `#{key}` is already registered by #{existing}" end EXTENSION_CLASS_MAP[key] = klass CONSTRUCTOR_KEYWORDS_CACHE.delete(klass) klass end |