new landscape styling & scripting for gameroom #id_tray apparatus, & some overall scripting & styling like wobble on click-to-close; new --undUser & --duoUser rootvars universally the table felt values; many new Jasmine tests to handle tray functionality
This commit is contained in:
@@ -1,28 +1,77 @@
|
||||
var Tray = (function () {
|
||||
var _open = false;
|
||||
var _dragStartX = null;
|
||||
var _dragStartY = null;
|
||||
var _dragStartLeft = null;
|
||||
var _dragStartTop = null;
|
||||
var _dragHandled = false;
|
||||
|
||||
var _wrap = null;
|
||||
var _btn = null;
|
||||
var _tray = null;
|
||||
|
||||
// Portrait bounds (X axis)
|
||||
var _minLeft = 0;
|
||||
var _maxLeft = 0;
|
||||
|
||||
// Stored so reset() can remove them from document
|
||||
var _onDocMove = null;
|
||||
var _onDocUp = null;
|
||||
// Landscape bounds (Y axis): _maxTop = closed (more negative), _minTop = open
|
||||
var _minTop = 0;
|
||||
var _maxTop = 0;
|
||||
|
||||
// Stored so reset() can remove them
|
||||
var _onDocMove = null;
|
||||
var _onDocUp = null;
|
||||
var _onBtnClick = null;
|
||||
var _closePendingHide = null; // portrait: pending display:none after slide
|
||||
|
||||
function _cancelPendingHide() {
|
||||
if (_closePendingHide && _wrap) {
|
||||
_wrap.removeEventListener('transitionend', _closePendingHide);
|
||||
}
|
||||
_closePendingHide = null;
|
||||
}
|
||||
|
||||
// Testing hook — null means use real window dimensions
|
||||
var _landscapeOverride = null;
|
||||
|
||||
function _isLandscape() {
|
||||
if (_landscapeOverride !== null) return _landscapeOverride;
|
||||
return window.innerWidth > window.innerHeight;
|
||||
}
|
||||
|
||||
function _computeBounds() {
|
||||
var rightPx = parseInt(getComputedStyle(_wrap).right, 10);
|
||||
if (isNaN(rightPx)) rightPx = 0;
|
||||
var handleW = _btn.offsetWidth || 48;
|
||||
_minLeft = 0;
|
||||
_maxLeft = window.innerWidth - rightPx - handleW;
|
||||
if (_isLandscape()) {
|
||||
// Landscape: the wrap slides on the Y axis.
|
||||
// Structure (column-reverse): tray above, handle below.
|
||||
// Tray is always display:block in landscape — wrap top hides/reveals it.
|
||||
// Closed: wrap top = -(trayH) so tray is above viewport, handle at y=0.
|
||||
// Open: wrap top = gearBtnTop - wrapH so handle bottom = gear btn top.
|
||||
var gearBtn = document.getElementById('id_gear_btn');
|
||||
var gearBtnTop = window.innerHeight;
|
||||
if (gearBtn) {
|
||||
gearBtnTop = Math.round(gearBtn.getBoundingClientRect().top);
|
||||
}
|
||||
var handleH = (_btn && _btn.offsetHeight) || 48;
|
||||
// Tray is display:block so offsetHeight includes it; fall back to 280.
|
||||
var wrapH = (_wrap && _wrap.offsetHeight) || (handleH + 280);
|
||||
|
||||
// Closed: tray hidden above viewport, handle visible at y=0.
|
||||
_maxTop = -(wrapH - handleH);
|
||||
|
||||
// Open: handle bottom at gear btn top.
|
||||
_minTop = gearBtnTop - wrapH;
|
||||
} else {
|
||||
// Portrait: slide on X axis.
|
||||
var rightPx = parseInt(getComputedStyle(_wrap).right, 10);
|
||||
if (isNaN(rightPx)) rightPx = 0;
|
||||
var handleW = _btn.offsetWidth || 48;
|
||||
_minLeft = 0;
|
||||
_maxLeft = window.innerWidth - rightPx - handleW;
|
||||
}
|
||||
}
|
||||
|
||||
function _applyVerticalBounds() {
|
||||
// Portrait only: nudge wrap top/bottom to avoid overlapping nav/footer bars.
|
||||
var nav = document.querySelector('nav');
|
||||
var footer = document.querySelector('footer');
|
||||
var rem = parseFloat(getComputedStyle(document.documentElement).fontSize);
|
||||
@@ -43,42 +92,93 @@ var Tray = (function () {
|
||||
|
||||
function open() {
|
||||
if (_open) return;
|
||||
_cancelPendingHide(); // abort any in-flight portrait close animation
|
||||
_open = true;
|
||||
if (_tray) _tray.style.display = 'block';
|
||||
// Portrait only: toggle tray display.
|
||||
// Landscape: tray is always display:block; wrap position controls visibility.
|
||||
if (!_isLandscape() && _tray) _tray.style.display = 'block';
|
||||
if (_btn) _btn.classList.add('open');
|
||||
if (_wrap) {
|
||||
_wrap.classList.remove('tray-dragging');
|
||||
_wrap.style.left = _minLeft + 'px';
|
||||
if (_isLandscape()) {
|
||||
_wrap.style.top = _minTop + 'px';
|
||||
} else {
|
||||
_wrap.style.left = _minLeft + 'px';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function close() {
|
||||
if (!_open) return;
|
||||
_open = false;
|
||||
if (_tray) _tray.style.display = 'none';
|
||||
if (_btn) _btn.classList.remove('open');
|
||||
if (_wrap) {
|
||||
_wrap.classList.remove('tray-dragging');
|
||||
_wrap.style.left = _maxLeft + 'px';
|
||||
if (_isLandscape()) {
|
||||
_wrap.style.top = _maxTop + 'px';
|
||||
// Snap after the slide completes.
|
||||
_closePendingHide = function (e) {
|
||||
if (e.propertyName !== 'top') return;
|
||||
if (_wrap) _wrap.removeEventListener('transitionend', _closePendingHide);
|
||||
_closePendingHide = null;
|
||||
_snapWrap();
|
||||
};
|
||||
_wrap.addEventListener('transitionend', _closePendingHide);
|
||||
} else {
|
||||
_wrap.style.left = _maxLeft + 'px';
|
||||
// Snap first (tray still visible so it peeks), then hide tray.
|
||||
_closePendingHide = function (e) {
|
||||
if (e.propertyName !== 'left') return;
|
||||
if (_wrap) _wrap.removeEventListener('transitionend', _closePendingHide);
|
||||
_closePendingHide = null;
|
||||
_snapWrap(function () {
|
||||
if (!_open && _tray) _tray.style.display = 'none';
|
||||
});
|
||||
};
|
||||
_wrap.addEventListener('transitionend', _closePendingHide);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isOpen() { return _open; }
|
||||
|
||||
function _snapWrap(onDone) {
|
||||
if (!_wrap) return;
|
||||
_wrap.classList.add('snap');
|
||||
_wrap.addEventListener('animationend', function handler() {
|
||||
if (_wrap) _wrap.classList.remove('snap');
|
||||
_wrap.removeEventListener('animationend', handler);
|
||||
if (onDone) onDone();
|
||||
});
|
||||
}
|
||||
|
||||
function _wobble() {
|
||||
if (!_wrap) return;
|
||||
// Portrait: show tray so it peeks in during the translateX animation,
|
||||
// then re-hide it if the tray is still closed when the animation ends.
|
||||
if (!_isLandscape() && _tray) _tray.style.display = 'block';
|
||||
_wrap.classList.add('wobble');
|
||||
_wrap.addEventListener('animationend', function handler() {
|
||||
_wrap.classList.remove('wobble');
|
||||
_wrap.removeEventListener('animationend', handler);
|
||||
if (!_isLandscape() && !_open && _tray) _tray.style.display = 'none';
|
||||
});
|
||||
}
|
||||
|
||||
function _startDrag(clientX) {
|
||||
_dragStartX = clientX;
|
||||
_dragStartLeft = _wrap ? (parseInt(_wrap.style.left, 10) || _maxLeft) : _maxLeft;
|
||||
_dragHandled = false;
|
||||
function _startDrag(clientX, clientY) {
|
||||
_dragHandled = false;
|
||||
if (_wrap) _wrap.classList.add('tray-dragging');
|
||||
if (_isLandscape()) {
|
||||
_dragStartY = clientY;
|
||||
_dragStartX = null;
|
||||
_dragStartTop = _wrap ? (parseInt(_wrap.style.top, 10) || _maxTop) : _maxTop;
|
||||
_dragStartLeft = null;
|
||||
} else {
|
||||
_dragStartX = clientX;
|
||||
_dragStartY = null;
|
||||
_dragStartLeft = _wrap ? (parseInt(_wrap.style.left, 10) || _maxLeft) : _maxLeft;
|
||||
_dragStartTop = null;
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
@@ -87,40 +187,72 @@ var Tray = (function () {
|
||||
_tray = document.getElementById('id_tray');
|
||||
if (!_btn) return;
|
||||
|
||||
_computeBounds();
|
||||
_applyVerticalBounds();
|
||||
if (_wrap) _wrap.style.left = _maxLeft + 'px';
|
||||
if (_isLandscape()) {
|
||||
// Show tray before measuring so offsetHeight includes it.
|
||||
if (_tray) _tray.style.display = 'block';
|
||||
_computeBounds();
|
||||
// Clear portrait's inline left/bottom so media-query CSS applies.
|
||||
if (_wrap) { _wrap.style.left = ''; _wrap.style.bottom = ''; }
|
||||
if (_wrap) _wrap.style.top = _maxTop + 'px';
|
||||
} else {
|
||||
// Clear landscape's inline top so portrait CSS applies.
|
||||
if (_wrap) _wrap.style.top = '';
|
||||
_applyVerticalBounds();
|
||||
_computeBounds();
|
||||
if (_wrap) _wrap.style.left = _maxLeft + 'px';
|
||||
}
|
||||
|
||||
// Drag start — pointer and mouse variants so Selenium W3C actions
|
||||
// and synthetic Jasmine PointerEvents both work.
|
||||
_btn.addEventListener('pointerdown', function (e) {
|
||||
_startDrag(e.clientX);
|
||||
_startDrag(e.clientX, e.clientY);
|
||||
try { _btn.setPointerCapture(e.pointerId); } catch (_) {}
|
||||
});
|
||||
_btn.addEventListener('mousedown', function (e) {
|
||||
if (e.button !== 0) return;
|
||||
if (_dragStartX !== null) return; // pointerdown already handled it
|
||||
_startDrag(e.clientX);
|
||||
if (_dragStartX !== null || _dragStartY !== null) return;
|
||||
_startDrag(e.clientX, e.clientY);
|
||||
});
|
||||
|
||||
// Drag move / end — on document so events that land elsewhere during
|
||||
// the drag (no capture, or Selenium pointer quirks) still bubble here.
|
||||
_onDocMove = function (e) {
|
||||
if (_dragStartX === null || !_wrap) return;
|
||||
var newLeft = _dragStartLeft + (e.clientX - _dragStartX);
|
||||
newLeft = Math.max(_minLeft, Math.min(_maxLeft, newLeft));
|
||||
_wrap.style.left = newLeft + 'px';
|
||||
if (newLeft < _maxLeft) {
|
||||
if (!_open) {
|
||||
_open = true;
|
||||
if (_tray) _tray.style.display = 'block';
|
||||
if (_btn) _btn.classList.add('open');
|
||||
if (!_wrap) return;
|
||||
if (_isLandscape()) {
|
||||
if (_dragStartY === null) return;
|
||||
var newTop = _dragStartTop + (e.clientY - _dragStartY);
|
||||
newTop = Math.max(_maxTop, Math.min(_minTop, newTop));
|
||||
_wrap.style.top = newTop + 'px';
|
||||
// Open when dragged below closed position; update state + class only.
|
||||
// Tray display is not toggled in landscape — position controls visibility.
|
||||
if (newTop > _maxTop) {
|
||||
if (!_open) {
|
||||
_open = true;
|
||||
if (_btn) _btn.classList.add('open');
|
||||
}
|
||||
} else {
|
||||
if (_open) {
|
||||
_open = false;
|
||||
if (_btn) _btn.classList.remove('open');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (_open) {
|
||||
_open = false;
|
||||
if (_tray) _tray.style.display = 'none';
|
||||
if (_btn) _btn.classList.remove('open');
|
||||
if (_dragStartX === null) return;
|
||||
var newLeft = _dragStartLeft + (e.clientX - _dragStartX);
|
||||
newLeft = Math.max(_minLeft, Math.min(_maxLeft, newLeft));
|
||||
_wrap.style.left = newLeft + 'px';
|
||||
if (newLeft < _maxLeft) {
|
||||
if (!_open) {
|
||||
_open = true;
|
||||
if (_tray) _tray.style.display = 'block';
|
||||
if (_btn) _btn.classList.add('open');
|
||||
}
|
||||
} else {
|
||||
if (_open) {
|
||||
_open = false;
|
||||
if (_tray) _tray.style.display = 'none';
|
||||
if (_btn) _btn.classList.remove('open');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -128,17 +260,25 @@ var Tray = (function () {
|
||||
document.addEventListener('mousemove', _onDocMove);
|
||||
|
||||
_onDocUp = function (e) {
|
||||
if (_dragStartX !== null && Math.abs(e.clientX - _dragStartX) > 10) {
|
||||
_dragHandled = true;
|
||||
if (_isLandscape()) {
|
||||
if (_dragStartY !== null && Math.abs(e.clientY - _dragStartY) > 10) {
|
||||
_dragHandled = true;
|
||||
}
|
||||
_dragStartY = null;
|
||||
_dragStartTop = null;
|
||||
} else {
|
||||
if (_dragStartX !== null && Math.abs(e.clientX - _dragStartX) > 10) {
|
||||
_dragHandled = true;
|
||||
}
|
||||
_dragStartX = null;
|
||||
_dragStartLeft = null;
|
||||
}
|
||||
_dragStartX = null;
|
||||
_dragStartLeft = null;
|
||||
if (_wrap) _wrap.classList.remove('tray-dragging');
|
||||
};
|
||||
document.addEventListener('pointerup', _onDocUp);
|
||||
document.addEventListener('mouseup', _onDocUp);
|
||||
|
||||
_btn.addEventListener('click', function () {
|
||||
_onBtnClick = function () {
|
||||
if (_dragHandled) {
|
||||
_dragHandled = false;
|
||||
return;
|
||||
@@ -148,26 +288,43 @@ var Tray = (function () {
|
||||
} else {
|
||||
_wobble();
|
||||
}
|
||||
});
|
||||
};
|
||||
_btn.addEventListener('click', _onBtnClick);
|
||||
|
||||
window.addEventListener('resize', function () {
|
||||
_computeBounds();
|
||||
_applyVerticalBounds();
|
||||
if (!_open && _wrap) _wrap.style.left = _maxLeft + 'px';
|
||||
if (_isLandscape()) {
|
||||
// Ensure tray is visible before measuring bounds.
|
||||
if (_tray) _tray.style.display = 'block';
|
||||
if (_wrap) { _wrap.style.left = ''; _wrap.style.bottom = ''; }
|
||||
_computeBounds();
|
||||
if (!_open && _wrap) _wrap.style.top = _maxTop + 'px';
|
||||
} else {
|
||||
// Switching to portrait: hide tray if closed.
|
||||
if (!_open && _tray) _tray.style.display = 'none';
|
||||
if (_wrap) _wrap.style.top = '';
|
||||
_computeBounds();
|
||||
_applyVerticalBounds();
|
||||
if (!_open && _wrap) _wrap.style.left = _maxLeft + 'px';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// reset() — restores module state; used by Jasmine afterEach
|
||||
function reset() {
|
||||
_open = false;
|
||||
_dragStartX = null;
|
||||
_dragStartLeft = null;
|
||||
_dragHandled = false;
|
||||
_open = false;
|
||||
_dragStartX = null;
|
||||
_dragStartY = null;
|
||||
_dragStartLeft = null;
|
||||
_dragStartTop = null;
|
||||
_dragHandled = false;
|
||||
_landscapeOverride = null;
|
||||
// Restore portrait default (display:none); landscape init() will show it.
|
||||
if (_tray) _tray.style.display = 'none';
|
||||
if (_btn) _btn.classList.remove('open');
|
||||
if (_wrap) {
|
||||
_wrap.classList.remove('wobble', 'tray-dragging');
|
||||
_wrap.classList.remove('wobble', 'snap', 'tray-dragging');
|
||||
_wrap.style.left = '';
|
||||
_wrap.style.top = '';
|
||||
}
|
||||
if (_onDocMove) {
|
||||
document.removeEventListener('pointermove', _onDocMove);
|
||||
@@ -179,6 +336,11 @@ var Tray = (function () {
|
||||
document.removeEventListener('mouseup', _onDocUp);
|
||||
_onDocUp = null;
|
||||
}
|
||||
if (_onBtnClick && _btn) {
|
||||
_btn.removeEventListener('click', _onBtnClick);
|
||||
_onBtnClick = null;
|
||||
}
|
||||
_cancelPendingHide();
|
||||
_wrap = null;
|
||||
_btn = null;
|
||||
_tray = null;
|
||||
@@ -190,5 +352,12 @@ var Tray = (function () {
|
||||
init();
|
||||
}
|
||||
|
||||
return { init: init, open: open, close: close, isOpen: isOpen, reset: reset };
|
||||
return {
|
||||
init: init,
|
||||
open: open,
|
||||
close: close,
|
||||
isOpen: isOpen,
|
||||
reset: reset,
|
||||
_testSetLandscape: function (v) { _landscapeOverride = v; },
|
||||
};
|
||||
}());
|
||||
|
||||
Reference in New Issue
Block a user