@terraware/web-components 4.2.13 → 4.2.14-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 (296) hide show
  1. package/components/AntSwitch/index.js +8 -14
  2. package/components/Autocomplete/Autocomplete.js +67 -70
  3. package/components/Badge/index.js +16 -20
  4. package/components/BusySpinner/index.js +30 -35
  5. package/components/Button/Button.js +23 -27
  6. package/components/Button/button.test.js +8 -10
  7. package/components/Checkbox.js +10 -19
  8. package/components/Confirm/index.js +44 -51
  9. package/components/DatePicker/DatePicker.js +116 -117
  10. package/components/DialogBox/DialogBox.js +76 -66
  11. package/components/Divisor.js +15 -20
  12. package/components/Dropdown.js +54 -61
  13. package/components/EditableTable/index.js +129 -135
  14. package/components/ErrorBox/ErrorBox.js +37 -36
  15. package/components/FileChooser/index.js +73 -77
  16. package/components/FormBottomBar/index.js +63 -69
  17. package/components/Icon/Icon.js +14 -21
  18. package/components/Icon/icons/index.js +251 -257
  19. package/components/IconTooltip/index.js +29 -34
  20. package/components/Map/MapBox.js +199 -220
  21. package/components/Map/MapContainer.js +14 -18
  22. package/components/Map/MapDateSliderControl.js +44 -46
  23. package/components/Map/MapDrawer.js +32 -33
  24. package/components/Map/MapLegend.js +96 -95
  25. package/components/Map/MapViewStyleControl.js +38 -48
  26. package/components/Map/ReactMapboxWrapper.js +2 -9
  27. package/components/Map/index.js +47 -55
  28. package/components/Map/types.js +2 -8
  29. package/components/Map/useMaintainLayerOrder.js +4 -11
  30. package/components/Markdown/index.js +14 -21
  31. package/components/Message/Message.js +59 -51
  32. package/components/MultiSelect/MultiSelect.test.js +22 -24
  33. package/components/MultiSelect/index.js +73 -69
  34. package/components/Navbar/NavFooter.js +8 -13
  35. package/components/Navbar/NavItem.js +43 -37
  36. package/components/Navbar/NavSection.js +13 -17
  37. package/components/Navbar/Navbar.js +35 -38
  38. package/components/Navbar/SubNavbar.js +8 -13
  39. package/components/Note.js +14 -19
  40. package/components/OverlayModal/OverlayModal.js +36 -37
  41. package/components/PageForm/index.js +30 -35
  42. package/components/PhotoChooser/index.js +93 -98
  43. package/components/Pill/index.js +29 -31
  44. package/components/PillList/index.js +16 -22
  45. package/components/PlacementWrapper/index.js +20 -25
  46. package/components/PopoverMenu/Popover.js +33 -37
  47. package/components/PopoverMenu/index.js +41 -46
  48. package/components/PopoverMultiSelect/index.js +18 -21
  49. package/components/ProgressCircle/ProgressCircle.js +23 -26
  50. package/components/RadioButton.js +7 -14
  51. package/components/Select/Select.js +8 -13
  52. package/components/Select/SelectT.js +110 -108
  53. package/components/Separator/index.js +8 -14
  54. package/components/Size.js +1 -5
  55. package/components/Slider/index.js +7 -13
  56. package/components/SummaryBox.js +36 -39
  57. package/components/Tabs/index.js +75 -77
  58. package/components/TextTruncated/index.js +64 -65
  59. package/components/Textfield/Textfield.js +100 -95
  60. package/components/Textfield/TruncatedTextArea.js +44 -46
  61. package/components/TimelineSlider/TimelineMarker.js +24 -28
  62. package/components/TimelineSlider/TimelineRail.js +5 -11
  63. package/components/TimelineSlider/index.js +47 -51
  64. package/components/Tooltip/Tooltip.js +21 -26
  65. package/components/ViewPhotosDialog/index.js +58 -62
  66. package/components/VirtualWalkthrough/Annotation.d.ts +41 -0
  67. package/components/VirtualWalkthrough/Annotation.d.ts.map +1 -0
  68. package/components/VirtualWalkthrough/Annotation.js +197 -0
  69. package/components/VirtualWalkthrough/AnnotationEditPane.d.ts +20 -0
  70. package/components/VirtualWalkthrough/AnnotationEditPane.d.ts.map +1 -0
  71. package/components/VirtualWalkthrough/AnnotationEditPane.js +136 -0
  72. package/components/VirtualWalkthrough/AnnotationPanel.d.ts +13 -0
  73. package/components/VirtualWalkthrough/AnnotationPanel.d.ts.map +1 -0
  74. package/components/VirtualWalkthrough/AnnotationPanel.js +196 -0
  75. package/components/VirtualWalkthrough/Application.d.ts +9 -0
  76. package/components/VirtualWalkthrough/Application.d.ts.map +1 -0
  77. package/components/VirtualWalkthrough/Application.js +32 -0
  78. package/components/VirtualWalkthrough/AutoRotator.d.ts +109 -0
  79. package/components/VirtualWalkthrough/AutoRotator.d.ts.map +1 -0
  80. package/components/VirtualWalkthrough/AutoRotator.js +217 -0
  81. package/components/VirtualWalkthrough/BlockingSpinner.d.ts +3 -0
  82. package/components/VirtualWalkthrough/BlockingSpinner.d.ts.map +1 -0
  83. package/components/VirtualWalkthrough/BlockingSpinner.js +27 -0
  84. package/components/VirtualWalkthrough/BoundaryRing.d.ts +9 -0
  85. package/components/VirtualWalkthrough/BoundaryRing.d.ts.map +1 -0
  86. package/components/VirtualWalkthrough/BoundaryRing.js +33 -0
  87. package/components/VirtualWalkthrough/CameraInfo.d.ts +16 -0
  88. package/components/VirtualWalkthrough/CameraInfo.d.ts.map +1 -0
  89. package/components/VirtualWalkthrough/CameraInfo.js +88 -0
  90. package/components/VirtualWalkthrough/ControlsInfoPane.d.ts +37 -0
  91. package/components/VirtualWalkthrough/ControlsInfoPane.d.ts.map +1 -0
  92. package/components/VirtualWalkthrough/ControlsInfoPane.js +196 -0
  93. package/components/VirtualWalkthrough/GradientSky.d.ts +9 -0
  94. package/components/VirtualWalkthrough/GradientSky.d.ts.map +1 -0
  95. package/components/VirtualWalkthrough/GradientSky.js +73 -0
  96. package/components/VirtualWalkthrough/SplatControls.d.ts +50 -0
  97. package/components/VirtualWalkthrough/SplatControls.d.ts.map +1 -0
  98. package/components/VirtualWalkthrough/SplatControls.js +252 -0
  99. package/components/VirtualWalkthrough/SplatCrop.d.ts +8 -0
  100. package/components/VirtualWalkthrough/SplatCrop.d.ts.map +1 -0
  101. package/components/VirtualWalkthrough/SplatCrop.js +18 -0
  102. package/components/VirtualWalkthrough/SplatFadeCrop.d.ts +8 -0
  103. package/components/VirtualWalkthrough/SplatFadeCrop.d.ts.map +1 -0
  104. package/components/VirtualWalkthrough/SplatFadeCrop.js +18 -0
  105. package/components/VirtualWalkthrough/SplatModel.d.ts +15 -0
  106. package/components/VirtualWalkthrough/SplatModel.d.ts.map +1 -0
  107. package/components/VirtualWalkthrough/SplatModel.js +60 -0
  108. package/components/VirtualWalkthrough/SplatRevealRain.d.ts +18 -0
  109. package/components/VirtualWalkthrough/SplatRevealRain.d.ts.map +1 -0
  110. package/components/VirtualWalkthrough/SplatRevealRain.js +38 -0
  111. package/components/VirtualWalkthrough/TfAnnotationManager.d.ts +110 -0
  112. package/components/VirtualWalkthrough/TfAnnotationManager.d.ts.map +1 -0
  113. package/components/VirtualWalkthrough/TfAnnotationManager.js +368 -0
  114. package/components/VirtualWalkthrough/TfXrNavigation.d.ts +15 -0
  115. package/components/VirtualWalkthrough/TfXrNavigation.d.ts.map +1 -0
  116. package/components/VirtualWalkthrough/TfXrNavigation.js +24 -0
  117. package/components/VirtualWalkthrough/annotation-styles.css +5 -0
  118. package/components/VirtualWalkthrough/application-styles.css +4 -0
  119. package/components/VirtualWalkthrough/boundary-ring.d.ts +47 -0
  120. package/components/VirtualWalkthrough/boundary-ring.d.ts.map +1 -0
  121. package/components/VirtualWalkthrough/boundary-ring.js +125 -0
  122. package/components/VirtualWalkthrough/boundary-ring.test.js +109 -0
  123. package/components/VirtualWalkthrough/groundPlane.d.ts +14 -0
  124. package/components/VirtualWalkthrough/groundPlane.d.ts.map +1 -0
  125. package/components/VirtualWalkthrough/groundPlane.js +47 -0
  126. package/components/VirtualWalkthrough/groundPlane.test.js +45 -0
  127. package/components/VirtualWalkthrough/gsplat-fade-crop-effect.d.ts +19 -0
  128. package/components/VirtualWalkthrough/gsplat-fade-crop-effect.d.ts.map +1 -0
  129. package/components/VirtualWalkthrough/gsplat-fade-crop-effect.js +119 -0
  130. package/components/VirtualWalkthrough/walkthrough-camera.d.ts +77 -0
  131. package/components/VirtualWalkthrough/walkthrough-camera.d.ts.map +1 -0
  132. package/components/VirtualWalkthrough/walkthrough-camera.js +320 -0
  133. package/components/svg/BlobbyGrayIconImage.js +36 -41
  134. package/components/svg/BlobbyGrayIconImport.js +36 -41
  135. package/components/svg/BlobbyGrayIconUploadToTheCloud.js +36 -41
  136. package/components/svg/BlobbyIconCloudChecked.js +161 -158
  137. package/components/svg/BlobbyIconExportOrUploadPhoto.js +161 -158
  138. package/components/svg/BlobbyIconFolder.js +161 -158
  139. package/components/svg/BlobbyIconGraphReport.js +161 -158
  140. package/components/svg/BlobbyIconHappy.js +161 -158
  141. package/components/svg/BlobbyIconHeartMonitor.js +161 -158
  142. package/components/svg/BlobbyIconLeaf.js +161 -158
  143. package/components/svg/BlobbyIconLibrary.js +161 -158
  144. package/components/svg/BlobbyIconNursery.js +163 -160
  145. package/components/svg/BlobbyIconOrganization.js +161 -158
  146. package/components/svg/BlobbyIconParchment.js +161 -158
  147. package/components/svg/BlobbyIconPeople.js +161 -158
  148. package/components/svg/BlobbyIconSeedBank.js +161 -158
  149. package/components/svg/BlobbyIconSeedling.js +161 -158
  150. package/components/svg/BlobbyIconSeeds.js +161 -158
  151. package/components/svg/BlobbyIconSite.js +164 -161
  152. package/components/svg/BlobbyIconSparkles.js +161 -158
  153. package/components/svg/BlobbyIconWrench.js +161 -158
  154. package/components/svg/Bug.js +18 -27
  155. package/components/svg/Calendar.js +18 -27
  156. package/components/svg/CaretDown.js +18 -27
  157. package/components/svg/CaretLeft.js +18 -27
  158. package/components/svg/CaretRight.js +18 -27
  159. package/components/svg/CaretUp.js +18 -27
  160. package/components/svg/ChevronDown.js +18 -27
  161. package/components/svg/ChevronUp.js +18 -27
  162. package/components/svg/Close.js +18 -27
  163. package/components/svg/Critical.js +18 -27
  164. package/components/svg/Edit.js +161 -158
  165. package/components/svg/Error.js +18 -27
  166. package/components/svg/Filter.js +18 -27
  167. package/components/svg/Folder.js +18 -27
  168. package/components/svg/Help.js +18 -27
  169. package/components/svg/Home.js +18 -27
  170. package/components/svg/IconAdd.js +18 -27
  171. package/components/svg/IconArrowRight.js +19 -28
  172. package/components/svg/IconBusinessNetwork.js +18 -27
  173. package/components/svg/IconCalendar.js +18 -27
  174. package/components/svg/IconCancel.js +18 -27
  175. package/components/svg/IconChargingBattery.js +18 -27
  176. package/components/svg/IconChecklist.js +19 -28
  177. package/components/svg/IconCheckmark.js +18 -27
  178. package/components/svg/IconCoinInHand.js +18 -27
  179. package/components/svg/IconColumns.js +18 -27
  180. package/components/svg/IconComment.js +18 -27
  181. package/components/svg/IconDashboard.js +18 -27
  182. package/components/svg/IconDataMigration.js +18 -27
  183. package/components/svg/IconDocument.js +19 -28
  184. package/components/svg/IconDownloadFromTheCloud.js +19 -28
  185. package/components/svg/IconEdit.js +19 -28
  186. package/components/svg/IconExpand.js +19 -28
  187. package/components/svg/IconExport.js +18 -27
  188. package/components/svg/IconExternalLink.js +18 -27
  189. package/components/svg/IconEye.js +18 -27
  190. package/components/svg/IconEyeOff.js +18 -27
  191. package/components/svg/IconFile.js +18 -27
  192. package/components/svg/IconFolder.js +18 -27
  193. package/components/svg/IconFullScreen.js +18 -27
  194. package/components/svg/IconFutures.js +18 -27
  195. package/components/svg/IconGraphReport.js +18 -27
  196. package/components/svg/IconHeartMonitor.js +18 -27
  197. package/components/svg/IconHelp.js +19 -28
  198. package/components/svg/IconHistory.js +18 -27
  199. package/components/svg/IconImport.js +18 -27
  200. package/components/svg/IconIndex.js +18 -27
  201. package/components/svg/IconLayers.js +18 -27
  202. package/components/svg/IconLibrary.js +18 -27
  203. package/components/svg/IconList.js +18 -27
  204. package/components/svg/IconLivePlant.js +18 -27
  205. package/components/svg/IconLocations.js +18 -27
  206. package/components/svg/IconMail.js +18 -27
  207. package/components/svg/IconManager.js +18 -27
  208. package/components/svg/IconMarker.js +18 -27
  209. package/components/svg/IconMenu.js +18 -27
  210. package/components/svg/IconMenuHorizontal.js +18 -27
  211. package/components/svg/IconModule.js +18 -27
  212. package/components/svg/IconMyLocation.js +18 -27
  213. package/components/svg/IconNote.js +19 -28
  214. package/components/svg/IconNursery.js +18 -27
  215. package/components/svg/IconOrg.js +18 -27
  216. package/components/svg/IconParchment.js +18 -27
  217. package/components/svg/IconPhoto.js +18 -27
  218. package/components/svg/IconPlantsFilled.js +18 -27
  219. package/components/svg/IconRedo.js +18 -27
  220. package/components/svg/IconRestorationSite.js +18 -27
  221. package/components/svg/IconSeedBank.js +18 -27
  222. package/components/svg/IconSeedling.js +18 -27
  223. package/components/svg/IconSettings.js +18 -27
  224. package/components/svg/IconSlice.js +18 -27
  225. package/components/svg/IconSubmit.js +18 -27
  226. package/components/svg/IconSubtract.js +19 -28
  227. package/components/svg/IconSynced.js +18 -27
  228. package/components/svg/IconTrashCan.js +18 -27
  229. package/components/svg/IconTreasureMap.js +18 -27
  230. package/components/svg/IconUnavailable.js +18 -27
  231. package/components/svg/IconUndo.js +18 -27
  232. package/components/svg/IconVariable.js +18 -27
  233. package/components/svg/IconVideo.js +19 -28
  234. package/components/svg/IconWifi.js +18 -27
  235. package/components/svg/Info.js +18 -27
  236. package/components/svg/Key.js +18 -27
  237. package/components/svg/Leaf.js +18 -27
  238. package/components/svg/Lock.js +18 -27
  239. package/components/svg/Logo.js +40 -45
  240. package/components/svg/MenuVertical.js +18 -27
  241. package/components/svg/Notification.js +18 -27
  242. package/components/svg/Person.js +18 -27
  243. package/components/svg/Plus.js +18 -27
  244. package/components/svg/RestorationSite.js +18 -27
  245. package/components/svg/Search.js +18 -27
  246. package/components/svg/Seeds.js +18 -27
  247. package/components/svg/Site.js +18 -27
  248. package/components/svg/Sparkles.js +18 -27
  249. package/components/svg/Species.js +18 -27
  250. package/components/svg/Species2.js +161 -158
  251. package/components/svg/Spinner.js +27 -36
  252. package/components/svg/Star.js +18 -27
  253. package/components/svg/Success.js +18 -27
  254. package/components/svg/SuccessFilled.js +18 -27
  255. package/components/svg/TerrawareLogoDesktop.js +44 -49
  256. package/components/svg/TerrawareLogoMobile.js +20 -29
  257. package/components/svg/Touchscreen.js +18 -27
  258. package/components/svg/UploadCloud.js +161 -158
  259. package/components/svg/Warning.js +18 -27
  260. package/components/svg/WelcomeClipboard.js +130 -135
  261. package/components/svg/index.js +127 -895
  262. package/components/table/EnhancedTableToolbar.js +45 -47
  263. package/components/table/EnhancedTableToolbarV2.js +18 -26
  264. package/components/table/TableCellRenderer.js +137 -143
  265. package/components/table/TableHeader.js +45 -47
  266. package/components/table/TableHeaderItem.js +65 -64
  267. package/components/table/density.js +3 -11
  268. package/components/table/index.js +225 -218
  269. package/components/table/sort.js +8 -18
  270. package/components/table/sort.test.js +6 -8
  271. package/components/table/types.js +1 -5
  272. package/components/types/index.js +1 -5
  273. package/hooks/useBoolean.d.ts +4 -0
  274. package/hooks/useBoolean.d.ts.map +1 -0
  275. package/hooks/useBoolean.js +12 -0
  276. package/hooks/useCameraPosition.d.ts +8 -0
  277. package/hooks/useCameraPosition.d.ts.map +1 -0
  278. package/hooks/useCameraPosition.js +33 -0
  279. package/index.js +57 -398
  280. package/license-report.html +1 -1
  281. package/package.json +10 -3
  282. package/style-dictionary-dist/TerrawareTheme.js +1 -7
  283. package/theme.js +7 -14
  284. package/utils/color.d.ts +2 -0
  285. package/utils/color.d.ts.map +1 -0
  286. package/utils/color.js +14 -0
  287. package/utils/date.js +13 -24
  288. package/utils/date.test.js +40 -43
  289. package/utils/index.js +5 -53
  290. package/utils/preventDefaultEvent.js +1 -7
  291. package/utils/text.js +2 -9
  292. package/utils/text.test.js +9 -11
  293. package/utils/useDeviceInfo.js +10 -16
  294. package/virtualWalkthrough.d.ts +31 -0
  295. package/virtualWalkthrough.d.ts.map +1 -0
  296. package/virtualWalkthrough.js +28 -0
