@absolutejs/absolute 0.14.0 → 0.15.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.
- package/.claude/settings.local.json +11 -0
- package/CLAUDE.md +11 -2
- package/LICENSE +74 -18
- package/README.md +4 -4
- package/THIRD_PARTY_NOTICES.md +61 -0
- package/dist/cli/index.js +24 -8
- package/dist/dev/client/cssUtils.ts +288 -0
- package/dist/dev/client/domDiff.ts +261 -0
- package/dist/dev/client/domState.ts +271 -0
- package/dist/dev/client/errorOverlay.ts +145 -0
- package/dist/dev/client/frameworkDetect.ts +63 -0
- package/dist/dev/client/handlers/html.ts +415 -0
- package/dist/dev/client/handlers/htmx.ts +248 -0
- package/dist/dev/client/handlers/react.ts +80 -0
- package/dist/dev/client/handlers/rebuild.ts +147 -0
- package/dist/dev/client/handlers/svelte.ts +129 -0
- package/dist/dev/client/handlers/vue.ts +254 -0
- package/dist/dev/client/headPatch.ts +213 -0
- package/dist/dev/client/hmrClient.ts +204 -0
- package/dist/dev/client/moduleVersions.ts +57 -0
- package/dist/dev/client/reactRefreshSetup.ts +21 -0
- package/dist/index.js +3028 -478
- package/dist/index.js.map +49 -19
- package/dist/{build → src/build}/compileSvelte.d.ts +2 -1
- package/dist/src/build/compileVue.d.ts +33 -0
- package/dist/src/build/generateReactIndexes.d.ts +1 -0
- package/dist/src/build/htmlScriptHMRPlugin.d.ts +13 -0
- package/dist/src/build/wrapHTMLScript.d.ts +24 -0
- package/dist/{core → src/core}/build.d.ts +2 -2
- package/dist/src/core/devBuild.d.ts +6 -0
- package/dist/{core → src/core}/index.d.ts +2 -1
- package/dist/src/core/lookup.d.ts +3 -0
- package/dist/{core → src/core}/pageHandlers.d.ts +4 -4
- package/dist/src/dev/assetStore.d.ts +12 -0
- package/dist/src/dev/buildHMRClient.d.ts +1 -0
- package/dist/src/dev/clientManager.d.ts +26 -0
- package/dist/src/dev/configResolver.d.ts +13 -0
- package/dist/src/dev/dependencyGraph.d.ts +13 -0
- package/dist/src/dev/fileHashTracker.d.ts +2 -0
- package/dist/src/dev/fileWatcher.d.ts +3 -0
- package/dist/src/dev/moduleMapper.d.ts +21 -0
- package/dist/src/dev/moduleVersionTracker.d.ts +7 -0
- package/dist/src/dev/pathUtils.d.ts +5 -0
- package/dist/src/dev/reactComponentClassifier.d.ts +2 -0
- package/dist/src/dev/rebuildTrigger.d.ts +10 -0
- package/dist/src/dev/simpleHTMLHMR.d.ts +4 -0
- package/dist/src/dev/simpleHTMXHMR.d.ts +4 -0
- package/dist/src/dev/simpleSvelteHMR.d.ts +1 -0
- package/dist/src/dev/simpleVueHMR.d.ts +1 -0
- package/dist/src/dev/webSocket.d.ts +9 -0
- package/dist/{index.d.ts → src/index.d.ts} +1 -0
- package/dist/src/plugins/hmr.d.ts +62 -0
- package/dist/{plugins → src/plugins}/index.d.ts +2 -1
- package/dist/{svelte → src/svelte}/renderToReadableStream.d.ts +3 -1
- package/dist/src/utils/getRegisterClientScript.d.ts +10 -0
- package/dist/{utils → src/utils}/index.d.ts +2 -0
- package/dist/src/utils/logger.d.ts +45 -0
- package/dist/src/utils/networking.d.ts +2 -0
- package/dist/src/utils/normalizePath.d.ts +9 -0
- package/dist/src/utils/registerClientScript.d.ts +51 -0
- package/dist/{types.d.ts → types/build.d.ts} +6 -0
- package/dist/types/client.d.ts +104 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/messages.d.ts +138 -0
- package/dist/types/websocket.d.ts +6 -0
- package/eslint.config.mjs +5 -1
- package/package.json +19 -14
- package/tsconfig.build.json +1 -1
- package/types/build.ts +46 -0
- package/types/client.ts +109 -0
- package/types/index.ts +4 -0
- package/types/messages.ts +205 -0
- package/types/websocket.ts +12 -0
- package/types/window-globals.ts +53 -0
- package/dist/build/compileVue.d.ts +0 -5
- package/dist/build/generateReactIndexes.d.ts +0 -1
- package/dist/core/lookup.d.ts +0 -1
- package/dist/utils/networking.d.ts +0 -1
- /package/dist/{build → src/build}/generateManifest.d.ts +0 -0
- /package/dist/{build → src/build}/outputLogs.d.ts +0 -0
- /package/dist/{build → src/build}/scanEntryPoints.d.ts +0 -0
- /package/dist/{build → src/build}/updateAssetPaths.d.ts +0 -0
- /package/dist/{cli → src/cli}/index.d.ts +0 -0
- /package/dist/{constants.d.ts → src/constants.d.ts} +0 -0
- /package/dist/{plugins → src/plugins}/networking.d.ts +0 -0
- /package/dist/{plugins → src/plugins}/pageRouter.d.ts +0 -0
- /package/dist/{svelte → src/svelte}/renderToPipeableStream.d.ts +0 -0
- /package/dist/{svelte → src/svelte}/renderToString.d.ts +0 -0
- /package/dist/{utils → src/utils}/cleanup.d.ts +0 -0
- /package/dist/{utils → src/utils}/commonAncestor.d.ts +0 -0
- /package/dist/{utils → src/utils}/escapeScriptContent.d.ts +0 -0
- /package/dist/{utils → src/utils}/generateHeadElement.d.ts +0 -0
- /package/dist/{utils → src/utils}/getDurationString.d.ts +0 -0
- /package/dist/{utils → src/utils}/getEnv.d.ts +0 -0
- /package/dist/{utils → src/utils}/stringModifiers.d.ts +0 -0
- /package/dist/{utils → src/utils}/validateSafePath.d.ts +0 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/* DOM diffing/patching for in-place updates (zero flicker) */
|
|
2
|
+
|
|
3
|
+
const getElementKey = (el: Node, index: number) => {
|
|
4
|
+
if (el.nodeType !== Node.ELEMENT_NODE) return 'text_' + index;
|
|
5
|
+
const element = el as Element;
|
|
6
|
+
if (element.id) return 'id_' + element.id;
|
|
7
|
+
if (element.hasAttribute('data-key'))
|
|
8
|
+
return 'key_' + element.getAttribute('data-key');
|
|
9
|
+
return 'tag_' + element.tagName + '_' + index;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const updateElementAttributes = (oldEl: Element, newEl: Element) => {
|
|
13
|
+
const newAttrs = Array.from(newEl.attributes);
|
|
14
|
+
const oldAttrs = Array.from(oldEl.attributes);
|
|
15
|
+
const runtimeAttrs = ['data-hmr-listeners-attached'];
|
|
16
|
+
|
|
17
|
+
oldAttrs.forEach(function (oldAttr) {
|
|
18
|
+
if (
|
|
19
|
+
!newEl.hasAttribute(oldAttr.name) &&
|
|
20
|
+
runtimeAttrs.indexOf(oldAttr.name) === -1
|
|
21
|
+
) {
|
|
22
|
+
oldEl.removeAttribute(oldAttr.name);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
newAttrs.forEach(function (newAttr) {
|
|
27
|
+
if (
|
|
28
|
+
runtimeAttrs.indexOf(newAttr.name) !== -1 &&
|
|
29
|
+
oldEl.hasAttribute(newAttr.name)
|
|
30
|
+
) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const oldValue = oldEl.getAttribute(newAttr.name);
|
|
34
|
+
if (oldValue !== newAttr.value) {
|
|
35
|
+
oldEl.setAttribute(newAttr.name, newAttr.value);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const updateTextNode = (oldNode: Node, newNode: Node) => {
|
|
41
|
+
if (oldNode.nodeValue !== newNode.nodeValue) {
|
|
42
|
+
oldNode.nodeValue = newNode.nodeValue;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
interface KeyedEntry {
|
|
47
|
+
index: number;
|
|
48
|
+
node: Node;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const matchChildren = (oldChildren: Node[], newChildren: Node[]) => {
|
|
52
|
+
const oldMap = new Map<string, KeyedEntry[]>();
|
|
53
|
+
const newMap = new Map<string, KeyedEntry[]>();
|
|
54
|
+
|
|
55
|
+
oldChildren.forEach(function (child, idx) {
|
|
56
|
+
const key = getElementKey(child, idx);
|
|
57
|
+
if (!oldMap.has(key)) {
|
|
58
|
+
oldMap.set(key, []);
|
|
59
|
+
}
|
|
60
|
+
oldMap.get(key)!.push({ index: idx, node: child });
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
newChildren.forEach(function (child, idx) {
|
|
64
|
+
const key = getElementKey(child, idx);
|
|
65
|
+
if (!newMap.has(key)) {
|
|
66
|
+
newMap.set(key, []);
|
|
67
|
+
}
|
|
68
|
+
newMap.get(key)!.push({ index: idx, node: child });
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
return { newMap, oldMap };
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const isHMRScript = (el: Node) => {
|
|
75
|
+
return (
|
|
76
|
+
el.nodeType === Node.ELEMENT_NODE &&
|
|
77
|
+
(el as Element).hasAttribute &&
|
|
78
|
+
(el as Element).hasAttribute('data-hmr-client')
|
|
79
|
+
);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const isHMRPreserved = (el: Node) => {
|
|
83
|
+
return (
|
|
84
|
+
isHMRScript(el) ||
|
|
85
|
+
(el.nodeType === Node.ELEMENT_NODE &&
|
|
86
|
+
(el as Element).hasAttribute &&
|
|
87
|
+
(el as Element).hasAttribute('data-hmr-overlay'))
|
|
88
|
+
);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const patchNode = (oldNode: Node, newNode: Node) => {
|
|
92
|
+
if (
|
|
93
|
+
oldNode.nodeType === Node.TEXT_NODE &&
|
|
94
|
+
newNode.nodeType === Node.TEXT_NODE
|
|
95
|
+
) {
|
|
96
|
+
updateTextNode(oldNode, newNode);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (
|
|
101
|
+
oldNode.nodeType === Node.ELEMENT_NODE &&
|
|
102
|
+
newNode.nodeType === Node.ELEMENT_NODE
|
|
103
|
+
) {
|
|
104
|
+
const oldEl = oldNode as Element;
|
|
105
|
+
const newEl = newNode as Element;
|
|
106
|
+
|
|
107
|
+
if (oldEl.tagName !== newEl.tagName) {
|
|
108
|
+
const clone = newEl.cloneNode(true);
|
|
109
|
+
oldEl.replaceWith(clone);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
updateElementAttributes(oldEl, newEl);
|
|
114
|
+
|
|
115
|
+
const oldChildren = Array.from(oldNode.childNodes);
|
|
116
|
+
const newChildren = Array.from(newNode.childNodes);
|
|
117
|
+
|
|
118
|
+
const oldChildrenFiltered = oldChildren.filter(function (child) {
|
|
119
|
+
return (
|
|
120
|
+
!isHMRScript(child) &&
|
|
121
|
+
!(
|
|
122
|
+
child.nodeType === Node.ELEMENT_NODE &&
|
|
123
|
+
(child as Element).tagName === 'SCRIPT'
|
|
124
|
+
)
|
|
125
|
+
);
|
|
126
|
+
});
|
|
127
|
+
const newChildrenFiltered = newChildren.filter(function (child) {
|
|
128
|
+
return (
|
|
129
|
+
!isHMRScript(child) &&
|
|
130
|
+
!(
|
|
131
|
+
child.nodeType === Node.ELEMENT_NODE &&
|
|
132
|
+
(child as Element).tagName === 'SCRIPT'
|
|
133
|
+
)
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const { oldMap } = matchChildren(
|
|
138
|
+
oldChildrenFiltered,
|
|
139
|
+
newChildrenFiltered
|
|
140
|
+
);
|
|
141
|
+
const matchedOld = new Set<Node>();
|
|
142
|
+
|
|
143
|
+
newChildrenFiltered.forEach(function (newChild, newIndex) {
|
|
144
|
+
const newKey = getElementKey(newChild, newIndex);
|
|
145
|
+
const oldMatches = oldMap.get(newKey) || [];
|
|
146
|
+
|
|
147
|
+
if (oldMatches.length > 0) {
|
|
148
|
+
let bestMatch: KeyedEntry | null = null;
|
|
149
|
+
for (let idx = 0; idx < oldMatches.length; idx++) {
|
|
150
|
+
if (!matchedOld.has(oldMatches[idx]!.node)) {
|
|
151
|
+
bestMatch = oldMatches[idx]!;
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (!bestMatch && oldMatches.length > 0) {
|
|
156
|
+
bestMatch = oldMatches[0]!;
|
|
157
|
+
}
|
|
158
|
+
if (bestMatch && !matchedOld.has(bestMatch.node)) {
|
|
159
|
+
matchedOld.add(bestMatch.node);
|
|
160
|
+
patchNode(bestMatch.node, newChild);
|
|
161
|
+
} else if (oldMatches.length > 0) {
|
|
162
|
+
const clone = newChild.cloneNode(true);
|
|
163
|
+
oldNode.insertBefore(
|
|
164
|
+
clone,
|
|
165
|
+
oldChildrenFiltered[newIndex] || null
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
} else {
|
|
169
|
+
const clone = newChild.cloneNode(true);
|
|
170
|
+
oldNode.insertBefore(
|
|
171
|
+
clone,
|
|
172
|
+
oldChildrenFiltered[newIndex] || null
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
oldChildrenFiltered.forEach(function (oldChild) {
|
|
178
|
+
if (!matchedOld.has(oldChild) && !isHMRPreserved(oldChild)) {
|
|
179
|
+
oldChild.remove();
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
export const patchDOMInPlace = (oldContainer: HTMLElement, newHTML: string) => {
|
|
186
|
+
const tempDiv = document.createElement('div');
|
|
187
|
+
tempDiv.innerHTML = newHTML;
|
|
188
|
+
const newContainer = tempDiv;
|
|
189
|
+
|
|
190
|
+
const oldChildren = Array.from(oldContainer.childNodes);
|
|
191
|
+
const newChildren = Array.from(newContainer.childNodes);
|
|
192
|
+
|
|
193
|
+
const oldChildrenFiltered = oldChildren.filter(function (child) {
|
|
194
|
+
return !(
|
|
195
|
+
child.nodeType === Node.ELEMENT_NODE &&
|
|
196
|
+
(child as Element).tagName === 'SCRIPT' &&
|
|
197
|
+
!(child as Element).hasAttribute('data-hmr-client')
|
|
198
|
+
);
|
|
199
|
+
});
|
|
200
|
+
const newChildrenFiltered = newChildren.filter(function (child) {
|
|
201
|
+
return !(
|
|
202
|
+
child.nodeType === Node.ELEMENT_NODE &&
|
|
203
|
+
(child as Element).tagName === 'SCRIPT'
|
|
204
|
+
);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
const { oldMap } = matchChildren(oldChildrenFiltered, newChildrenFiltered);
|
|
208
|
+
const matchedOld = new Set<Node>();
|
|
209
|
+
|
|
210
|
+
newChildrenFiltered.forEach(function (newChild, newIndex) {
|
|
211
|
+
const newKey = getElementKey(newChild, newIndex);
|
|
212
|
+
const oldMatches = oldMap.get(newKey) || [];
|
|
213
|
+
|
|
214
|
+
if (oldMatches.length > 0) {
|
|
215
|
+
let bestMatch: KeyedEntry | null = null;
|
|
216
|
+
for (let idx = 0; idx < oldMatches.length; idx++) {
|
|
217
|
+
if (!matchedOld.has(oldMatches[idx]!.node)) {
|
|
218
|
+
bestMatch = oldMatches[idx]!;
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (!bestMatch && oldMatches.length > 0) {
|
|
223
|
+
bestMatch = oldMatches[0]!;
|
|
224
|
+
}
|
|
225
|
+
if (bestMatch && !matchedOld.has(bestMatch.node)) {
|
|
226
|
+
matchedOld.add(bestMatch.node);
|
|
227
|
+
patchNode(bestMatch.node, newChild);
|
|
228
|
+
} else {
|
|
229
|
+
const clone = newChild.cloneNode(true);
|
|
230
|
+
oldContainer.insertBefore(
|
|
231
|
+
clone,
|
|
232
|
+
oldChildrenFiltered[newIndex] || null
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
} else {
|
|
236
|
+
const clone = newChild.cloneNode(true);
|
|
237
|
+
oldContainer.insertBefore(
|
|
238
|
+
clone,
|
|
239
|
+
oldChildrenFiltered[newIndex] || null
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
oldChildrenFiltered.forEach(function (oldChild) {
|
|
245
|
+
if (
|
|
246
|
+
!matchedOld.has(oldChild) &&
|
|
247
|
+
!(
|
|
248
|
+
oldChild.nodeType === Node.ELEMENT_NODE &&
|
|
249
|
+
(oldChild as Element).tagName === 'SCRIPT' &&
|
|
250
|
+
(oldChild as Element).hasAttribute('data-hmr-client')
|
|
251
|
+
) &&
|
|
252
|
+
!(
|
|
253
|
+
oldChild.nodeType === Node.ELEMENT_NODE &&
|
|
254
|
+
(oldChild as Element).hasAttribute &&
|
|
255
|
+
(oldChild as Element).hasAttribute('data-hmr-overlay')
|
|
256
|
+
)
|
|
257
|
+
) {
|
|
258
|
+
oldChild.remove();
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
};
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/* DOM state snapshot/restore to preserve user-visible state across HMR */
|
|
2
|
+
|
|
3
|
+
import type { DOMStateEntry, DOMStateSnapshot } from '../../../types/client';
|
|
4
|
+
|
|
5
|
+
export const saveDOMState = (root: HTMLElement) => {
|
|
6
|
+
const snapshot: DOMStateSnapshot = { activeKey: null, items: [] };
|
|
7
|
+
const selector =
|
|
8
|
+
'input, textarea, select, option, [contenteditable="true"], details';
|
|
9
|
+
const elements = root.querySelectorAll(selector);
|
|
10
|
+
|
|
11
|
+
elements.forEach(function (el, idx) {
|
|
12
|
+
const entry: DOMStateEntry = {
|
|
13
|
+
idx,
|
|
14
|
+
tag: el.tagName.toLowerCase()
|
|
15
|
+
};
|
|
16
|
+
const id = el.getAttribute('id');
|
|
17
|
+
const name = el.getAttribute('name');
|
|
18
|
+
if (id) entry.id = id;
|
|
19
|
+
else if (name) entry.name = name;
|
|
20
|
+
|
|
21
|
+
if (el.tagName === 'INPUT') {
|
|
22
|
+
const input = el as HTMLInputElement;
|
|
23
|
+
const type = input.getAttribute('type') || 'text';
|
|
24
|
+
entry.type = type;
|
|
25
|
+
if (type === 'checkbox' || type === 'radio') {
|
|
26
|
+
entry.checked = input.checked;
|
|
27
|
+
} else {
|
|
28
|
+
entry.value = input.value;
|
|
29
|
+
}
|
|
30
|
+
if (input.selectionStart !== null && input.selectionEnd !== null) {
|
|
31
|
+
entry.selStart = input.selectionStart;
|
|
32
|
+
entry.selEnd = input.selectionEnd;
|
|
33
|
+
}
|
|
34
|
+
} else if (el.tagName === 'TEXTAREA') {
|
|
35
|
+
const textarea = el as HTMLTextAreaElement;
|
|
36
|
+
entry.value = textarea.value;
|
|
37
|
+
if (
|
|
38
|
+
textarea.selectionStart !== null &&
|
|
39
|
+
textarea.selectionEnd !== null
|
|
40
|
+
) {
|
|
41
|
+
entry.selStart = textarea.selectionStart;
|
|
42
|
+
entry.selEnd = textarea.selectionEnd;
|
|
43
|
+
}
|
|
44
|
+
} else if (el.tagName === 'SELECT') {
|
|
45
|
+
const select = el as HTMLSelectElement;
|
|
46
|
+
const vals: string[] = [];
|
|
47
|
+
Array.from(select.options).forEach(function (opt) {
|
|
48
|
+
if (opt.selected) vals.push(opt.value);
|
|
49
|
+
});
|
|
50
|
+
entry.values = vals;
|
|
51
|
+
} else if (el.tagName === 'OPTION') {
|
|
52
|
+
entry.selected = (el as HTMLOptionElement).selected;
|
|
53
|
+
} else if (el.tagName === 'DETAILS') {
|
|
54
|
+
entry.open = (el as HTMLDetailsElement).open;
|
|
55
|
+
} else if (el.getAttribute('contenteditable') === 'true') {
|
|
56
|
+
entry.text = el.textContent || undefined;
|
|
57
|
+
}
|
|
58
|
+
snapshot.items.push(entry);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const active = document.activeElement;
|
|
62
|
+
if (active && root.contains(active)) {
|
|
63
|
+
const id = active.getAttribute('id');
|
|
64
|
+
const name = active.getAttribute('name');
|
|
65
|
+
if (id) snapshot.activeKey = 'id:' + id;
|
|
66
|
+
else if (name) snapshot.activeKey = 'name:' + name;
|
|
67
|
+
else
|
|
68
|
+
snapshot.activeKey =
|
|
69
|
+
'idx:' + Array.prototype.indexOf.call(elements, active);
|
|
70
|
+
}
|
|
71
|
+
return snapshot;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const restoreDOMState = (
|
|
75
|
+
root: HTMLElement,
|
|
76
|
+
snapshot: DOMStateSnapshot
|
|
77
|
+
) => {
|
|
78
|
+
if (!snapshot || !snapshot.items) return;
|
|
79
|
+
const selector =
|
|
80
|
+
'input, textarea, select, option, [contenteditable="true"], details';
|
|
81
|
+
const elements = root.querySelectorAll(selector);
|
|
82
|
+
|
|
83
|
+
snapshot.items.forEach(function (entry) {
|
|
84
|
+
let target: Element | null = null;
|
|
85
|
+
if (entry.id) {
|
|
86
|
+
target = root.querySelector('#' + CSS.escape(entry.id));
|
|
87
|
+
}
|
|
88
|
+
if (!target && entry.name) {
|
|
89
|
+
target = root.querySelector(
|
|
90
|
+
'[name="' + CSS.escape(entry.name) + '"]'
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
if (!target && elements[entry.idx]) {
|
|
94
|
+
target = elements[entry.idx]!;
|
|
95
|
+
}
|
|
96
|
+
if (!target) return;
|
|
97
|
+
|
|
98
|
+
if (target.tagName === 'INPUT') {
|
|
99
|
+
const input = target as HTMLInputElement;
|
|
100
|
+
const type = entry.type || input.getAttribute('type') || 'text';
|
|
101
|
+
if (type === 'checkbox' || type === 'radio') {
|
|
102
|
+
if (entry.checked !== undefined) input.checked = entry.checked;
|
|
103
|
+
} else if (entry.value !== undefined) {
|
|
104
|
+
input.value = entry.value;
|
|
105
|
+
}
|
|
106
|
+
if (
|
|
107
|
+
entry.selStart !== undefined &&
|
|
108
|
+
entry.selEnd !== undefined &&
|
|
109
|
+
input.setSelectionRange
|
|
110
|
+
) {
|
|
111
|
+
try {
|
|
112
|
+
input.setSelectionRange(entry.selStart, entry.selEnd);
|
|
113
|
+
} catch {
|
|
114
|
+
/* ignore */
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
} else if (target.tagName === 'TEXTAREA') {
|
|
118
|
+
const textarea = target as HTMLTextAreaElement;
|
|
119
|
+
if (entry.value !== undefined) textarea.value = entry.value;
|
|
120
|
+
if (
|
|
121
|
+
entry.selStart !== undefined &&
|
|
122
|
+
entry.selEnd !== undefined &&
|
|
123
|
+
textarea.setSelectionRange
|
|
124
|
+
) {
|
|
125
|
+
try {
|
|
126
|
+
textarea.setSelectionRange(entry.selStart, entry.selEnd);
|
|
127
|
+
} catch {
|
|
128
|
+
/* ignore */
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
} else if (target.tagName === 'SELECT') {
|
|
132
|
+
if (Array.isArray(entry.values)) {
|
|
133
|
+
const select = target as HTMLSelectElement;
|
|
134
|
+
Array.from(select.options).forEach(function (opt) {
|
|
135
|
+
opt.selected = entry.values!.indexOf(opt.value) !== -1;
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
} else if (target.tagName === 'OPTION') {
|
|
139
|
+
if (entry.selected !== undefined)
|
|
140
|
+
(target as HTMLOptionElement).selected = entry.selected;
|
|
141
|
+
} else if (target.tagName === 'DETAILS') {
|
|
142
|
+
if (entry.open !== undefined)
|
|
143
|
+
(target as HTMLDetailsElement).open = entry.open;
|
|
144
|
+
} else if (target.getAttribute('contenteditable') === 'true') {
|
|
145
|
+
if (entry.text !== undefined) target.textContent = entry.text;
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
if (snapshot.activeKey) {
|
|
150
|
+
let focusEl: Element | null = null;
|
|
151
|
+
if (snapshot.activeKey.startsWith('id:')) {
|
|
152
|
+
focusEl = root.querySelector(
|
|
153
|
+
'#' + CSS.escape(snapshot.activeKey.slice(3))
|
|
154
|
+
);
|
|
155
|
+
} else if (snapshot.activeKey.startsWith('name:')) {
|
|
156
|
+
focusEl = root.querySelector(
|
|
157
|
+
'[name="' + CSS.escape(snapshot.activeKey.slice(5)) + '"]'
|
|
158
|
+
);
|
|
159
|
+
} else if (snapshot.activeKey.startsWith('idx:')) {
|
|
160
|
+
const idx = parseInt(snapshot.activeKey.slice(4), 10);
|
|
161
|
+
if (!isNaN(idx) && elements[idx]) focusEl = elements[idx]!;
|
|
162
|
+
}
|
|
163
|
+
if (focusEl && (focusEl as HTMLElement).focus) {
|
|
164
|
+
(focusEl as HTMLElement).focus();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
export const saveFormState = () => {
|
|
170
|
+
const formState: Record<string, Record<string, boolean | string>> = {};
|
|
171
|
+
const forms = document.querySelectorAll('form');
|
|
172
|
+
forms.forEach(function (form, formIndex) {
|
|
173
|
+
const formId = form.id || 'form-' + formIndex;
|
|
174
|
+
formState[formId] = {};
|
|
175
|
+
const inputs = form.querySelectorAll('input, textarea, select');
|
|
176
|
+
inputs.forEach(function (input) {
|
|
177
|
+
const element = input as HTMLInputElement;
|
|
178
|
+
const name =
|
|
179
|
+
element.name ||
|
|
180
|
+
element.id ||
|
|
181
|
+
'input-' + formIndex + '-' + inputs.length;
|
|
182
|
+
if (element.type === 'checkbox' || element.type === 'radio') {
|
|
183
|
+
formState[formId]![name] = element.checked;
|
|
184
|
+
} else {
|
|
185
|
+
formState[formId]![name] = element.value;
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const standaloneInputs = document.querySelectorAll(
|
|
191
|
+
'input:not(form input), textarea:not(form textarea), select:not(form select)'
|
|
192
|
+
);
|
|
193
|
+
if (standaloneInputs.length > 0) {
|
|
194
|
+
formState['__standalone__'] = {};
|
|
195
|
+
standaloneInputs.forEach(function (input) {
|
|
196
|
+
const element = input as HTMLInputElement;
|
|
197
|
+
const name =
|
|
198
|
+
element.name ||
|
|
199
|
+
element.id ||
|
|
200
|
+
'standalone-' + standaloneInputs.length;
|
|
201
|
+
if (element.type === 'checkbox' || element.type === 'radio') {
|
|
202
|
+
formState['__standalone__']![name] = element.checked;
|
|
203
|
+
} else {
|
|
204
|
+
formState['__standalone__']![name] = element.value;
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
return formState;
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
export const restoreFormState = (
|
|
212
|
+
formState: Record<string, Record<string, boolean | string>>
|
|
213
|
+
) => {
|
|
214
|
+
Object.keys(formState).forEach(function (formId) {
|
|
215
|
+
const isStandalone = formId === '__standalone__';
|
|
216
|
+
const form = isStandalone
|
|
217
|
+
? null
|
|
218
|
+
: document.getElementById(formId) ||
|
|
219
|
+
document.querySelector(
|
|
220
|
+
'form:nth-of-type(' +
|
|
221
|
+
(parseInt(formId.replace('form-', '')) + 1) +
|
|
222
|
+
')'
|
|
223
|
+
);
|
|
224
|
+
Object.keys(formState[formId]!).forEach(function (name) {
|
|
225
|
+
let element: HTMLInputElement | null = null;
|
|
226
|
+
if (isStandalone) {
|
|
227
|
+
element = document.querySelector(
|
|
228
|
+
'input[name="' +
|
|
229
|
+
name +
|
|
230
|
+
'"], textarea[name="' +
|
|
231
|
+
name +
|
|
232
|
+
'"], select[name="' +
|
|
233
|
+
name +
|
|
234
|
+
'"]'
|
|
235
|
+
);
|
|
236
|
+
if (!element) {
|
|
237
|
+
element = document.getElementById(
|
|
238
|
+
name
|
|
239
|
+
) as HTMLInputElement | null;
|
|
240
|
+
}
|
|
241
|
+
} else if (form) {
|
|
242
|
+
element = form.querySelector('[name="' + name + '"], #' + name);
|
|
243
|
+
}
|
|
244
|
+
if (element) {
|
|
245
|
+
const value = formState[formId]![name]!;
|
|
246
|
+
if (element.type === 'checkbox' || element.type === 'radio') {
|
|
247
|
+
element.checked = value === true;
|
|
248
|
+
} else {
|
|
249
|
+
element.value = String(value);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
export const saveScrollState = () => {
|
|
257
|
+
return {
|
|
258
|
+
window: {
|
|
259
|
+
x: window.scrollX || window.pageXOffset,
|
|
260
|
+
y: window.scrollY || window.pageYOffset
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
export const restoreScrollState = (scrollState: {
|
|
266
|
+
window: { x: number; y: number };
|
|
267
|
+
}) => {
|
|
268
|
+
if (scrollState && scrollState.window) {
|
|
269
|
+
window.scrollTo(scrollState.window.x, scrollState.window.y);
|
|
270
|
+
}
|
|
271
|
+
};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/* AbsoluteJS Error Overlay - branded, per-framework, modern styling */
|
|
2
|
+
|
|
3
|
+
import type { ErrorOverlayOptions } from '../../../types/client';
|
|
4
|
+
|
|
5
|
+
let errorOverlayElement: HTMLDivElement | null = null;
|
|
6
|
+
|
|
7
|
+
const frameworkLabels: Record<string, string> = {
|
|
8
|
+
angular: 'Angular',
|
|
9
|
+
assets: 'Assets',
|
|
10
|
+
html: 'HTML',
|
|
11
|
+
htmx: 'HTMX',
|
|
12
|
+
react: 'React',
|
|
13
|
+
svelte: 'Svelte',
|
|
14
|
+
unknown: 'Unknown',
|
|
15
|
+
vue: 'Vue'
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const frameworkColors: Record<string, string> = {
|
|
19
|
+
angular: '#dd0031',
|
|
20
|
+
assets: '#563d7c',
|
|
21
|
+
html: '#e34c26',
|
|
22
|
+
htmx: '#1a365d',
|
|
23
|
+
react: '#61dafb',
|
|
24
|
+
svelte: '#ff3e00',
|
|
25
|
+
unknown: '#94a3b8',
|
|
26
|
+
vue: '#42b883'
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const hideErrorOverlay = () => {
|
|
30
|
+
if (errorOverlayElement && errorOverlayElement.parentNode) {
|
|
31
|
+
errorOverlayElement.parentNode.removeChild(errorOverlayElement);
|
|
32
|
+
errorOverlayElement = null;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const showErrorOverlay = (opts: ErrorOverlayOptions) => {
|
|
37
|
+
const message = opts.message || 'Build failed';
|
|
38
|
+
const file = opts.file;
|
|
39
|
+
const line = opts.line;
|
|
40
|
+
const column = opts.column;
|
|
41
|
+
const lineText = opts.lineText;
|
|
42
|
+
const framework = (opts.framework || 'unknown').toLowerCase();
|
|
43
|
+
const frameworkLabel = frameworkLabels[framework] || framework;
|
|
44
|
+
const accent = frameworkColors[framework] || '#94a3b8';
|
|
45
|
+
|
|
46
|
+
hideErrorOverlay();
|
|
47
|
+
|
|
48
|
+
const overlay = document.createElement('div');
|
|
49
|
+
overlay.id = 'absolutejs-error-overlay';
|
|
50
|
+
overlay.setAttribute('data-hmr-overlay', 'true');
|
|
51
|
+
overlay.style.cssText =
|
|
52
|
+
'position:fixed;inset:0;z-index:2147483647;background:linear-gradient(135deg,rgba(15,23,42,0.98) 0%,rgba(30,41,59,0.98) 100%);backdrop-filter:blur(12px);color:#e2e8f0;font-family:"JetBrains Mono","Fira Code",ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:14px;line-height:1.6;overflow:auto;padding:32px;box-sizing:border-box;display:flex;align-items:flex-start;justify-content:center;';
|
|
53
|
+
|
|
54
|
+
const card = document.createElement('div');
|
|
55
|
+
card.style.cssText =
|
|
56
|
+
'max-width:720px;width:100%;background:rgba(30,41,59,0.6);border:1px solid rgba(71,85,105,0.5);border-radius:16px;box-shadow:0 25px 50px -12px rgba(0,0,0,0.5),0 0 0 1px rgba(255,255,255,0.05);overflow:hidden;';
|
|
57
|
+
|
|
58
|
+
const header = document.createElement('div');
|
|
59
|
+
header.style.cssText =
|
|
60
|
+
'display:flex;align-items:center;justify-content:space-between;gap:16px;padding:20px 24px;background:rgba(15,23,42,0.5);border-bottom:1px solid rgba(71,85,105,0.4);';
|
|
61
|
+
header.innerHTML =
|
|
62
|
+
'<div style="display:flex;align-items:center;gap:12px;"><span style="font-weight:700;font-size:20px;color:#fff;letter-spacing:-0.02em;">AbsoluteJS</span><span style="padding:5px 10px;border-radius:8px;font-size:12px;font-weight:600;background:' +
|
|
63
|
+
accent +
|
|
64
|
+
';color:#fff;opacity:0.95;box-shadow:0 2px 4px rgba(0,0,0,0.2);">' +
|
|
65
|
+
frameworkLabel +
|
|
66
|
+
'</span></div><span style="color:#94a3b8;font-size:13px;font-weight:500;">Compilation Error</span>';
|
|
67
|
+
card.appendChild(header);
|
|
68
|
+
|
|
69
|
+
const content = document.createElement('div');
|
|
70
|
+
content.style.cssText = 'padding:24px;';
|
|
71
|
+
|
|
72
|
+
const errorSection = document.createElement('div');
|
|
73
|
+
errorSection.style.cssText = 'margin-bottom:20px;';
|
|
74
|
+
|
|
75
|
+
const errorLabel = document.createElement('div');
|
|
76
|
+
errorLabel.style.cssText =
|
|
77
|
+
'font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:0.08em;color:#94a3b8;margin-bottom:8px;';
|
|
78
|
+
errorLabel.textContent = 'What went wrong';
|
|
79
|
+
errorSection.appendChild(errorLabel);
|
|
80
|
+
|
|
81
|
+
const msgEl = document.createElement('pre');
|
|
82
|
+
msgEl.style.cssText =
|
|
83
|
+
'margin:0;padding:16px 20px;background:rgba(239,68,68,0.12);border:1px solid rgba(239,68,68,0.25);border-radius:10px;overflow-x:auto;white-space:pre-wrap;word-break:break-word;color:#fca5a5;font-size:13px;line-height:1.5;';
|
|
84
|
+
msgEl.textContent = message;
|
|
85
|
+
errorSection.appendChild(msgEl);
|
|
86
|
+
content.appendChild(errorSection);
|
|
87
|
+
|
|
88
|
+
if (file || line != null || column != null || lineText) {
|
|
89
|
+
const locSection = document.createElement('div');
|
|
90
|
+
locSection.style.cssText = 'margin-bottom:20px;';
|
|
91
|
+
|
|
92
|
+
const locLabel = document.createElement('div');
|
|
93
|
+
locLabel.style.cssText =
|
|
94
|
+
'font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:0.08em;color:#94a3b8;margin-bottom:8px;';
|
|
95
|
+
locLabel.textContent = 'Where';
|
|
96
|
+
locSection.appendChild(locLabel);
|
|
97
|
+
|
|
98
|
+
const locParts: string[] = [];
|
|
99
|
+
if (file) locParts.push(file);
|
|
100
|
+
if (line != null) locParts.push(String(line));
|
|
101
|
+
if (column != null) locParts.push(String(column));
|
|
102
|
+
const loc = locParts.join(':') || 'Unknown location';
|
|
103
|
+
|
|
104
|
+
const locEl = document.createElement('div');
|
|
105
|
+
locEl.style.cssText =
|
|
106
|
+
'padding:12px 20px;background:rgba(71,85,105,0.3);border-radius:10px;border:1px solid rgba(71,85,105,0.4);color:#cbd5e1;font-size:13px;';
|
|
107
|
+
locEl.textContent = loc;
|
|
108
|
+
locSection.appendChild(locEl);
|
|
109
|
+
|
|
110
|
+
if (lineText) {
|
|
111
|
+
const codeBlock = document.createElement('pre');
|
|
112
|
+
codeBlock.style.cssText =
|
|
113
|
+
'margin:8px 0 0;padding:14px 20px;background:rgba(15,23,42,0.8);border-radius:10px;border:1px solid rgba(71,85,105,0.4);color:#94a3b8;font-size:13px;overflow-x:auto;white-space:pre;';
|
|
114
|
+
codeBlock.textContent = lineText;
|
|
115
|
+
locSection.appendChild(codeBlock);
|
|
116
|
+
}
|
|
117
|
+
content.appendChild(locSection);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const footer = document.createElement('div');
|
|
121
|
+
footer.style.cssText =
|
|
122
|
+
'display:flex;justify-content:flex-end;padding-top:8px;';
|
|
123
|
+
|
|
124
|
+
const dismiss = document.createElement('button');
|
|
125
|
+
dismiss.textContent = 'Dismiss';
|
|
126
|
+
dismiss.style.cssText =
|
|
127
|
+
'padding:10px 20px;background:' +
|
|
128
|
+
accent +
|
|
129
|
+
';color:#fff;border:none;border-radius:10px;font-size:13px;font-weight:600;cursor:pointer;box-shadow:0 2px 8px rgba(0,0,0,0.2);transition:opacity 0.15s,transform 0.15s;';
|
|
130
|
+
dismiss.onmouseover = function () {
|
|
131
|
+
dismiss.style.opacity = '0.9';
|
|
132
|
+
dismiss.style.transform = 'translateY(-1px)';
|
|
133
|
+
};
|
|
134
|
+
dismiss.onmouseout = function () {
|
|
135
|
+
dismiss.style.opacity = '1';
|
|
136
|
+
dismiss.style.transform = 'translateY(0)';
|
|
137
|
+
};
|
|
138
|
+
dismiss.onclick = hideErrorOverlay;
|
|
139
|
+
footer.appendChild(dismiss);
|
|
140
|
+
content.appendChild(footer);
|
|
141
|
+
card.appendChild(content);
|
|
142
|
+
overlay.appendChild(card);
|
|
143
|
+
document.body.appendChild(overlay);
|
|
144
|
+
errorOverlayElement = overlay;
|
|
145
|
+
};
|