@cardstack/boxel-cli 0.12.0 → 0.13.0-unstable.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 (178) hide show
  1. package/bundled-realms/pretui/components/chip.gts +47 -2
  2. package/bundled-realms/pretui/components/chip.md +5 -2
  3. package/bundled-realms/pretui/components/chip.usage.gts +37 -2
  4. package/bundled-realms/pretui/components/status-chip.gts +12 -2
  5. package/bundled-realms/pretui/components/status-chip.md +2 -1
  6. package/bundled-realms/pretui/components/status-chip.usage.gts +2 -0
  7. package/bundled-test-harness/assets/{ai-module-creation-test-B9lSKNq1.js → ai-module-creation-test-Czsh1MyP.js} +1 -1
  8. package/bundled-test-harness/assets/{aibot-message-LXV0UlzI.js → aibot-message-Dg5nzE1b.js} +1 -1
  9. package/bundled-test-harness/assets/{app-BIQcKrVT.js → app-hXF_bqWy.js} +6 -6
  10. package/bundled-test-harness/assets/{assistant-transcript-test-CG_2-bog.js → assistant-transcript-test-kILRv_pf.js} +1 -1
  11. package/bundled-test-harness/assets/{binary-upload-dedupe-test-Q7e6bzl1.js → binary-upload-dedupe-test-Bt5RqtEk.js} +1 -1
  12. package/bundled-test-harness/assets/{bundled-base-DldokUSJ.js → bundled-base-DtxlKFXx.js} +3 -3
  13. package/bundled-test-harness/assets/{bundled-base-modules-test-VAlRx4t3.js → bundled-base-modules-test-D1fXHMTE.js} +1 -1
  14. package/bundled-test-harness/assets/{can-invoke-test-BGmUJHxo.js → can-invoke-test-DqBuecdl.js} +1 -1
  15. package/bundled-test-harness/assets/{card-chooser-paging-test-CzooKL4I.js → card-chooser-paging-test-Cf8r032c.js} +1 -1
  16. package/bundled-test-harness/assets/{card-chooser-test-C3fjaDYi.js → card-chooser-test-q_qnUKxi.js} +1 -1
  17. package/bundled-test-harness/assets/{card-def-field-def-relationships-test-yqv8ir8Y.js → card-def-field-def-relationships-test-C5xkWMZi.js} +1 -1
  18. package/bundled-test-harness/assets/{card-delete-test-BtjqrqKO.js → card-delete-test-KB4aJuv_.js} +1 -1
  19. package/bundled-test-harness/assets/{chip-D6Xffhjx.css → chip-B2Ns53A6.css} +35 -9
  20. package/bundled-test-harness/assets/{chip-BydgljDL.js → chip-DVJE_YDh.js} +11 -3
  21. package/bundled-test-harness/assets/{choreo-D7dLCdGB.js → choreo-CIrop3HM.js} +16 -35
  22. package/bundled-test-harness/assets/choreo-Ca2Y2sF3.js +2 -0
  23. package/bundled-test-harness/assets/{choreo-feature-reel-test--x-L3ZKc.js → choreo-feature-reel-test-CJHms7Sf.js} +2 -2
  24. package/bundled-test-harness/assets/{choreo-gallery-ZO9vXOxd.js → choreo-gallery-DZm1KZ20.js} +3 -3
  25. package/bundled-test-harness/assets/{choreo-gallery-build-order-test-DG5UTyc9.js → choreo-gallery-build-order-test-QKyMWELN.js} +4 -4
  26. package/bundled-test-harness/assets/{choreo-gallery-compositor-test-DJP80SNj.js → choreo-gallery-compositor-test-BQDptxfE.js} +1 -1
  27. package/bundled-test-harness/assets/{choreo-gallery-cover-test-CHYz7sM4.js → choreo-gallery-cover-test-Do-HYF96.js} +1 -1
  28. package/bundled-test-harness/assets/{choreo-gallery-crossing-stage-test-BuinlINr.js → choreo-gallery-crossing-stage-test-yJVXghRc.js} +10 -10
  29. package/bundled-test-harness/assets/{choreo-gallery-demos-alive-test-BjNCGfH9.js → choreo-gallery-demos-alive-test-BCeaaZye.js} +1 -1
  30. package/bundled-test-harness/assets/{choreo-gallery-drift-test-BJ17f3yv.js → choreo-gallery-drift-test-CJ1yq6R0.js} +1 -1
  31. package/bundled-test-harness/assets/{choreo-gallery-escort-test-Dadih8-S.js → choreo-gallery-escort-test-DypqSgvs.js} +1 -1
  32. package/bundled-test-harness/assets/{choreo-gallery-films-test-CCXty_WI.js → choreo-gallery-films-test-ClMy5M_p.js} +2 -2
  33. package/bundled-test-harness/assets/{choreo-gallery-filter-test-BozCoT_M.js → choreo-gallery-filter-test-CzOmgqgl.js} +1 -1
  34. package/bundled-test-harness/assets/{choreo-gallery-fold-test-Kez2P3_Y.js → choreo-gallery-fold-test-qNnH6wtC.js} +2 -2
  35. package/bundled-test-harness/assets/{choreo-gallery-guide-pages-test-BV5oxrYA.js → choreo-gallery-guide-pages-test-BtKWMkTv.js} +1 -1
  36. package/bundled-test-harness/assets/{choreo-gallery-guides-test-DwYBDgKi.js → choreo-gallery-guides-test-CXMsuDYi.js} +4 -3
  37. package/bundled-test-harness/assets/{choreo-gallery-hang-test-B-5u7cGJ.js → choreo-gallery-hang-test-BD4JQ04e.js} +2 -2
  38. package/bundled-test-harness/assets/{choreo-gallery-highlight-test-BZ5Lg8V6.js → choreo-gallery-highlight-test-BK280K2W.js} +2 -2
  39. package/bundled-test-harness/assets/{choreo-gallery-inline-edit-test-wSlSI-VM.js → choreo-gallery-inline-edit-test-DAgUcaaw.js} +2 -2
  40. package/bundled-test-harness/assets/{choreo-gallery-interruption-test-DBpCEiro.js → choreo-gallery-interruption-test-DY5KUilC.js} +4 -4
  41. package/bundled-test-harness/assets/{choreo-gallery-lone-stack-test-aMLrHsns.js → choreo-gallery-lone-stack-test-Dxd_Nb0-.js} +4 -4
  42. package/bundled-test-harness/assets/{choreo-gallery-navigation-test-B8b-2ZTz.js → choreo-gallery-navigation-test-DWJq12PN.js} +1 -1
  43. package/bundled-test-harness/assets/{choreo-gallery-playhead-transport-test-B95CYBh7.js → choreo-gallery-playhead-transport-test-DlQ6yTKW.js} +1 -1
  44. package/bundled-test-harness/assets/{choreo-gallery-poplayout-subtree-test-jZAAICV6.js → choreo-gallery-poplayout-subtree-test-D59W8gYt.js} +5 -5
  45. package/bundled-test-harness/assets/{choreo-gallery-presence-modes-test-BawIKB-a.js → choreo-gallery-presence-modes-test-BAU-dggP.js} +2 -2
  46. package/bundled-test-harness/assets/{choreo-gallery-presentation-test-CYIb-8ML.js → choreo-gallery-presentation-test-BqGsinE0.js} +2 -2
  47. package/bundled-test-harness/assets/{choreo-gallery-reduced-motion-test-C6Vi1WYr.js → choreo-gallery-reduced-motion-test-By0rb2Ai.js} +4 -3
  48. package/bundled-test-harness/assets/{choreo-gallery-sheet-standalone-test-_e4T5KDI.js → choreo-gallery-sheet-standalone-test-Cfw-4ULF.js} +2 -2
  49. package/bundled-test-harness/assets/{choreo-gallery-sheet-test-Cv51Te3G.js → choreo-gallery-sheet-test-dm53Pdzb.js} +2 -2
  50. package/bundled-test-harness/assets/{choreo-gallery-site-test-Cm35kHu_.js → choreo-gallery-site-test-SAlqtZgL.js} +3 -2
  51. package/bundled-test-harness/assets/{choreo-gallery-stage-Bp6QVqlI.js → choreo-gallery-stage-CU8LvQqX.js} +2 -2
  52. package/bundled-test-harness/assets/{choreo-gallery-stage-keyboard-test-DDX2cuMa.js → choreo-gallery-stage-keyboard-test-DQ4v5qA4.js} +2 -2
  53. package/bundled-test-harness/assets/{choreo-gallery-stages-test-Dfa_33_4.js → choreo-gallery-stages-test-CRvaS6w6.js} +2 -2
  54. package/bundled-test-harness/assets/{choreo-gallery-subdivision-test-CgR3WdcT.js → choreo-gallery-subdivision-test-CtOm-U35.js} +3 -3
  55. package/bundled-test-harness/assets/{choreo-gallery-table-plan-test-DF-cE3J9.js → choreo-gallery-table-plan-test-_0koIm2u.js} +1 -1
  56. package/bundled-test-harness/assets/{choreo-gallery-theater-test-BuIyb2ke.js → choreo-gallery-theater-test-yedwQVyP.js} +4 -4
  57. package/bundled-test-harness/assets/{choreo-gallery-tuning-test-BdZ45YBK.js → choreo-gallery-tuning-test-CmMtfJ5a.js} +1 -1
  58. package/bundled-test-harness/assets/{choreo-gallery-widget-room-test-HcPX75Dg.js → choreo-gallery-widget-room-test-jnYOas5x.js} +2 -2
  59. package/bundled-test-harness/assets/{choreo-gallery-wires-test-BhFCIjB1.js → choreo-gallery-wires-test-CSyqjVnm.js} +2 -2
  60. package/bundled-test-harness/assets/clip--wioup_3.js +2 -0
  61. package/bundled-test-harness/assets/{clip-BmBVqUyU.js → clip-Cm32sLyn.js} +3 -3
  62. package/bundled-test-harness/assets/{codeblocks-test-DbZYlmG_.js → codeblocks-test-DLVRpBAl.js} +1 -1
  63. package/bundled-test-harness/assets/{compaction-test-D1G3KCSe.js → compaction-test-zX01t57K.js} +1 -1
  64. package/bundled-test-harness/assets/{container-XSX5809v.js → container-CXZoEvul.js} +10 -9
  65. package/bundled-test-harness/assets/{create-listing-modal-test--oGT9P_F.js → create-listing-modal-test-DPgVovcL.js} +1 -1
  66. package/bundled-test-harness/assets/{debug-message-test-C8UZUtCt.js → debug-message-test-CWl-ido2.js} +1 -1
  67. package/bundled-test-harness/assets/{externals--YMzwHwd.js → externals-Ddhtv753.js} +37 -37
  68. package/bundled-test-harness/assets/{fallback-banner-live-delete-test-DPK_coJY.js → fallback-banner-live-delete-test-CFbMEFHo.js} +1 -1
  69. package/bundled-test-harness/assets/{fallback-banner-test-C2wi6Lu6.js → fallback-banner-test-3O0imkxJ.js} +1 -1
  70. package/bundled-test-harness/assets/{fallback-models-test-SqdXWB1Z.js → fallback-models-test-hyQfQLuM.js} +1 -1
  71. package/bundled-test-harness/assets/{file-attachment-test-DT0HK6yS.js → file-attachment-test-C63Ttyaj.js} +1 -1
  72. package/bundled-test-harness/assets/{film-Zt2YoVoG.js → film-9V69qBWw.js} +6 -6
  73. package/bundled-test-harness/assets/{film-77OfTGL7.js → film-Z87hSVo_.js} +8 -8
  74. package/bundled-test-harness/assets/film-h1N8vUjG.js +2 -0
  75. package/bundled-test-harness/assets/{formatted-aibot-message-test-B6i0k9wW.js → formatted-aibot-message-test-DH3WPvU1.js} +1 -1
  76. package/bundled-test-harness/assets/{general-test-C0Xb2Idr.js → general-test-wrSeNkrm.js} +1 -1
  77. package/bundled-test-harness/assets/{glimmer-motion-and-choreo-in-cards-test-DFlkZtek.js → glimmer-motion-and-choreo-in-cards-test-Byg3fSOY.js} +32 -32
  78. package/bundled-test-harness/assets/{glimmer-motion-and-choreo-test-B2ustNm9.js → glimmer-motion-and-choreo-test-BM7uX8k4.js} +10 -10
  79. package/bundled-test-harness/assets/group-Cgg5RLul.js +2 -0
  80. package/bundled-test-harness/assets/{group-BNxp6qON.js → group-DL-b_Hzj.js} +2 -2
  81. package/bundled-test-harness/assets/{index-query-engine-test-BKOqgBMv.js → index-query-engine-test-DvfKgn6s.js} +1 -1
  82. package/bundled-test-harness/assets/{instance-filter-matcher-test-CNIzKZvP.js → instance-filter-matcher-test-KqGi-K79.js} +1 -1
  83. package/bundled-test-harness/assets/item-Bu9hhPf2.js +2 -0
  84. package/bundled-test-harness/assets/{item-BDyVqxdA.js → item-CBEoTOyj.js} +2 -2
  85. package/bundled-test-harness/assets/{main-BiI-FhMc.js → main-CoUo04VF.js} +1 -1
  86. package/bundled-test-harness/assets/{markdown-preview-BItI_AEl.js → markdown-preview-D_QssEOi.js} +1 -1
  87. package/bundled-test-harness/assets/{markdown-preview-test-Bntyikh1.js → markdown-preview-test-nljzB8o_.js} +1 -1
  88. package/bundled-test-harness/assets/memory-B73-H_Rp.js +223 -0
  89. package/bundled-test-harness/assets/{model-cost-tier-test-Hu2-LpEw.js → model-cost-tier-test-B5ByLtAP.js} +1 -1
  90. package/bundled-test-harness/assets/{motion-DeM7UCtd.js → motion-BiMvMS08.js} +1 -1
  91. package/bundled-test-harness/assets/{node-BAM535H5.js → node-BWOLCOMI.js} +95 -6
  92. package/bundled-test-harness/assets/{operation-lowering-test-CG1gatuH.js → operation-lowering-test-Bz81V0Gt.js} +1 -1
  93. package/bundled-test-harness/assets/{operator-mode-card-chooser-test-Bo5ZnF_x.js → operator-mode-card-chooser-test-DNOnplV8.js} +1 -1
  94. package/bundled-test-harness/assets/{operator-mode-links-test-DVx_LQOU.js → operator-mode-links-test-BeXsOYMU.js} +1 -1
  95. package/bundled-test-harness/assets/{operator-mode-test-CCbFrYgN.js → operator-mode-test-jdeCXqhV.js} +1 -1
  96. package/bundled-test-harness/assets/{operator-mode-ui-test-Dm2Bf6Vn.js → operator-mode-ui-test-DdiFZylM.js} +1 -1
  97. package/bundled-test-harness/assets/{overlay-menu-items-test-Bk5AidFD.js → overlay-menu-items-test-C_Q4sJlC.js} +1 -1
  98. package/bundled-test-harness/assets/{overlays-CY7IgeOl.js → overlays-DZXjQykn.js} +3 -3
  99. package/bundled-test-harness/assets/{overlays-DhVKmSP-.js → overlays-hrAO43jG.js} +1 -1
  100. package/bundled-test-harness/assets/{panel-t9QyU1lY.js → panel-CXmB6GMs.js} +15 -75
  101. package/bundled-test-harness/assets/{panel-CUYJ8oHS.css → panel-D2zkZoIr.css} +39 -31
  102. package/bundled-test-harness/assets/{past-sessions-test-Dq-Y9T6W.js → past-sessions-test-Bm1UiLD0.js} +1 -1
  103. package/bundled-test-harness/assets/{permission-checking-realm-test-CuImx8ie.js → permission-checking-realm-test-Jq7R-yRT.js} +1 -1
  104. package/bundled-test-harness/assets/pill-menu-Ctn3JNqG.js +154 -0
  105. package/bundled-test-harness/assets/{pill-menu-test-kpWBGrX5.js → pill-menu-test-Cf2xO2w6.js} +1 -1
  106. package/bundled-test-harness/assets/plate-CnatwP3n.js +2 -0
  107. package/bundled-test-harness/assets/{plate-B4Lz2Ulo.js → plate-Cu5EnXQZ.js} +3 -3
  108. package/bundled-test-harness/assets/player-Bwn5tEsh.js +2 -0
  109. package/bundled-test-harness/assets/{player-B965XH82.js → player-CVdsx7Ng.js} +3 -3
  110. package/bundled-test-harness/assets/presence-BKkR2au7.js +553 -0
  111. package/bundled-test-harness/assets/presence-CcEXVges.js +2 -0
  112. package/bundled-test-harness/assets/{quick-look-test-VW5_W7dm.js → quick-look-test-BrnxRRsq.js} +1 -1
  113. package/bundled-test-harness/assets/{realm-api-keys-sg--fohv.js → realm-api-keys-CHPnfcko.js} +1 -1
  114. package/bundled-test-harness/assets/realm-api-keys-GLBmvu4U.js +2 -0
  115. package/bundled-test-harness/assets/{realm-api-keys-test-Cy7qSoAd.js → realm-api-keys-test-CHBGOFcx.js} +1 -1
  116. package/bundled-test-harness/assets/{realm-config-DvpP1xf-.js → realm-config-C7c3B2er.js} +1 -1
  117. package/bundled-test-harness/assets/{realm-config-policy-standing-test-DJk-ftgH.js → realm-config-policy-standing-test-C8aNeb6_.js} +1 -1
  118. package/bundled-test-harness/assets/{realm-config-routing-rule-edit-test-CTPiJrAL.js → realm-config-routing-rule-edit-test-Dmnw-VQz.js} +1 -1
  119. package/bundled-test-harness/assets/{realm-config-settings-test-CUa-PjMC.js → realm-config-settings-test-C6V0Mx35.js} +1 -1
  120. package/bundled-test-harness/assets/{reasoning-test-CeeGGwvf.js → reasoning-test-Dn0Xb42V.js} +1 -1
  121. package/bundled-test-harness/assets/{rendered-markdown-B8TH0yN4.js → rendered-markdown-DPEXS7MQ.js} +1 -1
  122. package/bundled-test-harness/assets/{rendered-markdown-test-CBRsIBqW.js → rendered-markdown-test-DxbJ1nFX.js} +1 -1
  123. package/bundled-test-harness/assets/{room-message-test-BTxZ_W74.js → room-message-test-BBE8UIPX.js} +1 -1
  124. package/bundled-test-harness/assets/{run-BSEzn_PH.js → run-CfyzhBXb.js} +2 -2
  125. package/bundled-test-harness/assets/{scrolling-test-C7R0UASi.js → scrolling-test-sLBz7Gxv.js} +2 -2
  126. package/bundled-test-harness/assets/{searchable-option-test-BiOqxIIs.js → searchable-option-test-Ix9_H5QV.js} +1 -1
  127. package/bundled-test-harness/assets/{sending-test-DOXQ0Tvx.js → sending-test-_Pqzq1bN.js} +1 -1
  128. package/bundled-test-harness/assets/{skills-test-BjCYI4Z_.js → skills-test-DUHRQGiC.js} +1 -1
  129. package/bundled-test-harness/assets/src-CEw1V0fi.js +11 -0
  130. package/bundled-test-harness/assets/{src-Bhefkhqj.js → src-CbBcj3xm.js} +11 -5
  131. package/bundled-test-harness/assets/{src-BDDYD1rs.js → src-D8bpj2tb.js} +2 -2
  132. package/bundled-test-harness/assets/{src-DySBAkac.js → src-Df4Vutrk.js} +3 -3
  133. package/bundled-test-harness/assets/{stick-to-bottom-resize-test-hN0AjdXP.js → stick-to-bottom-resize-test-Du4B3w2x.js} +4 -4
  134. package/bundled-test-harness/assets/{store-test-BD99H9dQ.js → store-test-9C-s0-H6.js} +1 -1
  135. package/bundled-test-harness/assets/{test-support-BcjhIHQa.js → test-support-BOFeeYuE.js} +25 -2
  136. package/bundled-test-harness/assets/test-support-CXESl4Wf.js +3 -0
  137. package/bundled-test-harness/assets/test-support-CgWmpebE.js +3 -0
  138. package/bundled-test-harness/assets/{test-support-Dl6FChlV.js → test-support-xTaMuaOA.js} +4 -27
  139. package/bundled-test-harness/assets/{tests-BqLDuQog.js → tests-CVpu0hZj.js} +437 -437
  140. package/bundled-test-harness/assets/{text-input-validator-test-CW6Ay7bo.js → text-input-validator-test-CcVh-MFa.js} +1 -1
  141. package/bundled-test-harness/assets/{titles-CFt3dEls.js → titles-BUbpcMoe.js} +3 -3
  142. package/bundled-test-harness/assets/titles-D80DwOQU.js +2 -0
  143. package/bundled-test-harness/assets/{to-device-streaming-test-DRxNCi7Z.js → to-device-streaming-test-RFHD62PN.js} +1 -1
  144. package/bundled-test-harness/assets/{token-usage-test-Ck9rB3Ie.js → token-usage-test-B2iJC-UX.js} +1 -1
  145. package/bundled-test-harness/assets/{tools-test-B3zniq10.js → tools-test-BZOWfXML.js} +1 -1
  146. package/bundled-test-harness/assets/{turn-settling-test-C0X6hL7x.js → turn-settling-test-Cvu2rvzM.js} +1 -1
  147. package/bundled-test-harness/assets/{turn-summary-0XOoAv3j.js → turn-summary-CAZVdcBd.js} +1 -1
  148. package/bundled-test-harness/index.html +26 -25
  149. package/bundled-test-harness/tests/index.html +26 -25
  150. package/bundled-types/choreo/choreo.gts +21 -42
  151. package/bundled-types/choreo/test-support/index.ts +9 -30
  152. package/bundled-types/glimmer-motion/index.ts +11 -3
  153. package/bundled-types/glimmer-motion/memory.ts +302 -0
  154. package/bundled-types/glimmer-motion/node.ts +125 -14
  155. package/bundled-types/glimmer-motion/presence-types.ts +13 -5
  156. package/bundled-types/glimmer-motion/presence.gts +199 -11
  157. package/bundled-types/glimmer-motion/snapshot.ts +280 -0
  158. package/bundled-types/glimmer-motion/test-support/index.ts +36 -4
  159. package/bundled-types/host-app/components/ai-assistant/llm-select.gts +3 -0
  160. package/bundled-types/host-app/components/ai-assistant/skill-menu/index.gts +3 -0
  161. package/bundled-types/host-app/components/matrix/room.gts +15 -3
  162. package/bundled-types/host-app/components/pill-menu/index.gts +74 -23
  163. package/package.json +1 -1
  164. package/bundled-test-harness/assets/choreo-Cd9yKlEK.js +0 -2
  165. package/bundled-test-harness/assets/clip-DnEy6oK5.js +0 -2
  166. package/bundled-test-harness/assets/film-C4NJTl9U.js +0 -2
  167. package/bundled-test-harness/assets/group-CPQojb-T.js +0 -2
  168. package/bundled-test-harness/assets/item-DGzMAZ9K.js +0 -2
  169. package/bundled-test-harness/assets/pill-menu-DaRpDMUm.js +0 -68
  170. package/bundled-test-harness/assets/plate-B7JjCVMj.js +0 -2
  171. package/bundled-test-harness/assets/player-DpGXST02.js +0 -2
  172. package/bundled-test-harness/assets/presence-CmLmFwhO.js +0 -2
  173. package/bundled-test-harness/assets/presence-Sv4y4tQy.js +0 -230
  174. package/bundled-test-harness/assets/realm-api-keys-DkdlXgAg.js +0 -2
  175. package/bundled-test-harness/assets/src-BpmcuCfH.js +0 -10
  176. package/bundled-test-harness/assets/test-support-CB3auo8h.js +0 -2
  177. package/bundled-test-harness/assets/test-support-DfGKwg35.js +0 -3
  178. package/bundled-test-harness/assets/titles-DVhrJZzS.js +0 -2
