UNPKG

15.8 kBJavaScriptView Raw
1import { getHomePage, getCurrentPage, addLeadingSlash, stripBasename, stripTrailing, requestAnimationFrame, eventCenter, Current } from '@tarojs/runtime';
2import queryString from 'query-string';
3import { bindPageResize } from '../events/resize.js';
4import { bindPageScroll } from '../events/scroll.js';
5import { setHistory, history } from '../history.js';
6import { loadAnimateStyle, loadRouterStyle } from '../style.js';
7import { routesAlias } from '../utils/index.js';
8import NavigationBarHandler from './navigation-bar.js';
9import stacks from './stack.js';
10
11/* eslint-disable dot-notation */
12class PageHandler {
13 constructor(config, history) {
14 this.history = history;
15 this.defaultAnimation = { duration: 300, delay: 50 };
16 this.config = config;
17 this.homePage = getHomePage(this.routes[0].path, this.basename, this.customRoutes, this.config.entryPagePath);
18 this.originHomePage = this.config.entryPagePath || this.routes[0].path || this.basename;
19 this.mount();
20 this.navigationBarHandler = new NavigationBarHandler(this);
21 }
22 get currentPage() {
23 const routePath = getCurrentPage(this.routerMode, this.basename);
24 return routePath === '/' ? this.homePage : routePath;
25 }
26 get appId() { return this.config.appId || 'app'; }
27 get router() { return this.config.router || {}; }
28 get routerMode() { return this.router.mode || 'hash'; }
29 get customRoutes() { return this.router.customRoutes || {}; }
30 get routes() { return this.config.routes || []; }
31 get tabBarList() { var _a; return ((_a = this.config.tabBar) === null || _a === void 0 ? void 0 : _a.list) || []; }
32 get PullDownRefresh() { return this.config.PullDownRefresh; }
33 get animation() { var _a, _b; return (_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.animation) !== null && _b !== void 0 ? _b : this.defaultAnimation; }
34 get animationDelay() {
35 var _a;
36 return (typeof this.animation === 'object'
37 ? this.animation.delay
38 : this.animation
39 ? (_a = this.defaultAnimation) === null || _a === void 0 ? void 0 : _a.delay
40 : 0) || 0;
41 }
42 get animationDuration() {
43 var _a;
44 return (typeof this.animation === 'object'
45 ? this.animation.duration
46 : this.animation
47 ? (_a = this.defaultAnimation) === null || _a === void 0 ? void 0 : _a.duration
48 : 0) || 0;
49 }
50 set pathname(p) { this.router.pathname = p; }
51 get pathname() { return this.router.pathname; }
52 // Note: 把 pathname 转换为原始路径,主要是处理 customRoutes 和 basename
53 get originPathname() { return routesAlias.getOrigin(addLeadingSlash(stripBasename(this.pathname, this.basename))); }
54 get basename() { return this.router.basename || ''; }
55 get pageConfig() {
56 const homePage = addLeadingSlash(this.homePage);
57 return this.routes.find(r => {
58 const pagePath = addLeadingSlash(r.path);
59 return [pagePath, homePage].includes(this.originPathname);
60 });
61 }
62 isTabBar(pathname) {
63 var _a;
64 const routePath = addLeadingSlash(stripBasename(pathname, this.basename)).split('?')[0];
65 const pagePath = ((_a = Object.entries(this.customRoutes).find(([, target]) => {
66 if (typeof target === 'string') {
67 return target === routePath;
68 }
69 else if ((target === null || target === void 0 ? void 0 : target.length) > 0) {
70 return target.includes(routePath);
71 }
72 return false;
73 })) === null || _a === void 0 ? void 0 : _a[0]) || routePath;
74 return !!pagePath && this.tabBarList.some(t => stripTrailing(t.pagePath) === pagePath);
75 }
76 isDefaultNavigationStyle() {
77 var _a, _b;
78 let style = (_a = this.config.window) === null || _a === void 0 ? void 0 : _a.navigationStyle;
79 if (typeof ((_b = this.pageConfig) === null || _b === void 0 ? void 0 : _b.navigationStyle) === 'string') {
80 style = this.pageConfig.navigationStyle;
81 }
82 return style !== 'custom';
83 }
84 isSamePage(page) {
85 const routePath = stripBasename(this.pathname, this.basename);
86 const pagePath = stripBasename(page === null || page === void 0 ? void 0 : page.path, this.basename);
87 return pagePath.startsWith(routePath + '?');
88 }
89 get search() {
90 let search = '?';
91 if (this.routerMode === 'hash') {
92 const idx = location.hash.indexOf('?');
93 if (idx > -1) {
94 search = location.hash.slice(idx);
95 }
96 }
97 else {
98 search = location.search;
99 }
100 return search.substring(1);
101 }
102 get usingWindowScroll() {
103 var _a;
104 let usingWindowScroll = false;
105 if (typeof ((_a = this.pageConfig) === null || _a === void 0 ? void 0 : _a.usingWindowScroll) === 'boolean') {
106 usingWindowScroll = this.pageConfig.usingWindowScroll;
107 }
108 const win = window;
109 win.__taroAppConfig || (win.__taroAppConfig = {});
110 win.__taroAppConfig.usingWindowScroll = usingWindowScroll;
111 return usingWindowScroll;
112 }
113 getQuery(stamp = '', search = '', options = {}) {
114 search = search ? `${search}&${this.search}` : this.search;
115 const query = search
116 ? queryString.parse(search, { decode: false })
117 : {};
118 query.stamp = stamp;
119 return Object.assign(Object.assign({}, query), options);
120 }
121 mount() {
122 setHistory(this.history, this.basename);
123 this.pathname = history.location.pathname;
124 // Note: 注入页面样式
125 this.animation && loadAnimateStyle(this.animationDuration);
126 loadRouterStyle(this.tabBarList.length > 1, this.usingWindowScroll, this.router.enhanceAnimation);
127 }
128 onReady(page, onLoad = true) {
129 var _a;
130 const pageEl = this.getPageContainer(page);
131 if (pageEl && !(pageEl === null || pageEl === void 0 ? void 0 : pageEl['__isReady'])) {
132 const el = pageEl.firstElementChild;
133 const componentOnReady = el === null || el === void 0 ? void 0 : el['componentOnReady'];
134 if (componentOnReady) {
135 componentOnReady === null || componentOnReady === void 0 ? void 0 : componentOnReady().then(() => {
136 requestAnimationFrame(() => {
137 var _a;
138 (_a = page.onReady) === null || _a === void 0 ? void 0 : _a.call(page);
139 pageEl['__isReady'] = true;
140 });
141 });
142 }
143 else {
144 (_a = page.onReady) === null || _a === void 0 ? void 0 : _a.call(page);
145 pageEl['__isReady'] = true;
146 }
147 onLoad && (pageEl['__page'] = page);
148 }
149 }
150 load(page, pageConfig = {}, stampId, pageNo = 0) {
151 var _a, _b;
152 if (!page)
153 return;
154 // NOTE: 页面栈推入太晚可能导致 getCurrentPages 无法获取到当前页面实例
155 stacks.push(page);
156 const param = this.getQuery(stampId, '', page.options);
157 let pageEl = this.getPageContainer(page);
158 if (pageEl) {
159 pageEl.classList.remove('taro_page_shade');
160 this.isTabBar(this.pathname) && pageEl.classList.add('taro_tabbar_page');
161 this.isDefaultNavigationStyle() && pageEl.classList.add('taro_navigation_page');
162 this.addAnimation(pageEl, pageNo === 0);
163 (_a = page.onShow) === null || _a === void 0 ? void 0 : _a.call(page);
164 this.navigationBarHandler.load();
165 this.bindPageEvents(page, pageConfig);
166 this.triggerRouterChange();
167 }
168 else {
169 // FIXME 在 iOS 端快速切换页面时,可能不会执行回调注入对应类名导致 TabBar 白屏
170 (_b = page.onLoad) === null || _b === void 0 ? void 0 : _b.call(page, param, () => {
171 var _a;
172 pageEl = this.getPageContainer(page);
173 this.isTabBar(this.pathname) && (pageEl === null || pageEl === void 0 ? void 0 : pageEl.classList.add('taro_tabbar_page'));
174 this.isDefaultNavigationStyle() && (pageEl === null || pageEl === void 0 ? void 0 : pageEl.classList.add('taro_navigation_page'));
175 this.addAnimation(pageEl, pageNo === 0);
176 (_a = page.onShow) === null || _a === void 0 ? void 0 : _a.call(page);
177 this.navigationBarHandler.load();
178 this.onReady(page, true);
179 this.bindPageEvents(page, pageConfig);
180 this.triggerRouterChange();
181 });
182 }
183 }
184 unload(page, delta = 1, top = false) {
185 var _a, _b, _c;
186 if (!page)
187 return;
188 stacks.delta = --delta;
189 stacks.pop();
190 if (this.animation && top) {
191 if (this.unloadTimer) {
192 clearTimeout(this.unloadTimer);
193 (_b = (_a = this.lastUnloadPage) === null || _a === void 0 ? void 0 : _a.onUnload) === null || _b === void 0 ? void 0 : _b.call(_a);
194 this.unloadTimer = null;
195 }
196 this.lastUnloadPage = page;
197 const pageEl = this.getPageContainer(page);
198 pageEl === null || pageEl === void 0 ? void 0 : pageEl.classList.remove('taro_page_stationed');
199 pageEl === null || pageEl === void 0 ? void 0 : pageEl.classList.remove('taro_page_show');
200 if (pageEl) {
201 pageEl.style.zIndex = '1';
202 }
203 this.unloadTimer = setTimeout(() => {
204 var _a, _b;
205 this.unloadTimer = null;
206 (_b = (_a = this.lastUnloadPage) === null || _a === void 0 ? void 0 : _a.onUnload) === null || _b === void 0 ? void 0 : _b.call(_a);
207 eventCenter.trigger('__taroPageOnShowAfterDestroyed');
208 }, this.animationDuration);
209 }
210 else {
211 const pageEl = this.getPageContainer(page);
212 pageEl === null || pageEl === void 0 ? void 0 : pageEl.classList.remove('taro_page_stationed');
213 pageEl === null || pageEl === void 0 ? void 0 : pageEl.classList.remove('taro_page_show');
214 (_c = page === null || page === void 0 ? void 0 : page.onUnload) === null || _c === void 0 ? void 0 : _c.call(page);
215 setTimeout(() => {
216 eventCenter.trigger('__taroPageOnShowAfterDestroyed');
217 }, 0);
218 }
219 if (delta >= 1)
220 this.unload(stacks.last, delta);
221 }
222 show(page, pageConfig = {}, pageNo = 0) {
223 var _a, _b;
224 if (!page)
225 return;
226 const param = this.getQuery(page['$taroParams']['stamp'], '', page.options);
227 let pageEl = this.getPageContainer(page);
228 if (pageEl) {
229 pageEl.classList.remove('taro_page_shade');
230 this.addAnimation(pageEl, pageNo === 0);
231 (_a = page.onShow) === null || _a === void 0 ? void 0 : _a.call(page);
232 this.navigationBarHandler.load();
233 this.bindPageEvents(page, pageConfig);
234 this.triggerRouterChange();
235 }
236 else {
237 (_b = page.onLoad) === null || _b === void 0 ? void 0 : _b.call(page, param, () => {
238 var _a;
239 pageEl = this.getPageContainer(page);
240 this.addAnimation(pageEl, pageNo === 0);
241 (_a = page.onShow) === null || _a === void 0 ? void 0 : _a.call(page);
242 this.navigationBarHandler.load();
243 this.onReady(page, false);
244 this.bindPageEvents(page, pageConfig);
245 this.triggerRouterChange();
246 });
247 }
248 }
249 hide(page, animation = false) {
250 var _a, _b, _c, _d, _e, _f, _g, _h;
251 if (!page)
252 return;
253 // NOTE: 修复多页并发问题,此处可能因为路由跳转过快,执行时页面可能还没有创建成功
254 const pageEl = this.getPageContainer(page);
255 if (pageEl) {
256 if (animation) {
257 if (this.hideTimer) {
258 clearTimeout(this.hideTimer);
259 this.hideTimer = null;
260 (_c = (_b = (_a = this.lastHidePage) === null || _a === void 0 ? void 0 : _a.classList) === null || _b === void 0 ? void 0 : _b.add) === null || _c === void 0 ? void 0 : _c.call(_b, 'taro_page_shade');
261 }
262 this.lastHidePage = pageEl;
263 this.hideTimer = setTimeout(() => {
264 this.hideTimer = null;
265 pageEl.classList.add('taro_page_shade');
266 }, this.animationDuration + this.animationDelay);
267 (_d = page.onHide) === null || _d === void 0 ? void 0 : _d.call(page);
268 }
269 else {
270 if (this.hideTimer) {
271 clearTimeout(this.hideTimer);
272 this.hideTimer = null;
273 (_g = (_f = (_e = this.lastHidePage) === null || _e === void 0 ? void 0 : _e.classList) === null || _f === void 0 ? void 0 : _f.add) === null || _g === void 0 ? void 0 : _g.call(_f, 'taro_page_shade');
274 }
275 (_h = page.onHide) === null || _h === void 0 ? void 0 : _h.call(page);
276 pageEl.classList.add('taro_page_shade');
277 this.lastHidePage = pageEl;
278 }
279 }
280 else {
281 setTimeout(() => this.hide(page), 0);
282 }
283 }
284 addAnimation(pageEl, first = false) {
285 if (!pageEl)
286 return;
287 if (this.animation && !first) {
288 setTimeout(() => {
289 pageEl.classList.add('taro_page_show');
290 setTimeout(() => {
291 pageEl.classList.add('taro_page_stationed');
292 }, this.animationDuration);
293 }, this.animationDelay);
294 }
295 else {
296 pageEl.classList.add('taro_page_show');
297 pageEl.classList.add('taro_page_stationed');
298 }
299 }
300 getPageContainer(page) {
301 var _a;
302 const path = page ? page === null || page === void 0 ? void 0 : page.path : (_a = Current.page) === null || _a === void 0 ? void 0 : _a.path;
303 const id = path === null || path === void 0 ? void 0 : path.replace(/([^a-z0-9\u00a0-\uffff_-])/ig, '\\$1');
304 if (page) {
305 return document.querySelector(`.taro_page#${id}`);
306 }
307 const el = (id
308 ? document.querySelector(`.taro_page#${id}`)
309 : document.querySelector('.taro_page') ||
310 document.querySelector('.taro_router'));
311 return el;
312 }
313 getScrollingElement(page) {
314 if (this.usingWindowScroll)
315 return window;
316 return this.getPageContainer(page) || window;
317 }
318 bindPageEvents(page, config = {}) {
319 var _a;
320 const scrollEl = this.getScrollingElement(page);
321 const distance = config.onReachBottomDistance || ((_a = this.config.window) === null || _a === void 0 ? void 0 : _a.onReachBottomDistance) || 50;
322 bindPageScroll(page, scrollEl, distance);
323 bindPageResize(page);
324 }
325 triggerRouterChange() {
326 /**
327 * @tarojs/runtime 中生命周期跑在 promise 中,所以这里需要 setTimeout 延迟事件调用
328 * TODO 考虑将生命周期返回 Promise,用于处理相关事件调用顺序
329 */
330 setTimeout(() => {
331 eventCenter.trigger('__afterTaroRouterChange', {
332 toLocation: {
333 path: this.pathname
334 }
335 });
336 }, 0);
337 }
338}
339
340export { PageHandler as default };