@nice2dev/ui-graphics 1.3.15 → 1.3.16

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.
Files changed (294) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/cjs/animation/AnimatedPerson.js +470 -143
  3. package/dist/cjs/animation/AnimationEditor.js +289 -101
  4. package/dist/cjs/animation/Audience.js +83 -61
  5. package/dist/cjs/animation/BodyRenderer.js +69 -28
  6. package/dist/cjs/animation/animationHelper.js +281 -288
  7. package/dist/cjs/animation/characterTypes.js +200 -203
  8. package/dist/cjs/animation/choreoDSL.js +205 -219
  9. package/dist/cjs/animation/choreography.js +4 -32
  10. package/dist/cjs/animation/karaokeIntegration.js +68 -77
  11. package/dist/cjs/animation/lottieExport.js +108 -114
  12. package/dist/cjs/animation/motionPath.js +225 -248
  13. package/dist/cjs/animation/physics.js +227 -254
  14. package/dist/cjs/animation/rig/RigPlayer.js +53 -56
  15. package/dist/cjs/animation/rig/RiggedBody.js +71 -26
  16. package/dist/cjs/animation/rig/choreoRigBridge.js +72 -77
  17. package/dist/cjs/animation/rig/ik.js +13 -18
  18. package/dist/cjs/animation/rig/poseOps.js +34 -41
  19. package/dist/cjs/animation/rig/presets.js +64 -72
  20. package/dist/cjs/animation/rig/rigMath.js +25 -32
  21. package/dist/cjs/animation/rig/rigTypes.js +59 -63
  22. package/dist/cjs/animation/shapes/arms.js +31 -13
  23. package/dist/cjs/animation/shapes/eyes.js +129 -47
  24. package/dist/cjs/animation/shapes/hair.js +72 -45
  25. package/dist/cjs/animation/shapes/heads.js +131 -57
  26. package/dist/cjs/animation/shapes/headwear.js +47 -38
  27. package/dist/cjs/animation/shapes/legs.js +31 -13
  28. package/dist/cjs/animation/shapes/mouths.js +120 -44
  29. package/dist/cjs/animation/shapes/noses.js +80 -25
  30. package/dist/cjs/animation/shapes/outfits.js +101 -39
  31. package/dist/cjs/animation/shapes/torsos.js +15 -11
  32. package/dist/cjs/animation/spineExport.js +103 -121
  33. package/dist/cjs/core/LocalUI.js +108 -43
  34. package/dist/cjs/core/NiceBrushEngine.js +758 -552
  35. package/dist/cjs/core/collaboration.js +249 -251
  36. package/dist/cjs/core/colorBlindness.js +100 -115
  37. package/dist/cjs/core/dragDrop.js +155 -158
  38. package/dist/cjs/core/editorAsk.js +51 -41
  39. package/dist/cjs/core/fixes.js +140 -171
  40. package/dist/cjs/core/gridGuides.js +166 -201
  41. package/dist/cjs/core/historyVisual.js +156 -135
  42. package/dist/cjs/core/i18n.js +13 -14
  43. package/dist/cjs/core/integrations.js +238 -249
  44. package/dist/cjs/core/minimap.js +149 -151
  45. package/dist/cjs/core/plugins.js +98 -113
  46. package/dist/cjs/core/rtl.js +71 -83
  47. package/dist/cjs/core/shortcuts.js +196 -225
  48. package/dist/cjs/core/theme.js +142 -149
  49. package/dist/cjs/core/yjsCollaboration.js +232 -293
  50. package/dist/cjs/design/design-tools.js +12 -22
  51. package/dist/cjs/font/NiceFontEditor.js +877 -316
  52. package/dist/cjs/font/fontFormatUtils.js +388 -448
  53. package/dist/cjs/game/GameAsset2dEditor.js +1201 -392
  54. package/dist/cjs/game/GameAsset2dEditor.module.css.js +146 -5
  55. package/dist/cjs/game/gameAssetTypes.js +7 -16
  56. package/dist/cjs/game/gameAssetUtils.js +390 -413
  57. package/dist/cjs/game/useGameAssetEditor.js +820 -763
  58. package/dist/cjs/index.js +81 -84
  59. package/dist/cjs/nice2dev-ui-graphics.css +1 -1
  60. package/dist/cjs/node_modules/lib0/array.js +8 -70
  61. package/dist/cjs/node_modules/lib0/binary.js +3 -20
  62. package/dist/cjs/node_modules/lib0/broadcastchannel.js +24 -86
  63. package/dist/cjs/node_modules/lib0/buffer.js +15 -82
  64. package/dist/cjs/node_modules/lib0/conditions.js +3 -16
  65. package/dist/cjs/node_modules/lib0/decoding.js +108 -287
  66. package/dist/cjs/node_modules/lib0/dom.js +14 -44
  67. package/dist/cjs/node_modules/lib0/encoding.js +68 -418
  68. package/dist/cjs/node_modules/lib0/environment.js +26 -92
  69. package/dist/cjs/node_modules/lib0/error.js +5 -29
  70. package/dist/cjs/node_modules/lib0/function.js +27 -66
  71. package/dist/cjs/node_modules/lib0/iterator.js +8 -33
  72. package/dist/cjs/node_modules/lib0/logging.common.js +12 -22
  73. package/dist/cjs/node_modules/lib0/logging.js +32 -71
  74. package/dist/cjs/node_modules/lib0/map.js +16 -86
  75. package/dist/cjs/node_modules/lib0/math.js +3 -32
  76. package/dist/cjs/node_modules/lib0/number.js +4 -15
  77. package/dist/cjs/node_modules/lib0/object.js +14 -88
  78. package/dist/cjs/node_modules/lib0/observable.js +38 -78
  79. package/dist/cjs/node_modules/lib0/pair.js +3 -20
  80. package/dist/cjs/node_modules/lib0/prng.js +8 -77
  81. package/dist/cjs/node_modules/lib0/promise.js +6 -30
  82. package/dist/cjs/node_modules/lib0/random.js +8 -23
  83. package/dist/cjs/node_modules/lib0/schema.js +313 -687
  84. package/dist/cjs/node_modules/lib0/set.js +3 -10
  85. package/dist/cjs/node_modules/lib0/storage.js +20 -55
  86. package/dist/cjs/node_modules/lib0/string.js +17 -72
  87. package/dist/cjs/node_modules/lib0/symbol.js +2 -12
  88. package/dist/cjs/node_modules/lib0/time.js +2 -15
  89. package/dist/cjs/node_modules/lib0/trait/equality.js +3 -28
  90. package/dist/cjs/node_modules/lib0/url.js +4 -18
  91. package/dist/cjs/node_modules/lib0/webcrypto.js +2 -5
  92. package/dist/cjs/node_modules/y-protocols/auth.js +5 -19
  93. package/dist/cjs/node_modules/y-protocols/awareness.js +53 -116
  94. package/dist/cjs/node_modules/y-protocols/sync.js +16 -103
  95. package/dist/cjs/node_modules/y-websocket/src/y-websocket.js +77 -219
  96. package/dist/cjs/node_modules/yjs/dist/yjs.js +2128 -4868
  97. package/dist/cjs/photo/FilterThumb.js +37 -25
  98. package/dist/cjs/photo/NiceAITools.js +679 -467
  99. package/dist/cjs/photo/PhotoEditor.js +34 -20
  100. package/dist/cjs/photo/PhotoEditor.module.css.js +296 -5
  101. package/dist/cjs/photo/PhotoEditorCanvas.js +250 -91
  102. package/dist/cjs/photo/PhotoEditorLeftPanel.js +1086 -224
  103. package/dist/cjs/photo/PhotoEditorToolbar.js +135 -19
  104. package/dist/cjs/photo/photoEditorActions.js +89 -72
  105. package/dist/cjs/photo/photoEditorTypes.js +29 -32
  106. package/dist/cjs/photo/photoFilters.js +959 -985
  107. package/dist/cjs/photo/photoOverlays.js +899 -945
  108. package/dist/cjs/photo/photoSelection.js +427 -499
  109. package/dist/cjs/photo/usePhotoEditor.js +739 -741
  110. package/dist/cjs/pixel/HSVPicker.js +191 -175
  111. package/dist/cjs/pixel/PixelEditor.js +159 -48
  112. package/dist/cjs/pixel/PixelEditor.module.css.js +209 -5
  113. package/dist/cjs/pixel/PixelEditorCanvas.js +29 -16
  114. package/dist/cjs/pixel/PixelEditorMenuBar.js +160 -162
  115. package/dist/cjs/pixel/PixelEditorRightPanel.js +377 -45
  116. package/dist/cjs/pixel/PixelEditorStatusBar.js +47 -20
  117. package/dist/cjs/pixel/PixelEditorTimeline.js +106 -23
  118. package/dist/cjs/pixel/PixelEditorToolbar.js +99 -21
  119. package/dist/cjs/pixel/SpriteGeneratorPanel.js +483 -303
  120. package/dist/cjs/pixel/animalGeneratorPresets.js +776 -601
  121. package/dist/cjs/pixel/asepriteFormat.js +511 -560
  122. package/dist/cjs/pixel/buildingGeneratorPresets.js +693 -520
  123. package/dist/cjs/pixel/characterGeneratorPresets.js +644 -544
  124. package/dist/cjs/pixel/pixel-pro.js +52 -63
  125. package/dist/cjs/pixel/pixelEditorExports.js +388 -428
  126. package/dist/cjs/pixel/pixelEditorTypes.js +91 -108
  127. package/dist/cjs/pixel/pixelEditorUtils.js +519 -561
  128. package/dist/cjs/pixel/spriteGeneratorCore.js +206 -257
  129. package/dist/cjs/pixel/usePixelEditor.js +1321 -1343
  130. package/dist/cjs/texture/Nice3DTexturePainter.js +707 -440
  131. package/dist/cjs/ui/NiceUIDesigner.js +954 -527
  132. package/dist/cjs/vector/NiceSvgToCode.js +586 -446
  133. package/dist/cjs/vector/VectorEditor.js +290 -117
  134. package/dist/cjs/vector/VectorEditor.module.css.js +86 -5
  135. package/dist/cjs/vector/VectorEditorMenuBar.js +333 -275
  136. package/dist/cjs/vector/VectorEditorRightPanel.js +1094 -371
  137. package/dist/cjs/vector/VectorEditorShapeRenderer.js +198 -60
  138. package/dist/cjs/vector/VectorEditorStatusBar.js +39 -13
  139. package/dist/cjs/vector/useVectorEditor.js +848 -889
  140. package/dist/cjs/vector/vectorAnimation.js +110 -137
  141. package/dist/cjs/vector/vectorBooleanOps.js +398 -485
  142. package/dist/cjs/vector/vectorEditorExport.js +201 -203
  143. package/dist/cjs/vector/vectorEditorImport.js +173 -189
  144. package/dist/cjs/vector/vectorEditorTypes.js +42 -49
  145. package/dist/cjs/vector/vectorGradients.js +151 -174
  146. package/dist/cjs/vector/vectorIconExport.js +52 -59
  147. package/dist/cjs/vector/vectorIconImport.js +169 -219
  148. package/dist/esm/animation/AnimatedPerson.js +470 -141
  149. package/dist/esm/animation/AnimationEditor.js +290 -100
  150. package/dist/esm/animation/Audience.js +83 -59
  151. package/dist/esm/animation/BodyRenderer.js +71 -25
  152. package/dist/esm/animation/animationHelper.js +284 -287
  153. package/dist/esm/animation/characterTypes.js +204 -202
  154. package/dist/esm/animation/choreoDSL.js +212 -218
  155. package/dist/esm/animation/choreography.js +8 -31
  156. package/dist/esm/animation/karaokeIntegration.js +74 -76
  157. package/dist/esm/animation/lottieExport.js +112 -113
  158. package/dist/esm/animation/motionPath.js +235 -247
  159. package/dist/esm/animation/physics.js +235 -253
  160. package/dist/esm/animation/rig/RigPlayer.js +53 -54
  161. package/dist/esm/animation/rig/RiggedBody.js +71 -24
  162. package/dist/esm/animation/rig/choreoRigBridge.js +74 -76
  163. package/dist/esm/animation/rig/ik.js +14 -17
  164. package/dist/esm/animation/rig/poseOps.js +37 -40
  165. package/dist/esm/animation/rig/presets.js +70 -71
  166. package/dist/esm/animation/rig/rigMath.js +28 -31
  167. package/dist/esm/animation/rig/rigTypes.js +62 -62
  168. package/dist/esm/animation/shapes/arms.js +32 -12
  169. package/dist/esm/animation/shapes/eyes.js +135 -46
  170. package/dist/esm/animation/shapes/hair.js +80 -44
  171. package/dist/esm/animation/shapes/heads.js +140 -56
  172. package/dist/esm/animation/shapes/headwear.js +54 -37
  173. package/dist/esm/animation/shapes/legs.js +32 -12
  174. package/dist/esm/animation/shapes/mouths.js +127 -43
  175. package/dist/esm/animation/shapes/noses.js +87 -24
  176. package/dist/esm/animation/shapes/outfits.js +106 -38
  177. package/dist/esm/animation/shapes/torsos.js +16 -10
  178. package/dist/esm/animation/spineExport.js +106 -120
  179. package/dist/esm/core/LocalUI.js +111 -42
  180. package/dist/esm/core/NiceBrushEngine.js +761 -551
  181. package/dist/esm/core/collaboration.js +255 -250
  182. package/dist/esm/core/colorBlindness.js +106 -114
  183. package/dist/esm/core/dragDrop.js +157 -157
  184. package/dist/esm/core/editorAsk.js +53 -40
  185. package/dist/esm/core/fixes.js +145 -170
  186. package/dist/esm/core/gridGuides.js +178 -200
  187. package/dist/esm/core/historyVisual.js +158 -134
  188. package/dist/esm/core/i18n.js +15 -13
  189. package/dist/esm/core/integrations.js +244 -248
  190. package/dist/esm/core/minimap.js +151 -150
  191. package/dist/esm/core/plugins.js +102 -112
  192. package/dist/esm/core/rtl.js +77 -82
  193. package/dist/esm/core/shortcuts.js +200 -224
  194. package/dist/esm/core/theme.js +150 -148
  195. package/dist/esm/core/yjsCollaboration.js +240 -292
  196. package/dist/esm/design/design-tools.js +13 -21
  197. package/dist/esm/font/NiceFontEditor.js +880 -315
  198. package/dist/esm/font/fontFormatUtils.js +394 -447
  199. package/dist/esm/game/GameAsset2dEditor.js +1202 -391
  200. package/dist/esm/game/GameAsset2dEditor.module.css.js +146 -3
  201. package/dist/esm/game/gameAssetTypes.js +8 -15
  202. package/dist/esm/game/gameAssetUtils.js +408 -412
  203. package/dist/esm/game/useGameAssetEditor.js +821 -762
  204. package/dist/esm/index.js +332 -69
  205. package/dist/esm/nice2dev-ui-graphics.css +1 -1
  206. package/dist/esm/node_modules/lib0/array.js +15 -69
  207. package/dist/esm/node_modules/lib0/binary.js +14 -19
  208. package/dist/esm/node_modules/lib0/broadcastchannel.js +27 -85
  209. package/dist/esm/node_modules/lib0/buffer.js +20 -80
  210. package/dist/esm/node_modules/lib0/conditions.js +4 -15
  211. package/dist/esm/node_modules/lib0/decoding.js +128 -286
  212. package/dist/esm/node_modules/lib0/dom.js +20 -43
  213. package/dist/esm/node_modules/lib0/encoding.js +90 -415
  214. package/dist/esm/node_modules/lib0/environment.js +33 -91
  215. package/dist/esm/node_modules/lib0/error.js +8 -28
  216. package/dist/esm/node_modules/lib0/function.js +31 -65
  217. package/dist/esm/node_modules/lib0/iterator.js +11 -32
  218. package/dist/esm/node_modules/lib0/logging.common.js +22 -21
  219. package/dist/esm/node_modules/lib0/logging.js +44 -70
  220. package/dist/esm/node_modules/lib0/map.js +21 -85
  221. package/dist/esm/node_modules/lib0/math.js +9 -31
  222. package/dist/esm/node_modules/lib0/number.js +7 -14
  223. package/dist/esm/node_modules/lib0/object.js +26 -87
  224. package/dist/esm/node_modules/lib0/observable.js +40 -77
  225. package/dist/esm/node_modules/lib0/pair.js +5 -19
  226. package/dist/esm/node_modules/lib0/prng.js +15 -76
  227. package/dist/esm/node_modules/lib0/promise.js +7 -29
  228. package/dist/esm/node_modules/lib0/random.js +10 -22
  229. package/dist/esm/node_modules/lib0/schema.js +367 -686
  230. package/dist/esm/node_modules/lib0/set.js +4 -9
  231. package/dist/esm/node_modules/lib0/storage.js +23 -54
  232. package/dist/esm/node_modules/lib0/string.js +26 -71
  233. package/dist/esm/node_modules/lib0/symbol.js +3 -11
  234. package/dist/esm/node_modules/lib0/time.js +3 -14
  235. package/dist/esm/node_modules/lib0/trait/equality.js +5 -27
  236. package/dist/esm/node_modules/lib0/url.js +5 -17
  237. package/dist/esm/node_modules/lib0/webcrypto.js +3 -4
  238. package/dist/esm/node_modules/y-protocols/auth.js +7 -18
  239. package/dist/esm/node_modules/y-protocols/awareness.js +58 -115
  240. package/dist/esm/node_modules/y-protocols/sync.js +26 -102
  241. package/dist/esm/node_modules/y-websocket/src/y-websocket.js +82 -218
  242. package/dist/esm/node_modules/yjs/dist/yjs.js +2263 -4899
  243. package/dist/esm/photo/FilterThumb.js +37 -23
  244. package/dist/esm/photo/NiceAITools.js +685 -466
  245. package/dist/esm/photo/PhotoEditor.js +34 -18
  246. package/dist/esm/photo/PhotoEditor.module.css.js +296 -3
  247. package/dist/esm/photo/PhotoEditorCanvas.js +250 -89
  248. package/dist/esm/photo/PhotoEditorLeftPanel.js +1086 -222
  249. package/dist/esm/photo/PhotoEditorToolbar.js +135 -17
  250. package/dist/esm/photo/photoEditorActions.js +93 -71
  251. package/dist/esm/photo/photoEditorTypes.js +32 -31
  252. package/dist/esm/photo/photoFilters.js +967 -984
  253. package/dist/esm/photo/photoOverlays.js +913 -944
  254. package/dist/esm/photo/photoSelection.js +438 -498
  255. package/dist/esm/photo/usePhotoEditor.js +740 -740
  256. package/dist/esm/pixel/HSVPicker.js +192 -174
  257. package/dist/esm/pixel/PixelEditor.js +160 -47
  258. package/dist/esm/pixel/PixelEditor.module.css.js +209 -3
  259. package/dist/esm/pixel/PixelEditorCanvas.js +29 -14
  260. package/dist/esm/pixel/PixelEditorMenuBar.js +160 -160
  261. package/dist/esm/pixel/PixelEditorRightPanel.js +377 -43
  262. package/dist/esm/pixel/PixelEditorStatusBar.js +47 -18
  263. package/dist/esm/pixel/PixelEditorTimeline.js +106 -21
  264. package/dist/esm/pixel/PixelEditorToolbar.js +99 -19
  265. package/dist/esm/pixel/SpriteGeneratorPanel.js +483 -301
  266. package/dist/esm/pixel/animalGeneratorPresets.js +782 -600
  267. package/dist/esm/pixel/asepriteFormat.js +517 -559
  268. package/dist/esm/pixel/buildingGeneratorPresets.js +699 -519
  269. package/dist/esm/pixel/characterGeneratorPresets.js +650 -543
  270. package/dist/esm/pixel/pixel-pro.js +53 -62
  271. package/dist/esm/pixel/pixelEditorExports.js +397 -427
  272. package/dist/esm/pixel/pixelEditorTypes.js +103 -107
  273. package/dist/esm/pixel/pixelEditorUtils.js +551 -560
  274. package/dist/esm/pixel/spriteGeneratorCore.js +223 -256
  275. package/dist/esm/pixel/usePixelEditor.js +1322 -1342
  276. package/dist/esm/texture/Nice3DTexturePainter.js +709 -439
  277. package/dist/esm/ui/NiceUIDesigner.js +957 -526
  278. package/dist/esm/vector/NiceSvgToCode.js +589 -445
  279. package/dist/esm/vector/VectorEditor.js +291 -116
  280. package/dist/esm/vector/VectorEditor.module.css.js +86 -3
  281. package/dist/esm/vector/VectorEditorMenuBar.js +333 -273
  282. package/dist/esm/vector/VectorEditorRightPanel.js +1094 -369
  283. package/dist/esm/vector/VectorEditorShapeRenderer.js +199 -59
  284. package/dist/esm/vector/VectorEditorStatusBar.js +39 -11
  285. package/dist/esm/vector/useVectorEditor.js +849 -888
  286. package/dist/esm/vector/vectorAnimation.js +115 -136
  287. package/dist/esm/vector/vectorBooleanOps.js +401 -484
  288. package/dist/esm/vector/vectorEditorExport.js +207 -202
  289. package/dist/esm/vector/vectorEditorImport.js +174 -188
  290. package/dist/esm/vector/vectorEditorTypes.js +50 -48
  291. package/dist/esm/vector/vectorGradients.js +158 -173
  292. package/dist/esm/vector/vectorIconExport.js +53 -58
  293. package/dist/esm/vector/vectorIconImport.js +171 -218
  294. package/package.json +9 -15
