@assistant-ui/react-generative-ui 0.0.24 → 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 (338) hide show
  1. package/README.md +2 -0
  2. package/dist/a2ui.d.ts +1 -4
  3. package/dist/a2ui.d.ts.map +1 -1
  4. package/dist/a2ui.js +1 -4
  5. package/dist/index.d.ts +1 -2
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +1 -10
  8. package/dist/index.server.d.ts +1 -2
  9. package/dist/index.server.d.ts.map +1 -1
  10. package/dist/index.server.js +1 -10
  11. package/dist/ir.d.ts +1 -111
  12. package/dist/ir.d.ts.map +1 -1
  13. package/dist/ir.js +1 -175
  14. package/dist/slack.d.ts +1 -4
  15. package/dist/slack.d.ts.map +1 -1
  16. package/dist/slack.js +1 -4
  17. package/dist/teams.d.ts +1 -4
  18. package/dist/teams.d.ts.map +1 -1
  19. package/dist/teams.js +1 -4
  20. package/package.json +4 -5
  21. package/src/a2ui.ts +1 -22
  22. package/src/index.server.ts +1 -2
  23. package/src/index.test.ts +75 -0
  24. package/src/index.ts +1 -2
  25. package/src/ir.ts +1 -279
  26. package/src/slack.ts +1 -39
  27. package/src/teams.ts +1 -35
  28. package/dist/JSONGenerativeUI.client.d.ts +0 -28
  29. package/dist/JSONGenerativeUI.client.d.ts.map +0 -1
  30. package/dist/JSONGenerativeUI.client.js +0 -101
  31. package/dist/JSONGenerativeUI.client.js.map +0 -1
  32. package/dist/JSONGenerativeUI.server.d.ts +0 -21
  33. package/dist/JSONGenerativeUI.server.d.ts.map +0 -1
  34. package/dist/JSONGenerativeUI.server.js +0 -32
  35. package/dist/JSONGenerativeUI.server.js.map +0 -1
  36. package/dist/JSONGenerativeUI.shared.d.ts +0 -63
  37. package/dist/JSONGenerativeUI.shared.d.ts.map +0 -1
  38. package/dist/JSONGenerativeUI.shared.js +0 -31
  39. package/dist/JSONGenerativeUI.shared.js.map +0 -1
  40. package/dist/RadioGroupScope.d.ts +0 -6
  41. package/dist/RadioGroupScope.d.ts.map +0 -1
  42. package/dist/RadioGroupScope.js +0 -25
  43. package/dist/RadioGroupScope.js.map +0 -1
  44. package/dist/a2ui/BindingContext.d.ts +0 -11
  45. package/dist/a2ui/BindingContext.d.ts.map +0 -1
  46. package/dist/a2ui/BindingContext.js +0 -12
  47. package/dist/a2ui/BindingContext.js.map +0 -1
  48. package/dist/a2ui/PresentRenderer.d.ts +0 -11
  49. package/dist/a2ui/PresentRenderer.d.ts.map +0 -1
  50. package/dist/a2ui/PresentRenderer.js +0 -39
  51. package/dist/a2ui/PresentRenderer.js.map +0 -1
  52. package/dist/a2ui/convert.d.ts +0 -15
  53. package/dist/a2ui/convert.d.ts.map +0 -1
  54. package/dist/a2ui/convert.js +0 -728
  55. package/dist/a2ui/convert.js.map +0 -1
  56. package/dist/a2ui/dataModel.d.ts +0 -9
  57. package/dist/a2ui/dataModel.d.ts.map +0 -1
  58. package/dist/a2ui/dataModel.js +0 -44
  59. package/dist/a2ui/dataModel.js.map +0 -1
  60. package/dist/a2ui/reducer.d.ts +0 -8
  61. package/dist/a2ui/reducer.d.ts.map +0 -1
  62. package/dist/a2ui/reducer.js +0 -242
  63. package/dist/a2ui/reducer.js.map +0 -1
  64. package/dist/a2ui/snapshot.d.ts +0 -3
  65. package/dist/a2ui/snapshot.d.ts.map +0 -1
  66. package/dist/a2ui/snapshot.js +0 -33
  67. package/dist/a2ui/snapshot.js.map +0 -1
  68. package/dist/a2ui/types.d.ts +0 -88
  69. package/dist/a2ui/types.d.ts.map +0 -1
  70. package/dist/a2ui/types.js +0 -6
  71. package/dist/a2ui/types.js.map +0 -1
  72. package/dist/a2ui/valueFunctions.d.ts +0 -16
  73. package/dist/a2ui/valueFunctions.d.ts.map +0 -1
  74. package/dist/a2ui/valueFunctions.js +0 -332
  75. package/dist/a2ui/valueFunctions.js.map +0 -1
  76. package/dist/actionRegistry.d.ts +0 -29
  77. package/dist/actionRegistry.d.ts.map +0 -1
  78. package/dist/actionRegistry.js +0 -41
  79. package/dist/actionRegistry.js.map +0 -1
  80. package/dist/answeredValues.d.ts +0 -16
  81. package/dist/answeredValues.d.ts.map +0 -1
  82. package/dist/answeredValues.js +0 -37
  83. package/dist/answeredValues.js.map +0 -1
  84. package/dist/buildPresentParameters.d.ts +0 -21
  85. package/dist/buildPresentParameters.d.ts.map +0 -1
  86. package/dist/buildPresentParameters.js +0 -95
  87. package/dist/buildPresentParameters.js.map +0 -1
  88. package/dist/constants.d.ts +0 -15
  89. package/dist/constants.d.ts.map +0 -1
  90. package/dist/constants.js +0 -19
  91. package/dist/constants.js.map +0 -1
  92. package/dist/convert/boundSpec.d.ts +0 -48
  93. package/dist/convert/boundSpec.d.ts.map +0 -1
  94. package/dist/convert/boundSpec.js +0 -108
  95. package/dist/convert/boundSpec.js.map +0 -1
  96. package/dist/convert/copyBounded.d.ts +0 -25
  97. package/dist/convert/copyBounded.d.ts.map +0 -1
  98. package/dist/convert/copyBounded.js +0 -38
  99. package/dist/convert/copyBounded.js.map +0 -1
  100. package/dist/convert/isElement.d.ts +0 -3
  101. package/dist/convert/isElement.d.ts.map +0 -1
  102. package/dist/convert/isElement.js +0 -6
  103. package/dist/convert/isElement.js.map +0 -1
  104. package/dist/convert/takeRun.d.ts +0 -6
  105. package/dist/convert/takeRun.d.ts.map +0 -1
  106. package/dist/convert/takeRun.js +0 -20
  107. package/dist/convert/takeRun.js.map +0 -1
  108. package/dist/defineGenerativeComponents.d.ts +0 -29
  109. package/dist/defineGenerativeComponents.d.ts.map +0 -1
  110. package/dist/defineGenerativeComponents.js +0 -34
  111. package/dist/defineGenerativeComponents.js.map +0 -1
  112. package/dist/fieldReferences.d.ts +0 -9
  113. package/dist/fieldReferences.d.ts.map +0 -1
  114. package/dist/fieldReferences.js +0 -53
  115. package/dist/fieldReferences.js.map +0 -1
  116. package/dist/generativeUIToJSX.d.ts +0 -18
  117. package/dist/generativeUIToJSX.d.ts.map +0 -1
  118. package/dist/generativeUIToJSX.js +0 -76
  119. package/dist/generativeUIToJSX.js.map +0 -1
  120. package/dist/index.shared.d.ts +0 -15
  121. package/dist/index.shared.d.ts.map +0 -1
  122. package/dist/index.shared.js +0 -9
  123. package/dist/ir.js.map +0 -1
  124. package/dist/renderGenerativeUI.d.ts +0 -14
  125. package/dist/renderGenerativeUI.d.ts.map +0 -1
  126. package/dist/renderGenerativeUI.js +0 -83
  127. package/dist/renderGenerativeUI.js.map +0 -1
  128. package/dist/scopeSchema.d.ts +0 -6
  129. package/dist/scopeSchema.d.ts.map +0 -1
  130. package/dist/scopeSchema.js +0 -56
  131. package/dist/scopeSchema.js.map +0 -1
  132. package/dist/slack/constants.d.ts +0 -93
  133. package/dist/slack/constants.d.ts.map +0 -1
  134. package/dist/slack/constants.js +0 -146
  135. package/dist/slack/constants.js.map +0 -1
  136. package/dist/slack/decodeBlockAction.d.ts +0 -10
  137. package/dist/slack/decodeBlockAction.d.ts.map +0 -1
  138. package/dist/slack/decodeBlockAction.js +0 -129
  139. package/dist/slack/decodeBlockAction.js.map +0 -1
  140. package/dist/slack/fromSlackBlocks.d.ts +0 -27
  141. package/dist/slack/fromSlackBlocks.d.ts.map +0 -1
  142. package/dist/slack/fromSlackBlocks.js +0 -483
  143. package/dist/slack/fromSlackBlocks.js.map +0 -1
  144. package/dist/slack/toSlackBlocks.d.ts +0 -4
  145. package/dist/slack/toSlackBlocks.d.ts.map +0 -1
  146. package/dist/slack/toSlackBlocks.js +0 -901
  147. package/dist/slack/toSlackBlocks.js.map +0 -1
  148. package/dist/slack/types.d.ts +0 -204
  149. package/dist/slack/types.d.ts.map +0 -1
  150. package/dist/slack/types.js +0 -0
  151. package/dist/teams/constants.d.ts +0 -34
  152. package/dist/teams/constants.d.ts.map +0 -1
  153. package/dist/teams/constants.js +0 -68
  154. package/dist/teams/constants.js.map +0 -1
  155. package/dist/teams/decodeSubmitData.d.ts +0 -26
  156. package/dist/teams/decodeSubmitData.d.ts.map +0 -1
  157. package/dist/teams/decodeSubmitData.js +0 -70
  158. package/dist/teams/decodeSubmitData.js.map +0 -1
  159. package/dist/teams/temporalId.d.ts +0 -5
  160. package/dist/teams/temporalId.d.ts.map +0 -1
  161. package/dist/teams/temporalId.js +0 -28
  162. package/dist/teams/temporalId.js.map +0 -1
  163. package/dist/teams/toAdaptiveCard.d.ts +0 -45
  164. package/dist/teams/toAdaptiveCard.d.ts.map +0 -1
  165. package/dist/teams/toAdaptiveCard.js +0 -642
  166. package/dist/teams/toAdaptiveCard.js.map +0 -1
  167. package/dist/teams/toTeamsAttachments.d.ts +0 -10
  168. package/dist/teams/toTeamsAttachments.d.ts.map +0 -1
  169. package/dist/teams/toTeamsAttachments.js +0 -99
  170. package/dist/teams/toTeamsAttachments.js.map +0 -1
  171. package/dist/teams/types.d.ts +0 -224
  172. package/dist/teams/types.d.ts.map +0 -1
  173. package/dist/teams/types.js +0 -0
  174. package/dist/temporal.d.ts +0 -33
  175. package/dist/temporal.d.ts.map +0 -1
  176. package/dist/temporal.js +0 -153
  177. package/dist/temporal.js.map +0 -1
  178. package/dist/types.d.ts +0 -99
  179. package/dist/types.d.ts.map +0 -1
  180. package/dist/types.js +0 -0
  181. package/dist/vocabulary/alert.d.ts +0 -32
  182. package/dist/vocabulary/alert.d.ts.map +0 -1
  183. package/dist/vocabulary/alert.js +0 -160
  184. package/dist/vocabulary/alert.js.map +0 -1
  185. package/dist/vocabulary/collectFormValues.d.ts +0 -23
  186. package/dist/vocabulary/collectFormValues.d.ts.map +0 -1
  187. package/dist/vocabulary/collectFormValues.js +0 -44
  188. package/dist/vocabulary/collectFormValues.js.map +0 -1
  189. package/dist/vocabulary/data.d.ts +0 -66
  190. package/dist/vocabulary/data.d.ts.map +0 -1
  191. package/dist/vocabulary/data.js +0 -503
  192. package/dist/vocabulary/data.js.map +0 -1
  193. package/dist/vocabulary/dispatch.d.ts +0 -9
  194. package/dist/vocabulary/dispatch.d.ts.map +0 -1
  195. package/dist/vocabulary/dispatch.js +0 -50
  196. package/dist/vocabulary/dispatch.js.map +0 -1
  197. package/dist/vocabulary/fact.d.ts +0 -19
  198. package/dist/vocabulary/fact.d.ts.map +0 -1
  199. package/dist/vocabulary/fact.js +0 -51
  200. package/dist/vocabulary/fact.js.map +0 -1
  201. package/dist/vocabulary/form.d.ts +0 -11
  202. package/dist/vocabulary/form.d.ts.map +0 -1
  203. package/dist/vocabulary/form.js +0 -23
  204. package/dist/vocabulary/form.js.map +0 -1
  205. package/dist/vocabulary/formatValue.d.ts +0 -7
  206. package/dist/vocabulary/formatValue.d.ts.map +0 -1
  207. package/dist/vocabulary/formatValue.js +0 -76
  208. package/dist/vocabulary/formatValue.js.map +0 -1
  209. package/dist/vocabulary/icon.d.ts +0 -41
  210. package/dist/vocabulary/icon.d.ts.map +0 -1
  211. package/dist/vocabulary/icon.js +0 -272
  212. package/dist/vocabulary/icon.js.map +0 -1
  213. package/dist/vocabulary/index.d.ts +0 -12
  214. package/dist/vocabulary/index.d.ts.map +0 -1
  215. package/dist/vocabulary/index.js +0 -36
  216. package/dist/vocabulary/index.js.map +0 -1
  217. package/dist/vocabulary/interactive.d.ts +0 -230
  218. package/dist/vocabulary/interactive.d.ts.map +0 -1
  219. package/dist/vocabulary/interactive.js +0 -584
  220. package/dist/vocabulary/interactive.js.map +0 -1
  221. package/dist/vocabulary/layout.d.ts +0 -79
  222. package/dist/vocabulary/layout.d.ts.map +0 -1
  223. package/dist/vocabulary/layout.js +0 -139
  224. package/dist/vocabulary/layout.js.map +0 -1
  225. package/dist/vocabulary/list.d.ts +0 -14
  226. package/dist/vocabulary/list.d.ts.map +0 -1
  227. package/dist/vocabulary/list.js +0 -50
  228. package/dist/vocabulary/list.js.map +0 -1
  229. package/dist/vocabulary/media.d.ts +0 -25
  230. package/dist/vocabulary/media.d.ts.map +0 -1
  231. package/dist/vocabulary/media.js +0 -42
  232. package/dist/vocabulary/media.js.map +0 -1
  233. package/dist/vocabulary/text.d.ts +0 -57
  234. package/dist/vocabulary/text.d.ts.map +0 -1
  235. package/dist/vocabulary/text.js +0 -49
  236. package/dist/vocabulary/text.js.map +0 -1
  237. package/dist/vocabulary/toTextContent.d.ts +0 -2
  238. package/dist/vocabulary/toTextContent.d.ts.map +0 -1
  239. package/dist/vocabulary/toTextContent.js +0 -6
  240. package/dist/vocabulary/toTextContent.js.map +0 -1
  241. package/src/JSONGenerativeUI.client.tsx +0 -168
  242. package/src/JSONGenerativeUI.server.tsx +0 -39
  243. package/src/JSONGenerativeUI.shared.ts +0 -90
  244. package/src/JSONGenerativeUI.test.tsx +0 -1298
  245. package/src/RadioGroupScope.server.test.tsx +0 -24
  246. package/src/RadioGroupScope.tsx +0 -34
  247. package/src/a2ui/BindingContext.ts +0 -26
  248. package/src/a2ui/PresentRenderer.tsx +0 -83
  249. package/src/a2ui/convert.test.ts +0 -2923
  250. package/src/a2ui/convert.ts +0 -1207
  251. package/src/a2ui/dataModel.ts +0 -69
  252. package/src/a2ui/reducer.test.ts +0 -422
  253. package/src/a2ui/reducer.ts +0 -363
  254. package/src/a2ui/snapshot.test.ts +0 -52
  255. package/src/a2ui/snapshot.ts +0 -52
  256. package/src/a2ui/types.ts +0 -114
  257. package/src/a2ui/valueFunctions.test.ts +0 -271
  258. package/src/a2ui/valueFunctions.ts +0 -523
  259. package/src/actionRegistry.test.ts +0 -90
  260. package/src/actionRegistry.ts +0 -88
  261. package/src/answeredValues.test.ts +0 -33
  262. package/src/answeredValues.tsx +0 -70
  263. package/src/buildPresentParameters.test.ts +0 -246
  264. package/src/buildPresentParameters.ts +0 -130
  265. package/src/constants.ts +0 -17
  266. package/src/convert/boundSpec.test.ts +0 -192
  267. package/src/convert/boundSpec.ts +0 -134
  268. package/src/convert/copyBounded.test.ts +0 -131
  269. package/src/convert/copyBounded.ts +0 -38
  270. package/src/convert/isElement.ts +0 -6
  271. package/src/convert/takeRun.test.ts +0 -64
  272. package/src/convert/takeRun.ts +0 -19
  273. package/src/defineGenerativeComponents.ts +0 -38
  274. package/src/fieldReferences.test.ts +0 -66
  275. package/src/fieldReferences.ts +0 -83
  276. package/src/generativeUIToJSX.test.ts +0 -268
  277. package/src/generativeUIToJSX.ts +0 -152
  278. package/src/index.server.test.ts +0 -106
  279. package/src/index.shared.ts +0 -61
  280. package/src/ir.test.ts +0 -203
  281. package/src/renderGenerativeUI.test.tsx +0 -373
  282. package/src/renderGenerativeUI.tsx +0 -165
  283. package/src/scopeSchema.test.ts +0 -72
  284. package/src/scopeSchema.ts +0 -71
  285. package/src/slack/a2uiSlack.test.ts +0 -198
  286. package/src/slack/constants.ts +0 -194
  287. package/src/slack/datePicker.test.ts +0 -612
  288. package/src/slack/decodeBlockAction.test.ts +0 -233
  289. package/src/slack/decodeBlockAction.ts +0 -232
  290. package/src/slack/fromSlackBlocks.test.ts +0 -1465
  291. package/src/slack/fromSlackBlocks.ts +0 -789
  292. package/src/slack/toSlackBlocks.test.ts +0 -3065
  293. package/src/slack/toSlackBlocks.ts +0 -1818
  294. package/src/slack/types.ts +0 -264
  295. package/src/teams/constants.ts +0 -93
  296. package/src/teams/decodeSubmitData.test.ts +0 -397
  297. package/src/teams/decodeSubmitData.ts +0 -110
  298. package/src/teams/temporal.test.ts +0 -766
  299. package/src/teams/temporalId.ts +0 -56
  300. package/src/teams/toAdaptiveCard.test.ts +0 -1897
  301. package/src/teams/toAdaptiveCard.ts +0 -1057
  302. package/src/teams/toTeamsAttachments.test.ts +0 -74
  303. package/src/teams/toTeamsAttachments.ts +0 -133
  304. package/src/teams/types.ts +0 -266
  305. package/src/temporal.test.ts +0 -337
  306. package/src/temporal.ts +0 -263
  307. package/src/types.ts +0 -119
  308. package/src/vocabulary/alert.test.tsx +0 -427
  309. package/src/vocabulary/alert.tsx +0 -217
  310. package/src/vocabulary/collectFormValues.test.ts +0 -389
  311. package/src/vocabulary/collectFormValues.ts +0 -116
  312. package/src/vocabulary/data.test.tsx +0 -858
  313. package/src/vocabulary/data.tsx +0 -819
  314. package/src/vocabulary/datepicker.dom.test.tsx +0 -1457
  315. package/src/vocabulary/dispatch.ts +0 -83
  316. package/src/vocabulary/fact.test.tsx +0 -94
  317. package/src/vocabulary/fact.tsx +0 -54
  318. package/src/vocabulary/form.test.tsx +0 -113
  319. package/src/vocabulary/form.tsx +0 -39
  320. package/src/vocabulary/formatValue.test.ts +0 -72
  321. package/src/vocabulary/formatValue.ts +0 -127
  322. package/src/vocabulary/icon.test.tsx +0 -64
  323. package/src/vocabulary/icon.tsx +0 -174
  324. package/src/vocabulary/index.test.ts +0 -65
  325. package/src/vocabulary/index.ts +0 -33
  326. package/src/vocabulary/interactive.dispatch.test.tsx +0 -454
  327. package/src/vocabulary/interactive.dom.test.tsx +0 -1747
  328. package/src/vocabulary/interactive.test.tsx +0 -377
  329. package/src/vocabulary/interactive.tsx +0 -1109
  330. package/src/vocabulary/layout.test.tsx +0 -323
  331. package/src/vocabulary/layout.tsx +0 -247
  332. package/src/vocabulary/list.test.tsx +0 -222
  333. package/src/vocabulary/list.tsx +0 -50
  334. package/src/vocabulary/media.test.tsx +0 -85
  335. package/src/vocabulary/media.tsx +0 -56
  336. package/src/vocabulary/text.test.tsx +0 -102
  337. package/src/vocabulary/text.tsx +0 -60
  338. package/src/vocabulary/toTextContent.ts +0 -2
