@mantou/gem 1.5.5 → 1.6.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.
@@ -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 window.matchMedia(this.PRINT).matches;
9
+ return matche(this.PRINT);
8
10
  },
9
11
  HOVER: '(hover: hover)',
10
12
  get isHover() {
11
- return window.matchMedia(this.HOVER).matches;
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 window.matchMedia(this.PHONE).matches;
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 window.matchMedia(this.LAPTOP).matches;
23
+ return matche(this.LAPTOP);
22
24
  },
23
25
 
24
26
  DESKTOP: '(min-width: 1279px) and (orientation: landscape)',
25
27
  get isDesktop() {
26
- return window.matchMedia(this.DESKTOP).matches;
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 window.matchMedia(this.PHONE_LANDSCAPE).matches;
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 window.matchMedia(this.TABLET).matches;
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 window.matchMedia(this.SMALL_PHONE).matches;
49
+ return matche(this.SMALL_PHONE);
43
50
  },
44
51
 
45
52
  WATCH: '(max-width: 319px)',
46
53
  get isWatch() {
47
- return window.matchMedia(this.WATCH).matches;
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 window.matchMedia(this.SHORT).matches;
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 window.matchMedia(this.PWA).matches;
65
+ return matche(this.PWA);
59
66
  },
60
67
 
61
68
  MOTION_REDUCE: '(prefers-reduced-motion: reduce)',
62
69
  get isMotionReduce() {
63
- return window.matchMedia(this.MOTION_REDUCE).matches;
70
+ return matche(this.MOTION_REDUCE);
64
71
  },
65
72
 
66
73
  DATA_REDUCE: '(prefers-reduced-data: reduce)',
67
74
  get isDataReduce() {
68
- return window.matchMedia(this.DATA_REDUCE).matches;
75
+ return matche(this.DATA_REDUCE);
69
76
  },
70
77
  };
@@ -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>;
@@ -12,6 +12,7 @@ import {
12
12
  kebabToCamelCase,
13
13
  PropProxyMap,
14
14
  removeItems,
15
+ useNativeCSSStyleSheet,
15
16
  } from './utils';
16
17
 
17
18
  export { html, svg, render, directive, TemplateResult, SVGTemplateResult } from 'lit-html';
