Skip to content

Commit f053a04

Browse files
committed
[#74195] Fix incorrect indentation on drag preview
Adds an optional `mirrorContainer` target to the drag-and-drop controller. When present, the dragula mirror (drag preview) is inserted into that element instead of `document.body`, so it inherits the container's padding and alignment. The Backlog inbox wires its own list element as both `container` and `mirrorContainer`, which keeps the dragged card visually aligned with the rest of the inbox rather than jumping to the page edge. https://community.openproject.org/wp/74195
1 parent 2f7e0e5 commit f053a04

4 files changed

Lines changed: 42 additions & 2 deletions

File tree

frontend/src/stimulus/controllers/dynamic/generic-drag-and-drop.controller.spec.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,14 @@ describe('GenericDragAndDropController', () => {
7070
return ariaPressedTarget.call(controller, el);
7171
}
7272

73+
function callResolveMirrorContainer():Element {
74+
const resolveMirrorContainer = Reflect.get(controller, 'resolveMirrorContainer') as (
75+
this:GenericDragAndDropController
76+
) => Element;
77+
78+
return resolveMirrorContainer.call(controller);
79+
}
80+
7381
describe('canStartDrag', () => {
7482
it('allows dragging a draggable row in handle-less mode', () => {
7583
const row = draggableRow();
@@ -147,4 +155,21 @@ describe('GenericDragAndDropController', () => {
147155
expect(callAriaPressedTarget(row)).toBe(handle);
148156
});
149157
});
158+
159+
describe('resolveMirrorContainer', () => {
160+
it('returns the configured mirror container target when present', () => {
161+
const mirrorContainer = document.createElement('div');
162+
163+
Object.defineProperty(controller, 'hasMirrorContainerTarget', { value: true, configurable: true });
164+
Object.defineProperty(controller, 'mirrorContainerTarget', { value: mirrorContainer, configurable: true });
165+
166+
expect(callResolveMirrorContainer()).toBe(mirrorContainer);
167+
});
168+
169+
it('falls back to document.body when no mirror container target exists', () => {
170+
Object.defineProperty(controller, 'hasMirrorContainerTarget', { value: false, configurable: true });
171+
172+
expect(callResolveMirrorContainer()).toBe(document.body);
173+
});
174+
});
150175
});

frontend/src/stimulus/controllers/dynamic/generic-drag-and-drop.controller.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,12 @@ interface TargetConfig {
4343
}
4444

4545
export default class GenericDragAndDropController extends Controller {
46-
static targets = ['container', 'scrollContainer'];
46+
static targets = ['container', 'scrollContainer', 'mirrorContainer'];
4747

4848
containerTargets:HTMLElement[];
4949
scrollContainerTargets:HTMLElement[];
50+
declare readonly hasMirrorContainerTarget:boolean;
51+
declare readonly mirrorContainerTarget:HTMLElement;
5052

5153
static values = {
5254
handle: { type: Boolean, default: true },
@@ -127,6 +129,7 @@ export default class GenericDragAndDropController extends Controller {
127129
moves: (el, _source, handle, _sibling) => this.canStartDrag(el, handle),
128130
accepts: (el:Element, target:Element, source:Element, sibling:Element) => this.accepts(el, target, source, sibling),
129131
revertOnSpill: true, // enable reverting of elements if they are dropped outside of a valid target
132+
mirrorContainer: this.resolveMirrorContainer(),
130133
},
131134
)
132135
.on('drag', (el, source) => {
@@ -252,6 +255,10 @@ export default class GenericDragAndDropController extends Controller {
252255
return container;
253256
}
254257

258+
private resolveMirrorContainer():Element {
259+
return this.hasMirrorContainerTarget ? this.mirrorContainerTarget : document.body;
260+
}
261+
255262
// Returns the data-draggable-id of the element preceding el in its container,
256263
// or null if el is the first item (signals "move to top").
257264
private resolveTargetPrevious(el:Element):string|null {

modules/backlogs/app/components/backlogs/inbox_component.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ def middle_count
9393

9494
def drop_target_config
9595
{
96-
generic_drag_and_drop_target: "container",
96+
generic_drag_and_drop_target: "container mirrorContainer",
9797
target_container_accessor: ":scope > ul",
9898
target_id: "inbox",
9999
target_allowed_drag_type: "story"

modules/backlogs/spec/requests/backlogs/backlog_spec.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,14 @@
106106
expect(response.body).to include('id="sprint_backlogs_container"')
107107
end
108108
end
109+
110+
it "uses the inbox border box as the drag mirror container" do
111+
get "/projects/#{project.identifier}/backlogs/backlog", headers: { "Turbo-Frame" => "backlogs_container" }
112+
113+
expect(response).to have_http_status(:ok)
114+
expect(response.body).to include(%(id="inbox_#{project.id}"))
115+
expect(response.body).to include('data-generic-drag-and-drop-target="container mirrorContainer"')
116+
end
109117
end
110118
end
111119

0 commit comments

Comments
 (0)