funicular 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +57 -0
  3. data/demo/test_chartjs.html +9 -9
  4. data/demo/test_component.html +8 -8
  5. data/demo/test_error_boundary.html +44 -41
  6. data/demo/test_router.html +48 -48
  7. data/demo/tic-tac-toe.html +25 -25
  8. data/docs/architecture.md +46 -9
  9. data/lib/funicular/ssr/runtime.rb +1 -0
  10. data/lib/funicular/vendor/mrbc/VERSION +1 -1
  11. data/lib/funicular/vendor/mrbc/mrbc.js +613 -486
  12. data/lib/funicular/vendor/mrbc/mrbc.wasm +0 -0
  13. data/lib/funicular/vendor/picoruby/VERSION +1 -1
  14. data/lib/funicular/vendor/picoruby/debug/picoruby.js +669 -449
  15. data/lib/funicular/vendor/picoruby/debug/picoruby.wasm +0 -0
  16. data/lib/funicular/vendor/picoruby/dist/picoruby.js +2 -2
  17. data/lib/funicular/vendor/picoruby/dist/picoruby.wasm +0 -0
  18. data/lib/funicular/vendor/picoruby-test-node/VERSION +1 -1
  19. data/lib/funicular/vendor/picoruby-test-node/picoruby.js +921 -629
  20. data/lib/funicular/vendor/picoruby-test-node/picoruby.wasm +0 -0
  21. data/lib/funicular/vendor/picoruby-test-node/picoruby.wasm.map +1 -1
  22. data/lib/funicular/version.rb +1 -1
  23. data/lib/generators/funicular/chat/templates/funicular_chat_component.rb.tt +37 -38
  24. data/minitest/dsl_test.rb +237 -0
  25. data/minitest/fixtures/funicular_app/components/greeting_component.rb +5 -5
  26. data/minitest/form_for_test.rb +2 -2
  27. data/minitest/hydration_test.rb +2 -2
  28. data/minitest/sig_tags_test.rb +30 -0
  29. data/minitest/view_context_test.rb +15 -15
  30. data/mrblib/0_tags.rb +62 -0
  31. data/mrblib/component.rb +113 -23
  32. data/mrblib/error_boundary.rb +25 -19
  33. data/mrblib/form_builder.rb +10 -10
  34. data/mrblib/funicular.rb +1 -1
  35. data/mrblib/styles.rb +102 -12
  36. data/mrblib/view_context.rb +3 -32
  37. data/sig/component.rbs +18 -4
  38. data/sig/error_boundary.rbs +4 -4
  39. data/sig/styles.rbs +18 -5
  40. data/sig/tags.rbs +54 -0
  41. data/sig/view_context.rbs +47 -34
  42. metadata +5 -1
data/mrblib/component.rb CHANGED
@@ -52,9 +52,60 @@ module Funicular
52
52
  end
53
53
  end
54
54
 
55
+ include Tags
56
+
55
57
  attr_accessor :props, :vdom, :dom_element, :mounted, :runtime, :children, :current_children
56
58
  attr_reader :refs
57
59
 
