@mantou/gem 1.6.4 → 1.6.10

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.
Files changed (158) hide show
  1. package/README.md +6 -38
  2. package/dist/gem.es.js +3260 -0
  3. package/dist/gem.umd.js +229 -0
  4. package/elements/{dialog-base.d.ts → base/dialog.d.ts} +4 -4
  5. package/elements/base/dialog.d.ts.map +1 -0
  6. package/elements/{dialog-base.js → base/dialog.js} +26 -28
  7. package/elements/base/dialog.js.map +1 -0
  8. package/elements/base/gesture.d.ts +66 -0
  9. package/elements/base/gesture.d.ts.map +1 -0
  10. package/elements/base/gesture.js +214 -0
  11. package/elements/base/gesture.js.map +1 -0
  12. package/elements/base/link.d.ts +44 -0
  13. package/elements/base/link.d.ts.map +1 -0
  14. package/elements/base/link.js +206 -0
  15. package/elements/base/link.js.map +1 -0
  16. package/elements/{modal-base.d.ts → base/modal-factory.d.ts} +20 -20
  17. package/elements/base/modal-factory.d.ts.map +1 -0
  18. package/elements/{modal-base.js → base/modal-factory.js} +10 -7
  19. package/elements/base/modal-factory.js.map +1 -0
  20. package/elements/base/reflect.d.ts +6 -0
  21. package/elements/base/reflect.d.ts.map +1 -0
  22. package/elements/base/reflect.js +33 -0
  23. package/elements/base/reflect.js.map +1 -0
  24. package/elements/base/route.d.ts +78 -0
  25. package/elements/base/route.d.ts.map +1 -0
  26. package/elements/base/route.js +220 -0
  27. package/elements/base/route.js.map +1 -0
  28. package/elements/base/title.d.ts +30 -0
  29. package/elements/base/title.d.ts.map +1 -0
  30. package/elements/base/title.js +74 -0
  31. package/elements/base/title.js.map +1 -0
  32. package/elements/base/unsafe.d.ts +14 -0
  33. package/elements/base/unsafe.d.ts.map +1 -0
  34. package/elements/base/unsafe.js +42 -0
  35. package/elements/base/unsafe.js.map +1 -0
  36. package/elements/base/use.d.ts +19 -0
  37. package/elements/base/use.d.ts.map +1 -0
  38. package/elements/base/use.js +70 -0
  39. package/elements/base/use.js.map +1 -0
  40. package/elements/gesture.d.ts +1 -67
  41. package/elements/gesture.d.ts.map +1 -1
  42. package/elements/gesture.js +3 -219
  43. package/elements/gesture.js.map +1 -1
  44. package/elements/link.d.ts +1 -45
  45. package/elements/link.d.ts.map +1 -1
  46. package/elements/link.js +4 -215
  47. package/elements/link.js.map +1 -1
  48. package/elements/reflect.d.ts +1 -8
  49. package/elements/reflect.d.ts.map +1 -1
  50. package/elements/reflect.js +3 -39
  51. package/elements/reflect.js.map +1 -1
  52. package/elements/route.d.ts +1 -81
  53. package/elements/route.d.ts.map +1 -1
  54. package/elements/route.js +4 -225
  55. package/elements/route.js.map +1 -1
  56. package/elements/title.d.ts +1 -30
  57. package/elements/title.d.ts.map +1 -1
  58. package/elements/title.js +3 -75
  59. package/elements/title.js.map +1 -1
  60. package/elements/unsafe.d.ts +1 -14
  61. package/elements/unsafe.d.ts.map +1 -1
  62. package/elements/unsafe.js +3 -46
  63. package/elements/unsafe.js.map +1 -1
  64. package/elements/use.d.ts +1 -19
  65. package/elements/use.d.ts.map +1 -1
  66. package/elements/use.js +3 -74
  67. package/elements/use.js.map +1 -1
  68. package/helper/i18n.js +47 -58
  69. package/helper/i18n.js.map +1 -1
  70. package/helper/request.js +11 -19
  71. package/helper/request.js.map +1 -1
  72. package/index.d.ts +5 -4
  73. package/index.d.ts.map +1 -1
  74. package/index.js +4 -2
  75. package/index.js.map +1 -1
  76. package/lib/element.d.ts.map +1 -1
  77. package/lib/element.js +1 -1
  78. package/lib/element.js.map +1 -1
  79. package/lib/history.d.ts +28 -16
  80. package/lib/history.d.ts.map +1 -1
  81. package/lib/history.js +179 -169
  82. package/lib/history.js.map +1 -1
  83. package/lib/utils.d.ts +3 -1
  84. package/lib/utils.d.ts.map +1 -1
  85. package/lib/utils.js +7 -5
  86. package/lib/utils.js.map +1 -1
  87. package/lib/version.d.ts +1 -1
  88. package/lib/version.js +1 -1
  89. package/package.json +25 -53
  90. package/src/{umd.ts → dist.ts} +4 -5
  91. package/src/elements/{dialog-base.ts → base/dialog.ts} +6 -7
  92. package/src/elements/base/gesture.ts +256 -0
  93. package/src/elements/base/link.ts +178 -0
  94. package/src/elements/{modal-base.ts → base/modal-factory.ts} +3 -3
  95. package/src/elements/base/reflect.ts +29 -0
  96. package/src/elements/base/route.ts +244 -0
  97. package/src/elements/base/title.ts +68 -0
  98. package/src/elements/base/unsafe.ts +37 -0
  99. package/src/elements/base/use.ts +57 -0
  100. package/src/elements/gesture.ts +3 -257
  101. package/src/elements/link.ts +4 -179
  102. package/src/elements/reflect.ts +3 -31
  103. package/src/elements/route.ts +4 -248
  104. package/src/elements/title.ts +3 -68
  105. package/src/elements/unsafe.ts +3 -37
  106. package/src/elements/use.ts +3 -57
  107. package/src/index.ts +4 -6
  108. package/src/lib/element.ts +2 -1
  109. package/src/lib/history.ts +183 -195
  110. package/src/lib/utils.ts +11 -5
  111. package/src/lib/version.ts +1 -1
  112. package/src/test/gem-element/advance.test.ts +1 -0
  113. package/src/test/gem-element/basic.test.ts +1 -0
  114. package/src/test/gem-element/decorators.test.ts +1 -0
  115. package/src/test/gem-element/multi.test.ts +1 -0
  116. package/src/test/history.test.ts +16 -10
  117. package/src/test/store.test.ts +1 -0
  118. package/src/test/utils.test.ts +1 -0
  119. package/elements/dialog-base.d.ts.map +0 -1
  120. package/elements/dialog-base.js.map +0 -1
  121. package/elements/modal-base.d.ts.map +0 -1
  122. package/elements/modal-base.js.map +0 -1
  123. package/src/examples/base-path/index.ts +0 -44
  124. package/src/examples/controlled/index.ts +0 -75
  125. package/src/examples/decorator/chidren.ts +0 -77
  126. package/src/examples/decorator/index.ts +0 -110
  127. package/src/examples/dialog/index.ts +0 -148
  128. package/src/examples/effect/index.ts +0 -45
  129. package/src/examples/elements/env.ts +0 -7
  130. package/src/examples/elements/layout.ts +0 -53
  131. package/src/examples/elements/nav.ts +0 -71
  132. package/src/examples/elements/source.ts +0 -68
  133. package/src/examples/gesture/canvas.ts +0 -69
  134. package/src/examples/gesture/index.ts +0 -97
  135. package/src/examples/gesture/manifest.json +0 -3
  136. package/src/examples/hash/index.ts +0 -55
  137. package/src/examples/i18n/index.ts +0 -63
  138. package/src/examples/index/index.ts +0 -33
  139. package/src/examples/index/manifest.json +0 -5
  140. package/src/examples/light-dom/index.ts +0 -31
  141. package/src/examples/multi-page/api.ts +0 -9
  142. package/src/examples/multi-page/index.ts +0 -78
  143. package/src/examples/multi-page/page-a.ts +0 -16
  144. package/src/examples/multi-page/page-b.ts +0 -12
  145. package/src/examples/multi-page/page-c.ts +0 -40
  146. package/src/examples/multi-page/store.ts +0 -10
  147. package/src/examples/perf-demo/index.ts +0 -136
  148. package/src/examples/ref-route/index.ts +0 -109
  149. package/src/examples/reflect/index.ts +0 -61
  150. package/src/examples/styled/index.ts +0 -39
  151. package/src/examples/svg-icon/index.ts +0 -31
  152. package/src/examples/theme/index.ts +0 -56
  153. package/src/examples/unsafe/index.ts +0 -29
  154. package/umd.d.ts +0 -18
  155. package/umd.d.ts.map +0 -1
  156. package/umd.js +0 -54
  157. package/umd.js.LICENSE.txt +0 -13
  158. package/umd.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mantou/gem",
