glimmer-dsl-web 0.3.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fda2e015b57a53d9281d9f04b7c222d8d42bf61bb33d670cb6ddb8cb0893d2c5
4
- data.tar.gz: 327db5ff9687b0fb8f5ed96533c9c86dc71ca20876b8db39e28d7b156aed280b
3
+ metadata.gz: b8b106a50bf2ec2656480068685e725017a8b7a32f49d38459416b01c550e9aa
4
+ data.tar.gz: ca6c4c2e8028059c5a305c45f181d6f5902e5737d65011dd5ce34b5af334baf0
5
5
  SHA512:
6
- metadata.gz: 7b36ba84170c0a90ac89a28218275fa4da16b21972bb298cd59b675fb41670c367aec3524f8582a7181d91f2a723ea36f7d7e6b8e2a1d4e2733b634b7e8e808f
7
- data.tar.gz: 4d88b75d775ca693fe50602f7d6b9fd136188f1d88d8d13dee8d7e281c9218a1fc81540e662b1428ff67ac1ee124a015919aec31b2853eb49378ff022003c225
6
+ metadata.gz: a95444fd3855a6e498e243eb06ffc80e7f25cdbe5ed3b178729450d37f3c528b42919dccddde64caa38e48fcb90b4bb16e42d4f5b3c66425ac2333c5176fe7ad
7
+ data.tar.gz: fc781cd899da6a3059b6b5b048c7f324f03d231a755403178996d90eed391cbbcb3c2a214eb6bf0b0e2b1e318560e1b8fded988f998582075b181d7ee74093cb
data/CHANGELOG.md CHANGED
@@ -1,16 +1,27 @@
1
1
  # Change Log
2
2
 
3
+ ## 0.4.0
4
+
5
+ - Support `style {}` block in `Glimmer::Web::Component` that would automatically add style in one place for all components, without repeating style for repeating components
6
+ - Upgrade to Glimmer DSL for CSS 1.5.0
7
+ - Remove support for including multiple `before_render` and `after_render` blocks in a component as it is not needed and can be confusing
8
+ - Hello, Style! Sample: `require 'glimmer-dsl-web/samples/hello/hello_style'`
9
+ - Optimize performance of Todo MVC by not adding an edit input field to every todo, yet adding it only upon editing a todo.
10
+ - Fix issue with `ElementProxy#add_css_class` and `ElementProxy#remove_css_class` crashing if called before rendering an element
11
+ - Fix issue with Hello, Observer (Data-Binding)! crashing if run after Hello, Observer! due to both samples sharing the same class by mistake
12
+
3
13
  ## 0.3.2
4
14
 
5
15
  - Optimize performance (~248% faster) of rendering by changing DSL ordering to avoid component checks at the top
6
16
  - Optimize performance of formatting html elements by adding Glimmer DSL shortcut methods
7
17
  - Optimize performance of component expressions by indexing component keywords
18
+ - Upgrade to glimmer 2.7.9
8
19
 
9
20
  ## 0.3.1
10
21
 
11
22
  - Optimize Todo MVC performance for filtering between all, active, and completed (it happens instantly now)
12
- - Append Todo MVC todos at the bottom instead of prepending them at the top (I copied the ES6 version initially which did things the opposite way from how Todo MVC behaves normally in other versions)
13
- - Make Todo MVC "items left" text show "item left" if there is only 1 todo (I missed this detail before)
23
+ - Append Todo MVC todos at the bottom instead of prepending them at the top (the ES6 version was copied initially, which ordered todos the opposite way from how Todo MVC behaves in other versions)
24
+ - Make Todo MVC "items left" text show "item left" if there is only 1 todo
14
25
  - Make Todo MVC footer links open a new tab/window (with `target: '_blank'` option)
15
26
  - Refactor/Simplify Todo MVC sample code
