-
Notifications
You must be signed in to change notification settings - Fork 2
getCSS - first implementation #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Changes from 13 commits
Commits
Show all changes
89 commits
Select commit
Hold shift + click to select a range
6ed31bc
getCSS - first implementation
ameerf-wix c8102db
fix build and lint
ameerf-wix 094cbf7
Merge branch 'master' of github.com:wix-incubator/interact into get_css
ameerf-wix f111288
gitignore
ameerf-wix 9f34f35
wip
ameerf-wix 36fd96e
Merge branch 'master' of github.com:wix-incubator/interact into get_css
ameerf-wix b117e78
wip
ameerf-wix 72b6f90
Merge branch 'master' of github.com:wix-incubator/interact into get_css
ameerf-wix 8a0c428
wip
ameerf-wix c030958
wip
ameerf-wix f6917c1
wip
ameerf-wix e084981
fixing conditions cascading logic and transitions
ameerf-wix 38f5df3
updated tests
ameerf-wix 1085c50
Merge branch 'master' of github.com:wix-incubator/interact into get_css
ameerf-wix 005a624
PR comments
ameerf-wix dc008a6
Merge branch 'master' into get_css
ameerabuf f280704
Merge branch 'get_css' of github.com:wix-incubator/interact into get_css
ameerf-wix 54208db
Merge branch 'master' of github.com:wix-incubator/interact into get_css
ameerf-wix a41f451
PR fixes
ameerf-wix 29abbe8
PR fixes
ameerf-wix bb114f4
infra changes
ameerf-wix 1dfe84b
docs and demo - v1
ameerf-wix a7d644d
docs and demo - v1
ameerf-wix 39eca8e
Merge branch 'master' of github.com:wix-incubator/interact into get_css
ameerf-wix fe20333
pr fixes
ameerf-wix fe89e7c
Merge branch 'master' of github.com:wix-incubator/interact into get_css
ameerf-wix 58c50d4
Merge branch 'master' of github.com:wix-incubator/interact into get_css
ameerf-wix e51a7bc
yarn format
ameerf-wix 85bf50d
Merge branch 'master' of github.com:wix-incubator/interact into get_css
ameerf-wix e3b7582
wip
ameerf-wix db95a87
Merge branch 'master' of github.com:wix-incubator/interact into get_css
ameerf-wix 3316716
wip
ameerf-wix 49eb8de
wip
ameerf-wix fbb2500
Merge branch 'master' of github.com:wix-incubator/interact into get_css
ameerf-wix c848200
wip
ameerf-wix 82d3a97
wip
ameerf-wix 41ac484
wip
ameerf-wix 57f5a46
wip
ameerf-wix 3860e67
Merge branch 'master' of github.com:wix-incubator/interact into get_css
ameerf-wix c5c90e3
wip
ameerf-wix 1433546
wip
ameerf-wix a190b06
wip
ameerf-wix dbe9dce
wip
ameerf-wix e7bb50b
wip
ameerf-wix a5f791f
wip
ameerf-wix 73c2293
wip
ameerf-wix 6c6693d
Merge branch 'master' of github.com:wix-incubator/interact into get_css
ameerf-wix ca228bd
wip
ameerf-wix 859c9b1
wip
ameerf-wix f2a1701
wip
ameerf-wix 222de0e
wip
ameerf-wix bace05a
wip
ameerf-wix f696caf
wip
ameerf-wix 14654f9
wip
ameerf-wix 514c61e
wip
ameerf-wix 4d52382
Merge branch 'master' into get_css
ameerabuf 04b3822
Merge branch 'get_css' of github.com:wix-incubator/interact into get_css
ameerf-wix b7e8f5e
wip
ameerf-wix 5d1c6ce
Merge branch 'master' of github.com:wix-incubator/interact into get_css
ameerf-wix b857beb
some PR fixes
ameerf-wix 474dc38
some PR fixes
ameerf-wix d2b3ad9
some PR fixes
ameerf-wix f7787ef
some PR fixes
ameerf-wix be23525
some PR fixes
ameerf-wix 27c6ae8
some PR fixes
ameerf-wix b4cb3f0
some PR fixes
ameerf-wix 9cfca48
some PR fixes
ameerf-wix dc02e70
Merge branch 'master' into get_css
ameerabuf 154ddf8
Merge branch 'get_css' of github.com:wix-incubator/interact into get_css
ameerf-wix c540916
Merge branch 'master' of github.com:wix-incubator/interact into get_css
ameerf-wix 810910c
removing redundant logic
ameerf-wix f501a2f
removing redundant logic
ameerf-wix f78b145
removing redundant logic
ameerf-wix 0515019
Merge branch 'master' into get_css
ameerabuf 34a7a7c
Merge branch 'get_css' of github.com:wix-incubator/interact into get_css
ameerf-wix ff98474
Merge branch 'master' of github.com:wix-incubator/interact into get_css
ameerf-wix 8a47be7
PR fixes
ameerf-wix 9314a3d
PR fixes
ameerf-wix 78f803e
PR fixes
ameerf-wix ad28114
Merge branch 'master' into get_css
ameerabuf f7cac5a
Merge branch 'get_css' of github.com:wix-incubator/interact into get_css
ameerf-wix dacdf88
PR fixes
ameerf-wix 33d9cc3
PR comments
ameerf-wix 92412a1
PR comments
ameerf-wix 8e27101
Merge branch 'master' of github.com:wix-incubator/interact into get_css
ameerf-wix da25114
PR comments
ameerf-wix 923e7cf
PR comments
ameerf-wix 01523dd
PR comments
ameerf-wix 4a4f5dc
PR comments
ameerf-wix File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,17 +1,350 @@ | ||
| import { InteractConfig } from '../types'; | ||
|
|
||
| export function generate(_config: InteractConfig): string { | ||
| const css: string[] = [ | ||
| `@media (prefers-reduced-motion: no-preference) { | ||
| [data-interact-initial="true"] > :first-child:not([data-motion-enter="done"]) { | ||
| visibility: hidden; | ||
| transform: none; | ||
| translate: none; | ||
| scale: none; | ||
| rotate: none; | ||
| import type { | ||
| InteractConfig, | ||
| GetCSSResult, | ||
| Effect, | ||
| EffectRef, | ||
| TimeEffect, | ||
| TransitionEffect, | ||
| CreateTransitionCSSParams, | ||
| Interaction, | ||
| Condition, | ||
| } from '../types'; | ||
| import { | ||
| createStateRuleAndCSSTransitions, | ||
| applySelectorCondition, | ||
| generateId, | ||
| isTimeTrigger, | ||
| shortestRepeatingPatternLength, | ||
| getFullPredicateByType, | ||
| getSelectorCondition | ||
| } from '../utils'; | ||
| import { getSelector } from './Interact'; | ||
| import { keyframesToCSS } from './utilities'; | ||
| import { effectToAnimationOptions } from '../handlers/utilities'; | ||
| import { getCSSAnimation, MotionKeyframeEffect } from '@wix/motion'; | ||
|
|
||
| const DEFAULT_INITIAL = { | ||
| visibility: 'hidden', | ||
| transform: 'none', | ||
| translate: 'none', | ||
| scale: 'none', | ||
| rotate: 'none', | ||
| }; | ||
|
|
||
| interface AnimationProps { | ||
| animation: string, | ||
| composition: CompositeOperation, | ||
| custom: Keyframe, | ||
| conditions: string[], | ||
| animationCustomPropName: string, | ||
| } | ||
|
|
||
| interface TransitionProps { | ||
| transition: string, | ||
| conditions: string[], | ||
| transitionCustomPropName: string, | ||
| } | ||
| interface CSSAnimationResult { | ||
| animation: string, | ||
| composition: CompositeOperation, | ||
| custom: Keyframe, | ||
| name: string, | ||
| keyframes: Keyframe[], | ||
| } | ||
|
|
||
| interface CSSTransitionResult { | ||
| stateRule: string, | ||
| transitions: string[], | ||
| } | ||
|
|
||
| function getTransitionData( | ||
| effect: Effect & { key: string }, | ||
| childSelector: string | ||
| ): CSSTransitionResult { | ||
| const args: CreateTransitionCSSParams = { | ||
| key: effect.key, | ||
| effectId: (effect as Effect).effectId!, | ||
| transition: (effect as TransitionEffect).transition, | ||
| properties: (effect as TransitionEffect).transitionProperties, | ||
| childSelector, | ||
| }; | ||
| return createStateRuleAndCSSTransitions(args); | ||
| } | ||
|
|
||
| function getAnimationData(effect: Effect): CSSAnimationResult[] { | ||
| const animationOptions = effectToAnimationOptions(effect as TimeEffect); | ||
|
|
||
| const cssAnimations = getCSSAnimation(null, animationOptions); | ||
|
|
||
| return cssAnimations | ||
| .filter((anim) => anim.name) | ||
| .map((anim) => ({ | ||
| animation: anim.animation, | ||
| composition: anim.composition || 'replace', | ||
| custom: anim.custom || {}, | ||
| name: anim.name as string, | ||
| keyframes: anim.keyframes, | ||
| })); | ||
| } | ||
|
|
||
| function resolveEffect( | ||
| effectRef: Effect | EffectRef, | ||
| effectsMap: Record<string, Effect>, | ||
| interaction: Interaction, | ||
| conditionDefinitions: Record<string, Condition>, | ||
| ): (Effect & { key: string }) | null { | ||
| const fullEffect: any = effectRef.effectId | ||
| ? { ...effectsMap[effectRef.effectId], ...effectRef } | ||
| : { ...effectRef }; | ||
|
|
||
| if (fullEffect.namedEffect || fullEffect.keyframeEffect || fullEffect.transition) { | ||
|
ameerabuf marked this conversation as resolved.
Outdated
|
||
| if (!fullEffect.key) { | ||
| fullEffect.key = interaction.key; | ||
| } | ||
|
|
||
| if (interaction.conditions && interaction.conditions.length) { | ||
| fullEffect.conditions = [...new Set(...interaction.conditions, ...(fullEffect.conditions || []))] | ||
| .filter((condition) => conditionDefinitions[condition]); | ||
| } | ||
|
|
||
| const { keyframeEffect } = fullEffect; | ||
| if (keyframeEffect && !keyframeEffect.name) { | ||
| keyframeEffect.name = (effectRef as TimeEffect & { keyframeEffect: MotionKeyframeEffect }).keyframeEffect ? | ||
| generateId() : effectRef.effectId; | ||
|
ameerabuf marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| fullEffect.initial = fullEffect.initial === 'disable' ? | ||
|
ameerabuf marked this conversation as resolved.
Outdated
|
||
| undefined : (fullEffect.initial || DEFAULT_INITIAL); | ||
|
|
||
| return fullEffect; | ||
| } | ||
|
|
||
| return null; | ||
|
ameerabuf marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| function buildCascadingTransitionCustomPropRule( | ||
| selector: string, | ||
| props: TransitionProps, | ||
| configConditions: Record<string, Condition> | ||
| ) { | ||
| const { transitionCustomPropName, transition, conditions } = props; | ||
|
|
||
| const declaration: string = `${transitionCustomPropName}: ${transition};`; | ||
|
|
||
| const selectorCondition = getSelectorCondition(conditions, configConditions); | ||
| const targetSelector = selectorCondition ? | ||
| applySelectorCondition(selector, selectorCondition) : selector; | ||
|
|
||
| let rule = `${targetSelector} { ${declaration} }`; | ||
|
|
||
| ['container' as const, 'media' as const].forEach((type) => { | ||
| const predicate = getFullPredicateByType(conditions, configConditions, type); | ||
| if (predicate) { | ||
| rule = `@${type} ${predicate} { ${rule} }`; | ||
| } | ||
| }); | ||
|
|
||
| return rule; | ||
| } | ||
|
|
||
| function buildCascadingAnimationCustomPropRule( | ||
|
ameerabuf marked this conversation as resolved.
Outdated
|
||
| selector: string, | ||
| props: AnimationProps, | ||
| configConditions: Record<string, Condition> | ||
| ) { | ||
| const declarations: string[] = []; | ||
| const { animationCustomPropName, animation, custom, conditions } = props; | ||
|
|
||
| const propsToApply = { [animationCustomPropName]: animation, ...custom }; | ||
| for (const [key, val] of Object.entries(propsToApply)) { | ||
| if (val !== undefined && val !== null) { | ||
| declarations.push(`${key}: ${val};`); | ||
| } | ||
| } | ||
|
|
||
| const selectorCondition = getSelectorCondition(conditions, configConditions); | ||
| const targetSelector = selectorCondition ? | ||
| applySelectorCondition(selector, selectorCondition) : selector; | ||
|
|
||
| let rule = `${targetSelector} { ${declarations.join(' ')} }`; | ||
|
ameerabuf marked this conversation as resolved.
Outdated
|
||
|
|
||
| ['container' as const, 'media' as const].forEach((type) => { | ||
| const predicate = getFullPredicateByType(conditions, configConditions, type); | ||
| if (predicate) { | ||
| rule = `@${type} ${predicate} { ${rule} }`; | ||
| } | ||
| }); | ||
|
|
||
| return rule; | ||
| } | ||
|
|
||
| function buildTransitionRule( | ||
| selector: string, | ||
| propsArray: TransitionProps[], | ||
| ): string { | ||
| const declarations: string[] = []; | ||
|
|
||
| const transitions = propsArray.map((props) => `var(${props.transitionCustomPropName}, _)`); | ||
| declarations.push(`transition: ${transitions.join(', ')};`); | ||
|
|
||
| return `${selector} { ${declarations.join(' ')} }`; | ||
|
ameerabuf marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| function buildAnimationRule( | ||
| selector: string, | ||
| propsArray: AnimationProps[], | ||
| ): string { | ||
| const declarations: string[] = []; | ||
|
|
||
| const animations = propsArray.map((props) => `var(${props.animationCustomPropName}, none)`); | ||
| declarations.push(`animation: ${animations.join(', ')};`); | ||
|
|
||
| let compositions = propsArray.map((props) => props.composition); | ||
| const compositionRepeatLength = shortestRepeatingPatternLength(compositions); | ||
| compositions = compositions.slice(0, compositionRepeatLength); | ||
|
|
||
| if (compositions.length === 1 && compositions[0] === 'replace') { | ||
| compositions = []; | ||
| } | ||
| }` | ||
| ]; | ||
|
|
||
| declarations.push(`animation-composition: ${compositions.join(', ')};`); | ||
|
|
||
| return `${selector} { ${declarations.join(' ')} }`; | ||
| } | ||
|
|
||
| /** | ||
| * Generates CSS for time-based animations from an InteractConfig. | ||
| * | ||
| * @param config - The interact configuration containing effects and interactions | ||
| * @returns GetCSSResult with keyframes and animationRules | ||
| */ | ||
| export function getCSS(config: InteractConfig): GetCSSResult { | ||
|
ameerabuf marked this conversation as resolved.
Outdated
|
||
| const keyframeMap = new Map<string, string>(); | ||
| const selectorTransitionPropsMap = new Map< | ||
| string, | ||
| TransitionProps[] | ||
| >(); | ||
| const selectorAnimationPropsMap = new Map< | ||
| string, | ||
| AnimationProps[] | ||
| >(); | ||
| const transitionRules: string[] = []; | ||
|
|
||
| const configConditions = config.conditions || {}; | ||
|
|
||
| for (const interaction of config.interactions) { | ||
| if (!isTimeTrigger(interaction.trigger)) { | ||
| continue; | ||
| } | ||
|
|
||
| for (const effectRef of interaction.effects) { | ||
| const effect = resolveEffect(effectRef, config.effects, interaction, configConditions); | ||
| if (!effect) continue; | ||
|
ameerabuf marked this conversation as resolved.
Outdated
|
||
|
|
||
| const childSelector = getSelector(effect, { | ||
|
ameerabuf marked this conversation as resolved.
Outdated
|
||
| asCombinator: true, // TODO: (ameerf) - correct? | ||
| addItemFilter: Boolean(effect.listItemSelector), // TODO: (ameerf) - correct? | ||
|
ameerabuf marked this conversation as resolved.
Outdated
|
||
| }); | ||
|
|
||
| const escapedKey = CSS.escape(effect.key); | ||
| const keyWithNoSpecialChars = effect.key.replace(/[^a-zA-Z0-9_-]/g, ''); | ||
|
ameerabuf marked this conversation as resolved.
Outdated
|
||
| const selector = `[data-interact-key="${escapedKey}"] ${childSelector}`; | ||
| const conditions = (effect.conditions || []); | ||
|
|
||
| if ( | ||
|
ameerabuf marked this conversation as resolved.
Outdated
|
||
| (effect as TransitionEffect).transition || | ||
| (effect as TransitionEffect).transitionProperties | ||
| ) { | ||
| const {stateRule, transitions} = getTransitionData(effect, childSelector); | ||
|
ameerabuf marked this conversation as resolved.
Outdated
|
||
| transitionRules.push(stateRule); | ||
| if (transitions.length === 0) { | ||
| continue; | ||
| } | ||
|
ydaniv marked this conversation as resolved.
Outdated
|
||
|
|
||
| if (!selectorTransitionPropsMap.has(selector)) { | ||
| selectorTransitionPropsMap.set(selector, []); | ||
| } | ||
| const transitionPropsArray = selectorTransitionPropsMap.get(selector)!; | ||
|
|
||
| for (const transition of transitions) { | ||
| const transitionCustomPropName = `--trans-def-${keyWithNoSpecialChars}-${transitionPropsArray.length}`; | ||
| transitionPropsArray.push({ | ||
| transition, | ||
| conditions, | ||
| transitionCustomPropName | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| if ( | ||
|
ameerabuf marked this conversation as resolved.
Outdated
|
||
| (effect as any).namedEffect || | ||
| (effect as any).keyframeEffect | ||
| ) { | ||
| const animationDataList = getAnimationData(effect); | ||
| if (animationDataList.length === 0) { | ||
| continue; | ||
| } | ||
|
|
||
| if (!selectorAnimationPropsMap.has(selector)) { | ||
| selectorAnimationPropsMap.set(selector, []); | ||
| } | ||
| const animationPropsArray = selectorAnimationPropsMap.get(selector)!; | ||
|
|
||
| for (const data of animationDataList) { | ||
| const keyframeCSS = keyframesToCSS(data.name, data.keyframes, effect.initial); | ||
| if (keyframeCSS) { | ||
| keyframeMap.set(data.name, keyframeCSS); | ||
| } | ||
|
|
||
| const { animation, composition, custom } = data; | ||
| const animationCustomPropName = `--anim-def-${keyWithNoSpecialChars}-${animationPropsArray.length}`; | ||
|
|
||
| animationPropsArray.push({ | ||
| animation, | ||
| composition, | ||
| custom, | ||
| conditions, | ||
| animationCustomPropName | ||
| }); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| for (const [baseSelector, transitionPropsArray] of selectorTransitionPropsMap) { | ||
| transitionPropsArray.forEach((transitionProps) => { | ||
| transitionRules.push(buildCascadingTransitionCustomPropRule( | ||
| baseSelector, | ||
| transitionProps, | ||
| configConditions | ||
| )); | ||
| }); | ||
|
|
||
| transitionRules.push(buildTransitionRule(baseSelector, transitionPropsArray)); | ||
| } | ||
|
|
||
| const animationRules: string[] = []; | ||
| for (const [baseSelector, animationPropsArray] of selectorAnimationPropsMap) { | ||
| animationPropsArray.forEach((animationProps) => { | ||
| animationRules.push(buildCascadingAnimationCustomPropRule( | ||
| baseSelector, | ||
| animationProps, | ||
| configConditions | ||
| )); | ||
| }); | ||
|
|
||
| animationRules.push(buildAnimationRule(baseSelector, animationPropsArray)); | ||
| } | ||
|
|
||
| return { | ||
| keyframes: Array.from(keyframeMap.values()), | ||
| animationRules, | ||
| transitionRules | ||
| }; | ||
| } | ||
|
|
||
| export function generate(config: InteractConfig): string { | ||
| const { keyframes, animationRules, transitionRules } = getCSS(config); | ||
| const css: string[] = [...keyframes, ...animationRules, ...transitionRules]; | ||
| return css.join('\n'); | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.