@mantou/gem 2.2.1 → 2.2.3

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 (93) hide show
  1. package/dist/gem.es.js +1154 -1154
  2. package/dist/gem.umd.js +12 -15
  3. package/elements/base/gesture.d.ts +1 -0
  4. package/elements/base/gesture.d.ts.map +1 -1
  5. package/elements/base/gesture.js +3 -2
  6. package/elements/base/gesture.js.map +1 -1
  7. package/elements/base/link.d.ts +1 -1
  8. package/elements/base/link.d.ts.map +1 -1
  9. package/elements/base/link.js +13 -9
  10. package/elements/base/link.js.map +1 -1
  11. package/elements/base/modal-factory.d.ts +73 -48
  12. package/elements/base/modal-factory.d.ts.map +1 -1
  13. package/elements/base/modal-factory.js +12 -11
  14. package/elements/base/modal-factory.js.map +1 -1
  15. package/elements/base/route.d.ts +1 -1
  16. package/elements/base/route.d.ts.map +1 -1
  17. package/elements/base/route.js +11 -15
  18. package/elements/base/route.js.map +1 -1
  19. package/elements/base/title.d.ts.map +1 -1
  20. package/elements/base/title.js +2 -4
  21. package/elements/base/title.js.map +1 -1
  22. package/elements/base/unsafe.d.ts +3 -2
  23. package/elements/base/unsafe.d.ts.map +1 -1
  24. package/elements/base/unsafe.js +45 -24
  25. package/elements/base/unsafe.js.map +1 -1
  26. package/helper/i18n.d.ts.map +1 -1
  27. package/helper/logger.d.ts.map +1 -1
  28. package/helper/react-utils.d.ts.map +1 -1
  29. package/helper/react-utils.js +2 -6
  30. package/helper/react-utils.js.map +1 -1
  31. package/helper/request.d.ts +4 -4
  32. package/helper/request.d.ts.map +1 -1
  33. package/helper/request.js +14 -14
  34. package/helper/request.js.map +1 -1
  35. package/helper/ssr-shim.d.ts.map +1 -1
  36. package/helper/ssr-shim.js +0 -1
  37. package/helper/ssr-shim.js.map +1 -1
  38. package/helper/theme.d.ts +15 -3
  39. package/helper/theme.d.ts.map +1 -1
  40. package/helper/theme.js +22 -7
  41. package/helper/theme.js.map +1 -1
  42. package/lib/decorators.d.ts +50 -50
  43. package/lib/decorators.d.ts.map +1 -1
  44. package/lib/decorators.js +52 -51
  45. package/lib/decorators.js.map +1 -1
  46. package/lib/directive.d.ts.map +1 -1
  47. package/lib/element.d.ts.map +1 -1
  48. package/lib/element.js.map +1 -1
  49. package/lib/history.d.ts +1 -1
  50. package/lib/history.d.ts.map +1 -1
  51. package/lib/history.js +3 -3
  52. package/lib/history.js.map +1 -1
  53. package/lib/lit-html.d.ts +17 -9
  54. package/lib/lit-html.d.ts.map +1 -1
  55. package/lib/lit-html.js +6 -9
  56. package/lib/lit-html.js.map +1 -1
  57. package/lib/reactive.d.ts +18 -5
  58. package/lib/reactive.d.ts.map +1 -1
  59. package/lib/reactive.js +29 -22
  60. package/lib/reactive.js.map +1 -1
  61. package/lib/repeat.d.ts.map +1 -1
  62. package/lib/store.d.ts +2 -2
  63. package/lib/store.d.ts.map +1 -1
  64. package/lib/store.js +1 -0
  65. package/lib/store.js.map +1 -1
  66. package/lib/utils.d.ts +4 -1
  67. package/lib/utils.d.ts.map +1 -1
  68. package/lib/utils.js +11 -9
  69. package/lib/utils.js.map +1 -1
  70. package/lib/version.d.ts +1 -1
  71. package/lib/version.js +1 -1
  72. package/package.json +2 -2
  73. package/src/elements/base/gesture.ts +4 -2
  74. package/src/elements/base/link.ts +3 -3
  75. package/src/elements/base/modal-factory.ts +12 -11
  76. package/src/elements/base/route.ts +9 -15
  77. package/src/elements/base/title.ts +2 -4
  78. package/src/elements/base/unsafe.ts +33 -14
  79. package/src/helper/react-utils.ts +4 -12
  80. package/src/helper/request.ts +17 -17
  81. package/src/helper/ssr-shim.ts +0 -1
  82. package/src/helper/theme.ts +25 -10
  83. package/src/lib/decorators.ts +53 -56
  84. package/src/lib/element.ts +0 -1
  85. package/src/lib/history.ts +3 -4
  86. package/src/lib/lit-html.ts +13 -6
  87. package/src/lib/reactive.ts +66 -28
  88. package/src/lib/store.ts +3 -2
  89. package/src/lib/utils.ts +15 -8
  90. package/src/lib/version.ts +1 -1
  91. package/src/test/gem-element/advance.test.ts +1 -1
  92. package/src/test/gem-element/basic.test.ts +1 -2
  93. package/src/test/utils.test.ts +1 -1