@@ -90,37 +90,16 @@ export function orphanCount(root: HTMLElement = testRoot()): number {
90
90
  }
91
91
 
92
92
  /**
93
- * The first LIVE match for `selector` — the copy that is still part of the
94
- * rendered tree, never a leaver parked in a region's orphan layer.
95
- *
96
- * Mid-crossing an identity exists twice: the arriving element in the live
97
- * tree, and the departing skin the region locked into `[data-choreo-orphans]`
98
- * so it can be flown and faded. That layer is the region's FIRST child, so a
99
- * bare `querySelector` answers with the ghost — an element whose component
100
- * has already been torn down, whose listeners are gone, and whose box is
101
- * where the OLD scene stood. Clicking it does nothing; measuring it measures
102
- * the past. Neither failure names itself.
103
- *
104
- * So any assertion or interaction a test performs while a crossing may be
105
- * aloft should come through here. A raised sprite (`c.Raise`) is deliberately
106
- * still live: it is the real element on a different layer, not a copy.
93
+ * `live()` / `liveAll()`: queries that skip memories. Mid-crossing an identity
94
+ * exists twice: the arriving element in the live tree, and the departing one
95
+ * the region keeps as a memory in its orphan layer so it can be flown and
96
+ * faded. The memory's test hooks carry a suffix (`data-test-title-memory`),
97
+ * but a class or role selector matches both, and the orphan layer is the
98
+ * region's FIRST child, so a bare `querySelector` answers with the memory. A
99
+ * raised sprite (`c.Raise`) is deliberately still live: it is the real element
100
+ * on a different layer, not a memory.
107
101
  */
