@typed/template 0.9.6 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (300) hide show
  1. package/RenderQueue/package.json +6 -0
  2. package/dist/cjs/Directive.js +1 -1
  3. package/dist/cjs/Directive.js.map +1 -1
  4. package/dist/cjs/ElementRef.js +1 -1
  5. package/dist/cjs/ElementRef.js.map +1 -1
  6. package/dist/cjs/ElementSource.js +1 -1
  7. package/dist/cjs/ElementSource.js.map +1 -1
  8. package/dist/cjs/EventHandler.js +11 -4
  9. package/dist/cjs/EventHandler.js.map +1 -1
  10. package/dist/cjs/Html.js +84 -44
  11. package/dist/cjs/Html.js.map +1 -1
  12. package/dist/cjs/HtmlChunk.js +67 -21
  13. package/dist/cjs/HtmlChunk.js.map +1 -1
  14. package/dist/cjs/Hydrate.js +6 -6
  15. package/dist/cjs/Hydrate.js.map +1 -1
  16. package/dist/cjs/Many.js +4 -4
  17. package/dist/cjs/Many.js.map +1 -1
  18. package/dist/cjs/Meta.js +10 -3
  19. package/dist/cjs/Meta.js.map +1 -1
  20. package/dist/cjs/Parser.js +1 -1
  21. package/dist/cjs/Placeholder.js +5 -9
  22. package/dist/cjs/Placeholder.js.map +1 -1
  23. package/dist/cjs/Platform.js +7 -5
  24. package/dist/cjs/Platform.js.map +1 -1
  25. package/dist/cjs/Render.js +8 -7
  26. package/dist/cjs/Render.js.map +1 -1
  27. package/dist/cjs/RenderContext.js +8 -92
  28. package/dist/cjs/RenderContext.js.map +1 -1
  29. package/dist/cjs/RenderEvent.js +9 -1
  30. package/dist/cjs/RenderEvent.js.map +1 -1
  31. package/dist/cjs/RenderQueue.js +341 -0
  32. package/dist/cjs/RenderQueue.js.map +1 -0
  33. package/dist/cjs/RenderTemplate.js +1 -1
  34. package/dist/cjs/RenderTemplate.js.map +1 -1
  35. package/dist/cjs/Template.js +12 -0
  36. package/dist/cjs/Template.js.map +1 -1
  37. package/dist/cjs/Test.js +64 -33
  38. package/dist/cjs/Test.js.map +1 -1
  39. package/dist/cjs/Vitest.js +12 -20
  40. package/dist/cjs/Vitest.js.map +1 -1
  41. package/dist/cjs/index.js +6 -3
  42. package/dist/cjs/index.js.map +1 -1
  43. package/dist/cjs/internal/EventSource.js +16 -9
  44. package/dist/cjs/internal/EventSource.js.map +1 -1
  45. package/dist/cjs/internal/HydrateContext.js.map +1 -1
  46. package/dist/cjs/internal/browser.js +11 -10
  47. package/dist/cjs/internal/browser.js.map +1 -1
  48. package/dist/cjs/internal/character-entities.js +2141 -0
  49. package/dist/cjs/internal/character-entities.js.map +1 -0
  50. package/dist/cjs/internal/errors.js +19 -2
  51. package/dist/cjs/internal/errors.js.map +1 -1
  52. package/dist/cjs/internal/indexRefCounter.js +36 -63
  53. package/dist/cjs/internal/indexRefCounter.js.map +1 -1
  54. package/dist/cjs/internal/parser.js +18 -17
  55. package/dist/cjs/internal/parser.js.map +1 -1
  56. package/dist/cjs/internal/parser2.js +382 -0
  57. package/dist/cjs/internal/parser2.js.map +1 -0
  58. package/dist/cjs/internal/server-parts.js +124 -0
  59. package/dist/cjs/internal/server-parts.js.map +1 -0
  60. package/dist/cjs/internal/server.js +15 -185
  61. package/dist/cjs/internal/server.js.map +1 -1
  62. package/dist/cjs/internal/utils.js +73 -9
  63. package/dist/cjs/internal/utils.js.map +1 -1
  64. package/dist/cjs/internal/v2/SyncPart.js +6 -0
  65. package/dist/cjs/internal/v2/SyncPart.js.map +1 -0
  66. package/dist/cjs/internal/v2/helpers.js +15 -0
  67. package/dist/cjs/internal/v2/helpers.js.map +1 -0
  68. package/dist/cjs/internal/v2/hydrate.js +202 -0
  69. package/dist/cjs/internal/v2/hydrate.js.map +1 -0
  70. package/dist/cjs/internal/v2/hydration-template.js +269 -0
  71. package/dist/cjs/internal/v2/hydration-template.js.map +1 -0
  72. package/dist/cjs/internal/v2/parts.js +169 -0
  73. package/dist/cjs/internal/v2/parts.js.map +1 -0
  74. package/dist/cjs/internal/v2/render-entry.js +110 -0
  75. package/dist/cjs/internal/v2/render-entry.js.map +1 -0
  76. package/dist/cjs/internal/v2/render-sync-parts.js +318 -0
  77. package/dist/cjs/internal/v2/render-sync-parts.js.map +1 -0
  78. package/dist/cjs/internal/v2/render.js +417 -0
  79. package/dist/cjs/internal/v2/render.js.map +1 -0
  80. package/dist/cjs/internal/v2/sync-parts.js +115 -0
  81. package/dist/cjs/internal/v2/sync-parts.js.map +1 -0
  82. package/dist/dts/ElementRef.d.ts +1 -1
  83. package/dist/dts/ElementRef.d.ts.map +1 -1
  84. package/dist/dts/ElementSource.d.ts +1 -1
  85. package/dist/dts/ElementSource.d.ts.map +1 -1
  86. package/dist/dts/EventHandler.d.ts +12 -8
  87. package/dist/dts/EventHandler.d.ts.map +1 -1
  88. package/dist/dts/Html.d.ts +6 -5
  89. package/dist/dts/Html.d.ts.map +1 -1
  90. package/dist/dts/HtmlChunk.d.ts.map +1 -1
  91. package/dist/dts/Hydrate.d.ts +1 -3
  92. package/dist/dts/Hydrate.d.ts.map +1 -1
  93. package/dist/dts/Many.d.ts +9 -11
  94. package/dist/dts/Many.d.ts.map +1 -1
  95. package/dist/dts/Meta.d.ts +5 -1
  96. package/dist/dts/Meta.d.ts.map +1 -1
  97. package/dist/dts/Parser.d.ts +1 -1
  98. package/dist/dts/Parser.d.ts.map +1 -1
  99. package/dist/dts/Part.d.ts +20 -56
  100. package/dist/dts/Part.d.ts.map +1 -1
  101. package/dist/dts/Placeholder.d.ts +6 -10
  102. package/dist/dts/Placeholder.d.ts.map +1 -1
  103. package/dist/dts/Platform.d.ts +2 -4
  104. package/dist/dts/Platform.d.ts.map +1 -1
  105. package/dist/dts/Render.d.ts +4 -8
  106. package/dist/dts/Render.d.ts.map +1 -1
  107. package/dist/dts/RenderContext.d.ts +3 -22
  108. package/dist/dts/RenderContext.d.ts.map +1 -1
  109. package/dist/dts/RenderEvent.d.ts +6 -1
  110. package/dist/dts/RenderEvent.d.ts.map +1 -1
  111. package/dist/dts/RenderQueue.d.ts +103 -0
  112. package/dist/dts/RenderQueue.d.ts.map +1 -0
  113. package/dist/dts/RenderTemplate.d.ts +3 -2
  114. package/dist/dts/RenderTemplate.d.ts.map +1 -1
  115. package/dist/dts/Renderable.d.ts +1 -1
  116. package/dist/dts/Template.d.ts +14 -1
  117. package/dist/dts/Template.d.ts.map +1 -1
  118. package/dist/dts/Test.d.ts +14 -1
  119. package/dist/dts/Test.d.ts.map +1 -1
  120. package/dist/dts/Vitest.d.ts +11 -8
  121. package/dist/dts/Vitest.d.ts.map +1 -1
  122. package/dist/dts/index.d.ts +4 -0
  123. package/dist/dts/index.d.ts.map +1 -1
  124. package/dist/dts/internal/EventSource.d.ts +2 -1
  125. package/dist/dts/internal/EventSource.d.ts.map +1 -1
  126. package/dist/dts/internal/browser.d.ts +3 -3
  127. package/dist/dts/internal/browser.d.ts.map +1 -1
  128. package/dist/dts/internal/character-entities.d.ts +2133 -0
  129. package/dist/dts/internal/character-entities.d.ts.map +1 -0
  130. package/dist/dts/internal/errors.d.ts +9 -1
  131. package/dist/dts/internal/errors.d.ts.map +1 -1
  132. package/dist/dts/internal/indexRefCounter.d.ts +0 -4
  133. package/dist/dts/internal/indexRefCounter.d.ts.map +1 -1
  134. package/dist/dts/internal/parser.d.ts +13 -0
  135. package/dist/dts/internal/parser.d.ts.map +1 -1
  136. package/dist/dts/internal/parser2.d.ts +12 -0
  137. package/dist/dts/internal/parser2.d.ts.map +1 -0
  138. package/dist/dts/internal/server-parts.d.ts +223 -0
  139. package/dist/dts/internal/server-parts.d.ts.map +1 -0
  140. package/dist/dts/internal/server.d.ts +2 -28
  141. package/dist/dts/internal/server.d.ts.map +1 -1
  142. package/dist/dts/internal/utils.d.ts +4 -1
  143. package/dist/dts/internal/utils.d.ts.map +1 -1
  144. package/dist/dts/internal/v2/SyncPart.d.ts +87 -0
  145. package/dist/dts/internal/v2/SyncPart.d.ts.map +1 -0
  146. package/dist/dts/internal/v2/helpers.d.ts +3 -0
  147. package/dist/dts/internal/v2/helpers.d.ts.map +1 -0
  148. package/dist/dts/internal/v2/hydrate.d.ts +7 -0
  149. package/dist/dts/internal/v2/hydrate.d.ts.map +1 -0
  150. package/dist/dts/internal/v2/hydration-template.d.ts +54 -0
  151. package/dist/dts/internal/v2/hydration-template.d.ts.map +1 -0
  152. package/dist/dts/internal/v2/parts.d.ts +245 -0
  153. package/dist/dts/internal/v2/parts.d.ts.map +1 -0
  154. package/dist/dts/internal/v2/render-entry.d.ts +6 -0
  155. package/dist/dts/internal/v2/render-entry.d.ts.map +1 -0
  156. package/dist/dts/internal/v2/render-sync-parts.d.ts +22 -0
  157. package/dist/dts/internal/v2/render-sync-parts.d.ts.map +1 -0
  158. package/dist/dts/internal/v2/render.d.ts +62 -0
  159. package/dist/dts/internal/v2/render.d.ts.map +1 -0
  160. package/dist/dts/internal/v2/sync-parts.d.ts +129 -0
  161. package/dist/dts/internal/v2/sync-parts.d.ts.map +1 -0
  162. package/dist/esm/ElementRef.js.map +1 -1
  163. package/dist/esm/EventHandler.js +14 -4
  164. package/dist/esm/EventHandler.js.map +1 -1
  165. package/dist/esm/Html.js +91 -50
  166. package/dist/esm/Html.js.map +1 -1
  167. package/dist/esm/HtmlChunk.js +75 -24
  168. package/dist/esm/HtmlChunk.js.map +1 -1
  169. package/dist/esm/Hydrate.js +5 -5
  170. package/dist/esm/Hydrate.js.map +1 -1
  171. package/dist/esm/Many.js +3 -3
  172. package/dist/esm/Many.js.map +1 -1
  173. package/dist/esm/Meta.js +7 -1
  174. package/dist/esm/Meta.js.map +1 -1
  175. package/dist/esm/Parser.js +1 -1
  176. package/dist/esm/Parser.js.map +1 -1
  177. package/dist/esm/Placeholder.js +4 -8
  178. package/dist/esm/Placeholder.js.map +1 -1
  179. package/dist/esm/Platform.js +3 -1
  180. package/dist/esm/Platform.js.map +1 -1
  181. package/dist/esm/Render.js +6 -5
  182. package/dist/esm/Render.js.map +1 -1
  183. package/dist/esm/RenderContext.js +5 -85
  184. package/dist/esm/RenderContext.js.map +1 -1
  185. package/dist/esm/RenderEvent.js +8 -1
  186. package/dist/esm/RenderEvent.js.map +1 -1
  187. package/dist/esm/RenderQueue.js +336 -0
  188. package/dist/esm/RenderQueue.js.map +1 -0
  189. package/dist/esm/RenderTemplate.js.map +1 -1
  190. package/dist/esm/Template.js +12 -0
  191. package/dist/esm/Template.js.map +1 -1
  192. package/dist/esm/Test.js +71 -30
  193. package/dist/esm/Test.js.map +1 -1
  194. package/dist/esm/Vitest.js +11 -8
  195. package/dist/esm/Vitest.js.map +1 -1
  196. package/dist/esm/index.js +4 -0
  197. package/dist/esm/index.js.map +1 -1
  198. package/dist/esm/internal/EventSource.js +12 -7
  199. package/dist/esm/internal/EventSource.js.map +1 -1
  200. package/dist/esm/internal/HydrateContext.js.map +1 -1
  201. package/dist/esm/internal/browser.js +10 -9
  202. package/dist/esm/internal/browser.js.map +1 -1
  203. package/dist/esm/internal/character-entities.js +2134 -0
  204. package/dist/esm/internal/character-entities.js.map +1 -0
  205. package/dist/esm/internal/errors.js +22 -2
  206. package/dist/esm/internal/errors.js.map +1 -1
  207. package/dist/esm/internal/indexRefCounter.js +36 -61
  208. package/dist/esm/internal/indexRefCounter.js.map +1 -1
  209. package/dist/esm/internal/parser.js +18 -18
  210. package/dist/esm/internal/parser.js.map +1 -1
  211. package/dist/esm/internal/parser2.js +393 -0
  212. package/dist/esm/internal/parser2.js.map +1 -0
  213. package/dist/esm/internal/server-parts.js +109 -0
  214. package/dist/esm/internal/server-parts.js.map +1 -0
  215. package/dist/esm/internal/server.js +12 -161
  216. package/dist/esm/internal/server.js.map +1 -1
  217. package/dist/esm/internal/utils.js +71 -7
  218. package/dist/esm/internal/utils.js.map +1 -1
  219. package/dist/esm/internal/v2/SyncPart.js +5 -0
  220. package/dist/esm/internal/v2/SyncPart.js.map +1 -0
  221. package/dist/esm/internal/v2/helpers.js +12 -0
  222. package/dist/esm/internal/v2/helpers.js.map +1 -0
  223. package/dist/esm/internal/v2/hydrate.js +195 -0
  224. package/dist/esm/internal/v2/hydrate.js.map +1 -0
  225. package/dist/esm/internal/v2/hydration-template.js +265 -0
  226. package/dist/esm/internal/v2/hydration-template.js.map +1 -0
  227. package/dist/esm/internal/v2/parts.js +150 -0
  228. package/dist/esm/internal/v2/parts.js.map +1 -0
  229. package/dist/esm/internal/v2/render-entry.js +102 -0
  230. package/dist/esm/internal/v2/render-entry.js.map +1 -0
  231. package/dist/esm/internal/v2/render-sync-parts.js +265 -0
  232. package/dist/esm/internal/v2/render-sync-parts.js.map +1 -0
  233. package/dist/esm/internal/v2/render.js +353 -0
  234. package/dist/esm/internal/v2/render.js.map +1 -0
  235. package/dist/esm/internal/v2/sync-parts.js +102 -0
  236. package/dist/esm/internal/v2/sync-parts.js.map +1 -0
  237. package/package.json +20 -13
  238. package/src/ElementRef.ts +1 -1
  239. package/src/ElementSource.ts +1 -1
  240. package/src/EventHandler.ts +29 -11
  241. package/src/Html.ts +199 -90
  242. package/src/HtmlChunk.ts +77 -30
  243. package/src/Hydrate.ts +20 -14
  244. package/src/Many.ts +17 -14
  245. package/src/Meta.ts +8 -1
  246. package/src/Parser.ts +1 -1
  247. package/src/Part.ts +22 -66
  248. package/src/Placeholder.ts +17 -15
  249. package/src/Platform.ts +5 -5
  250. package/src/Render.ts +23 -26
  251. package/src/RenderContext.ts +14 -142
  252. package/src/RenderEvent.ts +10 -1
  253. package/src/RenderQueue.ts +445 -0
  254. package/src/RenderTemplate.ts +7 -2
  255. package/src/Renderable.ts +1 -1
  256. package/src/Template.ts +15 -1
  257. package/src/Test.ts +122 -38
  258. package/src/Vitest.ts +20 -10
  259. package/src/index.ts +4 -0
  260. package/src/internal/EventSource.ts +14 -8
  261. package/src/internal/HydrateContext.ts +3 -4
  262. package/src/internal/browser.ts +26 -21
  263. package/src/internal/character-entities.ts +2136 -0
  264. package/src/internal/errors.ts +30 -3
  265. package/src/internal/indexRefCounter.ts +38 -70
  266. package/src/internal/parser.ts +19 -19
  267. package/src/internal/parser2.ts +468 -0
  268. package/src/internal/server-parts.ts +161 -0
  269. package/src/internal/server.ts +16 -272
  270. package/src/internal/utils.ts +83 -7
  271. package/src/internal/v2/SyncPart.ts +112 -0
  272. package/src/internal/v2/helpers.ts +13 -0
  273. package/src/internal/v2/hydrate.ts +289 -0
  274. package/src/internal/v2/hydration-template.ts +308 -0
  275. package/src/internal/v2/parts.ts +254 -0
  276. package/src/internal/v2/render-entry.ts +131 -0
  277. package/src/internal/v2/render-sync-parts.ts +440 -0
  278. package/src/internal/v2/render.ts +588 -0
  279. package/src/internal/v2/sync-parts.ts +133 -0
  280. package/dist/cjs/internal/hydrate.js +0 -274
  281. package/dist/cjs/internal/hydrate.js.map +0 -1
  282. package/dist/cjs/internal/parts.js +0 -451
  283. package/dist/cjs/internal/parts.js.map +0 -1
  284. package/dist/cjs/internal/render.js +0 -704
  285. package/dist/cjs/internal/render.js.map +0 -1
  286. package/dist/dts/internal/hydrate.d.ts +0 -33
  287. package/dist/dts/internal/hydrate.d.ts.map +0 -1
  288. package/dist/dts/internal/parts.d.ts +0 -314
  289. package/dist/dts/internal/parts.d.ts.map +0 -1
  290. package/dist/dts/internal/render.d.ts +0 -16
  291. package/dist/dts/internal/render.d.ts.map +0 -1
  292. package/dist/esm/internal/hydrate.js +0 -239
  293. package/dist/esm/internal/hydrate.js.map +0 -1
  294. package/dist/esm/internal/parts.js +0 -373
  295. package/dist/esm/internal/parts.js.map +0 -1
  296. package/dist/esm/internal/render.js +0 -689
  297. package/dist/esm/internal/render.js.map +0 -1
  298. package/src/internal/hydrate.ts +0 -366
  299. package/src/internal/parts.ts +0 -609
  300. package/src/internal/render.ts +0 -971
