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,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,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
|