line-message-builder 0.7.0 → 0.9.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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/.document +3 -0
  3. data/.release-please-manifest.json +1 -1
  4. data/.rubocop.yml +2 -0
  5. data/CHANGELOG.md +35 -0
  6. data/CONVENTIONS.md +36 -0
  7. data/README.md +49 -31
  8. data/lib/line/message/builder/actions/message.rb +67 -1
  9. data/lib/line/message/builder/actions/postback.rb +71 -1
  10. data/lib/line/message/builder/actions.rb +18 -1
  11. data/lib/line/message/builder/base.rb +113 -3
  12. data/lib/line/message/builder/container.rb +125 -5
  13. data/lib/line/message/builder/context.rb +111 -3
  14. data/lib/line/message/builder/flex/actionable.rb +46 -1
  15. data/lib/line/message/builder/flex/box.rb +191 -15
  16. data/lib/line/message/builder/flex/bubble.rb +96 -8
  17. data/lib/line/message/builder/flex/builder.rb +74 -8
  18. data/lib/line/message/builder/flex/button.rb +104 -17
  19. data/lib/line/message/builder/flex/carousel.rb +71 -8
  20. data/lib/line/message/builder/flex/image.rb +105 -20
  21. data/lib/line/message/builder/flex/partial.rb +94 -9
  22. data/lib/line/message/builder/flex/position.rb +122 -12
  23. data/lib/line/message/builder/flex/separator.rb +41 -0
  24. data/lib/line/message/builder/flex/size.rb +65 -7
  25. data/lib/line/message/builder/flex/span.rb +110 -0
  26. data/lib/line/message/builder/flex/text.rb +176 -28
  27. data/lib/line/message/builder/flex.rb +56 -12
  28. data/lib/line/message/builder/quick_reply.rb +16 -4
  29. data/lib/line/message/builder/text.rb +12 -1
  30. data/lib/line/message/builder/version.rb +1 -1
  31. data/lib/line/message/builder.rb +33 -3
  32. data/lib/line/message/rspec/matchers/have_flex_component.rb +11 -0
  33. data/lib/line/message/rspec/matchers/have_flex_separator.rb +20 -0
  34. data/lib/line/message/rspec/matchers.rb +1 -0
  35. data/lib/line/message/rspec.rb +14 -0
  36. data/llm.txt +437 -0
  37. metadata +6 -1
@@ -3,31 +3,151 @@
3
3
  module Line
4
4
  module Message
5
5
  module Builder
6
- # The container class is main container to manage messages.
7
- class Container < Base
6
+ # The `Container` class is the top-level entry point for constructing a batch
7
+ # of LINE messages using the builder DSL. It acts as a holder for one or
8
+ # more individual message objects (such as {Text} or {Flex} messages).
9
+ #
10
+ # When you use `Line::Message::Builder.with {}`, you are operating within
11
+ # the context of a `Container` instance. This container allows you to define
12
+ # multiple messages that can be sent together in a single API call to LINE,
13
+ # although the LINE API typically expects an array of message objects,
14
+ # which this container helps to build.
15
+ #
16
+ # Each message added to the container can also have its own quick reply.
17
+ #
18
+ # @example Building multiple messages
19
+ # message_payload = Line::Message::Builder.with do
20
+ # text "Hello, this is the first message!"
21
+ # flex alt_text: "This is a Flex Message" do
22
+ # bubble do
23
+ # body do
24
+ # body.text "This is a Flex Message body."
25
+ # end
26
+ # end
27
+ # end
28
+ # end.build # => Returns an array of message hashes
29
+ #
30
+ # @see Base
31
+ # @see Text
32
+ # @see Flex::Builder
33
+ # @see QuickReply
34
+ class Container
35
+ # @!attribute [r] context
36
+ # @return [Context] The context object, which can hold external data or
37
+ # helper methods accessible within the builder blocks.
8
38
  attr_reader :context
9
39
 
