Files
Reactive-Resume/packages/resume/src/stylesheet/registry/properties.ts
T
2026-07-30 12:39:15 +02:00

343 lines
10 KiB
TypeScript

import type { SemanticNodeKind } from "../types";
import { SEMANTIC_NODE_KINDS } from "./semantic";
export type PropertyDefinition = {
category:
| "flexbox"
| "layout"
| "dimension"
| "color"
| "text"
| "image"
| "spacing"
| "border"
| "transform"
| "structural";
inheritable: boolean;
appliesTo: readonly SemanticNodeKind[];
values: readonly string[];
units: readonly string[];
};
export type PropertyRegistry = Readonly<Record<string, PropertyDefinition | undefined>>;
export const SEMANTIC_CSS_CSS_WIDE_KEYWORDS_V1 = ["inherit", "initial", "revert", "unset"] as const;
export const SEMANTIC_CSS_LENGTH_UNITS_V1 = ["pt", "px", "in", "mm", "cm", "%", "vw", "vh", "em", "rem"] as const;
export const SEMANTIC_CSS_BORDER_STYLE_VALUES_V1 = ["dotted", "dashed", "solid"] as const;
export const SEMANTIC_CSS_LENGTH_VALUE_KEYWORDS_V1 = [
"auto",
"none",
"normal",
"max-content",
"min-content",
"fit-content",
"thin",
"medium",
"thick",
] as const;
export const SEMANTIC_CSS_LENGTH_PROPERTIES_V1 = [
"bottom",
"border-bottom-left-radius",
"border-bottom-right-radius",
"border-bottom-width",
"border-left-width",
"border-radius",
"border-right-width",
"border-top-left-radius",
"border-top-right-radius",
"border-top-width",
"border-width",
"column-gap",
"flex-basis",
"font-size",
"gap",
"height",
"left",
"letter-spacing",
"margin-bottom",
"margin-left",
"margin-right",
"margin-top",
"max-height",
"max-width",
"min-height",
"min-width",
"padding-bottom",
"padding-left",
"padding-right",
"padding-top",
"right",
"row-gap",
"text-indent",
"top",
"width",
"-resume-min-presence-ahead",
"-resume-shadow-width",
] as const;
const containerNodes = [
"page",
"region",
"header",
"contact-list",
"contact-item",
"section",
"section-heading",
"section-items",
"item",
"item-header",
"rich-text",
"list",
"list-item",
"horizontal-rule",
"template-part",
] as const satisfies readonly SemanticNodeKind[];
const textNodes = [
"name",
"headline",
"section-heading",
"combined-text",
"field",
"rich-heading",
"blockquote",
"paragraph",
"list-item-content",
"list-marker",
"strong",
"emphasis",
"underline",
"strike",
"code",
"text-span",
"mark",
"hard-break",
] as const satisfies readonly SemanticNodeKind[];
const linkContainerNodes = [...containerNodes, "link"] as SemanticNodeKind[];
const textAndLinkNodes = [...textNodes, "link"] as SemanticNodeKind[];
const colorNodes = [...containerNodes, ...textNodes, "link", "icon", "level"] as SemanticNodeKind[];
const spacingNodes = [...containerNodes, ...textNodes, "link", "picture"] as SemanticNodeKind[];
const structuralNodes = [...SEMANTIC_NODE_KINDS.filter((kind) => kind !== "resume")] as SemanticNodeKind[];
const lengthProperties = new Set<string>(SEMANTIC_CSS_LENGTH_PROPERTIES_V1);
const numericLengthUnits = SEMANTIC_CSS_LENGTH_UNITS_V1.filter((unit) => unit !== "%");
const borderStyleProperties = /^(?:border-style|border-(?:top|right|bottom|left)-style)$/;
const borderShorthandProperties = /^(?:border|border-(?:top|right|bottom|left))$/;
const propertyValueHints = {
"align-content": ["flex-start", "flex-end", "center", "stretch", "space-between", "space-around", "space-evenly"],
"align-items": ["flex-start", "flex-end", "center", "stretch", "baseline"],
"align-self": ["auto", "flex-start", "flex-end", "center", "stretch", "baseline"],
flex: ["none", "auto"],
"flex-direction": ["row", "row-reverse", "column", "column-reverse"],
"flex-wrap": ["nowrap", "wrap", "wrap-reverse"],
"flex-flow": ["row", "row-reverse", "column", "column-reverse", "nowrap", "wrap", "wrap-reverse"],
"justify-content": ["flex-start", "flex-end", "center", "space-between", "space-around", "space-evenly"],
display: ["flex", "none"],
position: ["absolute", "relative", "static"],
overflow: ["hidden"],
direction: ["ltr", "rtl"],
"font-style": ["normal", "italic"],
"font-weight": [
"thin",
"hairline",
"ultralight",
"extralight",
"light",
"normal",
"medium",
"semibold",
"demibold",
"bold",
"ultrabold",
"extrabold",
"heavy",
"black",
],
"line-height": ["normal"],
"text-align": ["left", "right", "center", "justify"],
"text-decoration": ["none", "underline", "line-through", "line-through underline", "underline line-through"],
"text-overflow": ["ellipsis"],
"text-transform": ["none", "capitalize", "lowercase", "uppercase", "upperfirst"],
"vertical-align": ["sub", "super"],
"object-fit": ["contain", "cover", "fill", "none", "scale-down"],
"object-position": ["left", "right", "top", "bottom", "center"],
"transform-origin": ["left", "right", "top", "bottom", "center"],
"break-before": ["auto", "page"],
"break-inside": ["auto", "avoid"],
size: ["A4", "letter"],
"-resume-fixed": ["true", "false", "0", "1"],
} as const satisfies Readonly<Record<string, readonly string[]>>;
function propertyValues(name: string): readonly string[] {
const values = borderStyleProperties.test(name)
? SEMANTIC_CSS_BORDER_STYLE_VALUES_V1
: borderShorthandProperties.test(name)
? SEMANTIC_CSS_BORDER_STYLE_VALUES_V1.map((style) => `1pt ${style}`)
: (propertyValueHints[name as keyof typeof propertyValueHints] ?? []);
return [...SEMANTIC_CSS_CSS_WIDE_KEYWORDS_V1, ...values];
}
function propertyUnits(name: string): readonly string[] {
if (name === "size" || name === "-resume-min-presence-ahead") return numericLengthUnits;
return lengthProperties.has(name) || name === "line-height" ? SEMANTIC_CSS_LENGTH_UNITS_V1 : [];
}
function entries(
names: readonly string[],
definition: Omit<PropertyDefinition, "units" | "values">,
): Readonly<Record<string, PropertyDefinition | undefined>> {
return Object.fromEntries(
names.map((name) => [
name,
{
...definition,
appliesTo: [...new Set(definition.appliesTo)],
values: propertyValues(name),
units: propertyUnits(name),
},
]),
);
}
const properties = {
...entries(
[
"align-content",
"align-items",
"align-self",
"flex",
"flex-direction",
"flex-wrap",
"flex-flow",
"flex-grow",
"flex-shrink",
"flex-basis",
"justify-content",
"gap",
"row-gap",
"column-gap",
],
{ category: "flexbox", inheritable: false, appliesTo: linkContainerNodes },
),
...entries(["aspect-ratio", "bottom"], {
category: "layout",
inheritable: false,
appliesTo: linkContainerNodes,
}),
...entries(["display"], {
category: "layout",
inheritable: false,
appliesTo: [...linkContainerNodes, "list-item-content", "list-marker"],
}),
...entries(["left", "position", "right", "top", "overflow", "z-index"], {
category: "layout",
inheritable: false,
appliesTo: linkContainerNodes,
}),
...entries(["width", "height", "min-width", "min-height", "max-width", "max-height"], {
category: "dimension",
inheritable: false,
appliesTo: [...linkContainerNodes, "picture"],
}),
...entries(["color"], { category: "color", inheritable: true, appliesTo: colorNodes }),
...entries(["background-color"], { category: "color", inheritable: false, appliesTo: linkContainerNodes }),
...entries(["opacity"], { category: "color", inheritable: false, appliesTo: colorNodes }),
...entries(["direction"], { category: "text", inheritable: true, appliesTo: textAndLinkNodes }),
...entries(["font-size"], {
category: "text",
inheritable: true,
appliesTo: [...textAndLinkNodes, "icon", "level"],
}),
...entries(["font-style", "font-weight", "letter-spacing", "line-height"], {
category: "text",
inheritable: true,
appliesTo: textAndLinkNodes,
}),
...entries(["max-lines"], { category: "text", inheritable: false, appliesTo: textAndLinkNodes }),
...entries(["text-align"], { category: "text", inheritable: true, appliesTo: textAndLinkNodes }),
...entries(["text-decoration", "text-decoration-color", "text-decoration-style"], {
category: "text",
inheritable: false,
appliesTo: textAndLinkNodes,
}),
...entries(["text-indent"], { category: "text", inheritable: true, appliesTo: textAndLinkNodes }),
...entries(["text-overflow"], { category: "text", inheritable: false, appliesTo: textAndLinkNodes }),
...entries(["text-transform"], { category: "text", inheritable: true, appliesTo: textAndLinkNodes }),
...entries(["vertical-align"], { category: "text", inheritable: false, appliesTo: textAndLinkNodes }),
...entries(["object-fit", "object-position"], { category: "image", inheritable: false, appliesTo: ["picture"] }),
...entries(["-resume-shadow-color", "-resume-shadow-width"], {
category: "image",
inheritable: false,
appliesTo: ["picture"],
}),
...entries(
[
"margin",
"margin-top",
"margin-right",
"margin-bottom",
"margin-left",
"margin-horizontal",
"margin-vertical",
"padding",
"padding-top",
"padding-right",
"padding-bottom",
"padding-left",
"padding-horizontal",
"padding-vertical",
],
{ category: "spacing", inheritable: false, appliesTo: spacingNodes },
),
...entries(
[
"border",
"border-color",
"border-style",
"border-width",
"border-top",
"border-right",
"border-bottom",
"border-left",
"border-top-color",
"border-top-style",
"border-top-width",
"border-right-color",
"border-right-style",
"border-right-width",
"border-bottom-color",
"border-bottom-style",
"border-bottom-width",
"border-left-color",
"border-left-style",
"border-left-width",
"border-radius",
"border-top-left-radius",
"border-top-right-radius",
"border-bottom-right-radius",
"border-bottom-left-radius",
],
{ category: "border", inheritable: false, appliesTo: [...linkContainerNodes, "picture"] },
),
...entries(["transform", "transform-origin"], {
category: "transform",
inheritable: false,
appliesTo: [...linkContainerNodes, "picture"],
}),
...entries(["order", "break-before", "break-inside"], {
category: "structural",
inheritable: false,
appliesTo: structuralNodes,
}),
...entries(["orphans", "widows"], { category: "structural", inheritable: false, appliesTo: textNodes }),
...entries(["size"], { category: "structural", inheritable: false, appliesTo: ["page"] }),
...entries(["-resume-fixed"], { category: "structural", inheritable: false, appliesTo: structuralNodes }),
...entries(["-resume-min-presence-ahead"], {
category: "structural",
inheritable: false,
appliesTo: structuralNodes,
}),
} satisfies PropertyRegistry;
export const PROPERTY_REGISTRY_V1: PropertyRegistry = properties;