Initial commit: Developer Tools MVP with visual editor

- Complete React app with 7 developer tools
- JSON Tool with visual structured editor
- Serialize Tool with visual structured editor
- URL, Base64, CSV/JSON, Beautifier, Diff tools
- Responsive navigation with dropdown menu
- Dark/light mode toggle
- Mobile-responsive design with sticky header
- All tools working with copy/paste functionality
This commit is contained in:
dwindown
2025-08-02 09:31:26 +07:00
commit 7f2dd5260f
45657 changed files with 4730486 additions and 0 deletions

2
node_modules/web-vitals/dist/modules/getCLS.d.ts generated vendored Normal file
View File

@@ -0,0 +1,2 @@
import { ReportHandler } from './types.js';
export declare const getCLS: (onReport: ReportHandler, reportAllChanges?: boolean | undefined) => void;

83
node_modules/web-vitals/dist/modules/getCLS.js generated vendored Normal file
View File

@@ -0,0 +1,83 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { initMetric } from './lib/initMetric.js';
import { observe } from './lib/observe.js';
import { onHidden } from './lib/onHidden.js';
import { onBFCacheRestore } from './lib/onBFCacheRestore.js';
import { bindReporter } from './lib/bindReporter.js';
import { getFCP } from './getFCP.js';
let isMonitoringFCP = false;
let fcpValue = -1;
export const getCLS = (onReport, reportAllChanges) => {
// Start monitoring FCP so we can only report CLS if FCP is also reported.
// Note: this is done to match the current behavior of CrUX.
if (!isMonitoringFCP) {
getFCP((metric) => {
fcpValue = metric.value;
});
isMonitoringFCP = true;
}
const onReportWrapped = (arg) => {
if (fcpValue > -1) {
onReport(arg);
}
};
let metric = initMetric('CLS', 0);
let report;
let sessionValue = 0;
let sessionEntries = [];
const entryHandler = (entry) => {
// Only count layout shifts without recent user input.
if (!entry.hadRecentInput) {
const firstSessionEntry = sessionEntries[0];
const lastSessionEntry = sessionEntries[sessionEntries.length - 1];
// If the entry occurred less than 1 second after the previous entry and
// less than 5 seconds after the first entry in the session, include the
// entry in the current session. Otherwise, start a new session.
if (sessionValue &&
entry.startTime - lastSessionEntry.startTime < 1000 &&
entry.startTime - firstSessionEntry.startTime < 5000) {
sessionValue += entry.value;
sessionEntries.push(entry);
}
else {
sessionValue = entry.value;
sessionEntries = [entry];
}
// If the current session value is larger than the current CLS value,
// update CLS and the entries contributing to it.
if (sessionValue > metric.value) {
metric.value = sessionValue;
metric.entries = sessionEntries;
report();
}
}
};
const po = observe('layout-shift', entryHandler);
if (po) {
report = bindReporter(onReportWrapped, metric, reportAllChanges);
onHidden(() => {
po.takeRecords().map(entryHandler);
report(true);
});
onBFCacheRestore(() => {
sessionValue = 0;
fcpValue = -1;
metric = initMetric('CLS', 0);
report = bindReporter(onReportWrapped, metric, reportAllChanges);
});
}
};

2
node_modules/web-vitals/dist/modules/getFCP.d.ts generated vendored Normal file
View File

@@ -0,0 +1,2 @@
import { ReportHandler } from './types.js';
export declare const getFCP: (onReport: ReportHandler, reportAllChanges?: boolean | undefined) => void;

63
node_modules/web-vitals/dist/modules/getFCP.js generated vendored Normal file
View File

