import type { DesignDocument, DocumentDiagnostic, DocumentDuplicateMetadataRemapper, DocumentNode, DocumentNodePatch, DocumentTransaction, DocumentValue } from './contracts.js'; import { type Paint, type Effect } from './appearance.js'; import { type LayoutGrid } from '../workspace/edit/canvasGuideModel.js'; export type { LayoutGrid } from '../workspace/edit/canvasGuideModel.js'; import { type RichTextCase } from './richText.js'; /** * Local and shared styles. * * A style is a named, reusable value of one kind (paint, text, effect, grid) stored once in * `document.metadata.styles`. A node references a style by id in `node.properties.styles[slot]` * and always keeps the *projected* value in its ordinary properties (`fill`, `color`, typography * keys, `effects`, `layoutGrids`), so the canvas, the visual compiler, and every other module render * and compile a styled node without knowing about styles at all. Editing a style re-projects it into * every consumer; detaching removes the reference and keeps the projected value, exactly as Figma does. * * Shared styles come from a `StyleLibraryDocument`. An imported style remembers its origin; applying a * newer library revision re-projects changed styles and reports the affected consumers before anything * is written. */ export type StyleKind = 'paint' | 'text' | 'effect' | 'grid'; export declare const STYLE_KINDS: readonly StyleKind[]; /** Where a style can attach on a node. `fill` and `color` are both paint slots (layer fill, text colour). */ export type StyleSlot = 'fill' | 'color' | 'text' | 'effect' | 'grid'; export declare const STYLE_SLOTS: readonly StyleSlot[]; export declare const STYLE_SLOT_KIND: Readonly>; export declare const STYLE_KIND_LABEL: Readonly>; export declare const STYLE_SLOT_LABEL: Readonly>; /** Paint styles reuse the appearance model, including ordered multi-paint stacks. */ export type StylePaint = Paint; export type PaintStyleValue = StylePaint | { paints: StylePaint[]; }; /** Node-level typography keys a text style may set. Colour is a paint style, links and lists are content. */ export interface TextStyleValue { fontFamily?: string; fontWeight?: number; fontSize?: number; italic?: boolean; underline?: boolean; strikethrough?: boolean; textCase?: RichTextCase; lineHeight?: number; letterSpacing?: number; indent?: number; } export declare const TEXT_STYLE_CHARACTER_KEYS: readonly ["fontFamily", "fontWeight", "fontSize", "italic", "underline", "strikethrough", "textCase"]; export declare const TEXT_STYLE_PARAGRAPH_KEYS: readonly ["lineHeight", "letterSpacing", "indent"]; export declare const TEXT_STYLE_KEYS: readonly (keyof TextStyleValue)[]; export type StyleEffect = Effect; export interface EffectStyleValue { effects: StyleEffect[]; } export interface GridStyleValue { grids: LayoutGrid[]; } export type StyleValue = PaintStyleValue | TextStyleValue | EffectStyleValue | GridStyleValue; export interface StyleValueByKind { paint: PaintStyleValue; text: TextStyleValue; effect: EffectStyleValue; grid: GridStyleValue; } export interface StyleOrigin { libraryId: string; styleId: string; revision: string; } export interface DesignStyle { id: string; kind: StyleKind; name: string; value: StyleValue; description?: string; origin?: StyleOrigin; } export interface StylesMetadata { version: 1; styles: Record; libraries?: Record; } /** A published set of styles. Ids are stable across revisions; a document imports the ones it uses. */ export interface StyleLibraryDocument { schemaVersion: 1; id: string; revision: string; styles: Record>; } export type StyleReferences = Partial>; export type StyleConsumer = { kind: 'node'; nodeId: string; slot: StyleSlot; } | { kind: 'override'; instanceId: string; definitionNodeId: string; slot: StyleSlot; }; export type StylePropagation = { kind: 'node'; nodeId: string; properties: Record; } | { kind: 'override'; instanceId: string; definitionNodeId: string; properties: Record; }; export interface StyleLibraryChange { styleId: string; name: string; kind: StyleKind; status: 'updated' | 'renamed' | 'removed' | 'unchanged' | 'unavailable'; consumers: StyleConsumer[]; } export interface StyleLibraryUpdatePlan { documentFingerprint: string; libraryId: string; revision: string; metadata: StylesMetadata; propagations: StylePropagation[]; changes: StyleLibraryChange[]; } export declare class StyleError extends Error { readonly code: string; readonly styleId?: string; readonly nodeId?: string; constructor(code: string, message: string, location?: { styleId?: string; nodeId?: string; }); } export declare function isStylePaint(value: unknown): value is StylePaint; export declare function isTextStyleValue(value: unknown): value is TextStyleValue; export declare function isStyleEffect(value: unknown): value is StyleEffect; export declare function isLayoutGrid(value: unknown): value is LayoutGrid; export declare function isStyleValue(kind: K, value: unknown): value is StyleValueByKind[K]; export declare function isDesignStyle(value: unknown): value is DesignStyle; export declare function isStylesMetadata(value: unknown): value is StylesMetadata; export declare function isStyleReferences(value: unknown): value is StyleReferences; export declare function validateStyleLibrary(input: unknown): DocumentDiagnostic[]; export declare const EMPTY_STYLES: StylesMetadata; /** The document's styles, or an empty set; an invalid sidecar is refused rather than guessed. */ export declare function documentStyles(document: DesignDocument): StylesMetadata; export declare function documentStyle(document: DesignDocument, styleId: string): DesignStyle; export declare function documentStylesOfKind(document: DesignDocument, kind: StyleKind): DesignStyle[]; export declare function nodeStyleReferences(node: Pick): StyleReferences; /** Slots that make sense for a layer kind; the panel offers exactly these. */ export declare function styleSlotsFor(node: Pick & Partial>): StyleSlot[]; /** Validation of the sidecar and every reference; part of the document-level diagnostics a host may run on load. */ export declare function documentStyleDiagnostics(document: DesignDocument): DocumentDiagnostic[]; export declare function createStyle(metadata: StylesMetadata, style: DesignStyle): StylesMetadata; export declare function renameStyle(metadata: StylesMetadata, styleId: string, name: string): StylesMetadata; export declare function describeStyle(metadata: StylesMetadata, styleId: string, description: string | undefined): StylesMetadata; export declare function setStyleValue(metadata: StylesMetadata, styleId: string, value: StyleValue): StylesMetadata; export declare function removeStyle(metadata: StylesMetadata, styleId: string): StylesMetadata; /** Cuts a shared style loose from its library: it stays in the document as a local style. */ export declare function detachStyleFromLibrary(metadata: StylesMetadata, styleId: string): StylesMetadata; /** * Writes a style's value into a node's properties and records the reference. Text styles pass through the * typography normalizer so a key set on the layer is cleared from every run and the plain projection stays exact. */ export declare function projectStyle(properties: Readonly>, style: DesignStyle, slot: StyleSlot, kind?: DocumentNode['kind']): Record; /** Removes the reference and keeps the projected value on the layer. */ export declare function detachStyleReference(properties: Readonly>, slot: StyleSlot): Record; /** Reads a style value back out of a layer, so a style can be created from the selection. */ export declare function styleValueFromNode(node: Pick, kind: StyleKind, slot?: StyleSlot): StyleValue; export declare function styleConsumers(document: DesignDocument, styleId: string): StyleConsumer[]; /** * Re-projects a style (its current value, or `value` when the caller is about to change it) into every consumer. * With `value === null` the consumers are detached instead: the reference goes, the projected values stay. */ export declare function propagateStyle(document: DesignDocument, styleId: string, value?: StyleValue | null | undefined): StylePropagation[]; export declare function applyStylePropagation(tx: DocumentTransaction, propagations: readonly StylePropagation[]): void; /** Writes the sidecar; an empty set removes the key so documents without styles carry no metadata. */ export declare function writeStyles(tx: DocumentTransaction, metadata: StylesMetadata): void; export declare function applyStylePatch(document: DesignDocument, nodeId: string, slot: StyleSlot, styleId: string): DocumentNodePatch; export declare function detachStylePatch(document: DesignDocument, nodeId: string, slot: StyleSlot): DocumentNodePatch; /** Creates a style and, when a layer is given, applies it to that layer in the same transaction. */ export declare function commitCreateStyle(tx: DocumentTransaction, document: DesignDocument, style: DesignStyle, applyTo?: { nodeId: string; slot: StyleSlot; }): void; export declare function commitStyleValue(tx: DocumentTransaction, document: DesignDocument, styleId: string, value: StyleValue): void; export declare function commitRenameStyle(tx: DocumentTransaction, document: DesignDocument, styleId: string, name: string): void; /** Deleting a style detaches its consumers; their projected values stay, as in Figma. */ export declare function commitRemoveStyle(tx: DocumentTransaction, document: DesignDocument, styleId: string): void; export declare function commitApplyStyle(tx: DocumentTransaction, document: DesignDocument, nodeId: string, slot: StyleSlot, styleId: string): void; export declare function commitDetachStyle(tx: DocumentTransaction, document: DesignDocument, nodeId: string, slot: StyleSlot): void; /** Figma's "update style from selection": the layer's current value becomes the style's value everywhere. */ export declare function commitStyleFromNode(tx: DocumentTransaction, document: DesignDocument, styleId: string, nodeId: string, slot: StyleSlot): void; /** Imports one library style as a shared style; the local id defaults to the library's stable id. */ export declare function importLibraryStyle(metadata: StylesMetadata, library: StyleLibraryDocument, styleId: string, localId?: string): StylesMetadata; /** * The diff of a newer library revision against the document: which imported styles change, which were removed * from the library (they stay as local styles), and which consumers each change reaches. Nothing is written. */ export declare function planStyleLibraryUpdate(document: DesignDocument, library: StyleLibraryDocument): StyleLibraryUpdatePlan; export declare function commitStyleLibraryUpdate(tx: DocumentTransaction, document: DesignDocument, plan: StyleLibraryUpdatePlan): void; /** * Styles never reference layers, so duplicating a subtree keeps the sidecar unchanged. Wrap a host remapper * with this so the style sidecar survives duplication next to host-owned sidecars. */ export declare function stylesDuplicateRemapper(inner?: DocumentDuplicateMetadataRemapper): DocumentDuplicateMetadataRemapper; /** The only metadata this module writes; a duplicate guard can treat this key as remap-free. */ export declare const STYLES_METADATA_KEY = "styles"; /** Node property keys owned by this module; they are sidecars with no source representation of their own. */ export declare const STYLE_SIDECAR_PROPERTY_KEYS: readonly string[]; /** * Source review: a style reference has no representation in code — the projected value is what compiles. These * diagnostics name a changed reference (or a changed style set) so the review controller can explain a blocked * review instead of reporting an unmapped field, mirroring the typography diagnostics. */ export declare function stylesSourceDiagnostics(after: DesignDocument, before: DesignDocument): DocumentDiagnostic[];