60
+ # Opt out of DSL collision detection for the given method names. The
61
+ # shadowed tag remains reachable through tag(:name, ...).
62
+ def self.allow_dsl_override(*names)
63
+ @dsl_overrides ||= [] #: Array[Symbol]
64
+ @dsl_overrides.concat(names)
65
+ end
66
+
67
+ def self.dsl_overrides
68
+ @dsl_overrides ||= [] #: Array[Symbol]
69
+ end
70
+
71
+ # Layer 1: catches `def`, `define_method`, and `alias` in subclasses at
72
+ # class-definition time. attr_* does not fire this hook on mruby, and
73
+ # included modules never do; validate_dsl_conflicts! covers those.
74
+ def self.method_added(name)
75
+ if self != Funicular::Component && Funicular::Tags::RESERVED_DSL[name] && !dsl_overrides.include?(name)
76
+ kind = Funicular::Tags::RESERVED_DSL[name] == :tag ? "<#{name}> tag helper" : "##{name} helper"
77
+ raise Funicular::DSLCollisionError,
78
+ "#{self}##{name} collides with the Funicular DSL (#{kind}). " \
79
+ "Rename it (e.g. `#{name}_value`), or declare `allow_dsl_override :#{name}` " \
80
+ "and use `tag(:#{name}, ...)` to emit the element."
81
+ end
82
+ super
83
+ end
84
+
85
+ # Layer 2: once per class, sweep methods the method_added hook cannot
86
+ # see (attr_* on mruby, user-included modules).
87
+ def self.validate_dsl_conflicts!
88
+ return if @dsl_validated
89
+ if instance_method(:render).arity != 0
90
+ raise Funicular::DSLCollisionError,
91
+ "#{self}#render must not take parameters as of Funicular 0.4.0: " \
92
+ "delete the parameter (`def render`) and drop the `h.` receivers " \
93
+ "inside; tags are bareword methods on the component now."
94
+ end
95
+ ancestors.each do |mod|
96
+ break if mod == Funicular::Component || mod == Funicular::Tags
97
+ next unless mod.is_a?(Module)
98
+ methods = mod.instance_methods(false) + mod.private_instance_methods(false)
99
+ offenders = methods.select { |m| Funicular::Tags::RESERVED_DSL[m] } - dsl_overrides
100
+ next if offenders.empty?
101
+ raise Funicular::DSLCollisionError,
102
+ "#{mod} defines methods that collide with the Funicular DSL: " \
103
+ "#{offenders.map { |m| m.to_s }.join(', ')}. Rename them, or declare " \
104
+ "`allow_dsl_override` and use `tag(...)` to emit the element."
105
+ end
106
+ @dsl_validated = true
107
+ end
108
+
58
109
  def initialize(props = {})
59
110
  @props = props
60
111
  @state = initialize_state || {}
@@ -93,7 +144,7 @@ module Funicular
93
144
  end
94
145
 
95
146
  def styles
96
- @style_accessor ||= StyleAccessor.new(self.class.styles_definitions)
147
+ @style_accessor ||= self.class.style_accessor_class.new(self.class.styles_definitions)
97
148
  end
98
149
 
99
150
  # Override this method in subclasses to define initial state
@@ -230,21 +281,21 @@ module Funicular
230
281
  # ) do
231
282
  # div { user.name }
232
283
  # end
233
- def render_suspense(h, name, fallback:, error: nil, &block)
284
+ def render_suspense(name, fallback:, error: nil, &block)
234
285
  current_children = @current_children
235
286
  child_count_before = current_children&.size
236
287
  result = nil
237
288
 
238
289
  if @suspense_states[name] == :rejected
239
290
  result = if error
240
- error.call(h, @suspense_errors[name])
291
+ error.call(@suspense_errors[name])
241
292
  else
242
- fallback.call(h)
293
+ fallback.call
243
294
  end
244
295
  elsif suspense_loading?(name)
245
- result = fallback.call(h)
296
+ result = fallback.call
246
297
  else
247
- result = block.call(h, resources)
298
+ result = block.call(resources)
248
299
  end
249
300
 
250
301
  if current_children && current_children.size == child_count_before
@@ -254,17 +305,25 @@ module Funicular
254
305
  result
255
306
  end
256
307
 
257
- # Class methods for styles DSL
308
+ # Class methods for styles DSL. The block is instance_exec'd on a
309
+ # StyleBuilder cleanroom so barewords define styles; it also receives
310
+ # the builder for the explicit `styles { |css| css.define(...) }` form.
258
311
  def self.styles(&block)
259
312
  builder = StyleBuilder.new
260
- block.call(builder)
313
+ builder.instance_exec(builder, &block) # steep:ignore
261
314
  @styles_definitions = builder.to_definitions
315
+ @style_accessor_class = nil
262
316
  end
263
317
 
264
318
  def self.styles_definitions
265
319
  @styles_definitions ||= {}
266
320
  end
267
321
 
