@mantou/gem 1.5.4 → 1.5.8
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.
- package/elements/dialog-base.js +1 -1
- package/elements/dialog-base.js.map +1 -1
- package/elements/modal-base.d.ts +45 -34
- package/elements/modal-base.d.ts.map +1 -1
- package/elements/modal-base.js +1 -1
- package/elements/modal-base.js.map +1 -1
- package/elements/route.d.ts +2 -2
- package/elements/route.d.ts.map +1 -1
- package/elements/route.js +3 -3
- package/elements/route.js.map +1 -1
- package/elements/use.d.ts +1 -0
- package/elements/use.d.ts.map +1 -1
- package/elements/use.js +4 -1
- package/elements/use.js.map +1 -1
- package/helper/i18n.d.ts +2 -4
- package/helper/i18n.d.ts.map +1 -1
- package/helper/i18n.js +69 -34
- package/helper/i18n.js.map +1 -1
- package/helper/mediaquery.d.ts +2 -0
- package/helper/mediaquery.d.ts.map +1 -1
- package/helper/mediaquery.js +18 -13
- package/helper/mediaquery.js.map +1 -1
- package/lib/decorators.d.ts +1 -1
- package/lib/decorators.d.ts.map +1 -1
- package/lib/element.d.ts +5 -5
- package/lib/element.d.ts.map +1 -1
- package/lib/element.js +21 -23
- package/lib/element.js.map +1 -1
- package/lib/utils.d.ts +4 -4
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +56 -29
- package/lib/utils.js.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +7 -7
- package/src/elements/dialog-base.ts +1 -1
- package/src/elements/modal-base.ts +1 -1
- package/src/elements/route.ts +3 -3
- package/src/elements/use.ts +3 -1
- package/src/examples/i18n/index.ts +14 -0
- package/src/examples/multi-page/page-c.ts +1 -0
- package/src/examples/ref-route/index.ts +1 -1
- package/src/helper/i18n.ts +41 -13
- package/src/helper/mediaquery.ts +20 -13
- package/src/lib/decorators.ts +1 -1
- package/src/lib/element.ts +26 -27
- package/src/lib/utils.ts +44 -28
- package/src/lib/version.ts +1 -1
- package/src/test/gem-element/advance.test.ts +8 -2
- package/src/test/utils.test.ts +9 -6
- package/umd.js +6 -6
- package/umd.js.map +1 -1
|
@@ -22,7 +22,16 @@ const i18n = new I18n<typeof en>({
|
|
|
22
22
|
onChange: console.log,
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
+
const i18nModule = i18n.createSubModule<typeof en>('test', {
|
|
26
|
+
en,
|
|
27
|
+
de: {
|
|
28
|
+
title: 'Das ist I18n',
|
|
29
|
+
},
|
|
30
|
+
zh: 'data:text/plain;base64,eyJ0aXRsZSI6Iui/meaYr0kxOG4ifQ==',
|
|
31
|
+
});
|
|
32
|
+
|
|
25
33
|
@connectStore(i18n.store)
|
|
34
|
+
@connectStore(i18nModule.store)
|
|
26
35
|
@customElement('app-root')
|
|
27
36
|
export class App extends GemElement {
|
|
28
37
|
render() {
|
|
@@ -35,6 +44,11 @@ export class App extends GemElement {
|
|
|
35
44
|
<p>${i18n.get('title')}</p>
|
|
36
45
|
<p>${i18n.get('hello', 'World', 'reverse')}</p>
|
|
37
46
|
<p>${i18n.get('detail', (s) => html`<a href="#">${s}</a>`)}</p>
|
|
47
|
+
|
|
48
|
+
<h2>sub module</h2>
|
|
49
|
+
<p>${i18nModule.get('title')}</p>
|
|
50
|
+
<p>${i18nModule.get('hello', 'World', 'reverse')}</p>
|
|
51
|
+
<p>${i18nModule.get('detail', (s) => html`<a href="#">${s}</a>`)}</p>
|
|
38
52
|
`;
|
|
39
53
|
}
|
|
40
54
|
}
|
package/src/helper/i18n.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { GemError } from '../lib/utils';
|
|
|
8
8
|
const splitReg = /\$\d(?:<[^>]*>)?/;
|
|
9
9
|
const matchReg = /\$(\d)(<([^>]*)>)?/g;
|
|
10
10
|
const matchStrIndex = 3;
|
|
11
|
+
const modules = new Set<I18n<any>>();
|
|
11
12
|
|
|
12
13
|
type Msg =
|
|
13
14
|
| {
|
|
@@ -56,11 +57,11 @@ export class I18n<T = Record<string, Msg>> {
|
|
|
56
57
|
|
|
57
58
|
store: Store<any>;
|
|
58
59
|
|
|
59
|
-
get cacheCurrentKey() {
|
|
60
|
+
get #cacheCurrentKey() {
|
|
60
61
|
return `${this.cachePrefix}:current`;
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
get urlParamsLang() {
|
|
64
|
+
get #urlParamsLang() {
|
|
64
65
|
const { hostname, pathname, search } = location;
|
|
65
66
|
const parts = hostname.split('.');
|
|
66
67
|
switch (this.urlParamsType) {
|
|
@@ -76,13 +77,19 @@ export class I18n<T = Record<string, Msg>> {
|
|
|
76
77
|
}
|
|
77
78
|
}
|
|
78
79
|
|
|
79
|
-
get cacheLang() {
|
|
80
|
-
return (this.cache && localStorage.getItem(this
|
|
80
|
+
get #cacheLang() {
|
|
81
|
+
return (this.cache && localStorage.getItem(this.#cacheCurrentKey)) || '';
|
|
81
82
|
}
|
|
82
83
|
|
|
83
|
-
|
|
84
|
+
get #isMain(): boolean {
|
|
85
|
+
return !modules.has(this);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
set #lang(lang: string) {
|
|
89
|
+
if (this.#isMain) {
|
|
90
|
+
document.documentElement.lang = lang;
|
|
91
|
+
}
|
|
84
92
|
this.currentLanguage = lang;
|
|
85
|
-
document.documentElement.lang = lang;
|
|
86
93
|
this.onChange?.(lang);
|
|
87
94
|
}
|
|
88
95
|
|
|
@@ -94,7 +101,7 @@ export class I18n<T = Record<string, Msg>> {
|
|
|
94
101
|
this.store = createStore(this as any);
|
|
95
102
|
Object.assign<I18n<T>, Options<T>>(this as I18n<T>, options);
|
|
96
103
|
|
|
97
|
-
let currentLanguage = this
|
|
104
|
+
let currentLanguage = this.#urlParamsLang || this.currentLanguage || this.#cacheLang;
|
|
98
105
|
|
|
99
106
|
const ele = document.querySelector(`[type*=${this.cachePrefix}]`);
|
|
100
107
|
if (ele) {
|
|
@@ -108,7 +115,7 @@ export class I18n<T = Record<string, Msg>> {
|
|
|
108
115
|
}
|
|
109
116
|
|
|
110
117
|
if (currentLanguage) {
|
|
111
|
-
this
|
|
118
|
+
this.#lang = currentLanguage;
|
|
112
119
|
this.setLanguage(currentLanguage);
|
|
113
120
|
} else {
|
|
114
121
|
this.resetLanguage();
|
|
@@ -147,11 +154,14 @@ export class I18n<T = Record<string, Msg>> {
|
|
|
147
154
|
}
|
|
148
155
|
|
|
149
156
|
async setLanguage(lang: string): Promise<string> {
|
|
157
|
+
if (this.#isMain) {
|
|
158
|
+
modules.forEach(async (i18n) => await i18n.setLanguage(lang));
|
|
159
|
+
}
|
|
150
160
|
let pack: Partial<T>;
|
|
151
161
|
const data = this.resources[lang];
|
|
152
162
|
if (!data) {
|
|
153
163
|
// eslint-disable-next-line no-console
|
|
154
|
-
console.warn(
|
|
164
|
+
console.warn(`${this.cachePrefix}: not found \`${lang}\``);
|
|
155
165
|
return this.resetLanguage();
|
|
156
166
|
}
|
|
157
167
|
|
|
@@ -190,7 +200,7 @@ export class I18n<T = Record<string, Msg>> {
|
|
|
190
200
|
this.resources[lang] = pack;
|
|
191
201
|
updateStore(this.store, {});
|
|
192
202
|
if (lang !== this.currentLanguage) {
|
|
193
|
-
this
|
|
203
|
+
this.#lang = lang;
|
|
194
204
|
}
|
|
195
205
|
// auto cache
|
|
196
206
|
if (this.cache === true) {
|
|
@@ -200,16 +210,34 @@ export class I18n<T = Record<string, Msg>> {
|
|
|
200
210
|
}
|
|
201
211
|
|
|
202
212
|
resetLanguage() {
|
|
203
|
-
this
|
|
213
|
+
if (this.#isMain) {
|
|
214
|
+
modules.forEach((i18n) => i18n.resetLanguage());
|
|
215
|
+
}
|
|
216
|
+
this.#lang = this.fallbackLanguage;
|
|
204
217
|
if (this.cache) {
|
|
205
|
-
localStorage.removeItem(this
|
|
218
|
+
localStorage.removeItem(this.#cacheCurrentKey);
|
|
206
219
|
}
|
|
207
220
|
return this.setLanguage(this.detectLanguage());
|
|
208
221
|
}
|
|
209
222
|
|
|
210
223
|
// 一般用在 `onChange` 中
|
|
211
224
|
setCache() {
|
|
212
|
-
|
|
225
|
+
if (this.#isMain) {
|
|
226
|
+
modules.forEach((i18n) => i18n.setCache());
|
|
227
|
+
}
|
|
228
|
+
localStorage.setItem(this.#cacheCurrentKey, this.currentLanguage);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
createSubModule<K = Record<string, Msg>>(name: string, resources: Resources<K>) {
|
|
232
|
+
const module = new I18n<K>({
|
|
233
|
+
resources,
|
|
234
|
+
cachePrefix: `${this.cachePrefix}-${name}`,
|
|
235
|
+
currentLanguage: this.currentLanguage,
|
|
236
|
+
fallbackLanguage: this.fallbackLanguage,
|
|
237
|
+
cache: this.cache,
|
|
238
|
+
});
|
|
239
|
+
modules.add(module);
|
|
240
|
+
return module;
|
|
213
241
|
}
|
|
214
242
|
|
|
215
243
|
/**
|
package/src/helper/mediaquery.ts
CHANGED
|
@@ -1,70 +1,77 @@
|
|
|
1
1
|
// https://gist.github.com/gokulkrishh/242e68d1ee94ad05f488
|
|
2
2
|
// https://mediag.com/news/popular-screen-resolutions-designing-for-all/
|
|
3
3
|
|
|
4
|
+
const matche = (query: string) => window.matchMedia(query).matches;
|
|
5
|
+
|
|
4
6
|
export const mediaQuery = {
|
|
5
7
|
PRINT: 'print',
|
|
6
8
|
get isPrint() {
|
|
7
|
-
return
|
|
9
|
+
return matche(this.PRINT);
|
|
8
10
|
},
|
|
9
11
|
HOVER: '(hover: hover)',
|
|
10
12
|
get isHover() {
|
|
11
|
-
return
|
|
13
|
+
return matche(this.HOVER);
|
|
12
14
|
},
|
|
13
15
|
|
|
14
16
|
PHONE: '(min-width: 319px) and (max-width: 480px)',
|
|
15
17
|
get isPhone() {
|
|
16
|
-
return
|
|
18
|
+
return matche(this.PHONE);
|
|
17
19
|
},
|
|
18
20
|
|
|
19
21
|
LAPTOP: '(min-width: 1023px) and (max-width: 1280px) and (orientation: landscape)',
|
|
20
22
|
get isLaptop() {
|
|
21
|
-
return
|
|
23
|
+
return matche(this.LAPTOP);
|
|
22
24
|
},
|
|
23
25
|
|
|
24
26
|
DESKTOP: '(min-width: 1279px) and (orientation: landscape)',
|
|
25
27
|
get isDesktop() {
|
|
26
|
-
return
|
|
28
|
+
return matche(this.DESKTOP);
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
WIDTHSCREEN: '(min-width: 1919px) and (orientation: landscape)',
|
|
32
|
+
get isWidthScreen() {
|
|
33
|
+
return matche(this.WIDTHSCREEN);
|
|
27
34
|
},
|
|
28
35
|
|
|
29
36
|
PHONE_LANDSCAPE: '(min-width: 479px) and (max-width: 767px) and (orientation: landscape)',
|
|
30
37
|
// PHONE_LANDSCAPE: '(min-width: 481px) and (max-width: 767px)',
|
|
31
38
|
get isPhoneLandscape() {
|
|
32
|
-
return
|
|
39
|
+
return matche(this.PHONE_LANDSCAPE);
|
|
33
40
|
},
|
|
34
41
|
|
|
35
42
|
TABLET: '(min-width: 768px) and (max-width: 1024px)',
|
|
36
43
|
get isTablet() {
|
|
37
|
-
return
|
|
44
|
+
return matche(this.TABLET);
|
|
38
45
|
},
|
|
39
46
|
|
|
40
47
|
SMALL_PHONE: '(min-width: 319px) and (max-width: 480px) and (max-height: 640px)',
|
|
41
48
|
get isSmallPhone() {
|
|
42
|
-
return
|
|
49
|
+
return matche(this.SMALL_PHONE);
|
|
43
50
|
},
|
|
44
51
|
|
|
45
52
|
WATCH: '(max-width: 319px)',
|
|
46
53
|
get isWatch() {
|
|
47
|
-
return
|
|
54
|
+
return matche(this.WATCH);
|
|
48
55
|
},
|
|
49
56
|
|
|
50
57
|
SHORT: '(min-width: 479px) and (max-height: 320px)',
|
|
51
58
|
get isShort() {
|
|
52
|
-
return
|
|
59
|
+
return matche(this.SHORT);
|
|
53
60
|
},
|
|
54
61
|
|
|
55
62
|
PWA: '(display-mode: standalone)',
|
|
56
63
|
// PWA: '(display-mode: browser)', // debugging
|
|
57
64
|
get isPWA() {
|
|
58
|
-
return
|
|
65
|
+
return matche(this.PWA);
|
|
59
66
|
},
|
|
60
67
|
|
|
61
68
|
MOTION_REDUCE: '(prefers-reduced-motion: reduce)',
|
|
62
69
|
get isMotionReduce() {
|
|
63
|
-
return
|
|
70
|
+
return matche(this.MOTION_REDUCE);
|
|
64
71
|
},
|
|
65
72
|
|
|
66
73
|
DATA_REDUCE: '(prefers-reduced-data: reduce)',
|
|
67
74
|
get isDataReduce() {
|
|
68
|
-
return
|
|
75
|
+
return matche(this.DATA_REDUCE);
|
|
69
76
|
},
|
|
70
77
|
};
|
package/src/lib/decorators.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { defineAttribute, defineCSSState, defineProperty, defineRef, GemElement,
|
|
|
2
2
|
import { Store } from './store';
|
|
3
3
|
import { Sheet, camelToKebabCase, randomStr } from './utils';
|
|
4
4
|
|
|
5
|
-
type GemElementPrototype = GemElement
|
|
5
|
+
type GemElementPrototype = GemElement<any>;
|
|
6
6
|
type GemElementConstructor = typeof GemElement;
|
|
7
7
|
type StaticField = Exclude<keyof GemElementConstructor, 'prototype' | 'rootElement'>;
|
|
8
8
|
type StaticFieldMember = string | Store<unknown> | Sheet<unknown>;
|
package/src/lib/element.ts
CHANGED
|
@@ -25,11 +25,10 @@ export { ifDefined } from 'lit-html/directives/if-defined';
|
|
|
25
25
|
type CustomStateSet = Set<string>;
|
|
26
26
|
|
|
27
27
|
declare global {
|
|
28
|
-
interface ElementInternals {
|
|
28
|
+
interface ElementInternals extends ARIAMixin {
|
|
29
29
|
states: CustomStateSet;
|
|
30
30
|
// https://w3c.github.io/aria/#role_definitions
|
|
31
31
|
role?: string;
|
|
32
|
-
ariaModal?: boolean;
|
|
33
32
|
}
|
|
34
33
|
// 用于 css 选择器选择元素,使用 @refobject 自动选择获取
|
|
35
34
|
// 必须使用 attr 赋值
|
|
@@ -38,8 +37,8 @@ declare global {
|
|
|
38
37
|
* @attr inert
|
|
39
38
|
*/
|
|
40
39
|
interface HTMLElement {
|
|
41
|
-
attachInternals
|
|
42
|
-
inert
|
|
40
|
+
attachInternals: () => ElementInternals;
|
|
41
|
+
inert: boolean;
|
|
43
42
|
}
|
|
44
43
|
}
|
|
45
44
|
|
|
@@ -53,20 +52,15 @@ function execCallback(fun: any) {
|
|
|
53
52
|
|
|
54
53
|
// global render task pool
|
|
55
54
|
const asyncRenderTaskList = new LinkedList<() => void>();
|
|
56
|
-
const tick = () => {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (task) {
|
|
64
|
-
task();
|
|
65
|
-
callback(timestamp);
|
|
66
|
-
}
|
|
67
|
-
});
|
|
55
|
+
const tick = (timeStamp = performance.now()) => {
|
|
56
|
+
if (performance.now() > timeStamp + 16) return requestAnimationFrame(tick);
|
|
57
|
+
const task = asyncRenderTaskList.get();
|
|
58
|
+
if (task) {
|
|
59
|
+
task();
|
|
60
|
+
tick(timeStamp);
|
|
61
|
+
}
|
|
68
62
|
};
|
|
69
|
-
asyncRenderTaskList.addEventListener('start', tick);
|
|
63
|
+
asyncRenderTaskList.addEventListener('start', () => addMicrotask(tick));
|
|
70
64
|
|
|
71
65
|
type GetDepFun<T> = () => T;
|
|
72
66
|
type EffectCallback<T> = (depValues: T, oldDepValues?: T) => any;
|
|
@@ -164,7 +158,7 @@ export abstract class GemElement<T = Record<string, unknown>> extends HTMLElemen
|
|
|
164
158
|
if (!this.attachInternals) {
|
|
165
159
|
// https://bugs.webkit.org/show_bug.cgi?id=197960
|
|
166
160
|
this.attachInternals = () => {
|
|
167
|
-
return { states: getCustomStateSet() };
|
|
161
|
+
return { states: getCustomStateSet() } as any;
|
|
168
162
|
};
|
|
169
163
|
}
|
|
170
164
|
this.#internals = this.attachInternals();
|
|
@@ -198,7 +192,7 @@ export abstract class GemElement<T = Record<string, unknown>> extends HTMLElemen
|
|
|
198
192
|
list?.forEach((effectItem) => {
|
|
199
193
|
const { callback, getDep, values, preCallback } = effectItem;
|
|
200
194
|
const newValues = getDep?.();
|
|
201
|
-
if (!getDep || isArrayChange(values, newValues)) {
|
|
195
|
+
if (!getDep || !values || isArrayChange(values, newValues)) {
|
|
202
196
|
execCallback(preCallback);
|
|
203
197
|
effectItem.preCallback = callback(newValues, values);
|
|
204
198
|
effectItem.values = newValues;
|
|
@@ -235,15 +229,17 @@ export abstract class GemElement<T = Record<string, unknown>> extends HTMLElemen
|
|
|
235
229
|
getDep?: T extends any[] ? () => [...T] : undefined,
|
|
236
230
|
) => {
|
|
237
231
|
if (!this.#effectList) this.#effectList = [];
|
|
238
|
-
const values = getDep?.() as T;
|
|
239
232
|
const effectItem: EffectItem<T> = {
|
|
240
233
|
callback,
|
|
241
234
|
getDep,
|
|
242
|
-
values,
|
|
235
|
+
values: undefined,
|
|
243
236
|
initialized: !!this.isConnected,
|
|
244
237
|
};
|
|
245
238
|
// 以挂载时立即执行副作用,未挂载时等挂载后执行
|
|
246
|
-
if (this.isConnected)
|
|
239
|
+
if (this.isConnected) {
|
|
240
|
+
effectItem.values = getDep?.() as T;
|
|
241
|
+
effectItem.preCallback = callback(effectItem.values);
|
|
242
|
+
}
|
|
247
243
|
this.#effectList.push(effectItem);
|
|
248
244
|
};
|
|
249
245
|
|
|
@@ -279,7 +275,8 @@ export abstract class GemElement<T = Record<string, unknown>> extends HTMLElemen
|
|
|
279
275
|
this.#effectList?.forEach((effectItem) => {
|
|
280
276
|
const { callback, getDep, initialized } = effectItem;
|
|
281
277
|
if (!initialized) {
|
|
282
|
-
effectItem.
|
|
278
|
+
effectItem.values = getDep?.();
|
|
279
|
+
effectItem.preCallback = callback(effectItem.values);
|
|
283
280
|
effectItem.initialized = true;
|
|
284
281
|
}
|
|
285
282
|
});
|
|
@@ -378,14 +375,16 @@ export abstract class GemElement<T = Record<string, unknown>> extends HTMLElemen
|
|
|
378
375
|
}
|
|
379
376
|
};
|
|
380
377
|
|
|
381
|
-
closestElement(
|
|
382
|
-
|
|
383
|
-
|
|
378
|
+
closestElement<K extends keyof HTMLElementTagNameMap>(tag: K): HTMLElementTagNameMap[K] | null;
|
|
379
|
+
closestElement<K extends abstract new (...args: any) => any>(constructor: K): InstanceType<K> | null;
|
|
380
|
+
closestElement<K extends abstract new (...args: any) => any>(constructorOrTag: K | string): Element | null {
|
|
381
|
+
const isConstructor = typeof constructorOrTag === 'function';
|
|
382
|
+
const tagName = typeof constructorOrTag === 'string' && constructorOrTag.toUpperCase();
|
|
384
383
|
const getRootElement = (ele: Element): Element | null => {
|
|
385
384
|
const rootEle = ele.parentElement || (ele.getRootNode() as ShadowRoot).host;
|
|
386
385
|
if (!rootEle) return null;
|
|
387
386
|
if (isConstructor) {
|
|
388
|
-
if (rootEle.constructor ===
|
|
387
|
+
if (rootEle.constructor === constructorOrTag) {
|
|
389
388
|
return rootEle;
|
|
390
389
|
}
|
|
391
390
|
} else if (rootEle.tagName === tagName) {
|
package/src/lib/utils.ts
CHANGED
|
@@ -50,56 +50,72 @@ interface LinkedListItem<T> {
|
|
|
50
50
|
next?: LinkedListItem<T>;
|
|
51
51
|
}
|
|
52
52
|
// work on nodejs
|
|
53
|
-
export class LinkedList<T
|
|
53
|
+
export class LinkedList<T = any> extends (globalThis.EventTarget || Object) {
|
|
54
54
|
addEventListener: <K extends keyof LinkedListEventMap>(
|
|
55
55
|
type: K,
|
|
56
56
|
listener: (this: LinkedList<T>, ev: LinkedListEventMap[K]) => any,
|
|
57
57
|
options?: boolean | AddEventListenerOptions,
|
|
58
58
|
) => void;
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
firstItem?: LinkedListItem<T>;
|
|
62
|
-
lastItem?: LinkedListItem<T>;
|
|
60
|
+
#map = new Map<T, LinkedListItem<T>>();
|
|
61
|
+
#firstItem?: LinkedListItem<T>;
|
|
62
|
+
#lastItem?: LinkedListItem<T>;
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const item: LinkedListItem<T> = { value };
|
|
67
|
-
if (!this.firstItem) {
|
|
68
|
-
this.dispatchEvent(new CustomEvent('start'));
|
|
69
|
-
this.firstItem = item;
|
|
70
|
-
}
|
|
71
|
-
if (this.lastItem) {
|
|
72
|
-
this.lastItem.next = item;
|
|
73
|
-
item.prev = this.lastItem;
|
|
74
|
-
}
|
|
75
|
-
this.lastItem = item;
|
|
76
|
-
|
|
77
|
-
const existItem = this.pool.get(value);
|
|
64
|
+
#delete(value: T) {
|
|
65
|
+
const existItem = this.#map.get(value);
|
|
78
66
|
if (existItem) {
|
|
79
67
|
if (existItem.prev) {
|
|
80
68
|
existItem.prev.next = existItem.next;
|
|
81
69
|
} else {
|
|
82
|
-
this
|
|
70
|
+
this.#firstItem = existItem.next;
|
|
83
71
|
}
|
|
84
72
|
if (existItem.next) {
|
|
85
73
|
existItem.next.prev = existItem.prev;
|
|
74
|
+
} else {
|
|
75
|
+
this.#lastItem = existItem.prev;
|
|
86
76
|
}
|
|
77
|
+
this.#map.delete(value);
|
|
87
78
|
}
|
|
88
|
-
|
|
89
|
-
this.pool.set(value, item);
|
|
79
|
+
return existItem;
|
|
90
80
|
}
|
|
91
81
|
|
|
92
|
-
get()
|
|
93
|
-
|
|
94
|
-
|
|
82
|
+
get size() {
|
|
83
|
+
return this.#map.size;
|
|
84
|
+
}
|
|
95
85
|
|
|
96
|
-
|
|
86
|
+
// 添加到尾部,已存在时会删除老的项目
|
|
87
|
+
// 如果是添加第一个,start 事件会在添加前触发,避免处理事件重复的逻辑
|
|
88
|
+
add(value: T) {
|
|
89
|
+
if (!this.#lastItem) {
|
|
90
|
+
this.dispatchEvent(new CustomEvent('start'));
|
|
91
|
+
}
|
|
92
|
+
const item: LinkedListItem<T> = this.#delete(value) || { value };
|
|
93
|
+
item.prev = this.#lastItem;
|
|
94
|
+
if (item.prev) {
|
|
95
|
+
item.prev.next = item;
|
|
96
|
+
}
|
|
97
|
+
item.next = undefined;
|
|
98
|
+
this.#lastItem = item;
|
|
99
|
+
if (!this.#firstItem) {
|
|
100
|
+
this.#firstItem = item;
|
|
101
|
+
}
|
|
102
|
+
this.#map.set(value, item);
|
|
103
|
+
}
|
|
97
104
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
105
|
+
// 删除这个元素后没有其他元素时立即出发 end 事件
|
|
106
|
+
delete(value: T) {
|
|
107
|
+
const deleteItem = this.#delete(value);
|
|
108
|
+
if (!this.#firstItem) {
|
|
101
109
|
this.dispatchEvent(new CustomEvent('end'));
|
|
102
110
|
}
|
|
111
|
+
return deleteItem;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// 获取头部元素
|
|
115
|
+
get(): T | undefined {
|
|
116
|
+
const firstItem = this.#firstItem;
|
|
117
|
+
if (!firstItem) return;
|
|
118
|
+
this.#delete(firstItem.value);
|
|
103
119
|
return firstItem.value;
|
|
104
120
|
}
|
|
105
121
|
}
|
package/src/lib/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Do not modify manually
|
|
2
|
-
export const version = '1.5.
|
|
2
|
+
export const version = '1.5.8';
|
|
@@ -25,10 +25,11 @@ customElements.define('async-gem-demo', AsyncGemDemo);
|
|
|
25
25
|
describe('异步 gem element 测试', () => {
|
|
26
26
|
it('异步 gem element 更新', async () => {
|
|
27
27
|
const el: AsyncGemDemo = await fixture(html`<async-gem-demo></async-gem-demo>`);
|
|
28
|
+
expect(el.renderCount).to.equal(1);
|
|
28
29
|
updateStore(store, { a: ++store.a });
|
|
29
30
|
el.setState({ a: ++el.state.a });
|
|
30
31
|
await nextFrame();
|
|
31
|
-
expect(el.renderCount).to.equal(
|
|
32
|
+
expect(el.renderCount).to.equal(2);
|
|
32
33
|
await nextFrame();
|
|
33
34
|
expect(el.renderCount).to.equal(2);
|
|
34
35
|
});
|
|
@@ -146,11 +147,15 @@ class EffectGemDemo extends GemElement {
|
|
|
146
147
|
@attribute attr = 'a';
|
|
147
148
|
@property prop = {};
|
|
148
149
|
effectCount = 0;
|
|
150
|
+
compareCount = 0;
|
|
149
151
|
constructor() {
|
|
150
152
|
super();
|
|
151
153
|
this.effect(
|
|
152
154
|
() => this.effectCount++,
|
|
153
|
-
() =>
|
|
155
|
+
() => {
|
|
156
|
+
this.compareCount++;
|
|
157
|
+
return [this.attr, this.prop];
|
|
158
|
+
},
|
|
154
159
|
);
|
|
155
160
|
}
|
|
156
161
|
mounted() {
|
|
@@ -170,6 +175,7 @@ customElements.define('effect-gem-demo', EffectGemDemo);
|
|
|
170
175
|
describe('gem element 副作用', () => {
|
|
171
176
|
it('依赖当前值', async () => {
|
|
172
177
|
const el: EffectGemDemo = await fixture(html`<effect-gem-demo></effect-gem-demo>`);
|
|
178
|
+
expect(el.compareCount).to.equal(1);
|
|
173
179
|
expect(el.effectCount).to.equal(3);
|
|
174
180
|
el.attr = 'b';
|
|
175
181
|
el.prop = {};
|
package/src/test/utils.test.ts
CHANGED
|
@@ -22,17 +22,20 @@ describe('utils 测试', () => {
|
|
|
22
22
|
const linkedList = new LinkedList<() => void>();
|
|
23
23
|
let countAtStart = 0;
|
|
24
24
|
let countAtPause = 0;
|
|
25
|
-
linkedList.addEventListener('start', () => (countAtStart = linkedList.
|
|
26
|
-
linkedList.addEventListener('end', () => (countAtPause = linkedList.
|
|
27
|
-
const fun1 = () =>
|
|
28
|
-
const fun2 = () =>
|
|
25
|
+
linkedList.addEventListener('start', () => (countAtStart = linkedList.size));
|
|
26
|
+
linkedList.addEventListener('end', () => (countAtPause = linkedList.size));
|
|
27
|
+
const fun1 = () => 1;
|
|
28
|
+
const fun2 = () => 2;
|
|
29
29
|
linkedList.add(fun1);
|
|
30
30
|
linkedList.add(fun2);
|
|
31
|
+
linkedList.delete(fun2);
|
|
32
|
+
expect(linkedList.size).to.equal(1);
|
|
33
|
+
linkedList.add(fun2);
|
|
31
34
|
linkedList.add(fun1);
|
|
32
|
-
expect(linkedList.
|
|
35
|
+
expect(linkedList.size).to.equal(2);
|
|
33
36
|
expect(linkedList.get()).to.equal(fun2);
|
|
34
37
|
expect(linkedList.get()).to.equal(fun1);
|
|
35
|
-
expect(linkedList.
|
|
38
|
+
expect(linkedList.size).to.equal(0);
|
|
36
39
|
expect(linkedList.get()).to.equal(undefined);
|
|
37
40
|
expect(countAtStart).to.equal(0);
|
|
38
41
|
expect(countAtPause).to.equal(0);
|