108
- export function live<E extends Element = HTMLElement>(
109
- selector: string,
110
- root: ParentNode = testRoot(),
111
- ): E | null {
112
- return liveAll<E>(selector, root)[0] ?? null;
113
- }
114
-
115
- /** every live match for `selector`, in document order — see `live()` */
116
- export function liveAll<E extends Element = HTMLElement>(
117
- selector: string,
118
- root: ParentNode = testRoot(),
119
- ): E[] {
120
- return [...root.querySelectorAll<E>(selector)].filter(
121
- (el) => !el.closest('[data-choreo-orphans]'),
122
- );
123
- }
102
+ export { live, liveAll } from 'glimmer-motion/test-support';
124
103
 
125
104
  /**
126
105
  * Elements still wearing a transform that nothing is animating.
@@ -2,9 +2,9 @@
2
2
  * glimmer-motion — Motion's React glue re-done for Glimmer, on the unchanged motion-dom engine.
3
3
  * Deep imports (`glimmer-motion/motion`, `glimmer-motion/presence`, …) are the same modules.
4
4
  *
5
- * `registerBusyProbe` and the participant-host API are the seams a layer built
6
- * on top (such as `@cardstack/choreo`) uses to take part in settling and in
7
- * motion elements' lifecycles.
5
+ * `registerBusyProbe`, the participant-host API and memories are the seams a
6
+ * layer built on top (such as `@cardstack/choreo`) uses to take part in
7
+ * settling, in motion elements' lifecycles, and in keeping leavers on screen.
8
8
  */
