cradova 3.15.0 → 3.15.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.
package/dist/index.js CHANGED
@@ -605,82 +605,93 @@ class List {
605
605
  state;
606
606
  item;
607
607
  length;
608
- options;
609
- renderingRange;
608
+ itemHeight = 35;
609
+ windowCoverage = 500;
610
+ overscan = 20;
611
+ scrollingDirection = "vertical";
612
+ opts;
613
+ columns = 1;
610
614
  container;
611
- firstItemIndex = 0;
612
- lastItemIndex = 0;
613
615
  rendered = false;
614
616
  subscribers = [];
615
- constructor(state, item, options) {
617
+ scrollPos = 0;
618
+ list;
619
+ startIndex = 0;
620
+ listContainer;
621
+ constructor(state, item, opts) {
616
622
  this.state = state;
617
- this.item = item || ((item2) => div(String(item2)));
623
+ this.item = item || ((item2, i2) => div(String(item2) + " " + i2));
618
624
  this.length = state.length;
619
- this.options = options;
620
- this.renderingRange = Math.round(Math.min(this.length > 50 ? this.length * 0.5 : this.length, window.innerHeight / (this.options?.itemHeight || 1)));
621
- this.lastItemIndex = this.renderingRange - 1;
622
- this.container = document.createElement("div");
623
- if (this.options?.className) {
624
- this.container.className = this.options?.className;
625
- }
626
- if (this.options?.id) {
627
- this.container.id = this.options?.id;
628
- }
625
+ this.opts = opts;
626
+ this.itemHeight = opts?.itemHeight || 35;
627
+ this.columns = opts?.columns || 1;
628
+ this.windowCoverage = opts?.windowHeight || opts?.windowWidth || 500;
629
+ this.overscan = opts?.overscan || 20;
630
+ this.scrollingDirection = opts?.scrollingDirection || "vertical";
631
+ this.container = div({
632
+ className: this.opts?.className,
633
+ onscroll: (e) => {
634
+ this.scrollPos = Math.floor(this.scrollingDirection === "vertical" ? e.target.scrollTop : e.target.scrollLeft);
635
+ requestAnimationFrame(() => this.render());
636
+ },
637
+ style: {
638
+ overflowY: this.scrollingDirection === "vertical" ? "scroll" : "hidden",
639
+ overflowX: this.scrollingDirection === "horizontal" ? "scroll" : "hidden",
640
+ height: this.opts?.windowHeight ? `${this.opts?.windowHeight}px` : "500px",
641
+ width: this.opts?.windowWidth ? `${this.opts?.windowWidth}px` : "100%"
642
+ }
643
+ }, div({
644
+ id: "listContainer",
645
+ style: {
646
+ height: `${Math.round(this.length * this.itemHeight / this.columns)}px`
647
+ }
648
+ }, div({
649
+ id: "list",
650
+ className: this.opts?.className,
651
+ style: {
652
+ transform: this.scrollingDirection === "vertical" ? `translateY(${this.scrollPos}px)` : `translateX(${this.scrollPos}px)`
653
+ }
654
+ })));
655
+ this.listContainer = this.container.querySelector("#listContainer");
656
+ this.list = this.container.querySelector("#list");
629
657
  }
630
658
  get Element() {
631
- if (this.rendered) {
632
- return this.container;
633
- }
634
- for (let i2 = 0;i2 < this.renderingRange; i2++) {
635
- const item = this.item(this.state[i2]);
636
- item.setAttribute("data-index", i2.toString());
637
- this.container.appendChild(item);
638
- }
639
- this.rendered = true;
640
- const domObser = () => {
641
- const observer = new IntersectionObserver((entries) => {
642
- entries.forEach((entry) => {
643
- if (entry.isIntersecting) {
644
- const isBottom = entry.target === this.container.lastElementChild;
645
- const isTop = !isBottom;
646
- observer.unobserve(entry.target);
647
- const index = Number(entry.target.getAttribute("data-index"));
648
- if (isBottom) {
649
- for (let i2 = index + 1;i2 < this.length; i2++) {
650
- const item = this.item(this.state[i2]);
651
- item.setAttribute("data-index", i2.toString());
652
- this.container.appendChild(item);
653
- }
654
- for (let i2 = index - this.renderingRange;i2 > 0; i2--) {
655
- this.container.removeChild(this.container.children[i2]);
656
- }
657
- this.firstItemIndex = Number(this.container.firstElementChild?.getAttribute("data-index") || 0);
658
- this.lastItemIndex = Number(this.container.lastElementChild?.getAttribute("data-index") || 0);
659
- }
660
- if (isTop) {
661
- for (let i2 = index - 1;i2 > 0; i2--) {
662
- const item = this.item(this.state[i2]);
663
- item.setAttribute("data-index", i2.toString());
664
- this.container.appendChild(item);
665
- }
666
- for (let i2 = index + this.renderingRange;i2 < this.length; i2++) {
667
- this.container.removeChild(this.container.children[i2]);
668
- }
669
- this.lastItemIndex = Number(this.container.lastElementChild?.getAttribute("data-index") || 0);
670
- this.firstItemIndex = Number(this.container.firstElementChild?.getAttribute("data-index") || 0);
671
- }
672
- }
673
- });
659
+ if (!this.rendered) {
660
+ this.render();
661
+ this.rendered = true;
662
+ const relativeScrolling = () => {
663
+ const rect = this.container.getBoundingClientRect();
664
+ if (rect.top < 0 && rect.bottom > window.innerHeight) {
665
+ this.scrollPos = Math.abs(rect.top);
666
+ requestAnimationFrame(() => this.render());
667
+ }
668
+ };
669
+ window.addEventListener("scroll", relativeScrolling);
670
+ window.CradovaEvent.after_page_is_killed.push(() => {
671
+ window.removeEventListener("scroll", relativeScrolling);
674
672
  });
675
- observer.observe(this.container.lastElementChild);
676
- observer.observe(this.container.firstElementChild);
677
- };
678
- window.addEventListener("scroll", domObser);
679
- window.CradovaEvent.after_page_is_killed.push(() => {
680
- window.removeEventListener("scroll", domObser);
681
- });
673
+ }
682
674
  return this.container;
683
675
  }
676
+ render() {
677
+ const startIndex = Math.floor(this.scrollPos / this.itemHeight) * this.columns;
678
+ this.list.style.transform = this.scrollingDirection === "vertical" ? `translateY(${Math.floor(this.scrollPos / this.itemHeight) * this.itemHeight}px)` : `translateX(${Math.floor(this.scrollPos / this.itemHeight) * this.itemHeight}px)`;
679
+ let renderedNodesCount = (Math.ceil(this.windowCoverage / this.itemHeight) + this.overscan) * this.columns;
680
+ renderedNodesCount = Math.min(this.length - startIndex, renderedNodesCount);
681
+ for (;this.list.firstElementChild; )
682
+ this.list.firstElementChild.remove();
683
+ let index = 0;
684
+ for (let i2 = 0;i2 < renderedNodesCount; i2++) {
685
+ index = i2 + startIndex;
686
+ if (this.state[index]) {
687
+ this.list.appendChild(this.item(this.state[index], index));
688
+ }
689
+ }
690
+ if (index + 1 === this.length) {
691
+ this.opts?.onScrollEnd?.();
692
+ }
693
+ this.startIndex = startIndex;
694
+ }
684
695
  computed(listener) {
685
696
  if (!listener) {
686
697
  console.error(` ✘ Cradova err: listener ${String(listener)} is not a valid event name or function`);
@@ -704,34 +715,41 @@ class List {
704
715
  }
705
716
  diffDOMBeforeUpdatingState(newState) {
706
717
  this.length = newState.length;
707
- this.renderingRange = Math.round(Math.min(this.length > 100 ? this.length * 0.5 : this.length, window.innerHeight / (this.options?.itemHeight || 1)));
708
- this.lastItemIndex = this.firstItemIndex + this.renderingRange;
709
- for (let i2 = this.lastItemIndex;i2 >= this.firstItemIndex; i2--) {
710
- if ((this.state[i2] === undefined || newState[i2] === undefined) && this.container.children[i2] !== undefined) {
711
- this.container.removeChild(this.container.children[i2]);
712
- continue;
713
- }
714
- if (JSON.stringify(this.state[i2]) === JSON.stringify(newState[i2])) {
715
- continue;
716
- }
717
- const item = this.item(newState[i2]);
718
- item.setAttribute("data-index", i2.toString());
719
- if (this.container.children[i2]) {
720
- this.container.replaceChild(item, this.container.children[i2]);
721
- } else {
722
- this.container.appendChild(item);
718
+ let startIndex = Math.floor(this.scrollPos / this.itemHeight) * this.columns;
719
+ startIndex = Math.floor(startIndex / this.columns) * this.columns;
720
+ let renderedNodesCount = this.list.childElementCount;
721
+ if (renderedNodesCount < this.overscan) {
722
+ this.state = newState;
723
+ this.render();
724
+ } else {
725
+ for (let i2 = 0;i2 < renderedNodesCount; i2++) {
726
+ const index = i2 + startIndex;
727
+ if (newState[index] === undefined) {
728
+ this.list.children[index]?.remove();
729
+ continue;
730
+ }
731
+ const item = this.item(newState[index], index);
732
+ if (this.list.children[index]) {
733
+ this.list.replaceChild(item, this.list.children[index]);
734
+ } else {
735
+ this.list.appendChild(item);
736
+ }
723
737
  }
738
+ this.list.style.transform = this.scrollingDirection === "vertical" ? `translateY(${Math.floor(this.scrollPos / this.itemHeight) * this.itemHeight}px)` : `translateX(${Math.floor(this.scrollPos / this.itemHeight) * this.itemHeight}px)`;
739
+ this.state = newState;
724
740
  }
725
- this.lastItemIndex = Number(this.container.lastElementChild?.getAttribute("data-index") || 0);
726
- this.firstItemIndex = Number(this.container.firstElementChild?.getAttribute("data-index") || 0);
727
- this.state = newState;
728
- this.subscribers.forEach((sub) => {
729
- const isComp = !isArrowFunc(sub);
730
- if (isComp) {
731
- compManager.recall(sub);
732
- } else {
733
- sub?.();
734
- }
741
+ if (this.length !== newState.length) {
742
+ this.listContainer.style.height = `${Math.round(this.length * this.itemHeight / this.columns)}px`;
743
+ }
744
+ queueMicrotask(() => {
745
+ this.subscribers.forEach((sub) => {
746
+ const isComp = !isArrowFunc(sub);
747
+ if (isComp) {
748
+ compManager.recall(sub);
749
+ } else {
750
+ sub?.();
751
+ }
752
+ });
735
753
  });
736
754
  }
737
755
  get data() {
@@ -52,14 +52,29 @@ export declare class Signal<Type extends Record<string, any> = any> {
52
52
  */
53
53
  export declare class List<T> {
54
54
  length: number;
55
+ private windowCoverage;
56
+ private overscan;
57
+ private scrollingDirection;
58
+ private opts?;
59
+ private columns;
55
60
  private rendered;
56
61
  subscribers: Function[];
57
- constructor(state: T[], item?: (item: T) => HTMLElement, options?: {
58
- itemHeight: number;
62
+ scrollPos: number;
63
+ list: HTMLElement;
64
+ startIndex: number;
65
+ listContainer: HTMLElement;
66
+ constructor(state: T[], item?: (item: T, i: number) => HTMLElement, opts?: {
67
+ itemHeight?: number;
59
68
  className?: string;
60
- id?: string;
69
+ columns?: number;
70
+ windowHeight?: number;
71
+ windowWidth?: number;
72
+ overscan?: number;
73
+ scrollingDirection?: "vertical" | "horizontal";
74
+ onScrollEnd?: () => void;
61
75
  });
62
76
  get Element(): HTMLElement;
77
+ private render;
63
78
  computed(listener?: (() => void) | Comp | ((ctx: Comp) => HTMLElement)): HTMLElement | undefined;
64
79
  private diffDOMBeforeUpdatingState;
65
80
  get data(): IterableIterator<T>;
@@ -2,11 +2,10 @@ import * as CSS from "csstype";
2
2
  import { Page, RefInstance, Signal } from "./classes.js";
3
3
  interface Attributes<E extends HTMLElement> {
4
4
  ref?: [RefInstance<any>, string];
5
- value?: any;
6
5
  style?: Partial<CSS.Properties>;
7
- [key: `data-${string}`]: string | undefined;
8
- [key: `aria-${string}`]: string | undefined;
9
- [key: `on${string}`]: ((this: E, event: StandardEvents) => void) | undefined;
6
+ [key: `data-${string}`]: string;
7
+ [key: `aria-${string}`]: string;
8
+ [key: `on${string}`]: (this: E, event: StandardEvents) => void;
10
9
  }
11
10
  type StandardEvents = KeyboardEvent | MouseEvent | TouchEvent | WheelEvent | DragEvent | ClipboardEvent | CompositionEvent | FocusEvent | InputEvent | AnimationEvent | TransitionEvent | Event;
12
11
  type OmitFunctions<E> = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cradova",
3
- "version": "3.15.0",
3
+ "version": "3.15.2",
4
4
  "description": "Build Powerful ⚡ Web Apps with Ease",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -55,7 +55,8 @@
55
55
  },
56
56
  "homepage": "https://github.com/CodeDynasty-dev/cradova",
57
57
  "dependencies": {
58
- "csstype": "^3.1.3"
58
+ "csstype": "^3.1.3",
59
+ "virtualized-list": "^2.2.0"
59
60
  },
60
61
  "docmach": {
61
62
  "docs-directory": "docs/docs",