@@ -0,0 +1,63 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { bindReporter } from './lib/bindReporter.js';
import { getVisibilityWatcher } from './lib/getVisibilityWatcher.js';
import { initMetric } from './lib/initMetric.js';
import { observe } from './lib/observe.js';
import { onBFCacheRestore } from './lib/onBFCacheRestore.js';
export const getFCP = (onReport, reportAllChanges) => {
const visibilityWatcher = getVisibilityWatcher();
let metric = initMetric('FCP');
let report;
const entryHandler = (entry) => {
if (entry.name === 'first-contentful-paint') {
if (po) {
po.disconnect();
}
// Only report if the page wasn't hidden prior to the first paint.
if (entry.startTime < visibilityWatcher.firstHiddenTime) {
metric.value = entry.startTime;
metric.entries.push(entry);
report(true);
}
}
};
// TODO(philipwalton): remove the use of `fcpEntry` once this bug is fixed.
// https://bugs.webkit.org/show_bug.cgi?id=225305
// The check for `getEntriesByName` is needed to support Opera:
// https://github.com/GoogleChrome/web-vitals/issues/159
// The check for `window.performance` is needed to support Opera mini:
// https://github.com/GoogleChrome/web-vitals/issues/185
const fcpEntry = window.performance && performance.getEntriesByName &&
performance.getEntriesByName('first-contentful-paint')[0];
const po = fcpEntry ? null : observe('paint', entryHandler);
if (fcpEntry || po) {
report = bindReporter(onReport, metric, reportAllChanges);
if (fcpEntry) {
entryHandler(fcpEntry);
}
onBFCacheRestore((event) => {
metric = initMetric('FCP');
report = bindReporter(onReport, metric, reportAllChanges);
requestAnimationFrame(() => {
requestAnimationFrame(() => {
metric.value = performance.now() - event.timeStamp;
report(true);
});
});
});
}
};

2
node_modules/web-vitals/dist/modules/getFID.d.ts generated vendored Normal file
View File

@@ -0,0 +1,2 @@
import { ReportHandler } from './types.js';
export declare const getFID: (onReport: ReportHandler, reportAllChanges?: boolean | undefined) => void;

66
node_modules/web-vitals/dist/modules/getFID.js generated vendored Normal file
View File

@@ -0,0 +1,66 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { bindReporter } from './lib/bindReporter.js';
import { getVisibilityWatcher } from './lib/getVisibilityWatcher.js';
import { initMetric } from './lib/initMetric.js';
import { observe } from './lib/observe.js';
import { onBFCacheRestore } from './lib/onBFCacheRestore.js';
import { onHidden } from './lib/onHidden.js';
import { firstInputPolyfill, resetFirstInputPolyfill } from './lib/polyfills/firstInputPolyfill.js';
export const getFID = (onReport, reportAllChanges) => {
const visibilityWatcher = getVisibilityWatcher();
let metric = initMetric('FID');
let report;
const entryHandler = (entry) => {
// Only report if the page wasn't hidden prior to the first input.
if (entry.startTime < visibilityWatcher.firstHiddenTime) {
metric.value = entry.processingStart - entry.startTime;
metric.entries.push(entry);
report(true);
}
};
const po = observe('first-input', entryHandler);
report = bindReporter(onReport, metric, reportAllChanges);
if (po) {
onHidden(() => {
po.takeRecords().map(entryHandler);
po.disconnect();
}, true);
}
if (window.__WEB_VITALS_POLYFILL__) {
// Prefer the native implementation if available,
if (!po) {
window.webVitals.firstInputPolyfill(entryHandler);
}
onBFCacheRestore(() => {
metric = initMetric('FID');
report = bindReporter(onReport, metric, reportAllChanges);
window.webVitals.resetFirstInputPolyfill();
window.webVitals.firstInputPolyfill(entryHandler);
});
}
else {
// Only monitor bfcache restores if the browser supports FID natively.
if (po) {
onBFCacheRestore(() => {
metric = initMetric('FID');
report = bindReporter(onReport, metric, reportAllChanges);
resetFirstInputPolyfill();
firstInputPolyfill(entryHandler);
});
}
}
};

2
node_modules/web-vitals/dist/modules/getLCP.d.ts generated vendored Normal file
View File

