vscroll 1.4.0-beta.2 → 1.4.3

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.
Files changed (128) hide show
  1. package/dist/bundles/vscroll.esm5.js +383 -392
  2. package/dist/bundles/vscroll.esm5.js.map +1 -1
  3. package/dist/bundles/vscroll.esm5.min.js +2 -2
  4. package/dist/bundles/vscroll.esm5.min.js.map +1 -1
  5. package/dist/bundles/vscroll.esm6.js +337 -342
  6. package/dist/bundles/vscroll.esm6.js.map +1 -1
  7. package/dist/bundles/vscroll.esm6.min.js +2 -2
  8. package/dist/bundles/vscroll.esm6.min.js.map +1 -1
  9. package/dist/bundles/vscroll.umd.js +386 -395
  10. package/dist/bundles/vscroll.umd.js.map +1 -1
  11. package/dist/bundles/vscroll.umd.min.js +2 -2
  12. package/dist/bundles/vscroll.umd.min.js.map +1 -1
  13. package/dist/esm2015/classes/buffer/checkCall.js.map +1 -1
  14. package/dist/esm2015/classes/buffer.js +0 -16
  15. package/dist/esm2015/classes/buffer.js.map +1 -1
  16. package/dist/esm2015/classes/domRoutines.js +56 -0
  17. package/dist/esm2015/classes/domRoutines.js.map +1 -1
  18. package/dist/esm2015/classes/item.js +4 -0
  19. package/dist/esm2015/classes/item.js.map +1 -1
  20. package/dist/esm2015/classes/paddings.js +3 -1
  21. package/dist/esm2015/classes/paddings.js.map +1 -1
  22. package/dist/esm2015/classes/state/cycle.js +7 -6
  23. package/dist/esm2015/classes/state/cycle.js.map +1 -1
  24. package/dist/esm2015/classes/state/fetch.js +0 -12
  25. package/dist/esm2015/classes/state/fetch.js.map +1 -1
  26. package/dist/esm2015/classes/state/render.js +1 -1
  27. package/dist/esm2015/classes/state/render.js.map +1 -1
  28. package/dist/esm2015/classes/state/scroll.js +6 -6
  29. package/dist/esm2015/classes/state/scroll.js.map +1 -1
  30. package/dist/esm2015/classes/state.js +23 -15
  31. package/dist/esm2015/classes/state.js.map +1 -1
  32. package/dist/esm2015/classes/viewport.js +12 -29
  33. package/dist/esm2015/classes/viewport.js.map +1 -1
  34. package/dist/esm2015/interfaces/index.js.map +1 -1
  35. package/dist/esm2015/interfaces/state.js.map +1 -1
  36. package/dist/esm2015/processes/adapter/append.js +15 -74
  37. package/dist/esm2015/processes/adapter/append.js.map +1 -1
  38. package/dist/esm2015/processes/adapter/reload.js +1 -1
  39. package/dist/esm2015/processes/adapter/reload.js.map +1 -1
  40. package/dist/esm2015/processes/adjust.js +44 -15
  41. package/dist/esm2015/processes/adjust.js.map +1 -1
  42. package/dist/esm2015/processes/end.js +18 -16
  43. package/dist/esm2015/processes/end.js.map +1 -1
  44. package/dist/esm2015/processes/fetch.js +3 -3
  45. package/dist/esm2015/processes/fetch.js.map +1 -1
  46. package/dist/esm2015/processes/init.js +2 -2
  47. package/dist/esm2015/processes/init.js.map +1 -1
  48. package/dist/esm2015/processes/render.js +8 -11
  49. package/dist/esm2015/processes/render.js.map +1 -1
  50. package/dist/esm2015/processes/scroll.js +21 -21
  51. package/dist/esm2015/processes/scroll.js.map +1 -1
  52. package/dist/esm2015/version.js +1 -1
  53. package/dist/esm2015/version.js.map +1 -1
  54. package/dist/esm2015/workflow.js +5 -6
  55. package/dist/esm2015/workflow.js.map +1 -1
  56. package/dist/esm5/classes/buffer/checkCall.js.map +1 -1
  57. package/dist/esm5/classes/buffer/defaultSize.js +1 -1
  58. package/dist/esm5/classes/buffer/defaultSize.js.map +1 -1
  59. package/dist/esm5/classes/buffer.js +7 -23
  60. package/dist/esm5/classes/buffer.js.map +1 -1
  61. package/dist/esm5/classes/domRoutines.js +56 -0
  62. package/dist/esm5/classes/domRoutines.js.map +1 -1
  63. package/dist/esm5/classes/item.js +4 -0
  64. package/dist/esm5/classes/item.js.map +1 -1
  65. package/dist/esm5/classes/logger.js +5 -5
  66. package/dist/esm5/classes/logger.js.map +1 -1
  67. package/dist/esm5/classes/paddings.js +3 -1
  68. package/dist/esm5/classes/paddings.js.map +1 -1
  69. package/dist/esm5/classes/state/cycle.js +7 -6
  70. package/dist/esm5/classes/state/cycle.js.map +1 -1
  71. package/dist/esm5/classes/state/fetch.js +0 -12
  72. package/dist/esm5/classes/state/fetch.js.map +1 -1
  73. package/dist/esm5/classes/state/render.js +1 -1
  74. package/dist/esm5/classes/state/render.js.map +1 -1
  75. package/dist/esm5/classes/state/scroll.js +11 -11
  76. package/dist/esm5/classes/state/scroll.js.map +1 -1
  77. package/dist/esm5/classes/state.js +23 -15
  78. package/dist/esm5/classes/state.js.map +1 -1
  79. package/dist/esm5/classes/viewport.js +13 -31
  80. package/dist/esm5/classes/viewport.js.map +1 -1
  81. package/dist/esm5/inputs/validation.js +2 -2
  82. package/dist/esm5/inputs/validation.js.map +1 -1
  83. package/dist/esm5/interfaces/index.js.map +1 -1
  84. package/dist/esm5/interfaces/state.js.map +1 -1
  85. package/dist/esm5/processes/adapter/append.js +15 -75
  86. package/dist/esm5/processes/adapter/append.js.map +1 -1
  87. package/dist/esm5/processes/adapter/fix.js +1 -1
  88. package/dist/esm5/processes/adapter/fix.js.map +1 -1
  89. package/dist/esm5/processes/adapter/insert.js +1 -1
  90. package/dist/esm5/processes/adapter/insert.js.map +1 -1
  91. package/dist/esm5/processes/adapter/reload.js +1 -1
  92. package/dist/esm5/processes/adapter/reload.js.map +1 -1
  93. package/dist/esm5/processes/adapter/remove.js +1 -1
  94. package/dist/esm5/processes/adapter/remove.js.map +1 -1
  95. package/dist/esm5/processes/adjust.js +44 -15
  96. package/dist/esm5/processes/adjust.js.map +1 -1
  97. package/dist/esm5/processes/end.js +18 -16
  98. package/dist/esm5/processes/end.js.map +1 -1
  99. package/dist/esm5/processes/fetch.js +3 -3
  100. package/dist/esm5/processes/fetch.js.map +1 -1
  101. package/dist/esm5/processes/init.js +2 -2
  102. package/dist/esm5/processes/init.js.map +1 -1
  103. package/dist/esm5/processes/preFetch.js +1 -1
  104. package/dist/esm5/processes/preFetch.js.map +1 -1
  105. package/dist/esm5/processes/render.js +8 -11
  106. package/dist/esm5/processes/render.js.map +1 -1
  107. package/dist/esm5/processes/scroll.js +21 -21
  108. package/dist/esm5/processes/scroll.js.map +1 -1
  109. package/dist/esm5/version.js +1 -1
  110. package/dist/esm5/version.js.map +1 -1
  111. package/dist/esm5/workflow.js +10 -13
  112. package/dist/esm5/workflow.js.map +1 -1
  113. package/dist/typings/classes/buffer.d.ts +0 -2
  114. package/dist/typings/classes/domRoutines.d.ts +12 -0
  115. package/dist/typings/classes/item.d.ts +1 -0
  116. package/dist/typings/classes/state/cycle.d.ts +1 -1
  117. package/dist/typings/classes/state/fetch.d.ts +0 -2
  118. package/dist/typings/classes/state/render.d.ts +1 -1
  119. package/dist/typings/classes/state/scroll.d.ts +4 -4
  120. package/dist/typings/classes/state.d.ts +6 -3
  121. package/dist/typings/classes/viewport.d.ts +2 -2
  122. package/dist/typings/interfaces/index.d.ts +2 -2
  123. package/dist/typings/interfaces/state.d.ts +2 -15
  124. package/dist/typings/processes/adapter/append.d.ts +1 -4
  125. package/dist/typings/processes/adjust.d.ts +1 -0
  126. package/dist/typings/processes/end.d.ts +1 -2
  127. package/dist/typings/workflow.d.ts +1 -1
  128. package/package.json +17 -17
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * vscroll (https://github.com/dhilt/vscroll) FESM2015
3
- * Version: 1.4.0-beta.2 (2021-09-29T18:28:03.001Z)
3
+ * Version: 1.4.3 (2021-11-30T02:54:02.729Z)
4
4
  * Author: Denis Hilt
5
5
  * License: MIT
6
6
  */
@@ -328,7 +328,7 @@ const reactiveConfigStorage = new Map();
328
328
 
329
329
  var core = {
330
330
  name: 'vscroll',
331
- version: '1.4.0-beta.2'
331
+ version: '1.4.3'
332
332
  };
333
333
 
334
334
  let instanceCount$1 = 0;
@@ -1276,10 +1276,10 @@ const BaseAdapterProcessFactory = (process) => { var _a; return _a = class BaseA
1276
1276
  const initProcesses = [CommonProcess.init, AdapterProcess.reset, AdapterProcess.reload];
1277
1277
  class Init extends BaseProcessFactory(CommonProcess.init) {
1278
1278
  static run(scroller, process) {
1279
- const { state: { cycle }, workflow } = scroller;
1279
+ const { state, workflow } = scroller;
1280
1280
  const isInitial = initProcesses.includes(process);
1281
1281
  scroller.logger.logCycle(true);
1282
- cycle.start(isInitial, process);
1282
+ state.startWorkflowCycle(isInitial, process);
1283
1283
  workflow.call({
1284
1284
  process: Init.process,
1285
1285
  status: ProcessStatus.next
@@ -1298,15 +1298,15 @@ class Scroll extends BaseProcessFactory(CommonProcess.scroll) {
1298
1298
  Scroll.onThrottle(scroller, position, () => Scroll.onScroll(scroller, workflow));
1299
1299
  }
1300
1300
  static onSynthetic(scroller, position) {
1301
- const { scrollState } = scroller.state;
1302
- const synthPos = scrollState.syntheticPosition;
1301
+ const { scroll } = scroller.state;
1302
+ const synthPos = scroll.syntheticPosition;
1303
1303
  if (synthPos !== null) {
1304
- if (scrollState.syntheticFulfill) {
1305
- scrollState.syntheticPosition = null;
1304
+ if (scroll.syntheticFulfill) {
1305
+ scroll.syntheticPosition = null;
1306
1306
  }
1307
- if (!scrollState.syntheticFulfill || synthPos === position) {
1307
+ if (!scroll.syntheticFulfill || synthPos === position) {
1308
1308
  scroller.logger.log(() => [
1309
- 'skipping scroll', position, `[${scrollState.syntheticFulfill ? '' : 'pre-'}synthetic]`
1309
+ 'skipping scroll', position, `[${scroll.syntheticFulfill ? '' : 'pre-'}synthetic]`
1310
1310
  ]);
1311
1311
  return true;
1312
1312
  }
@@ -1317,13 +1317,13 @@ class Scroll extends BaseProcessFactory(CommonProcess.scroll) {
1317
1317
  return false;
1318
1318
  }
1319
1319
  static onThrottle(scroller, position, done) {
1320
- const { state: { scrollState }, settings: { throttle }, logger } = scroller;
1321
- scrollState.current = Scroll.getScrollEvent(position, scrollState.previous);
1322
- const { direction, time } = scrollState.current;
1323
- const timeDiff = scrollState.previous ? time - scrollState.previous.time : Infinity;
1320
+ const { state: { scroll }, settings: { throttle }, logger } = scroller;
1321
+ scroll.current = Scroll.getScrollEvent(position, scroll.previous);
1322
+ const { direction, time } = scroll.current;
1323
+ const timeDiff = scroll.previous ? time - scroll.previous.time : Infinity;
1324
1324
  const delta = throttle - timeDiff;
1325
1325
  const shouldDelay = isFinite(delta) && delta > 0;
1326
- const alreadyDelayed = !!scrollState.scrollTimer;
1326
+ const alreadyDelayed = !!scroll.scrollTimer;
1327
1327
  logger.log(() => [
1328
1328
  direction === Direction.backward ? '\u2934' : '\u2935',
1329
1329
  position,
@@ -1331,17 +1331,17 @@ class Scroll extends BaseProcessFactory(CommonProcess.scroll) {
1331
1331
  shouldDelay ? (alreadyDelayed ? 'delayed' : `/ ${delta}ms delay`) : ''
1332
1332
  ]);
1333
1333
  if (!shouldDelay) {
1334
- if (scrollState.scrollTimer) {
1335
- clearTimeout(scrollState.scrollTimer);
1336
- scrollState.scrollTimer = null;
1334
+ if (scroll.scrollTimer) {
1335
+ clearTimeout(scroll.scrollTimer);
1336
+ scroll.scrollTimer = null;
1337
1337
  }
1338
1338
  done();
1339
1339
  return;
1340
1340
  }
1341
1341
  if (!alreadyDelayed) {
1342
- scrollState.scrollTimer = setTimeout(() => {
1342
+ scroll.scrollTimer = setTimeout(() => {
1343
1343
  logger.log(() => {
1344
- const curr = Scroll.getScrollEvent(scroller.viewport.scrollPosition, scrollState.current);
1344
+ const curr = Scroll.getScrollEvent(scroller.viewport.scrollPosition, scroll.current);
1345
1345
  return [
1346
1346
  curr.direction === Direction.backward ? '\u2934' : '\u2935',
1347
1347
  curr.position,
@@ -1350,7 +1350,7 @@ class Scroll extends BaseProcessFactory(CommonProcess.scroll) {
1350
1350
  position
1351
1351
  ];
1352
1352
  });
1353
- scrollState.scrollTimer = null;
1353
+ scroll.scrollTimer = null;
1354
1354
  done();
1355
1355
  }, delta);
1356
1356
  }
@@ -1369,11 +1369,11 @@ class Scroll extends BaseProcessFactory(CommonProcess.scroll) {
1369
1369
  return { position, direction, time };
1370
1370
  }
1371
1371
  static onScroll(scroller, workflow) {
1372
- const { state: { scrollState, cycle } } = scroller;
1373
- scrollState.previous = Object.assign({}, scrollState.current);
1374
- scrollState.current = null;
1372
+ const { state: { scroll, cycle } } = scroller;
1373
+ scroll.previous = Object.assign({}, scroll.current);
1374
+ scroll.current = null;
1375
1375
  if (cycle.busy.get()) {
1376
- scroller.logger.log(() => ['skipping scroll', scrollState.previous.position, '[pending]']);
1376
+ scroller.logger.log(() => ['skipping scroll', scroll.previous.position, '[pending]']);
1377
1377
  return;
1378
1378
  }
1379
1379
  workflow.call({
@@ -1424,7 +1424,7 @@ class Reload extends BaseAdapterProcessFactory(AdapterProcess.reload) {
1424
1424
  viewport.reset(buffer.startIndex);
1425
1425
  const payload = {};
1426
1426
  if (state.cycle.busy.get()) {
1427
- state.scrollState.cleanupTimers();
1427
+ state.scroll.stop();
1428
1428
  payload.finalize = true;
1429
1429
  state.cycle.interrupter = Reload.process;
1430
1430
  }
@@ -1475,6 +1475,10 @@ class Item {
1475
1475
  this.size = this.routines.getSize(this.element);
1476
1476
  }
1477
1477
  }
1478
+ makeVisible() {
1479
+ this.routines.makeElementVisible(this.element);
1480
+ this.invisible = false;
1481
+ }
1478
1482
  hide() {
1479
1483
  if (this.element) {
1480
1484
  this.routines.hideElement(this.element);
@@ -1535,47 +1539,65 @@ class Update extends BaseAdapterProcessFactory(AdapterProcess.update) {
1535
1539
  }
1536
1540
  }
1537
1541
 
1538
- class Append extends BaseAdapterProcessFactory(AdapterProcess.append) {
1539
- static run(scroller, { process, options }) {
1540
- const { params } = Append.parseInput(scroller, options, false, process);
1542
+ class Insert extends BaseAdapterProcessFactory(AdapterProcess.insert) {
1543
+ static run(scroller, options) {
1544
+ const { params } = Insert.parseInput(scroller, options);
1541
1545
  if (!params) {
1542
1546
  return;
1543
1547
  }
1544
- const shouldAppend = Append.doAppend(scroller, params, process !== AdapterProcess.append);
1548
+ const shouldInsert = Insert.doInsert(scroller, params);
1545
1549
  scroller.workflow.call({
1546
- process: Append.process,
1547
- status: shouldAppend ? ProcessStatus.next : ProcessStatus.done
1550
+ process: Insert.process,
1551
+ status: shouldInsert ? ProcessStatus.next : ProcessStatus.done
1548
1552
  });
1549
1553
  }
1550
- static doAppend(scroller, params, prepend) {
1551
- const { buffer } = scroller;
1552
- const { items, bof, eof, increase, decrease } = params;
1553
- const fixRight = (prepend && !increase) || (!prepend && !!decrease);
1554
- let result = false;
1555
- if ((prepend && bof && !buffer.bof.get()) || (!prepend && eof && !buffer.eof.get())) {
1556
- result = Append.doVirtual(scroller, items, prepend, fixRight);
1557
- }
1558
- else {
1559
- if (!buffer.size) {
1560
- result = Append.doEmpty(scroller, items, prepend, fixRight);
1561
- }
1562
- else {
1563
- result = Append.doRegular(scroller, items, prepend, fixRight);
1554
+ static doInsert(scroller, params) {
1555
+ if (!Insert.insertEmpty(scroller, params)) {
1556
+ if (!Insert.insertInBuffer(scroller, params)) {
1557
+ if (!Insert.insertVirtually(scroller, params)) {
1558
+ return false;
1559
+ }
1564
1560
  }
1565
1561
  }
1566
- return result;
1562
+ return true;
1567
1563
  }
1568
- static doVirtual(scroller, items, prepend, fixRight) {
1569
- const { buffer, logger, viewport, state: { fetch } } = scroller;
1570
- const absIndexToken = fixRight ? 'absMinIndex' : 'absMaxIndex';
1571
- if (!isFinite(buffer[absIndexToken])) {
1564
+ static insertEmpty(scroller, params) {
1565
+ const { buffer, routines, state: { fetch } } = scroller;
1566
+ if (buffer.size) {
1572
1567
  return false;
1573
1568
  }
1574
- if (prepend) {
1575
- buffer.prependVirtually(items.length, fixRight);
1569
+ const { beforeIndex, afterIndex, items, decrease } = params;
1570
+ if (!buffer.fillEmpty(items, beforeIndex, afterIndex, !!decrease, (index, data) => new Item(index, data, routines))) {
1571
+ return false;
1576
1572
  }
1577
- else {
1578
- buffer.appendVirtually(items.length, fixRight);
1573
+ fetch.fill(buffer.items, buffer.startIndex);
1574
+ return true;
1575
+ }
1576
+ static insertInBuffer(scroller, params) {
1577
+ const { before, after, beforeIndex, afterIndex, items, decrease } = params;
1578
+ const indexToInsert = scroller.buffer.getIndexToInsert(before || after, beforeIndex, afterIndex);
1579
+ if (isNaN(indexToInsert)) {
1580
+ return false;
1581
+ }
1582
+ const isBackward = Number.isInteger(beforeIndex) || before;
1583
+ const updateOptions = {
1584
+ predicate: ({ $index, data }) => {
1585
+ if (indexToInsert === $index) {
1586
+ return isBackward ? [...items, data] : [data, ...items];
1587
+ }
1588
+ return true;
1589
+ },
1590
+ fixRight: decrease
1591
+ };
1592
+ return Update.doUpdate(scroller, updateOptions);
1593
+ }
1594
+ static insertVirtually(scroller, params) {
1595
+ const { beforeIndex, afterIndex, items, decrease } = params;
1596
+ const { buffer, state: { fetch }, viewport } = scroller;
1597
+ const direction = Number.isInteger(beforeIndex) ? Direction.backward : Direction.forward;
1598
+ const indexToInsert = (direction === Direction.backward ? beforeIndex : afterIndex);
1599
+ if (!buffer.insertVirtually(items, indexToInsert, direction, !!decrease)) {
1600
+ return false;
1579
1601
  }
1580
1602
  const { index, diff } = viewport.getEdgeVisibleItem(buffer.items, Direction.backward);
1581
1603
  fetch.firstVisible.index = index;
@@ -1583,46 +1605,41 @@ class Append extends BaseAdapterProcessFactory(AdapterProcess.append) {
1583
1605
  fetch.simulate = true;
1584
1606
  fetch.firstVisible.delta = -buffer.getSizeByIndex(index) + diff;
1585
1607
  }
1586
- logger.log(() => `buffer.${[absIndexToken]} value is set to ${buffer[absIndexToken]}`);
1587
- logger.stat(`after virtual ${prepend ? 'prepend' : 'append'}`);
1588
1608
  return true;
1589
1609
  }
1590
- static doEmpty(scroller, items, prepend, fixRight) {
1591
- const { buffer, state: { fetch } } = scroller;
1592
- const absIndexToken = fixRight ? 'absMinIndex' : 'absMaxIndex';
1593
- const shift = prepend && !fixRight ? items.length - 1 : (!prepend && fixRight ? 1 - items.length : 0);
1594
- const bufferLimit = buffer[absIndexToken] + (fixRight ? -1 : 1) * (items.length - 1);
1595
- const newItems = [];
1596
- const startIndex = buffer[prepend ? 'minIndex' : 'maxIndex'];
1597
- let index = startIndex;
1598
- items.forEach(item => {
1599
- const newItem = new Item(index + shift, item, scroller.routines);
1600
- Array.prototype[prepend ? 'unshift' : 'push'].call(newItems, newItem);
1601
- index += (prepend ? -1 : 1);
1610
+ }
1611
+
1612
+ class Append extends BaseAdapterProcessFactory(AdapterProcess.append) {
1613
+ static run(scroller, { process, options }) {
1614
+ const { params } = Append.parseInput(scroller, options, false, process);
1615
+ if (!params) {
1616
+ return;
1617
+ }
1618
+ const shouldAppend = Append.doAppend(scroller, process, params);
1619
+ scroller.workflow.call({
1620
+ process: Append.process,
1621
+ status: shouldAppend ? ProcessStatus.next : ProcessStatus.done
1602
1622
  });
1603
- if (bufferLimit !== buffer[absIndexToken]) {
1604
- buffer[absIndexToken] = bufferLimit;
1605
- scroller.logger.log(() => `buffer.${absIndexToken} value is set to ${buffer[absIndexToken]}`);
1606
- }
1607
- (prepend ? fetch.prepend : fetch.append).call(fetch, newItems);
1608
- buffer.setItems(newItems);
1609
- fetch.first.indexBuffer = !isNaN(buffer.firstIndex) ? buffer.firstIndex : index;
1610
- fetch.last.indexBuffer = !isNaN(buffer.lastIndex) ? buffer.lastIndex : index;
1611
- fetch.firstVisible.index = startIndex;
1612
- return true;
1613
1623
  }
1614
- static doRegular(scroller, items, prepend, fixRight) {
1615
- const index = scroller.buffer[prepend ? 'firstIndex' : 'lastIndex'];
1616
- const updateOptions = {
1617
- predicate: ({ $index, data }) => {
1618
- if ($index === index) {
1619
- return prepend ? [...items.reverse(), data] : [data, ...items];
1620
- }
1621
- return true;
1622
- },
1623
- fixRight
1624
- };
1625
- return Update.doUpdate(scroller, updateOptions);
1624
+ static doAppend(scroller, process, params) {
1625
+ const { bof, eof, increase, decrease } = params;
1626
+ const { buffer } = scroller;
1627
+ const prepend = process === AdapterProcess.prepend;
1628
+ const opposite = prepend ? !increase : decrease;
1629
+ let beforeIndex, afterIndex, items = params.items;
1630
+ if (prepend) {
1631
+ beforeIndex = (bof ? buffer.absMinIndex : buffer.minIndex) + (!buffer.size ? 1 : 0);
1632
+ items = [...items].reverse();
1633
+ }
1634
+ else {
1635
+ afterIndex = (eof ? buffer.absMaxIndex : buffer.maxIndex) - (!buffer.size && !opposite ? 1 : 0);
1636
+ }
1637
+ return Insert.doInsert(scroller, {
1638
+ items,
1639
+ beforeIndex,
1640
+ afterIndex,
1641
+ decrease: opposite
1642
+ });
1626
1643
  }
1627
1644
  }
1628
1645
 
@@ -1769,76 +1786,6 @@ class UserClip extends BaseAdapterProcessFactory(AdapterProcess.clip) {
1769
1786
  }
1770
1787
  }
1771
1788
 
1772
- class Insert extends BaseAdapterProcessFactory(AdapterProcess.insert) {
1773
- static run(scroller, options) {
1774
- const { params } = Insert.parseInput(scroller, options);
1775
- if (!params) {
1776
- return;
1777
- }
1778
- const shouldInsert = Insert.doInsert(scroller, params);
1779
- scroller.workflow.call({
1780
- process: Insert.process,
1781
- status: shouldInsert ? ProcessStatus.next : ProcessStatus.done
1782
- });
1783
- }
1784
- static doInsert(scroller, params) {
1785
- if (!Insert.insertEmpty(scroller, params)) {
1786
- if (!Insert.insertInBuffer(scroller, params)) {
1787
- if (!Insert.insertVirtually(scroller, params)) {
1788
- return false;
1789
- }
1790
- }
1791
- }
1792
- return true;
1793
- }
1794
- static insertEmpty(scroller, params) {
1795
- const { buffer, routines, state: { fetch } } = scroller;
1796
- if (buffer.size) {
1797
- return false;
1798
- }
1799
- const { beforeIndex, afterIndex, items, decrease } = params;
1800
- if (!buffer.fillEmpty(items, beforeIndex, afterIndex, !!decrease, (index, data) => new Item(index, data, routines))) {
1801
- return false;
1802
- }
1803
- fetch.fill(buffer.items, buffer.startIndex);
1804
- return true;
1805
- }
1806
- static insertInBuffer(scroller, params) {
1807
- const { before, after, beforeIndex, afterIndex, items, decrease } = params;
1808
- const indexToInsert = scroller.buffer.getIndexToInsert(before || after, beforeIndex, afterIndex);
1809
- if (isNaN(indexToInsert)) {
1810
- return false;
1811
- }
1812
- const isBackward = Number.isInteger(beforeIndex) || before;
1813
- const updateOptions = {
1814
- predicate: ({ $index, data }) => {
1815
- if (indexToInsert === $index) {
1816
- return isBackward ? [...items, data] : [data, ...items];
1817
- }
1818
- return true;
1819
- },
1820
- fixRight: decrease
1821
- };
1822
- return Update.doUpdate(scroller, updateOptions);
1823
- }
1824
- static insertVirtually(scroller, params) {
1825
- const { beforeIndex, afterIndex, items, decrease } = params;
1826
- const { buffer, state: { fetch }, viewport } = scroller;
1827
- const direction = Number.isInteger(beforeIndex) ? Direction.backward : Direction.forward;
1828
- const indexToInsert = (direction === Direction.backward ? beforeIndex : afterIndex);
1829
- if (!buffer.insertVirtually(items, indexToInsert, direction, !!decrease)) {
1830
- return false;
1831
- }
1832
- const { index, diff } = viewport.getEdgeVisibleItem(buffer.items, Direction.backward);
1833
- fetch.firstVisible.index = index;
1834
- if (!isNaN(index)) {
1835
- fetch.simulate = true;
1836
- fetch.firstVisible.delta = -buffer.getSizeByIndex(index) + diff;
1837
- }
1838
- return true;
1839
- }
1840
- }
1841
-
1842
1789
  class Replace extends BaseAdapterProcessFactory(AdapterProcess.replace) {
1843
1790
  static run(scroller, options) {
1844
1791
  const { params } = Replace.parseInput(scroller, options);
@@ -2236,9 +2183,9 @@ class Fetch extends BaseProcessFactory(CommonProcess.fetch) {
2236
2183
  }
2237
2184
  }
2238
2185
  else {
2239
- const { state: { scrollState, fetch }, viewport } = scroller;
2240
- if (scrollState.positionBeforeAsync === null) {
2241
- scrollState.positionBeforeAsync = viewport.scrollPosition;
2186
+ const { state: { scroll, fetch }, viewport } = scroller;
2187
+ if (scroll.positionBeforeAsync === null) {
2188
+ scroll.positionBeforeAsync = viewport.scrollPosition;
2242
2189
  }
2243
2190
  fetch.cancel = () => {
2244
2191
  box.success = () => null;
@@ -2358,13 +2305,13 @@ class PostFetch extends BaseProcessFactory(CommonProcess.postFetch) {
2358
2305
 
2359
2306
  class Render extends BaseProcessFactory(CommonProcess.render) {
2360
2307
  static run(scroller) {
2361
- const { workflow, state: { cycle, render, scrollState }, viewport } = scroller;
2308
+ const { workflow, state: { cycle, render, scroll }, viewport, routines } = scroller;
2362
2309
  scroller.logger.stat('before new items render');
2363
- if (scrollState.positionBeforeAsync === null) {
2364
- scrollState.positionBeforeAsync = viewport.scrollPosition;
2310
+ if (scroll.positionBeforeAsync === null) {
2311
+ scroll.positionBeforeAsync = viewport.scrollPosition;
2365
2312
  }
2366
- render.renderTimer = setTimeout(() => {
2367
- render.renderTimer = null;
2313
+ render.cancel = routines.render(() => {
2314
+ render.cancel = null;
2368
2315
  if (Render.doRender(scroller)) {
2369
2316
  workflow.call({
2370
2317
  process: Render.process,
@@ -2379,7 +2326,7 @@ class Render extends BaseProcessFactory(CommonProcess.render) {
2379
2326
  payload: { error: 'Can\'t associate item with element' }
2380
2327
  });
2381
2328
  }
2382
- }, 0);
2329
+ });
2383
2330
  }
2384
2331
  static doRender(scroller) {
2385
2332
  const { state: { fetch, render }, viewport, buffer, logger } = scroller;
@@ -2398,29 +2345,91 @@ class Render extends BaseProcessFactory(CommonProcess.render) {
2398
2345
  }
2399
2346
  static processElement(scroller, item) {
2400
2347
  const { viewport, buffer } = scroller;
2401
- const element = viewport.element.querySelector(`[data-sid="${item.nodeId}"]`);
2348
+ const element = viewport.findItemElementById(item.nodeId);
2402
2349
  if (!element) {
2403
2350
  return false;
2404
2351
  }
2405
2352
  item.element = element;
2406
- item.element.style.left = '';
2407
- item.element.style.top = '';
2408
- item.element.style.position = '';
2409
- item.invisible = false;
2353
+ item.makeVisible();
2410
2354
  item.setSize(buffer.getSizeByIndex(item.$index));
2411
2355
  buffer.cacheItem(item);
2412
2356
  return true;
2413
2357
  }
2414
2358
  }
2415
2359
 
2360
+ const isInterrupted = ({ call }) => !!call.interrupted;
2361
+ class End extends BaseProcessFactory(CommonProcess.end) {
2362
+ static run(scroller, { error } = {}) {
2363
+ const { workflow, state: { cycle: { interrupter } } } = scroller;
2364
+ if (!error && !interrupter) {
2365
+ // set out params accessible via Adapter
2366
+ End.calculateParams(scroller, workflow);
2367
+ }
2368
+ // explicit interruption for we don't want to go through the inner loop finalizing
2369
+ if (isInterrupted(workflow)) {
2370
+ workflow.call({ process: End.process, status: ProcessStatus.done });
2371
+ return;
2372
+ }
2373
+ const next = End.shouldContinueRun(scroller, error);
2374
+ scroller.state.endInnerLoop();
2375
+ workflow.call({
2376
+ process: End.process,
2377
+ status: next ? ProcessStatus.next : ProcessStatus.done,
2378
+ payload: Object.assign({}, (interrupter ? { process: interrupter } : {}))
2379
+ });
2380
+ }
2381
+ static calculateParams(scroller, workflow) {
2382
+ const { adapter, viewport, buffer: { items } } = scroller;
2383
+ if (adapter.wanted.firstVisible) {
2384
+ const { item } = viewport.getEdgeVisibleItem(items, Direction.backward);
2385
+ if (!item || item.element !== adapter.firstVisible.element) {
2386
+ adapter.firstVisible = item ? item.get() : EMPTY_ITEM;
2387
+ }
2388
+ }
2389
+ // the workflow can be interrupter on firstVisible change
2390
+ if (adapter.wanted.lastVisible && !isInterrupted(workflow)) {
2391
+ const { item } = viewport.getEdgeVisibleItem(items, Direction.forward);
2392
+ if (!item || item.element !== adapter.lastVisible.element) {
2393
+ adapter.lastVisible = item ? item.get() : EMPTY_ITEM;
2394
+ }
2395
+ }
2396
+ }
2397
+ static shouldContinueRun(scroller, error) {
2398
+ const { cycle, fetch, render } = scroller.state;
2399
+ // Adapter.reload or Adapter.reset
2400
+ if (cycle.interrupter) {
2401
+ return true;
2402
+ }
2403
+ // critical error
2404
+ if (error) {
2405
+ return false;
2406
+ }
2407
+ // Adapter.check
2408
+ if (fetch.simulate && fetch.isCheck && !render.noSize) {
2409
+ return true;
2410
+ }
2411
+ // Adapter.remove or Adapter.update with clip
2412
+ if (fetch.simulate && fetch.doRemove) {
2413
+ return true;
2414
+ }
2415
+ // common inner loop (App start, scroll, Adapter.clip) with full fetch
2416
+ if (!fetch.simulate && ((fetch.hasNewItems && !render.noSize) || fetch.hasAnotherPack)) {
2417
+ return true;
2418
+ }
2419
+ return false;
2420
+ }
2421
+ }
2422
+
2416
2423
  class Adjust extends BaseProcessFactory(CommonProcess.adjust) {
2417
2424
  static run(scroller) {
2418
- const { workflow, viewport, state: { scrollState } } = scroller;
2419
- scrollState.positionBeforeAdjust = viewport.scrollPosition;
2425
+ const { workflow, viewport, state: { scroll } } = scroller;
2426
+ scroll.positionBeforeAdjust = viewport.scrollPosition;
2420
2427
  Adjust.setPaddings(scroller);
2421
- scrollState.positionAfterAdjust = viewport.scrollPosition;
2428
+ scroll.positionAfterAdjust = viewport.scrollPosition;
2422
2429
  // scroll position adjustments
2423
2430
  const position = Adjust.calculatePosition(scroller);
2431
+ // additional adjustment if the position can't be reached during the initial cycle
2432
+ Adjust.setAdditionalForwardPadding(scroller, position);
2424
2433
  // set new position using animation frame
2425
2434
  Adjust.setPosition(scroller, position, () => workflow.call({
2426
2435
  process: Adjust.process,
@@ -2451,7 +2460,8 @@ class Adjust extends BaseProcessFactory(CommonProcess.adjust) {
2451
2460
  }
2452
2461
  // lack of items case
2453
2462
  const bufferSize = viewport.getScrollableSize() - forward.size - backward.size;
2454
- const viewportSizeDiff = viewport.getSize() - (bwdSize + bufferSize + fwdSize);
2463
+ const scrollSize = bwdSize + bufferSize + fwdSize;
2464
+ const viewportSizeDiff = viewport.getSize() - scrollSize;
2455
2465
  if (viewportSizeDiff > 0) {
2456
2466
  if (inverse) {
2457
2467
  bwdSize += viewportSizeDiff;
@@ -2466,7 +2476,7 @@ class Adjust extends BaseProcessFactory(CommonProcess.adjust) {
2466
2476
  scroller.logger.stat('after paddings adjustments');
2467
2477
  }
2468
2478
  static calculatePosition(scroller) {
2469
- const { viewport, buffer, state: { fetch, render, scrollState } } = scroller;
2479
+ const { viewport, buffer, state: { fetch, render, scroll } } = scroller;
2470
2480
  let position = viewport.paddings.backward.size;
2471
2481
  // increase the position to meet the expectation of the first visible item
2472
2482
  if (!isNaN(fetch.firstVisible.index) && !isNaN(buffer.firstIndex)) {
@@ -2489,8 +2499,8 @@ class Adjust extends BaseProcessFactory(CommonProcess.adjust) {
2489
2499
  });
2490
2500
  }
2491
2501
  // slow fetch/render case
2492
- if (scrollState.positionBeforeAsync !== null) {
2493
- const diff = render.positionBefore - scrollState.positionBeforeAsync;
2502
+ if (scroll.positionBeforeAsync !== null) {
2503
+ const diff = render.positionBefore - scroll.positionBeforeAsync;
2494
2504
  if (diff !== 0) {
2495
2505
  scroller.logger.log(`shift position due to fetch-render difference (${diff})`);
2496
2506
  position += diff;
@@ -2502,22 +2512,47 @@ class Adjust extends BaseProcessFactory(CommonProcess.adjust) {
2502
2512
  }
2503
2513
  return Math.round(position);
2504
2514
  }
2515
+ static setAdditionalForwardPadding(scroller, position) {
2516
+ const { viewport, buffer, state: { cycle } } = scroller;
2517
+ if (!cycle.isInitial || !End.shouldContinueRun(scroller, null)) {
2518
+ return;
2519
+ }
2520
+ const diff = position - viewport.getMaxScrollPosition();
2521
+ if (diff <= 0) {
2522
+ return;
2523
+ }
2524
+ const last = buffer.getLastVisibleItem();
2525
+ if (!last) {
2526
+ return;
2527
+ }
2528
+ let size = 0;
2529
+ let index = last.$index + 1;
2530
+ while (size <= diff && index <= buffer.absMaxIndex) {
2531
+ size += buffer.getSizeByIndex(index++);
2532
+ }
2533
+ const shift = Math.min(size, diff);
2534
+ if (shift) {
2535
+ viewport.paddings.forward.size += shift;
2536
+ scroller.logger.log(`increase fwd padding due to lack of items (${diff} -> ${shift})`);
2537
+ }
2538
+ }
2505
2539
  static setPosition(scroller, position, done) {
2506
- const { state: { scrollState }, viewport } = scroller;
2507
- if (!scrollState.hasPositionChanged(position)) {
2540
+ const { state: { scroll }, viewport, routines } = scroller;
2541
+ if (!scroll.hasPositionChanged(position)) {
2508
2542
  return done();
2509
2543
  }
2510
- scrollState.syntheticPosition = position;
2511
- scrollState.syntheticFulfill = false;
2512
- scrollState.animationFrameId = requestAnimationFrame(() => {
2513
- const inertiaDiff = scrollState.positionAfterAdjust - viewport.scrollPosition;
2544
+ scroll.syntheticPosition = position;
2545
+ scroll.syntheticFulfill = false;
2546
+ scroll.cancelAnimation = routines.animate(() => {
2547
+ scroll.cancelAnimation = null;
2548
+ const inertiaDiff = scroll.positionAfterAdjust - viewport.scrollPosition;
2514
2549
  let diffLog = '';
2515
2550
  if (inertiaDiff > 0) {
2516
2551
  position -= inertiaDiff;
2517
- scrollState.syntheticPosition = position;
2552
+ scroll.syntheticPosition = position;
2518
2553
  diffLog = ` (-${inertiaDiff})`;
2519
2554
  }
2520
- scrollState.syntheticFulfill = true;
2555
+ scroll.syntheticFulfill = true;
2521
2556
  viewport.scrollPosition = position;
2522
2557
  scroller.logger.stat('after scroll adjustment' + diffLog);
2523
2558
  done();
@@ -2657,67 +2692,6 @@ class Clip extends BaseProcessFactory(CommonProcess.clip) {
2657
2692
  }
2658
2693
  }
2659
2694
 
2660
- const isInterrupted = ({ call }) => !!call.interrupted;
2661
- class End extends BaseProcessFactory(CommonProcess.end) {
2662
- static run(scroller, { error } = {}) {
2663
- const { workflow, state: { cycle: { interrupter } } } = scroller;
2664
- if (!error && !interrupter) {
2665
- // set out params accessible via Adapter
2666
- End.calculateParams(scroller, workflow);
2667
- }
2668
- // explicit interruption for we don't want to go through the inner loop finalizing
2669
- if (isInterrupted(workflow)) {
2670
- workflow.call({ process: End.process, status: ProcessStatus.done });
2671
- return;
2672
- }
2673
- const next = End.finalizeInnerLoop(scroller, error);
2674
- workflow.call({
2675
- process: End.process,
2676
- status: next ? ProcessStatus.next : ProcessStatus.done,
2677
- payload: Object.assign({}, (interrupter ? { process: interrupter } : {}))
2678
- });
2679
- }
2680
- static calculateParams(scroller, workflow) {
2681
- const { adapter, viewport, buffer: { items } } = scroller;
2682
- if (adapter.wanted.firstVisible) {
2683
- const { item } = viewport.getEdgeVisibleItem(items, Direction.backward);
2684
- if (!item || item.element !== adapter.firstVisible.element) {
2685
- adapter.firstVisible = item ? item.get() : EMPTY_ITEM;
2686
- }
2687
- }
2688
- // the workflow can be interrupter on firstVisible change
2689
- if (adapter.wanted.lastVisible && !isInterrupted(workflow)) {
2690
- const { item } = viewport.getEdgeVisibleItem(items, Direction.forward);
2691
- if (!item || item.element !== adapter.lastVisible.element) {
2692
- adapter.lastVisible = item ? item.get() : EMPTY_ITEM;
2693
- }
2694
- }
2695
- }
2696
- static finalizeInnerLoop(scroller, error) {
2697
- const { state, state: { cycle, clip, fetch } } = scroller;
2698
- const next = !!cycle.interrupter || (error ? false : End.getNext(scroller));
2699
- cycle.innerLoop.isInitial = false;
2700
- fetch.stopSimulate();
2701
- clip.reset(true);
2702
- state.endInnerLoop();
2703
- return next;
2704
- }
2705
- static getNext(scroller) {
2706
- const { state: { fetch, render } } = scroller;
2707
- if (fetch.simulate && fetch.isCheck && !render.noSize) { // Adapter.check
2708
- return true;
2709
- }
2710
- if (fetch.simulate && fetch.doRemove) { // Adapter.remove or Adapter.update with clip
2711
- return true;
2712
- }
2713
- if ( // common inner loop (App start, Scroll, Adapter.clip) accompanied by fetch
2714
- !fetch.simulate && ((fetch.hasNewItems && !render.noSize) || fetch.hasAnotherPack)) {
2715
- return true;
2716
- }
2717
- return false;
2718
- }
2719
- }
2720
-
2721
2695
  class Logger {
2722
2696
  constructor(scroller, packageInfo, adapter) {
2723
2697
  this.logs = [];
@@ -2908,12 +2882,50 @@ class Routines {
2908
2882
  constructor(settings) {
2909
2883
  this.horizontal = settings.horizontal;
2910
2884
  this.window = settings.windowViewport;
2885
+ this.viewport = settings.viewport;
2911
2886
  }
2912
2887
  checkElement(element) {
2913
2888
  if (!element) {
2914
2889
  throw new Error('HTML element is not defined');
2915
2890
  }
2916
2891
  }
2892
+ getHostElement(element) {
2893
+ if (this.window) {
2894
+ return document.documentElement;
2895
+ }
2896
+ if (this.viewport) {
2897
+ return this.viewport;
2898
+ }
2899
+ this.checkElement(element);
2900
+ const parent = element.parentElement;
2901
+ this.checkElement(parent);
2902
+ return parent;
2903
+ }
2904
+ getScrollEventReceiver(element) {
2905
+ if (this.window) {
2906
+ return window;
2907
+ }
2908
+ return this.getHostElement(element);
2909
+ }
2910
+ setupScrollRestoration() {
2911
+ if ('scrollRestoration' in history) {
2912
+ history.scrollRestoration = 'manual';
2913
+ }
2914
+ }
2915
+ dismissOverflowAnchor(element) {
2916
+ this.checkElement(element);
2917
+ element.style.overflowAnchor = 'none';
2918
+ }
2919
+ findElementBySelector(element, selector) {
2920
+ this.checkElement(element);
2921
+ return element.querySelector(selector);
2922
+ }
2923
+ findPaddingElement(element, direction) {
2924
+ return this.findElementBySelector(element, `[data-padding-${direction}]`);
2925
+ }
2926
+ findItemElement(element, id) {
2927
+ return this.findElementBySelector(element, `[data-sid="${id}"]`);
2928
+ }
2917
2929
  getScrollPosition(element) {
2918
2930
  if (this.window) {
2919
2931
  return window.pageYOffset;
@@ -2976,6 +2988,12 @@ class Routines {
2976
2988
  return element.offsetTop - (relativeElement ? relativeElement.scrollTop : 0) +
2977
2989
  (direction === (!opposite ? Direction.forward : Direction.backward) ? this.getSize(element) : 0);
2978
2990
  }
2991
+ makeElementVisible(element) {
2992
+ this.checkElement(element);
2993
+ element.style.left = '';
2994
+ element.style.top = '';
2995
+ element.style.position = '';
2996
+ }
2979
2997
  hideElement(element) {
2980
2998
  this.checkElement(element);
2981
2999
  element.style.display = 'none';
@@ -2988,11 +3006,25 @@ class Routines {
2988
3006
  this.checkElement(element);
2989
3007
  element.scrollIntoView(argument);
2990
3008
  }
3009
+ render(cb) {
3010
+ const timeoutId = setTimeout(() => cb());
3011
+ return () => clearTimeout(timeoutId);
3012
+ }
3013
+ animate(cb) {
3014
+ const animationFrameId = requestAnimationFrame(() => cb());
3015
+ return () => cancelAnimationFrame(animationFrameId);
3016
+ }
3017
+ onScroll(element, handler) {
3018
+ element.addEventListener('scroll', handler);
3019
+ return () => element.removeEventListener('scroll', handler);
3020
+ }
2991
3021
  }
2992
3022
 
2993
3023
  class Padding {
2994
3024
  constructor(element, direction, routines) {
2995
- this.element = element.querySelector(`[data-padding-${direction}]`);
3025
+ const found = routines.findPaddingElement(element, direction);
3026
+ routines.checkElement(found);
3027
+ this.element = found;
2996
3028
  this.direction = direction;
2997
3029
  this.routines = routines;
2998
3030
  }
@@ -3067,28 +3099,21 @@ class Viewport {
3067
3099
  this.routines = routines;
3068
3100
  this.state = state;
3069
3101
  this.logger = logger;
3070
- this.disabled = false;
3102
+ this.hostElement = this.routines.getHostElement(this.element);
3103
+ this.scrollEventReceiver = this.routines.getScrollEventReceiver(this.element);
3071
3104
  if (settings.windowViewport) {
3072
- this.hostElement = document.documentElement;
3073
- this.scrollEventReceiver = window;
3074
- }
3075
- else {
3076
- this.hostElement = settings.viewport || this.element.parentElement;
3077
- this.scrollEventReceiver = this.hostElement;
3078
- }
3079
- this.paddings = new Paddings(this.element, this.routines, settings);
3080
- if (settings.windowViewport && 'scrollRestoration' in history) {
3081
- history.scrollRestoration = 'manual';
3105
+ this.routines.setupScrollRestoration();
3082
3106
  }
3083
3107
  if (settings.dismissOverflowAnchor) {
3084
- this.hostElement.style.overflowAnchor = 'none';
3108
+ this.routines.dismissOverflowAnchor(this.hostElement);
3085
3109
  }
3110
+ this.paddings = new Paddings(this.element, this.routines, settings);
3086
3111
  }
3087
3112
  reset(startIndex) {
3088
3113
  this.setOffset();
3089
3114
  this.paddings.reset(this.getSize(), startIndex, this.offset);
3090
3115
  this.scrollPosition = this.paddings.backward.size || 0;
3091
- this.state.scrollState.reset();
3116
+ this.state.scroll.reset();
3092
3117
  }
3093
3118
  setPosition(value) {
3094
3119
  const oldPosition = this.scrollPosition;
@@ -3109,28 +3134,15 @@ class Viewport {
3109
3134
  set scrollPosition(value) {
3110
3135
  this.setPosition(value);
3111
3136
  }
3112
- disableScrollForOneLoop() {
3113
- if (this.disabled) {
3114
- return;
3115
- }
3116
- const { style } = this.hostElement;
3117
- if (style.overflowY === 'hidden') {
3118
- return;
3119
- }
3120
- this.disabled = true;
3121
- const overflow = style.overflowY;
3122
- setTimeout(() => {
3123
- this.disabled = false;
3124
- style.overflowY = overflow;
3125
- });
3126
- style.overflowY = 'hidden';
3127
- }
3128
3137
  getSize() {
3129
3138
  return this.routines.getSize(this.hostElement, true);
3130
3139
  }
3131
3140
  getScrollableSize() {
3132
3141
  return this.routines.getSize(this.element);
3133
3142
  }
3143
+ getMaxScrollPosition() {
3144
+ return this.getScrollableSize() - this.getSize();
3145
+ }
3134
3146
  getBufferPadding() {
3135
3147
  return this.getSize() * this.settings.padding;
3136
3148
  }
@@ -3143,6 +3155,9 @@ class Viewport {
3143
3155
  this.offset -= this.routines.getOffset(this.hostElement);
3144
3156
  }
3145
3157
  }
3158
+ findItemElementById(id) {
3159
+ return this.routines.findItemElement(this.element, id);
3160
+ }
3146
3161
  getEdgeVisibleItem(items, direction) {
3147
3162
  const bwd = direction === Direction.backward;
3148
3163
  const opposite = bwd ? Direction.forward : Direction.backward;
@@ -3744,22 +3759,6 @@ class Buffer {
3744
3759
  this.startIndex = this.absMinIndex;
3745
3760
  }
3746
3761
  }
3747
- appendVirtually(count, fixRight) {
3748
- if (fixRight) {
3749
- this.items.forEach(item => item.updateIndex(item.$index - count));
3750
- this.cache.shiftIndexes(-count);
3751
- this.items = [...this.items];
3752
- }
3753
- this.shiftExtremum(count, fixRight);
3754
- }
3755
- prependVirtually(count, fixRight) {
3756
- if (!fixRight) {
3757
- this.items.forEach(item => item.updateIndex(item.$index + count));
3758
- this.cache.shiftIndexes(count);
3759
- this.items = [...this.items];
3760
- }
3761
- this.shiftExtremum(count, fixRight);
3762
- }
3763
3762
  insertVirtually(items, index, direction, fixRight) {
3764
3763
  if (!this.checkCall.insertVirtual(items, index, direction)) {
3765
3764
  return false;
@@ -3949,6 +3948,7 @@ class InnerLoopModel {
3949
3948
  return this.count === 0;
3950
3949
  }
3951
3950
  done() {
3951
+ this.isInitial = false;
3952
3952
  this.count++;
3953
3953
  this.total++;
3954
3954
  this.busy.set(false);
@@ -3968,7 +3968,7 @@ class WorkflowCycleModel {
3968
3968
  this.innerLoop = new InnerLoopModel(loopCount);
3969
3969
  this.interrupter = null;
3970
3970
  this.busy = new Reactive(false);
3971
- this.done(cycleCount);
3971
+ this.end(cycleCount);
3972
3972
  }
3973
3973
  get loopId() {
3974
3974
  return `${this.instanceIndex}-${this.count}-${this.innerLoop.total}`;
@@ -3976,11 +3976,6 @@ class WorkflowCycleModel {
3976
3976
  get loopIdNext() {
3977
3977
  return `${this.instanceIndex}-${this.count}-${this.innerLoop.total + 1}`;
3978
3978
  }
3979
- done(count) {
3980
- this.count = count;
3981
- this.isInitial = false;
3982
- this.busy.set(false);
3983
- }
3984
3979
  start(isInitial, initiator) {
3985
3980
  this.isInitial = isInitial;
3986
3981
  this.initiator = initiator;
@@ -3989,6 +3984,11 @@ class WorkflowCycleModel {
3989
3984
  this.interrupter = null;
3990
3985
  this.busy.set(true);
3991
3986
  }
3987
+ end(count) {
3988
+ this.count = count;
3989
+ this.isInitial = false;
3990
+ this.busy.set(false);
3991
+ }
3992
3992
  dispose(forever) {
3993
3993
  if (forever) {
3994
3994
  // otherwise the value will be persisted during re-instantiation
@@ -4106,18 +4106,6 @@ class FetchModel {
4106
4106
  this.firstVisible.index = start;
4107
4107
  this.firstVisible.delta = 0;
4108
4108
  }
4109
- append(items) {
4110
- this.startSimulate(items);
4111
- this.last.index = items[items.length - 1].$index;
4112
- this.first.index = items[0].$index;
4113
- this.direction = Direction.forward;
4114
- }
4115
- prepend(items) {
4116
- this.startSimulate(items);
4117
- this.last.index = items[0].$index;
4118
- this.first.index = items[items.length - 1].$index;
4119
- this.direction = Direction.backward;
4120
- }
4121
4109
  check(items) {
4122
4110
  this.startSimulate(items);
4123
4111
  this.last.index = items[0].$index;
@@ -4160,11 +4148,11 @@ class RenderModel {
4160
4148
  this.sizeBefore = 0;
4161
4149
  this.sizeAfter = 0;
4162
4150
  this.positionBefore = 0;
4163
- this.renderTimer = null;
4151
+ this.cancel = null;
4164
4152
  }
4165
4153
  }
4166
4154
 
4167
- class ScrollState {
4155
+ class ScrollModel {
4168
4156
  constructor() {
4169
4157
  this.reset();
4170
4158
  }
@@ -4176,16 +4164,16 @@ class ScrollState {
4176
4164
  this.positionBeforeAsync = null;
4177
4165
  this.positionBeforeAdjust = null;
4178
4166
  this.positionAfterAdjust = null;
4179
- this.cleanupTimers();
4167
+ this.stop();
4180
4168
  }
4181
- cleanupTimers() {
4169
+ stop() {
4182
4170
  if (this.scrollTimer) {
4183
4171
  clearTimeout(this.scrollTimer);
4184
4172
  this.scrollTimer = null;
4185
4173
  }
4186
- if (this.animationFrameId) {
4187
- cancelAnimationFrame(this.animationFrameId);
4188
- this.animationFrameId = 0;
4174
+ if (this.cancelAnimation) {
4175
+ this.cancelAnimation();
4176
+ this.cancelAnimation = null;
4189
4177
  }
4190
4178
  }
4191
4179
  hasPositionChanged(position) {
@@ -4204,25 +4192,19 @@ class State {
4204
4192
  this.fetch = new FetchModel(settings.directionPriority);
4205
4193
  this.clip = new ClipModel();
4206
4194
  this.render = new RenderModel();
4207
- this.scrollState = new ScrollState();
4195
+ this.scroll = new ScrollModel();
4208
4196
  }
4209
4197
  get time() {
4210
4198
  return Number(new Date()) - this.initTime;
4211
4199
  }
4212
- endInnerLoop() {
4213
- const { fetch, render, cycle } = this;
4214
- if (fetch.cancel) {
4215
- fetch.cancel();
4216
- fetch.cancel = null;
4217
- }
4218
- if (render.renderTimer) {
4219
- clearTimeout(render.renderTimer);
4220
- render.renderTimer = null;
4221
- }
4222
- cycle.innerLoop.done();
4200
+ startWorkflowCycle(isInitial, initiator) {
4201
+ this.cycle.start(isInitial, initiator);
4202
+ }
4203
+ endWorkflowCycle(count) {
4204
+ this.cycle.end(count);
4223
4205
  }
4224
4206
  startInnerLoop() {
4225
- const { cycle, scrollState: scroll, fetch, render, clip } = this;
4207
+ const { cycle, scroll: scroll, fetch, render, clip } = this;
4226
4208
  cycle.innerLoop.start();
4227
4209
  scroll.positionBeforeAsync = null;
4228
4210
  if (!fetch.simulate) {
@@ -4235,10 +4217,24 @@ class State {
4235
4217
  doRender: fetch.simulate && fetch.items.length > 0
4236
4218
  } : {}));
4237
4219
  }
4220
+ endInnerLoop() {
4221
+ const { fetch, clip, render, cycle } = this;
4222
+ fetch.stopSimulate();
4223
+ clip.reset(true);
4224
+ if (fetch.cancel) {
4225
+ fetch.cancel();
4226
+ fetch.cancel = null;
4227
+ }
4228
+ if (render.cancel) {
4229
+ render.cancel();
4230
+ render.cancel = null;
4231
+ }
4232
+ cycle.innerLoop.done();
4233
+ }
4238
4234
  dispose() {
4235
+ this.scroll.stop();
4239
4236
  this.cycle.dispose();
4240
4237
  this.endInnerLoop();
4241
- this.scrollState.cleanupTimers();
4242
4238
  }
4243
4239
  }
4244
4240
 
@@ -4924,7 +4920,7 @@ class Workflow {
4924
4920
  this.cyclesDone = 0;
4925
4921
  this.interruptionCount = 0;
4926
4922
  this.errors = [];
4927
- this.disposeScrollEventHandler = () => null;
4923
+ this.offScroll = () => null;
4928
4924
  this.propagateChanges = run;
4929
4925
  this.stateMachineMethods = {
4930
4926
  run: this.runProcess(),
@@ -4952,14 +4948,13 @@ class Workflow {
4952
4948
  status: ProcessStatus.start
4953
4949
  });
4954
4950
  // set up scroll event listener
4955
- const { scrollEventReceiver } = this.scroller.viewport;
4951
+ const { viewport: { scrollEventReceiver }, routines } = this.scroller;
4956
4952
  const onScrollHandler = event => this.callWorkflow({
4957
4953
  process: CommonProcess.scroll,
4958
4954
  status: ProcessStatus.start,
4959
4955
  payload: { event }
4960
4956
  });
4961
- scrollEventReceiver.addEventListener('scroll', onScrollHandler);
4962
- this.disposeScrollEventHandler = () => scrollEventReceiver.removeEventListener('scroll', onScrollHandler);
4957
+ this.offScroll = routines.onScroll(scrollEventReceiver, onScrollHandler);
4963
4958
  }
4964
4959
  changeItems(items) {
4965
4960
  this.propagateChanges(items);
@@ -5045,14 +5040,14 @@ class Workflow {
5045
5040
  const { state, logger } = this.scroller;
5046
5041
  this.cyclesDone++;
5047
5042
  logger.logCycle(false);
5048
- state.cycle.done(this.cyclesDone + 1);
5043
+ state.endWorkflowCycle(this.cyclesDone + 1);
5049
5044
  this.finalize();
5050
5045
  }
5051
5046
  dispose() {
5052
5047
  if (this.initTimer) {
5053
5048
  clearTimeout(this.initTimer);
5054
5049
  }
5055
- this.disposeScrollEventHandler();
5050
+ this.offScroll();
5056
5051
  this.adapterRun$.dispose();
5057
5052
  this.scroller.dispose(true);
5058
5053
  Object.getOwnPropertyNames(this).forEach(prop => {