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.


ba74771Peter John 2026-08-07T16:03:34+05:30
improve components
examples/e2e.spec.js CHANGED
@@ -49,7 +49,18 @@ test('renders each counter exactly once, with no leftover duplicate SSR markup',
49
49
  }
50
50
  });
51
51
 
52
+ test('seeds each counter from its count attribute, and app-total sums them with no manual sync', async () => {
53
+ const first = page.locator('app-counter').nth(0);
54
+ const second = page.locator('app-counter').nth(1);
55
+ assert.strictEqual((await first.locator('h1').textContent()).trim(), '5', 'first app-counter has count="5"');
56
+ assert.strictEqual((await second.locator('h1').textContent()).trim(), '7', 'second app-counter has count="7"');
57
+ // computed server-side by cheerio-parsing the already-rendered preceding
58
+ // markup (both app-counter tags), via useAttr — not a separately-tracked
59
+ // total, and no shared store between the two components.
60
+ assert.strictEqual((await page.locator('app-total h1').textContent()).trim(), 'Total of 2 Counters: 12');
61
+ });
62
+
52
- test('clicking +/- updates the counter and the shared total reducer', async () => {
63
+ test('clicking +/- updates the counter and the derived total', async () => {
53
64
  const first = page.locator('app-counter').nth(0);
54
65
  const second = page.locator('app-counter').nth(1);
55
66
 
@@ -59,9 +70,26 @@ test('clicking +/- updates the counter and the shared total reducer', async () =
59
70
  await second.getByRole('button', { name: '+' }).click();
60
71
  await first.getByRole('button', { name: '-' }).click();
61
72
 
73
+ // first: 5 + 3 - 1 = 7, second: 7 + 1 = 8, total: sum of both, live. Reflection
74
+ // and the MutationObserver it feeds are both async, so wait for the total to
75
+ // actually settle rather than asserting immediately after the last click.
76
+ await page.waitForFunction(() => document.querySelector('app-total h1').textContent.trim() === 'Total of 2 Counters: 15');
62
- assert.strictEqual((await first.locator('h1').textContent()).trim(), '2');
77
+ assert.strictEqual((await first.locator('h1').textContent()).trim(), '7');
63
- assert.strictEqual((await second.locator('h1').textContent()).trim(), '1');
78
+ assert.strictEqual((await second.locator('h1').textContent()).trim(), '8');
79
+ assert.strictEqual((await page.locator('app-total h1').textContent()).trim(), 'Total of 2 Counters: 15');
80
+ });
81
+
82
+ test('external attribute writes are adopted directly, with no separate internal state to conflict with', async () => {
83
+ // Continuing from the previous test, the first counter currently shows 7.
84
+ // count now lives only in the attribute (via useProp) — there's no reducer
85
+ // value to reconcile it against, so an external write just is the new
86
+ // state, and app-total (via useAttr) picks it up automatically.
87
+ const first = page.locator('app-counter').nth(0);
88
+ await first.evaluate((el) => el.setAttribute('count', '30'));
89
+ await page.waitForFunction(() => document.querySelector('app-counter').querySelector('h1').textContent.trim() === '30');
90
+ assert.strictEqual((await first.locator('h1').textContent()).trim(), '30');
91
+ await page.waitForFunction(() => document.querySelector('app-total h1').textContent.trim() === 'Total of 2 Counters: 38');
64
- assert.strictEqual((await page.locator('app-total h1').textContent()).trim(), 'Total of 2 Counters: 3');
92
+ assert.strictEqual((await page.locator('app-total h1').textContent()).trim(), 'Total of 2 Counters: 38');
65
93
  });
66
94
 
67
95
  test('no console/page errors were thrown', () => {
examples/elements/app-counter.js CHANGED
@@ -1,68 +1,23 @@
1
- import { useEffect } from '../../index.js';
2
- import { createElement, css, html, useReducer } from '../../index.js';
1
+ import { createElement, css, html, useProp } from '../../index.js';
3
- import { totalReducer } from '../store.js';
4
2
 
3
+ // State lives only in the count attribute — useProp reads it (type-coerced to
4
+ // a number, since the default 0 is a number) and writes it straight back via
5
+ // setAttribute, with no separate internal copy to keep in sync. count still
6
+ // has to appear as a destructured param below for it to end up in
7
+ // observedAttributes; useProp doesn't add it there itself.
5
- const Counter = ({ name, meta }) => {
8
+ const Counter = ({ name, count }) => {
6
- const { count, actions, effects } = useReducer({
7
- initial: {
8
- count: 0,
9
- data: undefined,
10
- err: null,
11
- },
12
- reducer: {
13
- increment: (state) => {
14
- state.count += 1;
15
- },
16
- decrement: (state) => {
17
- state.count -= 1;
18
- },
19
- setLoading: (state, v) => {
20
- state.loading = v;
21
- },
22
- setData: (state, data) => {
23
- state.data = data;
24
- },
25
- setErr: (state, err) => {
26
- state.err = err;
27
- },
28
- },
29
- effects: {
30
- loadData: async (actions, id) => {
31
- try {
32
- actions.setLoading(true);
33
- const res = await fetch(`/api/posts/${id}`);
9
+ const [currentCount, setCount] = useProp('count', 0);
34
- actions.setData(await res.json());
35
- } catch (err) {
36
- actions.setErr(err);
37
- } finally {
38
- actions.setLoading(false);
39
- }
40
- },
41
- },
42
- });
43
- useEffect(() => {
44
- effects.loadData(name);
45
- }, []);
46
- const increment = () => {
10
+ const increment = () => setCount((c) => c + 1);
47
- actions.increment();
48
- totalReducer.actions.increment(count + 1);
49
- };
50
- const decrement = () => {
11
+ const decrement = () => setCount((c) => c - 1);
51
- actions.decrement();
52
- totalReducer.actions.decrement(count - 1);
53
- };
54
- const warningClass = count > 10 ? 'warning' : '';
12
+ const warningClass = currentCount > 10 ? 'warning' : '';
55
13
 
56
14
  return html`
57
15
  <div class="heading">
58
- <div class="label">
16
+ <div class="label">Counter: ${name}</div>
59
- Counter: ${name}
60
- <span>starts at ${meta?.start}</span>
61
- </div>
62
17
  <div class="controls">
63
18
  <button @click=${decrement}>-</button>
64
19
  <div class="count">
65
- <h1 class="${warningClass}">${count}</h1>
20
+ <h1 class="${warningClass}">${currentCount}</h1>
66
21
  </div>
67
22
  <button @click=${increment}>+</button>
68
23
  </div>
examples/elements/app-total.js CHANGED
@@ -1,8 +1,11 @@
1
- import { createElement, css, html, useReducer } from '../../index.js';
1
+ import { createElement, css, html, useAttr } from '../../index.js';
2
- import { totalReducer } from '../store.js';
3
2
 
4
3
  const Total = () => {
4
+ // No shared store: reads the count attribute directly off every app-counter
5
+ // on the page. Server-side this parses the already-rendered preceding
6
+ // markup; client-side it's backed by a MutationObserver, set up once.
7
+ const counts = useAttr('app-counter', 'count');
5
- const { total } = useReducer(totalReducer);
8
+ const total = counts.reduce((sum, v) => sum + (Number(v) || 0), 0);
6
9
  return html`
7
10
  <div>
8
11
  <h1>Total of 2 Counters: ${total}</h1>
examples/pages/index.js CHANGED
@@ -21,8 +21,8 @@ const head = ({ config }) => {
21
21
  const body = () => {
22
22
  return staticHtml`
23
23
  <div class="page">
24
- <app-counter name="1" meta="{'start': 5}"></app-counter>
24
+ <app-counter name="1" count="5"></app-counter>
25
- <app-counter name="2" meta="{'start': 7}"></app-counter>
25
+ <app-counter name="2" count="7"></app-counter>
26
26
  <app-total></app-total>
27
27
  </div>
28
28
  `;
examples/server.js CHANGED
@@ -12,7 +12,6 @@ const port = process.argv[2] || 3000;
12
12
  const elements = ['app-counter.js', 'app-total.js'];
13
13
  const srcMap = {
14
14
  '/index.js': `${rootDir}/index.js`,
15
- '/store.js': `${__dirname}/store.js`,
16
15
  };
17
16
  elements.forEach((el) => {
18
17
  srcMap['/elements/' + el] = `${__dirname}/elements/${el}`;
examples/store.js DELETED
@@ -1,15 +0,0 @@
1
- import { createReducer } from '../index.js';
2
-
3
- export const totalReducer = createReducer({
4
- initial: {
5
- total: 0,
6
- },
7
- reducer: {
8
- increment: (state) => {
9
- state.total += 1;
10
- },
11
- decrement: (state) => {
12
- state.total -= 1;
13
- },
14
- },
15
- });
index.d.ts CHANGED
@@ -69,3 +69,16 @@ export function staticHtml(strings: TemplateStringsArray, ...values: any[]): any
69
69
  // (e.g. meta=${jsonAttr(data)}) so it round-trips through this library's
70
70
  // attribute-parsing convention instead of being stringified as [object Object].
71
71
  export function jsonAttr(value: any): unknown;
72
+ // Reads `attribute` off every element matching `selector` — server-side via
73
+ // cheerio over the already-rendered preceding markup, client-side via a
74
+ // MutationObserver. Returns the raw attribute strings; parse them yourself.
75
+ export function useAttr(selector: string, attribute: string): string[];
76
+ // The element instance itself (e.g. to call host.setAttribute(...)). Only
77
+ // call this during render — capture the result via closure for later use.
78
+ export function useHost(): any;
79
+ // State lives only in the named attribute — no separate internal copy. `name`
80
+ // must also appear as a destructured render-function parameter for it to end
81
+ // up in observedAttributes. defaultValue's type controls how the attribute
82
+ // string is parsed (number/boolean/string); the setter accepts a value or an
83
+ // updater function, always resolved against the live attribute at call time.
84
+ export function useProp<T>(name: string, defaultValue: T): [T, (next: T | ((prev: T) => T)) => void];
index.js CHANGED
@@ -13,11 +13,20 @@ import { create } from 'mutative';
13
13
  const isBrowser = typeof window !== 'undefined';
14
14
  export { html, isBrowser, unsafeHTML, staticHtml };
15
15
 
16
- // @lit-labs/ssr pulls in Node-only dependencies (module resolution, fetch
16
+ // @lit-labs/ssr and cheerio both pull in Node-only dependencies (module
17
- // polyfills), so it can only ever be imported on the server. This project's
17
+ // resolution, fetch polyfills), so they can only ever be imported on the
18
- // index.js is loaded directly in the browser too (no bundler), so the import
18
+ // server. This project's index.js is loaded directly in the browser too (no
19
- // is conditional and dynamic — in the browser this line never executes.
19
+ // bundler), so these imports are conditional and dynamic — in the browser
20
+ // these lines never execute.
20
21
  const ssr = isBrowser ? null : await import('@lit-labs/ssr');
22
+ const cheerio = isBrowser ? null : await import('cheerio');
23
+
24
+ // Set by expandCustomElements right before rendering each matched component,
25
+ // to whatever HTML has already been generated before it in the current page
26
+ // (i.e. its preceding siblings, already expanded) — this is what lets useAttr
27
+ // answer "what does this other, already-rendered element's attribute say"
28
+ // during SSR, where there's no live DOM to query.
29
+ const ssrContext = { precedingHtml: '' };
21
30
 
22
31
  const attrRE = /\s([^'"/\s><]+?)[\s/>]|([^\s=]+)=\s?(".*?"|'.*?')/g;
23
32
  const voidElements = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr'];
@@ -88,6 +97,28 @@ const parseTag = (tag) => {
88
97
  };
89
98
  const parseAttrValue = (value) => (value && value.startsWith('{') ? JSON.parse(value.replace(/'/g, '"')) : value);
90
99
 
100
+ // Type is inferred from defaultValue, the way useProp's own default argument
101
+ // works: a number default means parse as a number, a boolean default means
102
+ // parse as a boolean, otherwise pass the string through as-is. Anything
103
+ // already parsed upstream (an object/array, via parseAttrValue) is returned
104
+ // unchanged.
105
+ const coerceAttr = (raw, defaultValue) => {
106
+ if (raw === undefined || raw === null) {
107
+ return defaultValue;
108
+ }
109
+ if (typeof raw !== 'string') {
110
+ return raw;
111
+ }
112
+ if (typeof defaultValue === 'number') {
113
+ return Number(raw);
114
+ }
115
+ if (typeof defaultValue === 'boolean') {
116
+ return raw !== 'false' && raw !== '0';
117
+ }
118
+ return raw;
119
+ };
120
+ const serializeAttr = (value) => (typeof value === 'object' && value !== null ? JSON.stringify(value).replace(/"/g, `'`) : String(value));
121
+
91
122
  class JsonAttrDirective extends Directive {
92
123
  render(value) {
93
124
  return JSON.stringify(value).replace(/"/g, `'`);
@@ -122,7 +153,8 @@ const expandCustomElements = (htmlString) => {
122
153
  return htmlString;
123
154
  }
124
155
  const re = new RegExp(`<(${tagNames.join('|')})(?=[\\s>])([^>]*)>\\s*</\\1>`, 'g');
125
- return htmlString.replace(re, (match, tagName, attrsPortion) => {
156
+ return htmlString.replace(re, (match, tagName, attrsPortion, offset) => {
157
+ ssrContext.precedingHtml = htmlString.slice(0, offset);
126
158
  const { attrs } = parseTag(`<${tagName}${attrsPortion}>`);
127
159
  Object.keys(attrs).forEach((key) => {
128
160
  attrs[key] = parseAttrValue(attrs[key]);
@@ -290,6 +322,7 @@ const task = (flush) => {
290
322
  }
291
323
  };
292
324
  const depsChanged = (prev, next) => prev == null || next.some((f, i) => !Object.is(f, prev[i]));
325
+ const shallowArrayEqual = (a, b) => a.length === b.length && a.every((v, i) => v === b[i]);
293
326
 
294
327
  export const createAttrs = (attrs) => attrs;
295
328
 
@@ -354,6 +387,12 @@ export const useReducer = (reducer, selector = identity) => {
354
387
  const state = comp.hooks.data[index].getValue();
355
388
  return { ...state, actions: comp.hooks.data[index].actions, effects: comp.hooks.data[index].effects };
356
389
  };
390
+ // The element instance itself, e.g. to reflect state onto its own attributes
391
+ // (host.setAttribute(...)) so other components can read it via useAttr. Safe to
392
+ // call only during render — capture the return value via closure for later use
393
+ // in effects/handlers, don't call this hook itself from inside one.
394
+ export const useHost = () => currentComponent.get();
395
+
357
396
  export const useEffect = (fn, deps) => {
358
397
  const comp = currentComponent.get();
359
398
  const index = comp.hooks.index++;
@@ -363,6 +402,64 @@ export const useEffect = (fn, deps) => {
363
402
  }
364
403
  };
365
404
 
405
+ // Reads `attribute` off every element matching `selector`, without either side
406
+ // needing a shared store: on the server there's no live DOM to query, so it
407
+ // reads from ssrContext.precedingHtml (everything already rendered before this
408
+ // component in the current page) via cheerio; in the browser it sets up a
409
+ // MutationObserver once and re-renders this component whenever a match changes.
410
+ export const useAttr = (selector, attribute) => {
411
+ const comp = currentComponent.get();
412
+ const index = comp.hooks.index++;
413
+ if (!isBrowser) {
414
+ const $ = cheerio.load(ssrContext.precedingHtml);
415
+ return $(selector)
416
+ .map((i, el) => $(el).attr(attribute))
417
+ .get();
418
+ }
419
+ if (!comp.hooks.data[index]) {
420
+ const read = () => Array.from(document.querySelectorAll(selector)).map((el) => el.getAttribute(attribute));
421
+ comp.hooks.data[index] = read();
422
+ const observer = new window.MutationObserver(() => {
423
+ const next = read();
424
+ if (!shallowArrayEqual(next, comp.hooks.data[index])) {
425
+ comp.hooks.data[index] = next;
426
+ comp.update();
427
+ }
428
+ });
429
+ observer.observe(document.body, { attributes: true, attributeFilter: [attribute], subtree: true });
430
+ comp.hooks.cleanups[index] = () => observer.disconnect();
431
+ }
432
+ return comp.hooks.data[index];
433
+ };
434
+
435
+ // The attribute is the only copy of this state — there's no separate internal
436
+ // value to keep in sync with it (that bidirectional sync, tried in an earlier
437
+ // version of this, was a genuine, unresolvable race: whichever side changed
438
+ // last could always be undone by a stale update from the other side still in
439
+ // flight). Reading here is just "what does the attribute say right now";
440
+ // writing is just "set the attribute" (which synchronously updates comp.attrs
441
+ // via attributeChangedCallback and schedules a re-render if connected).
442
+ //
443
+ // The setter accepts a plain value or an updater function (setCount(c => c+1))
444
+ // — always resolved against the live attribute value at call time, not a
445
+ // value captured in a render closure, so rapid successive calls (e.g. quick
446
+ // clicks, each queued before a re-render lands) don't read stale state and
447
+ // silently lose updates.
448
+ //
449
+ // name must still appear as a destructured render-function parameter (even if
450
+ // unused otherwise) for it to end up in observedAttributes — that list is read
451
+ // by the browser once, synchronously, when the class is defined, so it can't
452
+ // depend on anything this hook (called later, during render) discovers.
453
+ export const useProp = (name, defaultValue) => {
454
+ const comp = currentComponent.get();
455
+ const value = coerceAttr(comp.attrs[name], defaultValue);
456
+ const setValue = (next) => {
457
+ const resolved = typeof next === 'function' ? next(coerceAttr(comp.attrs[name], defaultValue)) : next;
458
+ comp.setAttribute(name, serializeAttr(resolved));
459
+ };
460
+ return [value, setValue];
461
+ };
462
+
366
463
  const registry = {};
367
464
  const injectedStyleTags = new Set();
368
465
  export const getElement = (name) => registry[name];
@@ -381,6 +478,7 @@ export const createElement = (meta, renderFn) => {
381
478
  constructor(ssrAttrs) {
382
479
  super();
383
480
  this._dirty = false;
481
+ this._effectsPending = false;
384
482
  this._connected = false;
385
483
  this._hydrated = false;
386
484
  this.attrs = ssrAttrs || {};
@@ -465,7 +563,20 @@ export const createElement = (meta, renderFn) => {
465
563
  }
466
564
 
467
565
  enqueueEffects() {
566
+ // Without this guard, several render cycles completing in quick succession
567
+ // (e.g. rapid clicks) each schedule their own separate effects-flush task;
568
+ // those can interleave with later renders' own effect scheduling in ways
569
+ // that break invariants effects rely on (observed this firsthand: a
570
+ // still-pending flush from an earlier render raced a newer one and
571
+ // clobbered state that had already moved on).
572
+ if (this._effectsPending) {
573
+ return;
574
+ }
575
+ this._effectsPending = true;
468
- this.batch(task, filo, () => this._flushEffects());
576
+ this.batch(task, filo, () => {
577
+ this._effectsPending = false;
578
+ this._flushEffects();
579
+ });
469
580
  }
470
581
 
471
582
  render() {
package-lock.json CHANGED
@@ -11,6 +11,7 @@
11
11
  "dependencies": {
12
12
  "@lit-labs/ssr": "^4.1.0",
13
13
  "@lit-labs/ssr-client": "^1.1.8",
14
+ "cheerio": "^1.2.0",
14
15
  "lit-html": "^3.3.3",
15
16
  "mutative": "^1.3.0"
16
17
  },
@@ -91,6 +92,82 @@
91
92
  "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
92
93
  "license": "MIT"
93
94
  },
95
+ "node_modules/boolbase": {
96
+ "version": "1.0.0",
97
+ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
98
+ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
99
+ "license": "ISC"
100
+ },
101
+ "node_modules/cheerio": {
102
+ "version": "1.2.0",
103
+ "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz",
104
+ "integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==",
105
+ "license": "MIT",
106
+ "dependencies": {
107
+ "cheerio-select": "^2.1.0",
108
+ "dom-serializer": "^2.0.0",
109
+ "domhandler": "^5.0.3",
110
+ "domutils": "^3.2.2",
111
+ "encoding-sniffer": "^0.2.1",
112
+ "htmlparser2": "^10.1.0",
113
+ "parse5": "^7.3.0",
114
+ "parse5-htmlparser2-tree-adapter": "^7.1.0",
115
+ "parse5-parser-stream": "^7.1.2",
116
+ "undici": "^7.19.0",
117
+ "whatwg-mimetype": "^4.0.0"
118
+ },
119
+ "engines": {
120
+ "node": ">=20.18.1"
121
+ },
122
+ "funding": {
123
+ "url": "https://github.com/cheeriojs/cheerio?sponsor=1"
124
+ }
125
+ },
126
+ "node_modules/cheerio-select": {
127
+ "version": "2.1.0",
128
+ "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz",
129
+ "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==",
130
+ "license": "BSD-2-Clause",
131
+ "dependencies": {
132
+ "boolbase": "^1.0.0",
133
+ "css-select": "^5.1.0",
134
+ "css-what": "^6.1.0",
135
+ "domelementtype": "^2.3.0",
136
+ "domhandler": "^5.0.3",
137
+ "domutils": "^3.0.1"
138
+ },
139
+ "funding": {
140
+ "url": "https://github.com/sponsors/fb55"
141
+ }
142
+ },
143
+ "node_modules/css-select": {
144
+ "version": "5.2.2",
145
+ "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
146
+ "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==",
147
+ "license": "BSD-2-Clause",
148
+ "dependencies": {
149
+ "boolbase": "^1.0.0",
150
+ "css-what": "^6.1.0",
151
+ "domhandler": "^5.0.2",
152
+ "domutils": "^3.0.1",
153
+ "nth-check": "^2.0.1"
154
+ },
155
+ "funding": {
156
+ "url": "https://github.com/sponsors/fb55"
157
+ }
158
+ },
159
+ "node_modules/css-what": {
160
+ "version": "6.2.2",
161
+ "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz",
162
+ "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==",
163
+ "license": "BSD-2-Clause",
164
+ "engines": {
165
+ "node": ">= 6"
166
+ },
167
+ "funding": {
168
+ "url": "https://github.com/sponsors/fb55"
169
+ }
170
+ },
94
171
  "node_modules/data-uri-to-buffer": {
95
172
  "version": "4.0.1",
96
173
  "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
@@ -100,6 +177,86 @@
100
177
  "node": ">= 12"
101
178
  }
102
179
  },
180
+ "node_modules/dom-serializer": {
181
+ "version": "2.0.0",
182
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
183
+ "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
184
+ "license": "MIT",
185
+ "dependencies": {
186
+ "domelementtype": "^2.3.0",
187
+ "domhandler": "^5.0.2",
188
+ "entities": "^4.2.0"
189
+ },
190
+ "funding": {
191
+ "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
192
+ }
193
+ },
194
+ "node_modules/dom-serializer/node_modules/entities": {
195
+ "version": "4.5.0",
196
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
197
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
198
+ "license": "BSD-2-Clause",
199
+ "engines": {
200
+ "node": ">=0.12"
201
+ },
202
+ "funding": {
203
+ "url": "https://github.com/fb55/entities?sponsor=1"
204
+ }
205
+ },
206
+ "node_modules/domelementtype": {
207
+ "version": "2.3.0",
208
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
209
+ "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
210
+ "funding": [
211
+ {
212
+ "type": "github",
213
+ "url": "https://github.com/sponsors/fb55"
214
+ }
215
+ ],
216
+ "license": "BSD-2-Clause"
217
+ },
218
+ "node_modules/domhandler": {
219
+ "version": "5.0.3",
220
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
221
+ "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
222
+ "license": "BSD-2-Clause",
223
+ "dependencies": {
224
+ "domelementtype": "^2.3.0"
225
+ },
226
+ "engines": {
227
+ "node": ">= 4"
228
+ },
229
+ "funding": {
230
+ "url": "https://github.com/fb55/domhandler?sponsor=1"
231
+ }
232
+ },
233
+ "node_modules/domutils": {
234
+ "version": "3.2.2",
235
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
236
+ "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
237
+ "license": "BSD-2-Clause",
238
+ "dependencies": {
239
+ "dom-serializer": "^2.0.0",
240
+ "domelementtype": "^2.3.0",
241
+ "domhandler": "^5.0.3"
242
+ },
243
+ "funding": {
244
+ "url": "https://github.com/fb55/domutils?sponsor=1"
245
+ }
246
+ },
247
+ "node_modules/encoding-sniffer": {
248
+ "version": "0.2.1",
249
+ "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz",
250
+ "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==",
251
+ "license": "MIT",
252
+ "dependencies": {
253
+ "iconv-lite": "^0.6.3",
254
+ "whatwg-encoding": "^3.1.1"
255
+ },
256
+ "funding": {
257
+ "url": "https://github.com/fb55/encoding-sniffer?sponsor=1"
258
+ }
259
+ },
103
260
  "node_modules/enhanced-resolve": {
104
261
  "version": "5.24.5",
105
262
  "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz",
@@ -166,6 +323,49 @@
166
323
  "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
167
324
  "license": "ISC"
168
325
  },
326
+ "node_modules/htmlparser2": {
327
+ "version": "10.1.0",
328
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz",
329
+ "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==",
330
+ "funding": [
331
+ "https://github.com/fb55/htmlparser2?sponsor=1",
332
+ {
333
+ "type": "github",
334
+ "url": "https://github.com/sponsors/fb55"
335
+ }
336
+ ],
337
+ "license": "MIT",
338
+ "dependencies": {
339
+ "domelementtype": "^2.3.0",
340
+ "domhandler": "^5.0.3",
341
+ "domutils": "^3.2.2",
342
+ "entities": "^7.0.1"
343
+ }
344
+ },
345
+ "node_modules/htmlparser2/node_modules/entities": {
346
+ "version": "7.0.1",
347
+ "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
348
+ "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
349
+ "license": "BSD-2-Clause",
350
+ "engines": {
351
+ "node": ">=0.12"
352
+ },
353
+ "funding": {
354
+ "url": "https://github.com/fb55/entities?sponsor=1"
355
+ }
356
+ },
357
+ "node_modules/iconv-lite": {
358
+ "version": "0.6.3",
359
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
360
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
361
+ "license": "MIT",
362
+ "dependencies": {
363
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
364
+ },
365
+ "engines": {
366
+ "node": ">=0.10.0"
367
+ }
368
+ },
169
369
  "node_modules/lit": {
170
370
  "version": "3.3.3",
171
371
  "resolved": "https://registry.npmjs.org/lit/-/lit-3.3.3.tgz",
@@ -244,6 +444,18 @@
244
444
  "url": "https://opencollective.com/node-fetch"
245
445
  }
246
446
  },
447
+ "node_modules/nth-check": {
448
+ "version": "2.1.1",
449
+ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
450
+ "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
451
+ "license": "BSD-2-Clause",
452
+ "dependencies": {
453
+ "boolbase": "^1.0.0"
454
+ },
455
+ "funding": {
456
+ "url": "https://github.com/fb55/nth-check?sponsor=1"
457
+ }
458
+ },
247
459
  "node_modules/parse5": {
248
460
  "version": "7.3.0",
249
461
  "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
@@ -256,6 +468,31 @@
256
468
  "url": "https://github.com/inikulin/parse5?sponsor=1"
257
469
  }
258
470
  },
471
+ "node_modules/parse5-htmlparser2-tree-adapter": {
472
+ "version": "7.1.0",
473
+ "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz",
474
+ "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==",
475
+ "license": "MIT",
476
+ "dependencies": {
477
+ "domhandler": "^5.0.3",
478
+ "parse5": "^7.0.0"
479
+ },
480
+ "funding": {
481
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
482
+ }
483
+ },
484
+ "node_modules/parse5-parser-stream": {
485
+ "version": "7.1.2",
486
+ "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz",
487
+ "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==",
488
+ "license": "MIT",
489
+ "dependencies": {
490
+ "parse5": "^7.0.0"
491
+ },
492
+ "funding": {
493
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
494
+ }
495
+ },
259
496
  "node_modules/playwright": {
260
497
  "version": "1.62.1",
261
498
  "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.62.1.tgz",
@@ -303,6 +540,12 @@
303
540
  "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
304
541
  }
305
542
  },
543
+ "node_modules/safer-buffer": {
544
+ "version": "2.1.2",
545
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
546
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
547
+ "license": "MIT"
548
+ },
306
549
  "node_modules/tapable": {
307
550
  "version": "2.3.3",
308
551
  "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz",
@@ -316,6 +559,15 @@
316
559
  "url": "https://opencollective.com/webpack"
317
560
  }
318
561
  },
562
+ "node_modules/undici": {
563
+ "version": "7.29.0",
564
+ "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz",
565
+ "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==",
566
+ "license": "MIT",
567
+ "engines": {
568
+ "node": ">=20.18.1"
569
+ }
570
+ },
319
571
  "node_modules/web-streams-polyfill": {
320
572
  "version": "3.3.3",
321
573
  "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
@@ -324,6 +576,28 @@
324
576
  "engines": {
325
577
  "node": ">= 8"
326
578
  }
579
+ },
580
+ "node_modules/whatwg-encoding": {
581
+ "version": "3.1.1",
582
+ "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
583
+ "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
584
+ "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation",
585
+ "license": "MIT",
586
+ "dependencies": {
587
+ "iconv-lite": "0.6.3"
588
+ },
589
+ "engines": {
590
+ "node": ">=18"
591
+ }
592
+ },
593
+ "node_modules/whatwg-mimetype": {
594
+ "version": "4.0.0",
595
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
596
+ "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
597
+ "license": "MIT",
598
+ "engines": {
599
+ "node": ">=18"
600
+ }
327
601
  }
328
602
  },
329
603
  "dependencies": {
@@ -380,11 +654,113 @@
380
654
  "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
381
655
  "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="
382
656
  },
657
+ "boolbase": {
658
+ "version": "1.0.0",
659
+ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
660
+ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="
661
+ },
662
+ "cheerio": {
663
+ "version": "1.2.0",
664
+ "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz",
665
+ "integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==",
666
+ "requires": {
667
+ "cheerio-select": "^2.1.0",
668
+ "dom-serializer": "^2.0.0",
669
+ "domhandler": "^5.0.3",
670
+ "domutils": "^3.2.2",
671
+ "encoding-sniffer": "^0.2.1",
672
+ "htmlparser2": "^10.1.0",
673
+ "parse5": "^7.3.0",
674
+ "parse5-htmlparser2-tree-adapter": "^7.1.0",
675
+ "parse5-parser-stream": "^7.1.2",
676
+ "undici": "^7.19.0",
677
+ "whatwg-mimetype": "^4.0.0"
678
+ }
679
+ },
680
+ "cheerio-select": {
681
+ "version": "2.1.0",
682
+ "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz",
683
+ "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==",
684
+ "requires": {
685
+ "boolbase": "^1.0.0",
686
+ "css-select": "^5.1.0",
687
+ "css-what": "^6.1.0",
688
+ "domelementtype": "^2.3.0",
689
+ "domhandler": "^5.0.3",
690
+ "domutils": "^3.0.1"
691
+ }
692
+ },
693
+ "css-select": {
694
+ "version": "5.2.2",
695
+ "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
696
+ "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==",
697
+ "requires": {
698
+ "boolbase": "^1.0.0",
699
+ "css-what": "^6.1.0",
700
+ "domhandler": "^5.0.2",
701
+ "domutils": "^3.0.1",
702
+ "nth-check": "^2.0.1"
703
+ }
704
+ },
705
+ "css-what": {
706
+ "version": "6.2.2",
707
+ "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz",
708
+ "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="
709
+ },
383
710
  "data-uri-to-buffer": {
384
711
  "version": "4.0.1",
385
712
  "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
386
713
  "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="
387
714
  },
715
+ "dom-serializer": {
716
+ "version": "2.0.0",
717
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
718
+ "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
719
+ "requires": {
720
+ "domelementtype": "^2.3.0",
721
+ "domhandler": "^5.0.2",
722
+ "entities": "^4.2.0"
723
+ },
724
+ "dependencies": {
725
+ "entities": {
726
+ "version": "4.5.0",
727
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
728
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="
729
+ }
730
+ }
731
+ },
732
+ "domelementtype": {
733
+ "version": "2.3.0",
734
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
735
+ "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="
736
+ },
737
+ "domhandler": {
738
+ "version": "5.0.3",
739
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
740
+ "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
741
+ "requires": {
742
+ "domelementtype": "^2.3.0"
743
+ }
744
+ },
745
+ "domutils": {
746
+ "version": "3.2.2",
747
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
748
+ "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
749
+ "requires": {
750
+ "dom-serializer": "^2.0.0",
751
+ "domelementtype": "^2.3.0",
752
+ "domhandler": "^5.0.3"
753
+ }
754
+ },
755
+ "encoding-sniffer": {
756
+ "version": "0.2.1",
757
+ "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz",
758
+ "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==",
759
+ "requires": {
760
+ "iconv-lite": "^0.6.3",
761
+ "whatwg-encoding": "^3.1.1"
762
+ }
763
+ },
388
764
  "enhanced-resolve": {
389
765
  "version": "5.24.5",
390
766
  "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz",
@@ -421,6 +797,32 @@
421
797
  "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
422
798
  "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
423
799
  },
800
+ "htmlparser2": {
801
+ "version": "10.1.0",
802
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz",
803
+ "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==",
804
+ "requires": {
805
+ "domelementtype": "^2.3.0",
806
+ "domhandler": "^5.0.3",
807
+ "domutils": "^3.2.2",
808
+ "entities": "^7.0.1"
809
+ },
810
+ "dependencies": {
811
+ "entities": {
812
+ "version": "7.0.1",
813
+ "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
814
+ "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="
815
+ }
816
+ }
817
+ },
818
+ "iconv-lite": {
819
+ "version": "0.6.3",
820
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
821
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
822
+ "requires": {
823
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
824
+ }
825
+ },
424
826
  "lit": {
425
827
  "version": "3.3.3",
426
828
  "resolved": "https://registry.npmjs.org/lit/-/lit-3.3.3.tgz",
@@ -469,6 +871,14 @@
469
871
  "formdata-polyfill": "^4.0.10"
470
872
  }
471
873
  },
874
+ "nth-check": {
875
+ "version": "2.1.1",
876
+ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
877
+ "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
878
+ "requires": {
879
+ "boolbase": "^1.0.0"
880
+ }
881
+ },
472
882
  "parse5": {
473
883
  "version": "7.3.0",
474
884
  "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
@@ -477,6 +887,23 @@
477
887
  "entities": "^6.0.0"
478
888
  }
479
889
  },
890
+ "parse5-htmlparser2-tree-adapter": {
891
+ "version": "7.1.0",
892
+ "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz",
893
+ "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==",
894
+ "requires": {
895
+ "domhandler": "^5.0.3",
896
+ "parse5": "^7.0.0"
897
+ }
898
+ },
899
+ "parse5-parser-stream": {
900
+ "version": "7.1.2",
901
+ "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz",
902
+ "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==",
903
+ "requires": {
904
+ "parse5": "^7.0.0"
905
+ }
906
+ },
480
907
  "playwright": {
481
908
  "version": "1.62.1",
482
909
  "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.62.1.tgz",
@@ -502,15 +929,38 @@
502
929
  "integrity": "sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==",
503
930
  "dev": true
504
931
  },
932
+ "safer-buffer": {
933
+ "version": "2.1.2",
934
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
935
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
936
+ },
505
937
  "tapable": {
506
938
  "version": "2.3.3",
507
939
  "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz",
508
940
  "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="
509
941
  },
942
+ "undici": {
943
+ "version": "7.29.0",
944
+ "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz",
945
+ "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw=="
946
+ },
510
947
  "web-streams-polyfill": {
511
948
  "version": "3.3.3",
512
949
  "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
513
950
  "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="
951
+ },
952
+ "whatwg-encoding": {
953
+ "version": "3.1.1",
954
+ "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
955
+ "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
956
+ "requires": {
957
+ "iconv-lite": "0.6.3"
958
+ }
959
+ },
960
+ "whatwg-mimetype": {
961
+ "version": "4.0.0",
962
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
963
+ "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg=="
514
964
  }
515
965
  }
516
966
  }
package.json CHANGED
@@ -41,6 +41,7 @@
41
41
  "dependencies": {
42
42
  "@lit-labs/ssr": "^4.1.0",
43
43
  "@lit-labs/ssr-client": "^1.1.8",
44
+ "cheerio": "^1.2.0",
44
45
  "lit-html": "^3.3.3",
45
46
  "mutative": "^1.3.0"
46
47
  }