@threadlabs/looma 0.1.16 → 0.1.18

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 (133) hide show
  1. package/README.md +2 -1
  2. package/dist/{chunk-SVINNT3E.js → chunk-DTQVRGVL.js} +29 -29
  3. package/dist/{chunk-G53JEAQK.js → chunk-SZJQ3NPX.js} +6 -3
  4. package/dist/chunk-YHXJC5M5.js +341 -0
  5. package/dist/cjs/loader.cjs.js +1 -1
  6. package/dist/cjs/looma.cjs.js +1 -1
  7. package/dist/cjs/{manager-BOpVXb92.js → manager-C9j3HZay.js} +5 -2
  8. package/dist/cjs/positioning-i0WrCAdF.js +384 -0
  9. package/dist/cjs/ui-affordance-scope.cjs.entry.js +3 -113
  10. package/dist/cjs/ui-context-menu.cjs.entry.js +43 -5
  11. package/dist/cjs/ui-dialog.cjs.entry.js +1 -1
  12. package/dist/cjs/ui-menu.cjs.entry.js +83 -2
  13. package/dist/cjs/ui-popover.cjs.entry.js +43 -3
  14. package/dist/cjs/ui-toast-region.cjs.entry.js +17 -2
  15. package/dist/cjs/ui-tooltip.cjs.entry.js +95 -27
  16. package/dist/cjs/ui-tree-item.cjs.entry.js +1 -1
  17. package/dist/collection/components/ui-context-menu/ui-context-menu.css +21 -4
  18. package/dist/collection/components/ui-context-menu/ui-context-menu.js +43 -4
  19. package/dist/collection/components/ui-menu/ui-menu.css +39 -0
  20. package/dist/collection/components/ui-menu/ui-menu.js +128 -1
  21. package/dist/collection/components/ui-popover/ui-popover.css +31 -0
  22. package/dist/collection/components/ui-popover/ui-popover.js +87 -1
  23. package/dist/collection/components/ui-toast-region/ui-toast-region.css +6 -0
  24. package/dist/collection/components/ui-toast-region/ui-toast-region.js +16 -1
  25. package/dist/collection/components/ui-tooltip/ui-tooltip.css +20 -0
  26. package/dist/collection/components/ui-tooltip/ui-tooltip.js +160 -25
  27. package/dist/collection/components/ui-tree-item/ui-tree-item.css +10 -6
  28. package/dist/collection/input-modality.js +24 -0
  29. package/dist/collection/overlay/manager.js +5 -2
  30. package/dist/collection/overlay/positioning.js +246 -0
  31. package/dist/components/p-CEYIKqYc.js +1 -0
  32. package/dist/components/p-DOulVJT7.js +1 -0
  33. package/dist/components/p-PM9a74r7.js +1 -0
  34. package/dist/components/ui-affordance-scope.js +1 -1
  35. package/dist/components/ui-context-menu.js +1 -1
  36. package/dist/components/ui-dialog.js +1 -1
  37. package/dist/components/ui-menu.js +1 -1
  38. package/dist/components/ui-popover.js +1 -1
  39. package/dist/components/ui-toast-region.js +1 -1
  40. package/dist/components/ui-tooltip.js +1 -1
  41. package/dist/components/ui-tree-item.js +1 -1
  42. package/dist/esm/loader.js +1 -1
  43. package/dist/esm/looma.js +1 -1
  44. package/dist/esm/{manager-CR7f2MQC.js → manager-PM9a74r7.js} +5 -2
  45. package/dist/esm/positioning-Bu0dmLBE.js +379 -0
  46. package/dist/esm/ui-affordance-scope.entry.js +1 -111
  47. package/dist/esm/ui-context-menu.entry.js +43 -5
  48. package/dist/esm/ui-dialog.entry.js +1 -1
  49. package/dist/esm/ui-menu.entry.js +83 -2
  50. package/dist/esm/ui-popover.entry.js +43 -3
  51. package/dist/esm/ui-toast-region.entry.js +17 -2
  52. package/dist/esm/ui-tooltip.entry.js +95 -27
  53. package/dist/esm/ui-tree-item.entry.js +1 -1
  54. package/dist/index.cjs +779 -59
  55. package/dist/index.d.cts +44 -1
  56. package/dist/index.d.ts +44 -1
  57. package/dist/index.js +235 -4
  58. package/dist/looma/looma.esm.js +1 -1
  59. package/dist/looma/p-063f5a89.entry.js +1 -0
  60. package/dist/looma/{p-c6721aba.entry.js → p-11083743.entry.js} +1 -1
  61. package/dist/looma/p-1674cda4.entry.js +1 -0
  62. package/dist/looma/p-2e6f791e.entry.js +1 -0
  63. package/dist/looma/p-54c41576.entry.js +1 -0
  64. package/dist/looma/p-9194a99d.entry.js +1 -0
  65. package/dist/looma/p-Bu0dmLBE.js +1 -0
  66. package/dist/looma/p-PM9a74r7.js +1 -0
  67. package/dist/looma/p-caa8fc7b.entry.js +1 -0
  68. package/dist/looma/{p-ef9ca2ed.entry.js → p-d01960c2.entry.js} +2 -2
  69. package/dist/types/components/ui-context-menu/ui-context-menu.d.ts +6 -0
  70. package/dist/types/components/ui-menu/ui-menu.d.ts +13 -0
  71. package/dist/types/components/ui-popover/ui-popover.d.ts +9 -0
  72. package/dist/types/components/ui-toast-region/ui-toast-region.d.ts +2 -0
  73. package/dist/types/components/ui-tooltip/ui-tooltip.d.ts +16 -1
  74. package/dist/types/components.d.ts +71 -0
  75. package/dist/types/input-modality.d.ts +6 -0
  76. package/dist/types/overlay/manager.d.ts +3 -0
  77. package/dist/types/overlay/positioning.d.ts +33 -0
  78. package/dist/ui-affordance-scope.entry-2W7ZKAGK.js +54 -0
  79. package/dist/{ui-avatar-group.entry-DQDP325O.js → ui-avatar-group.entry-SE5JG2FT.js} +1 -1
  80. package/dist/{ui-avatar.entry-WYIDMOFE.js → ui-avatar.entry-46JDI25A.js} +1 -1
  81. package/dist/{ui-badge.entry-VGHXPEDT.js → ui-badge.entry-U7G3MEC5.js} +1 -1
  82. package/dist/{ui-button.entry-LKPLOJ22.js → ui-button.entry-DELD6KMH.js} +1 -1
  83. package/dist/{ui-checkbox.entry-J74HEA22.js → ui-checkbox.entry-ZUG2P642.js} +1 -1
  84. package/dist/{ui-context-menu.entry-MQM2FCLI.js → ui-context-menu.entry-L4HTK3PV.js} +43 -6
  85. package/dist/{ui-dialog.entry-DQMMDO4Z.js → ui-dialog.entry-TQPYD2YI.js} +2 -2
  86. package/dist/{ui-disclosure.entry-ONF7UZDC.js → ui-disclosure.entry-23TII4YU.js} +1 -1
  87. package/dist/{ui-floating-action-button.entry-FKTGVC5T.js → ui-floating-action-button.entry-FOQYAFJI.js} +1 -1
  88. package/dist/{ui-form-field.entry-M3ISWQNU.js → ui-form-field.entry-SCPG4STY.js} +1 -1
  89. package/dist/{ui-icon-button.entry-AWFIQUHM.js → ui-icon-button.entry-U45ZVAC3.js} +1 -1
  90. package/dist/{ui-input.entry-JZ6RETMT.js → ui-input.entry-3AIA3G4C.js} +1 -1
  91. package/dist/{ui-menu-item.entry-5MFR5LFL.js → ui-menu-item.entry-3QO5TG2J.js} +1 -1
  92. package/dist/ui-menu.entry-JACWGDDG.js +184 -0
  93. package/dist/{ui-popover.entry-ELCMKO6Y.js → ui-popover.entry-2XP4H3S2.js} +46 -4
  94. package/dist/{ui-radio-group.entry-RJHUPPEM.js → ui-radio-group.entry-73IKFMDK.js} +1 -1
  95. package/dist/{ui-radio.entry-3VRDPOY4.js → ui-radio.entry-INJVXUKE.js} +1 -1
  96. package/dist/{ui-search-result-row.entry-J34YPAV7.js → ui-search-result-row.entry-4J732TDC.js} +1 -1
  97. package/dist/{ui-search-shell.entry-IJ4UUP27.js → ui-search-shell.entry-3C2WRVE2.js} +1 -1
  98. package/dist/{ui-select.entry-C6YD3HQ5.js → ui-select.entry-3ZLDGAKX.js} +1 -1
  99. package/dist/{ui-switch.entry-UZDF5QTI.js → ui-switch.entry-5MJRLR2P.js} +1 -1
  100. package/dist/{ui-tabs.entry-YRAKWOMR.js → ui-tabs.entry-F5H7OPBO.js} +1 -1
  101. package/dist/{ui-textarea.entry-JLDKFT3K.js → ui-textarea.entry-BNJ375JB.js} +1 -1
  102. package/dist/{ui-toast-region.entry-GC7C7KXF.js → ui-toast-region.entry-ZUIPJ2JH.js} +20 -3
  103. package/dist/ui-tooltip.entry-DZ4RMF35.js +214 -0
  104. package/dist/{ui-top-bar.entry-5W5UA5KV.js → ui-top-bar.entry-VKRXIUU6.js} +1 -1
  105. package/dist/{ui-tree-item.entry-3LM7IXIK.js → ui-tree-item.entry-NFUQ45OL.js} +2 -2
  106. package/dist/{ui-tree.entry-X4FU42A6.js → ui-tree.entry-H4CWDYI6.js} +1 -1
  107. package/editor/{chunk-RTHHTPWK.js → chunk-3QMBK3XZ.js} +245 -41
  108. package/editor/{chunk-OHD4OS32.js → chunk-OXFYYLUC.js} +632 -236
  109. package/editor/{chunk-CX4QDKSV.js → chunk-ZOUKEZ77.js} +21 -1
  110. package/editor/extensions/index.d.ts +49 -1
  111. package/editor/extensions/index.js +15 -3
  112. package/editor/index.d.ts +3 -2
  113. package/editor/index.js +16 -4
  114. package/editor/ui.d.ts +9 -1
  115. package/editor/ui.js +2 -2
  116. package/editor.css +173 -4
  117. package/package.json +1 -1
  118. package/styles.css +1 -7
  119. package/tokens.css +14 -2
  120. package/vue/editor/index.d.ts +103 -4
  121. package/vue/editor/index.js +199 -114
  122. package/dist/components/p-CR7f2MQC.js +0 -1
  123. package/dist/components/p-QiwSQmKJ.js +0 -1
  124. package/dist/looma/p-2ae73d5b.entry.js +0 -1
  125. package/dist/looma/p-3ac2e0d4.entry.js +0 -1
  126. package/dist/looma/p-65c25c94.entry.js +0 -1
  127. package/dist/looma/p-8c09c724.entry.js +0 -1
  128. package/dist/looma/p-95cd639d.entry.js +0 -1
  129. package/dist/looma/p-96f73d07.entry.js +0 -1
  130. package/dist/looma/p-CR7f2MQC.js +0 -1
  131. package/dist/ui-affordance-scope.entry-PFP2TDKO.js +0 -145
  132. package/dist/ui-menu.entry-IOEIDATQ.js +0 -98
  133. package/dist/ui-tooltip.entry-F26XDTYX.js +0 -145
