@tableslayer/ui 0.1.3 → 0.1.4

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 (205) hide show
  1. package/package.json +2 -13
  2. package/src/lib/components/Avatar/Avatar.svelte +82 -0
  3. package/src/lib/components/Avatar/AvatarFileInput.svelte +85 -0
  4. package/src/lib/components/Avatar/AvatarPopover.svelte +34 -0
  5. package/src/lib/components/Avatar/index.ts +4 -0
  6. package/src/lib/components/Avatar/types.ts +24 -0
  7. package/src/lib/components/BrushSizeSlider/BrushSizeSlider.svelte +174 -0
  8. package/src/lib/components/BrushSizeSlider/index.ts +1 -0
  9. package/src/lib/components/Button/Button.svelte +182 -0
  10. package/src/lib/components/Button/ConfirmActionButton.svelte +98 -0
  11. package/src/lib/components/Button/IconButton.svelte +121 -0
  12. package/src/lib/components/Button/RadioButton.svelte +93 -0
  13. package/src/lib/components/Button/index.ts +5 -0
  14. package/src/lib/components/Button/types.ts +54 -0
  15. package/src/lib/components/CardFan/CardFan.svelte +165 -0
  16. package/src/lib/components/CardFan/index.ts +2 -0
  17. package/src/lib/components/CardFan/types.ts +6 -0
  18. package/src/lib/components/CodeBlock/Code.svelte +7 -0
  19. package/src/lib/components/CodeBlock/CodeBlock.svelte +102 -0
  20. package/src/lib/components/CodeBlock/index.ts +3 -0
  21. package/src/lib/components/CodeBlock/types.ts +10 -0
  22. package/src/lib/components/ColorMode/ColorMode.svelte +8 -0
  23. package/src/lib/components/ColorMode/index.ts +2 -0
  24. package/src/lib/components/ColorMode/types.ts +12 -0
  25. package/src/lib/components/ColorPicker/ColorPicker.svelte +838 -0
  26. package/src/lib/components/ColorPicker/ColorPickerSwatch.svelte +32 -0
  27. package/src/lib/components/ColorPicker/index.ts +3 -0
  28. package/src/lib/components/ColorPicker/types.ts +51 -0
  29. package/src/lib/components/ContextMenu/ContextMenu.svelte +86 -0
  30. package/src/lib/components/ContextMenu/index.ts +2 -0
  31. package/src/lib/components/ContextMenu/types.ts +15 -0
  32. package/src/lib/components/DrawingSliders/DrawingSliders.svelte +379 -0
  33. package/src/lib/components/DrawingSliders/index.ts +1 -0
  34. package/src/lib/components/Editor/Editor.svelte +825 -0
  35. package/src/lib/components/Editor/index.ts +1 -0
  36. package/src/lib/components/FogSliders/FogSliders.svelte +33 -0
  37. package/src/lib/components/FogSliders/index.ts +1 -0
  38. package/src/lib/components/Hr/Hr.svelte +15 -0
  39. package/src/lib/components/Hr/index.ts +1 -0
  40. package/src/lib/components/Icon/Icon.svelte +6 -0
  41. package/src/lib/components/Icon/index.ts +2 -0
  42. package/src/lib/components/Icon/types.ts +20 -0
  43. package/src/lib/components/Input/DualInputSlider.svelte +126 -0
  44. package/src/lib/components/Input/FileInput.svelte +176 -0
  45. package/src/lib/components/Input/FormControl.svelte +150 -0
  46. package/src/lib/components/Input/FormError.svelte +37 -0
  47. package/src/lib/components/Input/Input.svelte +56 -0
  48. package/src/lib/components/Input/InputCheckbox.svelte +99 -0
  49. package/src/lib/components/Input/InputSlider.svelte +86 -0
  50. package/src/lib/components/Input/Label.svelte +19 -0
  51. package/src/lib/components/Input/index.ts +9 -0
  52. package/src/lib/components/Input/types.ts +39 -0
  53. package/src/lib/components/Link/Link.svelte +41 -0
  54. package/src/lib/components/Link/LinkBox.svelte +20 -0
  55. package/src/lib/components/Link/LinkOverlay.svelte +23 -0
  56. package/src/lib/components/Link/index.ts +4 -0
  57. package/src/lib/components/Link/types.ts +17 -0
  58. package/src/lib/components/Loading/Loader.svelte +60 -0
  59. package/src/lib/components/Loading/Skeleton.svelte +9 -0
  60. package/src/lib/components/Loading/index.ts +2 -0
  61. package/src/lib/components/Logo/Logo.svelte +16 -0
  62. package/src/lib/components/Logo/index.ts +1 -0
  63. package/src/lib/components/MarkerTooltip/MarkerTooltip.svelte +435 -0
  64. package/src/lib/components/MarkerTooltip/index.ts +1 -0
  65. package/src/lib/components/Menu/SelectorMenu.svelte +280 -0
  66. package/src/lib/components/Menu/index.ts +2 -0
  67. package/src/lib/components/Menu/types.ts +17 -0
  68. package/src/lib/components/MyCounterButton.svelte +11 -0
  69. package/src/lib/components/Panel/index.ts +2 -0
  70. package/src/lib/components/Panel/panel.svelte +18 -0
  71. package/src/lib/components/Panel/types.ts +8 -0
  72. package/src/lib/components/PersistButton/PersistButton.svelte +100 -0
  73. package/src/lib/components/PersistButton/index.ts +1 -0
  74. package/src/lib/components/Popover/Popover.svelte +81 -0
  75. package/src/lib/components/Popover/index.ts +2 -0
  76. package/src/lib/components/Popover/types.ts +19 -0
  77. package/src/lib/components/PropsTable/PropsTable.svelte +107 -0
  78. package/src/lib/components/RadialMenu/EffectPreview.svelte +36 -0
  79. package/src/lib/components/RadialMenu/EffectPreviewScene.svelte +194 -0
  80. package/src/lib/components/RadialMenu/RadialMenu.svelte +503 -0
  81. package/src/lib/components/RadialMenu/RadialMenuItem.svelte +176 -0
  82. package/src/lib/components/RadialMenu/index.ts +2 -0
  83. package/src/lib/components/RadialMenu/types.ts +35 -0
  84. package/src/lib/components/Select/Select.svelte +342 -0
  85. package/src/lib/components/Select/index.ts +2 -0
  86. package/src/lib/components/Select/types.ts +22 -0
  87. package/src/lib/components/Spacer/Spacer.svelte +14 -0
  88. package/src/lib/components/Spacer/index.ts +2 -0
  89. package/src/lib/components/Spacer/types.ts +5 -0
  90. package/src/lib/components/Stage/components/AnnotationLayer/AnnotationLayer.svelte +445 -0
  91. package/src/lib/components/Stage/components/AnnotationLayer/AnnotationMaterial.svelte +167 -0
  92. package/src/lib/components/Stage/components/AnnotationLayer/types.ts +196 -0
  93. package/src/lib/components/Stage/components/CursorLayer/CursorLayer.svelte +148 -0
  94. package/src/lib/components/Stage/components/CursorLayer/cursor.svg +26 -0
  95. package/src/lib/components/Stage/components/CursorLayer/index.ts +2 -0
  96. package/src/lib/components/Stage/components/CursorLayer/types.ts +23 -0
  97. package/src/lib/components/Stage/components/DrawingLayer/DrawingMaterial.svelte +364 -0
  98. package/src/lib/components/Stage/components/DrawingLayer/types.ts +65 -0
  99. package/src/lib/components/Stage/components/EdgeOverlayLayer/EdgeOverlayLayer.svelte +72 -0
  100. package/src/lib/components/Stage/components/EdgeOverlayLayer/types.ts +34 -0
  101. package/src/lib/components/Stage/components/FogLayer/FogLayer.svelte +75 -0
  102. package/src/lib/components/Stage/components/FogLayer/types.ts +51 -0
  103. package/src/lib/components/Stage/components/FogOfWarLayer/FogOfWarLayer.svelte +249 -0
  104. package/src/lib/components/Stage/components/FogOfWarLayer/FogOfWarMaterial.svelte +200 -0
  105. package/src/lib/components/Stage/components/FogOfWarLayer/types.ts +116 -0
  106. package/src/lib/components/Stage/components/GridLayer/GridLayer.svelte +20 -0
  107. package/src/lib/components/Stage/components/GridLayer/GridMaterial.svelte +69 -0
  108. package/src/lib/components/Stage/components/GridLayer/types.ts +79 -0
  109. package/src/lib/components/Stage/components/LayerInput/LayerInput.svelte +300 -0
  110. package/src/lib/components/Stage/components/MapLayer/MapLayer.svelte +196 -0
  111. package/src/lib/components/Stage/components/MapLayer/dataSources/GifDataSource.ts +265 -0
  112. package/src/lib/components/Stage/components/MapLayer/dataSources/IMapDataSource.ts +55 -0
  113. package/src/lib/components/Stage/components/MapLayer/dataSources/ImageDataSource.ts +87 -0
  114. package/src/lib/components/Stage/components/MapLayer/dataSources/VideoDataSource.ts +150 -0
  115. package/src/lib/components/Stage/components/MapLayer/dataSources/dataSourceFactory.ts +48 -0
  116. package/src/lib/components/Stage/components/MapLayer/dataSources/index.ts +16 -0
  117. package/src/lib/components/Stage/components/MapLayer/types.ts +58 -0
  118. package/src/lib/components/Stage/components/MarkerLayer/MarkerLayer.svelte +398 -0
  119. package/src/lib/components/Stage/components/MarkerLayer/MarkerToken.svelte +262 -0
  120. package/src/lib/components/Stage/components/MarkerLayer/types.ts +126 -0
  121. package/src/lib/components/Stage/components/MeasurementLayer/MeasurementLayer.svelte +364 -0
  122. package/src/lib/components/Stage/components/MeasurementLayer/MeasurementManager.svelte +473 -0
  123. package/src/lib/components/Stage/components/MeasurementLayer/measurements/BaseMeasurement.ts +427 -0
  124. package/src/lib/components/Stage/components/MeasurementLayer/measurements/BeamMeasurement.ts +105 -0
  125. package/src/lib/components/Stage/components/MeasurementLayer/measurements/CircleMeasurement.ts +98 -0
  126. package/src/lib/components/Stage/components/MeasurementLayer/measurements/ConeMeasurement.ts +163 -0
  127. package/src/lib/components/Stage/components/MeasurementLayer/measurements/LineMeasurement.ts +102 -0
  128. package/src/lib/components/Stage/components/MeasurementLayer/measurements/RectangleMeasurement.ts +120 -0
  129. package/src/lib/components/Stage/components/MeasurementLayer/measurements/index.ts +7 -0
  130. package/src/lib/components/Stage/components/MeasurementLayer/types.ts +94 -0
  131. package/src/lib/components/Stage/components/MeasurementLayer/utils/canvasDrawing.ts +357 -0
  132. package/src/lib/components/Stage/components/MeasurementLayer/utils/distanceCalculations.ts +170 -0
  133. package/src/lib/components/Stage/components/ParticleSystem/ParticleSystem.svelte +220 -0
  134. package/src/lib/components/Stage/components/ParticleSystem/particles/atlases/ash.png +0 -0
  135. package/src/lib/components/Stage/components/ParticleSystem/particles/atlases/leaves.png +0 -0
  136. package/src/lib/components/Stage/components/ParticleSystem/particles/atlases/rain.png +0 -0
  137. package/src/lib/components/Stage/components/ParticleSystem/particles/atlases/snow.png +0 -0
  138. package/src/lib/components/Stage/components/ParticleSystem/rng.js +20 -0
  139. package/src/lib/components/Stage/components/ParticleSystem/types.ts +95 -0
  140. package/src/lib/components/Stage/components/PerformanceDebugger/PerformanceDebugger.svelte +144 -0
  141. package/src/lib/components/Stage/components/PerformanceDebugger/index.ts +1 -0
  142. package/src/lib/components/Stage/components/PerformanceOverlay/PerformanceOverlay.svelte +208 -0
  143. package/src/lib/components/Stage/components/PerformanceOverlay/index.ts +1 -0
  144. package/src/lib/components/Stage/components/PointerInputManager/PointerInputManager.svelte +201 -0
  145. package/src/lib/components/Stage/components/Scene/Scene.svelte +651 -0
  146. package/src/lib/components/Stage/components/Scene/luts.ts +24 -0
  147. package/src/lib/components/Stage/components/Scene/types.ts +225 -0
  148. package/src/lib/components/Stage/components/Stage/Stage.svelte +332 -0
  149. package/src/lib/components/Stage/components/Stage/types.ts +136 -0
  150. package/src/lib/components/Stage/components/WeatherLayer/WeatherLayer.svelte +135 -0
  151. package/src/lib/components/Stage/components/WeatherLayer/presets/AshPreset.ts +71 -0
  152. package/src/lib/components/Stage/components/WeatherLayer/presets/LeavesPreset.ts +70 -0
  153. package/src/lib/components/Stage/components/WeatherLayer/presets/RainPreset.ts +68 -0
  154. package/src/lib/components/Stage/components/WeatherLayer/presets/SnowPreset.ts +70 -0
  155. package/src/lib/components/Stage/components/WeatherLayer/presets/index.ts +6 -0
  156. package/src/lib/components/Stage/components/WeatherLayer/types.ts +35 -0
  157. package/src/lib/components/Stage/helpers/clippingPlaneStore.svelte.ts +28 -0
  158. package/src/lib/components/Stage/helpers/debugState.svelte.ts +18 -0
  159. package/src/lib/components/Stage/helpers/grid.ts +548 -0
  160. package/src/lib/components/Stage/helpers/lazyBrush.ts +171 -0
  161. package/src/lib/components/Stage/helpers/performanceMetrics.svelte.ts +220 -0
  162. package/src/lib/components/Stage/helpers/utils.ts +21 -0
  163. package/src/lib/components/Stage/index.ts +49 -0
  164. package/src/lib/components/Stage/shaders/AnnotationEffects.frag +1070 -0
  165. package/src/lib/components/Stage/shaders/Annotations.frag +29 -0
  166. package/src/lib/components/Stage/shaders/Drawing.frag +83 -0
  167. package/src/lib/components/Stage/shaders/Drawing.vert +5 -0
  168. package/src/lib/components/Stage/shaders/Fog.frag +147 -0
  169. package/src/lib/components/Stage/shaders/FractalNoise.frag +96 -0
  170. package/src/lib/components/Stage/shaders/GridShader.frag +174 -0
  171. package/src/lib/components/Stage/shaders/Overlay.frag +23 -0
  172. package/src/lib/components/Stage/shaders/Overlay.vert +0 -0
  173. package/src/lib/components/Stage/shaders/Particles.frag +27 -0
  174. package/src/lib/components/Stage/shaders/Particles.vert +51 -0
  175. package/src/lib/components/Stage/shaders/ToolOutline.frag +59 -0
  176. package/src/lib/components/Stage/shaders/default.vert +8 -0
  177. package/src/lib/components/Stage/types.ts +4 -0
  178. package/src/lib/components/Table/Table.svelte +16 -0
  179. package/src/lib/components/Table/Td.svelte +17 -0
  180. package/src/lib/components/Table/Th.svelte +18 -0
  181. package/src/lib/components/Table/index.ts +4 -0
  182. package/src/lib/components/Table/types.ts +14 -0
  183. package/src/lib/components/Text/Text.svelte +23 -0
  184. package/src/lib/components/Text/index.ts +2 -0
  185. package/src/lib/components/Text/types.ts +12 -0
  186. package/src/lib/components/Title/Title.svelte +54 -0
  187. package/src/lib/components/Title/index.ts +2 -0
  188. package/src/lib/components/Title/types.ts +9 -0
  189. package/src/lib/components/Toast/Toast.svelte +155 -0
  190. package/src/lib/components/Toast/index.ts +5 -0
  191. package/src/lib/components/Toast/toastCookie.ts +24 -0
  192. package/src/lib/components/Toast/types.ts +6 -0
  193. package/src/lib/components/ToolTip/ToolTip.svelte +70 -0
  194. package/src/lib/components/ToolTip/index.ts +2 -0
  195. package/src/lib/components/ToolTip/types.ts +14 -0
  196. package/src/lib/components/index.ts +32 -0
  197. package/src/lib/components/types.ts +0 -0
  198. package/src/lib/index.ts +2 -0
  199. package/src/lib/styles/globals.css +108 -0
  200. package/src/lib/styles/normalize.css +9 -0
  201. package/src/lib/styles/reset.css +133 -0
  202. package/src/lib/styles/utilities.css +179 -0
  203. package/src/lib/styles/vars.css +1103 -0
  204. package/src/lib/types/awareness.ts +17 -0
  205. package/src/lib/utils/rle.ts +217 -0
