@cardstack/boxel-cli 0.12.0 → 0.13.0-unstable.1

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 (196) hide show
  1. package/bundled-realms/base/command.gts +4 -4
  2. package/bundled-realms/base/realm-api-keys.gts +35 -3
  3. package/bundled-realms/pretui/components/chip.gts +47 -2
  4. package/bundled-realms/pretui/components/chip.md +5 -2
  5. package/bundled-realms/pretui/components/chip.usage.gts +37 -2
  6. package/bundled-realms/pretui/components/status-chip.gts +12 -2
  7. package/bundled-realms/pretui/components/status-chip.md +2 -1
  8. package/bundled-realms/pretui/components/status-chip.usage.gts +2 -0
  9. package/bundled-test-harness/assets/{ai-module-creation-test-B9lSKNq1.js → ai-module-creation-test-DxxsXNXe.js} +1 -1
  10. package/bundled-test-harness/assets/{aibot-message-LXV0UlzI.js → aibot-message-Dg5nzE1b.js} +1 -1
  11. package/bundled-test-harness/assets/{app-BIQcKrVT.js → app-BEvPLshl.js} +8 -8
  12. package/bundled-test-harness/assets/{assistant-transcript-test-CG_2-bog.js → assistant-transcript-test-kILRv_pf.js} +1 -1
  13. package/bundled-test-harness/assets/{binary-upload-dedupe-test-Q7e6bzl1.js → binary-upload-dedupe-test-B_gcWqxF.js} +1 -1
  14. package/bundled-test-harness/assets/{bundled-base-DldokUSJ.js → bundled-base-CYD5iFX3.js} +3 -3
  15. package/bundled-test-harness/assets/{bundled-base-modules-test-VAlRx4t3.js → bundled-base-modules-test-bO7yZysu.js} +1 -1
  16. package/bundled-test-harness/assets/{can-invoke-test-BGmUJHxo.js → can-invoke-test-BBTE4_cE.js} +1 -1
  17. package/bundled-test-harness/assets/{card-chooser-paging-test-CzooKL4I.js → card-chooser-paging-test-14IqQei3.js} +1 -1
  18. package/bundled-test-harness/assets/{card-chooser-test-C3fjaDYi.js → card-chooser-test-CRg2JrXQ.js} +1 -1
  19. package/bundled-test-harness/assets/{card-def-field-def-relationships-test-yqv8ir8Y.js → card-def-field-def-relationships-test-BapYGyoB.js} +1 -1
  20. package/bundled-test-harness/assets/{card-delete-test-BtjqrqKO.js → card-delete-test-CvbDQUUE.js} +1 -1
  21. package/bundled-test-harness/assets/{chip-D6Xffhjx.css → chip-B2Ns53A6.css} +35 -9
  22. package/bundled-test-harness/assets/{chip-BydgljDL.js → chip-DVJE_YDh.js} +11 -3
  23. package/bundled-test-harness/assets/{choreo-D7dLCdGB.js → choreo-CIrop3HM.js} +16 -35
  24. package/bundled-test-harness/assets/choreo-Ca2Y2sF3.js +2 -0
  25. package/bundled-test-harness/assets/{choreo-feature-reel-test--x-L3ZKc.js → choreo-feature-reel-test-CJHms7Sf.js} +2 -2
  26. package/bundled-test-harness/assets/{choreo-gallery-ZO9vXOxd.js → choreo-gallery-DZm1KZ20.js} +3 -3
  27. package/bundled-test-harness/assets/{choreo-gallery-build-order-test-DG5UTyc9.js → choreo-gallery-build-order-test-QKyMWELN.js} +4 -4
  28. package/bundled-test-harness/assets/{choreo-gallery-compositor-test-DJP80SNj.js → choreo-gallery-compositor-test-BQDptxfE.js} +1 -1
  29. package/bundled-test-harness/assets/{choreo-gallery-cover-test-CHYz7sM4.js → choreo-gallery-cover-test-Do-HYF96.js} +1 -1
  30. package/bundled-test-harness/assets/{choreo-gallery-crossing-stage-test-BuinlINr.js → choreo-gallery-crossing-stage-test-yJVXghRc.js} +10 -10
  31. package/bundled-test-harness/assets/{choreo-gallery-demos-alive-test-BjNCGfH9.js → choreo-gallery-demos-alive-test-BCeaaZye.js} +1 -1
  32. package/bundled-test-harness/assets/{choreo-gallery-drift-test-BJ17f3yv.js → choreo-gallery-drift-test-CJ1yq6R0.js} +1 -1
  33. package/bundled-test-harness/assets/{choreo-gallery-escort-test-Dadih8-S.js → choreo-gallery-escort-test-DypqSgvs.js} +1 -1
  34. package/bundled-test-harness/assets/{choreo-gallery-films-test-CCXty_WI.js → choreo-gallery-films-test-ClMy5M_p.js} +2 -2
  35. package/bundled-test-harness/assets/{choreo-gallery-filter-test-BozCoT_M.js → choreo-gallery-filter-test-CzOmgqgl.js} +1 -1
  36. package/bundled-test-harness/assets/{choreo-gallery-fold-test-Kez2P3_Y.js → choreo-gallery-fold-test-qNnH6wtC.js} +2 -2
  37. package/bundled-test-harness/assets/{choreo-gallery-guide-pages-test-BV5oxrYA.js → choreo-gallery-guide-pages-test-BtKWMkTv.js} +1 -1
  38. package/bundled-test-harness/assets/{choreo-gallery-guides-test-DwYBDgKi.js → choreo-gallery-guides-test-CXMsuDYi.js} +4 -3
  39. package/bundled-test-harness/assets/{choreo-gallery-hang-test-B-5u7cGJ.js → choreo-gallery-hang-test-BD4JQ04e.js} +2 -2
  40. package/bundled-test-harness/assets/{choreo-gallery-highlight-test-BZ5Lg8V6.js → choreo-gallery-highlight-test-BK280K2W.js} +2 -2
  41. package/bundled-test-harness/assets/{choreo-gallery-inline-edit-test-wSlSI-VM.js → choreo-gallery-inline-edit-test-DAgUcaaw.js} +2 -2
  42. package/bundled-test-harness/assets/{choreo-gallery-interruption-test-DBpCEiro.js → choreo-gallery-interruption-test-DY5KUilC.js} +4 -4
  43. package/bundled-test-harness/assets/{choreo-gallery-lone-stack-test-aMLrHsns.js → choreo-gallery-lone-stack-test-Dxd_Nb0-.js} +4 -4
  44. package/bundled-test-harness/assets/{choreo-gallery-navigation-test-B8b-2ZTz.js → choreo-gallery-navigation-test-DWJq12PN.js} +1 -1
  45. package/bundled-test-harness/assets/{choreo-gallery-playhead-transport-test-B95CYBh7.js → choreo-gallery-playhead-transport-test-DlQ6yTKW.js} +1 -1
  46. package/bundled-test-harness/assets/{choreo-gallery-poplayout-subtree-test-jZAAICV6.js → choreo-gallery-poplayout-subtree-test-D59W8gYt.js} +5 -5
  47. package/bundled-test-harness/assets/{choreo-gallery-presence-modes-test-BawIKB-a.js → choreo-gallery-presence-modes-test-BAU-dggP.js} +2 -2
  48. package/bundled-test-harness/assets/{choreo-gallery-presentation-test-CYIb-8ML.js → choreo-gallery-presentation-test-BqGsinE0.js} +2 -2
  49. package/bundled-test-harness/assets/{choreo-gallery-reduced-motion-test-C6Vi1WYr.js → choreo-gallery-reduced-motion-test-By0rb2Ai.js} +4 -3
  50. package/bundled-test-harness/assets/{choreo-gallery-sheet-standalone-test-_e4T5KDI.js → choreo-gallery-sheet-standalone-test-Cfw-4ULF.js} +2 -2
  51. package/bundled-test-harness/assets/{choreo-gallery-sheet-test-Cv51Te3G.js → choreo-gallery-sheet-test-dm53Pdzb.js} +2 -2
  52. package/bundled-test-harness/assets/{choreo-gallery-site-test-Cm35kHu_.js → choreo-gallery-site-test-SAlqtZgL.js} +3 -2
  53. package/bundled-test-harness/assets/{choreo-gallery-stage-Bp6QVqlI.js → choreo-gallery-stage-CU8LvQqX.js} +2 -2
  54. package/bundled-test-harness/assets/{choreo-gallery-stage-keyboard-test-DDX2cuMa.js → choreo-gallery-stage-keyboard-test-DQ4v5qA4.js} +2 -2
  55. package/bundled-test-harness/assets/{choreo-gallery-stages-test-Dfa_33_4.js → choreo-gallery-stages-test-CRvaS6w6.js} +2 -2
  56. package/bundled-test-harness/assets/{choreo-gallery-subdivision-test-CgR3WdcT.js → choreo-gallery-subdivision-test-CtOm-U35.js} +3 -3
  57. package/bundled-test-harness/assets/{choreo-gallery-table-plan-test-DF-cE3J9.js → choreo-gallery-table-plan-test-_0koIm2u.js} +1 -1
  58. package/bundled-test-harness/assets/{choreo-gallery-theater-test-BuIyb2ke.js → choreo-gallery-theater-test-yedwQVyP.js} +4 -4
  59. package/bundled-test-harness/assets/{choreo-gallery-tuning-test-BdZ45YBK.js → choreo-gallery-tuning-test-CmMtfJ5a.js} +1 -1
  60. package/bundled-test-harness/assets/{choreo-gallery-widget-room-test-HcPX75Dg.js → choreo-gallery-widget-room-test-jnYOas5x.js} +2 -2
  61. package/bundled-test-harness/assets/{choreo-gallery-wires-test-BhFCIjB1.js → choreo-gallery-wires-test-CSyqjVnm.js} +2 -2
  62. package/bundled-test-harness/assets/clip--wioup_3.js +2 -0
  63. package/bundled-test-harness/assets/{clip-BmBVqUyU.js → clip-Cm32sLyn.js} +3 -3
  64. package/bundled-test-harness/assets/{codeblocks-test-DbZYlmG_.js → codeblocks-test-j4ES37-L.js} +1 -1
  65. package/bundled-test-harness/assets/{compaction-test-D1G3KCSe.js → compaction-test-CyhMODxy.js} +1 -1
  66. package/bundled-test-harness/assets/{container-BCOmNUou.css → container-CAEYXGQg.css} +179 -155
  67. package/bundled-test-harness/assets/{container-XSX5809v.js → container-CATqNgYF.js} +20 -19
  68. package/bundled-test-harness/assets/{create-listing-modal-test--oGT9P_F.js → create-listing-modal-test-QmWcm9hl.js} +1 -1
  69. package/bundled-test-harness/assets/{debug-message-test-C8UZUtCt.js → debug-message-test-D-4Dcu8y.js} +1 -1
  70. package/bundled-test-harness/assets/{externals--YMzwHwd.js → externals-Cw0MmkHM.js} +38 -38
  71. package/bundled-test-harness/assets/{fallback-banner-live-delete-test-DPK_coJY.js → fallback-banner-live-delete-test-Bzqw4uMs.js} +1 -1
  72. package/bundled-test-harness/assets/{fallback-banner-test-C2wi6Lu6.js → fallback-banner-test-BMQVK4gy.js} +1 -1
  73. package/bundled-test-harness/assets/{fallback-models-test-SqdXWB1Z.js → fallback-models-test-DkdjPwvA.js} +1 -1
  74. package/bundled-test-harness/assets/{file-attachment-test-DT0HK6yS.js → file-attachment-test-yvR5gw9_.js} +1 -1
  75. package/bundled-test-harness/assets/{film-Zt2YoVoG.js → film-9V69qBWw.js} +6 -6
  76. package/bundled-test-harness/assets/{film-77OfTGL7.js → film-Z87hSVo_.js} +8 -8
  77. package/bundled-test-harness/assets/film-h1N8vUjG.js +2 -0
  78. package/bundled-test-harness/assets/{formatted-aibot-message-test-B6i0k9wW.js → formatted-aibot-message-test-DH3WPvU1.js} +1 -1
  79. package/bundled-test-harness/assets/{general-test-C0Xb2Idr.js → general-test-D2j4JhrD.js} +1 -1
  80. 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
  81. package/bundled-test-harness/assets/{glimmer-motion-and-choreo-test-B2ustNm9.js → glimmer-motion-and-choreo-test-BM7uX8k4.js} +10 -10
  82. package/bundled-test-harness/assets/group-Cgg5RLul.js +2 -0
  83. package/bundled-test-harness/assets/{group-BNxp6qON.js → group-DL-b_Hzj.js} +2 -2
  84. package/bundled-test-harness/assets/{index-query-engine-test-BKOqgBMv.js → index-query-engine-test-DFkm037X.js} +1 -1
  85. package/bundled-test-harness/assets/{instance-filter-matcher-test-CNIzKZvP.js → instance-filter-matcher-test-UqB2Rto8.js} +1 -1
  86. package/bundled-test-harness/assets/item-Bu9hhPf2.js +2 -0
  87. package/bundled-test-harness/assets/{item-BDyVqxdA.js → item-CBEoTOyj.js} +2 -2
  88. package/bundled-test-harness/assets/{main-BiI-FhMc.js → main-CM62YtMN.js} +1 -1
  89. package/bundled-test-harness/assets/{markdown-preview-BItI_AEl.js → markdown-preview-D_QssEOi.js} +1 -1
  90. package/bundled-test-harness/assets/{markdown-preview-test-Bntyikh1.js → markdown-preview-test-nljzB8o_.js} +1 -1
  91. package/bundled-test-harness/assets/memory-B73-H_Rp.js +223 -0
  92. package/bundled-test-harness/assets/{model-cost-tier-test-Hu2-LpEw.js → model-cost-tier-test-CDXxio0B.js} +1 -1
  93. package/bundled-test-harness/assets/{motion-DeM7UCtd.js → motion-BiMvMS08.js} +1 -1
  94. package/bundled-test-harness/assets/{node-BAM535H5.js → node-BWOLCOMI.js} +95 -6
  95. package/bundled-test-harness/assets/{operation-lowering-test-CG1gatuH.js → operation-lowering-test-C_CaTJ0F.js} +1 -1
  96. package/bundled-test-harness/assets/{operator-mode-card-chooser-test-Bo5ZnF_x.js → operator-mode-card-chooser-test-CU5f7q6r.js} +1 -1
  97. package/bundled-test-harness/assets/{operator-mode-links-test-DVx_LQOU.js → operator-mode-links-test-CoeF3prV.js} +1 -1
  98. package/bundled-test-harness/assets/{operator-mode-test-CCbFrYgN.js → operator-mode-test-B9c6N5FF.js} +1 -1
  99. package/bundled-test-harness/assets/{operator-mode-ui-test-Dm2Bf6Vn.js → operator-mode-ui-test-CJQxypf_.js} +1 -1
  100. package/bundled-test-harness/assets/{overlay-menu-items-test-Bk5AidFD.js → overlay-menu-items-test-CFWZ-tzq.js} +1 -1
  101. package/bundled-test-harness/assets/{overlays-CY7IgeOl.js → overlays-DZXjQykn.js} +3 -3
  102. package/bundled-test-harness/assets/{overlays-DhVKmSP-.js → overlays-hrAO43jG.js} +1 -1
  103. package/bundled-test-harness/assets/{panel-t9QyU1lY.js → panel-CXmB6GMs.js} +15 -75
  104. package/bundled-test-harness/assets/{panel-CUYJ8oHS.css → panel-D2zkZoIr.css} +39 -31
  105. package/bundled-test-harness/assets/{past-sessions-test-Dq-Y9T6W.js → past-sessions-test-7FgLJlog.js} +1 -1
  106. package/bundled-test-harness/assets/{permission-checking-realm-test-CuImx8ie.js → permission-checking-realm-test-Cm9eB-hE.js} +1 -1
  107. package/bundled-test-harness/assets/pill-menu-Ctn3JNqG.js +154 -0
  108. package/bundled-test-harness/assets/{pill-menu-test-kpWBGrX5.js → pill-menu-test-Cf2xO2w6.js} +1 -1
  109. package/bundled-test-harness/assets/plate-CnatwP3n.js +2 -0
  110. package/bundled-test-harness/assets/{plate-B4Lz2Ulo.js → plate-Cu5EnXQZ.js} +3 -3
  111. package/bundled-test-harness/assets/player-Bwn5tEsh.js +2 -0
  112. package/bundled-test-harness/assets/{player-B965XH82.js → player-CVdsx7Ng.js} +3 -3
  113. package/bundled-test-harness/assets/presence-BKkR2au7.js +553 -0
  114. package/bundled-test-harness/assets/presence-CcEXVges.js +2 -0
  115. package/bundled-test-harness/assets/{quick-look-test-VW5_W7dm.js → quick-look-test-DrqgCLlM.js} +1 -1
  116. package/bundled-test-harness/assets/{realm-api-keys-Tlj0OK1c.css → realm-api-keys-16eN_yJb.css} +35 -25
  117. package/bundled-test-harness/assets/{realm-api-keys-sg--fohv.js → realm-api-keys-ZJedocgl.js} +11 -5
  118. package/bundled-test-harness/assets/realm-api-keys-m-EOSWuV.js +2 -0
  119. package/bundled-test-harness/assets/{realm-api-keys-test-mrgvnhFJ.js → realm-api-keys-test-Ba_1CKJu.js} +3 -5
  120. package/bundled-test-harness/assets/{realm-api-keys-test-Cy7qSoAd.js → realm-api-keys-test-jXscHLbS.js} +1 -1
  121. package/bundled-test-harness/assets/{realm-config-DvpP1xf-.js → realm-config-DIOozzdQ.js} +1 -1
  122. package/bundled-test-harness/assets/{realm-config-policy-standing-test-DJk-ftgH.js → realm-config-policy-standing-test-DEFQEEvl.js} +1 -1
  123. package/bundled-test-harness/assets/{realm-config-routing-rule-edit-test-CTPiJrAL.js → realm-config-routing-rule-edit-test-CtLYsxzq.js} +1 -1
  124. package/bundled-test-harness/assets/{realm-config-settings-test-CUa-PjMC.js → realm-config-settings-test-Dsatuv-T.js} +1 -1
  125. package/bundled-test-harness/assets/{reasoning-test-CeeGGwvf.js → reasoning-test-3p_PygAV.js} +1 -1
  126. package/bundled-test-harness/assets/{rendered-markdown-B8TH0yN4.js → rendered-markdown-DPEXS7MQ.js} +1 -1
  127. package/bundled-test-harness/assets/{rendered-markdown-test-CBRsIBqW.js → rendered-markdown-test-DxbJ1nFX.js} +1 -1
  128. package/bundled-test-harness/assets/{room-message-test-BTxZ_W74.js → room-message-test-BBE8UIPX.js} +1 -1
  129. package/bundled-test-harness/assets/{run-BSEzn_PH.js → run-CfyzhBXb.js} +2 -2
  130. package/bundled-test-harness/assets/{schema-generation-test-De1eamwr.js → schema-generation-test-hkFzUr7-.js} +1 -1
  131. package/bundled-test-harness/assets/{scrolling-test-C7R0UASi.js → scrolling-test-B8CudFE9.js} +2 -2
  132. package/bundled-test-harness/assets/{searchable-option-test-BiOqxIIs.js → searchable-option-test-B8lZyJij.js} +1 -1
  133. package/bundled-test-harness/assets/{sending-test-DOXQ0Tvx.js → sending-test-BtH1z2uE.js} +1 -1
  134. package/bundled-test-harness/assets/{skills-test-BjCYI4Z_.js → skills-test-DIQvoCLm.js} +1 -1
  135. package/bundled-test-harness/assets/src-CEw1V0fi.js +11 -0
  136. package/bundled-test-harness/assets/{src-Bhefkhqj.js → src-CbBcj3xm.js} +11 -5
  137. package/bundled-test-harness/assets/{src-BDDYD1rs.js → src-D8bpj2tb.js} +2 -2
  138. package/bundled-test-harness/assets/{src-DySBAkac.js → src-Df4Vutrk.js} +3 -3
  139. package/bundled-test-harness/assets/{stick-to-bottom-resize-test-hN0AjdXP.js → stick-to-bottom-resize-test-Du4B3w2x.js} +4 -4
  140. package/bundled-test-harness/assets/{store-test-BD99H9dQ.js → store-test-DoHgyfQA.js} +1 -1
  141. package/bundled-test-harness/assets/{test-support-BcjhIHQa.js → test-support-BOFeeYuE.js} +25 -2
  142. package/bundled-test-harness/assets/test-support-CXESl4Wf.js +3 -0
  143. package/bundled-test-harness/assets/test-support-CgWmpebE.js +3 -0
  144. package/bundled-test-harness/assets/{test-support-Dl6FChlV.js → test-support-xTaMuaOA.js} +4 -27
  145. package/bundled-test-harness/assets/{tests-BqLDuQog.js → tests-DXX9TrgY.js} +759 -750
  146. package/bundled-test-harness/assets/{text-input-validator-test-CW6Ay7bo.js → text-input-validator-test-D56aDP2e.js} +1 -1
  147. package/bundled-test-harness/assets/{titles-CFt3dEls.js → titles-BUbpcMoe.js} +3 -3
  148. package/bundled-test-harness/assets/titles-D80DwOQU.js +2 -0
  149. package/bundled-test-harness/assets/{to-device-streaming-test-DRxNCi7Z.js → to-device-streaming-test-Ug8UnlUl.js} +1 -1
  150. package/bundled-test-harness/assets/{token-usage-test-Ck9rB3Ie.js → token-usage-test-BolKbZVL.js} +1 -1
  151. package/bundled-test-harness/assets/{tools-DRCjAZCp.js → tools-CkouX4wE.js} +1 -1
  152. package/bundled-test-harness/assets/{tools-test-B3zniq10.js → tools-test-CdYgRu5Z.js} +1 -1
  153. package/bundled-test-harness/assets/{turn-settling-test-C0X6hL7x.js → turn-settling-test-CS0PlpTg.js} +1 -1
  154. package/bundled-test-harness/assets/{turn-summary-0XOoAv3j.js → turn-summary-CAZVdcBd.js} +1 -1
  155. package/bundled-test-harness/assets/{unshare-key-Dfy1IK9g.js → unshare-key-W_bNI_MF.js} +0 -1
  156. package/bundled-test-harness/index.html +29 -28
  157. package/bundled-test-harness/tests/index.html +29 -28
  158. package/bundled-types/base/command.d.ts +0 -1
  159. package/bundled-types/choreo/choreo.gts +21 -42
  160. package/bundled-types/choreo/test-support/index.ts +9 -30
  161. package/bundled-types/glimmer-motion/index.ts +11 -3
  162. package/bundled-types/glimmer-motion/memory.ts +302 -0
  163. package/bundled-types/glimmer-motion/node.ts +125 -14
  164. package/bundled-types/glimmer-motion/presence-types.ts +13 -5
  165. package/bundled-types/glimmer-motion/presence.gts +199 -11
  166. package/bundled-types/glimmer-motion/snapshot.ts +280 -0
  167. package/bundled-types/glimmer-motion/test-support/index.ts +36 -4
  168. package/bundled-types/host-app/components/ai-assistant/llm-select.gts +3 -0
  169. package/bundled-types/host-app/components/ai-assistant/skill-menu/index.gts +3 -0
  170. package/bundled-types/host-app/components/matrix/room.gts +15 -3
  171. package/bundled-types/host-app/components/operator-mode/dashboard/add-workspace.gts +5 -3
  172. package/bundled-types/host-app/components/operator-mode/dashboard/archived-workspace.gts +3 -5
  173. package/bundled-types/host-app/components/operator-mode/dashboard/catalog-shelf.gts +24 -7
  174. package/bundled-types/host-app/components/operator-mode/dashboard/workspace.gts +9 -4
  175. package/bundled-types/host-app/components/pill-menu/index.gts +74 -23
  176. package/bundled-types/host-app/lib/key-shares.ts +0 -1
  177. package/bundled-types/host-app/tools/get-key-share-usage.ts +0 -1
  178. package/dist/index.js +161 -161
  179. package/package.json +1 -1
  180. package/src/commands/keys/manage.ts +22 -6
  181. package/src/lib/user-keys.ts +18 -5
  182. package/bundled-test-harness/assets/choreo-Cd9yKlEK.js +0 -2
  183. package/bundled-test-harness/assets/clip-DnEy6oK5.js +0 -2
  184. package/bundled-test-harness/assets/film-C4NJTl9U.js +0 -2
  185. package/bundled-test-harness/assets/group-CPQojb-T.js +0 -2
  186. package/bundled-test-harness/assets/item-DGzMAZ9K.js +0 -2
  187. package/bundled-test-harness/assets/pill-menu-DaRpDMUm.js +0 -68
  188. package/bundled-test-harness/assets/plate-B7JjCVMj.js +0 -2
  189. package/bundled-test-harness/assets/player-DpGXST02.js +0 -2
  190. package/bundled-test-harness/assets/presence-CmLmFwhO.js +0 -2
  191. package/bundled-test-harness/assets/presence-Sv4y4tQy.js +0 -230
  192. package/bundled-test-harness/assets/realm-api-keys-DkdlXgAg.js +0 -2
  193. package/bundled-test-harness/assets/src-BpmcuCfH.js +0 -10
  194. package/bundled-test-harness/assets/test-support-CB3auo8h.js +0 -2
  195. package/bundled-test-harness/assets/test-support-DfGKwg35.js +0 -3
  196. package/bundled-test-harness/assets/titles-DVhrJZzS.js +0 -2