@@ -0,0 +1,384 @@
1
+ 'use strict';
2
+
3
+ const DEFAULT_AFFORDANCE_NEAR_RADIUS = 16;
4
+ let anchorSequence = 0;
5
+ const anchorBindings = new WeakMap();
6
+ function bindAnchorName(anchor, name) {
7
+ const binding = anchorBindings.get(anchor) ?? {
8
+ original: anchor.style.getPropertyValue("anchor-name"),
9
+ names: new Set(),
10
+ };
11
+ binding.names.add(name);
12
+ anchorBindings.set(anchor, binding);
13
+ const values = [binding.original, ...binding.names].filter(Boolean);
14
+ anchor.style.setProperty("anchor-name", values.join(", "));
15
+ }
16
+ function unbindAnchorName(anchor, name) {
17
+ const binding = anchorBindings.get(anchor);
18
+ if (!binding)
19
+ return;
20
+ binding.names.delete(name);
21
+ if (binding.names.size) {
22
+ const values = [binding.original, ...binding.names].filter(Boolean);
23
+ anchor.style.setProperty("anchor-name", values.join(", "));
24
+ return;
25
+ }
26
+ if (binding.original)
27
+ anchor.style.setProperty("anchor-name", binding.original);
28
+ else
29
+ anchor.style.removeProperty("anchor-name");
30
+ anchorBindings.delete(anchor);
31
+ }
32
+ function supportsAnchorPositioning(owner) {
33
+ const css = owner.CSS;
34
+ return typeof css?.supports === "function"
35
+ && css.supports("anchor-name: --ui-anchor-test")
36
+ && css.supports("position-anchor: --ui-anchor-test")
37
+ && css.supports("top: anchor(bottom)");
38
+ }
39
+ function topLayerOpen(surface) {
40
+ try {
41
+ return surface.matches(":popover-open");
42
+ }
43
+ catch {
44
+ return !surface.hidden;
45
+ }
46
+ }
47
+ function showInTopLayer(surface) {
48
+ surface.setAttribute("popover", "manual");
49
+ surface.hidden = false;
50
+ if (typeof surface.showPopover === "function" && !topLayerOpen(surface)) {
51
+ try {
52
+ surface.showPopover();
53
+ }
54
+ catch {
55
+ // A disconnected or already-transitioning surface still receives the
56
+ // fixed-position fallback below and will be retried on the next sync.
57
+ }
58
+ }
59
+ }
60
+ function hideFromTopLayer(surface) {
61
+ if (typeof surface.hidePopover === "function" && topLayerOpen(surface)) {
62
+ try {
63
+ surface.hidePopover();
64
+ }
65
+ catch {
66
+ // The surface may have been disconnected between state and DOM updates.
67
+ }
68
+ }
69
+ surface.hidden = true;
70
+ }
71
+ /**
72
+ * Places unanchored floating UI, such as a toast region, in the browser top
73
+ * layer. Its viewport position remains CSS-owned; the controller only provides
74
+ * the native Popover API boundary and its no-Popover fallback.
75
+ */
76
+ function createViewportSurface(surface) {
77
+ surface.setAttribute("popover", "manual");
78
+ surface.dataset.uiPositioning = "viewport";
79
+ return {
80
+ show() {
81
+ showInTopLayer(surface);
82
+ },
83
+ hide() {
84
+ hideFromTopLayer(surface);
85
+ },
86
+ destroy() {
87
+ hideFromTopLayer(surface);
88
+ },
89
+ };
90
+ }
91
+ function fallbackPosition(surface, anchor, placement, gap, viewportGap) {
92
+ const owner = surface.ownerDocument.defaultView ?? window;
93
+ const viewport = getVisualViewportRect(owner);
94
+ const rect = surface.getBoundingClientRect();
95
+ const preferTop = placement.startsWith("top");
96
+ const preferEnd = placement.endsWith("end");
97
+ const spaceBelow = viewport.bottom - anchor.bottom - viewportGap;
98
+ const spaceAbove = anchor.top - viewport.top - viewportGap;
99
+ const useTop = preferTop
100
+ ? !(spaceAbove < rect.height + gap && spaceBelow > spaceAbove)
101
+ : spaceBelow < rect.height + gap && spaceAbove > spaceBelow;
102
+ const unclampedTop = useTop
103
+ ? anchor.top - rect.height - gap
104
+ : anchor.bottom + gap;
105
+ const unclampedLeft = preferEnd
106
+ ? anchor.right - rect.width
107
+ : anchor.left;
108
+ const shifted = clampRectToViewport({
109
+ left: unclampedLeft,
110
+ top: unclampedTop,
111
+ width: rect.width,
112
+ height: rect.height,
113
+ }, viewport, viewportGap);
114
+ surface.style.left = `${Math.round(unclampedLeft + shifted.x)}px`;
115
+ surface.style.top = `${Math.round(unclampedTop + shifted.y)}px`;
116
+ surface.style.right = "auto";
117
+ surface.style.bottom = "auto";
118
+ }
119
+ /**
120
+ * One lightweight positioning controller for Looma floating UI. Native
121
+ * Popover provides the top layer and CSS Anchor Positioning follows a named
122
+ * element. Browsers without Anchor Positioning use the same flip/shift
123
+ * geometry through scroll/resize listeners that exist only while open.
124
+ */
125
+ function createAnchoredSurface(surface, options = {}) {
126
+ const owner = surface.ownerDocument.defaultView ?? window;
127
+ const placement = options.placement ?? "bottom-start";
128
+ const gap = Math.max(0, options.gap ?? 4);
129
+ const viewportGap = Math.max(0, options.viewportGap ?? 8);
130
+ const nativeAnchor = supportsAnchorPositioning(owner);
131
+ const anchorName = `--ui-anchor-${++anchorSequence}`;
132
+ let anchor = options.anchor ?? null;
133
+ let point = null;
134
+ let open = false;
135
+ let frame = null;
136
+ let listenerAbort = null;
137
+ surface.setAttribute("popover", "manual");
138
+ surface.dataset.uiPositioning = nativeAnchor ? "anchor" : "fallback";
139
+ surface.style.position = "fixed";
140
+ surface.style.margin = "0";
141
+ const bindAnchor = (next) => {
142
+ if (anchor)
143
+ unbindAnchorName(anchor, anchorName);
144
+ anchor = next;
145
+ if (!anchor) {
146
+ surface.style.removeProperty("position-anchor");
147
+ return;
148
+ }
149
+ bindAnchorName(anchor, anchorName);
150
+ surface.style.setProperty("position-anchor", anchorName);
151
+ };
152
+ const positionNative = () => {
153
+ const blockEnd = placement.startsWith("bottom");
154
+ const inlineEnd = placement.endsWith("end");
155
+ surface.style.top = blockEnd ? `calc(anchor(bottom) + ${gap}px)` : "auto";
156
+ surface.style.bottom = blockEnd ? "auto" : `calc(anchor(top) + ${gap}px)`;
157
+ surface.style.left = inlineEnd ? "auto" : "anchor(left)";
158
+ surface.style.right = inlineEnd ? "anchor(right)" : "auto";
159
+ surface.style.setProperty("position-try-fallbacks", "flip-block, flip-inline");
160
+ };
161
+ const position = () => {
162
+ frame = null;
163
+ if (!open)
164
+ return;
165
+ if (point) {
166
+ fallbackPosition(surface, {
167
+ left: point.x,
168
+ right: point.x,
169
+ top: point.y,
170
+ bottom: point.y}, "bottom-start", gap, viewportGap);
171
+ return;
172
+ }
173
+ if (!anchor)
174
+ return;
175
+ if (nativeAnchor)
176
+ positionNative();
177
+ else
178
+ fallbackPosition(surface, anchor.getBoundingClientRect(), placement, gap, viewportGap);
179
+ };
180
+ const schedule = () => {
181
+ if (!open || frame !== null)
182
+ return;
183
+ frame = owner.requestAnimationFrame(position);
184
+ };
185
+ const syncFallbackListeners = () => {
186
+ const needsListeners = open && (point !== null || !nativeAnchor);
187
+ if (!needsListeners) {
188
+ listenerAbort?.abort();
189
+ listenerAbort = null;
190
+ return;
191
+ }
192
+ if (listenerAbort)
193
+ return;
194
+ listenerAbort = new AbortController();
195
+ const signal = listenerAbort.signal;
196
+ owner.addEventListener("resize", schedule, { passive: true, signal });
197
+ owner.addEventListener("scroll", schedule, { passive: true, capture: true, signal });
198
+ owner.visualViewport?.addEventListener("resize", schedule, { passive: true, signal });
199
+ owner.visualViewport?.addEventListener("scroll", schedule, { passive: true, signal });
200
+ };
201
+ bindAnchor(anchor);
202
+ return {
203
+ setAnchor(next) {
204
+ point = null;
205
+ bindAnchor(next);
206
+ syncFallbackListeners();
207
+ schedule();
208
+ },
209
+ show() {
210
+ point = null;
211
+ open = true;
212
+ syncFallbackListeners();
213
+ showInTopLayer(surface);
214
+ position();
215
+ },
216
+ showAtPoint(nextPoint) {
217
+ point = nextPoint;
218
+ open = true;
219
+ syncFallbackListeners();
220
+ showInTopLayer(surface);
221
+ position();
222
+ },
223
+ hide() {
224
+ open = false;
225
+ point = null;
226
+ syncFallbackListeners();
227
+ if (frame !== null)
228
+ owner.cancelAnimationFrame(frame);
229
+ frame = null;
230
+ hideFromTopLayer(surface);
231
+ },
232
+ refresh: schedule,
233
+ destroy() {
234
+ open = false;
235
+ listenerAbort?.abort();
236
+ listenerAbort = null;
237
+ if (frame !== null)
238
+ owner.cancelAnimationFrame(frame);
239
+ frame = null;
240
+ hideFromTopLayer(surface);
241
+ bindAnchor(null);
242
+ },
243
+ };
244
+ }
245
+ function getVisualViewportRect(owner = window) {
246
+ const viewport = owner.visualViewport;
247
+ const left = viewport?.offsetLeft ?? 0;
248
+ const top = viewport?.offsetTop ?? 0;
249
+ const width = viewport?.width ?? owner.innerWidth;
250
+ const height = viewport?.height ?? owner.innerHeight;
251
+ return {
252
+ left,
253
+ top,
254
+ right: left + width,
255
+ bottom: top + height,
256
+ width,
257
+ height,
258
+ };
259
+ }
260
+ function clampRectToViewport(rect, viewport = getVisualViewportRect(), gutter = 0) {
261
+ const availableWidth = Math.max(0, viewport.width - (gutter * 2));
262
+ const availableHeight = Math.max(0, viewport.height - (gutter * 2));
263
+ const targetLeft = rect.width > availableWidth
264
+ ? viewport.left + gutter
265
+ : Math.min(Math.max(rect.left, viewport.left + gutter), viewport.right - gutter - rect.width);
266
+ const targetTop = rect.height > availableHeight
267
+ ? viewport.top + gutter
268
+ : Math.min(Math.max(rect.top, viewport.top + gutter), viewport.bottom - gutter - rect.height);
269
+ return { x: targetLeft - rect.left, y: targetTop - rect.top };
270
+ }
271
+ function distanceFromPointToRect(point, rect) {
272
+ const dx = Math.max(rect.left - point.x, 0, point.x - rect.right);
273
+ const dy = Math.max(rect.top - point.y, 0, point.y - rect.bottom);
274
+ return Math.hypot(dx, dy);
275
+ }
276
+ /**
277
+ * Coordinates anticipatory UI within one interaction scope.
278
+ *
279
+ * The coordinator deliberately owns one pointer listener and one animation-frame
280
+ * update for the whole scope. Anchors remain the only hit-testable controls; the
281
+ * reflected state is presentation-only, so overlapping near regions do not steal
282
+ * pointer input from each other or from surrounding content.
283
+ */
284
+ function createProximityCoordinator(scope, options = {}) {
285
+ const owner = scope.ownerDocument.defaultView ?? window;
286
+ const pointerTarget = options.pointerTarget ?? scope;
287
+ const anchorSelector = options.anchorSelector ?? "[data-ui-affordance]";
288
+ const configuredRadius = options.nearRadius ?? DEFAULT_AFFORDANCE_NEAR_RADIUS;
289
+ const nearRadius = Number.isFinite(configuredRadius)
290
+ ? Math.max(0, configuredRadius)
291
+ : DEFAULT_AFFORDANCE_NEAR_RADIUS;
292
+ const abort = new AbortController();
293
+ let anchors = [];
294
+ let frame = null;
295
+ let point = null;
296
+ let geometryDirty = false;
297
+ const clear = () => {
298
+ for (const { element } of anchors) {
299
+ if (element.hasAttribute("data-ui-proximity")) {
300
+ element.removeAttribute("data-ui-proximity");
301
+ }
302
+ }
303
+ if (scope.hasAttribute("data-ui-interaction")) {
304
+ scope.removeAttribute("data-ui-interaction");
305
+ }
306
+ };
307
+ const measure = () => {
308
+ anchors = Array.from(scope.querySelectorAll(anchorSelector))
309
+ .filter((element) => !element.hasAttribute("disabled") && element.getAttribute("aria-disabled") !== "true")
310
+ .map((element) => ({ element, rect: element.getBoundingClientRect() }));
311
+ geometryDirty = false;
312
+ };
313
+ const update = () => {
314
+ frame = null;
315
+ if (geometryDirty)
316
+ measure();
317
+ if (!point) {
318
+ clear();
319
+ return;
320
+ }
321
+ let engaged = false;
322
+ for (const anchor of anchors) {
323
+ const near = distanceFromPointToRect(point, anchor.rect) <= nearRadius;
324
+ if (near && anchor.element.getAttribute("data-ui-proximity") !== "near") {
325
+ anchor.element.setAttribute("data-ui-proximity", "near");
326
+ }
327
+ else if (!near && anchor.element.hasAttribute("data-ui-proximity")) {
328
+ anchor.element.removeAttribute("data-ui-proximity");
329
+ }
330
+ engaged ||= near;
331
+ }
332
+ if (engaged && scope.getAttribute("data-ui-interaction") !== "engaged") {
333
+ scope.setAttribute("data-ui-interaction", "engaged");
334
+ }
335
+ else if (!engaged && scope.hasAttribute("data-ui-interaction")) {
336
+ scope.removeAttribute("data-ui-interaction");
337
+ }
338
+ };
339
+ const schedule = () => {
340
+ if (frame !== null)
341
+ return;
342
+ frame = owner.requestAnimationFrame(update);
343
+ };
344
+ const onPointerMove = (event) => {
345
+ const pointer = event;
346
+ if (pointer.pointerType === "touch") {
347
+ point = null;
348
+ }
349
+ else {
350
+ point = { x: pointer.clientX, y: pointer.clientY };
351
+ }
352
+ schedule();
353
+ };
354
+ const invalidate = () => {
355
+ geometryDirty = true;
356
+ schedule();
357
+ };
358
+ pointerTarget.addEventListener("pointermove", onPointerMove, {
359
+ passive: true,
360
+ signal: abort.signal,
361
+ });
362
+ owner.addEventListener("resize", invalidate, { passive: true, signal: abort.signal });
363
+ owner.addEventListener("scroll", invalidate, { passive: true, capture: true, signal: abort.signal });
364
+ owner.visualViewport?.addEventListener("resize", invalidate, { passive: true, signal: abort.signal });
365
+ owner.visualViewport?.addEventListener("scroll", invalidate, { passive: true, signal: abort.signal });
366
+ measure();
367
+ return {
368
+ refresh: invalidate,
369
+ destroy() {
370
+ abort.abort();
371
+ if (frame !== null)
372
+ owner.cancelAnimationFrame(frame);
373
+ frame = null;
374
+ point = null;
375
+ clear();
376
+ anchors = [];
377
+ },
378
+ };
379
+ }
380
+
381
+ exports.DEFAULT_AFFORDANCE_NEAR_RADIUS = DEFAULT_AFFORDANCE_NEAR_RADIUS;
382
+ exports.createAnchoredSurface = createAnchoredSurface;
383
+ exports.createProximityCoordinator = createProximityCoordinator;
384
+ exports.createViewportSurface = createViewportSurface;
@@ -1,117 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var index = require('./index-D07f6BOw.js');
4
-
5
- const DEFAULT_AFFORDANCE_NEAR_RADIUS = 16;
6
- function distanceFromPointToRect(point, rect) {
7
- const dx = Math.max(rect.left - point.x, 0, point.x - rect.right);
8
- const dy = Math.max(rect.top - point.y, 0, point.y - rect.bottom);
9
- return Math.hypot(dx, dy);
10
- }
11
- /**
12
- * Coordinates anticipatory UI within one interaction scope.
13
- *
14
- * The coordinator deliberately owns one pointer listener and one animation-frame
15
- * update for the whole scope. Anchors remain the only hit-testable controls; the
16
- * reflected state is presentation-only, so overlapping near regions do not steal
17
- * pointer input from each other or from surrounding content.
18
- */
19
- function createProximityCoordinator(scope, options = {}) {
20
- const owner = scope.ownerDocument.defaultView ?? window;
21
- const pointerTarget = options.pointerTarget ?? scope;
22
- const anchorSelector = options.anchorSelector ?? "[data-ui-affordance]";
23
- const configuredRadius = options.nearRadius ?? DEFAULT_AFFORDANCE_NEAR_RADIUS;
24
- const nearRadius = Number.isFinite(configuredRadius)
25
- ? Math.max(0, configuredRadius)
26
- : DEFAULT_AFFORDANCE_NEAR_RADIUS;
27
- const abort = new AbortController();
28
- let anchors = [];
29
- let frame = null;
30
- let point = null;
31
- let geometryDirty = false;
32
- const clear = () => {
33
- for (const { element } of anchors) {
34
- if (element.hasAttribute("data-ui-proximity")) {
35
- element.removeAttribute("data-ui-proximity");
36
- }
37
- }
38
- if (scope.hasAttribute("data-ui-interaction")) {
39
- scope.removeAttribute("data-ui-interaction");
40
- }
41
- };
42
- const measure = () => {
43
- anchors = Array.from(scope.querySelectorAll(anchorSelector))
44
- .filter((element) => !element.hasAttribute("disabled") && element.getAttribute("aria-disabled") !== "true")
45
- .map((element) => ({ element, rect: element.getBoundingClientRect() }));
46
- geometryDirty = false;
47
- };
48
- const update = () => {
49
- frame = null;
50
- if (geometryDirty)
51
- measure();
52
- if (!point) {
53
- clear();
54
- return;
55
- }
56
- let engaged = false;
57
- for (const anchor of anchors) {
58
- const near = distanceFromPointToRect(point, anchor.rect) <= nearRadius;
59
- if (near && anchor.element.getAttribute("data-ui-proximity") !== "near") {
60
- anchor.element.setAttribute("data-ui-proximity", "near");
61
- }
62
- else if (!near && anchor.element.hasAttribute("data-ui-proximity")) {
63
- anchor.element.removeAttribute("data-ui-proximity");
64
- }
65
- engaged ||= near;
66
- }
67
- if (engaged && scope.getAttribute("data-ui-interaction") !== "engaged") {
68
- scope.setAttribute("data-ui-interaction", "engaged");
69
- }
70
- else if (!engaged && scope.hasAttribute("data-ui-interaction")) {
71
- scope.removeAttribute("data-ui-interaction");
72
- }
73
- };
74
- const schedule = () => {
75
- if (frame !== null)
76
- return;
77
- frame = owner.requestAnimationFrame(update);
78
- };
79
- const onPointerMove = (event) => {
80
- const pointer = event;
81
- if (pointer.pointerType === "touch") {
82
- point = null;
83
- }
84
- else {
85
- point = { x: pointer.clientX, y: pointer.clientY };
86
- }
87
- schedule();
88
- };
89
- const invalidate = () => {
90
- geometryDirty = true;
91
- schedule();
92
- };
93
- pointerTarget.addEventListener("pointermove", onPointerMove, {
94
- passive: true,
95
- signal: abort.signal,
96
- });
97
- owner.addEventListener("resize", invalidate, { passive: true, signal: abort.signal });
98
- owner.addEventListener("scroll", invalidate, { passive: true, capture: true, signal: abort.signal });
99
- owner.visualViewport?.addEventListener("resize", invalidate, { passive: true, signal: abort.signal });
100
- owner.visualViewport?.addEventListener("scroll", invalidate, { passive: true, signal: abort.signal });
101
- measure();
102
- return {
103
- refresh: invalidate,
104
- destroy() {
105
- abort.abort();
106
- if (frame !== null)
107
- owner.cancelAnimationFrame(frame);
108
- frame = null;
109
- point = null;
110
- clear();
111
- anchors = [];
112
- },
113
- };
114
- }
4
+ var positioning = require('./positioning-i0WrCAdF.js');
115
5
 
