From d13d6dcd645b288bad6f32e6354c670e5c36e701 Mon Sep 17 00:00:00 2001 From: Pablo Klaschka Date: Wed, 20 May 2020 15:20:38 +0200 Subject: [PATCH 01/13] =?UTF-8?q?=E2=80=94=20PerPluginStorage,=20the=20int?= =?UTF-8?q?erface?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- types/scenegraph.d.ts | 125 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/types/scenegraph.d.ts b/types/scenegraph.d.ts index 34e4a99..30319b4 100644 --- a/types/scenegraph.d.ts +++ b/types/scenegraph.d.ts @@ -10,6 +10,122 @@ declare interface ScaleFactor { scaleY: number; } +/** + * (**Since:** XD 29) + * + * Stores metadata accessible to multiple plugins, separated into silos by plugin ID. Your plugin can read & write the storage for its own plugin ID, but storage for other plugin IDs is *read-only*. + * + * Each per-plugin storage silo is a collection of key-value pairs. Keys and values must both be strings. + * + * *Each* scenenode has its own metadata storage, accessed via `SceneNode.sharedPluginData`. To store general metadata that is not specific to one scenenode, use `sharedPluginData` on the document's scenegraph root. + * @example ```js + * // This example shows how to save & retrieve rich JSON data in shared metadata storage. + // See below for simpler examples of using individual APIs. + const PLUGIN_ID = ""; + let richObject = { + list: [2, 4, 6], + name: "Hello world" + }; + node.sharedPluginData.setItem(PLUGIN_ID, "richData", JSON.stringify(richObject)); + + // Later on... + // (This could be in a different plugin, if it passes the original plugin's ID here) + let jsonString = node.sharedPluginData.getItem(PLUGIN_ID, "richData"); + if (jsonString) { // may be undefined + let richObjectCopy = JSON.parse(jsonString); + console.log(richObjectCopy.list.length); // 3 + } + */ +declare interface PerPluginStorage { + /** + * Returns a map where key is plugin ID and value is a nested map containing all the shared metadata for that plugin ID (i.e. the result of calling `getForPluginId()` with that ID). + */ + getAll(): { [key: string]: {[key:string]: string} }; + + /** + * Returns a map of key-value string pairs containing all shared metadata stored on this node by the given plugin. May be an empty object (zero keys), but is never null. + * + * This map is a clone of the stored metadata, so modifying it has no effect. + * @param pluginId + * + * @example + *const MY_PLUGIN_ID = ""; +let mySharedMetadata = node.sharedPluginData.getForPluginId(MY_PLUGIN_ID); +console.log("My shared 'foo' & 'bar' values:", + mySharedMetadata.foo, mySharedMetadata.bar); + + +console.log("Plugin B's shared 'foo' value:", + node.sharedPluginData.getForPluginId("B").foo); + */ + getForPluginId(pluginId: string): {[key:string]: string}; + + /** + * Returns a list of all keys stored on this node by the given plugin. May be empty (length zero), but is never null. + * @param pluginId + * + * @example + * console.log("All properties stored by plugin A on this node:", + node.sharedPluginData.keys("A")); + */ + keys(pluginId: string): string[]; + + /** + * Returns the value stored under the given key on this node by the given plugin, or `undefined` if the plugin hasn't stored anything under the given key. + * + * Because metadata is stored separately per plugin, two plugins can store two different values under the same key. + * @param pluginId + * @param key + * + * @example + * // These are two different values, stored independently per plugin + console.log("Plugin A's 'foo' value:", node.sharedPluginData.getItem("A", "foo")); + console.log("Plugin B's 'foo' value:", node.sharedPluginData.getItem("B", "foo")); + */ + getItem(pluginId: string, key: string): string | undefined; + + /** + * Set a metadata key which can be read by any other plugin. + * @param pluginId *Must* be equal to your plugin's ID. + * @param key + * @param value If undefined, behaves as if you'd called `removeItem()` instead. + * + * @example +const MY_PLUGIN_ID = ""; +node.sharedPluginData.setItem(MY_PLUGIN_ID, "foo", "42"); + +node.sharedPluginData.setItem("other_plugin_id", "foo", "42"); +// ^ ERROR: other plugin's metadata is read-only + +console.log(node.sharedPluginData.getItem(MY_PLUGIN_ID, "foo")); // "42" + */ + setItem(pluginId: string, key: string, value: string | undefined): void; + + /** + * Clears a shared metadata key stored by your plugin. + * @param pluginId *Must* be equal to your plugin's ID. + * @param key + * @example +const MY_PLUGIN_ID = ""; +node.sharedPluginData.setItem(MY_PLUGIN_ID, "foo", "42"); +console.log(node.sharedPluginData.getItem(MY_PLUGIN_ID, "foo")); // "42" + +node.sharedPluginData.removeItem(MY_PLUGIN_ID, "foo"); +console.log(node.sharedPluginData.getItem(MY_PLUGIN_ID, "foo")); // undefined + */ + removeItem(pluginId: string, key:string): void; + + /** + * Provided for convenience: you can `console.log(node.sharedPluginData)` to see the value of `getAll()`. + */ + toString(): string; + + /** + * Provided for convenience: you can include a `PerPluginStorage` object inside data you are going to convert to JSON, even though it is not a plain JavaScript object. Returns the same value as `getAll()`. + */ + toJSON(): object; +} + /** * Represents the children of a scenenode. Typically accessed via the SceneNode.children property. */ @@ -319,6 +435,15 @@ export class ImageFill { */ scaleBehaviour: string; + // TODO: assetId: string (readonly?) + /** + * (**Since**: XD 29) + * + * A unique identifier for the image asset used by this ImageFill. May be shared by other ImageFills, including those with different cropping, size, + rotation, or mirroring. If identical images are imported into XD from separate sources, they may have different `assetId`s however. + */ + assetId: string; + /** * Format the image data was originally encoded in, such as `image/gif` or `image/jpeg`. */ From 73fcf51e91b98dbab967d05158207bfacba9c1a6 Mon Sep 17 00:00:00 2001 From: Pablo Klaschka Date: Wed, 20 May 2020 15:27:40 +0200 Subject: [PATCH 02/13] application module updates --- types/application.d.ts | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/types/application.d.ts b/types/application.d.ts index ccc050f..eaa21a3 100644 --- a/types/application.d.ts +++ b/types/application.d.ts @@ -48,6 +48,17 @@ type RenditionResult = { outputFile: File; } +type DocumentInfo = { + /** + * Document name as displayed in the titlebar. For untitled documents, this will be a localized string such as "Untitled-1." + */ + name: string, + /** + * *Semi*-unique document identifier. Duplicating an .xd file on disk will result in two files with the same GUID. Duplicating a document via "Save As" will change its GUID; thus two *cloud* documents will never have the same GUID. The GUID of an Untitled document doesn't change when it is saved for the first time.

This returns the same value as `scenegraph.root.guid`. + */ + guid: string +}; + /** * Generate renditions of nodes in the document in a batch. Overwrites any existing files without warning. * @@ -72,3 +83,21 @@ export const appLanguage: string; * User's OS-wide locale setting. May not match the XD UI, since XD does not support all world languages. Includes both language and region (e.g. "fr_CA" or "en_US"). */ export const systemLocale: string; + +/** + * Information about the document which this instance of the plugin is attached to. + * + * > **Tip** + * > + * > _This does **not** indicate the frontmost "active" document window in the XD application._ + * > + * > In XD, each document window loads a separate copy of your plugin. When a given instance of your plugin calls this API, you will always receive information about the document that this instance of the plugin is attached to, even if it's not the active window. + * + * @example ```js +let application = require("application"); +let documentInfo = application.activeDocument; +console.log("Document title: " + documentInfo.name); +console.log("Document ID: " + documentInfo.guid); + ``` + */ +export const activeDocument: DocumentInfo; From bdde17e3903de15374e9ef307b20d4f81a45b9cb Mon Sep 17 00:00:00 2001 From: Pablo Klaschka Date: Wed, 20 May 2020 15:31:05 +0200 Subject: [PATCH 03/13] interactions module updates --- types/interactions.d.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/types/interactions.d.ts b/types/interactions.d.ts index 724672c..a1d8935 100644 --- a/types/interactions.d.ts +++ b/types/interactions.d.ts @@ -11,7 +11,9 @@ export const homeArtboard: Artboard | null; * * May include interactions that are impossible to trigger because the trigger node (or one of its ancestors) has `visible` = false. * - * Note: currently, this API excludes all of the document's keyboard/gamepad interactions. + * > **Tip** + * > + * > Currently, this API excludes some types of interactions: keypress/gamepad, scrolling, hover, component state transitions, or non-speech audio playback. */ export const allInteractions: Array<{ triggerNode: SceneNode, interactions: Array }>; From 60e181e9a2eec83788f1020d038f51c135459f88 Mon Sep 17 00:00:00 2001 From: Pablo Klaschka Date: Wed, 20 May 2020 15:37:13 +0200 Subject: [PATCH 04/13] Selection interface updates --- types/index.d.ts | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/types/index.d.ts b/types/index.d.ts index 2d9e97a..389ac1c 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1,4 +1,4 @@ -import {Artboard, SceneNode} from "./scenegraph"; +import {Artboard, SceneNode} from "scenegraph"; declare global { /** @@ -7,7 +7,7 @@ declare global { */ function require(module: string): void; - let module: {exports:any}; + let module: { exports: any }; /** * The selection object represents the currently selected set of nodes in the UI. You can set the selection to use it as input for commands, or to determine what is left selected for the user when your plugin’s edit operation completes. @@ -37,26 +37,37 @@ declare global { /** * Array representing the current selection plus any locked items that the user has attempted to select. */ - itemsIncludingLocked: Array; + readonly itemsIncludingLocked: Array; /** * True if the selection isn’t empty and consists of one or more non-Artboards. Never true at the same time as hasArtboards. */ - hasArtwork: boolean; + readonly hasArtwork: boolean; /** * True if the selection isn’t empty and consists of one or more Artboards. Never true at the same time as hasArtwork. */ - hasArtboards: boolean; + readonly hasArtboards: boolean; /** * The context in which selection and edit operations must occur. If the user hasn’t drilled into any container node, this value is the document root, and its scope includes all immediate children of the pasteboard (including Artboards), and all immediate children of all those Artboards. */ - editContext: SceneNode; + readonly editContext: SceneNode; /** - * The preferred parent to insert newly added content into. Takes into account the current edit context as well as the “focused artboard” if in the root context. + * The preferred parent to insert newly added content into. Takes into account the current edit context as well as the "focused artboard" if in the root context. + Typically this is the same parent where, for example, XD's shape drawing tools would add items. + * + * _Selected items are not necessarily all immediate children of the `insertionParent`._ They can be anywhere within the [edit context's](/reference/core/edit-context.md) scope. */ - insertionParent: SceneNode; + readonly insertionParent: SceneNode; /** * The artboard the user is currently most focused on (via recent selection or edit operations). May be null, for example if no artboards exist or if the user just deleted an artboard. */ - focusedArtboard: Artboard | null | undefined; + readonly focusedArtboard: Artboard | null | undefined; + + /** + * Returns true if the node is accessible for editing in the scope of the current edit context. + * If false, the node cannot be edited given the user's current selection. + * Nodes that are currently selected are always in the current edit context. + * @param node + */ + isInEditContext(node: SceneNode): boolean; } } From 7a4f1e8ed8c8154f556d1f42fb0bf02d489a7997 Mon Sep 17 00:00:00 2001 From: Pablo Klaschka Date: Wed, 20 May 2020 16:53:02 +0200 Subject: [PATCH 05/13] scenegraph updates --- sample.js | 3 +- types/scenegraph.d.ts | 408 +++++++++++++++++++++++++++++++----------- 2 files changed, 308 insertions(+), 103 deletions(-) diff --git a/sample.js b/sample.js index e14122c..ac99170 100644 --- a/sample.js +++ b/sample.js @@ -1,4 +1,4 @@ -const {Text, Ellipse, Color, RootNode} = require("scenegraph"); +const {Text, Ellipse, Color, RootNode, SceneNode} = require("scenegraph"); const clipboard = require("clipboard"); const shell = require("uxp").shell; const fs = require("uxp").storage.localFileSystem; @@ -9,6 +9,7 @@ const assets = require('assets'); * @param {RootNode} documentRoot */ async function test(selection, documentRoot) { + selection.items.forEach(async node => { console.log("Hello world: ", node); if (node instanceof Text) { diff --git a/types/scenegraph.d.ts b/types/scenegraph.d.ts index 30319b4..c8eeb9c 100644 --- a/types/scenegraph.d.ts +++ b/types/scenegraph.d.ts @@ -40,7 +40,7 @@ declare interface PerPluginStorage { /** * Returns a map where key is plugin ID and value is a nested map containing all the shared metadata for that plugin ID (i.e. the result of calling `getForPluginId()` with that ID). */ - getAll(): { [key: string]: {[key:string]: string} }; + getAll(): { [key: string]: { [key: string]: string } }; /** * Returns a map of key-value string pairs containing all shared metadata stored on this node by the given plugin. May be an empty object (zero keys), but is never null. @@ -50,15 +50,15 @@ declare interface PerPluginStorage { * * @example *const MY_PLUGIN_ID = ""; -let mySharedMetadata = node.sharedPluginData.getForPluginId(MY_PLUGIN_ID); -console.log("My shared 'foo' & 'bar' values:", - mySharedMetadata.foo, mySharedMetadata.bar); + let mySharedMetadata = node.sharedPluginData.getForPluginId(MY_PLUGIN_ID); + console.log("My shared 'foo' & 'bar' values:", + mySharedMetadata.foo, mySharedMetadata.bar); -console.log("Plugin B's shared 'foo' value:", - node.sharedPluginData.getForPluginId("B").foo); + console.log("Plugin B's shared 'foo' value:", + node.sharedPluginData.getForPluginId("B").foo); */ - getForPluginId(pluginId: string): {[key:string]: string}; + getForPluginId(pluginId: string): { [key: string]: string }; /** * Returns a list of all keys stored on this node by the given plugin. May be empty (length zero), but is never null. @@ -79,8 +79,8 @@ console.log("Plugin B's shared 'foo' value:", * * @example * // These are two different values, stored independently per plugin - console.log("Plugin A's 'foo' value:", node.sharedPluginData.getItem("A", "foo")); - console.log("Plugin B's 'foo' value:", node.sharedPluginData.getItem("B", "foo")); + console.log("Plugin A's 'foo' value:", node.sharedPluginData.getItem("A", "foo")); + console.log("Plugin B's 'foo' value:", node.sharedPluginData.getItem("B", "foo")); */ getItem(pluginId: string, key: string): string | undefined; @@ -91,13 +91,13 @@ console.log("Plugin B's shared 'foo' value:", * @param value If undefined, behaves as if you'd called `removeItem()` instead. * * @example -const MY_PLUGIN_ID = ""; -node.sharedPluginData.setItem(MY_PLUGIN_ID, "foo", "42"); + const MY_PLUGIN_ID = ""; + node.sharedPluginData.setItem(MY_PLUGIN_ID, "foo", "42"); -node.sharedPluginData.setItem("other_plugin_id", "foo", "42"); -// ^ ERROR: other plugin's metadata is read-only + node.sharedPluginData.setItem("other_plugin_id", "foo", "42"); + // ^ ERROR: other plugin's metadata is read-only -console.log(node.sharedPluginData.getItem(MY_PLUGIN_ID, "foo")); // "42" + console.log(node.sharedPluginData.getItem(MY_PLUGIN_ID, "foo")); // "42" */ setItem(pluginId: string, key: string, value: string | undefined): void; @@ -106,14 +106,14 @@ console.log(node.sharedPluginData.getItem(MY_PLUGIN_ID, "foo")); // "42" * @param pluginId *Must* be equal to your plugin's ID. * @param key * @example -const MY_PLUGIN_ID = ""; -node.sharedPluginData.setItem(MY_PLUGIN_ID, "foo", "42"); -console.log(node.sharedPluginData.getItem(MY_PLUGIN_ID, "foo")); // "42" + const MY_PLUGIN_ID = ""; + node.sharedPluginData.setItem(MY_PLUGIN_ID, "foo", "42"); + console.log(node.sharedPluginData.getItem(MY_PLUGIN_ID, "foo")); // "42" -node.sharedPluginData.removeItem(MY_PLUGIN_ID, "foo"); -console.log(node.sharedPluginData.getItem(MY_PLUGIN_ID, "foo")); // undefined + node.sharedPluginData.removeItem(MY_PLUGIN_ID, "foo"); + console.log(node.sharedPluginData.getItem(MY_PLUGIN_ID, "foo")); // undefined */ - removeItem(pluginId: string, key:string): void; + removeItem(pluginId: string, key: string): void; /** * Provided for convenience: you can `console.log(node.sharedPluginData)` to see the value of `getAll()`. @@ -130,35 +130,35 @@ console.log(node.sharedPluginData.getItem(MY_PLUGIN_ID, "foo")); // undefined * Represents the children of a scenenode. Typically accessed via the SceneNode.children property. */ declare interface SceneNodeList { - items: SceneNodeClass[]; + items: SceneNode[]; readonly length: number; forEach( - callback: (sceneNode: SceneNodeClass, index: number) => void, + callback: (sceneNode: SceneNode, index: number) => void, thisArg?: object ): void; forEachRight( - callback: (sceneNode: SceneNodeClass, index: number) => void, + callback: (sceneNode: SceneNode, index: number) => void, thisArg?: object ): void; filter( - callback: (sceneNode: SceneNodeClass, index: number) => boolean, + callback: (sceneNode: SceneNode, index: number) => boolean, thisArg?: object - ): Array; + ): Array; map( - callback: (sceneNode: SceneNodeClass, index: number) => any, + callback: (sceneNode: SceneNode, index: number) => any, thisArg?: object ): Array; some( - callback: (sceneNode: SceneNodeClass, index: number) => boolean, + callback: (sceneNode: SceneNode, index: number) => boolean, thisArg?: object ): boolean; - at(index: number): SceneNodeClass | null; + at(index: number): SceneNode | null; } export class Matrix { @@ -558,20 +558,22 @@ export interface Bounds { height: number; } -export interface SceneNode extends SceneNodeClass {} - /** * Base class of all scenegraph nodes. Nodes will always be an instance of some subclass of SceneNode. */ -declare abstract class SceneNodeClass { +export abstract class SceneNode { /** * Returns a unique identifier for this node that stays the same when the file is closed & reopened, or if the node is moved to a different part of the document. Cut-Paste will result in a new guid, however. + * + * The GUID is guaranteed unique _within_ the current document, but _other_ documents may contain the same GUID value. For example, if the user makes a copy of an XD file, both files will use the same GUIDs. + * + * The GUID of the root node changes if the document is duplicated via Save As. See `application.activeDocument.guid` for details. */ readonly guid: string; /** * Returns the parent node. Null if this is the root node, or a freshly constructed node which has not been added to a parent yet. */ - readonly parent: SceneNodeClass | null; + readonly parent: SceneNode | null; /** * Returns a list of this node’s children. List is length 0 if the node has no children. The first child is lowest in the z order. * This list is not an Array, so you must use at(i) instead of [i] to access children by index. It has a number of Array-like methods such as forEach() for convenience, however. @@ -653,6 +655,8 @@ declare abstract class SceneNodeClass { /** * Node name as seen in the Layers panel. Also used as filename during export. + * + * Setting this property will cause `hasDefaultName` to become false. */ name: string; @@ -685,7 +689,7 @@ declare abstract class SceneNodeClass { * * Note: If this node (or one of its ancestors) has `visible` = false, tap and drag interactions on it will not be triggered. * - * Currently, this API excludes any keyboard/gamepad interactions on this node. + * Currently, this API excludes some types of interactions: keypress/gamepad, scrolling, hover, component state transitions, or non-speech audio playback. * * @example ```javascript // Print all the interactions triggered by a node @@ -705,15 +709,155 @@ declare abstract class SceneNodeClass { /** * **Since:** XD 14 - * Metadata specific to your plugin. Must be a value which can be converted to a JSON string, or undefined to clear the stored metadata on this node. + * Metadata specific to your plugin. Must be a value which can be converted to a JSON string, or undefined to clear the + stored metadata on this node. stored metadata on this node. * - * Metadata is persisted with the document when it is saved. Duplicating a node (including across documents, via copy-paste) will duplicate the metadata with it. If the node lies within a Component or Repeat Grid, all instances of the node will have identical metadata (changes in one copy will automatically be synced to the other copy). Metadata stored by this plugin cannot be accessed by other plugins - each plugin has its own isolated metadata storage. - + * Metadata is persisted with the document when it is saved. Duplicating a node (including across documents, via copy-paste) + * will duplicate the metadata with it. If the node lies within a Component or Repeat Grid, all instances of the node will have + * identical metadata (changes in one copy will automatically be synced to the other copy). * - * To store general metadata for the document overall, set pluginData on the root node of the scenegraph. Metadata on the root node can be changed from any edit context. + * To store general metadata for the document overall, set pluginData on the [root](#module_scenegraph-root) node of the scenegraph. Metadata on + * the root node can be changed from _any_ edit context. + * + * Metadata stored in pluginData cannot be accessed by other plugins -- each plugin has its own isolated storage. To share metadata + * with other plugins, use `sharedPluginData`. */ pluginData: any; + /** + * (**Since**: XD 29) + * + * Metadata storage accessible by other plugins, separated into silos by plugin ID. Your plugin can read & write the storage for its own plugin ID, + * but storage for other plugin IDs is *read-only*. This property returns a PerPluginStorage API object. + * + * *Each* scenenode has its own metadata storage. To store general metadata that is not specific to one scenenode, use `sharedPluginData` on the + * [document's scenegraph root](scenegraph.md#module_scenegraph-root). + * + * Metadata is persisted with the document when it is saved. See `pluginData` for info on how metadata is duplicated when nodes are + * copied or synced. + * + */ + sharedPluginData: PerPluginStorage; + + + public static readonly BLEND_MODE_PASSTHROUGH: string; + public static readonly BLEND_MODE_NORMAL: string; + public static readonly BLEND_MODE_MULTIPLY: string; + public static readonly BLEND_MODE_DARKEN: string; + public static readonly BLEND_MODE_COLOR_BURN: string; + public static readonly BLEND_MODE_LIGHTEN: string; + public static readonly BLEND_MODE_SCREEN: string; + public static readonly BLEND_MODE_COLOR_DODGE: string; + public static readonly BLEND_MODE_OVERLAY: string; + public static readonly BLEND_MODE_SOFT_LIGHT: string; + public static readonly BLEND_MODE_HARD_LIGHT: string; + public static readonly BLEND_MODE_DIFFERENCE: string; + public static readonly BLEND_MODE_EXCLUSION: string; + public static readonly BLEND_MODE_HUE: string; + public static readonly BLEND_MODE_SATURATION: string; + public static readonly BLEND_MODE_COLOR: string; + public static readonly BLEND_MODE_LUMINOSITY: string; + + /** + * (**Since**: XD 27) + * + * Blend mode determines how a node is composited onto the content below it. + * + * One of: `SceneNode.BLEND_MODE_PASSTHROUGH`, `BLEND_MODE_NORMAL`, `BLEND_MODE_MULTIPLY`, `BLEND_MODE_DARKEN`, `BLEND_MODE_COLOR_BURN`, `BLEND_MODE_LIGHTEN`, `BLEND_MODE_SCREEN`, `BLEND_MODE_COLOR_DODGE`, `BLEND_MODE_OVERLAY`, `BLEND_MODE_SOFT_LIGHT`, + `BLEND_MODE_HARD_LIGHT`, `BLEND_MODE_DIFFERENCE`, `BLEND_MODE_EXCLUSION`, `BLEND_MODE_HUE`, `BLEND_MODE_SATURATION`, `BLEND_MODE_COLOR`, `BLEND_MODE_LUMINOSITY`. + * + * _Note:_ for leaf nodes (GraphicNode), the XD UI may show leaf nodes as blend mode "Normal" even when the underlying value is `BLEND_MODE_PASSTHROUGH`. This is because "Pass Through" and "Normal" are essentially equivalent for leaf nodes -- they only differ + * in appearance when a node has children. + * + * @example ```js + *node.blendMode = scenegraph.SceneNode.BLEND_MODE_LUMINOSITY; + *``` + */ + blendMode: string; // TODO: Implement the actual constant value possibilities + + public static readonly FIXED_LEFT: string; + public static readonly FIXED_RIGHT: string; + public static readonly FIXED_TOP: string; + public static readonly FIXED_BOTTOM: string; + public static readonly FIXED_BOTH: string; + public static readonly POSITION_PROPORTIONAL: string; + public static readonly SIZE_FIXED: string; + public static readonly SIZE_RESIZES: string; + + /** + * (**Since**: XD 29) + * + * Horizontal dynamic-layout settings used with the Responsive Resize feature. Setting this only determines how the node is updated when its parent is resized -- it does not change the node's current size or position. + * + * Both fields *must* be provided together when setting this property. + * + * Returns undefined if node's parent is a container where Responsive Resize is unavailable: + * * Certain containers such as RepeatGrid and the pasteboard (scenegraph root) do not support Responsive Resize. + * * Container may have Responsive Resize layout explicitly turned off (see `dynamicLayout` flag). + * + * Attempting to set this property when Responsive Resize is unavailable results in an error. + * + * Setting this property will cause `hasCustomConstraints` to become true. + * @example ```js + *let node = selection.items[0]; + *node.horizontalConstraints = { position: scenegraph.SceneNode.FIXED_LEFT, size: scenegraph.SceneNode.SIZE_FIXED }; + *``` + */ + horizontalConstraints: undefined | { + /** + * Horizontal position anchoring, one of `SceneNode.FIXED_LEFT`, `FIXED_RIGHT`, `FIXED_BOTH` or `POSITION_PROPORTIONAL`.

`FIXED_BOTH` sets fixed left & right offsets, so it always implies `size: SIZE_RESIZES` (similar to setting both `left` & `right` in CSS).

`POSITION_PROPORTIONAL` holds node position at a fixed percentage of the parent's width -- the same positioning behavior you'd get if Responsive Resize is turned off entirely. + */ + postition: string, + /** + * Horizontal sizing behavior, either `SceneNode.SIZE_FIXED` or `SceneNode.SIZE_RESIZES`.

`SIZE_FIXED` cannot be used with `position: FIXED_BOTH`, since it is impossible to fix both left & right edges without resizing when the parent resizes.

`SIZE_RESIZES` can be used with any `position` setting. With `position: FIXED_BOTH`, the node's size always equals the parent's size minus the fixed left & right offsets. With other position settings, the node's size maintains a fixed percentage of the parent's size. + */ + size: string + }; + + /** + * (**Since**: XD 29) + * + * Vertical dynamic-layout settings used with the Responsive Resize feature. Setting this only determines how the node is updated when its parent is resized -- it does not change the node's current size or position. + * + * Both fields *must* be provided together when setting this property. + * + * See `horizontalConstraints` for other important notes. + * + * @example ```js + *let node = selection.items[0]; + *node.verticalConstraints = { position: scenegraph.SceneNode.FIXED_TOP, size: scenegraph.SceneNode.SIZE_RESIZES }; + */ + verticalConstraints: undefined | { + /** + * Vertical position anchoring, one of `SceneNode.FIXED_TOP`, `FIXED_BOTTOM`, `FIXED_BOTH` or `POSITION_PROPORTIONAL`.

For details, see `horizontalConstraints` above. + */ + postition: string, + /** + * Vertical sizing behavior, either `SceneNode.SIZE_FIXED` or `SceneNode.SIZE_RESIZES`.

For details, see `horizontalConstraints` above. + */ + size: string + }; + + /** + * (**Since**: XD 29) + * + * True if this node's Responsive Resize layout settings, which are normally automatically inferred by XD, have been overridden with specific desired values. Constraints on a node are either all overridden, or all automatic -- never mixed. + * + * If false, each time the parent resizes XD will automatically guess the best layout settings to used based on the current size & position of this node within its parent. You can use the `horizontalConstraints` and `verticalConstraints` getters to check what computed settings XD would use based on the node's current size & position. + * + * Automatically becomes true any time you set `horizontalConstraints` or `verticalConstraints`. To reset to false, call `resetToAutoConstraints()`. + */ + readonly hasCustomConstraints: boolean; + + /** + * (**Since**: XD 29) + * + * Erase any overridden Responsive Resize layout settings, restoring the default behavior where XD will automatically guess the best layout settings for this node the next time its parent is resized. This function does not change the node's *current* size & position, however. + * + * Calling this will cause `hasCustomConstraints` to become false. + */ + resetToAutoConstraints(): void; + /** * Remove this node from its parent, effectively deleting it from the document. */ @@ -756,7 +900,7 @@ declare abstract class SceneNodeClass { /** * Base class for nodes that have a stroke and/or fill. This includes leaf nodes such as Rectangle, as well as BooleanGroup which is a container node. If you create a shape node, it will not be visible unless you explicitly give it either a stroke or a fill. */ -export class GraphicNode extends SceneNodeClass { +export class GraphicNode extends SceneNode { /** * The fill applied to this shape, if any. If this property is null or fillEnabled is false, no fill is drawn. Freshly created nodes have no fill by default. * @@ -888,10 +1032,10 @@ export class Artboard extends GraphicNode { * May include interactions that are impossible to trigger because the trigger node (or one of its ancestors) has `visible` = false. * * Note: currently, this API excludes any applicable keyboard/gamepad interactions. - * @see SceneNodeClass.triggeredInteractions + * @see SceneNode.triggeredInteractions * @see interactions.allInteractions */ - readonly incomingInteractions: Array<{ triggerNode: SceneNodeClass, interactions: Array }>; + readonly incomingInteractions: Array<{ triggerNode: SceneNode, interactions: Array }>; /** * **Since**: XD 19 @@ -904,24 +1048,24 @@ export class Artboard extends GraphicNode { /** * Adds a child node to this container node. You can only add leaf nodes this way; to create structured subtrees of content, use commands. - * @param {SceneNodeClass} node Child to add + * @param {SceneNode} node Child to add * @param {number} index Optional: index to insert child at. Child is appended to end of children list (top of z order) otherwise. */ - addChild(node: SceneNodeClass, index?: number): void; + addChild(node: SceneNode, index?: number): void; /** * Inserts a child node after the given reference node. - * @param {SceneNodeClass} node Child to add - * @param {SceneNodeClass} relativeTo New child is added immediately after this existing child + * @param {SceneNode} node Child to add + * @param {SceneNode} relativeTo New child is added immediately after this existing child */ - addChildAfter(node: SceneNodeClass, relativeTo: SceneNodeClass): void; + addChildAfter(node: SceneNode, relativeTo: SceneNode): void; /** * Inserts a child node before the given reference node. - * @param {SceneNodeClass} node Child to add - * @param {SceneNodeClass} relativeTo New child is added immediately before this existing child + * @param {SceneNode} node Child to add + * @param {SceneNode} relativeTo New child is added immediately before this existing child */ - addChildBefore(node: SceneNodeClass, relativeTo: SceneNodeClass): void; + addChildBefore(node: SceneNode, relativeTo: SceneNode): void; /** * Removes all children from this node. Equivalent to calling removeFromParent() on each child in turn, but faster. @@ -1031,14 +1175,14 @@ export class Path extends GraphicNode { /** * **Since**: XD 19 - * Leaf node shape that is a polygon with 3 or more sides. May also have rounded corners. The sides are not necessarily all equal in length: this is true only when the Polygon's width and height matches the aspect ratio of a regular (equilateral) polygon with the given number of sides. + * Leaf node shape that is either a convex polygon _or_ a star shape. May have rounded corners. The sides are not necessarily all equal in length: * - * When unrotated, the Polygon always has its bottommost side as a perfectly horizontal line - with the exception of the 4-sided Polygon, which is a diamond shape instead. + * When unrotated, a non-star Polygon always has its bottommost side as a perfectly horizontal line - with the exception of the 4-sided Polygon, which * * Like all shape nodes, has no size, fill, or stroke by default unless you set one. * * @example ```javascript - // Add a red triangle to the document and select it + // Add a red triangle to the document var polygon = new Polygon(); polygon.cornerCount = 3; polygon.width = 50; @@ -1046,6 +1190,16 @@ export class Path extends GraphicNode { polygon.fill = new Color("red"); selection.insertionParent.addChild(polygon); selection.items = [polygon]; + + // Add a blue 5-pointed star with rounded corners + var polygon = new Polygon(); + polygon.cornerCount = 5; + polygon.starRatio = 55; + polygon.setAllCornerRadii(4); + polygon.width = 100; + polygon.height = 95; + polygon.fill = new Color("blue"); + selection.insertionParent.addChild(polygon); * ``` */ export class Polygon extends GraphicNode { @@ -1061,11 +1215,14 @@ export class Polygon extends GraphicNode { /** * @default 3 - * Number of corners (vertices), and also therefore number of sides. + * For a non-star shape, defines the number of corners (vertices), and also therefore number of sides. For a star shape, defines the + number of star points -- there will be twice as many corners in total (the tips of the points _plus_ all the inside corners + between the points). * - * Setting cornerCount on an existing Polygon behaves in one of two different ways: - * * If the shape's aspect ratio gives it equilateral sides, the sides remain equilateral while the size and aspect ratio of the shape is changed to accomodate. - * * Otherwise, the size and aspect ratio of the shape remains unchanged. + * Setting `cornerCount` on an existing Polygon behaves in one of two different ways: + * * If the shape's aspect ratio gives it equilateral sides, the sides remain equilateral while the size and aspect ratio of the + * shape are automatically changed as needed. + * * Otherwise, the size and aspect ratio of the shape remain unchanged. * * This matches how changing the corner count in XD's UI behaves. * @@ -1083,6 +1240,17 @@ export class Polygon extends GraphicNode { */ cornerRadii: number[]; + /** + * @default 100 + * (**Since**: XD 26) + * + * Determines how prominent the shape's star points are. The default value of 100 is a normal convex polygon (not a star at all). + * For a star shape, consider that the outer vertices at the tips of the points all lie on a circle and the inner vertices + * between the points all lie on a second, smaller circle. The `starRatio` is the ratio of the smaller circle's diameter to the + * outer circle's diameter, expressed as a percentage. + */ + starRatio: number; + /** * Set the corner radius of all corners of the Polygon to the same value. * @param {number} radius @@ -1103,24 +1271,24 @@ export class BooleanGroup extends GraphicNode { /** * Adds a child node to this container node. You can only add leaf nodes this way; to create structured subtrees of content, use commands. - * @param {SceneNodeClass} node Child to add + * @param {SceneNode} node Child to add * @param {number} index Optional: index to insert child at. Child is appended to end of children list (top of z order) otherwise. */ - addChild(node: SceneNodeClass, index?: number): void; + addChild(node: SceneNode, index?: number): void; /** * Inserts a child node after the given reference node. - * @param {SceneNodeClass} node Child to add - * @param {SceneNodeClass} relativeTo New child is added immediately after this existing child + * @param {SceneNode} node Child to add + * @param {SceneNode} relativeTo New child is added immediately after this existing child */ - addChildAfter(node: SceneNodeClass, relativeTo: SceneNodeClass): void; + addChildAfter(node: SceneNode, relativeTo: SceneNode): void; /** * Inserts a child node before the given reference node. - * @param {SceneNodeClass} node Child to add - * @param {SceneNodeClass} relativeTo New child is added immediately before this existing child + * @param {SceneNode} node Child to add + * @param {SceneNode} relativeTo New child is added immediately before this existing child */ - addChildBefore(node: SceneNodeClass, relativeTo: SceneNodeClass): void; + addChildBefore(node: SceneNode, relativeTo: SceneNode): void; /** * Removes all children from this node. Equivalent to calling removeFromParent() on each child in turn, but faster. @@ -1297,32 +1465,45 @@ export class Text extends GraphicNode { * * In a Mask Group, the mask shape is included in the group’s children list, at the top of the z order. It is not visible - only its path outline is used, for clipping the group. */ -export class Group extends SceneNodeClass { +export class Group extends SceneNode { /** * The mask shape applied to this group, if any. This object is also present in the group’s children list. Though it has no direct visual appearance of its own, the mask affects the entire groups’s appearance by clipping all its other content. */ - readonly mask: SceneNodeClass | null; + readonly mask: SceneNode | null; + + /** + * (**Since:** XD 29) + * + * If true, Responsive Resize is enabled, and this node's children will use an intelligent layout algorithm whenever this node is resized. + * + * Returns undefined on node types that do not support Responsive Resize (such as RepeatGrid; see `horizontalConstraints` docs for a + * complete list). Attempting to set this property on such node types results in an error. + * + * @see SceneNode.horizontalConstraints + * @see SceneNode.verticalConstraints + */ + dynamicLayout: undefined | boolean; /** * Adds a child node to this container node. You can only add leaf nodes this way; to create structured subtrees of content, use commands. - * @param {SceneNodeClass} node Child to add + * @param {SceneNode} node Child to add * @param {number} index Optional: index to insert child at. Child is appended to end of children list (top of z order) otherwise. */ - addChild(node: SceneNodeClass, index?: number): void; + addChild(node: SceneNode, index?: number): void; /** * Inserts a child node after the given reference node. - * @param {SceneNodeClass} node Child to add - * @param {SceneNodeClass} relativeTo New child is added immediately after this existing child + * @param {SceneNode} node Child to add + * @param {SceneNode} relativeTo New child is added immediately after this existing child */ - addChildAfter(node: SceneNodeClass, relativeTo: SceneNodeClass): void; + addChildAfter(node: SceneNode, relativeTo: SceneNode): void; /** * Inserts a child node before the given reference node. - * @param {SceneNodeClass} node Child to add - * @param {SceneNodeClass} relativeTo New child is added immediately before this existing child + * @param {SceneNode} node Child to add + * @param {SceneNode} relativeTo New child is added immediately before this existing child */ - addChildBefore(node: SceneNodeClass, relativeTo: SceneNodeClass): void; + addChildBefore(node: SceneNode, relativeTo: SceneNode): void; /** * Removes all children from this node. Equivalent to calling removeFromParent() on each child in turn, but faster. @@ -1341,7 +1522,7 @@ export class Group extends SceneNodeClass { * * It is not currently possible for plugins to *create* a new component definition or a new SymbolInstance node, aside from using `require('commands').duplicate` to clone existing SymbolInstances. */ -export class SymbolInstance extends SceneNodeClass { +export class SymbolInstance extends SceneNode { /** * An identifier unique within this document that is shared by all instances of the same component. */ @@ -1355,24 +1536,24 @@ export class SymbolInstance extends SceneNodeClass { /** * Adds a child node to this container node. You can only add leaf nodes this way; to create structured subtrees of content, use commands. - * @param {SceneNodeClass} node Child to add + * @param {SceneNode} node Child to add * @param {number} index Optional: index to insert child at. Child is appended to end of children list (top of z order) otherwise. */ - addChild(node: SceneNodeClass, index?: number): void; + addChild(node: SceneNode, index?: number): void; /** * Inserts a child node after the given reference node. - * @param {SceneNodeClass} node Child to add - * @param {SceneNodeClass} relativeTo New child is added immediately after this existing child + * @param {SceneNode} node Child to add + * @param {SceneNode} relativeTo New child is added immediately after this existing child */ - addChildAfter(node: SceneNodeClass, relativeTo: SceneNodeClass): void; + addChildAfter(node: SceneNode, relativeTo: SceneNode): void; /** * Inserts a child node before the given reference node. - * @param {SceneNodeClass} node Child to add - * @param {SceneNodeClass} relativeTo New child is added immediately before this existing child + * @param {SceneNode} node Child to add + * @param {SceneNode} relativeTo New child is added immediately before this existing child */ - addChildBefore(node: SceneNodeClass, relativeTo: SceneNodeClass): void; + addChildBefore(node: SceneNode, relativeTo: SceneNode): void; /** * Removes all children from this node. Equivalent to calling removeFromParent() on each child in turn, but faster. @@ -1385,7 +1566,7 @@ export class SymbolInstance extends SceneNodeClass { * Each grid cell is a Group that is an immediate child of the RepeatGrid. These groups are automatically created and destroyed as needed when the RepeatGrid is resized. * It is not currently possible for plugins to create a new RepeatGrid node, aside from using commands.duplicate to clone existing RepeatGrids. */ -export class RepeatGrid extends SceneNodeClass { +export class RepeatGrid extends SceneNode { /** * Defines size of the RepeatGrid. Cells are created and destroyed as necessary to fill the current size. Cells that only partially fit will be clipped. */ @@ -1443,24 +1624,24 @@ export class RepeatGrid extends SceneNodeClass { /** * Adds a child node to this container node. You can only add leaf nodes this way; to create structured subtrees of content, use commands. - * @param {SceneNodeClass} node Child to add + * @param {SceneNode} node Child to add * @param {number} index Optional: index to insert child at. Child is appended to end of children list (top of z order) otherwise. */ - addChild(node: SceneNodeClass, index?: number): void; + addChild(node: SceneNode, index?: number): void; /** * Inserts a child node after the given reference node. - * @param {SceneNodeClass} node Child to add - * @param {SceneNodeClass} relativeTo New child is added immediately after this existing child + * @param {SceneNode} node Child to add + * @param {SceneNode} relativeTo New child is added immediately after this existing child */ - addChildAfter(node: SceneNodeClass, relativeTo: SceneNodeClass): void; + addChildAfter(node: SceneNode, relativeTo: SceneNode): void; /** * Inserts a child node before the given reference node. - * @param {SceneNodeClass} node Child to add - * @param {SceneNodeClass} relativeTo New child is added immediately before this existing child + * @param {SceneNode} node Child to add + * @param {SceneNode} relativeTo New child is added immediately before this existing child */ - addChildBefore(node: SceneNodeClass, relativeTo: SceneNodeClass): void; + addChildBefore(node: SceneNode, relativeTo: SceneNode): void; /** * Removes all children from this node. Equivalent to calling removeFromParent() on each child in turn, but faster. @@ -1471,35 +1652,36 @@ export class RepeatGrid extends SceneNodeClass { /** * Container node whose content is linked to an external resource, such as Creative Cloud Libraries. It cannot be edited except by first ungrouping it, breaking this link. */ -export class LinkedGraphic extends SceneNodeClass { +export class LinkedGraphic extends SceneNode { } -export interface RootNode extends RootNodeClass {} +export interface RootNode extends RootNodeClass { +} /** * Class representing the root node of the document. All Artboards are children of this node, as well as any pasteboard content that does not lie within an Artboard. Artboards must be grouped contiguously at the bottom of this node’s z order. The root node has no visual appearance of its own. */ -declare class RootNodeClass extends SceneNodeClass { +declare class RootNodeClass extends SceneNode { /** * Adds a child node to this container node. You can only add leaf nodes this way; to create structured subtrees of content, use commands. - * @param {SceneNodeClass} node Child to add + * @param {SceneNode} node Child to add * @param {number} index Optional: index to insert child at. Child is appended to end of children list (top of z order) otherwise. */ - addChild(node: SceneNodeClass, index?: number): void; + addChild(node: SceneNode, index?: number): void; /** * Inserts a child node after the given reference node. - * @param {SceneNodeClass} node Child to add - * @param {SceneNodeClass} relativeTo New child is added immediately after this existing child + * @param {SceneNode} node Child to add + * @param {SceneNode} relativeTo New child is added immediately after this existing child */ - addChildAfter(node: SceneNodeClass, relativeTo: SceneNodeClass): void; + addChildAfter(node: SceneNode, relativeTo: SceneNode): void; /** * Inserts a child node before the given reference node. - * @param {SceneNodeClass} node Child to add - * @param {SceneNodeClass} relativeTo New child is added immediately before this existing child + * @param {SceneNode} node Child to add + * @param {SceneNode} relativeTo New child is added immediately before this existing child */ - addChildBefore(node: SceneNodeClass, relativeTo: SceneNodeClass): void; + addChildBefore(node: SceneNode, relativeTo: SceneNode): void; /** * Removes all children from this node. Equivalent to calling removeFromParent() on each child in turn, but faster. @@ -1519,4 +1701,26 @@ export const selection: SceneNodeList; */ export const root: RootNodeClass; +/** + * (**Since**: XD 28) + * + * Returns the scenenode in this document that has the given node GUID. Returns undefined if no such node exists connected + * to the scenegraph tree (detached/orphan nodes will not be found). This provides a fast way of persistently remembering a node across plugin + * operations and even across document open/closes. + * @param guid SceneNode GUID -- must be all lowercase, as returned by the [`guid` getter](#SceneNode-guid). + * + * @example ```js + let node = scenegraph.selection.items[0]; + let guid = node.guid; + // ...later on: + let sameNode = scenegraph.getNodeByGUID(guid); + if (sameNode) { + // ^ Always check if node still exists - user may have deleted it + console.log("Found node again!", sameNode); +} + ``` + */ +export function getNodeByGUID(guid: string): SceneNode | undefined; + + export {}; From fff631a1d5565503065cdf53c05b13ff9f2c0d22 Mon Sep 17 00:00:00 2001 From: Pablo Klaschka Date: Wed, 20 May 2020 17:50:33 +0200 Subject: [PATCH 06/13] Resolved merge conflicts --- jsconfig.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jsconfig.json b/jsconfig.json index 49aa860..fc578c7 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -8,5 +8,5 @@ "es2015", "dom" ] - }, -} \ No newline at end of file + } +} From 48ce8f415351edfa289dca727b0fa3895587febe Mon Sep 17 00:00:00 2001 From: Pablo Klaschka Date: Wed, 20 May 2020 19:41:33 +0200 Subject: [PATCH 07/13] Update types/index.d.ts Co-authored-by: Eric Robinson --- types/index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/index.d.ts b/types/index.d.ts index 02c7148..825fcf0 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -92,7 +92,7 @@ declare global { * The preferred parent to insert newly added content into. Takes into account the current edit context as well as the "focused artboard" if in the root context. Typically this is the same parent where, for example, XD's shape drawing tools would add items. * - * _Selected items are not necessarily all immediate children of the `insertionParent`._ They can be anywhere within the [edit context's](/reference/core/edit-context.md) scope. + * _Selected items are not necessarily all immediate children of the `insertionParent`._ They can be anywhere within the edit context's scope. */ readonly insertionParent: SceneNode; /** From 7d45e0f295bafc76257155ab28059d3ef90ce2a8 Mon Sep 17 00:00:00 2001 From: Pablo Klaschka Date: Wed, 20 May 2020 19:41:56 +0200 Subject: [PATCH 08/13] Update types/index.d.ts Co-authored-by: Eric Robinson --- types/index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/index.d.ts b/types/index.d.ts index 825fcf0..1078354 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -75,7 +75,7 @@ declare global { /** * Array representing the current selection plus any locked items that the user has attempted to select. */ - readonly itemsIncludingLocked: Array; + readonly itemsIncludingLocked: ReadonlyArray; /** * True if the selection isn’t empty and consists of one or more non-Artboards. Never true at the same time as hasArtboards. */ From 3734f6b5cb1c85e7a2dad1eaf97570917bb8e228 Mon Sep 17 00:00:00 2001 From: Pablo Klaschka Date: Sat, 23 May 2020 22:30:47 +0200 Subject: [PATCH 09/13] Fixed a typo --- types/scenegraph.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/types/scenegraph.d.ts b/types/scenegraph.d.ts index 1ccf1ca..5697768 100644 --- a/types/scenegraph.d.ts +++ b/types/scenegraph.d.ts @@ -808,7 +808,7 @@ declare module 'scenegraph' { /** * Horizontal position anchoring, one of `SceneNode.FIXED_LEFT`, `FIXED_RIGHT`, `FIXED_BOTH` or `POSITION_PROPORTIONAL`.

`FIXED_BOTH` sets fixed left & right offsets, so it always implies `size: SIZE_RESIZES` (similar to setting both `left` & `right` in CSS).

`POSITION_PROPORTIONAL` holds node position at a fixed percentage of the parent's width -- the same positioning behavior you'd get if Responsive Resize is turned off entirely. */ - postition: string, + position: string, /** * Horizontal sizing behavior, either `SceneNode.SIZE_FIXED` or `SceneNode.SIZE_RESIZES`.

`SIZE_FIXED` cannot be used with `position: FIXED_BOTH`, since it is impossible to fix both left & right edges without resizing when the parent resizes.

`SIZE_RESIZES` can be used with any `position` setting. With `position: FIXED_BOTH`, the node's size always equals the parent's size minus the fixed left & right offsets. With other position settings, the node's size maintains a fixed percentage of the parent's size. */ @@ -832,7 +832,7 @@ declare module 'scenegraph' { /** * Vertical position anchoring, one of `SceneNode.FIXED_TOP`, `FIXED_BOTTOM`, `FIXED_BOTH` or `POSITION_PROPORTIONAL`.

For details, see `horizontalConstraints` above. */ - postition: string, + position: string, /** * Vertical sizing behavior, either `SceneNode.SIZE_FIXED` or `SceneNode.SIZE_RESIZES`.

For details, see `horizontalConstraints` above. */ From 22ac9ffcc34ffc0429d9b92968d05fa5beb8848f Mon Sep 17 00:00:00 2001 From: Pablo Klaschka Date: Tue, 28 Jul 2020 11:50:13 +0200 Subject: [PATCH 10/13] A few first enhancements based on review comments --- types/application.d.ts | 5 +++-- types/scenegraph.d.ts | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/types/application.d.ts b/types/application.d.ts index e230a63..2c897cf 100644 --- a/types/application.d.ts +++ b/types/application.d.ts @@ -116,7 +116,9 @@ declare module 'application' { */ name: string, /** - * *Semi*-unique document identifier. Duplicating an .xd file on disk will result in two files with the same GUID. Duplicating a document via "Save As" will change its GUID; thus two *cloud* documents will never have the same GUID. The GUID of an Untitled document doesn't change when it is saved for the first time.

This returns the same value as `scenegraph.root.guid`. + * *Semi*-unique document identifier. Duplicating an .xd file on disk will result in two files with the same GUID. Duplicating a document via "Save As" will change its GUID; thus two *cloud* documents will never have the same GUID. The GUID of an Untitled document doesn't change when it is saved for the first time. + * + * This returns the same value as `scenegraph.root.guid`. */ guid: string }; @@ -159,7 +161,6 @@ declare module 'application' { * > **Tip** * > * > _This does **not** indicate the frontmost "active" document window in the XD application._ - * > * > In XD, each document window loads a separate copy of your plugin. When a given instance of your plugin calls this API, you will always receive information about the document that this instance of the plugin is attached to, even if it's not the active window. * * @example ```js diff --git a/types/scenegraph.d.ts b/types/scenegraph.d.ts index 1ccf1ca..ea86a8c 100644 --- a/types/scenegraph.d.ts +++ b/types/scenegraph.d.ts @@ -131,7 +131,7 @@ declare module 'scenegraph' { /** * Represents the children of a scenenode. Typically accessed via the SceneNode.children property. */ - declare interface SceneNodeList { + interface SceneNodeList { items: SceneNode[]; readonly length: number; @@ -442,7 +442,7 @@ declare module 'scenegraph' { * (**Since**: XD 29) * * A unique identifier for the image asset used by this ImageFill. May be shared by other ImageFills, including those with different cropping, size, - rotation, or mirroring. If identical images are imported into XD from separate sources, they may have different `assetId`s however. + * rotation, or mirroring. If identical images are imported into XD from separate sources, they may have different `assetId`s however. */ assetId: string; /** @@ -1662,7 +1662,7 @@ declare module 'scenegraph' { /** * Class representing the root node of the document. All Artboards are children of this node, as well as any pasteboard content that does not lie within an Artboard. Artboards must be grouped contiguously at the bottom of this node’s z order. The root node has no visual appearance of its own. */ - declare class RootNodeClass extends SceneNode { + class RootNodeClass extends SceneNode { /** * Adds a child node to this container node. You can only add leaf nodes this way; to create structured subtrees of content, use commands. * @param {SceneNode} node Child to add From bb230c8500a61e3f8c97a7cfd8bbe75d47acbcad Mon Sep 17 00:00:00 2001 From: Pablo Klaschka Date: Tue, 28 Jul 2020 12:07:15 +0200 Subject: [PATCH 11/13] A few first enhancements based on review comments --- types/application.d.ts | 11 ++--- types/assets.d.ts | 10 ++-- types/index.d.ts | 2 + types/interactions.d.ts | 53 ++++++++++---------- types/scenegraph.d.ts | 106 ++++++++++++++++++++++------------------ 5 files changed, 96 insertions(+), 86 deletions(-) diff --git a/types/application.d.ts b/types/application.d.ts index 2c897cf..520ee62 100644 --- a/types/application.d.ts +++ b/types/application.d.ts @@ -163,12 +163,11 @@ declare module 'application' { * > _This does **not** indicate the frontmost "active" document window in the XD application._ * > In XD, each document window loads a separate copy of your plugin. When a given instance of your plugin calls this API, you will always receive information about the document that this instance of the plugin is attached to, even if it's not the active window. * - * @example ```js - let application = require("application"); - let documentInfo = application.activeDocument; - console.log("Document title: " + documentInfo.name); - console.log("Document ID: " + documentInfo.guid); - ``` + * @example + * let application = require("application"); + * let documentInfo = application.activeDocument; + * console.log("Document title: " + documentInfo.name); + * console.log("Document ID: " + documentInfo.guid); */ export const activeDocument: DocumentInfo; diff --git a/types/assets.d.ts b/types/assets.d.ts index 91e5fe8..84dbd4a 100644 --- a/types/assets.d.ts +++ b/types/assets.d.ts @@ -119,9 +119,8 @@ declare module 'assets' { * The list may contain a mix of solid Color assets and/or gradient assets. If there are no color/gradient assets, an empty array is returned. * * @example - * var assets = require("assets"), - * allColors = assets.colors.get(); - * + * var assets = require("assets"), + * allColors = assets.colors.get(); */ get(): Array; @@ -155,9 +154,8 @@ declare module 'assets' { * If there are no character style assets, an empty array is returned. * * @example - * var assets = require("assets"), - * allCharacterStyles = assets.characterStyles.get(); - * + * var assets = require("assets"), + * allCharacterStyles = assets.characterStyles.get(); */ get(): Array; diff --git a/types/index.d.ts b/types/index.d.ts index 1078354..50ff644 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -101,6 +101,8 @@ declare global { readonly focusedArtboard: Artboard | null | undefined; /** + * **Since:** XD 28 + * * Returns true if the node is accessible for editing in the scope of the current edit context. * If false, the node cannot be edited given the user's current selection. * Nodes that are currently selected are always in the current edit context. diff --git a/types/interactions.d.ts b/types/interactions.d.ts index 9fc8db8..917d590 100644 --- a/types/interactions.d.ts +++ b/types/interactions.d.ts @@ -7,37 +7,38 @@ declare module 'interactions' { */ export const homeArtboard: Artboard | null; -/** - * Returns a collection of *all* interactions across the entire document, grouped by triggering scenenode. Each entry in this array specifies a `triggerNode` and the result of getting [`triggerNode.triggeredInteractions`](./scenegraph.md#SceneNode-triggeredInteractions). - * - * May include interactions that are impossible to trigger because the trigger node (or one of its ancestors) has `visible` = false. - * - * > **Tip** - * > - * > Currently, this API excludes some types of interactions: keypress/gamepad, scrolling, hover, component state transitions, or non-speech audio playback. - */ -export const allInteractions: Array<{ triggerNode: SceneNode, interactions: Array }>; + /** + * Returns a collection of *all* interactions across the entire document, grouped by triggering scenenode. Each entry in this array specifies a `triggerNode` and the result of getting [`triggerNode.triggeredInteractions`](./scenegraph.md#SceneNode-triggeredInteractions). + * + * May include interactions that are impossible to trigger because the trigger node (or one of its ancestors) has `visible` = false. + * + * > **Tip** + * > + * > Currently, this API excludes some types of interactions: keypress/gamepad, scrolling, hover, component state transitions, or non-speech audio playback. + */ + export const allInteractions: Array<{ triggerNode: SceneNode, interactions: Array }>; /** * An interaction consists of a Trigger + Action pair and is attached to a single, specific scenenode. * - * @example ```javascript - { - trigger: { - type: "tap" - }, - action: { - type: "goToArtboard", - destination: Artboard node, - preserveScrollPosition: false, - transition: { - type: "dissolve", - duration: 0.4, - easing: "ease-out" - } - } -}``` * Note: Interaction objects are not plain JSON -- they may reference scenenodes (as seen above) and other strongly-typed objects. + * + * @example + * { + * trigger: { + * type: "tap" + * }, + * action: { + * type: "goToArtboard", + * destination: Artboard node, + * preserveScrollPosition: false, + * transition: { + * type: "dissolve", + * duration: 0.4, + * easing: "ease-out" + * } + * } + * } */ type Interaction = { /** diff --git a/types/scenegraph.d.ts b/types/scenegraph.d.ts index e77338a..e530c01 100644 --- a/types/scenegraph.d.ts +++ b/types/scenegraph.d.ts @@ -20,27 +20,38 @@ declare module 'scenegraph' { * Each per-plugin storage silo is a collection of key-value pairs. Keys and values must both be strings. * * *Each* scenenode has its own metadata storage, accessed via `SceneNode.sharedPluginData`. To store general metadata that is not specific to one scenenode, use `sharedPluginData` on the document's scenegraph root. - * @example ```js + * @example * // This example shows how to save & retrieve rich JSON data in shared metadata storage. - // See below for simpler examples of using individual APIs. - const PLUGIN_ID = ""; - let richObject = { - list: [2, 4, 6], - name: "Hello world" - }; - node.sharedPluginData.setItem(PLUGIN_ID, "richData", JSON.stringify(richObject)); - - // Later on... - // (This could be in a different plugin, if it passes the original plugin's ID here) - let jsonString = node.sharedPluginData.getItem(PLUGIN_ID, "richData"); - if (jsonString) { // may be undefined - let richObjectCopy = JSON.parse(jsonString); - console.log(richObjectCopy.list.length); // 3 - } + * // See below for simpler examples of using individual APIs. + * const PLUGIN_ID = ""; + * let richObject = { + * list: [2, 4, 6], + * name: "Hello world" + * }; + * node.sharedPluginData.setItem(PLUGIN_ID, "richData", JSON.stringify(richObject)); + * + * // Later on... + * // (This could be in a different plugin, if it passes the original plugin's ID here) + * let jsonString = node.sharedPluginData.getItem(PLUGIN_ID, "richData"); + * if (jsonString) { // may be undefined + * let richObjectCopy = JSON.parse(jsonString); + * console.log(richObjectCopy.list.length); // 3 + * } */ interface PerPluginStorage { /** * Returns a map where key is plugin ID and value is a nested map containing all the shared metadata for that plugin ID (i.e. the result of calling `getForPluginId()` with that ID). + * + * This map is a clone of the stored metadata, so modifying it has no effect. + * + * @example + * let allSharedMetadata = node.sharedPluginData.getAll(); + * console.log("Plugin A's 'foo' value:", + * allSharedMetadata["A"] && allSharedMetadata["A"].foo); + * console.log("All of plugin B's shared metadata on this node:", + * allSharedMetadata["B"]); + * console.log("List of plugins storing shared metadata on this node:", + * Object.keys(allSharedMetadata)); */ getAll(): { [key: string]: { [key: string]: string } }; @@ -51,14 +62,14 @@ declare module 'scenegraph' { * @param pluginId * * @example - *const MY_PLUGIN_ID = ""; - let mySharedMetadata = node.sharedPluginData.getForPluginId(MY_PLUGIN_ID); - console.log("My shared 'foo' & 'bar' values:", - mySharedMetadata.foo, mySharedMetadata.bar); - - - console.log("Plugin B's shared 'foo' value:", - node.sharedPluginData.getForPluginId("B").foo); + * const MY_PLUGIN_ID = ""; + * let mySharedMetadata = node.sharedPluginData.getForPluginId(MY_PLUGIN_ID); + * console.log("My shared 'foo' & 'bar' values:", + * mySharedMetadata.foo, mySharedMetadata.bar); + * + * + * console.log("Plugin B's shared 'foo' value:", + * node.sharedPluginData.getForPluginId("B").foo); */ getForPluginId(pluginId: string): { [key: string]: string }; @@ -68,7 +79,7 @@ declare module 'scenegraph' { * * @example * console.log("All properties stored by plugin A on this node:", - node.sharedPluginData.keys("A")); + * node.sharedPluginData.keys("A")); */ keys(pluginId: string): string[]; @@ -81,8 +92,8 @@ declare module 'scenegraph' { * * @example * // These are two different values, stored independently per plugin - console.log("Plugin A's 'foo' value:", node.sharedPluginData.getItem("A", "foo")); - console.log("Plugin B's 'foo' value:", node.sharedPluginData.getItem("B", "foo")); + * console.log("Plugin A's 'foo' value:", node.sharedPluginData.getItem("A", "foo")); + * console.log("Plugin B's 'foo' value:", node.sharedPluginData.getItem("B", "foo")); */ getItem(pluginId: string, key: string): string | undefined; @@ -93,13 +104,13 @@ declare module 'scenegraph' { * @param value If undefined, behaves as if you'd called `removeItem()` instead. * * @example - const MY_PLUGIN_ID = ""; - node.sharedPluginData.setItem(MY_PLUGIN_ID, "foo", "42"); - - node.sharedPluginData.setItem("other_plugin_id", "foo", "42"); - // ^ ERROR: other plugin's metadata is read-only - - console.log(node.sharedPluginData.getItem(MY_PLUGIN_ID, "foo")); // "42" + * const MY_PLUGIN_ID = ""; + * node.sharedPluginData.setItem(MY_PLUGIN_ID, "foo", "42"); + * + * node.sharedPluginData.setItem("other_plugin_id", "foo", "42"); + * // ^ ERROR: other plugin's metadata is read-only + * + * console.log(node.sharedPluginData.getItem(MY_PLUGIN_ID, "foo")); // "42" */ setItem(pluginId: string, key: string, value: string | undefined): void; @@ -107,13 +118,14 @@ declare module 'scenegraph' { * Clears a shared metadata key stored by your plugin. * @param pluginId *Must* be equal to your plugin's ID. * @param key + * * @example - const MY_PLUGIN_ID = ""; - node.sharedPluginData.setItem(MY_PLUGIN_ID, "foo", "42"); - console.log(node.sharedPluginData.getItem(MY_PLUGIN_ID, "foo")); // "42" - - node.sharedPluginData.removeItem(MY_PLUGIN_ID, "foo"); - console.log(node.sharedPluginData.getItem(MY_PLUGIN_ID, "foo")); // undefined + * const MY_PLUGIN_ID = ""; + * node.sharedPluginData.setItem(MY_PLUGIN_ID, "foo", "42"); + * console.log(node.sharedPluginData.getItem(MY_PLUGIN_ID, "foo")); // "42" + * + * node.sharedPluginData.removeItem(MY_PLUGIN_ID, "foo"); + * console.log(node.sharedPluginData.getItem(MY_PLUGIN_ID, "foo")); // undefined */ removeItem(pluginId: string, key: string): void; @@ -692,12 +704,11 @@ declare module 'scenegraph' { * * Currently, this API excludes some types of interactions: keypress/gamepad, scrolling, hover, component state transitions, or non-speech audio playback. * - * @example ```javascript + * @example * // Print all the interactions triggered by a node - *node.triggeredInteractions.forEach(interaction => { + * node.triggeredInteractions.forEach(interaction => { * console.log("Trigger: " + interaction.trigger.type + " -> Action: " + interaction.action.type); - *}); - * ``` + * }); * * @see interactions.allInteractions */ @@ -765,14 +776,13 @@ declare module 'scenegraph' { * Blend mode determines how a node is composited onto the content below it. * * One of: `SceneNode.BLEND_MODE_PASSTHROUGH`, `BLEND_MODE_NORMAL`, `BLEND_MODE_MULTIPLY`, `BLEND_MODE_DARKEN`, `BLEND_MODE_COLOR_BURN`, `BLEND_MODE_LIGHTEN`, `BLEND_MODE_SCREEN`, `BLEND_MODE_COLOR_DODGE`, `BLEND_MODE_OVERLAY`, `BLEND_MODE_SOFT_LIGHT`, - `BLEND_MODE_HARD_LIGHT`, `BLEND_MODE_DIFFERENCE`, `BLEND_MODE_EXCLUSION`, `BLEND_MODE_HUE`, `BLEND_MODE_SATURATION`, `BLEND_MODE_COLOR`, `BLEND_MODE_LUMINOSITY`. + * `BLEND_MODE_HARD_LIGHT`, `BLEND_MODE_DIFFERENCE`, `BLEND_MODE_EXCLUSION`, `BLEND_MODE_HUE`, `BLEND_MODE_SATURATION`, `BLEND_MODE_COLOR`, `BLEND_MODE_LUMINOSITY`. * * _Note:_ for leaf nodes (GraphicNode), the XD UI may show leaf nodes as blend mode "Normal" even when the underlying value is `BLEND_MODE_PASSTHROUGH`. This is because "Pass Through" and "Normal" are essentially equivalent for leaf nodes -- they only differ * in appearance when a node has children. * - * @example ```js - *node.blendMode = scenegraph.SceneNode.BLEND_MODE_LUMINOSITY; - *``` + * @example + * node.blendMode = scenegraph.SceneNode.BLEND_MODE_LUMINOSITY; */ blendMode: string; // TODO: Implement the actual constant value possibilities From 9e356dcc304d97f561a03a16adc12c0de743eac3 Mon Sep 17 00:00:00 2001 From: Pablo Klaschka Date: Tue, 28 Jul 2020 12:30:18 +0200 Subject: [PATCH 12/13] A few first enhancements based on review comments --- types/scenegraph.d.ts | 79 +++++++++++++++++++++---------------------- 1 file changed, 38 insertions(+), 41 deletions(-) diff --git a/types/scenegraph.d.ts b/types/scenegraph.d.ts index e530c01..758473b 100644 --- a/types/scenegraph.d.ts +++ b/types/scenegraph.d.ts @@ -809,10 +809,9 @@ declare module 'scenegraph' { * Attempting to set this property when Responsive Resize is unavailable results in an error. * * Setting this property will cause `hasCustomConstraints` to become true. - * @example ```js - *let node = selection.items[0]; - *node.horizontalConstraints = { position: scenegraph.SceneNode.FIXED_LEFT, size: scenegraph.SceneNode.SIZE_FIXED }; - *``` + * @example + * let node = selection.items[0]; + * node.horizontalConstraints = { position: scenegraph.SceneNode.FIXED_LEFT, size: scenegraph.SceneNode.SIZE_FIXED }; */ horizontalConstraints: undefined | { /** @@ -834,9 +833,9 @@ declare module 'scenegraph' { * * See `horizontalConstraints` for other important notes. * - * @example ```js - *let node = selection.items[0]; - *node.verticalConstraints = { position: scenegraph.SceneNode.FIXED_TOP, size: scenegraph.SceneNode.SIZE_RESIZES }; + * @example + * let node = selection.items[0]; + * node.verticalConstraints = { position: scenegraph.SceneNode.FIXED_TOP, size: scenegraph.SceneNode.SIZE_RESIZES }; */ verticalConstraints: undefined | { /** @@ -1192,35 +1191,34 @@ declare module 'scenegraph' { * * Like all shape nodes, has no size, fill, or stroke by default unless you set one. * - * @example ```javascript - // Add a red triangle to the document - var polygon = new Polygon(); - polygon.cornerCount = 3; - polygon.width = 50; - polygon.height = 100; - polygon.fill = new Color("red"); - selection.insertionParent.addChild(polygon); - selection.items = [polygon]; - - // Add a blue 5-pointed star with rounded corners - var polygon = new Polygon(); - polygon.cornerCount = 5; - polygon.starRatio = 55; - polygon.setAllCornerRadii(4); - polygon.width = 100; - polygon.height = 95; - polygon.fill = new Color("blue"); - selection.insertionParent.addChild(polygon); - * ``` + * @example + * // Add a red triangle to the document + * var polygon = new Polygon(); + * polygon.cornerCount = 3; + * polygon.width = 50; + * polygon.height = 100; + * polygon.fill = new Color("red"); + * selection.insertionParent.addChild(polygon); + * selection.items = [polygon]; + * + * // Add a blue 5-pointed star with rounded corners + * var polygon = new Polygon(); + * polygon.cornerCount = 5; + * polygon.starRatio = 55; + * polygon.setAllCornerRadii(4); + * polygon.width = 100; + * polygon.height = 95; + * polygon.fill = new Color("blue"); + * selection.insertionParent.addChild(polygon); */ export class Polygon extends GraphicNode { /** - * > 0 + * must be > 0 */ width: number; /** - * > 0 + * must be > 0 */ height: number; @@ -1247,7 +1245,7 @@ declare module 'scenegraph' { readonly hasRoundedCorners: boolean; /** - * List of corner radius for each corner of the polygon. To set corner radius, use [setAllCornerRadii()](#Polygon-setAllCornerRadii). + * List of corner radius for each corner of the polygon. To set corner radius, use `setAllCornerRadii()`. */ cornerRadii: number[]; @@ -1422,7 +1420,7 @@ declare module 'scenegraph' { * Horizontal alignment: Text.ALIGN_LEFT, ALIGN_CENTER, or ALIGN_RIGHT. This setting affects the layout of multiline text, and for point text it also affects how the text is positioned relative to its anchor point (x=0 in local coordinates) and what direction the text grows when edited by the user. * * Changing textAlign on existing point text will cause it to shift horizontally. To change textAlign while keeping the text in a fixed position, shift the text horizontally (moving its anchor point) to compensate: - * @example ```javascript + * @example * let originalBounds = textNode.localBounds; * textNode.textAlign = newAlignValue; * let newBounds = textNode.localBounds; @@ -1720,16 +1718,15 @@ declare module 'scenegraph' { * operations and even across document open/closes. * @param guid SceneNode GUID -- must be all lowercase, as returned by the [`guid` getter](#SceneNode-guid). * - * @example ```js - let node = scenegraph.selection.items[0]; - let guid = node.guid; - // ...later on: - let sameNode = scenegraph.getNodeByGUID(guid); - if (sameNode) { - // ^ Always check if node still exists - user may have deleted it - console.log("Found node again!", sameNode); -} - ``` + * @example + * let node = scenegraph.selection.items[0]; + * let guid = node.guid; + * // ...later on: + * let sameNode = scenegraph.getNodeByGUID(guid); + * if (sameNode) { + * // ^ Always check if node still exists - user may have deleted it + * console.log("Found node again!", sameNode); + * } */ export function getNodeByGUID(guid: string): SceneNode | undefined; From 2f025d14e5755f456f8de4b66274f2b1155a6bc6 Mon Sep 17 00:00:00 2001 From: Pablo Klaschka Date: Tue, 28 Jul 2020 12:44:04 +0200 Subject: [PATCH 13/13] Going back to SceneNode being an interface (as per review comments) This stops the static constants from getting "implemented" in inheriting classes --- types/scenegraph.d.ts | 85 ++++++++++++++++++++++++++----------------- 1 file changed, 51 insertions(+), 34 deletions(-) diff --git a/types/scenegraph.d.ts b/types/scenegraph.d.ts index 758473b..8b116b0 100644 --- a/types/scenegraph.d.ts +++ b/types/scenegraph.d.ts @@ -489,7 +489,6 @@ declare module 'scenegraph' { clone(): ImageFill; } - export class Shadow { /** * X offset of the shadow relative to the shape it is attached to, in global coordinates (i.e. independent of the shape's rotation or any parent's rotation). May be negative. @@ -574,7 +573,7 @@ declare module 'scenegraph' { /** * Base class of all scenegraph nodes. Nodes will always be an instance of some subclass of SceneNode. */ - export abstract class SceneNode { + abstract class SceneNodeClass { /** * Returns a unique identifier for this node that stays the same when the file is closed & reopened, or if the node is moved to a different part of the document. Cut-Paste will result in a new guid, however. * @@ -752,23 +751,6 @@ declare module 'scenegraph' { sharedPluginData: PerPluginStorage; - public static readonly BLEND_MODE_PASSTHROUGH: string; - public static readonly BLEND_MODE_NORMAL: string; - public static readonly BLEND_MODE_MULTIPLY: string; - public static readonly BLEND_MODE_DARKEN: string; - public static readonly BLEND_MODE_COLOR_BURN: string; - public static readonly BLEND_MODE_LIGHTEN: string; - public static readonly BLEND_MODE_SCREEN: string; - public static readonly BLEND_MODE_COLOR_DODGE: string; - public static readonly BLEND_MODE_OVERLAY: string; - public static readonly BLEND_MODE_SOFT_LIGHT: string; - public static readonly BLEND_MODE_HARD_LIGHT: string; - public static readonly BLEND_MODE_DIFFERENCE: string; - public static readonly BLEND_MODE_EXCLUSION: string; - public static readonly BLEND_MODE_HUE: string; - public static readonly BLEND_MODE_SATURATION: string; - public static readonly BLEND_MODE_COLOR: string; - public static readonly BLEND_MODE_LUMINOSITY: string; /** * (**Since**: XD 27) @@ -786,15 +768,6 @@ declare module 'scenegraph' { */ blendMode: string; // TODO: Implement the actual constant value possibilities - public static readonly FIXED_LEFT: string; - public static readonly FIXED_RIGHT: string; - public static readonly FIXED_TOP: string; - public static readonly FIXED_BOTTOM: string; - public static readonly FIXED_BOTH: string; - public static readonly POSITION_PROPORTIONAL: string; - public static readonly SIZE_FIXED: string; - public static readonly SIZE_RESIZES: string; - /** * (**Since**: XD 29) * @@ -907,10 +880,54 @@ declare module 'scenegraph' { resize(width: number, height: number): void; } + /* + Export the "name" of SceneNode. This provides access to the "restricted" static + properties. + You could get the "type" of this by using `typeof SceneNode` (especially in type + declarations). + */ + export const SceneNode: SceneNodeStatic; + + export interface SceneNode extends SceneNodeClass { + } + + /* + Define the shape of the SceneNode "restricted" static properties. + NOTE: These properties do not show up on sub-classes so they cannot + be a part of the class-inheritance tree. + */ + interface SceneNodeStatic { + readonly BLEND_MODE_PASSTHROUGH: string; + readonly BLEND_MODE_NORMAL: string; + readonly BLEND_MODE_MULTIPLY: string; + readonly BLEND_MODE_DARKEN: string; + readonly BLEND_MODE_COLOR_BURN: string; + readonly BLEND_MODE_LIGHTEN: string; + readonly BLEND_MODE_SCREEN: string; + readonly BLEND_MODE_COLOR_DODGE: string; + readonly BLEND_MODE_OVERLAY: string; + readonly BLEND_MODE_SOFT_LIGHT: string; + readonly BLEND_MODE_HARD_LIGHT: string; + readonly BLEND_MODE_DIFFERENCE: string; + readonly BLEND_MODE_EXCLUSION: string; + readonly BLEND_MODE_HUE: string; + readonly BLEND_MODE_SATURATION: string; + readonly BLEND_MODE_COLOR: string; + readonly BLEND_MODE_LUMINOSITY: string; + readonly FIXED_LEFT: string; + readonly FIXED_RIGHT: string; + readonly FIXED_TOP: string; + readonly FIXED_BOTTOM: string; + readonly FIXED_BOTH: string; + readonly POSITION_PROPORTIONAL: string; + readonly SIZE_FIXED: string; + readonly SIZE_RESIZES: string; + } + /** * Base class for nodes that have a stroke and/or fill. This includes leaf nodes such as Rectangle, as well as BooleanGroup which is a container node. If you create a shape node, it will not be visible unless you explicitly give it either a stroke or a fill. */ - export class GraphicNode extends SceneNode { + export class GraphicNode extends SceneNodeClass { /** * The fill applied to this shape, if any. If this property is null or fillEnabled is false, no fill is drawn. Freshly created nodes have no fill by default. * @@ -1474,7 +1491,7 @@ declare module 'scenegraph' { * * In a Mask Group, the mask shape is included in the group’s children list, at the top of the z order. It is not visible - only its path outline is used, for clipping the group. */ - export class Group extends SceneNode { + export class Group extends SceneNodeClass { /** * The mask shape applied to this group, if any. This object is also present in the group’s children list. Though it has no direct visual appearance of its own, the mask affects the entire groups’s appearance by clipping all its other content. */ @@ -1531,7 +1548,7 @@ declare module 'scenegraph' { * * It is not currently possible for plugins to *create* a new component definition or a new SymbolInstance node, aside from using `require('commands').duplicate` to clone existing SymbolInstances. */ - export class SymbolInstance extends SceneNode { + export class SymbolInstance extends SceneNodeClass { /** * An identifier unique within this document that is shared by all instances of the same component. */ @@ -1575,7 +1592,7 @@ declare module 'scenegraph' { * Each grid cell is a Group that is an immediate child of the RepeatGrid. These groups are automatically created and destroyed as needed when the RepeatGrid is resized. * It is not currently possible for plugins to create a new RepeatGrid node, aside from using commands.duplicate to clone existing RepeatGrids. */ - export class RepeatGrid extends SceneNode { + export class RepeatGrid extends SceneNodeClass { /** * Defines size of the RepeatGrid. Cells are created and destroyed as necessary to fill the current size. Cells that only partially fit will be clipped. */ @@ -1661,7 +1678,7 @@ declare module 'scenegraph' { /** * Container node whose content is linked to an external resource, such as Creative Cloud Libraries. It cannot be edited except by first ungrouping it, breaking this link. */ - export class LinkedGraphic extends SceneNode { + export class LinkedGraphic extends SceneNodeClass { } export interface RootNode extends RootNodeClass { @@ -1670,7 +1687,7 @@ declare module 'scenegraph' { /** * Class representing the root node of the document. All Artboards are children of this node, as well as any pasteboard content that does not lie within an Artboard. Artboards must be grouped contiguously at the bottom of this node’s z order. The root node has no visual appearance of its own. */ - class RootNodeClass extends SceneNode { + class RootNodeClass extends SceneNodeClass { /** * Adds a child node to this container node. You can only add leaf nodes this way; to create structured subtrees of content, use commands. * @param {SceneNode} node Child to add