Module: Plushie::Widget::CustomDSL

Defined in:
lib/plushie/widget.rb

Overview

DSL methods added to classes that include Plushie::Widget or are created via Widget.define.

Constant Summary collapse

VALID_KINDS =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Valid widget kind values.

%i[widget native_widget].freeze

Instance Method Summary collapse

Instance Method Details

#a11y_defaultsHash?

Returns the default a11y annotations, or nil.

Returns:

  • (Hash, nil)


381
# File 'lib/plushie/widget.rb', line 381

def a11y_defaults = @_a11y_defaults

#cache_key(fn) ⇒ Object

Declares a cache key function for view-level caching.

When the cache key proc returns the same value as the previous render, the widget's view is skipped and the cached normalized output is reused. Complementary to memo (subtree-level caching).

Examples:

cache_key ->(props, state) { [props[:version], state[:zoom]] }

Parameters:

  • fn (Proc)

    receives (props, state), returns a cache key



244
245
246
# File 'lib/plushie/widget.rb', line 244

def cache_key(fn)
  @_widget_cache_key = fn
end

#cache_key_fnProc?

Returns the cache key function, or nil.

Returns:

  • (Proc, nil)


373
# File 'lib/plushie/widget.rb', line 373

def cache_key_fn = @_widget_cache_key

#children(mode)

This method returns an undefined value.

Declare children mode.

Parameters:

  • mode (:none, :single, :many, Integer)

    child constraint



202
203
204
205
# File 'lib/plushie/widget.rb', line 202

def children(mode)
  @_widget_children_mode = mode
  @_widget_container = mode && mode != :none
end

#children_modeSymbol, ...

Returns the children mode (:none, :single, :many, or Integer).

Returns:

  • (Symbol, Integer, nil)


369
# File 'lib/plushie/widget.rb', line 369

def children_mode = @_widget_children_mode

#command(name, **params)

This method returns an undefined value.

Declares a command (for native widgets, informational in Ruby).

Parameters:

  • name (Symbol)

    command name

  • params (Hash{Symbol => Symbol})

    parameter names to types



296
297
298
# File 'lib/plushie/widget.rb', line 296

def command(name, **params)
  @_widget_commands << {name: name.to_sym, params: params}
end

#container?Boolean

Whether this is a container widget (accepts children).

Returns:

  • (Boolean)


365
# File 'lib/plushie/widget.rb', line 365

def container? = @_widget_container

#default_a11y(**defaults)

This method returns an undefined value.

Declare default a11y annotations for this widget type.

Merged into the widget's a11y prop during build when the user hasn't provided explicit overrides. User values win per field.

Examples:

default_a11y role: :button, label_from: :label

Parameters:

  • defaults (Hash)

    default a11y fields

Options Hash (**defaults):

  • :role (Symbol)

    accessible role

  • :label_from (Symbol)

    prop name to derive label from



218
219
220
# File 'lib/plushie/widget.rb', line 218

def default_a11y(**defaults)
  @_a11y_defaults = defaults.freeze
end

#event(name, fields: nil)

This method returns an undefined value.

Declares an event that this widget can emit.

Event declarations document the widget's public event contract. Widgets with event declarations or +handle_event+ participate in the event dispatch chain.

With +fields:+, declares typed fields that are validated at emit time. Fields are required by default. Use +Class, required: false+ to make a field optional.

Examples:

Simple event

event :click

Event with typed required fields

event :change, fields: {hue: Numeric, saturation: Numeric}

Event with optional field

event :change, fields: {
  hue: Numeric,
  saturation: Numeric,
  modifier: {type: String, required: false}
}

Parameters:

  • name (Symbol)

    event name (e.g. +:select+, +:change+)

  • fields (Hash{Symbol => Class, Hash}) (defaults to: nil)

    typed field declarations



274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
# File 'lib/plushie/widget.rb', line 274

def event(name, fields: nil)
  name = name.to_sym
  spec = if fields && !fields.empty?
    resolved_fields = fields.each_with_object({}) do |(k, v), h|
      h[k] = if v.is_a?(Hash)
        {type: v[:type], required: v.fetch(:required, true)}
      else
        {type: v, required: true}
      end
    end
    {name: name, fields: resolved_fields}
  else
    {name: name, fields: nil}
  end
  @_widget_events << spec
end

#finalize!

This method returns an undefined value.

Finalize the widget class by generating initialize, setters, and build.

Called automatically on first instantiation, or explicitly by Widget.define after the block has been evaluated.



394
395
396
397
398
399
400
401
402
403
404
405
# File 'lib/plushie/widget.rb', line 394

def finalize!
  return if @_finalized

  _validate!
  _set_defaults!
  _generate_readers!
  _generate_initialize!
  _generate_setters!
  _generate_push! if container?
  _generate_build!
  @_finalized = true
end

#native?Boolean

Whether this is a native (Rust-backed) widget.

Returns:

  • (Boolean)


326
# File 'lib/plushie/widget.rb', line 326