@@ -23,10 +23,14 @@ import { modifier } from 'ember-modifier';
23
23
  import {
24
24
  closestMotionConfig,
25
25
  flushPendingMounts,
26
+ keepAsMemory,
26
27
  motionSpeed,
27
28
  postRender,
29
+ readAnimations,
28
30
  registerBusyProbe,
31
+ type Shown,
29
32
  snapshotOnRender,
33
+ trackScrolls,
30
34
  } from 'glimmer-motion';
31
35
 
32
36
  import { type BeaconRef, measureBeacons } from './beacons.ts';
@@ -290,43 +294,6 @@ interface Signature {
290
294
 
291
295
  let debugStyle: HTMLStyleElement | undefined;
292
296
 
293
- /**
294
- * Two rules every region shares. An orphaned skin flies with its
295
- * backdrop-filters OFF: a backdrop-filter can never be cached — it
296
- * re-samples and re-blurs whatever is behind it on every frame — and
297
- * inside a scaling, fading leaver that is paid at full price for the
298
- * whole flight (the camera demo's four glass panels, over its gradient
299
- * washes, was the return trip's jank). It is also the wrong picture: a
300
- * skin is a memory of the OLD scene, and its glass sampling the new
301
- * scene behind it shows the wrong world. The panels keep their own
302
- * translucent grounds, so they still read as glass in the crossfade.
303
- *
304
- * And an orphaned skin does not REPLAY ITS BIRTH: re-inserting a node
305
- * restarts every CSS animation on it, so a leaver re-parented into the
306
- * orphan layer re-ran its entrance choreography — rules drawing
307
- * themselves, letters settling out of a blur — while it was supposed to
308
- * be dying. A one-frame-old memory acting out its own arrival is the
309
- * flash the Towers film chased for a night. The run's own exit motion
310
- * is WAAPI and survives re-parenting untouched; only stylesheet
311
- * animations restart, and on a memory they are all lies.
312
- */
313
- let orphanStyle: HTMLStyleElement | undefined;
314
- function ensureOrphanStyle() {
315
- if (orphanStyle) {
316
- return;
317
- }
318
- orphanStyle = document.createElement('style');
319
- orphanStyle.setAttribute('data-choreo-style', '');
320
- orphanStyle.textContent =
321
- '[data-choreo-orphans] *,' +
322
- '[data-choreo-orphans] *::before,' +
323
- '[data-choreo-orphans] *::after' +
324
- '{backdrop-filter:none!important;' +
325
- '-webkit-backdrop-filter:none!important;' +
326
- 'animation:none!important;}';
327
- document.head.appendChild(orphanStyle);
328
- }
329
-
330
297
  /**
331
298
  * A fingerprint of the collected timeline tree, for telling an EDIT from
332
299
  * noise while a run's own orphans are aloft. Mid-flight, compiled cues are
@@ -457,6 +424,10 @@ export class Choreo extends Component<Signature> implements ChoreoHost {
457
424
  /** participants whose Presence is letting them go: removed once, not on every pass while they leave */
458
425
  private leaving = new Set<ChoreoNode>();
459
426
  private snapshots = new Map<ChoreoNode, Snapshot>();
427
+ /** the stylesheet animations the region showed before the render, for the memories of what it removes */
428
+ private shownAnimations: Shown['animations'] = new Map();
429
+ /** every scroll offset inside the region, kept as it scrolls: a removed element has lost its own */
430
+ private scrolls?: Shown['scrolls'];
460
431
  /** participants with no layout box before this pass (display: none) */
461
432
  private unrendered = new Set<ChoreoNode>();
462
433
  /** the whole region had no layout box before this pass (hidden by an ancestor) */
@@ -736,6 +707,9 @@ export class Choreo extends Component<Signature> implements ChoreoHost {
736
707
  return;
737
708
  }
738
709
  this.rootSnapshot = this.element?.getBoundingClientRect();
710
+ this.shownAnimations = this.element
711
+ ? readAnimations(this.element)
712
+ : new Map();
739
713
  const wired = this.args.inView ? this.nearView : undefined;
740
714
  for (const node of this.participants) {
741
715
  const el = node.element;
@@ -1551,10 +1525,13 @@ export class Choreo extends Component<Signature> implements ChoreoHost {
1551
1525
  el.style.height = `${page.height / sy}px`;
1552
1526
  el.style.boxSizing = 'border-box';
1553
1527
  el.style.margin = '0';
1554
- el.style.pointerEvents = 'none';
1555
- // one texture, transformed — not a subtree re-rastered per frame
1556
- el.style.willChange = 'transform, opacity';
1557
- layer.appendChild(el);
1528
+ // a memory of the old scene: inert, held at the frame it showed, and one
1529
+ // texture transformed rather than a subtree re-rastered per frame
1530
+ keepAsMemory(
1531
+ el,
1532
+ { animations: this.shownAnimations, scrolls: this.scrolls ?? new Map() },
1533
+ () => layer.appendChild(el),
1534
+ );
1558
1535
  this.orphans.add(s.node);
1559
1536
  this.orphanBounds.set(s.node, s.initial);
1560
1537
  }
@@ -1615,7 +1592,8 @@ export class Choreo extends Component<Signature> implements ChoreoHost {
1615
1592
  host = modifier((el: HTMLDivElement) => {
1616
1593
  this.element = el;
1617
1594
  setChoreoHost(el, this);
1618
- ensureOrphanStyle();
1595
+ const scrolls = trackScrolls(el);
1596
+ this.scrolls = scrolls.scrolls;
1619
1597
  if (getComputedStyle(el).position === 'static') {
1620
1598
  el.style.position = 'relative';
1621
1599
  }
@@ -1629,6 +1607,7 @@ export class Choreo extends Component<Signature> implements ChoreoHost {
1629
1607
  }
1630
1608
  }
1631
1609
  return () => {
1610
+ scrolls.stop();
1632
1611
  setChoreoHost(el, undefined);
1633
1612
  this.element = undefined;
1634
1613
  };
@@ -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