feat: add semantic CSS stylesheets (#3274)

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
This commit is contained in:
Amruth Pillai
2026-07-30 12:39:15 +02:00
committed by GitHub
co-authored by Cursor Agent
parent 4ac19f81b3
commit d2ffbf9618
320 changed files with 78393 additions and 2915 deletions
+198
View File
@@ -0,0 +1,198 @@
import type { SemanticNode } from "@reactive-resume/resume/stylesheet";
import type { StylesheetMode } from "@reactive-resume/schema/resume/stylesheet";
import type { ReactNode } from "react";
import type { ResolvedPdfNodePresentation } from "./adapter";
import { createContext, use, useMemo } from "react";
import { semanticNodeKeys } from "./node-keys";
export type ResolvedResumePresentation = Readonly<Record<string, ResolvedPdfNodePresentation>>;
type SemanticRenderProviderProps = {
presentation: ResolvedResumePresentation;
mode: StylesheetMode;
sourceTree: SemanticNode;
renderTree: SemanticNode;
children: ReactNode;
};
const EMPTY_NODE = Object.freeze({}) satisfies ResolvedPdfNodePresentation;
const SemanticRenderContext = createContext<{
presentation: ResolvedResumePresentation;
mode: StylesheetMode;
renderedNodeKeys: ReadonlySet<string>;
sourceNodeKeys: ReadonlySet<string>;
renderedChildKeys: ReadonlyMap<string, readonly string[]>;
sourceChildKeys: ReadonlyMap<string, readonly string[]>;
sourceNodes: readonly SemanticNode[];
renderOrder: ReadonlyMap<string, number>;
} | null>(null);
const SemanticNodeKeyContext = createContext<string | undefined>(undefined);
const flattenTree = (root: SemanticNode): SemanticNode[] => {
const nodes: SemanticNode[] = [];
const visit = (node: SemanticNode) => {
nodes.push(node);
for (const child of node.children) visit(child);
};
visit(root);
return nodes;
};
const indexRenderTree = (root: SemanticNode) => {
const keys = new Set<string>();
const childKeys = new Map<string, readonly string[]>();
const order = new Map<string, number>();
const visit = (node: SemanticNode) => {
keys.add(node.key);
order.set(node.key, order.size);
childKeys.set(
node.key,
node.children.map(({ key }) => key),
);
for (const child of node.children) visit(child);
};
visit(root);
return {
renderedNodeKeys: keys as ReadonlySet<string>,
renderedChildKeys: childKeys as ReadonlyMap<string, readonly string[]>,
renderOrder: order as ReadonlyMap<string, number>,
};
};
export function SemanticRenderProvider({
presentation,
mode,
sourceTree,
renderTree,
children,
}: SemanticRenderProviderProps) {
const treeIndex = useMemo(() => indexRenderTree(renderTree), [renderTree]);
const sourceNodes = useMemo(() => flattenTree(sourceTree), [sourceTree]);
const sourceNodeKeys = useMemo(
() => new Set(sourceNodes.map(({ key }) => key)) as ReadonlySet<string>,
[sourceNodes],
);
const sourceChildKeys = useMemo(
() =>
new Map(
sourceNodes.map(({ key, children: sourceChildren }) => [key, sourceChildren.map(({ key }) => key)]),
) as ReadonlyMap<string, readonly string[]>,
[sourceNodes],
);
const value = useMemo(
() => ({ presentation, mode, sourceNodeKeys, sourceChildKeys, sourceNodes, ...treeIndex }),
[mode, presentation, sourceChildKeys, sourceNodeKeys, sourceNodes, treeIndex],
);
return <SemanticRenderContext.Provider value={value}>{children}</SemanticRenderContext.Provider>;
}
export function useResolvedNode(nodeKey: string | undefined): ResolvedPdfNodePresentation {
const context = use(SemanticRenderContext);
if (!context || !nodeKey) return EMPTY_NODE;
return context.presentation[nodeKey] ?? EMPTY_NODE;
}
export const useSemanticRenderMode = (): StylesheetMode => use(SemanticRenderContext)?.mode ?? "legacy";
export const useSemanticNodeVisible = (nodeKey: string | undefined): boolean => {
const context = use(SemanticRenderContext);
if (context?.mode !== "semantic" || !nodeKey) return true;
return context.renderedNodeKeys.has(nodeKey);
};
export const useSemanticNodeExists = (nodeKey: string | undefined): boolean => {
const context = use(SemanticRenderContext);
return Boolean(nodeKey && context?.sourceNodeKeys.has(nodeKey));
};
export const useRenderedChildKeys = (nodeKey: string | undefined): readonly string[] | undefined => {
const context = use(SemanticRenderContext);
if (context?.mode !== "semantic" || !nodeKey) return undefined;
const rendered = context.renderedChildKeys.get(nodeKey) ?? [];
const source = context.sourceChildKeys.get(nodeKey) ?? [];
return rendered.length === source.length && rendered.every((key, index) => key === source[index])
? undefined
: rendered;
};
export type RenderedChildEntry<T> = {
nodeKey: string;
value: T;
};
export const projectRenderedChildren = <T,>(
renderedChildKeys: readonly string[] | undefined,
entries: readonly RenderedChildEntry<T>[],
): T[] => {
if (!renderedChildKeys) return entries.map(({ value }) => value);
const valuesByNodeKey = new Map(entries.map(({ nodeKey, value }) => [nodeKey, value]));
return renderedChildKeys.flatMap((nodeKey) => {
const value = valuesByNodeKey.get(nodeKey);
return value === undefined ? [] : [value];
});
};
export const useSemanticSectionNodeKey = (pageNodeKey: string, sectionId: string): string => {
const context = use(SemanticRenderContext);
const section = context?.sourceNodes.find(
(node) => node.kind === "section" && node.id === sectionId && node.key.startsWith(`${pageNodeKey}/`),
);
return section?.key ?? semanticNodeKeys.section(semanticNodeKeys.region(pageNodeKey, "main"), sectionId);
};
export const useRenderedSectionIds = (pageNodeKey: string, authoredIds: readonly string[]): string[] => {
const context = use(SemanticRenderContext);
if (context?.mode !== "semantic") return [...authoredIds];
const authored = new Set(authoredIds);
return context.sourceNodes
.filter(
(node) =>
node.kind === "section" &&
node.id !== undefined &&
authored.has(node.id) &&
node.key.startsWith(`${pageNodeKey}/`) &&
context.renderedNodeKeys.has(node.key),
)
.sort(
(left, right) =>
(context.renderOrder.get(left.key) ?? Number.MAX_SAFE_INTEGER) -
(context.renderOrder.get(right.key) ?? Number.MAX_SAFE_INTEGER),
)
.flatMap(({ id }) => (id ? [id] : []));
};
export const useSemanticNodeBindings = () => {
const context = use(SemanticRenderContext);
return {
resolveNode: (nodeKey: string | undefined): ResolvedPdfNodePresentation => {
if (!context || !nodeKey) return EMPTY_NODE;
return context.presentation[nodeKey] ?? EMPTY_NODE;
},
isNodeVisible: (nodeKey: string | undefined): boolean => {
if (context?.mode !== "semantic" || !nodeKey) return true;
return context.renderedNodeKeys.has(nodeKey);
},
renderedChildKeysFor: (nodeKey: string | undefined): readonly string[] | undefined => {
if (context?.mode !== "semantic" || !nodeKey) return undefined;
const rendered = context.renderedChildKeys.get(nodeKey) ?? [];
const source = context.sourceChildKeys.get(nodeKey) ?? [];
return rendered.length === source.length && rendered.every((key, index) => key === source[index])
? undefined
: rendered;
},
};
};
export function SemanticNodeKeyProvider({ nodeKey, children }: { nodeKey: string | undefined; children: ReactNode }) {
return <SemanticNodeKeyContext.Provider value={nodeKey}>{children}</SemanticNodeKeyContext.Provider>;
}
export const useSemanticNodeKey = (): string | undefined => use(SemanticNodeKeyContext);
export function SemanticItemNodeKeyProvider({ itemId, children }: { itemId: string; children: ReactNode }) {
const parentNodeKey = useSemanticNodeKey();
const nodeKey = parentNodeKey ? semanticNodeKeys.item(parentNodeKey, itemId) : undefined;
return <SemanticNodeKeyProvider nodeKey={nodeKey}>{children}</SemanticNodeKeyProvider>;
}