@typed/template 0.2.0 → 0.3.1

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 (227) hide show
  1. package/dist/cjs/Directive.js +1 -1
  2. package/dist/cjs/Directive.js.map +1 -1
  3. package/dist/cjs/ElementRef.js +23 -13
  4. package/dist/cjs/ElementRef.js.map +1 -1
  5. package/dist/cjs/ElementSource.js +16 -18
  6. package/dist/cjs/ElementSource.js.map +1 -1
  7. package/dist/cjs/EventHandler.js +1 -1
  8. package/dist/cjs/EventHandler.js.map +1 -1
  9. package/dist/cjs/Html.js +31 -32
  10. package/dist/cjs/Html.js.map +1 -1
  11. package/dist/cjs/HtmlChunk.js +4 -1
  12. package/dist/cjs/HtmlChunk.js.map +1 -1
  13. package/dist/cjs/Hydrate.js +1 -1
  14. package/dist/cjs/Hydrate.js.map +1 -1
  15. package/dist/cjs/Many.js +14 -13
  16. package/dist/cjs/Many.js.map +1 -1
  17. package/dist/cjs/Parser.js +11 -323
  18. package/dist/cjs/Parser.js.map +1 -1
  19. package/dist/cjs/Placeholder.js +3 -3
  20. package/dist/cjs/Placeholder.js.map +1 -1
  21. package/dist/cjs/Platform.js +4 -4
  22. package/dist/cjs/Platform.js.map +1 -1
  23. package/dist/cjs/Render.js +1 -1
  24. package/dist/cjs/Render.js.map +1 -1
  25. package/dist/cjs/RenderContext.js +48 -27
  26. package/dist/cjs/RenderContext.js.map +1 -1
  27. package/dist/cjs/RenderTemplate.js +2 -17
  28. package/dist/cjs/RenderTemplate.js.map +1 -1
  29. package/dist/cjs/Template.js +27 -1
  30. package/dist/cjs/Template.js.map +1 -1
  31. package/dist/cjs/TemplateInstance.js +2 -2
  32. package/dist/cjs/TemplateInstance.js.map +1 -1
  33. package/dist/cjs/Test.js +20 -7
  34. package/dist/cjs/Test.js.map +1 -1
  35. package/dist/cjs/index.js +0 -12
  36. package/dist/cjs/index.js.map +1 -1
  37. package/dist/cjs/internal/EventSource.js +95 -0
  38. package/dist/cjs/internal/EventSource.js.map +1 -0
  39. package/dist/cjs/internal/browser.js +11 -11
  40. package/dist/cjs/internal/browser.js.map +1 -1
  41. package/dist/cjs/internal/chunks.js +4 -1
  42. package/dist/cjs/internal/chunks.js.map +1 -1
  43. package/dist/cjs/internal/errors.js +4 -0
  44. package/dist/cjs/internal/errors.js.map +1 -1
  45. package/dist/cjs/internal/hydrate.js +113 -80
  46. package/dist/cjs/internal/hydrate.js.map +1 -1
  47. package/dist/cjs/internal/indexRefCounter.js +49 -2
  48. package/dist/cjs/internal/indexRefCounter.js.map +1 -1
  49. package/dist/cjs/internal/parser.js +72 -21
  50. package/dist/cjs/internal/parser.js.map +1 -1
  51. package/dist/cjs/internal/parts.js +128 -28
  52. package/dist/cjs/internal/parts.js.map +1 -1
  53. package/dist/cjs/internal/render.js +460 -161
  54. package/dist/cjs/internal/render.js.map +1 -1
  55. package/dist/cjs/internal/server.js +5 -2
  56. package/dist/cjs/internal/server.js.map +1 -1
  57. package/dist/dts/Directive.d.ts.map +1 -1
  58. package/dist/dts/ElementRef.d.ts +4 -2
  59. package/dist/dts/ElementRef.d.ts.map +1 -1
  60. package/dist/dts/ElementSource.d.ts +10 -5
  61. package/dist/dts/ElementSource.d.ts.map +1 -1
  62. package/dist/dts/EventHandler.d.ts.map +1 -1
  63. package/dist/dts/Html.d.ts +1 -1
  64. package/dist/dts/Html.d.ts.map +1 -1
  65. package/dist/dts/HtmlChunk.d.ts.map +1 -1
  66. package/dist/dts/Many.d.ts +13 -11
  67. package/dist/dts/Many.d.ts.map +1 -1
  68. package/dist/dts/Parser.d.ts +3 -6
  69. package/dist/dts/Parser.d.ts.map +1 -1
  70. package/dist/dts/Part.d.ts +13 -3
  71. package/dist/dts/Part.d.ts.map +1 -1
  72. package/dist/dts/Placeholder.d.ts +2 -2
  73. package/dist/dts/Placeholder.d.ts.map +1 -1
  74. package/dist/dts/Render.d.ts +2 -1
  75. package/dist/dts/Render.d.ts.map +1 -1
  76. package/dist/dts/RenderContext.d.ts +5 -4
  77. package/dist/dts/RenderContext.d.ts.map +1 -1
  78. package/dist/dts/RenderTemplate.d.ts +2 -16
  79. package/dist/dts/RenderTemplate.d.ts.map +1 -1
  80. package/dist/dts/Renderable.d.ts +2 -2
  81. package/dist/dts/Renderable.d.ts.map +1 -1
  82. package/dist/dts/Template.d.ts +21 -3
  83. package/dist/dts/Template.d.ts.map +1 -1
  84. package/dist/dts/TemplateInstance.d.ts +3 -2
  85. package/dist/dts/TemplateInstance.d.ts.map +1 -1
  86. package/dist/dts/Test.d.ts +4 -6
  87. package/dist/dts/Test.d.ts.map +1 -1
  88. package/dist/dts/index.d.ts +0 -4
  89. package/dist/dts/index.d.ts.map +1 -1
  90. package/dist/dts/internal/EventSource.d.ts +12 -0
  91. package/dist/dts/internal/EventSource.d.ts.map +1 -0
  92. package/dist/dts/internal/browser.d.ts.map +1 -1
  93. package/dist/dts/internal/chunks.d.ts +1 -0
  94. package/dist/dts/internal/chunks.d.ts.map +1 -1
  95. package/dist/dts/internal/errors.d.ts +1 -0
  96. package/dist/dts/internal/errors.d.ts.map +1 -1
  97. package/dist/dts/internal/hydrate.d.ts +9 -16
  98. package/dist/dts/internal/hydrate.d.ts.map +1 -1
  99. package/dist/dts/internal/indexRefCounter.d.ts +5 -0
  100. package/dist/dts/internal/indexRefCounter.d.ts.map +1 -1
  101. package/dist/dts/internal/module-augmentation.d.ts +0 -4
  102. package/dist/dts/internal/module-augmentation.d.ts.map +1 -1
  103. package/dist/dts/internal/parser.d.ts +8 -0
  104. package/dist/dts/internal/parser.d.ts.map +1 -1
  105. package/dist/dts/internal/parts.d.ts +66 -56
  106. package/dist/dts/internal/parts.d.ts.map +1 -1
  107. package/dist/dts/internal/render.d.ts +1 -15
  108. package/dist/dts/internal/render.d.ts.map +1 -1
  109. package/dist/dts/internal/server.d.ts.map +1 -1
  110. package/dist/esm/Directive.js +1 -1
  111. package/dist/esm/Directive.js.map +1 -1
  112. package/dist/esm/ElementRef.js +12 -7
  113. package/dist/esm/ElementRef.js.map +1 -1
  114. package/dist/esm/ElementSource.js +16 -13
  115. package/dist/esm/ElementSource.js.map +1 -1
  116. package/dist/esm/EventHandler.js +1 -1
  117. package/dist/esm/EventHandler.js.map +1 -1
  118. package/dist/esm/Html.js +29 -24
  119. package/dist/esm/Html.js.map +1 -1
  120. package/dist/esm/HtmlChunk.js +6 -1
  121. package/dist/esm/HtmlChunk.js.map +1 -1
  122. package/dist/esm/Hydrate.js +1 -1
  123. package/dist/esm/Hydrate.js.map +1 -1
  124. package/dist/esm/Many.js +14 -10
  125. package/dist/esm/Many.js.map +1 -1
  126. package/dist/esm/Parser.js +6 -335
  127. package/dist/esm/Parser.js.map +1 -1
  128. package/dist/esm/Placeholder.js +2 -2
  129. package/dist/esm/Placeholder.js.map +1 -1
  130. package/dist/esm/Platform.js +2 -2
  131. package/dist/esm/Platform.js.map +1 -1
  132. package/dist/esm/Render.js +1 -1
  133. package/dist/esm/Render.js.map +1 -1
  134. package/dist/esm/RenderContext.js +38 -17
  135. package/dist/esm/RenderContext.js.map +1 -1
  136. package/dist/esm/RenderTemplate.js +2 -12
  137. package/dist/esm/RenderTemplate.js.map +1 -1
  138. package/dist/esm/Template.js +24 -0
  139. package/dist/esm/Template.js.map +1 -1
  140. package/dist/esm/TemplateInstance.js +2 -2
  141. package/dist/esm/TemplateInstance.js.map +1 -1
  142. package/dist/esm/Test.js +20 -7
  143. package/dist/esm/Test.js.map +1 -1
  144. package/dist/esm/index.js +0 -4
  145. package/dist/esm/index.js.map +1 -1
  146. package/dist/esm/internal/EventSource.js +91 -0
  147. package/dist/esm/internal/EventSource.js.map +1 -0
  148. package/dist/esm/internal/browser.js +12 -12
  149. package/dist/esm/internal/browser.js.map +1 -1
  150. package/dist/esm/internal/chunks.js +2 -0
  151. package/dist/esm/internal/chunks.js.map +1 -1
  152. package/dist/esm/internal/errors.js +3 -0
  153. package/dist/esm/internal/errors.js.map +1 -1
  154. package/dist/esm/internal/hydrate.js +113 -76
  155. package/dist/esm/internal/hydrate.js.map +1 -1
  156. package/dist/esm/internal/indexRefCounter.js +50 -2
  157. package/dist/esm/internal/indexRefCounter.js.map +1 -1
  158. package/dist/esm/internal/parser.js +98 -22
  159. package/dist/esm/internal/parser.js.map +1 -1
  160. package/dist/esm/internal/parts.js +110 -27
  161. package/dist/esm/internal/parts.js.map +1 -1
  162. package/dist/esm/internal/render.js +446 -157
  163. package/dist/esm/internal/render.js.map +1 -1
  164. package/dist/esm/internal/server.js +5 -4
  165. package/dist/esm/internal/server.js.map +1 -1
  166. package/package.json +10 -26
  167. package/src/Directive.ts +1 -1
  168. package/src/ElementRef.ts +18 -14
  169. package/src/ElementSource.ts +62 -47
  170. package/src/EventHandler.ts +1 -1
  171. package/src/Html.ts +58 -57
  172. package/src/HtmlChunk.ts +15 -1
  173. package/src/Hydrate.ts +1 -1
  174. package/src/Many.ts +53 -43
  175. package/src/Parser.ts +10 -453
  176. package/src/Part.ts +15 -3
  177. package/src/Placeholder.ts +4 -4
  178. package/src/Platform.ts +2 -2
  179. package/src/Render.ts +7 -2
  180. package/src/RenderContext.ts +47 -19
  181. package/src/RenderTemplate.ts +9 -54
  182. package/src/Renderable.ts +2 -1
  183. package/src/Template.ts +26 -0
  184. package/src/TemplateInstance.ts +9 -9
  185. package/src/Test.ts +40 -21
  186. package/src/index.ts +0 -4
  187. package/src/internal/EventSource.ts +153 -0
  188. package/src/internal/browser.ts +26 -25
  189. package/src/internal/chunks.ts +4 -0
  190. package/src/internal/errors.ts +4 -0
  191. package/src/internal/hydrate.ts +161 -107
  192. package/src/internal/indexRefCounter.ts +63 -2
  193. package/src/internal/module-augmentation.ts +0 -4
  194. package/src/internal/parser.ts +107 -25
  195. package/src/internal/parts.ts +158 -73
  196. package/src/internal/render.ts +638 -289
  197. package/src/internal/server.ts +5 -3
  198. package/Token/package.json +0 -6
  199. package/Tokenizer/package.json +0 -6
  200. package/dist/cjs/Token.js +0 -270
  201. package/dist/cjs/Token.js.map +0 -1
  202. package/dist/cjs/Tokenizer.js +0 -18
  203. package/dist/cjs/Tokenizer.js.map +0 -1
  204. package/dist/cjs/internal/readAttribute.js +0 -34
  205. package/dist/cjs/internal/readAttribute.js.map +0 -1
  206. package/dist/cjs/internal/tokenizer.js +0 -264
  207. package/dist/cjs/internal/tokenizer.js.map +0 -1
  208. package/dist/dts/Token.d.ts +0 -202
  209. package/dist/dts/Token.d.ts.map +0 -1
  210. package/dist/dts/Tokenizer.d.ts +0 -6
  211. package/dist/dts/Tokenizer.d.ts.map +0 -1
  212. package/dist/dts/internal/readAttribute.d.ts +0 -9
  213. package/dist/dts/internal/readAttribute.d.ts.map +0 -1
  214. package/dist/dts/internal/tokenizer.d.ts +0 -3
  215. package/dist/dts/internal/tokenizer.d.ts.map +0 -1
  216. package/dist/esm/Token.js +0 -264
  217. package/dist/esm/Token.js.map +0 -1
  218. package/dist/esm/Tokenizer.js +0 -9
  219. package/dist/esm/Tokenizer.js.map +0 -1
  220. package/dist/esm/internal/readAttribute.js +0 -24
  221. package/dist/esm/internal/readAttribute.js.map +0 -1
  222. package/dist/esm/internal/tokenizer.js +0 -296
  223. package/dist/esm/internal/tokenizer.js.map +0 -1
  224. package/src/Token.ts +0 -269
  225. package/src/Tokenizer.ts +0 -10
  226. package/src/internal/readAttribute.ts +0 -28
  227. package/src/internal/tokenizer.ts +0 -338