322
+ # Per-class accessor with one real method per declared style name.
323
+ def self.style_accessor_class
324
+ @style_accessor_class ||= StyleAccessor.accessor_for(styles_definitions)
325
+ end
326
+
268
327
  # Suspense DSL - register async data loaders
269
328
  #
270
329
  # @param name [Symbol] Name of the suspense data (becomes accessible as method)
@@ -447,19 +506,20 @@ module Funicular
447
506
  end
448
507
 
449
508
  # Override this method in subclasses to define render logic
450
- def render(h)
451
- raise "Subclasses must implement render(h)"
509
+ def render
510
+ raise "Subclasses must implement render"
452
511
  end
453
512
 
454
513
  # Build VDOM tree from render method
455
514
  # Called by VDOM::Renderer, Differ, and Patcher
456
515
  def build_vdom
516
+ self.class.validate_dsl_conflicts!
457
517
  previous_rendering = @rendering
458
518
  previous_children = @current_children
459
519
  @rendering = true
460
520
  @current_children = nil
461
521
  begin
462
- result = render(ViewContext.new(self))
522
+ result = render
463
523
  ensure
464
524
  @rendering = previous_rendering
465
525
  @current_children = previous_children
@@ -622,9 +682,45 @@ module Funicular
622
682
  normalize_vnode(value)
623
683
  end
624
684
 
685
+ # Internal element factory shared by the Tags mixin, FormBuilder, and
686
+ # the framework helpers. One instance per component; ViewContext itself
687
+ # is stateless (the render cursor lives on the component).
688
+ def __view__
689
+ @__view__ ||= ViewContext.new(self)
690
+ end
691
+
692
+ def routes
693
+ @runtime.routes
694
+ end
695
+
696
+ # Bareword DSL helpers available inside render (self is the component).
697
+ def component(component_class, props = {}, &block)
698
+ __view__.component(component_class, props, &block)
699
+ end
700
+
701
+ def form_for(model_key, options = {}, &block)
702
+ build_form_for(__view__, model_key, options, &block)
703
+ end
704
+
705
+ def link_to(path, **options, &block)
706
+ build_link_to(__view__, path, **options, &block)
707
+ end
708
+
709
+ def button_to(path, method: :post, **options, &block)
710
+ build_button_to(__view__, path, method: method, **options, &block)
711
+ end
712
+
713
+ def suspense(name, fallback:, error: nil, &block)
714
+ render_suspense(name, fallback: fallback, error: error, &block)
715
+ end
716
+
625
717
  def add_child_from_view(child)
718
+ unless @rendering
719
+ raise Funicular::RenderContextError,
720
+ "view DSL called outside render (tags may only be used while the component is rendering)"
721
+ end
626
722
  current_children = @current_children
627
- return unless @rendering && current_children
723
+ return unless current_children
628
724
 
629
725
  normalized = normalize_vnode(child)
630
726
  current_children << normalized if normalized
@@ -654,8 +750,8 @@ module Funicular
654
750
  ->(event) { event.preventDefault }
655
751
  end
656
752
 
657
- h.form({ onsubmit: submit_handler, class: form_class }.merge(options)) do |hh|
658
- builder = Funicular::FormBuilder.new(self, hh, model_key, options)
753
+ h.form({ onsubmit: submit_handler, class: form_class }.merge(options)) do
754
+ builder = Funicular::FormBuilder.new(self, h, model_key, options)
659
755
  block.call(builder)
660
756
  end
661
757
  end
@@ -881,15 +977,9 @@ module Funicular
881
977
  end
882
978
 
883
979
  def event_target(event)
884
- target = nil
885
- begin
886
- target = event[:target]
887
- rescue
888
- target = nil
889
- end
890
- return target if target
891
-
892
- event.target if event.respond_to?(:target)
980
+ event[:target]
981
+ rescue
982
+ nil
893
983
  end
894
984
 
895
985
  def add_form_field_value(data, field)
@@ -3,17 +3,23 @@ module Funicular
3
3
  # a fallback UI instead of crashing the entire application.
4
4
  #
5
5
  # Usage:
