It seems there’s a history feature, and I’d like to capture the direction in which I moved the object in the previous step.
For example: I just dragged an object, and now I want to know the direction of the movement—whether it was left, right, up, or down. If the object was moved both left and up at the same time, take the direction with the largest movement value.
Thank you.
Correct answer Eugene Tyson
The forum structure is far too messy I’m just gonna tag on here @rob day @dublove
Rob, I figured an event listener was the only way, but struggled to think of a way without two scripts, but thanks for your code, I hadn’t touched the script yet as I was still trying to figure out what DubLove is trying to do. But alas, we have landed on something that I think works.
@m1b I tried the Transform Again already and it’s not reliable enough and couldn’t dig into it for movement only.
I had a far longerpost but lost it with a single click of CANCEL instead of SEND, and of course the forum doesn’t save it, or ask if you cancel you lose your progress… no warning..
app.eventListeners.add("afterSelectionChanged", function () { $.global.ET_DirectionWatchSnapshot = getSelectionSnapshot(); });
app.eventListeners.add("afterSelectionAttributeChanged", function () { var oldSnapshot = $.global.ET_DirectionWatchSnapshot; var newSnapshot = getSelectionSnapshot();
if (!oldSnapshot || !newSnapshot) { $.global.ET_DirectionWatchSnapshot = newSnapshot; return; }
if (oldSnapshot.signature !== newSnapshot.signature) { $.global.ET_DirectionWatchSnapshot = newSnapshot; return; }
var dx = newSnapshot.bounds[1] - oldSnapshot.bounds[1]; var dy = newSnapshot.bounds[0] - oldSnapshot.bounds[0];
function getSelectionSignature(items) { var ids = [];
for (var i = 0; i < items.length; i++) { ids.push(String(items[i].id)); }
ids.sort(); return ids.join(","); }
function getSelectionBounds(items) { var b = items[0].geometricBounds; var top = b[0]; var left = b[1]; var bottom = b[2]; var right = b[3];
for (var i = 1; i < items.length; i++) { b = items[i].geometricBounds; top = Math.min(top, b[0]); left = Math.min(left, b[1]); bottom = Math.max(bottom, b[2]); right = Math.max(right, b[3]); }
Put this into the normal scripts folder - this is the one you run when you want to move items to a certain guide.
#target "InDesign"
(function () { var SCRIPT_NAME = "Align Using Last Move Direction"; var LABEL_PREFIX = "ET_DirectionWatch_";
if (app.documents.length === 0) { alert("Open a document first.", SCRIPT_NAME); return; }
if (app.selection.length === 0) { alert("Select one or more page items first.", SCRIPT_NAME); return; }
var direction = app.extractLabel(LABEL_PREFIX + "direction");
if (!direction) { alert( "No movement direction has been recorded yet.\r\r" + "Restart InDesign so the startup watcher loads, then select and nudge/move the object before running this script.", SCRIPT_NAME ); return; }
app.doScript( function () { var items = app.selection;
for (var i = 0; i < items.length; i++) { alignItemUsingDirection(items[i], direction, SCRIPT_NAME); } }, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, SCRIPT_NAME ); }());
function alignItemUsingDirection(item, direction, scriptName) { if (!item || !item.isValid || !hasBounds(item)) { return; }
var page = item.parentPage;
if (!page || !page.isValid) { alert("One selected item is not on a page.", scriptName); return; }
var guides = getPageAndSpreadGuides(page);
if (guides.length === 0) { alert("No guides found on this page or spread.", scriptName); return; }
var gb = item.geometricBounds; var target = getNearestGuideInDirection(guides, gb, direction);
if (target === null) { alert("No guide found in the recorded direction: " + direction, scriptName); return; }
if (direction === "left" || direction === "right") { item.move(undefined, [target.delta, 0]); } else { item.move(undefined, [0, target.delta]); } }
function getNearestGuideInDirection(guides, bounds, direction) { var edge; var orientation; var best = null;
if (best === null || Math.abs(delta) < Math.abs(best.delta)) { best = { delta: delta }; } }
return best; }
function getPageAndSpreadGuides(page) { var result = []; var i;
for (i = 0; i < page.guides.length; i++) { result.push(page.guides[i]); }
if (page.parent && page.parent.isValid && page.parent.guides) { for (i = 0; i < page.parent.guides.length; i++) { result.push(page.parent.guides[i]); } }
@Eugene Tyson @rob day this looks amazing! Well done!
> @m1b tried the Transform Again already and it’s not reliable enough
I just had a bug where I used an English-specific name for the menuAction instead of a locale-independent ID. Hopefully I’ve fixed that now. But is there some other reason you think it’s isn’t a reliable approach? It’s been rock solid in my testing.
The forum structure is far too messy I’m just gonna tag on here @rob day @dublove
Rob, I figured an event listener was the only way, but struggled to think of a way without two scripts, but thanks for your code, I hadn’t touched the script yet as I was still trying to figure out what DubLove is trying to do. But alas, we have landed on something that I think works.
@m1b I tried the Transform Again already and it’s not reliable enough and couldn’t dig into it for movement only.
I had a far longerpost but lost it with a single click of CANCEL instead of SEND, and of course the forum doesn’t save it, or ask if you cancel you lose your progress… no warning..
app.eventListeners.add("afterSelectionChanged", function () { $.global.ET_DirectionWatchSnapshot = getSelectionSnapshot(); });
app.eventListeners.add("afterSelectionAttributeChanged", function () { var oldSnapshot = $.global.ET_DirectionWatchSnapshot; var newSnapshot = getSelectionSnapshot();
if (!oldSnapshot || !newSnapshot) { $.global.ET_DirectionWatchSnapshot = newSnapshot; return; }
if (oldSnapshot.signature !== newSnapshot.signature) { $.global.ET_DirectionWatchSnapshot = newSnapshot; return; }
var dx = newSnapshot.bounds[1] - oldSnapshot.bounds[1]; var dy = newSnapshot.bounds[0] - oldSnapshot.bounds[0];
function getSelectionSignature(items) { var ids = [];
for (var i = 0; i < items.length; i++) { ids.push(String(items[i].id)); }
ids.sort(); return ids.join(","); }
function getSelectionBounds(items) { var b = items[0].geometricBounds; var top = b[0]; var left = b[1]; var bottom = b[2]; var right = b[3];
for (var i = 1; i < items.length; i++) { b = items[i].geometricBounds; top = Math.min(top, b[0]); left = Math.min(left, b[1]); bottom = Math.max(bottom, b[2]); right = Math.max(right, b[3]); }
Put this into the normal scripts folder - this is the one you run when you want to move items to a certain guide.
#target "InDesign"
(function () { var SCRIPT_NAME = "Align Using Last Move Direction"; var LABEL_PREFIX = "ET_DirectionWatch_";
if (app.documents.length === 0) { alert("Open a document first.", SCRIPT_NAME); return; }
if (app.selection.length === 0) { alert("Select one or more page items first.", SCRIPT_NAME); return; }
var direction = app.extractLabel(LABEL_PREFIX + "direction");
if (!direction) { alert( "No movement direction has been recorded yet.\r\r" + "Restart InDesign so the startup watcher loads, then select and nudge/move the object before running this script.", SCRIPT_NAME ); return; }
app.doScript( function () { var items = app.selection;
for (var i = 0; i < items.length; i++) { alignItemUsingDirection(items[i], direction, SCRIPT_NAME); } }, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, SCRIPT_NAME ); }());
function alignItemUsingDirection(item, direction, scriptName) { if (!item || !item.isValid || !hasBounds(item)) { return; }
var page = item.parentPage;
if (!page || !page.isValid) { alert("One selected item is not on a page.", scriptName); return; }
var guides = getPageAndSpreadGuides(page);
if (guides.length === 0) { alert("No guides found on this page or spread.", scriptName); return; }
var gb = item.geometricBounds; var target = getNearestGuideInDirection(guides, gb, direction);
if (target === null) { alert("No guide found in the recorded direction: " + direction, scriptName); return; }
if (direction === "left" || direction === "right") { item.move(undefined, [target.delta, 0]); } else { item.move(undefined, [0, target.delta]); } }
function getNearestGuideInDirection(guides, bounds, direction) { var edge; var orientation; var best = null;
if (best === null || Math.abs(delta) < Math.abs(best.delta)) { best = { delta: delta }; } }
return best; }
function getPageAndSpreadGuides(page) { var result = []; var i;
for (i = 0; i < page.guides.length; i++) { result.push(page.guides[i]); }
if (page.parent && page.parent.isValid && page.parent.guides) { for (i = 0; i < page.parent.guides.length; i++) { result.push(page.parent.guides[i]); } }
But if there is no reference line above the object when moving upwards, an error will be reported. This is not looking for the nearest, but for the reference line above.
Listen. jsx only determines which side of the object aligns to the guid line It should still be the original, aligned to the nearest guid line
For example, if I move an object upwards, it means I want to align the "top of the object" to the nearest reference line.
Also, the reference line on the homepage has not been recognized yet.
You’re right. I realised too after posting, but felt it was best for you to see if it works for your situation - and catch anything else etc.
I changed it so a move means “align the edge to the nearest horizontal guide,” so it shouldn’t matter if it’s above/below or left/right. I also added applied parent/master page guides.
Let us know what else you find or need.
#target "InDesign"
(function () { var SCRIPT_NAME = "Align Using Last Move Direction"; var LABEL_PREFIX = "ET_DirectionWatch_";
if (app.documents.length === 0) { alert("Open a document first.", SCRIPT_NAME); return; }
if (app.selection.length === 0) { alert("Select one or more page items first.", SCRIPT_NAME); return; }
var direction = app.extractLabel(LABEL_PREFIX + "direction");
if (!direction) { alert( "No movement direction has been recorded yet.\r\r" + "Restart InDesign so the startup watcher loads, then select and nudge/move the object before running this script.", SCRIPT_NAME ); return; }
app.doScript( function () { var items = app.selection;
for (var i = 0; i < items.length; i++) { alignItemUsingDirection(items[i], direction, SCRIPT_NAME); } }, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, SCRIPT_NAME ); }());
function alignItemUsingDirection(item, direction, scriptName) { if (!item || !item.isValid || !hasBounds(item)) { return; }
var page = item.parentPage;
if (!page || !page.isValid) { alert("One selected item is not on a page.", scriptName); return; }
var guides = getGuidesForPage(page);
if (guides.length === 0) { alert("No guides found on this page, spread, or applied parent page.", scriptName); return; }
var gb = item.geometricBounds; var target = getNearestGuideForDirection(guides, gb, direction);
if (target === null) { alert("No matching guide found for the recorded direction: " + direction, scriptName); return; }
if (direction === "left" || direction === "right") { item.move(undefined, [target.delta, 0]); } else { item.move(undefined, [0, target.delta]); } }
function getNearestGuideForDirection(guides, bounds, direction) { var edge; var orientation; var best = null;
for (var i = 0; i < guides.length; i++) { if (!guides[i].isValid || guides[i].orientation !== orientation) { continue; }
var delta = guides[i].location - edge;
if (best === null || Math.abs(delta) < Math.abs(best.delta)) { best = { delta: delta }; } }
return best; }
function getGuidesForPage(page) { var result = []; var i;
for (i = 0; i < page.guides.length; i++) { result.push(page.guides[i]); }
if (page.parent && page.parent.isValid && page.parent.guides) { for (i = 0; i < page.parent.guides.length; i++) { result.push(page.parent.guides[i]); } }
if (page.appliedMaster && page.appliedMaster.isValid && page.appliedMaster.guides) { for (i = 0; i < page.appliedMaster.guides.length; i++) { result.push(page.appliedMaster.guides[i]); } }
Hi @dublove, Rob has already given you a fantastic approach, which I think is better than mine, but for the sake of showing different ideas, here is my attempt (I hadn't seen Rob’s). My approach is simple: I use an invocation of the “Transform Again” menu.
To test it, just select some items and arrow nudge them in the direction you want, then run script.
/** * @file Move To Margin.js * * Demonstration showing my technique for * getting the direction of the last translation. * * The script will move selected objects in the * direction of the last translation until they * hit the nearest margin. * * The idea is to first use a "nudge" arrow key * before running the script. * * @author m1b * @version 2026-05-17 * @discussion https://community.adobe.com/questions-671/is-it-possible-to-capture-the-previous-mouse-action-the-direction-in-which-the-object-was-moved-1561822?tid=1561822 */ function main() {
if ( 0 === app.documents.length || 0 === app.activeDocument.selection.length ) return alert('Please select one or more page items and try again.');
var doc = app.activeDocument; var items = doc.selection; var vector = getVectorOfLastTranslation(doc);
if (!vector) return;
for (var i = items.length - 1; i >= 0; i--) translateToMargin(items[i], vector);
}; app.doScript(main, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, 'Align To Margin');
/** * Translates `item` so that it touched the nearest * page margin in the direction `vector`. * @author m1b * @version 2026-05-17 * @param {PageItem} item - an Indesign page item. * @param {Array<Number>} vector - the direction of translation [vx, vy]. */ function translateToMargin(item, vector) {
var page = item.parentPage;
if (!page) // not on a page return;
var pb = page.bounds; var mp = page.marginPreferences;
// compute distance to bring leading edge flush with the nearest margin var t = distanceToBoxEdge(item.geometricBounds, marginBox, vector);
if (null === t) // already past the margin in this direction return;
// move it item.move(undefined, [vector[0] * t, vector[1] * t]);
};
/** * Returns a vector showing the direction of the last translation, if any. * @author m1b * @version 2026-05-17 * @param {Document} doc - an Indesign document. * @returns {Array<Number>?} - [vx, vy] */ function getVectorOfLastTranslation(doc) {
var selection = doc.selection;
// use Transform Again as a trick to get the translation // note: use itemByID for locale-independence (id 118805 = "Transform Again") var action = app.menuActions.itemByID(118805);
if (!action.isValid) // failed to get menuActions return alert('Sorry, I failed to find the MenuAction "TransformAgain".');
if (!action.enabled) // no previous transform return;
var temp = app.activeDocument.rectangles.add();
// detect direction of last translation var b = temp.geometricBounds; var p0 = [b[1], b[0]]; // [x, y]
// perform the last transform doc.selection = [temp]; action.invoke();
// read the new position b = temp.geometricBounds;
var p1 = [b[1], b[0]]; var vx = p1[0] - p0[0]; var vy = p1[1] - p0[1];
if (0 === vx && 0 === vy) // no movement return;
return [vx, vy];
};
/** * Returns number `t` such that translating the item by t * a vector * brings its leading edge flush with the closest edge of `marginBox`. * @author claude code * @version 2026-05-17 * @param {Array} bounds - geometricBounds [top, left, bottom, right]. * @param {Object} marginBox - { top, left, bottom, right }. * @param {Array<Number>} vector - the direction vector [vx, vy]. * @returns {Number|null} - the scalar t, or null if no valid margin is found. */ function distanceToBoxEdge(bounds, marginBox, vector) {
var candidates = []; var vx = vector[0]; var vy = vector[1];
if (vx > 0) candidates.push((marginBox.right - bounds[3]) / vx); else if (vx < 0) candidates.push((marginBox.left - bounds[1]) / vx);
if (vy > 0) candidates.push((marginBox.bottom - bounds[2]) / vy); else if (vy < 0) candidates.push((marginBox.top - bounds[0]) / vy);
var minT = null;
for (var i = 0; i < candidates.length; i++) {
if ( candidates[i] >= 0 && (minT === null || candidates[i] < minT) ) minT = candidates[i];
}
return minT;
};
Edit 2026-05-17: fixed bug where menuAction used English-specific name. Now uses ID.
@dublove sorry, my mistake! I used an English-specific name for the menu action. Can you try it again? I know you have @Eugene Tyson’s marvellous contribution, but just for my learning I would like to know if that fixes the issue on your Chinese Indesign.
The application can listen for events. There is AFTER_SELECTION_ATTRIBUTE_CHANGED and AFTER_SELECTION_CHANGED. Not sure what you are trying to do, but an AFTER_SELECTION_CHANGED should let you get the before and after positions
As mentioned, aligning to guide lines may require four scripts (left, right, top, and bottom).
This results in too many scripts and makes the process too complicated, so I want to use mouse gestures to combine these four scripts into one.
It compares the last two historical operations to determine in which direction I moved the object.
For example, if I move an object upward in the previous step and then run the script, and if the script can detect that the previous action was moving the object upward(For example, nesSb[0] > oldSb[0]), it should assume that I intend to align the top edge of the object to the nearest guide line.
I don’t see a way , not from the History, the scripting DOM I can’t find any expose to mouse actions. You would need to know the position before moving, then compare it with the new position. Then comopare with geometric.bounds. If horizontal is larger then left/right, if vertical then up/down.
You’d have to somehow store the values before moving, possible maybe to write to the script label before moving, but you’d have to run a script before moving it so the label populates.
I’m not really sure of what you want to do here. Or why?
Typically if I need to move something the exact same distance I just use the Object>Transform Again options
Hard to know where to go with this.
There are apps that can write the mouse events and track mouse movement.
I just don’t even know where to start.
Maybe tell us a bit more of what you’re trying to do?
I want to combine the four scripts that align object to guide lines into a single script. Before running the script, I move the object slightly upward with the mouse.
Next, I run the script, hoping it will read the “previous action” to automatically determine that I want to align it upward.
So InDesign scripting can’t read the previous mouse move or the last drag direction from History.
Just an idea.
Instead of detecting previous manual movement, the combined script might be able to determine the intended guide which would need to compare to the selected object position and nearby guides.
Idea:
Get geometricBounds Get page guides
Measure distance - object edge/centre to nearby x y guides
Pick nearest
Closest guide is above (left/right/below) align up (left/right/down)
This way you can nudge objects near a guide, then with a single script it should align to the closest guide automatically.
Put it in this kinda order might work:
// codeIdea
// Compare selected object's bounds to all guides
// Find nearest guide
// If nearest guide is horizontal
// align top/bottom/center Y to that guide
// If nearest guide is vertical
// align left/right/center X to that guide
Tracking the previous movement, I can’t find an access point for that in scripting, so this is the next best thing and might even be slightly more reliable.
As previous script here
Change the dialog with four buttons: Up, Down, Left, Right.
But if you want the script to feel automatic, “align to nearest guide” is likely the better route.