@ceriousdevtech/vue-cerious-scroll 1.0.1
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/CHANGELOG.md +27 -0
- package/LICENSE +21 -0
- package/README.md +166 -0
- package/dist/cerious-scroll.d.ts +102 -0
- package/dist/content-element.d.ts +18 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +189 -0
- package/dist/use-cerious-scroll.d.ts +61 -0
- package/dist/viewport-change.d.ts +25 -0
- package/package.json +77 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to vue-cerious-scroll will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.0.1] - 2026-06-01
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- Verified compatibility with `@ceriousdevtech/cerious-scroll` 1.0.2
|
|
12
|
+
|
|
13
|
+
### Dependencies
|
|
14
|
+
- Peer dependency `@ceriousdevtech/cerious-scroll` tested against `^1.0.2` (range `^1.0.1` already satisfies this)
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## [1.0.0] - 2026-02-02
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
- Initial release of `@ceriousdevtech/vue-cerious-scroll`
|
|
22
|
+
- `<CeriousScroll>` component — drop-in virtual scroll list for Vue 3.3+
|
|
23
|
+
- `useCeriousScroll` composable for headless usage with custom container elements
|
|
24
|
+
- Synchronous `render()` rendering for each row — rows run with the app's `appContext` so globally registered components, directives, and plugins work normally
|
|
25
|
+
- Synchronous height measurement (no estimated heights, no correction passes)
|
|
26
|
+
- Full TypeScript support with exported prop and composable types
|
|
27
|
+
- Peer dependency on `@ceriousdevtech/cerious-scroll` core engine
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024–2026 Cerious DevTech LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# @ceriousdevtech/vue-cerious-scroll
|
|
2
|
+
|
|
3
|
+
[](LICENSE)
|
|
4
|
+
[](https://ceriousdevtech.github.io/vue-cerious-scroll/)
|
|
5
|
+
|
|
6
|
+
**Vue 3 bindings for [Cerious Scroll™](https://www.npmjs.com/package/@ceriousdevtech/cerious-scroll)** — high-performance virtual scrolling with **O(1) memory**, consistent **60 FPS+**, and **native variable-height support with no height estimation**.
|
|
7
|
+
|
|
8
|
+
Rows are rendered into the engine's own measured containers via Vue's synchronous `render()`, so every row's real height is measured (never estimated) — exactly the guarantee that makes CeriousScroll precise. Rows are rendered with your app's `appContext`, so **globally registered components, directives, and installed plugins work normally** inside each row.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @ceriousdevtech/vue-cerious-scroll @ceriousdevtech/cerious-scroll
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`vue` (>= 3.3) is a peer dependency.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Demo
|
|
23
|
+
|
|
24
|
+
**[Live demo →](https://ceriousdevtech.github.io/vue-cerious-scroll/)** — 100,000 rows, fixed/variable-height toggle, imperative jump-to-row, and live viewport stats.
|
|
25
|
+
|
|
26
|
+
To run locally:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install
|
|
30
|
+
npm run demo # dev server with HMR
|
|
31
|
+
npm run demo:build # production build to demo/dist
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The demo imports the wrapper by its package name, aliased to the library source,
|
|
35
|
+
so edits to `src/` are reflected live.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Quick start (component)
|
|
40
|
+
|
|
41
|
+
Give the container a height; provide `items` and an `#item` scoped slot.
|
|
42
|
+
|
|
43
|
+
```vue
|
|
44
|
+
<script setup lang="ts">
|
|
45
|
+
import { CeriousScroll } from '@ceriousdevtech/vue-cerious-scroll';
|
|
46
|
+
|
|
47
|
+
const items = Array.from({ length: 1_000_000 }, (_, i) => ({ id: i, name: `Item ${i}` }));
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<template>
|
|
51
|
+
<CeriousScroll :items="items" :style="{ height: '480px' }">
|
|
52
|
+
<template #item="{ item, index }">
|
|
53
|
+
<div class="row">{{ index }} — {{ item.name }}</div>
|
|
54
|
+
</template>
|
|
55
|
+
</CeriousScroll>
|
|
56
|
+
</template>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Variable heights need no configuration — just render rows of whatever height; the
|
|
60
|
+
engine measures each one.
|
|
61
|
+
|
|
62
|
+
### Without a full array (huge / sparse data)
|
|
63
|
+
|
|
64
|
+
```vue
|
|
65
|
+
<CeriousScroll
|
|
66
|
+
:total-elements="100_000_000"
|
|
67
|
+
:get-item="(index) => loadRow(index)"
|
|
68
|
+
:style="{ height: '600px' }"
|
|
69
|
+
>
|
|
70
|
+
<template #item="{ item, index }">
|
|
71
|
+
<Row :data="item" :index="index" />
|
|
72
|
+
</template>
|
|
73
|
+
</CeriousScroll>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Composable
|
|
79
|
+
|
|
80
|
+
`useCeriousScroll` gives you full control. Attach `containerRef` to your scroll
|
|
81
|
+
element; the composable renders the rows imperatively into the engine's measured
|
|
82
|
+
containers.
|
|
83
|
+
|
|
84
|
+
```vue
|
|
85
|
+
<script setup lang="ts">
|
|
86
|
+
import { h } from 'vue';
|
|
87
|
+
import { useCeriousScroll } from '@ceriousdevtech/vue-cerious-scroll';
|
|
88
|
+
|
|
89
|
+
const { containerRef } = useCeriousScroll({
|
|
90
|
+
items,
|
|
91
|
+
renderItem: (item, index) => h('div', { class: 'row' }, `${index} — ${item.name}`),
|
|
92
|
+
});
|
|
93
|
+
</script>
|
|
94
|
+
|
|
95
|
+
<template>
|
|
96
|
+
<div ref="containerRef" style="height: 480px; position: relative; overflow: hidden" />
|
|
97
|
+
</template>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
> `renderItem` returns a Vue `VNodeChild` (use `h(...)`, or render JSX/TSX).
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Component props
|
|
105
|
+
|
|
106
|
+
| Prop | Type | Description |
|
|
107
|
+
| --- | --- | --- |
|
|
108
|
+
| `items` | `readonly TItem[]` | Optional data array. `totalElements` defaults to `items.length`. |
|
|
109
|
+
| `totalElements` | `number` | Total item count. Required if `items` is omitted. |
|
|
110
|
+
| `getItem` | `(index) => TItem` | Lazy item getter for large/sparse datasets. |
|
|
111
|
+
| `renderItem` | `(item, index) => VNodeChild` | Render prop alternative to the `#item` scoped slot. |
|
|
112
|
+
| `options` | `CeriousScrollOptions` | Engine options (keyboard/touch/wheel/scrollbar/etc.). Read once at creation. |
|
|
113
|
+
| `autoRender` | `boolean` | Re-render on scroll/resize/data changes. Default `true`. |
|
|
114
|
+
|
|
115
|
+
The row is provided by the **`#item` scoped slot** (`{ item, index }`) or the
|
|
116
|
+
`render-item` prop. Apply `class` / `style` directly to the component — they fall
|
|
117
|
+
through onto the scroll container (set a height!).
|
|
118
|
+
|
|
119
|
+
### Events
|
|
120
|
+
|
|
121
|
+
| Event | Payload | Description |
|
|
122
|
+
| --- | --- | --- |
|
|
123
|
+
| `viewport-change` | `CeriousViewportChangeDetail` | Normalized viewport-change (wheel/touch/keyboard/scrollbar). |
|
|
124
|
+
| `measured-viewport` | `MeasuredViewportRange` | Measured range after each render pass. |
|
|
125
|
+
| `ready` | `CeriousScrollEngine` | The underlying engine instance, once ready. |
|
|
126
|
+
|
|
127
|
+
### Imperative API (via template `ref`)
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
const scroll = ref<InstanceType<typeof CeriousScroll> | null>(null);
|
|
131
|
+
// scroll.value?.jumpToElement(500);
|
|
132
|
+
// scroll.value?.scrollToPercentage(50);
|
|
133
|
+
// scroll.value?.reset();
|
|
134
|
+
// scroll.value?.render();
|
|
135
|
+
// scroll.value?.recalculate(); // drop cached heights + re-measure (see Notes)
|
|
136
|
+
// scroll.value?.scroller; // the raw engine
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Notes
|
|
142
|
+
|
|
143
|
+
- **No height estimation.** Rows are committed with Vue's synchronous `render()`
|
|
144
|
+
so the engine measures real `offsetHeight`. Later size changes are picked up by
|
|
145
|
+
the engine's built-in `ResizeObserver`.
|
|
146
|
+
- **`options` are read at creation.** Changing `options` after mount has no
|
|
147
|
+
effect; remount (e.g. with a `:key`) to apply new engine options.
|
|
148
|
+
- **Changing the item count** recreates the engine internally (scroll position
|
|
149
|
+
is preserved). Mutating items without changing the count just re-renders the
|
|
150
|
+
content in place (cheap; Vue patches each row, so focus/selection survive) — it
|
|
151
|
+
does **not** discard cached heights, so editable grids that produce a new
|
|
152
|
+
`items` array on every edit don't trigger a full viewport re-measure.
|
|
153
|
+
- **If every rendered row's height changes at once** (e.g. a density/layout
|
|
154
|
+
switch) the cached heights become stale and rows can misalign until the next
|
|
155
|
+
scroll. Call `recalculate()` (on the template `ref`, or from the composable
|
|
156
|
+
result) right after the change to drop the height cache and re-measure. Don't
|
|
157
|
+
call it on routine edits — a single cell edit keeps its row's size, and the
|
|
158
|
+
engine's built-in `ResizeObserver` picks up any incidental resize on its own.
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## License
|
|
163
|
+
|
|
164
|
+
Licensed by **Cerious DevTech LLC** under the **MIT License** (see `LICENSE-MIT`).
|
|
165
|
+
|
|
166
|
+
📧 info@ceriousdevtech.com
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { PropType, VNodeChild } from 'vue';
|
|
2
|
+
import { CeriousScrollOptions, MeasuredViewportRange } from '@ceriousdevtech/cerious-scroll';
|
|
3
|
+
import { CeriousViewportChangeDetail } from './viewport-change';
|
|
4
|
+
/**
|
|
5
|
+
* High-performance virtual scroll list. Provide `items` (or `total-elements` +
|
|
6
|
+
* `get-item`) and either an `#item` scoped slot or a `render-item` prop; give
|
|
7
|
+
* the container a height (e.g. `:style="{ height: '400px' }"`).
|
|
8
|
+
*
|
|
9
|
+
* Imperative methods (`render`, `jumpToElement`, `scrollToPercentage`, `reset`,
|
|
10
|
+
* `recalculate`) and the underlying `scroller` are available via a template ref.
|
|
11
|
+
*
|
|
12
|
+
* ```vue
|
|
13
|
+
* <CeriousScroll :items="items" :style="{ height: '400px' }">
|
|
14
|
+
* <template #item="{ item, index }">
|
|
15
|
+
* <div class="row">{{ index }} — {{ item.name }}</div>
|
|
16
|
+
* </template>
|
|
17
|
+
* </CeriousScroll>
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export declare const CeriousScroll: import('vue').DefineComponent<import('vue').ExtractPropTypes<{
|
|
21
|
+
/** Total item count. Falls back to `items.length` when omitted. */
|
|
22
|
+
totalElements: {
|
|
23
|
+
type: PropType<number | null>;
|
|
24
|
+
default: null;
|
|
25
|
+
};
|
|
26
|
+
/** Optional items array (passed to the row template as `item`). */
|
|
27
|
+
items: {
|
|
28
|
+
type: PropType<readonly unknown[] | null>;
|
|
29
|
+
default: null;
|
|
30
|
+
};
|
|
31
|
+
/** Optional lazy getter for very large/sparse datasets (alternative to `items`). */
|
|
32
|
+
getItem: {
|
|
33
|
+
type: PropType<(index: number) => unknown>;
|
|
34
|
+
default: undefined;
|
|
35
|
+
};
|
|
36
|
+
/** Render prop alternative to the `#item` scoped slot. */
|
|
37
|
+
renderItem: {
|
|
38
|
+
type: PropType<(item: unknown, index: number) => VNodeChild>;
|
|
39
|
+
default: undefined;
|
|
40
|
+
};
|
|
41
|
+
/** Options forwarded to `new CeriousScroll(...)` (read once, at creation). */
|
|
42
|
+
options: {
|
|
43
|
+
type: PropType<CeriousScrollOptions>;
|
|
44
|
+
default: undefined;
|
|
45
|
+
};
|
|
46
|
+
/** Automatically render after scroll/resize/data changes. Default: `true`. */
|
|
47
|
+
autoRender: {
|
|
48
|
+
type: BooleanConstructor;
|
|
49
|
+
default: boolean;
|
|
50
|
+
};
|
|
51
|
+
}>, () => import('vue').VNode<import('vue').RendererNode, import('vue').RendererElement, {
|
|
52
|
+
[key: string]: any;
|
|
53
|
+
}>, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
|
|
54
|
+
/** Normalized viewport-change payload (wheel/touch/keyboard/scrollbar). */
|
|
55
|
+
'viewport-change': (_detail: CeriousViewportChangeDetail) => true;
|
|
56
|
+
/** Measured range after each render pass. */
|
|
57
|
+
'measured-viewport': (_range: MeasuredViewportRange) => true;
|
|
58
|
+
/** Emitted once the underlying engine instance is ready (and after recreation). */
|
|
59
|
+
ready: (_scroller: unknown) => true;
|
|
60
|
+
}, string, import('vue').PublicProps, Readonly<import('vue').ExtractPropTypes<{
|
|
61
|
+
/** Total item count. Falls back to `items.length` when omitted. */
|
|
62
|
+
totalElements: {
|
|
63
|
+
type: PropType<number | null>;
|
|
64
|
+
default: null;
|
|
65
|
+
};
|
|
66
|
+
/** Optional items array (passed to the row template as `item`). */
|
|
67
|
+
items: {
|
|
68
|
+
type: PropType<readonly unknown[] | null>;
|
|
69
|
+
default: null;
|
|
70
|
+
};
|
|
71
|
+
/** Optional lazy getter for very large/sparse datasets (alternative to `items`). */
|
|
72
|
+
getItem: {
|
|
73
|
+
type: PropType<(index: number) => unknown>;
|
|
74
|
+
default: undefined;
|
|
75
|
+
};
|
|
76
|
+
/** Render prop alternative to the `#item` scoped slot. */
|
|
77
|
+
renderItem: {
|
|
78
|
+
type: PropType<(item: unknown, index: number) => VNodeChild>;
|
|
79
|
+
default: undefined;
|
|
80
|
+
};
|
|
81
|
+
/** Options forwarded to `new CeriousScroll(...)` (read once, at creation). */
|
|
82
|
+
options: {
|
|
83
|
+
type: PropType<CeriousScrollOptions>;
|
|
84
|
+
default: undefined;
|
|
85
|
+
};
|
|
86
|
+
/** Automatically render after scroll/resize/data changes. Default: `true`. */
|
|
87
|
+
autoRender: {
|
|
88
|
+
type: BooleanConstructor;
|
|
89
|
+
default: boolean;
|
|
90
|
+
};
|
|
91
|
+
}>> & Readonly<{
|
|
92
|
+
"onViewport-change"?: ((_detail: CeriousViewportChangeDetail) => any) | undefined;
|
|
93
|
+
"onMeasured-viewport"?: ((_range: MeasuredViewportRange) => any) | undefined;
|
|
94
|
+
onReady?: ((_scroller: unknown) => any) | undefined;
|
|
95
|
+
}>, {
|
|
96
|
+
totalElements: number | null;
|
|
97
|
+
items: readonly unknown[] | null;
|
|
98
|
+
getItem: (index: number) => unknown;
|
|
99
|
+
renderItem: (item: unknown, index: number) => VNodeChild;
|
|
100
|
+
options: CeriousScrollOptions;
|
|
101
|
+
autoRender: boolean;
|
|
102
|
+
}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOM helpers shared by the Vue bindings.
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) 2024-2026 Cerious DevTech LLC. All rights reserved.
|
|
5
|
+
*/
|
|
6
|
+
/** Attribute marking the dedicated content element rows are rendered into. */
|
|
7
|
+
export declare const CONTENT_ATTR = "data-cerious-scroll-content";
|
|
8
|
+
/** Attribute marking the inner mount node Vue renders each row into. */
|
|
9
|
+
export declare const ROW_ATTR = "data-cerious-scroll-row";
|
|
10
|
+
/**
|
|
11
|
+
* Ensure a dedicated, recyclable content element exists inside `container`.
|
|
12
|
+
*
|
|
13
|
+
* CeriousScroll renders/recycles row containers (clearing them with
|
|
14
|
+
* `textContent`/`innerHTML`) inside this element, while the native scrollbar
|
|
15
|
+
* and event listeners stay attached to the outer `container`. Keeping them
|
|
16
|
+
* separate prevents the scrollbar DOM from being wiped during rendering.
|
|
17
|
+
*/
|
|
18
|
+
export declare function ensureContentElement(container: HTMLElement): HTMLElement;
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const n=require("vue"),A=require("@ceriousdevtech/cerious-scroll"),V="data-cerious-scroll-content",N="data-cerious-scroll-row";function _(e){const u=e.querySelector(`[${V}]`);if(u)return u;const o=document.createElement("div");return o.setAttribute(V,""),o.style.position="relative",o.style.width="100%",o.style.height="100%",o.style.overflow="hidden",e.appendChild(o),o}function q(e,u){const o=l=>{const r=l.detail;r&&u({percentage:r.percentage,currentElement:r.currentElement,scrollOffset:r.scrollOffset,result:{element:r.result.element,offset:r.result.offset}})},f=l=>{const r=l.detail;r&&u({percentage:r.percentage,currentElement:r.element,scrollOffset:r.scrollOffset,result:{element:r.element,offset:r.scrollOffset}})};return e.addEventListener("cerious-viewport-change",o),e.addEventListener("viewport-change",f),()=>{e.removeEventListener("cerious-viewport-change",o),e.removeEventListener("viewport-change",f)}}function I(e,u){const o=typeof e=="number"?e:typeof u=="number"?u:void 0;if(o===void 0||Number.isNaN(o))throw new Error("useCeriousScroll: provide `totalElements` or `items`.");return Math.max(1,Math.floor(o))}function j(e){var O;const u=(O=n.getCurrentInstance())==null?void 0:O.appContext,o=n.ref(null),f=n.shallowRef(null);let l=null;const r=new Map;let s=null;const C=()=>n.toValue(e.autoRender)??!0,y=t=>{if(e.getItem)return e.getItem(t);const c=n.toValue(e.items);return c?c[t]:void 0},M=(t,c)=>{const i=u??null,m=n.effectScope(!0);return m.run(()=>n.watchEffect(()=>{const E=e.renderItem(y(t),t),d=Array.isArray(E)?E:[E];for(const w of d)n.isVNode(w)&&(w.appContext=i);const a=n.h(n.Fragment,d);a.appContext=i,n.render(a,c)},{flush:"sync"})),()=>m.stop()},b=t=>{t.stop(),n.render(null,t.mount),t.mount.remove()},v=()=>{var w;if(!l)return null;const{scroller:t,contentEl:c,container:i}=l,m=i.clientHeight||i.offsetHeight||0,E=(g,h)=>{const p=document.createElement("div");p.setAttribute(N,String(g)),h.appendChild(p);const S=M(g,p);r.set(g,{el:h,mount:p,stop:S})},d=t.renderViewport(m,c,E),a=new Set(t.getRenderedIndices());return r.forEach((g,h)=>{a.has(h)||(b(g),r.delete(h))}),(w=e.onMeasuredViewport)==null||w.call(e,d),d},R=t=>{if(!l)return;const c=l;l=null,c.unsubscribe(),t&&(s={currentElement:c.scroller.currentElement,scrollOffset:c.scroller.scrollOffset}),r.forEach(i=>b(i)),r.clear(),c.contentEl.textContent="",c.scroller.detachScrollbar(c.container),c.scroller.dispose(),f.value=null},T=t=>{var g,h;const c=_(t),i=e.options??{},m=i.onScroll,E={...i,onScroll:()=>{m==null||m(),C()&&v()}},d=I(n.toValue(e.totalElements),((g=n.toValue(e.items))==null?void 0:g.length)??null),a=new A.CeriousScroll(t,d,E);s&&(a.currentElement=Math.min(s.currentElement,d-1),a.scrollOffset=s.scrollOffset,s=null);const w=q(t,p=>{var S;(S=e.onViewportChange)==null||S.call(e,p)});l={scroller:a,contentEl:c,container:t,unsubscribe:w},f.value=a,(h=e.onReady)==null||h.call(e,a),C()&&requestAnimationFrame(()=>v())},P=()=>{R(!0);const t=o.value;t&&T(t)};return n.onMounted(()=>{const t=o.value;t&&T(t)}),n.onBeforeUnmount(()=>{R(!1)}),n.watch(()=>[n.toValue(e.totalElements),n.toValue(e.items)],()=>{var c;if(!l)return;const t=I(n.toValue(e.totalElements),((c=n.toValue(e.items))==null?void 0:c.length)??null);l.scroller.totalElements!==t&&P()}),{containerRef:o,scroller:f,render:v,jumpToElement:t=>l?(l.scroller.jumpToElement(t),v()):null,scrollToPercentage:t=>l?(l.scroller.handleScrollPercentage(t),v()):null,reset:()=>l?(l.scroller.reset(),v()):null,recalculate:()=>l?(l.scroller.clearAllCaches(),v()):null}}const F=n.defineComponent({name:"CeriousScroll",props:{totalElements:{type:Number,default:null},items:{type:Array,default:null},getItem:{type:Function,default:void 0},renderItem:{type:Function,default:void 0},options:{type:Object,default:void 0},autoRender:{type:Boolean,default:!0}},emits:{"viewport-change":e=>!0,"measured-viewport":e=>!0,ready:e=>!0},setup(e,{slots:u,emit:o,expose:f}){const l=(s,C)=>{var y;return e.renderItem?e.renderItem(s,C):(y=u.item)==null?void 0:y.call(u,{item:s,index:C})},r=j({totalElements:()=>e.totalElements,items:()=>e.items,getItem:e.getItem?s=>e.getItem(s):void 0,renderItem:l,options:e.options,autoRender:()=>e.autoRender,onViewportChange:s=>o("viewport-change",s),onMeasuredViewport:s=>o("measured-viewport",s),onReady:s=>o("ready",s)});return f({scroller:r.scroller,render:r.render,jumpToElement:r.jumpToElement,scrollToPercentage:r.scrollToPercentage,reset:r.reset,recalculate:r.recalculate}),()=>n.h("div",{ref:r.containerRef,style:{position:"relative",overflow:"hidden"}})}});Object.defineProperty(exports,"CeriousScrollEngine",{enumerable:!0,get:()=>A.CeriousScroll});exports.CeriousScroll=F;exports.useCeriousScroll=j;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ceriousdevtech/vue-cerious-scroll
|
|
3
|
+
*
|
|
4
|
+
* Vue 3 bindings for the CeriousScroll virtual scrolling engine.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) 2024-2026 Cerious DevTech LLC. All rights reserved.
|
|
7
|
+
*/
|
|
8
|
+
export { CeriousScroll } from './cerious-scroll';
|
|
9
|
+
export { useCeriousScroll } from './use-cerious-scroll';
|
|
10
|
+
export type { UseCeriousScrollOptions, UseCeriousScrollResult, } from './use-cerious-scroll';
|
|
11
|
+
export type { CeriousViewportChangeDetail } from './viewport-change';
|
|
12
|
+
export { CeriousScroll as CeriousScrollEngine } from '@ceriousdevtech/cerious-scroll';
|
|
13
|
+
export type { CeriousScrollOptions, KeyboardNavigationOptions, TouchNavigationOptions, WheelNavigationOptions, ElementRenderer, ElementHeightCalculator, ScrollResult, MeasuredViewportRange, } from '@ceriousdevtech/cerious-scroll';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { getCurrentInstance as P, ref as _, shallowRef as x, onMounted as F, onBeforeUnmount as L, watch as $, toValue as h, render as O, effectScope as q, watchEffect as B, isVNode as H, h as M, Fragment as U, defineComponent as W } from "vue";
|
|
2
|
+
import { CeriousScroll as z } from "@ceriousdevtech/cerious-scroll";
|
|
3
|
+
import { CeriousScroll as oe } from "@ceriousdevtech/cerious-scroll";
|
|
4
|
+
const A = "data-cerious-scroll-content", D = "data-cerious-scroll-row";
|
|
5
|
+
function G(e) {
|
|
6
|
+
const s = e.querySelector(`[${A}]`);
|
|
7
|
+
if (s) return s;
|
|
8
|
+
const n = document.createElement("div");
|
|
9
|
+
return n.setAttribute(A, ""), n.style.position = "relative", n.style.width = "100%", n.style.height = "100%", n.style.overflow = "hidden", e.appendChild(n), n;
|
|
10
|
+
}
|
|
11
|
+
function J(e, s) {
|
|
12
|
+
const n = (o) => {
|
|
13
|
+
const r = o.detail;
|
|
14
|
+
r && s({
|
|
15
|
+
percentage: r.percentage,
|
|
16
|
+
currentElement: r.currentElement,
|
|
17
|
+
scrollOffset: r.scrollOffset,
|
|
18
|
+
result: { element: r.result.element, offset: r.result.offset }
|
|
19
|
+
});
|
|
20
|
+
}, a = (o) => {
|
|
21
|
+
const r = o.detail;
|
|
22
|
+
r && s({
|
|
23
|
+
percentage: r.percentage,
|
|
24
|
+
currentElement: r.element,
|
|
25
|
+
scrollOffset: r.scrollOffset,
|
|
26
|
+
result: { element: r.element, offset: r.scrollOffset }
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
return e.addEventListener("cerious-viewport-change", n), e.addEventListener("viewport-change", a), () => {
|
|
30
|
+
e.removeEventListener("cerious-viewport-change", n), e.removeEventListener("viewport-change", a);
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function V(e, s) {
|
|
34
|
+
const n = typeof e == "number" ? e : typeof s == "number" ? s : void 0;
|
|
35
|
+
if (n === void 0 || Number.isNaN(n))
|
|
36
|
+
throw new Error("useCeriousScroll: provide `totalElements` or `items`.");
|
|
37
|
+
return Math.max(1, Math.floor(n));
|
|
38
|
+
}
|
|
39
|
+
function K(e) {
|
|
40
|
+
var I;
|
|
41
|
+
const s = (I = P()) == null ? void 0 : I.appContext, n = _(null), a = x(null);
|
|
42
|
+
let o = null;
|
|
43
|
+
const r = /* @__PURE__ */ new Map();
|
|
44
|
+
let c = null;
|
|
45
|
+
const C = () => h(e.autoRender) ?? !0, y = (t) => {
|
|
46
|
+
if (e.getItem) return e.getItem(t);
|
|
47
|
+
const l = h(e.items);
|
|
48
|
+
return l ? l[t] : void 0;
|
|
49
|
+
}, N = (t, l) => {
|
|
50
|
+
const u = s ?? null, f = q(!0);
|
|
51
|
+
return f.run(
|
|
52
|
+
() => B(
|
|
53
|
+
() => {
|
|
54
|
+
const E = e.renderItem(y(t), t), m = Array.isArray(E) ? E : [E];
|
|
55
|
+
for (const w of m)
|
|
56
|
+
H(w) && (w.appContext = u);
|
|
57
|
+
const i = M(U, m);
|
|
58
|
+
i.appContext = u, O(i, l);
|
|
59
|
+
},
|
|
60
|
+
{ flush: "sync" }
|
|
61
|
+
)
|
|
62
|
+
), () => f.stop();
|
|
63
|
+
}, S = (t) => {
|
|
64
|
+
t.stop(), O(null, t.mount), t.mount.remove();
|
|
65
|
+
}, v = () => {
|
|
66
|
+
var w;
|
|
67
|
+
if (!o) return null;
|
|
68
|
+
const { scroller: t, contentEl: l, container: u } = o, f = u.clientHeight || u.offsetHeight || 0, E = (d, g) => {
|
|
69
|
+
const p = document.createElement("div");
|
|
70
|
+
p.setAttribute(D, String(d)), g.appendChild(p);
|
|
71
|
+
const R = N(d, p);
|
|
72
|
+
r.set(d, { el: g, mount: p, stop: R });
|
|
73
|
+
}, m = t.renderViewport(f, l, E), i = new Set(t.getRenderedIndices());
|
|
74
|
+
return r.forEach((d, g) => {
|
|
75
|
+
i.has(g) || (S(d), r.delete(g));
|
|
76
|
+
}), (w = e.onMeasuredViewport) == null || w.call(e, m), m;
|
|
77
|
+
}, T = (t) => {
|
|
78
|
+
if (!o) return;
|
|
79
|
+
const l = o;
|
|
80
|
+
o = null, l.unsubscribe(), t && (c = {
|
|
81
|
+
currentElement: l.scroller.currentElement,
|
|
82
|
+
scrollOffset: l.scroller.scrollOffset
|
|
83
|
+
}), r.forEach((u) => S(u)), r.clear(), l.contentEl.textContent = "", l.scroller.detachScrollbar(l.container), l.scroller.dispose(), a.value = null;
|
|
84
|
+
}, b = (t) => {
|
|
85
|
+
var d, g;
|
|
86
|
+
const l = G(t), u = e.options ?? {}, f = u.onScroll, E = {
|
|
87
|
+
...u,
|
|
88
|
+
onScroll: () => {
|
|
89
|
+
f == null || f(), C() && v();
|
|
90
|
+
}
|
|
91
|
+
}, m = V(h(e.totalElements), ((d = h(e.items)) == null ? void 0 : d.length) ?? null), i = new z(t, m, E);
|
|
92
|
+
c && (i.currentElement = Math.min(c.currentElement, m - 1), i.scrollOffset = c.scrollOffset, c = null);
|
|
93
|
+
const w = J(t, (p) => {
|
|
94
|
+
var R;
|
|
95
|
+
(R = e.onViewportChange) == null || R.call(e, p);
|
|
96
|
+
});
|
|
97
|
+
o = { scroller: i, contentEl: l, container: t, unsubscribe: w }, a.value = i, (g = e.onReady) == null || g.call(e, i), C() && requestAnimationFrame(() => v());
|
|
98
|
+
}, j = () => {
|
|
99
|
+
T(!0);
|
|
100
|
+
const t = n.value;
|
|
101
|
+
t && b(t);
|
|
102
|
+
};
|
|
103
|
+
return F(() => {
|
|
104
|
+
const t = n.value;
|
|
105
|
+
t && b(t);
|
|
106
|
+
}), L(() => {
|
|
107
|
+
T(!1);
|
|
108
|
+
}), $(
|
|
109
|
+
() => [h(e.totalElements), h(e.items)],
|
|
110
|
+
() => {
|
|
111
|
+
var l;
|
|
112
|
+
if (!o) return;
|
|
113
|
+
const t = V(h(e.totalElements), ((l = h(e.items)) == null ? void 0 : l.length) ?? null);
|
|
114
|
+
o.scroller.totalElements !== t && j();
|
|
115
|
+
}
|
|
116
|
+
), {
|
|
117
|
+
containerRef: n,
|
|
118
|
+
scroller: a,
|
|
119
|
+
render: v,
|
|
120
|
+
jumpToElement: (t) => o ? (o.scroller.jumpToElement(t), v()) : null,
|
|
121
|
+
scrollToPercentage: (t) => o ? (o.scroller.handleScrollPercentage(t), v()) : null,
|
|
122
|
+
reset: () => o ? (o.scroller.reset(), v()) : null,
|
|
123
|
+
recalculate: () => o ? (o.scroller.clearAllCaches(), v()) : null
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
const te = W({
|
|
127
|
+
name: "CeriousScroll",
|
|
128
|
+
props: {
|
|
129
|
+
/** Total item count. Falls back to `items.length` when omitted. */
|
|
130
|
+
totalElements: { type: Number, default: null },
|
|
131
|
+
/** Optional items array (passed to the row template as `item`). */
|
|
132
|
+
items: { type: Array, default: null },
|
|
133
|
+
/** Optional lazy getter for very large/sparse datasets (alternative to `items`). */
|
|
134
|
+
getItem: { type: Function, default: void 0 },
|
|
135
|
+
/** Render prop alternative to the `#item` scoped slot. */
|
|
136
|
+
renderItem: {
|
|
137
|
+
type: Function,
|
|
138
|
+
default: void 0
|
|
139
|
+
},
|
|
140
|
+
/** Options forwarded to `new CeriousScroll(...)` (read once, at creation). */
|
|
141
|
+
options: { type: Object, default: void 0 },
|
|
142
|
+
/** Automatically render after scroll/resize/data changes. Default: `true`. */
|
|
143
|
+
autoRender: { type: Boolean, default: !0 }
|
|
144
|
+
},
|
|
145
|
+
emits: {
|
|
146
|
+
/** Normalized viewport-change payload (wheel/touch/keyboard/scrollbar). */
|
|
147
|
+
"viewport-change": (e) => !0,
|
|
148
|
+
/** Measured range after each render pass. */
|
|
149
|
+
"measured-viewport": (e) => !0,
|
|
150
|
+
/** Emitted once the underlying engine instance is ready (and after recreation). */
|
|
151
|
+
ready: (e) => !0
|
|
152
|
+
},
|
|
153
|
+
setup(e, { slots: s, emit: n, expose: a }) {
|
|
154
|
+
const o = (c, C) => {
|
|
155
|
+
var y;
|
|
156
|
+
return e.renderItem ? e.renderItem(c, C) : (y = s.item) == null ? void 0 : y.call(s, { item: c, index: C });
|
|
157
|
+
}, r = K({
|
|
158
|
+
totalElements: () => e.totalElements,
|
|
159
|
+
items: () => e.items,
|
|
160
|
+
getItem: e.getItem ? (c) => e.getItem(c) : void 0,
|
|
161
|
+
renderItem: o,
|
|
162
|
+
// Engine options are consumed at creation; reading the prop once matches
|
|
163
|
+
// that contract (remount via `:key` to apply new engine options).
|
|
164
|
+
options: e.options,
|
|
165
|
+
autoRender: () => e.autoRender,
|
|
166
|
+
onViewportChange: (c) => n("viewport-change", c),
|
|
167
|
+
onMeasuredViewport: (c) => n("measured-viewport", c),
|
|
168
|
+
onReady: (c) => n("ready", c)
|
|
169
|
+
});
|
|
170
|
+
return a({
|
|
171
|
+
/** The underlying engine instance (`null` before mount). */
|
|
172
|
+
scroller: r.scroller,
|
|
173
|
+
render: r.render,
|
|
174
|
+
jumpToElement: r.jumpToElement,
|
|
175
|
+
scrollToPercentage: r.scrollToPercentage,
|
|
176
|
+
reset: r.reset,
|
|
177
|
+
recalculate: r.recalculate
|
|
178
|
+
}), () => M("div", {
|
|
179
|
+
ref: r.containerRef,
|
|
180
|
+
// User-supplied class/style/attrs fall through and merge onto this root.
|
|
181
|
+
style: { position: "relative", overflow: "hidden" }
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
export {
|
|
186
|
+
te as CeriousScroll,
|
|
187
|
+
oe as CeriousScrollEngine,
|
|
188
|
+
K as useCeriousScroll
|
|
189
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { MaybeRefOrGetter, Ref, VNodeChild } from 'vue';
|
|
2
|
+
import { CeriousScroll as CeriousScrollEngine, CeriousScrollOptions, MeasuredViewportRange } from '@ceriousdevtech/cerious-scroll';
|
|
3
|
+
import { CeriousViewportChangeDetail } from './viewport-change';
|
|
4
|
+
export interface UseCeriousScrollOptions<TItem = unknown> {
|
|
5
|
+
/** Total number of items. Falls back to `items.length` when omitted. */
|
|
6
|
+
totalElements?: MaybeRefOrGetter<number | null | undefined>;
|
|
7
|
+
/** Optional items array (passed to `renderItem` as the first argument). */
|
|
8
|
+
items?: MaybeRefOrGetter<readonly TItem[] | null | undefined>;
|
|
9
|
+
/** Optional getter for very large/sparse datasets (alternative to `items`). */
|
|
10
|
+
getItem?: (index: number) => TItem;
|
|
11
|
+
/** Renders a single row to a Vue VNode. `item` is `undefined` with no data source. */
|
|
12
|
+
renderItem: (item: TItem, index: number) => VNodeChild;
|
|
13
|
+
/** Options forwarded to `new CeriousScroll(...)` (read once, at creation). */
|
|
14
|
+
options?: CeriousScrollOptions;
|
|
15
|
+
/** Automatically render after scroll/resize/data changes. Default: `true`. */
|
|
16
|
+
autoRender?: MaybeRefOrGetter<boolean | undefined>;
|
|
17
|
+
/** Invoked with the normalized viewport-change payload. */
|
|
18
|
+
onViewportChange?: (detail: CeriousViewportChangeDetail) => void;
|
|
19
|
+
/** Invoked with the measured range after each render pass. */
|
|
20
|
+
onMeasuredViewport?: (range: MeasuredViewportRange) => void;
|
|
21
|
+
/** Invoked once the underlying engine instance is ready (and after recreation). */
|
|
22
|
+
onReady?: (scroller: CeriousScrollEngine) => void;
|
|
23
|
+
}
|
|
24
|
+
export interface UseCeriousScrollResult {
|
|
25
|
+
/** Attach to the scroll container element (`<div :ref="containerRef">`). */
|
|
26
|
+
containerRef: Ref<HTMLElement | null>;
|
|
27
|
+
/** The underlying engine instance (`null` before mount / after unmount). */
|
|
28
|
+
scroller: Ref<CeriousScrollEngine | null>;
|
|
29
|
+
/** Imperatively trigger a render pass. */
|
|
30
|
+
render: () => MeasuredViewportRange | null;
|
|
31
|
+
/** Jump to an element index, then render. */
|
|
32
|
+
jumpToElement: (index: number) => MeasuredViewportRange | null;
|
|
33
|
+
/** Scroll to a percentage (0..100), then render. */
|
|
34
|
+
scrollToPercentage: (percentage: number) => MeasuredViewportRange | null;
|
|
35
|
+
/** Reset to the top, then render. */
|
|
36
|
+
reset: () => MeasuredViewportRange | null;
|
|
37
|
+
/**
|
|
38
|
+
* Discard all cached row heights and re-measure the viewport.
|
|
39
|
+
*
|
|
40
|
+
* Call this only when the heights of rows you've *already rendered* may have
|
|
41
|
+
* changed without their indices changing — e.g. a global font/density change,
|
|
42
|
+
* or swapping every row to a different layout. This forces a synchronous
|
|
43
|
+
* re-measure (one `offsetHeight` read per visible row), so do NOT call it on
|
|
44
|
+
* routine edits: a single cell edit doesn't need it (its row keeps its size,
|
|
45
|
+
* and the engine's ResizeObserver picks up any incidental resize on its own).
|
|
46
|
+
*/
|
|
47
|
+
recalculate: () => MeasuredViewportRange | null;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Bind a CeriousScroll engine to a container and render rows with Vue.
|
|
51
|
+
*
|
|
52
|
+
* Each row is rendered into an inner mount node (which lives inside the engine's
|
|
53
|
+
* recyclable container) via Vue's synchronous `render(vnode, el)`, so the engine
|
|
54
|
+
* measures the row's real height — no estimation. Because rendering is
|
|
55
|
+
* synchronous, no `flushSync`-style escape hatch is needed.
|
|
56
|
+
*
|
|
57
|
+
* Rows are rendered with the host component's `appContext`, so globally
|
|
58
|
+
* registered components, directives, and installed plugins are available inside
|
|
59
|
+
* each row.
|
|
60
|
+
*/
|
|
61
|
+
export declare function useCeriousScroll<TItem = unknown>(opts: UseCeriousScrollOptions<TItem>): UseCeriousScrollResult;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ScrollResult } from '@ceriousdevtech/cerious-scroll';
|
|
2
|
+
/**
|
|
3
|
+
* Normalized payload for viewport changes emitted by CeriousScroll.
|
|
4
|
+
*
|
|
5
|
+
* Mirrors the `cerious-viewport-change` CustomEvent dispatched on wheel/touch/
|
|
6
|
+
* keyboard navigation, and normalizes the native scrollbar's `viewport-change`
|
|
7
|
+
* event into the same shape.
|
|
8
|
+
*/
|
|
9
|
+
export interface CeriousViewportChangeDetail {
|
|
10
|
+
/** Scroll percentage from 0..100. */
|
|
11
|
+
percentage: number;
|
|
12
|
+
/** Current top-most element index tracked by CeriousScroll. */
|
|
13
|
+
currentElement: number;
|
|
14
|
+
/** Pixel offset within `currentElement`. */
|
|
15
|
+
scrollOffset: number;
|
|
16
|
+
/** Scroll operation result (element + offset). */
|
|
17
|
+
result: ScrollResult;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Subscribe to both viewport-change events emitted by CeriousScroll and invoke
|
|
21
|
+
* `callback` with a normalized {@link CeriousViewportChangeDetail}.
|
|
22
|
+
*
|
|
23
|
+
* @returns An unsubscribe function.
|
|
24
|
+
*/
|
|
25
|
+
export declare function subscribeViewportChange(container: HTMLElement, callback: (detail: CeriousViewportChangeDetail) => void): () => void;
|
package/package.json
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ceriousdevtech/vue-cerious-scroll",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Vue 3 bindings for CeriousScroll — high-performance virtual scrolling with O(1) memory and no height estimation",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.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.cjs"
|
|
14
|
+
},
|
|
15
|
+
"./package.json": "./package.json"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"LICENSE",
|
|
20
|
+
"README.md",
|
|
21
|
+
"CHANGELOG.md"
|
|
22
|
+
],
|
|
23
|
+
"sideEffects": false,
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"author": "Cerious DevTech LLC <info@ceriousdevtech.com>",
|
|
26
|
+
"homepage": "https://github.com/ceriousdevtech/vue-cerious-scroll#readme",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/ceriousdevtech/vue-cerious-scroll.git"
|
|
30
|
+
},
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/ceriousdevtech/vue-cerious-scroll/issues",
|
|
33
|
+
"email": "info@ceriousdevtech.com"
|
|
34
|
+
},
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
38
|
+
"keywords": [
|
|
39
|
+
"vue",
|
|
40
|
+
"vue3",
|
|
41
|
+
"virtual-scroll",
|
|
42
|
+
"virtual-scrolling",
|
|
43
|
+
"infinite-scroll",
|
|
44
|
+
"performance",
|
|
45
|
+
"large-lists",
|
|
46
|
+
"data-grid",
|
|
47
|
+
"variable-height",
|
|
48
|
+
"o1-memory",
|
|
49
|
+
"cerious-scroll"
|
|
50
|
+
],
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "vite build",
|
|
53
|
+
"test": "vitest run",
|
|
54
|
+
"test:watch": "vitest",
|
|
55
|
+
"typecheck": "vue-tsc --noEmit",
|
|
56
|
+
"demo": "vite --config demo/vite.config.ts",
|
|
57
|
+
"demo:build": "vite build --config demo/vite.config.ts"
|
|
58
|
+
},
|
|
59
|
+
"peerDependencies": {
|
|
60
|
+
"@ceriousdevtech/cerious-scroll": "^1.0.1",
|
|
61
|
+
"vue": "^3.3.0"
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@ceriousdevtech/cerious-scroll": "^1.0.1",
|
|
65
|
+
"@types/node": "^22.10.2",
|
|
66
|
+
"@vitejs/plugin-vue": "^5.2.1",
|
|
67
|
+
"@vue/test-utils": "^2.4.6",
|
|
68
|
+
"jsdom": "^25.0.1",
|
|
69
|
+
"typescript": "^5.6.3",
|
|
70
|
+
"vite": "^5.4.11",
|
|
71
|
+
"vite-plugin-dts": "^4.3.0",
|
|
72
|
+
"vitest": "^2.1.8",
|
|
73
|
+
"vue": "^3.5.13",
|
|
74
|
+
"vue-router": "^4.6.4",
|
|
75
|
+
"vue-tsc": "^2.1.10"
|
|
76
|
+
}
|
|
77
|
+
}
|