@arbidocs/blocks 0.5.121 → 0.5.123

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 (208) hide show
  1. package/dist/{ClippingMediaViewer-B6WG7Q64.cjs → ClippingMediaViewer-IEIBT74X.cjs} +8 -8
  2. package/dist/{ClippingMediaViewer-B6WG7Q64.cjs.map → ClippingMediaViewer-IEIBT74X.cjs.map} +1 -1
  3. package/dist/{ClippingPdfViewer-6GCVTV3V.cjs → ClippingPdfViewer-OGYLMVCE.cjs} +5 -5
  4. package/dist/{ClippingPdfViewer-6GCVTV3V.cjs.map → ClippingPdfViewer-OGYLMVCE.cjs.map} +1 -1
  5. package/dist/{ConnectedCallDock-DC5V5OUJ.cjs → ConnectedCallDock-XJOYVVAH.cjs} +21 -21
  6. package/dist/{ConnectedCallDock-DC5V5OUJ.cjs.map → ConnectedCallDock-XJOYVVAH.cjs.map} +1 -1
  7. package/dist/{ConnectedCallRoom-FQLXWTFB.cjs → ConnectedCallRoom-CUDYAPQK.cjs} +14 -14
  8. package/dist/{ConnectedCallRoom-FQLXWTFB.cjs.map → ConnectedCallRoom-CUDYAPQK.cjs.map} +1 -1
  9. package/dist/{ConnectedNativeDocxComparison-4Q5S7VLD.cjs → ConnectedNativeDocxComparison-QKHCYD6W.cjs} +15 -15
  10. package/dist/{ConnectedNativeDocxComparison-4Q5S7VLD.cjs.map → ConnectedNativeDocxComparison-QKHCYD6W.cjs.map} +1 -1
  11. package/dist/ConnectedPresentation-5DOJ35XL.cjs +321 -0
  12. package/dist/ConnectedPresentation-5DOJ35XL.cjs.map +1 -0
  13. package/dist/{ConnectedProductionSetDialog-4EAY6KAZ.cjs → ConnectedProductionSetDialog-LHB4VQWC.cjs} +3 -3
  14. package/dist/{ConnectedProductionSetDialog-4EAY6KAZ.cjs.map → ConnectedProductionSetDialog-LHB4VQWC.cjs.map} +1 -1
  15. package/dist/{ConnectedSlidesTab-7QDE2CZD.cjs → ConnectedSlidesTab-7YMEDGUU.cjs} +73 -49
  16. package/dist/ConnectedSlidesTab-7YMEDGUU.cjs.map +1 -0
  17. package/dist/{ConnectedStreamedWordArtifact-FFOQJBOI.cjs → ConnectedStreamedWordArtifact-AG62232A.cjs} +15 -15
  18. package/dist/{ConnectedStreamedWordArtifact-FFOQJBOI.cjs.map → ConnectedStreamedWordArtifact-AG62232A.cjs.map} +1 -1
  19. package/dist/{DocumentReviewCard-SNWVEZ7S.cjs → DocumentReviewCard-M53XFLI2.cjs} +15 -15
  20. package/dist/{DocumentReviewCard-SNWVEZ7S.cjs.map → DocumentReviewCard-M53XFLI2.cjs.map} +1 -1
  21. package/dist/{FilesViewerDialog-IZI63FOV.cjs → FilesViewerDialog-2QCCRWF2.cjs} +50 -49
  22. package/dist/FilesViewerDialog-2QCCRWF2.cjs.map +1 -0
  23. package/dist/{WorkspaceTableView-TEIRB6VJ.cjs → WorkspaceTableView-3XYAV4BV.cjs} +8 -8
  24. package/dist/{WorkspaceTableView-TEIRB6VJ.cjs.map → WorkspaceTableView-3XYAV4BV.cjs.map} +1 -1
  25. package/dist/agents/index.cjs +296 -296
  26. package/dist/{ai-KBYNRNZ6.cjs → ai-IEIR7SC2.cjs} +25 -25
  27. package/dist/ai-IEIR7SC2.cjs.map +1 -0
  28. package/dist/calls/index.cjs +121 -121
  29. package/dist/chat/index.cjs +1 -1
  30. package/dist/{chunk-NLOETJCB.cjs → chunk-2F57IO7S.cjs} +12 -12
  31. package/dist/{chunk-NLOETJCB.cjs.map → chunk-2F57IO7S.cjs.map} +1 -1
  32. package/dist/chunk-2RLA6QO2.cjs +174 -0
  33. package/dist/chunk-2RLA6QO2.cjs.map +1 -0
  34. package/dist/{chunk-Q6DQGGP4.cjs → chunk-325GVZE6.cjs} +3 -3
  35. package/dist/{chunk-Q6DQGGP4.cjs.map → chunk-325GVZE6.cjs.map} +1 -1
  36. package/dist/{chunk-LHWOKOMP.cjs → chunk-46P7IDBX.cjs} +3 -3
  37. package/dist/chunk-46P7IDBX.cjs.map +1 -0
  38. package/dist/{chunk-XTR26TNE.cjs → chunk-4TTX6ZNS.cjs} +20 -4
  39. package/dist/chunk-4TTX6ZNS.cjs.map +1 -0
  40. package/dist/{chunk-BVAT4EVC.cjs → chunk-5QERK4PK.cjs} +35 -35
  41. package/dist/{chunk-BVAT4EVC.cjs.map → chunk-5QERK4PK.cjs.map} +1 -1
  42. package/dist/{chunk-GX2EBMJW.cjs → chunk-6OK7ESTL.cjs} +7 -6
  43. package/dist/chunk-6OK7ESTL.cjs.map +1 -0
  44. package/dist/{chunk-C2OICDMI.cjs → chunk-7557LDAN.cjs} +10 -10
  45. package/dist/{chunk-C2OICDMI.cjs.map → chunk-7557LDAN.cjs.map} +1 -1
  46. package/dist/{chunk-474GPLCM.cjs → chunk-7L4X6QN2.cjs} +6 -6
  47. package/dist/{chunk-474GPLCM.cjs.map → chunk-7L4X6QN2.cjs.map} +1 -1
  48. package/dist/{chunk-52KGMAO5.cjs → chunk-7NLO7ASW.cjs} +15 -15
  49. package/dist/{chunk-52KGMAO5.cjs.map → chunk-7NLO7ASW.cjs.map} +1 -1
  50. package/dist/{chunk-H5OLHP2U.cjs → chunk-7OPOPYV6.cjs} +3 -3
  51. package/dist/{chunk-H5OLHP2U.cjs.map → chunk-7OPOPYV6.cjs.map} +1 -1
  52. package/dist/{chunk-W6B3RYTX.cjs → chunk-7PN3YENO.cjs} +4 -4
  53. package/dist/{chunk-W6B3RYTX.cjs.map → chunk-7PN3YENO.cjs.map} +1 -1
  54. package/dist/{chunk-734NPQNR.cjs → chunk-ADDQKZ5I.cjs} +4 -4
  55. package/dist/{chunk-734NPQNR.cjs.map → chunk-ADDQKZ5I.cjs.map} +1 -1
  56. package/dist/{chunk-JQLWKNJO.cjs → chunk-ALK3PUUB.cjs} +4 -4
  57. package/dist/{chunk-JQLWKNJO.cjs.map → chunk-ALK3PUUB.cjs.map} +1 -1
  58. package/dist/{chunk-4VZZWYEC.cjs → chunk-BQ5RIWWT.cjs} +3 -3
  59. package/dist/{chunk-4VZZWYEC.cjs.map → chunk-BQ5RIWWT.cjs.map} +1 -1
  60. package/dist/{chunk-Q3XJUXID.cjs → chunk-DWMWEALB.cjs} +499 -409
  61. package/dist/chunk-DWMWEALB.cjs.map +1 -0
  62. package/dist/{chunk-3SCNXNHI.cjs → chunk-E3L5DB5F.cjs} +5 -5
  63. package/dist/{chunk-3SCNXNHI.cjs.map → chunk-E3L5DB5F.cjs.map} +1 -1
  64. package/dist/{chunk-VJ5ZLRBD.cjs → chunk-GFUOCGY6.cjs} +8 -3
  65. package/dist/chunk-GFUOCGY6.cjs.map +1 -0
  66. package/dist/{chunk-7VUJGHMY.cjs → chunk-JD2HX2BR.cjs} +272 -156
  67. package/dist/chunk-JD2HX2BR.cjs.map +1 -0
  68. package/dist/{chunk-RYVZDFVO.cjs → chunk-JJZOL5MG.cjs} +3 -3
  69. package/dist/{chunk-RYVZDFVO.cjs.map → chunk-JJZOL5MG.cjs.map} +1 -1
  70. package/dist/{chunk-QFXILJMJ.cjs → chunk-K4763GD5.cjs} +17 -174
  71. package/dist/chunk-K4763GD5.cjs.map +1 -0
  72. package/dist/{chunk-EHP6OH5D.cjs → chunk-KA3F6TIH.cjs} +11 -11
  73. package/dist/{chunk-EHP6OH5D.cjs.map → chunk-KA3F6TIH.cjs.map} +1 -1
  74. package/dist/{chunk-UUENH4BJ.cjs → chunk-KXIRVKCJ.cjs} +3 -3
  75. package/dist/{chunk-UUENH4BJ.cjs.map → chunk-KXIRVKCJ.cjs.map} +1 -1
  76. package/dist/{chunk-AY6EZSZB.cjs → chunk-L4CIFLCU.cjs} +4 -4
  77. package/dist/{chunk-AY6EZSZB.cjs.map → chunk-L4CIFLCU.cjs.map} +1 -1
  78. package/dist/{chunk-4RBQ44RY.cjs → chunk-MLA2BJ2Y.cjs} +28 -28
  79. package/dist/{chunk-4RBQ44RY.cjs.map → chunk-MLA2BJ2Y.cjs.map} +1 -1
  80. package/dist/{chunk-DR2AH7JL.cjs → chunk-NG2EE7HR.cjs} +48 -48
  81. package/dist/{chunk-DR2AH7JL.cjs.map → chunk-NG2EE7HR.cjs.map} +1 -1
  82. package/dist/{chunk-ENGL7P3I.cjs → chunk-NQML7IUC.cjs} +26 -26
  83. package/dist/{chunk-ENGL7P3I.cjs.map → chunk-NQML7IUC.cjs.map} +1 -1
  84. package/dist/{chunk-4NPCDS2X.cjs → chunk-NY3YUCOW.cjs} +3 -3
  85. package/dist/{chunk-4NPCDS2X.cjs.map → chunk-NY3YUCOW.cjs.map} +1 -1
  86. package/dist/{chunk-JZEYHDWJ.cjs → chunk-OHHKBURG.cjs} +76 -76
  87. package/dist/chunk-OHHKBURG.cjs.map +1 -0
  88. package/dist/{chunk-5534FYXP.cjs → chunk-SDRYSLOX.cjs} +4 -4
  89. package/dist/{chunk-5534FYXP.cjs.map → chunk-SDRYSLOX.cjs.map} +1 -1
  90. package/dist/{chunk-2W5E62EY.cjs → chunk-SGYSDABK.cjs} +6 -6
  91. package/dist/{chunk-2W5E62EY.cjs.map → chunk-SGYSDABK.cjs.map} +1 -1
  92. package/dist/{chunk-KP5AITQ4.cjs → chunk-SNPK3R3O.cjs} +7 -7
  93. package/dist/{chunk-KP5AITQ4.cjs.map → chunk-SNPK3R3O.cjs.map} +1 -1
  94. package/dist/{chunk-EATIFPKP.cjs → chunk-T73BHNQX.cjs} +4 -4
  95. package/dist/{chunk-EATIFPKP.cjs.map → chunk-T73BHNQX.cjs.map} +1 -1
  96. package/dist/{chunk-4IOYQLKG.cjs → chunk-TP5PGWIO.cjs} +3 -3
  97. package/dist/{chunk-4IOYQLKG.cjs.map → chunk-TP5PGWIO.cjs.map} +1 -1
  98. package/dist/{chunk-GKVE5JGB.cjs → chunk-UFT22LAB.cjs} +3 -3
  99. package/dist/{chunk-GKVE5JGB.cjs.map → chunk-UFT22LAB.cjs.map} +1 -1
  100. package/dist/{chunk-FKFQCK3C.cjs → chunk-V6MW7B4B.cjs} +4 -4
  101. package/dist/{chunk-FKFQCK3C.cjs.map → chunk-V6MW7B4B.cjs.map} +1 -1
  102. package/dist/{chunk-2PZT3J34.cjs → chunk-VWBYE3XY.cjs} +6 -6
  103. package/dist/{chunk-2PZT3J34.cjs.map → chunk-VWBYE3XY.cjs.map} +1 -1
  104. package/dist/{chunk-GQXPNIRE.cjs → chunk-WAV762GP.cjs} +6 -6
  105. package/dist/{chunk-GQXPNIRE.cjs.map → chunk-WAV762GP.cjs.map} +1 -1
  106. package/dist/{chunk-5LTIZ44D.cjs → chunk-WFMWKPGY.cjs} +14 -7
  107. package/dist/chunk-WFMWKPGY.cjs.map +1 -0
  108. package/dist/{chunk-GPX5VC57.cjs → chunk-WV7BCGCK.cjs} +50 -50
  109. package/dist/{chunk-GPX5VC57.cjs.map → chunk-WV7BCGCK.cjs.map} +1 -1
  110. package/dist/{chunk-MZ2KZXNU.cjs → chunk-WXUU3COI.cjs} +3 -3
  111. package/dist/{chunk-MZ2KZXNU.cjs.map → chunk-WXUU3COI.cjs.map} +1 -1
  112. package/dist/{chunk-NAJN32MF.cjs → chunk-XF355M7O.cjs} +13 -13
  113. package/dist/{chunk-NAJN32MF.cjs.map → chunk-XF355M7O.cjs.map} +1 -1
  114. package/dist/connect/docx.cjs +18 -18
  115. package/dist/connect/index.cjs +995 -994
  116. package/dist/connect/index.cjs.map +1 -1
  117. package/dist/connect/viewer.cjs +38 -37
  118. package/dist/connection/conversation/ConnectedMessage.js +1 -1
  119. package/dist/connection/conversation/ConnectedMessage.js.map +2 -2
  120. package/dist/connection/presentation/ConnectedSlidesTab.js +39 -15
  121. package/dist/connection/presentation/ConnectedSlidesTab.js.map +2 -2
  122. package/dist/connection/presentation/PresentationPill.js +3 -8
  123. package/dist/connection/presentation/PresentationPill.js.map +2 -2
  124. package/dist/connection/presentation/requests.d.ts +5 -0
  125. package/dist/connection/presentation/requests.js +8 -1
  126. package/dist/connection/presentation/requests.js.map +2 -2
  127. package/dist/connection/viewer/ConnectedDocumentViewer.js +51 -60
  128. package/dist/connection/viewer/ConnectedDocumentViewer.js.map +2 -2
  129. package/dist/connection/viewer/ViewerMoreMenu.d.ts +18 -0
  130. package/dist/connection/viewer/ViewerMoreMenu.js +45 -0
  131. package/dist/connection/viewer/ViewerMoreMenu.js.map +7 -0
  132. package/dist/connection/viewer/ViewerPdfPane.js +4 -1
  133. package/dist/connection/viewer/ViewerPdfPane.js.map +2 -2
  134. package/dist/connection/viewer/documentPlace.d.ts +5 -4
  135. package/dist/connection/viewer/documentPlace.js +2 -2
  136. package/dist/connection/viewer/documentPlace.js.map +2 -2
  137. package/dist/connection/viewer/pdf/PdfClipOutlines.d.ts +11 -0
  138. package/dist/connection/viewer/pdf/PdfClipOutlines.js +42 -0
  139. package/dist/connection/viewer/pdf/PdfClipOutlines.js.map +7 -0
  140. package/dist/connection/viewer/pdf/PdfPassageHighlights.js +3 -2
  141. package/dist/connection/viewer/pdf/PdfPassageHighlights.js.map +2 -2
  142. package/dist/connection/viewer/pdf/clipOutlines.d.ts +17 -0
  143. package/dist/connection/viewer/pdf/clipOutlines.js +24 -0
  144. package/dist/connection/viewer/pdf/clipOutlines.js.map +7 -0
  145. package/dist/connection/viewer/pdf/passageHighlights.d.ts +2 -0
  146. package/dist/connection/viewer/pdf/passageHighlights.js +12 -1
  147. package/dist/connection/viewer/pdf/passageHighlights.js.map +2 -2
  148. package/dist/connection/viewer/pdf/usePdfClipOutlines.d.ts +7 -0
  149. package/dist/connection/viewer/pdf/usePdfClipOutlines.js +36 -0
  150. package/dist/connection/viewer/pdf/usePdfClipOutlines.js.map +7 -0
  151. package/dist/connection/viewer/pdf/usePdfPassageHighlights.js +11 -2
  152. package/dist/connection/viewer/pdf/usePdfPassageHighlights.js.map +2 -2
  153. package/dist/dashboards/index.cjs +1 -1
  154. package/dist/document-tools/DocumentToolButtons.js +2 -2
  155. package/dist/document-tools/DocumentToolButtons.js.map +2 -2
  156. package/dist/document-tools/index.cjs +13 -13
  157. package/dist/embed/index.cjs +38 -38
  158. package/dist/files/index.cjs +224 -224
  159. package/dist/index.cjs +1596 -1595
  160. package/dist/index.cjs.map +1 -1
  161. package/dist/presentation/BoxActionBar.d.ts +2 -2
  162. package/dist/presentation/BoxActionBar.js +62 -30
  163. package/dist/presentation/BoxActionBar.js.map +2 -2
  164. package/dist/presentation/ClipActionBar.js +60 -7
  165. package/dist/presentation/ClipActionBar.js.map +2 -2
  166. package/dist/presentation/ClipViewControls.d.ts +3 -3
  167. package/dist/presentation/ClipViewControls.js +59 -64
  168. package/dist/presentation/ClipViewControls.js.map +2 -2
  169. package/dist/presentation/PresentControls.d.ts +11 -0
  170. package/dist/presentation/PresentControls.js +117 -0
  171. package/dist/presentation/PresentControls.js.map +7 -0
  172. package/dist/presentation/PresenterView.js +36 -5
  173. package/dist/presentation/PresenterView.js.map +2 -2
  174. package/dist/presentation/SlidesEditor.js +73 -43
  175. package/dist/presentation/SlidesEditor.js.map +2 -2
  176. package/dist/presentation/StageItem.js +0 -5
  177. package/dist/presentation/StageItem.js.map +2 -2
  178. package/dist/presentation/panel-id.d.ts +12 -2
  179. package/dist/presentation/panel-id.js +6 -1
  180. package/dist/presentation/panel-id.js.map +2 -2
  181. package/dist/presentation/slide-layout.d.ts +5 -1
  182. package/dist/presentation/slide-layout.js +16 -0
  183. package/dist/presentation/slide-layout.js.map +2 -2
  184. package/dist/settings/index.cjs +146 -146
  185. package/dist/shell/PanelPortal.js +13 -4
  186. package/dist/shell/PanelPortal.js.map +2 -2
  187. package/dist/shell/index.cjs +92 -91
  188. package/dist/shell/panel-windows.js +1 -0
  189. package/dist/shell/panel-windows.js.map +2 -2
  190. package/dist/studio/index.cjs +28 -28
  191. package/dist/styles.css +1 -1
  192. package/dist/workflow-canvas/index.cjs +52 -52
  193. package/node_modules/@arbidocs/coedit/package.json +1 -1
  194. package/package.json +5 -5
  195. package/dist/ConnectedPresentation-4HLDZDWV.cjs +0 -183
  196. package/dist/ConnectedPresentation-4HLDZDWV.cjs.map +0 -1
  197. package/dist/ConnectedSlidesTab-7QDE2CZD.cjs.map +0 -1
  198. package/dist/FilesViewerDialog-IZI63FOV.cjs.map +0 -1
  199. package/dist/ai-KBYNRNZ6.cjs.map +0 -1
  200. package/dist/chunk-5LTIZ44D.cjs.map +0 -1
  201. package/dist/chunk-7VUJGHMY.cjs.map +0 -1
  202. package/dist/chunk-GX2EBMJW.cjs.map +0 -1
  203. package/dist/chunk-JZEYHDWJ.cjs.map +0 -1
  204. package/dist/chunk-LHWOKOMP.cjs.map +0 -1
  205. package/dist/chunk-Q3XJUXID.cjs.map +0 -1
  206. package/dist/chunk-QFXILJMJ.cjs.map +0 -1
  207. package/dist/chunk-VJ5ZLRBD.cjs.map +0 -1
  208. package/dist/chunk-XTR26TNE.cjs.map +0 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/presentation/StageItem.tsx"],