6
- # h.component(ErrorBoundary) do |hh|
7
- # hh.component(RiskyComponent)
6
+ # component(ErrorBoundary) do
7
+ # component(RiskyComponent)
8
8
  # end
9
9
  #
10
10
  # With custom fallback:
11
- # h.component(ErrorBoundary, fallback: ->(h, error) { h.div { "Error: #{error.message}" } }) do |hh|
12
- # hh.component(RiskyComponent)
11
+ # component(ErrorBoundary, fallback: ->(h, error) { h.div { "Error: #{error.message}" } }) do
12
+ # component(RiskyComponent)
13
13
  # end
14
14
  #
15
15
  # Props:
16
- # - fallback: Proc or Method that receives the error and returns VDOM
16
+ # - fallback: Proc or Method that receives (view_context, error) and
17
+ # returns VDOM. NOTE: unlike everywhere else, the fallback cannot use
18
+ # bareword tags. The proc is created in the parent component's scope
19
+ # but runs during the boundary's render, where only the boundary's
20
+ # cursor is active; a bareword tag would dispatch against the parent
21
+ # and raise RenderContextError. Build elements through the view
22
+ # context argument (`h.div { ... }`) instead.
17
23
  # - on_error: Optional callback when error is caught (for logging, reporting)
18
24
  #
19
25
  class ErrorBoundary < Component
@@ -60,47 +66,47 @@ module Funicular
60
66
  end
61
67
  end
62
68
 
63
- def render(h)
69
+ def render
64
70
  if state[:has_error]
65
- render_fallback(h)
71
+ render_fallback
66
72
  else
67
- render_children(h)
73
+ render_children
68
74
  end
69
75
  end
70
76
 
71
77
  private
72
78
 
73
- def render_fallback(h)
79
+ def render_fallback
74
80
  if props[:fallback]
75
- result = props[:fallback].call(h, state[:error])
81
+ result = props[:fallback].call(__view__, state[:error])
76
82
  if result.is_a?(VDOM::VNode)
77
83
  result
78
84
  else
79
- h.div { result.to_s }
85
+ div { result.to_s }
80
86
  end
81
87
  else
82
- default_fallback(h)
88
+ default_fallback
83
89
  end
84
90
  end
85
91
 
86
- def default_fallback(h)
87
- h.div(class: 'error-boundary-fallback', style: 'padding: 20px; background: #fee; border: 1px solid #f00; border-radius: 4px;') do |hh|
88
- hh.h3(style: 'color: #c00; margin: 0 0 10px 0;') { "Something went wrong" }
92
+ def default_fallback
93
+ div(class: 'error-boundary-fallback', style: 'padding: 20px; background: #fee; border: 1px solid #f00; border-radius: 4px;') do
94
+ h3(style: 'color: #c00; margin: 0 0 10px 0;') { "Something went wrong" }
89
95
  if state[:error]
90
- hh.div(style: 'font-family: monospace; white-space: pre-wrap; font-size: 12px; color: #600;') do
96
+ div(style: 'font-family: monospace; white-space: pre-wrap; font-size: 12px; color: #600;') do
91
97
  "#{state[:error].class}: #{state[:error].message}"
92
98
  end
93
99
  end
94
100
  if Funicular.env.development? && state[:error_info]
95
- hh.div(style: 'margin-top: 10px; font-size: 11px; color: #666;') do
101
+ div(style: 'margin-top: 10px; font-size: 11px; color: #666;') do
96
102
  "Component: #{state[:error_info][:component_class]}"
97
103
  end
98
104
  end
99
105
  end
100
106
  end
101
107
 
102
- def render_children(h)
103
- h.div(class: 'error-boundary-content') do
108
+ def render_children
109
+ div(class: 'error-boundary-content') do
104
110
  children
105
111
  end
106
112
  end
@@ -65,10 +65,10 @@ module Funicular
65
65
  attrs[:class] = css_class unless css_class.empty?
66
66
 
67
67
  # Render field + error message
