glimmer-dsl-web 0.7.1 → 0.7.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7f37d0d4bbc970ce93dc9ded190d19402fae86911c26c6941a45f0da3fa6d2a9
4
- data.tar.gz: 2a4dd7e2b7a6e5035b513201e0c341c46e4c8604479d161d0bfbe6d2e9a6abbe
3
+ metadata.gz: 6cdfd633318946532548053f4ab89f31e423280ae73ba9505b598b0e60695680
4
+ data.tar.gz: e95ac60a8077a1157abaf6da13cf0f7adab1f3f72fb727bb9cf270ae373cab2e
5
5
  SHA512:
6
- metadata.gz: 067b48f703219cda26d1cbbce6e4b2179216f6b6b9a5354786adfb04e4341a34100b9254d9849f2ad9d742fe1180d083d8f8ebc4a3945fc182f603dbabc9a952
7
- data.tar.gz: c4b8a62396faa659bc001e854e1d8219f27771d85b561b93d64831c574b42129c0c5c87fec47fae1913b19f2ddeffb4288b54d8ec2e9e009b8377de05406638f
6
+ metadata.gz: c77c3cc7a2eabae100de6dd48e5797f8ce28774c4da17e6324bfe61fd8ece5d3e7b811538eabef1989f31de589e2ce64a1bbf122998c963158e844f33ca73987
7
+ data.tar.gz: a9f1a3ca8a39beae60602248b395621cadf2ae15362be300ff34f2d1d7d9c1464dcdab0614f31838388ae7a3e556abf0437f372642566827bd2ec72ba831934d
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Change Log
2
2
 
3
+ ## 0.7.3
4
+
5
+ - Hello, Component Attribute Data-Binding! Sample: `require 'glimmer-dsl-web/samples/hello/hello_component_attribute_data_binding.rb'`
6
+
7
+ ## 0.7.2
8
+
9
+ - Hello, Component Attribute Listeners! Sample: `require 'glimmer-dsl-web/samples/hello/hello_component_attribute_listeners.rb'`
10
+
3
11
  ## 0.7.1
4
12
 
