Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 77 additions & 3 deletions spec/dd-simple-integration-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,16 +233,90 @@ describe('DD Integration Tests', () => {
it('should support event listeners on DDElement', () => {
const ddElement = DDElement.init(element);
const callback = vi.fn();

ddElement.setupDraggable({});
ddElement.on('dragstart', callback);
ddElement.off('dragstart');

// Should not throw
expect(typeof ddElement.on).toBe('function');
expect(typeof ddElement.off).toBe('function');

ddElement.cleanDraggable();
});
});

describe('DDDraggable._getClipping autoscroll', () => {
let draggable: DDDraggable;
let helper: HTMLElement;
let scrollEl: HTMLElement;

// Helper to mock getBoundingClientRect on an element
const mockRect = (el: HTMLElement, rect: Partial<DOMRect>) => {
vi.spyOn(el, 'getBoundingClientRect').mockReturnValue({
top: 0, bottom: 0, left: 0, right: 0, width: 0, height: 0, x: 0, y: 0,
toJSON: () => ({}),
...rect,
} as DOMRect);
};

beforeEach(() => {
draggable = new DDDraggable(element);
helper = document.createElement('div');
scrollEl = document.createElement('div');
document.body.appendChild(helper);
document.body.appendChild(scrollEl);
});

afterEach(() => {
draggable.destroy();
helper.remove();
scrollEl.remove();
});

it('returns 0 when helper is fully inside scroll container', () => {
mockRect(helper, { top: 100, bottom: 200 });
mockRect(scrollEl, { top: 0, bottom: 600 });
expect((draggable as any)._getClipping(helper, scrollEl)).toBe(0);
});

it('returns negative value when helper is clipped above scroll container', () => {
mockRect(helper, { top: -50, bottom: 50 });
mockRect(scrollEl, { top: 0, bottom: 600 });
const clip = (draggable as any)._getClipping(helper, scrollEl);
expect(clip).toBeLessThan(0);
});

it('returns positive value when helper is clipped below scroll container', () => {
mockRect(helper, { top: 550, bottom: 650 });
mockRect(scrollEl, { top: 0, bottom: 600 });
const clip = (draggable as any)._getClipping(helper, scrollEl);
expect(clip).toBeGreaterThan(0);
});

it('returns 0 when helper is fully outside a nested (non-root) scroll container — stops nested scroll', () => {
// helper is entirely above the nested container
mockRect(helper, { top: -200, bottom: -50 });
mockRect(scrollEl, { top: 0, bottom: 600 });
// scrollEl is NOT the document root, so fully-outside should stop scrolling
expect((draggable as any)._getClipping(helper, scrollEl)).toBe(0);
});

it('continues scrolling when helper is fully above the root scroll container', () => {
const rootEl = document.scrollingElement as HTMLElement || document.documentElement;
mockRect(helper, { top: -200, bottom: -50 });
mockRect(rootEl, { top: 0, bottom: 600 });
// For the root container, fully-outside-above should still return a negative clipping value
const clip = (draggable as any)._getClipping(helper, rootEl);
expect(clip).toBeLessThan(0);
});

it('continues scrolling when helper is fully below the root scroll container', () => {
const rootEl = document.scrollingElement as HTMLElement || document.documentElement;
mockRect(helper, { top: 700, bottom: 850 });
mockRect(rootEl, { top: 0, bottom: 600 });
const clip = (draggable as any)._getClipping(helper, rootEl);
expect(clip).toBeGreaterThan(0);
});
});
});
3 changes: 2 additions & 1 deletion src/dd-draggable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,8 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt
const elRect = el.getBoundingClientRect();
const scrollRect = scrollEl.getBoundingClientRect();
const viewportH = window.innerHeight || document.documentElement.clientHeight;
if (elRect.bottom < scrollRect.top || elRect.top > scrollRect.bottom) return 0; // fully outside
const isRoot = scrollEl === (document.scrollingElement || document.documentElement);
if (!isRoot && (elRect.bottom < scrollRect.top || elRect.top > scrollRect.bottom)) return 0; // fully outside nested container
const clippedBelow = elRect.bottom - Math.min(scrollRect.bottom, viewportH);
const clippedAbove = elRect.top - Math.max(scrollRect.top, 0);
if (clippedAbove < 0) return clippedAbove;
Expand Down