firefly-compiler 0.5.35 → 0.5.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. package/.hintrc +4 -4
  2. package/.vscode/settings.json +4 -4
  3. package/bin/Release.ff +157 -154
  4. package/bin/firefly.mjs +1 -1
  5. package/compiler/Builder.ff +275 -277
  6. package/compiler/Compiler.ff +234 -233
  7. package/compiler/Dependencies.ff +186 -187
  8. package/compiler/DependencyLock.ff +17 -17
  9. package/compiler/Deriver.ff +23 -31
  10. package/compiler/Dictionaries.ff +1 -1
  11. package/compiler/Inference.ff +43 -20
  12. package/compiler/JsEmitter.ff +1437 -1282
  13. package/compiler/LspHook.ff +202 -202
  14. package/compiler/Main.ff +25 -24
  15. package/compiler/ModuleCache.ff +178 -178
  16. package/compiler/Parser.ff +36 -109
  17. package/compiler/Resolver.ff +5 -8
  18. package/compiler/Substitution.ff +1 -1
  19. package/compiler/Syntax.ff +1 -16
  20. package/compiler/Token.ff +9 -0
  21. package/compiler/Tokenizer.ff +4 -0
  22. package/compiler/Workspace.ff +88 -88
  23. package/core/.firefly/include/package.json +5 -5
  24. package/core/.firefly/package.ff +2 -2
  25. package/core/Any.ff +26 -30
  26. package/core/Array.ff +298 -265
  27. package/core/Atomic.ff +63 -64
  28. package/core/Box.ff +7 -7
  29. package/core/BrowserSystem.ff +40 -40
  30. package/core/Buffer.ff +185 -152
  31. package/core/BuildSystem.ff +156 -148
  32. package/core/Channel.ff +95 -92
  33. package/core/Char.ff +3 -2
  34. package/core/Core.ff +16 -23
  35. package/core/Crypto.ff +94 -96
  36. package/core/Equal.ff +41 -36
  37. package/core/Error.ff +15 -10
  38. package/core/FileHandle.ff +45 -37
  39. package/core/Float.ff +176 -200
  40. package/core/HttpClient.ff +142 -148
  41. package/core/Instant.ff +6 -8
  42. package/core/Int.ff +40 -24
  43. package/core/IntMap.ff +61 -39
  44. package/core/Js.ff +305 -0
  45. package/core/JsSystem.ff +135 -114
  46. package/core/JsValue.ff +303 -159
  47. package/core/Json.ff +423 -443
  48. package/core/List.ff +482 -486
  49. package/core/Lock.ff +108 -144
  50. package/core/Log.ff +25 -14
  51. package/core/NodeSystem.ff +198 -191
  52. package/core/Ordering.ff +160 -161
  53. package/core/Path.ff +377 -409
  54. package/core/Queue.ff +90 -0
  55. package/core/Random.ff +140 -134
  56. package/core/RbMap.ff +216 -216
  57. package/core/Serializable.ff +16 -13
  58. package/core/Show.ff +44 -43
  59. package/core/SourceLocation.ff +68 -68
  60. package/core/Stream.ff +1 -1
  61. package/core/String.ff +224 -202
  62. package/core/StringMap.ff +58 -36
  63. package/core/Task.ff +165 -149
  64. package/experimental/benchmarks/ListGrab.ff +23 -23
  65. package/experimental/benchmarks/ListGrab.java +55 -55
  66. package/experimental/benchmarks/Pyrotek45.ff +30 -30
  67. package/experimental/benchmarks/Pyrotek45.java +64 -64
  68. package/experimental/bidirectional/Bidi.ff +88 -88
  69. package/experimental/lines/Main.ff +40 -0
  70. package/experimental/random/Index.ff +53 -53
  71. package/experimental/random/Process.ff +120 -120
  72. package/experimental/random/RunLength.ff +65 -65
  73. package/experimental/random/Scrape.ff +51 -51
  74. package/experimental/random/Symbols.ff +73 -73
  75. package/experimental/random/Tensor.ff +52 -52
  76. package/experimental/random/Units.ff +36 -36
  77. package/experimental/s3/S3TestAuthorizationHeader.ff +39 -39
  78. package/experimental/s3/S3TestPut.ff +16 -16
  79. package/experimental/tests/TestJson.ff +26 -26
  80. package/firefly.sh +0 -0
  81. package/fireflysite/.firefly/package.ff +4 -4
  82. package/fireflysite/CommunityOverview.ff +20 -20
  83. package/fireflysite/CountingButtonDemo.ff +58 -58
  84. package/fireflysite/DocumentParser.ff +325 -331
  85. package/fireflysite/ExamplesOverview.ff +40 -40
  86. package/fireflysite/FrontPage.ff +344 -344
  87. package/fireflysite/GettingStarted.ff +45 -45
  88. package/fireflysite/Guide.ff +456 -456
  89. package/fireflysite/Main.ff +163 -152
  90. package/fireflysite/MatchingPasswordsDemo.ff +82 -82
  91. package/fireflysite/PackagesOverview.ff +49 -49
  92. package/fireflysite/PostgresqlDemo.ff +34 -34
  93. package/fireflysite/ReferenceAll.ff +18 -18
  94. package/fireflysite/ReferenceIntroduction.ff +11 -11
  95. package/fireflysite/Styles.ff +567 -567
  96. package/fireflysite/Test.ff +121 -62
  97. package/fireflysite/assets/markdown/reference/BaseTypes.md +209 -209
  98. package/fireflysite/assets/markdown/reference/EmittedJavascript.md +65 -65
  99. package/fireflysite/assets/markdown/reference/Exceptions.md +101 -101
  100. package/fireflysite/assets/markdown/reference/FunctionsAndMethods.md +364 -364
  101. package/fireflysite/assets/markdown/reference/JavascriptInterop.md +235 -172
  102. package/fireflysite/assets/markdown/reference/ModulesAndPackages.md +162 -162
  103. package/fireflysite/assets/markdown/reference/OldStructuredConcurrency.md +48 -48
  104. package/fireflysite/assets/markdown/reference/PatternMatching.md +224 -224
  105. package/fireflysite/assets/markdown/reference/StatementsAndExpressions.md +86 -86
  106. package/fireflysite/assets/markdown/reference/StructuredConcurrency.md +99 -99
  107. package/fireflysite/assets/markdown/reference/TraitsAndInstances.md +100 -100
  108. package/fireflysite/assets/markdown/reference/UserDefinedTypes.md +184 -184
  109. package/fireflysite/assets/markdown/scratch/ControlFlow.md +136 -136
  110. package/fireflysite/assets/markdown/scratch/Toc.md +40 -40
  111. package/lsp/.firefly/package.ff +1 -1
  112. package/lsp/CompletionHandler.ff +827 -827
  113. package/lsp/Handler.ff +714 -714
  114. package/lsp/HoverHandler.ff +79 -79
  115. package/lsp/LanguageServer.ff +272 -272
  116. package/lsp/SignatureHelpHandler.ff +55 -55
  117. package/lsp/SymbolHandler.ff +181 -181
  118. package/lsp/TestReferences.ff +17 -17
  119. package/lsp/TestReferencesCase.ff +7 -7
  120. package/lsp/stderr.txt +1 -1
  121. package/lsp/stdout.txt +34 -34
  122. package/lux/.firefly/package.ff +1 -1
  123. package/lux/Css.ff +648 -648
  124. package/lux/CssTest.ff +48 -48
  125. package/lux/Lux.ff +608 -617
  126. package/lux/LuxEvent.ff +79 -116
  127. package/lux/Main.ff +123 -123
  128. package/lux/Main2.ff +143 -143
  129. package/lux/TestDry.ff +28 -28
  130. package/output/js/ff/compiler/Builder.mjs +72 -71
  131. package/output/js/ff/compiler/Compiler.mjs +19 -13
  132. package/output/js/ff/compiler/Dependencies.mjs +8 -7
  133. package/output/js/ff/compiler/DependencyLock.mjs +6 -4
  134. package/output/js/ff/compiler/Deriver.mjs +26 -24
  135. package/output/js/ff/compiler/Dictionaries.mjs +14 -18
  136. package/output/js/ff/compiler/Environment.mjs +6 -4
  137. package/output/js/ff/compiler/Inference.mjs +238 -164
  138. package/output/js/ff/compiler/JsEmitter.mjs +1160 -350
  139. package/output/js/ff/compiler/JsImporter.mjs +20 -18
  140. package/output/js/ff/compiler/LspHook.mjs +12 -10
  141. package/output/js/ff/compiler/Main.mjs +61 -41
  142. package/output/js/ff/compiler/ModuleCache.mjs +10 -8
  143. package/output/js/ff/compiler/Parser.mjs +153 -669
  144. package/output/js/ff/compiler/Patterns.mjs +12 -10
  145. package/output/js/ff/compiler/Resolver.mjs +52 -78
  146. package/output/js/ff/compiler/Substitution.mjs +12 -16
  147. package/output/js/ff/compiler/Syntax.mjs +50 -341
  148. package/output/js/ff/compiler/Token.mjs +126 -4
  149. package/output/js/ff/compiler/Tokenizer.mjs +62 -52
  150. package/output/js/ff/compiler/Unification.mjs +74 -90
  151. package/output/js/ff/compiler/Wildcards.mjs +4 -2
  152. package/output/js/ff/compiler/Workspace.mjs +26 -20
  153. package/output/js/ff/core/Any.mjs +20 -20
  154. package/output/js/ff/core/Array.mjs +268 -175
  155. package/output/js/ff/core/AssetSystem.mjs +8 -6
  156. package/output/js/ff/core/Atomic.mjs +84 -52
  157. package/output/js/ff/core/Bool.mjs +6 -4
  158. package/output/js/ff/core/BrowserSystem.mjs +38 -29
  159. package/output/js/ff/core/Buffer.mjs +285 -133
  160. package/output/js/ff/core/BuildSystem.mjs +36 -56
  161. package/output/js/ff/core/Channel.mjs +250 -97
  162. package/output/js/ff/core/Char.mjs +5 -3
  163. package/output/js/ff/core/Core.mjs +28 -34
  164. package/output/js/ff/core/Crypto.mjs +30 -52
  165. package/output/js/ff/core/Duration.mjs +4 -2
  166. package/output/js/ff/core/Equal.mjs +14 -12
  167. package/output/js/ff/core/Error.mjs +17 -11
  168. package/output/js/ff/core/FileHandle.mjs +76 -38
  169. package/output/js/ff/core/Float.mjs +92 -160
  170. package/output/js/ff/core/HttpClient.mjs +208 -76
  171. package/output/js/ff/core/Instant.mjs +8 -10
  172. package/output/js/ff/core/Int.mjs +36 -26
  173. package/output/js/ff/core/IntMap.mjs +79 -33
  174. package/output/js/ff/core/Js.mjs +751 -0
  175. package/output/js/ff/core/JsSystem.mjs +54 -60
  176. package/output/js/ff/core/JsValue.mjs +294 -143
  177. package/output/js/ff/core/Json.mjs +443 -253
  178. package/output/js/ff/core/List.mjs +262 -214
  179. package/output/js/ff/core/Lock.mjs +156 -125
  180. package/output/js/ff/core/Log.mjs +20 -10
  181. package/output/js/ff/core/Map.mjs +10 -8
  182. package/output/js/ff/core/NodeSystem.mjs +189 -123
  183. package/output/js/ff/core/Nothing.mjs +4 -2
  184. package/output/js/ff/core/Option.mjs +40 -38
  185. package/output/js/ff/core/Ordering.mjs +26 -20
  186. package/output/js/ff/core/Pair.mjs +4 -2
  187. package/output/js/ff/core/Path.mjs +517 -315
  188. package/output/js/ff/core/Queue.mjs +306 -0
  189. package/output/js/ff/core/Random.mjs +141 -77
  190. package/output/js/ff/core/RbMap.mjs +36 -34
  191. package/output/js/ff/core/Serializable.mjs +44 -28
  192. package/output/js/ff/core/Set.mjs +6 -4
  193. package/output/js/ff/core/Show.mjs +8 -6
  194. package/output/js/ff/core/SourceLocation.mjs +4 -2
  195. package/output/js/ff/core/Stream.mjs +30 -50
  196. package/output/js/ff/core/String.mjs +263 -172
  197. package/output/js/ff/core/StringMap.mjs +77 -31
  198. package/output/js/ff/core/Task.mjs +91 -76
  199. package/output/js/ff/core/Try.mjs +20 -18
  200. package/output/js/ff/core/Unit.mjs +4 -2
  201. package/package.json +1 -1
  202. package/postgresql/Pg.ff +53 -59
  203. package/rpc/.firefly/package.ff +1 -1
  204. package/rpc/Rpc.ff +70 -70
  205. package/s3/.firefly/package.ff +1 -1
  206. package/s3/S3.ff +92 -94
  207. package/vscode/LICENSE.txt +21 -21
  208. package/vscode/Prepublish.ff +15 -15
  209. package/vscode/README.md +16 -16
  210. package/vscode/client/package-lock.json +544 -544
  211. package/vscode/client/package.json +22 -22
  212. package/vscode/client/src/extension.ts +104 -104
  213. package/vscode/icons/firefly-icon.svg +10 -10
  214. package/vscode/language-configuration.json +61 -61
  215. package/vscode/package-lock.json +3623 -3623
  216. package/vscode/package.json +1 -1
  217. package/vscode/snippets.json +241 -241
  218. package/vscode/syntaxes/firefly-markdown-injection.json +45 -45
  219. package/webserver/.firefly/include/package.json +5 -5
  220. package/webserver/.firefly/package.ff +2 -2
  221. package/webserver/WebServer.ff +647 -685
  222. package/websocket/.firefly/package.ff +1 -1
  223. package/websocket/WebSocket.ff +100 -131
  224. package/core/UnsafeJs.ff +0 -42
  225. package/output/js/ff/core/UnsafeJs.mjs +0 -191