@@ -0,0 +1,2 @@
import { ReportHandler } from './types.js';
export declare const getLCP: (onReport: ReportHandler, reportAllChanges?: boolean | undefined) => void;

69
node_modules/web-vitals/dist/modules/getLCP.js generated vendored Normal file
View File

@@ -0,0 +1,69 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { bindReporter } from './lib/bindReporter.js';
import { getVisibilityWatcher } from './lib/getVisibilityWatcher.js';
import { initMetric } from './lib/initMetric.js';
import { observe } from './lib/observe.js';
import { onBFCacheRestore } from './lib/onBFCacheRestore.js';
import { onHidden } from './lib/onHidden.js';
const reportedMetricIDs = {};
export const getLCP = (onReport, reportAllChanges) => {
const visibilityWatcher = getVisibilityWatcher();
let metric = initMetric('LCP');
let report;
const entryHandler = (entry) => {
// The startTime attribute returns the value of the renderTime if it is not 0,
// and the value of the loadTime otherwise.
const value = entry.startTime;
// If the page was hidden prior to paint time of the entry,
// ignore it and mark the metric as final, otherwise add the entry.
if (value < visibilityWatcher.firstHiddenTime) {
metric.value = value;
metric.entries.push(entry);
report();
}
};
const po = observe('largest-contentful-paint', entryHandler);
if (po) {
report = bindReporter(onReport, metric, reportAllChanges);
const stopListening = () => {
if (!reportedMetricIDs[metric.id]) {
po.takeRecords().map(entryHandler);
po.disconnect();
reportedMetricIDs[metric.id] = true;
report(true);
}
};
// Stop listening after input. Note: while scrolling is an input that
// stop LCP observation, it's unreliable since it can be programmatically
// generated. See: https://github.com/GoogleChrome/web-vitals/issues/75
['keydown', 'click'].forEach((type) => {
addEventListener(type, stopListening, { once: true, capture: true });
});
onHidden(stopListening, true);
onBFCacheRestore((event) => {
metric = initMetric('LCP');
report = bindReporter(onReport, metric, reportAllChanges);
requestAnimationFrame(() => {
requestAnimationFrame(() => {
metric.value = performance.now() - event.timeStamp;
reportedMetricIDs[metric.id] = true;
report(true);
});
});
});
}
};

2
node_modules/web-vitals/dist/modules/getTTFB.d.ts generated vendored Normal file
View File

@@ -0,0 +1,2 @@
import { ReportHandler } from './types.js';
export declare const getTTFB: (onReport: ReportHandler) => void;

64
node_modules/web-vitals/dist/modules/getTTFB.js generated vendored Normal file
View File

@@ -0,0 +1,64 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { initMetric } from './lib/initMetric.js';
const afterLoad = (callback) => {
if (document.readyState === 'complete') {
// Queue a task so the callback runs after `loadEventEnd`.
setTimeout(callback, 0);
}
else {
// Queue a task so the callback runs after `loadEventEnd`.
addEventListener('load', () => setTimeout(callback, 0));
}
};
const getNavigationEntryFromPerformanceTiming = () => {
// Really annoying that TypeScript errors when using `PerformanceTiming`.
const timing = performance.timing;
const navigationEntry = {
entryType: 'navigation',
startTime: 0,
};
for (const key in timing) {
if (key !== 'navigationStart' && key !== 'toJSON') {
navigationEntry[key] = Math.max(timing[key] -
timing.navigationStart, 0);
}
}
return navigationEntry;
};
export const getTTFB = (onReport) => {
const metric = initMetric('TTFB');
afterLoad(() => {
try {
// Use the NavigationTiming L2 entry if available.
const navigationEntry = performance.getEntriesByType('navigation')[0] ||
getNavigationEntryFromPerformanceTiming();
metric.value = metric.delta =
navigationEntry.responseStart;
// In some cases the value reported is negative or is larger
// than the current page time. Ignore these cases:
// https://github.com/GoogleChrome/web-vitals/issues/137
// https://github.com/GoogleChrome/web-vitals/issues/162
if (metric.value < 0 || metric.value > performance.now())
return;
metric.entries = [navigationEntry];
onReport(metric);
}
catch (error) {
// Do nothing.
}
});
};