4
- "sourcesContent": ["/**\n * One clip on the stage, and its own gestures: moved by dragging it, resized from a corner (its\n * shape kept), reshaped from a side's middle (more or less of the page that way), snapping to the\n * stage and to the other clips.\n *\n * Built for smooth dragging: while a clip is moved or resized nothing re-renders and nothing is\n * laid out again. Its element is moved by a GPU transform (translate, scale) once per animation\n * frame, and the new frame reaches the presentation once, when the drag ends. Only reshaping, which\n * changes what the clip shows, re-renders the clip itself (and nothing else). The component is\n * memoised: other clips, the slide's text and the other slides are untouched by its drags.\n */\n\nimport { memo, useRef, useState, type PointerEvent as ReactPointerEvent } from 'react'\nimport { cn } from '@arbidocs/react/ui'\nimport { ClipImage } from './ClipImage'\nimport { ClipViewControls } from './ClipViewControls'\nimport {\n frameHeight,\n moveFrame,\n reshapeFrame,\n resizeFrame,\n type Clip,\n type Corner,\n type Edge,\n type Frame,\n type Guide,\n type Placed,\n} from './model'\nimport { TEXT_COLORS, clipEdgeCss } from './theme'\n\ntype Kind = 'move' | Corner | Edge\n\nconst isEdge = (kind: Kind): kind is Edge =>\n kind === 'n' || kind === 'e' || kind === 's' || kind === 'w'\n\n/** The handle on each side's middle: drag it to show more (or less) of the page that way. */\nconst EDGES: { edge: Edge; left: string; top: string; cursor: string; label: string }[] = [\n { edge: 'n', left: '50%', top: '0%', cursor: 'ns-resize', label: 'top' },\n { edge: 'e', left: '100%', top: '50%', cursor: 'ew-resize', label: 'right' },\n { edge: 's', left: '50%', top: '100%', cursor: 'ns-resize', label: 'bottom' },\n { edge: 'w', left: '0%', top: '50%', cursor: 'ew-resize', label: 'left' },\n]\n\nconst CORNERS: { corner: Corner; left: string; top: string; cursor: string }[] = [\n { corner: 'nw', left: '0%', top: '0%', cursor: 'nwse-resize' },\n { corner: 'ne', left: '100%', top: '0%', cursor: 'nesw-resize' },\n { corner: 'sw', left: '0%', top: '100%', cursor: 'nesw-resize' },\n { corner: 'se', left: '100%', top: '100%', cursor: 'nwse-resize' },\n]\n\nconst EDGE_CSS = clipEdgeCss()\n\n/** What a stage gives its clips (stable across renders: the clips are memoised). */\nexport interface StageItemHost {\n /** The stage's box on screen, as a gesture begins. */\n stageBox(): DOMRect | null\n /** The other clips of the slide, as a gesture begins: what this one snaps to. */\n others(clipId: string): Placed[]\n select(clipId: string): void\n /** The snap lines to draw while a clip is dragged (empty: none). */\n guides(guides: Guide[]): void\n /** A drag is on (the stage hides the selected clip's floating actions meanwhile). */\n dragging(on: boolean): void\n /** The drag's result, once it ends. */\n commit(clipId: string, frame: Frame, shape?: Pick<Clip, 'rect' | 'aspect'>): void\n}\n\nexport interface StageItemProps {\n clip: Clip\n frame: Frame\n editable: boolean\n selected: boolean\n dim: boolean\n /** Its source label, under it (empty: none). */\n label: string\n host: StageItemHost\n}\n\ninterface Gesture {\n kind: Kind\n pointer: { x: number; y: number }\n frame: Frame\n shape: Pick<Clip, 'rect' | 'aspect'>\n stage: DOMRect\n others: Placed[]\n /** The latest result, applied at the next animation frame and committed at the end. */\n next: { frame: Frame; shape?: Pick<Clip, 'rect' | 'aspect'>; guides: Guide[] } | null\n request: number | null\n guidesKey: string\n}\n\nexport const StageItem = memo(function StageItem({\n clip,\n frame,\n editable,\n selected,\n dim,\n label,\n host,\n}: StageItemProps) {\n const element = useRef<HTMLDivElement>(null)\n const gesture = useRef<Gesture | null>(null)\n const [dragging, setDragging] = useState(false)\n // Being reshaped: drawn as it will be (the one case that re-renders while dragging).\n const [reshape, setReshape] = useState<{\n frame: Frame\n shape: Pick<Clip, 'rect' | 'aspect'>\n } | null>(null)\n\n const shown = reshape ? { ...clip, ...reshape.shape } : clip\n const box = reshape?.frame ?? frame\n\n const view = () => element.current?.ownerDocument.defaultView ?? window\n\n const apply = () => {\n const g = gesture.current\n const el = element.current\n if (!g) return\n g.request = null\n const next = g.next\n if (!next || !el) return\n if (next.shape) {\n setReshape({ frame: next.frame, shape: next.shape })\n } else {\n // Moved or resized: a transform from where the drag began (top left fixed as the origin).\n const dx = (next.frame.x - g.frame.x) * g.stage.width\n const dy = (next.frame.y - g.frame.y) * g.stage.height\n const scale = next.frame.w / g.frame.w\n el.style.transform = `translate3d(${dx}px, ${dy}px, 0) scale(${scale})`\n }\n const key = next.guides.map((guide) => `${guide.axis}${guide.at}`).join()\n if (key !== g.guidesKey) {\n g.guidesKey = key\n host.guides(next.guides)\n }\n }\n\n const start = (event: ReactPointerEvent, kind: Kind) => {\n if (!editable || event.button !== 0) return\n const stage = host.stageBox()\n if (!stage) return\n event.preventDefault()\n event.stopPropagation()\n host.select(clip.id)\n event.currentTarget.setPointerCapture(event.pointerId)\n gesture.current = {\n kind,\n pointer: { x: event.clientX, y: event.clientY },\n frame,\n shape: { rect: clip.rect, aspect: clip.aspect },\n stage,\n others: host.others(clip.id),\n next: null,\n request: null,\n guidesKey: '',\n }\n }\n\n const move = (event: ReactPointerEvent) => {\n const g = gesture.current\n if (!g) return\n if (!g.next) {\n // The first move: the drag is on (handles and actions hide; the layer is promoted).\n setDragging(true)\n host.dragging(true)\n if (element.current) element.current.style.willChange = 'transform'\n }\n const dx = (event.clientX - g.pointer.x) / g.stage.width\n const dy = (event.clientY - g.pointer.y) / g.stage.height\n // Alt (Option) places freely, without snapping.\n const free = event.altKey\n if (isEdge(g.kind)) {\n const result = reshapeFrame(g.frame, g.shape, g.kind, dx, dy, g.others, free)\n g.next = {\n frame: result.frame,\n shape: { rect: result.rect, aspect: result.aspect },\n guides: result.guides,\n }\n } else {\n g.next =\n g.kind === 'move'\n ? moveFrame(g.frame, g.shape.aspect, dx, dy, g.others, free)\n : resizeFrame(g.frame, g.shape.aspect, g.kind, dx, dy, g.others, free)\n }\n g.request ??= view().requestAnimationFrame(apply)\n }\n\n const end = () => {\n const g = gesture.current\n gesture.current = null\n if (!g) return\n if (g.request !== null) view().cancelAnimationFrame(g.request)\n const el = element.current\n if (el) {\n el.style.transform = ''\n el.style.willChange = ''\n }\n if (!g.next) return // a click: the clip is selected, nothing moved\n setReshape(null)\n setDragging(false)\n host.dragging(false)\n host.guides([])\n host.commit(clip.id, g.next.frame, g.next.shape)\n }\n\n return (\n <div\n ref={element}\n className={cn(\n 'absolute origin-top-left',\n // Lifted off the background (a picture that is part of the slide's design is not).\n !clip.plain && 'rounded-[0.6cqw] shadow-[0_0.5cqw_1.6cqw_rgba(0,0,0,0.5)]',\n editable && 'cursor-move touch-none',\n selected && 'outline outline-2 outline-offset-2 outline-primary',\n dim && 'opacity-30'\n )}\n style={{\n left: `${box.x * 100}%`,\n top: `${box.y * 100}%`,\n width: `${box.w * 100}%`,\n height: `${frameHeight(box, shown.aspect) * 100}%`,\n }}\n onPointerDown={(event) => start(event, 'move')}\n onPointerMove={move}\n onPointerUp={end}\n onPointerCancel={end}\n data-testid={`stage-item-${clip.id}`}\n >\n {/* Softly rounded, with a faint light edge: crisp on any background. */}\n <div className={cn('absolute inset-0 overflow-hidden', !clip.plain && 'rounded-[0.6cqw]')}>\n <ClipImage clip={shown} live={!editable} />\n {/* A page's text fades out at the clip's edge rather than being sliced. */}\n {clip.kind === 'pdf' && !clip.plain && (\n <div\n className=\"pointer-events-none absolute inset-0\"\n style={{ background: EDGE_CSS }}\n data-testid=\"stage-clip-edge\"\n />\n )}\n {!clip.plain && (\n <div className=\"pointer-events-none absolute inset-0 rounded-[0.6cqw] ring-1 ring-inset ring-white/15\" />\n )}\n </div>\n {label && !clip.plain && (\n // Under the clip, in the room the layout keeps (CAPTION_SPACE): never over it.\n <div\n className=\"pointer-events-none absolute left-0 top-full mt-[0.5cqw] max-w-full truncate text-[1.7cqw] leading-tight\"\n style={{ color: TEXT_COLORS.muted }}\n >\n {label}\n </div>\n )}\n {/* Scroll and zoom what it shows, on a layer over it (not while it is dragged). */}\n {selected && !dragging && <ClipViewControls clip={clip} />}\n {/* A handle on each corner (a small dot in a finger-sized target), and a bar on each side's\n middle; their moves bubble to the clip's handlers while the gesture lasts. */}\n {selected &&\n CORNERS.map(({ corner, left, top, cursor }) => (\n <div\n key={corner}\n className=\"absolute flex size-6 -translate-x-1/2 -translate-y-1/2 items-center justify-center pointer-coarse:size-11\"\n style={{ left, top, cursor, touchAction: 'none' }}\n onPointerDown={(event) => start(event, corner)}\n aria-label={`Resize from the ${corner} corner`}\n data-testid={`stage-item-resize-${corner}`}\n >\n <span className=\"pointer-events-none size-3 rounded-full border-2 border-primary bg-background\" />\n </div>\n ))}\n {selected &&\n EDGES.map(({ edge, left, top, cursor, label: side }) => (\n <div\n key={edge}\n className=\"absolute flex size-6 -translate-x-1/2 -translate-y-1/2 items-center justify-center pointer-coarse:size-11\"\n style={{ left, top, cursor, touchAction: 'none' }}\n onPointerDown={(event) => start(event, edge)}\n aria-label={`Show more or less at the ${side}`}\n title=\"Drag to show more or less of the page\"\n data-testid={`stage-item-reshape-${edge}`}\n >\n <span\n className={cn(\n 'pointer-events-none rounded-full border-2 border-primary bg-background',\n edge === 'n' || edge === 's' ? 'h-2 w-4' : 'h-4 w-2'\n )}\n />\n </div>\n ))}\n </div>\n )\n})\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * One clip on the stage, and its own gestures: moved by dragging it, resized from a corner (its\n * shape kept), reshaped from a side's middle (more or less of the page that way), snapping to the\n * stage and to the other clips.\n *\n * Built for smooth dragging: while a clip is moved or resized nothing re-renders and nothing is\n * laid out again. Its element is moved by a GPU transform (translate, scale) once per animation\n * frame, and the new frame reaches the presentation once, when the drag ends. Only reshaping, which\n * changes what the clip shows, re-renders the clip itself (and nothing else). The component is\n * memoised: other clips, the slide's text and the other slides are untouched by its drags.\n */\n\nimport { memo, useRef, useState, type PointerEvent as ReactPointerEvent } from 'react'\nimport { cn } from '@arbidocs/react/ui'\nimport { ClipImage } from './ClipImage'\nimport {\n frameHeight,\n moveFrame,\n reshapeFrame,\n resizeFrame,\n type Clip,\n type Corner,\n type Edge,\n type Frame,\n type Guide,\n type Placed,\n} from './model'\nimport { TEXT_COLORS, clipEdgeCss } from './theme'\n\ntype Kind = 'move' | Corner | Edge\n\nconst isEdge = (kind: Kind): kind is Edge =>\n kind === 'n' || kind === 'e' || kind === 's' || kind === 'w'\n\n/** The handle on each side's middle: drag it to show more (or less) of the page that way. */\nconst EDGES: { edge: Edge; left: string; top: string; cursor: string; label: string }[] = [\n { edge: 'n', left: '50%', top: '0%', cursor: 'ns-resize', label: 'top' },\n { edge: 'e', left: '100%', top: '50%', cursor: 'ew-resize', label: 'right' },\n { edge: 's', left: '50%', top: '100%', cursor: 'ns-resize', label: 'bottom' },\n { edge: 'w', left: '0%', top: '50%', cursor: 'ew-resize', label: 'left' },\n]\n\nconst CORNERS: { corner: Corner; left: string; top: string; cursor: string }[] = [\n { corner: 'nw', left: '0%', top: '0%', cursor: 'nwse-resize' },\n { corner: 'ne', left: '100%', top: '0%', cursor: 'nesw-resize' },\n { corner: 'sw', left: '0%', top: '100%', cursor: 'nesw-resize' },\n { corner: 'se', left: '100%', top: '100%', cursor: 'nwse-resize' },\n]\n\nconst EDGE_CSS = clipEdgeCss()\n\n/** What a stage gives its clips (stable across renders: the clips are memoised). */\nexport interface StageItemHost {\n /** The stage's box on screen, as a gesture begins. */\n stageBox(): DOMRect | null\n /** The other clips of the slide, as a gesture begins: what this one snaps to. */\n others(clipId: string): Placed[]\n select(clipId: string): void\n /** The snap lines to draw while a clip is dragged (empty: none). */\n guides(guides: Guide[]): void\n /** A drag is on (the stage hides the selected clip's floating actions meanwhile). */\n dragging(on: boolean): void\n /** The drag's result, once it ends. */\n commit(clipId: string, frame: Frame, shape?: Pick<Clip, 'rect' | 'aspect'>): void\n}\n\nexport interface StageItemProps {\n clip: Clip\n frame: Frame\n editable: boolean\n selected: boolean\n dim: boolean\n /** Its source label, under it (empty: none). */\n label: string\n host: StageItemHost\n}\n\ninterface Gesture {\n kind: Kind\n pointer: { x: number; y: number }\n frame: Frame\n shape: Pick<Clip, 'rect' | 'aspect'>\n stage: DOMRect\n others: Placed[]\n /** The latest result, applied at the next animation frame and committed at the end. */\n next: { frame: Frame; shape?: Pick<Clip, 'rect' | 'aspect'>; guides: Guide[] } | null\n request: number | null\n guidesKey: string\n}\n\nexport const StageItem = memo(function StageItem({\n clip,\n frame,\n editable,\n selected,\n dim,\n label,\n host,\n}: StageItemProps) {\n const element = useRef<HTMLDivElement>(null)\n const gesture = useRef<Gesture | null>(null)\n // Being reshaped: drawn as it will be (the one case that re-renders while dragging).\n const [reshape, setReshape] = useState<{\n frame: Frame\n shape: Pick<Clip, 'rect' | 'aspect'>\n } | null>(null)\n\n const shown = reshape ? { ...clip, ...reshape.shape } : clip\n const box = reshape?.frame ?? frame\n\n const view = () => element.current?.ownerDocument.defaultView ?? window\n\n const apply = () => {\n const g = gesture.current\n const el = element.current\n if (!g) return\n g.request = null\n const next = g.next\n if (!next || !el) return\n if (next.shape) {\n setReshape({ frame: next.frame, shape: next.shape })\n } else {\n // Moved or resized: a transform from where the drag began (top left fixed as the origin).\n const dx = (next.frame.x - g.frame.x) * g.stage.width\n const dy = (next.frame.y - g.frame.y) * g.stage.height\n const scale = next.frame.w / g.frame.w\n el.style.transform = `translate3d(${dx}px, ${dy}px, 0) scale(${scale})`\n }\n const key = next.guides.map((guide) => `${guide.axis}${guide.at}`).join()\n if (key !== g.guidesKey) {\n g.guidesKey = key\n host.guides(next.guides)\n }\n }\n\n const start = (event: ReactPointerEvent, kind: Kind) => {\n if (!editable || event.button !== 0) return\n const stage = host.stageBox()\n if (!stage) return\n event.preventDefault()\n event.stopPropagation()\n host.select(clip.id)\n event.currentTarget.setPointerCapture(event.pointerId)\n gesture.current = {\n kind,\n pointer: { x: event.clientX, y: event.clientY },\n frame,\n shape: { rect: clip.rect, aspect: clip.aspect },\n stage,\n others: host.others(clip.id),\n next: null,\n request: null,\n guidesKey: '',\n }\n }\n\n const move = (event: ReactPointerEvent) => {\n const g = gesture.current\n if (!g) return\n if (!g.next) {\n // The first move: the drag is on (handles and actions hide; the layer is promoted).\n host.dragging(true)\n if (element.current) element.current.style.willChange = 'transform'\n }\n const dx = (event.clientX - g.pointer.x) / g.stage.width\n const dy = (event.clientY - g.pointer.y) / g.stage.height\n // Alt (Option) places freely, without snapping.\n const free = event.altKey\n if (isEdge(g.kind)) {\n const result = reshapeFrame(g.frame, g.shape, g.kind, dx, dy, g.others, free)\n g.next = {\n frame: result.frame,\n shape: { rect: result.rect, aspect: result.aspect },\n guides: result.guides,\n }\n } else {\n g.next =\n g.kind === 'move'\n ? moveFrame(g.frame, g.shape.aspect, dx, dy, g.others, free)\n : resizeFrame(g.frame, g.shape.aspect, g.kind, dx, dy, g.others, free)\n }\n g.request ??= view().requestAnimationFrame(apply)\n }\n\n const end = () => {\n const g = gesture.current\n gesture.current = null\n if (!g) return\n if (g.request !== null) view().cancelAnimationFrame(g.request)\n const el = element.current\n if (el) {\n el.style.transform = ''\n el.style.willChange = ''\n }\n if (!g.next) return // a click: the clip is selected, nothing moved\n setReshape(null)\n host.dragging(false)\n host.guides([])\n host.commit(clip.id, g.next.frame, g.next.shape)\n }\n\n return (\n <div\n ref={element}\n className={cn(\n 'absolute origin-top-left',\n // Lifted off the background (a picture that is part of the slide's design is not).\n !clip.plain && 'rounded-[0.6cqw] shadow-[0_0.5cqw_1.6cqw_rgba(0,0,0,0.5)]',\n editable && 'cursor-move touch-none',\n selected && 'outline outline-2 outline-offset-2 outline-primary',\n dim && 'opacity-30'\n )}\n style={{\n left: `${box.x * 100}%`,\n top: `${box.y * 100}%`,\n width: `${box.w * 100}%`,\n height: `${frameHeight(box, shown.aspect) * 100}%`,\n }}\n onPointerDown={(event) => start(event, 'move')}\n onPointerMove={move}\n onPointerUp={end}\n onPointerCancel={end}\n data-testid={`stage-item-${clip.id}`}\n >\n {/* Softly rounded, with a faint light edge: crisp on any background. */}\n <div className={cn('absolute inset-0 overflow-hidden', !clip.plain && 'rounded-[0.6cqw]')}>\n <ClipImage clip={shown} live={!editable} />\n {/* A page's text fades out at the clip's edge rather than being sliced. */}\n {clip.kind === 'pdf' && !clip.plain && (\n <div\n className=\"pointer-events-none absolute inset-0\"\n style={{ background: EDGE_CSS }}\n data-testid=\"stage-clip-edge\"\n />\n )}\n {!clip.plain && (\n <div className=\"pointer-events-none absolute inset-0 rounded-[0.6cqw] ring-1 ring-inset ring-white/15\" />\n )}\n </div>\n {label && !clip.plain && (\n // Under the clip, in the room the layout keeps (CAPTION_SPACE): never over it.\n <div\n className=\"pointer-events-none absolute left-0 top-full mt-[0.5cqw] max-w-full truncate text-[1.7cqw] leading-tight\"\n style={{ color: TEXT_COLORS.muted }}\n >\n {label}\n </div>\n )}\n {/* A handle on each corner (a small dot in a finger-sized target), and a bar on each side's\n middle; their moves bubble to the clip's handlers while the gesture lasts. */}\n {selected &&\n CORNERS.map(({ corner, left, top, cursor }) => (\n <div\n key={corner}\n className=\"absolute flex size-6 -translate-x-1/2 -translate-y-1/2 items-center justify-center pointer-coarse:size-11\"\n style={{ left, top, cursor, touchAction: 'none' }}\n onPointerDown={(event) => start(event, corner)}\n aria-label={`Resize from the ${corner} corner`}\n data-testid={`stage-item-resize-${corner}`}\n >\n <span className=\"pointer-events-none size-3 rounded-full border-2 border-primary bg-background\" />\n </div>\n ))}\n {selected &&\n EDGES.map(({ edge, left, top, cursor, label: side }) => (\n <div\n key={edge}\n className=\"absolute flex size-6 -translate-x-1/2 -translate-y-1/2 items-center justify-center pointer-coarse:size-11\"\n style={{ left, top, cursor, touchAction: 'none' }}\n onPointerDown={(event) => start(event, edge)}\n aria-label={`Show more or less at the ${side}`}\n title=\"Drag to show more or less of the page\"\n data-testid={`stage-item-reshape-${edge}`}\n >\n <span\n className={cn(\n 'pointer-events-none rounded-full border-2 border-primary bg-background',\n edge === 'n' || edge === 's' ? 'h-2 w-4' : 'h-4 w-2'\n )}\n />\n </div>\n ))}\n </div>\n )\n})\n"],
5
+ "mappings": "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",
6
6
  "names": ["StageItem"]
