@mantou/gem 1.7.6 → 1.7.7

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.
@@ -1,6 +1,6 @@
1
1
  // https://github.com/WICG/navigation-api#stakeholder-feedback
2
2
 
3
- import { createStore, updateStore, connect } from './store';
3
+ import { connect, useStore } 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 store = createStore<HistoryState>({
21
+ const [store, updateHistoryStore] = useStore<HistoryState>({
22
22
  $hasCloseHandle: false,
23
23
  $hasOpenHandle: false,
24
24
  $hasShouldCloseHandle: false,
@@ -85,10 +85,12 @@ function updateHistory(p: UpdateHistoryParams, native: typeof nativeHistory.push
85
85
  $hasOpenHandle: !!open,
86
86
  $hasShouldCloseHandle: !!shouldClose,
87
87
  $key: getKey(),
88
+ $title: title,
88
89
  ...data,
89
90
  };
90
91
  paramsMap.set(state.$key, params);
91
- updateStore(cleanObject(store), state);
92
+ cleanObject(store);
93
+ updateHistoryStore(state);
92
94
  const url = getUrlBarPath(path) + new QueryString(query) + hash;
93
95
  const prevHash = decodeURIComponent(location.hash);
94
96
  native(state, title, url);
@@ -100,12 +102,14 @@ function updateHistoryByNative(data: any, title: string, originUrl: string, nati
100
102
  validData(data);
101
103
  const state = {
102
104
  $key: getKey(),
105
+ $title: title,
103
106
  ...(data || {}),
104
107
  };
105
108
  const { pathname, search, hash } = new URL(originUrl, location.origin + location.pathname);
106
109
  const params = normalizeParams({ path: pathname, query: new QueryString(search), hash, title, data });
107
110
  paramsMap.set(state.$key, params);
108
- updateStore(cleanObject(store), state);
111
+ cleanObject(store);
112
+ updateHistoryStore(state);
109
113
  const url = getUrlBarPath(pathname) + params.query + hash;
110
114
  const prevHash = location.hash;
111
115
  native(state, title, url);
@@ -113,7 +117,7 @@ function updateHistoryByNative(data: any, title: string, originUrl: string, nati
113
117
  if (prevHash !== hash) window.dispatchEvent(new CustomEvent('hashchange'));
114
118
  }
115
119
 
116
- const gemBasePathStore = createStore({
120
+ const [gemBasePathStore, updateBasePathStore] = useStore({
117
121
  basePath: '',
118
122
  });
119
123
 
@@ -126,7 +130,7 @@ class GemHistory {
126
130
  }
127
131
  set basePath(basePath: string) {
128
132
  // 应用初始化的时候设置
129
- updateStore(gemBasePathStore, { basePath });
133
+ updateBasePathStore({ basePath });
130
134
  // paramsMap 更新后 ui 才会更新
131
135
  Object.assign(paramsMap.get(store.$key)!, { path: getInternalPath(location.pathname) });
132
136
  }
@@ -135,7 +139,7 @@ class GemHistory {
135
139
  }
136
140
  updateParams(params: UpdateHistoryParams) {
137
141
  Object.assign(paramsMap.get(store.$key)!, params);
138
- updateStore(store, {});
142
+ updateHistoryStore();
139
143
  }
140
144
  push(params: UpdateHistoryParams) {
141
145
  updateHistory(params, nativePushState);
@@ -162,7 +166,7 @@ class GemHistory {
162
166
  // `<gem-**>` 只读写父应用 `basePath`
163
167
  const gemHistory = new GemHistory();
164
168
 
165
- const gemTitleStore = createStore({ title: '' });
169
+ const [gemTitleStore, updateTitleStore] = useStore({ title: '' });
166
170
 
167
171
  const _GEMHISTORY = { history: gemHistory, titleStore: gemTitleStore, basePathStore: gemBasePathStore };
168
172
 
@@ -195,7 +199,7 @@ if (!window._GEMHISTORY) {
195
199
  const { pathname, search, hash } = location;
196
200
  gemHistory.replace({ path: pathname, query: search, hash });
197
201
  } else if (nativeHistory.state.$hasCloseHandle) {
198
- updateStore(store, nativeHistory.state);
202
+ updateHistoryStore(nativeHistory.state);
199
203
  const params = normalizeParams({ title: document.title });
200
204
  paramsMap.set(store.$key, params);
201
205
  // 有 handle 返回键的页面刷新需要清除返回 handler
@@ -203,7 +207,7 @@ if (!window._GEMHISTORY) {
203
207
  } else {
204
208
  // 有 gem 历史的正常普通刷新, 储存 params
205
209
  const params = normalizeParams({ title: document.title, hash: location.hash });
206
- updateStore(store, {
210
+ updateHistoryStore({
207
211
  $key: getKey(),
208
212
  ...(nativeHistory.state || {}),
209
213
  });
@@ -220,7 +224,7 @@ if (!window._GEMHISTORY) {
220
224
  connect(store, () => {
221
225
  const { title } = gemHistory.getParams();
222
226
  if (title !== gemTitleStore.title) {
223
- updateStore(gemTitleStore, { title });
227
+ updateTitleStore({ title });
224
228
  }
225
229
  });
226
230
 
@@ -251,7 +255,7 @@ if (!window._GEMHISTORY) {
251
255
  path: pathname,
252
256
  query: new QueryString(search),
253
257
  hash,
254
- title: document.title, // 浏览器缓存的 title
258
+ title: newState.$title, // document.title 是导航前的
255
259
  data: newState,
256
260
  });
257
261
  }
@@ -288,7 +292,8 @@ if (!window._GEMHISTORY) {
288
292
  }
289
293
  }
290
294
 
291
- updateStore(cleanObject(store), newState);
295
+ cleanObject(store);
296
+ updateHistoryStore(newState);
292
297
  });
293
298
  }
294
299
 
package/src/lib/store.ts CHANGED
@@ -13,32 +13,22 @@ export function createStore<T extends NonPrimitive>(originStore: T) {
13
13
  return originStore as Store<T>;
14
14
  }
15
15
 
16
- interface StoreObjectSet {
17
- [store: string]: NonPrimitive;
18
- }
19
- export type StoreSet<T> = {
20
- [P in keyof T]: T[P] & Store<T[P]>;
21
- };
22
- export function createStoreSet<T extends StoreObjectSet>(originStoreSet: T) {
23
- const keys = Object.keys(originStoreSet);
24
- keys.forEach((key) => {
25
- createStore(originStoreSet[key]);
26
- });
27
-
28
- return originStoreSet as StoreSet<T>;
29
- }
30
-
31
16
  export function updateStore<T extends NonPrimitive>(store: Store<T>, value?: Partial<T>) {
32
17
  Object.assign(store, value);
33
- const listeners = StoreListenerMap.get(store);
34
- listeners?.forEach((func) => {
18
+ StoreListenerMap.get(store)?.forEach((func) => {
35
19
  addMicrotask(func);
36
20
  });
37
21
  }
38
22
 
23
+ export function useStore<T extends NonPrimitive>(originStore: T) {
24
+ const store = createStore(originStore);
25
+ return [store, (value?: Partial<T>) => updateStore(store, value)] as const;
26
+ }
27
+
39
28
  export function connect<T extends NonPrimitive>(store: Store<T>, func: () => void) {
40
29
  const listeners = StoreListenerMap.get(store);
41
30
  listeners?.add(func);
31
+ return () => listeners?.delete(func);
42
32
  }
43
33
 
44
34
  export function disconnect<T extends NonPrimitive>(store: Store<T>, func: () => void) {
@@ -1 +1 @@
1
- export const version = '1.7.6';
1
+ export const version = '1.7.7';
@@ -1,3 +1,11 @@
1
+ /**
2
+ * 测试 HTMLElement 没有的特性或者不常用的特性
3
+ * - 异步渲染
4
+ * - Light DOM 自定义元素
5
+ * - 生命周期以及 Effect/Memo
6
+ * - 派生元素(类扩展)
7
+ * - 无渲染内容 Gem 元素
8
+ */
1
9
  import { fixture, expect, nextFrame } from '@open-wc/testing';
2
10
 
3
11
  import { GemElement, html } from '../../lib/element';
@@ -120,7 +128,10 @@ describe('gem element 生命周期', () => {
120
128
  const el = container.firstElementChild as LifecycleGemElement;
121
129
  expect(el.appTitle).to.equal('title');
122
130
  expect(el.renderCount).to.equal(1);
123
- expect((el.cloneNode() as LifecycleGemElement).appTitle).to.equal('title');
131
+ const clone = el.cloneNode() as LifecycleGemElement;
132
+ // BUG
133
+ clone.connectedCallback();
134
+ expect(clone.appTitle).to.equal('title');
124
135
 
125
136
  const el2 = new LifecycleGemElement('', '2');
126
137
  expect(el2.appTitle).to.equal('');
@@ -51,20 +51,24 @@ class DecoratorGemElement extends GemElement {
51
51
  }
52
52
  }
53
53
 
54
- @connectStore(store)
55
- @adoptedStyle(styles)
56
- @customElement('decorator-gem-demo2')
57
- class DecoratorGemElement2 extends GemElement {
58
- static observedStores = [];
59
- static adoptedStyleSheets = [];
60
- renderCount = 0;
61
- render() {
62
- this.renderCount++;
63
- return html``;
64
- }
65
- }
66
-
67
54
  describe('装饰器', () => {
55
+ it('使用装饰器定义的未插入文档元素', async () => {
56
+ const el = new DecoratorGemElement();
57
+ expect(el.propData).to.eql({ value: '' });
58
+ expect(el.getAttribute('rank-attr')).to.equal(null);
59
+ // BUG
60
+ expect(el.rankAttr).to.equal(undefined);
61
+ el.connectedCallback();
62
+ expect(el.getAttribute('rank-attr')).to.equal(null);
63
+ expect(el.rankAttr).to.equal('');
64
+
65
+ el.rankAttr = 'attr';
66
+ el.propData = { value: '1' };
67
+ const el2 = el.cloneNode() as DecoratorGemElement;
68
+ expect(el2.getAttribute('rank-attr')).to.equal('attr');
69
+ expect(el2.rankAttr).to.equal('attr');
70
+ expect(el2.propData).to.eql({ value: '' });
71
+ });
68
72
  it('装饰器定义的自定义元素', async () => {
69
73
  let a = 1;
70
74
  const el: DecoratorGemElement = await fixture(html`
@@ -104,12 +108,4 @@ describe('装饰器', () => {
104
108
  expect(el.renderCount).to.equal(3);
105
109
  expect(a).to.equal(2);
106
110
  });
107
-
108
- it('装饰器和静态属性共存', async () => {
109
- const el: DecoratorGemElement2 = await fixture(html`<decorator-gem-demo2></decorator-gem-demo2>`);
110
- updateStore(store, { a: 3 });
111
- await Promise.resolve();
112
- expect(el.renderCount).to.equal(2);
113
- expect(el.shadowRoot?.adoptedStyleSheets.length).to.equal(1);
114
- });
115
111
  });
@@ -1,12 +1,13 @@
1
1
  import { expect, fixture } from '@open-wc/testing';
2
2
 
3
- import { customElement, property, RefObject, refobject } from '../../lib/decorators';
3
+ import { attribute, customElement, property, RefObject, refobject } from '../../lib/decorators';
4
4
  import { GemElement, html } from '../../lib/element';
5
5
 
6
6
  @customElement('app-children')
7
7
  export class Children extends GemElement {
8
8
  @refobject inputRef: RefObject<HTMLInputElement>;
9
9
  @property value?: { value: number };
10
+ @attribute attr: string;
10
11
 
11
12
  render() {
12
13
  return html`<input ref=${this.inputRef.ref} />`;
@@ -19,13 +20,13 @@ export class App extends GemElement {
19
20
  @refobject childrenRef2: RefObject<Children>;
20
21
  render() {
21
22
  return html`
22
- <app-children ref=${this.childrenRef1.ref} .value=${{ value: 1 }}></app-children>
23
- <app-children ref=${this.childrenRef2.ref} .value=${{ value: 2 }}></app-children>
23
+ <app-children ref=${this.childrenRef1.ref} .value=${{ value: 1 }} attr="1"></app-children>
24
+ <app-children ref=${this.childrenRef2.ref} .value=${{ value: 2 }} attr="2"></app-children>
24
25
  `;
25
26
  }
26
27
  }
27
- describe('多个 gem element', () => {
28
- it('ref & prop', async () => {
28
+ describe('多个 gem element 一起工作', () => {
29
+ it('ref & prop & attr', async () => {
29
30
  const el: App = await fixture(html`<app-root></app-root>`);
30
31
  const children1 = el.childrenRef1.element!;
31
32
  const children2 = el.childrenRef2.element!;
@@ -34,5 +35,7 @@ describe('多个 gem element', () => {
34
35
  expect(input1 !== input2).to.equal(true);
35
36
  expect(children1.value).to.eql({ value: 1 });
36
37
  expect(children2.value).to.eql({ value: 2 });
38
+ expect(children1.attr).to.eql('1');
39
+ expect(children2.attr).to.eql('2');
37
40
  });
38
41
  });
@@ -1,6 +1,6 @@
1
1
  import { expect, aTimeout } from '@open-wc/testing';
2
2
 
3
- import { StoreListenerMap, createStore, createStoreSet, updateStore, connect, disconnect } from '../lib/store';
3
+ import { StoreListenerMap, createStore, updateStore, connect, disconnect } from '../lib/store';
4
4
 
5
5
  describe('store 测试', () => {
6
6
  it('create store', () => {
@@ -8,8 +8,6 @@ describe('store 测试', () => {
8
8
  const store = createStore(origin);
9
9
  expect(store).to.equal(origin);
10
10
  expect(!!StoreListenerMap.get(store)).to.equal(true);
11
- const stores = createStoreSet({ a: {} });
12
- expect(!!StoreListenerMap.get(stores.a)).to.equal(true);
13
11
  });
14
12
  it('update store', async () => {
15
13
  const store = createStore({ a: 1 });