PageSourceSearch

https://sol.vin/js/window_drag.js

js sol.vin collected 2026-10-09 21:58:27 UTC 6,584 bytes, 187 lines download raw bytes

1/**
2 * WindowDraggable - Modular, DRY helper for draggable windows in SOL.VIN
3 * 
4 * Allows windows with title bars (.window-header) to be dragged around freely
5 * (including outside viewport boundaries). Scrolling during drag is dynamically
6 * accounted for so the element stays locked to the pointer. Upon mouse/touch release,
7 * the window smoothly snaps back to its original layout origin.
8 */
9(function(window) {
10  'use strict';
11
12  var WindowDraggable = {
13    activeWindow: null,
14    startPageX: 0,
15    startPageY: 0,
16    lastClientX: 0,
17    lastClientY: 0,
18    currentTx: 0,
19    currentTy: 0,
20    initialTx: 0,
21    initialTy: 0,
22    dragged: false,
23
24    /**
25     * Initializes draggable behavior for all windows with header elements in scope.
26     */
27    init: function(scope) {
28      scope = scope || document;
29      var selector = '.terminal-window, .gui-box, [data-window-drag]';
30      var windows = scope.querySelectorAll(selector);
31
32      for (var i = 0; i < windows.length; i++) {
33        var win = windows[i];
34        var header = win.querySelector(':scope > .window-header, :scope > .menu-window-header') || win.querySelector('.window-header');
35        if (header) {
36          this.attach(win, header);
37        }
38      }
39    },
40
41    /**
42     * Attaches dragging event handlers to a specific window and header.
43     */
44    attach: function(win, header) {
45      if (!win || !header) return;
46      if (win.dataset.draggableInitialized === 'true') return;
47      win.dataset.draggableInitialized = 'true';
48
49      header.style.cursor = 'grab';
50      header.style.userSelect = 'none';
51      header.style.webkitUserSelect = 'none';
52
53      var self = this;
54
55      function updatePosition() {
56        if (!self.activeWindow || self.activeWindow !== win) return;
57
58        var scrollX = window.scrollX || window.pageXOffset || 0;
59        var scrollY = window.scrollY || window.pageYOffset || 0;
60
61        var currentPageX = self.lastClientX + scrollX;
62        var currentPageY = self.lastClientY + scrollY;
63
64        var dx = currentPageX - self.startPageX;
65        var dy = currentPageY - self.startPageY;
66
67        var proposedTx = self.initialTx + dx;
68        var proposedTy = self.initialTy + dy;
69
70        self.currentTx = proposedTx;
71        self.currentTy = proposedTy;
72        self.dragged = (Math.abs(dx) > 1 || Math.abs(dy) > 1);
73
74        win.style.transform = 'translate(' + proposedTx.toFixed(2) + 'px, ' + proposedTy.toFixed(2) + 'px)';
75      }
76
77      function onPointerDown(e) {
78        // Ignore clicks on control buttons or interactive links/inputs
79        var target = e.target;
80        if (target.closest('.window-btn, .window-controls, button, a, input, textarea, select, .carousel-overlay-control')) {
81          return;
82        }
83
84        var pointer = e.touches ? e.touches[0] : e;
85        if (e.button !== undefined && e.button !== 0) return; // Only left click
86
87        self.activeWindow = win;
88        self.dragged = false;
89
90        var scrollX = window.scrollX || window.pageXOffset || 0;
91        var scrollY = window.scrollY || window.pageYOffset || 0;
92
93        self.lastClientX = pointer.clientX;
94        self.lastClientY = pointer.clientY;
95        self.startPageX = (pointer.pageX !== undefined) ? pointer.pageX : (pointer.clientX + scrollX);
96        self.startPageY = (pointer.pageY !== undefined) ? pointer.pageY : (pointer.clientY + scrollY);
97
98        // Parse current transform translation if already translated
99        var matrix = window.getComputedStyle(win).transform;
100        self.initialTx = 0;
101        self.initialTy = 0;
102        if (matrix && matrix !== 'none') {
103          var values = matrix.split('(')[1].split(')')[0].split(',');
104          self.initialTx = parseFloat(values[4]) || 0;
105          self.initialTy = parseFloat(values[5]) || 0;
106        }
107
108        self.currentTx = self.initialTx;
109        self.currentTy = self.initialTy;
110
111        win.style.transition = 'none';
112        win.style.zIndex = '1000';
113        win.classList.add('is-window-dragging');
114        header.style.cursor = 'grabbing';
115
116        document.addEventListener('mousemove', onPointerMove, { passive: false });
117        document.addEventListener('mouseup', onPointerUp);
118        document.addEventListener('touchmove', onPointerMove, { passive: false });
119        document.addEventListener('touchend', onPointerUp);
120        document.addEventListener('touchcancel', onPointerUp);
121        window.addEventListener('scroll', onScroll, { passive: true });
122
123        e.preventDefault();
124      }
125
126      function onPointerMove(e) {
127        if (!self.activeWindow || self.activeWindow !== win) return;
128        var pointer = e.touches ? e.touches[0] : e;
129
130        self.lastClientX = pointer.clientX;
131        self.lastClientY = pointer.clientY;
132
133        updatePosition();
134
135        if (e.cancelable) e.preventDefault();
136      }
137
138      function onScroll() {
139        if (!self.activeWindow || self.activeWindow !== win) return;
140        updatePosition();
141      }
142
143      function onPointerUp() {
144        if (!self.activeWindow || self.activeWindow !== win) return;
145
146        document.removeEventListener('mousemove', onPointerMove);
147        document.removeEventListener('mouseup', onPointerUp);
148        document.removeEventListener('touchmove', onPointerMove);
149        document.removeEventListener('touchend', onPointerUp);
150        document.removeEventListener('touchcancel', onPointerUp);
151        window.removeEventListener('scroll', onScroll);
152
153        header.style.cursor = 'grab';
154        win.classList.remove('is-window-dragging');
155
156        if (self.dragged) {
157          // Snap back smoothly to original layout position (translate 0,0)
158          win.style.transition = 'transform 0.35s cubic-bezier(0.25, 0.8, 0.25, 1)';
159          win.style.transform = 'translate(0px, 0px)';
160
161          var onSnapEnd = function() {
162            win.removeEventListener('transitionend', onSnapEnd);
163            win.style.transform = '';
164            win.style.transition = '';
165            win.style.zIndex = '';
166          };
167          win.addEventListener('transitionend', onSnapEnd);
168
169          // Fallback timeout in case transitionend does not fire
170          setTimeout(onSnapEnd, 400);
171        } else {
172          win.style.transform = '';
173          win.style.transition = '';
174          win.style.zIndex = '';
175        }
176
177        self.activeWindow = null;
178        self.dragged = false;
179      }
180
181      header.addEventListener('mousedown', onPointerDown);
182      header.addEventListener('touchstart', onPointerDown, { passive: false });
183    }
184  };
185
186  window.WindowDraggable = WindowDraggable;
187})(window);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.