@needle-tools/engine 4.12.3 → 4.12.4-next.4498846

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 (80) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/components.needle.json +1 -1
  3. package/dist/{gltf-progressive-Bfpfaz84.umd.cjs → gltf-progressive-BqUnxvCx.umd.cjs} +1 -1
  4. package/dist/{gltf-progressive-hFPACYio.min.js → gltf-progressive-CSaX5HQb.min.js} +2 -2
  5. package/dist/{gltf-progressive-DPunMlEM.js → gltf-progressive-ChnIhDXx.js} +27 -27
  6. package/dist/{loader.worker-DWzfDpAl.js → loader.worker-C1GG9A7C.js} +6 -6
  7. package/dist/{needle-engine.bundle-u-rSDw6R.js → needle-engine.bundle-C9mGVluu.js} +7444 -7358
  8. package/dist/{needle-engine.bundle-CLPD2ttK.umd.cjs → needle-engine.bundle-CnmG19ga.umd.cjs} +300 -293
  9. package/dist/{needle-engine.bundle-B3ssYJS0.min.js → needle-engine.bundle-ZJOekt7-.min.js} +297 -290
  10. package/dist/needle-engine.d.ts +53 -28
  11. package/dist/needle-engine.js +4 -4
  12. package/dist/needle-engine.min.js +1 -1
  13. package/dist/needle-engine.umd.cjs +1 -1
  14. package/dist/{postprocessing-ClLv0reO.min.js → postprocessing-12-UW7je.min.js} +1 -1
  15. package/dist/{postprocessing-BHQvwehB.umd.cjs → postprocessing-B3Hu0Ryi.umd.cjs} +1 -1
  16. package/dist/{postprocessing-DLI2N3LL.js → postprocessing-R535krvT.js} +2 -2
  17. package/dist/{three-Bf2NBxAw.umd.cjs → three-BzxwLtUE.umd.cjs} +176 -176
  18. package/dist/{three-BCCkyCA5.js → three-D9pcFbxc.js} +4637 -4636
  19. package/dist/{three-W7zWTcfP.min.js → three-DMvLgxja.min.js} +176 -176
  20. package/dist/{three-examples-DB5Uoja4.min.js → three-examples-CIv2roOA.min.js} +1 -1
  21. package/dist/{three-examples-Djbk6WA4.umd.cjs → three-examples-CjSwCv_b.umd.cjs} +1 -1
  22. package/dist/{three-examples-D4rE49Ui.js → three-examples-F0MJj0vr.js} +1 -1
  23. package/dist/{three-mesh-ui-zsOOA5Pq.umd.cjs → three-mesh-ui-BLnJQzMl.umd.cjs} +1 -1
  24. package/dist/{three-mesh-ui-CIez6qJQ.min.js → three-mesh-ui-BllgajJz.min.js} +1 -1
  25. package/dist/{three-mesh-ui-3nSSizT4.js → three-mesh-ui-DYyiRn5Y.js} +1 -1
  26. package/dist/{vendor-tyBvnMF-.umd.cjs → vendor-BFgQSG2m.umd.cjs} +1 -1
  27. package/dist/{vendor-DMZcbVO1.js → vendor-BIFy-gRe.js} +1 -1
  28. package/dist/{vendor-sURMCFSI.min.js → vendor-ChgmXMYr.min.js} +1 -1
  29. package/lib/engine/debug/debug_overlay.js +13 -2
  30. package/lib/engine/debug/debug_overlay.js.map +1 -1
  31. package/lib/engine/engine_animation.d.ts +7 -0
  32. package/lib/engine/engine_animation.js +16 -0
  33. package/lib/engine/engine_animation.js.map +1 -1
  34. package/lib/engine/engine_input.js +5 -3
  35. package/lib/engine/engine_input.js.map +1 -1
  36. package/lib/engine/webcomponents/WebXRButtons.js +8 -1
  37. package/lib/engine/webcomponents/WebXRButtons.js.map +1 -1
  38. package/lib/engine/webcomponents/buttons.js +4 -0
  39. package/lib/engine/webcomponents/buttons.js.map +1 -1
  40. package/lib/engine/webcomponents/icons.js +44 -5
  41. package/lib/engine/webcomponents/icons.js.map +1 -1
  42. package/lib/engine/webcomponents/logo-element.js +0 -1
  43. package/lib/engine/webcomponents/logo-element.js.map +1 -1
  44. package/lib/engine/webcomponents/needle menu/needle-menu.d.ts +19 -3
  45. package/lib/engine/webcomponents/needle menu/needle-menu.js +248 -161
  46. package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
  47. package/lib/engine/webcomponents/needle-engine.ar-overlay.js +1 -0
  48. package/lib/engine/webcomponents/needle-engine.ar-overlay.js.map +1 -1
  49. package/lib/engine/xr/NeedleXRSession.d.ts +2 -0
  50. package/lib/engine/xr/NeedleXRSession.js +19 -10
  51. package/lib/engine/xr/NeedleXRSession.js.map +1 -1
  52. package/lib/engine-components/Animation.js +2 -0
  53. package/lib/engine-components/Animation.js.map +1 -1
  54. package/lib/engine-components/AnimatorController.js +2 -0
  55. package/lib/engine-components/AnimatorController.js.map +1 -1
  56. package/lib/engine-components/Light.d.ts +17 -12
  57. package/lib/engine-components/Light.js +52 -36
  58. package/lib/engine-components/Light.js.map +1 -1
  59. package/lib/engine-components/webxr/WebXR.js +6 -8
  60. package/lib/engine-components/webxr/WebXR.js.map +1 -1
  61. package/lib/engine-components/webxr/WebXRImageTracking.js +9 -2
  62. package/lib/engine-components/webxr/WebXRImageTracking.js.map +1 -1
  63. package/package.json +3 -3
  64. package/plugins/common/license.js +3 -3
  65. package/src/engine/debug/debug_overlay.ts +15 -2
  66. package/src/engine/engine_animation.ts +19 -1
  67. package/src/engine/engine_input.ts +5 -3
  68. package/src/engine/webcomponents/WebXRButtons.ts +9 -1
  69. package/src/engine/webcomponents/buttons.ts +5 -0
  70. package/src/engine/webcomponents/icons.ts +47 -5
  71. package/src/engine/webcomponents/index.ts +1 -1
  72. package/src/engine/webcomponents/logo-element.ts +0 -1
  73. package/src/engine/webcomponents/needle menu/needle-menu.ts +270 -165
  74. package/src/engine/webcomponents/needle-engine.ar-overlay.ts +1 -0
  75. package/src/engine/xr/NeedleXRSession.ts +23 -10
  76. package/src/engine-components/Animation.ts +4 -1
  77. package/src/engine-components/AnimatorController.ts +3 -0
  78. package/src/engine-components/Light.ts +50 -42
  79. package/src/engine-components/webxr/WebXR.ts +6 -9
  80. package/src/engine-components/webxr/WebXRImageTracking.ts +12 -2