6
node_modules/web-vitals/dist/modules/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,6 @@
export { getCLS } from './getCLS.js';
export { getFCP } from './getFCP.js';
export { getFID } from './getFID.js';
export { getLCP } from './getLCP.js';
export { getTTFB } from './getTTFB.js';
export * from './types.js';

21
node_modules/web-vitals/dist/modules/index.js generated vendored Normal file
View File

@@ -0,0 +1,21 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { getCLS } from './getCLS.js';
export { getFCP } from './getFCP.js';
export { getFID } from './getFID.js';
export { getLCP } from './getLCP.js';
export { getTTFB } from './getTTFB.js';
export * from './types.js';

View File

@@ -0,0 +1,2 @@
import { Metric, ReportHandler } from '../types.js';
export declare const bindReporter: (callback: ReportHandler, metric: Metric, reportAllChanges?: boolean | undefined) => (forceReport?: boolean | undefined) => void;

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export const bindReporter = (callback, metric, reportAllChanges) => {
let prevValue;
return (forceReport) => {
if (metric.value >= 0) {
if (forceReport || reportAllChanges) {
metric.delta = metric.value - (prevValue || 0);
// Report the metric if there's a non-zero delta or if no previous
// value exists (which can happen in the case of the document becoming
// hidden when the metric value is 0).
// See: https://github.com/GoogleChrome/web-vitals/issues/14
if (metric.delta || prevValue === undefined) {
prevValue = metric.value;
callback(metric);
}
}
}
};
};

View File

@@ -0,0 +1,6 @@
/**
* Performantly generate a unique, 30-char string by combining a version
* number, the current timestamp with a 13-digit number integer.
* @return {string}
*/
export declare const generateUniqueID: () => string;

View File

@@ -0,0 +1,23 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Performantly generate a unique, 30-char string by combining a version
* number, the current timestamp with a 13-digit number integer.
* @return {string}
*/
export const generateUniqueID = () => {
return `v2-${Date.now()}-${Math.floor(Math.random() * (9e12 - 1)) + 1e12}`;
};

View File

@@ -0,0 +1,3 @@
export declare const getVisibilityWatcher: () => {
readonly firstHiddenTime: number;
};

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { onBFCacheRestore } from './onBFCacheRestore.js';
import { onHidden } from './onHidden.js';
let firstHiddenTime = -1;
const initHiddenTime = () => {
return document.visibilityState === 'hidden' ? 0 : Infinity;
};
const trackChanges = () => {
// Update the time if/when the document becomes hidden.
onHidden(({ timeStamp }) => {
firstHiddenTime = timeStamp;
}, true);
};
export const getVisibilityWatcher = () => {
if (firstHiddenTime < 0) {
// If the document is hidden when this code runs, assume it was hidden
// since navigation start. This isn't a perfect heuristic, but it's the
// best we can do until an API is available to support querying past
// visibilityState.
if (window.__WEB_VITALS_POLYFILL__) {
firstHiddenTime = window.webVitals.firstHiddenTime;
if (firstHiddenTime === Infinity) {
trackChanges();
}
}
else {
firstHiddenTime = initHiddenTime();
trackChanges();
}
// Reset the time on bfcache restores.
onBFCacheRestore(() => {
// Schedule a task in order to track the `visibilityState` once it's
// had an opportunity to change to visible in all browsers.
// https://bugs.chromium.org/p/chromium/issues/detail?id=1133363
setTimeout(() => {
firstHiddenTime = initHiddenTime();
trackChanges();
}, 0);
});
}
return {
get firstHiddenTime() {
return firstHiddenTime;
}
};
};

