use-react-router-breadcrumbs
Advanced tools
Comparing version 2.0.2 to 3.0.0
@@ -5,3 +5,2 @@ 'use strict'; | ||
var _toConsumableArray = require('@babel/runtime/helpers/toConsumableArray'); | ||
var React = require('react'); | ||
@@ -12,37 +11,112 @@ var reactRouter = require('react-router'); | ||
var _toConsumableArray__default = /*#__PURE__*/_interopDefaultLegacy(_toConsumableArray); | ||
var React__default = /*#__PURE__*/_interopDefaultLegacy(React); | ||
/*! ***************************************************************************** | ||
Copyright (c) Microsoft Corporation. | ||
var joinPaths = function joinPaths(paths) { | ||
return paths.join('/').replace(/\/\/+/g, '/'); | ||
}; | ||
Permission to use, copy, modify, and/or distribute this software for any | ||
purpose with or without fee is hereby granted. | ||
var paramRe = /^:\w+$/; | ||
var dynamicSegmentValue = 3; | ||
var indexRouteValue = 2; | ||
var emptySegmentValue = 1; | ||
var staticSegmentValue = 10; | ||
var splatPenalty = -2; | ||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH | ||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY | ||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, | ||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM | ||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR | ||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR | ||
PERFORMANCE OF THIS SOFTWARE. | ||
***************************************************************************** */ | ||
var isSplat = function isSplat(s) { | ||
return s === '*'; | ||
}; | ||
function __rest(s, e) { | ||
var t = {}; | ||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) | ||
t[p] = s[p]; | ||
if (s != null && typeof Object.getOwnPropertySymbols === "function") | ||
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { | ||
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) | ||
t[p[i]] = s[p[i]]; | ||
} | ||
return t; | ||
function computeScore(path, index) { | ||
var segments = path.split('/'); | ||
var initialScore = segments.length; | ||
if (segments.some(isSplat)) { | ||
initialScore += splatPenalty; | ||
} | ||
if (index) { | ||
initialScore += indexRouteValue; | ||
} | ||
return segments.filter(function (s) { | ||
return !isSplat(s); | ||
}).reduce(function (score, segment) { | ||
if (paramRe.test(segment)) { | ||
return score + dynamicSegmentValue; | ||
} | ||
if (segment === '') { | ||
return score + emptySegmentValue; | ||
} | ||
return score + staticSegmentValue; | ||
}, initialScore); | ||
} | ||
var DEFAULT_MATCH_OPTIONS = { | ||
exact: true | ||
}; | ||
var NO_BREADCRUMB = 'NO_BREADCRUMB'; | ||
function compareIndexes(a, b) { | ||
var siblings = a.length === b.length && a.slice(0, -1).every(function (n, i) { | ||
return n === b[i]; | ||
}); | ||
return siblings ? a[a.length - 1] - b[b.length - 1] : 0; | ||
} | ||
function flattenRoutes(routes) { | ||
var branches = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; | ||
var parentsMeta = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : []; | ||
var parentPath = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : ''; | ||
routes.forEach(function (route, index) { | ||
if (typeof route.path !== 'string' && !route.index) { | ||
throw new Error('useBreadcrumbs: `path` or `index` must be provided in every route object'); | ||
} | ||
if (route.path && route.index) { | ||
throw new Error('useBreadcrumbs: `path` and `index` cannot be provided at the same time'); | ||
} | ||
var meta = { | ||
relativePath: route.path || '', | ||
childrenIndex: index, | ||
route: route | ||
}; | ||
if (meta.relativePath.charAt(0) === '/') { | ||
if (!meta.relativePath.startsWith(parentPath)) { | ||
throw new Error('useBreadcrumbs: The absolute path of the child route must start with the parent path'); | ||
} | ||
meta.relativePath = meta.relativePath.slice(parentPath.length); | ||
} | ||
var path = joinPaths([parentPath, meta.relativePath]); | ||
var routesMeta = parentsMeta.concat(meta); | ||
if (route.children && route.children.length > 0) { | ||
if (route.index) { | ||
throw new Error('useBreadcrumbs: Index route cannot have child routes'); | ||
} | ||
flattenRoutes(route.children, branches, routesMeta, path); | ||
} | ||
branches.push({ | ||
path: path, | ||
score: computeScore(path, route.index), | ||
routesMeta: routesMeta | ||
}); | ||
}); | ||
return branches; | ||
} | ||
function rankRouteBranches(branches) { | ||
return branches.sort(function (a, b) { | ||
return a.score !== b.score ? b.score - a.score : compareIndexes(a.routesMeta.map(function (meta) { | ||
return meta.childrenIndex; | ||
}), b.routesMeta.map(function (meta) { | ||
return meta.childrenIndex; | ||
})); | ||
}); | ||
} | ||
var NO_BREADCRUMB = Symbol('NO_BREADCRUMB'); | ||
var humanize = function humanize(str) { | ||
@@ -62,3 +136,3 @@ return str.replace(/^[\s_]+|[\s_]+$/g, '').replace(/[_\s]+/g, ' ').replace(/^[a-z]/, function (m) { | ||
location: location, | ||
key: match.url | ||
key: match.pathname | ||
}, props || {}); | ||
@@ -76,7 +150,6 @@ return Object.assign(Object.assign({}, componentProps), { | ||
pathSection = _ref2.pathSection; | ||
var match = reactRouter.matchPath(pathSection, Object.assign(Object.assign({}, DEFAULT_MATCH_OPTIONS), { | ||
var match = reactRouter.matchPath({ | ||
end: true, | ||
path: pathSection | ||
})) || { | ||
url: 'not-found' | ||
}; | ||
}, pathSection); | ||
return render({ | ||
@@ -95,11 +168,10 @@ breadcrumb: humanize(currentSection), | ||
pathSection = _ref3.pathSection, | ||
routes = _ref3.routes; | ||
branches = _ref3.branches; | ||
var breadcrumb; | ||
var getIsPathExcluded = function getIsPathExcluded(path) { | ||
return reactRouter.matchPath(pathSection, { | ||
return reactRouter.matchPath({ | ||
path: path, | ||
exact: true, | ||
strict: false | ||
}) != null; | ||
end: true | ||
}, pathSection) != null; | ||
}; | ||
@@ -111,17 +183,25 @@ | ||
routes.some(function (_a) { | ||
var userProvidedBreadcrumb = _a.breadcrumb, | ||
matchOptions = _a.matchOptions, | ||
path = _a.path, | ||
rest = __rest(_a, ["breadcrumb", "matchOptions", "path"]); | ||
branches.some(function (_ref4) { | ||
var path = _ref4.path, | ||
routesMeta = _ref4.routesMeta; | ||
var route = routesMeta[routesMeta.length - 1].route; | ||
var userProvidedBreadcrumb = route.breadcrumb; | ||
if (!path) { | ||
throw new Error('useBreadcrumbs: `path` must be provided in every route object'); | ||
if (!userProvidedBreadcrumb && route.index) { | ||
var parentMeta = routesMeta[routesMeta.length - 2]; | ||
if (parentMeta && parentMeta.route.breadcrumb) { | ||
userProvidedBreadcrumb = parentMeta.route.breadcrumb; | ||
} | ||
} | ||
var match = reactRouter.matchPath(pathSection, Object.assign(Object.assign({}, matchOptions || DEFAULT_MATCH_OPTIONS), { | ||
path: path | ||
})); | ||
var caseSensitive = route.caseSensitive, | ||
props = route.props; | ||
var match = reactRouter.matchPath({ | ||
path: path, | ||
end: true, | ||
caseSensitive: caseSensitive | ||
}, pathSection); | ||
if (match && userProvidedBreadcrumb === null || !match && matchOptions) { | ||
if (match && userProvidedBreadcrumb === null) { | ||
breadcrumb = NO_BREADCRUMB; | ||
@@ -137,7 +217,8 @@ return true; | ||
breadcrumb = render(Object.assign({ | ||
breadcrumb = render({ | ||
breadcrumb: userProvidedBreadcrumb || humanize(currentSection), | ||
match: match, | ||
location: location | ||
}, rest)); | ||
location: location, | ||
props: props | ||
}); | ||
return true; | ||
@@ -164,9 +245,10 @@ } | ||
var getBreadcrumbs = function getBreadcrumbs(_ref4) { | ||
var routes = _ref4.routes, | ||
location = _ref4.location, | ||
_ref4$options = _ref4.options, | ||
options = _ref4$options === void 0 ? {} : _ref4$options; | ||
var matches = []; | ||
var getBreadcrumbs = function getBreadcrumbs(_ref5) { | ||
var routes = _ref5.routes, | ||
location = _ref5.location, | ||
_ref5$options = _ref5.options, | ||
options = _ref5$options === void 0 ? {} : _ref5$options; | ||
var pathname = location.pathname; | ||
var branches = rankRouteBranches(flattenRoutes(routes)); | ||
var breadcrumbs = []; | ||
pathname.split('?')[0].split('/').reduce(function (previousSection, currentSection, index) { | ||
@@ -183,7 +265,7 @@ var pathSection = !currentSection ? '/' : "".concat(previousSection, "/").concat(currentSection); | ||
pathSection: pathSection, | ||
routes: routes | ||
branches: branches | ||
}, options)); | ||
if (breadcrumb !== NO_BREADCRUMB) { | ||
matches.push(breadcrumb); | ||
breadcrumbs.push(breadcrumb); | ||
} | ||
@@ -193,18 +275,8 @@ | ||
}, ''); | ||
return matches; | ||
return breadcrumbs; | ||
}; | ||
var flattenRoutes = function flattenRoutes(routes) { | ||
return routes.reduce(function (arr, route) { | ||
if (route.routes) { | ||
return arr.concat([route].concat(_toConsumableArray__default['default'](flattenRoutes(route.routes)))); | ||
} | ||
return arr.concat(route); | ||
}, []); | ||
}; | ||
var useReactRouterBreadcrumbs = function useReactRouterBreadcrumbs(routes, options) { | ||
return getBreadcrumbs({ | ||
routes: flattenRoutes(routes || []), | ||
routes: routes || [], | ||
location: reactRouter.useLocation(), | ||
@@ -211,0 +283,0 @@ options: options |
@@ -1,37 +0,112 @@ | ||
import _toConsumableArray from '@babel/runtime/helpers/toConsumableArray'; | ||
import React, { createElement } from 'react'; | ||
import { useLocation, matchPath } from 'react-router'; | ||
/*! ***************************************************************************** | ||
Copyright (c) Microsoft Corporation. | ||
var joinPaths = function joinPaths(paths) { | ||
return paths.join('/').replace(/\/\/+/g, '/'); | ||
}; | ||
Permission to use, copy, modify, and/or distribute this software for any | ||
purpose with or without fee is hereby granted. | ||
var paramRe = /^:\w+$/; | ||
var dynamicSegmentValue = 3; | ||
var indexRouteValue = 2; | ||
var emptySegmentValue = 1; | ||
var staticSegmentValue = 10; | ||
var splatPenalty = -2; | ||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH | ||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY | ||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, | ||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM | ||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR | ||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR | ||
PERFORMANCE OF THIS SOFTWARE. | ||
***************************************************************************** */ | ||
var isSplat = function isSplat(s) { | ||
return s === '*'; | ||
}; | ||
function __rest(s, e) { | ||
var t = {}; | ||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) | ||
t[p] = s[p]; | ||
if (s != null && typeof Object.getOwnPropertySymbols === "function") | ||
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { | ||
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) | ||
t[p[i]] = s[p[i]]; | ||
} | ||
return t; | ||
function computeScore(path, index) { | ||
var segments = path.split('/'); | ||
var initialScore = segments.length; | ||
if (segments.some(isSplat)) { | ||
initialScore += splatPenalty; | ||
} | ||
if (index) { | ||
initialScore += indexRouteValue; | ||
} | ||
return segments.filter(function (s) { | ||
return !isSplat(s); | ||
}).reduce(function (score, segment) { | ||
if (paramRe.test(segment)) { | ||
return score + dynamicSegmentValue; | ||
} | ||
if (segment === '') { | ||
return score + emptySegmentValue; | ||
} | ||
return score + staticSegmentValue; | ||
}, initialScore); | ||
} | ||
var DEFAULT_MATCH_OPTIONS = { | ||
exact: true | ||
}; | ||
var NO_BREADCRUMB = 'NO_BREADCRUMB'; | ||
function compareIndexes(a, b) { | ||
var siblings = a.length === b.length && a.slice(0, -1).every(function (n, i) { | ||
return n === b[i]; | ||
}); | ||
return siblings ? a[a.length - 1] - b[b.length - 1] : 0; | ||
} | ||
function flattenRoutes(routes) { | ||
var branches = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; | ||
var parentsMeta = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : []; | ||
var parentPath = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : ''; | ||
routes.forEach(function (route, index) { | ||
if (typeof route.path !== 'string' && !route.index) { | ||
throw new Error('useBreadcrumbs: `path` or `index` must be provided in every route object'); | ||
} | ||
if (route.path && route.index) { | ||
throw new Error('useBreadcrumbs: `path` and `index` cannot be provided at the same time'); | ||
} | ||
var meta = { | ||
relativePath: route.path || '', | ||
childrenIndex: index, | ||
route: route | ||
}; | ||
if (meta.relativePath.charAt(0) === '/') { | ||
if (!meta.relativePath.startsWith(parentPath)) { | ||
throw new Error('useBreadcrumbs: The absolute path of the child route must start with the parent path'); | ||
} | ||
meta.relativePath = meta.relativePath.slice(parentPath.length); | ||
} | ||
var path = joinPaths([parentPath, meta.relativePath]); | ||
var routesMeta = parentsMeta.concat(meta); | ||
if (route.children && route.children.length > 0) { | ||
if (route.index) { | ||
throw new Error('useBreadcrumbs: Index route cannot have child routes'); | ||
} | ||
flattenRoutes(route.children, branches, routesMeta, path); | ||
} | ||
branches.push({ | ||
path: path, | ||
score: computeScore(path, route.index), | ||
routesMeta: routesMeta | ||
}); | ||
}); | ||
return branches; | ||
} | ||
function rankRouteBranches(branches) { | ||
return branches.sort(function (a, b) { | ||
return a.score !== b.score ? b.score - a.score : compareIndexes(a.routesMeta.map(function (meta) { | ||
return meta.childrenIndex; | ||
}), b.routesMeta.map(function (meta) { | ||
return meta.childrenIndex; | ||
})); | ||
}); | ||
} | ||
var NO_BREADCRUMB = Symbol('NO_BREADCRUMB'); | ||
var humanize = function humanize(str) { | ||
@@ -51,3 +126,3 @@ return str.replace(/^[\s_]+|[\s_]+$/g, '').replace(/[_\s]+/g, ' ').replace(/^[a-z]/, function (m) { | ||
location: location, | ||
key: match.url | ||
key: match.pathname | ||
}, props || {}); | ||
@@ -65,7 +140,6 @@ return Object.assign(Object.assign({}, componentProps), { | ||
pathSection = _ref2.pathSection; | ||
var match = matchPath(pathSection, Object.assign(Object.assign({}, DEFAULT_MATCH_OPTIONS), { | ||
var match = matchPath({ | ||
end: true, | ||
path: pathSection | ||
})) || { | ||
url: 'not-found' | ||
}; | ||
}, pathSection); | ||
return render({ | ||
@@ -84,11 +158,10 @@ breadcrumb: humanize(currentSection), | ||
pathSection = _ref3.pathSection, | ||
routes = _ref3.routes; | ||
branches = _ref3.branches; | ||
var breadcrumb; | ||
var getIsPathExcluded = function getIsPathExcluded(path) { | ||
return matchPath(pathSection, { | ||
return matchPath({ | ||
path: path, | ||
exact: true, | ||
strict: false | ||
}) != null; | ||
end: true | ||
}, pathSection) != null; | ||
}; | ||
@@ -100,17 +173,25 @@ | ||
routes.some(function (_a) { | ||
var userProvidedBreadcrumb = _a.breadcrumb, | ||
matchOptions = _a.matchOptions, | ||
path = _a.path, | ||
rest = __rest(_a, ["breadcrumb", "matchOptions", "path"]); | ||
branches.some(function (_ref4) { | ||
var path = _ref4.path, | ||
routesMeta = _ref4.routesMeta; | ||
var route = routesMeta[routesMeta.length - 1].route; | ||
var userProvidedBreadcrumb = route.breadcrumb; | ||
if (!path) { | ||
throw new Error('useBreadcrumbs: `path` must be provided in every route object'); | ||
if (!userProvidedBreadcrumb && route.index) { | ||
var parentMeta = routesMeta[routesMeta.length - 2]; | ||
if (parentMeta && parentMeta.route.breadcrumb) { | ||
userProvidedBreadcrumb = parentMeta.route.breadcrumb; | ||
} | ||
} | ||
var match = matchPath(pathSection, Object.assign(Object.assign({}, matchOptions || DEFAULT_MATCH_OPTIONS), { | ||
path: path | ||
})); | ||
var caseSensitive = route.caseSensitive, | ||
props = route.props; | ||
var match = matchPath({ | ||
path: path, | ||
end: true, | ||
caseSensitive: caseSensitive | ||
}, pathSection); | ||
if (match && userProvidedBreadcrumb === null || !match && matchOptions) { | ||
if (match && userProvidedBreadcrumb === null) { | ||
breadcrumb = NO_BREADCRUMB; | ||
@@ -126,7 +207,8 @@ return true; | ||
breadcrumb = render(Object.assign({ | ||
breadcrumb = render({ | ||
breadcrumb: userProvidedBreadcrumb || humanize(currentSection), | ||
match: match, | ||
location: location | ||
}, rest)); | ||
location: location, | ||
props: props | ||
}); | ||
return true; | ||
@@ -153,9 +235,10 @@ } | ||
var getBreadcrumbs = function getBreadcrumbs(_ref4) { | ||
var routes = _ref4.routes, | ||
location = _ref4.location, | ||
_ref4$options = _ref4.options, | ||
options = _ref4$options === void 0 ? {} : _ref4$options; | ||
var matches = []; | ||
var getBreadcrumbs = function getBreadcrumbs(_ref5) { | ||
var routes = _ref5.routes, | ||
location = _ref5.location, | ||
_ref5$options = _ref5.options, | ||
options = _ref5$options === void 0 ? {} : _ref5$options; | ||
var pathname = location.pathname; | ||
var branches = rankRouteBranches(flattenRoutes(routes)); | ||
var breadcrumbs = []; | ||
pathname.split('?')[0].split('/').reduce(function (previousSection, currentSection, index) { | ||
@@ -172,7 +255,7 @@ var pathSection = !currentSection ? '/' : "".concat(previousSection, "/").concat(currentSection); | ||
pathSection: pathSection, | ||
routes: routes | ||
branches: branches | ||
}, options)); | ||
if (breadcrumb !== NO_BREADCRUMB) { | ||
matches.push(breadcrumb); | ||
breadcrumbs.push(breadcrumb); | ||
} | ||
@@ -182,18 +265,8 @@ | ||
}, ''); | ||
return matches; | ||
return breadcrumbs; | ||
}; | ||
var flattenRoutes = function flattenRoutes(routes) { | ||
return routes.reduce(function (arr, route) { | ||
if (route.routes) { | ||
return arr.concat([route].concat(_toConsumableArray(flattenRoutes(route.routes)))); | ||
} | ||
return arr.concat(route); | ||
}, []); | ||
}; | ||
var useReactRouterBreadcrumbs = function useReactRouterBreadcrumbs(routes, options) { | ||
return getBreadcrumbs({ | ||
routes: flattenRoutes(routes || []), | ||
routes: routes || [], | ||
location: useLocation(), | ||
@@ -200,0 +273,0 @@ options: options |
import React from 'react'; | ||
import { useLocation } from 'react-router'; | ||
import { useLocation, RouteObject, Params, PathPattern } from 'react-router'; | ||
declare type Location = ReturnType<typeof useLocation>; | ||
@@ -8,12 +8,17 @@ export interface Options { | ||
} | ||
export interface MatchOptions { | ||
exact?: boolean; | ||
strict?: boolean; | ||
sensitive?: boolean; | ||
export interface BreadcrumbMatch<ParamKey extends string = string> { | ||
params: Params<ParamKey>; | ||
pathname: string; | ||
pattern: PathPattern; | ||
route?: BreadcrumbsRoute; | ||
} | ||
export interface BreadcrumbsRoute { | ||
path: string; | ||
breadcrumb?: React.ComponentType | React.ElementType | string | null; | ||
matchOptions?: MatchOptions; | ||
routes?: BreadcrumbsRoute[]; | ||
export interface BreadcrumbComponentProps<ParamKey extends string = string> { | ||
key: string; | ||
match: BreadcrumbMatch<ParamKey>; | ||
location: Location; | ||
} | ||
export declare type BreadcrumbComponentType<ParamKey extends string = string> = React.ComponentType<BreadcrumbComponentProps<ParamKey>>; | ||
export interface BreadcrumbsRoute<ParamKey extends string = string> extends RouteObject { | ||
children?: BreadcrumbsRoute[]; | ||
breadcrumb?: BreadcrumbComponentType<ParamKey> | string | null; | ||
props?: { | ||
@@ -23,6 +28,4 @@ [x: string]: unknown; | ||
} | ||
export interface BreadcrumbData { | ||
match: { | ||
url: string; | ||
}; | ||
export interface BreadcrumbData<ParamKey extends string = string> { | ||
match: BreadcrumbMatch<ParamKey>; | ||
location: Location; | ||
@@ -37,3 +40,3 @@ key: string; | ||
}) => BreadcrumbData[]; | ||
declare const useReactRouterBreadcrumbs: (routes?: BreadcrumbsRoute[] | undefined, options?: Options | undefined) => BreadcrumbData[]; | ||
declare const useReactRouterBreadcrumbs: (routes?: BreadcrumbsRoute<string>[] | undefined, options?: Options | undefined) => BreadcrumbData[]; | ||
export default useReactRouterBreadcrumbs; |
@@ -1,1 +0,1 @@ | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@babel/runtime/helpers/toConsumableArray"),require("react"),require("react-router")):"function"==typeof define&&define.amd?define(["exports","@babel/runtime/helpers/toConsumableArray","react","react-router"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self)["react-router-breadcrumbs-hoc"]={},e._toConsumableArray,e.React,e.ReactRouter)}(this,(function(e,t,r,n){"use strict";function o(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var a=o(t),c=o(r);var u={exact:!0},i="NO_BREADCRUMB",s=function(e){return e.replace(/^[\s_]+|[\s_]+$/g,"").replace(/[_\s]+/g," ").replace(/^[a-z]/,(function(e){return e.toUpperCase()}))},l=function(e){var t=e.breadcrumb,n=e.match,o=e.location,a=e.props,u=Object.assign({match:n,location:o,key:n.url},a||{});return Object.assign(Object.assign({},u),{breadcrumb:"string"==typeof t?r.createElement("span",{key:u.key},t):c.default.createElement(t,Object.assign({},u))})},p=function(e){var t,r=e.currentSection,o=e.disableDefaults,a=e.excludePaths,c=e.location,p=e.pathSection,b=e.routes;return a&&a.some((function(e){return null!=n.matchPath(p,{path:e,exact:!0,strict:!1})}))?i:(b.some((function(e){var a=e.breadcrumb,b=e.matchOptions,f=e.path,d=function(e,t){var r={};for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&t.indexOf(n)<0&&(r[n]=e[n]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols){var o=0;for(n=Object.getOwnPropertySymbols(e);o<n.length;o++)t.indexOf(n[o])<0&&Object.prototype.propertyIsEnumerable.call(e,n[o])&&(r[n[o]]=e[n[o]])}return r}(e,["breadcrumb","matchOptions","path"]);if(!f)throw new Error("useBreadcrumbs: `path` must be provided in every route object");var m=n.matchPath(p,Object.assign(Object.assign({},b||u),{path:f}));return m&&null===a||!m&&b?(t=i,!0):!!m&&(!a&&o?(t=i,!0):(t=l(Object.assign({breadcrumb:a||s(r),match:m,location:c},d)),!0))})),t||(o?i:function(e){var t=e.currentSection,r=e.location,o=e.pathSection,a=n.matchPath(o,Object.assign(Object.assign({},u),{path:o}))||{url:"not-found"};return l({breadcrumb:s(t),match:a,location:r})}({pathSection:p,currentSection:"/"===p?"Home":r,location:c})))},b=function(e){var t=e.routes,r=e.location,n=e.options,o=void 0===n?{}:n,a=[];return r.pathname.split("?")[0].split("/").reduce((function(e,n,c){var u=n?"".concat(e,"/").concat(n):"/";if("/"===u&&0!==c)return"";var s=p(Object.assign({currentSection:n,location:r,pathSection:u,routes:t},o));return s!==i&&a.push(s),"/"===u?"":u}),""),a},f=function e(t){return t.reduce((function(t,r){return r.routes?t.concat([r].concat(a.default(e(r.routes)))):t.concat(r)}),[])};e.default=function(e,t){return b({routes:f(e||[]),location:n.useLocation(),options:t})},e.getBreadcrumbs=b,Object.defineProperty(e,"__esModule",{value:!0})})); | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("react"),require("react-router")):"function"==typeof define&&define.amd?define(["exports","react","react-router"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self)["react-router-breadcrumbs-hoc"]={},e.React,e.ReactRouter)}(this,(function(e,t,r){"use strict";function n(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var a=n(t),o=function(e){return e.join("/").replace(/\/\/+/g,"/")},c=/^:\w+$/,i=function(e){return"*"===e};function u(e,t){var r=e.split("/"),n=r.length;return r.some(i)&&(n+=-2),t&&(n+=2),r.filter((function(e){return!i(e)})).reduce((function(e,t){return c.test(t)?e+3:""===t?e+1:e+10}),n)}function s(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[],r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],n=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"";return e.forEach((function(e,a){if("string"!=typeof e.path&&!e.index)throw new Error("useBreadcrumbs: `path` or `index` must be provided in every route object");if(e.path&&e.index)throw new Error("useBreadcrumbs: `path` and `index` cannot be provided at the same time");var c={relativePath:e.path||"",childrenIndex:a,route:e};if("/"===c.relativePath.charAt(0)){if(!c.relativePath.startsWith(n))throw new Error("useBreadcrumbs: The absolute path of the child route must start with the parent path");c.relativePath=c.relativePath.slice(n.length)}var i=o([n,c.relativePath]),h=r.concat(c);if(e.children&&e.children.length>0){if(e.index)throw new Error("useBreadcrumbs: Index route cannot have child routes");s(e.children,t,h,i)}t.push({path:i,score:u(i,e.index),routesMeta:h})})),t}var h=Symbol("NO_BREADCRUMB"),l=function(e){return e.replace(/^[\s_]+|[\s_]+$/g,"").replace(/[_\s]+/g," ").replace(/^[a-z]/,(function(e){return e.toUpperCase()}))},d=function(e){var r=e.breadcrumb,n=e.match,o=e.location,c=e.props,i=Object.assign({match:n,location:o,key:n.pathname},c||{});return Object.assign(Object.assign({},i),{breadcrumb:"string"==typeof r?t.createElement("span",{key:i.key},r):a.default.createElement(r,Object.assign({},i))})},f=function(e){var t,n=e.currentSection,a=e.disableDefaults,o=e.excludePaths,c=e.location,i=e.pathSection,u=e.branches;return o&&o.some((function(e){return null!=r.matchPath({path:e,end:!0},i)}))?h:(u.some((function(e){var o=e.path,u=e.routesMeta,s=u[u.length-1].route,f=s.breadcrumb;if(!f&&s.index){var p=u[u.length-2];p&&p.route.breadcrumb&&(f=p.route.breadcrumb)}var b=s.caseSensitive,m=s.props,v=r.matchPath({path:o,end:!0,caseSensitive:b},i);return v&&null===f?(t=h,!0):!!v&&(!f&&a?(t=h,!0):(t=d({breadcrumb:f||l(n),match:v,location:c,props:m}),!0))})),t||(a?h:function(e){var t=e.currentSection,n=e.location,a=e.pathSection,o=r.matchPath({end:!0,path:a},a);return d({breadcrumb:l(t),match:o,location:n})}({pathSection:i,currentSection:"/"===i?"Home":n,location:c})))},p=function(e){var t=e.routes,r=e.location,n=e.options,a=void 0===n?{}:n,o=r.pathname,c=function(e){return e.sort((function(e,t){return e.score!==t.score?t.score-e.score:function(e,t){return e.length===t.length&&e.slice(0,-1).every((function(e,r){return e===t[r]}))?e[e.length-1]-t[t.length-1]:0}(e.routesMeta.map((function(e){return e.childrenIndex})),t.routesMeta.map((function(e){return e.childrenIndex})))}))}(s(t)),i=[];return o.split("?")[0].split("/").reduce((function(e,t,n){var o=t?"".concat(e,"/").concat(t):"/";if("/"===o&&0!==n)return"";var u=f(Object.assign({currentSection:t,location:r,pathSection:o,branches:c},a));return u!==h&&i.push(u),"/"===o?"":o}),""),i};e.default=function(e,t){return p({routes:e||[],location:r.useLocation(),options:t})},e.getBreadcrumbs=p,Object.defineProperty(e,"__esModule",{value:!0})})); |
{ | ||
"name": "use-react-router-breadcrumbs", | ||
"version": "2.0.2", | ||
"version": "3.0.0", | ||
"description": "A hook for displaying and setting breadcrumbs for react router", | ||
@@ -14,3 +14,3 @@ "main": "dist/cjs/index.js", | ||
"react": ">=16.8", | ||
"react-router": ">=5.1.0" | ||
"react-router": ">=6.0.0" | ||
}, | ||
@@ -40,3 +40,2 @@ "scripts": { | ||
"@types/react-dom": "^17.0.7", | ||
"@types/react-router": "^5.1.15", | ||
"@typescript-eslint/eslint-plugin": "^4.26.1", | ||
@@ -60,3 +59,3 @@ "@typescript-eslint/parser": "^4.26.1", | ||
"react-dom": "^17.0.2", | ||
"react-router": "^5.2.0", | ||
"react-router": "^6.0.0", | ||
"rollup": "^2.51.1", | ||
@@ -72,4 +71,4 @@ "rollup-plugin-size": "^0.2.2", | ||
"react-router", | ||
"react-router 5" | ||
"react-router 6" | ||
] | ||
} |
109
README.md
@@ -54,3 +54,3 @@ <h3 align="center"> | ||
- Render, map, and wrap breadcrumbs any way you want. | ||
- Compatible with existing [route configs](https://reacttraining.com/react-router/web/example/route-config). | ||
- Compatible with existing [route objects](https://reactrouter.com/docs/en/v6/examples/route-objects). | ||
@@ -118,4 +118,4 @@ ## Install | ||
}) => ( | ||
<span key={match.url}> | ||
<NavLink to={match.url}>{breadcrumb}</NavLink> | ||
<span key={match.pathname}> | ||
<NavLink to={match.pathname}>{breadcrumb}</NavLink> | ||
</span> | ||
@@ -136,5 +136,5 @@ ))} | ||
## [Route config](https://reacttraining.com/react-router/web/example/route-config) compatibility | ||
## [Route object](https://reactrouter.com/docs/en/v6/examples/route-objects) compatibility | ||
Add breadcrumbs to your existing [route config](https://reacttraining.com/react-router/web/example/route-config). This is a great way to keep all routing config paths in a single place! If a path ever changes, you'll only have to change it in your main route config rather than maintaining a _separate_ config for `use-react-router-breadcrumbs`. | ||
Add breadcrumbs to your existing [route object](https://reactrouter.com/docs/en/v6/examples/route-objects). This is a great way to keep all routing config paths in a single place! If a path ever changes, you'll only have to change it in your main route config rather than maintaining a _separate_ config for `use-react-router-breadcrumbs`. | ||
@@ -144,6 +144,6 @@ For example... | ||
```js | ||
const routeConfig = [ | ||
const routes = [ | ||
{ | ||
path: "/sandwiches", | ||
component: Sandwiches | ||
element: <Sandwiches /> | ||
} | ||
@@ -156,6 +156,6 @@ ]; | ||
```js | ||
const routeConfig = [ | ||
const routes = [ | ||
{ | ||
path: "/sandwiches", | ||
component: Sandwiches, | ||
element: <Sandwiches />, | ||
breadcrumb: 'I love sandwiches' | ||
@@ -166,6 +166,6 @@ } | ||
then you can just pass the whole route config right into the hook: | ||
then you can just pass the whole routes right into the hook: | ||
```js | ||
const breadcrumbs = useBreadcrumbs(routeConfig); | ||
const breadcrumbs = useBreadcrumbs(routes); | ||
``` | ||
@@ -175,5 +175,5 @@ | ||
If you pass a component as the `breadcrumb` prop it will be injected with react-router's [match](https://reacttraining.com/react-router/web/api/match) and [location](https://reacttraining.com/react-router/web/api/location) objects as props. These objects contain ids, hashes, queries, etc... from the route that will allow you to map back to whatever you want to display in the breadcrumb. | ||
If you pass a component as the `breadcrumb` prop it will be injected with react-router's [match](https://reactrouter.com/docs/en/v6/api#matchpath) and [location](https://reactrouter.com/docs/en/v6/api#location) objects as props. These objects contain ids, hashes, queries, etc... from the route that will allow you to map back to whatever you want to display in the breadcrumb. | ||
Let's use `redux` as an example with the [match](https://reacttraining.com/react-router/web/api/match) object: | ||
Let's use `redux` as an example with the [match](https://reactrouter.com/docs/en/v6/api#matchpath) object: | ||
@@ -204,5 +204,15 @@ ```js | ||
You cannot use hooks that rely on `RouteContext` like `useParams`, because the breadcrumbs are not in the context, you should use `match.params` instead: | ||
```tsx | ||
import type { BreadcrumbComponentType } from 'use-react-router-breadcrumbs'; | ||
const UserBreadcrumb: BreadcrumbComponentType<'id'> = ({ match }) => { | ||
return <div>{match.params.id}</div>; | ||
} | ||
``` | ||
---- | ||
Similarly, the [location](https://reacttraining.com/react-router/web/api/location) object could be useful for displaying dynamic breadcrumbs based on the route's state: | ||
Similarly, the [location](https://reactrouter.com/docs/en/v6/api#location) object could be useful for displaying dynamic breadcrumbs based on the route's state: | ||
@@ -261,2 +271,4 @@ ```jsx | ||
`use-react-router-breadcrumbs` uses the same strategy as `React Router v6` to calculate the routing order. | ||
... in certain cases. Consider the following: | ||
@@ -266,47 +278,68 @@ | ||
[ | ||
{ path: '/users/:id', breadcrumb: 'id-breadcrumb' }, | ||
{ path: '/users/create', breadcrumb: 'create-breadcrumb' }, | ||
{ | ||
path: 'users', | ||
children: [ | ||
{ path: ':id', breadcrumb: 'id-breadcrumb' }, | ||
{ path: 'create', breadcrumb: 'create-breadcrumb' }, | ||
], | ||
}, | ||
] | ||
``` | ||
If the user visits `example.com/users/create` they will see `id-breadcrumb` because `/users/:id` will match _before_ `/users/create`. | ||
If the user visits `example.com/users/create` they will see `create-breadcrumb`. | ||
To fix the issue above, just adjust the order of your routes: | ||
In addition, if the index route and the parent route provide breadcrumb at the same time, the index route provided will be used first: | ||
```js | ||
[ | ||
{ path: '/users/create', breadcrumb: 'create-breadcrumb' }, | ||
{ path: '/users/:id', breadcrumb: 'id-breadcrumb' }, | ||
{ | ||
path: 'users', | ||
breadcrumb: 'parent-breadcrumb', | ||
children: [ | ||
{ index: true, breadcrumb: 'child-breadcrumb' }, | ||
], | ||
}, | ||
] | ||
``` | ||
Now, `example.com/users/create` will display `create-breadcrumb` as expected, because it will match first before the `/users/:id` route. | ||
If the user visits `example.com/users` they will see `child-breadcrumb`. | ||
## API | ||
```js | ||
BreadcrumbsRoute = { | ||
path: String | ||
breadcrumb?: React.ComponentType | React.ElementType | string | null | ||
// see: https://reacttraining.com/react-router/web/api/matchPath | ||
matchOptions?: { | ||
exact?: boolean | ||
strict?: boolean | ||
sensitive?: boolean | ||
} | ||
// optional nested routes (for react-router config compatibility) | ||
routes?: BreadcrumbsRoute[], | ||
// optional props to be passed through directly to the breadcrumb component | ||
```ts | ||
interface BreadcrumbComponentProps<ParamKey extends string = string> { | ||
key: string; | ||
match: BreadcrumbMatch<ParamKey>; | ||
location: Location; | ||
} | ||
type BreadcrumbComponentType<ParamKey extends string = string> = | ||
React.ComponentType<BreadcrumbComponentProps<ParamKey>>; | ||
interface BreadcrumbsRoute<ParamKey extends string = string> | ||
extends RouteObject { | ||
children?: BreadcrumbsRoute[]; | ||
breadcrumb?: BreadcrumbComponentType<ParamKey> | string | null; | ||
props?: { [x: string]: unknown }; | ||
} | ||
Options = { | ||
interface Options { | ||
// disable all default generation of breadcrumbs | ||
disableDefaults?: boolean | ||
disableDefaults?: boolean; | ||
// exclude certain paths fom generating breadcrumbs | ||
excludePaths?: string[] | ||
excludePaths?: string[]; | ||
} | ||
interface BreadcrumbData<ParamKey extends string = string> { | ||
match: BreadcrumbMatch<ParamKey>; | ||
location: Location; | ||
key: string; | ||
breadcrumb: React.ReactNode; | ||
} | ||
// if routes are not passed, default breadcrumbs will be returned | ||
useBreadcrumbs(routes?: BreadcrumbsRoute[], options?: Options): Array<React.node> | ||
function useBreadcrumbs( | ||
routes?: BreadcrumbsRoute[], | ||
options?: Options | ||
): BreadcrumbData[]; | ||
``` |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
33568
35
508
336
1