Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
583 changes: 269 additions & 314 deletions packages/interact/src/core/css.ts

Large diffs are not rendered by default.

158 changes: 123 additions & 35 deletions packages/interact/src/core/cssUtils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
import type {
Condition,
ListPropertyName,
ListCustomProps,
CSSCoordinatedLists,
CSSRuleData,
} from '../types';
import type { Condition, ListPropertyName, CSSRuleData } from '../types';
import { toCSSPropertyName } from '@wix/motion';
import {
roundNumber,
Expand Down Expand Up @@ -85,8 +79,8 @@ export function keyframeObjectToKeyframeCSS(keyframeObj: Keyframe, percentage: n
const cssKey = keyframePropertyToCSS(key);
return `${cssKey}: ${value};`;
})
.join('\n');
return `${percentage}% {\n${properties}\n}`;
.join('\n ');
return `${percentage}% {\n ${properties}\n }`;
}

export function keyframesToCSS(name: string, keyframes: Keyframe[]): string {
Expand All @@ -102,9 +96,9 @@ export function keyframesToCSS(name: string, keyframes: Keyframe[]): string {

return keyframeObjectToKeyframeCSS(kf, percentage);
})
.join('\n');
.join('\n ');

return `@keyframes ${name} {\n${keyframeBlocks}\n}`;
return `@keyframes ${name} {\n ${keyframeBlocks}\n}`;
}