10
- def initialize(context: nil, &)
11
- @messages = []
40
+ # Initializes a new message container.
41
+ # This is typically not called directly but through `Line::Message::Builder.with`.
42
+ # The provided block is instance-eval'd, allowing DSL methods like
43
+ # {#text} and {#flex} to be called directly on the container instance.
44
+ #
45
+ # @param context [Object, nil] An optional context object that can be used
46
+ # to share data or helper methods within the builder block. It's wrapped
47
+ # in a {Context} object.
48
+ # @param mode [Symbol] The mode to use for building messages. Can be either
49
+ # `:api` (default) for direct LINE Messaging API format or `:sdkv2` for
50
+ # LINE Bot SDK v2 compatible format.
51
+ # @param block [Proc] A block containing DSL calls to define messages
52
+ # (e.g., `text "Hello"`, `flex { ... }`).
53
+ def initialize(context: nil, mode: :api, &block)
54
+ @messages = [] # Initializes an empty array to store message objects
55
+ @context = Context.new(context, mode:)
12
56
 
13
- super
57
+ instance_eval(&block) if ::Kernel.block_given?
14
58
  end
15
59
 
60
+ # Creates a new {Text} message and adds it to this container.
61
+ #
62
+ # @param text [String] The text content of the message.
63
+ # @param options [Hash] Additional options for the text message,
64
+ # such as `:quick_reply`. See {Text#initialize}.
65
+ # @param block [Proc, nil] An optional block that will be instance-eval'd
66
+ # in the context of the new {Text} message instance. This can be used
67
+ # to add a quick reply to the text message.
68
+ # @return [Text] The newly created {Text} message object.
69
+ #
70
+ # @example
71
+ # root.text "Hello, world!" do
72
+ # quick_reply do
73
+ # button action: :message, label: "Hi!", text: "Hi!"
74
+ # end
75
+ # end
16
76
  def text(text, **options, &)
17
77
  @messages << Text.new(text, context: context, **options, &)
18
78
  end
19
79
 
80
+ # Creates a new {Flex::Builder} for constructing a Flex Message and adds it
81
+ # to this container. The block is mandatory and is used to define the
82
+ # content of the Flex Message using the Flex Message DSL.
83
+ #
84
+ # @param options [Hash] Options for the Flex Message, primarily `:alt_text`.
85
+ # See {Flex::Builder#initialize}. It's important to provide `alt_text`.
86
+ # @param block [Proc] A block that will be instance-eval'd in the context
87
+ # of the new {Flex::Builder} instance. This block is used to define the
88
+ # structure and content of the Flex Message (e.g., bubbles, carousels).
89
+ # @return [Flex::Builder] The newly created {Flex::Builder} object.
90
+ # @raise [ArgumentError] if `alt_text` is not provided in options (validation
91
+ # is typically within Flex::Builder).
92
+ #
93
+ # @example
94
+ # flex alt_text: "Important information" do
95
+ # bubble do
96
+ # header { text "Header" }
97
+ # body { text "Body" }
98
+ # end
99
+ # end
20
100
  def flex(**options, &)
21
101
  @messages << Flex::Builder.new(context: context, **options, &)
22
102
  end
23
103
 
104
+ # Converts all messages held by this container into their hash representations.
105
+ # This method iterates over each message object (e.g., {Text}, {Flex::Builder})
106
+ # stored in the container and calls its `to_h` method. The result is an
107
+ # array of hashes, where each hash represents a single LINE message object
108
+ # ready for JSON serialization and sending to the LINE Messaging API.
109
+ #
110
+ # @return [Array<Hash>] An array of message objects, each represented as a hash.
111
+ # This is the format expected by the LINE API for the `messages` field
112
+ # in a request body.
24
113
  def build
25
114
  @messages.map(&:to_h)
26
115
  end
27
116
 
117
+ # Converts the array of message hashes (obtained from {#build}) into a
118
+ # JSON string. This is a convenience method for serializing the messages
119
+ # payload.
120
+ #
121
+ # @param args [Object] Optional arguments that are passed along to `to_json`
122
+ # method of the underlying array.
123
+ # @return [String] A JSON string representing the array of message objects.
28
124
  def to_json(*args)
29
125
  build.to_json(*args)
30
126
  end
