Comparing version 0.2.0 to 0.3.0
import { Accessor } from 'solid-js'; | ||
import { Context } from 'solid-js'; | ||
import { CSSStyleKeys } from './types'; | ||
import { CSSStyleKeys as CSSStyleKeys_2 } from '../types'; | ||
import { JSX } from 'solid-js/jsx-runtime'; | ||
@@ -9,2 +7,6 @@ | ||
declare type CSSStyleKeys = ValueOf<{ | ||
[Key in keyof CSSStyleDeclaration]: Key extends string ? CSSStyleDeclaration[Key] extends string ? Key : never : never; | ||
}>; | ||
declare interface DOMState { | ||
@@ -31,2 +33,3 @@ rect: DOMRect; | ||
export declare interface FlipContextProps { | ||
attachedFlipIds: Accessor<Set<string>>; | ||
firstState: Accessor<Record<string, DOMState | null>>; | ||
@@ -37,5 +40,7 @@ lastState: Accessor<Record<string, DOMState | null>>; | ||
setFirstState: (id: string, state: DOMState | null) => void; | ||
recordFirstState: (id: string, element: Element, properties?: CSSStyleKeys_2[]) => void; | ||
recordFirstState: (id: string, element: Element, properties?: CSSStyleKeys[]) => void; | ||
setLastState: (id: string, state: DOMState | null) => void; | ||
recordLastState: (id: string, element: Element, properties?: CSSStyleKeys_2[]) => void; | ||
recordLastState: (id: string, element: Element, properties?: CSSStyleKeys[]) => void; | ||
attach: (id: string) => void; | ||
detach: (id: string) => void; | ||
} | ||
@@ -48,2 +53,4 @@ | ||
properties?: ArrayOr<CSSStyleKeys>; | ||
enter?: string | boolean; | ||
exit?: string | boolean; | ||
with?: ArrayOr<unknown>; | ||
@@ -85,2 +92,4 @@ children: JSX.Element; | ||
declare type ValueOf<T> = T[keyof T]; | ||
export { } |
import { createComponent, isDev } from "solid-js/web"; | ||
import { createContext, createSignal, useContext, createMemo, splitProps, mergeProps, onMount, createComputed, on, createEffect, onCleanup } from "solid-js"; | ||
import { $PROXY, createSignal, createContext, useContext, createMemo, splitProps, mergeProps, onMount, createComputed, on, createRenderEffect, createEffect, onCleanup, getOwner, runWithOwner } from "solid-js"; | ||
const dimensionToNumber = (value, parent) => { | ||
if (value.endsWith("px")) return parseFloat(value.replace("px", "")); | ||
if (value.endsWith("%")) return parent * (parseFloat(value.replace("%", "")) / 100); | ||
return 0; | ||
let result = 0; | ||
if (value.endsWith("px")) result = parseFloat(value.replace("px", "")); | ||
if (value.endsWith("%")) result = parent * (parseFloat(value.replace("%", "")) / 100); | ||
if (!Number.isFinite(result)) result = 0; | ||
return result; | ||
}; | ||
@@ -50,2 +52,95 @@ const dimensionsToNumber = (value, parent) => { | ||
}; | ||
const $RAW = Symbol("store-raw"), $NODE = Symbol("store-node"), $HAS = Symbol("store-has"), $SELF = Symbol("store-self"); | ||
function isWrappable(obj) { | ||
let proto; | ||
return obj != null && typeof obj === "object" && (obj[$PROXY] || !(proto = Object.getPrototypeOf(obj)) || proto === Object.prototype || Array.isArray(obj)); | ||
} | ||
function unwrap(item, set = /* @__PURE__ */ new Set()) { | ||
let result, unwrapped, v, prop; | ||
if (result = item != null && item[$RAW]) return result; | ||
if (!isWrappable(item) || set.has(item)) return item; | ||
if (Array.isArray(item)) { | ||
if (Object.isFrozen(item)) item = item.slice(0); | ||
else set.add(item); | ||
for (let i = 0, l = item.length; i < l; i++) { | ||
v = item[i]; | ||
if ((unwrapped = unwrap(v, set)) !== v) item[i] = unwrapped; | ||
} | ||
} else { | ||
if (Object.isFrozen(item)) item = Object.assign({}, item); | ||
else set.add(item); | ||
const keys = Object.keys(item), desc = Object.getOwnPropertyDescriptors(item); | ||
for (let i = 0, l = keys.length; i < l; i++) { | ||
prop = keys[i]; | ||
if (desc[prop].get) continue; | ||
v = item[prop]; | ||
if ((unwrapped = unwrap(v, set)) !== v) item[prop] = unwrapped; | ||
} | ||
} | ||
return item; | ||
} | ||
function getNodes(target, symbol) { | ||
let nodes = target[symbol]; | ||
if (!nodes) | ||
Object.defineProperty(target, symbol, { | ||
value: nodes = /* @__PURE__ */ Object.create(null) | ||
}); | ||
return nodes; | ||
} | ||
function getNode(nodes, property, value) { | ||
if (nodes[property]) return nodes[property]; | ||
const [s, set] = createSignal(value, { | ||
equals: false, | ||
internal: true | ||
}); | ||
s.$ = set; | ||
return nodes[property] = s; | ||
} | ||
function setProperty(state, property, value, deleting = false) { | ||
if (!deleting && state[property] === value) return; | ||
const prev = state[property], len = state.length; | ||
if (value === void 0) { | ||
delete state[property]; | ||
if (state[$HAS] && state[$HAS][property] && prev !== void 0) state[$HAS][property].$(); | ||
} else { | ||
state[property] = value; | ||
if (state[$HAS] && state[$HAS][property] && prev === void 0) state[$HAS][property].$(); | ||
} | ||
let nodes = getNodes(state, $NODE), node; | ||
if (node = getNode(nodes, property, prev)) node.$(() => value); | ||
if (Array.isArray(state) && state.length !== len) { | ||
for (let i = state.length; i < len; i++) (node = nodes[i]) && node.$(); | ||
(node = getNode(nodes, "length", len)) && node.$(state.length); | ||
} | ||
(node = nodes[$SELF]) && node.$(); | ||
} | ||
const producers = /* @__PURE__ */ new WeakMap(); | ||
const setterTraps = { | ||
get(target, property) { | ||
if (property === $RAW) return target; | ||
const value = target[property]; | ||
let proxy; | ||
return isWrappable(value) ? producers.get(value) || (producers.set(value, proxy = new Proxy(value, setterTraps)), proxy) : value; | ||
}, | ||
set(target, property, value) { | ||
setProperty(target, property, unwrap(value)); | ||
return true; | ||
}, | ||
deleteProperty(target, property) { | ||
setProperty(target, property, void 0, true); | ||
return true; | ||
} | ||
}; | ||
function produce(fn) { | ||
return (state) => { | ||
if (isWrappable(state)) { | ||
let proxy; | ||
if (!(proxy = producers.get(state))) { | ||
producers.set(state, proxy = new Proxy(state, setterTraps)); | ||
} | ||
fn(proxy); | ||
} | ||
return state; | ||
}; | ||
} | ||
const FlipContext = createContext(); | ||
@@ -56,2 +151,3 @@ const { | ||
const FlipProvider = (props) => { | ||
const [attachedFlipIds, setAttachedFlipIds] = createSignal(/* @__PURE__ */ new Set()); | ||
const [firstState, setFirstState] = createSignal({}); | ||
@@ -61,2 +157,3 @@ const [lastState, setLastState] = createSignal({}); | ||
value: { | ||
attachedFlipIds, | ||
firstState, | ||
@@ -93,2 +190,12 @@ lastState, | ||
})); | ||
}, | ||
attach: (id) => { | ||
setAttachedFlipIds(produce((prev) => { | ||
prev.add(id); | ||
})); | ||
}, | ||
detach: (id) => { | ||
setAttachedFlipIds(produce((prev) => { | ||
prev.delete(id); | ||
})); | ||
} | ||
@@ -164,11 +271,15 @@ }, | ||
const { | ||
attachedFlipIds, | ||
getFirstState, | ||
setFirstState, | ||
setLastState, | ||
recordFirstState | ||
recordFirstState, | ||
detach, | ||
attach | ||
} = context; | ||
const [animationProps, triggerProps, local] = splitProps(mergeProps({ | ||
const [animationProps, timingProps, triggerProps, local] = splitProps(mergeProps({ | ||
duration: 300, | ||
easing: "ease-in-out", | ||
with: [] | ||
}, props), ["duration", "easing", "properties"], ["with"]); | ||
}, props), ["duration", "easing", "properties"], ["enter", "exit"], ["with"]); | ||
const [unflips, setUnflips] = createSignal([]); | ||
@@ -185,2 +296,95 @@ const triggerWith = createMemo(() => { | ||
}); | ||
const enterClass = createMemo(() => { | ||
const value = timingProps.enter; | ||
if (typeof value === "string") return value; | ||
if (value === true) return "enter"; | ||
return null; | ||
}); | ||
const exitClass = createMemo(() => { | ||
const value = timingProps.exit; | ||
if (typeof value === "string") return value; | ||
if (value === true) return "exit"; | ||
return null; | ||
}); | ||
const animate = (firstState, lastState) => { | ||
if (!(result instanceof Element)) { | ||
console.warn("Flip children must be a single DOM node"); | ||
return; | ||
} | ||
const firstParentState = nested == null ? void 0 : nested.firstParentState(); | ||
const lastParentState = nested == null ? void 0 : nested.lastParentState(); | ||
let parentDeltaX = 0; | ||
let parentDeltaY = 0; | ||
let parentDeltaWidth = 1; | ||
let parentDeltaHeight = 1; | ||
if (lastParentState && firstParentState) { | ||
const parentOffsetX = (firstParentState.rect.width - lastParentState.rect.width) / 2; | ||
const parentOffsetY = (firstParentState.rect.height - lastParentState.rect.height) / 2; | ||
parentDeltaX = firstParentState.rect.left - lastParentState.rect.left + parentOffsetX; | ||
parentDeltaY = firstParentState.rect.top - lastParentState.rect.top + parentOffsetY; | ||
parentDeltaWidth = firstParentState.rect.width / lastParentState.rect.width; | ||
parentDeltaHeight = firstParentState.rect.height / lastParentState.rect.height; | ||
} | ||
const offsetX = (firstState.rect.width - lastState.rect.width) / 2; | ||
const offsetY = (firstState.rect.height - lastState.rect.height) / 2; | ||
const deltaX = -1 * parentDeltaX + firstState.rect.left - lastState.rect.left + offsetX; | ||
const deltaY = -1 * parentDeltaY + firstState.rect.top - lastState.rect.top + offsetY; | ||
const deltaWidth = firstState.rect.width / lastState.rect.width / parentDeltaWidth; | ||
const deltaHeight = firstState.rect.height / lastState.rect.height / parentDeltaHeight; | ||
const safeDeltaWidth = deltaWidth === 0 ? 1 : deltaWidth; | ||
const safeDeltaHeight = deltaHeight === 0 ? 1 : deltaHeight; | ||
const unflipStates = unflips().map((it) => captureState(it, properties())); | ||
const startKeyframe = { | ||
transformOrigin: "50% 50%", | ||
translate: `${deltaX}px ${deltaY}px`, | ||
scale: `${deltaWidth} ${deltaHeight}`, | ||
backgroundColor: firstState.color, | ||
opacity: firstState.opacity, | ||
borderTopLeftRadius: `${firstState.borderTopLeftXRadius / safeDeltaWidth}px ${firstState.borderTopLeftYRadius / safeDeltaHeight}px`, | ||
borderTopRightRadius: `${firstState.borderTopRightXRadius / safeDeltaWidth}px ${firstState.borderTopRightYRadius / safeDeltaHeight}px`, | ||
borderBottomLeftRadius: `${firstState.borderBottomLeftXRadius / safeDeltaWidth}px ${firstState.borderBottomLeftYRadius / safeDeltaHeight}px`, | ||
borderBottomRightRadius: `${firstState.borderBottomRightXRadius / safeDeltaWidth}px ${firstState.borderBottomRightYRadius / safeDeltaHeight}px` | ||
}; | ||
if (animationProps.properties) { | ||
const properties2 = Array.isArray(animationProps.properties) ? animationProps.properties : [animationProps.properties]; | ||
properties2.forEach((property) => { | ||
var _a; | ||
const value = (_a = firstState.additionalProperties) == null ? void 0 : _a[property]; | ||
if (value) startKeyframe[property] = value; | ||
else console.warn(`Property "${property}" is not found in the first state`); | ||
}); | ||
} | ||
animation = result.animate([startKeyframe, {}], { | ||
duration: animationProps.duration, | ||
easing: animationProps.easing | ||
}); | ||
const animateUnflips = unflips().map((unflip, index) => { | ||
const firstUnflipState = unflipStates[index]; | ||
const x = firstUnflipState.rect.left - lastState.rect.left; | ||
const y = firstUnflipState.rect.top - lastState.rect.top; | ||
return () => { | ||
const target = unflip; | ||
const [parentScaleX, parentScaleY] = getComputedStyle(result).scale.split(" ").map(Number); | ||
if (!Number.isFinite(parentScaleX) || !Number.isFinite(parentScaleY)) { | ||
target.style.removeProperty("scale"); | ||
target.style.removeProperty("translate"); | ||
return true; | ||
} | ||
const scaleX = 1 / parentScaleX; | ||
const scaleY = 1 / parentScaleY; | ||
const offsetX2 = firstUnflipState.rect.width * (scaleX - 1) / 2 + x * (scaleX - 1); | ||
const offsetY2 = firstUnflipState.rect.height * (scaleY - 1) / 2 + y * (scaleY - 1); | ||
target.style.setProperty("translate", `${offsetX2}px ${offsetY2}px`); | ||
target.style.setProperty("scale", `${scaleX} ${scaleY}`); | ||
return false; | ||
}; | ||
}); | ||
const animateAll = () => { | ||
const isEnd = animateUnflips.map((it) => it()); | ||
if (isEnd.every(Boolean)) return; | ||
requestAnimationFrame(animateAll); | ||
}; | ||
animateAll(); | ||
return animation; | ||
}; | ||
let result = null; | ||
@@ -193,80 +397,17 @@ let animation = null; | ||
} | ||
const firstState = getFirstState(local.id); | ||
const enterClassName = enterClass(); | ||
let firstState = getFirstState(local.id); | ||
if (!firstState && enterClassName) { | ||
result.classList.add(enterClassName); | ||
firstState = captureState(result, properties()); | ||
result.classList.remove(enterClassName); | ||
setFirstState(local.id, firstState); | ||
} | ||
if (firstState) { | ||
animation == null ? void 0 : animation.cancel(); | ||
animation = null; | ||
const afterState = captureState(result, properties()); | ||
setLastState(local.id, afterState); | ||
const lastState = captureState(result, properties()); | ||
setLastState(local.id, lastState); | ||
requestAnimationFrame(() => { | ||
if (!(result instanceof Element)) { | ||
console.warn("Flip children must be a single DOM node"); | ||
return; | ||
} | ||
const firstParentState = nested == null ? void 0 : nested.firstParentState(); | ||
const lastParentState = nested == null ? void 0 : nested.lastParentState(); | ||
let parentDeltaX = 0; | ||
let parentDeltaY = 0; | ||
let parentDeltaWidth = 1; | ||
let parentDeltaHeight = 1; | ||
if (lastParentState && firstParentState) { | ||
const parentOffsetX = (firstParentState.rect.width - lastParentState.rect.width) / 2; | ||
const parentOffsetY = (firstParentState.rect.height - lastParentState.rect.height) / 2; | ||
parentDeltaX = firstParentState.rect.left - lastParentState.rect.left + parentOffsetX; | ||
parentDeltaY = firstParentState.rect.top - lastParentState.rect.top + parentOffsetY; | ||
parentDeltaWidth = firstParentState.rect.width / lastParentState.rect.width; | ||
parentDeltaHeight = firstParentState.rect.height / lastParentState.rect.height; | ||
} | ||
const offsetX = (firstState.rect.width - afterState.rect.width) / 2; | ||
const offsetY = (firstState.rect.height - afterState.rect.height) / 2; | ||
const deltaX = -1 * parentDeltaX + firstState.rect.left - afterState.rect.left + offsetX; | ||
const deltaY = -1 * parentDeltaY + firstState.rect.top - afterState.rect.top + offsetY; | ||
const deltaWidth = firstState.rect.width / afterState.rect.width / parentDeltaWidth; | ||
const deltaHeight = firstState.rect.height / afterState.rect.height / parentDeltaHeight; | ||
const unflipStates = unflips().map((it) => captureState(it, properties())); | ||
const startKeyframe = { | ||
transformOrigin: "50% 50%", | ||
translate: `${deltaX}px ${deltaY}px`, | ||
scale: `${deltaWidth} ${deltaHeight}`, | ||
backgroundColor: firstState.color, | ||
opacity: firstState.opacity, | ||
borderTopLeftRadius: `${firstState.borderTopLeftXRadius / deltaWidth}px ${firstState.borderTopLeftYRadius / deltaHeight}px`, | ||
borderTopRightRadius: `${firstState.borderTopRightXRadius / deltaWidth}px ${firstState.borderTopRightYRadius / deltaHeight}px`, | ||
borderBottomLeftRadius: `${firstState.borderBottomLeftXRadius / deltaWidth}px ${firstState.borderBottomLeftYRadius / deltaHeight}px`, | ||
borderBottomRightRadius: `${firstState.borderBottomRightXRadius / deltaWidth}px ${firstState.borderBottomRightYRadius / deltaHeight}px` | ||
}; | ||
if (animationProps.properties) { | ||
const properties2 = Array.isArray(animationProps.properties) ? animationProps.properties : [animationProps.properties]; | ||
properties2.forEach((property) => { | ||
var _a; | ||
const value = (_a = firstState.additionalProperties) == null ? void 0 : _a[property]; | ||
if (value) startKeyframe[property] = value; | ||
else console.warn(`Property "${property}" is not found in the first state`); | ||
}); | ||
} | ||
animation = result.animate([startKeyframe, {}], { | ||
duration: animationProps.duration, | ||
easing: animationProps.easing | ||
}); | ||
unflips().forEach((unflip, index) => { | ||
const firstUnflipState = unflipStates[index]; | ||
const x = firstUnflipState.rect.left - afterState.rect.left; | ||
const y = firstUnflipState.rect.top - afterState.rect.top; | ||
const animate = () => { | ||
const target = unflip; | ||
const [parentScaleX, parentScaleY] = getComputedStyle(result).scale.split(" ").map(Number); | ||
if (!Number.isFinite(parentScaleX) || !Number.isFinite(parentScaleY)) { | ||
target.style.removeProperty("scale"); | ||
target.style.removeProperty("translate"); | ||
return; | ||
} | ||
const scaleX = 1 / parentScaleX; | ||
const scaleY = 1 / parentScaleY; | ||
const offsetX2 = firstUnflipState.rect.width * (scaleX - 1) / 2 + x * (scaleX - 1); | ||
const offsetY2 = firstUnflipState.rect.height * (scaleY - 1) / 2 + y * (scaleY - 1); | ||
target.style.setProperty("translate", `${offsetX2}px ${offsetY2}px`); | ||
target.style.setProperty("scale", `${scaleX} ${scaleY}`); | ||
requestAnimationFrame(animate); | ||
}; | ||
animate(); | ||
}); | ||
animate(firstState, lastState); | ||
}); | ||
@@ -299,2 +440,5 @@ } else { | ||
})); | ||
createRenderEffect(on(() => local.id, () => { | ||
attach(local.id); | ||
})); | ||
createEffect(on(triggerWith, () => { | ||
@@ -310,3 +454,30 @@ flip(); | ||
} | ||
recordFirstState(local.id, result, properties()); | ||
detach(local.id); | ||
const newState = captureState(result, properties()); | ||
setFirstState(local.id, newState); | ||
const owner = getOwner(); | ||
const exitClassName = exitClass(); | ||
const id = local.id; | ||
const parentElement = result.parentElement; | ||
queueMicrotask(() => { | ||
runWithOwner(owner, () => { | ||
var _a; | ||
if (exitClassName && parentElement) { | ||
if (!(result instanceof HTMLElement) && !(result instanceof SVGElement)) return; | ||
result.classList.add(exitClassName); | ||
parentElement.append(result); | ||
const lastState = captureState(result, properties()); | ||
(_a = animate(newState, lastState)) == null ? void 0 : _a.addEventListener("finish", () => { | ||
result.remove(); | ||
}); | ||
} | ||
}); | ||
}); | ||
setTimeout(() => { | ||
runWithOwner(owner, () => { | ||
const ids = attachedFlipIds(); | ||
if (ids.has(id)) return; | ||
setFirstState(id, null); | ||
}); | ||
}, 16); | ||
}); | ||
@@ -313,0 +484,0 @@ return createComponent(NestedFlipProvider, { |
@@ -1,1 +0,1 @@ | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("solid-js/web"),require("solid-js")):"function"==typeof define&&define.amd?define(["exports","solid-js/web","solid-js"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self)["solid-flip"]={},e.solid,e.solid)}(this,(function(e,t,r){"use strict";const i=(e,t)=>e.endsWith("px")?parseFloat(e.replace("px","")):e.endsWith("%")?t*(parseFloat(e.replace("%",""))/100):0,o=(e,t)=>{if(!e.includes(" ")){const r=i(e,t);return[r,r]}const[r,o]=e.split(" ");return[i(r,t),i(o,t)]},n=(e,t=[])=>{const r=getComputedStyle(e),i=e.getBoundingClientRect(),n=Math.max(i.width,i.height),s=t.reduce(((e,t)=>({...e,[t]:null==r?void 0:r[t]})),{}),a=r.scale.split(" ").map((e=>{const t=Number(e);return Number.isFinite(t)?t:1})),[l,d]=1===a.length?[a[0],a[0]]:a,[c,u]=o(r.borderTopLeftRadius,n),[p,h]=o(r.borderTopRightRadius,n),[f,g]=o(r.borderBottomLeftRadius,n),[m,b]=o(r.borderBottomRightRadius,n);return{rect:i,color:r.backgroundColor,opacity:r.opacity,position:r.position,borderTopLeftXRadius:c*l,borderTopLeftYRadius:u*d,borderTopRightXRadius:p*l,borderTopRightYRadius:h*d,borderBottomLeftXRadius:f*l,borderBottomLeftYRadius:g*d,borderBottomRightXRadius:m*l,borderBottomRightYRadius:b*d,additionalProperties:s}},s=r.createContext(),{Provider:a}=s,l=r.createContext(),d=e=>{const i=r.useContext(s);if(!i)return console.warn("Flip must be used inside a FlipProvider"),e.children;const{getFirstState:o,getLastState:n}=i,a=r.useContext(l),d=r.createMemo((()=>{const t=o(e.id);if(!t)return null;const r=null==a?void 0:a.firstParentState();return{...t,rect:DOMRect.fromRect({x:t.rect.left+((null==r?void 0:r.rect.left)??0),y:t.rect.top+((null==r?void 0:r.rect.top)??0),width:t.rect.width,height:t.rect.height})}})),c=r.createMemo((()=>{const t=n(e.id);if(!t)return null;const r=null==a?void 0:a.lastParentState();return{...t,rect:DOMRect.fromRect({x:t.rect.left+((null==r?void 0:r.rect.left)??0),y:t.rect.top+((null==r?void 0:r.rect.top)??0),width:t.rect.width,height:t.rect.height})}}));return t.createComponent(l.Provider,{get value(){return{parentId:()=>e.id,firstParentState:d,lastParentState:c,unflips:()=>e.unflips,setUnflips:e.setUnflips}},get children(){return e.children}})};e.Flip=e=>{const i=r.useContext(s),o=r.useContext(l);if(!i)return console.warn("Flip must be used inside a FlipProvider"),e.children;const{getFirstState:a,setLastState:c,recordFirstState:u}=i,[p,h,f]=r.splitProps(r.mergeProps({duration:300,easing:"ease-in-out",with:[]},e),["duration","easing","properties"],["with"]),[g,m]=r.createSignal([]),b=r.createMemo((()=>{const e=h.with;return Array.isArray(e)?e:[e]})),R=r.createMemo((()=>{const e=p.properties;return e?Array.isArray(e)?e:[e]:[]}));let y=null,w=null;const x=()=>{if(!(y instanceof Element))return void console.warn("Flip children must be a single DOM node",y);const e=a(f.id);if(e){null==w||w.cancel(),w=null;const t=n(y,R());c(f.id,t),requestAnimationFrame((()=>{if(!(y instanceof Element))return void console.warn("Flip children must be a single DOM node");const r=null==o?void 0:o.firstParentState(),i=null==o?void 0:o.lastParentState();let s=0,a=0,l=1,d=1;if(i&&r){const e=(r.rect.width-i.rect.width)/2,t=(r.rect.height-i.rect.height)/2;s=r.rect.left-i.rect.left+e,a=r.rect.top-i.rect.top+t,l=r.rect.width/i.rect.width,d=r.rect.height/i.rect.height}const c=(e.rect.width-t.rect.width)/2,u=(e.rect.height-t.rect.height)/2,h=-1*s+e.rect.left-t.rect.left+c,f=-1*a+e.rect.top-t.rect.top+u,m=e.rect.width/t.rect.width/l,b=e.rect.height/t.rect.height/d,x=g().map((e=>n(e,R()))),v={transformOrigin:"50% 50%",translate:`${h}px ${f}px`,scale:`${m} ${b}`,backgroundColor:e.color,opacity:e.opacity,borderTopLeftRadius:`${e.borderTopLeftXRadius/m}px ${e.borderTopLeftYRadius/b}px`,borderTopRightRadius:`${e.borderTopRightXRadius/m}px ${e.borderTopRightYRadius/b}px`,borderBottomLeftRadius:`${e.borderBottomLeftXRadius/m}px ${e.borderBottomLeftYRadius/b}px`,borderBottomRightRadius:`${e.borderBottomRightXRadius/m}px ${e.borderBottomRightYRadius/b}px`};if(p.properties){(Array.isArray(p.properties)?p.properties:[p.properties]).forEach((t=>{var r;const i=null==(r=e.additionalProperties)?void 0:r[t];i?v[t]=i:console.warn(`Property "${t}" is not found in the first state`)}))}w=y.animate([v,{}],{duration:p.duration,easing:p.easing}),g().forEach(((e,r)=>{const i=x[r],o=i.rect.left-t.rect.left,n=i.rect.top-t.rect.top,s=()=>{const t=e,[r,a]=getComputedStyle(y).scale.split(" ").map(Number);if(!Number.isFinite(r)||!Number.isFinite(a))return t.style.removeProperty("scale"),void t.style.removeProperty("translate");const l=1/r,d=1/a,c=i.rect.width*(l-1)/2+o*(l-1),u=i.rect.height*(d-1)/2+n*(d-1);t.style.setProperty("translate",`${c}px ${u}px`),t.style.setProperty("scale",`${l} ${d}`),requestAnimationFrame(s)};s()}))}))}else u(f.id,y,R())};return r.onMount((()=>{y instanceof Element?(t.isDev&&(y instanceof HTMLElement||y instanceof SVGElement)&&(y.dataset.flipId=f.id),y.parentElement&&x()):console.warn("Flip children must be a single DOM node")})),r.createComputed(r.on(b,(()=>{y instanceof Element?u(f.id,y,R()):console.warn("Flip children must be a single DOM node")}),{defer:!0})),r.createEffect(r.on(b,(()=>{x()}),{defer:!0})),r.onCleanup((()=>{y instanceof Element?u(f.id,y,R()):console.warn("Flip children must be a single DOM node")})),t.createComponent(d,{get id(){return f.id},get unflips(){return g()},setUnflips:m,get children(){return y=e.children}})},e.FlipContext=s,e.FlipProvider=e=>{const[i,o]=r.createSignal({}),[s,l]=r.createSignal({});return t.createComponent(a,{value:{firstState:i,lastState:s,getFirstState:e=>i()[e],getLastState:e=>s()[e],setFirstState:(e,t)=>{o((r=>({...r,[e]:t})))},recordFirstState:(e,t,r=[])=>{const i=n(t,r);0===i.rect.width&&0===i.rect.height||o((t=>({...t,[e]:i})))},setLastState:(e,t)=>{l((r=>({...r,[e]:t})))},recordLastState:(e,t,r=[])=>{const i=n(t,r);0===i.rect.width&&0===i.rect.height||l((t=>({...t,[e]:i})))}},get children(){return e.children}})},e.NestedFlipContext=l,e.NestedFlipProvider=d,e.Unflip=e=>{const t=r.useContext(l);let i=null;return r.createEffect(r.on((()=>e.id),(()=>{if(!t)return;if(!(Array.isArray(i)?i.every((e=>e instanceof Element)):i instanceof Element))return void console.warn("Unflip children must be a DOM node",i);const r=t.parentId();(e.id??r)===r&&(null==t||t.setUnflips([...t.unflips(),...Array.isArray(i)?i:[i]]))}))),i=e.children},Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})})); | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("solid-js/web"),require("solid-js")):"function"==typeof define&&define.amd?define(["exports","solid-js/web","solid-js"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self)["solid-flip"]={},e.solid,e.solid)}(this,(function(e,t,r){"use strict";const n=(e,t)=>{let r=0;return e.endsWith("px")&&(r=parseFloat(e.replace("px",""))),e.endsWith("%")&&(r=t*(parseFloat(e.replace("%",""))/100)),Number.isFinite(r)||(r=0),r},i=(e,t)=>{if(!e.includes(" ")){const r=n(e,t);return[r,r]}const[r,i]=e.split(" ");return[n(r,t),n(i,t)]},o=(e,t=[])=>{const r=getComputedStyle(e),n=e.getBoundingClientRect(),o=Math.max(n.width,n.height),s=t.reduce(((e,t)=>({...e,[t]:null==r?void 0:r[t]})),{}),a=r.scale.split(" ").map((e=>{const t=Number(e);return Number.isFinite(t)?t:1})),[l,c]=1===a.length?[a[0],a[0]]:a,[d,u]=i(r.borderTopLeftRadius,o),[p,f]=i(r.borderTopRightRadius,o),[h,g]=i(r.borderBottomLeftRadius,o),[m,b]=i(r.borderBottomRightRadius,o);return{rect:n,color:r.backgroundColor,opacity:r.opacity,position:r.position,borderTopLeftXRadius:d*l,borderTopLeftYRadius:u*c,borderTopRightXRadius:p*l,borderTopRightYRadius:f*c,borderBottomLeftXRadius:h*l,borderBottomLeftYRadius:g*c,borderBottomRightXRadius:m*l,borderBottomRightYRadius:b*c,additionalProperties:s}},s=Symbol("store-raw"),a=Symbol("store-node"),l=Symbol("store-has"),c=Symbol("store-self");function d(e){let t;return null!=e&&"object"==typeof e&&(e[r.$PROXY]||!(t=Object.getPrototypeOf(e))||t===Object.prototype||Array.isArray(e))}function u(e,t=new Set){let r,n,i,o;if(r=null!=e&&e[s])return r;if(!d(e)||t.has(e))return e;if(Array.isArray(e)){Object.isFrozen(e)?e=e.slice(0):t.add(e);for(let r=0,o=e.length;r<o;r++)i=e[r],(n=u(i,t))!==i&&(e[r]=n)}else{Object.isFrozen(e)?e=Object.assign({},e):t.add(e);const r=Object.keys(e),s=Object.getOwnPropertyDescriptors(e);for(let a=0,l=r.length;a<l;a++)o=r[a],s[o].get||(i=e[o],(n=u(i,t))!==i&&(e[o]=n))}return e}function p(e,t,n){if(e[t])return e[t];const[i,o]=r.createSignal(n,{equals:!1,internal:!0});return i.$=o,e[t]=i}function f(e,t,r,n=!1){if(!n&&e[t]===r)return;const i=e[t],o=e.length;void 0===r?(delete e[t],e[l]&&e[l][t]&&void 0!==i&&e[l][t].$()):(e[t]=r,e[l]&&e[l][t]&&void 0===i&&e[l][t].$());let s,d=function(e,t){let r=e[t];return r||Object.defineProperty(e,t,{value:r=Object.create(null)}),r}(e,a);if((s=p(d,t,i))&&s.$((()=>r)),Array.isArray(e)&&e.length!==o){for(let t=e.length;t<o;t++)(s=d[t])&&s.$();(s=p(d,"length",o))&&s.$(e.length)}(s=d[c])&&s.$()}const h=new WeakMap,g={get(e,t){if(t===s)return e;const r=e[t];let n;return d(r)?h.get(r)||(h.set(r,n=new Proxy(r,g)),n):r},set:(e,t,r)=>(f(e,t,u(r)),!0),deleteProperty:(e,t)=>(f(e,t,void 0,!0),!0)};function m(e){return t=>{if(d(t)){let r;(r=h.get(t))||h.set(t,r=new Proxy(t,g)),e(r)}return t}}const b=r.createContext(),{Provider:y}=b,R=r.createContext(),w=e=>{const n=r.useContext(b);if(!n)return console.warn("Flip must be used inside a FlipProvider"),e.children;const{getFirstState:i,getLastState:o}=n,s=r.useContext(R),a=r.createMemo((()=>{const t=i(e.id);if(!t)return null;const r=null==s?void 0:s.firstParentState();return{...t,rect:DOMRect.fromRect({x:t.rect.left+((null==r?void 0:r.rect.left)??0),y:t.rect.top+((null==r?void 0:r.rect.top)??0),width:t.rect.width,height:t.rect.height})}})),l=r.createMemo((()=>{const t=o(e.id);if(!t)return null;const r=null==s?void 0:s.lastParentState();return{...t,rect:DOMRect.fromRect({x:t.rect.left+((null==r?void 0:r.rect.left)??0),y:t.rect.top+((null==r?void 0:r.rect.top)??0),width:t.rect.width,height:t.rect.height})}}));return t.createComponent(R.Provider,{get value(){return{parentId:()=>e.id,firstParentState:a,lastParentState:l,unflips:()=>e.unflips,setUnflips:e.setUnflips}},get children(){return e.children}})};e.Flip=e=>{const n=r.useContext(b),i=r.useContext(R);if(!n)return console.warn("Flip must be used inside a FlipProvider"),e.children;const{attachedFlipIds:s,getFirstState:a,setFirstState:l,setLastState:c,recordFirstState:d,detach:u,attach:p}=n,[f,h,g,m]=r.splitProps(r.mergeProps({duration:300,easing:"ease-in-out",with:[]},e),["duration","easing","properties"],["enter","exit"],["with"]),[y,v]=r.createSignal([]),S=r.createMemo((()=>{const e=g.with;return Array.isArray(e)?e:[e]})),x=r.createMemo((()=>{const e=f.properties;return e?Array.isArray(e)?e:[e]:[]})),F=r.createMemo((()=>{const e=h.enter;return"string"==typeof e?e:!0===e?"enter":null})),P=r.createMemo((()=>{const e=h.exit;return"string"==typeof e?e:!0===e?"exit":null})),O=(e,t)=>{if(!($ instanceof Element))return void console.warn("Flip children must be a single DOM node");const r=null==i?void 0:i.firstParentState(),n=null==i?void 0:i.lastParentState();let s=0,a=0,l=1,c=1;if(n&&r){const e=(r.rect.width-n.rect.width)/2,t=(r.rect.height-n.rect.height)/2;s=r.rect.left-n.rect.left+e,a=r.rect.top-n.rect.top+t,l=r.rect.width/n.rect.width,c=r.rect.height/n.rect.height}const d=(e.rect.width-t.rect.width)/2,u=(e.rect.height-t.rect.height)/2,p=-1*s+e.rect.left-t.rect.left+d,h=-1*a+e.rect.top-t.rect.top+u,g=e.rect.width/t.rect.width/l,m=e.rect.height/t.rect.height/c,b=0===g?1:g,R=0===m?1:m,w=y().map((e=>o(e,x()))),v={transformOrigin:"50% 50%",translate:`${p}px ${h}px`,scale:`${g} ${m}`,backgroundColor:e.color,opacity:e.opacity,borderTopLeftRadius:`${e.borderTopLeftXRadius/b}px ${e.borderTopLeftYRadius/R}px`,borderTopRightRadius:`${e.borderTopRightXRadius/b}px ${e.borderTopRightYRadius/R}px`,borderBottomLeftRadius:`${e.borderBottomLeftXRadius/b}px ${e.borderBottomLeftYRadius/R}px`,borderBottomRightRadius:`${e.borderBottomRightXRadius/b}px ${e.borderBottomRightYRadius/R}px`};if(f.properties){(Array.isArray(f.properties)?f.properties:[f.properties]).forEach((t=>{var r;const n=null==(r=e.additionalProperties)?void 0:r[t];n?v[t]=n:console.warn(`Property "${t}" is not found in the first state`)}))}L=$.animate([v,{}],{duration:f.duration,easing:f.easing});const S=y().map(((e,r)=>{const n=w[r],i=n.rect.left-t.rect.left,o=n.rect.top-t.rect.top;return()=>{const t=e,[r,s]=getComputedStyle($).scale.split(" ").map(Number);if(!Number.isFinite(r)||!Number.isFinite(s))return t.style.removeProperty("scale"),t.style.removeProperty("translate"),!0;const a=1/r,l=1/s,c=n.rect.width*(a-1)/2+i*(a-1),d=n.rect.height*(l-1)/2+o*(l-1);return t.style.setProperty("translate",`${c}px ${d}px`),t.style.setProperty("scale",`${a} ${l}`),!1}})),F=()=>{S.map((e=>e())).every(Boolean)||requestAnimationFrame(F)};return F(),L};let $=null,L=null;const M=()=>{if(!($ instanceof Element))return void console.warn("Flip children must be a single DOM node",$);const e=F();let t=a(m.id);if(!t&&e&&($.classList.add(e),t=o($,x()),$.classList.remove(e),l(m.id,t)),t){null==L||L.cancel(),L=null;const e=o($,x());c(m.id,e),requestAnimationFrame((()=>{O(t,e)}))}else d(m.id,$,x())};return r.onMount((()=>{$ instanceof Element?(t.isDev&&($ instanceof HTMLElement||$ instanceof SVGElement)&&($.dataset.flipId=m.id),$.parentElement&&M()):console.warn("Flip children must be a single DOM node")})),r.createComputed(r.on(S,(()=>{$ instanceof Element?d(m.id,$,x()):console.warn("Flip children must be a single DOM node")}),{defer:!0})),r.createRenderEffect(r.on((()=>m.id),(()=>{p(m.id)}))),r.createEffect(r.on(S,(()=>{M()}),{defer:!0})),r.onCleanup((()=>{if(!($ instanceof Element))return void console.warn("Flip children must be a single DOM node");u(m.id);const e=o($,x());l(m.id,e);const t=r.getOwner(),n=P(),i=m.id,a=$.parentElement;queueMicrotask((()=>{r.runWithOwner(t,(()=>{var t;if(n&&a){if(!($ instanceof HTMLElement||$ instanceof SVGElement))return;$.classList.add(n),a.append($);const r=o($,x());null==(t=O(e,r))||t.addEventListener("finish",(()=>{$.remove()}))}}))})),setTimeout((()=>{r.runWithOwner(t,(()=>{s().has(i)||l(i,null)}))}),16)})),t.createComponent(w,{get id(){return m.id},get unflips(){return y()},setUnflips:v,get children(){return $=e.children}})},e.FlipContext=b,e.FlipProvider=e=>{const[n,i]=r.createSignal(new Set),[s,a]=r.createSignal({}),[l,c]=r.createSignal({});return t.createComponent(y,{value:{attachedFlipIds:n,firstState:s,lastState:l,getFirstState:e=>s()[e],getLastState:e=>l()[e],setFirstState:(e,t)=>{a((r=>({...r,[e]:t})))},recordFirstState:(e,t,r=[])=>{const n=o(t,r);0===n.rect.width&&0===n.rect.height||a((t=>({...t,[e]:n})))},setLastState:(e,t)=>{c((r=>({...r,[e]:t})))},recordLastState:(e,t,r=[])=>{const n=o(t,r);0===n.rect.width&&0===n.rect.height||c((t=>({...t,[e]:n})))},attach:e=>{i(m((t=>{t.add(e)})))},detach:e=>{i(m((t=>{t.delete(e)})))}},get children(){return e.children}})},e.NestedFlipContext=R,e.NestedFlipProvider=w,e.Unflip=e=>{const t=r.useContext(R);let n=null;return r.createEffect(r.on((()=>e.id),(()=>{if(!t)return;if(!(Array.isArray(n)?n.every((e=>e instanceof Element)):n instanceof Element))return void console.warn("Unflip children must be a DOM node",n);const r=t.parentId();(e.id??r)===r&&(null==t||t.setUnflips([...t.unflips(),...Array.isArray(n)?n:[n]]))}))),n=e.children},Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})})); |
{ | ||
"name": "solid-flip", | ||
"version": "0.2.0", | ||
"version": "0.3.0", | ||
"description": "The most advanced Flip animation library for Solid.js inspired by react-flip-toolkit", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.umd.js", |
@@ -11,2 +11,8 @@ # Solid-flip | ||
## Roadmap | ||
- [X] add `enter`, `exit` properties in `Flip` | ||
- [ ] add `onEnter`, `onExit` properties in `Flip` | ||
- [ ] spring-based animation config | ||
- [ ] physics-based animation config | ||
# Installation | ||
@@ -74,7 +80,7 @@ ```bash | ||
# Example | ||
Please check the [example](./example) folder or ~~codesandbox for more details.~~ (TODO) | ||
Please check the [example](./example) folder or [codesandbox](https://codesandbox.io/p/devbox/solid-flip-example-jxqn29?embed=1&file=%2Fsrc%2FApp.tsx) for more details. | ||
## Flip | ||
https://github.com/user-attachments/assets/69367673-8edc-4c7d-816c-59a15743b05d | ||
<video width="100%" height="auto" src="https://github.com/user-attachments/assets/69367673-8edc-4c7d-816c-59a15743b05d"></video> | ||
@@ -95,3 +101,3 @@ ```tsx | ||
https://github.com/user-attachments/assets/a6b4f260-f76a-4ce6-b5a9-448697607a3b | ||
<video width="100%" height="auto" src="https://github.com/user-attachments/assets/a6b4f260-f76a-4ce6-b5a9-448697607a3b"></video> | ||
@@ -114,3 +120,3 @@ ```tsx | ||
https://github.com/user-attachments/assets/0547a512-7032-4cce-940f-512de78538ef | ||
<video width="100%" height="auto" src="https://github.com/user-attachments/assets/0547a512-7032-4cce-940f-512de78538ef"></video> | ||
@@ -147,9 +153,11 @@ ```tsx | ||
| Property | Type | Default | Description | | ||
|------------|------------------------|--------------|----------------------------------------------------------------| | ||
| id | `string` | _(required)_ | The unique id of the flip component | | ||
| with | `unknown \| unknown[]` | [] | The condition to determine when the content should be animated | | ||
| duration | `number` | 300 | The duration of the animation | | ||
| easing | `string` | 'ease' | The easing of the animation | | ||
| properties | `string \| string[]` | [] | The additional properties that will be animated | | ||
| Property | Type | Default | Description | | ||
|------------|------------------------|--------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | ||
| id* | `string` | _(required)_ | The unique id of the flip component | | ||
| with | `unknown \| unknown[]` | [] | The condition to determine when the content should be animated | | ||
| duration | `number` | 300 | The duration of the animation | | ||
| easing | `string` | 'ease' | The easing of the animation | | ||
| properties | `string \| string[]` | [] | The additional properties that will be animated | | ||
| enter | `string \| boolean` | false | If this value is truthy, The element will be animate when the element is inserted. The value means the initial class name of the element. If you set this value as `true`, entering class name will be set as `enter` | | ||
| exit | `string \| boolean` | false | If this value is truthy, The element will be animate when the element is removed. The value means the class name of the element after element removed. If you set this value as `true`, exiting class name will be set as `exit` | | ||
@@ -156,0 +164,0 @@ ## Unflip |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
39921
620
165