@mantou/gem 1.7.10 → 1.7.12

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 (67) hide show
  1. package/dist/gem.es.js +1839 -2597
  2. package/dist/gem.umd.js +23 -24
  3. package/elements/base/gesture.d.ts +1 -3
  4. package/elements/base/gesture.d.ts.map +1 -1
  5. package/elements/base/gesture.js +2 -3
  6. package/elements/base/gesture.js.map +1 -1
  7. package/elements/base/link.d.ts +2 -0
  8. package/elements/base/link.d.ts.map +1 -1
  9. package/elements/base/link.js +2 -0
  10. package/elements/base/link.js.map +1 -1
  11. package/elements/base/modal-factory.d.ts +23 -21
  12. package/elements/base/modal-factory.d.ts.map +1 -1
  13. package/elements/base/reflect.d.ts +5 -0
  14. package/elements/base/reflect.d.ts.map +1 -1
  15. package/elements/base/reflect.js +26 -7
  16. package/elements/base/reflect.js.map +1 -1
  17. package/elements/base/route.d.ts +9 -7
  18. package/elements/base/route.d.ts.map +1 -1
  19. package/elements/base/route.js +23 -7
  20. package/elements/base/route.js.map +1 -1
  21. package/elements/base/title.d.ts +1 -1
  22. package/elements/base/title.js +1 -1
  23. package/elements/base/unsafe.d.ts +1 -2
  24. package/elements/base/unsafe.d.ts.map +1 -1
  25. package/elements/base/unsafe.js +1 -2
  26. package/elements/base/unsafe.js.map +1 -1
  27. package/elements/base/use.d.ts +1 -1
  28. package/elements/base/use.js +1 -1
  29. package/helper/i18n.d.ts +1 -1
  30. package/helper/i18n.d.ts.map +1 -1
  31. package/helper/i18n.js +2 -3
  32. package/helper/i18n.js.map +1 -1
  33. package/helper/request.d.ts +17 -7
  34. package/helper/request.d.ts.map +1 -1
  35. package/helper/request.js +103 -53
  36. package/helper/request.js.map +1 -1
  37. package/lib/decorators.d.ts +1 -0
  38. package/lib/decorators.d.ts.map +1 -1
  39. package/lib/element.d.ts.map +1 -1
  40. package/lib/element.js +16 -18
  41. package/lib/element.js.map +1 -1
  42. package/lib/store.d.ts +1 -1
  43. package/lib/store.d.ts.map +1 -1
  44. package/lib/store.js +3 -1
  45. package/lib/store.js.map +1 -1
  46. package/lib/utils.d.ts +7 -7
  47. package/lib/utils.d.ts.map +1 -1
  48. package/lib/utils.js +19 -30
  49. package/lib/utils.js.map +1 -1
  50. package/lib/version.d.ts +1 -1
  51. package/lib/version.js +1 -1
  52. package/package.json +6 -5
  53. package/src/elements/base/gesture.ts +3 -3
  54. package/src/elements/base/link.ts +2 -0
  55. package/src/elements/base/reflect.ts +18 -6
  56. package/src/elements/base/route.ts +32 -11
  57. package/src/elements/base/title.ts +1 -1
  58. package/src/elements/base/unsafe.ts +1 -2
  59. package/src/elements/base/use.ts +1 -1
  60. package/src/helper/i18n.ts +2 -4
  61. package/src/helper/request.ts +122 -53
  62. package/src/lib/decorators.ts +1 -1
  63. package/src/lib/element.ts +18 -17
  64. package/src/lib/store.ts +3 -1
  65. package/src/lib/utils.ts +30 -37
  66. package/src/lib/version.ts +1 -1
  67. package/src/test/utils.test.ts +12 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mantou/gem",
3
- "version": "1.7.10",
3
+ "version": "1.7.12",
4
4
  "description": "💎 使用自定义元素的轻量级 WebApp 开发框架",
5
5
  "keywords": [
6
6
  "frontend",
@@ -30,7 +30,8 @@
30
30
  ],