@@ -1,3 +1,5 @@
1
+ // biome-ignore-all lint/plugin/no_this_effect: 定义装饰器
2
+
1
3
  import type { TemplateResult } from './lit-html';
2
4
  import type { Metadata, Sheet } from './reactive';
3
5
  import { _createTemplate, _RenderErrorEvent, GemElement, render, UpdateToken } from './reactive';
@@ -150,11 +152,11 @@ function decoratorAttr<T extends GemElement>(context: ClassFieldDecoratorContext
150
152
  /**
151
153
  * 定义一个响应式的 attribute,驼峰字段名将自动映射到烤串 attribute,默认值为空字符串;不要覆盖定义
152
154
  *
153
- * For example
155
+ * @example
154
156
  * ```ts
155
- * class App extends GemElement {
156
- * @attribute attr: string;
157
- * }
157
+ * class App extends GemElement {
158
+ * @​attribute attr: string;
159
+ * }
158
160
  * ```
159
161
  */
160
162
  export function attribute<T extends GemElement>(_: undefined, context: ClassFieldDecoratorContext<T, string>) {
@@ -170,11 +172,11 @@ export function numattribute<T extends GemElement>(_: undefined, context: ClassF
170
172
  /**
171
173
  * 定义一个响应式的 property,注意值可能为 `undefined`;不要覆盖定义
172
174
  *
173
- * For example
175
+ * @example
174
176
  * ```ts
175
- * class App extends GemElement {
176
- * @property prop: Data | undefined;
177
- * }
177
+ * class App extends GemElement {
178
+ * @​property prop: Data | undefined;
179
+ * }
178
180
  * ```
179
181
  */
180
182
  export function property<T extends GemElement>(_: undefined, context: ClassFieldDecoratorContext<T>) {
@@ -196,14 +198,14 @@ export function property<T extends GemElement>(_: undefined, context: ClassField
196
198
  /**
197
199
  * 依赖 `GemElement.memo`
198
200
  *
199
- * For example
201
+ * @example
200
202
  * ```ts
201
- * class App extends GemElement {
202
- * @memo(() => [])
203
- * get data() {
204
- * return 1;
205
- * }
206
- * }
203
+ * class App extends GemElement {
204
+ * @​memo(() => [])
205
+ * get data() {
206
+ * return 1;
207
+ * }
208
+ * }
207
209
  * ```
208
210
  */
209
211
  export function memo<T extends GemElement, V = any, K = any[] | undefined>(
@@ -240,14 +242,14 @@ export function memo<T extends GemElement, V = any, K = any[] | undefined>(
240
242
  * - 方法执行在字段前面
241
243
  * - 如果没有 oldValue,在为首次执行
242
244
  *
243
- * For example
245
+ * @example
244
246
  * ```ts
245
- * class App extends GemElement {
246
- * @effect(() => [])
247
- * #fetchData(newValue, oldValue) {
248
- * console.log('fetch')
249
- * }
250
- * }
247
+ * class App extends GemElement {
248
+ * @​effect(() => [])
249
+ * #fetchData(newValue, oldValue) {
250
+ * console.log('fetch')
251
+ * }
252
+ * }
251
253
  * ```
252
254
  */
253
255
  export function effect<T extends GemElement, V extends (depValues: K, oldDepValues?: K) => any, K = any[] | undefined>(
@@ -331,11 +333,11 @@ function defineCSSState(target: GemElementPrototype, prop: string, stateStr: str
331
333
  * 重新赋值转换 css 伪类
332
334
  * 将来也可能用于 IDE 识别
333
335
  *
334
- * For example
336
+ * @example
335
337
  * ```ts
336
- * class App extends GemElement {
337
- * @state state: boolean;
338
- * }
338
+ * class App extends GemElement {
339
+ * @​state state: boolean;
340
+ * }
339
341
  * ```
340
342
  */
341
343
  export function state<T extends GemElement>(_: undefined, context: ClassFieldDecoratorContext<T, boolean>) {
@@ -374,12 +376,12 @@ function defineStaticField(
374
376
  /**
375
377
  * 定义一个内部 slot,默认值即为字段名,不能使用全局属性 `slot`
376
378
  *
377
- * For example
379
+ * @example
378
380
  * ```ts
379
- * class App extends GemElement {
380
- * @slot slot: string;
381
- * @slot static slot: string;
382
- * }
381
+ * class App extends GemElement {
382
+ * @​slot slot: string;
383
+ * @​slot static slot: string;
384
+ * }
383
385
  * ```
384
386
  */
385
387
  export function slot(_: undefined, context: ClassFieldDecoratorContext<any, string>) {
@@ -391,12 +393,12 @@ export function slot(_: undefined, context: ClassFieldDecoratorContext<any, stri
391
393
  /**
392
394
  * 定义一个内部 part,默认值即为字段名,不能使用全局属性 `part`
393
395
  *
394
- * For example
396
+ * @example
395
397
  * ```ts
396
- * class App extends GemElement {
397
- * @part part: string;
398
- * @part static part: string;
399
- * }
398
+ * class App extends GemElement {
399
+ * @​part part: string;
400
+ * @​part static part: string;
401
+ * }
400
402
  * ```
401
403
  */
402
404
  export function part(_: undefined, context: ClassFieldDecoratorContext<any, string>) {
@@ -421,11 +423,11 @@ export type Emitter<T = any> = ((
421
423
  * 定义一个事件发射器,类似 `HTMLElement.click`,
422
424
  * 调用时将同步发送一个和字段名称的烤串式格式的自定义事件
423
425
  *
424
- * For example
426
+ * @example
425
427
  * ```ts
426
- * class App extends GemElement {
427
- * @emitter load: Function;
428
- * }
428
+ * class App extends GemElement {
429
+ * @​emitter load: Function;
430
+ * }
429
431
  * ```
430
432
  */
431
433
  export function emitter<T extends GemElement>(_: undefined, context: ClassFieldDecoratorContext<T, Emitter>) {
@@ -453,10 +455,10 @@ function defineEmitter<T extends GemElement>(
453
455
  /**
454
456
  * 分配一个构造的样式表,前面的优先级越高(因为装饰器是越近越早执行)
455
457
  *
456
- * For example
458
+ * @example
457
459
  * ```ts
458
- * @adoptedStyle(stylesheet)
459
- * class App extends GemElement {}
460
+ * ​@​adoptedStyle(stylesheet)
461
+ * class App extends GemElement {}
460
462
  * ```
461
463
  */
462
464
  export function adoptedStyle(sheet: Sheet<unknown>) {
@@ -468,10 +470,10 @@ export function adoptedStyle(sheet: Sheet<unknown>) {
468
470
  /**
469
471
  * 链接一个 store,当 store 更新时更新元素
470
472
  *
471
- * For example
473
+ * @example
472
474
  * ```ts
473
- * @connectStore(store)
474
- * class App extends GemElement {}
475
+ * ​@​connectStore(store)
476
+ * class App extends GemElement {}
475
477
  * ```
476
478
  */
477
479
  export function connectStore(store: Store<any>) {
@@ -480,15 +482,10 @@ export function connectStore(store: Store<any>) {
480
482
  };
481
483
  }
482
484
 
483
- export function shadow({
484
- mode = 'open',
485
- serializable = true,
486
- delegatesFocus,
487
- slotAssignment,
488
- }: Partial<ShadowRootInit> = {}) {
485
+ export function shadow({ mode = 'open', serializable = true, ...rest }: Partial<ShadowRootInit> = {}) {
489
486
  return (_: any, context: ClassDecoratorContext) => {
490
487
  const metadata = context.metadata as Metadata;
491
- assign(metadata, { mode, serializable, delegatesFocus, slotAssignment });
488
+ assign(metadata, { mode, serializable, ...rest });
492
489
  };
493
490
  }
494
491
 
@@ -523,10 +520,10 @@ export function aria(info: Metadata['aria']) {
523
520
  /**
524
521
  * 定义自定义元素
525
522
  *
526
- * For example
523
+ * @example
527
524
  * ```ts
528
- * @customElement('my-element')
529
- * class App extends GemElement {}
525
+ * ​@​customElement('my-element')
526
+ * class App extends GemElement {}
530
527
  * ```
531
528
  */
532
529
  export function customElement(name: string) {
@@ -14,7 +14,6 @@ export * from './reactive';
14
14
  export { repeat } from './repeat';
15
15
 
16
16
  declare global {
17
- // eslint-disable-next-line no-var
18
17
  var __GEM_DEVTOOLS__HOOK__:
19
18
  | (typeof reactiveExports & typeof decoratorsExports & typeof storeExports & typeof versionExports)
20
19
  | Record<string, never>
@@ -188,7 +188,6 @@ const gemTitleStore = createStore({ defaultTitle: document.title, url: '', title
188
188
  const HISTORY = { history: gemHistory, titleStore: gemTitleStore, basePathStore: gemBasePathStore };
189
189
 
190
190
  declare global {
191
- // eslint-disable-next-line no-var
192
191
  var _GEMHISTORY: typeof HISTORY | undefined;
193
192
  }
194
193
 
@@ -269,10 +268,10 @@ if (!globalThis._GEMHISTORY) {
269
268
  if (!paramsMap.has(newState.$key)) {
270
269
  const { pathname, search, hash } = location;
271
270
  paramsMap.set(newState.$key, {
272
- path: pathname,
271
+ path: getInternalPath(pathname),
273
272
  query: new QueryString(search),
274
273
  hash: decodeURIComponent(hash),
275
- title: newState.$title, // document.title 是导航前的
274
+ title: gemTitleStore.title || newState.$title, // document.title 是导航前的
276
275
  data: newState,
277
276
  });
278
277
  }
@@ -316,4 +315,4 @@ if (!globalThis._GEMHISTORY) {
316
315
  }
317
316
 
318
317
  const { history, titleStore, basePathStore } = globalThis._GEMHISTORY;
319
- export { history, titleStore, basePathStore };
318
+ export { basePathStore, history, titleStore };
@@ -1,3 +1,6 @@
1
+ /** biome-ignore-all lint/complexity/noCommaOperator: lit */
2
+ /** biome-ignore-all lint/style/useTemplate: lit */
3
+
1
4
  /**
2
5
  * @license
3
6
  * Copyright 2017 Google LLC
@@ -371,6 +374,7 @@ const getTemplateHtml = (strings: TemplateStringsArray, type: ResultType): [Trus
371
374
 
372
375
  /** @internal */
373
376
  export type { Template };
377
+
374
378
  class Template {
375
379
  /** @internal */
376
380
  el!: HTMLTemplateElement;
@@ -544,15 +548,13 @@ function resolveDirective(
544
548
  return value;
545
549
  }
546
550
 
547
- export type { TemplateInstance };
548
551
  /**
549
552
  * An updateable instance of a Template. Holds references to the Parts used to
550
553
  * update the template instance.
551
554
  */
552
- class TemplateInstance implements Disconnectable {
555
+ export class TemplateInstance implements Disconnectable {
553
556
  _$template: Template;
554
557
  _$parts: Array<Part | undefined> = [];
555
- /** @internal */
556
558
  _$conditions: ConditionChain[] = [];
557
559
 
558
560
  /** @internal */
@@ -653,12 +655,13 @@ class TemplateInstance implements Disconnectable {
653
655
  }
654
656
  }
655
657
 
656
- class ConditionChain {
658
+ export class ConditionChain {
657
659
  readonly mark: Comment;
658
660
  readonly list: { ele: Element; idx?: number }[];
659
661
 
660
662
  constructor(ele: Element, idx: number) {
661
663
  const mark = createMarker();
664
+ (mark as any)._$chain = this;
662
665
  ele.before(mark);
663
666
  this.mark = mark;
664
667
  this.list = [{ ele, idx }];
@@ -1009,6 +1012,7 @@ export interface RootPart extends ChildPart {
1009
1012
  }
1010
1013
 
1011
1014
  export type { AttributePart };
1015
+
1012
1016
  class AttributePart implements Disconnectable {
1013
1017
  readonly type: typeof ATTRIBUTE_PART | typeof PROPERTY_PART | typeof BOOLEAN_ATTRIBUTE_PART | typeof EVENT_PART =
1014
1018
  ATTRIBUTE_PART;
@@ -1132,6 +1136,7 @@ class AttributePart implements Disconnectable {
1132
1136
  }
1133
1137
 
1134
1138
  export type { PropertyPart };
1139
+
1135
1140
  class PropertyPart extends AttributePart {
1136
1141
  override readonly type = PROPERTY_PART;
1137
1142
 
@@ -1142,6 +1147,7 @@ class PropertyPart extends AttributePart {
1142
1147
  }
1143
1148
 
1144
1149
  export type { BooleanAttributePart };
1150
+
1145
1151
  class BooleanAttributePart extends AttributePart {
1146
1152
  override readonly type = BOOLEAN_ATTRIBUTE_PART;
1147
1153
 
@@ -1154,6 +1160,7 @@ class BooleanAttributePart extends AttributePart {
1154
1160
  type EventListenerWithOptions = EventListenerOrEventListenerObject & Partial<AddEventListenerOptions>;
1155
1161
 
1156
1162
  export type { EventPart };
1163
+
1157
1164
  class EventPart extends AttributePart {
1158
1165
  override readonly type = EVENT_PART;
1159
1166
 
@@ -1189,8 +1196,7 @@ class EventPart extends AttributePart {
1189
1196
  }
1190
1197
  }
1191
1198
 
1192
- export type { ElementPart };
1193
- class ElementPart implements Disconnectable {
1199
+ export class ElementPart implements Disconnectable {
1194
1200
  readonly type = ELEMENT_PART;
1195
1201
 
1196
1202
  /** @internal */
@@ -1244,6 +1250,7 @@ class ElementPart implements Disconnectable {
1244
1250
  export const createRef = <T = Element>() => new Ref<T>();
1245
1251
 
1246
1252
  export type { Ref };
1253
+
1247
1254
  class Ref<T = Element> {
1248
1255
  value?: T;
1249
1256
  }
@@ -1,7 +1,19 @@
1
+ // biome-ignore-all lint/plugin/no_this_effect: 兼容生命周期
2
+
1
3
  import type * as Lit from './lit-html';
2
4
  import type { Store } from './store';
3
5
  import { connect } from './store';
4
- import { addListener, addMicrotask, createUpdater, GemError, isArrayChange, LinkedList, randomStr, raw } from './utils';
6
+ import {
7
+ addListener,
8
+ addMicrotask,
9
+ createUpdater,
10
+ GemError,
11
+ isArrayChange,
12
+ LinkedList,
13
+ randomStr,
14
+ raw,
15
+ type Updater,
16
+ } from './utils';
5
17
 
6
18
  export let html = ((arr: TemplateStringsArray, ...args: any[]) =>
7
19
  raw(arr, ...args.map((e) => (e instanceof Element ? e.outerHTML : e)))) as unknown as typeof Lit.html;
@@ -13,6 +25,14 @@ export function setEngine(h: typeof Lit.html, r: typeof Lit.render) {
13
25
  render = r;
14
26
  }
15
27
 
28
+ let hydrationEngine = (_value: unknown, _container: HTMLElement | DocumentFragment): Lit.RootPart | undefined => {
29
+ return;
30
+ };
31
+
32
+ export function setHydrationEngine(engine: typeof hydrationEngine) {
33
+ hydrationEngine = engine;
34
+ }
35
+
16
36
  const { assign, defineProperty } = Object;
17
37
 
18
38
  // https://github.com/tc39/proposal-decorator-metadata
@@ -20,12 +40,16 @@ defineProperty(Symbol, 'metadata', { value: Symbol.for('Symbol.metadata') });
20
40
 
21
41
  const execCallback = (fun: any) => typeof fun === 'function' && fun();
22
42
 
43
+ const ssrNativeQueueMicrotask = (globalThis as any).nQM;
44
+
23
45
  // 读取构造样式表,跨多个 gem 工作
24
46
  export const SheetToken = Symbol.for('gem@sheetToken');
47
+ export type SheetTokenType = typeof SheetToken;
25
48
  // 更新元素,跨多个 gem 工作
26
49
  export const UpdateToken = Symbol.for('gem@update');
27
50
 
28
51
  const BoundaryCSSState = 'gem-style-boundary';
52
+ const ElementCSSState = 'gem-element';
29
53
 
30
54
  /**@internal */
31
55
  export const _RenderErrorEvent = 'gem@render-error';
@@ -67,7 +91,7 @@ class GemCSSSheet {
67
91
  // 不需要 GC
68
92
  #record = new Map<any, GemCSSStyleSheet>();
69
93
  #used = new Map<GemCSSStyleSheet, string>();
70
- getStyle(host?: HTMLElement) {
94
+ getStyle(host?: HTMLElement, isEleTheme?: boolean) {
71
95
  const isLight = host && !(host as GemElement).internals?.shadowRoot;
72
96
 
73
97
  // 对同一类 dom 只使用同一个样式表
@@ -86,7 +110,7 @@ class GemCSSSheet {
86
110
  if (isLight) {
87
111
  // light dom 嵌套时需要选择子元素
88
112
  // `> *` 实际上是多范围?是否存在性能问题
89
- scope = `@scope (${host.tagName}) to (:state(${BoundaryCSSState}) > *)`;
113
+ scope = isEleTheme ? '@scope' : `@scope (${host.tagName}) to (:state(${BoundaryCSSState}) > *)`;
90
114
  // 不能使用 @layer,两个 @layer 不能覆盖,只有顺序起作用
91
115
  // 所以外部不能通过元素名称选择器来覆盖样式,除非样式在之前插入(会自动反转应用到尾部, see `appleCSSStyleSheet`)
92
116
  style = `${scope}{ ${style} }`;
@@ -143,16 +167,13 @@ export function css<T extends Record<string, string>>(
143
167
  // 对于已经有 `-` 的保留原始 key,支持覆盖修改
144
168
  // :scope 下可以写嵌套样式 &:xxx,:host() 下不行(子内容可以)
145
169
  sheet[key] = isScope || key.includes('-') ? key : `${key}-${randomStr()}`;
146
- style += `${isScope ? ':where(:scope:not([hidden])),:host(:where(:not([hidden])))' : `.${sheet[key]}`} {${rules[key]}}`;
170
+ style += `${isScope ? ':where(&:not([hidden])),:host(:where(:not([hidden])))' : `.${sheet[key]}`} {${rules[key]}}`;
147
171
  });
148
172
  }
149
173
  styleSheet.setContent(style);
150
174
  return sheet as Sheet<T>;
151
175
  }
152
176
 
153
- // TODO: move to transform?
154
- export const isSSR = typeof process !== 'undefined';
155
-
156
177
  const updateStyleSheets = (map: Map<CSSStyleSheet, number>, sheets: CSSStyleSheet[], value: number) => {
157
178
  let needUpdate = false;
158
179
  sheets.forEach((e) => {
@@ -164,7 +185,8 @@ const updateStyleSheets = (map: Map<CSSStyleSheet, number>, sheets: CSSStyleShee
164
185
  });
165
186
  if (needUpdate) {
166
187
  // 避免重复更新,例如 list 元素增删,全 light dom 时 document 更新
167
- addMicrotask(rootUpdateFnMap.get(map)!, isSSR);
188
+ // 在 SSR 中应该区别 effect 强制执行
189
+ addMicrotask(rootUpdateFnMap.get(map)!, ssrNativeQueueMicrotask);
168
190
  }
169
191
  };
170
192
 
@@ -175,7 +197,7 @@ const appleCSSStyleSheet = (ele: HTMLElement, sheets: CSSStyleSheet[]) => {
175
197
  rootStyleSheetInfo.set(root, map);
176
198
  rootUpdateFnMap.set(map, () => {
177
199
  // 先找到外部样式表
178
- const newSheets = root.adoptedStyleSheets.filter((e) => !map.has(e));
200
+ const newSheets = (root.adoptedStyleSheets || []).filter((e) => !map.has(e));
179
201
  map.forEach((count, sheet) => count && newSheets.push(sheet));
180
202
  // 外层元素的样式要放到最后,以提升优先级,但是只考虑第一次出现样式表的位置
181
203
  root.adoptedStyleSheets = newSheets.reverse();
@@ -189,7 +211,7 @@ const appleCSSStyleSheet = (ele: HTMLElement, sheets: CSSStyleSheet[]) => {
189
211
  };
190
212
 
191
213
  /**必须使用在字段中,否则会读取到错误的实例 */
192
- export let createState: <T>(initState: T) => ReturnType<typeof createUpdater<T>>;
214
+ export let createState: <T>(initState: T) => Updater<T>;
193
215
 
194
216
  type GetDepFun<T> = () => T;
195
217
  type EffectCallback<T> = (depValues: T, oldDepValues?: T) => any;
@@ -234,6 +256,10 @@ export let _createTemplate: (ele: GemElement, item: RenderItem) => void;
234
256
  export type Metadata = Partial<ShadowRootInit> & {
235
257
  /** 内容可被外部样式化 */
236
258
  penetrable?: boolean;
259
+ /**
260
+ * 这个属性用来解决同步渲染长列表,且渲染过程有大量计算阻塞主线程
261
+ * 否则应该考虑 `content-visibility: auto` 和虚拟列表
262
+ */
237
263
  noBlocking?: boolean;
238
264
  aria?: Partial<
239
265
  ARIAMixin & {
@@ -258,7 +284,7 @@ export type Metadata = Partial<ShadowRootInit> & {
258
284
  // global render task pool
259
285
  const noBlockingTaskList = new LinkedList<() => void>();
260
286
  const tick = (timeStamp = performance.now()) => {
261
- if (performance.now() > timeStamp + 16) return requestAnimationFrame(tick);
287
+ if (performance.now() > timeStamp + 50) return requestAnimationFrame(tick);
262
288
  const task = noBlockingTaskList.get();
263
289
  if (task) {
264
290
  task();
@@ -269,12 +295,28 @@ noBlockingTaskList.addEventListener('start', () => addMicrotask(tick));
269
295
 
270
296
  let currentConstructGemElement: GemElement;
271
297
 
298
+ type KebabCase<S extends string> = S extends `${infer Head}${infer Tail}`
299
+ ? Tail extends Uncapitalize<Tail>
300
+ ? `${Lowercase<Head>}${KebabCase<Tail>}`
301
+ : `${Lowercase<Head>}-${KebabCase<Tail>}`
302
+ : S;
303
+
272
304
  export abstract class GemElement extends HTMLElement {
305
+ /** 用来判断 attribute 是否存在,但是 length/item 将读取到 NamedNodeMap */
306
+ declare readonly attributes: NamedNodeMap & {
307
+ [K in keyof this as K extends keyof NamedNodeMap
308
+ ? never
309
+ : K extends keyof GemElement
310
+ ? never
311
+ : K extends Uppercase<string & K>
312
+ ? never
313
+ : `${KebabCase<string & K>}`]?: Attr;
314
+ };
273
315
  #renderRoot: HTMLElement | ShadowRoot;
274
316
  #internals: ElementInternals & {
275
317
  // 有别于 adoptedStyleSheets,用来定义单个实例样式
276
318
  sheets: GemCSSStyleSheet[];
277
- stateList: ReturnType<typeof createState<any>>[];
319
+ stateList: Updater<any>[];
278
320
  };
279
321
  #effectList: EffectItem<any>[] = [];
280
322
  #memoList: EffectItem<any>[] = [];
@@ -297,13 +339,15 @@ export abstract class GemElement extends HTMLElement {
297
339
  const state = createUpdater(initState, (payload) => {
298
340
  const effect = ele.#effectList.at(0);
299
341
  // https://github.com/mantou132/gem/issues/203
300
- if (ele.#isMounted && effect && !effect.initialized) {
301
- throw new GemError(`Do't set state sync before insert the DOM`);
342
+ // SSR 不会执行 initEffect,应该跳过
343
+ if (!ssrNativeQueueMicrotask && ele.#isMounted && effect && !effect.initialized) {
344
+ throw new GemError(`Don't set state sync before insert the DOM`);
302
345
  }
303
346
  assign(state, payload);
304
347
  // 避免无限刷新
305
348
  // 挂载前 set state 不应该触发更新
306
- if (!ele.#rendering && ele.#isMounted) addMicrotask(ele.#update);
349
+ // SSR 中渲染 <gem-route>,其他情况的 SSR 应该也可以执行
350
+ if (!ele.#rendering && ele.#isMounted) addMicrotask(ele.#update, ssrNativeQueueMicrotask);
307
351
  });
308
352
  ele.#internals.stateList.push(state);
309
353
  return state;
@@ -314,13 +358,13 @@ export abstract class GemElement extends HTMLElement {
314
358
  constructor() {
315
359
  super();
316
360
 
317
- // eslint-disable-next-line @typescript-eslint/no-this-alias
318
361
  currentConstructGemElement = this;
319
362
 
320
- const { mode, serializable, delegatesFocus, slotAssignment, aria } = this.#metadata;
363
+ const { aria, mode, delegatesFocus, ...rest } = this.#metadata;
321
364
  const { focusable, ...internalsAria } = aria || {};
322
365
 
323
- this.#renderRoot = !mode ? this : this.attachShadow({ mode, serializable, delegatesFocus, slotAssignment });
366
+ // 这里认为 rest 不会有自定义字段和 ShadowRootInit 参数冲突
367
+ this.#renderRoot = !mode ? this : this.shadowRoot || this.attachShadow({ mode, delegatesFocus, ...rest });
324
368
  this.#internals = this.attachInternals() as GemElement['internals'];
325
369
  this.#internals.stateList = [];
326
370
  this.#internals.sheets = [];
@@ -385,16 +429,8 @@ export abstract class GemElement extends HTMLElement {
385
429
  const isLight = this.#renderRoot === this;
386
430
  const temp = item ? item.render() : isLight ? undefined : html`<slot></slot>`;
387
431
  if (temp === undefined) return;
388
- render(
389
- temp !== null
390
- ? temp
391
- : isLight
392
- ? // prettier-ignore
393
- html`<style>@scope{:scope{display:none !important}}</style>`
394
- : // prettier-ignore
395
- html`<style>:host{display:none !important}</style>`,
396
- this.#renderRoot,
397
- );
432
+ const r = temp !== null ? temp : html`<style>${isLight ? '@scope' : ':host'}{&{display:none!important;}}</style>`;
433
+ hydrationEngine(r, this.#renderRoot) || render(r, this.#renderRoot);
398
434
  } catch (err) {
399
435
  this.dispatchEvent(new CustomEvent(_RenderErrorEvent, { bubbles: true, composed: true, detail: err }));
400
436
  throw err;
@@ -483,6 +519,8 @@ export abstract class GemElement extends HTMLElement {
483
519
 
484
520
  const { observedStores, mode, penetrable } = this.#metadata;
485
521
 
522
+ // https://github.com/w3c/csswg-drafts/issues/10407#issuecomment-4418616596
523
+ this.#internals.states.add(ElementCSSState);
486
524
  // 有渲染函数的 light dom 应该添加边界,防止内容被外部样式化
487
525
  // 如果渲染内容需要应用外部样式,需要手动 `delete` 边界
488
526
  if (!mode && this.#renderList.length && !penetrable) this.#internals.states.add(BoundaryCSSState);
package/src/lib/store.ts CHANGED
@@ -1,12 +1,13 @@
1
- import { addMicrotask, createUpdater } from './utils';
1
+ import { addMicrotask, createUpdater, type Updater } from './utils';
2
2
 
3
- export type Store<T = any> = ReturnType<typeof createUpdater<T>>;
3
+ export type Store<T = any> = Updater<T>;
4
4
 
5
5
  /**@internal */
6
6
  export const _StoreListenerMap = new WeakMap<Store<any>, Set<() => void>>();
7
7
 
8
8
  export function createStore<T = any>(originStore: T) {
9
9
  const store = createUpdater(originStore, (value?: Partial<T>) => {
10
+ // biome-ignore lint/plugin/assign: 未知类型
10
11
  Object.assign(store, value);
11
12
  _StoreListenerMap.get(store)?.forEach((func) => addMicrotask(func));
12
13
  });
package/src/lib/utils.ts CHANGED
@@ -1,17 +1,17 @@
1
1
  const { assign, fromEntries, entries, keys } = Object;
2
2
 
3
+ // 用原生 Chrome 找不到调用栈
4
+ function queueMicrotask(cb: () => void) {
5
+ Promise.resolve().then(cb);
6
+ }
7
+
3
8
  const microtaskSet = new Set<() => void>();
4
- export function addMicrotask(func: () => void, isNodeForceMicrotask?: boolean) {
9
+ export function addMicrotask(func: () => void, method = queueMicrotask) {
5
10
  if (microtaskSet.has(func)) return;
6
- const next = () => {
11
+ method(() => {
7
12
  microtaskSet.delete(func);
8
13
  func();
9
- };
10
- if (isNodeForceMicrotask) {
11
- process.nextTick(next);
12
- } else {
13
- Promise.resolve().then(next);
14
- }
14
+ });
15
15
  microtaskSet.add(func);
16
16
  }
17
17
 
@@ -42,6 +42,8 @@ export function createUpdater<T, Fn = (payload?: Partial<T>) => any>(initState:
42
42
  return state as Fn & Omit<OverrideFn, keyof T> & T;
43
43
  }
44
44
 
45
+ export type Updater<T, Fn = (payload?: Partial<T>) => any> = ReturnType<typeof createUpdater<T, Fn>>;
46
+
45
47
  // 不编码 hash 用于比较
46
48
  export function absoluteLocation(currentPath = '', relativePath = '') {
47
49
  const { pathname, search, hash } = new URL(relativePath, location.origin + currentPath);
@@ -157,6 +159,11 @@ export class LinkedList<T = any> extends EventTarget {
157
159
  return deleteItem;
158
160
  }
159
161
 
162
+ clear() {
163
+ this.#map.clear();
164
+ this.#firstItem = this.#lastItem = undefined;
165
+ }
166
+
160
167
  /** 获取头部元素,会从链表删除 */
161
168
  get(): T | undefined {
162
169
  const firstItem = this.#firstItem;
@@ -1 +1 @@
1
- export const version = '2.2.1';
1
+ export const version = '2.2.3';
@@ -341,7 +341,7 @@ describe('gem element 继承', () => {
341
341
  });
342
342
  el.sayHi();
343
343
  await Promise.resolve();
344
- expect(window.name).to.equal(name + '21');
344
+ expect(window.name).to.equal(`${name}21`);
345
345
  });
346
346
  });
347
347
  @customElement('render-empty')
@@ -108,8 +108,7 @@ describe('基本 gem element 测试', () => {
108
108
  expect(el.disabled).to.equal(true);
109
109
  expect(el.count).to.equal(2);
110
110
  expect(el.shadowRoot?.textContent).to.equal('attr: value, disabled: true, count: 2, prop: , state: ');
111
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
112
- // @ts-ignore
111
+ // @ts-expect-error
113
112
  el.attr = null;
114
113
  await Promise.resolve();
115
114
  expect(el.hasAttribute('attr')).to.equal(false);
@@ -110,7 +110,7 @@ describe('utils 测试', () => {
110
110
  });
111
111
  expect(cssSheet.scroll.startsWith('scroll')).to.true;
112
112
  const rules = cssSheet[SheetToken].getStyle().cssRules;
113
- expect(rules.item(0).selectorText).to.equal(':where(:scope:not([hidden])), :host(:where(:not([hidden])))');
113
+ expect(rules.item(0).selectorText).to.equal(':where(&:not([hidden])), :host(:where(:not([hidden])))');
114
114
  expect(rules.item(1).selectorText.startsWith('.scroll')).to.true;
115
115
  expect(rules.item(1).style.background).to.equal('red');
116
116
  expect(rules.item(1).cssRules.item(0).selectorText).to.equal('&:hover *');