@@ -1,6 +1,8 @@
1
+ import { TestSimulateUserData } from "../../../needle-engine.js";
1
2
  import { showBalloonMessage } from "../../debug/debug.js";
2
3
  import type { Context } from "../../engine_context.js";
3
4
  import { hasCommercialLicense, onLicenseCheckResultChanged, Telemetry } from "../../engine_license.js";
5
+ import { LeftRoomResponse } from "../../engine_networking.js";
4
6
  import { isLocalNetwork } from "../../engine_networking_utils.js";
5
7
  import { DeviceUtilities, getParam } from "../../engine_utils.js";
6
8
  import { onXRSessionStart, XRSessionEventArgs } from "../../xr/events.js";
@@ -45,9 +47,17 @@ export declare type ButtonInfo = {
45
47
  label: string,
46
48
  /** Material icon name: https://fonts.google.com/icons */
47
49
  icon?: string,
48
- /** "left" or "right" to place the icon on the left or right side of the button. Default is "left" */
50
+ /** "left" or "right" to place the icon on the left or right side of the button. Default is "left" */
49
51
  iconSide?: "left" | "right",
50
- /** Low priority is icon is on the left, high priority is icon is on the right. Default is undefined */
52
+ /**
53
+ * Priority controls the order of buttons in the menu.
54
+ * If not enough space is available to show all buttons - the highest priority elements will always be visible
55
+ *
56
+ * **Sorting**
57
+ * Low priority is icon is on the left,
58
+ * high priority is icon is on the right.
59
+ * @default undefined
60
+ */
51
61
  priority?: number;
52
62
  /** Experimental. Allows to put two buttons in one row for the compact layout */
53
63
  class?: "row2";
@@ -101,6 +111,20 @@ export declare type ButtonInfo = {
101
111
  * @category HTML
102
112
  */
103
113
  export class NeedleMenu {
114
+
115
+ static setElementPriority(button: HTMLElement, priority: number) {
116
+ button.setAttribute("priority", String(priority));
117
+ }
118
+
119
+ static getElementPriority(button: HTMLElement): number | undefined {
120
+ const priority = button.getAttribute("priority");
121
+ if (priority) {
122
+ const val = Number.parseFloat(priority);
123
+ if (!Number.isNaN(val)) return val;
124
+ }
125
+ return undefined;
126
+ }
127
+
104
128
  private readonly _context: Context;
105
129
  private readonly _menu: NeedleMenuElement;
106
130
  private readonly _spatialMenu: NeedleSpatialMenu;
@@ -247,7 +271,6 @@ export class NeedleMenu {
247
271
  return;
248
272
  }
249
273
  this._muteButton = ButtonsFactory.getOrCreate().createMuteButton(this._context);
250
- this._muteButton.setAttribute("priority", "100");
251
274
  this._menu.appendChild(this._muteButton);
252
275
  }
253
276
  private _muteButton?: HTMLButtonElement;
@@ -260,7 +283,6 @@ export class NeedleMenu {
260
283
  }
261
284
  this._fullscreenButton = ButtonsFactory.getOrCreate().createFullscreenButton(this._context);
262
285
  if (this._fullscreenButton) {
263
- this._fullscreenButton.setAttribute("priority", "150");
264
286
  this._menu.appendChild(this._fullscreenButton);
265
287
  }
266
288
  }
@@ -274,6 +296,8 @@ export class NeedleMenu {
274
296
 
275
297
  }
276
298
 
299
+ // #region Web component
300
+
277
301
  /**
278
302
  * `<needle-menu>` web component — lightweight menu used by Needle Engine.
279
303
  *
@@ -333,141 +357,144 @@ export class NeedleMenuElement extends HTMLElement {
333
357
  pointer-events: none;
334
358
  }
335
359
 
336
- #root {
337
- position: absolute;
338
- width: auto;
339
- max-width: 95%;
340
- left: 50%;
341
- transform: translateX(-50%);
342
- top: min(20px, 10vh);
343
- padding: 0.3rem;
344
- display: flex;
345
- visibility: visible;
346
- flex-direction: row-reverse; /* if we overflow this should be right aligned so the logo is always visible */
347
- pointer-events: all;
348
- z-index: 1000;
349
- }
360
+ /** we put base styles in a layer to allow overrides more easily (e.g. the button.mode requested animation should override the base styles) */
361
+ @layer base {
350
362
 
351
- /** hide the menu if it's empty **/
352
- #root.has-no-options.logo-hidden {
353
- display: none;
354
- }
363
+ #root {
364
+ position: absolute;
365
+ width: auto;
366
+ max-width: 95%;
367
+ left: 50%;
368
+ transform: translateX(-50%);
369
+ top: min(20px, 10vh);
370
+ padding: 0.3rem;
371
+ display: flex;
372
+ visibility: visible;
373
+ flex-direction: row-reverse; /* if we overflow this should be right aligned so the logo is always visible */
374
+ pointer-events: all;
375
+ z-index: 1000;
376
+ }
355
377
 
