@chenzy-design/core 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 (310) hide show
  1. package/LICENSE +21 -0
  2. package/dist/back-top.d.ts +16 -0
  3. package/dist/back-top.d.ts.map +1 -0
  4. package/dist/back-top.js +29 -0
  5. package/dist/back-top.js.map +1 -0
  6. package/dist/back-top.test.d.ts +2 -0
  7. package/dist/back-top.test.d.ts.map +1 -0
  8. package/dist/back-top.test.js +44 -0
  9. package/dist/back-top.test.js.map +1 -0
  10. package/dist/banner.d.ts +19 -0
  11. package/dist/banner.d.ts.map +1 -0
  12. package/dist/banner.js +20 -0
  13. package/dist/banner.js.map +1 -0
  14. package/dist/banner.test.d.ts +2 -0
  15. package/dist/banner.test.d.ts.map +1 -0
  16. package/dist/banner.test.js +29 -0
  17. package/dist/banner.test.js.map +1 -0
  18. package/dist/breakpoints.d.ts +30 -0
  19. package/dist/breakpoints.d.ts.map +1 -0
  20. package/dist/breakpoints.js +45 -0
  21. package/dist/breakpoints.js.map +1 -0
  22. package/dist/breakpoints.test.d.ts +2 -0
  23. package/dist/breakpoints.test.d.ts.map +1 -0
  24. package/dist/breakpoints.test.js +46 -0
  25. package/dist/breakpoints.test.js.map +1 -0
  26. package/dist/calendar.d.ts +63 -0
  27. package/dist/calendar.d.ts.map +1 -0
  28. package/dist/calendar.js +94 -0
  29. package/dist/calendar.js.map +1 -0
  30. package/dist/calendar.test.d.ts +2 -0
  31. package/dist/calendar.test.d.ts.map +1 -0
  32. package/dist/calendar.test.js +88 -0
  33. package/dist/calendar.test.js.map +1 -0
  34. package/dist/cascader.d.ts +64 -0
  35. package/dist/cascader.d.ts.map +1 -0
  36. package/dist/cascader.js +79 -0
  37. package/dist/cascader.js.map +1 -0
  38. package/dist/cascader.test.d.ts +2 -0
  39. package/dist/cascader.test.d.ts.map +1 -0
  40. package/dist/cascader.test.js +107 -0
  41. package/dist/cascader.test.js.map +1 -0
  42. package/dist/color.d.ts +70 -0
  43. package/dist/color.d.ts.map +1 -0
  44. package/dist/color.js +217 -0
  45. package/dist/color.js.map +1 -0
  46. package/dist/color.test.d.ts +2 -0
  47. package/dist/color.test.d.ts.map +1 -0
  48. package/dist/color.test.js +161 -0
  49. package/dist/color.test.js.map +1 -0
  50. package/dist/config-provider.d.ts +57 -0
  51. package/dist/config-provider.d.ts.map +1 -0
  52. package/dist/config-provider.js +49 -0
  53. package/dist/config-provider.js.map +1 -0
  54. package/dist/config-provider.test.d.ts +2 -0
  55. package/dist/config-provider.test.d.ts.map +1 -0
  56. package/dist/config-provider.test.js +76 -0
  57. package/dist/config-provider.test.js.map +1 -0
  58. package/dist/copyable.d.ts +43 -0
  59. package/dist/copyable.d.ts.map +1 -0
  60. package/dist/copyable.js +102 -0
  61. package/dist/copyable.js.map +1 -0
  62. package/dist/copyable.test.d.ts +2 -0
  63. package/dist/copyable.test.d.ts.map +1 -0
  64. package/dist/copyable.test.js +97 -0
  65. package/dist/copyable.test.js.map +1 -0
  66. package/dist/date.d.ts +86 -0
  67. package/dist/date.d.ts.map +1 -0
  68. package/dist/date.js +226 -0
  69. package/dist/date.js.map +1 -0
  70. package/dist/date.test.d.ts +2 -0
  71. package/dist/date.test.d.ts.map +1 -0
  72. package/dist/date.test.js +171 -0
  73. package/dist/date.test.js.map +1 -0
  74. package/dist/dismiss.d.ts +28 -0
  75. package/dist/dismiss.d.ts.map +1 -0
  76. package/dist/dismiss.js +42 -0
  77. package/dist/dismiss.js.map +1 -0
  78. package/dist/editable.d.ts +59 -0
  79. package/dist/editable.d.ts.map +1 -0
  80. package/dist/editable.js +85 -0
  81. package/dist/editable.js.map +1 -0
  82. package/dist/editable.test.d.ts +2 -0
  83. package/dist/editable.test.d.ts.map +1 -0
  84. package/dist/editable.test.js +111 -0
  85. package/dist/editable.test.js.map +1 -0
  86. package/dist/ellipsis.d.ts +76 -0
  87. package/dist/ellipsis.d.ts.map +1 -0
  88. package/dist/ellipsis.js +99 -0
  89. package/dist/ellipsis.js.map +1 -0
  90. package/dist/ellipsis.test.d.ts +2 -0
  91. package/dist/ellipsis.test.d.ts.map +1 -0
  92. package/dist/ellipsis.test.js +94 -0
  93. package/dist/ellipsis.test.js.map +1 -0
  94. package/dist/floating.d.ts +78 -0
  95. package/dist/floating.d.ts.map +1 -0
  96. package/dist/floating.js +169 -0
  97. package/dist/floating.js.map +1 -0
  98. package/dist/floating.test.d.ts +2 -0
  99. package/dist/floating.test.d.ts.map +1 -0
  100. package/dist/floating.test.js +169 -0
  101. package/dist/floating.test.js.map +1 -0
  102. package/dist/focus-trap.d.ts +24 -0
  103. package/dist/focus-trap.d.ts.map +1 -0
  104. package/dist/focus-trap.js +64 -0
  105. package/dist/focus-trap.js.map +1 -0
  106. package/dist/form.d.ts +131 -0
  107. package/dist/form.d.ts.map +1 -0
  108. package/dist/form.js +257 -0
  109. package/dist/form.js.map +1 -0
  110. package/dist/form.test.d.ts +2 -0
  111. package/dist/form.test.d.ts.map +1 -0
  112. package/dist/form.test.js +323 -0
  113. package/dist/form.test.js.map +1 -0
  114. package/dist/highlight.d.ts +57 -0
  115. package/dist/highlight.d.ts.map +1 -0
  116. package/dist/highlight.js +124 -0
  117. package/dist/highlight.js.map +1 -0
  118. package/dist/highlight.test.d.ts +2 -0
  119. package/dist/highlight.test.d.ts.map +1 -0
  120. package/dist/highlight.test.js +155 -0
  121. package/dist/highlight.test.js.map +1 -0
  122. package/dist/id.d.ts +4 -0
  123. package/dist/id.d.ts.map +1 -0
  124. package/dist/id.js +14 -0
  125. package/dist/id.js.map +1 -0
  126. package/dist/id.test.d.ts +2 -0
  127. package/dist/id.test.d.ts.map +1 -0
  128. package/dist/id.test.js +13 -0
  129. package/dist/id.test.js.map +1 -0
  130. package/dist/index.d.ts +46 -0
  131. package/dist/index.d.ts.map +1 -0
  132. package/dist/index.js +45 -0
  133. package/dist/index.js.map +1 -0
  134. package/dist/inert-background.d.ts +27 -0
  135. package/dist/inert-background.d.ts.map +1 -0
  136. package/dist/inert-background.js +92 -0
  137. package/dist/inert-background.js.map +1 -0
  138. package/dist/inert-background.test.d.ts +2 -0
  139. package/dist/inert-background.test.d.ts.map +1 -0
  140. package/dist/inert-background.test.js +62 -0
  141. package/dist/inert-background.test.js.map +1 -0
  142. package/dist/input-number.d.ts +34 -0
  143. package/dist/input-number.d.ts.map +1 -0
  144. package/dist/input-number.js +69 -0
  145. package/dist/input-number.js.map +1 -0
  146. package/dist/input-number.test.d.ts +2 -0
  147. package/dist/input-number.test.d.ts.map +1 -0
  148. package/dist/input-number.test.js +74 -0
  149. package/dist/input-number.test.js.map +1 -0
  150. package/dist/list-selection.d.ts +38 -0
  151. package/dist/list-selection.d.ts.map +1 -0
  152. package/dist/list-selection.js +61 -0
  153. package/dist/list-selection.js.map +1 -0
  154. package/dist/list-selection.test.d.ts +2 -0
  155. package/dist/list-selection.test.d.ts.map +1 -0
  156. package/dist/list-selection.test.js +61 -0
  157. package/dist/list-selection.test.js.map +1 -0
  158. package/dist/live-announcer.d.ts +38 -0
  159. package/dist/live-announcer.d.ts.map +1 -0
  160. package/dist/live-announcer.js +89 -0
  161. package/dist/live-announcer.js.map +1 -0
  162. package/dist/live-announcer.test.d.ts +2 -0
  163. package/dist/live-announcer.test.d.ts.map +1 -0
  164. package/dist/live-announcer.test.js +70 -0
  165. package/dist/live-announcer.test.js.map +1 -0
  166. package/dist/lottie-icon.d.ts +88 -0
  167. package/dist/lottie-icon.d.ts.map +1 -0
  168. package/dist/lottie-icon.js +84 -0
  169. package/dist/lottie-icon.js.map +1 -0
  170. package/dist/lottie-icon.test.d.ts +2 -0
  171. package/dist/lottie-icon.test.d.ts.map +1 -0
  172. package/dist/lottie-icon.test.js +88 -0
  173. package/dist/lottie-icon.test.js.map +1 -0
  174. package/dist/notification.d.ts +61 -0
  175. package/dist/notification.d.ts.map +1 -0
  176. package/dist/notification.js +133 -0
  177. package/dist/notification.js.map +1 -0
  178. package/dist/notification.test.d.ts +2 -0
  179. package/dist/notification.test.d.ts.map +1 -0
  180. package/dist/notification.test.js +103 -0
  181. package/dist/notification.test.js.map +1 -0
  182. package/dist/overflow-list.d.ts +102 -0
  183. package/dist/overflow-list.d.ts.map +1 -0
  184. package/dist/overflow-list.js +159 -0
  185. package/dist/overflow-list.js.map +1 -0
  186. package/dist/overflow-list.test.d.ts +2 -0
  187. package/dist/overflow-list.test.d.ts.map +1 -0
  188. package/dist/overflow-list.test.js +223 -0
  189. package/dist/overflow-list.test.js.map +1 -0
  190. package/dist/pagination.d.ts +51 -0
  191. package/dist/pagination.d.ts.map +1 -0
  192. package/dist/pagination.js +127 -0
  193. package/dist/pagination.js.map +1 -0
  194. package/dist/pagination.test.d.ts +2 -0
  195. package/dist/pagination.test.d.ts.map +1 -0
  196. package/dist/pagination.test.js +147 -0
  197. package/dist/pagination.test.js.map +1 -0
  198. package/dist/progress.d.ts +64 -0
  199. package/dist/progress.d.ts.map +1 -0
  200. package/dist/progress.js +92 -0
  201. package/dist/progress.js.map +1 -0
  202. package/dist/progress.test.d.ts +2 -0
  203. package/dist/progress.test.d.ts.map +1 -0
  204. package/dist/progress.test.js +79 -0
  205. package/dist/progress.test.js.map +1 -0
  206. package/dist/resize-observer.d.ts +133 -0
  207. package/dist/resize-observer.d.ts.map +1 -0
  208. package/dist/resize-observer.js +358 -0
  209. package/dist/resize-observer.js.map +1 -0
  210. package/dist/resize-observer.test.d.ts +2 -0
  211. package/dist/resize-observer.test.d.ts.map +1 -0
  212. package/dist/resize-observer.test.js +377 -0
  213. package/dist/resize-observer.test.js.map +1 -0
  214. package/dist/roving.d.ts +26 -0
  215. package/dist/roving.d.ts.map +1 -0
  216. package/dist/roving.js +61 -0
  217. package/dist/roving.js.map +1 -0
  218. package/dist/roving.test.d.ts +2 -0
  219. package/dist/roving.test.d.ts.map +1 -0
  220. package/dist/roving.test.js +48 -0
  221. package/dist/roving.test.js.map +1 -0
  222. package/dist/scroll-list.d.ts +81 -0
  223. package/dist/scroll-list.d.ts.map +1 -0
  224. package/dist/scroll-list.js +175 -0
  225. package/dist/scroll-list.js.map +1 -0
  226. package/dist/scroll-list.test.d.ts +2 -0
  227. package/dist/scroll-list.test.d.ts.map +1 -0
  228. package/dist/scroll-list.test.js +135 -0
  229. package/dist/scroll-list.test.js.map +1 -0
  230. package/dist/scroll-lock.d.ts +11 -0
  231. package/dist/scroll-lock.d.ts.map +1 -0
  232. package/dist/scroll-lock.js +45 -0
  233. package/dist/scroll-lock.js.map +1 -0
  234. package/dist/scroll-lock.test.d.ts +2 -0
  235. package/dist/scroll-lock.test.d.ts.map +1 -0
  236. package/dist/scroll-lock.test.js +55 -0
  237. package/dist/scroll-lock.test.js.map +1 -0
  238. package/dist/sider.d.ts +26 -0
  239. package/dist/sider.d.ts.map +1 -0
  240. package/dist/sider.js +55 -0
  241. package/dist/sider.js.map +1 -0
  242. package/dist/sider.test.d.ts +2 -0
  243. package/dist/sider.test.d.ts.map +1 -0
  244. package/dist/sider.test.js +29 -0
  245. package/dist/sider.test.js.map +1 -0
  246. package/dist/spin.d.ts +25 -0
  247. package/dist/spin.d.ts.map +1 -0
  248. package/dist/spin.js +96 -0
  249. package/dist/spin.js.map +1 -0
  250. package/dist/spin.test.d.ts +2 -0
  251. package/dist/spin.test.d.ts.map +1 -0
  252. package/dist/spin.test.js +80 -0
  253. package/dist/spin.test.js.map +1 -0
  254. package/dist/table.d.ts +101 -0
  255. package/dist/table.d.ts.map +1 -0
  256. package/dist/table.js +244 -0
  257. package/dist/table.js.map +1 -0
  258. package/dist/table.test.d.ts +2 -0
  259. package/dist/table.test.d.ts.map +1 -0
  260. package/dist/table.test.js +184 -0
  261. package/dist/table.test.js.map +1 -0
  262. package/dist/textarea.d.ts +48 -0
  263. package/dist/textarea.d.ts.map +1 -0
  264. package/dist/textarea.js +76 -0
  265. package/dist/textarea.js.map +1 -0
  266. package/dist/textarea.test.d.ts +2 -0
  267. package/dist/textarea.test.d.ts.map +1 -0
  268. package/dist/textarea.test.js +77 -0
  269. package/dist/textarea.test.js.map +1 -0
  270. package/dist/time.d.ts +93 -0
  271. package/dist/time.d.ts.map +1 -0
  272. package/dist/time.js +181 -0
  273. package/dist/time.js.map +1 -0
  274. package/dist/time.test.d.ts +2 -0
  275. package/dist/time.test.d.ts.map +1 -0
  276. package/dist/time.test.js +159 -0
  277. package/dist/time.test.js.map +1 -0
  278. package/dist/toast.d.ts +60 -0
  279. package/dist/toast.d.ts.map +1 -0
  280. package/dist/toast.js +135 -0
  281. package/dist/toast.js.map +1 -0
  282. package/dist/toast.test.d.ts +2 -0
  283. package/dist/toast.test.d.ts.map +1 -0
  284. package/dist/toast.test.js +127 -0
  285. package/dist/toast.test.js.map +1 -0
  286. package/dist/tree.d.ts +129 -0
  287. package/dist/tree.d.ts.map +1 -0
  288. package/dist/tree.js +351 -0
  289. package/dist/tree.js.map +1 -0
  290. package/dist/tree.test.d.ts +2 -0
  291. package/dist/tree.test.d.ts.map +1 -0
  292. package/dist/tree.test.js +291 -0
  293. package/dist/tree.test.js.map +1 -0
  294. package/dist/upload.d.ts +62 -0
  295. package/dist/upload.d.ts.map +1 -0
  296. package/dist/upload.js +85 -0
  297. package/dist/upload.js.map +1 -0
  298. package/dist/upload.test.d.ts +2 -0
  299. package/dist/upload.test.d.ts.map +1 -0
  300. package/dist/upload.test.js +169 -0
  301. package/dist/upload.test.js.map +1 -0
  302. package/dist/virtual-list.d.ts +83 -0
  303. package/dist/virtual-list.d.ts.map +1 -0
  304. package/dist/virtual-list.js +143 -0
  305. package/dist/virtual-list.js.map +1 -0
  306. package/dist/virtual-list.test.d.ts +2 -0
  307. package/dist/virtual-list.test.d.ts.map +1 -0
  308. package/dist/virtual-list.test.js +148 -0
  309. package/dist/virtual-list.test.js.map +1 -0
  310. package/package.json +22 -0
