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.
- package/README.md +3 -3
- package/dist/Development.d.ts +8 -3
- package/dist/Development.js +14 -7
- package/dist/Effectify.d.ts +212 -0
- package/dist/Effectify.js +19 -0
- package/dist/FilePathPattern.d.ts +29 -0
- package/dist/FilePathPattern.js +86 -0
- package/dist/FileRouter.d.ts +39 -41
- package/dist/FileRouter.js +104 -158
- package/dist/FileRouterCodegen.d.ts +7 -8
- package/dist/FileRouterCodegen.js +97 -66
- package/dist/PlatformError.d.ts +46 -0
- package/dist/PlatformError.js +43 -0
- package/dist/PlatformRuntime.d.ts +27 -0
- package/dist/PlatformRuntime.js +51 -0
- package/dist/Route.d.ts +6 -2
- package/dist/Route.js +22 -0
- package/dist/RouteBody.d.ts +1 -1
- package/dist/RouteHttp.d.ts +1 -1
- package/dist/RouteHttp.js +12 -19
- package/dist/RouteMount.d.ts +2 -1
- package/dist/Start.d.ts +33 -6
- package/dist/Start.js +31 -13
- package/dist/Unique.d.ts +50 -0
- package/dist/Unique.js +187 -0
- package/dist/bun/BunHttpServer.js +5 -6
- package/dist/bun/BunPlatformHttpServer.d.ts +10 -0
- package/dist/bun/BunPlatformHttpServer.js +53 -0
- package/dist/bun/BunRoute.d.ts +4 -6
- package/dist/bun/BunRoute.js +10 -18
- package/dist/bun/BunRuntime.d.ts +2 -1
- package/dist/bun/BunRuntime.js +10 -5
- package/dist/bun/BunServer.d.ts +33 -0
- package/dist/bun/BunServer.js +133 -0
- package/dist/bun/BunServerRequest.d.ts +60 -0
- package/dist/bun/BunServerRequest.js +252 -0
- package/dist/bun/index.d.ts +1 -1
- package/dist/bun/index.js +1 -1
- package/dist/datastar/actions/fetch.d.ts +30 -0
- package/dist/datastar/actions/fetch.js +411 -0
- package/dist/datastar/actions/peek.d.ts +1 -0
- package/dist/datastar/actions/peek.js +14 -0
- package/dist/datastar/actions/setAll.d.ts +1 -0
- package/dist/datastar/actions/setAll.js +13 -0
- package/dist/datastar/actions/toggleAll.d.ts +1 -0
- package/dist/datastar/actions/toggleAll.js +13 -0
- package/dist/datastar/attributes/attr.d.ts +1 -0
- package/dist/datastar/attributes/attr.js +49 -0
- package/dist/datastar/attributes/bind.d.ts +1 -0
- package/dist/datastar/attributes/bind.js +183 -0
- package/dist/datastar/attributes/class.d.ts +1 -0
- package/dist/datastar/attributes/class.js +50 -0
- package/dist/datastar/attributes/computed.d.ts +1 -0
- package/dist/datastar/attributes/computed.js +27 -0
- package/dist/datastar/attributes/effect.d.ts +1 -0
- package/dist/datastar/attributes/effect.js +10 -0
- package/dist/datastar/attributes/indicator.d.ts +1 -0
- package/dist/datastar/attributes/indicator.js +32 -0
- package/dist/datastar/attributes/init.d.ts +1 -0
- package/dist/datastar/attributes/init.js +27 -0
- package/dist/datastar/attributes/jsonSignals.d.ts +1 -0
- package/dist/datastar/attributes/jsonSignals.js +31 -0
- package/dist/datastar/attributes/on.d.ts +1 -0
- package/dist/datastar/attributes/on.js +59 -0
- package/dist/datastar/attributes/onIntersect.d.ts +1 -0
- package/dist/datastar/attributes/onIntersect.js +54 -0
- package/dist/datastar/attributes/onInterval.d.ts +1 -0
- package/dist/datastar/attributes/onInterval.js +31 -0
- package/dist/datastar/attributes/onSignalPatch.d.ts +1 -0
- package/dist/datastar/attributes/onSignalPatch.js +44 -0
- package/dist/datastar/attributes/ref.d.ts +1 -0
- package/dist/datastar/attributes/ref.js +11 -0
- package/dist/datastar/attributes/show.d.ts +1 -0
- package/dist/datastar/attributes/show.js +32 -0
- package/dist/datastar/attributes/signals.d.ts +1 -0
- package/dist/datastar/attributes/signals.js +18 -0
- package/dist/datastar/attributes/style.d.ts +1 -0
- package/dist/datastar/attributes/style.js +56 -0
- package/dist/datastar/attributes/text.d.ts +1 -0
- package/dist/datastar/attributes/text.js +27 -0
- package/dist/datastar/engine.d.ts +156 -0
- package/dist/datastar/engine.js +971 -0
- package/dist/datastar/index.d.ts +24 -0
- package/dist/datastar/index.js +24 -0
- package/dist/datastar/load.d.ts +24 -0
- package/dist/datastar/load.js +24 -0
- package/dist/datastar/utils.d.ts +51 -0
- package/dist/datastar/utils.js +205 -0
- package/dist/datastar/watchers/patchElements.d.ts +1 -0
- package/dist/datastar/watchers/patchElements.js +420 -0
- package/dist/datastar/watchers/patchSignals.d.ts +1 -0
- package/dist/datastar/watchers/patchSignals.js +15 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/node/Effectify.d.ts +209 -0
- package/dist/node/Effectify.js +19 -0
- package/dist/node/FileSystem.d.ts +3 -5
- package/dist/node/FileSystem.js +42 -62
- package/dist/node/NodeFileSystem.d.ts +7 -0
- package/dist/node/NodeFileSystem.js +420 -0
- package/dist/node/NodeUtils.d.ts +2 -0
- package/dist/node/NodeUtils.js +20 -0
- package/dist/node/PlatformError.d.ts +46 -0
- package/dist/node/PlatformError.js +43 -0
- package/dist/testing/TestLogger.js +1 -1
- package/dist/x/tailwind/plugin.js +1 -1
- package/package.json +18 -7
- package/src/Development.ts +36 -40
- package/src/Effectify.ts +269 -0
- package/src/FilePathPattern.ts +115 -0
- package/src/FileRouter.ts +178 -255
- package/src/FileRouterCodegen.ts +135 -92
- package/src/PlatformError.ts +117 -0
- package/src/PlatformRuntime.ts +108 -0
- package/src/Route.ts +31 -2
- package/src/RouteBody.ts +1 -1
- package/src/RouteHttp.ts +15 -29
- package/src/RouteMount.ts +1 -1
- package/src/Start.ts +61 -27
- package/src/Unique.ts +232 -0
- package/src/bun/BunPlatformHttpServer.ts +88 -0
- package/src/bun/BunRoute.ts +14 -24
- package/src/bun/BunRuntime.ts +21 -5
- package/src/bun/BunServer.ts +228 -0
- package/src/bun/index.ts +1 -1
- package/src/datastar/README.md +18 -0
- package/src/datastar/actions/fetch.ts +609 -0
- package/src/datastar/actions/peek.ts +17 -0
- package/src/datastar/actions/setAll.ts +20 -0
- package/src/datastar/actions/toggleAll.ts +20 -0
- package/src/datastar/attributes/attr.ts +50 -0
- package/src/datastar/attributes/bind.ts +220 -0
- package/src/datastar/attributes/class.ts +57 -0
- package/src/datastar/attributes/computed.ts +33 -0
- package/src/datastar/attributes/effect.ts +11 -0
- package/src/datastar/attributes/indicator.ts +39 -0
- package/src/datastar/attributes/init.ts +35 -0
- package/src/datastar/attributes/jsonSignals.ts +38 -0
- package/src/datastar/attributes/on.ts +71 -0
- package/src/datastar/attributes/onIntersect.ts +65 -0
- package/src/datastar/attributes/onInterval.ts +39 -0
- package/src/datastar/attributes/onSignalPatch.ts +63 -0
- package/src/datastar/attributes/ref.ts +12 -0
- package/src/datastar/attributes/show.ts +33 -0
- package/src/datastar/attributes/signals.ts +22 -0
- package/src/datastar/attributes/style.ts +63 -0
- package/src/datastar/attributes/text.ts +30 -0
- package/src/datastar/engine.ts +1341 -0
- package/src/datastar/index.ts +25 -0
- package/src/datastar/utils.ts +286 -0
- package/src/datastar/watchers/patchElements.ts +554 -0
- package/src/datastar/watchers/patchSignals.ts +15 -0
- package/src/index.ts +1 -0
- package/src/node/{FileSystem.ts → NodeFileSystem.ts} +59 -97
- package/src/node/{Utils.ts → NodeUtils.ts} +2 -0
- package/src/testing/TestLogger.ts +1 -1
- package/src/x/tailwind/plugin.ts +1 -1
- package/dist/Random.d.ts +0 -5
- package/dist/Random.js +0 -49
- package/src/Commander.test.ts +0 -1639
- package/src/ContentNegotiation.test.ts +0 -603
- package/src/Development.test.ts +0 -119
- package/src/Entity.test.ts +0 -592
- package/src/FileRouterCodegen.todo.ts +0 -1133
- package/src/FileRouterPattern.test.ts +0 -147
- package/src/FileRouterPattern.ts +0 -59
- package/src/FileRouter_files.test.ts +0 -64
- package/src/FileRouter_path.test.ts +0 -145
- package/src/FileRouter_tree.test.ts +0 -132
- package/src/Http.test.ts +0 -319
- package/src/HttpAppExtra.test.ts +0 -103
- package/src/HttpUtils.test.ts +0 -85
- package/src/PathPattern.test.ts +0 -648
- package/src/Random.ts +0 -59
- package/src/RouteBody.test.ts +0 -232
- package/src/RouteHook.test.ts +0 -40
- package/src/RouteHttp.test.ts +0 -2909
- package/src/RouteMount.test.ts +0 -481
- package/src/RouteSchema.test.ts +0 -427
- package/src/RouteSse.test.ts +0 -249
- package/src/RouteTree.test.ts +0 -494
- package/src/RouteTrie.test.ts +0 -322
- package/src/RouterPattern.test.ts +0 -676
- package/src/RouterPattern.ts +0 -416
- package/src/StartApp.ts +0 -47
- package/src/Values.test.ts +0 -263
- package/src/bun/BunBundle.test.ts +0 -268
- package/src/bun/BunBundle_imports.test.ts +0 -48
- package/src/bun/BunHttpServer.test.ts +0 -251
- package/src/bun/BunHttpServer.ts +0 -306
- package/src/bun/BunImportTrackerPlugin.test.ts +0 -77
- package/src/bun/BunRoute.test.ts +0 -162
- package/src/bundler/BundleHttp.test.ts +0 -132
- package/src/effect/HttpRouter.test.ts +0 -548
- package/src/experimental/EncryptedCookies.test.ts +0 -488
- package/src/hyper/HyperHtml.test.ts +0 -209
- package/src/hyper/HyperRoute.test.tsx +0 -197
- package/src/middlewares/BasicAuthMiddleware.test.ts +0 -84
- package/src/testing/TestHttpClient.test.ts +0 -83
- package/src/testing/TestLogger.test.ts +0 -51
- package/src/x/datastar/Datastar.test.ts +0 -266
- package/src/x/tailwind/TailwindPlugin.test.ts +0 -333
- /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