glimmer-dsl-web 0.4.1 → 0.4.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 +4 -4
- data/CHANGELOG.md +13 -0
- data/README.md +293 -56
- data/VERSION +1 -1
- data/glimmer-dsl-web.gemspec +8 -5
- data/lib/glimmer/data_binding/element_binding.rb +35 -13
- data/lib/glimmer/dsl/web/class_name_inclusion_data_binding.rb +21 -0
- data/lib/glimmer/dsl/web/dsl.rb +6 -2
- data/lib/glimmer/dsl/web/element_expression.rb +12 -1
- data/lib/glimmer/dsl/web/inline_style_data_binding_expression.rb +21 -0
- data/lib/glimmer/dsl/web/{style_expression.rb → style_element_expression.rb} +1 -1
- data/lib/glimmer/web/component.rb +8 -2
- data/lib/glimmer/web/element_proxy.rb +95 -8
- data/lib/glimmer-dsl-web/samples/hello/hello_style.rb +30 -42
- data/lib/glimmer-dsl-web/samples/hello/hello_svg.rb +45 -0
- data/lib/glimmer-dsl-web/samples/regular/todo_mvc/views/edit_todo_input.rb +19 -10
- data/lib/glimmer-dsl-web/samples/regular/todo_mvc/views/new_todo_form.rb +8 -8
- data/lib/glimmer-dsl-web/samples/regular/todo_mvc/views/new_todo_input.rb +8 -5
- data/lib/glimmer-dsl-web/samples/regular/todo_mvc/views/todo_filters.rb +64 -51
- data/lib/glimmer-dsl-web/samples/regular/todo_mvc/views/todo_input.rb +14 -14
- data/lib/glimmer-dsl-web/samples/regular/todo_mvc/views/todo_list.rb +33 -37
- data/lib/glimmer-dsl-web/samples/regular/todo_mvc/views/todo_list_item.rb +62 -59
- data/lib/glimmer-dsl-web/samples/regular/todo_mvc/views/todo_mvc_footer.rb +9 -9
- data/lib/glimmer-dsl-web/samples/regular/todo_mvc.rb +20 -20
- metadata +8 -5
data/lib/glimmer/dsl/web/dsl.rb
CHANGED
@@ -26,7 +26,9 @@ 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/
|
29
|
+
require 'glimmer/dsl/web/style_element_expression'
|
30
|
+
require 'glimmer/dsl/web/inline_style_data_binding_expression'
|
31
|
+
require 'glimmer/dsl/web/class_name_inclusion_data_binding'
|
30
32
|
require 'glimmer/dsl/web/bind_expression'
|
31
33
|
require 'glimmer/dsl/web/data_binding_expression'
|
32
34
|
require 'glimmer/dsl/web/content_data_binding_expression'
|
@@ -41,8 +43,10 @@ module Glimmer
|
|
41
43
|
Web,
|
42
44
|
%w[
|
43
45
|
listener
|
44
|
-
|
46
|
+
style_element
|
45
47
|
content_data_binding
|
48
|
+
inline_style_data_binding
|
49
|
+
class_name_inclusion_data_binding
|
46
50
|
component
|
47
51
|
formatting_element
|
48
52
|
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
|
@@ -102,6 +102,11 @@ module Glimmer
|
|
102
102
|
self.keyword.gsub('_', '-')
|
103
103
|
end
|
104
104
|
|
105
|
+
def component_element_selector
|
106
|
+
".#{component_element_class}"
|
107
|
+
end
|
108
|
+
alias component_markup_root_selector component_element_selector
|
109
|
+
|
105
110
|
def component_shortcut_element_class
|
106
111
|
self.shortcut_keyword.gsub('_', '-')
|
107
112
|
end
|
@@ -203,7 +208,8 @@ module Glimmer
|
|
203
208
|
end
|
204
209
|
|
205
210
|
def remove_component_style(component)
|
206
|
-
|
211
|
+
# We must not remove the head style element until all components are removed of a component class
|
212
|
+
if Glimmer::Web::Component.component_count(component.class) == 0 && Glimmer::Web::Component.any_component_style?(component.class)
|
207
213
|
# TODO in the future, you would need to remove style using a jQuery call if you created head element in bulk
|
208
214
|
Glimmer::Web::Component.component_styles[component.class].remove
|
209
215
|
Glimmer::Web::Component.component_styles.delete(component.class)
|
@@ -219,7 +225,7 @@ module Glimmer
|
|
219
225
|
end
|
220
226
|
|
221
227
|
def component_count(component_class)
|
222
|
-
component_class_to_components_map[component_class]
|
228
|
+
component_class_to_components_map[component_class]&.size || 0
|
223
229
|
end
|
224
230
|
|
225
231
|
def components
|
@@ -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
|
-
|
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,8 @@ 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)_(.*)$/
|
147
|
+
REGEX_CLASS_NAME_SUB_PROPERTY = /^(class_name)_(.*)$/
|
125
148
|
|
126
149
|
attr_reader :keyword, :parent, :parent_component, :component, :args, :options, :children, :enabled, :foreground, :background, :removed, :rendered
|
127
150
|
alias rendered? rendered
|
@@ -176,6 +199,14 @@ module Glimmer
|
|
176
199
|
dom_element.attr('class').to_s.split if rendered?
|
177
200
|
end
|
178
201
|
|
202
|
+
def html?
|
203
|
+
ElementProxy.html_keyword_supported?(keyword)
|
204
|
+
end
|
205
|
+
|
206
|
+
def svg?
|
207
|
+
ElementProxy.svg_keyword_supported?(keyword)
|
208
|
+
end
|
209
|
+
|
179
210
|
def remove
|
180
211
|
return if @removed
|
181
212
|
on_remove_listeners = listeners_for('on_remove').dup
|
@@ -233,9 +264,16 @@ module Glimmer
|
|
233
264
|
end
|
234
265
|
parents_array
|
235
266
|
end
|
267
|
+
alias ancestors parents
|
236
268
|
|
237
|
-
def
|
238
|
-
|
269
|
+
def find_ancestor(include_self: false, &condition)
|
270
|
+
current_element_proxy = self
|
271
|
+
return current_element_proxy if include_self && condition.call(current_element_proxy)
|
272
|
+
until current_element_proxy.parent.nil?
|
273
|
+
current_element_proxy = current_element_proxy.parent
|
274
|
+
return current_element_proxy if condition.call(current_element_proxy)
|
275
|
+
end
|
276
|
+
nil
|
239
277
|
end
|
240
278
|
|
241
279
|
def print
|
@@ -272,6 +310,39 @@ module Glimmer
|
|
272
310
|
end
|
273
311
|
end
|
274
312
|
|
313
|
+
def class_name_included(one_class_name, value = nil)
|
314
|
+
if rendered?
|
315
|
+
if value.nil?
|
316
|
+
class_name.include?(one_class_name)
|
317
|
+
else
|
318
|
+
if value
|
319
|
+
add_css_class(one_class_name)
|
320
|
+
else
|
321
|
+
remove_css_class(one_class_name)
|
322
|
+
end
|
323
|
+
end
|
324
|
+
else
|
325
|
+
enqueue_args = ['class_name_included', one_class_name]
|
326
|
+
enqueue_args << value unless value.nil?
|
327
|
+
enqueue_post_render_method_call(*enqueue_args)
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
def style_property(property, value = nil)
|
332
|
+
if rendered?
|
333
|
+
property = property.to_s.gsub('_', '-')
|
334
|
+
if value.nil?
|
335
|
+
dom_element.css(property)
|
336
|
+
else
|
337
|
+
dom_element.css(property, value)
|
338
|
+
end
|
339
|
+
else
|
340
|
+
enqueue_args = ['style_property', property]
|
341
|
+
enqueue_args << value unless value.nil?
|
342
|
+
enqueue_post_render_method_call(*enqueue_args)
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
275
346
|
def parent_selector
|
276
347
|
@parent&.selector
|
277
348
|
end
|
@@ -550,13 +621,12 @@ module Glimmer
|
|
550
621
|
|
551
622
|
def data_bind(property, model_binding)
|
552
623
|
element_binding_read_translator = value_converters_for_input_type(type)&.[](:model_to_view)
|
553
|
-
|
554
|
-
element_binding = DataBinding::ElementBinding.new(*element_binding_parameters)
|
624
|
+
element_binding = DataBinding::ElementBinding.new(self, property, translator: element_binding_read_translator)
|
555
625
|
#TODO make this options observer dependent and all similar observers in element specific data binding handlers
|
556
626
|
element_binding.observe(model_binding)
|
557
627
|
element_binding.call(model_binding.evaluate_property)
|
558
628
|
data_bindings[element_binding] = model_binding
|
559
|
-
|
629
|
+
if !model_binding.binding_options[:read_only]
|
560
630
|
# TODO add guards against nil cases for hash below
|
561
631
|
listener_keyword = data_binding_listener_for_element_and_property(keyword, property)
|
562
632
|
if listener_keyword
|
@@ -605,14 +675,31 @@ module Glimmer
|
|
605
675
|
dom_element.respond_to?(method_name, include_private) ||
|
606
676
|
(!dom_element.prop(property_name).nil? && !dom_element.prop(property_name).is_a?(Proc)) ||
|
607
677
|
(!dom_element.prop(unnormalized_property_name).nil? && !dom_element.prop(unnormalized_property_name).is_a?(Proc)) ||
|
608
|
-
method_name.to_s.start_with?('on_')
|
678
|
+
method_name.to_s.start_with?('on_') ||
|
679
|
+
method_name.to_s.start_with?('style_') ||
|
680
|
+
method_name.to_s.start_with?('class_name_')
|
609
681
|
end
|
610
682
|
|
611
683
|
def method_missing(method_name, *args, &block)
|
612
684
|
# TODO consider doing more correct checking of availability of properties/methods using native ticks
|
613
685
|
property_name = property_name_for(method_name)
|
614
686
|
unnormalized_property_name = unnormalized_property_name_for(method_name)
|
615
|
-
if method_name.to_s.start_with?('
|
687
|
+
if method_name.to_s.start_with?('class_name_')
|
688
|
+
property, sub_property = method_name.to_s.match(REGEX_CLASS_NAME_SUB_PROPERTY).to_a.drop(1)
|
689
|
+
if args.empty?
|
690
|
+
class_name_included(sub_property)
|
691
|
+
else
|
692
|
+
class_name_included(sub_property, args.first)
|
693
|
+
end
|
694
|
+
elsif method_name.to_s.start_with?('style_')
|
695
|
+
property, sub_property = method_name.to_s.match(REGEX_STYLE_SUB_PROPERTY).to_a.drop(1)
|
696
|
+
sub_property = sub_property.gsub('_', '-')
|
697
|
+
if args.empty?
|
698
|
+
style_property(sub_property)
|
699
|
+
else
|
700
|
+
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
|
701
|
+
end
|
702
|
+
elsif method_name.to_s.start_with?('on_')
|
616
703
|
handle_observation_request(method_name, block)
|
617
704
|
elsif dom_element.respond_to?(method_name)
|
618
705
|
if rendered?
|
@@ -22,7 +22,6 @@
|
|
22
22
|
require 'glimmer-dsl-web'
|
23
23
|
|
24
24
|
class ButtonModel
|
25
|
-
BUTTON_STYLE_ATTRIBUTES = [:width, :height, :font_size, :background_color]
|
26
25
|
WIDTH_MIN = 160
|
27
26
|
WIDTH_MAX = 960
|
28
27
|
HEIGHT_MIN = 100
|
@@ -30,7 +29,7 @@ class ButtonModel
|
|
30
29
|
FONT_SIZE_MIN = 40
|
31
30
|
FONT_SIZE_MAX = 200
|
32
31
|
|
33
|
-
attr_accessor :text, :pushed,
|
32
|
+
attr_accessor :text, :pushed, :background_color, :width, :height, :font_size
|
34
33
|
|
35
34
|
def initialize
|
36
35
|
@text = 'Push'
|
@@ -84,15 +83,14 @@ class StyledButton
|
|
84
83
|
button {
|
85
84
|
inner_text <= [button_model, :text, computed_by: :pushed]
|
86
85
|
|
87
|
-
class_name <= [button_model, :pushed
|
88
|
-
|
89
|
-
]
|
86
|
+
class_name(:pushed) <= [button_model, :pushed]
|
87
|
+
class_name(:pulled) <= [button_model, :pushed, on_read: :!]
|
90
88
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
89
|
+
style(:width) <= [button_model, :width, on_read: :px]
|
90
|
+
style(:height) <= [button_model, :height, on_read: :px]
|
91
|
+
style(:font_size) <= [button_model, :font_size, on_read: :px]
|
92
|
+
style(:background_color) <= [button_model, :background_color]
|
93
|
+
style(:border_color) <= [button_model, :border_color, computed_by: :background_color]
|
96
94
|
|
97
95
|
onclick do
|
98
96
|
button_model.push
|
@@ -100,32 +98,22 @@ class StyledButton
|
|
100
98
|
}
|
101
99
|
}
|
102
100
|
|
103
|
-
style {
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
margin
|
101
|
+
style {
|
102
|
+
r(component_element_selector) {
|
103
|
+
font_family 'Courrier New, Courrier'
|
104
|
+
border_radius 5
|
105
|
+
border_width 17
|
106
|
+
margin 5
|
109
107
|
}
|
110
108
|
|
111
|
-
|
112
|
-
|
109
|
+
r("#{component_element_selector}.pulled") {
|
110
|
+
border_style :outset
|
113
111
|
}
|
114
112
|
|
115
|
-
|
116
|
-
|
113
|
+
r("#{component_element_selector}.pushed") {
|
114
|
+
border_style :inset
|
117
115
|
}
|
118
|
-
|
119
|
-
|
120
|
-
def button_style_value
|
121
|
-
"
|
122
|
-
width: #{button_model.width}px;
|
123
|
-
height: #{button_model.height}px;
|
124
|
-
font-size: #{button_model.font_size}px;
|
125
|
-
background-color: #{button_model.background_color};
|
126
|
-
border-color: #{button_model.border_color};
|
127
|
-
"
|
128
|
-
end
|
116
|
+
}
|
129
117
|
end
|
130
118
|
|
131
119
|
class StyledButtonRangeInput
|
@@ -183,22 +171,22 @@ class HelloStyle
|
|
183
171
|
}
|
184
172
|
}
|
185
173
|
|
186
|
-
style {
|
187
|
-
.styled-button-form {
|
188
|
-
padding
|
189
|
-
display
|
190
|
-
|
174
|
+
style {
|
175
|
+
r('.styled-button-form') {
|
176
|
+
padding 20
|
177
|
+
display 'inline-grid'
|
178
|
+
grid_template_columns 'auto auto'
|
191
179
|
}
|
192
180
|
|
193
|
-
.styled-button-form label, input {
|
194
|
-
display:
|
195
|
-
margin
|
181
|
+
r('.styled-button-form label, input') {
|
182
|
+
display :block
|
183
|
+
margin '5px 5px 5px 0'
|
196
184
|
}
|
197
185
|
|
198
|
-
|
199
|
-
display:
|
186
|
+
r("#{component_element_selector} .styled-button") {
|
187
|
+
display :block
|
200
188
|
}
|
201
|
-
|
189
|
+
}
|
202
190
|
end
|
203
191
|
|
204
192
|
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
|
@@ -6,11 +6,16 @@ class EditTodoInput < TodoInput
|
|
6
6
|
|
7
7
|
markup { # evaluated against instance as a smart default convention
|
8
8
|
input { |edit_input|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
9
|
+
# Data-bind inclusion of `li` `class` `editing` unidirectionally to todo editing attribute,
|
10
|
+
# meaning inclusion of editing class is determined by todo editing boolean attribute.
|
11
|
+
# `after_read` hook will have `input` grab keyboard focus when editing todo.
|
12
|
+
class_name(:editing) <= [ todo, :editing,
|
13
|
+
after_read: -> { edit_input.focus if todo.editing? }
|
14
|
+
]
|
13
15
|
|
16
|
+
# Data-bind `input` `value` property bidirectionally to `todo` `task` attribute
|
17
|
+
# meaning make any changes to the `todo` `task` attribute value automatically update the `input` `value` property
|
18
|
+
# and any changes to the `input` `value` property by the user automatically update the `todo` `task` attribute value.
|
14
19
|
value <=> [todo, :task]
|
15
20
|
|
16
21
|
onkeyup do |event|
|
@@ -31,17 +36,21 @@ class EditTodoInput < TodoInput
|
|
31
36
|
style { # evaluated against class as a smart default convention (common to all instances)
|
32
37
|
todo_input_styles
|
33
38
|
|
34
|
-
|
35
|
-
position
|
39
|
+
r("*:has(> #{component_element_selector})") {
|
40
|
+
position :relative
|
36
41
|
}
|
37
42
|
|
38
|
-
|
39
|
-
position
|
40
|
-
display
|
43
|
+
r(component_element_selector) {
|
44
|
+
position :absolute
|
45
|
+
display :none
|
41
46
|
width 'calc(100% - 43px)'
|
42
47
|
padding '12px 16px'
|
43
48
|
margin '0 0 0 43px'
|
44
|
-
top
|
49
|
+
top 0
|
50
|
+
}
|
51
|
+
|
52
|
+
r("#{component_element_selector}.editing") {
|
53
|
+
display :block
|
45
54
|
}
|
46
55
|
}
|
47
56
|
end
|
@@ -14,16 +14,16 @@ class NewTodoForm
|
|
14
14
|
}
|
15
15
|
|
16
16
|
style {
|
17
|
-
|
17
|
+
r('.header h1') {
|
18
18
|
color '#b83f45'
|
19
|
-
font_size
|
19
|
+
font_size 80
|
20
20
|
font_weight '200'
|
21
|
-
position
|
22
|
-
text_align
|
23
|
-
_webkit_text_rendering
|
24
|
-
_moz_text_rendering
|
25
|
-
text_rendering
|
26
|
-
top
|
21
|
+
position :absolute
|
22
|
+
text_align :center
|
23
|
+
_webkit_text_rendering :optimizeLegibility
|
24
|
+
_moz_text_rendering :optimizeLegibility
|
25
|
+
text_rendering :optimizeLegibility
|
26
|
+
top -140
|
27
27
|
width '100%'
|
28
28
|
}
|
29
29
|
}
|
@@ -5,6 +5,9 @@ class NewTodoInput < TodoInput
|
|
5
5
|
|
6
6
|
markup { # evaluated against instance as a smart convention
|
7
7
|
input(placeholder: "What needs to be done?", autofocus: "") {
|
8
|
+
# Data-bind `input` `value` property bidirectionally to `presenter.new_todo` `task` attribute
|
9
|
+
# meaning make any changes to the new todo task automatically update the input value
|
10
|
+
# and any changes to the input value by the user automatically update the new todo task value
|
8
11
|
value <=> [presenter.new_todo, :task]
|
9
12
|
|
10
13
|
onkeyup do |event|
|
@@ -16,16 +19,16 @@ class NewTodoInput < TodoInput
|
|
16
19
|
style { # evaluated against class as a smart convention (common to all instances)
|
17
20
|
todo_input_styles
|
18
21
|
|
19
|
-
|
22
|
+
r(component_element_selector) { # NewTodoInput has component_element_class as 'new-todo-input'
|
20
23
|
padding '16px 16px 16px 60px'
|
21
|
-
height
|
22
|
-
border
|
24
|
+
height 65
|
25
|
+
border :none
|
23
26
|
background 'rgba(0, 0, 0, 0.003)'
|
24
27
|
box_shadow 'inset 0 -2px 1px rgba(0,0,0,0.03)'
|
25
28
|
}
|
26
29
|
|
27
|
-
|
28
|
-
font_style
|
30
|
+
r("#{component_element_selector}::placeholder") {
|
31
|
+
font_style :italic
|
29
32
|
font_weight '400'
|
30
33
|
color 'rgba(0, 0, 0, 0.4)'
|
31
34
|
}
|