hyalite 0.0.1

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.
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,41 @@
1
+ require 'hyalite/transaction'
2
+
3
+ module Hyalite
4
+ class ReconcileTransaction < Transaction
5
+ def initialize
6
+ @mount_ready_wrapper = MountReadyWrapper.new
7
+ super [ @mount_ready_wrapper, EventSuppressionWrapper.new ]
8
+ end
9
+
10
+ def mount_ready
11
+ @mount_ready_wrapper.queue
12
+ end
13
+
14
+ class MountReadyWrapper
15
+ include TransactionWrapper
16
+
17
+ attr_reader :queue
18
+
19
+ def initialize
20
+ @queue = CallbackQueue.new
21
+ end
22
+
23
+ def close
24
+ @queue.notify_all
25
+ end
26
+ end
27
+
28
+ class EventSuppressionWrapper
29
+ include TransactionWrapper
30
+
31
+ def initialize
32
+ @previous_enabled = BrowserEvent.enabled?
33
+ BrowserEvent.enabled = false
34
+ end
35
+
36
+ def close
37
+ BrowserEvent.enabled = @previous_enabled
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,122 @@
1
+ module Hyalite
2
+ module Reconciler
3
+
4
+ SEPARATOR = '.';
5
+ SUBSEPARATOR = ':';
6
+
7
+ class << self
8
+ def mount_component(internal_instance, root_id, mount_ready, context)
9
+ markup = internal_instance.mount_component(root_id, mount_ready, context)
10
+ if internal_instance.current_element.respond_to?(:ref) && internal_instance.current_element.ref
11
+ mount_ready.enqueue do
12
+ internal_instance.current_element.owner.attach_ref(internal_instance.current_element.ref, internal_instance)
13
+ end
14
+ end
15
+ markup
16
+ end
17
+
18
+ def unmount_component(internal_instance)
19
+ #ReactRef.detachRefs(internalInstance, internalInstance._currentElement);
20
+ internal_instance.unmount_component
21
+ end
22
+
23
+ def receive_component(internal_instance, next_element, mount_ready, context)
24
+ prev_element = internal_instance.current_element
25
+
26
+ return if next_element == prev_element && internal_instance.respond_to?(:context) && context == internal_instance.context
27
+
28
+ # refs_changed = ReactRef.should_update_refs(prev_element, next_element)
29
+ #
30
+ # ReactRef.detach_refs(internal_instance, prev_element) if refs_changed
31
+
32
+ internal_instance.receive_component(next_element, mount_ready, context)
33
+
34
+ # transaction.enqueue(attach_refs, internal_instance) if refs_changed
35
+ end
36
+
37
+ def perform_update_if_necessary(internal_instance, mount_ready)
38
+ internal_instance.perform_update_if_necessary(mount_ready)
39
+ end
40
+
41
+ def update_children(prev_children, next_nested_child_nodes, mount_ready, context)
42
+ next_children = flatten_children(next_nested_child_nodes)
43
+ return nil if next_children.nil? && prev_children.nil?
44
+
45
+ next_children.each do |name, next_element|
46
+ prev_child = prev_children && prev_children[name]
47
+ prev_element = prev_child && prev_child.current_element
48
+ if should_update_component(prev_element, next_element)
49
+ receive_component(prev_child, next_element, mount_ready, context)
50
+ next_children[name] = prev_child
51
+ else
52
+ if prev_child
53
+ unmount_component(prev_child, name)
54
+ end
55
+
56
+ next_children[name] = Hyalite.instantiate_component(next_element, nil)
57
+ end
58
+ end
59
+
60
+ prev_children.each do |name, prev_child|
61
+ unless next_children && next_children.has_key?(name)
62
+ unmount_component(prev_children[name])
63
+ end
64
+ end
65
+
66
+ next_children;
67
+ end
68
+
69
+ def flatten_children(nested_child_nodes)
70
+ {}.tap do |res|
71
+ traverse_children(nested_child_nodes, "") do |name, child_node|
72
+ res[name] = child_node if child_node
73
+ end
74
+ end
75
+ end
76
+
77
+ def traverse_children(children, name_so_far)
78
+ children = nil if children == true || children == false
79
+
80
+ if children.nil? || children.is_a?(String) || children.is_a?(Numeric)
81
+ name = name_so_far.empty? ? SEPARATOR + component_key(children, 0) : name_so_far
82
+ yield [name, children]
83
+ return 1
84
+ end
85
+
86
+ case children
87
+ when Array
88
+ children.each_with_index do |child, i|
89
+ next_name = (name_so_far.empty? ? SEPARATOR : name_so_far + SUBSEPARATOR) + component_key(child, i)
90
+ traverse_children(child, next_name) {|n, c| yield [n, c] }
91
+ end
92
+ else
93
+ name = name_so_far.empty? ? SEPARATOR + component_key(children, 0) : name_so_far
94
+ yield [name, children]
95
+ end
96
+ end
97
+
98
+ def component_key(component, index)
99
+ return "$#{component.key}" if component && component.respond_to?(:key) && component.key
100
+ index.to_s(36)
101
+ end
102
+
103
+ def should_update_component(prev_element, next_element)
104
+ if prev_element && next_element
105
+ if prev_element.is_a?(String) || prev_element.is_a?(Numeric)
106
+ return next_element.is_a?(String) || next_element.is_a?(Numeric)
107
+ else
108
+ return prev_element.type == next_element.type && prev_element.key == next_element.key
109
+ end
110
+ end
111
+ false
112
+ end
113
+
114
+ def unmount_children(rendered_children)
115
+ rendered_children.values.each do |rendered_child|
116
+ unmount_component(rendered_child)
117
+ end
118
+ end
119
+ end
120
+
121
+ end
122
+ end
@@ -0,0 +1,23 @@
1
+ module Hyalite
2
+ module Component
3
+ module ShortHand
4
+ TAGS = %w(h1 h2 h3 h4 h5 h6 header footer section div p span code strong a img input button label ul li)
5
+
6
+ def self.included(klass)
7
+ TAGS.each do |tag|
8
+ define_method(tag) do |props, *children|
9
+ Hyalite.create_element(tag, props, *children)
10
+ end
11
+ end
12
+
13
+ klass.extend ClassMethods
14
+ end
15
+
16
+ module ClassMethods
17
+ def el(props, *children)
18
+ Hyalite.create_element(self, props, *children)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,18 @@
1
+ module Hyalite
2
+ class SyntheticEvent
3
+ attr_reader :event
4
+
5
+ def initialize(event)
6
+ @event = event
7
+ @listeners = []
8
+ end
9
+
10
+ def add_listener(listener, target_id)
11
+ @listeners << [listener, target_id]
12
+ end
13
+
14
+ def each_listener(&block)
15
+ @listeners.each {|listener| yield(listener) }
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,42 @@
1
+ require 'hyalite/internal_component'
2
+
3
+ module Hyalite
4
+ class TextComponent
5
+ include InternalComponent
6
+
7
+ def initialize(text)
8
+ @text = text
9
+ end
10
+
11
+ def current_element
12
+ @text
13
+ end
14
+
15
+ def mount_component(root_id, mount_ready, context)
16
+ @root_node_id = root_id
17
+ @native_node = $document.create_element('span').tap do |node|
18
+ DOMPropertyOperations.set_attribute_for_id(node, root_id)
19
+ Mount.node_id(node)
20
+ node.text = @text
21
+ end
22
+ end
23
+
24
+ def unmount_component
25
+ @native_node = nil
26
+ Mount.purge_id(@root_node_id)
27
+ end
28
+
29
+ def receive_component(next_text, mount_ready)
30
+ if next_text != @text
31
+ @text = next_text
32
+ node.text = @text
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def node
39
+ @node ||= Mount.node(@root_node_id)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,47 @@
1
+ module TransactionWrapper
2
+ def init
3
+ end
4
+
5
+ def close
6
+ end
7
+ end
8
+
9
+ class Transaction
10
+ include TransactionWrapper
11
+
12
+ def initialize(transaction_wrappers = nil, &block)
13
+ @transaction_wrappers = transaction_wrappers || []
14
+ if block_given?
15
+ @close_proc = block
16
+ @transaction_wrappers << self
17
+ end
18
+ end
19
+
20
+ def close
21
+ if @close_proc
22
+ @close_proc.call
23
+ else
24
+ close_all
25
+ end
26
+ end
27
+
28
+ def close_all
29
+ @transaction_wrappers.each do |wrapper|
30
+ wrapper.close
31
+ end
32
+ end
33
+
34
+ def init_all
35
+ @transaction_wrappers.each do |wrapper|
36
+ wrapper.init
37
+ end
38
+ end
39
+
40
+ def perform
41
+ init_all
42
+
43
+ yield(self)
44
+ ensure
45
+ close_all
46
+ end
47
+ end
@@ -0,0 +1,7 @@
1
+ class Object
2
+ def try
3
+ unless self.nil?
4
+ yield self
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,52 @@
1
+ module Hyalite
2
+ class UpdateQueue
3
+ class << self
4
+ def is_mounted(public_instance)
5
+ internal_instance = Hyalite.instance_map[public_instance]
6
+ if internal_instance
7
+ !!internal_instance.rendered_component
8
+ else
9
+ false
10
+ end
11
+ end
12
+
13
+ def enqueue_callback(public_instance, &block)
14
+ internal_instance = Hyalite.instance_map[public_instance]
15
+ return nil unless internal_instance
16
+
17
+ internal_instance.pending_callbacks ||= []
18
+ internal_instance.pending_callbacks << block
19
+ enqueue_update(internal_instance);
20
+ end
21
+
22
+ def enqueue_set_state(public_instance, partial_state)
23
+ internal_instance = Hyalite.instance_map[public_instance]
24
+
25
+ return unless internal_instance
26
+
27
+ queue = internal_instance.pending_state_queue || (internal_instance.pending_state_queue = [])
28
+ queue.push(partial_state)
29
+
30
+ enqueue_update(internal_instance);
31
+ end
32
+
33
+ def enqueue_update(internal_instance)
34
+ Hyalite.updates.enqueue_update(internal_instance)
35
+ end
36
+
37
+ def enqueue_element_internal(internal_instance, new_element)
38
+ internal_instance.pending_element = new_element
39
+ enqueue_update(internal_instance)
40
+ end
41
+
42
+ def enqueue_callback_internal(internal_instance, &callback)
43
+ if internal_instance.pending_callbacks
44
+ internal_instance.pending_callbacks << callback
45
+ else
46
+ internal_instance.pending_callbacks = [callback]
47
+ end
48
+ enqueue_update(internal_instance)
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,129 @@
1
+ require 'hyalite/transaction'
2
+ require 'hyalite/callback_queue'
3
+ require 'hyalite/reconciler'
4
+ require 'hyalite/reconcile_transaction'
5
+
6
+ module Hyalite
7
+ class Updates
8
+ class NestedUpdate
9
+ include TransactionWrapper
10
+
11
+ def initialize(dirty_components)
12
+ @dirty_components = dirty_components
13
+ end
14
+
15
+ def init
16
+ @init_length = @dirty_components.length
17
+ end
18
+
19
+ def close
20
+ if @dirty_components.length - @init_length > 0
21
+ @dirty_components.shift(@init_length)
22
+ else
23
+ @dirty_components.clear
24
+ end
25
+ end
26
+ end
27
+
28
+ class UpdateQueueing
29
+ include TransactionWrapper
30
+
31
+ def initialize(queue)
32
+ @queue = queue
33
+ end
34
+
35
+ def close
36
+ @queue.notify_all
37
+ end
38
+ end
39
+
40
+ attr_reader :reconcile_transaction
41
+
42
+ def initialize
43
+ @dirty_components = []
44
+
45
+ @is_batching_updates = false
46
+
47
+ @callback_queue = CallbackQueue.new
48
+ @asap_callback_queue = CallbackQueue.new
49
+ @asap_enqueued = false
50
+ @reconcile_transaction = ReconcileTransaction.new
51
+
52
+ @flush_transaction = Transaction.new([NestedUpdate.new(@dirty_components), UpdateQueueing.new(@callback_queue), @reconcile_transaction])
53
+ end
54
+
55
+ def enqueue_update(component)
56
+ unless @is_batching_updates
57
+ batched_updates do
58
+ enqueue_update(component)
59
+ end
60
+ end
61
+
62
+ @dirty_components << component
63
+ end
64
+
65
+ def batched_updates
66
+ already_batching_updates = @is_batching_updates
67
+
68
+ @is_batching_updates = true
69
+
70
+
71
+ if already_batching_updates
72
+ yield
73
+ else
74
+ transaction = Transaction.new do
75
+ flush_batched_updates
76
+ @is_batching_updates = false
77
+ end
78
+
79
+ transaction.perform do
80
+ yield
81
+ end
82
+ end
83
+ end
84
+
85
+ def flush_batched_updates
86
+ while @dirty_components.length > 0 || @asap_enqueued
87
+ if @dirty_components.length > 0
88
+ @flush_transaction.perform do |transaction|
89
+ run_batched_updates(transaction)
90
+ end
91
+ end
92
+
93
+ if @asap_enqueued
94
+ @asap_enqueued = false
95
+ @asap_callback_queue.notify_all
96
+ next
97
+ end
98
+ end
99
+ end
100
+
101
+ def run_batched_updates(transaction)
102
+ @dirty_components.sort{|c1, c2| c1.mount_order <=> c2.mount_order}.each do |component|
103
+ callbacks = component.pending_callbacks
104
+ component.pending_callbacks = nil
105
+
106
+ Reconciler.perform_update_if_necessary(component, @reconcile_transaction.mount_ready)
107
+
108
+ if callbacks
109
+ callbacks.each do |callback|
110
+ @callback_queue.enqueue(callback)
111
+ end
112
+ end
113
+ end
114
+ end
115
+
116
+ def asap(&callback)
117
+ @asap_callback_queue.enqueue(&callback)
118
+ @asap_enqueued = true
119
+ end
120
+
121
+ def mount_ready
122
+ @reconcile_transaction.mount_ready
123
+ end
124
+ end
125
+
126
+ def self.updates
127
+ @updates ||= Updates.new
128
+ end
129
+ end