@poveste/app 0.6.0 → 0.7.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.
@@ -124,7 +124,7 @@
124
124
  /* Cells are placed with `order`, so without this Tab and a screen reader walk
125
125
  them in slot order — the order they were recycled in, not the screen's.
126
126
  Chromium only for now; elsewhere the rule is ignored. */
127
- .poveste-story-variant-grid-cells[data-v-15980005] {
127
+ .poveste-story-variant-grid-cells[data-v-5f7bc1e1] {
128
128
  reading-flow: grid-order;
129
129
  }
130
130
  }
@@ -2,6 +2,6 @@ import _plugin_vue_export_helper_default from "../../_virtual/_plugin-vue_export
2
2
  import StoryVariantGrid_vue_vue_type_script_setup_true_lang_default from "./StoryVariantGrid.vue_vue_type_script_setup_true_lang.js";
3
3
  /* empty css */
4
4
  //#region src/app/components/story/StoryVariantGrid.vue
5
- var StoryVariantGrid_default = /*#__PURE__*/ _plugin_vue_export_helper_default(StoryVariantGrid_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-15980005"]]);
5
+ var StoryVariantGrid_default = /*#__PURE__*/ _plugin_vue_export_helper_default(StoryVariantGrid_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-5f7bc1e1"]]);
6
6
  //#endregion
7
7
  export { StoryVariantGrid_default as default };
@@ -1,7 +1,7 @@
1
1
  import { useStoryStore } from "../../stores/story.js";
2
2
  import { assignSlots, trimSlots } from "../../util/grid-slots.js";
3
3
  import StoryVariantGridItem_default from "./StoryVariantGridItem.js";
4
- import { useResizeObserver } from "@poveste/vendors/vue-use";
4
+ import { useDebounceFn, useResizeObserver } from "@poveste/vendors/vue-use";
5
5
  import { Fragment, computed, createBlock, createElementBlock, createElementVNode, defineComponent, normalizeStyle, onBeforeUnmount, onMounted, openBlock, ref, renderList, unref, watch } from "@poveste/vendors/vue";
6
6
  //#region src/app/components/story/StoryVariantGrid.vue?vue&type=script&setup=true&lang.ts
7
7
  var _hoisted_1 = { class: "poveste-story-variant-grid flex flex-col items-stretch h-full __poveste-pane-shadow-from-right" };
@@ -9,6 +9,8 @@ var _hoisted_2 = { class: "flex w-0 flex-1 mx-4" };
9
9
  var gap = 16;
10
10
  var overscanRows = 2;
11
11
  var initialCount = 10;
12
+ var FAST_PX_PER_MS = 8;
13
+ var FAST_REFRESH_MS = 150;
12
14
  var TRIM_DELAY = 2e3;
13
15
  var StoryVariantGrid_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
14
16
  __name: "StoryVariantGrid",
@@ -35,6 +37,23 @@ var StoryVariantGrid_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/
35
37
  scrollTop.value = el.value.scrollTop;
36
38
  viewportHeight.value = el.value.clientHeight;
37
39
  }
40
+ let lastScrollTop = 0;
41
+ let lastScrollTime = 0;
42
+ let lastWindowUpdate = 0;
43
+ const settleWindow = useDebounceFn(updateWindow, 120);
44
+ function onScroll() {
45
+ const now = performance.now();
46
+ const top = el.value?.scrollTop ?? 0;
47
+ const dt = now - lastScrollTime;
48
+ const velocity = dt > 0 ? Math.abs(top - lastScrollTop) / dt : 0;
49
+ lastScrollTop = top;
50
+ lastScrollTime = now;
51
+ if (velocity <= FAST_PX_PER_MS || now - lastWindowUpdate >= FAST_REFRESH_MS) {
52
+ updateWindow();
53
+ lastWindowUpdate = now;
54
+ }
55
+ settleWindow();
56
+ }
38
57
  function onItemResize(_w, h) {
39
58
  if (maxItemHeight.value < h) {
40
59
  maxItemHeight.value = h;
@@ -91,6 +110,7 @@ var StoryVariantGrid_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/
91
110
  }, { immediate: true });
92
111
  onBeforeUnmount(() => {
93
112
  clearTimeout(trimTimer);
113
+ settleWindow.cancel();
94
114
  });
95
115
  const totalHeight = computed(() => measured.value ? rowCount.value * rowHeight.value - gap : void 0);
96
116
  const offsetY = computed(() => measured.value ? startIndex.value / columnCount.value * rowHeight.value : 0);
@@ -111,7 +131,7 @@ var StoryVariantGrid_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/
111
131
  ref_key: "el",
112
132
  ref: el,
113
133
  class: "overflow-y-auto flex flex-1",
114
- onScroll: _cache[0] || (_cache[0] = ($event) => updateWindow())
134
+ onScroll: _cache[0] || (_cache[0] = ($event) => onScroll())
115
135
  }, [createElementVNode("div", _hoisted_2, [createElementVNode("div", {
116
136
  class: "m-auto",
117
137
  style: normalizeStyle({ minHeight: totalHeight.value != null ? `${totalHeight.value}px` : void 0 })
package/package.json CHANGED
@@ -1,17 +1,23 @@
1
1
  {
2
2
  "name": "@poveste/app",
3
3
  "type": "module",
4
- "version": "0.6.0",
4
+ "version": "0.7.0",
5
5
  "description": "Pre-bundled UI for Poveste",
6
6
  "author": {
7
7
  "name": "Sorin Gitlan"
8
8
  },
9
9
  "license": "MIT",
10
+ "homepage": "https://poveste.dev",
10
11
  "repository": {
11
12
  "url": "https://github.com/poveste-dev/poveste.git",
12
13
  "type": "git",
13
14
  "directory": "packages/poveste-app"
14
15
  },
16
+ "bugs": "https://github.com/poveste-dev/poveste/issues",
17
+ "keywords": [
18
+ "poveste",
19
+ "ui"
20
+ ],
15
21
  "publishConfig": {
16
22
  "access": "public"
17
23
  },
@@ -27,13 +33,16 @@
27
33
  "dist",
28
34
  "src"
29
35
  ],
36
+ "engines": {
37
+ "node": "^22.22.2 || ^24.15.0 || >=26.0.0"
38
+ },
30
39
  "dependencies": {
31
40
  "@fontsource/noto-sans-display": "^5.3.0",
32
41
  "fuse.js": "^7.0.0",
33
42
  "shiki": "^3.0.0",
34
- "@poveste/controls": "^0.6.0",
35
- "@poveste/shared": "^0.6.0",
36
- "@poveste/vendors": "^0.6.0"
43
+ "@poveste/controls": "^0.7.0",
44
+ "@poveste/vendors": "^0.7.0",
45
+ "@poveste/shared": "^0.7.0"
37
46
  },
38
47
  "devDependencies": {
39
48
  "@iconify-json/carbon": "^1.2.25",
@@ -1,6 +1,6 @@
1
1
  <script lang="ts" setup>
2
2
  import type { Slot } from '../../util/grid-slots'
3
- import { useResizeObserver } from '@vueuse/core'
3
+ import { useDebounceFn, useResizeObserver } from '@vueuse/core'
4
4
  import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
5
5
  import { useStoryStore } from '../../stores/story'
6
6
  import { assignSlots, trimSlots } from '../../util/grid-slots'
@@ -55,6 +55,43 @@ function updateWindow() {
55
55
  viewportHeight.value = el.value.clientHeight
56
56
  }
57
57
 
58
+ // A window shift retargets a row of sandbox iframes, and each retarget re-renders
59
+ // a story — fine while reading, ruinous while flicking a 1000-cell grid, which
60
+ // would render every row it flies past (#283). So the window follows a slow
61
+ // scroll promptly but skips most events during a fast flick. It is not frozen
62
+ // solid though: a bounded refresh keeps it tracking the scroller so a sustained
63
+ // fast drag still shows cells instead of a blank band, and the final window
64
+ // lands on settle. Resize/mount call `updateWindow` directly — not the churn path.
65
+ //
66
+ // The threshold sits above an ordinary mouse-wheel notch (a few px/ms) so plain
67
+ // reading takes the prompt path; only a genuine fling defers.
68
+ const FAST_PX_PER_MS = 8
69
+ // Cap on how long the window may lag the scroller during a fast flick: at most
70
+ // one retarget per this interval, versus one per row on the prompt path.
71
+ const FAST_REFRESH_MS = 150
72
+ let lastScrollTop = 0
73
+ let lastScrollTime = 0
74
+ let lastWindowUpdate = 0
75
+
76
+ // Lands the final window shortly after scrolling stops, whatever the speed was —
77
+ // this is what fills the tail of a fast scroll back in.
78
+ const settleWindow = useDebounceFn(updateWindow, 120)
79
+
80
+ function onScroll() {
81
+ const now = performance.now()
82
+ const top = el.value?.scrollTop ?? 0
83
+ const dt = now - lastScrollTime
84
+ const velocity = dt > 0 ? Math.abs(top - lastScrollTop) / dt : 0
85
+ lastScrollTop = top
86
+ lastScrollTime = now
87
+
88
+ if (velocity <= FAST_PX_PER_MS || now - lastWindowUpdate >= FAST_REFRESH_MS) {
89
+ updateWindow()
90
+ lastWindowUpdate = now
91
+ }
92
+ settleWindow()
93
+ }
94
+
58
95
  // The reported width is ignored: `columnCount` is what CSS lays out, so it is
59
96
  // the authority on how many items sit in a row.
60
97
  function onItemResize(_w: number, h: number) {
@@ -153,6 +190,8 @@ watch(visibleVariants, (visible) => {
153
190
 
154
191
  onBeforeUnmount(() => {
155
192
  clearTimeout(trimTimer)
193
+ // Drop any settle still pending, so it cannot fire updateWindow after teardown.
194
+ settleWindow.cancel()
156
195
  })
157
196
 
158
197
  // The spacer holds the full scroll extent; the grid is translated into place
@@ -185,7 +224,7 @@ watch(() => storyStore.currentVariant, (variant) => {
185
224
  <div
186
225
  ref="el"
187
226
  class="overflow-y-auto flex flex-1"
188
- @scroll="updateWindow()"
227
+ @scroll="onScroll()"
189
228
  >
190
229
  <div class="flex w-0 flex-1 mx-4">
191
230
  <div