@happyvertical/smrt-ui 0.51.39 → 0.53.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 (392) hide show
  1. package/AGENTS.md +25 -3
  2. package/README.md +558 -8
  3. package/dist/actions/__tests__/swipe-dismiss.test.js +69 -0
  4. package/dist/actions/swipe-dismiss.d.ts +42 -0
  5. package/dist/actions/swipe-dismiss.d.ts.map +1 -0
  6. package/dist/actions/swipe-dismiss.js +69 -0
  7. package/dist/components/calendar/Calendar.svelte +5 -0
  8. package/dist/components/calendar/Calendar.svelte.d.ts.map +1 -1
  9. package/dist/components/calendar/CalendarView.svelte +1250 -0
  10. package/dist/components/calendar/CalendarView.svelte.d.ts +66 -0
  11. package/dist/components/calendar/CalendarView.svelte.d.ts.map +1 -0
  12. package/dist/components/calendar/DayView.svelte +5 -0
  13. package/dist/components/calendar/DayView.svelte.d.ts.map +1 -1
  14. package/dist/components/calendar/__tests__/CalendarView.test.js +226 -0
  15. package/dist/components/calendar/__tests__/calendar-model.test.js +138 -0
  16. package/dist/components/calendar/calendar-model.d.ts +178 -0
  17. package/dist/components/calendar/calendar-model.d.ts.map +1 -0
  18. package/dist/components/calendar/calendar-model.js +362 -0
  19. package/dist/components/calendar/index.d.ts +12 -1
  20. package/dist/components/calendar/index.d.ts.map +1 -1
  21. package/dist/components/calendar/index.js +11 -1
  22. package/dist/components/data/CollectionList.svelte +23 -5
  23. package/dist/components/data/CollectionList.svelte.d.ts +8 -2
  24. package/dist/components/data/CollectionList.svelte.d.ts.map +1 -1
  25. package/dist/components/data/DataTable.svelte +178 -55
  26. package/dist/components/data/DataTable.svelte.d.ts.map +1 -1
  27. package/dist/components/data/DataTableController.d.ts +4 -0
  28. package/dist/components/data/DataTableController.d.ts.map +1 -1
  29. package/dist/components/data/DataTableController.js +11 -4
  30. package/dist/components/data/DataTableResponsive.d.ts +4 -0
  31. package/dist/components/data/DataTableResponsive.d.ts.map +1 -0
  32. package/dist/components/data/DataTableResponsive.js +22 -0
  33. package/dist/components/data/ListSortSelect.svelte +121 -0
  34. package/dist/components/data/ListSortSelect.svelte.d.ts +22 -0
  35. package/dist/components/data/ListSortSelect.svelte.d.ts.map +1 -0
  36. package/dist/components/data/SortableHeader.svelte +169 -0
  37. package/dist/components/data/SortableHeader.svelte.d.ts +30 -0
  38. package/dist/components/data/SortableHeader.svelte.d.ts.map +1 -0
  39. package/dist/components/data/__tests__/CollectionList.test.js +37 -0
  40. package/dist/components/data/__tests__/DataTable.test.js +76 -0
  41. package/dist/components/data/__tests__/DataTableController.test.js +35 -0
  42. package/dist/components/data/__tests__/DataTableResponsive.test.js +80 -0
  43. package/dist/components/data/__tests__/SortableHeader.test.js +101 -0
  44. package/dist/components/data/__tests__/link-step-surface.test.js +172 -0
  45. package/dist/components/data/__tests__/list-sort-state.svelte.test.js +71 -0
  46. package/dist/components/data/__tests__/list-sort.test.js +91 -0
  47. package/dist/components/data/__tests__/responsive-table.fixture.svelte +29 -0
  48. package/dist/components/data/__tests__/responsive-table.fixture.svelte.d.ts +7 -0
  49. package/dist/components/data/__tests__/responsive-table.fixture.svelte.d.ts.map +1 -0
  50. package/dist/components/data/__tests__/surface-navigation.test.js +121 -0
  51. package/dist/components/data/data-surface.d.ts +1 -0
  52. package/dist/components/data/data-surface.d.ts.map +1 -1
  53. package/dist/components/data/data-surface.js +1 -0
  54. package/dist/components/data/index.d.ts +6 -0
  55. package/dist/components/data/index.d.ts.map +1 -1
  56. package/dist/components/data/index.js +6 -0
  57. package/dist/components/data/link-surface.d.ts +79 -0
  58. package/dist/components/data/link-surface.d.ts.map +1 -0
  59. package/dist/components/data/link-surface.js +155 -0
  60. package/dist/components/data/list-sort-state.svelte.d.ts +39 -0
  61. package/dist/components/data/list-sort-state.svelte.d.ts.map +1 -0
  62. package/dist/components/data/list-sort-state.svelte.js +47 -0
  63. package/dist/components/data/list-sort.d.ts +94 -0
  64. package/dist/components/data/list-sort.d.ts.map +1 -0
  65. package/dist/components/data/list-sort.js +182 -0
  66. package/dist/components/data/step-surface.d.ts +73 -0
  67. package/dist/components/data/step-surface.d.ts.map +1 -0
  68. package/dist/components/data/step-surface.js +177 -0
  69. package/dist/components/data/surface-navigation.d.ts +49 -0
  70. package/dist/components/data/surface-navigation.d.ts.map +1 -0
  71. package/dist/components/data/surface-navigation.js +100 -0
  72. package/dist/components/data/types.d.ts +27 -0
  73. package/dist/components/data/types.d.ts.map +1 -1
  74. package/dist/components/display/DateDisplay.svelte +76 -14
  75. package/dist/components/display/DateDisplay.svelte.d.ts +2 -0
  76. package/dist/components/display/DateDisplay.svelte.d.ts.map +1 -1
  77. package/dist/components/display/Icon.svelte +3 -16
  78. package/dist/components/display/Icon.svelte.d.ts +0 -9
  79. package/dist/components/display/Icon.svelte.d.ts.map +1 -1
  80. package/dist/components/display/StatusBadge.svelte +29 -2
  81. package/dist/components/display/StatusBadge.svelte.d.ts +4 -2
  82. package/dist/components/display/StatusBadge.svelte.d.ts.map +1 -1
  83. package/dist/components/display/__tests__/DateDisplay.test.js +82 -1
  84. package/dist/components/display/__tests__/Icon.test.js +132 -2
  85. package/dist/components/display/__tests__/StatusBadge.test.js +78 -0
  86. package/dist/components/display/icons.svelte.d.ts +11 -0
  87. package/dist/components/display/icons.svelte.d.ts.map +1 -0
  88. package/dist/components/display/icons.svelte.js +60 -0
  89. package/dist/components/display/index.d.ts +3 -1
  90. package/dist/components/display/index.d.ts.map +1 -1
  91. package/dist/components/display/index.js +1 -0
  92. package/dist/components/display/types.d.ts +2 -0
  93. package/dist/components/display/types.d.ts.map +1 -1
  94. package/dist/components/feedback/ConfirmDialog.svelte +59 -48
  95. package/dist/components/feedback/ConfirmDialog.svelte.d.ts +14 -2
  96. package/dist/components/feedback/ConfirmDialog.svelte.d.ts.map +1 -1
  97. package/dist/components/feedback/PhoneSheet.svelte +224 -0
  98. package/dist/components/feedback/PhoneSheet.svelte.d.ts +35 -0
  99. package/dist/components/feedback/PhoneSheet.svelte.d.ts.map +1 -0
  100. package/dist/components/feedback/ToastViewport.svelte +156 -8
  101. package/dist/components/feedback/ToastViewport.svelte.d.ts +5 -1
  102. package/dist/components/feedback/ToastViewport.svelte.d.ts.map +1 -1
  103. package/dist/components/feedback/WorkingStrip.svelte +426 -0
  104. package/dist/components/feedback/WorkingStrip.svelte.d.ts +37 -0
  105. package/dist/components/feedback/WorkingStrip.svelte.d.ts.map +1 -0
  106. package/dist/components/feedback/__tests__/ConfirmDialog.test.js +38 -20
  107. package/dist/components/feedback/__tests__/PhoneSheet.test.js +101 -0
  108. package/dist/components/feedback/__tests__/WorkingStrip.test.js +172 -0
  109. package/dist/components/feedback/__tests__/confirm-message.fixture.svelte +9 -0
  110. package/dist/components/feedback/__tests__/confirm-message.fixture.svelte.d.ts +19 -0
  111. package/dist/components/feedback/__tests__/confirm-message.fixture.svelte.d.ts.map +1 -0
  112. package/dist/components/feedback/__tests__/toast.test.js +142 -1
  113. package/dist/components/feedback/index.d.ts +5 -1
  114. package/dist/components/feedback/index.d.ts.map +1 -1
  115. package/dist/components/feedback/index.js +5 -0
  116. package/dist/components/feedback/toast.d.ts +13 -1
  117. package/dist/components/feedback/toast.d.ts.map +1 -1
  118. package/dist/components/feedback/toast.js +51 -10
  119. package/dist/components/feedback/working-status.d.ts +34 -0
  120. package/dist/components/feedback/working-status.d.ts.map +1 -0
  121. package/dist/components/feedback/working-status.js +5 -0
  122. package/dist/components/forms/CameraCapture.svelte +619 -0
  123. package/dist/components/forms/CameraCapture.svelte.d.ts +42 -0
  124. package/dist/components/forms/CameraCapture.svelte.d.ts.map +1 -0
  125. package/dist/components/forms/Checkbox.svelte +34 -3
  126. package/dist/components/forms/Checkbox.svelte.d.ts +2 -0
  127. package/dist/components/forms/Checkbox.svelte.d.ts.map +1 -1
  128. package/dist/components/forms/Combobox.svelte +108 -12
  129. package/dist/components/forms/Combobox.svelte.d.ts +6 -0
  130. package/dist/components/forms/Combobox.svelte.d.ts.map +1 -1
  131. package/dist/components/forms/DictationButton.svelte +128 -0
  132. package/dist/components/forms/DictationButton.svelte.d.ts +19 -0
  133. package/dist/components/forms/DictationButton.svelte.d.ts.map +1 -0
  134. package/dist/components/forms/DictationStatus.svelte +164 -0
  135. package/dist/components/forms/DictationStatus.svelte.d.ts +11 -0
  136. package/dist/components/forms/DictationStatus.svelte.d.ts.map +1 -0
  137. package/dist/components/forms/Fieldset.svelte +5 -1
  138. package/dist/components/forms/Fieldset.svelte.d.ts +2 -0
  139. package/dist/components/forms/Fieldset.svelte.d.ts.map +1 -1
  140. package/dist/components/forms/Form.svelte +5 -0
  141. package/dist/components/forms/Form.svelte.d.ts +1 -0
  142. package/dist/components/forms/Form.svelte.d.ts.map +1 -1
  143. package/dist/components/forms/FormActionBar.svelte +84 -0
  144. package/dist/components/forms/FormActionBar.svelte.d.ts +23 -0
  145. package/dist/components/forms/FormActionBar.svelte.d.ts.map +1 -0
  146. package/dist/components/forms/Input.svelte +17 -1
  147. package/dist/components/forms/Input.svelte.d.ts +2 -0
  148. package/dist/components/forms/Input.svelte.d.ts.map +1 -1
  149. package/dist/components/forms/Listbox.svelte +39 -1
  150. package/dist/components/forms/Listbox.svelte.d.ts.map +1 -1
  151. package/dist/components/forms/MultiSelect.svelte +41 -1
  152. package/dist/components/forms/MultiSelect.svelte.d.ts.map +1 -1
  153. package/dist/components/forms/Radio.svelte +16 -4
  154. package/dist/components/forms/RadioGroup.svelte +7 -1
  155. package/dist/components/forms/RadioGroup.svelte.d.ts +2 -0
  156. package/dist/components/forms/RadioGroup.svelte.d.ts.map +1 -1
  157. package/dist/components/forms/SearchInput.svelte +207 -0
  158. package/dist/components/forms/SearchInput.svelte.d.ts +22 -0
  159. package/dist/components/forms/SearchInput.svelte.d.ts.map +1 -0
  160. package/dist/components/forms/SegmentedControl.svelte +152 -11
  161. package/dist/components/forms/SegmentedControl.svelte.d.ts +4 -0
  162. package/dist/components/forms/SegmentedControl.svelte.d.ts.map +1 -1
  163. package/dist/components/forms/Select.svelte +17 -1
  164. package/dist/components/forms/Select.svelte.d.ts +2 -0
  165. package/dist/components/forms/Select.svelte.d.ts.map +1 -1
  166. package/dist/components/forms/SignaturePad.svelte +436 -0
  167. package/dist/components/forms/SignaturePad.svelte.d.ts +32 -0
  168. package/dist/components/forms/SignaturePad.svelte.d.ts.map +1 -0
  169. package/dist/components/forms/StagedControlReview.svelte +21 -12
  170. package/dist/components/forms/StagedControlReview.svelte.d.ts.map +1 -1
  171. package/dist/components/forms/Switch.svelte +18 -5
  172. package/dist/components/forms/Switch.svelte.d.ts +2 -0
  173. package/dist/components/forms/Switch.svelte.d.ts.map +1 -1
  174. package/dist/components/forms/TagsInput.svelte +8 -1
  175. package/dist/components/forms/Textarea.svelte +16 -1
  176. package/dist/components/forms/Textarea.svelte.d.ts +2 -0
  177. package/dist/components/forms/Textarea.svelte.d.ts.map +1 -1
  178. package/dist/components/forms/__tests__/CameraCapture.test.js +504 -0
  179. package/dist/components/forms/__tests__/Combobox.test.js +354 -0
  180. package/dist/components/forms/__tests__/ControlHitArea.test.js +73 -0
  181. package/dist/components/forms/__tests__/DictationButton.test.js +176 -0
  182. package/dist/components/forms/__tests__/Fieldset.test.js +37 -0
  183. package/dist/components/forms/__tests__/Form.test.js +46 -0
  184. package/dist/components/forms/__tests__/FormActionBar.test.js +22 -0
  185. package/dist/components/forms/__tests__/Listbox.test.js +303 -0
  186. package/dist/components/forms/__tests__/MultiSelect.test.js +281 -0
  187. package/dist/components/forms/__tests__/SearchInput.test.js +92 -0
  188. package/dist/components/forms/__tests__/SignaturePad.test.js +338 -0
  189. package/dist/components/forms/__tests__/StagedControlReview.test.js +37 -3
  190. package/dist/components/forms/__tests__/camera-capture-session.test.js +207 -0
  191. package/dist/components/forms/__tests__/capture-form-retry.test.js +166 -0
  192. package/dist/components/forms/__tests__/capture-form.fixture.svelte +38 -0
  193. package/dist/components/forms/__tests__/capture-form.fixture.svelte.d.ts +10 -0
  194. package/dist/components/forms/__tests__/capture-form.fixture.svelte.d.ts.map +1 -0
  195. package/dist/components/forms/__tests__/capture-test-env.d.ts +70 -0
  196. package/dist/components/forms/__tests__/capture-test-env.d.ts.map +1 -0
  197. package/dist/components/forms/__tests__/capture-test-env.js +122 -0
  198. package/dist/components/forms/__tests__/capture-touch.fixture.svelte +15 -0
  199. package/dist/components/forms/__tests__/capture-touch.fixture.svelte.d.ts +8 -0
  200. package/dist/components/forms/__tests__/capture-touch.fixture.svelte.d.ts.map +1 -0
  201. package/dist/components/forms/__tests__/combobox-form.fixture.svelte +29 -0
  202. package/dist/components/forms/__tests__/combobox-form.fixture.svelte.d.ts +14 -0
  203. package/dist/components/forms/__tests__/combobox-form.fixture.svelte.d.ts.map +1 -0
  204. package/dist/components/forms/__tests__/combobox-labels.fixture.svelte +19 -0
  205. package/dist/components/forms/__tests__/combobox-labels.fixture.svelte.d.ts +11 -0
  206. package/dist/components/forms/__tests__/combobox-labels.fixture.svelte.d.ts.map +1 -0
  207. package/dist/components/forms/__tests__/composite-registration.fixture.svelte +29 -0
  208. package/dist/components/forms/__tests__/composite-registration.fixture.svelte.d.ts +8 -0
  209. package/dist/components/forms/__tests__/composite-registration.fixture.svelte.d.ts.map +1 -0
  210. package/dist/components/forms/__tests__/control-interaction.test.js +19 -1
  211. package/dist/components/forms/__tests__/control-proposals.test.js +63 -0
  212. package/dist/components/forms/__tests__/dictation-recording.test.js +582 -0
  213. package/dist/components/forms/__tests__/dictation.test.js +486 -0
  214. package/dist/components/forms/__tests__/form-attachment.fixture.svelte +28 -0
  215. package/dist/components/forms/__tests__/form-attachment.fixture.svelte.d.ts +11 -0
  216. package/dist/components/forms/__tests__/form-attachment.fixture.svelte.d.ts.map +1 -0
  217. package/dist/components/forms/__tests__/form-retry-form.fixture.svelte +31 -0
  218. package/dist/components/forms/__tests__/form-retry-form.fixture.svelte.d.ts +13 -0
  219. package/dist/components/forms/__tests__/form-retry-form.fixture.svelte.d.ts.map +1 -0
  220. package/dist/components/forms/__tests__/form-retry-form.test.js +107 -0
  221. package/dist/components/forms/__tests__/hidden-controls-form-retry.test.js +69 -0
  222. package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte +29 -0
  223. package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte.d.ts +9 -0
  224. package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte.d.ts.map +1 -0
  225. package/dist/components/forms/__tests__/listbox-form.fixture.svelte +27 -0
  226. package/dist/components/forms/__tests__/listbox-form.fixture.svelte.d.ts +13 -0
  227. package/dist/components/forms/__tests__/listbox-form.fixture.svelte.d.ts.map +1 -0
  228. package/dist/components/forms/__tests__/long-press.test.js +175 -0
  229. package/dist/components/forms/__tests__/multiselect-form.fixture.svelte +27 -0
  230. package/dist/components/forms/__tests__/multiselect-form.fixture.svelte.d.ts +13 -0
  231. package/dist/components/forms/__tests__/multiselect-form.fixture.svelte.d.ts.map +1 -0
  232. package/dist/components/forms/__tests__/native-file-field.test.js +177 -0
  233. package/dist/components/forms/__tests__/place-picker.fixture.svelte +33 -0
  234. package/dist/components/forms/__tests__/place-picker.fixture.svelte.d.ts +12 -0
  235. package/dist/components/forms/__tests__/place-picker.fixture.svelte.d.ts.map +1 -0
  236. package/dist/components/forms/__tests__/segmented-posting.fixture.svelte +28 -0
  237. package/dist/components/forms/__tests__/segmented-posting.fixture.svelte.d.ts +12 -0
  238. package/dist/components/forms/__tests__/segmented-posting.fixture.svelte.d.ts.map +1 -0
  239. package/dist/components/forms/__tests__/segmented-posting.test.js +92 -0
  240. package/dist/components/forms/__tests__/signature-pad-logic.test.js +54 -0
  241. package/dist/components/forms/__tests__/touch-density.fixture.svelte +34 -0
  242. package/dist/components/forms/__tests__/touch-density.fixture.svelte.d.ts +8 -0
  243. package/dist/components/forms/__tests__/touch-density.fixture.svelte.d.ts.map +1 -0
  244. package/dist/components/forms/__tests__/touch-density.test.js +66 -0
  245. package/dist/components/forms/audio-capture.d.ts +69 -0
  246. package/dist/components/forms/audio-capture.d.ts.map +1 -0
  247. package/dist/components/forms/audio-capture.js +193 -0
  248. package/dist/components/forms/camera-capture-session.d.ts +81 -0
  249. package/dist/components/forms/camera-capture-session.d.ts.map +1 -0
  250. package/dist/components/forms/camera-capture-session.js +130 -0
  251. package/dist/components/forms/control-interaction.d.ts +6 -0
  252. package/dist/components/forms/control-interaction.d.ts.map +1 -1
  253. package/dist/components/forms/control-interaction.js +12 -1
  254. package/dist/components/forms/control-proposals.d.ts +55 -0
  255. package/dist/components/forms/control-proposals.d.ts.map +1 -0
  256. package/dist/components/forms/control-proposals.js +188 -0
  257. package/dist/components/forms/dictation-transcribe.d.ts +36 -0
  258. package/dist/components/forms/dictation-transcribe.d.ts.map +1 -0
  259. package/dist/components/forms/dictation-transcribe.js +65 -0
  260. package/dist/components/forms/dictation.svelte.d.ts +200 -0
  261. package/dist/components/forms/dictation.svelte.d.ts.map +1 -0
  262. package/dist/components/forms/dictation.svelte.js +686 -0
  263. package/dist/components/forms/form-retry/__tests__/boundaries.test.js +23 -0
  264. package/dist/components/forms/form-retry/__tests__/content-digest.test.js +70 -0
  265. package/dist/components/forms/form-retry/__tests__/controller.test.js +802 -0
  266. package/dist/components/forms/form-retry/__tests__/fake-kit.d.ts +82 -0
  267. package/dist/components/forms/form-retry/__tests__/fake-kit.d.ts.map +1 -0
  268. package/dist/components/forms/form-retry/__tests__/fake-kit.js +180 -0
  269. package/dist/components/forms/form-retry/__tests__/form-draft.test.js +169 -0
  270. package/dist/components/forms/form-retry/__tests__/ssr.test.js +37 -0
  271. package/dist/components/forms/form-retry/__tests__/storage.d.ts +26 -0
  272. package/dist/components/forms/form-retry/__tests__/storage.d.ts.map +1 -0
  273. package/dist/components/forms/form-retry/__tests__/storage.js +63 -0
  274. package/dist/components/forms/form-retry/__tests__/submission-key.test.js +236 -0
  275. package/dist/components/forms/form-retry/__tests__/submitted-draft.test.js +334 -0
  276. package/dist/components/forms/form-retry/content-digest.d.ts +54 -0
  277. package/dist/components/forms/form-retry/content-digest.d.ts.map +1 -0
  278. package/dist/components/forms/form-retry/content-digest.js +169 -0
  279. package/dist/components/forms/form-retry/controller.d.ts +214 -0
  280. package/dist/components/forms/form-retry/controller.d.ts.map +1 -0
  281. package/dist/components/forms/form-retry/controller.js +404 -0
  282. package/dist/components/forms/form-retry/form-draft.d.ts +68 -0
  283. package/dist/components/forms/form-retry/form-draft.d.ts.map +1 -0
  284. package/dist/components/forms/form-retry/form-draft.js +161 -0
  285. package/dist/components/forms/form-retry/index.d.ts +19 -0
  286. package/dist/components/forms/form-retry/index.d.ts.map +1 -0
  287. package/dist/components/forms/form-retry/index.js +16 -0
  288. package/dist/components/forms/form-retry/submission-key.d.ts +244 -0
  289. package/dist/components/forms/form-retry/submission-key.d.ts.map +1 -0
  290. package/dist/components/forms/form-retry/submission-key.js +342 -0
  291. package/dist/components/forms/form-retry/submitted-draft.d.ts +112 -0
  292. package/dist/components/forms/form-retry/submitted-draft.d.ts.map +1 -0
  293. package/dist/components/forms/form-retry/submitted-draft.js +270 -0
  294. package/dist/components/forms/form-retry/types.d.ts +84 -0
  295. package/dist/components/forms/form-retry/types.d.ts.map +1 -0
  296. package/dist/components/forms/form-retry/types.js +13 -0
  297. package/dist/components/forms/index.d.ts +21 -2
  298. package/dist/components/forms/index.d.ts.map +1 -1
  299. package/dist/components/forms/index.js +22 -1
  300. package/dist/components/forms/insert-text.d.ts +12 -0
  301. package/dist/components/forms/insert-text.d.ts.map +1 -0
  302. package/dist/components/forms/insert-text.js +38 -0
  303. package/dist/components/forms/long-press.d.ts +88 -0
  304. package/dist/components/forms/long-press.d.ts.map +1 -0
  305. package/dist/components/forms/long-press.js +213 -0
  306. package/dist/components/forms/native-file-field.d.ts +94 -0
  307. package/dist/components/forms/native-file-field.d.ts.map +1 -0
  308. package/dist/components/forms/native-file-field.js +147 -0
  309. package/dist/components/forms/ready-beep.d.ts +24 -0
  310. package/dist/components/forms/ready-beep.d.ts.map +1 -0
  311. package/dist/components/forms/ready-beep.js +82 -0
  312. package/dist/components/forms/signature-pad-logic.d.ts +50 -0
  313. package/dist/components/forms/signature-pad-logic.d.ts.map +1 -0
  314. package/dist/components/forms/signature-pad-logic.js +48 -0
  315. package/dist/components/forms/types.d.ts +41 -0
  316. package/dist/components/forms/types.d.ts.map +1 -1
  317. package/dist/components/layout/PageHeader.svelte +151 -17
  318. package/dist/components/layout/PageHeader.svelte.d.ts +47 -7
  319. package/dist/components/layout/PageHeader.svelte.d.ts.map +1 -1
  320. package/dist/components/layout/__tests__/PageHeader.test.js +87 -1
  321. package/dist/components/layout/index.d.ts +1 -0
  322. package/dist/components/layout/index.d.ts.map +1 -1
  323. package/dist/components/layout/index.js +1 -0
  324. package/dist/components/layout/page-header-context.d.ts +25 -0
  325. package/dist/components/layout/page-header-context.d.ts.map +1 -0
  326. package/dist/components/layout/page-header-context.js +16 -0
  327. package/dist/components/nav/FilterChips.svelte +8 -1
  328. package/dist/components/nav/FilterChips.svelte.d.ts +2 -0
  329. package/dist/components/nav/FilterChips.svelte.d.ts.map +1 -1
  330. package/dist/components/nav/Tabs.svelte +241 -0
  331. package/dist/components/nav/Tabs.svelte.d.ts +15 -0
  332. package/dist/components/nav/Tabs.svelte.d.ts.map +1 -1
  333. package/dist/components/nav/__tests__/LinkTabs.test.js +117 -0
  334. package/dist/components/nav/index.d.ts +1 -0
  335. package/dist/components/nav/index.d.ts.map +1 -1
  336. package/dist/components/nav/index.js +1 -0
  337. package/dist/components/nav/tabs-overflow.d.ts +12 -0
  338. package/dist/components/nav/tabs-overflow.d.ts.map +1 -0
  339. package/dist/components/nav/tabs-overflow.js +17 -0
  340. package/dist/components/nav/types.d.ts +8 -0
  341. package/dist/components/nav/types.d.ts.map +1 -1
  342. package/dist/components/ui/Button.svelte +16 -2
  343. package/dist/components/ui/Button.svelte.d.ts +2 -0
  344. package/dist/components/ui/Button.svelte.d.ts.map +1 -1
  345. package/dist/components/ui/Card.svelte +14 -5
  346. package/dist/components/ui/Dropdown.svelte +55 -1
  347. package/dist/components/ui/Dropdown.svelte.d.ts +8 -0
  348. package/dist/components/ui/Dropdown.svelte.d.ts.map +1 -1
  349. package/dist/components/ui/IconToggle.svelte +139 -0
  350. package/dist/components/ui/IconToggle.svelte.d.ts +32 -0
  351. package/dist/components/ui/IconToggle.svelte.d.ts.map +1 -0
  352. package/dist/components/ui/IconToggleGroup.svelte +56 -0
  353. package/dist/components/ui/IconToggleGroup.svelte.d.ts +21 -0
  354. package/dist/components/ui/IconToggleGroup.svelte.d.ts.map +1 -0
  355. package/dist/components/ui/Pagination.svelte +1 -1
  356. package/dist/components/ui/Popover.svelte +17 -1
  357. package/dist/components/ui/Popover.svelte.d.ts +8 -0
  358. package/dist/components/ui/Popover.svelte.d.ts.map +1 -1
  359. package/dist/components/ui/__tests__/IconToggle.test.js +59 -0
  360. package/dist/components/ui/__tests__/IconToggleGroup.test.js +30 -0
  361. package/dist/components/ui/__tests__/Popover.test.js +44 -0
  362. package/dist/components/ui/__tests__/dropdown-popover-icon.test.js +125 -0
  363. package/dist/components/ui/__tests__/phone-touch-targets.test.js +39 -0
  364. package/dist/components/ui/index.d.ts +2 -0
  365. package/dist/components/ui/index.d.ts.map +1 -1
  366. package/dist/components/ui/index.js +2 -0
  367. package/dist/i18n/strings.ui.d.ts +65 -0
  368. package/dist/i18n/strings.ui.d.ts.map +1 -1
  369. package/dist/i18n/strings.ui.js +72 -0
  370. package/dist/styles/tokens.css +2 -0
  371. package/dist/svelte/__tests__/playground.test.js +3 -1
  372. package/dist/svelte/playground/CalendarPreview.svelte +140 -0
  373. package/dist/svelte/playground/CalendarPreview.svelte.d.ts +4 -0
  374. package/dist/svelte/playground/CalendarPreview.svelte.d.ts.map +1 -0
  375. package/dist/svelte/playground/CapturePreview.svelte +56 -0
  376. package/dist/svelte/playground/CapturePreview.svelte.d.ts +4 -0
  377. package/dist/svelte/playground/CapturePreview.svelte.d.ts.map +1 -0
  378. package/dist/svelte/playground.d.ts.map +1 -1
  379. package/dist/svelte/playground.js +20 -0
  380. package/dist/themes/ThemeProvider.svelte +9 -0
  381. package/dist/themes/ThemeProvider.svelte.d.ts +2 -0
  382. package/dist/themes/ThemeProvider.svelte.d.ts.map +1 -1
  383. package/dist/themes/css-generator.d.ts.map +1 -1
  384. package/dist/themes/css-generator.js +3 -0
  385. package/dist/themes/styles/glass.css +4 -0
  386. package/dist/themes/styles/happyvertical.css +4 -0
  387. package/dist/themes/styles/material.css +4 -0
  388. package/dist/themes/styles/smrt.css +4 -0
  389. package/dist/themes/styles/studio.css +4 -0
  390. package/dist/types-generic.d.ts +2 -0
  391. package/dist/types-generic.d.ts.map +1 -1
  392. package/package.json +9 -3
