@mantou/gem 2.0.0 → 2.1.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 (87) hide show
  1. package/dist/gem.es.js +1172 -1188
  2. package/dist/gem.umd.js +25 -25
  3. package/elements/base/gesture.d.ts +1 -2
  4. package/elements/base/gesture.d.ts.map +1 -1
  5. package/elements/base/gesture.js +21 -28
  6. package/elements/base/gesture.js.map +1 -1
  7. package/elements/base/link.d.ts +1 -2
  8. package/elements/base/link.d.ts.map +1 -1
  9. package/elements/base/link.js +17 -10
  10. package/elements/base/link.js.map +1 -1
  11. package/elements/base/modal-factory.d.ts +6 -4
  12. package/elements/base/modal-factory.d.ts.map +1 -1
  13. package/elements/base/modal-factory.js +3 -3
  14. package/elements/base/modal-factory.js.map +1 -1
  15. package/elements/base/route.d.ts +8 -2
  16. package/elements/base/route.d.ts.map +1 -1
  17. package/elements/base/route.js +4 -4
  18. package/elements/base/route.js.map +1 -1
  19. package/elements/base/title.d.ts +0 -1
  20. package/elements/base/title.d.ts.map +1 -1
  21. package/elements/base/title.js +15 -5
  22. package/elements/base/title.js.map +1 -1
  23. package/elements/base/unsafe.d.ts +1 -1
  24. package/elements/base/unsafe.d.ts.map +1 -1
  25. package/elements/base/unsafe.js +10 -4
  26. package/elements/base/unsafe.js.map +1 -1
  27. package/elements/base/use.d.ts +0 -1
  28. package/elements/base/use.d.ts.map +1 -1
  29. package/elements/base/use.js +11 -2
  30. package/elements/base/use.js.map +1 -1
  31. package/helper/i18n.d.ts.map +1 -1
  32. package/helper/i18n.js +11 -11
  33. package/helper/i18n.js.map +1 -1
  34. package/helper/theme.d.ts +8 -7
  35. package/helper/theme.d.ts.map +1 -1
  36. package/helper/theme.js +21 -21
  37. package/helper/theme.js.map +1 -1
  38. package/lib/decorators.d.ts +1 -1
  39. package/lib/decorators.d.ts.map +1 -1
  40. package/lib/decorators.js +3 -3
  41. package/lib/decorators.js.map +1 -1
  42. package/lib/element.d.ts +5 -4
  43. package/lib/element.d.ts.map +1 -1
  44. package/lib/element.js +14 -16
  45. package/lib/element.js.map +1 -1
  46. package/lib/history.d.ts +20 -8
  47. package/lib/history.d.ts.map +1 -1
  48. package/lib/history.js +12 -12
  49. package/lib/history.js.map +1 -1
  50. package/lib/store.d.ts +5 -10
  51. package/lib/store.d.ts.map +1 -1
  52. package/lib/store.js +7 -22
  53. package/lib/store.js.map +1 -1
  54. package/lib/utils.d.ts +1 -0
  55. package/lib/utils.d.ts.map +1 -1
  56. package/lib/utils.js +20 -14
  57. package/lib/utils.js.map +1 -1
  58. package/lib/version.d.ts +1 -1
  59. package/lib/version.d.ts.map +1 -1
  60. package/lib/version.js +1 -2
  61. package/lib/version.js.map +1 -1
  62. package/package.json +3 -4
  63. package/src/elements/base/gesture.ts +23 -25
  64. package/src/elements/base/link.ts +9 -9
  65. package/src/elements/base/modal-factory.ts +3 -3
  66. package/src/elements/base/route.ts +4 -4
  67. package/src/elements/base/title.ts +5 -4
  68. package/src/elements/base/unsafe.ts +6 -3
  69. package/src/elements/base/use.ts +4 -3
  70. package/src/helper/i18n.ts +11 -8
  71. package/src/helper/theme.ts +27 -26
  72. package/src/lib/decorators.ts +4 -4
  73. package/src/lib/element.ts +17 -19
  74. package/src/lib/history.ts +12 -12
  75. package/src/lib/store.ts +11 -31
  76. package/src/lib/utils.ts +22 -14
  77. package/src/lib/version.ts +1 -2
  78. package/src/test/gem-element/advance.test.ts +2 -2
  79. package/src/test/gem-element/basic.test.ts +3 -3
  80. package/src/test/gem-element/decorators.test.ts +4 -4
  81. package/src/test/store.test.ts +7 -7
  82. package/src/test/utils.test.ts +1 -1
  83. package/LICENSE +0 -21
  84. package/helper/react-shim.d.ts +0 -15
  85. package/helper/react-shim.d.ts.map +0 -1
  86. package/helper/react-shim.js +0 -56
  87. package/helper/react-shim.js.map +0 -1
