diff --git a/demo/index.html b/demo/index.html
index ed3b6b780..375fa1543 100644
--- a/demo/index.html
+++ b/demo/index.html
@@ -28,6 +28,7 @@ <h1>Demos</h1>
     <li><a href="responsive_break.html">Responsive: using breakpoints</a></li>
     <li><a href="responsive_none.html">Responsive: using layout:'none'</a></li>
     <li><a href="right-to-left(rtl).html">Right-To-Left (RTL)</a></li>
+    <li><a href="scrollSpeed.html">Scroll Speed</a></li>
     <li><a href="serialization.html">Serialization</a></li>
     <li><a href="sizeToContent.html">Size To Content</a></li>
     <li><a href="static.html">Static</a></li>
diff --git a/demo/scrollSpeed.html b/demo/scrollSpeed.html
new file mode 100644
index 000000000..0b11e0f47
--- /dev/null
+++ b/demo/scrollSpeed.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="utf-8">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge">
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <title>Scroll Speed Demo</title>
+
+  <link rel="stylesheet" href="demo.css"/>
+  <script src="../dist/gridstack-all.js"></script>
+
+</head>
+<body>
+  <div class="container-fluid">
+    <h1>Scroll Speed Demo</h1>
+    <p>This demo shows 20 widgets to test scroll speed behavior when dragging items. Try dragging widgets near the top or bottom of the viewport to see the scrolling effect.</p>
+    <div>
+      <label for="maxScrollSpeed">Max Scroll Speed:</label>
+      <input type="number" id="maxScrollSpeed" value="0" min="0" step="1" onchange="updateMaxScrollSpeed()" style="margin-left: 5px; width: 80px;">
+      <small style="margin-left: 10px;">0 = no limit, higher values = slower scrolling</small>
+    </div>
+    <br><br>
+    <div class="grid-stack"></div>
+  </div>
+  <script src="events.js"></script>
+  <script type="text/javascript">
+    let grid = GridStack.init({
+      float: true,
+      maxScrollSpeed: 0, // default to 0 (no limit)
+      resizable: { handles: 'all'}
+    });
+    addEvents(grid);
+
+    // Create 20 widgets with varied sizes and positions (all sizes between 2-5, no overlaps)
+    let items = [
+      // Row 1
+      {x: 0, y: 0, w: 3, h: 2, content: "Widget 1"},
+      {x: 3, y: 0, w: 4, h: 3, content: "Widget 2"},
+      {x: 7, y: 0, w: 2, h: 2, content: "Widget 3"},
+      {x: 9, y: 0, w: 3, h: 2, content: "Widget 4"},
+      
+      // Row 2
+      {x: 0, y: 2, w: 2, h: 3, content: "Widget 5"},
+      {x: 2, y: 2, w: 5, h: 2, content: "Widget 6"},
+      {x: 9, y: 2, w: 3, h: 3, content: "Widget 7"},
+      
+      // Row 3
+      {x: 0, y: 5, w: 4, h: 2, content: "Widget 8"},
+      {x: 4, y: 5, w: 2, h: 3, content: "Widget 9"},
+      {x: 6, y: 5, w: 3, h: 2, content: "Widget 10"},
+      
+      // Row 4
+      {x: 0, y: 7, w: 3, h: 3, content: "Widget 11"},
+      {x: 3, y: 7, w: 2, h: 2, content: "Widget 12"},
+      {x: 5, y: 7, w: 4, h: 3, content: "Widget 13"},
+      {x: 9, y: 7, w: 3, h: 2, content: "Widget 14"},
+      
+      // Row 5
+      {x: 0, y: 10, w: 5, h: 2, content: "Widget 15"},
+      {x: 5, y: 10, w: 2, h: 3, content: "Widget 16"},
+      {x: 7, y: 10, w: 3, h: 2, content: "Widget 17"},
+      
+      // Row 6
+      {x: 0, y: 12, w: 2, h: 2, content: "Widget 18"},
+      {x: 2, y: 12, w: 4, h: 3, content: "Widget 19"},
+      {x: 6, y: 12, w: 3, h: 2, content: "Widget 20"}
+    ];
+
+    grid.load(items);
+
+    updateMaxScrollSpeed = function() {
+      const value = parseInt(document.getElementById('maxScrollSpeed').value) || 0;
+      grid.opts.maxScrollSpeed = value;
+      console.log('Max scroll speed updated to:', value);
+    };
+  </script>
+</body>
+</html> 
\ No newline at end of file
diff --git a/doc/README.md b/doc/README.md
index e4c31a9ef..5b3b3ae5c 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -116,6 +116,7 @@ gridstack.js API
 - `marginBottom`: numberOrString
 - `marginLeft`: numberOrString
 - `maxRow` - maximum rows amount. Default is `0` which means no max.
