figma-node-selector-utility
Version:
A utility for selecting Figma nodes with Figma Tags and Attributes plugin.
119 lines (118 loc) • 4.94 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.$F = exports.FigmaNodeSelectorUtility = void 0;
class FigmaNodeSelectorUtility {
constructor() {
this.nodes = [];
this.updateNodes();
// Correctly attach the event listener to the `figma` global object.
figma.on('selectionchange', () => {
this.updateNodes();
});
}
updateNodes() {
this.nodes = [];
if (figma.currentPage.selection.length > 0) {
this.collectAllNodes(figma.currentPage.selection[0]);
}
}
collectAllNodes(node) {
this.nodes.push(node);
if (node.children) {
node.children.forEach((child) => {
this.collectAllNodes(child);
});
}
}
parseNodeData(node) {
return JSON.parse(node.getSharedPluginData("figma.attributes", "attributes") || '{}');
}
findByClassName(className, nodes) {
return nodes.filter(node => {
const data = this.parseNodeData(node);
return data.attributes && data.attributes.class && data.attributes.class.split(' ').includes(className);
});
}
findByTagName(tagName, nodes) {
return nodes.filter(node => {
const data = this.parseNodeData(node);
return data.tag === tagName;
});
}
findByAttribute(attribute, value = null, nodes) {
return nodes.filter(node => {
const data = this.parseNodeData(node);
// Check if the attribute exists
const hasAttribute = data.attributes && data.attributes.hasOwnProperty(attribute);
if (value === null) {
// If no value is provided, return nodes that have the attribute
return hasAttribute;
}
else {
// If a value is provided, return nodes where the attribute's value matches
return hasAttribute && data.attributes[attribute] === value;
}
});
}
find(selector) {
let currentNodes = [...this.nodes];
// Splitting the selector for group selectors (e.g., div, .class)
const groupSelectors = selector.split(',').map(s => s.trim());
let foundNodes = [];
groupSelectors.forEach(groupSelector => {
let tempNodes = [...currentNodes]; // Start with a copy of all current nodes
// Process compound selectors (e.g., div.className[attr=value] or div[attr])
const compoundSelectors = groupSelector.split(' ').filter(Boolean);
compoundSelectors.forEach(compoundSelector => {
// Handling attribute selectors within a compound selector
// Adjusted regex to optionally match attribute selectors without a value
const attrMatch = compoundSelector.match(/\[([^=\]]+)(?:=([^\]]+))?\]/);
if (attrMatch) {
const attrName = attrMatch[1].trim();
// Adjust to handle attribute presence without value
const attrValue = attrMatch.length > 2 ? attrMatch[2].trim().replace(/^"|"$/g, '').replace(/^'|'$/g, '') : null; // Removing possible quotes
tempNodes = this.findByAttribute(attrName, attrValue, tempNodes);
compoundSelector = compoundSelector.replace(attrMatch[0], ''); // Remove attribute selector from compound
}
// Process the rest (class, id, tagName)
tempNodes = this.processBasicSelectors(compoundSelector, tempNodes);
});
foundNodes = [...foundNodes, ...tempNodes];
});
return foundNodes;
}
// Supporting method for processing class, id, and tag name selectors.
processBasicSelectors(selector, nodes) {
// If the selector is empty after removing attribute selectors, return the nodes as is.
if (!selector)
return nodes;
// Class selector
if (selector.startsWith('.')) {
const className = selector.substring(1);
return this.findByClassName(className, nodes);
}
// ID selector
else if (selector.startsWith('#')) {
const idValue = selector.substring(1);
return this.findByAttribute('id', idValue, nodes);
}
// Tag name or other selectors
else {
return this.findByTagName(selector, nodes);
}
}
first(selector) {
return this.find(selector)[0] || null;
}
last(selector) {
const found = this.find(selector);
return found[found.length - 1] || null;
}
}
exports.FigmaNodeSelectorUtility = FigmaNodeSelectorUtility;
exports.$F = (function () {
const selectorInstance = new FigmaNodeSelectorUtility();
return function (selectorString) {
return selectorInstance.find(selectorString);
};
})();