foldkit-primitives 0.15.3 → 0.16.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 (422) hide show
  1. package/dist/device/fullscreen.d.ts +28 -0
  2. package/dist/device/fullscreen.d.ts.map +1 -0
  3. package/dist/device/fullscreen.js +69 -0
  4. package/dist/device/fullscreen.js.map +1 -0
  5. package/dist/device/geolocation.d.ts +56 -0
  6. package/dist/device/geolocation.d.ts.map +1 -0
  7. package/dist/device/geolocation.js +87 -0
  8. package/dist/device/geolocation.js.map +1 -0
  9. package/dist/device/index.d.ts +5 -0
  10. package/dist/device/index.js +6 -0
  11. package/dist/device/media-devices.d.ts +88 -0
  12. package/dist/device/media-devices.d.ts.map +1 -0
  13. package/dist/device/media-devices.js +117 -0
  14. package/dist/device/media-devices.js.map +1 -0
  15. package/dist/device/media-stream.d.ts +93 -0
  16. package/dist/device/media-stream.d.ts.map +1 -0
  17. package/dist/device/media-stream.js +106 -0
  18. package/dist/device/media-stream.js.map +1 -0
  19. package/dist/device/permissions.d.ts +106 -0
  20. package/dist/device/permissions.d.ts.map +1 -0
  21. package/dist/device/permissions.js +143 -0
  22. package/dist/device/permissions.js.map +1 -0
  23. package/dist/dom/autofocus.d.ts +15 -0
  24. package/dist/dom/autofocus.d.ts.map +1 -0
  25. package/dist/dom/autofocus.js +22 -0
  26. package/dist/dom/autofocus.js.map +1 -0
  27. package/dist/dom/clipboard.d.ts +30 -0
  28. package/dist/dom/clipboard.d.ts.map +1 -0
  29. package/dist/dom/clipboard.js +50 -0
  30. package/dist/dom/clipboard.js.map +1 -0
  31. package/dist/dom/editable-text.d.ts +72 -0
  32. package/dist/dom/editable-text.d.ts.map +1 -0
  33. package/dist/dom/editable-text.js +232 -0
  34. package/dist/dom/editable-text.js.map +1 -0
  35. package/dist/dom/fluid-hover.d.ts +93 -0
  36. package/dist/dom/fluid-hover.d.ts.map +1 -0
  37. package/dist/dom/fluid-hover.js +294 -0
  38. package/dist/dom/fluid-hover.js.map +1 -0
  39. package/dist/dom/focus-scope.d.ts +21 -0
  40. package/dist/dom/focus-scope.d.ts.map +1 -0
  41. package/dist/dom/focus-scope.js +93 -0
  42. package/dist/dom/focus-scope.js.map +1 -0
  43. package/dist/dom/follow-tab-stop.d.ts +11 -0
  44. package/dist/dom/follow-tab-stop.d.ts.map +1 -0
  45. package/dist/dom/follow-tab-stop.js +54 -0
  46. package/dist/dom/follow-tab-stop.js.map +1 -0
  47. package/dist/dom/image-decode.d.ts +27 -0
  48. package/dist/dom/image-decode.d.ts.map +1 -0
  49. package/dist/dom/image-decode.js +50 -0
  50. package/dist/dom/image-decode.js.map +1 -0
  51. package/dist/dom/index.d.ts +24 -0
  52. package/dist/dom/index.js +25 -0
  53. package/dist/dom/input-mask.d.ts +35 -0
  54. package/dist/dom/input-mask.d.ts.map +1 -0
  55. package/dist/dom/input-mask.js +90 -0
  56. package/dist/dom/input-mask.js.map +1 -0
  57. package/dist/dom/keep-in-view.d.ts +16 -0
  58. package/dist/dom/keep-in-view.d.ts.map +1 -0
  59. package/dist/dom/keep-in-view.js +38 -0
  60. package/dist/dom/keep-in-view.js.map +1 -0
  61. package/dist/dom/keyboard-inset.d.ts +29 -0
  62. package/dist/dom/keyboard-inset.d.ts.map +1 -0
  63. package/dist/dom/keyboard-inset.js +45 -0
  64. package/dist/dom/keyboard-inset.js.map +1 -0
  65. package/dist/dom/layers.d.ts +6 -0
  66. package/dist/dom/layers.d.ts.map +1 -0
  67. package/dist/dom/layers.js +35 -0
  68. package/dist/dom/layers.js.map +1 -0
  69. package/dist/dom/measure.d.ts +42 -0
  70. package/dist/dom/measure.d.ts.map +1 -0
  71. package/dist/dom/measure.js +123 -0
  72. package/dist/dom/measure.js.map +1 -0
  73. package/dist/dom/move.d.ts +45 -0
  74. package/dist/dom/move.d.ts.map +1 -0
  75. package/dist/dom/move.js +96 -0
  76. package/dist/dom/move.js.map +1 -0
  77. package/dist/dom/pointed-links.d.ts +13 -0
  78. package/dist/dom/pointed-links.d.ts.map +1 -0
  79. package/dist/dom/pointed-links.js +59 -0
  80. package/dist/dom/pointed-links.js.map +1 -0
  81. package/dist/dom/pointer-drag.d.ts +132 -0
  82. package/dist/dom/pointer-drag.d.ts.map +1 -0
  83. package/dist/dom/pointer-drag.js +307 -0
  84. package/dist/dom/pointer-drag.js.map +1 -0
  85. package/dist/dom/pointer-position.d.ts +133 -0
  86. package/dist/dom/pointer-position.d.ts.map +1 -0
  87. package/dist/dom/pointer-position.js +173 -0
  88. package/dist/dom/pointer-position.js.map +1 -0
  89. package/dist/dom/script-loader.d.ts +12 -0
  90. package/dist/dom/script-loader.d.ts.map +1 -0
  91. package/dist/dom/script-loader.js +54 -0
  92. package/dist/dom/script-loader.js.map +1 -0
  93. package/dist/dom/scroll-bars.d.ts +52 -0
  94. package/dist/dom/scroll-bars.d.ts.map +1 -0
  95. package/dist/dom/scroll-bars.js +259 -0
  96. package/dist/dom/scroll-bars.js.map +1 -0
  97. package/dist/dom/scroll-settle.d.ts +33 -0
  98. package/dist/dom/scroll-settle.d.ts.map +1 -0
  99. package/dist/dom/scroll-settle.js +84 -0
  100. package/dist/dom/scroll-settle.js.map +1 -0
  101. package/dist/dom/scroll.d.ts +21 -0
  102. package/dist/dom/scroll.d.ts.map +1 -0
  103. package/dist/dom/scroll.js +141 -0
  104. package/dist/dom/scroll.js.map +1 -0
  105. package/dist/dom/share.d.ts +19 -0
  106. package/dist/dom/share.d.ts.map +1 -0
  107. package/dist/dom/share.js +41 -0
  108. package/dist/dom/share.js.map +1 -0
  109. package/dist/dom/swipe.d.ts +121 -0
  110. package/dist/dom/swipe.d.ts.map +1 -0
  111. package/dist/dom/swipe.js +291 -0
  112. package/dist/dom/swipe.js.map +1 -0
  113. package/dist/dom/targets.d.ts +45 -0
  114. package/dist/dom/targets.d.ts.map +1 -0
  115. package/dist/dom/targets.js +70 -0
  116. package/dist/dom/targets.js.map +1 -0
  117. package/dist/dom/track-box.d.ts +61 -0
  118. package/dist/dom/track-box.d.ts.map +1 -0
  119. package/dist/dom/track-box.js +131 -0
  120. package/dist/dom/track-box.js.map +1 -0
  121. package/dist/events/active-element.d.ts +15 -0
  122. package/dist/events/active-element.d.ts.map +1 -0
  123. package/dist/events/active-element.js +33 -0
  124. package/dist/events/active-element.js.map +1 -0
  125. package/dist/events/hotkeys.d.ts +22 -0
  126. package/dist/events/hotkeys.d.ts.map +1 -0
  127. package/dist/events/hotkeys.js +56 -0
  128. package/dist/events/hotkeys.js.map +1 -0
  129. package/dist/events/idle.d.ts +29 -0
  130. package/dist/events/idle.d.ts.map +1 -0
  131. package/dist/events/idle.js +51 -0
  132. package/dist/events/idle.js.map +1 -0
  133. package/dist/events/index.d.ts +9 -0
  134. package/dist/events/index.js +10 -0
  135. package/dist/{input-modality-BY-bSrEc.d.mts → events/input-modality.d.ts} +9 -12
  136. package/dist/events/input-modality.d.ts.map +1 -0
  137. package/dist/{input-modality-BXD383jg.mjs → events/input-modality.js} +3 -4
  138. package/dist/events/input-modality.js.map +1 -0
  139. package/dist/events/keyboard.d.ts +30 -0
  140. package/dist/events/keyboard.d.ts.map +1 -0
  141. package/dist/events/keyboard.js +55 -0
  142. package/dist/events/keyboard.js.map +1 -0
  143. package/dist/events/pointer.d.ts +15 -0
  144. package/dist/events/pointer.d.ts.map +1 -0
  145. package/dist/events/pointer.js +28 -0
  146. package/dist/events/pointer.js.map +1 -0
  147. package/dist/events/scroll.d.ts +16 -0
  148. package/dist/events/scroll.d.ts.map +1 -0
  149. package/dist/events/scroll.js +29 -0
  150. package/dist/events/scroll.js.map +1 -0
  151. package/dist/events/visibility.d.ts +25 -0
  152. package/dist/events/visibility.d.ts.map +1 -0
  153. package/dist/events/visibility.js +28 -0
  154. package/dist/events/visibility.js.map +1 -0
  155. package/dist/events/window-size.d.ts +30 -0
  156. package/dist/events/window-size.d.ts.map +1 -0
  157. package/dist/events/window-size.js +63 -0
  158. package/dist/events/window-size.js.map +1 -0
  159. package/dist/index.d.ts +9 -0
  160. package/dist/index.js +10 -0
  161. package/dist/interaction/bundle-behavior.d.ts +37 -0
  162. package/dist/interaction/bundle-behavior.d.ts.map +1 -0
  163. package/dist/interaction/bundle-behavior.js +34 -0
  164. package/dist/interaction/bundle-behavior.js.map +1 -0
  165. package/dist/interaction/dismiss-layer.d.ts +100 -0
  166. package/dist/interaction/dismiss-layer.d.ts.map +1 -0
  167. package/dist/interaction/dismiss-layer.js +140 -0
  168. package/dist/interaction/dismiss-layer.js.map +1 -0
  169. package/dist/interaction/editable-text.d.ts +13 -0
  170. package/dist/interaction/editable-text.d.ts.map +1 -0
  171. package/dist/interaction/editable-text.js +17 -0
  172. package/dist/interaction/editable-text.js.map +1 -0
  173. package/dist/interaction/focus-scope.d.ts +21 -0
  174. package/dist/interaction/focus-scope.d.ts.map +1 -0
  175. package/dist/interaction/focus-scope.js +25 -0
  176. package/dist/interaction/focus-scope.js.map +1 -0
  177. package/dist/interaction/focus-visible.d.ts +9 -0
  178. package/dist/interaction/focus-visible.d.ts.map +1 -0
  179. package/dist/interaction/focus-visible.js +10 -0
  180. package/dist/interaction/focus-visible.js.map +1 -0
  181. package/dist/interaction/grid-navigation.d.ts +73 -0
  182. package/dist/interaction/grid-navigation.d.ts.map +1 -0
  183. package/dist/interaction/grid-navigation.js +137 -0
  184. package/dist/interaction/grid-navigation.js.map +1 -0
  185. package/dist/interaction/index.d.ts +21 -0
  186. package/dist/interaction/index.js +21 -0
  187. package/dist/interaction/layers.d.ts +12 -0
  188. package/dist/interaction/layers.d.ts.map +1 -0
  189. package/dist/interaction/layers.js +14 -0
  190. package/dist/interaction/layers.js.map +1 -0
  191. package/dist/interaction/list-navigation.d.ts +105 -0
  192. package/dist/interaction/list-navigation.d.ts.map +1 -0
  193. package/dist/interaction/list-navigation.js +168 -0
  194. package/dist/interaction/list-navigation.js.map +1 -0
  195. package/dist/interaction/live-announce.d.ts +70 -0
  196. package/dist/interaction/live-announce.d.ts.map +1 -0
  197. package/dist/interaction/live-announce.js +115 -0
  198. package/dist/interaction/live-announce.js.map +1 -0
  199. package/dist/interaction/long-press.d.ts +102 -0
  200. package/dist/interaction/long-press.d.ts.map +1 -0
  201. package/dist/interaction/long-press.js +115 -0
  202. package/dist/interaction/long-press.js.map +1 -0
  203. package/dist/interaction/move.d.ts +10 -0
  204. package/dist/interaction/move.d.ts.map +1 -0
  205. package/dist/interaction/move.js +17 -0
  206. package/dist/interaction/move.js.map +1 -0
  207. package/dist/interaction/overlay.d.ts +45 -0
  208. package/dist/interaction/overlay.d.ts.map +1 -0
  209. package/dist/interaction/overlay.js +57 -0
  210. package/dist/interaction/overlay.js.map +1 -0
  211. package/dist/interaction/placing.d.ts +86 -0
  212. package/dist/interaction/placing.d.ts.map +1 -0
  213. package/dist/interaction/placing.js +239 -0
  214. package/dist/interaction/placing.js.map +1 -0
  215. package/dist/interaction/pointer-drag.d.ts +23 -0
  216. package/dist/interaction/pointer-drag.d.ts.map +1 -0
  217. package/dist/interaction/pointer-drag.js +21 -0
  218. package/dist/interaction/pointer-drag.js.map +1 -0
  219. package/dist/interaction/press.d.ts +152 -0
  220. package/dist/interaction/press.d.ts.map +1 -0
  221. package/dist/interaction/press.js +229 -0
  222. package/dist/interaction/press.js.map +1 -0
  223. package/dist/interaction/roving-tabindex.d.ts +85 -0
  224. package/dist/interaction/roving-tabindex.d.ts.map +1 -0
  225. package/dist/interaction/roving-tabindex.js +142 -0
  226. package/dist/interaction/roving-tabindex.js.map +1 -0
  227. package/dist/interaction/selection.d.ts +72 -0
  228. package/dist/interaction/selection.d.ts.map +1 -0
  229. package/dist/interaction/selection.js +129 -0
  230. package/dist/interaction/selection.js.map +1 -0
  231. package/dist/interaction/targets.d.ts +15 -0
  232. package/dist/interaction/targets.d.ts.map +1 -0
  233. package/dist/interaction/targets.js +20 -0
  234. package/dist/interaction/targets.js.map +1 -0
  235. package/dist/interaction/tree-navigation.d.ts +111 -0
  236. package/dist/interaction/tree-navigation.d.ts.map +1 -0
  237. package/dist/interaction/tree-navigation.js +207 -0
  238. package/dist/interaction/tree-navigation.js.map +1 -0
  239. package/dist/interaction/typeahead.d.ts +73 -0
  240. package/dist/interaction/typeahead.d.ts.map +1 -0
  241. package/dist/interaction/typeahead.js +113 -0
  242. package/dist/interaction/typeahead.js.map +1 -0
  243. package/dist/internal.d.ts +16 -0
  244. package/dist/internal.d.ts.map +1 -0
  245. package/dist/{internal-DP-xLKbT.mjs → internal.js} +8 -7
  246. package/dist/internal.js.map +1 -0
  247. package/dist/media/breakpoints.d.ts +37 -0
  248. package/dist/media/breakpoints.d.ts.map +1 -0
  249. package/dist/media/breakpoints.js +64 -0
  250. package/dist/media/breakpoints.js.map +1 -0
  251. package/dist/media/index.d.ts +3 -0
  252. package/dist/media/index.js +4 -0
  253. package/dist/media/mediaquery.d.ts +40 -0
  254. package/dist/media/mediaquery.d.ts.map +1 -0
  255. package/dist/media/mediaquery.js +42 -0
  256. package/dist/media/mediaquery.js.map +1 -0
  257. package/dist/media/platform.d.ts +30 -0
  258. package/dist/media/platform.d.ts.map +1 -0
  259. package/dist/media/platform.js +50 -0
  260. package/dist/media/platform.js.map +1 -0
  261. package/dist/motion/index.d.ts +5 -0
  262. package/dist/motion/index.js +5 -0
  263. package/dist/motion/motion.d.ts +16 -0
  264. package/dist/motion/motion.d.ts.map +1 -0
  265. package/dist/motion/motion.js +25 -0
  266. package/dist/motion/motion.js.map +1 -0
  267. package/dist/motion/presence.d.ts +37 -0
  268. package/dist/motion/presence.d.ts.map +1 -0
  269. package/dist/motion/presence.js +65 -0
  270. package/dist/motion/presence.js.map +1 -0
  271. package/dist/motion/spring.d.ts +50 -0
  272. package/dist/motion/spring.d.ts.map +1 -0
  273. package/dist/motion/spring.js +92 -0
  274. package/dist/motion/spring.js.map +1 -0
  275. package/dist/motion/tween.d.ts +44 -0
  276. package/dist/motion/tween.d.ts.map +1 -0
  277. package/dist/motion/tween.js +65 -0
  278. package/dist/motion/tween.js.map +1 -0
  279. package/dist/net/broadcast.d.ts +39 -0
  280. package/dist/net/broadcast.d.ts.map +1 -0
  281. package/dist/net/broadcast.js +73 -0
  282. package/dist/net/broadcast.js.map +1 -0
  283. package/dist/net/index.d.ts +5 -0
  284. package/dist/net/index.js +6 -0
  285. package/dist/net/online.d.ts +24 -0
  286. package/dist/net/online.d.ts.map +1 -0
  287. package/dist/net/online.js +26 -0
  288. package/dist/net/online.js.map +1 -0
  289. package/dist/net/sharedHost.d.ts +64 -0
  290. package/dist/net/sharedHost.d.ts.map +1 -0
  291. package/dist/net/sharedHost.js +124 -0
  292. package/dist/net/sharedHost.js.map +1 -0
  293. package/dist/net/sse.d.ts +115 -0
  294. package/dist/net/sse.d.ts.map +1 -0
  295. package/dist/net/sse.js +138 -0
  296. package/dist/net/sse.js.map +1 -0
  297. package/dist/net/websocket.d.ts +172 -0
  298. package/dist/net/websocket.d.ts.map +1 -0
  299. package/dist/net/websocket.js +199 -0
  300. package/dist/net/websocket.js.map +1 -0
  301. package/dist/observers/bounds.d.ts +24 -0
  302. package/dist/observers/bounds.d.ts.map +1 -0
  303. package/dist/observers/bounds.js +51 -0
  304. package/dist/observers/bounds.js.map +1 -0
  305. package/dist/observers/index.d.ts +4 -0
  306. package/dist/observers/index.js +5 -0
  307. package/dist/observers/intersection.d.ts +18 -0
  308. package/dist/observers/intersection.d.ts.map +1 -0
  309. package/dist/observers/intersection.js +33 -0
  310. package/dist/observers/intersection.js.map +1 -0
  311. package/dist/observers/mutation.d.ts +25 -0
  312. package/dist/observers/mutation.d.ts.map +1 -0
  313. package/dist/observers/mutation.js +53 -0
  314. package/dist/observers/mutation.js.map +1 -0
  315. package/dist/observers/resize.d.ts +18 -0
  316. package/dist/observers/resize.d.ts.map +1 -0
  317. package/dist/observers/resize.js +33 -0
  318. package/dist/observers/resize.js.map +1 -0
  319. package/dist/state/history.d.ts +115 -0
  320. package/dist/state/history.d.ts.map +1 -0
  321. package/dist/state/history.js +167 -0
  322. package/dist/state/history.js.map +1 -0
  323. package/dist/state/index.d.ts +6 -0
  324. package/dist/state/index.js +7 -0
  325. package/dist/state/locale.d.ts +27 -0
  326. package/dist/state/locale.d.ts.map +1 -0
  327. package/dist/state/locale.js +25 -0
  328. package/dist/state/locale.js.map +1 -0
  329. package/dist/state/pagination.d.ts +53 -0
  330. package/dist/state/pagination.d.ts.map +1 -0
  331. package/dist/state/pagination.js +72 -0
  332. package/dist/state/pagination.js.map +1 -0
  333. package/dist/state/range.d.ts +9 -0
  334. package/dist/state/range.d.ts.map +1 -0
  335. package/dist/state/range.js +18 -0
  336. package/dist/state/range.js.map +1 -0
  337. package/dist/state/selection.d.ts +47 -0
  338. package/dist/state/selection.d.ts.map +1 -0
  339. package/dist/state/selection.js +39 -0
  340. package/dist/state/selection.js.map +1 -0
  341. package/dist/state/virtual.d.ts +201 -0
  342. package/dist/state/virtual.d.ts.map +1 -0
  343. package/dist/state/{index.mjs → virtual.js} +11 -313
  344. package/dist/state/virtual.js.map +1 -0
  345. package/dist/time/debounce.d.ts +43 -0
  346. package/dist/time/debounce.d.ts.map +1 -0
  347. package/dist/time/debounce.js +65 -0
  348. package/dist/time/debounce.js.map +1 -0
  349. package/dist/time/index.d.ts +6 -0
  350. package/dist/time/index.js +7 -0
  351. package/dist/time/interval.d.ts +37 -0
  352. package/dist/time/interval.d.ts.map +1 -0
  353. package/dist/time/interval.js +52 -0
  354. package/dist/time/interval.js.map +1 -0
  355. package/dist/time/relative.d.ts +8 -0
  356. package/dist/time/relative.d.ts.map +1 -0
  357. package/dist/time/relative.js +27 -0
  358. package/dist/time/relative.js.map +1 -0
  359. package/dist/time/throttle.d.ts +38 -0
  360. package/dist/time/throttle.d.ts.map +1 -0
  361. package/dist/time/throttle.js +39 -0
  362. package/dist/time/throttle.js.map +1 -0
  363. package/dist/time/ticks.d.ts +21 -0
  364. package/dist/time/ticks.d.ts.map +1 -0
  365. package/dist/time/ticks.js +30 -0
  366. package/dist/time/ticks.js.map +1 -0
  367. package/dist/time/timer.d.ts +32 -0
  368. package/dist/time/timer.d.ts.map +1 -0
  369. package/dist/time/timer.js +50 -0
  370. package/dist/time/timer.js.map +1 -0
  371. package/package.json +31 -31
  372. package/dist/device/index.d.mts +0 -345
  373. package/dist/device/index.d.mts.map +0 -1
  374. package/dist/device/index.mjs +0 -498
  375. package/dist/device/index.mjs.map +0 -1
  376. package/dist/dom/index.d.mts +0 -750
  377. package/dist/dom/index.d.mts.map +0 -1
  378. package/dist/dom/index.mjs +0 -1857
  379. package/dist/dom/index.mjs.map +0 -1
  380. package/dist/editable-text-1D31qthY.d.mts +0 -319
  381. package/dist/editable-text-1D31qthY.d.mts.map +0 -1
  382. package/dist/events/index.d.mts +0 -184
  383. package/dist/events/index.d.mts.map +0 -1
  384. package/dist/events/index.mjs +0 -315
  385. package/dist/events/index.mjs.map +0 -1
  386. package/dist/follow-tab-stop-Caog2qg1.mjs +0 -863
  387. package/dist/follow-tab-stop-Caog2qg1.mjs.map +0 -1
  388. package/dist/index-CmmxqSEi.d.mts +0 -149
  389. package/dist/index-CmmxqSEi.d.mts.map +0 -1
  390. package/dist/index.d.mts +0 -12
  391. package/dist/index.mjs +0 -12
  392. package/dist/input-modality-BXD383jg.mjs.map +0 -1
  393. package/dist/input-modality-BY-bSrEc.d.mts.map +0 -1
  394. package/dist/interaction/index.d.mts +0 -1194
  395. package/dist/interaction/index.d.mts.map +0 -1
  396. package/dist/interaction/index.mjs +0 -2012
  397. package/dist/interaction/index.mjs.map +0 -1
  398. package/dist/internal-DP-xLKbT.mjs.map +0 -1
  399. package/dist/media/index.d.mts +0 -109
  400. package/dist/media/index.d.mts.map +0 -1
  401. package/dist/media/index.mjs +0 -149
  402. package/dist/media/index.mjs.map +0 -1
  403. package/dist/motion/index.d.mts +0 -2
  404. package/dist/motion/index.mjs +0 -1
  405. package/dist/motion-DmFDppRn.mjs +0 -240
  406. package/dist/motion-DmFDppRn.mjs.map +0 -1
  407. package/dist/net/index.d.mts +0 -348
  408. package/dist/net/index.d.mts.map +0 -1
  409. package/dist/net/index.mjs +0 -535
  410. package/dist/net/index.mjs.map +0 -1
  411. package/dist/observers/index.d.mts +0 -84
  412. package/dist/observers/index.d.mts.map +0 -1
  413. package/dist/observers/index.mjs +0 -159
  414. package/dist/observers/index.mjs.map +0 -1
  415. package/dist/rolldown-runtime-D7D4PA-g.mjs +0 -13
  416. package/dist/state/index.d.mts +0 -444
  417. package/dist/state/index.d.mts.map +0 -1
  418. package/dist/state/index.mjs.map +0 -1
  419. package/dist/time/index.d.mts +0 -179
  420. package/dist/time/index.d.mts.map +0 -1
  421. package/dist/time/index.mjs +0 -242
  422. package/dist/time/index.mjs.map +0 -1