3
- "version": "1.6.4",
3
+ "version": "1.6.10",
4
4
  "description": "💎 使用自定义元素的轻量级 WebApp 开发框架",
5
5
  "main": "index.js",
6
6
  "module": "index.js",
@@ -12,7 +12,7 @@
12
12
  },
13
13
  "files": [
14
14
  "/index.*",
15
- "/umd.*",
15
+ "/dist/",
16
16
  "/elements/",
17
17
  "/lib/",
18
18
  "/helper/",
@@ -20,23 +20,13 @@
20
20
  ],
21
21
  "typings": "index.d.ts",
22
22
  "scripts": {
23
- "docs": "npx gem-book docs",
24
- "build:docs": "yarn docs -- --build",
25
- "lint": "tsc --noEmit && eslint --ext .ts --fix src/",
26
- "clean": "node -e \"fs.readdirSync('src').map(dir => require('rimraf').sync(dir))\"",
27
- "example": "cross-env NODE_ENV=development webpack serve",
28
- "build:examples": "rimraf build && node scripts/build-examples",
29
- "build:module": "yarn clean && tsc",
30
- "build:umd": "cross-env NODE_ENV=production webpack --config webpack.config.umd.js",
31
- "build": "yarn build:module && yarn build:umd",
32
- "prepare": "yarn build",
33
- "prepublishOnly": "yarn lint && yarn test",
34
- "release": "node scripts/release",
35
- "test": "yarn build:module && karma start --coverage"
36
- },
37
- "repository": {
38
- "type": "git",
39
- "url": "git+https://github.com/mantou132/gem.git"
23
+ "docs": "gem-book docs",
24
+ "build:docs": "yarn docs --build",
25
+ "build:module": "tsc -p tsconfig.build.json",
26
+ "build:lib": "vite build",
27
+ "build": "yarn build:module && yarn build:lib",
28
+ "start": "yarn build:module --watch",
29
+ "test": "wtr"
40
30
  },
