@preprio/toolkit 0.5.0-rc.4 → 0.5.1-dom.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 +67 -2
- package/dist/astro/index.cjs +1 -1
- package/dist/astro/index.js +1 -1
- package/dist/auto.cjs +4572 -0
- package/dist/auto.d.cts +42 -0
- package/dist/auto.d.ts +42 -0
- package/dist/auto.js +105 -0
- package/dist/browser/auto.js +117 -0
- package/dist/chunk-5I5TQ3NO.js +74 -0
- package/dist/chunk-AZC4BZJI.js +447 -0
- package/dist/{chunk-TG6N3C22.js → chunk-IKOOXTPM.js} +1 -1
- package/dist/{chunk-AHBN6FQP.js → chunk-IOSO232M.js} +4 -2
- package/dist/{chunk-PDNCNTDB.js → chunk-RRM5762S.js} +2 -71
- package/dist/chunk-SZTHSJUA.js +326 -0
- package/dist/{chunk-46ISDRHT.js → chunk-TEB7M7EM.js} +0 -4
- package/dist/chunk-UK664KSV.js +6 -0
- package/dist/{chunk-74KY5EAM.js → chunk-ZFGVP2GW.js} +28 -447
- package/dist/html-live-DzmEG9wU.d.cts +42 -0
- package/dist/html-live-DzmEG9wU.d.ts +42 -0
- package/dist/index.cjs +177 -2
- package/dist/index.d.cts +31 -28
- package/dist/index.d.ts +31 -28
- package/dist/index.js +21 -154
- package/dist/{live-preview-H4643QYF.js → live-preview-IYAS7PYV.js} +3 -2
- package/dist/nextjs/components.cjs +1 -1
- package/dist/nextjs/components.d.cts +2 -1
- package/dist/nextjs/components.d.ts +2 -1
- package/dist/nextjs/components.js +6 -3
- package/dist/nextjs/index.cjs +1 -1
- package/dist/nextjs/index.js +1 -1
- package/dist/nextjs/live-action.js +5 -3
- package/dist/nextjs/live.js +1 -1
- package/dist/nuxt/index.cjs +1 -1
- package/dist/nuxt/index.js +1 -1
- package/dist/pixel-DwW7cXJm.d.cts +49 -0
- package/dist/pixel-DwW7cXJm.d.ts +49 -0
- package/dist/react/components.cjs +1 -1
- package/dist/react/components.js +1 -1
- package/dist/sveltekit/index.cjs +1 -1
- package/dist/sveltekit/index.js +1 -1
- package/dist/{pixel-CX422dCO.d.cts → types-DtMa_-f2.d.cts} +1 -49
- package/dist/{pixel-CX422dCO.d.ts → types-DtMa_-f2.d.ts} +1 -49
- package/package.json +12 -2
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import {
|
|
2
|
+
HEADER_LIVE_RENDER
|
|
3
|
+
} from "./chunk-RRM5762S.js";
|
|
4
|
+
import {
|
|
5
|
+
createLiveBridge,
|
|
6
|
+
createLiveStore
|
|
7
|
+
} from "./chunk-IKOOXTPM.js";
|
|
8
|
+
import {
|
|
9
|
+
createScopedLogger
|
|
10
|
+
} from "./chunk-S3VT4UWM.js";
|
|
11
|
+
|
|
12
|
+
// src/core/live-preview/dom-patch.ts
|
|
13
|
+
var INDEX_SEGMENT = /^\d+$/;
|
|
14
|
+
var LIVE_ATTRIBUTE = "data-prepr-live";
|
|
15
|
+
function fieldKey(path) {
|
|
16
|
+
return path.split(".").filter((segment) => segment && segment !== "items").map(
|
|
17
|
+
(segment) => INDEX_SEGMENT.test(segment) ? String(Number.parseInt(segment, 10)) : segment
|
|
18
|
+
).join(".");
|
|
19
|
+
}
|
|
20
|
+
function patchTargets(patch) {
|
|
21
|
+
if (patch.type !== "field") return [];
|
|
22
|
+
const targets = [{ item: patch.itemId, field: patch.field }];
|
|
23
|
+
if (patch.edit) targets.unshift(patch.edit);
|
|
24
|
+
return targets;
|
|
25
|
+
}
|
|
26
|
+
function matchingElements(candidates, item, field) {
|
|
27
|
+
const wanted = fieldKey(field);
|
|
28
|
+
return candidates.filter((element) => {
|
|
29
|
+
if (element.getAttribute("data-prepr-id") !== item) return false;
|
|
30
|
+
const tagged = element.getAttribute("data-prepr-field");
|
|
31
|
+
return tagged !== null && fieldKey(tagged) === wanted;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
var DROPPED_ELEMENTS = /* @__PURE__ */ new Set([
|
|
35
|
+
"SCRIPT",
|
|
36
|
+
"STYLE",
|
|
37
|
+
"IFRAME",
|
|
38
|
+
"OBJECT",
|
|
39
|
+
"EMBED",
|
|
40
|
+
"TEMPLATE",
|
|
41
|
+
"NOSCRIPT",
|
|
42
|
+
"SVG",
|
|
43
|
+
"MATH",
|
|
44
|
+
"LINK",
|
|
45
|
+
"META",
|
|
46
|
+
"BASE",
|
|
47
|
+
"FORM",
|
|
48
|
+
"INPUT",
|
|
49
|
+
"BUTTON",
|
|
50
|
+
"TEXTAREA",
|
|
51
|
+
"SELECT"
|
|
52
|
+
]);
|
|
53
|
+
var ALLOWED_ELEMENTS = /* @__PURE__ */ new Set([
|
|
54
|
+
"A",
|
|
55
|
+
"ABBR",
|
|
56
|
+
"B",
|
|
57
|
+
"BLOCKQUOTE",
|
|
58
|
+
"BR",
|
|
59
|
+
"CODE",
|
|
60
|
+
"DEL",
|
|
61
|
+
"DIV",
|
|
62
|
+
"EM",
|
|
63
|
+
"H1",
|
|
64
|
+
"H2",
|
|
65
|
+
"H3",
|
|
66
|
+
"H4",
|
|
67
|
+
"H5",
|
|
68
|
+
"H6",
|
|
69
|
+
"HR",
|
|
70
|
+
"I",
|
|
71
|
+
"INS",
|
|
72
|
+
"LI",
|
|
73
|
+
"MARK",
|
|
74
|
+
"OL",
|
|
75
|
+
"P",
|
|
76
|
+
"PRE",
|
|
77
|
+
"S",
|
|
78
|
+
"SMALL",
|
|
79
|
+
"SPAN",
|
|
80
|
+
"STRONG",
|
|
81
|
+
"SUB",
|
|
82
|
+
"SUP",
|
|
83
|
+
"TABLE",
|
|
84
|
+
"TBODY",
|
|
85
|
+
"TD",
|
|
86
|
+
"TH",
|
|
87
|
+
"THEAD",
|
|
88
|
+
"TR",
|
|
89
|
+
"U",
|
|
90
|
+
"UL"
|
|
91
|
+
]);
|
|
92
|
+
var SAFE_HREF = /^(?:https?:|mailto:|tel:|\/|#|\.{0,2}\/|[^:]*$)/i;
|
|
93
|
+
function sanitizeRichText(html) {
|
|
94
|
+
const template = document.createElement("template");
|
|
95
|
+
template.innerHTML = html;
|
|
96
|
+
const out = document.createDocumentFragment();
|
|
97
|
+
const copy = (source, target) => {
|
|
98
|
+
for (const child of Array.from(source.childNodes)) {
|
|
99
|
+
if (child.nodeType === Node.TEXT_NODE) {
|
|
100
|
+
target.appendChild(document.createTextNode(child.textContent ?? ""));
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (child.nodeType !== Node.ELEMENT_NODE) continue;
|
|
104
|
+
const element = child;
|
|
105
|
+
const tag = element.tagName.toUpperCase();
|
|
106
|
+
if (DROPPED_ELEMENTS.has(tag)) continue;
|
|
107
|
+
if (!ALLOWED_ELEMENTS.has(tag)) {
|
|
108
|
+
copy(element, target);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
const clean = document.createElement(tag.toLowerCase());
|
|
112
|
+
if (tag === "A") {
|
|
113
|
+
const href = element.getAttribute("href")?.trim();
|
|
114
|
+
if (href && SAFE_HREF.test(href.replace(/[\t\n\r]/g, ""))) {
|
|
115
|
+
clean.setAttribute("href", href);
|
|
116
|
+
}
|
|
117
|
+
if (element.getAttribute("target") === "_blank") {
|
|
118
|
+
clean.setAttribute("target", "_blank");
|
|
119
|
+
clean.setAttribute("rel", "noopener noreferrer");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
copy(element, clean);
|
|
123
|
+
target.appendChild(clean);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
copy(template.content, out);
|
|
127
|
+
return out;
|
|
128
|
+
}
|
|
129
|
+
var RICH_TEXT = /^\s*<[a-z!/]/i;
|
|
130
|
+
function writeText(element, text) {
|
|
131
|
+
if (!element.children.length) {
|
|
132
|
+
if (element.textContent !== text) element.textContent = text;
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
for (const node of Array.from(element.childNodes)) {
|
|
136
|
+
const current = node.textContent ?? "";
|
|
137
|
+
if (node.nodeType !== Node.TEXT_NODE || !current.trim()) continue;
|
|
138
|
+
const next = current.match(/^\s*/)[0] + text + current.match(/\s*$/)[0];
|
|
139
|
+
if (current !== next) node.textContent = next;
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
function applyPatchLogToDom(log, root = document) {
|
|
145
|
+
const candidates = Array.from(
|
|
146
|
+
root.querySelectorAll("[data-prepr-encoded]")
|
|
147
|
+
);
|
|
148
|
+
let applied = 0;
|
|
149
|
+
let skipped = 0;
|
|
150
|
+
for (const patch of log) {
|
|
151
|
+
if (patch.type !== "field" || typeof patch.value !== "string") {
|
|
152
|
+
skipped++;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
let elements = [];
|
|
156
|
+
for (const { item, field } of patchTargets(patch)) {
|
|
157
|
+
elements = matchingElements(candidates, item, field);
|
|
158
|
+
if (elements.length) break;
|
|
159
|
+
}
|
|
160
|
+
let written = false;
|
|
161
|
+
for (const element of elements) {
|
|
162
|
+
const mode = element.closest(`[${LIVE_ATTRIBUTE}]`)?.getAttribute(LIVE_ATTRIBUTE);
|
|
163
|
+
if (mode === "off") continue;
|
|
164
|
+
if (mode === "html") {
|
|
165
|
+
element.replaceChildren(sanitizeRichText(patch.value));
|
|
166
|
+
written = true;
|
|
167
|
+
} else if (RICH_TEXT.test(patch.value)) {
|
|
168
|
+
continue;
|
|
169
|
+
} else if (writeText(element, patch.value)) {
|
|
170
|
+
written = true;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (written) applied++;
|
|
174
|
+
else skipped++;
|
|
175
|
+
}
|
|
176
|
+
return { applied, skipped };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// src/core/live-preview/morph.ts
|
|
180
|
+
function morphElement(current, next) {
|
|
181
|
+
syncAttributes(current, next);
|
|
182
|
+
morphChildren(current, next);
|
|
183
|
+
}
|
|
184
|
+
var SKIP_ATTR = "data-prepr-morph-skip";
|
|
185
|
+
var RUNTIME_ATTR_PREFIXES = ["data-prepr-"];
|
|
186
|
+
function isRuntimeAttribute(name) {
|
|
187
|
+
return RUNTIME_ATTR_PREFIXES.some((prefix) => name.startsWith(prefix));
|
|
188
|
+
}
|
|
189
|
+
function syncAttributes(current, next) {
|
|
190
|
+
for (const { name } of Array.from(current.attributes)) {
|
|
191
|
+
if (!next.hasAttribute(name) && !isRuntimeAttribute(name)) {
|
|
192
|
+
current.removeAttribute(name);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
for (const { name, value } of Array.from(next.attributes)) {
|
|
196
|
+
if (current.getAttribute(name) !== value) current.setAttribute(name, value);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function isSkipped(node) {
|
|
200
|
+
return node.nodeType === Node.ELEMENT_NODE && (node.hasAttribute(SKIP_ATTR) || node.localName === "prepr-toolbar");
|
|
201
|
+
}
|
|
202
|
+
function sameKind(a, b) {
|
|
203
|
+
if (a.nodeType !== b.nodeType) return false;
|
|
204
|
+
if (a.nodeType !== Node.ELEMENT_NODE) return true;
|
|
205
|
+
const ea = a;
|
|
206
|
+
const eb = b;
|
|
207
|
+
return ea.localName === eb.localName && ea.id === eb.id;
|
|
208
|
+
}
|
|
209
|
+
function morphChildren(current, next) {
|
|
210
|
+
const wanted = Array.from(next.childNodes).filter((n) => !isSkipped(n));
|
|
211
|
+
const existing = Array.from(current.childNodes).filter((n) => !isSkipped(n));
|
|
212
|
+
wanted.forEach((target, i) => {
|
|
213
|
+
const node = existing[i];
|
|
214
|
+
if (node && sameKind(node, target)) {
|
|
215
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
216
|
+
const element = node;
|
|
217
|
+
if (element.localName === "script") return;
|
|
218
|
+
if (element.localName === "astro-island" && !element.isEqualNode(target)) {
|
|
219
|
+
element.replaceWith(adopt(target, current));
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
morphElement(element, target);
|
|
223
|
+
} else if (node.nodeValue !== target.nodeValue) {
|
|
224
|
+
node.nodeValue = target.nodeValue;
|
|
225
|
+
}
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
const replacement = adopt(target, current);
|
|
229
|
+
if (node) node.replaceWith(replacement);
|
|
230
|
+
else current.appendChild(replacement);
|
|
231
|
+
});
|
|
232
|
+
for (const extra of existing.slice(wanted.length)) extra.remove();
|
|
233
|
+
}
|
|
234
|
+
function adopt(node, parent) {
|
|
235
|
+
const copy = parent.ownerDocument.importNode(node, true);
|
|
236
|
+
if (copy.nodeType === Node.ELEMENT_NODE) {
|
|
237
|
+
const element = copy;
|
|
238
|
+
const scripts = element.localName === "script" ? [element] : Array.from(element.querySelectorAll("script"));
|
|
239
|
+
scripts.forEach((s) => s.setAttribute("data-prepr-morphed", ""));
|
|
240
|
+
}
|
|
241
|
+
return copy;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// src/core/live-preview/html-live.ts
|
|
245
|
+
var debug = createScopedLogger("live-preview:html");
|
|
246
|
+
var COMPLETE_DOCUMENT = /<\/html>\s*$/i;
|
|
247
|
+
var SYNC_DEBOUNCE_MS = 150;
|
|
248
|
+
function startHtmlLivePreview(options = {}) {
|
|
249
|
+
if (typeof window === "undefined" || window.self === window.top) {
|
|
250
|
+
return { stop() {
|
|
251
|
+
} };
|
|
252
|
+
}
|
|
253
|
+
const store = createLiveStore();
|
|
254
|
+
const bridge = createLiveBridge(store, {
|
|
255
|
+
allowedEditorOrigins: () => options.allowedEditorOrigins,
|
|
256
|
+
strictMode: () => options.strictMode
|
|
257
|
+
});
|
|
258
|
+
bridge.start();
|
|
259
|
+
let timer = null;
|
|
260
|
+
let inFlight = false;
|
|
261
|
+
let pending = null;
|
|
262
|
+
let stopped = false;
|
|
263
|
+
const render = async (log) => {
|
|
264
|
+
inFlight = true;
|
|
265
|
+
try {
|
|
266
|
+
const response = await fetch(window.location.href, {
|
|
267
|
+
method: log.length ? "POST" : "GET",
|
|
268
|
+
headers: log.length ? { "Content-Type": "application/json", [HEADER_LIVE_RENDER]: "1" } : {},
|
|
269
|
+
body: log.length ? JSON.stringify({ log }) : void 0,
|
|
270
|
+
credentials: "same-origin",
|
|
271
|
+
cache: "no-store"
|
|
272
|
+
});
|
|
273
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
274
|
+
const html = await response.text();
|
|
275
|
+
if (stopped) return;
|
|
276
|
+
if (!COMPLETE_DOCUMENT.test(html)) {
|
|
277
|
+
throw new Error("incomplete page; a component failed to render");
|
|
278
|
+
}
|
|
279
|
+
const next = new DOMParser().parseFromString(html, "text/html");
|
|
280
|
+
if (next.title && next.title !== document.title)
|
|
281
|
+
document.title = next.title;
|
|
282
|
+
morphElement(document.body, next.body);
|
|
283
|
+
} catch (error) {
|
|
284
|
+
debug.log("re-render failed", error);
|
|
285
|
+
} finally {
|
|
286
|
+
inFlight = false;
|
|
287
|
+
}
|
|
288
|
+
if (pending && !stopped) {
|
|
289
|
+
const queued = pending;
|
|
290
|
+
pending = null;
|
|
291
|
+
void render(queued);
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
const schedule = (log) => {
|
|
295
|
+
if (timer) clearTimeout(timer);
|
|
296
|
+
timer = setTimeout(() => {
|
|
297
|
+
timer = null;
|
|
298
|
+
if (inFlight) pending = log;
|
|
299
|
+
else void render(log);
|
|
300
|
+
}, SYNC_DEBOUNCE_MS);
|
|
301
|
+
};
|
|
302
|
+
const apply = options.apply ?? "server";
|
|
303
|
+
const offLog = store.onLogChange((log) => {
|
|
304
|
+
if (!log.length) return;
|
|
305
|
+
if (apply === "server") {
|
|
306
|
+
schedule(log);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
applyPatchLogToDom(log);
|
|
310
|
+
});
|
|
311
|
+
const offInvalidate = store.onInvalidate(() => schedule([]));
|
|
312
|
+
return {
|
|
313
|
+
stop() {
|
|
314
|
+
stopped = true;
|
|
315
|
+
if (timer) clearTimeout(timer);
|
|
316
|
+
offLog();
|
|
317
|
+
offInvalidate();
|
|
318
|
+
bridge.stop();
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export {
|
|
324
|
+
applyPatchLogToDom,
|
|
325
|
+
startHtmlLivePreview
|
|
326
|
+
};
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
// src/version.ts
|
|
2
|
-
var VERSION = "0.5.0-rc.4";
|
|
3
|
-
|
|
4
1
|
// src/core/features.ts
|
|
5
2
|
function isEnabled(config) {
|
|
6
3
|
if (typeof config === "boolean") return config;
|
|
@@ -20,7 +17,6 @@ var ALL_FEATURES_ENABLED = Object.freeze({
|
|
|
20
17
|
});
|
|
21
18
|
|
|
22
19
|
export {
|
|
23
|
-
VERSION,
|
|
24
20
|
resolveFeatures,
|
|
25
21
|
ALL_FEATURES_ENABLED
|
|
26
22
|
};
|