~repos /atoms-element
git clone https://pyrossh.dev/repos/atoms-element.git
A simple web component library for defining your custom elements. It works on both client and server.
b8566f51
—
Peter John 4 years ago
initial commit
- .github/workflows/main.yml +12 -0
- __snapshots__/index.test.js.snap +410 -0
- example/elements/app-counter.js +63 -0
- example/elements/app-total.js +13 -0
- example/pages/index.js +24 -0
- example/server.js +62 -0
- example/store.js +11 -0
- index.d.ts +48 -0
- index.js +1185 -0
- index.test.js +196 -0
- src/lit-html.js → lit-html.js +88 -90
- package-lock.json +4 -5
- package.json +23 -14
- readme.md +41 -29
- src/index.js +0 -401
- src/lit-html-server.js +0 -1679
- test/index.test.js +0 -123
- test/setup.js +0 -2
.github/workflows/main.yml
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
on: [push]
|
|
3
|
+
jobs:
|
|
4
|
+
Test:
|
|
5
|
+
runs-on: ubuntu-latest
|
|
6
|
+
steps:
|
|
7
|
+
- uses: actions/checkout@v2
|
|
8
|
+
- uses: actions/setup-node@v1
|
|
9
|
+
with:
|
|
10
|
+
node-version: 14.x
|
|
11
|
+
- uses: bahmutov/npm-install@v1
|
|
12
|
+
- run: npm test
|
__snapshots__/index.test.js.snap
ADDED
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`compileTw 1`] = `
|
|
4
|
+
"
|
|
5
|
+
.text-white {
|
|
6
|
+
color: rgba(255, 255, 255, 1);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.font-bold {
|
|
10
|
+
font-weight: 700;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.border {
|
|
14
|
+
border-width: 1px;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.border-black {
|
|
18
|
+
border-color: rgba(0, 0, 0, 1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.p-4 {
|
|
22
|
+
padding: 1rem;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.m-4 {
|
|
26
|
+
margin: 1rem;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.w-20 {
|
|
30
|
+
width: 5rem;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.h-20 {
|
|
34
|
+
height: 5rem;
|
|
35
|
+
}
|
|
36
|
+
"
|
|
37
|
+
`;
|
|
38
|
+
|
|
39
|
+
exports[`createElement with attrs and hooks 1`] = `
|
|
40
|
+
"
|
|
41
|
+
<div>
|
|
42
|
+
<div>
|
|
43
|
+
<span>perPage: 5</span> </div> </div> <span>Count: 3</span> <button>Set</button> "
|
|
44
|
+
`;
|
|
45
|
+
|
|
46
|
+
exports[`createElement without attrs 1`] = `" <div></div>"`;
|
|
47
|
+
|
|
48
|
+
exports[`createPage 1`] = `
|
|
49
|
+
"
|
|
50
|
+
<!DOCTYPE html>
|
|
51
|
+
<html lang=\\"en\\">
|
|
52
|
+
<head>
|
|
53
|
+
<meta charset=\\"utf-8\\" />
|
|
54
|
+
<meta http-equiv=\\"x-ua-compatible\\" content=\\"ie=edge\\" />
|
|
55
|
+
<meta http-equiv=\\"Content-Type\\" content=\\"text/html; charset=utf-8\\">
|
|
56
|
+
<meta name=\\"viewport\\" content=\\"width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=5.0, shrink-to-fit=no\\">
|
|
57
|
+
<link rel=\\"sitemap\\" type=\\"application/xml\\" href=\\"/sitemap.xml\\" />
|
|
58
|
+
<link rel=\\"icon\\" type=\\"image/png\\" href=\\"/assets/icon.png\\" />
|
|
59
|
+
|
|
60
|
+
<title>123</title> <meta name=\\"title\\" content=\\"123\\"/> <meta name=\\"description\\" content=\\"123\\"/>
|
|
61
|
+
<style id=\\"global\\">
|
|
62
|
+
*, ::before, ::after {
|
|
63
|
+
|
|
64
|
+
box-sizing: border-box;
|
|
65
|
+
border-width: 0;
|
|
66
|
+
border-style: solid;
|
|
67
|
+
border-color: #e5e7eb;
|
|
68
|
+
|
|
69
|
+
}
|
|
70
|
+
hr {
|
|
71
|
+
|
|
72
|
+
height: 0;
|
|
73
|
+
color: inherit;
|
|
74
|
+
border-top-width: 1px;
|
|
75
|
+
|
|
76
|
+
}
|
|
77
|
+
abbr[title] {
|
|
78
|
+
|
|
79
|
+
-webkit-text-decoration: underline dotted;
|
|
80
|
+
text-decoration: underline dotted;
|
|
81
|
+
|
|
82
|
+
}
|
|
83
|
+
b, strong {
|
|
84
|
+
|
|
85
|
+
font-weight: bolder;
|
|
86
|
+
|
|
87
|
+
}
|
|
88
|
+
code, kbd, samp, pre {
|
|
89
|
+
|
|
90
|
+
font-family: ui-monospace, SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace;
|
|
91
|
+
font-size: 1em;
|
|
92
|
+
|
|
93
|
+
}
|
|
94
|
+
small {
|
|
95
|
+
|
|
96
|
+
font-size: 80%;
|
|
97
|
+
|
|
98
|
+
}
|
|
99
|
+
sub, sup {
|
|
100
|
+
|
|
101
|
+
font-size: 75%;
|
|
102
|
+
line-height: 0;
|
|
103
|
+
position: relative;
|
|
104
|
+
vertical-align: baseline;
|
|
105
|
+
|
|
106
|
+
}
|
|
107
|
+
sub {
|
|
108
|
+
|
|
109
|
+
bottom: -0.25em;
|
|
110
|
+
|
|
111
|
+
}
|
|
112
|
+
sup {
|
|
113
|
+
|
|
114
|
+
top: -0.5em;
|
|
115
|
+
|
|
116
|
+
}
|
|
117
|
+
table {
|
|
118
|
+
|
|
119
|
+
text-indent: 0;
|
|
120
|
+
border-color: inherit;
|
|
121
|
+
border-collapse: collapse;
|
|
122
|
+
|
|
123
|
+
}
|
|
124
|
+
button, input, optgroup, select, textarea {
|
|
125
|
+
|
|
126
|
+
font-size: 100%;
|
|
127
|
+
margin: 0;
|
|
128
|
+
padding: 0;
|
|
129
|
+
line-height: inherit;
|
|
130
|
+
color: inherit;
|
|
131
|
+
|
|
132
|
+
}
|
|
133
|
+
button, select {
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
}
|
|
137
|
+
button, [type='button'], [type='reset'], [type='submit'] {
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
}
|
|
141
|
+
::-moz-focus-inner {
|
|
142
|
+
|
|
143
|
+
border-style: none;
|
|
144
|
+
padding: 0;
|
|
145
|
+
|
|
146
|
+
}
|
|
147
|
+
:-moz-focusring {
|
|
148
|
+
|
|
149
|
+
outline: 1px dotted ButtonText;
|
|
150
|
+
|
|
151
|
+
}
|
|
152
|
+
:-moz-ui-invalid {
|
|
153
|
+
|
|
154
|
+
box-shadow: none;
|
|
155
|
+
|
|
156
|
+
}
|
|
157
|
+
legend {
|
|
158
|
+
|
|
159
|
+
padding: 0;
|
|
160
|
+
|
|
161
|
+
}
|
|
162
|
+
progress {
|
|
163
|
+
|
|
164
|
+
vertical-align: baseline;
|
|
165
|
+
|
|
166
|
+
}
|
|
167
|
+
::-webkit-inner-spin-button, ::-webkit-outer-spin-button {
|
|
168
|
+
|
|
169
|
+
height: auto;
|
|
170
|
+
|
|
171
|
+
}
|
|
172
|
+
[type='search'] {
|
|
173
|
+
|
|
174
|
+
-webkit-appearance: textfield;
|
|
175
|
+
outline-offset: -2px;
|
|
176
|
+
|
|
177
|
+
}
|
|
178
|
+
::-webkit-search-decoration {
|
|
179
|
+
|
|
180
|
+
-webkit-appearance: none;
|
|
181
|
+
|
|
182
|
+
}
|
|
183
|
+
::-webkit-file-upload-button {
|
|
184
|
+
|
|
185
|
+
-webkit-appearance: button;
|
|
186
|
+
font: inherit;
|
|
187
|
+
|
|
188
|
+
}
|
|
189
|
+
summary {
|
|
190
|
+
|
|
191
|
+
display: list-item;
|
|
192
|
+
|
|
193
|
+
}
|
|
194
|
+
blockquote, dl, dd, h1, h2, h3, h4, h5, h6, hr, figure, p, pre {
|
|
195
|
+
|
|
196
|
+
margin: 0;
|
|
197
|
+
|
|
198
|
+
}
|
|
199
|
+
button {
|
|
200
|
+
|
|
201
|
+
background-image: none;
|
|
202
|
+
|
|
203
|
+
outline: 1px dotted, 5px auto -webkit-focus-ring-color;
|
|
204
|
+
|
|
205
|
+
}
|
|
206
|
+
fieldset {
|
|
207
|
+
|
|
208
|
+
margin: 0;
|
|
209
|
+
padding: 0;
|
|
210
|
+
|
|
211
|
+
}
|
|
212
|
+
ol, ul {
|
|
213
|
+
|
|
214
|
+
list-style: none;
|
|
215
|
+
margin: 0;
|
|
216
|
+
padding: 0;
|
|
217
|
+
|
|
218
|
+
}
|
|
219
|
+
img {
|
|
220
|
+
|
|
221
|
+
border-style: solid;
|
|
222
|
+
|
|
223
|
+
}
|
|
224
|
+
textarea {
|
|
225
|
+
|
|
226
|
+
resize: vertical;
|
|
227
|
+
|
|
228
|
+
}
|
|
229
|
+
input::-moz-placeholder, textarea::-moz-placeholder {
|
|
230
|
+
|
|
231
|
+
opacity: 1;
|
|
232
|
+
color: #9ca3af;
|
|
233
|
+
|
|
234
|
+
}
|
|
235
|
+
input:-ms-input-placeholder, textarea:-ms-input-placeholder {
|
|
236
|
+
|
|
237
|
+
opacity: 1;
|
|
238
|
+
color: #9ca3af;
|
|
239
|
+
|
|
240
|
+
}
|
|
241
|
+
input::placeholder, textarea::placeholder {
|
|
242
|
+
|
|
243
|
+
opacity: 1;
|
|
244
|
+
color: #9ca3af;
|
|
245
|
+
|
|
246
|
+
}
|
|
247
|
+
button, [role='button'] {
|
|
248
|
+
|
|
249
|
+
cursor: pointer;
|
|
250
|
+
|
|
251
|
+
}
|
|
252
|
+
h1, h2, h3, h4, h5, h6 {
|
|
253
|
+
|
|
254
|
+
font-size: inherit;
|
|
255
|
+
font-weight: inherit;
|
|
256
|
+
|
|
257
|
+
}
|
|
258
|
+
a {
|
|
259
|
+
|
|
260
|
+
color: inherit;
|
|
261
|
+
text-decoration: inherit;
|
|
262
|
+
|
|
263
|
+
}
|
|
264
|
+
pre, code, kbd, samp {
|
|
265
|
+
|
|
266
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
|
|
267
|
+
|
|
268
|
+
}
|
|
269
|
+
img, svg, video, canvas, audio, iframe, embed, object {
|
|
270
|
+
|
|
271
|
+
display: block;
|
|
272
|
+
vertical-align: middle;
|
|
273
|
+
|
|
274
|
+
}
|
|
275
|
+
img, video {
|
|
276
|
+
|
|
277
|
+
max-width: 100%;
|
|
278
|
+
height: auto;
|
|
279
|
+
|
|
280
|
+
}
|
|
281
|
+
html {
|
|
282
|
+
|
|
283
|
+
-moz-tab-size: 4;
|
|
284
|
+
-o-tab-size: 4;
|
|
285
|
+
tab-size: 4;
|
|
286
|
+
line-height: 1.5;
|
|
287
|
+
-webkit-text-size-adjust: 100%;
|
|
288
|
+
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
|
|
289
|
+
width: 100%;
|
|
290
|
+
height: 100%;
|
|
291
|
+
|
|
292
|
+
}
|
|
293
|
+
body {
|
|
294
|
+
|
|
295
|
+
margin: 0px;
|
|
296
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
|
|
297
|
+
line-height: 1.4;
|
|
298
|
+
background-color: white;
|
|
299
|
+
width: 100%;
|
|
300
|
+
height: 100%;
|
|
301
|
+
display: flex;
|
|
302
|
+
flex-direction: column;
|
|
303
|
+
flex: 1 1 0%;
|
|
304
|
+
min-width: 320px;
|
|
305
|
+
min-height: 100vh;
|
|
306
|
+
font-weight: 400;
|
|
307
|
+
color: rgba(44, 62, 80, 1);
|
|
308
|
+
direction: ltr;
|
|
309
|
+
font-synthesis: none;
|
|
310
|
+
text-rendering: optimizeLegibility;
|
|
311
|
+
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
.flex {
|
|
316
|
+
display: flex;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.flex-1 {
|
|
320
|
+
flex: 1;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.flex-col {
|
|
324
|
+
flex-direction: column;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
.mt-20 {
|
|
328
|
+
margin-top: 5rem;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.items-center {
|
|
332
|
+
align-items: center;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.text-5xl {
|
|
336
|
+
font-size: 3rem;
|
|
337
|
+
line-height: 1;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
</style>
|
|
341
|
+
<script type=\\"module\\"><script>
|
|
342
|
+
</head>
|
|
343
|
+
<body>
|
|
344
|
+
|
|
345
|
+
<div>
|
|
346
|
+
<app-header></app-header> <main class=\\"flex flex-1 flex-col mt-20 items-center\\">
|
|
347
|
+
<h1 class=\\"text-5xl\\">123</h1> </main> </div>
|
|
348
|
+
<script>
|
|
349
|
+
window.props = {\\"config\\":{\\"title\\":\\"123\\"}};
|
|
350
|
+
</script>
|
|
351
|
+
<script type=\\"module\\"><script>
|
|
352
|
+
</body>
|
|
353
|
+
</html>
|
|
354
|
+
"
|
|
355
|
+
`;
|
|
356
|
+
|
|
357
|
+
exports[`css 1`] = `
|
|
358
|
+
"button {
|
|
359
|
+
|
|
360
|
+
color: magenta;
|
|
361
|
+
font-size: 10px;
|
|
362
|
+
|
|
363
|
+
font-size: 64px;
|
|
364
|
+
|
|
365
|
+
color: black;
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
color: navy;
|
|
369
|
+
|
|
370
|
+
}
|
|
371
|
+
container {
|
|
372
|
+
|
|
373
|
+
flex: 1;
|
|
374
|
+
align-items: center;
|
|
375
|
+
justify-content: center;
|
|
376
|
+
|
|
377
|
+
}
|
|
378
|
+
"
|
|
379
|
+
`;
|
|
380
|
+
|
|
381
|
+
exports[`render attribute keys 1`] = `
|
|
382
|
+
"
|
|
383
|
+
<div>
|
|
384
|
+
<app-counter name=\\"123\\" perPage=\\"1\\"></app-counter> </div>"
|
|
385
|
+
`;
|
|
386
|
+
|
|
387
|
+
exports[`render attributes within quotes 1`] = `
|
|
388
|
+
"
|
|
389
|
+
<div>
|
|
390
|
+
<app-counter name=\\"123\\" class=\\"high\\" age=\\"1\\" details1=\\"{'name':'123','address':{'street':'1'}}\\" items=\\"[1,2,3]\\"></app-counter> </div>"
|
|
391
|
+
`;
|
|
392
|
+
|
|
393
|
+
exports[`render multi template 1`] = `
|
|
394
|
+
"
|
|
395
|
+
<div>
|
|
396
|
+
|
|
397
|
+
<app-item meta=\\"{'index':1}\\">
|
|
398
|
+
<button>+</button> </app-item> <app-item meta=\\"{'index':2}\\">
|
|
399
|
+
<button>+</button> </app-item> </div>"
|
|
400
|
+
`;
|
|
401
|
+
|
|
402
|
+
exports[`render single template 1`] = `" <div>NoCountry false</div>"`;
|
|
403
|
+
|
|
404
|
+
exports[`render unsafeHTML 1`] = `" <div><div><p class=\\"123\\">this is unsafe</p></div></div>"`;
|
|
405
|
+
|
|
406
|
+
exports[`renderHtml 1`] = `
|
|
407
|
+
"
|
|
408
|
+
<div>
|
|
409
|
+
<app-counter name=\\"123\\" class=\\"abc high\\" age=\\"1\\" details1=\\"{'name':'123','address':{'street':'1'}}\\" items=\\"[1,2,3]\\"></app-counter> </div>"
|
|
410
|
+
`;
|
example/elements/app-counter.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { useEffect } from '../../index.js';
|
|
2
|
+
import { createElement, html, useReducer } from '../../index.js';
|
|
3
|
+
import { totalReducer } from '../store.js';
|
|
4
|
+
|
|
5
|
+
const Counter = ({ name, meta }) => {
|
|
6
|
+
const { count, actions, effects } = useReducer({
|
|
7
|
+
initial: {
|
|
8
|
+
count: 0,
|
|
9
|
+
data: undefined,
|
|
10
|
+
err: null,
|
|
11
|
+
},
|
|
12
|
+
reducer: {
|
|
13
|
+
increment: (state) => ({ ...state, count: state.count + 1 }),
|
|
14
|
+
decrement: (state) => ({ ...state, count: state.count - 1 }),
|
|
15
|
+
setLoading: (state, v) => ({ ...state, loading: v }),
|
|
16
|
+
setData: (state, data) => ({ ...state, data }),
|
|
17
|
+
setErr: (state, err) => ({ ...state, err }),
|
|
18
|
+
},
|
|
19
|
+
effects: {
|
|
20
|
+
loadData: async (actions, id) => {
|
|
21
|
+
try {
|
|
22
|
+
actions.setLoading(true);
|
|
23
|
+
const res = await fetch(`/api/posts/${id}`);
|
|
24
|
+
actions.setData(await res.json());
|
|
25
|
+
} catch (err) {
|
|
26
|
+
actions.setErr(err);
|
|
27
|
+
} finally {
|
|
28
|
+
actions.setLoading(false);
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
effects.loadData(name);
|
|
35
|
+
}, []);
|
|
36
|
+
const increment = () => {
|
|
37
|
+
actions.increment();
|
|
38
|
+
totalReducer.actions.increment(count + 1);
|
|
39
|
+
};
|
|
40
|
+
const decrement = () => {
|
|
41
|
+
actions.decrement();
|
|
42
|
+
totalReducer.actions.decrement(count - 1);
|
|
43
|
+
};
|
|
44
|
+
const warningClass = count > 10 ? 'text-red-500' : '';
|
|
45
|
+
|
|
46
|
+
return html`
|
|
47
|
+
<div class="mt-10">
|
|
48
|
+
<div class="mb-2">
|
|
49
|
+
Counter: ${name}
|
|
50
|
+
<span>starts at ${meta?.start}</span>
|
|
51
|
+
</div>
|
|
52
|
+
<div class="flex flex-1 flex-row items-center text-gray-700">
|
|
53
|
+
<button class="bg-gray-300 text-gray-700 rounded hover:bg-gray-200 px-4 py-2 text-3xl focus:outline-none" @click=${decrement}>-</button>
|
|
54
|
+
<div class="mx-20">
|
|
55
|
+
<h1 class="text-3xl font-mono ${warningClass}">${count}</h1>
|
|
56
|
+
</div>
|
|
57
|
+
<button class="bg-gray-300 text-gray-700 rounded hover:bg-gray-200 px-4 py-2 text-3xl focus:outline-none" @click=${increment}>+</button>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
`;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export default createElement(import.meta, Counter);
|
example/elements/app-total.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createElement, html, useReducer } from '../../index.js';
|
|
2
|
+
import { totalReducer } from '../store.js';
|
|
3
|
+
|
|
4
|
+
const Total = () => {
|
|
5
|
+
const { total } = useReducer(totalReducer);
|
|
6
|
+
return html`
|
|
7
|
+
<div class="m-20">
|
|
8
|
+
<h1 class="text-xl font-mono">Total of 2 Counters: ${total}</h1>
|
|
9
|
+
</div>
|
|
10
|
+
`;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default createElement(import.meta, Total);
|
example/pages/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { createPage, html } from '../../index.js';
|
|
2
|
+
import '../elements/app-counter.js';
|
|
3
|
+
import '../elements/app-total.js';
|
|
4
|
+
|
|
5
|
+
const head = ({ config }) => {
|
|
6
|
+
return html` <title>${config.title}</title> `;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const body = () => {
|
|
10
|
+
return html`
|
|
11
|
+
<div class="flex flex-1 flex-col items-center justify-center">
|
|
12
|
+
<app-counter name="1" meta="{'start': 5}"></app-counter>
|
|
13
|
+
<app-counter name="2" meta="{'start': 7}"></app-counter>
|
|
14
|
+
</div class="mt-10">
|
|
15
|
+
<app-total></app-total>
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
`;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default createPage({
|
|
22
|
+
head,
|
|
23
|
+
body,
|
|
24
|
+
});
|
example/server.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import http from 'http';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { dirname } from 'path';
|
|
5
|
+
import renderPage from './pages/index.js';
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = dirname(__filename);
|
|
9
|
+
const port = process.argv[2] || 3000;
|
|
10
|
+
const elements = ['app-counter.js', 'app-total.js'];
|
|
11
|
+
const srcMap = {
|
|
12
|
+
'/index.js': `${__dirname}/../index.js`,
|
|
13
|
+
'/lit-html.js': `${__dirname}/../lit-html.js`,
|
|
14
|
+
'/store.js': `${__dirname}/store.js`,
|
|
15
|
+
};
|
|
16
|
+
elements.forEach((el) => {
|
|
17
|
+
srcMap['/elements/' + el] = `${__dirname}/elements/${el}`;
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
http
|
|
21
|
+
.createServer((req, res) => {
|
|
22
|
+
if (req.url.includes('/api/posts')) {
|
|
23
|
+
const parts = req.url.split('/');
|
|
24
|
+
const id = parts[parts.length - 1];
|
|
25
|
+
res.setHeader('Content-type', 'application/json');
|
|
26
|
+
res.end(
|
|
27
|
+
JSON.stringify({
|
|
28
|
+
id,
|
|
29
|
+
title: `post ${id}`,
|
|
30
|
+
description: ` description ${id}`,
|
|
31
|
+
}),
|
|
32
|
+
);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (req.url === '/') {
|
|
36
|
+
res.statusCode = 200;
|
|
37
|
+
res.setHeader('Content-type', 'text/html');
|
|
38
|
+
const html = renderPage({
|
|
39
|
+
lang: 'en',
|
|
40
|
+
props: {
|
|
41
|
+
config: { lang: 'en', title: 'Counter App' },
|
|
42
|
+
},
|
|
43
|
+
headScript: '',
|
|
44
|
+
bodyScript: `
|
|
45
|
+
<script type="module">
|
|
46
|
+
${elements.map((el) => `import './elements/${el}';`).join('\n')}
|
|
47
|
+
</script>
|
|
48
|
+
`,
|
|
49
|
+
});
|
|
50
|
+
res.end(html);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const filename = srcMap[req.url];
|
|
54
|
+
if (filename) {
|
|
55
|
+
const data = fs.readFileSync(filename);
|
|
56
|
+
res.setHeader('Content-type', 'application/javascript');
|
|
57
|
+
res.end(data);
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
.listen(parseInt(port));
|
|
61
|
+
|
|
62
|
+
console.log(`Server listening on http://localhost:${port}`);
|
example/store.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createReducer } from '../index.js';
|
|
2
|
+
|
|
3
|
+
export const totalReducer = createReducer({
|
|
4
|
+
initial: {
|
|
5
|
+
total: 0,
|
|
6
|
+
},
|
|
7
|
+
reducer: {
|
|
8
|
+
increment: (state) => ({ ...state, total: state.total + 1 }),
|
|
9
|
+
decrement: (state) => ({ ...state, total: state.total - 1 }),
|
|
10
|
+
},
|
|
11
|
+
});
|
index.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export type Config = {
|
|
2
|
+
version: string
|
|
3
|
+
url: string
|
|
4
|
+
image: string
|
|
5
|
+
author: string
|
|
6
|
+
languages: Array<string>
|
|
7
|
+
title: string
|
|
8
|
+
description: string
|
|
9
|
+
keywords: string
|
|
10
|
+
categories: Array<any>
|
|
11
|
+
tags: Array<any>
|
|
12
|
+
strings: {[key: string]: any}
|
|
13
|
+
themes: {[key: string]: any}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type Location = {
|
|
17
|
+
readonly ancestorOrigins: DOMStringList;
|
|
18
|
+
hash: string;
|
|
19
|
+
host: string;
|
|
20
|
+
hostname: string;
|
|
21
|
+
href: string;
|
|
22
|
+
readonly origin: string;
|
|
23
|
+
pathname: string;
|
|
24
|
+
port: string;
|
|
25
|
+
protocol: string;
|
|
26
|
+
search: string;
|
|
27
|
+
assign: (url: string | URL) => void;
|
|
28
|
+
reload: () => void;
|
|
29
|
+
replace: (url: string | URL) => void;
|
|
30
|
+
toString: () => string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
export type Reducer<P, Q, R> = {
|
|
35
|
+
getValue: () => P;
|
|
36
|
+
subscribe: (fn: (v: P) => void) => void;
|
|
37
|
+
} & { actions: { [K in keyof Q]: (v: any) => void}} & { effects: { [K in keyof R]: (v: any) => void}}
|
|
38
|
+
|
|
39
|
+
export type ReducerActions<P> = {[k: string]: (state: P, v: any) => P};
|
|
40
|
+
export type EffectActions<P, Q extends ReducerActions<P>> = {[k: string]: (actions: { [K in keyof Q]: (v: any) => void}, v: any) => void};
|
|
41
|
+
|
|
42
|
+
export function createReducer<P, Q extends ReducerActions<P>>(props: { initial: P, reducer: Q }): Reducer<P, Q>;
|
|
43
|
+
|
|
44
|
+
export function useReducer<P, Q extends ReducerActions<P>, R extends EffectActions<P, Q>>(props: Reducer<P, Q, R> | { initial: P, reducer: Q, effects: R }) : P & Reducer<P, Q, R>;
|
|
45
|
+
|
|
46
|
+
export function createElement(meta: any, renderFn: any): any
|
|
47
|
+
export type Handler = (props: any) => string;
|
|
48
|
+
export function createPage(props: { head: Handler, body: Handler}): (props: { props: any, headScript: string, bodyScript: string }) => string;
|
index.js
ADDED
|
@@ -0,0 +1,1185 @@
|
|
|
1
|
+
import { html, render as litRender, directive, NodePart, isPrimitive } from './lit-html.js';
|
|
2
|
+
|
|
3
|
+
const isBrowser = typeof window !== 'undefined';
|
|
4
|
+
export { html, isBrowser };
|
|
5
|
+
|
|
6
|
+
const lastAttributeNameRegex =
|
|
7
|
+
/([ \x09\x0a\x0c\x0d])([^\0-\x1F\x7F-\x9F "'>=/]+)([ \x09\x0a\x0c\x0d]*=[ \x09\x0a\x0c\x0d]*(?:[^ \x09\x0a\x0c\x0d"'`<>=]*|"[^"]*|'[^']*))$/;
|
|
8
|
+
const tagRE = /<[a-zA-Z0-9\-\!\/](?:"[^"]*"|'[^']*'|[^'">])*>/g;
|
|
9
|
+
const whitespaceRE = /^\s*$/;
|
|
10
|
+
const attrRE = /\s([^'"/\s><]+?)[\s/>]|([^\s=]+)=\s?(".*?"|'.*?')/g;
|
|
11
|
+
const voidElements = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr'];
|
|
12
|
+
const STRIP_COMMENTS = /(\/\/.*$)|(\/\*[\s\S]*?\*\/)|(\s*=[^,\)]*(('(?:\\'|[^'\r\n])*')|("(?:\\"|[^"\r\n])*"))|(\s*=[^,\)]*))/gm;
|
|
13
|
+
const ARGUMENT_NAMES = /([^\s,]+)/g;
|
|
14
|
+
|
|
15
|
+
const parseFuncParams = (func) => {
|
|
16
|
+
const fnStr = func.toString().replace(STRIP_COMMENTS, '');
|
|
17
|
+
const result = fnStr.slice(fnStr.indexOf('(') + 1, fnStr.indexOf(')')).match(ARGUMENT_NAMES);
|
|
18
|
+
return (result || []).filter((it) => it !== '{' && it !== '}');
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const parseTag = (tag) => {
|
|
22
|
+
const res = {
|
|
23
|
+
type: 'tag',
|
|
24
|
+
name: '',
|
|
25
|
+
voidElement: false,
|
|
26
|
+
attrs: {},
|
|
27
|
+
children: [],
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const tagMatch = tag.match(/<\/?([^\s]+?)[/\s>]/);
|
|
31
|
+
if (tagMatch) {
|
|
32
|
+
res.name = tagMatch[1];
|
|
33
|
+
if (voidElements.includes(tagMatch[1]) || tag.charAt(tag.length - 2) === '/') {
|
|
34
|
+
res.voidElement = true;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// handle comment tag
|
|
38
|
+
if (res.name.startsWith('!--')) {
|
|
39
|
+
const endIndex = tag.indexOf('-->');
|
|
40
|
+
return {
|
|
41
|
+
type: 'comment',
|
|
42
|
+
comment: endIndex !== -1 ? tag.slice(4, endIndex) : '',
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const reg = new RegExp(attrRE);
|
|
48
|
+
let result = null;
|
|
49
|
+
for (;;) {
|
|
50
|
+
result = reg.exec(tag);
|
|
51
|
+
|
|
52
|
+
if (result === null) {
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!result[0].trim()) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (result[1]) {
|
|
61
|
+
const attr = result[1].trim();
|
|
62
|
+
let arr = [attr, ''];
|
|
63
|
+
|
|
64
|
+
if (attr.indexOf('=') > -1) {
|
|
65
|
+
arr = attr.split('=');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
res.attrs[arr[0]] = arr[1];
|
|
69
|
+
reg.lastIndex--;
|
|
70
|
+
} else if (result[2]) {
|
|
71
|
+
res.attrs[result[2]] = result[3].trim().substring(1, result[3].length - 1);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return res;
|
|
76
|
+
};
|
|
77
|
+
const parseHtml = (html) => {
|
|
78
|
+
const result = [];
|
|
79
|
+
const arr = [];
|
|
80
|
+
let current;
|
|
81
|
+
let level = -1;
|
|
82
|
+
|
|
83
|
+
// handle text at top level
|
|
84
|
+
if (html.indexOf('<') !== 0) {
|
|
85
|
+
var end = html.indexOf('<');
|
|
86
|
+
result.push({
|
|
87
|
+
type: 'text',
|
|
88
|
+
content: end === -1 ? html : html.substring(0, end),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
html.replace(tagRE, function (tag, index) {
|
|
93
|
+
const isOpen = tag.charAt(1) !== '/';
|
|
94
|
+
const isComment = tag.startsWith('<!--');
|
|
95
|
+
const start = index + tag.length;
|
|
96
|
+
const nextChar = html.charAt(start);
|
|
97
|
+
let parent;
|
|
98
|
+
|
|
99
|
+
if (isComment) {
|
|
100
|
+
const comment = parseTag(tag);
|
|
101
|
+
|
|
102
|
+
// if we're at root, push new base node
|
|
103
|
+
if (level < 0) {
|
|
104
|
+
result.push(comment);
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
parent = arr[level];
|
|
108
|
+
parent.children.push(comment);
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (isOpen) {
|
|
113
|
+
level++;
|
|
114
|
+
|
|
115
|
+
current = parseTag(tag);
|
|
116
|
+
|
|
117
|
+
if (!current.voidElement && nextChar && nextChar !== '<') {
|
|
118
|
+
current.children.push({
|
|
119
|
+
type: 'text',
|
|
120
|
+
content: html.slice(start, html.indexOf('<', start)),
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// if we're at root, push new base node
|
|
125
|
+
if (level === 0) {
|
|
126
|
+
result.push(current);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
parent = arr[level - 1];
|
|
130
|
+
|
|
131
|
+
if (parent) {
|
|
132
|
+
parent.children.push(current);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
arr[level] = current;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (!isOpen || current.voidElement) {
|
|
139
|
+
if (level > -1 && (current.voidElement || current.name === tag.slice(2, -1))) {
|
|
140
|
+
level--;
|
|
141
|
+
// move current up a level to match the end tag
|
|
142
|
+
current = level === -1 ? result : arr[level];
|
|
143
|
+
}
|
|
144
|
+
if (nextChar !== '<' && nextChar) {
|
|
145
|
+
// trailing text node
|
|
146
|
+
// if we're at the root, push a base text node. otherwise add as
|
|
147
|
+
// a child to the current node.
|
|
148
|
+
parent = level === -1 ? result : arr[level].children;
|
|
149
|
+
|
|
150
|
+
// calculate correct end of the content slice in case there's
|
|
151
|
+
// no tag after the text node.
|
|
152
|
+
const end = html.indexOf('<', start);
|
|
153
|
+
let content = html.slice(start, end === -1 ? undefined : end);
|
|
154
|
+
// if a node is nothing but whitespace, collapse it as the spec states:
|
|
155
|
+
// https://www.w3.org/TR/html4/struct/text.html#h-9.1
|
|
156
|
+
if (whitespaceRE.test(content)) {
|
|
157
|
+
content = ' ';
|
|
158
|
+
}
|
|
159
|
+
// don't add whitespace-only text nodes if they would be trailing text nodes
|
|
160
|
+
// or if they would be leading whitespace-only text nodes:
|
|
161
|
+
// * end > -1 indicates this is not a trailing text node
|
|
162
|
+
// * leading node is when level is -1 and parent has length 0
|
|
163
|
+
if ((end > -1 && level + parent.length >= 0) || content !== ' ') {
|
|
164
|
+
parent.push({
|
|
165
|
+
type: 'text',
|
|
166
|
+
content: content,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
return result;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const stringifyAttrs = (attrs) => {
|
|
177
|
+
const buff = [];
|
|
178
|
+
for (let key in attrs) {
|
|
179
|
+
buff.push(key + '="' + attrs[key] + '"');
|
|
180
|
+
}
|
|
181
|
+
if (!buff.length) {
|
|
182
|
+
return '';
|
|
183
|
+
}
|
|
184
|
+
return ' ' + buff.join(' ');
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const stringifyHtml = (buff, doc) => {
|
|
188
|
+
switch (doc.type) {
|
|
189
|
+
case 'text':
|
|
190
|
+
return buff + doc.content;
|
|
191
|
+
case 'tag':
|
|
192
|
+
buff += '<' + doc.name + (doc.attrs ? stringifyAttrs(doc.attrs) : '') + (doc.voidElement ? '/>' : '>');
|
|
193
|
+
if (doc.voidElement) {
|
|
194
|
+
return buff;
|
|
195
|
+
}
|
|
196
|
+
return buff + doc.children.reduce(stringifyHtml, '') + '</' + doc.name + '>';
|
|
197
|
+
case 'comment':
|
|
198
|
+
buff += '<!--' + doc.comment + '-->';
|
|
199
|
+
return buff;
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const hydrate = (node) => {
|
|
204
|
+
const Clazz = getElement(node.name);
|
|
205
|
+
if (Clazz) {
|
|
206
|
+
const newAttrs = {};
|
|
207
|
+
Object.keys(node.attrs).forEach((key) => {
|
|
208
|
+
const newValue = node.attrs[key];
|
|
209
|
+
newAttrs[key] = newValue && newValue.startsWith(`{`) ? JSON.parse(newValue.replace(/'/g, `"`)) : newValue;
|
|
210
|
+
});
|
|
211
|
+
const instance = new Clazz(newAttrs);
|
|
212
|
+
const res = instance.render();
|
|
213
|
+
node.children = parseHtml(res);
|
|
214
|
+
}
|
|
215
|
+
if (node.children) {
|
|
216
|
+
for (const child of node.children) {
|
|
217
|
+
hydrate(child);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const wrapAttribute = (attrName, suffix, text, v) => {
|
|
223
|
+
let buffer = text;
|
|
224
|
+
const hasQuote = suffix && suffix.includes(`="`);
|
|
225
|
+
if (attrName && !hasQuote) {
|
|
226
|
+
buffer += `"`;
|
|
227
|
+
}
|
|
228
|
+
buffer += v;
|
|
229
|
+
if (attrName && !hasQuote) {
|
|
230
|
+
buffer += `"`;
|
|
231
|
+
}
|
|
232
|
+
return buffer;
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
export const renderHtml = isBrowser
|
|
236
|
+
? litRender
|
|
237
|
+
: (template) => {
|
|
238
|
+
let js = '';
|
|
239
|
+
template.strings.forEach((text, i) => {
|
|
240
|
+
const value = template.values[i];
|
|
241
|
+
const type = typeof value;
|
|
242
|
+
let attrName, suffix;
|
|
243
|
+
const matchName = lastAttributeNameRegex.exec(text);
|
|
244
|
+
if (matchName) {
|
|
245
|
+
attrName = matchName[2];
|
|
246
|
+
suffix = matchName[3];
|
|
247
|
+
}
|
|
248
|
+
if (value === null || !(type === 'object' || type === 'function' || type === 'undefined')) {
|
|
249
|
+
js += wrapAttribute(attrName, suffix, text, type !== 'string' ? String(value) : value);
|
|
250
|
+
} else if (Array.isArray(value) && value.find((item) => item && item.strings && item.type === 'html')) {
|
|
251
|
+
js += text;
|
|
252
|
+
value.forEach((v) => {
|
|
253
|
+
js += renderHtml(v);
|
|
254
|
+
});
|
|
255
|
+
} else if (type === 'object') {
|
|
256
|
+
// TemplateResult
|
|
257
|
+
if (value.strings && value.type === 'html') {
|
|
258
|
+
js += text;
|
|
259
|
+
js += renderHtml(value);
|
|
260
|
+
} else {
|
|
261
|
+
js += wrapAttribute(attrName, suffix, text, JSON.stringify(value).replace(/"/g, `'`));
|
|
262
|
+
}
|
|
263
|
+
} else if (type == 'function') {
|
|
264
|
+
if (attrName) {
|
|
265
|
+
js += text.replace(' ' + attrName + '=', '');
|
|
266
|
+
} else {
|
|
267
|
+
// js += text;
|
|
268
|
+
// js += value();
|
|
269
|
+
}
|
|
270
|
+
} else if (type !== 'undefined') {
|
|
271
|
+
js += text;
|
|
272
|
+
js += value.toString();
|
|
273
|
+
} else {
|
|
274
|
+
js += text;
|
|
275
|
+
// console.log('value', value);
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
const nodes = parseHtml(js);
|
|
279
|
+
for (const node of nodes) {
|
|
280
|
+
hydrate(node);
|
|
281
|
+
}
|
|
282
|
+
const html = nodes.reduce((acc, node) => {
|
|
283
|
+
return acc + stringifyHtml('', node);
|
|
284
|
+
}, '');
|
|
285
|
+
return html;
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
const hyphenate = (s) => s.replace(/[A-Z]|^ms/g, '-$&').toLowerCase();
|
|
289
|
+
const percent = (v) => (v * 100).toFixed(2) + '%';
|
|
290
|
+
const createStyle = (...kvs) => {
|
|
291
|
+
const style = {};
|
|
292
|
+
for (let i = 0; i < kvs.length; i += 2) {
|
|
293
|
+
style[hyphenate(kvs[i])] = kvs[i + 1];
|
|
294
|
+
}
|
|
295
|
+
return style;
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
const mapApply = (obj) =>
|
|
299
|
+
Object.keys(obj.keys).reduce((acc, key) => {
|
|
300
|
+
Object.keys(obj.values).map((vkey) => {
|
|
301
|
+
const suffix = vkey ? '-' + vkey : '';
|
|
302
|
+
const className = `${key}${suffix}`;
|
|
303
|
+
if (Array.isArray(obj.keys[key])) {
|
|
304
|
+
const args = [];
|
|
305
|
+
obj.keys[key].forEach((kk) => {
|
|
306
|
+
args.push(kk, obj.values[vkey]);
|
|
307
|
+
});
|
|
308
|
+
acc[className] = createStyle(...args);
|
|
309
|
+
} else {
|
|
310
|
+
acc[className] = createStyle(obj.keys[key], obj.values[vkey]);
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
return acc;
|
|
314
|
+
}, {});
|
|
315
|
+
|
|
316
|
+
export const css = (obj, isChild = false, indent = '') => {
|
|
317
|
+
const cssText = Object.keys(obj).reduce((acc, key) => {
|
|
318
|
+
const value = obj[key];
|
|
319
|
+
acc += !isChild ? `${key} {\n` : '';
|
|
320
|
+
if (typeof value === 'object') {
|
|
321
|
+
acc += '\n' + css(value, true, indent + ' ');
|
|
322
|
+
} else {
|
|
323
|
+
acc += ' ' + indent + hyphenate(key) + ': ' + value + ';\n';
|
|
324
|
+
}
|
|
325
|
+
acc += !isChild ? `\n}\n` : '';
|
|
326
|
+
return acc;
|
|
327
|
+
}, '');
|
|
328
|
+
return cssText;
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
const extractClasses = (html) => {
|
|
332
|
+
let str = '';
|
|
333
|
+
const matches = html.match(/class=(?:["']\W+\s*(?:\w+)\()?["']([^'"]+)['"]/gim);
|
|
334
|
+
if (matches) {
|
|
335
|
+
matches.forEach((matched, i) => {
|
|
336
|
+
str += matched.replace('class="', '').replace('"', '') + (i === matches.length - 1 ? '' : ' ');
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
return str;
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
const getClassList = (template) => {
|
|
343
|
+
// need to have this incase of shadowDom
|
|
344
|
+
// const classes = template.strings.reduce((acc, item) => acc + extractClasses(item), '').split(' ');
|
|
345
|
+
const classes = [];
|
|
346
|
+
template.values.forEach((item) => {
|
|
347
|
+
if (typeof item === 'string') {
|
|
348
|
+
const list = item.split(' ');
|
|
349
|
+
return classes.push(...list.filter((cls) => classLookup[cls]));
|
|
350
|
+
}
|
|
351
|
+
return false;
|
|
352
|
+
});
|
|
353
|
+
return classes;
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
export const compileTw = (classList) => {
|
|
357
|
+
let styleSheet = ``;
|
|
358
|
+
classList.forEach((cls) => {
|
|
359
|
+
const item = classLookup[cls];
|
|
360
|
+
if (item) {
|
|
361
|
+
const className = cls.replace(`/`, `\\/`);
|
|
362
|
+
styleSheet += `
|
|
363
|
+
.${className} {
|
|
364
|
+
${Object.keys(item)
|
|
365
|
+
.map((key) => `${key}: ${item[key]};`)
|
|
366
|
+
.join('\n')}
|
|
367
|
+
}
|
|
368
|
+
`;
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
return styleSheet;
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
const colors = {
|
|
375
|
+
keys: {
|
|
376
|
+
bg: 'backgroundColor',
|
|
377
|
+
text: 'color',
|
|
378
|
+
divide: 'borderColor',
|
|
379
|
+
border: 'borderColor',
|
|
380
|
+
ring: '--tw-ring-color',
|
|
381
|
+
'border-l': 'borderLeftColor',
|
|
382
|
+
'border-r': 'borderRightColor',
|
|
383
|
+
'border-t': 'borderTopColor',
|
|
384
|
+
'border-b': 'borderBottomColor',
|
|
385
|
+
},
|
|
386
|
+
values: {
|
|
387
|
+
transparent: 'transparent',
|
|
388
|
+
current: 'currentColor',
|
|
389
|
+
black: 'rgba(0, 0, 0, 1)',
|
|
390
|
+
white: 'rgba(255, 255, 255, 1)',
|
|
391
|
+
'gray-50': 'rgba(249, 250, 251, 1)',
|
|
392
|
+
'gray-100': 'rgba(243, 244, 246, 1)',
|
|
393
|
+
'gray-200': 'rgba(229, 231, 235, 1)',
|
|
394
|
+
'gray-300': 'rgba(209, 213, 219, 1)',
|
|
395
|
+
'gray-400': 'rgba(156, 163, 175, 1)',
|
|
396
|
+
'gray-500': 'rgba(107, 114, 128, 1)',
|
|
397
|
+
'gray-600': 'rgba(75, 85, 99, 1)',
|
|
398
|
+
'gray-700': 'rgba(55, 65, 81, 1)',
|
|
399
|
+
'gray-800': 'rgba(31, 41, 55, 1)',
|
|
400
|
+
'gray-900': 'rgba(17, 24, 39, 1)',
|
|
401
|
+
'red-50': 'rgba(254, 242, 242, 1)',
|
|
402
|
+
'red-100': 'rgba(254, 226, 226, 1)',
|
|
403
|
+
'red-200': 'rgba(254, 202, 202, 1)',
|
|
404
|
+
'red-300': 'rgba(252, 165, 165, 1)',
|
|
405
|
+
'red-400': 'rgba(248, 113, 113, 1)',
|
|
406
|
+
'red-500': 'rgba(239, 68, 68, 1)',
|
|
407
|
+
'red-600': 'rgba(220, 38, 38, 1)',
|
|
408
|
+
'red-700': 'rgba(185, 28, 28, 1)',
|
|
409
|
+
'red-800': 'rgba(153, 27, 27, 1)',
|
|
410
|
+
'red-900': 'rgba(127, 29, 29, 1)',
|
|
411
|
+
'yellow-50': 'rgba(255, 251, 235, 1)',
|
|
412
|
+
'yellow-100': 'rgba(254, 243, 199, 1)',
|
|
413
|
+
'yellow-200': 'rgba(253, 230, 138, 1)',
|
|
414
|
+
'yellow-300': 'rgba(252, 211, 77, 1)',
|
|
415
|
+
'yellow-400': 'rgba(251, 191, 36, 1)',
|
|
416
|
+
'yellow-500': 'rgba(245, 158, 11, 1)',
|
|
417
|
+
'yellow-600': 'rgba(217, 119, 6, 1)',
|
|
418
|
+
'yellow-700': 'rgba(180, 83, 9, 1)',
|
|
419
|
+
'yellow-800': 'rgba(146, 64, 14, 1)',
|
|
420
|
+
'yellow-900': 'rgba(120, 53, 15, 1)',
|
|
421
|
+
'green-50': 'rgba(236, 253, 245, 1)',
|
|
422
|
+
'green-100': 'rgba(209, 250, 229, 1)',
|
|
423
|
+
'green-200': 'rgba(167, 243, 208, 1)',
|
|
424
|
+
'green-300': 'rgba(110, 231, 183, 1)',
|
|
425
|
+
'green-400': 'rgba(52, 211, 153, 1)',
|
|
426
|
+
'green-500': 'rgba(16, 185, 129, 1)',
|
|
427
|
+
'green-600': 'rgba(5, 150, 105, 1)',
|
|
428
|
+
'green-700': 'rgba(4, 120, 87, 1)',
|
|
429
|
+
'green-800': 'rgba(6, 95, 70, 1)',
|
|
430
|
+
'green-900': 'rgba(6, 78, 59, 1)',
|
|
431
|
+
'blue-50': 'rgba(239, 246, 255, 1)',
|
|
432
|
+
'blue-100': 'rgba(219, 234, 254, 1)',
|
|
433
|
+
'blue-200': 'rgba(191, 219, 254, 1)',
|
|
434
|
+
'blue-300': 'rgba(147, 197, 253, 1)',
|
|
435
|
+
'blue-400': 'rgba(96, 165, 250, 1)',
|
|
436
|
+
'blue-500': 'rgba(59, 130, 246, 1)',
|
|
437
|
+
'blue-600': 'rgba(37, 99, 235, 1)',
|
|
438
|
+
'blue-700': 'rgba(29, 78, 216, 1)',
|
|
439
|
+
'blue-800': 'rgba(30, 64, 175, 1)',
|
|
440
|
+
'blue-900': 'rgba(30, 58, 138, 1)',
|
|
441
|
+
'indigo-50': 'rgba(238, 242, 255, 1)',
|
|
442
|
+
'indigo-100': 'rgba(224, 231, 255, 1)',
|
|
443
|
+
'indigo-200': 'rgba(199, 210, 254, 1)',
|
|
444
|
+
'indigo-300': 'rgba(165, 180, 252, 1)',
|
|
445
|
+
'indigo-400': 'rgba(129, 140, 248, 1)',
|
|
446
|
+
'indigo-500': 'rgba(99, 102, 241, 1)',
|
|
447
|
+
'indigo-600': 'rgba(79, 70, 229, 1)',
|
|
448
|
+
'indigo-700': 'rgba(67, 56, 202, 1)',
|
|
449
|
+
'indigo-800': 'rgba(55, 48, 163, 1)',
|
|
450
|
+
'indigo-900': 'rgba(49, 46, 129, 1)',
|
|
451
|
+
'purple-50': 'rgba(245, 243, 255, 1)',
|
|
452
|
+
'purple-100': 'rgba(237, 233, 254, 1)',
|
|
453
|
+
'purple-200': 'rgba(221, 214, 254, 1)',
|
|
454
|
+
'purple-300': 'rgba(196, 181, 253, 1)',
|
|
455
|
+
'purple-400': 'rgba(167, 139, 250, 1)',
|
|
456
|
+
'purple-500': 'rgba(139, 92, 246, 1)',
|
|
457
|
+
'purple-600': 'rgba(124, 58, 237, 1)',
|
|
458
|
+
'purple-700': 'rgba(109, 40, 217, 1)',
|
|
459
|
+
'purple-800': 'rgba(91, 33, 182, 1)',
|
|
460
|
+
'purple-900': 'rgba(76, 29, 149, 1)',
|
|
461
|
+
'pink-50': 'rgba(253, 242, 248, 1)',
|
|
462
|
+
'pink-100': 'rgba(252, 231, 243, 1)',
|
|
463
|
+
'pink-200': 'rgba(251, 207, 232, 1)',
|
|
464
|
+
'pink-300': 'rgba(249, 168, 212, 1)',
|
|
465
|
+
'pink-400': 'rgba(244, 114, 182, 1)',
|
|
466
|
+
'pink-500': 'rgba(236, 72, 153, 1)',
|
|
467
|
+
'pink-600': 'rgba(219, 39, 119, 1)',
|
|
468
|
+
'pink-700': 'rgba(190, 24, 93, 1)',
|
|
469
|
+
'pink-800': 'rgba(157, 23, 77, 1)',
|
|
470
|
+
'pink-900': 'rgba(131, 24, 67, 1)',
|
|
471
|
+
},
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
const spacing = {
|
|
475
|
+
keys: {
|
|
476
|
+
mr: 'marginRight',
|
|
477
|
+
ml: 'marginLeft',
|
|
478
|
+
mt: 'marginTop',
|
|
479
|
+
mb: 'marginBottom',
|
|
480
|
+
mx: ['marginLeft', 'marginRight'],
|
|
481
|
+
my: ['marginTop', 'marginBottom'],
|
|
482
|
+
m: 'margin',
|
|
483
|
+
pr: 'paddingRight',
|
|
484
|
+
pl: 'paddingLeft',
|
|
485
|
+
pt: 'paddingTop',
|
|
486
|
+
pb: 'paddingBottom',
|
|
487
|
+
px: ['paddingLeft', 'paddingRight'],
|
|
488
|
+
py: ['paddingTop', 'paddingBottom'],
|
|
489
|
+
p: 'padding',
|
|
490
|
+
},
|
|
491
|
+
values: {
|
|
492
|
+
auto: 'auto',
|
|
493
|
+
0: '0px',
|
|
494
|
+
px: '1px',
|
|
495
|
+
0.5: '0.125rem',
|
|
496
|
+
1: '0.25rem',
|
|
497
|
+
1.5: '0.375rem',
|
|
498
|
+
2: '0.5rem',
|
|
499
|
+
2.5: '0.625rem',
|
|
500
|
+
3: '0.75rem',
|
|
501
|
+
3.5: '0.875rem',
|
|
502
|
+
4: '1rem',
|
|
503
|
+
5: '1.25rem',
|
|
504
|
+
6: '1.5rem',
|
|
505
|
+
7: '1.75rem',
|
|
506
|
+
8: '2rem',
|
|
507
|
+
9: '2.25rem',
|
|
508
|
+
10: '2.5rem',
|
|
509
|
+
11: '2.75rem',
|
|
510
|
+
12: '3rem',
|
|
511
|
+
14: '3.5rem',
|
|
512
|
+
16: '4rem',
|
|
513
|
+
20: '5rem',
|
|
514
|
+
24: '6rem',
|
|
515
|
+
28: '7rem',
|
|
516
|
+
32: '8rem',
|
|
517
|
+
36: '9rem',
|
|
518
|
+
40: '10rem',
|
|
519
|
+
44: '11rem',
|
|
520
|
+
48: '12rem',
|
|
521
|
+
52: '13rem',
|
|
522
|
+
56: '14rem',
|
|
523
|
+
60: '15rem',
|
|
524
|
+
64: '16rem',
|
|
525
|
+
72: '18rem',
|
|
526
|
+
80: '20rem',
|
|
527
|
+
96: '24rem',
|
|
528
|
+
},
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
const radius = {
|
|
532
|
+
keys: {
|
|
533
|
+
rounded: 'borderRadius',
|
|
534
|
+
'rounded-t': 'borderTopRadius',
|
|
535
|
+
'rounded-r': 'borderRightRadius',
|
|
536
|
+
'rounded-l': 'borderLeftRadius',
|
|
537
|
+
'rounded-b': 'borderBottomRadius',
|
|
538
|
+
'rounded-tl': ['borderTopRadius', 'borderLeftRadius'],
|
|
539
|
+
'rounded-tr': ['borderTopRadius', 'borderRightRadius'],
|
|
540
|
+
'rounded-bl': ['borderBottomRadius', 'borderLeftRadius'],
|
|
541
|
+
'rounded-br': ['borderBottomRadius', 'borderRightRadius'],
|
|
542
|
+
},
|
|
543
|
+
values: {
|
|
544
|
+
none: '0px',
|
|
545
|
+
sm: '0.125rem',
|
|
546
|
+
'': '0.25rem',
|
|
547
|
+
md: '0.375rem',
|
|
548
|
+
lg: '0.5rem',
|
|
549
|
+
xl: '0.75rem',
|
|
550
|
+
'2xl': '1rem',
|
|
551
|
+
'3xl': '1.5rem',
|
|
552
|
+
full: '9999px',
|
|
553
|
+
},
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
const borders = {
|
|
557
|
+
keys: {
|
|
558
|
+
border: 'borderWidth',
|
|
559
|
+
'border-l': 'borderLeftWidth',
|
|
560
|
+
'border-r': 'borderRightWidth',
|
|
561
|
+
'border-t': 'borderTopWidth',
|
|
562
|
+
'border-b': 'borderBottomWidth',
|
|
563
|
+
},
|
|
564
|
+
values: {
|
|
565
|
+
'': '1px',
|
|
566
|
+
0: '0px',
|
|
567
|
+
2: '2px',
|
|
568
|
+
4: '4px',
|
|
569
|
+
8: '8px',
|
|
570
|
+
},
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
const sizes = {
|
|
574
|
+
keys: {
|
|
575
|
+
h: 'height',
|
|
576
|
+
w: 'width',
|
|
577
|
+
top: 'top',
|
|
578
|
+
left: 'left',
|
|
579
|
+
bottom: 'bottom',
|
|
580
|
+
right: 'right',
|
|
581
|
+
minh: 'minHeight',
|
|
582
|
+
minw: 'minWidth',
|
|
583
|
+
maxh: 'maxHeight',
|
|
584
|
+
maxw: 'maxWidth',
|
|
585
|
+
},
|
|
586
|
+
values: {
|
|
587
|
+
0: '0px',
|
|
588
|
+
px: '1px',
|
|
589
|
+
0.5: '0.125rem',
|
|
590
|
+
1: '0.25rem',
|
|
591
|
+
1.5: '0.375rem',
|
|
592
|
+
2: '0.5rem',
|
|
593
|
+
2.5: '0.625rem',
|
|
594
|
+
3: '0.75rem',
|
|
595
|
+
3.5: '0.875rem',
|
|
596
|
+
4: '1rem',
|
|
597
|
+
5: '1.25rem',
|
|
598
|
+
6: '1.5rem',
|
|
599
|
+
7: '1.75rem',
|
|
600
|
+
8: '2rem',
|
|
601
|
+
9: '2.25rem',
|
|
602
|
+
10: '2.5rem',
|
|
603
|
+
11: '2.75rem',
|
|
604
|
+
12: '3rem',
|
|
605
|
+
14: '3.5rem',
|
|
606
|
+
16: '4rem',
|
|
607
|
+
20: '5rem',
|
|
608
|
+
24: '6rem',
|
|
609
|
+
28: '7rem',
|
|
610
|
+
32: '8rem',
|
|
611
|
+
36: '9rem',
|
|
612
|
+
40: '10rem',
|
|
613
|
+
44: '11rem',
|
|
614
|
+
48: '12rem',
|
|
615
|
+
52: '13rem',
|
|
616
|
+
56: '14rem',
|
|
617
|
+
60: '15rem',
|
|
618
|
+
64: '16rem',
|
|
619
|
+
72: '18rem',
|
|
620
|
+
80: '20rem',
|
|
621
|
+
96: '24rem',
|
|
622
|
+
auto: 'auto',
|
|
623
|
+
min: 'min-content',
|
|
624
|
+
max: 'max-content',
|
|
625
|
+
'1/2': percent(1 / 2),
|
|
626
|
+
'1/4': percent(1 / 4),
|
|
627
|
+
'2/4': percent(2 / 4),
|
|
628
|
+
'3/4': percent(3 / 4),
|
|
629
|
+
'1/5': percent(1 / 5),
|
|
630
|
+
'2/5': percent(2 / 5),
|
|
631
|
+
'3/5': percent(3 / 5),
|
|
632
|
+
'4/5': percent(4 / 5),
|
|
633
|
+
'1/6': percent(1 / 6),
|
|
634
|
+
'2/6': percent(2 / 6),
|
|
635
|
+
'3/6': percent(3 / 6),
|
|
636
|
+
'4/6': percent(4 / 6),
|
|
637
|
+
'5/6': percent(5 / 6),
|
|
638
|
+
'1/12': percent(1 / 12),
|
|
639
|
+
'2/12': percent(2 / 12),
|
|
640
|
+
'3/12': percent(3 / 12),
|
|
641
|
+
'4/12': percent(4 / 12),
|
|
642
|
+
'5/12': percent(5 / 12),
|
|
643
|
+
'6/12': percent(6 / 12),
|
|
644
|
+
'7/12': percent(7 / 12),
|
|
645
|
+
'8/12': percent(8 / 12),
|
|
646
|
+
'9/12': percent(9 / 12),
|
|
647
|
+
'10/12': percent(10 / 12),
|
|
648
|
+
'11/12': percent(11 / 12),
|
|
649
|
+
full: percent(1),
|
|
650
|
+
},
|
|
651
|
+
};
|
|
652
|
+
|
|
653
|
+
const classLookup = {
|
|
654
|
+
flex: createStyle('display', 'flex'),
|
|
655
|
+
'inline-flex': createStyle('display', 'inline-flex'),
|
|
656
|
+
block: createStyle('display', 'block'),
|
|
657
|
+
'inline-block': createStyle('display', 'inline-block'),
|
|
658
|
+
inline: createStyle('display', 'inline'),
|
|
659
|
+
table: createStyle('display', 'table'),
|
|
660
|
+
'inline-table': createStyle('display', 'inline-table'),
|
|
661
|
+
'inline-table': createStyle('display', 'inline-table'),
|
|
662
|
+
grid: createStyle('display', 'grid'),
|
|
663
|
+
'inline-grid': createStyle('display', 'inline-grid'),
|
|
664
|
+
contents: createStyle('display', 'contents'),
|
|
665
|
+
'list-item': createStyle('display', 'list-item'),
|
|
666
|
+
hidden: createStyle('display', 'none'),
|
|
667
|
+
'flex-1': createStyle('flex', '1'),
|
|
668
|
+
'flex-row': createStyle('flexDirection', 'row'),
|
|
669
|
+
'flex-col': createStyle('flexDirection', 'column'),
|
|
670
|
+
'flex-wrap': createStyle('flexWrap', 'wrap'),
|
|
671
|
+
'flex-nowrap': createStyle('flexWrap', 'nowrap'),
|
|
672
|
+
'flex-wrap-reverse': createStyle('flexWrap', 'wrap-reverse'),
|
|
673
|
+
'items-baseline': createStyle('alignItems', 'baseline'),
|
|
674
|
+
'items-start': createStyle('alignItems', 'flex-start'),
|
|
675
|
+
'items-center': createStyle('alignItems', 'center'),
|
|
676
|
+
'items-end': createStyle('alignItems', 'flex-end'),
|
|
677
|
+
'items-stretch': createStyle('alignItems', 'stretch'),
|
|
678
|
+
'justify-start': createStyle('justifyContent', 'flex-start'),
|
|
679
|
+
'justify-end': createStyle('justifyContent', 'flex-end'),
|
|
680
|
+
'justify-center': createStyle('justifyContent', 'center'),
|
|
681
|
+
'justify-between': createStyle('justifyContent', 'space-between'),
|
|
682
|
+
'justify-around': createStyle('justifyContent', 'space-around'),
|
|
683
|
+
'justify-evenly': createStyle('justifyContent', 'space-evenly'),
|
|
684
|
+
'text-left': createStyle('textAlign', 'left'),
|
|
685
|
+
'text-center': createStyle('textAlign', 'center'),
|
|
686
|
+
'text-right': createStyle('textAlign', 'right'),
|
|
687
|
+
'text-justify': createStyle('textAlign', 'justify'),
|
|
688
|
+
underline: createStyle('textDecoration', 'underline'),
|
|
689
|
+
'line-through': createStyle('textDecoration', 'line-through'),
|
|
690
|
+
'no-underline': createStyle('textDecoration', 'none'),
|
|
691
|
+
'whitespace-normal': createStyle('whiteSpace', 'normal'),
|
|
692
|
+
'whitespace-nowrap': createStyle('whiteSpace', 'nowrap'),
|
|
693
|
+
'whitespace-pre': createStyle('whiteSpace', 'pre'),
|
|
694
|
+
'whitespace-pre-line': createStyle('whiteSpace', 'pre-line'),
|
|
695
|
+
'whitespace-pre-wrap': createStyle('whiteSpace', 'pre-wrap'),
|
|
696
|
+
'break-normal': createStyle('wordBreak', 'normal', 'overflowWrap', 'normal'),
|
|
697
|
+
'break-words': createStyle('wordBreak', 'break-word'),
|
|
698
|
+
'break-all': createStyle('wordBreak', 'break-all'),
|
|
699
|
+
'font-sans': createStyle(
|
|
700
|
+
'fontFamily',
|
|
701
|
+
`ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"`,
|
|
702
|
+
),
|
|
703
|
+
'font-serif': createStyle('fontFamily', `ui-serif, Georgia, Cambria, "Times New Roman", Times, serif`),
|
|
704
|
+
'font-mono': createStyle('fontFamily', `ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace`),
|
|
705
|
+
'font-thin': createStyle('fontWeight', '100'),
|
|
706
|
+
'font-extralight': createStyle('fontWeight', '200'),
|
|
707
|
+
'font-light': createStyle('fontWeight', '300'),
|
|
708
|
+
'font-normal': createStyle('fontWeight', '400'),
|
|
709
|
+
'font-medium': createStyle('fontWeight', '500'),
|
|
710
|
+
'font-semibold': createStyle('fontWeight', '600'),
|
|
711
|
+
'font-bold': createStyle('fontWeight', '700'),
|
|
712
|
+
'font-extrabold': createStyle('fontWeight', '800'),
|
|
713
|
+
'font-black': createStyle('fontWeight', '900'),
|
|
714
|
+
'text-xs': createStyle('fontSize', '0.75rem', 'lineHeight', '1rem'),
|
|
715
|
+
'text-sm': createStyle('fontSize', '0.875rem', 'lineHeight', '1.25rem'),
|
|
716
|
+
'text-base': createStyle('fontSize', '1rem', 'lineHeight', '1.5rem'),
|
|
717
|
+
'text-lg': createStyle('fontSize', '1.125rem', 'lineHeight', '1.75rem'),
|
|
718
|
+
'text-xl': createStyle('fontSize', '1.25rem', 'lineHeight', '1.75rem'),
|
|
719
|
+
'text-2xl': createStyle('fontSize', '1.5rem', 'lineHeight', '2rem'),
|
|
720
|
+
'text-3xl': createStyle('fontSize', '1.875rem', 'lineHeight', '2.25rem'),
|
|
721
|
+
'text-4xl': createStyle('fontSize', '2.25rem', 'lineHeight', '2.5rem'),
|
|
722
|
+
'text-5xl': createStyle('fontSize', '3rem', 'lineHeight', '1'),
|
|
723
|
+
'text-6xl': createStyle('fontSize', '3.75rem;', 'lineHeight', '1'),
|
|
724
|
+
'text-7xl': createStyle('fontSize', '4.5rem', 'lineHeight', '1'),
|
|
725
|
+
'text-8xl': createStyle('fontSize', '6rem', 'lineHeight', '1'),
|
|
726
|
+
'text-9xl': createStyle('fontSize', '8rem', 'lineHeight', '1'),
|
|
727
|
+
'cursor-auto': createStyle('cursor', 'auto'),
|
|
728
|
+
'cursor-default': createStyle('cursor', 'default'),
|
|
729
|
+
'cursor-pointer': createStyle('cursor', 'pointer'),
|
|
730
|
+
'cursor-wait': createStyle('cursor', 'wait'),
|
|
731
|
+
'cursor-text': createStyle('cursor', 'text'),
|
|
732
|
+
'cursor-move': createStyle('cursor', 'move'),
|
|
733
|
+
'cursor-help': createStyle('cursor', 'help'),
|
|
734
|
+
'cursor-not-allowed': createStyle('cursor', 'not-allowed'),
|
|
735
|
+
'pointer-events-none': createStyle('pointerEvents', 'none'),
|
|
736
|
+
'pointer-events-auto': createStyle('pointerEvents', 'auto'),
|
|
737
|
+
'select-none': createStyle('userSelect', 'none'),
|
|
738
|
+
'select-text': createStyle('userSelect', 'text'),
|
|
739
|
+
'select-all': createStyle('userSelect', 'all'),
|
|
740
|
+
'select-auto': createStyle('userSelect', 'auto'),
|
|
741
|
+
'w-screen': '100vw',
|
|
742
|
+
'h-screen': '100vh',
|
|
743
|
+
...mapApply(sizes),
|
|
744
|
+
...mapApply(spacing),
|
|
745
|
+
...mapApply(colors),
|
|
746
|
+
...mapApply(borders),
|
|
747
|
+
...mapApply(radius),
|
|
748
|
+
static: createStyle('position', 'static'),
|
|
749
|
+
fixed: createStyle('position', 'fixed'),
|
|
750
|
+
absolute: createStyle('position', 'absolute'),
|
|
751
|
+
relative: createStyle('position', 'relative'),
|
|
752
|
+
sticky: createStyle('position', 'sticky'),
|
|
753
|
+
'overflow-auto': createStyle('overflow', 'auto'),
|
|
754
|
+
'overflow-hidden': createStyle('overflow', 'hidden'),
|
|
755
|
+
'overflow-visible': createStyle('overflow', 'visible'),
|
|
756
|
+
'overflow-scroll': createStyle('overflow', 'scroll'),
|
|
757
|
+
'overflow-x-auto': createStyle('overflowX', 'auto'),
|
|
758
|
+
'overflow-y-auto': createStyle('overflowY', 'auto'),
|
|
759
|
+
'overflow-x-hidden': createStyle('overflowX', 'hidden'),
|
|
760
|
+
'overflow-y-hidden': createStyle('overflowY', 'hidden'),
|
|
761
|
+
'overflow-x-visible': createStyle('overflowX', 'visible'),
|
|
762
|
+
'overflow-y-visible': createStyle('overflowY', 'visible'),
|
|
763
|
+
'overflow-x-scroll': createStyle('overflowX', 'scroll'),
|
|
764
|
+
'overflow-y-scroll': createStyle('overflowY', 'scroll'),
|
|
765
|
+
'origin-center': createStyle('transformOrigin', 'center'),
|
|
766
|
+
'origin-top': createStyle('transformOrigin', 'top'),
|
|
767
|
+
'origin-top-right': createStyle('transformOrigin', 'top right'),
|
|
768
|
+
'origin-right': createStyle('transformOrigin', 'right'),
|
|
769
|
+
'origin-bottom-right': createStyle('transformOrigin', 'bottom right'),
|
|
770
|
+
'origin-bottom': createStyle('transformOrigin', 'bottom'),
|
|
771
|
+
'origin-bottom-left': createStyle('transformOrigin', 'bottom left'),
|
|
772
|
+
'origin-left': createStyle('transformOrigin', 'left'),
|
|
773
|
+
'origin-top-left': createStyle('transformOrigin', 'top left'),
|
|
774
|
+
'shadow-sm': createStyle('box-shadow', '0 0 #0000, 0 0 #0000, 0 1px 2px 0 rgba(0, 0, 0, 0.05)'),
|
|
775
|
+
shadow: createStyle('box-shadow', '0 0 #0000, 0 0 #0000, 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)'),
|
|
776
|
+
'shadow-md': createStyle('box-shadow', '0 0 #0000, 0 0 #0000, 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)'),
|
|
777
|
+
'shadow-lg': createStyle('box-shadow', '0 0 #0000, 0 0 #0000, 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)'),
|
|
778
|
+
'shadow-xl': createStyle('box-shadow', '0 0 #0000, 0 0 #0000, 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)'),
|
|
779
|
+
'shadow-2xl': createStyle('box-shadow', '0 0 #0000, 0 0 #0000, 0 25px 50px -12px rgba(0, 0, 0, 0.25)'),
|
|
780
|
+
'shadow-inner': createStyle('box-shadow', '0 0 #0000, 0 0 #0000, inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)'),
|
|
781
|
+
'shadow-none': createStyle('box-shadow', '0 0 #0000, 0 0 #0000, 0 0 #0000'),
|
|
782
|
+
'ring-inset': createStyle('--tw-ring-inset', 'insest'),
|
|
783
|
+
'ring-0': createStyle('box-shadow', ' 0 0 0 calc(0px + 0px) rgba(59, 130, 246, 0.5)'),
|
|
784
|
+
'ring-1': createStyle('box-shadow', ' 0 0 0 calc(1px + 0px) rgba(59, 130, 246, 0.5)'),
|
|
785
|
+
'ring-2': createStyle('box-shadow', ' 0 0 0 calc(2px + 0px) rgba(59, 130, 246, 0.5)'),
|
|
786
|
+
'ring-4': createStyle('box-shadow', ' 0 0 0 calc(4px + 0px) rgba(59, 130, 246, 0.5)'),
|
|
787
|
+
'ring-8': createStyle('box-shadow', ' 0 0 0 calc(8px + 0px) rgba(59, 130, 246, 0.5)'),
|
|
788
|
+
ring: createStyle('box-shadow', ' 0 0 0 calc(3px + 0px) rgba(59, 130, 246, 0.5)'),
|
|
789
|
+
};
|
|
790
|
+
|
|
791
|
+
const pageStyles = {
|
|
792
|
+
'*, ::before, ::after': {
|
|
793
|
+
boxSizing: 'border-box',
|
|
794
|
+
borderWidth: '0',
|
|
795
|
+
borderStyle: 'solid',
|
|
796
|
+
borderColor: '#e5e7eb',
|
|
797
|
+
},
|
|
798
|
+
hr: { height: '0', color: 'inherit', borderTopWidth: '1px' },
|
|
799
|
+
'abbr[title]': {
|
|
800
|
+
WebkitTextDecoration: 'underline dotted',
|
|
801
|
+
textDecoration: 'underline dotted',
|
|
802
|
+
},
|
|
803
|
+
'b, strong': { fontWeight: 'bolder' },
|
|
804
|
+
'code, kbd, samp, pre': {
|
|
805
|
+
fontFamily: "ui-monospace, SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace",
|
|
806
|
+
fontSize: '1em',
|
|
807
|
+
},
|
|
808
|
+
small: { fontSize: '80%' },
|
|
809
|
+
'sub, sup': {
|
|
810
|
+
fontSize: '75%',
|
|
811
|
+
lineHeight: 0,
|
|
812
|
+
position: 'relative',
|
|
813
|
+
verticalAlign: 'baseline',
|
|
814
|
+
},
|
|
815
|
+
sub: { bottom: '-0.25em' },
|
|
816
|
+
sup: { top: '-0.5em' },
|
|
817
|
+
table: {
|
|
818
|
+
textIndent: '0',
|
|
819
|
+
borderColor: 'inherit',
|
|
820
|
+
borderCollapse: 'collapse',
|
|
821
|
+
},
|
|
822
|
+
'button, input, optgroup, select, textarea': {
|
|
823
|
+
fontSize: '100%',
|
|
824
|
+
margin: '0',
|
|
825
|
+
padding: '0',
|
|
826
|
+
lineHeight: 'inherit',
|
|
827
|
+
color: 'inherit',
|
|
828
|
+
},
|
|
829
|
+
'button, select': {},
|
|
830
|
+
"button, [type='button'], [type='reset'], [type='submit']": {},
|
|
831
|
+
'::-moz-focus-inner': { borderStyle: 'none', padding: '0' },
|
|
832
|
+
':-moz-focusring': { outline: '1px dotted ButtonText' },
|
|
833
|
+
':-moz-ui-invalid': { boxShadow: 'none' },
|
|
834
|
+
legend: { padding: '0' },
|
|
835
|
+
progress: { verticalAlign: 'baseline' },
|
|
836
|
+
'::-webkit-inner-spin-button, ::-webkit-outer-spin-button': {
|
|
837
|
+
height: 'auto',
|
|
838
|
+
},
|
|
839
|
+
"[type='search']": { WebkitAppearance: 'textfield', outlineOffset: '-2px' },
|
|
840
|
+
'::-webkit-search-decoration': { WebkitAppearance: 'none' },
|
|
841
|
+
'::-webkit-file-upload-button': {
|
|
842
|
+
WebkitAppearance: 'button',
|
|
843
|
+
font: 'inherit',
|
|
844
|
+
},
|
|
845
|
+
summary: { display: 'list-item' },
|
|
846
|
+
'blockquote, dl, dd, h1, h2, h3, h4, h5, h6, hr, figure, p, pre': {
|
|
847
|
+
margin: '0',
|
|
848
|
+
},
|
|
849
|
+
button: {
|
|
850
|
+
backgroundImage: 'none',
|
|
851
|
+
':focus': {
|
|
852
|
+
outline: '1px dotted, 5px auto -webkit-focus-ring-color',
|
|
853
|
+
},
|
|
854
|
+
},
|
|
855
|
+
fieldset: { margin: '0', padding: '0' },
|
|
856
|
+
'ol, ul': { listStyle: 'none', margin: '0', padding: '0' },
|
|
857
|
+
img: { borderStyle: 'solid' },
|
|
858
|
+
textarea: { resize: 'vertical' },
|
|
859
|
+
'input::-moz-placeholder, textarea::-moz-placeholder': {
|
|
860
|
+
opacity: 1,
|
|
861
|
+
color: '#9ca3af',
|
|
862
|
+
},
|
|
863
|
+
'input:-ms-input-placeholder, textarea:-ms-input-placeholder': {
|
|
864
|
+
opacity: 1,
|
|
865
|
+
color: '#9ca3af',
|
|
866
|
+
},
|
|
867
|
+
'input::placeholder, textarea::placeholder': {
|
|
868
|
+
opacity: 1,
|
|
869
|
+
color: '#9ca3af',
|
|
870
|
+
},
|
|
871
|
+
"button, [role='button']": { cursor: 'pointer' },
|
|
872
|
+
'h1, h2, h3, h4, h5, h6': { fontSize: 'inherit', fontWeight: 'inherit' },
|
|
873
|
+
a: { color: 'inherit', textDecoration: 'inherit' },
|
|
874
|
+
'pre, code, kbd, samp': {
|
|
875
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace",
|
|
876
|
+
},
|
|
877
|
+
'img, svg, video, canvas, audio, iframe, embed, object': {
|
|
878
|
+
display: 'block',
|
|
879
|
+
verticalAlign: 'middle',
|
|
880
|
+
},
|
|
881
|
+
'img, video': { maxWidth: '100%', height: 'auto' },
|
|
882
|
+
html: {
|
|
883
|
+
MozTabSize: '4',
|
|
884
|
+
OTabSize: '4',
|
|
885
|
+
tabSize: 4,
|
|
886
|
+
lineHeight: 1.5,
|
|
887
|
+
WebkitTextSizeAdjust: '100%',
|
|
888
|
+
fontFamily:
|
|
889
|
+
"ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'",
|
|
890
|
+
width: '100%',
|
|
891
|
+
height: '100%',
|
|
892
|
+
},
|
|
893
|
+
body: {
|
|
894
|
+
margin: '0px',
|
|
895
|
+
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif",
|
|
896
|
+
lineHeight: 1.4,
|
|
897
|
+
backgroundColor: 'white',
|
|
898
|
+
width: '100%',
|
|
899
|
+
height: '100%',
|
|
900
|
+
display: 'flex',
|
|
901
|
+
flexDirection: 'column',
|
|
902
|
+
flex: '1 1 0%',
|
|
903
|
+
minWidth: '320px',
|
|
904
|
+
minHeight: '100vh',
|
|
905
|
+
fontWeight: 400,
|
|
906
|
+
color: 'rgba(44, 62, 80, 1)',
|
|
907
|
+
direction: 'ltr',
|
|
908
|
+
fontSynthesis: 'none',
|
|
909
|
+
textRendering: 'optimizeLegibility',
|
|
910
|
+
},
|
|
911
|
+
};
|
|
912
|
+
|
|
913
|
+
// hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-indigo-500
|
|
914
|
+
|
|
915
|
+
const previousValues = new WeakMap();
|
|
916
|
+
export const unsafeHTML = isBrowser
|
|
917
|
+
? directive((value) => (part) => {
|
|
918
|
+
if (!(part instanceof NodePart)) {
|
|
919
|
+
throw new Error('unsafeHTML can only be used in text bindings');
|
|
920
|
+
}
|
|
921
|
+
const previousValue = previousValues.get(part);
|
|
922
|
+
if (previousValue !== undefined && isPrimitive(value) && value === previousValue.value && part.value === previousValue.fragment) {
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
const template = document.createElement('template');
|
|
926
|
+
template.innerHTML = value; // innerHTML casts to string internally
|
|
927
|
+
const fragment = document.importNode(template.content, true);
|
|
928
|
+
part.setValue(fragment);
|
|
929
|
+
previousValues.set(part, { value, fragment });
|
|
930
|
+
})
|
|
931
|
+
: (value) => value;
|
|
932
|
+
|
|
933
|
+
const fifo = (q) => q.shift();
|
|
934
|
+
const filo = (q) => q.pop();
|
|
935
|
+
const microtask = (flush) => () => queueMicrotask(flush);
|
|
936
|
+
const task = (flush) => {
|
|
937
|
+
if (isBrowser) {
|
|
938
|
+
const ch = new window.MessageChannel();
|
|
939
|
+
ch.port1.onmessage = flush;
|
|
940
|
+
return () => ch.port2.postMessage(null);
|
|
941
|
+
} else {
|
|
942
|
+
return () => setImmediate(flush);
|
|
943
|
+
}
|
|
944
|
+
};
|
|
945
|
+
const depsChanged = (prev, next) => prev == null || next.some((f, i) => !Object.is(f, prev[i]));
|
|
946
|
+
|
|
947
|
+
export const createAttrs = (attrs) => attrs;
|
|
948
|
+
|
|
949
|
+
export const createReducer = ({ initial, reducer, effects }) => {
|
|
950
|
+
let value = initial;
|
|
951
|
+
const subs = new Set();
|
|
952
|
+
const actions = Object.keys(reducer).reduce((acc, key) => {
|
|
953
|
+
const reduce = reducer[key];
|
|
954
|
+
acc[key] = (v) => {
|
|
955
|
+
value = reduce(value, v);
|
|
956
|
+
subs.forEach((sub) => {
|
|
957
|
+
sub(value);
|
|
958
|
+
});
|
|
959
|
+
};
|
|
960
|
+
return acc;
|
|
961
|
+
}, {});
|
|
962
|
+
const effectActions = Object.keys(effects || {}).reduce((acc, key) => {
|
|
963
|
+
const effect = effects[key];
|
|
964
|
+
acc[key] = (v) => effect(actions, v);
|
|
965
|
+
return acc;
|
|
966
|
+
}, {});
|
|
967
|
+
return {
|
|
968
|
+
getValue: () => value,
|
|
969
|
+
subscribe: (fn) => {
|
|
970
|
+
subs.add(fn);
|
|
971
|
+
},
|
|
972
|
+
unsubscribe: (fn) => {
|
|
973
|
+
subs.remove(fn);
|
|
974
|
+
},
|
|
975
|
+
actions,
|
|
976
|
+
effects: effectActions,
|
|
977
|
+
};
|
|
978
|
+
};
|
|
979
|
+
|
|
980
|
+
const currentComponent = {
|
|
981
|
+
current: undefined,
|
|
982
|
+
set(v) {
|
|
983
|
+
this.current = v;
|
|
984
|
+
this.current.hooks.index = 0;
|
|
985
|
+
},
|
|
986
|
+
get() {
|
|
987
|
+
return this.current;
|
|
988
|
+
},
|
|
989
|
+
};
|
|
990
|
+
|
|
991
|
+
export const useReducer = (reducer) => {
|
|
992
|
+
const comp = currentComponent.get();
|
|
993
|
+
const index = comp.hooks.index++;
|
|
994
|
+
if (!comp.hooks.data[index]) {
|
|
995
|
+
comp.hooks.data[index] = reducer.subscribe ? reducer : createReducer(reducer);
|
|
996
|
+
comp.hooks.data[index].subscribe(() => comp.update());
|
|
997
|
+
}
|
|
998
|
+
const state = comp.hooks.data[index].getValue();
|
|
999
|
+
return { ...state, actions: comp.hooks.data[index].actions, effects: comp.hooks.data[index].effects };
|
|
1000
|
+
};
|
|
1001
|
+
export const useEffect = (fn, deps) => {
|
|
1002
|
+
const comp = currentComponent.get();
|
|
1003
|
+
const index = comp.hooks.index++;
|
|
1004
|
+
if (!deps || depsChanged(comp.hooks.deps[index], deps)) {
|
|
1005
|
+
comp.hooks.deps[index] = deps || [];
|
|
1006
|
+
comp.hooks.effects[index] = fn;
|
|
1007
|
+
}
|
|
1008
|
+
};
|
|
1009
|
+
|
|
1010
|
+
const registry = {};
|
|
1011
|
+
export const getElement = (name) => registry[name];
|
|
1012
|
+
const BaseElement = isBrowser ? window.HTMLElement : class {};
|
|
1013
|
+
export const createElement = (meta, renderFn) => {
|
|
1014
|
+
const funcParams = parseFuncParams(renderFn);
|
|
1015
|
+
const RenderElement = class extends BaseElement {
|
|
1016
|
+
static get observedAttributes() {
|
|
1017
|
+
return funcParams.map((k) => k.toLowerCase());
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
constructor(ssrAttrs) {
|
|
1021
|
+
super();
|
|
1022
|
+
this._dirty = false;
|
|
1023
|
+
this._connected = false;
|
|
1024
|
+
this.attrs = ssrAttrs || {};
|
|
1025
|
+
this.state = {};
|
|
1026
|
+
this.hooks = {
|
|
1027
|
+
index: 0,
|
|
1028
|
+
data: {},
|
|
1029
|
+
deps: {},
|
|
1030
|
+
effects: {},
|
|
1031
|
+
cleanups: {},
|
|
1032
|
+
};
|
|
1033
|
+
this.renderFn = renderFn;
|
|
1034
|
+
// this.prevClassList = [];
|
|
1035
|
+
// this.shadow = this.attachShadow({ mode: 'open' });
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
connectedCallback() {
|
|
1039
|
+
this._connected = true;
|
|
1040
|
+
this.update();
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
disconnectedCallback() {
|
|
1044
|
+
this._connected = false;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
attributeChangedCallback(key, oldValue, newValue) {
|
|
1048
|
+
this.attrs[key] = newValue && newValue.startsWith(`{`) ? JSON.parse(newValue.replace(/'/g, `"`)) : newValue;
|
|
1049
|
+
if (this._connected) {
|
|
1050
|
+
this.update();
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
update() {
|
|
1055
|
+
if (this._dirty) {
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
this._dirty = true;
|
|
1059
|
+
this.enqueueUpdate();
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
_performUpdate() {
|
|
1063
|
+
if (!this._connected) {
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
this.render();
|
|
1067
|
+
this.enqueueEffects();
|
|
1068
|
+
this._dirty = false;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
_flushEffects() {
|
|
1072
|
+
const effects = this.hooks.effects;
|
|
1073
|
+
const cleanups = this.hooks.cleanups;
|
|
1074
|
+
const keys = Object.keys(effects);
|
|
1075
|
+
for (const key of keys) {
|
|
1076
|
+
if (effects[key]) {
|
|
1077
|
+
cleanups[key] && cleanups[key]();
|
|
1078
|
+
const cleanup = effects[key]();
|
|
1079
|
+
if (cleanup) {
|
|
1080
|
+
cleanups[key] = cleanup;
|
|
1081
|
+
}
|
|
1082
|
+
delete effects[key];
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
batch(runner, pick, callback) {
|
|
1088
|
+
const q = [];
|
|
1089
|
+
const flush = () => {
|
|
1090
|
+
let p;
|
|
1091
|
+
while ((p = pick(q))) callback(p);
|
|
1092
|
+
};
|
|
1093
|
+
const run = runner(flush);
|
|
1094
|
+
q.push(this) === 1 && run();
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
enqueueUpdate() {
|
|
1098
|
+
this.batch(microtask, fifo, () => this._performUpdate());
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
enqueueEffects() {
|
|
1102
|
+
this.batch(task, filo, () => this._flushEffects());
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
render() {
|
|
1106
|
+
currentComponent.set(this);
|
|
1107
|
+
const template = this.renderFn({
|
|
1108
|
+
...this.attrs,
|
|
1109
|
+
config: isBrowser ? window.props.config : global?.props?.config,
|
|
1110
|
+
location: isBrowser ? window.location : global?.location,
|
|
1111
|
+
});
|
|
1112
|
+
if (isBrowser) {
|
|
1113
|
+
// TODO: this can be optimized when we know whether the value belongs in a class (AttributePart)
|
|
1114
|
+
// maybe do this in lit-html itselfs
|
|
1115
|
+
const newClassList = getClassList(template).filter((cls) => {
|
|
1116
|
+
const globalStyles = document.getElementById('global').textContent;
|
|
1117
|
+
return !globalStyles.includes('.' + cls);
|
|
1118
|
+
});
|
|
1119
|
+
if (newClassList.length > 0) {
|
|
1120
|
+
document.getElementById('global').textContent += compileTw(newClassList);
|
|
1121
|
+
}
|
|
1122
|
+
renderHtml(template, this);
|
|
1123
|
+
// For shadows only
|
|
1124
|
+
// if (!this.styleElement) {
|
|
1125
|
+
// render(template, this.shadow);
|
|
1126
|
+
// const styleSheet = compileTw(classList);
|
|
1127
|
+
// this.prevClassList = classList;
|
|
1128
|
+
// this.styleElement = document.createElement('style');
|
|
1129
|
+
// this.shadow.appendChild(this.styleElement).textContent = css(pageStyles) + styleSheet;
|
|
1130
|
+
// } else {
|
|
1131
|
+
// const missingClassList = classList.filter((cls) => !this.prevClassList.includes(cls));
|
|
1132
|
+
// if (missingClassList.length > 0) {
|
|
1133
|
+
// const styleSheet = compileTw(missingClassList);
|
|
1134
|
+
// this.styleElement.textContent += '\n' + styleSheet;
|
|
1135
|
+
// this.prevClassList.push(...missingClassList);
|
|
1136
|
+
// }
|
|
1137
|
+
// render(template, this.shadow);
|
|
1138
|
+
// }
|
|
1139
|
+
} else {
|
|
1140
|
+
return renderHtml(template);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
};
|
|
1144
|
+
const parts = meta.url.split('/');
|
|
1145
|
+
const name = parts[parts.length - 1].split('?')[0].replace('.js', '');
|
|
1146
|
+
registry[name] = RenderElement;
|
|
1147
|
+
if (isBrowser && !window.customElements.get(name)) {
|
|
1148
|
+
window.customElements.define(name, registry[name]);
|
|
1149
|
+
}
|
|
1150
|
+
return renderFn;
|
|
1151
|
+
};
|
|
1152
|
+
|
|
1153
|
+
export const createPage = ({ head, body }) => {
|
|
1154
|
+
return ({ headScript, bodyScript, lang, props }) => {
|
|
1155
|
+
const headHtml = renderHtml(head(props));
|
|
1156
|
+
const bodyHtml = renderHtml(body(props));
|
|
1157
|
+
const classes = extractClasses(bodyHtml);
|
|
1158
|
+
return `
|
|
1159
|
+
<!DOCTYPE html>
|
|
1160
|
+
<html lang="${lang}">
|
|
1161
|
+
<head>
|
|
1162
|
+
<meta charset="utf-8" />
|
|
1163
|
+
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
|
1164
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
|
1165
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=5.0, shrink-to-fit=no">
|
|
1166
|
+
<link rel="sitemap" type="application/xml" href="/sitemap.xml" />
|
|
1167
|
+
<link rel="icon" type="image/png" href="/assets/icon.png" />
|
|
1168
|
+
${headHtml}
|
|
1169
|
+
<style id="global">
|
|
1170
|
+
${css(pageStyles)}
|
|
1171
|
+
${compileTw(new Set(classes.split(' ')))}
|
|
1172
|
+
</style>
|
|
1173
|
+
${headScript}
|
|
1174
|
+
</head>
|
|
1175
|
+
<body>
|
|
1176
|
+
${bodyHtml}
|
|
1177
|
+
<script>
|
|
1178
|
+
window.props = ${JSON.stringify(props)};
|
|
1179
|
+
</script>
|
|
1180
|
+
${bodyScript}
|
|
1181
|
+
</body>
|
|
1182
|
+
</html>
|
|
1183
|
+
`;
|
|
1184
|
+
};
|
|
1185
|
+
};
|
index.test.js
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { expect, test, jest } from '@jest/globals';
|
|
2
|
+
import { getElement, createElement, createPage, createReducer, html, renderHtml, unsafeHTML, css, compileTw, useReducer } from './index.js';
|
|
3
|
+
|
|
4
|
+
global.__DEV = true;
|
|
5
|
+
|
|
6
|
+
test('css', () => {
|
|
7
|
+
const styles = css({
|
|
8
|
+
button: {
|
|
9
|
+
color: 'magenta',
|
|
10
|
+
fontSize: '10px',
|
|
11
|
+
'@media screen and (min-width:40em)': {
|
|
12
|
+
fontSize: '64px',
|
|
13
|
+
},
|
|
14
|
+
':hover': {
|
|
15
|
+
color: 'black',
|
|
16
|
+
},
|
|
17
|
+
'@media screen and (min-width:56em)': {
|
|
18
|
+
':hover': {
|
|
19
|
+
color: 'navy',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
container: {
|
|
24
|
+
flex: 1,
|
|
25
|
+
alignItems: 'center',
|
|
26
|
+
justifyContent: 'center',
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
expect(styles).toMatchSnapshot();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('compileTw', () => {
|
|
33
|
+
expect(compileTw('text-white font-bold border border-black p-4 m-4 w-20 h-20'.split(' '))).toMatchSnapshot();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('renderHtml', () => {
|
|
37
|
+
const age = 1;
|
|
38
|
+
const data = { name: '123', address: { street: '1' } };
|
|
39
|
+
const items = [1, 2, 3];
|
|
40
|
+
const highlight = 'high';
|
|
41
|
+
const template = html`
|
|
42
|
+
<div>
|
|
43
|
+
<app-counter name="123" class="abc ${highlight}" age=${age} details1=${data} items=${items}></app-counter>
|
|
44
|
+
</div>
|
|
45
|
+
`;
|
|
46
|
+
const res = renderHtml(template);
|
|
47
|
+
expect(res).toMatchSnapshot();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('render attribute keys', () => {
|
|
51
|
+
const template = html`
|
|
52
|
+
<div>
|
|
53
|
+
<app-counter name="123" perPage="1"></app-counter>
|
|
54
|
+
</div>
|
|
55
|
+
`;
|
|
56
|
+
const res = renderHtml(template);
|
|
57
|
+
expect(res).toMatchSnapshot();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('render attributes within quotes', () => {
|
|
61
|
+
const age = 1;
|
|
62
|
+
const data = { name: '123', address: { street: '1' } };
|
|
63
|
+
const items = [1, 2, 3];
|
|
64
|
+
const classes = 'high';
|
|
65
|
+
const template = html`
|
|
66
|
+
<div>
|
|
67
|
+
<app-counter name="123" class=${classes} age="${age}" details1="${data}" items="${items}"></app-counter>
|
|
68
|
+
</div>
|
|
69
|
+
`;
|
|
70
|
+
const res = renderHtml(template);
|
|
71
|
+
expect(res).toMatchSnapshot();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('render unsafeHTML', () => {
|
|
75
|
+
const textContent = `<div><p class="123">this is unsafe</p></div>`;
|
|
76
|
+
const template = html` <div>${unsafeHTML(textContent)}</div> `;
|
|
77
|
+
const res = renderHtml(template);
|
|
78
|
+
expect(res).toMatchSnapshot();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('render single template', () => {
|
|
82
|
+
const template = html` <div>${html`NoCountry ${false}`}</div> `;
|
|
83
|
+
const res = renderHtml(template);
|
|
84
|
+
expect(res).toMatchSnapshot();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('render multi template', () => {
|
|
88
|
+
const template = html`
|
|
89
|
+
<div>
|
|
90
|
+
${[1, 2].map(
|
|
91
|
+
(v) => html`
|
|
92
|
+
<app-item meta="${{ index: v }}" @click=${() => {}} .handleClick=${() => {}}>
|
|
93
|
+
<button @click=${() => {}}>+</button>
|
|
94
|
+
</app-item>
|
|
95
|
+
`,
|
|
96
|
+
)}
|
|
97
|
+
</div>
|
|
98
|
+
`;
|
|
99
|
+
const res = renderHtml(template);
|
|
100
|
+
expect(res).toMatchSnapshot();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('createReducer', () => {
|
|
104
|
+
const countReducer = createReducer({
|
|
105
|
+
initial: {
|
|
106
|
+
count: 0,
|
|
107
|
+
},
|
|
108
|
+
reducer: {
|
|
109
|
+
increment: (state, a) => ({ ...state, count: state.count + a }),
|
|
110
|
+
decrement: (state, a) => ({ ...state, count: state.count - a }),
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
const mock = jest.fn();
|
|
114
|
+
countReducer.subscribe(mock);
|
|
115
|
+
countReducer.actions.increment(4);
|
|
116
|
+
expect(countReducer.getValue().count).toEqual(4);
|
|
117
|
+
expect(mock).toBeCalledWith({ count: 4 });
|
|
118
|
+
countReducer.actions.decrement(1);
|
|
119
|
+
expect(countReducer.getValue().count).toEqual(3);
|
|
120
|
+
expect(mock).toBeCalledWith({ count: 3 });
|
|
121
|
+
countReducer.actions.decrement(2);
|
|
122
|
+
expect(countReducer.getValue().count).toEqual(1);
|
|
123
|
+
expect(mock).toBeCalledWith({ count: 1 });
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('createElement without attrs', () => {
|
|
127
|
+
createElement({ url: '/base-element.js' }, () => {
|
|
128
|
+
return html` <div></div> `;
|
|
129
|
+
});
|
|
130
|
+
const Clazz = getElement('base-element');
|
|
131
|
+
const instance = new Clazz();
|
|
132
|
+
const res = instance.render();
|
|
133
|
+
expect(res).toMatchSnapshot();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test('createElement with attrs and hooks', () => {
|
|
137
|
+
createElement({ url: '/base-element.js' }, ({ perPage }) => {
|
|
138
|
+
const { count, actions } = useReducer({
|
|
139
|
+
initial: {
|
|
140
|
+
count: 3,
|
|
141
|
+
},
|
|
142
|
+
reducer: {
|
|
143
|
+
setValue: (state, v) => ({ ...state, count: v }),
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
return html`
|
|
147
|
+
<div>
|
|
148
|
+
<div>
|
|
149
|
+
<span>perPage: ${perPage}</span>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
<span>Count: ${count}</span>
|
|
153
|
+
</div>
|
|
154
|
+
<button @click=${actions.setValue}>Set</button>
|
|
155
|
+
</div>
|
|
156
|
+
`;
|
|
157
|
+
});
|
|
158
|
+
const Clazz = getElement('base-element');
|
|
159
|
+
const instance = new Clazz({ perPage: 5 });
|
|
160
|
+
const res = instance.render();
|
|
161
|
+
expect(Clazz.observedAttributes).toEqual(['perpage']);
|
|
162
|
+
expect(res).toMatchSnapshot();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test('createPage', () => {
|
|
166
|
+
const route = () => {
|
|
167
|
+
const langPart = this.config.lang === 'en' ? '' : `/${this.config.lang}`;
|
|
168
|
+
return `${langPart}`;
|
|
169
|
+
};
|
|
170
|
+
const head = ({ config }) => {
|
|
171
|
+
return html`
|
|
172
|
+
<title>${config.title}</title>
|
|
173
|
+
<meta name="title" content=${config.title} />
|
|
174
|
+
<meta name="description" content=${config.title} />
|
|
175
|
+
`;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const body = ({ config }) => {
|
|
179
|
+
return html`
|
|
180
|
+
<div>
|
|
181
|
+
<app-header></app-header>
|
|
182
|
+
<main class="flex flex-1 flex-col mt-20 items-center">
|
|
183
|
+
<h1 class="text-5xl">${config.title}</h1>
|
|
184
|
+
</main>
|
|
185
|
+
</div>
|
|
186
|
+
`;
|
|
187
|
+
};
|
|
188
|
+
const renderPage = createPage({
|
|
189
|
+
route,
|
|
190
|
+
head,
|
|
191
|
+
body,
|
|
192
|
+
});
|
|
193
|
+
const scripts = '<script type="module"><script>';
|
|
194
|
+
const res = renderPage({ lang: 'en', props: { config: { title: '123' } }, headScript: scripts, bodyScript: scripts });
|
|
195
|
+
expect(res).toMatchSnapshot();
|
|
196
|
+
});
|
src/lit-html.js → lit-html.js
RENAMED
|
@@ -52,11 +52,13 @@ const directives = new WeakMap();
|
|
|
52
52
|
* }
|
|
53
53
|
* });
|
|
54
54
|
*/
|
|
55
|
-
const directive =
|
|
55
|
+
const directive =
|
|
56
|
+
(f) =>
|
|
57
|
+
(...args) => {
|
|
56
|
-
|
|
58
|
+
const d = f(...args);
|
|
57
|
-
|
|
59
|
+
directives.set(d, true);
|
|
58
|
-
|
|
60
|
+
return d;
|
|
59
|
-
}
|
|
61
|
+
};
|
|
60
62
|
const isDirective = (o) => {
|
|
61
63
|
return typeof o === 'function' && directives.has(o);
|
|
62
64
|
};
|
|
@@ -77,10 +79,7 @@ const isDirective = (o) => {
|
|
|
77
79
|
/**
|
|
78
80
|
* True if the custom elements polyfill is in use.
|
|
79
81
|
*/
|
|
80
|
-
const isCEPolyfill = typeof window !== 'undefined' &&
|
|
82
|
+
const isCEPolyfill = typeof window !== 'undefined' && window.customElements != null && window.customElements.polyfillWrapFlushCallback !== undefined;
|
|
81
|
-
window.customElements != null &&
|
|
82
|
-
window.customElements.polyfillWrapFlushCallback !==
|
|
83
|
-
undefined;
|
|
84
83
|
/**
|
|
85
84
|
* Reparents nodes, starting from `start` (inclusive) to `end` (exclusive),
|
|
86
85
|
* into another container (could be the same container), before `before`. If
|
|
@@ -173,7 +172,10 @@ class Template {
|
|
|
173
172
|
let lastPartIndex = 0;
|
|
174
173
|
let index = -1;
|
|
175
174
|
let partIndex = 0;
|
|
175
|
+
const {
|
|
176
|
+
strings,
|
|
176
|
-
|
|
177
|
+
values: { length },
|
|
178
|
+
} = result;
|
|
177
179
|
while (partIndex < length) {
|
|
178
180
|
const node = walker.nextNode();
|
|
179
181
|
if (node === null) {
|
|
@@ -223,8 +225,7 @@ class Template {
|
|
|
223
225
|
stack.push(node);
|
|
224
226
|
walker.currentNode = node.content;
|
|
225
227
|
}
|
|
226
|
-
}
|
|
227
|
-
else if (node.nodeType === 3 /* Node.TEXT_NODE */) {
|
|
228
|
+
} else if (node.nodeType === 3 /* Node.TEXT_NODE */) {
|
|
228
229
|
const data = node.data;
|
|
229
230
|
if (data.indexOf(marker) >= 0) {
|
|
230
231
|
const parent = node.parentNode;
|
|
@@ -237,12 +238,10 @@ class Template {
|
|
|
237
238
|
let s = strings[i];
|
|
238
239
|
if (s === '') {
|
|
239
240
|
insert = createMarker();
|
|
240
|
-
}
|
|
241
|
-
else {
|
|
241
|
+
} else {
|
|
242
242
|
const match = lastAttributeNameRegex.exec(s);
|
|
243
243
|
if (match !== null && endsWith(match[2], boundAttributeSuffix)) {
|
|
244
|
-
s = s.slice(0, match.index) + match[1] +
|
|
245
|
-
|
|
244
|
+
s = s.slice(0, match.index) + match[1] + match[2].slice(0, -boundAttributeSuffix.length) + match[3];
|
|
246
245
|
}
|
|
247
246
|
insert = document.createTextNode(s);
|
|
248
247
|
}
|
|
@@ -254,15 +253,13 @@ class Template {
|
|
|
254
253
|
if (strings[lastIndex] === '') {
|
|
255
254
|
parent.insertBefore(createMarker(), node);
|
|
256
255
|
nodesToRemove.push(node);
|
|
257
|
-
}
|
|
258
|
-
else {
|
|
256
|
+
} else {
|
|
259
257
|
node.data = strings[lastIndex];
|
|
260
258
|
}
|
|
261
259
|
// We have a part for each match found
|
|
262
260
|
partIndex += lastIndex;
|
|
263
261
|
}
|
|
264
|
-
}
|
|
265
|
-
else if (node.nodeType === 8 /* Node.COMMENT_NODE */) {
|
|
262
|
+
} else if (node.nodeType === 8 /* Node.COMMENT_NODE */) {
|
|
266
263
|
if (node.data === marker) {
|
|
267
264
|
const parent = node.parentNode;
|
|
268
265
|
// Add a new marker node to be the startNode of the Part if any of
|
|
@@ -279,14 +276,12 @@ class Template {
|
|
|
279
276
|
// Else, we can remove it to save future costs.
|
|
280
277
|
if (node.nextSibling === null) {
|
|
281
278
|
node.data = '';
|
|
282
|
-
}
|
|
283
|
-
else {
|
|
279
|
+
} else {
|
|
284
280
|
nodesToRemove.push(node);
|
|
285
281
|
index--;
|
|
286
282
|
}
|
|
287
283
|
partIndex++;
|
|
288
|
-
}
|
|
289
|
-
else {
|
|
284
|
+
} else {
|
|
290
285
|
let i = -1;
|
|
291
286
|
while ((i = node.data.indexOf(marker, i + 1)) !== -1) {
|
|
292
287
|
// Comment node has a binding marker inside, make an inactive part
|
|
@@ -419,9 +414,7 @@ class TemplateInstance {
|
|
|
419
414
|
// The Custom Elements v1 polyfill supports upgrade(), so the order when
|
|
420
415
|
// polyfilled is the more ideal: Clone, Process, Adopt, Upgrade, Update,
|
|
421
416
|
// Connect.
|
|
422
|
-
const fragment = isCEPolyfill ?
|
|
423
|
-
this.template.element.content.cloneNode(true) :
|
|
424
|
-
|
|
417
|
+
const fragment = isCEPolyfill ? this.template.element.content.cloneNode(true) : document.importNode(this.template.element.content, true);
|
|
425
418
|
const stack = [];
|
|
426
419
|
const parts = this.template.parts;
|
|
427
420
|
// Edge needs all 4 parameters present; IE11 needs 3rd parameter to be null
|
|
@@ -461,8 +454,7 @@ class TemplateInstance {
|
|
|
461
454
|
const part = this.processor.handleTextExpression(this.options);
|
|
462
455
|
part.insertAfterNode(node.previousSibling);
|
|
463
456
|
this.__parts.push(part);
|
|
464
|
-
}
|
|
465
|
-
else {
|
|
457
|
+
} else {
|
|
466
458
|
this.__parts.push(...this.processor.handleAttributeExpressions(node, part.name, part.strings, this.options));
|
|
467
459
|
}
|
|
468
460
|
partIndex++;
|
|
@@ -496,8 +488,7 @@ class TemplateInstance {
|
|
|
496
488
|
* before any untrusted expressions have been mixed in. Therefor it is
|
|
497
489
|
* considered safe by construction.
|
|
498
490
|
*/
|
|
499
|
-
const policy = window.trustedTypes &&
|
|
500
|
-
|
|
491
|
+
const policy = typeof window !== 'undefined' && window.trustedTypes && trustedTypes.createPolicy('lit-html', { createHTML: (s) => s });
|
|
501
492
|
const commentMarker = ` ${marker} `;
|
|
502
493
|
/**
|
|
503
494
|
* The return type of `html`, which holds a Template and the values from
|
|
@@ -540,8 +531,7 @@ class TemplateResult {
|
|
|
540
531
|
// We're in comment position if we have a comment open with no following
|
|
541
532
|
// comment close. Because <-- can appear in an attribute value there can
|
|
542
533
|
// be false positives.
|
|
543
|
-
isCommentBinding = (commentOpen > -1 || isCommentBinding) &&
|
|
534
|
+
isCommentBinding = (commentOpen > -1 || isCommentBinding) && s.indexOf('-->', commentOpen + 1) === -1;
|
|
544
|
-
s.indexOf('-->', commentOpen + 1) === -1;
|
|
545
535
|
// Check to see if we have an attribute-like sequence preceding the
|
|
546
536
|
// expression. This can match "name=value" like structures in text,
|
|
547
537
|
// comments, and attribute values, so there can be false-positives.
|
|
@@ -553,14 +543,11 @@ class TemplateResult {
|
|
|
553
543
|
// <!-- foo=${'bar'}--> are handled correctly in the attribute branch
|
|
554
544
|
// below.
|
|
555
545
|
html += s + (isCommentBinding ? commentMarker : nodeMarker);
|
|
556
|
-
}
|
|
557
|
-
else {
|
|
546
|
+
} else {
|
|
558
547
|
// For attributes we use just a marker sentinel, and also append a
|
|
559
548
|
// $lit$ suffix to the name to opt-out of attribute-specific parsing
|
|
560
549
|
// that IE and Edge do for style and certain SVG attributes.
|
|
561
|
-
html += s.substr(0, attributeMatch.index) + attributeMatch[1] +
|
|
550
|
+
html += s.substr(0, attributeMatch.index) + attributeMatch[1] + attributeMatch[2] + boundAttributeSuffix + attributeMatch[3] + marker;
|
|
562
|
-
attributeMatch[2] + boundAttributeSuffix + attributeMatch[3] +
|
|
563
|
-
marker;
|
|
564
551
|
}
|
|
565
552
|
}
|
|
566
553
|
html += this.strings[l];
|
|
@@ -615,13 +602,14 @@ class SVGTemplateResult extends TemplateResult {
|
|
|
615
602
|
* http://polymer.github.io/PATENTS.txt
|
|
616
603
|
*/
|
|
617
604
|
const isPrimitive = (value) => {
|
|
618
|
-
return (value === null ||
|
|
619
|
-
|
|
605
|
+
return value === null || !(typeof value === 'object' || typeof value === 'function');
|
|
620
606
|
};
|
|
621
607
|
const isIterable = (value) => {
|
|
608
|
+
return (
|
|
622
|
-
|
|
609
|
+
Array.isArray(value) ||
|
|
623
610
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
624
|
-
!!(value && value[Symbol.iterator])
|
|
611
|
+
!!(value && value[Symbol.iterator])
|
|
612
|
+
);
|
|
625
613
|
};
|
|
626
614
|
/**
|
|
627
615
|
* Writes attribute values to the DOM for a group of AttributeParts bound to a
|
|
@@ -679,8 +667,7 @@ class AttributeCommitter {
|
|
|
679
667
|
const v = part.value;
|
|
680
668
|
if (isPrimitive(v) || !isIterable(v)) {
|
|
681
669
|
text += typeof v === 'string' ? v : String(v);
|
|
682
|
-
}
|
|
683
|
-
else {
|
|
670
|
+
} else {
|
|
684
671
|
for (const t of v) {
|
|
685
672
|
text += typeof t === 'string' ? t : String(t);
|
|
686
673
|
}
|
|
@@ -772,8 +759,8 @@ class NodePart {
|
|
|
772
759
|
* This part must be empty, as its contents are not automatically moved.
|
|
773
760
|
*/
|
|
774
761
|
appendIntoPart(part) {
|
|
775
|
-
part.__insert(this.startNode = createMarker());
|
|
762
|
+
part.__insert((this.startNode = createMarker()));
|
|
776
|
-
part.__insert(this.endNode = createMarker());
|
|
763
|
+
part.__insert((this.endNode = createMarker()));
|
|
777
764
|
}
|
|
778
765
|
/**
|
|
779
766
|
* Inserts this part after the `ref` part.
|
|
@@ -781,7 +768,7 @@ class NodePart {
|
|
|
781
768
|
* This part must be empty, as its contents are not automatically moved.
|
|
782
769
|
*/
|
|
783
770
|
insertAfterPart(ref) {
|
|
784
|
-
ref.__insert(this.startNode = createMarker());
|
|
771
|
+
ref.__insert((this.startNode = createMarker()));
|
|
785
772
|
this.endNode = ref.endNode;
|
|
786
773
|
ref.endNode = this.startNode;
|
|
787
774
|
}
|
|
@@ -805,21 +792,16 @@ class NodePart {
|
|
|
805
792
|
if (value !== this.value) {
|
|
806
793
|
this.__commitText(value);
|
|
807
794
|
}
|
|
808
|
-
}
|
|
809
|
-
else if (value instanceof TemplateResult) {
|
|
795
|
+
} else if (value instanceof TemplateResult) {
|
|
810
796
|
this.__commitTemplateResult(value);
|
|
811
|
-
}
|
|
812
|
-
else if (value instanceof Node) {
|
|
797
|
+
} else if (value instanceof Node) {
|
|
813
798
|
this.__commitNode(value);
|
|
814
|
-
}
|
|
815
|
-
else if (isIterable(value)) {
|
|
799
|
+
} else if (isIterable(value)) {
|
|
816
800
|
this.__commitIterable(value);
|
|
817
|
-
}
|
|
818
|
-
else if (value === nothing) {
|
|
801
|
+
} else if (value === nothing) {
|
|
819
802
|
this.value = nothing;
|
|
820
803
|
this.clear();
|
|
821
|
-
}
|
|
822
|
-
else {
|
|
804
|
+
} else {
|
|
823
805
|
// Fallback, will render the string representation
|
|
824
806
|
this.__commitText(value);
|
|
825
807
|
}
|
|
@@ -841,25 +823,21 @@ class NodePart {
|
|
|
841
823
|
// If `value` isn't already a string, we explicitly convert it here in case
|
|
842
824
|
// it can't be implicitly converted - i.e. it's a symbol.
|
|
843
825
|
const valueAsString = typeof value === 'string' ? value : String(value);
|
|
844
|
-
if (node === this.endNode.previousSibling &&
|
|
845
|
-
|
|
826
|
+
if (node === this.endNode.previousSibling && node.nodeType === 3 /* Node.TEXT_NODE */) {
|
|
846
827
|
// If we only have a single text node between the markers, we can just
|
|
847
828
|
// set its value, rather than replacing it.
|
|
848
829
|
// TODO(justinfagnani): Can we just check if this.value is primitive?
|
|
849
830
|
node.data = valueAsString;
|
|
850
|
-
}
|
|
851
|
-
else {
|
|
831
|
+
} else {
|
|
852
832
|
this.__commitNode(document.createTextNode(valueAsString));
|
|
853
833
|
}
|
|
854
834
|
this.value = value;
|
|
855
835
|
}
|
|
856
836
|
__commitTemplateResult(value) {
|
|
857
837
|
const template = this.options.templateFactory(value);
|
|
858
|
-
if (this.value instanceof TemplateInstance &&
|
|
838
|
+
if (this.value instanceof TemplateInstance && this.value.template === template) {
|
|
859
|
-
this.value.template === template) {
|
|
860
839
|
this.value.update(value.values);
|
|
861
|
-
}
|
|
862
|
-
else {
|
|
840
|
+
} else {
|
|
863
841
|
// Make sure we propagate the template processor from the TemplateResult
|
|
864
842
|
// so that we use its syntax extension, etc. The template factory comes
|
|
865
843
|
// from the render function options so that it can control template
|
|
@@ -899,8 +877,7 @@ class NodePart {
|
|
|
899
877
|
itemParts.push(itemPart);
|
|
900
878
|
if (partIndex === 0) {
|
|
901
879
|
itemPart.appendIntoPart(this);
|
|
902
|
-
}
|
|
903
|
-
else {
|
|
880
|
+
} else {
|
|
904
881
|
itemPart.insertAfterPart(itemParts[partIndex - 1]);
|
|
905
882
|
}
|
|
906
883
|
}
|
|
@@ -952,8 +929,7 @@ class BooleanAttributePart {
|
|
|
952
929
|
if (this.value !== value) {
|
|
953
930
|
if (value) {
|
|
954
931
|
this.element.setAttribute(this.name, '');
|
|
955
|
-
}
|
|
956
|
-
else {
|
|
932
|
+
} else {
|
|
957
933
|
this.element.removeAttribute(this.name);
|
|
958
934
|
}
|
|
959
935
|
this.value = value;
|
|
@@ -973,8 +949,7 @@ class BooleanAttributePart {
|
|
|
973
949
|
class PropertyCommitter extends AttributeCommitter {
|
|
974
950
|
constructor(element, name, strings) {
|
|
975
951
|
super(element, name, strings);
|
|
976
|
-
this.single =
|
|
977
|
-
|
|
952
|
+
this.single = strings.length === 2 && strings[0] === '' && strings[1] === '';
|
|
978
953
|
}
|
|
979
954
|
_createPart() {
|
|
980
955
|
return new PropertyPart(this);
|
|
@@ -993,8 +968,7 @@ class PropertyCommitter extends AttributeCommitter {
|
|
|
993
968
|
}
|
|
994
969
|
}
|
|
995
970
|
}
|
|
996
|
-
class PropertyPart extends AttributePart {
|
|
971
|
+
class PropertyPart extends AttributePart {}
|
|
997
|
-
}
|
|
998
972
|
// Detect event listener options support. If the `capture` property is read
|
|
999
973
|
// from the options object, then options are supported. If not, then the third
|
|
1000
974
|
// argument to add/removeEventListener is interpreted as the boolean capture
|
|
@@ -1008,14 +982,13 @@ let eventOptionsSupported = false;
|
|
|
1008
982
|
get capture() {
|
|
1009
983
|
eventOptionsSupported = true;
|
|
1010
984
|
return false;
|
|
1011
|
-
}
|
|
985
|
+
},
|
|
1012
986
|
};
|
|
1013
987
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1014
988
|
window.addEventListener('test', options, options);
|
|
1015
989
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1016
990
|
window.removeEventListener('test', options, options);
|
|
1017
|
-
}
|
|
1018
|
-
catch (_e) {
|
|
991
|
+
} catch (_e) {
|
|
1019
992
|
// event options not supported
|
|
1020
993
|
}
|
|
1021
994
|
})();
|
|
@@ -1042,11 +1015,10 @@ class EventPart {
|
|
|
1042
1015
|
}
|
|
1043
1016
|
const newListener = this.__pendingValue;
|
|
1044
1017
|
const oldListener = this.value;
|
|
1045
|
-
const shouldRemoveListener =
|
|
1018
|
+
const shouldRemoveListener =
|
|
1019
|
+
newListener == null ||
|
|
1046
|
-
oldListener != null &&
|
|
1020
|
+
(oldListener != null &&
|
|
1047
|
-
|
|
1021
|
+
(newListener.capture !== oldListener.capture || newListener.once !== oldListener.once || newListener.passive !== oldListener.passive));
|
|
1048
|
-
newListener.once !== oldListener.once ||
|
|
1049
|
-
newListener.passive !== oldListener.passive);
|
|
1050
1022
|
const shouldAddListener = newListener != null && (oldListener == null || shouldRemoveListener);
|
|
1051
1023
|
if (shouldRemoveListener) {
|
|
1052
1024
|
this.element.removeEventListener(this.eventName, this.__boundHandleEvent, this.__options);
|
|
@@ -1061,8 +1033,7 @@ class EventPart {
|
|
|
1061
1033
|
handleEvent(event) {
|
|
1062
1034
|
if (typeof this.value === 'function') {
|
|
1063
1035
|
this.value.call(this.eventContext || this.element, event);
|
|
1064
|
-
}
|
|
1065
|
-
else {
|
|
1036
|
+
} else {
|
|
1066
1037
|
this.value.handleEvent(event);
|
|
1067
1038
|
}
|
|
1068
1039
|
}
|
|
@@ -1070,10 +1041,7 @@ class EventPart {
|
|
|
1070
1041
|
// We copy options because of the inconsistent behavior of browsers when reading
|
|
1071
1042
|
// the third argument of add/removeEventListener. IE11 doesn't support options
|
|
1072
1043
|
// at all. Chrome 41 only reads `capture` if the argument is an object.
|
|
1073
|
-
const getOptions = (o) => o &&
|
|
1074
|
-
(eventOptionsSupported ?
|
|
1075
|
-
|
|
1044
|
+
const getOptions = (o) => o && (eventOptionsSupported ? { capture: o.capture, passive: o.passive, once: o.once } : o.capture);
|
|
1076
|
-
o.capture);
|
|
1077
1045
|
|
|
1078
1046
|
/**
|
|
1079
1047
|
* @license
|
|
@@ -1148,7 +1116,7 @@ function templateFactory(result) {
|
|
|
1148
1116
|
if (templateCache === undefined) {
|
|
1149
1117
|
templateCache = {
|
|
1150
1118
|
stringsArray: new WeakMap(),
|
|
1151
|
-
keyString: new Map()
|
|
1119
|
+
keyString: new Map(),
|
|
1152
1120
|
};
|
|
1153
1121
|
templateCaches.set(result.type, templateCache);
|
|
1154
1122
|
}
|
|
@@ -1206,7 +1174,7 @@ const render = (result, container, options) => {
|
|
|
1206
1174
|
let part = parts.get(container);
|
|
1207
1175
|
if (part === undefined) {
|
|
1208
1176
|
removeNodes(container, container.firstChild);
|
|
1209
|
-
parts.set(container, part = new NodePart(Object.assign({ templateFactory }, options)));
|
|
1177
|
+
parts.set(container, (part = new NodePart(Object.assign({ templateFactory }, options))));
|
|
1210
1178
|
part.appendInto(container);
|
|
1211
1179
|
}
|
|
1212
1180
|
part.setValue(result);
|
|
@@ -1243,4 +1211,34 @@ const html = (strings, ...values) => new TemplateResult(strings, values, 'html',
|
|
|
1243
1211
|
*/
|
|
1244
1212
|
const svg = (strings, ...values) => new SVGTemplateResult(strings, values, 'svg', defaultTemplateProcessor);
|
|
1245
1213
|
|
|
1246
|
-
export {
|
|
1214
|
+
export {
|
|
1215
|
+
AttributeCommitter,
|
|
1216
|
+
AttributePart,
|
|
1217
|
+
BooleanAttributePart,
|
|
1218
|
+
DefaultTemplateProcessor,
|
|
1219
|
+
EventPart,
|
|
1220
|
+
NodePart,
|
|
1221
|
+
PropertyCommitter,
|
|
1222
|
+
PropertyPart,
|
|
1223
|
+
SVGTemplateResult,
|
|
1224
|
+
Template,
|
|
1225
|
+
TemplateInstance,
|
|
1226
|
+
TemplateResult,
|
|
1227
|
+
createMarker,
|
|
1228
|
+
defaultTemplateProcessor,
|
|
1229
|
+
directive,
|
|
1230
|
+
html,
|
|
1231
|
+
isDirective,
|
|
1232
|
+
isIterable,
|
|
1233
|
+
isPrimitive,
|
|
1234
|
+
isTemplatePartActive,
|
|
1235
|
+
noChange,
|
|
1236
|
+
nothing,
|
|
1237
|
+
parts,
|
|
1238
|
+
removeNodes,
|
|
1239
|
+
render,
|
|
1240
|
+
reparentNodes,
|
|
1241
|
+
svg,
|
|
1242
|
+
templateCaches,
|
|
1243
|
+
templateFactory,
|
|
1244
|
+
};
|
package-lock.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "atoms-element",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"lockfileVersion": 2,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
|
+
"name": "atoms-element",
|
|
8
|
-
"version": "
|
|
9
|
+
"version": "3.0.1",
|
|
9
10
|
"license": "MIT",
|
|
10
11
|
"devDependencies": {
|
|
11
12
|
"jest": "^27.0.5"
|
|
@@ -1629,8 +1630,7 @@
|
|
|
1629
1630
|
"esprima": "^4.0.1",
|
|
1630
1631
|
"estraverse": "^5.2.0",
|
|
1631
1632
|
"esutils": "^2.0.2",
|
|
1632
|
-
"optionator": "^0.8.1"
|
|
1633
|
+
"optionator": "^0.8.1"
|
|
1633
|
-
"source-map": "~0.6.1"
|
|
1634
1634
|
},
|
|
1635
1635
|
"bin": {
|
|
1636
1636
|
"escodegen": "bin/escodegen.js",
|
|
@@ -2407,7 +2407,6 @@
|
|
|
2407
2407
|
"@types/node": "*",
|
|
2408
2408
|
"anymatch": "^3.0.3",
|
|
2409
2409
|
"fb-watchman": "^2.0.0",
|
|
2410
|
-
"fsevents": "^2.3.2",
|
|
2411
2410
|
"graceful-fs": "^4.2.4",
|
|
2412
2411
|
"jest-regex-util": "^27.0.1",
|
|
2413
2412
|
"jest-serializer": "^27.0.1",
|
package.json
CHANGED
|
@@ -1,34 +1,43 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "atoms-element",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "A simple web component library for defining your custom elements. It works on both client and server. It supports hooks and follows the same principles of react.",
|
|
5
5
|
"keywords": [
|
|
6
|
+
"pyrossh",
|
|
6
7
|
"atoms-element",
|
|
7
|
-
"
|
|
8
|
+
"web",
|
|
8
|
-
"
|
|
9
|
+
"component",
|
|
9
|
-
"custom element",
|
|
10
|
-
"
|
|
10
|
+
"ssr",
|
|
11
|
+
"client",
|
|
12
|
+
"server",
|
|
11
13
|
"react",
|
|
12
|
-
"lit-html"
|
|
14
|
+
"lit-html"
|
|
15
|
+
],
|
|
16
|
+
"files": [
|
|
13
|
-
"lit-html
|
|
17
|
+
"lit-html.js",
|
|
14
|
-
"
|
|
18
|
+
"index.js",
|
|
19
|
+
"index.d.ts"
|
|
15
20
|
],
|
|
16
21
|
"license": "MIT",
|
|
17
|
-
"main": "src/index.js",
|
|
18
|
-
"author": "
|
|
22
|
+
"author": "pyrossh",
|
|
19
23
|
"type": "module",
|
|
20
24
|
"engines": {
|
|
21
25
|
"node": ">=14.0.0"
|
|
22
26
|
},
|
|
23
27
|
"scripts": {
|
|
28
|
+
"example": "node example/server.js",
|
|
24
29
|
"test": "NODE_OPTIONS=--experimental-vm-modules jest"
|
|
25
30
|
},
|
|
26
31
|
"devDependencies": {
|
|
27
32
|
"jest": "^27.0.5"
|
|
28
33
|
},
|
|
29
34
|
"jest": {
|
|
35
|
+
"transform": {}
|
|
36
|
+
},
|
|
30
|
-
|
|
37
|
+
"prettier": {
|
|
38
|
+
"printWidth": 160,
|
|
39
|
+
"singleQuote": true,
|
|
31
|
-
|
|
40
|
+
"arrowParens": "always",
|
|
32
|
-
|
|
41
|
+
"trailingComma": "all"
|
|
33
42
|
}
|
|
34
|
-
}
|
|
43
|
+
}
|
readme.md
CHANGED
|
@@ -1,49 +1,61 @@
|
|
|
1
1
|
# atoms-element
|
|
2
2
|
|
|
3
|
+
A simple web component library for defining your custom elements. It works on both client and server. It supports hooks and follows the same principles of react.
|
|
3
|
-
|
|
4
|
+
Data props are attributes on the custom element by default so its easier to debug and functions/handlers are attached to the element.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
I initially started researching if it was possible to server render web components but found out not one framework supported it. I liked using
|
|
7
|
+
[haunted](https://github.com/matthewp/haunted) as it was react-like with hooks but was lost on how to implement server rendering. Libraries like
|
|
6
|
-
|
|
8
|
+
JSDOM couldn't be of use since it didn't support web components and I didn't want to use puppeteer for something like this.
|
|
7
9
|
|
|
10
|
+
After a year of thinking about it and researching it I found out this awesome framework [Tonic](https://github.com/optoolco/tonic).
|
|
8
|
-
|
|
11
|
+
That was the turning point I figured out how they implemented it using a simple html parser.
|
|
9
12
|
|
|
10
|
-
1. [lit-html](https://github.com/lit/lit) on the client
|
|
11
|
-
2. [lit-html-server](https://github.com/popeindustries/lit-html-server) on the server
|
|
12
|
-
|
|
13
|
+
After going through all these libraries,
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
1. [lit-html](https://github.com/lit/lit)
|
|
16
|
+
2. [lit-html-server](https://github.com/popeindustries/lit-html-server)
|
|
17
|
+
3. [haunted](https://github.com/matthewp/haunted)
|
|
18
|
+
4. [Tonic](https://github.com/optoolco/tonic)
|
|
19
|
+
5. [Atomico](https://github.com/atomicojs/atomico)
|
|
20
|
+
6. [fuco](https://github.com/wtnbass/fuco)
|
|
15
21
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
```
|
|
22
|
+
And figuring out how each one implemented their on custom elements I came up with atoms-element. It still doesn't have proper rehydration lit-html just replaces the DOM under the server rendered web component for now. Atomico has implemented proper SSR with hydration so I might need to look into that in the future or
|
|
23
|
+
use it instead of lit-html. Since I made a few modifications like json attributes and attrTypes I don't know if it will easy.
|
|
19
24
|
|
|
20
|
-
##
|
|
25
|
+
## Example
|
|
21
26
|
|
|
22
27
|
```js
|
|
23
|
-
import {
|
|
28
|
+
import { createElement, useReducer, html, renderHtml } from 'atoms-element/index.js';
|
|
24
|
-
|
|
25
|
-
|
|
29
|
+
|
|
26
|
-
name: string.isRequired,
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const Counter = ({ name }) => {
|
|
30
|
+
const Counter = ({ name, meta }) => {
|
|
30
|
-
const
|
|
31
|
+
const { count, actions } = useReducer({
|
|
31
|
-
|
|
32
|
+
initial: {
|
|
33
|
+
count: 0,
|
|
34
|
+
},
|
|
35
|
+
reducer: {
|
|
36
|
+
increment: (state) => ({ ...state, count: state.count + 1 }),
|
|
37
|
+
decrement: (state) => ({ ...state, count: state.count - 1 }),
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
const warningClass = count > 10 ? 'text-red-500' : '';
|
|
32
41
|
return html`
|
|
42
|
+
<div class="mt-10">
|
|
43
|
+
<div class="mb-2">
|
|
44
|
+
Counter: ${name}
|
|
45
|
+
<span>starts at ${meta?.start}</span>
|
|
33
|
-
|
|
46
|
+
</div>
|
|
34
|
-
<div class="font-bold mb-2">Counter: ${name}</div>
|
|
35
|
-
<div class="flex flex-1 flex-row
|
|
47
|
+
<div class="flex flex-1 flex-row items-center text-gray-700">
|
|
36
|
-
<button
|
|
48
|
+
<button class="bg-gray-300 text-gray-700 rounded hover:bg-gray-200 px-4 py-2 text-3xl focus:outline-none" @click=${actions.decrement}>-</button>
|
|
37
49
|
<div class="mx-20">
|
|
38
|
-
<h1 class="text-
|
|
50
|
+
<h1 class="text-3xl font-mono ${warningClass}">${count}</h1>
|
|
39
51
|
</div>
|
|
40
|
-
<button
|
|
52
|
+
<button class="bg-gray-300 text-gray-700 rounded hover:bg-gray-200 px-4 py-2 text-3xl focus:outline-none" @click=${actions.increment}>+</button>
|
|
41
53
|
</div>
|
|
42
54
|
</div>
|
|
43
55
|
`;
|
|
44
56
|
};
|
|
45
57
|
|
|
46
|
-
|
|
58
|
+
createElement({ url: 'app-counter.js' }, Counter);
|
|
47
59
|
|
|
48
|
-
|
|
60
|
+
console.log(renderHtml(html`<app-counter name="1"></app-counter>`));
|
|
49
61
|
```
|
src/index.js
DELETED
|
@@ -1,401 +0,0 @@
|
|
|
1
|
-
import { html as litHtml, render as litRender, directive as litDirective, NodePart, AttributePart, PropertyPart, isPrimitive } from './lit-html.js';
|
|
2
|
-
import { html as litServerHtml, directive as litServerDirective, isNodePart, isAttributePart, unsafePrefixString, renderToString } from './lit-html-server.js';
|
|
3
|
-
|
|
4
|
-
export const registry = {};
|
|
5
|
-
const isBrowser = typeof window.alert !== "undefined";
|
|
6
|
-
export const html = isBrowser ? litHtml : litServerHtml;
|
|
7
|
-
export const render = isBrowser ? litRender : renderToString;
|
|
8
|
-
export const directive = isBrowser ? litDirective : litServerDirective;
|
|
9
|
-
|
|
10
|
-
const previousValues = new WeakMap();
|
|
11
|
-
export const unsafeHTML = directive((value) => (part) => {
|
|
12
|
-
if (isBrowser) {
|
|
13
|
-
if (!(part instanceof NodePart)) {
|
|
14
|
-
throw new Error('unsafeHTML can only be used in text bindings');
|
|
15
|
-
}
|
|
16
|
-
const previousValue = previousValues.get(part);
|
|
17
|
-
if (previousValue !== undefined && isPrimitive(value) &&
|
|
18
|
-
value === previousValue.value && part.value === previousValue.fragment) {
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
const template = document.createElement('template');
|
|
22
|
-
template.innerHTML = value; // innerHTML casts to string internally
|
|
23
|
-
const fragment = document.importNode(template.content, true);
|
|
24
|
-
part.setValue(fragment);
|
|
25
|
-
previousValues.set(part, { value, fragment });
|
|
26
|
-
} else {
|
|
27
|
-
if (!isNodePart(part)) {
|
|
28
|
-
throw Error('The `unsafeHTML` directive can only be used in text nodes');
|
|
29
|
-
}
|
|
30
|
-
part.setValue(`${unsafePrefixString}${value}`);
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
const previousClassesCache = new WeakMap();
|
|
35
|
-
export const classMap = directive((classInfo) => (part) => {
|
|
36
|
-
if (isBrowser) {
|
|
37
|
-
if (!(part instanceof AttributePart) || (part instanceof PropertyPart) ||
|
|
38
|
-
part.committer.name !== 'class' || part.committer.parts.length > 1) {
|
|
39
|
-
throw new Error('The `classMap` directive must be used in the `class` attribute ' +
|
|
40
|
-
'and must be the only part in the attribute.');
|
|
41
|
-
}
|
|
42
|
-
const { committer } = part;
|
|
43
|
-
const { element } = committer;
|
|
44
|
-
let previousClasses = previousClassesCache.get(part);
|
|
45
|
-
if (previousClasses === undefined) {
|
|
46
|
-
// Write static classes once
|
|
47
|
-
// Use setAttribute() because className isn't a string on SVG elements
|
|
48
|
-
element.setAttribute('class', committer.strings.join(' '));
|
|
49
|
-
previousClassesCache.set(part, previousClasses = new Set());
|
|
50
|
-
}
|
|
51
|
-
const classList = element.classList;
|
|
52
|
-
// Remove old classes that no longer apply
|
|
53
|
-
// We use forEach() instead of for-of so that re don't require down-level
|
|
54
|
-
// iteration.
|
|
55
|
-
previousClasses.forEach((name) => {
|
|
56
|
-
if (!(name in classInfo)) {
|
|
57
|
-
classList.remove(name);
|
|
58
|
-
previousClasses.delete(name);
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
// Add or remove classes based on their classMap value
|
|
62
|
-
for (const name in classInfo) {
|
|
63
|
-
const value = classInfo[name];
|
|
64
|
-
if (value != previousClasses.has(name)) {
|
|
65
|
-
// We explicitly want a loose truthy check of `value` because it seems
|
|
66
|
-
// more convenient that '' and 0 are skipped.
|
|
67
|
-
if (value) {
|
|
68
|
-
classList.add(name);
|
|
69
|
-
previousClasses.add(name);
|
|
70
|
-
}
|
|
71
|
-
else {
|
|
72
|
-
classList.remove(name);
|
|
73
|
-
previousClasses.delete(name);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
if (typeof classList.commit === 'function') {
|
|
78
|
-
classList.commit();
|
|
79
|
-
}
|
|
80
|
-
} else {
|
|
81
|
-
if (!isAttributePart(part) || part.name !== 'class') {
|
|
82
|
-
throw Error('The `classMap` directive can only be used in the `class` attribute');
|
|
83
|
-
}
|
|
84
|
-
const classes = (classInfo);
|
|
85
|
-
let value = '';
|
|
86
|
-
for (const key in classes) {
|
|
87
|
-
if (classes[key]) {
|
|
88
|
-
value += `${value.length ? ' ' : ''}${key}`;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
part.setValue(value);
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
let currentCursor;
|
|
96
|
-
let currentComponent;
|
|
97
|
-
|
|
98
|
-
export const logError = (msg) => {
|
|
99
|
-
if (window.logError) {
|
|
100
|
-
window.logError(msg);
|
|
101
|
-
} else {
|
|
102
|
-
console.warn(msg);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const checkRequired = (context, data) => {
|
|
107
|
-
if (data === null || typeof data === 'undefined') {
|
|
108
|
-
logError(`'${context}' Field is required`);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const checkPrimitive = (primitiveType) => {
|
|
113
|
-
const common = {
|
|
114
|
-
type: primitiveType,
|
|
115
|
-
parse: (attr) => attr,
|
|
116
|
-
}
|
|
117
|
-
const validate = (context, data) => {
|
|
118
|
-
if (data === null || typeof data === 'undefined') {
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
const dataType = typeof data;
|
|
122
|
-
if (dataType !== primitiveType) {
|
|
123
|
-
logError(`'${context}' Expected type '${primitiveType}' got type '${dataType}'`);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
return {
|
|
127
|
-
validate,
|
|
128
|
-
...common,
|
|
129
|
-
isRequired: {
|
|
130
|
-
...common,
|
|
131
|
-
validate: (context, data) => {
|
|
132
|
-
checkRequired(context, data);
|
|
133
|
-
validate(context, data);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const checkComplex = (complexType, validate) => {
|
|
140
|
-
const common = {
|
|
141
|
-
type: complexType,
|
|
142
|
-
parse: (attr) => attr ? JSON.parse(attr.replace(/'/g, `"`)) : null
|
|
143
|
-
};
|
|
144
|
-
return (innerType) => {
|
|
145
|
-
return {
|
|
146
|
-
...common,
|
|
147
|
-
validate: (context, data) => {
|
|
148
|
-
if (!data) {
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
validate(innerType, context, data)
|
|
152
|
-
},
|
|
153
|
-
isRequired: {
|
|
154
|
-
...common,
|
|
155
|
-
validate: (context, data) => {
|
|
156
|
-
checkRequired(context, data);
|
|
157
|
-
validate(innerType, context, data);
|
|
158
|
-
}
|
|
159
|
-
},
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
export const number = checkPrimitive('number');
|
|
165
|
-
export const string = checkPrimitive('string');
|
|
166
|
-
export const boolean = checkPrimitive('boolean');
|
|
167
|
-
export const object = checkComplex('object', (innerType, context, data) => {
|
|
168
|
-
if (data.constructor !== Object) {
|
|
169
|
-
logError(`'${context}' Expected object literal '{}' got '${typeof data}'`);
|
|
170
|
-
}
|
|
171
|
-
for (const key of Object.keys(innerType)) {
|
|
172
|
-
const fieldValidator = innerType[key];
|
|
173
|
-
const item = data[key];
|
|
174
|
-
fieldValidator.validate(`${context}.${key}`, item)
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
export const array = checkComplex('array', (innerType, context, data) => {
|
|
178
|
-
if (!Array.isArray(data)) {
|
|
179
|
-
logError(`Expected Array got ${data}`);
|
|
180
|
-
}
|
|
181
|
-
for (let i = 0; i < data.length; i++) {
|
|
182
|
-
const item = data[i];
|
|
183
|
-
innerType.validate(`${context}[${i}]`, item)
|
|
184
|
-
}
|
|
185
|
-
});
|
|
186
|
-
export const func = checkComplex('function', (innerType, context, data) => {
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
export const hooks = (config) => {
|
|
190
|
-
const h = currentComponent.hooks;
|
|
191
|
-
const c = currentComponent;
|
|
192
|
-
const index = currentCursor++;
|
|
193
|
-
if (h.values.length <= index && config.oncreate) {
|
|
194
|
-
h.values[index] = config.oncreate(h, c, index);
|
|
195
|
-
}
|
|
196
|
-
if (config.onupdate) {
|
|
197
|
-
h.values[index] = config.onupdate(h, c, index);
|
|
198
|
-
}
|
|
199
|
-
return h.values[index];
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
export const defaultHooks = () => ({
|
|
203
|
-
values: [],
|
|
204
|
-
deps: [],
|
|
205
|
-
effects: [],
|
|
206
|
-
layoutEffects: [],
|
|
207
|
-
cleanup: []
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
export const __setCurrent__ = (c) => {
|
|
211
|
-
currentComponent = c;
|
|
212
|
-
currentCursor = 0;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
export const useDispatchEvent = (name) => hooks({
|
|
216
|
-
oncreate: (_, c) => (data) => c.dispatchEvent(new CustomEvent(name, data))
|
|
217
|
-
});
|
|
218
|
-
export const useRef = (initialValue) => hooks({
|
|
219
|
-
oncreate: (_h, _c) => ({ current: initialValue })
|
|
220
|
-
});
|
|
221
|
-
export const useState = (initialState) => hooks({
|
|
222
|
-
oncreate: (h, c, i) => [
|
|
223
|
-
typeof initialState === "function"
|
|
224
|
-
? initialState()
|
|
225
|
-
: initialState,
|
|
226
|
-
function setState(nextState) {
|
|
227
|
-
const state = h.values[i][0];
|
|
228
|
-
if (typeof nextState === "function") {
|
|
229
|
-
nextState = nextState(state);
|
|
230
|
-
}
|
|
231
|
-
if (!Object.is(state, nextState)) {
|
|
232
|
-
h.values[i][0] = nextState;
|
|
233
|
-
c.update();
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
]
|
|
237
|
-
});
|
|
238
|
-
export const useReducer = (reducer, initialState) => hooks({
|
|
239
|
-
oncreate: (h, c, i) => [
|
|
240
|
-
initialState,
|
|
241
|
-
function dispatch(action) {
|
|
242
|
-
const state = h.values[i][0];
|
|
243
|
-
const nextState = reducer(state, action);
|
|
244
|
-
if (!Object.is(state, nextState)) {
|
|
245
|
-
h.values[i][0] = nextState;
|
|
246
|
-
c.update();
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
]
|
|
250
|
-
});
|
|
251
|
-
const depsChanged = (prev, next) => prev == null || next.some((f, i) => !Object.is(f, prev[i]));
|
|
252
|
-
export const useEffect = (handler, deps) => hooks({
|
|
253
|
-
onupdate(h, _, i) {
|
|
254
|
-
if (!deps || depsChanged(h.deps[i], deps)) {
|
|
255
|
-
h.deps[i] = deps || [];
|
|
256
|
-
h.effects[i] = handler;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
});
|
|
260
|
-
export const useLayoutEffect = (handler, deps) => hooks({
|
|
261
|
-
onupdate(h, _, i) {
|
|
262
|
-
if (!deps || depsChanged(h.deps[i], deps)) {
|
|
263
|
-
h.deps[i] = deps || [];
|
|
264
|
-
h.layoutEffects[i] = handler;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
});
|
|
268
|
-
export const useMemo = (fn, deps) => hooks({
|
|
269
|
-
onupdate(h, _, i) {
|
|
270
|
-
let value = h.values[i];
|
|
271
|
-
if (!deps || depsChanged(h.deps[i], deps)) {
|
|
272
|
-
h.deps[i] = deps || [];
|
|
273
|
-
value = fn();
|
|
274
|
-
}
|
|
275
|
-
return value;
|
|
276
|
-
}
|
|
277
|
-
});
|
|
278
|
-
export const useCallback = (callback, deps) => useMemo(() => callback, deps);
|
|
279
|
-
|
|
280
|
-
const batch = (runner, pick, callback) => {
|
|
281
|
-
const q = [];
|
|
282
|
-
const flush = () => {
|
|
283
|
-
let p;
|
|
284
|
-
while ((p = pick(q)))
|
|
285
|
-
callback(p);
|
|
286
|
-
};
|
|
287
|
-
const run = runner(flush);
|
|
288
|
-
return (c) => q.push(c) === 1 && run();
|
|
289
|
-
};
|
|
290
|
-
const fifo = (q) => q.shift();
|
|
291
|
-
const filo = (q) => q.pop();
|
|
292
|
-
const microtask = (flush) => {
|
|
293
|
-
return () => queueMicrotask(flush);
|
|
294
|
-
};
|
|
295
|
-
const task = (flush) => {
|
|
296
|
-
if (isBrowser) {
|
|
297
|
-
const ch = new MessageChannel();
|
|
298
|
-
ch.port1.onmessage = flush;
|
|
299
|
-
return () => ch.port2.postMessage(null);
|
|
300
|
-
}
|
|
301
|
-
else {
|
|
302
|
-
return () => setImmediate(flush);
|
|
303
|
-
}
|
|
304
|
-
};
|
|
305
|
-
const enqueueLayoutEffects = batch(microtask, filo, c => c._flushEffects("layoutEffects"));
|
|
306
|
-
const enqueueEffects = batch(task, filo, c => c._flushEffects("effects"));
|
|
307
|
-
const enqueueUpdate = batch(microtask, fifo, c => c._performUpdate());
|
|
308
|
-
|
|
309
|
-
export function defineElement(name, fn, propTypes = {}) {
|
|
310
|
-
registry[name] = fn;
|
|
311
|
-
const keys = Object.keys(propTypes);
|
|
312
|
-
const funcKeys = keys.filter((key) => propTypes[key].type === 'function');
|
|
313
|
-
const attributes = keys.filter((key) => propTypes[key].type !== 'function').reduce((acc, key) => {
|
|
314
|
-
acc[key.toLowerCase()] = {
|
|
315
|
-
propName: key,
|
|
316
|
-
propType: propTypes[key],
|
|
317
|
-
};
|
|
318
|
-
return acc;
|
|
319
|
-
}, {});
|
|
320
|
-
if (isBrowser) {
|
|
321
|
-
if (customElements.get(name)) {
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
|
-
customElements.define(name, class extends HTMLElement {
|
|
325
|
-
constructor() {
|
|
326
|
-
super(...arguments);
|
|
327
|
-
this._dirty = false;
|
|
328
|
-
this._connected = false;
|
|
329
|
-
this.hooks = defaultHooks();
|
|
330
|
-
this.props = {};
|
|
331
|
-
this.renderer = fn;
|
|
332
|
-
}
|
|
333
|
-
connectedCallback() {
|
|
334
|
-
this._connected = true;
|
|
335
|
-
this.update();
|
|
336
|
-
}
|
|
337
|
-
disconnectedCallback() {
|
|
338
|
-
this._connected = false;
|
|
339
|
-
let cleanup;
|
|
340
|
-
while ((cleanup = this.hooks.cleanup.shift())) {
|
|
341
|
-
cleanup();
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
static get observedAttributes() {
|
|
346
|
-
return Object.keys(attributes);
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
attributeChangedCallback(key, oldValue, newValue) {
|
|
350
|
-
const attr = attributes[key];
|
|
351
|
-
const data = attr.propType.parse(newValue);
|
|
352
|
-
attr.propType.validate(`<${name}> ${key}`, data);
|
|
353
|
-
this.props[attr.propName] = data;
|
|
354
|
-
if (this._connected) {
|
|
355
|
-
this.update();
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
update() {
|
|
360
|
-
if (this._dirty) {
|
|
361
|
-
return;
|
|
362
|
-
}
|
|
363
|
-
this._dirty = true;
|
|
364
|
-
enqueueUpdate(this);
|
|
365
|
-
}
|
|
366
|
-
_performUpdate() {
|
|
367
|
-
if (!this._connected) {
|
|
368
|
-
return;
|
|
369
|
-
}
|
|
370
|
-
__setCurrent__(this);
|
|
371
|
-
this.render();
|
|
372
|
-
enqueueLayoutEffects(this);
|
|
373
|
-
enqueueEffects(this);
|
|
374
|
-
this._dirty = false;
|
|
375
|
-
}
|
|
376
|
-
_flushEffects(effectKey) {
|
|
377
|
-
const effects = this.hooks[effectKey];
|
|
378
|
-
const cleanups = this.hooks.cleanup;
|
|
379
|
-
for (let i = 0, len = effects.length; i < len; i++) {
|
|
380
|
-
if (effects[i]) {
|
|
381
|
-
cleanups[i] && cleanups[i]();
|
|
382
|
-
const cleanup = effects[i]();
|
|
383
|
-
if (cleanup) {
|
|
384
|
-
cleanups[i] = cleanup;
|
|
385
|
-
}
|
|
386
|
-
delete effects[i];
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
render() {
|
|
392
|
-
funcKeys.forEach((key) => {
|
|
393
|
-
this.props[key] = this[key]
|
|
394
|
-
})
|
|
395
|
-
render(this.renderer(this.props), this);
|
|
396
|
-
}
|
|
397
|
-
});
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
export const getElement = (name) => registry[name];
|
src/lit-html-server.js
DELETED
|
@@ -1,1679 +0,0 @@
|
|
|
1
|
-
/* SNOWPACK PROCESS POLYFILL (based on https://github.com/calvinmetcalf/node-process-es6) */
|
|
2
|
-
function defaultSetTimout() {
|
|
3
|
-
throw new Error('setTimeout has not been defined');
|
|
4
|
-
}
|
|
5
|
-
function defaultClearTimeout() {
|
|
6
|
-
throw new Error('clearTimeout has not been defined');
|
|
7
|
-
}
|
|
8
|
-
var cachedSetTimeout = defaultSetTimout;
|
|
9
|
-
var cachedClearTimeout = defaultClearTimeout;
|
|
10
|
-
var globalContext;
|
|
11
|
-
if (typeof window !== 'undefined') {
|
|
12
|
-
globalContext = window;
|
|
13
|
-
} else if (typeof self !== 'undefined') {
|
|
14
|
-
globalContext = self;
|
|
15
|
-
} else {
|
|
16
|
-
globalContext = {};
|
|
17
|
-
}
|
|
18
|
-
if (typeof globalContext.setTimeout === 'function') {
|
|
19
|
-
cachedSetTimeout = setTimeout;
|
|
20
|
-
}
|
|
21
|
-
if (typeof globalContext.clearTimeout === 'function') {
|
|
22
|
-
cachedClearTimeout = clearTimeout;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function runTimeout(fun) {
|
|
26
|
-
if (cachedSetTimeout === setTimeout) {
|
|
27
|
-
//normal enviroments in sane situations
|
|
28
|
-
return setTimeout(fun, 0);
|
|
29
|
-
}
|
|
30
|
-
// if setTimeout wasn't available but was latter defined
|
|
31
|
-
if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) {
|
|
32
|
-
cachedSetTimeout = setTimeout;
|
|
33
|
-
return setTimeout(fun, 0);
|
|
34
|
-
}
|
|
35
|
-
try {
|
|
36
|
-
// when when somebody has screwed with setTimeout but no I.E. maddness
|
|
37
|
-
return cachedSetTimeout(fun, 0);
|
|
38
|
-
} catch (e) {
|
|
39
|
-
try {
|
|
40
|
-
// When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
|
|
41
|
-
return cachedSetTimeout.call(null, fun, 0);
|
|
42
|
-
} catch (e) {
|
|
43
|
-
// same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error
|
|
44
|
-
return cachedSetTimeout.call(this, fun, 0);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
function runClearTimeout(marker) {
|
|
51
|
-
if (cachedClearTimeout === clearTimeout) {
|
|
52
|
-
//normal enviroments in sane situations
|
|
53
|
-
return clearTimeout(marker);
|
|
54
|
-
}
|
|
55
|
-
// if clearTimeout wasn't available but was latter defined
|
|
56
|
-
if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) {
|
|
57
|
-
cachedClearTimeout = clearTimeout;
|
|
58
|
-
return clearTimeout(marker);
|
|
59
|
-
}
|
|
60
|
-
try {
|
|
61
|
-
// when when somebody has screwed with setTimeout but no I.E. maddness
|
|
62
|
-
return cachedClearTimeout(marker);
|
|
63
|
-
} catch (e) {
|
|
64
|
-
try {
|
|
65
|
-
// When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
|
|
66
|
-
return cachedClearTimeout.call(null, marker);
|
|
67
|
-
} catch (e) {
|
|
68
|
-
// same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error.
|
|
69
|
-
// Some versions of I.E. have different rules for clearTimeout vs setTimeout
|
|
70
|
-
return cachedClearTimeout.call(this, marker);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
var queue = [];
|
|
78
|
-
var draining = false;
|
|
79
|
-
var currentQueue;
|
|
80
|
-
var queueIndex = -1;
|
|
81
|
-
|
|
82
|
-
function cleanUpNextTick() {
|
|
83
|
-
if (!draining || !currentQueue) {
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
draining = false;
|
|
87
|
-
if (currentQueue.length) {
|
|
88
|
-
queue = currentQueue.concat(queue);
|
|
89
|
-
} else {
|
|
90
|
-
queueIndex = -1;
|
|
91
|
-
}
|
|
92
|
-
if (queue.length) {
|
|
93
|
-
drainQueue();
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function drainQueue() {
|
|
98
|
-
if (draining) {
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
var timeout = runTimeout(cleanUpNextTick);
|
|
102
|
-
draining = true;
|
|
103
|
-
|
|
104
|
-
var len = queue.length;
|
|
105
|
-
while (len) {
|
|
106
|
-
currentQueue = queue;
|
|
107
|
-
queue = [];
|
|
108
|
-
while (++queueIndex < len) {
|
|
109
|
-
if (currentQueue) {
|
|
110
|
-
currentQueue[queueIndex].run();
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
queueIndex = -1;
|
|
114
|
-
len = queue.length;
|
|
115
|
-
}
|
|
116
|
-
currentQueue = null;
|
|
117
|
-
draining = false;
|
|
118
|
-
runClearTimeout(timeout);
|
|
119
|
-
}
|
|
120
|
-
function nextTick(fun) {
|
|
121
|
-
var args = new Array(arguments.length - 1);
|
|
122
|
-
if (arguments.length > 1) {
|
|
123
|
-
for (var i = 1; i < arguments.length; i++) {
|
|
124
|
-
args[i - 1] = arguments[i];
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
queue.push(new Item(fun, args));
|
|
128
|
-
if (queue.length === 1 && !draining) {
|
|
129
|
-
runTimeout(drainQueue);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
// v8 likes predictible objects
|
|
133
|
-
function Item(fun, array) {
|
|
134
|
-
this.fun = fun;
|
|
135
|
-
this.array = array;
|
|
136
|
-
}
|
|
137
|
-
Item.prototype.run = function () {
|
|
138
|
-
this.fun.apply(null, this.array);
|
|
139
|
-
};
|
|
140
|
-
var title = 'browser';
|
|
141
|
-
var platform = 'browser';
|
|
142
|
-
var browser = true;
|
|
143
|
-
var argv = [];
|
|
144
|
-
var version = ''; // empty string to avoid regexp issues
|
|
145
|
-
var versions = {};
|
|
146
|
-
var release = {};
|
|
147
|
-
var config = {};
|
|
148
|
-
|
|
149
|
-
function noop() { }
|
|
150
|
-
|
|
151
|
-
var on = noop;
|
|
152
|
-
var addListener = noop;
|
|
153
|
-
var once = noop;
|
|
154
|
-
var off = noop;
|
|
155
|
-
var removeListener = noop;
|
|
156
|
-
var removeAllListeners = noop;
|
|
157
|
-
var emit = noop;
|
|
158
|
-
|
|
159
|
-
function binding(name) {
|
|
160
|
-
throw new Error('process.binding is not supported');
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function cwd() { return '/' }
|
|
164
|
-
function chdir(dir) {
|
|
165
|
-
throw new Error('process.chdir is not supported');
|
|
166
|
-
} function umask() { return 0; }
|
|
167
|
-
|
|
168
|
-
// from https://github.com/kumavis/browser-process-hrtime/blob/master/index.js
|
|
169
|
-
var performance = globalContext.performance || {};
|
|
170
|
-
var performanceNow =
|
|
171
|
-
performance.now ||
|
|
172
|
-
performance.mozNow ||
|
|
173
|
-
performance.msNow ||
|
|
174
|
-
performance.oNow ||
|
|
175
|
-
performance.webkitNow ||
|
|
176
|
-
function () { return (new Date()).getTime() };
|
|
177
|
-
|
|
178
|
-
// generate timestamp or delta
|
|
179
|
-
// see http://nodejs.org/api/process.html#process_process_hrtime
|
|
180
|
-
function hrtime(previousTimestamp) {
|
|
181
|
-
var clocktime = performanceNow.call(performance) * 1e-3;
|
|
182
|
-
var seconds = Math.floor(clocktime);
|
|
183
|
-
var nanoseconds = Math.floor((clocktime % 1) * 1e9);
|
|
184
|
-
if (previousTimestamp) {
|
|
185
|
-
seconds = seconds - previousTimestamp[0];
|
|
186
|
-
nanoseconds = nanoseconds - previousTimestamp[1];
|
|
187
|
-
if (nanoseconds < 0) {
|
|
188
|
-
seconds--;
|
|
189
|
-
nanoseconds += 1e9;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
return [seconds, nanoseconds]
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
var startTime = new Date();
|
|
196
|
-
function uptime() {
|
|
197
|
-
var currentTime = new Date();
|
|
198
|
-
var dif = currentTime - startTime;
|
|
199
|
-
return dif / 1000;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
var process = {
|
|
203
|
-
nextTick: nextTick,
|
|
204
|
-
title: title,
|
|
205
|
-
browser: browser,
|
|
206
|
-
env: { "NODE_ENV": "production" },
|
|
207
|
-
argv: argv,
|
|
208
|
-
version: version,
|
|
209
|
-
versions: versions,
|
|
210
|
-
on: on,
|
|
211
|
-
addListener: addListener,
|
|
212
|
-
once: once,
|
|
213
|
-
off: off,
|
|
214
|
-
removeListener: removeListener,
|
|
215
|
-
removeAllListeners: removeAllListeners,
|
|
216
|
-
emit: emit,
|
|
217
|
-
binding: binding,
|
|
218
|
-
cwd: cwd,
|
|
219
|
-
chdir: chdir,
|
|
220
|
-
umask: umask,
|
|
221
|
-
hrtime: hrtime,
|
|
222
|
-
platform: platform,
|
|
223
|
-
release: release,
|
|
224
|
-
config: config,
|
|
225
|
-
uptime: uptime
|
|
226
|
-
};
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Collection of shared utilities used by directives.
|
|
230
|
-
* Manually added to ensure that directives can be used by both index.js and browser.js
|
|
231
|
-
*/
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* A value for parts that signals a Part to clear its content
|
|
235
|
-
*/
|
|
236
|
-
const nothing = '__nothing-lit-html-server-string__';
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* A prefix value for strings that should not be escaped
|
|
240
|
-
*/
|
|
241
|
-
const unsafePrefixString = '__unsafe-lit-html-server-string__';
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* Determine if "part" is a NodePart
|
|
245
|
-
*
|
|
246
|
-
* @param { unknown } part
|
|
247
|
-
* @returns { part is NodePart }
|
|
248
|
-
*/
|
|
249
|
-
function isNodePart(part) {
|
|
250
|
-
// @ts-ignore
|
|
251
|
-
return part && part.getValue !== undefined && !('name' in part);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Determine if "part" is an AttributePart
|
|
256
|
-
*
|
|
257
|
-
* @param { unknown } part
|
|
258
|
-
* @returns { part is AttributePart }
|
|
259
|
-
*/
|
|
260
|
-
function isAttributePart(part) {
|
|
261
|
-
// @ts-ignore
|
|
262
|
-
return part && part.getValue !== undefined && 'name' in part;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* Determine if "value" is a primitive
|
|
267
|
-
*
|
|
268
|
-
* @param { unknown } value
|
|
269
|
-
* @returns { value is null|string|boolean|number }
|
|
270
|
-
*/
|
|
271
|
-
function isPrimitive(value) {
|
|
272
|
-
const type = typeof value;
|
|
273
|
-
|
|
274
|
-
return value === null || !(type === 'object' || type === 'function');
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* Determine if "obj" is a directive function
|
|
279
|
-
*
|
|
280
|
-
* @param { unknown } fn
|
|
281
|
-
* @returns { fn is Function }
|
|
282
|
-
*/
|
|
283
|
-
function isDirective(fn) {
|
|
284
|
-
// @ts-ignore
|
|
285
|
-
return typeof fn === 'function' && fn.isDirective;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
/**
|
|
289
|
-
* Define new directive for "fn".
|
|
290
|
-
* The passed function should be a factory function,
|
|
291
|
-
* and must return a function that will eventually be called with a Part instance
|
|
292
|
-
*
|
|
293
|
-
* @param { (...args: Array<unknown>) => (part: Part) => void } fn
|
|
294
|
-
* @returns { (...args: Array<unknown>) => (part: Part) => void }
|
|
295
|
-
*/
|
|
296
|
-
function directive(fn) {
|
|
297
|
-
return function directive(...args) {
|
|
298
|
-
const result = fn(...args);
|
|
299
|
-
|
|
300
|
-
if (typeof result !== 'function') {
|
|
301
|
-
throw Error('directives are factory functions and must return a function when called');
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// @ts-ignore
|
|
305
|
-
result.isDirective = true;
|
|
306
|
-
return result;
|
|
307
|
-
};
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/* global ReadableStream */
|
|
311
|
-
/**
|
|
312
|
-
* A custom Readable stream factory for rendering a template result to a stream
|
|
313
|
-
*
|
|
314
|
-
* @param { TemplateResult } result - a template result returned from call to "html`...`"
|
|
315
|
-
* @param { TemplateResultProcessor } processor
|
|
316
|
-
* @param { RenderOptions } [options]
|
|
317
|
-
* @returns { ReadableStream }
|
|
318
|
-
*/
|
|
319
|
-
function browserStreamTemplateRenderer(result, processor, options) {
|
|
320
|
-
if (typeof ReadableStream === 'undefined') {
|
|
321
|
-
throw Error('ReadableStream not supported on this platform');
|
|
322
|
-
}
|
|
323
|
-
if (typeof TextEncoder === 'undefined') {
|
|
324
|
-
throw Error('TextEncoder not supported on this platform');
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
return new ReadableStream({
|
|
328
|
-
// @ts-ignore
|
|
329
|
-
process: null,
|
|
330
|
-
start(controller) {
|
|
331
|
-
const encoder = new TextEncoder();
|
|
332
|
-
const underlyingSource = this;
|
|
333
|
-
let stack = [result];
|
|
334
|
-
|
|
335
|
-
this.process = processor.getProcessor(
|
|
336
|
-
{
|
|
337
|
-
push(chunk) {
|
|
338
|
-
if (chunk === null) {
|
|
339
|
-
controller.close();
|
|
340
|
-
return false;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
controller.enqueue(encoder.encode(chunk.toString()));
|
|
344
|
-
// Pause processing (return "false") if stream is full
|
|
345
|
-
return controller.desiredSize != null ? controller.desiredSize > 0 : true;
|
|
346
|
-
},
|
|
347
|
-
destroy(err) {
|
|
348
|
-
controller.error(err);
|
|
349
|
-
underlyingSource.process = undefined;
|
|
350
|
-
// @ts-ignore
|
|
351
|
-
stack = undefined;
|
|
352
|
-
},
|
|
353
|
-
},
|
|
354
|
-
stack,
|
|
355
|
-
16384,
|
|
356
|
-
options
|
|
357
|
-
);
|
|
358
|
-
},
|
|
359
|
-
pull() {
|
|
360
|
-
this.process();
|
|
361
|
-
},
|
|
362
|
-
});
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* A minimal Buffer class that implements a partial brower Buffer API around strings.
|
|
367
|
-
* This module should be provided as an alias for Node's built-in 'buffer' module
|
|
368
|
-
* when run in the browser.
|
|
369
|
-
* @see package.json#browser
|
|
370
|
-
*/
|
|
371
|
-
class Buffer {
|
|
372
|
-
/**
|
|
373
|
-
* Determine if 'buffer' is a buffer
|
|
374
|
-
*
|
|
375
|
-
* @param { any } buffer
|
|
376
|
-
* @returns { boolean }
|
|
377
|
-
*/
|
|
378
|
-
static isBuffer(buffer) {
|
|
379
|
-
return buffer != null && typeof buffer === 'object' && buffer.string !== undefined;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
/**
|
|
383
|
-
* Create buffer from 'string'
|
|
384
|
-
*
|
|
385
|
-
* @param { string } string
|
|
386
|
-
* @returns { Buffer }
|
|
387
|
-
*/
|
|
388
|
-
static from(string) {
|
|
389
|
-
string = typeof string === 'string' ? string : String(string);
|
|
390
|
-
return new Buffer(string);
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
/**
|
|
394
|
-
* Join 'buffers' into a single string
|
|
395
|
-
*
|
|
396
|
-
* @param { Array<any> } buffers
|
|
397
|
-
* @param { number } [length]
|
|
398
|
-
* @returns { Buffer }
|
|
399
|
-
*/
|
|
400
|
-
static concat(buffers, length) {
|
|
401
|
-
let string = '';
|
|
402
|
-
|
|
403
|
-
for (let i = 0, n = buffers.length; i < n; i++) {
|
|
404
|
-
const buffer = buffers[i];
|
|
405
|
-
|
|
406
|
-
string += typeof buffer === 'string' ? buffer : String(buffer);
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
if (length !== undefined && string.length > length) {
|
|
410
|
-
string = string.slice(0, length);
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
return new Buffer(string);
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
/**
|
|
417
|
-
* Construct Buffer instance
|
|
418
|
-
*
|
|
419
|
-
* @param { string } string
|
|
420
|
-
*/
|
|
421
|
-
constructor(string) {
|
|
422
|
-
this.string = string;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
/**
|
|
426
|
-
* Stringify
|
|
427
|
-
*
|
|
428
|
-
* @returns { string }
|
|
429
|
-
*/
|
|
430
|
-
toString() {
|
|
431
|
-
return this.string;
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
/**
|
|
436
|
-
* Determine whether "result" is a TemplateResult
|
|
437
|
-
*
|
|
438
|
-
* @param { unknown } result
|
|
439
|
-
* @returns { result is TemplateResult }
|
|
440
|
-
*/
|
|
441
|
-
function isTemplateResult(result) {
|
|
442
|
-
// @ts-ignore
|
|
443
|
-
return result && typeof result.template !== 'undefined' && typeof result.values !== 'undefined';
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
/**
|
|
447
|
-
* Determine if "promise" is a Promise instance
|
|
448
|
-
*
|
|
449
|
-
* @param { unknown } promise
|
|
450
|
-
* @returns { promise is Promise<unknown> }
|
|
451
|
-
*/
|
|
452
|
-
function isPromise(promise) {
|
|
453
|
-
// @ts-ignore
|
|
454
|
-
return promise != null && promise.then != null;
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
/**
|
|
458
|
-
* Determine if "iterator" is an synchronous iterator
|
|
459
|
-
*
|
|
460
|
-
* @param { unknown } iterator
|
|
461
|
-
* @returns { iterator is IterableIterator<unknown> }
|
|
462
|
-
*/
|
|
463
|
-
function isSyncIterator(iterator) {
|
|
464
|
-
return (
|
|
465
|
-
iterator != null &&
|
|
466
|
-
// Ignore strings (which are also iterable)
|
|
467
|
-
typeof iterator !== 'string' &&
|
|
468
|
-
// @ts-ignore
|
|
469
|
-
typeof iterator[Symbol.iterator] === 'function'
|
|
470
|
-
);
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
/**
|
|
474
|
-
* Determine if "iterator" is an asynchronous iterator
|
|
475
|
-
*
|
|
476
|
-
* @param { unknown } iterator
|
|
477
|
-
* @returns { iterator is AsyncIterable<unknown> }
|
|
478
|
-
*/
|
|
479
|
-
function isAsyncIterator(iterator) {
|
|
480
|
-
// @ts-ignore
|
|
481
|
-
return iterator != null && typeof iterator[Symbol.asyncIterator] === 'function';
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
/**
|
|
485
|
-
* Determine if "result" is an iterator result object
|
|
486
|
-
*
|
|
487
|
-
* @param { unknown } result
|
|
488
|
-
* @returns { result is IteratorResult<unknown, unknown> }
|
|
489
|
-
*/
|
|
490
|
-
function isIteratorResult(result) {
|
|
491
|
-
// @ts-ignore
|
|
492
|
-
return result != null && typeof result === 'object' && 'value' in result && 'done' in result;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
/**
|
|
496
|
-
* Determine if "value" is an object
|
|
497
|
-
*
|
|
498
|
-
* @param { unknown } value
|
|
499
|
-
* @returns { value is object }
|
|
500
|
-
*/
|
|
501
|
-
function isObject(value) {
|
|
502
|
-
return Object.prototype.toString.call(value) === '[object Object]';
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
/**
|
|
506
|
-
* Determine if "value" is a Buffer
|
|
507
|
-
*
|
|
508
|
-
* @param { unknown } value
|
|
509
|
-
* @returns { value is Buffer }
|
|
510
|
-
*/
|
|
511
|
-
function isBuffer(value) {
|
|
512
|
-
return Buffer.isBuffer(value);
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
/**
|
|
516
|
-
* Determine if "value" is an Array
|
|
517
|
-
*
|
|
518
|
-
* @param { unknown } value
|
|
519
|
-
* @returns { value is Array }
|
|
520
|
-
*/
|
|
521
|
-
function isArray(value) {
|
|
522
|
-
return Array.isArray(value);
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
// https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html#output-encoding-rules-summary
|
|
526
|
-
// https://github.com/mathiasbynens/jsesc/blob/master/jsesc.js
|
|
527
|
-
|
|
528
|
-
/** @type { { [name: string]: string } } */
|
|
529
|
-
const HTML_ESCAPES = {
|
|
530
|
-
'"': '"',
|
|
531
|
-
"'": ''',
|
|
532
|
-
'&': '&',
|
|
533
|
-
'<': '<',
|
|
534
|
-
'>': '>',
|
|
535
|
-
};
|
|
536
|
-
const RE_HTML = /["'&<>]/g;
|
|
537
|
-
const RE_SCRIPT_STYLE_TAG = /<\/(script|style)/gi;
|
|
538
|
-
|
|
539
|
-
/**
|
|
540
|
-
* Safely escape "string" for inlining
|
|
541
|
-
*
|
|
542
|
-
* @param { string } string
|
|
543
|
-
* @param { string } context - one of text|attribute|script|style
|
|
544
|
-
* @returns { string }
|
|
545
|
-
*/
|
|
546
|
-
function escape(string, context = 'text') {
|
|
547
|
-
switch (context) {
|
|
548
|
-
case 'script':
|
|
549
|
-
case 'style':
|
|
550
|
-
return string.replace(RE_SCRIPT_STYLE_TAG, '<\\/$1').replace(/<!--/g, '\\x3C!--');
|
|
551
|
-
case 'attribute':
|
|
552
|
-
case 'text':
|
|
553
|
-
default:
|
|
554
|
-
return string.replace(RE_HTML, (match) => HTML_ESCAPES[match]);
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
const EMPTY_STRING_BUFFER = Buffer.from('');
|
|
559
|
-
|
|
560
|
-
/**
|
|
561
|
-
* Base class interface for Node/Attribute parts
|
|
562
|
-
*/
|
|
563
|
-
class Part {
|
|
564
|
-
/**
|
|
565
|
-
* Constructor
|
|
566
|
-
*
|
|
567
|
-
* @param { string } tagName
|
|
568
|
-
*/
|
|
569
|
-
constructor(tagName) {
|
|
570
|
-
this.tagName = tagName;
|
|
571
|
-
this._value;
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
/**
|
|
575
|
-
* Store the current value.
|
|
576
|
-
* Used by directives to temporarily transfer value
|
|
577
|
-
* (value will be deleted after reading).
|
|
578
|
-
*
|
|
579
|
-
* @param { any } value
|
|
580
|
-
*/
|
|
581
|
-
setValue(value) {
|
|
582
|
-
this._value = value;
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
/**
|
|
586
|
-
* Retrieve resolved string from passed "value"
|
|
587
|
-
*
|
|
588
|
-
* @param { any } value
|
|
589
|
-
* @param { RenderOptions } [options]
|
|
590
|
-
* @returns { any }
|
|
591
|
-
*/
|
|
592
|
-
getValue(value, options) {
|
|
593
|
-
return value;
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
/**
|
|
597
|
-
* No-op
|
|
598
|
-
*/
|
|
599
|
-
commit() { }
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
/**
|
|
603
|
-
* A dynamic template part for text nodes
|
|
604
|
-
*/
|
|
605
|
-
class NodePart extends Part {
|
|
606
|
-
/**
|
|
607
|
-
* Retrieve resolved value given passed "value"
|
|
608
|
-
*
|
|
609
|
-
* @param { any } value
|
|
610
|
-
* @param { RenderOptions } [options]
|
|
611
|
-
* @returns { any }
|
|
612
|
-
*/
|
|
613
|
-
getValue(value, options) {
|
|
614
|
-
return resolveNodeValue(value, this);
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
/**
|
|
619
|
-
* A dynamic template part for attributes.
|
|
620
|
-
* Unlike text nodes, attributes may contain multiple strings and parts.
|
|
621
|
-
*/
|
|
622
|
-
class AttributePart extends Part {
|
|
623
|
-
/**
|
|
624
|
-
* Constructor
|
|
625
|
-
*
|
|
626
|
-
* @param { string } name
|
|
627
|
-
* @param { Array<Buffer> } strings
|
|
628
|
-
* @param { string } tagName
|
|
629
|
-
*/
|
|
630
|
-
constructor(name, strings, tagName) {
|
|
631
|
-
super(tagName);
|
|
632
|
-
this.name = name;
|
|
633
|
-
this.strings = strings;
|
|
634
|
-
this.length = strings.length - 1;
|
|
635
|
-
this.prefix = Buffer.from(`${this.name}="`);
|
|
636
|
-
this.suffix = Buffer.from(`${this.strings[this.length]}"`);
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
/**
|
|
640
|
-
* Retrieve resolved string Buffer from passed "values".
|
|
641
|
-
* Resolves to a single string, or Promise for a single string,
|
|
642
|
-
* even when responsible for multiple values.
|
|
643
|
-
*
|
|
644
|
-
* @param { Array<unknown> } values
|
|
645
|
-
* @param { RenderOptions } [options]
|
|
646
|
-
* @returns { Buffer | Promise<Buffer> }
|
|
647
|
-
*/
|
|
648
|
-
getValue(values, options) {
|
|
649
|
-
let chunks = [this.prefix];
|
|
650
|
-
let chunkLength = this.prefix.length;
|
|
651
|
-
let pendingChunks;
|
|
652
|
-
|
|
653
|
-
for (let i = 0; i < this.length; i++) {
|
|
654
|
-
const string = this.strings[i];
|
|
655
|
-
let value = resolveAttributeValue(
|
|
656
|
-
values[i],
|
|
657
|
-
this,
|
|
658
|
-
options !== undefined ? options.serializePropertyAttributes : false
|
|
659
|
-
);
|
|
660
|
-
|
|
661
|
-
// Bail if 'nothing'
|
|
662
|
-
if (value === nothing) {
|
|
663
|
-
return EMPTY_STRING_BUFFER;
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
chunks.push(string);
|
|
667
|
-
chunkLength += string.length;
|
|
668
|
-
|
|
669
|
-
if (isBuffer(value)) {
|
|
670
|
-
chunks.push(value);
|
|
671
|
-
chunkLength += value.length;
|
|
672
|
-
} else if (isPromise(value)) {
|
|
673
|
-
// Lazy init for uncommon scenario
|
|
674
|
-
if (pendingChunks === undefined) {
|
|
675
|
-
pendingChunks = [];
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
// @ts-ignore
|
|
679
|
-
const index = chunks.push(value) - 1;
|
|
680
|
-
|
|
681
|
-
pendingChunks.push(
|
|
682
|
-
value.then((value) => {
|
|
683
|
-
// @ts-ignore
|
|
684
|
-
chunks[index] = value;
|
|
685
|
-
// @ts-ignore
|
|
686
|
-
chunkLength += value.length;
|
|
687
|
-
})
|
|
688
|
-
);
|
|
689
|
-
} else if (isArray(value)) {
|
|
690
|
-
for (const chunk of value) {
|
|
691
|
-
chunks.push(chunk);
|
|
692
|
-
chunkLength += chunk.length;
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
chunks.push(this.suffix);
|
|
698
|
-
chunkLength += this.suffix.length;
|
|
699
|
-
if (pendingChunks !== undefined) {
|
|
700
|
-
return Promise.all(pendingChunks).then(() => Buffer.concat(chunks, chunkLength));
|
|
701
|
-
}
|
|
702
|
-
return Buffer.concat(chunks, chunkLength);
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
/**
|
|
707
|
-
* A dynamic template part for boolean attributes.
|
|
708
|
-
* Boolean attributes are prefixed with "?"
|
|
709
|
-
*/
|
|
710
|
-
class BooleanAttributePart extends AttributePart {
|
|
711
|
-
/**
|
|
712
|
-
* Constructor
|
|
713
|
-
*
|
|
714
|
-
* @param { string } name
|
|
715
|
-
* @param { Array<Buffer> } strings
|
|
716
|
-
* @param { string } tagName
|
|
717
|
-
* @throws error when multiple expressions
|
|
718
|
-
*/
|
|
719
|
-
constructor(name, strings, tagName) {
|
|
720
|
-
super(name, strings, tagName);
|
|
721
|
-
|
|
722
|
-
this.nameAsBuffer = Buffer.from(this.name);
|
|
723
|
-
|
|
724
|
-
if (
|
|
725
|
-
strings.length !== 2 ||
|
|
726
|
-
strings[0] === EMPTY_STRING_BUFFER ||
|
|
727
|
-
strings[1] === EMPTY_STRING_BUFFER
|
|
728
|
-
) {
|
|
729
|
-
throw Error('Boolean attributes can only contain a single expression');
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
/**
|
|
734
|
-
* Retrieve resolved string Buffer from passed "values".
|
|
735
|
-
*
|
|
736
|
-
* @param { Array<unknown> } values
|
|
737
|
-
* @param { RenderOptions } [options]
|
|
738
|
-
* @returns { Buffer | Promise<Buffer> }
|
|
739
|
-
*/
|
|
740
|
-
getValue(values, options) {
|
|
741
|
-
let value = values[0];
|
|
742
|
-
|
|
743
|
-
if (isDirective(value)) {
|
|
744
|
-
value = resolveDirectiveValue(value, this);
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
if (isPromise(value)) {
|
|
748
|
-
return value.then((value) => (value ? this.nameAsBuffer : EMPTY_STRING_BUFFER));
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
return value ? this.nameAsBuffer : EMPTY_STRING_BUFFER;
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
/**
|
|
756
|
-
* A dynamic template part for property attributes.
|
|
757
|
-
* Property attributes are prefixed with "."
|
|
758
|
-
*/
|
|
759
|
-
class PropertyAttributePart extends AttributePart {
|
|
760
|
-
/**
|
|
761
|
-
* Retrieve resolved string Buffer from passed "values".
|
|
762
|
-
* Returns an empty string unless "options.serializePropertyAttributes=true"
|
|
763
|
-
*
|
|
764
|
-
* @param { Array<unknown> } values
|
|
765
|
-
* @param { RenderOptions } [options]
|
|
766
|
-
* @returns { Buffer | Promise<Buffer> }
|
|
767
|
-
*/
|
|
768
|
-
getValue(values, options) {
|
|
769
|
-
if (options !== undefined && options.serializePropertyAttributes) {
|
|
770
|
-
const value = super.getValue(values, options);
|
|
771
|
-
const prefix = Buffer.from('.');
|
|
772
|
-
|
|
773
|
-
return value instanceof Promise
|
|
774
|
-
? value.then((value) => Buffer.concat([prefix, value]))
|
|
775
|
-
: Buffer.concat([prefix, value]);
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
return EMPTY_STRING_BUFFER;
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
/**
|
|
783
|
-
* A dynamic template part for event attributes.
|
|
784
|
-
* Event attributes are prefixed with "@"
|
|
785
|
-
*/
|
|
786
|
-
class EventAttributePart extends AttributePart {
|
|
787
|
-
/**
|
|
788
|
-
* Retrieve resolved string Buffer from passed "values".
|
|
789
|
-
* Event bindings have no server-side representation,
|
|
790
|
-
* so always returns an empty string.
|
|
791
|
-
*
|
|
792
|
-
* @param { Array<unknown> } values
|
|
793
|
-
* @param { RenderOptions } [options]
|
|
794
|
-
* @returns { Buffer }
|
|
795
|
-
*/
|
|
796
|
-
getValue(values, options) {
|
|
797
|
-
return EMPTY_STRING_BUFFER;
|
|
798
|
-
}
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
/**
|
|
802
|
-
* Resolve "value" to string if possible
|
|
803
|
-
*
|
|
804
|
-
* @param { unknown } value
|
|
805
|
-
* @param { AttributePart } part
|
|
806
|
-
* @param { boolean } [serialiseObjectsAndArrays]
|
|
807
|
-
* @returns { any }
|
|
808
|
-
*/
|
|
809
|
-
function resolveAttributeValue(value, part, serialiseObjectsAndArrays = false) {
|
|
810
|
-
if (isDirective(value)) {
|
|
811
|
-
value = resolveDirectiveValue(value, part);
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
if (value === nothing) {
|
|
815
|
-
return value;
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
if (isPrimitive(value)) {
|
|
819
|
-
const string = typeof value !== 'string' ? String(value) : value;
|
|
820
|
-
// Escape if not prefixed with unsafePrefixString, otherwise strip prefix
|
|
821
|
-
return Buffer.from(
|
|
822
|
-
string.indexOf(unsafePrefixString) === 0 ? string.slice(33) : escape(string, 'attribute')
|
|
823
|
-
);
|
|
824
|
-
} else if (typeof value === 'object') {
|
|
825
|
-
return Buffer.from(JSON.stringify(value).replace(/"/g, `'`));
|
|
826
|
-
} else if (isBuffer(value)) {
|
|
827
|
-
return value;
|
|
828
|
-
} else if (serialiseObjectsAndArrays && (isObject(value) || isArray(value))) {
|
|
829
|
-
return Buffer.from(escape(JSON.stringify(value), 'attribute'));
|
|
830
|
-
} else if (isPromise(value)) {
|
|
831
|
-
return value.then((value) => resolveAttributeValue(value, part, serialiseObjectsAndArrays));
|
|
832
|
-
} else if (isSyncIterator(value)) {
|
|
833
|
-
if (!isArray(value)) {
|
|
834
|
-
value = Array.from(value);
|
|
835
|
-
}
|
|
836
|
-
return Buffer.concat(
|
|
837
|
-
// @ts-ignore: already converted to Array
|
|
838
|
-
value.reduce((values, value) => {
|
|
839
|
-
value = resolveAttributeValue(value, part, serialiseObjectsAndArrays);
|
|
840
|
-
// Flatten
|
|
841
|
-
if (isArray(value)) {
|
|
842
|
-
return values.concat(value);
|
|
843
|
-
}
|
|
844
|
-
values.push(value);
|
|
845
|
-
return values;
|
|
846
|
-
}, [])
|
|
847
|
-
);
|
|
848
|
-
} else {
|
|
849
|
-
return Buffer.from(String(value));
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
/**
|
|
854
|
-
* Resolve "value" to string Buffer if possible
|
|
855
|
-
*
|
|
856
|
-
* @param { unknown } value
|
|
857
|
-
* @param { NodePart } part
|
|
858
|
-
* @returns { any }
|
|
859
|
-
*/
|
|
860
|
-
function resolveNodeValue(value, part) {
|
|
861
|
-
if (isDirective(value)) {
|
|
862
|
-
value = resolveDirectiveValue(value, part);
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
if (value === nothing || value === undefined) {
|
|
866
|
-
return EMPTY_STRING_BUFFER;
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
if (isPrimitive(value)) {
|
|
870
|
-
const string = typeof value !== 'string' ? String(value) : value;
|
|
871
|
-
// Escape if not prefixed with unsafePrefixString, otherwise strip prefix
|
|
872
|
-
return Buffer.from(
|
|
873
|
-
string.indexOf(unsafePrefixString) === 0
|
|
874
|
-
? string.slice(33)
|
|
875
|
-
: escape(
|
|
876
|
-
string,
|
|
877
|
-
part.tagName === 'script' || part.tagName === 'style' ? part.tagName : 'text'
|
|
878
|
-
)
|
|
879
|
-
);
|
|
880
|
-
} else if (isTemplateResult(value) || isBuffer(value)) {
|
|
881
|
-
return value;
|
|
882
|
-
} else if (isPromise(value)) {
|
|
883
|
-
return value.then((value) => resolveNodeValue(value, part));
|
|
884
|
-
} else if (isSyncIterator(value)) {
|
|
885
|
-
if (!isArray(value)) {
|
|
886
|
-
value = Array.from(value);
|
|
887
|
-
}
|
|
888
|
-
// @ts-ignore: already converted to Array
|
|
889
|
-
return value.reduce((values, value) => {
|
|
890
|
-
value = resolveNodeValue(value, part);
|
|
891
|
-
// Flatten
|
|
892
|
-
if (isArray(value)) {
|
|
893
|
-
return values.concat(value);
|
|
894
|
-
}
|
|
895
|
-
values.push(value);
|
|
896
|
-
return values;
|
|
897
|
-
}, []);
|
|
898
|
-
} else if (isAsyncIterator(value)) {
|
|
899
|
-
return resolveAsyncIteratorValue(value, part);
|
|
900
|
-
} else {
|
|
901
|
-
throw Error(`unknown NodePart value: ${value}`);
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
/**
|
|
906
|
-
* Resolve values of async "iterator"
|
|
907
|
-
*
|
|
908
|
-
* @param { AsyncIterable<unknown> } iterator
|
|
909
|
-
* @param { NodePart } part
|
|
910
|
-
* @returns { AsyncGenerator }
|
|
911
|
-
*/
|
|
912
|
-
async function* resolveAsyncIteratorValue(iterator, part) {
|
|
913
|
-
for await (const value of iterator) {
|
|
914
|
-
yield resolveNodeValue(value, part);
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
/**
|
|
919
|
-
* Resolve value of "directive"
|
|
920
|
-
*
|
|
921
|
-
* @param { function } directive
|
|
922
|
-
* @param { Part } part
|
|
923
|
-
* @returns { unknown }
|
|
924
|
-
*/
|
|
925
|
-
function resolveDirectiveValue(directive, part) {
|
|
926
|
-
// Directives are synchronous, so it's safe to read and delete value
|
|
927
|
-
directive(part);
|
|
928
|
-
const value = part._value;
|
|
929
|
-
part._value = undefined;
|
|
930
|
-
return value;
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
/**
|
|
934
|
-
* Class representing the default Template processor.
|
|
935
|
-
* Exposes factory functions for generating Part instances to use for
|
|
936
|
-
* resolving a template's dynamic values.
|
|
937
|
-
*/
|
|
938
|
-
class DefaultTemplateProcessor {
|
|
939
|
-
/**
|
|
940
|
-
* Create part instance for dynamic attribute values
|
|
941
|
-
*
|
|
942
|
-
* @param { string } name
|
|
943
|
-
* @param { Array<Buffer> } strings
|
|
944
|
-
* @param { string } tagName
|
|
945
|
-
* @returns { AttributePart }
|
|
946
|
-
*/
|
|
947
|
-
handleAttributeExpressions(name, strings = [], tagName) {
|
|
948
|
-
const prefix = name[0];
|
|
949
|
-
|
|
950
|
-
if (prefix === '.') {
|
|
951
|
-
return new PropertyAttributePart(name.slice(1), strings, tagName);
|
|
952
|
-
} else if (prefix === '@') {
|
|
953
|
-
return new EventAttributePart(name.slice(1), strings, tagName);
|
|
954
|
-
} else if (prefix === '?') {
|
|
955
|
-
return new BooleanAttributePart(name.slice(1), strings, tagName);
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
return new AttributePart(name, strings, tagName);
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
/**
|
|
962
|
-
* Create part instance for dynamic text values
|
|
963
|
-
*
|
|
964
|
-
* @param { string } tagName
|
|
965
|
-
* @returns { NodePart }
|
|
966
|
-
*/
|
|
967
|
-
handleTextExpression(tagName) {
|
|
968
|
-
return new NodePart(tagName);
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
/* eslint no-constant-condition:0 */
|
|
973
|
-
|
|
974
|
-
/**
|
|
975
|
-
* Class for the default TemplateResult processor
|
|
976
|
-
* used by Promise/Stream TemplateRenderers.
|
|
977
|
-
*
|
|
978
|
-
* @implements TemplateResultProcessor
|
|
979
|
-
*/
|
|
980
|
-
class DefaultTemplateResultProcessor {
|
|
981
|
-
/**
|
|
982
|
-
* Process "stack" and push chunks to "renderer"
|
|
983
|
-
*
|
|
984
|
-
* @param { TemplateResultRenderer } renderer
|
|
985
|
-
* @param { Array<unknown> } stack
|
|
986
|
-
* @param { number } [highWaterMark] - byte length to buffer before pushing data
|
|
987
|
-
* @param { RenderOptions } [options]
|
|
988
|
-
* @returns { () => void }
|
|
989
|
-
*/
|
|
990
|
-
getProcessor(renderer, stack, highWaterMark = 0, options) {
|
|
991
|
-
/** @type { Array<Buffer> } */
|
|
992
|
-
const buffer = [];
|
|
993
|
-
let bufferLength = 0;
|
|
994
|
-
let processing = false;
|
|
995
|
-
|
|
996
|
-
function flushBuffer() {
|
|
997
|
-
if (buffer.length > 0) {
|
|
998
|
-
const keepPushing = renderer.push(Buffer.concat(buffer, bufferLength));
|
|
999
|
-
|
|
1000
|
-
bufferLength = buffer.length = 0;
|
|
1001
|
-
return keepPushing;
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
return function process() {
|
|
1006
|
-
if (processing) {
|
|
1007
|
-
return;
|
|
1008
|
-
}
|
|
1009
|
-
|
|
1010
|
-
while (true) {
|
|
1011
|
-
processing = true;
|
|
1012
|
-
let chunk = stack[0];
|
|
1013
|
-
let breakLoop = false;
|
|
1014
|
-
let popStack = true;
|
|
1015
|
-
|
|
1016
|
-
// Done
|
|
1017
|
-
if (chunk === undefined) {
|
|
1018
|
-
flushBuffer();
|
|
1019
|
-
return renderer.push(null);
|
|
1020
|
-
}
|
|
1021
|
-
|
|
1022
|
-
if (isTemplateResult(chunk)) {
|
|
1023
|
-
popStack = false;
|
|
1024
|
-
chunk = getTemplateResultChunk(chunk, stack, options);
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
// Skip if finished reading TemplateResult (null)
|
|
1028
|
-
if (chunk !== null) {
|
|
1029
|
-
if (isBuffer(chunk)) {
|
|
1030
|
-
buffer.push(chunk);
|
|
1031
|
-
bufferLength += chunk.length;
|
|
1032
|
-
// Flush buffered data if over highWaterMark
|
|
1033
|
-
if (bufferLength > highWaterMark) {
|
|
1034
|
-
// Break if backpressure triggered
|
|
1035
|
-
breakLoop = !flushBuffer();
|
|
1036
|
-
processing = !breakLoop;
|
|
1037
|
-
}
|
|
1038
|
-
} else if (isPromise(chunk)) {
|
|
1039
|
-
// Flush buffered data before waiting for Promise
|
|
1040
|
-
flushBuffer();
|
|
1041
|
-
// "processing" is still true, so prevented from restarting until Promise resolved
|
|
1042
|
-
breakLoop = true;
|
|
1043
|
-
// Add pending Promise for value to stack
|
|
1044
|
-
stack.unshift(chunk);
|
|
1045
|
-
chunk
|
|
1046
|
-
.then((chunk) => {
|
|
1047
|
-
// Handle IteratorResults from AsyncIterator
|
|
1048
|
-
if (isIteratorResult(chunk)) {
|
|
1049
|
-
if (chunk.done) {
|
|
1050
|
-
// Clear resolved Promise
|
|
1051
|
-
stack.shift();
|
|
1052
|
-
// Clear AsyncIterator
|
|
1053
|
-
stack.shift();
|
|
1054
|
-
} else {
|
|
1055
|
-
// Replace resolved Promise with IteratorResult value
|
|
1056
|
-
stack[0] = chunk.value;
|
|
1057
|
-
}
|
|
1058
|
-
} else {
|
|
1059
|
-
// Replace resolved Promise with value
|
|
1060
|
-
stack[0] = chunk;
|
|
1061
|
-
}
|
|
1062
|
-
processing = false;
|
|
1063
|
-
process();
|
|
1064
|
-
})
|
|
1065
|
-
.catch((err) => {
|
|
1066
|
-
stack.length = 0;
|
|
1067
|
-
renderer.destroy(err);
|
|
1068
|
-
});
|
|
1069
|
-
} else if (isArray(chunk)) {
|
|
1070
|
-
// First remove existing Array if at top of stack (not added by pending TemplateResult)
|
|
1071
|
-
if (stack[0] === chunk) {
|
|
1072
|
-
popStack = false;
|
|
1073
|
-
stack.shift();
|
|
1074
|
-
}
|
|
1075
|
-
stack.unshift(...chunk);
|
|
1076
|
-
} else if (isAsyncIterator(chunk)) {
|
|
1077
|
-
popStack = false;
|
|
1078
|
-
// Add AsyncIterator back to stack (will be cleared when done iterating)
|
|
1079
|
-
if (stack[0] !== chunk) {
|
|
1080
|
-
stack.unshift(chunk);
|
|
1081
|
-
}
|
|
1082
|
-
// Add pending Promise for IteratorResult to stack
|
|
1083
|
-
stack.unshift(chunk[Symbol.asyncIterator]().next());
|
|
1084
|
-
} else {
|
|
1085
|
-
stack.length = 0;
|
|
1086
|
-
return renderer.destroy(Error(`unknown chunk type: ${chunk}`));
|
|
1087
|
-
}
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
|
-
if (popStack) {
|
|
1091
|
-
stack.shift();
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
|
-
if (breakLoop) {
|
|
1095
|
-
break;
|
|
1096
|
-
}
|
|
1097
|
-
}
|
|
1098
|
-
};
|
|
1099
|
-
}
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
|
-
/**
|
|
1103
|
-
* Retrieve next chunk from "result".
|
|
1104
|
-
* Adds nested TemplateResults to the stack if necessary.
|
|
1105
|
-
*
|
|
1106
|
-
* @param { TemplateResult } result
|
|
1107
|
-
* @param { Array<unknown> } stack
|
|
1108
|
-
* @param { RenderOptions } [options]
|
|
1109
|
-
*/
|
|
1110
|
-
function getTemplateResultChunk(result, stack, options) {
|
|
1111
|
-
let chunk = result.readChunk(options);
|
|
1112
|
-
|
|
1113
|
-
// Skip empty strings
|
|
1114
|
-
if (isBuffer(chunk) && chunk.length === 0) {
|
|
1115
|
-
chunk = result.readChunk(options);
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
// Finished reading, dispose
|
|
1119
|
-
if (chunk === null) {
|
|
1120
|
-
stack.shift();
|
|
1121
|
-
} else if (isTemplateResult(chunk)) {
|
|
1122
|
-
// Add to top of stack
|
|
1123
|
-
stack.unshift(chunk);
|
|
1124
|
-
chunk = getTemplateResultChunk(chunk, stack, options);
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
return chunk;
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
/**
|
|
1131
|
-
* A factory for rendering a template result to a string resolving Promise
|
|
1132
|
-
*
|
|
1133
|
-
* @param { TemplateResult } result
|
|
1134
|
-
* @param { TemplateResultProcessor } processor
|
|
1135
|
-
* @param { boolean } [asBuffer]
|
|
1136
|
-
* @param { RenderOptions } [options]
|
|
1137
|
-
*/
|
|
1138
|
-
function promiseTemplateRenderer(result, processor, asBuffer = false, options) {
|
|
1139
|
-
return new Promise((resolve, reject) => {
|
|
1140
|
-
let stack = [result];
|
|
1141
|
-
/** @type { Array<Buffer> } */
|
|
1142
|
-
let buffer = [];
|
|
1143
|
-
let bufferLength = 0;
|
|
1144
|
-
|
|
1145
|
-
processor.getProcessor(
|
|
1146
|
-
{
|
|
1147
|
-
push(chunk) {
|
|
1148
|
-
if (chunk === null) {
|
|
1149
|
-
const concatBuffer = Buffer.concat(buffer, bufferLength);
|
|
1150
|
-
resolve(asBuffer ? concatBuffer : concatBuffer.toString());
|
|
1151
|
-
} else {
|
|
1152
|
-
buffer.push(chunk);
|
|
1153
|
-
bufferLength += chunk.length;
|
|
1154
|
-
}
|
|
1155
|
-
return true;
|
|
1156
|
-
},
|
|
1157
|
-
destroy(err) {
|
|
1158
|
-
buffer.length = stack.length = bufferLength = 0;
|
|
1159
|
-
// @ts-ignore
|
|
1160
|
-
buffer = undefined;
|
|
1161
|
-
// @ts-ignore
|
|
1162
|
-
stack = undefined;
|
|
1163
|
-
reject(err);
|
|
1164
|
-
},
|
|
1165
|
-
},
|
|
1166
|
-
stack,
|
|
1167
|
-
0,
|
|
1168
|
-
options
|
|
1169
|
-
)();
|
|
1170
|
-
});
|
|
1171
|
-
}
|
|
1172
|
-
|
|
1173
|
-
/**
|
|
1174
|
-
* A minimal Readable stream class to prevent browser parse errors resulting from
|
|
1175
|
-
* the static importing of Node-only code.
|
|
1176
|
-
* This module should be provided as an alias for Node's built-in 'stream' module
|
|
1177
|
-
* when run in the browser.
|
|
1178
|
-
* @see package.json#browser
|
|
1179
|
-
*/
|
|
1180
|
-
class Readable { }
|
|
1181
|
-
|
|
1182
|
-
/**
|
|
1183
|
-
* Factory for StreamTemplateRenderer instances
|
|
1184
|
-
*
|
|
1185
|
-
* @param { TemplateResult } result - a template result returned from call to "html`...`"
|
|
1186
|
-
* @param { TemplateResultProcessor } processor
|
|
1187
|
-
* @param { RenderOptions } [options]
|
|
1188
|
-
* @returns { Readable }
|
|
1189
|
-
*/
|
|
1190
|
-
function streamTemplateRenderer(result, processor, options) {
|
|
1191
|
-
return new StreamTemplateRenderer(result, processor, options);
|
|
1192
|
-
}
|
|
1193
|
-
|
|
1194
|
-
/**
|
|
1195
|
-
* A custom Readable stream class for rendering a template result to a stream
|
|
1196
|
-
*/
|
|
1197
|
-
class StreamTemplateRenderer extends Readable {
|
|
1198
|
-
/**
|
|
1199
|
-
* Constructor
|
|
1200
|
-
*
|
|
1201
|
-
* @param { TemplateResult } result - a template result returned from call to "html`...`"
|
|
1202
|
-
* @param { TemplateResultProcessor } processor
|
|
1203
|
-
* @param { RenderOptions } [options]
|
|
1204
|
-
*/
|
|
1205
|
-
constructor(result, processor, options) {
|
|
1206
|
-
super({ autoDestroy: true });
|
|
1207
|
-
|
|
1208
|
-
this.stack = [result];
|
|
1209
|
-
this.process = processor.getProcessor(this, this.stack, 16384, options);
|
|
1210
|
-
}
|
|
1211
|
-
|
|
1212
|
-
/**
|
|
1213
|
-
* Extend Readable.read()
|
|
1214
|
-
*/
|
|
1215
|
-
_read() {
|
|
1216
|
-
if (this.process !== undefined) {
|
|
1217
|
-
this.process();
|
|
1218
|
-
}
|
|
1219
|
-
}
|
|
1220
|
-
|
|
1221
|
-
/**
|
|
1222
|
-
* Extend Readalbe.destroy()
|
|
1223
|
-
*
|
|
1224
|
-
* @param { Error | null } [err]
|
|
1225
|
-
*/
|
|
1226
|
-
_destroy(err) {
|
|
1227
|
-
if (err) {
|
|
1228
|
-
this.emit('error', err);
|
|
1229
|
-
}
|
|
1230
|
-
this.emit('close');
|
|
1231
|
-
|
|
1232
|
-
// @ts-ignore
|
|
1233
|
-
this.process = undefined;
|
|
1234
|
-
// @ts-ignore
|
|
1235
|
-
this.stack = undefined;
|
|
1236
|
-
this.removeAllListeners();
|
|
1237
|
-
}
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
|
-
/**
|
|
1241
|
-
* @license
|
|
1242
|
-
* Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
1243
|
-
* This code may only be used under the BSD style license found at
|
|
1244
|
-
* http://polymer.github.io/LICENSE.txt
|
|
1245
|
-
* The complete set of authors may be found at
|
|
1246
|
-
* http://polymer.github.io/AUTHORS.txt
|
|
1247
|
-
* The complete set of contributors may be found at
|
|
1248
|
-
* http://polymer.github.io/CONTRIBUTORS.txt
|
|
1249
|
-
* Code distributed by Google as part of the polymer project is also
|
|
1250
|
-
* subject to an additional IP rights grant found at
|
|
1251
|
-
* http://polymer.github.io/PATENTS.txt
|
|
1252
|
-
*/
|
|
1253
|
-
/**
|
|
1254
|
-
* An expression marker with embedded unique key to avoid collision with
|
|
1255
|
-
* possible text in templates.
|
|
1256
|
-
*/
|
|
1257
|
-
const marker = `{{lit-${String(Math.random()).slice(2)}}}`;
|
|
1258
|
-
/**
|
|
1259
|
-
* This regex extracts the attribute name preceding an attribute-position
|
|
1260
|
-
* expression. It does this by matching the syntax allowed for attributes
|
|
1261
|
-
* against the string literal directly preceding the expression, assuming that
|
|
1262
|
-
* the expression is in an attribute-value position.
|
|
1263
|
-
*
|
|
1264
|
-
* See attributes in the HTML spec:
|
|
1265
|
-
* https://www.w3.org/TR/html5/syntax.html#elements-attributes
|
|
1266
|
-
*
|
|
1267
|
-
* " \x09\x0a\x0c\x0d" are HTML space characters:
|
|
1268
|
-
* https://www.w3.org/TR/html5/infrastructure.html#space-characters
|
|
1269
|
-
*
|
|
1270
|
-
* "\0-\x1F\x7F-\x9F" are Unicode control characters, which includes every
|
|
1271
|
-
* space character except " ".
|
|
1272
|
-
*
|
|
1273
|
-
* So an attribute is:
|
|
1274
|
-
* * The name: any character except a control character, space character, ('),
|
|
1275
|
-
* ("), ">", "=", or "/"
|
|
1276
|
-
* * Followed by zero or more space characters
|
|
1277
|
-
* * Followed by "="
|
|
1278
|
-
* * Followed by zero or more space characters
|
|
1279
|
-
* * Followed by:
|
|
1280
|
-
* * Any character except space, ('), ("), "<", ">", "=", (`), or
|
|
1281
|
-
* * (") then any non-("), or
|
|
1282
|
-
* * (') then any non-(')
|
|
1283
|
-
*/
|
|
1284
|
-
const lastAttributeNameRegex =
|
|
1285
|
-
// eslint-disable-next-line no-control-regex
|
|
1286
|
-
/([ \x09\x0a\x0c\x0d])([^\0-\x1F\x7F-\x9F "'>=/]+)([ \x09\x0a\x0c\x0d]*=[ \x09\x0a\x0c\x0d]*(?:[^ \x09\x0a\x0c\x0d"'`<>=]*|"[^"]*|'[^']*))$/;
|
|
1287
|
-
|
|
1288
|
-
const EMPTY_STRING_BUFFER$1 = Buffer.from('');
|
|
1289
|
-
const RE_QUOTE = /"[^"]*|'[^']*$/;
|
|
1290
|
-
/* eslint no-control-regex: 0 */
|
|
1291
|
-
const RE_TAG_NAME = /[a-zA-Z0-9._-]/;
|
|
1292
|
-
const TAG_OPEN = 1;
|
|
1293
|
-
const TAG_CLOSED = 0;
|
|
1294
|
-
const TAG_NONE = -1;
|
|
1295
|
-
|
|
1296
|
-
/**
|
|
1297
|
-
* A cacheable Template that stores the "strings" and "parts" associated with a
|
|
1298
|
-
* tagged template literal invoked with "html`...`".
|
|
1299
|
-
*/
|
|
1300
|
-
class Template {
|
|
1301
|
-
/**
|
|
1302
|
-
* Create Template instance
|
|
1303
|
-
*
|
|
1304
|
-
* @param { TemplateStringsArray } strings
|
|
1305
|
-
* @param { TemplateProcessor } processor
|
|
1306
|
-
*/
|
|
1307
|
-
constructor(strings, processor) {
|
|
1308
|
-
/** @type { Array<Buffer | null> } */
|
|
1309
|
-
this.strings = [];
|
|
1310
|
-
/** @type { Array<Part | null> } */
|
|
1311
|
-
this.parts = [];
|
|
1312
|
-
|
|
1313
|
-
this._prepare(strings, processor);
|
|
1314
|
-
}
|
|
1315
|
-
|
|
1316
|
-
/**
|
|
1317
|
-
* Prepare the template's static strings,
|
|
1318
|
-
* and create Part instances for the dynamic values,
|
|
1319
|
-
* based on lit-html syntax.
|
|
1320
|
-
*
|
|
1321
|
-
* @param { TemplateStringsArray } strings
|
|
1322
|
-
* @param { TemplateProcessor } processor
|
|
1323
|
-
*/
|
|
1324
|
-
_prepare(strings, processor) {
|
|
1325
|
-
const endIndex = strings.length - 1;
|
|
1326
|
-
let attributeMode = false;
|
|
1327
|
-
let nextString = strings[0];
|
|
1328
|
-
let tagName = '';
|
|
1329
|
-
|
|
1330
|
-
for (let i = 0; i < endIndex; i++) {
|
|
1331
|
-
let string = nextString;
|
|
1332
|
-
nextString = strings[i + 1];
|
|
1333
|
-
const [tagState, tagStateIndex] = getTagState(string);
|
|
1334
|
-
let skip = 0;
|
|
1335
|
-
let part;
|
|
1336
|
-
|
|
1337
|
-
// Open/close tag found at end of string
|
|
1338
|
-
if (tagState !== TAG_NONE) {
|
|
1339
|
-
attributeMode = tagState !== TAG_CLOSED;
|
|
1340
|
-
// Find tag name if open, or if closed and no existing tag name
|
|
1341
|
-
if (tagState === TAG_OPEN || tagName === '') {
|
|
1342
|
-
tagName = getTagName(string, tagState, tagStateIndex);
|
|
1343
|
-
}
|
|
1344
|
-
}
|
|
1345
|
-
|
|
1346
|
-
if (attributeMode) {
|
|
1347
|
-
const matchName = lastAttributeNameRegex.exec(string);
|
|
1348
|
-
|
|
1349
|
-
if (matchName) {
|
|
1350
|
-
let [, prefix, name, suffix] = matchName;
|
|
1351
|
-
|
|
1352
|
-
// Since attributes are conditional, remove "name" and "suffix" from static string
|
|
1353
|
-
string = string.slice(0, matchName.index + prefix.length);
|
|
1354
|
-
|
|
1355
|
-
const matchQuote = RE_QUOTE.exec(suffix);
|
|
1356
|
-
|
|
1357
|
-
// If attribute is quoted, handle potential multiple values
|
|
1358
|
-
if (matchQuote) {
|
|
1359
|
-
const quoteCharacter = matchQuote[0].charAt(0);
|
|
1360
|
-
// Store any text between quote character and value
|
|
1361
|
-
const attributeStrings = [Buffer.from(suffix.slice(matchQuote.index + 1))];
|
|
1362
|
-
let open = true;
|
|
1363
|
-
skip = 0;
|
|
1364
|
-
let attributeString;
|
|
1365
|
-
|
|
1366
|
-
// Scan ahead and gather all strings for this attribute
|
|
1367
|
-
while (open) {
|
|
1368
|
-
attributeString = strings[i + skip + 1];
|
|
1369
|
-
const closingQuoteIndex = attributeString.indexOf(quoteCharacter);
|
|
1370
|
-
|
|
1371
|
-
if (closingQuoteIndex === -1) {
|
|
1372
|
-
attributeStrings.push(Buffer.from(attributeString));
|
|
1373
|
-
skip++;
|
|
1374
|
-
} else {
|
|
1375
|
-
attributeStrings.push(Buffer.from(attributeString.slice(0, closingQuoteIndex)));
|
|
1376
|
-
nextString = attributeString.slice(closingQuoteIndex + 1);
|
|
1377
|
-
i += skip;
|
|
1378
|
-
open = false;
|
|
1379
|
-
}
|
|
1380
|
-
}
|
|
1381
|
-
|
|
1382
|
-
part = processor.handleAttributeExpressions(name, attributeStrings, tagName);
|
|
1383
|
-
} else {
|
|
1384
|
-
part = processor.handleAttributeExpressions(
|
|
1385
|
-
name,
|
|
1386
|
-
[EMPTY_STRING_BUFFER$1, EMPTY_STRING_BUFFER$1],
|
|
1387
|
-
tagName
|
|
1388
|
-
);
|
|
1389
|
-
}
|
|
1390
|
-
}
|
|
1391
|
-
} else {
|
|
1392
|
-
part = processor.handleTextExpression(tagName);
|
|
1393
|
-
}
|
|
1394
|
-
|
|
1395
|
-
this.strings.push(Buffer.from(string));
|
|
1396
|
-
// @ts-ignore: part will never be undefined here
|
|
1397
|
-
this.parts.push(part);
|
|
1398
|
-
// Add placehholders for strings/parts that wil be skipped due to multple values in a single AttributePart
|
|
1399
|
-
if (skip > 0) {
|
|
1400
|
-
this.strings.push(null);
|
|
1401
|
-
this.parts.push(null);
|
|
1402
|
-
skip = 0;
|
|
1403
|
-
}
|
|
1404
|
-
}
|
|
1405
|
-
|
|
1406
|
-
this.strings.push(Buffer.from(nextString));
|
|
1407
|
-
}
|
|
1408
|
-
}
|
|
1409
|
-
|
|
1410
|
-
/**
|
|
1411
|
-
* Determine if 'string' terminates with an opened or closed tag.
|
|
1412
|
-
*
|
|
1413
|
-
* Iterating through all characters has at worst a time complexity of O(n),
|
|
1414
|
-
* and is better than the alternative (using "indexOf/lastIndexOf") which is potentially O(2n).
|
|
1415
|
-
*
|
|
1416
|
-
* @param { string } string
|
|
1417
|
-
* @returns { Array<number> } - returns tuple "[-1, -1]" if no tag, "[0, i]" if closed tag, or "[1, i]" if open tag
|
|
1418
|
-
*/
|
|
1419
|
-
function getTagState(string) {
|
|
1420
|
-
for (let i = string.length - 1; i >= 0; i--) {
|
|
1421
|
-
const char = string[i];
|
|
1422
|
-
|
|
1423
|
-
if (char === '>') {
|
|
1424
|
-
return [TAG_CLOSED, i];
|
|
1425
|
-
} else if (char === '<') {
|
|
1426
|
-
return [TAG_OPEN, i];
|
|
1427
|
-
}
|
|
1428
|
-
}
|
|
1429
|
-
|
|
1430
|
-
return [TAG_NONE, -1];
|
|
1431
|
-
}
|
|
1432
|
-
|
|
1433
|
-
/**
|
|
1434
|
-
* Retrieve tag name from "string" starting at "tagStateIndex" position
|
|
1435
|
-
* Walks forward or backward based on "tagState" open or closed
|
|
1436
|
-
*
|
|
1437
|
-
* @param { string } string
|
|
1438
|
-
* @param { number } tagState
|
|
1439
|
-
* @param { number } tagStateIndex
|
|
1440
|
-
* @returns { string }
|
|
1441
|
-
*/
|
|
1442
|
-
function getTagName(string, tagState, tagStateIndex) {
|
|
1443
|
-
let tagName = '';
|
|
1444
|
-
|
|
1445
|
-
if (tagState === TAG_CLOSED) {
|
|
1446
|
-
// Walk backwards until open tag
|
|
1447
|
-
for (let i = tagStateIndex - 1; i >= 0; i--) {
|
|
1448
|
-
const char = string[i];
|
|
1449
|
-
|
|
1450
|
-
if (char === '<') {
|
|
1451
|
-
return getTagName(string, TAG_OPEN, i);
|
|
1452
|
-
}
|
|
1453
|
-
}
|
|
1454
|
-
} else {
|
|
1455
|
-
for (let i = tagStateIndex + 1; i < string.length; i++) {
|
|
1456
|
-
const char = string[i];
|
|
1457
|
-
|
|
1458
|
-
if (!RE_TAG_NAME.test(char)) {
|
|
1459
|
-
break;
|
|
1460
|
-
}
|
|
1461
|
-
|
|
1462
|
-
tagName += char;
|
|
1463
|
-
}
|
|
1464
|
-
}
|
|
1465
|
-
|
|
1466
|
-
return tagName;
|
|
1467
|
-
}
|
|
1468
|
-
|
|
1469
|
-
const EMPTY_STRING_BUFFER$2 = Buffer.from('');
|
|
1470
|
-
|
|
1471
|
-
let id = 0;
|
|
1472
|
-
|
|
1473
|
-
/**
|
|
1474
|
-
* A class for consuming the combined static and dynamic parts of a lit-html Template.
|
|
1475
|
-
* TemplateResults
|
|
1476
|
-
*/
|
|
1477
|
-
class TemplateResult {
|
|
1478
|
-
/**
|
|
1479
|
-
* Constructor
|
|
1480
|
-
*
|
|
1481
|
-
* @param { Template } template
|
|
1482
|
-
* @param { Array<unknown> } values
|
|
1483
|
-
*/
|
|
1484
|
-
constructor(template, values) {
|
|
1485
|
-
this.template = template;
|
|
1486
|
-
this.values = values;
|
|
1487
|
-
this.id = id++;
|
|
1488
|
-
this.index = 0;
|
|
1489
|
-
}
|
|
1490
|
-
|
|
1491
|
-
/**
|
|
1492
|
-
* Consume template result content.
|
|
1493
|
-
*
|
|
1494
|
-
* @param { RenderOptions } [options]
|
|
1495
|
-
* @returns { unknown }
|
|
1496
|
-
*/
|
|
1497
|
-
read(options) {
|
|
1498
|
-
let buffer = EMPTY_STRING_BUFFER$2;
|
|
1499
|
-
let chunk;
|
|
1500
|
-
/** @type { Array<Buffer> | undefined } */
|
|
1501
|
-
let chunks;
|
|
1502
|
-
|
|
1503
|
-
while ((chunk = this.readChunk(options)) !== null) {
|
|
1504
|
-
if (isBuffer(chunk)) {
|
|
1505
|
-
buffer = Buffer.concat([buffer, chunk], buffer.length + chunk.length);
|
|
1506
|
-
} else {
|
|
1507
|
-
if (chunks === undefined) {
|
|
1508
|
-
chunks = [];
|
|
1509
|
-
}
|
|
1510
|
-
buffer = reduce(buffer, chunks, chunk) || EMPTY_STRING_BUFFER$2;
|
|
1511
|
-
}
|
|
1512
|
-
}
|
|
1513
|
-
|
|
1514
|
-
if (chunks !== undefined) {
|
|
1515
|
-
chunks.push(buffer);
|
|
1516
|
-
return chunks.length > 1 ? chunks : chunks[0];
|
|
1517
|
-
}
|
|
1518
|
-
|
|
1519
|
-
return buffer;
|
|
1520
|
-
}
|
|
1521
|
-
|
|
1522
|
-
/**
|
|
1523
|
-
* Consume template result content one chunk at a time.
|
|
1524
|
-
* @param { RenderOptions } [options]
|
|
1525
|
-
* @returns { unknown }
|
|
1526
|
-
*/
|
|
1527
|
-
readChunk(options) {
|
|
1528
|
-
const isString = this.index % 2 === 0;
|
|
1529
|
-
const index = (this.index / 2) | 0;
|
|
1530
|
-
|
|
1531
|
-
// Finished
|
|
1532
|
-
if (!isString && index >= this.template.strings.length - 1) {
|
|
1533
|
-
// Reset
|
|
1534
|
-
this.index = 0;
|
|
1535
|
-
return null;
|
|
1536
|
-
}
|
|
1537
|
-
|
|
1538
|
-
this.index++;
|
|
1539
|
-
|
|
1540
|
-
if (isString) {
|
|
1541
|
-
return this.template.strings[index];
|
|
1542
|
-
}
|
|
1543
|
-
|
|
1544
|
-
const part = this.template.parts[index];
|
|
1545
|
-
let value;
|
|
1546
|
-
|
|
1547
|
-
if (isAttributePart(part)) {
|
|
1548
|
-
// AttributeParts can have multiple values, so slice based on length
|
|
1549
|
-
// (strings in-between values are already handled the instance)
|
|
1550
|
-
if (part.length > 1) {
|
|
1551
|
-
value = part.getValue(this.values.slice(index, index + part.length), options);
|
|
1552
|
-
this.index += part.length;
|
|
1553
|
-
} else {
|
|
1554
|
-
value = part.getValue([this.values[index]], options);
|
|
1555
|
-
}
|
|
1556
|
-
} else {
|
|
1557
|
-
value = part && part.getValue(this.values[index], options);
|
|
1558
|
-
}
|
|
1559
|
-
|
|
1560
|
-
return value;
|
|
1561
|
-
}
|
|
1562
|
-
}
|
|
1563
|
-
|
|
1564
|
-
/**
|
|
1565
|
-
* Commit "chunk" to string "buffer".
|
|
1566
|
-
* Returns new "buffer" value.
|
|
1567
|
-
*
|
|
1568
|
-
* @param { Buffer } buffer
|
|
1569
|
-
* @param { Array<unknown> } chunks
|
|
1570
|
-
* @param { unknown } chunk
|
|
1571
|
-
* @returns { Buffer | undefined }
|
|
1572
|
-
*/
|
|
1573
|
-
function reduce(buffer, chunks, chunk) {
|
|
1574
|
-
if (isBuffer(chunk)) {
|
|
1575
|
-
return Buffer.concat([buffer, chunk], buffer.length + chunk.length);
|
|
1576
|
-
} else if (isTemplateResult(chunk)) {
|
|
1577
|
-
chunks.push(buffer, chunk);
|
|
1578
|
-
return EMPTY_STRING_BUFFER$2;
|
|
1579
|
-
} else if (isArray(chunk)) {
|
|
1580
|
-
return chunk.reduce((buffer, chunk) => reduce(buffer, chunks, chunk), buffer);
|
|
1581
|
-
} else if (isPromise(chunk) || isAsyncIterator(chunk)) {
|
|
1582
|
-
chunks.push(buffer, chunk);
|
|
1583
|
-
return EMPTY_STRING_BUFFER$2;
|
|
1584
|
-
}
|
|
1585
|
-
}
|
|
1586
|
-
|
|
1587
|
-
/**
|
|
1588
|
-
* Default templateResult factory
|
|
1589
|
-
*
|
|
1590
|
-
* @param { unknown } value
|
|
1591
|
-
* @returns { TemplateResult }
|
|
1592
|
-
*/
|
|
1593
|
-
// prettier-ignore
|
|
1594
|
-
const DEFAULT_TEMPLATE_FN = (value) => html`${value}`;
|
|
1595
|
-
|
|
1596
|
-
const defaultTemplateProcessor = new DefaultTemplateProcessor();
|
|
1597
|
-
const defaultTemplateResultProcessor = new DefaultTemplateResultProcessor();
|
|
1598
|
-
const templateCache = new Map();
|
|
1599
|
-
const streamRenderer =
|
|
1600
|
-
typeof process !== 'undefined' &&
|
|
1601
|
-
typeof process.versions !== 'undefined' &&
|
|
1602
|
-
typeof process.versions.node !== 'undefined'
|
|
1603
|
-
? streamTemplateRenderer
|
|
1604
|
-
: browserStreamTemplateRenderer;
|
|
1605
|
-
|
|
1606
|
-
/**
|
|
1607
|
-
* Interprets a template literal as an HTML template that can be
|
|
1608
|
-
* rendered as a Readable stream or String
|
|
1609
|
-
*
|
|
1610
|
-
* @param { TemplateStringsArray } strings
|
|
1611
|
-
* @param { ...unknown } values
|
|
1612
|
-
* @returns { TemplateResult }
|
|
1613
|
-
*/
|
|
1614
|
-
function html(strings, ...values) {
|
|
1615
|
-
let template = templateCache.get(strings);
|
|
1616
|
-
|
|
1617
|
-
if (template === undefined) {
|
|
1618
|
-
template = new Template(strings, defaultTemplateProcessor);
|
|
1619
|
-
templateCache.set(strings, template);
|
|
1620
|
-
}
|
|
1621
|
-
|
|
1622
|
-
return new TemplateResult(template, values);
|
|
1623
|
-
}
|
|
1624
|
-
|
|
1625
|
-
/**
|
|
1626
|
-
* Render a template result to a Readable stream
|
|
1627
|
-
*
|
|
1628
|
-
* @param { unknown } result - a template result returned from call to "html`...`"
|
|
1629
|
-
* @param { RenderOptions } [options]
|
|
1630
|
-
* @returns { import('stream').Readable | ReadableStream }
|
|
1631
|
-
*/
|
|
1632
|
-
function renderToStream(result, options) {
|
|
1633
|
-
return streamRenderer(getRenderResult(result), defaultTemplateResultProcessor, options);
|
|
1634
|
-
}
|
|
1635
|
-
|
|
1636
|
-
/**
|
|
1637
|
-
* Render a template result to a string resolving Promise.
|
|
1638
|
-
*
|
|
1639
|
-
* @param { unknown } result - a template result returned from call to "html`...`"
|
|
1640
|
-
* @param { RenderOptions } [options]
|
|
1641
|
-
* @returns { Promise<string> }
|
|
1642
|
-
*/
|
|
1643
|
-
function renderToString(result, options) {
|
|
1644
|
-
return promiseTemplateRenderer(
|
|
1645
|
-
getRenderResult(result),
|
|
1646
|
-
defaultTemplateResultProcessor,
|
|
1647
|
-
false,
|
|
1648
|
-
options
|
|
1649
|
-
);
|
|
1650
|
-
}
|
|
1651
|
-
|
|
1652
|
-
/**
|
|
1653
|
-
* Render a template result to a Buffer resolving Promise.
|
|
1654
|
-
*
|
|
1655
|
-
* @param { unknown } result - a template result returned from call to "html`...`"
|
|
1656
|
-
* @param { RenderOptions } [options]
|
|
1657
|
-
* @returns { Promise<Buffer> }
|
|
1658
|
-
*/
|
|
1659
|
-
function renderToBuffer(result, options) {
|
|
1660
|
-
return promiseTemplateRenderer(
|
|
1661
|
-
getRenderResult(result),
|
|
1662
|
-
defaultTemplateResultProcessor,
|
|
1663
|
-
true,
|
|
1664
|
-
options
|
|
1665
|
-
);
|
|
1666
|
-
}
|
|
1667
|
-
|
|
1668
|
-
/**
|
|
1669
|
-
* Retrieve TemplateResult for render
|
|
1670
|
-
*
|
|
1671
|
-
* @param { unknown} result
|
|
1672
|
-
* @returns { TemplateResult }
|
|
1673
|
-
*/
|
|
1674
|
-
function getRenderResult(result) {
|
|
1675
|
-
// @ts-ignore
|
|
1676
|
-
return !isTemplateResult(result) ? DEFAULT_TEMPLATE_FN(result) : result;
|
|
1677
|
-
}
|
|
1678
|
-
|
|
1679
|
-
export { AttributePart, BooleanAttributePart, DefaultTemplateProcessor, DefaultTemplateResultProcessor, EventAttributePart, NodePart, Part, PropertyAttributePart, Template, TemplateResult, defaultTemplateProcessor, defaultTemplateResultProcessor, directive, html, isAttributePart, isDirective, isNodePart, isTemplateResult, nothing, renderToBuffer, renderToStream, renderToString, html as svg, templateCache, unsafePrefixString };
|
test/index.test.js
DELETED
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import { expect, test, jest } from '@jest/globals'
|
|
2
|
-
import { number, boolean, string, array, object } from '../src/index.js';
|
|
3
|
-
|
|
4
|
-
const logMock = jest.fn();
|
|
5
|
-
window.logError = logMock;
|
|
6
|
-
|
|
7
|
-
const expectError = (msg) => expect(logMock).toHaveBeenCalledWith(msg);
|
|
8
|
-
|
|
9
|
-
const primitives = [
|
|
10
|
-
{
|
|
11
|
-
type: 'number',
|
|
12
|
-
validator: number,
|
|
13
|
-
valid: [123, 40.5, 6410],
|
|
14
|
-
invalid: ['123', false, {}, [], new Date()],
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
type: 'boolean',
|
|
18
|
-
validator: boolean,
|
|
19
|
-
valid: [false, true],
|
|
20
|
-
invalid: ['123', 123, {}, [], new Date()],
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
type: 'string',
|
|
24
|
-
validator: string,
|
|
25
|
-
valid: ['', '123'],
|
|
26
|
-
invalid: [123, false, {}, [], new Date()],
|
|
27
|
-
}
|
|
28
|
-
]
|
|
29
|
-
|
|
30
|
-
primitives.forEach(value =>
|
|
31
|
-
it(`${value.type}`, () => {
|
|
32
|
-
const context = 'key'
|
|
33
|
-
expect(value.validator.type).toEqual(value.type);
|
|
34
|
-
expect(value.validator.isRequired.type).toEqual(value.type);
|
|
35
|
-
value.validator.validate(context);
|
|
36
|
-
for (const v of value.valid) {
|
|
37
|
-
value.validator.validate(context, v);
|
|
38
|
-
value.validator.isRequired.validate(context, v);
|
|
39
|
-
}
|
|
40
|
-
value.validator.isRequired.validate(context);
|
|
41
|
-
expectError(`'key' Field is required`);
|
|
42
|
-
for (const v of value.invalid) {
|
|
43
|
-
value.validator.validate(context, v);
|
|
44
|
-
expectError(`'key' Expected type '${value.type}' got type '${typeof v}'`)
|
|
45
|
-
}
|
|
46
|
-
})
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
test('object', () => {
|
|
50
|
-
const context = 'data';
|
|
51
|
-
object({}).validate(context, { name: '123' });
|
|
52
|
-
object({ name: string }).validate(context, { name: '123' });
|
|
53
|
-
object({ name: string.isRequired }).validate(context, { name: '' });
|
|
54
|
-
object({ name: string.isRequired }).validate(context, {});
|
|
55
|
-
expectError(`'data.name' Field is required`);
|
|
56
|
-
|
|
57
|
-
const schema = object({
|
|
58
|
-
address: object({
|
|
59
|
-
street: string,
|
|
60
|
-
})
|
|
61
|
-
})
|
|
62
|
-
schema.validate(context, {});
|
|
63
|
-
schema.validate(context, '123');
|
|
64
|
-
expectError(`'data' Expected object literal '{}' got 'string'`);
|
|
65
|
-
schema.validate(context, {
|
|
66
|
-
address: {},
|
|
67
|
-
});
|
|
68
|
-
schema.validate(context, {
|
|
69
|
-
address: '123',
|
|
70
|
-
});
|
|
71
|
-
expectError(`'data.address' Expected object literal '{}' got 'string'`);
|
|
72
|
-
schema.validate(context, {
|
|
73
|
-
address: {
|
|
74
|
-
street: 'avenue 1',
|
|
75
|
-
},
|
|
76
|
-
});
|
|
77
|
-
schema.validate(context, {
|
|
78
|
-
address: {
|
|
79
|
-
street: false,
|
|
80
|
-
},
|
|
81
|
-
});
|
|
82
|
-
expectError(`'data.address.street' Expected type 'string' got type 'boolean'`);
|
|
83
|
-
|
|
84
|
-
const schema2 = object({
|
|
85
|
-
address: object({
|
|
86
|
-
street: string.isRequired,
|
|
87
|
-
})
|
|
88
|
-
})
|
|
89
|
-
schema2.validate(context, {});
|
|
90
|
-
schema2.validate(context, {
|
|
91
|
-
address: {
|
|
92
|
-
street: '11th avenue',
|
|
93
|
-
},
|
|
94
|
-
});
|
|
95
|
-
schema2.validate(context, {
|
|
96
|
-
address: {},
|
|
97
|
-
});
|
|
98
|
-
expectError(`'data.address.street' Field is required`);
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
test('array', () => {
|
|
102
|
-
const context = 'items';
|
|
103
|
-
array(string).validate(context, ['123']);
|
|
104
|
-
array(string).validate(context, [123]);
|
|
105
|
-
expectError(`'items[0]' Expected type 'string' got type 'number'`);
|
|
106
|
-
array(array(string)).validate(context, [['123']]);
|
|
107
|
-
array(array(string)).validate(context, [[123]]);
|
|
108
|
-
expectError(`'items[0][0]' Expected type 'string' got type 'number'`);
|
|
109
|
-
|
|
110
|
-
const schema = object({
|
|
111
|
-
street: string.isRequired,
|
|
112
|
-
})
|
|
113
|
-
array(schema).validate(context, []);
|
|
114
|
-
array(schema).validate(context, [{ street: '123' }, { street: '456' }, { street: '789' }]);
|
|
115
|
-
array(schema).validate(context, [{}]);
|
|
116
|
-
expectError(`'items[0].street' Field is required`);
|
|
117
|
-
array(schema).validate(context, [{ street: false }]);
|
|
118
|
-
expectError(`'items[0].street' Expected type 'string' got type 'boolean'`);
|
|
119
|
-
array(schema).validate(context, [{ street: '123' }, {}]);
|
|
120
|
-
expectError(`'items[1].street' Field is required`);
|
|
121
|
-
array(schema).validate(context, [{ street: '123' }, { street: false }]);
|
|
122
|
-
expectError(`'items[1].street' Expected type 'string' got type 'boolean'`);
|
|
123
|
-
});
|
test/setup.js
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
global.window = {}
|
|
2
|
-
global.Document = class { };
|