mirror of
https://github.com/rio-labs/rio.git
synced 2026-01-05 12:49:48 -06:00
copy TextStyle parameters into Text
This commit is contained in:
@@ -1,5 +1,11 @@
|
||||
import { TextStyle } from "../dataModels";
|
||||
import { textStyleToCss } from "../cssUtils";
|
||||
import {
|
||||
Color,
|
||||
SolidFill,
|
||||
LinearGradientFill,
|
||||
ImageFill,
|
||||
TextStyle,
|
||||
} from "../dataModels";
|
||||
import { textfillToCss, textStyleToCss } from "../cssUtils";
|
||||
import { ComponentBase, ComponentState } from "./componentBase";
|
||||
|
||||
export type TextState = ComponentState & {
|
||||
@@ -9,6 +15,21 @@ export type TextState = ComponentState & {
|
||||
style?: "heading1" | "heading2" | "heading3" | "text" | "dim" | TextStyle;
|
||||
justify?: "left" | "right" | "center" | "justify";
|
||||
overflow?: "nowrap" | "wrap" | "ellipsize";
|
||||
|
||||
font?: string | null;
|
||||
fill?:
|
||||
| Color
|
||||
| SolidFill
|
||||
| LinearGradientFill
|
||||
| ImageFill
|
||||
| null
|
||||
| "not given";
|
||||
font_size?: number | null;
|
||||
italic?: boolean | null;
|
||||
font_weight?: "normal" | "bold" | null;
|
||||
underlined?: boolean | null;
|
||||
strikethrough?: boolean | null;
|
||||
all_caps?: boolean | null;
|
||||
};
|
||||
|
||||
export class TextComponent extends ComponentBase {
|
||||
@@ -32,7 +53,7 @@ export class TextComponent extends ComponentBase {
|
||||
): void {
|
||||
super.updateElement(deltaState, latentComponents);
|
||||
|
||||
// BEFORE WE DO ANYTHING ELSE, update the text style
|
||||
// BEFORE WE DO ANYTHING ELSE, replace the inner HTML element
|
||||
if (deltaState.style !== undefined) {
|
||||
// Change the element to <h1>, <h2>, <h3> or <span> as necessary
|
||||
let tagName: string = "SPAN";
|
||||
@@ -61,14 +82,71 @@ export class TextComponent extends ComponentBase {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now apply the style
|
||||
Object.assign(this.inner.style, textStyleToCss(deltaState.style));
|
||||
// Styling
|
||||
if (
|
||||
deltaState.style !== undefined ||
|
||||
deltaState.font !== undefined ||
|
||||
deltaState.fill !== undefined ||
|
||||
deltaState.font_size !== undefined ||
|
||||
deltaState.italic !== undefined ||
|
||||
deltaState.font_weight !== undefined ||
|
||||
deltaState.underlined !== undefined ||
|
||||
deltaState.strikethrough !== undefined ||
|
||||
deltaState.all_caps !== undefined
|
||||
) {
|
||||
let textStyleCss = textStyleToCss(
|
||||
deltaState.style ?? this.state.style
|
||||
);
|
||||
|
||||
if (deltaState.font) {
|
||||
textStyleCss["font-family"] = deltaState.font;
|
||||
}
|
||||
|
||||
if (
|
||||
deltaState.fill !== undefined &&
|
||||
deltaState.fill !== "not given"
|
||||
) {
|
||||
Object.assign(textStyleCss, textfillToCss(deltaState.fill));
|
||||
}
|
||||
|
||||
if (deltaState.font_size) {
|
||||
textStyleCss["font-size"] = `${deltaState.font_size}rem`;
|
||||
}
|
||||
|
||||
if (deltaState.italic) {
|
||||
textStyleCss["font-style"] = deltaState.italic
|
||||
? "italic"
|
||||
: "normal";
|
||||
}
|
||||
|
||||
if (deltaState.font_weight) {
|
||||
textStyleCss["font-weight"] = deltaState.font_weight;
|
||||
}
|
||||
|
||||
let textDecorations: string[] = [];
|
||||
|
||||
if (deltaState.underlined) {
|
||||
textDecorations.push("underline");
|
||||
}
|
||||
|
||||
if (deltaState.strikethrough) {
|
||||
textDecorations.push("line-through");
|
||||
}
|
||||
|
||||
textStyleCss["text-decoration"] = textDecorations.join(" ");
|
||||
|
||||
if (deltaState.all_caps) {
|
||||
textStyleCss["text-transform"] = deltaState.all_caps
|
||||
? "uppercase"
|
||||
: "none";
|
||||
}
|
||||
|
||||
Object.assign(this.inner.style, textStyleCss);
|
||||
}
|
||||
|
||||
// Text content
|
||||
//
|
||||
// Make sure not to allow any linebreaks if the text is not multiline.
|
||||
if (deltaState.text !== undefined) {
|
||||
this.inner.textContent = deltaState.text;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Color, AnyFill, TextStyle } from "./dataModels";
|
||||
import { Color, AnyFill, TextStyle, TextCompatibleFill } from "./dataModels";
|
||||
|
||||
export function colorToCssString(color: Color): string {
|
||||
const [r, g, b, a] = color;
|
||||
@@ -85,6 +85,57 @@ export function fillToCss(fill: AnyFill): {
|
||||
};
|
||||
}
|
||||
|
||||
export function textfillToCss(fill: TextCompatibleFill): {
|
||||
color: string;
|
||||
background: string;
|
||||
backgroundClip: string;
|
||||
textFillColor: string;
|
||||
} {
|
||||
// If no fill is provided, stick to the local text color. This allows
|
||||
// the user to have their text automatically adapt to different
|
||||
// themes/contexts.
|
||||
if (fill === null) {
|
||||
return {
|
||||
color: "var(--rio-local-text-color)",
|
||||
background: "var(--rio-local-text-background)",
|
||||
backgroundClip: "var(--rio-local-text-background-clip)",
|
||||
textFillColor: "var(--rio-local-text-fill-color)",
|
||||
};
|
||||
}
|
||||
|
||||
// Color?
|
||||
if (Array.isArray(fill)) {
|
||||
return {
|
||||
color: colorToCssString(fill),
|
||||
background: "none",
|
||||
backgroundClip: "unset",
|
||||
textFillColor: "unset",
|
||||
};
|
||||
}
|
||||
|
||||
// Solid fill, i.e. also a color
|
||||
if (fill.type === "solid") {
|
||||
return {
|
||||
color: colorToCssString(fill.color),
|
||||
background: "none",
|
||||
backgroundClip: "unset",
|
||||
textFillColor: "unset",
|
||||
};
|
||||
}
|
||||
|
||||
// Anything else
|
||||
return {
|
||||
color: "unset",
|
||||
background: fillToCss(fill).background,
|
||||
// TODO: The `backdrop-filter` in `cssProps` is ignored because it
|
||||
// doesn't do what we want. (It isn't clipped to the text, it blurs
|
||||
// everything behind the element.) This means FrostedGlassFill
|
||||
// doesn't blur the background when used on text.
|
||||
backgroundClip: "text",
|
||||
textFillColor: "transparent",
|
||||
};
|
||||
}
|
||||
|
||||
export function textStyleToCss(
|
||||
style: "heading1" | "heading2" | "heading3" | "text" | "dim" | TextStyle
|
||||
): {
|
||||
@@ -138,7 +189,7 @@ export function textStyleToCss(
|
||||
// Others
|
||||
fontFamily = globalPrefix + "font-name)";
|
||||
fontSize = globalPrefix + "font-size)";
|
||||
fontStyle = globalPrefix + "font-italic)";
|
||||
fontStyle = globalPrefix + "font-style)";
|
||||
textDecorations.push(globalPrefix + "text-decoration)");
|
||||
textTransform = globalPrefix + "all-caps)";
|
||||
}
|
||||
@@ -166,41 +217,9 @@ export function textStyleToCss(
|
||||
fontFamily = style.fontName;
|
||||
}
|
||||
|
||||
// If no fill is provided, stick to the local text color. This allows
|
||||
// the user to have their text automatically adapt to different
|
||||
// themes/contexts.
|
||||
if (style.fill === null) {
|
||||
color = "var(--rio-local-text-color)";
|
||||
background = "var(--rio-local-text-background)";
|
||||
backgroundClip = "var(--rio-local-text-background-clip)";
|
||||
textFillColor = "var(--rio-local-text-fill-color)";
|
||||
}
|
||||
// Color?
|
||||
else if (Array.isArray(style.fill)) {
|
||||
color = colorToCssString(style.fill);
|
||||
background = "none";
|
||||
backgroundClip = "unset";
|
||||
textFillColor = "unset";
|
||||
}
|
||||
// Solid fill, i.e. also a color
|
||||
else if (style.fill.type === "solid") {
|
||||
color = colorToCssString(style.fill.color);
|
||||
background = "none";
|
||||
backgroundClip = "unset";
|
||||
textFillColor = "unset";
|
||||
}
|
||||
// Anything else
|
||||
else {
|
||||
color = "unset";
|
||||
const cssProps = fillToCss(style.fill);
|
||||
background = cssProps.background;
|
||||
// TODO: The `backdrop-filter` in `cssProps` is ignored because it
|
||||
// doesn't do what we want. (It isn't clipped to the text, it blurs
|
||||
// everything behind the element.) This means FrostedGlassFill
|
||||
// doesn't blur the background when used on text.
|
||||
backgroundClip = "text";
|
||||
textFillColor = "transparent";
|
||||
}
|
||||
({ color, background, backgroundClip, textFillColor } = textfillToCss(
|
||||
style.fill
|
||||
));
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -54,9 +54,15 @@ export type AnyFill =
|
||||
| ImageFill
|
||||
| FrostedGlassFill;
|
||||
|
||||
export type TextCompatibleFill =
|
||||
| Color
|
||||
| SolidFill
|
||||
| LinearGradientFill
|
||||
| ImageFill
|
||||
| null;
|
||||
export type TextStyle = {
|
||||
fontName: string | null;
|
||||
fill: Color | SolidFill | LinearGradientFill | ImageFill | null;
|
||||
fill: TextCompatibleFill;
|
||||
fontSize: number;
|
||||
italic: boolean;
|
||||
fontWeight: "normal" | "bold";
|
||||
|
||||
@@ -2155,7 +2155,7 @@ $rio-input-box-small-label-spacing-top: 0.5rem;
|
||||
font-family: var(--rio-global-heading1-font-name);
|
||||
color: var(--rio-local-heading1-color);
|
||||
font-size: var(--rio-global-heading1-font-size);
|
||||
font-style: var(--rio-global-heading1-italic);
|
||||
font-style: var(--rio-global-heading1-font-style);
|
||||
font-weight: var(--rio-global-heading1-font-weight);
|
||||
text-decoration: var(--rio-global-heading1-text-decoration);
|
||||
text-transform: var(--rio-global-heading1-all-caps);
|
||||
@@ -2173,7 +2173,7 @@ $rio-input-box-small-label-spacing-top: 0.5rem;
|
||||
font-family: var(--rio-global-heading2-font-name);
|
||||
color: var(--rio-local-heading2-color);
|
||||
font-size: var(--rio-global-heading2-font-size);
|
||||
font-style: var(--rio-global-heading2-italic);
|
||||
font-style: var(--rio-global-heading2-font-style);
|
||||
font-weight: var(--rio-global-heading2-font-weight);
|
||||
text-decoration: var(--rio-global-heading2-text-decoration);
|
||||
text-transform: var(--rio-global-heading2-all-caps);
|
||||
@@ -2193,7 +2193,7 @@ $rio-input-box-small-label-spacing-top: 0.5rem;
|
||||
font-family: var(--rio-global-heading3-font-name);
|
||||
color: var(--rio-local-heading3-color);
|
||||
font-size: var(--rio-global-heading3-font-size);
|
||||
font-style: var(--rio-global-heading3-italic);
|
||||
font-style: var(--rio-global-heading3-font-style);
|
||||
font-weight: var(--rio-global-heading3-font-weight);
|
||||
text-decoration: var(--rio-global-heading3-text-decoration);
|
||||
text-transform: var(--rio-global-heading3-all-caps);
|
||||
@@ -2214,7 +2214,7 @@ $rio-input-box-small-label-spacing-top: 0.5rem;
|
||||
color: var(--rio-local-text-color);
|
||||
font-size: var(--rio-global-text-font-size);
|
||||
line-height: 1.35em; // Purposely uses em
|
||||
font-style: var(--rio-global-text-italic);
|
||||
font-style: var(--rio-global-text-font-style);
|
||||
font-weight: var(--rio-global-text-font-weight);
|
||||
text-decoration: var(--rio-global-text-text-decoration);
|
||||
text-transform: var(--rio-global-text-all-caps);
|
||||
|
||||
@@ -7,7 +7,7 @@ from uniserde import JsonDoc
|
||||
|
||||
import rio
|
||||
|
||||
from .. import deprecations
|
||||
from .. import deprecations, text_style, utils
|
||||
from .fundamental_component import FundamentalComponent
|
||||
|
||||
__all__ = [
|
||||
@@ -48,6 +48,29 @@ class Text(FundamentalComponent):
|
||||
Finally, if `"ellipsize"`, the text will be truncated when there isn't
|
||||
enough space and an ellipsis (`...`) will be added.
|
||||
|
||||
`font`: The `Font` to use for the text. When set to `None`, the default font
|
||||
for the current context (heading or regular text, etc) will be used.
|
||||
|
||||
`fill`: The fill (color, gradient, etc.) for the text. Overrides the `fill`
|
||||
from the `style`.
|
||||
|
||||
`font_size`: The font size. Overrides the `font_size` from the `style`.
|
||||
|
||||
`italic`: Whether the text is *italic* or not. Overrides the `italic` from
|
||||
the `style`.
|
||||
|
||||
`font_weight`: Whether the text is normal or **bold**. Overrides the
|
||||
`font_weight` from the `style`.
|
||||
|
||||
`underlined`: Whether the text is underlined or not. Overrides the
|
||||
`underlined` from the `style`.
|
||||
|
||||
`strikethrough`: Whether the text should have a line through it. Overrides
|
||||
the `strikethrough` from the `style`.
|
||||
|
||||
`all_caps`: Whether the text is transformed to ALL CAPS or not. Overrides
|
||||
the `all_caps` from the `style`.
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -84,6 +107,15 @@ class Text(FundamentalComponent):
|
||||
wrap: bool | t.Literal["ellipsize"] = False
|
||||
overflow: t.Literal["nowrap", "wrap", "ellipsize"] = "nowrap"
|
||||
|
||||
font: rio.Font | None = None
|
||||
fill: text_style._TextFill | None | utils.NotGiven = utils.NOT_GIVEN
|
||||
font_size: float | None = None
|
||||
italic: bool | None = None
|
||||
font_weight: t.Literal["normal", "bold"] | None = None
|
||||
underlined: bool | None = None
|
||||
strikethrough: bool | None = None
|
||||
all_caps: bool | None = None
|
||||
|
||||
def _custom_serialize_(self) -> JsonDoc:
|
||||
# Serialization doesn't handle unions. Hence the custom serialization
|
||||
# here
|
||||
@@ -109,10 +141,16 @@ class Text(FundamentalComponent):
|
||||
else:
|
||||
overflow = self.overflow
|
||||
|
||||
if isinstance(self.fill, utils.NotGiven):
|
||||
fill = "not given"
|
||||
else:
|
||||
fill = self.session._serialize_fill(self.fill)
|
||||
|
||||
# Build the result
|
||||
return {
|
||||
"style": style,
|
||||
"overflow": overflow,
|
||||
"fill": fill,
|
||||
}
|
||||
|
||||
def __repr__(self) -> str:
|
||||
|
||||
@@ -2601,7 +2601,7 @@ a.remove();
|
||||
"inherit" if style.font is None else style.font._serialize(self)
|
||||
)
|
||||
result[f"{css_prefix}-font-size"] = f"{style.font_size}rem"
|
||||
result[f"{css_prefix}-italic"] = (
|
||||
result[f"{css_prefix}-font-style"] = (
|
||||
"italic" if style.italic else "normal"
|
||||
)
|
||||
result[f"{css_prefix}-font-weight"] = style.font_weight
|
||||
|
||||
Reference in New Issue
Block a user