@@ -0,0 +1,404 @@
1
+ /**
2
+ * `createFormRetry()`: the browser half of `runOnce()` for a SvelteKit
3
+ * enhanced form (#3291).
4
+ *
5
+ * One controller per form. It owns the submission key (`submission-key.ts`),
6
+ * the conditional reset (`form-draft.ts`) and, when asked, the
7
+ * restore-after-reload draft (`submitted-draft.ts`), and exposes them as:
8
+ *
9
+ * - `enhance(submit?)` — a submit function in SvelteKit's `SubmitFunction`
10
+ * shape, for `<form method="POST" use:enhance={retry.enhance()}>`;
11
+ * - `attach` / `action` — an attachment (`{@attach retry.attach}`) or action
12
+ * (`use:retry.action`) that keeps a hidden key field in the form and puts a
13
+ * restored draft back into its controls;
14
+ * - `subscribe` — the Svelte store contract, so `$retry.status` and
15
+ * `$retry.token` are reactive in a component without this module importing
16
+ * Svelte.
17
+ *
18
+ * It is framework-free TypeScript: no `@sveltejs/kit` and no `svelte` import.
19
+ *
20
+ * ## What happens to one submit
21
+ *
22
+ * 1. A file the restored draft says was chosen, but is not chosen now, refuses
23
+ * the submit (`status: 'files-required'`) — a native file input cannot be
24
+ * restored, and sending without it would silently be different content.
25
+ * 2. A submit while another is unresolved is refused (`status: 'busy'`) and
26
+ * nothing is sent; the form keeps what the person typed.
27
+ * 3. Otherwise the key is written into the outgoing `FormData` (so the body is
28
+ * right even if a hidden field is stale or missing) and what the person can
29
+ * see is snapshotted — before the page's own submit function runs, because
30
+ * kit built the body before calling it. With restore enabled, the submitted
31
+ * values are then kept beside the key.
32
+ * 4. The result settles the slot:
33
+ * - `error` (transport failure, lost response, thrown server error): the key,
34
+ * the form and the draft are all kept, and `update()` is NOT called —
35
+ * `enhance`'s default would apply the error and can unmount the form. The
36
+ * unchanged re-submit is the same claim.
37
+ * - `failure` (validation): applied without rotating; the draft is dropped
38
+ * because nothing was recorded.
39
+ * - `success`: the draft is dropped; the form is reset — and the key
40
+ * rotated — only if the fields are unchanged since the submit. A person who
41
+ * typed the next entry while the first was in flight keeps it, under the
42
+ * same key, and sending it is a different claim because its content
43
+ * differs. With the page's own result callback, the key rotates only if
44
+ * the form is actually reset while that callback runs.
45
+ * - `redirect`: a confirmed write only with `redirectConfirmsWrite: true`.
46
+ * - A late result for a submit already settled is ignored entirely.
47
+ *
48
+ * An aborted request (the page called `controller.abort()`, before or during
49
+ * the request — including from its own submit function) produces no result
50
+ * from `enhance`; it settles like a transport error so the form is not stuck
51
+ * refusing submits.
52
+ *
53
+ * @module
54
+ */
55
+ import { draftOf, formWasCleared } from './form-draft.js';
56
+ import { beginSubmit, isSubmissionKeyPersistent, readSubmissionKey, rotateSubmissionKey, settleSubmit, storageBase, } from './submission-key.js';
57
+ import { applyDraftToForm, clearSubmittedDraft, DEFAULT_DRAFT_MAX_AGE_MS, describeFormData, isChosenFile, readSubmittedDraft, saveSubmittedDraft, } from './submitted-draft.js';
58
+ const DEFAULT_FIELD = 'submissionKey';
59
+ /** Create the retry controller for one form. */
60
+ export function createFormRetry(options) {
61
+ const fieldName = options.fieldName ?? DEFAULT_FIELD;
62
+ const now = options.now ?? (() => new Date());
63
+ const location = {
64
+ form: options.form,
65
+ scope: options.scope,
66
+ storage: options.storage,
67
+ storageKey: options.storageKey,
68
+ mint: options.mint,
69
+ };
70
+ const restoreOptions = options.restore;
71
+ const draftName = restoreOptions?.storageKey ??
72
+ `${storageBase(options.form, options.scope)}:draft`;
73
+ const exclude = new Set([
74
+ fieldName,
75
+ ...(restoreOptions?.exclude ?? []),
76
+ ]);
77
+ // Left out of the edit comparison: the key field (it rotates by design)
78
+ // and whatever the page says changes on its own.
79
+ const notEdits = new Set([
80
+ fieldName,
81
+ ...(options.ignoreFields ?? []),
82
+ ]);
83
+ let slot = {
84
+ token: readSubmissionKey(location),
85
+ inFlight: null,
86
+ };
87
+ let restored = null;
88
+ if (restoreOptions) {
89
+ const maxAge = restoreOptions.maxAgeMs ?? DEFAULT_DRAFT_MAX_AGE_MS;
90
+ const fresh = (savedAt) => {
91
+ const current = now();
92
+ if (restoreOptions.fresh)
93
+ return restoreOptions.fresh(savedAt, current);
94
+ const age = current.getTime() - savedAt.getTime();
95
+ return age >= 0 && age <= maxAge;
96
+ };
97
+ restored = readSubmittedDraft(options.storage, draftName, {
98
+ token: slot.token,
99
+ owner: restoreOptions.owner,
100
+ fresh,
101
+ });
102
+ if (restored && restoreOptions.values && restored.values !== undefined) {
103
+ try {
104
+ restoreOptions.values.restore(restored.values);
105
+ }
106
+ catch {
107
+ // The page rejected the stored value (it is untrusted data): restore
108
+ // nothing rather than half a draft, and never break the page over it.
109
+ clearSubmittedDraft(options.storage, draftName);
110
+ restored = null;
111
+ }
112
+ }
113
+ }
114
+ let appliedRestore = false;
115
+ let state = {
116
+ token: slot.token,
117
+ inFlight: null,
118
+ status: 'idle',
119
+ restored: restored !== null,
120
+ filesToReselect: uniqueFields(restored),
121
+ persistent: isSubmissionKeyPersistent(location),
122
+ };
123
+ const listeners = new Set();
124
+ function setState(patch) {
125
+ state = {
126
+ ...state,
127
+ ...patch,
128
+ token: slot.token,
129
+ inFlight: slot.inFlight,
130
+ persistent: isSubmissionKeyPersistent(location),
131
+ };
132
+ for (const listener of listeners)
133
+ listener(state);
134
+ }
135
+ function writeSlot(next) {
136
+ slot = next;
137
+ }
138
+ function clearDraft() {
139
+ if (restoreOptions)
140
+ clearSubmittedDraft(options.storage, draftName);
141
+ }
142
+ function missingFiles(formData) {
143
+ return state.filesToReselect.filter((field) => !formData.getAll(field).some(isChosenFile));
144
+ }
145
+ function saveDraft(formData, token, skip) {
146
+ if (!restoreOptions)
147
+ return;
148
+ if (!restoreOptions.owner) {
149
+ // A draft can never be restored without an owner, so keeping one would
150
+ // only leave the person's values on shared hardware for nothing.
151
+ clearDraft();
152
+ return;
153
+ }
154
+ const described = describeFormData(formData, skip);
155
+ let values;
156
+ if (restoreOptions.values) {
157
+ try {
158
+ values = restoreOptions.values.capture(formData);
159
+ }
160
+ catch {
161
+ values = undefined;
162
+ }
163
+ }
164
+ saveSubmittedDraft(options.storage, draftName, {
165
+ token,
166
+ owner: restoreOptions.owner,
167
+ savedAt: now(),
168
+ fields: described.fields,
169
+ files: described.files,
170
+ values,
171
+ });
172
+ }
173
+ function passwordFields(form) {
174
+ const names = [];
175
+ for (const element of Array.from(form.elements)) {
176
+ if (element instanceof HTMLInputElement &&
177
+ element.type === 'password' &&
178
+ element.name) {
179
+ names.push(element.name);
180
+ }
181
+ }
182
+ return names;
183
+ }
184
+ function enhance(submit) {
185
+ return async (input) => {
186
+ const { formData, formElement, cancel, controller } = input;
187
+ const missing = missingFiles(formData);
188
+ if (missing.length > 0) {
189
+ cancel();
190
+ setState({ status: 'files-required', filesToReselect: missing });
191
+ return;
192
+ }
193
+ const begun = beginSubmit(slot);
194
+ if (!begun.start) {
195
+ cancel();
196
+ setState({ status: 'busy' });
197
+ return;
198
+ }
199
+ writeSlot(begun.state);
200
+ const sent = begun.sent;
201
+ formData.set(fieldName, sent);
202
+ // What was sent is what the form held when kit built `formData`, which
203
+ // it did before calling this function. Snapshot it now, before awaiting
204
+ // the page's submit function: an edit made while that is pending was
205
+ // never sent, and must read as an edit when the result arrives.
206
+ const submitted = draftOf(formElement, notEdits);
207
+ let innerCancelled = false;
208
+ let innerCallback;
209
+ if (submit) {
210
+ try {
211
+ const returned = await submit({
212
+ ...input,
213
+ cancel: () => {
214
+ innerCancelled = true;
215
+ cancel();
216
+ },
217
+ });
218
+ if (typeof returned === 'function')
219
+ innerCallback = returned;
220
+ }
221
+ catch (error) {
222
+ // The page's own submit threw before anything was sent: release the
223
+ // slot so the form is not stuck refusing submits.
224
+ cancel();
225
+ writeSlot({ ...slot, inFlight: null });
226
+ setState({ status: 'idle' });
227
+ throw error;
228
+ }
229
+ }
230
+ if (innerCancelled) {
231
+ writeSlot({ ...slot, inFlight: null });
232
+ setState({ status: 'idle' });
233
+ return;
234
+ }
235
+ if (restoreOptions) {
236
+ saveDraft(formData, sent, new Set([...exclude, ...passwordFields(formElement)]));
237
+ }
238
+ setState({ status: 'submitting', filesToReselect: [] });
239
+ let settled = false;
240
+ const onAbort = () => {
241
+ if (settled)
242
+ return;
243
+ settled = true;
244
+ const next = settleSubmit(slot, sent, 'error', {
245
+ formCleared: false,
246
+ mint: () => rotateSubmissionKey(location),
247
+ });
248
+ if (next.disposition === 'ignore')
249
+ return;
250
+ writeSlot(next.state);
251
+ setState({ status: 'transport-error' });
252
+ };
253
+ controller.signal.addEventListener('abort', onAbort, { once: true });
254
+ // The page's own submit function may already have aborted (without
255
+ // cancelling): the abort event has fired, and kit will report no result.
256
+ if (controller.signal.aborted)
257
+ onAbort();
258
+ return async (resultInput) => {
259
+ controller.signal.removeEventListener('abort', onAbort);
260
+ if (settled)
261
+ return;
262
+ settled = true;
263
+ const { result } = resultInput;
264
+ const formCleared = formWasCleared(formElement, submitted, notEdits);
265
+ // The default handling resets an unchanged form on success, so the key
266
+ // retires now. A page's own result callback decides the reset itself
267
+ // (`update({ reset: false })`, or no update at all), and values left on
268
+ // screen must keep their key: then the key retires only if the form is
269
+ // actually reset while the callback runs.
270
+ const retireOnReset = innerCallback !== undefined && result.type === 'success';
271
+ const next = settleSubmit(slot, sent, result.type, {
272
+ formCleared: formCleared && !retireOnReset,
273
+ mint: () => rotateSubmissionKey(location),
274
+ redirectConfirmsWrite: options.redirectConfirmsWrite,
275
+ });
276
+ if (next.disposition === 'ignore')
277
+ return;
278
+ writeSlot(next.state);
279
+ if (next.disposition === 'transport-error') {
280
+ // Keep the key, the form and the draft. Do NOT call update(): on an
281
+ // error result it applies the error and can unmount the form.
282
+ setState({ status: 'transport-error' });
283
+ return;
284
+ }
285
+ if (next.disposition === 'rotate-and-apply') {
286
+ clearDraft();
287
+ setState({
288
+ status: result.type === 'redirect' ? 'redirect' : 'success',
289
+ restored: false,
290
+ });
291
+ }
292
+ else if (result.type === 'failure') {
293
+ // Nothing was recorded, so there is nothing to retry after a reload.
294
+ clearDraft();
295
+ setState({ status: 'failure', restored: false });
296
+ }
297
+ else {
298
+ setState({
299
+ status: result.type === 'redirect' ? 'redirect' : 'idle',
300
+ });
301
+ }
302
+ const update = (opts) => resultInput.update({ ...opts, reset: opts?.reset ?? formCleared });
303
+ if (!innerCallback) {
304
+ await update();
305
+ return;
306
+ }
307
+ if (!(retireOnReset && formCleared)) {
308
+ await innerCallback({ ...resultInput, update });
309
+ return;
310
+ }
311
+ // Watch for the reset like the capture fields do: capture phase on the
312
+ // document, so a page listener that stops propagation cannot hide it.
313
+ const doc = formElement.ownerDocument;
314
+ const seen = { reset: null };
315
+ const onReset = (event) => {
316
+ if (event.target === formElement)
317
+ seen.reset = event;
318
+ };
319
+ doc.addEventListener('reset', onReset, true);
320
+ try {
321
+ await innerCallback({ ...resultInput, update });
322
+ }
323
+ finally {
324
+ doc.removeEventListener('reset', onReset, true);
325
+ // A submit started while the callback ran carries the current key:
326
+ // rotating under it would hand its resend a different claim. Its own
327
+ // result decides retirement instead.
328
+ if (seen.reset &&
329
+ !seen.reset.defaultPrevented &&
330
+ slot.inFlight === null) {
331
+ writeSlot({ token: rotateSubmissionKey(location), inFlight: null });
332
+ setState({});
333
+ }
334
+ }
335
+ };
336
+ };
337
+ }
338
+ function syncHiddenField(form) {
339
+ let field = Array.from(form.elements).find((element) => element instanceof HTMLInputElement && element.name === fieldName);
340
+ if (!field) {
341
+ field = form.ownerDocument.createElement('input');
342
+ field.type = 'hidden';
343
+ field.name = fieldName;
344
+ form.prepend(field);
345
+ }
346
+ field.value = slot.token;
347
+ return field;
348
+ }
349
+ function attach(form) {
350
+ syncHiddenField(form);
351
+ if (restored && !appliedRestore) {
352
+ appliedRestore = true;
353
+ applyDraftToForm(form, restored, exclude);
354
+ }
355
+ const unsubscribe = subscribe(() => {
356
+ syncHiddenField(form);
357
+ });
358
+ return unsubscribe;
359
+ }
360
+ function subscribe(listener) {
361
+ listeners.add(listener);
362
+ listener(state);
363
+ return () => {
364
+ listeners.delete(listener);
365
+ };
366
+ }
367
+ function discard() {
368
+ if (slot.inFlight !== null)
369
+ return false;
370
+ clearDraft();
371
+ restored = null;
372
+ writeSlot({
373
+ token: rotateSubmissionKey(location),
374
+ inFlight: null,
375
+ });
376
+ setState({ status: 'idle', restored: false, filesToReselect: [] });
377
+ return true;
378
+ }
379
+ return {
380
+ get token() {
381
+ return slot.token;
382
+ },
383
+ fieldName,
384
+ get state() {
385
+ return state;
386
+ },
387
+ get restored() {
388
+ return restored;
389
+ },
390
+ subscribe,
391
+ enhance,
392
+ attach,
393
+ action: (form) => {
394
+ const cleanup = attach(form);
395
+ return { destroy: cleanup };
396
+ },
397
+ discard,
398
+ };
399
+ }
400
+ function uniqueFields(draft) {
401
+ if (!draft)
402
+ return [];
403
+ return Array.from(new Set(draft.files.map((file) => file.field)));
404
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Whether a form still holds what it submitted, so a successful submit knows
3
+ * if it may clear the fields (#3291).
4
+ *
5
+ * The problem this solves is created by the in-flight refusal in
6
+ * `submission-key.ts`. A submit over a slow link is unresolved for a while;
7
+ * the fields stay editable, so the person can type the NEXT entry into them.
8
+ * They cannot send it yet — that is the point — but when the first submit
9
+ * finally succeeds, `enhance`'s `update()` resets the form by default and the
10
+ * typed draft is gone. Nothing downstream can recover it: it never reached the
11
+ * server, so no claim has ever seen it.
12
+ *
13
+ * So the reset becomes conditional. If the fields still hold exactly what was
14
+ * submitted, clearing them is right — the ordinary case, and what makes the
15
+ * next entry a fresh form with a fresh key. If they do not, the person has
16
+ * moved on and their work is kept.
17
+ *
18
+ * Every control that posts a value is compared, hidden inputs included: a
19
+ * `Listbox`, `Combobox` or `MultiSelect` posts its selection through hidden
20
+ * inputs, and a selection changed while a submit is in flight is an edit the
21
+ * server has never seen. The ONE exception is the retry helper's own key field
22
+ * (and any name the caller adds to `ignore`): a confirmed write rotates it, so
23
+ * comparing it would make every form look edited and nothing would ever
24
+ * reset. A page-managed hidden field that changes after a submit (a rewritten
25
+ * nonce) therefore keeps the form — the safe direction — unless it is ignored.
26
+ *
27
+ * Files are compared as the browser would post them. A control can contribute
28
+ * a file no element shows: a capture field on the `formdata`-event fallback
29
+ * appends its file while the entry list is built and keeps an empty, unnamed
30
+ * input. So the draft also records every `File` entry of `new FormData(form)`
31
+ * (which fires `formdata`, as a real submit does) by name, type, size and
32
+ * modification time; a capture replaced mid-submit then reads as an edit.
33
+ *
34
+ * @module
35
+ */
36
+ /** The values a person can see and change, keyed by position and name. */
37
+ export type FormDraft = ReadonlyMap<string, string>;
38
+ /**
39
+ * What `form` would post at this moment, as far as an edit can change it:
40
+ * every value-carrying control except the names in `ignore` (by default the
41
+ * `submissionKey` key field).
42
+ */
43
+ export declare function draftOf(form: HTMLFormElement, ignore?: ReadonlySet<string>): FormDraft;
44
+ /**
45
+ * Has `form` been edited since `submitted` was taken? `true` means the person
46
+ * has typed something the server has never seen, so a reset would destroy it.
47
+ */
48
+ export declare function draftChanged(form: HTMLFormElement, submitted: FormDraft, ignore?: ReadonlySet<string>): boolean;
49
+ /**
50
+ * Did this submit leave the form EMPTY — the one question key retirement turns
51
+ * on (`settleSubmit()`)? Both of these must be no:
52
+ *
53
+ * - the person edited the fields while the submit was unresolved, so a reset
54
+ * would destroy work the server has never seen;
55
+ * - **the submitted form is no longer in the document.** A form remounted
56
+ * under a `{#key}` block, or unmounted when its panel closes, leaves a
57
+ * detached node that still holds exactly what it submitted, so an
58
+ * unchanged-check alone would retire the key — while the VISIBLE
59
+ * replacement may hold the same content, one tap from a second record under
60
+ * a fresh claim.
61
+ *
62
+ * A detached form therefore never counts as cleared. That is the safe
63
+ * direction: keeping a key can only make a later submit of the same content
64
+ * collapse onto the row that already exists, while retiring one wrongly is
65
+ * what records a duplicate.
66
+ */
67
+ export declare function formWasCleared(form: HTMLFormElement, submitted: FormDraft, ignore?: ReadonlySet<string>): boolean;
68
+ //# sourceMappingURL=form-draft.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form-draft.d.ts","sourceRoot":"","sources":["../../../../src/components/forms/form-retry/form-draft.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,0EAA0E;AAC1E,MAAM,MAAM,SAAS,GAAG,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAsDpD;;;;GAIG;AACH,wBAAgB,OAAO,CACrB,IAAI,EAAE,eAAe,EACrB,MAAM,GAAE,WAAW,CAAC,MAAM,CAAmB,GAC5C,SAAS,CAeX;AAgCD;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,eAAe,EACrB,SAAS,EAAE,SAAS,EACpB,MAAM,GAAE,WAAW,CAAC,MAAM,CAAmB,GAC5C,OAAO,CAOT;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,eAAe,EACrB,SAAS,EAAE,SAAS,EACpB,MAAM,GAAE,WAAW,CAAC,MAAM,CAAmB,GAC5C,OAAO,CAGT"}
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Whether a form still holds what it submitted, so a successful submit knows
3
+ * if it may clear the fields (#3291).
4
+ *
5
+ * The problem this solves is created by the in-flight refusal in
6
+ * `submission-key.ts`. A submit over a slow link is unresolved for a while;
7
+ * the fields stay editable, so the person can type the NEXT entry into them.
8
+ * They cannot send it yet — that is the point — but when the first submit
9
+ * finally succeeds, `enhance`'s `update()` resets the form by default and the
10
+ * typed draft is gone. Nothing downstream can recover it: it never reached the
11
+ * server, so no claim has ever seen it.
12
+ *
13
+ * So the reset becomes conditional. If the fields still hold exactly what was
14
+ * submitted, clearing them is right — the ordinary case, and what makes the
15
+ * next entry a fresh form with a fresh key. If they do not, the person has
16
+ * moved on and their work is kept.
17
+ *
18
+ * Every control that posts a value is compared, hidden inputs included: a
19
+ * `Listbox`, `Combobox` or `MultiSelect` posts its selection through hidden
20
+ * inputs, and a selection changed while a submit is in flight is an edit the
21
+ * server has never seen. The ONE exception is the retry helper's own key field
22
+ * (and any name the caller adds to `ignore`): a confirmed write rotates it, so
23
+ * comparing it would make every form look edited and nothing would ever
24
+ * reset. A page-managed hidden field that changes after a submit (a rewritten
25
+ * nonce) therefore keeps the form — the safe direction — unless it is ignored.
26
+ *
27
+ * Files are compared as the browser would post them. A control can contribute
28
+ * a file no element shows: a capture field on the `formdata`-event fallback
29
+ * appends its file while the entry list is built and keeps an empty, unnamed
30
+ * input. So the draft also records every `File` entry of `new FormData(form)`
31
+ * (which fires `formdata`, as a real submit does) by name, type, size and
32
+ * modification time; a capture replaced mid-submit then reads as an edit.
33
+ *
34
+ * @module
35
+ */
36
+ /**
37
+ * The names left out of the comparison by default: the controller's default
38
+ * key field. `createFormRetry()` passes its configured field name (plus
39
+ * `ignoreFields`) instead.
40
+ */
41
+ const DEFAULT_IGNORED = new Set(['submissionKey']);
42
+ function isEditableControl(element) {
43
+ if (!(element instanceof HTMLInputElement) &&
44
+ !(element instanceof HTMLTextAreaElement) &&
45
+ !(element instanceof HTMLSelectElement)) {
46
+ return false;
47
+ }
48
+ if (element instanceof HTMLInputElement) {
49
+ const type = element.type;
50
+ if (type === 'submit' ||
51
+ type === 'button' ||
52
+ type === 'reset' ||
53
+ type === 'image') {
54
+ return false;
55
+ }
56
+ }
57
+ return true;
58
+ }
59
+ function controlValue(element) {
60
+ if (element instanceof HTMLInputElement) {
61
+ if (element.type === 'checkbox' || element.type === 'radio') {
62
+ return element.checked ? 'on' : '';
63
+ }
64
+ if (element.type === 'file') {
65
+ // A file input cannot be restored from script, so the chosen files'
66
+ // identity (name, size, type) is all that can honestly be compared.
67
+ return Array.from(element.files ?? [], (file) => `${file.name}\u0000${file.size}\u0000${file.type}`).join('\u0001');
68
+ }
69
+ return element.value;
70
+ }
71
+ if (element instanceof HTMLSelectElement && element.multiple) {
72
+ return Array.from(element.selectedOptions, (option) => option.value).join('\u0001');
73
+ }
74
+ return element.value;
75
+ }
76
+ /**
77
+ * What `form` would post at this moment, as far as an edit can change it:
78
+ * every value-carrying control except the names in `ignore` (by default the
79
+ * `submissionKey` key field).
80
+ */
81
+ export function draftOf(form, ignore = DEFAULT_IGNORED) {
82
+ const draft = new Map();
83
+ let index = 0;
84
+ for (const element of Array.from(form.elements)) {
85
+ if (!isEditableControl(element))
86
+ continue;
87
+ const controlName = element.name;
88
+ if (controlName && ignore.has(controlName))
89
+ continue;
90
+ const name = controlName || `#${index}`;
91
+ // Several controls can share a name (a radio group, repeated fields); the
92
+ // index keeps them distinct so a change in any one of them is visible.
93
+ draft.set(`${index}:${name}`, controlValue(element));
94
+ index += 1;
95
+ }
96
+ draft.set('files', postedFiles(form));
97
+ return draft;
98
+ }
99
+ /**
100
+ * The identity of every file `form` would post, in entry order. Bytes are
101
+ * never read. Empty when the entry list cannot be built here (no `FormData`,
102
+ * or a `formdata` listener is already building one for this form).
103
+ */
104
+ function postedFiles(form) {
105
+ if (typeof FormData === 'undefined' || typeof File === 'undefined') {
106
+ return '';
107
+ }
108
+ let entries;
109
+ try {
110
+ entries = new FormData(form);
111
+ }
112
+ catch {
113
+ return '';
114
+ }
115
+ const files = [];
116
+ entries.forEach((value, name) => {
117
+ // Skip the "no file chosen" entry (empty name, no bytes): it is minted
118
+ // afresh, with a new modification time, every time the list is built.
119
+ if (value instanceof File && (value.name !== '' || value.size > 0)) {
120
+ files.push([name, value.name, value.type, value.size, value.lastModified].join('\u0000'));
121
+ }
122
+ });
123
+ return files.join('\u0001');
124
+ }
125
+ /**
126
+ * Has `form` been edited since `submitted` was taken? `true` means the person
127
+ * has typed something the server has never seen, so a reset would destroy it.
128
+ */
129
+ export function draftChanged(form, submitted, ignore = DEFAULT_IGNORED) {
130
+ const now = draftOf(form, ignore);
131
+ if (now.size !== submitted.size)
132
+ return true;
133
+ for (const [key, value] of now) {
134
+ if (submitted.get(key) !== value)
135
+ return true;
136
+ }
137
+ return false;
138
+ }
139
+ /**
140
+ * Did this submit leave the form EMPTY — the one question key retirement turns
141
+ * on (`settleSubmit()`)? Both of these must be no:
142
+ *
143
+ * - the person edited the fields while the submit was unresolved, so a reset
144
+ * would destroy work the server has never seen;
145
+ * - **the submitted form is no longer in the document.** A form remounted
146
+ * under a `{#key}` block, or unmounted when its panel closes, leaves a
147
+ * detached node that still holds exactly what it submitted, so an
148
+ * unchanged-check alone would retire the key — while the VISIBLE
149
+ * replacement may hold the same content, one tap from a second record under
150
+ * a fresh claim.
151
+ *
152
+ * A detached form therefore never counts as cleared. That is the safe
153
+ * direction: keeping a key can only make a later submit of the same content
154
+ * collapse onto the row that already exists, while retiring one wrongly is
155
+ * what records a duplicate.
156
+ */
157
+ export function formWasCleared(form, submitted, ignore = DEFAULT_IGNORED) {
158
+ if (!form.isConnected)
159
+ return false;
160
+ return !draftChanged(form, submitted, ignore);
161
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Form retry — subpath `@happyvertical/smrt-ui/form-retry` (#3291).
3
+ *
4
+ * The browser half of `runOnce()` (`@happyvertical/smrt-core`): a per-tab,
5
+ * per-form submission key, retained values across a failed submit, a
6
+ * conditional reset that keeps the next entry typed while the first was in
7
+ * flight, and opt-in restore-after-reload. Framework-free and Svelte-free, so
8
+ * this entry imports under plain Node and in any bundler; the same exports are
9
+ * also re-exported from `@happyvertical/smrt-ui/forms`, beside `Form`.
10
+ *
11
+ * Guide: docs/content/form-retry.md.
12
+ */
13
+ export { digestSubmissionContent } from './content-digest.js';
14
+ export { createFormRetry, type FormRetry, type FormRetryOptions, type FormRetryRestoreOptions, type FormRetryState, type FormRetryStatus, type FormRetryValuesHook, } from './controller.js';
15
+ export { draftChanged, draftOf, type FormDraft, formWasCleared, } from './form-draft.js';
16
+ export { beginSubmit, clearSubmissionKey, type FormRetryStorage, mintSubmissionKey, readSubmissionKey, rotateSubmissionKey, type SubmissionKeyLocation, type SubmissionSlot, type SubmitDisposition, type SubmitOutcome, settleSubmit, submitDisposition, } from './submission-key.js';
17
+ export type { SubmittedDraft, SubmittedFileInfo, } from './submitted-draft.js';
18
+ export type { FormRetryActionResult, FormRetryResultCallback, FormRetryResultInput, FormRetrySubmitFunction, FormRetrySubmitInput, FormRetryUpdateOptions, MaybePromise, } from './types.js';
19
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/forms/form-retry/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,EACL,eAAe,EACf,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,EAC5B,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,mBAAmB,GACzB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,YAAY,EACZ,OAAO,EACP,KAAK,SAAS,EACd,cAAc,GACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,WAAW,EACX,kBAAkB,EAClB,KAAK,gBAAgB,EACrB,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,EACnB,KAAK,qBAAqB,EAC1B,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,YAAY,EACZ,iBAAiB,GAClB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,cAAc,EACd,iBAAiB,GAClB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EACV,qBAAqB,EACrB,uBAAuB,EACvB,oBAAoB,EACpB,uBAAuB,EACvB,oBAAoB,EACpB,sBAAsB,EACtB,YAAY,GACb,MAAM,YAAY,CAAC"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Form retry — subpath `@happyvertical/smrt-ui/form-retry` (#3291).
3
+ *
4
+ * The browser half of `runOnce()` (`@happyvertical/smrt-core`): a per-tab,
5
+ * per-form submission key, retained values across a failed submit, a
6
+ * conditional reset that keeps the next entry typed while the first was in
7
+ * flight, and opt-in restore-after-reload. Framework-free and Svelte-free, so
8
+ * this entry imports under plain Node and in any bundler; the same exports are
9
+ * also re-exported from `@happyvertical/smrt-ui/forms`, beside `Form`.
10
+ *
11
+ * Guide: docs/content/form-retry.md.
12
+ */
13
+ export { digestSubmissionContent } from './content-digest.js';
14
+ export { createFormRetry, } from './controller.js';
15
+ export { draftChanged, draftOf, formWasCleared, } from './form-draft.js';
16
+ export { beginSubmit, clearSubmissionKey, mintSubmissionKey, readSubmissionKey, rotateSubmissionKey, settleSubmit, submitDisposition, } from './submission-key.js';