@@ -52,20 +53,15 @@ function execCallback(fun: any) {
52
53
 
53
54
  // global render task pool
54
55
  const asyncRenderTaskList = new LinkedList<() => void>();
55
- const tick = () => {
56
- window.requestAnimationFrame(function callback(timestamp) {
57
- if (performance.now() > timestamp + 16) {
58
- tick();
59
- return;
60
- }
61
- const task = asyncRenderTaskList.get();
62
- if (task) {
63
- task();
64
- callback(timestamp);
65
- }
66
- });
56
+ const tick = (timeStamp = performance.now()) => {
57
+ if (performance.now() > timeStamp + 16) return requestAnimationFrame(tick);
58
+ const task = asyncRenderTaskList.get();
59
+ if (task) {
60
+ task();
61
+ tick(timeStamp);
62
+ }
67
63
  };
68
- asyncRenderTaskList.addEventListener('start', tick);
64
+ asyncRenderTaskList.addEventListener('start', () => addMicrotask(tick));
69
65
 
70
66
  type GetDepFun<T> = () => T;
71
67
  type EffectCallback<T> = (depValues: T, oldDepValues?: T) => any;
@@ -80,7 +76,7 @@ type EffectItem<T> = {
80
76
  const initSymbol = Symbol('init');
81
77
  const updateSymbol = Symbol('update');
82
78
 
83
- interface GemElementOptions {
79
+ export interface GemElementOptions {
84
80
  isLight?: boolean;
85
81
  isAsync?: boolean;
86
82
  delegatesFocus?: boolean;
@@ -153,11 +149,13 @@ export abstract class GemElement<T = Record<string, unknown>> extends HTMLElemen
153
149
  if (!this.#internals) {
154
150
  const getCustomStateSet = () => {
155
151
  // https://wicg.github.io/custom-state-pseudo-class/
156
- const { classList } = this;
152
+ const getV = (v: string) => v.replace('--', '');
157
153
  return {
158
- has: (v) => classList.contains(v),
159
- add: (v) => classList.add(v),
160
- delete: (v) => classList.remove(v),
154
+ has: (v) => getV(v) in this.dataset,
155
+ add: (v) => {
156
+ this.dataset[getV(v)] = '';
157
+ },
158
+ delete: (v) => delete this.dataset[getV(v)],
161
159
  } as CustomStateSet;
162
160
  };
163
161
  if (!this.attachInternals) {
@@ -197,7 +195,7 @@ export abstract class GemElement<T = Record<string, unknown>> extends HTMLElemen
197
195
  list?.forEach((effectItem) => {
198
196
  const { callback, getDep, values, preCallback } = effectItem;
199
197
  const newValues = getDep?.();
200
- if (!getDep || isArrayChange(values, newValues)) {
198
+ if (!getDep || !values || isArrayChange(values, newValues)) {
201
199
  execCallback(preCallback);
202
200
  effectItem.preCallback = callback(newValues, values);
203
201
  effectItem.values = newValues;
@@ -234,15 +232,17 @@ export abstract class GemElement<T = Record<string, unknown>> extends HTMLElemen
234
232
  getDep?: T extends any[] ? () => [...T] : undefined,
235
233
  ) => {
236
234
  if (!this.#effectList) this.#effectList = [];
237
- const values = getDep?.() as T;
238
235
  const effectItem: EffectItem<T> = {
239
236
  callback,
240
237
  getDep,
241
- values,
238
+ values: undefined,
242
239
  initialized: !!this.isConnected,
243
240
  };
244
241
  // 以挂载时立即执行副作用,未挂载时等挂载后执行
245
- if (this.isConnected) effectItem.preCallback = callback(values);
242
+ if (this.isConnected) {
243
+ effectItem.values = getDep?.() as T;
244
+ effectItem.preCallback = callback(effectItem.values);
245
+ }
246
246
  this.#effectList.push(effectItem);
247
247
  };
248
248
 
@@ -278,7 +278,8 @@ export abstract class GemElement<T = Record<string, unknown>> extends HTMLElemen
278
278
  this.#effectList?.forEach((effectItem) => {
279
279
  const { callback, getDep, initialized } = effectItem;
280
280
  if (!initialized) {
281
- effectItem.preCallback = callback(getDep?.());
281
+ effectItem.values = getDep?.();
282
+ effectItem.preCallback = callback(effectItem.values);
282
283
  effectItem.initialized = true;
283
284
  }
284
285
  });
@@ -296,8 +297,18 @@ export abstract class GemElement<T = Record<string, unknown>> extends HTMLElemen
296
297
 
297
298
  #render = () => {
298
299
  this.#execMemo();
299
- if (this.render) return this.render();
300
- return this.#renderRoot === this ? undefined : html`<slot></slot>`;
300
+ const styles = useNativeCSSStyleSheet
301
+ ? ''
302
+ : html`${this.shadowRoot?.adoptedStyleSheets?.map(
303
+ (e: any) =>
304
+ html`
305
+ <style media=${e.media.mediaText}>
306
+ ${e.style}
307
+ </style>
308
+ `,
309
+ )}`;
310
+ if (this.render) return html`${this.render()}${styles}`;
311
+ return this.#renderRoot === this ? undefined : html`<slot></slot>${styles}`;
301
312
  };
302
313
 
303
314
  /**@lifecycle */
@@ -377,14 +388,16 @@ export abstract class GemElement<T = Record<string, unknown>> extends HTMLElemen
377
388
  }
378
389
  };
379
390
 
380
- closestElement(clsOrName: CustomElementConstructor | string) {
381
- const isConstructor = typeof clsOrName === 'function';
382
- const tagName = typeof clsOrName === 'string' && clsOrName.toUpperCase();
391
+ closestElement<K extends keyof HTMLElementTagNameMap>(tag: K): HTMLElementTagNameMap[K] | null;
392
+ closestElement<K extends abstract new (...args: any) => any>(constructor: K): InstanceType<K> | null;
393
+ closestElement<K extends abstract new (...args: any) => any>(constructorOrTag: K | string): Element | null {
394
+ const isConstructor = typeof constructorOrTag === 'function';
395
+ const tagName = typeof constructorOrTag === 'string' && constructorOrTag.toUpperCase();
383
396
  const getRootElement = (ele: Element): Element | null => {
384
397
  const rootEle = ele.parentElement || (ele.getRootNode() as ShadowRoot).host;
385
398
  if (!rootEle) return null;
386
399
  if (isConstructor) {
387
- if (rootEle.constructor === clsOrName) {
400
+ if (rootEle.constructor === constructorOrTag) {
388
401
  return rootEle;
389
402
  }
390
403
  } else if (rootEle.tagName === tagName) {
package/src/lib/utils.ts CHANGED
@@ -84,6 +84,7 @@ export class LinkedList<T = any> extends (globalThis.EventTarget || Object) {
84
84
  }
85
85
 
86
86
  // 添加到尾部,已存在时会删除老的项目
87
+ // 如果是添加第一个,start 事件会在添加前触发,避免处理事件重复的逻辑
87
88
  add(value: T) {
88
89
  if (!this.#lastItem) {
89
90
  this.dispatchEvent(new CustomEvent('start'));
@@ -101,6 +102,7 @@ export class LinkedList<T = any> extends (globalThis.EventTarget || Object) {
101
102
  this.#map.set(value, item);
102
103
  }
103
104
 
105
+ // 删除这个元素后没有其他元素时立即出发 end 事件
104
106
  delete(value: T) {
105
107
  const deleteItem = this.#delete(value);
106
108
  if (!this.#firstItem) {
@@ -238,9 +240,22 @@ export interface StyledKeyValuePair {
238
240
  [key: string]: StyledValueObject;
239
241
  }
240
242
 
243
+ // !!! 目前只有 Chrome/FirefoxNightly 支持
244
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1520690
245
+ // https://bugs.webkit.org/show_bug.cgi?id=228684
246
+ export let useNativeCSSStyleSheet = true;
247
+ try {
248
+ new (CSSStyleSheet as any)();
249
+ } catch {
250
+ useNativeCSSStyleSheet = false;
251
+ window.CSSStyleSheet = class {
252
+ media = { mediaText: '' };
253
+ style = '';
254
+ replaceSync = (style: string) => (this.style = style);
255
+ } as any;
256
+ }
257
+
241
258
  /**
242
- * !!! 目前只有 Chrome/FirefoxNightly 支持
243
- * https://bugzilla.mozilla.org/show_bug.cgi?id=1520690
244
259
  *
245
260
  * 创建 style sheet 用于 `adoptedStyleSheets`,不支持样式更新
246
261
  * @param rules string | Record<string, string> 不能动态更新的 css
@@ -1,2 +1,2 @@
1
1
  // Do not modify manually
2
- export const version = '1.5.5';
2
+ export const version = '1.6.0';
@@ -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(1);
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
- () => [this.attr, this.prop],
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 = {};