@@ -0,0 +1,126 @@
1
+ import type { JSONContent } from '@tiptap/core';
2
+
3
+ export enum MarkerVisibility {
4
+ Always = 0,
5
+ DM = 1,
6
+ Player = 2,
7
+ Hover = 3 // Hidden from players, revealed on DM hover
8
+ }
9
+
10
+ export enum MarkerShape {
11
+ None = 0,
12
+ Circle = 1,
13
+ Square = 2,
14
+ Triangle = 3,
15
+ Pin = 4
16
+ }
17
+
18
+ export enum MarkerSize {
19
+ Small = 1,
20
+ Medium = 2,
21
+ Large = 3
22
+ }
23
+
24
+ export interface Marker {
25
+ id: string;
26
+ title: string;
27
+ position: {
28
+ x: number;
29
+ y: number;
30
+ };
31
+ size: MarkerSize;
32
+ shape: MarkerShape;
33
+ shapeColor: string;
34
+ label: string | null;
35
+ imageUrl: string | null;
36
+ imageScale: number;
37
+ visibility: MarkerVisibility;
38
+ note: JSONContent | null;
39
+ pinnedTooltip?: boolean;
40
+ }
41
+
42
+ export interface MarkerLayerProps {
43
+ /**
44
+ * Whether the marker layer is visible
45
+ */
46
+ visible: boolean;
47
+
48
+ /**
49
+ * The shape of the marker icons
50
+ */
51
+ shape: {
52
+ /**
53
+ * The stroke color of the marker icons represented as a hex string (e.g. 0x60A3FE)
54
+ */
55
+ strokeColor: string;
56
+
57
+ /**
58
+ * The stroke width of the marker icons
59
+ */
60
+ strokeWidth: number;
61
+
62
+ /**
63
+ * The shadow color of the marker icons represented as a hex string (e.g. 0x60A3FE)
64
+ */
65
+ shadowColor: string;
66
+
67
+ /**
68
+ * The shadow blur of the marker icons
69
+ */
70
+ shadowBlur: number;
71
+
72
+ /**
73
+ * The shadow offset of the marker icons
74
+ */
75
+ shadowOffset: {
76
+ x: number;
77
+ y: number;
78
+ };
79
+ };
80
+
81
+ /**
82
+ * The text of the marker
83
+ */
84
+ text: {
85
+ /**
86
+ * The size of the marker text
87
+ */
88
+ size: number;
89
+
90
+ /**
91
+ * The color of the marker text
92
+ */
93
+ color: string;
94
+
95
+ /**
96
+ * The stroke width of the marker text
97
+ */
98
+ strokeWidth: number;
99
+
100
+ /**
101
+ * The color of the marker text stroke
102
+ */
103
+ strokeColor: string;
104
+ };
105
+
106
+ /**
107
+ * Whether to snap the marker to the grid
108
+ */
109
+ snapToGrid: boolean;
110
+
111
+ /**
112
+ * An array of Marker objects
113
+ */
114
+ markers: Marker[];
115
+ }
116
+
117
+ export interface MarkerLayerExports {
118
+ markerState: {
119
+ isHovering: boolean;
120
+ isDragging: boolean;
121
+ hoveredMarker: Marker | null;
122
+ selectedMarker: Marker | null;
123
+ };
124
+ maintainHover: (maintain: boolean) => void;
125
+ onSceneChange: () => void;
126
+ }
@@ -0,0 +1,364 @@
1
+ <script lang="ts">
2
+ import * as THREE from 'three';
3
+ import { T, type Props as ThrelteProps } from '@threlte/core';
4
+ import { type MeasurementLayerProps } from './types';
5
+ import { type DisplayProps } from '../Stage/types';
6
+ import { SceneLayer } from '../Scene/types';
7
+ import { type GridLayerProps, GridType } from '../GridLayer/types';
8
+ import { snapToGrid } from '../../helpers/grid';
9
+ import LayerInput from '../LayerInput/LayerInput.svelte';
10
+ import MeasurementManager from './MeasurementManager.svelte';
11
+
12
+ interface Props extends ThrelteProps<typeof THREE.Mesh> {
13
+ props: MeasurementLayerProps;
14
+ isActive: boolean;
15
+ display: DisplayProps;
16
+ grid: GridLayerProps;
17
+ sceneRotation?: number;
18
+ onMeasurementStart?: (startPoint: THREE.Vector2, type: number) => void;
19
+ onMeasurementUpdate?: (startPoint: THREE.Vector2, endPoint: THREE.Vector2, type: number) => void;
20
+ onMeasurementEnd?: () => void;
21
+ receivedMeasurement?: {
22
+ startPoint: { x: number; y: number };
23
+ endPoint: { x: number; y: number };
24
+ type: number;
25
+ beamWidth?: number;
26
+ coneAngle?: number;
27
+ // Visual properties
28
+ color?: string;
29
+ thickness?: number;
30
+ outlineColor?: string;
31
+ outlineThickness?: number;
32
+ opacity?: number;
33
+ markerSize?: number;
34
+ // Timing properties
35
+ autoHideDelay?: number;
36
+ fadeoutTime?: number;
37
+ // Distance properties
38
+ showDistance?: boolean;
39
+ snapToGrid?: boolean;
40
+ enableDMG252?: boolean;
41
+ } | null;
42
+ }
43
+
44
+ const {
45
+ props,
46
+ isActive,
47
+ display,
48
+ grid,
49
+ sceneRotation = 0,
50
+ onMeasurementStart,
51
+ onMeasurementUpdate,
52
+ onMeasurementEnd,
53
+ receivedMeasurement
54
+ }: Props = $props();
55
+
56
+ // Pre-allocated vectors to avoid GC pressure
57
+ const centerOffset = new THREE.Vector2();
58
+ let snappedPosition = new THREE.Vector2();
59
+ let inputMesh = $state(new THREE.Mesh());
60
+
61
+ // Update centerOffset when display resolution changes
62
+ $effect(() => {
63
+ centerOffset.set(display.resolution.x / 2, display.resolution.y / 2);
64
+ });
65
+
66
+ // Measurement state
67
+ let isDrawing = false;
68
+ let startPoint: THREE.Vector2 | null = null;
69
+ let measurementManager: MeasurementManager | null = null;
70
+
71
+ // Track previous display resolution to detect scene changes
72
+ let prevResX = display.resolution.x;
73
+ let prevResY = display.resolution.y;
74
+
75
+ // Reset measurement state when layer becomes inactive or display changes (scene switch)
76
+ $effect(() => {
77
+ const resChanged = display.resolution.x !== prevResX || display.resolution.y !== prevResY;
78
+
79
+ // Reset any lingering measurement state when layer deactivates or scene changes
80
+ if (!isActive || resChanged) {
81
+ isDrawing = false;
82
+ startPoint = null;
83
+ if (measurementManager) {
84
+ measurementManager.clearMeasurement();
85
+ measurementManager.hidePreview();
86
+ }
87
+ }
88
+
89
+ // Update tracked resolution
90
+ prevResX = display.resolution.x;
91
+ prevResY = display.resolution.y;
92
+ });
93
+
94
+ /**
95
+ * Handles mouse down events to initiate measurement creation.
96
+ * Converts screen coordinates to world coordinates, applies grid snapping if enabled,
97
+ * and starts a new measurement at the clicked position.
98
+ *
99
+ * @param {MouseEvent | TouchEvent} event - The mouse or touch event that triggered this handler
100
+ * @param {THREE.Vector2 | null} coords - The world coordinates of the mouse/touch position, or null if outside bounds
101
+ * @returns {void}
102
+ */
103
+ function handleMouseDown(event: MouseEvent | TouchEvent, coords: THREE.Vector2 | null): void {
104
+ if (!coords || !isActive || !measurementManager || !props) return;
105
+
106
+ coords.sub(centerOffset);
107
+
108
+ // For measurements on hex grids, snap to centers only
109
+ const isHexGrid = grid.gridType === GridType.Hex;
110
+ const snappedCoords = props.snapToGrid ? snapToGrid(coords, grid, display, isHexGrid) : coords;
111
+
112
+ // Always clear any existing measurement state before starting new one
113
+ // This handles edge cases where isDrawing might be unexpectedly true
114
+ if (isDrawing) {
115
+ measurementManager.clearMeasurement();
116
+ }
117
+
118
+ // Start new measurement
119
+ isDrawing = true;
120
+ startPoint = snappedCoords.clone(); // Clone to avoid reference issues
121
+ measurementManager.startMeasurement(snappedCoords);
122
+
123
+ // Notify parent component
124
+ if (onMeasurementStart) {
125
+ onMeasurementStart(snappedCoords, props.type);
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Handles mouse move events to update measurement preview and active measurements.
131
+ * Converts coordinates to world space, applies grid snapping, and either updates
132
+ * the current measurement being drawn or shows a preview indicator.
133
+ *
134
+ * @param {MouseEvent | TouchEvent} event - The mouse or touch event that triggered this handler
135
+ * @param {THREE.Vector2 | null} coords - The world coordinates of the mouse/touch position, or null if outside bounds
136
+ * @returns {void}
137
+ */
138
+ function handleMouseMove(event: MouseEvent | TouchEvent, coords: THREE.Vector2 | null): void {
139
+ if (!coords || !isActive || !measurementManager || !props) return;
140
+
141
+ coords.sub(centerOffset);
142
+
143
+ // Apply snapping if enabled
144
+ // For measurements on hex grids, snap to centers only
145
+ const isHexGrid = grid.gridType === GridType.Hex;
146
+ if (props.snapToGrid) {
147
+ snappedPosition = snapToGrid(coords, grid, display, isHexGrid);
148
+ } else {
149
+ snappedPosition.copy(coords);
150
+ }
151
+
152
+ if (isDrawing && startPoint && measurementManager) {
153
+ // Update current measurement
154
+ measurementManager.updateMeasurement(snappedPosition);
155
+
156
+ // Notify parent component
157
+ if (onMeasurementUpdate) {
158
+ onMeasurementUpdate(startPoint, snappedPosition, props.type);
159
+ }
160
+ } else {
161
+ // Update the preview indicator position
162
+ measurementManager.updatePreview(snappedPosition, isActive);
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Handles mouse up events to complete measurement creation.
168
+ * Finalizes the current measurement at the release position and resets the drawing state.
169
+ * Applies grid snapping to the final position if enabled.
170
+ *
171
+ * @param {MouseEvent | TouchEvent} event - The mouse or touch event that triggered this handler
172
+ * @param {THREE.Vector2 | null} coords - The world coordinates of the mouse/touch position, or null if outside bounds
173
+ * @returns {void}
174
+ */
175
+ function handleMouseUp(event: MouseEvent | TouchEvent, coords: THREE.Vector2 | null): void {
176
+ // Always reset drawing state when mouseUp is called, even if coords is null
177
+ // This prevents isDrawing from staying true after touch gestures or edge cases
178
+ const wasDrawing = isDrawing;
179
+ isDrawing = false;
180
+
181
+ if (!wasDrawing || !startPoint || !measurementManager) {
182
+ startPoint = null;
183
+ return;
184
+ }
185
+
186
+ if (coords) {
187
+ coords.sub(centerOffset);
188
+ // For measurements on hex grids, snap to centers only
189
+ const isHexGrid = grid.gridType === GridType.Hex;
190
+ const snappedCoords = props.snapToGrid ? snapToGrid(coords, grid, display, isHexGrid) : coords;
191
+
192
+ if (snappedCoords) {
193
+ measurementManager.finishMeasurement();
194
+ }
195
+ } else {
196
+ // Coords is null (released outside canvas) - clear the incomplete measurement
197
+ measurementManager.clearMeasurement();
198
+ }
199
+
200
+ startPoint = null;
201
+
202
+ // Notify parent component
203
+ if (onMeasurementEnd) {
204
+ onMeasurementEnd();
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Handles mouse leave events when the cursor exits the measurement area.
210
+ * Hides the preview indicator to provide clear visual feedback that measurements
211
+ * cannot be placed outside the active area.
212
+ * Also resets any in-progress measurement to prevent stuck state.
213
+ * @returns {void}
214
+ */
215
+ function handleMouseLeave(): void {
216
+ // Reset drawing state if user leaves canvas while drawing
217
+ if (isDrawing && measurementManager) {
218
+ measurementManager.clearMeasurement();
219
+ isDrawing = false;
220
+ startPoint = null;
221
+ if (onMeasurementEnd) {
222
+ onMeasurementEnd();
223
+ }
224
+ }
225
+
226
+ if (measurementManager) {
227
+ measurementManager.hidePreview();
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Handles mouse enter events when the cursor enters the measurement area.
233
+ * Shows the preview indicator if the measurement layer is active, providing
234
+ * visual feedback for where measurements can be placed.
235
+ * @returns {void}
236
+ */
237
+ function handleMouseEnter(): void {
238
+ if (measurementManager && isActive) {
239
+ measurementManager.showPreviewIndicator();
240
+ }
241
+ }
242
+
243
+ // Export methods for parent components to get measurement state
244
+ export function getCurrentMeasurement(): {
245
+ startPoint: THREE.Vector2 | null;
246
+ endPoint: THREE.Vector2 | null;
247
+ type: number;
248
+ } | null {
249
+ if (!measurementManager || !isDrawing || !startPoint) return null;
250
+
251
+ return {
252
+ startPoint: startPoint.clone(),
253
+ endPoint: snappedPosition.clone(),
254
+ type: props.type
255
+ };
256
+ }
257
+
258
+ export function isCurrentlyDrawing(): boolean {
259
+ return isDrawing;
260
+ }
261
+
262
+ // Track the last displayed measurement to avoid redundant updates
263
+ let lastDisplayedMeasurement: {
264
+ startX: number;
265
+ startY: number;
266
+ endX: number;
267
+ endY: number;
268
+ type: number;
269
+ } | null = null;
270
+
271
+ // Track if a measurement is currently fading (so we don't clear it prematurely)
272
+ let measurementIsFading = false;
273
+
274
+ // Effect to handle received measurements from other users
275
+ $effect(() => {
276
+ if (receivedMeasurement && measurementManager && !isActive) {
277
+ // Check if this is a new measurement (not the same as last displayed)
278
+ const isNewMeasurement =
279
+ !lastDisplayedMeasurement ||
280
+ lastDisplayedMeasurement.startX !== receivedMeasurement.startPoint.x ||
281
+ lastDisplayedMeasurement.startY !== receivedMeasurement.startPoint.y ||
282
+ lastDisplayedMeasurement.endX !== receivedMeasurement.endPoint.x ||
283
+ lastDisplayedMeasurement.endY !== receivedMeasurement.endPoint.y ||
284
+ lastDisplayedMeasurement.type !== receivedMeasurement.type;
285
+
286
+ if (isNewMeasurement) {
287
+ // Convert to Vector2 with center offset adjustment
288
+ const startPoint = new THREE.Vector2(receivedMeasurement.startPoint.x, receivedMeasurement.startPoint.y);
289
+ const endPoint = new THREE.Vector2(receivedMeasurement.endPoint.x, receivedMeasurement.endPoint.y);
290
+
291
+ // Display the received measurement with all properties if available
292
+ measurementManager.displayReceivedMeasurement(
293
+ startPoint,
294
+ endPoint,
295
+ receivedMeasurement.type,
296
+ receivedMeasurement.beamWidth,
297
+ receivedMeasurement.coneAngle,
298
+ receivedMeasurement.color,
299
+ receivedMeasurement.thickness,
300
+ receivedMeasurement.outlineColor,
301
+ receivedMeasurement.outlineThickness,
302
+ receivedMeasurement.opacity,
303
+ receivedMeasurement.markerSize,
304
+ receivedMeasurement.autoHideDelay,
305
+ receivedMeasurement.fadeoutTime,
306
+ receivedMeasurement.showDistance,
307
+ receivedMeasurement.snapToGrid,
308
+ receivedMeasurement.enableDMG252
309
+ );
310
+
311
+ // Mark that a measurement is now fading
312
+ measurementIsFading = true;
313
+
314
+ // Update last displayed measurement
315
+ lastDisplayedMeasurement = {
316
+ startX: receivedMeasurement.startPoint.x,
317
+ startY: receivedMeasurement.startPoint.y,
318
+ endX: receivedMeasurement.endPoint.x,
319
+ endY: receivedMeasurement.endPoint.y,
320
+ type: receivedMeasurement.type
321
+ };
322
+ }
323
+ } else if (!receivedMeasurement && lastDisplayedMeasurement && !measurementIsFading) {
324
+ // Only clear if no measurement is fading
325
+ // The measurement will clear itself after fade completes
326
+ lastDisplayedMeasurement = null;
327
+ }
328
+ });
329
+ </script>
330
+
331
+ <!-- Input handling -->
332
+ <LayerInput
333
+ {isActive}
334
+ target={inputMesh}
335
+ layerSize={{ width: display.resolution.x, height: display.resolution.y }}
336
+ onMouseDown={handleMouseDown}
337
+ onMouseMove={handleMouseMove}
338
+ onMouseUp={handleMouseUp}
339
+ onMouseLeave={handleMouseLeave}
340
+ onMouseEnter={handleMouseEnter}
341
+ />
342
+
343
+ <!-- This quad is user for raycasting / mouse input detection. It is invisible -->
344
+ <T.Mesh
345
+ bind:ref={inputMesh}
346
+ scale={[display.resolution.x, display.resolution.y, 1]}
347
+ layers={isActive ? [SceneLayer.Input] : undefined}
348
+ >
349
+ <T.MeshBasicMaterial visible={false} />
350
+ <T.PlaneGeometry />
351
+ </T.Mesh>
352
+
353
+ <!-- Measurement Manager Component -->
354
+ <MeasurementManager
355
+ bind:this={measurementManager}
356
+ {props}
357
+ visible={isActive}
358
+ displayProps={display}
359
+ gridProps={grid}
360
+ {sceneRotation}
361
+ onFadeComplete={() => {
362
+ measurementIsFading = false;
363
+ }}
364
+ />