@blotch/dolan 0.0.0-stage → 22.2.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 (273) hide show
  1. package/dist/commands/add.d.ts +22 -0
  2. package/dist/commands/add.js +132 -0
  3. package/dist/commands/add.js.map +1 -0
  4. package/dist/commands/add.test.d.ts +1 -0
  5. package/dist/commands/add.test.js +209 -0
  6. package/dist/commands/add.test.js.map +1 -0
  7. package/dist/commands/diff.d.ts +13 -0
  8. package/dist/commands/diff.js +155 -0
  9. package/dist/commands/diff.js.map +1 -0
  10. package/dist/commands/diff.test.d.ts +1 -0
  11. package/dist/commands/diff.test.js +327 -0
  12. package/dist/commands/diff.test.js.map +1 -0
  13. package/dist/commands/doctor.d.ts +15 -0
  14. package/dist/commands/doctor.js +248 -0
  15. package/dist/commands/doctor.js.map +1 -0
  16. package/dist/commands/doctor.test.d.ts +1 -0
  17. package/dist/commands/doctor.test.js +301 -0
  18. package/dist/commands/doctor.test.js.map +1 -0
  19. package/dist/commands/eject.d.ts +11 -0
  20. package/dist/commands/eject.js +55 -0
  21. package/dist/commands/eject.js.map +1 -0
  22. package/dist/commands/eject.test.d.ts +1 -0
  23. package/dist/commands/eject.test.js +151 -0
  24. package/dist/commands/eject.test.js.map +1 -0
  25. package/dist/commands/info.d.ts +1 -0
  26. package/dist/commands/info.js +73 -0
  27. package/dist/commands/info.js.map +1 -0
  28. package/dist/commands/info.test.d.ts +1 -0
  29. package/dist/commands/info.test.js +143 -0
  30. package/dist/commands/info.test.js.map +1 -0
  31. package/dist/commands/init.d.ts +1 -0
  32. package/dist/commands/init.js +98 -0
  33. package/dist/commands/init.js.map +1 -0
  34. package/dist/commands/init.test.d.ts +1 -0
  35. package/dist/commands/init.test.js +154 -0
  36. package/dist/commands/init.test.js.map +1 -0
  37. package/dist/commands/list.d.ts +16 -0
  38. package/dist/commands/list.js +124 -0
  39. package/dist/commands/list.js.map +1 -0
  40. package/dist/commands/list.test.d.ts +1 -0
  41. package/dist/commands/list.test.js +285 -0
  42. package/dist/commands/list.test.js.map +1 -0
  43. package/dist/commands/outdated.d.ts +13 -0
  44. package/dist/commands/outdated.js +116 -0
  45. package/dist/commands/outdated.js.map +1 -0
  46. package/dist/commands/outdated.test.d.ts +1 -0
  47. package/dist/commands/outdated.test.js +208 -0
  48. package/dist/commands/outdated.test.js.map +1 -0
  49. package/dist/commands/remove.d.ts +18 -0
  50. package/dist/commands/remove.js +161 -0
  51. package/dist/commands/remove.js.map +1 -0
  52. package/dist/commands/remove.test.d.ts +1 -0
  53. package/dist/commands/remove.test.js +361 -0
  54. package/dist/commands/remove.test.js.map +1 -0
  55. package/dist/commands/theme.d.ts +1 -0
  56. package/dist/commands/theme.js +66 -0
  57. package/dist/commands/theme.js.map +1 -0
  58. package/dist/commands/theme.test.d.ts +1 -0
  59. package/dist/commands/theme.test.js +133 -0
  60. package/dist/commands/theme.test.js.map +1 -0
  61. package/dist/commands/update.d.ts +31 -0
  62. package/dist/commands/update.js +601 -0
  63. package/dist/commands/update.js.map +1 -0
  64. package/dist/commands/update.test.d.ts +1 -0
  65. package/dist/commands/update.test.js +1233 -0
  66. package/dist/commands/update.test.js.map +1 -0
  67. package/dist/config.d.ts +11 -0
  68. package/dist/config.js +19 -0
  69. package/dist/config.js.map +1 -0
  70. package/dist/config.test.d.ts +1 -0
  71. package/dist/config.test.js +48 -0
  72. package/dist/config.test.js.map +1 -0
  73. package/dist/index.d.ts +2 -0
  74. package/dist/index.js +89 -0
  75. package/dist/index.js.map +1 -0
  76. package/dist/lockfile.d.ts +21 -0
  77. package/dist/lockfile.js +39 -0
  78. package/dist/lockfile.js.map +1 -0
  79. package/dist/registry.d.ts +14 -0
  80. package/dist/registry.js +85 -0
  81. package/dist/registry.js.map +1 -0
  82. package/dist/registry.test.d.ts +1 -0
  83. package/dist/registry.test.js +95 -0
  84. package/dist/registry.test.js.map +1 -0
  85. package/dist/test-utils.d.ts +16 -0
  86. package/dist/test-utils.js +65 -0
  87. package/dist/test-utils.js.map +1 -0
  88. package/dist/ui/components/accordion/accordion.test.ts +194 -0
  89. package/dist/ui/components/accordion/accordion.ts +173 -0
  90. package/dist/ui/components/accordion/index.ts +6 -0
  91. package/dist/ui/components/action-sheet/action-sheet.test.ts +211 -0
  92. package/dist/ui/components/action-sheet/action-sheet.ts +353 -0
  93. package/dist/ui/components/action-sheet/index.ts +6 -0
  94. package/dist/ui/components/alert/alert.test.ts +48 -0
  95. package/dist/ui/components/alert/alert.ts +104 -0
  96. package/dist/ui/components/alert/index.ts +1 -0
  97. package/dist/ui/components/alert-dialog/alert-dialog.test.ts +141 -0
  98. package/dist/ui/components/alert-dialog/alert-dialog.ts +243 -0
  99. package/dist/ui/components/alert-dialog/index.ts +7 -0
  100. package/dist/ui/components/area-chart/area-chart-render.test.ts +121 -0
  101. package/dist/ui/components/area-chart/area-chart.test.ts +158 -0
  102. package/dist/ui/components/area-chart/area-chart.ts +349 -0
  103. package/dist/ui/components/area-chart/index.ts +6 -0
  104. package/dist/ui/components/aspect-ratio/aspect-ratio.test.ts +10 -0
  105. package/dist/ui/components/aspect-ratio/aspect-ratio.ts +28 -0
  106. package/dist/ui/components/aspect-ratio/index.ts +1 -0
  107. package/dist/ui/components/avatar/avatar.test.ts +69 -0
  108. package/dist/ui/components/avatar/avatar.ts +131 -0
  109. package/dist/ui/components/avatar/index.ts +1 -0
  110. package/dist/ui/components/badge/badge.test.ts +49 -0
  111. package/dist/ui/components/badge/badge.ts +97 -0
  112. package/dist/ui/components/badge/index.ts +1 -0
  113. package/dist/ui/components/bar-chart/bar-chart-render.test.ts +174 -0
  114. package/dist/ui/components/bar-chart/bar-chart.test.ts +73 -0
  115. package/dist/ui/components/bar-chart/bar-chart.ts +327 -0
  116. package/dist/ui/components/bar-chart/index.ts +6 -0
  117. package/dist/ui/components/bottom-sheet/bottom-sheet.test.ts +163 -0
  118. package/dist/ui/components/bottom-sheet/bottom-sheet.ts +339 -0
  119. package/dist/ui/components/bottom-sheet/index.ts +1 -0
  120. package/dist/ui/components/button/button-render.test.ts +113 -0
  121. package/dist/ui/components/button/button.test.ts +70 -0
  122. package/dist/ui/components/button/button.ts +170 -0
  123. package/dist/ui/components/button/index.ts +6 -0
  124. package/dist/ui/components/button-group/button-group-render.test.ts +146 -0
  125. package/dist/ui/components/button-group/button-group.test.ts +54 -0
  126. package/dist/ui/components/button-group/button-group.ts +209 -0
  127. package/dist/ui/components/button-group/index.ts +8 -0
  128. package/dist/ui/components/candlestick-chart/candlestick-chart-render.test.ts +174 -0
  129. package/dist/ui/components/candlestick-chart/candlestick-chart.test.ts +96 -0
  130. package/dist/ui/components/candlestick-chart/candlestick-chart.ts +355 -0
  131. package/dist/ui/components/candlestick-chart/index.ts +7 -0
  132. package/dist/ui/components/card/card.test.ts +110 -0
  133. package/dist/ui/components/card/card.ts +162 -0
  134. package/dist/ui/components/card/index.ts +8 -0
  135. package/dist/ui/components/cartesian-chart/cartesian-chart-render.test.ts +188 -0
  136. package/dist/ui/components/cartesian-chart/cartesian-chart.test.ts +549 -0
  137. package/dist/ui/components/cartesian-chart/cartesian-chart.ts +1140 -0
  138. package/dist/ui/components/cartesian-chart/index.ts +18 -0
  139. package/dist/ui/components/checkbox/checkbox.test.ts +59 -0
  140. package/dist/ui/components/checkbox/checkbox.ts +96 -0
  141. package/dist/ui/components/checkbox/index.ts +1 -0
  142. package/dist/ui/components/collapsible/collapsible.test.ts +112 -0
  143. package/dist/ui/components/collapsible/collapsible.ts +122 -0
  144. package/dist/ui/components/collapsible/index.ts +5 -0
  145. package/dist/ui/components/dialog/dialog.test.ts +157 -0
  146. package/dist/ui/components/dialog/dialog.ts +248 -0
  147. package/dist/ui/components/dialog/index.ts +7 -0
  148. package/dist/ui/components/empty-state/empty-state.test.ts +60 -0
  149. package/dist/ui/components/empty-state/empty-state.ts +46 -0
  150. package/dist/ui/components/empty-state/index.ts +1 -0
  151. package/dist/ui/components/icon/icon.test.ts +52 -0
  152. package/dist/ui/components/icon/icon.ts +54 -0
  153. package/dist/ui/components/icon/icons.ts +569 -0
  154. package/dist/ui/components/icon/index.ts +2 -0
  155. package/dist/ui/components/input/index.ts +1 -0
  156. package/dist/ui/components/input/input.test.ts +134 -0
  157. package/dist/ui/components/input/input.ts +189 -0
  158. package/dist/ui/components/label/index.ts +1 -0
  159. package/dist/ui/components/label/label.test.ts +38 -0
  160. package/dist/ui/components/label/label.ts +24 -0
  161. package/dist/ui/components/line-chart/index.ts +6 -0
  162. package/dist/ui/components/line-chart/line-chart-render.test.ts +121 -0
  163. package/dist/ui/components/line-chart/line-chart.test.ts +81 -0
  164. package/dist/ui/components/line-chart/line-chart.ts +273 -0
  165. package/dist/ui/components/list/index.ts +1 -0
  166. package/dist/ui/components/list/list.test.ts +35 -0
  167. package/dist/ui/components/list/list.ts +71 -0
  168. package/dist/ui/components/nav-drawer/index.ts +8 -0
  169. package/dist/ui/components/nav-drawer/nav-drawer.test.ts +267 -0
  170. package/dist/ui/components/nav-drawer/nav-drawer.ts +504 -0
  171. package/dist/ui/components/pagination/index.ts +1 -0
  172. package/dist/ui/components/pagination/pagination.test.ts +96 -0
  173. package/dist/ui/components/pagination/pagination.ts +147 -0
  174. package/dist/ui/components/progress/index.ts +1 -0
  175. package/dist/ui/components/progress/progress.test.ts +71 -0
  176. package/dist/ui/components/progress/progress.ts +118 -0
  177. package/dist/ui/components/radio-group/index.ts +1 -0
  178. package/dist/ui/components/radio-group/radio-group.test.ts +115 -0
  179. package/dist/ui/components/radio-group/radio-group.ts +129 -0
  180. package/dist/ui/components/scatter-chart/index.ts +6 -0
  181. package/dist/ui/components/scatter-chart/scatter-chart-render.test.ts +157 -0
  182. package/dist/ui/components/scatter-chart/scatter-chart.test.ts +57 -0
  183. package/dist/ui/components/scatter-chart/scatter-chart.ts +277 -0
  184. package/dist/ui/components/scroll-area/index.ts +1 -0
  185. package/dist/ui/components/scroll-area/scroll-area.test.ts +40 -0
  186. package/dist/ui/components/scroll-area/scroll-area.ts +73 -0
  187. package/dist/ui/components/select/index.ts +1 -0
  188. package/dist/ui/components/select/select.test.ts +107 -0
  189. package/dist/ui/components/select/select.ts +146 -0
  190. package/dist/ui/components/separator/index.ts +1 -0
  191. package/dist/ui/components/separator/separator.test.ts +26 -0
  192. package/dist/ui/components/separator/separator.ts +45 -0
  193. package/dist/ui/components/sheet/index.ts +7 -0
  194. package/dist/ui/components/sheet/sheet.test.ts +44 -0
  195. package/dist/ui/components/sheet/sheet.ts +114 -0
  196. package/dist/ui/components/skeleton/index.ts +1 -0
  197. package/dist/ui/components/skeleton/skeleton.test.ts +13 -0
  198. package/dist/ui/components/skeleton/skeleton.ts +45 -0
  199. package/dist/ui/components/spinner/index.ts +1 -0
  200. package/dist/ui/components/spinner/spinner.test.ts +39 -0
  201. package/dist/ui/components/spinner/spinner.ts +83 -0
  202. package/dist/ui/components/switch/index.ts +1 -0
  203. package/dist/ui/components/switch/switch.test.ts +63 -0
  204. package/dist/ui/components/switch/switch.ts +98 -0
  205. package/dist/ui/components/tabs/index.ts +1 -0
  206. package/dist/ui/components/tabs/tabs.test.ts +199 -0
  207. package/dist/ui/components/tabs/tabs.ts +317 -0
  208. package/dist/ui/components/textarea/index.ts +1 -0
  209. package/dist/ui/components/textarea/textarea.test.ts +138 -0
  210. package/dist/ui/components/textarea/textarea.ts +221 -0
  211. package/dist/ui/components/toast/index.ts +9 -0
  212. package/dist/ui/components/toast/toast-state.test.ts +54 -0
  213. package/dist/ui/components/toast/toast-state.ts +46 -0
  214. package/dist/ui/components/toast/toast.test.ts +342 -0
  215. package/dist/ui/components/toast/toast.ts +527 -0
  216. package/dist/ui/components/toggle/index.ts +7 -0
  217. package/dist/ui/components/toggle/toggle.test.ts +186 -0
  218. package/dist/ui/components/toggle/toggle.ts +218 -0
  219. package/dist/ui/components/typography/index.ts +5 -0
  220. package/dist/ui/components/typography/typography.test.ts +55 -0
  221. package/dist/ui/components/typography/typography.ts +44 -0
  222. package/dist/ui/theme/dark.css +41 -0
  223. package/dist/ui/theme/default.css +56 -0
  224. package/dist/ui/theme/tailwind-plugin.test.ts +124 -0
  225. package/dist/ui/theme/tailwind-plugin.ts +151 -0
  226. package/dist/ui/utils/animate.test.ts +324 -0
  227. package/dist/ui/utils/animate.ts +544 -0
  228. package/dist/ui/utils/cn.test.ts +27 -0
  229. package/dist/ui/utils/cn.ts +6 -0
  230. package/dist/utils/analyze.d.ts +32 -0
  231. package/dist/utils/analyze.js +109 -0
  232. package/dist/utils/analyze.js.map +1 -0
  233. package/dist/utils/analyze.test.d.ts +1 -0
  234. package/dist/utils/analyze.test.js +146 -0
  235. package/dist/utils/analyze.test.js.map +1 -0
  236. package/dist/utils/animate.d.ts +210 -0
  237. package/dist/utils/animate.js +423 -0
  238. package/dist/utils/animate.js.map +1 -0
  239. package/dist/utils/animate.test.d.ts +1 -0
  240. package/dist/utils/animate.test.js +365 -0
  241. package/dist/utils/animate.test.js.map +1 -0
  242. package/dist/utils/banner.d.ts +1 -0
  243. package/dist/utils/banner.js +15 -0
  244. package/dist/utils/banner.js.map +1 -0
  245. package/dist/utils/banner.test.d.ts +1 -0
  246. package/dist/utils/banner.test.js +29 -0
  247. package/dist/utils/banner.test.js.map +1 -0
  248. package/dist/utils/cn.d.ts +2 -0
  249. package/dist/utils/cn.js +6 -0
  250. package/dist/utils/cn.js.map +1 -0
  251. package/dist/utils/cn.test.d.ts +1 -0
  252. package/dist/utils/cn.test.js +21 -0
  253. package/dist/utils/cn.test.js.map +1 -0
  254. package/dist/utils/format.d.ts +31 -0
  255. package/dist/utils/format.js +95 -0
  256. package/dist/utils/format.js.map +1 -0
  257. package/dist/utils/format.test.d.ts +1 -0
  258. package/dist/utils/format.test.js +71 -0
  259. package/dist/utils/format.test.js.map +1 -0
  260. package/dist/utils/logger.d.ts +6 -0
  261. package/dist/utils/logger.js +16 -0
  262. package/dist/utils/logger.js.map +1 -0
  263. package/dist/utils/logger.test.d.ts +1 -0
  264. package/dist/utils/logger.test.js +36 -0
  265. package/dist/utils/logger.test.js.map +1 -0
  266. package/dist/utils/resolve-paths.d.ts +12 -0
  267. package/dist/utils/resolve-paths.js +29 -0
  268. package/dist/utils/resolve-paths.js.map +1 -0
  269. package/dist/utils/resolve-paths.test.d.ts +1 -0
  270. package/dist/utils/resolve-paths.test.js +52 -0
  271. package/dist/utils/resolve-paths.test.js.map +1 -0
  272. package/package.json +56 -4
  273. package/README.md +0 -3
