perf-web-components 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,88 @@
1
+ # perf-web-components
2
+
3
+ High-performance, framework-agnostic web components built with native Custom Elements.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install perf-web-components
9
+ ```
10
+
11
+ ```js
12
+ import 'perf-web-components';
13
+ ```
14
+
15
+ ## Components
16
+
17
+ ### `<virtual-list>`
18
+
19
+ A virtualized list/grid that only renders the DOM nodes needed to fill the viewport, regardless of how many items are in the dataset. Useful for rendering large collections (thousands+ of rows) without the performance cost of mounting every item.
20
+
21
+ #### Attributes / Properties
22
+
23
+ | Attribute | Property | Type | Default | Description |
24
+ |------------------------|----------------------|-----------------------------|--------------|---------------------------------------------------------------------------|
25
+ | `max-nodes` | `maxNodes` | `number` | `20` | Size of the reused DOM node pool (number of items rendered at once). |
26
+ | `estimated-item-size` | `estimatedItemSize` | `number` | `50` | Estimated height (vertical) or width (horizontal) of each item, in px. |
27
+ | `scroll-direction` | `scrollDirection` | `'vertical' \| 'horizontal'`| `'vertical'` | Scroll axis for the list. |
28
+ | — | `items` | `T[]` | `[]` | The data array to render. Must be set via the JS property, not an attribute. |
29
+
30
+ #### Item templates
31
+
32
+ Provide a `<template>` child to control markup per item. Use `{{ }}` interpolation to bind fields:
33
+
34
+ ```html
35
+ <virtual-list max-nodes="10" estimated-item-size="60">
36
+ <template>
37
+ <div class="row">{{ index }}: {{ name }}</div>
38
+ </template>
39
+ </virtual-list>
40
+
41
+ <script type="module">
42
+ const list = document.querySelector('virtual-list');
43
+ list.items = [{ name: 'Item 1' }, { name: 'Item 2' } /* ... */];
44
+ </script>
45
+ ```
46
+
47
+ If no template is provided, each item is rendered as `JSON.stringify(item)` (or `String(item)` for primitives).
48
+
49
+ #### Framework Bindings (Angular, React, Vue, Lit, Svelte)
50
+
51
+ Modern frameworks bind directly to the Web Component's JS property, not just its attributes. `items` must always be set as a property since arrays cannot be passed through HTML attributes.
52
+
53
+ **Angular**
54
+ ```html
55
+ <virtual-list [items]="itemArray" max-nodes="5" estimated-item-size="60"></virtual-list>
56
+ ```
57
+
58
+ **Vue**
59
+ ```html
60
+ <virtual-list :items="itemArray" max-nodes="5" estimated-item-size="60"></virtual-list>
61
+ ```
62
+
63
+ **React**
64
+ ```jsx
65
+ <virtual-list
66
+ ref={el => el && (el.items = itemArray)}
67
+ max-nodes="5"
68
+ estimated-item-size="60"
69
+ />
70
+ ```
71
+
72
+ **Lit**
73
+ ```html
74
+ <virtual-list .items=${itemArray} max-nodes="5" estimated-item-size="60"></virtual-list>
75
+ ```
76
+
77
+ **Svelte**
78
+ ```svelte
79
+ <virtual-list this={el} max-nodes="5" estimated-item-size="60" use:setItems={itemArray}></virtual-list>
80
+ ```
81
+
82
+ ## Development
83
+
84
+ ```bash
85
+ npm run dev # start vite dev server
86
+ npm run build # type-check and build the library
87
+ npm run preview # preview the production build
88
+ ```
@@ -0,0 +1,46 @@
1
+ export type ScrollDirection = 'vertical' | 'horizontal';
2
+ export interface VirtualListOptions {
3
+ maxNodes?: number;
4
+ estimatedItemSize?: number;
5
+ scrollDirection?: ScrollDirection;
6
+ }
7
+ export declare class VirtualList<T = Record<string, unknown>> extends HTMLElement {
8
+ private _items;
9
+ private _maxNodes;
10
+ private _scrollDirection;
11
+ private _nodePool;
12
+ private _estimatedItemSize;
13
+ private _userTemplate;
14
+ private _pendingFrame;
15
+ private $viewport;
16
+ private $phantom;
17
+ private $content;
18
+ private $slot;
19
+ constructor();
20
+ static get observedAttributes(): string[];
21
+ attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
22
+ connectedCallback(): void;
23
+ disconnectedCallback(): void;
24
+ private _onSlotChange;
25
+ private _getTemplate;
26
+ set items(data: T[]);
27
+ get items(): T[];
28
+ set maxNodes(val: number);
29
+ get maxNodes(): number;
30
+ set estimatedItemSize(val: number);
31
+ get estimatedItemSize(): number;
32
+ set scrollDirection(val: ScrollDirection);
33
+ get scrollDirection(): ScrollDirection;
34
+ private _buildNodePool;
35
+ private _updateLayout;
36
+ private _onScroll;
37
+ private _render;
38
+ private _interpolate;
39
+ private _escapeHtml;
40
+ }
41
+ declare global {
42
+ interface HTMLElementTagNameMap {
43
+ 'virtual-list': VirtualList;
44
+ }
45
+ }
46
+ //# sourceMappingURL=virtual-list.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"virtual-list.d.ts","sourceRoot":"","sources":["../../../src/components/virtual-list/virtual-list.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,YAAY,CAAC;AAExD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,eAAe,CAAC;CACnC;AAED,qBAAa,WAAW,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAE,SAAQ,WAAW;IACvE,OAAO,CAAC,MAAM,CAAW;IACzB,OAAO,CAAC,SAAS,CAAc;IAC/B,OAAO,CAAC,gBAAgB,CAA+B;IACvD,OAAO,CAAC,SAAS,CAAwB;IACzC,OAAO,CAAC,kBAAkB,CAAc;IACxC,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,aAAa,CAAuB;IAE5C,OAAO,CAAC,SAAS,CAAiB;IAClC,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,KAAK,CAAyB;;IA8DtC,MAAM,KAAK,kBAAkB,IAAI,MAAM,EAAE,CAExC;IAED,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAiB9F,iBAAiB,IAAI,IAAI;IAYzB,oBAAoB,IAAI,IAAI;IAW5B,OAAO,CAAC,aAAa;IAMrB,OAAO,CAAC,YAAY;IAUpB,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,EAAE,EAclB;IAED,IAAI,KAAK,IAAI,CAAC,EAAE,CAEf;IAED,IAAI,QAAQ,CAAC,GAAG,EAAE,MAAM,EAEvB;IAED,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,IAAI,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAEhC;IAED,IAAI,iBAAiB,IAAI,MAAM,CAE9B;IAED,IAAI,eAAe,CAAC,GAAG,EAAE,eAAe,EAEvC;IAED,IAAI,eAAe,IAAI,eAAe,CAErC;IAED,OAAO,CAAC,cAAc;IActB,OAAO,CAAC,aAAa;IAgBrB,OAAO,CAAC,SAAS;IAQjB,OAAO,CAAC,OAAO;IA2Cf,OAAO,CAAC,YAAY;IAcpB,OAAO,CAAC,WAAW;CAQpB;AAOD,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,cAAc,EAAE,WAAW,CAAC;KAC7B;CACF"}
@@ -0,0 +1,3 @@
1
+ export { VirtualList } from './components/virtual-list/virtual-list.js';
2
+ export type { ScrollDirection, VirtualListOptions } from './components/virtual-list/virtual-list.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,2CAA2C,CAAC;AACxE,YAAY,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,2CAA2C,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,138 @@
1
+ //#region src/components/virtual-list/virtual-list.js
2
+ var e = class extends HTMLElement {
3
+ _items = [];
4
+ _maxNodes = 20;
5
+ _scrollDirection = "vertical";
6
+ _nodePool = [];
7
+ _estimatedItemSize = 50;
8
+ _userTemplate = null;
9
+ _pendingFrame = null;
10
+ $viewport;
11
+ $phantom;
12
+ $content;
13
+ $slot;
14
+ constructor() {
15
+ if (super(), this.attachShadow({ mode: "open" }), !this.shadowRoot) throw Error("Shadow root initialization failed");
16
+ this.shadowRoot.innerHTML = "\n <style>\n :host {\n display: block;\n position: relative;\n contain: strict;\n }\n .viewport {\n width: 100%;\n height: 100%;\n overflow: auto;\n position: relative;\n -webkit-overflow-scrolling: touch;\n }\n .phantom {\n position: absolute;\n top: 0;\n left: 0;\n pointer-events: none;\n }\n .content {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n will-change: transform;\n }\n .content.horizontal {\n display: flex;\n flex-direction: row;\n width: max-content;\n height: 100%;\n }\n </style>\n\n <div class=\"viewport\" id=\"viewport\">\n <div class=\"phantom\" id=\"phantom\"></div>\n <div class=\"content\" id=\"content\"></div>\n <slot id=\"slot\" style=\"display: none;\"></slot>\n </div>\n ", this.$viewport = this.shadowRoot.getElementById("viewport"), this.$phantom = this.shadowRoot.getElementById("phantom"), this.$content = this.shadowRoot.getElementById("content"), this.$slot = this.shadowRoot.getElementById("slot"), this._onScroll = this._onScroll.bind(this), this._onSlotChange = this._onSlotChange.bind(this);
17
+ }
18
+ static get observedAttributes() {
19
+ return [
20
+ "max-nodes",
21
+ "scroll-direction",
22
+ "estimated-item-size"
23
+ ];
24
+ }
25
+ attributeChangedCallback(e, t, n) {
26
+ if (t !== n) {
27
+ if (e === "max-nodes") {
28
+ let e = parseInt(n ?? "", 10);
29
+ this._maxNodes = Number.isNaN(e) ? 20 : e, this._buildNodePool();
30
+ } else if (e === "scroll-direction") this._scrollDirection = n === "horizontal" ? "horizontal" : "vertical";
31
+ else if (e === "estimated-item-size") {
32
+ let e = parseFloat(n ?? "");
33
+ this._estimatedItemSize = Number.isNaN(e) ? 50 : e;
34
+ }
35
+ this._updateLayout(), this._render();
36
+ }
37
+ }
38
+ connectedCallback() {
39
+ this.$viewport.addEventListener("scroll", this._onScroll, { passive: !0 }), this.$slot && this.$slot.addEventListener("slotchange", this._onSlotChange), this._getTemplate(), this._buildNodePool(), this._updateLayout(), this._render();
40
+ }
41
+ disconnectedCallback() {
42
+ this.$viewport.removeEventListener("scroll", this._onScroll), this.$slot && this.$slot.removeEventListener("slotchange", this._onSlotChange), this._pendingFrame !== null && (cancelAnimationFrame(this._pendingFrame), this._pendingFrame = null);
43
+ }
44
+ _onSlotChange() {
45
+ this._userTemplate = null, this._getTemplate(), this._render();
46
+ }
47
+ _getTemplate() {
48
+ if (!this._userTemplate) {
49
+ let e = this.querySelector("template");
50
+ e && (this._userTemplate = e.innerHTML);
51
+ }
52
+ return this._userTemplate;
53
+ }
54
+ set items(e) {
55
+ let t = Array.isArray(e) ? e : [];
56
+ this._items = new Proxy(t, { set: (e, t, n, r) => {
57
+ let i = Reflect.set(e, t, n, r);
58
+ return this._updateLayout(), this._render(), i;
59
+ } }), this._updateLayout(), this._render();
60
+ }
61
+ get items() {
62
+ return this._items;
63
+ }
64
+ set maxNodes(e) {
65
+ this.setAttribute("max-nodes", String(e));
66
+ }
67
+ get maxNodes() {
68
+ return this._maxNodes;
69
+ }
70
+ set estimatedItemSize(e) {
71
+ this.setAttribute("estimated-item-size", String(e));
72
+ }
73
+ get estimatedItemSize() {
74
+ return this._estimatedItemSize;
75
+ }
76
+ set scrollDirection(e) {
77
+ this.setAttribute("scroll-direction", e);
78
+ }
79
+ get scrollDirection() {
80
+ return this._scrollDirection;
81
+ }
82
+ _buildNodePool() {
83
+ if (!this.$content) return;
84
+ this.$content.innerHTML = "", this._nodePool = [];
85
+ let e = document.createDocumentFragment();
86
+ for (let t = 0; t < this._maxNodes; t++) {
87
+ let t = document.createElement("div");
88
+ this._nodePool.push(t), e.appendChild(t);
89
+ }
90
+ this.$content.appendChild(e);
91
+ }
92
+ _updateLayout() {
93
+ if (!this.$phantom || !this.$content) return;
94
+ let e = this._scrollDirection === "horizontal", t = this._items.length * this._estimatedItemSize;
95
+ e ? (this.$phantom.style.width = `${t}px`, this.$phantom.style.height = "100%", this.$content.classList.add("horizontal")) : (this.$phantom.style.height = `${t}px`, this.$phantom.style.width = "100%", this.$content.classList.remove("horizontal"));
96
+ }
97
+ _onScroll() {
98
+ this._pendingFrame === null && (this._pendingFrame = requestAnimationFrame(() => {
99
+ this._pendingFrame = null, this._render();
100
+ }));
101
+ }
102
+ _render() {
103
+ if (!this.$viewport || !this._nodePool.length) return;
104
+ if (!this._items.length) {
105
+ for (let e of this._nodePool) e.style.display = "none";
106
+ return;
107
+ }
108
+ let e = this._scrollDirection === "horizontal", t = e ? this.$viewport.scrollLeft : this.$viewport.scrollTop, n = Math.floor(t / this._estimatedItemSize), r = Math.max(0, this._items.length - this._maxNodes);
109
+ n = Math.min(Math.max(0, n), r);
110
+ let i = n * this._estimatedItemSize;
111
+ this.$content.style.transform = e ? `translate3d(${i}px, 0, 0)` : `translate3d(0, ${i}px, 0)`;
112
+ let a = this._getTemplate();
113
+ for (let e = 0; e < this._maxNodes; e++) {
114
+ let t = n + e, r = this._nodePool[e];
115
+ if (t < this._items.length) {
116
+ r.style.display = "";
117
+ let e = this._items[t];
118
+ a ? r.innerHTML = this._interpolate(a, e, t) : r.textContent = typeof e == "object" ? JSON.stringify(e) : String(e);
119
+ } else r.style.display = "none";
120
+ }
121
+ }
122
+ _interpolate(e, t, n) {
123
+ return e.replace(/\{\{\s*([\w.]+)\s*\}\}/g, (e, r) => {
124
+ if (r === "index") return String(n);
125
+ if (r === "item") return this._escapeHtml(typeof t == "object" && t ? JSON.stringify(t) : String(t));
126
+ let i = (r.startsWith("item.") ? r.slice(5) : r).split(".").reduce((e, t) => e && e[t] !== void 0 ? e[t] : void 0, t);
127
+ return i === void 0 ? "" : this._escapeHtml(String(i));
128
+ });
129
+ }
130
+ _escapeHtml(e) {
131
+ return e.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
132
+ }
133
+ };
134
+ typeof window < "u" && !customElements.get("virtual-list") && customElements.define("virtual-list", e);
135
+ //#endregion
136
+ export { e as VirtualList };
137
+
138
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/components/virtual-list/virtual-list.js"],"sourcesContent":["export class VirtualList extends HTMLElement {\n _items = [];\n _maxNodes = 20;\n _scrollDirection = 'vertical';\n _nodePool = [];\n _estimatedItemSize = 50;\n _userTemplate = null;\n _pendingFrame = null;\n $viewport;\n $phantom;\n $content;\n $slot;\n constructor() {\n super();\n this.attachShadow({ mode: 'open' });\n if (!this.shadowRoot) {\n throw new Error('Shadow root initialization failed');\n }\n this.shadowRoot.innerHTML = `\n <style>\n :host {\n display: block;\n position: relative;\n contain: strict;\n }\n .viewport {\n width: 100%;\n height: 100%;\n overflow: auto;\n position: relative;\n -webkit-overflow-scrolling: touch;\n }\n .phantom {\n position: absolute;\n top: 0;\n left: 0;\n pointer-events: none;\n }\n .content {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n will-change: transform;\n }\n .content.horizontal {\n display: flex;\n flex-direction: row;\n width: max-content;\n height: 100%;\n }\n </style>\n\n <div class=\"viewport\" id=\"viewport\">\n <div class=\"phantom\" id=\"phantom\"></div>\n <div class=\"content\" id=\"content\"></div>\n <slot id=\"slot\" style=\"display: none;\"></slot>\n </div>\n `;\n this.$viewport = this.shadowRoot.getElementById('viewport');\n this.$phantom = this.shadowRoot.getElementById('phantom');\n this.$content = this.shadowRoot.getElementById('content');\n this.$slot = this.shadowRoot.getElementById('slot');\n this._onScroll = this._onScroll.bind(this);\n this._onSlotChange = this._onSlotChange.bind(this);\n }\n static get observedAttributes() {\n return ['max-nodes', 'scroll-direction', 'estimated-item-size'];\n }\n attributeChangedCallback(name, oldValue, newValue) {\n if (oldValue === newValue)\n return;\n if (name === 'max-nodes') {\n const parsed = parseInt(newValue ?? '', 10);\n this._maxNodes = Number.isNaN(parsed) ? 20 : parsed;\n this._buildNodePool();\n }\n else if (name === 'scroll-direction') {\n this._scrollDirection = newValue === 'horizontal' ? 'horizontal' : 'vertical';\n }\n else if (name === 'estimated-item-size') {\n const parsed = parseFloat(newValue ?? '');\n this._estimatedItemSize = Number.isNaN(parsed) ? 50 : parsed;\n }\n this._updateLayout();\n this._render();\n }\n connectedCallback() {\n this.$viewport.addEventListener('scroll', this._onScroll, { passive: true });\n if (this.$slot) {\n this.$slot.addEventListener('slotchange', this._onSlotChange);\n }\n this._getTemplate();\n this._buildNodePool();\n this._updateLayout();\n this._render();\n }\n disconnectedCallback() {\n this.$viewport.removeEventListener('scroll', this._onScroll);\n if (this.$slot) {\n this.$slot.removeEventListener('slotchange', this._onSlotChange);\n }\n if (this._pendingFrame !== null) {\n cancelAnimationFrame(this._pendingFrame);\n this._pendingFrame = null;\n }\n }\n _onSlotChange() {\n this._userTemplate = null;\n this._getTemplate();\n this._render();\n }\n _getTemplate() {\n if (!this._userTemplate) {\n const templateTag = this.querySelector('template');\n if (templateTag) {\n this._userTemplate = templateTag.innerHTML;\n }\n }\n return this._userTemplate;\n }\n set items(data) {\n const list = Array.isArray(data) ? data : [];\n this._items = new Proxy(list, {\n set: (target, property, value, receiver) => {\n const success = Reflect.set(target, property, value, receiver);\n this._updateLayout();\n this._render();\n return success;\n }\n });\n this._updateLayout();\n this._render();\n }\n get items() {\n return this._items;\n }\n set maxNodes(val) {\n this.setAttribute('max-nodes', String(val));\n }\n get maxNodes() {\n return this._maxNodes;\n }\n set estimatedItemSize(val) {\n this.setAttribute('estimated-item-size', String(val));\n }\n get estimatedItemSize() {\n return this._estimatedItemSize;\n }\n set scrollDirection(val) {\n this.setAttribute('scroll-direction', val);\n }\n get scrollDirection() {\n return this._scrollDirection;\n }\n _buildNodePool() {\n if (!this.$content)\n return;\n this.$content.innerHTML = '';\n this._nodePool = [];\n const fragment = document.createDocumentFragment();\n for (let i = 0; i < this._maxNodes; i++) {\n const nodeWrapper = document.createElement('div');\n this._nodePool.push(nodeWrapper);\n fragment.appendChild(nodeWrapper);\n }\n this.$content.appendChild(fragment);\n }\n _updateLayout() {\n if (!this.$phantom || !this.$content)\n return;\n const isHoriz = this._scrollDirection === 'horizontal';\n const totalSize = this._items.length * this._estimatedItemSize;\n if (isHoriz) {\n this.$phantom.style.width = `${totalSize}px`;\n this.$phantom.style.height = '100%';\n this.$content.classList.add('horizontal');\n }\n else {\n this.$phantom.style.height = `${totalSize}px`;\n this.$phantom.style.width = '100%';\n this.$content.classList.remove('horizontal');\n }\n }\n _onScroll() {\n if (this._pendingFrame !== null)\n return;\n this._pendingFrame = requestAnimationFrame(() => {\n this._pendingFrame = null;\n this._render();\n });\n }\n _render() {\n if (!this.$viewport || !this._nodePool.length)\n return;\n if (!this._items.length) {\n for (const node of this._nodePool) {\n node.style.display = 'none';\n }\n return;\n }\n const isHoriz = this._scrollDirection === 'horizontal';\n const scrollOffset = isHoriz ? this.$viewport.scrollLeft : this.$viewport.scrollTop;\n let startIndex = Math.floor(scrollOffset / this._estimatedItemSize);\n const maxStartIndex = Math.max(0, this._items.length - this._maxNodes);\n startIndex = Math.min(Math.max(0, startIndex), maxStartIndex);\n const offset = startIndex * this._estimatedItemSize;\n this.$content.style.transform = isHoriz\n ? `translate3d(${offset}px, 0, 0)`\n : `translate3d(0, ${offset}px, 0)`;\n const userTemplate = this._getTemplate();\n for (let i = 0; i < this._maxNodes; i++) {\n const dataIndex = startIndex + i;\n const node = this._nodePool[i];\n if (dataIndex < this._items.length) {\n node.style.display = '';\n const itemData = this._items[dataIndex];\n if (userTemplate) {\n node.innerHTML = this._interpolate(userTemplate, itemData, dataIndex);\n }\n else {\n node.textContent = typeof itemData === 'object' ? JSON.stringify(itemData) : String(itemData);\n }\n }\n else {\n node.style.display = 'none';\n }\n }\n }\n _interpolate(templateStr, item, index) {\n return templateStr.replace(/\\{\\{\\s*([\\w.]+)\\s*\\}\\}/g, (_, key) => {\n if (key === 'index')\n return String(index);\n if (key === 'item') {\n return this._escapeHtml(typeof item === 'object' && item !== null ? JSON.stringify(item) : String(item));\n }\n const path = key.startsWith('item.') ? key.slice(5) : key;\n const val = path.split('.').reduce((obj, prop) => (obj && obj[prop] !== undefined ? obj[prop] : undefined), item);\n return val !== undefined ? this._escapeHtml(String(val)) : '';\n });\n }\n _escapeHtml(value) {\n return value\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;');\n }\n}\n// Auto-register custom element if window is available\nif (typeof window !== 'undefined' && !customElements.get('virtual-list')) {\n customElements.define('virtual-list', VirtualList);\n}\n//# sourceMappingURL=virtual-list.js.map"],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports):typeof define==`function`&&define.amd?define([`exports`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.PerfWebComponents={}))})(this,function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=class extends HTMLElement{_items=[];_maxNodes=20;_scrollDirection=`vertical`;_nodePool=[];_estimatedItemSize=50;_userTemplate=null;_pendingFrame=null;$viewport;$phantom;$content;$slot;constructor(){if(super(),this.attachShadow({mode:`open`}),!this.shadowRoot)throw Error(`Shadow root initialization failed`);this.shadowRoot.innerHTML=`
2
+ <style>
3
+ :host {
4
+ display: block;
5
+ position: relative;
6
+ contain: strict;
7
+ }
8
+ .viewport {
9
+ width: 100%;
10
+ height: 100%;
11
+ overflow: auto;
12
+ position: relative;
13
+ -webkit-overflow-scrolling: touch;
14
+ }
15
+ .phantom {
16
+ position: absolute;
17
+ top: 0;
18
+ left: 0;
19
+ pointer-events: none;
20
+ }
21
+ .content {
22
+ position: absolute;
23
+ top: 0;
24
+ left: 0;
25
+ width: 100%;
26
+ height: 100%;
27
+ will-change: transform;
28
+ }
29
+ .content.horizontal {
30
+ display: flex;
31
+ flex-direction: row;
32
+ width: max-content;
33
+ height: 100%;
34
+ }
35
+ </style>
36
+
37
+ <div class="viewport" id="viewport">
38
+ <div class="phantom" id="phantom"></div>
39
+ <div class="content" id="content"></div>
40
+ <slot id="slot" style="display: none;"></slot>
41
+ </div>
42
+ `,this.$viewport=this.shadowRoot.getElementById(`viewport`),this.$phantom=this.shadowRoot.getElementById(`phantom`),this.$content=this.shadowRoot.getElementById(`content`),this.$slot=this.shadowRoot.getElementById(`slot`),this._onScroll=this._onScroll.bind(this),this._onSlotChange=this._onSlotChange.bind(this)}static get observedAttributes(){return[`max-nodes`,`scroll-direction`,`estimated-item-size`]}attributeChangedCallback(e,t,n){if(t!==n){if(e===`max-nodes`){let e=parseInt(n??``,10);this._maxNodes=Number.isNaN(e)?20:e,this._buildNodePool()}else if(e===`scroll-direction`)this._scrollDirection=n===`horizontal`?`horizontal`:`vertical`;else if(e===`estimated-item-size`){let e=parseFloat(n??``);this._estimatedItemSize=Number.isNaN(e)?50:e}this._updateLayout(),this._render()}}connectedCallback(){this.$viewport.addEventListener(`scroll`,this._onScroll,{passive:!0}),this.$slot&&this.$slot.addEventListener(`slotchange`,this._onSlotChange),this._getTemplate(),this._buildNodePool(),this._updateLayout(),this._render()}disconnectedCallback(){this.$viewport.removeEventListener(`scroll`,this._onScroll),this.$slot&&this.$slot.removeEventListener(`slotchange`,this._onSlotChange),this._pendingFrame!==null&&(cancelAnimationFrame(this._pendingFrame),this._pendingFrame=null)}_onSlotChange(){this._userTemplate=null,this._getTemplate(),this._render()}_getTemplate(){if(!this._userTemplate){let e=this.querySelector(`template`);e&&(this._userTemplate=e.innerHTML)}return this._userTemplate}set items(e){let t=Array.isArray(e)?e:[];this._items=new Proxy(t,{set:(e,t,n,r)=>{let i=Reflect.set(e,t,n,r);return this._updateLayout(),this._render(),i}}),this._updateLayout(),this._render()}get items(){return this._items}set maxNodes(e){this.setAttribute(`max-nodes`,String(e))}get maxNodes(){return this._maxNodes}set estimatedItemSize(e){this.setAttribute(`estimated-item-size`,String(e))}get estimatedItemSize(){return this._estimatedItemSize}set scrollDirection(e){this.setAttribute(`scroll-direction`,e)}get scrollDirection(){return this._scrollDirection}_buildNodePool(){if(!this.$content)return;this.$content.innerHTML=``,this._nodePool=[];let e=document.createDocumentFragment();for(let t=0;t<this._maxNodes;t++){let t=document.createElement(`div`);this._nodePool.push(t),e.appendChild(t)}this.$content.appendChild(e)}_updateLayout(){if(!this.$phantom||!this.$content)return;let e=this._scrollDirection===`horizontal`,t=this._items.length*this._estimatedItemSize;e?(this.$phantom.style.width=`${t}px`,this.$phantom.style.height=`100%`,this.$content.classList.add(`horizontal`)):(this.$phantom.style.height=`${t}px`,this.$phantom.style.width=`100%`,this.$content.classList.remove(`horizontal`))}_onScroll(){this._pendingFrame===null&&(this._pendingFrame=requestAnimationFrame(()=>{this._pendingFrame=null,this._render()}))}_render(){if(!this.$viewport||!this._nodePool.length)return;if(!this._items.length){for(let e of this._nodePool)e.style.display=`none`;return}let e=this._scrollDirection===`horizontal`,t=e?this.$viewport.scrollLeft:this.$viewport.scrollTop,n=Math.floor(t/this._estimatedItemSize),r=Math.max(0,this._items.length-this._maxNodes);n=Math.min(Math.max(0,n),r);let i=n*this._estimatedItemSize;this.$content.style.transform=e?`translate3d(${i}px, 0, 0)`:`translate3d(0, ${i}px, 0)`;let a=this._getTemplate();for(let e=0;e<this._maxNodes;e++){let t=n+e,r=this._nodePool[e];if(t<this._items.length){r.style.display=``;let e=this._items[t];a?r.innerHTML=this._interpolate(a,e,t):r.textContent=typeof e==`object`?JSON.stringify(e):String(e)}else r.style.display=`none`}}_interpolate(e,t,n){return e.replace(/\{\{\s*([\w.]+)\s*\}\}/g,(e,r)=>{if(r===`index`)return String(n);if(r===`item`)return this._escapeHtml(typeof t==`object`&&t?JSON.stringify(t):String(t));let i=(r.startsWith(`item.`)?r.slice(5):r).split(`.`).reduce((e,t)=>e&&e[t]!==void 0?e[t]:void 0,t);return i===void 0?``:this._escapeHtml(String(i))})}_escapeHtml(e){return e.replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`).replace(/"/g,`&quot;`).replace(/'/g,`&#39;`)}};typeof window<`u`&&!customElements.get(`virtual-list`)&&customElements.define(`virtual-list`,t),e.VirtualList=t});
43
+ //# sourceMappingURL=index.umd.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.umd.cjs","names":[],"sources":["../src/components/virtual-list/virtual-list.js"],"sourcesContent":["export class VirtualList extends HTMLElement {\n _items = [];\n _maxNodes = 20;\n _scrollDirection = 'vertical';\n _nodePool = [];\n _estimatedItemSize = 50;\n _userTemplate = null;\n _pendingFrame = null;\n $viewport;\n $phantom;\n $content;\n $slot;\n constructor() {\n super();\n this.attachShadow({ mode: 'open' });\n if (!this.shadowRoot) {\n throw new Error('Shadow root initialization failed');\n }\n this.shadowRoot.innerHTML = `\n <style>\n :host {\n display: block;\n position: relative;\n contain: strict;\n }\n .viewport {\n width: 100%;\n height: 100%;\n overflow: auto;\n position: relative;\n -webkit-overflow-scrolling: touch;\n }\n .phantom {\n position: absolute;\n top: 0;\n left: 0;\n pointer-events: none;\n }\n .content {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n will-change: transform;\n }\n .content.horizontal {\n display: flex;\n flex-direction: row;\n width: max-content;\n height: 100%;\n }\n </style>\n\n <div class=\"viewport\" id=\"viewport\">\n <div class=\"phantom\" id=\"phantom\"></div>\n <div class=\"content\" id=\"content\"></div>\n <slot id=\"slot\" style=\"display: none;\"></slot>\n </div>\n `;\n this.$viewport = this.shadowRoot.getElementById('viewport');\n this.$phantom = this.shadowRoot.getElementById('phantom');\n this.$content = this.shadowRoot.getElementById('content');\n this.$slot = this.shadowRoot.getElementById('slot');\n this._onScroll = this._onScroll.bind(this);\n this._onSlotChange = this._onSlotChange.bind(this);\n }\n static get observedAttributes() {\n return ['max-nodes', 'scroll-direction', 'estimated-item-size'];\n }\n attributeChangedCallback(name, oldValue, newValue) {\n if (oldValue === newValue)\n return;\n if (name === 'max-nodes') {\n const parsed = parseInt(newValue ?? '', 10);\n this._maxNodes = Number.isNaN(parsed) ? 20 : parsed;\n this._buildNodePool();\n }\n else if (name === 'scroll-direction') {\n this._scrollDirection = newValue === 'horizontal' ? 'horizontal' : 'vertical';\n }\n else if (name === 'estimated-item-size') {\n const parsed = parseFloat(newValue ?? '');\n this._estimatedItemSize = Number.isNaN(parsed) ? 50 : parsed;\n }\n this._updateLayout();\n this._render();\n }\n connectedCallback() {\n this.$viewport.addEventListener('scroll', this._onScroll, { passive: true });\n if (this.$slot) {\n this.$slot.addEventListener('slotchange', this._onSlotChange);\n }\n this._getTemplate();\n this._buildNodePool();\n this._updateLayout();\n this._render();\n }\n disconnectedCallback() {\n this.$viewport.removeEventListener('scroll', this._onScroll);\n if (this.$slot) {\n this.$slot.removeEventListener('slotchange', this._onSlotChange);\n }\n if (this._pendingFrame !== null) {\n cancelAnimationFrame(this._pendingFrame);\n this._pendingFrame = null;\n }\n }\n _onSlotChange() {\n this._userTemplate = null;\n this._getTemplate();\n this._render();\n }\n _getTemplate() {\n if (!this._userTemplate) {\n const templateTag = this.querySelector('template');\n if (templateTag) {\n this._userTemplate = templateTag.innerHTML;\n }\n }\n return this._userTemplate;\n }\n set items(data) {\n const list = Array.isArray(data) ? data : [];\n this._items = new Proxy(list, {\n set: (target, property, value, receiver) => {\n const success = Reflect.set(target, property, value, receiver);\n this._updateLayout();\n this._render();\n return success;\n }\n });\n this._updateLayout();\n this._render();\n }\n get items() {\n return this._items;\n }\n set maxNodes(val) {\n this.setAttribute('max-nodes', String(val));\n }\n get maxNodes() {\n return this._maxNodes;\n }\n set estimatedItemSize(val) {\n this.setAttribute('estimated-item-size', String(val));\n }\n get estimatedItemSize() {\n return this._estimatedItemSize;\n }\n set scrollDirection(val) {\n this.setAttribute('scroll-direction', val);\n }\n get scrollDirection() {\n return this._scrollDirection;\n }\n _buildNodePool() {\n if (!this.$content)\n return;\n this.$content.innerHTML = '';\n this._nodePool = [];\n const fragment = document.createDocumentFragment();\n for (let i = 0; i < this._maxNodes; i++) {\n const nodeWrapper = document.createElement('div');\n this._nodePool.push(nodeWrapper);\n fragment.appendChild(nodeWrapper);\n }\n this.$content.appendChild(fragment);\n }\n _updateLayout() {\n if (!this.$phantom || !this.$content)\n return;\n const isHoriz = this._scrollDirection === 'horizontal';\n const totalSize = this._items.length * this._estimatedItemSize;\n if (isHoriz) {\n this.$phantom.style.width = `${totalSize}px`;\n this.$phantom.style.height = '100%';\n this.$content.classList.add('horizontal');\n }\n else {\n this.$phantom.style.height = `${totalSize}px`;\n this.$phantom.style.width = '100%';\n this.$content.classList.remove('horizontal');\n }\n }\n _onScroll() {\n if (this._pendingFrame !== null)\n return;\n this._pendingFrame = requestAnimationFrame(() => {\n this._pendingFrame = null;\n this._render();\n });\n }\n _render() {\n if (!this.$viewport || !this._nodePool.length)\n return;\n if (!this._items.length) {\n for (const node of this._nodePool) {\n node.style.display = 'none';\n }\n return;\n }\n const isHoriz = this._scrollDirection === 'horizontal';\n const scrollOffset = isHoriz ? this.$viewport.scrollLeft : this.$viewport.scrollTop;\n let startIndex = Math.floor(scrollOffset / this._estimatedItemSize);\n const maxStartIndex = Math.max(0, this._items.length - this._maxNodes);\n startIndex = Math.min(Math.max(0, startIndex), maxStartIndex);\n const offset = startIndex * this._estimatedItemSize;\n this.$content.style.transform = isHoriz\n ? `translate3d(${offset}px, 0, 0)`\n : `translate3d(0, ${offset}px, 0)`;\n const userTemplate = this._getTemplate();\n for (let i = 0; i < this._maxNodes; i++) {\n const dataIndex = startIndex + i;\n const node = this._nodePool[i];\n if (dataIndex < this._items.length) {\n node.style.display = '';\n const itemData = this._items[dataIndex];\n if (userTemplate) {\n node.innerHTML = this._interpolate(userTemplate, itemData, dataIndex);\n }\n else {\n node.textContent = typeof itemData === 'object' ? JSON.stringify(itemData) : String(itemData);\n }\n }\n else {\n node.style.display = 'none';\n }\n }\n }\n _interpolate(templateStr, item, index) {\n return templateStr.replace(/\\{\\{\\s*([\\w.]+)\\s*\\}\\}/g, (_, key) => {\n if (key === 'index')\n return String(index);\n if (key === 'item') {\n return this._escapeHtml(typeof item === 'object' && item !== null ? JSON.stringify(item) : String(item));\n }\n const path = key.startsWith('item.') ? key.slice(5) : key;\n const val = path.split('.').reduce((obj, prop) => (obj && obj[prop] !== undefined ? obj[prop] : undefined), item);\n return val !== undefined ? this._escapeHtml(String(val)) : '';\n });\n }\n _escapeHtml(value) {\n return value\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;');\n }\n}\n// Auto-register custom element if window is available\nif (typeof window !== 'undefined' && !customElements.get('virtual-list')) {\n customElements.define('virtual-list', VirtualList);\n}\n//# sourceMappingURL=virtual-list.js.map"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "perf-web-components",
3
+ "version": "0.1.0",
4
+ "description": "High-performance framework-agnostic web components",
5
+ "type": "module",
6
+ "main": "./dist/index.umd.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.umd.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "dev": "vite",
21
+ "build": "tsc && vite build",
22
+ "preview": "vite preview"
23
+ },
24
+ "devDependencies": {
25
+ "typescript": "^5.3.3",
26
+ "vite": "^8.3.0",
27
+ "vite-plugin-dts": "^3.7.2"
28
+ },
29
+ "keywords": [
30
+ "web-components",
31
+ "custom-elements",
32
+ "virtual-scroll",
33
+ "virtualization",
34
+ "performance",
35
+ "typescript"
36
+ ],
37
+ "author": "Santosh Kadam",
38
+ "license": "MIT"
39
+ }