@@ -2,9 +2,9 @@ import * as Chunk from "effect/Chunk"
2
2
  import { globalValue } from "effect/GlobalValue"
3
3
  import * as Option from "effect/Option"
4
4
  import * as Template from "../Template.js"
5
- import { SELF_CLOSING_TAGS, TEXT_ONLY_NODES_REGEX } from "../Token.js"
6
5
  import type { TextChunk } from "./chunks.js"
7
6
  import {
7
+ getClosingTagName,
8
8
  getPart,
9
9
  getStrictPart,
10
10
  getTextUntilCloseBrace,
@@ -16,6 +16,40 @@ import {
16
16
  // TODO: Consider ways to surface useful errors and warnings.
17
17
  // TODO: Profile for performance optimization
18
18
 
19
+ /**
20
+ * @since 1.0.0
21
+ */
22
+ export const TEXT_ONLY_NODES_REGEX = new Set([
23
+ "textarea",
24
+ "script",
25
+ "style",
26
+ "title",
27
+ "plaintext",
28
+ "xmp"
29
+ ])
30
+
31
+ /**
32
+ * @since 1.0.0
33
+ */
34
+ export const SELF_CLOSING_TAGS = new Set([
35
+ "area",
36
+ "base",
37
+ "br",
38
+ "col",
39
+ "command",
40
+ "embed",
41
+ "hr",
42
+ "img",
43
+ "input",
44
+ "keygen",
45
+ "link",
46
+ "meta",
47
+ "param",
48
+ "source",
49
+ "track",
50
+ "wbr"
51
+ ])
52
+
19
53
  export interface Parser {
20
54
  parse(templateStrings: ReadonlyArray<string>): Template.Template
21
55
  }
@@ -25,18 +59,39 @@ export function parse(templateStrings: ReadonlyArray<string>): Template.Template
25
59
  }
26
60
 
27
61
  const SPACE_REGEX = /\s/
28
- const isPartToken: TextPredicate = (input, pos) => input[pos] === "{" && input.slice(pos, pos + 8) === "{{__PART"
29
- const isPartEndToken: TextPredicate = (input, pos) => input[pos] === "_" && input.slice(pos, pos + 4) === "__}}"
30
- const isElementOpenToken: TextPredicate = (input, pos) => input[pos] === "<" && input[pos + 1] !== "/"
31
- const isElementCloseToken: TextPredicate = (input, pos) => input[pos] === "<" && input[pos + 1] === "/"
32
- const isEqualsToken: TextPredicate = (input, pos) => input[pos] === "="
33
- const isQuoteToken: TextPredicate = (input, pos) => input[pos] === `"`
34
- const isSingleQuoteToken: TextPredicate = (input, pos) => input[pos] === "'"
62
+ const PART_START = "{{__PART"
63
+ const PART_END = "__}}"
64
+ const chars = {
65
+ openBracket: "{",
66
+ closeBracket: "}",
67
+ underscore: "_",
68
+ equals: "=",
69
+ quote: `"`,
70
+ singleQuote: "'",
71
+ slash: "/",
72
+ greaterThan: ">",
73
+ lessThan: "<",
74
+ hypen: "-"
75
+ } as const
76
+
77
+ const isPartToken: TextPredicate = (input, pos) =>
78
+ input[pos] === chars.openBracket && input.slice(pos, pos + 8) === PART_START
79
+ const isPartEndToken: TextPredicate = (input, pos) =>
80
+ input[pos] === chars.underscore && input.slice(pos, pos + 4) === PART_END
81
+ const isElementOpenToken: TextPredicate = (input, pos) =>
82
+ input[pos] === chars.lessThan && input[pos + 1] !== chars.slash
83
+ const isElementCloseToken: TextPredicate = (input, pos) =>
84
+ input[pos] === chars.lessThan && input[pos + 1] === chars.slash
85
+ const isEqualsToken: TextPredicate = (input, pos) => input[pos] === chars.equals
86
+ const isQuoteToken: TextPredicate = (input, pos) => input[pos] === chars.quote
87
+ const isSingleQuoteToken: TextPredicate = (input, pos) => input[pos] === chars.singleQuote
35
88
  const isWhitespaceToken: TextPredicate = (input, pos) => SPACE_REGEX.test(input[pos])
36
- const isOpenTagEndToken: TextPredicate = (input, pos) => input[pos] === ">"
37
- const isSelfClosingTagEndToken: TextPredicate = (input, pos) => input[pos] === "/" && input[pos + 1] === ">"
89
+ const isOpenTagEndToken: TextPredicate = (input, pos) => input[pos] === chars.greaterThan
90
+ const isSelfClosingTagEndToken: TextPredicate = (input, pos) =>
91
+ input[pos] === chars.slash && input[pos + 1] === chars.greaterThan
38
92
  const isCommentEndToken: TextPredicate = (input, pos) =>
39
- input[pos] === "-" && input[pos + 1] === "-" && input[pos + 2] === ">"
93
+ input[pos] === chars.hypen && input[pos + 1] === chars.hypen && input[pos + 2] === chars.greaterThan
94
+ const isGreaterThanToken: TextPredicate = (input, pos) => input[pos] === chars.greaterThan
40
95
 
41
96
  type Context = "unknown" | "element"
42
97
 
@@ -154,6 +209,7 @@ class ParserImpl implements Parser {
154
209
 
155
210
  while (this.pos < this.length) {
156
211
  const node = this.parseNodeFromContext(this.context)
212
+
157
213
  if (node === undefined) {
158
214
  return nodes
159
215
  } else {
@@ -187,9 +243,18 @@ class ParserImpl implements Parser {
187
243
  const nextChar = this.nextChar()
188
244
 
189
245
  if (nextChar === "!") { // Comment
190
- this.consumeAmount(3)
246
+ this.consumeAmount(1)
191
247
 
192
- return [this.parseComment()]
248
+ const nextChar = this.nextChar()
249
+
250
+ if (nextChar == "-") {
251
+ this.consumeAmount(2)
252
+ return [this.parseComment()]
253
+ } else if (nextChar.toLowerCase() === "d") {
254
+ return [this.parseDocType()]
255
+ } else {
256
+ throw new SyntaxError(`Unknown comment type ${nextChar}`)
257
+ }
193
258
  } else if (nextChar === "/") { // Self-closing tag
194
259
  return this.selfClosingTagEnd()
195
260
  } else { // Elements
@@ -251,10 +316,17 @@ class ParserImpl implements Parser {
251
316
  this.path.pop()
252
317
  const element = new Template.ElementNode(tagName, attributes, children)
253
318
 
319
+ this.skipWhitespace()
320
+ this.consumeClosingTag(tagName)
321
+
254
322
  return element
255
323
  }
256
324
  }
257
325
 
326
+ private consumeClosingTag(tagName: string) {
327
+ this.chunk(getClosingTagName(tagName))
328
+ }
329
+
258
330
  private parseSelfClosingElement(tagName: string): Template.SelfClosingElementNode {
259
331
  const attributes = this.parseAttributes()
260
332
 
@@ -263,6 +335,7 @@ class ParserImpl implements Parser {
263
335
 
264
336
  private parseTextOnlyElement(tagName: string): Template.TextOnlyElement {
265
337
  const attributes = this.parseAttributes()
338
+
266
339
  this.path.push()
267
340
  const children = this.parseTextChildren()
268
341
  this.path.pop()
@@ -289,6 +362,14 @@ class ParserImpl implements Parser {
289
362
  return this.addPart(new Template.SparseCommentNode(textAndParts))
290
363
  }
291
364
 
365
+ private parseDocType(): Template.DocType {
366
+ this.parseTextUntil(isGreaterThanToken)
367
+ this.consumeAmount(1)
368
+ this.skipWhitespace()
369
+
370
+ return new Template.DocType("html")
371
+ }
372
+
292
373
  private parseTagName() {
293
374
  return this.parseTextUntilMany(tagNameMatches)
294
375
  }
@@ -304,6 +385,7 @@ class ParserImpl implements Parser {
304
385
  case null:
305
386
  return Skip
306
387
  case "whitespace":
388
+ this.skipWhitespace()
307
389
  return Continue([new Template.BooleanNode(name)])
308
390
  case "equals": {
309
391
  this.consumeAmount(1)
@@ -366,11 +448,13 @@ class ParserImpl implements Parser {
366
448
  case ".": {
367
449
  const property = name.slice(1)
368
450
 
369
- return this.addPart(
370
- property === "data"
371
- ? new Template.DataPartNode(unsafeParsePartIndex(text))
372
- : new Template.PropertyPartNode(property, unsafeParsePartIndex(text))
373
- )
451
+ if (property === "data") {
452
+ return this.addPart(new Template.DataPartNode(unsafeParsePartIndex(text)))
453
+ } else if (property === "props" || property === "properties") {
454
+ return this.addPart(new Template.PropertiesPartNode(unsafeParsePartIndex(text)))
455
+ } else {
456
+ return this.addPart(new Template.PropertyPartNode(property, unsafeParsePartIndex(text)))
457
+ }
374
458
  }
375
459
  case "@":
376
460
  return this.addPart(new Template.EventPartNode(name.slice(1), unsafeParsePartIndex(text)))
@@ -450,20 +534,18 @@ class ParserImpl implements Parser {
450
534
  }
451
535
 
452
536
  private parseTextUntil(predicate: (char: string, pos: number) => boolean) {
453
- let text = ""
454
-
537
+ const start = this.pos
538
+ let i = 0
455
539
  while (this.pos < this.length) {
456
- const char = this.nextChar()
457
-
458
540
  if (predicate(this.input, this.pos)) {
459
541
  break
460
542
  }
461
543
 
462
- text += char
544
+ i++
463
545
  this.consumeAmount(1)
464
546
  }
465
547
 
466
- return text
548
+ return this.input.slice(start, start + i)
467
549
  }
468
550
 
469
551
  private parseTextUntilMany<const T extends Predicates>(
@@ -617,7 +699,7 @@ function parseTextAndParts<T>(s: string, f: (index: number) => T): Array<Templat
617
699
  return out
618
700
  }
619
701
 
620
- export const parser: Parser = globalValue(Symbol.for("../Parser2.js"), () => new ParserImpl())
702
+ export const parser: Parser = globalValue(Symbol.for("@typed/template/Parser2"), () => new ParserImpl())
621
703
 
622
704
  const digestSize = 2
623
705
  const multiplier = 33
@@ -1,18 +1,13 @@
1
- import type { Context } from "@typed/context"
2
- import * as Fx from "@typed/fx/Fx"
3
- import { WithContext } from "@typed/fx/Sink"
4
- import { isText, type Rendered } from "@typed/wire"
1
+ import { isText } from "@typed/wire"
5
2
  import type { Cause } from "effect/Cause"
3
+ import * as Data from "effect/Data"
6
4
  import * as Effect from "effect/Effect"
7
5
  import { equals } from "effect/Equal"
8
- import { strict } from "effect/Equivalence"
9
- import type { Equivalence } from "effect/Equivalence"
10
- import * as Fiber from "effect/Fiber"
6
+ import * as Equivalence from "effect/Equivalence"
11
7
  import * as ReadonlyArray from "effect/ReadonlyArray"
12
8
  import type { Scope } from "effect/Scope"
13
- import * as SynchronizedRef from "effect/SynchronizedRef"
14
- import type { ElementRef } from "../ElementRef.js"
15
9
  import type { ElementSource } from "../ElementSource.js"
10
+ import type { EventHandler } from "../EventHandler.js"
16
11
  import { unescape } from "../HtmlChunk.js"
17
12
  import type {
18
13
  AttributePart,
@@ -23,6 +18,7 @@ import type {
23
18
  EventPart,
24
19
  NodePart,
25
20
  Part,
21
+ PropertiesPart,
26
22
  PropertyPart,
27
23
  RefPart,
28
24
  SparseAttributePart,
@@ -35,7 +31,7 @@ import type {
35
31
  import type { RenderContext } from "../RenderContext.js"
36
32
  import { findHoleComment } from "./utils.js"
37
33
 
38
- const strictEq = strict<any>()
34
+ const strictEq = Equivalence.strict<any>()
39
35
 
40
36
  const base = <T extends Part["_tag"]>(tag: T) =>
41
37
  class Base {
@@ -51,7 +47,7 @@ const base = <T extends Part["_tag"]>(tag: T) =>
51
47
  }
52
48
  ) => Effect.Effect<Scope, never, void>,
53
49
  public value: Extract<Part, { readonly _tag: T }>["value"],
54
- readonly eq: Equivalence<Extract<Part, { readonly _tag: T }>["value"]> = equals
50
+ readonly eq: Equivalence.Equivalence<Extract<Part, { readonly _tag: T }>["value"]> = equals
55
51
  ) {
56
52
  this.update = this.update.bind(this)
57
53
  }
@@ -64,7 +60,7 @@ const base = <T extends Part["_tag"]>(tag: T) =>
64
60
  return Effect.unit
65
61
  }
66
62
 
67
- return Effect.tap(
63
+ return Effect.flatMap(
68
64
  this.commit.call(this, {
69
65
  previous,
70
66
  value,
@@ -291,73 +287,20 @@ function diffDataSet(
291
287
  }
292
288
  }
293
289
 
294
- export class EventPartImpl extends base("event") implements EventPart {
290
+ export class EventPartImpl implements EventPart {
291
+ readonly _tag = "event"
292
+ readonly value: EventPart["value"] = null
293
+
295
294
  constructor(
296
295
  readonly name: string,
296
+ readonly index: number,
297
+ readonly source: ElementSource<any>,
297
298
  readonly onCause: <E>(cause: Cause<E>) => Effect.Effect<never, never, unknown>,
298
- index: number,
299
- commit: EventPartImpl["commit"],
300
- value: EventPart["value"]
299
+ readonly addEventListener: <Ev extends Event>(handler: EventHandler<never, never, Ev>) => void
301
300
  ) {
302
- super(index, commit, value, strictEq)
303
- }
304
-
305
- static browser<T extends Rendered, E>(
306
- name: string,
307
- index: number,
308
- ref: ElementRef<T>,
309
- element: HTMLElement | SVGElement,
310
- onCause: (cause: Cause<E>) => Effect.Effect<never, never, unknown>
311
- ): Effect.Effect<unknown, never, void> {
312
- return withSwitchFork((fork, ctx) => {
313
- const source = ref.query(element)
314
-
315
- return Effect.succeed(
316
- new EventPartImpl(
317
- name,
318
- onCause as any,
319
- index,
320
- ({ value }) => {
321
- return value
322
- ? Fx.run(
323
- source.events(name as keyof HTMLElementEventMap | keyof SVGElementEventMap, value.options),
324
- WithContext(onCause, value.handler)
325
- ).pipe(
326
- Effect.provide(ctx),
327
- fork
328
- )
329
- : fork(Effect.unit)
330
- },
331
- null
332
- )
333
- )
334
- })
335
301
  }
336
302
  }
337
303
 
338
- function withScopedFork<R, E, A>(f: (fork: Fx.ScopedFork) => Effect.Effect<R, E, A>): Effect.Effect<R | Scope, E, A> {
339
- return Effect.scopeWith((scope) => f(Effect.forkIn(scope)))
340
- }
341
-
342
- // Ensures only a single fiber is executing
343
- function withSwitchFork<R, E, A>(
344
- f: (fork: Fx.FxFork, ctx: Context<R | Scope>) => Effect.Effect<R, E, A>
345
- ): Effect.Effect<R | Scope, E, A> {
346
- return Effect.contextWithEffect((ctx) =>
347
- withScopedFork((fork) =>
348
- Effect.flatMap(
349
- SynchronizedRef.make<Fiber.Fiber<never, void>>(Fiber.unit),
350
- (ref) =>
351
- f((effect) =>
352
- SynchronizedRef.updateAndGetEffect(
353
- ref,
354
- (fiber) => Effect.flatMap(Fiber.interrupt(fiber), () => fork(effect))
355
- ), ctx)
356
- )
357
- )
358
- )
359
- }
360
-
361
304
  export class NodePartImpl extends base("node") implements NodePart {}
362
305
 
363
306
  export class PropertyPartImpl extends base("property") implements PropertyPart {
@@ -403,6 +346,147 @@ export class TextPartImpl extends base("text") implements TextPart {
403
346
  strictEq
404
347
  )
405
348
  }
349
+
350
+ static fromText(text: Text, index: number, ctx: RenderContext) {
351
+ return new TextPartImpl(
352
+ index,
353
+ ({ part, value }) => ctx.queue.add(part, () => text.nodeValue = value ?? null),
354
+ text.nodeValue,
355
+ strictEq
356
+ )
357
+ }
358
+
359
+ static getOrCreateText(document: Document, index: number, element: Element) {
360
+ const comment = findHoleComment(element, index)
361
+
362
+ return comment.previousSibling && isText(comment.previousSibling)
363
+ ? comment.previousSibling.nodeValue
364
+ : document.createTextNode("")
365
+ }
366
+ }
367
+
368
+ export class PropertiesPartImpl extends base("properties") implements PropertiesPart {
369
+ constructor(
370
+ index: number,
371
+ commit: PropertiesPartImpl["commit"],
372
+ value: PropertiesPartImpl["value"]
373
+ ) {
374
+ super(index, commit, value, equals)
375
+ }
376
+
377
+ getValue(value: unknown): unknown {
378
+ if (value == null) return null
379
+ return Data.struct(value)
380
+ }
381
+
382
+ static browser(index: number, element: HTMLElement | SVGElement, ctx: RenderContext) {
383
+ return new PropertiesPartImpl(
384
+ index,
385
+ ({ part, previous, value }) =>
386
+ ctx.queue.add(
387
+ part,
388
+ () => {
389
+ const diff = diffProperties(previous, value)
390
+ if (diff) {
391
+ const { added, removed } = diff
392
+
393
+ removed.forEach((nv) => removeNameValue(element, nv))
394
+ added.forEach((nv) => {
395
+ if ((nv.name[0] === "o" && nv.name[1] === "n") || nv.name[0] === "@") return
396
+
397
+ return addNameValue(element, nv)
398
+ })
399
+ }
400
+ }
401
+ ),
402
+ {}
403
+ )
404
+ }
405
+ }
406
+
407
+ function removeNameValue(element: HTMLElement | SVGElement, { name, type }: NameValue) {
408
+ switch (type) {
409
+ case "attr":
410
+ case "bool":
411
+ return element.removeAttribute(name)
412
+ case "prop":
413
+ return delete (element as any)[name]
414
+ }
415
+ }
416
+
417
+ function addNameValue(element: HTMLElement | SVGElement, { name, type, value }: NameValue) {
418
+ switch (type) {
419
+ case "attr":
420
+ return value == null ? element.removeAttribute(name) : element.setAttribute(name, value)
421
+ case "bool":
422
+ return value == null ? element.removeAttribute(name) : element.toggleAttribute(name, value)
423
+ case "prop":
424
+ return value == null ? (delete (element as any)[name]) : (element as any)[name] = value
425
+ }
426
+ }
427
+
428
+ type AttrNameValue = {
429
+ readonly type: "attr"
430
+ readonly name: string
431
+ readonly value: string
432
+ }
433
+
434
+ type BoolAttrNameValue = {
435
+ readonly type: "bool"
436
+ readonly name: string
437
+ readonly value: boolean
438
+ }
439
+
440
+ type PropNameValue = {
441
+ readonly type: "prop"
442
+ readonly name: string
443
+ readonly value: unknown
444
+ }
445
+
446
+ type NameValue = AttrNameValue | BoolAttrNameValue | PropNameValue
447
+
448
+ function diffProperties(
449
+ a: Record<string, unknown> | null | undefined,
450
+ b: Record<string, unknown> | null | undefined
451
+ ): { added: Array<NameValue>; removed: ReadonlyArray<NameValue> } | null {
452
+ if (!a) {
453
+ if (b) {
454
+ return { added: Object.entries(b).flatMap(([k, v]) => fromKeyValue(k, v)), removed: [] }
455
+ } else return null
456
+ } else if (!b) {
457
+ return { added: [], removed: Object.entries(a).flatMap(([k, v]) => fromKeyValue(k, v)) }
458
+ } else {
459
+ const { added, removed, unchanged } = diffStrings(Object.keys(a), Object.keys(b))
460
+
461
+ return {
462
+ added: added.concat(unchanged).flatMap((k) => fromKeyValue(k, b[k])),
463
+ removed: removed.flatMap((k) => fromKeyValue(k, a[k]))
464
+ }
465
+ }
466
+ }
467
+
468
+ function fromKeyValue(name: string, value: unknown): Array<NameValue> {
469
+ if (name[0] === ".") {
470
+ return value == null ? [] : [{
471
+ type: "prop",
472
+ name: name.slice(1),
473
+ value
474
+ }]
475
+ } else if (typeof value === "boolean") {
476
+ return [{
477
+ type: "bool",
478
+ name,
479
+ value
480
+ }]
481
+ } else {
482
+ if (name[0] === "o" || name[1] === "n") return []
483
+
484
+ return value == null ? [] : [{
485
+ type: "attr",
486
+ name,
487
+ value: String(value)
488
+ }]
489
+ }
406
490
  }
407
491
 
408
492
  const sparse = <T extends SparsePart["_tag"]>(tag: T) =>
@@ -418,7 +502,8 @@ const sparse = <T extends SparsePart["_tag"]>(tag: T) =>
418
502
  }
419
503
  ) => Effect.Effect<Scope, never, void>,
420
504
  public value: SparseAttributeValues<Extract<SparsePart, { readonly _tag: T }>["parts"]>,
421
- readonly eq: Equivalence<SparseAttributeValues<Extract<SparsePart, { readonly _tag: T }>["parts"]>> = equals
505
+ readonly eq: Equivalence.Equivalence<SparseAttributeValues<Extract<SparsePart, { readonly _tag: T }>["parts"]>> =
506
+ equals
422
507
  ) {}
423
508
 
424
509
  update = (value: this["value"]) => {