View File

@@ -0,0 +1,2 @@
import { Metric } from '../types.js';
export declare const initMetric: (name: Metric['name'], value?: number | undefined) => Metric;

25
node_modules/web-vitals/dist/modules/lib/initMetric.js generated vendored Normal file
View File

@@ -0,0 +1,25 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { generateUniqueID } from './generateUniqueID.js';
export const initMetric = (name, value) => {
return {
name,
value: typeof value === 'undefined' ? -1 : value,
delta: 0,
entries: [],
id: generateUniqueID()
};
};

12
node_modules/web-vitals/dist/modules/lib/observe.d.ts generated vendored Normal file
View File

@@ -0,0 +1,12 @@
export interface PerformanceEntryHandler {
(entry: PerformanceEntry): void;
}
/**
* Takes a performance entry type and a callback function, and creates a
* `PerformanceObserver` instance that will observe the specified entry type
* with buffering enabled and call the callback _for each entry_.
*
* This function also feature-detects entry support and wraps the logic in a
* try/catch to avoid errors in unsupporting browsers.
*/
export declare const observe: (type: string, callback: PerformanceEntryHandler) => PerformanceObserver | undefined;

41
node_modules/web-vitals/dist/modules/lib/observe.js generated vendored Normal file
View File

@@ -0,0 +1,41 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Takes a performance entry type and a callback function, and creates a
* `PerformanceObserver` instance that will observe the specified entry type
* with buffering enabled and call the callback _for each entry_.
*
* This function also feature-detects entry support and wraps the logic in a
* try/catch to avoid errors in unsupporting browsers.
*/
export const observe = (type, callback) => {
try {
if (PerformanceObserver.supportedEntryTypes.includes(type)) {
// More extensive feature detect needed for Firefox due to:
// https://github.com/GoogleChrome/web-vitals/issues/142
if (type === 'first-input' && !('PerformanceEventTiming' in self)) {
return;
}
const po = new PerformanceObserver((l) => l.getEntries().map(callback));
po.observe({ type, buffered: true });
return po;
}
}
catch (e) {
// Do nothing.
}
return;
};

View File

@@ -0,0 +1,5 @@
interface onBFCacheRestoreCallback {
(event: PageTransitionEvent): void;
}
export declare const onBFCacheRestore: (cb: onBFCacheRestoreCallback) => void;
export {};

View File

@@ -0,0 +1,22 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export const onBFCacheRestore = (cb) => {
addEventListener('pageshow', (event) => {
if (event.persisted) {
cb(event);
}
}, true);
};

View File

@@ -0,0 +1,4 @@
export interface OnHiddenCallback {
(event: Event): void;
}
export declare const onHidden: (cb: OnHiddenCallback, once?: boolean | undefined) => void;

30
node_modules/web-vitals/dist/modules/lib/onHidden.js generated vendored Normal file
View File

@@ -0,0 +1,30 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export const onHidden = (cb, once) => {
const onHiddenOrPageHide = (event) => {
if (event.type === 'pagehide' || document.visibilityState === 'hidden') {
cb(event);
if (once) {
removeEventListener('visibilitychange', onHiddenOrPageHide, true);
removeEventListener('pagehide', onHiddenOrPageHide, true);
}
}
};
addEventListener('visibilitychange', onHiddenOrPageHide, true);
// Some browsers have buggy implementations of visibilitychange,
// so we use pagehide in addition, just to be safe.
addEventListener('pagehide', onHiddenOrPageHide, true);
};

View File

@@ -0,0 +1,7 @@
import { FirstInputPolyfillCallback } from '../../types.js';
/**
* Accepts a callback to be invoked once the first input delay and event
* are known.
*/
export declare const firstInputPolyfill: (onFirstInput: FirstInputPolyfillCallback) => void;
export declare const resetFirstInputPolyfill: () => void;

View File

