openpencil/packages/core/src/index.ts
Danila Poyarkov 45df90c5c7
CSS Grid layout mode (#73)
* Switch to @open-pencil/yoga-layout with CSS Grid support

Use our fork (open-pencil/yoga, grid branch) which cherry-picks the
upstream CSS Grid PRs (#1893–#1898) onto current main. The JS bindings
are ported from the old embind approach to the new wasm_bridge.c API.

npm:@open-pencil/yoga-layout alias keeps all imports as 'yoga-layout'.

Also handle FinalizationRegistry change (upstream #1908) — node.free()
no longer exists, nodes are garbage collected automatically.

* Add CSS Grid layout mode

Scene graph:
- LayoutMode gains 'GRID' option alongside HORIZONTAL/VERTICAL
- GridTrack type (sizing: FIXED/FR/AUTO, value) for track definitions
- GridPosition type (column, row, columnSpan, rowSpan) for children
- New node props: gridTemplateColumns/Rows, gridColumnGap, gridRowGap,
  gridPosition

Layout engine:
- buildGridTree() sets Display.Grid and maps GridTrack[] to Yoga's
  setGridTemplateColumns/Rows API with FR/Points/Auto track types
- Grid children use gridPosition for column/row placement with span
- Flex layout path unchanged

Store:
- setLayoutMode('GRID') auto-creates NxM track grid based on child
  count (sqrt heuristic), defaults to 1fr tracks

UI (LayoutSection.vue):
- Grid button (grid-2x2 icon) added to flow direction row
- Columns/Rows track editors: ScrubInput for value + AppSelect for
  sizing mode (Fill fr / Fixed px / Auto), add/remove buttons
- Separate column gap and row gap ScrubInputs
- Wrap button hidden when grid is active
- Flex alignment grid hidden when grid is active

Kiwi serialization skips GRID mode for now (no .fig codec support).

* Add grid layout integration tests

12 new test scenarios covering:
- Basic 2x2 grid, fixed columns, mixed fr/fixed, unequal fr weights
- Column gap, row gap, both gaps combined
- Padding offsets
- Explicit gridPosition placement, column span, row span
- Absolute children skipped
- Hidden children collapsed
- Nested grid inside flex parent (computeAllLayouts)

Also fix: grid frames as children of flex parents now correctly use
Display.Grid via configureChildAsGrid() instead of falling through
to configureChildAsAutoLayout() which set FlexDirection.

* Deduplicate grid yoga configuration

Extract configureAsGrid() and createGridChildNode() shared by
buildGridTree (root-level grid) and configureChildAsGrid (grid
nested inside flex parent).

* Add grid layout to changelog

* Add grid support to JSX and Tailwind CSS export

OpenPencil format: grid → columns/rows/columnGap/rowGap props,
child colStart/rowStart/colSpan/rowSpan.

Tailwind format: grid grid-cols-N grid-rows-N gap-x-* gap-y-*,
mixed tracks use arbitrary values (grid-cols-[200px_1fr_auto]),
child col-start-*/row-start-*/col-span-*/row-span-*.

Both formats: padding emitted for grid frames (shared with flex),
flex-only props (justify/items/wrap/gap) scoped to isFlex.

* Remove duplicate gridTrackToTw, use shared formatTrack

Also drop unused GridTrackSizing import from LayoutSection.vue.

* Fix grid icon: use layout-grid instead of grid-2x2

unplugin-icons can't resolve icon names with digits after hyphens
(grid-2x2 → grid2x2 which doesn't exist).

* Polish layout UI and fix auto-layout behavior

- Replace text labels with compact icons: ↔/↕ for gap, ☐ for uniform padding, T/R/B/L for per-side padding
- Direction-aware gap icon: ↕ for vertical, ↔ for horizontal
- Pin +/− padding toggle button right of gap input
- Fix alignment grid axes for vertical layout (transpose primary/counter)
- Fix grid switch: set FIXED sizing, compute frame size from children
- Remove hardcoded white fill from Shift+A wrap
- Auto-detect horizontal vs vertical from selection bounds

* Add flex-to-grid switch integration test

Verify HUG frame expands and children are placed in 2x2 grid
when switching from vertical flex to grid layout.

* Update changelog for grid layout
2026-03-08 18:23:53 +03:00

236 lines
5.3 KiB
TypeScript

export type { GUID, Color, Vector, Matrix, Rect } from './types'
export { degToRad, radToDeg, rotatePoint, rotatedCorners, rotatedBBox } from './geometry'
export * from './constants'
export {
SceneGraph,
generateId,
type SceneNode,
type NodeType,
type Fill,
type FillType,
type Stroke,
type StrokeCap,
type StrokeJoin,
type MaskType,
type Effect,
type BlendMode,
type ImageScaleMode,
type GradientStop,
type GradientTransform,
type LayoutMode,
type LayoutSizing,
type LayoutAlign,
type LayoutCounterAlign,
type LayoutWrap,
type GridTrack,
type GridTrackSizing,
type GridPosition,
type ConstraintType,
type TextAutoResize,
type TextAlignVertical,
type TextCase,
type TextDecoration,
type ArcData,
type VectorNetwork,
type VectorVertex,
type VectorSegment,
type VectorRegion,
type GeometryPath,
type HandleMirroring,
type WindingRule,
type VariableType,
type VariableValue,
type Variable,
type VariableCollection,
type VariableCollectionMode,
type CharacterStyleOverride,
type StyleRun
} from './scene-graph'
export { FigmaAPI, FigmaNodeProxy, type FigmaFontName } from './figma-api'
export { ALL_TOOLS, defineTool, toolsToAI } from './tools'
export type { ToolDef, ParamDef, ParamType } from './tools'
export { executeRpcCommand, ALL_RPC_COMMANDS } from './rpc'
export type {
InfoResult, PageItem, TreeArgs, TreeResult, TreeNodeResult,
FindArgs, FindNodeResult, NodeArgs, NodeResult,
VariablesArgs, VariablesResult,
AnalyzeColorsArgs, AnalyzeColorsResult,
AnalyzeTypographyArgs, AnalyzeTypographyResult,
AnalyzeSpacingResult, SpacingValue,
AnalyzeClustersArgs, AnalyzeClustersResult, TypographyStyle
} from './rpc'
export { SkiaRenderer, type RenderOverlays } from './renderer/index'
export {
RenderProfiler,
FrameStats,
GPUTimer,
DrawCallCounter,
PhaseTimer,
CaptureStack,
toSpeedscopeJSON
} from './profiler'
export type { FrameCapture, NodeProfile } from './profiler'
export { computeLayout, computeAllLayouts, setTextMeasurer } from './layout'
export type { TextMeasurer } from './layout'
export { getCanvasKit, getGpuBackend, type CanvasKitOptions, type GpuBackend } from './canvaskit'
export {
collectFontKeys,
loadFont,
listFamilies,
initFontService,
getFontProvider,
isFontLoaded,
markFontLoaded,
ensureNodeFont,
ensureCJKFallback,
getCJKFallbackFamily,
styleToWeight,
weightToStyle
} from './fonts'
export {
parseColor,
normalizeColor,
colorToHex,
colorToHex8,
colorToHexRaw,
colorToRgba255,
colorToCSS,
colorToCSSCompact,
rgba255ToColor,
colorToFill,
colorDistance
} from './color'
export {
vectorNetworkToPath,
geometryBlobToPath,
decodeVectorNetworkBlob,
encodeVectorNetworkBlob,
computeVectorBounds
} from './vector'
export { computeSelectionBounds, computeSnap, type SnapGuide } from './snap'
export { UndoManager } from './undo'
export { TextEditor, type TextCaret, type TextEditorState } from './text-editor'
export {
getStyleAt,
applyStyleToRange,
removeStyleFromRange,
selectionHasStyle,
toggleBoldInRange,
toggleItalicInRange,
toggleDecorationInRange,
adjustRunsForInsert,
adjustRunsForDelete
} from './style-runs'
export { renderNodesToImage, renderThumbnail, computeContentBounds, type ExportFormat } from './render-image'
export {
renderNodesToSVG,
geometryBlobToSVGPath,
vectorNetworkToSVGPaths,
type SVGExportOptions
} from './svg-export'
export { svg, renderSVGNode, type SVGNode } from './svg-node'
export { exportFigFile } from './fig-export'
export {
FIG_KIWI_VERSION,
buildFigKiwi,
parseFigKiwiChunks,
decompressFigKiwiData,
decompressFigKiwiDataAsync,
sceneNodeToKiwi,
fractionalPosition,
mapToFigmaType
} from './kiwi-serialize'
export {
createElement,
renderTree,
renderJSX,
renderTreeNode,
buildComponent,
Frame,
Text,
Rectangle,
Ellipse,
Line,
Star,
Polygon,
Vector as VectorNode,
Group,
Section,
View,
Rect as RectNode,
Component as ComponentNode,
Instance as InstanceNode,
Page as PageNode,
INTRINSIC_ELEMENTS,
isTreeNode,
resolveToTree,
node,
type TreeNode,
type BaseProps,
type TextProps,
type StyleProps,
type RenderResult,
sceneNodeToJSX,
selectionToJSX,
type JSXFormat
} from './render'
export {
parseFigmaClipboard,
importClipboardNodes,
figmaNodesBounds,
parseOpenPencilClipboard,
buildFigmaClipboardHTML,
buildOpenPencilClipboardHTML,
prefetchFigmaSchema,
type TextPictureBuilder
} from './clipboard'
export { readFigFile, parseFigFile } from './kiwi/fig-file'
export { importNodeChanges } from './kiwi/fig-import'
export {
initCodec,
encodeMessage,
decodeMessage,
compress,
decompress,
getCompiledSchema,
getSchemaBytes,
createNodeChangesMessage,
createNodeChange,
parseVariableId,
encodePaintWithVariableBinding,
encodeNodeChangeWithVariables,
type NodeChange,
type GUID as KiwiGUID,
type Color as KiwiColor,
type Paint as KiwiPaint,
type Effect as KiwiEffect,
type VariableBinding,
type ParentIndex,
type FigmaMessage
} from './kiwi/codec'
export {
MESSAGE_TYPES,
NODE_TYPES,
NODE_PHASES,
BLEND_MODES,
PAINT_TYPES,
PROTOCOL_VERSION,
KIWI,
SESSION_ID,
ZSTD_MAGIC,
buildMultiplayerUrl,
isZstdCompressed,
hasFigWireHeader,
skipFigWireHeader,
isKiwiMessage,
getKiwiMessageType,
parseVarint,
FIG_WIRE_MAGIC
} from './kiwi/protocol'