@cardstack/boxel-cli 0.11.0-unstable.1 → 0.11.0-unstable.3

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 (177) hide show
  1. package/bundled-realms/pretui/components/agent-mark.gts +98 -0
  2. package/bundled-realms/pretui/components/agent-question.gts +340 -37
  3. package/bundled-realms/pretui/components/answer-footer.gts +249 -0
  4. package/bundled-realms/pretui/components/card-embed.gts +523 -0
  5. package/bundled-realms/pretui/components/card-pills.gts +725 -0
  6. package/bundled-realms/pretui/components/code-block.gts +87 -45
  7. package/bundled-realms/pretui/components/collapsed-turn.gts +133 -12
  8. package/bundled-realms/pretui/components/compaction-chip.gts +81 -67
  9. package/bundled-realms/pretui/components/composer-cap.gts +516 -0
  10. package/bundled-realms/pretui/components/context-ring.gts +108 -0
  11. package/bundled-realms/pretui/components/diff-block.gts +1002 -31
  12. package/bundled-realms/pretui/components/diff-stat.gts +102 -0
  13. package/bundled-realms/pretui/components/jump-chip.gts +86 -53
  14. package/bundled-realms/pretui/components/markdown-stream.gts +799 -0
  15. package/bundled-realms/pretui/components/markdown-surface.gts +71 -0
  16. package/bundled-realms/pretui/components/plan-mark.gts +147 -0
  17. package/bundled-realms/pretui/components/prose.gts +185 -8
  18. package/bundled-realms/pretui/components/prose.md +6 -8
  19. package/bundled-realms/pretui/components/quick-look.gts +377 -0
  20. package/bundled-realms/pretui/components/reactions.gts +136 -0
  21. package/bundled-realms/pretui/components/recommendation.gts +67 -36
  22. package/bundled-realms/pretui/components/result-card.gts +58 -22
  23. package/bundled-realms/pretui/components/segmented-control.gts +13 -8
  24. package/bundled-realms/pretui/components/session-prep.gts +12 -11
  25. package/bundled-realms/pretui/components/sliding-highlight.gts +8 -3
  26. package/bundled-realms/pretui/components/speaker-label.gts +132 -0
  27. package/bundled-realms/pretui/components/state-glyph.gts +121 -0
  28. package/bundled-realms/pretui/components/step-list.gts +62 -60
  29. package/bundled-realms/pretui/components/stick-to-bottom.gts +1150 -0
  30. package/bundled-realms/pretui/components/system-line.gts +143 -0
  31. package/bundled-realms/pretui/components/task-checklist.gts +184 -0
  32. package/bundled-realms/pretui/components/thinking.gts +63 -94
  33. package/bundled-realms/pretui/components/tool-change-table.gts +285 -0
  34. package/bundled-realms/pretui/components/tool-gate.gts +462 -0
  35. package/bundled-realms/pretui/components/tool-group.gts +920 -0
  36. package/bundled-realms/pretui/components/tool-icon.ts +64 -0
  37. package/bundled-realms/pretui/components/tool-meta.gts +82 -0
  38. package/bundled-realms/pretui/components/tool-plan.gts +197 -0
  39. package/bundled-realms/pretui/components/tool-row.gts +2087 -0
  40. package/bundled-realms/pretui/components/turn-changes.gts +227 -0
  41. package/bundled-realms/pretui/components/user-bubble.gts +575 -0
  42. package/bundled-realms/pretui/components/work-item.gts +185 -48
  43. package/bundled-realms/pretui/docs/scroll-anchoring-protocol.md +136 -0
  44. package/bundled-realms/pretui/internal/markdown-projection.ts +438 -0
  45. package/bundled-realms/pretui/internal/mount-queue.ts +79 -0
  46. package/bundled-realms/pretui/internal/tool-phrases.ts +168 -0
  47. package/bundled-realms/pretui/package.json +2 -1
  48. package/bundled-test-harness/assets/{ai-module-creation-test-beRNwH-e.js → ai-module-creation-test-B6FQKqIl.js} +1 -1
  49. package/bundled-test-harness/assets/{app-QAuw0l8v.js → app-KpPqdyX3.js} +4 -4
  50. package/bundled-test-harness/assets/{binary-upload-dedupe-test-bI8T0QLl.js → binary-upload-dedupe-test-DN00y1tM.js} +1 -1
  51. package/bundled-test-harness/assets/{can-invoke-test-DVaU_yRT.js → can-invoke-test-Bl-EUFM3.js} +1 -1
  52. package/bundled-test-harness/assets/{card-chooser-paging-test-DnxMnXKR.js → card-chooser-paging-test-BNuZRHX3.js} +1 -1
  53. package/bundled-test-harness/assets/{card-chooser-test-DUSnLr-u.js → card-chooser-test-wKfPOOT4.js} +1 -1
  54. package/bundled-test-harness/assets/{card-def-field-def-relationships-test-GBUr3tFK.js → card-def-field-def-relationships-test-CduQ6zan.js} +1 -1
  55. package/bundled-test-harness/assets/{card-delete-test-CQveO1VC.js → card-delete-test-DKxolj5o.js} +1 -1
  56. package/bundled-test-harness/assets/{check-correctness-test-D2M2p66N.js → check-correctness-test-B-kgHztp.js} +1 -1
  57. package/bundled-test-harness/assets/choreo-BEHW-NOc.js +2 -0
  58. package/bundled-test-harness/assets/{choreo-CJfBb_EY.js → choreo-C2cpCfhA.js} +164 -13
  59. package/bundled-test-harness/assets/{choreo-feature-reel-test-D2OrygZv.js → choreo-feature-reel-test-CPI_6nkT.js} +2 -2
  60. package/bundled-test-harness/assets/{choreo-gallery-w07xqtO-.js → choreo-gallery-BxA9cyzF.js} +3 -3
  61. package/bundled-test-harness/assets/{choreo-gallery-build-order-test-yQg4JER6.js → choreo-gallery-build-order-test-CLVoseAJ.js} +1 -1
  62. package/bundled-test-harness/assets/{choreo-gallery-compositor-test-C2THCXJN.js → choreo-gallery-compositor-test-BpT5Ia6B.js} +1 -1
  63. package/bundled-test-harness/assets/{choreo-gallery-cover-test-D4T_Tk4s.js → choreo-gallery-cover-test-DL_fdSsh.js} +1 -1
  64. package/bundled-test-harness/assets/{choreo-gallery-crossing-stage-test-tHUPJ-Hy.js → choreo-gallery-crossing-stage-test-BcKHhpOK.js} +2 -2
  65. package/bundled-test-harness/assets/{choreo-gallery-demos-alive-test-BOtBOnEZ.js → choreo-gallery-demos-alive-test-KufLhXG8.js} +1 -1
  66. package/bundled-test-harness/assets/{choreo-gallery-drift-test-D_6U1Z2c.js → choreo-gallery-drift-test-Cx58rYIA.js} +1 -1
  67. package/bundled-test-harness/assets/{choreo-gallery-escort-test-DDBU0n4C.js → choreo-gallery-escort-test-Wz-emmID.js} +1 -1
  68. package/bundled-test-harness/assets/{choreo-gallery-films-test-CgW1NSdr.js → choreo-gallery-films-test-BFrVs5WW.js} +2 -2
  69. package/bundled-test-harness/assets/{choreo-gallery-filter-test-DUIsNwdO.js → choreo-gallery-filter-test-DCKQkhwe.js} +1 -1
  70. package/bundled-test-harness/assets/{choreo-gallery-fold-test-Chzlxvdr.js → choreo-gallery-fold-test-UtpDgpG9.js} +1 -1
  71. package/bundled-test-harness/assets/{choreo-gallery-guide-pages-test-DOWnqALT.js → choreo-gallery-guide-pages-test-rXDU9JLM.js} +1 -1
  72. package/bundled-test-harness/assets/{choreo-gallery-guides-test-Dra2J4LJ.js → choreo-gallery-guides-test-DfVFdIuU.js} +3 -3
  73. package/bundled-test-harness/assets/{choreo-gallery-hang-test-zzn1eiB-.js → choreo-gallery-hang-test-Cj7MbkFf.js} +1 -1
  74. package/bundled-test-harness/assets/{choreo-gallery-highlight-test-CW99hncQ.js → choreo-gallery-highlight-test-D6CF3IYZ.js} +2 -2
  75. package/bundled-test-harness/assets/{choreo-gallery-inline-edit-test-CYFm1kUz.js → choreo-gallery-inline-edit-test-LfHWsbqc.js} +1 -1
  76. package/bundled-test-harness/assets/{choreo-gallery-interruption-test-CYIBrKV9.js → choreo-gallery-interruption-test-CvQQAgTp.js} +2 -2
  77. package/bundled-test-harness/assets/{choreo-gallery-lone-stack-test-DL8R3SG7.js → choreo-gallery-lone-stack-test-D7Zci7fm.js} +1 -1
  78. package/bundled-test-harness/assets/{choreo-gallery-navigation-test-Dcv04kDx.js → choreo-gallery-navigation-test-DB1EnBEW.js} +1 -1
  79. package/bundled-test-harness/assets/{choreo-gallery-playhead-transport-test-Cfz7GsXl.js → choreo-gallery-playhead-transport-test-CtBlc6AJ.js} +1 -1
  80. package/bundled-test-harness/assets/{choreo-gallery-poplayout-subtree-test-CwoklBkW.js → choreo-gallery-poplayout-subtree-test-BQbmjsvI.js} +1 -1
  81. package/bundled-test-harness/assets/{choreo-gallery-presence-modes-test-BDCIZzJ0.js → choreo-gallery-presence-modes-test-CIdXVG1K.js} +1 -1
  82. package/bundled-test-harness/assets/{choreo-gallery-presentation-test-DlyiHwFf.js → choreo-gallery-presentation-test-C_rPFp29.js} +1 -1
  83. package/bundled-test-harness/assets/{choreo-gallery-reduced-motion-test-DVz5SKNR.js → choreo-gallery-reduced-motion-test-CH5w8Lw-.js} +2 -2
  84. package/bundled-test-harness/assets/{choreo-gallery-sheet-standalone-test-CpD_-BvS.js → choreo-gallery-sheet-standalone-test-CDgI4mGX.js} +1 -1
  85. package/bundled-test-harness/assets/{choreo-gallery-sheet-test-BjvAKHDL.js → choreo-gallery-sheet-test-5lsFX7Rk.js} +1 -1
  86. package/bundled-test-harness/assets/{choreo-gallery-site-test-oq_7y_PN.js → choreo-gallery-site-test-DbMa2xeC.js} +2 -2
  87. package/bundled-test-harness/assets/{choreo-gallery-stage-Bzr4_8Xd.js → choreo-gallery-stage-b3Sk86hp.js} +2 -2
  88. package/bundled-test-harness/assets/{choreo-gallery-stage-keyboard-test-a83d2euJ.js → choreo-gallery-stage-keyboard-test-ds6fT5Eg.js} +1 -1
  89. package/bundled-test-harness/assets/{choreo-gallery-stages-test-C9Sl1kH6.js → choreo-gallery-stages-test-ahioKCct.js} +2 -2
  90. package/bundled-test-harness/assets/{choreo-gallery-subdivision-test-BVVnkOwk.js → choreo-gallery-subdivision-test-g7anTDhT.js} +1 -1
  91. package/bundled-test-harness/assets/{choreo-gallery-table-plan-test-B0YdYEA0.js → choreo-gallery-table-plan-test-Bozc4uUf.js} +1 -1
  92. package/bundled-test-harness/assets/{choreo-gallery-theater-test-BLA147xw.js → choreo-gallery-theater-test-kE535d-b.js} +3 -3
  93. package/bundled-test-harness/assets/{choreo-gallery-tuning-test-DSXiNzCh.js → choreo-gallery-tuning-test-6rHkQBnZ.js} +1 -1
  94. package/bundled-test-harness/assets/{choreo-gallery-widget-room-test-BI574PdO.js → choreo-gallery-widget-room-test-0Vq-uTo1.js} +2 -2
  95. package/bundled-test-harness/assets/{choreo-gallery-wires-test-B6Mz2WZl.js → choreo-gallery-wires-test-CdtnAWYO.js} +1 -1
  96. package/bundled-test-harness/assets/clip-BLfuHi8g.js +2 -0
  97. package/bundled-test-harness/assets/{clip-C_pXjVvY.js → clip-q1QE5f73.js} +1 -1
  98. package/bundled-test-harness/assets/{codeblocks-test-AdolhdCA.js → codeblocks-test-BDD7qK_V.js} +1 -1
  99. package/bundled-test-harness/assets/{compaction-test-DvyAQq56.js → compaction-test-BweolkhG.js} +1 -1
  100. package/bundled-test-harness/assets/{container-DIF_9lZ9.css → container-B5B6u-xm.css} +29 -22
  101. package/bundled-test-harness/assets/{container-DvvqmNpg.js → container-BnJjIM6J.js} +9 -6
  102. package/bundled-test-harness/assets/{create-listing-modal-test-D95JfQab.js → create-listing-modal-test-FvcAKpIa.js} +1 -1
  103. package/bundled-test-harness/assets/{debug-message-test-BcNtj7Wr.js → debug-message-test-CJ18Evt7.js} +1 -1
  104. package/bundled-test-harness/assets/{externals-6ZWftYIb.js → externals-BWjhmCD4.js} +12 -12
  105. package/bundled-test-harness/assets/{fallback-banner-live-delete-test-5lnN1pv2.js → fallback-banner-live-delete-test-C2H3BrT-.js} +1 -1
  106. package/bundled-test-harness/assets/{fallback-banner-test-DFb_m_Kq.js → fallback-banner-test-C10aYjkj.js} +1 -1
  107. package/bundled-test-harness/assets/{fallback-models-test-p3YIwj1L.js → fallback-models-test-BxoPiaGF.js} +1 -1
  108. package/bundled-test-harness/assets/{file-attachment-test-CmyjNWRR.js → file-attachment-test-DeKyzYaz.js} +1 -1
  109. package/bundled-test-harness/assets/film-B68PjRlr.js +2 -0
  110. package/bundled-test-harness/assets/{film-D7vtJO5q.js → film-CeUpnHN_.js} +6 -6
  111. package/bundled-test-harness/assets/{film-DN0A8JHx.js → film-DBZqyqW4.js} +6 -6
  112. package/bundled-test-harness/assets/{general-test-CX-TFDuX.js → general-test-BewzrpoB.js} +1 -1
  113. package/bundled-test-harness/assets/{glimmer-motion-and-choreo-in-cards-test-BHte2t9G.js → glimmer-motion-and-choreo-in-cards-test-HA-INN3d.js} +14 -14
  114. package/bundled-test-harness/assets/{glimmer-motion-and-choreo-test-BPNVeIio.js → glimmer-motion-and-choreo-test-BZ0ZuuAw.js} +3 -3
  115. package/bundled-test-harness/assets/{index-query-engine-test-Dz7Q4Xmk.js → index-query-engine-test-BtI8Y2LR.js} +1 -1
  116. package/bundled-test-harness/assets/{instance-filter-matcher-test-BoLU3bVg.js → instance-filter-matcher-test-BHXHGHjE.js} +1 -1
  117. package/bundled-test-harness/assets/{main-Dlgp8c7v.js → main-CqAHZ2kK.js} +1 -1
  118. package/bundled-test-harness/assets/{message-tool-test-CfjRSJvt.js → message-tool-test-NMaPJFsd.js} +1 -1
  119. package/bundled-test-harness/assets/{model-cost-tier-test-mZG84Uso.js → model-cost-tier-test-BgbfN2LH.js} +1 -1
  120. package/bundled-test-harness/assets/{operation-lowering-test-B0KpI1xu.js → operation-lowering-test-C4IaOjsA.js} +1 -1
  121. package/bundled-test-harness/assets/{operator-mode-card-chooser-test-KWS8lq16.js → operator-mode-card-chooser-test-Bwe47Rn7.js} +1 -1
  122. package/bundled-test-harness/assets/{operator-mode-links-test-CViFt0Ws.js → operator-mode-links-test-BU4jkC-B.js} +1 -1
  123. package/bundled-test-harness/assets/{operator-mode-test-D0gZM2yw.js → operator-mode-test-Bh0rGM9z.js} +1 -1
  124. package/bundled-test-harness/assets/{operator-mode-ui-test-CEvHJ9DS.js → operator-mode-ui-test-whgUw0ju.js} +1 -1
  125. package/bundled-test-harness/assets/{overlay-menu-items-test-Pw80RXsr.js → overlay-menu-items-test-Cwn1vr8q.js} +1 -1
  126. package/bundled-test-harness/assets/{overlays-BnOM360u.js → overlays-BOzLbVDK.js} +1 -1
  127. package/bundled-test-harness/assets/{overlays-kBVfa4i8.js → overlays-C_PuGDGh.js} +1 -1
  128. package/bundled-test-harness/assets/{past-sessions-test-CW0-Bjav.js → past-sessions-test-Bh24N-Kz.js} +1 -1
  129. package/bundled-test-harness/assets/{permission-checking-realm-test-D5uXQypS.js → permission-checking-realm-test-DKnnAy0f.js} +1 -1
  130. package/bundled-test-harness/assets/{plate-CFvCDv2t.js → plate-BL7UtyKE.js} +1 -1
  131. package/bundled-test-harness/assets/plate-CqG3igCJ.js +2 -0
  132. package/bundled-test-harness/assets/player-BJfsgdSI.js +2 -0
  133. package/bundled-test-harness/assets/{player-DzuYjrTU.js → player-Da_CIsp_.js} +1 -1
  134. package/bundled-test-harness/assets/{realm-config-policy-standing-test-Btatheni.js → realm-config-policy-standing-test-DngdYypq.js} +1 -1
  135. package/bundled-test-harness/assets/{realm-config-routing-rule-edit-test-Dh5zW13o.js → realm-config-routing-rule-edit-test-_rkFU8SN.js} +1 -1
  136. package/bundled-test-harness/assets/{realm-config-settings-test-BJa8gv_C.js → realm-config-settings-test-CR9NH0Zn.js} +1 -1
  137. package/bundled-test-harness/assets/{reasoning-test-2Zp37cQB.js → reasoning-test-0lH10kRl.js} +1 -1
  138. package/bundled-test-harness/assets/{room-message-test-BsrON13e.js → room-message-test-C-PddE4Y.js} +1 -1
  139. package/bundled-test-harness/assets/{run-BMyvn_UL.js → run-D4n4LPcK.js} +96 -6
  140. package/bundled-test-harness/assets/{run-realm-code-BxktQf6R.js → run-realm-code-C8N7DtN-.js} +1 -1
  141. package/bundled-test-harness/assets/{run-realm-code-test-7grpTos-.js → run-realm-code-test-DBdp147q.js} +1 -1
  142. package/bundled-test-harness/assets/{schema-generation-test-CM9ggQji.js → schema-generation-test-CB1Hb7GQ.js} +1 -1
  143. package/bundled-test-harness/assets/{scrolling-test-BER00htB.js → scrolling-test-3PhVin8h.js} +1 -1
  144. package/bundled-test-harness/assets/{searchable-option-test-BBr5BPow.js → searchable-option-test-BAdOiL4U.js} +1 -1
  145. package/bundled-test-harness/assets/{sending-test-BJGobs2E.js → sending-test-KSmu1tSJ.js} +1 -1
  146. package/bundled-test-harness/assets/{skills-test-GPOsHRN6.js → skills-test-D-dlcFpd.js} +1 -1
  147. package/bundled-test-harness/assets/{src-Df148w_b.js → src-B_DEhPze.js} +4 -4
  148. package/bundled-test-harness/assets/{src-Cf2wv-Le.js → src-BoTgwqUA.js} +2 -2
  149. package/bundled-test-harness/assets/{store-test-QvvHimzH.js → store-test-CEW7xQjZ.js} +1 -1
  150. package/bundled-test-harness/assets/{test-support-rNHuieEc.js → test-support-FXDPpIl3.js} +1 -1
  151. package/bundled-test-harness/assets/{test-support-C6N_2X-l.js → test-support-VX6ukQNR.js} +1 -1
  152. package/bundled-test-harness/assets/{tests-CmsQ-Z_3.js → tests-QYqqa6nv.js} +84 -84
  153. package/bundled-test-harness/assets/{text-input-validator-test-CYYH2N0L.js → text-input-validator-test-D8iNOWep.js} +1 -1
  154. package/bundled-test-harness/assets/{titles-B1pTaIfP.js → titles-DFJj5O0E.js} +1 -1
  155. package/bundled-test-harness/assets/titles-xGlgu7ys.js +2 -0
  156. package/bundled-test-harness/assets/{to-device-streaming-test-Ctqa0bVp.js → to-device-streaming-test-trCX-505.js} +1 -1
  157. package/bundled-test-harness/assets/{token-usage-test-BQVbK3X0.js → token-usage-test-C6sULooH.js} +1 -1
  158. package/bundled-test-harness/assets/{tools-DU8Rc15s.js → tools-UJfD-_al.js} +1 -1
  159. package/bundled-test-harness/assets/{tools-test-B13yvEKE.js → tools-test-Brav31AV.js} +1 -1
  160. package/bundled-test-harness/assets/{worker-DHBEBihX.js → worker-9ph9_Lpv.js} +31 -26
  161. package/bundled-test-harness/index.html +8 -8
  162. package/bundled-test-harness/tests/index.html +8 -8
  163. package/bundled-types/choreo/beacons.ts +16 -4
  164. package/bundled-types/choreo/changeset.ts +25 -5
  165. package/bundled-types/choreo/choreo.gts +245 -3
  166. package/bundled-types/choreo/far.ts +39 -0
  167. package/bundled-types/choreo/index.ts +2 -0
  168. package/bundled-types/choreo/run.ts +104 -1
  169. package/bundled-types/host-app/lib/realm-runner/worker.ts +8 -51
  170. package/bundled-types/runtime-common/realm-runner-script.d.ts +3 -0
  171. package/package.json +1 -1
  172. package/bundled-test-harness/assets/choreo-B3Z9QhDH.js +0 -2
  173. package/bundled-test-harness/assets/clip-BjcXuTwN.js +0 -2
  174. package/bundled-test-harness/assets/film-DMDPP7ma.js +0 -2
  175. package/bundled-test-harness/assets/plate-66qnQ-M9.js +0 -2
  176. package/bundled-test-harness/assets/player-pygg1PwA.js +0 -2
  177. package/bundled-test-harness/assets/titles-Ccpcl-jq.js +0 -2