export function CSSRuleToString(rule: CSSRuleData): string {
Expand Down Expand Up @@ -141,44 +135,138 @@ export function CSSRuleToString(rule: CSSRuleData): string {

const declarationsStr = declarations
.map(({ name, value, important }) => `${name}: ${value}${important ? ' !important' : ''};`)
.join('\n');
const cssRule = `${selector} {\n${declarationsStr}\n}`;
.join('\n ');
const cssRule = `${selector} {\n ${declarationsStr}\n}`;

return media ? `@media ${media} {\n${cssRule}\n}` : cssRule;
}

export function buildListsRule(
lists: CSSCoordinatedLists,
customProps?: ListCustomProps,
export const LIST_ANIMATION_PROPERTY_NAMES = [
'animation',
'animation-composition',
'animation-timeline',
'animation-range',
] as const satisfies readonly ListPropertyName[];
export const LIST_PROPERTY_NAMES = [
...LIST_ANIMATION_PROPERTY_NAMES,
'transition',
] as const satisfies readonly ListPropertyName[];
export const LIST_KINDS = ['animation', 'transition'] as const;
export type ListKind = (typeof LIST_KINDS)[number];
export type ListSlots = {
listIndex: number;
slotCursor: number;
slotsInSequence: number;
};

export function listKind(name: ListPropertyName): ListKind {
return name === 'transition' ? 'transition' : 'animation';
}

export const LIST_PROPERTY_FALLBACKS: Record<ListPropertyName, string> = {
transition: '_',
animation: 'none',
'animation-composition': 'replace',
'animation-timeline': 'auto',
'animation-range': 'normal',
};

// TODO: maybe add `-intrct` to names? --anm-0 or --trns-0 could collide with user-defined names
export function getCustomPropName(name: string, index: number, isSlot: boolean = false): string {
return `--${name.replace(/(?<!(^|-))([aeiou]|tion)/g, '')}${isSlot ? '-slot' : ''}-${index}`;
}

export function buildAtPropertyRules(
animationLength: number,
transitionLength: number,
animationSlotLength: number,
transitionSlotLength: number,
): string[] {
return LIST_PROPERTY_NAMES.flatMap((name) => [
...Array.from(
{ length: name === 'transition' ? transitionLength : animationLength },
(_, i) =>
`@property ${getCustomPropName(name, i)} { syntax: "*"; inherits: false; initial-value: ${LIST_PROPERTY_FALLBACKS[name]}; }`,
),
...Array.from(
{ length: name === 'transition' ? transitionSlotLength : animationSlotLength },
(_, i) =>
`@property ${getCustomPropName(name, i, true)} { syntax: "*"; inherits: false; initial-value: ${LIST_PROPERTY_FALLBACKS[name]}; }`,
),
]);
}

export function buildSequenceListsRule(
target: {
key: string;
childSelector?: string;
animation: ListSlots;
transition: ListSlots;
},
conditions?: string[],
configConditions?: Record<string, Condition>,
): CSSRuleData {
const { key, childSelector, properties } = lists;
): CSSRuleData | null {
const propertyNames = LIST_PROPERTY_NAMES.filter(
(name) => target[listKind(name)].slotsInSequence > 0,
);
if (propertyNames.length === 0) {
return null;
}

const declarations = Object.entries(properties)
.filter(
(entry: [string, { fallback: string; varNames: string[] }]) =>
entry[1] && entry[1].varNames.length,
)
.map(([name, { fallback, varNames }]) => ({
name,
value: varNames.map((n) => `var(${n}, ${fallback})`).join(', '),
}));
const declarations = propertyNames.map((name) => {
const { listIndex, slotCursor, slotsInSequence } = target[listKind(name)];

const rule: CSSRuleData = { key, childSelector, declarations };
return {
name: getCustomPropName(name, listIndex),
value: Array.from(
{ length: slotsInSequence },
(_, i) => `var(${getCustomPropName(name, slotCursor + i, true)})`,
).join(', '),
};
});

// option to assign into custom-properties instead of directly into the actual css properties
if (customProps) {
rule.declarations.forEach((declaration) => {
declaration.name = customProps[declaration.name as ListPropertyName];
});
}
const rule: CSSRuleData = { key: target.key, childSelector: target.childSelector, declarations };

// option to add conditions to the rules
if (conditions) {
rule.media = getFullPredicateByType(conditions, configConditions || {}, 'media');
rule.selectorCondition = getSelectorCondition(conditions, configConditions || {});
}

return rule;
}

export function buildListsRule(
targets: { key: string; childSelector?: string }[],
animationLength: number,
transitionLength: number,
): string {
if (targets.length === 0) {
return '';
}

const propertyNames = [
...(animationLength <= 0 ? [] : LIST_ANIMATION_PROPERTY_NAMES),
...(transitionLength <= 0 ? [] : ['transition']),
];
if (propertyNames.length === 0) {
return '';
}

const declarations = propertyNames.map((name) => ({
name,
value: Array.from(
{ length: name === 'transition' ? transitionLength : animationLength },
// TODO: maybe add `-intrct` to names? --anm-0 or --trns-0 could collide with user-defined names
(_, i) => `var(${getCustomPropName(name, i)})`,
).join(', '),
}));

const joinedSelector = targets
.map(
({ key, childSelector }) =>
`[data-interact-key="${key}"]${childSelector ? ` ${childSelector}` : ''}`,
)
.join(', ');

return `${joinedSelector} {\n${declarations.map(({ name, value }) => ` ${name}: ${value};`).join('\n')}\n}`;
}
11 changes: 8 additions & 3 deletions packages/interact/src/core/resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export function resolveEffectForCSS(
const { key: interactionKey, trigger } = interaction;
const isPointerMove = trigger === 'pointerMove';

// ensuring the original refernce of the effect has an id (required for states)
// ensuring the original reference of the effect has an id (required for states)
if (!effect.effectId) {
effect.effectId = fallbackId || generateId();
}
Expand Down Expand Up @@ -137,13 +137,18 @@ export function resolveSequenceForCSS(
...new Set((conditions || []).filter((condition: string) => configConditions[condition])),
];
// resolving effects and cascading the conditions from sequence
const resolvedEffects = effects.map((effect) => {
const resolvedEffects = effects.map((effect, index) => {
if (!effect.conditions) {
effect.conditions = [...conditions];
} else {
effect.conditions.push(...conditions);
}
return resolveEffectForCSS({ ...effect, triggerType }, interaction, config);
return resolveEffectForCSS(
{ ...effect, triggerType },
interaction,
config,
`${sequenceId}-eff-${index}`,
);
});

// removing unsupported effects and the whole sequence if all are unsupported
Expand Down
11 changes: 0 additions & 11 deletions packages/interact/src/core/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,3 @@ export function getElementHash(elementIdentifier: ElementIdentifier): string {
const { key, listContainer, listItemSelector, selector } = elementIdentifier;
return `${key}\0${listContainer || ''}\0${listItemSelector || ''}\0${selector || ''}`;
}

export function getUniqueEncodedHash(hash: string): string {
let h1 = 0;
let h2 = 0;
for (let i = 0; i < hash.length; i++) {
const ch = hash.charCodeAt(i);
h1 = ((h1 << 5) - h1 + ch) | 0;
h2 = ((h2 << 3) ^ (h2 >>> 2) ^ ch) | 0;
}
return ((h1 >>> 0) * 0x100000 + ((h2 >>> 0) % 0x100000)).toString(36);
}
11 changes: 0 additions & 11 deletions packages/interact/src/types/css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,6 @@ export type ListPropertyName =
| 'animation-timeline'
| 'animation-range';

export type CSSCoordinatedLists = {
key: string;
childSelector?: string;
properties: Partial<Record<ListPropertyName, { fallback: string; varNames: string[] }>>;
};

export type ListCustomProps = {
key: string;
childSelector?: string;
} & Record<ListPropertyName, string>;

export type CSSRuleData = {
key: string;
childSelector?: string;
Expand Down
4 changes: 0 additions & 4 deletions packages/interact/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@ export function isTemplatedKey(key: string) {
return /\[]/g.test(key);
}

export function kebabCustomProp(args: (string | number)[]) {
return `--${args.join('-')}`;
}

export function camelToKebabCase(property: string): string {
return property.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`);
}
Expand Down
Loading
Loading