hyalite 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +21 -0
- data/README.md +46 -0
- data/Rakefile +7 -0
- data/client/hyalite.rb +98 -0
- data/client/hyalite/adler32.rb +33 -0
- data/client/hyalite/browser_event.rb +201 -0
- data/client/hyalite/callback_queue.rb +22 -0
- data/client/hyalite/component.rb +77 -0
- data/client/hyalite/composite_component.rb +237 -0
- data/client/hyalite/dom_component.rb +329 -0
- data/client/hyalite/dom_operations.rb +17 -0
- data/client/hyalite/dom_property.rb +218 -0
- data/client/hyalite/dom_property_operations.rb +117 -0
- data/client/hyalite/element.rb +17 -0
- data/client/hyalite/event_dispatcher.rb +99 -0
- data/client/hyalite/event_plugin/change_event_plugin.rb +83 -0
- data/client/hyalite/event_plugin/event_plugin_registry.rb +49 -0
- data/client/hyalite/event_plugin/simple_event_plugin.rb +276 -0
- data/client/hyalite/input_wrapper.rb +94 -0
- data/client/hyalite/instance_handles.rb +78 -0
- data/client/hyalite/internal_component.rb +11 -0
- data/client/hyalite/linked_value_utils.rb +27 -0
- data/client/hyalite/mount.rb +285 -0
- data/client/hyalite/multi_children.rb +272 -0
- data/client/hyalite/reconcile_transaction.rb +41 -0
- data/client/hyalite/reconciler.rb +122 -0
- data/client/hyalite/short_hand.rb +23 -0
- data/client/hyalite/synthetic_event.rb +18 -0
- data/client/hyalite/text_component.rb +42 -0
- data/client/hyalite/transaction.rb +47 -0
- data/client/hyalite/try.rb +7 -0
- data/client/hyalite/update_queue.rb +52 -0
- data/client/hyalite/updates.rb +129 -0
- data/client/hyalite/utils.rb +19 -0
- data/example/Gemfile +5 -0
- data/example/Gemfile.lock +46 -0
- data/example/app/application.rb +27 -0
- data/example/config.ru +12 -0
- data/example/index.html.haml +8 -0
- data/hyalite.gemspec +27 -0
- data/lib/hyalite.rb +6 -0
- data/lib/hyalite/main.rb +5 -0
- data/lib/hyalite/version.rb +3 -0
- metadata +145 -0
@@ -0,0 +1,83 @@
|
|
1
|
+
class Browser::DOM::Element
|
2
|
+
def input_type
|
3
|
+
`self.native.type`
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
module Hyalite
|
8
|
+
class ChangeEventPlugin
|
9
|
+
EVENT_TYPES = {
|
10
|
+
change: {
|
11
|
+
phasedRegistrationNames: {
|
12
|
+
bubbled: "onChange",
|
13
|
+
captured: "onChangeCapture"
|
14
|
+
},
|
15
|
+
dependencies: [
|
16
|
+
:topBlur,
|
17
|
+
:topChange,
|
18
|
+
:topClick,
|
19
|
+
:topFocus,
|
20
|
+
:topInput,
|
21
|
+
:topKeyDown,
|
22
|
+
:topKeyUp,
|
23
|
+
:topSelectionChange
|
24
|
+
]
|
25
|
+
}
|
26
|
+
}
|
27
|
+
|
28
|
+
SUPPORTED_INPUT_TYPES = [
|
29
|
+
'color',
|
30
|
+
'date',
|
31
|
+
'datetime',
|
32
|
+
'datetime-local',
|
33
|
+
'email',
|
34
|
+
'month',
|
35
|
+
'number',
|
36
|
+
'password',
|
37
|
+
'range',
|
38
|
+
'search',
|
39
|
+
'tel',
|
40
|
+
'text',
|
41
|
+
'time',
|
42
|
+
'url',
|
43
|
+
'week'
|
44
|
+
]
|
45
|
+
|
46
|
+
def event_types
|
47
|
+
EVENT_TYPES
|
48
|
+
end
|
49
|
+
|
50
|
+
def is_text_input_element(elem)
|
51
|
+
node_name = elem.node_name.downcase
|
52
|
+
(node_name == 'input' && SUPPORTED_INPUT_TYPES.include?(elem.input_type)) || node_name == 'textarea'
|
53
|
+
end
|
54
|
+
|
55
|
+
def should_use_change_event(elem)
|
56
|
+
node_name = elem.node_name.downcase
|
57
|
+
node_name == 'select' || (node_name == 'input' && elem.input_type == 'file')
|
58
|
+
end
|
59
|
+
|
60
|
+
def should_use_click_event(elem)
|
61
|
+
elem.node_name.downcase == 'input' && %w(checkbox radio).include?(elem.input_type)
|
62
|
+
end
|
63
|
+
|
64
|
+
def extract_event(top_level_type, top_level_target, top_level_target_id, event)
|
65
|
+
if should_use_change_event(top_level_target)
|
66
|
+
target_id = top_level_target_id if top_level_type == :topChange
|
67
|
+
elsif is_text_input_element(top_level_target)
|
68
|
+
target_id = top_level_target_id if top_level_type == :topInput
|
69
|
+
elsif should_use_click_event(top_level_target)
|
70
|
+
target_id = top_level_target_id if top_level_type == :topClick
|
71
|
+
end
|
72
|
+
|
73
|
+
if target_id
|
74
|
+
SyntheticEvent.new(event).tap do |synthetic_event|
|
75
|
+
InstanceHandles.traverse_two_phase(target_id) do |target_id, upwards|
|
76
|
+
listener = BrowserEvent.listener_at_phase(target_id, EVENT_TYPES[:change], upwards ? :bubbled : :captured)
|
77
|
+
synthetic_event.add_listener(listener, target_id) if listener
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Hyalite
|
2
|
+
class EventPluginRegistry
|
3
|
+
def initialize(*plugins)
|
4
|
+
@registration_name_modules = {}
|
5
|
+
@registration_name_dependencies = {}
|
6
|
+
@plugins = []
|
7
|
+
plugins.each {|plugin| add_plugin(plugin) }
|
8
|
+
end
|
9
|
+
|
10
|
+
def add_plugin(plugin)
|
11
|
+
plugin.event_types.each do |key, dispatch_config|
|
12
|
+
dispatch_config[:phasedRegistrationNames].each do |phase, registration_name|
|
13
|
+
@registration_name_modules[registration_name] = plugin
|
14
|
+
@registration_name_dependencies[registration_name] = dispatch_config[:dependencies]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
@plugins << plugin
|
18
|
+
end
|
19
|
+
|
20
|
+
def include?(registration_name)
|
21
|
+
@registration_name_modules.has_key? registration_name
|
22
|
+
end
|
23
|
+
|
24
|
+
def dependencies(registration_name)
|
25
|
+
@registration_name_dependencies[registration_name]
|
26
|
+
end
|
27
|
+
|
28
|
+
def [](registration_name)
|
29
|
+
@registration_name_modules[registration_name]
|
30
|
+
end
|
31
|
+
|
32
|
+
def extract_events(top_level_type, top_level_target, top_level_target_id, event)
|
33
|
+
@plugins.each.with_object([]) do |plugin, events|
|
34
|
+
synthetic_event = plugin.extract_event(top_level_type, top_level_target, top_level_target_id, event)
|
35
|
+
events << synthetic_event if synthetic_event
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def registration_names
|
40
|
+
@registrasion_names ||= Set.new.tap do |names|
|
41
|
+
TOP_LEVEL_EVENTS_TO_DISPATCH_CONFIG.each_value do |value|
|
42
|
+
value[:phasedRegistrationNames].each_value do |name|
|
43
|
+
names << name
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,276 @@
|
|
1
|
+
module Hyalite
|
2
|
+
class SimpleEventPlugin
|
3
|
+
EVENT_TYPES = {
|
4
|
+
blur: {
|
5
|
+
phasedRegistrationNames: {
|
6
|
+
bubbled: :onBlur,
|
7
|
+
captured: :onBlurCapture
|
8
|
+
}
|
9
|
+
},
|
10
|
+
click: {
|
11
|
+
phasedRegistrationNames: {
|
12
|
+
bubbled: :onClick,
|
13
|
+
captured: :onClickCapture
|
14
|
+
}
|
15
|
+
},
|
16
|
+
contextMenu: {
|
17
|
+
phasedRegistrationNames: {
|
18
|
+
bubbled: :onContextMenu,
|
19
|
+
captured: :onContextMenuCapture
|
20
|
+
}
|
21
|
+
},
|
22
|
+
copy: {
|
23
|
+
phasedRegistrationNames: {
|
24
|
+
bubbled: :onCopy,
|
25
|
+
captured: :onCopyCapture
|
26
|
+
}
|
27
|
+
},
|
28
|
+
cut: {
|
29
|
+
phasedRegistrationNames: {
|
30
|
+
bubbled: :onCut,
|
31
|
+
captured: :onCutCapture
|
32
|
+
}
|
33
|
+
},
|
34
|
+
doubleClick: {
|
35
|
+
phasedRegistrationNames: {
|
36
|
+
bubbled: :onDoubleClick,
|
37
|
+
captured: :onDoubleClickCapture
|
38
|
+
}
|
39
|
+
},
|
40
|
+
drag: {
|
41
|
+
phasedRegistrationNames: {
|
42
|
+
bubbled: :onDrag,
|
43
|
+
captured: :onDragCapture
|
44
|
+
}
|
45
|
+
},
|
46
|
+
dragEnd: {
|
47
|
+
phasedRegistrationNames: {
|
48
|
+
bubbled: :onDragEnd,
|
49
|
+
captured: :onDragEndCapture
|
50
|
+
}
|
51
|
+
},
|
52
|
+
dragEnter: {
|
53
|
+
phasedRegistrationNames: {
|
54
|
+
bubbled: :onDragEnter,
|
55
|
+
captured: :onDragEnterCapture
|
56
|
+
}
|
57
|
+
},
|
58
|
+
dragExit: {
|
59
|
+
phasedRegistrationNames: {
|
60
|
+
bubbled: :onDragExit,
|
61
|
+
captured: :onDragExitCapture
|
62
|
+
}
|
63
|
+
},
|
64
|
+
dragLeave: {
|
65
|
+
phasedRegistrationNames: {
|
66
|
+
bubbled: :onDragLeave,
|
67
|
+
captured: :onDragLeaveCapture
|
68
|
+
}
|
69
|
+
},
|
70
|
+
dragOver: {
|
71
|
+
phasedRegistrationNames: {
|
72
|
+
bubbled: :onDragOver,
|
73
|
+
captured: :onDragOverCapture
|
74
|
+
}
|
75
|
+
},
|
76
|
+
dragStart: {
|
77
|
+
phasedRegistrationNames: {
|
78
|
+
bubbled: :onDragStart,
|
79
|
+
captured: :onDragStartCapture
|
80
|
+
}
|
81
|
+
},
|
82
|
+
drop: {
|
83
|
+
phasedRegistrationNames: {
|
84
|
+
bubbled: :onDrop,
|
85
|
+
captured: :onDropCapture
|
86
|
+
}
|
87
|
+
},
|
88
|
+
focus: {
|
89
|
+
phasedRegistrationNames: {
|
90
|
+
bubbled: :onFocus,
|
91
|
+
captured: :onFocusCapture
|
92
|
+
}
|
93
|
+
},
|
94
|
+
input: {
|
95
|
+
phasedRegistrationNames: {
|
96
|
+
bubbled: :onInput,
|
97
|
+
captured: :onInputCapture
|
98
|
+
}
|
99
|
+
},
|
100
|
+
keyDown: {
|
101
|
+
phasedRegistrationNames: {
|
102
|
+
bubbled: :onKeyDown,
|
103
|
+
captured: :onKeyDownCapture
|
104
|
+
}
|
105
|
+
},
|
106
|
+
keyPress: {
|
107
|
+
phasedRegistrationNames: {
|
108
|
+
bubbled: :onKeyPress,
|
109
|
+
captured: :onKeyPressCapture
|
110
|
+
}
|
111
|
+
},
|
112
|
+
keyUp: {
|
113
|
+
phasedRegistrationNames: {
|
114
|
+
bubbled: :onKeyUp,
|
115
|
+
captured: :onKeyUpCapture
|
116
|
+
}
|
117
|
+
},
|
118
|
+
load: {
|
119
|
+
phasedRegistrationNames: {
|
120
|
+
bubbled: :onLoad,
|
121
|
+
captured: :onLoadCapture
|
122
|
+
}
|
123
|
+
},
|
124
|
+
error: {
|
125
|
+
phasedRegistrationNames: {
|
126
|
+
bubbled: :onError,
|
127
|
+
captured: :onErrorCapture
|
128
|
+
}
|
129
|
+
},
|
130
|
+
mouseDown: {
|
131
|
+
phasedRegistrationNames: {
|
132
|
+
bubbled: :onMouseDown,
|
133
|
+
captured: :onMouseDownCapture
|
134
|
+
}
|
135
|
+
},
|
136
|
+
mouseMove: {
|
137
|
+
phasedRegistrationNames: {
|
138
|
+
bubbled: :onMouseMove,
|
139
|
+
captured: :onMouseMoveCapture
|
140
|
+
}
|
141
|
+
},
|
142
|
+
mouseOut: {
|
143
|
+
phasedRegistrationNames: {
|
144
|
+
bubbled: :onMouseOut,
|
145
|
+
captured: :onMouseOutCapture
|
146
|
+
}
|
147
|
+
},
|
148
|
+
mouseOver: {
|
149
|
+
phasedRegistrationNames: {
|
150
|
+
bubbled: :onMouseOver,
|
151
|
+
captured: :onMouseOverCapture
|
152
|
+
}
|
153
|
+
},
|
154
|
+
mouseUp: {
|
155
|
+
phasedRegistrationNames: {
|
156
|
+
bubbled: :onMouseUp,
|
157
|
+
captured: :onMouseUpCapture
|
158
|
+
}
|
159
|
+
},
|
160
|
+
paste: {
|
161
|
+
phasedRegistrationNames: {
|
162
|
+
bubbled: :onPaste,
|
163
|
+
captured: :onPasteCapture
|
164
|
+
}
|
165
|
+
},
|
166
|
+
reset: {
|
167
|
+
phasedRegistrationNames: {
|
168
|
+
bubbled: :onReset,
|
169
|
+
captured: :onResetCapture
|
170
|
+
}
|
171
|
+
},
|
172
|
+
scroll: {
|
173
|
+
phasedRegistrationNames: {
|
174
|
+
bubbled: :onScroll,
|
175
|
+
captured: :onScrollCapture
|
176
|
+
}
|
177
|
+
},
|
178
|
+
submit: {
|
179
|
+
phasedRegistrationNames: {
|
180
|
+
bubbled: :onSubmit,
|
181
|
+
captured: :onSubmitCapture
|
182
|
+
}
|
183
|
+
},
|
184
|
+
touchCancel: {
|
185
|
+
phasedRegistrationNames: {
|
186
|
+
bubbled: :onTouchCancel,
|
187
|
+
captured: :onTouchCancelCapture
|
188
|
+
}
|
189
|
+
},
|
190
|
+
touchEnd: {
|
191
|
+
phasedRegistrationNames: {
|
192
|
+
bubbled: :onTouchEnd,
|
193
|
+
captured: :onTouchEndCapture
|
194
|
+
}
|
195
|
+
},
|
196
|
+
touchMove: {
|
197
|
+
phasedRegistrationNames: {
|
198
|
+
bubbled: :onTouchMove,
|
199
|
+
captured: :onTouchMoveCapture
|
200
|
+
}
|
201
|
+
},
|
202
|
+
touchStart: {
|
203
|
+
phasedRegistrationNames: {
|
204
|
+
bubbled: :onTouchStart,
|
205
|
+
captured: :onTouchStartCapture
|
206
|
+
}
|
207
|
+
},
|
208
|
+
wheel: {
|
209
|
+
phasedRegistrationNames: {
|
210
|
+
bubbled: :onWheel,
|
211
|
+
captured: :onWheelCapture
|
212
|
+
}
|
213
|
+
}
|
214
|
+
}
|
215
|
+
|
216
|
+
TOP_LEVEL_EVENTS_TO_DISPATCH_CONFIG = {
|
217
|
+
topBlur: EVENT_TYPES[:blur],
|
218
|
+
topClick: EVENT_TYPES[:click],
|
219
|
+
topContextMenu: EVENT_TYPES[:contextMenu],
|
220
|
+
topCopy: EVENT_TYPES[:copy],
|
221
|
+
topCut: EVENT_TYPES[:cut],
|
222
|
+
topDoubleClick: EVENT_TYPES[:doubleClick],
|
223
|
+
topDrag: EVENT_TYPES[:drag],
|
224
|
+
topDragEnd: EVENT_TYPES[:dragEnd],
|
225
|
+
topDragEnter: EVENT_TYPES[:dragEnter],
|
226
|
+
topDragExit: EVENT_TYPES[:dragExit],
|
227
|
+
topDragLeave: EVENT_TYPES[:dragLeave],
|
228
|
+
topDragOver: EVENT_TYPES[:dragOver],
|
229
|
+
topDragStart: EVENT_TYPES[:dragStart],
|
230
|
+
topDrop: EVENT_TYPES[:drop],
|
231
|
+
topError: EVENT_TYPES[:error],
|
232
|
+
topFocus: EVENT_TYPES[:focus],
|
233
|
+
topInput: EVENT_TYPES[:input],
|
234
|
+
topKeyDown: EVENT_TYPES[:keyDown],
|
235
|
+
topKeyPress: EVENT_TYPES[:keyPress],
|
236
|
+
topKeyUp: EVENT_TYPES[:keyUp],
|
237
|
+
topLoad: EVENT_TYPES[:load],
|
238
|
+
topMouseDown: EVENT_TYPES[:mouseDown],
|
239
|
+
topMouseMove: EVENT_TYPES[:mouseMove],
|
240
|
+
topMouseOut: EVENT_TYPES[:mouseOut],
|
241
|
+
topMouseOver: EVENT_TYPES[:mouseOver],
|
242
|
+
topMouseUp: EVENT_TYPES[:mouseUp],
|
243
|
+
topPaste: EVENT_TYPES[:paste],
|
244
|
+
topReset: EVENT_TYPES[:reset],
|
245
|
+
topScroll: EVENT_TYPES[:scroll],
|
246
|
+
topSubmit: EVENT_TYPES[:submit],
|
247
|
+
topTouchCancel: EVENT_TYPES[:touchCancel],
|
248
|
+
topTouchEnd: EVENT_TYPES[:touchEnd],
|
249
|
+
topTouchMove: EVENT_TYPES[:touchMove],
|
250
|
+
topTouchStart: EVENT_TYPES[:touchStart],
|
251
|
+
topWheel: EVENT_TYPES[:wheel]
|
252
|
+
}
|
253
|
+
|
254
|
+
def initialize
|
255
|
+
TOP_LEVEL_EVENTS_TO_DISPATCH_CONFIG.each do |type, dispatch_config|
|
256
|
+
dispatch_config[:dependencies] = [type]
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def event_types
|
261
|
+
EVENT_TYPES
|
262
|
+
end
|
263
|
+
|
264
|
+
def extract_event(top_level_type, top_level_target, top_level_target_id, event)
|
265
|
+
dispatch_config = TOP_LEVEL_EVENTS_TO_DISPATCH_CONFIG[top_level_type]
|
266
|
+
return [] unless dispatch_config
|
267
|
+
|
268
|
+
SyntheticEvent.new(event).tap do |synthetic_event|
|
269
|
+
InstanceHandles.traverse_two_phase(top_level_target_id) do |target_id, upwards|
|
270
|
+
listener = BrowserEvent.listener_at_phase(target_id, dispatch_config, upwards ? :bubbled : :captured)
|
271
|
+
synthetic_event.add_listener(listener, target_id) if listener
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'hyalite/linked_value_utils'
|
3
|
+
require 'hyalite/dom_operations'
|
4
|
+
|
5
|
+
module Hyalite
|
6
|
+
class InputWrapper
|
7
|
+
include LinkedValueUtils
|
8
|
+
|
9
|
+
def initialize(dom_component)
|
10
|
+
@dom_component = dom_component
|
11
|
+
end
|
12
|
+
|
13
|
+
def mount_wrapper
|
14
|
+
props = @dom_component.current_element.props
|
15
|
+
@wrapper_state = {
|
16
|
+
initialChecked: props[:default_checked] || false,
|
17
|
+
initialValue: props[:default_value],
|
18
|
+
listeners: nil,
|
19
|
+
onChange: -> (event) { handle_change(event) }
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def unmount_wrapper
|
24
|
+
end
|
25
|
+
|
26
|
+
def native_props(inst)
|
27
|
+
props = @dom_component.current_element.props
|
28
|
+
|
29
|
+
props.merge({
|
30
|
+
defaultChecked: nil,
|
31
|
+
defaultValue: nil,
|
32
|
+
value: props[:value] || @wrapper_state[:initialValue],
|
33
|
+
checked: props[:checked] || @wrapper_state[:initialChecked],
|
34
|
+
onChange: @wrapper_state[:onChange],
|
35
|
+
})
|
36
|
+
end
|
37
|
+
|
38
|
+
def force_update_if_mounted(instance)
|
39
|
+
if instance.root_node_id
|
40
|
+
update_wrapper
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def update_wrapper
|
45
|
+
props = @dom_component.current_element.props
|
46
|
+
if props.has_key?(:checked)
|
47
|
+
checked = props[:checked]
|
48
|
+
node = Mount.node(@dom_component.root_node_id)
|
49
|
+
if checked
|
50
|
+
node[:checked] = checked
|
51
|
+
else
|
52
|
+
node.remove_attribute(:checked)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
value = LinkedValueUtils.value(props)
|
57
|
+
if value
|
58
|
+
DOMOperations.update_property_by_id(
|
59
|
+
@dom_component.root_node_id,
|
60
|
+
'value',
|
61
|
+
value.to_s
|
62
|
+
)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def handle_change(event)
|
67
|
+
props = @dom_component.current_element.props
|
68
|
+
return_value = execute_on_change(props, event)
|
69
|
+
|
70
|
+
Hyalite.updates.asap { force_update_if_mounted(@dom_component) }
|
71
|
+
|
72
|
+
if props[:type] == 'radio' && props[:name]
|
73
|
+
root_node = Mount.node(root_node_id)
|
74
|
+
query_root = root_node
|
75
|
+
|
76
|
+
while query_root.parent
|
77
|
+
query_root = query_root.parent
|
78
|
+
end
|
79
|
+
|
80
|
+
group = query_root =~ "input[name='#{name.to_json}'][type='radio']"
|
81
|
+
|
82
|
+
group.each do |other_node|
|
83
|
+
next if other_node == root_node || other_node.form != root_node.form
|
84
|
+
|
85
|
+
other_id = Mount.node_id(other_node)
|
86
|
+
other_instance = Mount.instances_by_root_id(other_id)
|
87
|
+
Hyalite.updates.asap { force_update_if_mounted(other_instance) }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
return_value
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|