UNPKG

vue-app-sdk

Version:
349 lines (348 loc) 11 kB
"use strict"; var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); const index = require("../utils/index2.cjs"); require("../vendors/nice-fns/dist/compactObject.cjs"); require("../vendors/nice-fns/dist/pascalCase.cjs"); const vue = require("vue"); const vueRouter = require("vue-router"); const SDK = require("./SDK.cjs"); const pick = require("../vendors/lodash-es/pick.cjs"); const isFunction = require("../vendors/lodash-es/isFunction.cjs"); var NavigationDirection = /* @__PURE__ */ ((NavigationDirection2) => { NavigationDirection2["forward"] = "forward"; NavigationDirection2["backward"] = "backward"; NavigationDirection2["unchanged"] = "unchanged"; return NavigationDirection2; })(NavigationDirection || {}); const ROUTER_ID = Symbol("router"); const DETAILS_CHANGED_FLAG = Symbol("details changed flag"); const DETAILS_RECORD_KEY = Symbol("details record"); function useRouteDetails() { const sdk = SDK.useAppSDK(); const detailsRecord = vue.inject(DETAILS_RECORD_KEY); const { getDetails, makeDetails } = sdk.getPlugin(ROUTER_ID); const { fullPath } = vueRouter.useRoute(); const details = vue.shallowReactive(makeDetails(getDetails(fullPath))); vue.watch(detailsRecord, (record) => { const newDetails = record[fullPath]; if (!newDetails) { return; } if (newDetails[DETAILS_CHANGED_FLAG] !== details[DETAILS_CHANGED_FLAG]) { index.assign(details, newDetails); } }); return vue.shallowReadonly(details); } function initDetailsWatcher(onCleanup) { const routeDetails = useRouteDetails(); let watcher = /* @__PURE__ */ new Map(); const unwatch = vue.watch( () => routeDetails, (details) => { var _a, _b, _c; if (watcher && details.from) { (_a = watcher.get(details.from.path)) == null ? void 0 : _a.forEach((fn) => fn(details.data, details)); (_b = watcher.get(details.from.fullPath)) == null ? void 0 : _b.forEach((fn) => fn(details.data, details)); (_c = watcher.get(details.from.name)) == null ? void 0 : _c.forEach((fn) => fn(details.data, details)); } }, { deep: true } ); function cleanup() { if (watcher) { unwatch(); watcher.clear(); watcher = null; onCleanup(); } } return { set(key, callback) { if (watcher && isFunction.default(callback)) { if (!watcher.has(key)) { watcher.set(key, []); } watcher.get(key).push(callback); } }, delete(key, callback) { if (watcher && watcher.has(key)) { const callbacks = watcher.get(key); const index2 = callbacks.indexOf(callback); if (index2 > -1) { callbacks.splice(index2, 1); } if (!callbacks.length) { watcher.delete(key); } } if (watcher && !watcher.size) { cleanup(); } } }; } const DETAILS_WATCHER_KEY = Symbol("details watcher key"); function watchDetails(fromKey, callback) { const vm = vue.getCurrentInstance(); if (!vm) { index.logger.error(`watchDetails() 必须在组件 setup() 作用域内调用!`); } if (!vm || !vm.proxy) { return () => { }; } const proxy = vm.proxy; proxy[DETAILS_WATCHER_KEY] || (proxy[DETAILS_WATCHER_KEY] = initDetailsWatcher(() => { proxy[DETAILS_WATCHER_KEY] = null; })); const watcher = proxy[DETAILS_WATCHER_KEY]; watcher.set(fromKey, callback); const unwatch = () => watcher.delete(fromKey, callback); vue.onBeforeUnmount(unwatch); return unwatch; } const DEFAULT_DETAILS_DATA = Symbol("details data"); const defaultIdentifyDirection = ({ latestPosition, currentPosition }) => { if (latestPosition == null || currentPosition == null) { return "forward"; } if (currentPosition < latestPosition) { return "backward"; } if (currentPosition === latestPosition) { return "unchanged"; } return "forward"; }; class Router { constructor(options = {}) { __publicField(this, "id", ROUTER_ID); /** * 最近记录的位置 */ __publicField(this, "latestPosition", null); /** * 路由详情记录 */ __publicField(this, "_detailsRecord"); /** * 是否正在导航 */ __publicField(this, "_isNavigating", vue.ref(false)); /** * 锁定 `beforeOnce` */ __publicField(this, "_onceLocked", false); /** * 详情数据 */ __publicField(this, "_detailsData", DEFAULT_DETAILS_DATA); /** * 设置详情数据 * @param data 详情数据 */ __publicField(this, "_setDetailsData", (data) => { this._detailsData = data; }); /** * 消费详情数据 */ __publicField(this, "_consumeDetailsData", () => { const isChanged = this._detailsData !== DEFAULT_DETAILS_DATA; const data = isChanged ? this._detailsData : void 0; return { isChanged, data }; }); /** * 获取路由详情 * @param fullPath 完整路径 * @returns 路由详情 */ __publicField(this, "getDetails", (fullPath) => { return this._detailsRecord.value[fullPath]; }); /** * 清理所有详情记录 */ __publicField(this, "clearDetailsRecord", () => { this._updateDetailsRecord({}); }); /** * 创建路由详情 * @param details 需要合并的路由详情 * @param increment 需要自增路由详情变更次数 */ __publicField(this, "makeDetails", (details, increment = false) => { const count = details ? details[DETAILS_CHANGED_FLAG] || 0 : 0; const result = index.assign( { from: void 0, data: void 0, [DETAILS_CHANGED_FLAG]: count }, details ); if (result.from) { result.from = Object.freeze(pick.default(result.from, ["path", "name", "fullPath", "hash"])); } if (increment) { result[DETAILS_CHANGED_FLAG]++; } return result; }); /** * 获取当前导航位置 * @returns 当前导航位置 */ __publicField(this, "getCurrentPosition", () => { var _a; return (_a = history.state) == null ? void 0 : _a.position; }); __publicField(this, "install", (sdk) => { const { app, router } = sdk; app.provide(DETAILS_RECORD_KEY, this._detailsRecord); router.beforeEach(() => { this._isNavigating.value = true; }); router.afterEach(() => { this._isNavigating.value = false; }); Object.defineProperty(router, "isNavigating", { enumerable: true, get: () => this._isNavigating.value }); router.afterEach((to, from, failure) => { if (vueRouter.isNavigationFailure(failure)) { return; } const latestPosition = this.latestPosition ?? this.getCurrentPosition(); const currentPosition = this.getCurrentPosition(); const direction = this.identifyDirection({ to, from, latestPosition, currentPosition }); switch (direction) { case "backward": sdk.callHookSync("sdk:router:backward", to, from); break; case "unchanged": sdk.callHookSync("sdk:router:replace", to, from); break; default: sdk.callHookSync("sdk:router:forward", to, from); break; } sdk.callHookSync("sdk:router:navigate", direction, to, from); this.latestPosition = currentPosition; }); router.beforeOnce = (guard) => { return router.beforeEach((...args) => { if (this._onceLocked) { return; } this._onceLocked = true; return guard(...args); }); }; router.afterEach(() => { this._onceLocked = false; }); const { _setDetailsData, _consumeDetailsData } = this; router.pushWithData = function(to, data) { _setDetailsData(data); return router.push(to); }; router.replaceWithData = function(to, data) { _setDetailsData(data); return router.replace(to); }; router.goWithData = function(delta, data) { _setDetailsData(data); return router.go(delta); }; router.forwardWithData = function(data) { return router.goWithData(1, data); }; router.backWithData = function(data) { return router.goWithData(-1, data); }; router.clearDetailsRecord = this.clearDetailsRecord; sdk.hook("sdk:router:navigate", (direction, to, from) => { const detailsRecord = this._detailsRecord.value; const { isChanged, data } = _consumeDetailsData(); const updateDetails = () => { detailsRecord[to.fullPath] = this.makeDetails( index.assign({}, detailsRecord[to.fullPath], { from, data }), true ); }; switch (direction) { case "unchanged": if (isChanged) { updateDetails(); } break; case "backward": delete detailsRecord[from.fullPath]; if (isChanged) { updateDetails(); } break; default: updateDetails(); break; } this._updateDetailsRecord(detailsRecord); _setDetailsData(DEFAULT_DETAILS_DATA); }); sdk.hook("sdk:cleanup", this.clearDetailsRecord); const unwatch = vue.watch(this._detailsRecord, (record) => sdk.callHookSync("sdk:router:detailsRecordChange", record)); return () => { this.latestPosition = null; this._onceLocked = false; this._detailsData = DEFAULT_DETAILS_DATA; unwatch(); }; }); this.options = options; const { persistentDetails = true, storage, storageKey = "__VUE_APP_SDK__ROUTE_DETAILS_RECORD__", window } = options; this._detailsRecord = index.createPersistentRef( { persistent: persistentDetails, window, storage, storageKey, shallow: true, value: {} }, true ); } /** * 识别导航方向 * @readonly */ get identifyDirection() { return this.options.identifyDirection || defaultIdentifyDirection; } /** * 更新详情记录 * @param value 详情记录 */ _updateDetailsRecord(value) { this._detailsRecord.value = value; if (this._detailsRecord.value === value) { vue.triggerRef(this._detailsRecord); } } } exports.NavigationDirection = NavigationDirection; exports.ROUTER_ID = ROUTER_ID; exports.Router = Router; exports.useRouteDetails = useRouteDetails; exports.watchDetails = watchDetails;