@mantou/gem 1.7.11 → 2.0.0-alpha.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 (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1 -1
  3. package/dist/gem.es.js +1962 -1689
  4. package/dist/gem.umd.js +52 -54
  5. package/elements/base/dialog.d.ts +0 -11
  6. package/elements/base/dialog.d.ts.map +1 -1
  7. package/elements/base/dialog.js +93 -66
  8. package/elements/base/dialog.js.map +1 -1
  9. package/elements/base/gesture.d.ts +1 -2
  10. package/elements/base/gesture.d.ts.map +1 -1
  11. package/elements/base/gesture.js +157 -96
  12. package/elements/base/gesture.js.map +1 -1
  13. package/elements/base/link.d.ts +5 -5
  14. package/elements/base/link.d.ts.map +1 -1
  15. package/elements/base/link.js +249 -145
  16. package/elements/base/link.js.map +1 -1
  17. package/elements/base/modal-factory.d.ts +147 -139
  18. package/elements/base/modal-factory.d.ts.map +1 -1
  19. package/elements/base/modal-factory.js +84 -25
  20. package/elements/base/modal-factory.js.map +1 -1
  21. package/elements/base/reflect.d.ts +0 -1
  22. package/elements/base/reflect.d.ts.map +1 -1
  23. package/elements/base/reflect.js +83 -38
  24. package/elements/base/reflect.js.map +1 -1
  25. package/elements/base/route.d.ts +12 -24
  26. package/elements/base/route.d.ts.map +1 -1
  27. package/elements/base/route.js +263 -164
  28. package/elements/base/route.js.map +1 -1
  29. package/elements/base/title.d.ts +3 -11
  30. package/elements/base/title.d.ts.map +1 -1
  31. package/elements/base/title.js +110 -59
  32. package/elements/base/title.js.map +1 -1
  33. package/elements/base/unsafe.d.ts +0 -1
  34. package/elements/base/unsafe.d.ts.map +1 -1
  35. package/elements/base/unsafe.js +82 -33
  36. package/elements/base/unsafe.js.map +1 -1
  37. package/elements/base/use.d.ts.map +1 -1
  38. package/elements/base/use.js +95 -47
  39. package/elements/base/use.js.map +1 -1
  40. package/helper/i18n.d.ts +4 -9
  41. package/helper/i18n.d.ts.map +1 -1
  42. package/helper/i18n.js +69 -65
  43. package/helper/i18n.js.map +1 -1
  44. package/helper/logger.js +10 -9
  45. package/helper/logger.js.map +1 -1
  46. package/helper/react-shim.d.ts +15 -0
  47. package/helper/react-shim.d.ts.map +1 -0
  48. package/helper/react-shim.js +56 -0
  49. package/helper/react-shim.js.map +1 -0
  50. package/helper/react-utils.d.ts +15 -0
  51. package/helper/react-utils.d.ts.map +1 -0
  52. package/helper/react-utils.js +50 -0
  53. package/helper/react-utils.js.map +1 -0
  54. package/helper/request.d.ts +17 -7
  55. package/helper/request.d.ts.map +1 -1
  56. package/helper/request.js +103 -53
  57. package/helper/request.js.map +1 -1
  58. package/helper/ssr-shim.d.ts.map +1 -1
  59. package/helper/ssr-shim.js +8 -1
  60. package/helper/ssr-shim.js.map +1 -1
  61. package/helper/theme.d.ts +14 -21
  62. package/helper/theme.d.ts.map +1 -1
  63. package/helper/theme.js +55 -38
  64. package/helper/theme.js.map +1 -1
  65. package/index.d.ts +4 -3
  66. package/index.d.ts.map +1 -1
  67. package/index.js +5 -3
  68. package/index.js.map +1 -1
  69. package/lib/decorators.d.ts +74 -41
  70. package/lib/decorators.d.ts.map +1 -1
  71. package/lib/decorators.js +324 -84
  72. package/lib/decorators.js.map +1 -1
  73. package/lib/directives.d.ts +4 -0
  74. package/lib/directives.d.ts.map +1 -0
  75. package/lib/directives.js +5 -0
  76. package/lib/directives.js.map +1 -0
  77. package/lib/element.d.ts +112 -110
  78. package/lib/element.d.ts.map +1 -1
  79. package/lib/element.js +431 -470
  80. package/lib/element.js.map +1 -1
  81. package/lib/history.d.ts +4 -0
  82. package/lib/history.d.ts.map +1 -1
  83. package/lib/history.js +12 -13
  84. package/lib/history.js.map +1 -1
  85. package/lib/store.d.ts +1 -1
  86. package/lib/store.d.ts.map +1 -1
  87. package/lib/store.js +4 -5
  88. package/lib/store.js.map +1 -1
  89. package/lib/utils.d.ts +3 -43
  90. package/lib/utils.d.ts.map +1 -1
  91. package/lib/utils.js +61 -246
  92. package/lib/utils.js.map +1 -1
  93. package/lib/version.d.ts +1 -1
  94. package/lib/version.d.ts.map +1 -1
  95. package/lib/version.js +2 -1
  96. package/lib/version.js.map +1 -1
  97. package/package.json +12 -11
  98. package/src/dist.ts +3 -2
  99. package/src/elements/base/dialog.ts +10 -20
  100. package/src/elements/base/gesture.ts +16 -14
  101. package/src/elements/base/link.ts +65 -44
  102. package/src/elements/base/modal-factory.ts +14 -14
  103. package/src/elements/base/reflect.ts +24 -32
  104. package/src/elements/base/route.ts +55 -68
  105. package/src/elements/base/title.ts +25 -42
  106. package/src/elements/base/unsafe.ts +12 -21
  107. package/src/elements/base/use.ts +17 -13
  108. package/src/helper/i18n.ts +20 -14
  109. package/src/helper/react-utils.ts +66 -0
  110. package/src/helper/request.ts +122 -53
  111. package/src/helper/ssr-shim.ts +8 -1
  112. package/src/helper/theme.ts +72 -45
  113. package/src/index.ts +6 -4
  114. package/src/lib/decorators.ts +394 -93
  115. package/src/lib/directives.ts +6 -0
  116. package/src/lib/element.ts +473 -499
  117. package/src/lib/history.ts +4 -3
  118. package/src/lib/store.ts +2 -1
  119. package/src/lib/utils.ts +19 -205
  120. package/src/lib/version.ts +2 -1
  121. package/src/test/elements/link.test.ts +1 -1
  122. package/src/test/elements/title.test.ts +1 -0
  123. package/src/test/gem-element/advance.test.ts +72 -49
  124. package/src/test/gem-element/basic.test.ts +37 -50
  125. package/src/test/gem-element/decorators.test.ts +22 -21
  126. package/src/test/gem-element/multi.test.ts +5 -5
  127. package/src/test/utils.test.ts +16 -38
@@ -1,5 +1,6 @@
1
1
  import { GemElement, html } from '../../lib/element';
2
- import { attribute, emitter, Emitter } from '../../lib/decorators';
2
+ import type { Emitter } from '../../lib/decorators';
3
+ import { attribute, emitter, mounted, shadow } from '../../lib/decorators';
3
4
 
4
5
  export type PanEventDetail = {
5
6
  // movement
@@ -58,6 +59,7 @@ function angleAB(
58
59
  *
59
60
  * @attr touch-action
60
61
  */
62
+ @shadow()
61
63
  export class GemGestureElement extends GemElement {
62
64
  @emitter pan: Emitter<PanEventDetail>;
63
65
  @emitter pinch: Emitter<PinchEventDetail>;
@@ -68,18 +70,6 @@ export class GemGestureElement extends GemElement {
68
70
 
69
71
  @attribute touchAction: string;
70
72
 
71
- constructor() {
72
- super();
73
- this.addEventListener('pointerdown', this.#onStart);
74
- this.addEventListener('pointermove', this.#onMoveSet);
75
- this.addEventListener('pointerup', this.#onEnd);
76
- this.addEventListener('pointerleave', this.#onEnd); // 有时候 up 没有触发?
77
- // 为什么空白区域会自动触发 `pointercancel`?
78
- this.addEventListener('pointercancel', this.#onEnd);
79
-
80
- this.addEventListener('dragstart', (evt) => evt.preventDefault());
81
- }
82
-
83
73
  #pressed = false; // 触发 press 之后不触发其他事件
84
74
  #pressTimer = 0;
85
75
 
@@ -204,7 +194,7 @@ export class GemGestureElement extends GemElement {
204
194
  evt.stopPropagation();
205
195
  // https://bugs.webkit.org/show_bug.cgi?id=210454
206
196
  if ('getCoalescedEvents' in evt) {
207
- evt.getCoalescedEvents().forEach((evt) => this.#onMove(evt));
197
+ evt.getCoalescedEvents().forEach((event) => this.#onMove(event));
208
198
  } else {
209
199
  this.#onMove(evt);
210
200
  }
@@ -253,6 +243,18 @@ export class GemGestureElement extends GemElement {
253
243
  this.#startEventMap.delete(pointerId);
254
244
  };
255
245
 
246
+ @mounted()
247
+ #init = () => {
248
+ this.addEventListener('pointerdown', this.#onStart);
249
+ this.addEventListener('pointermove', this.#onMoveSet);
250
+ this.addEventListener('pointerup', this.#onEnd);
251
+ this.addEventListener('pointerleave', this.#onEnd); // 有时候 up 没有触发?
252
+ // 为什么空白区域会自动触发 `pointercancel`?
253
+ this.addEventListener('pointercancel', this.#onEnd);
254
+
255
+ this.addEventListener('dragstart', (evt) => evt.preventDefault());
256
+ };
257
+
256
258
  render() {
257
259
  return html`
258
260
  <style>
@@ -1,14 +1,44 @@
1
- import { GemElement, html, ifDefined } from '../../lib/element';
2
- import { attribute, property, state, part, connectStore } from '../../lib/decorators';
1
+ import { createCSSSheet, GemElement, html } from '../../lib/element';
2
+ import {
3
+ attribute,
4
+ property,
5
+ state,
6
+ part,
7
+ connectStore,
8
+ shadow,
9
+ aria,
10
+ adoptedStyle,
11
+ mounted,
12
+ effect,
13
+ boolattribute,
14
+ } from '../../lib/decorators';
3
15
  import { history, basePathStore } from '../../lib/history';
4
- import { absoluteLocation } from '../../lib/utils';
16
+ import { absoluteLocation, css } from '../../lib/utils';
17
+ import { ifDefined } from '../../lib/directives';
5
18
 
6
- import { matchPath, RouteItem, RouteOptions, createHistoryParams, createPath } from './route';
19
+ import type { RouteItem, RouteOptions } from './route';
20
+ import { matchPath, createHistoryParams, createPath } from './route';
7
21
 
8
22
  function isExternal(path: string) {
9
23
  return !path.startsWith('/');
10
24
  }
11
25
 
26
+ const styles = createCSSSheet(css`
27
+ :host {
28
+ /* link default style */
29
+ cursor: pointer;
30
+ color: blue;
31
+ text-decoration: underline;
32
+ }
33
+ a {
34
+ all: unset;
35
+ display: contents;
36
+ }
37
+ a:visited {
38
+ color: unset;
39
+ }
40
+ `);
41
+
12
42
  /**
13
43
  * @customElement gem-link
14
44
  * @attr href
@@ -21,17 +51,21 @@ function isExternal(path: string) {
21
51
  * @attr hint
22
52
  * @part link
23
53
  *
24
- * print bug: https://github.com/mantou132/gem/issues/36
54
+ * Bug: print `<link>` https://github.com/mantou132/gem/issues/36
25
55
  */
26
56
  @connectStore(basePathStore)
57
+ @adoptedStyle(styles)
58
+ @shadow()
59
+ @aria({ focusable: true })
27
60
  export class GemLinkElement extends GemElement {
28
61
  @attribute href: string;
29
62
  @attribute target: string;
30
63
  @attribute path: string;
31
64
  @attribute query: string;
32
65
  @attribute hash: string;
33
- @attribute docTitle: string;
34
66
  @attribute hint: 'on' | 'off';
67
+ /** Preload route content when point over */
68
+ @boolattribute preload: boolean;
35
69
 
36
70
  // 动态路由,根据 route.pattern 和 options.params 计算出 path
37
71
  @property route?: RouteItem;
@@ -50,11 +84,6 @@ export class GemLinkElement extends GemElement {
50
84
  return this.hint || 'on';
51
85
  }
52
86
 
53
- constructor() {
54
- super({ focusable: true });
55
- this.addEventListener('click', this.#onClick);
56
- }
57
-
58
87
  #onClick = async () => {
59
88
  const locationString = this.getLocationString();
60
89
 
@@ -87,24 +116,19 @@ export class GemLinkElement extends GemElement {
87
116
  await this.prepare?.();
88
117
 
89
118
  if (this.route) {
90
- history.pushIgnoreCloseHandle({
91
- ...createHistoryParams(this.route, this.#routeOptions),
92
- title: this.route.title || this.docTitle,
93
- });
119
+ history.pushIgnoreCloseHandle(createHistoryParams(this.route, this.#routeOptions));
94
120
  } else if (this.href) {
95
- const { pathname, search, hash } = new URL(locationString, location.origin);
121
+ const url = new URL(locationString, location.origin);
96
122
  history.pushIgnoreCloseHandle({
97
- path: pathname,
98
- query: search,
99
- hash,
100
- title: this.docTitle,
123
+ path: url.pathname,
124
+ query: url.search,
125
+ hash: url.hash,
101
126
  });
102
127
  } else {
103
128
  history.pushIgnoreCloseHandle({
104
129
  path: this.path,
105
130
  query: this.query,
106
131
  hash: this.hash,
107
- title: this.docTitle,
108
132
  });
109
133
  }
110
134
  };
@@ -120,27 +144,26 @@ export class GemLinkElement extends GemElement {
120
144
  : new URL(history.basePath + locationString, location.origin).toString();
121
145
  };
122
146
 
147
+ #preload = () => {
148
+ // 也许是 `getter`
149
+ this.route?.content;
150
+ // 只触发 `getContent` 调用,参数不使用
151
+ this.route?.getContent?.({}, this.shadowRoot!);
152
+ };
153
+
154
+ @mounted()
155
+ #init() {
156
+ this.addEventListener('click', this.#onClick);
157
+ this.addEventListener('pointerenter', this.#preload);
158
+ }
159
+
123
160
  render() {
124
161
  return html`
125
- <style>
126
- :host {
127
- /* link default style */
128
- cursor: pointer;
129
- color: blue;
130
- text-decoration: underline;
131
- }
132
- a {
133
- all: unset;
134
- display: contents;
135
- }
136
- a:visited {
137
- color: unset;
138
- }
139
- </style>
140
162
  <a
141
163
  part=${this.link}
142
164
  @click=${this.#preventDefault}
143
165
  href=${ifDefined(this.#hint === 'off' ? undefined : this.#getHint())}
166
+ tabindex="-1"
144
167
  >
145
168
  <slot></slot>
146
169
  </a>
@@ -181,12 +204,10 @@ export class GemActiveLinkElement extends GemLinkElement {
181
204
  @attribute pattern: string; // 使用匹配模式设定 active
182
205
  @state active: boolean;
183
206
 
184
- constructor() {
185
- super();
186
- this.effect(() => {
187
- const { path, query, hash } = history.getParams();
188
- const isMatchPattern = this.pattern && matchPath(this.pattern, path);
189
- this.active = !!isMatchPattern || path + query + hash === this.getLocationString();
190
- });
191
- }
207
+ @effect()
208
+ #updateState = () => {
209
+ const { path, query, hash } = history.getParams();
210
+ const isMatchPattern = this.pattern && matchPath(this.pattern, path);
211
+ this.active = !!isMatchPattern || path + query + hash === this.getLocationString();
212
+ };
192
213
  }
@@ -1,6 +1,7 @@
1
1
  import { GemElement } from '../../lib/element';
2
- import { createStore, updateStore } from '../../lib/store';
2
+ import { connect, createStore, updateStore } from '../../lib/store';
3
3
  import { history } from '../../lib/history';
4
+ import { aria, mounted, shadow } from '../../lib/decorators';
4
5
 
5
6
  const open = Symbol('open mark');
6
7
 
@@ -15,7 +16,9 @@ const open = Symbol('open mark');
15
16
  export function createModalClass<T extends Record<string, unknown>>(options: T) {
16
17
  const final = Symbol();
17
18
 
18
- return class extends GemElement {
19
+ @shadow()
20
+ @aria({ role: 'alertdialog', ariaModal: 'true' })
21
+ class ModalElement extends GemElement {
19
22
  static inertStore: HTMLElement[] = [];
20
23
  static instance: GemElement | null = null;
21
24
  /**
@@ -32,11 +35,6 @@ export function createModalClass<T extends Record<string, unknown>>(options: T)
32
35
  return this.store[open];
33
36
  }
34
37
 
35
- /**@final */
36
- static get observedStores() {
37
- return [history.store, this.store];
38
- }
39
-
40
38
  /**
41
39
  * @final
42
40
  * 自带 100ms 延迟,以允许在其他 Dialog 的 `shouldClose` 中调用此方法;
@@ -88,12 +86,14 @@ export function createModalClass<T extends Record<string, unknown>>(options: T)
88
86
  return true;
89
87
  }
90
88
 
91
- constructor() {
92
- super();
93
- this.internals.role = 'alertdialog';
94
- this.internals.ariaModal = 'true';
95
- }
96
-
97
89
  label = '';
98
- };
90
+
91
+ @mounted()
92
+ #connectHistory = () => connect(history.store, this.update);
93
+
94
+ @mounted()
95
+ #connectStore = () => connect(ModalElement.store, this.update);
96
+ }
97
+
98
+ return ModalElement;
99
99
  }
@@ -7,7 +7,7 @@
7
7
  * 比如 duoyun-ui 文档站加载示例元素时把 GemReflectElement 渲染的元素清除
8
8
  */
9
9
  import { GemElement } from '../../lib/element';
10
- import { attribute, property } from '../../lib/decorators';
10
+ import { attribute, effect, property, willMount } from '../../lib/decorators';
11
11
 
12
12
  const START = 'gem-reflect-start';
13
13
  const END = 'gem-reflect-end';
@@ -37,37 +37,29 @@ export class GemReflectElement extends GemElement {
37
37
  return this.method || 'append';
38
38
  }
39
39
 
40
- constructor() {
41
- super({ isLight: true });
40
+ @willMount()
41
+ #init = () => {
42
+ this.hidden = true;
43
+ this.dataset.gemReflect = '';
44
+ };
42
45
 
43
- this.effect(
44
- () => {
45
- this.hidden = true;
46
- this.dataset.gemReflect = '';
47
- },
48
- () => [],
49
- );
46
+ #startNode = createReflectFragmentTag(START);
47
+ #endNode = createReflectFragmentTag(END);
48
+ #childNodes: undefined | ChildNode[];
50
49
 
51
- const startNode = createReflectFragmentTag(START);
52
- const endNode = createReflectFragmentTag(END);
53
- let childNodes: undefined | ChildNode[];
54
-
55
- this.effect(
56
- () => {
57
- (childNodes || [startNode, ...this.childNodes, endNode]).forEach((node, index, arr) => {
58
- const prev = arr[index - 1];
59
- if (prev) {
60
- prev.after(node);
61
- } else {
62
- this.target[this.#method](node);
63
- }
64
- });
65
- return () => {
66
- childNodes = getReflectFragment(startNode);
67
- childNodes.forEach((node) => node.remove());
68
- };
69
- },
70
- () => [this.target],
71
- );
72
- }
50
+ @effect((i) => [i.target])
51
+ #changeTarget = () => {
52
+ (this.#childNodes || [this.#startNode, ...this.childNodes, this.#endNode]).forEach((node, index, arr) => {
53
+ const prev = arr[index - 1];
54
+ if (prev) {
55
+ prev.after(node);
56
+ } else {
57
+ this.target[this.#method](node);
58
+ }
59
+ });
60
+ return () => {
61
+ this.#childNodes = getReflectFragment(this.#startNode);
62
+ this.#childNodes.forEach((node) => node.remove());
63
+ };
64
+ };
73
65
  }
@@ -1,8 +1,13 @@
1
- import { GemElement, html, TemplateResult } from '../../lib/element';
2
- import { property, emitter, Emitter, boolattribute } from '../../lib/decorators';
3
- import { createStore, updateStore, Store, connect } from '../../lib/store';
4
- import { titleStore, history, UpdateHistoryParams } from '../../lib/history';
5
- import { addListener, QueryString } from '../../lib/utils';
1
+ import type { TemplateResult } from '../../lib/element';
2
+ import { BoundaryCSSState, createState, GemElement, html } from '../../lib/element';
3
+ import type { Emitter } from '../../lib/decorators';
4
+ import { property, emitter, boolattribute, shadow, effect, template, willMount } from '../../lib/decorators';
5
+ import type { Store } from '../../lib/store';
6
+ import { createStore, updateStore, connect } from '../../lib/store';
7
+ import type { UpdateHistoryParams } from '../../lib/history';
8
+ import { titleStore, history } from '../../lib/history';
9
+ import type { QueryString } from '../../lib/utils';
10
+ import { addListener } from '../../lib/utils';
6
11
 
7
12
  const UNNAMED_PARAM_PREFIX = '_';
8
13
 
@@ -113,11 +118,6 @@ export function createHistoryParams(route: RouteItem, options?: RouteOptions): U
113
118
  };
114
119
  }
115
120
 
116
- interface ConstructorOptions {
117
- isLight?: boolean;
118
- routes?: RouteItem[] | RoutesObject;
119
- }
120
-
121
121
  const INIT_CONTENT = html``;
122
122
 
123
123
  type State = {
@@ -125,7 +125,7 @@ type State = {
125
125
  };
126
126
 
127
127
  export type RouteTrigger = {
128
- store: Store<any>;
128
+ store?: Store<any>;
129
129
  replace: (arg: UpdateHistoryParams) => void;
130
130
  getParams: () => { path: string; query?: string | QueryString; hash?: string };
131
131
  };
@@ -133,13 +133,13 @@ export type RouteTrigger = {
133
133
  const scrollPositionMap = new Map<string, number>();
134
134
 
135
135
  /**
136
- * @customElement gem-route
136
+ * @customElement gem-light-route
137
137
  * @attr inert 暂停路由更新
138
138
  * @fires routechange
139
139
  * @fires error
140
140
  * @fires loading
141
141
  */
142
- export class GemRouteElement extends GemElement<State> {
142
+ export class GemLightRouteElement extends GemElement {
143
143
  @boolattribute transition: boolean;
144
144
  @property routes?: RouteItem[] | RoutesObject;
145
145
  /**
@@ -168,8 +168,6 @@ export class GemRouteElement extends GemElement<State> {
168
168
  /**当前匹配的路由的 params */
169
169
  currentParams: Params = {};
170
170
 
171
- #lastLoader?: Promise<TemplateResult | undefined>;
172
-
173
171
  /**不要多个 `<gem-route>` 共享,因为那样会导致后面的元素卸载前触发更新 */
174
172
  static createLocationStore = () => {
175
173
  const { path, query, hash, data } = history.getParams();
@@ -201,14 +199,7 @@ export class GemRouteElement extends GemElement<State> {
201
199
  return typeof this.scrollContainer === 'function' ? this.scrollContainer() : this.scrollContainer;
202
200
  }
203
201
 
204
- constructor({ isLight, routes }: ConstructorOptions = {}) {
205
- super({ isLight });
206
- this.routes = this.routes || routes;
207
- }
208
-
209
- state: State = {
210
- content: INIT_CONTENT,
211
- };
202
+ #state = createState<State>({ content: INIT_CONTENT });
212
203
 
213
204
  #updateLocationStore = () => {
214
205
  if (!this.locationStore) return;
@@ -232,13 +223,14 @@ export class GemRouteElement extends GemElement<State> {
232
223
  }
233
224
  };
234
225
 
226
+ #lastLoader?: Promise<TemplateResult | undefined>;
235
227
  #setContent = (route: RouteItem | null, params: Params, content?: TemplateResult) => {
236
228
  this.beforeroutechange(route);
237
229
  this.#lastLoader = undefined;
238
230
  this.currentRoute = route;
239
231
  this.currentParams = params;
240
232
  const changeContent = async () => {
241
- this.setState({ content });
233
+ this.#state({ content });
242
234
  // 要确保新页面能读取到新路由数据
243
235
  this.#updateLocationStore();
244
236
  // 等待页面渲染完成
@@ -253,45 +245,39 @@ export class GemRouteElement extends GemElement<State> {
253
245
  }
254
246
  };
255
247
 
256
- mounted() {
257
- this.effect(
258
- () => {
259
- if (!this.#scrollContainer) return;
260
- return addListener(history, 'beforechange', () =>
261
- scrollPositionMap.set(history.currentKey, this.#scrollContainer!.scrollTop),
262
- );
263
- },
264
- () => [this.scrollContainer],
265
- );
248
+ @willMount()
249
+ #unBoundary = () => this.internals.states.delete(BoundaryCSSState);
266
250
 
267
- this.effect(
268
- // 触发 this.#update
269
- () => connect(this.trigger.store, () => this.setState({})),
270
- () => [this.trigger],
251
+ @effect((i) => [i.scrollContainer])
252
+ #rememberScrollPosition = () => {
253
+ if (!this.#scrollContainer) return;
254
+ return addListener(history, 'beforechange', () =>
255
+ scrollPositionMap.set(history.currentKey, this.#scrollContainer!.scrollTop),
271
256
  );
257
+ };
272
258
 
273
- this.effect(
274
- ([key, path], old) => {
275
- // 只有查询参数改变
276
- if (old && key === old[0] && path === old[1]) {
277
- this.#updateLocationStore();
278
- return;
279
- }
280
- this.update();
281
- },
282
- () => {
283
- const { path, query, hash } = this.trigger.getParams();
284
- return [this.key, path, query, hash] as const;
285
- },
286
- );
287
- }
259
+ // history or i18n
260
+ @effect((i) => [i.scrollContainer])
261
+ #connectTriggerStore = () => connect(this.trigger.store, () => this.#state({}));
288
262
 
289
- shouldUpdate() {
290
- return this.inert ? false : true;
291
- }
263
+ #updateContentDep = () => {
264
+ const { path, query, hash } = this.trigger.getParams();
265
+ return [this.key, path, query, hash] as const;
266
+ };
267
+
268
+ @effect((i) => i.#updateContentDep())
269
+ #updateContent = ([key, path]: any, old?: any) => {
270
+ // 只有查询参数改变
271
+ if (old && key === old[0] && path === old[1]) {
272
+ this.#updateLocationStore();
273
+ return;
274
+ }
275
+ this.update();
276
+ };
292
277
 
293
- render() {
294
- const { content } = this.state;
278
+ @template((i) => !i.inert)
279
+ #content = () => {
280
+ const { content } = this.#state;
295
281
  if (content === undefined) return;
296
282
 
297
283
  if (!this.shadowRoot) {
@@ -308,8 +294,9 @@ export class GemRouteElement extends GemElement<State> {
308
294
  </style>
309
295
  ${content === INIT_CONTENT ? html`<slot></slot>` : content}
310
296
  `;
311
- }
297
+ };
312
298
 
299
+ // 重写 `update`,并暴露为公共字段
313
300
  update = () => {
314
301
  const { path, hash, query } = this.trigger.getParams();
315
302
  const { route, params = {} } = GemRouteElement.findRoute(this.routes, path);
@@ -323,7 +310,10 @@ export class GemRouteElement extends GemElement<State> {
323
310
  return;
324
311
  }
325
312
  if (this.trigger === history) {
326
- updateStore(titleStore, { title: route?.title });
313
+ // 嵌套路由执行顺序也是从父到子
314
+ if (location.href !== titleStore.url || route?.title) {
315
+ updateStore(titleStore, { url: location.href, title: route?.title });
316
+ }
327
317
  }
328
318
  const contentOrLoader = content || getContent?.(params, this.shadowRoot || this);
329
319
  this.loading(route);
@@ -331,8 +321,8 @@ export class GemRouteElement extends GemElement<State> {
331
321
  this.#lastLoader = contentOrLoader;
332
322
  const isSomeLoader = () => this.#lastLoader === contentOrLoader;
333
323
  contentOrLoader
334
- .then((content) => {
335
- if (isSomeLoader()) this.#setContent(route, params, content);
324
+ .then((newContent) => {
325
+ if (isSomeLoader()) this.#setContent(route, params, newContent);
336
326
  })
337
327
  .catch((err) => {
338
328
  if (isSomeLoader()) this.error(err);
@@ -344,10 +334,7 @@ export class GemRouteElement extends GemElement<State> {
344
334
  }
345
335
 
346
336
  /**
347
- * @customElement gem-light-route
337
+ * @customElement gem-route
348
338
  */
349
- export class GemLightRouteElement extends GemRouteElement {
350
- constructor(options: ConstructorOptions = {}) {
351
- super({ ...options, isLight: true });
352
- }
353
- }
339
+ @shadow()
340
+ export class GemRouteElement extends GemLightRouteElement {}
@@ -7,46 +7,40 @@
7
7
  * 修改标题:titleStore 的 title 优先级高,比如 history 添加的 dialog Title
8
8
  *
9
9
  * - `<gem-route>` 匹配路由时自动设置 `route.title`
10
- * - `<gem-link>` 的 `doc-title` 属性和 `route.title`
11
- * - 修改路径时,`history` 默认设置 `title` 为空
10
+ * - 修改路径(查询参数)时,`history` 默认设置 `title` 为空,运行 Modal 设置临时标题
12
11
  * - 数据获取后,手动调用 `Title.setTitle`
13
12
  * - `<gem-title>` 作为默认值设置
14
13
  */
15
14
 
16
15
  import { GemElement, html } from '../../lib/element';
17
- import { attribute, boolattribute } from '../../lib/decorators';
16
+ import { attribute, boolattribute, effect, mounted, shadow } from '../../lib/decorators';
18
17
  import { updateStore, connect } from '../../lib/store';
19
18
  import { titleStore } from '../../lib/history';
20
- import { addMicrotask } from '../../lib/utils';
21
19
 
22
- const defaultTitle = document.title;
20
+ // 避免重定向时的中间状态标题
21
+ let timer = 0;
22
+ const setTitle = (documentTitle: string) => {
23
+ clearTimeout(timer);
24
+ timer = window.setTimeout(() => (document.title = documentTitle));
25
+ };
23
26
 
24
- let documentTitle = '';
25
- const setTitle = () => (document.title = documentTitle);
26
-
27
- function setDocumentTitle(str?: string | null, prefix = '', suffix = '') {
28
- const title = titleStore.title || str;
29
- if (title && title !== defaultTitle) {
27
+ function setDocumentTitle(defaultTitle?: string | null, prefix = '', suffix = '') {
28
+ const title = titleStore.title || defaultTitle;
29
+ if (title && title !== titleStore.defaultTitle) {
30
30
  GemTitleElement.title = title;
31
- documentTitle = prefix + GemTitleElement.title + suffix;
31
+ setTitle(prefix + GemTitleElement.title + suffix);
32
32
  } else {
33
- GemTitleElement.title = defaultTitle;
34
- documentTitle = GemTitleElement.title;
33
+ GemTitleElement.title = titleStore.defaultTitle;
34
+ setTitle(GemTitleElement.title);
35
35
  }
36
- // 避免重定向时的中间状态
37
- addMicrotask(setTitle);
38
36
  }
39
37
 
40
- connect(titleStore, setDocumentTitle);
41
-
42
- export const PREFIX = `${defaultTitle} | `;
43
- export const SUFFIX = ` - ${defaultTitle}`;
44
-
45
38
  /**
46
39
  * @customElement gem-title
47
40
  * @attr prefix
48
41
  * @attr suffix
49
42
  */
43
+ @shadow()
50
44
  export class GemTitleElement extends GemElement {
51
45
  @attribute prefix: string;
52
46
  @attribute suffix: string;
@@ -55,34 +49,23 @@ export class GemTitleElement extends GemElement {
55
49
  // 没有后缀的当前标题
56
50
  static title = document.title;
57
51
 
58
- /**@deprecated */
59
- static defaultPrefix = PREFIX;
60
- /**@deprecated */
61
- static defaultSuffix = SUFFIX;
62
-
63
52
  static setTitle(title: string) {
64
53
  updateStore(titleStore, { title });
65
54
  }
66
55
 
67
- constructor() {
68
- super();
69
- new MutationObserver(() => this.update()).observe(this, {
70
- characterData: true,
71
- childList: true,
72
- subtree: true,
73
- });
74
- }
56
+ @effect((i) => [i.inert])
57
+ #connectStore = () => !this.inert && connect(titleStore, this.update);
58
+
59
+ #ob = new MutationObserver(() => this.update());
75
60
 
76
- mounted = () => {
77
- this.effect(
78
- () => !this.inert && connect(titleStore, this.update),
79
- () => [this.inert],
80
- );
61
+ @mounted()
62
+ #obTextContent = () => {
63
+ this.#ob.observe(this, { characterData: true, childList: true, subtree: true });
81
64
  };
82
65
 
83
- render() {
84
- // 多个 <gem-title> 时,最终 document.title 按执行顺序决定
66
+ render = () => {
67
+ // 多个 <gem-title> 时,最终 document.title 按渲染顺序决定
85
68
  setDocumentTitle(this.textContent?.trim(), this.prefix, this.suffix);
86
69
  return html`${GemTitleElement.title}`;
87
- }
70
+ };
88
71
  }