@ceriousdevtech/vue-cerious-scroll 1.0.7 → 1.1.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 +21 -0
- package/README.md +38 -3
- package/dist/cerious-scroll.d.ts +3 -2
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +164 -131
- package/dist/use-cerious-scroll.d.ts +10 -1
- package/package.json +4 -3
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,27 @@ All notable changes to vue-cerious-scroll will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.1.1] - 2026-08-23
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Masonry real-content demo: network images, composed Vue card components, and a per-card carousel over 50,000 items. Demonstrates the three constraints recycled cards impose — media space reserved from intrinsic dimensions, card height enforced rather than estimated, and per-card UI state held outside component state and keyed by card index so it survives a card leaving the window.
|
|
12
|
+
- Demo image handling shows the practices that matter with virtualization: no `loading="lazy"` (the window is already the lazy loader), an instant placeholder colour behind each reserved box, a bounded low-priority prefetch window, and bucketed request widths so a resizing CDN can cache them.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- Updated `@ceriousdevtech/cerious-scroll` to `^1.1.1`.
|
|
16
|
+
|
|
17
|
+
## [1.1.0] - 2026-08-22
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- Declarative canonical and dynamic Masonry support through the existing `#item` slot or `render-item` prop.
|
|
21
|
+
- Dynamic-height probe rendering with short-lived Vue trees that are disposed after synchronous measurement.
|
|
22
|
+
- `jumpToItem(index, screenOffset?)` on the composable result and exposed component API.
|
|
23
|
+
- `CeriousScrollOptions` support for wrapper-owned Masonry rendering, canonical/dynamic demo routes, and regression coverage for both modes.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- Updated `@ceriousdevtech/cerious-scroll` to `^1.1.0` and re-exported the new Masonry and height-provider types.
|
|
27
|
+
- Masonry card-count changes recreate card-derived segment state; list and table count changes continue updating in place.
|
|
28
|
+
|
|
8
29
|
## [1.0.7] - 2026-06-24
|
|
9
30
|
|
|
10
31
|
### Fixed
|
package/README.md
CHANGED
|
@@ -109,7 +109,7 @@ const { containerRef } = useCeriousScroll({
|
|
|
109
109
|
| `totalElements` | `number` | Total item count. Required if `items` is omitted. |
|
|
110
110
|
| `getItem` | `(index) => TItem` | Lazy item getter for large/sparse datasets. |
|
|
111
111
|
| `renderItem` | `(item, index) => VNodeChild` | Render prop alternative to the `#item` scoped slot. |
|
|
112
|
-
| `options` | `CeriousScrollOptions` | Engine options
|
|
112
|
+
| `options` | `CeriousScrollOptions` | Engine options. Masonry's DOM callback is supplied by the wrapper. Read once at creation. |
|
|
113
113
|
| `autoRender` | `boolean` | Re-render on scroll/resize/data changes. Default `true`. |
|
|
114
114
|
|
|
115
115
|
The row is provided by the **`#item` scoped slot** (`{ item, index }`) or the
|
|
@@ -130,6 +130,7 @@ component — they fall through onto the scroll container (set a height!).
|
|
|
130
130
|
```ts
|
|
131
131
|
const scroll = ref<InstanceType<typeof CeriousScroll> | null>(null);
|
|
132
132
|
// scroll.value?.jumpToElement(500);
|
|
133
|
+
// scroll.value?.jumpToItem(500); // Masonry cards
|
|
133
134
|
// scroll.value?.scrollToPercentage(50);
|
|
134
135
|
// scroll.value?.reset();
|
|
135
136
|
// scroll.value?.render();
|
|
@@ -139,6 +140,40 @@ const scroll = ref<InstanceType<typeof CeriousScroll> | null>(null);
|
|
|
139
140
|
|
|
140
141
|
---
|
|
141
142
|
|
|
143
|
+
## Masonry layout
|
|
144
|
+
|
|
145
|
+
Set `layout: 'masonry'`; the wrapper renders the `#item` slot into each core
|
|
146
|
+
Masonry card, so no imperative DOM callback is exposed:
|
|
147
|
+
|
|
148
|
+
```vue
|
|
149
|
+
<CeriousScroll
|
|
150
|
+
ref="scroll"
|
|
151
|
+
class="gallery"
|
|
152
|
+
:total-elements="photos.length"
|
|
153
|
+
:get-item="(index) => photos[index]"
|
|
154
|
+
:options="{
|
|
155
|
+
layout: 'masonry',
|
|
156
|
+
masonry: {
|
|
157
|
+
getItemHeight: (_index, width) => width * 0.75 + 48,
|
|
158
|
+
targetColumnWidth: 280,
|
|
159
|
+
gap: 16
|
|
160
|
+
}
|
|
161
|
+
}"
|
|
162
|
+
>
|
|
163
|
+
<template #item="{ item: photo, index }">
|
|
164
|
+
<PhotoCard :photo="photo" :index="index" />
|
|
165
|
+
</template>
|
|
166
|
+
</CeriousScroll>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Omit `getItemHeight` for dynamic DOM measurement. Vue creates a short-lived
|
|
170
|
+
offscreen render tree for measurement and disposes it after the synchronous
|
|
171
|
+
height read; visible cards remain reactive Vue trees with provide/inject and
|
|
172
|
+
event handling.
|
|
173
|
+
|
|
174
|
+
Use `scroll.value?.jumpToItem(index, screenOffset?)` for card navigation. The
|
|
175
|
+
demo gallery includes canonical and dynamic Masonry pages.
|
|
176
|
+
|
|
142
177
|
## Table layout
|
|
143
178
|
|
|
144
179
|
Pass `:options="{ layout: 'table' }"` to render real `<table>` / `<tr>` / `<td>` rows with a frozen header and native column alignment. The `#item` slot returns the row's `<td>` cells; a `#header` slot provides the (declarative, reactive) `<thead>` row:
|
|
@@ -176,8 +211,8 @@ Pass `:options="{ layout: 'table' }"` to render real `<table>` / `<tr>` / `<td>`
|
|
|
176
211
|
the engine's built-in `ResizeObserver`.
|
|
177
212
|
- **`options` are read at creation.** Changing `options` after mount has no
|
|
178
213
|
effect; remount (e.g. with a `:key`) to apply new engine options.
|
|
179
|
-
- **Changing the item count**
|
|
180
|
-
|
|
214
|
+
- **Changing the item count** updates lists/tables in place. Masonry recreates
|
|
215
|
+
its card-count-derived segment layout. Mutating items without changing the count just re-renders the
|
|
181
216
|
content in place (cheap; Vue patches each row, so focus/selection survive) — it
|
|
182
217
|
does **not** discard cached heights, so editable grids that produce a new
|
|
183
218
|
`items` array on every edit don't trigger a full viewport re-measure.
|
package/dist/cerious-scroll.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { PropType, VNodeChild } from 'vue';
|
|
2
|
-
import {
|
|
2
|
+
import { MeasuredViewportRange } from '@ceriousdevtech/cerious-scroll';
|
|
3
|
+
import { CeriousScrollOptions } from './use-cerious-scroll';
|
|
3
4
|
import { CeriousViewportChangeDetail } from './viewport-change';
|
|
4
5
|
/**
|
|
5
6
|
* High-performance virtual scroll list. Provide `items` (or `total-elements` +
|
|
@@ -93,10 +94,10 @@ export declare const CeriousScroll: import('vue').DefineComponent<import('vue').
|
|
|
93
94
|
"onMeasured-viewport"?: ((_range: MeasuredViewportRange) => any) | undefined;
|
|
94
95
|
onReady?: ((_scroller: unknown) => any) | undefined;
|
|
95
96
|
}>, {
|
|
97
|
+
renderItem: (item: unknown, index: number) => VNodeChild;
|
|
96
98
|
totalElements: number | null;
|
|
97
99
|
items: readonly unknown[] | null;
|
|
98
100
|
getItem: (index: number) => unknown;
|
|
99
|
-
renderItem: (item: unknown, index: number) => VNodeChild;
|
|
100
101
|
options: CeriousScrollOptions;
|
|
101
102
|
autoRender: boolean;
|
|
102
103
|
}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const n=require("vue"),
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const n=require("vue"),L=require("@ceriousdevtech/cerious-scroll"),F="data-cerious-scroll-content",A="data-cerious-scroll-row";function X(e){const s=e.querySelector(`[${F}]`);if(s)return s;const u=document.createElement("div");return u.setAttribute(F,""),u.style.position="relative",u.style.width="100%",u.style.height="100%",u.style.overflowY="clip",u.style.overflowX="auto",e.appendChild(u),u}function Y(e,s){const u=p=>{const l=p.detail;l&&s({percentage:l.percentage,currentElement:l.currentElement,scrollOffset:l.scrollOffset,result:{element:l.result.element,offset:l.result.offset}})},w=p=>{const l=p.detail;l&&s({percentage:l.percentage,currentElement:l.element,scrollOffset:l.scrollOffset,result:{element:l.element,offset:l.scrollOffset}})};return e.addEventListener("cerious-viewport-change",u),e.addEventListener("viewport-change",w),()=>{e.removeEventListener("cerious-viewport-change",u),e.removeEventListener("viewport-change",w)}}function _(e,s){const u=typeof e=="number"?e:typeof s=="number"?s:void 0;if(u===void 0||Number.isNaN(u))throw new Error("useCeriousScroll: provide `totalElements` or `items`.");return Math.max(1,Math.floor(u))}function B(e){var N,q;const s=n.getCurrentInstance(),u=s==null?void 0:s.provides,w=s?{...s.appContext,provides:u??s.appContext.provides}:void 0,p=n.ref(null),l=n.shallowRef(null);let r=null;const f=new Map,T=((N=e.options)==null?void 0:N.layout)==="table",b=((q=e.options)==null?void 0:q.layout)==="masonry";let y=null;const $=t=>{if(!e.renderHeader)return;y==null||y();const o=w??null,c=n.effectScope(!0);c.run(()=>n.watchEffect(()=>{const a=e.renderHeader(),m=Array.isArray(a)?a:[a];for(const d of m)n.isVNode(d)&&(d.appContext=o);const i=n.h(n.Fragment,m);i.appContext=o,n.render(i,t)},{flush:"sync"})),y=()=>c.stop()},V=()=>n.toValue(e.autoRender)??!0,U=t=>{if(e.getItem)return e.getItem(t);const o=n.toValue(e.items);return o?o[t]:void 0},j=(t,o)=>{const c=w??null,a=n.effectScope(!0);return a.run(()=>n.watchEffect(()=>{const m=e.renderItem(U(t),t),i=Array.isArray(m)?m:[m];for(const C of i)n.isVNode(C)&&(C.appContext=c);const d=n.h(n.Fragment,i);d.appContext=c,n.render(d,o)},{flush:"sync"})),()=>a.stop()},M=t=>{t.stop(),n.render(null,t.mount),t.mount.remove()},W=(t,o)=>{let c=o.querySelector(`[${A}]`);if(c||(c=document.createElement("div"),o.appendChild(c)),c.setAttribute(A,String(t)),o.dataset.ceriousMasonry==="probe"){const i=j(t,c);queueMicrotask(()=>{i(),n.render(null,c)});return}f.forEach((i,d)=>{i.el===o&&d!==t&&(i.stop(),f.delete(d))});const a=f.get(t);if((a==null?void 0:a.mount)===c)return;const m=j(t,c);f.set(t,{el:o,mount:c,stop:m})},v=()=>{var C;if(!r)return null;const{scroller:t,contentEl:o,container:c}=r,a=c.clientHeight||c.offsetHeight||0,m=(h,E)=>{const g=document.createElement("div");T&&(g.style.display="contents"),g.setAttribute(A,String(h)),E.appendChild(g);const R=j(h,g);f.set(h,{el:E,mount:g,stop:R})},i=t.renderViewport(a,o,m),d=b?null:new Set(t.getRenderedIndices());return f.forEach((h,E)=>{(b?!h.el.isConnected||h.el.dataset.elementIndex!==String(E):!d.has(E))&&(M(h),f.delete(E))}),(C=e.onMeasuredViewport)==null||C.call(e,i),i},O=t=>{if(!r)return;const o=r;r=null,o.unsubscribe(),f.forEach(c=>M(c)),f.clear(),y==null||y(),y=null,o.scroller.detachScrollbar(o.container),o.scroller.dispose(),o.contentEl.textContent="",l.value=null},P=t=>{var g,R,H;const o=e.options??{},c=b?t:X(t),a=o.onScroll,{masonry:m,...i}=o,d={...i,onScroll:()=>{a==null||a(),V()&&v()}};if(b){if(!m)throw new Error("useCeriousScroll: layout 'masonry' requires `options.masonry`.");d.masonry={...m,renderItem:W}}if(T&&e.renderHeader){const S=(g=o.table)==null?void 0:g.header;d.table={...o.table,header:I=>{S==null||S(I),$(I)}}}const C=_(n.toValue(e.totalElements),((R=n.toValue(e.items))==null?void 0:R.length)??null),h=new L.CeriousScroll(t,C,d),E=Y(t,S=>{var I;(I=e.onViewportChange)==null||I.call(e,S)});r={scroller:h,contentEl:c,container:t,unsubscribe:E},l.value=h,(H=e.onReady)==null||H.call(e,h),V()&&requestAnimationFrame(()=>v())};return n.onMounted(()=>{const t=p.value;t&&P(t)}),n.onBeforeUnmount(()=>{O()}),n.watch(()=>[n.toValue(e.totalElements),n.toValue(e.items)],()=>{var c;if(!r)return;const t=_(n.toValue(e.totalElements),((c=n.toValue(e.items))==null?void 0:c.length)??null);if((b?r.scroller.itemCount:r.scroller.totalElements)!==t){if(b){const a=p.value;O(),a&&P(a);return}r.scroller.updateTotalElements(t),r.scroller.currentElement>t-1&&r.scroller.jumpToElement(t-1),V()&&v(),r.scroller.syncScrollbar()}}),{containerRef:p,scroller:l,render:v,jumpToElement:t=>r?(r.scroller.jumpToElement(t),v()):null,jumpToItem:(t,o=0)=>r?(r.scroller.jumpToItem(t,o),v()):null,scrollToPercentage:t=>r?(r.scroller.handleScrollPercentage(t),v()):null,reset:()=>r?(r.scroller.reset(),v()):null,recalculate:()=>r?(r.scroller.clearAllCaches(),v()):null}}const k=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:s,emit:u,expose:w}){const p=(r,f)=>{var T;return e.renderItem?e.renderItem(r,f):(T=s.item)==null?void 0:T.call(s,{item:r,index:f})},l=B({totalElements:()=>e.totalElements,items:()=>e.items,getItem:e.getItem?r=>e.getItem(r):void 0,renderItem:p,renderHeader:s.header?()=>s.header():void 0,options:e.options,autoRender:()=>e.autoRender,onViewportChange:r=>u("viewport-change",r),onMeasuredViewport:r=>u("measured-viewport",r),onReady:r=>u("ready",r)});return w({scroller:l.scroller,render:l.render,jumpToElement:l.jumpToElement,jumpToItem:l.jumpToItem,scrollToPercentage:l.scrollToPercentage,reset:l.reset,recalculate:l.recalculate}),()=>{var r;return n.h("div",{ref:l.containerRef,style:{position:"relative",overflow:"hidden"}},(r=s.default)==null?void 0:r.call(s))}}});Object.defineProperty(exports,"CeriousScrollEngine",{enumerable:!0,get:()=>L.CeriousScroll});exports.CeriousScroll=k;exports.useCeriousScroll=B;
|
package/dist/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
export { CeriousScroll } from './cerious-scroll';
|
|
9
9
|
export { useCeriousScroll } from './use-cerious-scroll';
|
|
10
|
-
export type { UseCeriousScrollOptions, UseCeriousScrollResult, } from './use-cerious-scroll';
|
|
10
|
+
export type { UseCeriousScrollOptions, UseCeriousScrollResult, CeriousScrollOptions, } from './use-cerious-scroll';
|
|
11
11
|
export type { CeriousViewportChangeDetail } from './viewport-change';
|
|
12
12
|
export { CeriousScroll as CeriousScrollEngine } from '@ceriousdevtech/cerious-scroll';
|
|
13
|
-
export type {
|
|
13
|
+
export type { KeyboardNavigationOptions, TouchNavigationOptions, WheelNavigationOptions, ElementRenderer, ElementHeightCalculator, ScrollResult, MeasuredViewportRange, HeightProvider, MasonryDeterminism, MasonryOptions, } from '@ceriousdevtech/cerious-scroll';
|
package/dist/index.js
CHANGED
|
@@ -1,145 +1,177 @@
|
|
|
1
|
-
import { getCurrentInstance as
|
|
2
|
-
import { CeriousScroll as
|
|
3
|
-
import { CeriousScroll as
|
|
4
|
-
const
|
|
5
|
-
function
|
|
6
|
-
const
|
|
7
|
-
if (
|
|
8
|
-
const
|
|
9
|
-
return
|
|
1
|
+
import { getCurrentInstance as z, ref as D, shallowRef as G, onMounted as J, onBeforeUnmount as K, watch as Q, toValue as y, render as A, effectScope as L, watchEffect as $, isVNode as B, h as V, Fragment as x, defineComponent as Z } from "vue";
|
|
2
|
+
import { CeriousScroll as ee } from "@ceriousdevtech/cerious-scroll";
|
|
3
|
+
import { CeriousScroll as pe } from "@ceriousdevtech/cerious-scroll";
|
|
4
|
+
const U = "data-cerious-scroll-content", O = "data-cerious-scroll-row";
|
|
5
|
+
function te(e) {
|
|
6
|
+
const c = e.querySelector(`[${U}]`);
|
|
7
|
+
if (c) return c;
|
|
8
|
+
const s = document.createElement("div");
|
|
9
|
+
return s.setAttribute(U, ""), s.style.position = "relative", s.style.width = "100%", s.style.height = "100%", s.style.overflowY = "clip", s.style.overflowX = "auto", e.appendChild(s), s;
|
|
10
10
|
}
|
|
11
|
-
function
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
percentage:
|
|
16
|
-
currentElement:
|
|
17
|
-
scrollOffset:
|
|
18
|
-
result: { element:
|
|
11
|
+
function re(e, c) {
|
|
12
|
+
const s = (p) => {
|
|
13
|
+
const o = p.detail;
|
|
14
|
+
o && c({
|
|
15
|
+
percentage: o.percentage,
|
|
16
|
+
currentElement: o.currentElement,
|
|
17
|
+
scrollOffset: o.scrollOffset,
|
|
18
|
+
result: { element: o.result.element, offset: o.result.offset }
|
|
19
19
|
});
|
|
20
|
-
},
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
percentage:
|
|
24
|
-
currentElement:
|
|
25
|
-
scrollOffset:
|
|
26
|
-
result: { element:
|
|
20
|
+
}, w = (p) => {
|
|
21
|
+
const o = p.detail;
|
|
22
|
+
o && c({
|
|
23
|
+
percentage: o.percentage,
|
|
24
|
+
currentElement: o.element,
|
|
25
|
+
scrollOffset: o.scrollOffset,
|
|
26
|
+
result: { element: o.element, offset: o.scrollOffset }
|
|
27
27
|
});
|
|
28
28
|
};
|
|
29
|
-
return e.addEventListener("cerious-viewport-change",
|
|
30
|
-
e.removeEventListener("cerious-viewport-change",
|
|
29
|
+
return e.addEventListener("cerious-viewport-change", s), e.addEventListener("viewport-change", w), () => {
|
|
30
|
+
e.removeEventListener("cerious-viewport-change", s), e.removeEventListener("viewport-change", w);
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
|
-
function
|
|
34
|
-
const
|
|
35
|
-
if (
|
|
33
|
+
function W(e, c) {
|
|
34
|
+
const s = typeof e == "number" ? e : typeof c == "number" ? c : void 0;
|
|
35
|
+
if (s === void 0 || Number.isNaN(s))
|
|
36
36
|
throw new Error("useCeriousScroll: provide `totalElements` or `items`.");
|
|
37
|
-
return Math.max(1, Math.floor(
|
|
37
|
+
return Math.max(1, Math.floor(s));
|
|
38
38
|
}
|
|
39
|
-
function
|
|
40
|
-
var
|
|
41
|
-
const
|
|
42
|
-
let
|
|
43
|
-
const
|
|
44
|
-
let
|
|
45
|
-
const
|
|
39
|
+
function ne(e) {
|
|
40
|
+
var q, _;
|
|
41
|
+
const c = z(), s = c == null ? void 0 : c.provides, w = c ? { ...c.appContext, provides: s ?? c.appContext.provides } : void 0, p = D(null), o = G(null);
|
|
42
|
+
let r = null;
|
|
43
|
+
const d = /* @__PURE__ */ new Map(), T = ((q = e.options) == null ? void 0 : q.layout) === "table", b = ((_ = e.options) == null ? void 0 : _.layout) === "masonry";
|
|
44
|
+
let g = null;
|
|
45
|
+
const X = (t) => {
|
|
46
46
|
if (!e.renderHeader) return;
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
() =>
|
|
47
|
+
g == null || g();
|
|
48
|
+
const n = w ?? null, l = L(!0);
|
|
49
|
+
l.run(
|
|
50
|
+
() => $(
|
|
51
51
|
() => {
|
|
52
|
-
const u = e.renderHeader(),
|
|
53
|
-
for (const
|
|
54
|
-
const
|
|
55
|
-
|
|
52
|
+
const u = e.renderHeader(), m = Array.isArray(u) ? u : [u];
|
|
53
|
+
for (const i of m) B(i) && (i.appContext = n);
|
|
54
|
+
const a = V(x, m);
|
|
55
|
+
a.appContext = n, A(a, t);
|
|
56
56
|
},
|
|
57
57
|
{ flush: "sync" }
|
|
58
58
|
)
|
|
59
|
-
),
|
|
60
|
-
},
|
|
61
|
-
if (e.getItem) return e.getItem(
|
|
62
|
-
const
|
|
63
|
-
return
|
|
64
|
-
},
|
|
65
|
-
const
|
|
59
|
+
), g = () => l.stop();
|
|
60
|
+
}, j = () => y(e.autoRender) ?? !0, Y = (t) => {
|
|
61
|
+
if (e.getItem) return e.getItem(t);
|
|
62
|
+
const n = y(e.items);
|
|
63
|
+
return n ? n[t] : void 0;
|
|
64
|
+
}, M = (t, n) => {
|
|
65
|
+
const l = w ?? null, u = L(!0);
|
|
66
66
|
return u.run(
|
|
67
|
-
() =>
|
|
67
|
+
() => $(
|
|
68
68
|
() => {
|
|
69
|
-
const
|
|
70
|
-
for (const C of
|
|
71
|
-
|
|
72
|
-
const
|
|
73
|
-
|
|
69
|
+
const m = e.renderItem(Y(t), t), a = Array.isArray(m) ? m : [m];
|
|
70
|
+
for (const C of a)
|
|
71
|
+
B(C) && (C.appContext = l);
|
|
72
|
+
const i = V(x, a);
|
|
73
|
+
i.appContext = l, A(i, n);
|
|
74
74
|
},
|
|
75
75
|
{ flush: "sync" }
|
|
76
76
|
)
|
|
77
77
|
), () => u.stop();
|
|
78
|
-
},
|
|
79
|
-
|
|
80
|
-
},
|
|
78
|
+
}, N = (t) => {
|
|
79
|
+
t.stop(), A(null, t.mount), t.mount.remove();
|
|
80
|
+
}, k = (t, n) => {
|
|
81
|
+
let l = n.querySelector(`[${O}]`);
|
|
82
|
+
if (l || (l = document.createElement("div"), n.appendChild(l)), l.setAttribute(O, String(t)), n.dataset.ceriousMasonry === "probe") {
|
|
83
|
+
const a = M(t, l);
|
|
84
|
+
queueMicrotask(() => {
|
|
85
|
+
a(), A(null, l);
|
|
86
|
+
});
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
d.forEach((a, i) => {
|
|
90
|
+
a.el === n && i !== t && (a.stop(), d.delete(i));
|
|
91
|
+
});
|
|
92
|
+
const u = d.get(t);
|
|
93
|
+
if ((u == null ? void 0 : u.mount) === l) return;
|
|
94
|
+
const m = M(t, l);
|
|
95
|
+
d.set(t, { el: n, mount: l, stop: m });
|
|
96
|
+
}, v = () => {
|
|
81
97
|
var C;
|
|
82
|
-
if (!
|
|
83
|
-
const { scroller:
|
|
84
|
-
const
|
|
85
|
-
T && (
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
},
|
|
89
|
-
return
|
|
90
|
-
|
|
91
|
-
}), (C = e.onMeasuredViewport) == null || C.call(e,
|
|
92
|
-
},
|
|
93
|
-
if (!
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
},
|
|
97
|
-
var
|
|
98
|
-
const
|
|
99
|
-
...
|
|
98
|
+
if (!r) return null;
|
|
99
|
+
const { scroller: t, contentEl: n, container: l } = r, u = l.clientHeight || l.offsetHeight || 0, m = (f, E) => {
|
|
100
|
+
const h = document.createElement("div");
|
|
101
|
+
T && (h.style.display = "contents"), h.setAttribute(O, String(f)), E.appendChild(h);
|
|
102
|
+
const R = M(f, h);
|
|
103
|
+
d.set(f, { el: E, mount: h, stop: R });
|
|
104
|
+
}, a = t.renderViewport(u, n, m), i = b ? null : new Set(t.getRenderedIndices());
|
|
105
|
+
return d.forEach((f, E) => {
|
|
106
|
+
(b ? !f.el.isConnected || f.el.dataset.elementIndex !== String(E) : !i.has(E)) && (N(f), d.delete(E));
|
|
107
|
+
}), (C = e.onMeasuredViewport) == null || C.call(e, a), a;
|
|
108
|
+
}, P = (t) => {
|
|
109
|
+
if (!r) return;
|
|
110
|
+
const n = r;
|
|
111
|
+
r = null, n.unsubscribe(), d.forEach((l) => N(l)), d.clear(), g == null || g(), g = null, n.scroller.detachScrollbar(n.container), n.scroller.dispose(), n.contentEl.textContent = "", o.value = null;
|
|
112
|
+
}, H = (t) => {
|
|
113
|
+
var h, R, F;
|
|
114
|
+
const n = e.options ?? {}, l = b ? t : te(t), u = n.onScroll, { masonry: m, ...a } = n, i = {
|
|
115
|
+
...a,
|
|
100
116
|
onScroll: () => {
|
|
101
|
-
u == null || u(),
|
|
117
|
+
u == null || u(), j() && v();
|
|
102
118
|
}
|
|
103
119
|
};
|
|
120
|
+
if (b) {
|
|
121
|
+
if (!m)
|
|
122
|
+
throw new Error("useCeriousScroll: layout 'masonry' requires `options.masonry`.");
|
|
123
|
+
i.masonry = {
|
|
124
|
+
...m,
|
|
125
|
+
renderItem: k
|
|
126
|
+
};
|
|
127
|
+
}
|
|
104
128
|
if (T && e.renderHeader) {
|
|
105
|
-
const
|
|
129
|
+
const I = (h = n.table) == null ? void 0 : h.header;
|
|
106
130
|
i.table = {
|
|
107
|
-
...
|
|
108
|
-
header: (
|
|
109
|
-
|
|
131
|
+
...n.table,
|
|
132
|
+
header: (S) => {
|
|
133
|
+
I == null || I(S), X(S);
|
|
110
134
|
}
|
|
111
135
|
};
|
|
112
136
|
}
|
|
113
|
-
const
|
|
114
|
-
var
|
|
115
|
-
(
|
|
137
|
+
const C = W(y(e.totalElements), ((R = y(e.items)) == null ? void 0 : R.length) ?? null), f = new ee(t, C, i), E = re(t, (I) => {
|
|
138
|
+
var S;
|
|
139
|
+
(S = e.onViewportChange) == null || S.call(e, I);
|
|
116
140
|
});
|
|
117
|
-
|
|
141
|
+
r = { scroller: f, contentEl: l, container: t, unsubscribe: E }, o.value = f, (F = e.onReady) == null || F.call(e, f), j() && requestAnimationFrame(() => v());
|
|
118
142
|
};
|
|
119
|
-
return
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
}),
|
|
123
|
-
|
|
124
|
-
}),
|
|
125
|
-
() => [
|
|
143
|
+
return J(() => {
|
|
144
|
+
const t = p.value;
|
|
145
|
+
t && H(t);
|
|
146
|
+
}), K(() => {
|
|
147
|
+
P();
|
|
148
|
+
}), Q(
|
|
149
|
+
() => [y(e.totalElements), y(e.items)],
|
|
126
150
|
() => {
|
|
127
|
-
var
|
|
128
|
-
if (!
|
|
129
|
-
const
|
|
130
|
-
|
|
151
|
+
var l;
|
|
152
|
+
if (!r) return;
|
|
153
|
+
const t = W(y(e.totalElements), ((l = y(e.items)) == null ? void 0 : l.length) ?? null);
|
|
154
|
+
if ((b ? r.scroller.itemCount : r.scroller.totalElements) !== t) {
|
|
155
|
+
if (b) {
|
|
156
|
+
const u = p.value;
|
|
157
|
+
P(), u && H(u);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
r.scroller.updateTotalElements(t), r.scroller.currentElement > t - 1 && r.scroller.jumpToElement(t - 1), j() && v(), r.scroller.syncScrollbar();
|
|
161
|
+
}
|
|
131
162
|
}
|
|
132
163
|
), {
|
|
133
|
-
containerRef:
|
|
134
|
-
scroller:
|
|
135
|
-
render:
|
|
136
|
-
jumpToElement: (
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
164
|
+
containerRef: p,
|
|
165
|
+
scroller: o,
|
|
166
|
+
render: v,
|
|
167
|
+
jumpToElement: (t) => r ? (r.scroller.jumpToElement(t), v()) : null,
|
|
168
|
+
jumpToItem: (t, n = 0) => r ? (r.scroller.jumpToItem(t, n), v()) : null,
|
|
169
|
+
scrollToPercentage: (t) => r ? (r.scroller.handleScrollPercentage(t), v()) : null,
|
|
170
|
+
reset: () => r ? (r.scroller.reset(), v()) : null,
|
|
171
|
+
recalculate: () => r ? (r.scroller.clearAllCaches(), v()) : null
|
|
140
172
|
};
|
|
141
173
|
}
|
|
142
|
-
const
|
|
174
|
+
const me = Z({
|
|
143
175
|
name: "CeriousScroll",
|
|
144
176
|
props: {
|
|
145
177
|
/** Total item count. Falls back to `items.length` when omitted. */
|
|
@@ -166,50 +198,51 @@ const le = D({
|
|
|
166
198
|
/** Emitted once the underlying engine instance is ready (and after recreation). */
|
|
167
199
|
ready: (e) => !0
|
|
168
200
|
},
|
|
169
|
-
setup(e, { slots:
|
|
170
|
-
const
|
|
201
|
+
setup(e, { slots: c, emit: s, expose: w }) {
|
|
202
|
+
const p = (r, d) => {
|
|
171
203
|
var T;
|
|
172
|
-
return e.renderItem ? e.renderItem(
|
|
173
|
-
},
|
|
204
|
+
return e.renderItem ? e.renderItem(r, d) : (T = c.item) == null ? void 0 : T.call(c, { item: r, index: d });
|
|
205
|
+
}, o = ne({
|
|
174
206
|
totalElements: () => e.totalElements,
|
|
175
207
|
items: () => e.items,
|
|
176
|
-
getItem: e.getItem ? (
|
|
177
|
-
renderItem:
|
|
208
|
+
getItem: e.getItem ? (r) => e.getItem(r) : void 0,
|
|
209
|
+
renderItem: p,
|
|
178
210
|
// Table mode: declarative header. Render the `#header` slot (a <tr> of
|
|
179
211
|
// <th>s) into the engine's <thead>. `undefined` when no slot is provided.
|
|
180
|
-
renderHeader:
|
|
212
|
+
renderHeader: c.header ? () => c.header() : void 0,
|
|
181
213
|
// Engine options are consumed at creation; reading the prop once matches
|
|
182
214
|
// that contract (remount via `:key` to apply new engine options).
|
|
183
215
|
options: e.options,
|
|
184
216
|
autoRender: () => e.autoRender,
|
|
185
|
-
onViewportChange: (
|
|
186
|
-
onMeasuredViewport: (
|
|
187
|
-
onReady: (
|
|
217
|
+
onViewportChange: (r) => s("viewport-change", r),
|
|
218
|
+
onMeasuredViewport: (r) => s("measured-viewport", r),
|
|
219
|
+
onReady: (r) => s("ready", r)
|
|
188
220
|
});
|
|
189
|
-
return
|
|
221
|
+
return w({
|
|
190
222
|
/** The underlying engine instance (`null` before mount). */
|
|
191
|
-
scroller:
|
|
192
|
-
render:
|
|
193
|
-
jumpToElement:
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
223
|
+
scroller: o.scroller,
|
|
224
|
+
render: o.render,
|
|
225
|
+
jumpToElement: o.jumpToElement,
|
|
226
|
+
jumpToItem: o.jumpToItem,
|
|
227
|
+
scrollToPercentage: o.scrollToPercentage,
|
|
228
|
+
reset: o.reset,
|
|
229
|
+
recalculate: o.recalculate
|
|
197
230
|
}), () => {
|
|
198
|
-
var
|
|
199
|
-
return
|
|
231
|
+
var r;
|
|
232
|
+
return V(
|
|
200
233
|
"div",
|
|
201
234
|
{
|
|
202
|
-
ref:
|
|
235
|
+
ref: o.containerRef,
|
|
203
236
|
// User-supplied class/style/attrs fall through and merge onto this root.
|
|
204
237
|
style: { position: "relative", overflow: "hidden" }
|
|
205
238
|
},
|
|
206
|
-
(
|
|
239
|
+
(r = c.default) == null ? void 0 : r.call(c)
|
|
207
240
|
);
|
|
208
241
|
};
|
|
209
242
|
}
|
|
210
243
|
});
|
|
211
244
|
export {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
245
|
+
me as CeriousScroll,
|
|
246
|
+
pe as CeriousScrollEngine,
|
|
247
|
+
ne as useCeriousScroll
|
|
215
248
|
};
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { MaybeRefOrGetter, Ref, VNodeChild } from 'vue';
|
|
2
|
-
import { CeriousScroll as CeriousScrollEngine, CeriousScrollOptions, MeasuredViewportRange } from '@ceriousdevtech/cerious-scroll';
|
|
2
|
+
import { CeriousScroll as CeriousScrollEngine, CeriousScrollOptions as CoreCeriousScrollOptions, MasonryOptions, MeasuredViewportRange } from '@ceriousdevtech/cerious-scroll';
|
|
3
3
|
import { CeriousViewportChangeDetail } from './viewport-change';
|
|
4
|
+
/** Engine options adapted for Vue-owned Masonry rendering. */
|
|
5
|
+
export type CeriousScrollOptions = Omit<CoreCeriousScrollOptions, 'masonry'> & {
|
|
6
|
+
masonry?: Omit<MasonryOptions, 'renderItem'> & {
|
|
7
|
+
/** Accepted for backward compatibility; Vue supplies the active renderer. */
|
|
8
|
+
renderItem?: MasonryOptions['renderItem'];
|
|
9
|
+
};
|
|
10
|
+
};
|
|
4
11
|
export interface UseCeriousScrollOptions<TItem = unknown> {
|
|
5
12
|
/** Total number of items. Falls back to `items.length` when omitted. */
|
|
6
13
|
totalElements?: MaybeRefOrGetter<number | null | undefined>;
|
|
@@ -35,6 +42,8 @@ export interface UseCeriousScrollResult {
|
|
|
35
42
|
render: () => MeasuredViewportRange | null;
|
|
36
43
|
/** Jump to an element index, then render. */
|
|
37
44
|
jumpToElement: (index: number) => MeasuredViewportRange | null;
|
|
45
|
+
/** Masonry mode only: jump to a card index, then render. */
|
|
46
|
+
jumpToItem: (index: number, screenOffset?: number) => MeasuredViewportRange | null;
|
|
38
47
|
/** Scroll to a percentage (0..100), then render. */
|
|
39
48
|
scrollToPercentage: (percentage: number) => MeasuredViewportRange | null;
|
|
40
49
|
/** Reset to the top, then render. */
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ceriousdevtech/vue-cerious-scroll",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Vue 3 bindings for CeriousScroll —
|
|
3
|
+
"version": "1.1.1",
|
|
4
|
+
"description": "Vue 3 bindings for CeriousScroll — virtualized lists, native tables, and Masonry grids",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
7
7
|
"module": "./dist/index.js",
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
"large-lists",
|
|
46
46
|
"data-grid",
|
|
47
47
|
"variable-height",
|
|
48
|
+
"masonry",
|
|
48
49
|
"o1-memory",
|
|
49
50
|
"cerious-scroll"
|
|
50
51
|
],
|
|
@@ -57,7 +58,7 @@
|
|
|
57
58
|
"demo:build": "vite build --config demo/vite.config.ts"
|
|
58
59
|
},
|
|
59
60
|
"dependencies": {
|
|
60
|
-
"@ceriousdevtech/cerious-scroll": "^1.
|
|
61
|
+
"@ceriousdevtech/cerious-scroll": "^1.1.1"
|
|
61
62
|
},
|
|
62
63
|
"peerDependencies": {
|
|
63
64
|
"vue": "^3.3.0"
|