@terraware/web-components 5.0.27 → 5.1.1-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (311) hide show
  1. package/components/AntSwitch/index.js +19 -22
  2. package/components/Autocomplete/Autocomplete.js +106 -130
  3. package/components/Badge/index.js +28 -29
  4. package/components/BusySpinner/index.js +40 -45
  5. package/components/Button/Button.js +41 -47
  6. package/components/Checkbox.js +64 -63
  7. package/components/Confirm/index.js +74 -83
  8. package/components/DatePicker/DatePicker.js +138 -141
  9. package/components/DialogBox/DialogBox.js +111 -118
  10. package/components/Divisor.js +18 -18
  11. package/components/Dropdown.js +66 -94
  12. package/components/EditableTable/index.js +211 -271
  13. package/components/ErrorBox/ErrorBox.js +47 -49
  14. package/components/FileChooser/index.js +117 -135
  15. package/components/FormBottomBar/index.js +75 -84
  16. package/components/Icon/Icon.js +12 -20
  17. package/components/Icon/icons/index.js +256 -256
  18. package/components/IconTooltip/index.js +49 -60
  19. package/components/Map/MapBox.js +545 -452
  20. package/components/Map/MapContainer.js +25 -29
  21. package/components/Map/MapDateSliderControl.js +64 -64
  22. package/components/Map/MapDrawer.js +41 -47
  23. package/components/Map/MapLegend.js +176 -172
  24. package/components/Map/MapViewStyleControl.js +59 -63
  25. package/components/Map/ReactMapboxWrapper.js +3 -2
  26. package/components/Map/index.js +161 -180
  27. package/components/Map/types.js +12 -17
  28. package/components/Map/useMaintainLayerOrder.js +16 -26
  29. package/components/Markdown/index.js +25 -26
  30. package/components/Message/Message.js +73 -76
  31. package/components/MultiSelect/index.js +112 -124
  32. package/components/Navbar/NavFooter.js +9 -13
  33. package/components/Navbar/NavItem.js +70 -76
  34. package/components/Navbar/NavSection.js +17 -19
  35. package/components/Navbar/Navbar.js +39 -46
  36. package/components/Navbar/SubNavbar.js +9 -13
  37. package/components/Note.js +18 -22
  38. package/components/OverlayModal/OverlayModal.js +56 -63
  39. package/components/PageForm/index.js +33 -41
  40. package/components/PhotoChooser/index.js +165 -173
  41. package/components/PhotosCarousel/PhotoSlide.js +32 -35
  42. package/components/PhotosCarousel/index.js +80 -85
  43. package/components/PhotosCarousel/usePhotosCarousel.js +69 -76
  44. package/components/Pill/index.js +41 -45
  45. package/components/PillList/index.js +17 -24
  46. package/components/PlacementWrapper/index.js +38 -59
  47. package/components/PopoverMenu/Popover.js +68 -85
  48. package/components/PopoverMenu/index.js +51 -56
  49. package/components/PopoverMultiSelect/index.js +52 -59
  50. package/components/ProgressCircle/ProgressCircle.js +28 -32
  51. package/components/RadioButton.js +17 -19
  52. package/components/Select/Select.js +13 -15
  53. package/components/Select/SelectT.js +235 -250
  54. package/components/Separator/index.js +14 -17
  55. package/components/Size.js +0 -1
  56. package/components/Slider/index.js +43 -56
  57. package/components/Stepper/index.d.ts +33 -0
  58. package/components/Stepper/index.d.ts.map +1 -0
  59. package/components/Stepper/index.js +66 -0
  60. package/components/SummaryBox.js +70 -72
  61. package/components/Tabs/index.js +133 -135
  62. package/components/TextTruncated/index.js +117 -130
  63. package/components/Textfield/Textfield.js +158 -190
  64. package/components/Textfield/TruncatedTextArea.js +70 -84
  65. package/components/TimelineSlider/TimelineMarker.js +25 -30
  66. package/components/TimelineSlider/TimelineRail.js +8 -11
  67. package/components/TimelineSlider/index.js +64 -66
  68. package/components/TimelineSliderV2/ClusterBadge.js +25 -29
  69. package/components/TimelineSliderV2/TimelineDot.js +31 -35
  70. package/components/TimelineSliderV2/TimelineRail.js +8 -11
  71. package/components/TimelineSliderV2/index.js +121 -103
  72. package/components/TimelineSliderV2/layout.js +124 -147
  73. package/components/TimelineSliderV2/types.js +0 -1
  74. package/components/Tooltip/Tooltip.js +47 -56
  75. package/components/ViewPhotosDialog/index.js +28 -35
  76. package/components/VirtualWalkthrough/Annotation.js +163 -207
  77. package/components/VirtualWalkthrough/AnnotationEditPane.js +198 -184
  78. package/components/VirtualWalkthrough/AnnotationPanel.js +187 -199
  79. package/components/VirtualWalkthrough/Application.js +31 -34
  80. package/components/VirtualWalkthrough/AutoRotator.js +85 -223
  81. package/components/VirtualWalkthrough/BlockingSpinner.js +26 -28
  82. package/components/VirtualWalkthrough/BoundaryRing.js +30 -34
  83. package/components/VirtualWalkthrough/BoundaryWall.js +32 -36
  84. package/components/VirtualWalkthrough/CameraInfo.js +100 -89
  85. package/components/VirtualWalkthrough/ControlsInfoPane.js +254 -195
  86. package/components/VirtualWalkthrough/GradientSky.js +72 -73
  87. package/components/VirtualWalkthrough/SplatControls.js +209 -221
  88. package/components/VirtualWalkthrough/SplatCrop.js +21 -20
  89. package/components/VirtualWalkthrough/SplatFadeCrop.js +21 -20
  90. package/components/VirtualWalkthrough/SplatModel.js +65 -85
  91. package/components/VirtualWalkthrough/SplatRevealRain.js +41 -46
  92. package/components/VirtualWalkthrough/TfAnnotationManager.js +197 -379
  93. package/components/VirtualWalkthrough/TfXrNavigation.js +73 -162
  94. package/components/VirtualWalkthrough/VirtualWalkthroughViewer.js +438 -431
  95. package/components/VirtualWalkthrough/VrAnnotationPanel.js +18 -22
  96. package/components/VirtualWalkthrough/XrAnnotationInteraction.js +14 -17
  97. package/components/VirtualWalkthrough/XrExitButton.js +14 -17
  98. package/components/VirtualWalkthrough/XrGazeDwell.js +14 -17
  99. package/components/VirtualWalkthrough/XrPointerRay.js +14 -17
  100. package/components/VirtualWalkthrough/XrStartPose.js +67 -121
  101. package/components/VirtualWalkthrough/boundary-ring.js +87 -117
  102. package/components/VirtualWalkthrough/boundary-wall.js +175 -258
  103. package/components/VirtualWalkthrough/camera-adoption.js +18 -64
  104. package/components/VirtualWalkthrough/groundPlane.js +32 -47
  105. package/components/VirtualWalkthrough/gsplat-fade-crop-effect.js +35 -35
  106. package/components/VirtualWalkthrough/splatFormat.js +13 -47
  107. package/components/VirtualWalkthrough/useAdoptedCamera.js +23 -44
  108. package/components/VirtualWalkthrough/useRestartOnVr.js +26 -37
  109. package/components/VirtualWalkthrough/useXrRenderTuning.js +33 -54
  110. package/components/VirtualWalkthrough/vr-annotation-panel-layout.js +67 -99
  111. package/components/VirtualWalkthrough/vr-annotation-panel.js +403 -433
  112. package/components/VirtualWalkthrough/vr-annotation-scroll.js +10 -28
  113. package/components/VirtualWalkthrough/walkthrough-camera.js +204 -329
  114. package/components/VirtualWalkthrough/xr-annotation-candidate-buffer.js +39 -80
  115. package/components/VirtualWalkthrough/xr-annotation-candidates.js +6 -18
  116. package/components/VirtualWalkthrough/xr-annotation-interaction.js +31 -56
  117. package/components/VirtualWalkthrough/xr-annotation-targeting.js +22 -38
  118. package/components/VirtualWalkthrough/xr-button-arming.js +7 -17
  119. package/components/VirtualWalkthrough/xr-button-hold.js +22 -36
  120. package/components/VirtualWalkthrough/xr-exit-button-mask.js +22 -36
  121. package/components/VirtualWalkthrough/xr-exit-button.js +148 -207
  122. package/components/VirtualWalkthrough/xr-face-buttons.js +19 -29
  123. package/components/VirtualWalkthrough/xr-gaze-dwell-state.js +23 -34
  124. package/components/VirtualWalkthrough/xr-gaze-dwell.js +107 -144
  125. package/components/VirtualWalkthrough/xr-interactive-ui.js +12 -24
  126. package/components/VirtualWalkthrough/xr-pointer-ray.js +13 -20
  127. package/components/VirtualWalkthrough/xr-progress-pie-material.js +117 -88
  128. package/components/VirtualWalkthrough/xr-progress-pie.js +1 -10
  129. package/components/VirtualWalkthrough/xr-ray-quad.js +16 -30
  130. package/components/VirtualWalkthrough/xr-render-tuning.js +17 -48
  131. package/components/VirtualWalkthrough/xr-scene-bounds.js +21 -37
  132. package/components/VirtualWalkthrough/xr-start-pose.js +27 -73
  133. package/components/VirtualWalkthrough/xr-teleport-gesture.js +16 -42
  134. package/components/svg/BlobbyGrayIconImage.js +41 -39
  135. package/components/svg/BlobbyGrayIconImport.js +41 -39
  136. package/components/svg/BlobbyGrayIconUploadToTheCloud.js +41 -39
  137. package/components/svg/BlobbyIconCloudChecked.js +202 -164
  138. package/components/svg/BlobbyIconExportOrUploadPhoto.js +202 -164
  139. package/components/svg/BlobbyIconFolder.js +202 -164
  140. package/components/svg/BlobbyIconGraphReport.js +202 -164
  141. package/components/svg/BlobbyIconHappy.js +202 -164
  142. package/components/svg/BlobbyIconHeartMonitor.js +202 -164
  143. package/components/svg/BlobbyIconLeaf.js +202 -164
  144. package/components/svg/BlobbyIconLibrary.js +202 -164
  145. package/components/svg/BlobbyIconNursery.js +204 -166
  146. package/components/svg/BlobbyIconOrganization.js +202 -164
  147. package/components/svg/BlobbyIconParchment.js +202 -164
  148. package/components/svg/BlobbyIconPeople.js +202 -164
  149. package/components/svg/BlobbyIconSeedBank.js +202 -164
  150. package/components/svg/BlobbyIconSeedling.js +202 -164
  151. package/components/svg/BlobbyIconSeeds.js +202 -164
  152. package/components/svg/BlobbyIconSite.js +206 -167
  153. package/components/svg/BlobbyIconSparkles.js +202 -164
  154. package/components/svg/BlobbyIconWrench.js +202 -164
  155. package/components/svg/Bug.js +19 -21
  156. package/components/svg/Calendar.js +19 -21
  157. package/components/svg/CaretDown.js +19 -21
  158. package/components/svg/CaretLeft.js +19 -21
  159. package/components/svg/CaretRight.js +19 -21
  160. package/components/svg/CaretUp.js +19 -21
  161. package/components/svg/ChevronDown.js +19 -21
  162. package/components/svg/ChevronUp.js +19 -21
  163. package/components/svg/Close.js +19 -21
  164. package/components/svg/Critical.js +19 -21
  165. package/components/svg/Edit.js +202 -164
  166. package/components/svg/Error.js +19 -21
  167. package/components/svg/Filter.js +19 -21
  168. package/components/svg/Folder.js +19 -21
  169. package/components/svg/Help.js +19 -21
  170. package/components/svg/Home.js +19 -21
  171. package/components/svg/IconAdd.js +19 -21
  172. package/components/svg/IconArrowRight.js +21 -23
  173. package/components/svg/IconBusinessNetwork.js +19 -21
  174. package/components/svg/IconCalendar.js +19 -21
  175. package/components/svg/IconCancel.js +19 -21
  176. package/components/svg/IconChargingBattery.js +19 -21
  177. package/components/svg/IconChecklist.js +21 -23
  178. package/components/svg/IconCheckmark.js +19 -21
  179. package/components/svg/IconCoinInHand.js +19 -21
  180. package/components/svg/IconColumns.js +19 -21
  181. package/components/svg/IconComment.js +20 -22
  182. package/components/svg/IconDashboard.js +19 -21
  183. package/components/svg/IconDataMigration.js +19 -21
  184. package/components/svg/IconDocument.js +21 -23
  185. package/components/svg/IconDownloadFromTheCloud.js +21 -23
  186. package/components/svg/IconEdit.js +21 -23
  187. package/components/svg/IconExpand.js +21 -23
  188. package/components/svg/IconExport.js +19 -21
  189. package/components/svg/IconExternalLink.js +19 -21
  190. package/components/svg/IconEye.js +19 -21
  191. package/components/svg/IconEyeOff.js +19 -21
  192. package/components/svg/IconFile.js +19 -21
  193. package/components/svg/IconFolder.js +19 -21
  194. package/components/svg/IconFullScreen.js +20 -22
  195. package/components/svg/IconFutures.js +19 -21
  196. package/components/svg/IconGraphReport.js +19 -21
  197. package/components/svg/IconGrid.js +19 -21
  198. package/components/svg/IconHeartMonitor.js +19 -21
  199. package/components/svg/IconHelp.js +21 -23
  200. package/components/svg/IconHistory.js +19 -21
  201. package/components/svg/IconImport.js +19 -21
  202. package/components/svg/IconIndex.js +19 -21
  203. package/components/svg/IconLayers.js +19 -21
  204. package/components/svg/IconLibrary.js +20 -22
  205. package/components/svg/IconList.js +19 -21
  206. package/components/svg/IconLivePlant.js +19 -21
  207. package/components/svg/IconLocations.js +19 -21
  208. package/components/svg/IconMail.js +19 -21
  209. package/components/svg/IconManager.js +19 -21
  210. package/components/svg/IconMap.js +19 -21
  211. package/components/svg/IconMarker.js +19 -21
  212. package/components/svg/IconMenu.js +19 -21
  213. package/components/svg/IconMenuHorizontal.js +19 -21
  214. package/components/svg/IconModule.js +19 -21
  215. package/components/svg/IconMyLocation.js +19 -21
  216. package/components/svg/IconNote.js +21 -23
  217. package/components/svg/IconNursery.js +21 -23
  218. package/components/svg/IconOrg.js +19 -21
  219. package/components/svg/IconParchment.js +19 -21
  220. package/components/svg/IconPhoto.js +19 -21
  221. package/components/svg/IconPlantsFilled.js +19 -21
  222. package/components/svg/IconRedo.js +19 -21
  223. package/components/svg/IconRestorationSite.js +19 -21
  224. package/components/svg/IconSeedBank.js +19 -21
  225. package/components/svg/IconSeedling.js +19 -21
  226. package/components/svg/IconSettings.js +19 -21
  227. package/components/svg/IconSlice.js +19 -21
  228. package/components/svg/IconSubmit.js +19 -21
  229. package/components/svg/IconSubtract.js +21 -23
  230. package/components/svg/IconSynced.js +19 -21
  231. package/components/svg/IconTrashCan.js +19 -21
  232. package/components/svg/IconTreasureMap.js +19 -21
  233. package/components/svg/IconUnavailable.js +19 -21
  234. package/components/svg/IconUndo.js +19 -21
  235. package/components/svg/IconVariable.js +19 -21
  236. package/components/svg/IconVideo.js +30 -31
  237. package/components/svg/IconWifi.js +19 -21
  238. package/components/svg/Info.js +19 -21
  239. package/components/svg/Key.js +19 -21
  240. package/components/svg/Leaf.js +19 -21
  241. package/components/svg/Lock.js +19 -21
  242. package/components/svg/Logo.js +48 -43
  243. package/components/svg/MenuVertical.js +19 -21
  244. package/components/svg/Notification.js +19 -21
  245. package/components/svg/Person.js +19 -21
  246. package/components/svg/Plus.js +19 -21
  247. package/components/svg/RestorationSite.js +19 -21
  248. package/components/svg/Search.js +19 -21
  249. package/components/svg/Seeds.js +19 -21
  250. package/components/svg/Site.js +19 -21
  251. package/components/svg/Sparkles.js +19 -21
  252. package/components/svg/Species.js +19 -21
  253. package/components/svg/Species2.js +202 -164
  254. package/components/svg/Spinner.js +54 -48
  255. package/components/svg/Star.js +19 -21
  256. package/components/svg/Success.js +19 -21
  257. package/components/svg/SuccessFilled.js +19 -21
  258. package/components/svg/TerrawareLogoDesktop.js +55 -49
  259. package/components/svg/TerrawareLogoMobile.js +25 -26
  260. package/components/svg/Touchscreen.js +19 -21
  261. package/components/svg/UploadCloud.js +202 -164
  262. package/components/svg/Warning.js +19 -21
  263. package/components/svg/WelcomeClipboard.js +156 -133
  264. package/components/svg/index.js +129 -129
  265. package/components/table/EnhancedTableToolbar.js +68 -74
  266. package/components/table/EnhancedTableToolbarV2.js +31 -41
  267. package/components/table/TableCellRenderer.js +196 -231
  268. package/components/table/TableHeader.js +71 -73
  269. package/components/table/TableHeaderItem.js +97 -102
  270. package/components/table/density.js +18 -19
  271. package/components/table/index.js +350 -369
  272. package/components/table/sort.js +27 -52
  273. package/components/table/types.js +0 -1
  274. package/components/types/index.js +0 -1
  275. package/hooks/cameraEntityContext.js +2 -13
  276. package/hooks/useBoolean.js +17 -13
  277. package/hooks/useCameraPosition.js +56 -56
  278. package/hooks/useDevicePerformance.js +17 -17
  279. package/hooks/useXr.js +85 -87
  280. package/index.d.ts +3 -1
  281. package/index.d.ts.map +1 -1
  282. package/index.js +59 -58
  283. package/package.json +1 -1
  284. package/setupTests.js +0 -4
  285. package/strings/csv/en.csv +1 -0
  286. package/strings/csv/es.csv +1 -0
  287. package/strings/csv/fr.csv +1 -0
  288. package/strings/export.js +111 -170
  289. package/strings/index.js +25 -54
  290. package/strings/locales.js +21 -41
  291. package/strings/strings-en.d.ts +1 -0
  292. package/strings/strings-en.d.ts.map +1 -1
  293. package/strings/strings-en.js +50 -51
  294. package/strings/strings-es.d.ts +1 -0
  295. package/strings/strings-es.d.ts.map +1 -1
  296. package/strings/strings-es.js +50 -51
  297. package/strings/strings-fr.d.ts +1 -0
  298. package/strings/strings-fr.d.ts.map +1 -1
  299. package/strings/strings-fr.js +50 -51
  300. package/strings/strings-gx.d.ts +1 -0
  301. package/strings/strings-gx.d.ts.map +1 -1
  302. package/strings/strings-gx.js +50 -51
  303. package/style-dictionary-dist/TerrawareTheme.js +186 -186
  304. package/theme.js +74 -76
  305. package/utils/color.js +8 -15
  306. package/utils/date.js +42 -108
  307. package/utils/index.js +4 -4
  308. package/utils/preventDefaultEvent.js +3 -3
  309. package/utils/text.js +11 -11
  310. package/utils/useDeviceInfo.js +42 -79
  311. package/virtualWalkthrough.js +23 -23
