@nnao45/figma-use 0.1.4 → 0.1.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nnao45/figma-use",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Control Figma from the command line. Full read/write access for AI agents.",
5
5
  "keywords": [
6
6
  "ai",
@@ -63,9 +63,11 @@
63
63
  "agentfmt": "^0.1.3",
64
64
  "citty": "^0.1.6",
65
65
  "consola": "^3.4.2",
66
+ "d3": "^7.9.0",
66
67
  "diff": "^8.0.3",
67
68
  "esbuild": "^0.25.4",
68
69
  "fontoxpath": "^3.34.0",
70
+ "jsdom": "^27.0.1",
69
71
  "postcss": "^8.5.6",
70
72
  "svgpath": "^2.6.0",
71
73
  "typescript": "^5.8.3",
@@ -78,6 +80,8 @@
78
80
  "@iconify/types": "^2.0.0",
79
81
  "@iconify/utils": "^3.1.0",
80
82
  "@types/bun": "^1.3.6",
83
+ "@types/d3": "^7.4.3",
84
+ "@types/jsdom": "^27.0.0",
81
85
  "@types/pngjs": "^6.0.5",
82
86
  "@types/react": "^19.2.9",
83
87
  "@types/ws": "^8.18.1",
@@ -28,6 +28,10 @@ export function Line(props: BaseProps): TreeNode {
28
28
  return node('line', props)
29
29
  }
30
30
 
31
+ export function Arrow(props: BaseProps): TreeNode {
32
+ return node('arrow', props)
33
+ }
34
+
31
35
  export function Image(props: BaseProps & { src: string }): TreeNode {
32
36
  return node('image', props)
33
37
  }
@@ -70,6 +74,7 @@ export const INTRINSIC_ELEMENTS = [
70
74
  'rectangle',
71
75
  'ellipse',
72
76
  'line',
77
+ 'arrow',
73
78
  'image',
74
79
  'svg',
75
80
  'star',
@@ -625,6 +625,29 @@ async function handleCommand(command: string, args?: unknown): Promise<unknown>
625
625
  }
626
626
 
627
627
  case 'create-line': {
628
+ const { x, y, length, rotation, name, parentId, stroke, strokeWeight } = args as {
629
+ x: number
630
+ y: number
631
+ length: number
632
+ rotation?: number
633
+ name?: string
634
+ parentId?: string
635
+ stroke?: string
636
+ strokeWeight?: number
637
+ }
638
+ const line = figma.createLine()
639
+ line.x = x
640
+ line.y = y
641
+ line.resize(length, 0)
642
+ if (rotation) line.rotation = rotation
643
+ if (name) line.name = name
644
+ if (stroke) line.strokes = [await createSolidPaint(stroke)]
645
+ if (strokeWeight !== undefined) line.strokeWeight = strokeWeight
646
+ await appendToParent(line, parentId)
647
+ return serializeNode(line)
648
+ }
649
+
650
+ case 'create-arrow': {
628
651
  const { x, y, length, rotation, name, parentId, stroke, strokeWeight, startCap, endCap } =
629
652
  args as {
630
653
  x: number
@@ -638,47 +661,28 @@ async function handleCommand(command: string, args?: unknown): Promise<unknown>
638
661
  startCap?: string
639
662
  endCap?: string
640
663
  }
641
- const startCapValue = normalizeLineCap(startCap)
642
- const endCapValue = normalizeLineCap(endCap)
643
- const useVector =
644
- (startCapValue && startCapValue !== 'NONE') || (endCapValue && endCapValue !== 'NONE')
645
-
646
- // If caps are specified, use VectorNetwork for independent start/end caps
647
- if (useVector) {
648
- const vector = figma.createVector()
649
- vector.x = x
650
- vector.y = y
651
-
652
- vector.vectorNetwork = {
653
- vertices: [
654
- { x: 0, y: 0, strokeCap: startCapValue ?? 'NONE' },
655
- { x: length, y: 0, strokeCap: endCapValue ?? 'NONE' }
656
- ],
657
- segments: [
658
- { start: 0, end: 1, tangentStart: { x: 0, y: 0 }, tangentEnd: { x: 0, y: 0 } }
659
- ],
660
- regions: []
661
- }
662
-
663
- if (stroke) vector.strokes = [await createSolidPaint(stroke)]
664
- if (strokeWeight !== undefined) vector.strokeWeight = strokeWeight
665
- if (rotation) vector.rotation = rotation
666
- if (name) vector.name = name
667
- await appendToParent(vector, parentId)
668
- return serializeNode(vector)
669
- }
670
-
671
- // No caps specified - use simple Line
672
- const line = figma.createLine()
673
- line.x = x
674
- line.y = y
675
- line.resize(length, 0)
676
- if (rotation) line.rotation = rotation
677
- if (name) line.name = name
678
- if (stroke) line.strokes = [await createSolidPaint(stroke)]
679
- if (strokeWeight !== undefined) line.strokeWeight = strokeWeight
680
- await appendToParent(line, parentId)
681
- return serializeNode(line)
664
+ const startCapValue = normalizeLineCap(startCap) ?? 'NONE'
665
+ const endCapValue = normalizeLineCap(endCap) ?? 'ARROW_LINES'
666
+
667
+ const vector = figma.createVector()
668
+ vector.x = x
669
+ vector.y = y
670
+ vector.name = name || 'Arrow'
671
+
672
+ vector.vectorNetwork = {
673
+ vertices: [
674
+ { x: 0, y: 0, strokeCap: startCapValue },
675
+ { x: length, y: 0, strokeCap: endCapValue }
676
+ ],
677
+ segments: [{ start: 0, end: 1, tangentStart: { x: 0, y: 0 }, tangentEnd: { x: 0, y: 0 } }],
678
+ regions: []
679
+ }
680
+
681
+ if (stroke) vector.strokes = [await createSolidPaint(stroke)]
682
+ if (strokeWeight !== undefined) vector.strokeWeight = strokeWeight
683
+ if (rotation) vector.rotation = rotation
684
+ await appendToParent(vector, parentId)
685
+ return serializeNode(vector)
682
686
  }
683
687
 
684
688
  case 'create-polygon': {
@@ -899,6 +899,29 @@ async function handleCommand(command: string, args?: unknown): Promise<unknown>
899
899
  }
900
900
 
901
901
  case 'create-line': {
902
+ const { x, y, length, rotation, name, parentId, stroke, strokeWeight } = args as {
903
+ x: number
904
+ y: number
905
+ length: number
906
+ rotation?: number
907
+ name?: string
908
+ parentId?: string
909
+ stroke?: string
910
+ strokeWeight?: number
911
+ }
912
+ const line = figma.createLine()
913
+ line.x = x
914
+ line.y = y
915
+ line.resize(length, 0)
916
+ if (rotation) line.rotation = rotation
917
+ if (name) line.name = name
918
+ if (stroke) line.strokes = [await createSolidPaint(stroke)]
919
+ if (strokeWeight !== undefined) line.strokeWeight = strokeWeight
920
+ await appendToParent(line, parentId)
921
+ return serializeNode(line)
922
+ }
923
+
924
+ case 'create-arrow': {
902
925
  const { x, y, length, rotation, name, parentId, stroke, strokeWeight, startCap, endCap } =
903
926
  args as {
904
927
  x: number
@@ -912,47 +935,28 @@ async function handleCommand(command: string, args?: unknown): Promise<unknown>
912
935
  startCap?: string
913
936
  endCap?: string
914
937
  }
915
- const startCapValue = normalizeLineCap(startCap)
916
- const endCapValue = normalizeLineCap(endCap)
917
- const useVector =
918
- (startCapValue && startCapValue !== 'NONE') || (endCapValue && endCapValue !== 'NONE')
919
-
920
- // If caps are specified, use VectorNetwork for independent start/end caps
921
- if (useVector) {
922
- const vector = figma.createVector()
923
- vector.x = x
924
- vector.y = y
925
-
926
- vector.vectorNetwork = {
927
- vertices: [
928
- { x: 0, y: 0, strokeCap: startCapValue ?? 'NONE' },
929
- { x: length, y: 0, strokeCap: endCapValue ?? 'NONE' }
930
- ],
931
- segments: [
932
- { start: 0, end: 1, tangentStart: { x: 0, y: 0 }, tangentEnd: { x: 0, y: 0 } }
933
- ],
934
- regions: []
935
- }
936
-
937
- if (stroke) vector.strokes = [await createSolidPaint(stroke)]
938
- if (strokeWeight !== undefined) vector.strokeWeight = strokeWeight
939
- if (rotation) vector.rotation = rotation
940
- if (name) vector.name = name
941
- await appendToParent(vector, parentId)
942
- return serializeNode(vector)
943
- }
944
-
945
- // No caps specified - use simple Line
946
- const line = figma.createLine()
947
- line.x = x
948
- line.y = y
949
- line.resize(length, 0)
950
- if (rotation) line.rotation = rotation
951
- if (name) line.name = name
952
- if (stroke) line.strokes = [await createSolidPaint(stroke)]
953
- if (strokeWeight !== undefined) line.strokeWeight = strokeWeight
954
- await appendToParent(line, parentId)
955
- return serializeNode(line)
938
+ const startCapValue = normalizeLineCap(startCap) ?? 'NONE'
939
+ const endCapValue = normalizeLineCap(endCap) ?? 'ARROW_LINES'
940
+
941
+ const vector = figma.createVector()
942
+ vector.x = x
943
+ vector.y = y
944
+ vector.name = name || 'Arrow'
945
+
946
+ vector.vectorNetwork = {
947
+ vertices: [
948
+ { x: 0, y: 0, strokeCap: startCapValue },
949
+ { x: length, y: 0, strokeCap: endCapValue }
950
+ ],
951
+ segments: [{ start: 0, end: 1, tangentStart: { x: 0, y: 0 }, tangentEnd: { x: 0, y: 0 } }],
952
+ regions: []
953
+ }
954
+
955
+ if (stroke) vector.strokes = [await createSolidPaint(stroke)]
956
+ if (strokeWeight !== undefined) vector.strokeWeight = strokeWeight
957
+ if (rotation) vector.rotation = rotation
958
+ await appendToParent(vector, parentId)
959
+ return serializeNode(vector)
956
960
  }
957
961
 
958
962
  case 'create-polygon': {
@@ -2631,30 +2635,25 @@ async function handleCommand(command: string, args?: unknown): Promise<unknown>
2631
2635
  const isText = type === 'text'
2632
2636
  const processed = processProps(props || {}, isText)
2633
2637
 
2634
- // Handle <line> with startCap/endCap - mark for VectorNetwork replacement
2635
- if (type === 'line' && (props.startCap || props.endCap)) {
2636
- const startCapValue = normalizeLineCap(props.startCap as string | undefined)
2637
- const endCapValue = normalizeLineCap(props.endCap as string | undefined)
2638
- const useVector =
2639
- (startCapValue && startCapValue !== 'NONE') || (endCapValue && endCapValue !== 'NONE')
2640
-
2641
- if (useVector) {
2642
- lineCapNodes.push({
2643
- path: [...path],
2644
- startCap: startCapValue,
2645
- endCap: endCapValue,
2646
- stroke: processed.stroke as string | undefined,
2647
- strokeWidth: processed.strokeWidth as number | undefined
2648
- })
2649
- }
2638
+ // Handle <arrow> - mark for VectorNetwork with caps
2639
+ if (type === 'arrow') {
2640
+ const startCapValue = normalizeLineCap(props.startCap as string | undefined) ?? 'NONE'
2641
+ const endCapValue = normalizeLineCap(props.endCap as string | undefined) ?? 'ARROW_LINES'
2642
+
2643
+ lineCapNodes.push({
2644
+ path: [...path],
2645
+ startCap: startCapValue,
2646
+ endCap: endCapValue,
2647
+ stroke: processed.stroke as string | undefined,
2648
+ strokeWidth: processed.strokeWidth as number | undefined
2649
+ })
2650
2650
 
2651
- // Don't pass startCap/endCap to Widget Line
2651
+ // Create normal Line as placeholder (will be replaced)
2652
2652
  const cleanProps = { ...processed }
2653
2653
  delete cleanProps.startCap
2654
2654
  delete cleanProps.endCap
2655
2655
  delete cleanProps.__startCap
2656
2656
  delete cleanProps.__endCap
2657
- // Create normal Line as placeholder
2658
2657
  return h(Line, cleanProps)
2659
2658
  }
2660
2659