@mantou/gem 2.1.0 → 2.2.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 (107) hide show
  1. package/dist/gem.es.js +1845 -1904
  2. package/dist/gem.umd.js +34 -191
  3. package/elements/base/dialog.d.ts +0 -4
  4. package/elements/base/dialog.d.ts.map +1 -1
  5. package/elements/base/dialog.js +0 -4
  6. package/elements/base/dialog.js.map +1 -1
  7. package/elements/base/gesture.d.ts +4 -14
  8. package/elements/base/gesture.d.ts.map +1 -1
  9. package/elements/base/gesture.js +34 -37
  10. package/elements/base/gesture.js.map +1 -1
  11. package/elements/base/link.d.ts +2 -19
  12. package/elements/base/link.d.ts.map +1 -1
  13. package/elements/base/link.js +29 -42
  14. package/elements/base/link.js.map +1 -1
  15. package/elements/base/modal-factory.d.ts +32 -33
  16. package/elements/base/modal-factory.d.ts.map +1 -1
  17. package/elements/base/reflect.d.ts +0 -3
  18. package/elements/base/reflect.d.ts.map +1 -1
  19. package/elements/base/reflect.js +0 -3
  20. package/elements/base/reflect.js.map +1 -1
  21. package/elements/base/route.d.ts +0 -10
  22. package/elements/base/route.d.ts.map +1 -1
  23. package/elements/base/route.js +10 -22
  24. package/elements/base/route.js.map +1 -1
  25. package/elements/base/title.d.ts +0 -6
  26. package/elements/base/title.d.ts.map +1 -1
  27. package/elements/base/title.js +13 -8
  28. package/elements/base/title.js.map +1 -1
  29. package/elements/base/unsafe.d.ts +1 -6
  30. package/elements/base/unsafe.d.ts.map +1 -1
  31. package/elements/base/unsafe.js +12 -12
  32. package/elements/base/unsafe.js.map +1 -1
  33. package/elements/base/use.d.ts +0 -4
  34. package/elements/base/use.d.ts.map +1 -1
  35. package/elements/base/use.js +15 -13
  36. package/elements/base/use.js.map +1 -1
  37. package/helper/theme.d.ts +3 -3
  38. package/helper/theme.d.ts.map +1 -1
  39. package/helper/theme.js +3 -3
  40. package/helper/theme.js.map +1 -1
  41. package/index.d.ts +0 -1
  42. package/index.d.ts.map +1 -1
  43. package/index.js +0 -1
  44. package/index.js.map +1 -1
  45. package/lib/decorators.d.ts +7 -6
  46. package/lib/decorators.d.ts.map +1 -1
  47. package/lib/decorators.js +48 -33
  48. package/lib/decorators.js.map +1 -1
  49. package/lib/directive.d.ts +83 -0
  50. package/lib/directive.d.ts.map +1 -0
  51. package/lib/directive.js +59 -0
  52. package/lib/directive.js.map +1 -0
  53. package/lib/element.d.ts +9 -23
  54. package/lib/element.d.ts.map +1 -1
  55. package/lib/element.js +61 -56
  56. package/lib/element.js.map +1 -1
  57. package/lib/lit-html.d.ts +331 -0
  58. package/lib/lit-html.d.ts.map +1 -0
  59. package/lib/lit-html.js +941 -0
  60. package/lib/lit-html.js.map +1 -0
  61. package/lib/ref.d.ts +1 -0
  62. package/lib/ref.d.ts.map +1 -0
  63. package/lib/ref.js +2 -0
  64. package/lib/ref.js.map +1 -0
  65. package/lib/repeat.d.ts +126 -0
  66. package/lib/repeat.d.ts.map +1 -0
  67. package/lib/repeat.js +533 -0
  68. package/lib/repeat.js.map +1 -0
  69. package/lib/store.d.ts +1 -1
  70. package/lib/store.d.ts.map +1 -1
  71. package/lib/store.js +4 -4
  72. package/lib/store.js.map +1 -1
  73. package/lib/utils.d.ts +1 -4
  74. package/lib/utils.d.ts.map +1 -1
  75. package/lib/utils.js +6 -15
  76. package/lib/utils.js.map +1 -1
  77. package/package.json +7 -9
  78. package/src/elements/base/dialog.ts +0 -4
  79. package/src/elements/base/gesture.ts +32 -35
  80. package/src/elements/base/link.ts +21 -41
  81. package/src/elements/base/reflect.ts +0 -3
  82. package/src/elements/base/route.ts +11 -21
  83. package/src/elements/base/title.ts +3 -7
  84. package/src/elements/base/unsafe.ts +9 -11
  85. package/src/elements/base/use.ts +8 -14
  86. package/src/helper/theme.ts +4 -4
  87. package/src/index.ts +0 -1
  88. package/src/lib/decorators.ts +53 -40
  89. package/src/lib/directive.ts +139 -0
  90. package/src/lib/element.ts +70 -79
  91. package/src/lib/lit-html.ts +1217 -0
  92. package/src/lib/repeat.ts +580 -0
  93. package/src/lib/store.ts +4 -4
  94. package/src/lib/utils.ts +6 -16
  95. package/src/test/elements/link.test.ts +1 -2
  96. package/src/test/elements/reflect.test.ts +13 -3
  97. package/src/test/elements/title.test.ts +1 -2
  98. package/src/test/gem-element/advance.test.ts +15 -8
  99. package/src/test/gem-element/basic.test.ts +22 -10
  100. package/src/test/gem-element/decorators.test.ts +6 -9
  101. package/src/test/gem-element/multi.test.ts +20 -11
  102. package/src/test/history.test.ts +2 -2
  103. package/src/test/store.test.ts +3 -3
  104. package/src/test/utils.test.ts +9 -10
  105. package/src/test/utils.ts +22 -0
  106. package/LICENSE +0 -21
  107. package/src/lib/directives.ts +0 -6