@@ -1,400 +1,218 @@
1
- import { createElement } from 'react';
2
- import { renderToStaticMarkup } from 'react-dom/server';
3
- import { Texture, FILTER_LINEAR, PIXELFORMAT_RGBA8 } from 'playcanvas';
4
- import { AnnotationManager } from 'playcanvas/scripts/esm/annotations.mjs';
5
- import Icon from '../Icon/Icon.js';
6
-
7
- // Maps each annotation icon type to a @terraware/web-components icon.
1
+ import { createElement } from "react";
2
+ import { renderToStaticMarkup } from "react-dom/server";
3
+ import { FILTER_LINEAR, PIXELFORMAT_RGBA8, Texture } from "playcanvas";
4
+ import { AnnotationManager } from "playcanvas/scripts/esm/annotations.mjs";
5
+ import Icon from "../Icon/Icon.js";
8
6
  const ANNOTATION_ICONS = {
9
- text: 'iconComment',
10
- image: 'iconPhoto',
11
- video: 'iconVideo'
7
+ text: 'iconComment',
8
+ image: 'iconPhoto',
9
+ video: 'iconVideo'
12
10
  };
13
-
14
- // Pixel dimensions of the (square) hotspot texture canvas at rest.
15
11
  const HOTSPOT_TEXTURE_SIZE = 64;
