@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.
Files changed (82) hide show
  1. package/dist/gem.js +7 -7
  2. package/dist/gem.mjs +7 -7
  3. package/elements/base/link.d.ts.map +1 -1
  4. package/elements/base/link.js +6 -3
  5. package/elements/base/link.js.map +1 -1
  6. package/elements/base/modal-factory.d.ts +3 -2
  7. package/elements/base/modal-factory.d.ts.map +1 -1
  8. package/elements/base/route.d.ts +8 -3
  9. package/elements/base/route.d.ts.map +1 -1
  10. package/elements/base/route.js +84 -44
  11. package/elements/base/route.js.map +1 -1
  12. package/elements/base/use.d.ts.map +1 -1
  13. package/elements/base/use.js +11 -0
  14. package/elements/base/use.js.map +1 -1
  15. package/helper/hmr.d.ts +3 -2
  16. package/helper/hmr.d.ts.map +1 -1
  17. package/helper/hmr.js +95 -23
  18. package/helper/hmr.js.map +1 -1
  19. package/helper/i18n.d.ts +2 -1
  20. package/helper/i18n.d.ts.map +1 -1
  21. package/helper/i18n.js +9 -1
  22. package/helper/i18n.js.map +1 -1
  23. package/helper/theme.d.ts +8 -4
  24. package/helper/theme.d.ts.map +1 -1
  25. package/helper/theme.js +15 -10
  26. package/helper/theme.js.map +1 -1
  27. package/lib/decorators.d.ts.map +1 -1
  28. package/lib/decorators.js +31 -46
  29. package/lib/decorators.js.map +1 -1
  30. package/lib/history.d.ts +10 -7
  31. package/lib/history.d.ts.map +1 -1
  32. package/lib/history.js +37 -51
  33. package/lib/history.js.map +1 -1
  34. package/lib/lit-html.d.ts +15 -0
  35. package/lib/lit-html.d.ts.map +1 -1
  36. package/lib/lit-html.js +3 -1
  37. package/lib/lit-html.js.map +1 -1
  38. package/lib/reactive.d.ts +6 -2
  39. package/lib/reactive.d.ts.map +1 -1
  40. package/lib/reactive.js +114 -118
  41. package/lib/reactive.js.map +1 -1
  42. package/lib/server.d.ts +26 -0
  43. package/lib/server.d.ts.map +1 -0
  44. package/lib/server.js +45 -0
  45. package/lib/server.js.map +1 -0
  46. package/lib/store.d.ts +4 -1
  47. package/lib/store.d.ts.map +1 -1
  48. package/lib/store.js +24 -9
  49. package/lib/store.js.map +1 -1
  50. package/lib/utils.d.ts +2 -2
  51. package/lib/utils.d.ts.map +1 -1
  52. package/lib/utils.js +20 -7
  53. package/lib/utils.js.map +1 -1
  54. package/lib/version.d.ts +1 -1
  55. package/lib/version.d.ts.map +1 -1
  56. package/lib/version.js +1 -1
  57. package/lib/version.js.map +1 -1
  58. package/package.json +3 -4
  59. package/src/elements/base/link.ts +5 -3
  60. package/src/elements/base/route.ts +84 -49
  61. package/src/elements/base/use.ts +11 -0
  62. package/src/helper/hmr.ts +105 -37
  63. package/src/helper/i18n.ts +12 -1
  64. package/src/helper/theme.ts +19 -10
  65. package/src/lib/decorators.ts +38 -51
  66. package/src/lib/history.ts +37 -46
  67. package/src/lib/lit-html.ts +3 -1
  68. package/src/lib/reactive.ts +115 -124
  69. package/src/lib/server.ts +59 -0
  70. package/src/lib/store.ts +24 -9
  71. package/src/lib/utils.ts +22 -7
  72. package/src/lib/version.ts +1 -1
  73. package/src/test/elements/gesture.test.ts +13 -5
  74. package/src/test/elements/route.test.ts +102 -0
  75. package/src/test/elements/title.test.ts +7 -0
  76. package/src/test/gem-element/advance.test.ts +19 -0
  77. package/src/test/gem-element/basic.test.ts +33 -0
  78. package/src/test/history.test.ts +19 -0
  79. package/src/test/store.test.ts +12 -0
  80. package/src/test/theme.test.ts +8 -1
  81. package/src/test/utils.test.ts +6 -0
  82. package/src/test/utils.ts +56 -0
