effect-start 0.18.0 → 0.20.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 (203) hide show
  1. package/README.md +3 -3
  2. package/dist/Development.d.ts +8 -3
  3. package/dist/Development.js +14 -7
  4. package/dist/Effectify.d.ts +212 -0
  5. package/dist/Effectify.js +19 -0
  6. package/dist/FilePathPattern.d.ts +29 -0
  7. package/dist/FilePathPattern.js +86 -0
  8. package/dist/FileRouter.d.ts +39 -41
  9. package/dist/FileRouter.js +104 -158
  10. package/dist/FileRouterCodegen.d.ts +7 -8
  11. package/dist/FileRouterCodegen.js +97 -66
  12. package/dist/PlatformError.d.ts +46 -0
  13. package/dist/PlatformError.js +43 -0
  14. package/dist/PlatformRuntime.d.ts +27 -0
  15. package/dist/PlatformRuntime.js +51 -0
  16. package/dist/Route.d.ts +6 -2
  17. package/dist/Route.js +22 -0
  18. package/dist/RouteBody.d.ts +1 -1
  19. package/dist/RouteHttp.d.ts +1 -1
  20. package/dist/RouteHttp.js +12 -19
  21. package/dist/RouteMount.d.ts +2 -1
  22. package/dist/Start.d.ts +33 -6
  23. package/dist/Start.js +31 -13
  24. package/dist/Unique.d.ts +50 -0
  25. package/dist/Unique.js +187 -0
  26. package/dist/bun/BunHttpServer.js +5 -6
  27. package/dist/bun/BunPlatformHttpServer.d.ts +10 -0
  28. package/dist/bun/BunPlatformHttpServer.js +53 -0
  29. package/dist/bun/BunRoute.d.ts +4 -6
  30. package/dist/bun/BunRoute.js +10 -18
  31. package/dist/bun/BunRuntime.d.ts +2 -1
  32. package/dist/bun/BunRuntime.js +10 -5
  33. package/dist/bun/BunServer.d.ts +33 -0
  34. package/dist/bun/BunServer.js +133 -0
  35. package/dist/bun/BunServerRequest.d.ts +60 -0
  36. package/dist/bun/BunServerRequest.js +252 -0
  37. package/dist/bun/index.d.ts +1 -1
  38. package/dist/bun/index.js +1 -1
  39. package/dist/datastar/actions/fetch.d.ts +30 -0
  40. package/dist/datastar/actions/fetch.js +411 -0
  41. package/dist/datastar/actions/peek.d.ts +1 -0
  42. package/dist/datastar/actions/peek.js +14 -0
  43. package/dist/datastar/actions/setAll.d.ts +1 -0
  44. package/dist/datastar/actions/setAll.js +13 -0
  45. package/dist/datastar/actions/toggleAll.d.ts +1 -0
  46. package/dist/datastar/actions/toggleAll.js +13 -0
  47. package/dist/datastar/attributes/attr.d.ts +1 -0
  48. package/dist/datastar/attributes/attr.js +49 -0
  49. package/dist/datastar/attributes/bind.d.ts +1 -0
  50. package/dist/datastar/attributes/bind.js +183 -0
  51. package/dist/datastar/attributes/class.d.ts +1 -0
  52. package/dist/datastar/attributes/class.js +50 -0
  53. package/dist/datastar/attributes/computed.d.ts +1 -0
  54. package/dist/datastar/attributes/computed.js +27 -0
  55. package/dist/datastar/attributes/effect.d.ts +1 -0
  56. package/dist/datastar/attributes/effect.js +10 -0
  57. package/dist/datastar/attributes/indicator.d.ts +1 -0
  58. package/dist/datastar/attributes/indicator.js +32 -0
  59. package/dist/datastar/attributes/init.d.ts +1 -0
  60. package/dist/datastar/attributes/init.js +27 -0
  61. package/dist/datastar/attributes/jsonSignals.d.ts +1 -0
  62. package/dist/datastar/attributes/jsonSignals.js +31 -0
  63. package/dist/datastar/attributes/on.d.ts +1 -0
  64. package/dist/datastar/attributes/on.js +59 -0
  65. package/dist/datastar/attributes/onIntersect.d.ts +1 -0
  66. package/dist/datastar/attributes/onIntersect.js +54 -0
  67. package/dist/datastar/attributes/onInterval.d.ts +1 -0
  68. package/dist/datastar/attributes/onInterval.js +31 -0
  69. package/dist/datastar/attributes/onSignalPatch.d.ts +1 -0
  70. package/dist/datastar/attributes/onSignalPatch.js +44 -0
  71. package/dist/datastar/attributes/ref.d.ts +1 -0
  72. package/dist/datastar/attributes/ref.js +11 -0
  73. package/dist/datastar/attributes/show.d.ts +1 -0
  74. package/dist/datastar/attributes/show.js +32 -0
  75. package/dist/datastar/attributes/signals.d.ts +1 -0
  76. package/dist/datastar/attributes/signals.js +18 -0
  77. package/dist/datastar/attributes/style.d.ts +1 -0
  78. package/dist/datastar/attributes/style.js +56 -0
  79. package/dist/datastar/attributes/text.d.ts +1 -0
  80. package/dist/datastar/attributes/text.js +27 -0
  81. package/dist/datastar/engine.d.ts +156 -0
  82. package/dist/datastar/engine.js +971 -0
  83. package/dist/datastar/index.d.ts +24 -0
  84. package/dist/datastar/index.js +24 -0
  85. package/dist/datastar/load.d.ts +24 -0
  86. package/dist/datastar/load.js +24 -0
  87. package/dist/datastar/utils.d.ts +51 -0
  88. package/dist/datastar/utils.js +205 -0
  89. package/dist/datastar/watchers/patchElements.d.ts +1 -0
  90. package/dist/datastar/watchers/patchElements.js +420 -0
  91. package/dist/datastar/watchers/patchSignals.d.ts +1 -0
  92. package/dist/datastar/watchers/patchSignals.js +15 -0
  93. package/dist/index.d.ts +1 -0
  94. package/dist/index.js +1 -0
  95. package/dist/node/Effectify.d.ts +209 -0
  96. package/dist/node/Effectify.js +19 -0
  97. package/dist/node/FileSystem.d.ts +3 -5
  98. package/dist/node/FileSystem.js +42 -62
  99. package/dist/node/NodeFileSystem.d.ts +7 -0
  100. package/dist/node/NodeFileSystem.js +420 -0
  101. package/dist/node/NodeUtils.d.ts +2 -0
  102. package/dist/node/NodeUtils.js +20 -0
  103. package/dist/node/PlatformError.d.ts +46 -0
  104. package/dist/node/PlatformError.js +43 -0
  105. package/dist/testing/TestLogger.js +1 -1
  106. package/dist/x/tailwind/plugin.js +1 -1
  107. package/package.json +18 -7
  108. package/src/Development.ts +36 -40
  109. package/src/Effectify.ts +269 -0
  110. package/src/FilePathPattern.ts +115 -0
  111. package/src/FileRouter.ts +178 -255
  112. package/src/FileRouterCodegen.ts +135 -92
  113. package/src/PlatformError.ts +117 -0
  114. package/src/PlatformRuntime.ts +108 -0
  115. package/src/Route.ts +31 -2
  116. package/src/RouteBody.ts +1 -1
  117. package/src/RouteHttp.ts +15 -29
  118. package/src/RouteMount.ts +1 -1
  119. package/src/Start.ts +61 -27
  120. package/src/Unique.ts +232 -0
  121. package/src/bun/BunPlatformHttpServer.ts +88 -0
  122. package/src/bun/BunRoute.ts +14 -24
  123. package/src/bun/BunRuntime.ts +21 -5
  124. package/src/bun/BunServer.ts +228 -0
  125. package/src/bun/index.ts +1 -1
  126. package/src/datastar/README.md +18 -0
  127. package/src/datastar/actions/fetch.ts +609 -0
  128. package/src/datastar/actions/peek.ts +17 -0
  129. package/src/datastar/actions/setAll.ts +20 -0
  130. package/src/datastar/actions/toggleAll.ts +20 -0
  131. package/src/datastar/attributes/attr.ts +50 -0
  132. package/src/datastar/attributes/bind.ts +220 -0
  133. package/src/datastar/attributes/class.ts +57 -0
  134. package/src/datastar/attributes/computed.ts +33 -0
  135. package/src/datastar/attributes/effect.ts +11 -0
  136. package/src/datastar/attributes/indicator.ts +39 -0
  137. package/src/datastar/attributes/init.ts +35 -0
  138. package/src/datastar/attributes/jsonSignals.ts +38 -0
  139. package/src/datastar/attributes/on.ts +71 -0
  140. package/src/datastar/attributes/onIntersect.ts +65 -0
  141. package/src/datastar/attributes/onInterval.ts +39 -0
  142. package/src/datastar/attributes/onSignalPatch.ts +63 -0
  143. package/src/datastar/attributes/ref.ts +12 -0
  144. package/src/datastar/attributes/show.ts +33 -0
  145. package/src/datastar/attributes/signals.ts +22 -0
  146. package/src/datastar/attributes/style.ts +63 -0
  147. package/src/datastar/attributes/text.ts +30 -0
  148. package/src/datastar/engine.ts +1341 -0
  149. package/src/datastar/index.ts +25 -0
  150. package/src/datastar/utils.ts +286 -0
  151. package/src/datastar/watchers/patchElements.ts +554 -0
  152. package/src/datastar/watchers/patchSignals.ts +15 -0
  153. package/src/index.ts +1 -0
  154. package/src/node/{FileSystem.ts → NodeFileSystem.ts} +59 -97
  155. package/src/node/{Utils.ts → NodeUtils.ts} +2 -0
  156. package/src/testing/TestLogger.ts +1 -1
  157. package/src/x/tailwind/plugin.ts +1 -1
  158. package/dist/Random.d.ts +0 -5
  159. package/dist/Random.js +0 -49
  160. package/src/Commander.test.ts +0 -1639
  161. package/src/ContentNegotiation.test.ts +0 -603
  162. package/src/Development.test.ts +0 -119
  163. package/src/Entity.test.ts +0 -592
  164. package/src/FileRouterCodegen.todo.ts +0 -1133
  165. package/src/FileRouterPattern.test.ts +0 -147
  166. package/src/FileRouterPattern.ts +0 -59
  167. package/src/FileRouter_files.test.ts +0 -64
  168. package/src/FileRouter_path.test.ts +0 -145
  169. package/src/FileRouter_tree.test.ts +0 -132
  170. package/src/Http.test.ts +0 -319
  171. package/src/HttpAppExtra.test.ts +0 -103
  172. package/src/HttpUtils.test.ts +0 -85
  173. package/src/PathPattern.test.ts +0 -648
  174. package/src/Random.ts +0 -59
  175. package/src/RouteBody.test.ts +0 -232
  176. package/src/RouteHook.test.ts +0 -40
  177. package/src/RouteHttp.test.ts +0 -2909
  178. package/src/RouteMount.test.ts +0 -481
  179. package/src/RouteSchema.test.ts +0 -427
  180. package/src/RouteSse.test.ts +0 -249
  181. package/src/RouteTree.test.ts +0 -494
  182. package/src/RouteTrie.test.ts +0 -322
  183. package/src/RouterPattern.test.ts +0 -676
  184. package/src/RouterPattern.ts +0 -416
  185. package/src/StartApp.ts +0 -47
  186. package/src/Values.test.ts +0 -263
  187. package/src/bun/BunBundle.test.ts +0 -268
  188. package/src/bun/BunBundle_imports.test.ts +0 -48
  189. package/src/bun/BunHttpServer.test.ts +0 -251
  190. package/src/bun/BunHttpServer.ts +0 -306
  191. package/src/bun/BunImportTrackerPlugin.test.ts +0 -77
  192. package/src/bun/BunRoute.test.ts +0 -162
  193. package/src/bundler/BundleHttp.test.ts +0 -132
  194. package/src/effect/HttpRouter.test.ts +0 -548
  195. package/src/experimental/EncryptedCookies.test.ts +0 -488
  196. package/src/hyper/HyperHtml.test.ts +0 -209
  197. package/src/hyper/HyperRoute.test.tsx +0 -197
  198. package/src/middlewares/BasicAuthMiddleware.test.ts +0 -84
  199. package/src/testing/TestHttpClient.test.ts +0 -83
  200. package/src/testing/TestLogger.test.ts +0 -51
  201. package/src/x/datastar/Datastar.test.ts +0 -266
  202. package/src/x/tailwind/TailwindPlugin.test.ts +0 -333
  203. /package/src/bun/{BunHttpServer_web.ts → BunServerRequest.ts} +0 -0