@@ -1,5 +1,5 @@
1
- import type { createCSSSheet, Metadata, TemplateResult } from './element';
2
- import { GemElement, UpdateToken, createTemplate, RenderErrorEvent, render } from './element';
1
+ import type { css, Metadata, TemplateResult } from './element';
2
+ import { GemElement, UpdateToken, _createTemplate, _RenderErrorEvent, render } from './element';
3
3
  import { camelToKebabCase, PropProxyMap, GemError } from './utils';
4
4
  import type { Store } from './store';
5
5
  import * as elementExports from './element';
@@ -7,7 +7,7 @@ import * as decoratorsExports from './decorators';
7
7
  import * as storeExports from './store';
8
8
  import * as versionExports from './version';
9
9
 
10
- type GemElementPrototype = GemElement;
10
+ type GemElementPrototype = GemElement & { '': never };
11
11
  type StaticField = Exclude<keyof Metadata, keyof ShadowRootInit | 'aria' | 'noBlocking' | 'penetrable'>;
12
12
 
13
13
  const { deleteProperty, getOwnPropertyDescriptor, defineProperty } = Reflect;
@@ -18,10 +18,8 @@ function pushStaticField(context: ClassFieldDecoratorContext | ClassDecoratorCon
18
18
  const metadata = context.metadata as Metadata;
19
19
  if (!getOwnPropertyDescriptor(metadata, field)) {
20
20
  // 继承基类
21
- const current = new Set<unknown>(metadata[field]);
22
- current.delete(member);
23
21
  defineProperty(metadata, field, {
24
- value: [...current],
22
+ value: metadata[field]?.filter((e) => e !== member) || [],
25
23
  });
26
24
  }
27
25
 
@@ -96,10 +94,13 @@ function defineProp(
96
94
  if (event) {
97
95
  proxy[prop] = v?.[isEventHandleSymbol]
98
96
  ? v
99
- : (detail: any, options: any) => {
97
+ : async (detail: any, options: any) => {
98
+ // 事件如果同步触发,则祖先元素可能还没有挂载好,其中就不能更新 state
99
+ // https://github.com/mantou132/gem/issues/203
100
+ await Promise.resolve();
100
101
  const evt = new CustomEvent(event, { ...options, ...eventOptions, detail });
101
102
  that.dispatchEvent(evt);
102
- v(detail, options);
103
+ await v(detail, options);
103
104
  };
104
105
  (proxy[prop] as any)[isEventHandleSymbol] = true;
105
106
  // emitter 不触发元素更新
@@ -111,21 +112,35 @@ function defineProp(
111
112
  });
112
113
  }
113
114
 
115
+ // https://github.com/tc39/proposal-decorators/issues/517
116
+ const targetCache = new WeakMap<DecoratorMetadataObject, GemElementPrototype>();
117
+ function getDecoratorTarget(element: GemElement, { metadata }: ClassFieldDecoratorContext) {
118
+ const target = targetCache.get(metadata);
119
+ if (target) return target;
120
+ let result = element as GemElementPrototype;
121
+ do {
122
+ result = getPrototypeOf(result);
123
+ } while (result.constructor[Symbol.metadata] !== metadata);
124
+ targetCache.set(metadata, result);
125
+ return result;
126
+ }
127
+
114
128
  type AttrType = BooleanConstructor | NumberConstructor | StringConstructor;
115
129
  function decoratorAttr<T extends GemElement>(context: ClassFieldDecoratorContext<T>, attrType: AttrType) {
116
130
  const prop = context.name as string;
117
131
  const attr = camelToKebabCase(prop);
118
132
  context.addInitializer(function (this: T) {
119
- const target = getPrototypeOf(this);
133
+ const target = getDecoratorTarget(this, context);
120
134
  if (!target.hasOwnProperty(prop)) {
121
135
  pushStaticField(context, 'observedAttributes', attr); // 没有 observe 的效果
122
136
  defineProp(target, prop, { attr, attrType });
123
- // 记录观察的 attribute
124
- const attrMap = observedTargetAttributes.get(target) || new Map<string, string>();
125
- attrMap.set(attr, prop);
126
- observedTargetAttributes.set(target, attrMap);
127
137
  }
128
138
  clearField(this, prop);
139
+ // 记录观察的 attribute, 用于 hack 的 setAttribute
140
+ const proto = getPrototypeOf(this);
141
+ const attrMap = observedTargetAttributes.get(proto) || new Map<string, string>();
142
+ attrMap.set(attr, prop);
143
+ observedTargetAttributes.set(proto, attrMap);
129
144
  });
130
145
  // 延时定义的元素需要继承原实例属性值
131
146
  return function (this: any, initValue: any) {
@@ -166,7 +181,7 @@ export function numattribute<T extends GemElement>(_: undefined, context: ClassF
166
181
  export function property<T extends GemElement>(_: undefined, context: ClassFieldDecoratorContext<T>) {
167
182
  const prop = context.name as string;
168
183
  context.addInitializer(function (this: T) {
169
- const target = getPrototypeOf(this);
184
+ const target = getDecoratorTarget(this, context);
170
185
  if (!target.hasOwnProperty(prop)) {
171
186
  pushStaticField(context, 'observedProperties', prop);
172
187
  defineProp(target, prop);
@@ -214,14 +229,15 @@ export function memo<T extends GemElement, V = any, K = any[] | undefined>(
214
229
  }
215
230
 
216
231
  /**
217
- * 依赖 `GemElement.effect`
218
- * 方法执行在字段前面
232
+ * - 依赖 `GemElement.effect`
233
+ * - 方法执行在字段前面
234
+ * - 如果没有 oldValue,在为首次执行
219
235
  *
220
236
  * For example
221
237
  * ```ts
222
238
  * class App extends GemElement {
223
239
  * @effect(() => [])
224
- * #fetchData() {
240
+ * #fetchData(newValue, oldValue) {
225
241
  * console.log('fetch')
226
242
  * }
227
243
  * }
@@ -273,7 +289,7 @@ export function template<T extends GemElement, V extends () => TemplateResult |
273
289
  { addInitializer, access }: ClassFieldDecoratorContext<T, V> | ClassMethodDecoratorContext<T, V>,
274
290
  ) {
275
291
  addInitializer(function (this: T) {
276
- createTemplate(this, {
292
+ _createTemplate(this, {
277
293
  render: access.get(this).bind(this),
278
294
  condition: condition && (() => condition(this) as any),
279
295
  });
@@ -287,7 +303,7 @@ export function fallback<T extends GemElement, V extends (err: any) => TemplateR
287
303
  { addInitializer, access }: ClassFieldDecoratorContext<T, V> | ClassMethodDecoratorContext<T, V>,
288
304
  ) {
289
305
  addInitializer(function (this: T) {
290
- this.addEventListener(RenderErrorEvent, ({ detail }: CustomEvent) => {
306
+ this.addEventListener(_RenderErrorEvent, ({ detail }: CustomEvent) => {
291
307
  render(access.get(this).apply(this, [detail]), this.internals.shadowRoot || this);
292
308
  });
293
309
  });
@@ -328,11 +344,11 @@ function defineCSSState(target: GemElementPrototype, prop: string, stateStr: str
328
344
  * ```
329
345
  */
330
346
  export function state<T extends GemElement>(_: undefined, context: ClassFieldDecoratorContext<T, boolean>) {
347
+ const prop = context.name as string;
348
+ const attr = camelToKebabCase(prop);
331
349
  context.addInitializer(function (this: T) {
332
- const target = getPrototypeOf(this);
333
- const prop = context.name as string;
350
+ const target = getDecoratorTarget(this, context);
334
351
  if (!target.hasOwnProperty(prop)) {
335
- const attr = camelToKebabCase(prop);
336
352
  pushStaticField(context, 'definedCSSStates', attr);
337
353
  defineCSSState(target, prop, attr);
338
354
  }
@@ -394,7 +410,7 @@ export function part(_: undefined, context: ClassFieldDecoratorContext<any, stri
394
410
  };
395
411
  }
396
412
 
397
- export type Emitter<T = any> = (detail?: T, options?: Omit<CustomEventInit<unknown>, 'detail'>) => void;
413
+ export type Emitter<T = any> = (detail?: T, options?: Omit<CustomEventInit<unknown>, 'detail'>) => Promise<void> | void;
398
414
 
399
415
  /**
400
416
  * 定义一个事件发射器,类似 `HTMLElement.click`,
@@ -408,28 +424,25 @@ export type Emitter<T = any> = (detail?: T, options?: Omit<CustomEventInit<unkno
408
424
  * ```
409
425
  */
410
426
  export function emitter<T extends GemElement>(_: undefined, context: ClassFieldDecoratorContext<T, Emitter>) {
411
- context.addInitializer(function (this: T) {
412
- defineEmitter(context, this, context.name as string);
413
- });
427
+ defineEmitter(context);
414
428
  }
415
429
  export function globalemitter<T extends GemElement>(_: undefined, context: ClassFieldDecoratorContext<T, Emitter>) {
416
- context.addInitializer(function (this: T) {
417
- defineEmitter(context, this, context.name as string, { bubbles: true, composed: true });
418
- });
430
+ defineEmitter(context, { bubbles: true, composed: true });
419
431
  }
420
- function defineEmitter(
432
+ function defineEmitter<T extends GemElement>(
421
433
  context: ClassFieldDecoratorContext,
422
- t: GemElement,
423
- prop: string,
424
434
  eventOptions?: Omit<CustomEventInit<unknown>, 'detail'>,
425
435
  ) {
426
- const target = getPrototypeOf(t);
427
- if (!target.hasOwnProperty(prop)) {
428
- const event = camelToKebabCase(prop);
429
- pushStaticField(context, 'definedEvents', event);
430
- defineProp(target, prop, { event, eventOptions });
431
- }
432
- clearField(t, prop);
436
+ const prop = context.name as string;
437
+ const event = camelToKebabCase(prop);
438
+ context.addInitializer(function (this: T) {
439
+ const target = getDecoratorTarget(this, context);
440
+ if (!target.hasOwnProperty(prop)) {
441
+ pushStaticField(context, 'definedEvents', event);
442
+ defineProp(target, prop, { event, eventOptions });
443
+ }
444
+ clearField(this, prop);
445
+ });
433
446
  }
434
447
 
435
448
  /**
@@ -441,7 +454,7 @@ function defineEmitter(
441
454
  * class App extends GemElement {}
442
455
  * ```
443
456
  */
444
- export function adoptedStyle(sheet: ReturnType<typeof createCSSSheet>) {
457
+ export function adoptedStyle(sheet: ReturnType<typeof css>) {
445
458
  return function (_: unknown, context: ClassDecoratorContext) {
446
459
  pushStaticField(context, 'adoptedStyleSheets', sheet);
447
460
  };
@@ -0,0 +1,139 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2017 Google LLC
4
+ * SPDX-License-Identifier: BSD-3-Clause
5
+ */
6
+
7
+ import type { Disconnectable, Part } from './lit-html.js';
8
+
9
+ export type {
10
+ AttributePart,
11
+ BooleanAttributePart,
12
+ ChildPart,
13
+ ElementPart,
14
+ EventPart,
15
+ Part,
16
+ PropertyPart,
17
+ } from './lit-html.js';
18
+
19
+ export interface DirectiveClass {
20
+ new (part: PartInfo): Directive;
21
+ }
22
+
23
+ /**
24
+ * This utility type extracts the signature of a directive class's render()
25
+ * method so we can use it for the type of the generated directive function.
26
+ */
27
+ export type DirectiveParameters<C extends Directive> = Parameters<C['render']>;
28
+
29
+ /**
30
+ * A generated directive function doesn't evaluate the directive, but just
31
+ * returns a DirectiveResult object that captures the arguments.
32
+ */
33
+ export interface DirectiveResult<C extends DirectiveClass = DirectiveClass> {
34
+ /**
35
+ * This property needs to remain unminified.
36
+ * @internal
37
+ */
38
+ ['_$litDirective$']: C;
39
+ /** @internal */
40
+ values: DirectiveParameters<InstanceType<C>>;
41
+ }
42
+
43
+ export const PartType = {
44
+ ATTRIBUTE: 1,
45
+ CHILD: 2,
46
+ PROPERTY: 3,
47
+ BOOLEAN_ATTRIBUTE: 4,
48
+ EVENT: 5,
49
+ ELEMENT: 6,
50
+ } as const;
51
+
52
+ export type PartType = (typeof PartType)[keyof typeof PartType];
53
+
54
+ export interface ChildPartInfo {
55
+ readonly type: typeof PartType.CHILD;
56
+ }
57
+
58
+ export interface AttributePartInfo {
59
+ readonly type:
60
+ | typeof PartType.ATTRIBUTE
61
+ | typeof PartType.PROPERTY
62
+ | typeof PartType.BOOLEAN_ATTRIBUTE
63
+ | typeof PartType.EVENT;
64
+ readonly strings?: ReadonlyArray<string>;
65
+ readonly name: string;
66
+ readonly tagName: string;
67
+ }
68
+
69
+ export interface ElementPartInfo {
70
+ readonly type: typeof PartType.ELEMENT;
71
+ }
72
+
73
+ /**
74
+ * Information about the part a directive is bound to.
75
+ *
76
+ * This is useful for checking that a directive is attached to a valid part,
77
+ * such as with directive that can only be used on attribute bindings.
78
+ */
79
+ export type PartInfo = ChildPartInfo | AttributePartInfo | ElementPartInfo;
80
+
81
+ /**
82
+ * Creates a user-facing directive function from a Directive class. This
83
+ * function has the same parameters as the directive's render() method.
84
+ */
85
+ export const directive =
86
+ <C extends DirectiveClass>(c: C) =>
87
+ (...values: DirectiveParameters<InstanceType<C>>): DirectiveResult<C> => ({
88
+ // This property needs to remain unminified.
89
+ ['_$litDirective$']: c,
90
+ values,
91
+ });
92
+
93
+ /**
94
+ * Base class for creating custom directives. Users should extend this class,
95
+ * implement `render` and/or `update`, and then pass their subclass to
96
+ * `directive`.
97
+ */
98
+ export abstract class Directive implements Disconnectable {
99
+ //@internal
100
+ __part!: Part;
101
+ //@internal
102
+ __attributeIndex: number | undefined;
103
+ //@internal
104
+ __directive?: Directive;
105
+
106
+ //@internal
107
+ _$parent!: Disconnectable;
108
+
109
+ // These will only exist on the AsyncDirective subclass
110
+ //@internal
111
+ _$disconnectableChildren?: Set<Disconnectable>;
112
+ // This property needs to remain unminified.
113
+ //@internal
114
+ ['_$notifyDirectiveConnectionChanged']?(isConnected: boolean): void;
115
+
116
+ constructor(_partInfo: PartInfo) {}
117
+
118
+ // See comment in Disconnectable interface for why this is a getter
119
+ get _$isConnected() {
120
+ return this._$parent._$isConnected;
121
+ }
122
+
123
+ /** @internal */
124
+ _$initialize(part: Part, parent: Disconnectable, attributeIndex: number | undefined) {
125
+ this.__part = part;
126
+ this._$parent = parent;
127
+ this.__attributeIndex = attributeIndex;
128
+ }
129
+ /** @internal */
130
+ _$resolve(part: Part, props: Array<unknown>): unknown {
131
+ return this.update(part, props);
132
+ }
133
+
134
+ abstract render(...props: Array<unknown>): unknown;
135
+
136
+ update(_part: Part, props: Array<unknown>): unknown {
137
+ return this.render(...props);
138
+ }
139
+ }
@@ -1,23 +1,14 @@
1
- import type { TemplateResult } from 'lit-html';
2
- import { html, render } from 'lit-html';
3
-
4
- import type { GemReflectElement } from '../elements/reflect';
5
-
1
+ import { html, render } from './lit-html';
2
+ import type { TemplateResult } from './lit-html';
6
3
  import type { Store } from './store';
7
4
  import { connect } from './store';
8
- import { LinkedList, addMicrotask, isArrayChange, addListener, randomStr, createUpdater } from './utils';
5
+ import { LinkedList, addMicrotask, isArrayChange, addListener, randomStr, createUpdater, GemError, raw } from './utils';
9
6
 
10
- export { html, svg, render, directive, TemplateResult, SVGTemplateResult } from 'lit-html';
7
+ export { directive } from './directive';
8
+ export { repeat } from './repeat';
9
+ export { html, svg, mathml, render, TemplateResult, createRef } from './lit-html';
11
10
 
12
11
  declare global {
13
- // 用于 css 选择器选择元素,使用 RefObject 自动选择获取
14
- // 必须使用 attr 赋值
15
- /**
16
- * @attr ref
17
- */
18
- interface HTMLElement {
19
- ref: string;
20
- }
21
12
  interface DOMStringMap {
22
13
  gemReflect?: '';
23
14
  gemStyle?: '';
@@ -35,13 +26,13 @@ defineProperty(Symbol, 'metadata', { value: Symbol.for('Symbol.metadata') });
35
26
 
36
27
  const execCallback = (fun: any) => typeof fun === 'function' && fun();
37
28
 
38
- // 跨多个 gem 工作
29
+ // 读取构造样式表,跨多个 gem 工作
39
30
  export const SheetToken = Symbol.for('gem@sheetToken');
40
- // proto prop
31
+ // 更新元素,跨多个 gem 工作
41
32
  export const UpdateToken = Symbol.for('gem@update');
42
33
 
43
34
  const BoundaryCSSState = 'gem-style-boundary';
44
- export const RenderErrorEvent = 'gem@render-error';
35
+ export const _RenderErrorEvent = 'gem@render-error';
45
36
 
46
37
  // fix modal-factory type error
47
38
  const updateTokenAlias = UpdateToken;
@@ -49,40 +40,6 @@ const updateTokenAlias = UpdateToken;
49
40
  const rootStyleSheetInfo = new WeakMap<Document | ShadowRoot, Map<CSSStyleSheet, number>>();
50
41
  const rootUpdateFnMap = new WeakMap<Map<CSSStyleSheet, number>, () => void>();
51
42
 
52
- const getReflectTargets = (ele: ShadowRoot | GemElement) =>
53
- [...ele.querySelectorAll<GemReflectElement>('[data-gem-reflect]')].map((e) => e.target);
54
-
55
- class RefObject<T = HTMLElement> {
56
- refSelector: string;
57
- ele: GemElement | ShadowRoot;
58
- ref: string;
59
-
60
- constructor(current: GemElement) {
61
- const ref = `ref-${randomStr()}`;
62
- this.refSelector = `[ref=${ref}]`;
63
- this.ele = current.internals.shadowRoot || current;
64
- this.ref = ref;
65
- }
66
- get element() {
67
- for (const e of [this.ele, ...getReflectTargets(this.ele)]) {
68
- // 在 LightDOM 中可能工作很慢?
69
- const result = e.querySelector(this.refSelector);
70
- if (result) return result as T;
71
- }
72
- }
73
- get elements() {
74
- return [this.ele, ...getReflectTargets(this.ele)]
75
- .map((e) => [...e.querySelectorAll(this.refSelector)] as T[])
76
- .flat();
77
- }
78
- toString() {
79
- return this.ref;
80
- }
81
- }
82
-
83
- /**必须使用在字段中,否则会读取到错误的实例 */
84
- export let createRef: <T>() => RefObject<T>;
85
-
86
43
  class GemCSSStyleSheet extends CSSStyleSheet {
87
44
  #ele?: HTMLStyleElement;
88
45
 
@@ -146,7 +103,7 @@ class GemCSSSheet {
146
103
  return sheet;
147
104
  }
148
105
 
149
- // 一般用于主题更新,不支持 layer
106
+ // 一般用于主题更新
150
107
  updateStyle() {
151
108
  this.#used.forEach((scope, sheet) => {
152
109
  sheet.replaceSync(scope ? `${scope}{${this.#content}}` : this.#content);
@@ -160,14 +117,26 @@ export type Sheet<T> = { [P in keyof T]: P } & { [SheetToken]: GemCSSSheet };
160
117
  *
161
118
  * 创建 style sheet 用于 `@adoptedStyle`,不支持样式更新,只支持自定义 CSS 属性
162
119
  */
163
- export function createCSSSheet<T extends Record<string, string>>(media: string, rules: T | string): Sheet<T>;
164
- export function createCSSSheet<T extends Record<string, string>>(rules: T | string): Sheet<T>;
165
- export function createCSSSheet<T extends Record<string, string>>(
166
- mediaOrRules: T | string,
167
- rulesValue?: T | string,
120
+ export function css<T extends Record<string, string>>(media: TemplateStringsArray, ...rest: any[]): Sheet<T>;
121
+ export function css<T extends Record<string, string>>(media: string, rules: T | string): Sheet<T>;
122
+ export function css<T extends Record<string, string>>(rules: T | string): Sheet<T>;
123
+ export function css<T extends Record<string, string>>(
124
+ mediaOrRules: T | string | TemplateStringsArray,
125
+ ...rest: any[]
168
126
  ): Sheet<T> {
169
- const media = rulesValue ? (mediaOrRules as string) : '';
170
- const rules = rulesValue || mediaOrRules;
127
+ let media = '';
128
+ let rules: T | string = '';
129
+
130
+ const rulesValue = rest.at(0);
131
+ if (Array.isArray(mediaOrRules)) {
132
+ rules = raw(mediaOrRules as TemplateStringsArray, ...rest);
133
+ } else if (rulesValue !== undefined) {
134
+ media = mediaOrRules as string;
135
+ rules = rulesValue;
136
+ } else {
137
+ rules = mediaOrRules as string;
138
+ }
139
+
171
140
  const styleSheet = new GemCSSSheet(media);
172
141
  const sheet: any = { [SheetToken]: styleSheet };
173
142
  let style = '';
@@ -232,7 +201,7 @@ type EffectItem<T> = {
232
201
  fixed?: boolean; // 不需要清理,只会添加一次
233
202
  values?: T;
234
203
  getDep?: GetDepFun<T>;
235
- preCallback?: () => void;
204
+ preCallback?: any;
236
205
  };
237
206
 
238
207
  function execEffectList(list?: EffectItem<any>[]) {
@@ -261,7 +230,20 @@ function clearEffect(list: EffectItem<any>[]) {
261
230
  type Render = () => TemplateResult | null | undefined;
262
231
  type RenderItem = { render: Render; condition?: () => boolean };
263
232
 
264
- export let createTemplate: (ele: GemElement, item: RenderItem) => void;
233
+ const nullTemplate = html`
234
+ <style>
235
+ :host {
236
+ display: none !important;
237
+ }
238
+ @scope {
239
+ :scope {
240
+ display: none !important;
241
+ }
242
+ }
243
+ </style>
244
+ `;
245
+
246
+ export let _createTemplate: (ele: GemElement, item: RenderItem) => void;
265
247
 
266
248
  export type Metadata = Partial<ShadowRootInit> & {
267
249
  /** 内容可被外部样式化 */
@@ -318,30 +300,33 @@ export abstract class GemElement extends HTMLElement {
318
300
  #isAppendReason?: boolean;
319
301
  #isMounted?: boolean;
320
302
  // not in constructor 的近似值
321
- #isConnected?: boolean;
303
+ #notInCons?: boolean;
322
304
  #rendering?: boolean;
323
- #clearStyle?: any;
305
+ #clearStyle?: (() => void) | undefined;
324
306
 
325
307
  [updateTokenAlias]() {
326
308
  // 避免 `connectedCallback` 中的 property 赋值造成多余更新
327
- if (this.#isMounted) {
328
- addMicrotask(this.#update);
329
- }
309
+ if (this.#isMounted) addMicrotask(this.#update);
330
310
  }
331
311
 
332
312
  static {
333
313
  createState = <T>(initState: T) => {
334
314
  const ele = currentConstructGemElement;
335
315
  const state = createUpdater(initState, (payload) => {
316
+ const effect = ele.#effectList.at(0);
317
+ // https://github.com/mantou132/gem/issues/203
318
+ if (ele.#isMounted && effect && !effect.initialized) {
319
+ throw new GemError(`Do't set state sync before insert the DOM`);
320
+ }
336
321
  assign(state, payload);
337
322
  // 避免无限刷新
338
- if (!ele.#rendering) addMicrotask(ele.#update);
323
+ // 挂载前 set state 不应该触发更新
324
+ if (!ele.#rendering && ele.#isMounted) addMicrotask(ele.#update);
339
325
  });
340
326
  ele.#internals.stateList.push(state);
341
327
  return state;
342
328
  };
343
- createRef = () => new RefObject(currentConstructGemElement);
344
- createTemplate = (ele, item) => ele.#renderList.push(item);
329
+ _createTemplate = (ele, item) => ele.#renderList.push(item);
345
330
  }
346
331
 
347
332
  constructor() {
@@ -385,7 +370,8 @@ export abstract class GemElement extends HTMLElement {
385
370
  }
386
371
 
387
372
  get #renderItem() {
388
- return this.#renderList.find((e) => !e.condition || e.condition());
373
+ // 从末尾开始寻找,允许被覆盖
374
+ return this.#renderList.findLast((e) => !e.condition || e.condition());
389
375
  }
390
376
 
391
377
  get internals() {
@@ -405,9 +391,10 @@ export abstract class GemElement extends HTMLElement {
405
391
 
406
392
  /**
407
393
  * - 条件渲染
408
- * - 不提供 `render` 时显示子内容
409
- * - 返回 `null` 时渲染空的子内容
394
+ * - 没有 `render` 时显示子内容
410
395
  * - 返回 `undefined` 时不会更新现有内容
396
+ * - 返回 `null` 时渲染空内容
397
+ * - 返回 html`` 时渲染空的子内容
411
398
  * */
412
399
  #render = (item?: RenderItem) => {
413
400
  try {
@@ -417,9 +404,10 @@ export abstract class GemElement extends HTMLElement {
417
404
  const temp = item ? item.render() : isLight ? undefined : html`<slot></slot>`;
418
405
  this.#rendering = false;
419
406
  if (temp === undefined) return;
420
- render(temp, this.#renderRoot);
407
+ render(temp === null ? nullTemplate : temp, this.#renderRoot);
421
408
  } catch (err) {
422
- this.dispatchEvent(new CustomEvent(RenderErrorEvent, { bubbles: true, composed: true, detail: err }));
409
+ this.dispatchEvent(new CustomEvent(_RenderErrorEvent, { bubbles: true, composed: true, detail: err }));
410
+ throw err;
423
411
  }
424
412
  };
425
413
 
@@ -493,8 +481,12 @@ export abstract class GemElement extends HTMLElement {
493
481
  #connectedCallback = async () => {
494
482
  if (this.#isAppendReason) {
495
483
  this.#isAppendReason = false;
484
+ // https://github.com/mantou132/gem/issues/202
485
+ this.#clearStyle?.();
486
+ this.#clearStyle = this.#prepareStyle();
496
487
  return;
497
488
  }
489
+ this.#notInCons = true;
498
490
  this.#compat();
499
491
 
500
492
  const { observedStores, mode, penetrable } = this.#metadata;
@@ -503,7 +495,6 @@ export abstract class GemElement extends HTMLElement {
503
495
  // 如果渲染内容需要应用外部样式,需要手动 `delete` 边界
504
496
  if (!mode && this.#renderList.length && !penetrable) this.#internals.states.add(BoundaryCSSState);
505
497
 
506
- this.#isConnected = true;
507
498
  this.#disconnectStore = observedStores?.map((store) => connect(store, this.#update));
508
499
  this.#render(this.#renderItem);
509
500
  this.#isMounted = true;
@@ -553,7 +544,7 @@ export abstract class GemElement extends HTMLElement {
553
544
  // 是否要异步执行回调?
554
545
  this.#effectList = clearEffect(this.#effectList);
555
546
  this.#memoList = clearEffect(this.#memoList);
556
- execCallback(this.#clearStyle);
547
+ this.#clearStyle?.();
557
548
  return GemElement.#final;
558
549
  }
559
550
 
@@ -576,7 +567,7 @@ export abstract class GemElement extends HTMLElement {
576
567
  callback,
577
568
  getDep,
578
569
  initialized: this.#isMounted,
579
- fixed: !this.#isConnected,
570
+ fixed: !this.#notInCons,
580
571
  };
581
572
  // 已挂载时立即执行副作用,未挂载时等挂载后执行
582
573
  if (this.#isMounted) {
@@ -607,7 +598,7 @@ export abstract class GemElement extends HTMLElement {
607
598
  this.#memoList.push({
608
599
  callback,
609
600
  getDep,
610
- fixed: !this.#isConnected,
601
+ fixed: !this.#notInCons,
611
602
  });
612
603
  };
613
604