peppermint-router
Advanced tools
Comparing version 0.0.2 to 0.1.0
@@ -5,2 +5,8 @@ # Change Log | ||
## [0.1.0 - 2018-07-26](https://github.com/alonrbar/peppermint-router/tree/v0.1.0) | ||
### Added | ||
- PromptNavigation component. | ||
## [0.0.2 - 2018-07-26](https://github.com/alonrbar/peppermint-router/tree/v0.0.2) | ||
@@ -7,0 +13,0 @@ |
@@ -79,2 +79,6 @@ import * as React from 'react'; | ||
// | ||
// RouteFallback | ||
// | ||
export interface RouteFallbackProps { | ||
@@ -84,2 +88,22 @@ component: React.ComponentType<any>; | ||
export class RouteFallback extends React.Component<RouteFallbackProps> { } | ||
export class RouteFallback extends React.Component<RouteFallbackProps> { } | ||
// | ||
// PromptNavigation | ||
// | ||
export interface PromptNavigationRenderProps { | ||
isNavigating: boolean; | ||
confirm: VoidFunction; | ||
cancel: VoidFunction; | ||
} | ||
export type PromptNavigationRender = (props: PromptNavigationRenderProps) => React.ReactNode; | ||
export interface PromptNavigationProps { | ||
enabled?: boolean; | ||
exitPrompt?: string; | ||
children?: PromptNavigationRender; | ||
} | ||
export const PromptNavigation: React.FunctionComponent<PromptNavigationProps>; |
@@ -7,5 +7,72 @@ 'use strict'; | ||
var React = require('react'); | ||
var _defineProperty = _interopDefault(require('@babel/runtime/helpers/defineProperty')); | ||
var React = require('react'); | ||
function removeStart(str, ...toRemove) { | ||
return removeSide(str, /^(\s*[\r\n]*)*/, String.prototype.startsWith, (s, tr) => s.substring(tr.length), ...toRemove); | ||
} | ||
function removeEnd(str, ...toRemove) { | ||
return removeSide(str, /(\s*[\r\n]*)*$/, String.prototype.endsWith, (s, tr) => s.substring(0, s.length - tr.length), ...toRemove); | ||
} | ||
function removeSide(str, whitespaceReplacePattern, shouldRemove, remove, ...toRemove) { | ||
// input validation | ||
if (typeof str !== "string") { | ||
throw new Error(`Missing arguement '${"str"}'.`); | ||
} | ||
if (!toRemove.every(tr => typeof tr === 'string')) { | ||
throw new Error(`Invalid argument '${toRemove}'. Only strings expected.`); | ||
} // default behavior: trim white spaces | ||
if (!toRemove.length) { | ||
return str.replace(whitespaceReplacePattern, ""); | ||
} // trim specified patterns | ||
let result = str.substring(0); | ||
let keepRunning = true; | ||
while (result.length && keepRunning) { | ||
keepRunning = false; | ||
for (const trimStr of toRemove) { | ||
if (!shouldRemove.call(result, trimStr)) continue; | ||
result = remove(result, trimStr); | ||
keepRunning = true; | ||
} | ||
} | ||
return result; | ||
} | ||
class WaitHandle { | ||
constructor() { | ||
_defineProperty(this, "_resolve", void 0); | ||
_defineProperty(this, "_reject", void 0); | ||
_defineProperty(this, "promise", void 0); | ||
this.promise = new Promise((resolve, reject) => { | ||
this._resolve = resolve; | ||
this._reject = reject; | ||
}); | ||
} | ||
wait() { | ||
return this.promise; | ||
} | ||
resolve(result) { | ||
this._resolve(result); | ||
} | ||
reject(err) { | ||
this._reject(err); | ||
} | ||
} | ||
const RouterContext = | ||
@@ -15,2 +82,59 @@ /*#__PURE__*/ | ||
function PromptNavigation(props) { | ||
const context = React.useContext(RouterContext); | ||
const [state, setState] = React.useState({ | ||
isNavigating: false | ||
}); | ||
React.useEffect(() => { | ||
// in-app navigation handler | ||
if (props.enabled !== false && props.children) { | ||
context.router.onBeforeNavigation = async () => { | ||
const confirmNavigation = new WaitHandle(); | ||
setState({ | ||
isNavigating: true, | ||
confirmNavigation | ||
}); | ||
let confirmed = true; | ||
await confirmNavigation.wait().catch(() => confirmed = false); | ||
return confirmed; | ||
}; | ||
} // out-of-app navigation handler | ||
if (props.enabled !== false && props.exitPrompt) { | ||
context.router.onBeforeUnload = () => props.exitPrompt; | ||
} // dispose | ||
return () => { | ||
context.router.onBeforeNavigation = null; | ||
context.router.onBeforeUnload = null; | ||
}; | ||
}); | ||
const clearState = () => setState({ | ||
isNavigating: false, | ||
confirmNavigation: null | ||
}); // render | ||
if (props.enabled !== false && props.children) { | ||
return props.children({ | ||
isNavigating: state.isNavigating, | ||
confirm: () => { | ||
const confirm = state.confirmNavigation; | ||
clearState(); | ||
confirm && confirm.resolve(); | ||
}, | ||
cancel: () => { | ||
const confirm = state.confirmNavigation; | ||
clearState(); | ||
confirm && confirm.reject(); | ||
} | ||
}); | ||
} | ||
return null; | ||
} | ||
class Route extends React.Component { | ||
@@ -70,41 +194,2 @@ constructor(...args) { | ||
function removeStart(str, ...toRemove) { | ||
return removeSide(str, /^(\s*[\r\n]*)*/, String.prototype.startsWith, (s, tr) => s.substring(tr.length), ...toRemove); | ||
} | ||
function removeEnd(str, ...toRemove) { | ||
return removeSide(str, /(\s*[\r\n]*)*$/, String.prototype.endsWith, (s, tr) => s.substring(0, s.length - tr.length), ...toRemove); | ||
} | ||
function removeSide(str, whitespaceReplacePattern, shouldRemove, remove, ...toRemove) { | ||
// input validation | ||
if (typeof str !== "string") { | ||
throw new Error(`Missing arguement '${"str"}'.`); | ||
} | ||
if (!toRemove.every(tr => typeof tr === 'string')) { | ||
throw new Error(`Invalid argument '${toRemove}'. Only strings expected.`); | ||
} // default behavior: trim white spaces | ||
if (!toRemove.length) { | ||
return str.replace(whitespaceReplacePattern, ""); | ||
} // trim specified patterns | ||
let result = str.substring(0); | ||
let keepRunning = true; | ||
while (result.length && keepRunning) { | ||
keepRunning = false; | ||
for (const trimStr of toRemove) { | ||
if (!shouldRemove.call(result, trimStr)) continue; | ||
result = remove(result, trimStr); | ||
keepRunning = true; | ||
} | ||
} | ||
return result; | ||
} | ||
class HashRouter { | ||
@@ -143,3 +228,2 @@ constructor() { | ||
// restore location hash | ||
window.history.replaceState(null, null, this._currentRoute.path); | ||
this.goTo(this._currentRoute.path); | ||
@@ -347,2 +431,3 @@ return; | ||
exports.HashRouter = HashRouter; | ||
exports.PromptNavigation = PromptNavigation; | ||
exports.Route = Route; | ||
@@ -349,0 +434,0 @@ exports.RouteFallback = RouteFallback; |
@@ -0,4 +1,71 @@ | ||
import { createContext, useContext, useState, useEffect, Component, createElement } from 'react'; | ||
import _defineProperty from '@babel/runtime/helpers/esm/defineProperty'; | ||
import { createContext, Component, createElement } from 'react'; | ||
function removeStart(str, ...toRemove) { | ||
return removeSide(str, /^(\s*[\r\n]*)*/, String.prototype.startsWith, (s, tr) => s.substring(tr.length), ...toRemove); | ||
} | ||
function removeEnd(str, ...toRemove) { | ||
return removeSide(str, /(\s*[\r\n]*)*$/, String.prototype.endsWith, (s, tr) => s.substring(0, s.length - tr.length), ...toRemove); | ||
} | ||
function removeSide(str, whitespaceReplacePattern, shouldRemove, remove, ...toRemove) { | ||
// input validation | ||
if (typeof str !== "string") { | ||
throw new Error(`Missing arguement '${"str"}'.`); | ||
} | ||
if (!toRemove.every(tr => typeof tr === 'string')) { | ||
throw new Error(`Invalid argument '${toRemove}'. Only strings expected.`); | ||
} // default behavior: trim white spaces | ||
if (!toRemove.length) { | ||
return str.replace(whitespaceReplacePattern, ""); | ||
} // trim specified patterns | ||
let result = str.substring(0); | ||
let keepRunning = true; | ||
while (result.length && keepRunning) { | ||
keepRunning = false; | ||
for (const trimStr of toRemove) { | ||
if (!shouldRemove.call(result, trimStr)) continue; | ||
result = remove(result, trimStr); | ||
keepRunning = true; | ||
} | ||
} | ||
return result; | ||
} | ||
class WaitHandle { | ||
constructor() { | ||
_defineProperty(this, "_resolve", void 0); | ||
_defineProperty(this, "_reject", void 0); | ||
_defineProperty(this, "promise", void 0); | ||
this.promise = new Promise((resolve, reject) => { | ||
this._resolve = resolve; | ||
this._reject = reject; | ||
}); | ||
} | ||
wait() { | ||
return this.promise; | ||
} | ||
resolve(result) { | ||
this._resolve(result); | ||
} | ||
reject(err) { | ||
this._reject(err); | ||
} | ||
} | ||
const RouterContext = | ||
@@ -8,2 +75,59 @@ /*#__PURE__*/ | ||
function PromptNavigation(props) { | ||
const context = useContext(RouterContext); | ||
const [state, setState] = useState({ | ||
isNavigating: false | ||
}); | ||
useEffect(() => { | ||
// in-app navigation handler | ||
if (props.enabled !== false && props.children) { | ||
context.router.onBeforeNavigation = async () => { | ||
const confirmNavigation = new WaitHandle(); | ||
setState({ | ||
isNavigating: true, | ||
confirmNavigation | ||
}); | ||
let confirmed = true; | ||
await confirmNavigation.wait().catch(() => confirmed = false); | ||
return confirmed; | ||
}; | ||
} // out-of-app navigation handler | ||
if (props.enabled !== false && props.exitPrompt) { | ||
context.router.onBeforeUnload = () => props.exitPrompt; | ||
} // dispose | ||
return () => { | ||
context.router.onBeforeNavigation = null; | ||
context.router.onBeforeUnload = null; | ||
}; | ||
}); | ||
const clearState = () => setState({ | ||
isNavigating: false, | ||
confirmNavigation: null | ||
}); // render | ||
if (props.enabled !== false && props.children) { | ||
return props.children({ | ||
isNavigating: state.isNavigating, | ||
confirm: () => { | ||
const confirm = state.confirmNavigation; | ||
clearState(); | ||
confirm && confirm.resolve(); | ||
}, | ||
cancel: () => { | ||
const confirm = state.confirmNavigation; | ||
clearState(); | ||
confirm && confirm.reject(); | ||
} | ||
}); | ||
} | ||
return null; | ||
} | ||
class Route extends Component { | ||
@@ -63,41 +187,2 @@ constructor(...args) { | ||
function removeStart(str, ...toRemove) { | ||
return removeSide(str, /^(\s*[\r\n]*)*/, String.prototype.startsWith, (s, tr) => s.substring(tr.length), ...toRemove); | ||
} | ||
function removeEnd(str, ...toRemove) { | ||
return removeSide(str, /(\s*[\r\n]*)*$/, String.prototype.endsWith, (s, tr) => s.substring(0, s.length - tr.length), ...toRemove); | ||
} | ||
function removeSide(str, whitespaceReplacePattern, shouldRemove, remove, ...toRemove) { | ||
// input validation | ||
if (typeof str !== "string") { | ||
throw new Error(`Missing arguement '${"str"}'.`); | ||
} | ||
if (!toRemove.every(tr => typeof tr === 'string')) { | ||
throw new Error(`Invalid argument '${toRemove}'. Only strings expected.`); | ||
} // default behavior: trim white spaces | ||
if (!toRemove.length) { | ||
return str.replace(whitespaceReplacePattern, ""); | ||
} // trim specified patterns | ||
let result = str.substring(0); | ||
let keepRunning = true; | ||
while (result.length && keepRunning) { | ||
keepRunning = false; | ||
for (const trimStr of toRemove) { | ||
if (!shouldRemove.call(result, trimStr)) continue; | ||
result = remove(result, trimStr); | ||
keepRunning = true; | ||
} | ||
} | ||
return result; | ||
} | ||
class HashRouter { | ||
@@ -136,3 +221,2 @@ constructor() { | ||
// restore location hash | ||
window.history.replaceState(null, null, this._currentRoute.path); | ||
this.goTo(this._currentRoute.path); | ||
@@ -339,3 +423,3 @@ return; | ||
export { HashRouter, Route, RouteFallback, RouterContext, RouterView }; | ||
export { HashRouter, PromptNavigation, Route, RouteFallback, RouterContext, RouterView }; | ||
//# sourceMappingURL=peppermint-router.esm.js.map |
{ | ||
"name": "peppermint-router", | ||
"description": "Lightweight hash router for React", | ||
"version": "0.0.2", | ||
"version": "0.1.0", | ||
"author": "Alon Bar", | ||
@@ -16,7 +16,8 @@ "license": "MIT", | ||
"scripts": { | ||
"dev": "rollup -c -w", | ||
"dev": "concurrently \"yarn build -w\" \"yarn typecheck --watch\"", | ||
"typecheck": "tsc --noEmit", | ||
"lint": "eslint \"./src/**/!(*.d).ts\"", | ||
"test": "cd test && yarn start", | ||
"release": "yarn typecheck && yarn lint && rollup -c" | ||
"build": "rollup -c", | ||
"release": "yarn typecheck && yarn lint && yarn build" | ||
}, | ||
@@ -46,2 +47,3 @@ "dependencies": { | ||
"babel-plugin-ts-nameof": "0.3.0", | ||
"concurrently": "4.1.1", | ||
"eslint": "5.16.0", | ||
@@ -48,0 +50,0 @@ "eslint-plugin-react": "7.14.3", |
@@ -5,2 +5,9 @@ # peppermint-router | ||
[![package size](https://img.shields.io/bundlephobia/minzip/peppermint-router?label=minified%20gzipped)](https://bundlephobia.com/result?p=peppermint-router) | ||
[![npm version](https://img.shields.io/npm/v/peppermint-router.svg?label=version)](https://www.npmjs.com/package/peppermint-router) | ||
[![npm license](https://img.shields.io/npm/l/peppermint-router.svg)](https://www.npmjs.com/package/peppermint-router) | ||
[![dependencies](https://david-dm.org/alonrbar/peppermint-router.svg)](https://github.com/alonrbar/peppermint-router) | ||
## The gist | ||
```jsx | ||
@@ -14,1 +21,36 @@ <RouterView> | ||
``` | ||
## Prompt navigation | ||
```jsx | ||
<PromptNavigation | ||
enabled={true} | ||
exitPrompt="Leave the application?" | ||
> | ||
{({ isNavigating, confirm, cancel }) => ( | ||
isNavigating && ( | ||
<div> | ||
<span>Move to another page?</span> | ||
<button onClick={confirm}>Confirm</button> | ||
<button onClick={cancel}>Cancel</button> | ||
</div> | ||
) | ||
)} | ||
</PromptNavigation> | ||
``` | ||
## Why? | ||
- Extremely compact - less than 2kb gzipped! | ||
- Does not require special `<Link>` tags and other boilerplate. | ||
- `<PromptNavigation>` component with custom prompt out of the box. | ||
## Why not? | ||
- Only hash routes | ||
- No SSR support | ||
- No React Native support | ||
## Changelog | ||
The change log can be found [here](https://github.com/alonrbar/peppermint-router/blob/master/CHANGELOG.md). |
@@ -0,1 +1,2 @@ | ||
export * from './PromptNavigation'; | ||
export * from './Route'; | ||
@@ -2,0 +3,0 @@ export * from './RouteFallback'; |
@@ -169,3 +169,2 @@ import { IMap, removeEnd, removeStart } from '../utils'; | ||
// restore location hash | ||
window.history.replaceState(null, null, this._currentRoute.path); | ||
this.goTo(this._currentRoute.path); | ||
@@ -172,0 +171,0 @@ return; |
export * from './types'; | ||
export * from './utils'; | ||
export * from './waitHandle'; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
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
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
94990
23
1201
54
0
26