def native? = @_widget_kind == :native_widget

#native_crateString?

Returns Rust crate path.

Returns:

  • (String, nil)

    Rust crate path



319
# File 'lib/plushie/widget.rb', line 319

def native_crate = @_widget_native_crate

#positional(name, default: REQUIRED)

This method returns an undefined value.

Declare a positional constructor argument.

Call order determines argument order after +id+. The name must also be a declared prop.

Parameters:

  • name (Symbol)

    argument name

  • default (Object) (defaults to: REQUIRED)

    default value (omit for mandatory)



194
195
196
# File 'lib/plushie/widget.rb', line 194

def positional(name, default: REQUIRED)
  @_widget_positionals << {name: name.to_sym, default: default}
end

#prop(*names, type: nil, doc: nil, default: nil)

This method returns an undefined value.

Declare one or more props.

Supports three forms:

  • Simple: +prop :label, :width, :height+ (names only, untyped)
  • Typed: +prop :value, :number, default: 0+ (name + type)
  • Rich: +prop :label, type: :string, doc: "Text label"+ (name + metadata)

Types are informational: they document the prop for introspection and future tooling. Values pass through to the wire protocol where the renderer handles validation.

Parameters:

  • names (Array<Symbol>)

    prop names

  • type (Symbol, nil) (defaults to: nil)

    type hint (rich form)

  • doc (String, nil) (defaults to: nil)

    documentation (rich form)

  • default (Object) (defaults to: nil)

    default value



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/plushie/widget.rb', line 147

def prop(*names, type: nil, doc: nil, default: nil)
  if names.length == 1 && (type || doc)
    # Rich form: prop :name, type: :string, doc: "..."
    name = names[0].to_sym
    if type && !KNOWN_PROP_TYPES.include?(type.to_sym)
      raise ArgumentError,
        "unsupported prop type #{type.inspect} for #{name.inspect}. " \
        "Known types: #{KNOWN_PROP_TYPES.inspect}"
    end
    is_auto = _check_prop_name!(name)
    unless is_auto
      @_widget_props << {name: name, type: type, default: default}
      (@_prop_meta ||= {})[name] = {type: type, doc: doc}.compact
    end
  elsif names.length == 2 && KNOWN_PROP_TYPES.include?(names[1].to_sym)
    # Typed form: prop :name, :string, default: 0
    name = names[0].to_sym
    type_val = names[1].to_sym
    is_auto = _check_prop_name!(name)
    unless is_auto
      @_widget_props << {name: name, type: type_val, default: default}
    end
  else
    # Simple form: prop :name1, :name2, ...
    if default
      raise ArgumentError,
        "default: cannot be used with the multi-name prop form. " \
        "Use `prop :#{names.first}, type: :any, default: ...` for a single prop with a default"
    end
    names.each do |n|
      sym = n.to_sym
      is_auto = _check_prop_name!(sym)
      next if is_auto

      @_widget_props << {name: sym, type: nil, default: nil}
    end
  end
end

#prop_metaHash{Symbol => Hash}

Returns metadata for declared props (type, doc).

Returns:

  • (Hash{Symbol => Hash})


377
# File 'lib/plushie/widget.rb', line 377

def prop_meta = @_prop_meta || {}

#prop_namesArray<Symbol>

Returns all declared prop names (plus auto-added :a11y, :event_rate).

Returns:

  • (Array<Symbol>)


343
344
345
# File 'lib/plushie/widget.rb', line 343

def prop_names
  @_widget_props.map { _1[:name] } + %i[a11y event_rate]
end

#rust_constructor(expr)

This method returns an undefined value.

Declares the Rust constructor expression used in the generated main.rs. Required for +:native_widget+ widgets.

Parameters:

  • expr (String)

    Rust expression



312
313
314
# File 'lib/plushie/widget.rb', line 312

def rust_constructor(expr)
  @_widget_rust_constructor = expr.to_s
end

#rust_constructor_exprString?

Returns Rust constructor expression.

Returns:

  • (String, nil)

    Rust constructor expression



322
# File 'lib/plushie/widget.rb', line 322

def rust_constructor_expr = @_widget_rust_constructor

#rust_crate(path)

This method returns an undefined value.

Declares the relative path to the Rust crate directory. Required for +:native_widget+ widgets.

Parameters:

  • path (String)

    path to crate



304
305
306
# File 'lib/plushie/widget.rb', line 304

def rust_crate(path)
  @_widget_native_crate = path.to_s
end

#state(name, default: nil)

This method returns an undefined value.

Declares a state field with a default value.

Declaring any state field makes the widget stateful: the runtime manages its state via a registry, renders it during tree normalization, and dispatches events through +handle_event+.

Parameters:

  • name (Symbol)

    state field name

  • default (Object) (defaults to: nil)

    initial value



231
232
233
# File 'lib/plushie/widget.rb', line 231

def state(name, default: nil)
  @_widget_state_fields << {name: name.to_sym, default: default}
end

#stateful?Boolean