356
- /** using a div here because then we can change the class for placement **/
357
- #root.bottom {
358
- top: auto;
359
- bottom: min(30px, 10vh);
360
- }
361
- #root.top {
362
- top: calc(.7rem + env(safe-area-inset-top));
363
- }
364
-
365
- .wrapper {
366
- position: relative;
367
- display: flex;
368
- flex-direction: row;
369
- justify-content: center;
370
- align-items: stretch;
371
- gap: 0px;
372
- padding: 0 0rem;
373
- }
378
+ /** hide the menu if it's empty **/
379
+ #root.has-no-options.logo-hidden {
380
+ display: none;
381
+ }
374
382
 
375
- .wrapper > *, .options > button, .options > select, ::slotted(*) {
376
- position: relative;
377
- border: none;
378
- border-radius: 0;
379
- outline: 1px solid rgba(0,0,0,0);
380
- display: flex;
381
- justify-content: center;
382
- align-items: center;
383
- max-height: 2.3rem;
384
- max-width: 100%;
383
+ /** using a div here because then we can change the class for placement **/
384
+ #root.bottom {
385
+ top: auto;
386
+ bottom: min(30px, 10vh);
387
+ }
388
+ #root.top {
389
+ top: calc(.7rem + env(safe-area-inset-top));
390
+ }
391
+
392
+ .wrapper {
393
+ position: relative;
394
+ display: flex;
395
+ flex-direction: row;
396
+ justify-content: center;
397
+ align-items: stretch;
398
+ gap: 0px;
399
+ padding: 0 0rem;
400
+ }
385
401
 
