locar 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -22,7 +22,7 @@ Some of these issues, such as incorrect North on some devices, may well be to do
22
22
 
23
23
  ## Cross-platform Compatibility
24
24
 
25
- It should now work on Chrome (and possibly Safari) on iOS as well as Android, due to a number of recent iOS fixes by project contributors.
25
+ It should now work on Chrome and Safari on iOS as well as Android, due to a number of recent iOS fixes by project contributors.
26
26
 
27
27
  Firefox unfortunately does not appear to fully implement the Device Orientation API so is unlikely to work.
28
28
 
@@ -42,7 +42,7 @@ import * as LocAR from 'locar';
42
42
  as long as you include `locar` as a dependency, e.g. in your `package.json`:
43
43
  ```
44
44
  "dependencies": {
45
- "locar" : "^0.1.0",
45
+ "locar" : "^0.1.2",
46
46
  "three" : "^0.175.0"
47
47
  }
48
48
  ```
@@ -59,7 +59,7 @@ Please see [here](https://github.com/AR-js-org/locar.js/blob/master/docs/tutoria
59
59
 
60
60
  ## Contributors
61
61
 
62
- Many thanks to all contributors to the project, in particular [Darian Elias Weiß](https://github.com/darianwwu) and [Kazuhiro Matsuyama](https://github.com/ma2yama) for providing extremely valuable iOS fixes and enhancements.
62
+ Many thanks to all contributors to the project, in particular [Darian Elias Weiß](https://github.com/darianwwu), [Kazuhiro Matsuyama](https://github.com/ma2yama) and [Luigi Mannoni](https://github.com/luigimannoni-smartify) for providing extremely valuable iOS fixes and enhancements.
63
63
 
64
64
  ## Disclaimer
65
65
 
package/dist/locar.es.js CHANGED
@@ -1,6 +1,6 @@
1
- import * as f from "three";
2
- import { Vector3 as I, Euler as A, Quaternion as w, EventDispatcher as C, MathUtils as m } from "three";
3
- class T {
1
+ import * as g from "three";
2
+ import { Vector3 as R, Euler as C, Quaternion as I, EventDispatcher as T, MathUtils as m } from "three";
3
+ class w {
4
4
  /**
5
5
  * Create a SphMercProjection.
6
6
  */
@@ -14,12 +14,12 @@ class T {
14
14
  * @return {Array} Two-member array containing easting and northing.
15
15
  */
16
16
  project(e, i) {
17
- return [this.#t(e), this.#h(i)];
17
+ return [this.#t(e), this.#l(i)];
18
18
  }
19
19
  /**
20
20
  * Unproject a Spherical Mercator easting and northing.
21
21
  * @param {Array} projected - Two-member array containing easting and northing
22
- * @return {Array} Two-member array containing longitude and latitude
22
+ * @return {Array} Two-member array containing longitude and latitude
23
23
  */
24
24
  unproject(e) {
25
25
  return [this.#e(e[0]), this.#s(e[1])];
@@ -27,7 +27,7 @@ class T {
27
27
  #t(e) {
28
28
  return e / 180 * this.HALF_EARTH;
29
29
  }
30
- #h(e) {
30
+ #l(e) {
31
31
  var i = Math.log(Math.tan((90 + e) * Math.PI / 360)) / (Math.PI / 180);
32
32
  return i * this.HALF_EARTH / 180;
33
33
  }
@@ -46,7 +46,7 @@ class T {
46
46
  return "epsg:3857";
47
47
  }
48
48
  }
49
- class P {
49
+ class A {
50
50
  constructor() {
51
51
  this.eventHandlers = {};
52
52
  }
@@ -59,17 +59,18 @@ class P {
59
59
  this.eventHandlers[e] = i;
60
60
  }
61
61
  /**
62
- * Emit an event.
62
+ * Emit an event.
63
63
  * @param {string} eventName - the event to emit.
64
- * @param ...params - parameters to pass to the event handler.
64
+ * @param ...params - parameters to pass to the event handler.
65
65
  */
66
66
  emit(e, ...i) {
67
67
  this.eventHandlers[e]?.call(this, ...i);
68
68
  }
69
69
  }
70
- class L extends P {
70
+ class k extends A {
71
71
  #t;
72
- #h;
72
+ // eslint-disable-next-line no-unused-private-class-members
73
+ #l;
73
74
  #e;
74
75
  #s;
75
76
  #c;
@@ -80,19 +81,19 @@ class L extends P {
80
81
  #n;
81
82
  /**
82
83
  * @param {THREE.Scene} scene - The Three.js scene to use.
83
- * @param {THREE.Camera} camera - The Three.js camera to use. Should usually
84
+ * @param {THREE.Camera} camera - The Three.js camera to use. Should usually
84
85
  * be a THREE.PerspectiveCamera.
85
86
  * @param {Object} options - Initialisation options for the GPS; see
86
87
  * setGpsOptions() below.
87
88
  * @param {Object} serverLogger - an object which can optionally log GPS position to a server for debugging. null by default, so no logging will be done. This object should implement a sendData() method to send data (2nd arg) to a given endpoint (1st arg). Please see source code for details. Ensure you comply with privacy laws (GDPR or equivalent) if implementing this.
88
89
  */
89
90
  constructor(e, i, t = {}, c = null) {
90
- super(), this.scene = e, this.camera = i, this.#t = new T(), this.#e = null, this.#s = 0, this.#c = 100, this.#o = null, this.setGpsOptions(t), this.#i = null, this.#r = 0, this.#a = 0, this.#n = c;
91
+ super(), this.scene = e, this.camera = i, this.#t = new w(), this.#e = null, this.#s = 0, this.#c = 100, this.#o = null, this.setGpsOptions(t), this.#i = null, this.#r = 0, this.#a = 0, this.#n = c;
91
92
  }
92
93
  /**
93
94
  * Set the projection to use.
94
- * @param {Object} any object which includes a project() method
95
- * taking longitude and latitude as arguments and returning an array
95
+ * @param {Object} any object which includes a project() method
96
+ * taking longitude and latitude as arguments and returning an array
96
97
  * containing easting and northing.
97
98
  */
98
99
  setProjection(e) {
@@ -102,7 +103,7 @@ class L extends P {
102
103
  * Set the GPS options.
103
104
  * @param {Object} object containing gpsMinDistance and/or gpsMinAccuracy
104
105
  * properties. The former specifies the number of metres which the device
105
- * must move to process a new GPS reading, and the latter specifies the
106
+ * must move to process a new GPS reading, and the latter specifies the
106
107
  * minimum accuracy, in metres, for a GPS reading to be counted.
107
108
  */
108
109
  setGpsOptions(e = {}) {
@@ -123,7 +124,7 @@ class L extends P {
123
124
  }
124
125
  return this.#o === null ? (this.#o = navigator.geolocation.watchPosition(
125
126
  (e) => {
126
- this.#l(e);
127
+ this.#h(e);
127
128
  },
128
129
  (e) => {
129
130
  this.emit("gpserror", e);
@@ -151,7 +152,7 @@ class L extends P {
151
152
  * ignored if lower than the specified minimum accuracy.
152
153
  */
153
154
  fakeGps(e, i, t = null, c = 0) {
154
- t !== null && this.setElevation(t), this.#l({
155
+ t !== null && this.setElevation(t), this.#h({
155
156
  coords: {
156
157
  longitude: e,
157
158
  latitude: i,
@@ -183,23 +184,23 @@ class L extends P {
183
184
  * @param {THREE.Mesh} object the object
184
185
  * @param {number} lon - the longitude.
185
186
  * @param {number} lat - the latitude.
186
- * @param {number} elev - the elevation in metres
187
+ * @param {number} elev - the elevation in metres
187
188
  * (if not specified, 0 is assigned)
188
189
  * @param {Object} properties - properties describing the object (for example,
189
190
  * the contents of the GeoJSON properties field).
190
191
  */
191
- add(e, i, t, c, d = {}) {
192
- e.properties = d, this.#d(e, i, t, c), this.scene.add(e), this.#n?.sendData("/object/new", {
192
+ add(e, i, t, c, h = {}) {
193
+ e.properties = h, this.#d(e, i, t, c), this.scene.add(e), this.#n?.sendData("/object/new", {
193
194
  position: e.position,
194
195
  x: e.position.x,
195
196
  z: e.position.z,
196
197
  session: this.#a,
197
- properties: d
198
+ properties: h
198
199
  });
199
200
  }
200
201
  #d(e, i, t, c) {
201
- const d = this.lonLatToWorldCoords(i, t);
202
- c !== void 0 && (e.position.y = c), [e.position.x, e.position.z] = d;
202
+ const h = this.lonLatToWorldCoords(i, t);
203
+ c !== void 0 && (e.position.y = c), [e.position.x, e.position.z] = h;
203
204
  }
204
205
  /**
205
206
  * Set the elevation (y coordinate) of the camera.
@@ -211,7 +212,7 @@ class L extends P {
211
212
  #u(e, i) {
212
213
  this.#i = this.#t.project(e, i);
213
214
  }
214
- #l(e) {
215
+ #h(e) {
215
216
  let i = Number.MAX_VALUE;
216
217
  this.#r++, this.#n?.sendData("/gps/new", {
217
218
  gpsCount: this.#r,
@@ -249,22 +250,22 @@ class L extends P {
249
250
  * Taken from original A-Frame AR.js location-based components
250
251
  */
251
252
  #m(e, i) {
252
- const t = f.MathUtils.degToRad(i.longitude - e.longitude), c = f.MathUtils.degToRad(i.latitude - e.latitude), d = Math.sin(c / 2) * Math.sin(c / 2) + Math.cos(f.MathUtils.degToRad(e.latitude)) * Math.cos(f.MathUtils.degToRad(i.latitude)) * (Math.sin(t / 2) * Math.sin(t / 2));
253
- return 2 * Math.atan2(Math.sqrt(d), Math.sqrt(1 - d)) * 6371e3;
253
+ const t = g.MathUtils.degToRad(i.longitude - e.longitude), c = g.MathUtils.degToRad(i.latitude - e.latitude), h = Math.sin(c / 2) * Math.sin(c / 2) + Math.cos(g.MathUtils.degToRad(e.latitude)) * Math.cos(g.MathUtils.degToRad(i.latitude)) * (Math.sin(t / 2) * Math.sin(t / 2));
254
+ return 2 * Math.atan2(Math.sqrt(h), Math.sqrt(1 - h)) * 6371e3;
254
255
  }
255
256
  }
256
- class H extends P {
257
+ class W extends A {
257
258
  #t;
258
259
  /**
259
260
  * Create a Webcam.
260
- * @param constraints {Object} - options to use for initialising the camera.
261
- * The same constraints object as used by standard MediaDevices API.
262
- * @param {string} videoElementSelector - selector to obtain the HTML video
263
- * element to render the webcam feed. If a falsy value (e.g. null or
261
+ * @param constraints {Object} - options to use for initialising the camera.
262
+ * This is the same constraints object as used by standard MediaDevices API.
263
+ * @param {string} videoElementSelector - selector to obtain the HTML video
264
+ * element to render the webcam feed. If a falsy value (e.g. null or
264
265
  * undefined), a video element will be created.
265
266
  */
266
267
  constructor(e = { video: { facingMode: "environment" } }, i) {
267
- super(), this.sceneWebcam = new f.Scene(), i ? this.#t = document.querySelector(i) : (this.#t = document.createElement("video"), this.#t.setAttribute("autoplay", !0), this.#t.setAttribute("playsinline", !0), this.#t.style.display = "none", document.body.appendChild(this.#t)), this.texture = new f.VideoTexture(this.#t), navigator.mediaDevices && navigator.mediaDevices.getUserMedia ? navigator.mediaDevices.getUserMedia(e).then((t) => {
268
+ super(), this.sceneWebcam = new g.Scene(), i ? this.#t = document.querySelector(i) : (this.#t = document.createElement("video"), this.#t.setAttribute("autoplay", !0), this.#t.setAttribute("playsinline", !0), this.#t.style.display = "none", document.body.appendChild(this.#t)), this.texture = new g.VideoTexture(this.#t), navigator.mediaDevices && navigator.mediaDevices.getUserMedia ? navigator.mediaDevices.getUserMedia(e).then((t) => {
268
269
  this.#t.addEventListener("loadedmetadata", () => {
269
270
  this.#t.setAttribute("width", this.#t.videoWidth), this.#t.setAttribute("height", this.#t.videoHeight), this.#t.play(), this.emit("webcamstarted", { texture: this.texture });
270
271
  }), this.#t.srcObject = t;
@@ -286,25 +287,25 @@ class H extends P {
286
287
  this.texture.dispose();
287
288
  }
288
289
  }
289
- const E = navigator.userAgent.match(/iPhone|iPad|iPod/i) || /Macintosh/i.test(navigator.userAgent) && navigator.maxTouchPoints != null && navigator.maxTouchPoints > 1, _ = new I(0, 0, 1), y = new A(), R = new w(), D = new w(-Math.sqrt(0.5), 0, 0, Math.sqrt(0.5));
290
- class x extends C {
290
+ const b = "locar-device-orientation-permission-modal", D = "locar-device-orientation-permission-button", S = "locar-device-orientation-permission-message", N = "locar-device-orientation-permission-inner", L = "locar-device-orientation-permission-button-inner", P = "This immersive website requires access to your device motion sensors.", O = navigator.userAgent.match(/iPhone|iPad|iPod/i) || /Macintosh/i.test(navigator.userAgent) && navigator.maxTouchPoints != null && navigator.maxTouchPoints > 1, x = new R(0, 0, 1), y = new C(), H = new I(), F = new I(-Math.sqrt(0.5), 0, 0, Math.sqrt(0.5));
291
+ class V extends T {
291
292
  /**
292
- * Create an instance of DeviceOrientationControls.
293
- * @param {Object} object - the object to attach the controls to
294
- * (usually your Three.js camera)
295
- * @param {Object} options - options for DeviceOrientationControls: currently accepts smoothingFactor, enablePermissionDialog
296
- */
293
+ * Create an instance of DeviceOrientationControls.
294
+ * @param {Object} object - the object to attach the controls to
295
+ * (usually your Three.js camera)
296
+ * @param {Object} options - options for DeviceOrientationControls: currently accepts smoothingFactor, enablePermissionDialog
297
+ */
297
298
  constructor(e, i = {}) {
298
- super(), this.eventEmitter = new P();
299
+ super(), this.eventEmitter = new A();
299
300
  const t = this;
300
- this.object = e, this.object.rotation.reorder("YXZ"), this.enabled = !0, this.deviceOrientation = null, this.screenOrientation = 0, this.alphaOffset = 0, this.orientationOffset = 0, this.initialOffset = null, this.lastCompassY = void 0, this.lastOrientation = null, this.TWO_PI = 2 * Math.PI, this.HALF_PI = 0.5 * Math.PI, this.orientationChangeEventName = "ondeviceorientationabsolute" in window ? "deviceorientationabsolute" : "deviceorientation", console.log("Device Orientation Event Name:", this.orientationChangeEventName), this.smoothingFactor = i.smoothingFactor || 1, this.enablePermissionDialog = i.enablePermissionDialog ?? !0;
301
+ this.object = e, this.object.rotation.reorder("YXZ"), this.enabled = !0, this.deviceOrientation = null, this.screenOrientation = 0, this.alphaOffset = 0, this.orientationOffset = 0, this.initialOffset = null, this.lastCompassY = void 0, this.lastOrientation = null, this.TWO_PI = 2 * Math.PI, this.HALF_PI = 0.5 * Math.PI, this.orientationChangeEventName = "ondeviceorientationabsolute" in window ? "deviceorientationabsolute" : "deviceorientation", this.smoothingFactor = i.smoothingFactor || 1, this.enablePermissionDialog = i.enablePermissionDialog ?? !0, this.enableInlineStyling = i.enableStyling ?? !0, this.preferConfirmDialog = i.preferConfirmDialog ?? !1;
301
302
  const c = function({
302
303
  alpha: n,
303
304
  beta: s,
304
305
  gamma: a,
305
306
  webkitCompassHeading: r
306
307
  }) {
307
- if (E) {
308
+ if (O) {
308
309
  const o = 360 - r;
309
310
  t.alphaOffset = m.degToRad(o - n), t.deviceOrientation = { alpha: n, beta: s, gamma: a, webkitCompassHeading: r };
310
311
  } else
@@ -314,15 +315,15 @@ class x extends C {
314
315
  detail: { cameraRotation: e.rotation }
315
316
  })
316
317
  );
317
- }, d = function() {
318
- t.screenOrientation = window.orientation || 0, E && (t.screenOrientation === 90 ? t.orientationOffset = -t.HALF_PI : t.screenOrientation === -90 ? t.orientationOffset = t.HALF_PI : t.orientationOffset = 0);
319
- }, M = function(n, s, a, r, o) {
320
- y.set(a, s, -r, "YXZ"), n.setFromEuler(y), n.multiply(D), n.multiply(R.setFromAxisAngle(_, -o));
318
+ }, h = function() {
319
+ t.screenOrientation = window.orientation || 0, O && (t.screenOrientation === 90 ? t.orientationOffset = -t.HALF_PI : t.screenOrientation === -90 ? t.orientationOffset = t.HALF_PI : t.orientationOffset = 0);
320
+ }, E = function(n, s, a, r, o) {
321
+ y.set(a, s, -r, "YXZ"), n.setFromEuler(y), n.multiply(F), n.multiply(H.setFromAxisAngle(x, -o));
321
322
  };
322
323
  this.connect = function() {
323
- d(), window.addEventListener(
324
+ h(), window.addEventListener(
324
325
  "orientationchange",
325
- d
326
+ h
326
327
  ), window.addEventListener(
327
328
  t.orientationChangeEventName,
328
329
  c
@@ -330,7 +331,7 @@ class x extends C {
330
331
  }, this.disconnect = function() {
331
332
  window.removeEventListener(
332
333
  "orientationchange",
333
- d
334
+ h
334
335
  ), window.removeEventListener(
335
336
  t.orientationChangeEventName,
336
337
  c
@@ -346,7 +347,8 @@ class x extends C {
346
347
  }).catch(function(n) {
347
348
  this.eventEmitter.emit("deviceorientationerror", {
348
349
  code: "LOCAR_DEVICE_ORIENTATION_PERMISSION_FAILED",
349
- message: "Permission request for device orientation failed - AR will not work correctly"
350
+ message: "Permission request for device orientation failed - AR will not work correctly",
351
+ error: JSON.stringify(n, null, 2)
350
352
  });
351
353
  }) : this.eventEmitter.emit("deviceorientationerror", {
352
354
  code: "LOCAR_DEVICE_ORIENTATION_INTERNAL_ERROR",
@@ -357,22 +359,22 @@ class x extends C {
357
359
  const s = t.deviceOrientation;
358
360
  if (s) {
359
361
  let a = s.alpha ? m.degToRad(s.alpha) + t.alphaOffset : 0, r = s.beta ? m.degToRad(s.beta) : 0, o = s.gamma ? m.degToRad(s.gamma) : 0;
360
- const g = t.screenOrientation ? m.degToRad(t.screenOrientation) : 0;
362
+ const f = t.screenOrientation ? m.degToRad(t.screenOrientation) : 0;
361
363
  if (t.smoothingFactor < 1) {
362
364
  if (t.lastOrientation) {
363
- const h = t.smoothingFactor;
365
+ const d = t.smoothingFactor;
364
366
  a = t._getSmoothedAngle(
365
367
  a,
366
368
  t.lastOrientation.alpha,
367
- h
369
+ d
368
370
  ), r = t._getSmoothedAngle(
369
371
  r + Math.PI,
370
372
  t.lastOrientation.beta,
371
- h
373
+ d
372
374
  ), o = t._getSmoothedAngle(
373
375
  o + t.HALF_PI,
374
376
  t.lastOrientation.gamma,
375
- h,
377
+ d,
376
378
  Math.PI
377
379
  );
378
380
  } else
@@ -383,42 +385,46 @@ class x extends C {
383
385
  gamma: o
384
386
  };
385
387
  }
386
- if (E) {
387
- const h = new w();
388
- M(
389
- h,
388
+ if (O) {
389
+ const d = new I();
390
+ E(
391
+ d,
390
392
  a,
391
393
  t.smoothingFactor < 1 ? r - Math.PI : r,
392
394
  t.smoothingFactor < 1 ? o - Math.PI / 2 : o,
393
- g
395
+ f
394
396
  );
395
- const u = new A().setFromQuaternion(
396
- h,
397
+ const u = new C().setFromQuaternion(
398
+ d,
397
399
  "YXZ"
398
400
  );
399
- let v = m.degToRad(360 - s.webkitCompassHeading);
400
- t.smoothingFactor < 1 && t.lastCompassY !== void 0 && (v = t._getSmoothedAngle(v, t.lastCompassY, t.smoothingFactor)), t.lastCompassY = v, u.y = v + (t.orientationOffset || 0), h.setFromEuler(u), t.object.quaternion.copy(h);
401
+ let p = m.degToRad(360 - s.webkitCompassHeading);
402
+ t.smoothingFactor < 1 && t.lastCompassY !== void 0 && (p = t._getSmoothedAngle(
403
+ p,
404
+ t.lastCompassY,
405
+ t.smoothingFactor
406
+ )), t.lastCompassY = p, u.y = p + (t.orientationOffset || 0), d.setFromEuler(u), t.object.quaternion.copy(d);
401
407
  } else
402
- M(
408
+ E(
403
409
  t.object.quaternion,
404
- E ? a + t.alphaOffset : a,
410
+ O ? a + t.alphaOffset : a,
405
411
  t.smoothingFactor < 1 ? r - Math.PI : r,
406
412
  t.smoothingFactor < 1 ? o - Math.PI / 2 : o,
407
- g
413
+ f
408
414
  );
409
415
  }
410
416
  }, this.getCorrectedHeading = function() {
411
417
  const { deviceOrientation: n } = t;
412
418
  if (!n) return 0;
413
419
  let s = 0;
414
- return E ? (s = 360 - n.webkitCompassHeading, t.orientationOffset && (s += t.orientationOffset * (180 / Math.PI), s = (s + 360) % 360)) : (n.absolute === !0 || t.orientationChangeEventName, s = n.alpha ? n.alpha : 0, s = (360 - s) % 360, s < 0 && (s += 360)), s;
420
+ return O ? (s = 360 - n.webkitCompassHeading, t.orientationOffset && (s += t.orientationOffset * (180 / Math.PI), s = (s + 360) % 360)) : (n.absolute === !0 || t.orientationChangeEventName, s = n.alpha ? n.alpha : 0, s = (360 - s) % 360, s < 0 && (s += 360)), s;
415
421
  }, this._orderAngle = function(n, s, a = t.TWO_PI) {
416
422
  return s > n && Math.abs(s - n) < a / 2 || n > s && Math.abs(s - n) > a / 2 ? { left: n, right: s } : { left: s, right: n };
417
423
  }, this._getSmoothedAngle = function(n, s, a, r = t.TWO_PI) {
418
- const o = t._orderAngle(n, s, r), g = o.left, h = o.right;
419
- o.left = 0, o.right -= g, o.right < 0 && (o.right += r);
420
- let u = h == s ? (1 - a) * o.right + a * o.left : a * o.right + (1 - a) * o.left;
421
- return u += g, u >= r && (u -= r), u;
424
+ const o = t._orderAngle(n, s, r), f = o.left, d = o.right;
425
+ o.left = 0, o.right -= f, o.right < 0 && (o.right += r);
426
+ let u = d == s ? (1 - a) * o.right + a * o.left : a * o.right + (1 - a) * o.left;
427
+ return u += f, u >= r && (u -= r), u;
422
428
  }, this.updateAlphaOffset = function() {
423
429
  t.initialOffset = !1;
424
430
  }, this.dispose = function() {
@@ -432,57 +438,91 @@ class x extends C {
432
438
  }, this.getGamma = function() {
433
439
  const { deviceOrientation: n } = t;
434
440
  return n && n.gamma ? m.degToRad(n.gamma) : 0;
435
- }, this.obtainPermissionGesture = function() {
436
- const n = document.createElement("div"), s = document.createElement("div"), a = document.createElement("div"), r = document.createElement("div");
437
- document.body.appendChild(n);
438
- const o = {
439
- display: "flex",
440
- position: "fixed",
441
- top: 0,
442
- left: 0,
443
- width: "100%",
444
- height: "100%",
445
- zIndex: 1,
446
- backgroundColor: "rgba(0,0,0,0.6)",
447
- justifyContent: "center",
448
- alignItems: "center"
449
- }, g = {
450
- backgroundColor: "white",
451
- padding: "6px",
452
- borderRadius: "3px",
453
- width: "36rem",
454
- height: "24rem"
455
- }, h = {
456
- width: "100%",
457
- height: "70%",
458
- display: "flex",
459
- justifyContent: "center",
460
- alignItems: "center"
461
- }, u = {
462
- display: "inline-flex",
463
- width: "100%",
464
- height: "30%",
465
- justifyContent: "center",
466
- alignItems: "center"
467
- };
468
- for (let l in o)
469
- n.style[l] = o[l];
470
- for (let l in g)
471
- s.style[l] = g[l];
472
- for (let l in h)
473
- a.style[l] = h[l];
474
- for (let l in u)
475
- r.style[l] = u[l];
476
- n.appendChild(s), s.appendChild(a), s.appendChild(r), a.innerHTML = '<div style="font-size: 24pt; margin: 1rem;">This immersive website requires access to your device motion sensors.</div>';
477
- const v = () => {
441
+ }, this.createObtainPermissionGestureDialog = function() {
442
+ const n = document.createElement("div");
443
+ n.classList.add(b);
444
+ const s = document.createElement("div");
445
+ s.classList.add(N);
446
+ const a = document.createElement("div");
447
+ a.classList.add(S);
448
+ const r = document.createElement("div");
449
+ r.classList.add(L);
450
+ const o = document.createElement("button");
451
+ if (o.classList.add(D), document.body.appendChild(n), this.enableInlineStyling === !0) {
452
+ const d = {
453
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'",
454
+ display: "flex",
455
+ position: "fixed",
456
+ zIndex: 1e5,
457
+ justifyContent: "center",
458
+ alignItems: "center",
459
+ backgroundColor: "rgba(0,0,0,0.2)",
460
+ inset: 0,
461
+ padding: "20px"
462
+ }, u = {
463
+ backgroundColor: "rgba(220, 220, 220, 0.85)",
464
+ padding: "6px 0",
465
+ borderRadius: "10px",
466
+ width: "100%",
467
+ maxWidth: "400px"
468
+ }, p = {
469
+ padding: "10px 12px",
470
+ textAlign: "center",
471
+ fontWeight: 400,
472
+ fontSize: "13px",
473
+ display: "flex",
474
+ justifyContent: "center",
475
+ alignItems: "center"
476
+ }, M = {
477
+ display: "block",
478
+ textAlign: "center",
479
+ textDecoration: "none",
480
+ borderTop: "rgb(180,180,180) solid 1px"
481
+ }, _ = {
482
+ display: "block",
483
+ width: "100%",
484
+ textAlign: "center",
485
+ appearance: "none",
486
+ background: "none",
487
+ border: "none",
488
+ outline: "none",
489
+ padding: "10px",
490
+ fontWeight: 400,
491
+ fontSize: "16px",
492
+ color: "#2e7cf1",
493
+ cursor: "pointer"
494
+ };
495
+ for (let l in d)
496
+ n.style[l] = d[l];
497
+ for (let l in u)
498
+ s.style[l] = u[l];
499
+ for (let l in p)
500
+ a.style[l] = p[l];
501
+ for (let l in M)
502
+ r.style[l] = M[l];
503
+ for (let l in _)
504
+ o.style[l] = _[l];
505
+ }
506
+ n.appendChild(s), s.appendChild(a), s.appendChild(r), a.appendChild(
507
+ document.createTextNode(P)
508
+ );
509
+ const f = () => {
478
510
  this.requestOrientationPermissions(), n.style.display = "none";
479
- }, p = document.createElement("button");
480
- p.addEventListener("click", v), p.style.width = "50%", p.style.height = "80%", p.style.fontSize = "20pt", p.appendChild(document.createTextNode("OK")), r.appendChild(p), document.body.appendChild(n);
511
+ };
512
+ o.addEventListener("click", f), o.appendChild(document.createTextNode("OK")), r.appendChild(o), document.body.appendChild(n);
513
+ }, this.obtainPermissionGesture = function() {
514
+ this.preferConfirmDialog === !0 ? window.confirm(P) && this.requestOrientationPermissions() : this.createObtainPermissionGestureDialog();
481
515
  };
482
516
  }
483
517
  on(e, i) {
484
518
  this.eventEmitter.on(e, i);
485
519
  }
520
+ /**
521
+ * Initialise device orientation controls.
522
+ * Should be called after you have created the DeviceOrientationControls
523
+ * object and set up the deviceorientationgranted and deviceorientationerror
524
+ * event handlers.
525
+ */
486
526
  init() {
487
527
  window.DeviceOrientationEvent === void 0 ? this.eventEmitter.emit("deviceorientationerror", {
488
528
  code: "LOCAR_DEVICE_ORIENTATION_NOT_SUPPORTED",
@@ -493,14 +533,14 @@ class x extends C {
493
533
  }) : typeof window.DeviceOrientationEvent.requestPermission == "function" && this.enablePermissionDialog ? this.obtainPermissionGesture() : this.eventEmitter.emit("deviceorientationgranted", { target: this });
494
534
  }
495
535
  }
496
- class N {
536
+ class j {
497
537
  /**
498
538
  * Create a ClickHandler.
499
539
  * @param {THREE.WebGLRenderer} - The Three.js renderer on which the click
500
540
  * events will be handled.
501
541
  */
502
542
  constructor(e) {
503
- this.raycaster = new f.Raycaster(), this.normalisedMousePosition = new f.Vector2(null, null), e.domElement.addEventListener("click", (i) => {
543
+ this.raycaster = new g.Raycaster(), this.normalisedMousePosition = new g.Vector2(null, null), e.domElement.addEventListener("click", (i) => {
504
544
  this.normalisedMousePosition.set(
505
545
  i.clientX / e.domElement.clientWidth * 2 - 1,
506
546
  -(i.clientY / e.domElement.clientHeight * 2) + 1
@@ -524,13 +564,13 @@ class N {
524
564
  return [];
525
565
  }
526
566
  }
527
- const S = "0.1.0";
567
+ const U = "0.1.2";
528
568
  export {
529
- N as ClickHandler,
530
- x as DeviceOrientationControls,
531
- P as EventEmitter,
532
- L as LocationBased,
533
- T as SphMercProjection,
534
- H as Webcam,
535
- S as version
569
+ j as ClickHandler,
570
+ V as DeviceOrientationControls,
571
+ A as EventEmitter,
572
+ k as LocationBased,
573
+ w as SphMercProjection,
574
+ W as Webcam,
575
+ U as version
536
576
  };
package/dist/locar.umd.js CHANGED
@@ -1 +1 @@
1
- (function(d,c){typeof exports=="object"&&typeof module<"u"?c(exports,require("three")):typeof define=="function"&&define.amd?define(["exports","three"],c):(d=typeof globalThis<"u"?globalThis:d||self,c(d.locar={},d.THREE))})(this,(function(d,c){"use strict";function E(u){const t=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(u){for(const i in u)if(i!=="default"){const e=Object.getOwnPropertyDescriptor(u,i);Object.defineProperty(t,i,e.get?e:{enumerable:!0,get:()=>u[i]})}}return t.default=u,Object.freeze(t)}const v=E(c);class A{constructor(){this.EARTH=4007501668e-2,this.HALF_EARTH=2003750834e-2}project(t,i){return[this.#t(t),this.#l(i)]}unproject(t){return[this.#e(t[0]),this.#s(t[1])]}#t(t){return t/180*this.HALF_EARTH}#l(t){var i=Math.log(Math.tan((90+t)*Math.PI/360))/(Math.PI/180);return i*this.HALF_EARTH/180}#e(t){return t/this.HALF_EARTH*180}#s(t){var i=t/this.HALF_EARTH*180;return i=180/Math.PI*(2*Math.atan(Math.exp(i*Math.PI/180))-Math.PI/2),i}getID(){return"epsg:3857"}}class y{constructor(){this.eventHandlers={}}on(t,i){this.eventHandlers[t]=i}emit(t,...i){this.eventHandlers[t]?.call(this,...i)}}class C extends y{#t;#l;#e;#s;#c;#o;#i;#r;#a;#n;constructor(t,i,e={},l=null){super(),this.scene=t,this.camera=i,this.#t=new A,this.#e=null,this.#s=0,this.#c=100,this.#o=null,this.setGpsOptions(e),this.#i=null,this.#r=0,this.#a=0,this.#n=l}setProjection(t){this.#t=t}setGpsOptions(t={}){t.gpsMinDistance!==void 0&&(this.#s=t.gpsMinDistance),t.gpsMinAccuracy!==void 0&&(this.#c=t.gpsMinAccuracy)}async startGps(){if(this.#n){const i=await(await this.#n.sendData("/gps/start",{gpsMinDistance:this.#s,gpsMinAccuracy:this.#c})).json();this.#a=i.session}return this.#o===null?(this.#o=navigator.geolocation.watchPosition(t=>{this.#h(t)},t=>{this.emit("gpserror",t)},{enableHighAccuracy:!0}),!0):!1}stopGps(){return this.#o!==null?(navigator.geolocation.clearWatch(this.#o),this.#o=null,!0):!1}fakeGps(t,i,e=null,l=0){e!==null&&this.setElevation(e),this.#h({coords:{longitude:t,latitude:i,accuracy:l}})}lonLatToWorldCoords(t,i){const e=this.#t.project(t,i);if(this.#i)e[0]-=this.#i[0],e[1]-=this.#i[1];else throw"No initial position determined";return[e[0],-e[1]]}add(t,i,e,l,m={}){t.properties=m,this.#d(t,i,e,l),this.scene.add(t),this.#n?.sendData("/object/new",{position:t.position,x:t.position.x,z:t.position.z,session:this.#a,properties:m})}#d(t,i,e,l){const m=this.lonLatToWorldCoords(i,e);l!==void 0&&(t.position.y=l),[t.position.x,t.position.z]=m}setElevation(t){this.camera.position.y=t}#u(t,i){this.#i=this.#t.project(t,i)}#h(t){let i=Number.MAX_VALUE;this.#r++,this.#n?.sendData("/gps/new",{gpsCount:this.#r,lat:t.coords.latitude,lon:t.coords.longitude,acc:t.coords.accuracy,session:this.#a}),t.coords.accuracy<=this.#c&&(this.#e===null?this.#e={latitude:t.coords.latitude,longitude:t.coords.longitude}:i=this.#m(this.#e,t.coords),i>=this.#s&&(this.#e.longitude=t.coords.longitude,this.#e.latitude=t.coords.latitude,this.#i||(this.#u(t.coords.longitude,t.coords.latitude),this.#n?.sendData("/worldorigin/new",{gpsCount:this.#r,lat:t.coords.latitude,lon:t.coords.longitude,session:this.#a,initialPosition:this.#i})),this.#d(this.camera,t.coords.longitude,t.coords.latitude),this.#n?.sendData("/gps/accepted",{gpsCount:this.#r,cameraX:this.camera.position.x,cameraZ:this.camera.position.z,session:this.#a,distMoved:i}),this.emit("gpsupdate",{position:t,distMoved:i})))}#m(t,i){const e=v.MathUtils.degToRad(i.longitude-t.longitude),l=v.MathUtils.degToRad(i.latitude-t.latitude),m=Math.sin(l/2)*Math.sin(l/2)+Math.cos(v.MathUtils.degToRad(t.latitude))*Math.cos(v.MathUtils.degToRad(i.latitude))*(Math.sin(e/2)*Math.sin(e/2));return 2*Math.atan2(Math.sqrt(m),Math.sqrt(1-m))*6371e3}}class _ extends y{#t;constructor(t={video:{facingMode:"environment"}},i){super(),this.sceneWebcam=new v.Scene,i?this.#t=document.querySelector(i):(this.#t=document.createElement("video"),this.#t.setAttribute("autoplay",!0),this.#t.setAttribute("playsinline",!0),this.#t.style.display="none",document.body.appendChild(this.#t)),this.texture=new v.VideoTexture(this.#t),navigator.mediaDevices&&navigator.mediaDevices.getUserMedia?navigator.mediaDevices.getUserMedia(t).then(e=>{this.#t.addEventListener("loadedmetadata",()=>{this.#t.setAttribute("width",this.#t.videoWidth),this.#t.setAttribute("height",this.#t.videoHeight),this.#t.play(),this.emit("webcamstarted",{texture:this.texture})}),this.#t.srcObject=e}).catch(e=>{this.emit("webcamerror",{code:e.name,message:e.message})}):this.emit("webcamerror",{code:"LOCAR_NO_MEDIA_DEVICES_API",message:"Media devices API not supported"})}dispose(){this.texture.dispose()}}const w=navigator.userAgent.match(/iPhone|iPad|iPod/i)||/Macintosh/i.test(navigator.userAgent)&&navigator.maxTouchPoints!=null&&navigator.maxTouchPoints>1,b=new c.Vector3(0,0,1),I=new c.Euler,D=new c.Quaternion,T=new c.Quaternion(-Math.sqrt(.5),0,0,Math.sqrt(.5));class L extends c.EventDispatcher{constructor(t,i={}){super(),this.eventEmitter=new y;const e=this;this.object=t,this.object.rotation.reorder("YXZ"),this.enabled=!0,this.deviceOrientation=null,this.screenOrientation=0,this.alphaOffset=0,this.orientationOffset=0,this.initialOffset=null,this.lastCompassY=void 0,this.lastOrientation=null,this.TWO_PI=2*Math.PI,this.HALF_PI=.5*Math.PI,this.orientationChangeEventName="ondeviceorientationabsolute"in window?"deviceorientationabsolute":"deviceorientation",console.log("Device Orientation Event Name:",this.orientationChangeEventName),this.smoothingFactor=i.smoothingFactor||1,this.enablePermissionDialog=i.enablePermissionDialog??!0;const l=function({alpha:n,beta:s,gamma:a,webkitCompassHeading:r}){if(w){const o=360-r;e.alphaOffset=c.MathUtils.degToRad(o-n),e.deviceOrientation={alpha:n,beta:s,gamma:a,webkitCompassHeading:r}}else n<0&&(n+=360),e.deviceOrientation={alpha:n,beta:s,gamma:a};window.dispatchEvent(new CustomEvent("camera-rotation-change",{detail:{cameraRotation:t.rotation}}))},m=function(){e.screenOrientation=window.orientation||0,w&&(e.screenOrientation===90?e.orientationOffset=-e.HALF_PI:e.screenOrientation===-90?e.orientationOffset=e.HALF_PI:e.orientationOffset=0)},P=function(n,s,a,r,o){I.set(a,s,-r,"YXZ"),n.setFromEuler(I),n.multiply(T),n.multiply(D.setFromAxisAngle(b,-o))};this.connect=function(){m(),window.addEventListener("orientationchange",m),window.addEventListener(e.orientationChangeEventName,l),e.enabled=!0},this.disconnect=function(){window.removeEventListener("orientationchange",m),window.removeEventListener(e.orientationChangeEventName,l),e.enabled=!1,e.initialOffset=!1,e.deviceOrientation=null},this.requestOrientationPermissions=function(){window.DeviceOrientationEvent!==void 0&&typeof window.DeviceOrientationEvent.requestPermission=="function"?window.DeviceOrientationEvent.requestPermission().then(n=>{n==="granted"?this.eventEmitter.emit("deviceorientationgranted",{target:this}):this.eventEmitter.emit("deviceorientationerror",{code:"LOCAR_DEVICE_ORIENTATION_PERMISSION_DENIED",message:"Permission for device orientation denied - AR will not work correctly"})}).catch(function(n){this.eventEmitter.emit("deviceorientationerror",{code:"LOCAR_DEVICE_ORIENTATION_PERMISSION_FAILED",message:"Permission request for device orientation failed - AR will not work correctly"})}):this.eventEmitter.emit("deviceorientationerror",{code:"LOCAR_DEVICE_ORIENTATION_INTERNAL_ERROR",message:"Internal error: no requestPermission() found although requestOrientationPermissions() was called - please raise an issue on GitHub"})},this.update=function({theta:n=0}={theta:0}){if(e.enabled===!1)return;const s=e.deviceOrientation;if(s){let a=s.alpha?c.MathUtils.degToRad(s.alpha)+e.alphaOffset:0,r=s.beta?c.MathUtils.degToRad(s.beta):0,o=s.gamma?c.MathUtils.degToRad(s.gamma):0;const p=e.screenOrientation?c.MathUtils.degToRad(e.screenOrientation):0;if(e.smoothingFactor<1){if(e.lastOrientation){const h=e.smoothingFactor;a=e._getSmoothedAngle(a,e.lastOrientation.alpha,h),r=e._getSmoothedAngle(r+Math.PI,e.lastOrientation.beta,h),o=e._getSmoothedAngle(o+e.HALF_PI,e.lastOrientation.gamma,h,Math.PI)}else r+=Math.PI,o+=e.HALF_PI;e.lastOrientation={alpha:a,beta:r,gamma:o}}if(w){const h=new c.Quaternion;P(h,a,e.smoothingFactor<1?r-Math.PI:r,e.smoothingFactor<1?o-Math.PI/2:o,p);const f=new c.Euler().setFromQuaternion(h,"YXZ");let O=c.MathUtils.degToRad(360-s.webkitCompassHeading);e.smoothingFactor<1&&e.lastCompassY!==void 0&&(O=e._getSmoothedAngle(O,e.lastCompassY,e.smoothingFactor)),e.lastCompassY=O,f.y=O+(e.orientationOffset||0),h.setFromEuler(f),e.object.quaternion.copy(h)}else P(e.object.quaternion,w?a+e.alphaOffset:a,e.smoothingFactor<1?r-Math.PI:r,e.smoothingFactor<1?o-Math.PI/2:o,p)}},this.getCorrectedHeading=function(){const{deviceOrientation:n}=e;if(!n)return 0;let s=0;return w?(s=360-n.webkitCompassHeading,e.orientationOffset&&(s+=e.orientationOffset*(180/Math.PI),s=(s+360)%360)):(n.absolute===!0||e.orientationChangeEventName,s=n.alpha?n.alpha:0,s=(360-s)%360,s<0&&(s+=360)),s},this._orderAngle=function(n,s,a=e.TWO_PI){return s>n&&Math.abs(s-n)<a/2||n>s&&Math.abs(s-n)>a/2?{left:n,right:s}:{left:s,right:n}},this._getSmoothedAngle=function(n,s,a,r=e.TWO_PI){const o=e._orderAngle(n,s,r),p=o.left,h=o.right;o.left=0,o.right-=p,o.right<0&&(o.right+=r);let f=h==s?(1-a)*o.right+a*o.left:a*o.right+(1-a)*o.left;return f+=p,f>=r&&(f-=r),f},this.updateAlphaOffset=function(){e.initialOffset=!1},this.dispose=function(){e.disconnect()},this.getAlpha=function(){const{deviceOrientation:n}=e;return n&&n.alpha?c.MathUtils.degToRad(n.alpha)+e.alphaOffset:0},this.getBeta=function(){const{deviceOrientation:n}=e;return n&&n.beta?c.MathUtils.degToRad(n.beta):0},this.getGamma=function(){const{deviceOrientation:n}=e;return n&&n.gamma?c.MathUtils.degToRad(n.gamma):0},this.obtainPermissionGesture=function(){const n=document.createElement("div"),s=document.createElement("div"),a=document.createElement("div"),r=document.createElement("div");document.body.appendChild(n);const o={display:"flex",position:"fixed",top:0,left:0,width:"100%",height:"100%",zIndex:1,backgroundColor:"rgba(0,0,0,0.6)",justifyContent:"center",alignItems:"center"},p={backgroundColor:"white",padding:"6px",borderRadius:"3px",width:"36rem",height:"24rem"},h={width:"100%",height:"70%",display:"flex",justifyContent:"center",alignItems:"center"},f={display:"inline-flex",width:"100%",height:"30%",justifyContent:"center",alignItems:"center"};for(let g in o)n.style[g]=o[g];for(let g in p)s.style[g]=p[g];for(let g in h)a.style[g]=h[g];for(let g in f)r.style[g]=f[g];n.appendChild(s),s.appendChild(a),s.appendChild(r),a.innerHTML='<div style="font-size: 24pt; margin: 1rem;">This immersive website requires access to your device motion sensors.</div>';const O=()=>{this.requestOrientationPermissions(),n.style.display="none"},M=document.createElement("button");M.addEventListener("click",O),M.style.width="50%",M.style.height="80%",M.style.fontSize="20pt",M.appendChild(document.createTextNode("OK")),r.appendChild(M),document.body.appendChild(n)}}on(t,i){this.eventEmitter.on(t,i)}init(){window.DeviceOrientationEvent===void 0?this.eventEmitter.emit("deviceorientationerror",{code:"LOCAR_DEVICE_ORIENTATION_NOT_SUPPORTED",message:"Device orientation API not supported"}):window.isSecureContext===!1?this.eventEmitter.emit("deviceorientationerror",{code:"LOCAR_DEVICE_ORIENTATION_NO_HTTPS",message:"DeviceOrientationEvent is only available in secure contexts (https)"}):typeof window.DeviceOrientationEvent.requestPermission=="function"&&this.enablePermissionDialog?this.obtainPermissionGesture():this.eventEmitter.emit("deviceorientationgranted",{target:this})}}class R{constructor(t){this.raycaster=new v.Raycaster,this.normalisedMousePosition=new v.Vector2(null,null),t.domElement.addEventListener("click",i=>{this.normalisedMousePosition.set(i.clientX/t.domElement.clientWidth*2-1,-(i.clientY/t.domElement.clientHeight*2)+1)})}raycast(t,i){if(this.normalisedMousePosition.x!==null&&this.normalisedMousePosition.y!==null){this.raycaster.setFromCamera(this.normalisedMousePosition,t);const e=this.raycaster.intersectObjects(i.children,!1);return this.normalisedMousePosition.set(null,null),e}return[]}}const S="0.1.0";d.ClickHandler=R,d.DeviceOrientationControls=L,d.EventEmitter=y,d.LocationBased=C,d.SphMercProjection=A,d.Webcam=_,d.version=S,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"})}));
1
+ (function(h,c){typeof exports=="object"&&typeof module<"u"?c(exports,require("three")):typeof define=="function"&&define.amd?define(["exports","three"],c):(h=typeof globalThis<"u"?globalThis:h||self,c(h.locar={},h.THREE))})(this,(function(h,c){"use strict";function b(m){const t=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(m){for(const i in m)if(i!=="default"){const e=Object.getOwnPropertyDescriptor(m,i);Object.defineProperty(t,i,e.get?e:{enumerable:!0,get:()=>m[i]})}}return t.default=m,Object.freeze(t)}const p=b(c);class A{constructor(){this.EARTH=4007501668e-2,this.HALF_EARTH=2003750834e-2}project(t,i){return[this.#t(t),this.#l(i)]}unproject(t){return[this.#e(t[0]),this.#s(t[1])]}#t(t){return t/180*this.HALF_EARTH}#l(t){var i=Math.log(Math.tan((90+t)*Math.PI/360))/(Math.PI/180);return i*this.HALF_EARTH/180}#e(t){return t/this.HALF_EARTH*180}#s(t){var i=t/this.HALF_EARTH*180;return i=180/Math.PI*(2*Math.atan(Math.exp(i*Math.PI/180))-Math.PI/2),i}getID(){return"epsg:3857"}}class M{constructor(){this.eventHandlers={}}on(t,i){this.eventHandlers[t]=i}emit(t,...i){this.eventHandlers[t]?.call(this,...i)}}class w extends M{#t;#l;#e;#s;#c;#o;#i;#r;#a;#n;constructor(t,i,e={},l=null){super(),this.scene=t,this.camera=i,this.#t=new A,this.#e=null,this.#s=0,this.#c=100,this.#o=null,this.setGpsOptions(e),this.#i=null,this.#r=0,this.#a=0,this.#n=l}setProjection(t){this.#t=t}setGpsOptions(t={}){t.gpsMinDistance!==void 0&&(this.#s=t.gpsMinDistance),t.gpsMinAccuracy!==void 0&&(this.#c=t.gpsMinAccuracy)}async startGps(){if(this.#n){const i=await(await this.#n.sendData("/gps/start",{gpsMinDistance:this.#s,gpsMinAccuracy:this.#c})).json();this.#a=i.session}return this.#o===null?(this.#o=navigator.geolocation.watchPosition(t=>{this.#h(t)},t=>{this.emit("gpserror",t)},{enableHighAccuracy:!0}),!0):!1}stopGps(){return this.#o!==null?(navigator.geolocation.clearWatch(this.#o),this.#o=null,!0):!1}fakeGps(t,i,e=null,l=0){e!==null&&this.setElevation(e),this.#h({coords:{longitude:t,latitude:i,accuracy:l}})}lonLatToWorldCoords(t,i){const e=this.#t.project(t,i);if(this.#i)e[0]-=this.#i[0],e[1]-=this.#i[1];else throw"No initial position determined";return[e[0],-e[1]]}add(t,i,e,l,g={}){t.properties=g,this.#d(t,i,e,l),this.scene.add(t),this.#n?.sendData("/object/new",{position:t.position,x:t.position.x,z:t.position.z,session:this.#a,properties:g})}#d(t,i,e,l){const g=this.lonLatToWorldCoords(i,e);l!==void 0&&(t.position.y=l),[t.position.x,t.position.z]=g}setElevation(t){this.camera.position.y=t}#u(t,i){this.#i=this.#t.project(t,i)}#h(t){let i=Number.MAX_VALUE;this.#r++,this.#n?.sendData("/gps/new",{gpsCount:this.#r,lat:t.coords.latitude,lon:t.coords.longitude,acc:t.coords.accuracy,session:this.#a}),t.coords.accuracy<=this.#c&&(this.#e===null?this.#e={latitude:t.coords.latitude,longitude:t.coords.longitude}:i=this.#m(this.#e,t.coords),i>=this.#s&&(this.#e.longitude=t.coords.longitude,this.#e.latitude=t.coords.latitude,this.#i||(this.#u(t.coords.longitude,t.coords.latitude),this.#n?.sendData("/worldorigin/new",{gpsCount:this.#r,lat:t.coords.latitude,lon:t.coords.longitude,session:this.#a,initialPosition:this.#i})),this.#d(this.camera,t.coords.longitude,t.coords.latitude),this.#n?.sendData("/gps/accepted",{gpsCount:this.#r,cameraX:this.camera.position.x,cameraZ:this.camera.position.z,session:this.#a,distMoved:i}),this.emit("gpsupdate",{position:t,distMoved:i})))}#m(t,i){const e=p.MathUtils.degToRad(i.longitude-t.longitude),l=p.MathUtils.degToRad(i.latitude-t.latitude),g=Math.sin(l/2)*Math.sin(l/2)+Math.cos(p.MathUtils.degToRad(t.latitude))*Math.cos(p.MathUtils.degToRad(i.latitude))*(Math.sin(e/2)*Math.sin(e/2));return 2*Math.atan2(Math.sqrt(g),Math.sqrt(1-g))*6371e3}}class D extends M{#t;constructor(t={video:{facingMode:"environment"}},i){super(),this.sceneWebcam=new p.Scene,i?this.#t=document.querySelector(i):(this.#t=document.createElement("video"),this.#t.setAttribute("autoplay",!0),this.#t.setAttribute("playsinline",!0),this.#t.style.display="none",document.body.appendChild(this.#t)),this.texture=new p.VideoTexture(this.#t),navigator.mediaDevices&&navigator.mediaDevices.getUserMedia?navigator.mediaDevices.getUserMedia(t).then(e=>{this.#t.addEventListener("loadedmetadata",()=>{this.#t.setAttribute("width",this.#t.videoWidth),this.#t.setAttribute("height",this.#t.videoHeight),this.#t.play(),this.emit("webcamstarted",{texture:this.texture})}),this.#t.srcObject=e}).catch(e=>{this.emit("webcamerror",{code:e.name,message:e.message})}):this.emit("webcamerror",{code:"LOCAR_NO_MEDIA_DEVICES_API",message:"Media devices API not supported"})}dispose(){this.texture.dispose()}}const S="locar-device-orientation-permission-modal",T="locar-device-orientation-permission-button",N="locar-device-orientation-permission-message",R="locar-device-orientation-permission-inner",L="locar-device-orientation-permission-button-inner",P="This immersive website requires access to your device motion sensors.",I=navigator.userAgent.match(/iPhone|iPad|iPod/i)||/Macintosh/i.test(navigator.userAgent)&&navigator.maxTouchPoints!=null&&navigator.maxTouchPoints>1,x=new c.Vector3(0,0,1),_=new c.Euler,F=new c.Quaternion,U=new c.Quaternion(-Math.sqrt(.5),0,0,Math.sqrt(.5));class j extends c.EventDispatcher{constructor(t,i={}){super(),this.eventEmitter=new M;const e=this;this.object=t,this.object.rotation.reorder("YXZ"),this.enabled=!0,this.deviceOrientation=null,this.screenOrientation=0,this.alphaOffset=0,this.orientationOffset=0,this.initialOffset=null,this.lastCompassY=void 0,this.lastOrientation=null,this.TWO_PI=2*Math.PI,this.HALF_PI=.5*Math.PI,this.orientationChangeEventName="ondeviceorientationabsolute"in window?"deviceorientationabsolute":"deviceorientation",this.smoothingFactor=i.smoothingFactor||1,this.enablePermissionDialog=i.enablePermissionDialog??!0,this.enableInlineStyling=i.enableStyling??!0,this.preferConfirmDialog=i.preferConfirmDialog??!1;const l=function({alpha:n,beta:s,gamma:a,webkitCompassHeading:r}){if(I){const o=360-r;e.alphaOffset=c.MathUtils.degToRad(o-n),e.deviceOrientation={alpha:n,beta:s,gamma:a,webkitCompassHeading:r}}else n<0&&(n+=360),e.deviceOrientation={alpha:n,beta:s,gamma:a};window.dispatchEvent(new CustomEvent("camera-rotation-change",{detail:{cameraRotation:t.rotation}}))},g=function(){e.screenOrientation=window.orientation||0,I&&(e.screenOrientation===90?e.orientationOffset=-e.HALF_PI:e.screenOrientation===-90?e.orientationOffset=e.HALF_PI:e.orientationOffset=0)},E=function(n,s,a,r,o){_.set(a,s,-r,"YXZ"),n.setFromEuler(_),n.multiply(U),n.multiply(F.setFromAxisAngle(x,-o))};this.connect=function(){g(),window.addEventListener("orientationchange",g),window.addEventListener(e.orientationChangeEventName,l),e.enabled=!0},this.disconnect=function(){window.removeEventListener("orientationchange",g),window.removeEventListener(e.orientationChangeEventName,l),e.enabled=!1,e.initialOffset=!1,e.deviceOrientation=null},this.requestOrientationPermissions=function(){window.DeviceOrientationEvent!==void 0&&typeof window.DeviceOrientationEvent.requestPermission=="function"?window.DeviceOrientationEvent.requestPermission().then(n=>{n==="granted"?this.eventEmitter.emit("deviceorientationgranted",{target:this}):this.eventEmitter.emit("deviceorientationerror",{code:"LOCAR_DEVICE_ORIENTATION_PERMISSION_DENIED",message:"Permission for device orientation denied - AR will not work correctly"})}).catch(function(n){this.eventEmitter.emit("deviceorientationerror",{code:"LOCAR_DEVICE_ORIENTATION_PERMISSION_FAILED",message:"Permission request for device orientation failed - AR will not work correctly",error:JSON.stringify(n,null,2)})}):this.eventEmitter.emit("deviceorientationerror",{code:"LOCAR_DEVICE_ORIENTATION_INTERNAL_ERROR",message:"Internal error: no requestPermission() found although requestOrientationPermissions() was called - please raise an issue on GitHub"})},this.update=function({theta:n=0}={theta:0}){if(e.enabled===!1)return;const s=e.deviceOrientation;if(s){let a=s.alpha?c.MathUtils.degToRad(s.alpha)+e.alphaOffset:0,r=s.beta?c.MathUtils.degToRad(s.beta):0,o=s.gamma?c.MathUtils.degToRad(s.gamma):0;const v=e.screenOrientation?c.MathUtils.degToRad(e.screenOrientation):0;if(e.smoothingFactor<1){if(e.lastOrientation){const u=e.smoothingFactor;a=e._getSmoothedAngle(a,e.lastOrientation.alpha,u),r=e._getSmoothedAngle(r+Math.PI,e.lastOrientation.beta,u),o=e._getSmoothedAngle(o+e.HALF_PI,e.lastOrientation.gamma,u,Math.PI)}else r+=Math.PI,o+=e.HALF_PI;e.lastOrientation={alpha:a,beta:r,gamma:o}}if(I){const u=new c.Quaternion;E(u,a,e.smoothingFactor<1?r-Math.PI:r,e.smoothingFactor<1?o-Math.PI/2:o,v);const f=new c.Euler().setFromQuaternion(u,"YXZ");let O=c.MathUtils.degToRad(360-s.webkitCompassHeading);e.smoothingFactor<1&&e.lastCompassY!==void 0&&(O=e._getSmoothedAngle(O,e.lastCompassY,e.smoothingFactor)),e.lastCompassY=O,f.y=O+(e.orientationOffset||0),u.setFromEuler(f),e.object.quaternion.copy(u)}else E(e.object.quaternion,I?a+e.alphaOffset:a,e.smoothingFactor<1?r-Math.PI:r,e.smoothingFactor<1?o-Math.PI/2:o,v)}},this.getCorrectedHeading=function(){const{deviceOrientation:n}=e;if(!n)return 0;let s=0;return I?(s=360-n.webkitCompassHeading,e.orientationOffset&&(s+=e.orientationOffset*(180/Math.PI),s=(s+360)%360)):(n.absolute===!0||e.orientationChangeEventName,s=n.alpha?n.alpha:0,s=(360-s)%360,s<0&&(s+=360)),s},this._orderAngle=function(n,s,a=e.TWO_PI){return s>n&&Math.abs(s-n)<a/2||n>s&&Math.abs(s-n)>a/2?{left:n,right:s}:{left:s,right:n}},this._getSmoothedAngle=function(n,s,a,r=e.TWO_PI){const o=e._orderAngle(n,s,r),v=o.left,u=o.right;o.left=0,o.right-=v,o.right<0&&(o.right+=r);let f=u==s?(1-a)*o.right+a*o.left:a*o.right+(1-a)*o.left;return f+=v,f>=r&&(f-=r),f},this.updateAlphaOffset=function(){e.initialOffset=!1},this.dispose=function(){e.disconnect()},this.getAlpha=function(){const{deviceOrientation:n}=e;return n&&n.alpha?c.MathUtils.degToRad(n.alpha)+e.alphaOffset:0},this.getBeta=function(){const{deviceOrientation:n}=e;return n&&n.beta?c.MathUtils.degToRad(n.beta):0},this.getGamma=function(){const{deviceOrientation:n}=e;return n&&n.gamma?c.MathUtils.degToRad(n.gamma):0},this.createObtainPermissionGestureDialog=function(){const n=document.createElement("div");n.classList.add(S);const s=document.createElement("div");s.classList.add(R);const a=document.createElement("div");a.classList.add(N);const r=document.createElement("div");r.classList.add(L);const o=document.createElement("button");if(o.classList.add(T),document.body.appendChild(n),this.enableInlineStyling===!0){const u={fontFamily:"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'",display:"flex",position:"fixed",zIndex:1e5,justifyContent:"center",alignItems:"center",backgroundColor:"rgba(0,0,0,0.2)",inset:0,padding:"20px"},f={backgroundColor:"rgba(220, 220, 220, 0.85)",padding:"6px 0",borderRadius:"10px",width:"100%",maxWidth:"400px"},O={padding:"10px 12px",textAlign:"center",fontWeight:400,fontSize:"13px",display:"flex",justifyContent:"center",alignItems:"center"},y={display:"block",textAlign:"center",textDecoration:"none",borderTop:"rgb(180,180,180) solid 1px"},C={display:"block",width:"100%",textAlign:"center",appearance:"none",background:"none",border:"none",outline:"none",padding:"10px",fontWeight:400,fontSize:"16px",color:"#2e7cf1",cursor:"pointer"};for(let d in u)n.style[d]=u[d];for(let d in f)s.style[d]=f[d];for(let d in O)a.style[d]=O[d];for(let d in y)r.style[d]=y[d];for(let d in C)o.style[d]=C[d]}n.appendChild(s),s.appendChild(a),s.appendChild(r),a.appendChild(document.createTextNode(P));const v=()=>{this.requestOrientationPermissions(),n.style.display="none"};o.addEventListener("click",v),o.appendChild(document.createTextNode("OK")),r.appendChild(o),document.body.appendChild(n)},this.obtainPermissionGesture=function(){this.preferConfirmDialog===!0?window.confirm(P)&&this.requestOrientationPermissions():this.createObtainPermissionGestureDialog()}}on(t,i){this.eventEmitter.on(t,i)}init(){window.DeviceOrientationEvent===void 0?this.eventEmitter.emit("deviceorientationerror",{code:"LOCAR_DEVICE_ORIENTATION_NOT_SUPPORTED",message:"Device orientation API not supported"}):window.isSecureContext===!1?this.eventEmitter.emit("deviceorientationerror",{code:"LOCAR_DEVICE_ORIENTATION_NO_HTTPS",message:"DeviceOrientationEvent is only available in secure contexts (https)"}):typeof window.DeviceOrientationEvent.requestPermission=="function"&&this.enablePermissionDialog?this.obtainPermissionGesture():this.eventEmitter.emit("deviceorientationgranted",{target:this})}}class q{constructor(t){this.raycaster=new p.Raycaster,this.normalisedMousePosition=new p.Vector2(null,null),t.domElement.addEventListener("click",i=>{this.normalisedMousePosition.set(i.clientX/t.domElement.clientWidth*2-1,-(i.clientY/t.domElement.clientHeight*2)+1)})}raycast(t,i){if(this.normalisedMousePosition.x!==null&&this.normalisedMousePosition.y!==null){this.raycaster.setFromCamera(this.normalisedMousePosition,t);const e=this.raycaster.intersectObjects(i.children,!1);return this.normalisedMousePosition.set(null,null),e}return[]}}const k="0.1.2";h.ClickHandler=q,h.DeviceOrientationControls=j,h.EventEmitter=M,h.LocationBased=w,h.SphMercProjection=A,h.Webcam=D,h.version=k,Object.defineProperty(h,Symbol.toStringTag,{value:"Module"})}));
@@ -0,0 +1,316 @@
1
+ declare module "locar" {
2
+ import * as THREE from "three";
3
+
4
+ /**
5
+ * Generic, small event emitter that only stores a single handler per event name.
6
+ */
7
+ export class EventEmitter {
8
+ protected eventHandlers: Record<string, (...args: any[]) => void>;
9
+ on(eventName: string, handler: (...args: any[]) => void): void;
10
+ emit(eventName: string, ...params: any[]): void;
11
+ }
12
+
13
+ /**
14
+ * Spherical Mercator (EPSG:3857) projection used by `LocationBased`.
15
+ */
16
+ export class SphMercProjection {
17
+ constructor();
18
+ /**
19
+ * @returns [easting, northing]
20
+ */
21
+ project(lon: number, lat: number): [number, number];
22
+ /**
23
+ * @returns [lon, lat]
24
+ */
25
+ unproject(projected: [number, number]): [number, number];
26
+ getID(): "epsg:3857";
27
+ }
28
+
29
+ /**
30
+ * Options for GPS in `LocationBased`.
31
+ * Mirrored from `setGpsOptions(...)`
32
+ */
33
+ export interface GpsOptions {
34
+ /**
35
+ * Meters the device must move to accept the next GPS reading.
36
+ */
37
+ gpsMinDistance?: number;
38
+ /**
39
+ * Minimum accuracy in meters to accept a GPS reading.
40
+ */
41
+ gpsMinAccuracy?: number;
42
+ }
43
+
44
+ /**
45
+ * Optional logger object passed to `LocationBased` for debug.
46
+ */
47
+ export interface ServerLogger {
48
+ /**
49
+ * Should send data to server. Implementation-specific in userland.
50
+ */
51
+ sendData(endpoint: string, data: any): Promise<Response> | Response;
52
+ }
53
+
54
+ /**
55
+ * Main location AR class.
56
+ */
57
+ export class LocationBased extends EventEmitter {
58
+ readonly scene: THREE.Scene;
59
+ readonly camera: THREE.Camera;
60
+
61
+ constructor(
62
+ scene: THREE.Scene,
63
+ camera: THREE.Camera,
64
+ options?: GpsOptions,
65
+ serverLogger?: ServerLogger | null,
66
+ );
67
+
68
+ /**
69
+ * Set the projection (must have `project(lon,lat): [x,y]`).
70
+ */
71
+ setProjection(proj: {
72
+ project(lon: number, lat: number): [number, number];
73
+ }): void;
74
+
75
+ /**
76
+ * Update GPS options at runtime.
77
+ */
78
+ setGpsOptions(options?: GpsOptions): void;
79
+
80
+ /**
81
+ * Start real GPS (`navigator.geolocation.watchPosition`).
82
+ */
83
+ startGps(): Promise<boolean> | boolean;
84
+
85
+ /**
86
+ * Stop real GPS.
87
+ */
88
+ stopGps(): boolean;
89
+
90
+ /**
91
+ * Send a fake GPS position.
92
+ */
93
+ fakeGps(lon: number, lat: number, elev?: number | null, acc?: number): void;
94
+
95
+ /**
96
+ * Convert lon/lat to world coordinates (needs initial position).
97
+ */
98
+ lonLatToWorldCoords(lon: number, lat: number): [number, number];
99
+
100
+ /**
101
+ * Add a THREE object at lon/lat/(elev) and put in the scene.
102
+ */
103
+ add(
104
+ object: THREE.Object3D,
105
+ lon: number,
106
+ lat: number,
107
+ elev?: number,
108
+ properties?: Record<string, any>,
109
+ ): void;
110
+
111
+ /**
112
+ * Set camera elevation (y).
113
+ */
114
+ setElevation(elev: number): void;
115
+
116
+ /**
117
+ * Events:
118
+ * - "gpsupdate": `{ position: GeolocationPosition, distMoved: number }``
119
+ * - "gpserror": `GeolocationPositionError`
120
+ */
121
+ on(
122
+ eventName: "gpsupdate",
123
+ handler: (data: {
124
+ position: GeolocationPosition;
125
+ distMoved: number;
126
+ }) => void,
127
+ ): void;
128
+ on(
129
+ eventName: "gpserror",
130
+ handler: (error: GeolocationPositionError) => void,
131
+ ): void;
132
+ on(eventName: string, handler: (...args: any[]) => void): void;
133
+ }
134
+
135
+ /**
136
+ * Small webcam wrapper that creates a hidden `<video>` and a `THREE.VideoTexture`.
137
+ */
138
+ export interface WebcamStartedEvent {
139
+ texture: THREE.VideoTexture;
140
+ }
141
+ export interface WebcamErrorEvent {
142
+ code: string;
143
+ message: string;
144
+ }
145
+
146
+ export class Webcam extends EventEmitter {
147
+ /**
148
+ * @param constraints `MediaDevices.getUserMedia` constraints
149
+ * @param videoElementSelector selector for an existing `<video>`; if falsy, it creates one
150
+ */
151
+ constructor(
152
+ constraints?: MediaStreamConstraints,
153
+ videoElementSelector?: string | null,
154
+ );
155
+
156
+ /**
157
+ * Texture that streams the camera feed.
158
+ */
159
+ readonly texture: THREE.VideoTexture;
160
+
161
+ /**
162
+ * Free GPU resources.
163
+ */
164
+ dispose(): void;
165
+
166
+ // events
167
+ on(
168
+ eventName: "webcamstarted",
169
+ handler: (e: WebcamStartedEvent) => void,
170
+ ): void;
171
+ on(eventName: "webcamerror", handler: (e: WebcamErrorEvent) => void): void;
172
+ on(eventName: string, handler: (...args: any[]) => void): void;
173
+ }
174
+
175
+ /**
176
+ * Click handler/raycaster wrapper.
177
+ */
178
+ export class ClickHandler {
179
+ constructor(renderer: THREE.WebGLRenderer);
180
+
181
+ /**
182
+ * Cast a ray and return intersects with scene children.
183
+ */
184
+ raycast(camera: THREE.Camera, scene: THREE.Scene): THREE.Intersection[];
185
+ }
186
+
187
+ export interface DeviceOrientationControlsOptions {
188
+ /**
189
+ * 0 < k <= 1. Lower = more smoothing.
190
+ */
191
+ smoothingFactor?: number;
192
+ /**
193
+ * Show iOS permission dialog.
194
+ */
195
+ enablePermissionDialog?: boolean;
196
+ /**
197
+ * Apply inline styles on the created dialog.
198
+ */
199
+ enableStyling?: boolean;
200
+ /**
201
+ * Use `window.confirm(...)` instead of building DOM.
202
+ */
203
+ preferConfirmDialog?: boolean;
204
+ }
205
+
206
+ export interface DeviceOrientationGrantedEvent {
207
+ target: DeviceOrientationControls;
208
+ }
209
+
210
+ export interface DeviceOrientationErrorEvent {
211
+ code: string;
212
+ message: string;
213
+ error?: string;
214
+ }
215
+
216
+ export class DeviceOrientationControls extends THREE.EventDispatcher {
217
+ /**
218
+ * @param object usually a `THREE.Camera`
219
+ */
220
+ constructor(
221
+ object: THREE.Object3D,
222
+ options?: DeviceOrientationControlsOptions,
223
+ );
224
+
225
+ /**
226
+ * Initialize the device orientation controls. Should be called first to set up permission flow.
227
+ */
228
+ init(): void;
229
+
230
+ /**
231
+ * Begin listening to orientation + screenorientation (must be called after permission is granted on iOS).
232
+ */
233
+ connect(): void;
234
+
235
+ /**
236
+ * Stop listening.
237
+ */
238
+ disconnect(): void;
239
+
240
+ /**
241
+ * iOS: must be called in a user gesture to ask for perms.
242
+ */
243
+ requestOrientationPermissions(): void;
244
+
245
+ /**
246
+ * Create the DOM dialog (iOS-style) and wire it to requestOrientationPermissions.
247
+ */
248
+ createObtainPermissionGestureDialog(): void;
249
+
250
+ /**
251
+ * Choose `confirm()` vs DOM dialog.
252
+ */
253
+ obtainPermissionGesture(): void;
254
+
255
+ /**
256
+ * Call each frame.
257
+ */
258
+ update(args?: { theta?: number }): void;
259
+
260
+ /**
261
+ * iOS heading correction.
262
+ */
263
+ getCorrectedHeading(): number;
264
+
265
+ /**
266
+ * Provided in AR.js fix — forces re-evaluation of alpha offset.
267
+ */
268
+ updateAlphaOffset(): void;
269
+
270
+ /**
271
+ * Getters (radians).
272
+ */
273
+ getAlpha(): number;
274
+ getBeta(): number;
275
+ getGamma(): number;
276
+
277
+ dispose(): void;
278
+
279
+ on(
280
+ eventName: "deviceorientationgranted",
281
+ handler: (ev: DeviceOrientationGrantedEvent) => void,
282
+ ): void;
283
+ on(
284
+ eventName: "deviceorientationerror",
285
+ handler: (ev: DeviceOrientationErrorEvent) => void,
286
+ ): void;
287
+ on(eventName: string, handler: (...args: any[]) => void): void;
288
+
289
+ enabled: boolean;
290
+ }
291
+
292
+ /**
293
+ * Version string.
294
+ */
295
+ export const version: string;
296
+
297
+ /**
298
+ * Default export from build (UMD/ES) is the namespace with all the above.
299
+ * Modeled so that:
300
+ * `import * as LocAR from "locar"`
301
+ * and
302
+ * `import LocAR from "locar"``
303
+ * both typecheck.
304
+ */
305
+ const LocAR: {
306
+ EventEmitter: typeof EventEmitter;
307
+ SphMercProjection: typeof SphMercProjection;
308
+ LocationBased: typeof LocationBased;
309
+ Webcam: typeof Webcam;
310
+ ClickHandler: typeof ClickHandler;
311
+ DeviceOrientationControls: typeof DeviceOrientationControls;
312
+ version: string;
313
+ };
314
+
315
+ export default LocAR;
316
+ }
package/package.json CHANGED
@@ -1,24 +1,35 @@
1
1
  {
2
2
  "name": "locar",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Location-based AR from AR.js.",
5
5
  "files": [
6
- "dist"
6
+ "dist",
7
+ "lib/types"
7
8
  ],
8
9
  "main": "./dist/locar.umd.js",
9
10
  "module": "./dist/locar.es.js",
11
+ "types": "lib/types/locar.d.ts",
10
12
  "exports": {
11
13
  ".": {
14
+ "types": "./lib/types/locar.d.ts",
12
15
  "import": "./dist/locar.es.js",
13
16
  "require": "./dist/locar.umd.js"
14
17
  }
15
18
  },
16
19
  "scripts": {
17
- "build": "vite build && npm pack",
20
+ "lint": "eslint ./lib --ext .js,.json,.css",
21
+ "lint:fix": "prettier --write ./lib && eslint ./lib --ext .js,.json,.css --fix",
22
+ "build": "npm run lint && vite build && npm pack",
18
23
  "makedocs": "jsdoc -r lib -d docs/api"
19
24
  },
20
25
  "devDependencies": {
26
+ "@eslint/css": "^0.11.0",
27
+ "@eslint/js": "^9.35.0",
28
+ "@eslint/json": "^0.13.2",
29
+ "eslint": "^9.38.0",
30
+ "globals": "^16.4.0",
21
31
  "jsdoc": "^4.0.4",
32
+ "prettier-eslint": "^16.4.2",
22
33
  "vite": "^7.1.3"
23
34
  },
24
35
  "dependencies": {