Whether this widget is stateful (has state, events, or handle_event).

Returns:

  • (Boolean)


330
331
332
333
334
335
# File 'lib/plushie/widget.rb', line 330

def stateful?
  !@_widget_state_fields.empty? ||
    !@_widget_events.empty? ||
    _owns_singleton_method?(:init) ||
    _owns_singleton_method?(:handle_event)
end

#type_namesArray<Symbol>

Returns the widget type names this widget handles.

Returns:

  • (Array<Symbol>)


339
# File 'lib/plushie/widget.rb', line 339

def type_names = [@_widget_type]

#validate_composite_type(name, value, type_spec) ⇒ Object



595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
# File 'lib/plushie/widget.rb', line 595

def validate_composite_type(name, value, type_spec)
  if (tuple_types = type_spec[:tuple])
    unless value.is_a?(Array) && value.length == tuple_types.length
      raise ArgumentError,
        "#{name} expects a #{tuple_types.length}-element Array, " \
        "got #{value.inspect}"
    end
    tuple_types.each_with_index do |elem_type, i|
      validate_prop_type("#{name}[#{i}]", value[i], elem_type)
    end
  elsif (enum_values = type_spec[:enum])
    unless enum_values.include?(value)
      raise ArgumentError,
        "#{name} expects one of #{enum_values.inspect}, got #{value.inspect}"
    end
  elsif (list_type = type_spec[:list])
    raise ArgumentError, "#{name} expects an Array, got #{value.class}" unless value.is_a?(Array)

    value.each_with_index do |elem, i|
      validate_prop_type("#{name}[#{i}]", elem, list_type)
    end
  elsif (map_types = type_spec[:map])
    raise ArgumentError, "#{name} expects a Hash, got #{value.class}" unless value.is_a?(Hash)

    map_types.each do |key, val_type|
      validate_prop_type("#{name}[#{key.inspect}]", value[key], val_type) if value.key?(key)
    end
  end
end

#validate_prop_type(name, value, type) ⇒ Object

Validate a prop value against its declared type. Supports Class/module types, :numeric, :boolean, and composite types ([T1, T2], [:a, :b], T).



568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
# File 'lib/plushie/widget.rb', line 568

def validate_prop_type(name, value, type)
  case type
  when Class, Module
    unless value.is_a?(type)
      raise ArgumentError,
        "#{name} expects #{type}, got #{value.class}: #{value.inspect}"
    end
  when :numeric
    unless value.is_a?(Numeric)
      raise ArgumentError,
        "#{name} expects a Numeric, got #{value.class}: #{value.inspect}"
    end
  when :boolean
    unless [true, false].include?(value)
      raise ArgumentError,
        "#{name} expects true or false, got #{value.inspect}"
    end
  when :symbol
    unless value.is_a?(Symbol)
      raise ArgumentError,
        "#{name} expects a Symbol, got #{value.class}: #{value.inspect}"
    end
  when Hash
    validate_composite_type(name, value, type)
  end
end

#widget(type_name, **opts)

This method returns an undefined value.

Declares the widget type name.

Parameters:

  • type_name (Symbol)

    the wire type name for this widget

  • opts (Hash)

    options

Options Hash (**opts):

  • :kind (Symbol) — default: :widget

    either +:widget+ or +:native_widget+



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/plushie/widget.rb', line 115

def widget(type_name, **opts)
  kind = opts.fetch(:kind, :widget)
  unless VALID_KINDS.include?(kind)
    raise ArgumentError,
      "unsupported widget kind #{kind.inspect}. Supported: #{VALID_KINDS.inspect}"
  end

  @_widget_type = type_name
  @_widget_kind = kind

  return unless opts.key?(:container)

  mode = (opts[:container] == true) ? :many : opts[:container]
  children(mode)
end

#widget_event_specsArray<Hash{Symbol => Object}>

Returns the declared event specs with field definitions.

Returns:

  • (Array<Hash{Symbol => Object}>)


361
# File 'lib/plushie/widget.rb', line 361

def widget_event_specs = @_widget_events

#widget_eventsArray<Symbol>

Returns the declared event names.

Returns:

  • (Array<Symbol>)


357
# File 'lib/plushie/widget.rb', line 357

def widget_events = @_widget_events.map { |e| e[:name] }

#widget_positionalsArray<Hash>

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns positional argument declarations.

Returns:

  • (Array<Hash>)

    positional argument declarations



385
# File 'lib/plushie/widget.rb', line 385

def widget_positionals = @_widget_positionals

#widget_propsArray<Hash{Symbol => Object}>

Returns the declared props metadata.

Returns:

  • (Array<Hash{Symbol => Object}>)


349
# File 'lib/plushie/widget.rb', line 349

def widget_props = @_widget_props

#widget_state_fieldsArray<Hash{Symbol => Object}>

Returns the declared state fields.

Returns:

  • (Array<Hash{Symbol => Object}>)


353
# File 'lib/plushie/widget.rb', line 353

def widget_state_fields = @_widget_state_fields