9
9
  export { type BusyProbe, registerBusyProbe } from './activity.ts';
10
10
  export { createDragControls, DragControls } from './gestures/drag-controls.ts';
@@ -38,6 +38,14 @@ export {
38
38
  RENDER_DETECTOR_ATTRIBUTE,
39
39
  snapshotOnRender,
40
40
  } from './layout-group.gts';
41
+ export type { ScrollPosition, Shown, ShownAnimation } from './memory.ts';
42
+ export {
43
+ keepAsMemory,
44
+ MEMORY_ATTRIBUTE,
45
+ MEMORY_HOOK_SUFFIX,
46
+ readAnimations,
47
+ trackScrolls,
48
+ } from './memory.ts';
41
49
  export type { MotionEl, MotionProps } from './motion.ts';
42
50
  export { default as motion, MotionModifier } from './motion.ts';
43
51
  export type { MotionConfigContext } from './motion-config.gts';
@@ -0,0 +1,302 @@
1
+ /**
2
+ * A memory: an element that has left, kept on screen for as long as its exit
3
+ * plays. `<Presence @mode="snapshot">` keeps a copy of its leaver as one, and
4
+ * a layer that parks removed elements itself (Choreo's orphan layer) keeps
5
+ * each element it parks as one, so a leaver looks and behaves the same
6
+ * whichever layer let it go.
7
+ *
8
+ * A memory shows the frame its element last showed, and nothing in it acts:
9
+ *
10
+ * - Its CSS animations hold still. An element put back in the document
11
+ * restarts every stylesheet animation on it, so an entrance that had
12
+ * finished would play again while the element leaves. Each animation the
13
+ * element was showing pauses at the time it had reached, and any other (one
14
+ * that had finished, or one the re-insertion started) jumps to its end.
15
+ * Animations on a scroll timeline follow the restored scroll offsets. The
16
+ * engine's own animations are not stylesheet animations, and are left
17
+ * alone.
18
+ * - Its scroll offsets are restored: an element taken out of the document
19
+ * loses them.
20
+ * - Its backdrop filters are off. A backdrop filter re-samples whatever is
21
+ * behind it on every frame, at full price inside a moving, fading element,
22
+ * and what is behind a memory is the scene that replaced it.
23
+ * - It is composited as one texture (`will-change: transform, opacity`).
24
+ * That also makes it a stacking context, and the containing block of every
25
+ * positioned descendant, `position: fixed` included: a descendant that was
26
+ * placed against a box outside the element is placed against the memory.
27
+ * - It is inert and hidden from assistive technology.
28
+ * - Its HTML elements carry no ids, `autofocus` or form-control names. A
29
+ * duplicate id that comes first in the document is what `getElementById`
30
+ * and `<label for>` find, and a checked radio button joining the document
31
+ * unchecks the live one in its group. Its SVG elements keep their ids,
32
+ * which its own fills, clips, masks and `<use>` references resolve through.
33
+ * A reference resolves to the first element in the document with its id,
34
+ * so while a memory comes before its replacement (as one in Choreo's orphan
35
+ * layer, its region's first child, does), the replacement's `url(#id)` and
36
+ * `<use>` references resolve to the memory's definitions. Definitions that
37
+ * change as their element is replaced need an id per version. Its iframes
38
+ * are blank, and its media neither plays nor loads.
39
+ * - Its test hooks carry a suffix: `data-test-title` becomes
40
+ * `data-test-title-memory`, with its value. A test's selector finds only the
41
+ * live element, which is what replaced the leaver or nothing, and a test that
42
+ * means the leaver names it with the suffix. `live()` and `liveAll()` in
43
+ * `glimmer-motion/test-support` skip memories for every other selector.
44
+ * - Its classes and its other attributes stay, so it looks as its element
45
+ * did wherever its styles are keyed on them. A rule keyed on an id
46
+ * (`#panel`, `:target`) no longer reaches it, so an element that may leave
47
+ * is styled by class. A memory put back in its parent (a
48
+ * `<Presence @mode="snapshot">` copy) therefore still matches structural
49
+ * selectors that read the live scene: `:has()`, `+`, `~`, `:nth-child()`,
50
+ * `:empty`. A rule that should see only live content excludes it, as in
51
+ * `:has(.menu:not([data-motion-memory], [data-motion-memory] *))`.
52
+ *
53
+ * Custom elements in a memory are connected again, and run their
54
+ * `connectedCallback`.
55
+ */
56
+
57
+ /** the attribute every memory carries */
58
+ export const MEMORY_ATTRIBUTE = 'data-motion-memory';
59
+
60
+ /** what a memory's `data-test-*` hooks are renamed with */
61
+ export const MEMORY_HOOK_SUFFIX = '-memory';
62
+
63
+ export interface ScrollPosition {
64
+ left: number;
65
+ top: number;
66
+ }
67
+
68
+ /** a CSS animation on the document timeline that an element was showing */
69
+ export interface ShownAnimation {
70
+ finished: boolean;
71
+ /** its `animation-name` */
72
+ name: string;
73
+ /** the pseudo-element it runs on, or null for the element itself */
74
+ pseudoElement: string | null;
75
+ /** its current time, in milliseconds */
76
+ time: number;
77
+ }
78
+
79
+ /** what a subtree showed before the render that removed it, for its memory to show */
80
+ export interface Shown {
81
+ /** the CSS animations each element was showing */
82
+ animations: { get(el: Element): readonly ShownAnimation[] | undefined };
83
+ /** each element's scroll offset */
84
+ scrolls: { get(el: Element): ScrollPosition | undefined };
85
+ }
86
+
87
+ /** form controls whose name would join a live radio group or form submission */
88
+ const NAMED_CONTROLS =
89
+ 'input, select, textarea, button, fieldset, output, object, form, iframe';
90
+
91
+ /** the element and every element inside it, in document order */
92
+ export function elementsOf(root: Element): Element[] {
93
+ return [root, ...root.querySelectorAll('*')];
94
+ }
95
+
96
+ /** a stylesheet animation on the document timeline: a scroll-driven one follows its scroller instead */
97
+ function onDocumentTimeline(animation: Animation): animation is CSSAnimation {
98
+ return (
99
+ animation instanceof CSSAnimation &&
100
+ animation.timeline === document.timeline
101
+ );
102
+ }
103
+
104
+ /**
105
+ * The CSS animations on the document timeline that `root` and the elements
106
+ * inside it are showing, by the element each runs on. Read it while `root` is
107
+ * still in the document: a removed element has none.
108
+ */
109
+ export function readAnimations(root: Element): Map<Element, ShownAnimation[]> {
110
+ const shown = new Map<Element, ShownAnimation[]>();
111
+ for (const animation of root.getAnimations({ subtree: true })) {
112
+ const effect = animation.effect;
113
+ if (
114
+ !onDocumentTimeline(animation) ||
115
+ !(effect instanceof KeyframeEffect) ||
116
+ !effect.target
117
+ ) {
118
+ continue;
119
+ }
120
+ const list = shown.get(effect.target) ?? [];
121
+ list.push({
122
+ finished: animation.playState === 'finished',
123
+ name: animation.animationName,
124
+ pseudoElement: effect.pseudoElement,
125
+ time: Number(animation.currentTime ?? 0),
126
+ });
127
+ shown.set(effect.target, list);
128
+ }
129
+ return shown;
130
+ }
131
+
132
+ /** the scroll offsets of `root` and of every element inside it that is scrolled */
133
+ export function readScrolls(root: Element): Map<Element, ScrollPosition> {
134
+ const scrolls = new Map<Element, ScrollPosition>();
135
+ for (const el of elementsOf(root)) {
136
+ if (el.scrollTop || el.scrollLeft) {
137
+ scrolls.set(el, { top: el.scrollTop, left: el.scrollLeft });
138
+ }
139
+ }
140
+ return scrolls;
141
+ }
142
+
143
+ /** what `root` shows now. Read it while `root` is still in the document. */
144
+ export function readShown(root: Element): Shown {
145
+ return { animations: readAnimations(root), scrolls: readScrolls(root) };
146
+ }
147
+
148
+ /** what `original` showed, moved onto the matching elements of `copy`, a deep clone of it */
149
+ export function shownOnCopy(
150
+ shown: Shown,
151
+ original: Element,
152
+ copy: Element,
153
+ ): Shown {
154
+ const from = elementsOf(original);
155
+ const to = elementsOf(copy);
156
+ const animations = new Map<Element, readonly ShownAnimation[]>();
157
+ const scrolls = new Map<Element, ScrollPosition>();
158
+ from.forEach((el, i) => {
159
+ const counterpart = to[i];
160
+ if (!counterpart) {
161
+ return;
162
+ }
163
+ const animation = shown.animations.get(el);
164
+ if (animation) {
165
+ animations.set(counterpart, animation);
166
+ }
167
+ const scroll = shown.scrolls.get(el);
168
+ if (scroll) {
169
+ scrolls.set(counterpart, scroll);
170
+ }
171
+ });
172
+ return { animations, scrolls };
173
+ }
174
+
175
+ /**
176
+ * Records the scroll offset of every element inside `root` as it scrolls. For
177
+ * a layer that learns which elements left only after the render that removed
178
+ * them, by when their offsets are gone.
179
+ */
180
+ export function trackScrolls(root: Element): {
181
+ scrolls: Shown['scrolls'];
182
+ stop(): void;
183
+ } {
184
+ const scrolls = new WeakMap<Element, ScrollPosition>();
185
+ const record = (event: Event) => {
186
+ const el = event.target;
187
+ if (el instanceof Element) {
188
+ scrolls.set(el, { top: el.scrollTop, left: el.scrollLeft });
189
+ }
190
+ };
191
+ // scroll events do not bubble, but they do pass ancestors while capturing
192
+ root.addEventListener('scroll', record, { capture: true, passive: true });
193
+ return {
194
+ scrolls,
195
+ stop: () => root.removeEventListener('scroll', record, { capture: true }),
196
+ };
197
+ }
198
+
199
+ let memoryStyle: HTMLStyleElement | undefined;
200
+
201
+ function ensureMemoryStyle() {
202
+ if (memoryStyle?.isConnected) {
203
+ return;
204
+ }
205
+ const m = `[${MEMORY_ATTRIBUTE}]`;
206
+ memoryStyle = document.createElement('style');
207
+ memoryStyle.setAttribute('data-motion-memory-style', '');
208
+ memoryStyle.textContent =
209
+ `${m},${m}::before,${m}::after,${m} *,${m} *::before,${m} *::after` +
210
+ '{backdrop-filter:none!important;-webkit-backdrop-filter:none!important}';
211
+ document.head.appendChild(memoryStyle);
212
+ }
213
+
214
+ /** nothing in a memory can be found by id or test hook, submitted, focused, loaded or played */
215
+ function neutralize(el: Element) {
216
+ for (const name of el.getAttributeNames()) {
217
+ if (name === 'data-test' || name.startsWith('data-test-')) {
218
+ el.setAttribute(`${name}${MEMORY_HOOK_SUFFIX}`, el.getAttribute(name)!);
219
+ el.removeAttribute(name);
220
+ }
221
+ }
222
+ if (!(el instanceof SVGElement)) {
223
+ el.removeAttribute('id');
224
+ }
225
+ el.removeAttribute('autofocus');
226
+ if (el.matches(NAMED_CONTROLS)) {
227
+ el.removeAttribute('name');
228
+ }
229
+ if (el instanceof HTMLIFrameElement) {
230
+ el.removeAttribute('srcdoc');
231
+ el.src = 'about:blank';
232
+ } else if (el instanceof HTMLMediaElement) {
233
+ el.removeAttribute('autoplay');
234
+ el.preload = 'none';
235
+ }
236
+ }
237
+
238
+ /** each stylesheet animation pauses where it was shown, or jumps to its end */
239
+ function holdAnimations(root: Element, shown: Shown['animations']) {
240
+ for (const animation of root.getAnimations({ subtree: true })) {
241
+ const effect = animation.effect;
242
+ if (!onDocumentTimeline(animation) || !(effect instanceof KeyframeEffect)) {
243
+ continue;
244
+ }
245
+ const was = effect.target
246
+ ? shown
247
+ .get(effect.target)
248
+ ?.find(
249
+ (a) =>
250
+ a.name === animation.animationName &&
251
+ a.pseudoElement === effect.pseudoElement,
252
+ )
253
+ : undefined;
254
+ try {
255
+ if (was && !was.finished) {
256
+ animation.pause();
257
+ animation.currentTime = was.time;
258
+ } else {
259
+ animation.finish();
260
+ }
261
+ } catch {
262
+ // an endless animation has no end to jump to
263
+ animation.cancel();
264
+ }
265
+ }
266
+ }
267
+
268
+ /**
269
+ * Makes `root` a memory of what `shown` describes, and puts it back in the
270
+ * document with `insert`. Ids, form-control names and test hooks are changed
271
+ * before `insert` runs, so none of them joins the document even for a moment;
272
+ * scroll offsets and animations are restored after it, in the same task, so
273
+ * no frame shows them reset. Whatever is already in a memory (a leaver lifted
274
+ * out of one that left with it, a copy still fading inside a leaver) is put
275
+ * back and restored, and not changed twice.
276
+ */
277
+ export function keepAsMemory(
278
+ root: HTMLElement | SVGElement,
279
+ shown: Shown,
280
+ insert: () => void,
281
+ ) {
282
+ ensureMemoryStyle();
283
+ const elements = elementsOf(root);
284
+ for (const el of elements) {
285
+ if (!el.closest(`[${MEMORY_ATTRIBUTE}]`)) {
286
+ neutralize(el);
287
+ }
288
+ }
289
+ root.setAttribute(MEMORY_ATTRIBUTE, '');
290
+ root.setAttribute('inert', '');
291
+ root.setAttribute('aria-hidden', 'true');
292
+ root.style.willChange = 'transform, opacity';
293
+ insert();
294
+ for (const el of elements) {
295
+ const scroll = shown.scrolls.get(el);
296
+ if (scroll) {
297
+ el.scrollTop = scroll.top;
298
+ el.scrollLeft = scroll.left;
299
+ }
300
+ }
301
+ holdAnimations(root, shown.animations);
302
+ }
@@ -37,6 +37,7 @@ import {
37
37
  SVGVisualElement,
38
38
  visualElementStore,
39
39
  } from 'motion-dom';
