@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.
- package/README.md +148 -98
- package/dist/esm/Canvas/Canvas.d.ts +64 -0
- package/dist/esm/Canvas/Canvas.d.ts.map +1 -0
- package/dist/esm/Canvas/Canvas.js +767 -0
- package/dist/esm/Canvas/Canvas.js.map +1 -0
- package/dist/esm/Canvas/CanvasControls.d.ts +32 -0
- package/dist/esm/Canvas/CanvasControls.d.ts.map +1 -0
- package/dist/esm/Canvas/CanvasControls.js +34 -0
- package/dist/esm/Canvas/CanvasControls.js.map +1 -0
- package/dist/esm/Canvas/CanvasItem.d.ts +10 -0
- package/dist/esm/Canvas/CanvasItem.d.ts.map +1 -0
- package/dist/esm/Canvas/CanvasItem.js +51 -0
- package/dist/esm/Canvas/CanvasItem.js.map +1 -0
- package/dist/esm/Canvas/CanvasMinimap.d.ts +25 -0
- package/dist/esm/Canvas/CanvasMinimap.d.ts.map +1 -0
- package/dist/esm/Canvas/CanvasMinimap.js +102 -0
- package/dist/esm/Canvas/CanvasMinimap.js.map +1 -0
- package/dist/esm/Canvas/CanvasOverlay.d.ts +6 -0
- package/dist/esm/Canvas/CanvasOverlay.d.ts.map +1 -0
- package/dist/esm/Canvas/CanvasOverlay.js +8 -0
- package/dist/esm/Canvas/CanvasOverlay.js.map +1 -0
- package/dist/esm/Canvas/canvasGesture.d.ts +8 -0
- package/dist/esm/Canvas/canvasGesture.d.ts.map +1 -0
- package/dist/esm/Canvas/canvasGesture.js +32 -0
- package/dist/esm/Canvas/canvasGesture.js.map +1 -0
- package/dist/esm/Canvas/canvasTransformActivity.d.ts +7 -0
- package/dist/esm/Canvas/canvasTransformActivity.d.ts.map +1 -0
- package/dist/esm/Canvas/canvasTransformActivity.js +22 -0
- package/dist/esm/Canvas/canvasTransformActivity.js.map +1 -0
- package/dist/esm/Canvas/glassAttributes.d.ts +4 -0
- package/dist/esm/Canvas/glassAttributes.d.ts.map +1 -0
- package/dist/esm/Canvas/glassAttributes.js +25 -0
- package/dist/esm/Canvas/glassAttributes.js.map +1 -0
- package/dist/esm/Canvas/index.d.ts +18 -0
- package/dist/esm/Canvas/index.d.ts.map +1 -0
- package/dist/esm/Canvas/index.js +38 -0
- package/dist/esm/Canvas/index.js.map +1 -0
- package/dist/esm/Canvas/isBackgroundPointerTarget.d.ts +2 -0
- package/dist/esm/Canvas/isBackgroundPointerTarget.d.ts.map +1 -0
- package/dist/esm/Canvas/isBackgroundPointerTarget.js +17 -0
- package/dist/esm/Canvas/isBackgroundPointerTarget.js.map +1 -0
- package/dist/esm/Canvas/isWithinScrollableContent.d.ts +11 -0
- package/dist/esm/Canvas/isWithinScrollableContent.d.ts.map +1 -0
- package/dist/esm/Canvas/isWithinScrollableContent.js +38 -0
- package/dist/esm/Canvas/isWithinScrollableContent.js.map +1 -0
- package/dist/esm/Canvas/panMomentum.d.ts +13 -0
- package/dist/esm/Canvas/panMomentum.d.ts.map +1 -0
- package/dist/esm/Canvas/panMomentum.js +41 -0
- package/dist/esm/Canvas/panMomentum.js.map +1 -0
- package/dist/esm/Canvas/pinchGesture.d.ts +16 -0
- package/dist/esm/Canvas/pinchGesture.d.ts.map +1 -0
- package/dist/esm/Canvas/pinchGesture.js +30 -0
- package/dist/esm/Canvas/pinchGesture.js.map +1 -0
- package/dist/esm/Canvas/selfSuspendingFrameLoop.d.ts +10 -0
- package/dist/esm/Canvas/selfSuspendingFrameLoop.d.ts.map +1 -0
- package/dist/esm/Canvas/selfSuspendingFrameLoop.js +33 -0
- package/dist/esm/Canvas/selfSuspendingFrameLoop.js.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/AnchoredOverlay.d.ts +13 -0
- package/dist/esm/Canvas/shapes/ChatBubble/AnchoredOverlay.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/AnchoredOverlay.js +61 -0
- package/dist/esm/Canvas/shapes/ChatBubble/AnchoredOverlay.js.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Avatar/PersonAvatarCircle.d.ts +20 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Avatar/PersonAvatarCircle.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Avatar/PersonAvatarCircle.js +27 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Avatar/PersonAvatarCircle.js.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Avatar/getAvatarColor.d.ts +2 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Avatar/getAvatarColor.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Avatar/getAvatarColor.js +20 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Avatar/getAvatarColor.js.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Avatar/getInitials.d.ts +2 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Avatar/getInitials.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Avatar/getInitials.js +11 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Avatar/getInitials.js.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Avatar/index.d.ts +5 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Avatar/index.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Avatar/index.js +4 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Avatar/index.js.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Chat.d.ts +41 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Chat.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Chat.js +76 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Chat.js.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ChatAuthorKind.d.ts +5 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ChatAuthorKind.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ChatAuthorKind.js +14 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ChatAuthorKind.js.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ChatBubble.d.ts +18 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ChatBubble.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ChatBubble.js +89 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ChatBubble.js.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ChatComposer.d.ts +23 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ChatComposer.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ChatComposer.js +107 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ChatComposer.js.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ChatMessage.d.ts +17 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ChatMessage.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ChatMessage.js +2 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ChatMessage.js.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ChatMessageBubble.d.ts +25 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ChatMessageBubble.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ChatMessageBubble.js +31 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ChatMessageBubble.js.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ChatMessageReaction.d.ts +6 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ChatMessageReaction.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ChatMessageReaction.js +2 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ChatMessageReaction.js.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ChatMessageReactionUser.d.ts +7 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ChatMessageReactionUser.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ChatMessageReactionUser.js +2 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ChatMessageReactionUser.js.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ChatOwnReaction.d.ts +6 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ChatOwnReaction.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ChatOwnReaction.js +2 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ChatOwnReaction.js.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ChatTypingAuthor.d.ts +8 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ChatTypingAuthor.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ChatTypingAuthor.js +2 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ChatTypingAuthor.js.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ChatVariant.d.ts +5 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ChatVariant.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ChatVariant.js +15 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ChatVariant.js.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Emoji/EmojiCategory.d.ts +6 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Emoji/EmojiCategory.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Emoji/EmojiCategory.js +2 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Emoji/EmojiCategory.js.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Emoji/EmojiCategoryKey.d.ts +12 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Emoji/EmojiCategoryKey.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Emoji/EmojiCategoryKey.js +18 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Emoji/EmojiCategoryKey.js.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Emoji/EmojiMemory.d.ts +5 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Emoji/EmojiMemory.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Emoji/EmojiMemory.js +2 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Emoji/EmojiMemory.js.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Emoji/EmojiPicker.d.ts +14 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Emoji/EmojiPicker.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Emoji/EmojiPicker.js +51 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Emoji/EmojiPicker.js.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Emoji/emojiCatalog.d.ts +3 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Emoji/emojiCatalog.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Emoji/emojiCatalog.js +155 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Emoji/emojiCatalog.js.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Emoji/index.d.ts +7 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Emoji/index.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Emoji/index.js +7 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Emoji/index.js.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Emoji/recentEmojis.d.ts +6 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Emoji/recentEmojis.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Emoji/recentEmojis.js +59 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Emoji/recentEmojis.js.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/FailedReply.d.ts +23 -0
- package/dist/esm/Canvas/shapes/ChatBubble/FailedReply.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/FailedReply.js +28 -0
- package/dist/esm/Canvas/shapes/ChatBubble/FailedReply.js.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Mentions/MentionCandidate.d.ts +9 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Mentions/MentionCandidate.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Mentions/MentionCandidate.js +2 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Mentions/MentionCandidate.js.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Mentions/MentionQuery.d.ts +5 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Mentions/MentionQuery.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Mentions/MentionQuery.js +2 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Mentions/MentionQuery.js.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Mentions/MentionSuggestions.d.ts +11 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Mentions/MentionSuggestions.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Mentions/MentionSuggestions.js +14 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Mentions/MentionSuggestions.js.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Mentions/activeMentionQuery.d.ts +3 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Mentions/activeMentionQuery.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Mentions/activeMentionQuery.js +34 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Mentions/activeMentionQuery.js.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Mentions/applyMention.d.ts +7 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Mentions/applyMention.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Mentions/applyMention.js +23 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Mentions/applyMention.js.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Mentions/index.d.ts +7 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Mentions/index.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Mentions/index.js +7 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Mentions/index.js.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Mentions/matchCandidates.d.ts +3 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Mentions/matchCandidates.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Mentions/matchCandidates.js +24 -0
- package/dist/esm/Canvas/shapes/ChatBubble/Mentions/matchCandidates.js.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/MessageReactions.d.ts +6 -0
- package/dist/esm/Canvas/shapes/ChatBubble/MessageReactions.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/MessageReactions.js +28 -0
- package/dist/esm/Canvas/shapes/ChatBubble/MessageReactions.js.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ReactionPicker.d.ts +20 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ReactionPicker.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ReactionPicker.js +50 -0
- package/dist/esm/Canvas/shapes/ChatBubble/ReactionPicker.js.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/TypingIndicator.d.ts +9 -0
- package/dist/esm/Canvas/shapes/ChatBubble/TypingIndicator.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/TypingIndicator.js +18 -0
- package/dist/esm/Canvas/shapes/ChatBubble/TypingIndicator.js.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/findOwnReaction.d.ts +5 -0
- package/dist/esm/Canvas/shapes/ChatBubble/findOwnReaction.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/findOwnReaction.js +19 -0
- package/dist/esm/Canvas/shapes/ChatBubble/findOwnReaction.js.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/index.d.ts +31 -0
- package/dist/esm/Canvas/shapes/ChatBubble/index.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/index.js +17 -0
- package/dist/esm/Canvas/shapes/ChatBubble/index.js.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/reactionsExcludingUser.d.ts +4 -0
- package/dist/esm/Canvas/shapes/ChatBubble/reactionsExcludingUser.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/ChatBubble/reactionsExcludingUser.js +16 -0
- package/dist/esm/Canvas/shapes/ChatBubble/reactionsExcludingUser.js.map +1 -0
- package/dist/esm/Canvas/shapes/Note/Note.d.ts +22 -0
- package/dist/esm/Canvas/shapes/Note/Note.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/Note/Note.js +237 -0
- package/dist/esm/Canvas/shapes/Note/Note.js.map +1 -0
- package/dist/esm/Canvas/shapes/Note/index.d.ts +3 -0
- package/dist/esm/Canvas/shapes/Note/index.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/Note/index.js +2 -0
- package/dist/esm/Canvas/shapes/Note/index.js.map +1 -0
- package/dist/esm/Canvas/shapes/Note/noteFont.d.ts +2 -0
- package/dist/esm/Canvas/shapes/Note/noteFont.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/Note/noteFont.js +26 -0
- package/dist/esm/Canvas/shapes/Note/noteFont.js.map +1 -0
- package/dist/esm/Canvas/shapes/Region/Region.d.ts +22 -0
- package/dist/esm/Canvas/shapes/Region/Region.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/Region/Region.js +150 -0
- package/dist/esm/Canvas/shapes/Region/Region.js.map +1 -0
- package/dist/esm/Canvas/shapes/Region/index.d.ts +3 -0
- package/dist/esm/Canvas/shapes/Region/index.d.ts.map +1 -0
- package/dist/esm/Canvas/shapes/Region/index.js +2 -0
- package/dist/esm/Canvas/shapes/Region/index.js.map +1 -0
- package/dist/esm/Canvas/useDragSelectionGuard.d.ts +2 -0
- package/dist/esm/Canvas/useDragSelectionGuard.d.ts.map +1 -0
- package/dist/esm/Canvas/useDragSelectionGuard.js +59 -0
- package/dist/esm/Canvas/useDragSelectionGuard.js.map +1 -0
- package/dist/esm/Canvas/zoomMechanism.d.ts +4 -0
- package/dist/esm/Canvas/zoomMechanism.d.ts.map +1 -0
- package/dist/esm/Canvas/zoomMechanism.js +58 -0
- package/dist/esm/Canvas/zoomMechanism.js.map +1 -0
- package/dist/esm/CommandDialog/CommandDialog.d.ts +3 -6
- package/dist/esm/CommandDialog/CommandDialog.d.ts.map +1 -1
- package/dist/esm/CommandDialog/CommandDialog.js +3 -2
- package/dist/esm/CommandDialog/CommandDialog.js.map +1 -1
- package/dist/esm/CommandDialog/CommandStepper.d.ts.map +1 -1
- package/dist/esm/CommandDialog/CommandStepper.js +3 -2
- package/dist/esm/CommandDialog/CommandStepper.js.map +1 -1
- package/dist/esm/CommandForm/commandFormMarkers.d.ts +12 -0
- package/dist/esm/CommandForm/commandFormMarkers.d.ts.map +1 -0
- package/dist/esm/CommandForm/commandFormMarkers.js +69 -0
- package/dist/esm/CommandForm/commandFormMarkers.js.map +1 -0
- package/dist/esm/CommandForm/fields/CalendarField.d.ts +1 -1
- package/dist/esm/CommandForm/fields/CalendarField.d.ts.map +1 -1
- package/dist/esm/CommandForm/fields/CheckboxField.d.ts +1 -1
- package/dist/esm/CommandForm/fields/CheckboxField.d.ts.map +1 -1
- package/dist/esm/CommandForm/fields/ChipsField.d.ts +1 -1
- package/dist/esm/CommandForm/fields/ChipsField.d.ts.map +1 -1
- package/dist/esm/CommandForm/fields/ColorPickerField.d.ts +1 -1
- package/dist/esm/CommandForm/fields/ColorPickerField.d.ts.map +1 -1
- package/dist/esm/CommandForm/fields/DropdownField.d.ts +1 -1
- package/dist/esm/CommandForm/fields/DropdownField.d.ts.map +1 -1
- package/dist/esm/CommandForm/fields/InputTextField.d.ts +1 -1
- package/dist/esm/CommandForm/fields/InputTextField.d.ts.map +1 -1
- package/dist/esm/CommandForm/fields/MultiSelectField.d.ts +1 -1
- package/dist/esm/CommandForm/fields/MultiSelectField.d.ts.map +1 -1
- package/dist/esm/CommandForm/fields/NumberField.d.ts +1 -1
- package/dist/esm/CommandForm/fields/NumberField.d.ts.map +1 -1
- package/dist/esm/CommandForm/fields/PasswordField.d.ts +1 -1
- package/dist/esm/CommandForm/fields/PasswordField.d.ts.map +1 -1
- package/dist/esm/CommandForm/fields/RadioButtonField.d.ts +1 -1
- package/dist/esm/CommandForm/fields/RadioButtonField.d.ts.map +1 -1
- package/dist/esm/CommandForm/fields/RadioGroupField.d.ts +1 -1
- package/dist/esm/CommandForm/fields/RadioGroupField.d.ts.map +1 -1
- package/dist/esm/CommandForm/fields/RatingField.d.ts +1 -1
- package/dist/esm/CommandForm/fields/RatingField.d.ts.map +1 -1
- package/dist/esm/CommandForm/fields/SliderField.d.ts +1 -1
- package/dist/esm/CommandForm/fields/SliderField.d.ts.map +1 -1
- package/dist/esm/CommandForm/fields/TextAreaField.d.ts +1 -1
- package/dist/esm/CommandForm/fields/TextAreaField.d.ts.map +1 -1
- package/dist/esm/CommandForm/fields/ToggleSwitchField.d.ts +1 -1
- package/dist/esm/CommandForm/fields/ToggleSwitchField.d.ts.map +1 -1
- package/dist/esm/CommandForm/index.d.ts +1 -0
- package/dist/esm/CommandForm/index.d.ts.map +1 -1
- package/dist/esm/CommandForm/index.js +1 -0
- package/dist/esm/CommandForm/index.js.map +1 -1
- package/dist/esm/Common/CratisComponentsProvider.js +1 -1
- package/dist/esm/Common/DatePickerInput.d.ts +11 -4
- package/dist/esm/Common/DatePickerInput.d.ts.map +1 -1
- package/dist/esm/Common/DatePickerInput.js +18 -2
- package/dist/esm/Common/DatePickerInput.js.map +1 -1
- package/dist/esm/Common/ErrorBoundary.d.ts +1 -1
- package/dist/esm/Compatibility/PrimeReact11PassThroughComponent.d.ts +3 -0
- package/dist/esm/Compatibility/PrimeReact11PassThroughComponent.d.ts.map +1 -0
- package/dist/esm/Compatibility/PrimeReact11PassThroughComponent.js +2 -0
- package/dist/esm/Compatibility/PrimeReact11PassThroughComponent.js.map +1 -0
- package/dist/esm/Compatibility/assertPrimeReact11PassThroughCompatibility.d.ts +3 -0
- package/dist/esm/Compatibility/assertPrimeReact11PassThroughCompatibility.d.ts.map +1 -0
- package/dist/esm/Compatibility/assertPrimeReact11PassThroughCompatibility.js +49 -0
- package/dist/esm/Compatibility/assertPrimeReact11PassThroughCompatibility.js.map +1 -0
- package/dist/esm/Compatibility/components3PrimeReact11PassThroughContract.d.ts +369 -0
- package/dist/esm/Compatibility/components3PrimeReact11PassThroughContract.d.ts.map +1 -0
- package/dist/esm/Compatibility/components3PrimeReact11PassThroughContract.js +271 -0
- package/dist/esm/Compatibility/components3PrimeReact11PassThroughContract.js.map +1 -0
- package/dist/esm/Compatibility/index.d.ts +5 -0
- package/dist/esm/Compatibility/index.d.ts.map +1 -0
- package/dist/esm/Compatibility/index.js +7 -0
- package/dist/esm/Compatibility/index.js.map +1 -0
- package/dist/esm/Compatibility/primeReact11PassThroughSentinelPreset.d.ts +8 -0
- package/dist/esm/Compatibility/primeReact11PassThroughSentinelPreset.d.ts.map +1 -0
- package/dist/esm/Compatibility/primeReact11PassThroughSentinelPreset.js +48 -0
- package/dist/esm/Compatibility/primeReact11PassThroughSentinelPreset.js.map +1 -0
- package/dist/esm/DataPage/DataPage.d.ts +4 -4
- package/dist/esm/DataPage/DataPage.d.ts.map +1 -1
- package/dist/esm/DataPage/DataPage.js +6 -10
- package/dist/esm/DataPage/DataPage.js.map +1 -1
- package/dist/esm/DataTables/Column.d.ts +3 -1
- package/dist/esm/DataTables/Column.d.ts.map +1 -1
- package/dist/esm/DataTables/Column.js.map +1 -1
- package/dist/esm/DataTables/ColumnFilterMenu.d.ts +22 -2
- package/dist/esm/DataTables/ColumnFilterMenu.d.ts.map +1 -1
- package/dist/esm/DataTables/ColumnFilterMenu.js +50 -13
- package/dist/esm/DataTables/ColumnFilterMenu.js.map +1 -1
- package/dist/esm/DataTables/DataTableCore.d.ts.map +1 -1
- package/dist/esm/DataTables/DataTableCore.js +22 -3
- package/dist/esm/DataTables/DataTableCore.js.map +1 -1
- package/dist/esm/DataTables/DataTableFilterMatcherRegistry.d.ts +14 -0
- package/dist/esm/DataTables/DataTableFilterMatcherRegistry.d.ts.map +1 -0
- package/dist/esm/DataTables/DataTableFilterMatcherRegistry.js +67 -0
- package/dist/esm/DataTables/DataTableFilterMatcherRegistry.js.map +1 -0
- package/dist/esm/DataTables/DataTableFilterMeta.d.ts +26 -1
- package/dist/esm/DataTables/DataTableFilterMeta.d.ts.map +1 -1
- package/dist/esm/DataTables/DataTableFilterMeta.js +24 -2
- package/dist/esm/DataTables/DataTableFilterMeta.js.map +1 -1
- package/dist/esm/DataTables/DataTableForObservableQuery.d.ts +3 -3
- package/dist/esm/DataTables/DataTableForObservableQuery.d.ts.map +1 -1
- package/dist/esm/DataTables/DataTableForObservableQuery.js +9 -3
- package/dist/esm/DataTables/DataTableForObservableQuery.js.map +1 -1
- package/dist/esm/DataTables/DataTableForQuery.d.ts +3 -3
- package/dist/esm/DataTables/DataTableForQuery.d.ts.map +1 -1
- package/dist/esm/DataTables/DataTableForQuery.js +8 -2
- package/dist/esm/DataTables/DataTableForQuery.js.map +1 -1
- package/dist/esm/DataTables/TablePaginator.d.ts +1 -1
- package/dist/esm/DataTables/TablePaginator.d.ts.map +1 -1
- package/dist/esm/DataTables/TablePaginator.js +13 -4
- package/dist/esm/DataTables/TablePaginator.js.map +1 -1
- package/dist/esm/DataTables/index.d.ts +1 -0
- package/dist/esm/DataTables/index.d.ts.map +1 -1
- package/dist/esm/DataTables/index.js +3 -1
- package/dist/esm/DataTables/index.js.map +1 -1
- package/dist/esm/Dialogs/Dialog.d.ts +2 -2
- package/dist/esm/Dialogs/Dialog.d.ts.map +1 -1
- package/dist/esm/Dialogs/Dialog.js +16 -18
- package/dist/esm/Dialogs/Dialog.js.map +1 -1
- package/dist/esm/Dropdown/Dropdown.d.ts +4 -2
- package/dist/esm/Dropdown/Dropdown.d.ts.map +1 -1
- package/dist/esm/Dropdown/Dropdown.js +8 -3
- package/dist/esm/Dropdown/Dropdown.js.map +1 -1
- package/dist/esm/Filter/FilterPanel.d.ts.map +1 -1
- package/dist/esm/Filter/FilterPanel.js +1 -1
- package/dist/esm/Filter/FilterPanel.js.map +1 -1
- package/dist/esm/Filter/RangeHistogramFilter.d.ts +4 -3
- package/dist/esm/Filter/RangeHistogramFilter.d.ts.map +1 -1
- package/dist/esm/Filter/RangeHistogramFilter.js +4 -23
- package/dist/esm/Filter/RangeHistogramFilter.js.map +1 -1
- package/dist/esm/Filter/index.d.ts +3 -2
- package/dist/esm/Filter/index.d.ts.map +1 -1
- package/dist/esm/Filter/index.js +1 -1
- package/dist/esm/Filter/types.d.ts +7 -1
- package/dist/esm/Filter/types.d.ts.map +1 -1
- package/dist/esm/Filter/utils.d.ts +5 -1
- package/dist/esm/Filter/utils.d.ts.map +1 -1
- package/dist/esm/Filter/utils.js +33 -1
- package/dist/esm/Filter/utils.js.map +1 -1
- package/dist/esm/Notifications/Toaster.d.ts +8 -1
- package/dist/esm/Notifications/Toaster.d.ts.map +1 -1
- package/dist/esm/Notifications/Toaster.js +16 -10
- package/dist/esm/Notifications/Toaster.js.map +1 -1
- package/dist/esm/Notifications/index.js +1 -1
- package/dist/esm/Notifications/toast.d.ts +52 -2
- package/dist/esm/Notifications/toast.d.ts.map +1 -1
- package/dist/esm/Notifications/toast.js +100 -2
- package/dist/esm/Notifications/toast.js.map +1 -1
- package/dist/esm/Notifications/toastCommandResult.js +2 -2
- package/dist/esm/Notifications/toastCommandResult.js.map +1 -1
- package/dist/esm/PatrickHand-latin-ext.woff2 +0 -0
- package/dist/esm/PatrickHand-latin.woff2 +0 -0
- package/dist/esm/PatrickHand-vietnamese.woff2 +0 -0
- package/dist/esm/index.d.ts +4 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +42 -35
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/styles.css +1285 -3
- 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
|