16
27
  - Upgrade to glimmer 2.7.8
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.3.2 (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.4.0 (Beta)
2
2
  ## Ruby in the Browser Web Frontend Framework
3
3
  ### Finally, Ruby Developer Productivity, Happiness, and Fun in the Frontend!!!
4
4
  [![Gem Version](https://badge.fury.io/rb/glimmer-dsl-web.svg)](http://badge.fury.io/rb/glimmer-dsl-web)
@@ -14,7 +14,7 @@ You can finally have Ruby developer happiness and productivity in the Frontend!
14
14
 
15
15
  [Glimmer](https://github.com/AndyObtiva/glimmer) DSL for Web enables building Web Frontends using [Ruby in the Browser](https://www.youtube.com/watch?v=4AdcfbI6A4c), as per [Matz's recommendation in his RubyConf 2022 keynote speech to replace JavaScript with Ruby](https://youtu.be/knutsgHTrfQ?t=789). It supports Rails' principle of the One Person Framework by not requiring any extra developers with JavaScript expertise, yet enabling Ruby (Backend) Software Engineers to develop the Frontend with Ruby code that is better than any JavaScript code produced by JS developers. It aims at providing the simplest, most intuitive, most straight-forward, and most productive frontend framework in existence. The framework follows the Ruby way (with [DSLs](https://martinfowler.com/books/dsl.html) and [TIMTOWTDI](https://en.wiktionary.org/wiki/TMTOWTDI#English)) and the Rails way ([Convention over Configuration](https://rubyonrails.org/doctrine)) in building Isomorphic Ruby on Rails Applications. It provides a Ruby [HTML DSL](#usage), which uniquely enables writing both structure code and logic code in one language. It supports both Unidirectional (One-Way) [Data-Binding](#hello-data-binding) (using `<=`) and Bidirectional (Two-Way) [Data-Binding](#hello-data-binding) (using `<=>`). Dynamic rendering (and re-rendering) of HTML content is also supported via [Content Data-Binding](#hello-content-data-binding). Modular design is supported with [Glimmer Web Components](#hello-component). And, a Ruby CSS DSL is supported with the included [Glimmer DSL for CSS](https://github.com/AndyObtiva/glimmer-dsl-css). To automatically convert legacy HTML & CSS code to Glimmer DSL Ruby code, Software Engineers could use the included [`html_to_glimmer`](https://github.com/AndyObtiva/glimmer-dsl-xml#html-to-glimmer-converter) and [`css_to_glimmer`](https://github.com/AndyObtiva/glimmer-dsl-css#css-to-glimmer-converter) commands. Many [samples](#samples) are demonstrated in the [Rails sample app](https://github.com/AndyObtiva/sample-glimmer-dsl-web-rails7-app) (there is a very minimal [Standalone [No Rails] static site sample app](https://github.com/Largo/glimmer-dsl-web-standalone-demo) too). You can finally live in pure Rubyland on the Web in both the frontend and backend with [Glimmer DSL for Web](https://rubygems.org/gems/glimmer-dsl-web)!
16
16
 
17
- [Glimmer DSL for Web](https://rubygems.org/gems/glimmer-dsl-web) aims to be a very simple Ruby-based drop-in replacement for your existing JavaScript Frontend library (e.g. React, Angular, Vue, Ember, Svelte) or your JavaScript Frontend layer in general. It does not change how your Frontend interacts with the Backend, meaning you can continue to write Rails Backend API endpoints as needed and make Ajax HTTP requests or read data embedded in elements, but from [Ruby in the Browser](https://www.youtube.com/watch?v=4AdcfbI6A4c). Whatever is possible in JavaScript is possible when using Glimmer DSL for Web as it integrates with any existing JavaScript library. The [Rails sample app](https://github.com/AndyObtiva/sample-glimmer-dsl-web-rails7-app) demonstrates how to [make Ajax HTTP calls](https://github.com/AndyObtiva/sample-glimmer-dsl-web-rails7-app/blob/master/app/assets/opal/sample_selector/models/sample_selector_presenter.rb) and how to [integrate with a JavaScript library](https://github.com/AndyObtiva/sample-glimmer-dsl-web-rails7-app/blob/master/app/views/layouts/application.html.erb) (highlightjs) that performs [code syntax highlighting](https://github.com/AndyObtiva/sample-glimmer-dsl-web-rails7-app/blob/master/app/assets/opal/sample_selector.rb).
17
+ [Glimmer DSL for Web](https://rubygems.org/gems/glimmer-dsl-web) aims to be a very simple Ruby-based drop-in replacement for your existing JavaScript Frontend library (e.g. React, Angular, Vue, Ember, Svelte) or your JavaScript Frontend layer in general. It does not change how your Frontend interacts with the Backend, meaning you can continue to write Rails Backend API endpoints as needed and make HTTP/Ajax requests or read data embedded in elements, but from [Ruby in the Browser](https://www.youtube.com/watch?v=4AdcfbI6A4c). Whatever is possible in JavaScript is possible when using Glimmer DSL for Web as it integrates with any existing JavaScript library. The [Rails sample app](https://github.com/AndyObtiva/sample-glimmer-dsl-web-rails7-app) demonstrates how to [make HTTP calls](https://github.com/AndyObtiva/sample-glimmer-dsl-web-rails7-app/blob/master/app/assets/opal/sample_selector/models/sample_api.rb) and how to [integrate with a JavaScript library](https://github.com/AndyObtiva/sample-glimmer-dsl-web-rails7-app/blob/master/app/views/layouts/application.html.erb) (highlightjs) that performs [code syntax highlighting](https://github.com/AndyObtiva/sample-glimmer-dsl-web-rails7-app/blob/master/app/assets/opal/sample_selector.rb).
18
18
 
19
19
  After looking through the [samples](#samples) below, read the [FAQ (Frequently Asked Questions)](#faq) to learn more about how Glimmer DSL for Web compares to other approaches/libraries like Hotwire (Turbo), Phlex, ViewComponent, Angular, Vue, React, Svelte, and other JS frameworks.
20
20
 
@@ -265,8 +265,64 @@ Glimmer HTML DSL Ruby code in the frontend:
265
265
  ```ruby
266
266
  require 'glimmer-dsl-web'
267
267
 
268
- Address = Struct.new(:street, :street2, :city, :state, :zip_code, keyword_init: true) do
269
- STATES = {...} # contains US States
268
+ Address = Struct.new(:street, :street2, :city, :state, :zip_code, :billing_and_shipping, keyword_init: true) do
269
+ STATES = {
270
+ "AK"=>"Alaska",
271
+ "AL"=>"Alabama",
272
+ "AR"=>"Arkansas",
273
+ "AS"=>"American Samoa",
274
+ "AZ"=>"Arizona",
275
+ "CA"=>"California",
276
+ "CO"=>"Colorado",
277
+ "CT"=>"Connecticut",
278
+ "DC"=>"District of Columbia",
279
+ "DE"=>"Delaware",
280
+ "FL"=>"Florida",
281
+ "GA"=>"Georgia",
282
+ "GU"=>"Guam",
283
+ "HI"=>"Hawaii",
284
+ "IA"=>"Iowa",
285
+ "ID"=>"Idaho",
286
+ "IL"=>"Illinois",
287
+ "IN"=>"Indiana",
288
+ "KS"=>"Kansas",
289
+ "KY"=>"Kentucky",
290
+ "LA"=>"Louisiana",
291
+ "MA"=>"Massachusetts",
292
+ "MD"=>"Maryland",
293
+ "ME"=>"Maine",
294
+ "MI"=>"Michigan",
295
+ "MN"=>"Minnesota",
296
+ "MO"=>"Missouri",
297
+ "MS"=>"Mississippi",
298
+ "MT"=>"Montana",
299
+ "NC"=>"North Carolina",
300
+ "ND"=>"North Dakota",
301
+ "NE"=>"Nebraska",
302
+ "NH"=>"New Hampshire",
303
+ "NJ"=>"New Jersey",
304
+ "NM"=>"New Mexico",
305
+ "NV"=>"Nevada",
306
+ "NY"=>"New York",
307
+ "OH"=>"Ohio",
308
+ "OK"=>"Oklahoma",
309
+ "OR"=>"Oregon",
310
+ "PA"=>"Pennsylvania",
311
+ "PR"=>"Puerto Rico",
312
+ "RI"=>"Rhode Island",
313
+ "SC"=>"South Carolina",
314
+ "SD"=>"South Dakota",
315
+ "TN"=>"Tennessee",
316
+ "TX"=>"Texas",
317
+ "UT"=>"Utah",
318
+ "VA"=>"Virginia",
319
+ "VI"=>"Virgin Islands",
320
+ "VT"=>"Vermont",
321
+ "WA"=>"Washington",
322
+ "WI"=>"Wisconsin",
323
+ "WV"=>"West Virginia",
324
+ "WY"=>"Wyoming"
325
+ }
270
326
 
271
327
  def state_code
272
328
  STATES.invert[state]
@@ -1310,6 +1366,8 @@ Learn more about the differences between various [Glimmer](https://github.com/An
1310
1366
 
1311
1367
  You can setup Glimmer DSL for Web in [Rails 7](#rails-7), [Rails 6](#rails-6), or [Standalone (No Rails)](#standalone-no-rails).
1312
1368
 
1369
+ Once done, read [Usage](#usage) instructions. Note that for serious app usage, it is recommended to build [components](#hello-component) and use the [`glimmer_component` Rails Helper](#hello-glimmer_component-rails-helper) to embed the top-level Web Frontend component in a Rails View.
1370
+
1313
1371
  (NOTE: Keep in mind this is a Beta. If you run into issues, try to go back to a [previous revision](https://rubygems.org/gems/glimmer-dsl-web/versions). Also, there is a slight chance any issues you encounter are fixed in master or some other branch that you could check out instead)
1314
1372
 
1315
1373
  ### Rails 7
@@ -1333,7 +1391,7 @@ rails new glimmer_app_server
1333
1391
  Add the following to `Gemfile`:
1334
1392
 
1335
1393
  ```
1336
- gem 'glimmer-dsl-web', '~> 0.3.2'
1394
+ gem 'glimmer-dsl-web', '~> 0.4.0'
1337
1395
  ```
1338
1396
 
1339
1397
  Run:
@@ -1562,7 +1620,7 @@ Disable the `webpacker` gem line in `Gemfile`:
1562
1620
  Add the following to `Gemfile`:
1563
1621
 
1564
1622
  ```ruby
1565
- gem 'glimmer-dsl-web', '~> 0.3.2'
1623
+ gem 'glimmer-dsl-web', '~> 0.4.0'
1566
1624
  ```
1567
1625
 
1568
1626
  Run:
@@ -1803,6 +1861,8 @@ You can get/set any element property or invoke any element function by simply ca
1803
1861
 
1804
1862
  Next, check out [Samples](#samples).
1805
1863
 
1864
+ Note that for serious app usage, it is recommended to build [components](#hello-component) and use the [`glimmer_component` Rails Helper](#hello-glimmer_component-rails-helper) to embed the top-level Web Frontend component in a Rails View.
1865
+
1806
1866
  ## Supported Glimmer DSL Keywords
1807
1867
 
1808
1868
  [All HTML elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Element), following the Ruby method name standard of lowercase and underscored names.
@@ -2256,7 +2316,7 @@ Glimmer HTML DSL Ruby code in the frontend:
2256
2316
  ```ruby
2257
2317
  require 'glimmer-dsl-web'
2258
2318
 
2259
- Address = Struct.new(:street, :street2, :city, :state, :zip_code, keyword_init: true) do
2319
+ Address = Struct.new(:street, :street2, :city, :state, :zip_code, :billing_and_shipping, keyword_init: true) do
2260
2320
  STATES = {
2261
2321
  "AK"=>"Alaska",
2262
2322
  "AL"=>"Alabama",
@@ -2391,7 +2451,9 @@ Document.ready? do
2391
2451
  }
2392
2452
  }
2393
2453
 
2454
+ # Programmable CSS using Glimmer DSL for CSS
2394
2455
  style {
2456
+ # `r` is an alias for `rule`, generating a CSS rule
2395
2457
  r("#{address_div.selector} *") {
2396
2458
  margin '5px'
2397
2459
  }
@@ -3370,8 +3432,9 @@ end
3370
3432
  - Declarative syntax that visually maps to the DOM (Document Object Model) hierarchy
3371
3433
  - Ability to mix declarative and imperative code conveniently in one language
3372
3434
  - Computers serve Software Engineers (not Software Engineers serve Computers)
3373
- - Think only about real world concepts directly relevant to web page interaction
3435
+ - Think only about real world concepts directly relevant to web page interaction and application domain model
3374
3436
  - Modular Software Design (e.g. support for Components)
3437
+ - No premature optimizations
3375
3438
 
3376
3439
  ## Supporting Libraries
3377
3440
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.2
1
+ 0.4.0
@@ -2,16 +2,16 @@
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.3.2 ruby lib
5
+ # stub: glimmer-dsl-web 0.4.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "glimmer-dsl-web".freeze
9
- s.version = "0.3.2"
9
+ s.version = "0.4.0"
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]
13
13
  s.authors = ["Andy Maleh".freeze]
14
- s.date = "2024-07-02"
14
+ s.date = "2024-07-22"
15
15
  s.description = "Glimmer DSL for Web (Ruby in the Browser Web Frontend Framework) enables building Web Frontends using Ruby in the Browser, as per Matz's recommendation in his RubyConf 2022 keynote speech to replace JavaScript with Ruby. It aims at providing the simplest, most intuitive, most straight-forward, and most productive frontend framework in existence. The framework follows the Ruby way (with DSLs and TIMTOWTDI) and the Rails way (Convention over Configuration) in building Isomorphic Ruby on Rails Applications. It provides a Ruby HTML DSL, which uniquely enables writing both structure code and logic code in one language. It supports both Unidirectional (One-Way) Data-Binding (using <=) and Bidirectional (Two-Way) Data-Binding (using <=>). Dynamic rendering (and re-rendering) of HTML content is also supported via Content Data-Binding. Modular design is supported with Glimmer Web Components. And, a Ruby CSS DSL is supported with the included Glimmer DSL for CSS. Many samples are demonstrated in the Rails sample app (there is a very minimal Standalone [No Rails] sample app too). You can finally live in pure Rubyland on the Web in both the frontend and backend with Glimmer DSL for Web! This gem relies on Opal Ruby.".freeze
16
16
  s.email = "andy.am@gmail.com".freeze
17
17
  s.extra_rdoc_files = [
@@ -46,6 +46,7 @@ Gem::Specification.new do |s|
46
46
  "lib/glimmer-dsl-web/samples/hello/hello_observer.rb",
47
47
  "lib/glimmer-dsl-web/samples/hello/hello_observer_data_binding.rb",
48
48
  "lib/glimmer-dsl-web/samples/hello/hello_paragraph.rb",
49
+ "lib/glimmer-dsl-web/samples/hello/hello_style.rb",
49
50
  "lib/glimmer-dsl-web/samples/hello/hello_world.rb",
50
51
  "lib/glimmer-dsl-web/samples/regular/button_counter.rb",
51
52
  "lib/glimmer-dsl-web/samples/regular/todo_mvc.rb",
@@ -81,6 +82,7 @@ Gem::Specification.new do |s|
81
82
  "lib/glimmer/util/proc_tracker.rb",
82
83
  "lib/glimmer/web.rb",
83
84
  "lib/glimmer/web/component.rb",
85
+ "lib/glimmer/web/component/component_style_container.rb",
84
86
  "lib/glimmer/web/element_proxy.rb",
85
87
  "lib/glimmer/web/event_proxy.rb",
86
88
  "lib/glimmer/web/formatting_element_proxy.rb",
@@ -93,9 +95,9 @@ Gem::Specification.new do |s|
93
95
 
94
96
  s.specification_version = 4
95
97
 
96
- s.add_runtime_dependency(%q<glimmer>.freeze, ["~> 2.7.9"])
98
+ s.add_runtime_dependency(%q<glimmer>.freeze, ["~> 2.8.0"])
97
99
  s.add_runtime_dependency(%q<glimmer-dsl-xml>.freeze, ["~> 1.4.0"])
98
- s.add_runtime_dependency(%q<glimmer-dsl-css>.freeze, ["~> 1.4.0"])
100
+ s.add_runtime_dependency(%q<glimmer-dsl-css>.freeze, ["~> 1.5.0"])
99
101
  s.add_runtime_dependency(%q<opal>.freeze, ["= 1.8.2"])
100
102
  s.add_runtime_dependency(%q<opal-rails>.freeze, ["= 2.0.3"])
101
103
  s.add_runtime_dependency(%q<opal-async>.freeze, ["~> 1.4.1"])
@@ -0,0 +1,13 @@
1
+ require 'glimmer/web/component'
2
+
3
+ class ComponentStyleContainer
4
+ include Glimmer::Web::Component
5
+
6
+ option :component
7
+
8
+ markup {
9
+ component_style_container_block = component.class.instance_variable_get("@style_block")
10
+ component_style_container_class = "#{component.class.component_element_class}-style"
11
+ style(class: component_style_container_class, &component_style_container_block)
12
+ }
13
+ end
@@ -73,17 +73,20 @@ module Glimmer
73
73
  end
74
74
 
75
75
  def before_render(&block)
76
- @before_render_blocks ||= []
77
- @before_render_blocks << block
76
+ @before_render = block
78
77
  end
79
78
 
80
79
  def markup(&block)
81
80
  @markup_block = block
82
81
  end
83
82
 
83
+ # TODO in the future support a string value too
84
+ def style(&block)
85
+ @style_block = block
86
+ end
87
+
84
88
  def after_render(&block)
85
- @after_render_blocks ||= []
86
- @after_render_blocks << block
89
+ @after_render = block
87
90
  end
88
91
 
89
92
  def keyword
@@ -95,6 +98,14 @@ module Glimmer
95
98
  self.name.underscore.gsub('::', '__').split('__').last
96
99
  end
97
100
 
101
+ def component_element_class
102
+ self.keyword.gsub('_', '-')
103
+ end
104
+
105
+ def component_shortcut_element_class
106
+ self.shortcut_keyword.gsub('_', '-')
107
+ end
108
+
98
109
  # Creates component without rendering
99
110
  def create(*args)
100
111
  args << {} unless args.last.is_a?(Hash)
@@ -105,6 +116,7 @@ module Glimmer
105
116
 
106
117
  # Creates and renders component
107
118
  def render(*args)
119
+ Glimmer::DSL::Engine.new_parent_stack unless Glimmer::DSL::Engine.parent.nil?
108
120
  rendered_component = send(keyword, *args)
109
121
  rendered_component
110
122
  end
@@ -172,14 +184,76 @@ module Glimmer
172
184
  def interpretation_stack
173
185
  @interpretation_stack ||= []
174
186
  end
187
+
188
+ def add_component(component)
189
+ component_class_to_components_map[component.class] ||= {}
190
+ component_class_to_components_map[component.class][component.object_id] = component
191
+ end
192
+
193
+ def remove_component(component)
194
+ component_class_to_components_map[component.class].delete(component.object_id)
195
+ component_class_to_components_map.delete(component.class) if component_class_to_components_map[component.class].empty?
196
+ end
197
+
198
+ def add_component_style(component)
199
+ # We must not remove the head style element until all components are removed of a component class
200
+ if Glimmer::Web::Component.component_count(component.class) == 1
201
+ Glimmer::Web::Component.component_styles[component.class] = ComponentStyleContainer.render(parent: 'head', component: component, component_style_container_block: component.style_block)
202
+ end
203
+ end
204
+
205
+ def remove_component_style(component)
206
+ if Glimmer::Web::Component.any_component_style?(component.class)
207
+ # TODO in the future, you would need to remove style using a jQuery call if you created head element in bulk
208
+ Glimmer::Web::Component.component_styles[component.class].remove
209
+ Glimmer::Web::Component.component_styles.delete(component.class)
210
+ end
211
+ end
212
+
213
+ def any_component?(component_class)
214
+ component_class_to_components_map.has_key?(component_class)
215
+ end
216
+
217
+ def any_component_style?(component_class)
218
+ component_styles.has_key?(component_class)
219
+ end
220
+
221
+ def component_count(component_class)
222
+ component_class_to_components_map[component_class].size
223
+ end
224
+
225
+ def components
226
+ component_class_to_components_map.values.map(&:values).flatten
227
+ end
228
+
229
+ def body_components
230
+ components.reject {|component| component.is_a?(ComponentStyleContainer)}
231
+ end
232
+
233
+ def head_components
234
+ components.select {|component| component.is_a?(ComponentStyleContainer)}
235
+ end
236
+
237
+ def remove_all_components
238
+ # removing body components automatically removes corresponding head components
239
+ body_components.each(&:remove)
240
+ end
241
+
242
+ def component_class_to_components_map
243
+ @component_class_to_components_map ||= {}
244
+ end
245
+
246
+ def component_styles
247
+ @component_styles ||= {}
248
+ end
175
249
  end
176
250
  # <- end of class methods
177
251
 
178
-
179
- attr_reader :markup_root, :parent, :args, :options
252
+ attr_reader :markup_root, :parent, :args, :options, :style_block, :component_style
180
253
  alias parent_proxy parent
181
254
 
182
255
  def initialize(parent, args, options, &content)
256
+ Glimmer::Web::Component.add_component(self)
183
257
  Component.interpretation_stack.push(self)
184
258
  @parent = parent
185
259
  options = args.delete_at(-1) if args.is_a?(Array) && args.last.is_a?(Hash)
@@ -192,10 +266,14 @@ module Glimmer
192
266
  options ||= {}
193
267
  @options = self.class.options.merge(options)
194
268
  @content = Util::ProcTracker.new(content) if content
269
+ # @style_blocks = {} # TODO enable when doing bulk head rendering in the future
195
270
  execute_hooks('before_render')
196
271
  markup_block = self.class.instance_variable_get("@markup_block")
272
+ # add_style_block
197
273
  raise Glimmer::Error, 'Invalid Glimmer web component for having no markup! Please define markup block!' if markup_block.nil?
198
274
  @markup_root = instance_exec(&markup_block)
275
+ add_style_block
276
+ # add_style_to_markup_root
199
277
  @markup_root.options[:parent] = options[:parent] if !options[:parent].nil?
200
278
  @parent ||= @markup_root.parent
201
279
  raise Glimmer::Error, 'Invalid Glimmer web component for having an empty markup! Please fill markup block!' if @markup_root.nil?
@@ -346,10 +424,35 @@ module Glimmer
346
424
  private
347
425
 
348
426
  def execute_hooks(hook_name)
349
- self.class.instance_variable_get("@#{hook_name}_blocks")&.each do |hook_block|
350
- instance_exec(&hook_block)
351
- end
427
+ hook_block = self.class.instance_variable_get("@#{hook_name}")
428
+ instance_exec(&hook_block) if hook_block
429
+ end
430
+
431
+ def add_style_block
432
+ style_block = self.class.instance_variable_get("@style_block")
433
+ # TODO handle case of style_block being a nil with style value being a string
434
+ return if style_block.nil?
435
+ # style_block_component_index = Component.interpretation_stack.size > 1 ? -2 : -1
436
+ # TODO It might be better to have each component create a style tag in head by accumulating style blocks here first
437
+ # Component.interpretation_stack[style_block_component_index].style_blocks << style_block
438
+ Glimmer::Web::Component.add_component_style(self)
439
+ end
440
+
441
+ # TODO render style blocks in head in bulk in the future
442
+ # def add_style_to_markup_root
443
+ # if Component.interpretation_stack.size == 1 && !style_blocks.empty?
444
+ ## TODO it might be better to generate element directly instead of relying on ComponentStyle
445
+ ## for performance reasons
446
+ ## TODO rename component_style and capture it by component (might need to have style_blocks a hash mapping component classes to style blocks)
447
+ # @component_style = ComponentStyleContainer.render(parent: 'head', component: self, style_blocks:)
448
+ # end
449
+ # end
450
+
451
+ def remove_style_block
452
+ Glimmer::Web::Component.remove_component_style(self)
352
453
  end
353
454
  end
354
455
  end
355
456
  end
457
+
458
+ require 'glimmer/web/component/component_style_container'
@@ -123,13 +123,18 @@ module Glimmer
123
123
  REGEX_FORMAT_DATE = /^\d{4}-\d{2}-\d{2}$/
124
124
  REGEX_FORMAT_TIME = /^\d{2}:\d{2}$/
125
125
 
126
- attr_reader :keyword, :parent, :parent_component, :args, :options, :children, :enabled, :foreground, :background, :removed?, :rendered
126
+ attr_reader :keyword, :parent, :parent_component, :component, :args, :options, :children, :enabled, :foreground, :background, :removed, :rendered
127
127
  alias rendered? rendered
128
+ alias removed? removed
128
129
 
129
130
  def initialize(keyword, parent, args, block)
130
131
  @keyword = keyword
131
132
  @parent = parent.is_a?(Glimmer::Web::Component) ? parent.markup_root : parent
132
133
  @parent_component = parent if parent.is_a?(Glimmer::Web::Component)
134
+ if Component.interpretation_stack.last&.markup_root.nil?
135
+ @component = Component.interpretation_stack.last
136
+ @component&.instance_variable_set("@markup_root", self)
137
+ end
133
138
  @options = args.last.is_a?(Hash) ? args.last.symbolize_keys : {}
134
139
  if parent.nil?
135
140
  options[:parent] ||= Component.interpretation_stack.last&.options&.[](:parent)
@@ -164,7 +169,7 @@ module Glimmer
164
169
 
165
170
  # Executes at the closing of a parent widget curly braces after all children/properties have been added/set
166
171
  def post_add_content
167
- render if bulk_render? && @parent.nil?
172
+ render if bulk_render? && @parent.nil? && !rendered?
168
173
  end
169
174
 
170
175
  def css_classes
@@ -172,6 +177,7 @@ module Glimmer
172
177
  end
173
178
 
174
179
  def remove
180
+ return if @removed
175
181
  on_remove_listeners = listeners_for('on_remove').dup
176
182
  if rendered?
177
183
  @children.dup.each do |child|
@@ -181,6 +187,10 @@ module Glimmer
181
187
  dom_element.remove
182
188
  end
183
189
  parent&.post_remove_child(self)
190
+ if component
191
+ Glimmer::Web::Component.remove_component(component)
192
+ component.remove_style_block
193
+ end
184
194
  @removed = true
185
195
  on_remove_listeners.each do |listener|
186
196
  listener.original_event_listener.call(EventProxy.new(listener: listener))
@@ -362,7 +372,12 @@ module Glimmer
362
372
  end
363
373
 
364
374
  def html_options
365
- body_class = ([name, element_id] + css_classes.to_a).join(' ')
375
+ framework_css_classes = [name, element_id]
376
+ if component
377
+ framework_css_classes.prepend(component.class.component_element_class)
378
+ framework_css_classes.prepend(component.class.component_shortcut_element_class) if component.class.component_shortcut_element_class != component.class.component_element_class
379
+ end
380
+ body_class = (framework_css_classes + css_classes.to_a).join(' ')
366
381
  html_options = options.dup
367
382
  GLIMMER_ATTRIBUTES.each do |attribute|
368
383
  next unless html_options.include?(attribute)
@@ -413,7 +428,7 @@ module Glimmer
413
428
  if rendered?
414
429
  dom_element.add_class(css_class)
415
430
  else
416
- enqueue_post_render_method_call('class_name=', value)
431
+ enqueue_post_render_method_call('add_css_class', css_class)
417
432
  end
418
433
  end
419
434
 
@@ -425,7 +440,7 @@ module Glimmer
425
440
  if rendered?
426
441
  dom_element.remove_class(css_class)
427
442
  else
428
- enqueue_post_render_method_call('class_name=', value)
443
+ enqueue_post_render_method_call('remove_css_class', css_class)
429
444
  end
430
445
  end
431
446
 
@@ -29,7 +29,7 @@ class NumberHolder
29
29
  end
30
30
  end
31
31
 
32
- class HelloObserver
32
+ class HelloObserverDataBinding
33
33
  include Glimmer::Web::Component
34
34
 
35
35
  before_render do
@@ -53,5 +53,5 @@ class HelloObserver
53
53
  end
54
54
 
55
55
  Document.ready? do
56
- HelloObserver.render
56
+ HelloObserverDataBinding.render
57
57
  end
@@ -0,0 +1,178 @@
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
+ class ButtonModel
25
+ WIDTH_MIN = 160
26
+ WIDTH_MAX = 960
27
+ HEIGHT_MIN = 100
28
+ HEIGHT_MAX = 600
29
+ FONT_SIZE_MIN = 40
30
+ FONT_SIZE_MAX = 200
31
+
32
+ attr_accessor :text, :pushed, :width, :height, :font_size
33
+
34
+ def initialize
35
+ @text = 'Push'
36
+ @width = WIDTH_MIN
37
+ @height = HEIGHT_MIN
38
+ @font_size = FONT_SIZE_MIN
39
+ end
40
+
41
+ def push
42
+ self.pushed = !pushed
43
+ end
44
+
45
+ def text
46
+ pushed ? 'Pull' : 'Push'
47
+ end
48
+
49
+ def width=(value)
50
+ @width = value
51
+ self.font_size = @width/4 if @font_size > @width/4
52
+ end
53
+
54
+ def height=(value)
55
+ @height = value
56
+ self.font_size = @height/2.5 if @font_size > @height/2.5
57
+ end
58
+
59
+ def font_size=(value)
60
+ @font_size = value
61
+ self.width = @font_size*4 if @height < @font_size*4
62
+ self.height = @font_size*2.5 if @height < @font_size*2.5
63
+ end
64
+ end
65
+
66
+ class StyledButton
67
+ include Glimmer::Web::Component
68
+
69
+ option :button_model
70
+
71
+ markup {
72
+ button {
73
+ inner_text <= [button_model, :text, computed_by: :pushed]
74
+
75
+ class_name <= [button_model, :pushed,
76
+ on_read: ->(pushed) { pushed ? 'pushed' : 'pulled' }
77
+ ]
78
+
79
+ style <= [ button_model, :width,
80
+ on_read: method(:button_style_value) # convert value on read before storing in style
81
+ ]
82
+
83
+ style <= [ button_model, :height,
84
+ on_read: method(:button_style_value) # convert value on read before storing in style
85
+ ]
86
+
87
+ style <= [ button_model, :font_size,
88
+ on_read: method(:button_style_value) # convert value on read before storing in style
89
+ ]
90
+
91
+ onclick do
92
+ button_model.push
93
+ end
94
+ }
95
+ }
96
+
97
+ style {'
98
+ button {
99
+ font-family: Courrier New, Courrier;
100
+ border-radius: 5px;
101
+ border-width: 17px;
102
+ border-color: #ACC7D5;
103
+ background-color: #ADD8E6;
104
+ margin: 5px;
105
+ }
106
+
107
+ button.pulled {
108
+ border-style: outset;
109
+ }
110
+
111
+ button.pushed {
112
+ border-style: inset;
113
+ }
114
+ '}
115
+
116
+ def button_style_value
117
+ "
118
+ width: #{button_model.width}px;
119
+ height: #{button_model.height}px;
120
+ font-size: #{button_model.font_size}px;
121
+ "
122
+ end
123
+ end
124
+
125
+ class StyledButtonRangeInput
126
+ include Glimmer::Web::Component
127
+
128
+ option :button_model
129
+ option :property
130
+ option :property_min
131
+ option :property_max
132
+
133
+ markup {
134
+ input(type: 'range', min: property_min, max: property_max) {
135
+ value <=> [button_model, property]
136
+ }
137
+ }
138
+ end
139
+
140
+ class HelloStyle
141
+ include Glimmer::Web::Component
142
+
143
+ before_render do
144
+ @button_model = ButtonModel.new
145
+ end
146
+
147
+ markup {
148
+ div(class: 'hello-style') {
149
+ div(class: 'form-row') {
150
+ label('Styled Button Width:', for: 'styled-button-width-input')
151
+ styled_button_range_input(button_model: @button_model, property: :width, property_min: ButtonModel::WIDTH_MIN, property_max: ButtonModel::WIDTH_MAX, id: 'styled-button-width-input')
152
+ }
153
+ div(class: 'form-row') {
154
+ label('Styled Button Height:', for: 'styled-button-height-input')
155
+ styled_button_range_input(button_model: @button_model, property: :height, property_min: ButtonModel::HEIGHT_MIN, property_max: ButtonModel::HEIGHT_MAX, id: 'styled-button-height-input')
156
+ }
157
+ div(class: 'form-row') {
158
+ label('Styled Button Font Size:', for: 'styled-button-font-size-input')
159
+ styled_button_range_input(button_model: @button_model, property: :font_size, property_min: ButtonModel::FONT_SIZE_MIN, property_max: ButtonModel::FONT_SIZE_MAX, id: 'styled-button-font-size-input')
160
+ }
161
+ styled_button(button_model: @button_model)
162
+ }
163
+ }
164
+
165
+ style {'
166
+ .hello-style {
167
+ padding: 20px;
168
+ }
169
+
170
+ .hello-style .form-row {
171
+ margin: 10px 0;
172
+ }
173
+ '}
174
+ end
175
+
176
+ Document.ready? do
177
+ HelloStyle.render
178
+ end
@@ -4,8 +4,8 @@ class EditTodoInput < TodoInput
4
4
  option :presenter
5
5
  option :todo
6
6
 
7
- markup {
8
- input(class: self.class.todo_input_class) { |edit_input|
7
+ markup { # evaluated against instance as a smart default convention
8
+ input { |edit_input|
9
9
  style <= [ todo, :editing,
10
10
  on_read: ->(editing) { editing ? '' : 'display: none;' },
11
11
  after_read: -> { edit_input.focus if todo.editing? }
@@ -27,27 +27,21 @@ class EditTodoInput < TodoInput
27
27
  end
28
28
  }
29
29
  }
30
-
31
- class << self
32
- def todo_input_class
33
- 'edit-todo'
34
- end
35
30
 
36
- def todo_input_styles
37
- super
38
-
39
- rule("*:has(> .#{todo_input_class})") {
40
- position 'relative'
41
- }
42
-
43
- rule(".#{todo_input_class}") {
44
- position 'absolute'
45
- display 'block'
46
- width 'calc(100% - 43px)'
47
- padding '12px 16px'
48
- margin '0 0 0 43px'
49
- top '0'
50
- }
51
- end
52
- end
31
+ style { # evaluated against class as a smart default convention (common to all instances)
32
+ todo_input_styles
33
+
34
+ rule("*:has(> .#{component_element_class})") {
35
+ position 'relative'
36
+ }
37
+
38
+ rule(".#{component_element_class}") {
39
+ position 'absolute'
40
+ display 'block'
41
+ width 'calc(100% - 43px)'
42
+ padding '12px 16px'
43
+ margin '0 0 0 43px'
44
+ top '0'
45
+ }
46
+ }
53
47
  end
@@ -10,21 +10,21 @@ class NewTodoForm
10
10
  h1('todos')
11
11
 
12
12
  new_todo_input(presenter: presenter)
13
-
14
- style {
15
- rule('.header h1') {
16
- color '#b83f45'
17
- font_size '80px'
18
- font_weight '200'
19
- position 'absolute'
20
- text_align 'center'
21
- _webkit_text_rendering 'optimizeLegibility'
22
- _moz_text_rendering 'optimizeLegibility'
23
- text_rendering 'optimizeLegibility'
24
- top '-140px'
25
- width '100%'
26
- }
27
- }
13
+ }
14
+ }
15
+
16
+ style {
17
+ rule('.header h1') {
18
+ color '#b83f45'
19
+ font_size '80px'
20
+ font_weight '200'
21
+ position 'absolute'
22
+ text_align 'center'
23
+ _webkit_text_rendering 'optimizeLegibility'
24
+ _moz_text_rendering 'optimizeLegibility'
25
+ text_rendering 'optimizeLegibility'
26
+ top '-140px'
27
+ width '100%'
28
28
  }
29
29
  }
30
30
  end
@@ -3,41 +3,31 @@ require_relative 'todo_input'
3
3
  class NewTodoInput < TodoInput
4
4
  option :presenter
5
5
 
6
- markup {
7
- input(class: self.class.todo_input_class, placeholder: "What needs to be done?", autofocus: "") {
6
+ markup { # evaluated against instance as a smart default convention
7
+ input(placeholder: "What needs to be done?", autofocus: "") {
8
8
  value <=> [presenter.new_todo, :task]
9
9
 
10
10
  onkeyup do |event|
11
11
  presenter.create_todo if event.key == 'Enter' || event.keyCode == "\r"
12
12
  end
13
-
14
- style {
15
- self.class.todo_input_styles
16
- }
17
13
  }
18
14
  }
19
15
 
20
- class << self
21
- def todo_input_class
22
- 'new-todo'
23
- end
16
+ style { # evaluated against class as a smart default convention (common to all instances)
17
+ todo_input_styles
18
+
19
+ rule(".#{component_element_class}") { # built-in component_class.component_element_class (e.g. NewTodoInput has CSS class as new-todo-input)
20
+ padding '16px 16px 16px 60px'
21
+ height '65px'
22
+ border 'none'
23
+ background 'rgba(0, 0, 0, 0.003)'
24
+ box_shadow 'inset 0 -2px 1px rgba(0,0,0,0.03)'
25
+ }
24
26
 
25
- def todo_input_styles
26
- super
27
-
28
- rule(".#{todo_input_class}") {
29
- padding '16px 16px 16px 60px'
30
- height '65px'
31
- border 'none'
32
- background 'rgba(0, 0, 0, 0.003)'
33
- box_shadow 'inset 0 -2px 1px rgba(0,0,0,0.03)'
34
- }
35
-
36
- rule(".#{todo_input_class}::placeholder") {
37
- font_style 'italic'
38
- font_weight '400'
39
- color 'rgba(0, 0, 0, 0.4)'
40
- }
41
- end
42
- end
27
+ rule(".#{component_element_class}::placeholder") { # built-in component_class.component_element_class (e.g. NewTodoInput has CSS class as new-todo-input)
28
+ font_style 'italic'
29
+ font_weight '400'
30
+ color 'rgba(0, 0, 0, 0.4)'
31
+ }
32
+ }
43
33
  end
@@ -45,80 +45,80 @@ class TodoFilters
45
45
  presenter.clear_completed
46
46
  end
47
47
  }
48
+ }
49
+ }
50
+
51
+ style {
52
+ rule('.todo-filters') {
53
+ border_top '1px solid #e6e6e6'
54
+ font_size '15px'
55
+ height '20px'
56
+ padding '10px 15px'
57
+ text_align 'center'
58
+ }
59
+
60
+ rule('.todo-filters:before') {
61
+ bottom '0'
62
+ box_shadow '0 1px 1px rgba(0,0,0,.2), 0 8px 0 -3px #f6f6f6, 0 9px 1px -3px rgba(0,0,0,.2), 0 16px 0 -6px #f6f6f6, 0 17px 2px -6px rgba(0,0,0,.2)'
63
+ content '""'
64
+ height '50px'
65
+ left '0'
66
+ overflow 'hidden'
67
+ position 'absolute'
68
+ right '0'
69
+ }
70
+
71
+ rule('.todo-count') {
72
+ float 'left'
73
+ text_align 'left'
74
+ }
75
+
76
+ rule('.todo-count .strong') {
77
+ font_weight '300'
78
+ }
79
+
80
+ rule('.filters') {
81
+ left '0'
82
+ list_style 'none'
83
+ margin '0'
84
+ padding '0'
85
+ position 'absolute'
86
+ right '0'
87
+ }
88
+
89
+ rule('.filters li') {
90
+ display 'inline'
91
+ }
92
+
93
+ rule('.filters li a') {
94
+ border '1px solid transparent'
95
+ border_radius '3px'
96
+ color 'inherit'
97
+ margin '3px'
98
+ padding '3px 7px'
99
+ text_decoration 'none'
100
+ cursor 'pointer'
101
+ }
102
+
103
+ rule('.filters li a.selected') {
104
+ border_color '#ce4646'
105
+ }
106
+
107
+ rule('.clear-completed, html .clear-completed:active') {
108
+ cursor 'pointer'
109
+ float 'right'
110
+ line_height '19px'
111
+ position 'relative'
112
+ text_decoration 'none'
113
+ }
114
+
115
+ media('(max-width: 430px)') {
116
+ rule('.todo-filters') {
117
+ height '50px'
118
+ }
48
119
 
49
- style {
50
- rule('.todo-filters') {
51
- border_top '1px solid #e6e6e6'
52
- font_size '15px'
53
- height '20px'
54
- padding '10px 15px'
55
- text_align 'center'
56
- }
57
-
58
- rule('.todo-filters:before') {
59
- bottom '0'
60
- box_shadow '0 1px 1px rgba(0,0,0,.2), 0 8px 0 -3px #f6f6f6, 0 9px 1px -3px rgba(0,0,0,.2), 0 16px 0 -6px #f6f6f6, 0 17px 2px -6px rgba(0,0,0,.2)'
61
- content '""'
62
- height '50px'
63
- left '0'
64
- overflow 'hidden'
65
- position 'absolute'
66
- right '0'
67
- }
68
-
69
- rule('.todo-count') {
70
- float 'left'
71
- text_align 'left'
72
- }
73
-
74
- rule('.todo-count .strong') {
75
- font_weight '300'
76
- }
77
-
78
- rule('.filters') {
79
- left '0'
80
- list_style 'none'
81
- margin '0'
82
- padding '0'
83
- position 'absolute'
84
- right '0'
85
- }
86
-
87
- rule('.filters li') {
88
- display 'inline'
89
- }
90
-
91
- rule('.filters li a') {
92
- border '1px solid transparent'
93
- border_radius '3px'
94
- color 'inherit'
95
- margin '3px'
96
- padding '3px 7px'
97
- text_decoration 'none'
98
- cursor 'pointer'
99
- }
100
-
101
- rule('.filters li a.selected') {
102
- border_color '#ce4646'
103
- }
104
-
105
- rule('.clear-completed, html .clear-completed:active') {
106
- cursor 'pointer'
107
- float 'right'
108
- line_height '19px'
109
- position 'relative'
110
- text_decoration 'none'
111
- }
112
-
113
- media('(max-width: 430px)') {
114
- rule('.todo-filters') {
115
- height '50px'
116
- }
117
-
118
- rule('.filters') {
119
- bottom '10px'
120
- }
121
- }
120
+ rule('.filters') {
121
+ bottom '10px'
122
122
  }
123
123
  }
124
124
  }
@@ -3,12 +3,8 @@ class TodoInput
3
3
  include Glimmer::Web::Component
4
4
 
5
5
  class << self
6
- def todo_input_class
7
- 'todo-input'
8
- end
9
-
10
6
  def todo_input_styles
11
- rule(".#{todo_input_class}") {
7
+ rule(".#{component_element_class}") {
12
8
  position 'relative'
13
9
  margin '0'
14
10
  width '100%'
@@ -24,9 +20,14 @@ class TodoInput
24
20
  _webkit_font_smoothing 'antialiased'
25
21
  }
26
22
 
27
- rule(".#{todo_input_class}::selection") {
23
+ rule(".#{component_element_class}::selection") {
28
24
  background 'red'
29
25
  }
26
+
27
+ rule(".#{component_element_class}:focus") {
28
+ box_shadow '0 0 2px 2px #cf7d7d'
29
+ outline '0'
30
+ }
30
31
  end
31
32
  end
32
33
  end
@@ -38,16 +38,10 @@ class TodoList
38
38
  todo_list_item(presenter:, todo:)
39
39
  end
40
40
  }
41
-
42
- style {
43
- todo_list_styles
44
- }
45
41
  }
46
42
  }
47
43
 
48
- def todo_list_styles
49
- TodoListItem.todo_list_item_styles
50
-
44
+ style {
51
45
  rule('.main') {
52
46
  border_top '1px solid #e6e6e6'
53
47
  position 'relative'
@@ -86,7 +80,7 @@ class TodoList
86
80
  transform 'rotate(90deg)'
87
81
  }
88
82
 
89
- rule('.toggle-all:focus+label, .toggle:focus+label, :focus') {
83
+ rule('.toggle-all:focus+label, .toggle:focus+label') {
90
84
  box_shadow '0 0 2px 2px #cf7d7d'
91
85
  outline '0'
92
86
  }
@@ -104,5 +98,5 @@ class TodoList
104
98
  rule('.todo-list.completed li.active') {
105
99
  display 'none'
106
100
  }
107
- end
101
+ }
108
102
  end
@@ -31,6 +31,12 @@ class TodoListItem
31
31
  inner_html <= [todo, :task]
32
32
 
33
33
  ondblclick do |event|
34
+ # if the markup root (li) last child is not an input field, re-open content and add an edit input field
35
+ unless markup_root.children.last.keyword == 'input'
36
+ markup_root.content {
37
+ edit_todo_input(presenter:, todo:)
38
+ }
39
+ end
34
40
  todo.start_editing
35
41
  end
36
42
  }
@@ -41,8 +47,6 @@ class TodoListItem
41
47
  end
42
48
  }
43
49
  }
44
-
45
- edit_todo_input(presenter:, todo:)
46
50
  }
47
51
  }
48
52
 
@@ -54,9 +58,7 @@ class TodoListItem
54
58
  classes.join(' ')
55
59
  end
56
60
 
57
- def self.todo_list_item_styles
58
- EditTodoInput.todo_input_styles
59
-
61
+ style {
60
62
  rule('.todo-list li.completed label') {
61
63
  color '#949494'
62
64
  text_decoration 'line-through'
@@ -155,6 +157,6 @@ class TodoListItem
155
157
  height '40px'
156
158
  }
157
159
  }
158
- end
160
+ }
159
161
  end
160
162
 
@@ -12,14 +12,10 @@ class TodoMvcFooter
12
12
  p {
13
13
  "Part of #{a('TodoMVC', href: 'http://todomvc.com', target: '_blank')}"
14
14
  }
15
-
16
- style {
17
- todo_mvc_styles
18
- }
19
15
  }
20
16
  }
21
17
 
22
- def todo_mvc_styles
18
+ style {
23
19
  rule('footer.info') {
24
20
  margin '65px auto 0'
25
21
  color '#4d4d4d'
@@ -41,5 +37,5 @@ class TodoMvcFooter
41
37
  rule('footer.info a:hover') {
42
38
  text_decoration 'underline'
43
39
  }
44
- end
40
+ }
45
41
  end
@@ -26,10 +26,6 @@ class TodoMvc
26
26
  todo_list(presenter: @presenter)
27
27
 
28
28
  todo_filters(presenter: @presenter)
29
-
30
- style {
31
- todo_mvc_styles
32
- }
33
29
  }
34
30
 
35
31
  todo_mvc_footer
@@ -40,7 +36,7 @@ class TodoMvc
40
36
  }
41
37
  }
