transitions-refine 0.3.28 → 0.3.30
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.
- package/demo.html +47 -2
- package/package.json +1 -1
- package/server/relay.mjs +1 -0
package/demo.html
CHANGED
|
@@ -4872,11 +4872,34 @@
|
|
|
4872
4872
|
// A replace suggestion may carry `patches` (one per related phase, e.g.
|
|
4873
4873
|
// open + close) — a recipe swap is one motion, so applying it writes BOTH.
|
|
4874
4874
|
// Otherwise fall back to the single `patch` on the active phase.
|
|
4875
|
-
|
|
4875
|
+
let patches=Array.isArray(s.patches)&&s.patches.length
|
|
4876
4876
|
? s.patches
|
|
4877
4877
|
: (s.patch&&s.patch.property?[{...s.patch,phase:(active&&active.phase)||null}]:[]);
|
|
4878
4878
|
if(!patches.length)return;
|
|
4879
4879
|
const groupId=active&&active.groupId;
|
|
4880
|
+
// Same-slot swap recipes (icon/text swaps) use two stacked members in one
|
|
4881
|
+
// group. The agent can sometimes return the recipe's blur/scale value for
|
|
4882
|
+
// only the currently selected member; expand those value patches to every
|
|
4883
|
+
// sibling member so replace applies the WHOLE group, not just the exiting
|
|
4884
|
+
// icon/text.
|
|
4885
|
+
const isSameSlotSwap=s.kind==="replace"&&/icon[-\s]?swap|text states swap|text[-\s]?swap|09-icon-swap|04-text-states-swap/i.test([s.title,s.reference,s.reason].filter(Boolean).join(" "));
|
|
4886
|
+
if(isSameSlotSwap&&groupId){
|
|
4887
|
+
const extra=[]; const has=new Set(patches.map(p=>[p.phase||"",p.property||"",p.member||"",p.blur!=null?"blur":"",p.scale!=null?"scale":"",p.translate!=null?"translate":""].join("|")));
|
|
4888
|
+
const valuePatches=patches.filter(p=>p.member&&(p.blur!=null||p.scale!=null)&&(p.property==="filter"||p.property==="transform"));
|
|
4889
|
+
for(const p of valuePatches){
|
|
4890
|
+
const target=entries.find(e=>e.kind==="phase"&&e.groupId===groupId&&(!p.phase||e.phase===p.phase))||active;
|
|
4891
|
+
const members=new Map();
|
|
4892
|
+
for(const t of (target&&target.effectiveTimings)||[]){
|
|
4893
|
+
const m=t.member||t.memberId;if(m&&!members.has(m))members.set(m,m);
|
|
4894
|
+
}
|
|
4895
|
+
for(const member of members.values()){
|
|
4896
|
+
const k=[p.phase||"",p.property||"",member,p.blur!=null?"blur":"",p.scale!=null?"scale":"",p.translate!=null?"translate":""].join("|");
|
|
4897
|
+
if(has.has(k))continue;
|
|
4898
|
+
has.add(k); extra.push({...p,member});
|
|
4899
|
+
}
|
|
4900
|
+
}
|
|
4901
|
+
if(extra.length)patches=patches.concat(extra);
|
|
4902
|
+
}
|
|
4880
4903
|
// Recipe timing per phase: a replace emits a phase-level timing patch
|
|
4881
4904
|
// (property:"all", duration+easing) plus lane-level value patches. An
|
|
4882
4905
|
// ADDED lane (e.g. a blur/filter lane the source never had) must ride the
|
|
@@ -5145,10 +5168,32 @@
|
|
|
5145
5168
|
if(sp.toSuf) active=stEls.length?stEls.some(e=>safeMatches(e,sp.toSuf)):true; // open
|
|
5146
5169
|
else if(sp.fromSuf) active=stEls.length?stEls.some(e=>!safeMatches(e,sp.fromSuf)):true; // close = not in fromState
|
|
5147
5170
|
else active=true; // no state info → always reflect (degrades to old behavior)
|
|
5171
|
+
const spVars={}; // union of this phase's value vars (scale/blur/translate)
|
|
5172
|
+
const memberEls=[]; // {el,css} of resolved members this phase
|
|
5148
5173
|
for(const mm of sp.members) for(const el of txMemberEls(mm.selector,sp.stateTarget)){
|
|
5149
5174
|
if(inUI(el))continue;
|
|
5150
|
-
if(mm.vars&&Object.keys(mm.vars).length){ const ex=desiredVars.get(el)||{}; desiredVars.set(el,{...ex,...mm.vars}); }
|
|
5175
|
+
if(mm.vars&&Object.keys(mm.vars).length){ const ex=desiredVars.get(el)||{}; desiredVars.set(el,{...ex,...mm.vars}); Object.assign(spVars,mm.vars); }
|
|
5151
5176
|
if(active) desiredCss.set(el,mm.css);
|
|
5177
|
+
memberEls.push({el,css:mm.css});
|
|
5178
|
+
}
|
|
5179
|
+
// SHARED-VAR recipes (icon/text swap, page slide, …) drive the non-
|
|
5180
|
+
// resting look through a custom property read by SEVERAL stacked
|
|
5181
|
+
// members from a COMMON ANCESTOR — and the "from" member is often not
|
|
5182
|
+
// even a captured lane (e.g. only opacity animates at rest, so the
|
|
5183
|
+
// entering icon has no filter/transform lane). Writing the var inline
|
|
5184
|
+
// on one member can't reach its siblings, and only the captured
|
|
5185
|
+
// member gets the recipe timing. So when a phase carries value vars:
|
|
5186
|
+
// (1) set the vars on the stateTarget container — every descendant
|
|
5187
|
+
// member inherits them, and each state's own CSS rule
|
|
5188
|
+
// (active → blur(0)/scale(1); inactive → blur(var)/scale(var))
|
|
5189
|
+
// gates where they actually show; and
|
|
5190
|
+
// (2) give the same recipe timing to the member's same-slot stacked
|
|
5191
|
+
// siblings (element children of the same parent sharing a class)
|
|
5192
|
+
// so the ENTERING member animates the new filter/transform lanes
|
|
5193
|
+
// instead of snapping.
|
|
5194
|
+
if(Object.keys(spVars).length){
|
|
5195
|
+
for(const el of stEls){ if(inUI(el))continue; const ex=desiredVars.get(el)||{}; desiredVars.set(el,{...ex,...spVars}); }
|
|
5196
|
+
if(active) for(const{el,css}of memberEls){ const parent=el.parentElement; if(!parent)continue; const cls=[...(el.classList||[])]; for(const sib of Array.from(parent.children)){ if(sib===el||inUI(sib)||!sib.classList)continue; if(cls.some(c=>sib.classList.contains(c))&&!desiredCss.has(sib))desiredCss.set(sib,css); } }
|
|
5152
5197
|
}
|
|
5153
5198
|
}
|
|
5154
5199
|
const allEls=new Set([...desiredCss.keys(),...desiredVars.keys()]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "transitions-refine",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.30",
|
|
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": {
|
package/server/relay.mjs
CHANGED
|
@@ -278,6 +278,7 @@ const RECIPES_BLOCK = [
|
|
|
278
278
|
"Recipe timing & easing — SET the transition to the recipe's documented values; do NOT keep the source's duration/easing when they differ from the recipe (a value being an on-grid token is NOT a reason to keep it if the recipe uses a different one):",
|
|
279
279
|
" - Easing: most surface/slide/reveal recipes use cubic-bezier(0.22, 1, 0.36, 1) (Smooth ease out) — page side-by-side, panel reveal, menu dropdown, modal, tooltip, accordion, tabs sliding, card resize, texts reveal, success check. Cross-fade swaps use ease-in-out — icon swap, text states swap. Overshoot curves stay as documented — notification badge / number pop-in cubic-bezier(0.34, 1.36, 0.64, 1), avatar return cubic-bezier(0.34, 3.85, 0.64, 1).",
|
|
280
280
|
" - SYMMETRIC recipes (SAME duration + easing BOTH directions — do NOT split open/close): page side-by-side (250ms), tabs sliding (250ms), accordion (250ms), icon swap (250ms), text states swap (150ms). Emit identical durationMs/easing for every phase.",
|
|
281
|
+
" - WHOLE-GROUP swaps: icon swap and text states swap are two stacked members in the same slot. A replace suggestion MUST patch BOTH members in EVERY phase, not just the currently selected/exiting member. Emit blur/scale (icon) or blur/translate (text) lane patches once per member per phase, copying each member name from `phases[].timings`.",
|
|
281
282
|
" - ASYMMETRIC recipes (open slower, close faster): menu dropdown / modal / tooltip / panel reveal → open 250ms, close 150ms.",
|
|
282
283
|
"Tie-break: prefer the lower-overhead recipe (card resize over panel reveal, dropdown over modal). If no recipe genuinely fits, return an empty suggestions array.",
|
|
283
284
|
].join("\n");
|