@@ -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 });
@@ -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>>(themeObj: T, props: Record<string, string> = {}) {
29
- const salt = randomStr();
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
- if (CSS.supports(`font:-apple-system-body`)) return theme(access.get(this).apply(this));
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
@@ -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, GemElement, render, UpdateToken } from './reactive';
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
- const observedTargetAttributes = new WeakMap<GemElementPrototype, Map<string, string>>();
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) => observedTargetAttributes.get(getPrototypeOf(i))?.get(n);
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.addInitializer(function (this: T) {
134
- const target = getDecoratorTarget(this, context);
135
- if (!hasOwn(target, prop)) {
136
- pushStaticField(context, 'observedAttributes', attr); // 没有 observe 的效果
137
- defineProp(target, prop, { attr, attrType });
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.addInitializer(function (this: T) {
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, ({ detail }: CustomEvent) => {
304
- render(access.get.call(this, this).apply(this, [detail]), this.internals.shadowRoot || this);
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 prop = context.name as string;
345
- const attr = camelToKebabCase(prop);
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<T extends GemElement>(
439
- context: ClassFieldDecoratorContext,
440
- eventOptions?: Omit<CustomEventInit<unknown>, 'detail'>,
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
  /**
@@ -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 paramsMap = new Map<string, HistoryParams>();
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
- const { $key, $hasCloseHandle, $hasOpenHandle, $hasShouldCloseHandle } = data || {};
52
- if (store.$key === $key) return;
53
- if ($key) throw new GemError('`$key` is not allowed');
54
- if ($hasCloseHandle) throw new GemError('`$hasCloseHandle` is not allowed');
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
- function dispatchBeforeChangeEvent() {
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
- paramsMap.get(store.$key) ||
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 时使用当前 document.title
84
- const title = params.title || (urlChanged ? '' : document.title);
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
- paramsMap.set(state.$key, params);
104
- dispatchBeforeChangeEvent();
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
- const params = normalizeParams({ path: pathname, query: new QueryString(search), hash, title, data });
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(paramsMap.get(store.$key)!, { path: getInternalPath(location.pathname) });
145
+ Object.assign(this.getParams(), { path: getInternalPath(location.pathname) });
153
146
  }
154
147
  getParams() {
155
- return paramsMap.get(store.$key)!;
148
+ return getParamsMap().get(store.$key)!;
156
149
  }
157
150
  updateParams(params: UpdateHistoryParams) {
158
- Object.assign(paramsMap.get(store.$key)!, params);
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
- paramsMap.get(store.$key)?.close?.();
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
- paramsMap.set(store.$key, params);
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
- paramsMap.set(store.$key, params);
224
+ getParamsMap().set(store.$key, params);
232
225
  }
233
226
 
234
227
  connect(gemTitleStore, () => {
235
- const params = paramsMap.get(store.$key);
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 (!paramsMap.has(newState.$key)) {
262
+ if (!getParamsMap().has(newState.$key)) {
270
263
  const { pathname, search, hash } = location;
271
- paramsMap.set(newState.$key, {
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
- paramsMap.get(newState.$key)?.open?.();
280
+ getParamsMap().get(newState.$key)?.open?.();
288
281
  } else if (prevState.$hasCloseHandle) {
289
- const prevParams = paramsMap.get(prevState.$key);
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
- dispatchBeforeChangeEvent();
313
- cleanObject(store);
314
- store(newState);
305
+ changeState(newState);
315
306
  });
316
307
  }
317
308
 
@@ -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