386
- /** basic font settings for all entries **/
387
- font-size: 1rem;
388
- font-family: 'Roboto Flex', sans-serif;
389
- font-optical-sizing: auto;
390
- font-weight: 500;
391
- font-weight: 200;
392
- font-variation-settings: "wdth" 100;
393
- color: rgb(20,20,20);
394
- }
395
-
396
- .options > select[multiple]:hover {
397
- max-height: 300px;
398
- }
399
-
400
- .floating-panel-style {
401
- background: rgba(255, 255, 255, .4);
402
- outline: rgb(0 0 0 / 5%) 1px solid;
403
- border: 1px solid rgba(255, 255, 255, .1);
404
- box-shadow: 0px 7px 0.5rem 0px rgb(0 0 0 / 6%), inset 0px 0px 1.3rem rgba(0,0,0,.05);
405
- border-radius: 1.5rem;
406
- /**
407
- * to make nested background filter work
408
- * https://stackoverflow.com/questions/60997948/backdrop-filter-not-working-for-nested-elements-in-chrome
409
- **/
410
- &::before {
411
- content: '';
412
- position: absolute;
413
- width: 100%;
414
- height: 100%;
415
- top: 0;
416
- left: 0;
417
- z-index: -1;
402
+ .wrapper > *, .options > button, .options > select, ::slotted(*) {
403
+ position: relative;
404
+ border: none;
405
+ border-radius: 0;
406
+ outline: 1px solid rgba(0,0,0,0);
407
+ display: flex;
408
+ justify-content: center;
409
+ align-items: center;
410
+ max-height: 2.3rem;
411
+ max-width: 100%;
412
+
413
+ /** basic font settings for all entries **/
414
+ font-size: 1rem;
415
+ font-family: 'Roboto Flex', sans-serif;
416
+ font-optical-sizing: auto;
417
+ font-weight: 400;
418
+ font-variation-settings: "wdth" 100;
419
+ color: rgb(20,20,20);
420
+ }
421
+
422
+ .options > select[multiple]:hover {
423
+ max-height: 300px;
424
+ }
425
+
426
+ .floating-panel-style {
427
+ background: rgba(255, 255, 255, .4);
428
+ outline: rgb(0 0 0 / 5%) 1px solid;
429
+ border: 1px solid rgba(255, 255, 255, .1);
430
+ box-shadow: 0px 7px 0.5rem 0px rgb(0 0 0 / 6%), inset 0px 0px 1.3rem rgba(0,0,0,.05);
418
431
  border-radius: 1.5rem;
419
- -webkit-backdrop-filter: blur(8px);
420
- backdrop-filter: blur(8px);
432
+ /**
433
+ * to make nested background filter work
434
+ * https://stackoverflow.com/questions/60997948/backdrop-filter-not-working-for-nested-elements-in-chrome
435
+ **/
436
+ &::before {
437
+ content: '';
438
+ position: absolute;
439
+ width: 100%;
440
+ height: 100%;
441
+ top: 0;
442
+ left: 0;
443
+ z-index: -1;
444
+ border-radius: 1.5rem;
445
+ -webkit-backdrop-filter: blur(8px);
446
+ backdrop-filter: blur(8px);
447
+ }
421
448
  }
422
- }
423
449
 
424
- a {
425
- color: inherit;
426
- text-decoration: none;
427
- }
450
+ a {
451
+ color: inherit;
452
+ text-decoration: none;
453
+ }
428
454
 
429
- .options {
430
- display: flex;
431
- flex-direction: row;
432
- align-items: center;
433
- }
455
+ .options {
456
+ display: flex;
457
+ flex-direction: row;
458
+ align-items: center;
459
+ }
434
460
 
435
- .options > *, ::slotted(*) {
436
- max-height: 2.25rem;
437
- padding: .4rem .5rem;
438
- }
461
+ .options > *, ::slotted(*) {
462
+ max-height: 2.25rem;
463
+ padding: .4rem .5rem;
464
+ }
439
465
 
440
- :host .options > *, ::slotted(*) {
441
- background: transparent;
442
- border: none;
443
- white-space: nowrap;
444
- transition: all 0.1s linear .02s;
445
- border-radius: 1.5rem;
446
- user-select: none;
447
- }
448
- :host .options > *:hover, ::slotted(*:hover) {
449
- cursor: pointer;
450
- color: black;
451
- background: rgba(245, 245, 245, .8);
452
- box-shadow: inset 0 0 1rem rgba(0,0,30,.2);
453
- outline: rgba(0,0,0,.1) 1px solid;
454
- }
455
- :host .options > *:active, ::slotted(*:active) {
456
- background: rgba(255, 255, 255, .8);
457
- box-shadow: inset 0px 1px 1px rgba(255,255,255,.5), inset 0 0 2rem rgba(0,0,30,.2), inset 0px 2px 4px rgba(0,0,20,.5);
458
- transition: all 0.05s linear;
459
- }
460
- :host .options > *:focus, ::slotted(*:focus) {
461
- outline: rgba(255,255,255,.5) 1px solid;
462
- }
463
- :host .options > *:focus-visible, ::slotted(*:focus-visible) {
464
- outline: rgba(0,0,0,.5) 1px solid;
465
- }
466
+ :host .options > *, ::slotted(*) {
467
+ background: transparent;
468
+ border: none;
469
+ white-space: nowrap;
470
+ transition: all 0.1s linear .02s;
471
+ border-radius: 1.5rem;
472
+ user-select: none;
473
+ }
474
+ :host .options > *:hover, ::slotted(*:hover) {
475
+ cursor: pointer;
476
+ color: black;
477
+ background: rgba(245, 245, 245, .8);
478
+ box-shadow: inset 0 0 1rem rgba(0,0,30,.2);
479
+ outline: rgba(0,0,0,.1) 1px solid;
480
+ }
481
+ :host .options > *:active, ::slotted(*:active) {
482
+ background: rgba(255, 255, 255, .8);
483
+ box-shadow: inset 0px 1px 1px rgba(255,255,255,.5), inset 0 0 2rem rgba(0,0,30,.2), inset 0px 2px 4px rgba(0,0,20,.5);
484
+ transition: all 0.05s linear;
485
+ }
486
+ :host .options > *:focus, ::slotted(*:focus) {
487
+ outline: rgba(255,255,255,.5) 1px solid;
488
+ }
489
+ :host .options > *:focus-visible, ::slotted(*:focus-visible) {
490
+ outline: rgba(0,0,0,.5) 1px solid;
491
+ }
466
492
 
467
- :host .options > *:disabled, ::slotted(*:disabled) {
468
- background: rgba(0,0,0,.05);
469
- color: rgba(60,60,60,.7);
470
- pointer-events: none;
493
+ :host .options > *:disabled, ::slotted(*:disabled) {
494
+ background: rgba(0,0,0,.05);
495
+ color: rgba(60,60,60,.7);
496
+ pointer-events: none;
497
+ }
471
498
  }
472
499
 
473
500
  button, ::slotted(button) {
@@ -475,6 +502,7 @@ export class NeedleMenuElement extends HTMLElement {
475
502
  }
476
503
 
477
504
  /** XR button animation **/
505
+
478
506
  :host button.this-mode-is-requested {
479
507
  background: repeating-linear-gradient(to right, #fff 0%, #fff 40%, #aaffff 55%, #fff 80%);
480
508
  background-size: 200% auto;
@@ -486,8 +514,12 @@ export class NeedleMenuElement extends HTMLElement {
486
514
  }
487
515
 
488
516
  @keyframes AnimationName {
489
- 0% { background-position: 0% 0 }
490
- 100% { background-position: -200% 0 }
517
+ 0% {
518
+ background-position: 0% 0
519
+ }
520
+ 100% {
521
+ background-position: -200% 0
522
+ }
491
523
  }
492
524
 
493
525
 
@@ -495,9 +527,9 @@ export class NeedleMenuElement extends HTMLElement {
495
527
 
496
528
  .logo {
497
529
  cursor: pointer;
498
- padding-left: 0.0rem;
499
- padding-bottom: .02rem;
500
- margin-right: 0.5rem;
530
+ padding-left: 0.5em;
531
+ padding-bottom: .02em;
532
+ margin-right: 0.6em;
501
533
  }
502
534
  .logo-hidden {
503
535
  .logo {
@@ -506,8 +538,8 @@ export class NeedleMenuElement extends HTMLElement {
506
538
  }
507
539
  :host .has-options .logo {
508
540
  border-left: 1px solid rgba(40,40,40,.4);
509
- margin-left: 0.3rem;
510
- margin-right: 0.5rem;
541
+ margin-left: 0.3em;
542
+ margin-right: 0.6em;
511
543
  }
512
544
 
513
545
  .logo > span {
@@ -520,6 +552,10 @@ export class NeedleMenuElement extends HTMLElement {
520
552
 
521
553
  /** Hide the menu button normally **/
522
554
  .compact-menu-button { display: none; }
555
+
556
+ /** Hide the compact only options when not in compact mode */
557
+ .options.compact-only { display: none; }
558
+
523
559
  /** And show it when we're in compact mode **/
524
560
  .compact .compact-menu-button {
525
561
  position: relative;
@@ -635,15 +671,24 @@ export class NeedleMenuElement extends HTMLElement {
635
671
  }
636
672
 
637
673
  .compact .options > *, .compact .options > ::slotted(*) {
638
- font-size: 1.2rem;
639
- padding: .6rem .5rem;
674
+ font-size: 1.2em;
675
+ padding: .6em .5em;
640
676
  width: 100%;
641
677
  }
678
+ .compact.has-options {
679
+ padding-left: 1em;
680
+ }
642
681
  .compact.has-options .logo {
643
682
  border: none;
644
683
  padding-left: 0;
645
- margin-left: 1rem;
646
- margin-bottom: .02rem;
684
+ margin-bottom: .02em;
685
+ }
686
+ .compact .options.compact-only {
687
+ display: initial;
688
+ & > * {
689
+ min-height: 1em;
690
+ padding: .4em .4em;
691
+ }
647
692
  }
648
693
  .compact .options {
649
694
  /** e.g. if we have a very wide menu item like a select with long option names we don't want to overflow **/
@@ -671,28 +716,15 @@ export class NeedleMenuElement extends HTMLElement {
671
716
  display: none !important;
672
717
  }
673
718
  }
674
-
675
- /* dark mode */
676
- /*
677
- @media (prefers-color-scheme: dark) {
678
- :host {
679
- background: rgba(0,0,0, .6);
680
- }
681
- :host button {
682
- color: rgba(200,200,200);
683
- }
684
- :host button:hover {
685
- background: rgba(100,100,100, .8);
686
- }
687
- }
688
- */
689
719
 
690
720
  </style>
691
721
 
692
722
  <div id="root" class="logo-hidden floating-panel-style bottom">
693
723
  <div class="wrapper">
724
+ <div class="options compact-only" part="options">
725
+ </div>
694
726
  <div class="foldout">
695
- <div class="options" part="options">
727
+ <div class="options main-container" part="options">
696
728
  <slot></slot>
697
729
  </div>
698
730
  <div class="options" part="options">
@@ -723,7 +755,8 @@ export class NeedleMenuElement extends HTMLElement {
723
755
  this.root = shadow.querySelector("#root") as HTMLDivElement;
724
756
 
725
757
  this.wrapper = this.root?.querySelector(".wrapper") as HTMLDivElement;
726
- this.options = this.root?.querySelector(".options") as HTMLDivElement;
758
+ this.options = this.root?.querySelector(".options.main-container") as HTMLDivElement;
759
+ this.optionsCompactMode = this.root?.querySelector(".options.compact-only") as HTMLDivElement;
727
760
  this.logoContainer = this.root?.querySelector(".logo") as HTMLDivElement;
728
761
  this.compactMenuButton = this.root?.querySelector(".compact-menu-button") as HTMLButtonElement;
729
762
  this.compactMenuButton.append(getIconElement("more_vert"));
@@ -838,7 +871,7 @@ export class NeedleMenuElement extends HTMLElement {
838
871
  connectedCallback() {
839
872
  window.addEventListener("resize", this.handleSizeChange);
840
873
  this.handleMenuVisible();
841
- this._sizeChangeInterval = setInterval(() => this.handleSizeChange(undefined, true), 5000);
874
+ this._sizeChangeInterval = setInterval(() => this.handleSizeChange(undefined, false), 5000);
842
875
  // the dom element is set after the constructor runs
843
876
  setTimeout(() => {
844
877
  this._domElement?.addEventListener("resize", this.handleSizeChange);
@@ -932,6 +965,8 @@ export class NeedleMenuElement extends HTMLElement {
932
965
  private readonly wrapper: HTMLDivElement;
933
966
  /** @private contains the buttons and dynamic elements */
934
967
  private readonly options: HTMLDivElement;
968
+ /** @private contains options visible when in compact mode */
969
+ private readonly optionsCompactMode: HTMLDivElement;
935
970
  /** @private contains the needle-logo html element */
936
971
  private readonly logoContainer: HTMLDivElement;
937
972
  /** @private compact menu button element */
@@ -1039,6 +1074,8 @@ export class NeedleMenuElement extends HTMLElement {
1039
1074
  }
1040
1075
 
1041
1076
  private _isHandlingChange = false;
1077
+ /** During modification of options container (e.g. when moving items into the extra buttons container) the mutation observer should not trigger an update event immediately. This is a workaround for the total size required for all elements not being calculated reliably. */
1078
+ private _pauseMutationObserverOptionsContainer = false;
1042
1079
 
1043
1080
  /** Called when any change in the web component is detected (including in children and child attributes) */
1044
1081
  private onChangeDetected(_mut: MutationRecord[]) {
@@ -1049,7 +1086,8 @@ export class NeedleMenuElement extends HTMLElement {
1049
1086
  this.handleMenuVisible();
1050
1087
  for (const mut of _mut) {
1051
1088
  if (mut.target == this.options) {
1052
- this.onOptionsChildrenChanged(mut);
1089
+ if (!this._pauseMutationObserverOptionsContainer)
1090
+ this.onOptionsChildrenChanged(mut)
1053
1091
  }
1054
1092
  }
1055
1093
  }
@@ -1134,29 +1172,34 @@ export class NeedleMenuElement extends HTMLElement {
1134
1172
 
1135
1173
 
1136
1174
  private _lastAvailableWidthChange = 0;
1137
- private _timeoutHandle: number = 0;
1175
+ private _timeoutHandleSize: number = 0;
1176
+ private _timeoutHandleCompactItems: number = 0;
1138
1177
 
1139
1178
  private handleSizeChange = (_evt?: Event, forceOrEvent?: boolean) => {
1140
1179
  if (!this._domElement) return;
1180
+ // if (this._isApplyingSizeUpdate) return;
1141
1181
 
1142
1182
  const width = this._domElement.clientWidth;
1143
1183
  if (width < 100) {
1144
- clearTimeout(this._timeoutHandle!);
1184
+ clearTimeout(this._timeoutHandleSize!);
1145
1185
  this.root.classList.add("compact");
1146
1186
  this.foldout.classList.add("floating-panel-style");
1147
1187
  return;
1148
1188
  }
1149
1189
 
1150
- const padding = 20 * 2;
1190
+ const padding = 10 * 2;
1151
1191
  const availableWidth = width - padding;
1152
1192
 
1153
1193
  // if the available width has not changed significantly then we can skip the rest
1154
1194
  if (!forceOrEvent && Math.abs(availableWidth - this._lastAvailableWidthChange) < 1) return;
1155
1195
  this._lastAvailableWidthChange = availableWidth;
1156
1196
 
1157
- clearTimeout(this._timeoutHandle!);
1197
+ clearTimeout(this._timeoutHandleSize!);
1198
+
1199
+ this._timeoutHandleSize = setTimeout(() => {
1200
+
1201
+ // console.warn("APPLY", this.root.classList.contains("compact") ? "COMPACT" : "FULL", "MODE (available width: " + availableWidth.toFixed(0) + "px)", getCurrentWidth());
1158
1202
 
1159
- this._timeoutHandle = setTimeout(() => {
1160
1203
  const spaceLeft = getSpaceLeft();
1161
1204
  if (spaceLeft < 0) {
1162
1205
  this.root.classList.add("compact")
@@ -1171,10 +1214,19 @@ export class NeedleMenuElement extends HTMLElement {
1171
1214
  this.foldout.classList.add("floating-panel-style");
1172
1215
  }
1173
1216
  }
1174
- }, 5) as unknown as number;
1217
+ this._pauseMutationObserverOptionsContainer = true;
1218
+ this.updateCompactFoldoutItem();
1219
+ window.requestAnimationFrame(() => this._pauseMutationObserverOptionsContainer = false);
1220
+
1221
+ }, 150) as unknown as number;
1175
1222
 
1176
1223
  const getCurrentWidth = () => {
1177
- return this.options.clientWidth + this.logoContainer.clientWidth;
1224
+ let totalWidthRequired = 0;
1225
+ totalWidthRequired += this.options.getBoundingClientRect().width;
1226
+ totalWidthRequired += this.optionsCompactMode.getBoundingClientRect().width;
1227
+ totalWidthRequired += 10 * this.options.childElementCount; // padding
1228
+ totalWidthRequired += this.logoContainer.style.display != "none" ? this.logoContainer.getBoundingClientRect().width : 0;;
1229
+ return totalWidthRequired;
1178
1230
  }
1179
1231
 
1180
1232
  let lastSpaceLeft = -1;
@@ -1188,6 +1240,59 @@ export class NeedleMenuElement extends HTMLElement {
1188
1240
  }
1189
1241
  }
1190
1242
 
1243
+ private updateCompactFoldoutItem() {
1244
+
1245
+ if (this.root.classList.contains("compact")) {
1246
+
1247
+ // Find items in the folding list with the highest priority
1248
+ // The one with the highest priority will be added to the visible container
1249
+ let priorityItem: HTMLElement | null = null;
1250
+ let priorityValue: number = -10000000;
1251
+ const testItem = (element: ChildNode | null) => {
1252
+ if (element instanceof HTMLElement) {
1253
+ const priority = NeedleMenu.getElementPriority(element);
1254
+ if (priority !== undefined && priority >= priorityValue) {
1255
+ // check if the element is hidden
1256
+ // @TODO: use computed styles
1257
+ const style = window.getComputedStyle(element);
1258
+ if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") {
1259
+ return;
1260
+ }
1261
+ priorityItem = element;
1262
+ priorityValue = priority;
1263
+ }
1264
+ }
1265
+ }
1266
+ for (let i = 0; i < this.options.children.length; i++) {
1267
+ testItem(this.options.children.item(i));
1268
+ }
1269
+ for (let i = 0; i < this.optionsCompactMode.children.length; i++) {
1270
+ testItem(this.optionsCompactMode.children.item(i));
1271
+ }
1272
+
1273
+ if (priorityItem && !this.optionsCompactMode.contains(priorityItem)) {
1274
+ this.optionsCompactMode.childNodes.forEach(element => {
1275
+ this.options.appendChild(element);
1276
+ });
1277
+ const item = priorityItem;
1278
+ this.optionsCompactMode.appendChild(item);
1279
+ // console.warn("In compact mode, moved item with priority " + priorityValue + " to compact foldout:", item);
1280
+ }
1281
+ else if (!priorityItem) {
1282
+ // console.warn("In compact mode but no item has priority, showing all items in foldout");
1283
+ this.optionsCompactMode.childNodes.forEach(element => {
1284
+ this.options.appendChild(element);
1285
+ });
1286
+ }
1287
+ }
1288
+ else {
1289
+ // console.warn("Not in compact mode but trying to update compact foldout item");
1290
+ this.optionsCompactMode.childNodes.forEach(element => {
1291
+ this.options.appendChild(element);
1292
+ });
1293
+ }
1294
+ }
1295
+ // private _foldoutItemVisibleInterval = 0;
1191
1296
 
1192
1297
 
1193
1298
  private ___insertDebugOptions() {
@@ -121,6 +121,7 @@ export class AROverlayHandler {
121
121
 
122
122
  const quitARSlot = document.createElement("slot");
123
123
  quitARSlot.style.display = "contents";
124
+ quitARSlot.style.padding = "10px";
124
125
  quitARSlot.setAttribute("name", "quit-ar");
125
126
  this.appendElement(quitARSlot, element);
126
127
  this._createdAROnlyElements.push(quitARSlot);