~repos /atoms-element

#js

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 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 = (f) => ((...args) => {
55
+ const directive =
56
+ (f) =>
57
+ (...args) => {
56
- const d = f(...args);
58
+ const d = f(...args);
57
- directives.set(d, true);
59
+ directives.set(d, true);
58
- return d;
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
- const { strings, values: { length } } = result;
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
- match[2].slice(0, -boundAttributeSuffix.length) + match[3];
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
- document.importNode(this.template.element.content, true);
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
- trustedTypes.createPolicy('lit-html', { createHTML: (s) => s });
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
- !(typeof value === 'object' || typeof value === 'function'));
605
+ return value === null || !(typeof value === 'object' || typeof value === 'function');
620
606
  };
621
607
  const isIterable = (value) => {
608
+ return (
622
- return Array.isArray(value) ||
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
- node.nodeType === 3 /* Node.TEXT_NODE */) {
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
- (strings.length === 2 && strings[0] === '' && strings[1] === '');
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 = newListener == null ||
1018
+ const shouldRemoveListener =
1019
+ newListener == null ||
1046
- oldListener != null &&
1020
+ (oldListener != null &&
1047
- (newListener.capture !== oldListener.capture ||
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
- { capture: o.capture, passive: o.passive, once: o.once } :
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 { AttributeCommitter, AttributePart, BooleanAttributePart, DefaultTemplateProcessor, EventPart, NodePart, PropertyCommitter, PropertyPart, SVGTemplateResult, Template, TemplateInstance, TemplateResult, createMarker, defaultTemplateProcessor, directive, html, isDirective, isIterable, isPrimitive, isTemplatePartActive, noChange, nothing, parts, removeNodes, render, reparentNodes, svg, templateCaches, templateFactory };
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": "1.0.0",
3
+ "version": "3.0.1",
4
4
  "lockfileVersion": 2,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
+ "name": "atoms-element",
8
- "version": "1.0.0",
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": "1.0.0",
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
- "pyros.sh",
8
+ "web",
8
- "web component",
9
+ "component",
9
- "custom element",
10
- "hooks",
10
+ "ssr",
11
+ "client",
12
+ "server",
11
13
  "react",
12
- "lit-html",
14
+ "lit-html"
15
+ ],
16
+ "files": [
13
- "lit-html-server",
17
+ "lit-html.js",
14
- "fuco"
18
+ "index.js",
19
+ "index.d.ts"
15
20
  ],
16
21
  "license": "MIT",
17
- "main": "src/index.js",
18
- "author": "pyros.sh",
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
- "setupFilesAfterEnv": [
37
+ "prettier": {
38
+ "printWidth": 160,
39
+ "singleQuote": true,
31
- "./test/setup.js"
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
- [![Version](https://img.shields.io/npm/v/atoms-element?style=flat-square&color=blue)](https://www.npmjs.com/package/atoms-element)
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
- A simple web component library for defining your custom elements. It works on both client and server. It supports hooks and follows the same
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
- principles of react. Props are attributes on the custom element by default so its easier to debug.
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
- It uses slightly modified versions of these libraries,
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
- 3. [fuco](https://github.com/wtnbass/fuco)
13
+ After going through all these libraries,
13
14
 
14
- ## Installation
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
- ```sh
17
- npm i atoms-element
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
- ## Usage
25
+ ## Example
21
26
 
22
27
  ```js
23
- import { defineElement, html, render, number } from 'atoms-element.js';
28
+ import { createElement, useReducer, html, renderHtml } from 'atoms-element/index.js';
24
-
25
- const propTypes = {
29
+
26
- name: string.isRequired,
27
- };
28
-
29
- const Counter = ({ name }) => {
30
+ const Counter = ({ name, meta }) => {
30
- const [count, setCount] = useState(0);
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
- <div>
46
+ </div>
34
- <div class="font-bold mb-2">Counter: ${name}</div>
35
- <div class="flex flex-1 flex-row text-3xl text-gray-700">
47
+ <div class="flex flex-1 flex-row items-center text-gray-700">
36
- <button @click=${() => setCount((v) => v - 1)}>-</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-1xl">${count}</h1>
50
+ <h1 class="text-3xl font-mono ${warningClass}">${count}</h1>
39
51
  </div>
40
- <button @click=${() => setCount((v) => v + 1)}>+</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
- defineElement('app-counter', Counter, propTypes);
58
+ createElement({ url: 'app-counter.js' }, Counter);
47
59
 
48
- render(html`<app-counter name="1"></app-counter>`, document.body);
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
- '"': '&quot;',
531
- "'": '&#x27;',
532
- '&': '&amp;',
533
- '<': '&lt;',
534
- '>': '&gt;',
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 { };