@mantou/gem 2.1.1 → 2.2.1

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 (151) hide show
  1. package/dist/gem.es.js +2082 -2153
  2. package/dist/gem.umd.js +25 -199
  3. package/elements/base/dialog.d.ts +1 -5
  4. package/elements/base/dialog.d.ts.map +1 -1
  5. package/elements/base/dialog.js +2 -6
  6. package/elements/base/dialog.js.map +1 -1
  7. package/elements/base/gesture.d.ts +6 -15
  8. package/elements/base/gesture.d.ts.map +1 -1
  9. package/elements/base/gesture.js +38 -34
  10. package/elements/base/gesture.js.map +1 -1
  11. package/elements/base/link.d.ts +1 -17
  12. package/elements/base/link.d.ts.map +1 -1
  13. package/elements/base/link.js +12 -37
  14. package/elements/base/link.js.map +1 -1
  15. package/elements/base/modal-factory.d.ts +48 -38
  16. package/elements/base/modal-factory.d.ts.map +1 -1
  17. package/elements/base/modal-factory.js +14 -14
  18. package/elements/base/modal-factory.js.map +1 -1
  19. package/elements/base/reflect.d.ts +1 -5
  20. package/elements/base/reflect.d.ts.map +1 -1
  21. package/elements/base/reflect.js +8 -13
  22. package/elements/base/reflect.js.map +1 -1
  23. package/elements/base/route.d.ts +17 -17
  24. package/elements/base/route.d.ts.map +1 -1
  25. package/elements/base/route.js +15 -27
  26. package/elements/base/route.js.map +1 -1
  27. package/elements/base/title.d.ts +1 -6
  28. package/elements/base/title.d.ts.map +1 -1
  29. package/elements/base/title.js +3 -8
  30. package/elements/base/title.js.map +1 -1
  31. package/elements/base/unsafe.d.ts +1 -6
  32. package/elements/base/unsafe.d.ts.map +1 -1
  33. package/elements/base/unsafe.js +3 -9
  34. package/elements/base/unsafe.js.map +1 -1
  35. package/elements/base/use.d.ts +1 -4
  36. package/elements/base/use.d.ts.map +1 -1
  37. package/elements/base/use.js +4 -11
  38. package/elements/base/use.js.map +1 -1
  39. package/elements/link.js +1 -1
  40. package/elements/link.js.map +1 -1
  41. package/elements/route.js +1 -1
  42. package/elements/route.js.map +1 -1
  43. package/helper/hmr.d.ts +11 -0
  44. package/helper/hmr.d.ts.map +1 -0
  45. package/helper/hmr.js +255 -0
  46. package/helper/hmr.js.map +1 -0
  47. package/helper/i18n.d.ts +2 -2
  48. package/helper/i18n.d.ts.map +1 -1
  49. package/helper/i18n.js +5 -4
  50. package/helper/i18n.js.map +1 -1
  51. package/helper/logger.d.ts +1 -1
  52. package/helper/logger.d.ts.map +1 -1
  53. package/helper/logger.js +8 -5
  54. package/helper/logger.js.map +1 -1
  55. package/helper/mediaquery.js +1 -1
  56. package/helper/mediaquery.js.map +1 -1
  57. package/helper/ssr-shim.d.ts.map +1 -1
  58. package/helper/ssr-shim.js +2 -0
  59. package/helper/ssr-shim.js.map +1 -1
  60. package/helper/theme.d.ts +4 -5
  61. package/helper/theme.d.ts.map +1 -1
  62. package/helper/theme.js +3 -3
  63. package/helper/theme.js.map +1 -1
  64. package/index.d.ts +1 -3
  65. package/index.d.ts.map +1 -1
  66. package/index.js +1 -4
  67. package/index.js.map +1 -1
  68. package/lib/decorators.d.ts +9 -16
  69. package/lib/decorators.d.ts.map +1 -1
  70. package/lib/decorators.js +84 -71
  71. package/lib/decorators.js.map +1 -1
  72. package/lib/directive.d.ts +65 -0
  73. package/lib/directive.d.ts.map +1 -0
  74. package/lib/directive.js +58 -0
  75. package/lib/directive.js.map +1 -0
  76. package/lib/element.d.ts +10 -153
  77. package/lib/element.d.ts.map +1 -1
  78. package/lib/element.js +19 -480
  79. package/lib/element.js.map +1 -1
  80. package/lib/history.d.ts +67 -9
  81. package/lib/history.d.ts.map +1 -1
  82. package/lib/history.js +12 -12
  83. package/lib/history.js.map +1 -1
  84. package/lib/lit-html.d.ts +232 -0
  85. package/lib/lit-html.d.ts.map +1 -0
  86. package/lib/lit-html.js +987 -0
  87. package/lib/lit-html.js.map +1 -0
  88. package/lib/reactive.d.ts +123 -0
  89. package/lib/reactive.d.ts.map +1 -0
  90. package/lib/reactive.js +478 -0
  91. package/lib/reactive.js.map +1 -0
  92. package/lib/repeat.d.ts +126 -0
  93. package/lib/repeat.d.ts.map +1 -0
  94. package/lib/repeat.js +533 -0
  95. package/lib/repeat.js.map +1 -0
  96. package/lib/store.d.ts +13 -2
  97. package/lib/store.d.ts.map +1 -1
  98. package/lib/store.js +5 -4
  99. package/lib/store.js.map +1 -1
  100. package/lib/utils.d.ts +24 -15
  101. package/lib/utils.d.ts.map +1 -1
  102. package/lib/utils.js +25 -30
  103. package/lib/utils.js.map +1 -1
  104. package/lib/version.d.ts +1 -1
  105. package/lib/version.js +1 -1
  106. package/package.json +8 -9
  107. package/src/dist.ts +6 -8
  108. package/src/elements/base/dialog.ts +2 -6
  109. package/src/elements/base/gesture.ts +35 -36
  110. package/src/elements/base/link.ts +20 -46
  111. package/src/elements/base/modal-factory.ts +14 -14
  112. package/src/elements/base/reflect.ts +2 -6
  113. package/src/elements/base/route.ts +22 -33
  114. package/src/elements/base/title.ts +4 -9
  115. package/src/elements/base/unsafe.ts +4 -9
  116. package/src/elements/base/use.ts +4 -11
  117. package/src/elements/link.ts +1 -1
  118. package/src/elements/route.ts +1 -1
  119. package/src/helper/hmr.ts +305 -0
  120. package/src/helper/i18n.ts +7 -7
  121. package/src/helper/logger.ts +9 -5
  122. package/src/helper/mediaquery.ts +1 -1
  123. package/src/helper/ssr-shim.ts +3 -0
  124. package/src/helper/theme.ts +6 -6
  125. package/src/index.ts +1 -5
  126. package/src/lib/decorators.ts +101 -100
  127. package/src/lib/directive.ts +137 -0
  128. package/src/lib/element.ts +26 -614
  129. package/src/lib/history.ts +14 -15
  130. package/src/lib/lit-html.ts +1269 -0
  131. package/src/lib/reactive.ts +596 -0
  132. package/src/lib/repeat.ts +580 -0
  133. package/src/lib/store.ts +5 -4
  134. package/src/lib/utils.ts +40 -46
  135. package/src/lib/version.ts +1 -1
  136. package/src/test/elements/link.test.ts +2 -3
  137. package/src/test/elements/reflect.test.ts +13 -3
  138. package/src/test/elements/title.test.ts +2 -3
  139. package/src/test/gem-element/advance.test.ts +22 -14
  140. package/src/test/gem-element/basic.test.ts +23 -11
  141. package/src/test/gem-element/decorators.test.ts +14 -17
  142. package/src/test/gem-element/multi.test.ts +20 -11
  143. package/src/test/history.test.ts +1 -2
  144. package/src/test/store.test.ts +3 -4
  145. package/src/test/utils.test.ts +12 -14
  146. package/src/test/utils.ts +22 -0
  147. package/lib/directives.d.ts +0 -4
  148. package/lib/directives.d.ts.map +0 -1
  149. package/lib/directives.js +0 -5
  150. package/lib/directives.js.map +0 -1
  151. package/src/lib/directives.ts +0 -6
