@ceriousdevtech/vue-cerious-scroll 1.0.6 → 1.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/CHANGELOG.md +20 -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 +166 -144
- package/dist/use-cerious-scroll.d.ts +12 -2
- package/package.json +4 -3
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,26 @@ 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.0] - 2026-08-22
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Declarative canonical and dynamic Masonry support through the existing `#item` slot or `render-item` prop.
|
|
12
|
+
- Dynamic-height probe rendering with short-lived Vue trees that are disposed after synchronous measurement.
|
|
13
|
+
- `jumpToItem(index, screenOffset?)` on the composable result and exposed component API.
|
|
14
|
+
- `CeriousScrollOptions` support for wrapper-owned Masonry rendering, canonical/dynamic demo routes, and regression coverage for both modes.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- Updated `@ceriousdevtech/cerious-scroll` to `^1.1.0` and re-exported the new Masonry and height-provider types.
|
|
18
|
+
- Masonry card-count changes recreate card-derived segment state; list and table count changes continue updating in place.
|
|
19
|
+
|
|
20
|
+
## [1.0.7] - 2026-06-24
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
- **`provide()` values from the component using `<CeriousScroll>` now reach `inject()` inside rows** ([#1](https://github.com/ceriousdevtech/vue-cerious-scroll/issues/1)). Rows (and the table header) are rendered as detached vnode trees via Vue's `render()`, so injection resolved only against the app context's `provides` — app-level/plugin provides worked, but local `provide()` on the owning component did not. The wrapper now renders rows with the owning component instance's `provides` (which prototype-chains to the app provides), so component-level, plugin, and app-level provide/inject all work inside virtualized rows. When the owner provides nothing, behavior is unchanged.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- Updated the core engine dependency to `@ceriousdevtech/cerious-scroll@^1.0.8` (native-scrollbar drag rendering is now coalesced to one render per frame, with no per-row layout thrash on fast drags).
|
|
27
|
+
|
|
8
28
|
## [1.0.6] - 2026-06-11
|
|
9
29
|
|
|
10
30
|
### Changed
|
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
|
|
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,156 +1,177 @@
|
|
|
1
|
-
import { getCurrentInstance as
|
|
2
|
-
import { CeriousScroll as
|
|
3
|
-
import { CeriousScroll as
|
|
4
|
-
const
|
|
5
|
-
function
|
|
6
|
-
const c = e.querySelector(`[${
|
|
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
7
|
if (c) return c;
|
|
8
|
-
const
|
|
9
|
-
return
|
|
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 c = (
|
|
42
|
-
let
|
|
43
|
-
const
|
|
44
|
-
let
|
|
45
|
-
const
|
|
46
|
-
let f = null;
|
|
47
|
-
const L = (t) => {
|
|
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) => {
|
|
48
46
|
if (!e.renderHeader) return;
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
() =>
|
|
47
|
+
g == null || g();
|
|
48
|
+
const n = w ?? null, l = L(!0);
|
|
49
|
+
l.run(
|
|
50
|
+
() => $(
|
|
53
51
|
() => {
|
|
54
|
-
const
|
|
55
|
-
for (const i of
|
|
56
|
-
const
|
|
57
|
-
|
|
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);
|
|
58
56
|
},
|
|
59
57
|
{ flush: "sync" }
|
|
60
58
|
)
|
|
61
|
-
),
|
|
62
|
-
},
|
|
59
|
+
), g = () => l.stop();
|
|
60
|
+
}, j = () => y(e.autoRender) ?? !0, Y = (t) => {
|
|
63
61
|
if (e.getItem) return e.getItem(t);
|
|
64
|
-
const
|
|
65
|
-
return
|
|
66
|
-
},
|
|
67
|
-
const
|
|
68
|
-
return
|
|
69
|
-
() =>
|
|
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
|
+
return u.run(
|
|
67
|
+
() => $(
|
|
70
68
|
() => {
|
|
71
|
-
const
|
|
72
|
-
for (const
|
|
73
|
-
|
|
74
|
-
const i =
|
|
75
|
-
i.appContext =
|
|
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);
|
|
76
74
|
},
|
|
77
75
|
{ flush: "sync" }
|
|
78
76
|
)
|
|
79
|
-
), () =>
|
|
80
|
-
},
|
|
81
|
-
t.stop(),
|
|
82
|
-
},
|
|
83
|
-
|
|
84
|
-
if (
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
if (
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
77
|
+
), () => u.stop();
|
|
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 = () => {
|
|
97
|
+
var C;
|
|
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,
|
|
105
116
|
onScroll: () => {
|
|
106
|
-
|
|
117
|
+
u == null || u(), j() && v();
|
|
107
118
|
}
|
|
108
119
|
};
|
|
109
|
-
if (b
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
+
}
|
|
128
|
+
if (T && e.renderHeader) {
|
|
129
|
+
const I = (h = n.table) == null ? void 0 : h.header;
|
|
130
|
+
i.table = {
|
|
131
|
+
...n.table,
|
|
132
|
+
header: (S) => {
|
|
133
|
+
I == null || I(S), X(S);
|
|
115
134
|
}
|
|
116
135
|
};
|
|
117
136
|
}
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
var R;
|
|
122
|
-
(R = e.onViewportChange) == null || R.call(e, C);
|
|
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);
|
|
123
140
|
});
|
|
124
|
-
|
|
125
|
-
}, B = () => {
|
|
126
|
-
A(!0);
|
|
127
|
-
const t = n.value;
|
|
128
|
-
t && M(t);
|
|
141
|
+
r = { scroller: f, contentEl: l, container: t, unsubscribe: E }, o.value = f, (F = e.onReady) == null || F.call(e, f), j() && requestAnimationFrame(() => v());
|
|
129
142
|
};
|
|
130
|
-
return
|
|
131
|
-
const t =
|
|
132
|
-
t &&
|
|
133
|
-
}),
|
|
134
|
-
|
|
135
|
-
}),
|
|
136
|
-
() => [
|
|
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)],
|
|
137
150
|
() => {
|
|
138
151
|
var l;
|
|
139
|
-
if (!
|
|
140
|
-
const t =
|
|
141
|
-
|
|
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
|
+
}
|
|
142
162
|
}
|
|
143
163
|
), {
|
|
144
|
-
containerRef:
|
|
145
|
-
scroller:
|
|
146
|
-
render:
|
|
147
|
-
jumpToElement: (t) =>
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
|
151
172
|
};
|
|
152
173
|
}
|
|
153
|
-
const
|
|
174
|
+
const me = Z({
|
|
154
175
|
name: "CeriousScroll",
|
|
155
176
|
props: {
|
|
156
177
|
/** Total item count. Falls back to `items.length` when omitted. */
|
|
@@ -177,15 +198,15 @@ const ce = G({
|
|
|
177
198
|
/** Emitted once the underlying engine instance is ready (and after recreation). */
|
|
178
199
|
ready: (e) => !0
|
|
179
200
|
},
|
|
180
|
-
setup(e, { slots: c, emit:
|
|
181
|
-
const
|
|
182
|
-
var
|
|
183
|
-
return e.renderItem ? e.renderItem(
|
|
184
|
-
},
|
|
201
|
+
setup(e, { slots: c, emit: s, expose: w }) {
|
|
202
|
+
const p = (r, d) => {
|
|
203
|
+
var T;
|
|
204
|
+
return e.renderItem ? e.renderItem(r, d) : (T = c.item) == null ? void 0 : T.call(c, { item: r, index: d });
|
|
205
|
+
}, o = ne({
|
|
185
206
|
totalElements: () => e.totalElements,
|
|
186
207
|
items: () => e.items,
|
|
187
|
-
getItem: e.getItem ? (
|
|
188
|
-
renderItem:
|
|
208
|
+
getItem: e.getItem ? (r) => e.getItem(r) : void 0,
|
|
209
|
+
renderItem: p,
|
|
189
210
|
// Table mode: declarative header. Render the `#header` slot (a <tr> of
|
|
190
211
|
// <th>s) into the engine's <thead>. `undefined` when no slot is provided.
|
|
191
212
|
renderHeader: c.header ? () => c.header() : void 0,
|
|
@@ -193,34 +214,35 @@ const ce = G({
|
|
|
193
214
|
// that contract (remount via `:key` to apply new engine options).
|
|
194
215
|
options: e.options,
|
|
195
216
|
autoRender: () => e.autoRender,
|
|
196
|
-
onViewportChange: (
|
|
197
|
-
onMeasuredViewport: (
|
|
198
|
-
onReady: (
|
|
217
|
+
onViewportChange: (r) => s("viewport-change", r),
|
|
218
|
+
onMeasuredViewport: (r) => s("measured-viewport", r),
|
|
219
|
+
onReady: (r) => s("ready", r)
|
|
199
220
|
});
|
|
200
|
-
return
|
|
221
|
+
return w({
|
|
201
222
|
/** The underlying engine instance (`null` before mount). */
|
|
202
|
-
scroller:
|
|
203
|
-
render:
|
|
204
|
-
jumpToElement:
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
|
208
230
|
}), () => {
|
|
209
|
-
var
|
|
210
|
-
return
|
|
231
|
+
var r;
|
|
232
|
+
return V(
|
|
211
233
|
"div",
|
|
212
234
|
{
|
|
213
|
-
ref:
|
|
235
|
+
ref: o.containerRef,
|
|
214
236
|
// User-supplied class/style/attrs fall through and merge onto this root.
|
|
215
237
|
style: { position: "relative", overflow: "hidden" }
|
|
216
238
|
},
|
|
217
|
-
(
|
|
239
|
+
(r = c.default) == null ? void 0 : r.call(c)
|
|
218
240
|
);
|
|
219
241
|
};
|
|
220
242
|
}
|
|
221
243
|
});
|
|
222
244
|
export {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
245
|
+
me as CeriousScroll,
|
|
246
|
+
pe as CeriousScrollEngine,
|
|
247
|
+
ne as useCeriousScroll
|
|
226
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. */
|
|
@@ -61,6 +70,7 @@ export interface UseCeriousScrollResult {
|
|
|
61
70
|
*
|
|
62
71
|
* Rows are rendered with the host component's `appContext`, so globally
|
|
63
72
|
* registered components, directives, and installed plugins are available inside
|
|
64
|
-
* each row
|
|
73
|
+
* each row — and with the owning component instance's `provides`, so local
|
|
74
|
+
* `provide()` values reach `inject()` inside rows too (see below).
|
|
65
75
|
*/
|
|
66
76
|
export declare function useCeriousScroll<TItem = unknown>(opts: UseCeriousScrollOptions<TItem>): UseCeriousScrollResult;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ceriousdevtech/vue-cerious-scroll",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"description": "Vue 3 bindings for CeriousScroll —
|
|
3
|
+
"version": "1.1.0",
|
|
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.0
|
|
61
|
+
"@ceriousdevtech/cerious-scroll": "^1.1.0"
|
|
61
62
|
},
|
|
62
63
|
"peerDependencies": {
|
|
63
64
|
"vue": "^3.3.0"
|