@threadlabs/looma 0.1.15 → 0.1.17

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 (123) hide show
  1. package/dist/{chunk-SVINNT3E.js → chunk-OY4D3W4U.js} +29 -29
  2. package/dist/{chunk-G53JEAQK.js → chunk-SZJQ3NPX.js} +6 -3
  3. package/dist/chunk-YHXJC5M5.js +341 -0
  4. package/dist/cjs/loader.cjs.js +1 -1
  5. package/dist/cjs/looma.cjs.js +1 -1
  6. package/dist/cjs/{manager-BOpVXb92.js → manager-C9j3HZay.js} +5 -2
  7. package/dist/cjs/positioning-i0WrCAdF.js +384 -0
  8. package/dist/cjs/ui-affordance-scope.cjs.entry.js +3 -113
  9. package/dist/cjs/ui-context-menu.cjs.entry.js +40 -5
  10. package/dist/cjs/ui-dialog.cjs.entry.js +1 -1
  11. package/dist/cjs/ui-menu.cjs.entry.js +83 -2
  12. package/dist/cjs/ui-popover.cjs.entry.js +43 -3
  13. package/dist/cjs/ui-toast-region.cjs.entry.js +17 -2
  14. package/dist/cjs/ui-tooltip.cjs.entry.js +95 -27
  15. package/dist/cjs/ui-tree-item.cjs.entry.js +1 -1
  16. package/dist/collection/components/ui-context-menu/ui-context-menu.css +17 -4
  17. package/dist/collection/components/ui-context-menu/ui-context-menu.js +38 -3
  18. package/dist/collection/components/ui-menu/ui-menu.css +39 -0
  19. package/dist/collection/components/ui-menu/ui-menu.js +128 -1
  20. package/dist/collection/components/ui-popover/ui-popover.css +31 -0
  21. package/dist/collection/components/ui-popover/ui-popover.js +87 -1
  22. package/dist/collection/components/ui-toast-region/ui-toast-region.css +6 -0
  23. package/dist/collection/components/ui-toast-region/ui-toast-region.js +16 -1
  24. package/dist/collection/components/ui-tooltip/ui-tooltip.css +20 -0
  25. package/dist/collection/components/ui-tooltip/ui-tooltip.js +160 -25
  26. package/dist/collection/components/ui-tree-item/ui-tree-item.css +10 -6
  27. package/dist/collection/input-modality.js +24 -0
  28. package/dist/collection/overlay/manager.js +5 -2
  29. package/dist/collection/overlay/positioning.js +246 -0
  30. package/dist/components/p-CEYIKqYc.js +1 -0
  31. package/dist/components/p-DOulVJT7.js +1 -0
  32. package/dist/components/p-PM9a74r7.js +1 -0
  33. package/dist/components/ui-affordance-scope.js +1 -1
  34. package/dist/components/ui-context-menu.js +1 -1
  35. package/dist/components/ui-dialog.js +1 -1
  36. package/dist/components/ui-menu.js +1 -1
  37. package/dist/components/ui-popover.js +1 -1
  38. package/dist/components/ui-toast-region.js +1 -1
  39. package/dist/components/ui-tooltip.js +1 -1
  40. package/dist/components/ui-tree-item.js +1 -1
  41. package/dist/esm/loader.js +1 -1
  42. package/dist/esm/looma.js +1 -1
  43. package/dist/esm/{manager-CR7f2MQC.js → manager-PM9a74r7.js} +5 -2
  44. package/dist/esm/positioning-Bu0dmLBE.js +379 -0
  45. package/dist/esm/ui-affordance-scope.entry.js +1 -111
  46. package/dist/esm/ui-context-menu.entry.js +40 -5
  47. package/dist/esm/ui-dialog.entry.js +1 -1
  48. package/dist/esm/ui-menu.entry.js +83 -2
  49. package/dist/esm/ui-popover.entry.js +43 -3
  50. package/dist/esm/ui-toast-region.entry.js +17 -2
  51. package/dist/esm/ui-tooltip.entry.js +95 -27
  52. package/dist/esm/ui-tree-item.entry.js +1 -1
  53. package/dist/index.cjs +776 -59
  54. package/dist/index.d.cts +44 -1
  55. package/dist/index.d.ts +44 -1
  56. package/dist/index.js +235 -4
  57. package/dist/looma/looma.esm.js +1 -1
  58. package/dist/looma/{p-c6721aba.entry.js → p-11083743.entry.js} +1 -1
  59. package/dist/looma/p-1674cda4.entry.js +1 -0
  60. package/dist/looma/p-2e6f791e.entry.js +1 -0
  61. package/dist/looma/p-54c41576.entry.js +1 -0
  62. package/dist/looma/p-9194a99d.entry.js +1 -0
  63. package/dist/looma/p-Bu0dmLBE.js +1 -0
  64. package/dist/looma/p-PM9a74r7.js +1 -0
  65. package/dist/looma/p-ba01f45d.entry.js +1 -0
  66. package/dist/looma/p-caa8fc7b.entry.js +1 -0
  67. package/dist/looma/{p-ef9ca2ed.entry.js → p-d01960c2.entry.js} +2 -2
  68. package/dist/types/components/ui-context-menu/ui-context-menu.d.ts +3 -0
  69. package/dist/types/components/ui-menu/ui-menu.d.ts +13 -0
  70. package/dist/types/components/ui-popover/ui-popover.d.ts +9 -0
  71. package/dist/types/components/ui-toast-region/ui-toast-region.d.ts +2 -0
  72. package/dist/types/components/ui-tooltip/ui-tooltip.d.ts +16 -1
  73. package/dist/types/components.d.ts +71 -0
  74. package/dist/types/input-modality.d.ts +6 -0
  75. package/dist/types/overlay/manager.d.ts +3 -0
  76. package/dist/types/overlay/positioning.d.ts +33 -0
  77. package/dist/ui-affordance-scope.entry-X7DTKJGL.js +54 -0
  78. package/dist/{ui-avatar-group.entry-DQDP325O.js → ui-avatar-group.entry-MC34FKAN.js} +1 -1
  79. package/dist/{ui-avatar.entry-WYIDMOFE.js → ui-avatar.entry-OKRQWTQV.js} +1 -1
  80. package/dist/{ui-badge.entry-VGHXPEDT.js → ui-badge.entry-RI7JB7KV.js} +1 -1
  81. package/dist/{ui-button.entry-LKPLOJ22.js → ui-button.entry-PFO5TTPN.js} +1 -1
  82. package/dist/{ui-checkbox.entry-J74HEA22.js → ui-checkbox.entry-UHFWVISP.js} +1 -1
  83. package/dist/{ui-context-menu.entry-MQM2FCLI.js → ui-context-menu.entry-AIXB6U5D.js} +40 -6
  84. package/dist/{ui-dialog.entry-DQMMDO4Z.js → ui-dialog.entry-LPREQH2T.js} +2 -2
  85. package/dist/{ui-disclosure.entry-ONF7UZDC.js → ui-disclosure.entry-7XCUIYVZ.js} +1 -1
  86. package/dist/{ui-floating-action-button.entry-FKTGVC5T.js → ui-floating-action-button.entry-5PNIMUAW.js} +1 -1
  87. package/dist/{ui-form-field.entry-M3ISWQNU.js → ui-form-field.entry-77YW46SM.js} +1 -1
  88. package/dist/{ui-icon-button.entry-AWFIQUHM.js → ui-icon-button.entry-4AD6ALPQ.js} +1 -1
  89. package/dist/{ui-input.entry-JZ6RETMT.js → ui-input.entry-E7E6QDTV.js} +1 -1
  90. package/dist/{ui-menu-item.entry-5MFR5LFL.js → ui-menu-item.entry-H3HFLUB6.js} +1 -1
  91. package/dist/ui-menu.entry-UAKZU4HG.js +184 -0
  92. package/dist/{ui-popover.entry-ELCMKO6Y.js → ui-popover.entry-GVQKX26B.js} +46 -4
  93. package/dist/{ui-radio-group.entry-RJHUPPEM.js → ui-radio-group.entry-MNSR6UEE.js} +1 -1
  94. package/dist/{ui-radio.entry-3VRDPOY4.js → ui-radio.entry-6D4WTI5E.js} +1 -1
  95. package/dist/{ui-search-result-row.entry-J34YPAV7.js → ui-search-result-row.entry-J2TILUH2.js} +1 -1
  96. package/dist/{ui-search-shell.entry-IJ4UUP27.js → ui-search-shell.entry-FSWHEZQF.js} +1 -1
  97. package/dist/{ui-select.entry-C6YD3HQ5.js → ui-select.entry-JRCYYNNW.js} +1 -1
  98. package/dist/{ui-switch.entry-UZDF5QTI.js → ui-switch.entry-YZWMNRP2.js} +1 -1
  99. package/dist/{ui-tabs.entry-YRAKWOMR.js → ui-tabs.entry-F2Y344JY.js} +1 -1
  100. package/dist/{ui-textarea.entry-JLDKFT3K.js → ui-textarea.entry-3EZCUUAK.js} +1 -1
  101. package/dist/{ui-toast-region.entry-GC7C7KXF.js → ui-toast-region.entry-BTV4AW3I.js} +20 -3
  102. package/dist/ui-tooltip.entry-U644CF5U.js +214 -0
  103. package/dist/{ui-top-bar.entry-5W5UA5KV.js → ui-top-bar.entry-FGGOREQR.js} +1 -1
  104. package/dist/{ui-tree-item.entry-3LM7IXIK.js → ui-tree-item.entry-XVMHEONY.js} +2 -2
  105. package/dist/{ui-tree.entry-X4FU42A6.js → ui-tree.entry-4R7DYGAM.js} +1 -1
  106. package/editor.css +3 -4
  107. package/package.json +1 -1
  108. package/tokens.css +14 -2
  109. package/vue/{chunk-IITL6UBR.js → chunk-5KWCMTXB.js} +3 -0
  110. package/vue/editor/index.js +63 -52
  111. package/vue/index.js +1 -1
  112. package/dist/components/p-CR7f2MQC.js +0 -1
  113. package/dist/components/p-QiwSQmKJ.js +0 -1
  114. package/dist/looma/p-2ae73d5b.entry.js +0 -1
  115. package/dist/looma/p-3ac2e0d4.entry.js +0 -1
  116. package/dist/looma/p-65c25c94.entry.js +0 -1
  117. package/dist/looma/p-8c09c724.entry.js +0 -1
  118. package/dist/looma/p-95cd639d.entry.js +0 -1
  119. package/dist/looma/p-96f73d07.entry.js +0 -1
  120. package/dist/looma/p-CR7f2MQC.js +0 -1
  121. package/dist/ui-affordance-scope.entry-PFP2TDKO.js +0 -145
  122. package/dist/ui-menu.entry-IOEIDATQ.js +0 -98
  123. package/dist/ui-tooltip.entry-F26XDTYX.js +0 -145