@@ -0,0 +1,544 @@
1
+ /**
2
+ * Shared animation utility for @blotch/ui components.
3
+ *
4
+ * Centralizes all motion constants (durations, easing curves, scale factors)
5
+ * and provides reusable animation helpers that wrap the Lynx element.animate() API.
6
+ * This ensures a consistent, cohesive motion language across the entire component library.
7
+ */
8
+
9
+ // ---------------------------------------------------------------------------
10
+ // Constants
11
+ // ---------------------------------------------------------------------------
12
+
13
+ /**
14
+ * Duration constants in milliseconds
15
+ */
16
+ export const DURATION = {
17
+ /**
18
+ * Micro-interactions: checkbox tick, radio dot
19
+ */
20
+ instant: 100,
21
+ /**
22
+ * Press/release, thumb slide
23
+ */
24
+ fast: 150,
25
+ /**
26
+ * Overlays, content transitions
27
+ */
28
+ normal: 250,
29
+ /**
30
+ * Sheet/drawer slide, accordion expand
31
+ */
32
+ slow: 350,
33
+ } as const;
34
+
35
+ /**
36
+ * Easing curves for different animation contexts
37
+ */
38
+ export const EASING = {
39
+ /**
40
+ * Entering elements — fast start, gentle stop
41
+ */
42
+ decelerate: 'cubic-bezier(0.0, 0.0, 0.2, 1)',
43
+ /**
44
+ * Exiting elements — gentle start, fast stop
45
+ */
46
+ accelerate: 'cubic-bezier(0.4, 0.0, 1, 1)',
47
+ /**
48
+ * General purpose movement
49
+ */
50
+ standard: 'cubic-bezier(0.4, 0.0, 0.2, 1)',
51
+ /**
52
+ * Bouncy overshoot — playful, energetic entrance
53
+ */
54
+ spring: 'cubic-bezier(0.34, 1.56, 0.64, 1)',
55
+ /**
56
+ * Gentle overshoot — refined entrance
57
+ */
58
+ springSubtle: 'cubic-bezier(0.22, 1.2, 0.36, 1)',
59
+ /**
60
+ * iOS-style sheet/drawer slide
61
+ */
62
+ sheet: 'cubic-bezier(0.32, 0.72, 0, 1)',
63
+ } as const;
64
+
65
+ /**
66
+ * Scale factors for transform animations
67
+ */
68
+ export const SCALE = {
69
+ /**
70
+ * Button/toggle press
71
+ */
72
+ pressDown: 0.97,
73
+ /**
74
+ * List item / card press (lighter)
75
+ */
76
+ pressDownLight: 0.985,
77
+ /**
78
+ * Small elements popping in (check, radio dot)
79
+ */
80
+ popFrom: 0.6,
81
+ /**
82
+ * Dialog entrance starting scale
83
+ */
84
+ dialogFrom: 0.9,
85
+ /**
86
+ * Dialog exit ending scale
87
+ */
88
+ dialogTo: 0.95,
89
+ } as const;
90
+
91
+ // ---------------------------------------------------------------------------
92
+ // Types
93
+ // ---------------------------------------------------------------------------
94
+
95
+ /**
96
+ * Options shared by most animation helpers
97
+ */
98
+ export type AnimateOptions = {
99
+ duration?: number;
100
+ easing?: string;
101
+ fill?: 'none' | 'forwards' | 'backwards' | 'both';
102
+ };
103
+
104
+ /**
105
+ * A cancelable animation handle returned by Lynx element.animate()
106
+ */
107
+ export type AnimationHandle = {
108
+ cancel(): void;
109
+ };
110
+
111
+ // ---------------------------------------------------------------------------
112
+ // Primitive Helpers
113
+ // ---------------------------------------------------------------------------
114
+
115
+ /**
116
+ * Fades an element in (opacity 0 → 1).
117
+ * Used for backdrops, content reveals, and soft entrances.
118
+ */
119
+ export const fadeIn = (
120
+ el: any,
121
+ options?: AnimateOptions,
122
+ ): AnimationHandle | undefined => {
123
+ if (!el) return undefined;
124
+ return el.animate([{ opacity: 0 }, { opacity: 1 }], {
125
+ duration: options?.duration ?? DURATION.normal,
126
+ easing: options?.easing ?? EASING.decelerate,
127
+ fill: options?.fill ?? 'forwards',
128
+ });
129
+ };
130
+
131
+ /**
132
+ * Fades an element out (opacity 1 → 0).
133
+ * Used for backdrops, content dismissals, and soft exits.
134
+ */
135
+ export const fadeOut = (
136
+ el: any,
137
+ options?: AnimateOptions,
138
+ ): AnimationHandle | undefined => {
139
+ if (!el) return undefined;
140
+ return el.animate([{ opacity: 1 }, { opacity: 0 }], {
141
+ duration: options?.duration ?? DURATION.fast,
142
+ easing: options?.easing ?? EASING.accelerate,
143
+ fill: options?.fill ?? 'forwards',
144
+ });
145
+ };
146
+
147
+ /**
148
+ * Scales and fades an element in — used for overlay panels (dialog, alert-dialog).
149
+ * Animates from a smaller scale + offset to full size.
150
+ */
151
+ export const scaleIn = (
152
+ el: any,
153
+ options?: AnimateOptions & { fromScale?: number; fromY?: number },
154
+ ): AnimationHandle | undefined => {
155
+ if (!el) return undefined;
156
+ const fromScale = options?.fromScale ?? SCALE.dialogFrom;
157
+ const fromY = options?.fromY ?? 10;
158
+ return el.animate(
159
+ [
160
+ { transform: `scale(${fromScale}) translateY(${fromY}px)`, opacity: 0 },
161
+ { transform: 'scale(1) translateY(0px)', opacity: 1 },
162
+ ],
163
+ {
164
+ duration: options?.duration ?? DURATION.slow,
165
+ easing: options?.easing ?? EASING.springSubtle,
166
+ fill: options?.fill ?? 'forwards',
167
+ },
168
+ );
169
+ };
170
+
171
+ /**
172
+ * Scales and fades an element out — used for overlay panel exit.
173
+ */
174
+ export const scaleOut = (
175
+ el: any,
176
+ options?: AnimateOptions & { toScale?: number; toY?: number },
177
+ ): AnimationHandle | undefined => {
178
+ if (!el) return undefined;
179
+ const toScale = options?.toScale ?? SCALE.dialogTo;
180
+ const toY = options?.toY ?? 10;
181
+ return el.animate(
182
+ [
183
+ { transform: 'scale(1) translateY(0px)', opacity: 1 },
184
+ { transform: `scale(${toScale}) translateY(${toY}px)`, opacity: 0 },
185
+ ],
186
+ {
187
+ duration: options?.duration ?? DURATION.fast,
188
+ easing: options?.easing ?? EASING.accelerate,
189
+ fill: options?.fill ?? 'forwards',
190
+ },
191
+ );
192
+ };
193
+
194
+ /**
195
+ * Slides an element in from a direction.
196
+ * Used for sheets (bottom), nav-drawers (left/right), toasts (bottom).
197
+ */
198
+ export const slideIn = (
199
+ el: any,
200
+ direction: 'up' | 'down' | 'left' | 'right',
201
+ options?: AnimateOptions,
202
+ ): AnimationHandle | undefined => {
203
+ if (!el) return undefined;
204
+ const translate = {
205
+ up: ['translateY(100%)', 'translateY(0%)'],
206
+ down: ['translateY(-100%)', 'translateY(0%)'],
207
+ left: ['translateX(-100%)', 'translateX(0%)'],
208
+ right: ['translateX(100%)', 'translateX(0%)'],
209
+ }[direction];
210
+ return el.animate(
211
+ [
212
+ { transform: translate[0], opacity: 0.8 },
213
+ { transform: translate[1], opacity: 1 },
214
+ ],
215
+ {
216
+ duration: options?.duration ?? DURATION.slow,
217
+ easing: options?.easing ?? EASING.sheet,
218
+ fill: options?.fill ?? 'forwards',
219
+ },
220
+ );
221
+ };
222
+
223
+ /**
224
+ * Slides an element out to a direction.
225
+ */
226
+ export const slideOut = (
227
+ el: any,
228
+ direction: 'up' | 'down' | 'left' | 'right',
229
+ options?: AnimateOptions,
230
+ ): AnimationHandle | undefined => {
231
+ if (!el) return undefined;
232
+ const translate = {
233
+ up: ['translateY(0%)', 'translateY(-100%)'],
234
+ down: ['translateY(0%)', 'translateY(100%)'],
235
+ left: ['translateX(0%)', 'translateX(-100%)'],
236
+ right: ['translateX(0%)', 'translateX(100%)'],
237
+ }[direction];
238
+ return el.animate(
239
+ [
240
+ { transform: translate[0], opacity: 1 },
241
+ { transform: translate[1], opacity: 0.8 },
242
+ ],
243
+ {
244
+ duration: options?.duration ?? DURATION.normal,
245
+ easing: options?.easing ?? EASING.accelerate,
246
+ fill: options?.fill ?? 'forwards',
247
+ },
248
+ );
249
+ };
250
+
251
+ /**
252
+ * Pops a small element in with spring physics.
253
+ * Used for checkbox marks, radio dots, badges.
254
+ */
255
+ export const popIn = (
256
+ el: any,
257
+ options?: AnimateOptions & { fromScale?: number },
258
+ ): AnimationHandle | undefined => {
259
+ if (!el) return undefined;
260
+ const from = options?.fromScale ?? SCALE.popFrom;
261
+ return el.animate(
262
+ [
263
+ { transform: `scale(${from})`, opacity: 0 },
264
+ { transform: 'scale(1)', opacity: 1 },
265
+ ],
266
+ {
267
+ duration: options?.duration ?? DURATION.fast,
268
+ easing: options?.easing ?? EASING.spring,
269
+ fill: options?.fill ?? 'forwards',
270
+ },
271
+ );
272
+ };
273
+
274
+ /**
275
+ * Pops a small element out (reverse of popIn).
276
+ */
277
+ export const popOut = (
278
+ el: any,
279
+ options?: AnimateOptions & { toScale?: number },
280
+ ): AnimationHandle | undefined => {
281
+ if (!el) return undefined;
282
+ const to = options?.toScale ?? 0.8;
283
+ return el.animate(
284
+ [
285
+ { transform: 'scale(1)', opacity: 1 },
286
+ { transform: `scale(${to})`, opacity: 0 },
287
+ ],
288
+ {
289
+ duration: options?.duration ?? DURATION.instant,
290
+ easing: options?.easing ?? EASING.accelerate,
291
+ fill: options?.fill ?? 'forwards',
292
+ },
293
+ );
294
+ };
295
+
296
+ // ---------------------------------------------------------------------------
297
+ // Press Feedback Helpers
298
+ // ---------------------------------------------------------------------------
299
+
300
+ /**
301
+ * Drives a press-feedback transform with an inline CSS transition instead of
302
+ * the imperative `element.animate()` (Web Animations) API.
303
+ *
304
+ * WHY NOT `el.animate()`: it plays on iOS/Android (native runs the renderer on
305
+ * the main thread, where `__ElementAnimate` maps to a real native animation)
306
+ * but is a DEAD no-op on Lynx web. On web the app runs inside `@lynx-js/web-core`'s
307
+ * worker against an OFFSCREEN document; `element.animate()` there creates a live
308
+ * Web-Animation on the offscreen node that is never serialized to the real DOM,
309
+ * so the on-screen element never moves. (Verified in a headless browser: the
310
+ * press handlers fire and `el.element.animate()` runs without error, yet the
311
+ * rendered node's computed transform stays `none`.) Inline STYLE mutations, by
312
+ * contrast, ARE serialized offscreen→real — that is how every `[style.*]`
313
+ * binding renders on web — so a `transition` + `transform` written via
314
+ * `setStyle` animates identically on native and web.
315
+ *
316
+ * The "from" state is always the element's current transform, so setting the
317
+ * `transition` and the new `transform` together reliably triggers the
318
+ * transition on both platforms (no from-state reflow needed, unlike an
319
+ * entrance animation would require).
320
+ */
321
+ const transitionTransform = (
322
+ el: any,
323
+ transform: string,
324
+ duration: number,
325
+ easing: string,
326
+ ): AnimationHandle | undefined => {
327
+ if (!el?.setStyle) return undefined;
328
+ el.setStyle('transition', `transform ${duration}ms ${easing}`);
329
+ el.setStyle('transform', transform);
330
+ // CSS transitions have no imperative handle to stop; re-pressing simply
331
+ // re-transitions from the current value. cancel() is a no-op kept for API
332
+ // parity with callers that store and cancel the previous handle.
333
+ return { cancel() {} };
334
+ };
335
+
336
+ /**
337
+ * Animates an element being pressed down (scale shrink).
338
+ *
339
+ * Wire this to BOTH the touch and mouse press-start events —
340
+ * `(bindtouchstart)` and `(bindmousedown)`. Native (iOS/Android) only ever
341
+ * fires touch events; Lynx web runs in the browser, where a mouse press fires
342
+ * `mousedown` and NEVER `touchstart`. Mouse events are inert on touch devices,
343
+ * so binding both yields identical press-and-hold feedback across platforms.
344
+ * See `transitionTransform` for why this uses a CSS transition, not
345
+ * `el.animate()`.
346
+ */
347
+ export const pressDown = (
348
+ el: any,
349
+ scale: number = SCALE.pressDown,
350
+ ): AnimationHandle | undefined =>
351
+ transitionTransform(el, `scale(${scale})`, DURATION.instant, EASING.standard);
352
+
353
+ /**
354
+ * Animates an element releasing from a press (scale back with spring).
355
+ *
356
+ * Wire this to the touch AND mouse release events — `(bindtouchend)` +
357
+ * `(bindtouchcancel)` and `(bindmouseup)` + `(bindmouseleave)`. See `pressDown`
358
+ * for why the mouse bindings are required on Lynx web. `mouseleave` is the
359
+ * `touchcancel` analogue: it restores the scale if the cursor leaves the
360
+ * element while still pressed.
361
+ */
362
+ export const pressRelease = (el: any): AnimationHandle | undefined =>
363
+ transitionTransform(el, 'scale(1)', DURATION.fast, EASING.spring);
364
+
365
+ // ---------------------------------------------------------------------------
366
+ // Composite Helpers
367
+ // ---------------------------------------------------------------------------
368
+
369
+ /**
370
+ * Orchestrates opening an overlay (backdrop + panel entrance).
371
+ * Provides staggered timing — backdrop fades first, then panel enters.
372
+ */
373
+ export const overlayOpen = (
374
+ backdrop: any,
375
+ panel: any,
376
+ type: 'center' | 'bottom' | 'left' | 'right',
377
+ ): { backdrop?: AnimationHandle; panel?: AnimationHandle } => {
378
+ // Backdrop fades in immediately
379
+ const backdropAnim = fadeIn(backdrop, { duration: DURATION.normal });
380
+
381
+ // Panel enters with a 30ms stagger after backdrop
382
+ let panelAnim: AnimationHandle | undefined;
383
+ setTimeout(() => {
384
+ if (type === 'center') {
385
+ panelAnim = scaleIn(panel);
386
+ } else if (type === 'bottom') {
387
+ panelAnim = slideIn(panel, 'up');
388
+ } else if (type === 'left') {
389
+ panelAnim = slideIn(panel, 'left');
390
+ } else {
391
+ panelAnim = slideIn(panel, 'right');
392
+ }
393
+ }, 30);
394
+
395
+ return { backdrop: backdropAnim, panel: panelAnim };
396
+ };
397
+
398
+ /**
399
+ * Orchestrates closing an overlay (panel exits, then backdrop fades).
400
+ * Returns total animation duration for cleanup scheduling.
401
+ */
402
+ export const overlayClose = (
403
+ backdrop: any,
404
+ panel: any,
405
+ type: 'center' | 'bottom' | 'left' | 'right',
406
+ ): number => {
407
+ // Panel exits immediately
408
+ if (type === 'center') {
409
+ scaleOut(panel, { duration: 180 });
410
+ } else if (type === 'bottom') {
411
+ slideOut(panel, 'down', { duration: DURATION.normal });
412
+ } else if (type === 'left') {
413
+ slideOut(panel, 'left', { duration: DURATION.normal });
414
+ } else {
415
+ slideOut(panel, 'right', { duration: DURATION.normal });
416
+ }
417
+
418
+ // Backdrop fades out in parallel
419
+ fadeOut(backdrop, { duration: DURATION.normal });
420
+
421
+ // Return the longest duration for setTimeout cleanup
422
+ return type === 'center' ? 180 : DURATION.normal;
423
+ };
424
+
425
+ // ---------------------------------------------------------------------------
426
+ // Continuous Animation Helpers
427
+ // ---------------------------------------------------------------------------
428
+
429
+ /**
430
+ * Creates a continuous pulse animation (for skeleton loading states).
431
+ * Oscillates opacity between 1 and 0.5 indefinitely.
432
+ */
433
+ export const pulse = (
434
+ el: any,
435
+ options?: { duration?: number },
436
+ ): AnimationHandle | undefined => {
437
+ if (!el) return undefined;
438
+ return el.animate([{ opacity: 1 }, { opacity: 0.5 }, { opacity: 1 }], {
439
+ duration: options?.duration ?? 1500,
440
+ easing: 'ease-in-out',
441
+ iterations: Infinity,
442
+ });
443
+ };
444
+
445
+ /**
446
+ * Creates a horizontal shake animation (for error states on inputs).
447
+ * Shakes ±4px then ±2px before settling.
448
+ */
449
+ export const shake = (
450
+ el: any,
451
+ options?: AnimateOptions,
452
+ ): AnimationHandle | undefined => {
453
+ if (!el) return undefined;
454
+ return el.animate(
455
+ [
456
+ { transform: 'translateX(0)' },
457
+ { transform: 'translateX(-4px)' },
458
+ { transform: 'translateX(4px)' },
459
+ { transform: 'translateX(-2px)' },
460
+ { transform: 'translateX(2px)' },
461
+ { transform: 'translateX(0)' },
462
+ ],
463
+ {
464
+ duration: options?.duration ?? DURATION.slow,
465
+ easing: options?.easing ?? EASING.standard,
466
+ fill: 'forwards',
467
+ },
468
+ );
469
+ };
470
+
471
+ /**
472
+ * Animates a spring-based translation between two X positions.
473
+ * Used for switch thumbs with a squish effect at midpoint.
474
+ */
475
+ export const springTranslateX = (
476
+ el: any,
477
+ fromX: number,
478
+ toX: number,
479
+ options?: AnimateOptions,
480
+ ): AnimationHandle | undefined => {
481
+ if (!el) return undefined;
482
+ const midX = (fromX + toX) / 2;
483
+ return el.animate(
484
+ [
485
+ { transform: `translateX(${fromX}px) scale(1)` },
486
+ { transform: `translateX(${midX}px) scale(1.1)` },
487
+ { transform: `translateX(${toX}px) scale(1)` },
488
+ ],
489
+ {
490
+ duration: options?.duration ?? 200,
491
+ easing: options?.easing ?? EASING.spring,
492
+ fill: options?.fill ?? 'forwards',
493
+ },
494
+ );
495
+ };
496
+
497
+ /**
498
+ * Content reveal animation — fade in with upward slide.
499
+ * Used for accordion content, tab content, alerts, etc.
500
+ */
501
+ export const revealIn = (
502
+ el: any,
503
+ options?: AnimateOptions & { fromY?: number },
504
+ ): AnimationHandle | undefined => {
505
+ if (!el) return undefined;
506
+ const fromY = options?.fromY ?? 8;
507
+ return el.animate(
508
+ [
509
+ { transform: `translateY(${fromY}px)`, opacity: 0 },
510
+ { transform: 'translateY(0)', opacity: 1 },
511
+ ],
512
+ {
513
+ duration: options?.duration ?? DURATION.normal,
514
+ easing: options?.easing ?? EASING.decelerate,
515
+ fill: options?.fill ?? 'forwards',
516
+ },
517
+ );
518
+ };
519
+
520
+ /**
521
+ * Direction-aware slide + fade for tab content transitions.
522
+ * Slides from left or right based on navigation direction, creating
523
+ * a spatial relationship between tab panels.
524
+ */
525
+ export const directionalSlideIn = (
526
+ el: any,
527
+ direction: 1 | -1,
528
+ options?: AnimateOptions & { distance?: number },
529
+ ): AnimationHandle | undefined => {
530
+ if (!el) return undefined;
531
+ const distance = options?.distance ?? 24;
532
+ const fromX = direction * distance;
533
+ return el.animate(
534
+ [
535
+ { transform: `translateX(${fromX}px)`, opacity: 0 },
536
+ { transform: 'translateX(0px)', opacity: 1 },
537
+ ],
538
+ {
539
+ duration: options?.duration ?? DURATION.slow,
540
+ easing: options?.easing ?? EASING.springSubtle,
541
+ fill: options?.fill ?? 'forwards',
542
+ },
543
+ );
544
+ };
@@ -0,0 +1,27 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { cn } from './cn';
4
+
5
+ describe('cn', () => {
6
+ it('merges class names', () => {
7
+ expect(cn('foo', 'bar')).toBe('foo bar');
8
+ });
9
+
10
+ it('handles conditional classes via clsx', () => {
11
+ expect(cn('base', false, 'visible')).toBe('base visible');
12
+ });
13
+
14
+ it('resolves tailwind conflicts via twMerge', () => {
15
+ expect(cn('px-4', 'px-2')).toBe('px-2');
16
+ expect(cn('text-red-500', 'text-blue-500')).toBe('text-blue-500');
17
+ });
18
+
19
+ it('handles undefined and null inputs', () => {
20
+ expect(cn('foo', undefined, null, 'bar')).toBe('foo bar');
21
+ });
22
+
23
+ it('handles empty inputs', () => {
24
+ expect(cn()).toBe('');
25
+ expect(cn('')).toBe('');
26
+ });
27
+ });
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
+
4
+ export const cn = (...inputs: ClassValue[]): string => {
5
+ return twMerge(clsx(inputs));
6
+ };
@@ -0,0 +1,32 @@
1
+ import { type Hunk } from 'diff';
2
+ export type FileStatus = 'up-to-date' | 'auto-update' | 'user-modified' | 'conflict' | 'new-upstream';
3
+ export type FileAnalysis = {
4
+ file: string;
5
+ status: FileStatus;
6
+ currentContent: string;
7
+ newContent: string;
8
+ };
9
+ export type ComponentAnalysis = {
10
+ name: string;
11
+ files: FileAnalysis[];
12
+ };
13
+ export declare const STATUS_ICONS: Record<FileStatus, string>;
14
+ export declare const STATUS_LABELS: Record<FileStatus, string>;
15
+ /**
16
+ * Three-way merge status detection. The lockfile stores the hash of each
17
+ * file at install time (the "base"). Comparing current (user's file),
18
+ * upstream (bundled source), and base (lockfile hash) lets us determine
19
+ * who changed what:
20
+ * base == current, base != upstream → upstream changed → safe auto-update
21
+ * base != current, base == upstream → user changed → keep theirs
22
+ * base != current, base != upstream → both changed → conflict
23
+ * When no base exists (missing lockfile entry), we can't tell who diverged,
24
+ * so any difference is flagged as a conflict to be safe.
25
+ */
26
+ export declare const analyzeFile: (currentContent: string | null, newContent: string, storedHash: string | null, hashContent: (content: string) => string) => FileStatus;
27
+ export declare const formatDiff: (currentContent: string, newContent: string) => string;
28
+ export declare const summarizeComponent: (analysis: ComponentAnalysis) => FileStatus;
29
+ export { type Hunk } from 'diff';
30
+ export declare const getHunks: (currentContent: string, newContent: string, context?: number) => Hunk[];
31
+ export declare const formatHunk: (hunk: Hunk) => string;
32
+ export declare const applySelectedHunks: (currentContent: string, newContent: string, acceptedIndices: number[]) => string | false;