svelte-pathfinder
Version:
Tiny, state-based, advanced router for SvelteJS.
911 lines (902 loc) • 32.2 kB
JavaScript
var pathfinder = (function (exports) {
'use strict';
function noop() {}
function run(fn) {
return fn();
}
function run_all(fns) {
fns.forEach(run);
}
function is_function(thing) {
return typeof thing === 'function';
}
function safe_not_equal(a, b) {
return a != a ? b == b : a !== b || a && typeof a === 'object' || typeof a === 'function';
}
function subscribe(store, ...callbacks) {
if (store == null) {
return noop;
}
const unsub = store.subscribe(...callbacks);
return unsub.unsubscribe ? () => unsub.unsubscribe() : unsub;
}
function get_store_value(store) {
let value;
subscribe(store, _ => value = _)();
return value;
}
function set_current_component(component) {
}
const dirty_components = [];
const binding_callbacks = [];
const render_callbacks = [];
const flush_callbacks = [];
const resolved_promise = Promise.resolve();
let update_scheduled = false;
function schedule_update() {
if (!update_scheduled) {
update_scheduled = true;
resolved_promise.then(flush);
}
}
function tick() {
schedule_update();
return resolved_promise;
}
function add_render_callback(fn) {
render_callbacks.push(fn);
}
// flush() calls callbacks in this order:
// 1. All beforeUpdate callbacks, in order: parents before children
// 2. All bind:this callbacks, in reverse order: children before parents.
// 3. All afterUpdate callbacks, in order: parents before children. EXCEPT
// for afterUpdates called during the initial onMount, which are called in
// reverse order: children before parents.
// Since callbacks might update component values, which could trigger another
// call to flush(), the following steps guard against this:
// 1. During beforeUpdate, any updated components will be added to the
// dirty_components array and will cause a reentrant call to flush(). Because
// the flush index is kept outside the function, the reentrant call will pick
// up where the earlier call left off and go through all dirty components. The
// current_component value is saved and restored so that the reentrant call will
// not interfere with the "parent" flush() call.
// 2. bind:this callbacks cannot trigger new flush() calls.
// 3. During afterUpdate, any updated components will NOT have their afterUpdate
// callback called a second time; the seen_callbacks set, outside the flush()
// function, guarantees this behavior.
const seen_callbacks = new Set();
let flushidx = 0; // Do *not* move this inside the flush() function
function flush() {
// Do not reenter flush while dirty components are updated, as this can
// result in an infinite loop. Instead, let the inner flush handle it.
// Reentrancy is ok afterwards for bindings etc.
if (flushidx !== 0) {
return;
}
do {
// first, call beforeUpdate functions
// and update components
try {
while (flushidx < dirty_components.length) {
const component = dirty_components[flushidx];
flushidx++;
set_current_component(component);
update(component.$$);
}
} catch (e) {
// reset dirty state to not end up in a deadlocked state and then rethrow
dirty_components.length = 0;
flushidx = 0;
throw e;
}
dirty_components.length = 0;
flushidx = 0;
while (binding_callbacks.length) binding_callbacks.pop()();
// then, once components are updated, call
// afterUpdate functions. This may cause
// subsequent updates...
for (let i = 0; i < render_callbacks.length; i += 1) {
const callback = render_callbacks[i];
if (!seen_callbacks.has(callback)) {
// ...so guard against infinite loops
seen_callbacks.add(callback);
callback();
}
}
render_callbacks.length = 0;
} while (dirty_components.length);
while (flush_callbacks.length) {
flush_callbacks.pop()();
}
update_scheduled = false;
seen_callbacks.clear();
}
function update($$) {
if ($$.fragment !== null) {
$$.update();
run_all($$.before_update);
const dirty = $$.dirty;
$$.dirty = [-1];
$$.fragment && $$.fragment.p($$.ctx, dirty);
$$.after_update.forEach(add_render_callback);
}
}
const subscriber_queue = [];
/**
* Creates a `Readable` store that allows reading by subscription.
* @param value initial value
* @param {StartStopNotifier}start start and stop notifications for subscriptions
*/
function readable(value, start) {
return {
subscribe: writable(value, start).subscribe
};
}
/**
* Create a `Writable` store that allows both updating and reading by subscription.
* @param {*=}value initial value
* @param {StartStopNotifier=}start start and stop notifications for subscriptions
*/
function writable(value, start = noop) {
let stop;
const subscribers = new Set();
function set(new_value) {
if (safe_not_equal(value, new_value)) {
value = new_value;
if (stop) {
// store is ready
const run_queue = !subscriber_queue.length;
for (const subscriber of subscribers) {
subscriber[1]();
subscriber_queue.push(subscriber, value);
}
if (run_queue) {
for (let i = 0; i < subscriber_queue.length; i += 2) {
subscriber_queue[i][0](subscriber_queue[i + 1]);
}
subscriber_queue.length = 0;
}
}
}
}
function update(fn) {
set(fn(value));
}
function subscribe(run, invalidate = noop) {
const subscriber = [run, invalidate];
subscribers.add(subscriber);
if (subscribers.size === 1) {
stop = start(set) || noop;
}
run(value);
return () => {
subscribers.delete(subscriber);
if (subscribers.size === 0) {
stop();
stop = null;
}
};
}
return {
set,
update,
subscribe
};
}
function derived(stores, fn, initial_value) {
const single = !Array.isArray(stores);
const stores_array = single ? [stores] : stores;
const auto = fn.length < 2;
return readable(initial_value, set => {
let inited = false;
const values = [];
let pending = 0;
let cleanup = noop;
const sync = () => {
if (pending) {
return;
}
cleanup();
const result = fn(single ? values[0] : values, set);
if (auto) {
set(result);
} else {
cleanup = is_function(result) ? result : noop;
}
};
const unsubscribers = stores_array.map((store, i) => subscribe(store, value => {
values[i] = value;
pending &= ~(1 << i);
if (inited) {
sync();
}
}, () => {
pending |= 1 << i;
}));
inited = true;
sync();
return function stop() {
run_all(unsubscribers);
cleanup();
};
});
}
const specialLinks = /^((mailto:)|(tel:)|(sms:)|(data:)|(blob:)|(javascript:)|(ftp(s?):\/\/)|(file:\/\/))/;
const hasLocation = typeof location !== 'undefined';
const hasProcess = typeof process !== 'undefined';
const hasHistory = typeof history !== 'undefined';
const hasPushState = hasHistory && isFn(history.pushState);
const hasWindow = typeof window !== 'undefined';
const isSubWindow = hasWindow && window !== window.parent;
const isFileScheme = hasLocation && (location.protocol === 'file:' || /[-_\w]+[.][\w]+$/i.test(location.pathname));
const sideEffect = hasWindow && hasHistory && hasLocation && !isSubWindow;
const useHashbang = !hasPushState || isFileScheme;
const hashbang = '#!';
const prefs = {
array: {
separator: ',',
format: 'bracket'
},
convertTypes: true,
breakHooks: true,
hashbang: false,
anchor: false,
scroll: false,
focus: false,
nesting: 3,
sideEffect,
base: ''
};
function getPath() {
const pathname = getLocation().pathname;
if (!pathname) return;
const base = getBase();
const path = trimPrefix(pathname, base);
return prependPrefix(path);
}
function getLocation() {
if (!hasLocation) return {};
if (prefs.hashbang || useHashbang) {
const hash = location.hash;
return new URL(hash.indexOf(hashbang) === 0 ? hash.substring(2) : hash.substring(1), 'file:');
}
return location;
}
function getBase() {
if (prefs.base) return prefs.base;
if (hasLocation && (prefs.hashbang || useHashbang)) return location.pathname;
return '/';
}
function getFullURL(url) {
(prefs.hashbang || useHashbang) && (url = hashbang + url);
const base = getBase();
return (base[base.length - 1] === '/' ? base.substring(0, base.length - 1) : base) + url;
}
function getShortURL(url) {
url = trimPrefix(url, getLocation().origin);
const base = getBase();
url = trimPrefix(url, base);
(prefs.hashbang || useHashbang) && (url = trimPrefix(url, hashbang));
return prependPrefix(url);
}
function isBtn(el) {
const tagName = el.tagName.toLowerCase();
const type = el.type && el.type.toLowerCase();
return tagName === 'button' || tagName === 'input' && ['button', 'submit', 'image'].includes(type);
}
function closest(el, tagName) {
while (el && el.nodeName.toLowerCase() !== tagName) el = el.parentNode;
return !el || el.nodeName.toLowerCase() !== tagName ? null : el;
}
function setScroll(scroll) {
let hash = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
const anchor = trimPrefix(normalizeHash(hash), '#');
if (scroll && prefs.scroll) {
const opts = isObj(prefs.scroll) ? {
...prefs.scroll,
...scroll
} : scroll;
const {
top = 0,
left = 0
} = scroll;
const {
scrollHeight,
scrollWidth
} = document.documentElement;
if (top <= scrollHeight && left <= scrollWidth) return scrollTo(opts);
const cancel = observeResize(entries => {
if (!entries[0]) return cancel();
if ((!top || entries[0].contentRect.height >= top) && (!left || entries[0].contentRect.width >= left)) {
cancel();
scrollTo(opts);
}
}, document.documentElement);
} else if (anchor && prefs.anchor) {
const opts = isObj(prefs.anchor) ? prefs.anchor : {};
const el = document.getElementById(anchor);
if (el) return scrollTo(opts, el);
const cancel = observeDom(() => {
const el = document.getElementById(anchor);
if (el) {
cancel();
scrollTo(opts, el);
}
});
} else if (prefs.scroll) {
scrollTo();
}
}
function setFocus(keepFocusId, activeElement) {
if (!prefs.focus) return;
setTimeout(() => {
const autofocus = focus();
if (autofocus) return autofocus.focus();
const cancel = observeDom(() => {
const autofocus = focus();
if (autofocus) {
cancel();
autofocus.focus();
}
});
const body = document.body;
const tabindex = body.getAttribute('tabindex');
body.tabIndex = -1;
body.focus({
preventScroll: true
});
if (tabindex !== null) {
body.setAttribute('tabindex', tabindex);
} else {
body.removeAttribute('tabindex');
}
getSelection().removeAllRanges();
});
function focus() {
if (keepFocusId) {
return document.getElementById(keepFocusId);
} else if (document.activeElement !== activeElement && document.activeElement !== document.body) {
return document.activeElement;
} else {
return document.querySelector('[autofocus]');
}
}
}
function parseQuery() {
let str = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
let {
decode = decodeURIComponent
} = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
return str ? str.replace('?', '').replace(/\+/g, ' ').split('&').filter(Boolean).reduce((obj, p) => {
let [key, val] = p.split(/=(.*)/, 2);
key = decode(key || '');
val = decode(val || '');
let o = parseKeys(key, val);
obj = Object.keys(o).reduce((obj, key) => {
const val = prefs.convertTypes ? convertType(o[key]) : o[key];
if (obj[key]) {
Array.isArray(obj[key]) ? obj[key] = obj[key].concat(val) : Object.assign(obj[key], val);
} else {
obj[key] = val;
}
return obj;
}, obj);
return obj;
}, {}) : {};
}
function stringifyQuery() {
let obj = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
let {
encode = encodeURIComponent
} = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
return Object.keys(obj).reduce((a, k) => {
if (Object.prototype.hasOwnProperty.call(obj, k) && isNaN(parseInt(k, 10))) {
if (Array.isArray(obj[k])) {
if (prefs.array.format === 'separator') {
a.push(`${k}=${obj[k].join(prefs.array.separator)}`);
} else {
obj[k].forEach(v => a.push(`${k}[]=${encode(v)}`));
}
} else if (isObj(obj[k])) {
let o = parseKeys(k, obj[k]);
a.push(stringifyObj(o));
} else {
a.push(`${k}=${encode(obj[k])}`);
}
}
return a;
}, []).join('&');
}
function injectParams(pattern, params) {
let {
encode = encodeURIComponent
} = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
return pattern.replace(/(\/|^)([:*][^/]*?)(\?)?(?=[/.]|$)/g, (param, _, key) => {
param = params[key === '*' ? 'wild' : key.substring(1)];
return param ? `/${encode(param)}` : '';
});
}
function parseParams() {
let path = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
let pattern = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '*';
let {
loose = false,
sensitive = false,
blank = false,
decode = decodeURIComponent
} = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
const blanks = {};
const rgx = pattern instanceof RegExp ? pattern : pattern.split('/').reduce((rgx, seg, i, _ref) => {
let {
length
} = _ref;
if (seg) {
const pfx = seg[0];
if (pfx === '*') {
blanks['wild'] = undefined;
rgx += '/(?<wild>.*)';
} else if (pfx === ':') {
const opt = seg.indexOf('?', 1);
const ext = seg.indexOf('.', 1);
const isOpt = !!~opt;
const isExt = !!~ext;
const key = seg.substring(1, isOpt ? opt : isExt ? ext : seg.length);
blanks[key] = undefined;
rgx += isOpt && !isExt ? `(?:/(?<${key}>[^/]+?))?` : `/(?<${key}>[^/]+?)`;
if (isExt) rgx += `${isOpt ? '?' : ''}\\${seg.substring(ext)}`;
} else {
rgx += `/${seg}`;
}
}
if (i === length - 1) {
rgx += loose ? '(?:$|/)' : '/?$';
}
return rgx;
}, '^');
const flags = sensitive ? '' : 'i';
const matches = new RegExp(rgx, flags).exec(path);
return matches ? Object.entries(matches.groups || {}).reduce((params, _ref2) => {
let [key, val] = _ref2;
const value = decode(val);
params[key] = prefs.convertTypes ? convertType(value) : value;
return params;
}, {}) : blank ? blanks : null;
}
function normalizeHash(fragment) {
let {
decode = decodeURIComponent
} = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
return decode(fragment);
}
function prependPrefix(str) {
let pfx = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '/';
let strict = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
str += '';
return !str && strict ? str : str.indexOf(pfx) !== 0 ? pfx + str : str;
}
function trimPrefix(str, pfx) {
return (str + '').indexOf(pfx) === 0 ? str.substring(pfx.length) : str;
}
function isObj(obj) {
return !Array.isArray(obj) && typeof obj === 'object' && obj !== null;
}
function isFn(fn) {
return typeof fn === 'function';
}
function shallowCopy(value) {
if (typeof value !== 'object' || value === null) return value;
return Object.create(Object.getPrototypeOf(value), Object.getOwnPropertyDescriptors(value));
}
function hookLauncher(hooks) {
return function () {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
const arr = [...hooks];
return !(prefs.breakHooks ? arr.some(cb => cb(...args) === false) : arr.reduce((stop, cb) => cb(...args) === false || stop, false));
};
}
function listenEvent() {
for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
window.addEventListener(...args);
return () => window.removeEventListener(...args);
}
function scrollTo() {
let {
top = 0,
left = 0,
...opts
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
let el = arguments.length > 1 ? arguments[1] : undefined;
if (el) {
document.documentElement.scrollIntoView ? el.scrollIntoView({
behavior: 'smooth',
...opts
}) : window.scrollTo({
top: el.offsetTop - top,
behavior: 'smooth',
...opts
});
} else {
window.scrollTo({
top,
left,
behavior: 'smooth',
...opts
});
}
}
function observeResize(cb, el) {
let t = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 5000;
const observer = new ResizeObserver(cb);
observer.observe(el);
const off = () => observer.unobserve(el);
setTimeout(off, t);
return off;
}
function observeDom(cb) {
let t = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 5000;
const observer = new MutationObserver(cb);
observer.observe(document.body, {
childList: true,
subtree: true
});
const off = () => observer.disconnect();
setTimeout(off, t);
return off;
}
function convertType(val) {
if (Array.isArray(val)) {
val[val.length - 1] = convertType(val[val.length - 1]);
return val;
} else if (typeof val === 'object') {
return Object.entries(val).reduce((obj, _ref3) => {
let [k, v] = _ref3;
obj[k] = convertType(v);
return obj;
}, {});
}
if (val === 'true' || val === 'false') {
return val === 'true';
} else if (val === 'null') {
return null;
} else if (val === 'undefined') {
return undefined;
} else if (val !== '' && !isNaN(Number(val)) && Number(val).toString() === val) {
return Number(val);
} else if (prefs.array.format === 'separator' && typeof val === 'string') {
const arr = val.split(prefs.array.separator);
return arr.length > 1 ? arr : val;
}
return val;
}
function parseKeys(key, val) {
const brackets = /(\[[^[\]]*])/,
child = /(\[[^[\]]*])/g;
let seg = brackets.exec(key),
parent = seg ? key.slice(0, seg.index) : key,
keys = [];
parent && keys.push(parent);
let i = 0;
while ((seg = child.exec(key)) && i < prefs.nesting) {
i++;
keys.push(seg[1]);
}
seg && keys.push(`[${key.slice(seg.index)}]`);
return parseObj(keys, val);
}
function parseObj(chain, val) {
let leaf = val;
for (let i = chain.length - 1; i >= 0; --i) {
let root = chain[i],
obj;
if (root === '[]') {
obj = [].concat(leaf);
} else {
obj = {};
const key = root.charAt(0) === '[' && root.charAt(root.length - 1) === ']' ? root.slice(1, -1) : root,
j = parseInt(key, 10);
if (!isNaN(j) && root !== key && String(j) === key && j >= 0) {
obj = [];
obj[j] = prefs.convertTypes ? convertType(leaf) : leaf;
} else {
obj[key] = leaf;
}
}
leaf = obj;
}
return leaf;
}
function stringifyObj() {
let obj = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
let nesting = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
return Object.entries(obj).map(_ref4 => {
let [key, val] = _ref4;
if (typeof val === 'object') {
return stringifyObj(val, nesting ? `${nesting}[${key}]` : key);
} else {
return `${nesting}[${key}]=${val}`;
}
}).join('&');
}
const pathable = createParsableStore(function path() {
let $path = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
if (typeof $path === 'string') $path = trimPrefix($path, '/').split('/');
return !Object.prototype.hasOwnProperty.call($path, 'toString') ? Object.defineProperty($path, 'toString', {
value() {
return prependPrefix(this.join('/'));
},
configurable: false,
writable: false
}) : $path;
});
const queryable = createParsableStore(function query() {
let $query = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
if (typeof $query === 'string') $query = parseQuery($query);
return !Object.prototype.hasOwnProperty.call($query, 'toString') ? Object.defineProperty($query, 'toString', {
value() {
return prependPrefix(stringifyQuery(this), '?', true);
},
configurable: false,
writable: false
}) : $query;
});
const fragmentable = createParsableStore(function fragment() {
let $fragment = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
return prependPrefix(normalizeHash($fragment), '#', true);
});
function createParamStore(path) {
return function (pattern) {
let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
if (pattern instanceof RegExp) throw new Error('Paramable does not support RegExp patterns.');
let params;
pattern = pattern.replace(/\/$/, '');
const {
subscribe
} = writable({}, set => {
return path.subscribe($path => {
params = parseParams($path.toString(), pattern, {
blank: true,
...options
});
set(shallowCopy(params));
});
});
function set() {
let value = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
if (Object.entries(params).some(_ref => {
let [key, val] = _ref;
return val !== value[key];
})) {
path.update($path => {
const tail = options.loose ? prependPrefix($path.slice(pattern.split('/').length - 1).join('/')) : '';
return injectParams(pattern + tail, value);
});
}
}
return {
get() {
return get_store_value(this);
},
update(fn) {
set(fn(this.get()));
},
subscribe,
set
};
};
}
function createParsableStore(parse) {
return (value, cbx) => {
let serialized = value && value.toString();
!Array.isArray(cbx) && (cbx = [cbx]);
const hooks = new Set(cbx);
const runHooks = hookLauncher(hooks);
const {
subscribe,
set
} = writable(value = parse(value), () => () => hooks.clear());
function update(val) {
val = parse(val);
if (val.toString() !== serialized && runHooks(val, value, parse.name) !== false) {
serialized = val.toString();
value = val;
set(value);
}
}
runHooks(null, value, parse.name);
return {
subscribe,
update(fn) {
update(fn(get_store_value(this)));
},
set(value) {
update(value);
},
hook(cb) {
if (isFn(cb)) {
hooks.add(cb);
cb(null, value, parse.name);
}
return () => hooks.delete(cb);
}
};
};
}
const pathname = getPath();
const {
search,
hash
} = getLocation();
let init = true;
let popstate = false;
let replace = false;
let len = 0;
const path = pathable(pathname, before);
const query = queryable(search, before);
const fragment = fragmentable(hash, before);
const state = writable({});
const url = derived([path, query, fragment], (_ref, set) => {
let [$path, $query, $fragment] = _ref;
let skip = false;
tick().then(() => {
if (skip) return;
set($path + $query + $fragment);
});
return () => skip = true;
}, pathname + search + hash);
const pattern = derived(path, $path => parseParams.bind(null, $path.toString()));
function before() {
if (!prefs.scroll && !prefs.focus) return;
state.update(function () {
let $state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
prefs.scroll && ($state._scroll = {
top: window.pageYOffset,
left: window.pageXOffset
});
prefs.focus && ($state._focus = document.activeElement.id);
return $state;
});
}
function after(url, state) {
const anchor = url.indexOf('#') >= 0 ? url.slice(url.indexOf('#')) : '';
const activeElement = document.activeElement;
!isObj(state) && (state = {});
tick().then(() => setFocus(state._focus, activeElement)).then(() => setScroll(state._scroll, anchor));
}
if (sideEffect || isSubWindow) {
const cleanup = new Set();
cleanup.add(url.subscribe($url => {
if (!init && !popstate && prefs.sideEffect) {
if (hasPushState) {
history[replace ? 'replaceState' : 'pushState']({}, null, getFullURL($url));
} else {
location.hash = getFullURL($url);
}
}
!popstate && after($url);
!replace && len++;
init = replace = popstate = false;
}));
if (hasPushState) {
cleanup.add(state.subscribe($state => {
if (init || !prefs.sideEffect) return;
history.replaceState($state, null, location.pathname + location.search + location.hash);
}));
cleanup.add(listenEvent('popstate', e => {
popstate = true;
goto(location.href, e.state);
after(getShortURL(location.href), e.state);
}));
} else {
cleanup.add(listenEvent('hashchange', () => {
popstate = true;
if (!prefs.hashbang && !useHashbang) return fragment.set(location.hash);
goto(location.hash);
after(getShortURL(location.hash));
}));
}
cleanup.add(listenEvent('beforeunload', () => {
cleanup.forEach(off => off());
cleanup.clear();
}, true));
}
function goto() {
let url = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
let data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
const {
pathname,
search,
hash
} = url instanceof URL ? url : new URL(getShortURL(url), 'file:');
path.set(pathname);
query.set(search);
fragment.set(hash);
tick().then(() => state.set(data || {}));
}
function back(url) {
if (len > 0 && sideEffect && prefs.sideEffect) {
history.back();
len--;
} else {
tick().then(() => goto(url));
}
}
function redirect(url, data) {
tick().then(() => {
replace = true;
goto(url, data);
});
}
function click(e) {
if (!e.target || e.ctrlKey || e.metaKey || e.altKey || e.shiftKey || e.button || e.which !== 1 || e.defaultPrevented) return;
const a = closest(e.target, 'a');
if (!a || a.target || a.hasAttribute('download') || a.hasAttribute('rel') && a.getAttribute('rel').includes('external')) return;
const href = a.getAttribute('href');
const url = a.href;
if (!href || url.indexOf(location.origin) !== 0 || specialLinks.test(href) || !prefs.hashbang && !useHashbang && href.startsWith('#')) return;
e.preventDefault();
goto(url, Object.assign({}, a.dataset));
}
function submit(e) {
if (!e.target || e.defaultPrevented) return;
const form = e.target;
const btn = e.submitter || isBtn(document.activeElement) && document.activeElement;
let action = form.action;
let method = form.method;
let target = form.target;
if (btn) {
btn.hasAttribute('formaction') && (action = btn.formAction);
btn.hasAttribute('formmethod') && (method = btn.formMethod);
btn.hasAttribute('formtarget') && (target = btn.formTarget);
}
if (method && method.toLowerCase() !== 'get') return;
if (target && target.toLowerCase() !== '_self') return;
const {
pathname,
hash
} = new URL(action);
const search = [];
const state = {};
const elements = form.elements;
const len = elements.length;
for (let i = 0; i < len; i++) {
const element = elements[i];
if (!element.name || element.disabled) continue;
if (['checkbox', 'radio'].includes(element.type) && !element.checked) {
continue;
}
if (isBtn(element) && element !== btn) {
continue;
}
if (element.type === 'hidden') {
state[element.name] = element.value;
continue;
}
search.push(`${element.name}=${element.value}`);
}
let url = prependPrefix(`${pathname}?${search.join('&')}${hash}`);
if (hasProcess && url.match(/^\/[a-zA-Z]:\//)) {
url = url.replace(/^\/[a-zA-Z]:\//, '/');
}
e.preventDefault();
goto(url, state);
}
const paramable = createParamStore(path);
exports.back = back;
exports.click = click;
exports.fragment = fragment;
exports.goto = goto;
exports.paramable = paramable;
exports.path = path;
exports.pattern = pattern;
exports.prefs = prefs;
exports.query = query;
exports.redirect = redirect;
exports.state = state;
exports.submit = submit;
exports.url = url;
return exports;
})({});