Skip to main content
dublove
Legend
May 16, 2026
Answered

Is it possible to capture the previous mouse action (the direction in which the object was moved)?

  • May 16, 2026
  • 5 replies
  • 174 views

Hi everyone~

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.. 

    Anyway I digress

    -----------------------------------------------------

    I think we all agree an event listener is needed

    So I made a startup script

    Make a Startup Scripts folder outside your Scripts Panel folder - (it’s under the highlighted one)

    Inside the Startup Scripts folder put this - then RESTART INDESIGN

    #target "InDesign"
    #targetengine "session"

    (function () {
    var SCRIPT_NAME = "Direction Watch Startup";
    var LABEL_PREFIX = "ET_DirectionWatch_";
    var MIN_MOVE = 0.001;

    if ($.global.ET_DirectionWatchInstalled) {
    return;
    }

    $.global.ET_DirectionWatchInstalled = true;
    $.global.ET_DirectionWatchSnapshot = getSelectionSnapshot();

    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];

    if (Math.abs(dx) < MIN_MOVE && Math.abs(dy) < MIN_MOVE) {
    $.global.ET_DirectionWatchSnapshot = newSnapshot;
    return;
    }

    var direction;

    if (Math.abs(dx) >= Math.abs(dy)) {
    direction = dx < 0 ? "left" : "right";
    }
    else {
    direction = dy < 0 ? "up" : "down";
    }

    app.insertLabel(LABEL_PREFIX + "direction", direction);
    app.insertLabel(LABEL_PREFIX + "dx", String(dx));
    app.insertLabel(LABEL_PREFIX + "dy", String(dy));
    app.insertLabel(LABEL_PREFIX + "time", String(new Date().getTime()));

    $.global.ET_DirectionWatchSnapshot = newSnapshot;
    });

    function getSelectionSnapshot() {
    if (!canReadSelection()) {
    return null;
    }

    var items = [];
    var selection;

    try {
    selection = app.selection;
    }
    catch (_) {
    return null;
    }

    for (var i = 0; i < selection.length; i++) {
    if (selection[i] && selection[i].isValid && hasBounds(selection[i])) {
    items.push(selection[i]);
    }
    }

    if (items.length === 0) {
    return null;
    }

    return {
    signature: getSelectionSignature(items),
    bounds: getSelectionBounds(items)
    };
    }

    function canReadSelection() {
    try {
    return app.documents.length > 0 && app.layoutWindows.length > 0;
    }
    catch (_) {
    return false;
    }
    }

    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]);
    }

    return [top, left, bottom, right];
    }

    function hasBounds(item) {
    try {
    var gb = item.geometricBounds;
    return gb && gb.length === 4;
    }
    catch (_) {
    return false;
    }
    }
    }());

    ====================================================================

    NOW THE SCRIPT TO LiSTEN TO MOVEMENT IS RUNNING WHEN INDESIGN STARTS

    ====================================================================

    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 (direction === "left") {
    edge = bounds[1];
    orientation = HorizontalOrVertical.VERTICAL;
    }
    else if (direction === "right") {
    edge = bounds[3];
    orientation = HorizontalOrVertical.VERTICAL;
    }
    else if (direction === "up") {
    edge = bounds[0];
    orientation = HorizontalOrVertical.HORIZONTAL;
    }
    else if (direction === "down") {
    edge = bounds[2];
    orientation = HorizontalOrVertical.HORIZONTAL;
    }
    else {
    return 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 (direction === "left" && delta > 0) {
    continue;
    }

    if (direction === "right" && delta < 0) {
    continue;
    }

    if (direction === "up" && delta > 0) {
    continue;
    }

    if (direction === "down" && delta < 0) {
    continue;
    }

    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]);
    }
    }

    return result;
    }

    function hasBounds(item) {
    try {
    var gb = item.geometricBounds;
    return gb && gb.length === 4;
    }
    catch (_) {
    return false;
    }
    }

    When you nudge/move an item it’s recorded silently - then run the script and it moves all

    So you just have to nudge or move left/right/up/down and it moves in that direction then run the script - I’m finding the fastest 

     

    5 replies

    m1b
    Community Expert
    Community Expert
    May 17, 2026

    @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. 

    dublove
    dubloveAuthor
    Legend
    May 17, 2026

    I'm confused.
    I forgot to mention that you shouldn't use the first line, or it will open a new instance of InDesign.
    //#target “InDesign”

    Community Expert
    May 17, 2026

    You can just take it out or null it with // if you don’t like it.

    I test it in 2026 - if another application is open it might try that. 

    But if you don’t want it just don’t use it.

    Eugene TysonCommunity ExpertCorrect answer
    Community Expert
    May 17, 2026

    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.. 

    Anyway I digress

    -----------------------------------------------------

    I think we all agree an event listener is needed

    So I made a startup script

    Make a Startup Scripts folder outside your Scripts Panel folder - (it’s under the highlighted one)

    Inside the Startup Scripts folder put this - then RESTART INDESIGN

    #target "InDesign"
    #targetengine "session"

    (function () {
    var SCRIPT_NAME = "Direction Watch Startup";
    var LABEL_PREFIX = "ET_DirectionWatch_";
    var MIN_MOVE = 0.001;

    if ($.global.ET_DirectionWatchInstalled) {
    return;
    }

    $.global.ET_DirectionWatchInstalled = true;
    $.global.ET_DirectionWatchSnapshot = getSelectionSnapshot();

    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];

    if (Math.abs(dx) < MIN_MOVE && Math.abs(dy) < MIN_MOVE) {
    $.global.ET_DirectionWatchSnapshot = newSnapshot;
    return;
    }

    var direction;

    if (Math.abs(dx) >= Math.abs(dy)) {
    direction = dx < 0 ? "left" : "right";
    }
    else {
    direction = dy < 0 ? "up" : "down";
    }

    app.insertLabel(LABEL_PREFIX + "direction", direction);
    app.insertLabel(LABEL_PREFIX + "dx", String(dx));
    app.insertLabel(LABEL_PREFIX + "dy", String(dy));
    app.insertLabel(LABEL_PREFIX + "time", String(new Date().getTime()));

    $.global.ET_DirectionWatchSnapshot = newSnapshot;
    });

    function getSelectionSnapshot() {
    if (!canReadSelection()) {
    return null;
    }

    var items = [];
    var selection;

    try {
    selection = app.selection;
    }
    catch (_) {
    return null;
    }

    for (var i = 0; i < selection.length; i++) {
    if (selection[i] && selection[i].isValid && hasBounds(selection[i])) {
    items.push(selection[i]);
    }
    }

    if (items.length === 0) {
    return null;
    }

    return {
    signature: getSelectionSignature(items),
    bounds: getSelectionBounds(items)
    };
    }

    function canReadSelection() {
    try {
    return app.documents.length > 0 && app.layoutWindows.length > 0;
    }
    catch (_) {
    return false;
    }
    }

    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]);
    }

    return [top, left, bottom, right];
    }

    function hasBounds(item) {
    try {
    var gb = item.geometricBounds;
    return gb && gb.length === 4;
    }
    catch (_) {
    return false;
    }
    }
    }());

    ====================================================================

    NOW THE SCRIPT TO LiSTEN TO MOVEMENT IS RUNNING WHEN INDESIGN STARTS

    ====================================================================

    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 (direction === "left") {
    edge = bounds[1];
    orientation = HorizontalOrVertical.VERTICAL;
    }
    else if (direction === "right") {
    edge = bounds[3];
    orientation = HorizontalOrVertical.VERTICAL;
    }
    else if (direction === "up") {
    edge = bounds[0];
    orientation = HorizontalOrVertical.HORIZONTAL;
    }
    else if (direction === "down") {
    edge = bounds[2];
    orientation = HorizontalOrVertical.HORIZONTAL;
    }
    else {
    return 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 (direction === "left" && delta > 0) {
    continue;
    }

    if (direction === "right" && delta < 0) {
    continue;
    }

    if (direction === "up" && delta > 0) {
    continue;
    }

    if (direction === "down" && delta < 0) {
    continue;
    }

    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]);
    }
    }

    return result;
    }

    function hasBounds(item) {
    try {
    var gb = item.geometricBounds;
    return gb && gb.length === 4;
    }
    catch (_) {
    return false;
    }
    }

    When you nudge/move an item it’s recorded silently - then run the script and it moves all

    So you just have to nudge or move left/right/up/down and it moves in that direction then run the script - I’m finding the fastest 

     

    dublove
    dubloveAuthor
    Legend
    May 17, 2026

    Hi ​@Eugene Tyson 

    It's so exciting.

    I tried it and it worked.


    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.

     

    Community Expert
    May 17, 2026

    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;

    if (direction === "left") {
    edge = bounds[1];
    orientation = HorizontalOrVertical.VERTICAL;
    }
    else if (direction === "right") {
    edge = bounds[3];
    orientation = HorizontalOrVertical.VERTICAL;
    }
    else if (direction === "up") {
    edge = bounds[0];
    orientation = HorizontalOrVertical.HORIZONTAL;
    }
    else if (direction === "down") {
    edge = bounds[2];
    orientation = HorizontalOrVertical.HORIZONTAL;
    }
    else {
    return 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]);
    }
    }

    return result;
    }

    function hasBounds(item) {
    try {
    var gb = item.geometricBounds;
    return gb && gb.length === 4;
    }
    catch (_) {
    return false;
    }
    }

     

    m1b
    Community Expert
    Community Expert
    May 17, 2026

    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.

    This script may also help you with your other question about aligning objects to guides, but the main point was getting the direction of the last move.

    — Mark

    /**
    * @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;

    var marginBox = {
    top: pb[0] + mp.top,
    left: pb[1] + mp.left,
    bottom: pb[2] - mp.bottom,
    right: pb[3] - mp.right,
    };

    // 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;

    // cleanup
    temp.remove();
    doc.selection = selection;

    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
    dubloveAuthor
    Legend
    May 17, 2026

    Hi ​@m1b 

    It seems like there's something wrong.

     

    m1b
    Community Expert
    Community Expert
    May 17, 2026

    @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.

    — Mark

    rob day
    Community Expert
    Community Expert
    May 16, 2026

    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

     

    https://www.indesignjs.de/extendscriptAPI/indesign-latest/#Application.html

    dublove
    dubloveAuthor
    Legend
    May 16, 2026

    @rob day 

    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.

     

     

    rob day
    Community Expert
    Community Expert
    May 16, 2026

    Here’s an afterSelectionChange event example:

     

     

    Community Expert
    May 16, 2026

    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?

    dublove
    dubloveAuthor
    Legend
    May 16, 2026

    Hi ​@Eugene Tyson 

    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.
     

    Community Expert
    May 16, 2026

    Ok - absolutely no way would have guessed that.

    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.