127
+
128
+ # Checks if a method is defined in the context object.
129
+ # This is part of Ruby's method_missing mechanism.
130
+ #
131
+ # @param method_name [Symbol] The name of the method being checked
132
+ # @param include_private [Boolean] Whether to include private methods
133
+ # @return [Boolean] True if the method exists in the context, false otherwise
134
+ def respond_to_missing?(method_name, include_private = false)
135
+ context.respond_to?(method_name, include_private) || super
136
+ end
137
+
138
+ # Delegates method calls to the context object if they exist there.
139
+ # This allows helper methods defined in the context to be called directly
140
+ # from within the builder DSL.
141
+ #
142
+ # @param method_name [Symbol] The name of the method being called
143
+ # @param args [Array] The arguments passed to the method
144
+ # @return [Object] The result of calling the method on the context
145
+ # @raise [NoMethodError] If the method doesn't exist in the context
146
+ def method_missing(method_name, ...)
147
+ return context.send(method_name, ...) if context.respond_to?(method_name)
148
+
149
+ super
150
+ end
31
151
  end
32
152
  end
33
153
  end
@@ -3,27 +3,135 @@
3
3
  module Line
4
4
  module Message
5
5
  module Builder
6
- # The context is wrapper for the context object to provide assigns support.
6
+ # The `Context` class is a crucial component of the `Line::Message::Builder`
7
+ # DSL, enabling a flexible and dynamic environment for message construction.
8
+ # It acts as a wrapper around an optional user-provided context object and
9
+ # manages a separate hash of "assigns" (local variables for the DSL).
10
+ #
11
+ # The primary purposes of the `Context` are:
12
+ # 1. **To provide access to helper methods**: If a user passes a context object
13
+ # (e.g., a Rails view context, a presenter, or any custom object) during
14
+ # builder initialization (`Line::Message::Builder.with(my_helper_object)`),
15
+ # methods defined on `my_helper_object` become directly callable within
16
+ # the DSL blocks.
17
+ # 2. **To allow local data storage (`assigns`)**: The `assigns` hash allows
18
+ # for setting and retrieving temporary data that can be shared across
19
+ # different parts of a complex message construction block, without needing
20
+ # to pass it explicitly or pollute the user-provided context.
21
+ #
22
+ # Method calls within the DSL that are not defined on the builder objects
23
+ # themselves are resolved by `Context` in the following order:
24
+ # - First, it checks if the method name corresponds to a key in the `assigns` hash.
25
+ # - If not found in `assigns`, it checks if the wrapped user-context object
26
+ # responds to the method.
27
+ # - If neither, the call proceeds up the normal Ruby method lookup chain.
28
+ #
29
+ # This mechanism allows for a clean and powerful way to integrate external logic
30
+ # and data into the message building process.
31
+ #
32
+ # @example Using a custom context
33
+ # class MyContext
34
+ # def current_user_name
35
+ # "Alice"
36
+ # end
37
+ # end
38
+ #
39
+ # context = MyContext.new
40
+ # Line::Message::Builder.with(context) do
41
+ # # `current_user_name` is resolved from `context` by Context
42
+ # text "Hello, #{current_user_name}!"
43
+ # end
7
44
  class Context
45
+ # @!attribute assigns
46
+ # A hash for storing arbitrary data that can be accessed within the
47
+ # builder DSL. This is useful for temporary variables or shared state
48
+ # during message construction.
49
+ # @return [Hash] The hash of assigned values.
50
+ # @example
51
+ # context.assigns[:user_id] = 123
52
+ # puts context.assigns[:user_id] # => 123
8
53
  attr_accessor :assigns
9
54
 
10
- def initialize(context)
55
+ # @!attribute [r] mode
56
+ # The mode in which the builder is operating. This affects how messages
57
+ # are formatted in the final output.
58
+ # @return [Symbol] Either `:api` for direct LINE Messaging API format
59
+ # or `:sdkv2` for LINE Bot SDK v2 compatible format.
60
+ attr_reader :mode
61
+
62
+ # Initializes a new Context object.
63
+ #
64
+ # @param context [Object, nil] An optional object whose methods will be made
65
+ # available within the DSL. If `nil`, only `assigns` and standard
66
+ # builder methods will be available.
67
+ # @param mode [Symbol] The mode of the context, which can be `:api` (default)
68
+ # for direct LINE Messaging API format or `:sdkv2` for LINE Bot SDK v2
69
+ # compatible format.
70
+ def initialize(context, mode: :api)
11
71
  @context = context
12
72
  @assigns = {}
73
+ @mode = mode
13
74
  end
14
75
 