@@ -0,0 +1,554 @@
1
+ import { watcher } from "../engine.ts"
2
+ import type { HTMLOrSVG, WatcherContext } from "../engine.ts"
3
+ import {
4
+ aliasify,
5
+ isHTMLOrSVG,
6
+ supportsViewTransitions,
7
+ } from "../utils.ts"
8
+
9
+ const isValidType = <T extends readonly string[]>(
10
+ arr: T,
11
+ value: string,
12
+ ): value is T[number] => (arr as readonly string[]).includes(value)
13
+
14
+ const PATCH_MODES = [
15
+ "remove",
16
+ "outer",
17
+ "inner",
18
+ "replace",
19
+ "prepend",
20
+ "append",
21
+ "before",
22
+ "after",
23
+ ] as const
24
+ type PatchElementsMode = (typeof PATCH_MODES)[number]
25
+
26
+ const NAMESPACES = ["html", "svg", "mathml"] as const
27
+ type Namespace = (typeof NAMESPACES)[number]
28
+
29
+ type PatchElementsArgs = {
30
+ selector: string
31
+ mode: PatchElementsMode
32
+ namespace: Namespace
33
+ useViewTransition: boolean
34
+ elements: string
35
+ }
36
+
37
+ watcher({
38
+ name: "datastar-patch-elements",
39
+ apply(
40
+ ctx,
41
+ {
42
+ selector = "",
43
+ mode = "outer",
44
+ namespace = "html",
45
+ useViewTransition = "",
46
+ elements = "",
47
+ },
48
+ ) {
49
+ if (!isValidType(PATCH_MODES, mode)) {
50
+ throw ctx.error("PatchElementsInvalidMode", { mode })
51
+ }
52
+
53
+ if (!selector && mode !== "outer" && mode !== "replace") {
54
+ throw ctx.error("PatchElementsExpectedSelector")
55
+ }
56
+
57
+ if (!isValidType(NAMESPACES, namespace)) {
58
+ throw ctx.error("PatchElementsInvalidNamespace", { namespace })
59
+ }
60
+
61
+ const args2: PatchElementsArgs = {
62
+ selector,
63
+ mode,
64
+ namespace,
65
+ useViewTransition: useViewTransition.trim() === "true",
66
+ elements,
67
+ }
68
+
69
+ if (supportsViewTransitions && useViewTransition) {
70
+ document.startViewTransition(() => onPatchElements(ctx, args2))
71
+ } else {
72
+ onPatchElements(ctx, args2)
73
+ }
74
+ },
75
+ })
76
+
77
+ const onPatchElements = (
78
+ { error }: WatcherContext,
79
+ { selector, mode, namespace, elements }: PatchElementsArgs,
80
+ ) => {
81
+ const elementsWithSvgsRemoved = elements.replace(
82
+ /<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim,
83
+ "",
84
+ )
85
+ const hasHtml = /<\/html>/.test(elementsWithSvgsRemoved)
86
+ const hasHead = /<\/head>/.test(elementsWithSvgsRemoved)
87
+ const hasBody = /<\/body>/.test(elementsWithSvgsRemoved)
88
+
89
+ const wrapperTag = namespace === "svg"
90
+ ? "svg"
91
+ : namespace === "mathml"
92
+ ? "math"
93
+ : ""
94
+ const wrappedEls = wrapperTag
95
+ ? `<${wrapperTag}>${elements}</${wrapperTag}>`
96
+ : elements
97
+
98
+ const newDocument = new DOMParser().parseFromString(
99
+ hasHtml || hasHead || hasBody
100
+ ? elements
101
+ : `<body><template>${wrappedEls}</template></body>`,
102
+ "text/html",
103
+ )
104
+
105
+ let newContent = document.createDocumentFragment()
106
+ if (hasHtml) {
107
+ newContent.appendChild(newDocument.documentElement)
108
+ } else if (hasHead && hasBody) {
109
+ newContent.appendChild(newDocument.head)
110
+ newContent.appendChild(newDocument.body)
111
+ } else if (hasHead) {
112
+ newContent.appendChild(newDocument.head)
113
+ } else if (hasBody) {
114
+ newContent.appendChild(newDocument.body)
115
+ } else if (wrapperTag) {
116
+ const wrapperEl = newDocument
117
+ .querySelector("template")!
118
+ .content
119
+ .querySelector(wrapperTag)!
120
+ for (const child of wrapperEl.childNodes) {
121
+ newContent.appendChild(child)
122
+ }
123
+ } else {
124
+ newContent = newDocument.querySelector("template")!.content
125
+ }
126
+
127
+ if (!selector && (mode === "outer" || mode === "replace")) {
128
+ for (const child of newContent.children) {
129
+ let target: Element
130
+ if (child instanceof HTMLHtmlElement) {
131
+ target = document.documentElement
132
+ } else if (child instanceof HTMLBodyElement) {
133
+ target = document.body
134
+ } else if (child instanceof HTMLHeadElement) {
135
+ target = document.head
136
+ } else {
137
+ target = document.getElementById(child.id)!
138
+ if (!target) {
139
+ console.warn(error("PatchElementsNoTargetsFound"), {
140
+ element: { id: child.id },
141
+ })
142
+ continue
143
+ }
144
+ }
145
+
146
+ applyToTargets(mode as PatchElementsMode, child, [target])
147
+ }
148
+ } else {
149
+ const targets = document.querySelectorAll(selector)
150
+ if (!targets.length) {
151
+ console.warn(error("PatchElementsNoTargetsFound"), { selector })
152
+ return
153
+ }
154
+
155
+ applyToTargets(mode as PatchElementsMode, newContent, targets)
156
+ }
157
+ }
158
+
159
+ const scripts = new WeakSet<HTMLScriptElement>()
160
+ for (const script of document.querySelectorAll("script")) {
161
+ scripts.add(script)
162
+ }
163
+
164
+ const execute = (target: Element): void => {
165
+ const elScripts = target instanceof HTMLScriptElement
166
+ ? [target]
167
+ : target.querySelectorAll("script")
168
+ for (const old of elScripts) {
169
+ if (!scripts.has(old)) {
170
+ const script = document.createElement("script")
171
+ for (const { name, value } of old.attributes) {
172
+ script.setAttribute(name, value)
173
+ }
174
+ script.text = old.text
175
+ old.replaceWith(script)
176
+ scripts.add(script)
177
+ }
178
+ }
179
+ }
180
+
181
+ const applyPatchMode = (
182
+ targets: Iterable<Element>,
183
+ element: DocumentFragment | Element,
184
+ action: string,
185
+ ) => {
186
+ for (const target of targets) {
187
+ const cloned = element.cloneNode(true) as Element
188
+ execute(cloned)
189
+ // @ts-ignore
190
+ target[action](cloned)
191
+ }
192
+ }
193
+
194
+ const applyToTargets = (
195
+ mode: PatchElementsMode,
196
+ element: DocumentFragment | Element,
197
+ targets: Iterable<Element>,
198
+ ) => {
199
+ switch (mode) {
200
+ case "remove":
201
+ for (const target of targets) {
202
+ target.remove()
203
+ }
204
+ break
205
+ case "outer":
206
+ case "inner":
207
+ for (const target of targets) {
208
+ morph(target, element.cloneNode(true) as Element, mode)
209
+ execute(target)
210
+ }
211
+ break
212
+ case "replace":
213
+ applyPatchMode(targets, element, "replaceWith")
214
+ break
215
+ case "prepend":
216
+ case "append":
217
+ case "before":
218
+ case "after":
219
+ applyPatchMode(targets, element, mode)
220
+ }
221
+ }
222
+
223
+ const ctxIdMap = new Map<Node, Set<string>>()
224
+ const ctxPersistentIds = new Set<string>()
225
+ const oldIdTagNameMap = new Map<string, string>()
226
+ const duplicateIds = new Set<string>()
227
+ const ctxPantry = document.createElement("div")
228
+ ctxPantry.hidden = true
229
+
230
+ const aliasedIgnoreMorph = aliasify("ignore-morph")
231
+ const aliasedIgnoreMorphAttr = `[${aliasedIgnoreMorph}]`
232
+ const morph = (
233
+ oldElt: Element | ShadowRoot,
234
+ newContent: DocumentFragment | Element,
235
+ mode: "outer" | "inner" = "outer",
236
+ ): void => {
237
+ if (
238
+ (isHTMLOrSVG(oldElt)
239
+ && isHTMLOrSVG(newContent)
240
+ && (oldElt as HTMLOrSVG).hasAttribute(aliasedIgnoreMorph)
241
+ && (newContent as HTMLOrSVG).hasAttribute(aliasedIgnoreMorph))
242
+ || oldElt.parentElement?.closest(aliasedIgnoreMorphAttr)
243
+ ) {
244
+ return
245
+ }
246
+
247
+ const normalizedElt = document.createElement("div")
248
+ normalizedElt.append(newContent)
249
+ document.body.insertAdjacentElement("afterend", ctxPantry)
250
+
251
+ const oldIdElements = oldElt.querySelectorAll("[id]")
252
+ for (const { id, tagName } of oldIdElements) {
253
+ if (oldIdTagNameMap.has(id)) {
254
+ duplicateIds.add(id)
255
+ } else {
256
+ oldIdTagNameMap.set(id, tagName)
257
+ }
258
+ }
259
+ if (oldElt instanceof Element && oldElt.id) {
260
+ if (oldIdTagNameMap.has(oldElt.id)) {
261
+ duplicateIds.add(oldElt.id)
262
+ } else {
263
+ oldIdTagNameMap.set(oldElt.id, oldElt.tagName)
264
+ }
265
+ }
266
+
267
+ ctxPersistentIds.clear()
268
+ const newIdElements = normalizedElt.querySelectorAll("[id]")
269
+ for (const { id, tagName } of newIdElements) {
270
+ if (ctxPersistentIds.has(id)) {
271
+ duplicateIds.add(id)
272
+ } else if (oldIdTagNameMap.get(id) === tagName) {
273
+ ctxPersistentIds.add(id)
274
+ }
275
+ }
276
+
277
+ for (const id of duplicateIds) {
278
+ ctxPersistentIds.delete(id)
279
+ }
280
+
281
+ oldIdTagNameMap.clear()
282
+ duplicateIds.clear()
283
+ ctxIdMap.clear()
284
+
285
+ const parent = mode === "outer" ? oldElt.parentElement! : oldElt
286
+ populateIdMapWithTree(parent, oldIdElements)
287
+ populateIdMapWithTree(normalizedElt, newIdElements)
288
+
289
+ morphChildren(
290
+ parent,
291
+ normalizedElt,
292
+ mode === "outer" ? oldElt : null,
293
+ oldElt.nextSibling,
294
+ )
295
+
296
+ ctxPantry.remove()
297
+ }
298
+
299
+ const morphChildren = (
300
+ oldParent: Element | ShadowRoot,
301
+ newParent: Element,
302
+ insertionPoint: Node | null = null,
303
+ endPoint: Node | null = null,
304
+ ): void => {
305
+ if (
306
+ oldParent instanceof HTMLTemplateElement
307
+ && newParent instanceof HTMLTemplateElement
308
+ ) {
309
+ oldParent = oldParent.content as unknown as Element
310
+ newParent = newParent.content as unknown as Element
311
+ }
312
+ insertionPoint ??= oldParent.firstChild
313
+
314
+ for (const newChild of newParent.childNodes) {
315
+ if (insertionPoint && insertionPoint !== endPoint) {
316
+ const bestMatch = findBestMatch(newChild, insertionPoint, endPoint)
317
+ if (bestMatch) {
318
+ if (bestMatch !== insertionPoint) {
319
+ let cursor: Node | null = insertionPoint
320
+ while (cursor && cursor !== bestMatch) {
321
+ const tempNode = cursor
322
+ cursor = cursor.nextSibling
323
+ removeNode(tempNode)
324
+ }
325
+ }
326
+ morphNode(bestMatch, newChild)
327
+ insertionPoint = bestMatch.nextSibling
328
+ continue
329
+ }
330
+ }
331
+
332
+ if (newChild instanceof Element && ctxPersistentIds.has(newChild.id)) {
333
+ const movedChild = document.getElementById(newChild.id) as Element
334
+
335
+ let current = movedChild
336
+ while ((current = current.parentNode as Element)) {
337
+ const idSet = ctxIdMap.get(current)
338
+ if (idSet) {
339
+ idSet.delete(newChild.id)
340
+ if (!idSet.size) {
341
+ ctxIdMap.delete(current)
342
+ }
343
+ }
344
+ }
345
+
346
+ moveBefore(oldParent, movedChild, insertionPoint)
347
+ morphNode(movedChild, newChild)
348
+ insertionPoint = movedChild.nextSibling
349
+ continue
350
+ }
351
+
352
+ if (ctxIdMap.has(newChild)) {
353
+ const namespaceURI = (newChild as Element).namespaceURI
354
+ const tagName = (newChild as Element).tagName
355
+ const newEmptyChild =
356
+ namespaceURI && namespaceURI !== "http://www.w3.org/1999/xhtml"
357
+ ? document.createElementNS(namespaceURI, tagName)
358
+ : document.createElement(tagName)
359
+ oldParent.insertBefore(newEmptyChild, insertionPoint)
360
+ morphNode(newEmptyChild, newChild)
361
+ insertionPoint = newEmptyChild.nextSibling
362
+ } else {
363
+ const newClonedChild = document.importNode(newChild, true)
364
+ oldParent.insertBefore(newClonedChild, insertionPoint)
365
+ insertionPoint = newClonedChild.nextSibling
366
+ }
367
+ }
368
+
369
+ while (insertionPoint && insertionPoint !== endPoint) {
370
+ const tempNode = insertionPoint
371
+ insertionPoint = insertionPoint.nextSibling
372
+ removeNode(tempNode)
373
+ }
374
+ }
375
+
376
+ const findBestMatch = (
377
+ node: Node,
378
+ startPoint: Node | null,
379
+ endPoint: Node | null,
380
+ ): Node | null => {
381
+ let bestMatch: Node | null | undefined = null
382
+ let nextSibling = node.nextSibling
383
+ let siblingSoftMatchCount = 0
384
+ let displaceMatchCount = 0
385
+
386
+ const nodeMatchCount = ctxIdMap.get(node)?.size || 0
387
+
388
+ let cursor = startPoint
389
+ while (cursor && cursor !== endPoint) {
390
+ if (isSoftMatch(cursor, node)) {
391
+ let isIdSetMatch = false
392
+ const oldSet = ctxIdMap.get(cursor)
393
+ const newSet = ctxIdMap.get(node)
394
+
395
+ if (newSet && oldSet) {
396
+ for (const id of oldSet) {
397
+ if (newSet.has(id)) {
398
+ isIdSetMatch = true
399
+ break
400
+ }
401
+ }
402
+ }
403
+
404
+ if (isIdSetMatch) {
405
+ return cursor
406
+ }
407
+
408
+ if (!bestMatch && !ctxIdMap.has(cursor)) {
409
+ if (!nodeMatchCount) {
410
+ return cursor
411
+ }
412
+ bestMatch = cursor
413
+ }
414
+ }
415
+
416
+ displaceMatchCount += ctxIdMap.get(cursor)?.size || 0
417
+ if (displaceMatchCount > nodeMatchCount) {
418
+ break
419
+ }
420
+
421
+ if (bestMatch === null && nextSibling && isSoftMatch(cursor, nextSibling)) {
422
+ siblingSoftMatchCount++
423
+ nextSibling = nextSibling.nextSibling
424
+
425
+ if (siblingSoftMatchCount >= 2) {
426
+ bestMatch = undefined
427
+ }
428
+ }
429
+
430
+ cursor = cursor.nextSibling
431
+ }
432
+
433
+ return bestMatch || null
434
+ }
435
+
436
+ const isSoftMatch = (oldNode: Node, newNode: Node): boolean =>
437
+ oldNode.nodeType === newNode.nodeType
438
+ && (oldNode as Element).tagName === (newNode as Element).tagName
439
+ && (!(oldNode as Element).id
440
+ || (oldNode as Element).id === (newNode as Element).id)
441
+
442
+ const removeNode = (node: Node): void => {
443
+ ctxIdMap.has(node)
444
+ ? moveBefore(ctxPantry, node, null)
445
+ : node.parentNode?.removeChild(node)
446
+ }
447
+
448
+ const moveBefore: (parentNode: Node, node: Node, after: Node | null) => void =
449
+ // @ts-expect-error
450
+ removeNode.call.bind(ctxPantry.moveBefore ?? ctxPantry.insertBefore)
451
+
452
+ const aliasedPreserveAttr = aliasify("preserve-attr")
453
+
454
+ const morphNode = (
455
+ oldNode: Node,
456
+ newNode: Node,
457
+ ): Node => {
458
+ const type = newNode.nodeType
459
+
460
+ if (type === 1) {
461
+ const oldElt = oldNode as Element
462
+ const newElt = newNode as Element
463
+ const shouldScopeChildren = oldElt.hasAttribute("data-scope-children")
464
+ if (
465
+ oldElt.hasAttribute(aliasedIgnoreMorph)
466
+ && newElt.hasAttribute(aliasedIgnoreMorph)
467
+ ) {
468
+ return oldNode
469
+ }
470
+
471
+ if (
472
+ oldElt instanceof HTMLInputElement
473
+ && newElt instanceof HTMLInputElement
474
+ && newElt.type !== "file"
475
+ ) {
476
+ if (newElt.getAttribute("value") !== oldElt.getAttribute("value")) {
477
+ oldElt.value = newElt.getAttribute("value") ?? ""
478
+ }
479
+ } else if (
480
+ oldElt instanceof HTMLTextAreaElement
481
+ && newElt instanceof HTMLTextAreaElement
482
+ ) {
483
+ if (newElt.value !== oldElt.value) {
484
+ oldElt.value = newElt.value
485
+ }
486
+ if (oldElt.firstChild && oldElt.firstChild.nodeValue !== newElt.value) {
487
+ oldElt.firstChild.nodeValue = newElt.value
488
+ }
489
+ }
490
+
491
+ const preserveAttrs = (
492
+ (newNode as HTMLElement).getAttribute(aliasedPreserveAttr) ?? ""
493
+ )
494
+ .split(" ")
495
+
496
+ for (const { name, value } of newElt.attributes) {
497
+ if (
498
+ oldElt.getAttribute(name) !== value
499
+ && !preserveAttrs.includes(name)
500
+ ) {
501
+ oldElt.setAttribute(name, value)
502
+ }
503
+ }
504
+
505
+ for (let i = oldElt.attributes.length - 1; i >= 0; i--) {
506
+ const { name } = oldElt.attributes[i]!
507
+ if (!newElt.hasAttribute(name) && !preserveAttrs.includes(name)) {
508
+ oldElt.removeAttribute(name)
509
+ }
510
+ }
511
+
512
+ if (shouldScopeChildren && !oldElt.hasAttribute("data-scope-children")) {
513
+ oldElt.setAttribute("data-scope-children", "")
514
+ }
515
+
516
+ if (!oldElt.isEqualNode(newElt)) {
517
+ morphChildren(oldElt, newElt)
518
+ }
519
+
520
+ if (shouldScopeChildren) {
521
+ oldElt.dispatchEvent(
522
+ new CustomEvent("datastar:scope-children", { bubbles: false }),
523
+ )
524
+ }
525
+ }
526
+
527
+ if (type === 8 || type === 3) {
528
+ if (oldNode.nodeValue !== newNode.nodeValue) {
529
+ oldNode.nodeValue = newNode.nodeValue
530
+ }
531
+ }
532
+
533
+ return oldNode
534
+ }
535
+
536
+ const populateIdMapWithTree = (
537
+ root: Element | ShadowRoot | null,
538
+ elements: Iterable<Element>,
539
+ ): void => {
540
+ for (const elt of elements) {
541
+ if (ctxPersistentIds.has(elt.id)) {
542
+ let current: Element | null = elt
543
+ while (current && current !== root) {
544
+ let idSet = ctxIdMap.get(current)
545
+ if (!idSet) {
546
+ idSet = new Set()
547
+ ctxIdMap.set(current, idSet)
548
+ }
549
+ idSet.add(elt.id)
550
+ current = current.parentElement
551
+ }
552
+ }
553
+ }
554
+ }
@@ -0,0 +1,15 @@
1
+ import { watcher } from "../engine.ts"
2
+ import { mergePatch } from "../engine.ts"
3
+ import { jsStrToObject } from "../utils.ts"
4
+
5
+ watcher({
6
+ name: "datastar-patch-signals",
7
+ apply({ error }, { signals, onlyIfMissing }) {
8
+ if (signals) {
9
+ const ifMissing = onlyIfMissing?.trim() === "true"
10
+ mergePatch(jsStrToObject(signals), { ifMissing })
11
+ } else {
12
+ throw error("PatchSignalsExpectedSignals")
13
+ }
14
+ },
15
+ })
package/src/index.ts CHANGED
@@ -4,3 +4,4 @@ export * as Entity from "./Entity.ts"
4
4
  export * as FileRouter from "./FileRouter.ts"
5
5
  export * as Route from "./Route.ts"
6
6
  export * as Start from "./Start.ts"
7
+ export * as Unique from "./Unique.ts"