glimmer-dsl-web 0.4.0 → 0.4.2

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: b8b106a50bf2ec2656480068685e725017a8b7a32f49d38459416b01c550e9aa
4
- data.tar.gz: ca6c4c2e8028059c5a305c45f181d6f5902e5737d65011dd5ce34b5af334baf0
3
+ metadata.gz: b8629cf007ddb0ff30d296d3c3dbddaf87572953cdbe9ee18128794a2c435441
4
+ data.tar.gz: 4353e741c102e61df81da7352a4b999c487e579b235993a8720ecc3ba7b8ec19
5
5
  SHA512:
6
- metadata.gz: a95444fd3855a6e498e243eb06ffc80e7f25cdbe5ed3b178729450d37f3c528b42919dccddde64caa38e48fcb90b4bb16e42d4f5b3c66425ac2333c5176fe7ad
7
- data.tar.gz: fc781cd899da6a3059b6b5b048c7f324f03d231a755403178996d90eed391cbbcb3c2a214eb6bf0b0e2b1e318560e1b8fded988f998582075b181d7ee74093cb
6
+ metadata.gz: 3c28bcc2664f375e9e7e795f80a104e62d8b73f54e72a6f7d649db92e68da6c3632bf1cede91ae9163a7b29cb4cb573f2bb8988f36bb75ca16be20a551182a34
7
+ data.tar.gz: 4d08a8fcf6b8d6ec2ab61d0e0e940806336c514e2f9ed01179f0b3d0a8299957ce4d0d2677ac5cd0bf3f84d114cfca69fc46592378a9d720ec4f52a6f278be16
data/CHANGELOG.md CHANGED
@@ -1,11 +1,23 @@
1
1
  # Change Log
2
2
 
3
+ ## 0.4.2
4
+
5
+ - Support element inline style data-binding (e.g. `style(:background_color) <= [@button_model, :background_color]`)
6
+ - Support SVG element keywords
7
+ - Hello, SVG! Sample: `require 'glimmer-dsl-web/samples/hello/hello_svg'`
8
+
9
+ ## 0.4.1
10
+
11
+ - Enhance Hello, Style! sample to allow setting styled button background color
12
+ - Fixed issue with `Glimmer::Web::Component::component_element_class` not working in components when programmatically changing the component markup root CSS class
13
+
3
14
  ## 0.4.0
4
15
 
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
16
+ - Support `style {}` block in `Glimmer::Web::Component` that would automatically add style in one place for all component instances
17
+ - Support `Glimmer::Web::Component::component_element_class` to return CSS class automatically generated for a component based on its name (e.g. `StyledButton` gets `styled-button` CSS class)
18
+ - Hello, Style! Sample: `require 'glimmer-dsl-web/samples/hello/hello_style'`
6
19
  - Upgrade to Glimmer DSL for CSS 1.5.0
7
20
  - 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
21
  - Optimize performance of Todo MVC by not adding an edit input field to every todo, yet adding it only upon editing a todo.
10
22
  - Fix issue with `ElementProxy#add_css_class` and `ElementProxy#remove_css_class` crashing if called before rendering an element
11
23
  - Fix issue with Hello, Observer (Data-Binding)! crashing if run after Hello, Observer! due to both samples sharing the same class by mistake