76
+ # Part of Ruby's dynamic method dispatch. It's overridden here to declare
77
+ # that instances of `Context` can respond to methods that are either:
78
+ # 1. Keys in the `@assigns` hash.
79
+ # 2. Methods to which the wrapped `@context` object responds.
80
+ #
81
+ # This ensures that `respond_to?` behaves consistently with how
82
+ # `method_missing` resolves method calls.
83
+ #
84
+ # @param method_name [Symbol] The name of the method being queried.
85
+ # @param include_private [Boolean] Whether to include private methods in
86
+ # the check.
87
+ # @return [Boolean] `true` if the context can handle the method,
88
+ # `false` otherwise.
89
+ # @!visibility private
15
90
  def respond_to_missing?(method_name, include_private = false)
16
91
  @assigns.key?(method_name) ||
17
- @context.respond_to?(method_name, include_private) ||
92
+ @context.respond_to?(method_name, include_private) || # Check @context directly
18
93
  super
19
94
  end
20
95
 
96
+ # Handles calls to methods not explicitly defined on the `Context` class.
97
+ # The resolution order is:
98
+ # 1. If `method_name` is a key in the `@assigns` hash, its value is returned.
99
+ # 2. If the wrapped `@context` object responds to `method_name`, the call
100
+ # is delegated to `@context`.
101
+ # 3. Otherwise, `super` is called, allowing the standard Ruby method
102
+ # lookup to continue (which will likely result in a `NoMethodError`
103
+ # if the method is truly undefined).
104
+ #
105
+ # This is the core mechanism that allows DSL blocks to seamlessly access
106
+ # data from `assigns` or methods from the user-provided context.
107
+ #
108
+ # @param method_name [Symbol] The name of the invoked method.
109
+ # @param ... [Object] Arguments passed to the method.
110
+ # @return [Object, nil] The value from `@assigns`, the result of the
111
+ # delegated call to `@context`, or raises `NoMethodError` via `super`.
112
+ # @raise [NoMethodError] If the method is not found in `assigns` or
113
+ # on the wrapped context.
114
+ # @!visibility private
21
115
  def method_missing(method_name, ...)
22
116
  return @assigns[method_name] if @assigns.key?(method_name)
117
+ # Check @context directly
23
118
  return @context.public_send(method_name, ...) if @context.respond_to?(method_name)
24
119
 
25
120
  super
26
121
  end
122
+
123
+ # Checks if the current mode is set to SDK v2 compatibility.
124
+ #
125
+ # @return [Boolean] `true` if the mode is `:sdkv2`, `false` otherwise.
126
+ # @example
127
+ # if context.sdkv2?
128
+ # # Format message for LINE Bot SDK v2
129
+ # else
130
+ # # Format message for direct API use
131
+ # end
132
+ def sdkv2?
133
+ mode == :sdkv2
134
+ end
27
135
  end
28
136
  end
29
137
  end
@@ -4,16 +4,61 @@ module Line
4
4
  module Message
5
5
  module Builder
6
6
  module Flex
7
- # The action DSL for flex components.
7
+ # The `Actionable` module provides a DSL for defining an action that can be
8
+ # triggered when a user interacts with certain Flex Message components
9
+ # (e.g., a {Button} component, or an entire {Bubble} or {Box} component
10
+ # if it's made tappable).
11
+ #
12
+ # When a component includes this module, it gains methods like {#message}
13
+ # and {#postback} to associate a specific LINE action with itself. The
14
+ # chosen action is stored in the `action` attribute.
15
+ #
16
+ # @!attribute [r] action
17
+ # @return [Actions::Message, Actions::Postback, nil] The action object
18
+ # associated with this component. `nil` if no action is defined.
19
+ #
20
+ # @see Line::Message::Builder::Actions::Message
21
+ # @see Line::Message::Builder::Actions::Postback
22
+ # @see https://developers.line.biz/en/reference/messaging-api/#action-objects
8
23
  module Actionable
24
+ # @!visibility private
25
+ # Automatically adds an `attr_reader :action` to the class that includes
26
+ # this module.
27
+ # @param base [Class] The class including this module.
9
28
  def self.included(base)
10
29
  base.attr_reader :action
11
30
  end
12
31
 
