@cratis/components 3.4.0 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (386) hide show
  1. package/README.md +148 -98
  2. package/dist/esm/Canvas/Canvas.d.ts +64 -0
  3. package/dist/esm/Canvas/Canvas.d.ts.map +1 -0
  4. package/dist/esm/Canvas/Canvas.js +767 -0
  5. package/dist/esm/Canvas/Canvas.js.map +1 -0
  6. package/dist/esm/Canvas/CanvasControls.d.ts +32 -0
  7. package/dist/esm/Canvas/CanvasControls.d.ts.map +1 -0
  8. package/dist/esm/Canvas/CanvasControls.js +34 -0
  9. package/dist/esm/Canvas/CanvasControls.js.map +1 -0
  10. package/dist/esm/Canvas/CanvasItem.d.ts +10 -0
  11. package/dist/esm/Canvas/CanvasItem.d.ts.map +1 -0
  12. package/dist/esm/Canvas/CanvasItem.js +51 -0
  13. package/dist/esm/Canvas/CanvasItem.js.map +1 -0
  14. package/dist/esm/Canvas/CanvasMinimap.d.ts +25 -0
  15. package/dist/esm/Canvas/CanvasMinimap.d.ts.map +1 -0
  16. package/dist/esm/Canvas/CanvasMinimap.js +102 -0
  17. package/dist/esm/Canvas/CanvasMinimap.js.map +1 -0
  18. package/dist/esm/Canvas/CanvasOverlay.d.ts +6 -0
  19. package/dist/esm/Canvas/CanvasOverlay.d.ts.map +1 -0
  20. package/dist/esm/Canvas/CanvasOverlay.js +8 -0
  21. package/dist/esm/Canvas/CanvasOverlay.js.map +1 -0
  22. package/dist/esm/Canvas/canvasGesture.d.ts +8 -0
  23. package/dist/esm/Canvas/canvasGesture.d.ts.map +1 -0
  24. package/dist/esm/Canvas/canvasGesture.js +32 -0
  25. package/dist/esm/Canvas/canvasGesture.js.map +1 -0
  26. package/dist/esm/Canvas/canvasTransformActivity.d.ts +7 -0
  27. package/dist/esm/Canvas/canvasTransformActivity.d.ts.map +1 -0
  28. package/dist/esm/Canvas/canvasTransformActivity.js +22 -0
  29. package/dist/esm/Canvas/canvasTransformActivity.js.map +1 -0
  30. package/dist/esm/Canvas/glassAttributes.d.ts +4 -0
  31. package/dist/esm/Canvas/glassAttributes.d.ts.map +1 -0
  32. package/dist/esm/Canvas/glassAttributes.js +25 -0
  33. package/dist/esm/Canvas/glassAttributes.js.map +1 -0
  34. package/dist/esm/Canvas/index.d.ts +18 -0
  35. package/dist/esm/Canvas/index.d.ts.map +1 -0
  36. package/dist/esm/Canvas/index.js +38 -0
  37. package/dist/esm/Canvas/index.js.map +1 -0
  38. package/dist/esm/Canvas/isBackgroundPointerTarget.d.ts +2 -0
  39. package/dist/esm/Canvas/isBackgroundPointerTarget.d.ts.map +1 -0
  40. package/dist/esm/Canvas/isBackgroundPointerTarget.js +17 -0
  41. package/dist/esm/Canvas/isBackgroundPointerTarget.js.map +1 -0
  42. package/dist/esm/Canvas/isWithinScrollableContent.d.ts +11 -0
  43. package/dist/esm/Canvas/isWithinScrollableContent.d.ts.map +1 -0
  44. package/dist/esm/Canvas/isWithinScrollableContent.js +38 -0
  45. package/dist/esm/Canvas/isWithinScrollableContent.js.map +1 -0
  46. package/dist/esm/Canvas/panMomentum.d.ts +13 -0
  47. package/dist/esm/Canvas/panMomentum.d.ts.map +1 -0
  48. package/dist/esm/Canvas/panMomentum.js +41 -0
  49. package/dist/esm/Canvas/panMomentum.js.map +1 -0
  50. package/dist/esm/Canvas/pinchGesture.d.ts +16 -0
  51. package/dist/esm/Canvas/pinchGesture.d.ts.map +1 -0
  52. package/dist/esm/Canvas/pinchGesture.js +30 -0
  53. package/dist/esm/Canvas/pinchGesture.js.map +1 -0
  54. package/dist/esm/Canvas/selfSuspendingFrameLoop.d.ts +10 -0
  55. package/dist/esm/Canvas/selfSuspendingFrameLoop.d.ts.map +1 -0
  56. package/dist/esm/Canvas/selfSuspendingFrameLoop.js +33 -0
  57. package/dist/esm/Canvas/selfSuspendingFrameLoop.js.map +1 -0
  58. package/dist/esm/Canvas/shapes/ChatBubble/AnchoredOverlay.d.ts +13 -0
  59. package/dist/esm/Canvas/shapes/ChatBubble/AnchoredOverlay.d.ts.map +1 -0
  60. package/dist/esm/Canvas/shapes/ChatBubble/AnchoredOverlay.js +61 -0
  61. package/dist/esm/Canvas/shapes/ChatBubble/AnchoredOverlay.js.map +1 -0
  62. package/dist/esm/Canvas/shapes/ChatBubble/Avatar/PersonAvatarCircle.d.ts +20 -0
  63. package/dist/esm/Canvas/shapes/ChatBubble/Avatar/PersonAvatarCircle.d.ts.map +1 -0
  64. package/dist/esm/Canvas/shapes/ChatBubble/Avatar/PersonAvatarCircle.js +27 -0
  65. package/dist/esm/Canvas/shapes/ChatBubble/Avatar/PersonAvatarCircle.js.map +1 -0
  66. package/dist/esm/Canvas/shapes/ChatBubble/Avatar/getAvatarColor.d.ts +2 -0
  67. package/dist/esm/Canvas/shapes/ChatBubble/Avatar/getAvatarColor.d.ts.map +1 -0
  68. package/dist/esm/Canvas/shapes/ChatBubble/Avatar/getAvatarColor.js +20 -0
  69. package/dist/esm/Canvas/shapes/ChatBubble/Avatar/getAvatarColor.js.map +1 -0
  70. package/dist/esm/Canvas/shapes/ChatBubble/Avatar/getInitials.d.ts +2 -0
  71. package/dist/esm/Canvas/shapes/ChatBubble/Avatar/getInitials.d.ts.map +1 -0
  72. package/dist/esm/Canvas/shapes/ChatBubble/Avatar/getInitials.js +11 -0
  73. package/dist/esm/Canvas/shapes/ChatBubble/Avatar/getInitials.js.map +1 -0
  74. package/dist/esm/Canvas/shapes/ChatBubble/Avatar/index.d.ts +5 -0
  75. package/dist/esm/Canvas/shapes/ChatBubble/Avatar/index.d.ts.map +1 -0
  76. package/dist/esm/Canvas/shapes/ChatBubble/Avatar/index.js +4 -0
  77. package/dist/esm/Canvas/shapes/ChatBubble/Avatar/index.js.map +1 -0
  78. package/dist/esm/Canvas/shapes/ChatBubble/Chat.d.ts +41 -0
  79. package/dist/esm/Canvas/shapes/ChatBubble/Chat.d.ts.map +1 -0
  80. package/dist/esm/Canvas/shapes/ChatBubble/Chat.js +76 -0
  81. package/dist/esm/Canvas/shapes/ChatBubble/Chat.js.map +1 -0
  82. package/dist/esm/Canvas/shapes/ChatBubble/ChatAuthorKind.d.ts +5 -0
  83. package/dist/esm/Canvas/shapes/ChatBubble/ChatAuthorKind.d.ts.map +1 -0
  84. package/dist/esm/Canvas/shapes/ChatBubble/ChatAuthorKind.js +14 -0
  85. package/dist/esm/Canvas/shapes/ChatBubble/ChatAuthorKind.js.map +1 -0
  86. package/dist/esm/Canvas/shapes/ChatBubble/ChatBubble.d.ts +18 -0
  87. package/dist/esm/Canvas/shapes/ChatBubble/ChatBubble.d.ts.map +1 -0
  88. package/dist/esm/Canvas/shapes/ChatBubble/ChatBubble.js +89 -0
  89. package/dist/esm/Canvas/shapes/ChatBubble/ChatBubble.js.map +1 -0
  90. package/dist/esm/Canvas/shapes/ChatBubble/ChatComposer.d.ts +23 -0
  91. package/dist/esm/Canvas/shapes/ChatBubble/ChatComposer.d.ts.map +1 -0
  92. package/dist/esm/Canvas/shapes/ChatBubble/ChatComposer.js +107 -0
  93. package/dist/esm/Canvas/shapes/ChatBubble/ChatComposer.js.map +1 -0
  94. package/dist/esm/Canvas/shapes/ChatBubble/ChatMessage.d.ts +17 -0
  95. package/dist/esm/Canvas/shapes/ChatBubble/ChatMessage.d.ts.map +1 -0
  96. package/dist/esm/Canvas/shapes/ChatBubble/ChatMessage.js +2 -0
  97. package/dist/esm/Canvas/shapes/ChatBubble/ChatMessage.js.map +1 -0
  98. package/dist/esm/Canvas/shapes/ChatBubble/ChatMessageBubble.d.ts +25 -0
  99. package/dist/esm/Canvas/shapes/ChatBubble/ChatMessageBubble.d.ts.map +1 -0
  100. package/dist/esm/Canvas/shapes/ChatBubble/ChatMessageBubble.js +31 -0
  101. package/dist/esm/Canvas/shapes/ChatBubble/ChatMessageBubble.js.map +1 -0
  102. package/dist/esm/Canvas/shapes/ChatBubble/ChatMessageReaction.d.ts +6 -0
  103. package/dist/esm/Canvas/shapes/ChatBubble/ChatMessageReaction.d.ts.map +1 -0
  104. package/dist/esm/Canvas/shapes/ChatBubble/ChatMessageReaction.js +2 -0
  105. package/dist/esm/Canvas/shapes/ChatBubble/ChatMessageReaction.js.map +1 -0
  106. package/dist/esm/Canvas/shapes/ChatBubble/ChatMessageReactionUser.d.ts +7 -0
  107. package/dist/esm/Canvas/shapes/ChatBubble/ChatMessageReactionUser.d.ts.map +1 -0
  108. package/dist/esm/Canvas/shapes/ChatBubble/ChatMessageReactionUser.js +2 -0
  109. package/dist/esm/Canvas/shapes/ChatBubble/ChatMessageReactionUser.js.map +1 -0
  110. package/dist/esm/Canvas/shapes/ChatBubble/ChatOwnReaction.d.ts +6 -0
  111. package/dist/esm/Canvas/shapes/ChatBubble/ChatOwnReaction.d.ts.map +1 -0
  112. package/dist/esm/Canvas/shapes/ChatBubble/ChatOwnReaction.js +2 -0
  113. package/dist/esm/Canvas/shapes/ChatBubble/ChatOwnReaction.js.map +1 -0
  114. package/dist/esm/Canvas/shapes/ChatBubble/ChatTypingAuthor.d.ts +8 -0
  115. package/dist/esm/Canvas/shapes/ChatBubble/ChatTypingAuthor.d.ts.map +1 -0
  116. package/dist/esm/Canvas/shapes/ChatBubble/ChatTypingAuthor.js +2 -0
  117. package/dist/esm/Canvas/shapes/ChatBubble/ChatTypingAuthor.js.map +1 -0
  118. package/dist/esm/Canvas/shapes/ChatBubble/ChatVariant.d.ts +5 -0
  119. package/dist/esm/Canvas/shapes/ChatBubble/ChatVariant.d.ts.map +1 -0
  120. package/dist/esm/Canvas/shapes/ChatBubble/ChatVariant.js +15 -0
  121. package/dist/esm/Canvas/shapes/ChatBubble/ChatVariant.js.map +1 -0
  122. package/dist/esm/Canvas/shapes/ChatBubble/Emoji/EmojiCategory.d.ts +6 -0
  123. package/dist/esm/Canvas/shapes/ChatBubble/Emoji/EmojiCategory.d.ts.map +1 -0
  124. package/dist/esm/Canvas/shapes/ChatBubble/Emoji/EmojiCategory.js +2 -0
  125. package/dist/esm/Canvas/shapes/ChatBubble/Emoji/EmojiCategory.js.map +1 -0
  126. package/dist/esm/Canvas/shapes/ChatBubble/Emoji/EmojiCategoryKey.d.ts +12 -0
  127. package/dist/esm/Canvas/shapes/ChatBubble/Emoji/EmojiCategoryKey.d.ts.map +1 -0
  128. package/dist/esm/Canvas/shapes/ChatBubble/Emoji/EmojiCategoryKey.js +18 -0
  129. package/dist/esm/Canvas/shapes/ChatBubble/Emoji/EmojiCategoryKey.js.map +1 -0
  130. package/dist/esm/Canvas/shapes/ChatBubble/Emoji/EmojiMemory.d.ts +5 -0
  131. package/dist/esm/Canvas/shapes/ChatBubble/Emoji/EmojiMemory.d.ts.map +1 -0
  132. package/dist/esm/Canvas/shapes/ChatBubble/Emoji/EmojiMemory.js +2 -0
  133. package/dist/esm/Canvas/shapes/ChatBubble/Emoji/EmojiMemory.js.map +1 -0
  134. package/dist/esm/Canvas/shapes/ChatBubble/Emoji/EmojiPicker.d.ts +14 -0
  135. package/dist/esm/Canvas/shapes/ChatBubble/Emoji/EmojiPicker.d.ts.map +1 -0
  136. package/dist/esm/Canvas/shapes/ChatBubble/Emoji/EmojiPicker.js +51 -0
  137. package/dist/esm/Canvas/shapes/ChatBubble/Emoji/EmojiPicker.js.map +1 -0
  138. package/dist/esm/Canvas/shapes/ChatBubble/Emoji/emojiCatalog.d.ts +3 -0
  139. package/dist/esm/Canvas/shapes/ChatBubble/Emoji/emojiCatalog.d.ts.map +1 -0
  140. package/dist/esm/Canvas/shapes/ChatBubble/Emoji/emojiCatalog.js +155 -0
  141. package/dist/esm/Canvas/shapes/ChatBubble/Emoji/emojiCatalog.js.map +1 -0
  142. package/dist/esm/Canvas/shapes/ChatBubble/Emoji/index.d.ts +7 -0
  143. package/dist/esm/Canvas/shapes/ChatBubble/Emoji/index.d.ts.map +1 -0
  144. package/dist/esm/Canvas/shapes/ChatBubble/Emoji/index.js +7 -0
  145. package/dist/esm/Canvas/shapes/ChatBubble/Emoji/index.js.map +1 -0
  146. package/dist/esm/Canvas/shapes/ChatBubble/Emoji/recentEmojis.d.ts +6 -0
  147. package/dist/esm/Canvas/shapes/ChatBubble/Emoji/recentEmojis.d.ts.map +1 -0
  148. package/dist/esm/Canvas/shapes/ChatBubble/Emoji/recentEmojis.js +59 -0
  149. package/dist/esm/Canvas/shapes/ChatBubble/Emoji/recentEmojis.js.map +1 -0
  150. package/dist/esm/Canvas/shapes/ChatBubble/FailedReply.d.ts +23 -0
  151. package/dist/esm/Canvas/shapes/ChatBubble/FailedReply.d.ts.map +1 -0
  152. package/dist/esm/Canvas/shapes/ChatBubble/FailedReply.js +28 -0
  153. package/dist/esm/Canvas/shapes/ChatBubble/FailedReply.js.map +1 -0
  154. package/dist/esm/Canvas/shapes/ChatBubble/Mentions/MentionCandidate.d.ts +9 -0
  155. package/dist/esm/Canvas/shapes/ChatBubble/Mentions/MentionCandidate.d.ts.map +1 -0
  156. package/dist/esm/Canvas/shapes/ChatBubble/Mentions/MentionCandidate.js +2 -0
  157. package/dist/esm/Canvas/shapes/ChatBubble/Mentions/MentionCandidate.js.map +1 -0
  158. package/dist/esm/Canvas/shapes/ChatBubble/Mentions/MentionQuery.d.ts +5 -0
  159. package/dist/esm/Canvas/shapes/ChatBubble/Mentions/MentionQuery.d.ts.map +1 -0
  160. package/dist/esm/Canvas/shapes/ChatBubble/Mentions/MentionQuery.js +2 -0
  161. package/dist/esm/Canvas/shapes/ChatBubble/Mentions/MentionQuery.js.map +1 -0
  162. package/dist/esm/Canvas/shapes/ChatBubble/Mentions/MentionSuggestions.d.ts +11 -0
  163. package/dist/esm/Canvas/shapes/ChatBubble/Mentions/MentionSuggestions.d.ts.map +1 -0
  164. package/dist/esm/Canvas/shapes/ChatBubble/Mentions/MentionSuggestions.js +14 -0
  165. package/dist/esm/Canvas/shapes/ChatBubble/Mentions/MentionSuggestions.js.map +1 -0
  166. package/dist/esm/Canvas/shapes/ChatBubble/Mentions/activeMentionQuery.d.ts +3 -0
  167. package/dist/esm/Canvas/shapes/ChatBubble/Mentions/activeMentionQuery.d.ts.map +1 -0
  168. package/dist/esm/Canvas/shapes/ChatBubble/Mentions/activeMentionQuery.js +34 -0
  169. package/dist/esm/Canvas/shapes/ChatBubble/Mentions/activeMentionQuery.js.map +1 -0
  170. package/dist/esm/Canvas/shapes/ChatBubble/Mentions/applyMention.d.ts +7 -0
  171. package/dist/esm/Canvas/shapes/ChatBubble/Mentions/applyMention.d.ts.map +1 -0
  172. package/dist/esm/Canvas/shapes/ChatBubble/Mentions/applyMention.js +23 -0
  173. package/dist/esm/Canvas/shapes/ChatBubble/Mentions/applyMention.js.map +1 -0
  174. package/dist/esm/Canvas/shapes/ChatBubble/Mentions/index.d.ts +7 -0
  175. package/dist/esm/Canvas/shapes/ChatBubble/Mentions/index.d.ts.map +1 -0
  176. package/dist/esm/Canvas/shapes/ChatBubble/Mentions/index.js +7 -0
  177. package/dist/esm/Canvas/shapes/ChatBubble/Mentions/index.js.map +1 -0
  178. package/dist/esm/Canvas/shapes/ChatBubble/Mentions/matchCandidates.d.ts +3 -0
  179. package/dist/esm/Canvas/shapes/ChatBubble/Mentions/matchCandidates.d.ts.map +1 -0
  180. package/dist/esm/Canvas/shapes/ChatBubble/Mentions/matchCandidates.js +24 -0
  181. package/dist/esm/Canvas/shapes/ChatBubble/Mentions/matchCandidates.js.map +1 -0
  182. package/dist/esm/Canvas/shapes/ChatBubble/MessageReactions.d.ts +6 -0
  183. package/dist/esm/Canvas/shapes/ChatBubble/MessageReactions.d.ts.map +1 -0
  184. package/dist/esm/Canvas/shapes/ChatBubble/MessageReactions.js +28 -0
  185. package/dist/esm/Canvas/shapes/ChatBubble/MessageReactions.js.map +1 -0
  186. package/dist/esm/Canvas/shapes/ChatBubble/ReactionPicker.d.ts +20 -0
  187. package/dist/esm/Canvas/shapes/ChatBubble/ReactionPicker.d.ts.map +1 -0
  188. package/dist/esm/Canvas/shapes/ChatBubble/ReactionPicker.js +50 -0
  189. package/dist/esm/Canvas/shapes/ChatBubble/ReactionPicker.js.map +1 -0
  190. package/dist/esm/Canvas/shapes/ChatBubble/TypingIndicator.d.ts +9 -0
  191. package/dist/esm/Canvas/shapes/ChatBubble/TypingIndicator.d.ts.map +1 -0
  192. package/dist/esm/Canvas/shapes/ChatBubble/TypingIndicator.js +18 -0
  193. package/dist/esm/Canvas/shapes/ChatBubble/TypingIndicator.js.map +1 -0
  194. package/dist/esm/Canvas/shapes/ChatBubble/findOwnReaction.d.ts +5 -0
  195. package/dist/esm/Canvas/shapes/ChatBubble/findOwnReaction.d.ts.map +1 -0
  196. package/dist/esm/Canvas/shapes/ChatBubble/findOwnReaction.js +19 -0
  197. package/dist/esm/Canvas/shapes/ChatBubble/findOwnReaction.js.map +1 -0
  198. package/dist/esm/Canvas/shapes/ChatBubble/index.d.ts +31 -0
  199. package/dist/esm/Canvas/shapes/ChatBubble/index.d.ts.map +1 -0
  200. package/dist/esm/Canvas/shapes/ChatBubble/index.js +17 -0
  201. package/dist/esm/Canvas/shapes/ChatBubble/index.js.map +1 -0
  202. package/dist/esm/Canvas/shapes/ChatBubble/reactionsExcludingUser.d.ts +4 -0
  203. package/dist/esm/Canvas/shapes/ChatBubble/reactionsExcludingUser.d.ts.map +1 -0
  204. package/dist/esm/Canvas/shapes/ChatBubble/reactionsExcludingUser.js +16 -0
  205. package/dist/esm/Canvas/shapes/ChatBubble/reactionsExcludingUser.js.map +1 -0
  206. package/dist/esm/Canvas/shapes/Note/Note.d.ts +22 -0
  207. package/dist/esm/Canvas/shapes/Note/Note.d.ts.map +1 -0
  208. package/dist/esm/Canvas/shapes/Note/Note.js +237 -0
  209. package/dist/esm/Canvas/shapes/Note/Note.js.map +1 -0
  210. package/dist/esm/Canvas/shapes/Note/index.d.ts +3 -0
  211. package/dist/esm/Canvas/shapes/Note/index.d.ts.map +1 -0
  212. package/dist/esm/Canvas/shapes/Note/index.js +2 -0
  213. package/dist/esm/Canvas/shapes/Note/index.js.map +1 -0
  214. package/dist/esm/Canvas/shapes/Note/noteFont.d.ts +2 -0
  215. package/dist/esm/Canvas/shapes/Note/noteFont.d.ts.map +1 -0
  216. package/dist/esm/Canvas/shapes/Note/noteFont.js +26 -0
  217. package/dist/esm/Canvas/shapes/Note/noteFont.js.map +1 -0
  218. package/dist/esm/Canvas/shapes/Region/Region.d.ts +22 -0
  219. package/dist/esm/Canvas/shapes/Region/Region.d.ts.map +1 -0
  220. package/dist/esm/Canvas/shapes/Region/Region.js +150 -0
  221. package/dist/esm/Canvas/shapes/Region/Region.js.map +1 -0
  222. package/dist/esm/Canvas/shapes/Region/index.d.ts +3 -0
  223. package/dist/esm/Canvas/shapes/Region/index.d.ts.map +1 -0
  224. package/dist/esm/Canvas/shapes/Region/index.js +2 -0
  225. package/dist/esm/Canvas/shapes/Region/index.js.map +1 -0
  226. package/dist/esm/Canvas/useDragSelectionGuard.d.ts +2 -0
  227. package/dist/esm/Canvas/useDragSelectionGuard.d.ts.map +1 -0
  228. package/dist/esm/Canvas/useDragSelectionGuard.js +59 -0
  229. package/dist/esm/Canvas/useDragSelectionGuard.js.map +1 -0
  230. package/dist/esm/Canvas/zoomMechanism.d.ts +4 -0
  231. package/dist/esm/Canvas/zoomMechanism.d.ts.map +1 -0
  232. package/dist/esm/Canvas/zoomMechanism.js +58 -0
  233. package/dist/esm/Canvas/zoomMechanism.js.map +1 -0
  234. package/dist/esm/CommandDialog/CommandDialog.d.ts +3 -6
  235. package/dist/esm/CommandDialog/CommandDialog.d.ts.map +1 -1
  236. package/dist/esm/CommandDialog/CommandDialog.js +3 -2
  237. package/dist/esm/CommandDialog/CommandDialog.js.map +1 -1
  238. package/dist/esm/CommandDialog/CommandStepper.d.ts.map +1 -1
  239. package/dist/esm/CommandDialog/CommandStepper.js +3 -2
  240. package/dist/esm/CommandDialog/CommandStepper.js.map +1 -1
  241. package/dist/esm/CommandForm/commandFormMarkers.d.ts +12 -0
  242. package/dist/esm/CommandForm/commandFormMarkers.d.ts.map +1 -0
  243. package/dist/esm/CommandForm/commandFormMarkers.js +69 -0
  244. package/dist/esm/CommandForm/commandFormMarkers.js.map +1 -0
  245. package/dist/esm/CommandForm/fields/CalendarField.d.ts +1 -1
  246. package/dist/esm/CommandForm/fields/CalendarField.d.ts.map +1 -1
  247. package/dist/esm/CommandForm/fields/CheckboxField.d.ts +1 -1
  248. package/dist/esm/CommandForm/fields/CheckboxField.d.ts.map +1 -1
  249. package/dist/esm/CommandForm/fields/ChipsField.d.ts +1 -1
  250. package/dist/esm/CommandForm/fields/ChipsField.d.ts.map +1 -1
  251. package/dist/esm/CommandForm/fields/ColorPickerField.d.ts +1 -1
  252. package/dist/esm/CommandForm/fields/ColorPickerField.d.ts.map +1 -1
  253. package/dist/esm/CommandForm/fields/DropdownField.d.ts +1 -1
  254. package/dist/esm/CommandForm/fields/DropdownField.d.ts.map +1 -1
  255. package/dist/esm/CommandForm/fields/InputTextField.d.ts +1 -1
  256. package/dist/esm/CommandForm/fields/InputTextField.d.ts.map +1 -1
  257. package/dist/esm/CommandForm/fields/MultiSelectField.d.ts +1 -1
  258. package/dist/esm/CommandForm/fields/MultiSelectField.d.ts.map +1 -1
  259. package/dist/esm/CommandForm/fields/NumberField.d.ts +1 -1
  260. package/dist/esm/CommandForm/fields/NumberField.d.ts.map +1 -1
  261. package/dist/esm/CommandForm/fields/PasswordField.d.ts +1 -1
  262. package/dist/esm/CommandForm/fields/PasswordField.d.ts.map +1 -1
  263. package/dist/esm/CommandForm/fields/RadioButtonField.d.ts +1 -1
  264. package/dist/esm/CommandForm/fields/RadioButtonField.d.ts.map +1 -1
  265. package/dist/esm/CommandForm/fields/RadioGroupField.d.ts +1 -1
  266. package/dist/esm/CommandForm/fields/RadioGroupField.d.ts.map +1 -1
  267. package/dist/esm/CommandForm/fields/RatingField.d.ts +1 -1
  268. package/dist/esm/CommandForm/fields/RatingField.d.ts.map +1 -1
  269. package/dist/esm/CommandForm/fields/SliderField.d.ts +1 -1
  270. package/dist/esm/CommandForm/fields/SliderField.d.ts.map +1 -1
  271. package/dist/esm/CommandForm/fields/TextAreaField.d.ts +1 -1
  272. package/dist/esm/CommandForm/fields/TextAreaField.d.ts.map +1 -1
  273. package/dist/esm/CommandForm/fields/ToggleSwitchField.d.ts +1 -1
  274. package/dist/esm/CommandForm/fields/ToggleSwitchField.d.ts.map +1 -1
  275. package/dist/esm/CommandForm/index.d.ts +1 -0
  276. package/dist/esm/CommandForm/index.d.ts.map +1 -1
  277. package/dist/esm/CommandForm/index.js +1 -0
  278. package/dist/esm/CommandForm/index.js.map +1 -1
  279. package/dist/esm/Common/CratisComponentsProvider.js +1 -1
  280. package/dist/esm/Common/DatePickerInput.d.ts +11 -4
  281. package/dist/esm/Common/DatePickerInput.d.ts.map +1 -1
  282. package/dist/esm/Common/DatePickerInput.js +18 -2
  283. package/dist/esm/Common/DatePickerInput.js.map +1 -1
  284. package/dist/esm/Common/ErrorBoundary.d.ts +1 -1
  285. package/dist/esm/Compatibility/PrimeReact11PassThroughComponent.d.ts +3 -0
  286. package/dist/esm/Compatibility/PrimeReact11PassThroughComponent.d.ts.map +1 -0
  287. package/dist/esm/Compatibility/PrimeReact11PassThroughComponent.js +2 -0
  288. package/dist/esm/Compatibility/PrimeReact11PassThroughComponent.js.map +1 -0
  289. package/dist/esm/Compatibility/assertPrimeReact11PassThroughCompatibility.d.ts +3 -0
  290. package/dist/esm/Compatibility/assertPrimeReact11PassThroughCompatibility.d.ts.map +1 -0
  291. package/dist/esm/Compatibility/assertPrimeReact11PassThroughCompatibility.js +49 -0
  292. package/dist/esm/Compatibility/assertPrimeReact11PassThroughCompatibility.js.map +1 -0
  293. package/dist/esm/Compatibility/components3PrimeReact11PassThroughContract.d.ts +369 -0
  294. package/dist/esm/Compatibility/components3PrimeReact11PassThroughContract.d.ts.map +1 -0
  295. package/dist/esm/Compatibility/components3PrimeReact11PassThroughContract.js +271 -0
  296. package/dist/esm/Compatibility/components3PrimeReact11PassThroughContract.js.map +1 -0
  297. package/dist/esm/Compatibility/index.d.ts +5 -0
  298. package/dist/esm/Compatibility/index.d.ts.map +1 -0
  299. package/dist/esm/Compatibility/index.js +7 -0
  300. package/dist/esm/Compatibility/index.js.map +1 -0
  301. package/dist/esm/Compatibility/primeReact11PassThroughSentinelPreset.d.ts +8 -0
  302. package/dist/esm/Compatibility/primeReact11PassThroughSentinelPreset.d.ts.map +1 -0
  303. package/dist/esm/Compatibility/primeReact11PassThroughSentinelPreset.js +48 -0
  304. package/dist/esm/Compatibility/primeReact11PassThroughSentinelPreset.js.map +1 -0
  305. package/dist/esm/DataPage/DataPage.d.ts +4 -4
  306. package/dist/esm/DataPage/DataPage.d.ts.map +1 -1
  307. package/dist/esm/DataPage/DataPage.js +6 -10
  308. package/dist/esm/DataPage/DataPage.js.map +1 -1
  309. package/dist/esm/DataTables/Column.d.ts +3 -1
  310. package/dist/esm/DataTables/Column.d.ts.map +1 -1
  311. package/dist/esm/DataTables/Column.js.map +1 -1
  312. package/dist/esm/DataTables/ColumnFilterMenu.d.ts +22 -2
  313. package/dist/esm/DataTables/ColumnFilterMenu.d.ts.map +1 -1
  314. package/dist/esm/DataTables/ColumnFilterMenu.js +50 -13
  315. package/dist/esm/DataTables/ColumnFilterMenu.js.map +1 -1
  316. package/dist/esm/DataTables/DataTableCore.d.ts.map +1 -1
  317. package/dist/esm/DataTables/DataTableCore.js +22 -3
  318. package/dist/esm/DataTables/DataTableCore.js.map +1 -1
  319. package/dist/esm/DataTables/DataTableFilterMatcherRegistry.d.ts +14 -0
  320. package/dist/esm/DataTables/DataTableFilterMatcherRegistry.d.ts.map +1 -0
  321. package/dist/esm/DataTables/DataTableFilterMatcherRegistry.js +67 -0
  322. package/dist/esm/DataTables/DataTableFilterMatcherRegistry.js.map +1 -0
  323. package/dist/esm/DataTables/DataTableFilterMeta.d.ts +26 -1
  324. package/dist/esm/DataTables/DataTableFilterMeta.d.ts.map +1 -1
  325. package/dist/esm/DataTables/DataTableFilterMeta.js +24 -2
  326. package/dist/esm/DataTables/DataTableFilterMeta.js.map +1 -1
  327. package/dist/esm/DataTables/DataTableForObservableQuery.d.ts +3 -3
  328. package/dist/esm/DataTables/DataTableForObservableQuery.d.ts.map +1 -1
  329. package/dist/esm/DataTables/DataTableForObservableQuery.js +9 -3
  330. package/dist/esm/DataTables/DataTableForObservableQuery.js.map +1 -1
  331. package/dist/esm/DataTables/DataTableForQuery.d.ts +3 -3
  332. package/dist/esm/DataTables/DataTableForQuery.d.ts.map +1 -1
  333. package/dist/esm/DataTables/DataTableForQuery.js +8 -2
  334. package/dist/esm/DataTables/DataTableForQuery.js.map +1 -1
  335. package/dist/esm/DataTables/TablePaginator.d.ts +1 -1
  336. package/dist/esm/DataTables/TablePaginator.d.ts.map +1 -1
  337. package/dist/esm/DataTables/TablePaginator.js +13 -4
  338. package/dist/esm/DataTables/TablePaginator.js.map +1 -1
  339. package/dist/esm/DataTables/index.d.ts +1 -0
  340. package/dist/esm/DataTables/index.d.ts.map +1 -1
  341. package/dist/esm/DataTables/index.js +3 -1
  342. package/dist/esm/DataTables/index.js.map +1 -1
  343. package/dist/esm/Dialogs/Dialog.d.ts +2 -2
  344. package/dist/esm/Dialogs/Dialog.d.ts.map +1 -1
  345. package/dist/esm/Dialogs/Dialog.js +16 -18
  346. package/dist/esm/Dialogs/Dialog.js.map +1 -1
  347. package/dist/esm/Dropdown/Dropdown.d.ts +4 -2
  348. package/dist/esm/Dropdown/Dropdown.d.ts.map +1 -1
  349. package/dist/esm/Dropdown/Dropdown.js +8 -3
  350. package/dist/esm/Dropdown/Dropdown.js.map +1 -1
  351. package/dist/esm/Filter/FilterPanel.d.ts.map +1 -1
  352. package/dist/esm/Filter/FilterPanel.js +1 -1
  353. package/dist/esm/Filter/FilterPanel.js.map +1 -1
  354. package/dist/esm/Filter/RangeHistogramFilter.d.ts +4 -3
  355. package/dist/esm/Filter/RangeHistogramFilter.d.ts.map +1 -1
  356. package/dist/esm/Filter/RangeHistogramFilter.js +4 -23
  357. package/dist/esm/Filter/RangeHistogramFilter.js.map +1 -1
  358. package/dist/esm/Filter/index.d.ts +3 -2
  359. package/dist/esm/Filter/index.d.ts.map +1 -1
  360. package/dist/esm/Filter/index.js +1 -1
  361. package/dist/esm/Filter/types.d.ts +7 -1
  362. package/dist/esm/Filter/types.d.ts.map +1 -1
  363. package/dist/esm/Filter/utils.d.ts +5 -1
  364. package/dist/esm/Filter/utils.d.ts.map +1 -1
  365. package/dist/esm/Filter/utils.js +33 -1
  366. package/dist/esm/Filter/utils.js.map +1 -1
  367. package/dist/esm/Notifications/Toaster.d.ts +8 -1
  368. package/dist/esm/Notifications/Toaster.d.ts.map +1 -1
  369. package/dist/esm/Notifications/Toaster.js +16 -10
  370. package/dist/esm/Notifications/Toaster.js.map +1 -1
  371. package/dist/esm/Notifications/index.js +1 -1
  372. package/dist/esm/Notifications/toast.d.ts +52 -2
  373. package/dist/esm/Notifications/toast.d.ts.map +1 -1
  374. package/dist/esm/Notifications/toast.js +100 -2
  375. package/dist/esm/Notifications/toast.js.map +1 -1
  376. package/dist/esm/Notifications/toastCommandResult.js +2 -2
  377. package/dist/esm/Notifications/toastCommandResult.js.map +1 -1
  378. package/dist/esm/PatrickHand-latin-ext.woff2 +0 -0
  379. package/dist/esm/PatrickHand-latin.woff2 +0 -0
  380. package/dist/esm/PatrickHand-vietnamese.woff2 +0 -0
  381. package/dist/esm/index.d.ts +4 -1
  382. package/dist/esm/index.d.ts.map +1 -1
  383. package/dist/esm/index.js +42 -35
  384. package/dist/esm/index.js.map +1 -1
  385. package/dist/esm/styles.css +1285 -3
  386. package/package.json +16 -6
