@mantou/gem 1.3.6 → 1.5.1
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/elements/link.d.ts.map +1 -1
- package/elements/link.js +2 -2
- package/elements/link.js.map +1 -1
- package/elements/modal-base.d.ts +86 -31
- package/elements/modal-base.d.ts.map +1 -1
- package/elements/reflect.d.ts.map +1 -1
- package/elements/reflect.js +8 -1
- package/elements/reflect.js.map +1 -1
- package/elements/route.d.ts +43 -12
- package/elements/route.d.ts.map +1 -1
- package/elements/route.js +114 -93
- package/elements/route.js.map +1 -1
- package/elements/use.d.ts.map +1 -1
- package/elements/use.js +12 -6
- package/elements/use.js.map +1 -1
- package/helper/i18n.d.ts +1 -1
- package/helper/i18n.d.ts.map +1 -1
- package/helper/i18n.js +7 -7
- package/helper/i18n.js.map +1 -1
- package/lib/element.d.ts +25 -5
- package/lib/element.d.ts.map +1 -1
- package/lib/element.js +49 -13
- package/lib/element.js.map +1 -1
- package/lib/history.js +1 -1
- package/lib/history.js.map +1 -1
- package/lib/utils.d.ts +6 -1
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +39 -1
- package/lib/utils.js.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +1 -1
- package/src/elements/link.ts +3 -3
- package/src/elements/reflect.ts +9 -1
- package/src/elements/route.ts +125 -105
- package/src/elements/use.ts +11 -5
- package/src/examples/elements/layout.ts +2 -1
- package/src/examples/ref-route/index.ts +73 -40
- package/src/examples/svg-icon/index.ts +12 -1
- package/src/helper/i18n.ts +9 -7
- package/src/lib/element.ts +54 -9
- package/src/lib/history.ts +1 -1
- package/src/lib/utils.ts +36 -2
- package/src/lib/version.ts +1 -1
- package/src/test/gem-element/advance.test.ts +47 -0
- package/src/test/utils.test.ts +6 -0
- package/umd.js +13 -14
- package/umd.js.map +1 -1
package/src/elements/route.ts
CHANGED
|
@@ -11,13 +11,16 @@ import {
|
|
|
11
11
|
UpdateHistoryParams,
|
|
12
12
|
titleStore,
|
|
13
13
|
updateStore,
|
|
14
|
-
|
|
14
|
+
Store,
|
|
15
|
+
QueryString,
|
|
16
|
+
createStore,
|
|
15
17
|
} from '../';
|
|
16
18
|
|
|
17
19
|
interface NamePostition {
|
|
18
20
|
[index: string]: number;
|
|
19
21
|
}
|
|
20
22
|
|
|
23
|
+
// TODO: use `URLPattern`
|
|
21
24
|
class ParamsRegExp extends RegExp {
|
|
22
25
|
namePosition: NamePostition;
|
|
23
26
|
constructor(pattern: string) {
|
|
@@ -36,15 +39,15 @@ class ParamsRegExp extends RegExp {
|
|
|
36
39
|
}
|
|
37
40
|
}
|
|
38
41
|
|
|
39
|
-
|
|
40
|
-
return new ParamsRegExp(pattern);
|
|
41
|
-
}
|
|
42
|
+
type Params = Record<string, string>;
|
|
42
43
|
|
|
43
44
|
// `/a/b/:c/:d` `/a/b/1/2`
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
const
|
|
45
|
+
// 匹配成功时返回 params
|
|
46
|
+
export function matchPath(pattern: string, path: string) {
|
|
47
|
+
const reg = new ParamsRegExp(pattern);
|
|
48
|
+
const matchResult = path.match(reg) || `${path}/`.match(reg);
|
|
49
|
+
if (!matchResult) return null;
|
|
50
|
+
const params: Params = {};
|
|
48
51
|
if (matchResult) {
|
|
49
52
|
Object.keys(reg.namePosition).forEach((name) => {
|
|
50
53
|
params[name] = matchResult[reg.namePosition[name] + 1];
|
|
@@ -53,10 +56,6 @@ function getParams(pattern: string, path: string) {
|
|
|
53
56
|
return params;
|
|
54
57
|
}
|
|
55
58
|
|
|
56
|
-
export function isMatch(pattern: string, path: string) {
|
|
57
|
-
return !!path.match(getReg(pattern)) || !!`${path}/`.match(getReg(pattern));
|
|
58
|
-
}
|
|
59
|
-
|
|
60
59
|
export interface RouteItem<T = unknown> {
|
|
61
60
|
// 支持 *
|
|
62
61
|
// 支持 /a/*
|
|
@@ -64,17 +63,17 @@ export interface RouteItem<T = unknown> {
|
|
|
64
63
|
pattern: string;
|
|
65
64
|
redirect?: string;
|
|
66
65
|
content?: TemplateResult;
|
|
67
|
-
getContent?: (params:
|
|
66
|
+
getContent?: (params: Params) => TemplateResult | Promise<TemplateResult>;
|
|
68
67
|
title?: string;
|
|
69
68
|
// 用来传递数据
|
|
70
69
|
data?: T;
|
|
71
70
|
}
|
|
72
71
|
|
|
73
|
-
|
|
72
|
+
type RoutesObject = Record<string, RouteItem>;
|
|
74
73
|
|
|
75
74
|
// params 中的成员不会验证
|
|
76
75
|
export interface RouteOptions extends Omit<UpdateHistoryParams, 'path'> {
|
|
77
|
-
params?:
|
|
76
|
+
params?: Params;
|
|
78
77
|
}
|
|
79
78
|
|
|
80
79
|
// 从路由创建路径
|
|
@@ -103,127 +102,148 @@ interface ConstructorOptions {
|
|
|
103
102
|
routes?: RouteItem[] | RoutesObject;
|
|
104
103
|
}
|
|
105
104
|
|
|
105
|
+
type State = {
|
|
106
|
+
content?: TemplateResult;
|
|
107
|
+
};
|
|
108
|
+
|
|
106
109
|
/**
|
|
107
110
|
* @customElement gem-route
|
|
111
|
+
* @attr inert 暂停路由更新
|
|
108
112
|
* @fires change
|
|
113
|
+
* @fires error
|
|
114
|
+
* @fires loading
|
|
109
115
|
*/
|
|
110
116
|
@connectStore(history.store)
|
|
111
117
|
@customElement('gem-route')
|
|
112
|
-
export class GemRouteElement extends GemElement {
|
|
118
|
+
export class GemRouteElement extends GemElement<State> {
|
|
113
119
|
@property routes?: RouteItem[] | RoutesObject;
|
|
120
|
+
/**
|
|
121
|
+
* @example
|
|
122
|
+
* const locationStore = GemRouteElement.createLocationStore()
|
|
123
|
+
* html`<gem-route .locationStore=${locationStore}>`
|
|
124
|
+
*/
|
|
125
|
+
@property locationStore?: Store<{ path: string; params: Params; query: QueryString; data?: any }>;
|
|
114
126
|
@property key: any; // 除了 href 提供另外一种方式来更新,比如语言更新也需要刷新 <gem-route>
|
|
115
|
-
@emitter change: Emitter<RouteItem | null>;
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
#key: any; // 用于内部比较
|
|
119
|
-
#redirect: boolean;
|
|
120
|
-
#isLight?: boolean;
|
|
127
|
+
@emitter change: Emitter<RouteItem | null>; // path 改变或者 key 改变,包含初始渲染
|
|
128
|
+
@emitter loading: Emitter<RouteItem>;
|
|
129
|
+
@emitter error: Emitter<any>;
|
|
121
130
|
|
|
122
131
|
currentRoute: RouteItem | null;
|
|
132
|
+
// 当前匹配的路由的 params
|
|
133
|
+
currentParams: Params = {};
|
|
123
134
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
135
|
+
#lastLoader?: Promise<TemplateResult>;
|
|
136
|
+
|
|
137
|
+
static createLocationStore = () => {
|
|
138
|
+
const { path, query, data } = history.getParams();
|
|
139
|
+
return createStore({ path, query, data, params: {} as Record<string, string> });
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
static findRoute = (target: RouteItem[] | RoutesObject = [], path: string) => {
|
|
143
|
+
let defaultRoute: RouteItem | null = null;
|
|
144
|
+
let routes: RouteItem[];
|
|
145
|
+
if (target instanceof Array) {
|
|
146
|
+
routes = target;
|
|
147
|
+
} else {
|
|
148
|
+
routes = Object.values(target);
|
|
149
|
+
}
|
|
150
|
+
for (const route of routes) {
|
|
151
|
+
if ('*' === route.pattern) {
|
|
152
|
+
defaultRoute = route;
|
|
153
|
+
} else {
|
|
154
|
+
const params = matchPath(route.pattern, path);
|
|
155
|
+
if (params) {
|
|
156
|
+
return { route, params };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return { route: defaultRoute };
|
|
161
|
+
};
|
|
128
162
|
|
|
129
163
|
constructor({ isLight, routes }: ConstructorOptions = {}) {
|
|
130
164
|
super({ isLight });
|
|
131
|
-
this.#isLight = isLight;
|
|
132
|
-
|
|
133
165
|
this.routes = routes;
|
|
134
|
-
|
|
135
|
-
const { path, query } = history.getParams();
|
|
136
|
-
this.#href = path + query;
|
|
137
166
|
}
|
|
138
167
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
168
|
+
state: State = {
|
|
169
|
+
content: undefined,
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
#updateLocationStore = () => {
|
|
173
|
+
if (this.locationStore) {
|
|
174
|
+
const { path, query, data } = history.getParams();
|
|
175
|
+
updateStore(this.locationStore, {
|
|
176
|
+
path,
|
|
177
|
+
params: this.currentParams,
|
|
178
|
+
query,
|
|
179
|
+
data,
|
|
180
|
+
});
|
|
143
181
|
}
|
|
144
182
|
};
|
|
145
183
|
|
|
146
|
-
#
|
|
147
|
-
this
|
|
148
|
-
|
|
184
|
+
#setContent = (route: RouteItem | null, params: Params, content?: TemplateResult) => {
|
|
185
|
+
this.#lastLoader = undefined;
|
|
186
|
+
this.currentRoute = route;
|
|
187
|
+
this.currentParams = params;
|
|
188
|
+
this.setState({ content });
|
|
189
|
+
this.change(this.currentRoute);
|
|
190
|
+
const title = route?.title;
|
|
191
|
+
if (title) updateStore(titleStore, { title });
|
|
192
|
+
this.#updateLocationStore();
|
|
149
193
|
};
|
|
150
194
|
|
|
195
|
+
mounted() {
|
|
196
|
+
this.effect(
|
|
197
|
+
([_, path]) => {
|
|
198
|
+
const { route, params = {} } = GemRouteElement.findRoute(this.routes, path);
|
|
199
|
+
const { redirect, content, getContent } = route || {};
|
|
200
|
+
if (redirect) {
|
|
201
|
+
history.replace({ path: redirect });
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const contentOrLoader = content || getContent?.(params);
|
|
205
|
+
if (contentOrLoader instanceof Promise) {
|
|
206
|
+
this.loading(route!);
|
|
207
|
+
this.#lastLoader = contentOrLoader;
|
|
208
|
+
const isSomeLoader = () => this.#lastLoader === contentOrLoader;
|
|
209
|
+
contentOrLoader
|
|
210
|
+
.then((content) => {
|
|
211
|
+
if (isSomeLoader()) this.#setContent(route, params, content);
|
|
212
|
+
})
|
|
213
|
+
.catch((err) => {
|
|
214
|
+
if (isSomeLoader()) this.error(err);
|
|
215
|
+
});
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
this.#setContent(route, params, contentOrLoader);
|
|
219
|
+
},
|
|
220
|
+
() => {
|
|
221
|
+
const { path } = history.getParams();
|
|
222
|
+
return [this.key, path];
|
|
223
|
+
},
|
|
224
|
+
);
|
|
225
|
+
this.effect(this.#updateLocationStore, () => [location.search]);
|
|
226
|
+
}
|
|
227
|
+
|
|
151
228
|
shouldUpdate() {
|
|
152
229
|
if (this.inert) return false;
|
|
153
|
-
|
|
154
|
-
const href = path + query;
|
|
155
|
-
if (href !== this.#href || this.key !== this.#key) {
|
|
156
|
-
this.#href = href;
|
|
157
|
-
this.#key = this.key;
|
|
158
|
-
return true;
|
|
159
|
-
}
|
|
160
|
-
return false;
|
|
230
|
+
return true;
|
|
161
231
|
}
|
|
162
232
|
|
|
163
233
|
render() {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
this.currentRoute = null;
|
|
167
|
-
|
|
168
|
-
let defaultRoute: RouteItem | null = null;
|
|
169
|
-
let routes: RouteItem[];
|
|
170
|
-
if (this.routes instanceof Array) {
|
|
171
|
-
routes = this.routes;
|
|
172
|
-
} else {
|
|
173
|
-
routes = Object.values(this.routes);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
for (const item of routes) {
|
|
177
|
-
const { pattern } = item;
|
|
178
|
-
if ('*' === pattern) {
|
|
179
|
-
defaultRoute = item;
|
|
180
|
-
} else if (isMatch(pattern, path)) {
|
|
181
|
-
this.currentRoute = item;
|
|
182
|
-
break;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
234
|
+
const { content } = this.state;
|
|
235
|
+
if (!this.shadowRoot) return html`${content}`;
|
|
185
236
|
|
|
186
|
-
if (!this.currentRoute) {
|
|
187
|
-
this.currentRoute = defaultRoute;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (!this.currentRoute) return this.#callback();
|
|
191
|
-
|
|
192
|
-
const { redirect, content, getContent } = this.currentRoute;
|
|
193
|
-
if (redirect) {
|
|
194
|
-
this.#redirect = true;
|
|
195
|
-
history.replace({ path: redirect });
|
|
196
|
-
// 不要渲染空内容,等待重定向结果
|
|
197
|
-
return undefined;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
this.#redirect = false;
|
|
201
237
|
return html`
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
:
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
}
|
|
210
|
-
</style>
|
|
211
|
-
`}
|
|
212
|
-
${content || getContent?.(this.getParams())}
|
|
238
|
+
<style>
|
|
239
|
+
:host,
|
|
240
|
+
:not(:defined) {
|
|
241
|
+
display: contents;
|
|
242
|
+
}
|
|
243
|
+
</style>
|
|
244
|
+
${content}
|
|
213
245
|
`;
|
|
214
246
|
}
|
|
215
|
-
|
|
216
|
-
mounted() {
|
|
217
|
-
this.#key = this.key;
|
|
218
|
-
addMicrotaskToStack(this.#initPage);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
updated() {
|
|
222
|
-
this.#initPage();
|
|
223
|
-
if (!this.#redirect) {
|
|
224
|
-
this.change(this.currentRoute);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
247
|
}
|
|
228
248
|
|
|
229
249
|
/**
|
package/src/elements/use.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { GemElement, html, attribute, property, customElement } from '../';
|
|
2
2
|
|
|
3
|
+
const eleCache = new Map<string, HTMLTemplateElement>();
|
|
3
4
|
/**
|
|
4
5
|
* svg 中的 `<use>` 不能穿透 ShadowDOM,
|
|
5
6
|
* 此元素用来模拟 `<use>`,
|
|
@@ -18,12 +19,16 @@ export class GemUseElement extends GemElement {
|
|
|
18
19
|
|
|
19
20
|
private getContent() {
|
|
20
21
|
const ele = this.element || (this.selector ? (this.root || document).querySelector(this.selector) : null);
|
|
21
|
-
if (ele
|
|
22
|
+
if (typeof ele === 'string') {
|
|
23
|
+
let temp = eleCache.get(ele);
|
|
24
|
+
if (!temp) {
|
|
25
|
+
temp = document.createElement('template');
|
|
26
|
+
temp.innerHTML = ele;
|
|
27
|
+
eleCache.set(ele, temp);
|
|
28
|
+
}
|
|
29
|
+
return temp.content.cloneNode(true);
|
|
30
|
+
} else if (ele instanceof HTMLTemplateElement) {
|
|
22
31
|
return ele.content.cloneNode(true);
|
|
23
|
-
} else if (typeof ele === 'string') {
|
|
24
|
-
const temp = document.createElement('template');
|
|
25
|
-
temp.innerHTML = ele;
|
|
26
|
-
return temp.content;
|
|
27
32
|
} else {
|
|
28
33
|
return ele?.cloneNode(true);
|
|
29
34
|
}
|
|
@@ -37,6 +42,7 @@ export class GemUseElement extends GemElement {
|
|
|
37
42
|
}
|
|
38
43
|
:host(:not([hidden])) {
|
|
39
44
|
display: inline-flex;
|
|
45
|
+
align-items: center;
|
|
40
46
|
}
|
|
41
47
|
svg {
|
|
42
48
|
width: 100%;
|
|
@@ -18,7 +18,8 @@ export class Layout extends GemElement {
|
|
|
18
18
|
<style>
|
|
19
19
|
body {
|
|
20
20
|
margin: 0;
|
|
21
|
-
font-family:
|
|
21
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans',
|
|
22
|
+
sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
|
|
22
23
|
}
|
|
23
24
|
</style>
|
|
24
25
|
</gem-reflect>
|
|
@@ -1,52 +1,77 @@
|
|
|
1
1
|
import { GemElement, html, history, render } from '../../';
|
|
2
|
-
import { customElement, RefObject, refobject } from '../../lib/decorators';
|
|
3
|
-
import { createHistoryParams, GemRouteElement,
|
|
2
|
+
import { connectStore, customElement, RefObject, refobject } from '../../lib/decorators';
|
|
3
|
+
import { createHistoryParams, GemRouteElement, RouteItem } from '../../elements/route';
|
|
4
4
|
import '../../elements/link';
|
|
5
5
|
|
|
6
6
|
import '../elements/layout';
|
|
7
7
|
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
return html`
|
|
13
|
-
<style>
|
|
14
|
-
gem-active-link {
|
|
15
|
-
display: block;
|
|
16
|
-
}
|
|
17
|
-
gem-active-link:where(.--active, :--active) {
|
|
18
|
-
color: inherit;
|
|
19
|
-
}
|
|
20
|
-
</style>
|
|
21
|
-
current route: home page, click navigation to a page
|
|
22
|
-
<gem-active-link .route=${routes.a} .options=${{ params: { b: 1 } }}>
|
|
23
|
-
a page link, params: {a: 1}
|
|
24
|
-
</gem-active-link>
|
|
25
|
-
`;
|
|
26
|
-
},
|
|
8
|
+
const homeRoute: RouteItem = {
|
|
9
|
+
pattern: '/',
|
|
10
|
+
getContent(params) {
|
|
11
|
+
return html`<route-home .params=${params}></route-home>`;
|
|
27
12
|
},
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
gem-active-link:where(.--active, :--active) {
|
|
37
|
-
color: inherit;
|
|
38
|
-
}
|
|
39
|
-
</style>
|
|
40
|
-
current route: /a/${b}, click navigation to home page, cuurent query: ${history.getParams().query.toString()}
|
|
41
|
-
<gem-active-link .route=${routes.a} .options=${{ params: { b: 1 }, query: '?a=1' }}>
|
|
42
|
-
a page link, query: ?a=1
|
|
43
|
-
</gem-active-link>
|
|
44
|
-
`;
|
|
45
|
-
},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const aRoute: RouteItem = {
|
|
16
|
+
pattern: '/a/:b',
|
|
17
|
+
async getContent() {
|
|
18
|
+
await new Promise((res) => setTimeout(res, 1000));
|
|
19
|
+
return html`<route-a></route-a>`;
|
|
46
20
|
},
|
|
47
21
|
};
|
|
48
22
|
|
|
23
|
+
const routes = {
|
|
24
|
+
home: homeRoute,
|
|
25
|
+
a: aRoute,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const locationStore = GemRouteElement.createLocationStore();
|
|
29
|
+
|
|
30
|
+
@customElement('route-home')
|
|
31
|
+
export class RouteHome extends GemElement {
|
|
32
|
+
params: Record<string, string>;
|
|
33
|
+
render = () => {
|
|
34
|
+
return html`
|
|
35
|
+
<style>
|
|
36
|
+
gem-active-link {
|
|
37
|
+
display: block;
|
|
38
|
+
}
|
|
39
|
+
gem-active-link:where(.--active, :--active) {
|
|
40
|
+
color: inherit;
|
|
41
|
+
}
|
|
42
|
+
</style>
|
|
43
|
+
current route: home page, current params: ${JSON.stringify(this.params)}, click navigation to /a page
|
|
44
|
+
<gem-active-link .route=${routes.a} .options=${{ params: { b: 1 } }}>
|
|
45
|
+
a page link, params: {a: 1}
|
|
46
|
+
</gem-active-link>
|
|
47
|
+
`;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@connectStore(locationStore)
|
|
52
|
+
@customElement('route-a')
|
|
53
|
+
export class RouteA extends GemElement {
|
|
54
|
+
render = () => {
|
|
55
|
+
return html`
|
|
56
|
+
<style>
|
|
57
|
+
gem-active-link {
|
|
58
|
+
display: block;
|
|
59
|
+
}
|
|
60
|
+
gem-active-link:where(.--active, :--active) {
|
|
61
|
+
color: inherit;
|
|
62
|
+
}
|
|
63
|
+
</style>
|
|
64
|
+
current route: /a/:b, current params: ${JSON.stringify(locationStore.params)}, click navigation to home page,
|
|
65
|
+
cuurent query: ${history.getParams().query.toString()}
|
|
66
|
+
<gem-active-link .route=${routes.a} .options=${{ params: { b: 1 }, query: '?a=1' }}>
|
|
67
|
+
a page link, query: ?a=1
|
|
68
|
+
</gem-active-link>
|
|
69
|
+
`;
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
49
73
|
@customElement('app-root')
|
|
74
|
+
@connectStore(locationStore)
|
|
50
75
|
export class App extends GemElement {
|
|
51
76
|
@refobject routeRef: RefObject<GemRouteElement>;
|
|
52
77
|
onclick = () => {
|
|
@@ -59,7 +84,15 @@ export class App extends GemElement {
|
|
|
59
84
|
render() {
|
|
60
85
|
return html`
|
|
61
86
|
<main>
|
|
62
|
-
<
|
|
87
|
+
<pre>${JSON.stringify(locationStore.params)}</pre>
|
|
88
|
+
<gem-route
|
|
89
|
+
ref=${this.routeRef.ref}
|
|
90
|
+
@loading=${console.log}
|
|
91
|
+
@change=${console.log}
|
|
92
|
+
@error=${console.error}
|
|
93
|
+
.routes=${routes}
|
|
94
|
+
.locationStore=${locationStore}
|
|
95
|
+
></gem-route>
|
|
63
96
|
</main>
|
|
64
97
|
`;
|
|
65
98
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { html, render } from '../../';
|
|
1
|
+
import { html, render, raw } from '../../';
|
|
2
2
|
|
|
3
3
|
import '../../elements/use';
|
|
4
4
|
|
|
@@ -14,6 +14,17 @@ const app = html`
|
|
|
14
14
|
</template>
|
|
15
15
|
<gem-use .root=${document.body} selector="#icon"></gem-use>
|
|
16
16
|
<gem-use selector="#icon"></gem-use>
|
|
17
|
+
<gem-use
|
|
18
|
+
.element=${raw`
|
|
19
|
+
<svg height="24px" viewBox="0 0 24 24" width="24px">
|
|
20
|
+
<path d="M0 0h24v24H0z" fill="none"/>
|
|
21
|
+
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/>
|
|
22
|
+
</svg>
|
|
23
|
+
`}
|
|
24
|
+
style="fill: red; height: 1.2em; gap: 0.3em"
|
|
25
|
+
>
|
|
26
|
+
English
|
|
27
|
+
</gem-use>
|
|
17
28
|
</div>
|
|
18
29
|
`;
|
|
19
30
|
|
package/src/helper/i18n.ts
CHANGED
|
@@ -35,6 +35,8 @@ interface Options<T> {
|
|
|
35
35
|
* 为了避免缓存超出 `localStorage` 限制,语言包 URL 应该带版本号查询参数
|
|
36
36
|
*/
|
|
37
37
|
cache?: boolean | 'manual';
|
|
38
|
+
// lib 需要设置,避免被应用中的 i18n 一起清理掉语言缓存
|
|
39
|
+
// 也用来作为 prerender 的 script type
|
|
38
40
|
cachePrefix?: string;
|
|
39
41
|
// 为 `path` 时页面的路由不能和语言代码相同
|
|
40
42
|
urlParamsType?: 'path' | 'querystring' | 'ccTLD' | 'gTLD';
|
|
@@ -47,7 +49,7 @@ export class I18n<T = Record<string, Msg>> {
|
|
|
47
49
|
fallbackLanguage: string;
|
|
48
50
|
currentLanguage: string;
|
|
49
51
|
cache?: boolean | 'manual';
|
|
50
|
-
cachePrefix = 'gem
|
|
52
|
+
cachePrefix = 'gem-i18n';
|
|
51
53
|
urlParamsType?: 'path' | 'querystring' | 'ccTLD' | 'gTLD';
|
|
52
54
|
urlParamsName?: string;
|
|
53
55
|
onChange?: (currentLanguage: string) => void;
|
|
@@ -94,7 +96,7 @@ export class I18n<T = Record<string, Msg>> {
|
|
|
94
96
|
|
|
95
97
|
let currentLanguage = this.urlParamsLang || this.currentLanguage || this.cacheLang;
|
|
96
98
|
|
|
97
|
-
const ele = document.querySelector(
|
|
99
|
+
const ele = document.querySelector(`[type*=${this.cachePrefix}]`);
|
|
98
100
|
if (ele) {
|
|
99
101
|
try {
|
|
100
102
|
const prerenderData = JSON.parse(ele.textContent || '') as I18nJSON<T>;
|
|
@@ -152,17 +154,17 @@ export class I18n<T = Record<string, Msg>> {
|
|
|
152
154
|
}
|
|
153
155
|
|
|
154
156
|
if (typeof data === 'string') {
|
|
155
|
-
const
|
|
156
|
-
const localKey = `${
|
|
157
|
+
const prefix = `${this.cachePrefix}:`;
|
|
158
|
+
const localKey = `${prefix}${data}`;
|
|
157
159
|
const localPackString = localStorage.getItem(localKey);
|
|
158
160
|
|
|
159
161
|
const fetchPack = async () => {
|
|
160
162
|
const pack = await (await fetch(data)).json();
|
|
161
163
|
if (this.cache) {
|
|
162
|
-
//
|
|
164
|
+
// 移除之前的版本缓存
|
|
163
165
|
for (let i = 0; i < localStorage.length; i++) {
|
|
164
166
|
const key = localStorage.key(i) as string;
|
|
165
|
-
if (key.startsWith(
|
|
167
|
+
if (key.startsWith(prefix)) {
|
|
166
168
|
localStorage.removeItem(key);
|
|
167
169
|
}
|
|
168
170
|
}
|
|
@@ -215,7 +217,7 @@ export class I18n<T = Record<string, Msg>> {
|
|
|
215
217
|
*
|
|
216
218
|
* html`
|
|
217
219
|
* <gem-reflect>
|
|
218
|
-
* <script type="
|
|
220
|
+
* <script type="${i18n.cachePrefix}">${JSON.stringify(i18n)}</script>
|
|
219
221
|
* </gem-reflect>
|
|
220
222
|
* `
|
|
221
223
|
*/
|