16
-
17
- // How much larger the hotspot appears while hovered.
18
12
  const HOTSPOT_HOVER_SCALE = 1.3;
19
-
20
- /**
21
- * Extended AnnotationManager that adds max size clamping for annotations
22
- * and fixes positioning when canvas is not at document origin.
23
- */
24
13
  class TfAnnotationManager extends AnnotationManager {
25
- static scriptName = 'tfAnnotationManager';
26
- _maxWorldSize = 1.0;
27
- _customParentDom = null;
28
- _clickHandlersAttached = new Set();
29
- _hotspotBackgroundColor = '#2C8658';
30
- _iconImages = new Map();
31
-
32
- /**
33
- * Maximum world-space size for annotations.
34
- * This prevents annotations from growing beyond this size regardless of camera distance.
35
- *
36
- * @attribute
37
- * @title Max World Size
38
- * @type {number}
39
- * @default 1.0
40
- */
41
- set maxWorldSize(value) {
42
- this._maxWorldSize = value;
43
- }
44
- get maxWorldSize() {
45
- return this._maxWorldSize;
46
- }
47
-
48
- /**
49
- * Background color for the hotspot texture canvas (the circle fill color).
50
- * This is a hex color string (e.g., '#2C8658') that's drawn on the texture canvas.
51
- * Note: This is different from hotspotColor and hoverColor, which are PlayCanvas Color objects
52
- * used for the material's emissive property.
53
- *
54
- * @attribute
55
- * @title Hotspot Background Color
56
- * @type {string}
57
- * @default '#2C8658'
58
- */
59
- set hotspotBackgroundColor(value) {
60
- this._hotspotBackgroundColor = value;
61
- }
62
- get hotspotBackgroundColor() {
63
- return this._hotspotBackgroundColor;
64
- }
65
-
66
- /**
67
- * Initialize with custom parent DOM if needed.
68
- */
69
- initialize() {
70
- // Find a container div with data-annotation-container attribute
71
- const canvas = this.app.graphicsDevice.canvas;
72
- const container = canvas.parentElement?.querySelector('[data-annotation-container]');
73
- if (container) {
74
- this._customParentDom = container;
75
- this._parentDom = container;
14
+ static scriptName = 'tfAnnotationManager';
15
+ _maxWorldSize = 1.0;
16
+ _customParentDom = null;
17
+ _clickHandlersAttached = new Set();
18
+ _hotspotBackgroundColor = '#2C8658';
19
+ _iconImages = new Map();
20
+ set maxWorldSize(value) {
21
+ this._maxWorldSize = value;
76
22
  }
77
-
78
- // Call parent initialize
79
- super.initialize();
80
- this._loadIconImages();
81
- }
82
-
83
- /**
84
- * Renders each web-components icon to an SVG image once. When an image finishes
85
- * loading, any annotations already using that icon are redrawn.
86
- * @private
87
- */
88
- _loadIconImages() {
89
- const fillColor = this._hotspotColor?.toString() ?? '#ffffff';
90
- Object.keys(ANNOTATION_ICONS).forEach(iconType => {
91
- const name = ANNOTATION_ICONS[iconType];
92
- const markup = renderToStaticMarkup(/*#__PURE__*/createElement(Icon, {
93
- name,
94
- fillColor
95
- })).replace(/^<svg/, '<svg width="128" height="128"');
96
- const image = new Image();
97
- image.onload = () => this._refreshIcon(iconType);
98
- image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(markup)}`;
99
- this._iconImages.set(iconType, image);
100
- });
101
- }
102
-
103
- /**
104
- * Redraws the texture for all annotations using the given icon, once its image
105
- * has loaded.
106
- * @private
107
- */
108
- _refreshIcon(iconType) {
109
- const annotationResources = this._annotationResources;
110
- if (!annotationResources) {
111
- return;
23
+ get maxWorldSize() {
24
+ return this._maxWorldSize;
112
25
  }
113
- annotationResources.forEach((_resources, annotation) => {
114
- if ((annotation.icon ?? 'text') === iconType) {
115
- this._applyAnnotationIcon(annotation);
116
- }
117
- });
118
- }
119
-
120
- /**
121
- * Update loop to attach click handlers to annotations and manage visibility.
122
- */
123
- update() {
124
- const annotationResources = this._annotationResources;
125
- if (!annotationResources) {
126
- return;
26
+ set hotspotBackgroundColor(value) {
27
+ this._hotspotBackgroundColor = value;
28
+ }
29
+ get hotspotBackgroundColor() {
30
+ return this._hotspotBackgroundColor;
127
31
  }
128
-
129
- // Attach click handlers and manage visibility for all annotations
130
- annotationResources.forEach((resources, annotation) => {
131
- if (resources && resources.hotspotDom) {
132
- // Attach click handler if not already attached
133
- if (!this._clickHandlersAttached.has(annotation)) {
134
- const callback = annotation.onClickCallback;
135
- if (callback) {
136
- this._clickHandlersAttached.add(annotation);
137
- resources.hotspotDom.addEventListener('pointerdown', e => {
138
- callback(e.clientX, e.clientY);
139
- });
140
- }
32
+ initialize() {
33
+ const canvas = this.app.graphicsDevice.canvas;
34
+ const container = canvas.parentElement?.querySelector('[data-annotation-container]');
35
+ if (container) {
36
+ this._customParentDom = container;
37
+ this._parentDom = container;
141
38
  }
142
-
143
- // Disable/enable the entity itself based on visibility
144
- const isVisible = annotation.enabled !== undefined ? annotation.enabled : true;
145
-
146
- // Hide tooltip if the active annotation is hidden
147
- if (this._activeAnnotation === annotation && !isVisible) {
148
- this._tooltipDom.style.visibility = 'hidden';
149
- this._tooltipDom.style.opacity = '0';
150
- this._activeAnnotation = null;
39
+ super.initialize();
40
+ this._loadIconImages();
41
+ }
42
+ _loadIconImages() {
43
+ const fillColor = this._hotspotColor?.toString() ?? '#ffffff';
44
+ Object.keys(ANNOTATION_ICONS).forEach((iconType)=>{
45
+ const name = ANNOTATION_ICONS[iconType];
46
+ const markup = renderToStaticMarkup(createElement(Icon, {
47
+ name,
48
+ fillColor
49
+ })).replace(/^<svg/, '<svg width="128" height="128"');
50
+ const image = new Image();
51
+ image.onload = ()=>this._refreshIcon(iconType);
52
+ image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(markup)}`;
53
+ this._iconImages.set(iconType, image);
54
+ });
55
+ }
56
+ _refreshIcon(iconType) {
57
+ const annotationResources = this._annotationResources;
58
+ if (!annotationResources) return;
59
+ annotationResources.forEach((_resources, annotation)=>{
60
+ if ((annotation.icon ?? 'text') === iconType) this._applyAnnotationIcon(annotation);
61
+ });
62
+ }
63
+ update() {
64
+ const annotationResources = this._annotationResources;
65
+ if (!annotationResources) return;
66
+ annotationResources.forEach((resources, annotation)=>{
67
+ if (resources && resources.hotspotDom) {
68
+ if (!this._clickHandlersAttached.has(annotation)) {
69
+ const callback = annotation.onClickCallback;
70
+ if (callback) {
71
+ this._clickHandlersAttached.add(annotation);
72
+ resources.hotspotDom.addEventListener('pointerdown', (e)=>{
73
+ callback(e.clientX, e.clientY);
74
+ });
75
+ }
76
+ }
77
+ const isVisible = void 0 !== annotation.enabled ? annotation.enabled : true;
78
+ if (this._activeAnnotation === annotation && !isVisible) {
79
+ this._tooltipDom.style.visibility = 'hidden';
80
+ this._tooltipDom.style.opacity = '0';
81
+ this._activeAnnotation = null;
82
+ }
83
+ }
84
+ });
85
+ }
86
+ _registerAnnotation(annotation) {
87
+ super._registerAnnotation(annotation);
88
+ this._applyAnnotationIcon(annotation);
89
+ const resources = this._annotationResources.get(annotation);
90
+ if (resources) {
91
+ resources.baseEntity.enabled = false;
92
+ resources.overlayEntity.enabled = false;
151
93
  }
152
- }
153
- });
154
- }
155
-
156
- /**
157
- * Override annotation registration to draw the media icon onto the hotspot
158
- * texture once the annotation (and its icon) is available.
159
- * @private
160
- */
161
- _registerAnnotation(annotation) {
162
- super._registerAnnotation(annotation);
163
- this._applyAnnotationIcon(annotation);
164
-
165
- // Keep the hotspot mesh hidden until the first scale pass sizes it. The annotation entity
166
- // starts at unit scale, so the plane would render a metre across for the frames before
167
- // _updateAnnotationRotationAndScale runs.
168
- const resources = this._annotationResources.get(annotation);
169
- if (resources) {
170
- resources.baseEntity.enabled = false;
171
- resources.overlayEntity.enabled = false;
172
94
  }
173
- }
174
-
175
- /**
176
- * Recreates the hotspot texture with the appropriate media icon drawn on it.
177
- * @private
178
- */
179
- _applyAnnotationIcon(annotation) {
180
- const resources = this._annotationResources.get(annotation);
181
- if (!resources) {
182
- return;
95
+ _applyAnnotationIcon(annotation) {
96
+ const resources = this._annotationResources.get(annotation);
97
+ if (!resources) return;
98
+ const icon = annotation.icon ?? 'text';
99
+ resources.texture.destroy();
100
+ resources.texture = this._createHotspotTexture(annotation.label, HOTSPOT_TEXTURE_SIZE, 6, icon);
101
+ resources.materials.forEach((material)=>{
102
+ material.emissiveMap = resources.texture;
103
+ material.opacityMap = resources.texture;
104
+ material.update();
105
+ });
183
106
  }
184
- const icon = annotation.icon ?? 'text';
185
- resources.texture.destroy();
186
- resources.texture = this._createHotspotTexture(annotation.label, HOTSPOT_TEXTURE_SIZE, 6, icon);
187
- resources.materials.forEach(material => {
188
- material.emissiveMap = resources.texture;
189
- material.opacityMap = resources.texture;
190
- material.update();
191
- });
192
- }
193
-
194
- /**
195
- * Draws the icon image onto the hotspot texture canvas, scaled to fit the
196
- * circle. Does nothing if the image has not finished loading yet; the
197
- * annotation is redrawn once it has (see `_refreshIcon`).
198
- * @private
199
- */
200
- _drawIcon(ctx, icon, size) {
201
- const image = this._iconImages.get(icon);
202
- if (!image || !image.complete || image.naturalWidth === 0) {
203
- return;
107
+ _drawIcon(ctx, icon, size) {
108
+ const image = this._iconImages.get(icon);
109
+ if (!image || !image.complete || 0 === image.naturalWidth) return;
110
+ const iconSize = 0.5 * size;
111
+ const offset = (size - iconSize) / 2;
112
+ ctx.drawImage(image, offset, offset, iconSize, iconSize);
204
113
  }
205
- const iconSize = size * 0.5;
206
- const offset = (size - iconSize) / 2;
207
- ctx.drawImage(image, offset, offset, iconSize, iconSize);
208
- }
209
-
210
- /**
211
- * Keep the media icon drawn on the hotspot texture. The label text itself
212
- * is not rendered on the hotspot, so we simply redraw with the icon.
213
- * @private
214
- */
215
- _onLabelChange(annotation) {
216
- this._applyAnnotationIcon(annotation);
217
- }
218
-
219
- /**
220
- * The hotspot colors are baked into the texture in `_createHotspotTexture`,
221
- * so the material emissive is kept white to let those colors render as drawn.
222
- * @private
223
- */
224
- _createHotspotMaterial(texture, options) {
225
- const material = super._createHotspotMaterial(texture, options);
226
- material.emissive.set(1, 1, 1);
227
- material.update();
228
- return material;
229
- }
230
-
231
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
232
- _setAnnotationHover(_annotation, _hover) {
233
- // No-op: the hover growth is handled in _updateAnnotationRotationAndScale
234
- // (via _hoverAnnotation); the texture is identical in both states.
235
- }
236
- _updateAllAnnotationColors() {
237
- // No-op: hotspot colors are baked into the texture (see _createHotspotMaterial).
238
- }
239
-
240
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
241
- _showTooltip(_annotation) {
242
- // No-op: AnnotationPanel handles all annotation UI, this removes default behavior
243
- }
244
-
245
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
246
- _onTextChange(_annotation, _text) {
247
- // No-op: AnnotationPanel handles all annotation UI, this removes default behavior
248
- }
249
-
250
- /**
251
- * Override position update to adjust for canvas offset and respect visibility.
252
- * @private
253
- */
254
- _updateAnnotationPositions(annotation, resources, screenPos) {
255
- const canvas = this.app.graphicsDevice.canvas;
256
- const rect = canvas.getBoundingClientRect();
257
-
258
- // If using a custom parent (positioned container), use raw screen coordinates
259
- // Otherwise, adjust for canvas position in document
260
- const offsetX = this._customParentDom ? 0 : rect.left + window.scrollX;
261
- const offsetY = this._customParentDom ? 0 : rect.top + window.scrollY;
262
-
263
- // Check if annotation is visible (defaults to true if not specified)
264
- const isVisible = annotation.enabled !== undefined ? annotation.enabled : true;
265
- resources.hotspotDom.style.display = isVisible ? 'block' : 'none';
266
- resources.hotspotDom.style.left = `${screenPos.x + offsetX}px`;
267
- resources.hotspotDom.style.top = `${screenPos.y + offsetY}px`;
268
- if (this._activeAnnotation === annotation) {
269
- this._tooltipDom.style.display = isVisible ? 'block' : 'none';
270
- this._tooltipDom.style.left = `${screenPos.x + offsetX}px`;
271
- this._tooltipDom.style.top = `${screenPos.y + offsetY}px`;
114
+ _onLabelChange(annotation) {
115
+ this._applyAnnotationIcon(annotation);
272
116
  }
273
- if (annotation.onScreenPositionUpdateCallback) {
274
- // Rendered hotspot diameter (px), set by _updateAnnotationRotationAndScale.
275
- // Parsed from the style rather than measured to avoid forcing a reflow.
276
- const hotspotSize = parseFloat(resources.hotspotDom.style.width) || undefined;
277
- annotation.onScreenPositionUpdateCallback(screenPos.x + offsetX, screenPos.y + offsetY, hotspotSize);
117
+ _createHotspotMaterial(texture, options) {
118
+ const material = super._createHotspotMaterial(texture, options);
119
+ material.emissive.set(1, 1, 1);
120
+ material.update();
121
+ return material;
278
122
  }
279
- }
280
-
281
- /**
282
- * Override to also disable the hotspot mesh (not just the DOM) when the annotation is behind the
283
- * camera. The base implementation leaves the mesh enabled, so a not-yet-scaled plane would stay
284
- * in view at its full unit size until the camera moves the anchor in front.
285
- * @private
286
- */
287
- _hideAnnotationElements(annotation, resources) {
288
- super._hideAnnotationElements(annotation, resources);
289
- resources.baseEntity.enabled = false;
290
- resources.overlayEntity.enabled = false;
291
- }
292
-
293
- /**
294
- * Override the scale update to clamp the world size to a maximum value.
295
- * Also scales the hotspot DOM element to match.
296
- * @private
297
- */
298
- _updateAnnotationRotationAndScale(annotation) {
299
- const cameraRotation = this._camera.getRotation();
300
- annotation.entity.setRotation(cameraRotation);
301
- annotation.entity.rotateLocal(90, 0, 0);
302
- const cameraPos = this._camera.getPosition();
303
- const distance = annotation.entity.getPosition().distance(cameraPos);
304
- const canvas = this.app.graphicsDevice.canvas;
305
- const screenHeight = canvas.clientHeight;
306
- const projMatrix = this._camera.camera.projectionMatrix;
307
-
308
- // The world size that projects to _hotspotSize screen pixels at this distance.
309
- const targetWorldSize = this._hotspotSize / screenHeight * (2 * distance / projMatrix.data[5]);
310
-
311
- // Annotations hang off an unscaled parent, so local scale is world scale and maxWorldSize
312
- // clamps the hotspot at a size in metres.
313
- const clampedScale = Math.min(targetWorldSize, this._maxWorldSize);
314
- const hovered = this._hoverAnnotation === annotation;
315
- const hoverScale = hovered ? HOTSPOT_HOVER_SCALE : 1;
316
- const entityScale = clampedScale * hoverScale;
317
- annotation.entity.setLocalScale(entityScale, entityScale, entityScale);
318
-
319
- // Scale the hotspot DOM element proportionally when clamped, including the
320
- // hover growth so it tracks the visible circle. This is a pure screen-space
321
- // size, so it uses the true _hotspotSize and the clamp ratio only.
322
- const resources = this._annotationResources.get(annotation);
323
- if (resources) {
324
- // This branch only runs for annotations in front of the camera, and the scale above is now
325
- // clamped, so it is safe to reveal the mesh (hidden on register / when behind the camera).
326
- resources.baseEntity.enabled = true;
327
- resources.overlayEntity.enabled = true;
123
+ _setAnnotationHover(_annotation, _hover) {}
124
+ _updateAllAnnotationColors() {}
125
+ _showTooltip(_annotation) {}
126
+ _onTextChange(_annotation, _text) {}
127
+ _updateAnnotationPositions(annotation, resources, screenPos) {
128
+ const canvas = this.app.graphicsDevice.canvas;
129
+ const rect = canvas.getBoundingClientRect();
130
+ const offsetX = this._customParentDom ? 0 : rect.left + window.scrollX;
131
+ const offsetY = this._customParentDom ? 0 : rect.top + window.scrollY;
132
+ const isVisible = void 0 !== annotation.enabled ? annotation.enabled : true;
133
+ resources.hotspotDom.style.display = isVisible ? 'block' : 'none';
134
+ resources.hotspotDom.style.left = `${screenPos.x + offsetX}px`;
135
+ resources.hotspotDom.style.top = `${screenPos.y + offsetY}px`;
136
+ if (this._activeAnnotation === annotation) {
137
+ this._tooltipDom.style.display = isVisible ? 'block' : 'none';
138
+ this._tooltipDom.style.left = `${screenPos.x + offsetX}px`;
139
+ this._tooltipDom.style.top = `${screenPos.y + offsetY}px`;
140
+ }
141
+ if (annotation.onScreenPositionUpdateCallback) {
142
+ const hotspotSize = parseFloat(resources.hotspotDom.style.width) || void 0;
143
+ annotation.onScreenPositionUpdateCallback(screenPos.x + offsetX, screenPos.y + offsetY, hotspotSize);
144
+ }
328
145
  }
329
- if (resources && resources.hotspotDom) {
330
- const clampRatio = clampedScale / targetWorldSize;
331
- const baseSize = this._hotspotSize + 5; // Match the +5 from the stylesheet
332
- const scaledSize = baseSize * clampRatio * hoverScale;
333
- resources.hotspotDom.style.width = `${scaledSize}px`;
334
- resources.hotspotDom.style.height = `${scaledSize}px`;
146
+ _hideAnnotationElements(annotation, resources) {
147
+ super._hideAnnotationElements(annotation, resources);
148
+ resources.baseEntity.enabled = false;
149
+ resources.overlayEntity.enabled = false;
335
150
  }
336
- }
337
-
338
- /**
339
- * Override to create hotspot texture with custom background color.
340
- * @private
341
- */
342
- _createHotspotTexture(label, size = HOTSPOT_TEXTURE_SIZE, borderWidth = 6, icon) {
343
- const canvas = document.createElement('canvas');
344
- canvas.width = size;
345
- canvas.height = size;
346
- const ctx = canvas.getContext('2d');
347
- if (!ctx) {
348
- throw new Error('Failed to get canvas 2d context');
151
+ _updateAnnotationRotationAndScale(annotation) {
152
+ const cameraRotation = this._camera.getRotation();
153
+ annotation.entity.setRotation(cameraRotation);
154
+ annotation.entity.rotateLocal(90, 0, 0);
155
+ const cameraPos = this._camera.getPosition();
156
+ const distance = annotation.entity.getPosition().distance(cameraPos);
157
+ const canvas = this.app.graphicsDevice.canvas;
158
+ const screenHeight = canvas.clientHeight;
159
+ const projMatrix = this._camera.camera.projectionMatrix;
160
+ const targetWorldSize = this._hotspotSize / screenHeight * (2 * distance / projMatrix.data[5]);
161
+ const clampedScale = Math.min(targetWorldSize, this._maxWorldSize);
162
+ const hovered = this._hoverAnnotation === annotation;
163
+ const hoverScale = hovered ? HOTSPOT_HOVER_SCALE : 1;
164
+ const entityScale = clampedScale * hoverScale;
165
+ annotation.entity.setLocalScale(entityScale, entityScale, entityScale);
166
+ const resources = this._annotationResources.get(annotation);
167
+ if (resources) {
168
+ resources.baseEntity.enabled = true;
169
+ resources.overlayEntity.enabled = true;
170
+ }
171
+ if (resources && resources.hotspotDom) {
172
+ const clampRatio = clampedScale / targetWorldSize;
173
+ const baseSize = this._hotspotSize + 5;
174
+ const scaledSize = baseSize * clampRatio * hoverScale;
175
+ resources.hotspotDom.style.width = `${scaledSize}px`;
176
+ resources.hotspotDom.style.height = `${scaledSize}px`;
177
+ }
349
178
  }
350
-
351
- // First clear with stroke color at zero alpha
352
- ctx.fillStyle = 'white';
353
- ctx.globalAlpha = 0;
354
- ctx.fillRect(0, 0, size, size);
355
- ctx.globalAlpha = 1.0;
356
-
357
- // Draw circle with custom background color
358
- const centerX = size / 2;
359
- const centerY = size / 2;
360
- const radius = size / 2 - 4;
361
-
362
- // The icon fill and border share the hotspot color
363
- const foregroundColor = this._hotspotColor?.toString() ?? '#ffffff';
364
-
365
- // Draw main circle
366
- ctx.beginPath();
367
- ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
368
- ctx.fillStyle = this._hotspotBackgroundColor;
369
- ctx.fill();
370
-
371
- // Draw border
372
- ctx.beginPath();
373
- ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
374
- ctx.lineWidth = borderWidth;
375
- ctx.strokeStyle = foregroundColor;
376
- ctx.stroke();
377
-
378
- // Draw the media icon inside the circle
379
- if (icon) {
380
- this._drawIcon(ctx, icon, size);
179
+ _createHotspotTexture(label, size = HOTSPOT_TEXTURE_SIZE, borderWidth = 6, icon) {
180
+ const canvas = document.createElement('canvas');
181
+ canvas.width = size;
182
+ canvas.height = size;
183
+ const ctx = canvas.getContext('2d');
184
+ if (!ctx) throw new Error('Failed to get canvas 2d context');
185
+ ctx.fillStyle = 'white';
186
+ ctx.globalAlpha = 0;
187
+ ctx.fillRect(0, 0, size, size);
188
+ ctx.globalAlpha = 1.0;
189
+ const centerX = size / 2;
190
+ const centerY = size / 2;
191
+ const radius = size / 2 - 4;
192
+ const foregroundColor = this._hotspotColor?.toString() ?? '#ffffff';
193
+ ctx.beginPath();
194
+ ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI);
195
+ ctx.fillStyle = this._hotspotBackgroundColor;
196
+ ctx.fill();
197
+ ctx.beginPath();
198
+ ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI);
199
+ ctx.lineWidth = borderWidth;
200
+ ctx.strokeStyle = foregroundColor;
201
+ ctx.stroke();
202
+ if (icon) this._drawIcon(ctx, icon, size);
203
+ const imageData = ctx.getImageData(0, 0, size, size);
204
+ const data = imageData.data;
205
+ return new Texture(this.app.graphicsDevice, {
206
+ width: size,
207
+ height: size,
208
+ format: PIXELFORMAT_RGBA8,
209
+ magFilter: FILTER_LINEAR,
210
+ minFilter: FILTER_LINEAR,
211
+ mipmaps: false,
212
+ levels: [
213
+ new Uint8Array(data.buffer)
214
+ ]
215
+ });
381
216
  }
382
-
383
- // Get pixel data
384
- const imageData = ctx.getImageData(0, 0, size, size);
385
- const data = imageData.data;
386
-
387
- // Create and return the texture
388
- return new Texture(this.app.graphicsDevice, {
389
- width: size,
390
- height: size,
391
- format: PIXELFORMAT_RGBA8,
392
- magFilter: FILTER_LINEAR,
393
- minFilter: FILTER_LINEAR,
394
- mipmaps: false,
395
- levels: [new Uint8Array(data.buffer)]
396
- });
397
- }
398
217
  }
399
-
400
218
  export { TfAnnotationManager };