firefly-compiler 0.4.40 → 0.4.46

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 (108) hide show
  1. package/.hintrc +4 -4
  2. package/.vscode/settings.json +4 -4
  3. package/bin/Release.ff +99 -71
  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 +186 -186
  8. package/compiler/DependencyLock.ff +17 -17
  9. package/compiler/JsEmitter.ff +946 -946
  10. package/compiler/LspHook.ff +202 -202
  11. package/compiler/ModuleCache.ff +178 -178
  12. package/compiler/Workspace.ff +88 -88
  13. package/core/.firefly/include/package-lock.json +394 -394
  14. package/core/.firefly/include/package.json +5 -5
  15. package/core/.firefly/include/prepare.sh +1 -1
  16. package/core/.firefly/package.ff +2 -2
  17. package/core/Array.ff +265 -265
  18. package/core/Atomic.ff +64 -64
  19. package/core/Box.ff +7 -7
  20. package/core/BrowserSystem.ff +40 -40
  21. package/core/BuildSystem.ff +148 -148
  22. package/core/Crypto.ff +96 -102
  23. package/core/Equal.ff +36 -36
  24. package/core/HttpClient.ff +87 -87
  25. package/core/JsSystem.ff +69 -69
  26. package/core/Json.ff +434 -434
  27. package/core/List.ff +415 -415
  28. package/core/Lock.ff +144 -144
  29. package/core/NodeSystem.ff +189 -189
  30. package/core/Ordering.ff +161 -161
  31. package/core/Path.ff +401 -401
  32. package/core/Random.ff +134 -134
  33. package/core/RbMap.ff +216 -216
  34. package/core/Show.ff +43 -43
  35. package/core/SourceLocation.ff +68 -68
  36. package/core/Task.ff +141 -141
  37. package/experimental/benchmarks/ListGrab.ff +23 -23
  38. package/experimental/benchmarks/ListGrab.java +55 -55
  39. package/experimental/benchmarks/Pyrotek45.ff +30 -30
  40. package/experimental/benchmarks/Pyrotek45.java +64 -64
  41. package/experimental/bidirectional/Bidi.ff +88 -88
  42. package/experimental/random/Index.ff +53 -53
  43. package/experimental/random/Process.ff +120 -120
  44. package/experimental/random/Scrape.ff +51 -51
  45. package/experimental/random/Symbols.ff +73 -73
  46. package/experimental/random/Tensor.ff +52 -52
  47. package/experimental/random/Units.ff +36 -36
  48. package/experimental/s3/S3TestAuthorizationHeader.ff +38 -38
  49. package/experimental/s3/S3TestPut.ff +15 -15
  50. package/experimental/tests/TestJson.ff +26 -26
  51. package/firefly.sh +0 -0
  52. package/fireflysite/Main.ff +13 -13
  53. package/lsp/.firefly/package.ff +1 -1
  54. package/lsp/CompletionHandler.ff +811 -811
  55. package/lsp/Handler.ff +714 -714
  56. package/lsp/HoverHandler.ff +79 -79
  57. package/lsp/LanguageServer.ff +272 -272
  58. package/lsp/SignatureHelpHandler.ff +55 -55
  59. package/lsp/SymbolHandler.ff +181 -181
  60. package/lsp/TestReferences.ff +16 -16
  61. package/lsp/TestReferencesCase.ff +7 -7
  62. package/lsp/stderr.txt +1 -1
  63. package/lsp/stdout.txt +34 -34
  64. package/lux/.firefly/package.ff +1 -1
  65. package/lux/Css.ff +648 -648
  66. package/lux/CssTest.ff +48 -48
  67. package/lux/Lux.ff +487 -487
  68. package/lux/LuxEvent.ff +116 -116
  69. package/lux/Main.ff +128 -128
  70. package/lux/Main2.ff +144 -144
  71. package/output/js/ff/compiler/Builder.mjs +43 -43
  72. package/output/js/ff/compiler/Dependencies.mjs +3 -3
  73. package/output/js/ff/core/Array.mjs +59 -59
  74. package/output/js/ff/core/Atomic.mjs +36 -36
  75. package/output/js/ff/core/BrowserSystem.mjs +11 -11
  76. package/output/js/ff/core/BuildSystem.mjs +30 -30
  77. package/output/js/ff/core/Crypto.mjs +58 -62
  78. package/output/js/ff/core/HttpClient.mjs +24 -24
  79. package/output/js/ff/core/Json.mjs +147 -147
  80. package/output/js/ff/core/List.mjs +50 -50
  81. package/output/js/ff/core/Lock.mjs +97 -97
  82. package/output/js/ff/core/NodeSystem.mjs +77 -77
  83. package/output/js/ff/core/Ordering.mjs +8 -8
  84. package/output/js/ff/core/Path.mjs +231 -231
  85. package/output/js/ff/core/Random.mjs +56 -56
  86. package/output/js/ff/core/Task.mjs +31 -31
  87. package/package.json +29 -29
  88. package/rpc/.firefly/package.ff +1 -1
  89. package/rpc/Rpc.ff +69 -69
  90. package/s3/.firefly/package.ff +1 -1
  91. package/s3/S3.ff +92 -92
  92. package/unsafejs/UnsafeJs.ff +19 -19
  93. package/vscode/LICENSE.txt +21 -21
  94. package/vscode/Prepublish.ff +15 -15
  95. package/vscode/README.md +16 -16
  96. package/vscode/client/package.json +22 -22
  97. package/vscode/client/src/extension.ts +104 -104
  98. package/vscode/icons/firefly-icon.svg +10 -10
  99. package/vscode/language-configuration.json +61 -61
  100. package/vscode/package-lock.json +3623 -3623
  101. package/vscode/package.json +160 -160
  102. package/vscode/snippets.json +241 -241
  103. package/webserver/.firefly/include/package-lock.json +16 -16
  104. package/webserver/.firefly/include/package.json +5 -5
  105. package/webserver/.firefly/package.ff +2 -2
  106. package/webserver/WebServer.ff +685 -685
  107. package/websocket/.firefly/package.ff +1 -1
  108. package/websocket/WebSocket.ff +131 -131
package/lux/Lux.ff CHANGED
@@ -1,487 +1,487 @@
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 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
+ }