@mantou/gem 2.2.14 → 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/link.d.ts.map +1 -1
- package/elements/base/link.js +6 -3
- package/elements/base/link.js.map +1 -1
- package/elements/base/modal-factory.d.ts +3 -2
- 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/elements/base/use.d.ts.map +1 -1
- package/elements/base/use.js +11 -0
- package/elements/base/use.js.map +1 -1
- package/helper/hmr.d.ts +3 -2
- package/helper/hmr.d.ts.map +1 -1
- package/helper/hmr.js +95 -23
- package/helper/hmr.js.map +1 -1
- package/helper/i18n.d.ts +2 -1
- package/helper/i18n.d.ts.map +1 -1
- package/helper/i18n.js +9 -1
- package/helper/i18n.js.map +1 -1
- package/helper/theme.d.ts +8 -4
- package/helper/theme.d.ts.map +1 -1
- package/helper/theme.js +15 -10
- package/helper/theme.js.map +1 -1
- package/lib/decorators.d.ts.map +1 -1
- package/lib/decorators.js +31 -46
- package/lib/decorators.js.map +1 -1
- package/lib/history.d.ts +10 -7
- package/lib/history.d.ts.map +1 -1
- package/lib/history.js +37 -51
- package/lib/history.js.map +1 -1
- package/lib/lit-html.d.ts +15 -0
- package/lib/lit-html.d.ts.map +1 -1
- package/lib/lit-html.js +3 -1
- package/lib/lit-html.js.map +1 -1
- package/lib/reactive.d.ts +6 -2
- package/lib/reactive.d.ts.map +1 -1
- package/lib/reactive.js +114 -118
- package/lib/reactive.js.map +1 -1
- package/lib/server.d.ts +26 -0
- package/lib/server.d.ts.map +1 -0
- package/lib/server.js +45 -0
- package/lib/server.js.map +1 -0
- package/lib/store.d.ts +4 -1
- package/lib/store.d.ts.map +1 -1
- package/lib/store.js +24 -9
- package/lib/store.js.map +1 -1
- package/lib/utils.d.ts +2 -2
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +20 -7
- package/lib/utils.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/link.ts +5 -3
- package/src/elements/base/route.ts +84 -49
- package/src/elements/base/use.ts +11 -0
- package/src/helper/hmr.ts +105 -37
- package/src/helper/i18n.ts +12 -1
- package/src/helper/theme.ts +19 -10
- package/src/lib/decorators.ts +38 -51
- package/src/lib/history.ts +37 -46
- package/src/lib/lit-html.ts +3 -1
- package/src/lib/reactive.ts +115 -124
- package/src/lib/server.ts +59 -0
- package/src/lib/store.ts +24 -9
- package/src/lib/utils.ts +22 -7
- 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 +33 -0
- package/src/test/history.test.ts +19 -0
- package/src/test/store.test.ts +12 -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/src/helper/i18n.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { RouteTrigger } from '../elements/base/route';
|
|
|
2
2
|
import type { UpdateHistoryParams } from '../lib/history';
|
|
3
3
|
import type { TemplateResult } from '../lib/lit-html';
|
|
4
4
|
import { html, UpdateToken } from '../lib/reactive';
|
|
5
|
+
import { isolate } from '../lib/server';
|
|
5
6
|
import { GemError } from '../lib/utils';
|
|
6
7
|
import { logger } from './logger';
|
|
7
8
|
|
|
@@ -65,13 +66,23 @@ interface I18nOptions<T> {
|
|
|
65
66
|
export class I18n<T = Record<string, Msg>> implements RouteTrigger {
|
|
66
67
|
resources: Resources<T>;
|
|
67
68
|
fallbackLanguage: string;
|
|
68
|
-
currentLanguage: string;
|
|
69
69
|
cache?: boolean | 'manual';
|
|
70
70
|
cachePrefix = 'gem-i18n';
|
|
71
71
|
urlParamsType?: 'path' | 'querystring' | 'ccTLD' | 'gTLD';
|
|
72
72
|
urlParamsName?: string;
|
|
73
73
|
onChange?: (currentLanguage: string) => void;
|
|
74
74
|
|
|
75
|
+
// SSR 时每个请求使用独立的语言
|
|
76
|
+
#state = isolate({ currentLanguage: '' });
|
|
77
|
+
|
|
78
|
+
get currentLanguage() {
|
|
79
|
+
return this.#state.currentLanguage;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
set currentLanguage(lang: string) {
|
|
83
|
+
this.#state.currentLanguage = lang;
|
|
84
|
+
}
|
|
85
|
+
|
|
75
86
|
// 兼容 `<gem-route>`
|
|
76
87
|
replace = ({ path }: UpdateHistoryParams) => this.setLanguage(path!);
|
|
77
88
|
getParams = () => ({ path: this.currentLanguage });
|
package/src/helper/theme.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { GemElement, Sheet } from '../lib/reactive';
|
|
2
2
|
import { css, SheetToken } from '../lib/reactive';
|
|
3
|
+
import { isServer } from '../lib/server';
|
|
3
4
|
import type { Store } from '../lib/store';
|
|
4
5
|
import { connect, createStore } from '../lib/store';
|
|
5
6
|
import type { Updater } from '../lib/utils';
|
|
@@ -25,8 +26,11 @@ export function getThemeProps<T>(theme: Theme<T>) {
|
|
|
25
26
|
return themePropsMap.get(theme) as Theme<T>;
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
function createThemeFromProps<T extends Record<string, unknown>>(
|
|
29
|
-
|
|
29
|
+
function createThemeFromProps<T extends Record<string, unknown>>(
|
|
30
|
+
themeObj: T,
|
|
31
|
+
props: Record<string, string> = {},
|
|
32
|
+
salt = randomStr(),
|
|
33
|
+
) {
|
|
30
34
|
const styleSheet = css({})[SheetToken];
|
|
31
35
|
const store = createStore<T>(themeObj);
|
|
32
36
|
// 同步分配变量名,新增的键在更新后立即可读;样式内容随 store 异步更新
|
|
@@ -74,14 +78,17 @@ function createThemeFromProps<T extends Record<string, unknown>>(themeObj: T, pr
|
|
|
74
78
|
|
|
75
79
|
/**
|
|
76
80
|
* 用于 `@adoptedStyle(theme)`,类似 `css`
|
|
81
|
+
*
|
|
82
|
+
* `key` 用作 css 变量名的后缀,SSR 时让服务端和客户端的变量名一致,
|
|
83
|
+
* `swc-plugin-gem` 的 `ssr` 选项会为模块顶层的主题自动添加
|
|
77
84
|
*/
|
|
78
|
-
export function createScopedTheme<T extends Record<string, unknown>>(themeObj: T) {
|
|
79
|
-
return createThemeFromProps(themeObj);
|
|
85
|
+
export function createScopedTheme<T extends Record<string, unknown>>(themeObj: T, key?: string) {
|
|
86
|
+
return createThemeFromProps(themeObj, {}, key);
|
|
80
87
|
}
|
|
81
88
|
|
|
82
|
-
/**
|
|
83
|
-
export function createTheme<T extends Record<string, unknown>>(themeObj: T) {
|
|
84
|
-
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);
|
|
85
92
|
document.adoptedStyleSheets.push(result[SheetToken].getStyle());
|
|
86
93
|
return result;
|
|
87
94
|
}
|
|
@@ -93,6 +100,7 @@ export function createOverrideTheme<T extends Record<string, unknown>>(theme: Th
|
|
|
93
100
|
|
|
94
101
|
/**
|
|
95
102
|
* 返回一个主题对象,但同时又是一个装饰器函数,用来装饰一个主题更新函数
|
|
103
|
+
* 所有实例共享变量名,`key` 同 `createScopedTheme`
|
|
96
104
|
*
|
|
97
105
|
* @example
|
|
98
106
|
* ```ts
|
|
@@ -103,8 +111,8 @@ export function createOverrideTheme<T extends Record<string, unknown>>(theme: Th
|
|
|
103
111
|
* }
|
|
104
112
|
* ```
|
|
105
113
|
* */
|
|
106
|
-
export function createDecoratorTheme<T extends Record<string, unknown>>(themeObj: T) {
|
|
107
|
-
const themeAsKeys = createThemeFromProps(themeObj);
|
|
114
|
+
export function createDecoratorTheme<T extends Record<string, unknown>>(themeObj: T, key?: string) {
|
|
115
|
+
const themeAsKeys = createThemeFromProps(themeObj, {}, key);
|
|
108
116
|
const props = getThemeProps(themeAsKeys);
|
|
109
117
|
const update = <E extends GemElement, V extends () => T, K = any[] | undefined>(
|
|
110
118
|
getDep?: K extends readonly any[] ? (instance: E) => K : undefined,
|
|
@@ -118,7 +126,8 @@ export function createDecoratorTheme<T extends Record<string, unknown>>(themeObj
|
|
|
118
126
|
this.memo(
|
|
119
127
|
() => {
|
|
120
128
|
// https://bugs.webkit.org/show_bug.cgi?id=326327
|
|
121
|
-
|
|
129
|
+
// SSR 需要更新样式表文本
|
|
130
|
+
if (isServer || CSS.supports(`font:-apple-system-body`)) return theme(access.get(this).apply(this));
|
|
122
131
|
const next = access.get(this).apply(this);
|
|
123
132
|
const rule = style.cssRules[0] as CSSStyleRule;
|
|
124
133
|
const eleTarget = this.shadowRoot
|
package/src/lib/decorators.ts
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import type { TemplateResult } from './lit-html';
|
|
4
4
|
import type { Metadata, Sheet } from './reactive';
|
|
5
|
-
import { _createTemplate, _RenderErrorEvent,
|
|
5
|
+
import { _createTemplate, _RenderErrorEvent, _renderRoot, GemElement, UpdateToken } from './reactive';
|
|
6
6
|
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;
|
|
@@ -300,8 +305,9 @@ export function template<T extends GemElement, V extends () => TemplateResult |
|
|
|
300
305
|
export function fallback<T extends GemElement, V extends (err: any) => TemplateResult | null | undefined>() {
|
|
301
306
|
return (_: any, { addInitializer, access }: ClassFieldDecoratorContext<T, V> | ClassMethodDecoratorContext<T, V>) => {
|
|
302
307
|
addInitializer(function (this: T) {
|
|
303
|
-
this.addEventListener(_RenderErrorEvent, (
|
|
304
|
-
|
|
308
|
+
this.addEventListener(_RenderErrorEvent, (evt: CustomEvent) => {
|
|
309
|
+
evt.preventDefault();
|
|
310
|
+
_renderRoot(access.get.call(this, this).apply(this, [evt.detail]), this.internals.shadowRoot || this);
|
|
305
311
|
});
|
|
306
312
|
});
|
|
307
313
|
};
|
|
@@ -341,16 +347,8 @@ function defineCSSState(target: GemElementPrototype, prop: string, stateStr: str
|
|
|
341
347
|
* ```
|
|
342
348
|
*/
|
|
343
349
|
export function state<T extends GemElement>(_: undefined, context: ClassFieldDecoratorContext<T, boolean>) {
|
|
344
|
-
const
|
|
345
|
-
|
|
346
|
-
context.addInitializer(function (this: T) {
|
|
347
|
-
const target = getDecoratorTarget(this, context);
|
|
348
|
-
if (!hasOwn(target, prop)) {
|
|
349
|
-
pushStaticField(context, 'definedCSSStates', attr);
|
|
350
|
-
defineCSSState(target, prop, attr);
|
|
351
|
-
}
|
|
352
|
-
clearField(this, prop);
|
|
353
|
-
});
|
|
350
|
+
const stateStr = camelToKebabCase(context.name as string);
|
|
351
|
+
defineMember(context, 'definedCSSStates', stateStr, (target, prop) => defineCSSState(target, prop, stateStr));
|
|
354
352
|
}
|
|
355
353
|
|
|
356
354
|
function defineStaticField(
|
|
@@ -435,20 +433,9 @@ export function emitter<T extends GemElement>(_: undefined, context: ClassFieldD
|
|
|
435
433
|
export function globalemitter<T extends GemElement>(_: undefined, context: ClassFieldDecoratorContext<T, Emitter>) {
|
|
436
434
|
defineEmitter(context, { bubbles: true, composed: true });
|
|
437
435
|
}
|
|
438
|
-
function defineEmitter<
|
|
439
|
-
context
|
|
440
|
-
|
|
441
|
-
) {
|
|
442
|
-
const prop = context.name as string;
|
|
443
|
-
const event = camelToKebabCase(prop);
|
|
444
|
-
context.addInitializer(function (this: T) {
|
|
445
|
-
const target = getDecoratorTarget(this, context);
|
|
446
|
-
if (!hasOwn(target, prop)) {
|
|
447
|
-
pushStaticField(context, 'definedEvents', event);
|
|
448
|
-
defineProp(target, prop, { event, eventOptions });
|
|
449
|
-
}
|
|
450
|
-
clearField(this, prop);
|
|
451
|
-
});
|
|
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 }));
|
|
452
439
|
}
|
|
453
440
|
|
|
454
441
|
/**
|
package/src/lib/history.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// https://github.com/WICG/navigation-api#stakeholder-feedback
|
|
2
2
|
|
|
3
|
+
import { getRequestValue } from './server';
|
|
3
4
|
import { connect, createStore } from './store';
|
|
4
5
|
import { absoluteLocation, cleanObject, GemError, QueryString } from './utils';
|
|
5
6
|
|
|
@@ -45,15 +46,20 @@ type HistoryParams = Omit<UpdateHistoryParams, 'query'> & {
|
|
|
45
46
|
hash: string;
|
|
46
47
|
};
|
|
47
48
|
|
|
48
|
-
const
|
|
49
|
+
const globalParamsMap = new Map<string, HistoryParams>();
|
|
50
|
+
// SSR 时每个请求使用独立的记录,请求结束后释放
|
|
51
|
+
// 从全局复制初始记录(设置 basePath 等操作需要它),记录会被原地修改,所以也要复制
|
|
52
|
+
const copyParamsMap = () => new Map([...globalParamsMap].map(([k, v]) => [k, { ...v }]));
|
|
53
|
+
const getParamsMap = () => getRequestValue(globalParamsMap, copyParamsMap);
|
|
54
|
+
|
|
55
|
+
// 内部状态字段,不能由外部设置
|
|
56
|
+
const internalStateKeys = ['$key', '$hasCloseHandle', '$hasOpenHandle', '$hasShouldCloseHandle'] as const;
|
|
49
57
|
|
|
50
58
|
function validData(data: any) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if ($hasOpenHandle) throw new GemError('`$hasOpenHandle` is not allowed');
|
|
56
|
-
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
|
+
}
|
|
57
63
|
}
|
|
58
64
|
|
|
59
65
|
function getUrlBarPath(internalPath: string) {
|
|
@@ -65,14 +71,17 @@ function getInternalPath(urlBarPath: string) {
|
|
|
65
71
|
return urlBarPath.replace(new RegExp(`^${gemHistory.basePath}/`), '/');
|
|
66
72
|
}
|
|
67
73
|
|
|
68
|
-
|
|
74
|
+
// 切换到新的历史记录状态
|
|
75
|
+
function changeState(state: HistoryState) {
|
|
69
76
|
gemHistory.dispatchEvent(new CustomEvent('beforechange'));
|
|
77
|
+
cleanObject(store);
|
|
78
|
+
store(state);
|
|
70
79
|
}
|
|
71
80
|
|
|
72
81
|
function normalizeParams(params: UpdateHistoryParams): HistoryParams {
|
|
73
82
|
const current =
|
|
74
|
-
|
|
75
|
-
({ path: getInternalPath(location.pathname), query: new QueryString() } as HistoryParams);
|
|
83
|
+
gemHistory.getParams() ||
|
|
84
|
+
({ path: getInternalPath(location.pathname), query: new QueryString(), title: document.title } as HistoryParams);
|
|
76
85
|
// 没提供 path 使用当前 path
|
|
77
86
|
const path = params.path ? absoluteLocation(current.path, params.path) : getInternalPath(location.pathname);
|
|
78
87
|
// 没提供 query 又没有提供 path 时使用当前 search
|
|
@@ -80,8 +89,9 @@ function normalizeParams(params: UpdateHistoryParams): HistoryParams {
|
|
|
80
89
|
const queryChanged = String(newQueryObject) !== String(current.query);
|
|
81
90
|
const query = queryChanged ? newQueryObject : current.query;
|
|
82
91
|
const urlChanged = path !== current.path || queryChanged;
|
|
83
|
-
// 没提供 title 又没有改变 URL 时使用当前
|
|
84
|
-
|
|
92
|
+
// 没提供 title 又没有改变 URL 时使用当前 title
|
|
93
|
+
// 不使用 document.title,它可能已被格式化(例如添加了网站名后缀)
|
|
94
|
+
const title = params.title || (urlChanged ? '' : current.title);
|
|
85
95
|
const statusChanged = params.close || params.data || params.open || params.shouldClose;
|
|
86
96
|
// 没提供 hash 又没有改变 URL 仅仅是状态更新时使用当前 hash
|
|
87
97
|
const hash = decodeURIComponent(params.hash ?? (!urlChanged && statusChanged ? location.hash : ''));
|
|
@@ -100,10 +110,8 @@ function updateHistory(p: UpdateHistoryParams, native: typeof nativeHistory.push
|
|
|
100
110
|
$title: title,
|
|
101
111
|
...data,
|
|
102
112
|
};
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
cleanObject(store);
|
|
106
|
-
store(state);
|
|
113
|
+
getParamsMap().set(state.$key, params);
|
|
114
|
+
changeState(state);
|
|
107
115
|
const url = getUrlBarPath(path) + new QueryString(query) + hash;
|
|
108
116
|
const prevHash = decodeURIComponent(location.hash);
|
|
109
117
|
native(state, title, url);
|
|
@@ -112,23 +120,8 @@ function updateHistory(p: UpdateHistoryParams, native: typeof nativeHistory.push
|
|
|
112
120
|
|
|
113
121
|
// 跨框架时,调用者对 basePath 无感知
|
|
114
122
|
function updateHistoryByNative(data: any, title: string, originUrl: string, native: typeof nativeHistory.pushState) {
|
|
115
|
-
validData(data);
|
|
116
|
-
const state = {
|
|
117
|
-
$key: getKey(),
|
|
118
|
-
$title: title,
|
|
119
|
-
...(data || {}),
|
|
120
|
-
};
|
|
121
123
|
const { pathname, search, hash } = new URL(originUrl, location.origin + location.pathname);
|
|
122
|
-
|
|
123
|
-
paramsMap.set(state.$key, params);
|
|
124
|
-
dispatchBeforeChangeEvent();
|
|
125
|
-
cleanObject(store);
|
|
126
|
-
store(state);
|
|
127
|
-
const url = getUrlBarPath(pathname) + params.query + hash;
|
|
128
|
-
const prevHash = location.hash;
|
|
129
|
-
native(state, title, url);
|
|
130
|
-
// `location.hash` 和 `hash` 都已经进行了 url 编码,可以直接进行相等判断
|
|
131
|
-
if (prevHash !== hash) dispatchEvent(new CustomEvent('hashchange'));
|
|
124
|
+
updateHistory({ path: pathname, query: search, hash, title, data }, native);
|
|
132
125
|
}
|
|
133
126
|
|
|
134
127
|
const gemBasePathStore = createStore({
|
|
@@ -149,13 +142,13 @@ class GemHistory extends EventTarget {
|
|
|
149
142
|
// 应用初始化的时候设置
|
|
150
143
|
gemBasePathStore({ basePath });
|
|
151
144
|
// paramsMap 更新后 ui 才会更新
|
|
152
|
-
Object.assign(
|
|
145
|
+
Object.assign(this.getParams(), { path: getInternalPath(location.pathname) });
|
|
153
146
|
}
|
|
154
147
|
getParams() {
|
|
155
|
-
return
|
|
148
|
+
return getParamsMap().get(store.$key)!;
|
|
156
149
|
}
|
|
157
150
|
updateParams(params: UpdateHistoryParams) {
|
|
158
|
-
Object.assign(
|
|
151
|
+
Object.assign(this.getParams(), params);
|
|
159
152
|
store();
|
|
160
153
|
}
|
|
161
154
|
push(params: UpdateHistoryParams) {
|
|
@@ -166,7 +159,7 @@ class GemHistory extends EventTarget {
|
|
|
166
159
|
}
|
|
167
160
|
pushIgnoreCloseHandle(params: UpdateHistoryParams) {
|
|
168
161
|
if (store.$hasCloseHandle) {
|
|
169
|
-
|
|
162
|
+
this.getParams()?.close?.();
|
|
170
163
|
this.replace(params);
|
|
171
164
|
} else {
|
|
172
165
|
this.push(params);
|
|
@@ -218,7 +211,7 @@ if (!globalThis._GEMHISTORY) {
|
|
|
218
211
|
} else if (nativeHistory.state.$hasCloseHandle) {
|
|
219
212
|
store(nativeHistory.state);
|
|
220
213
|
const params = normalizeParams({ title: document.title });
|
|
221
|
-
|
|
214
|
+
getParamsMap().set(store.$key, params);
|
|
222
215
|
// 有 handle 返回键的页面刷新需要清除返回 handler
|
|
223
216
|
gemHistory.back();
|
|
224
217
|
} else {
|
|
@@ -228,11 +221,11 @@ if (!globalThis._GEMHISTORY) {
|
|
|
228
221
|
$key: getKey(),
|
|
229
222
|
...(nativeHistory.state || {}),
|
|
230
223
|
});
|
|
231
|
-
|
|
224
|
+
getParamsMap().set(store.$key, params);
|
|
232
225
|
}
|
|
233
226
|
|
|
234
227
|
connect(gemTitleStore, () => {
|
|
235
|
-
const params =
|
|
228
|
+
const params = gemHistory.getParams();
|
|
236
229
|
if (params) {
|
|
237
230
|
params.title = gemTitleStore.title;
|
|
238
231
|
}
|
|
@@ -266,9 +259,9 @@ if (!globalThis._GEMHISTORY) {
|
|
|
266
259
|
// 刷新后再导航需要从当前 state 中构建 params
|
|
267
260
|
// 理论上该条历史记录中不会出现事件处理器
|
|
268
261
|
// 子 app 导航也需要从 state 中构建 params
|
|
269
|
-
if (!
|
|
262
|
+
if (!getParamsMap().has(newState.$key)) {
|
|
270
263
|
const { pathname, search, hash } = location;
|
|
271
|
-
|
|
264
|
+
getParamsMap().set(newState.$key, {
|
|
272
265
|
path: getInternalPath(pathname),
|
|
273
266
|
query: new QueryString(search),
|
|
274
267
|
hash: decodeURIComponent(hash),
|
|
@@ -284,9 +277,9 @@ if (!globalThis._GEMHISTORY) {
|
|
|
284
277
|
// 返回键关闭的 modal 能前进键重新打开
|
|
285
278
|
// 刷新后不能工作:刷新后 historyItem 中只有 url
|
|
286
279
|
// 能在子 app 中工作
|
|
287
|
-
|
|
280
|
+
getParamsMap().get(newState.$key)?.open?.();
|
|
288
281
|
} else if (prevState.$hasCloseHandle) {
|
|
289
|
-
const prevParams =
|
|
282
|
+
const prevParams = getParamsMap().get(prevState.$key);
|
|
290
283
|
const closeHandle = prevParams?.close;
|
|
291
284
|
const shouldCloseHandle = prevParams?.shouldClose;
|
|
292
285
|
const notAllowClose = shouldCloseHandle && !shouldCloseHandle();
|
|
@@ -309,9 +302,7 @@ if (!globalThis._GEMHISTORY) {
|
|
|
309
302
|
}
|
|
310
303
|
}
|
|
311
304
|
|
|
312
|
-
|
|
313
|
-
cleanObject(store);
|
|
314
|
-
store(newState);
|
|
305
|
+
changeState(newState);
|
|
315
306
|
});
|
|
316
307
|
}
|
|
317
308
|
|
package/src/lib/lit-html.ts
CHANGED
|
@@ -245,8 +245,10 @@ function trustFromTemplateString(tsa: TemplateStringsArray, stringFromTSA: strin
|
|
|
245
245
|
* @return Array containing `[html, attrNames]` (array returned for terseness,
|
|
246
246
|
* to avoid object fields since this code is shared with non-minified SSR
|
|
247
247
|
* code)
|
|
248
|
+
*
|
|
249
|
+
* Gem Note: export for SSR
|
|
248
250
|
*/
|
|
249
|
-
const getTemplateHtml = (strings: TemplateStringsArray, type: ResultType): [TrustedHTML, Array<string>] => {
|
|
251
|
+
export const getTemplateHtml = (strings: TemplateStringsArray, type: ResultType): [TrustedHTML, Array<string>] => {
|
|
250
252
|
// Insert makers into the template HTML to represent the position of
|
|
251
253
|
// bindings. The following code scans the template strings to determine the
|
|
252
254
|
// syntactic position of the bindings. They can be in text position, where
|