32
+ # Defines a message action for the component.
33
+ # When the component is tapped, a message with the given `text` is sent
34
+ # from the user to the chat.
35
+ #
36
+ # @param text [String] The text of the message to send.
37
+ # @param options [Hash] Additional options for the message action,
38
+ # such as `:label`. See {Actions::Message#initialize}.
39
+ # @param block [Proc, nil] An optional block, though not typically used
40
+ # directly for message actions here.
41
+ # @return [Actions::Message] The created message action object.
42
+ #
43
+ # @example Setting a message action on a button
44
+ # button_component.message "Hello User!", label: "Send Greeting"
13
45
  def message(text, **options, &)
14
46
  @action = Actions::Message.new(text, context: context, **options, &)
15
47
  end
16
48
 
49
+ # Defines a postback action for the component.
50
+ # When the component is tapped, a postback event with the given `data`
51
+ # is sent to the bot's webhook.
52
+ #
53
+ # @param data [String] The data payload for the postback event.
54
+ # @param options [Hash] Additional options for the postback action,
55
+ # such as `:label` or `:display_text`. See {Actions::Postback#initialize}.
56
+ # @param block [Proc, nil] An optional block, though not typically used
57
+ # directly for postback actions here.
58
+ # @return [Actions::Postback] The created postback action object.
59
+ #
60
+ # @example Setting a postback action on a button
61
+ # button_component.postback "action=buy&item_id=123", label: "Buy Item"
17
62
  def postback(data, **options, &)
18
63
  @action = Actions::Postback.new(data, context: context, **options, &)
19
64
  end
@@ -4,66 +4,206 @@ module Line
4
4
  module Message
5
5
  module Builder
6
6
  module Flex
7
- # The box is a component for the Flex message.
7
+ # Represents a "box" component in a LINE Flex Message.
8
+ #
9
+ # A box is a fundamental layout container that arranges its child components
10
+ # (`contents`) in a specified direction (`layout`: horizontal, vertical, or
11
+ # baseline). It can hold various other components like text, images, buttons,
12
+ # or even nested boxes, allowing for complex layouts.
13
+ #
14
+ # Boxes have numerous properties to control their appearance and the
15
+ # arrangement of their children, such as padding, margin, spacing between
16
+ # items, justification, and alignment.
17
+ #
18
+ # A box can also have an `action` associated with it, making the entire
19
+ # box area tappable.
20
+ #
21
+ # @example Creating a horizontal box with two text components
22
+ # Line::Message::Builder.with do
23
+ # flex alt_text: "Box example" do
24
+ # bubble do
25
+ # body do
26
+ # layout :horizontal
27
+ # spacing :md
28
+ # text "Item 1"
29
+ # text "Item 2"
30
+ # end
31
+ # end
32
+ # end
33
+ # end
34
+ #
35
+ # @see https://developers.line.biz/en/reference/messaging-api/#box
36
+ # @see Actionable For making the box tappable.
37
+ # @see HasPartial For including reusable component groups.
38
+ # @see Position::Padding For padding properties.
39
+ # @see Position::Margin For margin properties.
40
+ # @see Position::Offset For offset properties.
41
+ # @see Size::Flex For flex sizing property.
8
42
  class Box < Line::Message::Builder::Base
9
- include HasPartial
10
- include Actionable
11
- include Position::Padding
12
- include Position::Margin
13
- include Position::Offset
14
- include Size::Flex
43
+ include HasPartial # Allows including predefined partial component sets.
44
+ include Actionable # Enables defining an action for the entire box.
45
+ include Position::Padding # Adds padding options like `padding_all`, `padding_top`, etc.
46
+ include Position::Margin # Adds `margin` option.
47
+ include Position::Offset # Adds offset options like `offset_top`, `offset_start`, etc.
48
+ include Size::Flex # Adds `flex` option for controlling size within a parent box.
15
49
 
50
+ # @!attribute [r] contents
51
+ # @return [Array<Base>] An array holding the child components (e.g.,
52
+ # {Text}, {Image}, nested {Box} instances) of this box.
16
53
  attr_reader :contents
17
54
 
55
+ # Specifies the arrangement of child components.
56
+ # @!method layout(value)
57
+ # @param value [Symbol] `:horizontal` (default), `:vertical`, or `:baseline`.
58
+ # @return [Symbol] The current layout.
18
59
  option :layout, default: :horizontal, validator: Validators::Enum.new(
19
60
  :horizontal, :vertical, :baseline
20
61
  )