data/README.md CHANGED
@@ -1,5 +1,5 @@
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
- ## Ruby in the Browser Web Frontend Framework
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.2 (Beta)
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)
5
5
  [![Join the chat at https://gitter.im/AndyObtiva/glimmer](https://badges.gitter.im/AndyObtiva/glimmer.svg)](https://gitter.im/AndyObtiva/glimmer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
@@ -8,7 +8,7 @@
8
8
 
9
9
  **(Talk Videos: [Intro to Ruby in the Browser](https://youtu.be/4AdcfbI6A4c?si=MmxOrkhIXTDHQoYi) & [Frontend Ruby with Glimmer DSL for Web](https://youtu.be/rIZ-ILUv9ME?si=raygUXVPd_7ypWuE))**
10
10
 
11
- [![Todo MVC](/images/glimmer-dsl-web-samples-regular-todo-mvc.gif)](/lib/glimmer-dsl-web/samples/regular/todo_mvc.rb)
11
+ [![Todo MVC](/images/glimmer-dsl-web-samples-regular-todo-mvc.gif)](https://sample-glimmer-dsl-web-rails7-app-black-sound-6793.fly.dev/)
12
12
 
13
13
  You can finally have Ruby developer happiness and productivity in the Frontend! No more wasting time splitting your resources across multiple languages, using badly engineered, over-engineered, or premature-optimization-obsessed JavaScript libraries, fighting JavaScript build issues (e.g. webpack), or rewriting Ruby Backend code in Frontend JavaScript. With [Ruby in the Browser](https://www.youtube.com/watch?v=4AdcfbI6A4c), you can have an exponential jump in development productivity (2x or higher), time-to-release (1/2 or less time), cost (1/2 or cheaper), and maintainability (~50% the code that is simpler and more readable) over JavaScript libraries like React, Angular, Ember, Vue, and Svelte, while being able to reuse Backend Ruby code as is in the Frontend for faster interactions when needed. Also, with Frontend Ruby, companies can cut their hiring budget in half by having Backend Ruby Software Engineers do Frontend Development in Ruby! [Ruby in the Browser](https://www.youtube.com/watch?v=4AdcfbI6A4c) finally fulfills every smart highly-productive Rubyist's dream by bringing Ruby productivity fun to Frontend Development, the same productivity fun you had for years and decades in Backend Development.
14
14
 
@@ -1345,6 +1345,7 @@ Learn more about the differences between various [Glimmer](https://github.com/An
1345
1345
  - [Todo MVC](#todo-mvc)
1346
1346
  - [Design Principles](#design-principles)
1347
1347
  - [Supporting Libraries](#supporting-libraries)
1348
+ - [Influences and Inspiration](#influences-and-inspiration)
1348
1349
  - [Glimmer Process](#glimmer-process)
1349
1350
  - [Help](#help)
1350
1351
  - [Issues](#issues)
@@ -1391,7 +1392,7 @@ rails new glimmer_app_server
1391
1392
  Add the following to `Gemfile`:
1392
1393
 
1393
1394
  ```
1394
- gem 'glimmer-dsl-web', '~> 0.4.0'
1395
+ gem 'glimmer-dsl-web', '~> 0.4.2'
1395
1396
  ```
1396
1397
 
1397
1398
  Run:
@@ -1620,7 +1621,7 @@ Disable the `webpacker` gem line in `Gemfile`:
1620
1621
  Add the following to `Gemfile`:
1621
1622
 
1622
1623
  ```ruby
1623
- gem 'glimmer-dsl-web', '~> 0.4.0'
1624
+ gem 'glimmer-dsl-web', '~> 0.4.2'
1624
1625
  ```
1625
1626
 
1626
1627
  Run:
@@ -1881,7 +1882,11 @@ This external Sample Selector app is built using Rails and Glimmer DSL for Web,
1881
1882
 
1882
1883
  https://github.com/AndyObtiva/sample-glimmer-dsl-web-rails7-app
1883
1884
 
1884
- ![Sample Selector](https://raw.githubusercontent.com/AndyObtiva/sample-glimmer-dsl-web-rails7-app/master/sample-glimmer-dsl-web-rails7-app.png)
1885
+ A deployed version of the Sample Selector app can be accessed over here:
1886
+
1887
+ https://sample-glimmer-dsl-web-rails7-app-black-sound-6793.fly.dev/
1888
+
1889
+ [![Sample Selector](https://raw.githubusercontent.com/AndyObtiva/sample-glimmer-dsl-web-rails7-app/master/sample-glimmer-dsl-web-rails7-app.png)](https://sample-glimmer-dsl-web-rails7-app-black-sound-6793.fly.dev/)
1885
1890
 
1886
1891
  ### Hello Samples
1887
1892
 
@@ -3444,6 +3449,15 @@ Here is a list of notable 3rd party gems used by Glimmer DSL for Web:
3444
3449
  - [opal-async](https://github.com/AndyObtiva/opal-async): Non-blocking tasks and enumerators for Web.
3445
3450
  - [to_collection](https://github.com/AndyObtiva/to_collection): Treat an array of objects and a singular object uniformly as a collection of objects.
3446
3451
 
3452
+ ## Influences and Inspiration
3453
+
3454
+ - https://github.com/inesita-rb/inesita
3455
+ - https://github.com/opal/paggio
3456
+ - https://github.com/ruby-hyperloop/hyperloop
3457
+ - https://docs.hyperstack.org/
3458
+ - https://github.com/AndyObtiva/glimmer-dsl-opal
3459
+ - https://github.com/AndyObtiva/glimmer
3460
+
3447
3461
  ## Glimmer Process
3448
3462
 
3449
3463
  [Glimmer Process](https://github.com/AndyObtiva/glimmer/blob/master/PROCESS.md) is the lightweight software development process used for building Glimmer libraries and Glimmer apps, which goes beyond Agile, rendering all Agile processes obsolete. [Glimmer Process](https://github.com/AndyObtiva/glimmer/blob/master/PROCESS.md) is simply made up of 7 guidelines to pick and choose as necessary until software development needs are satisfied.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.0
1
+ 0.4.2
@@ -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.4.0 ruby lib
5
+ # stub: glimmer-dsl-web 0.4.2 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "glimmer-dsl-web".freeze
9
- s.version = "0.4.0"
9
+ s.version = "0.4.2"
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-22"
14
+ s.date = "2024-07-28"
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 = [
@@ -47,6 +47,7 @@ Gem::Specification.new do |s|
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
49
  "lib/glimmer-dsl-web/samples/hello/hello_style.rb",
50
+ "lib/glimmer-dsl-web/samples/hello/hello_svg.rb",
50
51
  "lib/glimmer-dsl-web/samples/hello/hello_world.rb",
51
52
  "lib/glimmer-dsl-web/samples/regular/button_counter.rb",
52
53
  "lib/glimmer-dsl-web/samples/regular/todo_mvc.rb",
@@ -72,12 +73,13 @@ Gem::Specification.new do |s|
72
73
  "lib/glimmer/dsl/web/element_expression.rb",
73
74
  "lib/glimmer/dsl/web/formatting_element_expression.rb",
74
75
  "lib/glimmer/dsl/web/general_element_expression.rb",
76
+ "lib/glimmer/dsl/web/inline_style_data_binding_expression.rb",
75
77
  "lib/glimmer/dsl/web/listener_expression.rb",
76
78
  "lib/glimmer/dsl/web/observe_expression.rb",
77
79
  "lib/glimmer/dsl/web/property_expression.rb",
78
80
  "lib/glimmer/dsl/web/shine_data_binding_expression.rb",
79
81
  "lib/glimmer/dsl/web/span_expression.rb",
80
- "lib/glimmer/dsl/web/style_expression.rb",
82
+ "lib/glimmer/dsl/web/style_element_expression.rb",
81
83
  "lib/glimmer/helpers/glimmer_helper.rb",
82
84
  "lib/glimmer/util/proc_tracker.rb",
83
85
  "lib/glimmer/web.rb",
@@ -97,7 +99,7 @@ Gem::Specification.new do |s|
97
99
 
98
100
  s.add_runtime_dependency(%q<glimmer>.freeze, ["~> 2.8.0"])
99
101
  s.add_runtime_dependency(%q<glimmer-dsl-xml>.freeze, ["~> 1.4.0"])
100
- s.add_runtime_dependency(%q<glimmer-dsl-css>.freeze, ["~> 1.5.0"])
102
+ s.add_runtime_dependency(%q<glimmer-dsl-css>.freeze, ["~> 1.5.1"])
101
103
  s.add_runtime_dependency(%q<opal>.freeze, ["= 1.8.2"])
102
104
  s.add_runtime_dependency(%q<opal-rails>.freeze, ["= 2.0.3"])
103
105
  s.add_runtime_dependency(%q<opal-async>.freeze, ["~> 1.4.1"])
@@ -6,30 +6,46 @@ module Glimmer
6
6
  class ElementBinding
7
7
  include Observable
8
8
  include Observer
9
-
10
- attr_reader :element, :property
11
- def initialize(element, property, translator = nil)
9
+
10
+ attr_reader :element, :property, :translator, :sub_property
11
+ def initialize(element, property, translator: nil)
12
12
  @element = element
13
- @property = property
13
+ if (property_parts = property.to_s.match(Glimmer::Web::ElementProxy::REGEX_STYLE_SUB_PROPERTY))
14
+ @property, @sub_property = property_parts.to_a.drop(1)
15
+ @sub_property = @sub_property.gsub('_', '-')
16
+ else
17
+ @property = property
18
+ end
14
19
  @translator = translator
15
20
 
16
- # TODO implement automatic cleanup upon calling element.remove
17
- # Alternatively, have this be built into ElementProxy and remove this code
18
- # if @element.respond_to?(:dispose)
19
- # @element.on_widget_disposed do |dispose_event|
20
- # unregister_all_observables
21
- # end
22
- # end
21
+ if @element.respond_to?(:remove)
22
+ unregister_handler = lambda { |dispose_event| unregister_all_observables }
23
+ @element.handle_observation_request('on_remove', unregister_handler)
24
+ end
23
25
  end
24
26
 
25
27
  def call(value)
26
28
  evaluated_property_value = evaluate_property
27
29
  converted_value = @translator&.call(value, evaluated_property_value) || value
28
- @element.send("#{@property}=", converted_value) unless converted_value == evaluated_property_value
30
+ unless converted_value == evaluated_property_value
31
+ if @sub_property
32
+ if @property.to_s == 'style'
33
+ @element.style_property(@sub_property, converted_value)
34
+ end
35
+ else
36
+ @element.send("#{@property}=", converted_value)
37
+ end
38
+ end
29
39
  end
30
40
 
31
41
  def evaluate_property
32
- @element.send(@property)
42
+ if @sub_property
43
+ if @property.to_s == 'style'
44
+ @element.style_property(@sub_property)
45
+ end
46
+ else
47
+ @element.send(@property)
48
+ end
33
49
  end
34
50
  end
35
51
  end
@@ -26,7 +26,8 @@ require 'glimmer/dsl/web/listener_expression'
26
26
  require 'glimmer/dsl/web/property_expression'
27
27
  require 'glimmer/dsl/web/a_expression'
28
28
  require 'glimmer/dsl/web/span_expression'
29
- require 'glimmer/dsl/web/style_expression'
29
+ require 'glimmer/dsl/web/style_element_expression'
30
+ require 'glimmer/dsl/web/inline_style_data_binding_expression'
30
31
  require 'glimmer/dsl/web/bind_expression'
31
32
  require 'glimmer/dsl/web/data_binding_expression'
32
33
  require 'glimmer/dsl/web/content_data_binding_expression'
@@ -41,8 +42,9 @@ module Glimmer
41
42
  Web,
42
43
  %w[
43
44
  listener
44
- style
45
+ style_element
45
46
  content_data_binding
47
+ inline_style_data_binding
46
48
  component
47
49
  formatting_element
48
50
  data_binding
@@ -10,7 +10,18 @@ module Glimmer
10
10
  include GeneralElementExpression
11
11
 
12
12
  def can_interpret?(parent, keyword, *args, &block)
13
- Glimmer::Web::ElementProxy.keyword_supported?(keyword)
13
+ Glimmer::Web::ElementProxy.keyword_supported?(keyword) &&
14
+ (
15
+ args.empty? ||
16
+ args.size == 1 && args.first.is_a?(String) ||
17
+ args.size == 1 && args.first.is_a?(Hash) ||
18
+ args.size == 2 && args.first.is_a?(String) && args.last.is_a?(Hash)
19
+ ) &&
20
+ ( # ensure SVG keywords only live under SVG element (unless it's the SVG element itself)
21
+ !Glimmer::Web::ElementProxy.svg_keyword_supported?(keyword) ||
22
+ keyword == 'svg' ||
23
+ parent.find_ancestor(include_self: true) { |ancestor| ancestor.keyword == 'svg' }
24
+ )
14
25
  end
15
26
  end
16
27
  end
@@ -0,0 +1,21 @@
1
+ require 'glimmer/dsl/expression'
2
+
3
+ module Glimmer
4
+ module DSL
5
+ module Web
6
+ class InlineStyleDataBindingExpression < Expression
7
+ def can_interpret?(parent, keyword, *args, &block)
8
+ keyword == 'style' &&
9
+ block.nil? &&
10
+ args.size == 1 &&
11
+ textual?(args.first)
12
+ end
13
+
14
+ def interpret(parent, keyword, *args, &block)
15
+ parent_attribute = "#{keyword}_#{args.first.to_s.underscore}"
16
+ Glimmer::DataBinding::Shine.new(parent, parent_attribute)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -4,7 +4,7 @@ require 'glimmer/dsl/web/general_element_expression'
4
4
  module Glimmer
5
5
  module DSL
6
6
  module Web
7
- class StyleExpression < Expression
7
+ class StyleElementExpression < Expression
8
8
  include GeneralElementExpression
9
9
  include Glimmer
10
10
 
@@ -31,6 +31,14 @@ module Glimmer
31
31
  ELEMENT_KEYWORDS.include?(keyword.to_s)
32
32
  end
33
33
 
34
+ def html_keyword_supported?(keyword)
35
+ HTML_ELEMENT_KEYWORDS.include?(keyword.to_s)
36
+ end
37
+
38
+ def svg_keyword_supported?(keyword)
39
+ SVG_ELEMENT_KEYWORDS.include?(keyword.to_s)
40
+ end
41
+
34
42
  # NOTE: Avoid using this method until we start supporting ElementProxy subclasses
35
43
  # in which case, we must cache them to avoid the slow performance of element_type
36
44
  # Factory Method that translates a Glimmer DSL keyword into a ElementProxy object
@@ -94,7 +102,7 @@ module Glimmer
94
102
 
95
103
  Event = Struct.new(:widget, keyword_init: true)
96
104
 
97
- ELEMENT_KEYWORDS = [
105
+ HTML_ELEMENT_KEYWORDS = [
98
106
  "a", "abbr", "acronym", "address", "applet", "area", "article", "aside", "audio",
99
107
  "base", "basefont", "bdi", "bdo", "bgsound", "big", "blink", "blockquote", "body",
100
108
  "button", "canvas", "caption", "center", "cite", "code", "col", "colgroup", "data",
@@ -110,6 +118,19 @@ module Glimmer
110
118
  "template", "textarea", "tfoot", "th", "thead", "time", "title", "tr", "track", "tt",
111
119
  "u", "ul", "var", "video", "wbr", "xmp",
112
120
  ]
121
+
122
+ SVG_ELEMENT_KEYWORDS = [
123
+ "animate", "animateMotion", "animateTransform", "circle", "clipPath", "defs", "desc", "ellipse",
124
+ "feBlend", "feColorMatrix", "feComponentTransfer", "feComposite", "feConvolveMatrix",
125
+ "feDiffuseLighting", "feDisplacementMap", "feDistantLight", "feDropShadow", "feFlood", "feFuncA",
126
+ "feFuncB", "feFuncG", "feFuncR", "feGaussianBlur", "feImage", "feMerge", "feMergeNode",
127
+ "feMorphology", "feOffset", "fePointLight", "feSpecularLighting", "feSpotLight", "feTile",
128
+ "feTurbulence", "filter", "foreignObject", "g", "image", "line", "linearGradient", "marker",
129
+ "mask", "metadata", "mpath", "path", "pattern", "polygon", "polyline", "radialGradient", "rect",
130
+ "set", "stop", "svg", "switch", "symbol", "text", "textPath", "tspan", "use", "view",
131
+ ].map(&:downcase)
132
+
133
+ ELEMENT_KEYWORDS = HTML_ELEMENT_KEYWORDS + SVG_ELEMENT_KEYWORDS
113
134
 
114
135
  GLIMMER_ATTRIBUTES = [:parent]
115
136
  PROPERTY_ALIASES = {
@@ -122,6 +143,7 @@ module Glimmer
122
143
  REGEX_FORMAT_DATETIME = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/
123
144
  REGEX_FORMAT_DATE = /^\d{4}-\d{2}-\d{2}$/
124
145
  REGEX_FORMAT_TIME = /^\d{2}:\d{2}$/
146
+ REGEX_STYLE_SUB_PROPERTY = /^(style)_(.*)$/
125
147
 
126
148
  attr_reader :keyword, :parent, :parent_component, :component, :args, :options, :children, :enabled, :foreground, :background, :removed, :rendered
127
149
  alias rendered? rendered
@@ -176,6 +198,14 @@ module Glimmer
176
198
  dom_element.attr('class').to_s.split if rendered?
177
199
  end
178
200
 
201
+ def html?
202
+ ElementProxy.html_keyword_supported?(keyword)
203
+ end
204
+
205
+ def svg?
206
+ ElementProxy.svg_keyword_supported?(keyword)
207
+ end
208
+
179
209
  def remove
180
210
  return if @removed
181
211
  on_remove_listeners = listeners_for('on_remove').dup
@@ -233,9 +263,16 @@ module Glimmer
233
263
  end
234
264
  parents_array
235
265
  end
266
+ alias ancestors parents
236
267
 
237
- def dialog_ancestor
238
- parents.detect {|p| p.is_a?(DialogProxy)}
268
+ def find_ancestor(include_self: false, &condition)
269
+ current_element_proxy = self
270
+ return current_element_proxy if include_self && condition.call(current_element_proxy)
271
+ until current_element_proxy.parent.nil?
272
+ current_element_proxy = current_element_proxy.parent
273
+ return current_element_proxy if condition.call(current_element_proxy)
274
+ end
275
+ nil
239
276
  end
240
277
 
241
278
  def print
@@ -272,6 +309,21 @@ module Glimmer
272
309
  end
273
310
  end
274
311
 
312
+ def style_property(property, value = nil)
313
+ if rendered?
314
+ property = property.to_s.gsub('_', '-')
315
+ if value.nil?
316
+ dom_element.css(property)
317
+ else
318
+ dom_element.css(property, value)
319
+ end
320
+ else
321
+ enqueue_args = ['style_property', property]
322
+ enqueue_args << value unless value.nil?
323
+ enqueue_post_render_method_call(*enqueue_args)
324
+ end
325
+ end
326
+
275
327
  def parent_selector
276
328
  @parent&.selector
277
329
  end
@@ -372,12 +424,7 @@ module Glimmer
372
424
  end
373
425
 
374
426
  def html_options
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(' ')
427
+ body_class = (base_css_classes + css_classes.to_a).uniq.compact.join(' ')
381
428
  html_options = options.dup
382
429
  GLIMMER_ATTRIBUTES.each do |attribute|
383
430
  next unless html_options.include?(attribute)
@@ -416,8 +463,8 @@ module Glimmer
416
463
 
417
464
  def class_name=(value)
418
465
  if rendered?
419
- value = value.is_a?(Array) ? value.join(' ') : value.to_s
420
- new_class_name = "#{name} #{element_id} #{value}"
466
+ values = value.is_a?(Array) ? value : [value.to_s]
467
+ new_class_name = (base_css_classes + values).uniq.compact.join(' ')
421
468
  dom_element.prop('className', new_class_name)
422
469
  else
423
470
  enqueue_post_render_method_call('class_name=', value)
@@ -555,13 +602,12 @@ module Glimmer
555
602
 
556
603
  def data_bind(property, model_binding)
557
604
  element_binding_read_translator = value_converters_for_input_type(type)&.[](:model_to_view)
558
- element_binding_parameters = [self, property, element_binding_read_translator]
559
- element_binding = DataBinding::ElementBinding.new(*element_binding_parameters)
605
+ element_binding = DataBinding::ElementBinding.new(self, property, translator: element_binding_read_translator)
560
606
  #TODO make this options observer dependent and all similar observers in element specific data binding handlers
561
607
  element_binding.observe(model_binding)
562
608
  element_binding.call(model_binding.evaluate_property)
563
609
  data_bindings[element_binding] = model_binding
564
- unless model_binding.binding_options[:read_only]
610
+ if !model_binding.binding_options[:read_only]
565
611
  # TODO add guards against nil cases for hash below
566
612
  listener_keyword = data_binding_listener_for_element_and_property(keyword, property)
567
613
  if listener_keyword
@@ -610,14 +656,23 @@ module Glimmer
610
656
  dom_element.respond_to?(method_name, include_private) ||
611
657
  (!dom_element.prop(property_name).nil? && !dom_element.prop(property_name).is_a?(Proc)) ||
612
658
  (!dom_element.prop(unnormalized_property_name).nil? && !dom_element.prop(unnormalized_property_name).is_a?(Proc)) ||
613
- method_name.to_s.start_with?('on_')
659
+ method_name.to_s.start_with?('on_') ||
660
+ method_name.to_s.start_with?('style_')
614
661
  end
615
662
 
616
663
  def method_missing(method_name, *args, &block)
617
664
  # TODO consider doing more correct checking of availability of properties/methods using native ticks
618
665
  property_name = property_name_for(method_name)
619
666
  unnormalized_property_name = unnormalized_property_name_for(method_name)
620
- if method_name.to_s.start_with?('on_')
667
+ if method_name.to_s.start_with?('style_')
668
+ property, sub_property = method_name.to_s.match(REGEX_STYLE_SUB_PROPERTY).to_a.drop(1)
669
+ sub_property = sub_property.gsub('_', '-')
670
+ if args.empty?
671
+ style_property(sub_property)
672
+ else
673
+ style_property(sub_property, args.first) # TODO in the future, support more sophisticated forms of CSS sub-property values, like [1.px, :solid, :black] for border
674
+ end
675
+ elsif method_name.to_s.start_with?('on_')
621
676
  handle_observation_request(method_name, block)
622
677
  elsif dom_element.respond_to?(method_name)
623
678
  if rendered?
@@ -831,6 +886,15 @@ module Glimmer
831
886
 
832
887
  private
833
888
 
889
+ def base_css_classes
890
+ framework_css_classes = [name, element_id]
891
+ if component
892
+ framework_css_classes.prepend(component.class.component_element_class)
893
+ framework_css_classes.prepend(component.class.component_shortcut_element_class) if component.class.component_shortcut_element_class != component.class.component_element_class
894
+ end
895
+ framework_css_classes
896
+ end
897
+
834
898
  def valid_js_date_string?(string)
835
899
  [REGEX_FORMAT_DATETIME, REGEX_FORMAT_DATE, REGEX_FORMAT_TIME].any? do |format|
836
900
  string.match(format)
@@ -29,13 +29,14 @@ class ButtonModel
29
29
  FONT_SIZE_MIN = 40
30
30
  FONT_SIZE_MAX = 200
31
31
 
32
- attr_accessor :text, :pushed, :width, :height, :font_size
32
+ attr_accessor :text, :pushed, :background_color, :width, :height, :font_size
33
33
 
34
34
  def initialize
35
35
  @text = 'Push'
36
36
  @width = WIDTH_MIN
37
37
  @height = HEIGHT_MIN
38
38
  @font_size = FONT_SIZE_MIN
39
+ @background_color = '#add8e6'
39
40
  end
40
41
 
41
42
  def push
@@ -61,6 +62,16 @@ class ButtonModel
61
62
  self.width = @font_size*4 if @height < @font_size*4
62
63
  self.height = @font_size*2.5 if @height < @font_size*2.5
63
64
  end
65
+
66
+ def border_color
67
+ red = background_color[1..2].hex
68
+ green = background_color[3..4].hex
69
+ blue = background_color[5..6].hex
70
+ new_red = red - 10
71
+ new_green = green - 10
72
+ new_blue = blue - 10
73
+ "##{new_red.to_s(16)}#{new_green.to_s(16)}#{new_blue.to_s(16)}"
74
+ end
64
75
  end
65
76
 
66
77
  class StyledButton
@@ -76,17 +87,11 @@ class StyledButton
76
87
  on_read: ->(pushed) { pushed ? 'pushed' : 'pulled' }
77
88
  ]
78
89
 
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
+ style(:width) <= [button_model, :width, on_read: :px]
91
+ style(:height) <= [button_model, :height, on_read: :px]
92
+ style(:font_size) <= [button_model, :font_size, on_read: :px]
93
+ style(:background_color) <= [button_model, :background_color]
94
+ style(:border_color) <= [button_model, :border_color, computed_by: :background_color]
90
95
 
91
96
  onclick do
92
97
  button_model.push
@@ -94,32 +99,22 @@ class StyledButton
94
99
  }
95
100
  }
96
101
 
97
- style {'
98
- button {
102
+ style {"
103
+ .#{component_element_class} {
99
104
  font-family: Courrier New, Courrier;
100
105
  border-radius: 5px;
101
106
  border-width: 17px;
102
- border-color: #ACC7D5;
103
- background-color: #ADD8E6;
104
107
  margin: 5px;
105
108
  }
106
109
 
107
- button.pulled {
110
+ .#{component_element_class}.pulled {
108
111
  border-style: outset;
109
112
  }
110
113
 
111
- button.pushed {
114
+ .#{component_element_class}.pushed {
112
115
  border-style: inset;
113
116
  }
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
117
+ "}
123
118
  end
124
119
 
125
120
  class StyledButtonRangeInput
@@ -137,6 +132,19 @@ class StyledButtonRangeInput
137
132
  }
138
133
  end
139
134
 
135
+ class StyledButtonColorInput
136
+ include Glimmer::Web::Component
137
+
138
+ option :button_model
139
+ option :property
140
+
141
+ markup {
142
+ input(type: 'color') {
143
+ value <=> [button_model, property]
144
+ }
145
+ }
146
+ end
147
+
140
148
  class HelloStyle
141
149
  include Glimmer::Web::Component
142
150
 
@@ -145,32 +153,41 @@ class HelloStyle
145
153
  end
146
154
 
147
155
  markup {
148
- div(class: 'hello-style') {
149
- div(class: 'form-row') {
150
- label('Styled Button Width:', for: 'styled-button-width-input')
156
+ div {
157
+ div(class: 'styled-button-form') {
158
+ label('Styled Button Width:', class: 'property-label', for: 'styled-button-width-input')
151
159
  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')
160
+
161
+ label('Styled Button Height:', class: 'property-label', for: 'styled-button-height-input')
155
162
  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')
163
+
164
+ label('Styled Button Font Size:', class: 'property-label', for: 'styled-button-font-size-input')
159
165
  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')
166
+
167
+ label('Styled Button Background Color:', for: 'styled-button-background-color-input')
168
+ styled_button_color_input(button_model: @button_model, property: :background_color, id: 'styled-button-background-color-input')
160
169
  }
170
+
161
171
  styled_button(button_model: @button_model)
162
172
  }
163
173
  }
164
174
 
165
- style {'
166
- .hello-style {
175
+ style {"
176
+ .styled-button-form {
167
177
  padding: 20px;
178
+ display: inline-grid;
179
+ grid-template-columns: auto auto;
180
+ }
181
+
182
+ .styled-button-form label, input {
183
+ display: block;
184
+ margin: 5px 5px 5px 0;
168
185
  }
169
186
 
170
- .hello-style .form-row {
171
- margin: 10px 0;
187
+ .#{component_element_class} .styled-button {
188
+ display: block;
172
189
  }
173
- '}
190
+ "}
174
191
  end
175
192
 
176
193
  Document.ready? do
@@ -0,0 +1,45 @@
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 HelloSvg
25
+ include Glimmer::Web::Component
26
+
27
+ markup {
28
+ div {
29
+ svg(width: '100%', height: '100') {
30
+ circle(cx: '50', cy: '50', r: '50', style: 'fill:blue;') {
31
+ animate(attributename: 'cx', begin: '0s', dur: '8s', from: '50', to: '90%', repeatcount: 'indefinite')
32
+ }
33
+ }
34
+ svg(width: '200', height: '180') {
35
+ rect(x: '30', y: '30', height: '110', width: '110', style: 'stroke:green;fill:red') {
36
+ animatetransform(attributename: 'transform', begin: '0.1s', dur: '10s', type: 'rotate', from: '0 85 85', to: '360 85 85', repeatcount: 'indefinite')
37
+ }
38
+ }
39
+ }
40
+ }
41
+ end
42
+
43
+ Document.ready? do
44
+ HelloSvg.render
45
+ end
@@ -3,7 +3,7 @@ require_relative 'todo_input'
3
3
  class NewTodoInput < TodoInput
4
4
  option :presenter
5
5
 
6
- markup { # evaluated against instance as a smart default convention
6
+ markup { # evaluated against instance as a smart convention
7
7
  input(placeholder: "What needs to be done?", autofocus: "") {
8
8
  value <=> [presenter.new_todo, :task]
9
9
 
@@ -13,10 +13,10 @@ class NewTodoInput < TodoInput
13
13
  }
14
14
  }
15
15
 
16
- style { # evaluated against class as a smart default convention (common to all instances)
16
+ style { # evaluated against class as a smart convention (common to all instances)
17
17
  todo_input_styles
18
18
 
19
- rule(".#{component_element_class}") { # built-in component_class.component_element_class (e.g. NewTodoInput has CSS class as new-todo-input)
19
+ rule(".#{component_element_class}") { # NewTodoInput has component_element_class as 'new-todo-input'
20
20
  padding '16px 16px 16px 60px'
21
21
  height '65px'
22
22
  border 'none'
@@ -24,7 +24,7 @@ class NewTodoInput < TodoInput
24
24
  box_shadow 'inset 0 -2px 1px rgba(0,0,0,0.03)'
25
25
  }
26
26
 
27
- rule(".#{component_element_class}::placeholder") { # built-in component_class.component_element_class (e.g. NewTodoInput has CSS class as new-todo-input)
27
+ rule(".#{component_element_class}::placeholder") {
28
28
  font_style 'italic'
29
29
  font_weight '400'
30
30
  color 'rgba(0, 0, 0, 0.4)'
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.4.0
4
+ version: 0.4.2
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-22 00:00:00.000000000 Z
11
+ date: 2024-07-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: glimmer
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 1.5.0
47
+ version: 1.5.1
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.5.0
54
+ version: 1.5.1
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: opal
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -280,6 +280,7 @@ files:
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
282
  - lib/glimmer-dsl-web/samples/hello/hello_style.rb
283
+ - lib/glimmer-dsl-web/samples/hello/hello_svg.rb
283
284
  - lib/glimmer-dsl-web/samples/hello/hello_world.rb
284
285
  - lib/glimmer-dsl-web/samples/regular/button_counter.rb
285
286
  - lib/glimmer-dsl-web/samples/regular/todo_mvc.rb
@@ -305,12 +306,13 @@ files:
305
306
  - lib/glimmer/dsl/web/element_expression.rb
306
307
  - lib/glimmer/dsl/web/formatting_element_expression.rb
307
308
  - lib/glimmer/dsl/web/general_element_expression.rb
309
+ - lib/glimmer/dsl/web/inline_style_data_binding_expression.rb
308
310
  - lib/glimmer/dsl/web/listener_expression.rb
309
311
  - lib/glimmer/dsl/web/observe_expression.rb
310
312
  - lib/glimmer/dsl/web/property_expression.rb
311
313
  - lib/glimmer/dsl/web/shine_data_binding_expression.rb
312
314
  - lib/glimmer/dsl/web/span_expression.rb
313
- - lib/glimmer/dsl/web/style_expression.rb
315
+ - lib/glimmer/dsl/web/style_element_expression.rb
314
316
  - lib/glimmer/helpers/glimmer_helper.rb
315
317
  - lib/glimmer/util/proc_tracker.rb
316
318
  - lib/glimmer/web.rb