Skip to main content
Glama
1yhy
by 1yhy
validation.ts5.05 kB
/** * Validation Utilities * * Type guards, validators, and visibility checks for Figma nodes. * * @module utils/validation */ import type { Rectangle, HasLayoutTrait, StrokeWeights, HasFramePropertiesTrait, } from "@figma/rest-api-spec"; import { isTruthy } from "remeda"; import type { CSSHexColor, CSSRGBAColor } from "~/types/index.js"; export { isTruthy }; // ==================== Visibility Types ==================== /** Properties for visibility checking */ export interface VisibilityProperties { visible?: boolean; opacity?: number; absoluteBoundingBox?: { x: number; y: number; width: number; height: number }; absoluteRenderBounds?: { x: number; y: number; width: number; height: number } | null; } /** Properties for parent container clipping check */ export interface ParentClipProperties { clipsContent?: boolean; absoluteBoundingBox?: { x: number; y: number; width: number; height: number }; } // ==================== Visibility Checks ==================== /** * Check if an element is visible */ export function isVisible(element: VisibilityProperties): boolean { if (element.visible === false) { return false; } if (element.opacity === 0) { return false; } if (element.absoluteRenderBounds === null) { return false; } return true; } /** * Check if an element is visible within its parent container (considering clipping) */ export function isVisibleInParent( element: VisibilityProperties, parent: ParentClipProperties, ): boolean { if (!isVisible(element)) { return false; } if ( parent && parent.clipsContent === true && element.absoluteBoundingBox && parent.absoluteBoundingBox ) { const elementBox = element.absoluteBoundingBox; const parentBox = parent.absoluteBoundingBox; const outsideParent = elementBox.x >= parentBox.x + parentBox.width || elementBox.x + elementBox.width <= parentBox.x || elementBox.y >= parentBox.y + parentBox.height || elementBox.y + elementBox.height <= parentBox.y; if (outsideParent) { return false; } } return true; } // ==================== Object Processing ==================== /** * Remove empty arrays and empty objects from an object */ export function removeEmptyKeys<T>(input: T): T { if (typeof input !== "object" || input === null) { return input; } if (Array.isArray(input)) { return input.map((item) => removeEmptyKeys(item)) as T; } const result = {} as T; for (const key in input) { if (Object.prototype.hasOwnProperty.call(input, key)) { const value = input[key]; const cleanedValue = removeEmptyKeys(value); if ( cleanedValue !== undefined && !(Array.isArray(cleanedValue) && cleanedValue.length === 0) && !( typeof cleanedValue === "object" && cleanedValue !== null && Object.keys(cleanedValue).length === 0 ) ) { result[key] = cleanedValue; } } } return result; } // ==================== Type Guards ==================== export function hasValue<K extends PropertyKey, T>( key: K, obj: unknown, typeGuard?: (val: unknown) => val is T, ): obj is Record<K, T> { const isObject = typeof obj === "object" && obj !== null; if (!isObject || !(key in obj)) return false; const val = (obj as Record<K, unknown>)[key]; return typeGuard ? typeGuard(val) : val !== undefined; } export function isFrame(val: unknown): val is HasFramePropertiesTrait { return ( typeof val === "object" && !!val && "clipsContent" in val && typeof val.clipsContent === "boolean" ); } export function isLayout(val: unknown): val is HasLayoutTrait { return ( typeof val === "object" && !!val && "absoluteBoundingBox" in val && typeof val.absoluteBoundingBox === "object" && !!val.absoluteBoundingBox && "x" in val.absoluteBoundingBox && "y" in val.absoluteBoundingBox && "width" in val.absoluteBoundingBox && "height" in val.absoluteBoundingBox ); } export function isStrokeWeights(val: unknown): val is StrokeWeights { return ( typeof val === "object" && val !== null && "top" in val && "right" in val && "bottom" in val && "left" in val ); } export function isRectangle<T, K extends string>( key: K, obj: T, ): obj is T & { [P in K]: Rectangle } { const recordObj = obj as Record<K, unknown>; return ( typeof obj === "object" && !!obj && key in recordObj && typeof recordObj[key] === "object" && !!recordObj[key] && "x" in recordObj[key] && "y" in recordObj[key] && "width" in recordObj[key] && "height" in recordObj[key] ); } export function isRectangleCornerRadii(val: unknown): val is number[] { return Array.isArray(val) && val.length === 4 && val.every((v) => typeof v === "number"); } export function isCSSColorValue(val: unknown): val is CSSRGBAColor | CSSHexColor { return typeof val === "string" && (val.startsWith("#") || val.startsWith("rgba")); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/1yhy/Figma-Context-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server