@@ -1,1857 +0,0 @@
1
- import { A as FocusScope, C as targetOf, D as Moved, E as MoveStarted, O as HideOutside, S as Targets, T as MoveEnded, _ as PointerDrag, a as TextCommitted, b as TargetHovered, c as DRAG_THRESHOLD, d as DragPlace, f as DragStarted, g as HOLD_TO_DRAG_MS, h as HOLD_SLOP, i as TextCancelled, j as tabbableWithin, k as ScrollLock, l as DragCancelled, m as DraggedOver, n as EditAsked, o as TextEdited, p as DragZone, r as EditableText, s as textOf, t as FollowTabStop, u as DragDropped, v as boxOf, w as Move, x as TargetPressed, y as zoneOf } from "../follow-tab-stop-Caog2qg1.mjs";
2
- import { Effect, Option, Queue, Schema, Stream } from "effect";
3
- import * as Mount from "foldkit/mount";
4
- import { defineMessageUnion } from "foldkit/message";
5
- import * as Dom from "foldkit/dom";
6
- import { fromString, toString } from "foldkit/url";
7
- //#region src/dom/autofocus.ts
8
- const Focused = Schema.TaggedStruct("Focused", {});
9
- /**
10
- * Autofocus as a Mount: focuses the element on insert, then emits `Focused`.
11
- * The message means focus was requested — a non-focusable element or a
12
- * hidden tab may decline, and the browser does not report that. Plain
13
- * `focus()`, no scroll options: scroll behavior stays the application's
14
- * choice.
15
- */
16
- const Autofocus = Mount.define("Autofocus", {
17
- messages: [Focused],
18
- execute: ({ element }) => Effect.sync(() => {
19
- const focusable = element;
20
- if (typeof focusable.focus === "function") focusable.focus.call(element);
21
- return Focused.make({});
22
- })
23
- });
24
- //#endregion
25
- //#region src/dom/clipboard.ts
26
- /**
27
- * The clipboard as Commands: run in `update` beside any bundle. A copy yields
28
- * `Copied` or `CopyFailed`, a read `Read` or `ReadFailed`. No Model involved:
29
- * the clipboard is not application state.
30
- */
31
- const ClipboardMessage = defineMessageUnion({
32
- Copied: {},
33
- CopyFailed: { message: Schema.String }
34
- });
35
- const ClipboardReadMessage = defineMessageUnion({
36
- Read: { text: Schema.String },
37
- ReadFailed: { message: Schema.String }
38
- });
39
- const failMessage$2 = (error) => error instanceof Error ? error.message : String(error);
40
- /**
41
- * Copies text to the clipboard. Without a clipboard API (SSR, insecure
42
- * context, denied permission) the command yields `CopyFailed` instead of
43
- * throwing, so `update` handles both outcomes as Messages.
44
- */
45
- const copyText = (text) => ({
46
- name: "Clipboard.copy",
47
- args: { text },
48
- effect: typeof navigator !== "undefined" && typeof navigator.clipboard?.writeText === "function" ? Effect.matchEffect(Effect.tryPromise({
49
- try: () => navigator.clipboard.writeText(text),
50
- catch: failMessage$2
51
- }), {
52
- onFailure: (message) => Effect.succeed(ClipboardMessage.CopyFailed({ message })),
53
- onSuccess: () => Effect.succeed(ClipboardMessage.Copied())
54
- }) : Effect.succeed(ClipboardMessage.CopyFailed({ message: "clipboard is unavailable" }))
55
- });
56
- /**
57
- * Reads the clipboard's text. The browser may ask the user first; a refusal,
58
- * an insecure context or no clipboard API yields `ReadFailed`.
59
- */
60
- const readText = () => ({
61
- name: "Clipboard.read",
62
- effect: typeof navigator !== "undefined" && typeof navigator.clipboard?.readText === "function" ? Effect.matchEffect(Effect.tryPromise({
63
- try: () => navigator.clipboard.readText(),
64
- catch: failMessage$2
65
- }), {
66
- onFailure: (message) => Effect.succeed(ClipboardReadMessage.ReadFailed({ message })),
67
- onSuccess: (text) => Effect.succeed(ClipboardReadMessage.Read({ text }))
68
- }) : Effect.succeed(ClipboardReadMessage.ReadFailed({ message: "clipboard is unavailable" }))
69
- });
70
- //#endregion
71
- //#region src/dom/input-mask.ts
72
- /** What `InputMask` reports after each edit: the masked text shown, and the characters typed. */
73
- const Input = Schema.TaggedStruct("Input", {
74
- value: Schema.String,
75
- raw: Schema.String
76
- });
77
- const isDigit = (char) => char >= "0" && char <= "9";
78
- const isLetter = (char) => char >= "a" && char <= "z" || char >= "A" && char <= "Z";
79
- const matches = (placeholder, char) => placeholder === "#" ? isDigit(char) : placeholder === "A" ? isLetter(char) : isDigit(char) || isLetter(char);
80
- const isPlaceholder = (char) => char === "#" || char === "A" || char === "*";
81
- /** Whether any placeholder at or after `from` consumes any input at or after `at`. */
82
- const fillable = (pattern, from, value, at) => {
83
- for (let i = from; i < pattern.length; i++) {
84
- const placeholder = pattern[i];
85
- if (!isPlaceholder(placeholder)) continue;
86
- for (let j = at; j < value.length; j++) if (matches(placeholder, value[j])) return true;
87
- }
88
- return false;
89
- };
90
- /**
91
- * The mask as a pure function: `#` takes a digit, `A` a letter, `*` either, and
92
- * any other character of the pattern is written in once input reaches past it.
93
- * Input that fits no placeholder is skipped. `masked` is what the field shows;
94
- * `raw` is only the characters typed.
95
- */
96
- const applyMask = (value, pattern) => {
97
- let masked = "";
98
- let raw = "";
99
- let at = 0;
100
- for (let i = 0; i < pattern.length; i++) {
101
- const slot = pattern[i];
102
- if (isPlaceholder(slot)) {
103
- while (at < value.length && !matches(slot, value[at])) at++;
104
- if (at >= value.length) break;
105
- masked += value[at];
106
- raw += value[at];
107
- at++;
108
- } else if (fillable(pattern, i + 1, value, at)) masked += slot;
109
- else break;
110
- }
111
- return {
112
- masked,
113
- raw
114
- };
115
- };
116
- const asField = (element) => {
117
- const field = element;
118
- return typeof field.value === "string" && typeof field.addEventListener === "function" ? field : null;
119
- };
120
- /**
121
- * An input mask as a Mount plus a pure mask function. The pattern language
122
- * is three placeholders — `#` digit, `A` letter, `*` either, all ASCII —
123
- * with every other character literal. Literals appear only once a later placeholder
124
- * consumes input, so partial values never dangle a separator. The Mount
125
- * rewrites the field, restores the caret approximately, and emits `Input`
126
- * with both the masked and raw values; the parent owns the state, like any
127
- * controlled input.
128
- */
129
- const InputMask = Mount.defineStream("InputMask", {
130
- messages: [Input],
131
- args: { pattern: Schema.String },
132
- execute: ({ element, pattern }) => Stream.callback((queue) => Effect.gen(function* () {
133
- const field = asField(element);
134
- if (field === null) return;
135
- const onInput = () => {
136
- const { masked, raw } = applyMask(field.value, pattern);
137
- if (masked !== field.value) {
138
- const caret = field.selectionStart ?? masked.length;
139
- field.value = masked;
140
- const at = Math.min(caret, masked.length);
141
- try {
142
- field.setSelectionRange?.(at, at);
143
- } catch {}
144
- }
145
- Queue.offerUnsafe(queue, Input.make({
146
- value: masked,
147
- raw
148
- }));
149
- };
150
- yield* Effect.acquireRelease(Effect.sync(() => {
151
- field.addEventListener("input", onInput);
152
- return field;
153
- }), (current) => Effect.sync(() => current.removeEventListener("input", onInput)));
154
- }))
155
- });
156
- //#endregion
157
- //#region src/dom/keep-in-view.ts
158
- /**
159
- * Keeps what is marked in view as a Mount: whenever an element in the mounted
160
- * one's subtree newly matches `selector`, such as the row just selected in a
161
- * list, it is scrolled into view the least amount that shows it (`nearest`).
162
- * The element keeps its identity and its focus: nothing is redrawn to move it.
163
- *
164
- * It watches with a `MutationObserver` for as long as it is mounted, which is as
165
- * long as the list or canvas that can change what is marked.
166
- */
167
- const KeepInView = Mount.defineStream("KeepInView", {
168
- messages: [Schema.Never],
169
- args: {
170
- /** What is marked, as a selector within the element: `[aria-selected="true"]`. */
171
- selector: Schema.String },
172
- execute: ({ element, selector }) => Stream.callback(() => Effect.acquireRelease(Effect.sync(() => {
173
- let marked = element.querySelector(selector);
174
- const observer = new MutationObserver(() => {
175
- const now = element.querySelector(selector);
176
- if (now !== null && now !== marked && typeof now.scrollIntoView === "function") now.scrollIntoView({
177
- block: "nearest",
178
- inline: "nearest"
179
- });
180
- marked = now;
181
- });
182
- observer.observe(element, {
183
- subtree: true,
184
- childList: true,
185
- attributes: true
186
- });
187
- return observer;
188
- }), (observer) => Effect.sync(() => observer.disconnect())))
189
- });
190
- //#endregion
191
- //#region src/dom/script-loader.ts
192
- /**
193
- * Script loading as a Command: appends a `src` script to the head unless one
194
- * is already there. Idempotent by URL — concurrent placements collapse onto
195
- * the first tag instead of fetching twice. Yields `Loaded` on success and
196
- * `LoadFailed` otherwise (network error, no document), so `update` handles
197
- * both outcomes as Messages.
198
- */
199
- const ScriptMessage = defineMessageUnion({
200
- Loaded: {},
201
- LoadFailed: { message: Schema.String }
202
- });
203
- const failMessage$1 = (error) => error instanceof Error ? error.message : String(error);
204
- const inflight = /* @__PURE__ */ new Map();
205
- /** Loads a script by URL, once per URL. */
206
- const loadScript = (src) => ({
207
- name: "Script.load",
208
- args: { src },
209
- effect: Effect.matchEffect(Effect.tryPromise({
210
- try: () => {
211
- if (typeof document === "undefined") return Promise.reject(/* @__PURE__ */ new Error("script loading is unavailable"));
212
- const running = inflight.get(src);
213
- if (running !== void 0) return running;
214
- if (Array.from(document.getElementsByTagName("script")).some((element) => element.getAttribute("src") === src)) return Promise.resolve();
215
- const tracked = new Promise((resolve, reject) => {
216
- const element = document.createElement("script");
217
- element.src = src;
218
- element.async = true;
219
- element.onload = () => resolve();
220
- element.onerror = () => {
221
- element.remove();
222
- reject(/* @__PURE__ */ new Error(`failed to load script: ${src}`));
223
- };
224
- document.head.appendChild(element);
225
- }).then(() => {
226
- inflight.delete(src);
227
- }, (error) => {
228
- inflight.delete(src);
229
- throw error;
230
- });
231
- inflight.set(src, tracked);
232
- return tracked;
233
- },
234
- catch: (error) => error
235
- }), {
236
- onFailure: (error) => Effect.succeed(ScriptMessage.LoadFailed({ message: failMessage$1(error) })),
237
- onSuccess: () => Effect.succeed(ScriptMessage.Loaded())
238
- })
239
- });
240
- //#endregion
241
- //#region src/dom/share.ts
242
- /**
243
- * Web Share as a Command: runs in `update` beside any bundle, yields
244
- * `Shared` on success. Dismissal is its own outcome, not a failure — the
245
- * application tells a cancelled sheet apart from a broken one. Without a
246
- * share API (desktop, insecure context) it yields `ShareFailed` instead of
247
- * throwing, so `update` handles every outcome as a Message.
248
- */
249
- const ShareData = Schema.Struct({
250
- title: Schema.optional(Schema.String),
251
- text: Schema.optional(Schema.String),
252
- url: Schema.optional(Schema.String)
253
- });
254
- const ShareMessage = defineMessageUnion({
255
- Shared: {},
256
- Dismissed: {},
257
- ShareFailed: { message: Schema.String }
258
- });
259
- const failMessage = (error) => error instanceof Error ? error.message : String(error);
260
- const isDismissal = (error) => typeof error?.name === "string" && error.name === "AbortError";
261
- /** Shares data through the platform sheet. */
262
- const share = (data) => ({
263
- name: "Share.share",
264
- args: { data },
265
- effect: typeof navigator !== "undefined" && typeof navigator.share === "function" ? Effect.matchEffect(Effect.tryPromise({
266
- try: () => navigator.share({
267
- ...data.title !== void 0 ? { title: data.title } : {},
268
- ...data.text !== void 0 ? { text: data.text } : {},
269
- ...data.url !== void 0 ? { url: data.url } : {}
270
- }),
271
- catch: (error) => error
272
- }), {
273
- onFailure: (error) => Effect.succeed(isDismissal(error) ? ShareMessage.Dismissed() : ShareMessage.ShareFailed({ message: failMessage(error) })),
274
- onSuccess: () => Effect.succeed(ShareMessage.Shared())
275
- }) : Effect.succeed(ShareMessage.ShareFailed({ message: "share is unavailable" }))
276
- });
277
- //#endregion
278
- //#region src/dom/measure.ts
279
- /** The custom properties `Measure` writes for a target named `name`. */
280
- const measured = (name) => ({
281
- x: `--fk-${name}-x`,
282
- y: `--fk-${name}-y`,
283
- w: `--fk-${name}-w`,
284
- h: `--fk-${name}-h`,
285
- display: `--fk-${name}-display`,
286
- /**
287
- * `1` for the measure that finds the mark on another element than the
288
- * last, `0` for any other (the layout moved or resized the same one, or
289
- * it is the first found): an overlay that glides to a new mark scales its
290
- * duration by it, so it keeps to its element as the layout settles.
291
- */
292
- moving: `--fk-${name}-moving`
293
- });
294
- /**
295
- * Measures marked elements as a Mount, for a view to draw something over them,
296
- * such as an editor's selection outline, with no Message and no Model: where
297
- * an element is, is presentation, known to the DOM and to no transition.
298
- *
299
- * For each named target, the first element in the mounted one's subtree that
300
- * matches its selector is measured relative to the mounted element's scroll
301
- * box, which is what an absolutely placed child of it is laid out in. The
302
- * mounted element gets `--fk-<name>-x`, `-y`, `-w` and `-h` in pixels, and
303
- * `--fk-<name>-display`: `block` while a target is found, `none` while none is,
304
- * and `--fk-<name>-moving` (see `measured`).
305
- *
306
- * It measures again when the subtree changes (a selection moves) or the
307
- * mounted element or a measured one changes size, in the frame the change is
308
- * drawn in; and, at most once a frame, when the mounted element scrolls, the
309
- * window resizes, an image or a font inside loads, and on every frame of a
310
- * transition or an animation inside. What moves a target by any other way,
311
- * such as a stylesheet added that shifts it without resizing it, is seen at
312
- * the next of these.
313
- */
314
- const Measure = Mount.defineStream("Measure", {
315
- messages: [Schema.Never],
316
- args: {
317
- /** Each target by name, as a selector within the element: `{ selected: '[aria-selected="true"]' }`. */
318
- targets: Schema.Record(Schema.String, Schema.String) },
319
- execute: ({ element, targets }) => Stream.callback(() => Effect.acquireRelease(Effect.sync(() => {
320
- const container = element;
321
- const names = Object.keys(targets);
322
- let watched = [];
323
- const last = /* @__PURE__ */ new Map();
324
- let frame;
325
- let animating = false;
326
- const measure = () => {
327
- if (frame !== void 0) cancelAnimationFrame(frame);
328
- frame = void 0;
329
- const box = container.getBoundingClientRect();
330
- const found = [];
331
- for (const name of names) {
332
- const property = measured(name);
333
- const target = container.querySelector(targets[name]);
334
- if (target === null) {
335
- container.style.setProperty(property.display, "none");
336
- continue;
337
- }
338
- found.push(target);
339
- const previous = last.get(name);
340
- last.set(name, target);
341
- const rect = target.getBoundingClientRect();
342
- const x = rect.left - box.left - container.clientLeft + container.scrollLeft;
343
- const y = rect.top - box.top - container.clientTop + container.scrollTop;
344
- container.style.setProperty(property.x, `${x}px`);
345
- container.style.setProperty(property.y, `${y}px`);
346
- container.style.setProperty(property.w, `${rect.width}px`);
347
- container.style.setProperty(property.h, `${rect.height}px`);
348
- container.style.setProperty(property.display, "block");
349
- container.style.setProperty(property.moving, previous === void 0 || previous === target ? "0" : "1");
350
- }
351
- if (found.length !== watched.length || found.some((each, at) => each !== watched[at])) {
352
- for (const each of watched) sizes.unobserve(each);
353
- for (const each of found) sizes.observe(each);
354
- watched = found;
355
- }
356
- if (animating) {
357
- animating = container.getAnimations({ subtree: true }).some((animation) => animation.playState === "running");
358
- if (animating) soon();
359
- }
360
- };
361
- const soon = () => {
362
- if (frame === void 0) frame = requestAnimationFrame(measure);
363
- };
364
- const moving = () => {
365
- animating = true;
366
- soon();
367
- };
368
- const changes = new MutationObserver(measure);
369
- const sizes = new ResizeObserver(measure);
370
- document.fonts.addEventListener("loadingdone", soon);
371
- changes.observe(container, {
372
- subtree: true,
373
- childList: true,
374
- attributes: true
375
- });
376
- sizes.observe(container);
377
- container.addEventListener("scroll", soon, { passive: true });
378
- window.addEventListener("resize", soon);
379
- container.addEventListener("load", soon, { capture: true });
380
- container.addEventListener("transitionrun", moving);
381
- container.addEventListener("animationstart", moving);
382
- measure();
383
- return () => {
384
- if (frame !== void 0) cancelAnimationFrame(frame);
385
- changes.disconnect();
386
- sizes.disconnect();
387
- container.removeEventListener("scroll", soon);
388
- window.removeEventListener("resize", soon);
389
- container.removeEventListener("load", soon, { capture: true });
390
- document.fonts.removeEventListener("loadingdone", soon);
391
- container.removeEventListener("transitionrun", moving);
392
- container.removeEventListener("animationstart", moving);
393
- };
394
- }), (stop) => Effect.sync(stop)))
395
- });
396
- //#endregion
397
- //#region src/dom/scroll.ts
398
- /**
399
- * Where the window is scrolled, across an application's own navigations, as
400
- * a stream. Left to itself, a browser keeps the scroll of the entry it
401
- * leaves when a link pushes a new one, and on Back restores the old offset
402
- * before the application has drawn the entry it returns to, so the offset is
403
- * clamped to a page still loading. Here a pushed entry starts at the top,
404
- * and Back, Forward or a reload return where that entry was, once the page
405
- * is tall enough to hold it.
406
- *
407
- * Three rules, each found the slow way:
408
- *
409
- * 1. The offset is taken when the reader acts. The next screen is drawn
410
- * before a Navigate Command pushes the address, so by the time the URL
411
- * changes the window is already clamped to the new, shorter page. The
412
- * scroll is recorded at the reader's click or key (capture phase), and at
413
- * the start of Back and Forward, which begin in the browser before
414
- * anything is drawn.
415
- * 2. A restore holds its place while the next screen settles. A restore made
416
- * when the entry changes is undone by the loading state drawn right after
417
- * it, so the target is held on each frame for a moment, until the reader
418
- * scrolls, touches or types, or another navigation starts.
419
- * 3. Entries are keyed by the Navigation API's entry key, which a reload and
420
- * a full navigation keep; offsets rest in `sessionStorage`, so Back into
421
- * another document restores too. Where the browser has no Navigation API
422
- * the stream attaches nothing and leaves the browser's own behaviour
423
- * alone.
424
- *
425
- * Lift with `Subscription.persistentEntry`; the stream sends no Messages. Only
426
- * the window scrolls: a nested container is the route graph's to know, when
427
- * `Site.transition` arrives.
428
- */
429
- const navigationTypeOf = (event) => "navigationType" in event ? Option.some(event.navigationType) : Option.none();
430
- const readSaved = (storageKey) => {
431
- try {
432
- const stored = JSON.parse(sessionStorage.getItem(storageKey) ?? "[]");
433
- return new Map(Array.isArray(stored) ? stored.filter((pair) => Array.isArray(pair) && typeof pair[0] === "string" && typeof pair[1] === "number") : []);
434
- } catch {
435
- return /* @__PURE__ */ new Map();
436
- }
437
- };
438
- /** Starts keeping the scroll; the release undoes everything it installed. */
439
- const attach = (storageKey, kept, patience) => {
440
- const navigation = window.navigation;
441
- if (navigation === void 0) return () => {};
442
- const restoration = window.history.scrollRestoration;
443
- window.history.scrollRestoration = "manual";
444
- const saved = readSaved(storageKey);
445
- const detaches = [];
446
- const on = (target, type, listener, capture) => {
447
- target.addEventListener(type, listener, capture);
448
- detaches.push(() => target.removeEventListener(type, listener, capture));
449
- };
450
- const keyOf = () => Option.fromNullOr(navigation.currentEntry).pipe(Option.map((entry) => entry.key));
451
- /** Where the current entry was left, if it has been. */
452
- const savedHere = () => Option.flatMap(keyOf(), (key) => Option.fromUndefinedOr(saved.get(key)));
453
- const remember = () => {
454
- const key = keyOf();
455
- if (Option.isNone(key)) return;
456
- saved.delete(key.value);
457
- saved.set(key.value, window.scrollY);
458
- for (const old of saved.keys()) {
459
- if (saved.size <= kept) break;
460
- saved.delete(old);
461
- }
462
- };
463
- let settling = new AbortController();
464
- /**
465
- * Holds the window at `top` while the next screen settles, whenever the page
466
- * can hold it, until the reader scrolls, touches or types, or another
467
- * navigation starts.
468
- */
469
- const settle = (top) => {
470
- settling.abort();
471
- settling = new AbortController();
472
- const { signal } = settling;
473
- for (const input of [
474
- "wheel",
475
- "touchstart",
476
- "keydown",
477
- "pointerdown"
478
- ]) window.addEventListener(input, () => settling.abort(), {
479
- passive: true,
480
- signal
481
- });
482
- const until = performance.now() + patience;
483
- const step = () => {
484
- if (signal.aborted) return;
485
- if (document.documentElement.scrollHeight - window.innerHeight >= top && Math.round(window.scrollY) !== top) window.scrollTo({
486
- top,
487
- behavior: "instant"
488
- });
489
- if (performance.now() < until) requestAnimationFrame(step);
490
- else settling.abort();
491
- };
492
- step();
493
- };
494
- const acted = () => remember();
495
- for (const input of ["click", "keydown"]) on(window, input, acted, true);
496
- const traversing = (event) => {
497
- if (Option.contains(navigationTypeOf(event), "traverse")) remember();
498
- };
499
- on(navigation, "navigate", traversing);
500
- const changed = (event) => {
501
- const type = navigationTypeOf(event);
502
- if (Option.contains(type, "push")) settle(0);
503
- else if (Option.contains(type, "traverse")) settle(Option.getOrElse(savedHere(), () => 0));
504
- };
505
- on(navigation, "currententrychange", changed);
506
- const hidden = () => {
507
- remember();
508
- try {
509
- sessionStorage.setItem(storageKey, JSON.stringify([...saved]));
510
- } catch {}
511
- };
512
- on(window, "pagehide", hidden);
513
- const here = savedHere();
514
- if (Option.isSome(here)) settle(here.value);
515
- return () => {
516
- settling.abort();
517
- for (const detach of detaches) detach();
518
- window.history.scrollRestoration = restoration;
519
- };
520
- };
521
- /**
522
- * Keeps the window's scroll across same-document navigations: a pushed entry
523
- * starts at the top, and Back, Forward or a reload return where that entry
524
- * was. Without a window the stream is empty instead of throwing. Lift with
525
- * `Subscription.persistentEntry`; the stream sends no Messages.
526
- */
527
- const keepScroll = (options = {}) => {
528
- if (typeof window === "undefined") return Stream.empty;
529
- const storageKey = options.storageKey ?? "foldkit:scroll";
530
- const kept = options.kept ?? 50;
531
- const patience = options.patience ?? 2e3;
532
- return Stream.callback(() => Effect.acquireRelease(Effect.sync(() => attach(storageKey, kept, patience)), (detach) => Effect.sync(detach)));
533
- };
534
- //#endregion
535
- //#region src/dom/pointed-links.ts
536
- /**
537
- * The address of the link a reader points at or tabs to, on the page's own
538
- * origin: where they may go next, for a `Data.preload` before they go. Each
539
- * link once while they stay on it, since crossing its text sends one event
540
- * per element inside it. Without a document the stream is empty. Lift with
541
- * `Subscription.persistentEntry`, mapping each address to a Message.
542
- */
543
- const pointedLinks = () => {
544
- if (typeof document === "undefined") return Stream.empty;
545
- const linkOf = (event) => {
546
- const link = event.target instanceof Element ? event.target.closest("a[href]") : null;
547
- return link instanceof HTMLAnchorElement && link.origin === window.location.origin ? fromString(link.href) : Option.none();
548
- };
549
- return Stream.merge(Dom.streamFromEventFilterMap({
550
- target: document,
551
- type: "pointerover",
552
- filterMapEvent: linkOf
553
- }), Dom.streamFromEventFilterMap({
554
- target: document,
555
- type: "focusin",
556
- filterMapEvent: linkOf
557
- })).pipe(Stream.changesWith((a, b) => toString(a) === toString(b)));
558
- };
559
- //#endregion
560
- //#region src/dom/fluid-hover.ts
561
- /** The custom properties `FluidHover` writes on its container for a highlight named `name`. */
562
- const fluidHovered = (name) => ({
563
- x: `--fk-${name}-x`,
564
- y: `--fk-${name}-y`,
565
- w: `--fk-${name}-w`,
566
- h: `--fk-${name}-h`,
567
- /** `1` while an item is highlighted, `0` once the pointer and focus have left. */
568
- shown: `--fk-${name}-shown`,
569
- /**
570
- * `0` for the frame the highlight appears in, then `1`: a highlight's
571
- * movement scales its duration by it, so it appears on its item rather
572
- * than sliding in from where it last was.
573
- */
574
- moving: `--fk-${name}-moving`
575
- });
576
- /**
577
- * The attribute `FluidHover` sets on its container, to the highlight's name,
578
- * while it runs: a stylist draws the highlight, and drops each item's own
579
- * hover, only under it.
580
- */
581
- const FLUID_HOVER_ATTRIBUTE = "data-fluid-hover";
582
- /** The attribute that names, inside an item, the control a press in a gap reaches. */
583
- const FLUID_PRESS_ATTRIBUTE = "data-fluid-press";
584
- /** The attribute on the item the highlight is shown on, for what CSS draws beside it. */
585
- const FLUID_LIT_ATTRIBUTE = "data-fluid-lit";
586
- /**
587
- * The attribute `FluidHover` sets on its container, to the highlight's name,
588
- * while the item that highlight is on is held. A stylist draws the press on
589
- * the highlight (`data-fluid-held`) rather than on the item, whose own press
590
- * is hidden under it; a press in a gap marks it too where `gapClick` sends
591
- * that press to the lit item.
592
- */
593
- const FLUID_HELD_ATTRIBUTE = "data-fluid-held";
594
- const Axis = Schema.Literals([
595
- "x",
596
- "y",
597
- "xy"
598
- ]);
599
- /** How far a point is from a rectangle along each axis: zero inside it. */
600
- const gap = (low, high, at) => Math.max(low - at, 0, at - high);
601
- const away = {
602
- x: (dx, dy) => dx * 1e6 + dy,
603
- y: (dx, dy) => dy * 1e6 + dx,
604
- xy: Math.hypot
605
- };
606
- const distance = (rect, x, y, axis) => away[axis](gap(rect.left, rect.right, x), gap(rect.top, rect.bottom, y));
607
- const isDisabled = (item) => item.matches("[aria-disabled=\"true\"], [data-disabled]:not([data-disabled=\"false\"]), :disabled");
608
- /**
609
- * One highlight that follows the pointer across a container's items, as a
610
- * Mount, with no Message and no Model: which item the pointer is nearest is
611
- * presentation, known to the DOM and to no transition.
612
- *
613
- * The item nearest the pointer is the one highlighted, so the pointer in a gap
614
- * between two items still points at one, and the highlight moves from item to
615
- * item instead of blinking off between them. On the `y` axis nearness is read
616
- * vertically (a list), on `x` horizontally (a strip), on `xy` both (a grid). A
617
- * disabled item (`aria-disabled="true"`, `data-disabled`, `:disabled`) is never
618
- * highlighted. An item reached by the keyboard (`:focus-visible`) is
619
- * highlighted too, and with `focused`, the item that selector finds from a
620
- * focused element that is no item (a tree's row, from its focused
621
- * `treeitem`, which also holds the rows beneath it); a touch highlights
622
- * nothing, since there is no pointer to
623
- * follow before the press.
624
- *
625
- * The container gets `--fk-<name>-x`, `-y`, `-w` and `-h` in pixels, relative
626
- * to its scroll box like `Measure`'s, and `-shown` and `-moving` (see
627
- * `fluidHovered`); a view draws the highlight as an absolutely placed child
628
- * that reads them. With `gapClick`, a click in a gap presses the highlighted
629
- * item: its descendant marked `data-fluid-press`, or the item itself; with
630
- * `gapReach` too, only a click within that many pixels of it, as a grid of
631
- * cards with wide gutters wants (Fluid Functionalism's uses 16). A popup
632
- * measured before it has a layout box keeps its last complete place, and
633
- * tries again for three frames, rather than drawing the highlight at zero.
634
- * The item the highlight is shown on carries `data-fluid-lit`, so a style
635
- * can change what sits beside it (a table hides the lines around the lit
636
- * row); its container carries `data-fluid-held` while that item is pressed.
637
- *
638
- * `follows` names the item the highlight sits on while the pointer is not
639
- * moving it: a menu marks the row its keys reach (`[data-active]`), and the
640
- * highlight goes there whenever that mark moves, so the keys and the pointer
641
- * light the same one. `from` names the item a fresh highlight grows out of,
642
- * as a tab's hover leaves the chosen tab. A highlight always fades where it
643
- * is: once the pointer leaves, nothing should move. It stays only for an item
644
- * the keyboard reached (`:focus-visible`), not one a click focused.
645
- */
646
- const FluidHover = Mount.defineStream("FluidHover", {
647
- messages: [Schema.Never],
648
- args: {
649
- /** The highlight's name, which names the custom properties. */
650
- name: Schema.String,
651
- /** The selector of an item within the container. */
652
- item: Schema.String,
653
- axis: Axis,
654
- gapClick: Schema.Boolean,
655
- /** How far from the highlighted item, in pixels, a click in a gap still presses it. */
656
- gapReach: Schema.optional(Schema.Number),
657
- /** The selector of the item the highlight sits on while the pointer is not moving it. */
658
- follows: Schema.optional(Schema.String),
659
- /** The selector of the item a fresh highlight grows out of. */
660
- from: Schema.optional(Schema.String),
661
- /** The selector, run from a focused element outside any item, of the item it lights. */
662
- focused: Schema.optional(Schema.String)
663
- },
664
- execute: ({ element, name, item: selector, axis, gapClick, gapReach, follows, from, focused }) => Stream.callback(() => Effect.acquireRelease(Effect.sync(() => {
665
- const container = element;
666
- const property = fluidHovered(name);
667
- let active = null;
668
- let followed = null;
669
- let pointer = null;
670
- let frame;
671
- const origin = () => from === void 0 ? null : container.querySelector(from);
672
- let retries = 0;
673
- const place = (at = active) => {
674
- if (at === null) return;
675
- const box = container.getBoundingClientRect();
676
- const rect = at.getBoundingClientRect();
677
- if (rect.width === 0 && rect.height === 0) {
678
- if (retries < 3) {
679
- retries += 1;
680
- requestAnimationFrame(() => place(at));
681
- }
682
- return;
683
- }
684
- retries = 0;
685
- const x = rect.left - box.left - container.clientLeft + container.scrollLeft;
686
- const y = rect.top - box.top - container.clientTop + container.scrollTop;
687
- container.style.setProperty(property.x, `${x}px`);
688
- container.style.setProperty(property.y, `${y}px`);
689
- container.style.setProperty(property.w, `${rect.width}px`);
690
- container.style.setProperty(property.h, `${rect.height}px`);
691
- };
692
- /** Marks `item` as the lit one, and nothing else. */
693
- const light = (item) => {
694
- container.querySelectorAll(`[${FLUID_LIT_ATTRIBUTE}]`).forEach((each) => each !== item && each.removeAttribute("data-fluid-lit"));
695
- item?.setAttribute(FLUID_LIT_ATTRIBUTE, "");
696
- };
697
- const show = (next) => {
698
- const appearing = container.style.getPropertyValue(property.shown) !== "1";
699
- active = next;
700
- light(next);
701
- const grows = appearing ? origin() : null;
702
- if (appearing) container.style.setProperty(property.moving, "0");
703
- if (grows === null) {
704
- place();
705
- container.style.setProperty(property.shown, "1");
706
- } else {
707
- place(grows);
708
- requestAnimationFrame(() => {
709
- if (active !== next) return;
710
- place();
711
- container.style.setProperty(property.shown, "1");
712
- });
713
- }
714
- if (appearing) requestAnimationFrame(() => container.style.setProperty(property.moving, "1"));
715
- };
716
- const hide = () => {
717
- container.style.setProperty(property.shown, "0");
718
- container.removeAttribute(FLUID_HELD_ATTRIBUTE);
719
- light(null);
720
- };
721
- /** Whether the keyboard has an item focused, which the highlight then stays on. */
722
- const keyed = () => {
723
- const focused = document.activeElement;
724
- return focused !== null && container.contains(focused) && focused.matches(":focus-visible");
725
- };
726
- const nearest = (x, y) => {
727
- let best = null;
728
- let bestDistance = Infinity;
729
- for (const each of Array.from(container.querySelectorAll(selector))) {
730
- if (isDisabled(each)) continue;
731
- const far = distance(each.getBoundingClientRect(), x, y, axis);
732
- if (far < bestDistance) {
733
- best = each;
734
- bestDistance = far;
735
- }
736
- }
737
- return best;
738
- };
739
- const follow = () => {
740
- frame = void 0;
741
- if (pointer === null) return;
742
- const next = nearest(pointer.x, pointer.y);
743
- if (next === null) hide();
744
- else if (next !== active || container.style.getPropertyValue(property.shown) !== "1") show(next);
745
- };
746
- const move = (event) => {
747
- if (event.pointerType === "touch") return;
748
- pointer = {
749
- x: event.clientX,
750
- y: event.clientY
751
- };
752
- if (frame === void 0) frame = requestAnimationFrame(follow);
753
- };
754
- const leave = () => {
755
- pointer = null;
756
- release();
757
- if (!keyed()) hide();
758
- };
759
- const focus = (event) => {
760
- if (!(event.target instanceof Element) || !event.target.matches(":focus-visible")) return;
761
- const owner = event.target.closest(selector) ?? (focused === void 0 ? null : event.target.querySelector(focused));
762
- if (owner !== null && container.contains(owner) && !isDisabled(owner)) show(owner);
763
- };
764
- const blur = (event) => {
765
- const to = event.relatedTarget;
766
- if (pointer === null && !(to instanceof Node && container.contains(to))) hide();
767
- };
768
- const reachOf = (item, event) => gapReach === void 0 || distance(item.getBoundingClientRect(), event.clientX, event.clientY, "xy") <= gapReach;
769
- /** Whether a click in a gap presses the lit item, within `gapReach` of it. */
770
- const reaches = (item, event) => gapClick && reachOf(item, event);
771
- const click = (event) => {
772
- if (active === null) return;
773
- if (container.style.getPropertyValue(property.shown) !== "1") return;
774
- const target = event.target;
775
- if (!(target instanceof Element) || target.closest(selector) !== null) return;
776
- if (!reaches(active, event)) return;
777
- const press = active.querySelector(`[data-fluid-press]`) ?? active;
778
- if (press instanceof HTMLElement) press.click();
779
- };
780
- const soon = () => {
781
- if (active !== null) requestAnimationFrame(() => place());
782
- };
783
- /**
784
- * Marks the container as held where the press is the lit item's: the
785
- * item itself, or a gap a click there reaches through (`gapClick`). A
786
- * press on another item is that item's, once the pointer has lit it.
787
- */
788
- const press = (event) => {
789
- if (event.button !== 0 || active === null) return;
790
- if (container.style.getPropertyValue(property.shown) !== "1") return;
791
- const target = event.target;
792
- if (!(target instanceof Element)) return;
793
- const inItem = target.closest(selector) !== null;
794
- if (inItem && !active.contains(target)) return;
795
- if (!inItem && !reaches(active, event)) return;
796
- container.setAttribute(FLUID_HELD_ATTRIBUTE, name);
797
- };
798
- const release = () => container.removeAttribute(FLUID_HELD_ATTRIBUTE);
799
- /** The marked item moved: the highlight goes with it, or fades if it is gone. */
800
- const marked = () => {
801
- if (follows === void 0) return;
802
- const next = container.querySelector(follows);
803
- if (next === followed) return;
804
- followed = next;
805
- if (next !== null && !isDisabled(next)) show(next);
806
- else if (pointer === null && !keyed()) hide();
807
- };
808
- const sizes = typeof ResizeObserver === "undefined" ? void 0 : new ResizeObserver(soon);
809
- const marks = follows === void 0 ? void 0 : new MutationObserver(marked);
810
- sizes?.observe(container);
811
- marks?.observe(container, {
812
- subtree: true,
813
- childList: true,
814
- attributes: true
815
- });
816
- container.addEventListener("pointermove", move, { passive: true });
817
- container.addEventListener("pointerdown", press);
818
- container.addEventListener("pointerleave", leave);
819
- window.addEventListener("pointerup", release);
820
- window.addEventListener("pointercancel", release);
821
- container.addEventListener("focusin", focus);
822
- container.addEventListener("focusout", blur);
823
- container.addEventListener("click", click);
824
- container.addEventListener("scroll", soon, { passive: true });
825
- window.addEventListener("resize", soon);
826
- container.style.setProperty(property.shown, "0");
827
- container.setAttribute(FLUID_HOVER_ATTRIBUTE, name);
828
- marked();
829
- return () => {
830
- if (frame !== void 0) cancelAnimationFrame(frame);
831
- sizes?.disconnect();
832
- marks?.disconnect();
833
- container.removeAttribute(FLUID_HOVER_ATTRIBUTE);
834
- container.removeAttribute(FLUID_HELD_ATTRIBUTE);
835
- light(null);
836
- container.removeEventListener("pointermove", move);
837
- container.removeEventListener("pointerdown", press);
838
- container.removeEventListener("pointerleave", leave);
839
- window.removeEventListener("pointerup", release);
840
- window.removeEventListener("pointercancel", release);
841
- container.removeEventListener("focusin", focus);
842
- container.removeEventListener("focusout", blur);
843
- container.removeEventListener("click", click);
844
- container.removeEventListener("scroll", soon);
845
- window.removeEventListener("resize", soon);
846
- };
847
- }), (stop) => Effect.sync(stop)))
848
- });
849
- //#endregion
850
- //#region src/dom/keyboard-inset.ts
851
- /**
852
- * The custom property `KeyboardInset` writes on its element: how much of the layout
853
- * viewport's bottom the on-screen keyboard (or anything else the visual viewport leaves
854
- * out) covers, in pixels.
855
- */
856
- const KEYBOARD_INSET = "--fk-keyboard-inset";
857
- /**
858
- * The part of the layout viewport's height below the visual viewport: what the on-screen
859
- * keyboard covers. Rounded down, since a fraction of a pixel of zoom is no keyboard.
860
- */
861
- const keyboardInset = (view) => view.visualViewport === null ? 0 : Math.max(0, Math.floor(view.innerHeight - view.visualViewport.height - view.visualViewport.offsetTop));
862
- /**
863
- * The on-screen keyboard's height as a Mount, with no Message and no Model: where it is
864
- * is the browser's. iOS Safari, and Chrome on Android unless the page asks otherwise,
865
- * leave the layout viewport as it is when the keyboard opens, so a panel fixed to the
866
- * screen keeps its height and its bottom under the keys. This writes `KEYBOARD_INSET` on
867
- * the element, from the visual viewport's size and scroll, for a style to pad or shrink by
868
- * (`var(--fk-keyboard-inset, 0px)`), and removes it when the element goes. Where there is
869
- * no visual viewport it writes nothing.
870
- */
871
- const KeyboardInset = Mount.defineStream("KeyboardInset", {
872
- messages: [Schema.Never],
873
- args: {},
874
- execute: ({ element }) => Stream.callback(() => Effect.acquireRelease(Effect.sync(() => {
875
- const view = element.ownerDocument.defaultView;
876
- const viewport = view?.visualViewport;
877
- if (!(element instanceof HTMLElement) || view === null || view === void 0 || viewport === null || viewport === void 0) return () => {};
878
- const target = element;
879
- const measure = () => target.style.setProperty(KEYBOARD_INSET, `${keyboardInset(view)}px`);
880
- measure();
881
- viewport.addEventListener("resize", measure);
882
- viewport.addEventListener("scroll", measure);
883
- return () => {
884
- viewport.removeEventListener("resize", measure);
885
- viewport.removeEventListener("scroll", measure);
886
- target.style.removeProperty(KEYBOARD_INSET);
887
- };
888
- }), (release) => Effect.sync(release)))
889
- });
890
- //#endregion
891
- //#region src/dom/scroll-bars.ts
892
- /**
893
- * The attribute that marks a bar beside a scroller `ScrollBars` runs on, set
894
- * to the axis it shows: `block` (the up-and-down bar) or `inline`.
895
- */
896
- const SCROLL_BAR_ATTRIBUTE = "data-scroll-bar";
897
- /**
898
- * The attribute `ScrollBars` sets on its scroller while it draws the bars, so a
899
- * style hides the native bar only then: with no Mount running (a coarse
900
- * pointer, a page not yet hydrated) the browser's bar stays.
901
- */
902
- const SCROLL_OVERLAID_ATTRIBUTE = "data-scroll-overlaid";
903
- /**
904
- * The custom properties `ScrollBars` writes on each bar, in pixels, for its
905
- * thumb to read: how far along the bar the thumb starts, and how long it is.
906
- * Physical: an inline bar's offset is from its left edge in either direction.
907
- */
908
- const scrollThumb = {
909
- offset: "--fk-scroll-thumb-offset",
910
- size: "--fk-scroll-thumb-size"
911
- };
912
- /** The shortest a thumb is drawn, however long the content, so it stays a target. */
913
- const SHORTEST = 20;
914
- const FINE_POINTER = "(hover: hover) and (pointer: fine)";
915
- /**
916
- * An overlay scrollbar as a Mount, with no Message and no Model: where a
917
- * scroller is scrolled is the browser's, and the bar only shows it.
918
- *
919
- * It runs on the scroller (or on an element holding it, named by `scroller`,
920
- * when the scroller carries another Mount) and drives the bars drawn beside
921
- * it: the scroller's parent's children marked `data-scroll-bar` (`block` or
922
- * `inline`), which a style lays over the scroller's edge. Each bar gets `data-scrollable` while its axis has
923
- * more to scroll, `data-shown` while the scroller scrolls or the pointer moves
924
- * over it, until `idle` milliseconds pass with neither (or longer, while the
925
- * pointer rests on a bar), and `data-dragging` while its thumb is dragged; and
926
- * `--fk-scroll-thumb-offset` and `-size` (see `scrollThumb`). Pressing a bar
927
- * off its thumb moves the thumb there, then drags it.
928
- *
929
- * It does nothing where the primary pointer is coarse or cannot hover, decided
930
- * when it mounts: touch keeps the browser's bar and its physics. Otherwise the
931
- * scroller gets `data-scroll-overlaid`, for a style to hide the native bar.
932
- * How the bar looks, rests, widens and fades is the style's.
933
- */
934
- const ScrollBars = Mount.defineStream("ScrollBars", {
935
- messages: [Schema.Never],
936
- args: {
937
- /** How long, in milliseconds, the bars stay after the last scroll or move. Default 600. */
938
- idle: Schema.optional(Schema.Number),
939
- /**
940
- * The scroller, as a selector within the element, when it cannot hold this
941
- * Mount itself (another component's, with a Mount of its own); looked for
942
- * once, when it mounts. Default: the element is the scroller.
943
- */
944
- scroller: Schema.optional(Schema.String)
945
- },
946
- execute: ({ element, idle = 600, scroller: within }) => Stream.callback(() => Effect.acquireRelease(Effect.sync(() => {
947
- if (typeof matchMedia !== "function" || !matchMedia(FINE_POINTER).matches) return () => {};
948
- const found = within === void 0 ? element : element.querySelector(within);
949
- if (!(found instanceof HTMLElement)) return () => {};
950
- const scroller = found;
951
- const area = scroller.parentElement;
952
- if (area === null) return () => {};
953
- const isBar = (child) => child instanceof HTMLElement && child.hasAttribute("data-scroll-bar");
954
- /** Looked for at each use, so a redraw that replaced a bar is followed. */
955
- const bars = () => Array.from(area.children).filter(isBar);
956
- const axisOf = (bar) => bar.getAttribute("data-scroll-bar") === "inline" ? "inline" : "block";
957
- /** The scroller along one axis, with the start measured from the top or the left. */
958
- const extent = (axis) => {
959
- if (axis === "block") return {
960
- view: scroller.clientHeight,
961
- content: scroller.scrollHeight,
962
- start: scroller.scrollTop
963
- };
964
- const room = scroller.scrollWidth - scroller.clientWidth;
965
- const rtl = getComputedStyle(scroller).direction === "rtl";
966
- return {
967
- view: scroller.clientWidth,
968
- content: scroller.scrollWidth,
969
- start: rtl ? room + scroller.scrollLeft : scroller.scrollLeft
970
- };
971
- };
972
- const scrollTo = (axis, start) => {
973
- if (axis === "block") {
974
- scroller.scrollTop = start;
975
- return;
976
- }
977
- const room = scroller.scrollWidth - scroller.clientWidth;
978
- const rtl = getComputedStyle(scroller).direction === "rtl";
979
- scroller.scrollLeft = rtl ? start - room : start;
980
- };
981
- const trackOf = (bar, axis) => axis === "block" ? bar.clientHeight : bar.clientWidth;
982
- /** Where the thumb is along its bar, or nothing while the axis has nothing to scroll. */
983
- const thumbOf = (bar, axis) => {
984
- const { view, content, start } = extent(axis);
985
- const track = trackOf(bar, axis);
986
- if (content - view < 1 || track <= 0) return null;
987
- const size = Math.min(track, Math.max(SHORTEST, track * view / content));
988
- return {
989
- track,
990
- size,
991
- offset: (track - size) * start / (content - view),
992
- room: content - view
993
- };
994
- };
995
- const set = (bar, name, value) => {
996
- if (bar.style.getPropertyValue(name) !== value) bar.style.setProperty(name, value);
997
- };
998
- const mark = (bar, name, on) => {
999
- if (bar.hasAttribute(name) !== on) bar.toggleAttribute(name, on);
1000
- };
1001
- let frame;
1002
- const update = () => {
1003
- if (frame !== void 0) cancelAnimationFrame(frame);
1004
- frame = void 0;
1005
- for (const bar of bars()) {
1006
- const axis = axisOf(bar);
1007
- const { view, content } = extent(axis);
1008
- mark(bar, "data-scrollable", content - view >= 1);
1009
- const thumb = thumbOf(bar, axis);
1010
- if (thumb === null) continue;
1011
- set(bar, scrollThumb.offset, `${thumb.offset}px`);
1012
- set(bar, scrollThumb.size, `${thumb.size}px`);
1013
- }
1014
- };
1015
- const soon = () => {
1016
- if (frame === void 0) frame = requestAnimationFrame(update);
1017
- };
1018
- let hiding;
1019
- let resting = null;
1020
- /** Ends the drag under way, if one is. */
1021
- let dragging = null;
1022
- const hide = () => {
1023
- hiding = void 0;
1024
- if (resting !== null || dragging !== null) return;
1025
- for (const bar of bars()) mark(bar, "data-shown", false);
1026
- };
1027
- const show = () => {
1028
- for (const bar of bars()) mark(bar, "data-shown", true);
1029
- if (hiding !== void 0) clearTimeout(hiding);
1030
- hiding = setTimeout(hide, idle);
1031
- };
1032
- const scrolled = () => {
1033
- update();
1034
- show();
1035
- };
1036
- const moved = (event) => {
1037
- if (event.pointerType !== "touch") show();
1038
- };
1039
- const entered = (event) => {
1040
- if (event.pointerType === "touch") return;
1041
- area.addEventListener("pointermove", moved, { passive: true });
1042
- show();
1043
- };
1044
- const left = () => {
1045
- area.removeEventListener("pointermove", moved);
1046
- resting = null;
1047
- if (hiding === void 0) hiding = setTimeout(hide, idle);
1048
- };
1049
- const over = (event) => {
1050
- const target = event.target;
1051
- resting = target instanceof Element ? bars().find((bar) => bar.contains(target)) ?? null : null;
1052
- };
1053
- const pressed = (event) => {
1054
- if (event.button !== 0 || event.pointerType === "touch" || dragging !== null) return;
1055
- const target = event.target;
1056
- if (!(target instanceof Element)) return;
1057
- const bar = bars().find((each) => each.contains(target));
1058
- if (bar === void 0) return;
1059
- const axis = axisOf(bar);
1060
- const thumb = thumbOf(bar, axis);
1061
- if (thumb === null) return;
1062
- event.preventDefault();
1063
- const along = (at) => {
1064
- const box = bar.getBoundingClientRect();
1065
- return axis === "block" ? at.clientY - box.top - bar.clientTop : at.clientX - box.left - bar.clientLeft;
1066
- };
1067
- const at = along(event);
1068
- const onThumb = at >= thumb.offset && at <= thumb.offset + thumb.size;
1069
- const grab = onThumb ? at - thumb.offset : thumb.size / 2;
1070
- const follow = (to) => {
1071
- const current = thumbOf(bar, axis);
1072
- if (current === null) return;
1073
- const offset = Math.min(Math.max(along(to) - grab, 0), current.track - current.size);
1074
- scrollTo(axis, offset / (current.track - current.size) * current.room);
1075
- };
1076
- const id = event.pointerId;
1077
- const move = (to) => {
1078
- if (to.pointerId === id) follow(to);
1079
- };
1080
- const end = (to) => {
1081
- if (to.pointerId === id) stop();
1082
- };
1083
- const owner = bar.ownerDocument;
1084
- const stop = () => {
1085
- owner.removeEventListener("pointermove", move);
1086
- owner.removeEventListener("pointerup", end);
1087
- owner.removeEventListener("pointercancel", end);
1088
- mark(bar, "data-dragging", false);
1089
- dragging = null;
1090
- show();
1091
- };
1092
- owner.addEventListener("pointermove", move);
1093
- owner.addEventListener("pointerup", end);
1094
- owner.addEventListener("pointercancel", end);
1095
- mark(bar, "data-dragging", true);
1096
- dragging = stop;
1097
- show();
1098
- if (!onThumb) follow(event);
1099
- };
1100
- const sizes = new ResizeObserver(soon);
1101
- const changes = new MutationObserver((records) => {
1102
- for (const record of records) {
1103
- for (const node of Array.from(record.removedNodes)) if (node instanceof Element) sizes.unobserve(node);
1104
- for (const node of Array.from(record.addedNodes)) if (node instanceof Element && (record.target === scroller || isBar(node))) sizes.observe(node);
1105
- }
1106
- soon();
1107
- });
1108
- sizes.observe(scroller);
1109
- for (const child of Array.from(scroller.children)) sizes.observe(child);
1110
- for (const bar of bars()) sizes.observe(bar);
1111
- changes.observe(scroller, { childList: true });
1112
- changes.observe(area, { childList: true });
1113
- scroller.addEventListener("scroll", scrolled, { passive: true });
1114
- area.addEventListener("pointerenter", entered);
1115
- area.addEventListener("pointerleave", left);
1116
- area.addEventListener("pointerover", over);
1117
- area.addEventListener("pointerdown", pressed);
1118
- scroller.setAttribute(SCROLL_OVERLAID_ATTRIBUTE, "");
1119
- update();
1120
- return () => {
1121
- dragging?.();
1122
- if (frame !== void 0) cancelAnimationFrame(frame);
1123
- if (hiding !== void 0) clearTimeout(hiding);
1124
- sizes.disconnect();
1125
- changes.disconnect();
1126
- scroller.removeEventListener("scroll", scrolled);
1127
- area.removeEventListener("pointerenter", entered);
1128
- area.removeEventListener("pointerleave", left);
1129
- area.removeEventListener("pointerover", over);
1130
- area.removeEventListener("pointerdown", pressed);
1131
- area.removeEventListener("pointermove", moved);
1132
- scroller.removeAttribute(SCROLL_OVERLAID_ATTRIBUTE);
1133
- for (const bar of bars()) {
1134
- for (const name of [
1135
- "data-scrollable",
1136
- "data-shown",
1137
- "data-dragging"
1138
- ]) bar.removeAttribute(name);
1139
- bar.style.removeProperty(scrollThumb.offset);
1140
- bar.style.removeProperty(scrollThumb.size);
1141
- }
1142
- };
1143
- }), (stop) => Effect.sync(stop)))
1144
- });
1145
- //#endregion
1146
- //#region src/dom/swipe.ts
1147
- const SwipeStarted = Schema.TaggedStruct("SwipeStarted", {
1148
- pointerId: Schema.Number,
1149
- pointerType: Schema.String,
1150
- /** The border box of what `measure` names (the element itself by default), as the swipe starts. */
1151
- width: Schema.Number,
1152
- height: Schema.Number
1153
- });
1154
- const Swiped = Schema.TaggedStruct("Swiped", {
1155
- pointerId: Schema.Number,
1156
- /** From where the pointer went down, in CSS pixels. */
1157
- deltaX: Schema.Number,
1158
- deltaY: Schema.Number
1159
- });
1160
- const SwipeEnded = Schema.TaggedStruct("SwipeEnded", {
1161
- pointerId: Schema.Number,
1162
- /** From where the pointer went down to where it came up, in CSS pixels. */
1163
- deltaX: Schema.Number,
1164
- deltaY: Schema.Number,
1165
- /**
1166
- * How fast the pointer was moving as it came up, in pixels a millisecond,
1167
- * over the last `SWIPE_WINDOW` milliseconds: 0 when it rested there first.
1168
- */
1169
- velocityX: Schema.Number,
1170
- velocityY: Schema.Number,
1171
- /** `false` when the pointer was cancelled or capture was lost. */
1172
- completed: Schema.Boolean
1173
- });
1174
- /** How far back, in milliseconds, the release velocity looks. */
1175
- const SWIPE_WINDOW = 100;
1176
- /**
1177
- * What a swipe never starts on when its element `scrolls`: a field whose own
1178
- * drag selects text or moves a thumb, and anything marked `data-no-swipe`.
1179
- */
1180
- const NO_SWIPE = "input, textarea, select, [contenteditable=\"\"], [contenteditable=\"true\"], [data-no-swipe]";
1181
- /** Every element a Swipe is mounted on: a touch inside one is that one's, not an outer one's. */
1182
- const owners = /* @__PURE__ */ new WeakSet();
1183
- /**
1184
- * The velocity of the samples within `SWIPE_WINDOW` of the last: the way
1185
- * from the oldest of them to the last over the time between. A pointer that
1186
- * came to rest before the last sample has no recent way to measure, so 0.
1187
- */
1188
- const velocityOf = (samples) => {
1189
- const last = samples.at(-1);
1190
- if (last === void 0) return {
1191
- x: 0,
1192
- y: 0
1193
- };
1194
- const first = samples.find((sample) => last.at - sample.at <= 100) ?? last;
1195
- const elapsed = last.at - first.at;
1196
- return elapsed <= 0 ? {
1197
- x: 0,
1198
- y: 0
1199
- } : {
1200
- x: (last.x - first.x) / elapsed,
1201
- y: (last.y - first.y) / elapsed
1202
- };
1203
- };
1204
- /**
1205
- * Whether anything from `target` up to `root` can still scroll along `axis`
1206
- * the way a finger moving `along` (positive is down or right) pulls it: a
1207
- * finger going down scrolls content toward its start.
1208
- */
1209
- const canScroll = (target, root, axis, along) => {
1210
- for (let at = target; at !== null; at = at.parentElement) {
1211
- const overflow = getComputedStyle(at)[axis === "y" ? "overflowY" : "overflowX"];
1212
- if (overflow === "auto" || overflow === "scroll") {
1213
- const position = Math.abs(axis === "y" ? at.scrollTop : at.scrollLeft);
1214
- const room = axis === "y" ? at.scrollHeight - at.clientHeight : at.scrollWidth - at.clientWidth;
1215
- if (room > 1 && (along > 0 ? position > .5 : position < room - .5)) return true;
1216
- }
1217
- if (at === root) return false;
1218
- }
1219
- return false;
1220
- };
1221
- /**
1222
- * `Move` with the speed of its release, for a gesture that a flick ends
1223
- * (a sheet dragged to dismiss, a card swiped away): a primary-button pointer
1224
- * down (or, given a `slop`, its first move past it) captures the pointer,
1225
- * each move reports the delta from where it was pressed, and up reports the
1226
- * last delta with the velocity over the last `SWIPE_WINDOW` milliseconds, by
1227
- * the events' own time stamps. Cancel or lost capture ends it incomplete.
1228
- * The start reports the size of what is moved, so a distance can be read as
1229
- * a share of it. What a delta or a speed means is the parent's `update`.
1230
- * Capture is released with the Mount.
1231
- *
1232
- * Given `scrolls`, the element holds content that scrolls (a sheet's panel),
1233
- * and a touch is followed by touch events, whose move can still refuse the
1234
- * browser's scroll once the touch is under way, where a pointer's cannot:
1235
- * past the slop, a touch that runs along the axis and finds nothing under it
1236
- * that can scroll further its way swipes, and the browser scrolls any other.
1237
- * A touch inside an element with a Swipe of its own (a handle in the panel)
1238
- * is that one's, and none starts on a field or on `data-no-swipe`
1239
- * (`NO_SWIPE`).
1240
- */
1241
- const Swipe = Mount.defineStream("Swipe", {
1242
- messages: [
1243
- SwipeStarted,
1244
- Swiped,
1245
- SwipeEnded
1246
- ],
1247
- args: {
1248
- /**
1249
- * The nearest ancestor matching this selector is measured as the swipe
1250
- * starts, for what the swipe moves (a sheet's panel, dragged by its
1251
- * handle). Default: the element itself.
1252
- */
1253
- measure: Schema.optional(Schema.String),
1254
- /**
1255
- * How far, in pixels, a press must move before it is a swipe. Until
1256
- * then nothing is captured or reported, so a press that stays put is
1257
- * still a click on whatever is under it (a tab inside a drag grip).
1258
- * Default 0: the press itself starts the swipe; with `scrolls`, the
1259
- * first move decides.
1260
- */
1261
- slop: Schema.optional(Schema.Number),
1262
- /** The axis the element's content scrolls along, which a touch yields to; see above. */
1263
- scrolls: Schema.optional(Schema.Literals(["x", "y"])),
1264
- /** Follows touches alone: a mouse or a pen is left to the page (and to a handle's own Swipe). */
1265
- touchOnly: Schema.optional(Schema.Boolean)
1266
- },
1267
- execute: ({ element, measure, slop = 0, scrolls, touchOnly = false }) => Stream.callback((queue) => Effect.acquireRelease(Effect.sync(() => {
1268
- const target = element;
1269
- let active = null;
1270
- const offer = (fact) => Queue.offerUnsafe(queue, fact);
1271
- const sampleOf = (at, x = 0, y = 0) => ({
1272
- at,
1273
- x,
1274
- y
1275
- });
1276
- const end = (pointerId, last, completed, captured) => {
1277
- if (active === null || active.pointerId !== pointerId) return;
1278
- const { samples, started } = active;
1279
- active = null;
1280
- if (!started) return;
1281
- if (captured) try {
1282
- target.releasePointerCapture?.(pointerId);
1283
- } catch {}
1284
- const final = completed ? last : samples.at(-1) ?? last;
1285
- const all = completed ? [...samples, final] : samples;
1286
- const start = all[0] ?? final;
1287
- const velocity = completed ? velocityOf(all) : {
1288
- x: 0,
1289
- y: 0
1290
- };
1291
- offer(SwipeEnded.make({
1292
- pointerId,
1293
- deltaX: final.x - start.x,
1294
- deltaY: final.y - start.y,
1295
- velocityX: velocity.x,
1296
- velocityY: velocity.y,
1297
- completed
1298
- }));
1299
- };
1300
- const begin = (pointer, capture) => {
1301
- pointer.started = true;
1302
- if (capture) try {
1303
- target.setPointerCapture?.(pointer.pointerId);
1304
- } catch {}
1305
- const box = (measure === void 0 ? element : element.closest(measure) ?? element).getBoundingClientRect();
1306
- offer(SwipeStarted.make({
1307
- pointerId: pointer.pointerId,
1308
- pointerType: pointer.pointerType,
1309
- width: box.width,
1310
- height: box.height
1311
- }));
1312
- };
1313
- const moved = (pointer, sample) => {
1314
- const start = pointer.samples[0] ?? sample;
1315
- pointer.samples = [
1316
- start,
1317
- ...pointer.samples.slice(1).filter((each) => sample.at - each.at <= 100),
1318
- sample
1319
- ];
1320
- offer(Swiped.make({
1321
- pointerId: pointer.pointerId,
1322
- deltaX: sample.x - start.x,
1323
- deltaY: sample.y - start.y
1324
- }));
1325
- };
1326
- const ownTouch = (from) => {
1327
- if (!(from instanceof Element)) return false;
1328
- const field = from.closest(NO_SWIPE);
1329
- if (field !== null && element.contains(field)) return false;
1330
- for (let at = from; at !== null && at !== element; at = at.parentElement) if (owners.has(at)) return false;
1331
- return true;
1332
- };
1333
- const touchOf = (event, id) => Array.from(event.changedTouches).find((touch) => touch.identifier === id);
1334
- let touched = null;
1335
- const pointerListeners = [
1336
- ["pointerdown", (event) => {
1337
- const pointer = event;
1338
- const type = pointer.pointerType ?? "mouse";
1339
- if (scrolls !== void 0 && type === "touch") return;
1340
- if (touchOnly && type !== "touch") return;
1341
- if ((pointer.button ?? 0) !== 0 || active !== null) return;
1342
- active = {
1343
- pointerId: pointer.pointerId ?? 0,
1344
- pointerType: type,
1345
- samples: [sampleOf(pointer.timeStamp, pointer.clientX, pointer.clientY)],
1346
- started: false
1347
- };
1348
- if (slop <= 0) begin(active, true);
1349
- }],
1350
- ["pointermove", (event) => {
1351
- const pointer = event;
1352
- if (active === null || active.pointerId !== (pointer.pointerId ?? 0)) return;
1353
- const sample = sampleOf(pointer.timeStamp, pointer.clientX, pointer.clientY);
1354
- if (!active.started) {
1355
- const start = active.samples[0] ?? sample;
1356
- if (Math.hypot(sample.x - start.x, sample.y - start.y) < slop) return;
1357
- begin(active, true);
1358
- }
1359
- moved(active, sample);
1360
- }],
1361
- ...[
1362
- "pointerup",
1363
- "pointercancel",
1364
- "lostpointercapture"
1365
- ].map((type) => [type, (event) => {
1366
- const pointer = event;
1367
- if (active?.pointerType === "touch" && scrolls !== void 0) return;
1368
- end(pointer.pointerId ?? 0, sampleOf(pointer.timeStamp, pointer.clientX, pointer.clientY), type === "pointerup", true);
1369
- }])
1370
- ];
1371
- const touchListeners = scrolls === void 0 ? [] : [
1372
- ["touchstart", (event) => {
1373
- const touches = event;
1374
- const touch = touches.changedTouches[0];
1375
- if (active !== null || touches.touches.length !== 1 || touch === void 0) return;
1376
- if (!ownTouch(touches.target)) return;
1377
- touched = touches.target;
1378
- active = {
1379
- pointerId: touch.identifier,
1380
- pointerType: "touch",
1381
- samples: [sampleOf(touches.timeStamp, touch.clientX, touch.clientY)],
1382
- started: false
1383
- };
1384
- }],
1385
- ["touchmove", (event) => {
1386
- const touches = event;
1387
- if (active === null) return;
1388
- const touch = touchOf(touches, active.pointerId);
1389
- if (touch === void 0) return;
1390
- const sample = sampleOf(touches.timeStamp, touch.clientX, touch.clientY);
1391
- if (!active.started) {
1392
- const start = active.samples[0] ?? sample;
1393
- const dx = sample.x - start.x;
1394
- const dy = sample.y - start.y;
1395
- if (Math.hypot(dx, dy) < Math.max(slop, 1)) return;
1396
- const along = scrolls === "y" ? dy : dx;
1397
- if (Math.abs(scrolls === "y" ? dx : dy) > Math.abs(along) || canScroll(touched ?? element, element, scrolls, along) || !touches.cancelable) {
1398
- active = null;
1399
- return;
1400
- }
1401
- begin(active, false);
1402
- }
1403
- touches.preventDefault();
1404
- moved(active, sample);
1405
- }],
1406
- ...["touchend", "touchcancel"].map((type) => [type, (event) => {
1407
- const touches = event;
1408
- if (active === null) return;
1409
- const touch = touchOf(touches, active.pointerId);
1410
- if (touch === void 0) return;
1411
- end(active.pointerId, sampleOf(touches.timeStamp, touch.clientX, touch.clientY), type === "touchend", false);
1412
- }])
1413
- ];
1414
- owners.add(element);
1415
- for (const [type, listener] of pointerListeners) element.addEventListener(type, listener);
1416
- for (const [type, listener] of touchListeners) element.addEventListener(type, listener, { passive: false });
1417
- return {
1418
- pointerListeners,
1419
- touchListeners,
1420
- current: () => active
1421
- };
1422
- }), ({ pointerListeners, touchListeners, current }) => Effect.sync(() => {
1423
- owners.delete(element);
1424
- for (const [type, listener] of pointerListeners) element.removeEventListener(type, listener);
1425
- for (const [type, listener] of touchListeners) element.removeEventListener(type, listener);
1426
- const pointer = current();
1427
- if (pointer !== null && !(pointer.pointerType === "touch" && scrolls !== void 0)) try {
1428
- element.releasePointerCapture?.(pointer.pointerId);
1429
- } catch {}
1430
- })))
1431
- });
1432
- //#endregion
1433
- //#region src/dom/scroll-settle.ts
1434
- const ScrollSettled = Schema.TaggedStruct("ScrollSettled", {
1435
- /** The item whose start lies nearest the scroller's start, from 0. */
1436
- index: Schema.Number });
1437
- /** How long a scroll must be still to count as settled, where `scrollend` is missing. */
1438
- const SETTLE_QUIET = 120;
1439
- /**
1440
- * Hears a scroller come to rest, as a Mount: after a swipe, a fling, a
1441
- * trackpad or a scroll a Command made, it reports which of its items starts
1442
- * nearest its own start (inside its `scroll-padding`), the start being the
1443
- * right edge right to left. A snapping carousel reads it as the slide shown.
1444
- *
1445
- * At insert it first scrolls, at once, to item `start` (the Mount's args are
1446
- * read only then, so this is where a redrawn or restored scroller begins),
1447
- * and reports where it lies, since a scroller drawn already at rest sends no
1448
- * `scrollend`. It listens for `scrollend` where the browser has it, and
1449
- * otherwise for a scroll that is quiet for `SETTLE_QUIET` milliseconds.
1450
- */
1451
- const ScrollSettle = Mount.defineStream("ScrollSettle", {
1452
- messages: [ScrollSettled],
1453
- args: {
1454
- /** Which children are the items; default every child. */
1455
- item: Schema.optional(Schema.String),
1456
- /** Default `inline`, the row a carousel scrolls along. */
1457
- axis: Schema.optional(Schema.Literals(["inline", "block"])),
1458
- /** The item to scroll to at insert; default the first, which needs no scroll. */
1459
- start: Schema.optional(Schema.Number)
1460
- },
1461
- execute: ({ element, item, axis = "inline", start = 0 }) => Stream.callback((queue) => Effect.acquireRelease(Effect.sync(() => {
1462
- const items = () => Array.from(item === void 0 ? element.children : element.querySelectorAll(`:scope > ${item}`));
1463
- /** How far `child`'s start lies past the scroller's start, along the axis. */
1464
- const offsetOf = (child) => {
1465
- const style = getComputedStyle(element);
1466
- const box = element.getBoundingClientRect();
1467
- const own = child.getBoundingClientRect();
1468
- if (axis === "block") return own.top - box.top - (parseFloat(style.scrollPaddingBlockStart) || 0);
1469
- const padding = parseFloat(style.scrollPaddingInlineStart) || 0;
1470
- return style.direction === "rtl" ? box.right - padding - own.right : own.left - box.left - padding;
1471
- };
1472
- const report = () => {
1473
- let nearest = -1;
1474
- let distance = Infinity;
1475
- items().forEach((child, index) => {
1476
- const away = Math.abs(offsetOf(child));
1477
- if (away < distance) {
1478
- nearest = index;
1479
- distance = away;
1480
- }
1481
- });
1482
- if (nearest >= 0) Queue.offerUnsafe(queue, ScrollSettled.make({ index: nearest }));
1483
- };
1484
- const first = items()[start];
1485
- const before = element.scrollLeft + element.scrollTop;
1486
- if (start > 0 && first !== void 0) {
1487
- const by = offsetOf(first);
1488
- const rtl = axis === "inline" && getComputedStyle(element).direction === "rtl";
1489
- element.scrollTo(axis === "block" ? {
1490
- top: element.scrollTop + by,
1491
- behavior: "instant"
1492
- } : {
1493
- left: element.scrollLeft + (rtl ? -by : by),
1494
- behavior: "instant"
1495
- });
1496
- }
1497
- if (element.scrollLeft + element.scrollTop === before) report();
1498
- let quiet;
1499
- const hasEnd = "onscrollend" in element;
1500
- const listener = hasEnd ? report : () => {
1501
- clearTimeout(quiet);
1502
- quiet = setTimeout(report, 120);
1503
- };
1504
- const type = hasEnd ? "scrollend" : "scroll";
1505
- element.addEventListener(type, listener);
1506
- return () => {
1507
- clearTimeout(quiet);
1508
- element.removeEventListener(type, listener);
1509
- };
1510
- }), (detach) => Effect.sync(detach)))
1511
- });
1512
- //#endregion
1513
- //#region src/dom/pointer-position.ts
1514
- /**
1515
- * A place in an element's padding box, in CSS pixels from its top left
1516
- * corner, and the pointer's pressure there (0 to 1; a mouse reads 0.5 while
1517
- * a button is down). A captured pointer can be outside the box, so `x` and
1518
- * `y` can be negative or past its size.
1519
- */
1520
- const PointerPoint = Schema.Struct({
1521
- x: Schema.Number,
1522
- y: Schema.Number,
1523
- pressure: Schema.Number
1524
- });
1525
- /** The padding box's size at the event, in CSS pixels. */
1526
- const Size = {
1527
- width: Schema.Number,
1528
- height: Schema.Number
1529
- };
1530
- const PointerPressed = Schema.TaggedStruct("PointerPressed", {
1531
- ...Size,
1532
- point: PointerPoint,
1533
- /** `mouse`, `pen` or `touch`, as the browser names it. */
1534
- pointerType: Schema.String
1535
- });
1536
- const PointerMoved = Schema.TaggedStruct("PointerMoved", {
1537
- ...Size,
1538
- /** Where the pointer is now. */
1539
- point: PointerPoint,
1540
- /**
1541
- * Every place it passed since the last move, ending with `point`: the
1542
- * browser's coalesced events, so a fast stroke keeps its curve. Just
1543
- * `point` where the browser coalesces none.
1544
- */
1545
- points: Schema.Array(PointerPoint)
1546
- });
1547
- const PointerReleased = Schema.TaggedStruct("PointerReleased", {
1548
- /** `false` when the pointer was cancelled, lost its capture, or pressed again unreleased. */
1549
- completed: Schema.Boolean });
1550
- /**
1551
- * The padding box of an element whose border box is `rect`: inside its
1552
- * border, padding included. An absolutely positioned child (a thumb, a
1553
- * stroke) is laid out against it, so a place read here is where that child
1554
- * sits, whatever padding a stylist adds.
1555
- */
1556
- const paddingBoxOf = (layout) => {
1557
- const { rect, border } = layout;
1558
- return {
1559
- left: rect.left + border.left,
1560
- top: rect.top + border.top,
1561
- width: Math.max(0, rect.width - border.left - border.right),
1562
- height: Math.max(0, rect.height - border.top - border.bottom)
1563
- };
1564
- };
1565
- const borderOf = (style) => ({
1566
- top: parseFloat(style.borderTopWidth),
1567
- right: parseFloat(style.borderRightWidth),
1568
- bottom: parseFloat(style.borderBottomWidth),
1569
- left: parseFloat(style.borderLeftWidth)
1570
- });
1571
- /**
1572
- * An element's padding box now, in the viewport, as `PointerPosition`
1573
- * measures it: for a size kept beside its places, which a `ResizeObserver`'s
1574
- * content box does not match once the element has padding. Read from the
1575
- * bounding rectangle and the computed border, since `clientWidth` and
1576
- * `clientLeft` round to whole pixels.
1577
- */
1578
- const measurePaddingBox = (element) => paddingBoxOf({
1579
- rect: element.getBoundingClientRect(),
1580
- border: borderOf(getComputedStyle(element))
1581
- });
1582
- const pointIn = (box, event) => ({
1583
- x: event.clientX - box.left,
1584
- y: event.clientY - box.top,
1585
- pressure: event.pressure
1586
- });
1587
- /**
1588
- * Where one pointer is inside an element, as a Mount, for a surface the
1589
- * pointer draws on or points into (a color area, a dial, a signature pad): a
1590
- * primary press, each move until the release, and the release, each with
1591
- * the place in the element's padding box and the box's size. The box is
1592
- * measured at each event, so an element that moved or resized is followed.
1593
- * Coordinates are raw, left to right and top down whatever the direction:
1594
- * what a place means (a share, an angle, a reading direction) is the parent's.
1595
- *
1596
- * The pointer is captured from press to release, so a drag that leaves the
1597
- * element is still heard, and the moves and the release are listened to only
1598
- * meanwhile. A press keeps the browser's own focus and text selection away,
1599
- * and a second pointer while one is down is ignored.
1600
- */
1601
- const PointerPosition = Mount.defineStream("PointerPosition", {
1602
- messages: [
1603
- PointerPressed,
1604
- PointerMoved,
1605
- PointerReleased
1606
- ],
1607
- args: {
1608
- /**
1609
- * A descendant to focus on a press, found by this selector (a thumb), so
1610
- * the keys go on from where the pointer left it.
1611
- */
1612
- focus: Schema.optionalKey(Schema.String) },
1613
- execute: ({ element, focus }) => Stream.callback((queue) => Effect.acquireRelease(Effect.sync(() => {
1614
- const offer = (fact) => Queue.offerUnsafe(queue, fact);
1615
- let held = Option.none();
1616
- const ours = (event) => event instanceof PointerEvent && Option.contains(held, event.pointerId);
1617
- const release = () => {
1618
- if (Option.isNone(held)) return;
1619
- const pointerId = held.value;
1620
- held = Option.none();
1621
- for (const [type, listener] of during) element.removeEventListener(type, listener);
1622
- try {
1623
- element.releasePointerCapture(pointerId);
1624
- } catch {}
1625
- };
1626
- const end = (completed) => (event) => {
1627
- if (!ours(event)) return;
1628
- release();
1629
- offer(PointerReleased.make({ completed }));
1630
- };
1631
- const move = (event) => {
1632
- if (!ours(event)) return;
1633
- const box = measurePaddingBox(element);
1634
- const point = pointIn(box, event);
1635
- const coalesced = event.getCoalescedEvents?.() ?? [];
1636
- offer(PointerMoved.make({
1637
- width: box.width,
1638
- height: box.height,
1639
- point,
1640
- points: coalesced.length > 0 ? coalesced.map((one) => pointIn(box, one)) : [point]
1641
- }));
1642
- };
1643
- const during = [
1644
- ["pointermove", move],
1645
- ["pointerup", end(true)],
1646
- ["pointercancel", end(false)],
1647
- ["lostpointercapture", end(false)]
1648
- ];
1649
- const down = (event) => {
1650
- if (!(event instanceof PointerEvent) || event.button !== 0) return;
1651
- if (Option.isSome(held)) {
1652
- if (!ours(event)) return;
1653
- release();
1654
- offer(PointerReleased.make({ completed: false }));
1655
- }
1656
- held = Option.some(event.pointerId);
1657
- for (const [type, listener] of during) element.addEventListener(type, listener);
1658
- try {
1659
- element.setPointerCapture(event.pointerId);
1660
- } catch {}
1661
- event.preventDefault();
1662
- if (focus !== void 0) {
1663
- const into = element.querySelector(focus);
1664
- if (into instanceof HTMLElement) into.focus();
1665
- }
1666
- const box = measurePaddingBox(element);
1667
- offer(PointerPressed.make({
1668
- width: box.width,
1669
- height: box.height,
1670
- point: pointIn(box, event),
1671
- pointerType: event.pointerType
1672
- }));
1673
- };
1674
- element.addEventListener("pointerdown", down);
1675
- return () => {
1676
- element.removeEventListener("pointerdown", down);
1677
- release();
1678
- };
1679
- }), (stop) => Effect.sync(stop)))
1680
- });
1681
- //#endregion
1682
- //#region src/dom/track-box.ts
1683
- /** The attribute on the mounted element naming the element `TrackBox` follows, by id. */
1684
- const TRACK_TARGET_ATTRIBUTE = "data-target";
1685
- /** The custom properties `TrackBox` writes for `name`, as `measured` names `Measure`'s. */
1686
- const trackedBox = (name) => ({
1687
- x: `--fk-${name}-x`,
1688
- y: `--fk-${name}-y`,
1689
- w: `--fk-${name}-w`,
1690
- h: `--fk-${name}-h`,
1691
- radius: `--fk-${name}-radius`
1692
- });
1693
- /** Sent as the followed element changes: its id, and whether it is on the page. */
1694
- const TrackedTarget = Schema.TaggedStruct("TrackedTarget", {
1695
- target: Schema.String,
1696
- found: Schema.Boolean
1697
- });
1698
- /**
1699
- * The radius a box of `width` by `height` draws for a computed
1700
- * `border-top-left-radius` of `value` (`8px`, `50%`, `8px 4px`): its
1701
- * horizontal length in pixels, a share of the width for a percentage, and
1702
- * never more than half the shorter side, where the browser stops it.
1703
- */
1704
- const cornerRadius = (value, width, height) => {
1705
- const first = value.trim().split(/\s+/)[0] ?? "";
1706
- const length = parseFloat(first);
1707
- if (!Number.isFinite(length)) return 0;
1708
- const pixels = first.endsWith("%") ? length * width / 100 : length;
1709
- return Math.max(0, Math.min(pixels, width / 2, height / 2));
1710
- };
1711
- /**
1712
- * Follows another element's box, as a Mount: the element whose id is the
1713
- * mounted element's `data-target` (`TRACK_TARGET_ATTRIBUTE`). Its box in
1714
- * viewport pixels is written on the mounted element as `--fk-<name>-x`,
1715
- * `-y`, `-w` and `-h`, and with `radius`, its top left corner's radius as
1716
- * `--fk-<name>-radius` (`cornerRadius`), so a fixed overlay drawn from them
1717
- * (a tour's cutout, a coach mark's ring) sits over it. While nothing is
1718
- * followed they are removed, so a stylist's fallbacks apply.
1719
- *
1720
- * The box is read again on a scroll anywhere (heard on the way down, since
1721
- * `scroll` does not bubble), a window resize, the followed element's own
1722
- * resize, and any change to the document's elements, which is also how a
1723
- * target drawn anew under the same id is found. `data-target` is read again
1724
- * whenever it changes, so one element can glide from target to target
1725
- * without being drawn again, which a Mount's args, read once, could not do.
1726
- * What moves the target by any other way (a stylesheet added) is seen at the
1727
- * next of these.
1728
- *
1729
- * Whether a target is on the page is the parent's to act on (a tour shows
1730
- * the step in the middle instead), so each change of the target or of
1731
- * whether it is found sends `TrackedTarget`, starting with the first one.
1732
- * Since it sends a Message, a component may draw it by default: a Scene test
1733
- * resolves it with what the parent maps it to. An empty `data-target` follows
1734
- * nothing and sends nothing.
1735
- */
1736
- const TrackBox = Mount.defineStream("TrackBox", {
1737
- messages: [TrackedTarget],
1738
- args: {
1739
- /** The custom properties' prefix: `tour` writes `--fk-tour-x`. */
1740
- name: Schema.String,
1741
- /** Whether to write the target's corner radius too; it reads the target's computed style. */
1742
- radius: Schema.optional(Schema.Boolean)
1743
- },
1744
- execute: ({ element, name, radius = false }) => Stream.callback((queue) => Effect.acquireRelease(Effect.sync(() => {
1745
- const document = element.ownerDocument;
1746
- const view = document.defaultView;
1747
- const properties = trackedBox(name);
1748
- const style = element instanceof HTMLElement ? element.style : void 0;
1749
- let target = null;
1750
- let told;
1751
- const place = () => {
1752
- if (style === void 0) return;
1753
- if (target === null) {
1754
- for (const property of Object.values(properties)) style.removeProperty(property);
1755
- return;
1756
- }
1757
- const box = target.getBoundingClientRect();
1758
- style.setProperty(properties.x, `${box.left}px`);
1759
- style.setProperty(properties.y, `${box.top}px`);
1760
- style.setProperty(properties.w, `${box.width}px`);
1761
- style.setProperty(properties.h, `${box.height}px`);
1762
- if (radius) {
1763
- const corner = getComputedStyle(target).borderTopLeftRadius;
1764
- style.setProperty(properties.radius, `${cornerRadius(corner, box.width, box.height)}px`);
1765
- }
1766
- };
1767
- const follow = () => {
1768
- const id = element.getAttribute("data-target") ?? "";
1769
- const found = id === "" ? null : document.getElementById(id);
1770
- if (found !== target) {
1771
- if (target !== null) resized.unobserve(target);
1772
- if (found !== null) resized.observe(found);
1773
- target = found;
1774
- }
1775
- place();
1776
- if (id !== "" && (told?.target !== id || told.found !== (found !== null))) {
1777
- told = TrackedTarget.make({
1778
- target: id,
1779
- found: found !== null
1780
- });
1781
- Queue.offerUnsafe(queue, told);
1782
- }
1783
- };
1784
- const resized = new ResizeObserver(place);
1785
- const changed = new MutationObserver(follow);
1786
- changed.observe(element, {
1787
- attributes: true,
1788
- attributeFilter: [TRACK_TARGET_ATTRIBUTE]
1789
- });
1790
- changed.observe(document, {
1791
- childList: true,
1792
- subtree: true
1793
- });
1794
- view?.addEventListener("scroll", place, {
1795
- capture: true,
1796
- passive: true
1797
- });
1798
- view?.addEventListener("resize", place);
1799
- follow();
1800
- return () => {
1801
- changed.disconnect();
1802
- resized.disconnect();
1803
- view?.removeEventListener("scroll", place, { capture: true });
1804
- view?.removeEventListener("resize", place);
1805
- };
1806
- }), (stop) => Effect.sync(stop)))
1807
- });
1808
- //#endregion
1809
- //#region src/dom/image-decode.ts
1810
- /**
1811
- * An image fetched and decoded before it is shown, as a Command: a lightbox
1812
- * or a carousel asks for the next picture, and shows it on `Decoded`, so the
1813
- * switch never passes through a blank frame. A Command, not a Mount: the
1814
- * Model decides to show the image, so the Message that leads there carries
1815
- * the news that it is ready.
1816
- */
1817
- const ImageDecodeMessage = defineMessageUnion({
1818
- Decoded: { src: Schema.String },
1819
- DecodeFailed: {
1820
- src: Schema.String,
1821
- message: Schema.String
1822
- }
1823
- });
1824
- /**
1825
- * Fetches and decodes `source` (`img.decode()`), yielding `Decoded` once it
1826
- * can be painted at once, or `DecodeFailed` when it cannot be fetched or read,
1827
- * or there is no document (a server render). Given `srcset` and `sizes`, as
1828
- * `foldkit-media`'s `Picture.Img` has them, the browser picks the candidate
1829
- * it would draw, so the decoded file is the one shown.
1830
- */
1831
- const decodeImage = (source) => ({
1832
- name: "Image.decode",
1833
- args: { src: source.src },
1834
- effect: typeof document === "undefined" ? Effect.succeed(ImageDecodeMessage.DecodeFailed({
1835
- src: source.src,
1836
- message: "there is no document"
1837
- })) : Effect.matchEffect(Effect.tryPromise({
1838
- try: () => {
1839
- const image = new Image();
1840
- if (source.sizes !== void 0) image.sizes = source.sizes;
1841
- if (source.srcset !== void 0) image.srcset = source.srcset;
1842
- image.src = source.src;
1843
- return image.decode();
1844
- },
1845
- catch: (error) => error instanceof Error ? error.message : String(error)
1846
- }), {
1847
- onFailure: (message) => Effect.succeed(ImageDecodeMessage.DecodeFailed({
1848
- src: source.src,
1849
- message
1850
- })),
1851
- onSuccess: () => Effect.succeed(ImageDecodeMessage.Decoded({ src: source.src }))
1852
- })
1853
- });
1854
- //#endregion
1855
- export { Autofocus, ClipboardMessage, ClipboardReadMessage, DRAG_THRESHOLD, DragCancelled, DragDropped, DragPlace, DragStarted, DragZone, DraggedOver, EditAsked, EditableText, FLUID_HELD_ATTRIBUTE, FLUID_HOVER_ATTRIBUTE, FLUID_LIT_ATTRIBUTE, FLUID_PRESS_ATTRIBUTE, FluidHover, FocusScope, Focused, FollowTabStop, HOLD_SLOP, HOLD_TO_DRAG_MS, HideOutside, ImageDecodeMessage, Input, InputMask, KEYBOARD_INSET, KeepInView, KeyboardInset, Measure, Move, MoveEnded, MoveStarted, Moved, NO_SWIPE, PointerDrag, PointerMoved, PointerPoint, PointerPosition, PointerPressed, PointerReleased, SCROLL_BAR_ATTRIBUTE, SCROLL_OVERLAID_ATTRIBUTE, SETTLE_QUIET, SWIPE_WINDOW, ScriptMessage, ScrollBars, ScrollLock, ScrollSettle, ScrollSettled, ShareData, ShareMessage, Swipe, SwipeEnded, SwipeStarted, Swiped, TRACK_TARGET_ATTRIBUTE, TargetHovered, TargetPressed, Targets, TextCancelled, TextCommitted, TextEdited, TrackBox, TrackedTarget, applyMask, boxOf, canScroll, copyText, cornerRadius, decodeImage, fluidHovered, keepScroll, keyboardInset, loadScript, measurePaddingBox, measured, paddingBoxOf, pointedLinks, readText, scrollThumb, share, tabbableWithin, targetOf, textOf, trackedBox, velocityOf, zoneOf };
1856
-
1857
- //# sourceMappingURL=index.mjs.map