@@ -2,8 +2,10 @@
2
2
 
3
3
  var index = require('./index-D07f6BOw.js');
4
4
  var events = require('./events-TcQIzpNv.js');
5
+ var manager = require('./manager-C9j3HZay.js');
6
+ var positioning = require('./positioning-i0WrCAdF.js');
5
7
 
6
- const uiMenuCss = () => `:host{--ui-menu-viewport-gap:var(--ui-space-2, 0.5rem);display:flex;flex-direction:column;inline-size:max-content;min-inline-size:min(12rem, calc(100vw - (var(--ui-menu-viewport-gap) * 2)));max-inline-size:calc(100vw - (var(--ui-menu-viewport-gap) * 2));padding:var(--ui-space-1);background:var(--ui-surface-elevated);border:1px solid var(--ui-border-default);border-radius:var(--ui-radius-3);box-shadow:var(--ui-shadow-lg)}::slotted(ui-menu-item){display:block;width:100%;padding:var(--ui-space-2) var(--ui-space-3);text-align:left;background:none;border:none;border-radius:var(--ui-radius-2);color:var(--ui-text-primary);font-size:var(--ui-font-size-md);font-weight:var(--ui-font-medium, 500);cursor:pointer;transition:background-color var(--ui-motion-fast) var(--ui-motion-ease), color var(--ui-motion-fast) var(--ui-motion-ease)}::slotted(ui-menu-item:hover:not([aria-disabled='true']):not([disabled])){background:var(--ui-surface-subtle)}::slotted(ui-menu-item[aria-disabled='true']),::slotted(ui-menu-item[disabled]){opacity:0.6;cursor:not-allowed}`;
8
+ const uiMenuCss = () => `:host{--ui-menu-viewport-gap:var(--ui-space-2, 0.5rem);display:flex;flex-direction:column;inline-size:max-content;min-inline-size:min(12rem, calc(100vw - (var(--ui-menu-viewport-gap) * 2)));max-inline-size:calc(100vw - (var(--ui-menu-viewport-gap) * 2));padding:var(--ui-space-1);background:var(--ui-surface-elevated);border:1px solid var(--ui-border-default);border-radius:var(--ui-radius-3);box-shadow:var(--ui-shadow-lg)}:host(:not([data-open])){display:none}:host([popover]){position:fixed;inset:auto;margin:0;overflow:auto;max-block-size:calc(100dvh - (var(--ui-menu-viewport-gap) * 2));opacity:0;transform:translateY(-4px) scale(0.99);transition:opacity var(--ui-motion-fast) var(--ui-motion-ease), transform var(--ui-motion-fast) var(--ui-motion-ease), display var(--ui-motion-fast) allow-discrete, overlay var(--ui-motion-fast) allow-discrete}:host([popover]:popover-open){opacity:1;transform:translateY(0) scale(1)}@starting-style{:host([popover]:popover-open){opacity:0;transform:translateY(-4px) scale(0.99)}}:host([hidden]){display:none !important}@media (prefers-reduced-motion: reduce){:host([popover]){transition:none}}::slotted(ui-menu-item){display:block;width:100%;padding:var(--ui-space-2) var(--ui-space-3);text-align:left;background:none;border:none;border-radius:var(--ui-radius-2);color:var(--ui-text-primary);font-size:var(--ui-font-size-md);font-weight:var(--ui-font-medium, 500);cursor:pointer;transition:background-color var(--ui-motion-fast) var(--ui-motion-ease), color var(--ui-motion-fast) var(--ui-motion-ease)}::slotted(ui-menu-item:hover:not([aria-disabled='true']):not([disabled])){background:var(--ui-surface-subtle)}::slotted(ui-menu-item[aria-disabled='true']),::slotted(ui-menu-item[disabled]){opacity:0.6;cursor:not-allowed}`;
7
9
 
