@mantou/gem 1.7.11 → 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.
@@ -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/utils.ts CHANGED
@@ -257,21 +257,29 @@ export interface StyledKeyValuePair {
257
257
 
258
258
  /**
259
259
  *
260
- * 创建 style sheet 用于 `adoptedStyleSheets`,不支持样式更新
261
- * @param rules string | Record<string, string> 不能动态更新的 css
262
- * @param mediaQuery string 媒体查询 StyleSheet.media.mediaText
260
+ * 创建 style sheet 用于 `adoptedStyleSheets`,不支持样式更新,只支持自定义 CSS 属性
261
+ * @param rules string | Record<string, string>
262
+ * @param media string 媒体查询
263
263
  */
264
- export function createCSSSheet<T extends StyledKeyValuePair>(rules: T | string, mediaQuery = ''): Sheet<T> {
265
- const styleSheet = new CSSStyleSheet();
264
+ export function createCSSSheet<T extends StyledKeyValuePair>(rules: T | string, media?: string): Sheet<T> {
265
+ const styleSheet = new CSSStyleSheet({ media });
266
266
  const sheet: any = {};
267
- styleSheet.media.mediaText = mediaQuery;
268
267
  let style = '';
269
268
  if (typeof rules === 'string') {
270
269
  style = rules;
271
270
  } else {
272
271
  Object.keys(rules).forEach((key: keyof T) => {
273
272
  sheet[key] = `${key as string}-${randomStr()}`;
274
- style += rules[key].styledContent.replace(/&/g, sheet[key]);
273
+ switch (rules[key].type) {
274
+ case 'class':
275
+ style += `.${sheet[key]} {${rules[key].styledContent}}`;
276
+ break;
277
+ case 'id':
278
+ style += `#${sheet[key]} {${rules[key].styledContent}}`;
279
+ break;
280
+ default:
281
+ style += `@keyframes ${key as string} {${rules[key].styledContent}}`;
282
+ }
275
283
  });
276
284
  }
277
285
  styleSheet.replaceSync(style);
@@ -285,24 +293,7 @@ export function randomStr(len = 5): string {
285
293
  return str;
286
294
  }
287
295
 
288
- const nestingRuleRegExp = new RegExp('&.*{((.|\n)*)}', 'g');
289
- // 只支持一层嵌套
290
- // https://drafts.csswg.org/css-nesting-1/
291
- // https://bugzilla.mozilla.org/show_bug.cgi?id=1648037
292
- // https://bugs.webkit.org/show_bug.cgi?id=223497
293
- function flatStyled(style: string, type: StyledType): StyledValueObject {
294
- const nestingRules: string[] = [];
295
- let styledContent =
296
- `& {${style.replace(nestingRuleRegExp, (substr) => {
297
- nestingRules.push(substr);
298
- return '';
299
- })}}` + nestingRules.join('');
300
- styledContent = styledContent.replace(/&/g, type === 'class' ? '.&' : '#&');
301
- return { styledContent, type };
302
- }
303
-
304
296
  // 写 css 文本,在 CSSStyleSheet 中使用,使用 styed-components 高亮
305
- // 暂时不支持 `at` 规则
306
297
  //
307
298
  // createCSSSheet({
308
299
  // red: styled.class`
@@ -313,18 +304,14 @@ function flatStyled(style: string, type: StyledType): StyledValueObject {
313
304
  // `,
314
305
  // });
315
306
  export const styled = {
316
- class: (arr: TemplateStringsArray, ...args: any[]) => {
317
- const style = raw(arr, ...args);
318
- return flatStyled(style, 'class');
307
+ class: (arr: TemplateStringsArray, ...args: any[]): StyledValueObject => {
308
+ return { styledContent: raw(arr, ...args), type: 'class' };
319
309
  },
320
- id: (arr: TemplateStringsArray, ...args: any[]) => {
321
- const style = raw(arr, ...args);
322
- return flatStyled(style, 'id');
310
+ id: (arr: TemplateStringsArray, ...args: any[]): StyledValueObject => {
311
+ return { styledContent: raw(arr, ...args), type: 'id' };
323
312
  },
324
313
  keyframes: (arr: TemplateStringsArray, ...args: any[]): StyledValueObject => {
325
- const keyframesContent = raw(arr, ...args);
326
- const styledContent = `@keyframes & {${keyframesContent}}`;
327
- return { styledContent, type: 'keyframes' };
314
+ return { styledContent: raw(arr, ...args), type: 'keyframes' };
328
315
  },
329
316
  };
330
317
 
@@ -385,7 +372,13 @@ export function objectMapToString<T = any>(
385
372
  }
386
373
 
387
374
  // Wait: Typescript lib dom CSSStyleDeclaration anchor position
388
- type StyleProp = keyof CSSStyleDeclaration | `--${string}` | 'anchorDefault' | 'anchorName';
375
+ type StyleProp =
376
+ | keyof CSSStyleDeclaration
377
+ | `--${string}`
378
+ | 'scrollbarWidth'
379
+ | 'scrollbarColor'
380
+ | 'anchorDefault'
381
+ | 'anchorName';
389
382
 
390
383
  export type StyleObject = Partial<Record<StyleProp, string | number | false | undefined | null>>;
391
384
 
@@ -1 +1 @@
1
- export const version = '1.7.11';
1
+ export const version = '1.7.12';
@@ -16,6 +16,7 @@ import {
16
16
 
17
17
  declare global {
18
18
  interface CSSRuleList {
19
+ // only test style rule
19
20
  item(index: number): CSSStyleRule;
20
21
  }
21
22
  }
@@ -106,15 +107,22 @@ describe('utils 测试', () => {
106
107
  `,
107
108
  wrap: styled.id`
108
109
  position: fixed;
110
+ animation: 3s infinite alternate slideIn;
111
+ `,
112
+ slideIn: styled.keyframes`
113
+ from {
114
+ transform: translateX(0%);
115
+ }
109
116
  `,
110
117
  });
111
118
  expect(cssSheet.scroll.startsWith('scroll')).to.true;
112
119
  const rules = cssSheet[SheetToken].cssRules;
113
120
  expect(rules.item(0).selectorText.startsWith('.scroll')).to.true;
114
- expect(rules.item(0).style.background.startsWith('red')).to.true;
115
- expect(/\.scroll(-|\w)+:hover \*/.test(rules.item(1).selectorText)).to.true;
116
- expect(rules.item(1).style.background.startsWith('blue')).to.true;
117
- expect(rules.item(2).selectorText.startsWith('#wrap')).to.true;
121
+ expect(rules.item(0).style.background).to.equal('red');
122
+ expect(rules.item(0).cssRules.item(0).selectorText).to.equal('&:hover *');
123
+ expect(rules.item(0).cssRules.item(0).style.background).to.equal('blue');
124
+ expect(rules.item(1).selectorText.startsWith('#wrap')).to.true;
125
+ expect((rules.item(2) as any).name).to.equal('slideIn');
118
126
  });
119
127
  it('styleMap/classMap/exportPartsMap', () => {
120
128
  expect(styleMap({ '--x': '1px', fontSize: '14px', content: `'*'` })).to.equal(