@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
@@ -183,7 +183,7 @@ class GemHistory extends EventTarget {
183
183
  // `<gem-**>` 只读写父应用 `basePath`
184
184
  const gemHistory = new GemHistory();
185
185
 
186
- const [gemTitleStore, updateTitleStore] = useStore({ title: '' });
186
+ const [gemTitleStore, updateTitleStore] = useStore({ defaultTitle: document.title, url: '', title: '' });
187
187
 
188
188
  const _GEMHISTORY = { history: gemHistory, titleStore: gemTitleStore, basePathStore: gemBasePathStore };
189
189
 
@@ -213,8 +213,9 @@ if (!window._GEMHISTORY) {
213
213
 
214
214
  if (!nativeHistory.state) {
215
215
  // 初始化 historyState
216
- const { pathname, search, hash } = location;
217
- gemHistory.replace({ path: pathname, query: search, hash });
216
+ const { protocol, pathname, search, hash } = location;
217
+ // 支持在 `data:` `blob:` 中 `import`
218
+ if (protocol.startsWith('http')) gemHistory.replace({ path: pathname, query: search, hash });
218
219
  } else if (nativeHistory.state.$hasCloseHandle) {
219
220
  updateHistoryStore(nativeHistory.state);
220
221
  const params = normalizeParams({ title: document.title });
package/src/lib/store.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { addMicrotask, GemError, NonPrimitive } from './utils';
1
+ import type { NonPrimitive } from './utils';
2
+ import { addMicrotask, GemError } from './utils';
2
3
 
3
4
  export const StoreListenerMap = new WeakMap<NonPrimitive, Set<() => void>>();
4
5
 
package/src/lib/utils.ts CHANGED
@@ -1,37 +1,18 @@
1
- const microtaskSet = new Set<() => void>();
1
+ let microtaskSet = new Set<() => void>();
2
2
  export function addMicrotask(func: () => void) {
3
3
  if (typeof func !== 'function') return;
4
4
  if (!microtaskSet.size) {
5
5
  // delayed execution callback after updating store
6
6
  globalThis.queueMicrotask(() => {
7
- microtaskSet.forEach((func) => func());
8
- microtaskSet.clear();
7
+ const set = microtaskSet;
8
+ microtaskSet = new Set();
9
+ set.forEach((f) => f());
9
10
  });
10
11
  }
11
12
  microtaskSet.delete(func);
12
13
  microtaskSet.add(func);
13
14
  }
14
15
 
15
- const microtaskStack: (() => void)[] = [];
16
-
17
- function execMicrotaskStack() {
18
- for (let i = microtaskStack.length - 1; i >= 0; i--) {
19
- microtaskStack[i]();
20
- }
21
- microtaskStack.length = 0;
22
- }
23
-
24
- /**
25
- * 添加回调函数到微任务队列栈;
26
- * 先进后执行,使用这个函数可以改变嵌套的元素 `mounted` 的顺序;
27
- */
28
- export function addMicrotaskToStack(func: () => void) {
29
- if (!microtaskStack.length) {
30
- addMicrotask(execMicrotaskStack);
31
- }
32
- microtaskStack.push(func);
33
- }
34
-
35
16
  // 不编码 hash 用于比较
36
17
  export function absoluteLocation(currentPath = '', relativePath = '') {
37
18
  const { pathname, search, hash } = new URL(relativePath, location.origin + currentPath);
@@ -235,74 +216,17 @@ export function raw(arr: TemplateStringsArray, ...args: any[]) {
235
216
  }
236
217
 
237
218
  // 写 css 文本,在 CSSStyleSheet 中使用
238
- export function css(arr: TemplateStringsArray, ...args: any[]) {
239
- return raw(arr, ...args);
240
- }
241
-
242
- // 跨多个 gem 工作
243
- export const SheetToken = Symbol.for('gem@sheetToken');
244
-
245
- export type Sheet<T> = {
246
- [P in keyof T]: P;
247
- } & { [SheetToken]: CSSStyleSheet };
248
-
249
- export type StyledType = 'id' | 'class' | 'keyframes';
250
- export interface StyledValueObject {
251
- styledContent: string;
252
- type: StyledType;
253
- }
254
- export interface StyledKeyValuePair {
255
- [key: string]: StyledValueObject;
256
- }
257
-
258
- /**
259
- *
260
- * 创建 style sheet 用于 `adoptedStyleSheets`,不支持样式更新
261
- * @param rules string | Record<string, string> 不能动态更新的 css
262
- * @param mediaQuery string 媒体查询 StyleSheet.media.mediaText
263
- */
264
- export function createCSSSheet<T extends StyledKeyValuePair>(rules: T | string, mediaQuery = ''): Sheet<T> {
265
- const styleSheet = new CSSStyleSheet();
266
- const sheet: any = {};
267
- styleSheet.media.mediaText = mediaQuery;
268
- let style = '';
269
- if (typeof rules === 'string') {
270
- style = rules;
271
- } else {
272
- Object.keys(rules).forEach((key: keyof T) => {
273
- sheet[key] = `${key as string}-${randomStr()}`;
274
- style += rules[key].styledContent.replace(/&/g, sheet[key]);
275
- });
276
- }
277
- styleSheet.replaceSync(style);
278
- sheet[SheetToken] = styleSheet;
279
- return sheet as Sheet<T>;
280
- }
219
+ export const css = raw;
281
220
 
282
221
  export function randomStr(len = 5): string {
283
- const str = Math.random().toString(36).slice(2).slice(0, len);
222
+ const str = Math.random()
223
+ .toString(36)
224
+ .slice(2, 2 + len);
284
225
  if (str.length < len) return str + randomStr(len - str.length);
285
226
  return str;
286
227
  }
287
228
 
288
- const nestingRuleRegExp = new RegExp('&.*{((.|\n)*)}', 'g');
289
- // 只支持一层嵌套
290
- // https://drafts.csswg.org/css-nesting-1/
291
- // https://bugzilla.mozilla.org/show_bug.cgi?id=1648037
292
- // https://bugs.webkit.org/show_bug.cgi?id=223497
293
- function flatStyled(style: string, type: StyledType): StyledValueObject {
294
- const nestingRules: string[] = [];
295
- let styledContent =
296
- `& {${style.replace(nestingRuleRegExp, (substr) => {
297
- nestingRules.push(substr);
298
- return '';
299
- })}}` + nestingRules.join('');
300
- styledContent = styledContent.replace(/&/g, type === 'class' ? '.&' : '#&');
301
- return { styledContent, type };
302
- }
303
-
304
229
  // 写 css 文本,在 CSSStyleSheet 中使用,使用 styed-components 高亮
305
- // 暂时不支持 `at` 规则
306
230
  //
307
231
  // createCSSSheet({
308
232
  // red: styled.class`
@@ -312,21 +236,7 @@ function flatStyled(style: string, type: StyledType): StyledValueObject {
312
236
  // }
313
237
  // `,
314
238
  // });
315
- export const styled = {
316
- class: (arr: TemplateStringsArray, ...args: any[]) => {
317
- const style = raw(arr, ...args);
318
- return flatStyled(style, 'class');
319
- },
320
- id: (arr: TemplateStringsArray, ...args: any[]) => {
321
- const style = raw(arr, ...args);
322
- return flatStyled(style, 'id');
323
- },
324
- keyframes: (arr: TemplateStringsArray, ...args: any[]): StyledValueObject => {
325
- const keyframesContent = raw(arr, ...args);
326
- const styledContent = `@keyframes & {${keyframesContent}}`;
327
- return { styledContent, type: 'keyframes' };
328
- },
329
- };
239
+ export const styled = { class: raw };
330
240
 
331
241
  export function camelToKebabCase(str: string) {
332
242
  return str.replace(/[A-Z]/g, ($1: string) => '-' + $1.toLowerCase());
@@ -337,9 +247,8 @@ export function kebabToCamelCase(str: string) {
337
247
  }
338
248
 
339
249
  export function cleanObject<T extends Record<string, unknown>>(o: T) {
340
- Object.keys(o).forEach((key: string) => {
341
- const k = key as keyof T;
342
- delete o[k];
250
+ Object.keys(o).forEach((key: keyof T) => {
251
+ delete o[key];
343
252
  });
344
253
  return o;
345
254
  }
@@ -360,17 +269,6 @@ export function isArrayChange(newValues: any[], oldValues: any[]) {
360
269
  return false;
361
270
  }
362
271
 
363
- export function removeItems(target: any[], items: any[]) {
364
- const set = new Set(items);
365
- return target.filter((e) => {
366
- if (set.has(e)) {
367
- set.delete(e);
368
- return false;
369
- }
370
- return true;
371
- });
372
- }
373
-
374
272
  export function objectMapToString<T = any>(
375
273
  object: Record<string, T>,
376
274
  separate: string,
@@ -385,7 +283,14 @@ export function objectMapToString<T = any>(
385
283
  }
386
284
 
387
285
  // Wait: Typescript lib dom CSSStyleDeclaration anchor position
388
- type StyleProp = keyof CSSStyleDeclaration | `--${string}` | 'anchorDefault' | 'anchorName';
286
+ type StyleProp =
287
+ | keyof CSSStyleDeclaration
288
+ | `--${string}`
289
+ | 'scrollbarWidth'
290
+ | 'scrollbarColor'
291
+ | 'positionAnchor'
292
+ | 'anchorDefault'
293
+ | 'anchorName';
389
294
 
390
295
  export type StyleObject = Partial<Record<StyleProp, string | number | false | undefined | null>>;
391
296
 
@@ -413,94 +318,3 @@ declare global {
413
318
  interface PropertyIndexedKeyframes extends StyleObject {}
414
319
  interface Keyframe extends StyleObject {}
415
320
  }
416
-
417
- /**
418
- * @example
419
- * ```css
420
- * animation: 150ms ease 0ms showMask;`
421
- * @keyframes showMask {
422
- * from {
423
- * opacity: 0;
424
- * }
425
- * }
426
- * ```
427
- */
428
- // export function createCSSAnimation(
429
- // keyframes: PropertyIndexedKeyframes | Keyframe[],
430
- // options?: number | (KeyframeEffectOptions & { name: string }),
431
- // ) {
432
- // const frames = new Map<number, StyleObject>();
433
- // if (Array.isArray(keyframes)) {
434
- // keyframes.forEach((keyframe, index) => {
435
- // const offset = keyframes.length === 1 ? 1 : keyframe.offset || index / (keyframes.length - 1);
436
- // frames.set(offset, {
437
- // ...keyframe,
438
- // // https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API/Keyframe_Formats#attributes
439
- // cssOffset: undefined,
440
- // offset: keyframe.cssOffset,
441
- // composite: undefined,
442
- // animationComposition: keyframe.composite,
443
- // easing: undefined,
444
- // animationTimingFunction: keyframe.easing,
445
- // } as StyleObject);
446
- // });
447
- // } else {
448
- // const offsetList: (number | null)[] = Array.isArray(keyframes.offset)
449
- // ? keyframes.offset
450
- // : keyframes.offset === undefined
451
- // ? []
452
- // : [keyframes.offset];
453
- // if (offsetList.length && offsetList.at(-1) !== 1) offsetList.push(1);
454
-
455
- // const setStyle = (offset: number, key: string, value: string | number | null | undefined) => {
456
- // const style = frames.get(offset) || {};
457
- // switch (key) {
458
- // case 'offset':
459
- // break;
460
- // case 'cssOffset':
461
- // Reflect.set(style, 'offset', value);
462
- // break;
463
- // case 'composite':
464
- // Reflect.set(style, 'animationComposition', value);
465
- // break;
466
- // case 'easing':
467
- // Reflect.set(style, 'animationTimingFunction', value);
468
- // break;
469
- // default:
470
- // Reflect.set(style, key, value);
471
- // }
472
- // frames.set(offset, style);
473
- // };
474
- // for (const key in keyframes) {
475
- // const framesValue = keyframes[key];
476
- // !Array.isArray(framesValue)
477
- // ? setStyle(1, key, framesValue)
478
- // : framesValue.length === 1
479
- // ? setStyle(1, key, framesValue[0])
480
- // : framesValue.forEach((value, index) =>
481
- // setStyle(offsetList[index] ?? index / (framesValue.length - 1), key, value),
482
- // );
483
- // }
484
- // }
485
-
486
- // let framesStr = '';
487
- // frames.forEach((rules, offset) => {
488
- // framesStr += `${(offset * 100).toFixed()}%{${styleMap(rules)}}`;
489
- // });
490
-
491
- // if (options) {
492
- // const {
493
- // // 只能使用 ms 数字
494
- // duration = 0,
495
- // easing = '',
496
- // delay = 0,
497
- // iterations = 1,
498
- // direction = '',
499
- // fill = '',
500
- // name = `ani-${randomStr()}`,
501
- // } = typeof options === 'number' ? ({ duration: options } as Exclude<typeof options, number>) : options;
502
- // return `${duration}ms ${easing} ${delay}ms ${iterations} ${direction} ${fill} ${name};@keyframes ${name}{${framesStr}}`;
503
- // }
504
-
505
- // return framesStr;
506
- // }
@@ -1 +1,2 @@
1
- export const version = '1.7.11';
1
+ // Do not modify manually
2
+ export const version = '0.0.0';
@@ -5,7 +5,7 @@ import type { GemActiveLinkElement } from '../../elements/link';
5
5
 
6
6
  import '../../elements/link';
7
7
 
8
- describe('GemTitleElement', () => {
8
+ describe('GemLinkElement', () => {
9
9
  it('basic', async () => {
10
10
  const ele: GemActiveLinkElement = await fixture(
11
11
  html`<gem-active-link pattern="/*" href="/#测试">active-link</gem-active-link>`,
@@ -8,6 +8,7 @@ import '../../elements/title';
8
8
  describe('GemTitleElement', () => {
9
9
  it('basic', async () => {
10
10
  const ele: GemTitleElement = await fixture(html`<gem-title>title</gem-title>`);
11
+ await nextFrame();
11
12
  expect(document.title).to.equal('title');
12
13
  ele.textContent = 'title2';
13
14
  await nextFrame();
@@ -8,35 +8,45 @@
8
8
  */
9
9
  import { fixture, expect, nextFrame } from '@open-wc/testing';
10
10
 
11
- import { GemElement, html } from '../../lib/element';
11
+ import type { Metadata } from '../../lib/element';
12
+ import { createCSSSheet, createState, GemElement, html, createRef } from '../../lib/element';
12
13
  import { createStore, updateStore } from '../../lib/store';
13
- import { attribute, property, customElement, emitter, adoptedStyle, refobject, RefObject } from '../../lib/decorators';
14
- import { createCSSSheet, css } from '../../lib/utils';
14
+ import {
15
+ attribute,
16
+ property,
17
+ customElement,
18
+ emitter,
19
+ adoptedStyle,
20
+ connectStore,
21
+ async,
22
+ shadow,
23
+ mounted,
24
+ } from '../../lib/decorators';
25
+ import { css } from '../../lib/utils';
15
26
 
16
27
  const store = createStore({
17
28
  a: 1,
18
29
  });
19
30
 
31
+ @connectStore(store)
32
+ @async()
33
+ @shadow()
34
+ @customElement('async-gem-demo')
20
35
  class AsyncGemDemo extends GemElement {
21
- static observedStores = [store];
22
- constructor() {
23
- super({ isAsync: true });
24
- }
25
- state = { a: 0 };
36
+ #state = createState({ a: 0 });
26
37
  renderCount = 0;
27
38
  render() {
28
39
  this.renderCount++;
29
40
  return null;
30
41
  }
31
42
  }
32
- customElements.define('async-gem-demo', AsyncGemDemo);
33
43
 
34
44
  describe('异步 gem element 测试', () => {
35
45
  it('异步 gem element 更新', async () => {
36
46
  const el: AsyncGemDemo = await fixture(html`<async-gem-demo></async-gem-demo>`);
37
47
  expect(el.renderCount).to.equal(1);
38
48
  updateStore(store, { a: ++store.a });
39
- el.setState({ a: ++el.state.a });
49
+ el.internals.stateList[0]({ a: ++(el.internals.stateList[0] as any).a });
40
50
  await nextFrame();
41
51
  expect(el.renderCount).to.equal(2);
42
52
  await nextFrame();
@@ -45,7 +55,7 @@ describe('异步 gem element 测试', () => {
45
55
  });
46
56
 
47
57
  const lightStyle = createCSSSheet(css`
48
- body {
58
+ div {
49
59
  font-size: 18.1px;
50
60
  }
51
61
  `);
@@ -53,37 +63,41 @@ const lightStyle = createCSSSheet(css`
53
63
  @customElement('light-gem-demo')
54
64
  @adoptedStyle(lightStyle)
55
65
  class LightGemDemo extends GemElement {
56
- constructor() {
57
- super({ isLight: true });
58
- }
59
66
  render() {
60
67
  return html`<div>hi</div>`;
61
68
  }
62
69
  }
63
70
  describe('没有 Shadow DOM 的 gem 元素', () => {
64
71
  it('渲染没有 Shadow DOM 的 gem 元素', async () => {
72
+ const el0: LightGemDemo = await fixture(html`<div></div>`);
65
73
  const el1: LightGemDemo = await fixture(html`<light-gem-demo></light-gem-demo>`);
66
74
  const el2: LightGemDemo = await fixture(html`<light-gem-demo></light-gem-demo>`);
67
75
  expect(el1.shadowRoot).to.equal(null);
68
76
  expect(el1.innerHTML.includes('hi')).to.equal(true);
69
- expect(getComputedStyle(document.body).fontSize).to.equal('18.1px');
77
+ expect(getComputedStyle(el0).fontSize).not.to.equal('18.1px');
78
+ expect(getComputedStyle(el2.firstElementChild!).fontSize).to.equal('18.1px');
70
79
  el1.remove();
71
- expect(getComputedStyle(document.body).fontSize).to.equal('18.1px');
80
+ await Promise.resolve();
81
+ expect(getComputedStyle(el2.firstElementChild!).fontSize).to.equal('18.1px');
72
82
  el2.remove();
73
- expect(getComputedStyle(document.body).fontSize).not.to.equal('18.1px');
83
+ // 样式在队列末尾执行
84
+ await Promise.resolve();
85
+ expect(getComputedStyle(el0).fontSize).not.to.equal('18.1px');
74
86
  });
75
87
  });
76
88
 
77
89
  class LifecycleGemElement extends GemElement {
78
90
  @attribute appTitle = 'default';
79
91
  @attribute appTitle2 = 'default2';
80
- @refobject divRef: RefObject<HTMLElement>;
81
92
  refInConstructor?: HTMLElement;
82
93
 
94
+ divRef = createRef<HTMLDivElement>();
95
+
83
96
  constructor(appTitle: string, appTitle2: string) {
84
97
  super();
85
- this.appTitle = appTitle;
86
- this.appTitle2 = appTitle2;
98
+ // 字段优先执行于构造函数
99
+ this.appTitle = appTitle ?? this.appTitle;
100
+ this.appTitle2 = appTitle2 ?? this.appTitle2;
87
101
  this.effect(
88
102
  () => {
89
103
  this.refInConstructor = this.divRef.element;
@@ -93,18 +107,14 @@ class LifecycleGemElement extends GemElement {
93
107
  }
94
108
  renderCount = 0;
95
109
  mountedCount = 0;
96
- updatedCount = 0;
97
-
98
- updated() {
99
- this.updatedCount++;
100
- }
101
110
 
102
- mounted() {
111
+ @mounted()
112
+ #init = () => {
103
113
  this.mountedCount++;
104
114
  return () => {
105
115
  this.renderCount = 0;
106
116
  };
107
- }
117
+ };
108
118
 
109
119
  render() {
110
120
  this.renderCount++;
@@ -116,6 +126,7 @@ describe('gem element 生命周期', () => {
116
126
  const el: LifecycleGemElement = await fixture(html`<lifecycle-gem-demo></lifecycle-gem-demo>`);
117
127
  // test #isMounted
118
128
  customElements.define('lifecycle-gem-demo', LifecycleGemElement);
129
+ await Promise.resolve();
119
130
  expect(!!el.refInConstructor).to.equal(true);
120
131
  expect(el.renderCount).to.equal(1);
121
132
  el.remove();
@@ -129,8 +140,6 @@ describe('gem element 生命周期', () => {
129
140
  expect(el.appTitle).to.equal('title');
130
141
  expect(el.renderCount).to.equal(1);
131
142
  const clone = el.cloneNode() as LifecycleGemElement;
132
- // v2 BUG
133
- clone.connectedCallback();
134
143
  expect(clone.appTitle).to.equal('title');
135
144
 
136
145
  const el2 = new LifecycleGemElement('', '2');
@@ -142,20 +151,20 @@ describe('gem element 生命周期', () => {
142
151
 
143
152
  // disconnectedCallback, connectedCallback
144
153
  container.append(el2);
154
+ await Promise.resolve();
145
155
  expect(el2.mountedCount).to.equal(1);
146
156
  expect(el2.renderCount).to.equal(1);
147
157
  el2.remove();
148
158
  expect(el2.mountedCount).to.equal(1);
149
159
  expect(el2.renderCount).to.equal(0);
150
160
  container.append(el2);
161
+ await Promise.resolve();
151
162
  expect(el2.mountedCount).to.equal(2);
152
163
  expect(el2.renderCount).to.equal(1);
153
- expect(el2.updatedCount).to.equal(0);
154
164
  el2.update();
155
165
  el2.update();
156
166
  await Promise.resolve();
157
167
  expect(el2.renderCount).to.equal(2);
158
- expect(el2.updatedCount).to.equal(1);
159
168
 
160
169
  const el3 = new LifecycleGemElement('test1', 'test2');
161
170
  expect(el3.appTitle).to.equal('test1');
@@ -166,6 +175,7 @@ describe('gem element 生命周期', () => {
166
175
  });
167
176
  });
168
177
 
178
+ @customElement('effect-gem-demo')
169
179
  class EffectGemDemo extends GemElement {
170
180
  @attribute attr = 'a';
171
181
  @property prop = {};
@@ -186,7 +196,8 @@ class EffectGemDemo extends GemElement {
186
196
  },
187
197
  );
188
198
  }
189
- mounted() {
199
+ @mounted()
200
+ #mounted = () => {
190
201
  this.effect(
191
202
  (_arr) => {
192
203
  this.effectCallCount++;
@@ -197,9 +208,8 @@ class EffectGemDemo extends GemElement {
197
208
  this.effect(() => {
198
209
  this.effectCallCount++;
199
210
  });
200
- }
211
+ };
201
212
  }
202
- customElements.define('effect-gem-demo', EffectGemDemo);
203
213
  describe('gem element 副作用', () => {
204
214
  it('依赖当前值', async () => {
205
215
  const el: EffectGemDemo = await fixture(html`<effect-gem-demo></effect-gem-demo>`);
@@ -230,57 +240,58 @@ describe('gem element 副作用', () => {
230
240
  expect(el.effectCallCount).to.equal(0);
231
241
  expect(el.hasConstructorEffect).to.equal(false);
232
242
  document.body.append(el);
243
+ await Promise.resolve();
233
244
  expect(el.hasConstructorEffect).to.equal(true);
234
245
  });
235
246
  });
236
247
 
248
+ @customElement('memo-gem-demo')
237
249
  class MemoGemDemo extends GemElement {
238
250
  @attribute attr = 'a';
239
251
  @property prop = {};
240
- memoCount = 0;
252
+ memoNum = 0;
241
253
  constructor() {
242
254
  super();
243
255
  this.memo(
244
- () => (this.memoCount += 1),
256
+ () => (this.memoNum += 1),
245
257
  () => [this.attr, this.prop],
246
258
  );
247
- }
248
- willMount() {
249
259
  this.memo(
250
260
  (_arr) => {
251
- this.memoCount += 2;
261
+ this.memoNum += 2;
252
262
  },
253
263
  () => [],
254
264
  );
255
265
  this.memo(() => {
256
- this.memoCount += 4;
266
+ this.memoNum += 4;
257
267
  });
258
268
  }
259
269
  }
260
- customElements.define('memo-gem-demo', MemoGemDemo);
261
270
  describe('gem element Memo', () => {
262
271
  it('依赖当前值', async () => {
263
272
  const el: MemoGemDemo = await fixture(html`<memo-gem-demo></memo-gem-demo>`);
264
- expect(el.memoCount).to.equal(7);
273
+ expect(el.memoNum).to.equal(7);
265
274
  el.attr = 'b';
266
275
  el.prop = {};
267
276
  await nextFrame();
268
- expect(el.memoCount).to.equal(12);
277
+ expect(el.memoNum).to.equal(12);
269
278
 
270
279
  el.update();
271
280
  await Promise.resolve();
272
- expect(el.memoCount).to.equal(16);
281
+ expect(el.memoNum).to.equal(16);
273
282
 
274
283
  document.body.append(el);
275
- expect(el.memoCount).to.equal(16);
284
+ expect(el.memoNum).to.equal(16);
276
285
 
277
286
  el.remove();
278
287
  await Promise.resolve();
279
288
  document.body.append(el);
280
- expect(el.memoCount).to.equal(22);
289
+ expect(el.memoNum).to.equal(23);
281
290
  });
282
291
  });
283
292
 
293
+ @customElement('i-gem')
294
+ @shadow()
284
295
  class I extends GemElement {
285
296
  @attribute appTitle = 'string';
286
297
  @property appData = { a: 1 };
@@ -295,13 +306,24 @@ class InheritGem extends I {
295
306
  }
296
307
  describe('gem element 继承', () => {
297
308
  it('静态字段继承', async () => {
298
- expect(I.observedAttributes).to.eql(['app-title']);
299
- expect(InheritGem.observedAttributes).to.eql(['app-title', 'app-title', 'app-title2']);
309
+ new I();
310
+ new InheritGem(); // 触发装饰器自定义初始化函数
311
+ const metadata: Metadata = Reflect.get(InheritGem, Symbol.metadata);
312
+ expect(metadata.observedAttributes).to.eql(['app-title', 'app-title2']);
300
313
  });
301
314
  it('attr/prop/emitter 继承', async () => {
302
315
  const name = window.name;
303
316
  const el: InheritGem = await fixture(html`<inherit-gem></inherit-gem>`);
304
- expect(el.appTitle).to.equal('1');
317
+ /**
318
+ * NOTE: 默认值不能覆盖,可以在 `constructor` 中重新定义默认值
319
+ *
320
+ * 原因:
321
+ *
322
+ * 1. 执行装饰器时必须保留之前设置的 prop 值,所以 prop 的字段默认值不能覆盖,统一 attr 和 prop 的行为
323
+ * 2. 很难区分是基类中设置的 attr 还是模版中指定的 attr
324
+ */
325
+ expect(el.appTitle).to.equal('string');
326
+ expect(el.appTitle2).to.equal('2');
305
327
  expect(el.appData.a).to.equal(1);
306
328
  el.appTitle = 'b';
307
329
  el.appData = { a: 2 };
@@ -315,6 +337,7 @@ describe('gem element 继承', () => {
315
337
  });
316
338
  });
317
339
  @customElement('render-empty')
340
+ @shadow()
318
341
  class RenderEmpty extends GemElement {
319
342
  render() {
320
343
  return undefined;