116
6
  const uiAffordanceScopeCss = () => `:host{display:contents;--ui-affordance-scope-engaged:0}:host([data-ui-interaction='engaged']){--ui-affordance-scope-engaged:1}`;
117
7
 
@@ -121,7 +11,7 @@ const UIAffordanceScope = class {
121
11
  }
122
12
  get host() { return index.getElement(this); }
123
13
  /** Distance outside each registered affordance that reveals its near state. */
124
- nearRadius = DEFAULT_AFFORDANCE_NEAR_RADIUS;
14
+ nearRadius = positioning.DEFAULT_AFFORDANCE_NEAR_RADIUS;
125
15
  coordinator = null;
126
16
  componentDidLoad() {
127
17
  this.connectCoordinator();
@@ -132,7 +22,7 @@ const UIAffordanceScope = class {
132
22
  }
133
23
  connectCoordinator() {
134
24
  this.coordinator?.destroy();
135
- this.coordinator = createProximityCoordinator(this.host, {
25
+ this.coordinator = positioning.createProximityCoordinator(this.host, {
136
26
  nearRadius: this.nearRadius,
137
27
  });
138
28
  }
@@ -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 uiContextMenuCss = () => `:host{display:contents}.menu{position:fixed;z-index:var(--ui-z-overlay, 1000);min-width:12rem}.menu[data-open]{display:block}.menu:not([data-open]){display:none !important}`;
8
+ const uiContextMenuCss = () => `:host{display:contents}:host([data-focus-trigger='pointer']) ::slotted(ui-menu-item:focus){outline:none}.menu{position:fixed;inset:auto;margin:0;z-index:var(--ui-z-overlay, 1000);min-width:12rem;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}.menu:popover-open{opacity:1;transform:translateY(0) scale(1)}@starting-style{.menu:popover-open{opacity:0;transform:translateY(-4px) scale(0.99)}}.menu[hidden]{display:none !important}@media (prefers-reduced-motion: reduce){.menu{transition:none}}`;
8
9
 
9
10
  const UIContextMenu = class {
10
11
  constructor(hostRef) {
@@ -18,11 +19,14 @@ const UIContextMenu = class {
18
19
  /** Optional id of the region that should also respond to a context-menu gesture. */
19
20
  for;
20
21
  internalOpen = false;
22
+ focusTrigger = 'programmatic';
21
23
  overlayId = `ui-context-menu-${Math.random().toString(36).slice(2, 11)}`;
22
24
  trigger = null;
23
25
  contextTarget = null;
24
26
  pointerX = 0;
25
27
  pointerY = 0;
28
+ pointPositioned = false;
29
+ surface = null;
26
30
  syncFromProp() {
27
31
  this.internalOpen = this.open;
28
32
  }
@@ -33,6 +37,12 @@ const UIContextMenu = class {
33
37
  syncOverlay() {
34
38
  this.syncTriggerState();
35
39
  if (this.internalOpen) {
40
+ if (this.pointPositioned) {
41
+ this.surface?.showAtPoint({ x: this.pointerX, y: this.pointerY });
42
+ }
43
+ else {
44
+ this.surface?.show();
45
+ }
36
46
  manager.openOverlay({
37
47
  id: this.overlayId,
38
48
  modal: false,
@@ -42,6 +52,7 @@ const UIContextMenu = class {
42
52
  });
43
53
  }
44
54
  else {
55
+ this.surface?.hide();
45
56
  manager.closeOverlay(this.overlayId);
46
57
  }
47
58
  }
@@ -50,18 +61,33 @@ const UIContextMenu = class {
50
61
  this.internalOpen = this.internalOpen || this.defaultOpen;
51
62
  this.resolveTargets();
52
63
  this.attachTargetListeners();
64
+ this.setupSurface();
53
65
  this.syncTriggerState();
54
66
  this.syncOverlay();
55
67
  }
56
68
  componentDidUpdate() {
57
69
  this.resolveTargets();
58
70
  this.attachTargetListeners();
71
+ if (!this.pointPositioned)
72
+ this.surface?.setAnchor(this.trigger);
59
73
  this.syncTriggerState();
60
74
  }
61
75
  disconnectedCallback() {
62
76
  this.detachTargetListeners();
77
+ this.surface?.destroy();
78
+ this.surface = null;
63
79
  manager.closeOverlay(this.overlayId);
64
80
  }
81
+ setupSurface() {
82
+ const menu = this.host.shadowRoot?.querySelector('.menu');
83
+ if (!menu)
84
+ return;
85
+ this.surface?.destroy();
86
+ this.surface = positioning.createAnchoredSurface(menu, {
87
+ anchor: this.trigger,
88
+ placement: 'bottom-start',
89
+ });
90
+ }
65
91
  resolveTargets() {
66
92
  const previousTrigger = this.trigger;
67
93
  const previousContextTarget = this.contextTarget;
@@ -101,6 +127,11 @@ const UIContextMenu = class {
101
127
  openMenu(x, y, trigger) {
102
128
  this.pointerX = x;
103
129
  this.pointerY = y;
130
+ this.focusTrigger = trigger;
131
+ this.pointPositioned = true;
132
+ // Context-click can reposition an already-open trigger menu, so do not
133
+ // depend on a state transition to run the positioning controller.
134
+ this.surface?.showAtPoint({ x, y });
104
135
  this.internalOpen = true;
105
136
  events.dispatchDetail(this.host, 'open', {
106
137
  open: true,
@@ -112,8 +143,14 @@ const UIContextMenu = class {
112
143
  openFromTrigger(trigger) {
113
144
  if (!this.trigger)
114
145
  return;
115
- const rect = this.trigger.getBoundingClientRect();
116
- this.openMenu(rect.left, rect.bottom, trigger);
146
+ this.pointPositioned = false;
147
+ this.internalOpen = true;
148
+ events.dispatchDetail(this.host, 'open', {
149
+ open: true,
150
+ reason: 'action',
151
+ trigger,
152
+ });
153
+ this.focusFirstItem();
117
154
  }
118
155
  closeMenu(reason, trigger, returnFocus = true) {
119
156
  if (!this.internalOpen)
@@ -158,6 +195,7 @@ const UIContextMenu = class {
158
195
  onKeydown = (event) => {
159
196
  if (!this.internalOpen)
160
197
  return;
198
+ this.focusTrigger = 'keyboard';
161
199
  if (event.key === 'Escape') {
162
200
  event.preventDefault();
163
201
  manager.requestTopOverlayClose('escape', 'keyboard');
@@ -182,7 +220,7 @@ const UIContextMenu = class {
182
220
  this.closeMenu('action', 'pointer');
183
221
  };
184
222
  render() {
185
- return (index.h(index.Host, { key: '79fbca2c89f1d1fd1f4796ad88d2a599e811a3f9', "data-open": this.internalOpen ? '' : undefined, onKeyDown: this.onKeydown, onClick: this.onItemClick }, index.h("slot", { key: '9308cadfa155930e3e46dfb4a69a847ae388958b', name: "trigger" }), index.h("div", { key: '16cfba2b32444ac9e3cac1f040f81f17fd5db23c', class: "menu", "data-open": this.internalOpen ? '' : undefined, style: { left: `${this.pointerX}px`, top: `${this.pointerY}px` } }, index.h("ui-menu", { key: '59f18e0df3bb1fe85efa8eeca00b336bbde47c70', open: this.internalOpen }, index.h("slot", { key: '5bdbb7773c9c1ae43ec2e05fca9558482815e28a' })))));
223
+ return (index.h(index.Host, { key: '4db0d5b55ead41925aa682b1e383b5735b49bf56', "data-open": this.internalOpen ? '' : undefined, "data-focus-trigger": this.internalOpen ? this.focusTrigger : undefined, onKeyDown: this.onKeydown, onClick: this.onItemClick }, index.h("slot", { key: '462925d53db169c13e485789ae91e28b0abd3e40', name: "trigger" }), index.h("div", { key: 'cac8f3b11c4872d38e976f210ce9a43fd854b335', class: "menu", "data-open": this.internalOpen ? '' : undefined }, index.h("ui-menu", { key: 'ba50f74e11466da2240f7568479d5d10a614b394', open: this.internalOpen }, index.h("slot", { key: '1fcb45bf124e2377ac6c7fc236d2babf0d9e3bae' })))));
186
224
  }
187
225
  static get watchers() { return {
188
226
  "open": [{
@@ -1,7 +1,7 @@
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
6
 
7
7
  const uiDialogCss = () => `:host{display:block}:host:not([data-open]){display:none}:host dialog{--ui-dialog-duration:var(--ui-motion-base);--ui-dialog-viewport-gap:var(--ui-space-4, 1rem);position:fixed;inset:0;inline-size:min( calc(100% - (var(--ui-dialog-viewport-gap) * 2)), var(--ui-dialog-max-width, 440px) );max-inline-size:none;max-block-size:calc(100dvh - (var(--ui-dialog-viewport-gap) * 2));margin:auto;padding:var(--ui-space-4);overflow:auto;background:var(--ui-surface-elevated);border:1px solid var(--ui-border-default);border-radius:var(--ui-radius-dialog);box-shadow:var(--ui-shadow-dialog);visibility:hidden;pointer-events:none;opacity:0;transform:scale(0.96);transition:opacity var(--ui-dialog-duration) var(--ui-motion-ease), transform var(--ui-dialog-duration) var(--ui-motion-ease), visibility var(--ui-dialog-duration), display var(--ui-dialog-duration) var(--ui-motion-ease) allow-discrete, overlay var(--ui-dialog-duration) var(--ui-motion-ease) allow-discrete}:host dialog[open]{visibility:visible;pointer-events:auto;opacity:1;transform:scale(1);animation:ui-dialog-enter var(--ui-dialog-duration) var(--ui-motion-ease)}@keyframes ui-dialog-enter{from{opacity:0;transform:scale(0.96)}}:host dialog::backdrop{background-color:transparent;transition:background-color var(--ui-dialog-duration) var(--ui-motion-ease), display var(--ui-dialog-duration) var(--ui-motion-ease) allow-discrete, overlay var(--ui-dialog-duration) var(--ui-motion-ease) allow-discrete}:host dialog[open]::backdrop{background-color:var(--ui-overlay-backdrop);animation:ui-dialog-backdrop-enter var(--ui-dialog-duration) var(--ui-motion-ease)}@keyframes ui-dialog-backdrop-enter{from{background-color:transparent}}`;