@@ -1,6 +1,7 @@
1
- import { GemElement, html } from '../../lib/element';
1
+ import { createCSSSheet, GemElement } from '../../lib/element';
2
2
  import type { Emitter } from '../../lib/decorators';
3
- import { attribute, emitter, mounted, shadow } from '../../lib/decorators';
3
+ import { adoptedStyle, attribute, emitter, mounted } from '../../lib/decorators';
4
+ import { css } from '../../lib/utils';
4
5
 
5
6
  export type PanEventDetail = {
6
7
  // movement
@@ -44,6 +45,14 @@ function angleAB(
44
45
  );
45
46
  }
46
47
 
48
+ const style = createCSSSheet(css`
49
+ :scope:where(:not([hidden])) {
50
+ display: block;
51
+ user-select: none;
52
+ touch-action: attr(touch-action, none);
53
+ }
54
+ `);
55
+
47
56
  /**
48
57
  * 块级元素,如果要设置成 `contents`,则内容块要设置 `touch-action: none`
49
58
  * https://javascript.info/pointer-events#event-pointercancel
@@ -59,7 +68,7 @@ function angleAB(
59
68
  *
60
69
  * @attr touch-action
61
70
  */
62
- @shadow()
71
+ @adoptedStyle(style)
63
72
  export class GemGestureElement extends GemElement {
64
73
  @emitter pan: Emitter<PanEventDetail>;
65
74
  @emitter pinch: Emitter<PinchEventDetail>;
@@ -75,11 +84,8 @@ export class GemGestureElement extends GemElement {
75
84
 
76
85
  #startEventMap: Map<number, PointerEvent> = new Map();
77
86
 
78
- /** 指针移动事件记录 */
79
- movesMap: Map<number, PanEventDetail[]> = new Map();
80
-
81
87
  #getMoves = (pointerId: number) => {
82
- return this.movesMap.get(pointerId) || [];
88
+ return this._movesMap.get(pointerId) || [];
83
89
  };
84
90
 
85
91
  #getStartEvent = (pointerId: number) => {
@@ -87,7 +93,7 @@ export class GemGestureElement extends GemElement {
87
93
  };
88
94
 
89
95
  #getOtherLastMove = (pointerId: number) => {
90
- for (const id of this.movesMap.keys()) {
96
+ for (const id of this._movesMap.keys()) {
91
97
  if (id !== pointerId) {
92
98
  const moves = this.#getMoves(id);
93
99
  return moves[moves.length - 1] || this.#getStartEvent(id);
@@ -130,7 +136,7 @@ export class GemGestureElement extends GemElement {
130
136
  #onStart = (evt: PointerEvent) => {
131
137
  evt.stopPropagation();
132
138
  this.setPointerCapture(evt.pointerId);
133
- this.movesMap.set(evt.pointerId, []);
139
+ this._movesMap.set(evt.pointerId, []);
134
140
  this.#startEventMap.set(evt.pointerId, evt);
135
141
  if (evt.isPrimary) {
136
142
  this.#pressed = false;
@@ -163,7 +169,7 @@ export class GemGestureElement extends GemElement {
163
169
  moves.push(move);
164
170
  this.pan(move);
165
171
 
166
- if (this.movesMap.size !== 1) {
172
+ if (this._movesMap.size !== 1) {
167
173
  const secondaryPoint = this.#getOtherLastMove(pointerId) as PanEventDetail;
168
174
  const moveLen = Math.sqrt(movementX ** 2 + movementY ** 2);
169
175
  const distanceLen = Math.sqrt(
@@ -205,7 +211,7 @@ export class GemGestureElement extends GemElement {
205
211
  const { pointerId } = evt;
206
212
  window.clearTimeout(this.#pressTimer);
207
213
 
208
- if (this.movesMap.size === 1) {
214
+ if (this._movesMap.size === 1) {
209
215
  this.end(null);
210
216
  }
211
217
 
@@ -239,10 +245,12 @@ export class GemGestureElement extends GemElement {
239
245
  }
240
246
  }
241
247
 
242
- this.movesMap.delete(pointerId);
248
+ this._movesMap.delete(pointerId);
243
249
  this.#startEventMap.delete(pointerId);
244
250
  };
245
251
 
252
+ #preventDefault = (evt: Event) => evt.preventDefault();
253
+
246
254
  @mounted()
247
255
  #init = () => {
248
256
  this.addEventListener('pointerdown', this.#onStart);
@@ -252,19 +260,9 @@ export class GemGestureElement extends GemElement {
252
260
  // 为什么空白区域会自动触发 `pointercancel`?
253
261
  this.addEventListener('pointercancel', this.#onEnd);
254
262
 
255
- this.addEventListener('dragstart', (evt) => evt.preventDefault());
263
+ this.addEventListener('dragstart', this.#preventDefault);
256
264
  };
257
265
 
258
- render() {
259
- return html`
260
- <style>
261
- :host(:where(:not([hidden]))) {
262
- display: block;
263
- user-select: none;
264
- touch-action: ${this.touchAction || 'none'};
265
- }
266
- </style>
267
- <slot></slot>
268
- `;
269
- }
266
+ /** 指针移动事件记录 */
267
+ _movesMap: Map<number, PanEventDetail[]> = new Map();
270
268
  }
@@ -11,6 +11,7 @@ import {
11
11
  mounted,
12
12
  effect,
13
13
  boolattribute,
14
+ template,
14
15
  } from '../../lib/decorators';
15
16
  import { history, basePathStore } from '../../lib/history';
16
17
  import { absoluteLocation, css } from '../../lib/utils';
@@ -85,7 +86,7 @@ export class GemLinkElement extends GemElement {
85
86
  }
86
87
 
87
88
  #onClick = async () => {
88
- const locationString = this.getLocationString();
89
+ const locationString = this._getLocationString();
89
90
 
90
91
  if (!locationString) return;
91
92
 
@@ -133,12 +134,10 @@ export class GemLinkElement extends GemElement {
133
134
  }
134
135
  };
135
136
 
136
- #preventDefault = (e: MouseEvent) => {
137
- e.preventDefault();
138
- };
137
+ #preventDefault = (e: MouseEvent) => e.preventDefault();
139
138
 
140
139
  #getHint = () => {
141
- const locationString = this.getLocationString();
140
+ const locationString = this._getLocationString();
142
141
  return isExternal(locationString)
143
142
  ? locationString
144
143
  : new URL(history.basePath + locationString, location.origin).toString();
@@ -157,7 +156,8 @@ export class GemLinkElement extends GemElement {
157
156
  this.addEventListener('pointerenter', this.#preload);
158
157
  }
159
158
 
160
- render() {
159
+ @template()
160
+ #content = () => {
161
161
  return html`
162
162
  <a
163
163
  part=${this.link}
@@ -168,12 +168,12 @@ export class GemLinkElement extends GemElement {
168
168
  <slot></slot>
169
169
  </a>
170
170
  `;
171
- }
171
+ };
172
172
 
173
173
  /**
174
174
  * 如果该元素是外部链接返回 URL,否则返回路径(不包含 basePath)
175
175
  */
176
- getLocationString() {
176
+ _getLocationString() {
177
177
  if (this.route) {
178
178
  const queryProp = this.#routeOptions?.query || '';
179
179
  const hashProp = this.#routeOptions?.hash || '';
@@ -208,6 +208,6 @@ export class GemActiveLinkElement extends GemLinkElement {
208
208
  #updateState = () => {
209
209
  const { path, query, hash } = history.getParams();
210
210
  const isMatchPattern = this.pattern && matchPath(this.pattern, path);
211
- this.active = !!isMatchPattern || path + query + hash === this.getLocationString();
211
+ this.active = !!isMatchPattern || path + query + hash === this._getLocationString();
212
212
  };
213
213
  }
@@ -1,5 +1,5 @@
1
1
  import { GemElement } from '../../lib/element';
2
- import { connect, createStore, updateStore } from '../../lib/store';
2
+ import { connect, createStore } from '../../lib/store';
3
3
  import { history } from '../../lib/history';
4
4
  import { aria, mounted, shadow } from '../../lib/decorators';
5
5
 
@@ -46,7 +46,7 @@ export function createModalClass<T extends Record<string, unknown>>(options: T)
46
46
  this.inertStore = ([...document.body.children] as HTMLElement[]).filter((e) => !e.inert);
47
47
  this.inertStore.forEach((e) => (e.inert = true));
48
48
  document.body.append(instance);
49
- const changeStore = () => updateStore(this.store, { [open]: true, ...opts });
49
+ const changeStore = () => this.store({ [open]: true, ...opts });
50
50
  setTimeout(() => {
51
51
  changeStore();
52
52
  history.push({
@@ -75,7 +75,7 @@ export function createModalClass<T extends Record<string, unknown>>(options: T)
75
75
  static closeHandle() {
76
76
  this.inertStore.forEach((e) => (e.inert = false));
77
77
  this.instance?.remove();
78
- updateStore(this.store, { [open]: false, ...options });
78
+ this.store({ [open]: false, ...options });
79
79
  return final;
80
80
  }
81
81
 
@@ -3,7 +3,7 @@ import { createState, GemElement, html } from '../../lib/element';
3
3
  import type { Emitter } from '../../lib/decorators';
4
4
  import { property, emitter, boolattribute, shadow, effect, template, light } from '../../lib/decorators';
5
5
  import type { Store } from '../../lib/store';
6
- import { createStore, updateStore, connect } from '../../lib/store';
6
+ import { createStore, connect } from '../../lib/store';
7
7
  import type { UpdateHistoryParams } from '../../lib/history';
8
8
  import { titleStore, history } from '../../lib/history';
9
9
  import type { QueryString } from '../../lib/utils';
@@ -205,7 +205,7 @@ export class GemLightRouteElement extends GemElement {
205
205
  #updateLocationStore = () => {
206
206
  if (!this.locationStore) return;
207
207
  const { path, query, hash, data } = history.getParams();
208
- updateStore(this.locationStore, {
208
+ this.locationStore({
209
209
  path,
210
210
  params: this.currentParams,
211
211
  query,
@@ -256,7 +256,7 @@ export class GemLightRouteElement extends GemElement {
256
256
 
257
257
  // history or i18n
258
258
  @effect((i) => [i.scrollContainer])
259
- #connectTriggerStore = () => connect(this.trigger.store, () => this.#state({}));
259
+ #connectTriggerStore = () => this.trigger.store && connect(this.trigger.store, () => this.#state());
260
260
 
261
261
  #updateContentDep = () => {
262
262
  const { path, query, hash } = this.trigger.getParams();
@@ -310,7 +310,7 @@ export class GemLightRouteElement extends GemElement {
310
310
  if (this.trigger === history) {
311
311
  // 嵌套路由执行顺序也是从父到子
312
312
  if (location.href !== titleStore.url || route?.title) {
313
- updateStore(titleStore, { url: location.href, title: route?.title });
313
+ titleStore({ url: location.href, title: route?.title });
314
314
  }
315
315
  }
316
316
  const contentOrLoader = content || getContent?.(params, this.shadowRoot || this);
@@ -13,8 +13,8 @@
13
13
  */
14
14
 
15
15
  import { GemElement, html } from '../../lib/element';
16
- import { attribute, boolattribute, effect, mounted, shadow } from '../../lib/decorators';
17
- import { updateStore, connect } from '../../lib/store';
16
+ import { attribute, boolattribute, effect, mounted, shadow, template } from '../../lib/decorators';
17
+ import { connect } from '../../lib/store';
18
18
  import { titleStore } from '../../lib/history';
19
19
 
20
20
  // 避免重定向时的中间状态标题
@@ -50,7 +50,7 @@ export class GemTitleElement extends GemElement {
50
50
  static title = document.title;
51
51
 
52
52
  static setTitle(title: string) {
53
- updateStore(titleStore, { title });
53
+ titleStore({ title });
54
54
  }
55
55
 
56
56
  @effect((i) => [i.inert])
@@ -63,7 +63,8 @@ export class GemTitleElement extends GemElement {
63
63
  this.#ob.observe(this, { characterData: true, childList: true, subtree: true });
64
64
  };
65
65
 
66
- render = () => {
66
+ @template()
67
+ #content = () => {
67
68
  // 多个 <gem-title> 时,最终 document.title 按渲染顺序决定
68
69
  setDocumentTitle(this.textContent?.trim(), this.prefix, this.suffix);
69
70
  return html`${GemTitleElement.title}`;
@@ -1,5 +1,5 @@
1
1
  import { GemElement } from '../../lib/element';
2
- import { attribute, shadow } from '../../lib/decorators';
2
+ import { attribute, shadow, template } from '../../lib/decorators';
3
3
  import { raw } from '../../lib/utils';
4
4
 
5
5
  /**
@@ -12,7 +12,8 @@ export class GemUnsafeElement extends GemElement {
12
12
  @attribute content: string;
13
13
  @attribute contentcss: string;
14
14
 
15
- render() {
15
+ @template()
16
+ #content = () => {
16
17
  this.shadowRoot!.innerHTML = raw`
17
18
  <style>
18
19
  :host(:where(:not([hidden]))) {
@@ -22,6 +23,8 @@ export class GemUnsafeElement extends GemElement {
22
23
  </style>
23
24
  ${this.content}
24
25
  `;
26
+
27
+ // 不要更新内容
25
28
  return undefined;
26
- }
29
+ };
27
30
  }
@@ -1,5 +1,5 @@
1
1
  import { createCSSSheet, GemElement, html } from '../../lib/element';
2
- import { adoptedStyle, attribute, property, shadow, state } from '../../lib/decorators';
2
+ import { adoptedStyle, attribute, property, shadow, state, template } from '../../lib/decorators';
3
3
  import { css } from '../../lib/utils';
4
4
 
5
5
  const styles = createCSSSheet(css`
@@ -50,10 +50,11 @@ export class GemUseElement extends GemElement {
50
50
  }
51
51
  };
52
52
 
53
- render() {
53
+ @template()
54
+ #content = () => {
54
55
  return html`
55
56
  ${this.#getContent()}
56
57
  <slot></slot>
57
58
  `;
58
- }
59
+ };
59
60
  }
@@ -113,6 +113,7 @@ export class I18n<T = Record<string, Msg>> implements RouteTrigger {
113
113
  this.onChange?.(lang);
114
114
  }
115
115
 
116
+ #inConstructor = true;
116
117
  constructor(options: I18nOptions<T>) {
117
118
  if (!options.resources[options.fallbackLanguage]) {
118
119
  throw new GemError('i18n: fallbackLanguage invalid');
@@ -139,6 +140,7 @@ export class I18n<T = Record<string, Msg>> implements RouteTrigger {
139
140
  } else {
140
141
  this.resetLanguage();
141
142
  }
143
+ this.#inConstructor = false;
142
144
  }
143
145
 
144
146
  get(s: keyof T): string;
@@ -207,14 +209,15 @@ export class I18n<T = Record<string, Msg>> implements RouteTrigger {
207
209
  }
208
210
  this.resources[lang] = pack;
209
211
 
210
- // update all element
211
- const temp: Element[] = [document.documentElement];
212
- while (!!temp.length) {
213
- const element = temp.pop()!;
214
- (element as any)[UpdateToken]?.();
215
- if (element.shadowRoot?.firstElementChild) temp.push(element.shadowRoot.firstElementChild);
216
- if (element.firstElementChild) temp.push(element.firstElementChild);
217
- if (element.nextElementSibling) temp.push(element.nextElementSibling);
212
+ if (!this.#inConstructor) {
213
+ // 更新所有元素,可能会更新许多不必要的元素
214
+ const temp: Element[] = [document.documentElement];
215
+ while (!!temp.length) {
216
+ const element = temp.pop()!;
217
+ (element as any)[UpdateToken]?.();
218
+ // 和 gem 更新机制一样使用深度优先
219
+ temp.push(...[...element.children, ...(element.shadowRoot?.children || [])].reverse());
220
+ }
218
221
  }
219
222
 
220
223
  if (lang !== this.currentLanguage) {
@@ -1,14 +1,15 @@
1
1
  import type { Store } from '../lib/store';
2
- import { connect, useStore } from '../lib/store';
3
- import { camelToKebabCase, randomStr } from '../lib/utils';
2
+ import { connect, createStore } from '../lib/store';
3
+ import { camelToKebabCase, createUpdater, randomStr } from '../lib/utils';
4
4
  import type { GemElement } from '../lib/element';
5
5
  import { SheetToken, createCSSSheet } from '../lib/element';
6
6
 
7
- export type Theme<T> = ReturnType<typeof createCSSSheet> & {
8
- [K in keyof T as K extends `${string}Color`
9
- ? `${string & K}${'' | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900}`
10
- : K]: T[K];
11
- };
7
+ export type Theme<T> = ReturnType<typeof createUpdater<T>> &
8
+ ReturnType<typeof createCSSSheet> & {
9
+ [K in keyof T as K extends `${string}Color`
10
+ ? `${string & K}${'' | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900}`
11
+ : K]: T[K];
12
+ };
12
13
 
13
14
  const themeStoreMap = new WeakMap();
14
15
  const themePropsMap = new WeakMap();
@@ -23,12 +24,12 @@ export function getThemeProps<T>(theme: Theme<T>) {
23
24
  return themePropsMap.get(theme) as Theme<T>;
24
25
  }
25
26
 
26
- function useThemeFromProps<T extends Record<string, unknown>>(themeObj: T, props: Record<string, string> = {}) {
27
+ function createThemeFromProps<T extends Record<string, unknown>>(themeObj: T, props: Record<string, string> = {}) {
27
28
  const salt = randomStr();
28
29
  const styleSheet = createCSSSheet({})[SheetToken];
29
- const [store, updateStore] = useStore<T>(themeObj);
30
+ const store = createStore<T>(themeObj);
30
31
  const theme: any = new Proxy(
31
- { [SheetToken]: styleSheet },
32
+ createUpdater({ [SheetToken]: styleSheet }, (payload: Partial<T>) => store(payload)),
32
33
  {
33
34
  get(target: any, key: string) {
34
35
  if (!target[key]) {
@@ -58,41 +59,41 @@ function useThemeFromProps<T extends Record<string, unknown>>(themeObj: T, props
58
59
  updateContent();
59
60
  styleSheet.updateStyle();
60
61
  });
61
- return [theme as Theme<T>, updateStore] as const;
62
+ return theme as Theme<T>;
62
63
  }
63
64
 
64
65
  /**
65
66
  * 用于 `@adoptedStyle(theme)`,类似 `createCSSSheet`
66
67
  */
67
- export function useScopedTheme<T extends Record<string, unknown>>(themeObj: T) {
68
- return useThemeFromProps(themeObj);
68
+ export function createScopedTheme<T extends Record<string, unknown>>(themeObj: T) {
69
+ return createThemeFromProps(themeObj);
69
70
  }
70
71
 
71
- /**全局主题 */
72
- export function useTheme<T extends Record<string, unknown>>(themeObj: T) {
73
- const result = useScopedTheme(themeObj);
74
- document.adoptedStyleSheets.push(result[0][SheetToken].getStyle());
72
+ /** 全局主题 */
73
+ export function createTheme<T extends Record<string, unknown>>(themeObj: T) {
74
+ const result = createScopedTheme(themeObj);
75
+ document.adoptedStyleSheets.push(result[SheetToken].getStyle());
75
76
  return result;
76
77
  }
77
78
 
78
- /**用来覆盖全局主题 */
79
- export function useOverrideTheme<T extends Record<string, unknown>>(theme: Theme<T>, themeObj: Partial<T>) {
80
- return useThemeFromProps(themeObj, getThemeProps(theme));
79
+ /** 用来覆盖全局主题 */
80
+ export function createOverrideTheme<T extends Record<string, unknown>>(theme: Theme<T>, themeObj: Partial<T>) {
81
+ return createThemeFromProps(themeObj, getThemeProps(theme));
81
82
  }
82
83
 
83
- export function useDecoratorTheme<T extends Record<string, unknown>>(themeObj: T) {
84
- const [themeAsKeys] = useThemeFromProps(themeObj);
84
+ export function createDecoratorTheme<T extends Record<string, unknown>>(themeObj: T) {
85
+ const themeAsKeys = createThemeFromProps<T>(themeObj);
85
86
  const props = getThemeProps(themeAsKeys);
86
- const updateDecorator =
87
+ const update =
87
88
  <E extends GemElement, V extends () => T, K = any[] | undefined>(
88
89
  getDep?: K extends readonly any[] ? (instance: E) => K : undefined,
89
90
  ) =>
90
91
  (_: any, { addInitializer, access }: ClassFieldDecoratorContext<E, V> | ClassMethodDecoratorContext<E, V>) => {
91
92
  addInitializer(function (this: E) {
92
- const [theme, update] = useThemeFromProps({ ...themeObj }, props);
93
+ const theme = createThemeFromProps({ ...themeObj }, props);
93
94
  this.internals.sheets.push(theme[SheetToken].getStyle(this));
94
- this.memo(() => update(access.get(this).apply(this)), getDep && (() => getDep(this) as any));
95
+ this.memo(() => theme(access.get(this).apply(this)), getDep && (() => getDep(this) as any));
95
96
  });
96
97
  };
97
- return [themeAsKeys, updateDecorator] as const;
98
+ return createUpdater(themeAsKeys, update) as ReturnType<typeof createUpdater<typeof themeAsKeys, typeof update>>;
98
99
  }
@@ -1,5 +1,5 @@
1
1
  import type { createCSSSheet, Metadata, TemplateResult } from './element';
2
- import { GemElement, UpdateToken, createTemplate, RenderErrorEvent, render } 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';
@@ -273,7 +273,7 @@ export function template<T extends GemElement, V extends () => TemplateResult |
273
273
  { addInitializer, access }: ClassFieldDecoratorContext<T, V> | ClassMethodDecoratorContext<T, V>,
274
274
  ) {
275
275
  addInitializer(function (this: T) {
276
- createTemplate(this, {
276
+ _createTemplate(this, {
277
277
  render: access.get(this).bind(this),
278
278
  condition: condition && (() => condition(this) as any),
279
279
  });
@@ -287,7 +287,7 @@ export function fallback<T extends GemElement, V extends (err: any) => TemplateR
287
287
  { addInitializer, access }: ClassFieldDecoratorContext<T, V> | ClassMethodDecoratorContext<T, V>,
288
288
  ) {
289
289
  addInitializer(function (this: T) {
290
- this.addEventListener(RenderErrorEvent, ({ detail }: CustomEvent) => {
290
+ this.addEventListener(_RenderErrorEvent, ({ detail }: CustomEvent) => {
291
291
  render(access.get(this).apply(this, [detail]), this.internals.shadowRoot || this);
292
292
  });
293
293
  });
@@ -456,7 +456,7 @@ export function adoptedStyle(sheet: ReturnType<typeof createCSSSheet>) {
456
456
  * class App extends GemElement {}
457
457
  * ```
458
458
  */
459
- export function connectStore(store: Store<unknown>) {
459
+ export function connectStore(store: Store<any>) {
460
460
  return function (_: unknown, context: ClassDecoratorContext) {
461
461
  pushStaticField(context, 'observedStores', store);
462
462
  };
@@ -5,7 +5,7 @@ import type { GemReflectElement } from '../elements/reflect';
5
5
 
6
6
  import type { Store } from './store';
7
7
  import { connect } from './store';
8
- import { LinkedList, addMicrotask, isArrayChange, addListener, randomStr } from './utils';
8
+ import { LinkedList, addMicrotask, isArrayChange, addListener, randomStr, createUpdater } from './utils';
9
9
 
10
10
  export { html, svg, render, directive, TemplateResult, SVGTemplateResult } from 'lit-html';
11
11
 
@@ -29,19 +29,19 @@ declare global {
29
29
  }
30
30
  }
31
31
 
32
- const { assign, defineProperty, setPrototypeOf } = Object;
32
+ const { assign, defineProperty } = Object;
33
33
 
34
34
  defineProperty(Symbol, 'metadata', { value: Symbol.for('Symbol.metadata') });
35
35
 
36
36
  const execCallback = (fun: any) => typeof fun === 'function' && fun();
37
37
 
38
- // 跨多个 gem 工作
38
+ // 读取构造样式表,跨多个 gem 工作
39
39
  export const SheetToken = Symbol.for('gem@sheetToken');
40
- // proto prop
40
+ // 更新元素,跨多个 gem 工作
41
41
  export const UpdateToken = Symbol.for('gem@update');
42
42
 
43
43
  const BoundaryCSSState = 'gem-style-boundary';
44
- export const RenderErrorEvent = 'gem@render-error';
44
+ export const _RenderErrorEvent = 'gem@render-error';
45
45
 
46
46
  // fix modal-factory type error
47
47
  const updateTokenAlias = UpdateToken;
@@ -177,8 +177,9 @@ export function createCSSSheet<T extends Record<string, string>>(
177
177
  Object.keys(rules).forEach((key) => {
178
178
  const isScope = key === '$';
179
179
  // 对于已经有 `-` 的保留原始 key,支持覆盖修改
180
+ // :scope 下可以写嵌套样式 &:xxx,:host() 下不行(子内容可以)
180
181
  sheet[key] = isScope || key.includes('-') ? key : `${key}-${randomStr()}`;
181
- style += `${isScope ? ':scope,:host' : `.${sheet[key]}`} {${rules[key]}}`;
182
+ style += `${isScope ? ':where(:scope:not([hidden])),:host(:where(:not([hidden])))' : `.${sheet[key]}`} {${rules[key]}}`;
182
183
  });
183
184
  }
184
185
  styleSheet.setContent(style);
@@ -221,7 +222,7 @@ const appleCSSStyleSheet = (ele: HTMLElement, sheets: CSSStyleSheet[]) => {
221
222
  };
222
223
 
223
224
  /**必须使用在字段中,否则会读取到错误的实例 */
224
- export let createState: <T>(initState: T) => T & ((state: Partial<T>) => void);
225
+ export let createState: <T>(initState: T) => ReturnType<typeof createUpdater<T>>;
225
226
 
226
227
  type GetDepFun<T> = () => T;
227
228
  type EffectCallback<T> = (depValues: T, oldDepValues?: T) => any;
@@ -260,7 +261,7 @@ function clearEffect(list: EffectItem<any>[]) {
260
261
  type Render = () => TemplateResult | null | undefined;
261
262
  type RenderItem = { render: Render; condition?: () => boolean };
262
263
 
263
- export let createTemplate: (ele: GemElement, item: RenderItem) => void;
264
+ export let _createTemplate: (ele: GemElement, item: RenderItem) => void;
264
265
 
265
266
  export type Metadata = Partial<ShadowRootInit> & {
266
267
  /** 内容可被外部样式化 */
@@ -273,7 +274,7 @@ export type Metadata = Partial<ShadowRootInit> & {
273
274
  }
274
275
  >;
275
276
  // 实例化时使用到,DevTools 需要读取
276
- observedStores: Store<unknown>[];
277
+ observedStores: Store<any>[];
277
278
  adoptedStyleSheets?: Sheet<unknown>[];
278
279
  // 以下静态字段仅供外部读取,没有实际作用
279
280
  observedProperties?: string[];
@@ -331,20 +332,16 @@ export abstract class GemElement extends HTMLElement {
331
332
  static {
332
333
  createState = <T>(initState: T) => {
333
334
  const ele = currentConstructGemElement;
334
- const state: any = (payload: Partial<T>) => {
335
+ const state = createUpdater(initState, (payload) => {
335
336
  assign(state, payload);
336
337
  // 避免无限刷新
337
338
  if (!ele.#rendering) addMicrotask(ele.#update);
338
- };
339
- setPrototypeOf(state, null);
340
- delete state.name;
341
- delete state.length;
339
+ });
342
340
  ele.#internals.stateList.push(state);
343
- assign(state, initState);
344
- return state as T & ((this: GemElement, state: Partial<T>) => void);
341
+ return state;
345
342
  };
346
343
  createRef = () => new RefObject(currentConstructGemElement);
347
- createTemplate = (ele, item) => ele.#renderList.push(item);
344
+ _createTemplate = (ele, item) => ele.#renderList.push(item);
348
345
  }
349
346
 
350
347
  constructor() {
@@ -388,7 +385,8 @@ export abstract class GemElement extends HTMLElement {
388
385
  }
389
386
 
390
387
  get #renderItem() {
391
- return this.#renderList.find((e) => !e.condition || e.condition());
388
+ // 从末尾开始寻找,允许被覆盖
389
+ return this.#renderList.findLast((e) => !e.condition || e.condition());
392
390
  }
393
391
 
394
392
  get internals() {
@@ -422,7 +420,7 @@ export abstract class GemElement extends HTMLElement {
422
420
  if (temp === undefined) return;
423
421
  render(temp, this.#renderRoot);
424
422
  } catch (err) {
425
- this.dispatchEvent(new CustomEvent(RenderErrorEvent, { bubbles: true, composed: true, detail: err }));
423
+ this.dispatchEvent(new CustomEvent(_RenderErrorEvent, { bubbles: true, composed: true, detail: err }));
426
424
  }
427
425
  };
428
426