68
- @view_context.div do |h|
69
- h.input(attrs)
68
+ @view_context.div do
69
+ @view_context.input(attrs)
70
70
  if has_error
71
- h.div(class: @error_class) { error_message }
71
+ @view_context.div(class: @error_class) { error_message }
72
72
  end
73
73
  end
74
74
  end
@@ -128,10 +128,10 @@ module Funicular
128
128
 
129
129
  attrs[:class] = css_class unless css_class.empty?
130
130
 
131
- @view_context.div do |h|
132
- h.textarea(attrs)
131
+ @view_context.div do
132
+ @view_context.textarea(attrs)
133
133
  if has_error
134
- h.div(class: @error_class) { error_message }
134
+ @view_context.div(class: @error_class) { error_message }
135
135
  end
136
136
  end
137
137
  end
@@ -194,18 +194,18 @@ module Funicular
194
194
 
195
195
  attrs[:class] = css_class unless css_class.empty?
196
196
 
197
- @view_context.div do |h|
198
- h.select(attrs) do |hh|
197
+ @view_context.div do
198
+ @view_context.select(attrs) do
199
199
  choices.each do |choice|
200
200
  option_value, option_text = choice.is_a?(Array) ? choice : [choice, choice]
201
201
  selected = value.to_s == option_value.to_s
202
- hh.option(value: option_value, selected: selected) do
202
+ @view_context.option(value: option_value, selected: selected) do
203
203
  option_text
204
204
  end
205
205
  end
206
206
  end
207
207
  if has_error
208
- h.div(class: @error_class) { error_message }
208
+ @view_context.div(class: @error_class) { error_message }
209
209
  end
210
210
  end
211
211
  end
data/mrblib/funicular.rb CHANGED
@@ -18,7 +18,7 @@ module Funicular
18
18
  # Guard against redefinition: when the mrblib runtime is loaded into a
19
19
  # CRuby/Rails process for SSR, lib/funicular/version.rb has already defined
20
20
  # VERSION for the CRuby gem. In the wasm build VERSION is undefined here.
21
- VERSION = '0.3.0' unless Funicular.const_defined?(:VERSION)
21
+ VERSION = '0.4.0' unless Funicular.const_defined?(:VERSION)
22
22
 
23
23
  def self.version
24
24
  VERSION
data/mrblib/styles.rb CHANGED
@@ -25,25 +25,42 @@ module Funicular
25
25
  end
26
26
  end
27
27
 
28
- class StyleAccessor
28
+ # Read-side accessor. One subclass is generated per component class with a
29
+ # real method per declared style name (see accessor_for), so lookups never
30
+ # go through method_missing at render time and a typo raises loudly.
31
+ # BasicObject keeps Object/Kernel names (display, hash, ...) usable as
32
+ # style names on both mruby and CRuby.
33
+ class StyleAccessor < BasicObject
34
+ def self.accessor_for(definitions)
35
+ klass = ::Class.new(self) #: singleton(StyleAccessor)
36
+ definitions.each_key do |name|
37
+ klass.__send__(:define_method, name) do |variant = nil|
38
+ self[name, variant] # steep:ignore
39
+ end
40
+ end
41
+ klass
42
+ end
43
+
29
44
  def initialize(definitions)
30
45
  @definitions = definitions
31
46
  end
32
47
 
33
48
  def [](name, variant = nil)
34
49
  style = @definitions[name]
35
- return StyleValue.new("") unless style
50
+ unless style
51
+ ::Kernel.raise ::ArgumentError,
52
+ "unknown style :#{name} (declared: #{@definitions.keys.map { |k| ":#{k}" }.join(', ')})"
53
+ end
36
54
 
37
55
  if variant.nil?
38
56
  # No arguments: return base or value
39
57
  StyleValue.new(style[:base] || style[:value] || "")
40
58
  elsif variant == true || variant == false
41
59
  # Boolean argument: base + active (if true)
42
- # JS::Object#== now supports direct comparison with Ruby true/false
43
60
  base = style[:base] || ""
44
61
  active_class = (variant == true) ? (style[:active] || "") : ""
