glimmer-dsl-web 0.6.0 → 0.6.2

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.
@@ -0,0 +1,351 @@
1
+ # Copyright (c) 2023-2024 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require 'glimmer-dsl-web'
23
+
24
+ unless Object.const_defined?(:Address)
25
+ Address = Struct.new(:full_name, :street, :street2, :city, :state, :zip_code, :billing_and_shipping, keyword_init: true) do
26
+ STATES = {
27
+ "AK"=>"Alaska", "AL"=>"Alabama", "AR"=>"Arkansas", "AS"=>"American Samoa", "AZ"=>"Arizona",
28
+ "CA"=>"California", "CO"=>"Colorado", "CT"=>"Connecticut", "DC"=>"District of Columbia", "DE"=>"Delaware",
29
+ "FL"=>"Florida", "GA"=>"Georgia", "GU"=>"Guam", "HI"=>"Hawaii", "IA"=>"Iowa", "ID"=>"Idaho", "IL"=>"Illinois",
30
+ "IN"=>"Indiana", "KS"=>"Kansas", "KY"=>"Kentucky", "LA"=>"Louisiana", "MA"=>"Massachusetts", "MD"=>"Maryland",
31
+ "ME"=>"Maine", "MI"=>"Michigan", "MN"=>"Minnesota", "MO"=>"Missouri", "MS"=>"Mississippi", "MT"=>"Montana",
32
+ "NC"=>"North Carolina", "ND"=>"North Dakota", "NE"=>"Nebraska", "NH"=>"New Hampshire", "NJ"=>"New Jersey",
33
+ "NM"=>"New Mexico", "NV"=>"Nevada", "NY"=>"New York", "OH"=>"Ohio", "OK"=>"Oklahoma", "OR"=>"Oregon",
34
+ "PA"=>"Pennsylvania", "PR"=>"Puerto Rico", "RI"=>"Rhode Island", "SC"=>"South Carolina", "SD"=>"South Dakota",
35
+ "TN"=>"Tennessee", "TX"=>"Texas", "UT"=>"Utah", "VA"=>"Virginia", "VI"=>"Virgin Islands", "VT"=>"Vermont",
36
+ "WA"=>"Washington", "WI"=>"Wisconsin", "WV"=>"West Virginia", "WY"=>"Wyoming"
37
+ }
38
+
39
+ def state_code
40
+ STATES.invert[state]
41
+ end
42
+
43
+ def state_code=(value)
44
+ self.state = STATES[value]
45
+ end
46
+
47
+ def summary
48
+ string_attributes = to_h.except(:billing_and_shipping)
49
+ summary = string_attributes.values.map(&:to_s).reject(&:empty?).join(', ')
50
+ summary += " (Billing & Shipping)" if billing_and_shipping
51
+ summary
52
+ end
53
+ end
54
+ end
55
+
56
+ unless Object.const_defined?(:AddressForm)
57
+ # AddressForm Glimmer Web Component (View component)
58
+ #
59
+ # Including Glimmer::Web::Component makes this class a View component and automatically
60
+ # generates a new Glimmer HTML DSL keyword that matches the lowercase underscored version
61
+ # of the name of the class. AddressForm generates address_form keyword, which can be used
62
+ # elsewhere in Glimmer HTML DSL code as done inside HelloComponentListeners below.
63
+ class AddressForm
64
+ include Glimmer::Web::Component
65
+
66
+ option :address
67
+
68
+ markup {
69
+ div {
70
+ div(style: {display: :grid, grid_auto_columns: '80px 260px'}) { |address_div|
71
+ label('Full Name: ', for: 'full-name-field')
72
+ input(id: 'full-name-field') {
73
+ value <=> [address, :full_name]
74
+ }
75
+
76
+ label('Street: ', for: 'street-field')
77
+ input(id: 'street-field') {
78
+ value <=> [address, :street]
79
+ }
80
+
81
+ label('Street 2: ', for: 'street2-field')
82
+ textarea(id: 'street2-field') {
83
+ value <=> [address, :street2]
84
+ }
85
+
86
+ label('City: ', for: 'city-field')
87
+ input(id: 'city-field') {
88
+ value <=> [address, :city]
89
+ }
90
+
91
+ label('State: ', for: 'state-field')
92
+ select(id: 'state-field') {
93
+ Address::STATES.each do |state_code, state|
94
+ option(value: state_code) { state }
95
+ end
96
+
97
+ value <=> [address, :state_code]
98
+ }
99
+
100
+ label('Zip Code: ', for: 'zip-code-field')
101
+ input(id: 'zip-code-field', type: 'number', min: '0', max: '99999') {
102
+ value <=> [address, :zip_code,
103
+ on_write: :to_s,
104
+ ]
105
+ }
106
+
107
+ style {
108
+ r("#{address_div.selector} *") {
109
+ margin '5px'
110
+ }
111
+ r("#{address_div.selector} input, #{address_div.selector} select") {
112
+ grid_column '2'
113
+ }
114
+ }
115
+ }
116
+
117
+ div(style: {margin: 5}) {
118
+ inner_text <= [address, :summary,
119
+ computed_by: address.members + ['state_code'],
120
+ ]
121
+ }
122
+ }
123
+ }
124
+ end
125
+ end
126
+
127
+ unless Object.const_defined?(:AccordionSection)
128
+ class AccordionSection
129
+ class Presenter
130
+ attr_accessor :collapsed, :instant_transition
131
+
132
+ def toggle_collapsed(instant: false)
133
+ self.instant_transition = instant
134
+ self.collapsed = !collapsed
135
+ end
136
+
137
+ def expand(instant: false)
138
+ self.instant_transition = instant
139
+ self.collapsed = false
140
+ end
141
+
142
+ def collapse(instant: false)
143
+ self.instant_transition = instant
144
+ self.collapsed = true
145
+ end
146
+ end
147
+
148
+ include Glimmer::Web::Component
149
+
150
+ events :expanded, :collapsed
151
+
152
+ option :title
153
+
154
+ attr_reader :presenter
155
+
156
+ before_render do
157
+ @presenter = Presenter.new
158
+ end
159
+
160
+ markup {
161
+ section {
162
+ # Unidirectionally data-bind the class inclusion of 'collapsed' to the @presenter.collapsed boolean attribute,
163
+ # meaning if @presenter.collapsed changes to true, the CSS class 'collapsed' is included on the element,
164
+ # and if it changes to false, the CSS class 'collapsed' is removed from the element.
165
+ class_name(:collapsed) <= [@presenter, :collapsed]
166
+ class_name(:instant_transition) <= [@presenter, :instant_transition]
167
+
168
+ header(title, class: 'accordion-section-title') {
169
+ onclick do |event|
170
+ @presenter.toggle_collapsed
171
+ if @presenter.collapsed
172
+ notify_listeners(:collapsed)
173
+ else
174
+ notify_listeners(:expanded)
175
+ end
176
+ end
177
+ }
178
+
179
+ div(slot: :section_content, class: 'accordion-section-content')
180
+ }
181
+ }
182
+
183
+ style {
184
+ r('.accordion-section-title') {
185
+ font_size 2.em
186
+ font_weight :bold
187
+ cursor :pointer
188
+ padding_left 20
189
+ position :relative
190
+ margin_block_start 0.33.em
191
+ margin_block_end 0.33.em
192
+ }
193
+
194
+ r('.accordion-section-title::before') {
195
+ content '"▼"'
196
+ position :absolute
197
+ font_size 0.5.em
198
+ top 10
199
+ left 0
200
+ }
201
+
202
+ r('.accordion-section-content') {
203
+ height 246
204
+ overflow :hidden
205
+ transition 'height 0.5s linear'
206
+ }
207
+
208
+ r("#{component_element_selector}.instant_transition .accordion-section-content") {
209
+ transition 'initial'
210
+ }
211
+
212
+ r("#{component_element_selector}.collapsed .accordion-section-title::before") {
213
+ content '"►"'
214
+ }
215
+
216
+ r("#{component_element_selector}.collapsed .accordion-section-content") {
217
+ height 0
218
+ }
219
+ }
220
+ end
221
+ end
222
+
223
+ unless Object.const_defined?(:Accordion)
224
+ class Accordion
225
+ include Glimmer::Web::Component
226
+
227
+ events :accordion_section_expanded, :accordion_section_collapsed
228
+
229
+ markup {
230
+ # given that no slots are specified, nesting content under the accordion component
231
+ # in consumer code adds content directly inside the markup root div.
232
+ div { |accordion|
233
+ # on render, all accordion sections would have been added by consumers already, so we can
234
+ # attach listeners to all of them by re-opening their content with `.content { ... }` block
235
+ on_render do
236
+ accordion_section_elements = accordion.children
237
+ accordion_sections = accordion_section_elements.map(&:component)
238
+ accordion_sections.each_with_index do |accordion_section, index|
239
+ accordion_section_number = index + 1
240
+
241
+ # ensure only the first section is expanded
242
+ accordion_section.presenter.collapse(instant: true) if accordion_section_number != 1
243
+
244
+ accordion_section.content {
245
+ on_expanded do
246
+ other_accordion_sections = accordion_sections.reject {|other_accordion_section| other_accordion_section == accordion_section }
247
+ other_accordion_sections.each { |other_accordion_section| other_accordion_section.presenter.collapse }
248
+ notify_listeners(:accordion_section_expanded, accordion_section_number)
249
+ end
250
+
251
+ on_collapsed do
252
+ notify_listeners(:accordion_section_collapsed, accordion_section_number)
253
+ end
254
+ }
255
+ end
256
+ end
257
+ }
258
+ }
259
+ end
260
+ end
261
+
262
+ unless Object.const_defined?(:HelloComponentListeners)
263
+ # HelloComponentListeners Glimmer Web Component (View component)
264
+ #
265
+ # This View component represents the main page being rendered,
266
+ # as done by its `render` class method below
267
+ #
268
+ # Note: check out HelloComponentListenersDefaultSlot for a simpler version that leverages the default slot feature
269
+ class HelloComponentListeners
270
+ class Presenter
271
+ attr_accessor :status_message
272
+
273
+ def initialize
274
+ @status_message = "Accordion section 1 is expanded!"
275
+ end
276
+ end
277
+
278
+ include Glimmer::Web::Component
279
+
280
+ before_render do
281
+ @presenter = Presenter.new
282
+ @shipping_address = Address.new(
283
+ full_name: 'Johnny Doe',
284
+ street: '3922 Park Ave',
285
+ street2: 'PO BOX 8382',
286
+ city: 'San Diego',
287
+ state: 'California',
288
+ zip_code: '91913',
289
+ )
290
+ @billing_address = Address.new(
291
+ full_name: 'John C Doe',
292
+ street: '123 Main St',
293
+ street2: 'Apartment 3C',
294
+ city: 'San Diego',
295
+ state: 'California',
296
+ zip_code: '91911',
297
+ )
298
+ @emergency_address = Address.new(
299
+ full_name: 'Mary Doe',
300
+ street: '2038 Ipswitch St',
301
+ street2: 'Suite 300',
302
+ city: 'San Diego',
303
+ state: 'California',
304
+ zip_code: '91912',
305
+ )
306
+ end
307
+
308
+ markup {
309
+ div {
310
+ h1(style: {font_style: :italic}) {
311
+ inner_html <= [@presenter, :status_message]
312
+ }
313
+
314
+ accordion { # any content nested under component directly is added under its markup root div element
315
+ accordion_section(title: 'Shipping Address') {
316
+ section_content { # contribute elements to section_content slot declared in AccordionSection component
317
+ address_form(address: @shipping_address)
318
+ }
319
+ }
320
+
321
+ accordion_section(title: 'Billing Address') {
322
+ section_content {
323
+ address_form(address: @billing_address)
324
+ }
325
+ }
326
+
327
+ accordion_section(title: 'Emergency Address') {
328
+ section_content {
329
+ address_form(address: @emergency_address)
330
+ }
331
+ }
332
+
333
+ # on_accordion_section_expanded listener matches event :accordion_section_expanded declared in Accordion component
334
+ on_accordion_section_expanded { |accordion_section_number|
335
+ @presenter.status_message = "Accordion section #{accordion_section_number} is expanded!"
336
+ }
337
+
338
+ on_accordion_section_collapsed { |accordion_section_number|
339
+ @presenter.status_message = "Accordion section #{accordion_section_number} is collapsed!"
340
+ }
341
+ }
342
+ }
343
+ }
344
+ end
345
+ end
346
+
347
+ Document.ready? do
348
+ # renders a top-level (root) HelloComponentListeners component
349
+ # Note: check out hello_component_listeners_default_slot.rb for a simpler version that leverages the default slot feature
350
+ HelloComponentListeners.render
351
+ end
@@ -0,0 +1,349 @@
1
+ # Copyright (c) 2023-2024 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require 'glimmer-dsl-web'
23
+
24
+ unless Object.const_defined?(:Address)
25
+ Address = Struct.new(:full_name, :street, :street2, :city, :state, :zip_code, :billing_and_shipping, keyword_init: true) do
26
+ STATES = {
27
+ "AK"=>"Alaska", "AL"=>"Alabama", "AR"=>"Arkansas", "AS"=>"American Samoa", "AZ"=>"Arizona",
28
+ "CA"=>"California", "CO"=>"Colorado", "CT"=>"Connecticut", "DC"=>"District of Columbia", "DE"=>"Delaware",
29
+ "FL"=>"Florida", "GA"=>"Georgia", "GU"=>"Guam", "HI"=>"Hawaii", "IA"=>"Iowa", "ID"=>"Idaho", "IL"=>"Illinois",
30
+ "IN"=>"Indiana", "KS"=>"Kansas", "KY"=>"Kentucky", "LA"=>"Louisiana", "MA"=>"Massachusetts", "MD"=>"Maryland",
31
+ "ME"=>"Maine", "MI"=>"Michigan", "MN"=>"Minnesota", "MO"=>"Missouri", "MS"=>"Mississippi", "MT"=>"Montana",
32
+ "NC"=>"North Carolina", "ND"=>"North Dakota", "NE"=>"Nebraska", "NH"=>"New Hampshire", "NJ"=>"New Jersey",
33
+ "NM"=>"New Mexico", "NV"=>"Nevada", "NY"=>"New York", "OH"=>"Ohio", "OK"=>"Oklahoma", "OR"=>"Oregon",
34
+ "PA"=>"Pennsylvania", "PR"=>"Puerto Rico", "RI"=>"Rhode Island", "SC"=>"South Carolina", "SD"=>"South Dakota",
35
+ "TN"=>"Tennessee", "TX"=>"Texas", "UT"=>"Utah", "VA"=>"Virginia", "VI"=>"Virgin Islands", "VT"=>"Vermont",
36
+ "WA"=>"Washington", "WI"=>"Wisconsin", "WV"=>"West Virginia", "WY"=>"Wyoming"
37
+ }
38
+
39
+ def state_code
40
+ STATES.invert[state]
41
+ end
42
+
43
+ def state_code=(value)
44
+ self.state = STATES[value]
45
+ end
46
+
47
+ def summary
48
+ string_attributes = to_h.except(:billing_and_shipping)
49
+ summary = string_attributes.values.map(&:to_s).reject(&:empty?).join(', ')
50
+ summary += " (Billing & Shipping)" if billing_and_shipping
51
+ summary
52
+ end
53
+ end
54
+ end
55
+
56
+ unless Object.const_defined?(:AddressForm)
57
+ # AddressForm Glimmer Web Component (View component)
58
+ #
59
+ # Including Glimmer::Web::Component makes this class a View component and automatically
60
+ # generates a new Glimmer HTML DSL keyword that matches the lowercase underscored version
61
+ # of the name of the class. AddressForm generates address_form keyword, which can be used
62
+ # elsewhere in Glimmer HTML DSL code as done inside HelloComponentListenersDefaultSlot below.
63
+ class AddressForm
64
+ include Glimmer::Web::Component
65
+
66
+ option :address
67
+
68
+ markup {
69
+ div {
70
+ div(style: {display: :grid, grid_auto_columns: '80px 260px'}) { |address_div|
71
+ label('Full Name: ', for: 'full-name-field')
72
+ input(id: 'full-name-field') {
73
+ value <=> [address, :full_name]
74
+ }
75
+
76
+ label('Street: ', for: 'street-field')
77
+ input(id: 'street-field') {
78
+ value <=> [address, :street]
79
+ }
80
+
81
+ label('Street 2: ', for: 'street2-field')
82
+ textarea(id: 'street2-field') {
83
+ value <=> [address, :street2]
84
+ }
85
+
86
+ label('City: ', for: 'city-field')
87
+ input(id: 'city-field') {
88
+ value <=> [address, :city]
89
+ }
90
+
91
+ label('State: ', for: 'state-field')
92
+ select(id: 'state-field') {
93
+ Address::STATES.each do |state_code, state|
94
+ option(value: state_code) { state }
95
+ end
96
+
97
+ value <=> [address, :state_code]
98
+ }
99
+
100
+ label('Zip Code: ', for: 'zip-code-field')
101
+ input(id: 'zip-code-field', type: 'number', min: '0', max: '99999') {
102
+ value <=> [address, :zip_code,
103
+ on_write: :to_s,
104
+ ]
105
+ }
106
+
107
+ style {
108
+ r("#{address_div.selector} *") {
109
+ margin '5px'
110
+ }
111
+ r("#{address_div.selector} input, #{address_div.selector} select") {
112
+ grid_column '2'
113
+ }
114
+ }
115
+ }
116
+
117
+ div(style: {margin: 5}) {
118
+ inner_text <= [address, :summary,
119
+ computed_by: address.members + ['state_code'],
120
+ ]
121
+ }
122
+ }
123
+ }
124
+ end
125
+ end
126
+
127
+ unless Object.const_defined?(:AccordionSection2)
128
+ # Note: this is similar to AccordionSection in HelloComponentSlots but specifies default_slot for simpler consumption
129
+ class AccordionSection2
130
+ class Presenter
131
+ attr_accessor :collapsed, :instant_transition
132
+
133
+ def toggle_collapsed(instant: false)
134
+ self.instant_transition = instant
135
+ self.collapsed = !collapsed
136
+ end
137
+
138
+ def expand(instant: false)
139
+ self.instant_transition = instant
140
+ self.collapsed = false
141
+ end
142
+
143
+ def collapse(instant: false)
144
+ self.instant_transition = instant
145
+ self.collapsed = true
146
+ end
147
+ end
148
+
149
+ include Glimmer::Web::Component
150
+
151
+ events :expanded, :collapsed
152
+
153
+ default_slot :section_content # automatically insert content in this element slot inside markup
154
+
155
+ option :title
156
+
157
+ attr_reader :presenter
158
+
159
+ before_render do
160
+ @presenter = Presenter.new
161
+ end
162
+
163
+ markup {
164
+ section { # represents the :markup_root_slot to allow inserting content here instead of in default_slot
165
+ # Unidirectionally data-bind the class inclusion of 'collapsed' to the @presenter.collapsed boolean attribute,
166
+ # meaning if @presenter.collapsed changes to true, the CSS class 'collapsed' is included on the element,
167
+ # and if it changes to false, the CSS class 'collapsed' is removed from the element.
168
+ class_name(:collapsed) <= [@presenter, :collapsed]
169
+ class_name(:instant_transition) <= [@presenter, :instant_transition]
170
+
171
+ header(title, class: 'accordion-section-title') {
172
+ onclick do |event|
173
+ @presenter.toggle_collapsed
174
+ if @presenter.collapsed
175
+ notify_listeners(:collapsed)
176
+ else
177
+ notify_listeners(:expanded)
178
+ end
179
+ end
180
+ }
181
+
182
+ div(slot: :section_content, class: 'accordion-section-content')
183
+ }
184
+ }
185
+
186
+ style {
187
+ r('.accordion-section-title') {
188
+ font_size 2.em
189
+ font_weight :bold
190
+ cursor :pointer
191
+ padding_left 20
192
+ position :relative
193
+ margin_block_start 0.33.em
194
+ margin_block_end 0.33.em
195
+ }
196
+
197
+ r('.accordion-section-title::before') {
198
+ content '"▼"'
199
+ position :absolute
200
+ font_size 0.5.em
201
+ top 10
202
+ left 0
203
+ }
204
+
205
+ r('.accordion-section-content') {
206
+ height 246
207
+ overflow :hidden
208
+ transition 'height 0.5s linear'
209
+ }
210
+
211
+ r("#{component_element_selector}.instant_transition .accordion-section-content") {
212
+ transition 'initial'
213
+ }
214
+
215
+ r("#{component_element_selector}.collapsed .accordion-section-title::before") {
216
+ content '"►"'
217
+ }
218
+
219
+ r("#{component_element_selector}.collapsed .accordion-section-content") {
220
+ height 0
221
+ }
222
+ }
223
+ end
224
+ end
225
+
226
+ unless Object.const_defined?(:Accordion)
227
+ class Accordion
228
+ include Glimmer::Web::Component
229
+
230
+ events :accordion_section_expanded, :accordion_section_collapsed
231
+
232
+ markup {
233
+ # given that no slots are specified, nesting content under the accordion component
234
+ # in consumer code adds content directly inside the markup root div.
235
+ div { |accordion| # represents the :markup_root_slot (top-level element)
236
+ # on render, all accordion sections would have been added by consumers already, so we can
237
+ # attach listeners to all of them by re-opening their content with `.content { ... }` block
238
+ on_render do
239
+ accordion_section_elements = accordion.children
240
+ accordion_sections = accordion_section_elements.map(&:component)
241
+ accordion_sections.each_with_index do |accordion_section, index|
242
+ accordion_section_number = index + 1
243
+
244
+ # ensure only the first section is expanded
245
+ accordion_section.presenter.collapse(instant: true) if accordion_section_number != 1
246
+
247
+ accordion_section.content { # re-open content and add component custom event listeners
248
+ on_expanded do
249
+ other_accordion_sections = accordion_sections.reject {|other_accordion_section| other_accordion_section == accordion_section }
250
+ other_accordion_sections.each { |other_accordion_section| other_accordion_section.presenter.collapse }
251
+ notify_listeners(:accordion_section_expanded, accordion_section_number)
252
+ end
253
+
254
+ on_collapsed do
255
+ notify_listeners(:accordion_section_collapsed, accordion_section_number)
256
+ end
257
+ }
258
+ end
259
+ end
260
+ }
261
+ }
262
+ end
263
+ end
264
+
265
+ unless Object.const_defined?(:HelloComponentListenersDefaultSlot)
266
+ # HelloComponentListenersDefaultSlot Glimmer Web Component (View component)
267
+ #
268
+ # This View component represents the main page being rendered,
269
+ # as done by its `render` class method below
270
+ #
271
+ # Note: this is a simpler version of HelloComponentSlots as it leverages the default slot feature
272
+ class HelloComponentListenersDefaultSlot
273
+ class Presenter
274
+ attr_accessor :status_message
275
+
276
+ def initialize
277
+ @status_message = "Accordion section 1 is expanded!"
278
+ end
279
+ end
280
+
281
+ include Glimmer::Web::Component
282
+
283
+ before_render do
284
+ @presenter = Presenter.new
285
+ @shipping_address = Address.new(
286
+ full_name: 'Johnny Doe',
287
+ street: '3922 Park Ave',
288
+ street2: 'PO BOX 8382',
289
+ city: 'San Diego',
290
+ state: 'California',
291
+ zip_code: '91913',
292
+ )
293
+ @billing_address = Address.new(
294
+ full_name: 'John C Doe',
295
+ street: '123 Main St',
296
+ street2: 'Apartment 3C',
297
+ city: 'San Diego',
298
+ state: 'California',
299
+ zip_code: '91911',
300
+ )
301
+ @emergency_address = Address.new(
302
+ full_name: 'Mary Doe',
303
+ street: '2038 Ipswitch St',
304
+ street2: 'Suite 300',
305
+ city: 'San Diego',
306
+ state: 'California',
307
+ zip_code: '91912',
308
+ )
309
+ end
310
+
311
+ markup {
312
+ div {
313
+ h1(style: {font_style: :italic}) {
314
+ inner_html <= [@presenter, :status_message]
315
+ }
316
+
317
+ accordion {
318
+ # any content nested under component directly is added to its markup_root_slot element if no default_slot is specified
319
+ accordion_section2(title: 'Shipping Address') {
320
+ address_form(address: @shipping_address) # automatically inserts content in default_slot :section_content
321
+ }
322
+
323
+ accordion_section2(title: 'Billing Address') {
324
+ address_form(address: @billing_address) # automatically inserts content in default_slot :section_content
325
+ }
326
+
327
+ accordion_section2(title: 'Emergency Address') {
328
+ address_form(address: @emergency_address) # automatically inserts content in default_slot :section_content
329
+ }
330
+
331
+ # on_accordion_section_expanded listener matches event :accordion_section_expanded declared in Accordion component
332
+ on_accordion_section_expanded { |accordion_section_number|
333
+ @presenter.status_message = "Accordion section #{accordion_section_number} is expanded!"
334
+ }
335
+
336
+ on_accordion_section_collapsed { |accordion_section_number|
337
+ @presenter.status_message = "Accordion section #{accordion_section_number} is collapsed!"
338
+ }
339
+ }
340
+ }
341
+ }
342
+ end
343
+ end
344
+
345
+ Document.ready? do
346
+ # renders a top-level (root) HelloComponentListenersDefaultSlot component
347
+ # Note: this is a simpler version of hello_component_slots.rb as it leverages the default slot feature
348
+ HelloComponentListenersDefaultSlot.render
349
+ end