@@ -0,0 +1,171 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { isSameDay, startOfDay, addMonths, addWeeks, addDays, getMonthGrid, getWeekGrid, getDayHours, weekdayOrder, daysBetween, formatDate, parseDateString, gridFocusMove, } from './date.js';
3
+ describe('date utils', () => {
4
+ it('isSameDay compares calendar day only', () => {
5
+ expect(isSameDay(new Date(2024, 0, 1, 9), new Date(2024, 0, 1, 23))).toBe(true);
6
+ expect(isSameDay(new Date(2024, 0, 1), new Date(2024, 0, 2))).toBe(false);
7
+ expect(isSameDay(null, new Date())).toBe(false);
8
+ });
9
+ it('startOfDay zeroes the time', () => {
10
+ const r = startOfDay(new Date(2024, 5, 15, 13, 30, 45));
11
+ expect(r.getHours()).toBe(0);
12
+ expect(r.getMinutes()).toBe(0);
13
+ expect(r.getDate()).toBe(15);
14
+ });
15
+ it('addMonths handles year boundary and clamps day', () => {
16
+ expect(addMonths(new Date(2024, 11, 10), 1).getMonth()).toBe(0); // Dec → Jan
17
+ expect(addMonths(new Date(2024, 11, 10), 1).getFullYear()).toBe(2025);
18
+ // Jan 31 + 1 month → Feb (clamped, never spills to March)
19
+ expect(addMonths(new Date(2024, 0, 31), 1).getMonth()).toBe(1);
20
+ });
21
+ it('getMonthGrid yields 42 cells with correct in-month flags', () => {
22
+ const cells = getMonthGrid(new Date(2024, 0, 15)); // Jan 2024, Sunday-start
23
+ expect(cells).toHaveLength(42);
24
+ const inMonth = cells.filter((c) => c.inMonth);
25
+ expect(inMonth).toHaveLength(31); // January has 31 days
26
+ expect(inMonth[0]?.date.getDate()).toBe(1);
27
+ });
28
+ it('getMonthGrid Monday-start shifts the lead', () => {
29
+ // Jan 2024: 1st is a Monday → Monday-start grid begins exactly on the 1st
30
+ const cells = getMonthGrid(new Date(2024, 0, 1), 1);
31
+ expect(cells[0]?.date.getDate()).toBe(1);
32
+ expect(cells[0]?.inMonth).toBe(true);
33
+ });
34
+ it('weekdayOrder rotates by week start', () => {
35
+ expect(weekdayOrder(0)).toEqual([0, 1, 2, 3, 4, 5, 6]);
36
+ expect(weekdayOrder(1)).toEqual([1, 2, 3, 4, 5, 6, 0]);
37
+ });
38
+ it('addWeeks steps by 7 days and crosses months', () => {
39
+ expect(addWeeks(new Date(2024, 0, 1), 1).getDate()).toBe(8);
40
+ expect(addWeeks(new Date(2024, 0, 8), -1).getDate()).toBe(1);
41
+ // Jan 29 + 1 week → Feb 5 (crosses month boundary)
42
+ const r = addWeeks(new Date(2024, 0, 29), 1);
43
+ expect(r.getMonth()).toBe(1);
44
+ expect(r.getDate()).toBe(5);
45
+ });
46
+ it('getWeekGrid yields 7 cells starting on the week start', () => {
47
+ // 2024-01-15 is a Monday; Sunday-start week begins 2024-01-14
48
+ const sun = getWeekGrid(new Date(2024, 0, 15), 0);
49
+ expect(sun).toHaveLength(7);
50
+ expect(sun[0]?.date.getDate()).toBe(14);
51
+ expect(sun[0]?.date.getDay()).toBe(0);
52
+ expect(sun[6]?.date.getDate()).toBe(20);
53
+ // Monday-start week containing the 15th begins on the 15th
54
+ const mon = getWeekGrid(new Date(2024, 0, 15), 1);
55
+ expect(mon[0]?.date.getDate()).toBe(15);
56
+ expect(mon[0]?.date.getDay()).toBe(1);
57
+ });
58
+ it('getWeekGrid flags inMonth relative to cursor month', () => {
59
+ // 2024-02-01 is a Thursday; Sunday-start week spills into late January
60
+ const cells = getWeekGrid(new Date(2024, 1, 1), 0);
61
+ expect(cells[0]?.date.getMonth()).toBe(0); // Jan 28
62
+ expect(cells[0]?.inMonth).toBe(false);
63
+ expect(cells.some((c) => c.inMonth)).toBe(true);
64
+ });
65
+ it('addDays steps by 1 day and crosses month boundaries', () => {
66
+ expect(addDays(new Date(2024, 0, 1), 1).getDate()).toBe(2);
67
+ expect(addDays(new Date(2024, 0, 1), -1).getMonth()).toBe(11); // Dec 31, 2023
68
+ expect(addDays(new Date(2024, 0, 1), -1).getFullYear()).toBe(2023);
69
+ // preserves time-of-day
70
+ const t = addDays(new Date(2024, 0, 1, 13, 30), 1);
71
+ expect(t.getHours()).toBe(13);
72
+ expect(t.getMinutes()).toBe(30);
73
+ });
74
+ it('getDayHours yields one slot per hour in range on the cursor day', () => {
75
+ const slots = getDayHours(new Date(2024, 5, 15, 8, 30));
76
+ expect(slots).toHaveLength(24);
77
+ expect(slots[0]?.hour).toBe(0);
78
+ expect(slots[0]?.date.getHours()).toBe(0);
79
+ expect(slots[0]?.date.getDate()).toBe(15);
80
+ expect(slots[23]?.hour).toBe(23);
81
+ });
82
+ it('getDayHours honours and clamps a custom from/to range', () => {
83
+ const slots = getDayHours(new Date(2024, 5, 15), 9, 11);
84
+ expect(slots.map((s) => s.hour)).toEqual([9, 10, 11]);
85
+ // out-of-bounds clamps into 0..23
86
+ expect(getDayHours(new Date(2024, 5, 15), -5, 30)).toHaveLength(24);
87
+ });
88
+ it('daysBetween counts whole days ignoring time-of-day', () => {
89
+ expect(daysBetween(new Date(2024, 0, 1, 23), new Date(2024, 0, 2, 1))).toBe(1);
90
+ expect(daysBetween(new Date(2024, 0, 1), new Date(2024, 0, 1, 23))).toBe(0);
91
+ expect(daysBetween(new Date(2024, 0, 10), new Date(2024, 0, 1))).toBe(-9);
92
+ // crosses month boundary
93
+ expect(daysBetween(new Date(2024, 0, 31), new Date(2024, 1, 2))).toBe(2);
94
+ });
95
+ });
96
+ describe('formatDate', () => {
97
+ it('formats common patterns with zero-padding', () => {
98
+ const d = new Date(2024, 2, 7, 9, 5, 3);
99
+ expect(formatDate(d, 'YYYY-MM-DD')).toBe('2024-03-07');
100
+ expect(formatDate(d, 'YYYY/MM/DD HH:mm')).toBe('2024/03/07 09:05');
101
+ expect(formatDate(d, 'YYYY-MM-DD HH:mm:ss')).toBe('2024-03-07 09:05:03');
102
+ });
103
+ it('emits non-token characters verbatim', () => {
104
+ const d = new Date(2024, 11, 25);
105
+ expect(formatDate(d, 'YYYY 年 MM 月 DD 日')).toBe('2024 年 12 月 25 日');
106
+ });
107
+ });
108
+ describe('parseDateString', () => {
109
+ it('round-trips with formatDate', () => {
110
+ const d = new Date(2024, 2, 7, 9, 5, 0);
111
+ const s = formatDate(d, 'YYYY-MM-DD HH:mm');
112
+ const back = parseDateString(s, 'YYYY-MM-DD HH:mm');
113
+ expect(back?.getTime()).toBe(d.getTime());
114
+ });
115
+ it('parses date-only patterns to start of day', () => {
116
+ const r = parseDateString('2024/03/07', 'YYYY/MM/DD');
117
+ expect(r?.getFullYear()).toBe(2024);
118
+ expect(r?.getMonth()).toBe(2);
119
+ expect(r?.getDate()).toBe(7);
120
+ expect(r?.getHours()).toBe(0);
121
+ });
122
+ it('accepts single-digit fields where the pattern allows', () => {
123
+ const r = parseDateString('2024-3-7', 'YYYY-MM-DD');
124
+ expect(r?.getMonth()).toBe(2);
125
+ expect(r?.getDate()).toBe(7);
126
+ });
127
+ it('rejects non-matching input and out-of-range fields', () => {
128
+ expect(parseDateString('', 'YYYY-MM-DD')).toBeNull();
129
+ expect(parseDateString('not a date', 'YYYY-MM-DD')).toBeNull();
130
+ expect(parseDateString('2024-13-01', 'YYYY-MM-DD')).toBeNull();
131
+ expect(parseDateString('2024-02-30', 'YYYY-MM-DD')).toBeNull(); // overflow rejected
132
+ expect(parseDateString('2024-03-07 25:00', 'YYYY-MM-DD HH:mm')).toBeNull();
133
+ expect(parseDateString('2024/03/07', 'YYYY-MM-DD')).toBeNull(); // wrong separators
134
+ });
135
+ });
136
+ describe('gridFocusMove (roving focus navigation)', () => {
137
+ const base = new Date(2026, 5, 15); // 2026-06-15 (Mon)
138
+ it('arrow left/right step ±1 day', () => {
139
+ expect(isSameDay(gridFocusMove(base, 'ArrowLeft'), new Date(2026, 5, 14))).toBe(true);
140
+ expect(isSameDay(gridFocusMove(base, 'ArrowRight'), new Date(2026, 5, 16))).toBe(true);
141
+ });
142
+ it('arrow up/down step ±1 week in month mode', () => {
143
+ expect(isSameDay(gridFocusMove(base, 'ArrowUp', 'month'), new Date(2026, 5, 8))).toBe(true);
144
+ expect(isSameDay(gridFocusMove(base, 'ArrowDown', 'month'), new Date(2026, 5, 22))).toBe(true);
145
+ });
146
+ it('arrow up/down degrade to ±1 day in week mode (single row)', () => {
147
+ expect(isSameDay(gridFocusMove(base, 'ArrowUp', 'week'), new Date(2026, 5, 14))).toBe(true);
148
+ expect(isSameDay(gridFocusMove(base, 'ArrowDown', 'week'), new Date(2026, 5, 16))).toBe(true);
149
+ });
150
+ it('page up/down step ±1 month in month mode, ±1 week in week mode', () => {
151
+ expect(isSameDay(gridFocusMove(base, 'PageUp', 'month'), new Date(2026, 4, 15))).toBe(true);
152
+ expect(isSameDay(gridFocusMove(base, 'PageDown', 'month'), new Date(2026, 6, 15))).toBe(true);
153
+ expect(isSameDay(gridFocusMove(base, 'PageUp', 'week'), new Date(2026, 5, 8))).toBe(true);
154
+ expect(isSameDay(gridFocusMove(base, 'PageDown', 'week'), new Date(2026, 5, 22))).toBe(true);
155
+ });
156
+ it('Home/End go to first/last day of the visible week (week start aware)', () => {
157
+ // Sunday-start: week of 2026-06-15 (Mon) is Sun 14 .. Sat 20
158
+ expect(isSameDay(gridFocusMove(base, 'Home', 'month', 0), new Date(2026, 5, 14))).toBe(true);
159
+ expect(isSameDay(gridFocusMove(base, 'End', 'month', 0), new Date(2026, 5, 20))).toBe(true);
160
+ // Monday-start: week is Mon 15 .. Sun 21
161
+ expect(isSameDay(gridFocusMove(base, 'Home', 'month', 1), new Date(2026, 5, 15))).toBe(true);
162
+ expect(isSameDay(gridFocusMove(base, 'End', 'month', 1), new Date(2026, 5, 21))).toBe(true);
163
+ });
164
+ it('crosses month boundary purely (caller re-derives the grid)', () => {
165
+ const firstOfMonth = new Date(2026, 5, 1); // Mon Jun 1
166
+ expect(isSameDay(gridFocusMove(firstOfMonth, 'ArrowLeft'), new Date(2026, 4, 31))).toBe(true);
167
+ const lastOfMonth = new Date(2026, 5, 30);
168
+ expect(isSameDay(gridFocusMove(lastOfMonth, 'ArrowRight'), new Date(2026, 6, 1))).toBe(true);
169
+ });
170
+ });
171
+ //# sourceMappingURL=date.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"date.test.js","sourceRoot":"","sources":["../src/date.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * useDismiss — close-on-outside-click and close-on-Escape for overlays.
3
+ */
4
+ /** 关闭来源:Esc 键 / 外部点击 */
5
+ export type DismissReason = 'esc' | 'outsideClick';
6
+ export interface DismissOptions {
7
+ /** 关闭回调,附带来源 reason('esc' | 'outsideClick') */
8
+ onDismiss: (reason: DismissReason) => void;
9
+ /** close when Escape pressed (default true) */
10
+ escape?: boolean;
11
+ /** close when clicking outside the element (default true) */
12
+ outsideClick?: boolean;
13
+ /**
14
+ * extra elements that also count as "inside" for outsideClick. Use this when
15
+ * a popup is portaled out of `element`'s subtree (e.g. floating to body) so a
16
+ * click inside the portaled popup is not treated as an outside click.
17
+ */
18
+ extraTargets?: Array<HTMLElement | null | undefined>;
19
+ /**
20
+ * Fired when an outside pointerdown is detected, *before* `onDismiss`. Receives
21
+ * the original PointerEvent. Return `false` (or call `preventDefault()`) to keep
22
+ * the overlay open — the dismiss is then skipped. Useful for an interceptable
23
+ * "clickOutside" hook distinct from the actual close.
24
+ */
25
+ onOutsidePointer?: (event: PointerEvent) => boolean | void;
26
+ }
27
+ export declare function useDismiss(element: HTMLElement, options: DismissOptions): () => void;
28
+ //# sourceMappingURL=dismiss.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dismiss.d.ts","sourceRoot":"","sources":["../src/dismiss.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,wBAAwB;AACxB,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,cAAc,CAAC;AAEnD,MAAM,WAAW,cAAc;IAC7B,+CAA+C;IAC/C,SAAS,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,CAAC;IAC3C,+CAA+C;IAC/C,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,6DAA6D;IAC7D,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;;OAIG;IACH,YAAY,CAAC,EAAE,KAAK,CAAC,WAAW,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACrD;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,GAAG,IAAI,CAAC;CAC5D;AAED,wBAAgB,UAAU,CACxB,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,cAAc,GACtB,MAAM,IAAI,CAoCZ"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * useDismiss — close-on-outside-click and close-on-Escape for overlays.
3
+ */
4
+ export function useDismiss(element, options) {
5
+ const { onDismiss, escape = true, outsideClick = true, extraTargets, onOutsidePointer } = options;
6
+ function isInside(target) {
7
+ if (element.contains(target))
8
+ return true;
9
+ if (extraTargets) {
10
+ for (const el of extraTargets) {
11
+ if (el && el.contains(target))
12
+ return true;
13
+ }
14
+ }
15
+ return false;
16
+ }
17
+ function onKeydown(e) {
18
+ if (escape && e.key === 'Escape')
19
+ onDismiss('esc');
20
+ }
21
+ function onPointer(e) {
22
+ if (!outsideClick)
23
+ return;
24
+ if (isInside(e.target))
25
+ return;
26
+ // Interceptable hook: fire before the dismiss decision. Returning false or
27
+ // calling preventDefault() keeps the overlay open.
28
+ if (onOutsidePointer) {
29
+ const keep = onOutsidePointer(e) === false || e.defaultPrevented;
30
+ if (keep)
31
+ return;
32
+ }
33
+ onDismiss('outsideClick');
34
+ }
35
+ document.addEventListener('keydown', onKeydown);
36
+ document.addEventListener('pointerdown', onPointer, true);
37
+ return () => {
38
+ document.removeEventListener('keydown', onKeydown);
39
+ document.removeEventListener('pointerdown', onPointer, true);
40
+ };
41
+ }
42
+ //# sourceMappingURL=dismiss.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dismiss.js","sourceRoot":"","sources":["../src/dismiss.ts"],"names":[],"mappings":"AAAA;;GAEG;AA2BH,MAAM,UAAU,UAAU,CACxB,OAAoB,EACpB,OAAuB;IAEvB,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,YAAY,GAAG,IAAI,EAAE,YAAY,EAAE,gBAAgB,EAAE,GACrF,OAAO,CAAC;IAEV,SAAS,QAAQ,CAAC,MAAmB;QACnC,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;YAAE,OAAO,IAAI,CAAC;QAC1C,IAAI,YAAY,EAAE,CAAC;YACjB,KAAK,MAAM,EAAE,IAAI,YAAY,EAAE,CAAC;gBAC9B,IAAI,EAAE,IAAI,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC;oBAAE,OAAO,IAAI,CAAC;YAC7C,CAAC;QACH,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,SAAS,SAAS,CAAC,CAAgB;QACjC,IAAI,MAAM,IAAI,CAAC,CAAC,GAAG,KAAK,QAAQ;YAAE,SAAS,CAAC,KAAK,CAAC,CAAC;IACrD,CAAC;IACD,SAAS,SAAS,CAAC,CAAe;QAChC,IAAI,CAAC,YAAY;YAAE,OAAO;QAC1B,IAAI,QAAQ,CAAC,CAAC,CAAC,MAAc,CAAC;YAAE,OAAO;QACvC,2EAA2E;QAC3E,mDAAmD;QACnD,IAAI,gBAAgB,EAAE,CAAC;YACrB,MAAM,IAAI,GAAG,gBAAgB,CAAC,CAAC,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,gBAAgB,CAAC;YACjE,IAAI,IAAI;gBAAE,OAAO;QACnB,CAAC;QACD,SAAS,CAAC,cAAc,CAAC,CAAC;IAC5B,CAAC;IAED,QAAQ,CAAC,gBAAgB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IAChD,QAAQ,CAAC,gBAAgB,CAAC,aAAa,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;IAE1D,OAAO,GAAG,EAAE;QACV,QAAQ,CAAC,mBAAmB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;QACnD,QAAQ,CAAC,mBAAmB,CAAC,aAAa,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;IAC/D,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * createEditable — framework-agnostic inline-edit state machine for Typography.
3
+ *
4
+ * State: idle | editing. Manages a draft value, commit/cancel transitions and
5
+ * the keyboard contract (Enter commits, Shift+Enter inserts a newline, Esc
6
+ * cancels). Pure logic + small imperative key/value helpers — no DOM, no
7
+ * framework reactivity. The render layer owns the <textarea>, focus moves and
8
+ * the controlled `value` (red line #1: editable does NOT write back; it only
9
+ * surfaces the draft via onChange on commit).
10
+ *
11
+ * See specs/components/basic/Typography.spec.md §3, §4.3, §11.
12
+ */
13
+ /** clamp a draft to maxLength (in code units); undefined/0-or-less = no limit. */
14
+ export declare function clampLength(value: string, maxLength?: number): string;
15
+ export type EditableTrigger = 'click' | 'dblclick' | 'icon';
16
+ export interface EditableKeyEvent {
17
+ key: string;
18
+ shiftKey?: boolean;
19
+ }
20
+ /** What a keydown should do while editing. */
21
+ export type EditableKeyAction = 'confirm' | 'cancel' | 'newline' | 'none';
22
+ /** Pure: map a keydown (while editing) to its semantic action. */
23
+ export declare function resolveEditableKey(e: EditableKeyEvent): EditableKeyAction;
24
+ export interface EditableOptions {
25
+ /** initial / source value (controlled by the render layer) */
26
+ value?: string;
27
+ /** character cap applied to the draft */
28
+ maxLength?: number;
29
+ /** how editing is entered (informational; render layer wires the trigger) */
30
+ trigger?: EditableTrigger;
31
+ /** re-render hook fired whenever editing/draft changes */
32
+ onChange?: (state: EditableSnapshot) => void;
33
+ /** fired on commit with the final (clamped) draft value */
34
+ onCommit?: (value: string) => void;
35
+ /** fired when entering edit mode */
36
+ onStart?: () => void;
37
+ /** fired when editing is cancelled */
38
+ onCancel?: () => void;
39
+ }
40
+ export interface EditableSnapshot {
41
+ editing: boolean;
42
+ draft: string;
43
+ }
44
+ export interface EditableApi {
45
+ readonly editing: boolean;
46
+ readonly draft: string;
47
+ /** enter edit mode, seeding the draft from `source` (or the configured value) */
48
+ start(source?: string): void;
49
+ /** update the draft (auto-clamped to maxLength) */
50
+ setDraft(next: string): void;
51
+ /** commit the current draft: leave edit mode + fire onCommit when changed */
52
+ confirm(): void;
53
+ /** discard the draft, leave edit mode, fire onCancel */
54
+ cancel(): void;
55
+ /** handle a keydown while editing; returns the action taken */
56
+ handleKey(e: EditableKeyEvent): EditableKeyAction;
57
+ }
58
+ export declare function createEditable(options?: EditableOptions): EditableApi;
59
+ //# sourceMappingURL=editable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editable.d.ts","sourceRoot":"","sources":["../src/editable.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,kFAAkF;AAClF,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAGrE;AAED,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,UAAU,GAAG,MAAM,CAAC;AAE5D,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,8CAA8C;AAC9C,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAC;AAE1E,kEAAkE;AAClE,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,gBAAgB,GAAG,iBAAiB,CAIzE;AAED,MAAM,WAAW,eAAe;IAC9B,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yCAAyC;IACzC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6EAA6E;IAC7E,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC7C,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,oCAAoC;IACpC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,iFAAiF;IACjF,KAAK,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,mDAAmD;IACnD,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,6EAA6E;IAC7E,OAAO,IAAI,IAAI,CAAC;IAChB,wDAAwD;IACxD,MAAM,IAAI,IAAI,CAAC;IACf,+DAA+D;IAC/D,SAAS,CAAC,CAAC,EAAE,gBAAgB,GAAG,iBAAiB,CAAC;CACnD;AAED,wBAAgB,cAAc,CAAC,OAAO,GAAE,eAAoB,GAAG,WAAW,CAqDzE"}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * createEditable — framework-agnostic inline-edit state machine for Typography.
3
+ *
4
+ * State: idle | editing. Manages a draft value, commit/cancel transitions and
5
+ * the keyboard contract (Enter commits, Shift+Enter inserts a newline, Esc
6
+ * cancels). Pure logic + small imperative key/value helpers — no DOM, no
7
+ * framework reactivity. The render layer owns the <textarea>, focus moves and
8
+ * the controlled `value` (red line #1: editable does NOT write back; it only
9
+ * surfaces the draft via onChange on commit).
10
+ *
11
+ * See specs/components/basic/Typography.spec.md §3, §4.3, §11.
12
+ */
13
+ /** clamp a draft to maxLength (in code units); undefined/0-or-less = no limit. */
14
+ export function clampLength(value, maxLength) {
15
+ if (maxLength === undefined || maxLength <= 0)
16
+ return value;
17
+ return value.length > maxLength ? value.slice(0, maxLength) : value;
18
+ }
19
+ /** Pure: map a keydown (while editing) to its semantic action. */
20
+ export function resolveEditableKey(e) {
21
+ if (e.key === 'Escape')
22
+ return 'cancel';
23
+ if (e.key === 'Enter')
24
+ return e.shiftKey ? 'newline' : 'confirm';
25
+ return 'none';
26
+ }
27
+ export function createEditable(options = {}) {
28
+ let editing = false;
29
+ let draft = options.value ?? '';
30
+ function emit() {
31
+ options.onChange?.({ editing, draft });
32
+ }
33
+ return {
34
+ get editing() {
35
+ return editing;
36
+ },
37
+ get draft() {
38
+ return draft;
39
+ },
40
+ start(source) {
41
+ if (editing)
42
+ return;
43
+ editing = true;
44
+ draft = clampLength(source ?? options.value ?? '', options.maxLength);
45
+ options.onStart?.();
46
+ emit();
47
+ },
48
+ setDraft(next) {
49
+ const clamped = clampLength(next, options.maxLength);
50
+ if (clamped === draft)
51
+ return;
52
+ draft = clamped;
53
+ emit();
54
+ },
55
+ confirm() {
56
+ if (!editing)
57
+ return;
58
+ editing = false;
59
+ const committed = draft;
60
+ // red line #1: surface via callback only; never self-mutate `value`.
61
+ if (committed !== (options.value ?? '')) {
62
+ options.onCommit?.(committed);
63
+ }
64
+ emit();
65
+ },
66
+ cancel() {
67
+ if (!editing)
68
+ return;
69
+ editing = false;
70
+ draft = options.value ?? '';
71
+ options.onCancel?.();
72
+ emit();
73
+ },
74
+ handleKey(e) {
75
+ const action = resolveEditableKey(e);
76
+ if (action === 'confirm')
77
+ this.confirm();
78
+ else if (action === 'cancel')
79
+ this.cancel();
80
+ // 'newline' / 'none' are left to the textarea's native behavior
81
+ return action;
82
+ },
83
+ };
84
+ }
85
+ //# sourceMappingURL=editable.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editable.js","sourceRoot":"","sources":["../src/editable.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,kFAAkF;AAClF,MAAM,UAAU,WAAW,CAAC,KAAa,EAAE,SAAkB;IAC3D,IAAI,SAAS,KAAK,SAAS,IAAI,SAAS,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IAC5D,OAAO,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AACtE,CAAC;AAYD,kEAAkE;AAClE,MAAM,UAAU,kBAAkB,CAAC,CAAmB;IACpD,IAAI,CAAC,CAAC,GAAG,KAAK,QAAQ;QAAE,OAAO,QAAQ,CAAC;IACxC,IAAI,CAAC,CAAC,GAAG,KAAK,OAAO;QAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;IACjE,OAAO,MAAM,CAAC;AAChB,CAAC;AAuCD,MAAM,UAAU,cAAc,CAAC,UAA2B,EAAE;IAC1D,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,IAAI,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;IAEhC,SAAS,IAAI;QACX,OAAO,CAAC,QAAQ,EAAE,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;IACzC,CAAC;IAED,OAAO;QACL,IAAI,OAAO;YACT,OAAO,OAAO,CAAC;QACjB,CAAC;QACD,IAAI,KAAK;YACP,OAAO,KAAK,CAAC;QACf,CAAC;QACD,KAAK,CAAC,MAAM;YACV,IAAI,OAAO;gBAAE,OAAO;YACpB,OAAO,GAAG,IAAI,CAAC;YACf,KAAK,GAAG,WAAW,CAAC,MAAM,IAAI,OAAO,CAAC,KAAK,IAAI,EAAE,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;YACtE,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;YACpB,IAAI,EAAE,CAAC;QACT,CAAC;QACD,QAAQ,CAAC,IAAI;YACX,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;YACrD,IAAI,OAAO,KAAK,KAAK;gBAAE,OAAO;YAC9B,KAAK,GAAG,OAAO,CAAC;YAChB,IAAI,EAAE,CAAC;QACT,CAAC;QACD,OAAO;YACL,IAAI,CAAC,OAAO;gBAAE,OAAO;YACrB,OAAO,GAAG,KAAK,CAAC;YAChB,MAAM,SAAS,GAAG,KAAK,CAAC;YACxB,qEAAqE;YACrE,IAAI,SAAS,KAAK,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC,EAAE,CAAC;gBACxC,OAAO,CAAC,QAAQ,EAAE,CAAC,SAAS,CAAC,CAAC;YAChC,CAAC;YACD,IAAI,EAAE,CAAC;QACT,CAAC;QACD,MAAM;YACJ,IAAI,CAAC,OAAO;gBAAE,OAAO;YACrB,OAAO,GAAG,KAAK,CAAC;YAChB,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;YAC5B,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC;YACrB,IAAI,EAAE,CAAC;QACT,CAAC;QACD,SAAS,CAAC,CAAC;YACT,MAAM,MAAM,GAAG,kBAAkB,CAAC,CAAC,CAAC,CAAC;YACrC,IAAI,MAAM,KAAK,SAAS;gBAAE,IAAI,CAAC,OAAO,EAAE,CAAC;iBACpC,IAAI,MAAM,KAAK,QAAQ;gBAAE,IAAI,CAAC,MAAM,EAAE,CAAC;YAC5C,gEAAgE;YAChE,OAAO,MAAM,CAAC;QAChB,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=editable.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editable.test.d.ts","sourceRoot":"","sources":["../src/editable.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,111 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { createEditable, clampLength, resolveEditableKey } from './editable.js';
3
+ describe('clampLength', () => {
4
+ it('returns value unchanged when no maxLength', () => {
5
+ expect(clampLength('hello')).toBe('hello');
6
+ });
7
+ it('truncates to maxLength', () => {
8
+ expect(clampLength('hello', 3)).toBe('hel');
9
+ });
10
+ it('ignores maxLength <= 0', () => {
11
+ expect(clampLength('hello', 0)).toBe('hello');
12
+ });
13
+ });
14
+ describe('resolveEditableKey', () => {
15
+ it('Enter → confirm', () => {
16
+ expect(resolveEditableKey({ key: 'Enter' })).toBe('confirm');
17
+ });
18
+ it('Shift+Enter → newline', () => {
19
+ expect(resolveEditableKey({ key: 'Enter', shiftKey: true })).toBe('newline');
20
+ });
21
+ it('Escape → cancel', () => {
22
+ expect(resolveEditableKey({ key: 'Escape' })).toBe('cancel');
23
+ });
24
+ it('other → none', () => {
25
+ expect(resolveEditableKey({ key: 'a' })).toBe('none');
26
+ });
27
+ });
28
+ describe('createEditable', () => {
29
+ it('start enters editing and seeds draft from value', () => {
30
+ const e = createEditable({ value: 'orig' });
31
+ e.start();
32
+ expect(e.editing).toBe(true);
33
+ expect(e.draft).toBe('orig');
34
+ });
35
+ it('start(source) seeds from explicit source', () => {
36
+ const e = createEditable({ value: 'orig' });
37
+ e.start('other');
38
+ expect(e.draft).toBe('other');
39
+ });
40
+ it('setDraft clamps to maxLength', () => {
41
+ const e = createEditable({ value: '', maxLength: 3 });
42
+ e.start();
43
+ e.setDraft('abcdef');
44
+ expect(e.draft).toBe('abc');
45
+ });
46
+ it('confirm fires onCommit only when changed and exits editing', () => {
47
+ const onCommit = vi.fn();
48
+ const e = createEditable({ value: 'a', onCommit });
49
+ e.start();
50
+ e.setDraft('b');
51
+ e.confirm();
52
+ expect(e.editing).toBe(false);
53
+ expect(onCommit).toHaveBeenCalledWith('b');
54
+ });
55
+ it('confirm does not fire onCommit when unchanged', () => {
56
+ const onCommit = vi.fn();
57
+ const e = createEditable({ value: 'a', onCommit });
58
+ e.start();
59
+ e.confirm();
60
+ expect(onCommit).not.toHaveBeenCalled();
61
+ });
62
+ it('cancel restores value and fires onCancel', () => {
63
+ const onCancel = vi.fn();
64
+ const onCommit = vi.fn();
65
+ const e = createEditable({ value: 'a', onCancel, onCommit });
66
+ e.start();
67
+ e.setDraft('changed');
68
+ e.cancel();
69
+ expect(e.editing).toBe(false);
70
+ expect(e.draft).toBe('a');
71
+ expect(onCancel).toHaveBeenCalled();
72
+ expect(onCommit).not.toHaveBeenCalled();
73
+ });
74
+ it('handleKey Enter confirms', () => {
75
+ const onCommit = vi.fn();
76
+ const e = createEditable({ value: 'a', onCommit });
77
+ e.start();
78
+ e.setDraft('b');
79
+ const action = e.handleKey({ key: 'Enter' });
80
+ expect(action).toBe('confirm');
81
+ expect(onCommit).toHaveBeenCalledWith('b');
82
+ });
83
+ it('handleKey Esc cancels', () => {
84
+ const e = createEditable({ value: 'a' });
85
+ e.start();
86
+ e.setDraft('b');
87
+ const action = e.handleKey({ key: 'Escape' });
88
+ expect(action).toBe('cancel');
89
+ expect(e.editing).toBe(false);
90
+ expect(e.draft).toBe('a');
91
+ });
92
+ it('handleKey Shift+Enter is newline (no confirm)', () => {
93
+ const onCommit = vi.fn();
94
+ const e = createEditable({ value: 'a', onCommit });
95
+ e.start();
96
+ const action = e.handleKey({ key: 'Enter', shiftKey: true });
97
+ expect(action).toBe('newline');
98
+ expect(e.editing).toBe(true);
99
+ expect(onCommit).not.toHaveBeenCalled();
100
+ });
101
+ it('does not write back value (red line #1)', () => {
102
+ const opts = { value: 'a', onCommit: vi.fn() };
103
+ const e = createEditable(opts);
104
+ e.start();
105
+ e.setDraft('b');
106
+ e.confirm();
107
+ // options.value untouched; only callback surfaced the new value
108
+ expect(opts.value).toBe('a');
109
+ });
110
+ });
111
+ //# sourceMappingURL=editable.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editable.test.js","sourceRoot":"","sources":["../src/editable.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * createEllipsis — framework-agnostic truncation/expand logic for Typography.
3
+ *
4
+ * Two paths (per spec §9):
5
+ * - CSS clamp (default): no measurement at all — the render layer applies
6
+ * `-webkit-line-clamp`/`text-overflow`. This module only tracks `expanded`
7
+ * and the expand/collapse toggle; `truncated` stays unknown (CSS handles it).
8
+ * - Measurement path (expandable / suffix / showTooltip): the render layer
9
+ * feeds measured box metrics into `measure()`, which decides whether the
10
+ * content overflows (`truncated`). Pure decision — no DOM here; the svelte
11
+ * layer owns the ResizeObserver + rAF-throttled measurement (red line #3).
12
+ *
13
+ * Pure helpers (overflow decision, middle-truncation slicing) are independently
14
+ * unit-tested. No framework deps, no DOM.
15
+ * See specs/components/basic/Typography.spec.md §3, §4.3, §11.
16
+ */
17
+ export type EllipsisPos = 'start' | 'middle' | 'end';
18
+ export interface EllipsisMeasureInput {
19
+ /** rendered scroll height of the content (px) */
20
+ scrollHeight: number;
21
+ /** visible client height of the clamp box (px) */
22
+ clientHeight: number;
23
+ /** rendered scroll width (px) — used for single-row overflow detection */
24
+ scrollWidth?: number;
25
+ /** visible client width (px) */
26
+ clientWidth?: number;
27
+ /** number of clamp rows (1 = single line) */
28
+ rows: number;
29
+ }
30
+ /**
31
+ * Pure: decide whether content overflows its clamp box. For multi-row we compare
32
+ * heights; for a single row we additionally allow a width comparison. A 1px
33
+ * tolerance absorbs sub-pixel rounding so equal content is not flagged.
34
+ */
35
+ export declare function isOverflowing(input: EllipsisMeasureInput): boolean;
36
+ /**
37
+ * Pure: build a middle/start/end truncated string given a budget of visible
38
+ * characters. Used only on the single-line precise path (pos: middle/start);
39
+ * end truncation is normally left to CSS. Always inserts the ellipsis char.
40
+ */
41
+ export declare function truncateText(text: string, maxChars: number, pos: EllipsisPos, ellipsis?: string): string;
42
+ export interface EllipsisOptions {
43
+ /** clamp rows (default 1) */
44
+ rows?: number;
45
+ /** whether an expand toggle is offered */
46
+ expandable?: boolean;
47
+ /** whether a collapse toggle is offered after expansion (Semi `collapsible`) */
48
+ collapsible?: boolean;
49
+ /** truncation position (single-line precise path); default 'end' */
50
+ pos?: EllipsisPos;
51
+ /** initial expanded state */
52
+ defaultExpanded?: boolean;
53
+ /** re-render hook fired when expanded/truncated changes */
54
+ onChange?: (state: EllipsisSnapshot) => void;
55
+ /** fired on expand/collapse toggle */
56
+ onExpand?: (expanded: boolean) => void;
57
+ }
58
+ export interface EllipsisSnapshot {
59
+ rows: number;
60
+ expanded: boolean;
61
+ /** null = unknown (CSS clamp path, not measured) */
62
+ truncated: boolean | null;
63
+ }
64
+ export interface EllipsisApi {
65
+ readonly rows: number;
66
+ readonly expanded: boolean;
67
+ readonly truncated: boolean | null;
68
+ /** toggle expanded (no-op when not expandable) */
69
+ toggle(): void;
70
+ /** explicitly set expanded */
71
+ setExpanded(next: boolean): void;
72
+ /** feed measured metrics (measurement path) → updates `truncated` */
73
+ measure(input: EllipsisMeasureInput): boolean;
74
+ }
75
+ export declare function createEllipsis(options?: EllipsisOptions): EllipsisApi;
76
+ //# sourceMappingURL=ellipsis.d.ts.map