@easemate/web-kit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (358) hide show
  1. package/README.md +824 -0
  2. package/build/components/code/index.cjs +152 -0
  3. package/build/components/code/index.d.cts +11 -0
  4. package/build/components/code/index.d.ts +11 -0
  5. package/build/components/code/index.js +148 -0
  6. package/build/components/code/utils/highlight-api.cjs +18 -0
  7. package/build/components/code/utils/highlight-api.d.cts +7 -0
  8. package/build/components/code/utils/highlight-api.d.ts +7 -0
  9. package/build/components/code/utils/highlight-api.js +14 -0
  10. package/build/components/code/utils/syntax-grammars.cjs +62 -0
  11. package/build/components/code/utils/syntax-grammars.d.cts +7 -0
  12. package/build/components/code/utils/syntax-grammars.d.ts +7 -0
  13. package/build/components/code/utils/syntax-grammars.js +59 -0
  14. package/build/components/code/utils/syntax-highlighter-theme.cjs +27 -0
  15. package/build/components/code/utils/syntax-highlighter-theme.d.cts +3 -0
  16. package/build/components/code/utils/syntax-highlighter-theme.d.ts +3 -0
  17. package/build/components/code/utils/syntax-highlighter-theme.js +23 -0
  18. package/build/components/code/utils/syntax-highlighter-types.cjs +2 -0
  19. package/build/components/code/utils/syntax-highlighter-types.d.cts +12 -0
  20. package/build/components/code/utils/syntax-highlighter-types.d.ts +12 -0
  21. package/build/components/code/utils/syntax-highlighter-types.js +1 -0
  22. package/build/components/code/utils/syntax-tokenizer.cjs +63 -0
  23. package/build/components/code/utils/syntax-tokenizer.d.cts +3 -0
  24. package/build/components/code/utils/syntax-tokenizer.d.ts +3 -0
  25. package/build/components/code/utils/syntax-tokenizer.js +58 -0
  26. package/build/components/curve/bezier-conversion.cjs +23 -0
  27. package/build/components/curve/bezier-conversion.d.cts +2 -0
  28. package/build/components/curve/bezier-conversion.d.ts +2 -0
  29. package/build/components/curve/bezier-conversion.js +19 -0
  30. package/build/components/curve/canvas-controls.cjs +300 -0
  31. package/build/components/curve/canvas-controls.d.cts +12 -0
  32. package/build/components/curve/canvas-controls.d.ts +12 -0
  33. package/build/components/curve/canvas-controls.js +296 -0
  34. package/build/components/curve/canvas.cjs +1208 -0
  35. package/build/components/curve/canvas.d.cts +24 -0
  36. package/build/components/curve/canvas.d.ts +24 -0
  37. package/build/components/curve/canvas.js +1204 -0
  38. package/build/components/curve/constants.cjs +203 -0
  39. package/build/components/curve/constants.d.cts +23 -0
  40. package/build/components/curve/constants.d.ts +23 -0
  41. package/build/components/curve/constants.js +200 -0
  42. package/build/components/curve/controls.cjs +942 -0
  43. package/build/components/curve/controls.d.cts +37 -0
  44. package/build/components/curve/controls.d.ts +37 -0
  45. package/build/components/curve/controls.js +938 -0
  46. package/build/components/curve/index.cjs +335 -0
  47. package/build/components/curve/index.d.cts +31 -0
  48. package/build/components/curve/index.d.ts +31 -0
  49. package/build/components/curve/index.js +330 -0
  50. package/build/components/curve/output.cjs +141 -0
  51. package/build/components/curve/output.d.cts +19 -0
  52. package/build/components/curve/output.d.ts +19 -0
  53. package/build/components/curve/output.js +137 -0
  54. package/build/components/curve/styles.cjs +493 -0
  55. package/build/components/curve/styles.d.cts +6 -0
  56. package/build/components/curve/styles.d.ts +6 -0
  57. package/build/components/curve/styles.js +490 -0
  58. package/build/components/curve/svg-renderer.cjs +185 -0
  59. package/build/components/curve/svg-renderer.d.cts +9 -0
  60. package/build/components/curve/svg-renderer.d.ts +9 -0
  61. package/build/components/curve/svg-renderer.js +175 -0
  62. package/build/components/curve/toolbar.cjs +368 -0
  63. package/build/components/curve/toolbar.d.cts +26 -0
  64. package/build/components/curve/toolbar.d.ts +26 -0
  65. package/build/components/curve/toolbar.js +364 -0
  66. package/build/components/curve/types.cjs +10 -0
  67. package/build/components/curve/types.d.cts +33 -0
  68. package/build/components/curve/types.d.ts +33 -0
  69. package/build/components/curve/types.js +7 -0
  70. package/build/components/curve/utils.cjs +541 -0
  71. package/build/components/curve/utils.d.cts +33 -0
  72. package/build/components/curve/utils.d.ts +33 -0
  73. package/build/components/curve/utils.js +521 -0
  74. package/build/components/index.cjs +18 -0
  75. package/build/components/index.d.cts +2 -0
  76. package/build/components/index.d.ts +2 -0
  77. package/build/components/index.js +2 -0
  78. package/build/decorators/Component.cjs +127 -0
  79. package/build/decorators/Component.d.cts +28 -0
  80. package/build/decorators/Component.d.ts +28 -0
  81. package/build/decorators/Component.js +123 -0
  82. package/build/decorators/Listen.cjs +154 -0
  83. package/build/decorators/Listen.d.cts +18 -0
  84. package/build/decorators/Listen.d.ts +18 -0
  85. package/build/decorators/Listen.js +151 -0
  86. package/build/decorators/OutsideClick.cjs +64 -0
  87. package/build/decorators/OutsideClick.d.cts +16 -0
  88. package/build/decorators/OutsideClick.d.ts +16 -0
  89. package/build/decorators/OutsideClick.js +59 -0
  90. package/build/decorators/Prop.cjs +273 -0
  91. package/build/decorators/Prop.d.cts +22 -0
  92. package/build/decorators/Prop.d.ts +22 -0
  93. package/build/decorators/Prop.js +270 -0
  94. package/build/decorators/Query.cjs +79 -0
  95. package/build/decorators/Query.d.cts +27 -0
  96. package/build/decorators/Query.d.ts +27 -0
  97. package/build/decorators/Query.js +76 -0
  98. package/build/decorators/Watch.cjs +52 -0
  99. package/build/decorators/Watch.d.cts +11 -0
  100. package/build/decorators/Watch.d.ts +11 -0
  101. package/build/decorators/Watch.js +49 -0
  102. package/build/decorators/index.cjs +15 -0
  103. package/build/decorators/index.d.cts +6 -0
  104. package/build/decorators/index.d.ts +6 -0
  105. package/build/decorators/index.js +6 -0
  106. package/build/elements/button/index.cjs +214 -0
  107. package/build/elements/button/index.d.cts +11 -0
  108. package/build/elements/button/index.d.ts +11 -0
  109. package/build/elements/button/index.js +210 -0
  110. package/build/elements/checkbox/index.cjs +316 -0
  111. package/build/elements/checkbox/index.d.cts +14 -0
  112. package/build/elements/checkbox/index.d.ts +14 -0
  113. package/build/elements/checkbox/index.js +312 -0
  114. package/build/elements/color/index.cjs +154 -0
  115. package/build/elements/color/index.d.cts +18 -0
  116. package/build/elements/color/index.d.ts +18 -0
  117. package/build/elements/color/index.js +150 -0
  118. package/build/elements/color/picker.cjs +544 -0
  119. package/build/elements/color/picker.d.cts +37 -0
  120. package/build/elements/color/picker.d.ts +37 -0
  121. package/build/elements/color/picker.js +540 -0
  122. package/build/elements/color/utils.cjs +235 -0
  123. package/build/elements/color/utils.d.cts +37 -0
  124. package/build/elements/color/utils.d.ts +37 -0
  125. package/build/elements/color/utils.js +218 -0
  126. package/build/elements/dropdown/index.cjs +875 -0
  127. package/build/elements/dropdown/index.d.cts +30 -0
  128. package/build/elements/dropdown/index.d.ts +30 -0
  129. package/build/elements/dropdown/index.js +871 -0
  130. package/build/elements/field/index.cjs +82 -0
  131. package/build/elements/field/index.d.cts +4 -0
  132. package/build/elements/field/index.d.ts +4 -0
  133. package/build/elements/field/index.js +78 -0
  134. package/build/elements/icons/animation/chevron.cjs +57 -0
  135. package/build/elements/icons/animation/chevron.d.cts +10 -0
  136. package/build/elements/icons/animation/chevron.d.ts +10 -0
  137. package/build/elements/icons/animation/chevron.js +53 -0
  138. package/build/elements/icons/animation/clear.cjs +74 -0
  139. package/build/elements/icons/animation/clear.d.cts +3 -0
  140. package/build/elements/icons/animation/clear.d.ts +3 -0
  141. package/build/elements/icons/animation/clear.js +70 -0
  142. package/build/elements/icons/animation/grid.cjs +77 -0
  143. package/build/elements/icons/animation/grid.d.cts +8 -0
  144. package/build/elements/icons/animation/grid.d.ts +8 -0
  145. package/build/elements/icons/animation/grid.js +73 -0
  146. package/build/elements/icons/animation/loading.cjs +68 -0
  147. package/build/elements/icons/animation/loading.d.cts +3 -0
  148. package/build/elements/icons/animation/loading.d.ts +3 -0
  149. package/build/elements/icons/animation/loading.js +64 -0
  150. package/build/elements/icons/animation/snap.cjs +133 -0
  151. package/build/elements/icons/animation/snap.d.cts +8 -0
  152. package/build/elements/icons/animation/snap.d.ts +8 -0
  153. package/build/elements/icons/animation/snap.js +129 -0
  154. package/build/elements/icons/index.cjs +40 -0
  155. package/build/elements/icons/index.d.cts +24 -0
  156. package/build/elements/icons/index.d.ts +24 -0
  157. package/build/elements/icons/index.js +24 -0
  158. package/build/elements/icons/interface/anchor-add.cjs +35 -0
  159. package/build/elements/icons/interface/anchor-add.d.cts +3 -0
  160. package/build/elements/icons/interface/anchor-add.d.ts +3 -0
  161. package/build/elements/icons/interface/anchor-add.js +31 -0
  162. package/build/elements/icons/interface/anchor-remove.cjs +34 -0
  163. package/build/elements/icons/interface/anchor-remove.d.cts +3 -0
  164. package/build/elements/icons/interface/anchor-remove.d.ts +3 -0
  165. package/build/elements/icons/interface/anchor-remove.js +30 -0
  166. package/build/elements/icons/interface/arrow-up.cjs +30 -0
  167. package/build/elements/icons/interface/arrow-up.d.cts +3 -0
  168. package/build/elements/icons/interface/arrow-up.d.ts +3 -0
  169. package/build/elements/icons/interface/arrow-up.js +26 -0
  170. package/build/elements/icons/interface/arrows-vertical.cjs +30 -0
  171. package/build/elements/icons/interface/arrows-vertical.d.cts +3 -0
  172. package/build/elements/icons/interface/arrows-vertical.d.ts +3 -0
  173. package/build/elements/icons/interface/arrows-vertical.js +26 -0
  174. package/build/elements/icons/interface/bezier-angle.cjs +33 -0
  175. package/build/elements/icons/interface/bezier-angle.d.cts +3 -0
  176. package/build/elements/icons/interface/bezier-angle.d.ts +3 -0
  177. package/build/elements/icons/interface/bezier-angle.js +29 -0
  178. package/build/elements/icons/interface/bezier-distribute.cjs +34 -0
  179. package/build/elements/icons/interface/bezier-distribute.d.cts +3 -0
  180. package/build/elements/icons/interface/bezier-distribute.d.ts +3 -0
  181. package/build/elements/icons/interface/bezier-distribute.js +30 -0
  182. package/build/elements/icons/interface/bezier-length.cjs +31 -0
  183. package/build/elements/icons/interface/bezier-length.d.cts +3 -0
  184. package/build/elements/icons/interface/bezier-length.d.ts +3 -0
  185. package/build/elements/icons/interface/bezier-length.js +27 -0
  186. package/build/elements/icons/interface/bezier-mirror.cjs +31 -0
  187. package/build/elements/icons/interface/bezier-mirror.d.cts +3 -0
  188. package/build/elements/icons/interface/bezier-mirror.d.ts +3 -0
  189. package/build/elements/icons/interface/bezier-mirror.js +27 -0
  190. package/build/elements/icons/interface/bezier.cjs +26 -0
  191. package/build/elements/icons/interface/bezier.d.cts +3 -0
  192. package/build/elements/icons/interface/bezier.d.ts +3 -0
  193. package/build/elements/icons/interface/bezier.js +22 -0
  194. package/build/elements/icons/interface/check.cjs +30 -0
  195. package/build/elements/icons/interface/check.d.cts +3 -0
  196. package/build/elements/icons/interface/check.d.ts +3 -0
  197. package/build/elements/icons/interface/check.js +26 -0
  198. package/build/elements/icons/interface/circle-arrow-left.cjs +30 -0
  199. package/build/elements/icons/interface/circle-arrow-left.d.cts +3 -0
  200. package/build/elements/icons/interface/circle-arrow-left.d.ts +3 -0
  201. package/build/elements/icons/interface/circle-arrow-left.js +26 -0
  202. package/build/elements/icons/interface/circle-arrow-right.cjs +30 -0
  203. package/build/elements/icons/interface/circle-arrow-right.d.cts +3 -0
  204. package/build/elements/icons/interface/circle-arrow-right.d.ts +3 -0
  205. package/build/elements/icons/interface/circle-arrow-right.js +26 -0
  206. package/build/elements/icons/interface/code.cjs +30 -0
  207. package/build/elements/icons/interface/code.d.cts +3 -0
  208. package/build/elements/icons/interface/code.d.ts +3 -0
  209. package/build/elements/icons/interface/code.js +26 -0
  210. package/build/elements/icons/interface/dots.cjs +32 -0
  211. package/build/elements/icons/interface/dots.d.cts +3 -0
  212. package/build/elements/icons/interface/dots.d.ts +3 -0
  213. package/build/elements/icons/interface/dots.js +28 -0
  214. package/build/elements/icons/interface/mention.cjs +30 -0
  215. package/build/elements/icons/interface/mention.d.cts +3 -0
  216. package/build/elements/icons/interface/mention.d.ts +3 -0
  217. package/build/elements/icons/interface/mention.js +26 -0
  218. package/build/elements/icons/interface/minus.cjs +30 -0
  219. package/build/elements/icons/interface/minus.d.cts +3 -0
  220. package/build/elements/icons/interface/minus.d.ts +3 -0
  221. package/build/elements/icons/interface/minus.js +26 -0
  222. package/build/elements/icons/interface/picker.cjs +34 -0
  223. package/build/elements/icons/interface/picker.d.cts +3 -0
  224. package/build/elements/icons/interface/picker.d.ts +3 -0
  225. package/build/elements/icons/interface/picker.js +30 -0
  226. package/build/elements/icons/interface/plus.cjs +30 -0
  227. package/build/elements/icons/interface/plus.d.cts +3 -0
  228. package/build/elements/icons/interface/plus.d.ts +3 -0
  229. package/build/elements/icons/interface/plus.js +26 -0
  230. package/build/elements/icons/interface/settings.cjs +30 -0
  231. package/build/elements/icons/interface/settings.d.cts +3 -0
  232. package/build/elements/icons/interface/settings.d.ts +3 -0
  233. package/build/elements/icons/interface/settings.js +26 -0
  234. package/build/elements/index.cjs +62 -0
  235. package/build/elements/index.d.cts +22 -0
  236. package/build/elements/index.d.ts +22 -0
  237. package/build/elements/index.js +22 -0
  238. package/build/elements/input/index.cjs +273 -0
  239. package/build/elements/input/index.d.cts +17 -0
  240. package/build/elements/input/index.d.ts +17 -0
  241. package/build/elements/input/index.js +269 -0
  242. package/build/elements/logo/index.cjs +732 -0
  243. package/build/elements/logo/index.d.cts +17 -0
  244. package/build/elements/logo/index.d.ts +17 -0
  245. package/build/elements/logo/index.js +728 -0
  246. package/build/elements/monitor/fps.cjs +432 -0
  247. package/build/elements/monitor/fps.d.cts +21 -0
  248. package/build/elements/monitor/fps.d.ts +21 -0
  249. package/build/elements/monitor/fps.js +428 -0
  250. package/build/elements/monitor/index.cjs +670 -0
  251. package/build/elements/monitor/index.d.cts +112 -0
  252. package/build/elements/monitor/index.d.ts +112 -0
  253. package/build/elements/monitor/index.js +666 -0
  254. package/build/elements/number/index.cjs +173 -0
  255. package/build/elements/number/index.d.cts +19 -0
  256. package/build/elements/number/index.d.ts +19 -0
  257. package/build/elements/number/index.js +169 -0
  258. package/build/elements/origin/index.cjs +169 -0
  259. package/build/elements/origin/index.d.cts +12 -0
  260. package/build/elements/origin/index.d.ts +12 -0
  261. package/build/elements/origin/index.js +165 -0
  262. package/build/elements/popover/index.cjs +209 -0
  263. package/build/elements/popover/index.d.cts +19 -0
  264. package/build/elements/popover/index.d.ts +19 -0
  265. package/build/elements/popover/index.js +205 -0
  266. package/build/elements/radio/index.cjs +301 -0
  267. package/build/elements/radio/index.d.cts +13 -0
  268. package/build/elements/radio/index.d.ts +13 -0
  269. package/build/elements/radio/index.js +283 -0
  270. package/build/elements/radio/input.cjs +329 -0
  271. package/build/elements/radio/input.d.cts +15 -0
  272. package/build/elements/radio/input.d.ts +15 -0
  273. package/build/elements/radio/input.js +325 -0
  274. package/build/elements/radio/option.cjs +15 -0
  275. package/build/elements/radio/option.d.cts +3 -0
  276. package/build/elements/radio/option.d.ts +3 -0
  277. package/build/elements/radio/option.js +11 -0
  278. package/build/elements/shared.cjs +66 -0
  279. package/build/elements/shared.d.cts +40 -0
  280. package/build/elements/shared.d.ts +40 -0
  281. package/build/elements/shared.js +59 -0
  282. package/build/elements/slider/index.cjs +232 -0
  283. package/build/elements/slider/index.d.cts +20 -0
  284. package/build/elements/slider/index.d.ts +20 -0
  285. package/build/elements/slider/index.js +228 -0
  286. package/build/elements/state/index.cjs +681 -0
  287. package/build/elements/state/index.d.cts +86 -0
  288. package/build/elements/state/index.d.ts +86 -0
  289. package/build/elements/state/index.js +677 -0
  290. package/build/elements/toggle/index.cjs +151 -0
  291. package/build/elements/toggle/index.d.cts +9 -0
  292. package/build/elements/toggle/index.d.ts +9 -0
  293. package/build/elements/toggle/index.js +147 -0
  294. package/build/elements/tooltip/index.cjs +187 -0
  295. package/build/elements/tooltip/index.d.cts +17 -0
  296. package/build/elements/tooltip/index.d.ts +17 -0
  297. package/build/elements/tooltip/index.js +183 -0
  298. package/build/index.cjs +40 -0
  299. package/build/index.d.cts +6 -0
  300. package/build/index.d.ts +6 -0
  301. package/build/index.js +12 -0
  302. package/build/init.cjs +325 -0
  303. package/build/init.d.cts +157 -0
  304. package/build/init.d.ts +157 -0
  305. package/build/init.js +289 -0
  306. package/build/internal/component-loaders.cjs +206 -0
  307. package/build/internal/component-loaders.d.cts +52 -0
  308. package/build/internal/component-loaders.d.ts +52 -0
  309. package/build/internal/component-loaders.js +167 -0
  310. package/build/internal/fonts.cjs +128 -0
  311. package/build/internal/fonts.d.cts +32 -0
  312. package/build/internal/fonts.d.ts +32 -0
  313. package/build/internal/fonts.js +123 -0
  314. package/build/internal/lazy-load.cjs +89 -0
  315. package/build/internal/lazy-load.d.cts +32 -0
  316. package/build/internal/lazy-load.d.ts +32 -0
  317. package/build/internal/lazy-load.js +86 -0
  318. package/build/internal/style-inject.cjs +236 -0
  319. package/build/internal/style-inject.d.cts +44 -0
  320. package/build/internal/style-inject.d.ts +44 -0
  321. package/build/internal/style-inject.js +226 -0
  322. package/build/register.cjs +36 -0
  323. package/build/register.d.cts +32 -0
  324. package/build/register.d.ts +32 -0
  325. package/build/register.js +34 -0
  326. package/build/theme/index.cjs +452 -0
  327. package/build/theme/index.d.cts +146 -0
  328. package/build/theme/index.d.ts +146 -0
  329. package/build/theme/index.js +423 -0
  330. package/build/theme/presets.cjs +54 -0
  331. package/build/theme/presets.d.cts +19 -0
  332. package/build/theme/presets.d.ts +19 -0
  333. package/build/theme/presets.js +51 -0
  334. package/build/theme/registry.cjs +204 -0
  335. package/build/theme/registry.d.cts +99 -0
  336. package/build/theme/registry.d.ts +99 -0
  337. package/build/theme/registry.js +194 -0
  338. package/build/theme/tokens.cjs +148 -0
  339. package/build/theme/tokens.d.cts +163 -0
  340. package/build/theme/tokens.d.ts +163 -0
  341. package/build/theme/tokens.js +145 -0
  342. package/build/utils/dismiss-controller.cjs +77 -0
  343. package/build/utils/dismiss-controller.d.cts +14 -0
  344. package/build/utils/dismiss-controller.d.ts +14 -0
  345. package/build/utils/dismiss-controller.js +73 -0
  346. package/build/utils/index.cjs +18 -0
  347. package/build/utils/index.d.cts +3 -0
  348. package/build/utils/index.d.ts +3 -0
  349. package/build/utils/index.js +3 -0
  350. package/build/utils/outside-click.cjs +82 -0
  351. package/build/utils/outside-click.d.cts +18 -0
  352. package/build/utils/outside-click.d.ts +18 -0
  353. package/build/utils/outside-click.js +74 -0
  354. package/build/utils/template-helpers.cjs +39 -0
  355. package/build/utils/template-helpers.d.cts +13 -0
  356. package/build/utils/template-helpers.d.ts +13 -0
  357. package/build/utils/template-helpers.js +28 -0
  358. package/package.json +96 -0