@@ -0,0 +1,152 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
let firstInputEvent;
let firstInputDelay;
let firstInputTimeStamp;
let callbacks;
const listenerOpts = { passive: true, capture: true };
const startTimeStamp = new Date();
/**
* Accepts a callback to be invoked once the first input delay and event
* are known.
*/
export const firstInputPolyfill = (onFirstInput) => {
callbacks.push(onFirstInput);
reportFirstInputDelayIfRecordedAndValid();
};
export const resetFirstInputPolyfill = () => {
callbacks = [];
firstInputDelay = -1;
firstInputEvent = null;
eachEventType(addEventListener);
};
/**
* Records the first input delay and event, so subsequent events can be
* ignored. All added event listeners are then removed.
*/
const recordFirstInputDelay = (delay, event) => {
if (!firstInputEvent) {
firstInputEvent = event;
firstInputDelay = delay;
firstInputTimeStamp = new Date;
eachEventType(removeEventListener);
reportFirstInputDelayIfRecordedAndValid();
}
};
/**
* Reports the first input delay and event (if they're recorded and valid)
* by running the array of callback functions.
*/
const reportFirstInputDelayIfRecordedAndValid = () => {
// In some cases the recorded delay is clearly wrong, e.g. it's negative
// or it's larger than the delta between now and initialization.
// - https://github.com/GoogleChromeLabs/first-input-delay/issues/4
// - https://github.com/GoogleChromeLabs/first-input-delay/issues/6
// - https://github.com/GoogleChromeLabs/first-input-delay/issues/7
if (firstInputDelay >= 0 &&
// @ts-ignore (subtracting two dates always returns a number)
firstInputDelay < firstInputTimeStamp - startTimeStamp) {
const entry = {
entryType: 'first-input',
name: firstInputEvent.type,
target: firstInputEvent.target,
cancelable: firstInputEvent.cancelable,
startTime: firstInputEvent.timeStamp,
processingStart: firstInputEvent.timeStamp + firstInputDelay,
};
callbacks.forEach(function (callback) {
callback(entry);
});
callbacks = [];
}
};
/**
* Handles pointer down events, which are a special case.
* Pointer events can trigger main or compositor thread behavior.
* We differentiate these cases based on whether or not we see a
* 'pointercancel' event, which are fired when we scroll. If we're scrolling
* we don't need to report input delay since FID excludes scrolling and
* pinch/zooming.
*/
const onPointerDown = (delay, event) => {
/**
* Responds to 'pointerup' events and records a delay. If a pointer up event
* is the next event after a pointerdown event, then it's not a scroll or
* a pinch/zoom.
*/
const onPointerUp = () => {
recordFirstInputDelay(delay, event);
removePointerEventListeners();
};
/**
* Responds to 'pointercancel' events and removes pointer listeners.
* If a 'pointercancel' is the next event to fire after a pointerdown event,
* it means this is a scroll or pinch/zoom interaction.
*/
const onPointerCancel = () => {
removePointerEventListeners();
};
/**
* Removes added pointer event listeners.
*/
const removePointerEventListeners = () => {
removeEventListener('pointerup', onPointerUp, listenerOpts);
removeEventListener('pointercancel', onPointerCancel, listenerOpts);
};
addEventListener('pointerup', onPointerUp, listenerOpts);
addEventListener('pointercancel', onPointerCancel, listenerOpts);
};
/**
* Handles all input events and records the time between when the event
* was received by the operating system and when it's JavaScript listeners
* were able to run.
*/
const onInput = (event) => {
// Only count cancelable events, which should trigger behavior
// important to the user.
if (event.cancelable) {
// In some browsers `event.timeStamp` returns a `DOMTimeStamp` value
// (epoch time) instead of the newer `DOMHighResTimeStamp`
// (document-origin time). To check for that we assume any timestamp
// greater than 1 trillion is a `DOMTimeStamp`, and compare it using
// the `Date` object rather than `performance.now()`.
// - https://github.com/GoogleChromeLabs/first-input-delay/issues/4
const isEpochTime = event.timeStamp > 1e12;
const now = isEpochTime ? new Date : performance.now();
// Input delay is the delta between when the system received the event
// (e.g. event.timeStamp) and when it could run the callback (e.g. `now`).
const delay = now - event.timeStamp;
if (event.type == 'pointerdown') {
onPointerDown(delay, event);
}
else {
recordFirstInputDelay(delay, event);
}
}
};
/**
* Invokes the passed callback const for = each event type with t =>he
* `onInput` const and = `listenerOpts =>`.
*/
const eachEventType = (callback) => {
const eventTypes = [
'mousedown',
'keydown',
'touchstart',
'pointerdown',
];
eventTypes.forEach((type) => callback(type, onInput, listenerOpts));
};