@@ -0,0 +1,368 @@
1
+ import { createElement } from 'react';
2
+ import { renderToStaticMarkup } from 'react-dom/server';
3
+ import { FILTER_LINEAR, PIXELFORMAT_RGBA8, Texture } from 'playcanvas';
4
+ import { AnnotationManager as PcAnnotationManager } from 'playcanvas/scripts/esm/annotations.mjs';
5
+ import Icon from '../Icon/Icon';
6
+ // Maps each annotation icon type to a @terraware/web-components icon.
7
+ const ANNOTATION_ICONS = {
8
+ text: 'iconComment',
9
+ image: 'iconPhoto',
10
+ video: 'iconVideo'
11
+ };
12
+
13
+ // Pixel dimensions of the (square) hotspot texture canvas at rest.
14
+ const HOTSPOT_TEXTURE_SIZE = 64;
15
+
16
+ // How much larger the hotspot appears while hovered.
17
+ const HOTSPOT_HOVER_SCALE = 1.3;
18
+
19
+ /**
20
+ * Extended AnnotationManager that adds max size clamping for annotations
21
+ * and fixes positioning when canvas is not at document origin.
22
+ */
23
+ export class TfAnnotationManager extends PcAnnotationManager {
24
+ static scriptName = 'tfAnnotationManager';
25
+ _maxWorldSize = 1.0;
26
+ _customParentDom = null;
27
+ _clickHandlersAttached = new Set();
28
+ _hotspotBackgroundColor = '#2C8658';
29
+ _iconImages = new Map();
30
+
31
+ /**
32
+ * Maximum world-space size for annotations.
33
+ * This prevents annotations from growing beyond this size regardless of camera distance.
34
+ *
35
+ * @attribute
36
+ * @title Max World Size
37
+ * @type {number}
38
+ * @default 1.0
39
+ */
40
+ set maxWorldSize(value) {
41
+ this._maxWorldSize = value;
42
+ }
43
+ get maxWorldSize() {
44
+ return this._maxWorldSize;
45
+ }
46
+
47
+ /**
48
+ * Background color for the hotspot texture canvas (the circle fill color).
49
+ * This is a hex color string (e.g., '#2C8658') that's drawn on the texture canvas.
50
+ * Note: This is different from hotspotColor and hoverColor, which are PlayCanvas Color objects
51
+ * used for the material's emissive property.
52
+ *
53
+ * @attribute
54
+ * @title Hotspot Background Color
55
+ * @type {string}
56
+ * @default '#2C8658'
57
+ */
58
+ set hotspotBackgroundColor(value) {
59
+ this._hotspotBackgroundColor = value;
60
+ }
61
+ get hotspotBackgroundColor() {
62
+ return this._hotspotBackgroundColor;
63
+ }
64
+
65
+ /**
66
+ * Initialize with custom parent DOM if needed.
67
+ */
68
+ initialize() {
69
+ // Find a container div with data-annotation-container attribute
70
+ const canvas = this.app.graphicsDevice.canvas;
71
+ const container = canvas.parentElement?.querySelector('[data-annotation-container]');
72
+ if (container) {
73
+ this._customParentDom = container;
74
+ this._parentDom = container;
75
+ }
76
+
77
+ // Call parent initialize
78
+ super.initialize();
79
+ this._loadIconImages();
80
+ }
81
+
82
+ /**
83
+ * Renders each web-components icon to an SVG image once. When an image finishes
84
+ * loading, any annotations already using that icon are redrawn.
85
+ * @private
86
+ */
87
+ _loadIconImages() {
88
+ const fillColor = this._hotspotColor?.toString() ?? '#ffffff';
89
+ Object.keys(ANNOTATION_ICONS).forEach(iconType => {
90
+ const name = ANNOTATION_ICONS[iconType];
91
+ const markup = renderToStaticMarkup(/*#__PURE__*/createElement(Icon, {
92
+ name,
93
+ fillColor
94
+ })).replace(/^<svg/, '<svg width="128" height="128"');
95
+ const image = new Image();
96
+ image.onload = () => this._refreshIcon(iconType);
97
+ image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(markup)}`;
98
+ this._iconImages.set(iconType, image);
99
+ });
100
+ }
101
+
102
+ /**
103
+ * Redraws the texture for all annotations using the given icon, once its image
104
+ * has loaded.
105
+ * @private
106
+ */
107
+ _refreshIcon(iconType) {
108
+ const annotationResources = this._annotationResources;
109
+ if (!annotationResources) {
110
+ return;
111
+ }
112
+ annotationResources.forEach((_resources, annotation) => {
113
+ if ((annotation.icon ?? 'text') === iconType) {
114
+ this._applyAnnotationIcon(annotation);
115
+ }
116
+ });
117
+ }
118
+
119
+ /**
120
+ * Update loop to attach click handlers to annotations and manage visibility.
121
+ */
122
+ update() {
123
+ const annotationResources = this._annotationResources;
124
+ if (!annotationResources) {
125
+ return;
126
+ }
127
+
128
+ // Attach click handlers and manage visibility for all annotations
129
+ annotationResources.forEach((resources, annotation) => {
130
+ if (resources && resources.hotspotDom) {
131
+ // Attach click handler if not already attached
132
+ if (!this._clickHandlersAttached.has(annotation)) {
133
+ const callback = annotation.onClickCallback;
134
+ if (callback) {
135
+ this._clickHandlersAttached.add(annotation);
136
+ resources.hotspotDom.addEventListener('pointerdown', e => {
137
+ callback(e.clientX, e.clientY);
138
+ });
139
+ }
140
+ }
141
+
142
+ // Disable/enable the entity itself based on visibility
143
+ const isVisible = annotation.enabled !== undefined ? annotation.enabled : true;
144
+
145
+ // Hide tooltip if the active annotation is hidden
146
+ if (this._activeAnnotation === annotation && !isVisible) {
147
+ this._tooltipDom.style.visibility = 'hidden';
148
+ this._tooltipDom.style.opacity = '0';
149
+ this._activeAnnotation = null;
150
+ }
151
+ }
152
+ });
153
+ }
154
+
155
+ /**
156
+ * Override annotation registration to draw the media icon onto the hotspot
157
+ * texture once the annotation (and its icon) is available.
158
+ * @private
159
+ */
160
+ _registerAnnotation(annotation) {
161
+ super._registerAnnotation(annotation);
162
+ this._applyAnnotationIcon(annotation);
163
+ }
164
+
165
+ /**
166
+ * Recreates the hotspot texture with the appropriate media icon drawn on it.
167
+ * @private
168
+ */
169
+ _applyAnnotationIcon(annotation) {
170
+ const resources = this._annotationResources.get(annotation);
171
+ if (!resources) {
172
+ return;
173
+ }
174
+ const icon = annotation.icon ?? 'text';
175
+ resources.texture.destroy();
176
+ resources.texture = this._createHotspotTexture(annotation.label, HOTSPOT_TEXTURE_SIZE, 6, icon);
177
+ resources.materials.forEach(material => {
178
+ material.emissiveMap = resources.texture;
179
+ material.opacityMap = resources.texture;
180
+ material.update();
181
+ });
182
+ }
183
+
184
+ /**
185
+ * Draws the icon image onto the hotspot texture canvas, scaled to fit the
186
+ * circle. Does nothing if the image has not finished loading yet; the
187
+ * annotation is redrawn once it has (see `_refreshIcon`).
188
+ * @private
189
+ */
190
+ _drawIcon(ctx, icon, size) {
191
+ const image = this._iconImages.get(icon);
192
+ if (!image || !image.complete || image.naturalWidth === 0) {
193
+ return;
194
+ }
195
+ const iconSize = size * 0.5;
196
+ const offset = (size - iconSize) / 2;
197
+ ctx.drawImage(image, offset, offset, iconSize, iconSize);
198
+ }
199
+
200
+ /**
201
+ * Keep the media icon drawn on the hotspot texture. The label text itself
202
+ * is not rendered on the hotspot, so we simply redraw with the icon.
203
+ * @private
204
+ */
205
+ _onLabelChange(annotation) {
206
+ this._applyAnnotationIcon(annotation);
207
+ }
208
+
209
+ /**
210
+ * The hotspot colors are baked into the texture in `_createHotspotTexture`,
211
+ * so the material emissive is kept white to let those colors render as drawn.
212
+ * @private
213
+ */
214
+ _createHotspotMaterial(texture, options) {
215
+ const material = super._createHotspotMaterial(texture, options);
216
+ material.emissive.set(1, 1, 1);
217
+ material.update();
218
+ return material;
219
+ }
220
+
221
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
222
+ _setAnnotationHover(_annotation, _hover) {
223
+ // No-op: the hover growth is handled in _updateAnnotationRotationAndScale
224
+ // (via _hoverAnnotation); the texture is identical in both states.
225
+ }
226
+ _updateAllAnnotationColors() {
227
+ // No-op: hotspot colors are baked into the texture (see _createHotspotMaterial).
228
+ }
229
+
230
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
231
+ _showTooltip(_annotation) {
232
+ // No-op: AnnotationPanel handles all annotation UI, this removes default behavior
233
+ }
234
+
235
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
236
+ _onTextChange(_annotation, _text) {
237
+ // No-op: AnnotationPanel handles all annotation UI, this removes default behavior
238
+ }
239
+
240
+ /**
241
+ * Override position update to adjust for canvas offset and respect visibility.
242
+ * @private
243
+ */
244
+ _updateAnnotationPositions(annotation, resources, screenPos) {
245
+ const canvas = this.app.graphicsDevice.canvas;
246
+ const rect = canvas.getBoundingClientRect();
247
+
248
+ // If using a custom parent (positioned container), use raw screen coordinates
249
+ // Otherwise, adjust for canvas position in document
250
+ const offsetX = this._customParentDom ? 0 : rect.left + window.scrollX;
251
+ const offsetY = this._customParentDom ? 0 : rect.top + window.scrollY;
252
+
253
+ // Check if annotation is visible (defaults to true if not specified)
254
+ const isVisible = annotation.enabled !== undefined ? annotation.enabled : true;
255
+ resources.hotspotDom.style.display = isVisible ? 'block' : 'none';
256
+ resources.hotspotDom.style.left = `${screenPos.x + offsetX}px`;
257
+ resources.hotspotDom.style.top = `${screenPos.y + offsetY}px`;
258
+ if (this._activeAnnotation === annotation) {
259
+ this._tooltipDom.style.display = isVisible ? 'block' : 'none';
260
+ this._tooltipDom.style.left = `${screenPos.x + offsetX}px`;
261
+ this._tooltipDom.style.top = `${screenPos.y + offsetY}px`;
262
+ }
263
+ if (annotation.onScreenPositionUpdateCallback) {
264
+ // Rendered hotspot diameter (px), set by _updateAnnotationRotationAndScale.
265
+ // Parsed from the style rather than measured to avoid forcing a reflow.
266
+ const hotspotSize = parseFloat(resources.hotspotDom.style.width) || undefined;
267
+ annotation.onScreenPositionUpdateCallback(screenPos.x + offsetX, screenPos.y + offsetY, hotspotSize);
268
+ }
269
+ }
270
+
271
+ /**
272
+ * Override the scale update to clamp the world size to a maximum value.
273
+ * Also scales the hotspot DOM element to match.
274
+ * @private
275
+ */
276
+ _updateAnnotationRotationAndScale(annotation) {
277
+ const cameraRotation = this._camera.getRotation();
278
+ annotation.entity.setRotation(cameraRotation);
279
+ annotation.entity.rotateLocal(90, 0, 0);
280
+ const cameraPos = this._camera.getPosition();
281
+ const distance = annotation.entity.getPosition().distance(cameraPos);
282
+ const canvas = this.app.graphicsDevice.canvas;
283
+ const screenHeight = canvas.clientHeight;
284
+ const projMatrix = this._camera.camera.projectionMatrix;
285
+
286
+ // Calculate world size based on screen size and distance
287
+ const unclamped = this._hotspotSize / screenHeight * (2 * distance / projMatrix.data[5]);
288
+
289
+ // Clamp to maximum world size
290
+ const worldSize = Math.min(unclamped, this._maxWorldSize);
291
+ const hovered = this._hoverAnnotation === annotation;
292
+ const hoverScale = hovered ? HOTSPOT_HOVER_SCALE : 1;
293
+ const entityScale = worldSize * hoverScale;
294
+ annotation.entity.setLocalScale(entityScale, entityScale, entityScale);
295
+
296
+ // Scale the hotspot DOM element proportionally when clamped, including the
297
+ // hover growth so it tracks the visible circle.
298
+ const resources = this._annotationResources.get(annotation);
299
+ if (resources && resources.hotspotDom) {
300
+ const scaleFactor = worldSize / unclamped;
301
+ const baseSize = this._hotspotSize + 5; // Match the +5 from the stylesheet
302
+ const scaledSize = baseSize * scaleFactor * hoverScale;
303
+ resources.hotspotDom.style.width = `${scaledSize}px`;
304
+ resources.hotspotDom.style.height = `${scaledSize}px`;
305
+ }
306
+ }
307
+
308
+ /**
309
+ * Override to create hotspot texture with custom background color.
310
+ * @private
311
+ */
312
+ _createHotspotTexture(label, size = HOTSPOT_TEXTURE_SIZE, borderWidth = 6, icon) {
313
+ const canvas = document.createElement('canvas');
314
+ canvas.width = size;
315
+ canvas.height = size;
316
+ const ctx = canvas.getContext('2d');
317
+ if (!ctx) {
318
+ throw new Error('Failed to get canvas 2d context');
319
+ }
320
+
321
+ // First clear with stroke color at zero alpha
322
+ ctx.fillStyle = 'white';
323
+ ctx.globalAlpha = 0;
324
+ ctx.fillRect(0, 0, size, size);
325
+ ctx.globalAlpha = 1.0;
326
+
327
+ // Draw circle with custom background color
328
+ const centerX = size / 2;
329
+ const centerY = size / 2;
330
+ const radius = size / 2 - 4;
331
+
332
+ // The icon fill and border share the hotspot color
333
+ const foregroundColor = this._hotspotColor?.toString() ?? '#ffffff';
334
+
335
+ // Draw main circle
336
+ ctx.beginPath();
337
+ ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
338
+ ctx.fillStyle = this._hotspotBackgroundColor;
339
+ ctx.fill();
340
+
341
+ // Draw border
342
+ ctx.beginPath();
343
+ ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
344
+ ctx.lineWidth = borderWidth;
345
+ ctx.strokeStyle = foregroundColor;
346
+ ctx.stroke();
347
+
348
+ // Draw the media icon inside the circle
349
+ if (icon) {
350
+ this._drawIcon(ctx, icon, size);
351
+ }
352
+
353
+ // Get pixel data
354
+ const imageData = ctx.getImageData(0, 0, size, size);
355
+ const data = imageData.data;
356
+
357
+ // Create and return the texture
358
+ return new Texture(this.app.graphicsDevice, {
359
+ width: size,
360
+ height: size,
361
+ format: PIXELFORMAT_RGBA8,
362
+ magFilter: FILTER_LINEAR,
363
+ minFilter: FILTER_LINEAR,
364
+ mipmaps: false,
365
+ levels: [new Uint8Array(data.buffer)]
366
+ });
367
+ }
368
+ }
@@ -0,0 +1,15 @@
1
+ import { XrNavigation as PcXrNavigation } from 'playcanvas/scripts/esm/xr-navigation.mjs';
2
+ /**
3
+ * Extended XrNavigation that fixes the bug where tryTeleport doesn't respect
4
+ * the enableTeleport flag setting.
5
+ */
6
+ export declare class TfXrNavigation extends PcXrNavigation {
7
+ static scriptName: string;
8
+ /**
9
+ * Override tryTeleport to respect the enableTeleport flag.
10
+ * The base implementation is always called from handleSelectEnd,
11
+ * but we only want to teleport when enableTeleport is true.
12
+ */
13
+ tryTeleport(inputSource: any): void;
14
+ }
15
+ //# sourceMappingURL=TfXrNavigation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TfXrNavigation.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/TfXrNavigation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,IAAI,cAAc,EAAE,MAAM,0CAA0C,CAAC;AAE1F;;;GAGG;AACH,qBAAa,cAAe,SAAQ,cAAc;IAChD,MAAM,CAAC,UAAU,SAAoB;IAErC;;;;OAIG;IACH,WAAW,CAAC,WAAW,EAAE,GAAG;CAS7B"}
@@ -0,0 +1,24 @@
1
+ import { XrNavigation as PcXrNavigation } from 'playcanvas/scripts/esm/xr-navigation.mjs';
2
+
3
+ /**
4
+ * Extended XrNavigation that fixes the bug where tryTeleport doesn't respect
5
+ * the enableTeleport flag setting.
6
+ */
7
+ export class TfXrNavigation extends PcXrNavigation {
8
+ static scriptName = 'tfXrNavigation';
9
+
10
+ /**
11
+ * Override tryTeleport to respect the enableTeleport flag.
12
+ * The base implementation is always called from handleSelectEnd,
13
+ * but we only want to teleport when enableTeleport is true.
14
+ */
15
+ tryTeleport(inputSource) {
16
+ // Only proceed with teleportation if enableTeleport is true
17
+ if (!this.enableTeleport) {
18
+ return;
19
+ }
20
+
21
+ // Call the parent implementation
22
+ super.tryTeleport(inputSource);
23
+ }
24
+ }
@@ -0,0 +1,5 @@
1
+ /* This is necessary to ensure annotations are in front of other elements in the page, such as overlay backdrops. */
2
+ .pc-annotation,
3
+ .pc-annotation-hotspot {
4
+ z-index: 5000;
5
+ }
@@ -0,0 +1,4 @@
1
+ /* Allow pointer events on annotation hotspots while container blocks events */
2
+ .annotation-container > * {
3
+ pointer-events: auto;
4
+ }
@@ -0,0 +1,47 @@
1
+ import { Color, Script, Vec3 } from 'playcanvas';
2
+ export type BoundaryRingGeometryParams = {
3
+ center: Vec3;
4
+ radius: number;
5
+ groundPlane: Vec3[];
6
+ /** Band width in world units (the ring spans radius ± width / 2). */
7
+ width: number;
8
+ dashCount?: number;
9
+ dashRatio?: number;
10
+ };
11
+ /** Flat-array triangle geometry ready for `Mesh.setPositions` / `Mesh.setIndices`. */
12
+ export type BoundaryRingGeometry = {
13
+ positions: number[];
14
+ indices: number[];
15
+ };
16
+ /**
17
+ * Triangle geometry for a dashed boundary ring laid flat on the ground plane. Each dash is a
18
+ * short band (a quad = two triangles) sweeping from `radius - width / 2` to `radius + width / 2`;
19
+ * every vertex is sampled on the circle in XZ and lifted onto the plane via `yOnPlane`.
20
+ * Returns empty arrays when radius, width, or dashCount is non-positive, or the plane is invalid.
21
+ */
22
+ export declare const boundaryRingMesh: ({ center, radius, groundPlane, width, dashCount, dashRatio, }: BoundaryRingGeometryParams) => BoundaryRingGeometry;
23
+ /**
24
+ * Draws a dashed boundary ring as a flat band mesh on the ground plane.
25
+ *
26
+ * Rendered as a thin triangle band whose width is a fraction of the radius (`widthRatio`).
27
+ * center / radius / groundPlane are set imperatively by the BoundaryRing component (see BoundaryRing.tsx for why
28
+ * they are not reactive Script props), which calls `rebuild()` after updating them.
29
+ */
30
+ export declare class BoundaryRingScript extends Script {
31
+ static scriptName: string;
32
+ center: Vec3;
33
+ radius: number;
34
+ groundPlane: Vec3[];
35
+ color: Color;
36
+ dashCount: number;
37
+ dashRatio: number;
38
+ /** Band width as a fraction of the radius. */
39
+ widthRatio: number;
40
+ private _material?;
41
+ private _mesh?;
42
+ initialize(): void;
43
+ rebuild(): void;
44
+ private _clearMesh;
45
+ destroy(): void;
46
+ }
47
+ //# sourceMappingURL=boundary-ring.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"boundary-ring.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/boundary-ring.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,EAIL,MAAM,EAEN,IAAI,EACL,MAAM,YAAY,CAAC;AAIpB,MAAM,MAAM,0BAA0B,GAAG;IACvC,MAAM,EAAE,IAAI,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,IAAI,EAAE,CAAC;IACpB,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,sFAAsF;AACtF,MAAM,MAAM,oBAAoB,GAAG;IAAE,SAAS,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAE9E;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,GAAI,+DAO9B,0BAA0B,KAAG,oBAgC/B,CAAC;AAEF;;;;;;GAMG;AACH,qBAAa,kBAAmB,SAAQ,MAAM;IAC5C,MAAM,CAAC,UAAU,SAAkB;IAEnC,MAAM,OAAc;IACpB,MAAM,SAAK;IACX,WAAW,EAAE,IAAI,EAAE,CAAM;IACzB,KAAK,QAAsB;IAC3B,SAAS,SAAM;IACf,SAAS,SAAO;IAChB,8CAA8C;IAC9C,UAAU,SAAQ;IAElB,OAAO,CAAC,SAAS,CAAC,CAAmB;IACrC,OAAO,CAAC,KAAK,CAAC,CAAO;IAErB,UAAU;IAUV,OAAO;IA+BP,OAAO,CAAC,UAAU;IAUlB,OAAO;CAKR"}
@@ -0,0 +1,125 @@
1
+ import { CULLFACE_NONE, Color, LAYERID_IMMEDIATE, Mesh, MeshInstance, Script, StandardMaterial, Vec3 } from 'playcanvas';
2
+ import { computeGroundPlane, yOnPlane } from './groundPlane';
3
+
4
+ /** Flat-array triangle geometry ready for `Mesh.setPositions` / `Mesh.setIndices`. */
5
+
6
+ /**
7
+ * Triangle geometry for a dashed boundary ring laid flat on the ground plane. Each dash is a
8
+ * short band (a quad = two triangles) sweeping from `radius - width / 2` to `radius + width / 2`;
9
+ * every vertex is sampled on the circle in XZ and lifted onto the plane via `yOnPlane`.
10
+ * Returns empty arrays when radius, width, or dashCount is non-positive, or the plane is invalid.
11
+ */
12
+ export const boundaryRingMesh = ({
13
+ center,
14
+ radius,
15
+ groundPlane,
16
+ width,
17
+ dashCount = 64,
18
+ dashRatio = 0.5
19
+ }) => {
20
+ const plane = computeGroundPlane(groundPlane);
21
+ if (radius <= 0 || width <= 0 || dashCount <= 0 || !plane) {
22
+ return {
23
+ positions: [],
24
+ indices: []
25
+ };
26
+ }
27
+ const innerR = radius - width / 2;
28
+ const outerR = radius + width / 2;
29
+ const step = Math.PI * 2 / dashCount;
30
+ const on = step * dashRatio;
31
+ const positions = [];
32
+ const indices = [];
33
+ const pushVertex = (r, angle) => {
34
+ const x = center.x + r * Math.cos(angle);
35
+ const z = center.z + r * Math.sin(angle);
36
+ positions.push(x, yOnPlane(x, z, plane.normal, plane.point, center.y), z);
37
+ };
38
+ for (let i = 0; i < dashCount; i++) {
39
+ const a0 = i * step;
40
+ const a1 = a0 + on;
41
+ const base = i * 4;
42
+ // Per dash: v0 inner@a0, v1 outer@a0, v2 inner@a1, v3 outer@a1.
43
+ pushVertex(innerR, a0);
44
+ pushVertex(outerR, a0);
45
+ pushVertex(innerR, a1);
46
+ pushVertex(outerR, a1);
47
+ indices.push(base, base + 1, base + 3, base, base + 3, base + 2);
48
+ }
49
+ return {
50
+ positions,
51
+ indices
52
+ };
53
+ };
54
+
55
+ /**
56
+ * Draws a dashed boundary ring as a flat band mesh on the ground plane.
57
+ *
58
+ * Rendered as a thin triangle band whose width is a fraction of the radius (`widthRatio`).
59
+ * center / radius / groundPlane are set imperatively by the BoundaryRing component (see BoundaryRing.tsx for why
60
+ * they are not reactive Script props), which calls `rebuild()` after updating them.
61
+ */
62
+ export class BoundaryRingScript extends Script {
63
+ static scriptName = 'boundaryRing';
64
+ center = new Vec3();
65
+ radius = 0;
66
+ groundPlane = [];
67
+ color = new Color(1, 1, 1);
68
+ dashCount = 64;
69
+ dashRatio = 0.5;
70
+ /** Band width as a fraction of the radius. */
71
+ widthRatio = 0.05;
72
+ initialize() {
73
+ const material = new StandardMaterial();
74
+ material.useLighting = false;
75
+ material.emissive = this.color;
76
+ material.cull = CULLFACE_NONE;
77
+ material.update();
78
+ this._material = material;
79
+ this.rebuild();
80
+ }
81
+ rebuild() {
82
+ this._clearMesh();
83
+ const geometry = boundaryRingMesh({
84
+ center: this.center,
85
+ radius: this.radius,
86
+ groundPlane: this.groundPlane,
87
+ width: this.radius * this.widthRatio,
88
+ dashCount: this.dashCount,
89
+ dashRatio: this.dashRatio
90
+ });
91
+ if (geometry.positions.length === 0 || !this._material) {
92
+ return;
93
+ }
94
+ const mesh = new Mesh(this.app.graphicsDevice);
95
+ mesh.setPositions(geometry.positions);
96
+ mesh.setIndices(geometry.indices);
97
+ mesh.update();
98
+ this._mesh = mesh;
99
+ const meshInstance = new MeshInstance(mesh, this._material);
100
+ if (this.entity.render) {
101
+ this.entity.render.meshInstances = [meshInstance];
102
+ } else {
103
+ // Render on the Immediate layer (drawn after the World layer where the splats render) so
104
+ // the ring is not composited behind them.
105
+ this.entity.addComponent('render', {
106
+ meshInstances: [meshInstance],
107
+ layers: [LAYERID_IMMEDIATE]
108
+ });
109
+ }
110
+ }
111
+ _clearMesh() {
112
+ if (this.entity.render) {
113
+ this.entity.render.meshInstances = [];
114
+ }
115
+ if (this._mesh) {
116
+ this._mesh.destroy();
117
+ this._mesh = undefined;
118
+ }
119
+ }
120
+ destroy() {
121
+ this._clearMesh();
122
+ this._material?.destroy();
123
+ this._material = undefined;
124
+ }
125
+ }