@@ -0,0 +1,938 @@
1
+ import { html } from 'lit-html';
2
+ import { DEFAULT_HANDLE_LENGTH, EASING_PRESETS } from "./constants.js";
3
+ import { controlsStyles } from "./styles.js";
4
+ import { EasingType, MAX_LINEAR_POINTS, MIN_LINEAR_POINTS } from "./types.js";
5
+ import { clampPoint, MIN_LINEAR_DELTA, normalizeLinearPoints, normalizeVector, parseCubicBezierValue, parseLinearTimingFunction, smoothLinearPoints, vectorLength } from "./utils.js";
6
+ import { Component } from '~/decorators/Component';
7
+ import { Prop } from '~/decorators/Prop';
8
+ import { dispatchControlEvent } from '~/elements/shared';
9
+ import '~/elements/icons/interface/minus';
10
+ import '~/elements/icons/interface/plus';
11
+ import '~/elements/button';
12
+ const HOST_HANDLER_MAP = {
13
+ 'easing-type-change': 'handleEasingTypeChange',
14
+ 'grid-toggle': 'handleGridToggle',
15
+ 'snap-toggle': 'handleSnapToggle',
16
+ 'grid-size-change': 'handleGridSizeChange',
17
+ 'points-change': 'handlePointsChange',
18
+ 'linear-point-focus': 'handleLinearPointFocus'
19
+ };
20
+ @Component({
21
+ tag: 'ease-curve-controls',
22
+ styles: controlsStyles,
23
+ template() {
24
+ return html `
25
+ <ease-field label="Easing">
26
+ <ease-dropdown
27
+ part="easing-type-dropdown"
28
+ placeholder="Select"
29
+ id="demo-easing-type"
30
+ name="demo-easing-type"
31
+ searchable
32
+ .value=${this._activePresetValue}
33
+ @change=${this.handlePresetSelection}
34
+ >
35
+ ${EASING_PRESETS.map(({ label, options }, index) => html `
36
+ ${index > 0 ? html `<hr slot="content" />` : null}
37
+
38
+ <h4 slot="content">${label}</h4>
39
+ ${options.map(({ value, label }) => html `
40
+ <button slot="content" type="button" value=${value}>${label}</button>`)}
41
+ `)}
42
+ </ease-dropdown>
43
+ </ease-field>
44
+
45
+ <ease-field label="Type">
46
+ <ease-radio-group .value=${this.easingType} @change=${this.handleTypeChange} part="radio-group">
47
+ <button slot="content" value=${EasingType.CUBIC_BEZIER}>Cubic</button>
48
+ <button slot="content" value=${EasingType.LINEAR}>Linear</button>
49
+ </ease-radio-group>
50
+ </ease-field>
51
+ `;
52
+ }
53
+ })
54
+ export class CurveControls extends HTMLElement {
55
+ @Prop({ reflect: true })
56
+ accessor easingType;
57
+ @Prop({
58
+ type: Object,
59
+ reflect: false,
60
+ onChange() {
61
+ this.handlePointsPropChange();
62
+ }
63
+ })
64
+ accessor points;
65
+ @Prop({ type: Boolean, reflect: true, defaultValue: true })
66
+ accessor showGrid;
67
+ @Prop({ type: Boolean, reflect: true, defaultValue: false })
68
+ accessor snapToGrid;
69
+ @Prop({ type: Number, reflect: true, defaultValue: 8 })
70
+ accessor gridSize = 8;
71
+ @Prop({
72
+ type: Number,
73
+ reflect: false,
74
+ defaultValue: null,
75
+ onChange(value) {
76
+ this.handleFocusedLinearIndexPropChange(value);
77
+ }
78
+ })
79
+ accessor focusedLinearIndex = null;
80
+ _activePresetValue = null;
81
+ isApplyingPreset = false;
82
+ #setActivePreset(value) {
83
+ if (this._activePresetValue === value) {
84
+ return;
85
+ }
86
+ this._activePresetValue = value;
87
+ this.requestRender();
88
+ }
89
+ #clearActivePreset() {
90
+ if (this._activePresetValue === null) {
91
+ return;
92
+ }
93
+ this._activePresetValue = null;
94
+ this.requestRender();
95
+ }
96
+ handleFocusedLinearIndexPropChange(_value) {
97
+ this.requestRender();
98
+ }
99
+ handlePointsPropChange() {
100
+ if (this.isApplyingPreset) {
101
+ return;
102
+ }
103
+ this.#clearActivePreset();
104
+ }
105
+ get maxPointsReached() {
106
+ const points = this.#getLinearPoints();
107
+ return points !== null && points.length >= MAX_LINEAR_POINTS;
108
+ }
109
+ get minPointsReached() {
110
+ const points = this.#getLinearPoints();
111
+ return points !== null && points.length <= MIN_LINEAR_POINTS;
112
+ }
113
+ #getEventTarget = () => {
114
+ const root = this.getRootNode();
115
+ if (root instanceof ShadowRoot && root.host instanceof HTMLElement) {
116
+ return root.host;
117
+ }
118
+ return this;
119
+ };
120
+ #getLinearPoints = () => {
121
+ if (!Array.isArray(this.points)) {
122
+ return null;
123
+ }
124
+ return this.points;
125
+ };
126
+ renderLinearDetailsControls() {
127
+ const points = this.#getLinearPoints();
128
+ if (!points) {
129
+ return null;
130
+ }
131
+ if (points.length < MIN_LINEAR_POINTS) {
132
+ return html `
133
+ <div class="control-group details-group">
134
+ <span class="details-empty">Curve requires at least 2 points.</span>
135
+ </div>
136
+ `;
137
+ }
138
+ const focusedIndex = this.focusedLinearIndex;
139
+ if (focusedIndex === null) {
140
+ return html `
141
+ <div class="control-group details-group">
142
+ <div class="details-panel">
143
+ <span class="details-empty">Select a point on the canvas to edit its details.</span>
144
+ </div>
145
+ </div>
146
+ `;
147
+ }
148
+ const selectedPoint = points[focusedIndex];
149
+ if (!selectedPoint) {
150
+ return null;
151
+ }
152
+ const previousIndex = focusedIndex > 0 ? focusedIndex - 1 : null;
153
+ const nextIndex = focusedIndex < points.length - 1 ? focusedIndex + 1 : null;
154
+ const isInnerPoint = focusedIndex > 0 && focusedIndex < points.length - 1;
155
+ const pointMeta = `${(selectedPoint.x * 100).toFixed(0)}%, ${(selectedPoint.y * 100).toFixed(0)}%`;
156
+ const pointType = selectedPoint.isLinked ? 'Smooth' : 'Corner';
157
+ const hasInHandle = selectedPoint.cpInX !== undefined || selectedPoint.cpInY !== undefined;
158
+ const hasOutHandle = selectedPoint.cpOutX !== undefined || selectedPoint.cpOutY !== undefined;
159
+ const hasAnyHandle = hasInHandle || hasOutHandle;
160
+ return html `
161
+ <div class="control-group details-group">
162
+ <div class="details-panel" role="group" aria-label="Linear point details">
163
+ <div class="point-navigation">
164
+ <button
165
+ type="button"
166
+ class="control-button"
167
+ @click=${(event) => this.#focusIndex(event, previousIndex)}
168
+ ?disabled=${previousIndex === null}
169
+ >
170
+ Previous
171
+ </button>
172
+ <div class="point-state" aria-live="polite">
173
+ <span>P${focusedIndex + 1}</span>
174
+ <span>${pointMeta}</span>
175
+ <span>(${pointType})</span>
176
+ </div>
177
+ <button
178
+ type="button"
179
+ class="control-button"
180
+ @click=${(event) => this.#focusIndex(event, nextIndex)}
181
+ ?disabled=${nextIndex === null}
182
+ >
183
+ Next
184
+ </button>
185
+ </div>
186
+
187
+ <div class="coordinate-editor" role="group" aria-label="Point coordinates">
188
+ <label class="coordinate-field">
189
+ <span>X</span>
190
+ <input
191
+ type="number"
192
+ min="0"
193
+ max="1"
194
+ step="0.01"
195
+ .value=${selectedPoint.x.toFixed(2)}
196
+ data-axis="x"
197
+ ?disabled=${!isInnerPoint}
198
+ @change=${this.#handleCoordinateInput}
199
+ />
200
+ </label>
201
+ <label class="coordinate-field">
202
+ <span>Y</span>
203
+ <input
204
+ type="number"
205
+ min="0"
206
+ max="1"
207
+ step="0.01"
208
+ .value=${selectedPoint.y.toFixed(2)}
209
+ data-axis="y"
210
+ @change=${this.#handleCoordinateInput}
211
+ />
212
+ </label>
213
+ <button
214
+ type="button"
215
+ class="coordinate-remove"
216
+ ?disabled=${!isInnerPoint || points.length <= MIN_LINEAR_POINTS}
217
+ @click=${this.#handleRemoveSelectedPoint}
218
+ >
219
+ Remove
220
+ </button>
221
+ </div>
222
+
223
+ ${isInnerPoint
224
+ ? html `
225
+ <div class="point-type-controls" role="group" aria-label="Point type controls">
226
+ <button
227
+ type="button"
228
+ class="control-button ${!selectedPoint.isLinked ? 'active' : ''}"
229
+ data-type="corner"
230
+ @click=${this.#handlePointTypeChange}
231
+ >
232
+ Corner (Independent)
233
+ </button>
234
+ <button
235
+ type="button"
236
+ class="control-button ${selectedPoint.isLinked ? 'active' : ''}"
237
+ data-type="smooth"
238
+ @click=${this.#handlePointTypeChange}
239
+ >
240
+ Smooth (Linked)
241
+ </button>
242
+ <button
243
+ type="button"
244
+ class="control-button ${selectedPoint.isLinked ? 'active' : ''}"
245
+ data-type="mirror-angle"
246
+ @click=${this.#handlePointTypeChange}
247
+ >
248
+ Mirror Angle: ${selectedPoint.isLinked ? 'On' : 'Off'}
249
+ </button>
250
+ <button
251
+ type="button"
252
+ class="control-button ${selectedPoint.mirrorLength !== false ? 'active' : ''}"
253
+ data-type="mirror-length"
254
+ @click=${this.#handlePointTypeChange}
255
+ ?disabled=${!selectedPoint.isLinked}
256
+ >
257
+ Mirror Length: ${selectedPoint.mirrorLength === false ? 'Off' : 'On'}
258
+ </button>
259
+ </div>
260
+
261
+ <div class="handle-actions" role="group" aria-label="Handle removal controls">
262
+ <button
263
+ type="button"
264
+ class="control-button"
265
+ data-handle="both"
266
+ @click=${this.#handleRemoveHandles}
267
+ ?disabled=${!hasAnyHandle}
268
+ >
269
+ Remove Both Handles
270
+ </button>
271
+ <button
272
+ type="button"
273
+ class="control-button"
274
+ data-handle="in"
275
+ @click=${this.#handleRemoveHandles}
276
+ ?disabled=${!hasInHandle}
277
+ >
278
+ Remove Incoming Handle
279
+ </button>
280
+ <button
281
+ type="button"
282
+ class="control-button"
283
+ data-handle="out"
284
+ @click=${this.#handleRemoveHandles}
285
+ ?disabled=${!hasOutHandle}
286
+ >
287
+ Remove Outgoing Handle
288
+ </button>
289
+ </div>
290
+ `
291
+ : this.#renderEndpointHandleControls(selectedPoint, focusedIndex)}
292
+ </div>
293
+ </div>
294
+ `;
295
+ }
296
+ #handleCoordinateInput = (event) => {
297
+ const target = event.target;
298
+ if (!(target instanceof HTMLInputElement)) {
299
+ return;
300
+ }
301
+ const axis = target.dataset.axis;
302
+ if (axis !== 'x' && axis !== 'y') {
303
+ return;
304
+ }
305
+ const points = this.#getLinearPoints();
306
+ if (!points) {
307
+ return;
308
+ }
309
+ const index = this.focusedLinearIndex;
310
+ if (index === null) {
311
+ return;
312
+ }
313
+ const selectedPoint = points[index];
314
+ if (!selectedPoint) {
315
+ return;
316
+ }
317
+ const parsed = Number.parseFloat(target.value);
318
+ if (Number.isNaN(parsed)) {
319
+ target.value = selectedPoint[axis].toFixed(2);
320
+ return;
321
+ }
322
+ const updatedPoints = points.map((point, pointIndex) => {
323
+ if (pointIndex !== index) {
324
+ return { ...point };
325
+ }
326
+ const updated = { ...point };
327
+ if (axis === 'x') {
328
+ if (index === 0 || index === points.length - 1) {
329
+ return updated;
330
+ }
331
+ const previous = points[index - 1];
332
+ const next = points[index + 1];
333
+ const minX = previous ? previous.x + MIN_LINEAR_DELTA : 0;
334
+ const maxX = next ? next.x - MIN_LINEAR_DELTA : 1;
335
+ updated.x = Math.min(Math.max(parsed, minX), maxX);
336
+ }
337
+ else {
338
+ updated.y = Math.min(Math.max(parsed, 0), 1);
339
+ }
340
+ return updated;
341
+ });
342
+ const normalizedPoints = normalizeLinearPoints(updatedPoints);
343
+ this.#emitPoints(normalizedPoints, event);
344
+ this.focusedLinearIndex = index;
345
+ this.#notifyHost('linear-point-focus', index, event);
346
+ const refreshedPoint = normalizedPoints[index];
347
+ if (refreshedPoint) {
348
+ target.value = refreshedPoint[axis].toFixed(2);
349
+ }
350
+ };
351
+ #handleRemoveSelectedPoint = (event) => {
352
+ const index = this.focusedLinearIndex;
353
+ if (index === null) {
354
+ return;
355
+ }
356
+ this.#removeLinearPointAt(index, event);
357
+ };
358
+ #handlePointTypeChange = (event) => {
359
+ event.preventDefault();
360
+ const target = event.currentTarget;
361
+ if (!(target instanceof HTMLButtonElement)) {
362
+ return;
363
+ }
364
+ const type = target.dataset.type;
365
+ const points = this.#getLinearPoints();
366
+ if (!points) {
367
+ return;
368
+ }
369
+ const index = this.focusedLinearIndex;
370
+ if (index === null) {
371
+ return;
372
+ }
373
+ const point = points[index];
374
+ if (!point) {
375
+ return;
376
+ }
377
+ const updatedPoints = [...points];
378
+ const updatedPoint = { ...point };
379
+ updatedPoints[index] = updatedPoint;
380
+ switch (type) {
381
+ case 'corner': {
382
+ updatedPoint.isLinked = false;
383
+ updatedPoint.mirrorLength = false;
384
+ break;
385
+ }
386
+ case 'smooth': {
387
+ this.#ensureSmoothHandles(updatedPoint, index, points);
388
+ updatedPoint.isLinked = true;
389
+ updatedPoint.mirrorLength = true;
390
+ this.#alignMirrorLength(updatedPoint);
391
+ break;
392
+ }
393
+ case 'mirror-angle': {
394
+ const nextState = !updatedPoint.isLinked;
395
+ if (nextState) {
396
+ this.#ensureSmoothHandles(updatedPoint, index, points);
397
+ updatedPoint.mirrorLength = true;
398
+ this.#alignMirrorLength(updatedPoint);
399
+ }
400
+ else {
401
+ updatedPoint.mirrorLength = false;
402
+ }
403
+ updatedPoint.isLinked = nextState;
404
+ break;
405
+ }
406
+ case 'mirror-length': {
407
+ if (!updatedPoint.isLinked) {
408
+ break;
409
+ }
410
+ const nextState = updatedPoint.mirrorLength === false;
411
+ updatedPoint.mirrorLength = nextState;
412
+ if (nextState) {
413
+ this.#ensureSmoothHandles(updatedPoint, index, points);
414
+ this.#alignMirrorLength(updatedPoint);
415
+ }
416
+ break;
417
+ }
418
+ default:
419
+ return;
420
+ }
421
+ const normalizedPoints = normalizeLinearPoints(updatedPoints);
422
+ this.#emitPoints(normalizedPoints, event);
423
+ this.focusedLinearIndex = index;
424
+ this.#notifyHost('linear-point-focus', index, event);
425
+ };
426
+ #ensureSmoothHandles(point, index, points) {
427
+ const prev = points[index - 1];
428
+ const next = points[index + 1];
429
+ let refDx = point.cpOutX ?? -(point.cpInX ?? 0);
430
+ let refDy = point.cpOutY ?? -(point.cpInY ?? 0);
431
+ if (refDx === 0 && refDy === 0) {
432
+ let dirX = 0;
433
+ let dirY = 0;
434
+ if (prev && next) {
435
+ dirX = next.x - prev.x;
436
+ dirY = next.y - prev.y;
437
+ }
438
+ else if (next) {
439
+ dirX = next.x - point.x;
440
+ dirY = next.y - point.y;
441
+ }
442
+ else if (prev) {
443
+ dirX = point.x - prev.x;
444
+ dirY = point.y - prev.y;
445
+ }
446
+ else {
447
+ dirX = 1;
448
+ dirY = 0;
449
+ }
450
+ const direction = normalizeVector(dirX, dirY);
451
+ const length = DEFAULT_HANDLE_LENGTH;
452
+ point.cpInX = -direction.dx * length;
453
+ point.cpInY = -direction.dy * length;
454
+ point.cpOutX = direction.dx * length;
455
+ point.cpOutY = direction.dy * length;
456
+ refDx = point.cpOutX;
457
+ refDy = point.cpOutY;
458
+ }
459
+ if (refDx === 0 && refDy === 0) {
460
+ return;
461
+ }
462
+ const normalized = normalizeVector(refDx, refDy);
463
+ const referenceLength = vectorLength(refDx, refDy);
464
+ const inLength = vectorLength(point.cpInX ?? 0, point.cpInY ?? 0) || referenceLength;
465
+ point.cpInX = -normalized.dx * inLength;
466
+ point.cpInY = -normalized.dy * inLength;
467
+ const outLength = vectorLength(point.cpOutX ?? 0, point.cpOutY ?? 0) || referenceLength;
468
+ point.cpOutX = normalized.dx * outLength;
469
+ point.cpOutY = normalized.dy * outLength;
470
+ }
471
+ #alignMirrorLength(point) {
472
+ if (!point.isLinked) {
473
+ point.mirrorLength = false;
474
+ return;
475
+ }
476
+ const outLength = vectorLength(point.cpOutX ?? 0, point.cpOutY ?? 0);
477
+ const inLength = vectorLength(point.cpInX ?? 0, point.cpInY ?? 0);
478
+ const targetLength = Math.max(outLength, inLength);
479
+ if (targetLength <= 0) {
480
+ return;
481
+ }
482
+ if (outLength > 0) {
483
+ const outDirection = normalizeVector(point.cpOutX ?? 0, point.cpOutY ?? 0);
484
+ point.cpOutX = outDirection.dx * targetLength;
485
+ point.cpOutY = outDirection.dy * targetLength;
486
+ }
487
+ else if (inLength > 0) {
488
+ const inDirection = normalizeVector(point.cpInX ?? 0, point.cpInY ?? 0);
489
+ point.cpOutX = -inDirection.dx * targetLength;
490
+ point.cpOutY = -inDirection.dy * targetLength;
491
+ }
492
+ if (inLength > 0) {
493
+ const inDirection = normalizeVector(point.cpInX ?? 0, point.cpInY ?? 0);
494
+ point.cpInX = inDirection.dx * targetLength;
495
+ point.cpInY = inDirection.dy * targetLength;
496
+ }
497
+ else if (outLength > 0) {
498
+ const outDirection = normalizeVector(point.cpOutX ?? 0, point.cpOutY ?? 0);
499
+ point.cpInX = -outDirection.dx * targetLength;
500
+ point.cpInY = -outDirection.dy * targetLength;
501
+ }
502
+ }
503
+ #handleRemoveHandles = (event) => {
504
+ event.preventDefault();
505
+ const target = event.currentTarget;
506
+ if (!(target instanceof HTMLButtonElement)) {
507
+ return;
508
+ }
509
+ const mode = target.dataset.handle ?? 'both';
510
+ const points = this.#getLinearPoints();
511
+ if (!points) {
512
+ return;
513
+ }
514
+ const index = this.focusedLinearIndex;
515
+ if (index === null) {
516
+ return;
517
+ }
518
+ const updatedPoints = [...points];
519
+ const existingPoint = updatedPoints[index];
520
+ if (!existingPoint) {
521
+ return;
522
+ }
523
+ const updatedPoint = { ...existingPoint };
524
+ updatedPoints[index] = updatedPoint;
525
+ const removeIncoming = mode === 'both' || mode === 'in';
526
+ const removeOutgoing = mode === 'both' || mode === 'out';
527
+ if (removeIncoming) {
528
+ delete updatedPoint.cpInX;
529
+ delete updatedPoint.cpInY;
530
+ }
531
+ if (removeOutgoing) {
532
+ delete updatedPoint.cpOutX;
533
+ delete updatedPoint.cpOutY;
534
+ }
535
+ if (removeIncoming && removeOutgoing) {
536
+ delete updatedPoint.isLinked;
537
+ delete updatedPoint.mirrorLength;
538
+ }
539
+ else if (removeIncoming || removeOutgoing) {
540
+ updatedPoint.isLinked = false;
541
+ updatedPoint.mirrorLength = false;
542
+ }
543
+ const normalizedPoints = normalizeLinearPoints(updatedPoints);
544
+ this.#emitPoints(normalizedPoints, event);
545
+ this.focusedLinearIndex = index;
546
+ this.#notifyHost('linear-point-focus', index, event);
547
+ };
548
+ #renderEndpointHandleControls(point, index) {
549
+ const isStart = index === 0;
550
+ const handleKey = isStart ? 'out' : 'in';
551
+ const handleLabel = isStart ? 'Outgoing' : 'Incoming';
552
+ const hasHandle = handleKey === 'out'
553
+ ? point.cpOutX !== undefined || point.cpOutY !== undefined
554
+ : point.cpInX !== undefined || point.cpInY !== undefined;
555
+ return html `
556
+ <div class="handle-actions" role="group" aria-label="${handleLabel} handle controls">
557
+ <button
558
+ type="button"
559
+ class="control-button"
560
+ data-handle=${handleKey}
561
+ @click=${this.#handleEndpointHandleAdd}
562
+ ?disabled=${hasHandle}
563
+ >
564
+ Add ${handleLabel} Handle
565
+ </button>
566
+ <button
567
+ type="button"
568
+ class="control-button"
569
+ data-handle=${handleKey}
570
+ @click=${this.#handleRemoveHandles}
571
+ ?disabled=${!hasHandle}
572
+ >
573
+ Remove ${handleLabel} Handle
574
+ </button>
575
+ </div>
576
+ `;
577
+ }
578
+ #handleEndpointHandleAdd = (event) => {
579
+ event.preventDefault();
580
+ const target = event.currentTarget;
581
+ if (!(target instanceof HTMLButtonElement)) {
582
+ return;
583
+ }
584
+ const handle = target.dataset.handle;
585
+ if (handle !== 'in' && handle !== 'out') {
586
+ return;
587
+ }
588
+ const points = this.#getLinearPoints();
589
+ if (!points) {
590
+ return;
591
+ }
592
+ const index = this.focusedLinearIndex;
593
+ if (index === null) {
594
+ return;
595
+ }
596
+ const neighborIndex = handle === 'out' ? index + 1 : index - 1;
597
+ const neighbor = points[neighborIndex];
598
+ if (!neighbor) {
599
+ return;
600
+ }
601
+ const updatedPoints = [...points];
602
+ const existingPoint = updatedPoints[index];
603
+ if (!existingPoint) {
604
+ return;
605
+ }
606
+ const updatedPoint = { ...existingPoint };
607
+ updatedPoints[index] = updatedPoint;
608
+ this.#createEndpointHandle(updatedPoint, neighbor, handle);
609
+ const normalizedPoints = normalizeLinearPoints(updatedPoints);
610
+ this.#emitPoints(normalizedPoints, event);
611
+ this.focusedLinearIndex = index;
612
+ this.#notifyHost('linear-point-focus', index, event);
613
+ };
614
+ #createEndpointHandle(point, neighbor, handle) {
615
+ const dx = neighbor.x - point.x;
616
+ const dy = neighbor.y - point.y;
617
+ const direction = normalizeVector(dx, dy);
618
+ const fallbackDirection = handle === 'out' ? { dx: 1, dy: 0 } : { dx: -1, dy: 0 };
619
+ const finalDirection = direction.dx === 0 && direction.dy === 0 ? fallbackDirection : direction;
620
+ const gap = Math.abs(dx);
621
+ const baseLength = gap > 0 ? gap * 0.5 : MIN_LINEAR_DELTA * 0.5;
622
+ const length = Math.min(DEFAULT_HANDLE_LENGTH, Math.max(MIN_LINEAR_DELTA * 0.5, baseLength));
623
+ if (handle === 'out') {
624
+ point.cpOutX = finalDirection.dx * length;
625
+ point.cpOutY = finalDirection.dy * length;
626
+ }
627
+ else {
628
+ point.cpInX = finalDirection.dx * length;
629
+ point.cpInY = finalDirection.dy * length;
630
+ }
631
+ point.isLinked = false;
632
+ point.mirrorLength = false;
633
+ }
634
+ #removeLinearPointAt(index, event) {
635
+ const points = this.#getLinearPoints();
636
+ if (!points || points.length <= MIN_LINEAR_POINTS) {
637
+ return;
638
+ }
639
+ if (index <= 0 || index >= points.length - 1) {
640
+ return;
641
+ }
642
+ const updatedPoints = points
643
+ .filter((_, pointIndex) => pointIndex !== index)
644
+ .map((point) => {
645
+ return { ...point };
646
+ });
647
+ const normalizedPoints = normalizeLinearPoints(updatedPoints);
648
+ this.#emitPoints(normalizedPoints, event);
649
+ const nextIndex = index - 1;
650
+ this.focusedLinearIndex = nextIndex;
651
+ this.#notifyHost('linear-point-focus', nextIndex, event);
652
+ }
653
+ resetCurve = (event) => {
654
+ event.preventDefault();
655
+ this.#clearActivePreset();
656
+ if (this.easingType === EasingType.CUBIC_BEZIER) {
657
+ const defaults = {
658
+ p1: { x: 0.25, y: 0.1 },
659
+ p2: { x: 0.25, y: 1 }
660
+ };
661
+ this.points = defaults;
662
+ this.#notifyHost('points-change', defaults, event);
663
+ return;
664
+ }
665
+ const defaults = [
666
+ { x: 0, y: 0 },
667
+ { x: 1, y: 1 }
668
+ ];
669
+ this.points = defaults;
670
+ this.#notifyHost('points-change', defaults, event);
671
+ this.focusedLinearIndex = null;
672
+ this.#notifyHost('linear-point-focus', null, event);
673
+ };
674
+ distributeLinearPoints = (event) => {
675
+ event.preventDefault();
676
+ const points = this.#getLinearPoints();
677
+ if (!points || points.length <= MIN_LINEAR_POINTS) {
678
+ return;
679
+ }
680
+ const lastIndex = points.length - 1;
681
+ const step = lastIndex > 0 ? 1 / lastIndex : 0;
682
+ const distributed = points.map((point, index) => {
683
+ const updated = { ...point };
684
+ if (index === 0) {
685
+ updated.x = 0;
686
+ }
687
+ else if (index === lastIndex) {
688
+ updated.x = 1;
689
+ }
690
+ else {
691
+ updated.x = Number.parseFloat((step * index).toFixed(4));
692
+ }
693
+ delete updated.cpInX;
694
+ delete updated.cpInY;
695
+ delete updated.cpOutX;
696
+ delete updated.cpOutY;
697
+ delete updated.isLinked;
698
+ return updated;
699
+ });
700
+ const normalizedPoints = normalizeLinearPoints(distributed);
701
+ this.#emitPoints(normalizedPoints, event);
702
+ if (this.focusedLinearIndex !== null) {
703
+ this.#notifyHost('linear-point-focus', this.focusedLinearIndex, event);
704
+ }
705
+ };
706
+ #focusIndex = (event, index) => {
707
+ event.preventDefault();
708
+ if (index !== null) {
709
+ this.focusedLinearIndex = index;
710
+ this.#notifyHost('linear-point-focus', index, event);
711
+ }
712
+ };
713
+ #setEasingType = (type, event) => {
714
+ if (this.easingType === type) {
715
+ return;
716
+ }
717
+ if (!this.isApplyingPreset) {
718
+ this.#clearActivePreset();
719
+ }
720
+ this.easingType = type;
721
+ this.#notifyHost('easing-type-change', type, event);
722
+ };
723
+ #emitPoints = (points, event) => {
724
+ if (!this.isApplyingPreset) {
725
+ this.#clearActivePreset();
726
+ }
727
+ this.points = points;
728
+ this.#notifyHost('points-change', points, event);
729
+ };
730
+ #applyPreset = (points, type, presetValue, event) => {
731
+ this.isApplyingPreset = true;
732
+ if (this.easingType !== type) {
733
+ this.#setEasingType(type, event);
734
+ }
735
+ this.points = points;
736
+ this.focusedLinearIndex = null;
737
+ this.#setActivePreset(presetValue);
738
+ this.#notifyHost('points-change', points, event);
739
+ if (type === EasingType.LINEAR) {
740
+ this.#notifyHost('linear-point-focus', null, event);
741
+ }
742
+ this.isApplyingPreset = false;
743
+ };
744
+ handleTypeChange = (event) => {
745
+ const detail = event.detail;
746
+ if (!detail) {
747
+ return;
748
+ }
749
+ const { value, event: originEvent } = detail;
750
+ if (value === EasingType.CUBIC_BEZIER || value === EasingType.LINEAR) {
751
+ this.#clearActivePreset();
752
+ this.#setEasingType(value, originEvent ?? event);
753
+ }
754
+ };
755
+ handlePresetSelection = (event) => {
756
+ const detail = event.detail;
757
+ if (!detail) {
758
+ return;
759
+ }
760
+ const rawValue = detail.value;
761
+ const originEvent = detail.event ?? event;
762
+ if (typeof rawValue !== 'string') {
763
+ return;
764
+ }
765
+ const cssValue = rawValue.trim();
766
+ if (cssValue.length === 0) {
767
+ this.#clearActivePreset();
768
+ return;
769
+ }
770
+ if (cssValue.startsWith(EasingType.CUBIC_BEZIER)) {
771
+ const parsed = parseCubicBezierValue(cssValue);
772
+ if (!parsed) {
773
+ this.#clearActivePreset();
774
+ return;
775
+ }
776
+ this.#applyPreset(parsed, EasingType.CUBIC_BEZIER, cssValue, originEvent);
777
+ return;
778
+ }
779
+ if (cssValue.startsWith(EasingType.LINEAR)) {
780
+ const parsedLinear = parseLinearTimingFunction(cssValue);
781
+ if (!parsedLinear) {
782
+ this.#clearActivePreset();
783
+ return;
784
+ }
785
+ // Apply smoothing to linear presets for better visual curves
786
+ const smoothedLinear = smoothLinearPoints(parsedLinear, 0.3);
787
+ this.#applyPreset(smoothedLinear, EasingType.LINEAR, cssValue, originEvent);
788
+ return;
789
+ }
790
+ this.#clearActivePreset();
791
+ };
792
+ handleSelectCubic = (event) => {
793
+ event.preventDefault();
794
+ this.#setEasingType(EasingType.CUBIC_BEZIER, event);
795
+ };
796
+ handleSelectLinear = (event) => {
797
+ event.preventDefault();
798
+ this.#setEasingType(EasingType.LINEAR, event);
799
+ };
800
+ addLinearPoint = (event) => {
801
+ event.preventDefault();
802
+ if (this.easingType !== EasingType.LINEAR) {
803
+ return;
804
+ }
805
+ const points = this.#getLinearPoints();
806
+ if (!points || this.maxPointsReached) {
807
+ return;
808
+ }
809
+ let insertIndex = 0;
810
+ let largestGap = -Infinity;
811
+ for (let index = 0; index < points.length - 1; index += 1) {
812
+ const start = points[index];
813
+ const end = points[index + 1];
814
+ if (!start || !end) {
815
+ continue;
816
+ }
817
+ const gap = end.x - start.x;
818
+ if (gap > largestGap) {
819
+ largestGap = gap;
820
+ insertIndex = index;
821
+ }
822
+ }
823
+ const startPoint = points[insertIndex];
824
+ const endPoint = points[insertIndex + 1];
825
+ if (!startPoint || !endPoint) {
826
+ return;
827
+ }
828
+ const newPointPosition = clampPoint({
829
+ x: (startPoint.x + endPoint.x) / 2,
830
+ y: (startPoint.y + endPoint.y) / 2
831
+ });
832
+ const newPoint = {
833
+ x: newPointPosition.x,
834
+ y: newPointPosition.y,
835
+ cpInX: -0.1,
836
+ cpInY: 0,
837
+ cpOutX: 0.1,
838
+ cpOutY: 0,
839
+ isLinked: true,
840
+ mirrorLength: true
841
+ };
842
+ const updatedPoints = [
843
+ ...points.slice(0, insertIndex + 1),
844
+ newPoint,
845
+ ...points.slice(insertIndex + 1)
846
+ ];
847
+ const insertedIndex = insertIndex + 1;
848
+ const normalizedPoints = normalizeLinearPoints(updatedPoints);
849
+ this.#emitPoints(normalizedPoints, event);
850
+ this.focusedLinearIndex = insertedIndex;
851
+ this.#notifyHost('linear-point-focus', insertedIndex, event);
852
+ };
853
+ removeLinearPoint = (event) => {
854
+ event.preventDefault();
855
+ if (this.easingType !== EasingType.LINEAR) {
856
+ return;
857
+ }
858
+ const points = this.#getLinearPoints();
859
+ if (!points || this.minPointsReached) {
860
+ return;
861
+ }
862
+ let removeIndex = 1;
863
+ let smallestSpan = Number.POSITIVE_INFINITY;
864
+ for (let index = 1; index < points.length - 1; index += 1) {
865
+ const previous = points[index - 1];
866
+ const next = points[index + 1];
867
+ if (!previous || !next) {
868
+ continue;
869
+ }
870
+ const span = next.x - previous.x;
871
+ if (span < smallestSpan) {
872
+ smallestSpan = span;
873
+ removeIndex = index;
874
+ }
875
+ }
876
+ if (removeIndex > 0 && removeIndex < points.length - 1) {
877
+ this.#removeLinearPointAt(removeIndex, event);
878
+ }
879
+ };
880
+ toggleGrid = (event) => {
881
+ event.preventDefault();
882
+ const nextValue = !this.showGrid;
883
+ this.showGrid = nextValue;
884
+ this.#notifyHost('grid-toggle', nextValue, event);
885
+ };
886
+ toggleSnapToGrid = (event) => {
887
+ event.preventDefault();
888
+ const nextValue = !this.snapToGrid;
889
+ this.snapToGrid = nextValue;
890
+ this.#notifyHost('snap-toggle', nextValue, event);
891
+ };
892
+ incrementGridSize = (event) => {
893
+ event.preventDefault();
894
+ this.#commitGridSize(this.gridSize + 1, event);
895
+ };
896
+ decrementGridSize = (event) => {
897
+ event.preventDefault();
898
+ this.#commitGridSize(this.gridSize - 1, event);
899
+ };
900
+ handleGridSliderInput = (event) => {
901
+ event.preventDefault();
902
+ const target = event.target;
903
+ if (!(target instanceof HTMLInputElement)) {
904
+ return;
905
+ }
906
+ this.#commitGridSize(Number(target.value), event);
907
+ };
908
+ handleGridSizeChange = (event) => {
909
+ event.preventDefault();
910
+ const target = event.target;
911
+ if (!(target instanceof HTMLInputElement)) {
912
+ return;
913
+ }
914
+ const parsed = Number(target.value);
915
+ if (Number.isNaN(parsed)) {
916
+ target.value = String(this.gridSize);
917
+ return;
918
+ }
919
+ this.#commitGridSize(parsed, event);
920
+ target.value = String(this.gridSize);
921
+ };
922
+ #clampGridSize = (value) => Math.max(1, Math.min(24, Math.round(value)));
923
+ #commitGridSize = (value, event) => {
924
+ const nextValue = this.#clampGridSize(value);
925
+ if (this.gridSize === nextValue) {
926
+ this.requestRender();
927
+ return;
928
+ }
929
+ this.gridSize = nextValue;
930
+ this.#notifyHost('grid-size-change', this.gridSize, event);
931
+ this.requestRender();
932
+ };
933
+ #notifyHost = (type, value, event) => {
934
+ const target = this.#getEventTarget();
935
+ const detail = { value, event };
936
+ dispatchControlEvent(target, type, detail);
937
+ };
938
+ }