Skip to content

Commit 9c301c3

Browse files
authored
Add support for cross-real communication
2 parents 3bf77dd + ddac7c8 commit 9c301c3

File tree

6 files changed

+3649
-83
lines changed

6 files changed

+3649
-83
lines changed

figma.d.ts

Lines changed: 72 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
1+
// Figma Plugin API version 1, update 5
2+
13
// Global variable with Figma's plugin API.
24
declare const figma: PluginAPI
35
declare const __html__: string
46

57
interface PluginAPI {
68
readonly apiVersion: "1.0.0"
79
readonly command: string
8-
readonly root: DocumentNode
910
readonly viewport: ViewportAPI
1011
closePlugin(message?: string): void
1112

13+
notify(message: string, options?: NotificationOptions): NotificationHandler
14+
1215
showUI(html: string, options?: ShowUIOptions): void
1316
readonly ui: UIAPI
1417

@@ -17,8 +20,13 @@ interface PluginAPI {
1720
getNodeById(id: string): BaseNode | null
1821
getStyleById(id: string): BaseStyle | null
1922

23+
readonly root: DocumentNode
2024
currentPage: PageNode
2125

26+
on(type: "selectionchange" | "currentpagechange" | "close", callback: () => void)
27+
once(type: "selectionchange" | "currentpagechange" | "close", callback: () => void)
28+
off(type: "selectionchange" | "currentpagechange" | "close", callback: () => void)
29+
2230
readonly mixed: symbol
2331

2432
createRectangle(): RectangleNode
@@ -28,17 +36,27 @@ interface PluginAPI {
2836
createStar(): StarNode
2937
createVector(): VectorNode
3038
createText(): TextNode
31-
createBooleanOperation(): BooleanOperationNode
3239
createFrame(): FrameNode
3340
createComponent(): ComponentNode
3441
createPage(): PageNode
3542
createSlice(): SliceNode
43+
/**
44+
* [DEPRECATED]: This API often fails to create a valid boolean operation. Use figma.union, figma.subtract, figma.intersect and figma.exclude instead.
45+
*/
46+
createBooleanOperation(): BooleanOperationNode
3647

3748
createPaintStyle(): PaintStyle
3849
createTextStyle(): TextStyle
3950
createEffectStyle(): EffectStyle
4051
createGridStyle(): GridStyle
4152

53+
// The styles are returned in the same order as displayed in the UI. Only
54+
// local styles are returned. Never styles from team library.
55+
getLocalPaintStyles(): PaintStyle[]
56+
getLocalTextStyles(): TextStyle[]
57+
getLocalEffectStyles(): EffectStyle[]
58+
getLocalGridStyles(): GridStyle[]
59+
4260
importComponentByKeyAsync(key: string): Promise<ComponentNode>
4361
importStyleByKeyAsync(key: string): Promise<BaseStyle>
4462

@@ -53,35 +71,54 @@ interface PluginAPI {
5371

5472
group(nodes: ReadonlyArray<BaseNode>, parent: BaseNode & ChildrenMixin, index?: number): FrameNode
5573
flatten(nodes: ReadonlyArray<BaseNode>, parent?: BaseNode & ChildrenMixin, index?: number): VectorNode
74+
75+
union(nodes: ReadonlyArray<BaseNode>, parent: BaseNode & ChildrenMixin, index?: number): BooleanOperationNode
76+
subtract(nodes: ReadonlyArray<BaseNode>, parent: BaseNode & ChildrenMixin, index?: number): BooleanOperationNode
77+
intersect(nodes: ReadonlyArray<BaseNode>, parent: BaseNode & ChildrenMixin, index?: number): BooleanOperationNode
78+
exclude(nodes: ReadonlyArray<BaseNode>, parent: BaseNode & ChildrenMixin, index?: number): BooleanOperationNode
5679
}
5780

5881
interface ClientStorageAPI {
5982
getAsync(key: string): Promise<any | undefined>
6083
setAsync(key: string, value: any): Promise<void>
6184
}
6285

63-
type ShowUIOptions = {
86+
interface NotificationOptions {
87+
timeout?: number,
88+
}
89+
90+
interface NotificationHandler {
91+
cancel: () => void,
92+
}
93+
94+
interface ShowUIOptions {
6495
visible?: boolean,
6596
width?: number,
6697
height?: number,
98+
position?: 'default' | 'last' | 'auto' // PROPOSED API ONLY
6799
}
68100

69-
type UIPostMessageOptions = {
70-
targetOrigin?: string,
101+
interface UIPostMessageOptions {
102+
origin?: string,
71103
}
72104

73-
type OnMessageProperties = {
74-
sourceOrigin: string,
105+
interface OnMessageProperties {
106+
origin: string,
75107
}
76108

109+
type MessageEventHandler = (pluginMessage: any, props: OnMessageProperties) => void
110+
77111
interface UIAPI {
78112
show(): void
79113
hide(): void
80114
resize(width: number, height: number): void
81115
close(): void
82116

83117
postMessage(pluginMessage: any, options?: UIPostMessageOptions): void
84-
onmessage: ((pluginMessage: any, props: OnMessageProperties) => void) | undefined
118+
onmessage: MessageEventHandler | undefined
119+
on(type: "message", callback: MessageEventHandler)
120+
once(type: "message", callback: MessageEventHandler)
121+
off(type: "message", callback: MessageEventHandler)
85122
}
86123

87124
interface ViewportAPI {
@@ -161,13 +198,13 @@ interface ColorStop {
161198
}
162199

163200
interface ImageFilters {
164-
exposure?: number
165-
contrast?: number
166-
saturation?: number
167-
temperature?: number
168-
tint?: number
169-
highlights?: number
170-
shadows?: number
201+
readonly exposure?: number
202+
readonly contrast?: number
203+
readonly saturation?: number
204+
readonly temperature?: number
205+
readonly tint?: number
206+
readonly highlights?: number
207+
readonly shadows?: number
171208
}
172209

173210
interface SolidPaint {
@@ -297,7 +334,7 @@ interface VectorPath {
297334

298335
type VectorPaths = ReadonlyArray<VectorPath>
299336

300-
type LetterSpacing = {
337+
interface LetterSpacing {
301338
readonly value: number
302339
readonly unit: "PIXELS" | "PERCENT"
303340
}
@@ -340,7 +377,7 @@ interface Font {
340377
interface BaseNodeMixin {
341378
readonly id: string
342379
readonly parent: (BaseNode & ChildrenMixin) | null
343-
name: string // Note: setting this also sets `autoRename` to false on TextNodes
380+
name: string // Note: setting this also sets \`autoRename\` to false on TextNodes
344381
readonly removed: boolean
345382
toString(): string
346383
remove(): void
@@ -360,13 +397,13 @@ interface SceneNodeMixin {
360397
}
361398

362399
interface ChildrenMixin {
363-
readonly children: ReadonlyArray<BaseNode>
400+
readonly children: ReadonlyArray<SceneNode>
364401

365-
appendChild(child: BaseNode): void
366-
insertChild(index: number, child: BaseNode): void
402+
appendChild(child: SceneNode): void
403+
insertChild(index: number, child: SceneNode): void
367404

368-
findAll(callback?: (node: BaseNode) => boolean): ReadonlyArray<BaseNode>
369-
findOne(callback: (node: BaseNode) => boolean): BaseNode | null
405+
findAll(callback?: (node: SceneNode) => boolean): SceneNode[]
406+
findOne(callback: (node: SceneNode) => boolean): SceneNode | null
370407
}
371408

372409
interface ConstraintMixin {
@@ -426,7 +463,7 @@ interface CornerMixin {
426463
}
427464

428465
interface ExportMixin {
429-
exportSettings: ExportSettings[]
466+
exportSettings: ReadonlyArray<ExportSettings>
430467
exportAsync(settings?: ExportSettings): Promise<Uint8Array> // Defaults to PNG format
431468
}
432469

@@ -444,8 +481,16 @@ interface DefaultContainerMixin extends
444481
////////////////////////////////////////////////////////////////////////////////
445482
// Nodes
446483

447-
interface DocumentNode extends BaseNodeMixin, ChildrenMixin {
484+
interface DocumentNode extends BaseNodeMixin {
448485
readonly type: "DOCUMENT"
486+
487+
readonly children: ReadonlyArray<PageNode>
488+
489+
appendChild(child: PageNode): void
490+
insertChild(index: number, child: PageNode): void
491+
492+
findAll(callback?: (node: (PageNode | SceneNode)) => boolean): Array<PageNode | SceneNode>
493+
findOne(callback: (node: (PageNode | SceneNode)) => boolean): PageNode | SceneNode | null
449494
}
450495

451496
interface PageNode extends BaseNodeMixin, ChildrenMixin, ExportMixin {
@@ -454,6 +499,8 @@ interface PageNode extends BaseNodeMixin, ChildrenMixin, ExportMixin {
454499

455500
guides: ReadonlyArray<Guide>
456501
selection: ReadonlyArray<SceneNode>
502+
503+
backgrounds: ReadonlyArray<Paint>
457504
}
458505

459506
interface FrameNode extends DefaultContainerMixin {
@@ -652,4 +699,4 @@ interface GridStyle extends BaseStyle {
652699
interface Image {
653700
readonly hash: string
654701
getBytesAsync(): Promise<Uint8Array>
655-
}
702+
}

package.json

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,16 @@
99
"build": "npm run build:clean && npm run build:lib",
1010
"build:clean": "rimraf dist",
1111
"build:lib": "cross-env BABEL_ENV=production tsc -p tsconfig.json",
12-
"prepublish": "npm run build"
12+
"prepublish": "npm run build",
13+
"test": "jest"
14+
},
15+
"jest": {
16+
"preset": "ts-jest/presets/js-with-babel"
1317
},
1418
"pre-commit": [
1519
"prettify",
16-
"tsc"
20+
"tsc",
21+
"test"
1722
],
1823
"repository": {
1924
"type": "git",
@@ -30,10 +35,13 @@
3035
},
3136
"homepage": "https://github.com/react-figma/figma-api-stub#readme",
3237
"devDependencies": {
38+
"@types/jest": "^24.0.23",
3339
"cross-env": "^6.0.3",
40+
"jest": "^24.9.0",
3441
"pre-commit": "^1.2.2",
3542
"prettier": "^1.18.2",
3643
"rimraf": "^3.0.0",
44+
"ts-jest": "^24.2.0",
3745
"typescript": "3.5.3"
3846
}
3947
}

src/__tests__/postmessage.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { createParentPostMessage, createFigma } from "../stubs";
2+
3+
describe("postMessage", () => {
4+
beforeEach(() => {
5+
// @ts-ignore
6+
global.figma = createFigma({});
7+
// @ts-ignore
8+
global.parent.postMessage = createParentPostMessage(global.figma);
9+
});
10+
11+
it("UI sends message and plugin receives it", () => {
12+
figma.ui.onmessage = jest.fn();
13+
parent.postMessage({ pluginMessage: "abc" }, "*");
14+
15+
expect(figma.ui.onmessage).toHaveBeenCalledTimes(1);
16+
expect(figma.ui.onmessage).toHaveBeenCalledWith("abc", expect.any(Object));
17+
});
18+
19+
it("Plugin sends message and UI receives it", () => {
20+
//@ts-ignore
21+
global.onmessage = jest.fn();
22+
figma.ui.postMessage("abc");
23+
//@ts-ignore
24+
expect(global.onmessage).toHaveBeenCalledTimes(1);
25+
});
26+
});

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export { createFigma } from "./stubs";
1+
export { createFigma, createParentPostMessage } from "./stubs";

0 commit comments

Comments
 (0)