31
31
  "typings": "index.d.ts",
32
32
  "scripts": {
33
- "docs": "gem-book docs",
33
+ "docs": "GEM_BOOK_REPLACE=true gem-book docs",
34
+ "docs:remote": "yarn docs",
34
35
  "build:docs": "yarn docs --build",
35
36
  "build:module": "tsc -p tsconfig.build.json",
36
37
  "build:lib": "vite build",
@@ -43,13 +44,13 @@
43
44
  },
44
45
  "devDependencies": {
45
46
  "@esm-bundle/chai": "^4.3.4-fix.0",
46
- "@gemjs/config": "^1.6.12",
47
+ "@gemjs/config": "^1.6.14",
47
48
  "@open-wc/testing": "^2.5.33",
48
49
  "@web/dev-server-esbuild": "^0.2.16",
49
50
  "@web/test-runner": "^0.13.22",
50
51
  "rimraf": "^3.0.2",
51
- "typescript": "^5.3.2",
52
- "vite": "^2.7.13"
52
+ "typescript": "^5.4.3",
53
+ "vite": "^4.5.2"
53
54
  },
54
55
  "author": "mantou132",
55
56
  "license": "MIT",
@@ -46,11 +46,9 @@ function angleAB(
46
46
  /**
47
47
  * 块级元素,如果要设置成 `contents`,则内容块要设置 `touch-action: none`
48
48
  * https://javascript.info/pointer-events#event-pointercancel
49
- *
50
49
  * 在移动端上,必须设置 `touch-action` 以允许滚动等原生动作
51
50
  *
52
- * 为什么空白区域会自动触发 `pointercancel`?
53
- *
51
+ * @customElement gem-gesture
54
52
  * @event pan
55
53
  * @event pinch
56
54
  * @event rotate
@@ -76,7 +74,9 @@ export class GemGestureElement extends GemElement {
76
74
  this.addEventListener('pointermove', this.#onMoveSet);
77
75
  this.addEventListener('pointerup', this.#onEnd);
78
76
  this.addEventListener('pointerleave', this.#onEnd); // 有时候 up 没有触发?
77
+ // 为什么空白区域会自动触发 `pointercancel`?
79
78
  this.addEventListener('pointercancel', this.#onEnd);
79
+
80
80
  this.addEventListener('dragstart', (evt) => evt.preventDefault());
81
81
  }
82
82
 
@@ -10,6 +10,7 @@ function isExternal(path: string) {
10
10
  }
11
11
 
12
12
  /**
13
+ * @customElement gem-link
13
14
  * @attr href
14
15
  * @attr target
15
16
  * @attr doc-title
@@ -171,6 +172,7 @@ export class GemLinkElement extends GemElement {
171
172
  }
172
173
 
173
174
  /**
175
+ * @customElement gem-active-link
174
176
  * @attr pattern
175
177
  * @state active
176
178
  */
@@ -7,7 +7,7 @@
7
7
  * 比如 duoyun-ui 文档站加载示例元素时把 GemReflectElement 渲染的元素清除
8
8
  */
9
9
  import { GemElement } from '../../lib/element';
10
- import { property } from '../../lib/decorators';
10
+ import { attribute, property } from '../../lib/decorators';
11
11
 
12
12
  const START = 'gem-reflect-start';
13
13
  const END = 'gem-reflect-end';
@@ -25,9 +25,18 @@ const getReflectFragment = (startNode: Comment) => {
25
25
  return result;
26
26
  };
27
27
 
28
+ /**
29
+ * @customElement gem-reflect
30
+ */
28
31
  export class GemReflectElement extends GemElement {
32
+ @attribute method: 'prepend' | 'append';
33
+
29
34
  @property target: HTMLElement = document.head;
30
35
 
36
+ get #method() {
37
+ return this.method || 'append';
38
+ }
39
+
31
40
  constructor() {
32
41
  super({ isLight: true });
33
42
 
@@ -45,11 +54,14 @@ export class GemReflectElement extends GemElement {
45
54
 
46
55
  this.effect(
47
56
  () => {
48
- if (!childNodes) {
49
- [startNode, ...this.childNodes, endNode].forEach((node) => this.target.append(node));
50
- } else {
51
- childNodes.forEach((node) => this.target.append(node));
52
- }
57
+ (childNodes || [startNode, ...this.childNodes, endNode]).forEach((node, index, arr) => {
58
+ const prev = arr[index - 1];
59
+ if (prev) {
60
+ prev.after(node);
61
+ } else {
62
+ this.target[this.#method](node);
63
+ }
64
+ });
53
65
  return () => {
54
66
  childNodes = getReflectFragment(startNode);
55
67
  childNodes.forEach((node) => node.remove());
@@ -72,13 +72,17 @@ export interface RouteItem<T = unknown> {
72
72
  pattern: string;
73
73
  redirect?: string;
74
74
  content?: TemplateResult;
75
- getContent?: (params: Params) => TemplateResult | Promise<TemplateResult>;
75
+ // 返回 `undefined` 意味着不会执行 `render`, 支持 React 等组件挂载到 `<gem-route>` 元素下
76
+ getContent?: (
77
+ params: Params,
78
+ renderRoot: DocumentFragment | GemLightRouteElement,
79
+ ) => TemplateResult | Promise<TemplateResult> | undefined | Promise<undefined>;
76
80
  title?: string;
77
81
  // 用来传递数据
78
82
  data?: T;
79
83
  }
80
84
 
81
- export type RoutesObject = Record<string, RouteItem>;
85
+ export type RoutesObject<T = unknown> = Record<string, RouteItem<T>>;
82
86
 
83
87
  // params 中的成员不会验证
84
88
  export interface RouteOptions extends Omit<UpdateHistoryParams, 'path'> {
@@ -88,7 +92,10 @@ export interface RouteOptions extends Omit<UpdateHistoryParams, 'path'> {
88
92
  // 从路由创建路径
89
93
  // 不包含 basePath
90
94
  export function createPath(route: RouteItem, options?: RouteOptions) {
91
- let path = route.pattern;
95
+ // /a/* /a/:0
96
+ // /a/*/:b/* /a/:0/:b/:1
97
+ let i = 0;
98
+ let path = route.pattern.replaceAll('*', () => `:${i++}`);
92
99
  const params = options?.params;
93
100
  if (params) {
94
101
  Object.keys(params).forEach((key) => {
@@ -111,19 +118,22 @@ interface ConstructorOptions {
111
118
  routes?: RouteItem[] | RoutesObject;
112
119
  }
113
120
 
121
+ const INIT_CONTENT = html``;
122
+
114
123
  type State = {
115
124
  content?: TemplateResult;
116
125
  };
117
126
 
118
127
  export type RouteTrigger = {
119
128
  store: Store<any>;
120
- replace: (arg: { path: string }) => void;
129
+ replace: (arg: UpdateHistoryParams) => void;
121
130
  getParams: () => { path: string; query?: string | QueryString; hash?: string };
122
131
  };
123
132
 
124
133
  const scrollPositionMap = new Map<string, number>();
125
134
 
126
135
  /**
136
+ * @customElement gem-route
127
137
  * @attr inert 暂停路由更新
128
138
  * @fires routechange
129
139
  * @fires error
@@ -158,7 +168,7 @@ export class GemRouteElement extends GemElement<State> {
158
168
  /**当前匹配的路由的 params */
159
169
  currentParams: Params = {};
160
170
 
161
- #lastLoader?: Promise<TemplateResult>;
171
+ #lastLoader?: Promise<TemplateResult | undefined>;
162
172
 
163
173
  /**不要多个 `<gem-route>` 共享,因为那样会导致后面的元素卸载前触发更新 */
164
174
  static createLocationStore = () => {
@@ -193,11 +203,11 @@ export class GemRouteElement extends GemElement<State> {
193
203
 
194
204
  constructor({ isLight, routes }: ConstructorOptions = {}) {
195
205
  super({ isLight });
196
- this.routes = routes;
206
+ this.routes = this.routes || routes;
197
207
  }
198
208
 
199
209
  state: State = {
200
- content: undefined,
210
+ content: INIT_CONTENT,
201
211
  };
202
212
 
203
213
  #updateLocationStore = () => {
@@ -282,7 +292,12 @@ export class GemRouteElement extends GemElement<State> {
282
292
 
283
293
  render() {
284
294
  const { content } = this.state;
285
- if (!this.shadowRoot) return html`${content}`;
295
+ if (content === undefined) return;
296
+
297
+ if (!this.shadowRoot) {
298
+ if (content === INIT_CONTENT) return;
299
+ return html`${content}`;
300
+ }
286
301
 
287
302
  return html`
288
303
  <style>
@@ -291,23 +306,26 @@ export class GemRouteElement extends GemElement<State> {
291
306
  display: contents;
292
307
  }
293
308
  </style>
294
- ${content ?? html`<slot></slot>`}
309
+ ${content === INIT_CONTENT ? html`<slot></slot>` : content}
295
310
  `;
296
311
  }
297
312
 
298
313
  update = () => {
299
- const { route, params = {} } = GemRouteElement.findRoute(this.routes, this.trigger.getParams().path);
314
+ const { path, hash, query } = this.trigger.getParams();
315
+ const { route, params = {} } = GemRouteElement.findRoute(this.routes, path);
300
316
  const { redirect, content, getContent } = route || {};
301
317
  if (redirect) {
302
318
  this.trigger.replace({
303
319
  path: createPath({ pattern: redirect }, { params }),
320
+ query,
321
+ hash,
304
322
  });
305
323
  return;
306
324
  }
307
325
  if (this.trigger === history) {
308
326
  updateStore(titleStore, { title: route?.title });
309
327
  }
310
- const contentOrLoader = content || getContent?.(params);
328
+ const contentOrLoader = content || getContent?.(params, this.shadowRoot || this);
311
329
  this.loading(route);
312
330
  if (contentOrLoader instanceof Promise) {
313
331
  this.#lastLoader = contentOrLoader;
@@ -325,6 +343,9 @@ export class GemRouteElement extends GemElement<State> {
325
343
  };
326
344
  }
327
345
 
346
+ /**
347
+ * @customElement gem-light-route
348
+ */
328
349
  export class GemLightRouteElement extends GemRouteElement {
329
350
  constructor(options: ConstructorOptions = {}) {
330
351
  super({ ...options, isLight: true });
@@ -43,7 +43,7 @@ export const PREFIX = `${defaultTitle} | `;
43
43
  export const SUFFIX = ` - ${defaultTitle}`;
44
44
 
45
45
  /**
46
- * 映射到 `document.title`
46
+ * @customElement gem-title
47
47
  * @attr prefix
48
48
  * @attr suffix
49
49
  */
@@ -2,8 +2,7 @@ import { GemElement } from '../../lib/element';
2
2
  import { attribute } from '../../lib/decorators';
3
3
 
4
4
  /**
5
- * 直接写会被解释成固定属性,需要使用变量赋值
6
- *
5
+ * @customElement gem-unsafe
7
6
  * @attr content
8
7
  * @attr contentcss
9
8
  */
@@ -6,9 +6,9 @@ const eleCache = new Map<string, HTMLTemplateElement>();
6
6
  * svg 中的 `<use>` 不能穿透 ShadowDOM,
7
7
  * 此元素用来模拟 `<use>`,
8
8
  * 由于是复制元素,所以不能像 `<use>` 一样自动更新
9
- *
10
9
  * slot 支持为 icon 添加效果,如涟漪,徽章
11
10
  *
11
+ * @customElement gem-use
12
12
  * @attr selector
13
13
  */
14
14
  export class GemUseElement extends GemElement {
@@ -74,10 +74,8 @@ export class I18n<T = Record<string, Msg>> implements RouteTrigger {
74
74
 
75
75
  store: Store<any>;
76
76
 
77
- replace = ({ path }: { path: string }) => {
78
- this.setLanguage(path);
79
- };
80
-
77
+ // 兼容 `<gem-route>`
78
+ replace = ({ path }: { path: string }) => this.setLanguage(path);
81
79
  getParams = () => ({ path: this.currentLanguage });
82
80
 
83
81
  get #cacheCurrentKey() {
@@ -1,60 +1,129 @@
1
- export const defaultReqInit: RequestInit = {
2
- headers: {
3
- 'Content-Type': 'application/json',
4
- },
5
- };
6
-
7
- export async function request<T>(uri: string, options: RequestInit = {}): Promise<T> {
8
- const res = await fetch(uri, {
9
- ...defaultReqInit,
10
- ...options,
11
- });
12
- if (res.status === 0) throw new Error('Request fail');
13
- if (res.status >= 500) throw new Error(res.statusText);
14
- try {
15
- const data = await res.json();
16
- if (res.status >= 400) throw new (Error as any)(data?.message || res.statusText, { cause: data });
17
- return data;
18
- } catch {
19
- throw new Error('Server error');
20
- }
21
- }
1
+ import { GemError } from '../lib/utils';
22
2
 
23
3
  type ReqBody = Record<string, unknown> | BodyInit;
4
+ type ReqParams = Record<string, string | number>;
24
5
  const TypedArray = Object.getPrototypeOf(Object.getPrototypeOf(new Int8Array())).constructor;
25
6
 
26
- function serializationBody(body?: ReqBody): BodyInit | undefined {
27
- if (
28
- body instanceof FormData ||
29
- body instanceof Blob ||
30
- body instanceof TypedArray ||
31
- body instanceof URLSearchParams ||
32
- body instanceof ReadableStream ||
33
- body instanceof ArrayBuffer
34
- ) {
35
- return body as BodyInit;
36
- }
37
- if (typeof body === 'object') {
38
- return JSON.stringify(body);
39
- }
40
- return body;
41
- }
7
+ type Options = {
8
+ origin?: string;
9
+ base?: string;
10
+ // only for get/del/put/post
11
+ appendHeaders?: (input?: any) => HeadersInit;
12
+ transformInput?: (input?: any) => any;
13
+ transformOutput?: (output: any, req: Request) => any;
14
+ } & RequestInit;
42
15
 
43
- function serializationUrl(url: string, params?: Record<string, string | number>) {
44
- const { origin, pathname, search } = new URL(url, location.origin);
45
- const query = new URLSearchParams(params as Record<string, string>).toString();
46
- return `${origin}${pathname}${search ? `${search}&` : query ? '?' : ''}${query}`;
47
- }
16
+ function initRequest(options: Options = {}) {
17
+ const {
18
+ origin = location.origin,
19
+ base = '',
20
+ appendHeaders = () => ({}),
21
+ transformInput = (p?: any) => p,
22
+ transformOutput = (p?: any) => p,
23
+ ...reqInit
24
+ } = options;
48
25
 
49
- export function get<T>(url: string, params?: Record<string, string | number>) {
50
- return request<T>(serializationUrl(url, params), { method: 'GET' });
51
- }
52
- export function del<T>(url: string, params?: Record<string, string | number>) {
53
- return request<T>(serializationUrl(url, params), { method: 'DELETE' });
54
- }
55
- export function put<T>(url: string, body?: ReqBody) {
56
- return request<T>(url, { method: 'PUT', body: serializationBody(body) });
57
- }
58
- export function post<T>(url: string, body?: ReqBody) {
59
- return request<T>(url, { method: 'POST', body: serializationBody(body) });
26
+ const serializationBody = (body?: ReqBody): BodyInit | undefined => {
27
+ if (
28
+ body instanceof FormData ||
29
+ body instanceof Blob ||
30
+ body instanceof TypedArray ||
31
+ body instanceof URLSearchParams ||
32
+ body instanceof ReadableStream ||
33
+ body instanceof ArrayBuffer
34
+ ) {
35
+ return body as BodyInit;
36
+ }
37
+ if (typeof body === 'object') {
38
+ return JSON.stringify(body);
39
+ }
40
+ return body;
41
+ };
42
+
43
+ const serializationUrl = (path: string, params?: ReqParams) => {
44
+ if (new URL(path, location.origin).pathname !== path) {
45
+ throw new GemError('`path` format is incorrect');
46
+ }
47
+ const query = new URLSearchParams(params as Record<string, string>).toString();
48
+ return `${origin}${base}${path}${query ? '?' : ''}${query}`;
49
+ };
50
+
51
+ const serializationHeaders = (headers?: HeadersInit) => {
52
+ if (!headers) return {};
53
+ // auto `toLowerCase`
54
+ if (headers instanceof Headers) return Object.fromEntries(headers);
55
+ const entries = Array.isArray(headers) ? headers : Object.entries(headers);
56
+ return Object.fromEntries(entries.map(([key, value]) => [key.toLowerCase(), value]));
57
+ };
58
+
59
+ const request = async <T>(uri: string, reqOptions: RequestInit = {}): Promise<T> => {
60
+ const req = new Request(uri, {
61
+ ...reqInit,
62
+ ...reqOptions,
63
+ headers: new Headers({
64
+ 'content-type': 'application/json',
65
+ ...serializationHeaders(reqInit.headers),
66
+ ...serializationHeaders(reqOptions.headers),
67
+ }),
68
+ });
69
+ const res = await fetch(req);
70
+
71
+ if (res.status === 0) throw new Error('Request fail');
72
+ if (res.status >= 500) throw new Error(res.statusText);
73
+
74
+ const contentType = req.headers.get('content-type')!;
75
+ const isClientError = res.status >= 400;
76
+ try {
77
+ let data: any;
78
+ if (contentType.includes('json')) {
79
+ data = await res.json();
80
+ } else if (contentType.includes('octet-stream')) {
81
+ data = await res.arrayBuffer();
82
+ } else {
83
+ data = await res.text();
84
+ }
85
+ if (isClientError) throw new (Error as any)(res.statusText, { cause: data });
86
+ return transformOutput(data, req);
87
+ } catch {
88
+ throw new Error('Parse error');
89
+ }
90
+ };
91
+
92
+ return {
93
+ request,
94
+ get<T>(path: string, params?: ReqParams) {
95
+ const i = transformInput(params);
96
+ return request<T>(serializationUrl(path, i), {
97
+ method: 'GET',
98
+ headers: appendHeaders(i),
99
+ });
100
+ },
101
+ del<T>(path: string, params?: ReqParams) {
102
+ const i = transformInput(params);
103
+ return request<T>(serializationUrl(path, i), {
104
+ method: 'DELETE',
105
+ headers: appendHeaders(i),
106
+ });
107
+ },
108
+ put<T>(path: string, body?: ReqBody) {
109
+ const i = transformInput(body);
110
+ return request<T>(serializationUrl(path), {
111
+ method: 'PUT',
112
+ headers: appendHeaders(i),
113
+ body: serializationBody(i),
114
+ });
115
+ },
116
+ post<T>(path: string, body?: ReqBody) {
117
+ const i = transformInput(body);
118
+ return request<T>(serializationUrl(path), {
119
+ method: 'POST',
120
+ headers: appendHeaders(i),
121
+ body: serializationBody(i),
122
+ });
123
+ },
124
+ };
60
125
  }
126
+
127
+ const { request, del, get, post, put } = initRequest();
128
+
129
+ export { initRequest, request, del, get, post, put };
@@ -19,7 +19,7 @@ function pushStaticField(target: GemElementPrototype, field: StaticField, member
19
19
  (cls[field] as any)[isSet ? 'add' : 'push'](member);
20
20
  }
21
21
 
22
- export type RefObject<T = HTMLElement> = { ref: string; element: T | undefined };
22
+ export type RefObject<T = HTMLElement> = { ref: string; element: T | undefined; elements: T[] };
23
23
 
24
24
  /**
25
25
  * 引用元素,只有第一个标记 ref 的元素有效
@@ -419,22 +419,17 @@ export abstract class GemElement<T = Record<string, unknown>> extends HTMLElemen
419
419
 
420
420
  closestElement<K extends keyof HTMLElementTagNameMap>(tag: K): HTMLElementTagNameMap[K] | null;
421
421
  closestElement<K extends abstract new (...args: any) => any>(constructor: K): InstanceType<K> | null;
422
- closestElement<K extends abstract new (...args: any) => any>(constructorOrTag: K | string): Element | null {
422
+ closestElement<K extends abstract new (...args: any) => any>(constructorOrTag: K | string) {
423
423
  const isConstructor = typeof constructorOrTag === 'function';
424
424
  const tagName = typeof constructorOrTag === 'string' && constructorOrTag.toUpperCase();
425
- const getRootElement = (ele: Element): Element | null => {
426
- const rootEle = ele.parentElement || (ele.getRootNode() as ShadowRoot).host;
427
- if (!rootEle) return null;
428
- if (isConstructor) {
429
- if (rootEle.constructor === constructorOrTag) {
430
- return rootEle;
431
- }
432
- } else if (rootEle.tagName === tagName) {
433
- return rootEle;
434
- }
435
- return getRootElement(rootEle);
436
- };
437
- return getRootElement(this);
425
+ const is = (ele: Element) => (isConstructor ? ele.constructor === constructorOrTag : ele.tagName === tagName);
426
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
427
+ let node: Element | null = this;
428
+ while (node) {
429
+ if (is(node)) break;
430
+ node = node.parentElement || (node.getRootNode() as ShadowRoot).host;
431
+ }
432
+ return node;
438
433
  }
439
434
 
440
435
  /**
@@ -572,7 +567,11 @@ export function defineProperty(
572
567
  });
573
568
  }
574
569
 
570
+ const getReflectTargets = (ele: ShadowRoot | GemElement) =>
571
+ [...ele.querySelectorAll<GemReflectElement>('[data-gem-reflect]')].map((e) => e.target);
572
+
575
573
  export function defineRef(target: GemElement, prop: string, ref: string) {
574
+ const refSelector = `[ref=${ref}]`;
576
575
  Object.defineProperty(target, prop, {
577
576
  configurable: true,
578
577
  get() {
@@ -586,12 +585,14 @@ export function defineRef(target: GemElement, prop: string, ref: string) {
586
585
  return ref;
587
586
  },
588
587
  get element() {
589
- const gemReflects = [...ele.querySelectorAll<GemReflectElement>('[data-gem-reflect]')].map((e) => e.target);
590
- for (const e of [ele, ...gemReflects]) {
591
- const result = e.querySelector(`[ref=${ref}]`);
588
+ for (const e of [ele, ...getReflectTargets(ele)]) {
589
+ const result = e.querySelector(refSelector);
592
590
  if (result) return result;
593
591
  }
594
592
  },
593
+ get elements() {
594
+ return [ele, ...getReflectTargets(ele)].map((e) => [...e.querySelectorAll(refSelector)]).flat();
595
+ },
595
596
  };
596
597
  proxy[prop] = refobject;
597
598
  }
package/src/lib/store.ts CHANGED
@@ -28,7 +28,9 @@ export function useStore<T extends NonPrimitive>(originStore: T) {
28
28
  export function connect<T extends NonPrimitive>(store: Store<T>, func: () => void) {
29
29
  const listeners = StoreListenerMap.get(store);
30
30
  listeners?.add(func);
31
- return () => listeners?.delete(func);
31
+ return () => {
32
+ listeners?.delete(func);
33
+ };
32
34
  }
33
35
 
34
36
  export function disconnect<T extends NonPrimitive>(store: Store<T>, func: () => void) {