firefly-compiler 0.4.79 → 0.4.81

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