@mantou/gem 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/gem.es.js +1023 -1038
- package/dist/gem.umd.js +25 -25
- package/elements/base/modal-factory.d.ts +6 -4
- package/elements/base/modal-factory.d.ts.map +1 -1
- package/elements/base/modal-factory.js +3 -3
- package/elements/base/modal-factory.js.map +1 -1
- package/elements/base/route.d.ts +8 -2
- package/elements/base/route.d.ts.map +1 -1
- package/elements/base/route.js +4 -4
- package/elements/base/route.js.map +1 -1
- package/elements/base/title.js +2 -2
- package/elements/base/title.js.map +1 -1
- package/helper/i18n.d.ts.map +1 -1
- package/helper/i18n.js +11 -11
- package/helper/i18n.js.map +1 -1
- package/helper/theme.d.ts +8 -7
- package/helper/theme.d.ts.map +1 -1
- package/helper/theme.js +21 -21
- package/helper/theme.js.map +1 -1
- package/lib/decorators.d.ts +1 -1
- package/lib/decorators.d.ts.map +1 -1
- package/lib/decorators.js.map +1 -1
- package/lib/element.d.ts +3 -2
- package/lib/element.d.ts.map +1 -1
- package/lib/element.js +6 -9
- package/lib/element.js.map +1 -1
- package/lib/history.d.ts +20 -8
- package/lib/history.d.ts.map +1 -1
- package/lib/history.js +12 -12
- package/lib/history.js.map +1 -1
- package/lib/store.d.ts +5 -10
- package/lib/store.d.ts.map +1 -1
- package/lib/store.js +7 -22
- package/lib/store.js.map +1 -1
- package/lib/utils.d.ts +1 -0
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +19 -14
- package/lib/utils.js.map +1 -1
- package/package.json +3 -3
- package/src/elements/base/modal-factory.ts +3 -3
- package/src/elements/base/route.ts +4 -4
- package/src/elements/base/title.ts +2 -2
- package/src/helper/i18n.ts +11 -8
- package/src/helper/theme.ts +27 -26
- package/src/lib/decorators.ts +1 -1
- package/src/lib/element.ts +9 -12
- package/src/lib/history.ts +12 -12
- package/src/lib/store.ts +11 -31
- package/src/lib/utils.ts +21 -14
- package/src/test/gem-element/advance.test.ts +2 -2
- package/src/test/gem-element/basic.test.ts +3 -3
- package/src/test/gem-element/decorators.test.ts +4 -4
- package/src/test/store.test.ts +7 -7
- package/src/test/utils.test.ts +1 -1
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
import { GemElement, html } from '../../lib/element';
|
|
16
16
|
import { attribute, boolattribute, effect, mounted, shadow } from '../../lib/decorators';
|
|
17
|
-
import {
|
|
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
|
-
|
|
53
|
+
titleStore({ title });
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
@effect((i) => [i.inert])
|
package/src/helper/i18n.ts
CHANGED
|
@@ -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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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) {
|
package/src/helper/theme.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import type { Store } from '../lib/store';
|
|
2
|
-
import { connect,
|
|
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
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
|
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
|
|
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
|
|
62
|
+
return theme as Theme<T>;
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
/**
|
|
65
66
|
* 用于 `@adoptedStyle(theme)`,类似 `createCSSSheet`
|
|
66
67
|
*/
|
|
67
|
-
export function
|
|
68
|
-
return
|
|
68
|
+
export function createScopedTheme<T extends Record<string, unknown>>(themeObj: T) {
|
|
69
|
+
return createThemeFromProps(themeObj);
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
|
|
72
|
-
export function
|
|
73
|
-
const result =
|
|
74
|
-
document.adoptedStyleSheets.push(result[
|
|
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
|
|
80
|
-
return
|
|
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
|
|
84
|
-
const
|
|
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
|
|
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
|
|
93
|
+
const theme = createThemeFromProps({ ...themeObj }, props);
|
|
93
94
|
this.internals.sheets.push(theme[SheetToken].getStyle(this));
|
|
94
|
-
this.memo(() =>
|
|
95
|
+
this.memo(() => theme(access.get(this).apply(this)), getDep && (() => getDep(this) as any));
|
|
95
96
|
});
|
|
96
97
|
};
|
|
97
|
-
return
|
|
98
|
+
return createUpdater(themeAsKeys, update) as ReturnType<typeof createUpdater<typeof themeAsKeys, typeof update>>;
|
|
98
99
|
}
|
package/src/lib/decorators.ts
CHANGED
|
@@ -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<
|
|
459
|
+
export function connectStore(store: Store<any>) {
|
|
460
460
|
return function (_: unknown, context: ClassDecoratorContext) {
|
|
461
461
|
pushStaticField(context, 'observedStores', store);
|
|
462
462
|
};
|
package/src/lib/element.ts
CHANGED
|
@@ -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,7 +29,7 @@ declare global {
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
const { assign, defineProperty
|
|
32
|
+
const { assign, defineProperty } = Object;
|
|
33
33
|
|
|
34
34
|
defineProperty(Symbol, 'metadata', { value: Symbol.for('Symbol.metadata') });
|
|
35
35
|
|
|
@@ -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) =>
|
|
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;
|
|
@@ -273,7 +274,7 @@ export type Metadata = Partial<ShadowRootInit> & {
|
|
|
273
274
|
}
|
|
274
275
|
>;
|
|
275
276
|
// 实例化时使用到,DevTools 需要读取
|
|
276
|
-
observedStores: Store<
|
|
277
|
+
observedStores: Store<any>[];
|
|
277
278
|
adoptedStyleSheets?: Sheet<unknown>[];
|
|
278
279
|
// 以下静态字段仅供外部读取,没有实际作用
|
|
279
280
|
observedProperties?: string[];
|
|
@@ -331,17 +332,13 @@ export abstract class GemElement extends HTMLElement {
|
|
|
331
332
|
static {
|
|
332
333
|
createState = <T>(initState: T) => {
|
|
333
334
|
const ele = currentConstructGemElement;
|
|
334
|
-
const state
|
|
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
|
-
|
|
344
|
-
return state as T & ((this: GemElement, state: Partial<T>) => void);
|
|
341
|
+
return state;
|
|
345
342
|
};
|
|
346
343
|
createRef = () => new RefObject(currentConstructGemElement);
|
|
347
344
|
createTemplate = (ele, item) => ele.#renderList.push(item);
|
package/src/lib/history.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// https://github.com/WICG/navigation-api#stakeholder-feedback
|
|
2
2
|
|
|
3
|
-
import { connect,
|
|
3
|
+
import { connect, createStore } from './store';
|
|
4
4
|
import { QueryString, cleanObject, GemError, absoluteLocation } from './utils';
|
|
5
5
|
|
|
6
6
|
const nativeHistory = window.history;
|
|
@@ -18,7 +18,7 @@ export interface HistoryState {
|
|
|
18
18
|
[index: string]: any;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
const
|
|
21
|
+
const store = createStore<HistoryState>({
|
|
22
22
|
$hasCloseHandle: false,
|
|
23
23
|
$hasOpenHandle: false,
|
|
24
24
|
$hasShouldCloseHandle: false,
|
|
@@ -103,7 +103,7 @@ function updateHistory(p: UpdateHistoryParams, native: typeof nativeHistory.push
|
|
|
103
103
|
paramsMap.set(state.$key, params);
|
|
104
104
|
dispatchBeforeChangeEvent();
|
|
105
105
|
cleanObject(store);
|
|
106
|
-
|
|
106
|
+
store(state);
|
|
107
107
|
const url = getUrlBarPath(path) + new QueryString(query) + hash;
|
|
108
108
|
const prevHash = decodeURIComponent(location.hash);
|
|
109
109
|
native(state, title, url);
|
|
@@ -123,7 +123,7 @@ function updateHistoryByNative(data: any, title: string, originUrl: string, nati
|
|
|
123
123
|
paramsMap.set(state.$key, params);
|
|
124
124
|
dispatchBeforeChangeEvent();
|
|
125
125
|
cleanObject(store);
|
|
126
|
-
|
|
126
|
+
store(state);
|
|
127
127
|
const url = getUrlBarPath(pathname) + params.query + hash;
|
|
128
128
|
const prevHash = location.hash;
|
|
129
129
|
native(state, title, url);
|
|
@@ -131,7 +131,7 @@ function updateHistoryByNative(data: any, title: string, originUrl: string, nati
|
|
|
131
131
|
if (prevHash !== hash) window.dispatchEvent(new CustomEvent('hashchange'));
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
const
|
|
134
|
+
const gemBasePathStore = createStore({
|
|
135
135
|
basePath: '',
|
|
136
136
|
});
|
|
137
137
|
|
|
@@ -147,7 +147,7 @@ class GemHistory extends EventTarget {
|
|
|
147
147
|
}
|
|
148
148
|
set basePath(basePath: string) {
|
|
149
149
|
// 应用初始化的时候设置
|
|
150
|
-
|
|
150
|
+
gemBasePathStore({ basePath });
|
|
151
151
|
// paramsMap 更新后 ui 才会更新
|
|
152
152
|
Object.assign(paramsMap.get(store.$key)!, { path: getInternalPath(location.pathname) });
|
|
153
153
|
}
|
|
@@ -156,7 +156,7 @@ class GemHistory extends EventTarget {
|
|
|
156
156
|
}
|
|
157
157
|
updateParams(params: UpdateHistoryParams) {
|
|
158
158
|
Object.assign(paramsMap.get(store.$key)!, params);
|
|
159
|
-
|
|
159
|
+
store();
|
|
160
160
|
}
|
|
161
161
|
push(params: UpdateHistoryParams) {
|
|
162
162
|
updateHistory(params, nativePushState);
|
|
@@ -183,7 +183,7 @@ class GemHistory extends EventTarget {
|
|
|
183
183
|
// `<gem-**>` 只读写父应用 `basePath`
|
|
184
184
|
const gemHistory = new GemHistory();
|
|
185
185
|
|
|
186
|
-
const
|
|
186
|
+
const gemTitleStore = createStore({ defaultTitle: document.title, url: '', title: '' });
|
|
187
187
|
|
|
188
188
|
const _GEMHISTORY = { history: gemHistory, titleStore: gemTitleStore, basePathStore: gemBasePathStore };
|
|
189
189
|
|
|
@@ -217,7 +217,7 @@ if (!window._GEMHISTORY) {
|
|
|
217
217
|
// 支持在 `data:` `blob:` 中 `import`
|
|
218
218
|
if (protocol.startsWith('http')) gemHistory.replace({ path: pathname, query: search, hash });
|
|
219
219
|
} else if (nativeHistory.state.$hasCloseHandle) {
|
|
220
|
-
|
|
220
|
+
store(nativeHistory.state);
|
|
221
221
|
const params = normalizeParams({ title: document.title });
|
|
222
222
|
paramsMap.set(store.$key, params);
|
|
223
223
|
// 有 handle 返回键的页面刷新需要清除返回 handler
|
|
@@ -225,7 +225,7 @@ if (!window._GEMHISTORY) {
|
|
|
225
225
|
} else {
|
|
226
226
|
// 有 gem 历史的正常普通刷新, 储存 params
|
|
227
227
|
const params = normalizeParams({ title: document.title, hash: location.hash });
|
|
228
|
-
|
|
228
|
+
store({
|
|
229
229
|
$key: getKey(),
|
|
230
230
|
...(nativeHistory.state || {}),
|
|
231
231
|
});
|
|
@@ -242,7 +242,7 @@ if (!window._GEMHISTORY) {
|
|
|
242
242
|
connect(store, () => {
|
|
243
243
|
const { title } = gemHistory.getParams();
|
|
244
244
|
if (title !== gemTitleStore.title) {
|
|
245
|
-
|
|
245
|
+
gemTitleStore({ title });
|
|
246
246
|
}
|
|
247
247
|
});
|
|
248
248
|
|
|
@@ -312,7 +312,7 @@ if (!window._GEMHISTORY) {
|
|
|
312
312
|
|
|
313
313
|
dispatchBeforeChangeEvent();
|
|
314
314
|
cleanObject(store);
|
|
315
|
-
|
|
315
|
+
store(newState);
|
|
316
316
|
});
|
|
317
317
|
}
|
|
318
318
|
|
package/src/lib/store.ts
CHANGED
|
@@ -1,40 +1,20 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { addMicrotask, GemError } from './utils';
|
|
1
|
+
import { addMicrotask, createUpdater } from './utils';
|
|
3
2
|
|
|
4
|
-
export
|
|
3
|
+
export type Store<T = any> = ReturnType<typeof createUpdater<T>>;
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
export type Store<T> = T & { '': string };
|
|
5
|
+
export const StoreListenerMap = new WeakMap<Store<any>, Set<() => void>>();
|
|
8
6
|
|
|
9
|
-
export function createStore<T
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
StoreListenerMap.set(originStore, new Set<() => void>());
|
|
14
|
-
return originStore as Store<T>;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function updateStore<T extends NonPrimitive>(store: Store<T>, value?: Partial<T>) {
|
|
18
|
-
Object.assign(store, value);
|
|
19
|
-
StoreListenerMap.get(store)?.forEach((func) => {
|
|
20
|
-
addMicrotask(func);
|
|
7
|
+
export function createStore<T = any>(originStore: T) {
|
|
8
|
+
const store = createUpdater(originStore, (value?: Partial<T>) => {
|
|
9
|
+
Object.assign(store, value);
|
|
10
|
+
StoreListenerMap.get(store)?.forEach((func) => addMicrotask(func));
|
|
21
11
|
});
|
|
12
|
+
StoreListenerMap.set(store, new Set<() => void>());
|
|
13
|
+
return store;
|
|
22
14
|
}
|
|
23
15
|
|
|
24
|
-
export function
|
|
25
|
-
const store = createStore(originStore);
|
|
26
|
-
return [store, (value?: Partial<T>) => updateStore(store, value)] as const;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function connect<T extends NonPrimitive>(store: Store<T>, func: () => void) {
|
|
16
|
+
export function connect(store: Store<any>, func: () => void) {
|
|
30
17
|
const listeners = StoreListenerMap.get(store);
|
|
31
18
|
listeners?.add(func);
|
|
32
|
-
return () =>
|
|
33
|
-
listeners?.delete(func);
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function disconnect<T extends NonPrimitive>(store: Store<T>, func: () => void) {
|
|
38
|
-
const listeners = StoreListenerMap.get(store);
|
|
39
|
-
listeners?.delete(func);
|
|
19
|
+
return () => listeners?.delete(func);
|
|
40
20
|
}
|
package/src/lib/utils.ts
CHANGED
|
@@ -1,18 +1,25 @@
|
|
|
1
|
-
|
|
1
|
+
const { assign, setPrototypeOf, fromEntries, entries, keys } = Object;
|
|
2
|
+
|
|
3
|
+
const microtaskSet = new Set<() => void>();
|
|
2
4
|
export function addMicrotask(func: () => void) {
|
|
3
|
-
if (
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
microtaskSet = new Set();
|
|
9
|
-
set.forEach((f) => f());
|
|
10
|
-
});
|
|
11
|
-
}
|
|
12
|
-
microtaskSet.delete(func);
|
|
5
|
+
if (microtaskSet.has(func)) return;
|
|
6
|
+
queueMicrotask(() => {
|
|
7
|
+
microtaskSet.delete(func);
|
|
8
|
+
func();
|
|
9
|
+
});
|
|
13
10
|
microtaskSet.add(func);
|
|
14
11
|
}
|
|
15
12
|
|
|
13
|
+
export function createUpdater<T, Fn = (payload?: Partial<T>) => any>(initState: T, fn: Fn) {
|
|
14
|
+
const state: any = fn;
|
|
15
|
+
setPrototypeOf(state, null);
|
|
16
|
+
delete state.name;
|
|
17
|
+
delete state.length;
|
|
18
|
+
assign(state, initState);
|
|
19
|
+
// TODO: type
|
|
20
|
+
return state as Fn & T;
|
|
21
|
+
}
|
|
22
|
+
|
|
16
23
|
// 不编码 hash 用于比较
|
|
17
24
|
export function absoluteLocation(currentPath = '', relativePath = '') {
|
|
18
25
|
const { pathname, search, hash } = new URL(relativePath, location.origin + currentPath);
|
|
@@ -156,11 +163,11 @@ export class QueryString extends URLSearchParams {
|
|
|
156
163
|
concat(param: any) {
|
|
157
164
|
let query: any;
|
|
158
165
|
if (typeof param === 'string') {
|
|
159
|
-
query =
|
|
166
|
+
query = fromEntries(new URLSearchParams(param).entries());
|
|
160
167
|
} else {
|
|
161
168
|
query = param;
|
|
162
169
|
}
|
|
163
|
-
|
|
170
|
+
entries(query).forEach(([key, value]) => {
|
|
164
171
|
this.append(key, this.#stringify(value));
|
|
165
172
|
});
|
|
166
173
|
}
|
|
@@ -247,7 +254,7 @@ export function kebabToCamelCase(str: string) {
|
|
|
247
254
|
}
|
|
248
255
|
|
|
249
256
|
export function cleanObject<T extends Record<string, unknown>>(o: T) {
|
|
250
|
-
|
|
257
|
+
keys(o).forEach((key: keyof T) => {
|
|
251
258
|
delete o[key];
|
|
252
259
|
});
|
|
253
260
|
return o;
|
|
@@ -10,7 +10,7 @@ import { fixture, expect, nextFrame } from '@open-wc/testing';
|
|
|
10
10
|
|
|
11
11
|
import type { Metadata } from '../../lib/element';
|
|
12
12
|
import { createCSSSheet, createState, GemElement, html, createRef } from '../../lib/element';
|
|
13
|
-
import { createStore
|
|
13
|
+
import { createStore } from '../../lib/store';
|
|
14
14
|
import {
|
|
15
15
|
attribute,
|
|
16
16
|
property,
|
|
@@ -45,7 +45,7 @@ describe('异步 gem element 测试', () => {
|
|
|
45
45
|
it('异步 gem element 更新', async () => {
|
|
46
46
|
const el: AsyncGemDemo = await fixture(html`<async-gem-demo></async-gem-demo>`);
|
|
47
47
|
expect(el.renderCount).to.equal(1);
|
|
48
|
-
|
|
48
|
+
store({ a: ++store.a });
|
|
49
49
|
el.internals.stateList[0]({ a: ++(el.internals.stateList[0] as any).a });
|
|
50
50
|
await nextFrame();
|
|
51
51
|
expect(el.renderCount).to.equal(2);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { fixture, expect, nextFrame } from '@open-wc/testing';
|
|
2
2
|
|
|
3
3
|
import { createCSSSheet, createState, GemElement, html } from '../../lib/element';
|
|
4
|
-
import { createStore
|
|
4
|
+
import { createStore } from '../../lib/store';
|
|
5
5
|
import { css } from '../../lib/utils';
|
|
6
6
|
import {
|
|
7
7
|
adoptedStyle,
|
|
@@ -142,8 +142,8 @@ describe('基本 gem element 测试', () => {
|
|
|
142
142
|
it('更新 store', async () => {
|
|
143
143
|
const a = store.a;
|
|
144
144
|
const el: GemDemo = await fixture(html`<gem-demo></gem-demo>`);
|
|
145
|
-
|
|
146
|
-
|
|
145
|
+
store({ a: ++store.a });
|
|
146
|
+
store({ a: ++store.a });
|
|
147
147
|
expect(store.a).to.equal(a + 2);
|
|
148
148
|
expect(el.renderCount).to.equal(1);
|
|
149
149
|
await Promise.resolve();
|
|
@@ -2,7 +2,7 @@ import { fixture, expect } from '@open-wc/testing';
|
|
|
2
2
|
|
|
3
3
|
import type { Metadata } from '../../lib/element';
|
|
4
4
|
import { createCSSSheet, GemElement, html, createRef } from '../../lib/element';
|
|
5
|
-
import { createStore
|
|
5
|
+
import { createStore } from '../../lib/store';
|
|
6
6
|
import { css } from '../../lib/utils';
|
|
7
7
|
import type { Emitter } from '../../lib/decorators';
|
|
8
8
|
import {
|
|
@@ -81,10 +81,10 @@ describe('装饰器', () => {
|
|
|
81
81
|
rank-count="2"
|
|
82
82
|
></decorator-gem-demo>
|
|
83
83
|
`);
|
|
84
|
-
|
|
84
|
+
store({ a: 1 });
|
|
85
85
|
await Promise.resolve();
|
|
86
86
|
const metadata: Metadata = Reflect.get(DecoratorGemElement, Symbol.metadata);
|
|
87
|
-
expect(metadata.observedStores).to.eql(
|
|
87
|
+
expect({ ...metadata.observedStores.at(0) }).to.eql({ a: 1 });
|
|
88
88
|
expect(metadata.observedAttributes).to.eql(['rank-attr', 'rank-disabled', 'rank-count']);
|
|
89
89
|
expect(metadata.definedEvents).to.eql(['say-hi']);
|
|
90
90
|
expect(metadata.definedCSSStates).to.eql(['open-state']);
|
|
@@ -102,7 +102,7 @@ describe('装饰器', () => {
|
|
|
102
102
|
expect(el.inputRef.ref.startsWith('ref-')).to.equal(true);
|
|
103
103
|
expect(el.bodySlot).to.equal('body-slot');
|
|
104
104
|
expect(el).shadowDom.to.equal('attr: attr, disabled: true, count: 2, prop: prop');
|
|
105
|
-
|
|
105
|
+
store({ a: 3 });
|
|
106
106
|
await Promise.resolve();
|
|
107
107
|
expect(el.renderCount).to.equal(3);
|
|
108
108
|
el.sayHi(2);
|
package/src/test/store.test.ts
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
import { expect, aTimeout } from '@open-wc/testing';
|
|
2
2
|
|
|
3
|
-
import { StoreListenerMap, createStore,
|
|
3
|
+
import { StoreListenerMap, createStore, connect } from '../lib/store';
|
|
4
4
|
|
|
5
5
|
describe('store 测试', () => {
|
|
6
6
|
it('create store', () => {
|
|
7
7
|
const origin = { a: 1 };
|
|
8
8
|
const store = createStore(origin);
|
|
9
|
-
expect(store).to.equal(origin);
|
|
9
|
+
expect({ ...store }).to.deep.equal(origin);
|
|
10
10
|
expect(!!StoreListenerMap.get(store)).to.equal(true);
|
|
11
11
|
});
|
|
12
12
|
it('update store', async () => {
|
|
13
13
|
const store = createStore({ a: 1 });
|
|
14
14
|
let flag = true;
|
|
15
15
|
const update = () => (flag = !flag);
|
|
16
|
-
connect(store, update);
|
|
17
|
-
|
|
16
|
+
const disconnect = connect(store, update);
|
|
17
|
+
store({ a: 1 });
|
|
18
18
|
await aTimeout(0);
|
|
19
19
|
expect(flag).to.equal(false);
|
|
20
|
-
|
|
20
|
+
store();
|
|
21
21
|
await aTimeout(0);
|
|
22
22
|
expect(flag).to.equal(true);
|
|
23
|
-
disconnect(
|
|
24
|
-
|
|
23
|
+
disconnect();
|
|
24
|
+
store({ a: 1 });
|
|
25
25
|
await aTimeout(0);
|
|
26
26
|
expect(flag).to.equal(true);
|
|
27
27
|
});
|
package/src/test/utils.test.ts
CHANGED
|
@@ -112,7 +112,7 @@ describe('utils 测试', () => {
|
|
|
112
112
|
});
|
|
113
113
|
expect(cssSheet.scroll.startsWith('scroll')).to.true;
|
|
114
114
|
const rules = cssSheet[SheetToken].getStyle().cssRules;
|
|
115
|
-
expect(rules.item(0).selectorText).to.equal(':scope, :host');
|
|
115
|
+
expect(rules.item(0).selectorText).to.equal(':where(:scope:not([hidden])), :host(:where(:not([hidden])))');
|
|
116
116
|
expect(rules.item(1).selectorText.startsWith('.scroll')).to.true;
|
|
117
117
|
expect(rules.item(1).style.background).to.equal('red');
|
|
118
118
|
expect(rules.item(1).cssRules.item(0).selectorText).to.equal('&:hover *');
|