react-router-magic
Version:
Like react-router but with magic inside
169 lines (151 loc) • 4.94 kB
text/typescript
import isFunction = require('lodash.isfunction');
import { RouterStoreState } from './Store';
import { MouseEvent } from 'react';
import { LocationDescriptorObject, History, Location } from 'history';
import isString = require('lodash.isstring');
export type Match = null | undefined | boolean | object;
export type ValOrFunc<Result> = Result | ((location: Location, parentMatch: Match) => Result);
export type NavParams = {
href: string;
match: Match;
location: Location;
navigate: () => void;
handleAnchorClick: (event: MouseEvent<any>) => void;
};
export type StringOrLocationDescriptorObject = string | LocationDescriptorObject;
export type ToProps = (
StringOrLocationDescriptorObject |
((currentMatch: Match, location: Location, parentMatch: Match) => StringOrLocationDescriptorObject)
);
export function execValOrFunc<Val>(
valOrFunc: ValOrFunc<Val>,
location: Location,
parentMatch: Match,
): Val {
if (isFunction(valOrFunc)) {
return valOrFunc(location, parentMatch);
}
return valOrFunc;
}
export function checkSwitchState(
props: { switchIndex?: number | false },
parentRouterState: RouterStoreState,
match: Match,
): Match {
if (parentRouterState.switch === false && props.switchIndex !== undefined) {
console.warn(`You should not set switchIndex props yourself`);
}
if (parentRouterState.switch && props.switchIndex === undefined) {
console.warn(`Error: Switch children are supposed to have switchIndex passed from parent`);
}
// in a switch but there are no match
if (
parentRouterState.switch &&
parentRouterState.switch.matchIndex === false
) {
return false;
}
// in a switch but switchIndex does not match
if (
parentRouterState.switch &&
props.switchIndex !== undefined &&
parentRouterState.switch.matchIndex !== props.switchIndex
) {
return false;
}
// ok match !
return match;
}
export function isModifiedEvent(event: MouseEvent<any>): boolean {
return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
}
export function createNavParams(
to: ToProps,
history: History,
location: Location,
replace: boolean = false,
match?: ValOrFunc<Match>,
parentMatch?: Match,
): NavParams {
const doMatch: Match = match ? execValOrFunc(match, location, parentMatch) : null;
const toLocation: LocationDescriptorObject = execTo(to, doMatch, location, parentMatch);
const href: string = history.createHref(toLocation);
const doNavigate: () => void = () => navigate(toLocation, history, replace);
const params: NavParams = {
href: href,
navigate: doNavigate,
match: doMatch,
location: location,
handleAnchorClick: createHandleAnchorClick(doNavigate),
};
return params;
}
export function createHandleAnchorClick(navigate: () => void):
(event: MouseEvent<any>, target?: string) => void {
return (event: MouseEvent<any>, target?: string) => {
if (
!event.defaultPrevented && // onClick prevented default
event.button === 0 && // ignore right clicks
!target && // let browser handle "target=_blank" etc.
!isModifiedEvent(event) // ignore clicks with modifier keys
) {
event.preventDefault();
navigate();
}
};
}
export function navigate(to: LocationDescriptorObject, history: History, replace: boolean = false): void {
if (replace) {
history.replace(to);
} else {
history.push(to);
}
}
export function execTo(
to: ToProps,
match: Match,
location: Location,
parentMatch?: Match,
): LocationDescriptorObject {
const toExecuted: StringOrLocationDescriptorObject = isFunction(to) ? (
to(match, location, parentMatch)
) : to;
return isString(toExecuted) ? { pathname: toExecuted } : toExecuted;
}
const REACT_STATICS: { [key: string]: boolean } = {
childContextTypes: true,
contextTypes: true,
defaultProps: true,
displayName: true,
getDefaultProps: true,
mixins: true,
propTypes: true,
type: true,
};
const KNOWN_STATICS: {[key: string]: boolean} = {
name: true,
length: true,
prototype: true,
caller: true,
arguments: true,
arity: true,
};
export function hoistNonReactStatics(
targetComponent: any,
sourceComponent: any,
customStatics?: { [key: string]: boolean },
) {
if (typeof sourceComponent !== 'string') { // don't hoist over string (html) components
let keys: string[] = Object.getOwnPropertyNames(sourceComponent);
for (let i = 0; i < keys.length; ++i) {
if (!REACT_STATICS[keys[i]] && !KNOWN_STATICS[keys[i]] && (!customStatics || !customStatics[keys[i]])) {
try {
targetComponent[keys[i]] = sourceComponent[keys[i]];
} catch (error) {
console.warn(error);
}
}
}
}
return targetComponent;
}