preact-material-components
Version:
preact wrapper for "Material Components for the web"
356 lines (285 loc) • 8.68 kB
JavaScript
import { Component, cloneElement, h } from 'preact';
var EMPTY$1 = {};
function assign(obj, props) {
// eslint-disable-next-line guard-for-in
for (var i in props) {
obj[i] = props[i];
}
return obj;
}
function exec(url, route, opts) {
if ( opts === void 0 ) opts=EMPTY$1;
var reg = /(?:\?([^#]*))?(#.*)?$/,
c = url.match(reg),
matches = {},
ret;
if (c && c[1]) {
var p = c[1].split('&');
for (var i=0; i<p.length; i++) {
var r = p[i].split('=');
matches[decodeURIComponent(r[0])] = decodeURIComponent(r.slice(1).join('='));
}
}
url = segmentize(url.replace(reg, ''));
route = segmentize(route || '');
var max = Math.max(url.length, route.length);
for (var i$1=0; i$1<max; i$1++) {
if (route[i$1] && route[i$1].charAt(0)===':') {
var param = route[i$1].replace(/(^\:|[+*?]+$)/g, ''),
flags = (route[i$1].match(/[+*?]+$/) || EMPTY$1)[0] || '',
plus = ~flags.indexOf('+'),
star = ~flags.indexOf('*'),
val = url[i$1] || '';
if (!val && !star && (flags.indexOf('?')<0 || plus)) {
ret = false;
break;
}
matches[param] = decodeURIComponent(val);
if (plus || star) {
matches[param] = url.slice(i$1).map(decodeURIComponent).join('/');
break;
}
}
else if (route[i$1]!==url[i$1]) {
ret = false;
break;
}
}
if (opts.default!==true && ret===false) { return false; }
return matches;
}
function pathRankSort(a, b) {
var aAttr = a.attributes || EMPTY$1,
bAttr = b.attributes || EMPTY$1;
if (aAttr.default) { return 1; }
if (bAttr.default) { return -1; }
var diff = rank(aAttr.path) - rank(bAttr.path);
return diff || (aAttr.path.length - bAttr.path.length);
}
function segmentize(url) {
return strip(url).split('/');
}
function rank(url) {
return (strip(url).match(/\/+/g) || '').length;
}
function strip(url) {
return url.replace(/(^\/+|\/+$)/g, '');
}
var customHistory = null;
var ROUTERS = [];
var subscribers = [];
var EMPTY = {};
function isPreactElement(node) {
return node.__preactattr_!=null || typeof Symbol!=='undefined' && node[Symbol.for('preactattr')]!=null;
}
function setUrl(url, type) {
if ( type === void 0 ) type='push';
if (customHistory && customHistory[type]) {
customHistory[type](url);
}
else if (typeof history!=='undefined' && history[type+'State']) {
history[type+'State'](null, null, url);
}
}
function getCurrentUrl() {
var url;
if (customHistory && customHistory.location) {
url = customHistory.location;
}
else if (customHistory && customHistory.getCurrentLocation) {
url = customHistory.getCurrentLocation();
}
else {
url = typeof location!=='undefined' ? location : EMPTY;
}
return ("" + (url.pathname || '') + (url.search || ''));
}
function route(url, replace) {
if ( replace === void 0 ) replace=false;
if (typeof url!=='string' && url.url) {
replace = url.replace;
url = url.url;
}
// only push URL into history if we can handle it
if (canRoute(url)) {
setUrl(url, replace ? 'replace' : 'push');
}
return routeTo(url);
}
/** Check if the given URL can be handled by any router instances. */
function canRoute(url) {
for (var i=ROUTERS.length; i--; ) {
if (ROUTERS[i].canRoute(url)) { return true; }
}
return false;
}
/** Tell all router instances to handle the given URL. */
function routeTo(url) {
var didRoute = false;
for (var i=0; i<ROUTERS.length; i++) {
if (ROUTERS[i].routeTo(url)===true) {
didRoute = true;
}
}
for (var i$1=subscribers.length; i$1--; ) {
subscribers[i$1](url);
}
return didRoute;
}
function routeFromLink(node) {
// only valid elements
if (!node || !node.getAttribute) { return; }
var href = node.getAttribute('href'),
target = node.getAttribute('target');
// ignore links with targets and non-path URLs
if (!href || !href.match(/^\//g) || (target && !target.match(/^_?self$/i))) { return; }
// attempt to route, if no match simply cede control to browser
return route(href);
}
function handleLinkClick(e) {
if (e.button==0) {
routeFromLink(e.currentTarget || e.target || this);
return prevent(e);
}
}
function prevent(e) {
if (e) {
if (e.stopImmediatePropagation) { e.stopImmediatePropagation(); }
if (e.stopPropagation) { e.stopPropagation(); }
e.preventDefault();
}
return false;
}
function delegateLinkHandler(e) {
// ignore events the browser takes care of already:
if (e.ctrlKey || e.metaKey || e.altKey || e.shiftKey || e.button!==0) { return; }
var t = e.target;
do {
if (String(t.nodeName).toUpperCase()==='A' && t.getAttribute('href') && isPreactElement(t)) {
if (t.hasAttribute('native')) { return; }
// if link is handled by the router, prevent browser defaults
if (routeFromLink(t)) {
return prevent(e);
}
}
} while ((t=t.parentNode));
}
var eventListenersInitialized = false;
function initEventListeners() {
if (eventListenersInitialized){
return;
}
if (typeof addEventListener==='function') {
if (!customHistory) {
addEventListener('popstate', function () { return routeTo(getCurrentUrl()); });
}
addEventListener('click', delegateLinkHandler);
}
eventListenersInitialized = true;
}
var Router = (function (Component$$1) {
function Router(props) {
Component$$1.call(this, props);
if (props.history) {
customHistory = props.history;
}
this.state = {
url: props.url || getCurrentUrl()
};
initEventListeners();
}
if ( Component$$1 ) Router.__proto__ = Component$$1;
Router.prototype = Object.create( Component$$1 && Component$$1.prototype );
Router.prototype.constructor = Router;
Router.prototype.shouldComponentUpdate = function shouldComponentUpdate (props) {
if (props.static!==true) { return true; }
return props.url!==this.props.url || props.onChange!==this.props.onChange;
};
/** Check if the given URL can be matched against any children */
Router.prototype.canRoute = function canRoute (url) {
return this.getMatchingChildren(this.props.children, url, false).length > 0;
};
/** Re-render children with a new URL to match against. */
Router.prototype.routeTo = function routeTo (url) {
this._didRoute = false;
this.setState({ url: url });
// if we're in the middle of an update, don't synchronously re-route.
if (this.updating) { return this.canRoute(url); }
this.forceUpdate();
return this._didRoute;
};
Router.prototype.componentWillMount = function componentWillMount () {
ROUTERS.push(this);
this.updating = true;
};
Router.prototype.componentDidMount = function componentDidMount () {
var this$1 = this;
if (customHistory) {
this.unlisten = customHistory.listen(function (location) {
this$1.routeTo(("" + (location.pathname || '') + (location.search || '')));
});
}
this.updating = false;
};
Router.prototype.componentWillUnmount = function componentWillUnmount () {
if (typeof this.unlisten==='function') { this.unlisten(); }
ROUTERS.splice(ROUTERS.indexOf(this), 1);
};
Router.prototype.componentWillUpdate = function componentWillUpdate () {
this.updating = true;
};
Router.prototype.componentDidUpdate = function componentDidUpdate () {
this.updating = false;
};
Router.prototype.getMatchingChildren = function getMatchingChildren (children, url, invoke) {
return children.slice().sort(pathRankSort).map( function (vnode) {
var attrs = vnode.attributes || {},
path = attrs.path,
matches = exec(url, path, attrs);
if (matches) {
if (invoke!==false) {
var newProps = { url: url, matches: matches };
assign(newProps, matches);
return cloneElement(vnode, newProps);
}
return vnode;
}
return false;
}).filter(Boolean);
};
Router.prototype.render = function render (ref, ref$1) {
var children = ref.children;
var onChange = ref.onChange;
var url = ref$1.url;
var active = this.getMatchingChildren(children, url, true);
var current = active[0] || null;
this._didRoute = !!current;
var previous = this.previousUrl;
if (url!==previous) {
this.previousUrl = url;
if (typeof onChange==='function') {
onChange({
router: this,
url: url,
previous: previous,
active: active,
current: current
});
}
}
return current;
};
return Router;
}(Component));
var Link = function (props) { return (
h('a', assign({ onClick: handleLinkClick }, props))
); };
var Route = function (props) { return h(props.component, props); };
Router.subscribers = subscribers;
Router.getCurrentUrl = getCurrentUrl;
Router.route = route;
Router.Router = Router;
Router.Route = Route;
Router.Link = Link;
export { subscribers, getCurrentUrl, route, Router, Route, Link };export default Router;
//# sourceMappingURL=preact-router.es.js.map