@fusion.dev/helix-core 0.215.0 → 0.215.2

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.
@@ -215,6 +215,12 @@ export declare class VirtualizeController implements ReactiveController {
215
215
  private heading;
216
216
  /** Whether a finger is on the box. iOS keeps scrolling after it lifts. */
217
217
  private touching;
218
+ /**
219
+ * Whether the reader uses a touch screen: the device says its pointer is
220
+ * coarse, or a finger has been on the box. Rows are held while the box moves
221
+ * on such a screen only, since a mouse wheel does not end on a row change.
222
+ */
223
+ private touched;
218
224
  /** The timer that ends a scroll, set again on every scroll event. */
219
225
  private resting;
220
226
  /**
@@ -299,8 +305,13 @@ export declare class VirtualizeController implements ReactiveController {
299
305
  * scroll offset, with what is owed to it and less the drift.
300
306
  */
301
307
  private offset;
302
- /** How far past the screen to draw toward the side the box is scrolling to. */
308
+ /**
309
+ * How far past the screen to draw toward the side the box is scrolling to,
310
+ * and on a touched screen on both sides, `BUFFER` boxes of it.
311
+ */
303
312
  private lead;
313
+ /** Whether the rows beyond the box are drawn on the side the box is not scrolling to. */
314
+ private both;
304
315
  /**
305
316
  * Reads the scroll offset and settles the window.
306
317
  *
@@ -309,6 +320,16 @@ export declare class VirtualizeController implements ReactiveController {
309
320
  * so requesting one per event would re-render the list for every pixel.
310
321
  */
311
322
  measure(): void;
323
+ /**
324
+ * Whether the rows already drawn are kept for now: the screen is touched, the
325
+ * box is moving, and the rows drawn cover all of it.
326
+ *
327
+ * Not in a waterfall, where an item measured moves the items under it and
328
+ * the host draws them again.
329
+ */
330
+ private holds;
331
+ /** The window with the rows `first` to `last`, and the spacers that stand in for the rest. */
332
+ private frame;
312
333
  /** The straight line: one estimate, so offset and index convert by division. */
313
334
  private uniform;
314
335
  /**
@@ -3,6 +3,7 @@ import { reportRuntime } from '../../utils/report-runtime/report-runtime.js';
3
3
  import { Heights } from './heights.js';
4
4
  const UNSTABLE = 10;
5
5
  const REST = 150;
6
+ const BUFFER = 2;
6
7
  export class VirtualizeController {
7
8
  constructor(host, options) {
8
9
  this.watching = false;
@@ -15,6 +16,7 @@ export class VirtualizeController {
15
16
  this.lastTop = 0;
16
17
  this.heading = 0;
17
18
  this.touching = false;
19
+ this.touched = typeof matchMedia === 'function' && matchMedia('(any-pointer: coarse)').matches;
18
20
  this.uneven = false;
19
21
  this.handleScroll = () => {
20
22
  const box = this.options.container();
@@ -34,6 +36,7 @@ export class VirtualizeController {
34
36
  };
35
37
  this.handleTouchStart = () => {
36
38
  this.touching = true;
39
+ this.touched = true;
37
40
  };
38
41
  this.handleTouchEnd = () => {
39
42
  this.touching = false;
@@ -43,7 +46,13 @@ export class VirtualizeController {
43
46
  };
44
47
  this.rest = () => {
45
48
  this.resting = undefined;
46
- if (this.touching || this.drift === 0) {
49
+ if (this.touching) {
50
+ return;
51
+ }
52
+ if (this.drift === 0) {
53
+ if (this.touched) {
54
+ this.measure();
55
+ }
47
56
  return;
48
57
  }
49
58
  this.square();
@@ -148,8 +157,14 @@ export class VirtualizeController {
148
157
  return this.scrolled(box) + this.owed - this.drift;
149
158
  }
150
159
  lead(box) {
160
+ if (this.touched) {
161
+ return this.span(box) * BUFFER;
162
+ }
151
163
  return this.heading === 0 ? 0 : this.span(box);
152
164
  }
165
+ both() {
166
+ return this.touched;
167
+ }
153
168
  measure() {
154
169
  this.watch();
155
170
  const box = this.options.container();
@@ -168,10 +183,14 @@ export class VirtualizeController {
168
183
  this.square();
169
184
  next = choose();
170
185
  }
171
- if (this.last?.count !== total || this.last.estimate !== size) {
186
+ const moved = this.last?.count !== total || this.last.estimate !== size;
187
+ if (moved) {
172
188
  this.last = { count: total, estimate: size };
173
189
  this.settling = 0;
174
190
  }
191
+ if (!moved && this.holds(box, total)) {
192
+ next = this.frame(this.window.first, this.window.last, total, size);
193
+ }
175
194
  const same = next.first === this.window.first &&
176
195
  next.last === this.window.last &&
177
196
  next.before === this.window.before &&
@@ -188,11 +207,38 @@ export class VirtualizeController {
188
207
  this.window = next;
189
208
  this.host.requestUpdate();
190
209
  }
210
+ holds(box, total) {
211
+ if (!this.touched || !this.scrolling() || this.laned(total)) {
212
+ return false;
213
+ }
214
+ const { first, last, before } = this.window;
215
+ if (last <= first || last > total) {
216
+ return false;
217
+ }
218
+ const size = this.line();
219
+ const drawn = this.measuring()
220
+ ? this.startOf(last, total) - this.startOf(first, total)
221
+ : (last - first) * size;
222
+ const top = this.scrolled(box);
223
+ return top >= before && top + this.span(box) <= before + drawn;
224
+ }
225
+ frame(first, last, total, size) {
226
+ return this.measuring()
227
+ ? {
228
+ after: Math.max(0, this.startOf(total, total) - this.startOf(last, total)),
229
+ before: this.startOf(first, total) + this.drift,
230
+ first,
231
+ last,
232
+ }
233
+ : { after: Math.max(0, (total - last) * size), before: first * size, first, last };
234
+ }
191
235
  uniform(box, total, size, overscan) {
192
236
  const extra = Math.ceil(this.lead(box) / size);
193
237
  const at = Math.min(Math.floor(this.offset(box) / size), total);
194
- const first = Math.max(0, at - overscan - (this.heading < 0 ? extra : 0));
195
- const fits = Math.ceil(this.span(box) / size) + overscan * 2 + (this.heading === 0 ? 0 : extra);
238
+ const first = Math.max(0, at - overscan - (this.heading < 0 || this.both() ? extra : 0));
239
+ const fits = Math.ceil(this.span(box) / size) +
240
+ overscan * 2 +
241
+ (this.both() ? extra * 2 : this.heading === 0 ? 0 : extra);
196
242
  const last = Math.min(total, first + fits);
197
243
  return { after: Math.max(0, (total - last) * size), before: first * size, first, last };
198
244
  }
@@ -287,10 +333,10 @@ export class VirtualizeController {
287
333
  placed(box, total, overscan) {
288
334
  const top = this.offset(box);
289
335
  const lead = this.lead(box);
290
- const from = this.heading < 0 ? top - lead : top;
336
+ const from = this.heading < 0 || this.both() ? top - lead : top;
291
337
  const more = overscan * (this.laned(total)?.lanes ?? 1);
292
338
  const first = Math.max(0, this.indexAt(from, total) - more);
293
- const bottom = top + this.span(box) + (this.heading > 0 ? lead : 0);
339
+ const bottom = top + this.span(box) + (this.heading > 0 || this.both() ? lead : 0);
294
340
  let last = this.indexAt(top, total);
295
341
  while (last < total && this.startOf(last, total) < bottom) {
296
342
  last += 1;
package/dist/version.d.ts CHANGED
@@ -6,4 +6,4 @@
6
6
  * release, so the two never disagree; a spec beside this holds them to it.
7
7
  * Nothing else in the running code knows which release it is.
8
8
  */
9
- export declare const VERSION = "0.215.0";
9
+ export declare const VERSION = "0.215.2";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.215.0';
1
+ export const VERSION = '0.215.2';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fusion.dev/helix-core",
3
3
  "description": "Helix element primitives: base classes, mixins, decorators, utilities.",
4
- "version": "0.215.0",
4
+ "version": "0.215.2",
5
5
  "license": "MIT",
6
6
  "author": "Ross Sheppard <ross.sheppard@fusion.dev>",
7
7
  "repository": {
@@ -81,12 +81,12 @@
81
81
  "bugs": "https://github.com/project-fusion/fusion/issues",
82
82
  "dependencies": {
83
83
  "@floating-ui/dom": "^1.8.0",
84
- "@fusion.dev/helix-icons": "0.215.0",
84
+ "@fusion.dev/helix-icons": "0.215.2",
85
85
  "tslib": "^2.8.1"
86
86
  },
87
87
  "devDependencies": {
88
- "@fusion.dev/helix-cem": "0.215.0",
89
- "@fusion.dev/helix-testing": "0.215.0",
88
+ "@fusion.dev/helix-cem": "0.215.2",
89
+ "@fusion.dev/helix-testing": "0.215.2",
90
90
  "@lit-labs/ssr": "^4.1.0",
91
91
  "lit": "^3.2.1"
92
92
  },