62
+
63
+ # Horizontal alignment of content in a horizontal box; vertical alignment in a vertical box.
64
+ # @!method justify_content(value)
65
+ # @param value [Symbol, nil] E.g., `:flex_start`, `:center`, `:flex_end`,
66
+ # `:space_between`, `:space_around`, `:space_evenly`.
67
+ # @return [Symbol, nil] The current justify_content value.
21
68
  option :justify_content, default: nil, validator: Validators::Enum.new(
22
69
  :flex_start, :center, :flex_end, :space_between, :space_around, :space_evenly
23
70
  )
71
+
72
+ # Vertical alignment of content in a horizontal box; horizontal alignment in a vertical box.
73
+ # @!method align_items(value)
74
+ # @param value [Symbol, nil] E.g., `:flex_start`, `:center`, `:flex_end`.
75
+ # @return [Symbol, nil] The current align_items value.
24
76
  option :align_items, default: nil, validator: Validators::Enum.new(
25
77
  :flex_start, :center, :flex_end
26
78
  )
79
+
80
+ # Specifies the minimum spacing between components.
81
+ # Not applicable if `layout` is `:baseline` or if the box contains only one component.
82
+ # @!method spacing(value)
83
+ # @param value [String, Symbol, nil] E.g., `"md"`, `:lg`, `"10px"`.
84
+ # Keywords: `:none`, `:xs`, `:sm`, `:md`, `:lg`, `:xl`, `:xxl`.
85
+ # @return [String, Symbol, nil] The current spacing value.
27
86
  option :spacing, default: nil, validator: Validators::Size.new(:pixel, :keyword)
87
+
88
+ # Width of the box. Can be a percentage or pixel value.
89
+ # @!method width(value)
90
+ # @param value [String, nil] E.g., `"100px"`, `"50%"`.
91
+ # @return [String, nil] The current width.
28
92
  option :width, default: nil, validator: Validators::Size.new(:pixel, :percentage)
93
+
94
+ # Maximum width of the box.
95
+ # @!method max_width(value)
96
+ # @param value [String, nil] E.g., `"100px"`, `"50%"`.
97
+ # @return [String, nil] The current maximum width.
29
98
  option :max_width, default: nil, validator: Validators::Size.new(:pixel, :percentage)
99
+
100
+ # Height of the box.
101
+ # @!method height(value)
102
+ # @param value [String, nil] E.g., `"100px"`, `"50%"`.
103
+ # @return [String, nil] The current height.
30
104
  option :height, default: nil, validator: Validators::Size.new(:pixel, :percentage)
105
+
106
+ # Maximum height of the box.
107
+ # @!method max_height(value)
108
+ # @param value [String, nil] E.g., `"100px"`, `"50%"`.
109
+ # @return [String, nil] The current maximum height.
31
110
  option :max_height, default: nil, validator: Validators::Size.new(:pixel, :percentage)
32
111
 
112
+ # Initializes a new Flex Message Box component.
113
+ # The provided block is instance-eval'd, allowing DSL methods for adding
114
+ # child components (e.g., {#text}, {#button}, nested {#box}) to be called.
115
+ #
116
+ # @param context [Object, nil] An optional context for the builder.
117
+ # @param options [Hash] A hash of options to set instance variables.
118
+ # Corresponds to the `option` definitions in this class and included modules.
119
+ # @param block [Proc, nil] A block to define the contents of this box.
33
120
  def initialize(context: nil, **options, &)
34
- @contents = []
35
-
36
- super
121
+ @contents = [] # Holds child components
122
+ super # Calls Base#initialize, sets options, and evals block
37
123
  end
38
124
 
125
+ # Adds a nested Flex {Box} component to this box's contents.
126
+ #
127
+ # @param options [Hash] Options for the nested box. See {Box#initialize}.
128
+ # @param block [Proc] A block to define the contents of the nested box.
129
+ # @return [Flex::Box] The newly created nested Box object.
39
130
  def box(**options, &)
40
131
  @contents << Flex::Box.new(context: context, **options, &)
41
132
  end
42
133
 