45
62
  StyleValue.new("#{base} #{active_class}".strip)
46
- elsif variant.is_a?(Symbol)
63
+ elsif ::Symbol === variant
47
64
  # Symbol argument: base + variants[symbol]
48
65
  base = style[:base] || ""
49
66
  variant_class = style[:variants] ? (style[:variants][variant] || "") : ""
@@ -53,27 +70,100 @@ module Funicular
53
70
  StyleValue.new(style[:base] || style[:value] || "")
54
71
  end
55
72
  end
73
+
74
+ def method_missing(name, *_args)
75
+ ::Kernel.raise ::NoMethodError,
76
+ "unknown style '#{name}' (declared: #{@definitions.keys.map { |k| ":#{k}" }.join(', ')})"
77
+ end
78
+
79
+ def respond_to_missing?(name, _include_private = false)
80
+ @definitions.key?(name)
81
+ end
56
82
  end
57
83
 
58
- class StyleBuilder
84
+ # Definition-side builder. The class-level `styles do ... end` block is
85
+ # instance_exec'd on an instance, so barewords define styles:
86
+ #
87
+ # styles do
88
+ # shell "app-shell"
89
+ # button base: "btn", variants: { primary: "btn--primary" }
90
+ # end
91
+ #
92
+ # BasicObject keeps the bareword namespace clean on both VMs; the block
93
+ # also receives the builder as an argument for the explicit form
94
+ # `styles { |css| css.define(:name, ...) }`, needed when a value is
95
+ # computed (a bareword helper call inside the block would be captured as
96
+ # a style definition, and its nil return raises via validation below).
97
+ class StyleBuilder < BasicObject
98
+ # Style names that would clobber the builder/accessor internals.
99
+ RESERVED_NAMES = %i[
100
+ initialize method_missing respond_to_missing? define to_definitions
101
+ validate_options
102
+ == != ! [] equal? __send__ __id__ instance_eval instance_exec
103
+ ]
104
+
59
105
  def initialize
60
- @definitions = {}
106
+ @definitions = {} #: Hash[Symbol, untyped]
61
107
  end
62
108
 
63
109
  def define(name, value = nil, **options)
64
- if value.is_a?(String)
110
+ if RESERVED_NAMES.include?(name)
111
+ ::Kernel.raise ::ArgumentError, "style name :#{name} is reserved"
112
+ end
113
+ if @definitions.key?(name)
114
+ ::Kernel.raise ::ArgumentError, "style :#{name} is already defined"
115
+ end
116
+
117
+ if ::String === value
65
118
  @definitions[name] = { value: value }
66
- elsif value.is_a?(Hash)
67
- @definitions[name] = value
68
- elsif !options.empty?
69
- @definitions[name] = options
119
+ elsif ::Hash === value
120
+ @definitions[name] = validate_options(name, value)
121
+ elsif (::NilClass === value) && !options.empty?
122
+ @definitions[name] = validate_options(name, options)
70
123
  else
71
- raise ArgumentError, "Invalid style definition for #{name}"
124
+ ::Kernel.raise ::ArgumentError,
125
+ "invalid style definition for :#{name}; expected a String, a " \
126
+ "Hash, or keyword options. Note: helper methods cannot be called " \
127
+ "bareword inside a styles block; use " \
128
+ "`styles { |css| css.define(...) }` for computed values."
129
+ end
130
+ self
131
+ end
132
+
133
+ def validate_options(name, options)
134
+ options.each do |key, val|
135
+ case key
136
+ when :value, :base, :active
137
+ unless ::String === val
138
+ ::Kernel.raise ::ArgumentError,
139
+ "style :#{name} option #{key}: must be a String. Note: helper " \
140
+ "methods cannot be called bareword inside a styles block; use " \
141
+ "`styles { |css| css.define(...) }` for computed values."
142
+ end
143
+ when :variants
144
+ unless (::Hash === val) && val.all? { |_k, v| ::String === v }
145
+ ::Kernel.raise ::ArgumentError,
146
+ "style :#{name} option variants: must be a Hash of Symbol => String"
147
+ end
148
+ else
149
+ ::Kernel.raise ::ArgumentError,
150
+ "style :#{name} has unknown option #{key.inspect} " \
151
+ "(allowed: value, base, active, variants)"
152
+ end
72
153
  end