+- `maxScrollSpeed` - (number) limits the speed that the user wll scroll up and down in the grid. This is most noticable in large grids. Default: `0` which indicates no limit on the scroll speed. Any value provided here should be positive.
 - `minRow` - minimum rows amount which is handy to prevent grid from collapsing when empty. Default is `0`. You can also do this with `min-height` CSS attribute on the grid div in pixels, which will round to the closest row.
 - `nonce` - If you are using a nonce-based Content Security Policy, pass your nonce here and
 GridStack will add it to the `<style>` elements it creates.
diff --git a/package.json b/package.json
index 7e18eb3e9..be891ca90 100644
--- a/package.json
+++ b/package.json
@@ -1,5 +1,5 @@
 {
-  "name": "gridstack",
+  "name": "@time-loop/gridstack.js",
   "version": "12.2.1",
   "license": "MIT",
   "author": "Alain Dumesny <alaind831+github@gmail.com> (https://github.com/adumesny)",
@@ -12,7 +12,7 @@
   "types": "./dist/gridstack.d.ts",
   "repository": {
     "type": "git",
-    "url": "git+https://github.com/gridstack/gridstack.js.git"
+    "url": "git+https://github.com/time-loop/github-packages"
   },
   "funding": [
     {
@@ -35,6 +35,9 @@
     "reset": "rm -rf dist node_modules",
     "prepublishOnly": "yarn build"
   },
+  "publishConfig": {
+    "registry": "https://npm.pkg.github.com"
+  },
   "keywords": [
     "Typescript",
     "gridstack.js",
diff --git a/spec/utils-spec.ts b/spec/utils-spec.ts
index 3aa4f3111..ba43d2a54 100644
--- a/spec/utils-spec.ts
+++ b/spec/utils-spec.ts
@@ -116,6 +116,88 @@ describe('gridstack utils', function() {
     });
   });
 
+  describe('_getScrollAmount', () => {
+    const innerHeight = 800;
+    const elHeight = 600;
+
+    it('should not scroll if element is inside viewport', () => {
+      const rect = { top: 100, bottom: 700, left: 0, right: 0, width: 50, height: 50, toJSON: () => '' };
+      const scrollAmount = Utils._getScrollAmount(rect as DOMRect, innerHeight, elHeight, -10);
+      expect(scrollAmount).toBe(0);
+    });
+
+    it('should not limit the scroll speed if the user has set maxScrollSpeed to 0', () => {
+      const rect = { top: 220, bottom: 850, left: 0, right: 0, width: 50, height: 50, toJSON: () => '' };
+      const scrollAmount = Utils._getScrollAmount(rect as DOMRect, innerHeight, elHeight, 50);
+      expect(scrollAmount).toBe(50);
+    });
+
+    it('should treat a negative maxScrollSpeed as positive', () => {
+      const rect = { top: 220, bottom: 850, left: 0, right: 0, width: 50, height: 50, toJSON: () => '' };
+      const scrollAmount = Utils._getScrollAmount(rect as DOMRect, innerHeight, elHeight, 50, -4 );
+      expect(scrollAmount).toBe(4);
+    });
+
+    describe('scrolling up', () => {
+      it('should scroll up', () => {
+        const rect = { top: -20, bottom: 580, left: 0, right: 0, width: 50, height: 50, toJSON: () => '' };
+        const scrollAmount = Utils._getScrollAmount(rect as DOMRect, innerHeight, elHeight, -30);
+        expect(scrollAmount).toBe(-20);
+      });
+      it('should scroll up to bring dragged element into view', () => {
+        const rect = { top: -20, bottom: 580, left: 0, right: 0, width: 50, height: 50, toJSON: () => '' };
+        const scrollAmount = Utils._getScrollAmount(rect as DOMRect, innerHeight, elHeight, -10);
+        expect(scrollAmount).toBe(-10);
+      });
+      it('should scroll up when dragged element is larger than viewport', () => {
+        const rect = { top: -20, bottom: 880, left: 0, right: 0, width: 50, height: 50, toJSON: () => '' };
+        const scrollAmount = Utils._getScrollAmount(rect as DOMRect, innerHeight, 900, -30);
+        expect(scrollAmount).toBe(-30);
+      });
+
+      it('should limit the scroll speed when the expected scroll speed is greater than the maxScrollSpeed', () => {
+        const rect = { top: -30, bottom: 880, left: 0, right: 0, width: 50, height: 50, toJSON: () => '' };
+        const scrollAmountWithoutLimit = Utils._getScrollAmount(rect as DOMRect, innerHeight, elHeight, -30);
+        expect(scrollAmountWithoutLimit).toBe(-30); // be completely sure that the scroll amount should be limited
+
+        const scrollAmount = Utils._getScrollAmount(rect as DOMRect, innerHeight, elHeight, -30, 10);
+        expect(scrollAmount).toBe(-10);
+      });
+    });
+
+    describe('scrolling down', () => {
+      it('should not scroll down if element is inside viewport', () => {
+        const rect = { top: 100, bottom: 700, left: 0, right: 0, width: 50, height: 50, toJSON: () => '' };
+        const scrollAmount = Utils._getScrollAmount(rect as DOMRect, innerHeight, elHeight, 10);
+        expect(scrollAmount).toBe(0);
+      });
+      it('should scroll down', () => {
+        const rect = { top: 220, bottom: 820, left: 0, right: 0, width: 50, height: 50, toJSON: () => '' };
+        const scrollAmount = Utils._getScrollAmount(rect as DOMRect, innerHeight, elHeight, 10);
+        expect(scrollAmount).toBe(10);
+      });
+      it('should scroll down to bring dragged element into view', () => {
+        const rect = { top: 220, bottom: 820, left: 0, right: 0, width: 50, height: 50, toJSON: () => '' };
+        const scrollAmount = Utils._getScrollAmount(rect as DOMRect, innerHeight, elHeight, 30);
+        expect(scrollAmount).toBe(20);
+      });
+      it('should scroll down when dragged element is larger than viewport', () => {
+        const rect = { top: -100, bottom: 820, left: 0, right: 0, width: 50, height: 50, toJSON: () => '' };
+        const scrollAmount = Utils._getScrollAmount(rect as DOMRect, innerHeight, 920, 10);
+        expect(scrollAmount).toBe(10);
+      });
+
+      it('should limit the scroll speed when the expected scroll speed is greater than the maxScrollSpeed', () => {
+        const rect = { top: 220, bottom: 850, left: 0, right: 0, width: 50, height: 50, toJSON: () => '' };
+        const scrollAmountWithoutLimit = Utils._getScrollAmount(rect as DOMRect, innerHeight, elHeight, 50);
+        expect(scrollAmountWithoutLimit).toBe(50); // be completely sure that the scroll amount should be limited
+
+        const scrollAmount = Utils._getScrollAmount(rect as DOMRect, innerHeight, elHeight, 10, 10);
+        expect(scrollAmount).toBe(10);
+      });
+    });
+  });
+
   describe('clone', () => {
     const a: any = {first: 1, second: 'text'};
     const b: any = {first: 1, second: {third: 3}};
diff --git a/src/gridstack.ts b/src/gridstack.ts
index 3ca758fc4..07fe4a16f 100644
--- a/src/gridstack.ts
+++ b/src/gridstack.ts
@@ -2541,7 +2541,7 @@ export class GridStack {
       const distance = ui.position.top - node._prevYPix;
       node._prevYPix = ui.position.top;
       if (this.opts.draggable.scroll !== false) {
-        Utils.updateScrollPosition(el, ui.position, distance);
+        Utils.updateScrollPosition(el, ui.position, distance, this.opts.maxScrollSpeed);
       }
 
       // get new position taking into account the margin in the direction we are moving! (need to pass mid point by margin)
diff --git a/src/types.ts b/src/types.ts
index b59735e5a..3520e1e44 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -217,6 +217,9 @@ export interface GridStackOptions {
   /** maximum rows amount. Default? is 0 which means no maximum rows */
   maxRow?: number;
 
+  /** maximum scroll speed when dragging items. Any negative value will be converted to the positive value. (default?: 0 = no limit) */
+  maxScrollSpeed?: number;
+
   /** minimum rows amount. Default is `0`. You can also do this with `min-height` CSS attribute
    * on the grid div in pixels, which will round to the closest row.
    */
diff --git a/src/utils.ts b/src/utils.ts
index 4014a1891..84841d2d8 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -351,37 +351,51 @@ export class Utils {
   }
 
   /** @internal */
-  static updateScrollPosition(el: HTMLElement, position: {top: number}, distance: number): void {
+  static _getScrollAmount(rect: DOMRect, viewportHeight: number, elHeight: number, distance: number, maxScrollSpeed?: number): number {
+    const offsetDiffDown = rect.bottom - viewportHeight;
+    const offsetDiffUp = rect.top;
+    const elementIsLargerThanViewport = elHeight > viewportHeight;
+    let scrollAmount = 0;
+
+    if (rect.top < 0 && distance < 0) {
+      // moving up
+      if (elementIsLargerThanViewport) {
+        scrollAmount = distance;
+      } else {
+        scrollAmount = Math.abs(offsetDiffUp) > Math.abs(distance) ? distance : offsetDiffUp;
+      }
+    } else if (rect.bottom > viewportHeight && distance > 0) {
+      // moving down
+      if (elementIsLargerThanViewport) {
+        scrollAmount = distance;
+      } else {
+        scrollAmount = offsetDiffDown > distance ? distance : offsetDiffDown;
+      }
+    }
+
+    if (maxScrollSpeed) {
+      maxScrollSpeed = Math.abs(maxScrollSpeed);
+      if (scrollAmount > maxScrollSpeed) {
+        scrollAmount = maxScrollSpeed;
+      } else if (scrollAmount < -maxScrollSpeed) {
+        scrollAmount = -maxScrollSpeed;
+      }
+    }
+    return scrollAmount;
+  }
+
+  /** @internal */
+  static updateScrollPosition(el: HTMLElement, position: {top: number}, distance: number, maxScrollSpeed?: number): void {
     // is widget in view?
     const rect = el.getBoundingClientRect();
-    const innerHeightOrClientHeight = (window.innerHeight || document.documentElement.clientHeight);
-    if (rect.top < 0 ||
-      rect.bottom > innerHeightOrClientHeight
-    ) {
-      // set scrollTop of first parent that scrolls
-      // if parent is larger than el, set as low as possible
-      // to get entire widget on screen
-      const offsetDiffDown = rect.bottom - innerHeightOrClientHeight;
-      const offsetDiffUp = rect.top;
+    const viewportHeight = (window.innerHeight || document.documentElement.clientHeight);
+    const scrollAmount = this._getScrollAmount(rect, viewportHeight, el.offsetHeight, distance, maxScrollSpeed);
+
+    if (scrollAmount) {
       const scrollEl = this.getScrollElement(el);
-      if (scrollEl !== null) {
+      if (scrollEl) {
         const prevScroll = scrollEl.scrollTop;
-        if (rect.top < 0 && distance < 0) {
-          // moving up
-          if (el.offsetHeight > innerHeightOrClientHeight) {
-            scrollEl.scrollTop += distance;
-          } else {
-            scrollEl.scrollTop += Math.abs(offsetDiffUp) > Math.abs(distance) ? distance : offsetDiffUp;
-          }
-        } else if (distance > 0) {
-          // moving down
-          if (el.offsetHeight > innerHeightOrClientHeight) {
-            scrollEl.scrollTop += distance;
-          } else {
-            scrollEl.scrollTop += offsetDiffDown > distance ? distance : offsetDiffDown;
-          }
-        }
-        // move widget y by amount scrolled
+        scrollEl.scrollTop += scrollAmount;
         position.top += scrollEl.scrollTop - prevScroll;
       }
     }