7
7
  }
@@ -19,14 +19,24 @@ export declare const isLegacyPresentationFile: (fileName: string | null | undefi
19
19
  type Session = {
20
20
  openPanel(panelId: string, mode: 'popout' | 'docked' | 'overlay' | 'main'): void;
21
21
  };
22
+ type Windows = {
23
+ createPopupWindow(panelId: string, onClose?: () => void, title?: string): string | null;
24
+ };
22
25
  /**
23
26
  * Open the presentation's own window (a pop-out: call it from a click), to edit the presentation
24
27
  * there, or (`present`) only to present it: that window closes when presenting ends. Recorded, so
25
28
  * a window the saved layout restores after a reload, with nothing in it (a presentation lives in
26
29
  * memory), is closed rather than reopened.
30
+ *
31
+ * Given the host's panel `windows`, the browser window opens at once, in the click, and the panel
32
+ * adopts it. The panel would otherwise open it only once the presentation's code has loaded: by
33
+ * then the click's permission to open a window has passed, and the browser blocks it as an
34
+ * unrequested pop-up (asking whether to allow pop-ups), where a document popped out opens at once.
27
35
  */
28
- export declare function openPresentation(session: Session, { present }?: {
29
- present?: boolean | undefined;
36
+ export declare function openPresentation(session: Session, { present, windows, title, }?: {
37
+ present?: boolean;
38
+ windows?: Windows;
39
+ title?: string;
30
40
  }): void;
31
41
  export declare const presentationOpenedInThisPage: () => boolean;
32
42
  /** The window was opened only to present (it closes when presenting ends). */
@@ -5,9 +5,14 @@ const LEGACY_PRESENTATION_SUFFIX = ".presentation.json";
5
5
  const isLegacyPresentationFile = (fileName) => !!fileName && fileName.endsWith(LEGACY_PRESENTATION_SUFFIX);
6
6
  let openedInThisPage = false;
7
7
  let presentOnly = false;
8
- function openPresentation(session, { present = false } = {}) {
8
+ function openPresentation(session, {
9
+ present = false,
10
+ windows,
11
+ title = "Presentation"
12
+ } = {}) {
9
13
  openedInThisPage = true;
10
14
  presentOnly = present;
15
+ windows?.createPopupWindow(`panel-${PRESENTATION_PANEL}`, void 0, title);
11
16
  session.openPanel(PRESENTATION_PANEL, "popout");
12
17
  }
13
18
  const presentationOpenedInThisPage = () => openedInThisPage;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/presentation/panel-id.ts"],
4
- "sourcesContent": ["/**\n * The presentation window's entry points, for any surface. Light: no PDF engine, no presentation\n * module (that loads with the window or the viewer's slides tab).\n *\n * A presentation is edited in the viewer's slides tab, or in a window of its own (`openPresentation`)\n * beside the documents its clips come from; Present always shows in that window, alone, to share.\n */\n\n/** The session's panel for the presentation's own window (`ConnectedPresentation`). */\nexport const PRESENTATION_PANEL = 'presentation-panel'\n\n/**\n * The names presentations were saved under before they were known by their folder (a saved\n * presentation is now `<title>.pdf` in Files \u203A Presentations): `.presentation.pdf`, and\n * `.presentation.json` (the editable data alone, from before presentations were PDFs).\n */\nexport const PRESENTATION_FILE_SUFFIX = '.presentation.pdf'\nexport const LEGACY_PRESENTATION_SUFFIX = '.presentation.json'\n\n/** A presentation saved before it was a PDF: its data alone, no slides to view. */\nexport const isLegacyPresentationFile = (fileName: string | null | undefined): boolean =>\n !!fileName && fileName.endsWith(LEGACY_PRESENTATION_SUFFIX)\n\ntype Session = { openPanel(panelId: string, mode: 'popout' | 'docked' | 'overlay' | 'main'): void }\n\nlet openedInThisPage = false\nlet presentOnly = false\n\n/**\n * Open the presentation's own window (a pop-out: call it from a click), to edit the presentation\n * there, or (`present`) only to present it: that window closes when presenting ends. Recorded, so\n * a window the saved layout restores after a reload, with nothing in it (a presentation lives in\n * memory), is closed rather than reopened.\n */\nexport function openPresentation(session: Session, { present = false } = {}): void {\n openedInThisPage = true\n presentOnly = present\n session.openPanel(PRESENTATION_PANEL, 'popout')\n}\n\nexport const presentationOpenedInThisPage = (): boolean => openedInThisPage\n\n/** The window was opened only to present (it closes when presenting ends). */\nexport const presentationWindowOnlyPresents = (): boolean => presentOnly\n"],
5
- "mappings": ";AASO,MAAM,qBAAqB;AAO3B,MAAM,2BAA2B;AACjC,MAAM,6BAA6B;AAGnC,MAAM,2BAA2B,CAAC,aACvC,CAAC,CAAC,YAAY,SAAS,SAAS,0BAA0B;AAI5D,IAAI,mBAAmB;AACvB,IAAI,cAAc;AAQX,SAAS,iBAAiB,SAAkB,EAAE,UAAU,MAAM,IAAI,CAAC,GAAS;AACjF,qBAAmB;AACnB,gBAAc;AACd,UAAQ,UAAU,oBAAoB,QAAQ;AAChD;AAEO,MAAM,+BAA+B,MAAe;AAGpD,MAAM,iCAAiC,MAAe;",
4
+ "sourcesContent": ["/**\n * The presentation window's entry points, for any surface. Light: no PDF engine, no presentation\n * module (that loads with the window or the viewer's slides tab).\n *\n * A presentation is edited in the viewer's slides tab, or in a window of its own (`openPresentation`)\n * beside the documents its clips come from; Present always shows in that window, alone, to share.\n */\n\n/** The session's panel for the presentation's own window (`ConnectedPresentation`). */\nexport const PRESENTATION_PANEL = 'presentation-panel'\n\n/**\n * The names presentations were saved under before they were known by their folder (a saved\n * presentation is now `<title>.pdf` in Files \u203A Presentations): `.presentation.pdf`, and\n * `.presentation.json` (the editable data alone, from before presentations were PDFs).\n */\nexport const PRESENTATION_FILE_SUFFIX = '.presentation.pdf'\nexport const LEGACY_PRESENTATION_SUFFIX = '.presentation.json'\n\n/** A presentation saved before it was a PDF: its data alone, no slides to view. */\nexport const isLegacyPresentationFile = (fileName: string | null | undefined): boolean =>\n !!fileName && fileName.endsWith(LEGACY_PRESENTATION_SUFFIX)\n\ntype Session = { openPanel(panelId: string, mode: 'popout' | 'docked' | 'overlay' | 'main'): void }\ntype Windows = {\n createPopupWindow(panelId: string, onClose?: () => void, title?: string): string | null\n}\n\nlet openedInThisPage = false\nlet presentOnly = false\n\n/**\n * Open the presentation's own window (a pop-out: call it from a click), to edit the presentation\n * there, or (`present`) only to present it: that window closes when presenting ends. Recorded, so\n * a window the saved layout restores after a reload, with nothing in it (a presentation lives in\n * memory), is closed rather than reopened.\n *\n * Given the host's panel `windows`, the browser window opens at once, in the click, and the panel\n * adopts it. The panel would otherwise open it only once the presentation's code has loaded: by\n * then the click's permission to open a window has passed, and the browser blocks it as an\n * unrequested pop-up (asking whether to allow pop-ups), where a document popped out opens at once.\n */\nexport function openPresentation(\n session: Session,\n {\n present = false,\n windows,\n title = 'Presentation',\n }: { present?: boolean; windows?: Windows; title?: string } = {}\n): void {\n openedInThisPage = true\n presentOnly = present\n windows?.createPopupWindow(`panel-${PRESENTATION_PANEL}`, undefined, title)\n session.openPanel(PRESENTATION_PANEL, 'popout')\n}\n\nexport const presentationOpenedInThisPage = (): boolean => openedInThisPage\n\n/** The window was opened only to present (it closes when presenting ends). */\nexport const presentationWindowOnlyPresents = (): boolean => presentOnly\n"],
5
+ "mappings": ";AASO,MAAM,qBAAqB;AAO3B,MAAM,2BAA2B;AACjC,MAAM,6BAA6B;AAGnC,MAAM,2BAA2B,CAAC,aACvC,CAAC,CAAC,YAAY,SAAS,SAAS,0BAA0B;AAO5D,IAAI,mBAAmB;AACvB,IAAI,cAAc;AAaX,SAAS,iBACd,SACA;AAAA,EACE,UAAU;AAAA,EACV;AAAA,EACA,QAAQ;AACV,IAA8D,CAAC,GACzD;AACN,qBAAmB;AACnB,gBAAc;AACd,WAAS,kBAAkB,SAAS,kBAAkB,IAAI,QAAW,KAAK;AAC1E,UAAQ,UAAU,oBAAoB,QAAQ;AAChD;AAEO,MAAM,+BAA+B,MAAe;AAGpD,MAAM,iCAAiC,MAAe;",
6
6
  "names": []
7
7
  }
@@ -46,7 +46,11 @@ export declare const LAYOUT_INFO: Record<SlideLayout, {
46
46
  label: string;
47
47
  text?: string;
48
48
  }>;
49
- export declare function slideGeometry(slide: Pick<Slide, 'layout' | 'caption'>, options: {
49
+ export declare function slideGeometry(
50
+ /** `items`, when given: a points slide with none puts its text across the slide. */
51
+ slide: Pick<Slide, 'layout' | 'caption'> & {
52
+ items?: readonly unknown[];
53
+ }, options: {
50
54
  labels: boolean;
51
55
  logo: LogoCorner | null;
52
56
  }): SlideGeometry;
@@ -87,6 +87,22 @@ function slideGeometry(slide, options) {
87
87
  logo,
88
88
  reserve: { top, below, bottom, inset }
89
89
  };
90
+ if (slide.layout === "points" && slide.items && slide.items.length === 0) {
91
+ const y = STAGE_MARGIN + top;
92
+ geometry.text = {
93
+ x: TEXT_INSET,
94
+ y,
95
+ w: 1 - 2 * TEXT_INSET,
96
+ h: 1 - y - STAGE_MARGIN - bottom,
97
+ size: 0.024,
98
+ weight: 400,
99
+ align: "left",
100
+ tone: "body",
101
+ bullets: true,
102
+ leading: 1.45
103
+ };
104
+ return geometry;
105
+ }
90
106
  if (slide.layout === "points") {
91
107
  const y = STAGE_MARGIN + top;
92
108
  geometry.text = {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/presentation/slide-layout.ts"],
4
- "sourcesContent": ["/**\n * Where a slide's parts go, by its layout: its title, the accent rule, its text (points, or a\n * subtitle), the logo in the footer, and the room its clips are arranged in. One description,\n * drawn by the stage, the saved PDF and the PowerPoint export alike.\n *\n * All positions are fractions of the stage (x, w of its width; y, h of its height); type sizes\n * are fractions of the stage width.\n */\n\nimport { CAPTION_SPACE, STAGE_MARGIN, type Reserve, type Slide, type SlideLayout } from './model'\nimport type { LogoCorner } from './theme'\n\n/** Text sits this far in from the stage's sides: a generous margin. */\nconst TEXT_INSET = 0.06\n\nexport interface TextBox {\n x: number\n y: number\n w: number\n h: number\n /** Type size, a fraction of the stage width. */\n size: number\n weight: 400 | 600 | 700\n align: 'left' | 'center'\n tone: 'title' | 'body' | 'muted'\n /** Lines start with a bullet in the accent colour. */\n bullets?: boolean\n /** Line height, a multiple of the type size. */\n leading: number\n /** Where the text sits in its box, top to bottom (default top). */\n anchor?: 'top' | 'middle' | 'bottom'\n}\n\nexport interface Box {\n x: number\n y: number\n w: number\n h: number\n}\n\nexport interface SlideGeometry {\n /** Where its title goes (none on a blank slide: its text is in its boxes). */\n title: TextBox | null\n /** The accent rule (under or above the title), when the slide shows a title. */\n rule: Box | null\n /** Where its points (or subtitle) go, if this layout has text. */\n text: TextBox | null\n logo: Box | null\n /** The room its clips keep free (they are arranged in the rest). */\n reserve: Reserve\n}\n\nexport const LAYOUT_INFO: Record<SlideLayout, { label: string; text?: string }> = {\n clips: { label: 'Title and clips' },\n points: { label: 'Clips and points', text: 'One point per line' },\n compare: { label: 'Side by side' },\n title: { label: 'Title slide', text: 'Subtitle' },\n section: { label: 'Section', text: 'Subtitle' },\n free: { label: 'Blank' },\n}\n\n/** The footer's height while a logo shows. */\nconst FOOTER = 0.085\n\nexport function slideGeometry(\n slide: Pick<Slide, 'layout' | 'caption'>,\n options: { labels: boolean; logo: LogoCorner | null }\n): SlideGeometry {\n const below = options.labels ? CAPTION_SPACE : 0\n const bottom = options.logo ? FOOTER : 0\n const logo: Box | null = options.logo\n ? {\n x: options.logo === 'bl' ? TEXT_INSET : 1 - TEXT_INSET - 0.16,\n y: 1 - 0.03 - 0.055,\n w: 0.16,\n h: 0.055,\n }\n : null\n const hasTitle = slide.caption.trim() !== ''\n\n // Blank: nothing of its own but the logo; whatever is placed on it goes anywhere.\n if (slide.layout === 'free') {\n return { title: null, rule: null, text: null, logo, reserve: { top: 0, below, bottom } }\n }\n\n if (slide.layout === 'title' || slide.layout === 'section') {\n const centred = slide.layout === 'title'\n const title: TextBox = centred\n ? {\n x: 0.1,\n y: 0.28,\n w: 0.8,\n h: 0.2,\n size: 0.052,\n weight: 700,\n align: 'center',\n tone: 'title',\n leading: 1.1,\n // Just above the rule, however many lines it takes.\n anchor: 'bottom',\n }\n : {\n x: TEXT_INSET + 0.02,\n y: 0.36,\n w: 0.8,\n h: 0.18,\n size: 0.046,\n weight: 700,\n align: 'left',\n tone: 'title',\n leading: 1.1,\n }\n return {\n title,\n rule: centred\n ? { x: 0.47, y: 0.505, w: 0.06, h: 0.007 }\n : { x: TEXT_INSET + 0.02, y: 0.33, w: 0.07, h: 0.008 },\n text: {\n x: title.x,\n y: centred ? 0.545 : 0.555,\n w: title.w,\n h: 0.1,\n size: 0.021,\n weight: 400,\n align: title.align,\n tone: 'muted',\n leading: 1.3,\n },\n logo,\n // Clips, if any, go under the text.\n reserve: { top: 0.62, below, bottom },\n }\n }\n\n const title: TextBox = {\n x: TEXT_INSET,\n y: 0.05,\n w: 1 - 2 * TEXT_INSET,\n h: 0.075,\n size: 0.03,\n weight: 600,\n align: 'left',\n tone: 'title',\n leading: 1.15,\n }\n const top = hasTitle ? 0.14 : 0\n // Clips line up with the slide's text (its title, its points); with none, they take the room.\n const inset = hasTitle || slide.layout === 'points' ? TEXT_INSET - STAGE_MARGIN : 0\n const geometry: SlideGeometry = {\n title,\n rule: hasTitle ? { x: TEXT_INSET, y: 0.128, w: 0.045, h: 0.006 } : null,\n text: null,\n logo,\n reserve: { top, below, bottom, inset },\n }\n if (slide.layout === 'points') {\n const y = STAGE_MARGIN + top\n geometry.text = {\n x: 0.6,\n y,\n w: 1 - 0.6 - TEXT_INSET,\n h: 1 - y - STAGE_MARGIN - bottom - below,\n // Level with the clips, which are centred in their room.\n anchor: 'middle',\n size: 0.0195,\n weight: 400,\n align: 'left',\n tone: 'body',\n bullets: true,\n leading: 1.45,\n }\n geometry.reserve = { ...geometry.reserve, right: 0.4 - inset }\n }\n if (slide.layout === 'compare') geometry.reserve = { ...geometry.reserve, columns: 2 }\n return geometry\n}\n\n/** The lines of a slide's text (blank lines dropped). */\nexport const textLines = (text: string): string[] =>\n text\n .split('\\n')\n .map((line) => line.trim())\n .filter(Boolean)\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Where a slide's parts go, by its layout: its title, the accent rule, its text (points, or a\n * subtitle), the logo in the footer, and the room its clips are arranged in. One description,\n * drawn by the stage, the saved PDF and the PowerPoint export alike.\n *\n * All positions are fractions of the stage (x, w of its width; y, h of its height); type sizes\n * are fractions of the stage width.\n */\n\nimport { CAPTION_SPACE, STAGE_MARGIN, type Reserve, type Slide, type SlideLayout } from './model'\nimport type { LogoCorner } from './theme'\n\n/** Text sits this far in from the stage's sides: a generous margin. */\nconst TEXT_INSET = 0.06\n\nexport interface TextBox {\n x: number\n y: number\n w: number\n h: number\n /** Type size, a fraction of the stage width. */\n size: number\n weight: 400 | 600 | 700\n align: 'left' | 'center'\n tone: 'title' | 'body' | 'muted'\n /** Lines start with a bullet in the accent colour. */\n bullets?: boolean\n /** Line height, a multiple of the type size. */\n leading: number\n /** Where the text sits in its box, top to bottom (default top). */\n anchor?: 'top' | 'middle' | 'bottom'\n}\n\nexport interface Box {\n x: number\n y: number\n w: number\n h: number\n}\n\nexport interface SlideGeometry {\n /** Where its title goes (none on a blank slide: its text is in its boxes). */\n title: TextBox | null\n /** The accent rule (under or above the title), when the slide shows a title. */\n rule: Box | null\n /** Where its points (or subtitle) go, if this layout has text. */\n text: TextBox | null\n logo: Box | null\n /** The room its clips keep free (they are arranged in the rest). */\n reserve: Reserve\n}\n\nexport const LAYOUT_INFO: Record<SlideLayout, { label: string; text?: string }> = {\n clips: { label: 'Title and clips' },\n points: { label: 'Clips and points', text: 'One point per line' },\n compare: { label: 'Side by side' },\n title: { label: 'Title slide', text: 'Subtitle' },\n section: { label: 'Section', text: 'Subtitle' },\n free: { label: 'Blank' },\n}\n\n/** The footer's height while a logo shows. */\nconst FOOTER = 0.085\n\nexport function slideGeometry(\n /** `items`, when given: a points slide with none puts its text across the slide. */\n slide: Pick<Slide, 'layout' | 'caption'> & { items?: readonly unknown[] },\n options: { labels: boolean; logo: LogoCorner | null }\n): SlideGeometry {\n const below = options.labels ? CAPTION_SPACE : 0\n const bottom = options.logo ? FOOTER : 0\n const logo: Box | null = options.logo\n ? {\n x: options.logo === 'bl' ? TEXT_INSET : 1 - TEXT_INSET - 0.16,\n y: 1 - 0.03 - 0.055,\n w: 0.16,\n h: 0.055,\n }\n : null\n const hasTitle = slide.caption.trim() !== ''\n\n // Blank: nothing of its own but the logo; whatever is placed on it goes anywhere.\n if (slide.layout === 'free') {\n return { title: null, rule: null, text: null, logo, reserve: { top: 0, below, bottom } }\n }\n\n if (slide.layout === 'title' || slide.layout === 'section') {\n const centred = slide.layout === 'title'\n const title: TextBox = centred\n ? {\n x: 0.1,\n y: 0.28,\n w: 0.8,\n h: 0.2,\n size: 0.052,\n weight: 700,\n align: 'center',\n tone: 'title',\n leading: 1.1,\n // Just above the rule, however many lines it takes.\n anchor: 'bottom',\n }\n : {\n x: TEXT_INSET + 0.02,\n y: 0.36,\n w: 0.8,\n h: 0.18,\n size: 0.046,\n weight: 700,\n align: 'left',\n tone: 'title',\n leading: 1.1,\n }\n return {\n title,\n rule: centred\n ? { x: 0.47, y: 0.505, w: 0.06, h: 0.007 }\n : { x: TEXT_INSET + 0.02, y: 0.33, w: 0.07, h: 0.008 },\n text: {\n x: title.x,\n y: centred ? 0.545 : 0.555,\n w: title.w,\n h: 0.1,\n size: 0.021,\n weight: 400,\n align: title.align,\n tone: 'muted',\n leading: 1.3,\n },\n logo,\n // Clips, if any, go under the text.\n reserve: { top: 0.62, below, bottom },\n }\n }\n\n const title: TextBox = {\n x: TEXT_INSET,\n y: 0.05,\n w: 1 - 2 * TEXT_INSET,\n h: 0.075,\n size: 0.03,\n weight: 600,\n align: 'left',\n tone: 'title',\n leading: 1.15,\n }\n const top = hasTitle ? 0.14 : 0\n // Clips line up with the slide's text (its title, its points); with none, they take the room.\n const inset = hasTitle || slide.layout === 'points' ? TEXT_INSET - STAGE_MARGIN : 0\n const geometry: SlideGeometry = {\n title,\n rule: hasTitle ? { x: TEXT_INSET, y: 0.128, w: 0.045, h: 0.006 } : null,\n text: null,\n logo,\n reserve: { top, below, bottom, inset },\n }\n if (slide.layout === 'points' && slide.items && slide.items.length === 0) {\n // Points alone: across the slide, under its title (no room kept for clips that are not there).\n const y = STAGE_MARGIN + top\n geometry.text = {\n x: TEXT_INSET,\n y,\n w: 1 - 2 * TEXT_INSET,\n h: 1 - y - STAGE_MARGIN - bottom,\n size: 0.024,\n weight: 400,\n align: 'left',\n tone: 'body',\n bullets: true,\n leading: 1.45,\n }\n return geometry\n }\n if (slide.layout === 'points') {\n const y = STAGE_MARGIN + top\n geometry.text = {\n x: 0.6,\n y,\n w: 1 - 0.6 - TEXT_INSET,\n h: 1 - y - STAGE_MARGIN - bottom - below,\n // Level with the clips, which are centred in their room.\n anchor: 'middle',\n size: 0.0195,\n weight: 400,\n align: 'left',\n tone: 'body',\n bullets: true,\n leading: 1.45,\n }\n geometry.reserve = { ...geometry.reserve, right: 0.4 - inset }\n }\n if (slide.layout === 'compare') geometry.reserve = { ...geometry.reserve, columns: 2 }\n return geometry\n}\n\n/** The lines of a slide's text (blank lines dropped). */\nexport const textLines = (text: string): string[] =>\n text\n .split('\\n')\n .map((line) => line.trim())\n .filter(Boolean)\n"],
5
+ "mappings": "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",
6
6
  "names": ["title"]
7
7
  }