@@ -1,7 +1,7 @@
1
- import { GemElement } from '../../lib/element';
2
- import { connect, createStore } from '../../lib/store';
3
- import { history } from '../../lib/history';
4
1
  import { aria, mounted, shadow } from '../../lib/decorators';
2
+ import { history } from '../../lib/history';
3
+ import { GemElement } from '../../lib/reactive';
4
+ import { connect, createStore } from '../../lib/store';
5
5
 
6
6
  const open = Symbol('open mark');
7
7
 
@@ -32,7 +32,7 @@ export function createModalClass<T extends Record<string, unknown>>(options: T)
32
32
 
33
33
  /**@final */
34
34
  static get isOpen() {
35
- return this.store[open];
35
+ return ModalElement.store[open];
36
36
  }
37
37
 
38
38
  /**
@@ -41,19 +41,19 @@ export function createModalClass<T extends Record<string, unknown>>(options: T)
41
41
  * 浏览器 history 为异步 API,需要设置较长延迟;
42
42
  */
43
43
  static open(opts: T) {
44
- const instance = new this();
45
- this.instance = instance;
46
- this.inertStore = ([...document.body.children] as HTMLElement[]).filter((e) => !e.inert);
47
- this.inertStore.forEach((e) => (e.inert = true));
44
+ const instance = new ModalElement();
45
+ ModalElement.instance = instance;
46
+ ModalElement.inertStore = ([...document.body.children] as HTMLElement[]).filter((e) => !e.inert);
47
+ ModalElement.inertStore.forEach((e) => (e.inert = true));
48
48
  document.body.append(instance);
49
- const changeStore = () => this.store({ [open]: true, ...opts });
49
+ const changeStore = () => ModalElement.store({ [open]: true, ...opts });
50
50
  setTimeout(() => {
51
51
  changeStore();
52
52
  history.push({
53
53
  title: instance.label,
54
54
  open: changeStore,
55
- close: this.closeHandle.bind(this),
56
- shouldClose: this.shouldClose.bind(this),
55
+ close: ModalElement.closeHandle.bind(ModalElement),
56
+ shouldClose: ModalElement.shouldClose.bind(ModalElement),
57
57
  });
58
58
  }, 100);
59
59
  return final;
@@ -73,9 +73,9 @@ export function createModalClass<T extends Record<string, unknown>>(options: T)
73
73
  * history 中自动调用
74
74
  */
75
75
  static closeHandle() {
76
- this.inertStore.forEach((e) => (e.inert = false));
77
- this.instance?.remove();
78
- this.store({ [open]: false, ...options });
76
+ ModalElement.inertStore.forEach((e) => (e.inert = false));
77
+ ModalElement.instance?.remove();
78
+ ModalElement.store({ [open]: false, ...options });
79
79
  return final;
80
80
  }
81
81
 
@@ -1,13 +1,13 @@
1
1
  /**
2
- * - ref 读取时需要用到 `target` `dataset`
3
2
  * - 预渲染可能造成重复的元素,在序列化时可以用正则移除 GemReflectElement 渲染的从开始到结束连续的元素
4
3
  * - `replaceAll(/<!--gem-reflect-start-->.*?<!--gem-reflect-end-->/gs, '')`
5
4
  *
6
5
  * 不能在在顶级代码块中清除这些元素,因为重复加载该代码时会把正常 reflect 元素清除,
7
6
  * 比如 duoyun-ui 文档站加载示例元素时把 GemReflectElement 渲染的元素清除
8
7
  */
9
- import { GemElement } from '../../lib/element';
8
+
10
9
  import { attribute, effect, property, willMount } from '../../lib/decorators';
10
+ import { GemElement } from '../../lib/reactive';
11
11
 
12
12
  const START = 'gem-reflect-start';
13
13
  const END = 'gem-reflect-end';
@@ -25,9 +25,6 @@ const getReflectFragment = (startNode: Comment) => {
25
25
  return result;
26
26
  };
27
27
 
28
- /**
29
- * @customElement gem-reflect
30
- */
31
28
  export class GemReflectElement extends GemElement {
32
29
  @attribute method: 'prepend' | 'append';
33
30
 
@@ -40,7 +37,6 @@ export class GemReflectElement extends GemElement {
40
37
  @willMount()
41
38
  #init = () => {
42
39
  this.hidden = true;
43
- this.dataset.gemReflect = '';
44
40
  };
45
41
 
46
42
  #startNode = createReflectFragmentTag(START);
@@ -1,11 +1,11 @@
1
- import type { TemplateResult } from '../../lib/element';
2
- import { createState, GemElement, html } from '../../lib/element';
3
1
  import type { Emitter } from '../../lib/decorators';
4
- import { property, emitter, boolattribute, shadow, effect, template, light } from '../../lib/decorators';
5
- import type { Store } from '../../lib/store';
6
- import { createStore, connect } from '../../lib/store';
2
+ import { adoptedStyle, boolattribute, effect, emitter, light, property, shadow, template } from '../../lib/decorators';
3
+ import { createState, css, GemElement, html } from '../../lib/element';
7
4
  import type { UpdateHistoryParams } from '../../lib/history';
8
- import { titleStore, history } from '../../lib/history';
5
+ import { history, titleStore } from '../../lib/history';
6
+ import type { TemplateResult } from '../../lib/lit-html';
7
+ import type { Store } from '../../lib/store';
8
+ import { connect, createStore } from '../../lib/store';
9
9
  import type { QueryString } from '../../lib/utils';
10
10
  import { addListener } from '../../lib/utils';
11
11
 
@@ -30,11 +30,10 @@ class ParamsRegExp extends RegExp {
30
30
 
31
31
  type Params = Record<string, string>;
32
32
  declare global {
33
- interface Window {
34
- // https://bugzilla.mozilla.org/show_bug.cgi?id=1731418
35
- // https://github.com/WebKit/standards-positions/issues/61
36
- URLPattern: any;
37
- }
33
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1731418
34
+ // https://github.com/WebKit/standards-positions/issues/61
35
+ // eslint-disable-next-line no-var
36
+ var URLPattern: any;
38
37
  }
39
38
 
40
39
  /**
@@ -51,8 +50,8 @@ declare global {
51
50
  * ```
52
51
  */
53
52
  export function matchPath(pattern: string, path: string) {
54
- if (window.URLPattern) {
55
- const urLPattern = new window.URLPattern({ pathname: pattern });
53
+ if (globalThis.URLPattern) {
54
+ const urLPattern = new URLPattern({ pathname: pattern });
56
55
  const matchResult = urLPattern.exec({ pathname: path }) || urLPattern.exec({ pathname: `${path}/` });
57
56
  if (!matchResult) return null;
58
57
  return matchResult.pathname.groups as Params;
@@ -132,13 +131,6 @@ export type RouteTrigger = {
132
131
 
133
132
  const scrollPositionMap = new Map<string, number>();
134
133
 
135
- /**
136
- * @customElement gem-light-route
137
- * @attr inert 暂停路由更新
138
- * @fires routechange
139
- * @fires error
140
- * @fires loading
141
- */
142
134
  @light({ penetrable: true })
143
135
  export class GemLightRouteElement extends GemElement {
144
136
  @boolattribute transition: boolean;
@@ -178,7 +170,7 @@ export class GemLightRouteElement extends GemElement {
178
170
  static findRoute = (target: RouteItem[] | RoutesObject = [], path: string) => {
179
171
  let defaultRoute: RouteItem | null = null;
180
172
  let routes: RouteItem[];
181
- if (target instanceof Array) {
173
+ if (Array.isArray(target)) {
182
174
  routes = target;
183
175
  } else {
184
176
  routes = Object.values(target);
@@ -283,15 +275,7 @@ export class GemLightRouteElement extends GemElement {
283
275
  return html`${content}`;
284
276
  }
285
277
 
286
- return html`
287
- <style>
288
- :host(:where(:not([hidden]))),
289
- :not(:defined) {
290
- display: contents;
291
- }
292
- </style>
293
- ${content === INIT_CONTENT ? html`<slot></slot>` : content}
294
- `;
278
+ return content === INIT_CONTENT ? html`<slot></slot>` : content;
295
279
  };
296
280
 
297
281
  // 重写 `update`,并暴露为公共字段
@@ -331,8 +315,13 @@ export class GemLightRouteElement extends GemElement {
331
315
  };
332
316
  }
333
317
 
334
- /**
335
- * @customElement gem-route
336
- */
318
+ const style = css`
319
+ :host(:where(:not([hidden]))),
320
+ :not(:defined) {
321
+ display: contents;
322
+ }
323
+ `;
324
+
337
325
  @shadow()
326
+ @adoptedStyle(style)
338
327
  export class GemRouteElement extends GemLightRouteElement {}
@@ -12,16 +12,16 @@
12
12
  * - `<gem-title>` 作为默认值设置
13
13
  */
14
14
 
15
- import { GemElement, html } from '../../lib/element';
16
15
  import { attribute, boolattribute, effect, mounted, shadow, template } from '../../lib/decorators';
17
- import { connect } from '../../lib/store';
18
16
  import { titleStore } from '../../lib/history';
17
+ import { GemElement, html } from '../../lib/reactive';
18
+ import { connect } from '../../lib/store';
19
19
 
20
20
  // 避免重定向时的中间状态标题
21
- let timer = 0;
21
+ let timer: ReturnType<typeof setTimeout> | number = 0;
22
22
  const setTitle = (documentTitle: string) => {
23
23
  clearTimeout(timer);
24
- timer = window.setTimeout(() => (document.title = documentTitle));
24
+ timer = setTimeout(() => (document.title = documentTitle));
25
25
  };
26
26
 
27
27
  function setDocumentTitle(defaultTitle?: string | null, prefix = '', suffix = '') {
@@ -35,11 +35,6 @@ function setDocumentTitle(defaultTitle?: string | null, prefix = '', suffix = ''
35
35
  }
36
36
  }
37
37
 
38
- /**
39
- * @customElement gem-title
40
- * @attr prefix
41
- * @attr suffix
42
- */
43
38
  @shadow()
44
39
  export class GemTitleElement extends GemElement {
45
40
  @attribute prefix: string;
@@ -1,12 +1,6 @@
1
- import { GemElement } from '../../lib/element';
2
1
  import { attribute, shadow, template } from '../../lib/decorators';
3
- import { raw } from '../../lib/utils';
2
+ import { GemElement } from '../../lib/reactive';
4
3
 
5
- /**
6
- * @customElement gem-unsafe
7
- * @attr content
8
- * @attr contentcss
9
- */
10
4
  @shadow()
11
5
  export class GemUnsafeElement extends GemElement {
12
6
  @attribute content: string;
@@ -14,16 +8,17 @@ export class GemUnsafeElement extends GemElement {
14
8
 
15
9
  @template()
16
10
  #content = () => {
17
- this.shadowRoot!.innerHTML = raw`
11
+ const style = /*html*/ `
18
12
  <style>
19
13
  :host(:where(:not([hidden]))) {
20
14
  display: contents;
21
15
  }
22
16
  ${this.contentcss}
23
17
  </style>
24
- ${this.content}
25
18
  `;
26
19
 
20
+ this.shadowRoot!.innerHTML = this.content + style.trim();
21
+
27
22
  // 不要更新内容
28
23
  return undefined;
29
24
  };
@@ -1,8 +1,7 @@
1
- import { createCSSSheet, GemElement, html } from '../../lib/element';
2
1
  import { adoptedStyle, attribute, property, shadow, state, template } from '../../lib/decorators';
3
- import { css } from '../../lib/utils';
2
+ import { css, GemElement, html } from '../../lib/reactive';
4
3
 
5
- const styles = createCSSSheet(css`
4
+ const styles = css`
6
5
  :host(:where(:not([hidden]))) {
7
6
  position: relative;
8
7
  display: inline-flex;
@@ -12,7 +11,7 @@ const styles = createCSSSheet(css`
12
11
  width: 100%;
13
12
  height: 100%;
14
13
  }
15
- `);
14
+ `;
16
15
 
17
16
  const eleCache = new Map<string, HTMLTemplateElement>();
18
17
  /**
@@ -20,9 +19,6 @@ const eleCache = new Map<string, HTMLTemplateElement>();
20
19
  * 此元素用来模拟 `<use>`,
21
20
  * 由于是复制元素,所以不能像 `<use>` 一样自动更新
22
21
  * slot 支持为 icon 添加效果,如涟漪,徽章
23
- *
24
- * @customElement gem-use
25
- * @attr selector
26
22
  */
27
23
  @shadow()
28
24
  @adoptedStyle(styles)
@@ -52,9 +48,6 @@ export class GemUseElement extends GemElement {
52
48
 
53
49
  @template()
54
50
  #content = () => {
55
- return html`
56
- ${this.#getContent()}
57
- <slot></slot>
58
- `;
51
+ return html`${this.#getContent()}<slot></slot>`;
59
52
  };
60
53
  }
@@ -1,4 +1,4 @@
1
- import { GemLinkElement, GemActiveLinkElement } from './base/link';
1
+ import { GemActiveLinkElement, GemLinkElement } from './base/link';
2
2
 
3
3
  export * from './base/link';
4
4
 
@@ -1,4 +1,4 @@
1
- import { GemRouteElement, GemLightRouteElement } from './base/route';
1
+ import { GemLightRouteElement, GemRouteElement } from './base/route';
2
2
 
3
3
  export * from './base/route';
4
4
 
@@ -0,0 +1,305 @@
1
+ import { attribute, boolattribute, emitter, numattribute, property, state } from '../lib/decorators';
2
+ import { GemElement, type Metadata, UpdateToken } from '../lib/reactive';
3
+ import type { Store } from '../lib/store';
4
+ import { Logger } from './logger';
5
+
6
+ const logger = new Logger('HMR');
7
+
8
+ const nativeDefineElement = window.customElements.define.bind(window.customElements);
9
+
10
+ const cache = new Map<string, Element[]>();
11
+ function updateElement(name: string, fn: (ele: Element) => void) {
12
+ if (!cache.has(name)) {
13
+ const elements = [];
14
+ const temp: Element[] = [document.documentElement];
15
+ while (temp.length) {
16
+ const element = temp.pop()!;
17
+ if (element.tagName.toLowerCase() === name) elements.push(element);
18
+ temp.push(...[...element.children, ...(element.shadowRoot?.children || [])].reverse());
19
+ }
20
+ cache.set(name, elements);
21
+ }
22
+ const elements = cache.get(name)!;
23
+ if (elements.length === 0) {
24
+ const ele = document.createElement(name);
25
+ Element.prototype.remove.apply(ele);
26
+ fn(ele);
27
+ } else {
28
+ elements.forEach(fn);
29
+ }
30
+ queueMicrotask(() => cache.delete(name));
31
+ }
32
+
33
+ function getMetadata(cons: any): Metadata {
34
+ return (cons as any)[Symbol.metadata] || {};
35
+ }
36
+
37
+ function getHmrMethodKeys(obj: any) {
38
+ return Object.getOwnPropertyNames(obj).filter((key) => key.startsWith('_hmr_'));
39
+ }
40
+
41
+ /** 不支持修改 store */
42
+ function checkMetadataNeedReload({ mode, penetrable, noBlocking, observedStores }: Metadata, newMetadata: Metadata) {
43
+ const stringifyStores = (stores?: Store<any>[]) =>
44
+ JSON.stringify(
45
+ stores?.map((store) => JSON.stringify({ ...store }, (_, v) => (typeof v === 'function' ? v.toString() : v))),
46
+ );
47
+ if (
48
+ mode !== newMetadata.mode ||
49
+ penetrable !== newMetadata.penetrable ||
50
+ noBlocking !== newMetadata.noBlocking ||
51
+ stringifyStores(observedStores) !== stringifyStores(newMetadata.observedStores)
52
+ ) {
53
+ return true;
54
+ }
55
+ }
56
+
57
+ /** 不支持删减函数字段,因为原函数可能已经被绑定 */
58
+ function checkClassNeedReload(existed: CustomElementConstructor, newClass: CustomElementConstructor) {
59
+ if (
60
+ getHmrMethodKeys(existed).join() !== getHmrMethodKeys(newClass).join() ||
61
+ getHmrMethodKeys(existed.prototype).join() !== getHmrMethodKeys(newClass.prototype).join()
62
+ ) {
63
+ return true;
64
+ }
65
+ }
66
+
67
+ /** 不支持删减实例其他字段,因为新实例总是使用老 Class 定义 */
68
+ function checkFieldsNeedReload({ instanceFields }: ReturnType<typeof getFields>) {
69
+ if (instanceFields.other?.add?.length || instanceFields.other?.remove?.length) {
70
+ return true;
71
+ }
72
+ }
73
+
74
+ function diffArr<T>(oldList: T[], newList: T[], fn: (i: T) => string = (e) => String(e)) {
75
+ const getMap = (list: T[]) => new Map(list.map((e) => [fn(e), e]));
76
+ const oldMap = getMap(oldList);
77
+ const newMap = getMap(newList);
78
+ const result = { remove: [] as T[], add: [] as T[], some: [] as T[], all: [] as T[] };
79
+ getMap([...oldList, ...newList]).forEach((e, k) => {
80
+ result.all.push(e);
81
+ if (newMap.has(k) && oldMap.has(k)) {
82
+ result.some.push(e);
83
+ } else if (!newMap.has(k)) {
84
+ result.remove.push(e);
85
+ } else if (!oldMap.has(k)) {
86
+ result.add.push(e);
87
+ }
88
+ });
89
+ return result;
90
+ }
91
+
92
+ function diffRecord<T>(oldRecord: Partial<Record<string, T[]>>, newRecord: Partial<Record<string, T[]>>) {
93
+ const oldKeys = Object.keys(oldRecord);
94
+ const newKeys = Object.keys(newRecord);
95
+ return diffArr(oldKeys, newKeys);
96
+ }
97
+
98
+ type HasFieldsRecordClass = { _defined_fields_?: [string, string, boolean][] };
99
+ function getFields(existed: CustomElementConstructor, newClass: CustomElementConstructor) {
100
+ const translate = (cls: HasFieldsRecordClass) => {
101
+ const fieldList = (cls._defined_fields_ || []).map(([name, type, isStatic]) => ({ name, type, isStatic }));
102
+ const fieldGroup = Object.groupBy(fieldList, ({ isStatic }) => (isStatic ? 'staticFields' : 'instanceFields'));
103
+ const groupBy = (list: typeof fieldGroup.staticFields) => Object.groupBy(list || [], ({ type }) => type);
104
+ return {
105
+ staticFields: groupBy(fieldGroup.staticFields),
106
+ instanceFields: groupBy(fieldGroup.instanceFields),
107
+ };
108
+ };
109
+ const oldFields = translate(existed as unknown as HasFieldsRecordClass);
110
+ const newFields = translate(newClass as unknown as HasFieldsRecordClass);
111
+ const result = Object.fromEntries(
112
+ Object.entries(oldFields).map(([classFieldType]: [keyof typeof oldFields, any]) => [
113
+ classFieldType,
114
+ Object.fromEntries(
115
+ diffRecord(oldFields[classFieldType], newFields[classFieldType]).all.map((type) => [
116
+ type,
117
+ diffArr(oldFields[classFieldType][type] || [], newFields[classFieldType][type] || [], ({ name }) => name),
118
+ ]),
119
+ ),
120
+ ]),
121
+ );
122
+ return result as Record<keyof typeof oldFields, (typeof result)[0]>;
123
+ }
124
+
125
+ const deleteProperty = Reflect.deleteProperty;
126
+ const getProperty = Reflect.get;
127
+ const getPrototypeOf = Reflect.getPrototypeOf;
128
+ const setProperty = (target: any, propertyKey: string | symbol, value: any) =>
129
+ Reflect.defineProperty(target, propertyKey, {
130
+ configurable: true,
131
+ enumerable: true,
132
+ writable: true,
133
+ value,
134
+ });
135
+ const safeProp = new Set(['part', 'slot', 'other']);
136
+ const descMap = {
137
+ attribute,
138
+ boolattribute,
139
+ numattribute,
140
+ property,
141
+ state,
142
+ emitter,
143
+ };
144
+
145
+ function setArrValue<T>(obj: any, field: string, val: T[] = []) {
146
+ if (!Object.getOwnPropertyDescriptor(obj, field)) {
147
+ setProperty(obj, field, val);
148
+ } else {
149
+ const old = obj[field] as T[];
150
+ old.length = 0;
151
+ old.push(...val);
152
+ }
153
+ }
154
+
155
+ declare global {
156
+ interface Window {
157
+ _hmrClassRegistry: Map<string, any>;
158
+ _hmrRegisterClass: (name: string) => (cls: HasFieldsRecordClass, ctx: ClassDecoratorContext) => void;
159
+ }
160
+ }
161
+
162
+ window._hmrClassRegistry = new Map();
163
+ window._hmrRegisterClass =
164
+ (name: string) =>
165
+ (cls: HasFieldsRecordClass, { addInitializer }: ClassDecoratorContext) => {
166
+ addInitializer(() => {
167
+ const existed = window._hmrClassRegistry.get(name);
168
+
169
+ if (!existed) {
170
+ window._hmrClassRegistry.set(name, cls);
171
+ return;
172
+ }
173
+
174
+ logger.info(`class <${name}> update,`, { existed, cls });
175
+
176
+ let current: any[] = [existed, cls];
177
+ while (current) {
178
+ const [oldClass, newClass] = current;
179
+ if (oldClass === newClass || current.some((e) => e === GemElement || e === Function.prototype)) break;
180
+ if (current.some((e) => !e) || checkClassNeedReload(oldClass, newClass)) {
181
+ location.reload();
182
+ return;
183
+ }
184
+
185
+ // 修正 `instanceof`
186
+ setProperty(newClass, Symbol.hasInstance, function hasInstance(instance: any) {
187
+ const isOldInstance = instance instanceof oldClass;
188
+ if (isOldInstance) return true;
189
+ setProperty(newClass, Symbol.hasInstance, undefined);
190
+ const isNew = instance instanceof newClass;
191
+ setProperty(newClass, Symbol.hasInstance, hasInstance);
192
+ return isNew;
193
+ });
194
+
195
+ const oldMetadata = getMetadata(oldClass);
196
+ const newMetadata = getMetadata(newClass);
197
+
198
+ if (checkMetadataNeedReload(oldMetadata, newMetadata)) {
199
+ location.reload();
200
+ return;
201
+ }
202
+
203
+ const fields = getFields(oldClass, newClass);
204
+
205
+ if (checkFieldsNeedReload(fields)) {
206
+ location.reload();
207
+ return;
208
+ }
209
+
210
+ Object.entries(fields.staticFields).forEach(([type, { remove, add }]) => {
211
+ remove.forEach((prop) => {
212
+ if (safeProp.has(type)) {
213
+ deleteProperty(oldClass, prop.name);
214
+ }
215
+ });
216
+ add.forEach((prop) => {
217
+ if (safeProp.has(type)) {
218
+ setProperty(oldClass, prop.name, getProperty(newClass, prop.name));
219
+ }
220
+ });
221
+ });
222
+
223
+ const instanceFieldsEntries = Object.entries(fields.instanceFields);
224
+ // 删除的属性重新加回来要先清除占位值
225
+ instanceFieldsEntries.forEach(([type, { add }]) => {
226
+ add.forEach((prop) => {
227
+ if (type in descMap) {
228
+ deleteProperty(oldClass.prototype, prop.name);
229
+ }
230
+ });
231
+ });
232
+ updateElement(name, (element) => {
233
+ instanceFieldsEntries.forEach(([type, { add, remove }]) => {
234
+ add.forEach((prop) => {
235
+ if (type in descMap) {
236
+ (descMap as any)[type](undefined, {
237
+ name: prop.name,
238
+ metadata: oldMetadata,
239
+ addInitializer: (fn: () => void) => fn.apply(element),
240
+ } as ClassMemberDecoratorContext);
241
+ }
242
+ });
243
+ remove.forEach((prop) => {
244
+ setProperty(element, prop.name, undefined);
245
+ });
246
+ });
247
+ });
248
+ // 删除的属性在 proto 上添加占位符
249
+ instanceFieldsEntries.forEach(([type, { remove }]) => {
250
+ remove.forEach((prop) => {
251
+ if (type in descMap) {
252
+ // attribute 映射 prop 没有移除
253
+ setProperty(oldClass.prototype, prop.name, undefined);
254
+ }
255
+ });
256
+ });
257
+
258
+ setArrValue(oldMetadata, 'definedParts', newMetadata.definedParts);
259
+ setArrValue(oldMetadata, 'definedSlots', newMetadata.definedSlots);
260
+ setArrValue(oldMetadata, 'observedProperties', newMetadata.observedProperties);
261
+ setArrValue(oldMetadata, 'observedAttributes', newMetadata.observedAttributes);
262
+ setArrValue(oldMetadata, 'definedEvents', newMetadata.definedEvents);
263
+ setArrValue(oldMetadata, 'definedCSSStates', newMetadata.definedCSSStates);
264
+ setArrValue(oldMetadata, 'adoptedStyleSheets', newMetadata.adoptedStyleSheets);
265
+ setArrValue(oldClass, '_defined_fields_', newClass._defined_fields_);
266
+
267
+ [
268
+ [oldClass, newClass],
269
+ [oldClass.prototype, newClass.prototype],
270
+ ].forEach(([existedObj, newObj]) => {
271
+ Object.assign(
272
+ existedObj,
273
+ Object.fromEntries(
274
+ getHmrMethodKeys(newObj)
275
+ .filter((key) => existedObj[key].toString() !== newObj[key].toString())
276
+ .map((key) => [key, newObj[key]]),
277
+ ),
278
+ );
279
+ });
280
+
281
+ current = [getPrototypeOf(oldClass), getPrototypeOf(newClass)];
282
+ }
283
+ });
284
+ };
285
+
286
+ window.customElements.define = (name: string, cls: CustomElementConstructor) => {
287
+ const existed = customElements.get(name);
288
+
289
+ if (!existed) {
290
+ nativeDefineElement(name, cls);
291
+ return;
292
+ }
293
+
294
+ // 等待类更新
295
+ queueMicrotask(() => {
296
+ logger.info(`<${name}> update,`, { cls });
297
+
298
+ updateElement(name, (element) => {
299
+ // 触发样式更新,支持 Light DOM
300
+ element.after(element);
301
+ // 重新渲染
302
+ (element as any)[UpdateToken]?.();
303
+ });
304
+ });
305
+ };
@@ -1,9 +1,8 @@
1
+ import type { RouteTrigger } from '../elements/base/route';
1
2
  import type { UpdateHistoryParams } from '../lib/history';
2
- import type { TemplateResult } from '../lib/element';
3
- import { html, UpdateToken } from '../lib/element';
3
+ import type { TemplateResult } from '../lib/lit-html';
4
+ import { html, UpdateToken } from '../lib/reactive';
4
5
  import { GemError } from '../lib/utils';
5
- import type { RouteTrigger } from '../elements/base/route';
6
-
7
6
  import { logger } from './logger';
8
7
 
9
8
  // Hello, $1
@@ -91,9 +90,10 @@ export class I18n<T = Record<string, Msg>> implements RouteTrigger {
91
90
  return parts.shift();
92
91
  case 'path':
93
92
  return pathname.split('/')[1];
94
- default:
93
+ default: {
95
94
  const fragment = (this.urlParamsName && new URLSearchParams(search).get(this.urlParamsName)) || '';
96
95
  return fragment in this.resources ? fragment : '';
96
+ }
97
97
  }
98
98
  }
99
99
 
@@ -198,7 +198,7 @@ export class I18n<T = Record<string, Msg>> implements RouteTrigger {
198
198
  if (localPackString) {
199
199
  try {
200
200
  pack = JSON.parse(localPackString);
201
- } catch (e) {
201
+ } catch {
202
202
  pack = await fetchPack();
203
203
  }
204
204
  } else {
@@ -212,7 +212,7 @@ export class I18n<T = Record<string, Msg>> implements RouteTrigger {
212
212
  if (!this.#inConstructor) {
213
213
  // 更新所有元素,可能会更新许多不必要的元素
214
214
  const temp: Element[] = [document.documentElement];
215
- while (!!temp.length) {
215
+ while (temp.length) {
216
216
  const element = temp.pop()!;
217
217
  (element as any)[UpdateToken]?.();
218
218
  // 和 gem 更新机制一样使用深度优先