Spaces:
Sleeping
Sleeping
Merge branch 'fix/floating-panels-swallow-clicks'
Browse filesStop perpetual 3D float on panels that contain controls, so clicks are
no longer swallowed by a drifting button.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- app/ui/motion_runtime.py +28 -2
app/ui/motion_runtime.py
CHANGED
|
@@ -101,6 +101,27 @@ _DRIVER = r"""
|
|
| 101 |
return true;
|
| 102 |
}
|
| 103 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
// ββ Depth ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 105 |
// A translateZ only reads as depth when an ancestor carries a
|
| 106 |
// perspective, and a perspective β like a transform β makes that
|
|
@@ -132,7 +153,7 @@ _DRIVER = r"""
|
|
| 132 |
// long as the node lives, so nothing else may clear it. Anything that wants
|
| 133 |
// the transform (hover, press, tilt) pauses this first and resumes after.
|
| 134 |
function float3d(el, i) {
|
| 135 |
-
if (!
|
| 136 |
depthParent(el);
|
| 137 |
el.style.transformStyle = 'preserve-3d';
|
| 138 |
var ph = (i % 5) / 5;
|
|
@@ -158,6 +179,9 @@ _DRIVER = r"""
|
|
| 158 |
// Pointer tilt. Hands over from the float and hands back on leave, because
|
| 159 |
// two animations writing the same transform component fight each other.
|
| 160 |
function tilt3d(el, max) {
|
|
|
|
|
|
|
|
|
|
| 161 |
var frame = 0;
|
| 162 |
el.addEventListener('pointerenter', function () { holdFloat(el); });
|
| 163 |
el.addEventListener('pointermove', function (e) {
|
|
@@ -342,7 +366,9 @@ _DRIVER = r"""
|
|
| 342 |
root.querySelectorAll('.ups-ev').forEach(function (el, i) {
|
| 343 |
if (el.dataset.upsD) return;
|
| 344 |
el.dataset.upsD = '1';
|
| 345 |
-
|
|
|
|
|
|
|
| 346 |
depthParent(el);
|
| 347 |
el.style.transformStyle = 'preserve-3d';
|
| 348 |
el.__upsFloat = M.animate(el, {
|
|
|
|
| 101 |
return true;
|
| 102 |
}
|
| 103 |
|
| 104 |
+
// Perpetual motion is a stricter test than canMove.
|
| 105 |
+
//
|
| 106 |
+
// A `click` fires only when mousedown and mouseup land on the SAME element.
|
| 107 |
+
// A surface that drifts for as long as it lives β or turns under the
|
| 108 |
+
// pointer β moves every control it contains out from under the cursor
|
| 109 |
+
// between those two events, so clicks are silently swallowed and the user
|
| 110 |
+
// has to stab at the button several times before one lands. That is the
|
| 111 |
+
// same rule the button handler further down already states ("a button that
|
| 112 |
+
// moves under the cursor is a button that gets mis-clicked"); it just has
|
| 113 |
+
// to apply to the surface CARRYING the button, not only to the button.
|
| 114 |
+
//
|
| 115 |
+
// Entrance animations are deliberately exempt: they are finite and settle,
|
| 116 |
+
// so the arrival choreography is unchanged. Only never-ending motion is
|
| 117 |
+
// withheld, and only from surfaces that actually hold controls β a KPI
|
| 118 |
+
// tile or a verdict card still floats and tilts exactly as before.
|
| 119 |
+
var CONTROLS = 'button, input, select, textarea, a[href], [role="button"], ' +
|
| 120 |
+
'[role="tab"], [role="option"], [contenteditable="true"]';
|
| 121 |
+
function canDrift(el) {
|
| 122 |
+
return canMove(el) && !el.querySelector(CONTROLS) && !el.matches(CONTROLS);
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
// ββ Depth ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 126 |
// A translateZ only reads as depth when an ancestor carries a
|
| 127 |
// perspective, and a perspective β like a transform β makes that
|
|
|
|
| 153 |
// long as the node lives, so nothing else may clear it. Anything that wants
|
| 154 |
// the transform (hover, press, tilt) pauses this first and resumes after.
|
| 155 |
function float3d(el, i) {
|
| 156 |
+
if (!canDrift(el)) return;
|
| 157 |
depthParent(el);
|
| 158 |
el.style.transformStyle = 'preserve-3d';
|
| 159 |
var ph = (i % 5) / 5;
|
|
|
|
| 179 |
// Pointer tilt. Hands over from the float and hands back on leave, because
|
| 180 |
// two animations writing the same transform component fight each other.
|
| 181 |
function tilt3d(el, max) {
|
| 182 |
+
// Guarded here rather than at each call site so no future caller can
|
| 183 |
+
// reintroduce a control-bearing surface that moves under the pointer.
|
| 184 |
+
if (!canDrift(el)) return;
|
| 185 |
var frame = 0;
|
| 186 |
el.addEventListener('pointerenter', function () { holdFloat(el); });
|
| 187 |
el.addEventListener('pointermove', function (e) {
|
|
|
|
| 366 |
root.querySelectorAll('.ups-ev').forEach(function (el, i) {
|
| 367 |
if (el.dataset.upsD) return;
|
| 368 |
el.dataset.upsD = '1';
|
| 369 |
+
// repeat: Infinity below β same rule as float3d: an entry that never
|
| 370 |
+
// settles must not be carrying anything clickable.
|
| 371 |
+
if (!canDrift(el)) return;
|
| 372 |
depthParent(el);
|
| 373 |
el.style.transformStyle = 'preserve-3d';
|
| 374 |
el.__upsFloat = M.animate(el, {
|