@fastkit/vue-page
Version:
Middleware for more convenient control of routing in Vue applications.
1,158 lines (1,152 loc) • 33.6 kB
JavaScript
import { EV } from '@fastkit/ev';
import { defineComponent, createVNode, ref, computed, Transition, h, Fragment, inject, onBeforeUnmount, nextTick, reactive, provide } from 'vue';
import { withCtx, NumberishPropOption, resolveNumberish, ClientOnly, extractRouteMatchedItems, getRouteQuery, isComponentCustomOptions, getRouteMatchedComponents } from '@fastkit/vue-utils';
import { RouterView, useRouter, stringifyQuery, RouterLink, useLink } from 'vue-router';
import { IN_WINDOW, isObject, isPromise } from '@fastkit/helpers';
import { Cookies } from '@fastkit/cookies';
import { TinyLogger, createTinyError } from '@fastkit/tiny-logger';
var SuspenseRouteBucket = class extends EV {
_sources = ref([]);
_total = ref(0);
_progress;
get sources() {
return this._sources.value;
}
get total() {
return this._total.value;
}
get progress() {
return this._progress.value;
}
constructor() {
super();
this._progress = computed(() => {
const { total, sources } = this;
return {
total,
resolved: total - sources.length
};
});
this.push = this.push.bind(this);
this.remove = this.remove.bind(this);
}
push(source) {
if (!source) return;
const { sources } = this;
if (!sources.includes(source)) {
sources.push(source);
this._total.value++;
const payload = { bucket: this, source };
this.emit("push", payload);
if (sources.length === 1) {
this.emit("start", payload);
}
}
}
remove(source) {
if (!source) return;
const { sources } = this;
const index = sources.indexOf(source);
if (index !== -1) {
sources.splice(index, 1);
const payload = { bucket: this, source };
this.emit("remove", payload);
if (sources.length === 0) {
this._total.value = 0;
this.emit("finish", payload);
}
}
}
ensureReady() {
return new Promise((resolve) => {
const { total } = this;
if (total === 0) return resolve();
this.once("finish", () => {
resolve();
});
});
}
};
var _suspenseRouteBucket;
function getSuspenseRouteBucket() {
if (!_suspenseRouteBucket) {
_suspenseRouteBucket = new SuspenseRouteBucket();
}
return _suspenseRouteBucket;
}
var ScrollBehaviorMessenger = class {
static fns = [];
static trigger() {
this.fns.forEach((fn) => fn(false));
this.fns = [];
}
static reserveNextPosition(position) {
return new Promise((resolve) => {
this.fns.push((canceled) => {
resolve(canceled ? void 0 : position);
});
});
}
static release() {
this.fns.forEach((fn) => fn(true));
this.fns = [];
}
};
function resolveRawRouterScrollBehavior(source) {
if (source === "top") {
source = (to, from, savedPosition) => savedPosition || { top: 0, left: 0 };
}
return source;
}
function extractMatchedBehaviors(route) {
const components = getRouteMatchedComponents(
route
);
const behaviors = [];
components.forEach(({ scrollBehavior }) => {
if (scrollBehavior != null) {
behaviors.push(resolveRawRouterScrollBehavior(scrollBehavior));
}
});
return behaviors;
}
function findLastMatchedBehavior(route) {
const behaviors = extractMatchedBehaviors(route);
return behaviors[behaviors.length - 1];
}
function createScrollBehavior() {
const scrollBehavior = (to, from, savedPosition) => {
let position = false;
const isRouteChanged = to !== from;
if (savedPosition) {
position = savedPosition;
} else if (isRouteChanged) {
const matchedBehavior = findLastMatchedBehavior(to) || resolveRawRouterScrollBehavior("top");
position = matchedBehavior(to, from, savedPosition);
} else if (to.path === from.path && to.hash !== from.hash) ;
return getSuspenseRouteBucket().ensureReady().then(async () => {
if (to.hash) {
let { hash } = to;
if (typeof window.CSS !== "undefined" && typeof window.CSS.escape !== "undefined") {
hash = `#${window.CSS.escape(hash.substr(1))}`;
}
try {
const el = document.querySelector(hash);
if (el) {
position = { el: hash };
}
} catch (e) {
console.warn(
"Failed to save scroll position. Please add CSS.escape() polyfill (https://github.com/mathiasbynens/CSS.escape)."
);
}
}
return ScrollBehaviorMessenger.reserveNextPosition(position);
});
};
return scrollBehavior;
}
function parseWatchQueryOption(route, opts) {
if (opts === true) {
opts = Object.keys(route.query);
}
if (!opts || !opts.length) return "";
const query = {};
opts.forEach((row) => {
query[row] = route.query[row];
});
const queryStr = stringifyQuery(query);
let key = "&";
if (queryStr) {
key += `?${queryStr}`;
}
return key;
}
function extractWatchQueryOption(source) {
if (!isObject(source)) return;
if (source.watchQuery) return source.watchQuery;
if (isObject(source.type) && source.type.watchQuery) {
return source.type.watchQuery;
}
}
function generateWatchQueryKeySuffix(routeProps) {
const option = extractWatchQueryOption(routeProps.Component);
return parseWatchQueryOption(routeProps.route, option);
}
var interpolatePath = (route, match) => match.path.replace(/(:\w+)\([^)]+\)/g, "$1").replace(/(:\w+)[?+*]/g, "$1").replace(/:\w+/g, (r) => route.params[r.slice(1)]?.toString() || "");
var generateRouteKey = (routeProps, override) => {
const matchedRoute = routeProps.route.matched.find(
(m) => routeProps.Component && m.components?.default === routeProps.Component.type
);
if (!matchedRoute) {
return "";
}
const source = override ?? matchedRoute?.meta.key ?? interpolatePath(routeProps.route, matchedRoute);
return typeof source === "function" ? source(routeProps.route) : source;
};
var generateRouteKeyWithWatchQuery = (routeProps, override) => {
const routeKey = generateRouteKey(routeProps, override);
if (!routeKey) return routeKey;
return `${routeKey}${generateWatchQueryKeySuffix(routeProps)}`;
};
function routeKeyWithWatchQueryByRouteItem(route, item) {
const routeKey = interpolatePath(route, item.route);
const routeProps = {
Component: item.Component,
route
};
return `${routeKey}${generateWatchQueryKeySuffix(routeProps)}`;
}
var __forcePrefetchStates = {};
function getForcePrefetchStates(pageKey) {
return __forcePrefetchStates[pageKey];
}
function consumeForcePrefetchStates(pageKey) {
const value = getForcePrefetchStates(pageKey);
setForcePrefetchStates(pageKey, false);
return value;
}
function setForcePrefetchStates(pageKey, value) {
if (IN_WINDOW) return;
if (value) {
__forcePrefetchStates[pageKey] = value;
} else {
delete __forcePrefetchStates[pageKey];
}
}
var name = "vue-page";
new TinyLogger(name);
var VuePageError = createTinyError(name);
// src/injections.ts
var VuePageControlInjectionKey = Symbol("VuePageControl");
function useVuePageControl(opts = {}) {
const pageControl = inject(VuePageControlInjectionKey, null);
if (!pageControl) {
throw new VuePageError(`missing provided VuePageControl.`);
}
const { onStart, onFinish, onError } = opts;
if (onStart || onFinish) {
onStart && pageControl.on("start", onStart);
onFinish && pageControl.on("finish", onFinish);
onError && pageControl.on("error", onError);
onBeforeUnmount(() => {
onStart && pageControl.off("start", onStart);
onFinish && pageControl.off("finish", onFinish);
onError && pageControl.off("error", onError);
});
}
return pageControl;
}
// src/composables/page-error.ts
var name2 = "VuePageControlError";
function isVuePageControlError(source) {
return source instanceof VuePageControlError;
}
var VuePageControlError = class extends Error {
name = name2;
statusCode;
constructor(rawSettings = {}) {
let settings;
if (rawSettings instanceof Error) {
if (isVuePageControlError(rawSettings)) {
settings = {
statusCode: rawSettings.statusCode,
message: rawSettings.message,
stack: rawSettings.stack
};
} else {
settings = {
message: rawSettings.message,
stack: rawSettings.stack
};
}
} else {
settings = // eslint-disable-next-line no-nested-ternary
typeof rawSettings === "string" ? { message: rawSettings } : rawSettings && typeof rawSettings === "object" ? {
statusCode: rawSettings.statusCode,
message: rawSettings.message,
stack: rawSettings.stack
} : {};
}
const {
statusCode = 500,
message = "An error has occurred.",
stack
} = settings;
super(message);
this.statusCode = statusCode;
if (stack) {
this.stack = stack;
}
}
toJSON() {
return {
name: this.name,
statusCode: this.statusCode,
message: this.message,
stack: this.stack
};
}
};
var VErrorPage = defineComponent({
name: "VErrorPage",
setup() {
const control = useVuePageControl();
return () => {
const pageError = control.pageError || new VuePageControlError();
return createVNode("div", {
"class": "v-error-page",
"style": {
padding: "32px",
fontSize: "16px",
lineHeight: "1.5"
}
}, [createVNode("h1", {
"class": "v-error-page__status-code",
"style": {
fontSize: "3em",
margin: "16px 0"
}
}, [pageError.statusCode]), createVNode("p", {
"class": "v-error-page__message",
"style": {
margin: "1em 0"
}
}, [pageError.message]), createVNode("pre", {
"class": "v-error-page__stack",
"style": {
whiteSpace: "pre-wrap"
}
}, [createVNode("code", {
"style": {
fontFamily: "monospace, monospace",
fontSize: "0.75em"
}
}, [pageError.stack])])]);
};
}
});
// src/composables/page-control.ts
function extractPrefetch(Component2) {
if (!isComponentCustomOptions(Component2)) return;
let { prefetch, middleware = [] } = Component2;
if (!Array.isArray(middleware)) {
middleware = [middleware];
}
const { prefetchHandler } = Component2;
if (prefetch && typeof prefetch === "object") {
prefetch = prefetch.prefetch;
}
if (typeof prefetch === "function" || middleware.length) {
return {
prefetch,
prefetchHandler,
middleware
};
}
}
function updateWatchQueryOption(Component2, queries) {
if (!isComponentCustomOptions(Component2)) return;
if (Component2.watchQuery === true) return;
Component2.watchQuery = Component2.watchQuery || [];
const { watchQuery } = Component2;
for (const query of queries) {
if (!watchQuery.includes(query)) {
watchQuery.push(query);
}
}
}
function extractRouteMatchedItemsWithPrefetch(control, to, from) {
const extracted = [];
const fromMatched = extractRouteMatchedItems(from);
const fromItems = {};
fromMatched.forEach((_item) => {
const fromKey = routeKeyWithWatchQueryByRouteItem(from, _item);
fromItems[fromKey] = _item;
});
const matched = extractRouteMatchedItems(to);
matched.forEach((_item) => {
const prefetchSettings = extractPrefetch(_item.Component);
if (!prefetchSettings) return;
const { prefetchHandler, middleware } = prefetchSettings;
let { prefetch } = prefetchSettings;
const pageKey = routeKeyWithWatchQueryByRouteItem(to, _item);
const fromItem = fromItems[pageKey];
let forcePrefetch;
if (prefetchHandler) {
const ctx = {
control,
to,
from,
matched: {
from: fromItem,
to: _item
},
pageKey
};
forcePrefetch = prefetchHandler(ctx);
}
setForcePrefetchStates(pageKey, forcePrefetch || false);
if (!forcePrefetch && fromItem) {
prefetch = void 0;
}
function updateQueries(queries) {
updateWatchQueryOption(_item.Component, queries);
}
extracted.push({ ..._item, prefetch, updateQueries, middleware });
});
return {
extracted,
matched
};
}
function createMiddleware(source) {
return source;
}
var DEFAULT_REDIRECT_STATUS = 302;
var STATE_BUCKET_SYMBOL = "pgc-state";
var QUERY_BUCKET_SYMBOL = "pgc-query";
function resolveRawVuePageControlRedirectSpec(source) {
if (typeof source === "string") {
source = { path: source };
}
const { statusCode = DEFAULT_REDIRECT_STATUS, query, hash } = source;
const { path } = source;
const { name: name3, params } = source;
return {
statusCode,
query,
hash,
path,
name: name3,
params
};
}
var ERROR_STATE_NAME = "__vpe__";
var VuePageControl = class extends EV {
app;
router;
cookies;
RouterLink;
useLink;
_from = ref(null);
_to = ref(null);
_initialState;
_beforeRoute = ref(null);
_route;
_stopFn;
_prefetchQueues = ref([]);
_initialStateConsumed = false;
_runningQueues;
_preftechProgress;
_suspenseComponents = ref([]);
_err = ref(null);
_ErrorComponent;
request;
response;
_serverRedirect;
_writeResponse;
isClient;
middleware;
_redirectSpec;
_providedMap = /* @__PURE__ */ new Map();
_transitioning = ref(false);
_serverRedirected = ref(false);
get isServer() {
return !this.isClient;
}
get serverRedirected() {
return this._serverRedirected.value;
}
get from() {
return this._from.value;
}
get to() {
return this._to.value;
}
get initialState() {
return this._initialState;
}
get beforeRoute() {
return this._beforeRoute.value;
}
get route() {
return this._route.value;
}
get prefetchQueues() {
return this._prefetchQueues.value.map((g) => g());
}
get initialStateConsumed() {
return this._initialStateConsumed;
}
get runningQueues() {
return this._runningQueues.value;
}
get preftechProgress() {
return this._preftechProgress.value;
}
get suspenseComponents() {
return this._suspenseComponents.value.map((g) => g());
}
get pageError() {
const _err = this._err.value;
return _err && _err();
}
get ErrorComponent() {
return this._ErrorComponent;
}
get transitioning() {
return this._transitioning.value;
}
constructor(settings) {
super();
const {
app,
router,
initialState = {},
initialRoute,
ErrorComponent,
request,
response,
serverRedirect,
writeResponse,
middleware = []
} = settings;
this.app = app;
this.router = router;
this.request = request;
this.response = response;
this.isClient = typeof window !== "undefined";
this.RouterLink = settings.RouterLink || RouterLink;
this.useLink = settings.useLink || useLink;
const cookiesContext = this.isClient ? document : { req: request, res: response };
this.cookies = new Cookies(cookiesContext, { bucket: reactive({}) });
this.middleware = middleware;
this._serverRedirect = serverRedirect;
this._writeResponse = writeResponse;
this._ErrorComponent = ErrorComponent || VErrorPage;
initialState[STATE_BUCKET_SYMBOL] = initialState[STATE_BUCKET_SYMBOL] || {};
initialState[QUERY_BUCKET_SYMBOL] = initialState[QUERY_BUCKET_SYMBOL] || {};
initialState[STATE_BUCKET_SYMBOL] = reactive(
initialState[STATE_BUCKET_SYMBOL]
);
this._initialState = initialState;
this._route = ref(initialRoute);
this._runningQueues = computed(
() => this.prefetchQueues.filter((q) => q.running)
);
this._preftechProgress = computed(() => {
const { prefetchQueues, runningQueues } = this;
const total = prefetchQueues.length;
const resolved = total - runningQueues.length;
const per = total === 0 ? 0 : resolved / total;
return {
total,
resolved,
per,
finished: resolved >= total
};
});
[
"_handleBeforeResolve",
"_handleAfterEach",
"setInitialState",
"injectData",
"onSuspensePending",
"redirect"
].forEach((fn) => {
const _fn = this[fn];
this[fn] = _fn.bind(this);
});
if (IN_WINDOW) {
const initialError = this.consumeInitialState(ERROR_STATE_NAME);
if (initialError) {
this._setPageError(initialError);
}
}
this._init();
}
/** @private */
_getQueryBucket(provideKey) {
const obj = this.initialState[QUERY_BUCKET_SYMBOL];
let bucket = obj[provideKey];
if (!bucket) {
bucket = [];
obj[provideKey] = bucket;
}
return bucket;
}
/** @private */
_pushQueryToBucket(provideKey, query) {
const bucket = this._getQueryBucket(provideKey);
if (!bucket.includes(query)) {
bucket.push(query);
}
}
async initState(key, initializer) {
const bucket = this.initialState[STATE_BUCKET_SYMBOL];
let data = bucket[key];
if (data === void 0) {
data = typeof initializer === "function" ? await initializer() : initializer;
}
bucket[key] = reactive(data);
this.app.provide(key, data);
return data;
}
// eslint-disable-line prettier/prettier
useState(key, defaultValue) {
const bucket = this.initialState[STATE_BUCKET_SYMBOL];
let data = bucket[key];
if (data === void 0) {
data = typeof defaultValue === "function" ? defaultValue() : defaultValue;
}
return data;
}
provide(key, value) {
this.app.provide(key, value);
this._providedMap.set(key, value);
}
// eslint-disable-line prettier/prettier
inject(key, defaultValue, treatDefaultAsFactory) {
let value = this._providedMap.get(key);
if (value === void 0 && defaultValue !== void 0) {
value = treatDefaultAsFactory && typeof defaultValue === "function" ? defaultValue() : defaultValue;
}
return value;
}
addMiddleware(middleware) {
this.middleware.push(middleware);
}
writeResponse(params) {
const { _writeResponse } = this;
if (!_writeResponse) {
throw new Error("missing provided writeResponse function.");
}
return _writeResponse(params);
}
onSuspensePending(Component2) {
if (!this.suspenseComponents.includes(Component2)) {
this._suspenseComponents.value.push(() => Component2);
}
}
onSuspenseResolved(Component2) {
const index = this.suspenseComponents.indexOf(Component2);
if (index !== -1) {
this._suspenseComponents.value.splice(index, 1);
}
}
setInitialState(key, data) {
if (this.initialState) {
this.initialState[key] = data;
}
}
consumeInitialState(key) {
const { initialState } = this;
if (!initialState) return;
const v = initialState[key];
delete initialState[key];
return v;
}
injectData(key, defaultData, allowNull) {
const queue = this.prefetchQueues.find(
({ _provideKey }) => _provideKey === key
);
const data = queue && queue.resolvedData;
const result = data === void 0 ? defaultData : data;
if (!allowNull && result == null) {
throw new Error(`missing provided data. "${key}"`);
}
return result;
}
_stop() {
if (this._stopFn) {
this._stopFn();
delete this._stopFn;
}
}
_init() {
this._stop();
const fns = [];
fns.push(
this.router.beforeResolve(this._handleBeforeResolve),
this.router.afterEach(this._handleAfterEach)
);
this._stopFn = () => {
fns.forEach((fn) => fn());
delete this._stopFn;
};
}
async _handleAfterEach(to, from) {
delete this._redirectSpec;
this._transitioning.value = false;
const { router } = this;
this._beforeRoute.value = router.resolve(from);
this._route.value = router.resolve(to);
this.emit("finish", this.preftechProgress);
}
_closePrefetchs() {
this.prefetchQueues.forEach((queue) => queue.close());
this._prefetchQueues.value = [];
}
redirect(redirectSpec) {
this._redirectSpec = resolveRawVuePageControlRedirectSpec(redirectSpec);
if (!this.transitioning) {
this._triggerRedirect();
}
}
_triggerRedirect() {
const { _redirectSpec } = this;
if (!_redirectSpec) return;
let { path, query, hash } = _redirectSpec;
const { statusCode, name: name3, params } = _redirectSpec;
if (name3) {
const tmp = this.router.resolve({ name: name3, params, query, hash });
path = tmp.path;
query = tmp.query;
hash = tmp.hash;
}
const queryStr = query ? stringifyQuery(query) : query;
const queryAppends = queryStr ? path.includes("?") ? `&${queryStr}` : `?${queryStr}` : "";
let fullPath = `${path}${queryAppends}`;
if (/^\//.test(fullPath)) {
const { base } = this.router.options.history;
fullPath = `${base}/${fullPath.replace(/^\//, "")}`;
}
if (this.isClient) {
const isInSite = /(^[.]{1,2}\/)|(^\/(?!\/))/.test(fullPath);
if (isInSite) {
this.router.replace({ path, query, hash });
} else {
window.location.replace(fullPath);
}
return false;
}
if (!this._serverRedirect) {
throw new Error("required server redirect function.");
}
this._serverRedirected.value = true;
this._serverRedirect(fullPath, statusCode);
}
async _handleBeforeResolve(to, from) {
this._to.value = to;
this._from.value = from;
this._closePrefetchs();
delete this._redirectSpec;
if (IN_WINDOW && !this.initialStateConsumed && this.pageError) {
this._initialStateConsumed = true;
return;
}
try {
const { extracted, matched } = extractRouteMatchedItemsWithPrefetch(
this,
to,
from
);
if (!matched.length) {
throw new VuePageControlError({
statusCode: 404,
message: "This page could not be found"
});
}
this._transitioning.value = true;
const allMiddleware = [...this.middleware];
extracted.forEach(({ middleware }) => {
allMiddleware.push(...middleware);
});
for (const middlewareFn of allMiddleware) {
await middlewareFn(this);
if (this._redirectSpec) {
break;
}
}
if (this._redirectSpec) {
return this._triggerRedirect();
}
const requiredItems = extracted.filter(
(e) => !!e.prefetch
);
if (!requiredItems.length) {
this._initialStateConsumed = true;
this._deletePageError();
return;
}
const queueSetups = [];
let queues = requiredItems.map((item) => {
const queue = new VuePagePrefetchQueue(this, item, to);
if (queue._setupPromise) {
queueSetups.push(queue._setupPromise);
}
return queue;
});
this._prefetchQueues.value = queues.map((q) => () => q);
this.emit("start", this.preftechProgress);
await Promise.all(queueSetups);
if (this._redirectSpec) {
return this._triggerRedirect();
}
queues.forEach((queue) => {
const { item, provideKey } = queue;
if (provideKey) {
item.updateQueries(this._getQueryBucket(provideKey));
}
});
const providePromises = [];
queues = queues.filter(({ _providePromise }) => {
if (!_providePromise) return false;
providePromises.push(_providePromise);
return true;
});
this._prefetchQueues.value = queues.map((q) => () => q);
await Promise.all(providePromises);
if (this._redirectSpec) {
return this._triggerRedirect();
}
this._initialStateConsumed = true;
this._deletePageError();
} catch (_err) {
this._setPageError(_err);
}
}
_deletePageError() {
this._err.value = null;
}
_setPageError(_err, withEmit = true) {
const err = new VuePageControlError(_err);
this._err.value = () => err;
if (!IN_WINDOW) {
this.setInitialState(ERROR_STATE_NAME, err.toJSON());
this._writeStates(err.statusCode);
}
withEmit && this.emit("error", err);
return err;
}
_writeStates(status) {
if (IN_WINDOW || !this.response) return;
if (this.response.headersSent) return;
this.response.statusCode = status;
this.writeResponse({
status
});
}
};
var VuePagePrefetchQueue = class extends EV {
control;
item;
route;
_resolved = ref(false);
_canceled = ref(false);
_running;
_resolvedData;
/** @private */
_providePromise;
/** @private */
_provideKey;
/** @private */
_setupPromise;
get resolved() {
return this._resolved.value;
}
get canceled() {
return this._canceled.value;
}
get provideKey() {
return this._provideKey;
}
get resolvedData() {
return this._resolvedData;
}
get params() {
return this.route.params;
}
get query() {
return this.route.query;
}
get running() {
return this._running.value;
}
constructor(control, item, to) {
super();
this.control = control;
this.item = item;
this.route = to;
["close", "cancel", "provide", "injectOtherQueue", "getQuery"].forEach((fn) => {
const _fn = this[fn];
this[fn] = _fn.bind(this);
});
this._running = computed(() => !this.resolved && !this.canceled);
const prefetchFn = typeof item.prefetch === "function" ? item.prefetch : item.prefetch.prefetch;
this._setupPromise = prefetchFn(this);
}
// eslint-disable-next-line no-dupe-class-members
getQuery(key, type = String, defaultValue) {
const { provideKey } = this;
if (!provideKey) {
throw new Error("missing provideKey");
}
this.control._pushQueryToBucket(provideKey, key);
return getRouteQuery(this.query, key, type, defaultValue);
}
close() {
this._canceled.value = true;
this.emit("close", void 0);
this.offAll();
delete this._providePromise;
delete this._setupPromise;
delete this._resolvedData;
delete this.item;
delete this.control;
}
cancel() {
return this.close();
}
provide(key, data) {
this._provideKey = key;
let promise;
if (IN_WINDOW && !this.control.initialStateConsumed) {
const consumed = this.control.consumeInitialState(key);
promise = Promise.resolve(consumed);
} else if (typeof data !== "function") {
promise = Promise.resolve(data);
} else {
const payload = data(this);
promise = isPromise(payload) ? payload : Promise.resolve(payload);
}
this._providePromise = promise.then((v) => {
this._resolved.value = true;
this._resolvedData = v;
if (!IN_WINDOW) {
this.control.setInitialState(key, v);
}
return v;
});
return this._providePromise;
}
injectOtherQueue(key, defaultData) {
const queue = this.control.prefetchQueues.find(
({ _provideKey }) => _provideKey === key
);
if (!queue || !queue._providePromise)
return Promise.resolve(defaultData);
return queue._providePromise.then((value) => {
const v = value === void 0 ? defaultData : value;
return v;
});
}
};
function createPrefetch(key, data) {
const prefetch = function prefetch2(queue) {
return queue.provide(key, data);
};
function inject3(defaultValue, allowNull) {
const pageControl = useVuePageControl();
return pageControl.injectData(key, defaultValue, allowNull);
}
return { key, prefetch, inject: inject3 };
}
// src/composables/plugin.ts
function installVuePageControl(settings) {
const pageControl = new VuePageControl(settings);
settings.app.provide(VuePageControlInjectionKey, pageControl);
settings.app.config.globalProperties.$vpc = pageControl;
return pageControl;
}
function useState(key, defaultValue) {
return inject(key, defaultValue, true);
}
function provideState(key, value) {
return provide(key, value);
}
var DEFAULT_TRANSITION = "page";
var VPage = defineComponent({
name: "VPage",
props: {
pageKey: {
type: [Function, String],
default: null
}
},
setup(props) {
const currentComponent = ref(null);
const transition = computed(() => {
const vnode = currentComponent.value;
if (!vnode) return DEFAULT_TRANSITION;
return DEFAULT_TRANSITION;
});
return () => createVNode(RouterView, null, {
default: withCtx((routeProps) => {
const {
Component: Component2
} = routeProps;
let key = generateRouteKeyWithWatchQuery(routeProps, props.pageKey);
if (consumeForcePrefetchStates(key)) {
key = `${key}:${Date.now()}`;
}
return [createVNode(Transition, {
"name": transition.value,
"mode": "out-in",
"onEnter": (ev) => {
ScrollBehaviorMessenger.trigger();
},
"onEnterCancelled": () => {
ScrollBehaviorMessenger.release();
}
}, {
default: () => [Component2 ? h(Component2, {
key
}) : void 0]
})];
})
});
}
});
var VPageProgress = defineComponent({
name: "VPageProgress",
props: {
color: String,
failedColor: String,
height: NumberishPropOption,
throttle: NumberishPropOption,
duration: NumberishPropOption,
continuous: Boolean,
rtl: Boolean
},
setup(props) {
useVuePageControl({
onStart: () => {
start();
},
onFinish: () => {
finish();
},
onError: () => {
canSucceed.value = false;
}
});
let _timer = null;
let _throttle = null;
let _cut;
const show = ref(false);
const percent = ref(0);
const skipTimerCount = ref(0);
const canSucceed = ref(true);
const throttle = computed(() => resolveNumberish(props.throttle, 200));
const duration = computed(() => resolveNumberish(props.duration, 3e3));
const reversed = ref(props.rtl);
const continuous = computed(() => props.continuous);
const color = computed(() => props.color || "black");
const failedColor = computed(() => props.failedColor || "red");
const height = computed(() => resolveNumberish(props.height, 2));
const width = computed(() => `${percent.value}%`);
const styles = computed(() => ({
position: "fixed",
top: 0,
left: props.rtl ? "auto" : 0,
right: 0,
zIndex: 999999,
width: width.value,
height: `${height.value}px`,
background: canSucceed.value ? color.value : failedColor.value,
opacity: 1,
// transition: `width ${duration.value}ms`,
transition: `width 0.1s, opacity 0.4s`
}));
function clear() {
_timer !== null && clearInterval(_timer);
_throttle !== null && clearTimeout(_throttle);
_timer = null;
}
function increase(num) {
percent.value = Math.min(100, Math.floor(percent.value + num));
}
function decrease(num) {
percent.value = Math.max(0, Math.floor(percent.value - num));
}
function finish() {
percent.value = reversed.value ? 0 : 100;
hide();
}
function hide() {
clear();
window.setTimeout(() => {
show.value = false;
nextTick(() => {
percent.value = 0;
reversed.value = false;
});
}, 500);
}
function start() {
clear();
percent.value = 0;
reversed.value = false;
skipTimerCount.value = 0;
canSucceed.value = true;
if (throttle.value) {
_throttle = window.setTimeout(() => startTimer(), throttle.value);
} else {
startTimer();
}
}
function startTimer() {
if (!show.value) {
show.value = true;
}
_timer = window.setInterval(() => {
if (typeof _cut === "undefined") {
_cut = 1e4 / Math.floor(duration.value);
}
if (skipTimerCount.value > 0) {
skipTimerCount.value--;
return;
}
if (reversed.value) {
decrease(_cut);
} else {
increase(_cut);
}
if (continuous.value && (percent.value >= 100 || percent.value <= 0)) {
skipTimerCount.value = 1;
reversed.value = !reversed.value;
}
}, 100);
}
return () => {
if (!show.value) return;
return createVNode("div", {
"class": "v-page-progress",
"style": styles.value
}, null);
};
}
});
var VPageLink = defineComponent({
name: "VPageLink",
setup(_props, ctx) {
const vpc = useVuePageControl();
return () => h(vpc.RouterLink, ctx.attrs, ctx.slots);
}
});
var VPageRoot = defineComponent({
name: "VPageRoot",
setup(props, ctx) {
const control = useVuePageControl();
useRouter();
return () => {
if (control.serverRedirected) {
return [];
}
const {
pageError
} = control;
const content = pageError ? h(control.ErrorComponent, {
key: "error"
}) : ctx.slots.default?.();
return createVNode(Fragment, null, [content, createVNode(ClientOnly, null, {
default: () => [createVNode(VPageProgress, null, null)]
})]);
};
}
});
export { ScrollBehaviorMessenger, VErrorPage, VPage, VPageLink, VPageProgress, VPageRoot, VuePageControl, VuePageControlError, VuePageControlInjectionKey, VuePagePrefetchQueue, createMiddleware, createPrefetch, createScrollBehavior, extractRouteMatchedItemsWithPrefetch, getSuspenseRouteBucket, installVuePageControl, isVuePageControlError, provideState, useState, useVuePageControl };
//# sourceMappingURL=vue-page.mjs.map
//# sourceMappingURL=vue-page.mjs.map