Revisão Logo, Favicon, Robots

This commit is contained in:
2026-05-30 23:54:48 -03:00
parent 3e91269f12
commit 4e26c13240
105 changed files with 8137 additions and 693 deletions
+35
View File
@@ -5904,6 +5904,15 @@
"node": ">=12"
}
},
"node_modules/invariant": {
"version": "2.2.4",
"resolved": "https://registry.npmmirror.com/invariant/-/invariant-2.2.4.tgz",
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.0.0"
}
},
"node_modules/is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@@ -6828,6 +6837,26 @@
"react": "^19.2.3"
}
},
"node_modules/react-fast-compare": {
"version": "3.2.2",
"resolved": "https://registry.npmmirror.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz",
"integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==",
"license": "MIT"
},
"node_modules/react-helmet-async": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/react-helmet-async/-/react-helmet-async-3.0.0.tgz",
"integrity": "sha512-nA3IEZfXiclgrz4KLxAhqJqIfFDuvzQwlKwpdmzZIuC1KNSghDEIXmyU0TKtbM+NafnkICcwx8CECFrZ/sL/1w==",
"license": "Apache-2.0",
"dependencies": {
"invariant": "^2.2.4",
"react-fast-compare": "^3.2.2",
"shallowequal": "^1.1.0"
},
"peerDependencies": {
"react": "^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/react-hook-form": {
"version": "7.70.0",
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.70.0.tgz",
@@ -7291,6 +7320,12 @@
"integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
"license": "MIT"
},
"node_modules/shallowequal": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/shallowequal/-/shallowequal-1.1.0.tgz",
"integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==",
"license": "MIT"
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+3 -3
View File
@@ -1,7 +1,7 @@
"use client";
import {
Presence
} from "./chunk-3Q7Z2X7T.js";
} from "./chunk-INCCO7S4.js";
import {
Combination_default,
DismissableLayer,
@@ -9,7 +9,7 @@ import {
Portal,
hideOthers,
useFocusGuards
} from "./chunk-26QBR6Z3.js";
} from "./chunk-LIWCVBQK.js";
import {
Primitive,
composeEventHandlers,
@@ -17,7 +17,7 @@ import {
createContextScope,
useControllableState,
useId
} from "./chunk-2ZZW3GAI.js";
} from "./chunk-MOFDO34C.js";
import {
composeRefs,
useComposedRefs
+3 -3
View File
@@ -1,13 +1,13 @@
"use client";
import {
Presence
} from "./chunk-3Q7Z2X7T.js";
} from "./chunk-INCCO7S4.js";
import {
createCollection,
useDirection,
usePrevious,
useSize
} from "./chunk-5BI4FO63.js";
} from "./chunk-L2OGOWTU.js";
import {
Primitive,
composeEventHandlers,
@@ -15,7 +15,7 @@ import {
useCallbackRef,
useControllableState,
useId
} from "./chunk-2ZZW3GAI.js";
} from "./chunk-MOFDO34C.js";
import {
useComposedRefs
} from "./chunk-2VUH7NEY.js";
+8 -8
View File
@@ -1,10 +1,4 @@
"use client";
import {
createCollection,
useDirection,
usePrevious,
useSize
} from "./chunk-5BI4FO63.js";
import {
Combination_default,
DismissableLayer,
@@ -12,7 +6,13 @@ import {
Portal,
hideOthers,
useFocusGuards
} from "./chunk-26QBR6Z3.js";
} from "./chunk-LIWCVBQK.js";
import {
createCollection,
useDirection,
usePrevious,
useSize
} from "./chunk-L2OGOWTU.js";
import {
Primitive,
composeEventHandlers,
@@ -21,7 +21,7 @@ import {
useControllableState,
useId,
useLayoutEffect2
} from "./chunk-2ZZW3GAI.js";
} from "./chunk-MOFDO34C.js";
import {
composeRefs,
useComposedRefs
+35 -29
View File
@@ -1,109 +1,115 @@
{
"hash": "baa9bd3c",
"hash": "bef2a67d",
"configHash": "eccf13b4",
"lockfileHash": "f25e87f1",
"browserHash": "4d672fc5",
"lockfileHash": "c4c8a51c",
"browserHash": "e7109be1",
"optimized": {
"react": {
"src": "../../react/index.js",
"file": "react.js",
"fileHash": "2adf36c9",
"fileHash": "d2a0cc23",
"needsInterop": true
},
"react-dom": {
"src": "../../react-dom/index.js",
"file": "react-dom.js",
"fileHash": "91ce8077",
"fileHash": "e89a827d",
"needsInterop": true
},
"react/jsx-dev-runtime": {
"src": "../../react/jsx-dev-runtime.js",
"file": "react_jsx-dev-runtime.js",
"fileHash": "051319e1",
"fileHash": "9ce7ae60",
"needsInterop": true
},
"react/jsx-runtime": {
"src": "../../react/jsx-runtime.js",
"file": "react_jsx-runtime.js",
"fileHash": "1945fc54",
"fileHash": "8ba5f754",
"needsInterop": true
},
"@radix-ui/react-dialog": {
"src": "../../@radix-ui/react-dialog/dist/index.mjs",
"file": "@radix-ui_react-dialog.js",
"fileHash": "8be1d102",
"fileHash": "312e2a39",
"needsInterop": false
},
"@radix-ui/react-label": {
"src": "../../@radix-ui/react-label/dist/index.mjs",
"file": "@radix-ui_react-label.js",
"fileHash": "7c8329ad",
"fileHash": "3b607856",
"needsInterop": false
},
"@radix-ui/react-radio-group": {
"src": "../../@radix-ui/react-radio-group/dist/index.mjs",
"file": "@radix-ui_react-radio-group.js",
"fileHash": "c1cf0673",
"fileHash": "20509c2b",
"needsInterop": false
},
"@radix-ui/react-select": {
"src": "../../@radix-ui/react-select/dist/index.mjs",
"file": "@radix-ui_react-select.js",
"fileHash": "e8a77682",
"fileHash": "91be68ee",
"needsInterop": false
},
"@radix-ui/react-slot": {
"src": "../../@radix-ui/react-slot/dist/index.mjs",
"file": "@radix-ui_react-slot.js",
"fileHash": "4581ea7a",
"fileHash": "ce1344ec",
"needsInterop": false
},
"class-variance-authority": {
"src": "../../class-variance-authority/dist/index.mjs",
"file": "class-variance-authority.js",
"fileHash": "e840c683",
"fileHash": "0e5017ff",
"needsInterop": false
},
"clsx": {
"src": "../../clsx/dist/clsx.mjs",
"file": "clsx.js",
"fileHash": "79cbbdef",
"fileHash": "2dbad761",
"needsInterop": false
},
"gsap": {
"src": "../../gsap/index.js",
"file": "gsap.js",
"fileHash": "096a1c72",
"fileHash": "1393a05a",
"needsInterop": false
},
"gsap/ScrollTrigger": {
"src": "../../gsap/ScrollTrigger.js",
"file": "gsap_ScrollTrigger.js",
"fileHash": "05527b31",
"fileHash": "965be7ec",
"needsInterop": false
},
"lucide-react": {
"src": "../../lucide-react/dist/esm/lucide-react.js",
"file": "lucide-react.js",
"fileHash": "4c9ad860",
"fileHash": "3db69029",
"needsInterop": false
},
"react-dom/client": {
"src": "../../react-dom/client.js",
"file": "react-dom_client.js",
"fileHash": "93025179",
"fileHash": "8798ba07",
"needsInterop": true
},
"react-helmet-async": {
"src": "../../react-helmet-async/lib/index.esm.js",
"file": "react-helmet-async.js",
"fileHash": "60b58734",
"needsInterop": false
},
"react-router-dom": {
"src": "../../react-router-dom/dist/index.mjs",
"file": "react-router-dom.js",
"fileHash": "a7f80be8",
"fileHash": "03035af3",
"needsInterop": false
},
"tailwind-merge": {
"src": "../../tailwind-merge/dist/bundle-mjs.mjs",
"file": "tailwind-merge.js",
"fileHash": "e87c9277",
"fileHash": "afbfe76d",
"needsInterop": false
}
},
@@ -111,20 +117,20 @@
"chunk-U7P2NEEE": {
"file": "chunk-U7P2NEEE.js"
},
"chunk-INCCO7S4": {
"file": "chunk-INCCO7S4.js"
},
"chunk-YWBEB5PG": {
"file": "chunk-YWBEB5PG.js"
},
"chunk-3Q7Z2X7T": {
"file": "chunk-3Q7Z2X7T.js"
"chunk-LIWCVBQK": {
"file": "chunk-LIWCVBQK.js"
},
"chunk-5BI4FO63": {
"file": "chunk-5BI4FO63.js"
"chunk-L2OGOWTU": {
"file": "chunk-L2OGOWTU.js"
},
"chunk-26QBR6Z3": {
"file": "chunk-26QBR6Z3.js"
},
"chunk-2ZZW3GAI": {
"file": "chunk-2ZZW3GAI.js"
"chunk-MOFDO34C": {
"file": "chunk-MOFDO34C.js"
},
"chunk-2VUH7NEY": {
"file": "chunk-2VUH7NEY.js"
+143
View File
@@ -0,0 +1,143 @@
import {
useLayoutEffect2
} from "./chunk-MOFDO34C.js";
import {
useComposedRefs
} from "./chunk-2VUH7NEY.js";
import {
require_react
} from "./chunk-WUR7D6NS.js";
import {
__toESM
} from "./chunk-G3PMV62Z.js";
// node_modules/@radix-ui/react-presence/dist/index.mjs
var React2 = __toESM(require_react(), 1);
var React = __toESM(require_react(), 1);
function useStateMachine(initialState, machine) {
return React.useReducer((state, event) => {
const nextState = machine[state][event];
return nextState ?? state;
}, initialState);
}
var Presence = (props) => {
const { present, children } = props;
const presence = usePresence(present);
const child = typeof children === "function" ? children({ present: presence.isPresent }) : React2.Children.only(children);
const ref = useComposedRefs(presence.ref, getElementRef(child));
const forceMount = typeof children === "function";
return forceMount || presence.isPresent ? React2.cloneElement(child, { ref }) : null;
};
Presence.displayName = "Presence";
function usePresence(present) {
const [node, setNode] = React2.useState();
const stylesRef = React2.useRef(null);
const prevPresentRef = React2.useRef(present);
const prevAnimationNameRef = React2.useRef("none");
const initialState = present ? "mounted" : "unmounted";
const [state, send] = useStateMachine(initialState, {
mounted: {
UNMOUNT: "unmounted",
ANIMATION_OUT: "unmountSuspended"
},
unmountSuspended: {
MOUNT: "mounted",
ANIMATION_END: "unmounted"
},
unmounted: {
MOUNT: "mounted"
}
});
React2.useEffect(() => {
const currentAnimationName = getAnimationName(stylesRef.current);
prevAnimationNameRef.current = state === "mounted" ? currentAnimationName : "none";
}, [state]);
useLayoutEffect2(() => {
const styles = stylesRef.current;
const wasPresent = prevPresentRef.current;
const hasPresentChanged = wasPresent !== present;
if (hasPresentChanged) {
const prevAnimationName = prevAnimationNameRef.current;
const currentAnimationName = getAnimationName(styles);
if (present) {
send("MOUNT");
} else if (currentAnimationName === "none" || styles?.display === "none") {
send("UNMOUNT");
} else {
const isAnimating = prevAnimationName !== currentAnimationName;
if (wasPresent && isAnimating) {
send("ANIMATION_OUT");
} else {
send("UNMOUNT");
}
}
prevPresentRef.current = present;
}
}, [present, send]);
useLayoutEffect2(() => {
if (node) {
let timeoutId;
const ownerWindow = node.ownerDocument.defaultView ?? window;
const handleAnimationEnd = (event) => {
const currentAnimationName = getAnimationName(stylesRef.current);
const isCurrentAnimation = currentAnimationName.includes(CSS.escape(event.animationName));
if (event.target === node && isCurrentAnimation) {
send("ANIMATION_END");
if (!prevPresentRef.current) {
const currentFillMode = node.style.animationFillMode;
node.style.animationFillMode = "forwards";
timeoutId = ownerWindow.setTimeout(() => {
if (node.style.animationFillMode === "forwards") {
node.style.animationFillMode = currentFillMode;
}
});
}
}
};
const handleAnimationStart = (event) => {
if (event.target === node) {
prevAnimationNameRef.current = getAnimationName(stylesRef.current);
}
};
node.addEventListener("animationstart", handleAnimationStart);
node.addEventListener("animationcancel", handleAnimationEnd);
node.addEventListener("animationend", handleAnimationEnd);
return () => {
ownerWindow.clearTimeout(timeoutId);
node.removeEventListener("animationstart", handleAnimationStart);
node.removeEventListener("animationcancel", handleAnimationEnd);
node.removeEventListener("animationend", handleAnimationEnd);
};
} else {
send("ANIMATION_END");
}
}, [node, send]);
return {
isPresent: ["mounted", "unmountSuspended"].includes(state),
ref: React2.useCallback((node2) => {
stylesRef.current = node2 ? getComputedStyle(node2) : null;
setNode(node2);
}, [])
};
}
function getAnimationName(styles) {
return styles?.animationName || "none";
}
function getElementRef(element) {
let getter = Object.getOwnPropertyDescriptor(element.props, "ref")?.get;
let mayWarn = getter && "isReactWarning" in getter && getter.isReactWarning;
if (mayWarn) {
return element.ref;
}
getter = Object.getOwnPropertyDescriptor(element, "ref")?.get;
mayWarn = getter && "isReactWarning" in getter && getter.isReactWarning;
if (mayWarn) {
return element.props.ref;
}
return element.props.ref || element.ref;
}
export {
Presence
};
//# sourceMappingURL=chunk-INCCO7S4.js.map
File diff suppressed because one or more lines are too long
+248
View File
@@ -0,0 +1,248 @@
import {
createContextScope,
useLayoutEffect2
} from "./chunk-MOFDO34C.js";
import {
composeRefs,
useComposedRefs
} from "./chunk-2VUH7NEY.js";
import {
require_jsx_runtime
} from "./chunk-2YVA4HRZ.js";
import {
require_react
} from "./chunk-WUR7D6NS.js";
import {
__toESM
} from "./chunk-G3PMV62Z.js";
// node_modules/@radix-ui/react-collection/dist/index.mjs
var import_react = __toESM(require_react(), 1);
// node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs
var React = __toESM(require_react(), 1);
var import_jsx_runtime = __toESM(require_jsx_runtime(), 1);
function createSlot(ownerName) {
const SlotClone = createSlotClone(ownerName);
const Slot2 = React.forwardRef((props, forwardedRef) => {
const { children, ...slotProps } = props;
const childrenArray = React.Children.toArray(children);
const slottable = childrenArray.find(isSlottable);
if (slottable) {
const newElement = slottable.props.children;
const newChildren = childrenArray.map((child) => {
if (child === slottable) {
if (React.Children.count(newElement) > 1) return React.Children.only(null);
return React.isValidElement(newElement) ? newElement.props.children : null;
} else {
return child;
}
});
return (0, import_jsx_runtime.jsx)(SlotClone, { ...slotProps, ref: forwardedRef, children: React.isValidElement(newElement) ? React.cloneElement(newElement, void 0, newChildren) : null });
}
return (0, import_jsx_runtime.jsx)(SlotClone, { ...slotProps, ref: forwardedRef, children });
});
Slot2.displayName = `${ownerName}.Slot`;
return Slot2;
}
var Slot = createSlot("Slot");
function createSlotClone(ownerName) {
const SlotClone = React.forwardRef((props, forwardedRef) => {
const { children, ...slotProps } = props;
if (React.isValidElement(children)) {
const childrenRef = getElementRef(children);
const props2 = mergeProps(slotProps, children.props);
if (children.type !== React.Fragment) {
props2.ref = forwardedRef ? composeRefs(forwardedRef, childrenRef) : childrenRef;
}
return React.cloneElement(children, props2);
}
return React.Children.count(children) > 1 ? React.Children.only(null) : null;
});
SlotClone.displayName = `${ownerName}.SlotClone`;
return SlotClone;
}
var SLOTTABLE_IDENTIFIER = /* @__PURE__ */ Symbol("radix.slottable");
function createSlottable(ownerName) {
const Slottable2 = ({ children }) => {
return (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children });
};
Slottable2.displayName = `${ownerName}.Slottable`;
Slottable2.__radixId = SLOTTABLE_IDENTIFIER;
return Slottable2;
}
var Slottable = createSlottable("Slottable");
function isSlottable(child) {
return React.isValidElement(child) && typeof child.type === "function" && "__radixId" in child.type && child.type.__radixId === SLOTTABLE_IDENTIFIER;
}
function mergeProps(slotProps, childProps) {
const overrideProps = { ...childProps };
for (const propName in childProps) {
const slotPropValue = slotProps[propName];
const childPropValue = childProps[propName];
const isHandler = /^on[A-Z]/.test(propName);
if (isHandler) {
if (slotPropValue && childPropValue) {
overrideProps[propName] = (...args) => {
const result = childPropValue(...args);
slotPropValue(...args);
return result;
};
} else if (slotPropValue) {
overrideProps[propName] = slotPropValue;
}
} else if (propName === "style") {
overrideProps[propName] = { ...slotPropValue, ...childPropValue };
} else if (propName === "className") {
overrideProps[propName] = [slotPropValue, childPropValue].filter(Boolean).join(" ");
}
}
return { ...slotProps, ...overrideProps };
}
function getElementRef(element) {
let getter = Object.getOwnPropertyDescriptor(element.props, "ref")?.get;
let mayWarn = getter && "isReactWarning" in getter && getter.isReactWarning;
if (mayWarn) {
return element.ref;
}
getter = Object.getOwnPropertyDescriptor(element, "ref")?.get;
mayWarn = getter && "isReactWarning" in getter && getter.isReactWarning;
if (mayWarn) {
return element.props.ref;
}
return element.props.ref || element.ref;
}
// node_modules/@radix-ui/react-collection/dist/index.mjs
var import_jsx_runtime2 = __toESM(require_jsx_runtime(), 1);
var import_react2 = __toESM(require_react(), 1);
var import_jsx_runtime3 = __toESM(require_jsx_runtime(), 1);
function createCollection(name) {
const PROVIDER_NAME = name + "CollectionProvider";
const [createCollectionContext, createCollectionScope] = createContextScope(PROVIDER_NAME);
const [CollectionProviderImpl, useCollectionContext] = createCollectionContext(
PROVIDER_NAME,
{ collectionRef: { current: null }, itemMap: /* @__PURE__ */ new Map() }
);
const CollectionProvider = (props) => {
const { scope, children } = props;
const ref = import_react.default.useRef(null);
const itemMap = import_react.default.useRef(/* @__PURE__ */ new Map()).current;
return (0, import_jsx_runtime2.jsx)(CollectionProviderImpl, { scope, itemMap, collectionRef: ref, children });
};
CollectionProvider.displayName = PROVIDER_NAME;
const COLLECTION_SLOT_NAME = name + "CollectionSlot";
const CollectionSlotImpl = createSlot(COLLECTION_SLOT_NAME);
const CollectionSlot = import_react.default.forwardRef(
(props, forwardedRef) => {
const { scope, children } = props;
const context = useCollectionContext(COLLECTION_SLOT_NAME, scope);
const composedRefs = useComposedRefs(forwardedRef, context.collectionRef);
return (0, import_jsx_runtime2.jsx)(CollectionSlotImpl, { ref: composedRefs, children });
}
);
CollectionSlot.displayName = COLLECTION_SLOT_NAME;
const ITEM_SLOT_NAME = name + "CollectionItemSlot";
const ITEM_DATA_ATTR = "data-radix-collection-item";
const CollectionItemSlotImpl = createSlot(ITEM_SLOT_NAME);
const CollectionItemSlot = import_react.default.forwardRef(
(props, forwardedRef) => {
const { scope, children, ...itemData } = props;
const ref = import_react.default.useRef(null);
const composedRefs = useComposedRefs(forwardedRef, ref);
const context = useCollectionContext(ITEM_SLOT_NAME, scope);
import_react.default.useEffect(() => {
context.itemMap.set(ref, { ref, ...itemData });
return () => void context.itemMap.delete(ref);
});
return (0, import_jsx_runtime2.jsx)(CollectionItemSlotImpl, { ...{ [ITEM_DATA_ATTR]: "" }, ref: composedRefs, children });
}
);
CollectionItemSlot.displayName = ITEM_SLOT_NAME;
function useCollection(scope) {
const context = useCollectionContext(name + "CollectionConsumer", scope);
const getItems = import_react.default.useCallback(() => {
const collectionNode = context.collectionRef.current;
if (!collectionNode) return [];
const orderedNodes = Array.from(collectionNode.querySelectorAll(`[${ITEM_DATA_ATTR}]`));
const items = Array.from(context.itemMap.values());
const orderedItems = items.sort(
(a, b) => orderedNodes.indexOf(a.ref.current) - orderedNodes.indexOf(b.ref.current)
);
return orderedItems;
}, [context.collectionRef, context.itemMap]);
return getItems;
}
return [
{ Provider: CollectionProvider, Slot: CollectionSlot, ItemSlot: CollectionItemSlot },
useCollection,
createCollectionScope
];
}
// node_modules/@radix-ui/react-direction/dist/index.mjs
var React3 = __toESM(require_react(), 1);
var import_jsx_runtime4 = __toESM(require_jsx_runtime(), 1);
var DirectionContext = React3.createContext(void 0);
function useDirection(localDir) {
const globalDir = React3.useContext(DirectionContext);
return localDir || globalDir || "ltr";
}
// node_modules/@radix-ui/react-use-size/dist/index.mjs
var React4 = __toESM(require_react(), 1);
function useSize(element) {
const [size, setSize] = React4.useState(void 0);
useLayoutEffect2(() => {
if (element) {
setSize({ width: element.offsetWidth, height: element.offsetHeight });
const resizeObserver = new ResizeObserver((entries) => {
if (!Array.isArray(entries)) {
return;
}
if (!entries.length) {
return;
}
const entry = entries[0];
let width;
let height;
if ("borderBoxSize" in entry) {
const borderSizeEntry = entry["borderBoxSize"];
const borderSize = Array.isArray(borderSizeEntry) ? borderSizeEntry[0] : borderSizeEntry;
width = borderSize["inlineSize"];
height = borderSize["blockSize"];
} else {
width = element.offsetWidth;
height = element.offsetHeight;
}
setSize({ width, height });
});
resizeObserver.observe(element, { box: "border-box" });
return () => resizeObserver.unobserve(element);
} else {
setSize(void 0);
}
}, [element]);
return size;
}
// node_modules/@radix-ui/react-use-previous/dist/index.mjs
var React5 = __toESM(require_react(), 1);
function usePrevious(value) {
const ref = React5.useRef({ value, previous: value });
return React5.useMemo(() => {
if (ref.current.value !== value) {
ref.current.previous = ref.current.value;
ref.current.value = value;
}
return ref.current.previous;
}, [value]);
}
export {
createCollection,
useDirection,
useSize,
usePrevious
};
//# sourceMappingURL=chunk-L2OGOWTU.js.map
File diff suppressed because one or more lines are too long
+1352
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+354
View File
@@ -0,0 +1,354 @@
import {
composeRefs
} from "./chunk-2VUH7NEY.js";
import {
require_react_dom
} from "./chunk-YF4B4G2L.js";
import {
require_jsx_runtime
} from "./chunk-2YVA4HRZ.js";
import {
require_react
} from "./chunk-WUR7D6NS.js";
import {
__toESM
} from "./chunk-G3PMV62Z.js";
// node_modules/@radix-ui/primitive/dist/index.mjs
var canUseDOM = !!(typeof window !== "undefined" && window.document && window.document.createElement);
function composeEventHandlers(originalEventHandler, ourEventHandler, { checkForDefaultPrevented = true } = {}) {
return function handleEvent(event) {
originalEventHandler?.(event);
if (checkForDefaultPrevented === false || !event.defaultPrevented) {
return ourEventHandler?.(event);
}
};
}
// node_modules/@radix-ui/react-context/dist/index.mjs
var React = __toESM(require_react(), 1);
var import_jsx_runtime = __toESM(require_jsx_runtime(), 1);
function createContext2(rootComponentName, defaultContext) {
const Context = React.createContext(defaultContext);
const Provider = (props) => {
const { children, ...context } = props;
const value = React.useMemo(() => context, Object.values(context));
return (0, import_jsx_runtime.jsx)(Context.Provider, { value, children });
};
Provider.displayName = rootComponentName + "Provider";
function useContext2(consumerName) {
const context = React.useContext(Context);
if (context) return context;
if (defaultContext !== void 0) return defaultContext;
throw new Error(`\`${consumerName}\` must be used within \`${rootComponentName}\``);
}
return [Provider, useContext2];
}
function createContextScope(scopeName, createContextScopeDeps = []) {
let defaultContexts = [];
function createContext3(rootComponentName, defaultContext) {
const BaseContext = React.createContext(defaultContext);
const index = defaultContexts.length;
defaultContexts = [...defaultContexts, defaultContext];
const Provider = (props) => {
const { scope, children, ...context } = props;
const Context = scope?.[scopeName]?.[index] || BaseContext;
const value = React.useMemo(() => context, Object.values(context));
return (0, import_jsx_runtime.jsx)(Context.Provider, { value, children });
};
Provider.displayName = rootComponentName + "Provider";
function useContext2(consumerName, scope) {
const Context = scope?.[scopeName]?.[index] || BaseContext;
const context = React.useContext(Context);
if (context) return context;
if (defaultContext !== void 0) return defaultContext;
throw new Error(`\`${consumerName}\` must be used within \`${rootComponentName}\``);
}
return [Provider, useContext2];
}
const createScope = () => {
const scopeContexts = defaultContexts.map((defaultContext) => {
return React.createContext(defaultContext);
});
return function useScope(scope) {
const contexts = scope?.[scopeName] || scopeContexts;
return React.useMemo(
() => ({ [`__scope${scopeName}`]: { ...scope, [scopeName]: contexts } }),
[scope, contexts]
);
};
};
createScope.scopeName = scopeName;
return [createContext3, composeContextScopes(createScope, ...createContextScopeDeps)];
}
function composeContextScopes(...scopes) {
const baseScope = scopes[0];
if (scopes.length === 1) return baseScope;
const createScope = () => {
const scopeHooks = scopes.map((createScope2) => ({
useScope: createScope2(),
scopeName: createScope2.scopeName
}));
return function useComposedScopes(overrideScopes) {
const nextScopes = scopeHooks.reduce((nextScopes2, { useScope, scopeName }) => {
const scopeProps = useScope(overrideScopes);
const currentScope = scopeProps[`__scope${scopeName}`];
return { ...nextScopes2, ...currentScope };
}, {});
return React.useMemo(() => ({ [`__scope${baseScope.scopeName}`]: nextScopes }), [nextScopes]);
};
};
createScope.scopeName = baseScope.scopeName;
return createScope;
}
// node_modules/@radix-ui/react-use-layout-effect/dist/index.mjs
var React2 = __toESM(require_react(), 1);
var useLayoutEffect2 = globalThis?.document ? React2.useLayoutEffect : () => {
};
// node_modules/@radix-ui/react-id/dist/index.mjs
var React3 = __toESM(require_react(), 1);
var useReactId = React3[" useId ".trim().toString()] || (() => void 0);
var count = 0;
function useId(deterministicId) {
const [id, setId] = React3.useState(useReactId());
useLayoutEffect2(() => {
if (!deterministicId) setId((reactId) => reactId ?? String(count++));
}, [deterministicId]);
return deterministicId || (id ? `radix-${id}` : "");
}
// node_modules/@radix-ui/react-use-controllable-state/dist/index.mjs
var React5 = __toESM(require_react(), 1);
var React22 = __toESM(require_react(), 1);
// node_modules/@radix-ui/react-use-effect-event/dist/index.mjs
var React4 = __toESM(require_react(), 1);
var useReactEffectEvent = React4[" useEffectEvent ".trim().toString()];
var useReactInsertionEffect = React4[" useInsertionEffect ".trim().toString()];
// node_modules/@radix-ui/react-use-controllable-state/dist/index.mjs
var useInsertionEffect = React5[" useInsertionEffect ".trim().toString()] || useLayoutEffect2;
function useControllableState({
prop,
defaultProp,
onChange = () => {
},
caller
}) {
const [uncontrolledProp, setUncontrolledProp, onChangeRef] = useUncontrolledState({
defaultProp,
onChange
});
const isControlled = prop !== void 0;
const value = isControlled ? prop : uncontrolledProp;
if (true) {
const isControlledRef = React5.useRef(prop !== void 0);
React5.useEffect(() => {
const wasControlled = isControlledRef.current;
if (wasControlled !== isControlled) {
const from = wasControlled ? "controlled" : "uncontrolled";
const to = isControlled ? "controlled" : "uncontrolled";
console.warn(
`${caller} is changing from ${from} to ${to}. Components should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled value for the lifetime of the component.`
);
}
isControlledRef.current = isControlled;
}, [isControlled, caller]);
}
const setValue = React5.useCallback(
(nextValue) => {
if (isControlled) {
const value2 = isFunction(nextValue) ? nextValue(prop) : nextValue;
if (value2 !== prop) {
onChangeRef.current?.(value2);
}
} else {
setUncontrolledProp(nextValue);
}
},
[isControlled, prop, setUncontrolledProp, onChangeRef]
);
return [value, setValue];
}
function useUncontrolledState({
defaultProp,
onChange
}) {
const [value, setValue] = React5.useState(defaultProp);
const prevValueRef = React5.useRef(value);
const onChangeRef = React5.useRef(onChange);
useInsertionEffect(() => {
onChangeRef.current = onChange;
}, [onChange]);
React5.useEffect(() => {
if (prevValueRef.current !== value) {
onChangeRef.current?.(value);
prevValueRef.current = value;
}
}, [value, prevValueRef]);
return [value, setValue, onChangeRef];
}
function isFunction(value) {
return typeof value === "function";
}
// node_modules/@radix-ui/react-primitive/dist/index.mjs
var React7 = __toESM(require_react(), 1);
var ReactDOM = __toESM(require_react_dom(), 1);
// node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs
var React6 = __toESM(require_react(), 1);
var import_jsx_runtime2 = __toESM(require_jsx_runtime(), 1);
function createSlot(ownerName) {
const SlotClone = createSlotClone(ownerName);
const Slot2 = React6.forwardRef((props, forwardedRef) => {
const { children, ...slotProps } = props;
const childrenArray = React6.Children.toArray(children);
const slottable = childrenArray.find(isSlottable);
if (slottable) {
const newElement = slottable.props.children;
const newChildren = childrenArray.map((child) => {
if (child === slottable) {
if (React6.Children.count(newElement) > 1) return React6.Children.only(null);
return React6.isValidElement(newElement) ? newElement.props.children : null;
} else {
return child;
}
});
return (0, import_jsx_runtime2.jsx)(SlotClone, { ...slotProps, ref: forwardedRef, children: React6.isValidElement(newElement) ? React6.cloneElement(newElement, void 0, newChildren) : null });
}
return (0, import_jsx_runtime2.jsx)(SlotClone, { ...slotProps, ref: forwardedRef, children });
});
Slot2.displayName = `${ownerName}.Slot`;
return Slot2;
}
var Slot = createSlot("Slot");
function createSlotClone(ownerName) {
const SlotClone = React6.forwardRef((props, forwardedRef) => {
const { children, ...slotProps } = props;
if (React6.isValidElement(children)) {
const childrenRef = getElementRef(children);
const props2 = mergeProps(slotProps, children.props);
if (children.type !== React6.Fragment) {
props2.ref = forwardedRef ? composeRefs(forwardedRef, childrenRef) : childrenRef;
}
return React6.cloneElement(children, props2);
}
return React6.Children.count(children) > 1 ? React6.Children.only(null) : null;
});
SlotClone.displayName = `${ownerName}.SlotClone`;
return SlotClone;
}
var SLOTTABLE_IDENTIFIER = /* @__PURE__ */ Symbol("radix.slottable");
function createSlottable(ownerName) {
const Slottable2 = ({ children }) => {
return (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children });
};
Slottable2.displayName = `${ownerName}.Slottable`;
Slottable2.__radixId = SLOTTABLE_IDENTIFIER;
return Slottable2;
}
var Slottable = createSlottable("Slottable");
function isSlottable(child) {
return React6.isValidElement(child) && typeof child.type === "function" && "__radixId" in child.type && child.type.__radixId === SLOTTABLE_IDENTIFIER;
}
function mergeProps(slotProps, childProps) {
const overrideProps = { ...childProps };
for (const propName in childProps) {
const slotPropValue = slotProps[propName];
const childPropValue = childProps[propName];
const isHandler = /^on[A-Z]/.test(propName);
if (isHandler) {
if (slotPropValue && childPropValue) {
overrideProps[propName] = (...args) => {
const result = childPropValue(...args);
slotPropValue(...args);
return result;
};
} else if (slotPropValue) {
overrideProps[propName] = slotPropValue;
}
} else if (propName === "style") {
overrideProps[propName] = { ...slotPropValue, ...childPropValue };
} else if (propName === "className") {
overrideProps[propName] = [slotPropValue, childPropValue].filter(Boolean).join(" ");
}
}
return { ...slotProps, ...overrideProps };
}
function getElementRef(element) {
let getter = Object.getOwnPropertyDescriptor(element.props, "ref")?.get;
let mayWarn = getter && "isReactWarning" in getter && getter.isReactWarning;
if (mayWarn) {
return element.ref;
}
getter = Object.getOwnPropertyDescriptor(element, "ref")?.get;
mayWarn = getter && "isReactWarning" in getter && getter.isReactWarning;
if (mayWarn) {
return element.props.ref;
}
return element.props.ref || element.ref;
}
// node_modules/@radix-ui/react-primitive/dist/index.mjs
var import_jsx_runtime3 = __toESM(require_jsx_runtime(), 1);
var NODES = [
"a",
"button",
"div",
"form",
"h2",
"h3",
"img",
"input",
"label",
"li",
"nav",
"ol",
"p",
"select",
"span",
"svg",
"ul"
];
var Primitive = NODES.reduce((primitive, node) => {
const Slot2 = createSlot(`Primitive.${node}`);
const Node = React7.forwardRef((props, forwardedRef) => {
const { asChild, ...primitiveProps } = props;
const Comp = asChild ? Slot2 : node;
if (typeof window !== "undefined") {
window[/* @__PURE__ */ Symbol.for("radix-ui")] = true;
}
return (0, import_jsx_runtime3.jsx)(Comp, { ...primitiveProps, ref: forwardedRef });
});
Node.displayName = `Primitive.${node}`;
return { ...primitive, [node]: Node };
}, {});
function dispatchDiscreteCustomEvent(target, event) {
if (target) ReactDOM.flushSync(() => target.dispatchEvent(event));
}
// node_modules/@radix-ui/react-use-callback-ref/dist/index.mjs
var React8 = __toESM(require_react(), 1);
function useCallbackRef(callback) {
const callbackRef = React8.useRef(callback);
React8.useEffect(() => {
callbackRef.current = callback;
});
return React8.useMemo(() => (...args) => callbackRef.current?.(...args), []);
}
export {
composeEventHandlers,
createContext2,
createContextScope,
useLayoutEffect2,
useId,
useControllableState,
Primitive,
dispatchDiscreteCustomEvent,
useCallbackRef
};
//# sourceMappingURL=chunk-MOFDO34C.js.map
File diff suppressed because one or more lines are too long
+1150
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+69
View File
@@ -0,0 +1,69 @@
2.2.4 / 2018-03-13
==================
* Use flow strict mode (i.e. `@flow strict`).
2.2.3 / 2018-02-19
==================
* Change license from BSD+Patents to MIT.
2.2.2 / 2016-11-15
==================
* Add LICENSE file.
* Misc housekeeping.
2.2.1 / 2016-03-09
==================
* Use `NODE_ENV` variable instead of `__DEV__` to cache `process.env.NODE_ENV`.
2.2.0 / 2015-11-17
==================
* Use `error.name` instead of `Invariant Violation`.
2.1.3 / 2015-11-17
==================
* Remove `@provideModule` pragma.
2.1.2 / 2015-10-27
==================
* Fix license.
2.1.1 / 2015-09-20
==================
* Use correct SPDX license.
* Test "browser.js" using browserify.
* Switch from "envify" to "loose-envify".
2.1.0 / 2015-06-03
==================
* Add "envify" as a dependency.
* Fixed license field in "package.json".
2.0.0 / 2015-02-21
==================
* Switch to using the "browser" field. There are now browser and server versions that respect the "format" in production.
1.0.2 / 2014-09-24
==================
* Added tests, npmignore and gitignore.
* Clarifications in README.
1.0.1 / 2014-09-24
==================
* Actually include 'invariant.js'.
1.0.0 / 2014-09-24
==================
* Initial release.
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2013-present, Facebook, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+37
View File
@@ -0,0 +1,37 @@
# invariant
[![Build Status](https://travis-ci.org/zertosh/invariant.svg?branch=master)](https://travis-ci.org/zertosh/invariant)
A mirror of Facebook's `invariant` (e.g. [React](https://github.com/facebook/react/blob/v0.13.3/src/vendor/core/invariant.js), [flux](https://github.com/facebook/flux/blob/2.0.2/src/invariant.js)).
A way to provide descriptive errors in development but generic errors in production.
## Install
With [npm](http://npmjs.org) do:
```sh
npm install invariant
```
## `invariant(condition, message)`
```js
var invariant = require('invariant');
invariant(someTruthyVal, 'This will not throw');
// No errors
invariant(someFalseyVal, 'This will throw an error with this message');
// Error: Invariant Violation: This will throw an error with this message
```
**Note:** When `process.env.NODE_ENV` is not `production`, the message is required. If omitted, `invariant` will throw regardless of the truthiness of the condition. When `process.env.NODE_ENV` is `production`, the message is optional so they can be minified away.
### Browser
When used with [browserify](https://github.com/substack/node-browserify), it'll use `browser.js` (instead of `invariant.js`) and the [envify](https://github.com/hughsk/envify) transform will inline the value of `process.env.NODE_ENV`.
### Node
The node version is optimized around the performance implications of accessing `process.env`. The value of `process.env.NODE_ENV` is cached, and repeatedly used instead of reading `process.env`. See [Server rendering is slower with npm react #812](https://github.com/facebook/react/issues/812)
+49
View File
@@ -0,0 +1,49 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
/**
* Use invariant() to assert state which your program assumes to be true.
*
* Provide sprintf-style format (only %s is supported) and arguments
* to provide information about what broke and what you were
* expecting.
*
* The invariant message will be stripped in production, but the invariant
* will remain to ensure logic does not differ in production.
*/
var invariant = function(condition, format, a, b, c, d, e, f) {
if (process.env.NODE_ENV !== 'production') {
if (format === undefined) {
throw new Error('invariant requires an error message argument');
}
}
if (!condition) {
var error;
if (format === undefined) {
error = new Error(
'Minified exception occurred; use the non-minified dev environment ' +
'for the full error message and additional helpful warnings.'
);
} else {
var args = [a, b, c, d, e, f];
var argIndex = 0;
error = new Error(
format.replace(/%s/g, function() { return args[argIndex++]; })
);
error.name = 'Invariant Violation';
}
error.framesToPop = 1; // we don't care about invariant's own frame
throw error;
}
};
module.exports = invariant;
+51
View File
@@ -0,0 +1,51 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
/**
* Use invariant() to assert state which your program assumes to be true.
*
* Provide sprintf-style format (only %s is supported) and arguments
* to provide information about what broke and what you were
* expecting.
*
* The invariant message will be stripped in production, but the invariant
* will remain to ensure logic does not differ in production.
*/
var NODE_ENV = process.env.NODE_ENV;
var invariant = function(condition, format, a, b, c, d, e, f) {
if (NODE_ENV !== 'production') {
if (format === undefined) {
throw new Error('invariant requires an error message argument');
}
}
if (!condition) {
var error;
if (format === undefined) {
error = new Error(
'Minified exception occurred; use the non-minified dev environment ' +
'for the full error message and additional helpful warnings.'
);
} else {
var args = [a, b, c, d, e, f];
var argIndex = 0;
error = new Error(
format.replace(/%s/g, function() { return args[argIndex++]; })
);
error.name = 'Invariant Violation';
}
error.framesToPop = 1; // we don't care about invariant's own frame
throw error;
}
};
module.exports = invariant;
+7
View File
@@ -0,0 +1,7 @@
/* @flow strict */
declare module.exports: (
condition: any,
format?: string,
...args: Array<any>
) => void;
+35
View File
@@ -0,0 +1,35 @@
{
"name": "invariant",
"version": "2.2.4",
"description": "invariant",
"keywords": [
"test",
"invariant"
],
"license": "MIT",
"author": "Andres Suarez <zertosh@gmail.com>",
"files": [
"browser.js",
"invariant.js",
"invariant.js.flow"
],
"repository": "https://github.com/zertosh/invariant",
"scripts": {
"test": "NODE_ENV=production tap test/*.js && NODE_ENV=development tap test/*.js"
},
"dependencies": {
"loose-envify": "^1.0.0"
},
"devDependencies": {
"browserify": "^11.0.1",
"flow-bin": "^0.67.1",
"tap": "^1.4.0"
},
"main": "invariant.js",
"browser": "browser.js",
"browserify": {
"transform": [
"loose-envify"
]
}
}
+22
View File
@@ -0,0 +1,22 @@
MIT License
Copyright (c) 2018 Formidable Labs
Copyright (c) 2017 Evgeny Poberezkin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+165
View File
@@ -0,0 +1,165 @@
[![React Fast Compare — Formidable, We build the modern web](https://raw.githubusercontent.com/FormidableLabs/react-fast-compare/master/react-fast-compare-Hero.png)](https://formidable.com/open-source/)
[![Downloads][downloads_img]][npm_site]
[![Bundle Size][bundle_img]](#bundle-size)
[![GH Actions Status][actions_img]][actions_site]
[![Coverage Status][cov_img]][cov_site]
[![npm version][npm_img]][npm_site]
[![Maintenance Status][maintenance_img]](#maintenance-status)
The fastest deep equal comparison for React. Very quick general-purpose deep
comparison, too. Great for `React.memo` and `shouldComponentUpdate`.
This is a fork of the brilliant
[fast-deep-equal](https://github.com/epoberezkin/fast-deep-equal) with some
extra handling for React.
![benchmark chart](https://raw.githubusercontent.com/FormidableLabs/react-fast-compare/master/assets/benchmarking.png "benchmarking chart")
(Check out the [benchmarking details](#benchmarking-this-library).)
## Install
```sh
$ yarn add react-fast-compare
# or
$ npm install react-fast-compare
```
## Highlights
- ES5 compatible; works in node.js (0.10+) and browsers (IE9+)
- deeply compares any value (besides objects with circular references)
- handles React-specific circular references, like elements
- checks equality Date and RegExp objects
- should as fast as [fast-deep-equal](https://github.com/epoberezkin/fast-deep-equal) via a single unified library, and with added guardrails for circular references.
- small: under 660 bytes minified+gzipped
## Usage
```jsx
const isEqual = require("react-fast-compare");
// general usage
console.log(isEqual({ foo: "bar" }, { foo: "bar" })); // true
// React.memo
// only re-render ExpensiveComponent when the props have deeply changed
const DeepMemoComponent = React.memo(ExpensiveComponent, isEqual);
// React.Component shouldComponentUpdate
// only re-render AnotherExpensiveComponent when the props have deeply changed
class AnotherExpensiveComponent extends React.Component {
shouldComponentUpdate(nextProps) {
return !isEqual(this.props, nextProps);
}
render() {
// ...
}
}
```
## Do I Need `React.memo` (or `shouldComponentUpdate`)?
> What's faster than a really fast deep comparison? No deep comparison at all.
—This Readme
Deep checks in `React.memo` or a `shouldComponentUpdate` should not be used blindly.
First, see if the default
[React.memo](https://reactjs.org/docs/react-api.html#reactmemo) or
[PureComponent](https://reactjs.org/docs/react-api.html#reactpurecomponent)
will work for you. If it won't (if you need deep checks), it's wise to make
sure you've correctly indentified the bottleneck in your application by
[profiling the performance](https://reactjs.org/docs/optimizing-performance.html#profiling-components-with-the-chrome-performance-tab).
After you've determined that you _do_ need deep equality checks and you've
identified the minimum number of places to apply them, then this library may
be for you!
## Benchmarking this Library
The absolute values are much less important than the relative differences
between packages.
Benchmarking source can be found
[here](https://github.com/FormidableLabs/react-fast-compare/blob/master/benchmark/index.js).
Each "operation" consists of running all relevant tests. The React benchmark
uses both the generic tests and the react tests; these runs will be slower
simply because there are more tests in each operation.
The results below are from a local test on a laptop _(stats last updated 6/2/2020)_:
### Generic Data
```
react-fast-compare x 177,600 ops/sec ±1.73% (92 runs sampled)
fast-deep-equal x 184,211 ops/sec ±0.65% (87 runs sampled)
lodash.isEqual x 39,826 ops/sec ±1.32% (86 runs sampled)
nano-equal x 176,023 ops/sec ±0.89% (92 runs sampled)
shallow-equal-fuzzy x 146,355 ops/sec ±0.64% (89 runs sampled)
fastest: fast-deep-equal
```
`react-fast-compare` and `fast-deep-equal` should be the same speed for these
tests; any difference is just noise. `react-fast-compare` won't be faster than
`fast-deep-equal`, because it's based on it.
### React and Generic Data
```
react-fast-compare x 86,392 ops/sec ±0.70% (93 runs sampled)
fast-deep-equal x 85,567 ops/sec ±0.95% (92 runs sampled)
lodash.isEqual x 7,369 ops/sec ±1.78% (84 runs sampled)
fastest: react-fast-compare,fast-deep-equal
```
Two of these packages cannot handle comparing React elements, because they
contain circular reference: `nano-equal` and `shallow-equal-fuzzy`.
### Running Benchmarks
```sh
$ yarn install
$ yarn run benchmark
```
## Differences between this library and `fast-deep-equal`
`react-fast-compare` is based on `fast-deep-equal`, with some additions:
- `react-fast-compare` has `try`/`catch` guardrails for stack overflows from undetected (non-React) circular references.
- `react-fast-compare` has a _single_ unified entry point for all uses. No matter what your target application is, `import equal from 'react-fast-compare'` just works. `fast-deep-equal` has multiple entry points for different use cases.
This version of `react-fast-compare` tracks `fast-deep-equal@3.1.1`.
## Bundle Size
There are a variety of ways to calculate bundle size for JavaScript code.
You can see our size test code in the `compress` script in
[`package.json`](https://github.com/FormidableLabs/react-fast-compare/blob/master/package.json).
[Bundlephobia's calculation](https://bundlephobia.com/result?p=react-fast-compare) is slightly higher,
as they [do not mangle during minification](https://github.com/pastelsky/package-build-stats/blob/v6.1.1/src/getDependencySizeTree.js#L139).
## License
[MIT](https://github.com/FormidableLabs/react-fast-compare/blob/readme/LICENSE)
## Contributing
Please see our [contributions guide](./CONTRIBUTING.md).
## Maintenance Status
**Active:** Formidable is actively working on this project, and we expect to continue for work for the foreseeable future. Bug reports, feature requests and pull requests are welcome.
[actions_img]: https://github.com/FormidableLabs/react-fast-compare/actions/workflows/ci.yml/badge.svg
[actions_site]: https://github.com/formidablelabs/react-fast-compare/actions/workflows/ci.yml
[cov_img]: https://codecov.io/gh/FormidableLabs/react-fast-compare/branch/master/graph/badge.svg
[cov_site]: https://codecov.io/gh/FormidableLabs/react-fast-compare
[npm_img]: https://badge.fury.io/js/react-fast-compare.svg
[npm_site]: http://badge.fury.io/js/react-fast-compare
[appveyor_img]: https://ci.appveyor.com/api/projects/status/github/formidablelabs/react-fast-compare?branch=master&svg=true
[appveyor_site]: https://ci.appveyor.com/project/FormidableLabs/react-fast-compare
[bundle_img]: https://img.shields.io/badge/minzipped%20size-656%20B-flatgreen.svg
[downloads_img]: https://img.shields.io/npm/dm/react-fast-compare.svg
[maintenance_img]: https://img.shields.io/badge/maintenance-active-flatgreen.svg
+2
View File
@@ -0,0 +1,2 @@
declare function isEqual<A = any, B = any>(a: A, b: B): boolean;
export = isEqual;
+139
View File
@@ -0,0 +1,139 @@
/* global Map:readonly, Set:readonly, ArrayBuffer:readonly */
var hasElementType = typeof Element !== 'undefined';
var hasMap = typeof Map === 'function';
var hasSet = typeof Set === 'function';
var hasArrayBuffer = typeof ArrayBuffer === 'function' && !!ArrayBuffer.isView;
// Note: We **don't** need `envHasBigInt64Array` in fde es6/index.js
function equal(a, b) {
// START: fast-deep-equal es6/index.js 3.1.3
if (a === b) return true;
if (a && b && typeof a == 'object' && typeof b == 'object') {
if (a.constructor !== b.constructor) return false;
var length, i, keys;
if (Array.isArray(a)) {
length = a.length;
if (length != b.length) return false;
for (i = length; i-- !== 0;)
if (!equal(a[i], b[i])) return false;
return true;
}
// START: Modifications:
// 1. Extra `has<Type> &&` helpers in initial condition allow es6 code
// to co-exist with es5.
// 2. Replace `for of` with es5 compliant iteration using `for`.
// Basically, take:
//
// ```js
// for (i of a.entries())
// if (!b.has(i[0])) return false;
// ```
//
// ... and convert to:
//
// ```js
// it = a.entries();
// while (!(i = it.next()).done)
// if (!b.has(i.value[0])) return false;
// ```
//
// **Note**: `i` access switches to `i.value`.
var it;
if (hasMap && (a instanceof Map) && (b instanceof Map)) {
if (a.size !== b.size) return false;
it = a.entries();
while (!(i = it.next()).done)
if (!b.has(i.value[0])) return false;
it = a.entries();
while (!(i = it.next()).done)
if (!equal(i.value[1], b.get(i.value[0]))) return false;
return true;
}
if (hasSet && (a instanceof Set) && (b instanceof Set)) {
if (a.size !== b.size) return false;
it = a.entries();
while (!(i = it.next()).done)
if (!b.has(i.value[0])) return false;
return true;
}
// END: Modifications
if (hasArrayBuffer && ArrayBuffer.isView(a) && ArrayBuffer.isView(b)) {
length = a.length;
if (length != b.length) return false;
for (i = length; i-- !== 0;)
if (a[i] !== b[i]) return false;
return true;
}
if (a.constructor === RegExp) return a.source === b.source && a.flags === b.flags;
// START: Modifications:
// Apply guards for `Object.create(null)` handling. See:
// - https://github.com/FormidableLabs/react-fast-compare/issues/64
// - https://github.com/epoberezkin/fast-deep-equal/issues/49
if (a.valueOf !== Object.prototype.valueOf && typeof a.valueOf === 'function' && typeof b.valueOf === 'function') return a.valueOf() === b.valueOf();
if (a.toString !== Object.prototype.toString && typeof a.toString === 'function' && typeof b.toString === 'function') return a.toString() === b.toString();
// END: Modifications
keys = Object.keys(a);
length = keys.length;
if (length !== Object.keys(b).length) return false;
for (i = length; i-- !== 0;)
if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false;
// END: fast-deep-equal
// START: react-fast-compare
// custom handling for DOM elements
if (hasElementType && a instanceof Element) return false;
// custom handling for React/Preact
for (i = length; i-- !== 0;) {
if ((keys[i] === '_owner' || keys[i] === '__v' || keys[i] === '__o') && a.$$typeof) {
// React-specific: avoid traversing React elements' _owner
// Preact-specific: avoid traversing Preact elements' __v and __o
// __v = $_original / $_vnode
// __o = $_owner
// These properties contain circular references and are not needed when
// comparing the actual elements (and not their owners)
// .$$typeof and ._store on just reasonable markers of elements
continue;
}
// all other properties should be traversed as usual
if (!equal(a[keys[i]], b[keys[i]])) return false;
}
// END: react-fast-compare
// START: fast-deep-equal
return true;
}
return a !== a && b !== b;
}
// end fast-deep-equal
module.exports = function isEqual(a, b) {
try {
return equal(a, b);
} catch (error) {
if (((error.message || '').match(/stack|recursion/i))) {
// warn on circular references, don't crash
// browsers give this different errors name and messages:
// chrome/safari: "RangeError", "Maximum call stack size exceeded"
// firefox: "InternalError", too much recursion"
// edge: "Error", "Out of stack space"
console.warn('react-fast-compare cannot handle circular refs');
return false;
}
// some other error. we should definitely know about these
throw error;
}
};
+105
View File
@@ -0,0 +1,105 @@
{
"name": "react-fast-compare",
"version": "3.2.2",
"description": "Fastest deep equal comparison for React. Great for React.memo & shouldComponentUpdate. Also really fast general-purpose deep comparison.",
"main": "index.js",
"typings": "index.d.ts",
"scripts": {
"preversion": "yarn test",
"benchmark": "node benchmark",
"eslint": "eslint \"*.js\" benchmark test",
"tslint": "eslint test/typescript/*.tsx",
"test-browser": "karma start test/browser/karma.conf.js",
"test-node": "mocha \"test/node/*.spec.js\"",
"test-node-cov": "nyc mocha \"test/node/*.spec.js\"",
"test-ts-usage": "tsc --esModuleInterop --jsx react --noEmit test/typescript/sample-react-redux-usage.tsx test/typescript/sample-usage.tsx",
"test-ts-defs": "tsc --target ES5 index.d.ts",
"test": "builder concurrent --buffer eslint tslint test-ts-usage test-ts-defs test-node-cov test-browser",
"compress": "terser --compress --mangle=\"toplevel:true\" -- index.js",
"size-min-gz": "yarn -s compress | gzip -9 | wc -c"
},
"repository": {
"type": "git",
"url": "https://github.com/FormidableLabs/react-fast-compare"
},
"keywords": [
"fast",
"equal",
"react",
"compare",
"shouldComponentUpdate",
"deep-equal"
],
"author": "Chris Bolin",
"license": "MIT",
"bugs": {
"url": "https://github.com/FormidableLabs/react-fast-compare/issues"
},
"homepage": "https://github.com/FormidableLabs/react-fast-compare",
"devDependencies": {
"@babel/core": "^7.21.0",
"@babel/preset-env": "^7.20.2",
"@changesets/cli": "^2.26.1",
"@svitejs/changesets-changelog-github-compact": "^0.1.1",
"@testing-library/dom": "^9.0.1",
"@testing-library/preact": "^3.2.3",
"@types/node": "^18.15.0",
"@types/react": "^16.9.35",
"@types/react-dom": "^16.9.8",
"@types/react-redux": "^7.1.25",
"@typescript-eslint/parser": "^5.54.1",
"assert": "^2.0.0",
"babel-loader": "^9.1.2",
"benchmark": "^2.1.4",
"builder": "^5.0.0",
"codecov": "^3.8.3",
"core-js": "^3.29.0",
"eslint": "^8.35.0",
"eslint-plugin-react": "^7.32.2",
"fast-deep-equal": "3.1.3",
"fast-deep-equal-git": "epoberezkin/fast-deep-equal#v3.1.3",
"jsdom": "^21.1.0",
"jsdom-global": "^3.0.2",
"karma": "^6.4.1",
"karma-chrome-launcher": "^3.1.1",
"karma-firefox-launcher": "^2.1.2",
"karma-mocha": "^2.0.1",
"karma-mocha-reporter": "^2.2.5",
"karma-safari-launcher": "^1.0.0",
"karma-webpack": "^5.0.0",
"lodash": "^4.17.10",
"mocha": "^10.2.0",
"nano-equal": "^2.0.2",
"nyc": "^15.1.0",
"preact": "^10.13.1",
"process": "^0.11.10",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-redux": "^8.0.5",
"react-test-renderer": "^18.2.0",
"redux": "^4.2.1",
"shallow-equal-fuzzy": "0.0.2",
"sinon": "^15.0.1",
"terser": "^5.16.6",
"typescript": "^4.9.5",
"webpack": "^5.76.0"
},
"publishConfig": {
"provenance": true
},
"nyc": {
"exclude": [
"**/test/**",
"node_modules"
],
"reporter": [
"lcov",
"text-summary"
]
},
"files": [
"index.js",
"index.d.ts"
],
"types": "index.d.ts"
}
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2018 The New York Times Company
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
http://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.
+240
View File
@@ -0,0 +1,240 @@
# react-helmet-async
[![CI](https://github.com/staylor/react-helmet-async/actions/workflows/ci.yml/badge.svg)](https://github.com/staylor/react-helmet-async/actions/workflows/ci.yml)
[Announcement post on Times Open blog](https://open.nytimes.com/the-future-of-meta-tag-management-for-modern-react-development-ec26a7dc9183)
This package is a fork of [React Helmet](https://github.com/nfl/react-helmet).
`<Helmet>` usage is synonymous, but server and client now requires `<HelmetProvider>` to encapsulate state per request.
`react-helmet` relies on `react-side-effect`, which is not thread-safe. If you are doing anything asynchronous on the server, you need Helmet to encapsulate data on a per-request basis, this package does just that.
## React 19
React 19 has built-in support for hoisting `<title>`, `<meta>`, `<link>`, `<style>`, and `<script>` elements to `<head>`. Starting with version 3.0.0, this package detects the React version at runtime:
- **React 19+**: `<Helmet>` renders actual DOM elements and lets React handle hoisting them to `<head>`. `<HelmetProvider>` becomes a transparent passthrough. The existing API is fully compatible — you do not need to change any code.
- **React 1618**: The existing behavior is preserved. `<Helmet>` collects all instances, deduplicates tags, and applies changes to the DOM via manual manipulation (client) or serializes them for the response (server).
> **Note:** `htmlAttributes` and `bodyAttributes` do not have a React 19 equivalent, so they are still applied via direct DOM manipulation on both code paths.
If you are starting a new React 19 project and do not need `htmlAttributes`/`bodyAttributes`, SSR `context` serialization, `onChangeClientState`, `prioritizeSeoTags`, or `titleTemplate` support, you may not need this package at all — React 19's built-in metadata handling may be sufficient.
## Usage
**New in 1.0.0:** No more default export! `import { Helmet } from 'react-helmet-async'`
The main way that this package differs from `react-helmet` is that it requires using a Provider to encapsulate Helmet state for your React tree. If you use libraries like Redux or Apollo, you are already familiar with this paradigm:
```javascript
import React from 'react';
import { createRoot } from 'react-dom/client';
import { Helmet, HelmetProvider } from 'react-helmet-async';
const app = (
<HelmetProvider>
<App>
<Helmet>
<title>Hello World</title>
<link rel="canonical" href="https://www.tacobell.com/" />
</Helmet>
<h1>Hello World</h1>
</App>
</HelmetProvider>
);
createRoot(document.getElementById('app')).render(app);
```
On the server, we will no longer use static methods to extract state. `react-side-effect`
exposed a `.rewind()` method, which Helmet used when calling `Helmet.renderStatic()`. Instead, we are going
to pass a `context` prop to `HelmetProvider`, which will hold our state specific to each request.
```javascript
import React from 'react';
import { renderToString } from 'react-dom/server';
import { Helmet, HelmetProvider } from 'react-helmet-async';
const helmetContext = {};
const app = (
<HelmetProvider context={helmetContext}>
<App>
<Helmet>
<title>Hello World</title>
<link rel="canonical" href="https://www.tacobell.com/" />
</Helmet>
<h1>Hello World</h1>
</App>
</HelmetProvider>
);
const html = renderToString(app);
const { helmet } = helmetContext;
// helmet.title.toString() etc…
```
> **React 19 SSR note:** When using React 19, `<title>`, `<meta>`, and `<link>` tags rendered inside `<Helmet>` are included directly in the React render output and hoisted to `<head>` by React itself. The `context` object will not be populated with helmet state on React 19. If you rely on the `context` for server rendering, you can render these tags directly in your component tree instead and let React 19 handle them natively.
## Streams
This package only works with streaming if your `<head>` data is output outside of `renderToNodeStream()`.
This is possible if your data hydration method already parses your React tree. Example:
```javascript
import through from 'through';
import { renderToNodeStream } from 'react-dom/server';
import { getDataFromTree } from 'react-apollo';
import { Helmet, HelmetProvider } from 'react-helmet-async';
import template from 'server/template';
const helmetContext = {};
const app = (
<HelmetProvider context={helmetContext}>
<App>
<Helmet>
<title>Hello World</title>
<link rel="canonical" href="https://www.tacobell.com/" />
</Helmet>
<h1>Hello World</h1>
</App>
</HelmetProvider>
);
await getDataFromTree(app);
const [header, footer] = template({
helmet: helmetContext.helmet,
});
res.status(200);
res.write(header);
renderToNodeStream(app)
.pipe(
through(
function write(data) {
this.queue(data);
},
function end() {
this.queue(footer);
this.queue(null);
}
)
)
.pipe(res);
```
> **React 19:** React 19's `renderToReadableStream` natively handles `<title>`, `<meta>`, and `<link>` hoisting during streaming, so the manual context extraction shown above is not necessary.
## Usage in Jest
While testing in using jest, if there is a need to emulate SSR, the following string is required to have the test behave the way they are expected to.
```javascript
import { HelmetProvider } from 'react-helmet-async';
HelmetProvider.canUseDOM = false;
```
> This is only relevant for React 1618. On React 19, `HelmetProvider` is a passthrough and `canUseDOM` has no effect.
## Prioritizing tags for SEO
It is understood that in some cases for SEO, certain tags should appear earlier in the HEAD. Using the `prioritizeSeoTags` flag on any `<Helmet>` component allows the server render of react-helmet-async to expose a method for prioritizing relevant SEO tags.
In the component:
```javascript
<Helmet prioritizeSeoTags>
<title>A fancy webpage</title>
<link rel="notImportant" href="https://www.chipotle.com" />
<meta name="whatever" value="notImportant" />
<link rel="canonical" href="https://www.tacobell.com" />
<meta property="og:title" content="A very important title"/>
</Helmet>
```
In your server template:
```javascript
<html>
<head>
${helmet.title.toString()}
${helmet.priority.toString()}
${helmet.meta.toString()}
${helmet.link.toString()}
${helmet.script.toString()}
</head>
...
</html>
```
Will result in:
```html
<html>
<head>
<title>A fancy webpage</title>
<meta property="og:title" content="A very important title"/>
<link rel="canonical" href="https://www.tacobell.com" />
<meta name="whatever" value="notImportant" />
<link rel="notImportant" href="https://www.chipotle.com" />
</head>
...
</html>
```
A list of prioritized tags and attributes can be found in [constants.ts](./src/constants.ts).
> **React 19:** The `prioritizeSeoTags` flag has no effect on React 19, since tags are rendered as regular JSX elements and their order in `<head>` is determined by React's rendering order.
## Usage without Context
You can optionally use `<Helmet>` outside a context by manually creating a stateful `HelmetData` instance, and passing that stateful object to each `<Helmet>` instance:
```js
import React from 'react';
import { renderToString } from 'react-dom/server';
import { Helmet, HelmetData } from 'react-helmet-async';
const helmetData = new HelmetData({});
const app = (
<App>
<Helmet helmetData={helmetData}>
<title>Hello World</title>
<link rel="canonical" href="https://www.tacobell.com/" />
</Helmet>
<h1>Hello World</h1>
</App>
);
const html = renderToString(app);
const { helmet } = helmetData.context;
```
> **React 19:** The `helmetData` prop is ignored on React 19, since `<Helmet>` renders elements directly without the need for external state management.
## Compatibility
| React Version | Behavior |
|---|---|
| 16.6+ | Full support via `HelmetProvider` context and manual DOM updates |
| 17.x | Full support via `HelmetProvider` context and manual DOM updates |
| 18.x | Full support via `HelmetProvider` context and manual DOM updates |
| 19.x+ | Renders native JSX elements; React handles `<head>` hoisting |
## Development
```bash
pnpm install
pnpm test # unit tests
pnpm run test:e2e # server + browser e2e tests
pnpm run test:all # everything
```
## License
Licensed under the Apache 2.0 License, Copyright © 2018 Scott Taylor
+23
View File
@@ -0,0 +1,23 @@
import { Component } from 'react';
import type { HelmetServerState } from './types';
export interface DispatcherContextProp {
setHelmet: (newState: HelmetServerState | null) => void;
helmetInstances: {
get: () => HelmetDispatcher[];
add: (helmet: HelmetDispatcher) => void;
remove: (helmet: HelmetDispatcher) => void;
};
}
interface DispatcherProps {
context: DispatcherContextProp;
}
export default class HelmetDispatcher extends Component<DispatcherProps> {
rendered: boolean;
shouldComponentUpdate(nextProps: DispatcherProps): boolean;
componentDidUpdate(): void;
componentWillUnmount(): void;
emitChange(): void;
init(): void;
render(): null;
}
export {};
+26
View File
@@ -0,0 +1,26 @@
import type HelmetDispatcher from './Dispatcher';
import type { HelmetServerState } from './types';
export declare function clearInstances(): void;
export interface HelmetDataType {
instances: HelmetDispatcher[];
context: HelmetDataContext;
}
interface HelmetDataContext {
helmet: HelmetServerState | null;
}
export declare const isDocument: boolean;
export default class HelmetData implements HelmetDataType {
instances: never[];
canUseDOM: boolean;
context: HelmetDataContext;
value: {
setHelmet: (serverState: HelmetServerState | null) => void;
helmetInstances: {
get: () => HelmetDispatcher[];
add: (instance: HelmetDispatcher) => void;
remove: (instance: HelmetDispatcher) => void;
};
};
constructor(context: any, canUseDOM?: boolean);
}
export {};
+17
View File
@@ -0,0 +1,17 @@
import type { PropsWithChildren } from 'react';
import React, { Component } from 'react';
import HelmetData from './HelmetData';
import type { HelmetServerState } from './types';
export declare const Context: React.Context<{}>;
interface ProviderProps {
context?: {
helmet?: HelmetServerState | null;
};
}
export default class HelmetProvider extends Component<PropsWithChildren<ProviderProps>> {
static canUseDOM: boolean;
helmetData: HelmetData | null;
constructor(props: PropsWithChildren<ProviderProps>);
render(): React.JSX.Element;
}
export {};
+38
View File
@@ -0,0 +1,38 @@
import React, { Component } from 'react';
import type { HelmetProps } from './types';
interface React19DispatcherProps extends HelmetProps {
/**
* The processed props including mapped children. These come from Helmet's
* mapChildrenToProps or the raw API props.
*/
[key: string]: any;
}
/**
* React 19+ Dispatcher: Instead of manual DOM manipulation, this component
* renders actual JSX elements. React 19 automatically hoists <title>, <meta>,
* <link>, <style>, and <script async> to <head>.
*
* For htmlAttributes and bodyAttributes, we still apply via direct DOM
* manipulation since React 19 doesn't handle those.
*/
export default class React19Dispatcher extends Component<React19DispatcherProps> {
componentDidMount(): void;
componentDidUpdate(): void;
componentWillUnmount(): void;
resolveTitle(): string | undefined;
renderTitle(): React.DetailedReactHTMLElement<{
[key: string]: any;
}, HTMLElement> | null;
renderBase(): React.DetailedReactHTMLElement<{
[key: string]: any;
}, HTMLElement> | null;
renderMeta(): React.DetailedReactHTMLElement<React.HTMLAttributes<HTMLElement>, HTMLElement>[] | null;
renderLink(): React.DetailedReactHTMLElement<React.HTMLAttributes<HTMLElement>, HTMLElement>[] | null;
renderScript(): React.DetailedReactHTMLElement<React.HTMLAttributes<HTMLElement>, HTMLElement>[] | null;
renderStyle(): React.DetailedReactHTMLElement<React.HTMLAttributes<HTMLElement>, HTMLElement>[] | null;
renderNoscript(): React.DetailedReactHTMLElement<React.HTMLAttributes<HTMLElement>, HTMLElement>[] | null;
render(): React.FunctionComponentElement<{
children?: React.ReactNode;
}>;
}
export {};
+3
View File
@@ -0,0 +1,3 @@
import type { StateUpdate } from './types';
declare const handleStateChangeOnClient: (newState: StateUpdate) => void;
export default handleStateChangeOnClient;
+58
View File
@@ -0,0 +1,58 @@
export declare enum TAG_PROPERTIES {
CHARSET = "charset",
CSS_TEXT = "cssText",
HREF = "href",
HTTPEQUIV = "http-equiv",
INNER_HTML = "innerHTML",
ITEM_PROP = "itemprop",
NAME = "name",
PROPERTY = "property",
REL = "rel",
SRC = "src"
}
export declare enum ATTRIBUTE_NAMES {
BODY = "bodyAttributes",
HTML = "htmlAttributes",
TITLE = "titleAttributes"
}
export declare enum TAG_NAMES {
BASE = "base",
BODY = "body",
HEAD = "head",
HTML = "html",
LINK = "link",
META = "meta",
NOSCRIPT = "noscript",
SCRIPT = "script",
STYLE = "style",
TITLE = "title",
FRAGMENT = "Symbol(react.fragment)"
}
export declare const SEO_PRIORITY_TAGS: {
link: {
rel: string[];
};
script: {
type: string[];
};
meta: {
charset: string;
name: string[];
property: string[];
};
};
export declare const VALID_TAG_NAMES: TAG_NAMES[];
export declare const REACT_TAG_MAP: {
accesskey: string;
charset: string;
class: string;
contenteditable: string;
contextmenu: string;
'http-equiv': string;
itemprop: string;
tabindex: string;
};
export declare const HTML_TAG_MAP: {
[key: string]: string;
};
export declare const HELMET_ATTRIBUTE = "data-rh";
+21
View File
@@ -0,0 +1,21 @@
import type { PropsWithChildren } from 'react';
import React, { Component } from 'react';
import type { HelmetProps } from './types';
export type { Attributes, BodyProps, HelmetDatum, HelmetHTMLBodyDatum, HelmetHTMLElementDatum, HelmetProps, HelmetServerState, HelmetTags, HtmlProps, LinkProps, MetaProps, StateUpdate, TagList, TitleProps, } from './types';
export { default as HelmetData } from './HelmetData';
export { default as HelmetProvider } from './Provider';
export declare class Helmet extends Component<PropsWithChildren<HelmetProps>> {
static defaultProps: {
defer: boolean;
encodeSpecialCharacters: boolean;
prioritizeSeoTags: boolean;
};
shouldComponentUpdate(nextProps: HelmetProps): boolean;
private mapNestedChildrenToProps;
private flattenArrayTypeChildren;
private mapObjectTypeChildren;
private mapArrayTypeChildrenToProps;
private warnOnInvalidChildren;
private mapChildrenToProps;
render(): React.JSX.Element;
}
+983
View File
@@ -0,0 +1,983 @@
// src/index.tsx
import React5, { Component as Component4 } from "react";
import fastCompare from "react-fast-compare";
import invariant from "invariant";
// src/Provider.tsx
import React3, { Component } from "react";
// src/server.ts
import React from "react";
// src/constants.ts
var TAG_NAMES = /* @__PURE__ */ ((TAG_NAMES2) => {
TAG_NAMES2["BASE"] = "base";
TAG_NAMES2["BODY"] = "body";
TAG_NAMES2["HEAD"] = "head";
TAG_NAMES2["HTML"] = "html";
TAG_NAMES2["LINK"] = "link";
TAG_NAMES2["META"] = "meta";
TAG_NAMES2["NOSCRIPT"] = "noscript";
TAG_NAMES2["SCRIPT"] = "script";
TAG_NAMES2["STYLE"] = "style";
TAG_NAMES2["TITLE"] = "title";
TAG_NAMES2["FRAGMENT"] = "Symbol(react.fragment)";
return TAG_NAMES2;
})(TAG_NAMES || {});
var SEO_PRIORITY_TAGS = {
link: { rel: ["amphtml", "canonical", "alternate"] },
script: { type: ["application/ld+json"] },
meta: {
charset: "",
name: ["generator", "robots", "description"],
property: [
"og:type",
"og:title",
"og:url",
"og:image",
"og:image:alt",
"og:description",
"twitter:url",
"twitter:title",
"twitter:description",
"twitter:image",
"twitter:image:alt",
"twitter:card",
"twitter:site"
]
}
};
var VALID_TAG_NAMES = Object.values(TAG_NAMES);
var REACT_TAG_MAP = {
accesskey: "accessKey",
charset: "charSet",
class: "className",
contenteditable: "contentEditable",
contextmenu: "contextMenu",
"http-equiv": "httpEquiv",
itemprop: "itemProp",
tabindex: "tabIndex"
};
var HTML_TAG_MAP = Object.entries(REACT_TAG_MAP).reduce(
(carry, [key, value]) => {
carry[value] = key;
return carry;
},
{}
);
var HELMET_ATTRIBUTE = "data-rh";
// src/utils.ts
var HELMET_PROPS = {
DEFAULT_TITLE: "defaultTitle",
DEFER: "defer",
ENCODE_SPECIAL_CHARACTERS: "encodeSpecialCharacters",
ON_CHANGE_CLIENT_STATE: "onChangeClientState",
TITLE_TEMPLATE: "titleTemplate",
PRIORITIZE_SEO_TAGS: "prioritizeSeoTags"
};
var getInnermostProperty = (propsList, property) => {
for (let i = propsList.length - 1; i >= 0; i -= 1) {
const props = propsList[i];
if (Object.prototype.hasOwnProperty.call(props, property)) {
return props[property];
}
}
return null;
};
var getTitleFromPropsList = (propsList) => {
let innermostTitle = getInnermostProperty(propsList, "title" /* TITLE */);
const innermostTemplate = getInnermostProperty(propsList, HELMET_PROPS.TITLE_TEMPLATE);
if (Array.isArray(innermostTitle)) {
innermostTitle = innermostTitle.join("");
}
if (innermostTemplate && innermostTitle) {
return innermostTemplate.replace(/%s/g, () => innermostTitle);
}
const innermostDefaultTitle = getInnermostProperty(propsList, HELMET_PROPS.DEFAULT_TITLE);
return innermostTitle || innermostDefaultTitle || void 0;
};
var getOnChangeClientState = (propsList) => getInnermostProperty(propsList, HELMET_PROPS.ON_CHANGE_CLIENT_STATE) || (() => {
});
var getAttributesFromPropsList = (tagType, propsList) => propsList.filter((props) => typeof props[tagType] !== "undefined").map((props) => props[tagType]).reduce((tagAttrs, current) => ({ ...tagAttrs, ...current }), {});
var getBaseTagFromPropsList = (primaryAttributes, propsList) => propsList.filter((props) => typeof props["base" /* BASE */] !== "undefined").map((props) => props["base" /* BASE */]).reverse().reduce((innermostBaseTag, tag) => {
if (!innermostBaseTag.length) {
const keys = Object.keys(tag);
for (let i = 0; i < keys.length; i += 1) {
const attributeKey = keys[i];
const lowerCaseAttributeKey = attributeKey.toLowerCase();
if (primaryAttributes.indexOf(lowerCaseAttributeKey) !== -1 && tag[lowerCaseAttributeKey]) {
return innermostBaseTag.concat(tag);
}
}
}
return innermostBaseTag;
}, []);
var warn = (msg) => console && typeof console.warn === "function" && console.warn(msg);
var getTagsFromPropsList = (tagName, primaryAttributes, propsList) => {
const approvedSeenTags = {};
return propsList.filter((props) => {
if (Array.isArray(props[tagName])) {
return true;
}
if (typeof props[tagName] !== "undefined") {
warn(
`Helmet: ${tagName} should be of type "Array". Instead found type "${typeof props[tagName]}"`
);
}
return false;
}).map((props) => props[tagName]).reverse().reduce((approvedTags, instanceTags) => {
const instanceSeenTags = {};
instanceTags.filter((tag) => {
let primaryAttributeKey;
const keys2 = Object.keys(tag);
for (let i = 0; i < keys2.length; i += 1) {
const attributeKey = keys2[i];
const lowerCaseAttributeKey = attributeKey.toLowerCase();
if (primaryAttributes.indexOf(lowerCaseAttributeKey) !== -1 && !(primaryAttributeKey === "rel" /* REL */ && tag[primaryAttributeKey].toLowerCase() === "canonical") && !(lowerCaseAttributeKey === "rel" /* REL */ && tag[lowerCaseAttributeKey].toLowerCase() === "stylesheet")) {
primaryAttributeKey = lowerCaseAttributeKey;
}
if (primaryAttributes.indexOf(attributeKey) !== -1 && (attributeKey === "innerHTML" /* INNER_HTML */ || attributeKey === "cssText" /* CSS_TEXT */ || attributeKey === "itemprop" /* ITEM_PROP */)) {
primaryAttributeKey = attributeKey;
}
}
if (!primaryAttributeKey || !tag[primaryAttributeKey]) {
return false;
}
const value = tag[primaryAttributeKey].toLowerCase();
if (!approvedSeenTags[primaryAttributeKey]) {
approvedSeenTags[primaryAttributeKey] = {};
}
if (!instanceSeenTags[primaryAttributeKey]) {
instanceSeenTags[primaryAttributeKey] = {};
}
if (!approvedSeenTags[primaryAttributeKey][value]) {
instanceSeenTags[primaryAttributeKey][value] = true;
return true;
}
return false;
}).reverse().forEach((tag) => approvedTags.push(tag));
const keys = Object.keys(instanceSeenTags);
for (let i = 0; i < keys.length; i += 1) {
const attributeKey = keys[i];
const tagUnion = {
...approvedSeenTags[attributeKey],
...instanceSeenTags[attributeKey]
};
approvedSeenTags[attributeKey] = tagUnion;
}
return approvedTags;
}, []).reverse();
};
var getAnyTrueFromPropsList = (propsList, checkedTag) => {
if (Array.isArray(propsList) && propsList.length) {
for (let index = 0; index < propsList.length; index += 1) {
const prop = propsList[index];
if (prop[checkedTag]) {
return true;
}
}
}
return false;
};
var reducePropsToState = (propsList) => ({
baseTag: getBaseTagFromPropsList(["href" /* HREF */], propsList),
bodyAttributes: getAttributesFromPropsList("bodyAttributes" /* BODY */, propsList),
defer: getInnermostProperty(propsList, HELMET_PROPS.DEFER),
encode: getInnermostProperty(propsList, HELMET_PROPS.ENCODE_SPECIAL_CHARACTERS),
htmlAttributes: getAttributesFromPropsList("htmlAttributes" /* HTML */, propsList),
linkTags: getTagsFromPropsList(
"link" /* LINK */,
["rel" /* REL */, "href" /* HREF */],
propsList
),
metaTags: getTagsFromPropsList(
"meta" /* META */,
[
"name" /* NAME */,
"charset" /* CHARSET */,
"http-equiv" /* HTTPEQUIV */,
"property" /* PROPERTY */,
"itemprop" /* ITEM_PROP */
],
propsList
),
noscriptTags: getTagsFromPropsList("noscript" /* NOSCRIPT */, ["innerHTML" /* INNER_HTML */], propsList),
onChangeClientState: getOnChangeClientState(propsList),
scriptTags: getTagsFromPropsList(
"script" /* SCRIPT */,
["src" /* SRC */, "innerHTML" /* INNER_HTML */],
propsList
),
styleTags: getTagsFromPropsList("style" /* STYLE */, ["cssText" /* CSS_TEXT */], propsList),
title: getTitleFromPropsList(propsList),
titleAttributes: getAttributesFromPropsList("titleAttributes" /* TITLE */, propsList),
prioritizeSeoTags: getAnyTrueFromPropsList(propsList, HELMET_PROPS.PRIORITIZE_SEO_TAGS)
});
var flattenArray = (possibleArray) => Array.isArray(possibleArray) ? possibleArray.join("") : possibleArray;
var checkIfPropsMatch = (props, toMatch) => {
const keys = Object.keys(props);
for (let i = 0; i < keys.length; i += 1) {
if (toMatch[keys[i]] && toMatch[keys[i]].includes(props[keys[i]])) {
return true;
}
}
return false;
};
var prioritizer = (elementsList, propsToMatch) => {
if (Array.isArray(elementsList)) {
return elementsList.reduce(
(acc, elementAttrs) => {
if (checkIfPropsMatch(elementAttrs, propsToMatch)) {
acc.priority.push(elementAttrs);
} else {
acc.default.push(elementAttrs);
}
return acc;
},
{ priority: [], default: [] }
);
}
return { default: elementsList, priority: [] };
};
var without = (obj, key) => {
return {
...obj,
[key]: void 0
};
};
// src/server.ts
var SELF_CLOSING_TAGS = ["noscript" /* NOSCRIPT */, "script" /* SCRIPT */, "style" /* STYLE */];
var encodeSpecialCharacters = (str, encode = true) => {
if (encode === false) {
return String(str);
}
return String(str).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#x27;");
};
var generateElementAttributesAsString = (attributes) => Object.keys(attributes).reduce((str, key) => {
const attr = typeof attributes[key] !== "undefined" ? `${key}="${attributes[key]}"` : `${key}`;
return str ? `${str} ${attr}` : attr;
}, "");
var generateTitleAsString = (type, title, attributes, encode) => {
const attributeString = generateElementAttributesAsString(attributes);
const flattenedTitle = flattenArray(title);
return attributeString ? `<${type} ${HELMET_ATTRIBUTE}="true" ${attributeString}>${encodeSpecialCharacters(
flattenedTitle,
encode
)}</${type}>` : `<${type} ${HELMET_ATTRIBUTE}="true">${encodeSpecialCharacters(
flattenedTitle,
encode
)}</${type}>`;
};
var generateTagsAsString = (type, tags, encode = true) => tags.reduce((str, t) => {
const tag = t;
const attributeHtml = Object.keys(tag).filter(
(attribute) => !(attribute === "innerHTML" /* INNER_HTML */ || attribute === "cssText" /* CSS_TEXT */)
).reduce((string, attribute) => {
const attr = typeof tag[attribute] === "undefined" ? attribute : `${attribute}="${encodeSpecialCharacters(tag[attribute], encode)}"`;
return string ? `${string} ${attr}` : attr;
}, "");
const tagContent = tag.innerHTML || tag.cssText || "";
const isSelfClosing = SELF_CLOSING_TAGS.indexOf(type) === -1;
return `${str}<${type} ${HELMET_ATTRIBUTE}="true" ${attributeHtml}${isSelfClosing ? `/>` : `>${tagContent}</${type}>`}`;
}, "");
var convertElementAttributesToReactProps = (attributes, initProps = {}) => Object.keys(attributes).reduce((obj, key) => {
const mapped = REACT_TAG_MAP[key];
obj[mapped || key] = attributes[key];
return obj;
}, initProps);
var generateTitleAsReactComponent = (_type, title, attributes) => {
const initProps = {
key: title,
[HELMET_ATTRIBUTE]: true
};
const props = convertElementAttributesToReactProps(attributes, initProps);
return [React.createElement("title" /* TITLE */, props, title)];
};
var generateTagsAsReactComponent = (type, tags) => tags.map((tag, i) => {
const mappedTag = {
key: i,
[HELMET_ATTRIBUTE]: true
};
Object.keys(tag).forEach((attribute) => {
const mapped = REACT_TAG_MAP[attribute];
const mappedAttribute = mapped || attribute;
if (mappedAttribute === "innerHTML" /* INNER_HTML */ || mappedAttribute === "cssText" /* CSS_TEXT */) {
const content = tag.innerHTML || tag.cssText;
mappedTag.dangerouslySetInnerHTML = { __html: content };
} else {
mappedTag[mappedAttribute] = tag[attribute];
}
});
return React.createElement(type, mappedTag);
});
var getMethodsForTag = (type, tags, encode = true) => {
switch (type) {
case "title" /* TITLE */:
return {
toComponent: () => generateTitleAsReactComponent(type, tags.title, tags.titleAttributes),
toString: () => generateTitleAsString(type, tags.title, tags.titleAttributes, encode)
};
case "bodyAttributes" /* BODY */:
case "htmlAttributes" /* HTML */:
return {
toComponent: () => convertElementAttributesToReactProps(tags),
toString: () => generateElementAttributesAsString(tags)
};
default:
return {
toComponent: () => generateTagsAsReactComponent(type, tags),
toString: () => generateTagsAsString(type, tags, encode)
};
}
};
var getPriorityMethods = ({ metaTags, linkTags, scriptTags, encode }) => {
const meta = prioritizer(metaTags, SEO_PRIORITY_TAGS.meta);
const link = prioritizer(linkTags, SEO_PRIORITY_TAGS.link);
const script = prioritizer(scriptTags, SEO_PRIORITY_TAGS.script);
const priorityMethods = {
toComponent: () => [
...generateTagsAsReactComponent("meta" /* META */, meta.priority),
...generateTagsAsReactComponent("link" /* LINK */, link.priority),
...generateTagsAsReactComponent("script" /* SCRIPT */, script.priority)
],
toString: () => (
// generate all the tags as strings and concatenate them
`${getMethodsForTag("meta" /* META */, meta.priority, encode)} ${getMethodsForTag(
"link" /* LINK */,
link.priority,
encode
)} ${getMethodsForTag("script" /* SCRIPT */, script.priority, encode)}`
)
};
return {
priorityMethods,
metaTags: meta.default,
linkTags: link.default,
scriptTags: script.default
};
};
var mapStateOnServer = (props) => {
const {
baseTag,
bodyAttributes,
encode = true,
htmlAttributes,
noscriptTags,
styleTags,
title = "",
titleAttributes,
prioritizeSeoTags
} = props;
let { linkTags, metaTags, scriptTags } = props;
let priorityMethods = {
toComponent: () => [],
toString: () => ""
};
if (prioritizeSeoTags) {
({ priorityMethods, linkTags, metaTags, scriptTags } = getPriorityMethods(props));
}
return {
priority: priorityMethods,
base: getMethodsForTag("base" /* BASE */, baseTag, encode),
bodyAttributes: getMethodsForTag("bodyAttributes" /* BODY */, bodyAttributes, encode),
htmlAttributes: getMethodsForTag("htmlAttributes" /* HTML */, htmlAttributes, encode),
link: getMethodsForTag("link" /* LINK */, linkTags, encode),
meta: getMethodsForTag("meta" /* META */, metaTags, encode),
noscript: getMethodsForTag("noscript" /* NOSCRIPT */, noscriptTags, encode),
script: getMethodsForTag("script" /* SCRIPT */, scriptTags, encode),
style: getMethodsForTag("style" /* STYLE */, styleTags, encode),
title: getMethodsForTag("title" /* TITLE */, { title, titleAttributes }, encode)
};
};
var server_default = mapStateOnServer;
// src/HelmetData.ts
var instances = [];
var isDocument = !!(typeof window !== "undefined" && window.document && window.document.createElement);
var HelmetData = class {
instances = [];
canUseDOM = isDocument;
context;
value = {
setHelmet: (serverState) => {
this.context.helmet = serverState;
},
helmetInstances: {
get: () => this.canUseDOM ? instances : this.instances,
add: (instance) => {
(this.canUseDOM ? instances : this.instances).push(instance);
},
remove: (instance) => {
const index = (this.canUseDOM ? instances : this.instances).indexOf(instance);
(this.canUseDOM ? instances : this.instances).splice(index, 1);
}
}
};
constructor(context, canUseDOM) {
this.context = context;
this.canUseDOM = canUseDOM || false;
if (!canUseDOM) {
context.helmet = server_default({
baseTag: [],
bodyAttributes: {},
encodeSpecialCharacters: true,
htmlAttributes: {},
linkTags: [],
metaTags: [],
noscriptTags: [],
scriptTags: [],
styleTags: [],
title: "",
titleAttributes: {}
});
}
}
};
// src/reactVersion.ts
import React2 from "react";
var major = parseInt(React2.version.split(".")[0], 10);
var isReact19 = major >= 19;
// src/Provider.tsx
var defaultValue = {};
var Context = React3.createContext(defaultValue);
var HelmetProvider = class _HelmetProvider extends Component {
static canUseDOM = isDocument;
helmetData;
constructor(props) {
super(props);
if (isReact19) {
this.helmetData = null;
} else {
this.helmetData = new HelmetData(this.props.context || {}, _HelmetProvider.canUseDOM);
}
}
render() {
if (isReact19) {
return /* @__PURE__ */ React3.createElement(React3.Fragment, null, this.props.children);
}
return /* @__PURE__ */ React3.createElement(Context.Provider, { value: this.helmetData.value }, this.props.children);
}
};
// src/Dispatcher.tsx
import { Component as Component2 } from "react";
import shallowEqual from "shallowequal";
// src/client.ts
var updateTags = (type, tags) => {
const headElement = document.head || document.querySelector("head" /* HEAD */);
const tagNodes = headElement.querySelectorAll(`${type}[${HELMET_ATTRIBUTE}]`);
const oldTags = [].slice.call(tagNodes);
const newTags = [];
let indexToDelete;
if (tags && tags.length) {
tags.forEach((tag) => {
const newElement = document.createElement(type);
for (const attribute in tag) {
if (Object.prototype.hasOwnProperty.call(tag, attribute)) {
if (attribute === "innerHTML" /* INNER_HTML */) {
newElement.innerHTML = tag.innerHTML;
} else if (attribute === "cssText" /* CSS_TEXT */) {
const cssText = tag.cssText;
newElement.appendChild(document.createTextNode(cssText));
} else {
const attr = attribute;
const value = typeof tag[attr] === "undefined" ? "" : tag[attr];
newElement.setAttribute(attribute, value);
}
}
}
newElement.setAttribute(HELMET_ATTRIBUTE, "true");
if (oldTags.some((existingTag, index) => {
indexToDelete = index;
return newElement.isEqualNode(existingTag);
})) {
oldTags.splice(indexToDelete, 1);
} else {
newTags.push(newElement);
}
});
}
oldTags.forEach((tag) => tag.parentNode?.removeChild(tag));
newTags.forEach((tag) => headElement.appendChild(tag));
return {
oldTags,
newTags
};
};
var updateAttributes = (tagName, attributes) => {
const elementTag = document.getElementsByTagName(tagName)[0];
if (!elementTag) {
return;
}
const helmetAttributeString = elementTag.getAttribute(HELMET_ATTRIBUTE);
const helmetAttributes = helmetAttributeString ? helmetAttributeString.split(",") : [];
const attributesToRemove = [...helmetAttributes];
const attributeKeys = Object.keys(attributes);
for (const attribute of attributeKeys) {
const value = attributes[attribute] || "";
if (elementTag.getAttribute(attribute) !== value) {
elementTag.setAttribute(attribute, value);
}
if (helmetAttributes.indexOf(attribute) === -1) {
helmetAttributes.push(attribute);
}
const indexToSave = attributesToRemove.indexOf(attribute);
if (indexToSave !== -1) {
attributesToRemove.splice(indexToSave, 1);
}
}
for (let i = attributesToRemove.length - 1; i >= 0; i -= 1) {
elementTag.removeAttribute(attributesToRemove[i]);
}
if (helmetAttributes.length === attributesToRemove.length) {
elementTag.removeAttribute(HELMET_ATTRIBUTE);
} else if (elementTag.getAttribute(HELMET_ATTRIBUTE) !== attributeKeys.join(",")) {
elementTag.setAttribute(HELMET_ATTRIBUTE, attributeKeys.join(","));
}
};
var updateTitle = (title, attributes) => {
if (typeof title !== "undefined" && document.title !== title) {
document.title = flattenArray(title);
}
updateAttributes("title" /* TITLE */, attributes);
};
var commitTagChanges = (newState, cb) => {
const {
baseTag,
bodyAttributes,
htmlAttributes,
linkTags,
metaTags,
noscriptTags,
onChangeClientState,
scriptTags,
styleTags,
title,
titleAttributes
} = newState;
updateAttributes("body" /* BODY */, bodyAttributes);
updateAttributes("html" /* HTML */, htmlAttributes);
updateTitle(title, titleAttributes);
const tagUpdates = {
baseTag: updateTags("base" /* BASE */, baseTag),
linkTags: updateTags("link" /* LINK */, linkTags),
metaTags: updateTags("meta" /* META */, metaTags),
noscriptTags: updateTags("noscript" /* NOSCRIPT */, noscriptTags),
scriptTags: updateTags("script" /* SCRIPT */, scriptTags),
styleTags: updateTags("style" /* STYLE */, styleTags)
};
const addedTags = {};
const removedTags = {};
Object.keys(tagUpdates).forEach((tagType) => {
const { newTags, oldTags } = tagUpdates[tagType];
if (newTags.length) {
addedTags[tagType] = newTags;
}
if (oldTags.length) {
removedTags[tagType] = tagUpdates[tagType].oldTags;
}
});
if (cb) {
cb();
}
onChangeClientState(newState, addedTags, removedTags);
};
var _helmetCallback = null;
var handleStateChangeOnClient = (newState) => {
if (_helmetCallback) {
cancelAnimationFrame(_helmetCallback);
}
if (newState.defer) {
_helmetCallback = requestAnimationFrame(() => {
commitTagChanges(newState, () => {
_helmetCallback = null;
});
});
} else {
commitTagChanges(newState);
_helmetCallback = null;
}
};
var client_default = handleStateChangeOnClient;
// src/Dispatcher.tsx
var HelmetDispatcher = class extends Component2 {
rendered = false;
shouldComponentUpdate(nextProps) {
return !shallowEqual(nextProps, this.props);
}
componentDidUpdate() {
this.emitChange();
}
componentWillUnmount() {
const { helmetInstances } = this.props.context;
helmetInstances.remove(this);
this.emitChange();
}
emitChange() {
const { helmetInstances, setHelmet } = this.props.context;
let serverState = null;
const state = reducePropsToState(
helmetInstances.get().map((instance) => {
const { context: _context, ...props } = instance.props;
return props;
})
);
if (HelmetProvider.canUseDOM) {
client_default(state);
} else if (server_default) {
serverState = server_default(state);
}
setHelmet(serverState);
}
// componentWillMount will be deprecated
// for SSR, initialize on first render
// constructor is also unsafe in StrictMode
init() {
if (this.rendered) {
return;
}
this.rendered = true;
const { helmetInstances } = this.props.context;
helmetInstances.add(this);
this.emitChange();
}
render() {
this.init();
return null;
}
};
// src/React19Dispatcher.tsx
import React4, { Component as Component3 } from "react";
var react19Instances = [];
var toHtmlAttributes = (props) => {
const result = {};
for (const key of Object.keys(props)) {
result[HTML_TAG_MAP[key] || key] = props[key];
}
return result;
};
var toReactProps = (attrs) => {
const result = {};
for (const key of Object.keys(attrs)) {
const mapped = REACT_TAG_MAP[key];
result[mapped || key] = attrs[key];
}
return result;
};
var applyAttributes = (tagName, attributes) => {
if (!isDocument)
return;
const el = document.getElementsByTagName(tagName)[0];
if (!el)
return;
const managedAttr = "data-rh-managed";
const prev = el.getAttribute(managedAttr);
const prevKeys = prev ? prev.split(",") : [];
const nextKeys = Object.keys(attributes);
for (const key of prevKeys) {
if (!nextKeys.includes(key)) {
el.removeAttribute(key);
}
}
for (const key of nextKeys) {
const value = attributes[key];
if (value === void 0 || value === null || value === false) {
el.removeAttribute(key);
} else if (value === true) {
el.setAttribute(key, "");
} else {
el.setAttribute(key, String(value));
}
}
if (nextKeys.length > 0) {
el.setAttribute(managedAttr, nextKeys.join(","));
} else {
el.removeAttribute(managedAttr);
}
};
var syncAllAttributes = () => {
const htmlAttrs = {};
const bodyAttrs = {};
for (const instance of react19Instances) {
const { htmlAttributes, bodyAttributes } = instance.props;
if (htmlAttributes) {
Object.assign(htmlAttrs, toHtmlAttributes(htmlAttributes));
}
if (bodyAttributes) {
Object.assign(bodyAttrs, toHtmlAttributes(bodyAttributes));
}
}
applyAttributes("html" /* HTML */, htmlAttrs);
applyAttributes("body" /* BODY */, bodyAttrs);
};
var React19Dispatcher = class extends Component3 {
componentDidMount() {
react19Instances.push(this);
syncAllAttributes();
}
componentDidUpdate() {
syncAllAttributes();
}
componentWillUnmount() {
const index = react19Instances.indexOf(this);
if (index !== -1) {
react19Instances.splice(index, 1);
}
syncAllAttributes();
}
resolveTitle() {
const { title, titleTemplate, defaultTitle } = this.props;
if (title && titleTemplate) {
return titleTemplate.replace(/%s/g, () => Array.isArray(title) ? title.join("") : title);
}
return title || defaultTitle || void 0;
}
renderTitle() {
const title = this.resolveTitle();
if (title === void 0)
return null;
const titleAttributes = this.props.titleAttributes || {};
return React4.createElement("title" /* TITLE */, toReactProps(titleAttributes), title);
}
renderBase() {
const { base } = this.props;
if (!base)
return null;
return React4.createElement("base" /* BASE */, toReactProps(base));
}
renderMeta() {
const { meta } = this.props;
if (!meta || !Array.isArray(meta))
return null;
return meta.map(
(attrs, i) => React4.createElement("meta" /* META */, {
key: i,
...toReactProps(attrs)
})
);
}
renderLink() {
const { link } = this.props;
if (!link || !Array.isArray(link))
return null;
return link.map(
(attrs, i) => React4.createElement("link" /* LINK */, {
key: i,
...toReactProps(attrs)
})
);
}
renderScript() {
const { script } = this.props;
if (!script || !Array.isArray(script))
return null;
return script.map((attrs, i) => {
const { innerHTML, ...rest } = attrs;
const props = toReactProps(rest);
if (innerHTML) {
props.dangerouslySetInnerHTML = { __html: innerHTML };
}
return React4.createElement("script" /* SCRIPT */, { key: i, ...props });
});
}
renderStyle() {
const { style } = this.props;
if (!style || !Array.isArray(style))
return null;
return style.map((attrs, i) => {
const { cssText, ...rest } = attrs;
const props = toReactProps(rest);
if (cssText) {
props.dangerouslySetInnerHTML = { __html: cssText };
}
return React4.createElement("style" /* STYLE */, { key: i, ...props });
});
}
renderNoscript() {
const { noscript } = this.props;
if (!noscript || !Array.isArray(noscript))
return null;
return noscript.map((attrs, i) => {
const { innerHTML, ...rest } = attrs;
const props = toReactProps(rest);
if (innerHTML) {
props.dangerouslySetInnerHTML = { __html: innerHTML };
}
return React4.createElement("noscript" /* NOSCRIPT */, { key: i, ...props });
});
}
render() {
return React4.createElement(
React4.Fragment,
null,
this.renderTitle(),
this.renderBase(),
this.renderMeta(),
this.renderLink(),
this.renderScript(),
this.renderStyle(),
this.renderNoscript()
);
}
};
// src/index.tsx
var Helmet = class extends Component4 {
static defaultProps = {
defer: true,
encodeSpecialCharacters: true,
prioritizeSeoTags: false
};
shouldComponentUpdate(nextProps) {
return !fastCompare(without(this.props, "helmetData"), without(nextProps, "helmetData"));
}
mapNestedChildrenToProps(child, nestedChildren) {
if (!nestedChildren) {
return null;
}
switch (child.type) {
case "script" /* SCRIPT */:
case "noscript" /* NOSCRIPT */:
return {
innerHTML: nestedChildren
};
case "style" /* STYLE */:
return {
cssText: nestedChildren
};
default:
throw new Error(
`<${child.type} /> elements are self-closing and can not contain children. Refer to our API for more information.`
);
}
}
flattenArrayTypeChildren(child, arrayTypeChildren, newChildProps, nestedChildren) {
return {
...arrayTypeChildren,
[child.type]: [
...arrayTypeChildren[child.type] || [],
{
...newChildProps,
...this.mapNestedChildrenToProps(child, nestedChildren)
}
]
};
}
mapObjectTypeChildren(child, newProps, newChildProps, nestedChildren) {
switch (child.type) {
case "title" /* TITLE */:
return {
...newProps,
[child.type]: nestedChildren,
titleAttributes: { ...newChildProps }
};
case "body" /* BODY */:
return {
...newProps,
bodyAttributes: { ...newChildProps }
};
case "html" /* HTML */:
return {
...newProps,
htmlAttributes: { ...newChildProps }
};
default:
return {
...newProps,
[child.type]: { ...newChildProps }
};
}
}
mapArrayTypeChildrenToProps(arrayTypeChildren, newProps) {
let newFlattenedProps = { ...newProps };
Object.keys(arrayTypeChildren).forEach((arrayChildName) => {
newFlattenedProps = {
...newFlattenedProps,
[arrayChildName]: arrayTypeChildren[arrayChildName]
};
});
return newFlattenedProps;
}
warnOnInvalidChildren(child, nestedChildren) {
invariant(
VALID_TAG_NAMES.some((name) => child.type === name),
typeof child.type === "function" ? `You may be attempting to nest <Helmet> components within each other, which is not allowed. Refer to our API for more information.` : `Only elements types ${VALID_TAG_NAMES.join(
", "
)} are allowed. Helmet does not support rendering <${child.type}> elements. Refer to our API for more information.`
);
invariant(
!nestedChildren || typeof nestedChildren === "string" || Array.isArray(nestedChildren) && !nestedChildren.some((nestedChild) => typeof nestedChild !== "string"),
`Helmet expects a string as a child of <${child.type}>. Did you forget to wrap your children in braces? ( <${child.type}>{\`\`}</${child.type}> ) Refer to our API for more information.`
);
return true;
}
mapChildrenToProps(children, newProps) {
let arrayTypeChildren = {};
React5.Children.forEach(children, (child) => {
if (!child || !child.props) {
return;
}
const { children: nestedChildren, ...childProps } = child.props;
const newChildProps = Object.keys(childProps).reduce((obj, key) => {
obj[HTML_TAG_MAP[key] || key] = childProps[key];
return obj;
}, {});
let { type } = child;
if (typeof type === "symbol") {
type = type.toString();
} else {
this.warnOnInvalidChildren(child, nestedChildren);
}
switch (type) {
case "Symbol(react.fragment)" /* FRAGMENT */:
newProps = this.mapChildrenToProps(nestedChildren, newProps);
break;
case "link" /* LINK */:
case "meta" /* META */:
case "noscript" /* NOSCRIPT */:
case "script" /* SCRIPT */:
case "style" /* STYLE */:
arrayTypeChildren = this.flattenArrayTypeChildren(
child,
arrayTypeChildren,
newChildProps,
nestedChildren
);
break;
default:
newProps = this.mapObjectTypeChildren(child, newProps, newChildProps, nestedChildren);
break;
}
});
return this.mapArrayTypeChildrenToProps(arrayTypeChildren, newProps);
}
render() {
const { children, ...props } = this.props;
let newProps = { ...props };
let { helmetData } = props;
if (children) {
newProps = this.mapChildrenToProps(children, newProps);
}
if (helmetData && !(helmetData instanceof HelmetData)) {
const data = helmetData;
helmetData = new HelmetData(data.context, true);
delete newProps.helmetData;
}
if (isReact19) {
return /* @__PURE__ */ React5.createElement(React19Dispatcher, { ...newProps });
}
return helmetData ? /* @__PURE__ */ React5.createElement(HelmetDispatcher, { ...newProps, context: helmetData.value }) : /* @__PURE__ */ React5.createElement(Context.Consumer, null, (context) => /* @__PURE__ */ React5.createElement(HelmetDispatcher, { ...newProps, context }));
}
};
export {
Helmet,
HelmetData,
HelmetProvider
};
+1014
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
export declare const isReact19: boolean;
+45
View File
@@ -0,0 +1,45 @@
import React from 'react';
import type { MappedServerState } from './types';
declare const mapStateOnServer: (props: MappedServerState) => {
priority: {
toComponent: () => React.ReactElement[];
toString: () => string;
};
base: {
toComponent: () => {};
toString: () => string;
};
bodyAttributes: {
toComponent: () => {};
toString: () => string;
};
htmlAttributes: {
toComponent: () => {};
toString: () => string;
};
link: {
toComponent: () => {};
toString: () => string;
};
meta: {
toComponent: () => {};
toString: () => string;
};
noscript: {
toComponent: () => {};
toString: () => string;
};
script: {
toComponent: () => {};
toString: () => string;
};
style: {
toComponent: () => {};
toString: () => string;
};
title: {
toComponent: () => {};
toString: () => string;
};
};
export default mapStateOnServer;
+84
View File
@@ -0,0 +1,84 @@
import type { HTMLAttributes, JSX } from 'react';
import type HelmetData from './HelmetData';
export type Attributes = {
[key: string]: string;
};
interface OtherElementAttributes {
[key: string]: string | number | boolean | null | undefined;
}
export type HtmlProps = JSX.IntrinsicElements['html'] & OtherElementAttributes;
export type BodyProps = JSX.IntrinsicElements['body'] & OtherElementAttributes;
export type LinkProps = JSX.IntrinsicElements['link'];
export type MetaProps = JSX.IntrinsicElements['meta'] & {
charset?: string | undefined;
'http-equiv'?: string | undefined;
itemprop?: string | undefined;
};
export type TitleProps = HTMLAttributes<HTMLTitleElement>;
export interface HelmetTags {
baseTag: HTMLBaseElement[];
linkTags: HTMLLinkElement[];
metaTags: HTMLMetaElement[];
noscriptTags: HTMLElement[];
scriptTags: HTMLScriptElement[];
styleTags: HTMLStyleElement[];
}
export interface HelmetDatum {
toString(): string;
toComponent(): React.ReactElement[];
}
export interface HelmetHTMLBodyDatum {
toString(): string;
toComponent(): React.HTMLAttributes<HTMLBodyElement>;
}
export interface HelmetHTMLElementDatum {
toString(): string;
toComponent(): React.HTMLAttributes<HTMLHtmlElement>;
}
export interface HelmetServerState {
base: HelmetDatum;
bodyAttributes: HelmetHTMLBodyDatum;
htmlAttributes: HelmetHTMLElementDatum;
link: HelmetDatum;
meta: HelmetDatum;
noscript: HelmetDatum;
script: HelmetDatum;
style: HelmetDatum;
title: HelmetDatum;
priority: HelmetDatum;
}
export type MappedServerState = HelmetProps & HelmetTags & {
encode?: boolean;
};
export interface TagList {
[key: string]: HTMLElement[];
}
export interface StateUpdate extends HelmetTags {
bodyAttributes: BodyProps;
defer: boolean;
htmlAttributes: HtmlProps;
onChangeClientState: (newState: StateUpdate, addedTags: TagList, removedTags: TagList) => void;
title: string;
titleAttributes: TitleProps;
}
export interface HelmetProps {
async?: boolean;
base?: Attributes;
bodyAttributes?: BodyProps;
defaultTitle?: string;
defer?: boolean;
encodeSpecialCharacters?: boolean;
helmetData?: HelmetData;
htmlAttributes?: HtmlProps;
onChangeClientState?: (newState: StateUpdate, addedTags: HelmetTags, removedTags: HelmetTags) => void;
link?: LinkProps[];
meta?: MetaProps[];
noscript?: Attributes[];
script?: Attributes[];
style?: Attributes[];
title?: string;
titleAttributes?: Attributes;
titleTemplate?: string;
prioritizeSeoTags?: boolean;
}
export {};
+33
View File
@@ -0,0 +1,33 @@
interface PropList {
[key: string]: any;
}
type PropsList = PropList[];
type AttributeList = string[];
interface MatchProps {
[key: string]: string | AttributeList;
}
declare const reducePropsToState: (propsList: PropsList) => {
baseTag: any;
bodyAttributes: any;
defer: any;
encode: any;
htmlAttributes: any;
linkTags: any;
metaTags: any;
noscriptTags: any;
onChangeClientState: any;
scriptTags: any;
styleTags: any;
title: any;
titleAttributes: any;
prioritizeSeoTags: boolean;
};
export declare const flattenArray: (possibleArray: string[] | string) => string;
export { reducePropsToState };
export declare const prioritizer: (elementsList: HTMLElement[], propsToMatch: MatchProps) => {
priority: HTMLElement[];
default: HTMLElement[];
};
export declare const without: (obj: PropList, key: string) => {
[x: string]: any;
};
+81
View File
@@ -0,0 +1,81 @@
{
"name": "react-helmet-async",
"version": "3.0.0",
"description": "Thread-safe Helmet for React 1618, with native support for React 19+",
"sideEffects": false,
"main": "./lib/index.js",
"module": "./lib/index.esm.js",
"types": "./lib/index.d.ts",
"exports": {
".": {
"import": {
"types": "./lib/index.d.ts",
"default": "./lib/index.esm.js"
},
"require": {
"types": "./lib/index.d.ts",
"default": "./lib/index.js"
}
}
},
"repository": "http://github.com/staylor/react-helmet-async",
"author": "Scott Taylor <scott.c.taylor@mac.com>",
"license": "Apache-2.0",
"files": [
"lib/"
],
"dependencies": {
"invariant": "^2.2.4",
"react-fast-compare": "^3.2.2",
"shallowequal": "^1.1.0"
},
"devDependencies": {
"@commitlint/cli": "18.4.3",
"@commitlint/config-conventional": "18.4.3",
"@playwright/test": "^1.58.2",
"@remix-run/eslint-config": "2.3.1",
"@testing-library/jest-dom": "6.1.5",
"@testing-library/react": "14.1.2",
"@types/eslint": "8.44.8",
"@types/invariant": "2.2.37",
"@types/jsdom": "21.1.6",
"@types/react": "18.2.39",
"@types/react-dom": "^18.3.7",
"@types/shallowequal": "1.1.5",
"@vitejs/plugin-react": "4.2.0",
"esbuild": "0.19.8",
"eslint": "8.54.0",
"eslint-config-prettier": "9.0.0",
"eslint-plugin-prettier": "5.0.1",
"husky": "8.0.3",
"jsdom": "22.1.0",
"playwright": "^1.58.2",
"prettier": "3.1.0",
"raf": "3.4.1",
"react": "18.2.0",
"react-dom": "18.2.0",
"rimraf": "5.0.5",
"tsx": "4.6.1",
"typescript": "5.2.2",
"vite": "4.5.0",
"vitest": "0.34.6"
},
"peerDependencies": {
"react": "^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
},
"scripts": {
"clean": "rimraf lib",
"lint": "eslint --cache --cache-location ./node_modules/.cache/eslint --report-unused-disable-directives .",
"lint-fix": "pnpm lint --fix",
"test": "vitest run",
"test:e2e:server": "vitest run --config e2e/vitest.config.ts",
"test:e2e:browser": "playwright test --config e2e/playwright.config.ts",
"test:e2e": "pnpm run test:e2e:server && pnpm run test:e2e:browser",
"test:all": "pnpm test && pnpm run test:e2e",
"test-watch": "pnpm test -- --watch",
"test-update": "pnpm test -- -u",
"compile": "pnpm run clean && NODE_ENV=production tsx build.ts && pnpm run types",
"prepare": "pnpm run compile && husky install",
"types": "tsc --project tsconfig.build.json"
}
}
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Alberto Leal <mailforalberto@gmail.com> (github.com/dashed)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+63
View File
@@ -0,0 +1,63 @@
# shallowequal [![Build Status](https://travis-ci.org/dashed/shallowequal.svg)](https://travis-ci.org/dashed/shallowequal) [![Downloads](https://img.shields.io/npm/dm/shallowequal.svg)](https://npmjs.com/shallowequal) [![npm version](https://img.shields.io/npm/v/shallowequal.svg?style=flat)](https://www.npmjs.com/package/shallowequal)
[![Greenkeeper badge](https://badges.greenkeeper.io/dashed/shallowequal.svg)](https://greenkeeper.io/)
> `shallowequal` is like lodash's [`isEqualWith`](https://lodash.com/docs/4.17.4#isEqualWith) but for shallow (strict) equal.
`shallowequal(value, other, [customizer], [thisArg])`
Performs a ***shallow equality*** comparison between two values (i.e. `value` and `other`) to determine if they are equivalent.
The equality is performed by iterating through keys on the given `value`, and returning `false` whenever any key has values which are not **strictly equal** between `value` and `other`. Otherwise, return `true` whenever the values of all keys are strictly equal.
If `customizer` (expected to be a function) is provided it is invoked to compare values. If `customizer` returns `undefined` (i.e. `void 0`), then comparisons are handled by the `shallowequal` function instead.
The `customizer` is bound to `thisArg` and invoked with three arguments: `(value, other, key)`.
**NOTE:** Docs are (shamelessly) adapted from [lodash's v3.x docs](https://lodash.com/docs/3.10.1#isEqualWith)
## Install
```sh
$ yarn add shallowequal
# npm v5+
$ npm install shallowequal
# before npm v5
$ npm install --save shallowequal
```
## Usage
```js
const shallowequal = require('shallowequal');
const object = { 'user': 'fred' };
const other = { 'user': 'fred' };
object == other;
// → false
shallowequal(object, other);
// → true
```
## Credit
Code for `shallowEqual` originated from https://github.com/gaearon/react-pure-render/ and has since been refactored to have the exact same API as `lodash.isEqualWith` (as of `v4.17.4`).
## Development
- `node.js` and `npm`. See: https://github.com/creationix/nvm#installation
- `yarn`. See: https://yarnpkg.com/en/docs/install
- `npm` dependencies. Run: `yarn install`
### Chores
- Lint: `yarn lint`
- Test: `yarn test`
- Pretty: `yarn pretty`
- Pre-publish: `yarn prepublish`
## License
MIT.
+46
View File
@@ -0,0 +1,46 @@
//
module.exports = function shallowEqual(objA, objB, compare, compareContext) {
var ret = compare ? compare.call(compareContext, objA, objB) : void 0;
if (ret !== void 0) {
return !!ret;
}
if (objA === objB) {
return true;
}
if (typeof objA !== "object" || !objA || typeof objB !== "object" || !objB) {
return false;
}
var keysA = Object.keys(objA);
var keysB = Object.keys(objB);
if (keysA.length !== keysB.length) {
return false;
}
var bHasOwnProperty = Object.prototype.hasOwnProperty.bind(objB);
// Test for A's keys different from B.
for (var idx = 0; idx < keysA.length; idx++) {
var key = keysA[idx];
if (!bHasOwnProperty(key)) {
return false;
}
var valueA = objA[key];
var valueB = objB[key];
ret = compare ? compare.call(compareContext, valueA, valueB, key) : void 0;
if (ret === false || (ret === void 0 && valueA !== valueB)) {
return false;
}
}
return true;
};
+8
View File
@@ -0,0 +1,8 @@
// @flow
declare module.exports: <T, U>(
objA?: ?T,
objB?: ?U,
compare?: ?(objValue: any, otherValue: any, key?: string) => boolean | void,
compareContext?: ?any
) => boolean;
+51
View File
@@ -0,0 +1,51 @@
// @flow
module.exports = function shallowEqual<T, U>(
objA?: ?T,
objB?: ?U,
compare?: ?(objValue: any, otherValue: any, key?: string) => boolean | void,
compareContext?: ?any
): boolean {
var ret = compare ? compare.call(compareContext, objA, objB) : void 0;
if (ret !== void 0) {
return !!ret;
}
if (objA === objB) {
return true;
}
if (typeof objA !== "object" || !objA || typeof objB !== "object" || !objB) {
return false;
}
var keysA = Object.keys(objA);
var keysB = Object.keys(objB);
if (keysA.length !== keysB.length) {
return false;
}
var bHasOwnProperty = Object.prototype.hasOwnProperty.bind(objB);
// Test for A's keys different from B.
for (var idx = 0; idx < keysA.length; idx++) {
var key = keysA[idx];
if (!bHasOwnProperty(key)) {
return false;
}
var valueA = objA[key];
var valueB = objB[key];
ret = compare ? compare.call(compareContext, valueA, valueB, key) : void 0;
if (ret === false || (ret === void 0 && valueA !== valueB)) {
return false;
}
}
return true;
};
+60
View File
@@ -0,0 +1,60 @@
{
"name": "shallowequal",
"version": "1.1.0",
"description": "Like lodash isEqualWith but for shallow equal.",
"main": "index.js",
"scripts": {
"lint": "eslint index.js test",
"test": "mocha --require babel-register",
"build:strip-flow":
"flow-remove-types --pretty index.original.js > index.js",
"build:gen-flow": "flow gen-flow-files index.original.js > index.js.flow",
"build": "npm run build:strip-flow && npm run build:gen-flow",
"prepublish":
"npm run build && npm run pretty && npm run lint && npm run test",
"travis": "npm run lint && npm run test",
"pretty": "prettier --write --tab-width 2 'test/**/*.js' '*.{js,js.flow}'",
"precommit": "lint-staged"
},
"lint-staged": {
"*.{js,json,css,js.flow}": ["prettier --write", "git add"]
},
"author": {
"name": "Alberto Leal",
"email": "mailforalberto@gmail.com",
"url": "github.com/dashed"
},
"repository": "dashed/shallowequal",
"license": "MIT",
"files": ["index.js", "index.js.flow", "index.original.js"],
"keywords": [
"shallowequal",
"shallow",
"equal",
"isequal",
"compare",
"isequalwith"
],
"eslintConfig": {
"parser": "babel-eslint",
"env": {
"browser": true,
"node": true,
"mocha": true
},
"extends": ["eslint:recommended"]
},
"devDependencies": {
"babel-eslint": "^8.0.0",
"babel-preset-env": "^1.6.1",
"babel-register": "^6.24.1",
"chai": "^4.0.0",
"eslint": "^4.7.1",
"flow-bin": "^0.75.0",
"flow-remove-types": "^1.2.3",
"husky": "^0.14.3",
"lint-staged": "^6.0.0",
"mocha": "^5.0.0",
"prettier": "^1.9.2"
}
}