transitions-refine 0.3.27 → 0.3.29
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 +66 -8
- package/package.json +1 -1
- package/server/relay.mjs +9 -4
package/demo.html
CHANGED
|
@@ -2633,9 +2633,12 @@
|
|
|
2633
2633
|
if(tm){const sc=tm[1].match(/scale\(\s*([^),]+)/i);
|
|
2634
2634
|
if(sc){const inner=sc[1].trim();const vm=inner.match(/var\(\s*(--[\w-]+)/);
|
|
2635
2635
|
const num=vm?parseFloat(cs.getPropertyValue(vm[1])):parseFloat(inner);
|
|
2636
|
-
// pick the value furthest from the resting 1 (the real pre-scale)
|
|
2637
|
-
|
|
2638
|
-
|
|
2636
|
+
// pick the value furthest from the resting 1 (the real pre-scale);
|
|
2637
|
+
// a var-backed scale at rest (1) still reports its var — the hook
|
|
2638
|
+
// for ADDING a start-scale the source neutralized (e.g. icon swap).
|
|
2639
|
+
if(Number.isFinite(num)&&(Math.abs(num-1)>1e-4||vm)){
|
|
2640
|
+
const better=!bestScale||Math.abs(num-1)>Math.abs(bestScale.scale-1)||(bestScale.varName==null&&vm);
|
|
2641
|
+
if(better)bestScale={scale:num,...(vm?{varName:vm[1]}:{})};}}
|
|
2639
2642
|
const tr=tm[1].match(/translate(?:X|Y|3d)?\(\s*([^),]+)/i);
|
|
2640
2643
|
if(tr){const inner=tr[1].trim();const vm=inner.match(/var\(\s*(--[\w-]+)/);
|
|
2641
2644
|
const vn=vm?rootVar(vm[1]):null;
|
|
@@ -4869,11 +4872,55 @@
|
|
|
4869
4872
|
// A replace suggestion may carry `patches` (one per related phase, e.g.
|
|
4870
4873
|
// open + close) — a recipe swap is one motion, so applying it writes BOTH.
|
|
4871
4874
|
// Otherwise fall back to the single `patch` on the active phase.
|
|
4872
|
-
|
|
4875
|
+
let patches=Array.isArray(s.patches)&&s.patches.length
|
|
4873
4876
|
? s.patches
|
|
4874
4877
|
: (s.patch&&s.patch.property?[{...s.patch,phase:(active&&active.phase)||null}]:[]);
|
|
4875
4878
|
if(!patches.length)return;
|
|
4876
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
|
+
}
|
|
4903
|
+
// Recipe timing per phase: a replace emits a phase-level timing patch
|
|
4904
|
+
// (property:"all", duration+easing) plus lane-level value patches. An
|
|
4905
|
+
// ADDED lane (e.g. a blur/filter lane the source never had) must ride the
|
|
4906
|
+
// recipe's phase timing — NOT the source lane's leftover timing — so the
|
|
4907
|
+
// whole recipe moves in lockstep (e.g. filter deblurs exactly as the page
|
|
4908
|
+
// finishes sliding, instead of lagging on the old duration). Collect the
|
|
4909
|
+
// phase timing up front so synthesis is order-independent.
|
|
4910
|
+
const phaseTiming=new Map(); // phaseKey -> {durationMs,delayMs,easing}
|
|
4911
|
+
for(const p of patches){
|
|
4912
|
+
if(p.durationMs==null&&p.easing==null&&p.delayMs==null)continue;
|
|
4913
|
+
const key=p.phase||"";
|
|
4914
|
+
const cur=phaseTiming.get(key)||{};
|
|
4915
|
+
// a coarse property:"all" patch describes the whole phase; prefer it.
|
|
4916
|
+
if(p.property==="all"||!phaseTiming.has(key)){
|
|
4917
|
+
phaseTiming.set(key,{
|
|
4918
|
+
durationMs:p.durationMs??cur.durationMs,
|
|
4919
|
+
delayMs:p.delayMs??cur.delayMs,
|
|
4920
|
+
easing:p.easing??cur.easing,
|
|
4921
|
+
});
|
|
4922
|
+
}
|
|
4923
|
+
}
|
|
4877
4924
|
for(const p of patches){
|
|
4878
4925
|
if(!p.property)continue;
|
|
4879
4926
|
const timingOverride={};
|
|
@@ -4910,20 +4957,31 @@
|
|
|
4910
4957
|
// (readElValueHint reports var-backed values even at their resting 0),
|
|
4911
4958
|
// so the patch doesn't need to know the page's CSS custom properties.
|
|
4912
4959
|
const sibling=(prop)=>memberLanes.find(t=>t.property===prop)||memberLanes[0]||et[0]||null;
|
|
4960
|
+
// Prefer this patch's own timing, then the recipe's phase timing, then
|
|
4961
|
+
// the sibling lane's timing — so an added lane matches the recipe, not
|
|
4962
|
+
// the source's stale duration/easing.
|
|
4963
|
+
const pt=phaseTiming.get(p.phase||"")||{};
|
|
4964
|
+
// recipe timing to stamp on a value lane (blur/scale/translate) so it
|
|
4965
|
+
// rides the recipe duration/easing, not the source's leftover timing.
|
|
4966
|
+
const recipeTiming=()=>{const t={};if(pt.durationMs!=null)t.durationMs=pt.durationMs;if(pt.delayMs!=null)t.delayMs=pt.delayMs;if(pt.easing!=null)t.easing=pt.easing;return t;};
|
|
4913
4967
|
const synthTiming=(base)=>({
|
|
4914
|
-
durationMs:p.durationMs??(base&&base.durationMs)??250,
|
|
4915
|
-
delayMs:p.delayMs??(base&&base.delayMs)??0,
|
|
4916
|
-
easing:p.easing??(base&&base.easing)??"ease",
|
|
4968
|
+
durationMs:p.durationMs??pt.durationMs??(base&&base.durationMs)??250,
|
|
4969
|
+
delayMs:p.delayMs??pt.delayMs??(base&&base.delayMs)??0,
|
|
4970
|
+
easing:p.easing??pt.easing??(base&&base.easing)??"ease",
|
|
4917
4971
|
});
|
|
4918
4972
|
const applyValue=(prop,mk)=>{
|
|
4919
4973
|
// mk(lane, hint) → override fields for one lane, or null to skip.
|
|
4920
4974
|
let vLanes=lanes.filter(t=>t.property===prop);
|
|
4921
4975
|
if(!vLanes.length&&p.property==="all")vLanes=et.filter(t=>t.property===prop&&(!p.member||t.member===p.member||t.memberId===p.member));
|
|
4922
4976
|
if(vLanes.length){
|
|
4977
|
+
const rt=recipeTiming();
|
|
4923
4978
|
for(const lane of vLanes){
|
|
4924
4979
|
const hint=laneValueHint(lane.selector,prop);
|
|
4925
4980
|
const ov=mk(lane,hint);
|
|
4926
|
-
|
|
4981
|
+
// an EXISTING lane the recipe reuses (e.g. Page side-by-side's
|
|
4982
|
+
// filter lane at the source's 300ms) must ALSO adopt the recipe's
|
|
4983
|
+
// phase duration/easing, so the value settles in lockstep.
|
|
4984
|
+
if(ov)registry.setPropOverride(target.id,lane.laneId,{...rt,...ov});
|
|
4927
4985
|
}
|
|
4928
4986
|
return;
|
|
4929
4987
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "transitions-refine",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.29",
|
|
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
|
@@ -268,13 +268,18 @@ const RECIPES_BLOCK = [
|
|
|
268
268
|
"Recipe non-resting values — the pre-blur/pre-scale/slide-distance the recipe animates FROM (settling to blur 0 / scale 1 / translate 0). When you pick one of these recipes you MUST also emit a lane-level value patch for EACH of its values, EVEN IF the input has no such lane (add the lane so the element gains the recipe's blur/scale/distance):",
|
|
269
269
|
" - Panel reveal → filter blur 2px (Small)",
|
|
270
270
|
" - Page side-by-side → filter blur 3px (Medium) AND transform translate distance 8px, both on the sliding page",
|
|
271
|
-
" - Icon swap → filter blur 2px (Small)",
|
|
271
|
+
" - Icon swap → filter blur 2px (Small) AND transform scale 0.25 (start-scale) on the swapping icon",
|
|
272
272
|
" - Skeleton loader and reveal → filter blur 2px (Small)",
|
|
273
273
|
" - Texts reveal → filter blur 3px (Medium)",
|
|
274
274
|
" - Success check → filter blur 8px (Large)",
|
|
275
275
|
" - Modal open/close → transform scale 0.96 (Large)",
|
|
276
276
|
" - Menu dropdown → transform scale 0.97 (Medium) open, 0.99 (Tiny) close",
|
|
277
277
|
" - Tooltip open/close → transform scale 0.98 (Small)",
|
|
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
|
+
" - 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
|
+
" - 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`.",
|
|
282
|
+
" - ASYMMETRIC recipes (open slower, close faster): menu dropdown / modal / tooltip / panel reveal → open 250ms, close 150ms.",
|
|
278
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.",
|
|
279
284
|
].join("\n");
|
|
280
285
|
|
|
@@ -317,7 +322,7 @@ function buildPrompt(job) {
|
|
|
317
322
|
);
|
|
318
323
|
if (multiPhase) {
|
|
319
324
|
lines.push(
|
|
320
|
-
"RELATED PHASES: `phases` lists this transition's related states (e.g. open AND close). They are ONE motion — a recipe swap must update them TOGETHER. For the replace suggestion emit a `patches` array with ONE timing entry per phase in `phases`, shaped `{\"phase\":<phase>,\"property\":\"all\",\"durationMs\":...,\"easing\":...}`. If the recipe uses transform pre-scale, filter pre-blur or a slide distance (see \"Recipe non-resting values\" above), add EXTRA per-lane value patches for those lanes (not property:\"all\"), e.g. `{\"phase\":\"open\",\"property\":\"transform\",\"member\":\"Panel\",\"varName\":\"--dropdown-pre-scale\",\"scale\":0.97}`, `{\"phase\":\"open\",\"property\":\"filter\",\"member\":\"Panel\",\"varName\":\"--panel-blur\",\"blur\":2}` or `{\"phase\":\"forward\",\"property\":\"transform\",\"member\":\"Page panel\",\"translateVarName\":\"--page-slide-distance\",\"translate\":8}`. Copy each lane's `member` + `varName`/`translateVarName` from the input timings when that lane exists; when the recipe adds a lane the input LACKS (e.g. Page side-by-side prescribes a 3px blur but the captured page has no `filter` lane), STILL emit the value patch — reuse the sibling lane's `member` so it targets the right element and omit the var name (Apply discovers the page's backing variable itself).
|
|
325
|
+
"RELATED PHASES: `phases` lists this transition's related states (e.g. open AND close). They are ONE motion — a recipe swap must update them TOGETHER. For the replace suggestion emit a `patches` array with ONE timing entry per phase in `phases`, shaped `{\"phase\":<phase>,\"property\":\"all\",\"durationMs\":...,\"easing\":...}`. If the recipe uses transform pre-scale, filter pre-blur or a slide distance (see \"Recipe non-resting values\" above), add EXTRA per-lane value patches for those lanes (not property:\"all\"), e.g. `{\"phase\":\"open\",\"property\":\"transform\",\"member\":\"Panel\",\"varName\":\"--dropdown-pre-scale\",\"scale\":0.97}`, `{\"phase\":\"open\",\"property\":\"filter\",\"member\":\"Panel\",\"varName\":\"--panel-blur\",\"blur\":2}` or `{\"phase\":\"forward\",\"property\":\"transform\",\"member\":\"Page panel\",\"translateVarName\":\"--page-slide-distance\",\"translate\":8}`. Copy each lane's `member` + `varName`/`translateVarName` from the input timings when that lane exists; when the recipe adds a lane the input LACKS (e.g. Page side-by-side prescribes a 3px blur but the captured page has no `filter` lane), STILL emit the value patch — reuse the sibling lane's `member` so it targets the right element and omit the var name (Apply discovers the page's backing variable itself). Set each phase's `durationMs` AND `easing` from \"Recipe timing & easing\" above (SYMMETRIC recipes use the SAME duration+easing for every phase — do NOT split open/close; ASYMMETRIC recipes use open 250ms / close 150ms). The added value lanes (blur/translate) automatically inherit their phase's timing on Apply, so you don't need to repeat duration/easing on those lane-level patches. Also include a single `patch` for live preview (first timing patch is fine). Apply will write every phase from `patches`.",
|
|
321
326
|
"",
|
|
322
327
|
);
|
|
323
328
|
}
|
|
@@ -327,7 +332,7 @@ function buildPrompt(job) {
|
|
|
327
332
|
"refineType is \"replace\": suggest a WHOLE-TRANSITION replacement ONLY — do NOT propose motion-token tweaks (no kind \"duration\"/\"delay\"/\"easing\").",
|
|
328
333
|
multiPhase
|
|
329
334
|
? "Pick the SINGLE best-fit recipe from the list above. Emit ONE suggestion with kind \"replace\": include the `patches` array (one entry per related phase, as described above) AND a single `patch` (the first phase) for the live preview, add a `reference` field with the reference filename, and name the recipe in `title`/`reason`. If no recipe genuinely fits the usage, return an empty suggestions array."
|
|
330
|
-
: "Pick the SINGLE best-fit recipe from the list above. Emit ONE suggestion with kind \"replace\": set its `patch` to the
|
|
335
|
+
: "Pick the SINGLE best-fit recipe from the list above. Emit ONE suggestion with kind \"replace\": set its `patch` to the recipe's documented duration AND easing (see \"Recipe timing & easing\" — do NOT keep the source's easing when the recipe differs) on the property that already transitions (or \"all\") so Apply works live, add a `reference` field with the reference filename, and name the recipe in `title`/`reason`. If the recipe prescribes a non-resting pre-blur/pre-scale/slide-distance (see \"Recipe non-resting values\" above), put those values on the `patch` too (add `blur` for a filter-blur recipe like Page side-by-side → 3px, `scale` for a scale recipe, `translate` for a slide distance like Page side-by-side → 8px) so the sliding/scaling element gains them even when the captured transition had no such lane. If no recipe genuinely fits the usage, return an empty suggestions array.",
|
|
331
336
|
"Answer in ONE response — do NOT read or search files.",
|
|
332
337
|
);
|
|
333
338
|
} else if (refineType === "both") {
|
|
@@ -337,7 +342,7 @@ function buildPrompt(job) {
|
|
|
337
342
|
MISSING_BLUR_NOTE,
|
|
338
343
|
multiPhase
|
|
339
344
|
? "(2) Whole-transition replacement (kind \"replace\"): ALWAYS evaluate one — pick the SINGLE best-fit recipe. Emit at most ONE \"replace\" suggestion with the `patches` array (one entry per related phase) AND a single `patch` (the first phase), a `reference` field, and the recipe named in `title`/`reason`. If no recipe genuinely fits, omit the replace suggestion."
|
|
340
|
-
: "(2) Whole-transition replacement (kind \"replace\"): ALWAYS evaluate one — pick the SINGLE best-fit recipe from the list above. Emit at most ONE \"replace\" suggestion: set its `patch` to the
|
|
345
|
+
: "(2) Whole-transition replacement (kind \"replace\"): ALWAYS evaluate one — pick the SINGLE best-fit recipe from the list above. Emit at most ONE \"replace\" suggestion: set its `patch` to the recipe's documented duration AND easing (see \"Recipe timing & easing\" — do NOT keep the source's easing when the recipe differs) on the property that already transitions (or \"all\"); if the recipe prescribes a non-resting pre-blur/pre-scale/slide-distance (see \"Recipe non-resting values\" above) put those `blur`/`scale`/`translate` values on the `patch` too so e.g. Page side-by-side gains its 3px blur and 8px distance. Add a `reference` field with the reference filename, and name the recipe in `title`/`reason`. If no recipe genuinely fits, omit the replace suggestion.",
|
|
341
346
|
"Answer in ONE response — do NOT read or search files.",
|
|
342
347
|
);
|
|
343
348
|
} else {
|