5
13
  - Fix issue with not being able to pass arguments containing apostrophe (') to glimmer_component helper (provided by GlimmerHelper)
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # [<img src="https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png" height=85 />](https://github.com/AndyObtiva/glimmer) Glimmer DSL for Web 0.7.1 (Beta)
1
+ # [<img src="https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png" height=85 />](https://github.com/AndyObtiva/glimmer) Glimmer DSL for Web 0.7.3 (Beta)
2
2
  ## Ruby-in-the-Browser Web Frontend Framework
3
3
  ### The "Rails" of Frontend Frameworks!!! ([Fukuoka Award Winning](https://andymaleh.blogspot.com/2025/01/glimmer-dsl-for-web-wins-in-fukuoka.html))
4
4
  #### Finally, Ruby Developer Productivity, Happiness, and Fun in the Frontend!!!
@@ -1348,10 +1348,12 @@ Learn more about the differences between various [Glimmer](https://github.com/An
1348
1348
  - [Hello, Observer (Data-Binding)!](#hello-observer)
1349
1349
  - [Hello, Data-Binding!](#hello-data-binding)
1350
1350
  - [Hello, Content Data-Binding!](#hello-content-data-binding)
1351
- - [Hello, Component!](#hello-compoent)
1351
+ - [Hello, Component!](#hello-component)
1352
1352
  - [Hello, Component Slots!](#hello-component-slots)
1353
- - [Hello, Component Listeners!](#hello-compoent-listeners)
1354
- - [Hello, Component Listeners (Default Slot)!](#hello-compoent-listeners-default-slot)
1353
+ - [Hello, Component Listeners!](#hello-component-listeners)
1354
+ - [Hello, Component Listeners (Default Slot)!](#hello-component-listeners-default-slot)
1355
+ - [Hello, Component Attribute Listeners!](#hello-component-attribute-listeners)
1356
+ - [Hello, Component Attribute Data-Binding!](#hello-component-attribute-data-binding)
1355
1357
  - [Hello, glimmer_component Rails Helper!](#hello-glimmer_component-rails-helper)
1356
1358
  - [Hello, Paragraph!](#hello-paragraph)
1357
1359
  - [Hello, Style!](#hello-style)
@@ -1389,7 +1391,7 @@ Once done, read [Usage](#usage) instructions. Note that for serious app usage, i
1389
1391
 
1390
1392
  ### Rails 8
1391
1393
 
1392
- Rails 8 instructions are not ready yet though they would rely on the `opal --watch` command in the Development environment to generate JavaScript files in the directory that propshaft is setup with, and Rails 8 would rely on the `opal` command to generate JavaScript files for the Production environment.
1394
+ Rails 8 instructions are not ready yet though they would rely on the `opal --watch` command in the Development environment to generate JavaScript files in the directory that propshaft is setup with, and Rails 8 would rely on the `opal` command to generate JavaScript files for the Production environment.
1393
1395
 
1394
1396
  Instructions will be added in the future.
1395
1397
 
@@ -1416,7 +1418,7 @@ rails new glimmer_app_server
1416
1418
  Add the following to `Gemfile`:
1417
1419
 
1418
1420
  ```
1419
- gem 'glimmer-dsl-web', '~> 0.7.1'
1421
+ gem 'glimmer-dsl-web', '~> 0.7.3'
1420
1422
  ```
1421
1423
 
1422
1424
  Run:
@@ -2994,367 +2996,357 @@ Glimmer HTML DSL Ruby code in the frontend:
2994
2996
  ```ruby
2995
2997
  require 'glimmer-dsl-web'
2996
2998
 
2997
- unless Object.const_defined?(:Address)
2998
- Address = Struct.new(:full_name, :street, :street2, :city, :state, :zip_code, keyword_init: true) do
2999
- STATES = {
3000
- "AK"=>"Alaska",
3001
- "AL"=>"Alabama",
3002
- "AR"=>"Arkansas",
3003
- "AS"=>"American Samoa",
3004
- "AZ"=>"Arizona",
3005
- "CA"=>"California",
3006
- "CO"=>"Colorado",
3007
- "CT"=>"Connecticut",
3008
- "DC"=>"District of Columbia",
3009
- "DE"=>"Delaware",
3010
- "FL"=>"Florida",
3011
- "GA"=>"Georgia",
3012
- "GU"=>"Guam",
3013
- "HI"=>"Hawaii",
3014
- "IA"=>"Iowa",
3015
- "ID"=>"Idaho",
3016
- "IL"=>"Illinois",
3017
- "IN"=>"Indiana",
3018
- "KS"=>"Kansas",
3019
- "KY"=>"Kentucky",
3020
- "LA"=>"Louisiana",
3021
- "MA"=>"Massachusetts",
3022
- "MD"=>"Maryland",
3023
- "ME"=>"Maine",
3024
- "MI"=>"Michigan",
3025
- "MN"=>"Minnesota",
3026
- "MO"=>"Missouri",
3027
- "MS"=>"Mississippi",
3028
- "MT"=>"Montana",
3029
- "NC"=>"North Carolina",
3030
- "ND"=>"North Dakota",
3031
- "NE"=>"Nebraska",
3032
- "NH"=>"New Hampshire",
3033
- "NJ"=>"New Jersey",
3034
- "NM"=>"New Mexico",
3035
- "NV"=>"Nevada",
3036
- "NY"=>"New York",
3037
- "OH"=>"Ohio",
3038
- "OK"=>"Oklahoma",
3039
- "OR"=>"Oregon",
3040
- "PA"=>"Pennsylvania",
3041
- "PR"=>"Puerto Rico",
3042
- "RI"=>"Rhode Island",
3043
- "SC"=>"South Carolina",
3044
- "SD"=>"South Dakota",
3045
- "TN"=>"Tennessee",
3046
- "TX"=>"Texas",
3047
- "UT"=>"Utah",
3048
- "VA"=>"Virginia",
3049
- "VI"=>"Virgin Islands",
3050
- "VT"=>"Vermont",
3051
- "WA"=>"Washington",
3052
- "WI"=>"Wisconsin",
3053
- "WV"=>"West Virginia",
3054
- "WY"=>"Wyoming"
3055
- }
3056
-
3057
- def state_code
3058
- STATES.invert[state]
3059
- end
3060
-
3061
- def state_code=(value)
3062
- self.state = STATES[value]
3063
- end
2999
+ Address = Struct.new(:full_name, :street, :street2, :city, :state, :zip_code, keyword_init: true) do
3000
+ STATES = {
3001
+ "AK"=>"Alaska",
3002
+ "AL"=>"Alabama",
3003
+ "AR"=>"Arkansas",
3004
+ "AS"=>"American Samoa",
3005
+ "AZ"=>"Arizona",
3006
+ "CA"=>"California",
3007
+ "CO"=>"Colorado",
3008
+ "CT"=>"Connecticut",
3009
+ "DC"=>"District of Columbia",
3010
+ "DE"=>"Delaware",
3011
+ "FL"=>"Florida",
3012
+ "GA"=>"Georgia",
3013
+ "GU"=>"Guam",
3014
+ "HI"=>"Hawaii",
3015
+ "IA"=>"Iowa",
3016
+ "ID"=>"Idaho",
3017
+ "IL"=>"Illinois",
3018
+ "IN"=>"Indiana",
3019
+ "KS"=>"Kansas",
3020
+ "KY"=>"Kentucky",
3021
+ "LA"=>"Louisiana",
3022
+ "MA"=>"Massachusetts",
3023
+ "MD"=>"Maryland",
3024
+ "ME"=>"Maine",
3025
+ "MI"=>"Michigan",
3026
+ "MN"=>"Minnesota",
3027
+ "MO"=>"Missouri",
3028
+ "MS"=>"Mississippi",
3029
+ "MT"=>"Montana",
3030
+ "NC"=>"North Carolina",
3031
+ "ND"=>"North Dakota",
3032
+ "NE"=>"Nebraska",
3033
+ "NH"=>"New Hampshire",
3034
+ "NJ"=>"New Jersey",
3035
+ "NM"=>"New Mexico",
3036
+ "NV"=>"Nevada",
3037
+ "NY"=>"New York",
3038
+ "OH"=>"Ohio",
3039
+ "OK"=>"Oklahoma",
3040
+ "OR"=>"Oregon",
3041
+ "PA"=>"Pennsylvania",
3042
+ "PR"=>"Puerto Rico",
3043
+ "RI"=>"Rhode Island",
3044
+ "SC"=>"South Carolina",
3045
+ "SD"=>"South Dakota",
3046
+ "TN"=>"Tennessee",
3047
+ "TX"=>"Texas",
3048
+ "UT"=>"Utah",
3049
+ "VA"=>"Virginia",
3050
+ "VI"=>"Virgin Islands",
3051
+ "VT"=>"Vermont",
3052
+ "WA"=>"Washington",
3053
+ "WI"=>"Wisconsin",
3054
+ "WV"=>"West Virginia",
3055
+ "WY"=>"Wyoming"
3056
+ }
3064
3057
 
3065
- def summary
3066
- to_h.values.map(&:to_s).reject(&:empty?).join(', ')
3067
- end
3058
+ def state_code
3059
+ STATES.invert[state]
3060
+ end
3061
+
3062
+ def state_code=(value)
3063
+ self.state = STATES[value]
3064
+ end
3065
+
3066
+ def summary
3067
+ to_h.values.map(&:to_s).reject(&:empty?).join(', ')
3068
3068
  end
3069
3069
  end
3070
3070
 
3071
- unless Object.const_defined?(:AddressForm)
3072
- # AddressForm Glimmer Web Component (View component)
3073
- #
3074
- # Including Glimmer::Web::Component makes this class a View component and automatically
3075
- # generates a new Glimmer HTML DSL keyword that matches the lowercase underscored version
3076
- # of the name of the class. AddressForm generates address_form keyword, which can be used
3077
- # elsewhere in Glimmer HTML DSL code as done inside HelloComponentListeners below.
3078
- class AddressForm
3079
- include Glimmer::Web::Component
3080
-
3081
- option :address
3082
-
3083
- markup {
3084
- div {
3085
- div(style: {display: :grid, grid_auto_columns: '80px 260px'}) { |address_div|
3086
- label('Full Name: ', for: 'full-name-field')
3087
- input(id: 'full-name-field') {
3088
- value <=> [address, :full_name]
3089
- }
3090
-
3091
- label('Street: ', for: 'street-field')
3092
- input(id: 'street-field') {
3093
- value <=> [address, :street]
3094
- }
3095
-
3096
- label('Street 2: ', for: 'street2-field')
3097
- textarea(id: 'street2-field') {
3098
- value <=> [address, :street2]
3099
- }
3100
-
3101
- label('City: ', for: 'city-field')
3102
- input(id: 'city-field') {
3103
- value <=> [address, :city]
3104
- }
3105
-
3106
- label('State: ', for: 'state-field')
3107
- select(id: 'state-field') {
3108
- Address::STATES.each do |state_code, state|
3109
- option(value: state_code) { state }
3110
- end
3071
+ # AddressForm Glimmer Web Component (View component)
3072
+ #
3073
+ # Including Glimmer::Web::Component makes this class a View component and automatically
3074
+ # generates a new Glimmer HTML DSL keyword that matches the lowercase underscored version
3075
+ # of the name of the class. AddressForm generates address_form keyword, which can be used
3076
+ # elsewhere in Glimmer HTML DSL code as done inside HelloComponentListeners below.
3077
+ class AddressForm
3078
+ include Glimmer::Web::Component
3111
3079
 
3112
- value <=> [address, :state_code]
3113
- }
3114
-
3115
- label('Zip Code: ', for: 'zip-code-field')
3116
- input(id: 'zip-code-field', type: 'number', min: '0', max: '99999') {
3117
- value <=> [address, :zip_code,
3118
- on_write: :to_s,
3119
- ]
3120
- }
3121
-
3122
- style {
3123
- r("#{address_div.selector} *") {
3124
- margin '5px'
3125
- }
3126
- r("#{address_div.selector} input, #{address_div.selector} select") {
3127
- grid_column '2'
3128
- }
3129
- }
3080
+ option :address
3081
+
3082
+ markup {
3083
+ div {
3084
+ div(style: {display: :grid, grid_auto_columns: '80px 260px'}) { |address_div|
3085
+ label('Full Name: ', for: 'full-name-field')
3086
+ input(id: 'full-name-field') {
3087
+ value <=> [address, :full_name]
3130
3088
  }
3131
3089
 
3132
- div(style: {margin: 5}) {
3133
- inner_text <= [address, :summary,
3134
- computed_by: address.members + ['state_code'],
3135
- ]
3090
+ label('Street: ', for: 'street-field')
3091
+ input(id: 'street-field') {
3092
+ value <=> [address, :street]
3136
3093
  }
3137
- }
3138
- }
3139
- end
3140
- end
3141
-
3142
- unless Object.const_defined?(:AccordionSection)
3143
- class AccordionSection
3144
- class Presenter
3145
- attr_accessor :collapsed, :instant_transition
3146
-
3147
- def toggle_collapsed(instant: false)
3148
- self.instant_transition = instant
3149
- self.collapsed = !collapsed
3150
- end
3151
-
3152
- def expand(instant: false)
3153
- self.instant_transition = instant
3154
- self.collapsed = false
3155
- end
3156
-
3157
- def collapse(instant: false)
3158
- self.instant_transition = instant
3159
- self.collapsed = true
3160
- end
3161
- end
3162
-
3163
- include Glimmer::Web::Component
3164
-
3165
- events :expanded, :collapsed
3166
-
3167
- option :title
3168
-
3169
- attr_reader :presenter
3170
-
3171
- before_render do
3172
- @presenter = Presenter.new
3173
- end
3174
-
3175
- markup {
3176
- section {
3177
- # Unidirectionally data-bind the class inclusion of 'collapsed' to the @presenter.collapsed boolean attribute,
3178
- # meaning if @presenter.collapsed changes to true, the CSS class 'collapsed' is included on the element,
3179
- # and if it changes to false, the CSS class 'collapsed' is removed from the element.
3180
- class_name(:collapsed) <= [@presenter, :collapsed]
3181
- class_name(:instant_transition) <= [@presenter, :instant_transition]
3182
-
3183
- header(title, class: 'accordion-section-title') {
3184
- onclick do |event|
3185
- @presenter.toggle_collapsed
3186
- if @presenter.collapsed
3187
- notify_listeners(:collapsed)
3188
- else
3189
- notify_listeners(:expanded)
3190
- end
3094
+
3095
+ label('Street 2: ', for: 'street2-field')
3096
+ textarea(id: 'street2-field') {
3097
+ value <=> [address, :street2]
3098
+ }
3099
+
3100
+ label('City: ', for: 'city-field')
3101
+ input(id: 'city-field') {
3102
+ value <=> [address, :city]
3103
+ }
3104
+
3105
+ label('State: ', for: 'state-field')
3106
+ select(id: 'state-field') {
3107
+ Address::STATES.each do |state_code, state|
3108
+ option(value: state_code) { state }
3191
3109
  end
3110
+
3111
+ value <=> [address, :state_code]
3192
3112
  }
3193
3113
 
3194
- div(slot: :section_content, class: 'accordion-section-content')
3195
- }
3196
- }
3197
-
3198
- style {
3199
- r('.accordion-section-title') {
3200
- font_size 2.em
3201
- font_weight :bold
3202
- cursor :pointer
3203
- padding_left 20
3204
- position :relative
3205
- margin_block_start 0.33.em
3206
- margin_block_end 0.33.em
3207
- }
3208
-
3209
- r('.accordion-section-title::before') {
3210
- content '"▼"'
3211
- position :absolute
3212
- font_size 0.5.em
3213
- top 10
3214
- left 0
3215
- }
3216
-
3217
- r('.accordion-section-content') {
3218
- height 246
3219
- overflow :hidden
3220
- transition 'height 0.5s linear'
3221
- }
3222
-
3223
- r("#{component_element_selector}.instant_transition .accordion-section-content") {
3224
- transition 'initial'
3225
- }
3226
-
3227
- r("#{component_element_selector}.collapsed .accordion-section-title::before") {
3228
- content '"►"'
3114
+ label('Zip Code: ', for: 'zip-code-field')
3115
+ input(id: 'zip-code-field', type: 'number', min: '0', max: '99999') {
3116
+ value <=> [address, :zip_code,
3117
+ on_write: :to_s,
3118
+ ]
3119
+ }
3120
+
3121
+ style {
3122
+ r("#{address_div.selector} *") {
3123
+ margin '5px'
3124
+ }
3125
+ r("#{address_div.selector} input, #{address_div.selector} select") {
3126
+ grid_column '2'
3127
+ }
3128
+ }
3229
3129
  }
3230
3130
 
3231
- r("#{component_element_selector}.collapsed .accordion-section-content") {
3232
- height 0
3131
+ div(style: {margin: 5}) {
3132
+ inner_text <= [address, :summary,
3133
+ computed_by: address.members + ['state_code'],
3134
+ ]
3233
3135
  }
3234
3136
  }
3235
- end
3137
+ }
3236
3138
  end
3237
3139
 
3238
- unless Object.const_defined?(:Accordion)
3239
- class Accordion
3240
- include Glimmer::Web::Component
3140
+ class AccordionSection
3141
+ class Presenter
3142
+ attr_accessor :collapsed, :instant_transition
3241
3143
 
3242
- events :accordion_section_expanded, :accordion_section_collapsed
3144
+ def toggle_collapsed(instant: false)
3145
+ self.instant_transition = instant
3146
+ self.collapsed = !collapsed
3147
+ end
3243
3148
 
3244
- markup {
3245
- # given that no slots are specified, nesting content under the accordion component
3246
- # in consumer code adds content directly inside the markup root div.
3247
- div { |accordion|
3248
- # on render, all accordion sections would have been added by consumers already, so we can
3249
- # attach listeners to all of them by re-opening their content with `.content { ... }` block
3250
- on_render do
3251
- accordion_section_elements = accordion.children
3252
- accordion_sections = accordion_section_elements.map(&:component)
3253
- accordion_sections.each_with_index do |accordion_section, index|
3254
- accordion_section_number = index + 1
3149
+ def expand(instant: false)
3150
+ self.instant_transition = instant
3151
+ self.collapsed = false
3152
+ end
3153
+
3154
+ def collapse(instant: false)
3155
+ self.instant_transition = instant
3156
+ self.collapsed = true
3157
+ end
3158
+ end
3159
+
3160
+ include Glimmer::Web::Component
3161
+
3162
+ events :expanded, :collapsed
3255
3163
 
3256
- # ensure only the first section is expanded
3257
- accordion_section.presenter.collapse(instant: true) if accordion_section_number != 1
3164
+ option :title
3258
3165
 
3259
- accordion_section.content {
3260
- on_expanded do
3261
- other_accordion_sections = accordion_sections.reject {|other_accordion_section| other_accordion_section == accordion_section }
3262
- other_accordion_sections.each { |other_accordion_section| other_accordion_section.presenter.collapse }
3263
- notify_listeners(:accordion_section_expanded, accordion_section_number)
3264
- end
3166
+ attr_reader :presenter
3265
3167
 
3266
- on_collapsed do
3267
- notify_listeners(:accordion_section_collapsed, accordion_section_number)
3268
- end
3269
- }
3168
+ before_render do
3169
+ @presenter = Presenter.new
3170
+ end
3171
+
3172
+ markup {
3173
+ section {
3174
+ # Unidirectionally data-bind the class inclusion of 'collapsed' to the @presenter.collapsed boolean attribute,
3175
+ # meaning if @presenter.collapsed changes to true, the CSS class 'collapsed' is included on the element,
3176
+ # and if it changes to false, the CSS class 'collapsed' is removed from the element.
3177
+ class_name(:collapsed) <= [@presenter, :collapsed]
3178
+ class_name(:instant_transition) <= [@presenter, :instant_transition]
3179
+
3180
+ header(title, class: 'accordion-section-title') {
3181
+ onclick do |event|
3182
+ @presenter.toggle_collapsed
3183
+ if @presenter.collapsed
3184
+ notify_listeners(:collapsed)
3185
+ else
3186
+ notify_listeners(:expanded)
3270
3187
  end
3271
3188
  end
3272
3189
  }
3190
+
3191
+ div(slot: :section_content, class: 'accordion-section-content')
3273
3192
  }
3274
- end
3193
+ }
3194
+
3195
+ style {
3196
+ r('.accordion-section-title') {
3197
+ font_size 2.em
3198
+ font_weight :bold
3199
+ cursor :pointer
3200
+ padding_left 20
3201
+ position :relative
3202
+ margin_block_start 0.33.em
3203
+ margin_block_end 0.33.em
3204
+ }
3205
+
3206
+ r('.accordion-section-title::before') {
3207
+ content '"▼"'
3208
+ position :absolute
3209
+ font_size 0.5.em
3210
+ top 10
3211
+ left 0
3212
+ }
3213
+
3214
+ r('.accordion-section-content') {
3215
+ height 246
3216
+ overflow :hidden
3217
+ transition 'height 0.5s linear'
3218
+ }
3219
+
3220
+ r("#{component_element_selector}.instant_transition .accordion-section-content") {
3221
+ transition 'initial'
3222
+ }
3223
+
3224
+ r("#{component_element_selector}.collapsed .accordion-section-title::before") {
3225
+ content '"►"'
3226
+ }
3227
+
3228
+ r("#{component_element_selector}.collapsed .accordion-section-content") {
3229
+ height 0
3230
+ }
3231
+ }
3275
3232
  end
3276
3233
 
3277
- unless Object.const_defined?(:HelloComponentListeners)
3278
- # HelloComponentListeners Glimmer Web Component (View component)
3279
- #
3280
- # This View component represents the main page being rendered,
3281
- # as done by its `render` class method below
3282
- class HelloComponentListeners
3283
- class Presenter
3284
- attr_accessor :status_message
3285
-
3286
- def initialize
3287
- @status_message = "Accordion section 1 is expanded!"
3234
+ class Accordion
3235
+ include Glimmer::Web::Component
3236
+
3237
+ events :accordion_section_expanded, :accordion_section_collapsed
3238
+
3239
+ markup {
3240
+ # given that no slots are specified, nesting content under the accordion component
3241
+ # in consumer code adds content directly inside the markup root div.
3242
+ div { |accordion|
3243
+ # on render, all accordion sections would have been added by consumers already, so we can
3244
+ # attach listeners to all of them by re-opening their content with `.content { ... }` block
3245
+ on_render do
3246
+ accordion_section_elements = accordion.children
3247
+ accordion_sections = accordion_section_elements.map(&:component)
3248
+ accordion_sections.each_with_index do |accordion_section, index|
3249
+ accordion_section_number = index + 1
3250
+
3251
+ # ensure only the first section is expanded
3252
+ accordion_section.presenter.collapse(instant: true) if accordion_section_number != 1
3253
+
3254
+ accordion_section.content {
3255
+ on_expanded do
3256
+ other_accordion_sections = accordion_sections.reject {|other_accordion_section| other_accordion_section == accordion_section }
3257
+ other_accordion_sections.each { |other_accordion_section| other_accordion_section.presenter.collapse }
3258
+ notify_listeners(:accordion_section_expanded, accordion_section_number)
3259
+ end
3260
+
3261
+ on_collapsed do
3262
+ notify_listeners(:accordion_section_collapsed, accordion_section_number)
3263
+ end
3264
+ }
3265
+ end
3288
3266
  end
3289
- end
3290
-
3291
- include Glimmer::Web::Component
3267
+ }
3268
+ }
3269
+ end
3270
+
3271
+ # HelloComponentListeners Glimmer Web Component (View component)
3272
+ #
3273
+ # This View component represents the main page being rendered,
3274
+ # as done by its `render` class method below
3275
+ class HelloComponentListeners
3276
+ class Presenter
3277
+ attr_accessor :status_message
3292
3278
 
3293
- before_render do
3294
- @presenter = Presenter.new
3295
- @shipping_address = Address.new(
3296
- full_name: 'Johnny Doe',
3297
- street: '3922 Park Ave',
3298
- street2: 'PO BOX 8382',
3299
- city: 'San Diego',
3300
- state: 'California',
3301
- zip_code: '91913',
3302
- )
3303
- @billing_address = Address.new(
3304
- full_name: 'John C Doe',
3305
- street: '123 Main St',
3306
- street2: 'Apartment 3C',
3307
- city: 'San Diego',
3308
- state: 'California',
3309
- zip_code: '91911',
3310
- )
3311
- @emergency_address = Address.new(
3312
- full_name: 'Mary Doe',
3313
- street: '2038 Ipswitch St',
3314
- street2: 'Suite 300',
3315
- city: 'San Diego',
3316
- state: 'California',
3317
- zip_code: '91912',
3318
- )
3279
+ def initialize
3280
+ @status_message = "Accordion section 1 is expanded!"
3319
3281
  end
3320
-
3321
- markup {
3322
- div {
3323
- h1(style: {font_style: :italic}) {
3324
- inner_html <= [@presenter, :status_message]
3325
- }
3326
-
3327
- accordion { # any content nested under component directly is added under its markup root div element
3328
- accordion_section(title: 'Shipping Address') {
3329
- section_content { # contribute elements to section_content slot declared in AccordionSection component
3330
- address_form(address: @shipping_address)
3331
- }
3332
- }
3333
-
3334
- accordion_section(title: 'Billing Address') {
3335
- section_content {
3336
- address_form(address: @billing_address)
3337
- }
3338
- }
3339
-
3340
- accordion_section(title: 'Emergency Address') {
3341
- section_content {
3342
- address_form(address: @emergency_address)
3343
- }
3282
+ end
3283
+
3284
+ include Glimmer::Web::Component
3285
+
3286
+ before_render do
3287
+ @presenter = Presenter.new
3288
+ @shipping_address = Address.new(
3289
+ full_name: 'Johnny Doe',
3290
+ street: '3922 Park Ave',
3291
+ street2: 'PO BOX 8382',
3292
+ city: 'San Diego',
3293
+ state: 'California',
3294
+ zip_code: '91913',
3295
+ )
3296
+ @billing_address = Address.new(
3297
+ full_name: 'John C Doe',
3298
+ street: '123 Main St',
3299
+ street2: 'Apartment 3C',
3300
+ city: 'San Diego',
3301
+ state: 'California',
3302
+ zip_code: '91911',
3303
+ )
3304
+ @emergency_address = Address.new(
3305
+ full_name: 'Mary Doe',
3306
+ street: '2038 Ipswitch St',
3307
+ street2: 'Suite 300',
3308
+ city: 'San Diego',
3309
+ state: 'California',
3310
+ zip_code: '91912',
3311
+ )
3312
+ end
3313
+
3314
+ markup {
3315
+ div {
3316
+ h1(style: {font_style: :italic}) {
3317
+ inner_html <= [@presenter, :status_message]
3318
+ }
3319
+
3320
+ accordion { # any content nested under component directly is added under its markup root div element
3321
+ accordion_section(title: 'Shipping Address') {
3322
+ section_content { # contribute elements to section_content slot declared in AccordionSection component
3323
+ address_form(address: @shipping_address)
3344
3324
  }
3345
-
3346
- # on_accordion_section_expanded listener matches event :accordion_section_expanded declared in Accordion component
3347
- on_accordion_section_expanded { |accordion_section_number|
3348
- @presenter.status_message = "Accordion section #{accordion_section_number} is expanded!"
3325
+ }
3326
+
3327
+ accordion_section(title: 'Billing Address') {
3328
+ section_content {
3329
+ address_form(address: @billing_address)
3349
3330
  }
3350
-
3351
- on_accordion_section_collapsed { |accordion_section_number|
3352
- @presenter.status_message = "Accordion section #{accordion_section_number} is collapsed!"
3331
+ }
3332
+
3333
+ accordion_section(title: 'Emergency Address') {
3334
+ section_content {
3335
+ address_form(address: @emergency_address)
3353
3336
  }
3354
3337
  }
3338
+
3339
+ # on_accordion_section_expanded listener matches event :accordion_section_expanded declared in Accordion component
3340
+ on_accordion_section_expanded { |accordion_section_number|
3341
+ @presenter.status_message = "Accordion section #{accordion_section_number} is expanded!"
3342
+ }
3343
+
3344
+ on_accordion_section_collapsed { |accordion_section_number|
3345
+ @presenter.status_message = "Accordion section #{accordion_section_number} is collapsed!"
3346
+ }
3355
3347
  }
3356
3348
  }
3357
- end
3349
+ }
3358
3350
  end
3359
3351
 
3360
3352
  Document.ready? do
@@ -3758,6 +3750,188 @@ Screenshot:
3758
3750
 
3759
3751
  ![Hello, Component Listeners!](/images/glimmer-dsl-web-samples-hello-hello-component-listeners.gif)
3760
3752
 
3753
+ #### Hello, Component Attribute Listeners!
3754
+
3755
+ [lib/glimmer-dsl-web/samples/hello/hello_component_attribute_listeners.rb](/lib/glimmer-dsl-web/samples/hello/hello_component_attribute_listeners.rb)
3756
+
3757
+ Glimmer HTML DSL Ruby code in the frontend:
3758
+
3759
+ ```ruby
3760
+ require 'glimmer-dsl-web'
3761
+
3762
+ class AddressTypeSelector
3763
+ include Glimmer::Web::Component
3764
+
3765
+ attribute :address_types, default: []
3766
+ attribute :selected_address_type
3767
+
3768
+ before_render do
3769
+ self.selected_address_type ||= address_types.first
3770
+ end
3771
+
3772
+ markup {
3773
+ select { |select_element|
3774
+ address_types.each do |address_type|
3775
+ option(value: address_type) { address_type }
3776
+ end
3777
+
3778
+ # Bidirectionally data-bind select value to selected_address_type attribute on self (component)
3779
+ value <=> [self, :selected_address_type]
3780
+ }
3781
+ }
3782
+
3783
+ style {
3784
+ r(component_element_selector) {
3785
+ font_size 2.em
3786
+ margin_left 5.px
3787
+ }
3788
+ }
3789
+ end
3790
+
3791
+ class AddressTypeSelectorPage
3792
+ include Glimmer::Web::Component
3793
+
3794
+ markup {
3795
+ div {
3796
+ h1('Address type for delivery:', style: {display: :inline})
3797
+
3798
+ address_type_selector(address_types: ['Home', 'Work', 'Other']) {
3799
+ # We can listen to the updates of any attribute/option in a Glimmer Web Component
3800
+ # on_{attribute_name}_update do execute code when component attribute/option with attribute_name is updated
3801
+ # This is an alternative to using Component Listeners, which require that the component explicitly calls notify_listeners,
3802
+ # whereas Component Attribute Listeners get tracked automatically, but depend on a specific attribute
3803
+ # The trade-off is Component Listeners provide more flexibility when needed as they are not bound to specific attributes,
3804
+ # but often Component Attribute Listeners are good enough as a solution for certain problems.
3805
+ on_selected_address_type_update do |address_type|
3806
+ $$.alert("You selected the address type: #{address_type}")
3807
+ end
3808
+ }
3809
+ }
3810
+ }
3811
+ end
3812
+
3813
+ Document.ready? do
3814
+ AddressTypeSelectorPage.render
3815
+ end
3816
+ ```
3817
+
3818
+ Screenshot:
3819
+
3820
+ ![Hello, Component Attribute Listeners!](/images/glimmer-dsl-web-samples-hello-hello-component-attribute-listeners.png)
3821
+
3822
+ ![Hello, Component Attribute Listeners Selected Address Type!](/images/glimmer-dsl-web-samples-hello-hello-component-attribute-listeners-selected-address-type.png)
3823
+
3824
+ ![Hello, Component Attribute Listeners Selected Address Type Dialog!](/images/glimmer-dsl-web-samples-hello-hello-component-attribute-listeners-selected-address-type-dialog.png)
3825
+
3826
+ #### Hello, Component Attribute Data-Binding!
3827
+
3828
+ [lib/glimmer-dsl-web/samples/hello/hello_component_attribute_data_binding.rb](/lib/glimmer-dsl-web/samples/hello/hello_component_attribute_data_binding.rb)
3829
+
3830
+ ```ruby
3831
+ require 'glimmer-dsl-web'
3832
+
3833
+ class AddressTypePresenter
3834
+ attr_accessor :address_types, :selected_address_type
3835
+
3836
+ def initialize(address_types:, selected_address_type: nil)
3837
+ @address_types = address_types
3838
+ @selected_address_type = selected_address_type
3839
+ @selected_address_type ||= address_types.first
3840
+ end
3841
+ end
3842
+
3843
+ class AddressTypeSelect
3844
+ include Glimmer::Web::Component
3845
+
3846
+ attribute :address_types, default: []
3847
+ attribute :selected_address_type
3848
+
3849
+ markup {
3850
+ select { |select_element|
3851
+ address_types.each do |address_type|
3852
+ option(value: address_type) { address_type }
3853
+ end
3854
+
3855
+ value <=> [self, :selected_address_type]
3856
+ }
3857
+ }
3858
+
3859
+ style {
3860
+ r(component_element_selector) {
3861
+ font_size 2.em
3862
+ margin_left 5.px
3863
+ }
3864
+ }
3865
+ end
3866
+
3867
+ class AddressTypeRadioGroup
3868
+ include Glimmer::Web::Component
3869
+
3870
+ attribute :address_types, default: []
3871
+ attribute :selected_address_type
3872
+
3873
+ markup {
3874
+ div { |select_element|
3875
+ address_types.each do |address_type|
3876
+ input_id = "radio_address_type_#{address_type}"
3877
+ input(id: input_id, type: 'radio', name: 'radio_address_type', value: address_type) {
3878
+ checked <=> [self, :selected_address_type,
3879
+ on_read: ->(address_type_string_value) { address_type_string_value == address_type },
3880
+ on_write: ->(radio_input_boolean_value) { radio_input_boolean_value ? address_type : selected_address_type },
3881
+ ]
3882
+ }
3883
+ label(for: input_id) { address_type }
3884
+ end
3885
+ }
3886
+ }
3887
+
3888
+ style {
3889
+ r(component_element_selector) {
3890
+ font_size 2.em
3891
+ margin_top 10.px
3892
+ }
3893
+ r("#{component_element_selector} label") {
3894
+ margin_left 2.px
3895
+ margin_right 10.px
3896
+ }
3897
+ }
3898
+ end
3899
+
3900
+ class AddressTypeComponentAttributeDataBindingPage
3901
+ include Glimmer::Web::Component
3902
+
3903
+ before_render do
3904
+ @presenter = AddressTypePresenter.new(address_types: ['Home', 'Work', 'Other'])
3905
+ end
3906
+
3907
+ markup {
3908
+ div {
3909
+ h1('Address type for delivery:')
3910
+
3911
+ address_type_select(address_types: @presenter.address_types, selected_address_type: @presenter.selected_address_type) {
3912
+ # Not only can we data-bind attributes on basic HTML elements,
3913
+ # but we can also data-bind custom attributes on Glimmer Web Components
3914
+ selected_address_type <=> [@presenter, :selected_address_type]
3915
+ }
3916
+
3917
+ address_type_radio_group(address_types: @presenter.address_types, selected_address_type: @presenter.selected_address_type) {
3918
+ # Not only can we data-bind attributes on basic HTML elements,
3919
+ # but we can also data-bind custom attributes on Glimmer Web Components
3920
+ selected_address_type <=> [@presenter, :selected_address_type]
3921
+ }
3922
+ }
3923
+ }
3924
+ end
3925
+
3926
+ Document.ready? do
3927
+ AddressTypeComponentAttributeDataBindingPage.render
3928
+ end
3929
+ ```
3930
+
3931
+ Screenshot:
3932
+
3933
+ ![Hello, Component Attribute Data-Binding!](/images/glimmer-dsl-web-samples-hello-hello-component-attribute-data-binding.gif)
3934
+
3761
3935
  #### Hello, glimmer_component Rails Helper!
3762
3936
 
3763
3937
  You may insert a Glimmer component anywhere into a Rails View using
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.7.1
1
+ 0.7.3
@@ -2,11 +2,11 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: glimmer-dsl-web 0.7.1 ruby lib
5
+ # stub: glimmer-dsl-web 0.7.3 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "glimmer-dsl-web".freeze
9
- s.version = "0.7.1".freeze
9
+ s.version = "0.7.3".freeze
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib".freeze]
@@ -33,6 +33,8 @@ Gem::Specification.new do |s|
33
33
  "lib/glimmer-dsl-web/ext/kernel.rb",
34
34
  "lib/glimmer-dsl-web/samples/hello/hello_button.rb",
35
35
  "lib/glimmer-dsl-web/samples/hello/hello_component.rb",
36
+ "lib/glimmer-dsl-web/samples/hello/hello_component_attribute_data_binding.rb",
37
+ "lib/glimmer-dsl-web/samples/hello/hello_component_attribute_listeners.rb",
36
38
  "lib/glimmer-dsl-web/samples/hello/hello_component_listeners.rb",
37
39
  "lib/glimmer-dsl-web/samples/hello/hello_component_listeners_default_slot.rb",
38
40
  "lib/glimmer-dsl-web/samples/hello/hello_component_slots.rb",
@@ -0,0 +1,127 @@
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?(:AddressTypePresenter)
25
+ class AddressTypePresenter
26
+ attr_accessor :address_types, :selected_address_type
27
+
28
+ def initialize(address_types:, selected_address_type: nil)
29
+ @address_types = address_types
30
+ @selected_address_type = selected_address_type
31
+ @selected_address_type ||= address_types.first
32
+ end
33
+ end
34
+ end
35
+
36
+ unless Object.const_defined?(:AddressTypeSelect)
37
+ class AddressTypeSelect
38
+ include Glimmer::Web::Component
39
+
40
+ attribute :address_types, default: []
41
+ attribute :selected_address_type
42
+
43
+ markup {
44
+ select { |select_element|
45
+ address_types.each do |address_type|
46
+ option(value: address_type) { address_type }
47
+ end
48
+
49
+ value <=> [self, :selected_address_type]
50
+ }
51
+ }
52
+
53
+ style {
54
+ r(component_element_selector) {
55
+ font_size 2.em
56
+ margin_left 5.px
57
+ }
58
+ }
59
+ end
60
+ end
61
+
62
+ unless Object.const_defined?(:AddressTypeRadioGroup)
63
+ class AddressTypeRadioGroup
64
+ include Glimmer::Web::Component
65
+
66
+ attribute :address_types, default: []
67
+ attribute :selected_address_type
68
+
69
+ markup {
70
+ div { |select_element|
71
+ address_types.each do |address_type|
72
+ input_id = "radio_address_type_#{address_type}"
73
+ input(id: input_id, type: 'radio', name: 'radio_address_type', value: address_type) {
74
+ checked <=> [self, :selected_address_type,
75
+ on_read: ->(address_type_string_value) { address_type_string_value == address_type },
76
+ on_write: ->(radio_input_boolean_value) { radio_input_boolean_value ? address_type : selected_address_type },
77
+ ]
78
+ }
79
+ label(for: input_id) { address_type }
80
+ end
81
+ }
82
+ }
83
+
84
+ style {
85
+ r(component_element_selector) {
86
+ font_size 2.em
87
+ margin_top 10.px
88
+ }
89
+ r("#{component_element_selector} label") {
90
+ margin_left 2.px
91
+ margin_right 10.px
92
+ }
93
+ }
94
+ end
95
+ end
96
+
97
+ unless Object.const_defined?(:AddressTypeComponentAttributeDataBindingPage)
98
+ class AddressTypeComponentAttributeDataBindingPage
99
+ include Glimmer::Web::Component
100
+
101
+ before_render do
102
+ @presenter = AddressTypePresenter.new(address_types: ['Home', 'Work', 'Other'])
103
+ end
104
+
105
+ markup {
106
+ div {
107
+ h1('Address type for delivery:')
108
+
109
+ address_type_select(address_types: @presenter.address_types, selected_address_type: @presenter.selected_address_type) {
110
+ # Not only can we data-bind attributes on basic HTML elements,
111
+ # but we can also data-bind custom attributes on Glimmer Web Components
112
+ selected_address_type <=> [@presenter, :selected_address_type]
113
+ }
114
+
115
+ address_type_radio_group(address_types: @presenter.address_types, selected_address_type: @presenter.selected_address_type) {
116
+ # Not only can we data-bind attributes on basic HTML elements,
117
+ # but we can also data-bind custom attributes on Glimmer Web Components
118
+ selected_address_type <=> [@presenter, :selected_address_type]
119
+ }
120
+ }
121
+ }
122
+ end
123
+ end
124
+
125
+ Document.ready? do
126
+ AddressTypeComponentAttributeDataBindingPage.render
127
+ end
@@ -0,0 +1,81 @@
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?(:AddressTypeSelector)
25
+ class AddressTypeSelector
26
+ include Glimmer::Web::Component
27
+
28
+ attribute :address_types, default: []
29
+ attribute :selected_address_type
30
+
31
+ before_render do
32
+ self.selected_address_type ||= address_types.first
33
+ end
34
+
35
+ markup {
36
+ select(placeholder: 'Select an address type') { |select_element|
37
+ address_types.each do |address_type|
38
+ option(value: address_type) { address_type }
39
+ end
40
+
41
+ # Bidirectionally data-bind select value to selected_address_type attribute on self (component)
42
+ value <=> [self, :selected_address_type]
43
+ }
44
+ }
45
+
46
+ style {
47
+ r(component_element_selector) {
48
+ font_size 2.em
49
+ margin_left 5.px
50
+ }
51
+ }
52
+ end
53
+ end
54
+
55
+ unless Object.const_defined?(:AddressTypeSelectorPage)
56
+ class AddressTypeSelectorPage
57
+ include Glimmer::Web::Component
58
+
59
+ markup {
60
+ div {
61
+ h1('Address type for delivery:', style: {display: :inline})
62
+
63
+ address_type_selector(address_types: ['Home', 'Work', 'Other']) {
64
+ # We can listen to the updates of any attribute/option in a Glimmer Web Component
65
+ # on_{attribute_name}_update do execute code when component attribute/option with attribute_name is updated
66
+ # This is an alternative to using Component Listeners, which require that the component explicitly calls notify_listeners,
67
+ # whereas Component Attribute Listeners get tracked automatically, but depend on a specific attribute
68
+ # The trade-off is Component Listeners provide more flexibility when needed as they are not bound to specific attributes,
69
+ # but often Component Attribute Listeners are good enough as a solution for certain problems.
70
+ on_selected_address_type_update do |address_type|
71
+ $$.alert("You selected the address type: #{address_type}")
72
+ end
73
+ }
74
+ }
75
+ }
76
+ end
77
+ end
78
+
79
+ Document.ready? do
80
+ AddressTypeSelectorPage.render
81
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: glimmer-dsl-web
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.1
4
+ version: 0.7.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Maleh
@@ -266,6 +266,8 @@ files:
266
266
  - lib/glimmer-dsl-web/ext/kernel.rb
267
267
  - lib/glimmer-dsl-web/samples/hello/hello_button.rb
268
268
  - lib/glimmer-dsl-web/samples/hello/hello_component.rb
269
+ - lib/glimmer-dsl-web/samples/hello/hello_component_attribute_data_binding.rb
270
+ - lib/glimmer-dsl-web/samples/hello/hello_component_attribute_listeners.rb
269
271
  - lib/glimmer-dsl-web/samples/hello/hello_component_listeners.rb
270
272
  - lib/glimmer-dsl-web/samples/hello/hello_component_listeners_default_slot.rb
271
273
  - lib/glimmer-dsl-web/samples/hello/hello_component_slots.rb