154
+ options
73
155
  end
74
156
 
75
157
  def to_definitions
76
158
  @definitions
77
159
  end
160
+
161
+ def method_missing(name, value = nil, **options)
162
+ define(name, value, **options)
163
+ end
164
+
165
+ def respond_to_missing?(_name, _include_private = false)
166
+ true
167
+ end
78
168
  end
79
169
  end
@@ -1,40 +1,11 @@
1
1
  module Funicular
2
2
  class ViewContext
3
- HTML_TAGS = %w[
4
- div span p a data
5
- h1 h2 h3 h4 h5 h6
6
- ul ol li
7
- table thead tbody tr th td
8
- form input textarea button select option label
9
- header footer nav section article aside
10
- img video audio canvas
11
- br hr
12
- ]
3
+ HTML_TAGS = Funicular::Tags::HTML_TAGS
13
4
 
14
5
  def initialize(component)
15
6
  @component = component
16
7
  end
17
8
 
18
- def state
19
- @component.state
20
- end
21
-
22
- def props
23
- @component.props
24
- end
25
-
26
- def resources
27
- @component.resources
28
- end
29
-
30
- def styles
31
- @component.styles
32
- end
33
-
34
- def routes
35
- @component.runtime.routes
36
- end
37
-
38
9
  def tag(name, props = {}, &block)
39
10
  build_element(name, props, &block)
40
11
  end
@@ -66,7 +37,7 @@ module Funicular
66
37
  end
67
38
 
68
39
  def suspense(name, fallback:, error: nil, &block)
69
- @component.render_suspense(self, name, fallback: fallback, error: error, &block)
40
+ @component.render_suspense(name, fallback: fallback, error: error, &block)
70
41
  end
71
42
 
72
43
  def link_to(path, **options, &block)
@@ -81,7 +52,7 @@ module Funicular
81
52
  children = [] #: Array[Funicular::VDOM::child_t]
82
53
  previous = @component.current_children
83
54
  @component.current_children = children
84
- result = block.call(self)
55
+ result = block.call
85
56
  @component.current_children = previous
86
57
 
87
58
  if result && !result.equal?(children) && children.empty?
data/sig/component.rbs CHANGED
@@ -17,6 +17,8 @@ module Funicular
17
17
  def error: (Symbol key) -> untyped
18
18
  end
19
19
 
20
+ include Tags
21
+
20
22
  attr_accessor props: Hash[Symbol, untyped]
21
23
  attr_accessor vdom: VDOM::VNode | VDOM::Text | nil
22
24
  attr_accessor dom_element: JS::Element
@@ -29,8 +31,13 @@ module Funicular
29
31
  type suspense_definition = { loader: untyped, on_resolve: untyped, min_delay: Integer? }
30
32
 
31
33
  def initialize: (?Hash[Symbol, untyped] props) -> void
34
+ def self.allow_dsl_override: (*Symbol names) -> void
35
+ def self.dsl_overrides: () -> Array[Symbol]
36
+ def self.method_added: (Symbol name) -> void
37
+ def self.validate_dsl_conflicts!: () -> void
32
38
  def self.styles: () { (StyleBuilder) -> void } -> void
33
39
  def self.styles_definitions: () -> Hash[Symbol, Hash[Symbol, untyped]]
40
+ def self.style_accessor_class: () -> singleton(StyleAccessor)
34
41
  def self.use_suspense: (Symbol name, untyped loader, ?on_resolve: untyped, ?min_delay: Integer) -> void
35
42
  def self.suspense_definitions: () -> Hash[Symbol, suspense_definition]
36
43
 
@@ -46,22 +53,29 @@ module Funicular
46
53
  def suspense_loading?: (*Symbol names) -> bool
47
54
  def suspense_error?: (Symbol name) -> bool
