why-hydration 0.2.0 → 0.3.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.
@@ -12,6 +12,230 @@ function readNodeEnv() {
12
12
  }
13
13
  var isDev = readNodeEnv() !== "production";
14
14
 
15
+ // src/core/classify/detectors.ts
16
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
17
+ var REACT_ID_RE = /^:[rR][0-9a-z]*:$/;
18
+ var HEX_TOKEN_RE = /^[0-9a-f]{16,}$/i;
19
+ var RANDOM_DECIMAL_RE = /^0?\.\d{6,}$/;
20
+ var NANOID_RE = /^[A-Za-z0-9_-]{10,}$/;
21
+ var ARABIC_INDIC_DIGITS = /[٠-٩۰-۹]/;
22
+ var LATIN_DIGITS = /[0-9]/;
23
+ var BIDI_CONTROLS = /[\u200e\u200f\u061c\u2066-\u2069]/g;
24
+ function stripBidiControls(value2) {
25
+ return value2.replace(BIDI_CONTROLS, "");
26
+ }
27
+ function differsOnlyByBidiControls(a, b) {
28
+ const sa = stripBidiControls(a);
29
+ const sb = stripBidiControls(b);
30
+ return a !== b && sa === sb && sa.trim() !== "";
31
+ }
32
+ function normalizeNumerals(value2) {
33
+ return stripBidiControls(value2).replace(/[٠-٩۰-۹٫٬]/g, (ch) => {
34
+ if (ch === "\u066B") return ".";
35
+ if (ch === "\u066C") return ",";
36
+ const code2 = ch.charCodeAt(0);
37
+ const zero = code2 >= 1776 ? 1776 : 1632;
38
+ return String(code2 - zero);
39
+ });
40
+ }
41
+ function isRandomLike(value2) {
42
+ const v = value2.trim();
43
+ if (!v) return false;
44
+ if (UUID_RE.test(v)) return true;
45
+ if (REACT_ID_RE.test(v)) return true;
46
+ if (HEX_TOKEN_RE.test(v)) return true;
47
+ if (RANDOM_DECIMAL_RE.test(v)) return true;
48
+ if (NANOID_RE.test(v) && /[A-Za-z]/.test(v) && /[0-9]/.test(v)) return true;
49
+ return false;
50
+ }
51
+ function looksLikeTime(value2) {
52
+ return /\b\d{1,2}:\d{2}(?::\d{2})?(?:\s?[AaPp][Mm])?\b/.test(
53
+ normalizeNumerals(value2).trim()
54
+ );
55
+ }
56
+ var ISO_DATE = /^\d{4}-\d{1,2}(?:-\d{1,2})?(?:[T ]\d{1,2}:\d{2}(?::\d{2})?)?/;
57
+ var NUMERIC_DATE = /^\d{1,4}[/.-]\d{1,2}[/.-]\d{1,4}$/;
58
+ var TIME_OF_DAY = /^\d{1,2}:\d{2}(?::\d{2})?(?:\s?[AaPp]\.?[Mm]\.?)?$/;
59
+ var MONTH_NAME = /\b(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*\b/i;
60
+ function looksLikeDate(v) {
61
+ return ISO_DATE.test(v) || NUMERIC_DATE.test(v) || TIME_OF_DAY.test(v) || MONTH_NAME.test(v) && /\d/.test(v);
62
+ }
63
+ function toTimestamp(value2) {
64
+ const v = value2.trim();
65
+ if (!v) return null;
66
+ if (/^\d{10,13}$/.test(v)) {
67
+ const n = Number(v);
68
+ return v.length === 10 ? n * 1e3 : n;
69
+ }
70
+ if (!looksLikeDate(v)) return null;
71
+ const parsed = Date.parse(v);
72
+ if (!Number.isNaN(parsed)) return parsed;
73
+ if (looksLikeTime(v)) {
74
+ const anchored = Date.parse(`1970-01-01 ${v}`);
75
+ if (!Number.isNaN(anchored)) return anchored;
76
+ }
77
+ return null;
78
+ }
79
+ function hasArabicIndicDigits(value2) {
80
+ return ARABIC_INDIC_DIGITS.test(value2);
81
+ }
82
+ function hasLatinDigits(value2) {
83
+ return LATIN_DIGITS.test(value2);
84
+ }
85
+ var NUMERIC_LIKE = /^[+-]?[\d.,\s\u00a0\u2009]+$/;
86
+ function isSameNumberDifferentSeparators(a, b) {
87
+ const at = normalizeNumerals(a).trim();
88
+ const bt = normalizeNumerals(b).trim();
89
+ if (!NUMERIC_LIKE.test(at) || !NUMERIC_LIKE.test(bt)) return false;
90
+ const digitsOnly = (s) => s.replace(/\D/g, "");
91
+ const da = digitsOnly(at);
92
+ const db = digitsOnly(bt);
93
+ if (!da || da !== db) return false;
94
+ const hasSep = (s) => /[.,\s\u00a0\u2009]/.test(s);
95
+ return (hasSep(at) || hasSep(bt)) && a.trim() !== b.trim();
96
+ }
97
+ var CONTENT_ATTRIBUTES = /* @__PURE__ */ new Set([
98
+ "value",
99
+ "placeholder",
100
+ "title",
101
+ "alt",
102
+ "label",
103
+ "aria-label",
104
+ "aria-valuetext",
105
+ "content",
106
+ "datetime"
107
+ ]);
108
+ function isContentAttribute(name) {
109
+ return CONTENT_ATTRIBUTES.has(name.toLowerCase());
110
+ }
111
+ var THIRD_PARTY_MARKERS = /googlefc|adsbygoogle|google_ads|googletag|__tcfapi|onetrust|optanon|cookiebot|usercentrics|iubenda|didomi|quantcast|grammarly|data-gramm|gtm-|_hjsettings|hotjar|fullstory|intercom|drift|zendesk|livechat|tawk|hubspot|turnstile|recaptcha/i;
112
+ function looksLikeThirdPartyNode(html, tagName) {
113
+ const tag = (tagName ?? "").toUpperCase();
114
+ if (tag === "IFRAME" || tag === "EMBED" || tag === "OBJECT") return true;
115
+ const h = (html ?? "").toLowerCase();
116
+ if (!h) return false;
117
+ return THIRD_PARTY_MARKERS.test(h) || h.includes("about:blank");
118
+ }
119
+ var DATE_PARTS_RE = /^(\d{1,4})[/.-](\d{1,2})[/.-](\d{1,4})$/;
120
+ function isSameDateDifferentOrder(a, b) {
121
+ const partsA = normalizeNumerals(a).trim().match(DATE_PARTS_RE);
122
+ const partsB = normalizeNumerals(b).trim().match(DATE_PARTS_RE);
123
+ if (!partsA || !partsB) return false;
124
+ const setA = [partsA[1], partsA[2], partsA[3]].sort().join("|");
125
+ const setB = [partsB[1], partsB[2], partsB[3]].sort().join("|");
126
+ return setA === setB && a.trim() !== b.trim();
127
+ }
128
+ var EXTENSION_ATTRIBUTES = /* @__PURE__ */ new Set([
129
+ "cz-shortcut-listen",
130
+ "data-gramm",
131
+ "data-gramm_editor",
132
+ "data-gramm_id",
133
+ "data-gr-c-s-loaded",
134
+ "data-lt-installed",
135
+ "data-new-gr-c-s-check-loaded",
136
+ "data-new-gr-c-s-loaded",
137
+ "spellcheck-extension",
138
+ "bis_register",
139
+ "__processed_by_react_dev_tools"
140
+ ]);
141
+ function isExtensionAttribute(name) {
142
+ const n = name.toLowerCase();
143
+ if (EXTENSION_ATTRIBUTES.has(n)) return true;
144
+ return n.startsWith("data-gr-") || n.startsWith("data-gramm") || n.startsWith("__bis") || n.startsWith("bis_");
145
+ }
146
+ var CLOSES_P = [
147
+ "address",
148
+ "article",
149
+ "aside",
150
+ "blockquote",
151
+ "center",
152
+ "details",
153
+ "dialog",
154
+ "dir",
155
+ "div",
156
+ "dl",
157
+ "fieldset",
158
+ "figcaption",
159
+ "figure",
160
+ "footer",
161
+ "form",
162
+ "h1",
163
+ "h2",
164
+ "h3",
165
+ "h4",
166
+ "h5",
167
+ "h6",
168
+ "header",
169
+ "hgroup",
170
+ "hr",
171
+ "li",
172
+ "dd",
173
+ "dt",
174
+ "listing",
175
+ "main",
176
+ "menu",
177
+ "nav",
178
+ "ol",
179
+ "p",
180
+ "plaintext",
181
+ "pre",
182
+ "search",
183
+ "section",
184
+ "summary",
185
+ "table",
186
+ "ul",
187
+ "xmp"
188
+ ];
189
+ var CLOSES_P_SET = new Set(CLOSES_P.map((t) => t.toUpperCase()));
190
+ var TABLE_CHILDREN = /* @__PURE__ */ new Set([
191
+ "CAPTION",
192
+ "COLGROUP",
193
+ "THEAD",
194
+ "TBODY",
195
+ "TFOOT"
196
+ ]);
197
+ var TABLE_SCAFFOLD = ["script", "template", "style"];
198
+ var TABLE_LEVEL = ["caption", "colgroup", "thead", "tbody", "tfoot"];
199
+ var SECTION_LEVEL = ["tr"];
200
+ var ROW_LEVEL = ["td", "th"];
201
+ var notIn = (tags) => `:not(${[...tags, ...TABLE_SCAFFOLD].join(", ")})`;
202
+ var PARSER_REPAIRS = {
203
+ P: CLOSES_P.join(", "),
204
+ A: "a",
205
+ BUTTON: "button",
206
+ FORM: "form",
207
+ TABLE: [
208
+ `:scope > ${notIn([...TABLE_LEVEL, ...SECTION_LEVEL])}`,
209
+ ...["thead", "tbody", "tfoot"].flatMap((section) => [
210
+ `:scope > ${section} > ${notIn(SECTION_LEVEL)}`,
211
+ `:scope > ${section} > tr > ${notIn(ROW_LEVEL)}`
212
+ ]),
213
+ `:scope > tr > ${notIn(ROW_LEVEL)}`
214
+ ].join(", ")
215
+ };
216
+ function isInvalidNesting(parentTag, childTag) {
217
+ if (!parentTag || !childTag) return false;
218
+ const p = parentTag.toUpperCase();
219
+ const c = childTag.toUpperCase();
220
+ if (p === "P") return CLOSES_P_SET.has(c);
221
+ if (p === "A") return c === "A";
222
+ if (p === "BUTTON") return c === "BUTTON" || c === "A";
223
+ if (p === "FORM") return c === "FORM";
224
+ if (p === "TABLE") return !TABLE_CHILDREN.has(c) && !isScaffold(c);
225
+ if (p === "THEAD" || p === "TBODY" || p === "TFOOT") {
226
+ return c !== "TR" && !isScaffold(c);
227
+ }
228
+ if (p === "TR") return c !== "TD" && c !== "TH" && !isScaffold(c);
229
+ return false;
230
+ }
231
+ function isScaffold(tag) {
232
+ return TABLE_SCAFFOLD.includes(tag.toLowerCase());
233
+ }
234
+ function messageIndicatesInvalidNesting(message) {
235
+ if (!message) return false;
236
+ return /validateDOMNesting/i.test(message) || /cannot (?:be a|contain).*(?:descendant|child)/i.test(message) || /cannot appear as a (?:child|descendant)/i.test(message);
237
+ }
238
+
15
239
  // src/core/diff.ts
16
240
  var IGNORED_ATTRIBUTES = /* @__PURE__ */ new Set(["data-reactroot"]);
17
241
  function parseServerHtml(html, rootTagName) {
@@ -38,9 +262,15 @@ function diffSnapshotAgainstDom(serverHtml, clientRoot) {
38
262
  function collectChildren(serverParent, clientParent, parentPath, out, limit) {
39
263
  if (out.length >= limit) return;
40
264
  const serverChildren = meaningfulChildNodes(serverParent);
41
- const clientChildren = meaningfulChildNodes(clientParent);
42
265
  const parentPending = hasPendingSuspense(serverParent);
43
266
  const parentTag = elementTag(clientParent);
267
+ const clientChildren = repairClientChildren(
268
+ meaningfulChildNodes(clientParent),
269
+ parentTag,
270
+ parentPath,
271
+ out,
272
+ limit
273
+ );
44
274
  const pairs = alignChildren(serverChildren, clientChildren);
45
275
  let index = 0;
46
276
  for (let k = 0; k < pairs.length; k++) {
@@ -88,7 +318,10 @@ function collectChildren(serverParent, clientParent, parentPath, out, limit) {
88
318
  parentTagName: parentTag,
89
319
  server: null,
90
320
  client: serialize(clientNode),
91
- element: asElement(clientNode)
321
+ // An added text node has no element of its own, so point at its
322
+ // parent, as text changes do. With `null` the component lookup found
323
+ // nothing and fell back to whichever error was reported last.
324
+ element: asElement(clientNode) ?? asElement(clientParent)
92
325
  });
93
326
  } else if (serverNode && clientNode) {
94
327
  collectNode(serverNode, clientNode, path, parentTag, out, limit);
@@ -134,7 +367,7 @@ function collectNode(serverNode, clientNode, path, parentTag, out, limit) {
134
367
  parentTagName: parentTag,
135
368
  server: serialize(serverEl),
136
369
  client: serialize(clientEl),
137
- element: clientEl
370
+ element: liveElement(clientEl)
138
371
  });
139
372
  return;
140
373
  }
@@ -162,7 +395,7 @@ function diffAttributes(serverEl, clientEl, path, parentTag) {
162
395
  attribute: name,
163
396
  server: serverValue,
164
397
  client: clientValue,
165
- element: clientEl
398
+ element: liveElement(clientEl)
166
399
  };
167
400
  }
168
401
  return null;
@@ -309,7 +542,8 @@ function meaningfulChildNodes(parent) {
309
542
  for (const node of raw) {
310
543
  if (node.nodeType === Node.COMMENT_NODE) {
311
544
  const data = node.nodeValue ?? "";
312
- if (data === "$?" || data === "$" || data === "$!") boundaryStack.push(data);
545
+ if (data === "$?" || data === "$" || data === "$!")
546
+ boundaryStack.push(data);
313
547
  else if (data === "/$") boundaryStack.pop();
314
548
  continue;
315
549
  }
@@ -332,7 +566,94 @@ function serialize(node) {
332
566
  }
333
567
  function asElement(node) {
334
568
  if (!node) return null;
335
- return node.nodeType === Node.ELEMENT_NODE ? node : null;
569
+ return node.nodeType === Node.ELEMENT_NODE ? liveElement(node) : null;
570
+ }
571
+ var LIVE = /* @__PURE__ */ new WeakMap();
572
+ function liveElement(el) {
573
+ return LIVE.get(el) ?? el;
574
+ }
575
+ function repairClientChildren(children, parentTag, parentPath, out, limit) {
576
+ let repaired = null;
577
+ children.forEach((child, i) => {
578
+ const expansion = repairedForm(child, parentTag);
579
+ if (!expansion) {
580
+ repaired?.push(child);
581
+ return;
582
+ }
583
+ repaired ??= children.slice(0, i);
584
+ repaired.push(...expansion.nodes);
585
+ if (out.length < limit) {
586
+ out.push({
587
+ kind: "structure",
588
+ path: childPath(parentPath, child, i),
589
+ tagName: expansion.offender.tagName,
590
+ parentTagName: invalidParent(expansion.offender, child),
591
+ // No values, on purpose: React reports the same nesting in its own
592
+ // warning with none, and matching it lets the two collapse into one.
593
+ server: null,
594
+ client: null,
595
+ element: expansion.offender
596
+ });
597
+ }
598
+ });
599
+ return repaired ?? children;
600
+ }
601
+ function invalidParent(offender, top) {
602
+ for (let el = offender.parentElement; el; el = el.parentElement) {
603
+ if (isInvalidNesting(el.tagName, offender.tagName)) return el.tagName;
604
+ if (el === top) break;
605
+ }
606
+ return top.tagName;
607
+ }
608
+ function repairedForm(node, parentTag) {
609
+ if (node.nodeType !== Node.ELEMENT_NODE) return null;
610
+ const el = node;
611
+ const selector = PARSER_REPAIRS[el.tagName];
612
+ if (!selector) return null;
613
+ let offender = null;
614
+ try {
615
+ offender = el.querySelector(selector);
616
+ } catch {
617
+ return null;
618
+ }
619
+ if (!offender) return null;
620
+ const html = serializeKeepingTextBoundaries(el);
621
+ const container = parseServerHtml(html, parentTag ?? "div");
622
+ if (container.innerHTML === html) return null;
623
+ const nodes = meaningfulChildNodes(container);
624
+ mapToLive(el, nodes);
625
+ return { nodes, offender };
626
+ }
627
+ function serializeKeepingTextBoundaries(el) {
628
+ const copy = el.cloneNode(true);
629
+ const walker = copy.ownerDocument.createTreeWalker(
630
+ copy,
631
+ NodeFilter.SHOW_TEXT
632
+ );
633
+ const texts = [];
634
+ while (walker.nextNode()) texts.push(walker.currentNode);
635
+ for (const t of texts) {
636
+ if (t.previousSibling?.nodeType === Node.TEXT_NODE) {
637
+ t.parentNode.insertBefore(copy.ownerDocument.createComment(" "), t);
638
+ }
639
+ }
640
+ return copy.outerHTML;
641
+ }
642
+ function mapToLive(live, repaired) {
643
+ const liveEls = [live, ...Array.from(live.querySelectorAll("*"))];
644
+ let i = 0;
645
+ for (const root of repaired) {
646
+ if (root.nodeType !== Node.ELEMENT_NODE) continue;
647
+ const el = root;
648
+ for (const r of [el, ...Array.from(el.querySelectorAll("*"))]) {
649
+ let j = i;
650
+ while (j < liveEls.length && liveEls[j].tagName !== r.tagName) j++;
651
+ if (j < liveEls.length) {
652
+ LIVE.set(r, liveEls[j]);
653
+ i = j + 1;
654
+ }
655
+ }
656
+ }
336
657
  }
337
658
  function elementTag(node) {
338
659
  return node.nodeType === Node.ELEMENT_NODE ? node.tagName : void 0;
@@ -353,179 +674,6 @@ function childPath(parentPath, node, index) {
353
674
  return `${parentPath} > #text[${index}]`;
354
675
  }
355
676
 
356
- // src/core/classify/detectors.ts
357
- var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
358
- var REACT_ID_RE = /^:[rR][0-9a-z]*:$/;
359
- var HEX_TOKEN_RE = /^[0-9a-f]{16,}$/i;
360
- var RANDOM_DECIMAL_RE = /^0?\.\d{6,}$/;
361
- var NANOID_RE = /^[A-Za-z0-9_-]{10,}$/;
362
- var ARABIC_INDIC_DIGITS = /[٠-٩۰-۹]/;
363
- var LATIN_DIGITS = /[0-9]/;
364
- var BIDI_CONTROLS = /[\u200e\u200f\u061c\u2066-\u2069]/g;
365
- function stripBidiControls(value2) {
366
- return value2.replace(BIDI_CONTROLS, "");
367
- }
368
- function differsOnlyByBidiControls(a, b) {
369
- const sa = stripBidiControls(a);
370
- const sb = stripBidiControls(b);
371
- return a !== b && sa === sb && sa.trim() !== "";
372
- }
373
- function normalizeNumerals(value2) {
374
- return stripBidiControls(value2).replace(/[٠-٩۰-۹٫٬]/g, (ch) => {
375
- if (ch === "\u066B") return ".";
376
- if (ch === "\u066C") return ",";
377
- const code2 = ch.charCodeAt(0);
378
- const zero = code2 >= 1776 ? 1776 : 1632;
379
- return String(code2 - zero);
380
- });
381
- }
382
- function isRandomLike(value2) {
383
- const v = value2.trim();
384
- if (!v) return false;
385
- if (UUID_RE.test(v)) return true;
386
- if (REACT_ID_RE.test(v)) return true;
387
- if (HEX_TOKEN_RE.test(v)) return true;
388
- if (RANDOM_DECIMAL_RE.test(v)) return true;
389
- if (NANOID_RE.test(v) && /[A-Za-z]/.test(v) && /[0-9]/.test(v)) return true;
390
- return false;
391
- }
392
- function looksLikeTime(value2) {
393
- return /\b\d{1,2}:\d{2}(?::\d{2})?(?:\s?[AaPp][Mm])?\b/.test(
394
- normalizeNumerals(value2).trim()
395
- );
396
- }
397
- var ISO_DATE = /^\d{4}-\d{1,2}(?:-\d{1,2})?(?:[T ]\d{1,2}:\d{2}(?::\d{2})?)?/;
398
- var NUMERIC_DATE = /^\d{1,4}[/.-]\d{1,2}[/.-]\d{1,4}$/;
399
- var TIME_OF_DAY = /^\d{1,2}:\d{2}(?::\d{2})?(?:\s?[AaPp]\.?[Mm]\.?)?$/;
400
- var MONTH_NAME = /\b(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*\b/i;
401
- function looksLikeDate(v) {
402
- return ISO_DATE.test(v) || NUMERIC_DATE.test(v) || TIME_OF_DAY.test(v) || MONTH_NAME.test(v) && /\d/.test(v);
403
- }
404
- function toTimestamp(value2) {
405
- const v = value2.trim();
406
- if (!v) return null;
407
- if (/^\d{10,13}$/.test(v)) {
408
- const n = Number(v);
409
- return v.length === 10 ? n * 1e3 : n;
410
- }
411
- if (!looksLikeDate(v)) return null;
412
- const parsed = Date.parse(v);
413
- if (!Number.isNaN(parsed)) return parsed;
414
- if (looksLikeTime(v)) {
415
- const anchored = Date.parse(`1970-01-01 ${v}`);
416
- if (!Number.isNaN(anchored)) return anchored;
417
- }
418
- return null;
419
- }
420
- function hasArabicIndicDigits(value2) {
421
- return ARABIC_INDIC_DIGITS.test(value2);
422
- }
423
- function hasLatinDigits(value2) {
424
- return LATIN_DIGITS.test(value2);
425
- }
426
- var NUMERIC_LIKE = /^[+-]?[\d.,\s\u00a0\u2009]+$/;
427
- function isSameNumberDifferentSeparators(a, b) {
428
- const at = normalizeNumerals(a).trim();
429
- const bt = normalizeNumerals(b).trim();
430
- if (!NUMERIC_LIKE.test(at) || !NUMERIC_LIKE.test(bt)) return false;
431
- const digitsOnly = (s) => s.replace(/\D/g, "");
432
- const da = digitsOnly(at);
433
- const db = digitsOnly(bt);
434
- if (!da || da !== db) return false;
435
- const hasSep = (s) => /[.,\s\u00a0\u2009]/.test(s);
436
- return (hasSep(at) || hasSep(bt)) && a.trim() !== b.trim();
437
- }
438
- var CONTENT_ATTRIBUTES = /* @__PURE__ */ new Set([
439
- "value",
440
- "placeholder",
441
- "title",
442
- "alt",
443
- "label",
444
- "aria-label",
445
- "aria-valuetext",
446
- "content",
447
- "datetime"
448
- ]);
449
- function isContentAttribute(name) {
450
- return CONTENT_ATTRIBUTES.has(name.toLowerCase());
451
- }
452
- var THIRD_PARTY_MARKERS = /googlefc|adsbygoogle|google_ads|googletag|__tcfapi|onetrust|optanon|cookiebot|usercentrics|iubenda|didomi|quantcast|grammarly|data-gramm|gtm-|_hjsettings|hotjar|fullstory|intercom|drift|zendesk|livechat|tawk|hubspot|turnstile|recaptcha/i;
453
- function looksLikeThirdPartyNode(html, tagName) {
454
- const tag = (tagName ?? "").toUpperCase();
455
- if (tag === "IFRAME" || tag === "EMBED" || tag === "OBJECT") return true;
456
- const h = (html ?? "").toLowerCase();
457
- if (!h) return false;
458
- return THIRD_PARTY_MARKERS.test(h) || h.includes("about:blank");
459
- }
460
- var DATE_PARTS_RE = /^(\d{1,4})[/.-](\d{1,2})[/.-](\d{1,4})$/;
461
- function isSameDateDifferentOrder(a, b) {
462
- const partsA = normalizeNumerals(a).trim().match(DATE_PARTS_RE);
463
- const partsB = normalizeNumerals(b).trim().match(DATE_PARTS_RE);
464
- if (!partsA || !partsB) return false;
465
- const setA = [partsA[1], partsA[2], partsA[3]].sort().join("|");
466
- const setB = [partsB[1], partsB[2], partsB[3]].sort().join("|");
467
- return setA === setB && a.trim() !== b.trim();
468
- }
469
- var EXTENSION_ATTRIBUTES = /* @__PURE__ */ new Set([
470
- "cz-shortcut-listen",
471
- "data-gramm",
472
- "data-gramm_editor",
473
- "data-gramm_id",
474
- "data-gr-c-s-loaded",
475
- "data-lt-installed",
476
- "data-new-gr-c-s-check-loaded",
477
- "data-new-gr-c-s-loaded",
478
- "spellcheck-extension",
479
- "bis_register",
480
- "__processed_by_react_dev_tools"
481
- ]);
482
- function isExtensionAttribute(name) {
483
- const n = name.toLowerCase();
484
- if (EXTENSION_ATTRIBUTES.has(n)) return true;
485
- return n.startsWith("data-gr-") || n.startsWith("data-gramm") || n.startsWith("__bis") || n.startsWith("bis_");
486
- }
487
- var BLOCK_TAGS = /* @__PURE__ */ new Set([
488
- "DIV",
489
- "P",
490
- "SECTION",
491
- "ARTICLE",
492
- "UL",
493
- "OL",
494
- "LI",
495
- "TABLE",
496
- "HEADER",
497
- "FOOTER",
498
- "MAIN",
499
- "ASIDE",
500
- "NAV",
501
- "H1",
502
- "H2",
503
- "H3",
504
- "H4",
505
- "H5",
506
- "H6",
507
- "FORM",
508
- "BLOCKQUOTE",
509
- "PRE",
510
- "HR"
511
- ]);
512
- function isInvalidNesting(parentTag, childTag) {
513
- if (!parentTag || !childTag) return false;
514
- const p = parentTag.toUpperCase();
515
- const c = childTag.toUpperCase();
516
- if (p === "P" && BLOCK_TAGS.has(c)) return true;
517
- if (p === "A" && c === "A") return true;
518
- if (p === "BUTTON" && (c === "BUTTON" || c === "A")) return true;
519
- if ((p === "TABLE" || p === "THEAD" || p === "TBODY") && c === "DIV") {
520
- return true;
521
- }
522
- return false;
523
- }
524
- function messageIndicatesInvalidNesting(message) {
525
- if (!message) return false;
526
- return /validateDOMNesting/i.test(message) || /cannot (?:be a|contain).*(?:descendant|child)/i.test(message) || /cannot appear as a (?:child|descendant)/i.test(message);
527
- }
528
-
529
677
  // src/core/classify/messages.ts
530
678
  var code = (text) => ({ code: text });
531
679
  var value = (text) => ({ value: text });
@@ -992,6 +1140,16 @@ var ReportCollector = class {
992
1140
  this.options = options;
993
1141
  this.maxReports = options.maxReports ?? 25;
994
1142
  }
1143
+ /**
1144
+ * Swap the rules, threshold, ignore list and cap for everything reported
1145
+ * from now on. Reports already collected are kept as they are: they were
1146
+ * correct under the options in force when they were made, and re-deciding
1147
+ * them would re-fire `onReport` for mismatches the caller already saw.
1148
+ */
1149
+ configure(options) {
1150
+ this.options = options;
1151
+ this.maxReports = options.maxReports ?? 25;
1152
+ }
995
1153
  /**
996
1154
  * Register a sink. Pass `replay` for sinks that render *state* (the overlay)
997
1155
  * rather than react to *events* (`onReport`, the console): they need the
@@ -1350,5 +1508,5 @@ exports.plainText = plainText;
1350
1508
  exports.renderMessage = renderMessage;
1351
1509
  exports.reportFromMessage = reportFromMessage;
1352
1510
  exports.signatureOf = signatureOf;
1353
- //# sourceMappingURL=chunk-KX6G7I4Y.cjs.map
1354
- //# sourceMappingURL=chunk-KX6G7I4Y.cjs.map
1511
+ //# sourceMappingURL=chunk-QFHUKJ5K.cjs.map
1512
+ //# sourceMappingURL=chunk-QFHUKJ5K.cjs.map