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