Initial commit

This commit is contained in:
2025-03-07 19:22:02 +01:00
commit 4a98255d83
55743 changed files with 5280367 additions and 0 deletions
@@ -0,0 +1,85 @@
import { motionTokens, createPresenceComponent } from '@fluentui/react-motion';
import { sizeEnterAtom, sizeExitAtom, whitespaceAtom } from './collapse-atoms';
import { fadeAtom } from '../../atoms/fade-atom';
/** Define a presence motion for collapse/expand that can stagger the size and opacity motions by a given delay. */ export const createCollapseDelayedPresence = ({ // enter
enterSizeDuration = motionTokens.durationNormal, enterOpacityDuration = enterSizeDuration, enterEasing = motionTokens.curveEasyEaseMax, enterDelay = 0, // exit: durations and easing default to enter values for symmetry
exitSizeDuration = enterSizeDuration, exitOpacityDuration = enterOpacityDuration, exitEasing = enterEasing, exitDelay = 0 } = {})=>({ element, animateOpacity = true, orientation = 'vertical' })=>{
// ----- ENTER -----
// The enter transition is an array of up to 3 motion atoms: size, whitespace and opacity.
const enterAtoms = [
sizeEnterAtom({
orientation,
duration: enterSizeDuration,
easing: enterEasing,
element
}),
whitespaceAtom({
direction: 'enter',
orientation,
duration: enterSizeDuration,
easing: enterEasing
})
];
// Fade in only if animateOpacity is true. Otherwise, leave opacity unaffected.
if (animateOpacity) {
enterAtoms.push({
...fadeAtom({
direction: 'enter',
duration: enterOpacityDuration,
easing: enterEasing
}),
delay: enterDelay,
fill: 'both'
});
}
// ----- EXIT -----
// The exit transition is an array of up to 3 motion atoms: opacity, size and whitespace.
const exitAtoms = [];
// Fade out only if animateOpacity is true. Otherwise, leave opacity unaffected.
if (animateOpacity) {
exitAtoms.push(fadeAtom({
direction: 'exit',
duration: exitOpacityDuration,
easing: exitEasing
}));
}
exitAtoms.push(sizeExitAtom({
orientation,
duration: exitSizeDuration,
easing: exitEasing,
element,
delay: exitDelay
}), whitespaceAtom({
direction: 'exit',
orientation,
duration: exitSizeDuration,
easing: exitEasing,
delay: exitDelay
}));
return {
enter: enterAtoms,
exit: exitAtoms
};
};
/** Defines a presence motion for collapse/expand. */ export const createCollapsePresence = ({ enterDuration = motionTokens.durationNormal, enterEasing = motionTokens.curveEasyEaseMax, exitDuration = enterDuration, exitEasing = enterEasing } = {})=>// Implement a regular collapse as a special case of the delayed collapse,
// where the delays are zero, and the size and opacity durations are equal.
createCollapseDelayedPresence({
enterSizeDuration: enterDuration,
enterEasing,
exitSizeDuration: exitDuration,
exitEasing
});
/** A React component that applies collapse/expand transitions to its children. */ export const Collapse = createPresenceComponent(createCollapsePresence());
export const CollapseSnappy = createPresenceComponent(createCollapsePresence({
enterDuration: motionTokens.durationFast
}));
export const CollapseRelaxed = createPresenceComponent(createCollapsePresence({
enterDuration: motionTokens.durationSlower
}));
export const CollapseDelayed = createPresenceComponent(createCollapseDelayedPresence({
enterSizeDuration: motionTokens.durationNormal,
enterOpacityDuration: motionTokens.durationSlower,
enterDelay: motionTokens.durationNormal,
exitDelay: motionTokens.durationSlower,
enterEasing: motionTokens.curveEasyEase
}));
File diff suppressed because one or more lines are too long
@@ -0,0 +1,101 @@
// ----- SIZE -----
const sizeValuesForOrientation = (orientation, element)=>{
const sizeName = orientation === 'horizontal' ? 'maxWidth' : 'maxHeight';
const overflowName = orientation === 'horizontal' ? 'overflowX' : 'overflowY';
const measuredSize = orientation === 'horizontal' ? element.scrollWidth : element.scrollHeight;
const toSize = `${measuredSize}px`;
return {
sizeName,
overflowName,
toSize
};
};
export const sizeEnterAtom = ({ orientation, duration, easing, element, fromSize = '0' })=>{
const { sizeName, overflowName, toSize } = sizeValuesForOrientation(orientation, element);
return {
keyframes: [
{
[sizeName]: fromSize,
[overflowName]: 'hidden'
},
{
[sizeName]: toSize,
offset: 0.9999,
[overflowName]: 'hidden'
},
{
[sizeName]: 'unset',
[overflowName]: 'unset'
}
],
duration,
easing
};
};
export const sizeExitAtom = ({ orientation, duration, easing, element, delay = 0, fromSize = '0' })=>{
const { sizeName, overflowName, toSize } = sizeValuesForOrientation(orientation, element);
return {
keyframes: [
{
[sizeName]: toSize,
[overflowName]: 'hidden'
},
{
[sizeName]: fromSize,
[overflowName]: 'hidden'
}
],
duration,
easing,
fill: 'both',
delay
};
};
// ----- WHITESPACE -----
// Whitespace animation includes padding and margin.
const whitespaceValuesForOrientation = (orientation)=>{
// horizontal whitespace collapse
if (orientation === 'horizontal') {
return {
paddingStart: 'paddingInlineStart',
paddingEnd: 'paddingInlineEnd',
marginStart: 'marginInlineStart',
marginEnd: 'marginInlineEnd'
};
}
// vertical whitespace collapse
return {
paddingStart: 'paddingBlockStart',
paddingEnd: 'paddingBlockEnd',
marginStart: 'marginBlockStart',
marginEnd: 'marginBlockEnd'
};
};
/**
* A collapse animates an element's height to zero,
but the zero height does not eliminate padding or margin in the box model.
So here we generate keyframes to animate those whitespace properties to zero.
*/ export const whitespaceAtom = ({ direction, orientation, duration, easing, delay = 0 })=>{
const { paddingStart, paddingEnd, marginStart, marginEnd } = whitespaceValuesForOrientation(orientation);
// The keyframe with zero whitespace is at the start for enter and at the end for exit.
const offset = direction === 'enter' ? 0 : 1;
const keyframes = [
{
[paddingStart]: '0',
[paddingEnd]: '0',
[marginStart]: '0',
[marginEnd]: '0',
offset
}
];
const atom = {
keyframes,
duration,
easing,
delay
};
if (direction === 'exit') {
atom.fill = 'forwards';
}
return atom;
};
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
export { };
@@ -0,0 +1 @@
{"version":3,"sources":["../src/components/Collapse/collapse-types.ts"],"sourcesContent":["export type CollapseOrientation = 'horizontal' | 'vertical';\n\n// TODO: reduce duplication between CollapseDelayedVariantParams and CollapseVariantParams\nexport type CollapseDelayedVariantParams = {\n /** Time (ms) for the size expand. Defaults to the durationNormal value (200 ms). */\n enterSizeDuration?: number;\n\n /** Time (ms) for the fade-in. Defaults to the enterSizeDuration param, to sync fade-in with expand. */\n enterOpacityDuration?: number;\n\n /** Time (ms) for the size collapse. Defaults to the enterSizeDuration param, for temporal symmetry.. */\n exitSizeDuration?: number;\n\n /** Defaults to the exitSizeDuration param, to sync the fade-out with the collapse. */\n exitOpacityDuration?: number;\n\n /** Time (ms) between the size expand start and the fade-in start. Defaults to `0`. */\n enterDelay?: number;\n\n /** Time (ms) between the fade-out start and the size collapse start. Defaults to `0`. */\n exitDelay?: number;\n\n /** Easing curve for the enter transition, shared by size and opacity. Defaults to the easeEaseMax value. */\n enterEasing?: string;\n\n /** Easing curve for the exit transition, shared by size and opacity. Defaults to the enterEasing param. */\n exitEasing?: string;\n};\n\nexport type CollapseRuntimeParams = {\n /** Whether to animate the opacity. Defaults to `true`. */\n animateOpacity?: boolean;\n\n /** The orientation of the size animation. Defaults to `'vertical'` to expand/collapse the height. */\n orientation?: CollapseOrientation;\n};\n\nexport type CollapseVariantParams = {\n /** Time (ms) for the enter transition (expand). Defaults to the `durationNormal` value (200 ms). */\n enterDuration?: number;\n\n /** Easing curve for the enter transition (expand). Defaults to the `easeEaseMax` value. */\n enterEasing?: string;\n\n /** Time (ms) for the exit transition (collapse). Defaults to the `enterDuration` param for symmetry. */\n exitDuration?: number;\n\n /** Easing curve for the exit transition (collapse). Defaults to the `enterEasing` param for symmetry. */\n exitEasing?: string;\n};\n"],"names":[],"rangeMappings":"","mappings":"AAqCA,WAYE"}
@@ -0,0 +1 @@
export { Collapse, CollapseDelayed, CollapseRelaxed, CollapseSnappy, createCollapseDelayedPresence, createCollapsePresence } from './Collapse';
@@ -0,0 +1 @@
{"version":3,"sources":["../src/components/Collapse/index.ts"],"sourcesContent":["export {\n Collapse,\n CollapseDelayed,\n CollapseRelaxed,\n CollapseSnappy,\n createCollapseDelayedPresence,\n createCollapsePresence,\n} from './Collapse';\nexport type { CollapseRuntimeParams } from './collapse-types';\n"],"names":["Collapse","CollapseDelayed","CollapseRelaxed","CollapseSnappy","createCollapseDelayedPresence","createCollapsePresence"],"rangeMappings":"","mappings":"AAAA,SACEA,QAAQ,EACRC,eAAe,EACfC,eAAe,EACfC,cAAc,EACdC,6BAA6B,EAC7BC,sBAAsB,QACjB,aAAa"}
@@ -0,0 +1,21 @@
import { motionTokens, createPresenceComponent } from '@fluentui/react-motion';
import { fadeAtom } from '../../atoms/fade-atom';
/** Define a presence motion for fade in/out */ export const createFadePresence = ({ enterDuration = motionTokens.durationNormal, enterEasing = motionTokens.curveEasyEase, exitDuration = enterDuration, exitEasing = enterEasing } = {})=>({
enter: fadeAtom({
direction: 'enter',
duration: enterDuration,
easing: enterEasing
}),
exit: fadeAtom({
direction: 'exit',
duration: exitDuration,
easing: exitEasing
})
});
/** A React component that applies fade in/out transitions to its children. */ export const Fade = createPresenceComponent(createFadePresence());
export const FadeSnappy = createPresenceComponent(createFadePresence({
enterDuration: motionTokens.durationFast
}));
export const FadeRelaxed = createPresenceComponent(createFadePresence({
enterDuration: motionTokens.durationGentle
}));
@@ -0,0 +1 @@
{"version":3,"sources":["../src/components/Fade/Fade.ts"],"sourcesContent":["import { motionTokens, createPresenceComponent } from '@fluentui/react-motion';\nimport type { PresenceMotionCreator } from '../../types';\nimport { fadeAtom } from '../../atoms/fade-atom';\n\ntype FadeVariantParams = {\n /** Time (ms) for the enter transition (fade-in). Defaults to the `durationNormal` value (200 ms). */\n enterDuration?: number;\n\n /** Easing curve for the enter transition (fade-in). Defaults to the `easeEase` value. */\n enterEasing?: string;\n\n /** Time (ms) for the exit transition (fade-out). Defaults to the `enterDuration` param for symmetry. */\n exitDuration?: number;\n\n /** Easing curve for the exit transition (fade-out). Defaults to the `enterEasing` param for symmetry. */\n exitEasing?: string;\n};\n\n/** Define a presence motion for fade in/out */\nexport const createFadePresence: PresenceMotionCreator<FadeVariantParams> = ({\n enterDuration = motionTokens.durationNormal,\n enterEasing = motionTokens.curveEasyEase,\n exitDuration = enterDuration,\n exitEasing = enterEasing,\n} = {}) => ({\n enter: fadeAtom({ direction: 'enter', duration: enterDuration, easing: enterEasing }),\n exit: fadeAtom({ direction: 'exit', duration: exitDuration, easing: exitEasing }),\n});\n\n/** A React component that applies fade in/out transitions to its children. */\nexport const Fade = createPresenceComponent(createFadePresence());\n\nexport const FadeSnappy = createPresenceComponent(createFadePresence({ enterDuration: motionTokens.durationFast }));\n\nexport const FadeRelaxed = createPresenceComponent(createFadePresence({ enterDuration: motionTokens.durationGentle }));\n"],"names":["motionTokens","createPresenceComponent","fadeAtom","createFadePresence","enterDuration","durationNormal","enterEasing","curveEasyEase","exitDuration","exitEasing","enter","direction","duration","easing","exit","Fade","FadeSnappy","durationFast","FadeRelaxed","durationGentle"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;","mappings":"AAAA,SAASA,YAAY,EAAEC,uBAAuB,QAAQ,yBAAyB;AAE/E,SAASC,QAAQ,QAAQ,wBAAwB;AAgBjD,8CAA8C,GAC9C,OAAO,MAAMC,qBAA+D,CAAC,EAC3EC,gBAAgBJ,aAAaK,cAAc,EAC3CC,cAAcN,aAAaO,aAAa,EACxCC,eAAeJ,aAAa,EAC5BK,aAAaH,WAAW,EACzB,GAAG,CAAC,CAAC,GAAM,CAAA;QACVI,OAAOR,SAAS;YAAES,WAAW;YAASC,UAAUR;YAAeS,QAAQP;QAAY;QACnFQ,MAAMZ,SAAS;YAAES,WAAW;YAAQC,UAAUJ;YAAcK,QAAQJ;QAAW;IACjF,CAAA,EAAG;AAEH,4EAA4E,GAC5E,OAAO,MAAMM,OAAOd,wBAAwBE,sBAAsB;AAElE,OAAO,MAAMa,aAAaf,wBAAwBE,mBAAmB;IAAEC,eAAeJ,aAAaiB,YAAY;AAAC,IAAI;AAEpH,OAAO,MAAMC,cAAcjB,wBAAwBE,mBAAmB;IAAEC,eAAeJ,aAAamB,cAAc;AAAC,IAAI"}
@@ -0,0 +1 @@
export { Fade, FadeRelaxed, FadeSnappy, createFadePresence } from './Fade';
@@ -0,0 +1 @@
{"version":3,"sources":["../src/components/Fade/index.ts"],"sourcesContent":["export { Fade, FadeRelaxed, FadeSnappy, createFadePresence } from './Fade';\n"],"names":["Fade","FadeRelaxed","FadeSnappy","createFadePresence"],"rangeMappings":"","mappings":"AAAA,SAASA,IAAI,EAAEC,WAAW,EAAEC,UAAU,EAAEC,kBAAkB,QAAQ,SAAS"}
@@ -0,0 +1,54 @@
import { motionTokens, createPresenceComponent } from '@fluentui/react-motion';
/** Define a presence motion for scale in/out */ export const createScalePresence = ({ enterDuration = motionTokens.durationGentle, enterEasing = motionTokens.curveDecelerateMax, exitDuration = motionTokens.durationNormal, exitEasing = motionTokens.curveAccelerateMax } = {})=>({ animateOpacity = true })=>{
const fromOpacity = animateOpacity ? 0 : 1;
const toOpacity = 1;
const fromScale = 0.9; // Could be a custom param in the future
const toScale = 1;
const enterKeyframes = [
{
opacity: fromOpacity,
transform: `scale3d(${fromScale}, ${fromScale}, 1)`,
visibility: 'visible'
},
{
opacity: toOpacity,
transform: `scale3d(${toScale}, ${toScale}, 1)`
}
];
const exitKeyframes = [
{
opacity: toOpacity,
transform: `scale3d(${toScale}, ${toScale}, 1)`
},
{
opacity: fromOpacity,
transform: `scale3d(${fromScale}, ${fromScale}, 1)`,
visibility: 'hidden'
}
];
return {
enter: {
duration: enterDuration,
easing: enterEasing,
keyframes: enterKeyframes
},
exit: {
duration: exitDuration,
easing: exitEasing,
keyframes: exitKeyframes
}
};
};
/** A React component that applies scale in/out transitions to its children. */ export const Scale = createPresenceComponent(createScalePresence());
export const ScaleSnappy = createPresenceComponent(createScalePresence({
enterDuration: motionTokens.durationNormal,
enterEasing: motionTokens.curveDecelerateMax,
exitDuration: motionTokens.durationFast,
exitEasing: motionTokens.curveAccelerateMax
}));
export const ScaleRelaxed = createPresenceComponent(createScalePresence({
enterDuration: motionTokens.durationSlow,
enterEasing: motionTokens.curveDecelerateMax,
exitDuration: motionTokens.durationGentle,
exitEasing: motionTokens.curveAccelerateMax
}));
@@ -0,0 +1 @@
{"version":3,"sources":["../src/components/Scale/Scale.ts"],"sourcesContent":["import { motionTokens, createPresenceComponent } from '@fluentui/react-motion';\nimport { PresenceMotionFnCreator } from '../../types';\nimport { ScaleRuntimeParams_unstable, ScaleVariantParams_unstable } from './Scale.types';\n\n/** Define a presence motion for scale in/out */\nexport const createScalePresence: PresenceMotionFnCreator<ScaleVariantParams_unstable, ScaleRuntimeParams_unstable> =\n ({\n enterDuration = motionTokens.durationGentle,\n enterEasing = motionTokens.curveDecelerateMax,\n exitDuration = motionTokens.durationNormal,\n exitEasing = motionTokens.curveAccelerateMax,\n } = {}) =>\n ({ animateOpacity = true }) => {\n const fromOpacity = animateOpacity ? 0 : 1;\n const toOpacity = 1;\n const fromScale = 0.9; // Could be a custom param in the future\n const toScale = 1;\n\n const enterKeyframes = [\n { opacity: fromOpacity, transform: `scale3d(${fromScale}, ${fromScale}, 1)`, visibility: 'visible' },\n { opacity: toOpacity, transform: `scale3d(${toScale}, ${toScale}, 1)` },\n ];\n\n const exitKeyframes = [\n { opacity: toOpacity, transform: `scale3d(${toScale}, ${toScale}, 1)` },\n { opacity: fromOpacity, transform: `scale3d(${fromScale}, ${fromScale}, 1)`, visibility: 'hidden' },\n ];\n return {\n enter: {\n duration: enterDuration,\n easing: enterEasing,\n keyframes: enterKeyframes,\n },\n exit: { duration: exitDuration, easing: exitEasing, keyframes: exitKeyframes },\n };\n };\n\n/** A React component that applies scale in/out transitions to its children. */\nexport const Scale = createPresenceComponent(createScalePresence());\n\nexport const ScaleSnappy = createPresenceComponent(\n createScalePresence({\n enterDuration: motionTokens.durationNormal,\n enterEasing: motionTokens.curveDecelerateMax,\n exitDuration: motionTokens.durationFast,\n exitEasing: motionTokens.curveAccelerateMax,\n }),\n);\n\nexport const ScaleRelaxed = createPresenceComponent(\n createScalePresence({\n enterDuration: motionTokens.durationSlow,\n enterEasing: motionTokens.curveDecelerateMax,\n exitDuration: motionTokens.durationGentle,\n exitEasing: motionTokens.curveAccelerateMax,\n }),\n);\n"],"names":["motionTokens","createPresenceComponent","createScalePresence","enterDuration","durationGentle","enterEasing","curveDecelerateMax","exitDuration","durationNormal","exitEasing","curveAccelerateMax","animateOpacity","fromOpacity","toOpacity","fromScale","toScale","enterKeyframes","opacity","transform","visibility","exitKeyframes","enter","duration","easing","keyframes","exit","Scale","ScaleSnappy","durationFast","ScaleRelaxed","durationSlow"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"AAAA,SAASA,YAAY,EAAEC,uBAAuB,QAAQ,yBAAyB;AAI/E,8CAA8C,GAC9C,OAAO,MAAMC,sBACX,CAAC,EACCC,gBAAgBH,aAAaI,cAAc,EAC3CC,cAAcL,aAAaM,kBAAkB,EAC7CC,eAAeP,aAAaQ,cAAc,EAC1CC,aAAaT,aAAaU,kBAAkB,EAC7C,GAAG,CAAC,CAAC,GACN,CAAC,EAAEC,iBAAiB,IAAI,EAAE;QACxB,MAAMC,cAAcD,iBAAiB,IAAI;QACzC,MAAME,YAAY;QAClB,MAAMC,YAAY,KAAK,wCAAwC;QAC/D,MAAMC,UAAU;QAEhB,MAAMC,iBAAiB;YACrB;gBAAEC,SAASL;gBAAaM,WAAW,CAAC,QAAQ,EAAEJ,UAAU,EAAE,EAAEA,UAAU,IAAI,CAAC;gBAAEK,YAAY;YAAU;YACnG;gBAAEF,SAASJ;gBAAWK,WAAW,CAAC,QAAQ,EAAEH,QAAQ,EAAE,EAAEA,QAAQ,IAAI,CAAC;YAAC;SACvE;QAED,MAAMK,gBAAgB;YACpB;gBAAEH,SAASJ;gBAAWK,WAAW,CAAC,QAAQ,EAAEH,QAAQ,EAAE,EAAEA,QAAQ,IAAI,CAAC;YAAC;YACtE;gBAAEE,SAASL;gBAAaM,WAAW,CAAC,QAAQ,EAAEJ,UAAU,EAAE,EAAEA,UAAU,IAAI,CAAC;gBAAEK,YAAY;YAAS;SACnG;QACD,OAAO;YACLE,OAAO;gBACLC,UAAUnB;gBACVoB,QAAQlB;gBACRmB,WAAWR;YACb;YACAS,MAAM;gBAAEH,UAAUf;gBAAcgB,QAAQd;gBAAYe,WAAWJ;YAAc;QAC/E;IACF,EAAE;AAEJ,6EAA6E,GAC7E,OAAO,MAAMM,QAAQzB,wBAAwBC,uBAAuB;AAEpE,OAAO,MAAMyB,cAAc1B,wBACzBC,oBAAoB;IAClBC,eAAeH,aAAaQ,cAAc;IAC1CH,aAAaL,aAAaM,kBAAkB;IAC5CC,cAAcP,aAAa4B,YAAY;IACvCnB,YAAYT,aAAaU,kBAAkB;AAC7C,IACA;AAEF,OAAO,MAAMmB,eAAe5B,wBAC1BC,oBAAoB;IAClBC,eAAeH,aAAa8B,YAAY;IACxCzB,aAAaL,aAAaM,kBAAkB;IAC5CC,cAAcP,aAAaI,cAAc;IACzCK,YAAYT,aAAaU,kBAAkB;AAC7C,IACA"}
@@ -0,0 +1,3 @@
// eslint-disable-next-line @typescript-eslint/naming-convention
// eslint-disable-next-line @typescript-eslint/naming-convention
export { };
@@ -0,0 +1 @@
{"version":3,"sources":["../src/components/Scale/Scale.types.ts"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/naming-convention\nexport type ScaleVariantParams_unstable = {\n /** Time (ms) for the enter transition. Defaults to the `durationNormal` value (200 ms). */\n enterDuration?: number;\n\n /** Easing curve for the enter transition. Defaults to the `easeEaseMax` value. */\n enterEasing?: string;\n\n /** Time (ms) for the exit transition. Defaults to the `enterDuration` param for symmetry. */\n exitDuration?: number;\n\n /** Easing curve for the exit transition. Defaults to the `enterEasing` param for symmetry. */\n exitEasing?: string;\n};\n\n// eslint-disable-next-line @typescript-eslint/naming-convention\nexport type ScaleRuntimeParams_unstable = {\n /** Whether to animate the opacity. Defaults to `true`. */\n animateOpacity?: boolean;\n};\n"],"names":[],"rangeMappings":";;","mappings":"AAAA,gEAAgE;AAehE,gEAAgE;AAChE,WAGE"}
@@ -0,0 +1 @@
export { Scale, ScaleRelaxed, ScaleSnappy, createScalePresence } from './Scale';
@@ -0,0 +1 @@
{"version":3,"sources":["../src/components/Scale/index.ts"],"sourcesContent":["export { Scale, ScaleRelaxed, ScaleSnappy, createScalePresence } from './Scale';\n"],"names":["Scale","ScaleRelaxed","ScaleSnappy","createScalePresence"],"rangeMappings":"","mappings":"AAAA,SAASA,KAAK,EAAEC,YAAY,EAAEC,WAAW,EAAEC,mBAAmB,QAAQ,UAAU"}