why-hydration 0.1.5 → 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.
@@ -1,642 +0,0 @@
1
- import { isDev, ReportCollector, inspectRoot, reportFromMessage, formatConsoleArgs, isHydrationMessage } from './chunk-AS5DZZHI.js';
2
- import { getServerHtmlForRoot, readSnapshot, DEFAULT_SNAPSHOT_SELECTORS } from './chunk-XIM33ZGB.js';
3
- import * as React2 from 'react';
4
-
5
- // src/react/console.ts
6
- function installConsoleInterceptor(onMessage) {
7
- if (typeof console === "undefined") return () => {
8
- };
9
- const original = console.error;
10
- const patched = (...args) => {
11
- try {
12
- const message = formatConsoleArgs(args);
13
- if (message && isHydrationMessage(message)) {
14
- onMessage(message, args);
15
- }
16
- } catch {
17
- }
18
- original.apply(console, args);
19
- };
20
- console.error = patched;
21
- return () => {
22
- if (console.error === patched) {
23
- console.error = original;
24
- }
25
- };
26
- }
27
- var BADGE = "color:#f59e0b;font-weight:bold";
28
- var DIM = "color:#9ca3af";
29
- function createConsoleReporter() {
30
- const sink = (report) => {
31
- const title = `%c\u2B21 why-hydration%c ${report.cause.category} \u2014 ${report.node.path}`;
32
- console.group(title, BADGE, DIM);
33
- console.log("%cServer:%c", "color:#fb7185", "", report.server ?? "(none)");
34
- console.log("%cClient:%c", "color:#4ade80", "", report.client ?? "(none)");
35
- if (report.component) {
36
- console.log("%cComponent:%c", DIM, "", `<${report.component}>`);
37
- }
38
- if (report.location?.file) {
39
- const loc = report.location.line ? `${report.location.file}:${report.location.line}` : report.location.file;
40
- console.log("%cSource:%c", DIM, "", loc);
41
- }
42
- console.log("%cWhy:%c", DIM, "", report.cause.explanation);
43
- console.log("%cFix:%c", "color:#86efac", "", report.cause.suggestion);
44
- if (report.cause.docsUrl) {
45
- console.log("%cDocs:%c", DIM, "", report.cause.docsUrl);
46
- }
47
- if (report.componentStack) {
48
- console.log("%cComponent stack:%c", DIM, "", report.componentStack);
49
- }
50
- console.groupEnd();
51
- };
52
- return sink;
53
- }
54
-
55
- // src/react/capture.ts
56
- var MAX_CAPTURED = 50;
57
- var captured = /* @__PURE__ */ new Set();
58
- var listeners = /* @__PURE__ */ new Set();
59
- var uninstall = null;
60
- function startCapture() {
61
- if (uninstall || typeof window === "undefined") return;
62
- uninstall = installConsoleInterceptor((message) => {
63
- if (captured.has(message) || captured.size >= MAX_CAPTURED) return;
64
- captured.add(message);
65
- for (const listener of [...listeners]) listener(message);
66
- });
67
- }
68
- function subscribeCapture(listener) {
69
- startCapture();
70
- listeners.add(listener);
71
- for (const message of [...captured]) listener(message);
72
- return () => {
73
- if (!listeners.delete(listener)) return;
74
- if (listeners.size === 0 && uninstall) {
75
- uninstall();
76
- uninstall = null;
77
- }
78
- };
79
- }
80
-
81
- // src/react/overlay.ts
82
- var CONTAINER_ID = "why-hydration-overlay";
83
- var CATEGORY_LABELS = {
84
- "non-deterministic-value": "Non-deterministic value",
85
- "date-time": "Date / time",
86
- "locale-format": "Locale formatting",
87
- "browser-only-api": "Browser-only API",
88
- "viewport-branching": "Viewport branching",
89
- "invalid-html-nesting": "Invalid HTML nesting",
90
- "whitespace-minification": "Whitespace / minification",
91
- "third-party-dom-mutation": "Third-party DOM mutation",
92
- "attribute-mismatch": "Attribute mismatch",
93
- unknown: "Unknown"
94
- };
95
- var STYLES = `
96
- :host {
97
- all: initial;
98
- /* The "all" shorthand deliberately excludes direction/unicode-bidi (CSS spec),
99
- so a host page with <html dir="rtl"> would otherwise leak direction:rtl
100
- into this shadow tree and flip flex/grid order + text alignment. Report
101
- content is English, so the overlay always renders left-to-right,
102
- independent of the host page's directionality. */
103
- direction: ltr;
104
- unicode-bidi: isolate;
105
- }
106
- * { box-sizing: border-box; }
107
- .wh-panel {
108
- position: fixed;
109
- z-index: 2147483647;
110
- width: min(420px, calc(100vw - 32px));
111
- max-height: min(70vh, 640px);
112
- display: flex;
113
- flex-direction: column;
114
- font: 13px/1.5 ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
115
- color: #e5e7eb;
116
- background: #0b0f17;
117
- border: 1px solid #1f2937;
118
- border-radius: 12px;
119
- box-shadow: 0 12px 40px rgba(0,0,0,.5);
120
- overflow: hidden;
121
- direction: ltr;
122
- text-align: left;
123
- }
124
- .wh-bottom-right { bottom: 16px; right: 16px; }
125
- .wh-bottom-left { bottom: 16px; left: 16px; }
126
- .wh-top-right { top: 16px; right: 16px; }
127
- .wh-top-left { top: 16px; left: 16px; }
128
- .wh-header {
129
- display: flex; align-items: center; gap: 8px;
130
- padding: 10px 12px;
131
- background: linear-gradient(180deg, #151b26, #0d1220);
132
- border-bottom: 1px solid #1f2937;
133
- }
134
- .wh-dot { width: 8px; height: 8px; border-radius: 50%; background: #f59e0b; flex: none; }
135
- .wh-title { font-weight: 600; color: #f3f4f6; }
136
- .wh-count {
137
- margin-left: auto; font-size: 11px; color: #9ca3af;
138
- background: #111827; border: 1px solid #1f2937; border-radius: 999px;
139
- padding: 1px 8px;
140
- }
141
- .wh-btn {
142
- appearance: none; border: 1px solid #1f2937; background: #111827;
143
- color: #9ca3af; border-radius: 6px; cursor: pointer;
144
- font-size: 12px; padding: 3px 8px;
145
- }
146
- .wh-btn:hover { color: #f3f4f6; border-color: #374151; }
147
- .wh-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding: 8px; display: flex; flex-direction: column; gap: 8px; }
148
- .wh-card { flex: 0 0 auto; border: 1px solid #1f2937; border-radius: 10px; background: #0f1521; overflow: hidden; }
149
- .wh-card-head { display: flex; align-items: center; gap: 8px; padding: 8px 10px; }
150
- .wh-cat {
151
- font-weight: 600; color: #fbbf24; font-size: 12px;
152
- }
153
- .wh-conf { margin-left: auto; font-size: 11px; color: #6b7280; }
154
- .wh-body { padding: 0 10px 10px; }
155
- .wh-where { font-size: 11px; color: #9ca3af; word-break: break-all; margin-bottom: 4px; }
156
- .wh-where code { color: #93c5fd; }
157
- .wh-comp { color: #f3f4f6; font-weight: 600; }
158
- .wh-loc { font-size: 11px; color: #7dd3fc; word-break: break-all; margin-bottom: 8px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
159
- .wh-diff { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; margin-bottom: 8px; }
160
- .wh-side { border-radius: 8px; padding: 6px 8px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; word-break: break-word; }
161
- .wh-server { background: rgba(244,63,94,.08); border: 1px solid rgba(244,63,94,.35); }
162
- .wh-client { background: rgba(34,197,94,.08); border: 1px solid rgba(34,197,94,.35); }
163
- .wh-side .wh-label { display: block; font-family: inherit; font-size: 9px; text-transform: uppercase; letter-spacing: .04em; margin-bottom: 3px; }
164
- .wh-server .wh-label { color: #fb7185; }
165
- .wh-client .wh-label { color: #4ade80; }
166
- .wh-explain { color: #cbd5e1; margin-bottom: 8px; }
167
- .wh-fix { color: #e5e7eb; background: #111827; border-left: 3px solid #22c55e; border-radius: 4px; padding: 6px 8px; }
168
- .wh-fix strong { color: #86efac; }
169
- .wh-docs { display: inline-block; margin-top: 6px; color: #60a5fa; text-decoration: none; font-size: 11px; }
170
- .wh-docs:hover { text-decoration: underline; }
171
- .wh-empty-value { color: #6b7280; font-style: italic; }
172
- .wh-hint {
173
- display: none; align-items: center; gap: 8px;
174
- padding: 7px 12px; font-size: 11px; color: #cbd5e1;
175
- background: #0d1220; border-top: 1px solid #1f2937;
176
- animation: wh-fade .2s ease;
177
- }
178
- .wh-hint.wh-show { display: flex; }
179
- .wh-hint-text { flex: 1; }
180
- .wh-hint-x {
181
- appearance: none; border: 0; background: transparent; cursor: pointer;
182
- color: #9ca3af; font-size: 14px; line-height: 1; padding: 2px 4px;
183
- }
184
- .wh-hint-x:hover { color: #f3f4f6; }
185
- @keyframes wh-fade { from { opacity: 0 } to { opacity: 1 } }
186
- @media (max-width: 420px) {
187
- .wh-panel { width: auto; max-height: min(80vh, 640px); }
188
- .wh-bottom-right, .wh-bottom-left { left: 8px; right: 8px; }
189
- .wh-top-right, .wh-top-left { left: 8px; right: 8px; }
190
- .wh-diff { grid-template-columns: 1fr; }
191
- }
192
- `;
193
- function el(tag, className, text) {
194
- const node = document.createElement(tag);
195
- if (className) node.className = className;
196
- if (text != null) node.textContent = text;
197
- return node;
198
- }
199
- function shortenPath(file) {
200
- const normalized = file.replace(/\\/g, "/");
201
- const marker = normalized.match(/(?:^|\/)(?:src|app|pages|components)\//);
202
- if (marker && marker.index != null) {
203
- return normalized.slice(marker.index).replace(/^\//, "");
204
- }
205
- const parts = normalized.split("/");
206
- return parts.slice(-2).join("/");
207
- }
208
- function renderValue(value) {
209
- if (value == null || value === "") {
210
- return el("span", "wh-empty-value", value === "" ? "(empty)" : "(none)");
211
- }
212
- const span = document.createElement("span");
213
- span.textContent = value;
214
- return span;
215
- }
216
- function renderCard(report) {
217
- const card = el("div", "wh-card");
218
- const head = el("div", "wh-card-head");
219
- head.appendChild(
220
- el("span", "wh-cat", CATEGORY_LABELS[report.cause.category] ?? "Mismatch")
221
- );
222
- head.appendChild(
223
- el("span", "wh-conf", `${Math.round(report.cause.confidence * 100)}%`)
224
- );
225
- card.appendChild(head);
226
- const body = el("div", "wh-body");
227
- const where = el("div", "wh-where");
228
- if (report.component) {
229
- where.appendChild(el("strong", "wh-comp", `<${report.component}>`));
230
- where.append(" \xB7 ");
231
- } else if (report.node.tagName) {
232
- where.append(`<${report.node.tagName.toLowerCase()}> \xB7 `);
233
- }
234
- const code = el("code");
235
- code.textContent = report.node.path;
236
- where.appendChild(code);
237
- if (report.node.attribute) {
238
- where.append(` \xB7 @${report.node.attribute}`);
239
- }
240
- body.appendChild(where);
241
- if (report.location?.file) {
242
- const loc = el("div", "wh-loc");
243
- const file = shortenPath(report.location.file);
244
- loc.textContent = report.location.line ? `${file}:${report.location.line}` : file;
245
- body.appendChild(loc);
246
- }
247
- const diff = el("div", "wh-diff");
248
- const server = el("div", "wh-side wh-server");
249
- server.appendChild(el("span", "wh-label", "Server"));
250
- server.appendChild(renderValue(report.server));
251
- const client = el("div", "wh-side wh-client");
252
- client.appendChild(el("span", "wh-label", "Client"));
253
- client.appendChild(renderValue(report.client));
254
- diff.appendChild(server);
255
- diff.appendChild(client);
256
- body.appendChild(diff);
257
- body.appendChild(el("div", "wh-explain", report.cause.explanation));
258
- const fix = el("div", "wh-fix");
259
- const strong = el("strong");
260
- strong.textContent = "Fix: ";
261
- fix.appendChild(strong);
262
- fix.append(report.cause.suggestion);
263
- body.appendChild(fix);
264
- if (report.cause.docsUrl && /^https?:\/\//i.test(report.cause.docsUrl)) {
265
- const a = el("a", "wh-docs", "Learn more \u2192");
266
- a.href = report.cause.docsUrl;
267
- a.target = "_blank";
268
- a.rel = "noreferrer noopener";
269
- body.appendChild(a);
270
- }
271
- card.appendChild(body);
272
- return card;
273
- }
274
- function createOverlay(options = {}) {
275
- const position = options.position ?? "bottom-right";
276
- let host = null;
277
- let listEl = null;
278
- let countEl = null;
279
- let hintEl = null;
280
- let hintTextEl = null;
281
- let hintCheckTimer = null;
282
- let hintDismissed = false;
283
- let count = 0;
284
- function ensureMounted() {
285
- if (host) return;
286
- document.getElementById(CONTAINER_ID)?.remove();
287
- host = document.createElement("div");
288
- host.id = CONTAINER_ID;
289
- host.setAttribute("data-why-hydration", "overlay");
290
- host.setAttribute("aria-hidden", "false");
291
- host.setAttribute("dir", "ltr");
292
- const shadow = host.attachShadow({ mode: "open" });
293
- const style = document.createElement("style");
294
- style.textContent = STYLES;
295
- shadow.appendChild(style);
296
- const panel = el("div", `wh-panel wh-${position}`);
297
- panel.setAttribute("role", "dialog");
298
- panel.setAttribute("aria-label", "Hydration mismatch diagnostics");
299
- const header = el("div", "wh-header");
300
- header.appendChild(el("span", "wh-dot"));
301
- header.appendChild(el("span", "wh-title", "Hydration mismatch"));
302
- countEl = el("span", "wh-count", "0");
303
- header.appendChild(countEl);
304
- const close = el("button", "wh-btn", "Dismiss");
305
- close.setAttribute("aria-label", "Dismiss diagnostics overlay");
306
- close.addEventListener("click", () => destroy());
307
- header.appendChild(close);
308
- panel.appendChild(header);
309
- listEl = el("div", "wh-list");
310
- panel.appendChild(listEl);
311
- hintEl = el("div", "wh-hint");
312
- hintTextEl = el("span", "wh-hint-text");
313
- hintEl.appendChild(hintTextEl);
314
- const hintX = el("button", "wh-hint-x", "\u2715");
315
- hintX.setAttribute("aria-label", "Dismiss hint");
316
- hintX.addEventListener("click", () => dismissHint());
317
- hintEl.appendChild(hintX);
318
- panel.appendChild(hintEl);
319
- shadow.appendChild(panel);
320
- document.body.appendChild(host);
321
- host.addEventListener("keydown", (e) => {
322
- if (e.key === "Escape") destroy();
323
- });
324
- }
325
- function dismissHint() {
326
- hintDismissed = true;
327
- hintEl?.classList.remove("wh-show");
328
- }
329
- function updateHint() {
330
- if (!hintEl || !hintTextEl || hintDismissed) return;
331
- hintTextEl.textContent = `\u2193 ${count} issues \u2014 scroll to see all`;
332
- if (count >= 4) hintEl.classList.add("wh-show");
333
- }
334
- function destroy() {
335
- if (hintCheckTimer) clearTimeout(hintCheckTimer);
336
- hintCheckTimer = null;
337
- host?.remove();
338
- host = null;
339
- listEl = null;
340
- countEl = null;
341
- hintEl = null;
342
- hintTextEl = null;
343
- count = 0;
344
- hintDismissed = false;
345
- }
346
- function push(report) {
347
- ensureMounted();
348
- count += 1;
349
- if (countEl) countEl.textContent = String(count);
350
- listEl?.appendChild(renderCard(report));
351
- if (hintCheckTimer) clearTimeout(hintCheckTimer);
352
- hintCheckTimer = setTimeout(updateHint, 400);
353
- }
354
- return { push, destroy };
355
- }
356
-
357
- // src/react/fiber.ts
358
- function getFiber(node) {
359
- for (const key in node) {
360
- if (key.startsWith("__reactFiber$") || key.startsWith("__reactInternalInstance$")) {
361
- return node[key] ?? null;
362
- }
363
- }
364
- return null;
365
- }
366
- function componentName(type) {
367
- if (!type || typeof type === "string") return void 0;
368
- if (typeof type === "function") {
369
- const fn = type;
370
- return fn.displayName || fn.name || void 0;
371
- }
372
- if (typeof type === "object") {
373
- const obj = type;
374
- if (obj.displayName) return obj.displayName;
375
- if (obj.render) return obj.render.displayName || obj.render.name;
376
- if (obj.type) return componentName(obj.type);
377
- }
378
- return void 0;
379
- }
380
- function resolveReactSource(node) {
381
- if (!node) return {};
382
- try {
383
- let fiber = getFiber(node);
384
- let component;
385
- let location;
386
- let hops = 0;
387
- while (fiber && hops < 80) {
388
- if (!component) {
389
- const name = componentName(fiber.type);
390
- if (name && !/^[a-z]/.test(name)) component = name;
391
- }
392
- if (!location && fiber._debugSource?.fileName) {
393
- location = {
394
- file: fiber._debugSource.fileName,
395
- line: fiber._debugSource.lineNumber,
396
- column: fiber._debugSource.columnNumber
397
- };
398
- }
399
- if (component && location) break;
400
- fiber = fiber.return ?? null;
401
- hops += 1;
402
- }
403
- const result = {};
404
- if (component) result.component = component;
405
- if (location) result.location = location;
406
- return result;
407
- } catch {
408
- return {};
409
- }
410
- }
411
-
412
- // src/react/controller.ts
413
- var INSPECT_FALLBACK_MS = 50;
414
- function buildIgnore(ignore) {
415
- if (!ignore || ignore.length === 0) return void 0;
416
- return (divergence) => {
417
- const node = divergence.element ?? null;
418
- for (const rule of ignore) {
419
- if (typeof rule === "function") {
420
- if (node && rule(node)) return true;
421
- } else if (node) {
422
- try {
423
- if (node.matches(rule) || node.closest(rule)) return true;
424
- } catch {
425
- }
426
- }
427
- }
428
- return false;
429
- };
430
- }
431
- var InspectorController = class {
432
- constructor(options = {}) {
433
- this.cleanups = [];
434
- this.started = false;
435
- this.pendingContext = {};
436
- this.messages = /* @__PURE__ */ new Set();
437
- this.warnedRoots = /* @__PURE__ */ new Set();
438
- this.settlingTimers = [];
439
- this.inspectScheduled = false;
440
- this.inspectTimer = null;
441
- this.onRecoverableError = (error, info) => {
442
- if (!isDev) return;
443
- const message = error instanceof Error ? error.message : String(error);
444
- this.rememberMessage(message);
445
- this.mergeContext({
446
- componentStack: info?.componentStack,
447
- component: firstComponentFromStack(info?.componentStack),
448
- reactMessage: message
449
- });
450
- this.scheduleInspect();
451
- };
452
- this.options = options;
453
- this.collector = new ReportCollector({
454
- maxReports: options.maxReports,
455
- extra: options.classify,
456
- ignore: buildIgnore(options?.ignore)
457
- });
458
- }
459
- start() {
460
- if (this.started || !isDev || typeof window === "undefined") return;
461
- this.started = true;
462
- if (this.options.onReport) {
463
- this.cleanups.push(this.collector.addSink(this.options.onReport));
464
- }
465
- this.cleanups.push(this.collector.addSink(createConsoleReporter()));
466
- if (this.options.overlay !== false) {
467
- const overlayOpts = typeof this.options.overlay === "object" ? this.options.overlay : {};
468
- const handle = createOverlay(overlayOpts);
469
- this.cleanups.push(
470
- this.collector.addSink(handle.push, { replay: true }),
471
- () => handle.destroy()
472
- );
473
- }
474
- this.cleanups.push(
475
- subscribeCapture((message) => {
476
- this.rememberMessage(message);
477
- this.mergeContext({ reactMessage: message });
478
- this.scheduleInspect();
479
- })
480
- );
481
- this.scheduleSettlingInspections();
482
- }
483
- // React applies client values to mismatched subtrees via a client re-render a
484
- // few hundred ms after the initial hydration commit. We diff once per frame
485
- // and then across this short "settling window" to catch that, deduped. This
486
- // is a bounded, one-shot window — NOT a standing observer — so DOM changes
487
- // from later app state are never mistaken for hydration mismatches.
488
- scheduleSettlingInspections() {
489
- this.scheduleInspect();
490
- for (const delay of [80, 250, 700, 1500]) {
491
- const timer = setTimeout(() => this.inspectAllRoots(), delay);
492
- this.settlingTimers.push(timer);
493
- }
494
- }
495
- rememberMessage(message) {
496
- if (this.messages.size >= MAX_CAPTURED && !this.messages.has(message)) {
497
- return;
498
- }
499
- this.messages.add(message);
500
- }
501
- mergeContext(ctx) {
502
- this.pendingContext = {
503
- componentStack: ctx.componentStack ?? this.pendingContext.componentStack,
504
- component: ctx.component ?? this.pendingContext.component,
505
- location: ctx.location ?? this.pendingContext.location,
506
- reactMessage: ctx.reactMessage ?? this.pendingContext.reactMessage
507
- };
508
- }
509
- domContext() {
510
- return {
511
- componentStack: this.pendingContext.componentStack,
512
- component: this.pendingContext.component,
513
- location: this.pendingContext.location
514
- };
515
- }
516
- stop() {
517
- for (const timer of this.settlingTimers.splice(0)) clearTimeout(timer);
518
- this.clearScheduledInspect();
519
- for (const cleanup of this.cleanups.splice(0)) cleanup();
520
- this.started = false;
521
- }
522
- getReports() {
523
- return this.collector.getReports();
524
- }
525
- inspectNow(context = {}) {
526
- this.mergeContext(context);
527
- this.inspectAllRoots();
528
- }
529
- inspectAllRoots() {
530
- if (!this.started || typeof document === "undefined" || this.collector.isFull) {
531
- return;
532
- }
533
- const configured = this.options.roots;
534
- const selectors = configured ?? snapshotSelectors();
535
- const context = this.domContext();
536
- const seen = /* @__PURE__ */ new Set();
537
- for (const selector of selectors) {
538
- let root = null;
539
- try {
540
- root = document.querySelector(selector);
541
- } catch {
542
- this.warnRoot(selector, `"${selector}" is not a valid CSS selector.`);
543
- continue;
544
- }
545
- if (!root || seen.has(root)) continue;
546
- seen.add(root);
547
- if (configured && getServerHtmlForRoot(root) == null) {
548
- this.warnRoot(
549
- selector,
550
- `no server HTML was captured for "${selector}". Add it to the \`selectors\` prop of <HydrationSnapshotScript> so the server markup for that root is snapshotted.`
551
- );
552
- continue;
553
- }
554
- inspectRoot(
555
- root,
556
- this.collector,
557
- context,
558
- (divergence) => resolveReactSource(divergence.element ?? null)
559
- );
560
- }
561
- for (const message of this.messages) {
562
- reportFromMessage(message, this.collector, this.pendingContext);
563
- }
564
- }
565
- warnRoot(selector, detail) {
566
- if (this.warnedRoots.has(selector)) return;
567
- this.warnedRoots.add(selector);
568
- console.warn(`[why-hydration] Skipping root: ${detail}`);
569
- }
570
- scheduleInspect() {
571
- if (this.inspectScheduled) return;
572
- this.inspectScheduled = true;
573
- const run = () => {
574
- if (!this.inspectScheduled) return;
575
- this.clearScheduledInspect();
576
- this.inspectAllRoots();
577
- };
578
- if (typeof requestAnimationFrame === "function") requestAnimationFrame(run);
579
- this.inspectTimer = setTimeout(run, INSPECT_FALLBACK_MS);
580
- }
581
- clearScheduledInspect() {
582
- this.inspectScheduled = false;
583
- if (this.inspectTimer != null) {
584
- clearTimeout(this.inspectTimer);
585
- this.inspectTimer = null;
586
- }
587
- }
588
- };
589
- function snapshotSelectors() {
590
- const snapshot = readSnapshot();
591
- const keys = snapshot ? Object.keys(snapshot.roots) : [];
592
- return keys.length > 0 ? keys : [...DEFAULT_SNAPSHOT_SELECTORS];
593
- }
594
- function firstComponentFromStack(stack) {
595
- if (!stack) return void 0;
596
- const match = /\n?\s*(?:at|in)\s+([A-Za-z0-9_$]+)/.exec(stack);
597
- return match?.[1];
598
- }
599
- function InspectorImpl(props) {
600
- const { children, overlay, onReport, ignore, classify, maxReports } = props;
601
- startCapture();
602
- const optionsRef = React2.useRef({});
603
- optionsRef.current = { overlay, onReport, ignore, classify, maxReports };
604
- React2.useEffect(() => {
605
- const controller = new InspectorController(optionsRef.current);
606
- controller.start();
607
- return () => controller.stop();
608
- }, []);
609
- return React2.createElement(React2.Fragment, null, children);
610
- }
611
-
612
- // src/react/index.tsx
613
- function PassThrough(props) {
614
- return React2.createElement(React2.Fragment, null, props.children);
615
- }
616
- var HydrationInspector = process.env.NODE_ENV !== "production" ? InspectorImpl : PassThrough;
617
- function createHydrationInspector(options = {}) {
618
- if (process.env.NODE_ENV === "production") {
619
- return {
620
- onRecoverableError: () => {
621
- },
622
- Provider: PassThrough
623
- };
624
- }
625
- const controller = new InspectorController(options);
626
- controller.start();
627
- function Provider(props) {
628
- React2.useEffect(() => {
629
- controller.start();
630
- return () => controller.stop();
631
- }, []);
632
- return React2.createElement(React2.Fragment, null, props.children);
633
- }
634
- return {
635
- onRecoverableError: controller.onRecoverableError,
636
- Provider
637
- };
638
- }
639
-
640
- export { HydrationInspector, createHydrationInspector };
641
- //# sourceMappingURL=chunk-3I6F4K4L.js.map
642
- //# sourceMappingURL=chunk-3I6F4K4L.js.map