isomorfeus-preact 10.9.0 → 22.9.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (266) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +14 -3
  3. data/README.md +23 -39
  4. data/lib/browser/delegate_native.rb +94 -72
  5. data/lib/browser/document.rb +38 -0
  6. data/lib/browser/element.rb +160 -176
  7. data/lib/browser/event.rb +99 -94
  8. data/lib/browser/history.rb +40 -20
  9. data/lib/browser/location.rb +59 -0
  10. data/lib/browser/window.rb +58 -0
  11. data/lib/data_uri/open_uri.rb +18 -0
  12. data/lib/data_uri/uri.rb +61 -0
  13. data/lib/data_uri.rb +4 -0
  14. data/lib/isomorfeus/preact/config.rb +42 -54
  15. data/lib/isomorfeus/preact/imports.rb +5 -20
  16. data/lib/isomorfeus/preact/ssr.rb +34 -0
  17. data/lib/isomorfeus/preact/thread_local_component_cache.rb +9 -11
  18. data/lib/isomorfeus/preact/version.rb +5 -0
  19. data/lib/isomorfeus/preact/view_helper.rb +72 -0
  20. data/lib/isomorfeus/props/validator.rb +19 -11
  21. data/lib/isomorfeus/top_level.rb +55 -33
  22. data/lib/isomorfeus-preact.rb +78 -103
  23. data/lib/link.rb +52 -0
  24. data/lib/lucid_app.rb +117 -0
  25. data/lib/lucid_component.rb +154 -0
  26. data/lib/nano_css.rb +282 -465
  27. data/lib/preact/component.rb +222 -0
  28. data/lib/preact/component_resolution.rb +49 -83
  29. data/lib/preact/context.rb +68 -0
  30. data/lib/preact/elements.rb +2 -9
  31. data/lib/preact/module_component_resolution.rb +50 -0
  32. data/lib/preact/prop_declaration_mixin.rb +73 -0
  33. data/lib/preact.rb +1548 -257
  34. data/lib/redirect.rb +34 -0
  35. data/lib/route.rb +15 -0
  36. data/lib/router.rb +145 -0
  37. data/lib/switch.rb +70 -0
  38. data/opal/iso_uri.rb +29 -0
  39. data/opal/uri/common.rb +18 -0
  40. data/opal/uri/generic.rb +47 -0
  41. metadata +45 -309
  42. data/lib/isomorfeus/preact/memcached_component_cache.rb +0 -19
  43. data/lib/isomorfeus/preact/redis_component_cache.rb +0 -19
  44. data/lib/isomorfeus/preact/ssr/history.rb +0 -23
  45. data/lib/isomorfeus/preact/ssr/render_core.rb +0 -117
  46. data/lib/isomorfeus/preact/ssr/top_level.rb +0 -37
  47. data/lib/isomorfeus/preact_view_helper.rb +0 -129
  48. data/lib/isomorfeus_preact/lucid_app/api.rb +0 -38
  49. data/lib/isomorfeus_preact/lucid_app/base.rb +0 -7
  50. data/lib/isomorfeus_preact/lucid_app/mixin.rb +0 -14
  51. data/lib/isomorfeus_preact/lucid_app/native_component_constructor.rb +0 -101
  52. data/lib/isomorfeus_preact/lucid_component/api.rb +0 -123
  53. data/lib/isomorfeus_preact/lucid_component/app_store_proxy.rb +0 -32
  54. data/lib/isomorfeus_preact/lucid_component/base.rb +0 -7
  55. data/lib/isomorfeus_preact/lucid_component/class_store_proxy.rb +0 -37
  56. data/lib/isomorfeus_preact/lucid_component/initializer.rb +0 -9
  57. data/lib/isomorfeus_preact/lucid_component/mixin.rb +0 -13
  58. data/lib/isomorfeus_preact/lucid_component/native_component_constructor.rb +0 -87
  59. data/lib/isomorfeus_preact/lucid_component/styles_wrapper.rb +0 -40
  60. data/lib/isomorfeus_preact/lucid_func/base.rb +0 -7
  61. data/lib/isomorfeus_preact/lucid_func/initializer.rb +0 -8
  62. data/lib/isomorfeus_preact/lucid_func/mixin.rb +0 -10
  63. data/lib/isomorfeus_preact/lucid_func/native_component_constructor.rb +0 -56
  64. data/lib/isomorfeus_preact/preact/function_component/api.rb +0 -164
  65. data/lib/isomorfeus_preact/preact/function_component/base.rb +0 -7
  66. data/lib/isomorfeus_preact/preact/function_component/initializer.rb +0 -6
  67. data/lib/isomorfeus_preact/preact/function_component/mixin.rb +0 -8
  68. data/lib/isomorfeus_preact/preact/function_component/native_component_constructor.rb +0 -47
  69. data/lib/lucid_app/context.rb +0 -24
  70. data/lib/lucid_prop_declaration/mixin.rb +0 -126
  71. data/lib/preact/component/api.rb +0 -137
  72. data/lib/preact/component/base.rb +0 -7
  73. data/lib/preact/component/callbacks.rb +0 -111
  74. data/lib/preact/component/initializer.rb +0 -7
  75. data/lib/preact/component/mixin.rb +0 -11
  76. data/lib/preact/component/native_component_constructor.rb +0 -77
  77. data/lib/preact/context_wrapper.rb +0 -48
  78. data/lib/preact/native_constant_wrapper.rb +0 -29
  79. data/lib/preact/option_hooks.rb +0 -98
  80. data/lib/preact/params.rb +0 -16
  81. data/lib/preact/props.rb +0 -69
  82. data/lib/preact/ref.rb +0 -17
  83. data/lib/preact/state.rb +0 -87
  84. data/lib/preact/version.rb +0 -3
  85. data/node_modules/.package-lock.json +0 -38
  86. data/node_modules/preact/LICENSE +0 -21
  87. data/node_modules/preact/README.md +0 -188
  88. data/node_modules/preact/compat/LICENSE +0 -21
  89. data/node_modules/preact/compat/client.js +0 -19
  90. data/node_modules/preact/compat/client.mjs +0 -17
  91. data/node_modules/preact/compat/dist/compat.js +0 -2
  92. data/node_modules/preact/compat/dist/compat.js.map +0 -1
  93. data/node_modules/preact/compat/dist/compat.mjs +0 -2
  94. data/node_modules/preact/compat/dist/compat.module.js +0 -2
  95. data/node_modules/preact/compat/dist/compat.module.js.map +0 -1
  96. data/node_modules/preact/compat/dist/compat.umd.js +0 -2
  97. data/node_modules/preact/compat/dist/compat.umd.js.map +0 -1
  98. data/node_modules/preact/compat/jsx-dev-runtime.js +0 -3
  99. data/node_modules/preact/compat/jsx-dev-runtime.mjs +0 -3
  100. data/node_modules/preact/compat/jsx-runtime.js +0 -3
  101. data/node_modules/preact/compat/jsx-runtime.mjs +0 -3
  102. data/node_modules/preact/compat/package.json +0 -49
  103. data/node_modules/preact/compat/scheduler.js +0 -15
  104. data/node_modules/preact/compat/scheduler.mjs +0 -23
  105. data/node_modules/preact/compat/server.browser.js +0 -4
  106. data/node_modules/preact/compat/server.js +0 -15
  107. data/node_modules/preact/compat/server.mjs +0 -4
  108. data/node_modules/preact/compat/src/Children.js +0 -21
  109. data/node_modules/preact/compat/src/PureComponent.js +0 -15
  110. data/node_modules/preact/compat/src/forwardRef.js +0 -44
  111. data/node_modules/preact/compat/src/index.d.ts +0 -164
  112. data/node_modules/preact/compat/src/index.js +0 -223
  113. data/node_modules/preact/compat/src/internal.d.ts +0 -47
  114. data/node_modules/preact/compat/src/memo.js +0 -34
  115. data/node_modules/preact/compat/src/portals.js +0 -82
  116. data/node_modules/preact/compat/src/render.js +0 -238
  117. data/node_modules/preact/compat/src/suspense-list.d.ts +0 -14
  118. data/node_modules/preact/compat/src/suspense-list.js +0 -126
  119. data/node_modules/preact/compat/src/suspense.d.ts +0 -15
  120. data/node_modules/preact/compat/src/suspense.js +0 -270
  121. data/node_modules/preact/compat/src/util.js +0 -28
  122. data/node_modules/preact/compat/test-utils.js +0 -1
  123. data/node_modules/preact/debug/LICENSE +0 -21
  124. data/node_modules/preact/debug/dist/debug.js +0 -2
  125. data/node_modules/preact/debug/dist/debug.js.map +0 -1
  126. data/node_modules/preact/debug/dist/debug.mjs +0 -2
  127. data/node_modules/preact/debug/dist/debug.module.js +0 -2
  128. data/node_modules/preact/debug/dist/debug.module.js.map +0 -1
  129. data/node_modules/preact/debug/dist/debug.umd.js +0 -2
  130. data/node_modules/preact/debug/dist/debug.umd.js.map +0 -1
  131. data/node_modules/preact/debug/package.json +0 -26
  132. data/node_modules/preact/debug/src/check-props.js +0 -54
  133. data/node_modules/preact/debug/src/component-stack.js +0 -146
  134. data/node_modules/preact/debug/src/constants.js +0 -3
  135. data/node_modules/preact/debug/src/debug.js +0 -437
  136. data/node_modules/preact/debug/src/index.js +0 -6
  137. data/node_modules/preact/debug/src/internal.d.ts +0 -82
  138. data/node_modules/preact/debug/src/util.js +0 -11
  139. data/node_modules/preact/devtools/LICENSE +0 -21
  140. data/node_modules/preact/devtools/dist/devtools.js +0 -2
  141. data/node_modules/preact/devtools/dist/devtools.js.map +0 -1
  142. data/node_modules/preact/devtools/dist/devtools.mjs +0 -2
  143. data/node_modules/preact/devtools/dist/devtools.module.js +0 -2
  144. data/node_modules/preact/devtools/dist/devtools.module.js.map +0 -1
  145. data/node_modules/preact/devtools/dist/devtools.umd.js +0 -2
  146. data/node_modules/preact/devtools/dist/devtools.umd.js.map +0 -1
  147. data/node_modules/preact/devtools/package.json +0 -25
  148. data/node_modules/preact/devtools/src/devtools.js +0 -10
  149. data/node_modules/preact/devtools/src/index.d.ts +0 -8
  150. data/node_modules/preact/devtools/src/index.js +0 -15
  151. data/node_modules/preact/dist/preact.js +0 -2
  152. data/node_modules/preact/dist/preact.js.map +0 -1
  153. data/node_modules/preact/dist/preact.min.js +0 -2
  154. data/node_modules/preact/dist/preact.min.js.map +0 -1
  155. data/node_modules/preact/dist/preact.mjs +0 -2
  156. data/node_modules/preact/dist/preact.module.js +0 -2
  157. data/node_modules/preact/dist/preact.module.js.map +0 -1
  158. data/node_modules/preact/dist/preact.umd.js +0 -2
  159. data/node_modules/preact/dist/preact.umd.js.map +0 -1
  160. data/node_modules/preact/hooks/LICENSE +0 -21
  161. data/node_modules/preact/hooks/dist/hooks.js +0 -2
  162. data/node_modules/preact/hooks/dist/hooks.js.map +0 -1
  163. data/node_modules/preact/hooks/dist/hooks.mjs +0 -2
  164. data/node_modules/preact/hooks/dist/hooks.module.js +0 -2
  165. data/node_modules/preact/hooks/dist/hooks.module.js.map +0 -1
  166. data/node_modules/preact/hooks/dist/hooks.umd.js +0 -2
  167. data/node_modules/preact/hooks/dist/hooks.umd.js.map +0 -1
  168. data/node_modules/preact/hooks/package.json +0 -35
  169. data/node_modules/preact/hooks/src/index.d.ts +0 -139
  170. data/node_modules/preact/hooks/src/index.js +0 -417
  171. data/node_modules/preact/hooks/src/internal.d.ts +0 -78
  172. data/node_modules/preact/jsx-runtime/LICENSE +0 -21
  173. data/node_modules/preact/jsx-runtime/dist/jsxRuntime.js +0 -2
  174. data/node_modules/preact/jsx-runtime/dist/jsxRuntime.js.map +0 -1
  175. data/node_modules/preact/jsx-runtime/dist/jsxRuntime.mjs +0 -2
  176. data/node_modules/preact/jsx-runtime/dist/jsxRuntime.module.js +0 -2
  177. data/node_modules/preact/jsx-runtime/dist/jsxRuntime.module.js.map +0 -1
  178. data/node_modules/preact/jsx-runtime/dist/jsxRuntime.umd.js +0 -2
  179. data/node_modules/preact/jsx-runtime/dist/jsxRuntime.umd.js.map +0 -1
  180. data/node_modules/preact/jsx-runtime/package.json +0 -28
  181. data/node_modules/preact/jsx-runtime/src/index.d.ts +0 -50
  182. data/node_modules/preact/jsx-runtime/src/index.js +0 -77
  183. data/node_modules/preact/package.json +0 -304
  184. data/node_modules/preact/src/cjs.js +0 -3
  185. data/node_modules/preact/src/clone-element.js +0 -34
  186. data/node_modules/preact/src/component.js +0 -225
  187. data/node_modules/preact/src/constants.js +0 -3
  188. data/node_modules/preact/src/create-context.js +0 -68
  189. data/node_modules/preact/src/create-element.js +0 -98
  190. data/node_modules/preact/src/diff/catch-error.js +0 -40
  191. data/node_modules/preact/src/diff/children.js +0 -335
  192. data/node_modules/preact/src/diff/index.js +0 -533
  193. data/node_modules/preact/src/diff/props.js +0 -158
  194. data/node_modules/preact/src/index.d.ts +0 -317
  195. data/node_modules/preact/src/index.js +0 -13
  196. data/node_modules/preact/src/internal.d.ts +0 -155
  197. data/node_modules/preact/src/jsx.d.ts +0 -1014
  198. data/node_modules/preact/src/options.js +0 -16
  199. data/node_modules/preact/src/render.js +0 -75
  200. data/node_modules/preact/src/util.js +0 -27
  201. data/node_modules/preact/test-utils/dist/testUtils.js +0 -2
  202. data/node_modules/preact/test-utils/dist/testUtils.js.map +0 -1
  203. data/node_modules/preact/test-utils/dist/testUtils.mjs +0 -2
  204. data/node_modules/preact/test-utils/dist/testUtils.module.js +0 -2
  205. data/node_modules/preact/test-utils/dist/testUtils.module.js.map +0 -1
  206. data/node_modules/preact/test-utils/dist/testUtils.umd.js +0 -2
  207. data/node_modules/preact/test-utils/dist/testUtils.umd.js.map +0 -1
  208. data/node_modules/preact/test-utils/package.json +0 -28
  209. data/node_modules/preact/test-utils/src/index.d.ts +0 -3
  210. data/node_modules/preact/test-utils/src/index.js +0 -118
  211. data/node_modules/preact-render-to-string/LICENSE +0 -21
  212. data/node_modules/preact-render-to-string/README.md +0 -102
  213. data/node_modules/preact-render-to-string/dist/commonjs.js +0 -2
  214. data/node_modules/preact-render-to-string/dist/commonjs.js.map +0 -1
  215. data/node_modules/preact-render-to-string/dist/index.d.ts +0 -16
  216. data/node_modules/preact-render-to-string/dist/index.js +0 -1
  217. data/node_modules/preact-render-to-string/dist/index.js.map +0 -1
  218. data/node_modules/preact-render-to-string/dist/index.mjs +0 -2
  219. data/node_modules/preact-render-to-string/dist/index.module.js +0 -2
  220. data/node_modules/preact-render-to-string/dist/index.module.js.map +0 -1
  221. data/node_modules/preact-render-to-string/dist/jsx-entry.js +0 -2
  222. data/node_modules/preact-render-to-string/dist/jsx-entry.js.map +0 -1
  223. data/node_modules/preact-render-to-string/dist/jsx.d.ts +0 -13
  224. data/node_modules/preact-render-to-string/dist/jsx.js +0 -1
  225. data/node_modules/preact-render-to-string/dist/jsx.js.map +0 -1
  226. data/node_modules/preact-render-to-string/dist/jsx.mjs +0 -2
  227. data/node_modules/preact-render-to-string/dist/jsx.modern.js +0 -2
  228. data/node_modules/preact-render-to-string/dist/jsx.modern.js.map +0 -1
  229. data/node_modules/preact-render-to-string/dist/jsx.module.js +0 -2
  230. data/node_modules/preact-render-to-string/dist/jsx.module.js.map +0 -1
  231. data/node_modules/preact-render-to-string/dist/preact-render-to-string-tests.d.ts +0 -1
  232. data/node_modules/preact-render-to-string/jsx.js +0 -1
  233. data/node_modules/preact-render-to-string/package.json +0 -142
  234. data/node_modules/preact-render-to-string/src/index.d.ts +0 -16
  235. data/node_modules/preact-render-to-string/src/index.js +0 -462
  236. data/node_modules/preact-render-to-string/src/jsx.d.ts +0 -13
  237. data/node_modules/preact-render-to-string/src/jsx.js +0 -76
  238. data/node_modules/preact-render-to-string/src/polyfills.js +0 -8
  239. data/node_modules/preact-render-to-string/src/preact-render-to-string-tests.d.ts +0 -1
  240. data/node_modules/preact-render-to-string/src/util.js +0 -78
  241. data/node_modules/preact-render-to-string/typings.json +0 -5
  242. data/node_modules/pretty-format/.npmignore +0 -3
  243. data/node_modules/pretty-format/LICENSE.md +0 -15
  244. data/node_modules/pretty-format/README.md +0 -94
  245. data/node_modules/pretty-format/index.js +0 -343
  246. data/node_modules/pretty-format/package.json +0 -26
  247. data/node_modules/pretty-format/plugins/ReactElement.js +0 -74
  248. data/node_modules/pretty-format/plugins/ReactTestComponent.js +0 -58
  249. data/node_modules/pretty-format/printString.js +0 -7
  250. data/node_modules/wouter-preact/cjs/index.js +0 -180
  251. data/node_modules/wouter-preact/cjs/matcher.js +0 -72
  252. data/node_modules/wouter-preact/cjs/package.json +0 -1
  253. data/node_modules/wouter-preact/cjs/react-deps.js +0 -75
  254. data/node_modules/wouter-preact/cjs/static-location.js +0 -21
  255. data/node_modules/wouter-preact/cjs/use-location.js +0 -94
  256. data/node_modules/wouter-preact/index.d.ts +0 -110
  257. data/node_modules/wouter-preact/index.js +0 -178
  258. data/node_modules/wouter-preact/matcher.d.ts +0 -30
  259. data/node_modules/wouter-preact/matcher.js +0 -66
  260. data/node_modules/wouter-preact/package.json +0 -33
  261. data/node_modules/wouter-preact/react-deps.js +0 -15
  262. data/node_modules/wouter-preact/static-location.d.ts +0 -16
  263. data/node_modules/wouter-preact/static-location.js +0 -17
  264. data/node_modules/wouter-preact/use-location.d.ts +0 -43
  265. data/node_modules/wouter-preact/use-location.js +0 -86
  266. data/package.json +0 -8
