xote 6.1.2 → 6.2.0

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.
@@ -0,0 +1,24 @@
1
+ let signalTextStart = "<!--$-->"
2
+ let signalTextEnd = "<!--/$-->"
3
+ let signalTextStartContent = "$"
4
+ let signalTextEndContent = "/$"
5
+
6
+ let signalFragmentStart = "<!--#-->"
7
+ let signalFragmentEnd = "<!--/#-->"
8
+ let signalFragmentStartContent = "#"
9
+ let signalFragmentEndContent = "/#"
10
+
11
+ let keyedListStart = "<!--kl-->"
12
+ let keyedListEnd = "<!--/kl-->"
13
+ let keyedListStartContent = "kl"
14
+ let keyedListEndContent = "/kl"
15
+
16
+ let keyedItemPrefixContent = "k:"
17
+ let keyedItemStart = (key: string): string => `<!--${keyedItemPrefixContent}${key}-->`
18
+ let keyedItemEnd = "<!--/k-->"
19
+ let keyedItemEndContent = "/k"
20
+
21
+ let lazyComponentStart = "<!--lc-->"
22
+ let lazyComponentEnd = "<!--/lc-->"
23
+ let lazyComponentStartContent = "lc"
24
+ let lazyComponentEndContent = "/lc"
@@ -0,0 +1,68 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+
4
+ let keyedItemPrefixContent = "k:";
5
+
6
+ function keyedItemStart(key) {
7
+ return `<!--` + keyedItemPrefixContent + key + `-->`;
8
+ }
9
+
10
+ let signalTextStart = "<!--$-->";
11
+
12
+ let signalTextEnd = "<!--/$-->";
13
+
14
+ let signalTextStartContent = "$";
15
+
16
+ let signalTextEndContent = "/$";
17
+
18
+ let signalFragmentStart = "<!--#-->";
19
+
20
+ let signalFragmentEnd = "<!--/#-->";
21
+
22
+ let signalFragmentStartContent = "#";
23
+
24
+ let signalFragmentEndContent = "/#";
25
+
26
+ let keyedListStart = "<!--kl-->";
27
+
28
+ let keyedListEnd = "<!--/kl-->";
29
+
30
+ let keyedListStartContent = "kl";
31
+
32
+ let keyedListEndContent = "/kl";
33
+
34
+ let keyedItemEnd = "<!--/k-->";
35
+
36
+ let keyedItemEndContent = "/k";
37
+
38
+ let lazyComponentStart = "<!--lc-->";
39
+
40
+ let lazyComponentEnd = "<!--/lc-->";
41
+
42
+ let lazyComponentStartContent = "lc";
43
+
44
+ let lazyComponentEndContent = "/lc";
45
+
46
+ export {
47
+ signalTextStart,
48
+ signalTextEnd,
49
+ signalTextStartContent,
50
+ signalTextEndContent,
51
+ signalFragmentStart,
52
+ signalFragmentEnd,
53
+ signalFragmentStartContent,
54
+ signalFragmentEndContent,
55
+ keyedListStart,
56
+ keyedListEnd,
57
+ keyedListStartContent,
58
+ keyedListEndContent,
59
+ keyedItemPrefixContent,
60
+ keyedItemStart,
61
+ keyedItemEnd,
62
+ keyedItemEndContent,
63
+ lazyComponentStart,
64
+ lazyComponentEnd,
65
+ lazyComponentStartContent,
66
+ lazyComponentEndContent,
67
+ }
68
+ /* No side effect */
@@ -0,0 +1,46 @@
1
+ let isReactiveProp = (value: 'a): bool => {
2
+ ignore(value)
3
+ %raw(`value && typeof value === 'object' && ('TAG' in value) && (value.TAG === 'Static' || value.TAG === 'Reactive')`)
4
+ }
5
+
6
+ let toStringAttr = (key: string, value: 'a): (string, View.attrValue) => {
7
+ if isReactiveProp(value) {
8
+ let prop: Prop.t<string> = Obj.magic(value)
9
+ switch prop {
10
+ | Static(value) => View.attr(key, value)
11
+ | Reactive(signal) => View.signalAttr(key, signal)
12
+ }
13
+ } else if typeof(value) == #function {
14
+ let compute: unit => string = Obj.magic(value)
15
+ View.computedAttr(key, compute)
16
+ } else if typeof(value) == #object {
17
+ let signal: Signal.t<string> = Obj.magic(value)
18
+ View.signalAttr(key, signal)
19
+ } else {
20
+ let value: string = Obj.magic(value)
21
+ View.attr(key, value)
22
+ }
23
+ }
24
+
25
+ let toBoolAttr = (key: string, value: 'a): (string, View.attrValue) => {
26
+ if isReactiveProp(value) {
27
+ let prop: Prop.t<bool> = Obj.magic(value)
28
+ switch prop {
29
+ | Static(value) => View.attr(key, RuntimeAttr.boolToString(value))
30
+ | Reactive(signal) => {
31
+ let stringSignal = Computed.make(() => RuntimeAttr.boolToString(Signal.get(signal)))
32
+ View.signalAttr(key, stringSignal)
33
+ }
34
+ }
35
+ } else if typeof(value) == #function {
36
+ let compute: unit => bool = Obj.magic(value)
37
+ View.computedAttr(key, () => RuntimeAttr.boolToString(compute()))
38
+ } else if typeof(value) == #object {
39
+ let signal: Signal.t<bool> = Obj.magic(value)
40
+ let stringSignal = Computed.make(() => RuntimeAttr.boolToString(Signal.get(signal)))
41
+ View.signalAttr(key, stringSignal)
42
+ } else {
43
+ let value: bool = Obj.magic(value)
44
+ View.attr(key, RuntimeAttr.boolToString(value))
45
+ }
46
+ }
@@ -0,0 +1,52 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as View$Xote from "./View.res.mjs";
4
+ import * as Signal$Xote from "./Signal.res.mjs";
5
+ import * as Computed$Xote from "./Computed.res.mjs";
6
+ import * as RuntimeAttr$Xote from "./RuntimeAttr.res.mjs";
7
+
8
+ function isReactiveProp(value) {
9
+ return (value && typeof value === 'object' && ('TAG' in value) && (value.TAG === 'Static' || value.TAG === 'Reactive'));
10
+ }
11
+
12
+ function toStringAttr(key, value) {
13
+ if (isReactiveProp(value)) {
14
+ if (value.TAG === "Reactive") {
15
+ return View$Xote.signalAttr(key, value._0);
16
+ } else {
17
+ return View$Xote.attr(key, value._0);
18
+ }
19
+ } else if (typeof value === "function") {
20
+ return View$Xote.computedAttr(key, value);
21
+ } else if (typeof value === "object") {
22
+ return View$Xote.signalAttr(key, value);
23
+ } else {
24
+ return View$Xote.attr(key, value);
25
+ }
26
+ }
27
+
28
+ function toBoolAttr(key, value) {
29
+ if (isReactiveProp(value)) {
30
+ if (value.TAG !== "Reactive") {
31
+ return View$Xote.attr(key, RuntimeAttr$Xote.boolToString(value._0));
32
+ }
33
+ let signal = value._0;
34
+ let stringSignal = Computed$Xote.make(() => RuntimeAttr$Xote.boolToString(Signal$Xote.get(signal)), undefined, undefined);
35
+ return View$Xote.signalAttr(key, stringSignal);
36
+ }
37
+ if (typeof value === "function") {
38
+ return View$Xote.computedAttr(key, () => RuntimeAttr$Xote.boolToString(value()));
39
+ }
40
+ if (typeof value !== "object") {
41
+ return View$Xote.attr(key, RuntimeAttr$Xote.boolToString(value));
42
+ }
43
+ let stringSignal$1 = Computed$Xote.make(() => RuntimeAttr$Xote.boolToString(Signal$Xote.get(value)), undefined, undefined);
44
+ return View$Xote.signalAttr(key, stringSignal$1);
45
+ }
46
+
47
+ export {
48
+ isReactiveProp,
49
+ toStringAttr,
50
+ toBoolAttr,
51
+ }
52
+ /* View-Xote Not a pure module */
@@ -0,0 +1,43 @@
1
+ type owner = {
2
+ disposers: array<Effect.disposer>,
3
+ mutable computeds: array<Obj.t>,
4
+ }
5
+
6
+ let currentOwner: ref<option<owner>> = ref(None)
7
+
8
+ let createOwner = (): owner => {
9
+ disposers: [],
10
+ computeds: [],
11
+ }
12
+
13
+ let runWithOwner = (owner: owner, fn: unit => 'a): 'a => {
14
+ let previousOwner = currentOwner.contents
15
+ currentOwner := Some(owner)
16
+ let result = fn()
17
+ currentOwner := previousOwner
18
+ result
19
+ }
20
+
21
+ let addDisposer = (owner: owner, disposer: Effect.disposer): unit => {
22
+ owner.disposers->Array.push(disposer)->ignore
23
+ }
24
+
25
+ let disposeOwner = (owner: owner): unit => {
26
+ owner.disposers->Array.forEach(disposer => disposer.dispose())
27
+
28
+ owner.computeds->Array.forEach(computed => {
29
+ let c: Signal.t<Obj.t> = Obj.magic(computed)
30
+ Computed.dispose(c)
31
+ })
32
+ }
33
+
34
+ @warning("-27")
35
+ let setOwner = (element: Dom.element, owner: owner): unit => {
36
+ %raw(`element["__xote_owner__"] = owner`)
37
+ }
38
+
39
+ @warning("-27")
40
+ let getOwner = (element: Dom.element): option<owner> => {
41
+ let owner: Nullable.t<owner> = %raw(`element["__xote_owner__"]`)
42
+ owner->Nullable.toOption
43
+ }
@@ -0,0 +1,51 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Computed$Xote from "./Computed.res.mjs";
4
+ import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
5
+
6
+ let currentOwner = {
7
+ contents: undefined
8
+ };
9
+
10
+ function createOwner() {
11
+ return {
12
+ disposers: [],
13
+ computeds: []
14
+ };
15
+ }
16
+
17
+ function runWithOwner(owner, fn) {
18
+ let previousOwner = currentOwner.contents;
19
+ currentOwner.contents = owner;
20
+ let result = fn();
21
+ currentOwner.contents = previousOwner;
22
+ return result;
23
+ }
24
+
25
+ function addDisposer(owner, disposer) {
26
+ owner.disposers.push(disposer);
27
+ }
28
+
29
+ function disposeOwner(owner) {
30
+ owner.disposers.forEach(disposer => disposer.dispose());
31
+ owner.computeds.forEach(Computed$Xote.dispose);
32
+ }
33
+
34
+ function setOwner(element, owner) {
35
+ ((element["__xote_owner__"] = owner));
36
+ }
37
+
38
+ function getOwner(element) {
39
+ return Primitive_option.fromNullable((element["__xote_owner__"]));
40
+ }
41
+
42
+ export {
43
+ currentOwner,
44
+ createOwner,
45
+ runWithOwner,
46
+ addDisposer,
47
+ disposeOwner,
48
+ setOwner,
49
+ getOwner,
50
+ }
51
+ /* No side effect */
package/src/SSR.res CHANGED
@@ -1,62 +1,5 @@
1
- /* ============================================================================
2
- * HTML Utilities
3
- * ============================================================================ */
4
-
5
- module Html = {
6
- /* Escape HTML special characters to prevent XSS */
7
- let escape = (str: string): string => {
8
- str
9
- ->String.replaceAll("&", "&amp;")
10
- ->String.replaceAll("<", "&lt;")
11
- ->String.replaceAll(">", "&gt;")
12
- ->String.replaceAll("\"", "&quot;")
13
- ->String.replaceAll("'", "&#x27;")
14
- }
15
-
16
- /* Void elements that don't have closing tags */
17
- let voidElements = [
18
- "area",
19
- "base",
20
- "br",
21
- "col",
22
- "embed",
23
- "hr",
24
- "img",
25
- "input",
26
- "link",
27
- "meta",
28
- "param",
29
- "source",
30
- "track",
31
- "wbr",
32
- ]
33
-
34
- let isVoidElement = (tag: string): bool => {
35
- voidElements->Array.includes(tag)
36
- }
37
- }
38
-
39
- /* ============================================================================
40
- * Hydration Markers
41
- * ============================================================================ */
42
-
43
- module Markers = {
44
- /* Markers for different reactive node types */
45
- let signalTextStart = "<!--$-->"
46
- let signalTextEnd = "<!--/$-->"
47
-
48
- let signalFragmentStart = "<!--#-->"
49
- let signalFragmentEnd = "<!--/#-->"
50
-
51
- let keyedListStart = "<!--kl-->"
52
- let keyedListEnd = "<!--/kl-->"
53
-
54
- let keyedItemStart = (key: string): string => `<!--k:${key}-->`
55
- let keyedItemEnd = "<!--/k-->"
56
-
57
- let lazyComponentStart = "<!--lc-->"
58
- let lazyComponentEnd = "<!--/lc-->"
59
- }
1
+ module Html = RuntimeHtml
2
+ module Markers = RuntimeHydrationMarkers
60
3
 
61
4
  /* ============================================================================
62
5
  * Render Options
@@ -73,39 +16,26 @@ type renderOptions = {
73
16
 
74
17
  module Attributes = {
75
18
  /* Render a single attribute to string */
76
- let renderAttr = ((key, value): (string, Node.attrValue)): string => {
19
+ let renderAttr = ((key, value): (string, View.attrValue)): string => {
77
20
  let attrValue = switch value {
78
- | Node.Static(v) => v
79
- | Node.SignalValue(signal) => Signal.peek(signal)
80
- | Node.Compute(fn) => fn()
21
+ | View.Static(v) => v
22
+ | View.SignalValue(signal) => Signal.peek(signal)
23
+ | View.Compute(fn) => fn()
81
24
  }
82
25
 
83
- /* Handle boolean attributes */
84
- switch key {
85
- | "checked"
86
- | "disabled"
87
- | "required"
88
- | "readonly"
89
- | "multiple"
90
- | "aria-hidden"
91
- | "aria-expanded"
92
- | "aria-selected"
93
- | "draggable"
94
- | "hidden"
95
- | "contenteditable"
96
- | "spellcheck"
97
- | "autofocus" =>
98
- if attrValue == "true" {
26
+ if RuntimeAttr.isBoolean(key) {
27
+ if RuntimeAttr.shouldRenderBoolean(attrValue) {
99
28
  key
100
29
  } else {
101
30
  ""
102
31
  }
103
- | _ => `${key}="${Html.escape(attrValue)}"`
32
+ } else {
33
+ `${key}="${Html.escape(attrValue)}"`
104
34
  }
105
35
  }
106
36
 
107
37
  /* Render all attributes to string */
108
- let renderAttrs = (attrs: array<(string, Node.attrValue)>): string => {
38
+ let renderAttrs = (attrs: array<(string, View.attrValue)>): string => {
109
39
  let rendered =
110
40
  attrs
111
41
  ->Array.map(renderAttr)
@@ -120,30 +50,32 @@ module Attributes = {
120
50
  }
121
51
 
122
52
  /* ============================================================================
123
- * Node Rendering
53
+ * View Rendering
124
54
  * ============================================================================ */
125
55
 
126
56
  /* Render a virtual node to an HTML string */
127
- let rec renderNodeToString = (node: Node.node): string => {
57
+ let rec renderNodeToString = (node: View.node): string => {
128
58
  switch node {
129
- | Node.Text(content) => Html.escape(content)
59
+ | View.Text(content) => Html.escape(content)
130
60
 
131
- | Node.SignalText(signal) => {
61
+ | View.SignalText(signal) => {
132
62
  /* Read current signal value and wrap with hydration markers */
133
63
  let value = Signal.peek(signal)
134
64
  Markers.signalTextStart ++ Html.escape(value) ++ Markers.signalTextEnd
135
65
  }
136
66
 
137
- | Node.Fragment(children) => children->Array.map(renderNodeToString)->Array.join("")
67
+ | View.Fragment(children) => children->Array.map(renderNodeToString)->Array.join("")
138
68
 
139
- | Node.SignalFragment(signal) => {
69
+ | View.SignalFragment(signal) => {
140
70
  /* Read current signal value and wrap with hydration markers */
141
71
  let children = Signal.peek(signal)
142
72
  let content = children->Array.map(renderNodeToString)->Array.join("")
143
73
  Markers.signalFragmentStart ++ content ++ Markers.signalFragmentEnd
144
74
  }
145
75
 
146
- | Node.Element({tag, attrs, children, events: _}) => {
76
+ | View.Keyed({child, key: _, identity: _}) => renderNodeToString(child)
77
+
78
+ | View.Element({tag, attrs, children, events: _}) => {
147
79
  let attrsStr = Attributes.renderAttrs(attrs)
148
80
 
149
81
  if Html.isVoidElement(tag) {
@@ -154,13 +86,13 @@ let rec renderNodeToString = (node: Node.node): string => {
154
86
  }
155
87
  }
156
88
 
157
- | Node.LazyComponent(fn) => {
89
+ | View.LazyComponent(fn) => {
158
90
  /* Execute the lazy component and render its result */
159
91
  let childNode = fn()
160
92
  Markers.lazyComponentStart ++ renderNodeToString(childNode) ++ Markers.lazyComponentEnd
161
93
  }
162
94
 
163
- | Node.KeyedList({signal, keyFn, renderItem}) => {
95
+ | View.KeyedList({signal, keyFn, renderItem}) => {
164
96
  let items = Signal.peek(signal)
165
97
  let content =
166
98
  items
@@ -181,7 +113,7 @@ let rec renderNodeToString = (node: Node.node): string => {
181
113
  * ============================================================================ */
182
114
 
183
115
  /* Render a component to an HTML string synchronously */
184
- let renderToString = (component: unit => Node.node, ~options: renderOptions={}): string => {
116
+ let renderToString = (component: unit => View.node, ~options: renderOptions={}): string => {
185
117
  let _ = options /* Will be used for nonce/renderId in future phases */
186
118
  let node = component()
187
119
  renderNodeToString(node)
@@ -189,7 +121,7 @@ let renderToString = (component: unit => Node.node, ~options: renderOptions={}):
189
121
 
190
122
  /* Render a component and wrap with a hydration root marker */
191
123
  let renderToStringWithRoot = (
192
- component: unit => Node.node,
124
+ component: unit => View.node,
193
125
  ~rootId: string="root",
194
126
  ~options: renderOptions={},
195
127
  ): string => {
@@ -219,7 +151,7 @@ let renderDocument = (
219
151
  ~styles: array<string>=[],
220
152
  ~stateScript: string="",
221
153
  ~nonce: option<string>=?,
222
- component: unit => Node.node,
154
+ component: unit => View.node,
223
155
  ): string => {
224
156
  let content = renderToString(component)
225
157
  let hydrationScript = generateHydrationScript(~nonce?)