@@ -0,0 +1,377 @@
1
+ // Pretui — QuickLook: a sheet that shows a card or file over the transcript, with a way to open it properly.
2
+ import Component from '@glimmer/component';
3
+ import { on } from '@ember/modifier';
4
+ import { guidFor } from '@ember/object/internals';
5
+ import { modifier } from 'ember-modifier';
6
+ import { motion } from 'glimmer-motion';
7
+ import XIcon from '@cardstack/boxel-icons/x';
8
+ import { IconButton } from './icon-button';
9
+
10
+ // ── QuickLook ────────────────────────────────────────────────────────────
11
+ // A look before a commitment: the reader clicks a card or a file in the
12
+ // transcript and sees it here, over the transcript, without leaving the
13
+ // conversation. The bar says what it is (its kind, quietly, and its title)
14
+ // and offers the way to open it properly; Escape, Space (when the card has
15
+ // nothing to scroll), the close button and a click on the backdrop all put
16
+ // it away.
17
+ //
18
+ // It is modal: Tab cycles inside the sheet, and while it is open everything
19
+ // beside it in its parent is `inert`, so render it as a direct child of the
20
+ // region it covers (the transcript and the composer are then out of reach).
21
+ // Keys are handled on the sheet as they bubble, so a control inside the card
22
+ // (a dropdown in an edit-format card) takes its own Escape first.
23
+ //
24
+ // Presentational only. Whether it is open is the caller's state: render it
25
+ // to open it, stop rendering it to close it. It never waits on an animation
26
+ // and never keeps a "closing" state of its own (ADR 007 §1.1).
27
+ //
28
+ // Motion is declared, not performed: the sheet and the backdrop carry
29
+ // `{{motion id= role=}}` so a Choreo region around them can fly the sheet
30
+ // out of whatever opened it and fade the backdrop. Outside a region the
31
+ // declaration does nothing and the sheet simply appears.
32
+
33
+ export interface QuickLookSignature {
34
+ Args: {
35
+ /** the card's or the file's title: the sheet's accessible name */
36
+ title: string;
37
+ /** what it is, said quietly before the title: 'Tea lot', 'PNG image' */
38
+ kind?: string;
39
+ /** put it away: Escape, Space, the close button, the backdrop */
40
+ onClose: () => void;
41
+ /** the close button's name (default 'Close') */
42
+ closeLabel?: string;
43
+ /** where focus goes when it closes. Default: whatever had focus when
44
+ * it opened, the pill or embed that opened it */
45
+ returnFocusTo?: HTMLElement | null;
46
+ };
47
+ Blocks: {
48
+ /** the bar's actions: Open (the one primary action), Open in code */
49
+ actions: [];
50
+ /** the card or the file; it scrolls in its own box */
51
+ default: [];
52
+ };
53
+ Element: HTMLDivElement;
54
+ }
55
+
56
+ // Space closes only when it would not do something else: typing a space,
57
+ // pressing a focused button or link, or toggling a control inside the card.
58
+ const SPACE_KEEPERS =
59
+ 'input, textarea, select, button, a[href], summary, label, [contenteditable=""], [contenteditable="true"], [role="button"], [role="checkbox"], [role="radio"], [role="switch"], [role="textbox"], [role="combobox"], [role="slider"], [role="menuitem"], [role="option"], [role="tab"], [role="treeitem"], [role="gridcell"]';
60
+
61
+ // What Tab can land on, before the tabindex and visibility checks.
62
+ const TABBABLE =
63
+ 'a[href], area[href], button, input, select, textarea, summary, iframe, audio[controls], video[controls], [contenteditable=""], [contenteditable="true"], [tabindex]';
64
+
65
+ function tabbablesIn(root: HTMLElement): HTMLElement[] {
66
+ return [...root.querySelectorAll<HTMLElement>(TABBABLE)].filter(
67
+ (el) =>
68
+ el.tabIndex >= 0 &&
69
+ !(el as HTMLButtonElement).disabled &&
70
+ !(el instanceof HTMLInputElement && el.type === 'hidden') &&
71
+ !el.closest('[inert]') &&
72
+ el.getClientRects().length > 0,
73
+ );
74
+ }
75
+
76
+ export class QuickLook extends Component<QuickLookSignature> {
77
+ private guid = guidFor(this);
78
+
79
+ get titleId(): string {
80
+ return this.guid + '-title';
81
+ }
82
+ get closeLabel(): string {
83
+ return this.args.closeLabel ?? 'Close';
84
+ }
85
+
86
+ close = () => {
87
+ this.args.onClose();
88
+ };
89
+
90
+ private sheet: HTMLElement | undefined;
91
+ private body: HTMLElement | undefined;
92
+
93
+ /** Tab wraps between the sheet's first and last tabbable elements. */
94
+ private trapTab(ev: KeyboardEvent) {
95
+ let sheet = this.sheet;
96
+ if (!sheet) {
97
+ return;
98
+ }
99
+ let stops = tabbablesIn(sheet);
100
+ let first = stops[0];
101
+ let last = stops[stops.length - 1];
102
+ let active = document.activeElement;
103
+ if (!first || !last) {
104
+ ev.preventDefault();
105
+ sheet.focus({ preventScroll: true });
106
+ return;
107
+ }
108
+ if (ev.shiftKey && (active === first || active === sheet)) {
109
+ ev.preventDefault();
110
+ last.focus();
111
+ } else if (!ev.shiftKey && active === last) {
112
+ ev.preventDefault();
113
+ first.focus();
114
+ }
115
+ }
116
+
117
+ /** the card is taller than its box: Space pages it rather than closing */
118
+ private get bodyScrolls(): boolean {
119
+ let body = this.body;
120
+ return Boolean(body && body.scrollHeight > body.clientHeight + 1);
121
+ }
122
+
123
+ /** Tab stays in the sheet. Escape closes, unless something inside the
124
+ * card already used it; Space closes when focus is not somewhere a space
125
+ * means something else and the card has nothing to scroll. Every key the
126
+ * sheet uses stops here, so a host's document-level handler does not
127
+ * also run. */
128
+ onKey = (event: Event) => {
129
+ let ev = event as KeyboardEvent;
130
+ if (ev.isComposing) {
131
+ return;
132
+ }
133
+ if (ev.key === 'Tab') {
134
+ this.trapTab(ev);
135
+ return;
136
+ }
137
+ if (ev.defaultPrevented) {
138
+ return;
139
+ }
140
+ if (ev.key === 'Escape') {
141
+ ev.preventDefault();
142
+ ev.stopPropagation();
143
+ this.close();
144
+ return;
145
+ }
146
+ if (ev.key === ' ' || ev.key === 'Spacebar') {
147
+ let target = ev.target as Element | null;
148
+ if (target instanceof Element && target.closest(SPACE_KEEPERS)) {
149
+ return;
150
+ }
151
+ if (this.bodyScrolls) {
152
+ // focus inside the card: the browser pages it. Focus on the sheet
153
+ // itself: the card's box is not its scroller, so page it here
154
+ if (target === this.sheet && this.body) {
155
+ ev.preventDefault();
156
+ ev.stopPropagation();
157
+ let page = this.body.clientHeight * 0.875;
158
+ this.body.scrollBy({ top: ev.shiftKey ? -page : page });
159
+ }
160
+ return;
161
+ }
162
+ ev.preventDefault();
163
+ ev.stopPropagation();
164
+ this.close();
165
+ }
166
+ };
167
+
168
+ private registerBody = modifier((body: HTMLElement) => {
169
+ this.body = body;
170
+ return () => (this.body = undefined);
171
+ });
172
+
173
+ /** Everything beside the sheet in its parent is inert while it is open;
174
+ * what was already inert is left as it was. */
175
+ private inertBehind = modifier((root: HTMLElement) => {
176
+ let made: HTMLElement[] = [];
177
+ for (let sibling of root.parentElement?.children ?? []) {
178
+ if (
179
+ sibling !== root &&
180
+ sibling instanceof HTMLElement &&
181
+ !sibling.inert
182
+ ) {
183
+ sibling.inert = true;
184
+ made.push(sibling);
185
+ }
186
+ }
187
+ return () => {
188
+ for (let el of made) {
189
+ el.inert = false;
190
+ }
191
+ };
192
+ });
193
+
194
+ /** Focus moves into the sheet when it is rendered, and back to what
195
+ * opened it when it is torn down: on the state change, never at the end
196
+ * of an animation (ADR 007 §1.5). */
197
+ private focusIn = modifier(
198
+ (sheet: HTMLElement, [returnTo]: [HTMLElement | null | undefined]) => {
199
+ this.sheet = sheet;
200
+ let previous =
201
+ returnTo ??
202
+ (document.activeElement instanceof HTMLElement
203
+ ? document.activeElement
204
+ : null);
205
+ sheet.focus({ preventScroll: true });
206
+ return () => {
207
+ this.sheet = undefined;
208
+ let active = document.activeElement;
209
+ let focusWasHere =
210
+ !active || active === document.body || sheet.contains(active);
211
+ if (focusWasHere && previous?.isConnected) {
212
+ previous.focus({ preventScroll: true });
213
+ }
214
+ };
215
+ },
216
+ );
217
+
218
+ <template>
219
+ <div class='pretui-quick-look' {{this.inertBehind}} data-test-pretui-quick-look>
220
+ {{! the backdrop is a pointer target only: the close button is the
221
+ keyboard's way out, so it stays out of the tab order and the tree }}
222
+ {{! template-lint-disable no-invalid-interactive }}
223
+ <div
224
+ class='pretui-quick-look-backdrop'
225
+ aria-hidden='true'
226
+ {{motion id='quick-look-backdrop' role='quick-look-backdrop'}}
227
+ {{on 'click' this.close}}
228
+ data-test-pretui-quick-look-backdrop
229
+ ></div>
230
+ <div
231
+ class='pretui-quick-look-sheet'
232
+ role='dialog'
233
+ aria-modal='true'
234
+ aria-labelledby={{this.titleId}}
235
+ tabindex='-1'
236
+ {{on 'keydown' this.onKey}}
237
+ {{motion id='quick-look' role='quick-look'}}
238
+ {{this.focusIn @returnFocusTo}}
239
+ data-test-pretui-quick-look-sheet
240
+ ...attributes
241
+ >
242
+ <div class='pretui-quick-look-bar'>
243
+ <div class='pretui-quick-look-heading'>
244
+ {{#if @kind}}
245
+ <span
246
+ class='pretui-quick-look-kind'
247
+ data-test-pretui-quick-look-kind
248
+ >{{@kind}}</span>
249
+ {{/if}}
250
+ <h2
251
+ id={{this.titleId}}
252
+ class='pretui-quick-look-title'
253
+ title={{@title}}
254
+ data-test-pretui-quick-look-title
255
+ >{{@title}}</h2>
256
+ </div>
257
+ <div
258
+ class='pretui-quick-look-actions'
259
+ data-test-pretui-quick-look-actions
260
+ >
261
+ {{yield to='actions'}}
262
+ </div>
263
+ <IconButton
264
+ class='pretui-quick-look-close'
265
+ @label={{this.closeLabel}}
266
+ @icon={{XIcon}}
267
+ @tone='neutral'
268
+ @appearance='plain'
269
+ @size='s'
270
+ {{on 'click' this.close}}
271
+ data-test-pretui-quick-look-close
272
+ />
273
+ </div>
274
+ <div
275
+ class='pretui-quick-look-body'
276
+ {{this.registerBody}}
277
+ data-test-pretui-quick-look-body
278
+ >
279
+ {{yield}}
280
+ </div>
281
+ </div>
282
+ </div>
283
+ <style scoped>
284
+ @layer PretComponent {
285
+ .pretui-quick-look {
286
+ position: absolute;
287
+ inset: 0;
288
+ z-index: var(--pretui-quick-look-z, 20);
289
+ }
290
+ .pretui-quick-look-backdrop {
291
+ position: absolute;
292
+ inset: 0;
293
+ background-color: var(
294
+ --pretui-scrim,
295
+ color-mix(in oklch, var(--background) 55%, transparent)
296
+ );
297
+ }
298
+ .pretui-quick-look-sheet {
299
+ position: absolute;
300
+ inset: var(
301
+ --pretui-quick-look-inset,
302
+ var(--boxel-sp-3xl) var(--boxel-sp-sm) var(--boxel-sp-sm)
303
+ );
304
+ display: flex;
305
+ flex-direction: column;
306
+ min-height: 0;
307
+ overflow: hidden;
308
+ border-radius: var(--boxel-border-radius-lg);
309
+ background-color: var(--card);
310
+ color: var(--card-foreground);
311
+ box-shadow:
312
+ var(--pretui-shadow-hairline, 0 0 0 1px var(--border)),
313
+ var(--pretui-shadow-overlay, var(--shadow-lg));
314
+ }
315
+ .pretui-quick-look-sheet:focus {
316
+ outline: none;
317
+ }
318
+ .pretui-quick-look-sheet:focus-visible {
319
+ box-shadow:
320
+ 0 0 0 2px var(--ring),
321
+ var(--pretui-shadow-overlay, var(--shadow-lg));
322
+ }
323
+ .pretui-quick-look-bar {
324
+ display: flex;
325
+ align-items: center;
326
+ gap: var(--boxel-sp-xs);
327
+ min-height: var(--boxel-sp-3xl);
328
+ padding: var(--boxel-sp-xs) var(--boxel-sp-xs) var(--boxel-sp-xs)
329
+ var(--boxel-sp);
330
+ border-bottom: 1px solid var(--border);
331
+ }
332
+ .pretui-quick-look-heading {
333
+ display: flex;
334
+ flex-direction: column;
335
+ flex: 1;
336
+ min-width: 0;
337
+ }
338
+ .pretui-quick-look-kind {
339
+ font-size: var(--boxel-caption-font-size);
340
+ line-height: var(--boxel-caption-line-height);
341
+ color: var(--subtle-foreground);
342
+ overflow: hidden;
343
+ white-space: nowrap;
344
+ text-overflow: ellipsis;
345
+ }
346
+ .pretui-quick-look-title {
347
+ margin: 0;
348
+ font-size: var(--boxel-ui-label-font-size);
349
+ line-height: var(--boxel-ui-label-line-height);
350
+ font-weight: 600;
351
+ overflow: hidden;
352
+ white-space: nowrap;
353
+ text-overflow: ellipsis;
354
+ }
355
+ .pretui-quick-look-actions {
356
+ display: flex;
357
+ align-items: center;
358
+ gap: var(--boxel-sp-xs);
359
+ flex-shrink: 0;
360
+ }
361
+ .pretui-quick-look-actions:empty {
362
+ display: none;
363
+ }
364
+ .pretui-quick-look-close {
365
+ flex-shrink: 0;
366
+ color: var(--muted-foreground);
367
+ }
368
+ .pretui-quick-look-body {
369
+ flex: 1;
370
+ min-height: 0;
371
+ overflow: auto;
372
+ overscroll-behavior: contain;
373
+ }
374
+ }
375
+ </style>
376
+ </template>
377
+ }
@@ -0,0 +1,136 @@
1
+ // Pretui — Reactions: who reacted to a message or a call, as small pills under it.
2
+ import Component from '@glimmer/component';
3
+ import { on } from '@ember/modifier';
4
+ import { fn } from '@ember/helper';
5
+
6
+ export interface Reaction {
7
+ /** the emoji: '👍', '👎', '🎉' */
8
+ key: string;
9
+ /** who reacted, in order, as the room names them */
10
+ by: string[];
11
+ /** the reader is among them: the pill is pressed */
12
+ mine?: boolean;
13
+ }
14
+
15
+ export interface ReactionsSignature {
16
+ Args: {
17
+ reactions: Reaction[];
18
+ /** toggle the reader's own reaction; omit and the pills only show */
19
+ onToggle?: (key: string) => void;
20
+ /** what the reactions are on, for the group's name: 'the read' */
21
+ subject?: string;
22
+ };
23
+ Element: HTMLDivElement;
24
+ }
25
+
26
+ /**
27
+ * "👍 Chris" under the thing it answers, the way a chat shows a reaction:
28
+ * the content stays as it was, and the pill says who. Two names show, then
29
+ * "+N"; every name is in the pill's title and its accessible name. With
30
+ * `@onToggle` a pill is a pressed toggle for the reader's own reaction.
31
+ */
32
+ export class Reactions extends Component<ReactionsSignature> {
33
+ names = (reaction: Reaction): string => {
34
+ let [a, b, ...rest] = reaction.by;
35
+ let shown = [a, b].filter(Boolean).join(', ');
36
+ return rest.length ? `${shown} +${rest.length}` : shown;
37
+ };
38
+ label = (reaction: Reaction): string =>
39
+ `${reaction.key} by ${reaction.by.join(', ')}`;
40
+ toggle = (key: string) => this.args.onToggle?.(key);
41
+ get groupLabel(): string {
42
+ return this.args.subject
43
+ ? `Reactions to ${this.args.subject}`
44
+ : 'Reactions';
45
+ }
46
+ <template>
47
+ <div
48
+ class='pretui-reactions'
49
+ role='group'
50
+ aria-label={{this.groupLabel}}
51
+ data-test-pretui-reactions
52
+ ...attributes
53
+ >
54
+ {{#each @reactions as |reaction|}}
55
+ {{#if @onToggle}}
56
+ <button
57
+ type='button'
58
+ class='pretui-reaction'
59
+ aria-pressed={{if reaction.mine 'true' 'false'}}
60
+ aria-label={{this.label reaction}}
61
+ title={{this.label reaction}}
62
+ {{on 'click' (fn this.toggle reaction.key)}}
63
+ data-test-pretui-reaction={{reaction.key}}
64
+ ><span
65
+ class='pretui-reaction-key'
66
+ aria-hidden='true'
67
+ >{{reaction.key}}</span><span
68
+ class='pretui-reaction-by'
69
+ aria-hidden='true'
70
+ >{{this.names reaction}}</span></button>
71
+ {{else}}
72
+ <span
73
+ class='pretui-reaction'
74
+ data-mine={{if reaction.mine 'true'}}
75
+ role='img'
76
+ aria-label={{this.label reaction}}
77
+ title={{this.label reaction}}
78
+ data-test-pretui-reaction={{reaction.key}}
79
+ ><span
80
+ class='pretui-reaction-key'
81
+ aria-hidden='true'
82
+ >{{reaction.key}}</span><span
83
+ class='pretui-reaction-by'
84
+ aria-hidden='true'
85
+ >{{this.names reaction}}</span></span>
86
+ {{/if}}
87
+ {{/each}}
88
+ </div>
89
+ <style scoped>
90
+ @layer PretComponent {
91
+ .pretui-reactions {
92
+ display: flex;
93
+ flex-wrap: wrap;
94
+ gap: var(--boxel-sp-4xs);
95
+ }
96
+ .pretui-reaction {
97
+ display: inline-flex;
98
+ align-items: center;
99
+ gap: var(--boxel-sp-4xs);
100
+ /* the least a pointer target takes */
101
+ height: 1.5rem;
102
+ padding: 0 var(--boxel-sp-xs) 0 var(--boxel-sp-2xs);
103
+ border: 0;
104
+ border-radius: var(--boxel-border-radius-pill);
105
+ background-color: var(--muted);
106
+ box-shadow: inset 0 0 0 1px var(--border);
107
+ font-family: inherit;
108
+ font-size: var(--boxel-font-size-xs);
109
+ font-weight: 500;
110
+ line-height: 1;
111
+ color: var(--muted-foreground);
112
+ }
113
+ .pretui-reaction-key {
114
+ font-size: var(--boxel-font-size-xs);
115
+ }
116
+ .pretui-reaction[data-mine],
117
+ .pretui-reaction[aria-pressed='true'] {
118
+ background-color: color-mix(in oklch, var(--primary) 14%, var(--card));
119
+ box-shadow: inset 0 0 0 1px
120
+ color-mix(in oklch, var(--primary) 45%, transparent);
121
+ color: var(--foreground);
122
+ }
123
+ button.pretui-reaction {
124
+ cursor: pointer;
125
+ }
126
+ button.pretui-reaction:hover {
127
+ color: var(--foreground);
128
+ }
129
+ button.pretui-reaction:focus-visible {
130
+ outline: 2px solid var(--ring);
131
+ outline-offset: 1px;
132
+ }
133
+ }
134
+ </style>
135
+ </template>
136
+ }