@@ -0,0 +1,767 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import React, { useRef, useState, useCallback, useEffect, useMemo } from 'react';
3
+ import * as PIXI from 'pixi.js';
4
+ import { LAYER_ATTR, TRANSFORM_HOST_ATTR } from './glassAttributes.js';
5
+ import { CanvasControls } from './CanvasControls.js';
6
+ import { canvasGesture } from './canvasGesture.js';
7
+ import { canvasTransformActivity } from './canvasTransformActivity.js';
8
+ import { isBackgroundPointerTarget } from './isBackgroundPointerTarget.js';
9
+ import { isWithinScrollableContent } from './isWithinScrollableContent.js';
10
+ import { velocityFromSamples, trimSamples, decayVelocity } from './panMomentum.js';
11
+ import { pinchSnapshotOf, pinchChangeBetween } from './pinchGesture.js';
12
+ import { useDragSelectionGuard } from './useDragSelectionGuard.js';
13
+ import { applyZoomLayer, shouldUseCssZoom, isMultiTouchCapableDevice } from './zoomMechanism.js';
14
+
15
+ /**
16
+ * The easing curve for programmatic camera moves: a cubic ease-in-out, so the camera picks up speed gently
17
+ * and settles gently instead of starting at full tilt — the difference between a camera move and a yank.
18
+ * @param progress Linear progress through the animation, 0 to 1.
19
+ * @returns The eased progress.
20
+ */
21
+ function easeInOutCubic(progress) {
22
+ return progress < 0.5
23
+ ? 4 * progress * progress * progress
24
+ : 1 - Math.pow(-2 * progress + 2, 3) / 2;
25
+ }
26
+ const CanvasItemRegistryContext = React.createContext(null);
27
+ const ZOOM_INTENSITY = 0.008;
28
+ // How long after the last wheel event a gesture counts as settled - the moment the crisp resting zoom is
29
+ // re-applied and the held-back virtualization updates flush.
30
+ const GESTURE_SETTLE_MS = 150;
31
+ // A trackpad's momentum comes for free — the OS keeps delivering decaying wheel events after the
32
+ // fingers lift. A touch drag has no such thing: `touch-action: none` hands the whole gesture to us,
33
+ // so lifting a finger stops the pan dead unless we fake the same decay ourselves. These tune that feel.
34
+ // Only samples within this trailing window (from the drag's last moment, not its whole history)
35
+ // contribute to the release velocity, so a drag that was moving fast but came to rest before the
36
+ // finger actually lifted correctly produces no momentum.
37
+ const MOMENTUM_SAMPLE_WINDOW_MS = 100;
38
+ // Below this speed (px/ms) momentum is imperceptible — used both to skip starting it on a slow
39
+ // release and to end the decay loop once it coasts down to a stop.
40
+ const MOMENTUM_MIN_VELOCITY = 0.02;
41
+ function Canvas({ children, items = [], renderItem, onItemPointerDown, onReady, onTransformChange, onHandleReady, initialZoom = 1, initialPan = { x: 0, y: 0 }, minZoom = 0.1, maxZoom = 5, showControls = true, showMinimap = false, minimapWorldWidth, minimapWorldHeight, minimapItems, controlsPlacement = 'bottom-left', className, style, onHelp, helpTitle, disableControlsGlass = false, readOnly = false, backgroundDragPans = true, }) {
42
+ const containerRef = useRef(null);
43
+ useDragSelectionGuard(containerRef);
44
+ const appRef = useRef(null);
45
+ const worldRef = useRef(null);
46
+ const minimapRef = useRef(null);
47
+ const overlayRef = useRef(null);
48
+ const zoomLayerRef = useRef(null);
49
+ const spritesRef = useRef(new Map());
50
+ const animationFrameRef = useRef(null);
51
+ const [pixiReady, setPixiReady] = useState(false);
52
+ // Mutable refs so event callbacks always read current values without re-binding
53
+ const panRef = useRef({ x: initialPan.x, y: initialPan.y });
54
+ const zoomRef = useRef(initialZoom);
55
+ // The transform styles rendered by JSX are frozen at their first-mount values: after mount the
56
+ // overlay/zoom-layer styles are owned exclusively by the imperative per-frame writes. If the JSX
57
+ // re-rendered them from live props (a parent may persist and echo the transform back through
58
+ // `initialPan`/`initialZoom`), a React commit racing a gesture would rewrite the styles with
59
+ // stale values — visible as the viewport flicking to an old position and back.
60
+ const initialTransformRef = useRef({ pan: initialPan, zoom: initialZoom });
61
+ // Wheel gestures coalesce onto animation frames (trackpads deliver several events per frame, and each
62
+ // direct style write would reflow), and while a gesture is in motion the zoom layer stays on the
63
+ // composited transform path with virtualization told to hold still. The settle timer restores the crisp
64
+ // resting state shortly after the last wheel event.
65
+ const gestureActiveRef = useRef(false);
66
+ const gestureSettleTimerRef = useRef(undefined);
67
+ const transformFramePendingRef = useRef(false);
68
+ const isPanningRef = useRef(false);
69
+ const lastPointerRef = useRef({ x: 0, y: 0 });
70
+ // Direct DOM write, not React state — the container's base cursor is 'default' (StudioIssues#222:
71
+ // an idle background shouldn't look grabbable until you actually start dragging it), and swapping
72
+ // to 'grabbing' for the life of a drag-pan is exactly the kind of per-gesture churn this file
73
+ // already avoids re-rendering for elsewhere (see the transform writes below).
74
+ const setPanCursor = useCallback((panning) => {
75
+ if (containerRef.current)
76
+ containerRef.current.style.cursor = panning ? 'grabbing' : 'default';
77
+ }, []);
78
+ // Touch pointers currently down on the canvas, in the order they landed, plus the pinch geometry
79
+ // they had on the previous move. Touch is the only input that can deliver several simultaneous
80
+ // pointers, so mouse/pen never enter this map and keep the single-pointer drag path below.
81
+ const touchPointersRef = useRef(new Map());
82
+ const pinchRef = useRef(null);
83
+ // Recent positions of the single finger currently panning, newest last, trimmed to
84
+ // MOMENTUM_SAMPLE_WINDOW_MS — the trailing window startTouchMomentum reads the release velocity from.
85
+ const touchPanSamplesRef = useRef([]);
86
+ // Keep callback refs current to avoid stale closures in PIXI event handlers
87
+ const onReadyRef = useRef(onReady);
88
+ const onItemPointerDownRef = useRef(onItemPointerDown);
89
+ const onTransformChangeRef = useRef(onTransformChange);
90
+ const onHandleReadyRef = useRef(onHandleReady);
91
+ const itemsRef = useRef(items);
92
+ useEffect(() => { onReadyRef.current = onReady; }, [onReady]);
93
+ useEffect(() => { onItemPointerDownRef.current = onItemPointerDown; }, [onItemPointerDown]);
94
+ useEffect(() => { onTransformChangeRef.current = onTransformChange; }, [onTransformChange]);
95
+ useEffect(() => { onHandleReadyRef.current = onHandleReady; }, [onHandleReady]);
96
+ useEffect(() => {
97
+ itemsRef.current = items;
98
+ }, [items]);
99
+ // ── Item registry — auto-builds minimap items from CanvasItem children ──
100
+ const itemRegistryRef = useRef(new Map());
101
+ const [itemRegistryVersion, setItemRegistryVersion] = useState(0);
102
+ const registerItem = useCallback((id, entry) => {
103
+ const existing = itemRegistryRef.current.get(id);
104
+ if (existing &&
105
+ existing.x === entry.x && existing.y === entry.y &&
106
+ existing.width === entry.width && existing.height === entry.height)
107
+ return;
108
+ itemRegistryRef.current.set(id, entry);
109
+ setItemRegistryVersion(version => version + 1);
110
+ }, []);
111
+ const unregisterItem = useCallback((id) => {
112
+ itemRegistryRef.current.delete(id);
113
+ setItemRegistryVersion(version => version + 1);
114
+ }, []);
115
+ const registryContextValue = useMemo(() => ({
116
+ register: registerItem,
117
+ unregister: unregisterItem,
118
+ }), [registerItem, unregisterItem]);
119
+ const autoMinimapItems = useMemo(() => {
120
+ const result = [];
121
+ itemRegistryRef.current.forEach(entry => {
122
+ result.push({ x: entry.x, y: entry.y, width: entry.width, height: entry.height });
123
+ });
124
+ return result;
125
+ }, [itemRegistryVersion]);
126
+ const effectiveMinimapItems = minimapItems ?? autoMinimapItems;
127
+ const refreshMinimap = useCallback(() => {
128
+ const app = appRef.current;
129
+ if (!app || !minimapRef.current)
130
+ return;
131
+ minimapRef.current.update(panRef.current, zoomRef.current, app.renderer.width, app.renderer.height);
132
+ }, []);
133
+ const applyWorldTransform = useCallback(() => {
134
+ const world = worldRef.current;
135
+ if (!world)
136
+ return;
137
+ world.position.set(panRef.current.x, panRef.current.y);
138
+ world.scale.set(zoomRef.current);
139
+ // Pan is a CSS transform on the outer overlay; zoom is applied to an inner layer. The zoom
140
+ // mechanism is hybrid, split at 100% (the two are identical there, so the switch is seamless):
141
+ // - Above 100% we use CSS `zoom`, which re-lays-out and re-rasterizes at the effective
142
+ // resolution — `transform: scale()` would rasterize once at 1x and stretch, blurring text
143
+ // when UPSCALING in Safari. EXCEPT on multi-touch-capable devices (iPad and friends), which
144
+ // always use `transform: scale()` even above 100% — see zoomMechanism.ts for why.
145
+ // - At or below 100% we use `transform: scale()`, which is GPU-composited: zooming out stays
146
+ // smooth (no per-frame full-board reflow) and text scales down properly. CSS `zoom` instead
147
+ // reflows every step (jerky) and clamps font-size to a floor when zoomed out, so labels stop
148
+ // shrinking and word-wrap. Downscaling never blurs, so the crispness reason does not apply here.
149
+ if (overlayRef.current) {
150
+ overlayRef.current.style.transform = `translate(${panRef.current.x}px, ${panRef.current.y}px)`;
151
+ }
152
+ if (zoomLayerRef.current) {
153
+ applyZoomLayer(zoomLayerRef.current, zoomRef.current, gestureActiveRef.current);
154
+ }
155
+ onTransformChangeRef.current?.(zoomRef.current, panRef.current);
156
+ // Every applied transform frame is announced so followers (cursors, selection toolbar, tour
157
+ // anchors) can ride the transform instead of polling it on their own animation-frame loops.
158
+ canvasTransformActivity.notify();
159
+ }, []);
160
+ const render = useCallback(() => {
161
+ const app = appRef.current;
162
+ if (!app)
163
+ return;
164
+ // Boards that only use the HTML overlay leave the PIXI world empty — skip the per-frame
165
+ // GPU pass entirely rather than clearing and presenting an empty stage on every gesture frame.
166
+ if ((worldRef.current?.children.length ?? 0) === 0)
167
+ return;
168
+ // The system ticker is stopped (see the init effect below) — drive pixi's scheduled
169
+ // housekeeping (texture GC and friends) from the frames that actually render instead.
170
+ PIXI.Ticker.system.update();
171
+ app.renderer.render(app.stage);
172
+ }, []);
173
+ // Initialize PIXI once
174
+ useEffect(() => {
175
+ const container = containerRef.current;
176
+ if (!container || appRef.current)
177
+ return;
178
+ let mounted = true;
179
+ (async () => {
180
+ const rect = container.getBoundingClientRect();
181
+ const width = rect.width > 0 ? rect.width : container.clientWidth || 800;
182
+ const height = rect.height > 0 ? rect.height : container.clientHeight || 600;
183
+ const app = new PIXI.Application();
184
+ await app.init({
185
+ // Transparent clear: the body carries the surface color and the user-selected
186
+ // appearance background, which must show through the canvas like everywhere else.
187
+ backgroundAlpha: 0,
188
+ antialias: true,
189
+ autoDensity: true,
190
+ resolution: window.devicePixelRatio || 1,
191
+ width,
192
+ height,
193
+ autoStart: false,
194
+ });
195
+ if (!mounted || !containerRef.current) {
196
+ app.destroy(true, { children: true });
197
+ return;
198
+ }
199
+ appRef.current = app;
200
+ // World container - all items live here; zoom/pan applied via transform
201
+ const world = new PIXI.Container();
202
+ world.position.set(panRef.current.x, panRef.current.y);
203
+ world.scale.set(zoomRef.current);
204
+ app.stage.addChild(world);
205
+ worldRef.current = world;
206
+ const canvas = app.canvas;
207
+ canvas.style.display = 'block';
208
+ canvas.style.touchAction = 'none';
209
+ // WebGL canvases rasterize as opaque black in the glass capture pipeline - exclude
210
+ // this one from every captured scene so glass never refracts a black rectangle.
211
+ canvas.setAttribute(LAYER_ATTR, 'true');
212
+ containerRef.current.appendChild(canvas);
213
+ setPixiReady(true);
214
+ app.renderer.render(app.stage);
215
+ // Initializing a renderer hooks pixi's SchedulerSystem onto the auto-starting system
216
+ // ticker, which then runs a requestAnimationFrame loop forever even though nothing here
217
+ // renders from a ticker (autoStart is false and every render is explicit) — one of the
218
+ // permanent loops that kept an idle board page busy (StudioIssues#200). Stop it after
219
+ // every init, because each new renderer restarts it; render() drives the scheduled
220
+ // housekeeping instead.
221
+ PIXI.Ticker.system.stop();
222
+ onReadyRef.current?.({ app, world });
223
+ })();
224
+ return () => {
225
+ mounted = false;
226
+ if (appRef.current) {
227
+ appRef.current.destroy(true, { children: true });
228
+ appRef.current = null;
229
+ worldRef.current = null;
230
+ }
231
+ spritesRef.current.clear();
232
+ };
233
+ }, []); // intentional: PIXI init runs exactly once
234
+ // Handle container resize
235
+ useEffect(() => {
236
+ const container = containerRef.current;
237
+ if (!container)
238
+ return;
239
+ const observer = new ResizeObserver(() => {
240
+ const app = appRef.current;
241
+ if (!app)
242
+ return;
243
+ app.renderer.resize(container.clientWidth, container.clientHeight);
244
+ render();
245
+ });
246
+ observer.observe(container);
247
+ return () => observer.disconnect();
248
+ }, [render]);
249
+ // One transform apply per animation frame, no matter how many wheel events arrived - the refs already
250
+ // hold the latest pan/zoom, so coalescing loses nothing and avoids multiple reflows per frame.
251
+ // No React state is touched here: a gesture must cause zero Canvas re-renders (the controls'
252
+ // zoom readout polls the ref on its own slow interval instead).
253
+ const scheduleTransformApply = useCallback(() => {
254
+ if (transformFramePendingRef.current)
255
+ return;
256
+ transformFramePendingRef.current = true;
257
+ requestAnimationFrame(() => {
258
+ transformFramePendingRef.current = false;
259
+ applyWorldTransform();
260
+ refreshMinimap();
261
+ render();
262
+ });
263
+ }, [applyWorldTransform, refreshMinimap, render]);
264
+ // Marks the gesture active (composited zoom path, virtualization held still) and re-arms the settle
265
+ // timer. On settle the crisp resting hybrid is re-applied through the same applyWorldTransform as
266
+ // every gesture frame — one writer, one atomic style pass — and a new gesture cancels the timer,
267
+ // so a settle can never land in the middle of the next gesture.
268
+ const noteGestureActivity = useCallback(() => {
269
+ gestureActiveRef.current = true;
270
+ canvasGesture.set(true);
271
+ clearTimeout(gestureSettleTimerRef.current);
272
+ gestureSettleTimerRef.current = setTimeout(() => {
273
+ gestureActiveRef.current = false;
274
+ applyWorldTransform();
275
+ canvasGesture.set(false);
276
+ }, GESTURE_SETTLE_MS);
277
+ }, [applyWorldTransform]);
278
+ useEffect(() => () => {
279
+ clearTimeout(gestureSettleTimerRef.current);
280
+ canvasGesture.set(false);
281
+ }, []);
282
+ // Zooms toward a fixed focus point on the container, holding the world point under it still —
283
+ // the one zoom every input path shares, whether the factor came from a wheel delta or a pinch.
284
+ const zoomTowards = useCallback((focusX, focusY, factor) => {
285
+ const newZoom = Math.max(minZoom, Math.min(maxZoom, zoomRef.current * factor));
286
+ const worldX = (focusX - panRef.current.x) / zoomRef.current;
287
+ const worldY = (focusY - panRef.current.y) / zoomRef.current;
288
+ panRef.current = { x: focusX - worldX * newZoom, y: focusY - worldY * newZoom };
289
+ zoomRef.current = newZoom;
290
+ noteGestureActivity();
291
+ scheduleTransformApply();
292
+ }, [minZoom, maxZoom, noteGestureActivity, scheduleTransformApply]);
293
+ // Wheel: pan (scroll) or zoom (Ctrl+scroll / pinch)
294
+ const handleWheel = useCallback((e) => {
295
+ const container = containerRef.current;
296
+ if (!container)
297
+ return;
298
+ // A plain scroll/trackpad gesture landing over a scrollable overlay (a chat's message list
299
+ // and similar) scrolls that content instead of panning the board underneath it
300
+ // (StudioIssues#229). A zoom gesture (ctrl/cmd) always wins — pinch-to-zoom over a chat
301
+ // panel is still a zoom, not a captured scroll.
302
+ if (!(e.ctrlKey || e.metaKey) && isWithinScrollableContent(e.target, container))
303
+ return;
304
+ e.preventDefault();
305
+ // A fresh gesture always wins over a still-coasting one from the last touch pan.
306
+ if (animationFrameRef.current !== null) {
307
+ cancelAnimationFrame(animationFrameRef.current);
308
+ animationFrameRef.current = null;
309
+ }
310
+ const rect = container.getBoundingClientRect();
311
+ const mouseX = e.clientX - rect.left;
312
+ const mouseY = e.clientY - rect.top;
313
+ if (e.ctrlKey || e.metaKey) {
314
+ zoomTowards(mouseX, mouseY, Math.exp(-e.deltaY * ZOOM_INTENSITY));
315
+ }
316
+ else {
317
+ panRef.current = { x: panRef.current.x - e.deltaX, y: panRef.current.y - e.deltaY };
318
+ noteGestureActivity();
319
+ scheduleTransformApply();
320
+ }
321
+ }, [zoomTowards, noteGestureActivity, scheduleTransformApply]);
322
+ useEffect(() => {
323
+ const container = containerRef.current;
324
+ if (!container)
325
+ return;
326
+ container.addEventListener('wheel', handleWheel, { passive: false });
327
+ return () => container.removeEventListener('wheel', handleWheel);
328
+ }, [handleWheel]);
329
+ // Safari/WebKit reports a trackpad pinch through its own non-standard gesture events —
330
+ // including on iPadOS with a Magic Keyboard trackpad — separately from the wheel+ctrlKey path
331
+ // above, which Safari never fires for a trackpad pinch. Left unhandled, Safari's default action
332
+ // is to zoom the whole page instead of the canvas — but canceling that alone would swallow the
333
+ // pinch entirely, so gesturechange also drives the same zoom-towards-focus the ctrl+wheel path
334
+ // applies. A touchscreen pinch fires these gesture events too, alongside the pointer events the
335
+ // touch path already zooms from — with touch pointers down, this handler only cancels the page
336
+ // zoom and leaves the zooming to the touch path. The events don't exist outside WebKit, so all
337
+ // of this is a no-op everywhere else.
338
+ useEffect(() => {
339
+ const container = containerRef.current;
340
+ if (!container)
341
+ return;
342
+ let lastScale = 1;
343
+ const handleGestureStart = (event) => {
344
+ event.preventDefault();
345
+ lastScale = event.scale || 1;
346
+ };
347
+ const handleGestureChange = (event) => {
348
+ event.preventDefault();
349
+ if (touchPointersRef.current.size > 0)
350
+ return;
351
+ const gesture = event;
352
+ if (!gesture.scale || !lastScale)
353
+ return;
354
+ const rect = container.getBoundingClientRect();
355
+ zoomTowards(gesture.clientX - rect.left, gesture.clientY - rect.top, gesture.scale / lastScale);
356
+ lastScale = gesture.scale;
357
+ };
358
+ const handleGestureEnd = (event) => event.preventDefault();
359
+ // Explicitly non-passive: WebKit is free to treat an options-less gesture listener as
360
+ // passive, silently ignoring preventDefault() — which reads as "pinch zooms the browser
361
+ // page" on an iPad Magic Keyboard trackpad (StudioIssues#241, previously #73).
362
+ container.addEventListener('gesturestart', handleGestureStart, { passive: false });
363
+ container.addEventListener('gesturechange', handleGestureChange, { passive: false });
364
+ container.addEventListener('gestureend', handleGestureEnd, { passive: false });
365
+ return () => {
366
+ container.removeEventListener('gesturestart', handleGestureStart);
367
+ container.removeEventListener('gesturechange', handleGestureChange);
368
+ container.removeEventListener('gestureend', handleGestureEnd);
369
+ };
370
+ }, [zoomTowards]);
371
+ // The two handlers above only ever see events that bubble through the canvas surface's own DOM
372
+ // subtree — but a toolbar, panel, or minimap floating over the canvas is frequently portalled to
373
+ // document.body (or otherwise rendered as a sibling, not a descendant), so a pinch or Ctrl/Cmd+
374
+ // wheel with the cursor sitting over one of those never reaches the listeners above at all, and
375
+ // the browser is free to zoom the whole page instead (StudioIssues#228). Listen at the window
376
+ // level too, and go purely by screen position — no matter what element actually receives the
377
+ // event, if it lands within the canvas's own rectangle the browser's native zoom is cancelled.
378
+ // For events whose target sits inside the canvas subtree this only cancels the browser zoom —
379
+ // the container's own listeners drive the canvas zoom, so applying it here too would double up.
380
+ // For a pinch whose target is a portalled sibling (never reaching the container listeners), it
381
+ // also drives the same zoom-towards-focus, so a pinch over a floating panel zooms the board
382
+ // instead of dying with the browser zoom cancelled (StudioIssues#241).
383
+ useEffect(() => {
384
+ const isWithinCanvas = (x, y) => {
385
+ const rect = containerRef.current?.getBoundingClientRect();
386
+ return !!rect && x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
387
+ };
388
+ const reachesContainerListeners = (event) => event.target instanceof Node && !!containerRef.current?.contains(event.target);
389
+ const handleWindowWheel = (event) => {
390
+ if ((event.ctrlKey || event.metaKey) && isWithinCanvas(event.clientX, event.clientY)) {
391
+ event.preventDefault();
392
+ }
393
+ };
394
+ let lastScale = 1;
395
+ const handleWindowGestureStart = (event) => {
396
+ const gesture = event;
397
+ if (!isWithinCanvas(gesture.clientX, gesture.clientY))
398
+ return;
399
+ event.preventDefault();
400
+ lastScale = gesture.scale || 1;
401
+ };
402
+ const handleWindowGestureChange = (event) => {
403
+ const gesture = event;
404
+ if (!isWithinCanvas(gesture.clientX, gesture.clientY))
405
+ return;
406
+ event.preventDefault();
407
+ if (reachesContainerListeners(event))
408
+ return;
409
+ if (touchPointersRef.current.size > 0)
410
+ return;
411
+ if (!gesture.scale || !lastScale)
412
+ return;
413
+ const rect = containerRef.current?.getBoundingClientRect();
414
+ if (!rect)
415
+ return;
416
+ zoomTowards(gesture.clientX - rect.left, gesture.clientY - rect.top, gesture.scale / lastScale);
417
+ lastScale = gesture.scale;
418
+ };
419
+ const handleWindowGestureEnd = (event) => {
420
+ const gesture = event;
421
+ if (isWithinCanvas(gesture.clientX, gesture.clientY))
422
+ event.preventDefault();
423
+ };
424
+ // Non-passive everywhere: WebKit treating any of these as passive is what lets the page
425
+ // zoom through, and window-level listeners are exactly where it is most inclined to.
426
+ window.addEventListener('wheel', handleWindowWheel, { passive: false });
427
+ window.addEventListener('gesturestart', handleWindowGestureStart, { passive: false });
428
+ window.addEventListener('gesturechange', handleWindowGestureChange, { passive: false });
429
+ window.addEventListener('gestureend', handleWindowGestureEnd, { passive: false });
430
+ return () => {
431
+ window.removeEventListener('wheel', handleWindowWheel);
432
+ window.removeEventListener('gesturestart', handleWindowGestureStart);
433
+ window.removeEventListener('gesturechange', handleWindowGestureChange);
434
+ window.removeEventListener('gestureend', handleWindowGestureEnd);
435
+ };
436
+ }, [zoomTowards]);
437
+ // Touch: two fingers pan and zoom together, anywhere on the surface — the trackpad's
438
+ // wheel/ctrl+wheel equivalent for a tablet. The world point under the pinch midpoint stays put
439
+ // while the midpoint itself drags the board, so zooming and panning feel like one gesture.
440
+ const applyPinch = useCallback(() => {
441
+ const container = containerRef.current;
442
+ const snapshot = pinchSnapshotOf(touchPointersRef.current.values());
443
+ if (!container || !snapshot)
444
+ return;
445
+ const previous = pinchRef.current;
446
+ pinchRef.current = snapshot;
447
+ // The first move of a gesture only establishes the baseline — there is nothing to move yet.
448
+ if (!previous)
449
+ return;
450
+ const change = pinchChangeBetween(previous, snapshot);
451
+ const rect = container.getBoundingClientRect();
452
+ const focusX = snapshot.center.x - rect.left;
453
+ const focusY = snapshot.center.y - rect.top;
454
+ const newZoom = Math.max(minZoom, Math.min(maxZoom, zoomRef.current * change.scale));
455
+ // The world point that was under the midpoint BEFORE this step is the one being held, so the
456
+ // midpoint's own movement is what pans — exactly as zoom-towards-cursor does for the wheel.
457
+ const worldX = (focusX - change.panX - panRef.current.x) / zoomRef.current;
458
+ const worldY = (focusY - change.panY - panRef.current.y) / zoomRef.current;
459
+ panRef.current = { x: focusX - worldX * newZoom, y: focusY - worldY * newZoom };
460
+ zoomRef.current = newZoom;
461
+ noteGestureActivity();
462
+ scheduleTransformApply();
463
+ }, [minZoom, maxZoom, noteGestureActivity, scheduleTransformApply]);
464
+ // Coasts the board after a touch pan release, decaying the release velocity to a stop — the
465
+ // manual equivalent of the momentum a trackpad's own wheel-event stream gives panning for free.
466
+ // Shares animationFrameRef with smoothPanToWorld/smoothPanZoomToWorld so only one of the three
467
+ // ever drives the transform at once; each cancels whichever of the others was still running.
468
+ const startTouchMomentum = useCallback(() => {
469
+ const initialVelocity = velocityFromSamples(touchPanSamplesRef.current, MOMENTUM_MIN_VELOCITY);
470
+ if (!initialVelocity)
471
+ return;
472
+ if (animationFrameRef.current !== null) {
473
+ cancelAnimationFrame(animationFrameRef.current);
474
+ animationFrameRef.current = null;
475
+ }
476
+ let velocity = initialVelocity;
477
+ let lastFrameTime = performance.now();
478
+ const animate = (currentTime) => {
479
+ const frameElapsedMs = currentTime - lastFrameTime;
480
+ lastFrameTime = currentTime;
481
+ panRef.current = {
482
+ x: panRef.current.x + velocity.x * frameElapsedMs,
483
+ y: panRef.current.y + velocity.y * frameElapsedMs,
484
+ };
485
+ velocity = decayVelocity(velocity, frameElapsedMs);
486
+ applyWorldTransform();
487
+ refreshMinimap();
488
+ render();
489
+ noteGestureActivity();
490
+ if (Math.hypot(velocity.x, velocity.y) > MOMENTUM_MIN_VELOCITY) {
491
+ animationFrameRef.current = requestAnimationFrame(animate);
492
+ }
493
+ else {
494
+ animationFrameRef.current = null;
495
+ }
496
+ };
497
+ animationFrameRef.current = requestAnimationFrame(animate);
498
+ }, [applyWorldTransform, refreshMinimap, render, noteGestureActivity]);
499
+ // Pointer: pan via middle mouse, left-click on empty background, or a single finger on it
500
+ const handlePointerDown = useCallback((e) => {
501
+ // A fresh gesture always wins over a still-coasting one from the last touch pan.
502
+ if (animationFrameRef.current !== null) {
503
+ cancelAnimationFrame(animationFrameRef.current);
504
+ animationFrameRef.current = null;
505
+ }
506
+ if (e.pointerType === 'touch') {
507
+ touchPointersRef.current.set(e.pointerId, { x: e.clientX, y: e.clientY });
508
+ if (touchPointersRef.current.size > 1) {
509
+ // A second finger turns whatever was happening into a pinch: end the one-finger drag
510
+ // so the two paths never both move the board in the same frame.
511
+ isPanningRef.current = false;
512
+ setPanCursor(false);
513
+ pinchRef.current = pinchSnapshotOf(touchPointersRef.current.values());
514
+ return;
515
+ }
516
+ }
517
+ const isBackground = isBackgroundPointerTarget(e.pointerType, readOnly, e.target, containerRef.current);
518
+ // A host that claims the background drag for itself only gives up the mouse/pen gesture: touch
519
+ // has no middle button and no wheel, so one finger must keep panning or the board is stuck.
520
+ const mayDragPan = backgroundDragPans || e.pointerType === 'touch';
521
+ if (e.button === 1 || (e.button === 0 && isBackground && mayDragPan)) {
522
+ if (e.button === 1)
523
+ e.preventDefault();
524
+ isPanningRef.current = true;
525
+ setPanCursor(true);
526
+ lastPointerRef.current = { x: e.clientX, y: e.clientY };
527
+ if (e.pointerType === 'touch') {
528
+ touchPanSamplesRef.current = [{ x: e.clientX, y: e.clientY, time: performance.now() }];
529
+ }
530
+ e.currentTarget.setPointerCapture(e.pointerId);
531
+ }
532
+ }, [readOnly, backgroundDragPans, setPanCursor]);
533
+ const handlePointerMove = useCallback((e) => {
534
+ if (e.pointerType === 'touch' && touchPointersRef.current.has(e.pointerId)) {
535
+ touchPointersRef.current.set(e.pointerId, { x: e.clientX, y: e.clientY });
536
+ if (touchPointersRef.current.size > 1) {
537
+ applyPinch();
538
+ return;
539
+ }
540
+ }
541
+ if (!isPanningRef.current)
542
+ return;
543
+ const deltaX = e.clientX - lastPointerRef.current.x;
544
+ const deltaY = e.clientY - lastPointerRef.current.y;
545
+ lastPointerRef.current = { x: e.clientX, y: e.clientY };
546
+ panRef.current = { x: panRef.current.x + deltaX, y: panRef.current.y + deltaY };
547
+ if (e.pointerType === 'touch') {
548
+ const samples = [...touchPanSamplesRef.current, { x: e.clientX, y: e.clientY, time: performance.now() }];
549
+ touchPanSamplesRef.current = trimSamples(samples, MOMENTUM_SAMPLE_WINDOW_MS);
550
+ }
551
+ // Same gesture path as wheel pan: coalesce onto one rAF and signal the gesture so
552
+ // virtualization holds still during a drag-pan too.
553
+ noteGestureActivity();
554
+ scheduleTransformApply();
555
+ }, [applyPinch, noteGestureActivity, scheduleTransformApply]);
556
+ const handlePointerUp = useCallback((e) => {
557
+ if (touchPointersRef.current.delete(e.pointerId)) {
558
+ // Re-baseline on the fingers that are left: keeping the old geometry would register the
559
+ // lifted finger's absence as a huge pinch/pan step on the next move.
560
+ pinchRef.current = pinchSnapshotOf(touchPointersRef.current.values());
561
+ const [remaining] = [...touchPointersRef.current.values()];
562
+ if (remaining) {
563
+ // Lifting back down to one finger continues the same gesture as a plain drag-pan
564
+ // rather than stopping dead until the user lifts and starts over.
565
+ isPanningRef.current = true;
566
+ setPanCursor(true);
567
+ lastPointerRef.current = { x: remaining.x, y: remaining.y };
568
+ touchPanSamplesRef.current = [{ x: remaining.x, y: remaining.y, time: performance.now() }];
569
+ return;
570
+ }
571
+ }
572
+ const wasPanningTouch = e.pointerType === 'touch' && isPanningRef.current;
573
+ isPanningRef.current = false;
574
+ setPanCursor(false);
575
+ if (wasPanningTouch)
576
+ startTouchMomentum();
577
+ }, [startTouchMomentum, setPanCursor]);
578
+ // Sync items → PIXI containers
579
+ useEffect(() => {
580
+ const world = worldRef.current;
581
+ if (!pixiReady || !world || !renderItem)
582
+ return;
583
+ const incoming = new Map(items.map(item => [item.id, item]));
584
+ // Remove deleted items
585
+ spritesRef.current.forEach((container, id) => {
586
+ if (!incoming.has(id)) {
587
+ world.removeChild(container);
588
+ container.destroy({ children: true });
589
+ spritesRef.current.delete(id);
590
+ }
591
+ });
592
+ // Add new items; update positions of existing ones
593
+ items.forEach(item => {
594
+ const existing = spritesRef.current.get(item.id);
595
+ if (existing) {
596
+ existing.position.set(item.x, item.y);
597
+ // Refresh handler so it always references the latest item object
598
+ existing.removeAllListeners('pointerdown');
599
+ existing.on('pointerdown', (event) => {
600
+ onItemPointerDownRef.current?.(item, event);
601
+ });
602
+ }
603
+ else {
604
+ const container = renderItem(item);
605
+ container.position.set(item.x, item.y);
606
+ container.eventMode = 'static';
607
+ container.on('pointerdown', (event) => {
608
+ onItemPointerDownRef.current?.(item, event);
609
+ });
610
+ world.addChild(container);
611
+ spritesRef.current.set(item.id, container);
612
+ }
613
+ });
614
+ // Render directly (not via the empty-world-skipping render()) so removing the last item
615
+ // still clears its pixels from the canvas.
616
+ appRef.current?.renderer.render(appRef.current.stage);
617
+ }, [pixiReady, items, renderItem]);
618
+ // Controls handlers
619
+ const handleZoomIn = useCallback(() => {
620
+ const container = containerRef.current;
621
+ if (!container)
622
+ return;
623
+ const cx = container.clientWidth / 2;
624
+ const cy = container.clientHeight / 2;
625
+ const factor = 1.2;
626
+ const newZoom = Math.min(maxZoom, zoomRef.current * factor);
627
+ const worldX = (cx - panRef.current.x) / zoomRef.current;
628
+ const worldY = (cy - panRef.current.y) / zoomRef.current;
629
+ panRef.current = { x: cx - worldX * newZoom, y: cy - worldY * newZoom };
630
+ zoomRef.current = newZoom;
631
+ applyWorldTransform();
632
+ refreshMinimap();
633
+ render();
634
+ }, [maxZoom, applyWorldTransform, refreshMinimap, render]);
635
+ const handleZoomOut = useCallback(() => {
636
+ const container = containerRef.current;
637
+ if (!container)
638
+ return;
639
+ const cx = container.clientWidth / 2;
640
+ const cy = container.clientHeight / 2;
641
+ const factor = 1.2;
642
+ const newZoom = Math.max(minZoom, zoomRef.current / factor);
643
+ const worldX = (cx - panRef.current.x) / zoomRef.current;
644
+ const worldY = (cy - panRef.current.y) / zoomRef.current;
645
+ panRef.current = { x: cx - worldX * newZoom, y: cy - worldY * newZoom };
646
+ zoomRef.current = newZoom;
647
+ applyWorldTransform();
648
+ refreshMinimap();
649
+ render();
650
+ }, [minZoom, applyWorldTransform, refreshMinimap, render]);
651
+ const handleZoomReset = useCallback(() => {
652
+ zoomRef.current = 1;
653
+ applyWorldTransform();
654
+ refreshMinimap();
655
+ render();
656
+ }, [applyWorldTransform, refreshMinimap, render]);
657
+ const handleMinimapPan = useCallback((pan) => {
658
+ panRef.current = pan;
659
+ applyWorldTransform();
660
+ refreshMinimap();
661
+ render();
662
+ }, [applyWorldTransform, refreshMinimap, render]);
663
+ const smoothPanToWorld = useCallback((worldX, worldY, durationMs = 600) => {
664
+ const container = containerRef.current;
665
+ if (!container)
666
+ return;
667
+ if (animationFrameRef.current !== null) {
668
+ cancelAnimationFrame(animationFrameRef.current);
669
+ animationFrameRef.current = null;
670
+ }
671
+ const viewportWidth = container.clientWidth;
672
+ const viewportHeight = container.clientHeight;
673
+ const targetPanX = viewportWidth / 2 - worldX * zoomRef.current;
674
+ const targetPanY = viewportHeight / 2 - worldY * zoomRef.current;
675
+ const startPanX = panRef.current.x;
676
+ const startPanY = panRef.current.y;
677
+ const startTime = performance.now();
678
+ const animate = (currentTime) => {
679
+ const elapsed = currentTime - startTime;
680
+ const progress = Math.min(elapsed / durationMs, 1);
681
+ const eased = easeInOutCubic(progress);
682
+ panRef.current = {
683
+ x: startPanX + (targetPanX - startPanX) * eased,
684
+ y: startPanY + (targetPanY - startPanY) * eased,
685
+ };
686
+ applyWorldTransform();
687
+ refreshMinimap();
688
+ render();
689
+ if (progress < 1) {
690
+ animationFrameRef.current = requestAnimationFrame(animate);
691
+ }
692
+ else {
693
+ animationFrameRef.current = null;
694
+ }
695
+ };
696
+ animationFrameRef.current = requestAnimationFrame(animate);
697
+ }, [applyWorldTransform, refreshMinimap, render]);
698
+ const smoothPanZoomToWorld = useCallback((worldX, worldY, targetZoom = 1, durationMs = 600) => {
699
+ const container = containerRef.current;
700
+ if (!container)
701
+ return;
702
+ if (animationFrameRef.current !== null) {
703
+ cancelAnimationFrame(animationFrameRef.current);
704
+ animationFrameRef.current = null;
705
+ }
706
+ const viewportWidth = container.clientWidth;
707
+ const viewportHeight = container.clientHeight;
708
+ const startZoom = zoomRef.current;
709
+ const endZoom = Math.min(maxZoom, Math.max(minZoom, targetZoom));
710
+ const startPanX = panRef.current.x;
711
+ const startPanY = panRef.current.y;
712
+ // Final pan that centers the world point at the target zoom.
713
+ const endPanX = viewportWidth / 2 - worldX * endZoom;
714
+ const endPanY = viewportHeight / 2 - worldY * endZoom;
715
+ const startTime = performance.now();
716
+ const animate = (currentTime) => {
717
+ const elapsed = currentTime - startTime;
718
+ const progress = Math.min(elapsed / durationMs, 1);
719
+ const eased = easeInOutCubic(progress);
720
+ zoomRef.current = startZoom + (endZoom - startZoom) * eased;
721
+ panRef.current = {
722
+ x: startPanX + (endPanX - startPanX) * eased,
723
+ y: startPanY + (endPanY - startPanY) * eased,
724
+ };
725
+ applyWorldTransform();
726
+ refreshMinimap();
727
+ render();
728
+ if (progress < 1) {
729
+ animationFrameRef.current = requestAnimationFrame(animate);
730
+ }
731
+ else {
732
+ animationFrameRef.current = null;
733
+ }
734
+ };
735
+ animationFrameRef.current = requestAnimationFrame(animate);
736
+ }, [maxZoom, minZoom, applyWorldTransform, refreshMinimap, render]);
737
+ const getContainerRect = useCallback(() => {
738
+ return containerRef.current?.getBoundingClientRect() ?? null;
739
+ }, []);
740
+ // Measured, not estimated: the registry holds what each CanvasItem's ResizeObserver last reported, so a
741
+ // fit-to-content computed from it includes everything an item really renders (specifications under the
742
+ // rows included), unlike the size estimates the minimap falls back to before anything has mounted.
743
+ const getItemBounds = useCallback(() => Array.from(itemRegistryRef.current.values(), entry => ({ x: entry.x, y: entry.y, width: entry.width, height: entry.height })), []);
744
+ // Expose imperative handle once the canvas is set up
745
+ useEffect(() => {
746
+ if (!pixiReady)
747
+ return;
748
+ onHandleReadyRef.current?.({ smoothPanToWorld, smoothPanZoomToWorld, getContainerRect, getItemBounds });
749
+ }, [pixiReady, smoothPanToWorld, smoothPanZoomToWorld, getContainerRect, getItemBounds]);
750
+ return (jsxs("div", { ref: containerRef, className: `canvas-surface relative overflow-hidden${className ? ` ${className}` : ''}`, style: { cursor: 'default', ...style }, onPointerDown: handlePointerDown, onPointerMove: handlePointerMove, onPointerUp: handlePointerUp, onPointerCancel: handlePointerUp, children: [jsx(CanvasItemRegistryContext.Provider, { value: registryContextValue, children: jsx("div", { ref: overlayRef, [TRANSFORM_HOST_ATTR]: 'true', style: {
751
+ position: 'absolute',
752
+ top: 0,
753
+ left: 0,
754
+ transformOrigin: '0 0',
755
+ transform: `translate(${initialTransformRef.current.pan.x}px, ${initialTransformRef.current.pan.y}px)`,
756
+ pointerEvents: 'none',
757
+ }, children: jsx("div", { ref: zoomLayerRef, [TRANSFORM_HOST_ATTR]: 'true',
758
+ // The read-only overlay below keeps pointers off the board; `inert` keeps the keyboard
759
+ // and assistive tech off it too, so nothing on a read-only board can be tabbed to and
760
+ // activated (a public viewer must never be able to fire a command).
761
+ inert: readOnly, style: shouldUseCssZoom(initialTransformRef.current.zoom, false, isMultiTouchCapableDevice)
762
+ ? { zoom: initialTransformRef.current.zoom }
763
+ : { transform: `scale(${initialTransformRef.current.zoom})`, transformOrigin: '0 0' }, children: children }) }) }), readOnly && (jsx("div", { style: { position: 'absolute', inset: 0, zIndex: 2, cursor: 'grab' } })), showControls && (jsx(CanvasControls, { getZoom: () => zoomRef.current, onZoomIn: handleZoomIn, onZoomOut: handleZoomOut, onZoomReset: handleZoomReset, showMinimapToggle: showMinimap, minimapRef: minimapRef, minimapWorldWidth: minimapWorldWidth, minimapWorldHeight: minimapWorldHeight, minimapItems: effectiveMinimapItems, onMinimapPan: handleMinimapPan, placement: controlsPlacement, onHelp: onHelp, helpTitle: helpTitle, disableGlass: disableControlsGlass }))] }));
764
+ }
765
+
766
+ export { Canvas, CanvasItemRegistryContext };
767
+ //# sourceMappingURL=Canvas.js.map