48
55
  def suspense_error: (Symbol name) -> untyped
49
- def render_suspense: (ViewContext h, Symbol name, fallback: untyped, ?error: untyped) { (ViewContext, ResourceAccessor) -> untyped } -> untyped
56
+ def render_suspense: (Symbol name, fallback: untyped, ?error: untyped) { (ResourceAccessor) -> untyped } -> untyped
50
57
 
51
58
  def patch: (Hash[Symbol, untyped] new_state) -> void
52
59
  def mount: (JS::Element container) -> void
53
60
  def hydrate: (JS::Element dom_element) -> void
54
61
  def unmount: () -> void
55
- def render: (ViewContext h) -> (VDOM::VNode | String | Integer | Float | Array[untyped] | nil)
62
+ def render: () -> (VDOM::VNode | String | Integer | Float | Array[untyped] | nil)
56
63
  def build_vdom: () -> (VDOM::VNode | VDOM::Text | nil)
57
64
 
58
65
  def bind_events: (JS::Element dom_element, VDOM::VNode | VDOM::Text | nil vnode) -> void
59
66
  def collect_refs: (JS::Element dom_element, VDOM::VNode | VDOM::Text | nil vnode, ?Hash[Symbol, JS::Element] refs_map) -> Hash[Symbol, JS::Element]
60
67
  def normalize_vnode_for_view: (untyped value) -> (VDOM::Element | VDOM::Text | VDOM::Component | nil)
68
+ def __view__: () -> ViewContext
69
+ def routes: () -> untyped
70
+ def component: (singleton(Component) component_class, ?Hash[Symbol, untyped] props) ?{ () -> untyped } -> VDOM::Component
71
+ def form_for: (Symbol model_key, ?Hash[Symbol, untyped] options) { (FormBuilder) -> void } -> VDOM::Element
72
+ def link_to: (String path, **untyped options) ?{ -> untyped } -> VDOM::Element
73
+ def button_to: (String path, ?method: Symbol, **untyped options) ?{ -> untyped } -> VDOM::Element
74
+ def suspense: (Symbol name, fallback: untyped, ?error: untyped) { (ResourceAccessor) -> untyped } -> untyped
61
75
  def add_child_from_view: (untyped child) -> void
62
76
  def build_form_for: (ViewContext h, Symbol model_key, ?Hash[Symbol, untyped] options) { (FormBuilder) -> void } -> VDOM::Element
63
- def build_link_to: (ViewContext h, String path, **untyped options) { (ViewContext) -> untyped } -> VDOM::Element
64
- def build_button_to: (ViewContext h, String path, ?method: Symbol, **untyped options) { (ViewContext) -> untyped } -> VDOM::Element
77
+ def build_link_to: (ViewContext h, String path, **untyped options) ?{ () -> untyped } -> VDOM::Element
78
+ def build_button_to: (ViewContext h, String path, ?method: Symbol, **untyped options) ?{ () -> untyped } -> VDOM::Element
65
79
 
66
80
  def component_will_mount: () -> void
67
81
  def component_mounted: () -> void
@@ -5,10 +5,10 @@ module Funicular
5
5
  def initialize_state: () -> Hash[Symbol, untyped]
6
6
  def catch_error: (Exception error, ?Hash[Symbol, untyped]? error_info) -> bool
7
7
  def reset: () -> void
8
- def render: (ViewContext h) -> (VDOM::VNode | String | Integer | Float | Array[untyped] | nil)
8
+ def render: () -> (VDOM::VNode | String | Integer | Float | Array[untyped] | nil)
9
9
 
10
- private def render_fallback: (ViewContext h) -> (VDOM::VNode | String)
11
- private def default_fallback: (ViewContext h) -> VDOM::VNode
12
- private def render_children: (ViewContext h) -> VDOM::VNode
10
+ private def render_fallback: () -> (VDOM::VNode | String)
11
+ private def default_fallback: () -> VDOM::VNode
12
+ private def render_children: () -> VDOM::VNode
13
13
  end
14
14
  end