kerfjs 0.16.0 → 1.0.2

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.
@@ -0,0 +1,543 @@
1
+ import { effect, isSignal } from './chunk-4E26PO2C.js';
2
+
3
+ // src/utils/urlScreen.ts
4
+ var URL_ATTRS = /* @__PURE__ */ new Set(["href", "src", "xlink:href", "formaction", "action", "data"]);
5
+ var DANGEROUS_SCHEMES = /* @__PURE__ */ new Set(["javascript", "vbscript"]);
6
+ var CONTROL_CHARS = /[\u0000-\u001F\u007F]/g;
7
+ function normalizeUrl(value) {
8
+ return value.replace(CONTROL_CHARS, "").replace(/^\s+/, "");
9
+ }
10
+ function extractScheme(value) {
11
+ const m = /^([a-zA-Z][a-zA-Z0-9+.-]*):/.exec(normalizeUrl(value));
12
+ return m ? m[1].toLowerCase() : null;
13
+ }
14
+ function isDangerousDataUrl(value) {
15
+ const media = /^data:([^;,]*)/.exec(normalizeUrl(value).toLowerCase())?.[1].trim() ?? "";
16
+ if (media === "" || media === "text/plain" || media === "text/css") return false;
17
+ if (media === "image/svg+xml") return true;
18
+ if (media.startsWith("image/")) return false;
19
+ if (media.startsWith("font/") || media.startsWith("application/font")) return false;
20
+ if (media.startsWith("audio/") || media.startsWith("video/")) return false;
21
+ return true;
22
+ }
23
+ function isDangerousUrlValue(name, value) {
24
+ if (!URL_ATTRS.has(name)) return false;
25
+ const scheme = extractScheme(value);
26
+ if (scheme === null) return false;
27
+ if (DANGEROUS_SCHEMES.has(scheme)) return true;
28
+ return scheme === "data" && isDangerousDataUrl(value);
29
+ }
30
+ function dangerousUrlWarning(name, value) {
31
+ return `dropped dangerous URL value for ${name}=${JSON.stringify(value.slice(0, 80))}. kerf blocks javascript:, vbscript:, and script-executing data: URLs (text/html, image/svg+xml, xml) in href/src/data/formaction/action/xlink:href by default. Wrap in raw() if this is intentional (e.g. bookmarklets), or sanitize upstream.`;
32
+ }
33
+
34
+ // src/bindings.ts
35
+ var BIND_ATTR = "data-kfb";
36
+ var TEXT_MARKER_PREFIX = "kfb:";
37
+ var BIND_ATTR_ROW = "data-kfbrow";
38
+ var ROW_TEXT_PREFIX = "kfbr:";
39
+ var context = null;
40
+ var rowSink = null;
41
+ var rowCounter = 0;
42
+ var NO_DISPOSERS = [];
43
+ function newBindingContext() {
44
+ return { counter: 0, list: [] };
45
+ }
46
+ function _setBindingContext(c) {
47
+ context = c;
48
+ }
49
+ function captureRowBindings(renderRow) {
50
+ const prevSink = rowSink;
51
+ const prevCounter = rowCounter;
52
+ rowSink = [];
53
+ rowCounter = 0;
54
+ try {
55
+ const html = renderRow();
56
+ return { html, bindings: rowSink };
57
+ } finally {
58
+ rowSink = prevSink;
59
+ rowCounter = prevCounter;
60
+ }
61
+ }
62
+ function bindAttr(attr, signal) {
63
+ if (rowSink !== null) {
64
+ const id = `a${rowCounter++}`;
65
+ rowSink.push({ kind: "attr", id, attr, signal });
66
+ return id;
67
+ }
68
+ if (context !== null) {
69
+ const id = `a${context.counter++}`;
70
+ context.list.push({ kind: "attr", id, attr, signal });
71
+ return id;
72
+ }
73
+ return null;
74
+ }
75
+ function bindMarkerAttr() {
76
+ return rowSink !== null ? BIND_ATTR_ROW : BIND_ATTR;
77
+ }
78
+ function bindText(signal) {
79
+ if (rowSink !== null) {
80
+ const id = `t${rowCounter++}`;
81
+ rowSink.push({ kind: "text", id, signal });
82
+ return `<!--${ROW_TEXT_PREFIX}${id}-->`;
83
+ }
84
+ if (context !== null) {
85
+ const id = `t${context.counter++}`;
86
+ context.list.push({ kind: "text", id, signal });
87
+ return `<!--${TEXT_MARKER_PREFIX}${id}-->`;
88
+ }
89
+ return null;
90
+ }
91
+ function wireBindings(rootEl, ctx, prevDisposers) {
92
+ for (const d of prevDisposers) d();
93
+ if (ctx.list.length === 0) return NO_DISPOSERS;
94
+ const disposers = [];
95
+ wireInto(rootEl, BIND_ATTR, TEXT_MARKER_PREFIX, ctx.list, disposers);
96
+ return disposers;
97
+ }
98
+ function wireRowBindings(rowNode, bindings) {
99
+ const disposers = new Array(bindings.length);
100
+ const rootIds = rowNode.getAttribute(BIND_ATTR_ROW);
101
+ let rootIdSet = null;
102
+ let descIndex = null;
103
+ let textMarkers = null;
104
+ for (let i = 0; i < bindings.length; i++) {
105
+ const b = bindings[i];
106
+ if (b.kind === "attr") {
107
+ let onRoot = false;
108
+ if (rootIds !== null) {
109
+ if (rootIds === b.id) {
110
+ onRoot = true;
111
+ } else if (rootIds.indexOf(",") !== -1) {
112
+ rootIdSet ??= new Set(rootIds.split(","));
113
+ onRoot = rootIdSet.has(b.id);
114
+ }
115
+ }
116
+ let el;
117
+ if (onRoot) {
118
+ el = rowNode;
119
+ } else {
120
+ descIndex ??= indexAttrEls(rowNode, BIND_ATTR_ROW);
121
+ el = descIndex.get(b.id);
122
+ }
123
+ if (el === void 0) continue;
124
+ disposers[i] = attachAttrEffect(el, b.attr, b.signal);
125
+ } else {
126
+ if (textMarkers === null) {
127
+ textMarkers = /* @__PURE__ */ new Map();
128
+ collectComments(rowNode, ROW_TEXT_PREFIX, textMarkers);
129
+ }
130
+ const marker = textMarkers.get(b.id);
131
+ if (marker === void 0) continue;
132
+ disposers[i] = attachTextEffect(marker, b.signal);
133
+ }
134
+ }
135
+ return disposers;
136
+ }
137
+ function disposeRowBindings(disposers) {
138
+ if (disposers === void 0) return;
139
+ for (const d of disposers) d();
140
+ }
141
+ function wireInto(scope, attrName, textPrefix, bindings, disposers) {
142
+ const attrEls = indexAttrEls(scope, attrName);
143
+ const textMarkers = /* @__PURE__ */ new Map();
144
+ collectComments(scope, textPrefix, textMarkers);
145
+ for (const b of bindings) {
146
+ if (b.kind === "attr") {
147
+ const el = attrEls.get(b.id);
148
+ if (el === void 0) continue;
149
+ disposers.push(attachAttrEffect(el, b.attr, b.signal));
150
+ } else {
151
+ const marker = textMarkers.get(b.id);
152
+ if (marker === void 0) continue;
153
+ disposers.push(attachTextEffect(marker, b.signal));
154
+ }
155
+ }
156
+ }
157
+ function indexAttrEls(scope, attrName) {
158
+ const map = /* @__PURE__ */ new Map();
159
+ for (const el of scope.querySelectorAll(`[${attrName}]`)) {
160
+ for (const id of el.getAttribute(attrName).split(",")) map.set(id, el);
161
+ }
162
+ return map;
163
+ }
164
+ function attachAttrEffect(el, attr, signal) {
165
+ return effect(() => setBoundAttr(el, attr, signal.value));
166
+ }
167
+ function attachTextEffect(marker, signal) {
168
+ const text = marker.ownerDocument.createTextNode("");
169
+ marker.parentNode.insertBefore(text, marker.nextSibling);
170
+ return effect(() => {
171
+ text.data = coerceText(signal.value);
172
+ });
173
+ }
174
+ function setBoundAttr(el, name, value) {
175
+ if (value == null || value === false) {
176
+ el.removeAttribute(name);
177
+ return;
178
+ }
179
+ if (value === true) {
180
+ el.setAttribute(name, "");
181
+ return;
182
+ }
183
+ if (isSafeHtmlValue(value)) {
184
+ el.setAttribute(name, value.__html);
185
+ return;
186
+ }
187
+ const str = String(value);
188
+ if (isDangerousUrlValue(name, str)) {
189
+ console.warn(`kerf binding: ${dangerousUrlWarning(name, str)}`);
190
+ el.removeAttribute(name);
191
+ return;
192
+ }
193
+ el.setAttribute(name, str);
194
+ }
195
+ var SAFE_HTML_BRAND = /* @__PURE__ */ Symbol.for("kerfjs.SafeHtml");
196
+ function isSafeHtmlValue(v) {
197
+ return typeof v === "object" && v !== null && v[SAFE_HTML_BRAND] === true;
198
+ }
199
+ function coerceText(value) {
200
+ if (value == null || typeof value === "boolean") return "";
201
+ return String(value);
202
+ }
203
+ function collectComments(node, prefix, out) {
204
+ for (let c = node.firstChild; c !== null; c = c.nextSibling) {
205
+ if (c.nodeType === Node.COMMENT_NODE) {
206
+ const data = c.data;
207
+ if (data.startsWith(prefix)) out.set(data.slice(prefix.length), c);
208
+ } else if (c.nodeType === Node.ELEMENT_NODE) {
209
+ collectComments(c, prefix, out);
210
+ }
211
+ }
212
+ }
213
+
214
+ // src/segment.ts
215
+ function flatten(segment, withMarkers) {
216
+ if (segment.kind === "static") return segment.html;
217
+ if (segment.kind === "list") {
218
+ const items = segment.items.map((i) => i.html).join("");
219
+ return withMarkers ? `<!--kf-list:${segment.id}-->${items}` : items;
220
+ }
221
+ return segment.parts.map((p) => flatten(p, withMarkers)).join("");
222
+ }
223
+ function flattenWithoutListItems(segment) {
224
+ if (segment.kind === "static") return segment.html;
225
+ if (segment.kind === "list") return `<!--kf-list:${segment.id}-->`;
226
+ return segment.parts.map(flattenWithoutListItems).join("");
227
+ }
228
+ function collectLists(segment, out = /* @__PURE__ */ new Map()) {
229
+ if (segment.kind === "list") out.set(segment.id, segment);
230
+ else if (segment.kind === "mixed") {
231
+ for (const part of segment.parts) collectLists(part, out);
232
+ }
233
+ return out;
234
+ }
235
+ function mergeChildSegments(parts) {
236
+ if (parts.length === 0) return { kind: "static", html: "" };
237
+ if (parts.every((p) => p.kind === "static")) {
238
+ return {
239
+ kind: "static",
240
+ html: parts.map((p) => p.html).join("")
241
+ };
242
+ }
243
+ const merged = [];
244
+ let coalesced = "";
245
+ for (const p of parts) {
246
+ if (p.kind === "static") {
247
+ coalesced += p.html;
248
+ } else {
249
+ if (coalesced !== "") {
250
+ merged.push({ kind: "static", html: coalesced });
251
+ coalesced = "";
252
+ }
253
+ merged.push(p);
254
+ }
255
+ }
256
+ if (coalesced !== "") merged.push({ kind: "static", html: coalesced });
257
+ return { kind: "mixed", parts: merged };
258
+ }
259
+ function wrapWithTags(child, openTag, closeTag) {
260
+ if (child.kind === "static") {
261
+ return { kind: "static", html: openTag + child.html + closeTag };
262
+ }
263
+ if (child.kind === "mixed") {
264
+ return {
265
+ kind: "mixed",
266
+ parts: [
267
+ { kind: "static", html: openTag },
268
+ ...child.parts,
269
+ { kind: "static", html: closeTag }
270
+ ]
271
+ };
272
+ }
273
+ return {
274
+ kind: "mixed",
275
+ parts: [
276
+ { kind: "static", html: openTag },
277
+ child,
278
+ { kind: "static", html: closeTag }
279
+ ]
280
+ };
281
+ }
282
+
283
+ // src/utils/escapeHtml.ts
284
+ function escapeHtml(str) {
285
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
286
+ }
287
+ function escapeAttr(str) {
288
+ return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
289
+ }
290
+
291
+ // src/utils/jsx-attr-aliases.ts
292
+ var ATTR_ALIASES = {
293
+ // HTML attributes
294
+ className: "class",
295
+ htmlFor: "for",
296
+ httpEquiv: "http-equiv",
297
+ acceptCharset: "accept-charset",
298
+ accessKey: "accesskey",
299
+ autoCapitalize: "autocapitalize",
300
+ autoComplete: "autocomplete",
301
+ autoFocus: "autofocus",
302
+ autoPlay: "autoplay",
303
+ colSpan: "colspan",
304
+ contentEditable: "contenteditable",
305
+ crossOrigin: "crossorigin",
306
+ dateTime: "datetime",
307
+ defaultChecked: "checked",
308
+ defaultValue: "value",
309
+ encType: "enctype",
310
+ formAction: "formaction",
311
+ formEncType: "formenctype",
312
+ formMethod: "formmethod",
313
+ formNoValidate: "formnovalidate",
314
+ formTarget: "formtarget",
315
+ hrefLang: "hreflang",
316
+ inputMode: "inputmode",
317
+ maxLength: "maxlength",
318
+ minLength: "minlength",
319
+ noModule: "nomodule",
320
+ noValidate: "novalidate",
321
+ readOnly: "readonly",
322
+ referrerPolicy: "referrerpolicy",
323
+ rowSpan: "rowspan",
324
+ spellCheck: "spellcheck",
325
+ srcDoc: "srcdoc",
326
+ srcLang: "srclang",
327
+ srcSet: "srcset",
328
+ tabIndex: "tabindex",
329
+ useMap: "usemap",
330
+ // SVG presentation attributes (camelCase → kebab-case)
331
+ strokeWidth: "stroke-width",
332
+ strokeLinecap: "stroke-linecap",
333
+ strokeLinejoin: "stroke-linejoin",
334
+ strokeDasharray: "stroke-dasharray",
335
+ strokeDashoffset: "stroke-dashoffset",
336
+ strokeMiterlimit: "stroke-miterlimit",
337
+ strokeOpacity: "stroke-opacity",
338
+ fillOpacity: "fill-opacity",
339
+ fillRule: "fill-rule",
340
+ clipPath: "clip-path",
341
+ clipRule: "clip-rule",
342
+ colorInterpolation: "color-interpolation",
343
+ colorInterpolationFilters: "color-interpolation-filters",
344
+ floodColor: "flood-color",
345
+ floodOpacity: "flood-opacity",
346
+ lightingColor: "lighting-color",
347
+ stopColor: "stop-color",
348
+ stopOpacity: "stop-opacity",
349
+ shapeRendering: "shape-rendering",
350
+ imageRendering: "image-rendering",
351
+ textRendering: "text-rendering",
352
+ pointerEvents: "pointer-events",
353
+ vectorEffect: "vector-effect",
354
+ paintOrder: "paint-order",
355
+ // SVG text/font attributes
356
+ fontFamily: "font-family",
357
+ fontSize: "font-size",
358
+ fontStyle: "font-style",
359
+ fontVariant: "font-variant",
360
+ fontWeight: "font-weight",
361
+ fontStretch: "font-stretch",
362
+ textAnchor: "text-anchor",
363
+ textDecoration: "text-decoration",
364
+ dominantBaseline: "dominant-baseline",
365
+ alignmentBaseline: "alignment-baseline",
366
+ baselineShift: "baseline-shift",
367
+ letterSpacing: "letter-spacing",
368
+ wordSpacing: "word-spacing",
369
+ writingMode: "writing-mode",
370
+ // SVG marker attributes
371
+ markerStart: "marker-start",
372
+ markerMid: "marker-mid",
373
+ markerEnd: "marker-end",
374
+ // SVG xlink (legacy but still used)
375
+ xlinkHref: "xlink:href",
376
+ xlinkShow: "xlink:show",
377
+ xlinkActuate: "xlink:actuate",
378
+ xlinkType: "xlink:type",
379
+ xlinkRole: "xlink:role",
380
+ xlinkTitle: "xlink:title",
381
+ xlinkArcrole: "xlink:arcrole",
382
+ xmlBase: "xml:base",
383
+ xmlLang: "xml:lang",
384
+ xmlSpace: "xml:space",
385
+ xmlnsXlink: "xmlns:xlink"
386
+ };
387
+
388
+ // src/jsx-runtime.ts
389
+ var SAFE_HTML_BRAND2 = /* @__PURE__ */ Symbol.for("kerfjs.SafeHtml");
390
+ var SafeHtml = class {
391
+ __html;
392
+ __segment;
393
+ // Branded so `isSafeHtml()` recognizes instances from any copy of this module.
394
+ [SAFE_HTML_BRAND2] = true;
395
+ constructor(input) {
396
+ if (typeof input === "string") {
397
+ this.__segment = { kind: "static", html: input };
398
+ this.__html = input;
399
+ } else {
400
+ this.__segment = input;
401
+ this.__html = flatten(input, false);
402
+ }
403
+ }
404
+ toString() {
405
+ return this.__html;
406
+ }
407
+ };
408
+ function isSafeHtml(value) {
409
+ return typeof value === "object" && value !== null && value[SAFE_HTML_BRAND2] === true;
410
+ }
411
+ function raw(html) {
412
+ return new SafeHtml(html);
413
+ }
414
+ function listSafeHtml(id, items) {
415
+ return new SafeHtml({ kind: "list", id, items });
416
+ }
417
+ function granularListSafeHtml(id, items, patches) {
418
+ return new SafeHtml({ kind: "list", id, items, patches });
419
+ }
420
+ var VOID_TAGS = /* @__PURE__ */ new Set([
421
+ "area",
422
+ "base",
423
+ "br",
424
+ "col",
425
+ "embed",
426
+ "hr",
427
+ "img",
428
+ "input",
429
+ "link",
430
+ "meta",
431
+ "source",
432
+ "track",
433
+ "wbr"
434
+ ]);
435
+ function toSegment(child) {
436
+ if (child == null || typeof child === "boolean") return { kind: "static", html: "" };
437
+ if (isSignal(child)) {
438
+ const marker = bindText(child);
439
+ if (marker !== null) return { kind: "static", html: marker };
440
+ const v = child.value;
441
+ return { kind: "static", html: v == null || typeof v === "boolean" ? "" : escapeHtml(String(v)) };
442
+ }
443
+ if (isSafeHtml(child)) {
444
+ return child.__segment ?? { kind: "static", html: child.__html };
445
+ }
446
+ if (typeof child === "string") return { kind: "static", html: escapeHtml(child) };
447
+ if (typeof child === "number") return { kind: "static", html: String(child) };
448
+ if (Array.isArray(child)) return mergeChildSegments(child.map(toSegment));
449
+ const maybeNode = child;
450
+ if (typeof maybeNode === "object" && maybeNode !== null && ("nodeType" in maybeNode || "outerHTML" in maybeNode)) {
451
+ throw new Error(
452
+ "JSX: DOM elements cannot be passed as children (the JSX runtime renders to HTML strings). Build the tree in one JSX expression and use querySelector after toElement() to get element refs."
453
+ );
454
+ }
455
+ throw new Error(
456
+ `JSX: unsupported child of type ${describeValue(child)}. Children must be SafeHtml, string, number, boolean, null, undefined, or an array of those. Common mistakes: passing a Signal/Store object directly (use signal.value or store.state.value), passing a function (call it first), or passing a Promise (await it before render).`
457
+ );
458
+ }
459
+ function describeValue(v) {
460
+ if (Array.isArray(v)) return "array";
461
+ if (typeof v === "object" && v !== null) {
462
+ const ctor = v.constructor?.name;
463
+ return ctor && ctor !== "Object" ? `object (${ctor})` : "object";
464
+ }
465
+ return typeof v;
466
+ }
467
+ var SAFE_ATTR_NAME = /^[A-Za-z_:][\w.:-]*$/;
468
+ function assertEmittableAttrName(key, name, isFn) {
469
+ if (/^on[a-z]/i.test(name)) {
470
+ if (isFn) {
471
+ throw new Error(
472
+ `JSX: inline event handlers like ${key}={fn} are not supported by kerf's JSX \u2192 HTML-string runtime. Use event delegation from the mount root instead:
473
+
474
+ delegate(rootEl, 'click', '[data-action="..."]', (evt, target) => { ... });
475
+ <button data-action="...">click</button>
476
+
477
+ See docs/5-event-delegation.md for the tier-1/tier-2/tier-3 model.`
478
+ );
479
+ }
480
+ throw new Error(
481
+ `JSX: event-handler attribute ${JSON.stringify(key)} is not allowed \u2014 an \`on*\` attribute (whether a string emitted into HTML or a signal bound via setAttribute) installs a live inline handler, an XSS vector. kerf uses event delegation: delegate(rootEl, 'click', '[data-action="..."]', handler). See docs/5-event-delegation.md.`
482
+ );
483
+ }
484
+ if (!SAFE_ATTR_NAME.test(name)) {
485
+ throw new Error(
486
+ `JSX: invalid attribute name ${JSON.stringify(key)}. Attribute names must be a letter/underscore/colon followed by letters, digits, or "_.:-" (e.g. class, data-id, aria-label, xlink:href). This usually means an untrusted object was spread into JSX ({...obj}) with attacker-controlled keys \u2014 validate keys first.`
487
+ );
488
+ }
489
+ }
490
+ function renderAttr(key, value) {
491
+ const name = ATTR_ALIASES[key] ?? key;
492
+ if (value == null || value === false) return "";
493
+ assertEmittableAttrName(key, name, typeof value === "function");
494
+ if (value === true) return ` ${name}`;
495
+ let strValue;
496
+ if (isSafeHtml(value)) {
497
+ strValue = value.__html;
498
+ } else if (typeof value === "number") {
499
+ strValue = String(value);
500
+ } else if (typeof value === "string") {
501
+ if (isDangerousUrlValue(name, value)) {
502
+ console.warn(`JSX: ${dangerousUrlWarning(name, value)}`);
503
+ return "";
504
+ }
505
+ strValue = escapeAttr(value);
506
+ } else {
507
+ throw new Error(
508
+ `JSX: unsupported value for attribute "${key}" \u2014 got ${describeValue(value)}. Attribute values must be string, number, boolean, null, undefined, or SafeHtml. Did you mean to read .value off a Signal, or stringify the object first?`
509
+ );
510
+ }
511
+ return ` ${name}="${strValue}"`;
512
+ }
513
+ function jsx(tag, props) {
514
+ if (typeof tag === "function") return tag(props);
515
+ const { children, ...attrs } = props;
516
+ let attrStr = "";
517
+ let bindIds = null;
518
+ for (const [k, v] of Object.entries(attrs)) {
519
+ if (isSignal(v)) {
520
+ const name = ATTR_ALIASES[k] ?? k;
521
+ assertEmittableAttrName(k, name, false);
522
+ const id = bindAttr(name, v);
523
+ if (id !== null) {
524
+ (bindIds ??= []).push(id);
525
+ continue;
526
+ }
527
+ attrStr += renderAttr(k, v.value);
528
+ continue;
529
+ }
530
+ attrStr += renderAttr(k, v);
531
+ }
532
+ if (bindIds !== null) attrStr += ` ${bindMarkerAttr()}="${bindIds.join(",")}"`;
533
+ if (VOID_TAGS.has(tag)) return new SafeHtml(`<${tag}${attrStr}>`);
534
+ const childSegment = children != null ? toSegment(children) : { kind: "static", html: "" };
535
+ return new SafeHtml(wrapWithTags(childSegment, `<${tag}${attrStr}>`, `</${tag}>`));
536
+ }
537
+ function Fragment({ children }) {
538
+ return new SafeHtml(children != null ? toSegment(children) : { kind: "static", html: "" });
539
+ }
540
+
541
+ export { Fragment, SafeHtml, _setBindingContext, captureRowBindings, collectLists, disposeRowBindings, flatten, flattenWithoutListItems, granularListSafeHtml, isSafeHtml, jsx, listSafeHtml, newBindingContext, raw, wireBindings, wireRowBindings };
542
+ //# sourceMappingURL=chunk-QNYOMGI4.js.map
543
+ //# sourceMappingURL=chunk-QNYOMGI4.js.map