ananyajoshi Claude Opus 5 (1M context) commited on
Commit
443ea08
Β·
2 Parent(s): a722c74932e21f

Merge branch 'fix/floating-panels-swallow-clicks'

Browse files

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

Files changed (1) hide show
  1. 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 (!canMove(el)) return;
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
- if (!canMove(el)) return;
 
 
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, {