data/lib/preact.rb CHANGED
@@ -1,296 +1,1587 @@
1
+ if RUBY_ENGINE == 'opal'
2
+ class VNode
3
+ # just a empty place holder to make is_a?(VNode) work
4
+ # internally using the js implementation
5
+ end
6
+ else
7
+ class VNode
8
+ # full ruby implementation
9
+ attr_accessor :__c
10
+ attr_reader :_children
11
+ attr_reader :_component
12
+ attr_reader :_depth
13
+ attr_reader :_dom
14
+ attr_reader :_hydrating
15
+ attr_reader :_nextDom
16
+ attr_reader :_original
17
+ attr_reader :constructor
18
+ attr_reader :key
19
+ attr_reader :props
20
+ attr_reader :ref
21
+ attr_reader :type
22
+
23
+ def initialize(type, props, key, ref, original)
24
+ @type = type
25
+ @props = props
26
+ @key = key
27
+ @ref = ref
28
+ @_depth = 0
29
+ @_original = original.nil? ? Preact._vnode_id += 1 : original
30
+ end
31
+ end
32
+ end
33
+
34
+ class Fragment
35
+ def initialize(props, _context)
36
+ @props = props
37
+ end
38
+
39
+ def render
40
+ @props[:children]
41
+ end
42
+ end
43
+
1
44
  module Preact
