transitions-refine 0.3.32 → 0.3.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/demo.html +70 -0
  2. package/package.json +1 -1
package/demo.html CHANGED
@@ -2383,6 +2383,7 @@
2383
2383
  for(const n of Object.keys(rec.prevVars||{})){try{const pv=rec.prevVars[n];if(pv)el.style.setProperty(n,pv);else el.style.removeProperty(n);}catch{}}
2384
2384
  }
2385
2385
  store.applied&&store.applied.clear();
2386
+ try{tlSyncNoneGuard(TL_LP_GUARD_PHASE_ID,null);}catch{}
2386
2387
  }
2387
2388
 
2388
2389
  // ── registry (per-lane overrides) ──
@@ -2666,6 +2667,66 @@
2666
2667
  }catch{}
2667
2668
  return out;
2668
2669
  }
2670
+ // ── FLIP "jump-to-start" guard ──────────────────────────────────────────
2671
+ // Many transitions teleport to a start offset before animating, using a
2672
+ // state class that sets `transition: none` (the FLIP pattern), e.g.
2673
+ // `.t-text-swap.is-enter-start { transform: translateY(...); transition: none }`.
2674
+ // Our live-preview mirrors edited timings as an INLINE `transition` on the
2675
+ // element; inline wins (specificity 1,0,0,0) over that class rule, so the
2676
+ // instant teleport becomes an animation — the entering content then eases
2677
+ // in from the EXIT position instead of its start offset, flipping the
2678
+ // direction. Fix generally: find those page-authored `transition: none`
2679
+ // start-states and re-assert them with `!important` so the teleport stays
2680
+ // instant regardless of our inline transition. We only match STATE variants
2681
+ // (a selector whose subject compound carries a class the element is NOT
2682
+ // currently wearing) so we never freeze an element's BASE transition — the
2683
+ // base is what carries the lanes we add. @media blocks (e.g. reduced-motion)
2684
+ // are skipped so we don't promote their `transition: none` to always-on.
2685
+ const _TL_NONE_CACHE=new WeakMap();
2686
+ function tlCollectNoneSelectors(el){
2687
+ let cached=_TL_NONE_CACHE.get(el); if(cached)return cached;
2688
+ const out=new Set();
2689
+ try{
2690
+ const own=(typeof el.className==="string"?el.className.trim().split(/\s+/):Array.from(el.classList||[])).filter(Boolean);
2691
+ if(own.length){
2692
+ const ownSet=new Set(own);
2693
+ const esc=s=>s.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");
2694
+ const tokRes=own.map(c=>new RegExp("\\."+esc(c)+"(?![\\w-])"));
2695
+ for(const sheet of Array.from(document.styleSheets||[])){
2696
+ let rules;try{rules=sheet.cssRules;}catch{continue;}
2697
+ for(const rule of Array.from(rules||[])){
2698
+ if(rule.selectorText===undefined)continue; // skip @media / @supports etc.
2699
+ const tv=rule.style&&rule.style.getPropertyValue("transition");
2700
+ if(!tv||!/^\s*none\s*$/i.test(tv))continue;
2701
+ for(const part of rule.selectorText.split(",")){
2702
+ const s=part.trim(); if(!s)continue;
2703
+ // subject = rightmost compound that carries one of the element's tokens.
2704
+ const compounds=s.split(/\s*[>+~]\s*|\s+/).filter(Boolean);
2705
+ let subject=null;
2706
+ for(let i=compounds.length-1;i>=0;i--){ if(tokRes.some(re=>re.test(compounds[i]))){subject=compounds[i];break;} }
2707
+ if(!subject)continue;
2708
+ const subjClasses=(subject.match(/\.[-\w]+/g)||[]).map(c=>c.slice(1));
2709
+ // require a state class the element isn't currently wearing.
2710
+ if(subjClasses.some(c=>!ownSet.has(c)))out.add(s);
2711
+ }
2712
+ }
2713
+ }
2714
+ }
2715
+ }catch{}
2716
+ _TL_NONE_CACHE.set(el,out);
2717
+ return out;
2718
+ }
2719
+ function tlSyncNoneGuard(id,selectors){
2720
+ try{
2721
+ let st=document.getElementById(id);
2722
+ if(!selectors||!selectors.size){ if(st)st.remove(); return; }
2723
+ if(!st){ st=document.createElement("style"); st.id=id; (document.head||document.documentElement).appendChild(st); }
2724
+ const css=Array.from(selectors).map(s=>s+"{transition:none !important;}").join("\n");
2725
+ if(st.textContent!==css)st.textContent=css;
2726
+ }catch{}
2727
+ }
2728
+ const TL_LP_GUARD_FLAT_ID="tl-live-none-guard-flat";
2729
+ const TL_LP_GUARD_PHASE_ID="tl-live-none-guard-phase";
2669
2730
  // Same, resolving the element from a CSS selector (used when only a lane's
2670
2731
  // selector is known, e.g. a grouped phase member).
2671
2732
  function laneValueHint(selector, property){
@@ -5147,12 +5208,17 @@
5147
5208
  _TX_LIVE_BASE.delete(el);
5148
5209
  applied.delete(el);
5149
5210
  }
5211
+ // keep FLIP jump-to-start states instant under our inline transition
5212
+ const guard=new Set();
5213
+ for(const el of desired.keys()){ for(const s of tlCollectNoneSelectors(el)) guard.add(s); }
5214
+ tlSyncNoneGuard(TL_LP_GUARD_FLAT_ID,guard);
5150
5215
  },[entries,registry]);
5151
5216
  // restore everything on unmount
5152
5217
  useEffect(()=>()=>{
5153
5218
  const applied=liveAppliedRef.current;
5154
5219
  for(const [el,rec] of applied){ try{el.style.transition=rec.prevInline;}catch{} for(const n of Object.keys(rec.vars||{})){try{const pv=(rec.prevVars||{})[n];if(pv)el.style.setProperty(n,pv);else el.style.removeProperty(n);}catch{}} _TX_LIVE_BASE.delete(el); }
5155
5220
  applied.clear();
5221
+ tlSyncNoneGuard(TL_LP_GUARD_FLAT_ID,null);
5156
5222
  },[]);
5157
5223
  // ── live styling: grouped phases (state-gated) ──────────────────────────
5158
5224
  // Open and close of one component share the SAME element(s), so a single
@@ -5277,6 +5343,10 @@
5277
5343
  for(const n of Object.keys(rec.prevVars||{})){try{const pv=rec.prevVars[n];if(pv)el.style.setProperty(n,pv);else el.style.removeProperty(n);}catch{}}
5278
5344
  store.applied.delete(el);
5279
5345
  }
5346
+ // keep FLIP jump-to-start states instant under our inline transition
5347
+ const guard=new Set();
5348
+ for(const el of desiredCss.keys()){ for(const s of tlCollectNoneSelectors(el)) guard.add(s); }
5349
+ tlSyncNoneGuard(TL_LP_GUARD_PHASE_ID,guard);
5280
5350
  };
5281
5351
  // Coalesce via microtask (NOT rAF): the gate must update the moment the
5282
5352
  // state class/attr flips — before the browser styles the next frame — so
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "transitions-refine",
3
- "version": "0.3.32",
3
+ "version": "0.3.33",
4
4
  "description": "Live, agent-driven Refine panel for CSS/Motion transitions — injects a timeline + Refine UI and runs transitions.dev suggestions via your coding agent.",
5
5
  "type": "module",
6
6
  "bin": {