40
+ import { MotionGlobalConfig } from 'motion-utils';
40
41
 
41
42
  import { registerBusyProbe } from './activity.ts';
42
43
  import { initFeatures } from './features.ts';
@@ -65,6 +66,19 @@ import { postRender } from './scheduler.ts';
65
66
  import { motionSpeed, onMotionSpeed, slowed } from './speed.ts';
66
67
  import { styleValue } from './unitless.ts';
67
68
 
69
+ /**
70
+ * The values motion-dom can hand to the browser to play (its
71
+ * `acceleratedValues`), each with the property its computed style shows it
72
+ * under. While the browser plays one, its motion value does not follow frame
73
+ * by frame.
74
+ */
75
+ const BROWSER_PLAYED: Record<string, string> = {
76
+ opacity: 'opacity',
77
+ clipPath: 'clip-path',
78
+ filter: 'filter',
79
+ transform: 'transform',
80
+ };
81
+
68
82
  /** motion-dom's own `defaultLayoutTransition`, which it does not export */
69
83
  const DEFAULT_LAYOUT_TRANSITION = {
70
84
  duration: 0.45,
@@ -272,7 +286,7 @@ const pendingPops: { apply: () => void }[] = [];
272
286
  let popFlushBooked = false;
273
287
 
274
288
  /** where a popLayout leaver is sitting, and the box it has to be pinned inside */
275
- interface PopSeat {
289
+ export interface PopSeat {
276
290
  h: number;
277
291
  isRTL: boolean;
278
292
  left: number;
@@ -282,7 +296,29 @@ interface PopSeat {
282
296
  w: number;
283
297
  }
284
298
 
285
- function seatOf(el: MotionEl): PopSeat | undefined {
299
+ /**
300
+ * The two insets that pin a box lifted out of flow to its seat: one inline,
301
+ * from the side `anchorX` names (mirrored in right-to-left text), and one
302
+ * block, from the side `anchorY` names.
303
+ */
304
+ export function seatInsets(
305
+ seat: PopSeat,
306
+ anchorX: 'left' | 'right',
307
+ anchorY: 'top' | 'bottom',
308
+ ): {
309
+ block: ['top' | 'bottom', number];
310
+ inline: ['left' | 'right', number];
311
+ } {
312
+ const { w, h, top, left, parentWidth, parentHeight, isRTL } = seat;
313
+ const fromLeft = (anchorX === 'left') !== isRTL;
314
+ return {
315
+ inline: fromLeft ? ['left', left] : ['right', parentWidth - w - left],
316
+ block:
317
+ anchorY === 'bottom' ? ['bottom', parentHeight - h - top] : ['top', top],
318
+ };
319
+ }
320
+
321
+ export function seatOf(el: MotionEl): PopSeat | undefined {
286
322
  const parent = (el as HTMLElement).offsetParent as HTMLElement | null;
287
323
  const cs = getComputedStyle(el);
288
324
  const w = parseFloat(cs.width),
@@ -618,6 +654,8 @@ export class MotionNode implements MotionParticipant, PopMeasurable {
618
654
  private popStyle?: HTMLStyleElement;
619
655
  /** the seat measured before the render that removed this element */
620
656
  private popSeat?: PopSeat;
657
+ /** snapshot: the seat this element had in the last frame (see recordSeat) */
658
+ private shownSeat?: PopSeat;
621
659
  private unregisterPop?: () => void;
622
660
  /** participant identity: a participant host above selects this element by these */
623
661
  id: string | null = null;
@@ -1326,6 +1364,87 @@ export class MotionNode implements MotionParticipant, PopMeasurable {
1326
1364
  }
1327
1365
  }
1328
1366
 
1367
+ /**
1368
+ * `<Presence @mode="snapshot">`: reads the seat this element has now, for
1369
+ * the copy of it to be pinned to if it leaves. <Presence> calls this in each
1370
+ * frame that follows a render, and whenever this element or the box it is
1371
+ * pinned inside resizes. That covers the moves a render or a resize makes;
1372
+ * a move that is neither, such as a transition on a sibling's width that
1373
+ * shifts this element without resizing either box, leaves the seat where it
1374
+ * was. Returns the elements the seat depends on, for <Presence> to watch
1375
+ * for resizes.
1376
+ */
1377
+ recordSeat(): Element[] {
1378
+ const el = this.element;
1379
+ if (this.destroyed || !el?.isConnected) {
1380
+ return [];
1381
+ }
1382
+ this.shownSeat = seatOf(el);
1383
+ const parent = (el as HTMLElement).offsetParent;
1384
+ return parent ? [el, parent] : [el];
1385
+ }
1386
+
1387
+ /**
1388
+ * `<Presence @mode="snapshot">`: what a static copy of this element needs to
1389
+ * play the exit this element would have played. <Presence> reads it from
1390
+ * its diff, before the render that removes the element.
1391
+ *
1392
+ * `seat` is the one recorded before this render (see recordSeat). By the
1393
+ * time the diff runs, content earlier in the template has already been
1394
+ * patched for this render, and may have moved the element somewhere no
1395
+ * frame showed it.
1396
+ *
1397
+ * The copy starts from every value this element shows now. The browser
1398
+ * plays some of them (opacity, clip-path, filter, a whole transform), and a
1399
+ * motion value does not follow those frame by frame, so those are read from
1400
+ * the computed style. The copy inherits nothing from the variant tree: the
1401
+ * tree it would inherit from has already moved on.
1402
+ *
1403
+ * Undefined when there is no exit to play: the element declares no `exit`
1404
+ * of its own, has not mounted, or animations are skipped.
1405
+ */
1406
+ exitForCopy():
1407
+ | { element: MotionEl; props: MotionProps; seat: PopSeat | undefined }
1408
+ | undefined {
1409
+ const { element, ve, latest } = this;
1410
+ if (this.destroyed || !element?.isConnected || !ve?.current || !latest) {
1411
+ return undefined;
1412
+ }
1413
+ const props = latest.props as MotionProps;
1414
+ if (
1415
+ props.exit === undefined ||
1416
+ this.config.skipAnimations ||
1417
+ MotionGlobalConfig.skipAnimations ||
1418
+ MotionGlobalConfig.instantAnimations
1419
+ ) {
1420
+ return undefined;
1421
+ }
1422
+ const computed = getComputedStyle(element);
1423
+ const initial: Record<string, string | number> = {};
1424
+ ve.values.forEach((value, key) => {
1425
+ const css = BROWSER_PLAYED[key];
1426
+ if (css) {
1427
+ const shown = computed.getPropertyValue(css);
1428
+ initial[key] = key === 'opacity' ? parseFloat(shown) : shown;
1429
+ } else {
1430
+ initial[key] = value.get() as string | number;
1431
+ }
1432
+ });
1433
+ return {
1434
+ element,
1435
+ seat: this.shownSeat,
1436
+ props: {
1437
+ initial,
1438
+ exit: props.exit,
1439
+ variants: props.variants,
1440
+ transition: props.transition,
1441
+ custom: props.custom,
1442
+ transformTemplate: props.transformTemplate,
1443
+ inherit: false,
1444
+ },
1445
+ };
1446
+ }
1447
+
1329
1448
  /** popLayout: a leaving element is lifted out of flow at its last box so siblings can close up */
1330
1449
  private pop(el: MotionEl, isPresent: boolean, handle: PresenceHandle) {
1331
1450
  if (isPresent) {
@@ -1342,18 +1461,10 @@ export class MotionNode implements MotionParticipant, PopMeasurable {
1342
1461
  if (!seat) {
1343
1462
  return;
1344
1463
  }
1345
- const { w, h, top, left, parentWidth, parentHeight, isRTL } = seat;
1346
- const right = parentWidth - w - left,
1347
- bottom = parentHeight - h - top;
1348
- const x =
1349
- handle.anchorX === 'left'
1350
- ? isRTL
1351
- ? `right:${right}`
1352
- : `left:${left}`
1353
- : isRTL
1354
- ? `left:${left}`
1355
- : `right:${right}`;
1356
- const y = handle.anchorY === 'bottom' ? `bottom:${bottom}` : `top:${top}`;
1464
+ const { w, h } = seat;
1465
+ const { inline, block } = seatInsets(seat, handle.anchorX, handle.anchorY);
1466
+ const x = `${inline[0]}:${inline[1]}`;
1467
+ const y = `${block[0]}:${block[1]}`;
1357
1468
  const id = `pop-${Math.random().toString(36).slice(2, 8)}`;
1358
1469
  el.dataset['motionPopId'] = id;
1359
1470
  // measured above against the layout as it stands; the lift itself waits
@@ -1,12 +1,20 @@
1
1
  import type { PresenceContextProps } from 'motion-dom';
2
2
 
3
+ import type { MotionEl, MotionProps, PopSeat } from './node.ts';
4
+
3
5
  /**
4
- * A direct child of a popLayout <Presence>, which has to be measured before
5
- * the render that removes it. React's PopChildMeasure does this in
6
- * getSnapshotBeforeUpdate; see MotionNode.measureForPop.
6
+ * A direct child of a <Presence>, which has to be read before the render
7
+ * that removes it: popLayout measures its seat (React's PopChildMeasure does
8
+ * this in getSnapshotBeforeUpdate; see MotionNode.measureForPop), and
9
+ * snapshot copies it (see MotionNode.exitForCopy), pinning the copy to the
10
+ * seat recorded before that render (see MotionNode.recordSeat).
7
11
  */
8
12
  export interface PopMeasurable {
13
+ exitForCopy():
14
+ | { element: MotionEl; props: MotionProps; seat: PopSeat | undefined }
15
+ | undefined;
9
16
  measureForPop(): void;
17
+ recordSeat(): Element[];
10
18
  }
11
19
 
12
20
  /** what one item inside <Presence> hands to its {{motion}} element */
@@ -16,8 +24,8 @@ export interface PresenceHandle {
16
24
  readonly context: PresenceContextProps;
17
25
  readonly isPresent: boolean;
18
26
  readonly key: string;
19
- readonly mode: 'sync' | 'wait' | 'popLayout';
20
- /** popLayout: the direct child asks to be measured while it is still in flow */
27
+ readonly mode: 'sync' | 'wait' | 'popLayout' | 'snapshot';
28
+ /** popLayout and snapshot: the direct child asks to be read while it is still in flow */
21
29
  popCandidate(node: PopMeasurable): () => void;
22
30
  /** motion elements that inherit this presence (React: PresenceContext consumers) ask to be refreshed when it flips */
23
31
  subscribe(refresh: () => void): () => void;