atoms-element v5.0.0

#js

git clone https://git.pyrossh.dev/atoms-element

A simple web component library for defining your custom elements. It works on both client and server.


b8566f5 — Peter John 2021-06-26T14:05:19+05:30
initial commit

Generated files not shown: package-lock.json

Files changed (8) hide show
  1. .gitignore +1 -0
  2. package.json +34 -0
  3. readme.md +49 -0
  4. src/index.js +401 -0
  5. src/lit-html-server.js +1679 -0
  6. src/lit-html.js +1246 -0
  7. test/index.test.js +123 -0
  8. test/setup.js +2 -0
.gitignore ADDED
@@ -0,0 +1 @@
1
+ node_modules
package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "atoms-element",
3
+ "version": "1.0.0",
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
+ "keywords": [
6
+ "atoms-element",
7
+ "pyros.sh",
8
+ "web component",
9
+ "custom element",
10
+ "hooks",
11
+ "react",
12
+ "lit-html",
13
+ "lit-html-server",
14
+ "fuco"
15
+ ],
16
+ "license": "MIT",
17
+ "main": "src/index.js",
18
+ "author": "pyros.sh",
19
+ "type": "module",
20
+ "engines": {
21
+ "node": ">=14.0.0"
22
+ },
23
+ "scripts": {
24
+ "test": "NODE_OPTIONS=--experimental-vm-modules jest"
25
+ },
26
+ "devDependencies": {
27
+ "jest": "^27.0.5"
28
+ },
29
+ "jest": {
30
+ "setupFilesAfterEnv": [
31
+ "./test/setup.js"
32
+ ]
33
+ }
34
+ }
readme.md ADDED
@@ -0,0 +1,49 @@
1
+ # atoms-element
2
+
3
+ [![Version](https://img.shields.io/npm/v/atoms-element?style=flat-square&color=blue)](https://www.npmjs.com/package/atoms-element)
4
+
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
+ principles of react. Props are attributes on the custom element by default so its easier to debug.
7
+
8
+ It uses slightly modified versions of these libraries,
9
+
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
+
14
+ ## Installation
15
+
16
+ ```sh
17
+ npm i atoms-element
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ ```js
23
+ import { defineElement, html, render, number } from 'atoms-element.js';
24
+
25
+ const propTypes = {
26
+ name: string.isRequired,
27
+ };
28
+
29
+ const Counter = ({ name }) => {
30
+ const [count, setCount] = useState(0);
31
+
32
+ return html`
33
+ <div>
34
+ <div class="font-bold mb-2">Counter: ${name}</div>
35
+ <div class="flex flex-1 flex-row text-3xl text-gray-700">
36
+ <button @click=${() => setCount((v) => v - 1)}>-</button>
37
+ <div class="mx-20">
38
+ <h1 class="text-1xl">${count}</h1>
39
+ </div>
40
+ <button @click=${() => setCount((v) => v + 1)}>+</button>
41
+ </div>
42
+ </div>
43
+ `;
44
+ };
45
+
46
+ defineElement('app-counter', Counter, propTypes);
47
+
48
+ render(html`<app-counter name="1"></app-counter>`, document.body);
49
+ ```
src/index.js ADDED
@@ -0,0 +1,401 @@
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 ADDED
@@ -0,0 +1,1679 @@
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 };
src/lit-html.js ADDED
@@ -0,0 +1,1246 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
4
+ * This code may only be used under the BSD style license found at
5
+ * http://polymer.github.io/LICENSE.txt
6
+ * The complete set of authors may be found at
7
+ * http://polymer.github.io/AUTHORS.txt
8
+ * The complete set of contributors may be found at
9
+ * http://polymer.github.io/CONTRIBUTORS.txt
10
+ * Code distributed by Google as part of the polymer project is also
11
+ * subject to an additional IP rights grant found at
12
+ * http://polymer.github.io/PATENTS.txt
13
+ */
14
+ const directives = new WeakMap();
15
+ /**
16
+ * Brands a function as a directive factory function so that lit-html will call
17
+ * the function during template rendering, rather than passing as a value.
18
+ *
19
+ * A _directive_ is a function that takes a Part as an argument. It has the
20
+ * signature: `(part: Part) => void`.
21
+ *
22
+ * A directive _factory_ is a function that takes arguments for data and
23
+ * configuration and returns a directive. Users of directive usually refer to
24
+ * the directive factory as the directive. For example, "The repeat directive".
25
+ *
26
+ * Usually a template author will invoke a directive factory in their template
27
+ * with relevant arguments, which will then return a directive function.
28
+ *
29
+ * Here's an example of using the `repeat()` directive factory that takes an
30
+ * array and a function to render an item:
31
+ *
32
+ * ```js
33
+ * html`<ul><${repeat(items, (item) => html`<li>${item}</li>`)}</ul>`
34
+ * ```
35
+ *
36
+ * When `repeat` is invoked, it returns a directive function that closes over
37
+ * `items` and the template function. When the outer template is rendered, the
38
+ * return directive function is called with the Part for the expression.
39
+ * `repeat` then performs it's custom logic to render multiple items.
40
+ *
41
+ * @param f The directive factory function. Must be a function that returns a
42
+ * function of the signature `(part: Part) => void`. The returned function will
43
+ * be called with the part object.
44
+ *
45
+ * @example
46
+ *
47
+ * import {directive, html} from 'lit-html';
48
+ *
49
+ * const immutable = directive((v) => (part) => {
50
+ * if (part.value !== v) {
51
+ * part.setValue(v)
52
+ * }
53
+ * });
54
+ */
55
+ const directive = (f) => ((...args) => {
56
+ const d = f(...args);
57
+ directives.set(d, true);
58
+ return d;
59
+ });
60
+ const isDirective = (o) => {
61
+ return typeof o === 'function' && directives.has(o);
62
+ };
63
+
64
+ /**
65
+ * @license
66
+ * Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
67
+ * This code may only be used under the BSD style license found at
68
+ * http://polymer.github.io/LICENSE.txt
69
+ * The complete set of authors may be found at
70
+ * http://polymer.github.io/AUTHORS.txt
71
+ * The complete set of contributors may be found at
72
+ * http://polymer.github.io/CONTRIBUTORS.txt
73
+ * Code distributed by Google as part of the polymer project is also
74
+ * subject to an additional IP rights grant found at
75
+ * http://polymer.github.io/PATENTS.txt
76
+ */
77
+ /**
78
+ * True if the custom elements polyfill is in use.
79
+ */
80
+ const isCEPolyfill = typeof window !== 'undefined' &&
81
+ window.customElements != null &&
82
+ window.customElements.polyfillWrapFlushCallback !==
83
+ undefined;
84
+ /**
85
+ * Reparents nodes, starting from `start` (inclusive) to `end` (exclusive),
86
+ * into another container (could be the same container), before `before`. If
87
+ * `before` is null, it appends the nodes to the container.
88
+ */
89
+ const reparentNodes = (container, start, end = null, before = null) => {
90
+ while (start !== end) {
91
+ const n = start.nextSibling;
92
+ container.insertBefore(start, before);
93
+ start = n;
94
+ }
95
+ };
96
+ /**
97
+ * Removes nodes, starting from `start` (inclusive) to `end` (exclusive), from
98
+ * `container`.
99
+ */
100
+ const removeNodes = (container, start, end = null) => {
101
+ while (start !== end) {
102
+ const n = start.nextSibling;
103
+ container.removeChild(start);
104
+ start = n;
105
+ }
106
+ };
107
+
108
+ /**
109
+ * @license
110
+ * Copyright (c) 2018 The Polymer Project Authors. All rights reserved.
111
+ * This code may only be used under the BSD style license found at
112
+ * http://polymer.github.io/LICENSE.txt
113
+ * The complete set of authors may be found at
114
+ * http://polymer.github.io/AUTHORS.txt
115
+ * The complete set of contributors may be found at
116
+ * http://polymer.github.io/CONTRIBUTORS.txt
117
+ * Code distributed by Google as part of the polymer project is also
118
+ * subject to an additional IP rights grant found at
119
+ * http://polymer.github.io/PATENTS.txt
120
+ */
121
+ /**
122
+ * A sentinel value that signals that a value was handled by a directive and
123
+ * should not be written to the DOM.
124
+ */
125
+ const noChange = {};
126
+ /**
127
+ * A sentinel value that signals a NodePart to fully clear its content.
128
+ */
129
+ const nothing = {};
130
+
131
+ /**
132
+ * @license
133
+ * Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
134
+ * This code may only be used under the BSD style license found at
135
+ * http://polymer.github.io/LICENSE.txt
136
+ * The complete set of authors may be found at
137
+ * http://polymer.github.io/AUTHORS.txt
138
+ * The complete set of contributors may be found at
139
+ * http://polymer.github.io/CONTRIBUTORS.txt
140
+ * Code distributed by Google as part of the polymer project is also
141
+ * subject to an additional IP rights grant found at
142
+ * http://polymer.github.io/PATENTS.txt
143
+ */
144
+ /**
145
+ * An expression marker with embedded unique key to avoid collision with
146
+ * possible text in templates.
147
+ */
148
+ const marker = `{{lit-${String(Math.random()).slice(2)}}}`;
149
+ /**
150
+ * An expression marker used text-positions, multi-binding attributes, and
151
+ * attributes with markup-like text values.
152
+ */
153
+ const nodeMarker = `<!--${marker}-->`;
154
+ const markerRegex = new RegExp(`${marker}|${nodeMarker}`);
155
+ /**
156
+ * Suffix appended to all bound attribute names.
157
+ */
158
+ const boundAttributeSuffix = '$lit$';
159
+ /**
160
+ * An updatable Template that tracks the location of dynamic parts.
161
+ */
162
+ class Template {
163
+ constructor(result, element) {
164
+ this.parts = [];
165
+ this.element = element;
166
+ const nodesToRemove = [];
167
+ const stack = [];
168
+ // Edge needs all 4 parameters present; IE11 needs 3rd parameter to be null
169
+ const walker = document.createTreeWalker(element.content, 133 /* NodeFilter.SHOW_{ELEMENT|COMMENT|TEXT} */, null, false);
170
+ // Keeps track of the last index associated with a part. We try to delete
171
+ // unnecessary nodes, but we never want to associate two different parts
172
+ // to the same index. They must have a constant node between.
173
+ let lastPartIndex = 0;
174
+ let index = -1;
175
+ let partIndex = 0;
176
+ const { strings, values: { length } } = result;
177
+ while (partIndex < length) {
178
+ const node = walker.nextNode();
179
+ if (node === null) {
180
+ // We've exhausted the content inside a nested template element.
181
+ // Because we still have parts (the outer for-loop), we know:
182
+ // - There is a template in the stack
183
+ // - The walker will find a nextNode outside the template
184
+ walker.currentNode = stack.pop();
185
+ continue;
186
+ }
187
+ index++;
188
+ if (node.nodeType === 1 /* Node.ELEMENT_NODE */) {
189
+ if (node.hasAttributes()) {
190
+ const attributes = node.attributes;
191
+ const { length } = attributes;
192
+ // Per
193
+ // https://developer.mozilla.org/en-US/docs/Web/API/NamedNodeMap,
194
+ // attributes are not guaranteed to be returned in document order.
195
+ // In particular, Edge/IE can return them out of order, so we cannot
196
+ // assume a correspondence between part index and attribute index.
197
+ let count = 0;
198
+ for (let i = 0; i < length; i++) {
199
+ if (endsWith(attributes[i].name, boundAttributeSuffix)) {
200
+ count++;
201
+ }
202
+ }
203
+ while (count-- > 0) {
204
+ // Get the template literal section leading up to the first
205
+ // expression in this attribute
206
+ const stringForPart = strings[partIndex];
207
+ // Find the attribute name
208
+ const name = lastAttributeNameRegex.exec(stringForPart)[2];
209
+ // Find the corresponding attribute
210
+ // All bound attributes have had a suffix added in
211
+ // TemplateResult#getHTML to opt out of special attribute
212
+ // handling. To look up the attribute value we also need to add
213
+ // the suffix.
214
+ const attributeLookupName = name.toLowerCase() + boundAttributeSuffix;
215
+ const attributeValue = node.getAttribute(attributeLookupName);
216
+ node.removeAttribute(attributeLookupName);
217
+ const statics = attributeValue.split(markerRegex);
218
+ this.parts.push({ type: 'attribute', index, name, strings: statics });
219
+ partIndex += statics.length - 1;
220
+ }
221
+ }
222
+ if (node.tagName === 'TEMPLATE') {
223
+ stack.push(node);
224
+ walker.currentNode = node.content;
225
+ }
226
+ }
227
+ else if (node.nodeType === 3 /* Node.TEXT_NODE */) {
228
+ const data = node.data;
229
+ if (data.indexOf(marker) >= 0) {
230
+ const parent = node.parentNode;
231
+ const strings = data.split(markerRegex);
232
+ const lastIndex = strings.length - 1;
233
+ // Generate a new text node for each literal section
234
+ // These nodes are also used as the markers for node parts
235
+ for (let i = 0; i < lastIndex; i++) {
236
+ let insert;
237
+ let s = strings[i];
238
+ if (s === '') {
239
+ insert = createMarker();
240
+ }
241
+ else {
242
+ const match = lastAttributeNameRegex.exec(s);
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];
246
+ }
247
+ insert = document.createTextNode(s);
248
+ }
249
+ parent.insertBefore(insert, node);
250
+ this.parts.push({ type: 'node', index: ++index });
251
+ }
252
+ // If there's no text, we must insert a comment to mark our place.
253
+ // Else, we can trust it will stick around after cloning.
254
+ if (strings[lastIndex] === '') {
255
+ parent.insertBefore(createMarker(), node);
256
+ nodesToRemove.push(node);
257
+ }
258
+ else {
259
+ node.data = strings[lastIndex];
260
+ }
261
+ // We have a part for each match found
262
+ partIndex += lastIndex;
263
+ }
264
+ }
265
+ else if (node.nodeType === 8 /* Node.COMMENT_NODE */) {
266
+ if (node.data === marker) {
267
+ const parent = node.parentNode;
268
+ // Add a new marker node to be the startNode of the Part if any of
269
+ // the following are true:
270
+ // * We don't have a previousSibling
271
+ // * The previousSibling is already the start of a previous part
272
+ if (node.previousSibling === null || index === lastPartIndex) {
273
+ index++;
274
+ parent.insertBefore(createMarker(), node);
275
+ }
276
+ lastPartIndex = index;
277
+ this.parts.push({ type: 'node', index });
278
+ // If we don't have a nextSibling, keep this node so we have an end.
279
+ // Else, we can remove it to save future costs.
280
+ if (node.nextSibling === null) {
281
+ node.data = '';
282
+ }
283
+ else {
284
+ nodesToRemove.push(node);
285
+ index--;
286
+ }
287
+ partIndex++;
288
+ }
289
+ else {
290
+ let i = -1;
291
+ while ((i = node.data.indexOf(marker, i + 1)) !== -1) {
292
+ // Comment node has a binding marker inside, make an inactive part
293
+ // The binding won't work, but subsequent bindings will
294
+ // TODO (justinfagnani): consider whether it's even worth it to
295
+ // make bindings in comments work
296
+ this.parts.push({ type: 'node', index: -1 });
297
+ partIndex++;
298
+ }
299
+ }
300
+ }
301
+ }
302
+ // Remove text binding nodes after the walk to not disturb the TreeWalker
303
+ for (const n of nodesToRemove) {
304
+ n.parentNode.removeChild(n);
305
+ }
306
+ }
307
+ }
308
+ const endsWith = (str, suffix) => {
309
+ const index = str.length - suffix.length;
310
+ return index >= 0 && str.slice(index) === suffix;
311
+ };
312
+ const isTemplatePartActive = (part) => part.index !== -1;
313
+ // Allows `document.createComment('')` to be renamed for a
314
+ // small manual size-savings.
315
+ const createMarker = () => document.createComment('');
316
+ /**
317
+ * This regex extracts the attribute name preceding an attribute-position
318
+ * expression. It does this by matching the syntax allowed for attributes
319
+ * against the string literal directly preceding the expression, assuming that
320
+ * the expression is in an attribute-value position.
321
+ *
322
+ * See attributes in the HTML spec:
323
+ * https://www.w3.org/TR/html5/syntax.html#elements-attributes
324
+ *
325
+ * " \x09\x0a\x0c\x0d" are HTML space characters:
326
+ * https://www.w3.org/TR/html5/infrastructure.html#space-characters
327
+ *
328
+ * "\0-\x1F\x7F-\x9F" are Unicode control characters, which includes every
329
+ * space character except " ".
330
+ *
331
+ * So an attribute is:
332
+ * * The name: any character except a control character, space character, ('),
333
+ * ("), ">", "=", or "/"
334
+ * * Followed by zero or more space characters
335
+ * * Followed by "="
336
+ * * Followed by zero or more space characters
337
+ * * Followed by:
338
+ * * Any character except space, ('), ("), "<", ">", "=", (`), or
339
+ * * (") then any non-("), or
340
+ * * (') then any non-(')
341
+ */
342
+ const lastAttributeNameRegex =
343
+ // eslint-disable-next-line no-control-regex
344
+ /([ \x09\x0a\x0c\x0d])([^\0-\x1F\x7F-\x9F "'>=/]+)([ \x09\x0a\x0c\x0d]*=[ \x09\x0a\x0c\x0d]*(?:[^ \x09\x0a\x0c\x0d"'`<>=]*|"[^"]*|'[^']*))$/;
345
+
346
+ /**
347
+ * @license
348
+ * Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
349
+ * This code may only be used under the BSD style license found at
350
+ * http://polymer.github.io/LICENSE.txt
351
+ * The complete set of authors may be found at
352
+ * http://polymer.github.io/AUTHORS.txt
353
+ * The complete set of contributors may be found at
354
+ * http://polymer.github.io/CONTRIBUTORS.txt
355
+ * Code distributed by Google as part of the polymer project is also
356
+ * subject to an additional IP rights grant found at
357
+ * http://polymer.github.io/PATENTS.txt
358
+ */
359
+ /**
360
+ * An instance of a `Template` that can be attached to the DOM and updated
361
+ * with new values.
362
+ */
363
+ class TemplateInstance {
364
+ constructor(template, processor, options) {
365
+ this.__parts = [];
366
+ this.template = template;
367
+ this.processor = processor;
368
+ this.options = options;
369
+ }
370
+ update(values) {
371
+ let i = 0;
372
+ for (const part of this.__parts) {
373
+ if (part !== undefined) {
374
+ part.setValue(values[i]);
375
+ }
376
+ i++;
377
+ }
378
+ for (const part of this.__parts) {
379
+ if (part !== undefined) {
380
+ part.commit();
381
+ }
382
+ }
383
+ }
384
+ _clone() {
385
+ // There are a number of steps in the lifecycle of a template instance's
386
+ // DOM fragment:
387
+ // 1. Clone - create the instance fragment
388
+ // 2. Adopt - adopt into the main document
389
+ // 3. Process - find part markers and create parts
390
+ // 4. Upgrade - upgrade custom elements
391
+ // 5. Update - set node, attribute, property, etc., values
392
+ // 6. Connect - connect to the document. Optional and outside of this
393
+ // method.
394
+ //
395
+ // We have a few constraints on the ordering of these steps:
396
+ // * We need to upgrade before updating, so that property values will pass
397
+ // through any property setters.
398
+ // * We would like to process before upgrading so that we're sure that the
399
+ // cloned fragment is inert and not disturbed by self-modifying DOM.
400
+ // * We want custom elements to upgrade even in disconnected fragments.
401
+ //
402
+ // Given these constraints, with full custom elements support we would
403
+ // prefer the order: Clone, Process, Adopt, Upgrade, Update, Connect
404
+ //
405
+ // But Safari does not implement CustomElementRegistry#upgrade, so we
406
+ // can not implement that order and still have upgrade-before-update and
407
+ // upgrade disconnected fragments. So we instead sacrifice the
408
+ // process-before-upgrade constraint, since in Custom Elements v1 elements
409
+ // must not modify their light DOM in the constructor. We still have issues
410
+ // when co-existing with CEv0 elements like Polymer 1, and with polyfills
411
+ // that don't strictly adhere to the no-modification rule because shadow
412
+ // DOM, which may be created in the constructor, is emulated by being placed
413
+ // in the light DOM.
414
+ //
415
+ // The resulting order is on native is: Clone, Adopt, Upgrade, Process,
416
+ // Update, Connect. document.importNode() performs Clone, Adopt, and Upgrade
417
+ // in one step.
418
+ //
419
+ // The Custom Elements v1 polyfill supports upgrade(), so the order when
420
+ // polyfilled is the more ideal: Clone, Process, Adopt, Upgrade, Update,
421
+ // Connect.
422
+ const fragment = isCEPolyfill ?
423
+ this.template.element.content.cloneNode(true) :
424
+ document.importNode(this.template.element.content, true);
425
+ const stack = [];
426
+ const parts = this.template.parts;
427
+ // Edge needs all 4 parameters present; IE11 needs 3rd parameter to be null
428
+ const walker = document.createTreeWalker(fragment, 133 /* NodeFilter.SHOW_{ELEMENT|COMMENT|TEXT} */, null, false);
429
+ let partIndex = 0;
430
+ let nodeIndex = 0;
431
+ let part;
432
+ let node = walker.nextNode();
433
+ // Loop through all the nodes and parts of a template
434
+ while (partIndex < parts.length) {
435
+ part = parts[partIndex];
436
+ if (!isTemplatePartActive(part)) {
437
+ this.__parts.push(undefined);
438
+ partIndex++;
439
+ continue;
440
+ }
441
+ // Progress the tree walker until we find our next part's node.
442
+ // Note that multiple parts may share the same node (attribute parts
443
+ // on a single element), so this loop may not run at all.
444
+ while (nodeIndex < part.index) {
445
+ nodeIndex++;
446
+ if (node.nodeName === 'TEMPLATE') {
447
+ stack.push(node);
448
+ walker.currentNode = node.content;
449
+ }
450
+ if ((node = walker.nextNode()) === null) {
451
+ // We've exhausted the content inside a nested template element.
452
+ // Because we still have parts (the outer for-loop), we know:
453
+ // - There is a template in the stack
454
+ // - The walker will find a nextNode outside the template
455
+ walker.currentNode = stack.pop();
456
+ node = walker.nextNode();
457
+ }
458
+ }
459
+ // We've arrived at our part's node.
460
+ if (part.type === 'node') {
461
+ const part = this.processor.handleTextExpression(this.options);
462
+ part.insertAfterNode(node.previousSibling);
463
+ this.__parts.push(part);
464
+ }
465
+ else {
466
+ this.__parts.push(...this.processor.handleAttributeExpressions(node, part.name, part.strings, this.options));
467
+ }
468
+ partIndex++;
469
+ }
470
+ if (isCEPolyfill) {
471
+ document.adoptNode(fragment);
472
+ customElements.upgrade(fragment);
473
+ }
474
+ return fragment;
475
+ }
476
+ }
477
+
478
+ /**
479
+ * @license
480
+ * Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
481
+ * This code may only be used under the BSD style license found at
482
+ * http://polymer.github.io/LICENSE.txt
483
+ * The complete set of authors may be found at
484
+ * http://polymer.github.io/AUTHORS.txt
485
+ * The complete set of contributors may be found at
486
+ * http://polymer.github.io/CONTRIBUTORS.txt
487
+ * Code distributed by Google as part of the polymer project is also
488
+ * subject to an additional IP rights grant found at
489
+ * http://polymer.github.io/PATENTS.txt
490
+ */
491
+ /**
492
+ * Our TrustedTypePolicy for HTML which is declared using the html template
493
+ * tag function.
494
+ *
495
+ * That HTML is a developer-authored constant, and is parsed with innerHTML
496
+ * before any untrusted expressions have been mixed in. Therefor it is
497
+ * considered safe by construction.
498
+ */
499
+ const policy = window.trustedTypes &&
500
+ trustedTypes.createPolicy('lit-html', { createHTML: (s) => s });
501
+ const commentMarker = ` ${marker} `;
502
+ /**
503
+ * The return type of `html`, which holds a Template and the values from
504
+ * interpolated expressions.
505
+ */
506
+ class TemplateResult {
507
+ constructor(strings, values, type, processor) {
508
+ this.strings = strings;
509
+ this.values = values;
510
+ this.type = type;
511
+ this.processor = processor;
512
+ }
513
+ /**
514
+ * Returns a string of HTML used to create a `<template>` element.
515
+ */
516
+ getHTML() {
517
+ const l = this.strings.length - 1;
518
+ let html = '';
519
+ let isCommentBinding = false;
520
+ for (let i = 0; i < l; i++) {
521
+ const s = this.strings[i];
522
+ // For each binding we want to determine the kind of marker to insert
523
+ // into the template source before it's parsed by the browser's HTML
524
+ // parser. The marker type is based on whether the expression is in an
525
+ // attribute, text, or comment position.
526
+ // * For node-position bindings we insert a comment with the marker
527
+ // sentinel as its text content, like <!--{{lit-guid}}-->.
528
+ // * For attribute bindings we insert just the marker sentinel for the
529
+ // first binding, so that we support unquoted attribute bindings.
530
+ // Subsequent bindings can use a comment marker because multi-binding
531
+ // attributes must be quoted.
532
+ // * For comment bindings we insert just the marker sentinel so we don't
533
+ // close the comment.
534
+ //
535
+ // The following code scans the template source, but is *not* an HTML
536
+ // parser. We don't need to track the tree structure of the HTML, only
537
+ // whether a binding is inside a comment, and if not, if it appears to be
538
+ // the first binding in an attribute.
539
+ const commentOpen = s.lastIndexOf('<!--');
540
+ // We're in comment position if we have a comment open with no following
541
+ // comment close. Because <-- can appear in an attribute value there can
542
+ // be false positives.
543
+ isCommentBinding = (commentOpen > -1 || isCommentBinding) &&
544
+ s.indexOf('-->', commentOpen + 1) === -1;
545
+ // Check to see if we have an attribute-like sequence preceding the
546
+ // expression. This can match "name=value" like structures in text,
547
+ // comments, and attribute values, so there can be false-positives.
548
+ const attributeMatch = lastAttributeNameRegex.exec(s);
549
+ if (attributeMatch === null) {
550
+ // We're only in this branch if we don't have a attribute-like
551
+ // preceding sequence. For comments, this guards against unusual
552
+ // attribute values like <div foo="<!--${'bar'}">. Cases like
553
+ // <!-- foo=${'bar'}--> are handled correctly in the attribute branch
554
+ // below.
555
+ html += s + (isCommentBinding ? commentMarker : nodeMarker);
556
+ }
557
+ else {
558
+ // For attributes we use just a marker sentinel, and also append a
559
+ // $lit$ suffix to the name to opt-out of attribute-specific parsing
560
+ // that IE and Edge do for style and certain SVG attributes.
561
+ html += s.substr(0, attributeMatch.index) + attributeMatch[1] +
562
+ attributeMatch[2] + boundAttributeSuffix + attributeMatch[3] +
563
+ marker;
564
+ }
565
+ }
566
+ html += this.strings[l];
567
+ return html;
568
+ }
569
+ getTemplateElement() {
570
+ const template = document.createElement('template');
571
+ let value = this.getHTML();
572
+ if (policy !== undefined) {
573
+ // this is secure because `this.strings` is a TemplateStringsArray.
574
+ // TODO: validate this when
575
+ // https://github.com/tc39/proposal-array-is-template-object is
576
+ // implemented.
577
+ value = policy.createHTML(value);
578
+ }
579
+ template.innerHTML = value;
580
+ return template;
581
+ }
582
+ }
583
+ /**
584
+ * A TemplateResult for SVG fragments.
585
+ *
586
+ * This class wraps HTML in an `<svg>` tag in order to parse its contents in the
587
+ * SVG namespace, then modifies the template to remove the `<svg>` tag so that
588
+ * clones only container the original fragment.
589
+ */
590
+ class SVGTemplateResult extends TemplateResult {
591
+ getHTML() {
592
+ return `<svg>${super.getHTML()}</svg>`;
593
+ }
594
+ getTemplateElement() {
595
+ const template = super.getTemplateElement();
596
+ const content = template.content;
597
+ const svgElement = content.firstChild;
598
+ content.removeChild(svgElement);
599
+ reparentNodes(content, svgElement.firstChild);
600
+ return template;
601
+ }
602
+ }
603
+
604
+ /**
605
+ * @license
606
+ * Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
607
+ * This code may only be used under the BSD style license found at
608
+ * http://polymer.github.io/LICENSE.txt
609
+ * The complete set of authors may be found at
610
+ * http://polymer.github.io/AUTHORS.txt
611
+ * The complete set of contributors may be found at
612
+ * http://polymer.github.io/CONTRIBUTORS.txt
613
+ * Code distributed by Google as part of the polymer project is also
614
+ * subject to an additional IP rights grant found at
615
+ * http://polymer.github.io/PATENTS.txt
616
+ */
617
+ const isPrimitive = (value) => {
618
+ return (value === null ||
619
+ !(typeof value === 'object' || typeof value === 'function'));
620
+ };
621
+ const isIterable = (value) => {
622
+ return Array.isArray(value) ||
623
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
624
+ !!(value && value[Symbol.iterator]);
625
+ };
626
+ /**
627
+ * Writes attribute values to the DOM for a group of AttributeParts bound to a
628
+ * single attribute. The value is only set once even if there are multiple parts
629
+ * for an attribute.
630
+ */
631
+ class AttributeCommitter {
632
+ constructor(element, name, strings) {
633
+ this.dirty = true;
634
+ this.element = element;
635
+ this.name = name;
636
+ this.strings = strings;
637
+ this.parts = [];
638
+ for (let i = 0; i < strings.length - 1; i++) {
639
+ this.parts[i] = this._createPart();
640
+ }
641
+ }
642
+ /**
643
+ * Creates a single part. Override this to create a differnt type of part.
644
+ */
645
+ _createPart() {
646
+ return new AttributePart(this);
647
+ }
648
+ _getValue() {
649
+ const strings = this.strings;
650
+ const l = strings.length - 1;
651
+ const parts = this.parts;
652
+ // If we're assigning an attribute via syntax like:
653
+ // attr="${foo}" or attr=${foo}
654
+ // but not
655
+ // attr="${foo} ${bar}" or attr="${foo} baz"
656
+ // then we don't want to coerce the attribute value into one long
657
+ // string. Instead we want to just return the value itself directly,
658
+ // so that sanitizeDOMValue can get the actual value rather than
659
+ // String(value)
660
+ // The exception is if v is an array, in which case we do want to smash
661
+ // it together into a string without calling String() on the array.
662
+ //
663
+ // This also allows trusted values (when using TrustedTypes) being
664
+ // assigned to DOM sinks without being stringified in the process.
665
+ if (l === 1 && strings[0] === '' && strings[1] === '') {
666
+ const v = parts[0].value;
667
+ if (typeof v === 'symbol') {
668
+ return String(v);
669
+ }
670
+ if (typeof v === 'string' || !isIterable(v)) {
671
+ return v;
672
+ }
673
+ }
674
+ let text = '';
675
+ for (let i = 0; i < l; i++) {
676
+ text += strings[i];
677
+ const part = parts[i];
678
+ if (part !== undefined) {
679
+ const v = part.value;
680
+ if (isPrimitive(v) || !isIterable(v)) {
681
+ text += typeof v === 'string' ? v : String(v);
682
+ }
683
+ else {
684
+ for (const t of v) {
685
+ text += typeof t === 'string' ? t : String(t);
686
+ }
687
+ }
688
+ }
689
+ }
690
+ text += strings[l];
691
+ return text;
692
+ }
693
+ commit() {
694
+ if (this.dirty) {
695
+ this.dirty = false;
696
+ this.element.setAttribute(this.name, this._getValue());
697
+ }
698
+ }
699
+ }
700
+ /**
701
+ * A Part that controls all or part of an attribute value.
702
+ */
703
+ class AttributePart {
704
+ constructor(committer) {
705
+ this.value = undefined;
706
+ this.committer = committer;
707
+ }
708
+ setValue(value) {
709
+ if (value !== noChange && (!isPrimitive(value) || value !== this.value)) {
710
+ if (typeof value === 'object') {
711
+ this.value = JSON.stringify(value).replace(/"/g, `'`);
712
+ } else {
713
+ this.value = value;
714
+ }
715
+ // If the value is a not a directive, dirty the committer so that it'll
716
+ // call setAttribute. If the value is a directive, it'll dirty the
717
+ // committer if it calls setValue().
718
+ if (!isDirective(value)) {
719
+ this.committer.dirty = true;
720
+ }
721
+ }
722
+ }
723
+ commit() {
724
+ while (isDirective(this.value)) {
725
+ const directive = this.value;
726
+ this.value = noChange;
727
+ directive(this);
728
+ }
729
+ if (this.value === noChange) {
730
+ return;
731
+ }
732
+ this.committer.commit();
733
+ }
734
+ }
735
+ /**
736
+ * A Part that controls a location within a Node tree. Like a Range, NodePart
737
+ * has start and end locations and can set and update the Nodes between those
738
+ * locations.
739
+ *
740
+ * NodeParts support several value types: primitives, Nodes, TemplateResults,
741
+ * as well as arrays and iterables of those types.
742
+ */
743
+ class NodePart {
744
+ constructor(options) {
745
+ this.value = undefined;
746
+ this.__pendingValue = undefined;
747
+ this.options = options;
748
+ }
749
+ /**
750
+ * Appends this part into a container.
751
+ *
752
+ * This part must be empty, as its contents are not automatically moved.
753
+ */
754
+ appendInto(container) {
755
+ this.startNode = container.appendChild(createMarker());
756
+ this.endNode = container.appendChild(createMarker());
757
+ }
758
+ /**
759
+ * Inserts this part after the `ref` node (between `ref` and `ref`'s next
760
+ * sibling). Both `ref` and its next sibling must be static, unchanging nodes
761
+ * such as those that appear in a literal section of a template.
762
+ *
763
+ * This part must be empty, as its contents are not automatically moved.
764
+ */
765
+ insertAfterNode(ref) {
766
+ this.startNode = ref;
767
+ this.endNode = ref.nextSibling;
768
+ }
769
+ /**
770
+ * Appends this part into a parent part.
771
+ *
772
+ * This part must be empty, as its contents are not automatically moved.
773
+ */
774
+ appendIntoPart(part) {
775
+ part.__insert(this.startNode = createMarker());
776
+ part.__insert(this.endNode = createMarker());
777
+ }
778
+ /**
779
+ * Inserts this part after the `ref` part.
780
+ *
781
+ * This part must be empty, as its contents are not automatically moved.
782
+ */
783
+ insertAfterPart(ref) {
784
+ ref.__insert(this.startNode = createMarker());
785
+ this.endNode = ref.endNode;
786
+ ref.endNode = this.startNode;
787
+ }
788
+ setValue(value) {
789
+ this.__pendingValue = value;
790
+ }
791
+ commit() {
792
+ if (this.startNode.parentNode === null) {
793
+ return;
794
+ }
795
+ while (isDirective(this.__pendingValue)) {
796
+ const directive = this.__pendingValue;
797
+ this.__pendingValue = noChange;
798
+ directive(this);
799
+ }
800
+ const value = this.__pendingValue;
801
+ if (value === noChange) {
802
+ return;
803
+ }
804
+ if (isPrimitive(value)) {
805
+ if (value !== this.value) {
806
+ this.__commitText(value);
807
+ }
808
+ }
809
+ else if (value instanceof TemplateResult) {
810
+ this.__commitTemplateResult(value);
811
+ }
812
+ else if (value instanceof Node) {
813
+ this.__commitNode(value);
814
+ }
815
+ else if (isIterable(value)) {
816
+ this.__commitIterable(value);
817
+ }
818
+ else if (value === nothing) {
819
+ this.value = nothing;
820
+ this.clear();
821
+ }
822
+ else {
823
+ // Fallback, will render the string representation
824
+ this.__commitText(value);
825
+ }
826
+ }
827
+ __insert(node) {
828
+ this.endNode.parentNode.insertBefore(node, this.endNode);
829
+ }
830
+ __commitNode(value) {
831
+ if (this.value === value) {
832
+ return;
833
+ }
834
+ this.clear();
835
+ this.__insert(value);
836
+ this.value = value;
837
+ }
838
+ __commitText(value) {
839
+ const node = this.startNode.nextSibling;
840
+ value = value == null ? '' : value;
841
+ // If `value` isn't already a string, we explicitly convert it here in case
842
+ // it can't be implicitly converted - i.e. it's a symbol.
843
+ const valueAsString = typeof value === 'string' ? value : String(value);
844
+ if (node === this.endNode.previousSibling &&
845
+ node.nodeType === 3 /* Node.TEXT_NODE */) {
846
+ // If we only have a single text node between the markers, we can just
847
+ // set its value, rather than replacing it.
848
+ // TODO(justinfagnani): Can we just check if this.value is primitive?
849
+ node.data = valueAsString;
850
+ }
851
+ else {
852
+ this.__commitNode(document.createTextNode(valueAsString));
853
+ }
854
+ this.value = value;
855
+ }
856
+ __commitTemplateResult(value) {
857
+ const template = this.options.templateFactory(value);
858
+ if (this.value instanceof TemplateInstance &&
859
+ this.value.template === template) {
860
+ this.value.update(value.values);
861
+ }
862
+ else {
863
+ // Make sure we propagate the template processor from the TemplateResult
864
+ // so that we use its syntax extension, etc. The template factory comes
865
+ // from the render function options so that it can control template
866
+ // caching and preprocessing.
867
+ const instance = new TemplateInstance(template, value.processor, this.options);
868
+ const fragment = instance._clone();
869
+ instance.update(value.values);
870
+ this.__commitNode(fragment);
871
+ this.value = instance;
872
+ }
873
+ }
874
+ __commitIterable(value) {
875
+ // For an Iterable, we create a new InstancePart per item, then set its
876
+ // value to the item. This is a little bit of overhead for every item in
877
+ // an Iterable, but it lets us recurse easily and efficiently update Arrays
878
+ // of TemplateResults that will be commonly returned from expressions like:
879
+ // array.map((i) => html`${i}`), by reusing existing TemplateInstances.
880
+ // If _value is an array, then the previous render was of an
881
+ // iterable and _value will contain the NodeParts from the previous
882
+ // render. If _value is not an array, clear this part and make a new
883
+ // array for NodeParts.
884
+ if (!Array.isArray(this.value)) {
885
+ this.value = [];
886
+ this.clear();
887
+ }
888
+ // Lets us keep track of how many items we stamped so we can clear leftover
889
+ // items from a previous render
890
+ const itemParts = this.value;
891
+ let partIndex = 0;
892
+ let itemPart;
893
+ for (const item of value) {
894
+ // Try to reuse an existing part
895
+ itemPart = itemParts[partIndex];
896
+ // If no existing part, create a new one
897
+ if (itemPart === undefined) {
898
+ itemPart = new NodePart(this.options);
899
+ itemParts.push(itemPart);
900
+ if (partIndex === 0) {
901
+ itemPart.appendIntoPart(this);
902
+ }
903
+ else {
904
+ itemPart.insertAfterPart(itemParts[partIndex - 1]);
905
+ }
906
+ }
907
+ itemPart.setValue(item);
908
+ itemPart.commit();
909
+ partIndex++;
910
+ }
911
+ if (partIndex < itemParts.length) {
912
+ // Truncate the parts array so _value reflects the current state
913
+ itemParts.length = partIndex;
914
+ this.clear(itemPart && itemPart.endNode);
915
+ }
916
+ }
917
+ clear(startNode = this.startNode) {
918
+ removeNodes(this.startNode.parentNode, startNode.nextSibling, this.endNode);
919
+ }
920
+ }
921
+ /**
922
+ * Implements a boolean attribute, roughly as defined in the HTML
923
+ * specification.
924
+ *
925
+ * If the value is truthy, then the attribute is present with a value of
926
+ * ''. If the value is falsey, the attribute is removed.
927
+ */
928
+ class BooleanAttributePart {
929
+ constructor(element, name, strings) {
930
+ this.value = undefined;
931
+ this.__pendingValue = undefined;
932
+ if (strings.length !== 2 || strings[0] !== '' || strings[1] !== '') {
933
+ throw new Error('Boolean attributes can only contain a single expression');
934
+ }
935
+ this.element = element;
936
+ this.name = name;
937
+ this.strings = strings;
938
+ }
939
+ setValue(value) {
940
+ this.__pendingValue = value;
941
+ }
942
+ commit() {
943
+ while (isDirective(this.__pendingValue)) {
944
+ const directive = this.__pendingValue;
945
+ this.__pendingValue = noChange;
946
+ directive(this);
947
+ }
948
+ if (this.__pendingValue === noChange) {
949
+ return;
950
+ }
951
+ const value = !!this.__pendingValue;
952
+ if (this.value !== value) {
953
+ if (value) {
954
+ this.element.setAttribute(this.name, '');
955
+ }
956
+ else {
957
+ this.element.removeAttribute(this.name);
958
+ }
959
+ this.value = value;
960
+ }
961
+ this.__pendingValue = noChange;
962
+ }
963
+ }
964
+ /**
965
+ * Sets attribute values for PropertyParts, so that the value is only set once
966
+ * even if there are multiple parts for a property.
967
+ *
968
+ * If an expression controls the whole property value, then the value is simply
969
+ * assigned to the property under control. If there are string literals or
970
+ * multiple expressions, then the strings are expressions are interpolated into
971
+ * a string first.
972
+ */
973
+ class PropertyCommitter extends AttributeCommitter {
974
+ constructor(element, name, strings) {
975
+ super(element, name, strings);
976
+ this.single =
977
+ (strings.length === 2 && strings[0] === '' && strings[1] === '');
978
+ }
979
+ _createPart() {
980
+ return new PropertyPart(this);
981
+ }
982
+ _getValue() {
983
+ if (this.single) {
984
+ return this.parts[0].value;
985
+ }
986
+ return super._getValue();
987
+ }
988
+ commit() {
989
+ if (this.dirty) {
990
+ this.dirty = false;
991
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
992
+ this.element[this.name] = this._getValue();
993
+ }
994
+ }
995
+ }
996
+ class PropertyPart extends AttributePart {
997
+ }
998
+ // Detect event listener options support. If the `capture` property is read
999
+ // from the options object, then options are supported. If not, then the third
1000
+ // argument to add/removeEventListener is interpreted as the boolean capture
1001
+ // value so we should only pass the `capture` property.
1002
+ let eventOptionsSupported = false;
1003
+ // Wrap into an IIFE because MS Edge <= v41 does not support having try/catch
1004
+ // blocks right into the body of a module
1005
+ (() => {
1006
+ try {
1007
+ const options = {
1008
+ get capture() {
1009
+ eventOptionsSupported = true;
1010
+ return false;
1011
+ }
1012
+ };
1013
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1014
+ window.addEventListener('test', options, options);
1015
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1016
+ window.removeEventListener('test', options, options);
1017
+ }
1018
+ catch (_e) {
1019
+ // event options not supported
1020
+ }
1021
+ })();
1022
+ class EventPart {
1023
+ constructor(element, eventName, eventContext) {
1024
+ this.value = undefined;
1025
+ this.__pendingValue = undefined;
1026
+ this.element = element;
1027
+ this.eventName = eventName;
1028
+ this.eventContext = eventContext;
1029
+ this.__boundHandleEvent = (e) => this.handleEvent(e);
1030
+ }
1031
+ setValue(value) {
1032
+ this.__pendingValue = value;
1033
+ }
1034
+ commit() {
1035
+ while (isDirective(this.__pendingValue)) {
1036
+ const directive = this.__pendingValue;
1037
+ this.__pendingValue = noChange;
1038
+ directive(this);
1039
+ }
1040
+ if (this.__pendingValue === noChange) {
1041
+ return;
1042
+ }
1043
+ const newListener = this.__pendingValue;
1044
+ const oldListener = this.value;
1045
+ const shouldRemoveListener = newListener == null ||
1046
+ oldListener != null &&
1047
+ (newListener.capture !== oldListener.capture ||
1048
+ newListener.once !== oldListener.once ||
1049
+ newListener.passive !== oldListener.passive);
1050
+ const shouldAddListener = newListener != null && (oldListener == null || shouldRemoveListener);
1051
+ if (shouldRemoveListener) {
1052
+ this.element.removeEventListener(this.eventName, this.__boundHandleEvent, this.__options);
1053
+ }
1054
+ if (shouldAddListener) {
1055
+ this.__options = getOptions(newListener);
1056
+ this.element.addEventListener(this.eventName, this.__boundHandleEvent, this.__options);
1057
+ }
1058
+ this.value = newListener;
1059
+ this.__pendingValue = noChange;
1060
+ }
1061
+ handleEvent(event) {
1062
+ if (typeof this.value === 'function') {
1063
+ this.value.call(this.eventContext || this.element, event);
1064
+ }
1065
+ else {
1066
+ this.value.handleEvent(event);
1067
+ }
1068
+ }
1069
+ }
1070
+ // We copy options because of the inconsistent behavior of browsers when reading
1071
+ // the third argument of add/removeEventListener. IE11 doesn't support options
1072
+ // 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 } :
1076
+ o.capture);
1077
+
1078
+ /**
1079
+ * @license
1080
+ * Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
1081
+ * This code may only be used under the BSD style license found at
1082
+ * http://polymer.github.io/LICENSE.txt
1083
+ * The complete set of authors may be found at
1084
+ * http://polymer.github.io/AUTHORS.txt
1085
+ * The complete set of contributors may be found at
1086
+ * http://polymer.github.io/CONTRIBUTORS.txt
1087
+ * Code distributed by Google as part of the polymer project is also
1088
+ * subject to an additional IP rights grant found at
1089
+ * http://polymer.github.io/PATENTS.txt
1090
+ */
1091
+ /**
1092
+ * Creates Parts when a template is instantiated.
1093
+ */
1094
+ class DefaultTemplateProcessor {
1095
+ /**
1096
+ * Create parts for an attribute-position binding, given the event, attribute
1097
+ * name, and string literals.
1098
+ *
1099
+ * @param element The element containing the binding
1100
+ * @param name The attribute name
1101
+ * @param strings The string literals. There are always at least two strings,
1102
+ * event for fully-controlled bindings with a single expression.
1103
+ */
1104
+ handleAttributeExpressions(element, name, strings, options) {
1105
+ const prefix = name[0];
1106
+ if (prefix === '.') {
1107
+ const committer = new PropertyCommitter(element, name.slice(1), strings);
1108
+ return committer.parts;
1109
+ }
1110
+ if (prefix === '@') {
1111
+ return [new EventPart(element, name.slice(1), options.eventContext)];
1112
+ }
1113
+ if (prefix === '?') {
1114
+ return [new BooleanAttributePart(element, name.slice(1), strings)];
1115
+ }
1116
+ const committer = new AttributeCommitter(element, name, strings);
1117
+ return committer.parts;
1118
+ }
1119
+ /**
1120
+ * Create parts for a text-position binding.
1121
+ * @param templateFactory
1122
+ */
1123
+ handleTextExpression(options) {
1124
+ return new NodePart(options);
1125
+ }
1126
+ }
1127
+ const defaultTemplateProcessor = new DefaultTemplateProcessor();
1128
+
1129
+ /**
1130
+ * @license
1131
+ * Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
1132
+ * This code may only be used under the BSD style license found at
1133
+ * http://polymer.github.io/LICENSE.txt
1134
+ * The complete set of authors may be found at
1135
+ * http://polymer.github.io/AUTHORS.txt
1136
+ * The complete set of contributors may be found at
1137
+ * http://polymer.github.io/CONTRIBUTORS.txt
1138
+ * Code distributed by Google as part of the polymer project is also
1139
+ * subject to an additional IP rights grant found at
1140
+ * http://polymer.github.io/PATENTS.txt
1141
+ */
1142
+ /**
1143
+ * The default TemplateFactory which caches Templates keyed on
1144
+ * result.type and result.strings.
1145
+ */
1146
+ function templateFactory(result) {
1147
+ let templateCache = templateCaches.get(result.type);
1148
+ if (templateCache === undefined) {
1149
+ templateCache = {
1150
+ stringsArray: new WeakMap(),
1151
+ keyString: new Map()
1152
+ };
1153
+ templateCaches.set(result.type, templateCache);
1154
+ }
1155
+ let template = templateCache.stringsArray.get(result.strings);
1156
+ if (template !== undefined) {
1157
+ return template;
1158
+ }
1159
+ // If the TemplateStringsArray is new, generate a key from the strings
1160
+ // This key is shared between all templates with identical content
1161
+ const key = result.strings.join(marker);
1162
+ // Check if we already have a Template for this key
1163
+ template = templateCache.keyString.get(key);
1164
+ if (template === undefined) {
1165
+ // If we have not seen this key before, create a new Template
1166
+ template = new Template(result, result.getTemplateElement());
1167
+ // Cache the Template for this key
1168
+ templateCache.keyString.set(key, template);
1169
+ }
1170
+ // Cache all future queries for this TemplateStringsArray
1171
+ templateCache.stringsArray.set(result.strings, template);
1172
+ return template;
1173
+ }
1174
+ const templateCaches = new Map();
1175
+
1176
+ /**
1177
+ * @license
1178
+ * Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
1179
+ * This code may only be used under the BSD style license found at
1180
+ * http://polymer.github.io/LICENSE.txt
1181
+ * The complete set of authors may be found at
1182
+ * http://polymer.github.io/AUTHORS.txt
1183
+ * The complete set of contributors may be found at
1184
+ * http://polymer.github.io/CONTRIBUTORS.txt
1185
+ * Code distributed by Google as part of the polymer project is also
1186
+ * subject to an additional IP rights grant found at
1187
+ * http://polymer.github.io/PATENTS.txt
1188
+ */
1189
+ const parts = new WeakMap();
1190
+ /**
1191
+ * Renders a template result or other value to a container.
1192
+ *
1193
+ * To update a container with new values, reevaluate the template literal and
1194
+ * call `render` with the new result.
1195
+ *
1196
+ * @param result Any value renderable by NodePart - typically a TemplateResult
1197
+ * created by evaluating a template tag like `html` or `svg`.
1198
+ * @param container A DOM parent to render to. The entire contents are either
1199
+ * replaced, or efficiently updated if the same result type was previous
1200
+ * rendered there.
1201
+ * @param options RenderOptions for the entire render tree rendered to this
1202
+ * container. Render options must *not* change between renders to the same
1203
+ * container, as those changes will not effect previously rendered DOM.
1204
+ */
1205
+ const render = (result, container, options) => {
1206
+ let part = parts.get(container);
1207
+ if (part === undefined) {
1208
+ removeNodes(container, container.firstChild);
1209
+ parts.set(container, part = new NodePart(Object.assign({ templateFactory }, options)));
1210
+ part.appendInto(container);
1211
+ }
1212
+ part.setValue(result);
1213
+ part.commit();
1214
+ };
1215
+
1216
+ /**
1217
+ * @license
1218
+ * Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
1219
+ * This code may only be used under the BSD style license found at
1220
+ * http://polymer.github.io/LICENSE.txt
1221
+ * The complete set of authors may be found at
1222
+ * http://polymer.github.io/AUTHORS.txt
1223
+ * The complete set of contributors may be found at
1224
+ * http://polymer.github.io/CONTRIBUTORS.txt
1225
+ * Code distributed by Google as part of the polymer project is also
1226
+ * subject to an additional IP rights grant found at
1227
+ * http://polymer.github.io/PATENTS.txt
1228
+ */
1229
+ // IMPORTANT: do not change the property name or the assignment expression.
1230
+ // This line will be used in regexes to search for lit-html usage.
1231
+ // TODO(justinfagnani): inject version number at build time
1232
+ if (typeof window !== 'undefined') {
1233
+ (window['litHtmlVersions'] || (window['litHtmlVersions'] = [])).push('1.4.1');
1234
+ }
1235
+ /**
1236
+ * Interprets a template literal as an HTML template that can efficiently
1237
+ * render to and update a container.
1238
+ */
1239
+ const html = (strings, ...values) => new TemplateResult(strings, values, 'html', defaultTemplateProcessor);
1240
+ /**
1241
+ * Interprets a template literal as an SVG template that can efficiently
1242
+ * render to and update a container.
1243
+ */
1244
+ const svg = (strings, ...values) => new SVGTemplateResult(strings, values, 'svg', defaultTemplateProcessor);
1245
+
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 };
test/index.test.js ADDED
@@ -0,0 +1,123 @@
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 ADDED
@@ -0,0 +1,2 @@
1
+ global.window = {}
2
+ global.Document = class { };