package/lux/Lux.ff CHANGED
@@ -1,617 +1,608 @@
1
- import Lux
2
- import LuxEvent
3
- import Css
4
-
5
- capability Lux(
6
- document: LuxDocument
7
- jsSystem: JsSystem
8
- mutable dry: Option[Array[DryNode]]
9
- mutable cssClasses: StringMap[CssClass]
10
- mutable renderLock: Lock
11
- mutable task: Task
12
- mutable renderQueue: Array[RenderQueueItem]
13
- mutable element: LuxElement
14
- mutable depth: Int
15
- mutable keys: Option[StringMap[JsValue]]
16
- mutable key: String
17
- mutable attributes: Option[StringMap[String]]
18
- mutable texts: Array[String]
19
- )
20
-
21
- capability RenderQueueItem(
22
- luxCopy: Lux
23
- render: () => Unit
24
- )
25
-
26
- class LuxElement(element: JsValue, mutable child: Int, mutable keepChildren: Bool)
27
-
28
- class LuxDocument(document: JsValue)
29
-
30
- data LuxInvaldNameException(name: String)
31
-
32
- class DryNode {
33
- DryElement(tagName: String, attributes: StringMap[String], children: Array[DryNode])
34
- DryFragment(children: Array[DryNode])
35
- DryText(text: String)
36
- }
37
-
38
- extend self: DryNode {
39
- toHtml(): String {
40
- // https://www.w3.org/TR/xml/ - wrt. checkXmmlName, we only accept the ASCII subset
41
- function checkXmlName(name: String): String {
42
- if(name.size() == 0) {throw(LuxInvaldNameException(name))}
43
- if(!name.grabFirst().isAsciiLetter() && !name.startsWith(":") && !name.startsWith("_")) {throw(LuxInvaldNameException(name))}
44
- if(!name.all {c => c.isAsciiLetterOrDigit() || c == ':' || c == '_' || c == '-' || c == '.'}) {throw(LuxInvaldNameException(name))}
45
- name
46
- }
47
- function escapeAttributeValue(value: String): String {
48
- value.replace("&", "&#x26;").replace("<", "&#x3C;").replace(">", "&#x3E;").replace("\"", "&#x22;")
49
- }
50
- function escapeText(text: String): String {
51
- text.replace("&", "&#x26;").replace("<", "&#x3C;").replace(">", "&#x3E;")
52
- }
53
- let voidHtmlElements = [
54
- "area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"
55
- ].toSet()
56
- function toHtml(node: DryNode): String {
57
- | DryElement(tagName, attributes, children) =>
58
- let attributeHtml = attributes.toList().map {| Pair(name, value) =>
59
- " " + checkXmlName(name) + "=\"" + escapeAttributeValue(value) + "\""
60
- }.join()
61
- let childrenHtml = children.toList().map {toHtml(_)}.join()
62
- let endHtml = if(voidHtmlElements.contains(tagName)) {""} else {"</" + tagName + ">"}
63
- "<" + checkXmlName(tagName) + attributeHtml + ">" + childrenHtml + endHtml
64
- | DryFragment(children) =>
65
- children.toList().map {toHtml(_)}.join()
66
- | DryText(text) =>
67
- escapeText(text)
68
- }
69
- toHtml(self)
70
- }
71
- }
72
-
73
- extend self: LuxElement {
74
- childAt(index: Int): JsValue {
75
- self.element->childNodes.get(index)
76
- }
77
- insertBefore(newNode: JsValue, referenceNode: JsValue): JsValue {
78
- self.element->insertBefore(newNode, referenceNode)
79
- }
80
- removeAt(index: Int): Bool {
81
- let node = self.childAt(index)
82
- let remove = !node.isNullOrUndefined()
83
- if(remove) {
84
- self.element->removeChild(node)
85
- }
86
- remove
87
- }
88
- }
89
-
90
- extend self: LuxDocument {
91
- createTextNode(value: String): JsValue {
92
- self.document->createTextNode(value)
93
- }
94
-
95
- createElement(tagName: String): JsValue {
96
- self.document->createElement(tagName)
97
- }
98
-
99
- createFragment(): JsValue {
100
- self.document->createDocumentFragment()
101
- }
102
- }
103
-
104
- extend self: Lux {
105
-
106
- copyFrom(lux: Lux) {
107
- self.renderLock = lux.renderLock
108
- self.renderQueue = lux.renderQueue
109
- self.element = lux.element.LuxElement()
110
- self.depth = lux.depth
111
- self.keys = lux.keys
112
- self.key = lux.key
113
- self.attributes = lux.attributes
114
- }
115
-
116
- text(value: String) {
117
- if(value.size() != 0) {
118
- self.texts.push(value)
119
- }
120
- }
121
-
122
- add(tagName: String, body: () => Unit = {}) {
123
- patchText(self)
124
- self.dry.map {dry =>
125
- self.dry = Some([].toArray())
126
- let savedAttributes = self.attributes
127
- let savedKeys = self.keys
128
- self.attributes = if(self.key != "") {
129
- let attributes = StringMap.new()
130
- attributes.set("data-lux-key", tagName.upper() + ">" + self.key)
131
- attributes
132
- }
133
- self.key = ""
134
- self.depth += 1
135
- try {
136
- body()
137
- } finally {
138
- patchText(self)
139
- dry.push(DryElement(tagName, self.attributes.else {StringMap.new()}, self.dry.grab()))
140
- self.depth -= 1
141
- self.attributes = savedAttributes
142
- self.keys = savedKeys
143
- self.element.child += 1
144
- self.dry = Some(dry)
145
- }
146
- }.else:
147
- let node = patchElement(self, tagName)
148
- if(!node->luxHandlers.isNullOrUndefined()) {
149
- node->luxHandlers.grabArray().each {pair =>
150
- node->removeEventListener(pair->event, pair->handler)
151
- }
152
- node->luxHandlers = self.jsSystem.array([])
153
- }
154
- let savedAttributes = self.attributes
155
- let savedKeys = self.keys
156
- let savedElement = self.element
157
- self.attributes = None
158
- self.element = LuxElement(node, 0, keepChildren = False)
159
- self.element.element->classList->value = ""
160
- self.key = ""
161
- self.depth += 1
162
- try {
163
- body()
164
- } finally {
165
- patchText(self)
166
- if(!self.element.keepChildren) {
167
- doWhile {removeCurrentChild(self)}
168
- }
169
- patchAttributes(self)
170
- self.depth -= 1
171
- self.attributes = savedAttributes
172
- self.keys = savedKeys
173
- self.element = savedElement
174
- self.element.child += 1
175
- }
176
- }
177
-
178
- div(body: () => Unit = {}) {self.add("div", body)}
179
- span(body: () => Unit = {}) {self.add("span", body)}
180
- label(body: () => Unit = {}) {self.add("label", body)}
181
- button(body: () => Unit = {}) {self.add("button", body)}
182
- form(body: () => Unit = {}) {self.add("form", body)}
183
- input(body: () => Unit = {}) {self.add("input", body)}
184
-
185
- keyed(key: String, body: () => Unit) {
186
- try {
187
- self.key = key
188
- body()
189
- } finally {
190
- self.key = ""
191
- }
192
- }
193
-
194
- keyedList[T](values: List[T], getKey: T => String, body: T => Unit) {
195
- try {
196
- values.each {item =>
197
- self.key = getKey(item)
198
- body(item)
199
- }
200
- } finally {
201
- self.key = ""
202
- }
203
- }
204
-
205
- set(attribute: String, value: String) {
206
- let attributes = self.attributes.else {
207
- let map = StringMap.new()
208
- self.attributes = Some(map)
209
- map
210
- }
211
- attributes.set(attribute, value)
212
- }
213
-
214
- setId(value: String) {self.set("id", value)}
215
-
216
- setValue(value: String) { // TODO: Not an attribute
217
- self.dry.map {_ => self.set("value", value)}.else:
218
- self.element.element->value = value
219
- }
220
-
221
- css(style: Css) {
222
- let attributes = self.attributes.else {
223
- let map = StringMap.new()
224
- self.attributes = Some(map)
225
- map
226
- }
227
- if(attributes.has("style")) {
228
- attributes.set("style", attributes.grab("style") + ";" + style.property + ":" + style.value)
229
- } else {
230
- attributes.set("style", style.property + ":" + style.value)
231
- }
232
- }
233
-
234
- cssClass(class: CssClass) {
235
- if(!self.cssClasses.has(class.name())) {
236
- self.cssClasses.set(class.name(), class)
237
- if(self.dry.isEmpty()):
238
- let styleSheet = self.document.createElement("style")
239
- styleSheet->textContent = class.show()
240
- self.document.document->head->appendChild(styleSheet)
241
- }
242
- if(!self.dry.isEmpty()) {
243
- let classNames = self.attributes.flatMap {_.get("class")}.else {""}
244
- self.set("class", (classNames + " " + class.name()).trim())
245
- } else {
246
- self.element.element->classList->add(class.name())
247
- self.set("class", self.element.element->className.grabString())
248
- }
249
- }
250
-
251
- on(event: String, handler: LuxEvent => Unit) {
252
- if(self.dry.isEmpty()):
253
- let jsHandler = unsafeAsyncFunction1ToJs(self) {jsEvent =>
254
- self.renderLock.do(reentrant = False) {
255
- handler(unsafeJsToValue(jsEvent))
256
- processRenderQueue(self)
257
- }
258
- }
259
- self.element.element->addEventListener(event, jsHandler)
260
- if(self.element.element->luxHandlers.isNullOrUndefined()) {
261
- self.element.element->luxHandlers = self.jsSystem.array([])
262
- }
263
- self.element.element->luxHandlers->push(
264
- self.jsSystem.object().with("event", event).with("handler", jsHandler)
265
- )
266
- }
267
-
268
- onClick(handler: LuxEvent => Unit) {self.on("click", handler)}
269
- onInput(handler: LuxEvent => Unit) {self.on("input", handler)}
270
-
271
- useState[T: HasAnyTag](initialValue: T, body: (T, T => Unit) => Unit) {
272
- self.dry.map {_ => body(initialValue, {_ => })}.else:
273
- self.depth += 1
274
- let value = getStateOnElement(self.element.element, self.depth, initialValue)
275
- mutable i = 0
276
- while {i < self.renderQueue.size()} {
277
- let item = self.renderQueue.grab(i)
278
- if(item.luxCopy.element.element.equals(self.element.element) && item.luxCopy.depth == self.depth) {
279
- self.renderQueue.delete(i, 1)
280
- } else {
281
- i += 1
282
- }
283
- }
284
- let luxCopy = self.Lux(element = self.element.LuxElement())
285
- function setState(newValue: T): Unit {
286
- setStateOnElement(luxCopy.element.element, luxCopy.depth, newValue)
287
- self.renderQueue.push(RenderQueueItem(
288
- luxCopy = luxCopy
289
- render = {
290
- let child = luxCopy.element.child
291
- let element = luxCopy.element.childAt(child)
292
- body(newValue, {setState(_)})
293
- if(!element.equals(luxCopy.element.childAt(child))) {
294
- removeCurrentChild(luxCopy.Lux(
295
- element = luxCopy.element.LuxElement(child = luxCopy.element.child + 1)
296
- ))
297
- }
298
- }
299
- ))
300
- }
301
- try {
302
- body(value, {setState(_)})
303
- } finally {
304
- self.depth -= 1
305
- }
306
- }
307
-
308
- useCallback1[A1: Equal: HasAnyTag](callback: A1 => Unit, body: (A1 => Unit) => Unit) {
309
- self.dry.map {_ => body(callback)}.else:
310
- self.depth += 1
311
- setStateOnElement(self.element.element, self.depth, callback)
312
- let element = self.element.element
313
- let depth = self.depth
314
- try {
315
- body {a1 =>
316
- let latestCallback = getStateOnElement(element, depth, callback)
317
- latestCallback(a1)
318
- }
319
- } finally {
320
- self.depth -= 1
321
- }
322
- }
323
-
324
- useLazy1[A1: Equal: HasAnyTag](a1: A1, body: A1 => Unit = {_ => }) {
325
- self.dry.map {_ => body(a1)}.else:
326
- self.depth += 1
327
- try {
328
- let old = getStateOnElement(self.element.element, self.depth, None)
329
- old.{
330
- | Some(o1) {a1 == o1} =>
331
- self.element.keepChildren = True
332
- | _ =>
333
- setStateOnElement(self.element.element, self.depth, Some(a1))
334
- body(a1)
335
- }
336
- } finally {
337
- self.depth -= 1
338
- }
339
- }
340
-
341
- useMemo1[A1: Equal: HasAnyTag, T: HasAnyTag](a1: A1, compute: A1 => T, body: T => Unit) {
342
- self.dry.map {_ => body(compute(a1))}.else:
343
- self.depth += 1
344
- try {
345
- let old = getStateOnElement(self.element.element, self.depth, Pair(a1, None))
346
- let computed = old.{
347
- | Pair(o1, _) {a1 != o1} =>
348
- let v = compute(a1)
349
- setStateOnElement(self.element.element, self.depth, Pair(a1, Some(v)))
350
- v
351
- | Pair(_, Some(v)) =>
352
- v
353
- | Pair(_, None) =>
354
- let v = compute(a1)
355
- setStateOnElement(self.element.element, self.depth, Pair(a1, Some(v)))
356
- v
357
- }
358
- body(computed)
359
- } finally {
360
- self.depth -= 1
361
- }
362
- }
363
-
364
- useSuspense(suspense: () => Unit, body: Lux => Unit) {
365
- self.dry.map {_ => suspense()}.else:
366
- let oldSubtask = getTaskOnElement(self.element.element)
367
- oldSubtask.each {task =>
368
- forceAsyncAbort(self, task)
369
- }
370
- let fragment = self.document.createFragment()
371
- let luxCopy = self.Lux(element = self.element.LuxElement())
372
- let subtask = self.task.spawn {task =>
373
- let lux = luxCopy.Lux(
374
- task = task
375
- renderLock = task.lock()
376
- renderQueue = Array.new()
377
- element = luxCopy.element.LuxElement(element = fragment)
378
- )
379
- try {
380
- body(lux)
381
- task.throwIfAborted()
382
- lux.copyFrom(luxCopy)
383
- lux.renderLock.do(reentrant = False) {
384
- abortTasksOnElement(lux, lux.element.childAt(lux.element.child), onlyChildren = True)
385
- lux.element.removeAt(lux.element.child)
386
- lux.element.insertBefore(fragment, lux.jsSystem.null())
387
- }
388
- } catchAny {error =>
389
- if(error.name() != "AbortError") {
390
- error.rethrow()
391
- }
392
- }
393
- }
394
- suspense()
395
- setTaskOnElement(self.element.childAt(self.element.child - 1), Some(subtask))
396
- }
397
-
398
- }
399
-
400
- processRenderQueue(self: Lux) {
401
- self.renderQueue.sortBy {-_.luxCopy.depth}
402
- let newLux = self.Lux(element = self.element.LuxElement())
403
- try {
404
- while {!self.renderQueue.isEmpty()} {
405
- let item = self.renderQueue.pop().grab()
406
- self.copyFrom(item.luxCopy)
407
- item.render()
408
- }
409
- } finally {
410
- self.copyFrom(newLux)
411
- }
412
- }
413
-
414
- removeCurrentChild(self: Lux): Bool {
415
- let child = self.element.childAt(self.element.child)
416
- if(!child.isNullOrUndefined() && !child->children.isNullOrUndefined()) {
417
- abortTasksOnElement(self, child, False)
418
- }
419
- self.element.removeAt(self.element.child)
420
- }
421
-
422
- getStateOnElement[T /*: HasAnyTag*/](element: JsValue, depth: Int, fallback: T): T {
423
- let value = element.get("lux" + depth)
424
- if(value.isNullOrUndefined()) {fallback} else {
425
- unsafeJsToValue(value)
426
- }
427
- }
428
-
429
- setStateOnElement[T /*: HasAnyTag*/](element: JsValue, depth: Int, value: T): Unit {
430
- element.set("lux" + depth, unsafeJsFromValue(value))
431
- }
432
-
433
-
434
- getTaskOnElement(element: JsValue): Option[Task] {
435
- let value = element->luxTask
436
- if(value.isNullOrUndefined()) {None} else {
437
- unsafeJsToValue(value)
438
- }
439
- }
440
-
441
- setTaskOnElement(element: JsValue, task: Option[Task]): Unit {
442
- element->luxTask = unsafeJsFromValue(task)
443
- }
444
-
445
- abortTasksOnElement(lux: Lux, element: JsValue, onlyChildren: Bool): Unit {
446
- if(!onlyChildren) {
447
- getTaskOnElement(element).each {task => forceAsyncAbort(lux, task)}
448
- }
449
- element->children.each {child =>
450
- abortTasksOnElement(lux, child, False)
451
- }
452
- }
453
-
454
- unsafeJsToValue[T](jsValue: JsValue): T
455
- target js sync """return jsValue_"""
456
-
457
- unsafeJsFromValue[T](value: T): JsValue
458
- target js sync """return value_"""
459
-
460
- unsafeAsyncFunction1ToJs[R](self: Lux, body: JsValue => R): JsValue
461
- target js async "return async a1 => await body_(a1, $task)"
462
-
463
- forceAsyncAbort(self: Lux, task: Task): Unit {
464
- task.abort()
465
- }
466
-
467
- patchText(self: Lux) {
468
- if(!self.texts.isEmpty()):
469
- let value = self.texts.drain().join()
470
- self.dry.map {_.push(DryText(value))}.else:
471
- let oldNode = self.element.childAt(self.element.child)
472
- let oldValue = if(!oldNode.isNullOrUndefined()) {oldNode->data} else {oldNode}
473
- if(oldValue.isNullOrUndefined() || oldValue.grabString() != value) {
474
- let node = self.document.createTextNode(value)
475
- self.element.insertBefore(node, oldNode)
476
- }
477
- self.element.child += 1
478
- }
479
-
480
- patchElement(self: Lux, tagName: String): JsValue {
481
- let newKey = if(self.key != "") {tagName.upper() + ">" + self.key} else {""}
482
- let oldNode = self.element.childAt(self.element.child)
483
- let oldKey = if(!oldNode.isNullOrUndefined() && !oldNode->getAttribute.isNullOrUndefined()) {
484
- oldNode->getAttribute("data-lux-key")
485
- } else {oldNode}
486
- let match = if(newKey != "") {
487
- !oldKey.isNullOrUndefined() &&
488
- oldKey.grabString() == newKey
489
- } else {
490
- oldKey.isNullOrUndefined() &&
491
- !oldNode.isNullOrUndefined() &&
492
- !oldNode->tagName.isNullOrUndefined() &&
493
- oldNode->tagName.grabString() == tagName.upper()
494
- }
495
- if(match) {
496
- oldNode
497
- } else {
498
- let newNode = if(newKey == "") {self.document.createElement(tagName)} else {
499
- let keys = self.keys.else {
500
- let map = StringMap.new()
501
- mutable i = self.element.child
502
- mutable c = self.element.childAt(i)
503
- while {!c.isNullOrUndefined()} {
504
- if(!c->getAttribute.isNullOrUndefined()) {
505
- let k = c->getAttribute("data-lux-key")
506
- if(!k.isNullOrUndefined()) {map.set(k.grabString(), c)}
507
- }
508
- c = self.element.childAt(i)
509
- i += 1
510
- }
511
- self.keys = Some(map)
512
- map
513
- }
514
- if(keys.has(newKey)) {
515
- let foundNode = keys.grab(newKey)
516
- keys.remove(newKey)
517
- foundNode
518
- } else {
519
- let createdNode = self.document.createElement(tagName)
520
- createdNode->setAttribute("data-lux-key", newKey)
521
- createdNode
522
- }
523
- }
524
- self.element.insertBefore(newNode, oldNode)
525
- newNode
526
- }
527
- }
528
-
529
- patchAttributes(self: Lux) {
530
- let attributes = self.element.element->attributes
531
- self.attributes.{
532
- | None =>
533
- mutable i = attributes->length.grabInt() - 1
534
- while {i >= 0} {
535
- let attribute = attributes.get(i)
536
- self.element.element->removeAttribute(attribute->name)
537
- i -= 1
538
- }
539
- | Some(map) =>
540
- mutable i = attributes->length.grabInt() - 1
541
- while {i >= 0} {
542
- let attribute = attributes.get(i)
543
- if(!map.has(attribute->name.grabString())) {
544
- self.element.element->removeAttribute(attribute->name)
545
- }
546
- i -= 1
547
- }
548
- map.each {name, value =>
549
- let oldValue = self.element.element->getAttribute(name)
550
- if(oldValue.isNullOrUndefined() || !oldValue.equals(value)) {
551
- self.element.element->setAttribute(name, value)
552
- }
553
- }
554
- }
555
- }
556
-
557
- render(browserSystem: BrowserSystem, element: JsValue, body: Lux => Unit) {
558
- mutable document = element
559
- while {!document->parentNode.isNullOrUndefined()} {
560
- document = document->parentNode
561
- }
562
- // [...document.querySelectorAll("[lux-class]")].map(x => x.getAttribute("lux-class"))
563
- let staticCssClasses = StringMap.new()
564
- let dummyCssClass = CssClass([], [], [])
565
- document->querySelectorAll("[lux-class]").each {e =>
566
- staticCssClasses.set(e->getAttribute("lux-class").grabString(), dummyCssClass)
567
- }
568
- let lux = Lux(
569
- jsSystem = browserSystem.js()
570
- renderLock = browserSystem.mainTask().lock()
571
- dry = None
572
- cssClasses = staticCssClasses
573
- task = browserSystem.mainTask()
574
- depth = 0
575
- document = LuxDocument(document)
576
- element = LuxElement(element, 0, keepChildren = False)
577
- keys = None
578
- key = ""
579
- attributes = None
580
- texts = Array.new()
581
- renderQueue = Array.new()
582
- )
583
- lux.renderLock.do(reentrant = False) {
584
- body(lux)
585
- patchText(lux)
586
- }
587
- }
588
-
589
- renderById(browserSystem: BrowserSystem, id: String, body: Lux => Unit) {
590
- let element = browserSystem.js()->document->getElementById(id)
591
- render(browserSystem, element, body)
592
- }
593
-
594
- renderToString(nodeSystem: NodeSystem, body: Lux => Unit): Pair[String, String] {
595
- let children = [].toArray()
596
- let lux = Lux(
597
- jsSystem = nodeSystem.js()
598
- renderLock = nodeSystem.mainTask().lock()
599
- dry = Some(children)
600
- cssClasses = StringMap.new()
601
- task = nodeSystem.mainTask()
602
- depth = 0
603
- document = LuxDocument(nodeSystem.js().null())
604
- element = LuxElement(nodeSystem.js().null(), 0, keepChildren = False)
605
- keys = None
606
- key = ""
607
- attributes = None
608
- texts = Array.new()
609
- renderQueue = Array.new()
610
- )
611
- body(lux)
612
- patchText(lux)
613
- let styleTags = lux.cssClasses.values().map {c =>
614
- "<style lux-class=\"" + c.name() + "\">" + c.show() + "</style>"
615
- }.join()
616
- Pair(children.toList().map {_.toHtml()}.join(), styleTags)
617
- }
1
+ import Lux
2
+ import LuxEvent
3
+ import Css
4
+
5
+ capability Lux(
6
+ document: LuxDocument
7
+ jsSystem: JsSystem
8
+ mutable dry: Option[Array[DryNode]]
9
+ mutable cssClasses: StringMap[CssClass]
10
+ mutable renderLock: Lock
11
+ mutable task: Task
12
+ mutable renderQueue: Array[RenderQueueItem]
13
+ mutable element: LuxElement
14
+ mutable depth: Int
15
+ mutable keys: Option[StringMap[JsValue]]
16
+ mutable key: String
17
+ mutable attributes: Option[StringMap[String]]
18
+ mutable texts: Array[String]
19
+ )
20
+
21
+ capability RenderQueueItem(
22
+ luxCopy: Lux
23
+ render: () => Unit
24
+ )
25
+
26
+ class LuxElement(element: JsValue, mutable child: Int, mutable keepChildren: Bool)
27
+
28
+ class LuxDocument(document: JsValue)
29
+
30
+ data LuxInvaldNameException(name: String)
31
+
32
+ class DryNode {
33
+ DryElement(tagName: String, attributes: StringMap[String], children: Array[DryNode])
34
+ DryFragment(children: Array[DryNode])
35
+ DryText(text: String)
36
+ }
37
+
38
+ extend self: DryNode {
39
+ toHtml(): String {
40
+ // https://www.w3.org/TR/xml/ - wrt. checkXmmlName, we only accept the ASCII subset
41
+ function checkXmlName(name: String): String {
42
+ if(name.size() == 0) {throw(LuxInvaldNameException(name))}
43
+ if(!name.grabFirst().isAsciiLetter() && !name.startsWith(":") && !name.startsWith("_")) {throw(LuxInvaldNameException(name))}
44
+ if(!name.all {c => c.isAsciiLetterOrDigit() || c == ':' || c == '_' || c == '-' || c == '.'}) {throw(LuxInvaldNameException(name))}
45
+ name
46
+ }
47
+ function escapeAttributeValue(value: String): String {
48
+ value.replace("&", "&#x26;").replace("<", "&#x3C;").replace(">", "&#x3E;").replace("\"", "&#x22;")
49
+ }
50
+ function escapeText(text: String): String {
51
+ text.replace("&", "&#x26;").replace("<", "&#x3C;").replace(">", "&#x3E;")
52
+ }
53
+ let voidHtmlElements = [
54
+ "area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"
55
+ ].toSet()
56
+ function toHtml(node: DryNode): String {
57
+ | DryElement(tagName, attributes, children) =>
58
+ let attributeHtml = attributes.toList().map {| Pair(name, value) =>
59
+ " " + checkXmlName(name) + "=\"" + escapeAttributeValue(value) + "\""
60
+ }.join()
61
+ let childrenHtml = children.toList().map {toHtml(_)}.join()
62
+ let endHtml = if(voidHtmlElements.contains(tagName)) {""} else {"</" + tagName + ">"}
63
+ "<" + checkXmlName(tagName) + attributeHtml + ">" + childrenHtml + endHtml
64
+ | DryFragment(children) =>
65
+ children.toList().map {toHtml(_)}.join()
66
+ | DryText(text) =>
67
+ escapeText(text)
68
+ }
69
+ toHtml(self)
70
+ }
71
+ }
72
+
73
+ extend self: LuxElement {
74
+ childAt(index: Int): JsValue {
75
+ self.element->childNodes.get(index)
76
+ }
77
+ insertBefore(newNode: JsValue, referenceNode: JsValue): JsValue {
78
+ self.element->insertBefore(newNode, referenceNode)
79
+ }
80
+ removeAt(index: Int): Bool {
81
+ let node = self.childAt(index)
82
+ let remove = !node.isNullOrUndefined()
83
+ if(remove) {
84
+ self.element->removeChild(node)
85
+ }
86
+ remove
87
+ }
88
+ }
89
+
90
+ extend self: LuxDocument {
91
+ createTextNode(value: String): JsValue {
92
+ self.document->createTextNode(value)
93
+ }
94
+
95
+ createElement(tagName: String): JsValue {
96
+ self.document->createElement(tagName)
97
+ }
98
+
99
+ createFragment(): JsValue {
100
+ self.document->createDocumentFragment()
101
+ }
102
+ }
103
+
104
+ extend self: Lux {
105
+
106
+ copyFrom(lux: Lux) {
107
+ self.renderLock = lux.renderLock
108
+ self.renderQueue = lux.renderQueue
109
+ self.element = lux.element.LuxElement()
110
+ self.depth = lux.depth
111
+ self.keys = lux.keys
112
+ self.key = lux.key
113
+ self.attributes = lux.attributes
114
+ }
115
+
116
+ text(value: String) {
117
+ if(value.size() != 0) {
118
+ self.texts.push(value)
119
+ }
120
+ }
121
+
122
+ add(tagName: String, body: () => Unit = {}) {
123
+ patchText(self)
124
+ self.dry.map {dry =>
125
+ self.dry = Some([].toArray())
126
+ let savedAttributes = self.attributes
127
+ let savedKeys = self.keys
128
+ self.attributes = if(self.key != "") {
129
+ let attributes = StringMap.new()
130
+ attributes.set("data-lux-key", tagName.upper() + ">" + self.key)
131
+ attributes
132
+ }
133
+ self.key = ""
134
+ self.depth += 1
135
+ try {
136
+ body()
137
+ } finally {
138
+ patchText(self)
139
+ dry.push(DryElement(tagName, self.attributes.else {StringMap.new()}, self.dry.grab()))
140
+ self.depth -= 1
141
+ self.attributes = savedAttributes
142
+ self.keys = savedKeys
143
+ self.element.child += 1
144
+ self.dry = Some(dry)
145
+ }
146
+ }.else:
147
+ let node = patchElement(self, tagName)
148
+ if(!node->luxHandlers.isNullOrUndefined()) {
149
+ node->luxHandlers.grabArray().each {pair =>
150
+ node->removeEventListener(pair->event, pair->handler)
151
+ }
152
+ node->luxHandlers = self.jsSystem.array([])
153
+ }
154
+ let savedAttributes = self.attributes
155
+ let savedKeys = self.keys
156
+ let savedElement = self.element
157
+ self.attributes = if(self.key != "") {
158
+ let attributes = StringMap.new()
159
+ attributes.set("data-lux-key", tagName.upper() + ">" + self.key)
160
+ attributes
161
+ }
162
+ self.element = LuxElement(node, 0, keepChildren = False)
163
+ //self.element.element->classList->value = ""
164
+ self.keys = None
165
+ self.key = ""
166
+ self.depth += 1
167
+ try {
168
+ body()
169
+ } finally {
170
+ patchText(self)
171
+ if(!self.element.keepChildren) {
172
+ doWhile {removeCurrentChild(self)}
173
+ }
174
+ patchAttributes(self)
175
+ self.depth -= 1
176
+ self.attributes = savedAttributes
177
+ self.keys = savedKeys
178
+ self.element = savedElement
179
+ self.element.child += 1
180
+ }
181
+ }
182
+
183
+ div(body: () => Unit = {}) {self.add("div", body)}
184
+ span(body: () => Unit = {}) {self.add("span", body)}
185
+ label(body: () => Unit = {}) {self.add("label", body)}
186
+ button(body: () => Unit = {}) {self.add("button", body)}
187
+ form(body: () => Unit = {}) {self.add("form", body)}
188
+ input(body: () => Unit = {}) {self.add("input", body)}
189
+
190
+ keyed(key: String, body: () => Unit) {
191
+ try {
192
+ self.key = key
193
+ body()
194
+ } finally {
195
+ self.key = ""
196
+ }
197
+ }
198
+
199
+ keyedList[T](values: List[T], getKey: T => String, body: T => Unit) {
200
+ try {
201
+ values.each {item =>
202
+ self.key = getKey(item)
203
+ body(item)
204
+ }
205
+ } finally {
206
+ self.key = ""
207
+ }
208
+ }
209
+
210
+ set(attribute: String, value: String) {
211
+ let attributes = self.attributes.else {
212
+ let map = StringMap.new()
213
+ self.attributes = Some(map)
214
+ map
215
+ }
216
+ attributes.set(attribute, value)
217
+ }
218
+
219
+ setId(value: String) {self.set("id", value)}
220
+
221
+ setValue(value: String) { // TODO: Not an attribute
222
+ self.dry.map {_ => self.set("value", value)}.else:
223
+ self.element.element->value = value
224
+ }
225
+
226
+ css(style: Css) {
227
+ let attributes = self.attributes.else {
228
+ let map = StringMap.new()
229
+ self.attributes = Some(map)
230
+ map
231
+ }
232
+ if(attributes.has("style")) {
233
+ attributes.set("style", attributes.grab("style") + ";" + style.property + ":" + style.value)
234
+ } else {
235
+ attributes.set("style", style.property + ":" + style.value)
236
+ }
237
+ }
238
+
239
+ cssClass(class: CssClass) {
240
+ if(!self.cssClasses.has(class.name())) {
241
+ self.cssClasses.set(class.name(), class)
242
+ if(self.dry.isEmpty()):
243
+ let styleSheet = self.document.createElement("style")
244
+ styleSheet->textContent = class.show()
245
+ self.document.document->head->appendChild(styleSheet)
246
+ }
247
+ let classNames = self.attributes.flatMap {_.get("class")}.map {_ + " "}.else {""}
248
+ self.set("class", classNames + class.name())
249
+ }
250
+
251
+ on(event: String, handler: LuxEvent => Unit) {
252
+ if(self.dry.isEmpty()):
253
+ let jsHandler = Js->{jsEvent =>
254
+ self.renderLock.do {
255
+ handler(LuxEvent(jsEvent))
256
+ processRenderQueue(self)!
257
+ }
258
+ }
259
+ self.element.element->addEventListener(event, jsHandler)
260
+ if(self.element.element->luxHandlers.isNullOrUndefined()) {
261
+ self.element.element->luxHandlers = self.jsSystem.array([])
262
+ }
263
+ self.element.element->luxHandlers->push(
264
+ self.jsSystem.object().with("event", event).with("handler", jsHandler)
265
+ )
266
+ }
267
+
268
+ onClick(handler: LuxEvent => Unit) {self.on("click", handler)}
269
+ onInput(handler: LuxEvent => Unit) {self.on("input", handler)}
270
+
271
+ useState[T: HasAnyTag](initialValue: T, body: (T, T => Unit) => Unit) {
272
+ self.dry.map {_ => body(initialValue, {_ => })}.else:
273
+ self.depth += 1
274
+ let value = getStateOnElement(self.element.element, self.depth, initialValue)
275
+ mutable i = 0
276
+ while {i < self.renderQueue.size()} {
277
+ let item = self.renderQueue.grab(i)
278
+ if(item.luxCopy.element.element.equals(self.element.element) && item.luxCopy.depth == self.depth) {
279
+ self.renderQueue.delete(i, 1)
280
+ } else {
281
+ i += 1
282
+ }
283
+ }
284
+ let luxCopy = self.Lux(element = self.element.LuxElement())
285
+ function setState(newValue: T): Unit {
286
+ setStateOnElement(luxCopy.element.element, luxCopy.depth, newValue)
287
+ self.renderQueue.push(RenderQueueItem(
288
+ luxCopy = luxCopy
289
+ render = {
290
+ let child = luxCopy.element.child
291
+ let element = luxCopy.element.childAt(child)
292
+ body(newValue, {setState(_)})
293
+ if(!element.equals(luxCopy.element.childAt(child))) {
294
+ removeCurrentChild(luxCopy.Lux(
295
+ element = luxCopy.element.LuxElement(child = luxCopy.element.child + 1)
296
+ ))
297
+ }
298
+ }
299
+ ))
300
+ }
301
+ try {
302
+ body(value, {setState(_)})
303
+ } finally {
304
+ self.depth -= 1
305
+ }
306
+ }
307
+
308
+ useCallback1[A1: Equal: HasAnyTag](callback: A1 => Unit, body: (A1 => Unit) => Unit) {
309
+ self.dry.map {_ => body(callback)}.else:
310
+ self.depth += 1
311
+ setStateOnElement(self.element.element, self.depth, callback)
312
+ let element = self.element.element
313
+ let depth = self.depth
314
+ try {
315
+ body {a1 =>
316
+ let latestCallback = getStateOnElement(element, depth, callback)
317
+ latestCallback(a1)
318
+ }
319
+ } finally {
320
+ self.depth -= 1
321
+ }
322
+ }
323
+
324
+ useLazy1[A1: Equal: HasAnyTag](a1: A1, body: A1 => Unit = {_ => }) {
325
+ self.dry.map {_ => body(a1)}.else:
326
+ self.depth += 1
327
+ try {
328
+ let old = getStateOnElement(self.element.element, self.depth, None)
329
+ old.{
330
+ | Some(o1) {a1 == o1} =>
331
+ self.element.keepChildren = True
332
+ | _ =>
333
+ setStateOnElement(self.element.element, self.depth, Some(a1))
334
+ body(a1)
335
+ }
336
+ } finally {
337
+ self.depth -= 1
338
+ }
339
+ }
340
+
341
+ useMemo1[A1: Equal: HasAnyTag, T: HasAnyTag](a1: A1, compute: A1 => T, body: T => Unit) {
342
+ self.dry.map {_ => body(compute(a1))}.else:
343
+ self.depth += 1
344
+ try {
345
+ let old = getStateOnElement(self.element.element, self.depth, Pair(a1, None))
346
+ let computed = old.{
347
+ | Pair(o1, _) {a1 != o1} =>
348
+ let v = compute(a1)
349
+ setStateOnElement(self.element.element, self.depth, Pair(a1, Some(v)))
350
+ v
351
+ | Pair(_, Some(v)) =>
352
+ v
353
+ | Pair(_, None) =>
354
+ let v = compute(a1)
355
+ setStateOnElement(self.element.element, self.depth, Pair(a1, Some(v)))
356
+ v
357
+ }
358
+ body(computed)
359
+ } finally {
360
+ self.depth -= 1
361
+ }
362
+ }
363
+
364
+ useSuspense(suspense: () => Unit, body: Lux => Unit) {
365
+ self.dry.map {_ => suspense()}.else:
366
+ let oldSubtask = getTaskOnElement(self.element.element)
367
+ oldSubtask.each {task =>
368
+ forceAsyncAbort(self, task)
369
+ }
370
+ let fragment = self.document.createFragment()
371
+ let luxCopy = self.Lux(element = self.element.LuxElement())
372
+ let subtask = self.task.spawn {task =>
373
+ let lux = luxCopy.Lux(
374
+ task = task
375
+ renderLock = task.lock()
376
+ renderQueue = Array.new()
377
+ element = luxCopy.element.LuxElement(element = fragment)
378
+ )
379
+ try {
380
+ body(lux)
381
+ task.throwIfAborted()
382
+ lux.copyFrom(luxCopy)
383
+ lux.renderLock.do {
384
+ abortTasksOnElement(lux, lux.element.childAt(lux.element.child), onlyChildren = True)
385
+ lux.element.removeAt(lux.element.child)
386
+ lux.element.insertBefore(fragment, lux.jsSystem.null())
387
+ }
388
+ } catchAny {error =>
389
+ if(error.name() != "AbortError") {
390
+ error.rethrow()
391
+ }
392
+ }
393
+ }
394
+ suspense()
395
+ setTaskOnElement(self.element.childAt(self.element.child - 1), Some(subtask))
396
+ }
397
+
398
+ }
399
+
400
+ processRenderQueue(self: Lux) {
401
+ self.renderQueue.sortBy {-_.luxCopy.depth}
402
+ let newLux = self.Lux(element = self.element.LuxElement())
403
+ try {
404
+ while {!self.renderQueue.isEmpty()} {
405
+ let item = self.renderQueue.pop().grab()
406
+ self.copyFrom(item.luxCopy)
407
+ item.render()
408
+ }
409
+ } finally {
410
+ self.copyFrom(newLux)
411
+ }
412
+ }
413
+
414
+ removeCurrentChild(self: Lux): Bool {
415
+ let child = self.element.childAt(self.element.child)
416
+ if(!child.isNullOrUndefined() && !child->children.isNullOrUndefined()) {
417
+ abortTasksOnElement(self, child, False)
418
+ }
419
+ self.element.removeAt(self.element.child)
420
+ }
421
+
422
+ getStateOnElement[T /*: HasAnyTag*/](element: JsValue, depth: Int, fallback: T): T {
423
+ let value = element.get("lux" + depth)
424
+ if(value.isNullOrUndefined()) {fallback} else {
425
+ value?
426
+ }
427
+ }
428
+
429
+ setStateOnElement[T /*: HasAnyTag*/](element: JsValue, depth: Int, value: T): Unit {
430
+ element.set("lux" + depth, value!)
431
+ }
432
+
433
+
434
+ getTaskOnElement(element: JsValue): Option[Task] {
435
+ let value = element->luxTask
436
+ if(value.isNullOrUndefined()) {None} else {
437
+ value?
438
+ }
439
+ }
440
+
441
+ setTaskOnElement(element: JsValue, task: Option[Task]): Unit {
442
+ element->luxTask = task!
443
+ }
444
+
445
+ abortTasksOnElement(lux: Lux, element: JsValue, onlyChildren: Bool): Unit {
446
+ if(!onlyChildren) {
447
+ getTaskOnElement(element).each {task => forceAsyncAbort(lux, task)}
448
+ }
449
+ element->children.each {child =>
450
+ abortTasksOnElement(lux, child, False)
451
+ }
452
+ }
453
+
454
+ forceAsyncAbort(self: Lux, task: Task): Unit {
455
+ task.abort()
456
+ }
457
+
458
+ patchText(self: Lux) {
459
+ if(!self.texts.isEmpty()):
460
+ let value = self.texts.drain().join()
461
+ self.dry.map {_.push(DryText(value))}.else:
462
+ let oldNode = self.element.childAt(self.element.child)
463
+ let oldValue = if(!oldNode.isNullOrUndefined()) {oldNode->data} else {oldNode}
464
+ if(oldValue.isNullOrUndefined() || oldValue.grabString() != value) {
465
+ let node = self.document.createTextNode(value)
466
+ self.element.insertBefore(node, oldNode)
467
+ }
468
+ self.element.child += 1
469
+ }
470
+
471
+ patchElement(self: Lux, tagName: String): JsValue {
472
+ let newKey = if(self.key != "") {tagName.upper() + ">" + self.key} else {""}
473
+ let oldNode = self.element.childAt(self.element.child)
474
+ let oldKey = if(!oldNode.isNullOrUndefined() && !oldNode->getAttribute.isNullOrUndefined()) {
475
+ oldNode->getAttribute("data-lux-key")
476
+ } else {oldNode}
477
+ let match = if(newKey != "") {
478
+ !oldKey.isNullOrUndefined() &&
479
+ oldKey.grabString() == newKey
480
+ } else {
481
+ oldKey.isNullOrUndefined() &&
482
+ !oldNode.isNullOrUndefined() &&
483
+ !oldNode->tagName.isNullOrUndefined() &&
484
+ oldNode->tagName.grabString() == tagName.upper()
485
+ }
486
+ if(match) {
487
+ oldNode
488
+ } else {
489
+ let newNode = if(newKey == "") {self.document.createElement(tagName)} else {
490
+ let keys = self.keys.else {
491
+ let map = StringMap.new()
492
+ mutable i = self.element.child
493
+ mutable c = self.element.childAt(i)
494
+ while {!c.isNullOrUndefined()} {
495
+ if(!c->getAttribute.isNullOrUndefined()) {
496
+ let k = c->getAttribute("data-lux-key")
497
+ if(!k.isNullOrUndefined()) {map.set(k.grabString(), c)}
498
+ }
499
+ i += 1
500
+ c = self.element.childAt(i)
501
+ }
502
+ self.keys = Some(map)
503
+ map
504
+ }
505
+ if(keys.has(newKey)) {
506
+ let foundNode = keys.grab(newKey)
507
+ keys.remove(newKey)
508
+ foundNode
509
+ } else {
510
+ let createdNode = self.document.createElement(tagName)
511
+ createdNode->setAttribute("data-lux-key", newKey)
512
+ createdNode
513
+ }
514
+ }
515
+ self.element.insertBefore(newNode, oldNode)
516
+ newNode
517
+ }
518
+ }
519
+
520
+ patchAttributes(self: Lux) {
521
+ let attributes = self.element.element->attributes
522
+ self.attributes.{
523
+ | None =>
524
+ mutable i = attributes->length.grabInt() - 1
525
+ while {i >= 0} {
526
+ let attribute = attributes.get(i)
527
+ self.element.element->removeAttribute(attribute->name)
528
+ i -= 1
529
+ }
530
+ | Some(map) =>
531
+ mutable i = attributes->length.grabInt() - 1
532
+ while {i >= 0} {
533
+ let attribute = attributes.get(i)
534
+ if(!map.has(attribute->name.grabString())) {
535
+ self.element.element->removeAttribute(attribute->name)
536
+ }
537
+ i -= 1
538
+ }
539
+ map.each {name, value =>
540
+ let oldValue = self.element.element->getAttribute(name)
541
+ if(oldValue.isNullOrUndefined() || !oldValue.equals(value)) {
542
+ self.element.element->setAttribute(name, value)
543
+ }
544
+ }
545
+ }
546
+ }
547
+
548
+ render(browserSystem: BrowserSystem, element: JsValue, body: Lux => Unit) {
549
+ mutable document = element
550
+ while {!document->parentNode.isNullOrUndefined()} {
551
+ document = document->parentNode
552
+ }
553
+ // [...document.querySelectorAll("[lux-class]")].map(x => x.getAttribute("lux-class"))
554
+ let staticCssClasses = StringMap.new()
555
+ let dummyCssClass = CssClass([], [], [])
556
+ document->querySelectorAll("[lux-class]").each {e =>
557
+ staticCssClasses.set(e->getAttribute("lux-class").grabString(), dummyCssClass)
558
+ }
559
+ let lux = Lux(
560
+ jsSystem = browserSystem.js()
561
+ renderLock = browserSystem.mainTask().lock()
562
+ dry = None
563
+ cssClasses = staticCssClasses
564
+ task = browserSystem.mainTask()
565
+ depth = 0
566
+ document = LuxDocument(document)
567
+ element = LuxElement(element, 0, keepChildren = False)
568
+ keys = None
569
+ key = ""
570
+ attributes = None
571
+ texts = Array.new()
572
+ renderQueue = Array.new()
573
+ )
574
+ lux.renderLock.do {
575
+ body(lux)
576
+ patchText(lux)
577
+ }
578
+ }
579
+
580
+ renderById(browserSystem: BrowserSystem, id: String, body: Lux => Unit) {
581
+ let element = browserSystem.js()->document->getElementById(id)
582
+ render(browserSystem, element, body)
583
+ }
584
+
585
+ renderToString(nodeSystem: NodeSystem, body: Lux => Unit): Pair[String, String] {
586
+ let children = [].toArray()
587
+ let lux = Lux(
588
+ jsSystem = nodeSystem.js()
589
+ renderLock = nodeSystem.mainTask().lock()
590
+ dry = Some(children)
591
+ cssClasses = StringMap.new()
592
+ task = nodeSystem.mainTask()
593
+ depth = 0
594
+ document = LuxDocument(nodeSystem.js().null())
595
+ element = LuxElement(nodeSystem.js().null(), 0, keepChildren = False)
596
+ keys = None
597
+ key = ""
598
+ attributes = None
599
+ texts = Array.new()
600
+ renderQueue = Array.new()
601
+ )
602
+ body(lux)
603
+ patchText(lux)
604
+ let styleTags = lux.cssClasses.values().map {c =>
605
+ "<style lux-class=\"" + c.name() + "\">" + c.show() + "</style>"
606
+ }.join()
607
+ Pair(children.toList().map {_.toHtml()}.join(), styleTags)
608
+ }