figma-plugin-react-hooks
Advanced tools
+0
-3
@@ -219,5 +219,2 @@ "use strict"; | ||
| // src/types.ts | ||
| var import_figma_plugin_api2 = require("figma-plugin-api"); | ||
| // src/hook.ts | ||
@@ -224,0 +221,0 @@ var defaultOptions = { |
+4
-4
| { | ||
| "version": 3, | ||
| "sources": ["../src/hook.ts", "../src/useMountedEffect.ts", "../src/index.ts", "../src/typeUtils.ts", "../src/constants.ts", "../src/utils.ts", "../src/types.ts"], | ||
| "sourcesContent": ["/* eslint-disable react-hooks/rules-of-hooks */\n\nimport { useEffect, useState } from 'react';\n\nimport useMountedEffect from './useMountedEffect';\n\nimport { api, listeners, setlisteners, updateApiWithOptions, updateUiApiWithOptions } from '.';\n\nimport {\n BareNode,\n FigmaSelectionHookOptions,\n FigmaSelectionListener,\n ResolverOptions,\n SerializedResolvedNode\n} from './types';\n\nexport { FigmaSelectionHookOptions } from './types';\nexport { FIGMA_MIXED } from './constants';\n\nconst defaultOptions = {\n nodeTypes: undefined,\n resolveChildren: false,\n resolveVariables: false,\n resolveProperties: 'all',\n addAncestorsVisibleProperty: false\n} as const satisfies ResolverOptions;\n\n/**\n * Only one config will take presence and it will be the config of the first hook that is mounted\n */\nconst useFigmaSelection = <const Options extends FigmaSelectionHookOptions>(\n hookOptions?: Options\n): [readonly SerializedResolvedNode<Options>[], (selection: readonly BareNode[]) => void] => {\n const opts = { ...defaultOptions, ...hookOptions } as const;\n\n const [selection, setSelection] = useState<readonly SerializedResolvedNode<Options>[]>([]);\n\n useMountedEffect(() => {\n console.warn('useFigmaSelection: changing options once mounted will not affect the behavior of the hook');\n }, [hookOptions]);\n\n useEffect(() => {\n const mount = async () => {\n // Typing the listeners explicitly is difficult due to the architecture, so we have to assert\n listeners.push(setSelection as unknown as FigmaSelectionListener);\n\n // if it's the first listener, register for selection change\n if (listeners.length === 1) {\n try {\n if (opts.apiOptions) {\n updateApiWithOptions(opts.apiOptions);\n updateUiApiWithOptions(opts.apiOptions);\n }\n await api._registerForSelectionChange(opts);\n } catch (e) {\n console.error(e);\n }\n }\n };\n\n mount();\n\n return () => {\n setlisteners(listeners.filter((l) => l !== (setSelection as unknown as FigmaSelectionListener)));\n if (!listeners.length) {\n // if it was the last listener, then we don't have to listen to selection change anymore\n api._deregisterForSelectionChange();\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return [selection as readonly SerializedResolvedNode<Options>[], api._setSelection];\n};\n\nexport default useFigmaSelection;\n", "import { useEffect, useRef } from 'react';\n\nconst useMountedEffect = (effect: React.EffectCallback, deps?: React.DependencyList | undefined) => {\n const didMountRef = useRef(false);\n\n useEffect(() => {\n if (didMountRef.current) {\n return effect();\n }\n didMountRef.current = true;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, deps);\n};\n\nexport default useMountedEffect;\n", "import { createUIAPI, createPluginAPI, RPCOptions } from 'figma-plugin-api';\n\nimport { nodeCanHaveChildren } from './typeUtils';\nimport { resolveAndFilterNodes } from './utils';\n\nimport {\n BareNode,\n FigmaSelectionHookOptions,\n FigmaSelectionListener,\n ResolverOptions,\n SerializedResolvedNode\n} from './types';\n\nexport { FIGMA_MIXED } from './constants';\n\ndeclare global {\n interface Window {\n _figma_onSelectionChange?: (selection: readonly SerializedResolvedNode<ResolverOptions>[]) => void;\n }\n}\n\nconst uiApiMethods = {\n _onSelectionChange(selection: readonly SerializedResolvedNode<ResolverOptions>[]) {\n if (typeof window._figma_onSelectionChange !== 'undefined') {\n window._figma_onSelectionChange(selection);\n }\n }\n};\n\nexport let uiApi = createUIAPI(uiApiMethods);\n\nexport const updateUiApiWithOptions = (rpcOptions: RPCOptions) => {\n uiApi = createUIAPI(uiApiMethods, rpcOptions);\n};\n\nconst selectionChangeHandler = () => {\n const resolvedSelection = resolveAndFilterNodes(figma.currentPage.selection, options);\n uiApi._onSelectionChange(resolvedSelection);\n};\n\nconst changesApplyToSelectedNodesOrDescendants = (e: DocumentChangeEvent, nodes: readonly SceneNode[]): boolean => {\n const changesApplyToNodes = e.documentChanges.some(\n (change) => nodes.findIndex((node) => node.id === change.id) !== -1\n );\n\n if (changesApplyToNodes) {\n return true;\n }\n\n const descendants: SceneNode[] = [];\n\n nodes.forEach((node) => {\n if (nodeCanHaveChildren(node)) {\n descendants.push(...node.children);\n }\n });\n\n if (descendants.length === 0) {\n return false;\n }\n\n return changesApplyToSelectedNodesOrDescendants(e, descendants);\n};\n\nconst documentChangeHandler = (e: DocumentChangeEvent) => {\n if (figma.currentPage.selection.length > 0) {\n const selection = figma.currentPage.selection;\n\n if (changesApplyToSelectedNodesOrDescendants(e, selection)) {\n selectionChangeHandler();\n }\n }\n};\n\nconst apiMethods = {\n _registerForSelectionChange(opts: FigmaSelectionHookOptions) {\n options = opts;\n\n const apiOptions = opts.apiOptions;\n if (apiOptions) {\n updateUiApiWithOptions(apiOptions);\n updateApiWithOptions(apiOptions);\n }\n\n figma.on('selectionchange', selectionChangeHandler);\n figma.on('documentchange', documentChangeHandler);\n selectionChangeHandler();\n },\n _deregisterForSelectionChange() {\n figma.off('selectionchange', selectionChangeHandler);\n figma.off('documentchange', documentChangeHandler);\n },\n _setSelection<N extends readonly BareNode[]>(newSelection: N) {\n figma.currentPage.selection = newSelection as unknown as readonly SceneNode[];\n }\n};\n\nexport let api = createPluginAPI(apiMethods);\n\nexport const updateApiWithOptions = (rpcOptions: RPCOptions) => {\n api = createPluginAPI(apiMethods, rpcOptions);\n};\n\nlet options: FigmaSelectionHookOptions;\n\nexport let listeners: FigmaSelectionListener[] = [];\n\nexport const setlisteners = (newListeners: FigmaSelectionListener[]) => {\n listeners = newListeners;\n};\n\n// In plugin UI, add a global function to receive selection change events\nif (typeof window !== 'undefined') {\n window._figma_onSelectionChange = (selection) => {\n listeners.forEach((l) => {\n l(selection);\n });\n };\n}\n", "import { ArrayType } from './typePrimitives';\n\nexport const isArray = Array.isArray as <T>(arg: T) => arg is ArrayType<T>;\n\nexport const strictObjectKeys = Object.keys as <T extends object>(obj: T) => Array<keyof T>;\n\nexport const nodeCanHaveChildren = <T extends SceneNode>(node: T): node is T & ChildrenMixin => {\n return 'children' in node;\n};\n", "/**\n * Used to replace `figma.mixed` during JSON serialization\n */\nexport const FIGMA_MIXED = 'mixed-57999e63-7384-42a1-acf8-d80b9f6c36a7';\n", "import { FIGMA_MIXED } from './constants';\nimport { nodeCanHaveChildren, strictObjectKeys } from './typeUtils';\n\nimport { ResolvedNode, ResolverOptions, SceneNodePropertyKey, SerializedResolvedNode } from './types';\n\nconst defaultNodePropertyGetterFilter = <Node extends SceneNode>(key: keyof Node, node: Node): boolean => {\n return (\n // Can only get component property definitions of a component set or non-variant component\n !(key === 'componentPropertyDefinitions' && node.parent?.type === 'COMPONENT_SET') &&\n // reading horizontalPadding and verticalPadding is no longer supported as left and right padding may differ\n key !== 'horizontalPadding' &&\n key !== 'verticalPadding'\n );\n};\n\nconst resolveNodeProperties = <Node extends SceneNode, const Keys extends readonly SceneNodePropertyKey[]>(\n node: Node,\n propertyKeys?: Keys\n): ResolvedNode<Node, Keys> => {\n // Narrow the type from the default which has a mapped type\n const descriptors = Object.getOwnPropertyDescriptors<Node>(Object.getPrototypeOf(node)) as {\n [P in keyof Node]: TypedPropertyDescriptor<Node[P]>;\n };\n\n // In reality the type is string | number | keyof T from getOwnPropertyDescriptors\n // but we can safely assert a narrower type\n const getterKeys = strictObjectKeys(descriptors);\n let getters = getterKeys.filter((key) => typeof descriptors[key].get === 'function');\n\n // type has to be included manually as it doesn't have a getter but isn't a static property either\n getters.push('type');\n\n if (propertyKeys) {\n getters = getters.filter((key) => propertyKeys.includes(key as SceneNodePropertyKey));\n }\n\n getters = getters.filter((key) => defaultNodePropertyGetterFilter(key, node));\n\n const objectWithProperties = {\n id: node.id\n } as Node;\n\n for (const getter of getters) {\n objectWithProperties[getter] = node[getter];\n }\n\n return objectWithProperties;\n};\n\nconst resolveAndSerializeNodeProperties = <Node extends SceneNode, const Options extends ResolverOptions>(\n object: Node,\n options: Options,\n ancestorsVisible: boolean\n): SerializedResolvedNode<Options> => {\n const { resolveProperties, addAncestorsVisibleProperty } = options;\n\n const resolvedNode = resolveNodeProperties(\n object,\n resolveProperties === 'all' ? undefined : resolveProperties\n ) as Record<string, unknown>;\n\n for (const key of strictObjectKeys(resolvedNode)) {\n if (resolvedNode[key] === figma.mixed) {\n resolvedNode[key] = FIGMA_MIXED;\n }\n }\n\n if (addAncestorsVisibleProperty) {\n resolvedNode.ancestorsVisible = ancestorsVisible;\n }\n\n return resolvedNode as SerializedResolvedNode<Options>;\n};\n\n// TODO: Resolve variables\nexport const resolveAndFilterNodes = <const Options extends ResolverOptions>(\n nodes: readonly SceneNode[],\n options: Options,\n ancestorsVisible: boolean = true\n): SerializedResolvedNode<Options>[] => {\n const result: SerializedResolvedNode<Options>[] = [];\n const { nodeTypes, resolveChildren } = options;\n\n if (nodeTypes !== undefined) {\n nodes.forEach((node) => {\n if (nodeTypes.includes(node.type)) {\n result.push(resolveAndSerializeNodeProperties(node, options, ancestorsVisible && node.visible));\n } else if (nodeCanHaveChildren(node) && resolveChildren) {\n result.push(...resolveAndFilterNodes(node.children, options, ancestorsVisible && node.visible));\n }\n });\n } else {\n nodes.forEach((node) => {\n if (nodeCanHaveChildren(node) && resolveChildren) {\n const newNode = {\n ...resolveAndSerializeNodeProperties(node, options, ancestorsVisible && node.visible),\n children: resolveAndFilterNodes(node.children, options, ancestorsVisible && node.visible)\n };\n result.push(newNode);\n } else {\n result.push(resolveAndSerializeNodeProperties(node, options, ancestorsVisible && node.visible));\n }\n });\n }\n\n return result;\n};\n", "import { RPCOptions } from 'figma-plugin-api';\n\nimport { FIGMA_MIXED } from './constants';\nimport { ApplicableNonFunctionPropertyKeys, ArrayElementUnion, NonFunctionPropertyKeys } from './typePrimitives';\n\n// For typedoc\nexport { RPCOptions } from 'figma-plugin-api';\nexport { FIGMA_MIXED } from './constants';\n\n/**\n * @internal\n * Describes the properties of an unresolved node\n */\nexport type BareNode = Pick<SceneNode, 'id'>;\n\ntype SceneNodeType = SceneNode['type'];\n\n/**\n * @internal\n */\nexport type SceneNodeFromTypes<T extends readonly SceneNodeType[] | undefined = undefined> = T extends undefined\n ? SceneNode\n : // @ts-expect-error If the check is flipped, the type widens to SceneNode. On simpler types this doesn't happen but it seems like Extract does this.\n ExtractedSceneNode<ArrayElementUnion<T>>;\n\n/**\n * @internal\n * Utility type to get only matching node types from the SceneNode union type.\n */\ntype ExtractedSceneNode<T extends SceneNodeType> = Extract<SceneNode, { type: T }>;\n\n/**\n * @internal\n * Get the non-function property keys for a SceneNode\n */\nexport type SceneNodePropertyKey<T extends SceneNodeType | undefined = undefined> = NonFunctionPropertyKeys<\n T extends SceneNodeType ? ExtractedSceneNode<T> : SceneNode\n>;\n\nexport type OptSceneNodeProperties = readonly SceneNodePropertyKey[] | 'all';\n\n/**\n * Use `satisfies` (for TS >= 4.9) with this type to allow for type checking the options object\n * while the type of the object remains exact.\n *\n * This allows us to infer the type of the returned nodes correctly.\n *\n * Example:\n * ```typescript\n * const options = {\n * nodeTypes: ['TEXT', 'FRAME'],\n * resolveProperties: ['name', 'characters', 'children]\n * } satisfies FigmaSelectionHookOptions;\n * ```\n */\nexport type FigmaSelectionHookOptions = {\n /**\n * Only return specific types of nodes.\n *\n * If left undefined, all nodes in the selection will be returned.\n *\n * Default: `undefined`\n */\n nodeTypes?: readonly SceneNodeType[] | undefined;\n /**\n * Figma node properties are lazy-loaded, so to use any property you have to resolve it first.\n *\n * Resolving all node properties causes a performance hit, so you can specify which properties you want to resolve.\n *\n * If set to `[]`, no properties will be resolved and you will only get the ids of the nodes.\n *\n * Node methods (such as `getPluginData`) will never be resolved.\n *\n * Default: `'all'`\n */\n resolveProperties?: OptSceneNodeProperties;\n /**\n * Resolve children nodes of the selection.\n *\n * If used with `nodeTypes`, all nodes of the specified types will be returned as a flat array.\n *\n * Default: `false`\n */\n resolveChildren?: boolean;\n /**\n * Resolve bound variables of the selection.\n *\n * Default: `false`\n */\n resolveVariables?: boolean;\n /**\n * Add `ancestorsVisible` property to all nodes.\n *\n * This property is true if all ancestors of the node are visible.\n *\n * Default: `false`\n */\n addAncestorsVisibleProperty?: boolean;\n /**\n * Options for figma-plugin-api\n *\n * Default: see the RPCOptions type\n */\n apiOptions?: RPCOptions | undefined;\n};\n\n/**\n * @internal\n */\nexport type ResolverOptions = Omit<FigmaSelectionHookOptions, 'apiOptions'>;\n\ntype SerializedNodeProperty<\n Prop,\n Node extends SceneNode,\n Options extends ResolverOptions\n> = Prop extends PluginAPI['mixed']\n ? typeof FIGMA_MIXED\n : Prop extends readonly SceneNode[]\n ? Options['resolveChildren'] extends true\n ? readonly SerializedNode<Node, Options>[]\n : readonly BareNode[]\n : Prop;\n\ntype ApplicableNodeKeys<\n Node extends SceneNode,\n Properties extends OptSceneNodeProperties\n> = Properties extends readonly SceneNodePropertyKey[]\n ? ApplicableNonFunctionPropertyKeys<Node, ArrayElementUnion<Properties>>\n : NonFunctionPropertyKeys<Node>;\n\n/**\n * @internal\n */\nexport type SerializedNode<Node extends SceneNode, Options extends ResolverOptions> = Node extends SceneNode\n ? {\n type: Node['type'];\n id: Node['id'];\n } & {\n [Key in Options['resolveProperties'] extends SceneNodePropertyKey[]\n ? ApplicableNodeKeys<Node, Options['resolveProperties']>\n : keyof Node]: SerializedNodeProperty<Node[Key], Node, Options>;\n }\n : never;\n\n/**\n * @internal\n */\nexport type ResolvedNode<\n Node extends SceneNode,\n Keys extends readonly SceneNodePropertyKey[] | undefined = undefined\n> = Pick<\n Node,\n Keys extends readonly SceneNodePropertyKey[]\n ? ApplicableNonFunctionPropertyKeys<Node, ArrayElementUnion<Keys>> | 'id' | 'type'\n : ApplicableNonFunctionPropertyKeys<Node, SceneNodePropertyKey>\n>;\n\n/**\n * @internal\n */\ntype SerializedResolvedNodeBase<Node extends SceneNode, Options extends ResolverOptions> = Node extends SceneNode\n ? SerializedNode<Node, Options>\n : never;\n\n/**\n * @internal\n */\ntype AncestorsVisibleMixin = {\n ancestorsVisible: boolean;\n};\n\n/**\n * @internal\n */\ntype ResolveVariablesMixin = {\n boundVariableInstances: readonly Variable[];\n};\n\n/**\n * @internal\n * All Figma nodes are converted to this type for serialization and sending to the plugin UI\n */\nexport type SerializedResolvedNode<Options extends ResolverOptions> =\n Options['addAncestorsVisibleProperty'] extends true\n ? Options['resolveVariables'] extends true\n ? SerializedResolvedNodeBase<SceneNodeFromTypes<Options['nodeTypes']>, Options> &\n AncestorsVisibleMixin &\n ResolveVariablesMixin\n : SerializedResolvedNodeBase<SceneNodeFromTypes<Options['nodeTypes']>, Options> & AncestorsVisibleMixin\n : SerializedResolvedNodeBase<SceneNodeFromTypes<Options['nodeTypes']>, Options>;\n\n/**\n * @internal\n */\nexport type FigmaSelectionListener = (selection: readonly SerializedResolvedNode<ResolverOptions>[]) => void;\n"], | ||
| "mappings": "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", | ||
| "names": ["import_react", "options", "import_figma_plugin_api"] | ||
| "sources": ["../src/hook.ts", "../src/useMountedEffect.ts", "../src/index.ts", "../src/typeUtils.ts", "../src/constants.ts", "../src/utils.ts"], | ||
| "sourcesContent": ["/* eslint-disable react-hooks/rules-of-hooks */\n\nimport { useEffect, useState } from 'react';\n\nimport useMountedEffect from './useMountedEffect';\n\nimport { api, listeners, setlisteners, updateApiWithOptions, updateUiApiWithOptions } from '.';\n\nimport {\n BareNode,\n FigmaSelectionHookOptions,\n FigmaSelectionListener,\n ResolverOptions,\n SerializedResolvedNode\n} from './types';\n\nexport { FigmaSelectionHookOptions } from './types';\nexport { FIGMA_MIXED } from './constants';\n\nconst defaultOptions = {\n nodeTypes: undefined,\n resolveChildren: false,\n resolveVariables: false,\n resolveProperties: 'all',\n addAncestorsVisibleProperty: false\n} as const satisfies ResolverOptions;\n\n/**\n * Only one config will take presence and it will be the config of the first hook that is mounted\n */\nconst useFigmaSelection = <const Options extends FigmaSelectionHookOptions>(\n hookOptions?: Options\n): [readonly SerializedResolvedNode<Options>[], (selection: readonly BareNode[]) => void] => {\n const opts = { ...defaultOptions, ...hookOptions } as const;\n\n const [selection, setSelection] = useState<readonly SerializedResolvedNode<Options>[]>([]);\n\n useMountedEffect(() => {\n console.warn('useFigmaSelection: changing options once mounted will not affect the behavior of the hook');\n }, [hookOptions]);\n\n useEffect(() => {\n const mount = async () => {\n // Typing the listeners explicitly is difficult due to the architecture, so we have to assert\n listeners.push(setSelection as unknown as FigmaSelectionListener);\n\n // if it's the first listener, register for selection change\n if (listeners.length === 1) {\n try {\n if (opts.apiOptions) {\n updateApiWithOptions(opts.apiOptions);\n updateUiApiWithOptions(opts.apiOptions);\n }\n await api._registerForSelectionChange(opts);\n } catch (e) {\n console.error(e);\n }\n }\n };\n\n mount();\n\n return () => {\n setlisteners(listeners.filter((l) => l !== (setSelection as unknown as FigmaSelectionListener)));\n if (!listeners.length) {\n // if it was the last listener, then we don't have to listen to selection change anymore\n api._deregisterForSelectionChange();\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return [selection as readonly SerializedResolvedNode<Options>[], api._setSelection];\n};\n\nexport default useFigmaSelection;\n", "import { useEffect, useRef } from 'react';\n\nconst useMountedEffect = (effect: React.EffectCallback, deps?: React.DependencyList | undefined) => {\n const didMountRef = useRef(false);\n\n useEffect(() => {\n if (didMountRef.current) {\n return effect();\n }\n didMountRef.current = true;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, deps);\n};\n\nexport default useMountedEffect;\n", "import { createUIAPI, createPluginAPI } from 'figma-plugin-api';\n\nimport { nodeCanHaveChildren } from './typeUtils';\nimport { resolveAndFilterNodes } from './utils';\n\n// Allow esbuild to drop the import\nimport type { RPCOptions } from 'figma-plugin-api';\n\nimport {\n BareNode,\n FigmaSelectionHookOptions,\n FigmaSelectionListener,\n ResolverOptions,\n SerializedResolvedNode\n} from './types';\n\nexport { FIGMA_MIXED } from './constants';\n\ndeclare global {\n interface Window {\n _figma_onSelectionChange?: (selection: readonly SerializedResolvedNode<ResolverOptions>[]) => void;\n }\n}\n\nconst uiApiMethods = {\n _onSelectionChange(selection: readonly SerializedResolvedNode<ResolverOptions>[]) {\n if (typeof window._figma_onSelectionChange !== 'undefined') {\n window._figma_onSelectionChange(selection);\n }\n }\n};\n\nexport let uiApi = createUIAPI(uiApiMethods);\n\nexport const updateUiApiWithOptions = (rpcOptions: RPCOptions) => {\n uiApi = createUIAPI(uiApiMethods, rpcOptions);\n};\n\nconst selectionChangeHandler = () => {\n const resolvedSelection = resolveAndFilterNodes(figma.currentPage.selection, options);\n uiApi._onSelectionChange(resolvedSelection);\n};\n\nconst changesApplyToSelectedNodesOrDescendants = (e: DocumentChangeEvent, nodes: readonly SceneNode[]): boolean => {\n const changesApplyToNodes = e.documentChanges.some(\n (change) => nodes.findIndex((node) => node.id === change.id) !== -1\n );\n\n if (changesApplyToNodes) {\n return true;\n }\n\n const descendants: SceneNode[] = [];\n\n nodes.forEach((node) => {\n if (nodeCanHaveChildren(node)) {\n descendants.push(...node.children);\n }\n });\n\n if (descendants.length === 0) {\n return false;\n }\n\n return changesApplyToSelectedNodesOrDescendants(e, descendants);\n};\n\nconst documentChangeHandler = (e: DocumentChangeEvent) => {\n if (figma.currentPage.selection.length > 0) {\n const selection = figma.currentPage.selection;\n\n if (changesApplyToSelectedNodesOrDescendants(e, selection)) {\n selectionChangeHandler();\n }\n }\n};\n\nconst apiMethods = {\n _registerForSelectionChange(opts: FigmaSelectionHookOptions) {\n options = opts;\n\n const apiOptions = opts.apiOptions;\n if (apiOptions) {\n updateUiApiWithOptions(apiOptions);\n updateApiWithOptions(apiOptions);\n }\n\n figma.on('selectionchange', selectionChangeHandler);\n figma.on('documentchange', documentChangeHandler);\n selectionChangeHandler();\n },\n _deregisterForSelectionChange() {\n figma.off('selectionchange', selectionChangeHandler);\n figma.off('documentchange', documentChangeHandler);\n },\n _setSelection<N extends readonly BareNode[]>(newSelection: N) {\n figma.currentPage.selection = newSelection as unknown as readonly SceneNode[];\n }\n};\n\nexport let api = createPluginAPI(apiMethods);\n\nexport const updateApiWithOptions = (rpcOptions: RPCOptions) => {\n api = createPluginAPI(apiMethods, rpcOptions);\n};\n\nlet options: FigmaSelectionHookOptions;\n\nexport let listeners: FigmaSelectionListener[] = [];\n\nexport const setlisteners = (newListeners: FigmaSelectionListener[]) => {\n listeners = newListeners;\n};\n\n// In plugin UI, add a global function to receive selection change events\nif (typeof window !== 'undefined') {\n window._figma_onSelectionChange = (selection) => {\n listeners.forEach((l) => {\n l(selection);\n });\n };\n}\n", "import { ArrayType } from './typePrimitives';\n\nexport const isArray = Array.isArray as <T>(arg: T) => arg is ArrayType<T>;\n\nexport const strictObjectKeys = Object.keys as <T extends object>(obj: T) => Array<keyof T>;\n\nexport const nodeCanHaveChildren = <T extends SceneNode>(node: T): node is T & ChildrenMixin => {\n return 'children' in node;\n};\n", "/**\n * Used to replace `figma.mixed` during JSON serialization\n */\nexport const FIGMA_MIXED = 'mixed-57999e63-7384-42a1-acf8-d80b9f6c36a7';\n", "import { FIGMA_MIXED } from './constants';\nimport { nodeCanHaveChildren, strictObjectKeys } from './typeUtils';\n\nimport { ResolvedNode, ResolverOptions, SceneNodePropertyKey, SerializedResolvedNode } from './types';\n\nconst defaultNodePropertyGetterFilter = <Node extends SceneNode>(key: keyof Node, node: Node): boolean => {\n return (\n // Can only get component property definitions of a component set or non-variant component\n !(key === 'componentPropertyDefinitions' && node.parent?.type === 'COMPONENT_SET') &&\n // reading horizontalPadding and verticalPadding is no longer supported as left and right padding may differ\n key !== 'horizontalPadding' &&\n key !== 'verticalPadding'\n );\n};\n\nconst resolveNodeProperties = <Node extends SceneNode, const Keys extends readonly SceneNodePropertyKey[]>(\n node: Node,\n propertyKeys?: Keys\n): ResolvedNode<Node, Keys> => {\n // Narrow the type from the default which has a mapped type\n const descriptors = Object.getOwnPropertyDescriptors<Node>(Object.getPrototypeOf(node)) as {\n [P in keyof Node]: TypedPropertyDescriptor<Node[P]>;\n };\n\n // In reality the type is string | number | keyof T from getOwnPropertyDescriptors\n // but we can safely assert a narrower type\n const getterKeys = strictObjectKeys(descriptors);\n let getters = getterKeys.filter((key) => typeof descriptors[key].get === 'function');\n\n // type has to be included manually as it doesn't have a getter but isn't a static property either\n getters.push('type');\n\n if (propertyKeys) {\n getters = getters.filter((key) => propertyKeys.includes(key as SceneNodePropertyKey));\n }\n\n getters = getters.filter((key) => defaultNodePropertyGetterFilter(key, node));\n\n const objectWithProperties = {\n id: node.id\n } as Node;\n\n for (const getter of getters) {\n objectWithProperties[getter] = node[getter];\n }\n\n return objectWithProperties;\n};\n\nconst resolveAndSerializeNodeProperties = <Node extends SceneNode, const Options extends ResolverOptions>(\n object: Node,\n options: Options,\n ancestorsVisible: boolean\n): SerializedResolvedNode<Options> => {\n const { resolveProperties, addAncestorsVisibleProperty } = options;\n\n const resolvedNode = resolveNodeProperties(\n object,\n resolveProperties === 'all' ? undefined : resolveProperties\n ) as Record<string, unknown>;\n\n for (const key of strictObjectKeys(resolvedNode)) {\n if (resolvedNode[key] === figma.mixed) {\n resolvedNode[key] = FIGMA_MIXED;\n }\n }\n\n if (addAncestorsVisibleProperty) {\n resolvedNode.ancestorsVisible = ancestorsVisible;\n }\n\n return resolvedNode as SerializedResolvedNode<Options>;\n};\n\n// TODO: Resolve variables\nexport const resolveAndFilterNodes = <const Options extends ResolverOptions>(\n nodes: readonly SceneNode[],\n options: Options,\n ancestorsVisible: boolean = true\n): SerializedResolvedNode<Options>[] => {\n const result: SerializedResolvedNode<Options>[] = [];\n const { nodeTypes, resolveChildren } = options;\n\n if (nodeTypes !== undefined) {\n nodes.forEach((node) => {\n if (nodeTypes.includes(node.type)) {\n result.push(resolveAndSerializeNodeProperties(node, options, ancestorsVisible && node.visible));\n } else if (nodeCanHaveChildren(node) && resolveChildren) {\n result.push(...resolveAndFilterNodes(node.children, options, ancestorsVisible && node.visible));\n }\n });\n } else {\n nodes.forEach((node) => {\n if (nodeCanHaveChildren(node) && resolveChildren) {\n const newNode = {\n ...resolveAndSerializeNodeProperties(node, options, ancestorsVisible && node.visible),\n children: resolveAndFilterNodes(node.children, options, ancestorsVisible && node.visible)\n };\n result.push(newNode);\n } else {\n result.push(resolveAndSerializeNodeProperties(node, options, ancestorsVisible && node.visible));\n }\n });\n }\n\n return result;\n};\n"], | ||
| "mappings": "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", | ||
| "names": ["import_react", "options"] | ||
| } |
+0
-3
@@ -197,5 +197,2 @@ var __defProp = Object.defineProperty; | ||
| // src/types.ts | ||
| import { RPCOptions as RPCOptions2 } from "figma-plugin-api"; | ||
| // src/hook.ts | ||
@@ -202,0 +199,0 @@ var defaultOptions = { |
+4
-4
| { | ||
| "version": 3, | ||
| "sources": ["../src/hook.ts", "../src/useMountedEffect.ts", "../src/index.ts", "../src/typeUtils.ts", "../src/constants.ts", "../src/utils.ts", "../src/types.ts"], | ||
| "sourcesContent": ["/* eslint-disable react-hooks/rules-of-hooks */\n\nimport { useEffect, useState } from 'react';\n\nimport useMountedEffect from './useMountedEffect';\n\nimport { api, listeners, setlisteners, updateApiWithOptions, updateUiApiWithOptions } from '.';\n\nimport {\n BareNode,\n FigmaSelectionHookOptions,\n FigmaSelectionListener,\n ResolverOptions,\n SerializedResolvedNode\n} from './types';\n\nexport { FigmaSelectionHookOptions } from './types';\nexport { FIGMA_MIXED } from './constants';\n\nconst defaultOptions = {\n nodeTypes: undefined,\n resolveChildren: false,\n resolveVariables: false,\n resolveProperties: 'all',\n addAncestorsVisibleProperty: false\n} as const satisfies ResolverOptions;\n\n/**\n * Only one config will take presence and it will be the config of the first hook that is mounted\n */\nconst useFigmaSelection = <const Options extends FigmaSelectionHookOptions>(\n hookOptions?: Options\n): [readonly SerializedResolvedNode<Options>[], (selection: readonly BareNode[]) => void] => {\n const opts = { ...defaultOptions, ...hookOptions } as const;\n\n const [selection, setSelection] = useState<readonly SerializedResolvedNode<Options>[]>([]);\n\n useMountedEffect(() => {\n console.warn('useFigmaSelection: changing options once mounted will not affect the behavior of the hook');\n }, [hookOptions]);\n\n useEffect(() => {\n const mount = async () => {\n // Typing the listeners explicitly is difficult due to the architecture, so we have to assert\n listeners.push(setSelection as unknown as FigmaSelectionListener);\n\n // if it's the first listener, register for selection change\n if (listeners.length === 1) {\n try {\n if (opts.apiOptions) {\n updateApiWithOptions(opts.apiOptions);\n updateUiApiWithOptions(opts.apiOptions);\n }\n await api._registerForSelectionChange(opts);\n } catch (e) {\n console.error(e);\n }\n }\n };\n\n mount();\n\n return () => {\n setlisteners(listeners.filter((l) => l !== (setSelection as unknown as FigmaSelectionListener)));\n if (!listeners.length) {\n // if it was the last listener, then we don't have to listen to selection change anymore\n api._deregisterForSelectionChange();\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return [selection as readonly SerializedResolvedNode<Options>[], api._setSelection];\n};\n\nexport default useFigmaSelection;\n", "import { useEffect, useRef } from 'react';\n\nconst useMountedEffect = (effect: React.EffectCallback, deps?: React.DependencyList | undefined) => {\n const didMountRef = useRef(false);\n\n useEffect(() => {\n if (didMountRef.current) {\n return effect();\n }\n didMountRef.current = true;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, deps);\n};\n\nexport default useMountedEffect;\n", "import { createUIAPI, createPluginAPI, RPCOptions } from 'figma-plugin-api';\n\nimport { nodeCanHaveChildren } from './typeUtils';\nimport { resolveAndFilterNodes } from './utils';\n\nimport {\n BareNode,\n FigmaSelectionHookOptions,\n FigmaSelectionListener,\n ResolverOptions,\n SerializedResolvedNode\n} from './types';\n\nexport { FIGMA_MIXED } from './constants';\n\ndeclare global {\n interface Window {\n _figma_onSelectionChange?: (selection: readonly SerializedResolvedNode<ResolverOptions>[]) => void;\n }\n}\n\nconst uiApiMethods = {\n _onSelectionChange(selection: readonly SerializedResolvedNode<ResolverOptions>[]) {\n if (typeof window._figma_onSelectionChange !== 'undefined') {\n window._figma_onSelectionChange(selection);\n }\n }\n};\n\nexport let uiApi = createUIAPI(uiApiMethods);\n\nexport const updateUiApiWithOptions = (rpcOptions: RPCOptions) => {\n uiApi = createUIAPI(uiApiMethods, rpcOptions);\n};\n\nconst selectionChangeHandler = () => {\n const resolvedSelection = resolveAndFilterNodes(figma.currentPage.selection, options);\n uiApi._onSelectionChange(resolvedSelection);\n};\n\nconst changesApplyToSelectedNodesOrDescendants = (e: DocumentChangeEvent, nodes: readonly SceneNode[]): boolean => {\n const changesApplyToNodes = e.documentChanges.some(\n (change) => nodes.findIndex((node) => node.id === change.id) !== -1\n );\n\n if (changesApplyToNodes) {\n return true;\n }\n\n const descendants: SceneNode[] = [];\n\n nodes.forEach((node) => {\n if (nodeCanHaveChildren(node)) {\n descendants.push(...node.children);\n }\n });\n\n if (descendants.length === 0) {\n return false;\n }\n\n return changesApplyToSelectedNodesOrDescendants(e, descendants);\n};\n\nconst documentChangeHandler = (e: DocumentChangeEvent) => {\n if (figma.currentPage.selection.length > 0) {\n const selection = figma.currentPage.selection;\n\n if (changesApplyToSelectedNodesOrDescendants(e, selection)) {\n selectionChangeHandler();\n }\n }\n};\n\nconst apiMethods = {\n _registerForSelectionChange(opts: FigmaSelectionHookOptions) {\n options = opts;\n\n const apiOptions = opts.apiOptions;\n if (apiOptions) {\n updateUiApiWithOptions(apiOptions);\n updateApiWithOptions(apiOptions);\n }\n\n figma.on('selectionchange', selectionChangeHandler);\n figma.on('documentchange', documentChangeHandler);\n selectionChangeHandler();\n },\n _deregisterForSelectionChange() {\n figma.off('selectionchange', selectionChangeHandler);\n figma.off('documentchange', documentChangeHandler);\n },\n _setSelection<N extends readonly BareNode[]>(newSelection: N) {\n figma.currentPage.selection = newSelection as unknown as readonly SceneNode[];\n }\n};\n\nexport let api = createPluginAPI(apiMethods);\n\nexport const updateApiWithOptions = (rpcOptions: RPCOptions) => {\n api = createPluginAPI(apiMethods, rpcOptions);\n};\n\nlet options: FigmaSelectionHookOptions;\n\nexport let listeners: FigmaSelectionListener[] = [];\n\nexport const setlisteners = (newListeners: FigmaSelectionListener[]) => {\n listeners = newListeners;\n};\n\n// In plugin UI, add a global function to receive selection change events\nif (typeof window !== 'undefined') {\n window._figma_onSelectionChange = (selection) => {\n listeners.forEach((l) => {\n l(selection);\n });\n };\n}\n", "import { ArrayType } from './typePrimitives';\n\nexport const isArray = Array.isArray as <T>(arg: T) => arg is ArrayType<T>;\n\nexport const strictObjectKeys = Object.keys as <T extends object>(obj: T) => Array<keyof T>;\n\nexport const nodeCanHaveChildren = <T extends SceneNode>(node: T): node is T & ChildrenMixin => {\n return 'children' in node;\n};\n", "/**\n * Used to replace `figma.mixed` during JSON serialization\n */\nexport const FIGMA_MIXED = 'mixed-57999e63-7384-42a1-acf8-d80b9f6c36a7';\n", "import { FIGMA_MIXED } from './constants';\nimport { nodeCanHaveChildren, strictObjectKeys } from './typeUtils';\n\nimport { ResolvedNode, ResolverOptions, SceneNodePropertyKey, SerializedResolvedNode } from './types';\n\nconst defaultNodePropertyGetterFilter = <Node extends SceneNode>(key: keyof Node, node: Node): boolean => {\n return (\n // Can only get component property definitions of a component set or non-variant component\n !(key === 'componentPropertyDefinitions' && node.parent?.type === 'COMPONENT_SET') &&\n // reading horizontalPadding and verticalPadding is no longer supported as left and right padding may differ\n key !== 'horizontalPadding' &&\n key !== 'verticalPadding'\n );\n};\n\nconst resolveNodeProperties = <Node extends SceneNode, const Keys extends readonly SceneNodePropertyKey[]>(\n node: Node,\n propertyKeys?: Keys\n): ResolvedNode<Node, Keys> => {\n // Narrow the type from the default which has a mapped type\n const descriptors = Object.getOwnPropertyDescriptors<Node>(Object.getPrototypeOf(node)) as {\n [P in keyof Node]: TypedPropertyDescriptor<Node[P]>;\n };\n\n // In reality the type is string | number | keyof T from getOwnPropertyDescriptors\n // but we can safely assert a narrower type\n const getterKeys = strictObjectKeys(descriptors);\n let getters = getterKeys.filter((key) => typeof descriptors[key].get === 'function');\n\n // type has to be included manually as it doesn't have a getter but isn't a static property either\n getters.push('type');\n\n if (propertyKeys) {\n getters = getters.filter((key) => propertyKeys.includes(key as SceneNodePropertyKey));\n }\n\n getters = getters.filter((key) => defaultNodePropertyGetterFilter(key, node));\n\n const objectWithProperties = {\n id: node.id\n } as Node;\n\n for (const getter of getters) {\n objectWithProperties[getter] = node[getter];\n }\n\n return objectWithProperties;\n};\n\nconst resolveAndSerializeNodeProperties = <Node extends SceneNode, const Options extends ResolverOptions>(\n object: Node,\n options: Options,\n ancestorsVisible: boolean\n): SerializedResolvedNode<Options> => {\n const { resolveProperties, addAncestorsVisibleProperty } = options;\n\n const resolvedNode = resolveNodeProperties(\n object,\n resolveProperties === 'all' ? undefined : resolveProperties\n ) as Record<string, unknown>;\n\n for (const key of strictObjectKeys(resolvedNode)) {\n if (resolvedNode[key] === figma.mixed) {\n resolvedNode[key] = FIGMA_MIXED;\n }\n }\n\n if (addAncestorsVisibleProperty) {\n resolvedNode.ancestorsVisible = ancestorsVisible;\n }\n\n return resolvedNode as SerializedResolvedNode<Options>;\n};\n\n// TODO: Resolve variables\nexport const resolveAndFilterNodes = <const Options extends ResolverOptions>(\n nodes: readonly SceneNode[],\n options: Options,\n ancestorsVisible: boolean = true\n): SerializedResolvedNode<Options>[] => {\n const result: SerializedResolvedNode<Options>[] = [];\n const { nodeTypes, resolveChildren } = options;\n\n if (nodeTypes !== undefined) {\n nodes.forEach((node) => {\n if (nodeTypes.includes(node.type)) {\n result.push(resolveAndSerializeNodeProperties(node, options, ancestorsVisible && node.visible));\n } else if (nodeCanHaveChildren(node) && resolveChildren) {\n result.push(...resolveAndFilterNodes(node.children, options, ancestorsVisible && node.visible));\n }\n });\n } else {\n nodes.forEach((node) => {\n if (nodeCanHaveChildren(node) && resolveChildren) {\n const newNode = {\n ...resolveAndSerializeNodeProperties(node, options, ancestorsVisible && node.visible),\n children: resolveAndFilterNodes(node.children, options, ancestorsVisible && node.visible)\n };\n result.push(newNode);\n } else {\n result.push(resolveAndSerializeNodeProperties(node, options, ancestorsVisible && node.visible));\n }\n });\n }\n\n return result;\n};\n", "import { RPCOptions } from 'figma-plugin-api';\n\nimport { FIGMA_MIXED } from './constants';\nimport { ApplicableNonFunctionPropertyKeys, ArrayElementUnion, NonFunctionPropertyKeys } from './typePrimitives';\n\n// For typedoc\nexport { RPCOptions } from 'figma-plugin-api';\nexport { FIGMA_MIXED } from './constants';\n\n/**\n * @internal\n * Describes the properties of an unresolved node\n */\nexport type BareNode = Pick<SceneNode, 'id'>;\n\ntype SceneNodeType = SceneNode['type'];\n\n/**\n * @internal\n */\nexport type SceneNodeFromTypes<T extends readonly SceneNodeType[] | undefined = undefined> = T extends undefined\n ? SceneNode\n : // @ts-expect-error If the check is flipped, the type widens to SceneNode. On simpler types this doesn't happen but it seems like Extract does this.\n ExtractedSceneNode<ArrayElementUnion<T>>;\n\n/**\n * @internal\n * Utility type to get only matching node types from the SceneNode union type.\n */\ntype ExtractedSceneNode<T extends SceneNodeType> = Extract<SceneNode, { type: T }>;\n\n/**\n * @internal\n * Get the non-function property keys for a SceneNode\n */\nexport type SceneNodePropertyKey<T extends SceneNodeType | undefined = undefined> = NonFunctionPropertyKeys<\n T extends SceneNodeType ? ExtractedSceneNode<T> : SceneNode\n>;\n\nexport type OptSceneNodeProperties = readonly SceneNodePropertyKey[] | 'all';\n\n/**\n * Use `satisfies` (for TS >= 4.9) with this type to allow for type checking the options object\n * while the type of the object remains exact.\n *\n * This allows us to infer the type of the returned nodes correctly.\n *\n * Example:\n * ```typescript\n * const options = {\n * nodeTypes: ['TEXT', 'FRAME'],\n * resolveProperties: ['name', 'characters', 'children]\n * } satisfies FigmaSelectionHookOptions;\n * ```\n */\nexport type FigmaSelectionHookOptions = {\n /**\n * Only return specific types of nodes.\n *\n * If left undefined, all nodes in the selection will be returned.\n *\n * Default: `undefined`\n */\n nodeTypes?: readonly SceneNodeType[] | undefined;\n /**\n * Figma node properties are lazy-loaded, so to use any property you have to resolve it first.\n *\n * Resolving all node properties causes a performance hit, so you can specify which properties you want to resolve.\n *\n * If set to `[]`, no properties will be resolved and you will only get the ids of the nodes.\n *\n * Node methods (such as `getPluginData`) will never be resolved.\n *\n * Default: `'all'`\n */\n resolveProperties?: OptSceneNodeProperties;\n /**\n * Resolve children nodes of the selection.\n *\n * If used with `nodeTypes`, all nodes of the specified types will be returned as a flat array.\n *\n * Default: `false`\n */\n resolveChildren?: boolean;\n /**\n * Resolve bound variables of the selection.\n *\n * Default: `false`\n */\n resolveVariables?: boolean;\n /**\n * Add `ancestorsVisible` property to all nodes.\n *\n * This property is true if all ancestors of the node are visible.\n *\n * Default: `false`\n */\n addAncestorsVisibleProperty?: boolean;\n /**\n * Options for figma-plugin-api\n *\n * Default: see the RPCOptions type\n */\n apiOptions?: RPCOptions | undefined;\n};\n\n/**\n * @internal\n */\nexport type ResolverOptions = Omit<FigmaSelectionHookOptions, 'apiOptions'>;\n\ntype SerializedNodeProperty<\n Prop,\n Node extends SceneNode,\n Options extends ResolverOptions\n> = Prop extends PluginAPI['mixed']\n ? typeof FIGMA_MIXED\n : Prop extends readonly SceneNode[]\n ? Options['resolveChildren'] extends true\n ? readonly SerializedNode<Node, Options>[]\n : readonly BareNode[]\n : Prop;\n\ntype ApplicableNodeKeys<\n Node extends SceneNode,\n Properties extends OptSceneNodeProperties\n> = Properties extends readonly SceneNodePropertyKey[]\n ? ApplicableNonFunctionPropertyKeys<Node, ArrayElementUnion<Properties>>\n : NonFunctionPropertyKeys<Node>;\n\n/**\n * @internal\n */\nexport type SerializedNode<Node extends SceneNode, Options extends ResolverOptions> = Node extends SceneNode\n ? {\n type: Node['type'];\n id: Node['id'];\n } & {\n [Key in Options['resolveProperties'] extends SceneNodePropertyKey[]\n ? ApplicableNodeKeys<Node, Options['resolveProperties']>\n : keyof Node]: SerializedNodeProperty<Node[Key], Node, Options>;\n }\n : never;\n\n/**\n * @internal\n */\nexport type ResolvedNode<\n Node extends SceneNode,\n Keys extends readonly SceneNodePropertyKey[] | undefined = undefined\n> = Pick<\n Node,\n Keys extends readonly SceneNodePropertyKey[]\n ? ApplicableNonFunctionPropertyKeys<Node, ArrayElementUnion<Keys>> | 'id' | 'type'\n : ApplicableNonFunctionPropertyKeys<Node, SceneNodePropertyKey>\n>;\n\n/**\n * @internal\n */\ntype SerializedResolvedNodeBase<Node extends SceneNode, Options extends ResolverOptions> = Node extends SceneNode\n ? SerializedNode<Node, Options>\n : never;\n\n/**\n * @internal\n */\ntype AncestorsVisibleMixin = {\n ancestorsVisible: boolean;\n};\n\n/**\n * @internal\n */\ntype ResolveVariablesMixin = {\n boundVariableInstances: readonly Variable[];\n};\n\n/**\n * @internal\n * All Figma nodes are converted to this type for serialization and sending to the plugin UI\n */\nexport type SerializedResolvedNode<Options extends ResolverOptions> =\n Options['addAncestorsVisibleProperty'] extends true\n ? Options['resolveVariables'] extends true\n ? SerializedResolvedNodeBase<SceneNodeFromTypes<Options['nodeTypes']>, Options> &\n AncestorsVisibleMixin &\n ResolveVariablesMixin\n : SerializedResolvedNodeBase<SceneNodeFromTypes<Options['nodeTypes']>, Options> & AncestorsVisibleMixin\n : SerializedResolvedNodeBase<SceneNodeFromTypes<Options['nodeTypes']>, Options>;\n\n/**\n * @internal\n */\nexport type FigmaSelectionListener = (selection: readonly SerializedResolvedNode<ResolverOptions>[]) => void;\n"], | ||
| "mappings": "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", | ||
| "names": ["useEffect", "options", "RPCOptions", "useEffect"] | ||
| "sources": ["../src/hook.ts", "../src/useMountedEffect.ts", "../src/index.ts", "../src/typeUtils.ts", "../src/constants.ts", "../src/utils.ts"], | ||
| "sourcesContent": ["/* eslint-disable react-hooks/rules-of-hooks */\n\nimport { useEffect, useState } from 'react';\n\nimport useMountedEffect from './useMountedEffect';\n\nimport { api, listeners, setlisteners, updateApiWithOptions, updateUiApiWithOptions } from '.';\n\nimport {\n BareNode,\n FigmaSelectionHookOptions,\n FigmaSelectionListener,\n ResolverOptions,\n SerializedResolvedNode\n} from './types';\n\nexport { FigmaSelectionHookOptions } from './types';\nexport { FIGMA_MIXED } from './constants';\n\nconst defaultOptions = {\n nodeTypes: undefined,\n resolveChildren: false,\n resolveVariables: false,\n resolveProperties: 'all',\n addAncestorsVisibleProperty: false\n} as const satisfies ResolverOptions;\n\n/**\n * Only one config will take presence and it will be the config of the first hook that is mounted\n */\nconst useFigmaSelection = <const Options extends FigmaSelectionHookOptions>(\n hookOptions?: Options\n): [readonly SerializedResolvedNode<Options>[], (selection: readonly BareNode[]) => void] => {\n const opts = { ...defaultOptions, ...hookOptions } as const;\n\n const [selection, setSelection] = useState<readonly SerializedResolvedNode<Options>[]>([]);\n\n useMountedEffect(() => {\n console.warn('useFigmaSelection: changing options once mounted will not affect the behavior of the hook');\n }, [hookOptions]);\n\n useEffect(() => {\n const mount = async () => {\n // Typing the listeners explicitly is difficult due to the architecture, so we have to assert\n listeners.push(setSelection as unknown as FigmaSelectionListener);\n\n // if it's the first listener, register for selection change\n if (listeners.length === 1) {\n try {\n if (opts.apiOptions) {\n updateApiWithOptions(opts.apiOptions);\n updateUiApiWithOptions(opts.apiOptions);\n }\n await api._registerForSelectionChange(opts);\n } catch (e) {\n console.error(e);\n }\n }\n };\n\n mount();\n\n return () => {\n setlisteners(listeners.filter((l) => l !== (setSelection as unknown as FigmaSelectionListener)));\n if (!listeners.length) {\n // if it was the last listener, then we don't have to listen to selection change anymore\n api._deregisterForSelectionChange();\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return [selection as readonly SerializedResolvedNode<Options>[], api._setSelection];\n};\n\nexport default useFigmaSelection;\n", "import { useEffect, useRef } from 'react';\n\nconst useMountedEffect = (effect: React.EffectCallback, deps?: React.DependencyList | undefined) => {\n const didMountRef = useRef(false);\n\n useEffect(() => {\n if (didMountRef.current) {\n return effect();\n }\n didMountRef.current = true;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, deps);\n};\n\nexport default useMountedEffect;\n", "import { createUIAPI, createPluginAPI } from 'figma-plugin-api';\n\nimport { nodeCanHaveChildren } from './typeUtils';\nimport { resolveAndFilterNodes } from './utils';\n\n// Allow esbuild to drop the import\nimport type { RPCOptions } from 'figma-plugin-api';\n\nimport {\n BareNode,\n FigmaSelectionHookOptions,\n FigmaSelectionListener,\n ResolverOptions,\n SerializedResolvedNode\n} from './types';\n\nexport { FIGMA_MIXED } from './constants';\n\ndeclare global {\n interface Window {\n _figma_onSelectionChange?: (selection: readonly SerializedResolvedNode<ResolverOptions>[]) => void;\n }\n}\n\nconst uiApiMethods = {\n _onSelectionChange(selection: readonly SerializedResolvedNode<ResolverOptions>[]) {\n if (typeof window._figma_onSelectionChange !== 'undefined') {\n window._figma_onSelectionChange(selection);\n }\n }\n};\n\nexport let uiApi = createUIAPI(uiApiMethods);\n\nexport const updateUiApiWithOptions = (rpcOptions: RPCOptions) => {\n uiApi = createUIAPI(uiApiMethods, rpcOptions);\n};\n\nconst selectionChangeHandler = () => {\n const resolvedSelection = resolveAndFilterNodes(figma.currentPage.selection, options);\n uiApi._onSelectionChange(resolvedSelection);\n};\n\nconst changesApplyToSelectedNodesOrDescendants = (e: DocumentChangeEvent, nodes: readonly SceneNode[]): boolean => {\n const changesApplyToNodes = e.documentChanges.some(\n (change) => nodes.findIndex((node) => node.id === change.id) !== -1\n );\n\n if (changesApplyToNodes) {\n return true;\n }\n\n const descendants: SceneNode[] = [];\n\n nodes.forEach((node) => {\n if (nodeCanHaveChildren(node)) {\n descendants.push(...node.children);\n }\n });\n\n if (descendants.length === 0) {\n return false;\n }\n\n return changesApplyToSelectedNodesOrDescendants(e, descendants);\n};\n\nconst documentChangeHandler = (e: DocumentChangeEvent) => {\n if (figma.currentPage.selection.length > 0) {\n const selection = figma.currentPage.selection;\n\n if (changesApplyToSelectedNodesOrDescendants(e, selection)) {\n selectionChangeHandler();\n }\n }\n};\n\nconst apiMethods = {\n _registerForSelectionChange(opts: FigmaSelectionHookOptions) {\n options = opts;\n\n const apiOptions = opts.apiOptions;\n if (apiOptions) {\n updateUiApiWithOptions(apiOptions);\n updateApiWithOptions(apiOptions);\n }\n\n figma.on('selectionchange', selectionChangeHandler);\n figma.on('documentchange', documentChangeHandler);\n selectionChangeHandler();\n },\n _deregisterForSelectionChange() {\n figma.off('selectionchange', selectionChangeHandler);\n figma.off('documentchange', documentChangeHandler);\n },\n _setSelection<N extends readonly BareNode[]>(newSelection: N) {\n figma.currentPage.selection = newSelection as unknown as readonly SceneNode[];\n }\n};\n\nexport let api = createPluginAPI(apiMethods);\n\nexport const updateApiWithOptions = (rpcOptions: RPCOptions) => {\n api = createPluginAPI(apiMethods, rpcOptions);\n};\n\nlet options: FigmaSelectionHookOptions;\n\nexport let listeners: FigmaSelectionListener[] = [];\n\nexport const setlisteners = (newListeners: FigmaSelectionListener[]) => {\n listeners = newListeners;\n};\n\n// In plugin UI, add a global function to receive selection change events\nif (typeof window !== 'undefined') {\n window._figma_onSelectionChange = (selection) => {\n listeners.forEach((l) => {\n l(selection);\n });\n };\n}\n", "import { ArrayType } from './typePrimitives';\n\nexport const isArray = Array.isArray as <T>(arg: T) => arg is ArrayType<T>;\n\nexport const strictObjectKeys = Object.keys as <T extends object>(obj: T) => Array<keyof T>;\n\nexport const nodeCanHaveChildren = <T extends SceneNode>(node: T): node is T & ChildrenMixin => {\n return 'children' in node;\n};\n", "/**\n * Used to replace `figma.mixed` during JSON serialization\n */\nexport const FIGMA_MIXED = 'mixed-57999e63-7384-42a1-acf8-d80b9f6c36a7';\n", "import { FIGMA_MIXED } from './constants';\nimport { nodeCanHaveChildren, strictObjectKeys } from './typeUtils';\n\nimport { ResolvedNode, ResolverOptions, SceneNodePropertyKey, SerializedResolvedNode } from './types';\n\nconst defaultNodePropertyGetterFilter = <Node extends SceneNode>(key: keyof Node, node: Node): boolean => {\n return (\n // Can only get component property definitions of a component set or non-variant component\n !(key === 'componentPropertyDefinitions' && node.parent?.type === 'COMPONENT_SET') &&\n // reading horizontalPadding and verticalPadding is no longer supported as left and right padding may differ\n key !== 'horizontalPadding' &&\n key !== 'verticalPadding'\n );\n};\n\nconst resolveNodeProperties = <Node extends SceneNode, const Keys extends readonly SceneNodePropertyKey[]>(\n node: Node,\n propertyKeys?: Keys\n): ResolvedNode<Node, Keys> => {\n // Narrow the type from the default which has a mapped type\n const descriptors = Object.getOwnPropertyDescriptors<Node>(Object.getPrototypeOf(node)) as {\n [P in keyof Node]: TypedPropertyDescriptor<Node[P]>;\n };\n\n // In reality the type is string | number | keyof T from getOwnPropertyDescriptors\n // but we can safely assert a narrower type\n const getterKeys = strictObjectKeys(descriptors);\n let getters = getterKeys.filter((key) => typeof descriptors[key].get === 'function');\n\n // type has to be included manually as it doesn't have a getter but isn't a static property either\n getters.push('type');\n\n if (propertyKeys) {\n getters = getters.filter((key) => propertyKeys.includes(key as SceneNodePropertyKey));\n }\n\n getters = getters.filter((key) => defaultNodePropertyGetterFilter(key, node));\n\n const objectWithProperties = {\n id: node.id\n } as Node;\n\n for (const getter of getters) {\n objectWithProperties[getter] = node[getter];\n }\n\n return objectWithProperties;\n};\n\nconst resolveAndSerializeNodeProperties = <Node extends SceneNode, const Options extends ResolverOptions>(\n object: Node,\n options: Options,\n ancestorsVisible: boolean\n): SerializedResolvedNode<Options> => {\n const { resolveProperties, addAncestorsVisibleProperty } = options;\n\n const resolvedNode = resolveNodeProperties(\n object,\n resolveProperties === 'all' ? undefined : resolveProperties\n ) as Record<string, unknown>;\n\n for (const key of strictObjectKeys(resolvedNode)) {\n if (resolvedNode[key] === figma.mixed) {\n resolvedNode[key] = FIGMA_MIXED;\n }\n }\n\n if (addAncestorsVisibleProperty) {\n resolvedNode.ancestorsVisible = ancestorsVisible;\n }\n\n return resolvedNode as SerializedResolvedNode<Options>;\n};\n\n// TODO: Resolve variables\nexport const resolveAndFilterNodes = <const Options extends ResolverOptions>(\n nodes: readonly SceneNode[],\n options: Options,\n ancestorsVisible: boolean = true\n): SerializedResolvedNode<Options>[] => {\n const result: SerializedResolvedNode<Options>[] = [];\n const { nodeTypes, resolveChildren } = options;\n\n if (nodeTypes !== undefined) {\n nodes.forEach((node) => {\n if (nodeTypes.includes(node.type)) {\n result.push(resolveAndSerializeNodeProperties(node, options, ancestorsVisible && node.visible));\n } else if (nodeCanHaveChildren(node) && resolveChildren) {\n result.push(...resolveAndFilterNodes(node.children, options, ancestorsVisible && node.visible));\n }\n });\n } else {\n nodes.forEach((node) => {\n if (nodeCanHaveChildren(node) && resolveChildren) {\n const newNode = {\n ...resolveAndSerializeNodeProperties(node, options, ancestorsVisible && node.visible),\n children: resolveAndFilterNodes(node.children, options, ancestorsVisible && node.visible)\n };\n result.push(newNode);\n } else {\n result.push(resolveAndSerializeNodeProperties(node, options, ancestorsVisible && node.visible));\n }\n });\n }\n\n return result;\n};\n"], | ||
| "mappings": "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", | ||
| "names": ["useEffect", "options", "useEffect"] | ||
| } |
| { | ||
| "version": 3, | ||
| "sources": ["../src/index.ts", "../src/typeUtils.ts", "../src/constants.ts", "../src/utils.ts"], | ||
| "sourcesContent": ["import { createUIAPI, createPluginAPI, RPCOptions } from 'figma-plugin-api';\n\nimport { nodeCanHaveChildren } from './typeUtils';\nimport { resolveAndFilterNodes } from './utils';\n\nimport {\n BareNode,\n FigmaSelectionHookOptions,\n FigmaSelectionListener,\n ResolverOptions,\n SerializedResolvedNode\n} from './types';\n\nexport { FIGMA_MIXED } from './constants';\n\ndeclare global {\n interface Window {\n _figma_onSelectionChange?: (selection: readonly SerializedResolvedNode<ResolverOptions>[]) => void;\n }\n}\n\nconst uiApiMethods = {\n _onSelectionChange(selection: readonly SerializedResolvedNode<ResolverOptions>[]) {\n if (typeof window._figma_onSelectionChange !== 'undefined') {\n window._figma_onSelectionChange(selection);\n }\n }\n};\n\nexport let uiApi = createUIAPI(uiApiMethods);\n\nexport const updateUiApiWithOptions = (rpcOptions: RPCOptions) => {\n uiApi = createUIAPI(uiApiMethods, rpcOptions);\n};\n\nconst selectionChangeHandler = () => {\n const resolvedSelection = resolveAndFilterNodes(figma.currentPage.selection, options);\n uiApi._onSelectionChange(resolvedSelection);\n};\n\nconst changesApplyToSelectedNodesOrDescendants = (e: DocumentChangeEvent, nodes: readonly SceneNode[]): boolean => {\n const changesApplyToNodes = e.documentChanges.some(\n (change) => nodes.findIndex((node) => node.id === change.id) !== -1\n );\n\n if (changesApplyToNodes) {\n return true;\n }\n\n const descendants: SceneNode[] = [];\n\n nodes.forEach((node) => {\n if (nodeCanHaveChildren(node)) {\n descendants.push(...node.children);\n }\n });\n\n if (descendants.length === 0) {\n return false;\n }\n\n return changesApplyToSelectedNodesOrDescendants(e, descendants);\n};\n\nconst documentChangeHandler = (e: DocumentChangeEvent) => {\n if (figma.currentPage.selection.length > 0) {\n const selection = figma.currentPage.selection;\n\n if (changesApplyToSelectedNodesOrDescendants(e, selection)) {\n selectionChangeHandler();\n }\n }\n};\n\nconst apiMethods = {\n _registerForSelectionChange(opts: FigmaSelectionHookOptions) {\n options = opts;\n\n const apiOptions = opts.apiOptions;\n if (apiOptions) {\n updateUiApiWithOptions(apiOptions);\n updateApiWithOptions(apiOptions);\n }\n\n figma.on('selectionchange', selectionChangeHandler);\n figma.on('documentchange', documentChangeHandler);\n selectionChangeHandler();\n },\n _deregisterForSelectionChange() {\n figma.off('selectionchange', selectionChangeHandler);\n figma.off('documentchange', documentChangeHandler);\n },\n _setSelection<N extends readonly BareNode[]>(newSelection: N) {\n figma.currentPage.selection = newSelection as unknown as readonly SceneNode[];\n }\n};\n\nexport let api = createPluginAPI(apiMethods);\n\nexport const updateApiWithOptions = (rpcOptions: RPCOptions) => {\n api = createPluginAPI(apiMethods, rpcOptions);\n};\n\nlet options: FigmaSelectionHookOptions;\n\nexport let listeners: FigmaSelectionListener[] = [];\n\nexport const setlisteners = (newListeners: FigmaSelectionListener[]) => {\n listeners = newListeners;\n};\n\n// In plugin UI, add a global function to receive selection change events\nif (typeof window !== 'undefined') {\n window._figma_onSelectionChange = (selection) => {\n listeners.forEach((l) => {\n l(selection);\n });\n };\n}\n", "import { ArrayType } from './typePrimitives';\n\nexport const isArray = Array.isArray as <T>(arg: T) => arg is ArrayType<T>;\n\nexport const strictObjectKeys = Object.keys as <T extends object>(obj: T) => Array<keyof T>;\n\nexport const nodeCanHaveChildren = <T extends SceneNode>(node: T): node is T & ChildrenMixin => {\n return 'children' in node;\n};\n", "/**\n * Used to replace `figma.mixed` during JSON serialization\n */\nexport const FIGMA_MIXED = 'mixed-57999e63-7384-42a1-acf8-d80b9f6c36a7';\n", "import { FIGMA_MIXED } from './constants';\nimport { nodeCanHaveChildren, strictObjectKeys } from './typeUtils';\n\nimport { ResolvedNode, ResolverOptions, SceneNodePropertyKey, SerializedResolvedNode } from './types';\n\nconst defaultNodePropertyGetterFilter = <Node extends SceneNode>(key: keyof Node, node: Node): boolean => {\n return (\n // Can only get component property definitions of a component set or non-variant component\n !(key === 'componentPropertyDefinitions' && node.parent?.type === 'COMPONENT_SET') &&\n // reading horizontalPadding and verticalPadding is no longer supported as left and right padding may differ\n key !== 'horizontalPadding' &&\n key !== 'verticalPadding'\n );\n};\n\nconst resolveNodeProperties = <Node extends SceneNode, const Keys extends readonly SceneNodePropertyKey[]>(\n node: Node,\n propertyKeys?: Keys\n): ResolvedNode<Node, Keys> => {\n // Narrow the type from the default which has a mapped type\n const descriptors = Object.getOwnPropertyDescriptors<Node>(Object.getPrototypeOf(node)) as {\n [P in keyof Node]: TypedPropertyDescriptor<Node[P]>;\n };\n\n // In reality the type is string | number | keyof T from getOwnPropertyDescriptors\n // but we can safely assert a narrower type\n const getterKeys = strictObjectKeys(descriptors);\n let getters = getterKeys.filter((key) => typeof descriptors[key].get === 'function');\n\n // type has to be included manually as it doesn't have a getter but isn't a static property either\n getters.push('type');\n\n if (propertyKeys) {\n getters = getters.filter((key) => propertyKeys.includes(key as SceneNodePropertyKey));\n }\n\n getters = getters.filter((key) => defaultNodePropertyGetterFilter(key, node));\n\n const objectWithProperties = {\n id: node.id\n } as Node;\n\n for (const getter of getters) {\n objectWithProperties[getter] = node[getter];\n }\n\n return objectWithProperties;\n};\n\nconst resolveAndSerializeNodeProperties = <Node extends SceneNode, const Options extends ResolverOptions>(\n object: Node,\n options: Options,\n ancestorsVisible: boolean\n): SerializedResolvedNode<Options> => {\n const { resolveProperties, addAncestorsVisibleProperty } = options;\n\n const resolvedNode = resolveNodeProperties(\n object,\n resolveProperties === 'all' ? undefined : resolveProperties\n ) as Record<string, unknown>;\n\n for (const key of strictObjectKeys(resolvedNode)) {\n if (resolvedNode[key] === figma.mixed) {\n resolvedNode[key] = FIGMA_MIXED;\n }\n }\n\n if (addAncestorsVisibleProperty) {\n resolvedNode.ancestorsVisible = ancestorsVisible;\n }\n\n return resolvedNode as SerializedResolvedNode<Options>;\n};\n\n// TODO: Resolve variables\nexport const resolveAndFilterNodes = <const Options extends ResolverOptions>(\n nodes: readonly SceneNode[],\n options: Options,\n ancestorsVisible: boolean = true\n): SerializedResolvedNode<Options>[] => {\n const result: SerializedResolvedNode<Options>[] = [];\n const { nodeTypes, resolveChildren } = options;\n\n if (nodeTypes !== undefined) {\n nodes.forEach((node) => {\n if (nodeTypes.includes(node.type)) {\n result.push(resolveAndSerializeNodeProperties(node, options, ancestorsVisible && node.visible));\n } else if (nodeCanHaveChildren(node) && resolveChildren) {\n result.push(...resolveAndFilterNodes(node.children, options, ancestorsVisible && node.visible));\n }\n });\n } else {\n nodes.forEach((node) => {\n if (nodeCanHaveChildren(node) && resolveChildren) {\n const newNode = {\n ...resolveAndSerializeNodeProperties(node, options, ancestorsVisible && node.visible),\n children: resolveAndFilterNodes(node.children, options, ancestorsVisible && node.visible)\n };\n result.push(newNode);\n } else {\n result.push(resolveAndSerializeNodeProperties(node, options, ancestorsVisible && node.visible));\n }\n });\n }\n\n return result;\n};\n"], | ||
| "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,8BAAyD;;;ACElD,IAAM,UAAU,MAAM;AAEtB,IAAM,mBAAmB,OAAO;AAEhC,IAAM,sBAAsB,CAAsB,SAAuC;AAC9F,SAAO,cAAc;AACvB;;;ACLO,IAAM,cAAc;;;ACE3B,IAAM,kCAAkC,CAAyB,KAAiB,SAAwB;AAL1G;AAME;AAAA;AAAA,IAEE,EAAE,QAAQ,oCAAkC,UAAK,WAAL,mBAAa,UAAS;AAAA,IAElE,QAAQ,uBACR,QAAQ;AAAA;AAEZ;AAEA,IAAM,wBAAwB,CAC5B,MACA,iBAC6B;AAE7B,QAAM,cAAc,OAAO,0BAAgC,OAAO,eAAe,IAAI,CAAC;AAMtF,QAAM,aAAa,iBAAiB,WAAW;AAC/C,MAAI,UAAU,WAAW,OAAO,CAAC,QAAQ,OAAO,YAAY,GAAG,EAAE,QAAQ,UAAU;AAGnF,UAAQ,KAAK,MAAM;AAEnB,MAAI,cAAc;AAChB,cAAU,QAAQ,OAAO,CAAC,QAAQ,aAAa,SAAS,GAA2B,CAAC;AAAA,EACtF;AAEA,YAAU,QAAQ,OAAO,CAAC,QAAQ,gCAAgC,KAAK,IAAI,CAAC;AAE5E,QAAM,uBAAuB;AAAA,IAC3B,IAAI,KAAK;AAAA,EACX;AAEA,aAAW,UAAU,SAAS;AAC5B,yBAAqB,MAAM,IAAI,KAAK,MAAM;AAAA,EAC5C;AAEA,SAAO;AACT;AAEA,IAAM,oCAAoC,CACxC,QACAA,UACA,qBACoC;AACpC,QAAM,EAAE,mBAAmB,4BAA4B,IAAIA;AAE3D,QAAM,eAAe;AAAA,IACnB;AAAA,IACA,sBAAsB,QAAQ,SAAY;AAAA,EAC5C;AAEA,aAAW,OAAO,iBAAiB,YAAY,GAAG;AAChD,QAAI,aAAa,GAAG,MAAM,MAAM,OAAO;AACrC,mBAAa,GAAG,IAAI;AAAA,IACtB;AAAA,EACF;AAEA,MAAI,6BAA6B;AAC/B,iBAAa,mBAAmB;AAAA,EAClC;AAEA,SAAO;AACT;AAGO,IAAM,wBAAwB,CACnC,OACAA,UACA,mBAA4B,SACU;AACtC,QAAM,SAA4C,CAAC;AACnD,QAAM,EAAE,WAAW,gBAAgB,IAAIA;AAEvC,MAAI,cAAc,QAAW;AAC3B,UAAM,QAAQ,CAAC,SAAS;AACtB,UAAI,UAAU,SAAS,KAAK,IAAI,GAAG;AACjC,eAAO,KAAK,kCAAkC,MAAMA,UAAS,oBAAoB,KAAK,OAAO,CAAC;AAAA,MAChG,WAAW,oBAAoB,IAAI,KAAK,iBAAiB;AACvD,eAAO,KAAK,GAAG,sBAAsB,KAAK,UAAUA,UAAS,oBAAoB,KAAK,OAAO,CAAC;AAAA,MAChG;AAAA,IACF,CAAC;AAAA,EACH,OAAO;AACL,UAAM,QAAQ,CAAC,SAAS;AACtB,UAAI,oBAAoB,IAAI,KAAK,iBAAiB;AAChD,cAAM,UAAU,iCACX,kCAAkC,MAAMA,UAAS,oBAAoB,KAAK,OAAO,IADtE;AAAA,UAEd,UAAU,sBAAsB,KAAK,UAAUA,UAAS,oBAAoB,KAAK,OAAO;AAAA,QAC1F;AACA,eAAO,KAAK,OAAO;AAAA,MACrB,OAAO;AACL,eAAO,KAAK,kCAAkC,MAAMA,UAAS,oBAAoB,KAAK,OAAO,CAAC;AAAA,MAChG;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO;AACT;;;AHrFA,IAAM,eAAe;AAAA,EACnB,mBAAmB,WAA+D;AAChF,QAAI,OAAO,OAAO,6BAA6B,aAAa;AAC1D,aAAO,yBAAyB,SAAS;AAAA,IAC3C;AAAA,EACF;AACF;AAEO,IAAI,YAAQ,qCAAY,YAAY;AAEpC,IAAM,yBAAyB,CAAC,eAA2B;AAChE,cAAQ,qCAAY,cAAc,UAAU;AAC9C;AAEA,IAAM,yBAAyB,MAAM;AACnC,QAAM,oBAAoB,sBAAsB,MAAM,YAAY,WAAW,OAAO;AACpF,QAAM,mBAAmB,iBAAiB;AAC5C;AAEA,IAAM,2CAA2C,CAAC,GAAwB,UAAyC;AACjH,QAAM,sBAAsB,EAAE,gBAAgB;AAAA,IAC5C,CAAC,WAAW,MAAM,UAAU,CAAC,SAAS,KAAK,OAAO,OAAO,EAAE,MAAM;AAAA,EACnE;AAEA,MAAI,qBAAqB;AACvB,WAAO;AAAA,EACT;AAEA,QAAM,cAA2B,CAAC;AAElC,QAAM,QAAQ,CAAC,SAAS;AACtB,QAAI,oBAAoB,IAAI,GAAG;AAC7B,kBAAY,KAAK,GAAG,KAAK,QAAQ;AAAA,IACnC;AAAA,EACF,CAAC;AAED,MAAI,YAAY,WAAW,GAAG;AAC5B,WAAO;AAAA,EACT;AAEA,SAAO,yCAAyC,GAAG,WAAW;AAChE;AAEA,IAAM,wBAAwB,CAAC,MAA2B;AACxD,MAAI,MAAM,YAAY,UAAU,SAAS,GAAG;AAC1C,UAAM,YAAY,MAAM,YAAY;AAEpC,QAAI,yCAAyC,GAAG,SAAS,GAAG;AAC1D,6BAAuB;AAAA,IACzB;AAAA,EACF;AACF;AAEA,IAAM,aAAa;AAAA,EACjB,4BAA4B,MAAiC;AAC3D,cAAU;AAEV,UAAM,aAAa,KAAK;AACxB,QAAI,YAAY;AACd,6BAAuB,UAAU;AACjC,2BAAqB,UAAU;AAAA,IACjC;AAEA,UAAM,GAAG,mBAAmB,sBAAsB;AAClD,UAAM,GAAG,kBAAkB,qBAAqB;AAChD,2BAAuB;AAAA,EACzB;AAAA,EACA,gCAAgC;AAC9B,UAAM,IAAI,mBAAmB,sBAAsB;AACnD,UAAM,IAAI,kBAAkB,qBAAqB;AAAA,EACnD;AAAA,EACA,cAA6C,cAAiB;AAC5D,UAAM,YAAY,YAAY;AAAA,EAChC;AACF;AAEO,IAAI,UAAM,yCAAgB,UAAU;AAEpC,IAAM,uBAAuB,CAAC,eAA2B;AAC9D,YAAM,yCAAgB,YAAY,UAAU;AAC9C;AAEA,IAAI;AAEG,IAAI,YAAsC,CAAC;AAE3C,IAAM,eAAe,CAAC,iBAA2C;AACtE,cAAY;AACd;AAGA,IAAI,OAAO,WAAW,aAAa;AACjC,SAAO,2BAA2B,CAAC,cAAc;AAC/C,cAAU,QAAQ,CAAC,MAAM;AACvB,QAAE,SAAS;AAAA,IACb,CAAC;AAAA,EACH;AACF;", | ||
| "sourcesContent": ["import { createUIAPI, createPluginAPI } from 'figma-plugin-api';\n\nimport { nodeCanHaveChildren } from './typeUtils';\nimport { resolveAndFilterNodes } from './utils';\n\n// Allow esbuild to drop the import\nimport type { RPCOptions } from 'figma-plugin-api';\n\nimport {\n BareNode,\n FigmaSelectionHookOptions,\n FigmaSelectionListener,\n ResolverOptions,\n SerializedResolvedNode\n} from './types';\n\nexport { FIGMA_MIXED } from './constants';\n\ndeclare global {\n interface Window {\n _figma_onSelectionChange?: (selection: readonly SerializedResolvedNode<ResolverOptions>[]) => void;\n }\n}\n\nconst uiApiMethods = {\n _onSelectionChange(selection: readonly SerializedResolvedNode<ResolverOptions>[]) {\n if (typeof window._figma_onSelectionChange !== 'undefined') {\n window._figma_onSelectionChange(selection);\n }\n }\n};\n\nexport let uiApi = createUIAPI(uiApiMethods);\n\nexport const updateUiApiWithOptions = (rpcOptions: RPCOptions) => {\n uiApi = createUIAPI(uiApiMethods, rpcOptions);\n};\n\nconst selectionChangeHandler = () => {\n const resolvedSelection = resolveAndFilterNodes(figma.currentPage.selection, options);\n uiApi._onSelectionChange(resolvedSelection);\n};\n\nconst changesApplyToSelectedNodesOrDescendants = (e: DocumentChangeEvent, nodes: readonly SceneNode[]): boolean => {\n const changesApplyToNodes = e.documentChanges.some(\n (change) => nodes.findIndex((node) => node.id === change.id) !== -1\n );\n\n if (changesApplyToNodes) {\n return true;\n }\n\n const descendants: SceneNode[] = [];\n\n nodes.forEach((node) => {\n if (nodeCanHaveChildren(node)) {\n descendants.push(...node.children);\n }\n });\n\n if (descendants.length === 0) {\n return false;\n }\n\n return changesApplyToSelectedNodesOrDescendants(e, descendants);\n};\n\nconst documentChangeHandler = (e: DocumentChangeEvent) => {\n if (figma.currentPage.selection.length > 0) {\n const selection = figma.currentPage.selection;\n\n if (changesApplyToSelectedNodesOrDescendants(e, selection)) {\n selectionChangeHandler();\n }\n }\n};\n\nconst apiMethods = {\n _registerForSelectionChange(opts: FigmaSelectionHookOptions) {\n options = opts;\n\n const apiOptions = opts.apiOptions;\n if (apiOptions) {\n updateUiApiWithOptions(apiOptions);\n updateApiWithOptions(apiOptions);\n }\n\n figma.on('selectionchange', selectionChangeHandler);\n figma.on('documentchange', documentChangeHandler);\n selectionChangeHandler();\n },\n _deregisterForSelectionChange() {\n figma.off('selectionchange', selectionChangeHandler);\n figma.off('documentchange', documentChangeHandler);\n },\n _setSelection<N extends readonly BareNode[]>(newSelection: N) {\n figma.currentPage.selection = newSelection as unknown as readonly SceneNode[];\n }\n};\n\nexport let api = createPluginAPI(apiMethods);\n\nexport const updateApiWithOptions = (rpcOptions: RPCOptions) => {\n api = createPluginAPI(apiMethods, rpcOptions);\n};\n\nlet options: FigmaSelectionHookOptions;\n\nexport let listeners: FigmaSelectionListener[] = [];\n\nexport const setlisteners = (newListeners: FigmaSelectionListener[]) => {\n listeners = newListeners;\n};\n\n// In plugin UI, add a global function to receive selection change events\nif (typeof window !== 'undefined') {\n window._figma_onSelectionChange = (selection) => {\n listeners.forEach((l) => {\n l(selection);\n });\n };\n}\n", "import { ArrayType } from './typePrimitives';\n\nexport const isArray = Array.isArray as <T>(arg: T) => arg is ArrayType<T>;\n\nexport const strictObjectKeys = Object.keys as <T extends object>(obj: T) => Array<keyof T>;\n\nexport const nodeCanHaveChildren = <T extends SceneNode>(node: T): node is T & ChildrenMixin => {\n return 'children' in node;\n};\n", "/**\n * Used to replace `figma.mixed` during JSON serialization\n */\nexport const FIGMA_MIXED = 'mixed-57999e63-7384-42a1-acf8-d80b9f6c36a7';\n", "import { FIGMA_MIXED } from './constants';\nimport { nodeCanHaveChildren, strictObjectKeys } from './typeUtils';\n\nimport { ResolvedNode, ResolverOptions, SceneNodePropertyKey, SerializedResolvedNode } from './types';\n\nconst defaultNodePropertyGetterFilter = <Node extends SceneNode>(key: keyof Node, node: Node): boolean => {\n return (\n // Can only get component property definitions of a component set or non-variant component\n !(key === 'componentPropertyDefinitions' && node.parent?.type === 'COMPONENT_SET') &&\n // reading horizontalPadding and verticalPadding is no longer supported as left and right padding may differ\n key !== 'horizontalPadding' &&\n key !== 'verticalPadding'\n );\n};\n\nconst resolveNodeProperties = <Node extends SceneNode, const Keys extends readonly SceneNodePropertyKey[]>(\n node: Node,\n propertyKeys?: Keys\n): ResolvedNode<Node, Keys> => {\n // Narrow the type from the default which has a mapped type\n const descriptors = Object.getOwnPropertyDescriptors<Node>(Object.getPrototypeOf(node)) as {\n [P in keyof Node]: TypedPropertyDescriptor<Node[P]>;\n };\n\n // In reality the type is string | number | keyof T from getOwnPropertyDescriptors\n // but we can safely assert a narrower type\n const getterKeys = strictObjectKeys(descriptors);\n let getters = getterKeys.filter((key) => typeof descriptors[key].get === 'function');\n\n // type has to be included manually as it doesn't have a getter but isn't a static property either\n getters.push('type');\n\n if (propertyKeys) {\n getters = getters.filter((key) => propertyKeys.includes(key as SceneNodePropertyKey));\n }\n\n getters = getters.filter((key) => defaultNodePropertyGetterFilter(key, node));\n\n const objectWithProperties = {\n id: node.id\n } as Node;\n\n for (const getter of getters) {\n objectWithProperties[getter] = node[getter];\n }\n\n return objectWithProperties;\n};\n\nconst resolveAndSerializeNodeProperties = <Node extends SceneNode, const Options extends ResolverOptions>(\n object: Node,\n options: Options,\n ancestorsVisible: boolean\n): SerializedResolvedNode<Options> => {\n const { resolveProperties, addAncestorsVisibleProperty } = options;\n\n const resolvedNode = resolveNodeProperties(\n object,\n resolveProperties === 'all' ? undefined : resolveProperties\n ) as Record<string, unknown>;\n\n for (const key of strictObjectKeys(resolvedNode)) {\n if (resolvedNode[key] === figma.mixed) {\n resolvedNode[key] = FIGMA_MIXED;\n }\n }\n\n if (addAncestorsVisibleProperty) {\n resolvedNode.ancestorsVisible = ancestorsVisible;\n }\n\n return resolvedNode as SerializedResolvedNode<Options>;\n};\n\n// TODO: Resolve variables\nexport const resolveAndFilterNodes = <const Options extends ResolverOptions>(\n nodes: readonly SceneNode[],\n options: Options,\n ancestorsVisible: boolean = true\n): SerializedResolvedNode<Options>[] => {\n const result: SerializedResolvedNode<Options>[] = [];\n const { nodeTypes, resolveChildren } = options;\n\n if (nodeTypes !== undefined) {\n nodes.forEach((node) => {\n if (nodeTypes.includes(node.type)) {\n result.push(resolveAndSerializeNodeProperties(node, options, ancestorsVisible && node.visible));\n } else if (nodeCanHaveChildren(node) && resolveChildren) {\n result.push(...resolveAndFilterNodes(node.children, options, ancestorsVisible && node.visible));\n }\n });\n } else {\n nodes.forEach((node) => {\n if (nodeCanHaveChildren(node) && resolveChildren) {\n const newNode = {\n ...resolveAndSerializeNodeProperties(node, options, ancestorsVisible && node.visible),\n children: resolveAndFilterNodes(node.children, options, ancestorsVisible && node.visible)\n };\n result.push(newNode);\n } else {\n result.push(resolveAndSerializeNodeProperties(node, options, ancestorsVisible && node.visible));\n }\n });\n }\n\n return result;\n};\n"], | ||
| "mappings": "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", | ||
| "names": ["options"] | ||
| } |
+1
-1
@@ -1,2 +0,2 @@ | ||
| import { RPCOptions } from 'figma-plugin-api'; | ||
| import type { RPCOptions } from 'figma-plugin-api'; | ||
| import { BareNode, FigmaSelectionHookOptions, FigmaSelectionListener, ResolverOptions, SerializedResolvedNode } from './types'; | ||
@@ -3,0 +3,0 @@ export { FIGMA_MIXED } from './constants'; |
| { | ||
| "version": 3, | ||
| "sources": ["../src/index.ts", "../src/typeUtils.ts", "../src/constants.ts", "../src/utils.ts"], | ||
| "sourcesContent": ["import { createUIAPI, createPluginAPI, RPCOptions } from 'figma-plugin-api';\n\nimport { nodeCanHaveChildren } from './typeUtils';\nimport { resolveAndFilterNodes } from './utils';\n\nimport {\n BareNode,\n FigmaSelectionHookOptions,\n FigmaSelectionListener,\n ResolverOptions,\n SerializedResolvedNode\n} from './types';\n\nexport { FIGMA_MIXED } from './constants';\n\ndeclare global {\n interface Window {\n _figma_onSelectionChange?: (selection: readonly SerializedResolvedNode<ResolverOptions>[]) => void;\n }\n}\n\nconst uiApiMethods = {\n _onSelectionChange(selection: readonly SerializedResolvedNode<ResolverOptions>[]) {\n if (typeof window._figma_onSelectionChange !== 'undefined') {\n window._figma_onSelectionChange(selection);\n }\n }\n};\n\nexport let uiApi = createUIAPI(uiApiMethods);\n\nexport const updateUiApiWithOptions = (rpcOptions: RPCOptions) => {\n uiApi = createUIAPI(uiApiMethods, rpcOptions);\n};\n\nconst selectionChangeHandler = () => {\n const resolvedSelection = resolveAndFilterNodes(figma.currentPage.selection, options);\n uiApi._onSelectionChange(resolvedSelection);\n};\n\nconst changesApplyToSelectedNodesOrDescendants = (e: DocumentChangeEvent, nodes: readonly SceneNode[]): boolean => {\n const changesApplyToNodes = e.documentChanges.some(\n (change) => nodes.findIndex((node) => node.id === change.id) !== -1\n );\n\n if (changesApplyToNodes) {\n return true;\n }\n\n const descendants: SceneNode[] = [];\n\n nodes.forEach((node) => {\n if (nodeCanHaveChildren(node)) {\n descendants.push(...node.children);\n }\n });\n\n if (descendants.length === 0) {\n return false;\n }\n\n return changesApplyToSelectedNodesOrDescendants(e, descendants);\n};\n\nconst documentChangeHandler = (e: DocumentChangeEvent) => {\n if (figma.currentPage.selection.length > 0) {\n const selection = figma.currentPage.selection;\n\n if (changesApplyToSelectedNodesOrDescendants(e, selection)) {\n selectionChangeHandler();\n }\n }\n};\n\nconst apiMethods = {\n _registerForSelectionChange(opts: FigmaSelectionHookOptions) {\n options = opts;\n\n const apiOptions = opts.apiOptions;\n if (apiOptions) {\n updateUiApiWithOptions(apiOptions);\n updateApiWithOptions(apiOptions);\n }\n\n figma.on('selectionchange', selectionChangeHandler);\n figma.on('documentchange', documentChangeHandler);\n selectionChangeHandler();\n },\n _deregisterForSelectionChange() {\n figma.off('selectionchange', selectionChangeHandler);\n figma.off('documentchange', documentChangeHandler);\n },\n _setSelection<N extends readonly BareNode[]>(newSelection: N) {\n figma.currentPage.selection = newSelection as unknown as readonly SceneNode[];\n }\n};\n\nexport let api = createPluginAPI(apiMethods);\n\nexport const updateApiWithOptions = (rpcOptions: RPCOptions) => {\n api = createPluginAPI(apiMethods, rpcOptions);\n};\n\nlet options: FigmaSelectionHookOptions;\n\nexport let listeners: FigmaSelectionListener[] = [];\n\nexport const setlisteners = (newListeners: FigmaSelectionListener[]) => {\n listeners = newListeners;\n};\n\n// In plugin UI, add a global function to receive selection change events\nif (typeof window !== 'undefined') {\n window._figma_onSelectionChange = (selection) => {\n listeners.forEach((l) => {\n l(selection);\n });\n };\n}\n", "import { ArrayType } from './typePrimitives';\n\nexport const isArray = Array.isArray as <T>(arg: T) => arg is ArrayType<T>;\n\nexport const strictObjectKeys = Object.keys as <T extends object>(obj: T) => Array<keyof T>;\n\nexport const nodeCanHaveChildren = <T extends SceneNode>(node: T): node is T & ChildrenMixin => {\n return 'children' in node;\n};\n", "/**\n * Used to replace `figma.mixed` during JSON serialization\n */\nexport const FIGMA_MIXED = 'mixed-57999e63-7384-42a1-acf8-d80b9f6c36a7';\n", "import { FIGMA_MIXED } from './constants';\nimport { nodeCanHaveChildren, strictObjectKeys } from './typeUtils';\n\nimport { ResolvedNode, ResolverOptions, SceneNodePropertyKey, SerializedResolvedNode } from './types';\n\nconst defaultNodePropertyGetterFilter = <Node extends SceneNode>(key: keyof Node, node: Node): boolean => {\n return (\n // Can only get component property definitions of a component set or non-variant component\n !(key === 'componentPropertyDefinitions' && node.parent?.type === 'COMPONENT_SET') &&\n // reading horizontalPadding and verticalPadding is no longer supported as left and right padding may differ\n key !== 'horizontalPadding' &&\n key !== 'verticalPadding'\n );\n};\n\nconst resolveNodeProperties = <Node extends SceneNode, const Keys extends readonly SceneNodePropertyKey[]>(\n node: Node,\n propertyKeys?: Keys\n): ResolvedNode<Node, Keys> => {\n // Narrow the type from the default which has a mapped type\n const descriptors = Object.getOwnPropertyDescriptors<Node>(Object.getPrototypeOf(node)) as {\n [P in keyof Node]: TypedPropertyDescriptor<Node[P]>;\n };\n\n // In reality the type is string | number | keyof T from getOwnPropertyDescriptors\n // but we can safely assert a narrower type\n const getterKeys = strictObjectKeys(descriptors);\n let getters = getterKeys.filter((key) => typeof descriptors[key].get === 'function');\n\n // type has to be included manually as it doesn't have a getter but isn't a static property either\n getters.push('type');\n\n if (propertyKeys) {\n getters = getters.filter((key) => propertyKeys.includes(key as SceneNodePropertyKey));\n }\n\n getters = getters.filter((key) => defaultNodePropertyGetterFilter(key, node));\n\n const objectWithProperties = {\n id: node.id\n } as Node;\n\n for (const getter of getters) {\n objectWithProperties[getter] = node[getter];\n }\n\n return objectWithProperties;\n};\n\nconst resolveAndSerializeNodeProperties = <Node extends SceneNode, const Options extends ResolverOptions>(\n object: Node,\n options: Options,\n ancestorsVisible: boolean\n): SerializedResolvedNode<Options> => {\n const { resolveProperties, addAncestorsVisibleProperty } = options;\n\n const resolvedNode = resolveNodeProperties(\n object,\n resolveProperties === 'all' ? undefined : resolveProperties\n ) as Record<string, unknown>;\n\n for (const key of strictObjectKeys(resolvedNode)) {\n if (resolvedNode[key] === figma.mixed) {\n resolvedNode[key] = FIGMA_MIXED;\n }\n }\n\n if (addAncestorsVisibleProperty) {\n resolvedNode.ancestorsVisible = ancestorsVisible;\n }\n\n return resolvedNode as SerializedResolvedNode<Options>;\n};\n\n// TODO: Resolve variables\nexport const resolveAndFilterNodes = <const Options extends ResolverOptions>(\n nodes: readonly SceneNode[],\n options: Options,\n ancestorsVisible: boolean = true\n): SerializedResolvedNode<Options>[] => {\n const result: SerializedResolvedNode<Options>[] = [];\n const { nodeTypes, resolveChildren } = options;\n\n if (nodeTypes !== undefined) {\n nodes.forEach((node) => {\n if (nodeTypes.includes(node.type)) {\n result.push(resolveAndSerializeNodeProperties(node, options, ancestorsVisible && node.visible));\n } else if (nodeCanHaveChildren(node) && resolveChildren) {\n result.push(...resolveAndFilterNodes(node.children, options, ancestorsVisible && node.visible));\n }\n });\n } else {\n nodes.forEach((node) => {\n if (nodeCanHaveChildren(node) && resolveChildren) {\n const newNode = {\n ...resolveAndSerializeNodeProperties(node, options, ancestorsVisible && node.visible),\n children: resolveAndFilterNodes(node.children, options, ancestorsVisible && node.visible)\n };\n result.push(newNode);\n } else {\n result.push(resolveAndSerializeNodeProperties(node, options, ancestorsVisible && node.visible));\n }\n });\n }\n\n return result;\n};\n"], | ||
| "mappings": ";;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,aAAa,uBAAmC;;;ACElD,IAAM,UAAU,MAAM;AAEtB,IAAM,mBAAmB,OAAO;AAEhC,IAAM,sBAAsB,CAAsB,SAAuC;AAC9F,SAAO,cAAc;AACvB;;;ACLO,IAAM,cAAc;;;ACE3B,IAAM,kCAAkC,CAAyB,KAAiB,SAAwB;AAL1G;AAME;AAAA;AAAA,IAEE,EAAE,QAAQ,oCAAkC,UAAK,WAAL,mBAAa,UAAS;AAAA,IAElE,QAAQ,uBACR,QAAQ;AAAA;AAEZ;AAEA,IAAM,wBAAwB,CAC5B,MACA,iBAC6B;AAE7B,QAAM,cAAc,OAAO,0BAAgC,OAAO,eAAe,IAAI,CAAC;AAMtF,QAAM,aAAa,iBAAiB,WAAW;AAC/C,MAAI,UAAU,WAAW,OAAO,CAAC,QAAQ,OAAO,YAAY,GAAG,EAAE,QAAQ,UAAU;AAGnF,UAAQ,KAAK,MAAM;AAEnB,MAAI,cAAc;AAChB,cAAU,QAAQ,OAAO,CAAC,QAAQ,aAAa,SAAS,GAA2B,CAAC;AAAA,EACtF;AAEA,YAAU,QAAQ,OAAO,CAAC,QAAQ,gCAAgC,KAAK,IAAI,CAAC;AAE5E,QAAM,uBAAuB;AAAA,IAC3B,IAAI,KAAK;AAAA,EACX;AAEA,aAAW,UAAU,SAAS;AAC5B,yBAAqB,MAAM,IAAI,KAAK,MAAM;AAAA,EAC5C;AAEA,SAAO;AACT;AAEA,IAAM,oCAAoC,CACxC,QACAA,UACA,qBACoC;AACpC,QAAM,EAAE,mBAAmB,4BAA4B,IAAIA;AAE3D,QAAM,eAAe;AAAA,IACnB;AAAA,IACA,sBAAsB,QAAQ,SAAY;AAAA,EAC5C;AAEA,aAAW,OAAO,iBAAiB,YAAY,GAAG;AAChD,QAAI,aAAa,GAAG,MAAM,MAAM,OAAO;AACrC,mBAAa,GAAG,IAAI;AAAA,IACtB;AAAA,EACF;AAEA,MAAI,6BAA6B;AAC/B,iBAAa,mBAAmB;AAAA,EAClC;AAEA,SAAO;AACT;AAGO,IAAM,wBAAwB,CACnC,OACAA,UACA,mBAA4B,SACU;AACtC,QAAM,SAA4C,CAAC;AACnD,QAAM,EAAE,WAAW,gBAAgB,IAAIA;AAEvC,MAAI,cAAc,QAAW;AAC3B,UAAM,QAAQ,CAAC,SAAS;AACtB,UAAI,UAAU,SAAS,KAAK,IAAI,GAAG;AACjC,eAAO,KAAK,kCAAkC,MAAMA,UAAS,oBAAoB,KAAK,OAAO,CAAC;AAAA,MAChG,WAAW,oBAAoB,IAAI,KAAK,iBAAiB;AACvD,eAAO,KAAK,GAAG,sBAAsB,KAAK,UAAUA,UAAS,oBAAoB,KAAK,OAAO,CAAC;AAAA,MAChG;AAAA,IACF,CAAC;AAAA,EACH,OAAO;AACL,UAAM,QAAQ,CAAC,SAAS;AACtB,UAAI,oBAAoB,IAAI,KAAK,iBAAiB;AAChD,cAAM,UAAU,iCACX,kCAAkC,MAAMA,UAAS,oBAAoB,KAAK,OAAO,IADtE;AAAA,UAEd,UAAU,sBAAsB,KAAK,UAAUA,UAAS,oBAAoB,KAAK,OAAO;AAAA,QAC1F;AACA,eAAO,KAAK,OAAO;AAAA,MACrB,OAAO;AACL,eAAO,KAAK,kCAAkC,MAAMA,UAAS,oBAAoB,KAAK,OAAO,CAAC;AAAA,MAChG;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO;AACT;;;AHrFA,IAAM,eAAe;AAAA,EACnB,mBAAmB,WAA+D;AAChF,QAAI,OAAO,OAAO,6BAA6B,aAAa;AAC1D,aAAO,yBAAyB,SAAS;AAAA,IAC3C;AAAA,EACF;AACF;AAEO,IAAI,QAAQ,YAAY,YAAY;AAEpC,IAAM,yBAAyB,CAAC,eAA2B;AAChE,UAAQ,YAAY,cAAc,UAAU;AAC9C;AAEA,IAAM,yBAAyB,MAAM;AACnC,QAAM,oBAAoB,sBAAsB,MAAM,YAAY,WAAW,OAAO;AACpF,QAAM,mBAAmB,iBAAiB;AAC5C;AAEA,IAAM,2CAA2C,CAAC,GAAwB,UAAyC;AACjH,QAAM,sBAAsB,EAAE,gBAAgB;AAAA,IAC5C,CAAC,WAAW,MAAM,UAAU,CAAC,SAAS,KAAK,OAAO,OAAO,EAAE,MAAM;AAAA,EACnE;AAEA,MAAI,qBAAqB;AACvB,WAAO;AAAA,EACT;AAEA,QAAM,cAA2B,CAAC;AAElC,QAAM,QAAQ,CAAC,SAAS;AACtB,QAAI,oBAAoB,IAAI,GAAG;AAC7B,kBAAY,KAAK,GAAG,KAAK,QAAQ;AAAA,IACnC;AAAA,EACF,CAAC;AAED,MAAI,YAAY,WAAW,GAAG;AAC5B,WAAO;AAAA,EACT;AAEA,SAAO,yCAAyC,GAAG,WAAW;AAChE;AAEA,IAAM,wBAAwB,CAAC,MAA2B;AACxD,MAAI,MAAM,YAAY,UAAU,SAAS,GAAG;AAC1C,UAAM,YAAY,MAAM,YAAY;AAEpC,QAAI,yCAAyC,GAAG,SAAS,GAAG;AAC1D,6BAAuB;AAAA,IACzB;AAAA,EACF;AACF;AAEA,IAAM,aAAa;AAAA,EACjB,4BAA4B,MAAiC;AAC3D,cAAU;AAEV,UAAM,aAAa,KAAK;AACxB,QAAI,YAAY;AACd,6BAAuB,UAAU;AACjC,2BAAqB,UAAU;AAAA,IACjC;AAEA,UAAM,GAAG,mBAAmB,sBAAsB;AAClD,UAAM,GAAG,kBAAkB,qBAAqB;AAChD,2BAAuB;AAAA,EACzB;AAAA,EACA,gCAAgC;AAC9B,UAAM,IAAI,mBAAmB,sBAAsB;AACnD,UAAM,IAAI,kBAAkB,qBAAqB;AAAA,EACnD;AAAA,EACA,cAA6C,cAAiB;AAC5D,UAAM,YAAY,YAAY;AAAA,EAChC;AACF;AAEO,IAAI,MAAM,gBAAgB,UAAU;AAEpC,IAAM,uBAAuB,CAAC,eAA2B;AAC9D,QAAM,gBAAgB,YAAY,UAAU;AAC9C;AAEA,IAAI;AAEG,IAAI,YAAsC,CAAC;AAE3C,IAAM,eAAe,CAAC,iBAA2C;AACtE,cAAY;AACd;AAGA,IAAI,OAAO,WAAW,aAAa;AACjC,SAAO,2BAA2B,CAAC,cAAc;AAC/C,cAAU,QAAQ,CAAC,MAAM;AACvB,QAAE,SAAS;AAAA,IACb,CAAC;AAAA,EACH;AACF;", | ||
| "sourcesContent": ["import { createUIAPI, createPluginAPI } from 'figma-plugin-api';\n\nimport { nodeCanHaveChildren } from './typeUtils';\nimport { resolveAndFilterNodes } from './utils';\n\n// Allow esbuild to drop the import\nimport type { RPCOptions } from 'figma-plugin-api';\n\nimport {\n BareNode,\n FigmaSelectionHookOptions,\n FigmaSelectionListener,\n ResolverOptions,\n SerializedResolvedNode\n} from './types';\n\nexport { FIGMA_MIXED } from './constants';\n\ndeclare global {\n interface Window {\n _figma_onSelectionChange?: (selection: readonly SerializedResolvedNode<ResolverOptions>[]) => void;\n }\n}\n\nconst uiApiMethods = {\n _onSelectionChange(selection: readonly SerializedResolvedNode<ResolverOptions>[]) {\n if (typeof window._figma_onSelectionChange !== 'undefined') {\n window._figma_onSelectionChange(selection);\n }\n }\n};\n\nexport let uiApi = createUIAPI(uiApiMethods);\n\nexport const updateUiApiWithOptions = (rpcOptions: RPCOptions) => {\n uiApi = createUIAPI(uiApiMethods, rpcOptions);\n};\n\nconst selectionChangeHandler = () => {\n const resolvedSelection = resolveAndFilterNodes(figma.currentPage.selection, options);\n uiApi._onSelectionChange(resolvedSelection);\n};\n\nconst changesApplyToSelectedNodesOrDescendants = (e: DocumentChangeEvent, nodes: readonly SceneNode[]): boolean => {\n const changesApplyToNodes = e.documentChanges.some(\n (change) => nodes.findIndex((node) => node.id === change.id) !== -1\n );\n\n if (changesApplyToNodes) {\n return true;\n }\n\n const descendants: SceneNode[] = [];\n\n nodes.forEach((node) => {\n if (nodeCanHaveChildren(node)) {\n descendants.push(...node.children);\n }\n });\n\n if (descendants.length === 0) {\n return false;\n }\n\n return changesApplyToSelectedNodesOrDescendants(e, descendants);\n};\n\nconst documentChangeHandler = (e: DocumentChangeEvent) => {\n if (figma.currentPage.selection.length > 0) {\n const selection = figma.currentPage.selection;\n\n if (changesApplyToSelectedNodesOrDescendants(e, selection)) {\n selectionChangeHandler();\n }\n }\n};\n\nconst apiMethods = {\n _registerForSelectionChange(opts: FigmaSelectionHookOptions) {\n options = opts;\n\n const apiOptions = opts.apiOptions;\n if (apiOptions) {\n updateUiApiWithOptions(apiOptions);\n updateApiWithOptions(apiOptions);\n }\n\n figma.on('selectionchange', selectionChangeHandler);\n figma.on('documentchange', documentChangeHandler);\n selectionChangeHandler();\n },\n _deregisterForSelectionChange() {\n figma.off('selectionchange', selectionChangeHandler);\n figma.off('documentchange', documentChangeHandler);\n },\n _setSelection<N extends readonly BareNode[]>(newSelection: N) {\n figma.currentPage.selection = newSelection as unknown as readonly SceneNode[];\n }\n};\n\nexport let api = createPluginAPI(apiMethods);\n\nexport const updateApiWithOptions = (rpcOptions: RPCOptions) => {\n api = createPluginAPI(apiMethods, rpcOptions);\n};\n\nlet options: FigmaSelectionHookOptions;\n\nexport let listeners: FigmaSelectionListener[] = [];\n\nexport const setlisteners = (newListeners: FigmaSelectionListener[]) => {\n listeners = newListeners;\n};\n\n// In plugin UI, add a global function to receive selection change events\nif (typeof window !== 'undefined') {\n window._figma_onSelectionChange = (selection) => {\n listeners.forEach((l) => {\n l(selection);\n });\n };\n}\n", "import { ArrayType } from './typePrimitives';\n\nexport const isArray = Array.isArray as <T>(arg: T) => arg is ArrayType<T>;\n\nexport const strictObjectKeys = Object.keys as <T extends object>(obj: T) => Array<keyof T>;\n\nexport const nodeCanHaveChildren = <T extends SceneNode>(node: T): node is T & ChildrenMixin => {\n return 'children' in node;\n};\n", "/**\n * Used to replace `figma.mixed` during JSON serialization\n */\nexport const FIGMA_MIXED = 'mixed-57999e63-7384-42a1-acf8-d80b9f6c36a7';\n", "import { FIGMA_MIXED } from './constants';\nimport { nodeCanHaveChildren, strictObjectKeys } from './typeUtils';\n\nimport { ResolvedNode, ResolverOptions, SceneNodePropertyKey, SerializedResolvedNode } from './types';\n\nconst defaultNodePropertyGetterFilter = <Node extends SceneNode>(key: keyof Node, node: Node): boolean => {\n return (\n // Can only get component property definitions of a component set or non-variant component\n !(key === 'componentPropertyDefinitions' && node.parent?.type === 'COMPONENT_SET') &&\n // reading horizontalPadding and verticalPadding is no longer supported as left and right padding may differ\n key !== 'horizontalPadding' &&\n key !== 'verticalPadding'\n );\n};\n\nconst resolveNodeProperties = <Node extends SceneNode, const Keys extends readonly SceneNodePropertyKey[]>(\n node: Node,\n propertyKeys?: Keys\n): ResolvedNode<Node, Keys> => {\n // Narrow the type from the default which has a mapped type\n const descriptors = Object.getOwnPropertyDescriptors<Node>(Object.getPrototypeOf(node)) as {\n [P in keyof Node]: TypedPropertyDescriptor<Node[P]>;\n };\n\n // In reality the type is string | number | keyof T from getOwnPropertyDescriptors\n // but we can safely assert a narrower type\n const getterKeys = strictObjectKeys(descriptors);\n let getters = getterKeys.filter((key) => typeof descriptors[key].get === 'function');\n\n // type has to be included manually as it doesn't have a getter but isn't a static property either\n getters.push('type');\n\n if (propertyKeys) {\n getters = getters.filter((key) => propertyKeys.includes(key as SceneNodePropertyKey));\n }\n\n getters = getters.filter((key) => defaultNodePropertyGetterFilter(key, node));\n\n const objectWithProperties = {\n id: node.id\n } as Node;\n\n for (const getter of getters) {\n objectWithProperties[getter] = node[getter];\n }\n\n return objectWithProperties;\n};\n\nconst resolveAndSerializeNodeProperties = <Node extends SceneNode, const Options extends ResolverOptions>(\n object: Node,\n options: Options,\n ancestorsVisible: boolean\n): SerializedResolvedNode<Options> => {\n const { resolveProperties, addAncestorsVisibleProperty } = options;\n\n const resolvedNode = resolveNodeProperties(\n object,\n resolveProperties === 'all' ? undefined : resolveProperties\n ) as Record<string, unknown>;\n\n for (const key of strictObjectKeys(resolvedNode)) {\n if (resolvedNode[key] === figma.mixed) {\n resolvedNode[key] = FIGMA_MIXED;\n }\n }\n\n if (addAncestorsVisibleProperty) {\n resolvedNode.ancestorsVisible = ancestorsVisible;\n }\n\n return resolvedNode as SerializedResolvedNode<Options>;\n};\n\n// TODO: Resolve variables\nexport const resolveAndFilterNodes = <const Options extends ResolverOptions>(\n nodes: readonly SceneNode[],\n options: Options,\n ancestorsVisible: boolean = true\n): SerializedResolvedNode<Options>[] => {\n const result: SerializedResolvedNode<Options>[] = [];\n const { nodeTypes, resolveChildren } = options;\n\n if (nodeTypes !== undefined) {\n nodes.forEach((node) => {\n if (nodeTypes.includes(node.type)) {\n result.push(resolveAndSerializeNodeProperties(node, options, ancestorsVisible && node.visible));\n } else if (nodeCanHaveChildren(node) && resolveChildren) {\n result.push(...resolveAndFilterNodes(node.children, options, ancestorsVisible && node.visible));\n }\n });\n } else {\n nodes.forEach((node) => {\n if (nodeCanHaveChildren(node) && resolveChildren) {\n const newNode = {\n ...resolveAndSerializeNodeProperties(node, options, ancestorsVisible && node.visible),\n children: resolveAndFilterNodes(node.children, options, ancestorsVisible && node.visible)\n };\n result.push(newNode);\n } else {\n result.push(resolveAndSerializeNodeProperties(node, options, ancestorsVisible && node.visible));\n }\n });\n }\n\n return result;\n};\n"], | ||
| "mappings": "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", | ||
| "names": ["options"] | ||
| } |
+2
-2
| /// <reference types="@figma/plugin-typings" /> | ||
| import { RPCOptions } from 'figma-plugin-api'; | ||
| import { FIGMA_MIXED } from './constants'; | ||
| import { ApplicableNonFunctionPropertyKeys, ArrayElementUnion, NonFunctionPropertyKeys } from './typePrimitives'; | ||
| export { RPCOptions } from 'figma-plugin-api'; | ||
| import type { RPCOptions } from 'figma-plugin-api'; | ||
| export type { RPCOptions } from 'figma-plugin-api'; | ||
| export { FIGMA_MIXED } from './constants'; | ||
@@ -7,0 +7,0 @@ /** |
+3
-3
| { | ||
| "name": "figma-plugin-react-hooks", | ||
| "version": "2.0.0", | ||
| "version": "2.0.1", | ||
| "description": "", | ||
@@ -49,4 +49,4 @@ "exports": { | ||
| "peerDependencies": { | ||
| "react": ">=17.0.0", | ||
| "figma-plugin-api": ">=1.1.0" | ||
| "figma-plugin-api": ">=1.1.0", | ||
| "react": ">=17.0.0" | ||
| }, | ||
@@ -53,0 +53,0 @@ "devDependencies": { |
+4
-1
@@ -1,2 +0,2 @@ | ||
| import { createUIAPI, createPluginAPI, RPCOptions } from 'figma-plugin-api'; | ||
| import { createUIAPI, createPluginAPI } from 'figma-plugin-api'; | ||
@@ -6,2 +6,5 @@ import { nodeCanHaveChildren } from './typeUtils'; | ||
| // Allow esbuild to drop the import | ||
| import type { RPCOptions } from 'figma-plugin-api'; | ||
| import { | ||
@@ -8,0 +11,0 @@ BareNode, |
+4
-3
@@ -1,8 +0,9 @@ | ||
| import { RPCOptions } from 'figma-plugin-api'; | ||
| import { FIGMA_MIXED } from './constants'; | ||
| import { ApplicableNonFunctionPropertyKeys, ArrayElementUnion, NonFunctionPropertyKeys } from './typePrimitives'; | ||
| // Allow esbuild to drop the import | ||
| import type { RPCOptions } from 'figma-plugin-api'; | ||
| // For typedoc | ||
| export { RPCOptions } from 'figma-plugin-api'; | ||
| export type { RPCOptions } from 'figma-plugin-api'; | ||
| export { FIGMA_MIXED } from './constants'; | ||
@@ -9,0 +10,0 @@ |
120247
-8.91%1568
-0.06%