41
31
  "keywords": [
42
32
  "web",
@@ -44,46 +34,28 @@
44
34
  "global state management",
45
35
  "web framework"
46
36
  ],
47
- "author": "mantou132",
48
- "license": "ISC",
49
- "bugs": {
50
- "url": "https://github.com/mantou132/gem/issues"
51
- },
52
- "homepage": "https://github.com/mantou132/gem#readme",
53
37
  "dependencies": {
54
38
  "lit-html": "^1.4.1"
55
39
  },
56
40
  "devDependencies": {
41
+ "@esm-bundle/chai": "^4.3.4-fix.0",
42
+ "@gemjs/config": "^1.6.10",
57
43
  "@open-wc/testing": "^2.5.33",
58
- "@open-wc/testing-karma": "^4.0.9",
59
- "@open-wc/testing-karma-bs": "^1.3.94",
60
- "@typescript-eslint/eslint-plugin": "^4.25.0",
61
- "@typescript-eslint/parser": "^4.25.0",
62
- "cross-env": "^7.0.3",
63
- "eslint": "^7.27.0",
64
- "eslint-config-prettier": "^8.3.0",
65
- "eslint-plugin-prettier": "^3.4.0",
66
- "html-webpack-plugin": "^5.3.1",
67
- "husky": "^6.0.0",
68
- "inquirer": "^8.1.0",
69
- "lint-staged": "^11.0.0",
70
- "ora": "^5.4.0",
71
- "prettier": "^2.3.0",
44
+ "@web/dev-server-esbuild": "^0.2.16",
45
+ "@web/test-runner": "^0.13.22",
72
46
  "rimraf": "^3.0.2",
73
- "semver": "^7.3.5",
74
- "ts-loader": "^9.2.2",
75
- "typescript": "^4.3.2",
76
- "webpack": "^5.38.1",
77
- "webpack-cli": "^4.7.0",
78
- "webpack-dev-server": "^3.11.2",
79
- "webpack-merge": "^5.7.3"
47
+ "typescript": "^4.5.0",
48
+ "vite": "^2.7.13"
80
49
  },
