mirror of
https://github.com/motion-canvas/motion-canvas.git
synced 2026-01-11 06:48:12 -05:00
feat: add missing layout props (#72)
This commit is contained in:
@@ -185,8 +185,8 @@ export class Node<TProps extends NodeProps = NodeProps>
|
||||
this.layout.releaseSize();
|
||||
}
|
||||
|
||||
@compound(['width', 'height'], Size)
|
||||
@property(undefined, Size.lerp)
|
||||
@compound(['width', 'height'])
|
||||
@property(undefined, Size.lerp, Size)
|
||||
public declare readonly size: Property<
|
||||
{width: Length; height: Length},
|
||||
Size,
|
||||
@@ -246,8 +246,8 @@ export class Node<TProps extends NodeProps = NodeProps>
|
||||
@property(0)
|
||||
public declare readonly offsetY: Signal<number, this>;
|
||||
|
||||
@compound({x: 'offsetX', y: 'offsetY'}, Vector2)
|
||||
@property(undefined, Vector2.lerp)
|
||||
@compound({x: 'offsetX', y: 'offsetY'})
|
||||
@property(undefined, Vector2.lerp, Vector2)
|
||||
public declare readonly offset: Signal<Vector2, this>;
|
||||
|
||||
@property(1)
|
||||
@@ -256,8 +256,8 @@ export class Node<TProps extends NodeProps = NodeProps>
|
||||
@property(1)
|
||||
public declare readonly scaleY: Signal<number, this>;
|
||||
|
||||
@compound({x: 'scaleX', y: 'scaleY'}, Vector2)
|
||||
@property(undefined, Vector2.lerp)
|
||||
@compound({x: 'scaleX', y: 'scaleY'})
|
||||
@property(undefined, Vector2.lerp, Vector2)
|
||||
public declare readonly scale: Signal<Vector2, this>;
|
||||
|
||||
@property(false)
|
||||
@@ -313,8 +313,8 @@ export class Node<TProps extends NodeProps = NodeProps>
|
||||
@property(0)
|
||||
public declare readonly shadowOffsetY: Signal<number, this>;
|
||||
|
||||
@compound({x: 'shadowOffsetX', y: 'shadowOffsetY'}, Vector2)
|
||||
@property(undefined, Vector2.lerp)
|
||||
@compound({x: 'shadowOffsetX', y: 'shadowOffsetY'})
|
||||
@property(undefined, Vector2.lerp, Vector2)
|
||||
public declare readonly shadowOffset: Signal<Vector2, this>;
|
||||
|
||||
@computed()
|
||||
@@ -381,11 +381,11 @@ export class Node<TProps extends NodeProps = NodeProps>
|
||||
return filters;
|
||||
}
|
||||
|
||||
@compound(['x', 'y'], Vector2)
|
||||
@property(undefined, Vector2.lerp)
|
||||
@compound(['x', 'y'])
|
||||
@property(undefined, Vector2.lerp, Vector2)
|
||||
public declare readonly position: Signal<Vector2, this>;
|
||||
|
||||
@property(undefined, Vector2.lerp)
|
||||
@property(undefined, Vector2.lerp, Vector2)
|
||||
public declare readonly absolutePosition: Signal<Vector2, this>;
|
||||
|
||||
protected getAbsolutePosition(): Vector2 {
|
||||
|
||||
@@ -40,12 +40,9 @@ import {addInitializer} from './initializers';
|
||||
* @param mapping - An array of signals to turn into a compound property or a
|
||||
* record mapping the property in the compound object to the
|
||||
* corresponding signal.
|
||||
*
|
||||
* @param klass - A class used to instantiate the returned value.
|
||||
*/
|
||||
export function compound(
|
||||
mapping: string[] | Record<string, string>,
|
||||
klass?: new (from: any) => any,
|
||||
): PropertyDecorator {
|
||||
return (target: any, key) => {
|
||||
const entries = Array.isArray(mapping)
|
||||
@@ -54,10 +51,9 @@ export function compound(
|
||||
|
||||
target.constructor.prototype[`get${capitalize(key.toString())}`] =
|
||||
function () {
|
||||
const object = Object.fromEntries(
|
||||
return Object.fromEntries(
|
||||
entries.map(([key, property]) => [key, this[property]()]),
|
||||
);
|
||||
return klass ? new klass(object) : object;
|
||||
};
|
||||
|
||||
target.constructor.prototype[`set${capitalize(key.toString())}`] =
|
||||
|
||||
@@ -32,7 +32,7 @@ export interface Property<
|
||||
export type PropertyOwner<TGetterValue, TSetterValue> = {
|
||||
[key: `get${Capitalize<string>}`]: SignalGetter<TGetterValue> | undefined;
|
||||
[key: `set${Capitalize<string>}`]: SignalSetter<TSetterValue> | undefined;
|
||||
[key: `tween${Capitalize<string>}`]: SignalTween<TSetterValue> | undefined;
|
||||
[key: `tween${Capitalize<string>}`]: SignalTween<TGetterValue> | undefined;
|
||||
};
|
||||
|
||||
export function createProperty<
|
||||
@@ -44,7 +44,8 @@ export function createProperty<
|
||||
node: TNode,
|
||||
property: TProperty,
|
||||
initial?: TSetterValue,
|
||||
defaultInterpolation: InterpolationFunction<TSetterValue> = deepLerp,
|
||||
defaultInterpolation: InterpolationFunction<TGetterValue> = deepLerp,
|
||||
klass?: new (value: TSetterValue) => TGetterValue,
|
||||
): Property<TSetterValue, TGetterValue, TNode> {
|
||||
let getter: SignalGetter<TGetterValue>;
|
||||
let setter: SignalSetter<TSetterValue>;
|
||||
@@ -59,10 +60,27 @@ export function createProperty<
|
||||
);
|
||||
}
|
||||
|
||||
let wrap: (value: SignalValue<TSetterValue>) => SignalValue<TGetterValue>;
|
||||
let unwrap: (value: SignalValue<TSetterValue>) => TGetterValue;
|
||||
if (klass) {
|
||||
wrap = value =>
|
||||
isReactive(value) ? () => new klass(value()) : new klass(value);
|
||||
unwrap = value => new klass(isReactive(value) ? value() : value);
|
||||
} else {
|
||||
wrap = value => <SignalValue<TGetterValue>>value;
|
||||
unwrap = value => <TGetterValue>(isReactive(value) ? value() : value);
|
||||
}
|
||||
|
||||
let signal: Property<TSetterValue, TGetterValue, TNode> | null = null;
|
||||
if (!originalGetter || !originalSetter) {
|
||||
signal = <Property<TSetterValue, TGetterValue, TNode>>(
|
||||
(<unknown>createSignal(initial, defaultInterpolation, node))
|
||||
(<unknown>(
|
||||
createSignal(
|
||||
initial === undefined ? undefined : wrap(initial),
|
||||
defaultInterpolation,
|
||||
node,
|
||||
)
|
||||
))
|
||||
);
|
||||
if (!tweener) {
|
||||
return signal;
|
||||
@@ -83,20 +101,20 @@ export function createProperty<
|
||||
newValue?: SignalValue<TSetterValue>,
|
||||
duration?: number,
|
||||
timingFunction: TimingFunction = easeInOutCubic,
|
||||
interpolationFunction: InterpolationFunction<TSetterValue> = defaultInterpolation,
|
||||
interpolationFunction: InterpolationFunction<TGetterValue> = defaultInterpolation,
|
||||
) {
|
||||
if (newValue === undefined) {
|
||||
return getter();
|
||||
}
|
||||
|
||||
if (duration === undefined) {
|
||||
return setter(newValue);
|
||||
return setter(wrap(newValue));
|
||||
}
|
||||
|
||||
if (tweener) {
|
||||
return tweener.call(
|
||||
node,
|
||||
newValue,
|
||||
wrap(newValue),
|
||||
duration,
|
||||
timingFunction,
|
||||
interpolationFunction,
|
||||
@@ -106,11 +124,7 @@ export function createProperty<
|
||||
const from = getter();
|
||||
return tween(duration, value => {
|
||||
setter(
|
||||
interpolationFunction(
|
||||
from,
|
||||
isReactive(newValue) ? newValue() : newValue,
|
||||
timingFunction(value),
|
||||
),
|
||||
interpolationFunction(from, unwrap(newValue), timingFunction(value)),
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -174,10 +188,12 @@ export function createProperty<
|
||||
* @param initial - An option initial value of the property.
|
||||
* @param interpolationFunction - The default function used to interpolate
|
||||
* between values.
|
||||
* @param klass - A class used to instantiate the returned value.
|
||||
*/
|
||||
export function property<T>(
|
||||
initial?: T,
|
||||
interpolationFunction?: InterpolationFunction<T>,
|
||||
klass?: new (value: any) => T,
|
||||
): PropertyDecorator {
|
||||
return (target: any, key) => {
|
||||
addInitializer(target, (instance: any, context: any) => {
|
||||
@@ -186,6 +202,7 @@ export function property<T>(
|
||||
<string>key,
|
||||
context.defaults[key] ?? initial,
|
||||
interpolationFunction ?? deepLerp,
|
||||
klass,
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -31,16 +31,16 @@ export class Gradient {
|
||||
public declare readonly fromX: Signal<number, this>;
|
||||
@property(0)
|
||||
public declare readonly fromY: Signal<number, this>;
|
||||
@compound({x: 'fromX', y: 'fromY'}, Vector2)
|
||||
@property(undefined, Vector2.lerp)
|
||||
@compound({x: 'fromX', y: 'fromY'})
|
||||
@property(undefined, Vector2.lerp, Vector2)
|
||||
public declare readonly from: Signal<Vector2, this>;
|
||||
|
||||
@property(0)
|
||||
public declare readonly toX: Signal<number, this>;
|
||||
@property(0)
|
||||
public declare readonly toY: Signal<number, this>;
|
||||
@compound({x: 'toX', y: 'toY'}, Vector2)
|
||||
@property(undefined, Vector2.lerp)
|
||||
@compound({x: 'toX', y: 'toY'})
|
||||
@property(undefined, Vector2.lerp, Vector2)
|
||||
public declare readonly to: Signal<Vector2, this>;
|
||||
|
||||
@property(0)
|
||||
|
||||
@@ -1,32 +1,61 @@
|
||||
import {computed, initialize, property} from '../decorators';
|
||||
import {createSignal, Signal} from '@motion-canvas/core/lib/utils';
|
||||
import {Rect} from '@motion-canvas/core/lib/types';
|
||||
import {
|
||||
AlignItems,
|
||||
compound,
|
||||
computed,
|
||||
initialize,
|
||||
Property,
|
||||
property,
|
||||
} from '../decorators';
|
||||
import {createSignal, Signal} from '@motion-canvas/core/lib/utils';
|
||||
import {
|
||||
PossibleSpacing,
|
||||
Rect,
|
||||
Spacing,
|
||||
Vector2,
|
||||
} from '@motion-canvas/core/lib/types';
|
||||
import {
|
||||
FlexAlign,
|
||||
FlexDirection,
|
||||
JustifyContent,
|
||||
FlexWrap,
|
||||
FlexJustify,
|
||||
LayoutMode,
|
||||
Length,
|
||||
FlexBasis,
|
||||
} from './types';
|
||||
import {TwoDView} from '../scenes';
|
||||
|
||||
export interface LayoutProps {
|
||||
tagName?: keyof HTMLElementTagNameMap;
|
||||
mode?: LayoutMode;
|
||||
width?: number;
|
||||
height?: number;
|
||||
|
||||
maxWidth?: Length;
|
||||
maxHeight?: Length;
|
||||
minWidth?: Length;
|
||||
minHeight?: Length;
|
||||
ratio?: number;
|
||||
|
||||
marginTop?: number;
|
||||
marginBottom?: number;
|
||||
marginLeft?: number;
|
||||
marginRight?: number;
|
||||
margin?: PossibleSpacing;
|
||||
|
||||
paddingTop?: number;
|
||||
paddingBottom?: number;
|
||||
paddingLeft?: number;
|
||||
paddingRight?: number;
|
||||
padding?: PossibleSpacing;
|
||||
|
||||
direction?: FlexDirection;
|
||||
justifyContent?: JustifyContent;
|
||||
alignItems?: AlignItems;
|
||||
ratio?: string;
|
||||
basis?: FlexBasis;
|
||||
grow?: number;
|
||||
shrink?: number;
|
||||
wrap?: FlexWrap;
|
||||
|
||||
justifyContent?: FlexJustify;
|
||||
alignItems?: FlexAlign;
|
||||
rowGap?: Length;
|
||||
columnGap?: Length;
|
||||
gap?: Length;
|
||||
|
||||
fontFamily?: string;
|
||||
fontSize?: number;
|
||||
@@ -34,13 +63,24 @@ export interface LayoutProps {
|
||||
fontWeight?: number;
|
||||
lineHeight?: number;
|
||||
letterSpacing?: number;
|
||||
wrap?: boolean;
|
||||
textWrap?: boolean;
|
||||
}
|
||||
|
||||
export class Layout {
|
||||
@property(null)
|
||||
public declare readonly mode: Signal<LayoutMode, this>;
|
||||
|
||||
@property(null)
|
||||
public declare readonly maxWidth: Signal<Length, this>;
|
||||
@property(null)
|
||||
public declare readonly maxHeight: Signal<Length, this>;
|
||||
@property(null)
|
||||
public declare readonly minWidth: Signal<Length, this>;
|
||||
@property(null)
|
||||
public declare readonly minHeight: Signal<Length, this>;
|
||||
@property(null)
|
||||
public declare readonly ratio: Signal<number | null, this>;
|
||||
|
||||
@property(0)
|
||||
public declare readonly marginTop: Signal<number, this>;
|
||||
@property(0)
|
||||
@@ -49,6 +89,14 @@ export class Layout {
|
||||
public declare readonly marginLeft: Signal<number, this>;
|
||||
@property(0)
|
||||
public declare readonly marginRight: Signal<number, this>;
|
||||
@compound({
|
||||
top: 'marginTop',
|
||||
bottom: 'marginBottom',
|
||||
left: 'marginLeft',
|
||||
right: 'marginRight',
|
||||
})
|
||||
@property(undefined, Spacing.lerp, Spacing)
|
||||
public declare readonly margin: Property<PossibleSpacing, Spacing, this>;
|
||||
|
||||
@property(0)
|
||||
public declare readonly paddingTop: Signal<number, this>;
|
||||
@@ -58,15 +106,36 @@ export class Layout {
|
||||
public declare readonly paddingLeft: Signal<number, this>;
|
||||
@property(0)
|
||||
public declare readonly paddingRight: Signal<number, this>;
|
||||
@compound({
|
||||
top: 'paddingTop',
|
||||
bottom: 'paddingBottom',
|
||||
left: 'paddingLeft',
|
||||
right: 'paddingRight',
|
||||
})
|
||||
@property(undefined, Spacing.lerp, Spacing)
|
||||
public declare readonly padding: Property<PossibleSpacing, Spacing, this>;
|
||||
|
||||
@property('row')
|
||||
public declare readonly direction: Signal<FlexDirection, this>;
|
||||
@property('none')
|
||||
public declare readonly ratio: Signal<string, this>;
|
||||
@property('flex-start')
|
||||
public declare readonly justifyContent: Signal<JustifyContent, this>;
|
||||
@property('auto')
|
||||
public declare readonly alignItems: Signal<AlignItems, this>;
|
||||
@property(null)
|
||||
public declare readonly basis: Signal<FlexBasis, this>;
|
||||
@property(0)
|
||||
public declare readonly grow: Signal<number, this>;
|
||||
@property(1)
|
||||
public declare readonly shrink: Signal<number, this>;
|
||||
@property('nowrap')
|
||||
public declare readonly wrap: Signal<FlexWrap, this>;
|
||||
|
||||
@property('normal')
|
||||
public declare readonly justifyContent: Signal<FlexJustify, this>;
|
||||
@property('normal')
|
||||
public declare readonly alignItems: Signal<FlexAlign, this>;
|
||||
@property(null)
|
||||
public declare readonly gap: Signal<Length, this>;
|
||||
@property(null)
|
||||
public declare readonly rowGap: Signal<Length, this>;
|
||||
@property(null)
|
||||
public declare readonly columnGap: Signal<Length, this>;
|
||||
|
||||
@property(null)
|
||||
public declare readonly fontFamily: Signal<string | null, this>;
|
||||
@@ -81,7 +150,7 @@ export class Layout {
|
||||
@property(null)
|
||||
public declare readonly letterSpacing: Signal<number | null, this>;
|
||||
@property(null)
|
||||
public declare readonly wrap: Signal<boolean | null, this>;
|
||||
public declare readonly textWrap: Signal<boolean | null, this>;
|
||||
|
||||
public readonly element: HTMLElement;
|
||||
public readonly styles: CSSStyleDeclaration;
|
||||
@@ -101,7 +170,21 @@ export class Layout {
|
||||
initialize(this, {defaults: props});
|
||||
}
|
||||
|
||||
public toPixels(value: number) {
|
||||
protected parseValue(value: number | string | null): string {
|
||||
return value === null ? '' : value.toString();
|
||||
}
|
||||
|
||||
protected parsePixels(value: number | null): string {
|
||||
return value === null ? '' : `${value}px`;
|
||||
}
|
||||
|
||||
protected parseLength(value: null | number | string): string {
|
||||
if (value === null) {
|
||||
return '';
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
return value;
|
||||
}
|
||||
return `${value}px`;
|
||||
}
|
||||
|
||||
@@ -118,26 +201,12 @@ export class Layout {
|
||||
}
|
||||
|
||||
public setWidth(width: Length): this {
|
||||
if (width === null) {
|
||||
this.element.style.width = 'auto';
|
||||
} else if (typeof width === 'string') {
|
||||
this.element.style.width = width;
|
||||
} else {
|
||||
this.element.style.width = this.toPixels(width);
|
||||
}
|
||||
|
||||
this.element.style.width = this.parseLength(width);
|
||||
return this;
|
||||
}
|
||||
|
||||
public setHeight(height: Length): this {
|
||||
if (height === null) {
|
||||
this.element.style.height = 'auto';
|
||||
} else if (typeof height === 'string') {
|
||||
this.element.style.height = height;
|
||||
} else {
|
||||
this.element.style.height = this.toPixels(height);
|
||||
}
|
||||
|
||||
this.element.style.height = this.parseLength(height);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -147,40 +216,51 @@ export class Layout {
|
||||
this.element.style.position =
|
||||
mode === 'disabled' || mode === 'root' ? 'absolute' : 'relative';
|
||||
|
||||
this.element.style.marginTop = this.toPixels(this.marginTop());
|
||||
this.element.style.marginBottom = this.toPixels(this.marginBottom());
|
||||
this.element.style.marginLeft = this.toPixels(this.marginLeft());
|
||||
this.element.style.marginRight = this.toPixels(this.marginRight());
|
||||
this.element.style.paddingTop = this.toPixels(this.paddingTop());
|
||||
this.element.style.paddingBottom = this.toPixels(this.paddingBottom());
|
||||
this.element.style.paddingLeft = this.toPixels(this.paddingLeft());
|
||||
this.element.style.paddingRight = this.toPixels(this.paddingRight());
|
||||
this.element.style.maxWidth = this.parseLength(this.maxWidth());
|
||||
this.element.style.minWidth = this.parseLength(this.minWidth());
|
||||
this.element.style.maxHeight = this.parseLength(this.maxHeight());
|
||||
this.element.style.minWidth = this.parseLength(this.minWidth());
|
||||
this.element.style.aspectRatio = this.parseValue(this.ratio());
|
||||
|
||||
this.element.style.marginTop = this.parsePixels(this.marginTop());
|
||||
this.element.style.marginBottom = this.parsePixels(this.marginBottom());
|
||||
this.element.style.marginLeft = this.parsePixels(this.marginLeft());
|
||||
this.element.style.marginRight = this.parsePixels(this.marginRight());
|
||||
|
||||
this.element.style.paddingTop = this.parsePixels(this.paddingTop());
|
||||
this.element.style.paddingBottom = this.parsePixels(this.paddingBottom());
|
||||
this.element.style.paddingLeft = this.parsePixels(this.paddingLeft());
|
||||
this.element.style.paddingRight = this.parsePixels(this.paddingRight());
|
||||
|
||||
this.element.style.flexDirection = this.direction();
|
||||
this.element.style.aspectRatio = this.ratio();
|
||||
this.element.style.flexBasis = this.parseLength(this.basis());
|
||||
this.element.style.flexWrap = this.wrap();
|
||||
|
||||
this.element.style.justifyContent = this.justifyContent();
|
||||
this.element.style.alignItems = this.alignItems();
|
||||
this.element.style.gap = this.parseLength(this.gap());
|
||||
this.element.style.rowGap = this.parseLength(this.rowGap());
|
||||
this.element.style.columnGap = this.parseLength(this.columnGap());
|
||||
|
||||
this.element.style.flexGrow = this.sizeLockCounter() > 0 ? '0' : '';
|
||||
this.element.style.flexShrink = this.sizeLockCounter() > 0 ? '0' : '';
|
||||
if (this.sizeLockCounter() > 0) {
|
||||
this.element.style.flexGrow = '0';
|
||||
this.element.style.flexShrink = '0';
|
||||
} else {
|
||||
this.element.style.flexGrow = this.parsePixels(this.grow());
|
||||
this.element.style.flexShrink = this.parsePixels(this.shrink());
|
||||
}
|
||||
}
|
||||
|
||||
@computed()
|
||||
public applyFont() {
|
||||
this.element.style.fontFamily = this.fontFamily() ?? '';
|
||||
const fontSize = this.fontSize();
|
||||
this.element.style.fontSize = fontSize ? this.toPixels(fontSize) : '';
|
||||
this.element.style.fontStyle = this.fontStyle() ?? '';
|
||||
const lineHeight = this.lineHeight();
|
||||
this.element.style.lineHeight =
|
||||
lineHeight === null ? '' : this.toPixels(lineHeight);
|
||||
const fontWeight = this.fontWeight();
|
||||
this.element.style.fontWeight =
|
||||
fontWeight === null ? '' : fontWeight.toString();
|
||||
const letterSpacing = this.letterSpacing();
|
||||
this.element.style.letterSpacing = letterSpacing
|
||||
? this.toPixels(letterSpacing)
|
||||
: '';
|
||||
const wrap = this.wrap();
|
||||
this.element.style.fontFamily = this.parseValue(this.fontFamily());
|
||||
this.element.style.fontSize = this.parsePixels(this.fontSize());
|
||||
this.element.style.fontStyle = this.parseValue(this.fontStyle());
|
||||
this.element.style.lineHeight = this.parsePixels(this.lineHeight());
|
||||
this.element.style.fontWeight = this.parseValue(this.fontWeight());
|
||||
this.element.style.letterSpacing = this.parsePixels(this.letterSpacing());
|
||||
|
||||
const wrap = this.textWrap();
|
||||
this.element.style.whiteSpace =
|
||||
wrap === null ? '' : wrap ? 'normal' : 'nowrap';
|
||||
}
|
||||
|
||||
@@ -1,22 +1,32 @@
|
||||
export type FlexDirection = 'row' | 'row-reverse' | 'column' | 'column-reverse';
|
||||
|
||||
export type JustifyContent =
|
||||
| 'flex-start'
|
||||
export type FlexWrap = 'nowrap' | 'wrap' | 'wrap-reverse';
|
||||
|
||||
export type FlexBasis =
|
||||
| Length
|
||||
| 'content'
|
||||
| 'max-content'
|
||||
| 'min-content'
|
||||
| 'fit-content'
|
||||
| null;
|
||||
|
||||
export type FlexJustify =
|
||||
| 'normal'
|
||||
| 'center'
|
||||
| 'flex-end'
|
||||
| 'start'
|
||||
| 'end'
|
||||
| 'space-between'
|
||||
| 'space-around'
|
||||
| 'space-evenly';
|
||||
| 'space-evenly'
|
||||
| 'stretch';
|
||||
|
||||
export type AlignItems =
|
||||
| 'auto'
|
||||
| 'flex-start'
|
||||
export type FlexAlign =
|
||||
| 'normal'
|
||||
| 'center'
|
||||
| 'flex-end'
|
||||
| 'start'
|
||||
| 'end'
|
||||
| 'stretch'
|
||||
| 'baseline'
|
||||
| 'space-between'
|
||||
| 'space-around';
|
||||
| 'baseline';
|
||||
|
||||
export type ResolvedLayoutMode = 'disabled' | 'enabled' | 'root' | 'pop';
|
||||
export type LayoutMode = ResolvedLayoutMode | null;
|
||||
|
||||
@@ -13,7 +13,7 @@ export class TwoDView extends Node {
|
||||
fontFamily: 'Roboto',
|
||||
fontSize: 48,
|
||||
lineHeight: 64,
|
||||
wrap: false,
|
||||
textWrap: false,
|
||||
fontStyle: 'normal',
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user