View File

@@ -0,0 +1 @@
export declare const getFirstHiddenTime: () => number;

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
let firstHiddenTime = document.visibilityState === 'hidden' ? 0 : Infinity;
const onVisibilityChange = (event) => {
if (document.visibilityState === 'hidden') {
firstHiddenTime = event.timeStamp;
removeEventListener('visibilitychange', onVisibilityChange, true);
}
};
// Note: do not add event listeners unconditionally (outside of polyfills).
addEventListener('visibilitychange', onVisibilityChange, true);
export const getFirstHiddenTime = () => firstHiddenTime;

1
node_modules/web-vitals/dist/modules/polyfill.d.ts generated vendored Normal file
View File

@@ -0,0 +1 @@
export {};

25
node_modules/web-vitals/dist/modules/polyfill.js generated vendored Normal file
View File

@@ -0,0 +1,25 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { firstInputPolyfill, resetFirstInputPolyfill } from './lib/polyfills/firstInputPolyfill.js';
import { getFirstHiddenTime } from './lib/polyfills/getFirstHiddenTimePolyfill.js';
resetFirstInputPolyfill();
self.webVitals = {
firstInputPolyfill: firstInputPolyfill,
resetFirstInputPolyfill: resetFirstInputPolyfill,
get firstHiddenTime() {
return getFirstHiddenTime();
},
};

33
node_modules/web-vitals/dist/modules/types.d.ts generated vendored Normal file
View File

@@ -0,0 +1,33 @@
export interface Metric {
name: 'CLS' | 'FCP' | 'FID' | 'LCP' | 'TTFB';
value: number;
delta: number;
id: string;
entries: (PerformanceEntry | FirstInputPolyfillEntry | NavigationTimingPolyfillEntry)[];
}
export interface ReportHandler {
(metric: Metric): void;
}
export interface PerformanceEventTiming extends PerformanceEntry {
processingStart: DOMHighResTimeStamp;
processingEnd: DOMHighResTimeStamp;
duration: DOMHighResTimeStamp;
cancelable?: boolean;
target?: Element;
}
export declare type FirstInputPolyfillEntry = Omit<PerformanceEventTiming, 'processingEnd' | 'toJSON'>;
export interface FirstInputPolyfillCallback {
(entry: FirstInputPolyfillEntry): void;
}
export declare type NavigationTimingPolyfillEntry = Omit<PerformanceNavigationTiming, 'initiatorType' | 'nextHopProtocol' | 'redirectCount' | 'transferSize' | 'encodedBodySize' | 'decodedBodySize' | 'toJSON'>;
export interface WebVitalsGlobal {
firstInputPolyfill: (onFirstInput: FirstInputPolyfillCallback) => void;
resetFirstInputPolyfill: () => void;
firstHiddenTime: number;
}
declare global {
interface Window {
webVitals: WebVitalsGlobal;
__WEB_VITALS_POLYFILL__: boolean;
}
}

16
node_modules/web-vitals/dist/modules/types.js generated vendored Normal file
View File

@@ -0,0 +1,16 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export {};