@kitn.ai/ui 0.28.0 → 0.30.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 (615) hide show
  1. package/README.md +11 -7
  2. package/dist/{Icon-DObhsRtm.js → Icon-BzBET3E-.js} +1 -1
  3. package/dist/{action-icons-CcgNN2oT.js → action-icons-DaW2pU6m.js} +1 -1
  4. package/dist/agent-tooling/construct/public.d.ts +15 -0
  5. package/dist/agent-tooling/construct/schema.d.ts +111 -0
  6. package/dist/arrow-down-D783iMJj.js +1 -0
  7. package/dist/arrow-left-0VLM5kyg.js +1 -0
  8. package/dist/artifact-DGjzHYUT.js +1 -0
  9. package/dist/{attachments-Qr4kg-2b.js → attachments-YvXHlg88.js} +1 -1
  10. package/dist/{audio-visualizer-DToYdfSc.js → audio-visualizer-9lz5IHsL.js} +1 -1
  11. package/dist/{badge-CdEu_jn2.js → badge-Dh_vp8A5.js} +1 -1
  12. package/dist/{bell-DQAAOpDz.js → bell-CT7_WUwS.js} +1 -1
  13. package/dist/{button-DkuOnGLy.js → button-C_3te6fm.js} +1 -1
  14. package/dist/{card-renderer-Jx70m6_l.js → card-renderer-DZVaGH2e.js} +1 -1
  15. package/dist/check-CiEOVnEN.js +1 -0
  16. package/dist/{checkbox-D7iRB3nL.js → checkbox-Bu2T2e0U.js} +1 -1
  17. package/dist/{checkbox-group-B2_rpoPM.js → checkbox-group-BiryEXPQ.js} +1 -1
  18. package/dist/chevron-down-CSobuZRG.js +1 -0
  19. package/dist/chevron-right-DR9ZAP-2.js +1 -0
  20. package/dist/{choice-card-m0BYqqOK.js → choice-card-CM6IjkmL.js} +1 -1
  21. package/dist/circle-BC66EYF7.js +1 -0
  22. package/dist/circle-check-BqmahW0O.js +1 -0
  23. package/dist/{circle-x-BPG0iPst.js → circle-x-Bk-dA5Wf.js} +1 -1
  24. package/dist/{code-block-ClMLKb7C.js → code-block-B26t-Q7q.js} +1 -1
  25. package/dist/{collapsible-B4DlWicL.js → collapsible-Bhc636Cf.js} +1 -1
  26. package/dist/components/builder-composer-triggers.d.ts +62 -0
  27. package/dist/components/builder-layout.d.ts +54 -0
  28. package/dist/components/builder-message-actions.d.ts +72 -0
  29. package/dist/components/builder-panel.d.ts +189 -0
  30. package/dist/components/builder-preview.d.ts +11 -0
  31. package/dist/components/builder-shell-controls.d.ts +69 -0
  32. package/dist/components/builder-skeleton.d.ts +58 -0
  33. package/dist/components/builder-start.d.ts +75 -0
  34. package/dist/components/builder-workspace-variants.d.ts +23 -0
  35. package/dist/components/captions.d.ts +66 -0
  36. package/dist/{composer-sQ0HcvyG.js → composer-DMI85B6j.js} +1 -1
  37. package/dist/{confirm-card-rQWDQsnC.js → confirm-card-B0YIAwXp.js} +1 -1
  38. package/dist/construct-cli.es.js +3 -3
  39. package/dist/construct.d.ts +5 -0
  40. package/dist/construct.js +327 -0
  41. package/dist/{context-0kg4ZX1J.js → context-DWgfhqWp.js} +1 -1
  42. package/dist/controllable-BagRpyHv.js +1 -0
  43. package/dist/{conversation-item-BxwQLaFJ.js → conversation-item-BsOsVGmT.js} +1 -1
  44. package/dist/{conversation-list-BCfGN1JU.js → conversation-list-aX3Vtc7L.js} +1 -1
  45. package/dist/{copy-n9u9rqpi.js → copy-BmEwsNhJ.js} +1 -1
  46. package/dist/core-C8fzo39E.js +1 -12
  47. package/dist/{create-tween-uPkArjIG.js → create-tween-CNOAU3Fx.js} +1 -1
  48. package/dist/{create-tween-C-q-JCM-.js → create-tween-WfqiENls.js} +1 -1
  49. package/dist/{create-tween-CRrJXxBd.js → create-tween-wlFtW9Kh.js} +1 -1
  50. package/dist/{create-tween-DZ3RRDHD.js → create-tween-zt79ba32.js} +1 -1
  51. package/dist/{default-input-CaMrJkgH.js → default-input-DMV5Eaze.js} +1 -1
  52. package/dist/define-CI0y3eE4.js +1 -0
  53. package/dist/define.js +40 -40
  54. package/dist/define.server.js +45 -45
  55. package/dist/{disclosure-WUImBbDv.js → disclosure-NnACBxsC.js} +1 -1
  56. package/dist/{dropdown-wZVeJOMd.js → dropdown-DT7G0RGR.js} +1 -1
  57. package/dist/elements/agent-card.js +1 -1
  58. package/dist/elements/artifact.js +1 -1
  59. package/dist/elements/attachments.js +1 -1
  60. package/dist/elements/audio-visualizer.js +1 -1
  61. package/dist/elements/avatar.js +1 -1
  62. package/dist/elements/badge.js +1 -1
  63. package/dist/elements/button.js +1 -1
  64. package/dist/elements/card.js +1 -1
  65. package/dist/elements/cards.js +1 -1
  66. package/dist/elements/chain-of-thought.js +1 -1
  67. package/dist/elements/chat-scope-picker.js +1 -1
  68. package/dist/elements/chat-workspace.js +1 -1
  69. package/dist/elements/chat.js +1 -1
  70. package/dist/elements/checkbox-group.js +1 -1
  71. package/dist/elements/checkbox.js +1 -1
  72. package/dist/elements/checkpoint.js +1 -1
  73. package/dist/elements/choice.js +1 -1
  74. package/dist/elements/coachmark.js +1 -1
  75. package/dist/elements/code-block.js +1 -1
  76. package/dist/elements/command.js +1 -1
  77. package/dist/elements/compare.js +1 -1
  78. package/dist/elements/composer.js +1 -1
  79. package/dist/elements/confirm-card.js +1 -1
  80. package/dist/elements/context-meter.js +1 -1
  81. package/dist/elements/conversation-item.js +1 -1
  82. package/dist/elements/conversation-list.js +1 -1
  83. package/dist/elements/dialog.js +1 -1
  84. package/dist/elements/dock.js +1 -1
  85. package/dist/elements/dropdown.js +1 -1
  86. package/dist/elements/editable-label.js +1 -1
  87. package/dist/elements/embed.js +1 -1
  88. package/dist/elements/empty.js +1 -1
  89. package/dist/elements/feedback-bar.js +1 -1
  90. package/dist/elements/file-tree.js +1 -1
  91. package/dist/elements/file-upload.js +1 -1
  92. package/dist/elements/form.js +1 -1
  93. package/dist/elements/hover-card.js +1 -1
  94. package/dist/elements/icon.js +1 -1
  95. package/dist/elements/image.js +1 -1
  96. package/dist/elements/input.js +1 -1
  97. package/dist/elements/kbd.js +1 -1
  98. package/dist/elements/link-preview.js +1 -1
  99. package/dist/elements/loader.js +1 -1
  100. package/dist/elements/markdown.js +1 -1
  101. package/dist/elements/menu.js +1 -1
  102. package/dist/elements/message-skills.js +1 -1
  103. package/dist/elements/message.js +1 -1
  104. package/dist/elements/model-switcher.js +1 -1
  105. package/dist/elements/nav.js +1 -1
  106. package/dist/elements/notice.js +1 -1
  107. package/dist/elements/pane-grid.js +1 -1
  108. package/dist/elements/pane-group.js +1 -1
  109. package/dist/elements/pane.js +1 -1
  110. package/dist/elements/popover.js +1 -1
  111. package/dist/elements/progress-bar.js +1 -1
  112. package/dist/elements/prompt-dock.js +1 -1
  113. package/dist/elements/prompt-input.js +1 -1
  114. package/dist/elements/prompt-suggestions.js +1 -1
  115. package/dist/elements/radio-group.js +1 -1
  116. package/dist/elements/reasoning.js +1 -1
  117. package/dist/elements/remote.js +1 -1
  118. package/dist/elements/resizable.js +1 -1
  119. package/dist/elements/response-stream.js +1 -1
  120. package/dist/elements/screen.js +1 -1
  121. package/dist/elements/scroll-area.js +1 -1
  122. package/dist/elements/scroll-button.js +1 -1
  123. package/dist/elements/search.js +1 -1
  124. package/dist/elements/segmented.js +1 -1
  125. package/dist/elements/select.js +1 -1
  126. package/dist/elements/separator.js +1 -1
  127. package/dist/elements/setting-item.js +1 -1
  128. package/dist/elements/settings-group.js +1 -1
  129. package/dist/elements/skeleton.js +1 -1
  130. package/dist/elements/slider.js +1 -1
  131. package/dist/elements/source.js +1 -1
  132. package/dist/elements/status.js +1 -1
  133. package/dist/elements/switch.js +1 -1
  134. package/dist/elements/tabs.js +1 -1
  135. package/dist/elements/tasks.js +1 -1
  136. package/dist/elements/text-shimmer.js +1 -1
  137. package/dist/elements/thinking-bar.js +1 -1
  138. package/dist/elements/thread.js +1 -1
  139. package/dist/elements/toast.js +1 -1
  140. package/dist/elements/tool.js +1 -1
  141. package/dist/elements/tooltip.js +1 -1
  142. package/dist/elements/voice-input.js +1 -1
  143. package/dist/elements/voice-output.js +1 -1
  144. package/dist/{ellipsis-CbOpJnU3.js → ellipsis-6pJ7SxYT.js} +1 -1
  145. package/dist/{embed-CrfjkbUG.js → embed-BMje-nxj.js} +1 -1
  146. package/dist/engine-javascript-C1x7zo1_.js +1 -141
  147. package/dist/{external-link-ByXAwBBQ.js → external-link-Beh9DKeR.js} +1 -1
  148. package/dist/{file-text-DMib2qwt.js → file-text-D1GPfZ8Q.js} +1 -1
  149. package/dist/{file-tree-CJqM2aP7.js → file-tree-Dwj2lqg-.js} +1 -1
  150. package/dist/{folder-_BAb929C.js → folder-b6D8x1DY.js} +1 -1
  151. package/dist/{form-DSZgZQZk.js → form-DWgdxDXu.js} +1 -1
  152. package/dist/{hover-card-BQbul9eq.js → hover-card-CWRkqm40.js} +1 -1
  153. package/dist/{icon-CdcfRhYI.js → icon-CL0jdhsf.js} +1 -1
  154. package/dist/index.js +1741 -1725
  155. package/dist/index.server.js +1582 -1566
  156. package/dist/{info-DsFjGuI_.js → info-0cOUherQ.js} +1 -1
  157. package/dist/{input-DKbmkMos.js → input-BH6Ru91Q.js} +1 -1
  158. package/dist/kai.es.js +1 -1
  159. package/dist/{kbd-PvdGhX4s.js → kbd-DRAnVtNX.js} +1 -1
  160. package/dist/{link-zF8PzWfF.js → link-Dlr0vssB.js} +1 -1
  161. package/dist/{link-preview-DbM1866N.js → link-preview-DODgRyV9.js} +1 -1
  162. package/dist/loader-KvJB6NrT.js +1 -0
  163. package/dist/{markdown-LMvmAyBk.js → markdown-DX9tuJVj.js} +1 -1
  164. package/dist/mcp.es.js +9 -5
  165. package/dist/{message-BYZECyek.js → message-BqQ7Ot5q.js} +1 -1
  166. package/dist/{message-BSsgvHvf.js → message-DkaGfBd8.js} +1 -1
  167. package/dist/message-circle-BPpp5QKv.js +1 -0
  168. package/dist/message-square-BLbsd9BJ.js +1 -0
  169. package/dist/{minimize-2-BQRoBV1V.js → minimize-2-CyOHJwcN.js} +1 -1
  170. package/dist/{model-switcher-CMpCOYcB.js → model-switcher-0hrKDiK2.js} +1 -1
  171. package/dist/{overlay-Cb-MkyDa.js → overlay-DBMFXie6.js} +1 -1
  172. package/dist/{panel-right-i8warFxT.js → panel-right-t8SWJL6e.js} +1 -1
  173. package/dist/{paperclip-ByY3UVO6.js → paperclip-CdzuOjbU.js} +1 -1
  174. package/dist/play-BXy1u7rW.js +1 -0
  175. package/dist/primitives/use-auto-resize.d.ts +11 -0
  176. package/dist/{progress-bar-jVAHvMUf.js → progress-bar-CiBtxMWT.js} +1 -1
  177. package/dist/{prompt-suggestion-CD-qtNgC.js → prompt-suggestion-I_DHP21i.js} +1 -1
  178. package/dist/{radio-BsoBMhkF.js → radio-C_p3n39l.js} +1 -1
  179. package/dist/{reasoning-4kBPtlTx.js → reasoning-BDwy_tIe.js} +1 -1
  180. package/dist/register-impl-DvEnhIxx.js +424 -0
  181. package/dist/{resizable-uPmY9VY-.js → resizable-SYC-fStM.js} +1 -1
  182. package/dist/{rotate-ccw-B2xn93py.js → rotate-ccw-B1nwmwuY.js} +1 -1
  183. package/dist/{rotate-cw-CMVLoCyH.js → rotate-cw-CLAnBcm1.js} +1 -1
  184. package/dist/{scroll-area-BqLUo8ZZ.js → scroll-area-zRyXoLhI.js} +1 -1
  185. package/dist/{scroll-button-DslXiJyL.js → scroll-button-jzrbYCG9.js} +1 -1
  186. package/dist/{select-2cLRtPGT.js → select-DM-0nEe0.js} +1 -1
  187. package/dist/{separator-BWQtNJcM.js → separator-BToOOoOt.js} +1 -1
  188. package/dist/{settings-BBR63jLg.js → settings-PaGo_S4U.js} +1 -1
  189. package/dist/{settings-group-B2ciy-Iw.js → settings-group-BFXGIfXq.js} +1 -1
  190. package/dist/{skeleton-CkHI-HgC.js → skeleton-DYpmFnnm.js} +1 -1
  191. package/dist/{slider-DDR9U-i0.js → slider-ebutAmkQ.js} +1 -1
  192. package/dist/{solid-DruVOXnf.js → solid-15v_3WVL.js} +3563 -3412
  193. package/dist/{solid-BzCFdL25.js → solid-5P-otgdi.js} +4502 -4296
  194. package/dist/solid.d.ts +6 -0
  195. package/dist/solid.js +259 -254
  196. package/dist/solid.server.js +259 -254
  197. package/dist/{source-GEnFF528.js → source-c1gpRl9h.js} +1 -1
  198. package/dist/{star-CilHq8GP.js → star-pcV_3LqF.js} +1 -1
  199. package/dist/{store-Ciyk6eZG.js → store-CSgbFFCg.js} +1 -1
  200. package/dist/{switch-KkeQIbuH.js → switch-Bh2OdLV2.js} +1 -1
  201. package/dist/{tasks-card-BrN4bggJ.js → tasks-card-CbqQAn2S.js} +1 -1
  202. package/dist/text-shimmer-BSzgEnDH.js +1 -0
  203. package/dist/textarea-DrYNt_0z.js +1 -0
  204. package/dist/theme.tokens.css +22 -21
  205. package/dist/{thumbs-up-CsT9Wzb9.js → thumbs-up-BL3-NenK.js} +1 -1
  206. package/dist/{toast-store-tgK6MuMv.js → toast-store-DkOlYczt.js} +1 -1
  207. package/dist/{tool-DzUjIBT8.js → tool-CeIWUVUe.js} +1 -1
  208. package/dist/{tooltip-Bkkg_4t0.js → tooltip-GA-nmg06.js} +1 -1
  209. package/dist/{trash-2-jBKxjGGQ.js → trash-2-CT66QWlm.js} +1 -1
  210. package/dist/{triangle-alert-DlQ68ppf.js → triangle-alert-CeHLWxhD.js} +1 -1
  211. package/dist/ui/color-field.d.ts +48 -0
  212. package/dist/ui/textarea.d.ts +4 -0
  213. package/dist/ui/toggle-chip.d.ts +40 -0
  214. package/dist/{upload-TcAHXv9W.js → upload-9Csb0F0J.js} +1 -1
  215. package/dist/{use-card-resolution-DaZqAdWf.js → use-card-resolution-Zg0a08Z-.js} +1 -1
  216. package/dist/{variant-aurora-DbW_OWL2.js → variant-aurora-4VBQHIKV.js} +1 -1
  217. package/dist/{variant-aurora-CNmfHhcx.js → variant-aurora-DBeCAQNk.js} +2 -2
  218. package/dist/{variant-aurora-D6GsdTPA.js → variant-aurora-FzHVXHby.js} +2 -2
  219. package/dist/{variant-aurora-BhLuYxJR.js → variant-aurora-LE3fqxgw.js} +1 -1
  220. package/dist/{variant-custom-BvYHR62b.js → variant-custom-AFRGcOBS.js} +1 -1
  221. package/dist/{variant-custom-DV2x254s.js → variant-custom-BQkNuPpE.js} +2 -2
  222. package/dist/{variant-custom-BRvaNHSi.js → variant-custom-DI5R0rln.js} +1 -1
  223. package/dist/{variant-custom-DMTrEO5w.js → variant-custom-WWnrrRHS.js} +2 -2
  224. package/dist/{variant-wave-CyBueXj9.js → variant-wave-DUCbH5oI.js} +2 -2
  225. package/dist/{variant-wave-BNMGrDfj.js → variant-wave-DuQV7DhQ.js} +2 -2
  226. package/dist/{variant-wave-BpDd8t3Z.js → variant-wave-nSI2sfII.js} +1 -1
  227. package/dist/{variant-wave-sp-qwsdI.js → variant-wave-wTP5v_jU.js} +1 -1
  228. package/dist/x-eiO9V6J6.js +1 -0
  229. package/{src/elements/styles.css → kit-base.css} +27 -44
  230. package/package.json +32 -17
  231. package/solid.css +33 -0
  232. package/theme.css +39 -23
  233. package/bin/route.test.js +0 -58
  234. package/dist/arrow-down-CjRU3JgV.js +0 -1
  235. package/dist/arrow-left-Bnpb1UEW.js +0 -1
  236. package/dist/artifact-Bwt7OgcR.js +0 -1
  237. package/dist/check-njkVdZu2.js +0 -1
  238. package/dist/chevron-down-B72s6PXR.js +0 -1
  239. package/dist/chevron-right-BatuHzUm.js +0 -1
  240. package/dist/circle-check-B62pODB3.js +0 -1
  241. package/dist/circle-zx0ByzY9.js +0 -1
  242. package/dist/controllable-CnGpMF6r.js +0 -1
  243. package/dist/define-By-uL588.js +0 -1
  244. package/dist/loader-BM4CEz4G.js +0 -1
  245. package/dist/message-circle-DruwObfX.js +0 -1
  246. package/dist/message-square-BG6q6mlm.js +0 -1
  247. package/dist/play-BvKKq3fX.js +0 -1
  248. package/dist/register-impl-B1hLkrI8.js +0 -424
  249. package/dist/text-shimmer-Dcj1MHuq.js +0 -1
  250. package/dist/textarea-Dpvbngbg.js +0 -1
  251. package/dist/x-Z5UntkIJ.js +0 -1
  252. package/src/agent-tooling/README.md +0 -192
  253. package/src/agent-tooling/archetypes.ts +0 -205
  254. package/src/agent-tooling/catalog/README.md +0 -676
  255. package/src/agent-tooling/catalog/catalog-types.ts +0 -231
  256. package/src/agent-tooling/catalog/fabrications.ts +0 -96
  257. package/src/agent-tooling/catalog/invariants.ts +0 -284
  258. package/src/agent-tooling/catalog/labs-titles.ts +0 -114
  259. package/src/agent-tooling/catalog/scenarios.ts +0 -87
  260. package/src/agent-tooling/catalog/surfaces.ts +0 -347
  261. package/src/agent-tooling/construct/cli-entry.ts +0 -8
  262. package/src/agent-tooling/construct/cli.ts +0 -147
  263. package/src/agent-tooling/construct/codegen.ts +0 -1635
  264. package/src/agent-tooling/construct/construct.v1.schema.json +0 -319
  265. package/src/agent-tooling/construct/dev.ts +0 -148
  266. package/src/agent-tooling/construct/fixtures/demo-widget.construct.json +0 -7
  267. package/src/agent-tooling/construct/fixtures/ops-console.construct.json +0 -44
  268. package/src/agent-tooling/construct/fixtures/owner-widget.construct.json +0 -32
  269. package/src/agent-tooling/construct/schema.ts +0 -452
  270. package/src/agent-tooling/integrations/anthropic.ts +0 -442
  271. package/src/agent-tooling/integrations/cloudflare.ts +0 -218
  272. package/src/agent-tooling/integrations/langgraph.ts +0 -133
  273. package/src/agent-tooling/integrations/mastra.ts +0 -182
  274. package/src/agent-tooling/integrations/mock.ts +0 -148
  275. package/src/agent-tooling/integrations/ollama.ts +0 -97
  276. package/src/agent-tooling/integrations/openai.ts +0 -95
  277. package/src/agent-tooling/integrations/openrouter.ts +0 -82
  278. package/src/agent-tooling/integrations/pi.ts +0 -176
  279. package/src/agent-tooling/integrations/pydantic-ai.ts +0 -108
  280. package/src/agent-tooling/integrations/vercel-ai-sdk.ts +0 -398
  281. package/src/agent-tooling/mcp/css-raw.d.ts +0 -12
  282. package/src/agent-tooling/mcp/manifest.ts +0 -422
  283. package/src/agent-tooling/mcp/server.ts +0 -116
  284. package/src/agent-tooling/mcp/stdio.ts +0 -18
  285. package/src/agent-tooling/mcp/tools/construct.ts +0 -130
  286. package/src/agent-tooling/mcp/tools/debug.ts +0 -621
  287. package/src/agent-tooling/mcp/tools/reference.ts +0 -980
  288. package/src/agent-tooling/mcp/tools/scaffold.ts +0 -6708
  289. package/src/agent-tooling/mcp/tools/theme.ts +0 -456
  290. package/src/agent-tooling/mcp/tools/types.ts +0 -14
  291. package/src/agent-tooling/mcp/validate-args.ts +0 -141
  292. package/src/agent-tooling/recipes/composed-thread.ts +0 -714
  293. package/src/agent-tooling/recipes/index.ts +0 -22
  294. package/src/agent-tooling/recipes/types.ts +0 -32
  295. package/src/agent-tooling/registry.ts +0 -132
  296. package/src/agent-tooling/route-emit.ts +0 -305
  297. package/src/agent-tooling/types.ts +0 -390
  298. package/src/components/artifact-card.tsx +0 -120
  299. package/src/components/artifact.tsx +0 -914
  300. package/src/components/attachment-types.ts +0 -66
  301. package/src/components/attachments.tsx +0 -536
  302. package/src/components/audio-visualizer/audio-visualizer.voice-fixture.ts +0 -257
  303. package/src/components/audio-visualizer/aurora.glsl.ts +0 -330
  304. package/src/components/audio-visualizer/fit-scale.ts +0 -112
  305. package/src/components/audio-visualizer/index.tsx +0 -528
  306. package/src/components/audio-visualizer/labs/lab-choreography.ts +0 -187
  307. package/src/components/audio-visualizer/labs/lab-shaders.ts +0 -363
  308. package/src/components/audio-visualizer/labs/lab-visualizer.tsx +0 -102
  309. package/src/components/audio-visualizer/shader-canvas.tsx +0 -876
  310. package/src/components/audio-visualizer/sizes.ts +0 -54
  311. package/src/components/audio-visualizer/variant-aurora.tsx +0 -305
  312. package/src/components/audio-visualizer/variant-bar.tsx +0 -175
  313. package/src/components/audio-visualizer/variant-custom.tsx +0 -344
  314. package/src/components/audio-visualizer/variant-grid.tsx +0 -175
  315. package/src/components/audio-visualizer/variant-radial.tsx +0 -163
  316. package/src/components/audio-visualizer/variant-wave.tsx +0 -130
  317. package/src/components/audio-visualizer/wave.glsl.ts +0 -130
  318. package/src/components/card-fallback.tsx +0 -60
  319. package/src/components/card-renderer.tsx +0 -220
  320. package/src/components/card.tsx +0 -212
  321. package/src/components/chain-of-thought.tsx +0 -348
  322. package/src/components/chat-container.tsx +0 -89
  323. package/src/components/chat-scope-picker.tsx +0 -57
  324. package/src/components/chat-thread.tsx +0 -1014
  325. package/src/components/checkpoint.tsx +0 -88
  326. package/src/components/choice-card.tsx +0 -743
  327. package/src/components/coachmark.tsx +0 -249
  328. package/src/components/code-block.tsx +0 -181
  329. package/src/components/composer-dom.ts +0 -159
  330. package/src/components/composer-highlight.ts +0 -242
  331. package/src/components/composer-history.ts +0 -82
  332. package/src/components/composer.tsx +0 -1075
  333. package/src/components/confirm-card.tsx +0 -402
  334. package/src/components/context.tsx +0 -377
  335. package/src/components/conversation-item.tsx +0 -241
  336. package/src/components/conversation-list.tsx +0 -429
  337. package/src/components/conversation-panel.tsx +0 -118
  338. package/src/components/dismissed-stub.tsx +0 -80
  339. package/src/components/embed.tsx +0 -200
  340. package/src/components/empty.tsx +0 -177
  341. package/src/components/feedback-bar.tsx +0 -166
  342. package/src/components/file-tree.tsx +0 -530
  343. package/src/components/file-upload.tsx +0 -167
  344. package/src/components/form-widgets.tsx +0 -525
  345. package/src/components/form.tsx +0 -1296
  346. package/src/components/home-panel.tsx +0 -159
  347. package/src/components/image.tsx +0 -65
  348. package/src/components/link-preview.tsx +0 -195
  349. package/src/components/loader.tsx +0 -367
  350. package/src/components/markdown.tsx +0 -162
  351. package/src/components/message-skills.tsx +0 -40
  352. package/src/components/message.tsx +0 -765
  353. package/src/components/model-switcher.tsx +0 -132
  354. package/src/components/prompt-input.tsx +0 -188
  355. package/src/components/prompt-suggestion.tsx +0 -150
  356. package/src/components/reasoning.tsx +0 -293
  357. package/src/components/response-compare-types.ts +0 -152
  358. package/src/components/response-compare.tsx +0 -455
  359. package/src/components/response-stream.tsx +0 -109
  360. package/src/components/screen.tsx +0 -181
  361. package/src/components/scroll-button.tsx +0 -93
  362. package/src/components/source.tsx +0 -192
  363. package/src/components/tasks-card.tsx +0 -801
  364. package/src/components/text-shimmer.tsx +0 -37
  365. package/src/components/thinking-bar.tsx +0 -50
  366. package/src/components/thread.tsx +0 -228
  367. package/src/components/toast.tsx +0 -503
  368. package/src/components/tool-classify.ts +0 -26
  369. package/src/components/tool-types.ts +0 -49
  370. package/src/components/tool.tsx +0 -190
  371. package/src/components/use-card-resolution.ts +0 -44
  372. package/src/components/voice-input.tsx +0 -222
  373. package/src/components/voice-output.tsx +0 -242
  374. package/src/components/widget-tab-bar.tsx +0 -73
  375. package/src/components/workspace-shell.tsx +0 -285
  376. package/src/diagnostics/hook.ts +0 -352
  377. package/src/diagnostics/index.ts +0 -82
  378. package/src/diagnostics/report-request.ts +0 -296
  379. package/src/elements/agent-card.tsx +0 -67
  380. package/src/elements/artifact.tsx +0 -194
  381. package/src/elements/attachments.tsx +0 -140
  382. package/src/elements/audio-visualizer.tsx +0 -227
  383. package/src/elements/autoloader.ts +0 -89
  384. package/src/elements/avatar.tsx +0 -33
  385. package/src/elements/badge.tsx +0 -32
  386. package/src/elements/button.tsx +0 -149
  387. package/src/elements/card.tsx +0 -133
  388. package/src/elements/cards.tsx +0 -266
  389. package/src/elements/chain-of-thought.tsx +0 -141
  390. package/src/elements/chat-scope-picker.tsx +0 -65
  391. package/src/elements/chat-types.ts +0 -102
  392. package/src/elements/chat-workspace.tsx +0 -188
  393. package/src/elements/chat.tsx +0 -304
  394. package/src/elements/checkbox-group.tsx +0 -164
  395. package/src/elements/checkbox.tsx +0 -134
  396. package/src/elements/checkpoint.tsx +0 -47
  397. package/src/elements/choice.tsx +0 -100
  398. package/src/elements/coachmark.tsx +0 -90
  399. package/src/elements/code-block.tsx +0 -60
  400. package/src/elements/command.tsx +0 -197
  401. package/src/elements/compare.tsx +0 -111
  402. package/src/elements/compiled.css +0 -2
  403. package/src/elements/composer.tsx +0 -162
  404. package/src/elements/confirm-card.tsx +0 -76
  405. package/src/elements/context-meter.tsx +0 -107
  406. package/src/elements/conversation-item.tsx +0 -120
  407. package/src/elements/conversation-list.tsx +0 -203
  408. package/src/elements/css.ts +0 -5
  409. package/src/elements/default-input.tsx +0 -392
  410. package/src/elements/define-entry.ts +0 -12
  411. package/src/elements/define.tsx +0 -563
  412. package/src/elements/diagnostic-events.ts +0 -114
  413. package/src/elements/dialog.tsx +0 -145
  414. package/src/elements/disclosure.ts +0 -95
  415. package/src/elements/dock.tsx +0 -215
  416. package/src/elements/dropdown.tsx +0 -123
  417. package/src/elements/editable-label.tsx +0 -140
  418. package/src/elements/element-data-types.ts +0 -152
  419. package/src/elements/element-diagnostics.ts +0 -392
  420. package/src/elements/element-manifest.json +0 -354
  421. package/src/elements/element-nonscalar.json +0 -187
  422. package/src/elements/element-types.d.ts +0 -4353
  423. package/src/elements/embed.tsx +0 -35
  424. package/src/elements/empty.tsx +0 -29
  425. package/src/elements/feedback-bar.tsx +0 -66
  426. package/src/elements/file-tree.tsx +0 -62
  427. package/src/elements/file-upload.tsx +0 -44
  428. package/src/elements/form.tsx +0 -94
  429. package/src/elements/hover-card.tsx +0 -80
  430. package/src/elements/icon.tsx +0 -41
  431. package/src/elements/image.tsx +0 -32
  432. package/src/elements/input.tsx +0 -492
  433. package/src/elements/kbd.tsx +0 -45
  434. package/src/elements/link-preview.tsx +0 -34
  435. package/src/elements/loader.tsx +0 -25
  436. package/src/elements/markdown.tsx +0 -38
  437. package/src/elements/menu.tsx +0 -241
  438. package/src/elements/message-skills.tsx +0 -83
  439. package/src/elements/message.tsx +0 -361
  440. package/src/elements/model-switcher.tsx +0 -121
  441. package/src/elements/nav.tsx +0 -87
  442. package/src/elements/notice.tsx +0 -53
  443. package/src/elements/pane-grid.tsx +0 -111
  444. package/src/elements/pane-group.tsx +0 -119
  445. package/src/elements/pane.tsx +0 -129
  446. package/src/elements/popover.tsx +0 -81
  447. package/src/elements/progress-bar.tsx +0 -39
  448. package/src/elements/prompt-dock.tsx +0 -82
  449. package/src/elements/prompt-input.tsx +0 -240
  450. package/src/elements/prompt-suggestions.tsx +0 -113
  451. package/src/elements/radio-group.tsx +0 -129
  452. package/src/elements/reasoning.tsx +0 -71
  453. package/src/elements/register-impl.ts +0 -148
  454. package/src/elements/register.ts +0 -76
  455. package/src/elements/remote.tsx +0 -225
  456. package/src/elements/resizable.globals.d.ts +0 -36
  457. package/src/elements/resizable.tsx +0 -704
  458. package/src/elements/response-stream.tsx +0 -40
  459. package/src/elements/screen.tsx +0 -90
  460. package/src/elements/scroll-area.tsx +0 -33
  461. package/src/elements/scroll-button.tsx +0 -179
  462. package/src/elements/search.tsx +0 -185
  463. package/src/elements/segmented.tsx +0 -129
  464. package/src/elements/select.tsx +0 -165
  465. package/src/elements/separator.tsx +0 -36
  466. package/src/elements/setting-item.tsx +0 -50
  467. package/src/elements/settings-group.tsx +0 -37
  468. package/src/elements/skeleton.tsx +0 -45
  469. package/src/elements/slider.tsx +0 -171
  470. package/src/elements/slot-text.ts +0 -73
  471. package/src/elements/slots.ts +0 -787
  472. package/src/elements/source.tsx +0 -126
  473. package/src/elements/status.tsx +0 -42
  474. package/src/elements/switch.tsx +0 -118
  475. package/src/elements/tabs.tsx +0 -95
  476. package/src/elements/tasks.tsx +0 -103
  477. package/src/elements/text-shimmer.tsx +0 -28
  478. package/src/elements/thinking-bar.tsx +0 -43
  479. package/src/elements/thread.tsx +0 -139
  480. package/src/elements/toast.tsx +0 -106
  481. package/src/elements/tool.tsx +0 -78
  482. package/src/elements/tooltip.tsx +0 -85
  483. package/src/elements/validate-messages.ts +0 -76
  484. package/src/elements/voice-input.tsx +0 -100
  485. package/src/elements/voice-output.tsx +0 -82
  486. package/src/index.ts +0 -430
  487. package/src/primitives/audio-bands.ts +0 -174
  488. package/src/primitives/card-component-types.ts +0 -63
  489. package/src/primitives/card-contract.ts +0 -80
  490. package/src/primitives/card-data-types.ts +0 -361
  491. package/src/primitives/card-host.tsx +0 -37
  492. package/src/primitives/card-recovery.ts +0 -160
  493. package/src/primitives/card-registry.tsx +0 -78
  494. package/src/primitives/card-resolution.ts +0 -36
  495. package/src/primitives/card-routing.ts +0 -85
  496. package/src/primitives/card-schemas/artifact.schema.json +0 -92
  497. package/src/primitives/card-schemas/card-envelope.schema.json +0 -14
  498. package/src/primitives/card-schemas/card-event.schema.json +0 -12
  499. package/src/primitives/card-schemas/choice.schema.json +0 -75
  500. package/src/primitives/card-schemas/confirm.schema.json +0 -65
  501. package/src/primitives/card-schemas/embed.schema.json +0 -63
  502. package/src/primitives/card-schemas/form.result.schema.json +0 -7
  503. package/src/primitives/card-schemas/form.schema.json +0 -53
  504. package/src/primitives/card-schemas/link.schema.json +0 -54
  505. package/src/primitives/card-schemas/tasks.result.schema.json +0 -16
  506. package/src/primitives/card-schemas/tasks.schema.json +0 -83
  507. package/src/primitives/card-tags.ts +0 -61
  508. package/src/primitives/card-validate-cards.ts +0 -255
  509. package/src/primitives/card-validate-schemas.ts +0 -58
  510. package/src/primitives/card-validate.ts +0 -131
  511. package/src/primitives/chat-config.tsx +0 -103
  512. package/src/primitives/composer-model.ts +0 -49
  513. package/src/primitives/composer-triggers.ts +0 -35
  514. package/src/primitives/controllable.ts +0 -21
  515. package/src/primitives/conversation-store.ts +0 -248
  516. package/src/primitives/create-kai-chat.ts +0 -66
  517. package/src/primitives/create-tween.ts +0 -266
  518. package/src/primitives/embed-providers.ts +0 -254
  519. package/src/primitives/field-mask.ts +0 -256
  520. package/src/primitives/field-semantics.ts +0 -115
  521. package/src/primitives/highlighter.ts +0 -157
  522. package/src/primitives/input-mask.ts +0 -853
  523. package/src/primitives/link-preview.ts +0 -87
  524. package/src/primitives/message-feedback.ts +0 -124
  525. package/src/primitives/pdf-preview.ts +0 -121
  526. package/src/primitives/toast-store.ts +0 -338
  527. package/src/primitives/url-scheme-policy.ts +0 -70
  528. package/src/primitives/use-audio-analysis.ts +0 -340
  529. package/src/primitives/use-auto-resize.ts +0 -31
  530. package/src/primitives/use-resize-observer.ts +0 -45
  531. package/src/primitives/use-sequencer.ts +0 -72
  532. package/src/primitives/use-speech-recognition.ts +0 -146
  533. package/src/primitives/use-stick-to-bottom.ts +0 -75
  534. package/src/primitives/use-text-stream.ts +0 -143
  535. package/src/primitives/use-voice-recorder.ts +0 -62
  536. package/src/primitives/visualizer-sequences.ts +0 -350
  537. package/src/remote/host-embed.ts +0 -345
  538. package/src/remote/index.ts +0 -2
  539. package/src/remote/origin.ts +0 -30
  540. package/src/remote/provider-runtime.ts +0 -262
  541. package/src/remote/provider.ts +0 -2
  542. package/src/remote/validate.ts +0 -22
  543. package/src/remote/version.ts +0 -12
  544. package/src/remote/wire.ts +0 -48
  545. package/src/schemas/from-tool-call.ts +0 -151
  546. package/src/schemas/index.ts +0 -341
  547. package/src/schemas/provider-subsets.ts +0 -538
  548. package/src/schemas/registry.ts +0 -355
  549. package/src/schemas/tool-defs.ts +0 -793
  550. package/src/solid.ts +0 -173
  551. package/src/state/index.ts +0 -44
  552. package/src/state/messages.ts +0 -53
  553. package/src/state/mock.ts +0 -297
  554. package/src/state/parts.ts +0 -295
  555. package/src/state/persistence.ts +0 -180
  556. package/src/state/stream.ts +0 -246
  557. package/src/state/suggestions.ts +0 -9
  558. package/src/state/threads.ts +0 -142
  559. package/src/test-utils/fake-clock.ts +0 -88
  560. package/src/types.ts +0 -95
  561. package/src/ui/action-icons.ts +0 -53
  562. package/src/ui/agent-card.tsx +0 -218
  563. package/src/ui/avatar.tsx +0 -23
  564. package/src/ui/badge.tsx +0 -21
  565. package/src/ui/button.tsx +0 -49
  566. package/src/ui/card.tsx +0 -245
  567. package/src/ui/checkbox-group.tsx +0 -153
  568. package/src/ui/checkbox.tsx +0 -63
  569. package/src/ui/collapsible.tsx +0 -163
  570. package/src/ui/command.tsx +0 -103
  571. package/src/ui/dialog.tsx +0 -223
  572. package/src/ui/dock.tsx +0 -630
  573. package/src/ui/dropdown.tsx +0 -636
  574. package/src/ui/editable-label.tsx +0 -112
  575. package/src/ui/hover-card.tsx +0 -345
  576. package/src/ui/icon.tsx +0 -167
  577. package/src/ui/input.tsx +0 -391
  578. package/src/ui/kbd.tsx +0 -118
  579. package/src/ui/nav.tsx +0 -282
  580. package/src/ui/notice.tsx +0 -84
  581. package/src/ui/overlay.tsx +0 -277
  582. package/src/ui/pane-grid.tsx +0 -117
  583. package/src/ui/pane-group.tsx +0 -291
  584. package/src/ui/pane.tsx +0 -193
  585. package/src/ui/popover.tsx +0 -106
  586. package/src/ui/progress-bar.tsx +0 -83
  587. package/src/ui/prompt-dock.tsx +0 -145
  588. package/src/ui/radio.tsx +0 -150
  589. package/src/ui/resizable.tsx +0 -693
  590. package/src/ui/scroll-area.tsx +0 -26
  591. package/src/ui/segmented.tsx +0 -109
  592. package/src/ui/select.tsx +0 -168
  593. package/src/ui/separator.tsx +0 -10
  594. package/src/ui/settings-group.tsx +0 -67
  595. package/src/ui/skeleton.tsx +0 -73
  596. package/src/ui/slider.tsx +0 -178
  597. package/src/ui/status.tsx +0 -48
  598. package/src/ui/switch.tsx +0 -119
  599. package/src/ui/tabs.tsx +0 -168
  600. package/src/ui/textarea.tsx +0 -21
  601. package/src/ui/tooltip.tsx +0 -118
  602. package/src/utils/cn.ts +0 -30
  603. package/src/wire/chunk.ts +0 -248
  604. package/src/wire/consume.ts +0 -599
  605. package/src/wire/diagnostics.ts +0 -727
  606. package/src/wire/encode-probe.ts +0 -214
  607. package/src/wire/encode.ts +0 -947
  608. package/src/wire/files.ts +0 -342
  609. package/src/wire/formats/anthropic.ts +0 -274
  610. package/src/wire/formats/openai.ts +0 -228
  611. package/src/wire/index.ts +0 -95
  612. package/src/wire/media-types.ts +0 -344
  613. package/src/wire/read.ts +0 -396
  614. package/src/wire/sink-helpers.ts +0 -50
  615. package/src/wire/sse.ts +0 -153