81
- "husky": {
82
- "hooks": {
83
- "pre-commit": "lint-staged"
84
- }
50
+ "author": "mantou132",
51
+ "license": "MIT",
52
+ "repository": {
53
+ "type": "git",
54
+ "url": "git+https://github.com/mantou132/gem.git",
55
+ "directory": "packages/gem"
56
+ },
57
+ "bugs": {
58
+ "url": "https://github.com/mantou132/gem/issues"
85
59
  },
86
- "lint-staged": {
87
- "*.ts": "eslint --fix"
88
- }
60
+ "homepage": "https://github.com/mantou132/gem#readme"
89
61
  }
@@ -1,6 +1,5 @@
1
1
  /**
2
- * 自定义元素不应该重复定义,gem 不应该重复引入
3
- * 包含所有资产,导出到全局变量 `Gem`,以便供 webpack 的 `externals` 使用
2
+ * 导出所有内容,包括 gem 元素
4
3
  */
5
4
  export * from './elements/link';
6
5
  export * from './elements/route';
@@ -9,12 +8,12 @@ export * from './elements/use';
9
8
  export * from './elements/unsafe';
10
9
  export * from './elements/reflect';
11
10
 
12
- export * from './elements/modal-base';
13
- export * from './elements/dialog-base';
11
+ export * from './elements/base/modal-factory';
12
+ export * from './elements/base/dialog';
14
13
 
15
14
  export * from './helper/theme';
16
15
  export * from './helper/i18n';
17
16
  export * from './helper/request';
18
17
  export * from './helper/mediaquery';
19
18
 
20
- export * from './';
19
+ export * from '.';
@@ -1,6 +1,6 @@
1
- import { GemElement } from '../lib/element';
2
- import { attribute, state, connectStore } from '../lib/decorators';
3
- import { history } from '../lib/history';
1
+ import { GemElement } from '../../lib/element';
2
+ import { attribute, state, connectStore } from '../../lib/decorators';
3
+ import { history } from '../../lib/history';
4
4
 
5
5
  /**
6
6
  * 在模版中声明的 dialog,使用 `open` 方法 打开;
@@ -10,8 +10,9 @@ import { history } from '../lib/history';
10
10
  * @attr label
11
11
  */
12
12
  @connectStore(history.store)