8
10
  const UIMenu = class {
9
11
  constructor(hostRef) {
@@ -12,19 +14,84 @@ const UIMenu = class {
12
14
  get host() { return index.getElement(this); }
13
15
  open = false;
14
16
  defaultOpen = false;
17
+ /** Id of the control that anchors this menu in the top layer. */
18
+ for;
19
+ /** Preferred placement; Looma flips and shifts when space is constrained. */
20
+ placement = 'bottom-start';
15
21
  internalOpen = false;
22
+ overlayId = `ui-menu-${Math.random().toString(36).slice(2, 11)}`;
23
+ anchor = null;
24
+ surface = null;
16
25
  syncFromProp() {
17
26
  this.internalOpen = this.open;
18
27
  }
28
+ resetSurface() {
29
+ this.setupSurface();
30
+ this.syncSurface();
31
+ }
32
+ syncSurface() {
33
+ this.syncAnchorState();
34
+ if (!this.surface)
35
+ return;
36
+ if (this.internalOpen) {
37
+ this.surface.show();
38
+ manager.openOverlay({
39
+ id: this.overlayId,
40
+ modal: false,
41
+ element: this.host,
42
+ relatedElements: this.anchor ? [this.anchor] : undefined,
43
+ dismissible: true,
44
+ requestClose: (reason, trigger) => this.closeMenu(reason, trigger),
45
+ });
46
+ }
47
+ else {
48
+ this.surface.hide();
49
+ manager.closeOverlay(this.overlayId);
50
+ }
51
+ }
19
52
  componentDidLoad() {
20
53
  this.syncFromProp();
21
54
  this.internalOpen = this.internalOpen || this.defaultOpen;
22
55
  this.host.addEventListener('click', this.onItemClick);
23
56
  this.host.addEventListener('keydown', this.onKeydown);
57
+ this.setupSurface();
58
+ this.syncSurface();
24
59
  }
25
60
  disconnectedCallback() {
26
61
  this.host.removeEventListener('click', this.onItemClick);
27
62
  this.host.removeEventListener('keydown', this.onKeydown);
63
+ this.surface?.destroy();
64
+ this.surface = null;
65
+ manager.closeOverlay(this.overlayId);
66
+ }
67
+ setupSurface() {
68
+ this.surface?.destroy();
69
+ this.surface = null;
70
+ this.anchor = this.for ? this.host.ownerDocument.getElementById(this.for) : null;
71
+ if (!this.anchor)
72
+ return;
73
+ this.surface = positioning.createAnchoredSurface(this.host, {
74
+ anchor: this.anchor,
75
+ placement: this.placement,
76
+ });
77
+ }
78
+ syncAnchorState() {
79
+ if (!this.anchor)
80
+ return;
81
+ this.anchor.setAttribute('aria-haspopup', 'menu');
82
+ this.anchor.setAttribute('aria-expanded', this.internalOpen ? 'true' : 'false');
83
+ }
84
+ closeMenu(reason, trigger) {
85
+ if (!this.internalOpen)
86
+ return;
87
+ this.internalOpen = false;
88
+ events.dispatchDetail(this.host, 'close', {
89
+ open: false,
90
+ reason: reason,
91
+ trigger: trigger,
92
+ });
93
+ if (reason === 'escape')
94
+ this.anchor?.focus();
28
95
  }
29
96
  getItems() {
30
97
  return Array.from(this.host.querySelectorAll('ui-menu-item'));
@@ -51,6 +118,11 @@ const UIMenu = class {
51
118
  this.internalOpen = false;
52
119
  };
53
120
  onKeydown = (e) => {
121
+ if (e.key === 'Escape') {
122
+ e.preventDefault();
123
+ manager.requestTopOverlayClose('escape', 'keyboard');
124
+ return;
125
+ }
54
126
  const items = this.getItems().filter((i) => !this.isItemDisabled(i));
55
127
  if (items.length === 0)
56
128
  return;
@@ -83,11 +155,20 @@ const UIMenu = class {
83
155
  }
84
156
  };
85
157
  render() {
86
- return (index.h(index.Host, { key: '5e463cbbce272c6b6132a839b818deef3e39a532', role: "menu", "aria-orientation": "vertical", "data-open": this.internalOpen ? '' : undefined }, index.h("slot", { key: 'feea6a21d41a7b27a6270814bcf0b3f3ded394a1' })));
158
+ return (index.h(index.Host, { key: '06ad0750df843cb874bd4f257d8567157c855493', role: "menu", "aria-orientation": "vertical", "data-open": this.internalOpen ? '' : undefined }, index.h("slot", { key: '50118ac749a2747ca06f4d483499c4cd20125a89' })));
87
159
  }
88
160
  static get watchers() { return {
89
161
  "open": [{
90
162
  "syncFromProp": 0
163
+ }],
164
+ "for": [{
165
+ "resetSurface": 0
166
+ }],
167
+ "placement": [{
168
+ "resetSurface": 0
169
+ }],
170
+ "internalOpen": [{
171
+ "syncSurface": 0
91
172
  }]
92
173
  }; }
93
174
  };
@@ -1,10 +1,11 @@
1
1
  'use strict';
2
2
 
3
3
  var index = require('./index-D07f6BOw.js');
4
- var manager = require('./manager-BOpVXb92.js');
4
+ var manager = require('./manager-C9j3HZay.js');
5
5
  var events = require('./events-TcQIzpNv.js');
6
+ var positioning = require('./positioning-i0WrCAdF.js');
6
7
 
7
- const uiPopoverCss = () => `:host{--ui-popover-viewport-gap:var(--ui-space-2, 0.5rem);padding:var(--ui-space-3);background:var(--ui-surface-elevated);border:1px solid var(--ui-border-default);border-radius:var(--ui-radius-3);box-shadow:var(--ui-shadow-lg);max-inline-size:min( 20rem, calc(100vw - (var(--ui-popover-viewport-gap) * 2)) );max-block-size:calc(100dvh - (var(--ui-popover-viewport-gap) * 2));overflow:auto}`;
8
+ const uiPopoverCss = () => `:host{--ui-popover-viewport-gap:var(--ui-space-2, 0.5rem);padding:var(--ui-space-3);background:var(--ui-surface-elevated);border:1px solid var(--ui-border-default);border-radius:var(--ui-radius-3);box-shadow:var(--ui-shadow-lg);max-inline-size:min( 20rem, calc(100vw - (var(--ui-popover-viewport-gap) * 2)) );max-block-size:calc(100dvh - (var(--ui-popover-viewport-gap) * 2));overflow:auto}:host(:not([data-open])){display:none}:host([popover]){position:fixed;inset:auto;margin:0;opacity:0;transform:translateY(-4px) scale(0.99);transition:opacity var(--ui-motion-fast) var(--ui-motion-ease), transform var(--ui-motion-fast) var(--ui-motion-ease), display var(--ui-motion-fast) allow-discrete, overlay var(--ui-motion-fast) allow-discrete}:host([popover]:popover-open){opacity:1;transform:translateY(0) scale(1)}@starting-style{:host([popover]:popover-open){opacity:0;transform:translateY(-4px) scale(0.99)}}:host([hidden]){display:none !important}@media (prefers-reduced-motion: reduce){:host([popover]){transition:none}}`;
8
9
 
9
10
  const UIPopover = class {
10
11
  constructor(hostRef) {
@@ -13,8 +14,13 @@ const UIPopover = class {
13
14
  get host() { return index.getElement(this); }
14
15
  open = false;
15
16
  defaultOpen = false;
17
+ /** Optional id of the element this popover follows in the top layer. */
18
+ for;
19
+ placement = 'bottom-start';
16
20
  internalOpen = false;
17
21
  overlayId = `ui-popover-${Math.random().toString(36).slice(2, 11)}`;
22
+ anchor = null;
23
+ surface = null;
18
24
  syncFromProp() {
19
25
  this.internalOpen = this.open;
20
26
  }
@@ -35,12 +41,37 @@ const UIPopover = class {
35
41
  });
36
42
  }
37
43
  }
44
+ resetSurface() {
45
+ this.setupSurface();
46
+ this.syncSurface();
47
+ }
48
+ setupSurface() {
49
+ this.surface?.destroy();
50
+ this.surface = null;
51
+ this.anchor = this.for ? this.host.ownerDocument.getElementById(this.for) : null;
52
+ if (!this.anchor)
53
+ return;
54
+ this.surface = positioning.createAnchoredSurface(this.host, {
55
+ anchor: this.anchor,
56
+ placement: this.placement,
57
+ });
58
+ }
59
+ syncSurface() {
60
+ if (!this.surface)
61
+ return;
62
+ if (this.internalOpen)
63
+ this.surface.show();
64
+ else
65
+ this.surface.hide();
66
+ }
38
67
  syncOverlay() {
68
+ this.syncSurface();
39
69
  if (this.internalOpen) {
40
70
  manager.openOverlay({
41
71
  id: this.overlayId,
42
72
  modal: false,
43
73
  element: this.host,
74
+ relatedElements: this.anchor ? [this.anchor] : undefined,
44
75
  dismissible: true,
45
76
  requestClose: (reason, trigger) => this.handleRequestClose(reason, trigger),
46
77
  });
@@ -53,10 +84,13 @@ const UIPopover = class {
53
84
  this.syncFromProp();
54
85
  this.internalOpen = this.internalOpen || this.defaultOpen;
55
86
  this.host.addEventListener('keydown', this.onKeydown);
87
+ this.setupSurface();
56
88
  this.syncOverlay();
57
89
  }
58
90
  disconnectedCallback() {
59
91
  this.host.removeEventListener('keydown', this.onKeydown);
92
+ this.surface?.destroy();
93
+ this.surface = null;
60
94
  manager.closeOverlay(this.overlayId);
61
95
  }
62
96
  componentDidUpdate() {
@@ -76,7 +110,7 @@ const UIPopover = class {
76
110
  }
77
111
  };
78
112
  render() {
79
- return (index.h(index.Host, { key: '67e7f52220db9d997fb11b686d6abcbdf9557a8d', "data-open": this.internalOpen ? '' : undefined, onKeyDown: this.onKeydown }, index.h("slot", { key: 'd7c1fa2ab75994fe0a7dd63f8289754c7754c30f' })));
113
+ return (index.h(index.Host, { key: '8e45be54147ff8f76a0d5e7fd875c3f4cf5ac40a', "data-open": this.internalOpen ? '' : undefined, onKeyDown: this.onKeydown }, index.h("slot", { key: '3141729cd32c8dcdaec1fbd763eb034242563c8f' })));
80
114
  }
81
115
  static get watchers() { return {
82
116
  "open": [{
@@ -84,6 +118,12 @@ const UIPopover = class {
84
118
  }],
85
119
  "internalOpen": [{
86
120
  "onOpenChange": 0
121
+ }],
122
+ "for": [{
123
+ "resetSurface": 0
124
+ }],
125
+ "placement": [{
126
+ "resetSurface": 0
87
127
  }]
88
128
  }; }
89
129
  };
@@ -2,8 +2,9 @@
2
2
 
3
3
  var index = require('./index-D07f6BOw.js');
4
4
  var events = require('./events-TcQIzpNv.js');
5
+ var positioning = require('./positioning-i0WrCAdF.js');
5
6
 
6
- const uiToastRegionCss = () => `:host{position:fixed;top:calc(var(--ui-space-4) + env(safe-area-inset-top));left:50%;transform:translateX(-50%);z-index:9999;display:grid;gap:var(--ui-space-2);inline-size:min(24rem, calc(100vw - (var(--ui-space-4) * 2)));max-inline-size:100%;pointer-events:none}:host>*{pointer-events:auto}::slotted([data-ui-toast]){display:grid;grid-template-columns:1fr auto;gap:var(--ui-space-2);align-items:start;border:1px solid var(--ui-border-default);border-radius:var(--ui-radius-3);padding:var(--ui-space-3) var(--ui-space-4);background:var(--ui-surface-elevated);color:var(--ui-text-primary);box-shadow:var(--ui-shadow-lg);animation:ui-toast-enter 0.25s var(--ui-motion-ease) forwards}@keyframes ui-toast-enter{from{opacity:0;transform:translateY(-0.5rem)}to{opacity:1;transform:translateY(0)}}::slotted([data-ui-toast][aria-hidden='true']){display:none}::slotted([data-ui-toast-dismiss]){border:0;background:transparent;color:var(--ui-text-secondary);cursor:pointer;font:inherit}`;
7
+ const uiToastRegionCss = () => `:host{position:fixed;top:calc(var(--ui-space-4) + env(safe-area-inset-top));left:50%;transform:translateX(-50%);z-index:9999;display:grid;gap:var(--ui-space-2);inline-size:min(24rem, calc(100vw - (var(--ui-space-4) * 2)));max-inline-size:100%;pointer-events:none}:host([popover]){right:auto;bottom:auto;margin:0}:host>*{pointer-events:auto}::slotted([data-ui-toast]){display:grid;grid-template-columns:1fr auto;gap:var(--ui-space-2);align-items:start;border:1px solid var(--ui-border-default);border-radius:var(--ui-radius-3);padding:var(--ui-space-3) var(--ui-space-4);background:var(--ui-surface-elevated);color:var(--ui-text-primary);box-shadow:var(--ui-shadow-lg);animation:ui-toast-enter 0.25s var(--ui-motion-ease) forwards}@keyframes ui-toast-enter{from{opacity:0;transform:translateY(-0.5rem)}to{opacity:1;transform:translateY(0)}}::slotted([data-ui-toast][aria-hidden='true']){display:none}::slotted([data-ui-toast-dismiss]){border:0;background:transparent;color:var(--ui-text-secondary);cursor:pointer;font:inherit}`;
7
8
 
8
9
  const UIToastRegion = class {
9
10
  constructor(hostRef) {
@@ -12,15 +13,26 @@ const UIToastRegion = class {
12
13
  get host() { return index.getElement(this); }
13
14
  open = true;
14
15
  internalOpen = true;
16
+ surface = null;
15
17
  syncFromProp() {
16
18
  this.internalOpen = this.open;
17
19
  }
20
+ syncSurface() {
21
+ if (this.internalOpen)
22
+ this.surface?.show();
23
+ else
24
+ this.surface?.hide();
25
+ }
18
26
  componentDidLoad() {
27
+ this.surface = positioning.createViewportSurface(this.host);
19
28
  this.syncFromProp();
29
+ this.syncSurface();
20
30
  this.host.addEventListener('click', this.onClick);
21
31
  }
22
32
  disconnectedCallback() {
23
33
  this.host.removeEventListener('click', this.onClick);
34
+ this.surface?.destroy();
35
+ this.surface = null;
24
36
  }
25
37
  getToasts() {
26
38
  return Array.from(this.host.querySelectorAll('[data-ui-toast]'));
@@ -54,11 +66,14 @@ const UIToastRegion = class {
54
66
  }
55
67
  };
56
68
  render() {
57
- return (index.h(index.Host, { key: '0edcce54864ba991b092d004d3b45cf697b9c3ac', role: "region", "aria-label": "Notifications", "data-open": this.internalOpen ? '' : undefined }, index.h("slot", { key: '2e30794a05b018fb55c4313c8e17f7262ecee7da' })));
69
+ return (index.h(index.Host, { key: '693e24016ec0881a4d619a489f1b655feee41784', role: "region", "aria-label": "Notifications", "data-open": this.internalOpen ? '' : undefined }, index.h("slot", { key: 'e666b2a73a5d7eb8dd607df3d1c5135b78cf3775' })));
58
70
  }
59
71
  static get watchers() { return {
60
72
  "open": [{
61
73
  "syncFromProp": 0
74
+ }],
75
+ "internalOpen": [{
76
+ "syncSurface": 0
62
77
  }]
63
78
  }; }
64
79
  };
@@ -1,10 +1,11 @@
1
1
  'use strict';
2
2
 
3
3
  var index = require('./index-D07f6BOw.js');
4
- var manager = require('./manager-BOpVXb92.js');
4
+ var manager = require('./manager-C9j3HZay.js');
5
5
  var events = require('./events-TcQIzpNv.js');
6
+ var positioning = require('./positioning-i0WrCAdF.js');
6
7
 
7
- const uiTooltipCss = () => `:host{display:inline-block;border:1px solid var(--ui-border-default);border-radius:var(--ui-radius-2);background:var(--ui-surface-elevated);color:var(--ui-text-primary);padding:var(--ui-space-2) var(--ui-space-3);font-size:var(--ui-font-size-sm);box-shadow:var(--ui-shadow-md)}:host([hidden]){display:none !important}`;
8
+ const uiTooltipCss = () => `:host{position:fixed;inset:auto;margin:0;display:inline-block;border:1px solid var(--ui-border-default);border-radius:var(--ui-radius-2);background:var(--ui-surface-elevated);color:var(--ui-text-primary);padding:var(--ui-space-2) var(--ui-space-3);font-size:var(--ui-font-size-sm);box-shadow:var(--ui-shadow-md);opacity:0;transform:translateY(4px);transition:opacity var(--ui-motion-fast) var(--ui-motion-ease), transform var(--ui-motion-fast) var(--ui-motion-ease), display var(--ui-motion-fast) allow-discrete, overlay var(--ui-motion-fast) allow-discrete}:host(:popover-open){opacity:1;transform:translateY(0)}@starting-style{:host(:popover-open){opacity:0;transform:translateY(4px)}}:host([hidden]){display:none !important}@media (prefers-reduced-motion: reduce){:host{transition:none}}`;
8
9
 
9
10
  const UITooltip = class {
10
11
  constructor(hostRef) {
@@ -14,51 +15,79 @@ const UITooltip = class {
14
15
  for = '';
15
16
  open = false;
16
17
  defaultOpen = false;
18
+ placement = 'top-start';
19
+ /** Pointer hover intent delay in milliseconds. Keyboard focus is immediate. */
20
+ showDelay = 500;
21
+ /** Pointer leave grace period in milliseconds. */
22
+ hideDelay = 100;
17
23
  internalOpen = false;
18
24
  overlayId = `ui-tooltip-${Math.random().toString(36).slice(2, 11)}`;
19
25
  trigger = null;
26
+ surface = null;
27
+ showTimer = null;
28
+ hideTimer = null;
20
29
  syncFromProp() {
30
+ this.clearTimers();
21
31
  this.internalOpen = this.open;
22
32
  }
33
+ resetSurface() {
34
+ this.syncTrigger();
35
+ this.setupSurface();
36
+ this.syncOverlay();
37
+ }
23
38
  syncOverlay() {
24
39
  if (this.internalOpen) {
40
+ this.surface?.show();
25
41
  manager.openOverlay({
26
42
  id: this.overlayId,
27
43
  modal: false,
28
44
  element: this.host,
45
+ relatedElements: this.trigger ? [this.trigger] : undefined,
29
46
  dismissible: true,
30
47
  requestClose: (reason, trigger) => this.handleRequestClose(reason, trigger),
31
48
  });
32
49
  }
33
50
  else {
51
+ this.surface?.hide();
34
52
  manager.closeOverlay(this.overlayId);
35
53
  }
36
54
  }
37
55
  componentDidLoad() {
38
56
  this.syncFromProp();
39
57
  this.internalOpen = this.internalOpen || this.defaultOpen;
40
- this.resolveTrigger();
41
- this.attachTriggerListeners();
58
+ this.syncTrigger();
59
+ this.setupSurface();
42
60
  this.host.addEventListener('keydown', this.onKeydown);
43
61
  this.syncOverlay();
44
62
  }
45
63
  componentDidUpdate() {
46
- this.resolveTrigger();
47
- this.attachTriggerListeners();
48
- this.syncOverlay();
64
+ this.syncTrigger();
49
65
  }
50
66
  disconnectedCallback() {
67
+ this.clearTimers();
51
68
  this.detachTriggerListeners();
69
+ this.surface?.destroy();
70
+ this.surface = null;
52
71
  this.host.removeEventListener('keydown', this.onKeydown);
53
72
  manager.closeOverlay(this.overlayId);
54
73
  }
55
- resolveTrigger() {
56
- if (this.for) {
57
- this.trigger = document.getElementById(this.for);
58
- }
59
- else {
60
- this.trigger = this.host.previousElementSibling;
61
- }
74
+ syncTrigger() {
75
+ const next = this.for
76
+ ? this.host.ownerDocument.getElementById(this.for)
77
+ : this.host.previousElementSibling;
78
+ if (next === this.trigger)
79
+ return;
80
+ this.detachTriggerListeners();
81
+ this.trigger = next;
82
+ this.attachTriggerListeners();
83
+ this.surface?.setAnchor(next);
84
+ }
85
+ setupSurface() {
86
+ this.surface?.destroy();
87
+ this.surface = positioning.createAnchoredSurface(this.host, {
88
+ anchor: this.trigger,
89
+ placement: this.placement,
90
+ });
62
91
  }
63
92
  attachTriggerListeners() {
64
93
  this.detachTriggerListeners();
@@ -77,31 +106,64 @@ const UITooltip = class {
77
106
  this.trigger.removeEventListener('focusin', this.onTriggerFocusIn);
78
107
  this.trigger.removeEventListener('focusout', this.onTriggerFocusOut);
79
108
  }
109
+ clearShowTimer() {
110
+ if (this.showTimer === null)
111
+ return;
112
+ clearTimeout(this.showTimer);
113
+ this.showTimer = null;
114
+ }
115
+ clearHideTimer() {
116
+ if (this.hideTimer === null)
117
+ return;
118
+ clearTimeout(this.hideTimer);
119
+ this.hideTimer = null;
120
+ }
121
+ clearTimers() {
122
+ this.clearShowTimer();
123
+ this.clearHideTimer();
124
+ }
125
+ setInteractionOpen(nextOpen, trigger) {
126
+ if (this.internalOpen === nextOpen)
127
+ return;
128
+ this.internalOpen = nextOpen;
129
+ events.dispatchDetail(this.host, nextOpen ? 'open' : 'close', {
130
+ open: nextOpen,
131
+ reason: 'action',
132
+ trigger,
133
+ });
134
+ }
80
135
  onTriggerEnter = () => {
81
- this.internalOpen = true;
82
- events.dispatchDetail(this.host, 'open', { open: true, reason: 'action', trigger: 'pointer' });
83
- this.syncOverlay();
136
+ this.clearHideTimer();
137
+ if (this.internalOpen || this.showTimer !== null)
138
+ return;
139
+ this.showTimer = setTimeout(() => {
140
+ this.showTimer = null;
141
+ this.setInteractionOpen(true, 'pointer');
142
+ }, Math.max(0, this.showDelay));
84
143
  };
85
144
  onTriggerLeave = () => {
86
- this.internalOpen = false;
87
- events.dispatchDetail(this.host, 'close', { open: false, reason: 'action', trigger: 'pointer' });
88
- this.syncOverlay();
145
+ this.clearShowTimer();
146
+ if (!this.internalOpen || this.hideTimer !== null)
147
+ return;
148
+ this.hideTimer = setTimeout(() => {
149
+ this.hideTimer = null;
150
+ this.setInteractionOpen(false, 'pointer');
151
+ }, Math.max(0, this.hideDelay));
89
152
  };
90
153
  onTriggerFocusIn = () => {
91
- this.internalOpen = true;
92
- events.dispatchDetail(this.host, 'open', { open: true, reason: 'action', trigger: 'keyboard' });
93
- this.syncOverlay();
154
+ this.clearTimers();
155
+ this.setInteractionOpen(true, 'keyboard');
94
156
  };
95
157
  onTriggerFocusOut = (e) => {
96
158
  const related = e.relatedTarget;
97
159
  if (related && this.host.contains(related))
98
160
  return;
99
- this.internalOpen = false;
100
- events.dispatchDetail(this.host, 'close', { open: false, reason: 'action', trigger: 'keyboard' });
101
- this.syncOverlay();
161
+ this.clearTimers();
162
+ this.setInteractionOpen(false, 'keyboard');
102
163
  };
103
164
  handleRequestClose(reason, trigger) {
104
165
  if (reason === 'escape') {
166
+ this.clearTimers();
105
167
  this.internalOpen = false;
106
168
  events.dispatchDetail(this.host, 'close', {
107
169
  open: false,
@@ -117,12 +179,18 @@ const UITooltip = class {
117
179
  }
118
180
  };
119
181
  render() {
120
- return (index.h(index.Host, { key: '64b5f4e7a56cbf5560edc6d7118022a49d5e2786', role: "tooltip", hidden: !this.internalOpen, "data-open": this.internalOpen ? '' : undefined, onKeyDown: this.onKeydown }, index.h("slot", { key: 'cf6f96689021a42a01b782f0e1d0730b5a9aa1f8' })));
182
+ return (index.h(index.Host, { key: '4c3890a7371508ede06a5b370d1cec32db25280f', role: "tooltip", hidden: !this.internalOpen, "data-open": this.internalOpen ? '' : undefined, onKeyDown: this.onKeydown }, index.h("slot", { key: '2e3bcd0dc69b0c3e3376d73dc4efc82e4f4cfaaa' })));
121
183
  }
122
184
  static get watchers() { return {
123
185
  "open": [{
124
186
  "syncFromProp": 0
125
187
  }],
188
+ "for": [{
189
+ "resetSurface": 0
190
+ }],
191
+ "placement": [{
192
+ "resetSurface": 0
193
+ }],
126
194
  "internalOpen": [{
127
195
  "syncOverlay": 0
128
196
  }]
@@ -603,7 +603,7 @@ function loomaIconMarkup(name, className = 'looma-icon') {
603
603
  ].join('');
604
604
  }
605
605
 
606
- const uiTreeItemCss = () => `:host{--ui-tree-indent:16px;--ui-tree-row-min-height:44px;--ui-tree-drop-color:var(--ui-accent-solid);position:relative;display:block;min-inline-size:0;margin-inline-start:calc(var(--ui-tree-item-depth, 0) * var(--ui-tree-indent))}.row{position:relative;display:grid;grid-template-columns:20px auto minmax(0, 1fr) auto;align-items:center;min-block-size:var(--ui-tree-row-min-height);min-inline-size:0;border-radius:var(--ui-radius-md);color:var(--ui-text-secondary);transition:background-color var(--ui-motion-fast) var(--ui-motion-ease), color var(--ui-motion-fast) var(--ui-motion-ease), opacity var(--ui-motion-fast) var(--ui-motion-ease), box-shadow var(--ui-motion-fast) var(--ui-motion-ease)}.row:hover,:host([data-selected]) .row,.row:focus-within{color:var(--ui-text-primary);background:var(--ui-surface-muted)}:host([data-disabled]) .row{opacity:0.55}:host([data-dragging]) .row{opacity:0.42}:host(:focus){outline:none}:host(:focus-visible) .row{box-shadow:0 0 0 2px var(--ui-focus-ring)}:host([data-drop-position='inside']) .row{background:var(--ui-accent-subtle);box-shadow:inset 0 0 0 1px var(--ui-tree-drop-color)}.disclosure,.disclosure-spacer{grid-column:1;grid-row:1;inline-size:20px;block-size:28px}.disclosure{display:grid;place-items:center;padding:0;color:var(--ui-text-muted);background:transparent;border:0;border-radius:var(--ui-radius-sm);cursor:pointer}.disclosure:hover{color:var(--ui-text-primary);background:var(--ui-surface-elevated)}.disclosure-icon{display:block;inline-size:14px;block-size:14px;transform:rotate(-90deg);transition:transform var(--ui-motion-fast) var(--ui-motion-ease)}.disclosure-icon--expanded{transform:rotate(0deg)}.disclosure-icon :is(svg),.drag-handle :is(svg){display:block;inline-size:100%;block-size:100%}.leading{display:none}.leading:has(*){display:inline-grid;place-items:center;grid-column:2;grid-row:1;inline-size:16px;block-size:16px;margin-inline-start:var(--ui-space-1)}.label{grid-column:3;grid-row:1;min-inline-size:0;padding:var(--ui-space-1) var(--ui-space-2)}.actions{grid-column:4;grid-row:1;display:inline-flex;align-items:center;min-inline-size:0}.drag-handle{position:absolute;z-index:2;inset-block-start:50%;inset-inline-start:-22px;inline-size:20px;block-size:32px;display:grid;place-items:center;padding:7px 3px;color:var(--ui-text-muted);background:var(--ui-surface-elevated);border:1px solid transparent;border-radius:var(--ui-radius-sm);cursor:grab;opacity:0;pointer-events:none;transform:translateY(-50%);transition:opacity var(--ui-motion-fast) var(--ui-motion-ease)}.row:hover .drag-handle,.drag-handle:focus-visible,:host([data-dragging]) .drag-handle{opacity:1;pointer-events:auto}.drag-handle:active{cursor:grabbing}.drop-indicator{position:absolute;z-index:3;inset-inline:1px;display:none;block-size:2px;border-radius:999px;background:var(--ui-tree-drop-color);pointer-events:none}.drop-indicator::before,.drop-indicator::after{content:'';position:absolute;inset-block-start:50%;inline-size:7px;block-size:7px;border:2px solid var(--ui-tree-drop-color);border-radius:999px;background:var(--ui-surface-elevated);transform:translateY(-50%)}.drop-indicator::before{inset-inline-start:-1px}.drop-indicator::after{inset-inline-end:-1px}:host([data-drop-position='before']) .drop-indicator{display:block;inset-block-start:-1px}:host([data-drop-position='after']) .drop-indicator{display:block;inset-block-end:-1px}.children[hidden]{display:none}@media (hover: none), (pointer: coarse){.drag-handle{display:none}}@media (prefers-reduced-motion: reduce){.row,.disclosure-icon,.drag-handle{transition:none}}`;
606
+ const uiTreeItemCss = () => `:host{--ui-tree-indent:16px;--ui-tree-row-min-height:32px;--ui-tree-drop-color:var(--ui-accent-solid);position:relative;display:block;min-inline-size:0;margin-inline-start:calc(var(--ui-tree-item-depth, 0) * var(--ui-tree-indent))}.row{position:relative;display:grid;grid-template-columns:20px auto minmax(0, 1fr) auto;align-items:center;min-block-size:var(--ui-tree-row-min-height);min-inline-size:0;font-size:var(--ui-tree-font-size, var(--ui-font-size-ui, 0.9375rem));border-radius:var(--ui-radius-md);color:var(--ui-text-secondary);transition:background-color var(--ui-motion-fast) var(--ui-motion-ease), color var(--ui-motion-fast) var(--ui-motion-ease), opacity var(--ui-motion-fast) var(--ui-motion-ease), box-shadow var(--ui-motion-fast) var(--ui-motion-ease), min-block-size var(--ui-motion-base) var(--ui-motion-ease)}.row:hover,:host([data-selected]) .row,.row:focus-within{color:var(--ui-text-primary);background:var(--ui-surface-muted)}:host([data-disabled]) .row{opacity:0.55}:host([data-dragging]) .row{opacity:0.42}:host(:focus){outline:none}:host(:focus-visible) .row{box-shadow:0 0 0 2px var(--ui-focus-ring)}:host([data-drop-position='inside']) .row{background:var(--ui-accent-subtle);box-shadow:inset 0 0 0 1px var(--ui-tree-drop-color)}.disclosure,.disclosure-spacer{grid-column:1;grid-row:1;inline-size:20px;block-size:28px}.disclosure{display:grid;place-items:center;padding:0;color:var(--ui-text-muted);background:transparent;border:0;border-radius:var(--ui-radius-sm);cursor:pointer}.disclosure:hover{color:var(--ui-text-primary);background:var(--ui-surface-elevated)}.disclosure-icon{display:block;inline-size:14px;block-size:14px;transform:rotate(-90deg);transition:transform var(--ui-motion-fast) var(--ui-motion-ease)}.disclosure-icon--expanded{transform:rotate(0deg)}.disclosure-icon :is(svg),.drag-handle :is(svg){display:block;inline-size:100%;block-size:100%}.leading{display:none}.leading:has(*){display:inline-grid;place-items:center;grid-column:2;grid-row:1;inline-size:16px;block-size:16px;margin-inline-start:var(--ui-space-1)}.label{grid-column:3;grid-row:1;min-inline-size:0;padding:var(--ui-space-1) var(--ui-space-2)}.actions{grid-column:4;grid-row:1;display:inline-flex;align-items:center;min-inline-size:0}.drag-handle{position:absolute;z-index:2;inset-block-start:50%;inset-inline-start:-22px;inline-size:20px;block-size:32px;display:grid;place-items:center;padding:7px 3px;color:var(--ui-text-muted);background:var(--ui-surface-elevated);border:1px solid transparent;border-radius:var(--ui-radius-sm);cursor:grab;opacity:0;pointer-events:none;transform:translateY(-50%);transition:opacity var(--ui-motion-fast) var(--ui-motion-ease)}.row:hover .drag-handle,.drag-handle:focus-visible,:host([data-dragging]) .drag-handle{opacity:1;pointer-events:auto}.drag-handle:active{cursor:grabbing}.drop-indicator{position:absolute;z-index:3;inset-inline:1px;display:none;block-size:2px;border-radius:999px;background:var(--ui-tree-drop-color);pointer-events:none}.drop-indicator::before,.drop-indicator::after{content:'';position:absolute;inset-block-start:50%;inline-size:7px;block-size:7px;border:2px solid var(--ui-tree-drop-color);border-radius:999px;background:var(--ui-surface-elevated);transform:translateY(-50%)}.drop-indicator::before{inset-inline-start:-1px}.drop-indicator::after{inset-inline-end:-1px}:host([data-drop-position='before']) .drop-indicator{display:block;inset-block-start:-1px}:host([data-drop-position='after']) .drop-indicator{display:block;inset-block-end:-1px}.children[hidden]{display:none}:host-context(html[data-ui-input-modality='touch']){--ui-tree-row-min-height:44px}:host-context(html[data-ui-input-modality='touch']) .drag-handle{display:none}@media (prefers-reduced-motion: reduce){.row,.disclosure-icon,.drag-handle{transition:none}}`;
607
607
 
608
608
  const UITreeItem = class {
609
609
  constructor(hostRef) {
@@ -4,14 +4,27 @@
4
4
 
5
5
  .menu {
6
6
  position: fixed;
7
+ inset: auto;
8
+ margin: 0;
7
9
  z-index: var(--ui-z-overlay, 1000);
8
10
  min-width: 12rem;
11
+ opacity: 0;
12
+ transform: translateY(-4px) scale(0.99);
13
+ transition:
14
+ opacity var(--ui-motion-fast) var(--ui-motion-ease),
15
+ transform var(--ui-motion-fast) var(--ui-motion-ease),
16
+ display var(--ui-motion-fast) allow-discrete,
17
+ overlay var(--ui-motion-fast) allow-discrete;
9
18
  }
10
19
 
11
- .menu[data-open] {
12
- display: block;
20
+ .menu:popover-open { opacity: 1; transform: translateY(0) scale(1); }
21
+
22
+ @starting-style {
23
+ .menu:popover-open { opacity: 0; transform: translateY(-4px) scale(0.99); }
13
24
  }
14
25
 
15
- .menu:not([data-open]) {
16
- display: none !important;
26
+ .menu[hidden] { display: none !important; }
27
+
28
+ @media (prefers-reduced-motion: reduce) {
29
+ .menu { transition: none; }
17
30
  }
@@ -1,6 +1,7 @@
1
1
  import { Host, h } from "@stencil/core";
2
2
  import { openOverlay, closeOverlay, requestTopOverlayClose } from "../../overlay/manager";
3
3
  import { dispatchDetail } from "../../utils/events";
4
+ import { createAnchoredSurface } from "../../overlay/positioning";
4
5
  export class UIContextMenu {
5
6
  host;
6
7
  /** Controls the menu when the consumer owns open state. */
@@ -15,6 +16,8 @@ export class UIContextMenu {
15
16
  contextTarget = null;
16
17
  pointerX = 0;
17
18
  pointerY = 0;
19
+ pointPositioned = false;
20
+ surface = null;
18
21
  syncFromProp() {
19
22
  this.internalOpen = this.open;
20
23
  }
@@ -25,6 +28,12 @@ export class UIContextMenu {
25
28
  syncOverlay() {
26
29
  this.syncTriggerState();
27
30
  if (this.internalOpen) {
31
+ if (this.pointPositioned) {
32
+ this.surface?.showAtPoint({ x: this.pointerX, y: this.pointerY });
33
+ }
34
+ else {
35
+ this.surface?.show();
36
+ }
28
37
  openOverlay({
29
38
  id: this.overlayId,
30
39
  modal: false,
@@ -34,6 +43,7 @@ export class UIContextMenu {
34
43
  });
35
44
  }
36
45
  else {
46
+ this.surface?.hide();
37
47
  closeOverlay(this.overlayId);
38
48
  }
39
49
  }
@@ -42,18 +52,33 @@ export class UIContextMenu {
42
52
  this.internalOpen = this.internalOpen || this.defaultOpen;
43
53
  this.resolveTargets();
44
54
  this.attachTargetListeners();
55
+ this.setupSurface();
45
56
  this.syncTriggerState();
46
57
  this.syncOverlay();
47
58
  }
48
59
  componentDidUpdate() {
49
60
  this.resolveTargets();
50
61
  this.attachTargetListeners();
62
+ if (!this.pointPositioned)
63
+ this.surface?.setAnchor(this.trigger);
51
64
  this.syncTriggerState();
52
65
  }
53
66
  disconnectedCallback() {
54
67
  this.detachTargetListeners();
68
+ this.surface?.destroy();
69
+ this.surface = null;
55
70
  closeOverlay(this.overlayId);
56
71
  }
72
+ setupSurface() {
73
+ const menu = this.host.shadowRoot?.querySelector('.menu');
74
+ if (!menu)
75
+ return;
76
+ this.surface?.destroy();
77
+ this.surface = createAnchoredSurface(menu, {
78
+ anchor: this.trigger,
79
+ placement: 'bottom-start',
80
+ });
81
+ }
57
82
  resolveTargets() {
58
83
  const previousTrigger = this.trigger;
59
84
  const previousContextTarget = this.contextTarget;
@@ -93,6 +118,10 @@ export class UIContextMenu {
93
118
  openMenu(x, y, trigger) {
94
119
  this.pointerX = x;
95
120
  this.pointerY = y;
121
+ this.pointPositioned = true;
122
+ // Context-click can reposition an already-open trigger menu, so do not
123
+ // depend on a state transition to run the positioning controller.
124
+ this.surface?.showAtPoint({ x, y });
96
125
  this.internalOpen = true;
97
126
  dispatchDetail(this.host, 'open', {
98
127
  open: true,
@@ -104,8 +133,14 @@ export class UIContextMenu {
104
133
  openFromTrigger(trigger) {
105
134
  if (!this.trigger)
106
135
  return;
107
- const rect = this.trigger.getBoundingClientRect();
108
- this.openMenu(rect.left, rect.bottom, trigger);
136
+ this.pointPositioned = false;
137
+ this.internalOpen = true;
138
+ dispatchDetail(this.host, 'open', {
139
+ open: true,
140
+ reason: 'action',
141
+ trigger,
142
+ });
143
+ this.focusFirstItem();
109
144
  }
110
145
  closeMenu(reason, trigger, returnFocus = true) {
111
146
  if (!this.internalOpen)
@@ -174,7 +209,7 @@ export class UIContextMenu {
174
209
  this.closeMenu('action', 'pointer');
175
210
  };
176
211
  render() {
177
- return (h(Host, { key: '79fbca2c89f1d1fd1f4796ad88d2a599e811a3f9', "data-open": this.internalOpen ? '' : undefined, onKeyDown: this.onKeydown, onClick: this.onItemClick }, h("slot", { key: '9308cadfa155930e3e46dfb4a69a847ae388958b', name: "trigger" }), h("div", { key: '16cfba2b32444ac9e3cac1f040f81f17fd5db23c', class: "menu", "data-open": this.internalOpen ? '' : undefined, style: { left: `${this.pointerX}px`, top: `${this.pointerY}px` } }, h("ui-menu", { key: '59f18e0df3bb1fe85efa8eeca00b336bbde47c70', open: this.internalOpen }, h("slot", { key: '5bdbb7773c9c1ae43ec2e05fca9558482815e28a' })))));
212
+ return (h(Host, { key: 'aee76fad23434fb3cdaf885c698f62237c2a9468', "data-open": this.internalOpen ? '' : undefined, onKeyDown: this.onKeydown, onClick: this.onItemClick }, h("slot", { key: 'da047f870b7a93f1acbba5fa1628b5cb66139d99', name: "trigger" }), h("div", { key: '388a473ac7fb626a20d0feda14f0d8dd16f6ab94', class: "menu", "data-open": this.internalOpen ? '' : undefined }, h("ui-menu", { key: '67ca5c05ff40d2dd1d5f17a8634636503f98c02d', open: this.internalOpen }, h("slot", { key: 'b7c7dfac4342afa1df67459e94de7ac7dca4a251' })))));
178
213
  }
179
214
  static get is() { return "ui-context-menu"; }
180
215
  static get encapsulation() { return "shadow"; }