@@ -1,853 +0,0 @@
1
- // src/primitives/input-mask.ts
2
- // Tier 2, the stateful half: one `HTMLInputElement` driven through the pure format engine
3
- // in `field-mask.ts`. Framework-agnostic -- no Solid, no DOM beyond the one input and its
4
- // document. Spec: docs/superpowers/specs/2026-08-24-form-field-formats-design.md
5
- // (§2 tier 2, §3, and the §5 improvement list, which is binding here).
6
- //
7
- // THE FORMATTED TEXT IS `input.value` (spec §2). There is no overlay and no ghost layer,
8
- // so the caret, selection, find-in-page and every mobile affordance are the browser's own.
9
- // What this module adds on top is: interception, normalization, a caret that never rests
10
- // inside a literal run, its own undo stack (a programmatic `.value` write destroys the
11
- // native one), and a stated clipboard policy.
12
- //
13
- // Four rules hold this file together; each is one of the §5 improvements and each is the
14
- // reason some obvious-looking shortcut is not taken:
15
- // 1. `commit` is the ONLY writer of `el.value`, the selection, the undo stacks and the
16
- // callbacks (§5.5). The reference this was derived from repeated that sequence in five
17
- // places and they had already drifted apart.
18
- // 2. `beforeinput` is the interception point WHERE IT IS CANCELABLE; where it is not, the
19
- // longest-common-prefix/suffix diff in `input` reconciles whatever the browser did
20
- // (§5.1). Both paths end in `applyEdit` -> `commit`, so there is one edit semantics.
21
- // 3. Between `compositionstart` and `compositionend` this module does NOTHING: no cancel,
22
- // no `.value` write, no caret move, no clamp (§5.2). Cancelling mid-composition breaks
23
- // the composition outright, and Android word suggestion is far more common in these
24
- // fields than CJK input.
25
- // 4. `.value` is never shadowed with `Object.defineProperty` (§5.8). The canonical value
26
- // is read through `getCanonicalValue()`; the element facade will publish it with
27
- // `setFormValue()`.
28
- //
29
- // ONE KNOWN IMPRECISION, recorded rather than papered over. An undo entry's selection is
30
- // read from the element at commit time. On the `beforeinput` path that is exactly right --
31
- // the event was canceled, so the caret has not moved yet. On the `input` diff fallback the
32
- // browser has ALREADY moved it, so the entry stores the post-edit caret against the
33
- // pre-edit text: undoing a browser-driven edit restores the correct text with a caret that
34
- // is merely plausible. Recovering the true one means caching the selection from
35
- // `selectionchange` and trusting that it fires before `input`, which is browser-timing
36
- // dependent and unverifiable in jsdom -- a guess dressed as a fix. The caret is clamped, so
37
- // it is never out of range. Task 6 can measure the real ordering and decide.
38
- import {
39
- compileMask,
40
- formatForDisplay,
41
- formatRaw,
42
- formattedToRawIndex,
43
- normalizeToRaw,
44
- rawFromFormatted,
45
- rawToFormattedIndex,
46
- type CaseMode,
47
- type MaskPattern,
48
- type RejectReason,
49
- } from './field-mask';
50
- import { canonicalize, type FieldSemanticType } from './field-semantics';
51
-
52
- /** What a copy or cut puts on the clipboard. A stated policy the consumer selects, NOT a
53
- * consequence of `obscure`: copying bullets is not a security control -- the
54
- * value is in the page -- and whether a card number may be copied at all is an app-layer
55
- * decision (CLAUDE.md: the kit decides HOW, the app decides WHETHER). */
56
- export type CopyPolicy = 'formatted' | 'canonical' | 'obscured' | 'blocked';
57
-
58
- /** Why an edit or a re-configuration refused, or partly refused, the content it was given.
59
- *
60
- * A superset of `field-mask.ts`'s `RejectReason`, widened HERE rather than there because
61
- * the extra member is not an engine verdict: the engine's three describe one edit failing
62
- * against one pattern, while `format-change-clipped` describes the PATTERN moving out from
63
- * under a value that was already accepted. Widening keeps every consumer that narrows on
64
- * the engine's three compiling unchanged. */
65
- export type InputMaskRejectReason = RejectReason | 'format-change-clipped';
66
-
67
- export interface InputMaskOptions {
68
- format: string;
69
- guide?: string;
70
- semantic?: FieldSemanticType;
71
- caseMode?: CaseMode;
72
- copyPolicy?: CopyPolicy;
73
- /** Tier 3. Wired -- it selects the default copy policy -- but does NOT yet transform the
74
- * display; that lands with the obscured rendering in tier 3 (task 10). */
75
- obscure?: boolean;
76
- initialValue?: string;
77
- onInput?: (detail: { canonical: string; formatted: string }) => void;
78
- onReject?: (detail: { reason: InputMaskRejectReason; data: string }) => void;
79
- }
80
-
81
- export interface InputMask {
82
- /** Accepts the canonical form or the formatted form; both normalize to the same value. */
83
- setValue(value: string): void;
84
- getRawValue(): string;
85
- getCanonicalValue(): string;
86
- getFormattedValue(): string;
87
- setObscure(on: boolean): void;
88
- /** Re-compiles and preserves the value, re-fitted to the new pattern. */
89
- update(next: Partial<InputMaskOptions>): void;
90
- detach(): void;
91
- }
92
-
93
- /** Undo history is capped (spec §5.6). A long-lived field otherwise grows an unbounded
94
- * array; dropping from the bottom keeps the recent history, which is the useful end. */
95
- const UNDO_LIMIT = 200;
96
-
97
- /** The obscured glyph, U+2022. Tier 3's display uses the same one. */
98
- const BULLET = '•';
99
-
100
- /** One undo entry: the state to restore, INCLUDING the selection the user had when the
101
- * edit that superseded it began (spec §5.6). Restoring text without the caret is what
102
- * makes a custom undo stack feel broken.
103
- *
104
- * `pattern` is a fourth field beyond the three the spec names, and it is what lets the
105
- * history SURVIVE an `update()`. A formatted string can only be decoded by the pattern
106
- * that produced it -- `V-123` under `V-***` is raw `123`, and under `#####` it is raw `123`
107
- * read from different positions -- so an entry restored under a pattern that did not write
108
- * it is garbage. Carrying the pattern lets `restore` decode with the right one and then
109
- * re-fit; without it the only safe thing to do on a format change is throw the whole stack
110
- * away, which is itself a silent drop of the user's history. */
111
- interface UndoEntry {
112
- readonly formatted: string;
113
- readonly pattern: MaskPattern;
114
- readonly selStart: number;
115
- readonly selEnd: number;
116
- }
117
-
118
- /** How a commit affects the undo history.
119
- * - `run` an insertion that may coalesce into the run already open.
120
- * - `break` an edit that ends the run and always gets its own entry.
121
- * - `none` a restore or a re-render: no history, and the redo stack survives. */
122
- type UndoMode = 'break' | 'run' | 'none';
123
-
124
- /** `inputType`s that insert. `insertText` is the one that coalesces into a typing run;
125
- * the rest are bulk edits and each gets its own undo entry. */
126
- const INSERT_TYPES = new Set([
127
- 'insertText',
128
- 'insertFromPaste',
129
- 'insertFromPasteAsQuotation',
130
- 'insertFromDrop',
131
- 'insertReplacementText',
132
- 'insertFromYank',
133
- 'insertTranspose',
134
- ]);
135
-
136
- /** `inputType`s that delete. `deleteByCut` is DELIBERATELY absent: the `cut` listener
137
- * cancels the cut and performs the deletion itself, so this event never arrives for a
138
- * cut -- and if it did, the range would already be gone and a second delete would eat a
139
- * character the user never selected. */
140
- const DELETE_TYPES = new Set([
141
- 'deleteContent',
142
- 'deleteContentBackward',
143
- 'deleteContentForward',
144
- 'deleteWordBackward',
145
- 'deleteWordForward',
146
- 'deleteSoftLineBackward',
147
- 'deleteSoftLineForward',
148
- 'deleteHardLineBackward',
149
- 'deleteHardLineForward',
150
- 'deleteByDrag',
151
- ]);
152
-
153
- /** Caret-moving keys break an open typing run (spec §5.6). Pointer-driven moves break it
154
- * through the `mousedown` listener. */
155
- const NAV_KEYS = new Set([
156
- 'ArrowLeft',
157
- 'ArrowRight',
158
- 'ArrowUp',
159
- 'ArrowDown',
160
- 'Home',
161
- 'End',
162
- 'PageUp',
163
- 'PageDown',
164
- ]);
165
-
166
- export function createInputMask(el: HTMLInputElement, options: InputMaskOptions): InputMask {
167
- let opts: InputMaskOptions = { ...options };
168
- let pattern: MaskPattern = compileMask(opts.format, opts.guide);
169
- let hasGuide = opts.guide !== undefined;
170
- let caseMode: CaseMode = opts.caseMode ?? 'preserve';
171
- let semantic: FieldSemanticType = opts.semantic ?? 'custom';
172
- let obscure = opts.obscure ?? false;
173
-
174
- /** The formatted text, authoritative and equal to `el.value` outside a composition. */
175
- let formatted = '';
176
- /** The pattern that produced `formatted`. Diverges from `pattern` for exactly the span of
177
- * an `update()`, which is why an undo entry pushed during one must be stamped with THIS
178
- * and not with the pattern that has already been swapped in. */
179
- let formattedPattern: MaskPattern = pattern;
180
- /** The fill-position characters, authoritative. Kept beside `formatted` rather than
181
- * re-read from it on every access: a consumer guide whose characters happen to satisfy
182
- * their own position's class (`@@@` guided `abc`) would otherwise read back as content. */
183
- let raw = '';
184
-
185
- const undoStack: UndoEntry[] = [];
186
- const redoStack: UndoEntry[] = [];
187
- let runOpen = false;
188
-
189
- let composing = false;
190
- /** Set while THIS module writes the selection, so the `selectionchange` clamp does not
191
- * react to its own work. Not a timer and not a race: it is set and cleared around one
192
- * synchronous call. */
193
- let settingSelection = false;
194
- let detached = false;
195
- /** A consumer write (`setValue` / `update`) that arrived mid-composition, held until
196
- * `compositionend`. Only the latest survives: an earlier one has already been superseded
197
- * by its own author, and replaying both would flash an intermediate value. */
198
- let pendingWrite: (() => void) | null = null;
199
-
200
- /** With a guide the field is always `format.length` long and unfilled positions show the
201
- * guide; without one it shows only up to the last typed character (spec §2). */
202
- const display = (value: string): string =>
203
- hasGuide ? formatForDisplay(pattern, value) : formatRaw(pattern, value);
204
-
205
- /** A caret offset in `formatted` -> a raw index, clamped to what is actually typed. */
206
- const toRaw = (pos: number): number =>
207
- Math.min(formattedToRawIndex(pattern, formatted, pos), raw.length);
208
-
209
- function selectionRaw(): [number, number] {
210
- const start = el.selectionStart ?? formatted.length;
211
- const end = el.selectionEnd ?? formatted.length;
212
- return [toRaw(start), toRaw(Math.max(start, end))];
213
- }
214
-
215
- function setSelection(start: number, end: number): void {
216
- settingSelection = true;
217
- try {
218
- el.setSelectionRange(start, end);
219
- } catch {
220
- // `setSelectionRange` throws on input types that do not support selection
221
- // (`number`, `email`, ...). The value write already landed; a caret is presentation,
222
- // not data, so there is nothing here to report.
223
- } finally {
224
- settingSelection = false;
225
- }
226
- }
227
-
228
- function reject(reason: InputMaskRejectReason, data: string): void {
229
- // Loud, always (spec §5.3). The silent `preventDefault` this replaces is the exact
230
- // shape CLAUDE.md calls the default-wrong choice: a decision made while withholding
231
- // the information that it happened.
232
- opts.onReject?.({ reason, data });
233
- }
234
-
235
- // ---------------------------------------------------------------------------------
236
- // The single commit path (§5.5). Nothing else in this file assigns `el.value`, touches
237
- // the selection, touches the undo stacks or calls a consumer callback.
238
- // ---------------------------------------------------------------------------------
239
-
240
- /** `raw` travels in the options bag rather than being re-derived from `nextFormatted`
241
- * for the reason given at the `raw` declaration: `rawFromFormatted` over a display
242
- * string trusts the guide. Every caller already has the raw it just computed. */
243
- function commit(
244
- nextFormatted: string,
245
- caret: number,
246
- write: { undo: UndoMode; raw: string; notify?: boolean; selectionEnd?: number },
247
- ): void {
248
- if (write.undo !== 'none') {
249
- if (write.undo === 'break' || !runOpen) {
250
- undoStack.push({
251
- formatted,
252
- pattern: formattedPattern,
253
- selStart: el.selectionStart ?? formatted.length,
254
- selEnd: el.selectionEnd ?? formatted.length,
255
- });
256
- if (undoStack.length > UNDO_LIMIT) undoStack.shift();
257
- }
258
- runOpen = write.undo === 'run';
259
- redoStack.length = 0;
260
- }
261
-
262
- formatted = nextFormatted;
263
- formattedPattern = pattern;
264
- raw = write.raw;
265
- el.value = nextFormatted; // native setter; no descriptor shadowing (§5.8)
266
-
267
- const start = Math.max(0, Math.min(caret, nextFormatted.length));
268
- const end = Math.max(start, Math.min(write.selectionEnd ?? caret, nextFormatted.length));
269
- setSelection(start, end);
270
-
271
- if (write.notify !== false) opts.onInput?.({ canonical: getCanonicalValue(), formatted });
272
- }
273
-
274
- /** The raw-space adapter every edit goes through: it is what guarantees the formatted
275
- * text and the caret are always derived from the same raw. */
276
- function commitRaw(nextRaw: string, rawCaret: number, write: { undo: UndoMode; notify?: boolean }): void {
277
- const next = display(nextRaw);
278
- commit(next, rawToFormattedIndex(pattern, next, rawCaret), { ...write, raw: nextRaw });
279
- }
280
-
281
- // ---------------------------------------------------------------------------------
282
- // Edit application
283
- // ---------------------------------------------------------------------------------
284
-
285
- /** A copy of the pattern with extra permissive fill positions, used ONLY to answer "was
286
- * anything actually lost to capacity?".
287
- *
288
- * Without it, `absorbed.length < inserted.length` cannot tell an overflow from an
289
- * absorbed separator: `chg 4821` is eight characters landing in seven fill positions
290
- * with nothing lost, and reporting `over-capacity` for it would be a false alarm on the
291
- * single most common paste in the target field family. Re-normalizing against a wider
292
- * pattern answers it exactly. Returns `null` when the format is already at the engine's
293
- * length cap, in which case the check is skipped rather than guessed. */
294
- function widerPattern(extra: number): MaskPattern | null {
295
- try {
296
- return compileMask(pattern.format + '@'.repeat(extra));
297
- } catch {
298
- return null;
299
- }
300
- }
301
-
302
- /** Replace raw `[rawStart, rawEnd)` with whatever of `inserted` the mask accepts.
303
- *
304
- * The whole edit is expressed as ONE normalization over `prefix + inserted + tail`,
305
- * which is what keeps typing, pasting, autofill and the diff fallback on identical
306
- * semantics. `formatRaw` re-inserts the literals in front of the insertion point so
307
- * `normalizeToRaw` -- which walks the FORMAT, and is the literal-aware normalizer that
308
- * fixes spec §5.7 -- sees a string aligned to the pattern from index 0. That is why
309
- * pasting `V-123` under `V-***` yields `V-123` and not `V-V12`.
310
- *
311
- * Returns whether it committed. */
312
- function applyEdit(rawStart: number, rawEnd: number, inserted: string, kind: 'insert' | 'bulk'): boolean {
313
- const room = pattern.capacity - (raw.length - (rawEnd - rawStart));
314
- if (inserted.length > 0 && room <= 0) {
315
- reject('full', inserted);
316
- return false;
317
- }
318
-
319
- const prefix = raw.slice(0, rawStart);
320
- const prefixText = formatRaw(pattern, prefix);
321
- const withInsert = normalizeToRaw(pattern, prefixText + inserted, caseMode);
322
- const absorbed = withInsert.slice(prefix.length);
323
- const tail = raw.slice(rawEnd);
324
- const nextRaw = normalizeToRaw(pattern, formatRaw(pattern, withInsert) + tail, caseMode);
325
-
326
- if (inserted.length > 0 && absorbed.length === 0) {
327
- reject('wrong-class', inserted);
328
- return false;
329
- }
330
- if (nextRaw === raw) return false; // a delete with nothing to delete: no edit, no noise
331
-
332
- commitRaw(nextRaw, prefix.length + absorbed.length, {
333
- undo: kind === 'insert' ? 'run' : 'break',
334
- notify: true,
335
- });
336
-
337
- // Reported AFTER the commit, and deliberately alongside it rather than instead of it.
338
- // `full` and `wrong-class` refuse the whole edit and leave the text unchanged, which is
339
- // the §5.3 contract. `over-capacity` is the clip that `field-mask.ts` documents the
340
- // CALLER as responsible for reporting ("input past the last fill position is clipped --
341
- // the caller compares lengths"): refusing an entire paste for being one character long
342
- // is worse than accepting what fits and saying so. `data` is the input that was
343
- // refused or partly refused, not a computed remainder.
344
- const lostTail = nextRaw.length < withInsert.length + tail.length;
345
- const lostInsert =
346
- withInsert.length === pattern.capacity &&
347
- absorbed.length < inserted.length &&
348
- overflowed(prefixText, inserted, withInsert.length);
349
- if (lostTail || lostInsert) reject('over-capacity', inserted);
350
- return true;
351
- }
352
-
353
- function overflowed(prefixText: string, inserted: string, absorbedTotal: number): boolean {
354
- const wider = widerPattern(inserted.length);
355
- if (wider === null) return false;
356
- return normalizeToRaw(wider, prefixText + inserted, caseMode).length > absorbedTotal;
357
- }
358
-
359
- /** The §5.1 fallback: the browser already mutated the field, so work out what it did.
360
- * Longest common prefix/suffix, caret-independent, and robust against alphanumeric
361
- * literals -- the one idea carried over from the reference unchanged. */
362
- function reconcile(): void {
363
- const current = el.value;
364
- const max = Math.min(current.length, formatted.length);
365
- let pre = 0;
366
- while (pre < max && current[pre] === formatted[pre]) pre += 1;
367
- let suf = 0;
368
- while (
369
- suf < max - pre &&
370
- current[current.length - 1 - suf] === formatted[formatted.length - 1 - suf]
371
- ) {
372
- suf += 1;
373
- }
374
-
375
- const insertedText = current.slice(pre, current.length - suf);
376
- const start = toRaw(pre);
377
- const end = Math.max(start, toRaw(formatted.length - suf));
378
- applyEdit(start, end, insertedText, 'bulk');
379
-
380
- if (el.value !== formatted) {
381
- // The edit was refused (or was a no-op). The browser's text is still on screen, so
382
- // put the authoritative text back -- a refusal must leave the field unchanged, and
383
- // "unchanged" here means undoing the browser, not leaving its version in place.
384
- commit(formatted, rawToFormattedIndex(pattern, formatted, raw.length), {
385
- undo: 'none',
386
- raw,
387
- notify: false,
388
- });
389
- }
390
- }
391
-
392
- // ---------------------------------------------------------------------------------
393
- // Undo / redo (§5.6)
394
- // ---------------------------------------------------------------------------------
395
-
396
- function snapshot(): UndoEntry {
397
- return {
398
- formatted,
399
- pattern: formattedPattern,
400
- selStart: el.selectionStart ?? formatted.length,
401
- selEnd: el.selectionEnd ?? formatted.length,
402
- };
403
- }
404
-
405
- function restore(entry: UndoEntry): void {
406
- runOpen = false;
407
- if (entry.pattern === pattern) {
408
- commit(entry.formatted, entry.selStart, {
409
- undo: 'none',
410
- selectionEnd: entry.selEnd,
411
- raw: rawFromFormatted(pattern, entry.formatted),
412
- notify: true,
413
- });
414
- return;
415
- }
416
- // Recorded under an older pattern (an `update()` happened since). Decode with the
417
- // pattern that wrote it, then re-fit through the current one. The FORMATTED text is
418
- // what gets re-normalized, never the bare raw: `normalizeToRaw` is not idempotent on
419
- // raw when a fill character happens to equal a leading literal -- `V-***` holding
420
- // `V12` would re-normalize to `12` and lose a character on every undo. With the
421
- // literals present (`V-V12`) the positional walk consumes the leading `V` as the
422
- // literal it is.
423
- //
424
- // Note what this canNOT do: if the new pattern is narrower, the re-fit clips again, so
425
- // undo does not resurrect what `update()` already reported as clipped. Undo reverses an
426
- // EDIT; a format change is not one. That is why the loud report below is the half that
427
- // actually protects the value.
428
- const decoded = rawFromFormatted(entry.pattern, entry.formatted);
429
- const refit = normalizeToRaw(pattern, formatRaw(entry.pattern, decoded), caseMode);
430
- commitRaw(refit, refit.length, { undo: 'none', notify: true });
431
- }
432
-
433
- function undo(): void {
434
- const entry = undoStack.pop();
435
- if (entry === undefined) return;
436
- redoStack.push(snapshot());
437
- restore(entry);
438
- }
439
-
440
- function redo(): void {
441
- const entry = redoStack.pop();
442
- if (entry === undefined) return;
443
- undoStack.push(snapshot());
444
- restore(entry);
445
- }
446
-
447
- // ---------------------------------------------------------------------------------
448
- // Clipboard (§5.10)
449
- // ---------------------------------------------------------------------------------
450
-
451
- /** Bullets at the FILLED `*` positions only. `#` and `@` positions, literals and guide
452
- * characters stay revealed -- which is what makes `**** **** **** ####` mean "show the
453
- * last four" with no `showLast` prop (spec §2 tier 3). */
454
- function obscured(text: string): string {
455
- const chars = text.split('');
456
- for (let i = 0; i < pattern.fillIndexes.length && i < raw.length; i += 1) {
457
- const index = pattern.fillIndexes[i]!;
458
- if (pattern.format[index] === '*' && index < chars.length) chars[index] = BULLET;
459
- }
460
- return chars.join('');
461
- }
462
-
463
- function clipboardText(): string {
464
- const start = el.selectionStart ?? 0;
465
- const end = el.selectionEnd ?? 0;
466
- switch (copyPolicy()) {
467
- case 'blocked':
468
- return '';
469
- case 'formatted':
470
- return formatted.slice(start, end);
471
- case 'obscured':
472
- return obscured(formatted).slice(start, end);
473
- case 'canonical':
474
- default: {
475
- const [rawStart, rawEnd] = selectionRaw();
476
- // A whole-field selection copies the canonical value -- the thing a consumer means
477
- // by "copy this phone number". A partial selection has no canonical form of its
478
- // own (its literals depend on where it sat), so it copies the characters it covers.
479
- if (rawStart === 0 && rawEnd === raw.length) {
480
- return canonicalize(pattern, formatRaw(pattern, raw), semantic);
481
- }
482
- return raw.slice(rawStart, rawEnd);
483
- }
484
- }
485
- }
486
-
487
- /** Explicit option wins; otherwise `obscure` picks the default (spec §5.10). */
488
- function copyPolicy(): CopyPolicy {
489
- return opts.copyPolicy ?? (obscure ? 'obscured' : 'canonical');
490
- }
491
-
492
- // ---------------------------------------------------------------------------------
493
- // Listeners
494
- // ---------------------------------------------------------------------------------
495
-
496
- function onBeforeInput(event: Event): void {
497
- if (detached || composing) return;
498
- const e = event as InputEvent;
499
- // Not cancelable -- composition on Android, several IMEs. Do nothing here and let the
500
- // diff in `input` absorb whatever the browser does (§5.1).
501
- if (!e.cancelable) return;
502
- const inputType = e.inputType;
503
-
504
- if (INSERT_TYPES.has(inputType)) {
505
- e.preventDefault();
506
- const data = e.data ?? e.dataTransfer?.getData('text/plain') ?? '';
507
- const [start, end] = selectionRaw();
508
- applyEdit(start, end, data, inputType === 'insertText' ? 'insert' : 'bulk');
509
- return;
510
- }
511
-
512
- if (DELETE_TYPES.has(inputType)) {
513
- e.preventDefault();
514
- let [start, end] = selectionRaw();
515
- if (start === end) {
516
- // A DOM selection that maps to an EMPTY raw range is a selection over literals
517
- // only. It is not a collapsed caret, and running the directional branch on it
518
- // destroys a character the user did not select: selecting exactly the `-` of
519
- // `555-123-4567` and pressing Backspace would eat the `5` in front of it.
520
- // A literal is not content, so there is nothing to delete and nothing to report --
521
- // no decision is being withheld, the field visibly does not change.
522
- if ((el.selectionStart ?? 0) !== (el.selectionEnd ?? 0)) return;
523
- const backward = inputType.endsWith('Backward');
524
- // A masked field has no words and no lines, so the word/line deletes take the whole
525
- // side of the caret. Stated, not silent: the alternative is pretending `4821` is a
526
- // word, which is a guess about a format the consumer chose.
527
- const wide = /^delete(Word|SoftLine|HardLine)/.test(inputType);
528
- if (wide) {
529
- if (backward) start = 0;
530
- else end = raw.length;
531
- } else if (backward) {
532
- if (start === 0) return;
533
- start -= 1;
534
- } else {
535
- if (end >= raw.length) return;
536
- end += 1;
537
- }
538
- }
539
- applyEdit(start, end, '', 'bulk');
540
- return;
541
- }
542
-
543
- if (inputType === 'historyUndo') {
544
- e.preventDefault();
545
- undo();
546
- return;
547
- }
548
- if (inputType === 'historyRedo') {
549
- e.preventDefault();
550
- redo();
551
- }
552
- // Anything else is the browser's: it happens, and `input` reconciles it.
553
- }
554
-
555
- function onInputEvent(): void {
556
- if (detached || composing) return;
557
- if (el.value === formatted) return; // our own commit, or a genuine no-op
558
- reconcile();
559
- }
560
-
561
- function onCompositionStart(): void {
562
- if (detached) return;
563
- composing = true;
564
- }
565
-
566
- function onCompositionEnd(): void {
567
- if (detached) return;
568
- composing = false;
569
- // A consumer write that arrived mid-composition wins over the composed text: it is the
570
- // later statement of intent, and it is the value the consumer's own model now holds.
571
- const pending = pendingWrite;
572
- pendingWrite = null;
573
- if (pending !== null) {
574
- pending();
575
- return;
576
- }
577
- // Otherwise exactly one reconciliation, here (§5.2). Every `input` that arrived during
578
- // the composition was ignored on purpose.
579
- if (el.value !== formatted) reconcile();
580
- }
581
-
582
- /** Run a consumer write now, or hold it until the composition ends (§5.2 applied to the
583
- * masker's own writes: a framework re-render calling `setValue` mid-composition is the
584
- * classic controlled-input IME bug, and rewriting `.value` there kills the composition). */
585
- function writeOrDefer(apply: () => void): void {
586
- if (composing) {
587
- pendingWrite = apply;
588
- return;
589
- }
590
- apply();
591
- }
592
-
593
- function onKeyDown(event: Event): void {
594
- if (detached || composing) return;
595
- const e = event as KeyboardEvent;
596
- const mod = e.ctrlKey || e.metaKey;
597
- const key = e.key.toLowerCase();
598
- if (mod && key === 'z') {
599
- e.preventDefault();
600
- if (e.shiftKey) redo();
601
- else undo();
602
- return;
603
- }
604
- if (mod && key === 'y') {
605
- e.preventDefault();
606
- redo();
607
- return;
608
- }
609
- if (NAV_KEYS.has(e.key)) runOpen = false;
610
- }
611
-
612
- function onMouseDown(): void {
613
- runOpen = false;
614
- }
615
-
616
- function onBlur(): void {
617
- runOpen = false;
618
- }
619
-
620
- /** The caret window: `[floor, frontier]`.
621
- *
622
- * FLOOR is the first fill position, so the caret never rests inside a leading literal
623
- * run -- with `CHG-####` the field focuses at `CHG-|####` and Home, ArrowLeft and a click
624
- * into the prefix all land there. On an UNGUIDED empty field the floor collapses to 0,
625
- * because there is no rendered prefix to be trapped behind until a character exists;
626
- * `rawToFormattedIndex` clamps to the text that exists, which is what makes that fall out
627
- * rather than needing a special case.
628
- *
629
- * FRONTIER is one past the last filled character (`12/23/4|yyy`), so a click into the
630
- * unfilled guide tail snaps back and End stops at the content rather than at the end of
631
- * the template. Between the two, every position is legal and is left completely alone. */
632
- function caretWindow(): [floor: number, frontier: number] {
633
- return [
634
- rawToFormattedIndex(pattern, formatted, 0),
635
- rawToFormattedIndex(pattern, formatted, raw.length),
636
- ];
637
- }
638
-
639
- /** ONE clamp function, idempotent, NO TIMERS (§5.9) -- but four triggers, and the trigger
640
- * list is MEASURED, not assumed. §5.9's objection to the reference was the racing
641
- * `mousedown`+rAF / `mouseup`+`setTimeout` pair and the keyboard-driven selection it
642
- * missed; it is not an objection to listening for more than one event, and an earlier
643
- * version of this comment asserted that `selectionchange` alone covered everything. It
644
- * does not. Measured in real Chromium (tests/e2e/input-mask-ivp.spec.ts, scenarios 9-10),
645
- * logging every candidate event with the offset it saw:
646
- *
647
- * Home -> ["el:keyup@0"]
648
- * click -> ["el:selectionchange@4", "doc:selectionchange@4", "el:mouseup@0", "el:click@0"]
649
- *
650
- * Two facts fall out, and both were live defects the owner hit on a guided field:
651
- * - A KEYBOARD caret move fires NO `selectionchange` whatsoever. Home, End and the
652
- * arrows reached the clamp on no engine, which is exactly "I could only go back to
653
- * the start of ####" failing to hold. `keyup` is the event that does fire.
654
- * - A CLICK fires `selectionchange` early, and then the browser applies its own
655
- * pixel-derived offset AFTER it -- note the `@4` from our clamp being replaced by the
656
- * `@0` visible at `mouseup`. Clamping only on `selectionchange` gets overwritten by
657
- * the very gesture it was reacting to. `mouseup` is where the offset is final.
658
- *
659
- * So: `selectionchange` (the general case), `keyup` (keyboard), `mouseup` (pointer), and
660
- * `focus` -- the last because a selection that does not CHANGE fires nothing at all, so a
661
- * field entered while its caret is already at 0 would otherwise render with the caret
662
- * inside the literal prefix and never get a chance to be corrected. Every trigger is the
663
- * same idempotent call that early-returns when the selection is already legal, so firing
664
- * several times for one gesture costs a comparison and changes nothing. */
665
- /** "Is the caret in THIS field?" -- asked of the element's own root, never of the
666
- * document.
667
- *
668
- * `document.activeElement` RETARGETS: for a focused node inside a shadow tree it reports
669
- * the OUTERMOST host, so on any element this kit actually ships the answer is `<kai-chat>`
670
- * and never the input. `el.ownerDocument.activeElement !== el` was therefore true on every
671
- * trigger, and the whole clamp early-returned -- the caret discipline was dead in the
672
- * ops-console app (input at `kai-chat` shadow -> card -> `kai-form` shadow) while passing
673
- * in Storybook, whose story mounts the input into a FLAT document where the two agree.
674
- * Measured in that app on the dist build (m11-diagnose.mjs), focusing the `CHG-####`
675
- * field:
676
- *
677
- * el:focus docActiveIsEl=false docActive=kai-chat rootActiveIsEl=true
678
- * el:mouseup docActiveIsEl=false docActive=kai-chat rootActiveIsEl=true
679
- * doc:selectionchange docActiveIsEl=false docActive=kai-chat rootActiveIsEl=true
680
- *
681
- * All four triggers FIRE at the right offsets through two shadow roots -- the trigger
682
- * table above holds unchanged; it was only this guard that was wrong. `getRootNode()` is
683
- * read per call rather than captured, because the element can be moved between roots, and
684
- * it degrades correctly: in a flat document the root IS the document, and while detached
685
- * the root is a fragment with no `activeElement`, which reads as not focused. */
686
- function isFocused(): boolean {
687
- const root = el.getRootNode() as Document | ShadowRoot;
688
- return root.activeElement === el;
689
- }
690
-
691
- function clampSelection(): void {
692
- if (detached || composing || settingSelection) return;
693
- if (!isFocused()) return;
694
- const start = el.selectionStart;
695
- const end = el.selectionEnd;
696
- if (start === null || end === null) return;
697
-
698
- const [floor, frontier] = caretWindow();
699
-
700
- let nextStart = start;
701
- let nextEnd = end;
702
- if (start === end) {
703
- nextStart = nextEnd = Math.max(floor, Math.min(start, frontier));
704
- } else {
705
- // A range obeys the SAME window at both ends. Clamping only the end was the earlier
706
- // reading, and it let Ctrl-A leave the anchor at 0 -- visibly selecting a literal
707
- // prefix that is not content and cannot be edited. Collapsing to the window keeps
708
- // select-all meaning "all of the value": the raw range is unchanged, so a canonical
709
- // copy of a fully-selected field still yields the whole value, literals included.
710
- nextEnd = Math.min(end, frontier);
711
- nextStart = Math.min(Math.max(start, floor), nextEnd);
712
- }
713
- if (nextStart === start && nextEnd === end) return; // already valid: do nothing at all
714
- setSelection(nextStart, nextEnd);
715
- }
716
-
717
- function onCopy(event: Event): void {
718
- if (detached) return;
719
- const e = event as ClipboardEvent;
720
- e.preventDefault();
721
- e.clipboardData?.setData('text/plain', clipboardText());
722
- }
723
-
724
- function onCut(event: Event): void {
725
- if (detached) return;
726
- const e = event as ClipboardEvent;
727
- e.preventDefault();
728
- e.clipboardData?.setData('text/plain', clipboardText());
729
- const [start, end] = selectionRaw();
730
- if (start === end) return;
731
- runOpen = false;
732
- applyEdit(start, end, '', 'bulk');
733
- }
734
-
735
- function getCanonicalValue(): string {
736
- // `formatRaw`, not the display string: a guide character must never be read as content.
737
- return canonicalize(pattern, formatRaw(pattern, raw), semantic);
738
- }
739
-
740
- const elementListeners: Array<[string, EventListener]> = [
741
- ['beforeinput', onBeforeInput],
742
- ['input', onInputEvent],
743
- ['compositionstart', onCompositionStart],
744
- // `compositionupdate` is deliberately NOT bound, though §5.2 names it: `composing` is
745
- // already true for the whole window, so a handler would have nothing to do. Binding one
746
- // to "keep the set complete" would be an empty listener implying a hook that is not there.
747
- ['compositionend', onCompositionEnd],
748
- ['keydown', onKeyDown],
749
- ['mousedown', onMouseDown],
750
- // The three non-`selectionchange` clamp triggers. See `clampSelection` for the measured
751
- // Chromium event logs that make each of them necessary rather than defensive.
752
- ['keyup', clampSelection],
753
- ['mouseup', clampSelection],
754
- ['focus', clampSelection],
755
- ['blur', onBlur],
756
- ['copy', onCopy],
757
- ['cut', onCut],
758
- ];
759
- const doc = el.ownerDocument;
760
-
761
- for (const [kind, handler] of elementListeners) el.addEventListener(kind, handler);
762
- // `selectionchange` fires on the DOCUMENT for `<input>` in every browser this kit
763
- // targets; the element-targeted version is newer and not yet universal.
764
- doc.addEventListener('selectionchange', clampSelection);
765
-
766
- {
767
- const seed = normalizeToRaw(pattern, opts.initialValue ?? el.value, caseMode);
768
- // No undo entry and no callback: attaching is not an edit the consumer made.
769
- commitRaw(seed, seed.length, { undo: 'none', notify: false });
770
- }
771
-
772
- return {
773
- setValue(value: string): void {
774
- writeOrDefer(() => {
775
- const next = normalizeToRaw(pattern, value, caseMode);
776
- // A bulk replacement, so it breaks the run and gets its own undo entry -- but it
777
- // does NOT notify: a controlled widget that wrote this value would echo itself
778
- // forever. (`update()` below is the opposite case, and for a stated reason.)
779
- commitRaw(next, next.length, { undo: 'break', notify: false });
780
- });
781
- },
782
-
783
- getRawValue: () => raw,
784
- getCanonicalValue,
785
- getFormattedValue: () => formatted,
786
-
787
- setObscure(on: boolean): void {
788
- obscure = on;
789
- // Tier 3 (task 10) makes this change the rendered text. Today it selects the default
790
- // copy policy and nothing else, which is stated on the option rather than implied.
791
- },
792
-
793
- update(next: Partial<InputMaskOptions>): void {
794
- // Configuration lands immediately -- a new `onInput` must not be shadowed by the old
795
- // one for the length of a composition. Only the VALUE WRITE is deferrable.
796
- const previousText = formatRaw(pattern, raw);
797
- const previousRaw = raw;
798
- const previousFormatted = formatted;
799
-
800
- // COMPILE FIRST, ASSIGN AFTER. `compileMask` throws on a format over the length cap
801
- // or a guide that does not align, and the throw must leave this masker exactly as it
802
- // was. Merging into `opts` first would park the rejected config in state where the
803
- // NEXT update -- one that says nothing about `format` -- picks it up and applies a
804
- // change that was already refused. The element facade hits precisely this shape when
805
- // `format` and `guide` are separate reactive attributes that do not land in the same
806
- // tick.
807
- const merged: InputMaskOptions = { ...opts, ...next };
808
- const nextPattern = compileMask(merged.format, merged.guide);
809
-
810
- opts = merged;
811
- pattern = nextPattern;
812
- hasGuide = merged.guide !== undefined;
813
- caseMode = merged.caseMode ?? 'preserve';
814
- semantic = merged.semantic ?? 'custom';
815
- obscure = merged.obscure ?? false;
816
- runOpen = false;
817
- // The undo history is NOT cleared: entries carry the pattern that wrote them and
818
- // `restore` re-fits across a change. Redo is, because it holds futures that the new
819
- // pattern may not be able to reach.
820
- redoStack.length = 0;
821
-
822
- writeOrDefer(() => {
823
- const nextRaw = normalizeToRaw(pattern, previousText, caseMode);
824
- const nextFormatted = display(nextRaw);
825
- const changed = nextFormatted !== previousFormatted;
826
- // A format change that cannot hold what the field already held DESTROYS user
827
- // content, and the consumer's own model still has the old value -- so silence here
828
- // is worse than an ordinary silent drop: nothing would ever converge. The element
829
- // facade re-calls `update()` on any `format` prop change, so this fires on a
830
- // routine reactive render. Report the loss, and notify so a controlled consumer
831
- // catches up. `data` carries the pre-change text, which after this call is the only
832
- // copy of the discarded characters anywhere.
833
- if (nextRaw.length < previousRaw.length) reject('format-change-clipped', previousText);
834
- // `undo: 'none'`, always. An entry pushed here would be DEAD BY CONSTRUCTION: it
835
- // would hold `previousFormatted` under the old pattern, and `restore` re-fits a
836
- // stale entry with exactly the computation two lines up
837
- // (`normalizeToRaw(pattern, previousText)`), so restoring it lands on the text
838
- // already on screen -- the first Ctrl+Z after a format change would do nothing
839
- // visible. The history the user cares about is the entries UNDERNEATH, which
840
- // survive because each carries the pattern that wrote it.
841
- commitRaw(nextRaw, nextRaw.length, { undo: 'none', notify: changed });
842
- });
843
- },
844
-
845
- detach(): void {
846
- if (detached) return;
847
- detached = true;
848
- pendingWrite = null; // a deferred write must not fire into a field we no longer own
849
- for (const [kind, handler] of elementListeners) el.removeEventListener(kind, handler);
850
- doc.removeEventListener('selectionchange', clampSelection);
851
- },
852
- };
853
- }