13
- export abstract class DialogBaseElement extends GemElement {
14
- @state opened = false;
13
+ export abstract class GemDialogBaseElement extends GemElement {
14
+ @attribute label: string;
15
+ @state opened: boolean;
15
16
 
16
17
  inert = true;
17
18
 
@@ -19,8 +20,6 @@ export abstract class DialogBaseElement extends GemElement {
19
20
  #parentElement: Node | null;
20
21
  #inertStore: HTMLElement[] = [];
21
22
 
22
- @attribute label = '';
23
-
24
23
  constructor() {
25
24
  super();
26
25
  this.internals.role = 'dialog';
@@ -0,0 +1,256 @@
1
+ import { GemElement, html } from '../../lib/element';
2
+ import { attribute, emitter, Emitter } from '../../lib/decorators';
3
+
4
+ export type PanEventDetail = {
5
+ // movement
6
+ x: number;
7
+ y: number;
8
+ clientX: number;
9
+ clientY: number;
10
+ timeStamp: number;
11
+ isPrimary: boolean;
12
+ pointerId: number;
13
+ };
14
+ export interface SwipeEventDetail {
15
+ direction: 'top' | 'right' | 'bottom' | 'left';
16
+ speed: number; // px/ms
17
+ }
18
+ export interface PinchEventDetail {
19
+ // center base viewport
20
+ x: number;
21
+ y: number;
22
+ scale: number;
23
+ }
24
+ export interface RotateEventDetail {
25
+ // center base viewport
26
+ x: number;
27
+ y: number;
28
+ rotate: number;
29
+ }
30
+
31
+ function angleAB(
32
+ a: number,
33
+ b: number,
34
+ c: number,
35
+ { clientX: x1, clientY: y1 }: PanEventDetail,
36
+ { clientX: x2, clientY: y2 }: PanEventDetail,
37
+ { clientX: x3, clientY: y3 }: PanEventDetail,
38
+ ) {
39
+ // https://en.wikipedia.org/wiki/Law_of_cosines
40
+ // https://blog.csdn.net/z278930050/article/details/53319091
41
+ return (
42
+ Math.sign((x2 - x1) * (y3 - y2) - (y2 - y1) * (x3 - x2)) * Math.acos((a ** 2 + b ** 2 - c ** 2) / (2 * a * b)) * 180
43
+ );
44
+ }
45
+
46
+ /**
47
+ * 块级元素,如果要设置成 `contents`,则内容块要设置 `touch-action: none`
48
+ * https://javascript.info/pointer-events#event-pointercancel
49
+ *
50
+ * 在移动端上,必须设置 `touch-action` 以允许滚动等原生动作
51
+ *
52
+ * 为什么空白区域会自动触发 `pointercancel`?
53
+ *
54
+ * @event pan
55
+ * @event pinch
56
+ * @event rotate
57
+ * @event swipe
58
+ * @event press
59
+ * @event end
60
+ *
61
+ * @attr touch-action
62
+ */
63
+ export class GemGestureElement extends GemElement {
64
+ @emitter pan: Emitter<PanEventDetail>;
65
+ @emitter pinch: Emitter<PinchEventDetail>;
66
+ @emitter rotate: Emitter<RotateEventDetail>;
67
+ @emitter swipe: Emitter<SwipeEventDetail>;
68
+ @emitter press: Emitter<null>;
69
+ @emitter end: Emitter<null>;
70
+
71
+ @attribute touchAction: string;
72
+
73
+ pressed = false; // 触发 press 之后不触发其他事件
74
+ pressTimer = 0;
75
+
76
+ startEventMap: Map<number, PointerEvent> = new Map();
77
+ movesMap: Map<number, PanEventDetail[]> = new Map();
78
+
79
+ constructor() {
80
+ super();
81
+ this.addEventListener('pointerdown', this.onStart);
82
+ this.addEventListener('pointermove', this.onMove);
83
+ this.addEventListener('pointerup', this.onEnd);
84
+ this.addEventListener('pointercancel', this.onEnd);
85
+ this.addEventListener('dragstart', (evt) => evt.preventDefault());
86
+ }
87
+
88
+ getMoves(pointerId: number) {
89
+ return this.movesMap.get(pointerId) || [];
90
+ }
91
+
92
+ getStartEvent(pointerId: number) {
93
+ return this.startEventMap.get(pointerId) as PointerEvent;
94
+ }
95
+
96
+ getOtherLastmove(pointerId: number) {
97
+ for (const id of this.movesMap.keys()) {
98
+ if (id !== pointerId) {
99
+ const moves = this.getMoves(id);
100
+ return moves[moves.length - 1] || this.getStartEvent(id);
101
+ }
102
+ }
103
+ }
104
+
105
+ getMovementX(x: number, y: number) {
106
+ const touchAction = this.touchAction;
107
+ if (
108
+ (touchAction.includes('pan-right') && x > 0) ||
109
+ (touchAction.includes('pan-left') && x < 0) ||
110
+ touchAction.includes('pan-x') ||
111
+ (((touchAction.includes('pan-down') && y > 0) ||
112
+ (touchAction.includes('pan-up') && y < 0) ||
113
+ touchAction.includes('pan-y')) &&
114
+ Math.abs(y) > Math.abs(x)) // horizontally scrolling
115
+ ) {
116
+ return 0;
117
+ }
118
+ return x;
119
+ }
120
+
121
+ getMovementY(x: number, y: number) {
122
+ const touchAction = this.touchAction;
123
+ if (
124
+ (touchAction.includes('pan-down') && y > 0) ||
125
+ (touchAction.includes('pan-up') && y < 0) ||
126
+ touchAction.includes('pan-y') ||
127
+ (((touchAction.includes('pan-right') && x > 0) ||
128
+ (touchAction.includes('pan-left') && x < 0) ||
129
+ touchAction.includes('pan-x')) &&
130
+ Math.abs(x) > Math.abs(y)) // vertical scrolling
131
+ ) {
132
+ return 0;
133
+ }
134
+ return y;
135
+ }
136
+
137
+ onStart(evt: PointerEvent) {
138
+ evt.stopPropagation();
139
+ this.setPointerCapture(evt.pointerId);
140
+ this.movesMap.set(evt.pointerId, []);
141
+ this.startEventMap.set(evt.pointerId, evt);
142
+ if (evt.isPrimary) {
143
+ this.pressed = false;
144
+ this.pressTimer = window.setTimeout(() => {
145
+ this.press(null);
146
+ this.pressed = true;
147
+ }, 251);
148
+ }
149
+ }
150
+
151
+ onMove(evt: PointerEvent) {
152
+ evt.stopPropagation();
153
+ const { pointerId, clientX, clientY, isPrimary, pointerType, timeStamp } = evt;
154
+ if (!this.pressed && this.hasPointerCapture(pointerId)) {
155
+ const moves = this.getMoves(pointerId);
156
+ const startEvent = this.getStartEvent(pointerId);
157
+ const lastMove = moves[moves.length - 1] || startEvent;
158
+ // https://bugs.webkit.org/show_bug.cgi?id=220194
159
+ // https://bugs.chromium.org/p/chromium/issues/detail?id=1092358
160
+ const movementX = clientX - lastMove.clientX;
161
+ const movementY = clientY - lastMove.clientY;
162
+ const move = {
163
+ x: this.getMovementX(movementX, movementY),
164
+ y: this.getMovementY(movementX, movementY),
165
+ clientX,
166
+ clientY,
167
+ timeStamp,
168
+ isPrimary,
169
+ pointerId,
170
+ };
171
+ moves.push(move);
172
+ this.pan(move);
173
+
174
+ if (this.movesMap.size !== 1) {
175
+ const secondaryPoint = this.getOtherLastmove(pointerId) as PanEventDetail;
176
+ const moveLen = Math.sqrt(movementX ** 2 + movementY ** 2);
177
+ const distanceLen = Math.sqrt(
178
+ (lastMove.clientX - secondaryPoint.clientX) ** 2 + (lastMove.clientY - secondaryPoint.clientY) ** 2,
179
+ );
180
+ const newDistanceLen = Math.sqrt(
181
+ (clientX - secondaryPoint.clientX) ** 2 + (clientY - secondaryPoint.clientY) ** 2,
182
+ );
183
+ const x = (lastMove.clientX + secondaryPoint.clientX) / 2;
184
+ const y = (lastMove.clientY + secondaryPoint.clientY) / 2;
185
+ this.pinch({ x, y, scale: newDistanceLen / distanceLen });
186
+ this.rotate({ x, y, rotate: angleAB(newDistanceLen, distanceLen, moveLen, secondaryPoint, lastMove, move) });
187
+ }
188
+
189
+ const accuracy = pointerType === 'touch' ? 5 : 0;
190
+ if (
191
+ Math.abs(movementX) > accuracy ||
192
+ Math.abs(movementY) > accuracy ||
193
+ Math.abs(move.clientX - startEvent.clientX) > accuracy ||
194
+ Math.abs(move.clientY - startEvent.clientY) > accuracy
195
+ ) {
196
+ window.clearTimeout(this.pressTimer);
197
+ }
198
+ }
199
+ }
200
+
201
+ onEnd(evt: PointerEvent) {
202
+ evt.stopPropagation();
203
+ const { pointerId } = evt;
204
+ window.clearTimeout(this.pressTimer);
205
+
206
+ if (this.movesMap.size === 1) {
207
+ this.end(null);
208
+ }
209
+
210
+ // auto release: https://javascript.info/pointer-events#pointer-capturing
211
+ if (!this.pressed && this.hasPointerCapture(pointerId)) {
212
+ const moves = this.getMoves(pointerId);
213
+
214
+ if (moves.length > 2) {
215
+ const { x, y, timeStamp, clientX, clientY } = moves[moves.length - 1];
216
+ const move2 = moves[moves.length - 3];
217
+ if (Math.abs(x) > 2) {
218
+ if (Math.abs(x) > Math.abs(y)) {
219
+ const speed = Math.abs(move2.clientX - clientX) / (timeStamp - move2.timeStamp);
220
+ if (x > 0) {
221
+ this.swipe({ direction: 'right', speed });
222
+ } else {
223
+ this.swipe({ direction: 'left', speed });
224
+ }
225
+ }
226
+ }
227
+ if (Math.abs(y) > 2) {
228
+ const speed = Math.abs(move2.clientY - clientY) / (timeStamp - move2.timeStamp);
229
+ if (Math.abs(y) > Math.abs(x)) {
230
+ if (y > 0) {
231
+ this.swipe({ direction: 'bottom', speed });
232
+ } else {
233
+ this.swipe({ direction: 'top', speed });
234
+ }
235
+ }
236
+ }
237
+ }
238
+ }
239
+
240
+ this.movesMap.delete(pointerId);
241
+ this.startEventMap.delete(pointerId);
242
+ }
243
+
244
+ render() {
245
+ return html`
246
+ <style>
247
+ :host {
248
+ display: block;
249
+ user-select: none;
250
+ touch-action: ${this.touchAction || 'none'};
251
+ }
252
+ </style>
253
+ <slot></slot>
254
+ `;
255
+ }
256
+ }
@@ -0,0 +1,178 @@
1
+ import { GemElement, html, ifDefined } from '../../lib/element';
2
+ import { attribute, property, state, part, connectStore } from '../../lib/decorators';
3
+ import { history, basePathStore } from '../../lib/history';
4
+ import { absoluteLocation } from '../../lib/utils';
5
+
6
+ import { matchPath, RouteItem, RouteOptions, createHistoryParams, createPath } from './route';
7
+
8
+ /**
9
+ * @attr href
10
+ * @attr target
11
+ * @attr doc-title
12
+ * @attr path
13
+ * @attr query
14
+ * @attr hash
15
+ * @attr pattern
16
+ * @attr hint
17
+ * @part link
18
+ *
19
+ * print bug: https://github.com/mantou132/gem/issues/36
20
+ */
21
+ @connectStore(basePathStore)
22
+ export class GemLinkElement extends GemElement {
23
+ @attribute href: string;
24
+ @attribute target: string;
25
+ @attribute path: string;
26
+ @attribute query: string;
27
+ @attribute hash: string;
28
+ @attribute docTitle: string;
29
+ @attribute hint: 'on' | 'off';
30
+
31
+ // 动态路由,根据 route.pattern 和 options.params 计算出 path
32
+ @property route: RouteItem | undefined;
33
+ @property options: RouteOptions | undefined;
34
+ @property prepare: (() => void | Promise<void>) | undefined;
35
+
36
+ @part link: string;
37
+
38
+ constructor() {
39
+ super();
40
+ this.tabIndex = 0;
41
+ this.addEventListener('click', this.clickHandle);
42
+ }
43
+
44
+ // 不包含 basePath
45
+ getPathInfo() {
46
+ if (this.route) {
47
+ const queryProp = this.options?.query || '';
48
+ const hashProp = this.options?.hash || '';
49
+ return createPath(this.route, this.options) + queryProp + hashProp;
50
+ } else {
51
+ const url = this.href || this.path + this.query + this.hash;
52
+ const { path, query } = history.getParams();
53
+ if (url.startsWith('#')) {
54
+ return `${path}${query}${url}`;
55
+ } else if (url.startsWith('?')) {
56
+ return `${path}${url}`;
57
+ } else if (url.startsWith('.')) {
58
+ return absoluteLocation(path, url);
59
+ } else {
60
+ return url;
61
+ }
62
+ }
63
+ }
64
+
65
+ isExternal(pathInfo: string) {
66
+ return !pathInfo.startsWith('/');
67
+ }
68
+
69
+ clickHandle = async (e: MouseEvent) => {
70
+ const pathInfo = this.getPathInfo();
71
+
72
+ if (!pathInfo) return;
73
+
74
+ // 外部链接使用 `window.open`
75
+ if (this.isExternal(pathInfo)) {
76
+ switch (this.target) {
77
+ case '_self':
78
+ window.location.href = pathInfo;
79
+ return;
80
+ case '_parent':
81
+ window.parent.location.href = pathInfo;
82
+ return;
83
+ case '_top':
84
+ window.top!.location.href = pathInfo;
85
+ return;
86
+ default:
87
+ window.open(pathInfo);
88
+ return;
89
+ }
90
+ }
91
+
92
+ const { path, query, hash } = history.getParams();
93
+ if (path + query + hash === pathInfo) {
94
+ // 点击当前路由链接时,什么也没做
95
+ return;
96
+ }
97
+
98
+ e.stopPropagation();
99
+
100
+ await this.prepare?.();
101
+
102
+ if (this.route) {
103
+ history.pushIgnoreCloseHandle({
104
+ ...createHistoryParams(this.route, this.options),
105
+ title: this.route.title || this.docTitle,
106
+ });
107
+ } else if (this.href) {
108
+ const { pathname, search, hash } = new URL(pathInfo, location.origin);
109
+ history.pushIgnoreCloseHandle({
110
+ path: pathname,
111
+ query: search,
112
+ hash: hash,
113
+ title: this.docTitle,
114
+ });
115
+ } else {
116
+ history.pushIgnoreCloseHandle({ path: this.path, query: this.query, hash: this.hash, title: this.docTitle });
117
+ }
118
+ };
119
+
120
+ preventDefault = (e: MouseEvent) => {
121
+ e.preventDefault();
122
+ };
123
+
124
+ render(pathInfo = this.getPathInfo()) {
125
+ return html`
126
+ <style>
127
+ :host {
128
+ /* link default style */
129
+ cursor: pointer;
130
+ color: blue;
131
+ text-decoration: underline;
132
+ }
133
+ a {
134
+ all: unset;
135
+ display: contents;
136
+ }
137
+ a:visited {
138
+ color: unset;
139
+ }
140
+ </style>
141
+ <a
142
+ part=${this.link}
143
+ @click=${this.preventDefault}
144
+ href=${ifDefined(
145
+ this.hint === 'off'
146
+ ? undefined
147
+ : this.isExternal(pathInfo)
148
+ ? pathInfo
149
+ : new URL(history.basePath + pathInfo, location.origin).toString(),
150
+ )}
151
+ >
152
+ <slot></slot>
153
+ </a>
154
+ `;
155
+ }
156
+ }
157
+
158
+ /**
159
+ * @attr pattern
160
+ * @state active
161
+ */
162
+ @connectStore(history.store)
163
+ export class GemActiveLinkElement extends GemLinkElement {
164
+ @attribute pattern: string; // 使用匹配模式设定 active
165
+ @state active: boolean;
166
+
167
+ render() {
168
+ const { path, query, hash } = history.getParams();
169
+ const isMatchPattern = this.pattern && matchPath(this.pattern, path);
170
+ const pathInfo = this.getPathInfo();
171
+ if (isMatchPattern || path + query + hash === pathInfo) {
172
+ this.active = true;
173
+ } else {
174
+ this.active = false;
175
+ }
176
+ return super.render(pathInfo);
177
+ }
178
+ }
@@ -1,6 +1,6 @@
1
- import { GemElement } from '../lib/element';
2
- import { createStore, updateStore } from '../lib/store';
3
- import { history } from '../lib/history';
1
+ import { GemElement } from '../../lib/element';
2
+ import { createStore, updateStore } from '../../lib/store';
3
+ import { history } from '../../lib/history';
4
4
 
5
5
  const open = Symbol('open mark');
6
6
 
@@ -0,0 +1,29 @@
1
+ import { GemElement } from '../../lib/element';
2
+ import { property } from '../../lib/decorators';
3
+
4
+ // support prerender
5
+ document.querySelectorAll('[data-reflect]').forEach((e) => e.remove());
6
+
7
+ export class GemReflectElement extends GemElement {
8
+ @property target: HTMLElement = document.head;
9
+
10
+ constructor() {
11
+ super({ isLight: true });
12
+ this.hidden = true;
13
+ this.effect(
14
+ () => {
15
+ const childNodes = [...this.childNodes];
16
+ childNodes.forEach((node) => {
17
+ if (node instanceof HTMLElement) {
18
+ node.dataset.reflect = '';
19
+ }
20
+ this.target.append(node);
21
+ });
22
+ return () => {
23
+ childNodes.forEach((node) => node.remove());
24
+ };
25
+ },
26
+ () => [this.target],
27
+ );
28
+ }
29
+ }