2
- %x{
3
- self.render_buffer = [];
4
-
5
- self.set_validate_prop = function(component, prop_name) {
6
- let core = component.preact_component;
7
- if (typeof core.propTypes == "undefined") {
8
- core.propTypes = {};
9
- core.propValidations = {};
10
- core.propValidations[prop_name] = {};
45
+ EMPTY_ARR = []
46
+ UNSAFE_NAME = /[\s\n\\\/='"\0<>]/
47
+
48
+ if RUBY_ENGINE == 'opal'
49
+ %x{
50
+ function _catchError(error, vnode, oldVNode) {
51
+ let component, ctor, handled;
52
+
53
+ for (; (vnode = vnode._parent); ) {
54
+ if ((component = vnode._component) && !component._processingException) {
55
+ try {
56
+ if (component["$respond_to?"]("get_derived_state_from_error")) {
57
+ component.$set_state(component.$get_derived_state_from_error(error));
58
+ handled = component._dirty;
59
+ }
60
+
61
+ if (component["$respond_to?"]("component_did_catch")) {
62
+ component.$component_did_catch(error);
63
+ handled = component._dirty;
64
+ }
65
+
66
+ // This is an error boundary. Mark it as having bailed out, and whether it was mid-hydration.
67
+ if (handled) {
68
+ return (component._pendingError = component);
69
+ }
70
+ } catch (e) {
71
+ error = e;
72
+ }
73
+ }
74
+ }
75
+
76
+ throw error;
11
77
  }
12
- core.propTypes[prop_name] = core.prototype.validateProp;
13
- };
14
-
15
- self.props_are_equal = function(this_props, next_props) {
16
- let counter = 0;
17
- for (var property in next_props) {
18
- counter++;
19
- if (next_props.hasOwnProperty(property)) {
20
- if (!this_props.hasOwnProperty(property)) { return false; };
21
- if (property === "children") { if (next_props.children !== this_props.children) { return false; }}
22
- else if (typeof next_props[property] === "object" && next_props[property] !== null && typeof next_props[property]['$!='] === "function" &&
23
- typeof this_props[property] !== "undefined" && this_props[property] !== null ) {
24
- if (#{ !! (`next_props[property]` != `this_props[property]`) }) { return false; }
25
- } else if (next_props[property] !== this_props[property]) { return false; }
78
+
79
+ const EMPTY_OBJ = {};
80
+ const EMPTY_ARR = [];
81
+ const slice = EMPTY_ARR.slice;
82
+
83
+ function assign(obj, props) {
84
+ for (let i in props) obj[i] = props[i];
85
+ return obj;
86
+ }
87
+
88
+ function applyRef(ref, value, vnode) {
89
+ try {
90
+ let converted_value;
91
+ if (value == null || typeof(value) === 'undefined' ) { converted_value = nil; }
92
+ else if (typeof value.$$class !== 'undefined') { converted_value = value; }
93
+ else if (value instanceof Element || value instanceof Node) { converted_value = #{Browser::Element.new(`value`)}; }
94
+ if (typeof ref === "function") ref.$call(converted_value);
95
+ else ref["$[]="]("current", converted_value);
96
+ } catch (e) {
97
+ _catchError(e, vnode);
26
98
  }
27
99
  }
28
- if (counter !== Object.keys(this_props).length) { return false; }
29
- return true;
30
- };
31
-
32
- self.state_is_not_equal = function(this_state, next_state) {
33
- let counter = 0;
34
- for (var property in next_state) {
35
- counter++;
36
- if (next_state.hasOwnProperty(property)) {
37
- if (!this_state.hasOwnProperty(property)) { return true; };
38
- if (typeof next_state[property] === "object" && next_state[property] !== null && typeof next_state[property]['$!='] === "function" &&
39
- typeof this_state[property] !== "undefined" && this_state[property] !== null) {
40
- if (#{ !! (`next_state[property]` != `this_state[property]`) }) { return true }
41
- } else if (next_state[property] !== this_state[property]) { return true }
100
+
101
+ function getDomSibling(vnode, childIndex) {
102
+ if (childIndex == null) {
103
+ // Use childIndex==null as a signal to resume the search from the vnode's sibling
104
+ return vnode._parent
105
+ ? getDomSibling(vnode._parent, vnode._parent._children.indexOf(vnode) + 1)
106
+ : null;
107
+ }
108
+
109
+ let sibling;
110
+ for (; childIndex < vnode._children.length; childIndex++) {
111
+ sibling = vnode._children[childIndex];
112
+
113
+ if (sibling != null && sibling._dom != null) {
114
+ // Since updateParentDomPointers keeps _dom pointer correct,
115
+ // we can rely on _dom to tell us if this subtree contains a
116
+ // rendered DOM node, and what the first rendered DOM node is
117
+ return sibling._dom;
118
+ }
42
119
  }
120
+
121
+ // If we get here, we have not found a DOM node in this vnode's children.
122
+ // We must resume from this vnode's sibling (in it's parent _children array)
123
+ // Only climb up and search the parent if we aren't searching through a DOM
124
+ // VNode (meaning we reached the DOM parent of the original vnode that began
125
+ // the search)
126
+ return typeof vnode.type == 'function' ? getDomSibling(vnode) : null;
43
127
  }
44
- if (counter !== Object.keys(this_state).length) { return true; }
45
- return false;
46
- };
47
-
48
- self.lower_camelize = function(snake_cased_word) {
49
- if (self.prop_dictionary[snake_cased_word]) { return self.prop_dictionary[snake_cased_word]; }
50
- let parts = snake_cased_word.split('_');
51
- let res = parts[0];
52
- for (let i = 1; i < parts.length; i++) {
53
- res += parts[i][0].toUpperCase() + parts[i].slice(1);
128
+
129
+ function placeChild(
130
+ parentDom,
131
+ childVNode,
132
+ oldVNode,
133
+ oldChildren,
134
+ newDom,
135
+ oldDom
136
+ ) {
137
+ let nextDom;
138
+ if (childVNode._nextDom !== undefined) {
139
+ // Only Fragments or components that return Fragment like VNodes will
140
+ // have a non-undefined _nextDom. Continue the diff from the sibling
141
+ // of last DOM child of this child VNode
142
+ nextDom = childVNode._nextDom;
143
+
144
+ // Eagerly cleanup _nextDom. We don't need to persist the value because
145
+ // it is only used by `diffChildren` to determine where to resume the diff after
146
+ // diffing Components and Fragments. Once we store it the nextDOM local var, we
147
+ // can clean up the property
148
+ childVNode._nextDom = undefined;
149
+ } else if (oldVNode == null || oldVNode === nil || newDom != oldDom || newDom.parentNode == null || newDom.parentNode === nil) {
150
+ outer: if (oldDom == null || oldDom === nil || oldDom.parentNode !== parentDom) {
151
+ parentDom.appendChild(newDom);
152
+ nextDom = null;
153
+ } else {
154
+ // `j<oldChildrenLength; j+=2` is an alternative to `j++<oldChildrenLength/2`
155
+ for (
156
+ let sibDom = oldDom, j = 0;
157
+ (sibDom = sibDom.nextSibling) && j < oldChildren.length;
158
+ j += 2
159
+ ) {
160
+ if (sibDom == newDom) {
161
+ break outer;
162
+ }
163
+ }
164
+ parentDom.insertBefore(newDom, oldDom);
165
+ nextDom = oldDom;
166
+ }
167
+ }
168
+
169
+ // If we have pre-calculated the nextDOM node, use it. Else calculate it now
170
+ // Strictly check for `undefined` here cuz `null` is a valid value of `nextDom`.
171
+ // See more detail in create-element.js:createVNode
172
+ if (nextDom !== undefined) {
173
+ oldDom = nextDom;
174
+ } else {
175
+ oldDom = newDom.nextSibling;
176
+ }
177
+
178
+ return oldDom;
54
179
  }
55
- self.prop_dictionary[snake_cased_word] = res;
56
- return res;
57
- };
58
-
59
- self.native_element_or_component_to_ruby = function (element) {
60
- if (element == null || typeof(element) === 'undefined' ) { return nil; }
61
- if (typeof element.__ruby_instance !== 'undefined') { return element.__ruby_instance; }
62
- if (element instanceof Element || element instanceof Node) { return #{Browser::Element.new(`element`)}; }
63
- return element;
64
- };
65
-
66
- self.native_to_ruby_event = function(event) {
67
- if ('target' in event) { return #{::Browser::Event.new(`event`)}; }
68
- else if (Array.isArray(event)) { return event; }
69
- else { return Opal.Hash.$new(event); }
70
- };
71
-
72
- self.internal_prepare_args_and_render = function(component, args, block) {
73
- const operain = self.internal_render;
74
- if (args.length > 0) {
75
- let last_arg = args[args.length - 1];
76
- if (last_arg && last_arg.constructor === String) {
77
- if (args.length === 1) { return operain(component, null, last_arg, null); }
78
- else { operain(component, args[0], last_arg, null); }
79
- } else { operain(component, args[0], null, block); }
80
- } else { operain(component, null, null, block); }
81
- };
82
-
83
- self.using_did_catch = false;
84
-
85
- self.active_components = [];
86
-
87
- self.active_component = function() {
88
- let length = self.active_components.length;
89
- if (length === 0) { return null; };
90
- return self.active_components[length-1];
91
- };
92
-
93
- self.active_redux_components = [];
94
-
95
- self.active_redux_component = function() {
96
- let length = self.active_redux_components.length;
97
- if (length === 0) { return null; };
98
- return self.active_redux_components[length-1];
99
- };
100
-
101
- self.register_active_component = function(component) {
102
- self.active_components.push(component);
103
- if (typeof(component.data_access) === 'function') {
104
- self.active_redux_components.push(component);
180
+
181
+ function reorderChildren(childVNode, oldDom, parentDom) {
182
+ // Note: VNodes in nested suspended trees may be missing _children.
183
+ let c = childVNode._children;
184
+ let tmp = 0;
185
+ for (; c && tmp < c.length; tmp++) {
186
+ let vnode = c[tmp];
187
+ if (vnode) {
188
+ // We typically enter this code path on sCU bailout, where we copy
189
+ // oldVNode._children to newVNode._children. If that is the case, we need
190
+ // to update the old children's _parent pointer to point to the newVNode
191
+ // (childVNode here).
192
+ vnode._parent = childVNode;
193
+
194
+ if (typeof vnode.type == 'function') {
195
+ oldDom = reorderChildren(vnode, oldDom, parentDom);
196
+ } else {
197
+ oldDom = placeChild(
198
+ parentDom,
199
+ vnode,
200
+ vnode,
201
+ c,
202
+ vnode._dom,
203
+ oldDom
204
+ );
205
+ }
206
+ }
207
+ }
208
+
209
+ return oldDom;
105
210
  }
106
- };
107
211
 
108
- self.unregister_active_component = function(component) {
109
- if (typeof(component.data_access) === 'function') {
110
- self.active_redux_components.pop();
212
+ function removeNode(node) {
213
+ let parentNode = node.parentNode;
214
+ if (parentNode) parentNode.removeChild(node);
111
215
  }
112
- self.active_components.pop();
113
- };
114
-
115
- self.prop_dictionary = {};
116
-
117
- self.to_native_preact_props = function(ruby_style_props) {
118
- let result = {};
119
- let keys = ruby_style_props.$$keys;
120
- let keys_length = keys.length;
121
- let key = '';
122
- for (let i = 0; i < keys_length; i++) {
123
- key = keys[i];
124
- let value = ruby_style_props.$$smap[key];
125
- if (key[0] === 'o' && key[1] === 'n' && key[2] === '_') {
126
- let type = typeof value;
127
- if (type === "function") {
128
- let active_c = self.active_component();
129
- result[self.lower_camelize(key)] = function(event, info) {
130
- let ruby_event = self.native_to_ruby_event(event);
131
- #{`active_c.__ruby_instance`.instance_exec(`ruby_event`, `info`, &`value`)};
132
- }
133
- } else if (typeof value.preact_event_handler_function === "function") {
134
- result[self.lower_camelize(key)] = value.preact_event_handler_function;
135
- } else if (type === "string" ) {
136
- let active_component = self.active_component();
137
- let method_ref;
138
- let method_name = '$' + value;
139
- if (typeof active_component[method_name] === "function") {
140
- // got a ruby instance
141
- if (active_component.native?.method_refs?.[value]) { method_ref = active_component.native.method_refs[value]; } // ruby instance with native
142
- else if (active_component.method_refs?.[value]) { method_ref = active_component.method_refs[value]; } // ruby function component
143
- else { method_ref = active_component.$method_ref(value); } // create the ref
144
- } else if (typeof active_component.__ruby_instance[method_name] === "function") {
145
- // got a native instance
146
- if (active_component.method_refs?.[value]) { method_ref = active_component.method_refs[value]; }
147
- else { method_ref = active_component.__ruby_instance.$method_ref(value); } // create ref for native
148
- }
149
- if (method_ref) {
150
- result[self.lower_camelize(key)] = method_ref.preact_event_handler_function;
216
+
217
+ self.unmount = function(vnode, parentVNode, skipRemove) {
218
+ let r;
219
+
220
+ if ((r = vnode.ref) && r && r !== nil) {
221
+ try {
222
+ if (typeof r === "function") {
223
+ applyRef(r, null, parentVNode);
151
224
  } else {
152
- let component_name;
153
- if (active_component.__ruby_instance) { component_name = active_component.__ruby_instance.$to_s(); }
154
- else { component_name = active_component.$to_s(); }
155
- #{Isomorfeus.raise_error(message: "Is #{`value`} a valid method of #{`component_name`}? If so then please use: #{`key`}: method_ref(:#{`value`}) within component: #{`component_name`}")}
225
+ let rc = r["$[]"]("current");
226
+ if (rc === nil || rc === vnode._dom) { applyRef(r, null, parentVNode); }
156
227
  }
157
- } else if (type === "object" && value === nil) {
158
- result[self.lower_camelize(key)] = null;
228
+ } catch (e) {
229
+ // ignore error, continue unmount
230
+ }
231
+ }
232
+
233
+ if ((r = vnode._component) != null && r !== nil) {
234
+ if (r["$respond_to?"]("component_will_unmount")) {
235
+ try {
236
+ r.$component_will_unmount();
237
+ } catch (e) {
238
+ _catchError(e, parentVNode);
239
+ }
240
+ }
241
+
242
+ r.base = r._parentDom = null;
243
+ }
244
+
245
+ if ((r = vnode._children)) {
246
+ for (let i = 0; i < r.length; i++) {
247
+ if (r[i]) {
248
+ self.unmount(r[i], parentVNode, typeof vnode.type != 'function');
249
+ }
250
+ }
251
+ }
252
+
253
+ if (!skipRemove && vnode._dom != null && vnode._dom !== nil) removeNode(vnode._dom);
254
+
255
+ // Must be set to `undefined` to properly clean up `_nextDom`
256
+ // for which `null` is a valid value. See comment in `create-element.js`
257
+ vnode._parent = vnode._dom = vnode._nextDom = undefined;
258
+ }
259
+
260
+ function diffChildren(
261
+ parentDom,
262
+ renderResult,
263
+ newParentVNode,
264
+ oldParentVNode,
265
+ globalContext,
266
+ isSvg,
267
+ excessDomChildren,
268
+ commitQueue,
269
+ oldDom,
270
+ isHydrating
271
+ ) {
272
+ let i, j, oldVNode, childVNode, newDom, firstChildDom, refs;
273
+
274
+ // This is a compression of oldParentVNode!=null && oldParentVNode != EMPTY_OBJ && oldParentVNode._children || EMPTY_ARR
275
+ // as EMPTY_OBJ._children should be `undefined`.
276
+ let oldChildren = (oldParentVNode && oldParentVNode !== nil && oldParentVNode._children) ? oldParentVNode._children : EMPTY_ARR;
277
+
278
+ let oldChildrenLength = oldChildren.length;
279
+
280
+ newParentVNode._children = [];
281
+ for (i = 0; i < renderResult.length; i++) {
282
+ childVNode = renderResult[i];
283
+
284
+ if (childVNode === nil || childVNode == null || typeof childVNode == 'boolean' || childVNode.$$is_boolean) {
285
+ childVNode = newParentVNode._children[i] = null;
286
+ }
287
+ // If this newVNode is being reused (e.g. <div>{reuse}{reuse}</div>) in the same diff,
288
+ // or we are rendering a component (e.g. setState) copy the oldVNodes so it can have
289
+ // it's own DOM & etc. pointers
290
+ else if (typeof childVNode == 'string' || typeof childVNode == 'number' || typeof childVNode == 'bigint') {
291
+ childVNode = newParentVNode._children[i] = self.createVNode(
292
+ null,
293
+ childVNode,
294
+ null,
295
+ null,
296
+ childVNode
297
+ );
298
+ } else if (childVNode.$$is_string || childVNode.$$is_number) {
299
+ let str = childVNode.valueOf();
300
+ childVNode = newParentVNode._children[i] = self.createVNode(
301
+ null,
302
+ str,
303
+ null,
304
+ null,
305
+ str
306
+ );
307
+ } else if (Array.isArray(childVNode)) {
308
+ childVNode = newParentVNode._children[i] = self.createVNode(
309
+ Opal.Fragment,
310
+ #{{ children: `childVNode` }},
311
+ null,
312
+ null,
313
+ null
314
+ );
315
+ } else if (childVNode._depth > 0) {
316
+ // VNode is already in use, clone it. This can happen in the following
317
+ // scenario:
318
+ // const reuse = <div />
319
+ // <div>{reuse}<span />{reuse}</div>
320
+ childVNode = newParentVNode._children[i] = self.createVNode(
321
+ childVNode.type,
322
+ childVNode.props,
323
+ childVNode.key,
324
+ childVNode.ref ? childVNode.ref : null,
325
+ childVNode._original
326
+ );
327
+ } else {
328
+ childVNode = newParentVNode._children[i] = childVNode;
329
+ }
330
+
331
+ if (childVNode === nil || childVNode == null) {
332
+ continue;
333
+ }
334
+ childVNode._parent = newParentVNode;
335
+ childVNode._depth = newParentVNode._depth + 1;
336
+
337
+ // Check if we find a corresponding element in oldChildren.
338
+ // If found, delete the array item by setting to `undefined`.
339
+ // We use `undefined`, as `null` is reserved for empty placeholders
340
+ // (holes).
341
+ oldVNode = oldChildren[i];
342
+
343
+ if (
344
+ oldVNode === null || oldVNode === nil ||
345
+ (oldVNode && oldVNode !== nil &&
346
+ childVNode.key == oldVNode.key &&
347
+ childVNode.type === oldVNode.type)
348
+ ) {
349
+ oldChildren[i] = undefined;
159
350
  } else {
160
- let active_component = self.active_component();
161
- let component_name;
162
- if (active_component.__ruby_instance) { component_name = active_component.__ruby_instance.$to_s(); }
163
- else { component_name = active_component.$to_s(); }
164
- #{Isomorfeus.raise_error(message: "Received invalid value for #{`key`} with #{`value`} within component: #{`component_name`}")}
165
- console.error( + key + " event handler:", value, " within component:", self.active_component());
166
- }
167
- } else if (key[0] === 'a' && key.startsWith("aria_")) {
168
- result[key.replace("_", "-")] = value;
169
- } else if (key === "style" || key === "theme") {
170
- if (typeof value.$to_n === "function") { value = value.$to_n() }
171
- result[key] = value;
351
+ // Either oldVNode === undefined or oldChildrenLength > 0,
352
+ // so after this loop oldVNode == null or oldVNode is a valid value.
353
+ for (j = 0; j < oldChildrenLength; j++) {
354
+ oldVNode = oldChildren[j];
355
+ // If childVNode is unkeyed, we only match similarly unkeyed nodes, otherwise we match by key.
356
+ // We always match by type (in either case).
357
+ if (
358
+ oldVNode && oldVNode !== nil &&
359
+ childVNode.key == oldVNode.key &&
360
+ childVNode.type === oldVNode.type
361
+ ) {
362
+ oldChildren[j] = undefined;
363
+ break;
364
+ }
365
+ oldVNode = null;
366
+ }
367
+ }
368
+
369
+ oldVNode = (oldVNode && oldVNode !== nil) ? oldVNode : EMPTY_OBJ;
370
+
371
+ // Morph the old element into the new one, but don't append it to the dom yet
372
+ diff(
373
+ parentDom,
374
+ childVNode,
375
+ oldVNode,
376
+ globalContext,
377
+ isSvg,
378
+ excessDomChildren,
379
+ commitQueue,
380
+ oldDom,
381
+ isHydrating
382
+ );
383
+
384
+ newDom = childVNode._dom;
385
+
386
+ if ((j = childVNode.ref) && j !== nil && oldVNode.ref != j) {
387
+ if (!refs) refs = [];
388
+ if (oldVNode.ref && oldVNode.ref !== nil) refs.push(oldVNode.ref, null, childVNode);
389
+ refs.push(j, childVNode._component || newDom, childVNode);
390
+ }
391
+
392
+ if (newDom != null) {
393
+ if (firstChildDom == null) {
394
+ firstChildDom = newDom;
395
+ }
396
+
397
+ if (
398
+ typeof childVNode.type == 'function' &&
399
+ childVNode._children === oldVNode._children
400
+ ) {
401
+ childVNode._nextDom = oldDom = reorderChildren(
402
+ childVNode,
403
+ oldDom,
404
+ parentDom
405
+ );
406
+ } else {
407
+ oldDom = placeChild(
408
+ parentDom,
409
+ childVNode,
410
+ oldVNode,
411
+ oldChildren,
412
+ newDom,
413
+ oldDom
414
+ );
415
+ }
416
+
417
+ if (typeof newParentVNode.type == 'function') {
418
+ // Because the newParentVNode is Fragment-like, we need to set it's
419
+ // _nextDom property to the nextSibling of its last child DOM node.
420
+ //
421
+ // `oldDom` contains the correct value here because if the last child
422
+ // is a Fragment-like, then oldDom has already been set to that child's _nextDom.
423
+ // If the last child is a DOM VNode, then oldDom will be set to that DOM
424
+ // node's nextSibling.
425
+ newParentVNode._nextDom = oldDom;
426
+ }
427
+ } else if (
428
+ oldDom &&
429
+ oldVNode._dom == oldDom &&
430
+ oldDom.parentNode != parentDom
431
+ ) {
432
+ // The above condition is to handle null placeholders. See test in placeholder.test.js:
433
+ // `efficiently replace null placeholders in parent rerenders`
434
+ oldDom = getDomSibling(oldVNode);
435
+ }
436
+ }
437
+
438
+ newParentVNode._dom = firstChildDom;
439
+
440
+ // Remove remaining oldChildren if there are any.
441
+ for (i = oldChildrenLength; i--; ) {
442
+ if (oldChildren[i] != null) {
443
+ if (
444
+ typeof newParentVNode.type == 'function' &&
445
+ oldChildren[i]._dom != null &&
446
+ oldChildren[i]._dom == newParentVNode._nextDom
447
+ ) {
448
+ // If the newParentVNode.__nextDom points to a dom node that is about to
449
+ // be unmounted, then get the next sibling of that vnode and set
450
+ // _nextDom to it
451
+ newParentVNode._nextDom = getDomSibling(oldParentVNode, i + 1);
452
+ }
453
+
454
+ self.unmount(oldChildren[i], oldChildren[i]);
455
+ }
456
+ }
457
+
458
+ // Set refs only after unmount
459
+ if (refs) {
460
+ for (i = 0; i < refs.length; i++) {
461
+ applyRef(refs[i], refs[++i], refs[++i]);
462
+ }
463
+ }
464
+ }
465
+
466
+ function eventProxy(e) {
467
+ this._listeners[e.type + false].$call(#{Browser::Event.new(`e`)});
468
+ }
469
+
470
+ function eventProxyCapture(e) {
471
+ this._listeners[e.type + true].$call(#{Browser::Event.new(`e`)});
472
+ }
473
+
474
+ const IS_NON_DIMENSIONAL = /acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;
475
+
476
+ function setStyle(style, key, value) {
477
+ if (key[0] === '-') {
478
+ style.setProperty(key, value);
479
+ } else if (value == null || value === nil) {
480
+ style[key] = '';
481
+ } else if (typeof value != 'number' || IS_NON_DIMENSIONAL.test(key)) {
482
+ style[key] = value;
172
483
  } else {
173
- result[self.lower_camelize(key)] = value;
484
+ style[key] = value + 'px';
174
485
  }
175
486
  }
176
- return result;
177
- };
178
487
 
179
- self.render_block_result = function(block_result) {
180
- if (block_result.constructor === String || block_result.constructor === Number) {
181
- Opal.Preact.render_buffer[Opal.Preact.render_buffer.length - 1].push(block_result);
488
+ self.setProperty = function(dom, name, value, oldValue, isSvg) {
489
+ let useCapture;
490
+
491
+ o: if (name === 'style') {
492
+ if (typeof value === 'string') {
493
+ dom.style.cssText = value;
494
+ } else {
495
+ if (typeof oldValue === 'string') {
496
+ dom.style.cssText = oldValue = '';
497
+ }
498
+
499
+ if (value && value !== nil && value["$is_a?"](Opal.Hash)) {
500
+ value = value.$to_n();
501
+ }
502
+
503
+ if (oldValue && oldValue !== nil && oldValue["$is_a?"](Opal.Hash)) {
504
+ oldValue = oldValue.$to_n();
505
+ }
506
+
507
+ if (oldValue && oldValue !== nil) {
508
+ for (name in oldValue) {
509
+ if (!(value && name in value)) {
510
+ setStyle(dom.style, name, '');
511
+ }
512
+ }
513
+ }
514
+
515
+ if (value && value !== nil) {
516
+ for (name in value) {
517
+ if (!oldValue || oldValue === nil || value[name] !== oldValue[name]) {
518
+ setStyle(dom.style, name, value[name]);
519
+ }
520
+ }
521
+ }
522
+ }
523
+ }
524
+ // Benchmark for comparison: https://esbench.com/bench/574c954bdb965b9a00965ac6
525
+ else if (name[0] === 'o' && name[1] === 'n' && name[2] === '_') {
526
+ useCapture = name !== (name = name.replace(/_capture$/, ''));
527
+
528
+ // Infer correct casing for DOM built-in events:
529
+ let namesl = name.slice(3);
530
+ let domname = 'on' + namesl;
531
+ if (domname in dom) name = namesl;
532
+ else name = namesl;
533
+
534
+ let evhandler = value;
535
+
536
+ if (!dom._listeners) dom._listeners = {};
537
+ dom._listeners[name + useCapture] = evhandler;
538
+
539
+ if (value && value !== nil) {
540
+ if (!oldValue || oldValue === nil) {
541
+ const handler = useCapture ? eventProxyCapture : eventProxy;
542
+ dom.addEventListener(name, handler, useCapture);
543
+ }
544
+ } else {
545
+ const handler = useCapture ? eventProxyCapture : eventProxy;
546
+ dom.removeEventListener(name, handler, useCapture);
547
+ }
548
+ } else if (name !== 'dangerouslySetInnerHTML') {
549
+ if (isSvg) {
550
+ // Normalize incorrect prop usage for SVG:
551
+ // - xlink:href / xlinkHref --> href (xlink:href was removed from SVG and isn't needed)
552
+ // - className --> class
553
+ name = name.replace(/xlink(H|:h)/, 'h').replace(/sName$/, 's');
554
+ } else if (
555
+ name !== 'href' &&
556
+ name !== 'list' &&
557
+ name !== 'form' &&
558
+ // Default value in browsers is `-1` and an empty string is
559
+ // cast to `0` instead
560
+ name !== 'tabIndex' &&
561
+ name !== 'download' &&
562
+ name in dom
563
+ ) {
564
+ try {
565
+ dom[name] = (value == null || value === nil) ? '' : value;
566
+ // labelled break is 1b smaller here than a return statement (sorry)
567
+ break o;
568
+ } catch (e) {}
569
+ }
570
+
571
+ // ARIA-attributes have a different notion of boolean values.
572
+ // The value `false` is different from the attribute not
573
+ // existing on the DOM, so we can't remove it. For non-boolean
574
+ // ARIA-attributes we could treat false as a removal, but the
575
+ // amount of exceptions would cost us too many bytes. On top of
576
+ // that other VDOM frameworks also always stringify `false`.
577
+
578
+ if (typeof value === 'function') {
579
+ // never serialize functions as attribute values
580
+ } else if (
581
+ value != null && value !== nil &&
582
+ (value !== false || (name[0] === 'a' && name[1] === 'r'))
583
+ ) {
584
+ dom.setAttribute(name, value);
585
+ } else {
586
+ dom.removeAttribute(name);
587
+ }
588
+ }
182
589
  }
183
- };
184
-
185
- self.internal_render = function(component, props, string_child, block) {
186
- const oper = Opal.global.Preact;
187
- const operabu = self.render_buffer;
188
- let native_props;
189
- if (props && props !== nil) { native_props = self.to_native_preact_props(props); }
190
- if (string_child) {
191
- operabu[operabu.length - 1].push(oper.createElement(component, native_props, string_child));
192
- } else if (block && block !== nil) {
193
- operabu.push([]);
194
- // console.log("internal_render pushed", Opal.Preact.render_buffer, Opal.Preact.render_buffer.toString());
195
- let block_result = block.$call();
196
- if (block_result && block_result !== nil) { Opal.Preact.render_block_result(block_result); }
197
- // console.log("internal_render popping", Opal.Preact.render_buffer, Opal.Preact.render_buffer.toString());
198
- let children = operabu.pop();
199
- operabu[operabu.length - 1].push(oper.createElement.apply(this, [component, native_props].concat(children)));
200
- } else {
201
- operabu[operabu.length - 1].push(oper.createElement(component, native_props));
590
+
591
+ function diff_props(dom, new_props, old_props, is_svg, hydrate) {
592
+ #{`old_props`.each do |prop, value|
593
+ `if (prop !== "children" && prop !== "key" && !(prop.$$is_string && Object.hasOwnProperty.call(new_props.$$smap, prop))) { self.setProperty(dom, prop, null, value, is_svg); }`
594
+ nil
595
+ end
596
+ `new_props`.each do |prop, value|
597
+ if (`!hydrate || (prop[0] === 'o' && prop[1] === 'n' && prop[2] === '_')` || value.is_a?(Proc)) &&
598
+ `prop !== "children" && prop !== "key" && prop !== "value" && prop !== "checked"` &&
599
+ ((p = `old_props`[prop]) ? p : nil) != value
600
+ `self.setProperty(dom, prop, value, old_props["$[]"](prop), is_svg)`
601
+ end
602
+ end
603
+ }
202
604
  }
203
- };
204
605
 
205
- self.deep_force_update = function(vnode) {
206
- vnode?.__c?.forceUpdate?.();
207
- if (vnode?.__k) {
208
- for (let i=0; i<vnode.__k.length; i++) {
209
- self.deep_force_update(vnode.__k[i]);
606
+ function diffElementNodes(
607
+ dom,
608
+ newVNode,
609
+ oldVNode,
610
+ globalContext,
611
+ isSvg,
612
+ excessDomChildren,
613
+ commitQueue,
614
+ isHydrating
615
+ ) {
616
+ let oldProps = oldVNode.props;
617
+ let newProps = newVNode.props;
618
+ let nodeType = newVNode.type;
619
+ let i = 0;
620
+
621
+ // Tracks entering and exiting SVG namespace when descending through the tree.
622
+ if (nodeType === 'svg') isSvg = true;
623
+
624
+ if (excessDomChildren != null) {
625
+ for (; i < excessDomChildren.length; i++) {
626
+ const child = excessDomChildren[i];
627
+
628
+ // if newVNode matches an element in excessDomChildren or the `dom`
629
+ // argument matches an element in excessDomChildren, remove it from
630
+ // excessDomChildren so it isn't later removed in diffChildren
631
+ if (
632
+ child &&
633
+ 'setAttribute' in child === !!nodeType &&
634
+ (nodeType ? child.localName === nodeType : child.nodeType === 3)
635
+ ) {
636
+ dom = child;
637
+ excessDomChildren[i] = null;
638
+ break;
639
+ }
640
+ }
210
641
  }
642
+
643
+ if (dom == null) {
644
+ if (nodeType === null) {
645
+ // createTextNode returns Text, we expect PreactElement
646
+ return document.createTextNode(newProps);
647
+ }
648
+
649
+ if (isSvg) {
650
+ dom = document.createElementNS(
651
+ 'http://www.w3.org/2000/svg',
652
+ // We know `newVNode.type` is a string
653
+ nodeType
654
+ );
655
+ } else {
656
+ let np = newProps.$to_n();
657
+ dom = document.createElement(
658
+ // We know `newVNode.type` is a string
659
+ nodeType,
660
+ np.is && np
661
+ );
662
+ }
663
+
664
+ // we created a new parent, so none of the previously attached children can be reused:
665
+ excessDomChildren = null;
666
+ // we are creating a new node, so we can assume this is a new subtree (in case we are hydrating), this deopts the hydrate
667
+ isHydrating = false;
668
+ }
669
+
670
+ if (nodeType === null) {
671
+ // During hydration, we still have to split merged text from SSR'd HTML.
672
+ if (!oldProps || oldProps === nil || (!oldProps["$=="](newProps) && (!isHydrating || !dom.data["$=="](newProps)))) {
673
+ dom.data = newProps;
674
+ }
675
+ } else {
676
+ // If excessDomChildren was not null, repopulate it with the current element's children:
677
+ excessDomChildren = excessDomChildren && slice.call(dom.childNodes);
678
+
679
+ oldProps = oldVNode.props || Opal.Hash.$new();
680
+
681
+ let oldHtml = oldProps["$[]"]("dangerouslySetInnerHTML");
682
+ let newHtml = newProps["$[]"]("dangerouslySetInnerHTML");
683
+
684
+ // During hydration, props are not diffed at all (including dangerouslySetInnerHTML)
685
+ // @TODO we should warn in debug mode when props don't match here.
686
+ if (!isHydrating) {
687
+ // But, if we are in a situation where we are using existing DOM (e.g. replaceNode)
688
+ // we should read the existing DOM attributes to diff them
689
+ if (excessDomChildren != null) {
690
+ oldProps = Opal.Hash.$new();
691
+ for (i = 0; i < dom.attributes.length; i++) {
692
+ oldProps["$[]="](dom.attributes[i].name, dom.attributes[i].value);
693
+ }
694
+ }
695
+
696
+ if (newHtml !== nil || oldHtml !== nil) {
697
+ // Avoid re-applying the same '__html' if it has not changed between re-render
698
+ if (
699
+ newHtml === nil ||
700
+ ((oldHtml === nil || newHtml["$[]"]("__html") != oldHtml["$[]"]("__html")) &&
701
+ newHtml["$[]"]("__html") !== dom.innerHTML)
702
+ ) {
703
+ dom.innerHTML = (newHtml !== nil && newHtml["$[]"]("__html")) || '';
704
+ }
705
+ }
706
+ }
707
+
708
+ diff_props(dom, newProps, oldProps, isSvg, isHydrating);
709
+
710
+ // If the new vnode didn't have dangerouslySetInnerHTML, diff its children
711
+ if (newHtml !== nil) {
712
+ newVNode._children = [];
713
+ } else {
714
+ i = newVNode.props["$[]"]("children");
715
+ diffChildren(
716
+ dom,
717
+ Array.isArray(i) ? i : [i],
718
+ newVNode,
719
+ oldVNode,
720
+ globalContext,
721
+ isSvg && nodeType !== 'foreignObject',
722
+ excessDomChildren,
723
+ commitQueue,
724
+ excessDomChildren
725
+ ? excessDomChildren[0]
726
+ : oldVNode._children && getDomSibling(oldVNode, 0),
727
+ isHydrating
728
+ );
729
+
730
+ // Remove children that are not part of any vnode.
731
+ if (excessDomChildren != null) {
732
+ for (i = excessDomChildren.length; i--; ) {
733
+ if (excessDomChildren[i] != null) removeNode(excessDomChildren[i]);
734
+ }
735
+ }
736
+ }
737
+
738
+ // (as above, don't diff props during hydration)
739
+ if (!isHydrating) {
740
+ if (
741
+ // instead of newProps["$key?"]("value")
742
+ Object.hasOwnProperty.call(newProps.$$smap, "value") &&
743
+ (i = newProps["$[]"]("value")) !== nil &&
744
+ // #2756 For the <progress>-element the initial value is 0,
745
+ // despite the attribute not being present. When the attribute
746
+ // is missing the progress bar is treated as indeterminate.
747
+ // To fix that we'll always update it when it is 0 for progress elements
748
+ (i !== dom.value || (nodeType === 'progress' && !i) ||
749
+ // This is only for IE 11 to fix <select> value not being updated.
750
+ // To avoid a stale select value we need to set the option.value
751
+ // again, which triggers IE11 to re-evaluate the select value
752
+ (nodeType === 'option' && i !== oldProps["$[]"]("value")))
753
+ ) {
754
+ self.setProperty(dom, 'value', i, oldProps["$[]"]("value"), false, null);
755
+ }
756
+ if (
757
+ Object.hasOwnProperty.call(newProps.$$smap, "checked") &&
758
+ (i = newProps["$[]"]("checked")) !== nil &&
759
+ i !== dom.checked
760
+ ) {
761
+ self.setProperty(dom, 'checked', i, oldProps["$[]"]("checked"), false, null);
762
+ }
763
+ }
764
+ }
765
+
766
+ return dom;
211
767
  }
212
- };
213
- }
214
768
 
215
- def self.create_element(type, props = nil, children = nil, &block)
216
- %x{
217
- const operabu = self.render_buffer;
218
- let component = null;
219
- let native_props = null;
220
- if (typeof type.preact_component !== 'undefined') { component = type.preact_component; }
221
- else { component = type; }
222
- if (block !== nil) {
223
- operabu.push([]);
224
- // console.log("create_element pushed", Opal.Preact.render_buffer, Opal.Preact.render_buffer.toString());
225
- let block_result = block.$call();
226
- if (block_result && block_result !== nil) { Opal.Preact.render_block_result(block_result); }
227
- // console.log("create_element popping", Opal.Preact.render_buffer, Opal.Preact.render_buffer.toString());
228
- children = operabu.pop();
229
- } else if (children === nil) { children = []; }
230
- else if (typeof children === 'string') { children = [children]; }
231
- if (props && props !== nil) { native_props = self.to_native_preact_props(props); }
232
- return Opal.global.Preact.createElement.apply(this, [component, native_props].concat(children));
769
+ function validate_props(newType, newProps) {
770
+ if (newType.declared_props && newType.declared_props !== nil) {
771
+ #{
772
+ `newType.declared_props`.each do |prop, value|
773
+ `newProps`[prop] = value[:default] if value.key?(:default) && !`newProps`.key?(prop)
774
+ end
775
+ }
776
+ if (Opal.Isomorfeus.development) { #{`newType`.validate_props(`newProps`)} }
777
+ }
778
+ }
779
+
780
+ function diff(
781
+ parentDom,
782
+ newVNode,
783
+ oldVNode,
784
+ globalContext,
785
+ isSvg,
786
+ excessDomChildren,
787
+ commitQueue,
788
+ oldDom,
789
+ isHydrating
790
+ ) {
791
+ let newType = newVNode.type;
792
+
793
+ // When passing through createElement it assigns the object
794
+ // constructor as undefined. This to prevent JSON-injection.
795
+ if (newVNode.constructor !== undefined) return null;
796
+
797
+ // If the previous diff bailed out, resume creating/hydrating.
798
+ if (oldVNode._hydrating != null) {
799
+ isHydrating = oldVNode._hydrating;
800
+ oldDom = newVNode._dom = oldVNode._dom;
801
+ // if we resume, we want the tree to be "unlocked"
802
+ newVNode._hydrating = null;
803
+ excessDomChildren = [oldDom];
804
+ }
805
+
806
+ try {
807
+ outer: if (typeof newType == 'function') {
808
+ let c, ctxType, isNew, oldProps, oldState, renderResult, snapshot, clearProcessingException;
809
+ let newProps = newVNode.props;
810
+
811
+ // Necessary for createContext api. Setting this property will pass
812
+ // the context value as `this.context` just for this component.
813
+ ctxType = newType.context_type;
814
+ let provider = (ctxType && ctxType !== nil) && globalContext["$[]"](ctxType.context_id);
815
+ let componentContext = (ctxType && ctxType !== nil) ? ((provider && provider !== nil) ? provider.props["$[]"]("value") : ctxType.value) : globalContext;
816
+
817
+ // Get component and set it to `c`
818
+ if (oldVNode._component) {
819
+ c = newVNode._component = oldVNode._component;
820
+ clearProcessingException = c._processingException = c._pendingError;
821
+ } else {
822
+ // Instantiate the new component
823
+ // validate props
824
+ validate_props(newType, newProps);
825
+ // The check above verifies that newType is suppose to be constructed
826
+ newVNode._component = c = newType.$new(newProps, componentContext);
827
+
828
+ if (provider && provider !== nil) provider.$sub(c);
829
+
830
+ c.props = newProps;
831
+ if (c.state === nil || !c.state) c.state = Opal.Hash.$new();
832
+ c.context = componentContext;
833
+ c._globalContext = globalContext;
834
+ isNew = c._dirty = true;
835
+ c._renderCallbacks = [];
836
+ }
837
+
838
+ // Invoke get_derived_state_from_props
839
+ if (c._nextState === nil) {
840
+ c._nextState = c.state;
841
+ }
842
+
843
+ if (!isNew) { validate_props(newType, newProps); }
844
+ if (c["$respond_to?"]("get_derived_state_from_props")) {
845
+ if (c._nextState == c.state) {
846
+ c._nextState = c._nextState.$dup();
847
+ }
848
+ c._nextState["$merge!"](c.$get_derived_state_from_props(newProps, c._nextState));
849
+ }
850
+
851
+ oldProps = c.props;
852
+ oldState = c.state;
853
+
854
+ // Invoke pre-render lifecycle methods
855
+ if (isNew) {
856
+ if (c["$respond_to?"]("component_did_mount")) {
857
+ c._renderCallbacks.push(c.$component_did_mount);
858
+ }
859
+ } else {
860
+ if (
861
+ (!c._force &&
862
+ c["$respond_to?"]("should_component_update?") &&
863
+ c["$should_component_update?"](
864
+ newProps,
865
+ c._nextState,
866
+ componentContext
867
+ ) === false) ||
868
+ newVNode._original === oldVNode._original
869
+ ) {
870
+ c.props = newProps;
871
+ c.state = c._nextState;
872
+ // More info about this here: https://gist.github.com/JoviDeCroock/bec5f2ce93544d2e6070ef8e0036e4e8
873
+ if (newVNode._original !== oldVNode._original) c._dirty = false;
874
+ c._vnode = newVNode;
875
+ newVNode._dom = oldVNode._dom;
876
+ newVNode._children = oldVNode._children;
877
+ newVNode._children.forEach(vnode => {
878
+ if (vnode) vnode._parent = newVNode;
879
+ });
880
+ if (c._renderCallbacks.length) {
881
+ commitQueue.push(c);
882
+ }
883
+
884
+ break outer;
885
+ }
886
+
887
+ if (c["$respond_to?"]("component_did_update")) {
888
+ c._renderCallbacks.push(() => {
889
+ c.$component_did_update(oldProps, oldState, snapshot);
890
+ });
891
+ }
892
+ }
893
+
894
+ c.context = componentContext;
895
+ c.props = newProps;
896
+ c._vnode = newVNode;
897
+ c._parentDom = parentDom;
898
+ c.state = c._nextState;
899
+ c._dirty = false;
900
+
901
+ renderResult = c.$render();
902
+
903
+ // Handle setState called in render, see #2553
904
+ c.state = c._nextState;
905
+
906
+ if (c.getChildContext != null) {
907
+ globalContext = globalContext.$merge(c.$get_child_context());
908
+ }
909
+
910
+ if (!isNew && c["$respond_to?"]("get_snapshot_before_update")) {
911
+ snapshot = c.$get_snapshot_before_update(oldProps, oldState);
912
+ }
913
+
914
+ if (renderResult !== nil && renderResult != null && renderResult.type === Opal.Fragment && renderResult.key == null) {
915
+ renderResult = renderResult.props["$[]"]("children");
916
+ }
917
+
918
+ diffChildren(
919
+ parentDom,
920
+ Array.isArray(renderResult) ? renderResult : [renderResult],
921
+ newVNode,
922
+ oldVNode,
923
+ globalContext,
924
+ isSvg,
925
+ excessDomChildren,
926
+ commitQueue,
927
+ oldDom,
928
+ isHydrating
929
+ );
930
+
931
+ c.base = newVNode._dom;
932
+
933
+ // We successfully rendered this VNode, unset any stored hydration/bailout state:
934
+ newVNode._hydrating = null;
935
+
936
+ if (c._renderCallbacks.length) {
937
+ commitQueue.push(c);
938
+ }
939
+
940
+ if (clearProcessingException) {
941
+ c._pendingError = c._processingException = null;
942
+ }
943
+
944
+ c._force = false;
945
+
946
+ } else if (
947
+ excessDomChildren == null &&
948
+ newVNode._original === oldVNode._original
949
+ ) {
950
+ newVNode._children = oldVNode._children;
951
+ newVNode._dom = oldVNode._dom;
952
+ } else {
953
+ newVNode._dom = diffElementNodes(
954
+ oldVNode._dom,
955
+ newVNode,
956
+ oldVNode,
957
+ globalContext,
958
+ isSvg,
959
+ excessDomChildren,
960
+ commitQueue,
961
+ isHydrating
962
+ );
963
+ }
964
+ } catch (e) {
965
+ newVNode._original = null;
966
+ // if hydrating or creating initial tree, bailout preserves DOM:
967
+ if (isHydrating || excessDomChildren != null) {
968
+ newVNode._dom = oldDom;
969
+ newVNode._hydrating = !!isHydrating;
970
+ excessDomChildren[excessDomChildren.indexOf(oldDom)] = null;
971
+ // ^ could possibly be simplified to:
972
+ // excessDomChildren.length = 0;
973
+ }
974
+ _catchError(e, newVNode, oldVNode);
975
+ }
976
+ }
977
+
978
+ function commitRoot(commitQueue, root) {
979
+ commitQueue.some(c => {
980
+ try {
981
+ // Reuse the commitQueue variable here so the type changes
982
+ commitQueue = c._renderCallbacks;
983
+ c._renderCallbacks = [];
984
+ commitQueue.some(cb => {
985
+ // See above comment on commitQueue
986
+ cb.call(c);
987
+ });
988
+ } catch (e) {
989
+ _catchError(e, c._vnode);
990
+ }
991
+ });
992
+ }
993
+
994
+ let vnodeId = 0;
995
+ const vnode_class = #{VNode};
996
+
997
+ function is_a_vnode(type) { return type === vnode_class; }
998
+ function is_nil() { return false; }
999
+
1000
+ self.createVNode = function(type, props, key, ref, original) {
1001
+ // V8 seems to be better at detecting type shapes if the object is allocated from the same call site
1002
+ // Do not inline into createElement and coerceToVNode!
1003
+ let eql;
1004
+ const vnode = {
1005
+ type,
1006
+ props,
1007
+ key,
1008
+ ref,
1009
+ _children: null,
1010
+ _parent: null,
1011
+ _depth: 0,
1012
+ _dom: null,
1013
+ // _nextDom must be initialized to undefined b/c it will eventually
1014
+ // be set to dom.nextSibling which can return `null` and it is important
1015
+ // to be able to distinguish between an uninitialized _nextDom and
1016
+ // a _nextDom that has been set to `null`
1017
+ _nextDom: undefined,
1018
+ _component: null,
1019
+ _hydrating: null,
1020
+ constructor: undefined,
1021
+ _original: original == null ? ++vnodeId : original,
1022
+ "$is_a?": is_a_vnode,
1023
+ "$==": eql = function(other) {
1024
+ for(let prop in vnode) {
1025
+ if (prop === 'props') {
1026
+ let res = vnode[prop]["$=="](other[prop]);
1027
+ if (!res) return false;
1028
+ } else if (vnode[prop] != other[prop]) {
1029
+ return false;
1030
+ }
1031
+ }
1032
+ return true;
1033
+ },
1034
+ "$eql?": eql,
1035
+ "$nil?": is_nil
1036
+ };
1037
+
1038
+ return vnode;
1039
+ }
1040
+
1041
+ self.render = function(vnode, parentDom, replaceNode) {
1042
+ // We abuse the `replaceNode` parameter in `hydrate()` to signal if we are in
1043
+ // hydration mode or not by passing the `hydrate` function instead of a DOM
1044
+ // element..
1045
+ let isHydrating = typeof replaceNode === 'function';
1046
+
1047
+ let reno = (replaceNode !== nil && replaceNode);
1048
+ let nohy_reno = (!isHydrating && reno);
1049
+
1050
+ // To be able to support calling `render()` multiple times on the same
1051
+ // DOM node, we need to obtain a reference to the previous tree. We do
1052
+ // this by assigning a new `_children` property to DOM nodes which points
1053
+ // to the last rendered tree. By default this property is not present, which
1054
+ // means that we are mounting a new tree for the first time.
1055
+ let oldVNode = isHydrating
1056
+ ? null
1057
+ : (reno && replaceNode._children) || parentDom._children;
1058
+
1059
+ let ov = (oldVNode && oldVNode !== nil);
1060
+
1061
+ vnode = (
1062
+ nohy_reno || parentDom
1063
+ )._children = self.$create_element(Opal.Fragment, nil, [vnode]);
1064
+
1065
+ // List of effects that need to be called after diffing.
1066
+ let commitQueue = [];
1067
+ diff(
1068
+ parentDom,
1069
+ // Determine the new vnode tree and store it on the DOM element on
1070
+ // our custom `_children` property.
1071
+ vnode,
1072
+ ov ? oldVNode : EMPTY_OBJ,
1073
+ Opal.Hash.$new(),
1074
+ parentDom.ownerSVGElement !== undefined,
1075
+ nohy_reno ? [replaceNode] : ov ? null : parentDom.firstChild ? slice.call(parentDom.childNodes) : null,
1076
+ commitQueue,
1077
+ nohy_reno ? replaceNode : ov ? oldVNode._dom : parentDom.firstChild,
1078
+ isHydrating
1079
+ );
1080
+
1081
+ // Flush all queued effects
1082
+ commitRoot(commitQueue, vnode);
1083
+ };
1084
+
1085
+ function updateParentDomPointers(vnode) {
1086
+ if ((vnode = vnode._parent) != null && vnode._component != null) {
1087
+ vnode._dom = vnode._component.base = null;
1088
+ for (let i = 0; i < vnode._children.length; i++) {
1089
+ let child = vnode._children[i];
1090
+ if (child != null && child._dom != null) {
1091
+ vnode._dom = vnode._component.base = child._dom;
1092
+ break;
1093
+ }
1094
+ }
1095
+
1096
+ return updateParentDomPointers(vnode);
1097
+ }
1098
+ }
1099
+
1100
+ function renderComponent(component) {
1101
+ let vnode = component._vnode,
1102
+ oldDom = vnode._dom,
1103
+ parentDom = component._parentDom;
1104
+
1105
+ if (parentDom) {
1106
+ let commitQueue = [];
1107
+ const oldVNode = assign({}, vnode);
1108
+ oldVNode._original = vnode._original + 1;
1109
+
1110
+ diff(
1111
+ parentDom,
1112
+ vnode,
1113
+ oldVNode,
1114
+ component._globalContext,
1115
+ parentDom.ownerSVGElement !== undefined,
1116
+ vnode._hydrating != null ? [oldDom] : null,
1117
+ commitQueue,
1118
+ (oldDom == null || oldDom === nil) ? getDomSibling(vnode) : oldDom,
1119
+ vnode._hydrating
1120
+ );
1121
+ commitRoot(commitQueue, vnode);
1122
+
1123
+ if (vnode._dom != oldDom) {
1124
+ updateParentDomPointers(vnode);
1125
+ }
1126
+ }
1127
+ }
1128
+
1129
+ self.process = function() {
1130
+ let queue;
1131
+ while ((self.process._rerenderCount = self.rerender_queue.length)) {
1132
+ queue = self.rerender_queue.sort((a, b) => a._vnode._depth - b._vnode._depth);
1133
+ self.rerender_queue = [];
1134
+ // Don't update `renderCount` yet. Keep its value non-zero to prevent unnecessary
1135
+ // process() calls from getting scheduled while `queue` is still being consumed.
1136
+ queue.some(c => {
1137
+ if (c._dirty) renderComponent(c);
1138
+ });
1139
+ }
1140
+ }
1141
+ self.process._rerenderCount = 0;
233
1142
  }
1143
+ else
1144
+ IGNORED_PROPS = %i[key ref __self __source].freeze
1145
+ IS_NON_DIMENSIONAL = /acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|^--/i
1146
+ JS_TO_CSS = {}
1147
+ ENCODED_ENTITIES = /[&<>"]/
1148
+ VOID_ELEMENTS = /^(area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)$/
234
1149
  end
235
1150
 
236
- def self.to_child_array(props_children)
237
- `Opal.global.Preact.toChildArray(children)`
238
- end
1151
+ class << self
1152
+ def _ctxi
1153
+ @_ctxi ||= 0
1154
+ end
239
1155
 
240
- def self.clone_element(native_preact_element, props = nil, children = nil, &block)
241
- block_result = `null`
242
- if block_given?
243
- block_result = block.call
244
- block_result = `null` unless block_result
1156
+ def _ctxi=(i)
1157
+ @_ctxi = i
245
1158
  end
246
- native_props = props ? `Opal.Preact.to_native_preact_props(props)` : `null`
247
- `Opal.global.Preact.cloneElement(native_preact_element, native_props, block_result)`
248
- end
249
1159
 
250
- def self.create_context(const_name, default_value)
251
- %x{
252
- Opal.global[const_name] = Opal.global.Preact.createContext(default_value);
253
- var new_const = #{Preact::ContextWrapper.new(`Opal.global[const_name]`)};
254
- #{Object.const_set(const_name, `new_const`)};
255
- return new_const;
256
- }
257
- end
1160
+ def _context_id
1161
+ "__cC#{self._ctxi += 1}"
1162
+ end
258
1163
 
259
- def self.create_ref
260
- Preact::Ref.new(`Opal.global.Preact.createRef()`)
261
- end
1164
+ def clone_element(vnode, props = nil, children = nil)
1165
+ normalized_props = {}
1166
+ if RUBY_ENGINE == 'opal'
1167
+ normalized_props.merge!(`vnode.props`)
1168
+ else
1169
+ normalized_props.merge!(vnode.props)
1170
+ end
262
1171
 
263
- def self.hydrate(native_preact_element, container_node, replace_node)
264
- `Opal.global.Preact.hydrate(native_preact_element, container_node)`
265
- end
1172
+ if props
1173
+ normalized_props.merge!(props)
1174
+ key = normalized_props.delete(:key)
1175
+ ref = normalized_props.delete(:ref)
1176
+ else
1177
+ key = nil
1178
+ ref = nil
1179
+ end
266
1180
 
267
- def self.location_hook(location)
268
- %x{
269
- if (Opal.global.locationHook) { return Opal.global.locationHook; }
270
- else if (Opal.global.staticLocationHook) { return Opal.global.staticLocationHook(location); }
271
- else { #{raise "Wouter Location Hooks not imported!"}; }
272
- }
273
- end
1181
+ normalized_props[:children] = children unless children.nil?
274
1182
 
275
- def self.render(native_preact_element, container_node, replace_node)
276
- # container is a native DOM element
277
- if block_given?
278
- `Opal.global.Preact.render(native_preact_element, container_node, function() { block.$call() })`
279
- else
280
- `Opal.global.Preact.render(native_preact_element, container_node)`
1183
+ if RUBY_ENGINE == 'opal'
1184
+ `self.createVNode(vnode.type, normalized_props, #{key || `vnode.key`}, #{ref || `vnode.ref`}, null)`
1185
+ else
1186
+ _create_vnode(vnode.type, normalized_props, key || vnode.key, ref || vnode.ref, nil)
1187
+ end
281
1188
  end
282
- end
283
1189
 
284
- # render_to_string is in top_level_ssr.rb
1190
+ def create_element(type, props = nil, children = nil, &block)
1191
+ if props
1192
+ if props.is_a?(Hash)
1193
+ normalized_props = props.dup
1194
+ key = normalized_props.delete(:key)
1195
+ ref = normalized_props.delete(:ref)
1196
+ else
1197
+ children = props
1198
+ normalized_props = {}
1199
+ key = nil
1200
+ ref = nil
1201
+ end
1202
+ else
1203
+ normalized_props = {}
1204
+ key = nil
1205
+ ref = nil
1206
+ end
285
1207
 
286
- def self.unmount_component_at_node(element_or_query)
287
- if `(typeof element_or_query === 'string')` || (`(typeof element_or_query.$class === 'function')` && element_or_query.class == String)
288
- element = `document.body.querySelector(element_or_query)`
289
- elsif `(typeof element_or_query.$is_a === 'function')` && element_or_query.is_a?(Browser::Element)
290
- element = element_or_query.to_n
291
- else
292
- element = element_or_query
1208
+ if block_given?
1209
+ pr = render_buffer
1210
+ pr.push([])
1211
+ block_result = block.call
1212
+ children = pr.pop
1213
+ if Preact.is_renderable?(block_result)
1214
+ children.push(block_result)
1215
+ end
1216
+ end
1217
+
1218
+ normalized_props[:children] = children unless children.nil?
1219
+
1220
+ if RUBY_ENGINE == 'opal'
1221
+ `self.createVNode(type, normalized_props, key, ref, null)`
1222
+ else
1223
+ _create_vnode(type, normalized_props, key, ref, nil)
1224
+ end
293
1225
  end
294
- `Opal.global.Preact.render(null, element)`
1226
+
1227
+ def create_context(const_name, default_value = nil)
1228
+ context = Preact::Context.new(default_value)
1229
+ Object.const_set(const_name, context)
1230
+ end
1231
+
1232
+ def _init_render
1233
+ self.render_buffer = []
1234
+ self.rerender_queue = []
1235
+ Isomorfeus.reset_something_loading
1236
+ end
1237
+
1238
+ def is_renderable?(block_result)
1239
+ block_result &&
1240
+ (block_result.is_a?(VNode) || block_result.is_a?(String) || block_result.is_a?(Numeric) ||
1241
+ (block_result.is_a?(Array) && block_result.length > 0 && is_renderable?(block_result[0])))
1242
+ end
1243
+
1244
+ if RUBY_ENGINE == 'opal'
1245
+ attr_accessor :_vnode_id
1246
+ attr_accessor :render_buffer
1247
+ attr_accessor :rerender_queue
1248
+
1249
+ def _enqueue_render(c)
1250
+ if ((`!c._dirty` && (`c._dirty = true`) && rerender_queue.push(c) && `!self.process._rerenderCount++`))
1251
+ `setTimeout(self.process)`
1252
+ end
1253
+ end
1254
+
1255
+ def _render_element(component, props, &block)
1256
+ %x{
1257
+ let opr = Opal.Preact.render_buffer;
1258
+ opr[opr.length-1].push(#{self.create_element(component, props, nil, &block)});
1259
+ }
1260
+ nil
1261
+ end
1262
+
1263
+ def hydrate(vnode, container_node)
1264
+ render(vnode, container_node, `self.render`)
1265
+ end
1266
+
1267
+ def element_or_query_to_n(element_or_query)
1268
+ if `!element_or_query || element_or_query === nil`
1269
+ return `null`
1270
+ elsif `(element_or_query instanceof HTMLElement)`
1271
+ return element_or_query
1272
+ elsif `(typeof element_or_query === 'string')` || element_or_query.is_a?(String)
1273
+ return `document.body.querySelector(element_or_query)`
1274
+ elsif `(typeof element_or_query === 'function')`
1275
+ return element_or_query
1276
+ elsif element_or_query.is_a?(Browser::Element)
1277
+ return element_or_query.to_n
1278
+ else
1279
+ return element_or_query
1280
+ end
1281
+ end
1282
+
1283
+ def render(vnode, container_node, replace_node = nil)
1284
+ _init_render
1285
+ container_node = element_or_query_to_n(container_node)
1286
+ replace_node = element_or_query_to_n(replace_node)
1287
+ `self.render(vnode, container_node, replace_node)`
1288
+ end
1289
+
1290
+ def unmount_component_at_node(element_or_query)
1291
+ element_or_query = element_or_query_to_n(element_or_query)
1292
+ `self.render(null, element_or_query)`
1293
+ end
1294
+ else # RUBY_ENGINE
1295
+ def _vnode_id
1296
+ Thread.current[:@_isomorfeus_preact_vnode_id] ||= 0
1297
+ end
1298
+
1299
+ def _vnode_id=(i)
1300
+ Thread.current[:@_isomorfeus_preact_vnode_id] = i
1301
+ end
1302
+
1303
+ def render_buffer
1304
+ Thread.current[:@_isomorfeus_preact_render_buffer]
1305
+ end
1306
+
1307
+ def render_buffer=(i)
1308
+ Thread.current[:@_isomorfeus_preact_render_buffer] = i
1309
+ end
1310
+
1311
+ def rerender_queue
1312
+ Thread.current[:@_isomorfeus_preact_rerender_queue]
1313
+ end
1314
+
1315
+ def rerender_queue=(i)
1316
+ Thread.current[:@_isomorfeus_preact_rerender_queue] = i
1317
+ end
1318
+
1319
+ def _create_vnode(type, props, key, ref, original)
1320
+ VNode.new(type, props, key, ref, original)
1321
+ end
1322
+
1323
+ def _encode_entities(input)
1324
+ s = input.to_s
1325
+ return s unless ENCODED_ENTITIES.match?(s)
1326
+ s.gsub(/&/, '&amp;').gsub(/</, '&lt;').gsub(/>/, '&gt;').gsub(/"/, '&quot;')
1327
+ # TODO performance maybe, maybe similar to new js way, need to measure
1328
+ # for (; i<str.length; i++) {
1329
+ # switch (str.charCodeAt(i)) {
1330
+ # case 60: ch = '&lt;'; break;
1331
+ # case 62: ch = '&gt;'; break;
1332
+ # case 34: ch = '&quot;'; break;
1333
+ # case 38: ch = '&amp;'; break;
1334
+ # default: continue;
1335
+ # }
1336
+ # if (i > start) out += str.slice(start, i);
1337
+ # out += ch;
1338
+ # start = i + 1;
1339
+ # }
1340
+ end
1341
+
1342
+ def _get_children(accumulator, children)
1343
+ if children.is_a?(Array)
1344
+ children.reduce(accumulator) { |accumulator, child| _get_children(accumulator, child) }
1345
+ elsif children != nil && children != false
1346
+ accumulator.push(children)
1347
+ end
1348
+ accumulator
1349
+ end
1350
+
1351
+ def _style_obj_to_css(v)
1352
+ str = ''
1353
+ v.each do |prop, val|
1354
+ if val != nil && val != ''
1355
+ str << ' ' if !str.empty?
1356
+ prop_s = prop.to_s
1357
+ str << prop_s[0] == '-' ? prop_s : JS_TO_CSS[prop] || (JS_TO_CSS[prop] = prop.gsub(/([A-Z])/, "-\\1").downcase)
1358
+ str << ': '
1359
+ str << "#{val}"
1360
+ str << 'px' if val.is_a?(Numeric) && IS_NON_DIMENSIONAL.match?(prop_s) == false
1361
+ str << ';'
1362
+ end
1363
+ end
1364
+ return str.empty? ? nil : str
1365
+ end
1366
+
1367
+ def _render_element(element, props, &block)
1368
+ pr = Preact.render_buffer
1369
+ pr[pr.length-1].push(create_element(element, props, nil, &block))
1370
+ nil
1371
+ end
1372
+
1373
+ def _render_to_string(vnode, context, opts, inner = nil, is_svg_mode = false, select_value = nil)
1374
+ return '' if !vnode || vnode == true
1375
+
1376
+ if vnode.is_a?(String) # text nodes
1377
+ return _encode_entities(vnode)
1378
+ elsif vnode.is_a?(Numeric) # numeric nodes
1379
+ return vnode.to_s
1380
+ end
1381
+
1382
+ if vnode.is_a?(Array) # array of nodes
1383
+ rendered = ''
1384
+ vnode.each_index do |i|
1385
+ rendered << _render_to_string(vnode[i], context, opts, inner, is_svg_mode, select_value)
1386
+ end
1387
+ return rendered
1388
+ end
1389
+
1390
+ node_name = vnode.type
1391
+ props = vnode.props
1392
+ is_component = false
1393
+ is_fragment = false
1394
+
1395
+ # components
1396
+ if !node_name.is_a?(String) && (node_name.ancestors.include?(Preact::Component) || is_fragment = node_name.ancestors.include?(Fragment))
1397
+ is_component = true
1398
+ if opts && opts[:shallow] && (inner || opts[:render_root_component] == false)
1399
+ node_name = node_name.class.to_s
1400
+ elsif is_fragment
1401
+ children = []
1402
+ _get_children(children, vnode.props[:children])
1403
+ return _render_to_string(children, context, opts, opts&.fetch(:shallow_high_order) != false, is_svg_mode, select_value)
1404
+ else
1405
+ rendered = nil
1406
+
1407
+ vnode.__c = {
1408
+ __v: vnode,
1409
+ context: context,
1410
+ props: vnode.props,
1411
+ __d: true
1412
+ }
1413
+
1414
+ c = vnode.__c
1415
+ # silently drop state updates
1416
+ mark_as_dirty = proc { c[:__d] = true }
1417
+ c[:set_state] = mark_as_dirty,
1418
+ c[:force_update] = mark_as_dirty,
1419
+
1420
+ # class-based components
1421
+ cx_type = node_name.context_type
1422
+ provider = cx_type && context[cx_type.context_id]
1423
+ cctx = cx_type ? (provider ? provider.props[:value] : cx_type.value) : context
1424
+
1425
+ # validate props
1426
+ declared_props = node_name.instance_variable_get(:@declared_props)
1427
+ if declared_props
1428
+ declared_props.each do |prop, value|
1429
+ props[prop] = value[:default] if !props.key?(prop) && value.key?(:default)
1430
+ end
1431
+ node_name.validate_props(props) if Isomorfeus.development?
1432
+ end
1433
+
1434
+ c = vnode.__c = node_name.new(props, cctx)
1435
+ c.__v = vnode
1436
+ # turn off stateful re-rendering:
1437
+ c._dirty = c.__d = true
1438
+ c.instance_variable_set(:@props, props) if c.props.nil?
1439
+ c.instance_variable_set(:@state, {}) if c.state.nil?
1440
+
1441
+ c._next_state = c.__s = c.state if c._next_state == nil && c.__s == nil
1442
+
1443
+ c.instance_variable_set(:@context, cctx)
1444
+
1445
+ if c.respond_to?(:get_derived_state_from_props)
1446
+ c.instance_variable_set(:@state, {}.merge!(c.state, c.get_derived_state_from_props(c.props, c.state)))
1447
+ end
1448
+
1449
+ rendered = c.render
1450
+
1451
+ context = {}.merge!(context, c.get_child_context) if c.respond_to?(:get_child_context)
1452
+
1453
+ return _render_to_string(rendered, context, opts, opts&.fetch(:shallow_high_order) != false, is_svg_mode, select_value)
1454
+ end
1455
+ end
1456
+
1457
+ # render JSX to HTML
1458
+ node_name_s = node_name.to_s
1459
+ s = "<#{node_name_s}"
1460
+ prop_children = nil
1461
+ html = nil
1462
+
1463
+ if props
1464
+ attrs = props.keys
1465
+
1466
+ # allow sorting lexicographically for more determinism (useful for tests, such as via preact-jsx-chai)
1467
+ attrs.sort! if opts && opts[:sort_attributes] == true
1468
+
1469
+ attrs.each do |name|
1470
+ v = props[name]
1471
+ if name == :children
1472
+ prop_children = v
1473
+ next
1474
+ end
1475
+
1476
+ next if UNSAFE_NAME.match?(name.to_s)
1477
+ next if !(opts && opts[:all_attributes]) && IGNORED_PROPS.include?(name)
1478
+
1479
+ if name == :default_value
1480
+ name = :value
1481
+ elsif name == :class_name
1482
+ next if props[:class] != nil
1483
+ name = :class
1484
+ elsif is_svg_mode && name.to_s.match?(/^xlink:?./)
1485
+ name = name.to_s.downcase.gsub(/^xlink:?/, 'xlink:')
1486
+ end
1487
+
1488
+ if name == :html_for
1489
+ next if props.key?(:for)
1490
+ name = :for
1491
+ end
1492
+
1493
+ v = _style_obj_to_css(v) if name == :style && v && v.is_a?(Hash)
1494
+
1495
+ # always use string values instead of booleans for aria attributes
1496
+ # also see https://github.com/preactjs/preact/pull/2347/files
1497
+ v = v.to_s if name.to_s.start_with?('aria') && (v == true || v == false)
1498
+
1499
+ if name == :dangerouslySetInnerHTML
1500
+ html = v && v[:__html]
1501
+ elsif node_name == :textarea && name == :value
1502
+ # <textarea value="a&b"> --> <textarea>a&amp;b</textarea>
1503
+ prop_children = v
1504
+ elsif name.to_s.start_with?('on_')
1505
+ next
1506
+ elsif v || v == 0 || v == '' && !v.is_a?(Proc)
1507
+ if v == true || v == ''
1508
+ v = name
1509
+ # in non-xml mode, allow boolean attributes
1510
+ if !opts || !opts[:xml]
1511
+ s << " #{name}"
1512
+ next
1513
+ end
1514
+ end
1515
+
1516
+ if name == :value
1517
+ if node_name == :select
1518
+ select_value = v
1519
+ next
1520
+ elsif (
1521
+ # If we're looking at an <option> and it's the currently selected one
1522
+ node_name == :option &&
1523
+ select_value == v &&
1524
+ # and the <option> doesn't already have a selected attribute on it
1525
+ props[:selected] == nil
1526
+ )
1527
+ s << ' selected'
1528
+ end
1529
+ end
1530
+ s << " #{name}=\"#{_encode_entities(v)}\""
1531
+ end
1532
+ end
1533
+ end
1534
+
1535
+ s << '>'
1536
+
1537
+ raise "#{node_name_s} is not a valid HTML tag name in #{s}" if (UNSAFE_NAME.match?(node_name_s))
1538
+
1539
+ is_void = VOID_ELEMENTS.match?(node_name_s)
1540
+ pieces = []
1541
+
1542
+ if html
1543
+ s << html
1544
+ elsif prop_children
1545
+ children = []
1546
+ if _get_children(children, prop_children).size > 0
1547
+ last_was_text = false
1548
+
1549
+ children.each do |child|
1550
+ if child != nil && child != false
1551
+ child_svg_mode = node_name == :svg ? true : ((node_name == :foreignObject) ? false : is_svg_mode)
1552
+ ret = _render_to_string(child, context, opts, true, child_svg_mode, select_value)
1553
+
1554
+ # Skip if we received an empty string
1555
+ if ret
1556
+ pieces.push(ret)
1557
+ end
1558
+ end
1559
+ end
1560
+ end
1561
+ end
1562
+
1563
+ if (pieces.length > 0) || html
1564
+ s << pieces.join('')
1565
+ elsif opts && opts[:xml]
1566
+ return s.chop! << ' />'
1567
+ end
1568
+
1569
+ if is_void && !children && !html
1570
+ s = s.sub(/>$/, ' />')
1571
+ else
1572
+ s << "</#{node_name}>"
1573
+ end
1574
+
1575
+ return s
1576
+ end
1577
+
1578
+ def render_to_string(vnode, context = nil, opts = nil)
1579
+ _init_render
1580
+ context = {} unless context
1581
+ _render_to_string(vnode, context, opts)
1582
+ end
1583
+ end # RUBY_ENGINE
295
1584
  end
296
1585
  end
1586
+
1587
+ Preact._vnode_id = 0