@@ -1,378 +1,1103 @@
1
- import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
- import { NiceButton, NiceColorPicker, NiceTextInput, NiceSelect, NiceSlider, NiceNumberInput, NiceTabs } from '@nice2dev/ui';
3
- import { clickOnActivationKey, NiceIcon } from '@nice2dev/ui-core';
4
- import React, { useState } from 'react';
5
- import { useNiceTranslation } from '../core/i18n.js';
6
- import { ANIMATION_PRESETS, animationToCSSKeyframes, animationToCSSRule } from './vectorAnimation.js';
7
- import styles from './VectorEditor.module.css.js';
8
- import { FONTS } from './vectorEditorTypes.js';
9
- import { PRESET_GRADIENTS, gradientToCSS, gradientId, cloneGradient } from './vectorGradients.js';
10
- import { iconComponentToShapes } from './vectorIconImport.js';
11
-
1
+ import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
+ import { NiceButton, NiceColorPicker, NiceTextInput, NiceSelect, NiceSlider, NiceNumberInput, NiceTabs } from "@nice2dev/ui";
3
+ import { clickOnActivationKey, NiceIcon } from "@nice2dev/ui-core";
4
+ import React, { useState } from "react";
5
+ import { useNiceTranslation } from "../core/i18n.js";
6
+ import { ANIMATION_PRESETS, animationToCSSKeyframes, animationToCSSRule } from "./vectorAnimation.js";
7
+ import styles from "./VectorEditor.module.css.js";
8
+ import { FONTS } from "./vectorEditorTypes.js";
9
+ import { PRESET_GRADIENTS, gradientToCSS, gradientId, cloneGradient } from "./vectorGradients.js";
10
+ import { iconComponentToShapes } from "./vectorIconImport.js";
12
11
  const VectorEditorRightPanel$1 = ({ api, library, artW = 512, artH = 512 }) => {
13
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
14
- const { t } = useNiceTranslation();
15
- const { tool, selId, selShape, shapes, fillCol, strokeCol, strokeW, shapeOpacity, cornerRadius, polygonSides, starPoints2, starInnerRatio, fontSize, fontFamily, snapGrid, gridSize, setFillCol, setStrokeCol, setStrokeW, setShapeOpacity, setCornerRadius, setPolygonSides, setStarPoints2, setStarInnerRatio, setFontSize, setFontFamily, setGridSize, updateSel, setSelId, setTool, setShapes, snap, animations, isPlaying, setShapeAnimation, setIsPlaying, setPreviewTime, } = api;
16
- // Gradient editing state
17
- const [fillMode, setFillMode] = useState('solid');
18
- const [selectedGradient, setSelectedGradient] = useState(null);
19
- const [showGradientEditor, setShowGradientEditor] = useState(false);
20
- // Library: insert the next picked icon as a small top-right corner badge
21
- // (ports the badge/overlay feature from the retired NiceIconEditor).
22
- const [asBadge, setAsBadge] = useState(false);
23
- const applyGradient = (gradient) => {
24
- const newGrad = { ...cloneGradient(gradient), id: gradientId() };
25
- setSelectedGradient(newGrad);
26
- // Store gradient in shape as serialized reference
27
- updateSel({
28
- fill: `url(#${newGrad.id})`,
29
- // Store gradient data as custom property (will be used in SVG export)
30
- });
31
- };
32
- const propertiesContent = (jsxs("div", { className: styles.rightPanelTabBody, children: [jsxs("div", { className: styles.panelSection, children: [jsx("div", { className: styles.panelTitle, children: jsx("span", { children: t('vectorEditor.properties.appearance', 'Appearance') }) }), jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('vectorEditor.properties.fill', 'Fill') }), jsxs("div", { style: { display: 'flex', gap: 4 }, children: [jsx(NiceButton, { className: `${styles.menuBtn} ${fillMode === 'solid' ? styles.menuBtnActive : ''}`, style: { padding: '2px 6px', fontSize: 10 }, variant: fillMode === 'solid' ? 'primary' : 'ghost', size: "sm", onClick: () => setFillMode('solid'), children: t('vectorEditor.properties.solid', 'Solid') }), jsx(NiceButton, { className: `${styles.menuBtn} ${fillMode === 'gradient' ? styles.menuBtnActive : ''}`, style: { padding: '2px 6px', fontSize: 10 }, variant: fillMode === 'gradient' ? 'primary' : 'ghost', size: "sm", onClick: () => setFillMode('gradient'), children: t('vectorEditor.properties.gradient', 'Gradient') })] })] }), fillMode === 'solid' ? (jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel }), jsx("div", { className: styles.propColorSwatch, style: { background: fillCol }, children: jsx(NiceColorPicker, { value: fillCol, showInput: false, showRandom: false, onChange: (c) => {
33
- setFillCol(c);
34
- updateSel({ fill: c });
35
- } }) }), jsx(NiceTextInput, { className: styles.propInput, value: (selShape === null || selShape === void 0 ? void 0 : selShape.fill) || fillCol, onChange: (val) => {
36
- setFillCol(val);
37
- updateSel({ fill: val });
38
- } })] })) : (jsxs(Fragment, { children: [jsx("div", { className: styles.propRow, style: { flexWrap: 'wrap', gap: 4 }, children: PRESET_GRADIENTS.map((grad, i) => (jsx("div", { role: "button", tabIndex: 0, onKeyDown: clickOnActivationKey, style: {
39
- width: 24,
40
- height: 24,
41
- borderRadius: 4,
42
- background: gradientToCSS(grad),
43
- cursor: 'pointer',
44
- border: (selectedGradient === null || selectedGradient === void 0 ? void 0 : selectedGradient.id) === grad.id
45
- ? '2px solid var(--nice-bg, #FFF)'
46
- : '1px solid var(--nice-border-color, #444)',
47
- }, onClick: () => applyGradient(grad), title: t('vectorEditor.gradient', '{type} gradient').replace('{type}', String(grad.type)) }, i))) }), jsx("div", { className: styles.propRow, children: jsxs(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", style: { width: '100%', fontSize: 10 }, onClick: () => setShowGradientEditor(!showGradientEditor), children: [jsx(NiceIcon, { name: showGradientEditor ? 'chevron-down' : 'chevron-right', size: 14 }), ' ', showGradientEditor
48
- ? t('vectorEditor.properties.hideEditor', 'Hide Editor')
49
- : t('vectorEditor.properties.editGradient', 'Edit Gradient')] }) }), showGradientEditor && selectedGradient && (jsxs("div", { style: {
50
- padding: 4,
51
- background: 'var(--nice-bg-secondary, #252525)',
52
- borderRadius: 4,
53
- margin: '4px 0',
54
- }, children: [jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('vectorEditor.properties.type', 'Type') }), jsx(NiceSelect, { className: styles.propInput, value: selectedGradient.type, onChange: (val) => {
55
- const type = val;
56
- let newGrad;
57
- if (type === 'linear') {
58
- newGrad = {
59
- type: 'linear',
60
- id: gradientId(),
61
- angle: 0,
62
- stops: selectedGradient.stops,
63
- };
64
- }
65
- else {
66
- newGrad = {
67
- type: 'radial',
68
- id: gradientId(),
69
- cx: 0.5,
70
- cy: 0.5,
71
- r: 0.7,
72
- stops: selectedGradient.stops,
73
- };
74
- }
75
- setSelectedGradient(newGrad);
76
- updateSel({ fill: `url(#${newGrad.id})` });
77
- }, options: [
78
- { value: 'linear', label: t('vectorEditor.properties.linear', 'Linear') },
79
- { value: 'radial', label: t('vectorEditor.properties.radial', 'Radial') },
80
- ] })] }), selectedGradient.type === 'linear' && (jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('vectorEditor.properties.angle', 'Angle') }), jsx(NiceSlider, { className: styles.propInput, min: 0, max: 360, value: selectedGradient.angle, onChange: (val) => {
81
- const newGrad = {
82
- ...selectedGradient,
83
- id: gradientId(),
84
- angle: val,
85
- };
86
- setSelectedGradient(newGrad);
87
- updateSel({ fill: `url(#${newGrad.id})` });
88
- } }), jsxs("span", { style: { fontSize: 10, minWidth: 28 }, children: [selectedGradient.angle, "\u00B0"] })] })), selectedGradient.type === 'radial' && (jsxs(Fragment, { children: [jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('vectorEditor.properties.cx', 'CX') }), jsx(NiceSlider, { className: styles.propInput, min: 0, max: 100, value: selectedGradient.cx * 100, onChange: (val) => {
89
- const newGrad = {
90
- ...selectedGradient,
91
- id: gradientId(),
92
- cx: val / 100,
93
- };
94
- setSelectedGradient(newGrad);
95
- updateSel({ fill: `url(#${newGrad.id})` });
96
- } })] }), jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('vectorEditor.properties.cy', 'CY') }), jsx(NiceSlider, { className: styles.propInput, min: 0, max: 100, value: selectedGradient.cy * 100, onChange: (val) => {
97
- const newGrad = {
98
- ...selectedGradient,
99
- id: gradientId(),
100
- cy: val / 100,
101
- };
102
- setSelectedGradient(newGrad);
103
- updateSel({ fill: `url(#${newGrad.id})` });
104
- } })] }), jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('vectorEditor.properties.r', 'R') }), jsx(NiceSlider, { className: styles.propInput, min: 10, max: 100, value: selectedGradient.r * 100, onChange: (val) => {
105
- const newGrad = {
106
- ...selectedGradient,
107
- id: gradientId(),
108
- r: val / 100,
109
- };
110
- setSelectedGradient(newGrad);
111
- updateSel({ fill: `url(#${newGrad.id})` });
112
- } })] })] })), jsx("div", { style: { fontSize: 10, color: 'var(--nice-text-secondary, #888)', marginTop: 4 }, children: t('vectorEditor.properties.colorStops', 'Color Stops:') }), selectedGradient.stops.map((stop, i) => (jsxs("div", { className: styles.propRow, children: [jsx(NiceColorPicker, { value: stop.color, showInput: false, showRandom: false, style: { width: 24, height: 20 }, onChange: (color) => {
113
- const newStops = [...selectedGradient.stops];
114
- newStops[i] = { ...newStops[i], color };
115
- const newGrad = { ...selectedGradient, id: gradientId(), stops: newStops };
116
- setSelectedGradient(newGrad);
117
- updateSel({ fill: `url(#${newGrad.id})` });
118
- } }), jsx(NiceSlider, { min: 0, max: 100, value: stop.offset * 100, style: { flex: 1 }, onChange: (val) => {
119
- const newStops = [...selectedGradient.stops];
120
- newStops[i] = { ...newStops[i], offset: val / 100 };
121
- newStops.sort((a, b) => a.offset - b.offset);
122
- const newGrad = { ...selectedGradient, id: gradientId(), stops: newStops };
123
- setSelectedGradient(newGrad);
124
- updateSel({ fill: `url(#${newGrad.id})` });
125
- } }), jsxs("span", { style: { fontSize: 9, minWidth: 24 }, children: [Math.round(stop.offset * 100), "%"] })] }, i))), jsxs("div", { className: styles.propRow, style: { gap: 4 }, children: [jsx(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", style: { fontSize: 10, flex: 1 }, onClick: () => {
126
- const newStops = [
127
- ...selectedGradient.stops,
128
- { offset: 0.5, color: 'var(--nice-text-secondary, #888888)' },
129
- ];
130
- newStops.sort((a, b) => a.offset - b.offset);
131
- const newGrad = { ...selectedGradient, id: gradientId(), stops: newStops };
132
- setSelectedGradient(newGrad);
133
- updateSel({ fill: `url(#${newGrad.id})` });
134
- }, children: t('vectorEditor.properties.addStop', '+ Add Stop') }), jsx(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", style: { fontSize: 10, flex: 1 }, disabled: selectedGradient.stops.length <= 2, onClick: () => {
135
- if (selectedGradient.stops.length <= 2) {
136
- return;
137
- }
138
- const newStops = selectedGradient.stops.slice(0, -1);
139
- const newGrad = { ...selectedGradient, id: gradientId(), stops: newStops };
140
- setSelectedGradient(newGrad);
141
- updateSel({ fill: `url(#${newGrad.id})` });
142
- }, children: t('vectorEditor.properties.removeStop', '- Remove') })] })] }))] })), jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('vectorEditor.properties.stroke', 'Stroke') }), jsx("div", { className: styles.propColorSwatch, style: { background: strokeCol }, children: jsx(NiceColorPicker, { value: strokeCol, showInput: false, showRandom: false, onChange: (c) => {
143
- setStrokeCol(c);
144
- updateSel({ stroke: c });
145
- } }) }), jsx(NiceTextInput, { className: styles.propInput, value: (selShape === null || selShape === void 0 ? void 0 : selShape.stroke) || strokeCol, onChange: (val) => {
146
- setStrokeCol(val);
147
- updateSel({ stroke: val });
148
- } })] }), jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('vectorEditor.properties.width', 'Width') }), jsx(NiceNumberInput, { className: styles.propInput, min: 0, max: 100, value: (_a = selShape === null || selShape === void 0 ? void 0 : selShape.strokeWidth) !== null && _a !== void 0 ? _a : strokeW, onChange: (val) => {
149
- const v = val !== null && val !== void 0 ? val : 0;
150
- setStrokeW(v);
151
- updateSel({ strokeWidth: v });
152
- } })] }), jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('vectorEditor.properties.opacity', 'Opacity') }), jsx(NiceSlider, { className: styles.propInput, min: 0, max: 1, step: 0.01, value: (_b = selShape === null || selShape === void 0 ? void 0 : selShape.opacity) !== null && _b !== void 0 ? _b : shapeOpacity, onChange: (val) => {
153
- setShapeOpacity(val);
154
- updateSel({ opacity: val });
155
- } }), jsxs("span", { style: { fontSize: 10, minWidth: 28 }, children: [Math.round(((_c = selShape === null || selShape === void 0 ? void 0 : selShape.opacity) !== null && _c !== void 0 ? _c : shapeOpacity) * 100), "%"] })] }), jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('vectorEditor.properties.rotation', 'Rotation') }), jsx(NiceNumberInput, { className: styles.propInput, min: -360, max: 360, value: (_d = selShape === null || selShape === void 0 ? void 0 : selShape.rotation) !== null && _d !== void 0 ? _d : 0, onChange: (val) => updateSel({ rotation: val !== null && val !== void 0 ? val : 0 }) }), jsx("span", { style: { fontSize: 10 }, children: "\u00B0" })] })] }), jsxs("div", { className: styles.panelSection, children: [jsx("div", { className: styles.panelTitle, children: jsx("span", { children: t('vectorEditor.properties.toolOptions', 'Tool Options') }) }), (tool === 'roundrect' || (selShape === null || selShape === void 0 ? void 0 : selShape.type) === 'roundrect') && (jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('vectorEditor.properties.radius', 'Radius') }), jsx(NiceNumberInput, { className: styles.propInput, min: 0, max: 200, value: (_e = selShape === null || selShape === void 0 ? void 0 : selShape.rx) !== null && _e !== void 0 ? _e : cornerRadius, onChange: (val) => {
156
- const v = val !== null && val !== void 0 ? val : 0;
157
- setCornerRadius(v);
158
- updateSel({ rx: v });
159
- } })] })), (tool === 'polygon' || (selShape === null || selShape === void 0 ? void 0 : selShape.type) === 'polygon') && (jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('vectorEditor.properties.sides', 'Sides') }), jsx(NiceNumberInput, { className: styles.propInput, min: 3, max: 32, value: (_f = selShape === null || selShape === void 0 ? void 0 : selShape.sides) !== null && _f !== void 0 ? _f : polygonSides, onChange: (val) => {
160
- const v = val !== null && val !== void 0 ? val : 3;
161
- setPolygonSides(v);
162
- updateSel({ sides: v });
163
- } })] })), (tool === 'star' || (selShape === null || selShape === void 0 ? void 0 : selShape.type) === 'star') && (jsxs(Fragment, { children: [jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('vectorEditor.properties.points', 'Points') }), jsx(NiceNumberInput, { className: styles.propInput, min: 3, max: 32, value: (_g = selShape === null || selShape === void 0 ? void 0 : selShape.sides) !== null && _g !== void 0 ? _g : starPoints2, onChange: (val) => {
164
- const v = val !== null && val !== void 0 ? val : 3;
165
- setStarPoints2(v);
166
- updateSel({ sides: v });
167
- } })] }), jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('vectorEditor.properties.innerR', 'Inner R') }), jsx(NiceSlider, { className: styles.propInput, min: 0.1, max: 0.9, step: 0.05, value: (_h = selShape === null || selShape === void 0 ? void 0 : selShape.innerRadius) !== null && _h !== void 0 ? _h : starInnerRatio, onChange: (val) => {
168
- setStarInnerRatio(val);
169
- updateSel({ innerRadius: val });
170
- } }), jsxs("span", { style: { fontSize: 10 }, children: [Math.round(((_j = selShape === null || selShape === void 0 ? void 0 : selShape.innerRadius) !== null && _j !== void 0 ? _j : starInnerRatio) * 100), "%"] })] })] })), (tool === 'text' || (selShape === null || selShape === void 0 ? void 0 : selShape.type) === 'text') && (jsxs(Fragment, { children: [jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('vectorEditor.properties.size', 'Size') }), jsx(NiceNumberInput, { className: styles.propInput, min: 8, max: 200, value: (_k = selShape === null || selShape === void 0 ? void 0 : selShape.fontSize) !== null && _k !== void 0 ? _k : fontSize, onChange: (val) => {
171
- const v = val !== null && val !== void 0 ? val : 12;
172
- setFontSize(v);
173
- updateSel({ fontSize: v });
174
- } })] }), jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('vectorEditor.properties.font', 'Font') }), jsx(NiceSelect, { className: styles.propInput, value: (_l = selShape === null || selShape === void 0 ? void 0 : selShape.fontFamily) !== null && _l !== void 0 ? _l : fontFamily, onChange: (val) => {
175
- setFontFamily(val);
176
- updateSel({ fontFamily: val });
177
- }, options: FONTS.map((f) => ({ value: f, label: f })) })] })] })), snapGrid && (jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('vectorEditor.properties.grid', 'Grid') }), jsx(NiceNumberInput, { className: styles.propInput, min: 4, max: 128, value: gridSize, onChange: (val) => setGridSize(val !== null && val !== void 0 ? val : 8) })] }))] }), selShape && (jsxs("div", { className: styles.panelSection, children: [jsx("div", { className: styles.panelTitle, children: jsx("span", { children: t('vectorEditor.properties.transform', 'Transform') }) }), jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('vectorEditor.properties.x', 'X') }), jsx(NiceNumberInput, { className: styles.propInput, value: Math.round(selShape.x), onChange: (val) => updateSel({ x: val !== null && val !== void 0 ? val : 0 }) })] }), jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('vectorEditor.properties.y', 'Y') }), jsx(NiceNumberInput, { className: styles.propInput, value: Math.round(selShape.y), onChange: (val) => updateSel({ y: val !== null && val !== void 0 ? val : 0 }) })] }), jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('vectorEditor.properties.w', 'W') }), jsx(NiceNumberInput, { className: styles.propInput, value: Math.round(selShape.w), onChange: (val) => updateSel({ w: val !== null && val !== void 0 ? val : 0 }) })] }), jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('vectorEditor.properties.h', 'H') }), jsx(NiceNumberInput, { className: styles.propInput, value: Math.round(selShape.h), onChange: (val) => updateSel({ h: val !== null && val !== void 0 ? val : 0 }) })] })] }))] }));
178
- // Insert an icon as editable shapes. `badge` scales it down and pins it to the
179
- // top-right corner (the former NiceIconEditor "badge/overlay" feature).
180
- const insertIcon = (Icon, badge) => {
181
- var _a, _b;
182
- if (badge) {
183
- const box = Math.round(Math.min(artW, artH) * 0.42);
184
- const made = iconComponentToShapes(Icon, { artW: box, artH: box, margin: 4 });
185
- if (made.length === 0) {
186
- return;
12
+ const { t } = useNiceTranslation();
13
+ const {
14
+ tool,
15
+ selId,
16
+ selShape,
17
+ shapes,
18
+ fillCol,
19
+ strokeCol,
20
+ strokeW,
21
+ shapeOpacity,
22
+ cornerRadius,
23
+ polygonSides,
24
+ starPoints2,
25
+ starInnerRatio,
26
+ fontSize,
27
+ fontFamily,
28
+ snapGrid,
29
+ gridSize,
30
+ setFillCol,
31
+ setStrokeCol,
32
+ setStrokeW,
33
+ setShapeOpacity,
34
+ setCornerRadius,
35
+ setPolygonSides,
36
+ setStarPoints2,
37
+ setStarInnerRatio,
38
+ setFontSize,
39
+ setFontFamily,
40
+ setGridSize,
41
+ updateSel,
42
+ setSelId,
43
+ setTool,
44
+ setShapes,
45
+ snap,
46
+ animations,
47
+ isPlaying,
48
+ setShapeAnimation,
49
+ setIsPlaying,
50
+ setPreviewTime
51
+ } = api;
52
+ const [fillMode, setFillMode] = useState("solid");
53
+ const [selectedGradient, setSelectedGradient] = useState(null);
54
+ const [showGradientEditor, setShowGradientEditor] = useState(false);
55
+ const [asBadge, setAsBadge] = useState(false);
56
+ const applyGradient = (gradient) => {
57
+ const newGrad = { ...cloneGradient(gradient), id: gradientId() };
58
+ setSelectedGradient(newGrad);
59
+ updateSel({
60
+ fill: `url(#${newGrad.id})`
61
+ // Store gradient data as custom property (will be used in SVG export)
62
+ });
63
+ };
64
+ const propertiesContent = /* @__PURE__ */ jsxs("div", { className: styles.rightPanelTabBody, children: [
65
+ /* @__PURE__ */ jsxs("div", { className: styles.panelSection, children: [
66
+ /* @__PURE__ */ jsx("div", { className: styles.panelTitle, children: /* @__PURE__ */ jsx("span", { children: t("vectorEditor.properties.appearance", "Appearance") }) }),
67
+ /* @__PURE__ */ jsxs("div", { className: styles.propRow, children: [
68
+ /* @__PURE__ */ jsx("span", { className: styles.propLabel, children: t("vectorEditor.properties.fill", "Fill") }),
69
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 4 }, children: [
70
+ /* @__PURE__ */ jsx(
71
+ NiceButton,
72
+ {
73
+ className: `${styles.menuBtn} ${fillMode === "solid" ? styles.menuBtnActive : ""}`,
74
+ style: { padding: "2px 6px", fontSize: 10 },
75
+ variant: fillMode === "solid" ? "primary" : "ghost",
76
+ size: "sm",
77
+ onClick: () => setFillMode("solid"),
78
+ children: t("vectorEditor.properties.solid", "Solid")
187
79
  }
188
- const dx = artW - box;
189
- const offset = made.map((s) => ({
190
- ...s,
191
- x: s.x + dx,
192
- ...(s.x2 != null ? { x2: s.x2 + dx } : {}),
193
- ...(s.points ? { points: s.points.map((p) => ({ ...p, x: p.x + dx })) } : {}),
194
- }));
195
- const next = [...shapes, ...offset];
196
- setShapes(next);
197
- snap(next);
198
- if ((_a = offset[0]) === null || _a === void 0 ? void 0 : _a.id) {
199
- setSelId(offset[0].id);
80
+ ),
81
+ /* @__PURE__ */ jsx(
82
+ NiceButton,
83
+ {
84
+ className: `${styles.menuBtn} ${fillMode === "gradient" ? styles.menuBtnActive : ""}`,
85
+ style: { padding: "2px 6px", fontSize: 10 },
86
+ variant: fillMode === "gradient" ? "primary" : "ghost",
87
+ size: "sm",
88
+ onClick: () => setFillMode("gradient"),
89
+ children: t("vectorEditor.properties.gradient", "Gradient")
200
90
  }
201
- setTool('pointer');
202
- return;
203
- }
204
- const inserted = iconComponentToShapes(Icon, { artW, artH });
205
- if (inserted.length === 0) {
206
- return;
207
- }
208
- const next = [...shapes, ...inserted];
209
- setShapes(next);
210
- snap(next);
211
- if ((_b = inserted[0]) === null || _b === void 0 ? void 0 : _b.id) {
212
- setSelId(inserted[0].id);
213
- }
214
- setTool('pointer');
215
- };
216
- const reorderShape = (id, dir) => {
217
- const idx = shapes.findIndex((s) => s.id === id);
218
- if (idx < 0) {
219
- return;
220
- }
221
- const next = shapes.slice();
222
- const target = idx + dir;
223
- if (target < 0 || target >= next.length) {
224
- return;
225
- }
226
- [next[idx], next[target]] = [next[target], next[idx]];
227
- setShapes(next);
228
- snap(next);
229
- };
230
- const layersContent = (jsx("div", { className: styles.rightPanelTabBody, children: jsxs("div", { className: styles.panelSection, children: [jsx("div", { className: styles.panelTitle, children: jsxs("span", { children: [t('vectorEditor.layers.title', 'Layers'), " (", shapes.length, ")"] }) }), [...shapes].reverse().map((s, revIdx) => {
231
- var _a;
232
- const realIdx = shapes.length - 1 - revIdx;
233
- const active = s.id === selId;
234
- return (jsxs("div", { role: "button", tabIndex: 0, onKeyDown: clickOnActivationKey, className: `${styles.layerRow} ${active ? styles.layerRowActive : ''} ${s.locked ? styles.layerRowLocked : ''}`, onClick: () => {
235
- setSelId(s.id);
236
- setTool('pointer');
237
- }, children: [jsx("button", { type: "button", className: styles.layerIconBtn, title: s.hidden
238
- ? t('vectorEditor.layers.show', 'Show')
239
- : t('vectorEditor.layers.hide', 'Hide'), onClick: (e) => {
240
- e.stopPropagation();
241
- const next = shapes.map((x) => (x.id === s.id ? { ...x, hidden: !x.hidden } : x));
242
- setShapes(next);
243
- snap(next);
244
- }, children: jsx(NiceIcon, { name: s.hidden ? 'eye-off' : 'eye', size: 14 }) }), jsx("button", { type: "button", className: styles.layerIconBtn, title: s.locked
245
- ? t('vectorEditor.layers.unlock', 'Unlock')
246
- : t('vectorEditor.layers.lock', 'Lock'), onClick: (e) => {
247
- e.stopPropagation();
248
- const next = shapes.map((x) => (x.id === s.id ? { ...x, locked: !x.locked } : x));
249
- setShapes(next);
250
- snap(next);
251
- }, children: jsx(NiceIcon, { name: "lock", size: 14, style: { opacity: s.locked ? 1 : 0.4 } }) }), jsx("span", { className: styles.shapeIcon, children: jsx(NiceIcon, { name: s.type === 'rect' || s.type === 'roundrect'
252
- ? 'square'
253
- : s.type === 'ellipse'
254
- ? 'circle'
255
- : s.type === 'line'
256
- ? 'line'
257
- : s.type === 'arrow'
258
- ? 'arrow-right'
259
- : s.type === 'polygon'
260
- ? 'hexagon'
261
- : s.type === 'star'
262
- ? 'star'
263
- : s.type === 'polyline'
264
- ? 'pen'
265
- : s.type === 'path'
266
- ? 'wand'
267
- : 'type', size: 14 }) }), jsx("input", { className: styles.layerNameInput, value: (_a = s.name) !== null && _a !== void 0 ? _a : s.type, disabled: s.locked, onClick: (e) => e.stopPropagation(), onChange: (e) => {
268
- const v = e.target.value;
269
- const next = shapes.map((x) => (x.id === s.id ? { ...x, name: v } : x));
270
- setShapes(next);
271
- }, onBlur: () => snap(shapes) }), jsx("button", { type: "button", className: styles.layerIconBtn, title: t('vectorEditor.layers.moveUp', 'Move up'), onClick: (e) => {
272
- e.stopPropagation();
273
- reorderShape(s.id, 1);
274
- }, disabled: realIdx === shapes.length - 1, children: jsx(NiceIcon, { name: "arrow-up", size: 14 }) }), jsx("button", { type: "button", className: styles.layerIconBtn, title: t('vectorEditor.layers.moveDown', 'Move down'), onClick: (e) => {
275
- e.stopPropagation();
276
- reorderShape(s.id, -1);
277
- }, disabled: realIdx === 0, children: jsx(NiceIcon, { name: "arrow-down", size: 14 }) })] }, s.id));
278
- }), shapes.length === 0 && (jsx("div", { style: { color: 'var(--nice-text-secondary)', fontSize: 11, padding: 4 }, children: t('vectorEditor.layers.empty', 'No layers yet. Use the tools to draw, or insert from the Library tab.') }))] }) }));
279
- const libraryContent = (jsx("div", { className: styles.rightPanelTabBody, children: library && library.length > 0 ? (jsxs(Fragment, { children: [jsx("div", { className: styles.propRow, style: { padding: '4px 0' }, children: jsxs(NiceButton, { className: `${styles.menuBtn} ${asBadge ? styles.menuBtnActive : ''}`, variant: asBadge ? 'primary' : 'ghost', size: "sm", style: { width: '100%', fontSize: 11 }, onClick: () => setAsBadge((v) => !v), "aria-pressed": asBadge, children: [asBadge && jsx(NiceIcon, { name: "plus", size: 14 }), ' ', asBadge
280
- ? t('vectorEditor.library.insertAsBadgeOn', 'Inserting as corner badge')
281
- : t('vectorEditor.library.insertAsBadge', 'Insert as corner badge')] }) }), jsx("div", { className: styles.libraryGrid, children: library.map((entry) => {
282
- const Icon = entry.Component;
283
- return (jsxs("button", { type: "button", className: styles.libraryCell, title: entry.name, onClick: () => insertIcon(Icon, asBadge), children: [jsx("div", { style: {
284
- width: 28,
285
- height: 28,
286
- display: 'flex',
287
- alignItems: 'center',
288
- justifyContent: 'center',
289
- }, children: jsx(Icon, { size: "md" }) }), jsx("span", { children: entry.name })] }, entry.name));
290
- }) })] })) : (jsx("div", { style: { color: 'var(--nice-text-secondary)', fontSize: 11, padding: 12 }, children: t('vectorEditor.library.passProp', 'Pass a library prop to NiceVectorEditor to populate this tab with insertable icons.') })) }));
291
- const selAnim = selId ? animations[selId] : undefined;
292
- const animationCount = Object.keys(animations).length;
293
- const timelineContent = (jsxs("div", { className: styles.rightPanelTabBody, children: [jsxs("div", { className: styles.panelSection, children: [jsx("div", { className: styles.panelTitle, children: jsx("span", { children: t('vectorEditor.timeline.animationTitle', 'Animation') }) }), !selId ? (jsx("div", { style: { color: 'var(--nice-text-secondary)', fontSize: 11, padding: 4 }, children: t('vectorEditor.timeline.attachHint', 'Select a shape to attach an animation.') })) : (jsxs(Fragment, { children: [jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('vectorEditor.timeline.preset', 'Preset') }), jsx("div", { style: { display: 'flex', flexWrap: 'wrap', gap: 4 }, children: Object.keys(ANIMATION_PRESETS).map((key) => (jsx(NiceButton, { variant: "ghost", size: "sm", onClick: () => setShapeAnimation(selId, ANIMATION_PRESETS[key]), children: key }, key))) })] }), selAnim && (jsxs(Fragment, { children: [jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('vectorEditor.timeline.duration', 'Duration (ms)') }), jsx(NiceNumberInput, { value: selAnim.duration, min: 100, max: 10000, step: 100, onChange: (v) => setShapeAnimation(selId, {
294
- ...selAnim,
295
- duration: Number(v) || selAnim.duration,
296
- }) })] }), jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('vectorEditor.timeline.repeat', 'Repeat') }), jsx(NiceSelect, { value: selAnim.repeat === 'infinite' ? 'infinite' : String(selAnim.repeat), onChange: (v) => setShapeAnimation(selId, {
297
- ...selAnim,
298
- repeat: v === 'infinite' ? 'infinite' : Number(v),
299
- }), options: [
300
- { value: 'infinite', label: 'infinite' },
301
- { value: '1', label: '1x' },
302
- { value: '2', label: '2x' },
303
- { value: '3', label: '3x' },
304
- { value: '5', label: '5x' },
305
- ] })] }), jsxs("div", { className: styles.propRow, children: [jsx("span", { className: styles.propLabel, children: t('vectorEditor.timeline.direction', 'Direction') }), jsx(NiceSelect, { value: selAnim.direction, onChange: (v) => setShapeAnimation(selId, {
306
- ...selAnim,
307
- direction: v,
308
- }), options: [
309
- { value: 'normal', label: 'normal' },
310
- { value: 'alternate', label: 'alternate' },
311
- ] })] }), jsx("div", { className: styles.propRow, children: jsx(NiceButton, { variant: "ghost", size: "sm", onClick: () => setShapeAnimation(selId, null), children: t('vectorEditor.timeline.remove', 'Remove animation') }) }), jsx("div", { className: styles.panelTitle, style: { marginTop: 8 }, children: jsx("span", { children: t('vectorEditor.timeline.keyframes', 'Keyframes') }) }), selAnim.keyframes.map((k, i) => (jsxs("div", { className: styles.propRow, style: { flexDirection: 'column', alignItems: 'stretch', gap: 2 }, children: [jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 4 }, children: [jsx("span", { className: styles.propLabel, style: { minWidth: 24 }, children: t('vectorEditor.timeline.tLabel', 't') }), jsx(NiceSlider, { value: k.t, min: 0, max: 1, step: 0.01, onChange: (v) => {
312
- const nextKfs = selAnim.keyframes.map((kf, j) => j === i ? { ...kf, t: Number(v) } : kf);
313
- nextKfs.sort((a, b) => a.t - b.t);
314
- setShapeAnimation(selId, { ...selAnim, keyframes: nextKfs });
315
- } }), jsx(NiceButton, { variant: "ghost", size: "sm", disabled: selAnim.keyframes.length <= 2, onClick: () => {
316
- const nextKfs = selAnim.keyframes.filter((_, j) => j !== i);
317
- setShapeAnimation(selId, { ...selAnim, keyframes: nextKfs });
318
- }, "aria-label": t('vectorEditor.timeline.removeKeyframe', 'Remove keyframe'), children: jsx(NiceIcon, { name: "trash", size: 14 }) })] }), jsx("div", { style: { display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', gap: 2 }, children: ['rotate', 'scale', 'translateX', 'translateY', 'opacity'].map((field) => {
319
- var _a;
320
- return (jsx(NiceNumberInput, { value: (_a = k[field]) !== null && _a !== void 0 ? _a : 0, step: field === 'scale' || field === 'opacity' ? 0.1 : 1, placeholder: field[0].toUpperCase(), onChange: (v) => {
321
- const num = v == null ? undefined : Number(v);
322
- const nextKfs = selAnim.keyframes.map((kf, j) => j === i ? { ...kf, [field]: num } : kf);
323
- setShapeAnimation(selId, { ...selAnim, keyframes: nextKfs });
324
- } }, field));
325
- }) })] }, i))), jsx("div", { className: styles.propRow, children: jsx(NiceButton, { variant: "ghost", size: "sm", onClick: () => {
326
- const last = selAnim.keyframes[selAnim.keyframes.length - 1];
327
- const prev = selAnim.keyframes[selAnim.keyframes.length - 2];
328
- const newT = prev ? (prev.t + last.t) / 2 : Math.min(1, last.t + 0.1);
329
- const nextKfs = [...selAnim.keyframes, { ...last, t: newT }].sort((a, b) => a.t - b.t);
330
- setShapeAnimation(selId, { ...selAnim, keyframes: nextKfs });
331
- }, children: t('vectorEditor.timeline.addKeyframe', '+ Add keyframe') }) })] }))] }))] }), jsxs("div", { className: styles.panelSection, children: [jsx("div", { className: styles.panelTitle, children: jsx("span", { children: t('vectorEditor.timeline.playbackTitle', 'Playback') }) }), jsxs("div", { className: styles.propRow, children: [jsxs(NiceButton, { variant: isPlaying ? 'primary' : 'ghost', size: "sm", onClick: () => {
332
- if (isPlaying) {
333
- setIsPlaying(false);
334
- }
335
- else {
336
- setPreviewTime(0);
337
- setIsPlaying(true);
338
- }
339
- }, disabled: animationCount === 0, children: [jsx(NiceIcon, { name: isPlaying ? 'pause' : 'play', size: 14 }), ' ', isPlaying
340
- ? t('vectorEditor.timeline.pauseLabel', 'Pause')
341
- : t('vectorEditor.timeline.playLabel', 'Play')] }), jsxs(NiceButton, { variant: "ghost", size: "sm", onClick: () => {
342
- setIsPlaying(false);
343
- setPreviewTime(0);
344
- }, children: [jsx(NiceIcon, { name: "stop", size: 14 }), " ", t('vectorEditor.timeline.stopLabel', 'Stop')] })] }), animationCount > 0 && (jsx("div", { className: styles.propRow, children: jsxs(NiceButton, { variant: "ghost", size: "sm", onClick: () => {
345
- const lines = [];
346
- for (const [shapeId, a] of Object.entries(animations)) {
347
- const name = `anim-${shapeId}`;
348
- lines.push(animationToCSSKeyframes(name, a));
349
- lines.push(animationToCSSRule(`[data-shape="${shapeId}"]`, name, a));
350
- }
351
- const css = lines.join('\n\n');
352
- if (typeof navigator !== 'undefined' && navigator.clipboard) {
353
- navigator.clipboard.writeText(css).catch(() => { });
354
- }
355
- }, children: [jsx(NiceIcon, { name: "clipboard", size: 14 }), ' ', t('vectorEditor.timeline.copyCssKeyframes', 'Copy CSS keyframes')] }) }))] })] }));
356
- const tabs = [
91
+ )
92
+ ] })
93
+ ] }),
94
+ fillMode === "solid" ? /* @__PURE__ */ jsxs("div", { className: styles.propRow, children: [
95
+ /* @__PURE__ */ jsx("span", { className: styles.propLabel }),
96
+ /* @__PURE__ */ jsx("div", { className: styles.propColorSwatch, style: { background: fillCol }, children: /* @__PURE__ */ jsx(
97
+ NiceColorPicker,
98
+ {
99
+ value: fillCol,
100
+ showInput: false,
101
+ showRandom: false,
102
+ onChange: (c) => {
103
+ setFillCol(c);
104
+ updateSel({ fill: c });
105
+ }
106
+ }
107
+ ) }),
108
+ /* @__PURE__ */ jsx(
109
+ NiceTextInput,
110
+ {
111
+ className: styles.propInput,
112
+ value: selShape?.fill || fillCol,
113
+ onChange: (val) => {
114
+ setFillCol(val);
115
+ updateSel({ fill: val });
116
+ }
117
+ }
118
+ )
119
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
120
+ /* @__PURE__ */ jsx("div", { className: styles.propRow, style: { flexWrap: "wrap", gap: 4 }, children: PRESET_GRADIENTS.map((grad, i) => /* @__PURE__ */ jsx(
121
+ "div",
122
+ {
123
+ role: "button",
124
+ tabIndex: 0,
125
+ onKeyDown: clickOnActivationKey,
126
+ style: {
127
+ width: 24,
128
+ height: 24,
129
+ borderRadius: 4,
130
+ background: gradientToCSS(grad),
131
+ cursor: "pointer",
132
+ border: selectedGradient?.id === grad.id ? "2px solid var(--nice-bg, #FFF)" : "1px solid var(--nice-border-color, #444)"
133
+ },
134
+ onClick: () => applyGradient(grad),
135
+ title: t("vectorEditor.gradient", "{type} gradient").replace(
136
+ "{type}",
137
+ String(grad.type)
138
+ )
139
+ },
140
+ i
141
+ )) }),
142
+ /* @__PURE__ */ jsx("div", { className: styles.propRow, children: /* @__PURE__ */ jsxs(
143
+ NiceButton,
144
+ {
145
+ className: styles.menuBtn,
146
+ variant: "ghost",
147
+ size: "sm",
148
+ style: { width: "100%", fontSize: 10 },
149
+ onClick: () => setShowGradientEditor(!showGradientEditor),
150
+ children: [
151
+ /* @__PURE__ */ jsx(NiceIcon, { name: showGradientEditor ? "chevron-down" : "chevron-right", size: 14 }),
152
+ " ",
153
+ showGradientEditor ? t("vectorEditor.properties.hideEditor", "Hide Editor") : t("vectorEditor.properties.editGradient", "Edit Gradient")
154
+ ]
155
+ }
156
+ ) }),
157
+ showGradientEditor && selectedGradient && /* @__PURE__ */ jsxs(
158
+ "div",
159
+ {
160
+ style: {
161
+ padding: 4,
162
+ background: "var(--nice-bg-secondary, #252525)",
163
+ borderRadius: 4,
164
+ margin: "4px 0"
165
+ },
166
+ children: [
167
+ /* @__PURE__ */ jsxs("div", { className: styles.propRow, children: [
168
+ /* @__PURE__ */ jsx("span", { className: styles.propLabel, children: t("vectorEditor.properties.type", "Type") }),
169
+ /* @__PURE__ */ jsx(
170
+ NiceSelect,
171
+ {
172
+ className: styles.propInput,
173
+ value: selectedGradient.type,
174
+ onChange: (val) => {
175
+ const type = val;
176
+ let newGrad;
177
+ if (type === "linear") {
178
+ newGrad = {
179
+ type: "linear",
180
+ id: gradientId(),
181
+ angle: 0,
182
+ stops: selectedGradient.stops
183
+ };
184
+ } else {
185
+ newGrad = {
186
+ type: "radial",
187
+ id: gradientId(),
188
+ cx: 0.5,
189
+ cy: 0.5,
190
+ r: 0.7,
191
+ stops: selectedGradient.stops
192
+ };
193
+ }
194
+ setSelectedGradient(newGrad);
195
+ updateSel({ fill: `url(#${newGrad.id})` });
196
+ },
197
+ options: [
198
+ { value: "linear", label: t("vectorEditor.properties.linear", "Linear") },
199
+ { value: "radial", label: t("vectorEditor.properties.radial", "Radial") }
200
+ ]
201
+ }
202
+ )
203
+ ] }),
204
+ selectedGradient.type === "linear" && /* @__PURE__ */ jsxs("div", { className: styles.propRow, children: [
205
+ /* @__PURE__ */ jsx("span", { className: styles.propLabel, children: t("vectorEditor.properties.angle", "Angle") }),
206
+ /* @__PURE__ */ jsx(
207
+ NiceSlider,
208
+ {
209
+ className: styles.propInput,
210
+ min: 0,
211
+ max: 360,
212
+ value: selectedGradient.angle,
213
+ onChange: (val) => {
214
+ const newGrad = {
215
+ ...selectedGradient,
216
+ id: gradientId(),
217
+ angle: val
218
+ };
219
+ setSelectedGradient(newGrad);
220
+ updateSel({ fill: `url(#${newGrad.id})` });
221
+ }
222
+ }
223
+ ),
224
+ /* @__PURE__ */ jsxs("span", { style: { fontSize: 10, minWidth: 28 }, children: [
225
+ selectedGradient.angle,
226
+ "°"
227
+ ] })
228
+ ] }),
229
+ selectedGradient.type === "radial" && /* @__PURE__ */ jsxs(Fragment, { children: [
230
+ /* @__PURE__ */ jsxs("div", { className: styles.propRow, children: [
231
+ /* @__PURE__ */ jsx("span", { className: styles.propLabel, children: t("vectorEditor.properties.cx", "CX") }),
232
+ /* @__PURE__ */ jsx(
233
+ NiceSlider,
234
+ {
235
+ className: styles.propInput,
236
+ min: 0,
237
+ max: 100,
238
+ value: selectedGradient.cx * 100,
239
+ onChange: (val) => {
240
+ const newGrad = {
241
+ ...selectedGradient,
242
+ id: gradientId(),
243
+ cx: val / 100
244
+ };
245
+ setSelectedGradient(newGrad);
246
+ updateSel({ fill: `url(#${newGrad.id})` });
247
+ }
248
+ }
249
+ )
250
+ ] }),
251
+ /* @__PURE__ */ jsxs("div", { className: styles.propRow, children: [
252
+ /* @__PURE__ */ jsx("span", { className: styles.propLabel, children: t("vectorEditor.properties.cy", "CY") }),
253
+ /* @__PURE__ */ jsx(
254
+ NiceSlider,
255
+ {
256
+ className: styles.propInput,
257
+ min: 0,
258
+ max: 100,
259
+ value: selectedGradient.cy * 100,
260
+ onChange: (val) => {
261
+ const newGrad = {
262
+ ...selectedGradient,
263
+ id: gradientId(),
264
+ cy: val / 100
265
+ };
266
+ setSelectedGradient(newGrad);
267
+ updateSel({ fill: `url(#${newGrad.id})` });
268
+ }
269
+ }
270
+ )
271
+ ] }),
272
+ /* @__PURE__ */ jsxs("div", { className: styles.propRow, children: [
273
+ /* @__PURE__ */ jsx("span", { className: styles.propLabel, children: t("vectorEditor.properties.r", "R") }),
274
+ /* @__PURE__ */ jsx(
275
+ NiceSlider,
276
+ {
277
+ className: styles.propInput,
278
+ min: 10,
279
+ max: 100,
280
+ value: selectedGradient.r * 100,
281
+ onChange: (val) => {
282
+ const newGrad = {
283
+ ...selectedGradient,
284
+ id: gradientId(),
285
+ r: val / 100
286
+ };
287
+ setSelectedGradient(newGrad);
288
+ updateSel({ fill: `url(#${newGrad.id})` });
289
+ }
290
+ }
291
+ )
292
+ ] })
293
+ ] }),
294
+ /* @__PURE__ */ jsx(
295
+ "div",
296
+ {
297
+ style: { fontSize: 10, color: "var(--nice-text-secondary, #888)", marginTop: 4 },
298
+ children: t("vectorEditor.properties.colorStops", "Color Stops:")
299
+ }
300
+ ),
301
+ selectedGradient.stops.map((stop, i) => /* @__PURE__ */ jsxs("div", { className: styles.propRow, children: [
302
+ /* @__PURE__ */ jsx(
303
+ NiceColorPicker,
304
+ {
305
+ value: stop.color,
306
+ showInput: false,
307
+ showRandom: false,
308
+ style: { width: 24, height: 20 },
309
+ onChange: (color) => {
310
+ const newStops = [...selectedGradient.stops];
311
+ newStops[i] = { ...newStops[i], color };
312
+ const newGrad = { ...selectedGradient, id: gradientId(), stops: newStops };
313
+ setSelectedGradient(newGrad);
314
+ updateSel({ fill: `url(#${newGrad.id})` });
315
+ }
316
+ }
317
+ ),
318
+ /* @__PURE__ */ jsx(
319
+ NiceSlider,
320
+ {
321
+ min: 0,
322
+ max: 100,
323
+ value: stop.offset * 100,
324
+ style: { flex: 1 },
325
+ onChange: (val) => {
326
+ const newStops = [...selectedGradient.stops];
327
+ newStops[i] = { ...newStops[i], offset: val / 100 };
328
+ newStops.sort((a, b) => a.offset - b.offset);
329
+ const newGrad = { ...selectedGradient, id: gradientId(), stops: newStops };
330
+ setSelectedGradient(newGrad);
331
+ updateSel({ fill: `url(#${newGrad.id})` });
332
+ }
333
+ }
334
+ ),
335
+ /* @__PURE__ */ jsxs("span", { style: { fontSize: 9, minWidth: 24 }, children: [
336
+ Math.round(stop.offset * 100),
337
+ "%"
338
+ ] })
339
+ ] }, i)),
340
+ /* @__PURE__ */ jsxs("div", { className: styles.propRow, style: { gap: 4 }, children: [
341
+ /* @__PURE__ */ jsx(
342
+ NiceButton,
343
+ {
344
+ className: styles.menuBtn,
345
+ variant: "ghost",
346
+ size: "sm",
347
+ style: { fontSize: 10, flex: 1 },
348
+ onClick: () => {
349
+ const newStops = [
350
+ ...selectedGradient.stops,
351
+ { offset: 0.5, color: "var(--nice-text-secondary, #888888)" }
352
+ ];
353
+ newStops.sort((a, b) => a.offset - b.offset);
354
+ const newGrad = { ...selectedGradient, id: gradientId(), stops: newStops };
355
+ setSelectedGradient(newGrad);
356
+ updateSel({ fill: `url(#${newGrad.id})` });
357
+ },
358
+ children: t("vectorEditor.properties.addStop", "+ Add Stop")
359
+ }
360
+ ),
361
+ /* @__PURE__ */ jsx(
362
+ NiceButton,
363
+ {
364
+ className: styles.menuBtn,
365
+ variant: "ghost",
366
+ size: "sm",
367
+ style: { fontSize: 10, flex: 1 },
368
+ disabled: selectedGradient.stops.length <= 2,
369
+ onClick: () => {
370
+ if (selectedGradient.stops.length <= 2) {
371
+ return;
372
+ }
373
+ const newStops = selectedGradient.stops.slice(0, -1);
374
+ const newGrad = { ...selectedGradient, id: gradientId(), stops: newStops };
375
+ setSelectedGradient(newGrad);
376
+ updateSel({ fill: `url(#${newGrad.id})` });
377
+ },
378
+ children: t("vectorEditor.properties.removeStop", "- Remove")
379
+ }
380
+ )
381
+ ] })
382
+ ]
383
+ }
384
+ )
385
+ ] }),
386
+ /* @__PURE__ */ jsxs("div", { className: styles.propRow, children: [
387
+ /* @__PURE__ */ jsx("span", { className: styles.propLabel, children: t("vectorEditor.properties.stroke", "Stroke") }),
388
+ /* @__PURE__ */ jsx("div", { className: styles.propColorSwatch, style: { background: strokeCol }, children: /* @__PURE__ */ jsx(
389
+ NiceColorPicker,
390
+ {
391
+ value: strokeCol,
392
+ showInput: false,
393
+ showRandom: false,
394
+ onChange: (c) => {
395
+ setStrokeCol(c);
396
+ updateSel({ stroke: c });
397
+ }
398
+ }
399
+ ) }),
400
+ /* @__PURE__ */ jsx(
401
+ NiceTextInput,
402
+ {
403
+ className: styles.propInput,
404
+ value: selShape?.stroke || strokeCol,
405
+ onChange: (val) => {
406
+ setStrokeCol(val);
407
+ updateSel({ stroke: val });
408
+ }
409
+ }
410
+ )
411
+ ] }),
412
+ /* @__PURE__ */ jsxs("div", { className: styles.propRow, children: [
413
+ /* @__PURE__ */ jsx("span", { className: styles.propLabel, children: t("vectorEditor.properties.width", "Width") }),
414
+ /* @__PURE__ */ jsx(
415
+ NiceNumberInput,
416
+ {
417
+ className: styles.propInput,
418
+ min: 0,
419
+ max: 100,
420
+ value: selShape?.strokeWidth ?? strokeW,
421
+ onChange: (val) => {
422
+ const v = val ?? 0;
423
+ setStrokeW(v);
424
+ updateSel({ strokeWidth: v });
425
+ }
426
+ }
427
+ )
428
+ ] }),
429
+ /* @__PURE__ */ jsxs("div", { className: styles.propRow, children: [
430
+ /* @__PURE__ */ jsx("span", { className: styles.propLabel, children: t("vectorEditor.properties.opacity", "Opacity") }),
431
+ /* @__PURE__ */ jsx(
432
+ NiceSlider,
433
+ {
434
+ className: styles.propInput,
435
+ min: 0,
436
+ max: 1,
437
+ step: 0.01,
438
+ value: selShape?.opacity ?? shapeOpacity,
439
+ onChange: (val) => {
440
+ setShapeOpacity(val);
441
+ updateSel({ opacity: val });
442
+ }
443
+ }
444
+ ),
445
+ /* @__PURE__ */ jsxs("span", { style: { fontSize: 10, minWidth: 28 }, children: [
446
+ Math.round((selShape?.opacity ?? shapeOpacity) * 100),
447
+ "%"
448
+ ] })
449
+ ] }),
450
+ /* @__PURE__ */ jsxs("div", { className: styles.propRow, children: [
451
+ /* @__PURE__ */ jsx("span", { className: styles.propLabel, children: t("vectorEditor.properties.rotation", "Rotation") }),
452
+ /* @__PURE__ */ jsx(
453
+ NiceNumberInput,
454
+ {
455
+ className: styles.propInput,
456
+ min: -360,
457
+ max: 360,
458
+ value: selShape?.rotation ?? 0,
459
+ onChange: (val) => updateSel({ rotation: val ?? 0 })
460
+ }
461
+ ),
462
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 10 }, children: "°" })
463
+ ] })
464
+ ] }),
465
+ /* @__PURE__ */ jsxs("div", { className: styles.panelSection, children: [
466
+ /* @__PURE__ */ jsx("div", { className: styles.panelTitle, children: /* @__PURE__ */ jsx("span", { children: t("vectorEditor.properties.toolOptions", "Tool Options") }) }),
467
+ (tool === "roundrect" || selShape?.type === "roundrect") && /* @__PURE__ */ jsxs("div", { className: styles.propRow, children: [
468
+ /* @__PURE__ */ jsx("span", { className: styles.propLabel, children: t("vectorEditor.properties.radius", "Radius") }),
469
+ /* @__PURE__ */ jsx(
470
+ NiceNumberInput,
471
+ {
472
+ className: styles.propInput,
473
+ min: 0,
474
+ max: 200,
475
+ value: selShape?.rx ?? cornerRadius,
476
+ onChange: (val) => {
477
+ const v = val ?? 0;
478
+ setCornerRadius(v);
479
+ updateSel({ rx: v });
480
+ }
481
+ }
482
+ )
483
+ ] }),
484
+ (tool === "polygon" || selShape?.type === "polygon") && /* @__PURE__ */ jsxs("div", { className: styles.propRow, children: [
485
+ /* @__PURE__ */ jsx("span", { className: styles.propLabel, children: t("vectorEditor.properties.sides", "Sides") }),
486
+ /* @__PURE__ */ jsx(
487
+ NiceNumberInput,
488
+ {
489
+ className: styles.propInput,
490
+ min: 3,
491
+ max: 32,
492
+ value: selShape?.sides ?? polygonSides,
493
+ onChange: (val) => {
494
+ const v = val ?? 3;
495
+ setPolygonSides(v);
496
+ updateSel({ sides: v });
497
+ }
498
+ }
499
+ )
500
+ ] }),
501
+ (tool === "star" || selShape?.type === "star") && /* @__PURE__ */ jsxs(Fragment, { children: [
502
+ /* @__PURE__ */ jsxs("div", { className: styles.propRow, children: [
503
+ /* @__PURE__ */ jsx("span", { className: styles.propLabel, children: t("vectorEditor.properties.points", "Points") }),
504
+ /* @__PURE__ */ jsx(
505
+ NiceNumberInput,
506
+ {
507
+ className: styles.propInput,
508
+ min: 3,
509
+ max: 32,
510
+ value: selShape?.sides ?? starPoints2,
511
+ onChange: (val) => {
512
+ const v = val ?? 3;
513
+ setStarPoints2(v);
514
+ updateSel({ sides: v });
515
+ }
516
+ }
517
+ )
518
+ ] }),
519
+ /* @__PURE__ */ jsxs("div", { className: styles.propRow, children: [
520
+ /* @__PURE__ */ jsx("span", { className: styles.propLabel, children: t("vectorEditor.properties.innerR", "Inner R") }),
521
+ /* @__PURE__ */ jsx(
522
+ NiceSlider,
523
+ {
524
+ className: styles.propInput,
525
+ min: 0.1,
526
+ max: 0.9,
527
+ step: 0.05,
528
+ value: selShape?.innerRadius ?? starInnerRatio,
529
+ onChange: (val) => {
530
+ setStarInnerRatio(val);
531
+ updateSel({ innerRadius: val });
532
+ }
533
+ }
534
+ ),
535
+ /* @__PURE__ */ jsxs("span", { style: { fontSize: 10 }, children: [
536
+ Math.round((selShape?.innerRadius ?? starInnerRatio) * 100),
537
+ "%"
538
+ ] })
539
+ ] })
540
+ ] }),
541
+ (tool === "text" || selShape?.type === "text") && /* @__PURE__ */ jsxs(Fragment, { children: [
542
+ /* @__PURE__ */ jsxs("div", { className: styles.propRow, children: [
543
+ /* @__PURE__ */ jsx("span", { className: styles.propLabel, children: t("vectorEditor.properties.size", "Size") }),
544
+ /* @__PURE__ */ jsx(
545
+ NiceNumberInput,
546
+ {
547
+ className: styles.propInput,
548
+ min: 8,
549
+ max: 200,
550
+ value: selShape?.fontSize ?? fontSize,
551
+ onChange: (val) => {
552
+ const v = val ?? 12;
553
+ setFontSize(v);
554
+ updateSel({ fontSize: v });
555
+ }
556
+ }
557
+ )
558
+ ] }),
559
+ /* @__PURE__ */ jsxs("div", { className: styles.propRow, children: [
560
+ /* @__PURE__ */ jsx("span", { className: styles.propLabel, children: t("vectorEditor.properties.font", "Font") }),
561
+ /* @__PURE__ */ jsx(
562
+ NiceSelect,
563
+ {
564
+ className: styles.propInput,
565
+ value: selShape?.fontFamily ?? fontFamily,
566
+ onChange: (val) => {
567
+ setFontFamily(val);
568
+ updateSel({ fontFamily: val });
569
+ },
570
+ options: FONTS.map((f) => ({ value: f, label: f }))
571
+ }
572
+ )
573
+ ] })
574
+ ] }),
575
+ snapGrid && /* @__PURE__ */ jsxs("div", { className: styles.propRow, children: [
576
+ /* @__PURE__ */ jsx("span", { className: styles.propLabel, children: t("vectorEditor.properties.grid", "Grid") }),
577
+ /* @__PURE__ */ jsx(
578
+ NiceNumberInput,
579
+ {
580
+ className: styles.propInput,
581
+ min: 4,
582
+ max: 128,
583
+ value: gridSize,
584
+ onChange: (val) => setGridSize(val ?? 8)
585
+ }
586
+ )
587
+ ] })
588
+ ] }),
589
+ selShape && /* @__PURE__ */ jsxs("div", { className: styles.panelSection, children: [
590
+ /* @__PURE__ */ jsx("div", { className: styles.panelTitle, children: /* @__PURE__ */ jsx("span", { children: t("vectorEditor.properties.transform", "Transform") }) }),
591
+ /* @__PURE__ */ jsxs("div", { className: styles.propRow, children: [
592
+ /* @__PURE__ */ jsx("span", { className: styles.propLabel, children: t("vectorEditor.properties.x", "X") }),
593
+ /* @__PURE__ */ jsx(
594
+ NiceNumberInput,
595
+ {
596
+ className: styles.propInput,
597
+ value: Math.round(selShape.x),
598
+ onChange: (val) => updateSel({ x: val ?? 0 })
599
+ }
600
+ )
601
+ ] }),
602
+ /* @__PURE__ */ jsxs("div", { className: styles.propRow, children: [
603
+ /* @__PURE__ */ jsx("span", { className: styles.propLabel, children: t("vectorEditor.properties.y", "Y") }),
604
+ /* @__PURE__ */ jsx(
605
+ NiceNumberInput,
606
+ {
607
+ className: styles.propInput,
608
+ value: Math.round(selShape.y),
609
+ onChange: (val) => updateSel({ y: val ?? 0 })
610
+ }
611
+ )
612
+ ] }),
613
+ /* @__PURE__ */ jsxs("div", { className: styles.propRow, children: [
614
+ /* @__PURE__ */ jsx("span", { className: styles.propLabel, children: t("vectorEditor.properties.w", "W") }),
615
+ /* @__PURE__ */ jsx(
616
+ NiceNumberInput,
617
+ {
618
+ className: styles.propInput,
619
+ value: Math.round(selShape.w),
620
+ onChange: (val) => updateSel({ w: val ?? 0 })
621
+ }
622
+ )
623
+ ] }),
624
+ /* @__PURE__ */ jsxs("div", { className: styles.propRow, children: [
625
+ /* @__PURE__ */ jsx("span", { className: styles.propLabel, children: t("vectorEditor.properties.h", "H") }),
626
+ /* @__PURE__ */ jsx(
627
+ NiceNumberInput,
628
+ {
629
+ className: styles.propInput,
630
+ value: Math.round(selShape.h),
631
+ onChange: (val) => updateSel({ h: val ?? 0 })
632
+ }
633
+ )
634
+ ] })
635
+ ] })
636
+ ] });
637
+ const insertIcon = (Icon, badge) => {
638
+ if (badge) {
639
+ const box = Math.round(Math.min(artW, artH) * 0.42);
640
+ const made = iconComponentToShapes(Icon, { artW: box, artH: box, margin: 4 });
641
+ if (made.length === 0) {
642
+ return;
643
+ }
644
+ const dx = artW - box;
645
+ const offset = made.map((s) => ({
646
+ ...s,
647
+ x: s.x + dx,
648
+ ...s.x2 != null ? { x2: s.x2 + dx } : {},
649
+ ...s.points ? { points: s.points.map((p) => ({ ...p, x: p.x + dx })) } : {}
650
+ }));
651
+ const next2 = [...shapes, ...offset];
652
+ setShapes(next2);
653
+ snap(next2);
654
+ if (offset[0]?.id) {
655
+ setSelId(offset[0].id);
656
+ }
657
+ setTool("pointer");
658
+ return;
659
+ }
660
+ const inserted = iconComponentToShapes(Icon, { artW, artH });
661
+ if (inserted.length === 0) {
662
+ return;
663
+ }
664
+ const next = [...shapes, ...inserted];
665
+ setShapes(next);
666
+ snap(next);
667
+ if (inserted[0]?.id) {
668
+ setSelId(inserted[0].id);
669
+ }
670
+ setTool("pointer");
671
+ };
672
+ const reorderShape = (id, dir) => {
673
+ const idx = shapes.findIndex((s) => s.id === id);
674
+ if (idx < 0) {
675
+ return;
676
+ }
677
+ const next = shapes.slice();
678
+ const target = idx + dir;
679
+ if (target < 0 || target >= next.length) {
680
+ return;
681
+ }
682
+ [next[idx], next[target]] = [next[target], next[idx]];
683
+ setShapes(next);
684
+ snap(next);
685
+ };
686
+ const layersContent = /* @__PURE__ */ jsx("div", { className: styles.rightPanelTabBody, children: /* @__PURE__ */ jsxs("div", { className: styles.panelSection, children: [
687
+ /* @__PURE__ */ jsx("div", { className: styles.panelTitle, children: /* @__PURE__ */ jsxs("span", { children: [
688
+ t("vectorEditor.layers.title", "Layers"),
689
+ " (",
690
+ shapes.length,
691
+ ")"
692
+ ] }) }),
693
+ [...shapes].reverse().map((s, revIdx) => {
694
+ const realIdx = shapes.length - 1 - revIdx;
695
+ const active = s.id === selId;
696
+ return /* @__PURE__ */ jsxs(
697
+ "div",
357
698
  {
358
- id: 'properties',
359
- label: t('vectorEditor.tabs.properties', 'Properties'),
360
- content: propertiesContent,
699
+ role: "button",
700
+ tabIndex: 0,
701
+ onKeyDown: clickOnActivationKey,
702
+ className: `${styles.layerRow} ${active ? styles.layerRowActive : ""} ${s.locked ? styles.layerRowLocked : ""}`,
703
+ onClick: () => {
704
+ setSelId(s.id);
705
+ setTool("pointer");
706
+ },
707
+ children: [
708
+ /* @__PURE__ */ jsx(
709
+ "button",
710
+ {
711
+ type: "button",
712
+ className: styles.layerIconBtn,
713
+ title: s.hidden ? t("vectorEditor.layers.show", "Show") : t("vectorEditor.layers.hide", "Hide"),
714
+ onClick: (e) => {
715
+ e.stopPropagation();
716
+ const next = shapes.map((x) => x.id === s.id ? { ...x, hidden: !x.hidden } : x);
717
+ setShapes(next);
718
+ snap(next);
719
+ },
720
+ children: /* @__PURE__ */ jsx(NiceIcon, { name: s.hidden ? "eye-off" : "eye", size: 14 })
721
+ }
722
+ ),
723
+ /* @__PURE__ */ jsx(
724
+ "button",
725
+ {
726
+ type: "button",
727
+ className: styles.layerIconBtn,
728
+ title: s.locked ? t("vectorEditor.layers.unlock", "Unlock") : t("vectorEditor.layers.lock", "Lock"),
729
+ onClick: (e) => {
730
+ e.stopPropagation();
731
+ const next = shapes.map((x) => x.id === s.id ? { ...x, locked: !x.locked } : x);
732
+ setShapes(next);
733
+ snap(next);
734
+ },
735
+ children: /* @__PURE__ */ jsx(NiceIcon, { name: "lock", size: 14, style: { opacity: s.locked ? 1 : 0.4 } })
736
+ }
737
+ ),
738
+ /* @__PURE__ */ jsx("span", { className: styles.shapeIcon, children: /* @__PURE__ */ jsx(
739
+ NiceIcon,
740
+ {
741
+ name: s.type === "rect" || s.type === "roundrect" ? "square" : s.type === "ellipse" ? "circle" : s.type === "line" ? "line" : s.type === "arrow" ? "arrow-right" : s.type === "polygon" ? "hexagon" : s.type === "star" ? "star" : s.type === "polyline" ? "pen" : s.type === "path" ? "wand" : "type",
742
+ size: 14
743
+ }
744
+ ) }),
745
+ /* @__PURE__ */ jsx(
746
+ "input",
747
+ {
748
+ className: styles.layerNameInput,
749
+ value: s.name ?? s.type,
750
+ disabled: s.locked,
751
+ onClick: (e) => e.stopPropagation(),
752
+ onChange: (e) => {
753
+ const v = e.target.value;
754
+ const next = shapes.map((x) => x.id === s.id ? { ...x, name: v } : x);
755
+ setShapes(next);
756
+ },
757
+ onBlur: () => snap(shapes)
758
+ }
759
+ ),
760
+ /* @__PURE__ */ jsx(
761
+ "button",
762
+ {
763
+ type: "button",
764
+ className: styles.layerIconBtn,
765
+ title: t("vectorEditor.layers.moveUp", "Move up"),
766
+ onClick: (e) => {
767
+ e.stopPropagation();
768
+ reorderShape(s.id, 1);
769
+ },
770
+ disabled: realIdx === shapes.length - 1,
771
+ children: /* @__PURE__ */ jsx(NiceIcon, { name: "arrow-up", size: 14 })
772
+ }
773
+ ),
774
+ /* @__PURE__ */ jsx(
775
+ "button",
776
+ {
777
+ type: "button",
778
+ className: styles.layerIconBtn,
779
+ title: t("vectorEditor.layers.moveDown", "Move down"),
780
+ onClick: (e) => {
781
+ e.stopPropagation();
782
+ reorderShape(s.id, -1);
783
+ },
784
+ disabled: realIdx === 0,
785
+ children: /* @__PURE__ */ jsx(NiceIcon, { name: "arrow-down", size: 14 })
786
+ }
787
+ )
788
+ ]
361
789
  },
790
+ s.id
791
+ );
792
+ }),
793
+ shapes.length === 0 && /* @__PURE__ */ jsx("div", { style: { color: "var(--nice-text-secondary)", fontSize: 11, padding: 4 }, children: t(
794
+ "vectorEditor.layers.empty",
795
+ "No layers yet. Use the tools to draw, or insert from the Library tab."
796
+ ) })
797
+ ] }) });
798
+ const libraryContent = /* @__PURE__ */ jsx("div", { className: styles.rightPanelTabBody, children: library && library.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
799
+ /* @__PURE__ */ jsx("div", { className: styles.propRow, style: { padding: "4px 0" }, children: /* @__PURE__ */ jsxs(
800
+ NiceButton,
801
+ {
802
+ className: `${styles.menuBtn} ${asBadge ? styles.menuBtnActive : ""}`,
803
+ variant: asBadge ? "primary" : "ghost",
804
+ size: "sm",
805
+ style: { width: "100%", fontSize: 11 },
806
+ onClick: () => setAsBadge((v) => !v),
807
+ "aria-pressed": asBadge,
808
+ children: [
809
+ asBadge && /* @__PURE__ */ jsx(NiceIcon, { name: "plus", size: 14 }),
810
+ " ",
811
+ asBadge ? t("vectorEditor.library.insertAsBadgeOn", "Inserting as corner badge") : t("vectorEditor.library.insertAsBadge", "Insert as corner badge")
812
+ ]
813
+ }
814
+ ) }),
815
+ /* @__PURE__ */ jsx("div", { className: styles.libraryGrid, children: library.map((entry) => {
816
+ const Icon = entry.Component;
817
+ return /* @__PURE__ */ jsxs(
818
+ "button",
362
819
  {
363
- id: 'layers',
364
- label: `${t('vectorEditor.tabs.layers', 'Layers')} (${shapes.length})`,
365
- content: layersContent,
820
+ type: "button",
821
+ className: styles.libraryCell,
822
+ title: entry.name,
823
+ onClick: () => insertIcon(Icon, asBadge),
824
+ children: [
825
+ /* @__PURE__ */ jsx(
826
+ "div",
827
+ {
828
+ style: {
829
+ width: 28,
830
+ height: 28,
831
+ display: "flex",
832
+ alignItems: "center",
833
+ justifyContent: "center"
834
+ },
835
+ children: /* @__PURE__ */ jsx(Icon, { size: "md" })
836
+ }
837
+ ),
838
+ /* @__PURE__ */ jsx("span", { children: entry.name })
839
+ ]
366
840
  },
367
- { id: 'library', label: t('vectorEditor.tabs.library', 'Library'), content: libraryContent },
841
+ entry.name
842
+ );
843
+ }) })
844
+ ] }) : /* @__PURE__ */ jsx("div", { style: { color: "var(--nice-text-secondary)", fontSize: 11, padding: 12 }, children: t(
845
+ "vectorEditor.library.passProp",
846
+ "Pass a library prop to NiceVectorEditor to populate this tab with insertable icons."
847
+ ) }) });
848
+ const selAnim = selId ? animations[selId] : void 0;
849
+ const animationCount = Object.keys(animations).length;
850
+ const timelineContent = /* @__PURE__ */ jsxs("div", { className: styles.rightPanelTabBody, children: [
851
+ /* @__PURE__ */ jsxs("div", { className: styles.panelSection, children: [
852
+ /* @__PURE__ */ jsx("div", { className: styles.panelTitle, children: /* @__PURE__ */ jsx("span", { children: t("vectorEditor.timeline.animationTitle", "Animation") }) }),
853
+ !selId ? /* @__PURE__ */ jsx("div", { style: { color: "var(--nice-text-secondary)", fontSize: 11, padding: 4 }, children: t("vectorEditor.timeline.attachHint", "Select a shape to attach an animation.") }) : /* @__PURE__ */ jsxs(Fragment, { children: [
854
+ /* @__PURE__ */ jsxs("div", { className: styles.propRow, children: [
855
+ /* @__PURE__ */ jsx("span", { className: styles.propLabel, children: t("vectorEditor.timeline.preset", "Preset") }),
856
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 4 }, children: Object.keys(ANIMATION_PRESETS).map((key) => /* @__PURE__ */ jsx(
857
+ NiceButton,
858
+ {
859
+ variant: "ghost",
860
+ size: "sm",
861
+ onClick: () => setShapeAnimation(selId, ANIMATION_PRESETS[key]),
862
+ children: key
863
+ },
864
+ key
865
+ )) })
866
+ ] }),
867
+ selAnim && /* @__PURE__ */ jsxs(Fragment, { children: [
868
+ /* @__PURE__ */ jsxs("div", { className: styles.propRow, children: [
869
+ /* @__PURE__ */ jsx("span", { className: styles.propLabel, children: t("vectorEditor.timeline.duration", "Duration (ms)") }),
870
+ /* @__PURE__ */ jsx(
871
+ NiceNumberInput,
872
+ {
873
+ value: selAnim.duration,
874
+ min: 100,
875
+ max: 1e4,
876
+ step: 100,
877
+ onChange: (v) => setShapeAnimation(selId, {
878
+ ...selAnim,
879
+ duration: Number(v) || selAnim.duration
880
+ })
881
+ }
882
+ )
883
+ ] }),
884
+ /* @__PURE__ */ jsxs("div", { className: styles.propRow, children: [
885
+ /* @__PURE__ */ jsx("span", { className: styles.propLabel, children: t("vectorEditor.timeline.repeat", "Repeat") }),
886
+ /* @__PURE__ */ jsx(
887
+ NiceSelect,
888
+ {
889
+ value: selAnim.repeat === "infinite" ? "infinite" : String(selAnim.repeat),
890
+ onChange: (v) => setShapeAnimation(selId, {
891
+ ...selAnim,
892
+ repeat: v === "infinite" ? "infinite" : Number(v)
893
+ }),
894
+ options: [
895
+ { value: "infinite", label: "infinite" },
896
+ { value: "1", label: "1x" },
897
+ { value: "2", label: "2x" },
898
+ { value: "3", label: "3x" },
899
+ { value: "5", label: "5x" }
900
+ ]
901
+ }
902
+ )
903
+ ] }),
904
+ /* @__PURE__ */ jsxs("div", { className: styles.propRow, children: [
905
+ /* @__PURE__ */ jsx("span", { className: styles.propLabel, children: t("vectorEditor.timeline.direction", "Direction") }),
906
+ /* @__PURE__ */ jsx(
907
+ NiceSelect,
908
+ {
909
+ value: selAnim.direction,
910
+ onChange: (v) => setShapeAnimation(selId, {
911
+ ...selAnim,
912
+ direction: v
913
+ }),
914
+ options: [
915
+ { value: "normal", label: "normal" },
916
+ { value: "alternate", label: "alternate" }
917
+ ]
918
+ }
919
+ )
920
+ ] }),
921
+ /* @__PURE__ */ jsx("div", { className: styles.propRow, children: /* @__PURE__ */ jsx(
922
+ NiceButton,
923
+ {
924
+ variant: "ghost",
925
+ size: "sm",
926
+ onClick: () => setShapeAnimation(selId, null),
927
+ children: t("vectorEditor.timeline.remove", "Remove animation")
928
+ }
929
+ ) }),
930
+ /* @__PURE__ */ jsx("div", { className: styles.panelTitle, style: { marginTop: 8 }, children: /* @__PURE__ */ jsx("span", { children: t("vectorEditor.timeline.keyframes", "Keyframes") }) }),
931
+ selAnim.keyframes.map((k, i) => /* @__PURE__ */ jsxs(
932
+ "div",
933
+ {
934
+ className: styles.propRow,
935
+ style: { flexDirection: "column", alignItems: "stretch", gap: 2 },
936
+ children: [
937
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 4 }, children: [
938
+ /* @__PURE__ */ jsx("span", { className: styles.propLabel, style: { minWidth: 24 }, children: t("vectorEditor.timeline.tLabel", "t") }),
939
+ /* @__PURE__ */ jsx(
940
+ NiceSlider,
941
+ {
942
+ value: k.t,
943
+ min: 0,
944
+ max: 1,
945
+ step: 0.01,
946
+ onChange: (v) => {
947
+ const nextKfs = selAnim.keyframes.map(
948
+ (kf, j) => j === i ? { ...kf, t: Number(v) } : kf
949
+ );
950
+ nextKfs.sort((a, b) => a.t - b.t);
951
+ setShapeAnimation(selId, { ...selAnim, keyframes: nextKfs });
952
+ }
953
+ }
954
+ ),
955
+ /* @__PURE__ */ jsx(
956
+ NiceButton,
957
+ {
958
+ variant: "ghost",
959
+ size: "sm",
960
+ disabled: selAnim.keyframes.length <= 2,
961
+ onClick: () => {
962
+ const nextKfs = selAnim.keyframes.filter((_, j) => j !== i);
963
+ setShapeAnimation(selId, { ...selAnim, keyframes: nextKfs });
964
+ },
965
+ "aria-label": t("vectorEditor.timeline.removeKeyframe", "Remove keyframe"),
966
+ children: /* @__PURE__ */ jsx(NiceIcon, { name: "trash", size: 14 })
967
+ }
968
+ )
969
+ ] }),
970
+ /* @__PURE__ */ jsx("div", { style: { display: "grid", gridTemplateColumns: "repeat(5, 1fr)", gap: 2 }, children: ["rotate", "scale", "translateX", "translateY", "opacity"].map(
971
+ (field) => /* @__PURE__ */ jsx(
972
+ NiceNumberInput,
973
+ {
974
+ value: k[field] ?? 0,
975
+ step: field === "scale" || field === "opacity" ? 0.1 : 1,
976
+ placeholder: field[0].toUpperCase(),
977
+ onChange: (v) => {
978
+ const num = v == null ? void 0 : Number(v);
979
+ const nextKfs = selAnim.keyframes.map(
980
+ (kf, j) => j === i ? { ...kf, [field]: num } : kf
981
+ );
982
+ setShapeAnimation(selId, { ...selAnim, keyframes: nextKfs });
983
+ }
984
+ },
985
+ field
986
+ )
987
+ ) })
988
+ ]
989
+ },
990
+ i
991
+ )),
992
+ /* @__PURE__ */ jsx("div", { className: styles.propRow, children: /* @__PURE__ */ jsx(
993
+ NiceButton,
994
+ {
995
+ variant: "ghost",
996
+ size: "sm",
997
+ onClick: () => {
998
+ const last = selAnim.keyframes[selAnim.keyframes.length - 1];
999
+ const prev = selAnim.keyframes[selAnim.keyframes.length - 2];
1000
+ const newT = prev ? (prev.t + last.t) / 2 : Math.min(1, last.t + 0.1);
1001
+ const nextKfs = [...selAnim.keyframes, { ...last, t: newT }].sort(
1002
+ (a, b) => a.t - b.t
1003
+ );
1004
+ setShapeAnimation(selId, { ...selAnim, keyframes: nextKfs });
1005
+ },
1006
+ children: t("vectorEditor.timeline.addKeyframe", "+ Add keyframe")
1007
+ }
1008
+ ) })
1009
+ ] })
1010
+ ] })
1011
+ ] }),
1012
+ /* @__PURE__ */ jsxs("div", { className: styles.panelSection, children: [
1013
+ /* @__PURE__ */ jsx("div", { className: styles.panelTitle, children: /* @__PURE__ */ jsx("span", { children: t("vectorEditor.timeline.playbackTitle", "Playback") }) }),
1014
+ /* @__PURE__ */ jsxs("div", { className: styles.propRow, children: [
1015
+ /* @__PURE__ */ jsxs(
1016
+ NiceButton,
1017
+ {
1018
+ variant: isPlaying ? "primary" : "ghost",
1019
+ size: "sm",
1020
+ onClick: () => {
1021
+ if (isPlaying) {
1022
+ setIsPlaying(false);
1023
+ } else {
1024
+ setPreviewTime(0);
1025
+ setIsPlaying(true);
1026
+ }
1027
+ },
1028
+ disabled: animationCount === 0,
1029
+ children: [
1030
+ /* @__PURE__ */ jsx(NiceIcon, { name: isPlaying ? "pause" : "play", size: 14 }),
1031
+ " ",
1032
+ isPlaying ? t("vectorEditor.timeline.pauseLabel", "Pause") : t("vectorEditor.timeline.playLabel", "Play")
1033
+ ]
1034
+ }
1035
+ ),
1036
+ /* @__PURE__ */ jsxs(
1037
+ NiceButton,
1038
+ {
1039
+ variant: "ghost",
1040
+ size: "sm",
1041
+ onClick: () => {
1042
+ setIsPlaying(false);
1043
+ setPreviewTime(0);
1044
+ },
1045
+ children: [
1046
+ /* @__PURE__ */ jsx(NiceIcon, { name: "stop", size: 14 }),
1047
+ " ",
1048
+ t("vectorEditor.timeline.stopLabel", "Stop")
1049
+ ]
1050
+ }
1051
+ )
1052
+ ] }),
1053
+ animationCount > 0 && /* @__PURE__ */ jsx("div", { className: styles.propRow, children: /* @__PURE__ */ jsxs(
1054
+ NiceButton,
368
1055
  {
369
- id: 'timeline',
370
- label: `${t('vectorEditor.tabs.timeline', 'Timeline')}${animationCount ? ` (${animationCount})` : ''}`,
371
- content: timelineContent,
372
- },
373
- ];
374
- return (jsx("div", { className: styles.rightPanel, children: jsx(NiceTabs, { tabs: tabs, size: "sm" }) }));
1056
+ variant: "ghost",
1057
+ size: "sm",
1058
+ onClick: () => {
1059
+ const lines = [];
1060
+ for (const [shapeId, a] of Object.entries(animations)) {
1061
+ const name = `anim-${shapeId}`;
1062
+ lines.push(animationToCSSKeyframes(name, a));
1063
+ lines.push(animationToCSSRule(`[data-shape="${shapeId}"]`, name, a));
1064
+ }
1065
+ const css = lines.join("\n\n");
1066
+ if (typeof navigator !== "undefined" && navigator.clipboard) {
1067
+ navigator.clipboard.writeText(css).catch(() => {
1068
+ });
1069
+ }
1070
+ },
1071
+ children: [
1072
+ /* @__PURE__ */ jsx(NiceIcon, { name: "clipboard", size: 14 }),
1073
+ " ",
1074
+ t("vectorEditor.timeline.copyCssKeyframes", "Copy CSS keyframes")
1075
+ ]
1076
+ }
1077
+ ) })
1078
+ ] })
1079
+ ] });
1080
+ const tabs = [
1081
+ {
1082
+ id: "properties",
1083
+ label: t("vectorEditor.tabs.properties", "Properties"),
1084
+ content: propertiesContent
1085
+ },
1086
+ {
1087
+ id: "layers",
1088
+ label: `${t("vectorEditor.tabs.layers", "Layers")} (${shapes.length})`,
1089
+ content: layersContent
1090
+ },
1091
+ { id: "library", label: t("vectorEditor.tabs.library", "Library"), content: libraryContent },
1092
+ {
1093
+ id: "timeline",
1094
+ label: `${t("vectorEditor.tabs.timeline", "Timeline")}${animationCount ? ` (${animationCount})` : ""}`,
1095
+ content: timelineContent
1096
+ }
1097
+ ];
1098
+ return /* @__PURE__ */ jsx("div", { className: styles.rightPanel, children: /* @__PURE__ */ jsx(NiceTabs, { tabs, size: "sm" }) });
1099
+ };
1100
+ const VectorEditorRightPanel = React.memo(VectorEditorRightPanel$1);
1101
+ export {
1102
+ VectorEditorRightPanel as default
375
1103
  };
376
- var VectorEditorRightPanel = React.memo(VectorEditorRightPanel$1);
377
-
378
- export { VectorEditorRightPanel as default };