feat(figma-api): add getNodeByIdAsync() and getMainComponentAsync() (#779)

Scripts written for Figma's dynamic-page mode can call figma.getNodeByIdAsync() and instance.getMainComponentAsync(); both resolve to the same nodes as their synchronous forms. getMainComponentAsync joins the instance surface type check against @figma/plugin-typings.
This commit is contained in:
mrhard9090 2026-09-30 04:07:05 +03:00 committed by GitHub
parent 1aa10a4fed
commit bb86d2ad7f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 25 additions and 0 deletions

View file

@ -10,6 +10,7 @@
### Added
- Run scripts written for Figma's dynamic-page mode that call `figma.getNodeByIdAsync()` or `getMainComponentAsync()`; both resolve to the same nodes as their synchronous forms.
- Export components to Storybook with `openpencil export -f storybook`: one CSF3 story file per component set or component for React, Vue, or HTML, with a story and `select` controls per variant, a design image per variant, and an `openpencil://` link that opens the variant in OpenPencil. `--watch` re-exports on every save and removes stories of deleted components (#727).
- Export HTML and Tailwind JSX from the app's export options and through the IO registry, and export Tailwind JSX from the CLI with `-f tailwind-jsx` (`-f jsx --style tailwind` still works). HTML export of a single layer now includes the layer itself, as other formats do.
- Choose PPTX in the Export panel's format list, alongside PNG, JPG, WEBP, SVG, and PDF.

View file

@ -70,6 +70,7 @@ type InstancePropertySurfaceMatch = Expect<
| 'setProperties'
| 'isExposedInstance'
| 'exposedInstances'
| 'getMainComponentAsync'
>,
Pick<
InstanceNode,
@ -78,6 +79,7 @@ type InstancePropertySurfaceMatch = Expect<
| 'setProperties'
| 'isExposedInstance'
| 'exposedInstances'
| 'getMainComponentAsync'
>
>
>

View file

@ -137,6 +137,11 @@ export class FigmaAPI implements NodeProxyHost {
return node ? this.wrapNode(id) : null
}
/** The async lookup that Figma requires in dynamic-page mode; same result as getNodeById. */
async getNodeByIdAsync(id: string): Promise<FigmaNodeProxy | null> {
return this.getNodeById(id)
}
// --- Node Creation ---
private _createNode(type: NodeType): FigmaNodeProxy {

View file

@ -222,6 +222,11 @@ export class FigmaNodeProxy {
setPageBackgrounds(this[INTERNAL_GRAPH], this._raw(), value)
}
/** The async form Figma requires in dynamic-page mode; same result as mainComponent. */
async getMainComponentAsync(): Promise<FigmaNodeProxy | null> {
return this.mainComponent
}
get mainComponent(): FigmaNodeProxy | null {
const n = this._raw()
if (!n.componentId) return null

View file

@ -162,6 +162,7 @@ Common node properties are readable/writable through the proxy, including:
- `figma.createImage(data)`
- `figma.loadFontAsync(fontName)` no-ops because OpenPencil does not gate text edits on plugin font loading
- `figma.listAvailableFontsAsync()` returns host-provided fonts when available
- `figma.getNodeByIdAsync(id)` and `instance.getMainComponentAsync()` resolve to the same nodes as `figma.getNodeById(id)` and `instance.mainComponent`, for scripts written for Figma's dynamic-page mode
- `figma.notify(message)` logs a warning in headless mode
- `figma.viewport`

View file

@ -61,4 +61,15 @@ describe('components', () => {
expect(instance.type).toBe('INSTANCE')
expect(expectDefined(instance.mainComponent, 'instance main component').id).toBe(comp.id)
})
test('async lookups resolve like their synchronous forms', async () => {
const api = createAPI()
const component = api.createComponent()
const instance = component.createInstance()
expect((await api.getNodeByIdAsync(instance.id))?.id).toBe(instance.id)
expect(await api.getNodeByIdAsync('0:404')).toBeNull()
expect((await instance.getMainComponentAsync())?.id).toBe(component.id)
expect(await component.getMainComponentAsync()).toBeNull()
})
})