42
38
 
43
- def todo_mvc_styles
39
+ style {
44
40
  rule('body, button, html') {
45
41
  margin '0'
46
42
  padding '0'
@@ -79,7 +75,7 @@ class TodoMvc
79
75
  font_weight '300'
80
76
  }
81
77
  }
82
- end
78
+ }
83
79
  end
84
80
 
85
81
  Document.ready? do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: glimmer-dsl-web
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Maleh
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-07-02 00:00:00.000000000 Z
11
+ date: 2024-07-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: glimmer
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 2.7.9
19
+ version: 2.8.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 2.7.9
26
+ version: 2.8.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: glimmer-dsl-xml
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 1.4.0
47
+ version: 1.5.0
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 1.4.0
54
+ version: 1.5.0
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: opal
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -279,6 +279,7 @@ files:
279
279
  - lib/glimmer-dsl-web/samples/hello/hello_observer.rb
280
280
  - lib/glimmer-dsl-web/samples/hello/hello_observer_data_binding.rb
281
281
  - lib/glimmer-dsl-web/samples/hello/hello_paragraph.rb
282
+ - lib/glimmer-dsl-web/samples/hello/hello_style.rb
282
283
  - lib/glimmer-dsl-web/samples/hello/hello_world.rb
283
284
  - lib/glimmer-dsl-web/samples/regular/button_counter.rb
284
285
  - lib/glimmer-dsl-web/samples/regular/todo_mvc.rb
@@ -314,6 +315,7 @@ files:
314
315
  - lib/glimmer/util/proc_tracker.rb
315
316
  - lib/glimmer/web.rb
316
317
  - lib/glimmer/web/component.rb
318
+ - lib/glimmer/web/component/component_style_container.rb
317
319
  - lib/glimmer/web/element_proxy.rb
318
320
  - lib/glimmer/web/event_proxy.rb
319
321
  - lib/glimmer/web/formatting_element_proxy.rb