@@ -0,0 +1,440 @@
1
+ import * as Fx from "@typed/fx"
2
+ import * as Effect from "effect/Effect"
3
+ import { identity } from "effect/Function"
4
+ import type { AttrPartNode, ClassNamePartNode, CommentPartNode, PartNode, TextNode } from "../../Template.js"
5
+ import { convertCharacterEntities } from "../character-entities.js"
6
+ import {
7
+ AttributePartImpl,
8
+ BooleanPartImpl,
9
+ ClassNamePartImpl,
10
+ CommentPartImpl,
11
+ DataPartImpl,
12
+ NodePartImpl,
13
+ PropertyPartImpl,
14
+ splitClassNames,
15
+ TextPartImpl
16
+ } from "./sync-parts.js"
17
+ import type { SyncPart } from "./SyncPart.js"
18
+
19
+ import { diffable, isComment } from "@typed/wire"
20
+ import udomdiff from "udomdiff"
21
+ import type { Directive } from "../../Directive.js"
22
+ import { isDirective } from "../../Directive.js"
23
+ import type { Renderable } from "../../Renderable.js"
24
+ import { isRenderEvent } from "../../RenderEvent.js"
25
+ import { isCommentWithValue } from "../utils.js"
26
+ import { syncPartToPart } from "./parts.js"
27
+
28
+ export function makeAttributePart(
29
+ index: number,
30
+ element: HTMLElement | SVGElement,
31
+ attr: Attr
32
+ ) {
33
+ const setValue = makeAttributeValueSetter(element, attr)
34
+ return new AttributePartImpl(attr.name, index, ({ value }) => setValue(value), attr.value)
35
+ }
36
+
37
+ export function makeAttributeValueSetter(element: HTMLElement | SVGElement, attr: Attr) {
38
+ let isSet = false
39
+ const setValue = (value: string | null | undefined) => {
40
+ if (isNullOrUndefined(value)) {
41
+ element.removeAttribute(attr.name)
42
+ isSet = false
43
+ } else {
44
+ attr.value = value
45
+ if (isSet === false) {
46
+ element.setAttributeNode(attr)
47
+ isSet = true
48
+ }
49
+ }
50
+ }
51
+
52
+ return setValue
53
+ }
54
+
55
+ export function makeBooleanAttributePart(
56
+ name: string,
57
+ index: number,
58
+ element: HTMLElement | SVGElement
59
+ ) {
60
+ return new BooleanPartImpl(
61
+ name,
62
+ index,
63
+ ({ value }) => element.toggleAttribute(name, value === true),
64
+ element.hasAttribute(name)
65
+ )
66
+ }
67
+
68
+ export function makeClassNamePart(
69
+ index: number,
70
+ element: HTMLElement | SVGElement,
71
+ initial: ReadonlyArray<string> = Array.from(element.classList)
72
+ ) {
73
+ return new ClassNamePartImpl(
74
+ index,
75
+ ({ previous, value }) => {
76
+ const { added, removed } = diffStrings(previous, value)
77
+ element.classList.add(...added)
78
+ element.classList.remove(...removed)
79
+ },
80
+ initial
81
+ )
82
+ }
83
+
84
+ export function makeDataPart(index: number, element: HTMLElement | SVGElement) {
85
+ return new DataPartImpl(
86
+ index,
87
+ ({ previous, value }) => {
88
+ const diff = diffDataSet(previous, value)
89
+ if (diff) {
90
+ const { added, removed } = diff
91
+ removed.forEach((k) => delete element.dataset[k])
92
+ added.forEach(([k, v]) => element.dataset[k] = v)
93
+ }
94
+ },
95
+ element.dataset
96
+ )
97
+ }
98
+
99
+ export function makePropertyPart(name: string, index: number, element: HTMLElement | SVGElement) {
100
+ return new PropertyPartImpl(
101
+ name,
102
+ index,
103
+ ({ value }) => {
104
+ ;(element as any)[name] = value
105
+ },
106
+ element[name as keyof typeof element]
107
+ )
108
+ }
109
+
110
+ export function makeTextPart(index: number, text: Text) {
111
+ return new TextPartImpl(
112
+ index,
113
+ ({ value }) => {
114
+ if (value) {
115
+ text.nodeValue = convertCharacterEntities(value)
116
+ } else {
117
+ text.nodeValue = null
118
+ }
119
+ },
120
+ text.nodeValue
121
+ )
122
+ }
123
+
124
+ function isNullOrUndefined<T>(value: T | null | undefined): value is null | undefined {
125
+ return value === null || value === undefined
126
+ }
127
+
128
+ function diffStrings(
129
+ previous: ReadonlyArray<string> | null | undefined,
130
+ current: ReadonlyArray<string> | null | undefined
131
+ ): { added: ReadonlyArray<string>; removed: ReadonlyArray<string>; unchanged: ReadonlyArray<string> } {
132
+ if (previous == null || previous.length === 0) {
133
+ return {
134
+ added: current || [],
135
+ removed: [],
136
+ unchanged: []
137
+ }
138
+ } else if (current == null || current.length === 0) {
139
+ return {
140
+ added: [],
141
+ removed: previous,
142
+ unchanged: []
143
+ }
144
+ } else {
145
+ const added = current.filter((c) => !previous.includes(c))
146
+ const removed: Array<string> = []
147
+ const unchanged: Array<string> = []
148
+
149
+ for (let i = 0; i < previous.length; ++i) {
150
+ if (current.includes(previous[i])) {
151
+ unchanged.push(previous[i])
152
+ } else {
153
+ removed.push(previous[i])
154
+ }
155
+ }
156
+
157
+ return {
158
+ added,
159
+ removed,
160
+ unchanged
161
+ }
162
+ }
163
+ }
164
+
165
+ function diffDataSet(
166
+ a: Record<string, string | undefined> | null | undefined,
167
+ b: Record<string, string | undefined> | null | undefined
168
+ ):
169
+ | { added: Array<readonly [string, string | undefined]>; removed: ReadonlyArray<string> }
170
+ | null
171
+ {
172
+ if (!a) return b ? { added: Object.entries(b), removed: [] } : null
173
+ if (!b) return { added: [], removed: Object.keys(a) }
174
+
175
+ const { added, removed, unchanged } = diffStrings(Object.keys(a), Object.keys(b))
176
+
177
+ return {
178
+ added: added.concat(unchanged).map((k) => [k, b[k]] as const),
179
+ removed
180
+ }
181
+ }
182
+
183
+ export function makeSparsePartHandler<A extends PartNode, B extends SyncPart, C, D, X, Z, ZE, ZR>(
184
+ parts: ReadonlyArray<A | TextNode>,
185
+ renderables: ReadonlyArray<Renderable<any, any>>,
186
+ makePart: (index: number, setValue: (value: C) => void) => B,
187
+ handleText: (text: string) => D,
188
+ join: (values: ReadonlyArray<C | D>) => X,
189
+ setValue: (value: X) => Effect.Effect<Z, ZE, ZR>
190
+ ): Fx.Fx<Z, any, any> {
191
+ return Fx.mapEffect(
192
+ Fx.withEmitter<X, any, any, any>((sink) =>
193
+ Effect.zipRight(
194
+ Effect.gen(function*() {
195
+ const values = new Map<number, C | D>()
196
+ const expected = parts.length
197
+
198
+ const setValueIfReady = () => {
199
+ if (values.size === expected) {
200
+ return sink.succeed(join(Array.from({ length: expected }, (_, i) => values.get(i)!)))
201
+ }
202
+ }
203
+
204
+ for (let i = 0; i < parts.length; ++i) {
205
+ const index = i
206
+ const part = parts[i]
207
+ if (part._tag === "text") {
208
+ values.set(i, handleText(part.value))
209
+ } else {
210
+ const child = makePart(part.index, (value) => {
211
+ values.set(index, value)
212
+ setValueIfReady()
213
+ })
214
+ const value = renderables[part.index]
215
+
216
+ yield* Effect.forkScoped(matchRenderable(value, {
217
+ Fx: (fx) =>
218
+ fx.run(Fx.Sink.make((cause) =>
219
+ Effect.promise(() => sink.failCause(cause)), (value) =>
220
+ Effect.sync(() => child.update(value as never)))),
221
+ Effect: (effect) =>
222
+ Effect.tap(effect, (value) => child.update(value as never)),
223
+ Directive: (directive) =>
224
+ directive(syncPartToPart(child, ({ value }) => Effect.sync(() => child.update(value as never)))),
225
+ Otherwise: (value) => Effect.sync(() => child.update(value as never))
226
+ }))
227
+ }
228
+ }
229
+
230
+ setValueIfReady()
231
+ }),
232
+ Effect.never
233
+ )
234
+ ),
235
+ setValue
236
+ )
237
+ }
238
+
239
+ export function handleSparseAttribute<R>(
240
+ element: HTMLElement | SVGElement,
241
+ attr: Attr,
242
+ parts: ReadonlyArray<AttrPartNode | TextNode>,
243
+ values: ReadonlyArray<Renderable<any, any>>,
244
+ schedule: (f: () => void) => Effect.Effect<void, never, R>
245
+ ): Effect.Effect<void, any, any> {
246
+ const set = makeAttributeValueSetter(element, attr)
247
+ return Fx.drain(makeSparsePartHandler(
248
+ parts,
249
+ values,
250
+ (index, setValue: (value: string | null | undefined) => void) =>
251
+ new AttributePartImpl(attr.name, index, ({ value }) => setValue(value), attr.value),
252
+ (text) => text,
253
+ (values): string => values.flatMap((v) => isNullOrUndefined(v) ? [] : [v]).join(""),
254
+ (value) => schedule(() => set(value))
255
+ ))
256
+ }
257
+
258
+ export function handleSparseClassName<R>(
259
+ element: HTMLElement | SVGElement,
260
+ parts: ReadonlyArray<ClassNamePartNode | TextNode>,
261
+ values: ReadonlyArray<Renderable<any, any>>,
262
+ schedule: (f: () => void) => Effect.Effect<void, never, R>
263
+ ): Effect.Effect<void, any, any> {
264
+ let previous = Array.from(element.classList)
265
+
266
+ return Fx.drain(makeSparsePartHandler(
267
+ parts,
268
+ values,
269
+ (index, setValue: (value: ReadonlyArray<string>) => void) =>
270
+ new ClassNamePartImpl(index, ({ value }) => setValue(value), previous),
271
+ splitClassNames,
272
+ (values) => values.flat(1),
273
+ (values) =>
274
+ schedule(() => {
275
+ const { added, removed } = diffStrings(previous, values)
276
+ element.classList.add(...added)
277
+ element.classList.remove(...removed)
278
+ previous = values
279
+ })
280
+ ))
281
+ }
282
+
283
+ export function handleSparseComment<R>(
284
+ comment: Comment,
285
+ parts: ReadonlyArray<CommentPartNode | TextNode>,
286
+ values: ReadonlyArray<Renderable<any, any>>,
287
+ schedule: (f: () => void) => Effect.Effect<void, never, R>
288
+ ): Effect.Effect<void, any, any> {
289
+ return Fx.drain(makeSparsePartHandler(
290
+ parts,
291
+ values,
292
+ (index, setValue: (value: string | null | undefined) => void) =>
293
+ new CommentPartImpl(index, ({ value }) => setValue(value), comment.textContent),
294
+ identity,
295
+ (values): string => values.flatMap((v) => isNullOrUndefined(v) ? [] : [v]).join(""),
296
+ (value) => schedule(() => (comment.textContent = value))
297
+ ))
298
+ }
299
+
300
+ export function makeNodePart(
301
+ index: number,
302
+ comment: Comment,
303
+ document: Document,
304
+ text: Text | null,
305
+ nodes: Array<Node>
306
+ ) {
307
+ return new NodePartImpl(
308
+ index,
309
+ ({ value }) => {
310
+ matchNodeValue(
311
+ value,
312
+ (content) => {
313
+ if (text === null) {
314
+ text = document.createTextNode("")
315
+ }
316
+ text.textContent = convertCharacterEntities(content)
317
+
318
+ nodes = diffChildren(comment, nodes, [text], document)
319
+ },
320
+ (updatedNodes) => {
321
+ nodes = diffChildren(comment, nodes, updatedNodes, document)
322
+ }
323
+ )
324
+ },
325
+ nodes
326
+ )
327
+ }
328
+
329
+ export function getPreviousTextSibling(node: Node | null) {
330
+ if (!node) return null
331
+
332
+ if (node && node.nodeType === node.TEXT_NODE) {
333
+ // During hydration there should be a comment to separate these values
334
+ if (
335
+ node.previousSibling &&
336
+ isCommentWithValue(node.previousSibling, "text")
337
+ ) {
338
+ return node as Text
339
+ }
340
+ }
341
+
342
+ return null
343
+ }
344
+
345
+ export function findPreviousNodes(comment: Comment, index: number, hash: string) {
346
+ const previousComments = new Set([`hole${index - 1}`, `typed-${hash}`])
347
+
348
+ const nodes: Array<Node> = []
349
+
350
+ let node = comment.previousSibling
351
+ while (node) {
352
+ if (isComment(node) && previousComments.has(node.data)) {
353
+ break
354
+ }
355
+
356
+ nodes.unshift(node)
357
+ node = node.previousSibling
358
+ }
359
+
360
+ return nodes
361
+ }
362
+
363
+ function diffChildren(
364
+ comment: Comment,
365
+ currentNodes: Array<Node>,
366
+ nextNodes: Array<Node>,
367
+ document: Document
368
+ ) {
369
+ return udomdiff(
370
+ comment.parentNode!,
371
+ currentNodes,
372
+ nextNodes,
373
+ diffable(document),
374
+ comment
375
+ )
376
+ }
377
+
378
+ function matchNodeValue<A, B>(value: unknown, onText: (text: string) => A, onNodes: (nodes: Array<Node>) => B): A | B {
379
+ switch (typeof value) {
380
+ // primitives are handled as text content
381
+ case "string":
382
+ case "symbol":
383
+ case "number":
384
+ case "bigint":
385
+ case "boolean":
386
+ return onText(String(value))
387
+ case "undefined":
388
+ case "object": {
389
+ if (!value) {
390
+ return onNodes([])
391
+ } else if (Array.isArray(value)) {
392
+ // arrays can be used to cleanup, if empty
393
+ if (value.length === 0) return onNodes([])
394
+ // or diffed, if these contains nodes or "wires"
395
+ else if (value.some((x) => typeof x === "object")) {
396
+ return onNodes(value.flatMap(renderEventToArray))
397
+ } // in all other cases the content is stringified as is
398
+ else return onText(String(value))
399
+ } else {
400
+ return onNodes(renderEventToArray(value))
401
+ }
402
+ }
403
+ case "function":
404
+ return onNodes([])
405
+ }
406
+ }
407
+
408
+ function renderEventToArray(x: unknown): Array<Node> {
409
+ if (x === null || x === undefined) return []
410
+ if (isRenderEvent(x)) {
411
+ const value = x.valueOf()
412
+ return Array.isArray(value) ? value : [value]
413
+ }
414
+
415
+ return [x as Node]
416
+ }
417
+
418
+ export function makeCommentPart(index: number, comment: Comment) {
419
+ return new CommentPartImpl(index, ({ value }) => (comment.nodeValue = value ?? null), comment.textContent)
420
+ }
421
+
422
+ function matchRenderable<A, B, C, D>(
423
+ renderable: Renderable<any, any>,
424
+ matches: {
425
+ Fx: (fx: Fx.Fx<any, any, any>) => A
426
+ Effect: (effect: Effect.Effect<any, any, any>) => B
427
+ Directive: (directive: Directive<any, any>) => C
428
+ Otherwise: (_: any) => D
429
+ }
430
+ ): A | B | C | D {
431
+ if (Fx.isFx(renderable)) {
432
+ return matches.Fx(renderable)
433
+ } else if (Effect.isEffect(renderable)) {
434
+ return matches.Effect(renderable)
435
+ } else if (isDirective<any, any>(renderable)) {
436
+ return matches.Directive(renderable)
437
+ } else {
438
+ return matches.Otherwise(renderable)
439
+ }
440
+ }