43
- def text(text, **options, &)
134
+ # Adds a Flex {Text} component to this box's contents.
135
+ #
136
+ # @param text [String] The text content.
137
+ # @param options [Hash] Options for the text component. See {Text#initialize}.
138
+ # @param block [Proc, nil] An optional block for the text component. This can be used
139
+ # to define an action for the text or to add {Span} components within the text.
140
+ # @return [Flex::Text] The newly created Text object.
141
+ # @example Simple text component
142
+ # text "Hello, World!"
143
+ # @example Text with an action
144
+ # text "Click me" do
145
+ # message "Action", text: "You clicked me!"
146
+ # end
147
+ # @example Text with spans
148
+ # text "This has " do
149
+ # span "styled", color: "#FF0000"
150
+ # span " parts"
151
+ # end
152
+ def text(text = nil, **options, &)
44
153
  @contents << Flex::Text.new(text, context: context, **options, &)
45
154
  end
46
155
 
156
+ # Adds a Flex {Button} component to this box's contents.
157
+ #
158
+ # @param options [Hash] Options for the button component. See {Button#initialize}.
159
+ # @param block [Proc] A block to define the button's action and properties.
160
+ # @return [Flex::Button] The newly created Button object.
47
161
  def button(**options, &)
48
162
  @contents << Flex::Button.new(context: context, **options, &)
49
163
  end
50
164
 
165
+ # Adds a Flex {Image} component to this box's contents.
166
+ #
167
+ # @param url [String] The URL of the image.
168
+ # @param options [Hash] Options for the image component. See {Image#initialize}.
169
+ # @param block [Proc, nil] An optional block for the image component (e.g., for an action).
170
+ # @return [Flex::Image] The newly created Image object.
51
171
  def image(url, **options, &)
52
172
  @contents << Flex::Image.new(url, context: context, **options, &)
53
173
  end
54
174
 
55
- def to_h # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
175
+ # Adds a Flex {Separator} component to this box's contents.
176
+ #
177
+ # A separator is a simple component that draws a horizontal line,
178
+ # creating a visual division between other components in a container.
179
+ #
180
+ # @param options [Hash] Options for the separator component. See {Separator}.
181
+ # @param block [Proc, nil] An optional block for advanced separator configuration.
182
+ # @return [Flex::Separator] The newly created Separator object.
183
+ def separator(**options, &)
184
+ @contents << Flex::Separator.new(context: context, **options, &)
185
+ end
186
+
187
+ def to_h
56
188
  raise RequiredError, "layout is required" if layout.nil?
57
189
 
190
+ return to_sdkv2 if context.sdkv2?
191
+
192
+ to_api
193
+ end
194
+
195
+ private
196
+
197
+ def to_api # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
58
198
  {
59
199
  type: "box",
60
200
  layout: layout,
61
- # Position
201
+ # Position & Layout
62
202
  justifyContent: justify_content,
63
203
  alignItems: align_items,
64
204
  spacing: spacing,
65
205
  # Position::Padding
66
- paddingAll: padding,
206
+ paddingAll: padding || padding_all,
67
207
  paddingTop: padding_top,
68
208
  paddingBottom: padding_bottom,
69
209
  paddingStart: padding_start,
@@ -83,8 +223,44 @@ module Line
83
223
  maxHeight: max_height,
84
224
  # Size::Flex
85
225
  flex: flex,
226
+ # Contents & Action
227
+ contents: contents.map(&:to_h),
228
+ action: action&.to_h # From Actionable module
229
+ }.compact
230
+ end
231
+
232
+ def to_sdkv2 # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
233
+ {
234
+ type: "box",
235
+ layout: layout,
236
+ # Position & Layout
237
+ justify_content: justify_content,
238
+ align_items: align_items,
239
+ spacing: spacing,
240
+ # Position::Padding
241
+ padding_all: padding || padding_all,
242
+ padding_top: padding_top,
243
+ padding_bottom: padding_bottom,
244
+ padding_start: padding_start,
245
+ padding_end: padding_end,
246
+ # Position::Margin
247
+ margin: margin,
248
+ # Position::Offset
249
+ position: position,
250
+ offset_top: offset_top,
251
+ offset_bottom: offset_bottom,
252
+ offset_start: offset_start,
253
+ offset_end: offset_end,
254
+ # Size
255
+ width: width,
256
+ max_width: max_width,
257
+ height: height,
258
+ max_height: max_height,
259
+ # Size::Flex
260
+ flex: flex,
261
+ # Contents & Action
86
262
  contents: contents.map(&:to_h),
87
- action: action&.to_h
263
+ action: action&.to_h # From Actionable module
88
264
  }.compact
89
265
  end
90
266
  end