hyalite 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +3 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +46 -0
  5. data/Rakefile +7 -0
  6. data/client/hyalite.rb +98 -0
  7. data/client/hyalite/adler32.rb +33 -0
  8. data/client/hyalite/browser_event.rb +201 -0
  9. data/client/hyalite/callback_queue.rb +22 -0
  10. data/client/hyalite/component.rb +77 -0
  11. data/client/hyalite/composite_component.rb +237 -0
  12. data/client/hyalite/dom_component.rb +329 -0
  13. data/client/hyalite/dom_operations.rb +17 -0
  14. data/client/hyalite/dom_property.rb +218 -0
  15. data/client/hyalite/dom_property_operations.rb +117 -0
  16. data/client/hyalite/element.rb +17 -0
  17. data/client/hyalite/event_dispatcher.rb +99 -0
  18. data/client/hyalite/event_plugin/change_event_plugin.rb +83 -0
  19. data/client/hyalite/event_plugin/event_plugin_registry.rb +49 -0
  20. data/client/hyalite/event_plugin/simple_event_plugin.rb +276 -0
  21. data/client/hyalite/input_wrapper.rb +94 -0
  22. data/client/hyalite/instance_handles.rb +78 -0
  23. data/client/hyalite/internal_component.rb +11 -0
  24. data/client/hyalite/linked_value_utils.rb +27 -0
  25. data/client/hyalite/mount.rb +285 -0
  26. data/client/hyalite/multi_children.rb +272 -0
  27. data/client/hyalite/reconcile_transaction.rb +41 -0
  28. data/client/hyalite/reconciler.rb +122 -0
  29. data/client/hyalite/short_hand.rb +23 -0
  30. data/client/hyalite/synthetic_event.rb +18 -0
  31. data/client/hyalite/text_component.rb +42 -0
  32. data/client/hyalite/transaction.rb +47 -0
  33. data/client/hyalite/try.rb +7 -0
  34. data/client/hyalite/update_queue.rb +52 -0
  35. data/client/hyalite/updates.rb +129 -0
  36. data/client/hyalite/utils.rb +19 -0
  37. data/example/Gemfile +5 -0
  38. data/example/Gemfile.lock +46 -0
  39. data/example/app/application.rb +27 -0
  40. data/example/config.ru +12 -0
  41. data/example/index.html.haml +8 -0
  42. data/hyalite.gemspec +27 -0
  43. data/lib/hyalite.rb +6 -0
  44. data/lib/hyalite/main.rb +5 -0
  45. data/lib/hyalite/version.rb +3 -0
  46. 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