@mantou/gem 2.2.15 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/gem.js +7 -7
- package/dist/gem.mjs +7 -7
- package/elements/base/modal-factory.d.ts +2 -1
- package/elements/base/modal-factory.d.ts.map +1 -1
- package/elements/base/route.d.ts +8 -3
- package/elements/base/route.d.ts.map +1 -1
- package/elements/base/route.js +84 -44
- package/elements/base/route.js.map +1 -1
- package/helper/theme.d.ts +8 -4
- package/helper/theme.d.ts.map +1 -1
- package/helper/theme.js +12 -9
- package/helper/theme.js.map +1 -1
- package/lib/decorators.d.ts.map +1 -1
- package/lib/decorators.js +27 -43
- package/lib/decorators.js.map +1 -1
- package/lib/history.d.ts.map +1 -1
- package/lib/history.js +19 -37
- package/lib/history.js.map +1 -1
- package/lib/reactive.d.ts +6 -5
- package/lib/reactive.d.ts.map +1 -1
- package/lib/reactive.js +85 -112
- package/lib/reactive.js.map +1 -1
- package/lib/server.d.ts +12 -0
- package/lib/server.d.ts.map +1 -1
- package/lib/server.js +2 -0
- package/lib/server.js.map +1 -1
- package/lib/store.d.ts +0 -6
- package/lib/store.d.ts.map +1 -1
- package/lib/store.js +5 -17
- package/lib/store.js.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.d.ts.map +1 -1
- package/lib/version.js +1 -1
- package/lib/version.js.map +1 -1
- package/package.json +3 -4
- package/src/elements/base/route.ts +84 -49
- package/src/helper/theme.ts +16 -9
- package/src/lib/decorators.ts +34 -48
- package/src/lib/history.ts +19 -32
- package/src/lib/reactive.ts +89 -120
- package/src/lib/server.ts +14 -0
- package/src/lib/store.ts +3 -19
- package/src/lib/version.ts +1 -1
- package/src/test/elements/gesture.test.ts +13 -5
- package/src/test/elements/route.test.ts +102 -0
- package/src/test/elements/title.test.ts +7 -0
- package/src/test/gem-element/advance.test.ts +19 -0
- package/src/test/gem-element/basic.test.ts +13 -0
- package/src/test/history.test.ts +19 -0
- package/src/test/theme.test.ts +8 -1
- package/src/test/utils.test.ts +6 -0
- package/src/test/utils.ts +56 -0
package/lib/server.d.ts
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
|
+
import type { GemElement } from './reactive';
|
|
1
2
|
/** 服务端渲染时不执行副作用,同步更新 */
|
|
2
3
|
export declare const isServer: boolean;
|
|
4
|
+
/** SSR 接入 Gem 的钩子,`@mantou/gem-ssr` 在服务端和客户端分别设置 */
|
|
5
|
+
export declare const ssrHooks: {
|
|
6
|
+
/** 渲染元素内容,返回 `true` 时跳过渲染:服务端收集模版,客户端水合 */
|
|
7
|
+
render?: (value: unknown, container: HTMLElement | DocumentFragment) => boolean;
|
|
8
|
+
/** 元素挂载前调用,返回 `true` 时推迟挂载,之后调用 `connect` 挂载:客户端恢复状态,等待父模版水合 */
|
|
9
|
+
connect?: (ele: GemElement, connect: () => void) => boolean;
|
|
10
|
+
/** 创建有 key 的 store,返回初始值:客户端恢复服务端的值 */
|
|
11
|
+
createStore?: (key: string) => object | undefined;
|
|
12
|
+
/** 更新有 key 的 store:服务端记录,渲染后输出到页面中 */
|
|
13
|
+
updateStore?: (key: string, store: object) => void;
|
|
14
|
+
};
|
|
3
15
|
/**
|
|
4
16
|
* SSR 时返回当前请求中 `key` 对应的值,第一次访问时使用 `init` 创建,
|
|
5
17
|
* 不在请求中(包括客户端)时返回 `key` 本身
|
package/lib/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/lib/server.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/lib/server.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAM7C,wBAAwB;AACxB,eAAO,MAAM,QAAQ,SAAoB,CAAC;AAE1C,oDAAoD;AACpD,eAAO,MAAM,QAAQ,EAAE;IACrB,2CAA2C;IAC3C,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,GAAG,gBAAgB,KAAK,OAAO,CAAC;IAChF,gEAAgE;IAChE,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,IAAI,KAAK,OAAO,CAAC;IAC5D,uCAAuC;IACvC,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IAClD,sCAAsC;IACtC,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/C,CAAC;AAEP;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,CAAC,CAAC,SAAS,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,CAO3D,CAAC;AAEjB;;;;GAIG;AACH,eAAO,MAAM,OAAO,EAAE,CAAC,CAAC,SAAS,MAAM,EAAE,MAAM,EAAE,CAAC,KAAK,CAcjC,CAAC"}
|
package/lib/server.js
CHANGED
|
@@ -6,6 +6,8 @@ const { get, set, has, deleteProperty, defineProperty, getOwnPropertyDescriptor,
|
|
|
6
6
|
const getRequestScope = globalThis.__GEM_SSR__;
|
|
7
7
|
/** 服务端渲染时不执行副作用,同步更新 */
|
|
8
8
|
export const isServer = !!getRequestScope;
|
|
9
|
+
/** SSR 接入 Gem 的钩子,`@mantou/gem-ssr` 在服务端和客户端分别设置 */
|
|
10
|
+
export const ssrHooks = {};
|
|
9
11
|
/**
|
|
10
12
|
* SSR 时返回当前请求中 `key` 对应的值,第一次访问时使用 `init` 创建,
|
|
11
13
|
* 不在请求中(包括客户端)时返回 `key` 本身
|
package/lib/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/lib/server.ts"],"names":[],"mappings":"AAAA,gBAAgB;AAChB,EAAE;AACF,0DAA0D;AAC1D,gFAAgF;
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/lib/server.ts"],"names":[],"mappings":"AAAA,gBAAgB;AAChB,EAAE;AACF,0DAA0D;AAC1D,gFAAgF;AAIhF,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,cAAc,EAAE,cAAc,EAAE,wBAAwB,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;AAErG,MAAM,eAAe,GAA0D,UAAkB,CAAC,WAAW,CAAC;AAE9G,wBAAwB;AACxB,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,CAAC,eAAe,CAAC;AAE1C,oDAAoD;AACpD,MAAM,CAAC,MAAM,QAAQ,GASjB,EAAE,CAAC;AAEP;;;GAGG;AACH,MAAM,CAAC,MAAM,eAAe,GAAmD,eAAe;IAC5F,CAAC,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE;QACZ,MAAM,KAAK,GAAG,eAAe,EAAE,CAAC;QAChC,IAAI,CAAC,KAAK;YAAE,OAAO,GAAG,CAAC;QACvB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5C,OAAO,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACxB,CAAC;IACH,CAAC,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC;AAEjB;;;;GAIG;AACH,MAAM,CAAC,MAAM,OAAO,GAAuC,eAAe;IACxE,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE;QACT,MAAM,IAAI,GAAG,GAAG,EAAE,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC;QACnC,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,eAAe,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QACpD,OAAO,IAAI,KAAK,CAAC,MAAM,EAAE;YACvB,GAAG,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,GAAG,CAAC;YACpC,GAAG,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,GAAG,EAAE,KAAK,CAAC;YAClD,GAAG,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,GAAG,CAAC;YACpC,cAAc,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE,CAAC,cAAc,CAAC,OAAO,EAAE,EAAE,GAAG,CAAC;YAC1D,cAAc,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,UAAU,EAAE,EAAE,CAAC,cAAc,CAAC,OAAO,EAAE,EAAE,GAAG,EAAE,UAAU,CAAC;YAClF,wBAAwB,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE,CAAC,wBAAwB,CAAC,OAAO,EAAE,EAAE,GAAG,CAAC;YAC9E,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;SAClC,CAAC,CAAC;IACL,CAAC;IACH,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC"}
|
package/lib/store.d.ts
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import { type Updater } from './utils';
|
|
2
2
|
export type Store<T = any> = Updater<T>;
|
|
3
|
-
/** SSR 时当前请求中更新过的有 key 的 store,供 `@mantou/gem-ssr` 使用 */
|
|
4
|
-
export declare const _getUpdatedStores: () => Map<any, any>;
|
|
5
|
-
declare let restoreStore: ((key: string) => object | undefined) | undefined;
|
|
6
|
-
/** 设置客户端恢复 store 的方法,供 `@mantou/gem-ssr` 使用 */
|
|
7
|
-
export declare function _setStoreRestorer(restorer: typeof restoreStore): void;
|
|
8
3
|
/**
|
|
9
4
|
* 创建 store,`key` 用来在 SSR 时同步服务端的值,`swc-plugin-gem` 的 `ssr` 选项会为模块顶层的 store 自动添加
|
|
10
5
|
*/
|
|
@@ -22,5 +17,4 @@ export declare function createStore<T = any>(originStore: T, key?: string): ((va
|
|
|
22
17
|
[Symbol.metadata]: never;
|
|
23
18
|
}, keyof T> & T;
|
|
24
19
|
export declare function connect(store: Store<any>, func: () => void): () => boolean | undefined;
|
|
25
|
-
export {};
|
|
26
20
|
//# sourceMappingURL=store.d.ts.map
|
package/lib/store.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/lib/store.ts"],"names":[],"mappings":"AACA,OAAO,EAA4C,KAAK,OAAO,EAAE,MAAM,SAAS,CAAC;AAEjF,MAAM,MAAM,KAAK,CAAC,CAAC,GAAG,GAAG,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/lib/store.ts"],"names":[],"mappings":"AACA,OAAO,EAA4C,KAAK,OAAO,EAAE,MAAM,SAAS,CAAC;AAEjF,MAAM,MAAM,KAAK,CAAC,CAAC,GAAG,GAAG,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC;AAOxC;;GAEG;AACH,wBAAgB,WAAW,CAAC,CAAC,GAAG,GAAG,EAAE,WAAW,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,aAIzB,OAAO,CAAC,CAAC,CAAC;;;;;;;;;;;;gBAYjD;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,MAAM,IAAI,6BAK1D"}
|
package/lib/store.js
CHANGED
|
@@ -1,18 +1,8 @@
|
|
|
1
|
-
import { getRequestValue, isolate, isServer } from './server';
|
|
1
|
+
import { getRequestValue, isolate, isServer, ssrHooks } from './server';
|
|
2
2
|
import { addMicrotask, assignState, createUpdater } from './utils';
|
|
3
3
|
/**@internal */
|
|
4
4
|
export const _StoreListenerMap = new WeakMap();
|
|
5
5
|
const createListeners = () => new Set();
|
|
6
|
-
// SSR 时当前请求中更新过的有 key 的 store,渲染后输出到页面中
|
|
7
|
-
const updatedStores = new Map();
|
|
8
|
-
/** SSR 时当前请求中更新过的有 key 的 store,供 `@mantou/gem-ssr` 使用 */
|
|
9
|
-
export const _getUpdatedStores = () => getRequestValue(updatedStores, () => new Map());
|
|
10
|
-
// 客户端读取服务端渲染时 store 的值
|
|
11
|
-
let restoreStore;
|
|
12
|
-
/** 设置客户端恢复 store 的方法,供 `@mantou/gem-ssr` 使用 */
|
|
13
|
-
export function _setStoreRestorer(restorer) {
|
|
14
|
-
restoreStore = restorer;
|
|
15
|
-
}
|
|
16
6
|
/**
|
|
17
7
|
* 创建 store,`key` 用来在 SSR 时同步服务端的值,`swc-plugin-gem` 的 `ssr` 选项会为模块顶层的 store 自动添加
|
|
18
8
|
*/
|
|
@@ -26,13 +16,11 @@ export function createStore(originStore, key) {
|
|
|
26
16
|
return;
|
|
27
17
|
// SSR 时请求中添加的监听器只属于当前请求,其他请求更新 store 时不会触发
|
|
28
18
|
getRequestValue(listeners, createListeners).forEach(addMicrotask);
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (key && updated !== updatedStores)
|
|
32
|
-
updated.set(key, store);
|
|
19
|
+
if (key)
|
|
20
|
+
ssrHooks.updateStore?.(key, store);
|
|
33
21
|
}));
|
|
34
|
-
if (key
|
|
35
|
-
assignState(store,
|
|
22
|
+
if (key)
|
|
23
|
+
assignState(store, ssrHooks.createStore?.(key));
|
|
36
24
|
_StoreListenerMap.set(store, listeners);
|
|
37
25
|
return store;
|
|
38
26
|
}
|
package/lib/store.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/lib/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/lib/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACxE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAgB,MAAM,SAAS,CAAC;AAIjF,eAAe;AACf,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,OAAO,EAA+B,CAAC;AAE5E,MAAM,eAAe,GAAG,GAAG,EAAE,CAAC,IAAI,GAAG,EAAc,CAAC;AAEpD;;GAEG;AACH,MAAM,UAAU,WAAW,CAAU,WAAc,EAAE,GAAY;IAC/D,MAAM,SAAS,GAAG,eAAe,EAAE,CAAC;IACpC,kBAAkB;IAClB,MAAM,KAAK,GAAG,OAAO,CACnB,aAAa,CAAC,WAAW,EAAE,CAAC,KAAkB,EAAE,EAAE;QAChD,WAAW,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QAC1B,SAAS,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QAChC,IAAI,CAAC,QAAQ;YAAE,OAAO;QACtB,2CAA2C;QAC3C,eAAe,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QAClE,IAAI,GAAG;YAAE,QAAQ,CAAC,WAAW,EAAE,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAC9C,CAAC,CAAC,CACH,CAAC;IACF,IAAI,GAAG;QAAE,WAAW,CAAC,KAAK,EAAE,QAAQ,CAAC,WAAW,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACzD,iBAAiB,CAAC,GAAG,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;IACxC,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,KAAiB,EAAE,IAAgB;IACzD,MAAM,cAAc,GAAG,iBAAiB,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACpD,MAAM,SAAS,GAAG,cAAc,IAAI,eAAe,CAAC,cAAc,EAAE,eAAe,CAAC,CAAC;IACrF,SAAS,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;IACrB,OAAO,GAAG,EAAE,CAAC,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;AACvC,CAAC"}
|
package/lib/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const version = "2.
|
|
1
|
+
export declare const version = "2.3.0";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/lib/version.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/lib/version.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,OAAO,
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/lib/version.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,OAAO,UAAU,CAAC"}
|
package/lib/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const version = '2.
|
|
1
|
+
export const version = '2.3.0';
|
|
2
2
|
//# sourceMappingURL=version.js.map
|
package/lib/version.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/lib/version.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,OAAO,GAAG,
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/lib/version.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mantou/gem",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "💎 使用自定义元素的轻量级 WebApp 开发框架",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"frontend",
|
|
@@ -31,9 +31,8 @@
|
|
|
31
31
|
],
|
|
32
32
|
"typings": "index.d.ts",
|
|
33
33
|
"scripts": {
|
|
34
|
-
"docs": "
|
|
35
|
-
"docs
|
|
36
|
-
"build:docs": "pnpm run docs --build",
|
|
34
|
+
"docs": "gem-book docs",
|
|
35
|
+
"build:docs": "gem-book build docs",
|
|
37
36
|
"build:module": "tsc -p tsconfig.build.json",
|
|
38
37
|
"build:lib": "rslib build",
|
|
39
38
|
"build": "pnpm build:module && pnpm build:lib",
|
|
@@ -9,32 +9,59 @@ import { connect, createStore } from '../../lib/store';
|
|
|
9
9
|
import type { QueryString } from '../../lib/utils';
|
|
10
10
|
import { addListener } from '../../lib/utils';
|
|
11
11
|
|
|
12
|
-
const UNNAMED_PARAM_PREFIX = '_';
|
|
13
|
-
|
|
14
|
-
// '/test/:a' /^\/test\/(?<a>[^/]+)$/i
|
|
15
|
-
// '/test/:a/b' /^\/test\/?<a>([^/]+)\/b$/i
|
|
16
|
-
// '/test/*' /^\/test\/(?<_0>.*)$/i
|
|
17
|
-
// '/test/:a/*' /^\/test\/(?<a>[^/]+)\/(?<_0>.*)$/i
|
|
18
|
-
// '/test/*/:b /^\/test\/(?<_0>.*)\/(?<a>[^/]+)$/i
|
|
19
|
-
class ParamsRegExp extends RegExp {
|
|
20
|
-
constructor(pattern: string) {
|
|
21
|
-
let i = 0;
|
|
22
|
-
super(
|
|
23
|
-
`^${pattern.replace(/((:[^/$]+)|(\*))/g, (_m, name: string) => {
|
|
24
|
-
if (name === '*') return `(?<${UNNAMED_PARAM_PREFIX}${i++}>.*)`;
|
|
25
|
-
return `(?<${name.slice(1)}>[^/]+)`;
|
|
26
|
-
})}$`,
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
12
|
type Params = Record<string, string>;
|
|
13
|
+
type Matcher = (path: string) => Params | null | undefined;
|
|
14
|
+
|
|
32
15
|
declare global {
|
|
33
16
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1731418
|
|
34
17
|
// https://github.com/WebKit/standards-positions/issues/61
|
|
35
18
|
var URLPattern: any;
|
|
36
19
|
}
|
|
37
20
|
|
|
21
|
+
const UNNAMED_PARAM_PREFIX = '_';
|
|
22
|
+
|
|
23
|
+
// 不支持 `URLPattern` 时转换成正则
|
|
24
|
+
// '/test/:a' /^\/test\/(?<a>[^/]+)$/
|
|
25
|
+
// '/test/:a/b' /^\/test\/(?<a>[^/]+)\/b$/
|
|
26
|
+
// '/test/*' /^\/test\/(?<_0>.*)$/
|
|
27
|
+
// '/test/:a/*' /^\/test\/(?<a>[^/]+)\/(?<_0>.*)$/
|
|
28
|
+
// '/test/*/:b' /^\/test\/(?<_0>.*)\/(?<b>[^/]+)$/
|
|
29
|
+
function createRegExpMatcher(pattern: string): Matcher {
|
|
30
|
+
let i = 0;
|
|
31
|
+
const source = pattern.replace(/:[^/$]+|\*/g, (name) =>
|
|
32
|
+
name === '*' ? `(?<${UNNAMED_PARAM_PREFIX}${i++}>.*)` : `(?<${name.slice(1)}>[^/]+)`,
|
|
33
|
+
);
|
|
34
|
+
const reg = new RegExp(`^${source}$`);
|
|
35
|
+
return (path) => {
|
|
36
|
+
const result = path.match(reg);
|
|
37
|
+
if (!result) return null;
|
|
38
|
+
const params: Params = {};
|
|
39
|
+
for (const key in result.groups) {
|
|
40
|
+
params[key.startsWith(UNNAMED_PARAM_PREFIX) ? key.slice(1) : key] = result.groups[key];
|
|
41
|
+
}
|
|
42
|
+
return params;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// pattern 开头不含参数和特殊字符的部分
|
|
47
|
+
const LITERAL_PREFIX = /^[\w\-./~]*/;
|
|
48
|
+
|
|
49
|
+
// `URLPattern` 的创建和 `exec` 都比较慢,不含参数时直接比较,否则先检查固定的开头
|
|
50
|
+
function createMatcher(pattern: string): Matcher {
|
|
51
|
+
const literal = LITERAL_PREFIX.exec(pattern)![0];
|
|
52
|
+
if (literal === pattern) return (path) => (path === pattern ? {} : null);
|
|
53
|
+
// 根 layout 的 pattern
|
|
54
|
+
if (pattern === '*') return (path) => ({ 0: path });
|
|
55
|
+
if (!globalThis.URLPattern) return createRegExpMatcher(pattern);
|
|
56
|
+
const urlPattern = new URLPattern({ pathname: pattern });
|
|
57
|
+
// 参数前的 `/` 可能是可选的,例如 `/a/:b?` 匹配 `/a`
|
|
58
|
+
const prefix = literal.replace(/\/$/, '');
|
|
59
|
+
return (path) => (path.startsWith(prefix) ? urlPattern.exec({ pathname: path })?.pathname.groups : null);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// pattern 来自路由定义,数量有限
|
|
63
|
+
const matchers = new Map<string, Matcher>();
|
|
64
|
+
|
|
38
65
|
/**
|
|
39
66
|
* - 大小写敏感
|
|
40
67
|
* - 匹配成功时返回 params
|
|
@@ -49,22 +76,9 @@ declare global {
|
|
|
49
76
|
* ```
|
|
50
77
|
*/
|
|
51
78
|
export function matchPath(pattern: string, path: string) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (!matchResult) return null;
|
|
56
|
-
return matchResult.pathname.groups as Params;
|
|
57
|
-
}
|
|
58
|
-
const reg = new ParamsRegExp(pattern);
|
|
59
|
-
const matchResult = path.match(reg) || `${path}/`.match(reg);
|
|
60
|
-
if (!matchResult) return null;
|
|
61
|
-
const params: Params = {};
|
|
62
|
-
if (matchResult.groups) {
|
|
63
|
-
Object.entries(matchResult.groups).forEach(([key, value]) => {
|
|
64
|
-
params[key.startsWith(UNNAMED_PARAM_PREFIX) ? key.slice(1) : key] = value;
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
return params;
|
|
79
|
+
let match = matchers.get(pattern);
|
|
80
|
+
if (!match) matchers.set(pattern, (match = createMatcher(pattern)));
|
|
81
|
+
return match(path) || match(`${path}/`) || null;
|
|
68
82
|
}
|
|
69
83
|
|
|
70
84
|
export interface RouteItem<T = unknown> {
|
|
@@ -79,7 +93,7 @@ export interface RouteItem<T = unknown> {
|
|
|
79
93
|
getContent?: (
|
|
80
94
|
params: Params,
|
|
81
95
|
renderRoot: DocumentFragment | GemLightRouteElement,
|
|
82
|
-
) => TemplateResult | Promise<TemplateResult
|
|
96
|
+
) => TemplateResult | undefined | Promise<TemplateResult | undefined>;
|
|
83
97
|
title?: string;
|
|
84
98
|
// 用来传递数据
|
|
85
99
|
data?: T;
|
|
@@ -130,6 +144,9 @@ export type RouteTrigger = {
|
|
|
130
144
|
|
|
131
145
|
const scrollPositionMap = new Map<string, number>();
|
|
132
146
|
|
|
147
|
+
// 导航到了新的历史记录,前进、后退和页面加载时为 `false`
|
|
148
|
+
let pushed = false;
|
|
149
|
+
|
|
133
150
|
@light({ penetrable: true })
|
|
134
151
|
export class GemLightRouteElement extends GemElement {
|
|
135
152
|
@boolattribute transition: boolean;
|
|
@@ -148,8 +165,13 @@ export class GemLightRouteElement extends GemElement {
|
|
|
148
165
|
@emitter routechange: Emitter<RouteItem | null>; // path 改变或者 key 改变,包含初始渲染
|
|
149
166
|
@emitter error: Emitter<any>;
|
|
150
167
|
|
|
151
|
-
|
|
152
|
-
|
|
168
|
+
/**
|
|
169
|
+
* 自动让滚动容器恢复滚动位置,导航到新的历史记录时滚动到顶部,
|
|
170
|
+
* 函数返回空值时不处理当前路由,例如只有子路由渲染后才需要滚动
|
|
171
|
+
*/
|
|
172
|
+
@property scrollContainer?: Element | (() => Element | null | undefined);
|
|
173
|
+
/**恢复滚动位置前等待,例如等待异步加载的内容撑开滚动容器 */
|
|
174
|
+
@property scrollWait?: () => Promise<unknown>;
|
|
153
175
|
/**默认当有 `hash` 时不进行滚动 */
|
|
154
176
|
@property scrollIgnoreHash?: boolean;
|
|
155
177
|
|
|
@@ -205,14 +227,24 @@ export class GemLightRouteElement extends GemElement {
|
|
|
205
227
|
});
|
|
206
228
|
};
|
|
207
229
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
230
|
+
// 已经处理过滚动的历史记录,同一条历史记录重新渲染时保持滚动位置
|
|
231
|
+
#scrolledKey?: string;
|
|
232
|
+
|
|
233
|
+
#updateScrollContainerPosition = async () => {
|
|
234
|
+
const container = this.#scrollContainer;
|
|
235
|
+
const key = history.currentKey;
|
|
236
|
+
if (!container || key === this.#scrolledKey) return;
|
|
237
|
+
this.#scrolledKey = key;
|
|
238
|
+
const scrollTop = scrollPositionMap.get(key);
|
|
239
|
+
if (!pushed && scrollTop !== undefined) {
|
|
240
|
+
await this.scrollWait?.();
|
|
241
|
+
// 等待时可能已经导航
|
|
242
|
+
if (key === history.currentKey) container.scroll(0, scrollTop);
|
|
243
|
+
return;
|
|
215
244
|
}
|
|
245
|
+
// 前进、后退或者页面加载时位置未知(例如刷新后),保持浏览器恢复的位置,浏览器只恢复文档的滚动位置
|
|
246
|
+
if (!pushed && container === document.scrollingElement) return;
|
|
247
|
+
if (this.scrollIgnoreHash || !history.getParams().hash) container.scroll(0, 0);
|
|
216
248
|
};
|
|
217
249
|
|
|
218
250
|
#lastLoader?: TemplateResult | Promise<TemplateResult | undefined>;
|
|
@@ -239,10 +271,13 @@ export class GemLightRouteElement extends GemElement {
|
|
|
239
271
|
|
|
240
272
|
@effect((i) => [i.scrollContainer])
|
|
241
273
|
#rememberScrollPosition = () => {
|
|
242
|
-
if (!this
|
|
243
|
-
return addListener(history, 'beforechange', () =>
|
|
244
|
-
|
|
245
|
-
|
|
274
|
+
if (!this.scrollContainer) return;
|
|
275
|
+
return addListener(history, 'beforechange', () => {
|
|
276
|
+
// 前进和后退时浏览器的历史记录已经改变,Gem 的还没有改变
|
|
277
|
+
pushed = globalThis.history.state?.$key === history.currentKey;
|
|
278
|
+
const container = this.#scrollContainer;
|
|
279
|
+
if (container) scrollPositionMap.set(history.currentKey, container.scrollTop);
|
|
280
|
+
});
|
|
246
281
|
};
|
|
247
282
|
|
|
248
283
|
// history or i18n
|
package/src/helper/theme.ts
CHANGED
|
@@ -26,8 +26,11 @@ export function getThemeProps<T>(theme: Theme<T>) {
|
|
|
26
26
|
return themePropsMap.get(theme) as Theme<T>;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
function createThemeFromProps<T extends Record<string, unknown>>(
|
|
30
|
-
|
|
29
|
+
function createThemeFromProps<T extends Record<string, unknown>>(
|
|
30
|
+
themeObj: T,
|
|
31
|
+
props: Record<string, string> = {},
|
|
32
|
+
salt = randomStr(),
|
|
33
|
+
) {
|
|
31
34
|
const styleSheet = css({})[SheetToken];
|
|
32
35
|
const store = createStore<T>(themeObj);
|
|
33
36
|
// 同步分配变量名,新增的键在更新后立即可读;样式内容随 store 异步更新
|
|
@@ -75,14 +78,17 @@ function createThemeFromProps<T extends Record<string, unknown>>(themeObj: T, pr
|
|
|
75
78
|
|
|
76
79
|
/**
|
|
77
80
|
* 用于 `@adoptedStyle(theme)`,类似 `css`
|
|
81
|
+
*
|
|
82
|
+
* `key` 用作 css 变量名的后缀,SSR 时让服务端和客户端的变量名一致,
|
|
83
|
+
* `swc-plugin-gem` 的 `ssr` 选项会为模块顶层的主题自动添加
|
|
78
84
|
*/
|
|
79
|
-
export function createScopedTheme<T extends Record<string, unknown>>(themeObj: T) {
|
|
80
|
-
return createThemeFromProps(themeObj);
|
|
85
|
+
export function createScopedTheme<T extends Record<string, unknown>>(themeObj: T, key?: string) {
|
|
86
|
+
return createThemeFromProps(themeObj, {}, key);
|
|
81
87
|
}
|
|
82
88
|
|
|
83
|
-
/**
|
|
84
|
-
export function createTheme<T extends Record<string, unknown>>(themeObj: T) {
|
|
85
|
-
const result = createScopedTheme(themeObj);
|
|
89
|
+
/** 全局主题,`key` 同 `createScopedTheme` */
|
|
90
|
+
export function createTheme<T extends Record<string, unknown>>(themeObj: T, key?: string) {
|
|
91
|
+
const result = createScopedTheme(themeObj, key);
|
|
86
92
|
document.adoptedStyleSheets.push(result[SheetToken].getStyle());
|
|
87
93
|
return result;
|
|
88
94
|
}
|
|
@@ -94,6 +100,7 @@ export function createOverrideTheme<T extends Record<string, unknown>>(theme: Th
|
|
|
94
100
|
|
|
95
101
|
/**
|
|
96
102
|
* 返回一个主题对象,但同时又是一个装饰器函数,用来装饰一个主题更新函数
|
|
103
|
+
* 所有实例共享变量名,`key` 同 `createScopedTheme`
|
|
97
104
|
*
|
|
98
105
|
* @example
|
|
99
106
|
* ```ts
|
|
@@ -104,8 +111,8 @@ export function createOverrideTheme<T extends Record<string, unknown>>(theme: Th
|
|
|
104
111
|
* }
|
|
105
112
|
* ```
|
|
106
113
|
* */
|
|
107
|
-
export function createDecoratorTheme<T extends Record<string, unknown>>(themeObj: T) {
|
|
108
|
-
const themeAsKeys = createThemeFromProps(themeObj);
|
|
114
|
+
export function createDecoratorTheme<T extends Record<string, unknown>>(themeObj: T, key?: string) {
|
|
115
|
+
const themeAsKeys = createThemeFromProps(themeObj, {}, key);
|
|
109
116
|
const props = getThemeProps(themeAsKeys);
|
|
110
117
|
const update = <E extends GemElement, V extends () => T, K = any[] | undefined>(
|
|
111
118
|
getDep?: K extends readonly any[] ? (instance: E) => K : undefined,
|
package/src/lib/decorators.ts
CHANGED
|
@@ -7,7 +7,7 @@ import type { Store } from './store';
|
|
|
7
7
|
import { camelToKebabCase, GemError, PropProxyMap } from './utils';
|
|
8
8
|
|
|
9
9
|
type GemElementPrototype = GemElement & { '': never };
|
|
10
|
-
type StaticField = Exclude<keyof Metadata, keyof ShadowRootInit | 'aria' | 'noBlocking' | 'penetrable'>;
|
|
10
|
+
type StaticField = Exclude<keyof Metadata, keyof ShadowRootInit | 'aria' | 'noBlocking' | 'penetrable' | 'attrProps'>;
|
|
11
11
|
|
|
12
12
|
const { deleteProperty, getOwnPropertyDescriptor, defineProperty } = Reflect;
|
|
13
13
|
const { getPrototypeOf, assign, hasOwn } = Object;
|
|
@@ -32,12 +32,31 @@ function clearField<T extends GemElement>(instance: T, prop: string) {
|
|
|
32
32
|
(instance as any)[prop] = value;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
/**
|
|
36
|
+
* 定义响应式的成员:在原型上定义访问器(基类定义过就跳过),移除实例上的同名字段,
|
|
37
|
+
* `field` 记录到元数据中,供外部读取
|
|
38
|
+
*/
|
|
39
|
+
function defineMember(
|
|
40
|
+
context: ClassFieldDecoratorContext,
|
|
41
|
+
field: StaticField,
|
|
42
|
+
name: string,
|
|
43
|
+
define: (target: GemElementPrototype, prop: string) => void,
|
|
44
|
+
) {
|
|
45
|
+
const prop = context.name as string;
|
|
46
|
+
pushStaticField(context, field, name);
|
|
47
|
+
context.addInitializer(function (this: GemElement) {
|
|
48
|
+
const target = getDecoratorTarget(this, context);
|
|
49
|
+
if (!hasOwn(target, prop)) define(target, prop);
|
|
50
|
+
clearField(this, prop);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
36
54
|
// hack 修改 attribute 行为,如果是观察的,就使用 `setter`
|
|
37
55
|
// 不在 Devtools 中工作 https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_scripts#dom_access
|
|
38
56
|
// 使用 Gem DevTools 注入脚本监听 attribute 变化
|
|
39
57
|
const { setAttribute, toggleAttribute, removeAttribute } = Element.prototype;
|
|
40
|
-
const getObservedPropName = (i: GemElement, n: string) =>
|
|
58
|
+
const getObservedPropName = (i: GemElement, n: string) =>
|
|
59
|
+
((i.constructor as any)[Symbol.metadata] as Metadata | undefined)?.attrProps?.[n];
|
|
41
60
|
GemElement.prototype.setAttribute = function (n: string, v: string) {
|
|
42
61
|
const prop = getObservedPropName(this, n);
|
|
43
62
|
if (!prop) return setAttribute.call(this, n, v);
|
|
@@ -130,19 +149,12 @@ type AttrType = BooleanConstructor | NumberConstructor | StringConstructor;
|
|
|
130
149
|
function decoratorAttr<T extends GemElement>(context: ClassFieldDecoratorContext<T>, attrType: AttrType) {
|
|
131
150
|
const prop = context.name as string;
|
|
132
151
|
const attr = camelToKebabCase(prop);
|
|
133
|
-
context.
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
clearField(this, prop);
|
|
140
|
-
// 记录观察的 attribute, 用于 hack 的 setAttribute
|
|
141
|
-
const proto = getPrototypeOf(this);
|
|
142
|
-
const attrMap = observedTargetAttributes.get(proto) || new Map<string, string>();
|
|
143
|
-
attrMap.set(attr, prop);
|
|
144
|
-
observedTargetAttributes.set(proto, attrMap);
|
|
145
|
-
});
|
|
152
|
+
const metadata = context.metadata as Metadata;
|
|
153
|
+
// 记录观察的 attribute,用于 hack 的 setAttribute,原型链继承基类的记录
|
|
154
|
+
if (!hasOwn(metadata, 'attrProps')) metadata.attrProps = Object.create(metadata.attrProps || null);
|
|
155
|
+
metadata.attrProps![attr] = prop;
|
|
156
|
+
// `observedAttributes` 没有 observe 的效果
|
|
157
|
+
defineMember(context, 'observedAttributes', attr, (target) => defineProp(target, prop, { attr, attrType }));
|
|
146
158
|
// 延时定义的元素需要继承原实例属性值
|
|
147
159
|
return function (this: any, initValue: any) {
|
|
148
160
|
return this.getAttribute(attr) ?? initValue;
|
|
@@ -181,14 +193,7 @@ export function numattribute<T extends GemElement>(_: undefined, context: ClassF
|
|
|
181
193
|
*/
|
|
182
194
|
export function property<T extends GemElement>(_: undefined, context: ClassFieldDecoratorContext<T>) {
|
|
183
195
|
const prop = context.name as string;
|
|
184
|
-
context
|
|
185
|
-
const target = getDecoratorTarget(this, context);
|
|
186
|
-
if (!hasOwn(target, prop)) {
|
|
187
|
-
pushStaticField(context, 'observedProperties', prop);
|
|
188
|
-
defineProp(target, prop);
|
|
189
|
-
}
|
|
190
|
-
clearField(this, prop);
|
|
191
|
-
});
|
|
196
|
+
defineMember(context, 'observedProperties', prop, defineProp);
|
|
192
197
|
// 延时定义的元素需要继承原实例属性值
|
|
193
198
|
return function (this: any, initValue: any) {
|
|
194
199
|
return this[prop] ?? initValue;
|
|
@@ -342,16 +347,8 @@ function defineCSSState(target: GemElementPrototype, prop: string, stateStr: str
|
|
|
342
347
|
* ```
|
|
343
348
|
*/
|
|
344
349
|
export function state<T extends GemElement>(_: undefined, context: ClassFieldDecoratorContext<T, boolean>) {
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
context.addInitializer(function (this: T) {
|
|
348
|
-
const target = getDecoratorTarget(this, context);
|
|
349
|
-
if (!hasOwn(target, prop)) {
|
|
350
|
-
pushStaticField(context, 'definedCSSStates', attr);
|
|
351
|
-
defineCSSState(target, prop, attr);
|
|
352
|
-
}
|
|
353
|
-
clearField(this, prop);
|
|
354
|
-
});
|
|
350
|
+
const stateStr = camelToKebabCase(context.name as string);
|
|
351
|
+
defineMember(context, 'definedCSSStates', stateStr, (target, prop) => defineCSSState(target, prop, stateStr));
|
|
355
352
|
}
|
|
356
353
|
|
|
357
354
|
function defineStaticField(
|
|
@@ -436,20 +433,9 @@ export function emitter<T extends GemElement>(_: undefined, context: ClassFieldD
|
|
|
436
433
|
export function globalemitter<T extends GemElement>(_: undefined, context: ClassFieldDecoratorContext<T, Emitter>) {
|
|
437
434
|
defineEmitter(context, { bubbles: true, composed: true });
|
|
438
435
|
}
|
|
439
|
-
function defineEmitter<
|
|
440
|
-
context
|
|
441
|
-
|
|
442
|
-
) {
|
|
443
|
-
const prop = context.name as string;
|
|
444
|
-
const event = camelToKebabCase(prop);
|
|
445
|
-
context.addInitializer(function (this: T) {
|
|
446
|
-
const target = getDecoratorTarget(this, context);
|
|
447
|
-
if (!hasOwn(target, prop)) {
|
|
448
|
-
pushStaticField(context, 'definedEvents', event);
|
|
449
|
-
defineProp(target, prop, { event, eventOptions });
|
|
450
|
-
}
|
|
451
|
-
clearField(this, prop);
|
|
452
|
-
});
|
|
436
|
+
function defineEmitter(context: ClassFieldDecoratorContext, eventOptions?: Omit<CustomEventInit<unknown>, 'detail'>) {
|
|
437
|
+
const event = camelToKebabCase(context.name as string);
|
|
438
|
+
defineMember(context, 'definedEvents', event, (target, prop) => defineProp(target, prop, { event, eventOptions }));
|
|
453
439
|
}
|
|
454
440
|
|
|
455
441
|
/**
|
package/src/lib/history.ts
CHANGED
|
@@ -52,13 +52,14 @@ const globalParamsMap = new Map<string, HistoryParams>();
|
|
|
52
52
|
const copyParamsMap = () => new Map([...globalParamsMap].map(([k, v]) => [k, { ...v }]));
|
|
53
53
|
const getParamsMap = () => getRequestValue(globalParamsMap, copyParamsMap);
|
|
54
54
|
|
|
55
|
+
// 内部状态字段,不能由外部设置
|
|
56
|
+
const internalStateKeys = ['$key', '$hasCloseHandle', '$hasOpenHandle', '$hasShouldCloseHandle'] as const;
|
|
57
|
+
|
|
55
58
|
function validData(data: any) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if ($hasOpenHandle) throw new GemError('`$hasOpenHandle` is not allowed');
|
|
61
|
-
if ($hasShouldCloseHandle) throw new GemError('`$hasShouldCloseHandle` is not allowed');
|
|
59
|
+
if (store.$key === data?.$key) return;
|
|
60
|
+
for (const key of internalStateKeys) {
|
|
61
|
+
if (data?.[key]) throw new GemError(`\`${key}\` is not allowed`);
|
|
62
|
+
}
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
function getUrlBarPath(internalPath: string) {
|
|
@@ -70,13 +71,17 @@ function getInternalPath(urlBarPath: string) {
|
|
|
70
71
|
return urlBarPath.replace(new RegExp(`^${gemHistory.basePath}/`), '/');
|
|
71
72
|
}
|
|
72
73
|
|
|
73
|
-
|
|
74
|
+
// 切换到新的历史记录状态
|
|
75
|
+
function changeState(state: HistoryState) {
|
|
74
76
|
gemHistory.dispatchEvent(new CustomEvent('beforechange'));
|
|
77
|
+
cleanObject(store);
|
|
78
|
+
store(state);
|
|
75
79
|
}
|
|
76
80
|
|
|
77
81
|
function normalizeParams(params: UpdateHistoryParams): HistoryParams {
|
|
78
82
|
const current =
|
|
79
|
-
gemHistory.getParams() ||
|
|
83
|
+
gemHistory.getParams() ||
|
|
84
|
+
({ path: getInternalPath(location.pathname), query: new QueryString(), title: document.title } as HistoryParams);
|
|
80
85
|
// 没提供 path 使用当前 path
|
|
81
86
|
const path = params.path ? absoluteLocation(current.path, params.path) : getInternalPath(location.pathname);
|
|
82
87
|
// 没提供 query 又没有提供 path 时使用当前 search
|
|
@@ -84,8 +89,9 @@ function normalizeParams(params: UpdateHistoryParams): HistoryParams {
|
|
|
84
89
|
const queryChanged = String(newQueryObject) !== String(current.query);
|
|
85
90
|
const query = queryChanged ? newQueryObject : current.query;
|
|
86
91
|
const urlChanged = path !== current.path || queryChanged;
|
|
87
|
-
// 没提供 title 又没有改变 URL 时使用当前
|
|
88
|
-
|
|
92
|
+
// 没提供 title 又没有改变 URL 时使用当前 title
|
|
93
|
+
// 不使用 document.title,它可能已被格式化(例如添加了网站名后缀)
|
|
94
|
+
const title = params.title || (urlChanged ? '' : current.title);
|
|
89
95
|
const statusChanged = params.close || params.data || params.open || params.shouldClose;
|
|
90
96
|
// 没提供 hash 又没有改变 URL 仅仅是状态更新时使用当前 hash
|
|
91
97
|
const hash = decodeURIComponent(params.hash ?? (!urlChanged && statusChanged ? location.hash : ''));
|
|
@@ -105,9 +111,7 @@ function updateHistory(p: UpdateHistoryParams, native: typeof nativeHistory.push
|
|
|
105
111
|
...data,
|
|
106
112
|
};
|
|
107
113
|
getParamsMap().set(state.$key, params);
|
|
108
|
-
|
|
109
|
-
cleanObject(store);
|
|
110
|
-
store(state);
|
|
114
|
+
changeState(state);
|
|
111
115
|
const url = getUrlBarPath(path) + new QueryString(query) + hash;
|
|
112
116
|
const prevHash = decodeURIComponent(location.hash);
|
|
113
117
|
native(state, title, url);
|
|
@@ -116,23 +120,8 @@ function updateHistory(p: UpdateHistoryParams, native: typeof nativeHistory.push
|
|
|
116
120
|
|
|
117
121
|
// 跨框架时,调用者对 basePath 无感知
|
|
118
122
|
function updateHistoryByNative(data: any, title: string, originUrl: string, native: typeof nativeHistory.pushState) {
|
|
119
|
-
validData(data);
|
|
120
|
-
const state = {
|
|
121
|
-
$key: getKey(),
|
|
122
|
-
$title: title,
|
|
123
|
-
...(data || {}),
|
|
124
|
-
};
|
|
125
123
|
const { pathname, search, hash } = new URL(originUrl, location.origin + location.pathname);
|
|
126
|
-
|
|
127
|
-
getParamsMap().set(state.$key, params);
|
|
128
|
-
dispatchBeforeChangeEvent();
|
|
129
|
-
cleanObject(store);
|
|
130
|
-
store(state);
|
|
131
|
-
const url = getUrlBarPath(pathname) + params.query + hash;
|
|
132
|
-
const prevHash = location.hash;
|
|
133
|
-
native(state, title, url);
|
|
134
|
-
// `location.hash` 和 `hash` 都已经进行了 url 编码,可以直接进行相等判断
|
|
135
|
-
if (prevHash !== hash) dispatchEvent(new CustomEvent('hashchange'));
|
|
124
|
+
updateHistory({ path: pathname, query: search, hash, title, data }, native);
|
|
136
125
|
}
|
|
137
126
|
|
|
138
127
|
const gemBasePathStore = createStore({
|
|
@@ -313,9 +302,7 @@ if (!globalThis._GEMHISTORY) {
|
|
|
313
302
|
}
|
|
314
303
|
}
|
|
315
304
|
|
|
316
|
-
|
|
317
|
-
cleanObject(store);
|
|
318
|
-
store(newState);
|
|
305
|
+
changeState(newState);
|
|
319
306
|
});
|
|
320
307
|
}
|
|
321
308
|
|