@nnao45/figma-use 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +991 -0
- package/LICENSE +22 -0
- package/README.md +569 -0
- package/SKILL.md +683 -0
- package/bin/figma-use.js +9 -0
- package/dist/cli/index.js +496 -0
- package/package.json +87 -0
- package/packages/cli/src/render/component-set.tsx +157 -0
- package/packages/cli/src/render/components.tsx +115 -0
- package/packages/cli/src/render/icon.ts +166 -0
- package/packages/cli/src/render/index.ts +47 -0
- package/packages/cli/src/render/jsx-dev-runtime.ts +6 -0
- package/packages/cli/src/render/jsx-runtime.ts +90 -0
- package/packages/cli/src/render/mini-react.ts +33 -0
- package/packages/cli/src/render/render-from-string.ts +121 -0
- package/packages/cli/src/render/render-jsx.ts +44 -0
- package/packages/cli/src/render/tree.ts +148 -0
- package/packages/cli/src/render/vars.ts +186 -0
- package/packages/cli/src/render/widget-renderer.ts +163 -0
- package/packages/plugin/src/main.ts +2747 -0
- package/packages/plugin/src/query.ts +253 -0
- package/packages/plugin/src/rpc.ts +5238 -0
- package/packages/plugin/src/ui.html +25 -0
- package/packages/plugin/src/ui.ts +74 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { evaluateXPathToNodes, evaluateXPathToBoolean } from 'fontoxpath'
|
|
2
|
+
|
|
3
|
+
const NODE_TYPES = {
|
|
4
|
+
ELEMENT_NODE: 1,
|
|
5
|
+
ATTRIBUTE_NODE: 2,
|
|
6
|
+
TEXT_NODE: 3,
|
|
7
|
+
DOCUMENT_NODE: 9
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface FigmaDocument {
|
|
11
|
+
nodeType: number
|
|
12
|
+
nodeName: string
|
|
13
|
+
documentElement: FigmaNode
|
|
14
|
+
_children?: FigmaNode[]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const QUERYABLE_ATTRS = [
|
|
18
|
+
'name',
|
|
19
|
+
'width',
|
|
20
|
+
'height',
|
|
21
|
+
'x',
|
|
22
|
+
'y',
|
|
23
|
+
'visible',
|
|
24
|
+
'opacity',
|
|
25
|
+
'cornerRadius',
|
|
26
|
+
'characters',
|
|
27
|
+
'fontSize',
|
|
28
|
+
'layoutMode',
|
|
29
|
+
'itemSpacing',
|
|
30
|
+
'paddingTop',
|
|
31
|
+
'paddingBottom',
|
|
32
|
+
'paddingLeft',
|
|
33
|
+
'paddingRight',
|
|
34
|
+
'strokeWeight',
|
|
35
|
+
'rotation',
|
|
36
|
+
'constrainProportions'
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
interface FigmaAttr {
|
|
40
|
+
nodeType: number
|
|
41
|
+
nodeName: string
|
|
42
|
+
name: string
|
|
43
|
+
localName: string
|
|
44
|
+
namespaceURI: null
|
|
45
|
+
prefix: null
|
|
46
|
+
value: string
|
|
47
|
+
ownerElement: FigmaNode
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface FigmaNode {
|
|
51
|
+
nodeType: number
|
|
52
|
+
nodeName: string
|
|
53
|
+
localName: string
|
|
54
|
+
namespaceURI: null
|
|
55
|
+
prefix: null
|
|
56
|
+
_figmaNode: SceneNode | PageNode
|
|
57
|
+
_attrs?: FigmaAttr[]
|
|
58
|
+
_parent?: FigmaNode | null
|
|
59
|
+
_children?: FigmaNode[]
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function wrapNode(
|
|
63
|
+
node: SceneNode | PageNode,
|
|
64
|
+
parent?: FigmaNode | FigmaDocument | null
|
|
65
|
+
): FigmaNode {
|
|
66
|
+
const wrapped: FigmaNode = {
|
|
67
|
+
nodeType: NODE_TYPES.ELEMENT_NODE,
|
|
68
|
+
nodeName: node.type,
|
|
69
|
+
localName: node.type,
|
|
70
|
+
namespaceURI: null,
|
|
71
|
+
prefix: null,
|
|
72
|
+
_figmaNode: node,
|
|
73
|
+
_parent: parent as FigmaNode | null
|
|
74
|
+
}
|
|
75
|
+
return wrapped
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function createDocument(rootNode: SceneNode | PageNode): FigmaDocument {
|
|
79
|
+
const doc: FigmaDocument = {
|
|
80
|
+
nodeType: NODE_TYPES.DOCUMENT_NODE,
|
|
81
|
+
nodeName: '#document',
|
|
82
|
+
documentElement: null as unknown as FigmaNode
|
|
83
|
+
}
|
|
84
|
+
const root = wrapNode(rootNode, doc as unknown as FigmaNode)
|
|
85
|
+
doc.documentElement = root
|
|
86
|
+
doc._children = [root]
|
|
87
|
+
return doc
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function getAttrs(wrapped: FigmaNode): FigmaAttr[] {
|
|
91
|
+
if (wrapped._attrs) return wrapped._attrs
|
|
92
|
+
|
|
93
|
+
const node = wrapped._figmaNode
|
|
94
|
+
const attrs: FigmaAttr[] = []
|
|
95
|
+
|
|
96
|
+
for (const attrName of QUERYABLE_ATTRS) {
|
|
97
|
+
if (attrName in node) {
|
|
98
|
+
const value = (node as unknown as Record<string, unknown>)[attrName]
|
|
99
|
+
// Skip figma.mixed symbol and undefined/null
|
|
100
|
+
if (value === undefined || value === null || typeof value === 'symbol') {
|
|
101
|
+
continue
|
|
102
|
+
}
|
|
103
|
+
attrs.push({
|
|
104
|
+
nodeType: NODE_TYPES.ATTRIBUTE_NODE,
|
|
105
|
+
nodeName: attrName,
|
|
106
|
+
name: attrName,
|
|
107
|
+
localName: attrName,
|
|
108
|
+
namespaceURI: null,
|
|
109
|
+
prefix: null,
|
|
110
|
+
value: String(value),
|
|
111
|
+
ownerElement: wrapped
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
wrapped._attrs = attrs
|
|
117
|
+
return attrs
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function getChildren(wrapped: FigmaNode): FigmaNode[] {
|
|
121
|
+
if (wrapped._children) return wrapped._children
|
|
122
|
+
|
|
123
|
+
const node = wrapped._figmaNode
|
|
124
|
+
if (!('children' in node) || !node.children) {
|
|
125
|
+
wrapped._children = []
|
|
126
|
+
return []
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
wrapped._children = node.children.map((child) => wrapNode(child, wrapped))
|
|
130
|
+
return wrapped._children
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function isDocument(node: unknown): node is FigmaDocument {
|
|
134
|
+
return (node as FigmaDocument)?.nodeType === NODE_TYPES.DOCUMENT_NODE
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const figmaDomFacade = {
|
|
138
|
+
getAllAttributes(node: FigmaNode | FigmaDocument): FigmaAttr[] {
|
|
139
|
+
if (isDocument(node)) return []
|
|
140
|
+
return getAttrs(node)
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
getAttribute(node: FigmaNode | FigmaDocument, attributeName: string): string | null {
|
|
144
|
+
if (isDocument(node)) return null
|
|
145
|
+
const figmaNode = node._figmaNode
|
|
146
|
+
if (attributeName in figmaNode) {
|
|
147
|
+
const value = (figmaNode as unknown as Record<string, unknown>)[attributeName]
|
|
148
|
+
// Skip figma.mixed symbol and undefined/null
|
|
149
|
+
if (value === undefined || value === null || typeof value === 'symbol') {
|
|
150
|
+
return null
|
|
151
|
+
}
|
|
152
|
+
return String(value)
|
|
153
|
+
}
|
|
154
|
+
return null
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
getChildNodes(node: FigmaNode | FigmaDocument): FigmaNode[] {
|
|
158
|
+
if (isDocument(node)) return node._children || []
|
|
159
|
+
return getChildren(node)
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
getData(node: FigmaAttr): string {
|
|
163
|
+
return node.value
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
getFirstChild(node: FigmaNode | FigmaDocument): FigmaNode | null {
|
|
167
|
+
if (isDocument(node)) return node.documentElement
|
|
168
|
+
const children = getChildren(node)
|
|
169
|
+
return children[0] || null
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
getLastChild(node: FigmaNode | FigmaDocument): FigmaNode | null {
|
|
173
|
+
if (isDocument(node)) return node.documentElement
|
|
174
|
+
const children = getChildren(node)
|
|
175
|
+
return children[children.length - 1] || null
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
getNextSibling(node: FigmaNode | FigmaDocument): FigmaNode | null {
|
|
179
|
+
if (isDocument(node)) return null
|
|
180
|
+
const parent = node._parent
|
|
181
|
+
if (!parent) return null
|
|
182
|
+
|
|
183
|
+
const siblings = getChildren(parent)
|
|
184
|
+
const idx = siblings.indexOf(node)
|
|
185
|
+
return siblings[idx + 1] || null
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
getParentNode(node: FigmaNode | FigmaDocument): FigmaNode | FigmaDocument | null {
|
|
189
|
+
if (isDocument(node)) return null
|
|
190
|
+
return node._parent || null
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
getPreviousSibling(node: FigmaNode | FigmaDocument): FigmaNode | null {
|
|
194
|
+
if (isDocument(node)) return null
|
|
195
|
+
const parent = node._parent
|
|
196
|
+
if (!parent) return null
|
|
197
|
+
|
|
198
|
+
const siblings = getChildren(parent)
|
|
199
|
+
const idx = siblings.indexOf(node)
|
|
200
|
+
return idx > 0 ? siblings[idx - 1] : null
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
interface QueryOptions {
|
|
205
|
+
limit?: number
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function queryNodes(
|
|
209
|
+
selector: string,
|
|
210
|
+
root: BaseNode | BaseNode[],
|
|
211
|
+
options: QueryOptions = {}
|
|
212
|
+
): SceneNode[] {
|
|
213
|
+
const { limit = 1000 } = options
|
|
214
|
+
const roots = Array.isArray(root) ? root : [root]
|
|
215
|
+
const results: SceneNode[] = []
|
|
216
|
+
|
|
217
|
+
console.log('[xpath] Query:', selector)
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
for (const rootNode of roots) {
|
|
221
|
+
const doc = createDocument(rootNode as PageNode)
|
|
222
|
+
|
|
223
|
+
console.log('[xpath] Evaluating on:', rootNode.name)
|
|
224
|
+
|
|
225
|
+
const nodes = evaluateXPathToNodes(selector, doc, figmaDomFacade)
|
|
226
|
+
|
|
227
|
+
console.log('[xpath] Found:', nodes.length)
|
|
228
|
+
|
|
229
|
+
for (const node of nodes) {
|
|
230
|
+
if (results.length >= limit) break
|
|
231
|
+
const figmaNode = (node as FigmaNode)._figmaNode
|
|
232
|
+
if (figmaNode && 'type' in figmaNode && figmaNode.type !== 'PAGE') {
|
|
233
|
+
results.push(figmaNode as SceneNode)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
} catch (err) {
|
|
238
|
+
console.error('[xpath] Error:', err)
|
|
239
|
+
throw err
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
console.log('[xpath] Total results:', results.length)
|
|
243
|
+
return results
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export function matchNode(selector: string, node: SceneNode): boolean {
|
|
247
|
+
try {
|
|
248
|
+
const wrapped = wrapNode(node)
|
|
249
|
+
return evaluateXPathToBoolean(`self::*[${selector}]`, wrapped, figmaDomFacade)
|
|
250
|
+
} catch {
|
|
251
|
+
return false
|
|
252
|
+
}
|
|
253
|
+
}
|