@@ -1,337 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import {
3
- classifyTemporal,
4
- formatTemporalInstant,
5
- formatTemporalUnixSeconds,
6
- fromLocalDateTime,
7
- fromOffsetDateTime,
8
- mergeTemporalMinutes,
9
- normalizeTemporalInputValue,
10
- splitTemporalMinutes,
11
- temporalOffsetLabel,
12
- toLocalDateTime,
13
- toPickerLocalDateTime,
14
- } from "./temporal";
15
-
16
- describe("temporal values", () => {
17
- it("splits instants in their own offset or another offset", () => {
18
- expect(splitTemporalMinutes("2025-12-15T17:00:00+02:00")).toEqual({
19
- date: "2025-12-15",
20
- time: "17:00",
21
- droppedPrecision: false,
22
- });
23
- expect(splitTemporalMinutes("2025-12-14T23:30:00Z", "+02:00")).toEqual({
24
- date: "2025-12-15",
25
- time: "01:30",
26
- droppedPrecision: false,
27
- });
28
- expect(temporalOffsetLabel("2025-12-15T17:00Z")).toBe("UTC");
29
- expect(temporalOffsetLabel("2025-12-15T17:00+02:00")).toBe("UTC+02:00");
30
- });
31
-
32
- it("merges a wall minute using the original offset and precision", () => {
33
- expect(
34
- fromOffsetDateTime(
35
- "2025-12-16T18:20",
36
- "2025-12-15T17:00:30.123456+02:00",
37
- ),
38
- ).toBe("2025-12-16T18:20:30.123456+02:00");
39
- expect(fromOffsetDateTime("2025-12-16T18:20", "2025-12-15T17:00:00Z")).toBe(
40
- "2025-12-16T18:20:00Z",
41
- );
42
- expect(fromOffsetDateTime("2025-12-16T18:20")).toBe("2025-12-16T18:20");
43
- expect(fromOffsetDateTime("", "2025-12-15T17:00Z")).toBe("");
44
- });
45
- it.each([
46
- ["17:00", { time: "17:00", droppedPrecision: false }],
47
- ["17:00:00", { time: "17:00", droppedPrecision: false }],
48
- ["17:00:30", { time: "17:00", droppedPrecision: true }],
49
- [
50
- "2025-12-15T17:00",
51
- { date: "2025-12-15", time: "17:00", droppedPrecision: false },
52
- ],
53
- [
54
- "2025-12-15T17:00:00.000",
55
- { date: "2025-12-15", time: "17:00", droppedPrecision: false },
56
- ],
57
- [
58
- "2025-12-15T17:00:00.001",
59
- { date: "2025-12-15", time: "17:00", droppedPrecision: true },
60
- ],
61
- [
62
- "2025-12-15T17:00Z",
63
- { date: "2025-12-15", time: "17:00", droppedPrecision: false },
64
- ],
65
- [
66
- "2025-12-15T17:00:30.001Z",
67
- { date: "2025-12-15", time: "17:00", droppedPrecision: true },
68
- ],
69
- [
70
- "2025-12-15T17:00:00.000Z",
71
- { date: "2025-12-15", time: "17:00", droppedPrecision: false },
72
- ],
73
- ["2025-12-15", undefined],
74
- ["invalid", undefined],
75
- ["", undefined],
76
- ])("splits %s into minute precision wall-clock parts", (value, expected) => {
77
- expect(splitTemporalMinutes(value as string)).toEqual(expected);
78
- });
79
-
80
- it.each([
81
- ["2025-12-15", "17:00", "2025-12-15T17:00"],
82
- ["2025-12-15", "17:00:30", "2025-12-15T17:00"],
83
- ["", "17:00", ""],
84
- ["2025-12-15", "", ""],
85
- ["2025-02-29", "17:00", ""],
86
- ["2025-12-15", "24:00", ""],
87
- ])("merges %j and %j without assigning a zone", (date, time, expected) => {
88
- expect(mergeTemporalMinutes(date!, time!)).toBe(expected);
89
- });
90
-
91
- it.each([
92
- [1765818000, "Z", "2025-12-15T17:00:00Z"],
93
- [1765810800, "+02:00", "2025-12-15T17:00:00+02:00"],
94
- [0, "-00:00", "1970-01-01T00:00:00-00:00"],
95
- [-1, "Z", "1969-12-31T23:59:59Z"],
96
- [0, "+24:00", undefined],
97
- [0.5, "Z", undefined],
98
- [Infinity, "Z", undefined],
99
- [NaN, "Z", undefined],
100
- [Number.MAX_SAFE_INTEGER, "Z", undefined],
101
- ])(
102
- "formats unix seconds %s with offset %s safely",
103
- (seconds, offset, expected) => {
104
- expect(
105
- formatTemporalUnixSeconds(seconds as number, offset as string),
106
- ).toBe(expected);
107
- },
108
- );
109
-
110
- it.each([
111
- ["12:00:00", "12:00"],
112
- ["12:00:00.000", "12:00"],
113
- ["12:00:05.1200", "12:00:05.12"],
114
- ["2025-12-15T12:00:00.000", "2025-12-15T12:00"],
115
- ["2025-12-15T12:00:05.1200", "2025-12-15T12:00:05.12"],
116
- ["12:00:05.123456", "12:00:05.123"],
117
- ["12:00:05.000456", "12:00:05"],
118
- ["2025-12-15T12:00:05.123456789", "2025-12-15T12:00:05.123"],
119
- ["2025-12-15", "2025-12-15"],
120
- ])("normalizes the browser input value %s", (value, expected) => {
121
- expect(normalizeTemporalInputValue(value!)).toBe(expected);
122
- });
123
-
124
- it("completes normalization of a 100000 digit zero fraction", () => {
125
- const zeros = "0".repeat(100_000);
126
- for (const value of [zeros, `12:00:00.${zeros}`, `12:00:00.${zeros}1`]) {
127
- expect(normalizeTemporalInputValue(value)).toBe(value);
128
- }
129
- expect(normalizeTemporalInputValue("12:00:00.000000000")).toBe("12:00");
130
- expect(normalizeTemporalInputValue("12:00:00.100000000")).toBe(
131
- "12:00:00.1",
132
- );
133
- });
134
-
135
- it("uses a fixed viewer time zone with seasonal offsets", () => {
136
- expect(Intl.DateTimeFormat().resolvedOptions().timeZone).toBe(
137
- "America/New_York",
138
- );
139
- expect(new Date("2026-01-15T12:00Z").getTimezoneOffset()).toBe(300);
140
- expect(new Date("2026-07-15T12:00Z").getTimezoneOffset()).toBe(240);
141
- });
142
-
143
- it.each(["0002", "0020", "0202"])(
144
- "preserves minute edits in %s when the zone offset has seconds",
145
- (year) => {
146
- const local = `${year}-12-15T12:00`;
147
- const value = fromLocalDateTime(local, "2025-12-15T17:00Z");
148
- expect(value).toBe(`${year}-12-15T16:56:02Z`);
149
- expect(toPickerLocalDateTime(value)).toBe(local);
150
- },
151
- );
152
-
153
- it("preserves entered seconds with a minute precision anchor", () => {
154
- const local = "2025-12-15T12:00:30";
155
- const value = fromLocalDateTime(local, "2025-12-15T17:00Z");
156
- expect(value).toBe("2025-12-15T17:00:30Z");
157
- expect(toPickerLocalDateTime(value)).toBe(local);
158
- });
159
-
160
- it.each([
161
- ["2026-07-15", "date"],
162
- ["2024-02-29", "date"],
163
- ["00:00", "time"],
164
- ["23:59:59", "time"],
165
- ["12:34:56.1", "time"],
166
- ["12:34:56.1200", "time"],
167
- ["2026-07-15T12:34", "floating"],
168
- ["2026-07-15T12:34:56", "floating"],
169
- ["2026-07-15T12:34:56.123456", "floating"],
170
- ])("classifies and passes through %s as %s", (value, kind) => {
171
- expect(classifyTemporal(value!)).toMatchObject({ kind, value });
172
- expect(toLocalDateTime(value!)).toBe(value);
173
- expect(fromLocalDateTime(value!, value!)).toBe(value);
174
- });
175
-
176
- describe.each([
177
- { offset: "Z", local: "2026-07-15T08:34" },
178
- { offset: "+08:00", local: "2026-07-15T00:34" },
179
- { offset: "-05:30", local: "2026-07-15T14:04" },
180
- ])("$offset instants", ({ offset, local }) => {
181
- it.each([
182
- { suffix: "", precision: "minutes" },
183
- { suffix: ":56", precision: "seconds" },
184
- { suffix: ":56.1", precision: 1 },
185
- { suffix: ":56.12", precision: 2 },
186
- { suffix: ":56.123", precision: 3 },
187
- { suffix: ":56.123456789", precision: 9 },
188
- { suffix: ":00.000009", precision: 6 },
189
- ])(
190
- "round trips local wall time at $precision precision",
191
- ({ suffix, precision }) => {
192
- const value = `2026-07-15T12:34${suffix}${offset}`;
193
- const parsed = classifyTemporal(value);
194
- expect(parsed).toMatchObject({
195
- kind: "instant",
196
- epochMs: Date.parse(value),
197
- offset,
198
- precision,
199
- });
200
- if (parsed.kind !== "instant") throw new Error("Expected an instant");
201
- expect(formatTemporalInstant(parsed)).toBe(value);
202
- const wallTime = toLocalDateTime(value, parsed.precision);
203
- expect(wallTime).toBe(`${local}${suffix}`);
204
- expect(fromLocalDateTime(wallTime, value)).toBe(value);
205
- },
206
- );
207
- });
208
-
209
- it("formats an instant in another offset and precision across calendar boundaries", () => {
210
- const parsed = classifyTemporal("2026-01-01T00:15:30.120Z");
211
- if (parsed.kind !== "instant") throw new Error("Expected an instant");
212
- expect(formatTemporalInstant(parsed, "-05:30", "minutes")).toBe(
213
- "2025-12-31T18:45-05:30",
214
- );
215
- expect(formatTemporalInstant(parsed, "+08:00", "seconds")).toBe(
216
- "2026-01-01T08:15:30+08:00",
217
- );
218
- expect(formatTemporalInstant(parsed, "Z", 6)).toBe(
219
- "2026-01-01T00:15:30.120000Z",
220
- );
221
- });
222
-
223
- it("preserves offset spelling and uses the edited fractional digits", () => {
224
- expect(
225
- fromLocalDateTime(
226
- "2026-07-16T10:20:30.123456789",
227
- "2026-07-15T12:34:56.123456789-00:00",
228
- ),
229
- ).toBe("2026-07-16T14:20:30.123456789-00:00");
230
- });
231
-
232
- it.each([
233
- ["2025-12-15T12:00:00.750", "2025-12-15T17:00:00.750Z", 3],
234
- ["2025-12-15T12:00:00.75", "2025-12-15T17:00:00.750Z", 2],
235
- ["2025-12-15T12:00:00", "2025-12-15T17:00:00.000Z", undefined],
236
- ["2025-12-15T12:00:00.250", "2025-12-15T17:00:00.250Z", 3],
237
- ["2025-12-15T12:00:00.750987", "2025-12-15T17:00:00.750987Z", 6],
238
- ])(
239
- "round trips a local fraction edit of %s",
240
- (local, expected, precision) => {
241
- const result = fromLocalDateTime(local!, "2025-12-15T17:00:00.250Z");
242
- expect(result).toBe(expected);
243
- expect(toLocalDateTime(result, precision)).toBe(local);
244
- },
245
- );
246
-
247
- it.each([
248
- [
249
- "2026-07-16T08:34:56",
250
- "2026-07-15T12:34:56.000456Z",
251
- "2026-07-16T12:34:56.000456Z",
252
- ],
253
- [
254
- "2026-07-16T08:34",
255
- "2026-07-15T12:34:00.000456Z",
256
- "2026-07-16T12:34:00.000456Z",
257
- ],
258
- ])(
259
- "keeps a sub-millisecond tail the control cannot show after an edit to %s",
260
- (local, anchor, expected) => {
261
- expect(fromLocalDateTime(local, anchor)).toBe(expected);
262
- },
263
- );
264
-
265
- it("uses zero for a native zero or omitted fraction", () => {
266
- expect(
267
- fromLocalDateTime("2025-12-15T12:00:00.000", "2025-12-15T17:00:00.250Z"),
268
- ).toBe("2025-12-15T17:00:00.000Z");
269
- expect(
270
- fromLocalDateTime("2025-12-15T12:00:00", "2025-12-15T17:00:00.250123Z"),
271
- ).toBe("2025-12-15T17:00:00.000000Z");
272
- });
273
-
274
- it.each([
275
- ["2026-01-15T12:34", "2026-01-15T12:34:00-05:00"],
276
- ["2026-07-15T12:34", "2026-07-15T12:34:00-04:00"],
277
- ])("uses the viewer offset at %s for an empty value", (value, expected) => {
278
- expect(fromLocalDateTime(value!, "")).toBe(expected);
279
- expect(fromLocalDateTime(value!)).toBe(expected);
280
- });
281
-
282
- it.each([
283
- {
284
- value: "2026-03-08T02:30:45",
285
- parts: [2026, 2, 8, 2, 30, 45] as const,
286
- expected: "2026-03-08T07:30:45.000000Z",
287
- local: "2026-03-08T03:30:45",
288
- },
289
- {
290
- value: "2026-11-01T01:30:45",
291
- parts: [2026, 10, 1, 1, 30, 45] as const,
292
- expected: "2026-11-01T05:30:45.000000Z",
293
- local: "2026-11-01T01:30:45",
294
- },
295
- ])(
296
- "resolves DST wall time $value like the native constructor",
297
- ({ value, parts, expected, local }) => {
298
- const result = fromLocalDateTime(value, "2026-01-15T12:00:00.123456Z");
299
- expect(result).toBe(expected);
300
- const [year, month, day, hour, minute, second] = parts;
301
- expect(Date.parse(result)).toBe(
302
- new Date(year, month, day, hour, minute, second).getTime(),
303
- );
304
- expect(toLocalDateTime(result)).toBe(local);
305
- expect(fromLocalDateTime(value, value)).toBe(value);
306
- },
307
- );
308
-
309
- it("preserves an empty input when an instant is cleared", () => {
310
- expect(fromLocalDateTime("", "2026-07-15T12:34:56+08:00")).toBe("");
311
- });
312
-
313
- it.each([
314
- "",
315
- "not a date",
316
- "2026-02-29",
317
- "2026-04-31",
318
- "2026-13-01",
319
- "2026-01-00",
320
- "24:00",
321
- "12:60",
322
- "12:34:60",
323
- "2026-07-15T24:00Z",
324
- "2026-07-15T12:60Z",
325
- "2026-07-15T12:34:60Z",
326
- "2026-02-29T12:34Z",
327
- "2026-07-15T12:34.1Z",
328
- "2026-07-15T12:34+24:00",
329
- "2026-07-15T12:34+08:60",
330
- "2026-07-15T12:34+0800",
331
- "2026-07-15 12:34Z",
332
- ])("classifies %j as unparseable and passes it through", (value) => {
333
- expect(classifyTemporal(value)).toEqual({ kind: "unparseable", value });
334
- expect(toLocalDateTime(value)).toBe(value);
335
- expect(fromLocalDateTime(value)).toBe(value);
336
- });
337
- });
package/src/temporal.ts DELETED
@@ -1,263 +0,0 @@
1
- export type TemporalPrecision = "minutes" | "seconds" | number;
2
-
3
- export type TemporalInstant = {
4
- kind: "instant";
5
- epochMs: number;
6
- offset: string;
7
- precision: TemporalPrecision;
8
- subMillisecondDigits?: string;
9
- };
10
-
11
- export type TemporalValue =
12
- | { kind: "date" | "unparseable"; value: string }
13
- | {
14
- kind: "time" | "floating";
15
- value: string;
16
- precision: TemporalPrecision;
17
- }
18
- | TemporalInstant;
19
-
20
- const DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
21
- const TIME_PATTERN =
22
- /^([01]\d|2[0-3]):([0-5]\d)(?::([0-5]\d)(?:\.(\d{1,9}))?)?$/;
23
- const DATETIME_PATTERN =
24
- /^(\d{4}-\d{2}-\d{2})T([01]\d|2[0-3]):([0-5]\d)(?::([0-5]\d)(?:\.(\d+))?)?(Z|[+-](?:[01]\d|2[0-3]):[0-5]\d)?$/;
25
- const INPUT_PATTERN =
26
- /^((?:\d{4}-\d{2}-\d{2}T)?(?:[01]\d|2[0-3]):[0-5]\d)(?::([0-5]\d)(?:\.(\d{1,9}))?)?$/;
27
-
28
- export const normalizeTemporalInputValue = (value: string): string => {
29
- const match = INPUT_PATTERN.exec(value);
30
- if (!match || match[2] === undefined) return value;
31
- const digits = match[3]?.slice(0, 3) ?? "";
32
- let end = digits.length;
33
- while (end > 0 && digits[end - 1] === "0") end--;
34
- const fraction = digits.slice(0, end);
35
- if (match[2] === "00" && !fraction) return match[1]!;
36
- return `${match[1]}:${match[2]}${fraction ? `.${fraction}` : ""}`;
37
- };
38
-
39
- const isCalendarDate = (value: string): boolean => {
40
- const date = new Date(`${value}T00:00:00Z`);
41
- return (
42
- Number.isFinite(date.getTime()) && date.toISOString().slice(0, 10) === value
43
- );
44
- };
45
-
46
- export const classifyTemporal = (value: string): TemporalValue => {
47
- if (DATE_PATTERN.test(value) && isCalendarDate(value)) {
48
- return { kind: "date", value };
49
- }
50
- const time = TIME_PATTERN.exec(value);
51
- if (time) {
52
- return {
53
- kind: "time",
54
- value,
55
- precision:
56
- time[4]?.length ?? (time[3] === undefined ? "minutes" : "seconds"),
57
- };
58
- }
59
- const datetime = DATETIME_PATTERN.exec(value);
60
- if (!datetime || !isCalendarDate(datetime[1]!)) {
61
- return { kind: "unparseable", value };
62
- }
63
- const precision =
64
- datetime[5]?.length ?? (datetime[4] === undefined ? "minutes" : "seconds");
65
- const offset = datetime[6];
66
- if (offset === undefined) return { kind: "floating", value, precision };
67
- return {
68
- kind: "instant",
69
- epochMs: Date.parse(value),
70
- offset,
71
- precision,
72
- ...(datetime[5] && datetime[5].length > 3
73
- ? { subMillisecondDigits: datetime[5].slice(3) }
74
- : {}),
75
- };
76
- };
77
-
78
- export const getTemporalInputStep = (
79
- ...values: (string | undefined)[]
80
- ): "any" | 1 | undefined => {
81
- let step: 1 | undefined;
82
- for (const value of values) {
83
- const temporal = classifyTemporal(value ?? "");
84
- const precision =
85
- "precision" in temporal
86
- ? temporal.precision
87
- : INPUT_PATTERN.exec(value ?? "")?.[3]?.length;
88
- if (typeof precision === "number") return "any";
89
- if (precision === "seconds") step = 1;
90
- }
91
- return step;
92
- };
93
-
94
- export const splitTemporalMinutes = (
95
- value: string,
96
- offset?: string,
97
- ): { date?: string; time: string; droppedPrecision: boolean } | undefined => {
98
- const temporal = classifyTemporal(value);
99
- if (temporal.kind === "date" || temporal.kind === "unparseable")
100
- return undefined;
101
- const wallTime =
102
- temporal.kind === "instant"
103
- ? formatTemporalInstant(temporal, offset).slice(
104
- 0,
105
- -(offset ?? temporal.offset).length,
106
- )
107
- : temporal.value;
108
- const end = temporal.kind === "time" ? 5 : 16;
109
- return {
110
- ...(temporal.kind === "time" ? {} : { date: wallTime.slice(0, 10) }),
111
- time: wallTime.slice(end - 5, end),
112
- droppedPrecision: /[1-9]/.test(wallTime.slice(end)),
113
- };
114
- };
115
-
116
- export const mergeTemporalMinutes = (date: string, time: string): string => {
117
- if (
118
- classifyTemporal(date).kind !== "date" ||
119
- classifyTemporal(time).kind !== "time"
120
- )
121
- return "";
122
- return `${date}T${time.slice(0, 5)}`;
123
- };
124
-
125
- const pad = (value: number, digits = 2): string =>
126
- String(value).padStart(digits, "0");
127
-
128
- const offsetMinutes = (offset: string): number =>
129
- offset === "Z"
130
- ? 0
131
- : (offset.startsWith("-") ? -1 : 1) *
132
- (Number(offset.slice(1, 3)) * 60 + Number(offset.slice(4, 6)));
133
-
134
- export const formatTemporalInstant = (
135
- instant: TemporalInstant,
136
- offset = instant.offset,
137
- precision = instant.precision,
138
- ): string => {
139
- const date = new Date(instant.epochMs + offsetMinutes(offset) * 60_000);
140
- const iso = date.toISOString();
141
- const wallTime =
142
- precision === "minutes"
143
- ? iso.slice(0, 16)
144
- : precision === "seconds"
145
- ? iso.slice(0, 19)
146
- : `${iso.slice(0, 19)}.${(pad(date.getUTCMilliseconds(), 3) + (instant.subMillisecondDigits ?? "")).padEnd(precision, "0").slice(0, precision)}`;
147
- return `${wallTime}${offset}`;
148
- };
149
-
150
- export const formatTemporalUnixSeconds = (
151
- seconds: number,
152
- offset = "Z",
153
- ): string | undefined => {
154
- if (!Number.isInteger(seconds)) return undefined;
155
- try {
156
- const value = formatTemporalInstant({
157
- kind: "instant",
158
- epochMs: seconds * 1000,
159
- offset,
160
- precision: "seconds",
161
- });
162
- return classifyTemporal(value).kind === "instant" ? value : undefined;
163
- } catch {
164
- return undefined;
165
- }
166
- };
167
-
168
- export const temporalOffsetLabel = (value: string): string | undefined => {
169
- const temporal = classifyTemporal(value);
170
- if (temporal.kind !== "instant") return undefined;
171
- return temporal.offset === "Z" ? "UTC" : `UTC${temporal.offset}`;
172
- };
173
-
174
- export const fromOffsetDateTime = (
175
- value: string,
176
- previousValue?: string,
177
- ): string => {
178
- if (value === "") return "";
179
- if (classifyTemporal(value).kind !== "floating") return value;
180
- const previous = classifyTemporal(previousValue ?? "");
181
- if (previous.kind !== "instant") return value;
182
- const epochMs = Date.parse(`${value}${previous.offset}`);
183
- const minuteRemainder =
184
- previous.precision === "minutes"
185
- ? 0
186
- : ((previous.epochMs % 60_000) + 60_000) % 60_000;
187
- return formatTemporalInstant({
188
- ...previous,
189
- epochMs: epochMs + minuteRemainder,
190
- });
191
- };
192
-
193
- export const toLocalDateTime = (
194
- value: string,
195
- precision?: TemporalPrecision,
196
- ): string => {
197
- const temporal = classifyTemporal(value);
198
- if (temporal.kind !== "instant") return value;
199
- const date = new Date(temporal.epochMs);
200
- const wallTime = `${pad(date.getFullYear(), 4)}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`;
201
- return (precision ?? temporal.precision) === "minutes"
202
- ? wallTime
203
- : `${wallTime}:${pad(date.getSeconds())}${typeof precision === "number" ? formatTemporalInstant(temporal, "Z", precision).slice(19, -1) : ""}`;
204
- };
205
-
206
- export const toPickerLocalDateTime = (value: string): string => {
207
- const temporal = classifyTemporal(value);
208
- if (temporal.kind !== "instant") return normalizeTemporalInputValue(value);
209
- const local = toLocalDateTime(
210
- value,
211
- typeof temporal.precision === "number" ? 3 : temporal.precision,
212
- );
213
- return typeof temporal.precision !== "number" || local.endsWith(".000")
214
- ? normalizeTemporalInputValue(local)
215
- : local;
216
- };
217
-
218
- export const fromLocalDateTime = (
219
- value: string,
220
- previousValue?: string,
221
- ): string => {
222
- const temporal = classifyTemporal(value);
223
- const previous = classifyTemporal(previousValue ?? "");
224
- if (temporal.kind !== "floating" || previous.kind === "floating") {
225
- return value;
226
- }
227
- const date = new Date(
228
- value.replace(
229
- /\.(\d{1,2})$/,
230
- (_, digits: string) => `.${digits.padEnd(3, "0")}`,
231
- ),
232
- );
233
- const minutes = -date.getTimezoneOffset();
234
- const offset = `${minutes < 0 ? "-" : "+"}${pad(Math.floor(Math.abs(minutes) / 60))}:${pad(Math.abs(minutes) % 60)}`;
235
- if (previous.kind !== "instant") {
236
- return formatTemporalInstant({
237
- kind: "instant",
238
- epochMs: date.getTime(),
239
- offset,
240
- precision: "seconds",
241
- });
242
- }
243
- return formatTemporalInstant({
244
- ...previous,
245
- epochMs: date.getTime(),
246
- ...(previous.precision === "minutes" &&
247
- (temporal.precision === "seconds" || date.getUTCSeconds() !== 0)
248
- ? { precision: "seconds" }
249
- : {}),
250
- ...(date.getMilliseconds() ===
251
- new Date(previous.epochMs).getUTCMilliseconds()
252
- ? {}
253
- : typeof temporal.precision === "number"
254
- ? {
255
- precision: Math.max(
256
- temporal.precision,
257
- typeof previous.precision === "number" ? previous.precision : 0,
258
- ),
259
- subMillisecondDigits: value.slice(value.indexOf(".") + 4),
260
- }
261
- : { subMillisecondDigits: "" }),
262
- });
263
- };