@yasanchezz/vue-infinite-grid 0.1.10 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,7 +18,7 @@ Register it as a global component in `main.js`
18
18
 
19
19
  ``` js
20
20
  import VueGrid from '@yasanchezz/vue-infinite-grid';
21
- import '@yasanchezz/vue-infinite-grid/dist/style.css';
21
+ import '@yasanchezz/vue-infinite-grid/dist/vue-infinite-grid.css';
22
22
 
23
23
  createApp(App)
24
24
  .use(VueGrid)
@@ -33,7 +33,7 @@ createApp(App)
33
33
  ref="grid"
34
34
  :rows="rows"
35
35
  :rows-count="10"
36
- :height="200"
36
+ :row-initial-height="200"
37
37
  class="my-grid"
38
38
  >
39
39
  <template #row="{ row }">
@@ -59,17 +59,20 @@ createApp(App)
59
59
  ```
60
60
 
61
61
  Each item in `rows` must have a unique `id: string`, which `VueGrid` uses as
62
- the `:key` for the row and to track its measured height.
62
+ the `:key` for the row and to track its measured height. A row can also set
63
+ `sticky: true` to stay pinned to the top of the scroll container while it's
64
+ part of the rendered window (see [Styling notes](#styling-notes)).
63
65
 
64
66
  ## Props
65
67
 
66
68
  | Prop | Type | Required | Default | Description |
67
69
  | --- | --- | --- | --- | --- |
68
- | `rows` | `GridRow[]` (`{ id: string }`) | yes | - | The full list of rows. Only a window of these is actually rendered at a time. |
70
+ | `rows` | `GridRow[]` (`{ id: string, sticky?: boolean }`) | yes | - | The full list of rows. Only a window of these is actually rendered at a time. |
69
71
  | `rowsCount` | `number` | yes | - | How many rows to render at once (the size of the rendered window). |
70
- | `height` | `number` | yes | - | Estimated row height in px, used for scroll/offset math before a row has actually been measured. |
72
+ | `rowInitialHeight` | `number` | yes | - | Estimated row height in px, used for scroll/offset math before a row has actually been measured. |
71
73
  | `container` | `Element \| null \| ((container: Element \| null) => Element \| null \| undefined)` | no | `document.documentElement` | The scrollable element to listen to. Can be a function that receives the grid's root element (useful for resolving a custom scroll container after mount). |
72
- | `autoCalculatedRowsCount` | `boolean` | no | `false` | When `true`, grows the rendered window beyond `rowsCount` based on measured row heights so it always covers the viewport (useful when actual row heights are smaller than the estimated `height`). |
74
+ | `autoCalculatedRowsCount` | `boolean` | no | `false` | When `true`, grows the rendered window beyond `rowsCount` based on measured row heights so it always covers the viewport (useful when actual row heights are smaller than the estimated `rowInitialHeight`). |
75
+ | `rowsOffset` | `number` | no | `rowInitialHeight * Math.trunc(rowsCount / 5)` (or `1000` if that's `0`) | Extra px of buffer kept rendered above/below the viewport before the window shifts, to reduce blank space while scrolling fast. |
73
76
 
74
77
  ## Slots
75
78
 
@@ -97,7 +100,12 @@ window.addEventListener('resize', () => grid.value.init()) // recalculate layout
97
100
  </script>
98
101
 
99
102
  <template>
100
- <VueGrid ref="grid" :rows="rows" :rows-count="10" :height="200" />
103
+ <VueGrid
104
+ ref="grid"
105
+ :rows="rows"
106
+ :rows-count="10"
107
+ :row-initial-height="200"
108
+ />
101
109
  </template>
102
110
  ```
103
111
 
@@ -107,3 +115,10 @@ window.addEventListener('resize', () => grid.value.init()) // recalculate layout
107
115
  column layout on the root element (e.g. `grid-template-columns`) and have
108
116
  each row use `display: grid; grid-template-columns: subgrid;` spanning
109
117
  `grid-column: 1 / -1` so it inherits those columns.
118
+
119
+ ### Sticky rows
120
+
121
+ Setting `sticky: true` on a row makes `VueGrid` add a `grid-row--sticky`
122
+ class to it (`position: sticky; top: 0;`) and keep that row rendered and
123
+ pinned to the top of the scroll container even after it scrolls out of the
124
+ regular rendered window, until a later sticky row takes its place.
package/dist/VueGrid.d.ts CHANGED
@@ -1,12 +1,14 @@
1
1
  declare const __VLS_export: <GridRow extends {
2
2
  id: string;
3
+ sticky?: boolean;
3
4
  }>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
4
5
  props: import('vue').PublicProps & __VLS_PrettifyLocal<{
5
6
  autoCalculatedRowsCount?: boolean;
6
7
  container?: (Element | null | undefined) | ((container: Element | null) => Element | null | undefined);
8
+ rowInitialHeight: number;
7
9
  rows: GridRow[];
8
10
  rowsCount: number;
9
- height: number;
11
+ rowsOffset?: number;
10
12
  }> & (typeof globalThis extends {
11
13
  __VLS_PROPS_FALLBACK: infer P;
12
14
  } ? P : {});
@@ -1,2 +1,2 @@
1
- .infinite-grid[data-v-923e3f26]{overflow-anchor:none;align-content:start;display:grid;overflow:clip}.grid-row[data-v-923e3f26]{grid-column:1/-1;grid-template-columns:subgrid;display:grid}.content[data-v-923e3f26]{display:contents}.before[data-v-923e3f26],.after[data-v-923e3f26]{pointer-events:none;grid-column:1/-1}
1
+ @layer VueGrid{.infinite-grid[data-v-145b839c]{overflow-anchor:none;align-content:start;width:max-content;display:grid;overflow:clip}.grid-row[data-v-145b839c]{grid-column:1/-1;grid-template-columns:subgrid;display:grid}.grid-row--sticky[data-v-145b839c]{position:sticky;top:0}.content[data-v-145b839c]{display:contents}.before[data-v-145b839c],.after[data-v-145b839c]{pointer-events:none;grid-column:1/-1}}
2
2
  /*$vite$:1*/
@@ -1,6 +1,6 @@
1
- import { Fragment as e, computed as t, createElementBlock as n, createElementVNode as r, defineComponent as i, normalizeStyle as a, onMounted as o, onUnmounted as s, openBlock as c, ref as l, renderList as u, renderSlot as d, shallowRef as f, useTemplateRef as p, watch as m, watchEffect as h } from "vue";
1
+ import { Fragment as e, computed as t, createElementBlock as n, createElementVNode as r, defineComponent as i, normalizeClass as a, normalizeStyle as o, onMounted as s, onUnmounted as c, openBlock as l, ref as u, renderList as ee, renderSlot as te, shallowRef as d, useTemplateRef as f, watch as p, watchEffect as m } from "vue";
2
2
  //#region src/VueGrid.vue?vue&type=script&setup=true&lang.ts
3
- var g = ["data-index"], _ = /*#__PURE__*/ ((e, t) => {
3
+ var h = ["data-index"], g = /*#__PURE__*/ ((e, t) => {
4
4
  let n = e.__vccOpts || e;
5
5
  for (let [e, r] of t) n[e] = r;
6
6
  return n;
@@ -13,135 +13,169 @@ var g = ["data-index"], _ = /*#__PURE__*/ ((e, t) => {
13
13
  },
14
14
  container: {
15
15
  type: [null, Function],
16
- default: document.documentElement
16
+ default: null
17
17
  },
18
+ rowInitialHeight: {},
18
19
  rows: {},
19
20
  rowsCount: {},
20
- height: {}
21
+ rowsOffset: { default: 0 }
21
22
  },
22
- setup(i, { expose: _ }) {
23
- let v = f([]), y = p("grid"), b = p("content"), x = t(() => i.container instanceof Function ? i.container(y.value) ?? document.documentElement : i.container ?? document.documentElement), S = t(() => x.value === document.documentElement || x.value === document.body ? window : x.value), C = /* @__PURE__ */ new Map(), w = i.height * Math.trunc(i.rowsCount / 5) || 1e3, T, E, D = i.rowsCount;
24
- function O() {
25
- if (i.autoCalculatedRowsCount) {
26
- let e = window.innerHeight + i.height, t = P - w, n = t < e ? Math.ceil(e / t * D) : D;
27
- D = n < i.rowsCount ? i.rowsCount : n;
28
- }
23
+ setup(i, { expose: g }) {
24
+ let _ = d([]), v = f("grid"), y = f("content"), b = t(() => i.container instanceof Function ? i.container(v.value) ?? document.documentElement : i.container ?? document.documentElement), x = t(() => b.value === document.documentElement || b.value === document.body ? window : b.value), S = /* @__PURE__ */ new Map(), C = i.rowsOffset || i.rowInitialHeight * Math.trunc(i.rowsCount / 5) || 1e3, w, T, E = /* @__PURE__ */ new Map();
25
+ function D() {
26
+ E = new Map(i.rows.map((e, t) => [e.id, t]));
27
+ }
28
+ let O = new Float32Array(i.rows.length);
29
+ function k() {
30
+ O = new Float32Array(i.rows.map((e) => S.get(e.id) ?? i.rowInitialHeight));
29
31
  }
30
- let k = 0;
31
- function A() {
32
- let e = 0, t = y.value;
33
- for (; t instanceof HTMLElement && t != x.value;) e += t.offsetTop, t = t.offsetParent;
34
- k = e;
32
+ function A(e, t) {
33
+ S.set(e, t);
34
+ let n = E.get(e);
35
+ n != null && (O[n] = t);
35
36
  }
36
- let j = l({ height: "0px" }), M = 0;
37
+ function j(e) {
38
+ return O.at(e) ?? i.rowInitialHeight;
39
+ }
40
+ let M = i.rowsCount;
37
41
  function N() {
38
- return M = i.rows.map((e) => C.get(e.id) ?? i.height).reduce((e, t) => e + t, 0), j.value.height = `${M}px`, M;
42
+ if (i.autoCalculatedRowsCount) {
43
+ let e = window.innerHeight + i.rowInitialHeight, t = z - C, n = t < e ? Math.ceil(e / t * M) : M;
44
+ M = Number.isFinite(n) && n >= i.rowsCount ? n : i.rowsCount;
45
+ }
39
46
  }
40
47
  let P = 0;
41
48
  function F() {
42
- P = v.value.map((e) => C.get(e.id) ?? i.height).reduce((e, t) => e + t, 0);
49
+ let e = 0, t = v.value;
50
+ for (; t instanceof HTMLElement && t != b.value;) e += t.offsetTop, t = t.offsetParent;
51
+ P = e;
43
52
  }
44
- let I = l({ height: "0px" }), L = 0;
53
+ let I = u({ height: "0px" }), L = 0;
45
54
  function R() {
46
- let e = x.value.scrollTop - w - k, t = 0;
47
- for (let n of i.rows) {
48
- let r = C.get(n.id) ?? i.height;
55
+ L = O.reduce((e, t) => e + t, 0), I.value.height = `${L}px`;
56
+ }
57
+ let z = 0, B = 0, V = M;
58
+ function H() {
59
+ z = O.slice(B, V).reduce((e, t) => e + t, 0);
60
+ }
61
+ let U = u({ height: "0px" }), W = 0;
62
+ function G() {
63
+ let e = b.value.scrollTop - C - P, t = 0;
64
+ for (let n of i.rows.keys()) {
65
+ let r = j(n);
49
66
  if (t + r > e) break;
50
67
  t += r;
51
68
  }
52
- let n = M - P, r = t > n ? n : t;
53
- return L = r, I.value.height = `${r}px`, r;
69
+ let n = L - z;
70
+ W = t > n ? n : t;
54
71
  }
55
- let z = l({ height: "0px" }), B = 0;
56
- function V() {
57
- B = M - L - P, z.value.height = `${B}px`;
72
+ let K = 0;
73
+ function q() {
74
+ let e = W - K;
75
+ e = e < 0 ? 0 : e, U.value.height = `${e}px`;
58
76
  }
59
- function H() {
60
- cancelAnimationFrame(T), T = requestAnimationFrame(() => {
61
- A(), F(), N(), R(), V();
62
- let e = 0, t = 0;
63
- for (let [n, r] of i.rows.entries()) {
64
- let a = C.get(r.id) ?? i.height;
65
- if (e += a, !(e <= L)) {
66
- t = n;
77
+ let J = u({ height: "0px" }), Y = 0;
78
+ function X() {
79
+ Y = L - W - z, J.value.height = `${Y}px`;
80
+ }
81
+ function Z() {
82
+ cancelAnimationFrame(w), w = requestAnimationFrame(() => {
83
+ F(), H(), R(), G(), q(), X();
84
+ let e = 0, t = 0, n, r;
85
+ for (let [a, o] of i.rows.entries()) {
86
+ let i = j(a);
87
+ if (e += i, e > W) {
88
+ t = a;
67
89
  break;
68
90
  }
91
+ o.sticky && (n = o, r = a, K = i);
69
92
  }
70
- let n = t + D > i.rows.length ? i.rows.slice(i.rows.length - D) : i.rows.slice(t, t + D);
71
- B || (n = i.rows.slice(i.rows.length - D)), (n.at(0) !== v.value.at(0) || n.length !== v.value.length) && (v.value = n), F(), N(), V();
93
+ [B, V] = t + M > i.rows.length || !Y ? [i.rows.length - M, i.rows.length] : [t, t + M];
94
+ let a = n && r != null && r < B ? [n] : [], o = a.length, s = i.rows.slice(B, V), c = a.concat(s);
95
+ o || (K = 0), (c.at(o) !== _.value.at(o) || c.length !== _.value.length) && (_.value = c), H(), R(), q(), X();
72
96
  });
73
97
  }
74
- let U = new ResizeObserver((e) => {
98
+ let Q = new ResizeObserver((e) => {
75
99
  for (let t of e) {
76
- let e = t.target, n = t.borderBoxSize.at(0)?.blockSize ?? i.height;
100
+ let e = t.target, n = t.borderBoxSize.at(0)?.blockSize ?? i.rowInitialHeight;
77
101
  if (e instanceof HTMLElement) {
78
102
  let t = e.dataset.index;
79
- t != null && C.set(t, n);
103
+ t != null && A(t, n);
80
104
  }
81
105
  }
82
- cancelAnimationFrame(E), E = requestAnimationFrame(() => {
83
- F(), O(), H();
106
+ cancelAnimationFrame(T), T = requestAnimationFrame(() => {
107
+ H(), N(), Z();
84
108
  });
85
- }), W = new MutationObserver((e) => {
86
- for (let t of e) t.addedNodes.values().filter((e) => e instanceof HTMLElement).forEach((e) => U.observe(e)), t.removedNodes.values().filter((e) => e instanceof HTMLElement).forEach((e) => U.unobserve(e));
109
+ }), $ = new MutationObserver((e) => {
110
+ for (let t of e) t.addedNodes.values().filter((e) => e instanceof HTMLElement).forEach((e) => Q.observe(e)), t.removedNodes.values().filter((e) => e instanceof HTMLElement).forEach((e) => Q.unobserve(e));
87
111
  });
88
- return h(() => {
89
- W.disconnect(), b.value && W.observe(b.value, { childList: !0 });
90
- }), m(() => i.rows, H, { flush: "post" }), m(S, (e, t) => {
91
- t?.removeEventListener("scroll", H), e.addEventListener("scroll", H, { passive: !0 });
92
- }, { immediate: !0 }), o(() => {
93
- H();
94
- }), s(() => {
95
- S.value.removeEventListener("scroll", H);
96
- }), _({
112
+ m(() => {
113
+ $.disconnect(), y.value && $.observe(y.value, { childList: !0 });
114
+ }), p(() => i.rows, () => {
115
+ D(), k(), Z();
116
+ }, { flush: "post" }), p(x, (e, t) => {
117
+ t?.removeEventListener("scroll", Z), e.addEventListener("scroll", Z, { passive: !0 });
118
+ }, { immediate: !0 }), s(() => {
119
+ D(), k(), Z();
120
+ }), c(() => {
121
+ Q.disconnect(), $.disconnect(), x.value.removeEventListener("scroll", Z), S.clear(), E.clear(), O = /* @__PURE__ */ new Float32Array();
122
+ }), g({
97
123
  init() {
98
- F(), O(), H();
124
+ H(), N(), Z();
99
125
  },
100
126
  async scrollTo(e) {
101
127
  let t = 0;
102
- for (let n of i.rows) {
103
- if (n.id === e) break;
104
- t += C.get(n.id) ?? i.height;
128
+ for (let [n, r] of i.rows.entries()) {
129
+ if (r.id === e) break;
130
+ t += j(n);
105
131
  }
106
- return x.value.scrollTo(0, t + k), new Promise((t) => {
107
- let n = x.value.querySelector(`[data-index="${e}"]`);
132
+ return b.value.scrollTo(0, t + P), new Promise((t) => {
133
+ let n = b.value.querySelector(`[data-index="${e}"]`);
108
134
  if (n instanceof HTMLElement) return t(n);
109
135
  let r = new MutationObserver((n) => {
110
136
  let i = n.flatMap((e) => Array.from(e.addedNodes)).filter((e) => e instanceof HTMLElement).find((t) => t.dataset.index === e);
111
137
  i && (t(i), r.disconnect());
112
138
  });
113
- h(() => {
114
- r.disconnect(), b.value && r.observe(b.value, { childList: !0 });
115
- }), s(() => r.disconnect());
139
+ m(() => {
140
+ r.disconnect(), y.value && r.observe(y.value, { childList: !0 });
141
+ }), c(() => r.disconnect());
116
142
  });
117
143
  }
118
- }), (t, i) => (c(), n("div", {
144
+ });
145
+ function ne(e) {
146
+ let t = "grid-row";
147
+ return {
148
+ [t]: !0,
149
+ [`${t}--sticky`]: e.sticky
150
+ };
151
+ }
152
+ return (t, i) => (l(), n("div", {
119
153
  ref_key: "grid",
120
- ref: y,
154
+ ref: v,
121
155
  class: "infinite-grid",
122
- style: a(j.value)
156
+ style: o(I.value)
123
157
  }, [
124
158
  r("div", {
125
159
  class: "before",
126
- style: a(I.value)
160
+ style: o(U.value)
127
161
  }, null, 4),
128
162
  r("div", {
129
163
  ref_key: "content",
130
- ref: b,
164
+ ref: y,
131
165
  class: "content"
132
- }, [(c(!0), n(e, null, u(v.value, (e) => (c(), n("div", {
166
+ }, [(l(!0), n(e, null, ee(_.value, (e) => (l(), n("div", {
133
167
  key: e.id,
134
- class: "grid-row",
168
+ class: a(ne(e)),
135
169
  "data-index": e.id
136
- }, [d(t.$slots, "row", { row: e }, void 0, !0)], 8, g))), 128))], 512),
170
+ }, [te(t.$slots, "row", { row: e }, void 0, !0)], 10, h))), 128))], 512),
137
171
  r("div", {
138
172
  class: "after",
139
- style: a(z.value)
173
+ style: o(J.value)
140
174
  }, null, 4)
141
175
  ], 4));
142
176
  }
143
- }), [["__scopeId", "data-v-923e3f26"]]), v = { install(e) {
144
- e.component("VueGrid", _);
177
+ }), [["__scopeId", "data-v-145b839c"]]), _ = { install(e) {
178
+ e.component("VueGrid", g);
145
179
  } };
146
180
  //#endregion
147
- export { v as default };
181
+ export { _ as default };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yasanchezz/vue-infinite-grid",
3
- "version": "0.1.10",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"
@@ -16,24 +16,25 @@
16
16
  "@stylistic/eslint-plugin": "^5.10.0",
17
17
  "@tsconfig/node24": "^24.0.4",
18
18
  "@types/node": "^24.13.3",
19
- "@typescript-eslint/utils": "^8.65.0",
19
+ "@typescript-eslint/utils": "^8.67.0",
20
20
  "@vitejs/plugin-vue": "^6.0.8",
21
21
  "@vue/eslint-config-typescript": "^14.9.0",
22
22
  "@vue/tsconfig": "^0.9.1",
23
- "eslint": "^10.7.0",
23
+ "eslint": "^10.8.1",
24
24
  "eslint-config-prettier": "^10.1.8",
25
25
  "eslint-plugin-oxlint": "~1.69.0",
26
26
  "eslint-plugin-vue": "~10.9.2",
27
27
  "jiti": "^2.7.0",
28
- "npm-run-all2": "^9.0.2",
28
+ "npm-run-all2": "^9.0.3",
29
29
  "oxfmt": "^0.54.0",
30
30
  "oxlint": "~1.69.0",
31
+ "sass-embedded": "^1.100.0",
31
32
  "typescript": "~6.0.3",
32
- "vite": "^8.1.5",
33
+ "vite": "^8.2.1",
33
34
  "vite-plugin-dts": "^5.0.3",
34
- "vite-plugin-vue-devtools": "^8.1.5",
35
+ "vite-plugin-vue-devtools": "^8.2.1",
35
36
  "vue-eslint-parser": "^10.4.1",
36
- "vue-tsc": "^3.3.8"
37
+ "vue-tsc": "^3.3.9"
37
38
  },
38
39
  "engines": {
39
40
  "node": ">= 24.12.0",
package/dist/favicon.ico DELETED
Binary file