tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

DragAndDrop.js (27242B)


      1 /***
      2 MochiKit.DragAndDrop 1.4
      3 
      4 See <http://mochikit.com/> for documentation, downloads, license, etc.
      5 
      6 Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
      7    Mochi-ized By Thomas Herve (_firstname_@nimail.org)
      8 
      9 ***/
     10 
     11 if (typeof(dojo) != 'undefined') {
     12    dojo.provide('MochiKit.DragAndDrop');
     13    dojo.require('MochiKit.Base');
     14    dojo.require('MochiKit.DOM');
     15    dojo.require('MochiKit.Iter');
     16    dojo.require('MochiKit.Visual');
     17    dojo.require('MochiKit.Signal');
     18 }
     19 
     20 if (typeof(JSAN) != 'undefined') {
     21    JSAN.use("MochiKit.Base", []);
     22    JSAN.use("MochiKit.DOM", []);
     23    JSAN.use("MochiKit.Visual", []);
     24    JSAN.use("MochiKit.Iter", []);
     25    JSAN.use("MochiKit.Signal", []);
     26 }
     27 
     28 try {
     29    if (typeof(MochiKit.Base) == 'undefined' ||
     30        typeof(MochiKit.DOM) == 'undefined' ||
     31        typeof(MochiKit.Visual) == 'undefined' ||
     32        typeof(MochiKit.Signal) == 'undefined' ||
     33        typeof(MochiKit.Iter) == 'undefined') {
     34        throw "";
     35    }
     36 } catch (e) {
     37    throw "MochiKit.DragAndDrop depends on MochiKit.Base, MochiKit.DOM, MochiKit.Visual, MochiKit.Signal and MochiKit.Iter!";
     38 }
     39 
     40 if (typeof(MochiKit.DragAndDrop) == 'undefined') {
     41    MochiKit.DragAndDrop = {};
     42 }
     43 
     44 MochiKit.DragAndDrop.NAME = 'MochiKit.DragAndDrop';
     45 MochiKit.DragAndDrop.VERSION = '1.4';
     46 
     47 MochiKit.DragAndDrop.__repr__ = function () {
     48    return '[' + this.NAME + ' ' + this.VERSION + ']';
     49 };
     50 
     51 MochiKit.DragAndDrop.toString = function () {
     52    return this.__repr__();
     53 };
     54 
     55 MochiKit.DragAndDrop.EXPORT = [
     56    "Droppable",
     57    "Draggable"
     58 ];
     59 
     60 MochiKit.DragAndDrop.EXPORT_OK = [
     61    "Droppables",
     62    "Draggables"
     63 ];
     64 
     65 MochiKit.DragAndDrop.Droppables = {
     66    /***
     67 
     68    Manage all droppables. Shouldn't be used, use the Droppable object instead.
     69 
     70    ***/
     71    drops: [],
     72 
     73    remove: function (element) {
     74        this.drops = MochiKit.Base.filter(function (d) {
     75            return d.element != MochiKit.DOM.getElement(element);
     76        }, this.drops);
     77    },
     78 
     79    register: function (drop) {
     80        this.drops.push(drop);
     81    },
     82 
     83    unregister: function (drop) {
     84        this.drops = MochiKit.Base.filter(function (d) {
     85            return d != drop;
     86        }, this.drops);
     87    },
     88 
     89    prepare: function (element) {
     90        MochiKit.Base.map(function (drop) {
     91            if (drop.isAccepted(element)) {
     92                if (drop.options.activeclass) {
     93                    MochiKit.DOM.addElementClass(drop.element,
     94                                                 drop.options.activeclass);
     95                }
     96                drop.options.onactive(drop.element, element);
     97            }
     98        }, this.drops);
     99    },
    100 
    101    findDeepestChild: function (drops) {
    102        deepest = drops[0];
    103 
    104        for (i = 1; i < drops.length; ++i) {
    105            if (MochiKit.DOM.isParent(drops[i].element, deepest.element)) {
    106                deepest = drops[i];
    107            }
    108        }
    109        return deepest;
    110    },
    111 
    112    show: function (point, element) {
    113        if (!this.drops.length) {
    114            return;
    115        }
    116        var affected = [];
    117 
    118        if (this.last_active) {
    119            this.last_active.deactivate();
    120        }
    121        MochiKit.Iter.forEach(this.drops, function (drop) {
    122            if (drop.isAffected(point, element)) {
    123                affected.push(drop);
    124            }
    125        });
    126        if (affected.length > 0) {
    127            drop = this.findDeepestChild(affected);
    128            MochiKit.Position.within(drop.element, point.page.x, point.page.y);
    129            drop.options.onhover(element, drop.element,
    130                MochiKit.Position.overlap(drop.options.overlap, drop.element));
    131            drop.activate();
    132        }
    133    },
    134 
    135    fire: function (event, element) {
    136        if (!this.last_active) {
    137            return;
    138        }
    139        MochiKit.Position.prepare();
    140 
    141        if (this.last_active.isAffected(event.mouse(), element)) {
    142            this.last_active.options.ondrop(element,
    143               this.last_active.element, event);
    144        }
    145    },
    146 
    147    reset: function (element) {
    148        MochiKit.Base.map(function (drop) {
    149            if (drop.options.activeclass) {
    150                MochiKit.DOM.removeElementClass(drop.element,
    151                                                drop.options.activeclass);
    152            }
    153            drop.options.ondesactive(drop.element, element);
    154        }, this.drops);
    155        if (this.last_active) {
    156            this.last_active.deactivate();
    157        }
    158    }
    159 };
    160 
    161 /** @id MochiKit.DragAndDrop.Droppable */
    162 MochiKit.DragAndDrop.Droppable = function (element, options) {
    163    var cls = arguments.callee;
    164    if (!(this instanceof cls)) {
    165        return new cls(element, options);
    166    }
    167    this.__init__(element, options);
    168 };
    169 
    170 MochiKit.DragAndDrop.Droppable.prototype = {
    171    /***
    172 
    173    A droppable object. Simple use is to create giving an element:
    174 
    175        new MochiKit.DragAndDrop.Droppable('myelement');
    176 
    177    Generally you'll want to define the 'ondrop' function and maybe the
    178    'accept' option to filter draggables.
    179 
    180    ***/
    181    __class__: MochiKit.DragAndDrop.Droppable,
    182 
    183    __init__: function (element, /* optional */options) {
    184        var d = MochiKit.DOM;
    185        var b = MochiKit.Base;
    186        this.element = d.getElement(element);
    187        this.options = b.update({
    188 
    189            /** @id MochiKit.DragAndDrop.greedy */
    190            greedy: true,
    191 
    192            /** @id MochiKit.DragAndDrop.hoverclass */
    193            hoverclass: null,
    194 
    195            /** @id MochiKit.DragAndDrop.activeclass */
    196            activeclass: null,
    197 
    198            /** @id MochiKit.DragAndDrop.hoverfunc */
    199            hoverfunc: b.noop,
    200 
    201            /** @id MochiKit.DragAndDrop.accept */
    202            accept: null,
    203 
    204            /** @id MochiKit.DragAndDrop.onactive */
    205            onactive: b.noop,
    206 
    207            /** @id MochiKit.DragAndDrop.ondesactive */
    208            ondesactive: b.noop,
    209 
    210            /** @id MochiKit.DragAndDrop.onhover */
    211            onhover: b.noop,
    212 
    213            /** @id MochiKit.DragAndDrop.ondrop */
    214            ondrop: b.noop,
    215 
    216            /** @id MochiKit.DragAndDrop.containment */
    217            containment: [],
    218            tree: false
    219        }, options || {});
    220 
    221        // cache containers
    222        this.options._containers = [];
    223        b.map(MochiKit.Base.bind(function (c) {
    224            this.options._containers.push(d.getElement(c));
    225        }, this), this.options.containment);
    226 
    227        d.makePositioned(this.element); // fix IE
    228 
    229        MochiKit.DragAndDrop.Droppables.register(this);
    230    },
    231 
    232    /** @id MochiKit.DragAndDrop.isContained */
    233    isContained: function (element) {
    234        if (this.options._containers.length) {
    235            var containmentNode;
    236            if (this.options.tree) {
    237                containmentNode = element.treeNode;
    238            } else {
    239                containmentNode = element.parentNode;
    240            }
    241            return MochiKit.Iter.some(this.options._containers, function (c) {
    242                return containmentNode == c;
    243            });
    244        } else {
    245            return true;
    246        }
    247    },
    248 
    249    /** @id MochiKit.DragAndDrop.isAccepted */
    250    isAccepted: function (element) {
    251        return ((!this.options.accept) || MochiKit.Iter.some(
    252          this.options.accept, function (c) {
    253            return MochiKit.DOM.hasElementClass(element, c);
    254        }));
    255    },
    256 
    257    /** @id MochiKit.DragAndDrop.isAffected */
    258    isAffected: function (point, element) {
    259        return ((this.element != element) &&
    260                this.isContained(element) &&
    261                this.isAccepted(element) &&
    262                MochiKit.Position.within(this.element, point.page.x,
    263                                                       point.page.y));
    264    },
    265 
    266    /** @id MochiKit.DragAndDrop.deactivate */
    267    deactivate: function () {
    268        /***
    269 
    270        A droppable is deactivate when a draggable has been over it and left.
    271 
    272        ***/
    273        if (this.options.hoverclass) {
    274            MochiKit.DOM.removeElementClass(this.element,
    275                                            this.options.hoverclass);
    276        }
    277        this.options.hoverfunc(this.element, false);
    278        MochiKit.DragAndDrop.Droppables.last_active = null;
    279    },
    280 
    281    /** @id MochiKit.DragAndDrop.activate */
    282    activate: function () {
    283        /***
    284 
    285        A droppable is active when a draggable is over it.
    286 
    287        ***/
    288        if (this.options.hoverclass) {
    289            MochiKit.DOM.addElementClass(this.element, this.options.hoverclass);
    290        }
    291        this.options.hoverfunc(this.element, true);
    292        MochiKit.DragAndDrop.Droppables.last_active = this;
    293    },
    294 
    295    /** @id MochiKit.DragAndDrop.destroy */
    296    destroy: function () {
    297        /***
    298 
    299        Delete this droppable.
    300 
    301        ***/
    302        MochiKit.DragAndDrop.Droppables.unregister(this);
    303    },
    304 
    305    /** @id MochiKit.DragAndDrop.repr */
    306    repr: function () {
    307        return '[' + this.__class__.NAME + ", options:" + MochiKit.Base.repr(this.options) + "]";
    308    }
    309 };
    310 
    311 MochiKit.DragAndDrop.Draggables = {
    312    /***
    313 
    314    Manage draggables elements. Not intended to direct use.
    315 
    316    ***/
    317    drags: [],
    318 
    319    register: function (draggable) {
    320        if (this.drags.length === 0) {
    321            var conn = MochiKit.Signal.connect;
    322            this.eventMouseUp = conn(document, 'onmouseup', this, this.endDrag);
    323            this.eventMouseMove = conn(document, 'onmousemove', this,
    324                                       this.updateDrag);
    325            this.eventKeypress = conn(document, 'onkeypress', this,
    326                                      this.keyPress);
    327        }
    328        this.drags.push(draggable);
    329    },
    330 
    331    unregister: function (draggable) {
    332        this.drags = MochiKit.Base.filter(function (d) {
    333            return d != draggable;
    334        }, this.drags);
    335        if (this.drags.length === 0) {
    336            var disc = MochiKit.Signal.disconnect;
    337            disc(this.eventMouseUp);
    338            disc(this.eventMouseMove);
    339            disc(this.eventKeypress);
    340        }
    341    },
    342 
    343    activate: function (draggable) {
    344        // allows keypress events if window is not currently focused
    345        // fails for Safari
    346        window.focus();
    347        this.activeDraggable = draggable;
    348    },
    349 
    350    deactivate: function () {
    351        this.activeDraggable = null;
    352    },
    353 
    354    updateDrag: function (event) {
    355        if (!this.activeDraggable) {
    356            return;
    357        }
    358        var pointer = event.mouse();
    359        // Mozilla-based browsers fire successive mousemove events with
    360        // the same coordinates, prevent needless redrawing (moz bug?)
    361        if (this._lastPointer && (MochiKit.Base.repr(this._lastPointer.page) ==
    362                                  MochiKit.Base.repr(pointer.page))) {
    363            return;
    364        }
    365        this._lastPointer = pointer;
    366        this.activeDraggable.updateDrag(event, pointer);
    367    },
    368 
    369    endDrag: function (event) {
    370        if (!this.activeDraggable) {
    371            return;
    372        }
    373        this._lastPointer = null;
    374        this.activeDraggable.endDrag(event);
    375        this.activeDraggable = null;
    376    },
    377 
    378    keyPress: function (event) {
    379        if (this.activeDraggable) {
    380            this.activeDraggable.keyPress(event);
    381        }
    382    },
    383 
    384    notify: function (eventName, draggable, event) {
    385        MochiKit.Signal.signal(this, eventName, draggable, event);
    386    }
    387 };
    388 
    389 /** @id MochiKit.DragAndDrop.Draggable */
    390 MochiKit.DragAndDrop.Draggable = function (element, options) {
    391    var cls = arguments.callee;
    392    if (!(this instanceof cls)) {
    393        return new cls(element, options);
    394    }
    395    this.__init__(element, options);
    396 };
    397 
    398 MochiKit.DragAndDrop.Draggable.prototype = {
    399    /***
    400 
    401    A draggable object. Simple instantiate :
    402 
    403        new MochiKit.DragAndDrop.Draggable('myelement');
    404 
    405    ***/
    406    __class__ : MochiKit.DragAndDrop.Draggable,
    407 
    408    __init__: function (element, /* optional */options) {
    409        var v = MochiKit.Visual;
    410        var b = MochiKit.Base;
    411        options = b.update({
    412 
    413            /** @id MochiKit.DragAndDrop.handle */
    414            handle: false,
    415 
    416            /** @id MochiKit.DragAndDrop.starteffect */
    417            starteffect: function (innerelement) {
    418                this._savedOpacity = MochiKit.Style.getStyle(innerelement, 'opacity') || 1.0;
    419                new v.Opacity(innerelement, {duration:0.2, from:this._savedOpacity, to:0.7});
    420            },
    421            /** @id MochiKit.DragAndDrop.reverteffect */
    422            reverteffect: function (innerelement, top_offset, left_offset) {
    423                var dur = Math.sqrt(Math.abs(top_offset^2) +
    424                          Math.abs(left_offset^2))*0.02;
    425                return new v.Move(innerelement,
    426                            {x: -left_offset, y: -top_offset, duration: dur});
    427            },
    428 
    429            /** @id MochiKit.DragAndDrop.endeffect */
    430            endeffect: function (innerelement) {
    431                new v.Opacity(innerelement, {duration:0.2, from:0.7, to:this._savedOpacity});
    432            },
    433 
    434            /** @id MochiKit.DragAndDrop.onchange */
    435            onchange: b.noop,
    436 
    437            /** @id MochiKit.DragAndDrop.zindex */
    438            zindex: 1000,
    439 
    440            /** @id MochiKit.DragAndDrop.revert */
    441            revert: false,
    442 
    443            /** @id MochiKit.DragAndDrop.scroll */
    444            scroll: false,
    445 
    446            /** @id MochiKit.DragAndDrop.scrollSensitivity */
    447            scrollSensitivity: 20,
    448 
    449            /** @id MochiKit.DragAndDrop.scrollSpeed */
    450            scrollSpeed: 15,
    451            // false, or xy or [x, y] or function (x, y){return [x, y];}
    452 
    453            /** @id MochiKit.DragAndDrop.snap */
    454            snap: false
    455        }, options || {});
    456 
    457        var d = MochiKit.DOM;
    458        this.element = d.getElement(element);
    459 
    460        if (options.handle && (typeof(options.handle) == 'string')) {
    461            this.handle = d.getFirstElementByTagAndClassName(null,
    462                                       options.handle, this.element);
    463        }
    464        if (!this.handle) {
    465            this.handle = d.getElement(options.handle);
    466        }
    467        if (!this.handle) {
    468            this.handle = this.element;
    469        }
    470 
    471        if (options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
    472            options.scroll = d.getElement(options.scroll);
    473            this._isScrollChild = MochiKit.DOM.isChildNode(this.element, options.scroll);
    474        }
    475 
    476        d.makePositioned(this.element);  // fix IE
    477 
    478        this.delta = this.currentDelta();
    479        this.options = options;
    480        this.dragging = false;
    481 
    482        this.eventMouseDown = MochiKit.Signal.connect(this.handle,
    483                              'onmousedown', this, this.initDrag);
    484        MochiKit.DragAndDrop.Draggables.register(this);
    485    },
    486 
    487    /** @id MochiKit.DragAndDrop.destroy */
    488    destroy: function () {
    489        MochiKit.Signal.disconnect(this.eventMouseDown);
    490        MochiKit.DragAndDrop.Draggables.unregister(this);
    491    },
    492 
    493    /** @id MochiKit.DragAndDrop.currentDelta */
    494    currentDelta: function () {
    495        var s = MochiKit.Style.getStyle;
    496        return [
    497          parseInt(s(this.element, 'left') || '0'),
    498          parseInt(s(this.element, 'top') || '0')];
    499    },
    500 
    501    /** @id MochiKit.DragAndDrop.initDrag */
    502    initDrag: function (event) {
    503        if (!event.mouse().button.left) {
    504            return;
    505        }
    506        // abort on form elements, fixes a Firefox issue
    507        var src = event.target();
    508        var tagName = (src.tagName || '').toUpperCase();
    509        if (tagName === 'INPUT' || tagName === 'SELECT' ||
    510            tagName === 'OPTION' || tagName === 'BUTTON' ||
    511            tagName === 'TEXTAREA') {
    512            return;
    513        }
    514 
    515        if (this._revert) {
    516            this._revert.cancel();
    517            this._revert = null;
    518        }
    519 
    520        var pointer = event.mouse();
    521        var pos = MochiKit.Position.cumulativeOffset(this.element);
    522        this.offset = [pointer.page.x - pos.x, pointer.page.y - pos.y];
    523 
    524        MochiKit.DragAndDrop.Draggables.activate(this);
    525        event.stop();
    526    },
    527 
    528    /** @id MochiKit.DragAndDrop.startDrag */
    529    startDrag: function (event) {
    530        this.dragging = true;
    531        if (this.options.selectclass) {
    532            MochiKit.DOM.addElementClass(this.element,
    533                                         this.options.selectclass);
    534        }
    535        if (this.options.zindex) {
    536            this.originalZ = parseInt(MochiKit.Style.getStyle(this.element,
    537                                      'z-index') || '0');
    538            this.element.style.zIndex = this.options.zindex;
    539        }
    540 
    541        if (this.options.ghosting) {
    542            this._clone = this.element.cloneNode(true);
    543            this.ghostPosition = MochiKit.Position.absolutize(this.element);
    544            this.element.parentNode.insertBefore(this._clone, this.element);
    545        }
    546 
    547        if (this.options.scroll) {
    548            if (this.options.scroll == window) {
    549                var where = this._getWindowScroll(this.options.scroll);
    550                this.originalScrollLeft = where.left;
    551                this.originalScrollTop = where.top;
    552            } else {
    553                this.originalScrollLeft = this.options.scroll.scrollLeft;
    554                this.originalScrollTop = this.options.scroll.scrollTop;
    555            }
    556        }
    557 
    558        MochiKit.DragAndDrop.Droppables.prepare(this.element);
    559        MochiKit.DragAndDrop.Draggables.notify('start', this, event);
    560        if (this.options.starteffect) {
    561            this.options.starteffect(this.element);
    562        }
    563    },
    564 
    565    /** @id MochiKit.DragAndDrop.updateDrag */
    566    updateDrag: function (event, pointer) {
    567        if (!this.dragging) {
    568            this.startDrag(event);
    569        }
    570        MochiKit.Position.prepare();
    571        MochiKit.DragAndDrop.Droppables.show(pointer, this.element);
    572        MochiKit.DragAndDrop.Draggables.notify('drag', this, event);
    573        this.draw(pointer);
    574        this.options.onchange(this);
    575 
    576        if (this.options.scroll) {
    577            this.stopScrolling();
    578            var p, q;
    579            if (this.options.scroll == window) {
    580                var s = this._getWindowScroll(this.options.scroll);
    581                p = new MochiKit.Style.Coordinates(s.left, s.top);
    582                q = new MochiKit.Style.Coordinates(s.left + s.width,
    583                                                   s.top + s.height);
    584            } else {
    585                p = MochiKit.Position.page(this.options.scroll);
    586                p.x += this.options.scroll.scrollLeft;
    587                p.y += this.options.scroll.scrollTop;
    588                p.x += (window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0);
    589                p.y += (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0);
    590                q = new MochiKit.Style.Coordinates(p.x + this.options.scroll.offsetWidth,
    591                                                   p.y + this.options.scroll.offsetHeight);
    592            }
    593            var speed = [0, 0];
    594            if (pointer.page.x > (q.x - this.options.scrollSensitivity)) {
    595                speed[0] = pointer.page.x - (q.x - this.options.scrollSensitivity);
    596            } else if (pointer.page.x < (p.x + this.options.scrollSensitivity)) {
    597                speed[0] = pointer.page.x - (p.x + this.options.scrollSensitivity);
    598            }
    599            if (pointer.page.y > (q.y - this.options.scrollSensitivity)) {
    600                speed[1] = pointer.page.y - (q.y - this.options.scrollSensitivity);
    601            } else if (pointer.page.y < (p.y + this.options.scrollSensitivity)) {
    602                speed[1] = pointer.page.y - (p.y + this.options.scrollSensitivity);
    603            }
    604            this.startScrolling(speed);
    605        }
    606 
    607        // fix AppleWebKit rendering
    608        if (/AppleWebKit'/.test(navigator.appVersion)) {
    609            window.scrollBy(0, 0);
    610        }
    611        event.stop();
    612    },
    613 
    614    /** @id MochiKit.DragAndDrop.finishDrag */
    615    finishDrag: function (event, success) {
    616        var dr = MochiKit.DragAndDrop;
    617        this.dragging = false;
    618        if (this.options.selectclass) {
    619            MochiKit.DOM.removeElementClass(this.element,
    620                                            this.options.selectclass);
    621        }
    622 
    623        if (this.options.ghosting) {
    624            // XXX: from a user point of view, it would be better to remove
    625            // the node only *after* the MochiKit.Visual.Move end when used
    626            // with revert.
    627            MochiKit.Position.relativize(this.element, this.ghostPosition);
    628            MochiKit.DOM.removeElement(this._clone);
    629            this._clone = null;
    630        }
    631 
    632        if (success) {
    633            dr.Droppables.fire(event, this.element);
    634        }
    635        dr.Draggables.notify('end', this, event);
    636 
    637        var revert = this.options.revert;
    638        if (revert && typeof(revert) == 'function') {
    639            revert = revert(this.element);
    640        }
    641 
    642        var d = this.currentDelta();
    643        if (revert && this.options.reverteffect) {
    644            this._revert = this.options.reverteffect(this.element,
    645                d[1] - this.delta[1], d[0] - this.delta[0]);
    646        } else {
    647            this.delta = d;
    648        }
    649 
    650        if (this.options.zindex) {
    651            this.element.style.zIndex = this.originalZ;
    652        }
    653 
    654        if (this.options.endeffect) {
    655            this.options.endeffect(this.element);
    656        }
    657 
    658        dr.Draggables.deactivate();
    659        dr.Droppables.reset(this.element);
    660    },
    661 
    662    /** @id MochiKit.DragAndDrop.keyPress */
    663    keyPress: function (event) {
    664        if (event.key().string != "KEY_ESCAPE") {
    665            return;
    666        }
    667        this.finishDrag(event, false);
    668        event.stop();
    669    },
    670 
    671    /** @id MochiKit.DragAndDrop.endDrag */
    672    endDrag: function (event) {
    673        if (!this.dragging) {
    674            return;
    675        }
    676        this.stopScrolling();
    677        this.finishDrag(event, true);
    678        event.stop();
    679    },
    680 
    681    /** @id MochiKit.DragAndDrop.draw */
    682    draw: function (point) {
    683        var pos = MochiKit.Position.cumulativeOffset(this.element);
    684        var d = this.currentDelta();
    685        pos.x -= d[0];
    686        pos.y -= d[1];
    687 
    688        if (this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
    689            pos.x -= this.options.scroll.scrollLeft - this.originalScrollLeft;
    690            pos.y -= this.options.scroll.scrollTop - this.originalScrollTop;
    691        }
    692 
    693        var p = [point.page.x - pos.x - this.offset[0],
    694                 point.page.y - pos.y - this.offset[1]];
    695 
    696        if (this.options.snap) {
    697            if (typeof(this.options.snap) == 'function') {
    698                p = this.options.snap(p[0], p[1]);
    699            } else {
    700                if (this.options.snap instanceof Array) {
    701                    var i = -1;
    702                    p = MochiKit.Base.map(MochiKit.Base.bind(function (v) {
    703                            i += 1;
    704                            return Math.round(v/this.options.snap[i]) *
    705                                   this.options.snap[i];
    706                        }, this), p);
    707                } else {
    708                    p = MochiKit.Base.map(MochiKit.Base.bind(function (v) {
    709                        return Math.round(v/this.options.snap) *
    710                               this.options.snap;
    711                        }, this), p);
    712                }
    713            }
    714        }
    715        var style = this.element.style;
    716        if ((!this.options.constraint) ||
    717            (this.options.constraint == 'horizontal')) {
    718            style.left = p[0] + 'px';
    719        }
    720        if ((!this.options.constraint) ||
    721            (this.options.constraint == 'vertical')) {
    722            style.top = p[1] + 'px';
    723        }
    724        if (style.visibility == 'hidden') {
    725            style.visibility = '';  // fix gecko rendering
    726        }
    727    },
    728 
    729    /** @id MochiKit.DragAndDrop.stopScrolling */
    730    stopScrolling: function () {
    731        if (this.scrollInterval) {
    732            clearInterval(this.scrollInterval);
    733            this.scrollInterval = null;
    734            MochiKit.DragAndDrop.Draggables._lastScrollPointer = null;
    735        }
    736    },
    737 
    738    /** @id MochiKit.DragAndDrop.startScrolling */
    739    startScrolling: function (speed) {
    740        if (!speed[0] && !speed[1]) {
    741            return;
    742        }
    743        this.scrollSpeed = [speed[0] * this.options.scrollSpeed,
    744                            speed[1] * this.options.scrollSpeed];
    745        this.lastScrolled = new Date();
    746        this.scrollInterval = setInterval(MochiKit.Base.bind(this.scroll, this), 10);
    747    },
    748 
    749    /** @id MochiKit.DragAndDrop.scroll */
    750    scroll: function () {
    751        var current = new Date();
    752        var delta = current - this.lastScrolled;
    753        this.lastScrolled = current;
    754 
    755        if (this.options.scroll == window) {
    756            var s = this._getWindowScroll(this.options.scroll);
    757            if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
    758                var dm = delta / 1000;
    759                this.options.scroll.scrollTo(s.left + dm * this.scrollSpeed[0],
    760                                             s.top + dm * this.scrollSpeed[1]);
    761            }
    762        } else {
    763            this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
    764            this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000;
    765        }
    766 
    767        var d = MochiKit.DragAndDrop;
    768 
    769        MochiKit.Position.prepare();
    770        d.Droppables.show(d.Draggables._lastPointer, this.element);
    771        d.Draggables.notify('drag', this);
    772        if (this._isScrollChild) {
    773            d.Draggables._lastScrollPointer = d.Draggables._lastScrollPointer || d.Draggables._lastPointer;
    774            d.Draggables._lastScrollPointer.x += this.scrollSpeed[0] * delta / 1000;
    775            d.Draggables._lastScrollPointer.y += this.scrollSpeed[1] * delta / 1000;
    776            if (d.Draggables._lastScrollPointer.x < 0) {
    777                d.Draggables._lastScrollPointer.x = 0;
    778            }
    779            if (d.Draggables._lastScrollPointer.y < 0) {
    780                d.Draggables._lastScrollPointer.y = 0;
    781            }
    782            this.draw(d.Draggables._lastScrollPointer);
    783        }
    784 
    785        this.options.onchange(this);
    786    },
    787 
    788    _getWindowScroll: function (win) {
    789        var vp, w, h;
    790        MochiKit.DOM.withWindow(win, function () {
    791            vp = MochiKit.Style.getViewportPosition(win.document);
    792        });
    793        if (win.innerWidth) {
    794            w = win.innerWidth;
    795            h = win.innerHeight;
    796        } else if (win.document.documentElement && win.document.documentElement.clientWidth) {
    797            w = win.document.documentElement.clientWidth;
    798            h = win.document.documentElement.clientHeight;
    799        } else {
    800            w = win.document.body.offsetWidth;
    801            h = win.document.body.offsetHeight;
    802        }
    803        return {top: vp.x, left: vp.y, width: w, height: h};
    804    },
    805 
    806    /** @id MochiKit.DragAndDrop.repr */
    807    repr: function () {
    808        return '[' + this.__class__.NAME + ", options:" + MochiKit.Base.repr(this.options) + "]";
    809    }
    810 };
    811 
    812 MochiKit.DragAndDrop.__new__ = function () {
    813    MochiKit.Base.nameFunctions(this);
    814 
    815    this.EXPORT_TAGS = {
    816        ":common": this.EXPORT,
    817        ":all": MochiKit.Base.concat(this.EXPORT, this.EXPORT_OK)
    818    };
    819 };
    820 
    821 MochiKit.DragAndDrop.__new__();
    822 
    823 MochiKit.Base._exportSymbols(this, MochiKit.DragAndDrop);