413 lines
12 KiB
Plaintext
413 lines
12 KiB
Plaintext
const stateIndexKey = "__TSR_index";
|
|
const popStateEvent = "popstate";
|
|
const beforeUnloadEvent = "beforeunload";
|
|
function createHistory(opts) {
|
|
let location = opts.getLocation();
|
|
const subscribers = /* @__PURE__ */ new Set();
|
|
const notify = (action) => {
|
|
location = opts.getLocation();
|
|
subscribers.forEach((subscriber) => subscriber({ location, action }));
|
|
};
|
|
const handleIndexChange = (action) => {
|
|
if (opts.notifyOnIndexChange ?? true) notify(action);
|
|
else location = opts.getLocation();
|
|
};
|
|
const tryNavigation = async ({
|
|
task,
|
|
navigateOpts,
|
|
...actionInfo
|
|
}) => {
|
|
var _a, _b;
|
|
const ignoreBlocker = (navigateOpts == null ? void 0 : navigateOpts.ignoreBlocker) ?? false;
|
|
if (ignoreBlocker) {
|
|
task();
|
|
return;
|
|
}
|
|
const blockers = ((_a = opts.getBlockers) == null ? void 0 : _a.call(opts)) ?? [];
|
|
const isPushOrReplace = actionInfo.type === "PUSH" || actionInfo.type === "REPLACE";
|
|
if (typeof document !== "undefined" && blockers.length && isPushOrReplace) {
|
|
for (const blocker of blockers) {
|
|
const nextLocation = parseHref(actionInfo.path, actionInfo.state);
|
|
const isBlocked = await blocker.blockerFn({
|
|
currentLocation: location,
|
|
nextLocation,
|
|
action: actionInfo.type
|
|
});
|
|
if (isBlocked) {
|
|
(_b = opts.onBlocked) == null ? void 0 : _b.call(opts);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
task();
|
|
};
|
|
return {
|
|
get location() {
|
|
return location;
|
|
},
|
|
get length() {
|
|
return opts.getLength();
|
|
},
|
|
subscribers,
|
|
subscribe: (cb) => {
|
|
subscribers.add(cb);
|
|
return () => {
|
|
subscribers.delete(cb);
|
|
};
|
|
},
|
|
push: (path, state, navigateOpts) => {
|
|
const currentIndex = location.state[stateIndexKey];
|
|
state = assignKeyAndIndex(currentIndex + 1, state);
|
|
tryNavigation({
|
|
task: () => {
|
|
opts.pushState(path, state);
|
|
notify({ type: "PUSH" });
|
|
},
|
|
navigateOpts,
|
|
type: "PUSH",
|
|
path,
|
|
state
|
|
});
|
|
},
|
|
replace: (path, state, navigateOpts) => {
|
|
const currentIndex = location.state[stateIndexKey];
|
|
state = assignKeyAndIndex(currentIndex, state);
|
|
tryNavigation({
|
|
task: () => {
|
|
opts.replaceState(path, state);
|
|
notify({ type: "REPLACE" });
|
|
},
|
|
navigateOpts,
|
|
type: "REPLACE",
|
|
path,
|
|
state
|
|
});
|
|
},
|
|
go: (index, navigateOpts) => {
|
|
tryNavigation({
|
|
task: () => {
|
|
opts.go(index);
|
|
handleIndexChange({ type: "GO", index });
|
|
},
|
|
navigateOpts,
|
|
type: "GO"
|
|
});
|
|
},
|
|
back: (navigateOpts) => {
|
|
tryNavigation({
|
|
task: () => {
|
|
opts.back((navigateOpts == null ? void 0 : navigateOpts.ignoreBlocker) ?? false);
|
|
handleIndexChange({ type: "BACK" });
|
|
},
|
|
navigateOpts,
|
|
type: "BACK"
|
|
});
|
|
},
|
|
forward: (navigateOpts) => {
|
|
tryNavigation({
|
|
task: () => {
|
|
opts.forward((navigateOpts == null ? void 0 : navigateOpts.ignoreBlocker) ?? false);
|
|
handleIndexChange({ type: "FORWARD" });
|
|
},
|
|
navigateOpts,
|
|
type: "FORWARD"
|
|
});
|
|
},
|
|
canGoBack: () => location.state[stateIndexKey] !== 0,
|
|
createHref: (str) => opts.createHref(str),
|
|
block: (blocker) => {
|
|
var _a;
|
|
if (!opts.setBlockers) return () => {
|
|
};
|
|
const blockers = ((_a = opts.getBlockers) == null ? void 0 : _a.call(opts)) ?? [];
|
|
opts.setBlockers([...blockers, blocker]);
|
|
return () => {
|
|
var _a2, _b;
|
|
const blockers2 = ((_a2 = opts.getBlockers) == null ? void 0 : _a2.call(opts)) ?? [];
|
|
(_b = opts.setBlockers) == null ? void 0 : _b.call(opts, blockers2.filter((b) => b !== blocker));
|
|
};
|
|
},
|
|
flush: () => {
|
|
var _a;
|
|
return (_a = opts.flush) == null ? void 0 : _a.call(opts);
|
|
},
|
|
destroy: () => {
|
|
var _a;
|
|
return (_a = opts.destroy) == null ? void 0 : _a.call(opts);
|
|
},
|
|
notify
|
|
};
|
|
}
|
|
function assignKeyAndIndex(index, state) {
|
|
if (!state) {
|
|
state = {};
|
|
}
|
|
return {
|
|
...state,
|
|
key: createRandomKey(),
|
|
[stateIndexKey]: index
|
|
};
|
|
}
|
|
function createBrowserHistory(opts) {
|
|
var _a;
|
|
const win = (opts == null ? void 0 : opts.window) ?? (typeof document !== "undefined" ? window : void 0);
|
|
const originalPushState = win.history.pushState;
|
|
const originalReplaceState = win.history.replaceState;
|
|
let blockers = [];
|
|
const _getBlockers = () => blockers;
|
|
const _setBlockers = (newBlockers) => blockers = newBlockers;
|
|
const createHref = (opts == null ? void 0 : opts.createHref) ?? ((path) => path);
|
|
const parseLocation = (opts == null ? void 0 : opts.parseLocation) ?? (() => parseHref(
|
|
`${win.location.pathname}${win.location.search}${win.location.hash}`,
|
|
win.history.state
|
|
));
|
|
if (!((_a = win.history.state) == null ? void 0 : _a.key)) {
|
|
win.history.replaceState(
|
|
{
|
|
[stateIndexKey]: 0,
|
|
key: createRandomKey()
|
|
},
|
|
""
|
|
);
|
|
}
|
|
let currentLocation = parseLocation();
|
|
let rollbackLocation;
|
|
let nextPopIsGo = false;
|
|
let ignoreNextPop = false;
|
|
let skipBlockerNextPop = false;
|
|
let ignoreNextBeforeUnload = false;
|
|
const getLocation = () => currentLocation;
|
|
let next;
|
|
let scheduled;
|
|
const flush = () => {
|
|
if (!next) {
|
|
return;
|
|
}
|
|
history._ignoreSubscribers = true;
|
|
(next.isPush ? win.history.pushState : win.history.replaceState)(
|
|
next.state,
|
|
"",
|
|
next.href
|
|
);
|
|
history._ignoreSubscribers = false;
|
|
next = void 0;
|
|
scheduled = void 0;
|
|
rollbackLocation = void 0;
|
|
};
|
|
const queueHistoryAction = (type, destHref, state) => {
|
|
const href = createHref(destHref);
|
|
if (!scheduled) {
|
|
rollbackLocation = currentLocation;
|
|
}
|
|
currentLocation = parseHref(destHref, state);
|
|
next = {
|
|
href,
|
|
state,
|
|
isPush: (next == null ? void 0 : next.isPush) || type === "push"
|
|
};
|
|
if (!scheduled) {
|
|
scheduled = Promise.resolve().then(() => flush());
|
|
}
|
|
};
|
|
const onPushPop = (type) => {
|
|
currentLocation = parseLocation();
|
|
history.notify({ type });
|
|
};
|
|
const onPushPopEvent = async () => {
|
|
if (ignoreNextPop) {
|
|
ignoreNextPop = false;
|
|
return;
|
|
}
|
|
const nextLocation = parseLocation();
|
|
const delta = nextLocation.state[stateIndexKey] - currentLocation.state[stateIndexKey];
|
|
const isForward = delta === 1;
|
|
const isBack = delta === -1;
|
|
const isGo = !isForward && !isBack || nextPopIsGo;
|
|
nextPopIsGo = false;
|
|
const action = isGo ? "GO" : isBack ? "BACK" : "FORWARD";
|
|
const notify = isGo ? {
|
|
type: "GO",
|
|
index: delta
|
|
} : {
|
|
type: isBack ? "BACK" : "FORWARD"
|
|
};
|
|
if (skipBlockerNextPop) {
|
|
skipBlockerNextPop = false;
|
|
} else {
|
|
const blockers2 = _getBlockers();
|
|
if (typeof document !== "undefined" && blockers2.length) {
|
|
for (const blocker of blockers2) {
|
|
const isBlocked = await blocker.blockerFn({
|
|
currentLocation,
|
|
nextLocation,
|
|
action
|
|
});
|
|
if (isBlocked) {
|
|
ignoreNextPop = true;
|
|
win.history.go(1);
|
|
history.notify(notify);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
currentLocation = parseLocation();
|
|
history.notify(notify);
|
|
};
|
|
const onBeforeUnload = (e) => {
|
|
if (ignoreNextBeforeUnload) {
|
|
ignoreNextBeforeUnload = false;
|
|
return;
|
|
}
|
|
let shouldBlock = false;
|
|
const blockers2 = _getBlockers();
|
|
if (typeof document !== "undefined" && blockers2.length) {
|
|
for (const blocker of blockers2) {
|
|
const shouldHaveBeforeUnload = blocker.enableBeforeUnload ?? true;
|
|
if (shouldHaveBeforeUnload === true) {
|
|
shouldBlock = true;
|
|
break;
|
|
}
|
|
if (typeof shouldHaveBeforeUnload === "function" && shouldHaveBeforeUnload() === true) {
|
|
shouldBlock = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (shouldBlock) {
|
|
e.preventDefault();
|
|
return e.returnValue = "";
|
|
}
|
|
return;
|
|
};
|
|
const history = createHistory({
|
|
getLocation,
|
|
getLength: () => win.history.length,
|
|
pushState: (href, state) => queueHistoryAction("push", href, state),
|
|
replaceState: (href, state) => queueHistoryAction("replace", href, state),
|
|
back: (ignoreBlocker) => {
|
|
if (ignoreBlocker) skipBlockerNextPop = true;
|
|
ignoreNextBeforeUnload = true;
|
|
return win.history.back();
|
|
},
|
|
forward: (ignoreBlocker) => {
|
|
if (ignoreBlocker) skipBlockerNextPop = true;
|
|
ignoreNextBeforeUnload = true;
|
|
win.history.forward();
|
|
},
|
|
go: (n) => {
|
|
nextPopIsGo = true;
|
|
win.history.go(n);
|
|
},
|
|
createHref: (href) => createHref(href),
|
|
flush,
|
|
destroy: () => {
|
|
win.history.pushState = originalPushState;
|
|
win.history.replaceState = originalReplaceState;
|
|
win.removeEventListener(beforeUnloadEvent, onBeforeUnload, {
|
|
capture: true
|
|
});
|
|
win.removeEventListener(popStateEvent, onPushPopEvent);
|
|
},
|
|
onBlocked: () => {
|
|
if (rollbackLocation && currentLocation !== rollbackLocation) {
|
|
currentLocation = rollbackLocation;
|
|
}
|
|
},
|
|
getBlockers: _getBlockers,
|
|
setBlockers: _setBlockers,
|
|
notifyOnIndexChange: false
|
|
});
|
|
win.addEventListener(beforeUnloadEvent, onBeforeUnload, { capture: true });
|
|
win.addEventListener(popStateEvent, onPushPopEvent);
|
|
win.history.pushState = function(...args) {
|
|
const res = originalPushState.apply(win.history, args);
|
|
if (!history._ignoreSubscribers) onPushPop("PUSH");
|
|
return res;
|
|
};
|
|
win.history.replaceState = function(...args) {
|
|
const res = originalReplaceState.apply(win.history, args);
|
|
if (!history._ignoreSubscribers) onPushPop("REPLACE");
|
|
return res;
|
|
};
|
|
return history;
|
|
}
|
|
function createHashHistory(opts) {
|
|
const win = (opts == null ? void 0 : opts.window) ?? (typeof document !== "undefined" ? window : void 0);
|
|
return createBrowserHistory({
|
|
window: win,
|
|
parseLocation: () => {
|
|
const hashSplit = win.location.hash.split("#").slice(1);
|
|
const pathPart = hashSplit[0] ?? "/";
|
|
const searchPart = win.location.search;
|
|
const hashEntries = hashSplit.slice(1);
|
|
const hashPart = hashEntries.length === 0 ? "" : `#${hashEntries.join("#")}`;
|
|
const hashHref = `${pathPart}${searchPart}${hashPart}`;
|
|
return parseHref(hashHref, win.history.state);
|
|
},
|
|
createHref: (href) => `${win.location.pathname}${win.location.search}#${href}`
|
|
});
|
|
}
|
|
function createMemoryHistory(opts = {
|
|
initialEntries: ["/"]
|
|
}) {
|
|
const entries = opts.initialEntries;
|
|
let index = opts.initialIndex ? Math.min(Math.max(opts.initialIndex, 0), entries.length - 1) : entries.length - 1;
|
|
const states = entries.map(
|
|
(_entry, index2) => assignKeyAndIndex(index2, void 0)
|
|
);
|
|
const getLocation = () => parseHref(entries[index], states[index]);
|
|
return createHistory({
|
|
getLocation,
|
|
getLength: () => entries.length,
|
|
pushState: (path, state) => {
|
|
if (index < entries.length - 1) {
|
|
entries.splice(index + 1);
|
|
states.splice(index + 1);
|
|
}
|
|
states.push(state);
|
|
entries.push(path);
|
|
index = Math.max(entries.length - 1, 0);
|
|
},
|
|
replaceState: (path, state) => {
|
|
states[index] = state;
|
|
entries[index] = path;
|
|
},
|
|
back: () => {
|
|
index = Math.max(index - 1, 0);
|
|
},
|
|
forward: () => {
|
|
index = Math.min(index + 1, entries.length - 1);
|
|
},
|
|
go: (n) => {
|
|
index = Math.min(Math.max(index + n, 0), entries.length - 1);
|
|
},
|
|
createHref: (path) => path
|
|
});
|
|
}
|
|
function parseHref(href, state) {
|
|
const hashIndex = href.indexOf("#");
|
|
const searchIndex = href.indexOf("?");
|
|
return {
|
|
href,
|
|
pathname: href.substring(
|
|
0,
|
|
hashIndex > 0 ? searchIndex > 0 ? Math.min(hashIndex, searchIndex) : hashIndex : searchIndex > 0 ? searchIndex : href.length
|
|
),
|
|
hash: hashIndex > -1 ? href.substring(hashIndex) : "",
|
|
search: searchIndex > -1 ? href.slice(searchIndex, hashIndex === -1 ? void 0 : hashIndex) : "",
|
|
state: state || { [stateIndexKey]: 0, key: createRandomKey() }
|
|
};
|
|
}
|
|
function createRandomKey() {
|
|
return (Math.random() + 1).toString(36).substring(7);
|
|
}
|
|
export {
|
|
createBrowserHistory,
|
|
createHashHistory,
|
|
createHistory,
|
|
createMemoryHistory,
|
|
parseHref
|
|
};
|
|
//# sourceMappingURL=index.js.map
|