@radix-ng/primitives 1.0.0-beta.3 → 1.0.0-beta.5

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 (118) hide show
  1. package/README.md +1 -1
  2. package/fesm2022/radix-ng-primitives-accordion.mjs +5 -3
  3. package/fesm2022/radix-ng-primitives-accordion.mjs.map +1 -1
  4. package/fesm2022/radix-ng-primitives-alert-dialog.mjs +3 -2
  5. package/fesm2022/radix-ng-primitives-alert-dialog.mjs.map +1 -1
  6. package/fesm2022/radix-ng-primitives-autocomplete.mjs +617 -659
  7. package/fesm2022/radix-ng-primitives-autocomplete.mjs.map +1 -1
  8. package/fesm2022/radix-ng-primitives-calendar.mjs +5 -3
  9. package/fesm2022/radix-ng-primitives-calendar.mjs.map +1 -1
  10. package/fesm2022/radix-ng-primitives-checkbox.mjs +33 -18
  11. package/fesm2022/radix-ng-primitives-checkbox.mjs.map +1 -1
  12. package/fesm2022/radix-ng-primitives-combobox.mjs +1305 -572
  13. package/fesm2022/radix-ng-primitives-combobox.mjs.map +1 -1
  14. package/fesm2022/radix-ng-primitives-config.mjs +13 -4
  15. package/fesm2022/radix-ng-primitives-config.mjs.map +1 -1
  16. package/fesm2022/radix-ng-primitives-context-menu.mjs +51 -10
  17. package/fesm2022/radix-ng-primitives-context-menu.mjs.map +1 -1
  18. package/fesm2022/radix-ng-primitives-core.mjs +1352 -64
  19. package/fesm2022/radix-ng-primitives-core.mjs.map +1 -1
  20. package/fesm2022/radix-ng-primitives-date-field.mjs +5 -3
  21. package/fesm2022/radix-ng-primitives-date-field.mjs.map +1 -1
  22. package/fesm2022/radix-ng-primitives-dialog.mjs +290 -120
  23. package/fesm2022/radix-ng-primitives-dialog.mjs.map +1 -1
  24. package/fesm2022/radix-ng-primitives-direction-provider.mjs +70 -0
  25. package/fesm2022/radix-ng-primitives-direction-provider.mjs.map +1 -0
  26. package/fesm2022/radix-ng-primitives-dismissable-layer.mjs +519 -184
  27. package/fesm2022/radix-ng-primitives-dismissable-layer.mjs.map +1 -1
  28. package/fesm2022/radix-ng-primitives-drawer.mjs +3 -3
  29. package/fesm2022/radix-ng-primitives-drawer.mjs.map +1 -1
  30. package/fesm2022/radix-ng-primitives-editable.mjs +12 -7
  31. package/fesm2022/radix-ng-primitives-editable.mjs.map +1 -1
  32. package/fesm2022/radix-ng-primitives-field.mjs +3 -2
  33. package/fesm2022/radix-ng-primitives-field.mjs.map +1 -1
  34. package/fesm2022/radix-ng-primitives-floating-focus-manager.mjs +803 -0
  35. package/fesm2022/radix-ng-primitives-floating-focus-manager.mjs.map +1 -0
  36. package/fesm2022/radix-ng-primitives-focus-scope.mjs +305 -70
  37. package/fesm2022/radix-ng-primitives-focus-scope.mjs.map +1 -1
  38. package/fesm2022/radix-ng-primitives-menu.mjs +893 -289
  39. package/fesm2022/radix-ng-primitives-menu.mjs.map +1 -1
  40. package/fesm2022/radix-ng-primitives-menubar.mjs +32 -4
  41. package/fesm2022/radix-ng-primitives-menubar.mjs.map +1 -1
  42. package/fesm2022/radix-ng-primitives-navigation-menu.mjs +144 -159
  43. package/fesm2022/radix-ng-primitives-navigation-menu.mjs.map +1 -1
  44. package/fesm2022/radix-ng-primitives-number-field.mjs +7 -2
  45. package/fesm2022/radix-ng-primitives-number-field.mjs.map +1 -1
  46. package/fesm2022/radix-ng-primitives-popover.mjs +284 -212
  47. package/fesm2022/radix-ng-primitives-popover.mjs.map +1 -1
  48. package/fesm2022/radix-ng-primitives-popper.mjs +94 -51
  49. package/fesm2022/radix-ng-primitives-popper.mjs.map +1 -1
  50. package/fesm2022/radix-ng-primitives-presence.mjs +1 -1
  51. package/fesm2022/radix-ng-primitives-presence.mjs.map +1 -1
  52. package/fesm2022/radix-ng-primitives-preview-card.mjs +141 -173
  53. package/fesm2022/radix-ng-primitives-preview-card.mjs.map +1 -1
  54. package/fesm2022/radix-ng-primitives-radio.mjs +19 -14
  55. package/fesm2022/radix-ng-primitives-radio.mjs.map +1 -1
  56. package/fesm2022/radix-ng-primitives-roving-focus.mjs +4 -2
  57. package/fesm2022/radix-ng-primitives-roving-focus.mjs.map +1 -1
  58. package/fesm2022/radix-ng-primitives-scroll-area.mjs +5 -4
  59. package/fesm2022/radix-ng-primitives-scroll-area.mjs.map +1 -1
  60. package/fesm2022/radix-ng-primitives-select.mjs +241 -164
  61. package/fesm2022/radix-ng-primitives-select.mjs.map +1 -1
  62. package/fesm2022/radix-ng-primitives-slider.mjs +262 -29
  63. package/fesm2022/radix-ng-primitives-slider.mjs.map +1 -1
  64. package/fesm2022/radix-ng-primitives-stepper.mjs +16 -10
  65. package/fesm2022/radix-ng-primitives-stepper.mjs.map +1 -1
  66. package/fesm2022/radix-ng-primitives-switch.mjs +10 -5
  67. package/fesm2022/radix-ng-primitives-switch.mjs.map +1 -1
  68. package/fesm2022/radix-ng-primitives-tabs.mjs +15 -10
  69. package/fesm2022/radix-ng-primitives-tabs.mjs.map +1 -1
  70. package/fesm2022/radix-ng-primitives-time-field.mjs +5 -3
  71. package/fesm2022/radix-ng-primitives-time-field.mjs.map +1 -1
  72. package/fesm2022/radix-ng-primitives-toast.mjs +15 -36
  73. package/fesm2022/radix-ng-primitives-toast.mjs.map +1 -1
  74. package/fesm2022/radix-ng-primitives-toggle-group.mjs +14 -7
  75. package/fesm2022/radix-ng-primitives-toggle-group.mjs.map +1 -1
  76. package/fesm2022/radix-ng-primitives-toggle.mjs +12 -6
  77. package/fesm2022/radix-ng-primitives-toggle.mjs.map +1 -1
  78. package/fesm2022/radix-ng-primitives-toolbar.mjs +5 -3
  79. package/fesm2022/radix-ng-primitives-toolbar.mjs.map +1 -1
  80. package/fesm2022/radix-ng-primitives-tooltip.mjs +251 -143
  81. package/fesm2022/radix-ng-primitives-tooltip.mjs.map +1 -1
  82. package/package.json +10 -1
  83. package/types/radix-ng-primitives-accordion.d.ts +4 -3
  84. package/types/radix-ng-primitives-autocomplete.d.ts +217 -152
  85. package/types/radix-ng-primitives-calendar.d.ts +5 -3
  86. package/types/radix-ng-primitives-checkbox.d.ts +27 -15
  87. package/types/radix-ng-primitives-combobox.d.ts +672 -283
  88. package/types/radix-ng-primitives-config.d.ts +1 -1
  89. package/types/radix-ng-primitives-context-menu.d.ts +15 -5
  90. package/types/radix-ng-primitives-core.d.ts +764 -14
  91. package/types/radix-ng-primitives-date-field.d.ts +3 -2
  92. package/types/radix-ng-primitives-dialog.d.ts +88 -32
  93. package/types/radix-ng-primitives-direction-provider.d.ts +41 -0
  94. package/types/radix-ng-primitives-dismissable-layer.d.ts +147 -99
  95. package/types/radix-ng-primitives-editable.d.ts +11 -5
  96. package/types/radix-ng-primitives-field.d.ts +1 -0
  97. package/types/radix-ng-primitives-floating-focus-manager.d.ts +272 -0
  98. package/types/radix-ng-primitives-focus-scope.d.ts +132 -1
  99. package/types/radix-ng-primitives-menu.d.ts +192 -103
  100. package/types/radix-ng-primitives-navigation-menu.d.ts +37 -75
  101. package/types/radix-ng-primitives-number-field.d.ts +8 -3
  102. package/types/radix-ng-primitives-popover.d.ts +71 -92
  103. package/types/radix-ng-primitives-popper.d.ts +39 -9
  104. package/types/radix-ng-primitives-preview-card.d.ts +39 -72
  105. package/types/radix-ng-primitives-radio.d.ts +13 -6
  106. package/types/radix-ng-primitives-roving-focus.d.ts +7 -6
  107. package/types/radix-ng-primitives-scroll-area.d.ts +2 -2
  108. package/types/radix-ng-primitives-select.d.ts +142 -109
  109. package/types/radix-ng-primitives-slider.d.ts +64 -12
  110. package/types/radix-ng-primitives-stepper.d.ts +15 -7
  111. package/types/radix-ng-primitives-switch.d.ts +10 -4
  112. package/types/radix-ng-primitives-tabs.d.ts +12 -6
  113. package/types/radix-ng-primitives-time-field.d.ts +3 -2
  114. package/types/radix-ng-primitives-toast.d.ts +7 -7
  115. package/types/radix-ng-primitives-toggle-group.d.ts +15 -8
  116. package/types/radix-ng-primitives-toggle.d.ts +10 -3
  117. package/types/radix-ng-primitives-toolbar.d.ts +3 -2
  118. package/types/radix-ng-primitives-tooltip.d.ts +61 -80
@@ -1,15 +1,15 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, inject, signal, numberAttribute, DestroyRef, input, Directive, model, booleanAttribute, output, computed, effect, untracked, isDevMode, ElementRef, afterNextRender } from '@angular/core';
3
- import { createContext, injectId, watch, useGraceArea } from '@radix-ng/primitives/core';
2
+ import { InjectionToken, inject, signal, numberAttribute, DestroyRef, input, Directive, ElementRef, model, booleanAttribute, output, computed, effect, untracked, isDevMode, afterNextRender } from '@angular/core';
3
+ import * as i1$2 from '@radix-ng/primitives/core';
4
+ import { createContext, createFloatingRootContext, injectId, watch, createCancelableChangeEventDetails, provideFloatingTree, provideFloatingRootContext, rdxDevError, RDX_FLOATING_ROOT_CONTEXT, RDX_FLOATING_REGISTRATION, useGraceArea, RdxFloatingNodeRegistration } from '@radix-ng/primitives/core';
4
5
  import * as i1 from '@radix-ng/primitives/popper';
5
- import { RdxPopper, RdxPopperContentWrapper, RdxPopperArrow, RdxPopperContent, provideRdxPopperContentConfig, RdxPopperAnchor } from '@radix-ng/primitives/popper';
6
+ import { RdxPopper, RdxPopperContentWrapper, RdxPopperArrow, RdxPopperContent, provideRdxPopperContentWrapper, provideRdxPopperContentConfig, RdxPopperAnchor } from '@radix-ng/primitives/popper';
6
7
  import { RdxVisuallyHiddenDirective } from '@radix-ng/primitives/visually-hidden';
7
8
  import * as i1$1 from '@radix-ng/primitives/portal';
8
9
  import { RdxPortalPresence } from '@radix-ng/primitives/portal';
9
10
  import { provideRdxPresenceContext } from '@radix-ng/primitives/presence';
10
- import { outputFromObservable, outputToObservable } from '@angular/core/rxjs-interop';
11
- import * as i1$2 from '@radix-ng/primitives/dismissable-layer';
12
- import { RdxDismissableLayer } from '@radix-ng/primitives/dismissable-layer';
11
+ import { RdxDismiss } from '@radix-ng/primitives/dismissable-layer';
12
+ import { createRdxTriggerInteraction } from '@radix-ng/primitives/floating-focus-manager';
13
13
 
14
14
  const defaultTooltipConfig = {
15
15
  delay: 600,
@@ -133,6 +133,15 @@ class RdxTooltip {
133
133
  this.popper = inject(RdxPopper);
134
134
  this.destroyRef = inject(DestroyRef);
135
135
  this.hasAppliedDefaultOpen = false;
136
+ /**
137
+ * Per-popup floating root context (ADR 0015) — the shared store the positioner's dismissal
138
+ * capability reads (`open`, `triggers`, the reference/floating elements). The tree node is
139
+ * registered by the positioner; this context exists independently so dismissal can read `open()`.
140
+ */
141
+ this.floatingContext = createFloatingRootContext({
142
+ ownerDocument: inject(ElementRef).nativeElement.ownerDocument,
143
+ open: () => this.open()
144
+ });
136
145
  /**
137
146
  * Whether the tooltip is currently open.
138
147
  */
@@ -176,7 +185,10 @@ class RdxTooltip {
176
185
  this.triggers = signal([], ...(ngDevMode ? [{ debugName: "triggers" }] : /* istanbul ignore next */ []));
177
186
  this.payload = signal(undefined, ...(ngDevMode ? [{ debugName: "payload" }] : /* istanbul ignore next */ []));
178
187
  this.cursorPosition = signal(undefined, ...(ngDevMode ? [{ debugName: "cursorPosition" }] : /* istanbul ignore next */ []));
188
+ this.openChangeReason = signal('none', ...(ngDevMode ? [{ debugName: "openChangeReason" }] : /* istanbul ignore next */ []));
189
+ this.preventUnmountOnClose = signal(false, ...(ngDevMode ? [{ debugName: "preventUnmountOnClose" }] : /* istanbul ignore next */ []));
179
190
  this.openedInstant = signal(false, ...(ngDevMode ? [{ debugName: "openedInstant" }] : /* istanbul ignore next */ []));
191
+ this.suppressNextOpenChangeEmit = false;
180
192
  /** Local instant window used when this tooltip is not inside a provider. */
181
193
  this.localInstant = createTooltipInstantController(() => this.defaultConfig.timeout, this.destroyRef);
182
194
  this.instantGroup = this.provider ?? this.localInstant;
@@ -190,6 +202,7 @@ class RdxTooltip {
190
202
  this.defaultConfig.closeDelay, ...(ngDevMode ? [{ debugName: "resolvedCloseDelay" }] : /* istanbul ignore next */ []));
191
203
  /** Whether the most recent open happened without the delay. */
192
204
  this.instant = this.openedInstant.asReadonly();
205
+ this.present = computed(() => this.open() || this.preventUnmountOnClose(), ...(ngDevMode ? [{ debugName: "present" }] : /* istanbul ignore next */ []));
193
206
  this.virtualAnchor = computed(() => {
194
207
  const axis = this.trackCursorAxis();
195
208
  const element = this.trigger();
@@ -213,7 +226,7 @@ class RdxTooltip {
213
226
  }
214
227
  };
215
228
  }, ...(ngDevMode ? [{ debugName: "virtualAnchor" }] : /* istanbul ignore next */ []));
216
- this.openTimer = useTimeoutFn(() => this.applyOpen(false), () => this.resolvedDelay(), { immediate: false }, this.destroyRef);
229
+ this.openTimer = useTimeoutFn(() => this.applyOpen(false, this.trigger(), this.payload(), 'trigger-hover'), () => this.resolvedDelay(), { immediate: false }, this.destroyRef);
217
230
  this.closeTimer = useTimeoutFn(() => this.applyClose(), () => this.resolvedCloseDelay(), { immediate: false }, this.destroyRef);
218
231
  effect(() => {
219
232
  const defaultOpen = this.defaultOpen();
@@ -230,9 +243,19 @@ class RdxTooltip {
230
243
  });
231
244
  // Keep the popper anchored to the active trigger, or to the cursor while tracking.
232
245
  effect(() => this.popper.anchorOverride.set(this.virtualAnchor()));
246
+ // Sync the dismissal reference (the active trigger) so an outside-press on the trigger counts
247
+ // as "inside" and never dismisses (ADR 0015).
248
+ effect(() => this.floatingContext.setReferenceElement(this.trigger() ?? null));
233
249
  watch([this.open], ([isOpen]) => {
234
- this.onOpenChange.emit(isOpen);
250
+ if (this.suppressNextOpenChangeEmit) {
251
+ this.suppressNextOpenChangeEmit = false;
252
+ }
253
+ else {
254
+ const { eventDetails } = createCancelableChangeEventDetails(this.openChangeReason(), new Event('tooltip.open-change'), this.trigger());
255
+ this.onOpenChange.emit({ open: isOpen, eventDetails });
256
+ }
235
257
  if (isOpen) {
258
+ this.preventUnmountOnClose.set(false);
236
259
  this.instantGroup.onOpen();
237
260
  }
238
261
  else {
@@ -242,26 +265,36 @@ class RdxTooltip {
242
265
  }, { defer: true });
243
266
  }
244
267
  /** Opens immediately, optionally switching the active trigger/payload. */
245
- show(trigger = this.trigger(), payload) {
246
- this.applyOpen(true, trigger, payload);
268
+ show(trigger = this.trigger(), payload, event) {
269
+ this.applyOpen(true, trigger, payload, 'trigger-focus', event);
247
270
  }
248
- close() {
271
+ close(reason = 'none', event) {
272
+ this.openTimer.stop();
273
+ this.closeTimer.stop();
274
+ this.applyClose(reason, event);
275
+ }
276
+ cancelPendingOpen() {
277
+ this.openTimer.stop();
278
+ }
279
+ closeHoverOpen(event) {
249
280
  this.openTimer.stop();
250
281
  this.closeTimer.stop();
251
- this.applyClose();
282
+ if (this.open() && this.openChangeReason() === 'trigger-hover') {
283
+ this.applyClose('trigger-hover', event);
284
+ }
252
285
  }
253
286
  /** Closes after the resolved close delay, e.g. when the pointer or focus leaves. */
254
- scheduleClose() {
287
+ scheduleClose(event) {
255
288
  this.openTimer.stop();
256
289
  if (this.resolvedCloseDelay() <= 0) {
257
- this.applyClose();
290
+ this.applyClose('trigger-hover', event);
258
291
  }
259
292
  else {
260
293
  this.closeTimer.start();
261
294
  }
262
295
  }
263
296
  /** Hover/focus entered — open after the delay, or instantly within the instant window. */
264
- onTriggerEnter(trigger = this.trigger(), payload) {
297
+ onTriggerEnter(trigger = this.trigger(), payload, event) {
265
298
  if (this.disabled()) {
266
299
  return;
267
300
  }
@@ -271,7 +304,7 @@ class RdxTooltip {
271
304
  this.payload.set(payload);
272
305
  this.closeTimer.stop();
273
306
  if (this.instantGroup.isInstant() || this.resolvedDelay() <= 0) {
274
- this.applyOpen(true, trigger, payload);
307
+ this.applyOpen(true, trigger, payload, 'trigger-hover', event);
275
308
  }
276
309
  else {
277
310
  this.openTimer.start();
@@ -286,16 +319,18 @@ class RdxTooltip {
286
319
  }
287
320
  registerTrigger(trigger) {
288
321
  this.triggers.update((triggers) => (triggers.includes(trigger) ? triggers : [...triggers, trigger]));
322
+ this.floatingContext.triggers.add(trigger);
289
323
  if (!this.trigger()) {
290
324
  this.trigger.set(trigger);
291
325
  }
292
326
  return () => {
293
327
  this.triggers.update((triggers) => triggers.filter((candidate) => candidate !== trigger));
328
+ this.floatingContext.triggers.delete(trigger);
294
329
  if (this.trigger() === trigger) {
295
330
  const nextTrigger = this.triggers()[0];
296
331
  this.trigger.set(nextTrigger);
297
332
  if (!nextTrigger && !this.destroyRef.destroyed) {
298
- this.applyClose();
333
+ this.applyClose('none');
299
334
  }
300
335
  }
301
336
  };
@@ -308,12 +343,21 @@ class RdxTooltip {
308
343
  this.triggerDelay.set(delay);
309
344
  this.triggerCloseDelay.set(closeDelay);
310
345
  }
311
- applyOpen(instant, trigger = this.trigger(), payload) {
346
+ applyOpen(instant, trigger = this.trigger(), payload, reason = 'none', event) {
312
347
  if (this.disabled()) {
313
348
  return;
314
349
  }
350
+ const wasOpen = this.open();
315
351
  this.openTimer.stop();
316
352
  this.closeTimer.stop();
353
+ if (!wasOpen) {
354
+ const { eventDetails } = createCancelableChangeEventDetails(reason, event ?? new Event('tooltip.open-change'), trigger);
355
+ this.onOpenChange.emit({ open: true, eventDetails });
356
+ if (eventDetails.isCanceled()) {
357
+ return;
358
+ }
359
+ this.suppressNextOpenChangeEmit = true;
360
+ }
317
361
  if (trigger) {
318
362
  this.trigger.set(trigger);
319
363
  }
@@ -321,21 +365,43 @@ class RdxTooltip {
321
365
  this.payload.set(payload);
322
366
  }
323
367
  this.openedInstant.set(instant || this.instantGroup.isInstant());
368
+ this.preventUnmountOnClose.set(false);
369
+ this.openChangeReason.set(reason);
324
370
  this.open.set(true);
325
371
  }
326
- applyClose() {
372
+ applyClose(reason = 'none', event) {
373
+ if (!this.open()) {
374
+ return;
375
+ }
376
+ const change = createCancelableChangeEventDetails(reason, event ?? new Event('tooltip.open-change'), this.trigger());
377
+ const { eventDetails } = change;
378
+ this.onOpenChange.emit({ open: false, eventDetails });
379
+ if (eventDetails.isCanceled()) {
380
+ return;
381
+ }
382
+ this.suppressNextOpenChangeEmit = true;
383
+ this.preventUnmountOnClose.set(change.shouldPreventUnmountOnClose());
327
384
  this.openedInstant.set(this.instantGroup.isInstant());
385
+ this.openChangeReason.set(reason);
328
386
  this.open.set(false);
329
387
  }
330
388
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxTooltip, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
331
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxTooltip, isStandalone: true, selector: "[rdxTooltip]", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, defaultOpen: { classPropertyName: "defaultOpen", publicName: "defaultOpen", isSignal: true, isRequired: false, transformFunction: null }, delay: { classPropertyName: "delay", publicName: "delay", isSignal: true, isRequired: false, transformFunction: null }, closeDelay: { classPropertyName: "closeDelay", publicName: "closeDelay", isSignal: true, isRequired: false, transformFunction: null }, disableHoverablePopup: { classPropertyName: "disableHoverablePopup", publicName: "disableHoverablePopup", isSignal: true, isRequired: false, transformFunction: null }, trackCursorAxis: { classPropertyName: "trackCursorAxis", publicName: "trackCursorAxis", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, handle: { classPropertyName: "handle", publicName: "handle", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", onOpenChange: "onOpenChange" }, providers: [provideRdxTooltipContext(context)], exportAs: ["rdxTooltip"], hostDirectives: [{ directive: i1.RdxPopper }], ngImport: i0 }); }
389
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxTooltip, isStandalone: true, selector: "[rdxTooltip]", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, defaultOpen: { classPropertyName: "defaultOpen", publicName: "defaultOpen", isSignal: true, isRequired: false, transformFunction: null }, delay: { classPropertyName: "delay", publicName: "delay", isSignal: true, isRequired: false, transformFunction: null }, closeDelay: { classPropertyName: "closeDelay", publicName: "closeDelay", isSignal: true, isRequired: false, transformFunction: null }, disableHoverablePopup: { classPropertyName: "disableHoverablePopup", publicName: "disableHoverablePopup", isSignal: true, isRequired: false, transformFunction: null }, trackCursorAxis: { classPropertyName: "trackCursorAxis", publicName: "trackCursorAxis", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, handle: { classPropertyName: "handle", publicName: "handle", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", onOpenChange: "onOpenChange" }, providers: [
390
+ provideRdxTooltipContext(context),
391
+ provideFloatingTree(),
392
+ provideFloatingRootContext(() => inject(RdxTooltip).floatingContext)
393
+ ], exportAs: ["rdxTooltip"], hostDirectives: [{ directive: i1.RdxPopper }], ngImport: i0 }); }
332
394
  }
333
395
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxTooltip, decorators: [{
334
396
  type: Directive,
335
397
  args: [{
336
398
  selector: '[rdxTooltip]',
337
399
  exportAs: 'rdxTooltip',
338
- providers: [provideRdxTooltipContext(context)],
400
+ providers: [
401
+ provideRdxTooltipContext(context),
402
+ provideFloatingTree(),
403
+ provideFloatingRootContext(() => inject(RdxTooltip).floatingContext)
404
+ ],
339
405
  hostDirectives: [RdxPopper]
340
406
  }]
341
407
  }], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], defaultOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultOpen", required: false }] }], delay: [{ type: i0.Input, args: [{ isSignal: true, alias: "delay", required: false }] }], closeDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeDelay", required: false }] }], disableHoverablePopup: [{ type: i0.Input, args: [{ isSignal: true, alias: "disableHoverablePopup", required: false }] }], trackCursorAxis: [{ type: i0.Input, args: [{ isSignal: true, alias: "trackCursorAxis", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], handle: [{ type: i0.Input, args: [{ isSignal: true, alias: "handle", required: false }] }], onOpenChange: [{ type: i0.Output, args: ["onOpenChange"] }] } });
@@ -343,6 +409,7 @@ function contextFor(root) {
343
409
  return {
344
410
  contentId: root.contentId,
345
411
  isOpen: root.open,
412
+ present: root.present,
346
413
  instant: root.instant,
347
414
  disabled: root.disabled,
348
415
  disableHoverablePopup: root.disableHoverablePopup,
@@ -350,11 +417,14 @@ function contextFor(root) {
350
417
  trigger: root.trigger.asReadonly(),
351
418
  triggers: root.triggers.asReadonly(),
352
419
  payload: root.payload.asReadonly(),
353
- open: (trigger, payload) => root.show(trigger, payload),
354
- close: () => root.close(),
355
- closeDelayed: () => root.scheduleClose(),
420
+ openChangeReason: root.openChangeReason.asReadonly(),
421
+ open: (trigger, payload, event) => root.show(trigger, payload, event),
422
+ close: (reason, event) => root.close(reason, event),
423
+ cancelPendingOpen: () => root.cancelPendingOpen(),
424
+ closeHoverOpen: (event) => root.closeHoverOpen(event),
425
+ closeDelayed: (event) => root.scheduleClose(event),
356
426
  registerTrigger: (trigger) => root.registerTrigger(trigger),
357
- onTriggerEnter: (trigger, payload) => root.onTriggerEnter(trigger, payload),
427
+ onTriggerEnter: (trigger, payload, event) => root.onTriggerEnter(trigger, payload, event),
358
428
  onTriggerLeave: () => root.onTriggerLeave(),
359
429
  setCursorPosition: (position) => root.setCursorPosition(position),
360
430
  setDelays: (delay, closeDelay) => root.setDelays(delay, closeDelay)
@@ -431,7 +501,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
431
501
  */
432
502
  class RdxTooltipPortal {
433
503
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxTooltipPortal, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
434
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxTooltipPortal, isStandalone: true, selector: "ng-template[rdxTooltipPortal]", providers: [provideRdxPresenceContext(() => ({ present: injectRdxTooltipContext().isOpen }))], exportAs: ["rdxTooltipPortal"], hostDirectives: [{ directive: i1$1.RdxPortalPresence, inputs: ["container", "container"] }], ngImport: i0 }); }
504
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxTooltipPortal, isStandalone: true, selector: "ng-template[rdxTooltipPortal]", providers: [provideRdxPresenceContext(() => ({ present: injectRdxTooltipContext().present }))], exportAs: ["rdxTooltipPortal"], hostDirectives: [{ directive: i1$1.RdxPortalPresence, inputs: ["container", "container"] }], ngImport: i0 }); }
435
505
  }
436
506
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxTooltipPortal, decorators: [{
437
507
  type: Directive,
@@ -439,7 +509,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
439
509
  selector: 'ng-template[rdxTooltipPortal]',
440
510
  exportAs: 'rdxTooltipPortal',
441
511
  hostDirectives: [{ directive: RdxPortalPresence, inputs: ['container'] }],
442
- providers: [provideRdxPresenceContext(() => ({ present: injectRdxTooltipContext().isOpen }))]
512
+ providers: [provideRdxPresenceContext(() => ({ present: injectRdxTooltipContext().present }))]
443
513
  }]
444
514
  }] });
445
515
  /**
@@ -450,9 +520,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
450
520
  class RdxTooltipPortalMisuseGuard {
451
521
  constructor() {
452
522
  if (isDevMode()) {
453
- throw new Error('[rdxTooltipPortal] is now a structural directive. ' +
523
+ rdxDevError('tooltip/portal-on-element', '`rdxTooltipPortal` is now a structural directive. ' +
454
524
  'Use `*rdxTooltipPortal` on the positioner element or `<ng-template rdxTooltipPortal>`. ' +
455
- 'rdxTooltipPortalPresence has been removed. See https://radix-ng.com/components/tooltip.md');
525
+ 'rdxTooltipPortalPresence has been removed.', 'components/tooltip');
456
526
  }
457
527
  }
458
528
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxTooltipPortalMisuseGuard, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
@@ -467,89 +537,41 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
467
537
 
468
538
  /**
469
539
  * Positions the tooltip popup against its trigger (or a custom anchor).
540
+ *
541
+ * A "thin" positioner (ADR 0012): it inherits the popper positioning surface (inputs, `placed`
542
+ * output, unified vars + placement attrs) from {@link RdxPopperContentWrapper} and adds tooltip's own
543
+ * concerns — Base UI-aligned defaults (`side: 'top'`) via the config provider, dismiss handling
544
+ * (ADR 0015 — inline {@link RdxDismiss} on the shared floating tree, dismissal-only with
545
+ * no focus manager), the cursor-follow pointer-through behavior (via the inherited `nonInteractive`
546
+ * signal), the open/closed state attributes, and the hover grace area.
470
547
  */
471
- class RdxTooltipPositioner {
548
+ class RdxTooltipPositioner extends RdxPopperContentWrapper {
472
549
  constructor() {
550
+ super();
473
551
  this.rootContext = injectRdxTooltipContext();
474
- this.wrapper = inject(RdxPopperContentWrapper);
475
552
  this.destroyRef = inject(DestroyRef);
476
- this.dismissableLayer = inject(RdxDismissableLayer);
477
- this.elementRef = inject(ElementRef);
478
- /**
479
- * An element to position the popup against. Defaults to the trigger.
480
- */
481
- this.anchor = input(...(ngDevMode ? [undefined, { debugName: "anchor" }] : /* istanbul ignore next */ []));
482
- /**
483
- * The preferred side of the anchor to render against when open.
484
- */
485
- this.side = input('top', ...(ngDevMode ? [{ debugName: "side" }] : /* istanbul ignore next */ []));
486
- /**
487
- * The distance in pixels from the anchor.
488
- */
489
- this.sideOffset = input(0, { ...(ngDevMode ? { debugName: "sideOffset" } : /* istanbul ignore next */ {}), transform: numberAttribute });
490
- /**
491
- * The preferred alignment against the anchor.
492
- */
493
- this.align = input('center', ...(ngDevMode ? [{ debugName: "align" }] : /* istanbul ignore next */ []));
494
- /**
495
- * An offset in pixels from the `start` or `end` alignment options.
496
- */
497
- this.alignOffset = input(0, { ...(ngDevMode ? { debugName: "alignOffset" } : /* istanbul ignore next */ {}), transform: numberAttribute });
498
- /**
499
- * The padding between the arrow and the edges of the content.
500
- */
501
- this.arrowPadding = input(5, { ...(ngDevMode ? { debugName: "arrowPadding" } : /* istanbul ignore next */ {}), transform: numberAttribute });
502
- /**
503
- * When `true`, overrides the `side` and `align` preferences to prevent collisions with boundary edges.
504
- */
505
- this.avoidCollisions = input(true, { ...(ngDevMode ? { debugName: "avoidCollisions" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
506
- /**
507
- * The element used as the collision boundary.
508
- */
509
- this.collisionBoundary = input(...(ngDevMode ? [undefined, { debugName: "collisionBoundary" }] : /* istanbul ignore next */ []));
510
- /**
511
- * The distance in pixels from the boundary edges where collision detection should occur.
512
- */
513
- this.collisionPadding = input(5, ...(ngDevMode ? [{ debugName: "collisionPadding" }] : /* istanbul ignore next */ []));
514
- /**
515
- * The sticky behavior on the `align` axis.
516
- */
517
- this.sticky = input('partial', ...(ngDevMode ? [{ debugName: "sticky" }] : /* istanbul ignore next */ []));
518
- /**
519
- * Whether to hide the content when the trigger becomes fully occluded.
520
- */
521
- this.hideWhenDetached = input(false, { ...(ngDevMode ? { debugName: "hideWhenDetached" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
522
- /**
523
- * The CSS position strategy used by Floating UI.
524
- */
525
- this.positionStrategy = input('fixed', ...(ngDevMode ? [{ debugName: "positionStrategy" }] : /* istanbul ignore next */ []));
526
- /**
527
- * Whether to update the position of the floating element on every animation frame if required.
528
- */
529
- this.updatePositionStrategy = input('always', ...(ngDevMode ? [{ debugName: "updatePositionStrategy" }] : /* istanbul ignore next */ []));
530
- /**
531
- * Emits when the element is placed.
532
- */
533
- this.placed = outputFromObservable(outputToObservable(this.wrapper.placed));
553
+ this.floatingContext = inject(RDX_FLOATING_ROOT_CONTEXT);
554
+ this.registration = inject(RDX_FLOATING_REGISTRATION, { optional: true });
555
+ this.containerRef = inject(ElementRef);
534
556
  /**
535
557
  * Event handler called when the escape key is down. Can be prevented.
536
558
  */
537
- this.escapeKeyDown = outputFromObservable(outputToObservable(this.dismissableLayer.escapeKeyDown));
559
+ this.escapeKeyDown = output();
538
560
  /**
539
- * Event handler called when a `pointerdown` event happens outside of the `DismissableLayer`. Can be prevented.
561
+ * Event handler called when a `pointerdown` event happens outside of the popup. Can be prevented.
540
562
  */
541
- this.pointerDownOutside = outputFromObservable(outputToObservable(this.dismissableLayer.pointerDownOutside));
563
+ this.pointerDownOutside = output();
542
564
  this.triggerEl = signal(null, ...(ngDevMode ? [{ debugName: "triggerEl" }] : /* istanbul ignore next */ []));
543
565
  this.containerEl = signal(null, ...(ngDevMode ? [{ debugName: "containerEl" }] : /* istanbul ignore next */ []));
544
566
  this.graceArea = useGraceArea(this.triggerEl, this.containerEl, 300);
545
567
  this.afterNextRender = afterNextRender(() => {
546
568
  this.triggerEl.set(this.rootContext.trigger() ?? null);
547
- this.containerEl.set(this.elementRef.nativeElement);
569
+ this.containerEl.set(this.containerRef.nativeElement);
548
570
  const handleScroll = (event) => {
549
571
  const target = event.target;
550
572
  const trigger = this.rootContext.trigger();
551
573
  if (trigger && target?.contains(trigger)) {
552
- this.rootContext.close();
574
+ this.rootContext.close('none', event);
553
575
  }
554
576
  };
555
577
  window.addEventListener('scroll', handleScroll, { capture: true });
@@ -566,58 +588,77 @@ class RdxTooltipPositioner {
566
588
  this.rootContext.closeDelayed();
567
589
  });
568
590
  });
569
- this.dismissableLayer.focusOutside.subscribe((e) => e.preventDefault());
570
- this.dismissableLayer.dismiss.subscribe(() => this.rootContext.close());
591
+ // Register as the floating element so dismissal containment (outside-press / focus) treats the
592
+ // popup as "inside".
593
+ this.floatingContext.setFloatingElement(this.containerRef.nativeElement);
594
+ // Dismissal-only (ADR 0017 §1 — a tooltip has no focus manager): Escape and an outside press
595
+ // close it; focus-out is intentionally a no-op (a tooltip never traps or follows focus).
596
+ new RdxDismiss(this.floatingContext, () => this.registration?.node() ?? null, {
597
+ escapeKey: () => true,
598
+ outsidePress: () => true,
599
+ focusOutside: () => false,
600
+ onEscapeKeyDown: (event) => this.escapeKeyDown.emit(event),
601
+ onPointerDownOutside: (event) => this.pointerDownOutside.emit(event),
602
+ onDismiss: (reason, event) => this.rootContext.close(reason === 'escape-key' ? 'escape-key' : 'outside-press', event)
603
+ });
571
604
  // While following the cursor the popup sits right under the pointer; if it could intercept
572
605
  // the pointer it would steal hover from the trigger and the tooltip would flicker. Render it
573
606
  // pointer-events: none so the pointer always passes through to the trigger underneath.
574
- effect(() => this.wrapper.nonInteractive.set(this.rootContext.trackCursorAxis() !== 'none'));
607
+ effect(() => this.nonInteractive.set(this.rootContext.trackCursorAxis() !== 'none'));
575
608
  }
576
609
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxTooltipPositioner, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
577
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxTooltipPositioner, isStandalone: true, selector: "[rdxTooltipPositioner]", inputs: { anchor: { classPropertyName: "anchor", publicName: "anchor", isSignal: true, isRequired: false, transformFunction: null }, side: { classPropertyName: "side", publicName: "side", isSignal: true, isRequired: false, transformFunction: null }, sideOffset: { classPropertyName: "sideOffset", publicName: "sideOffset", isSignal: true, isRequired: false, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, alignOffset: { classPropertyName: "alignOffset", publicName: "alignOffset", isSignal: true, isRequired: false, transformFunction: null }, arrowPadding: { classPropertyName: "arrowPadding", publicName: "arrowPadding", isSignal: true, isRequired: false, transformFunction: null }, avoidCollisions: { classPropertyName: "avoidCollisions", publicName: "avoidCollisions", isSignal: true, isRequired: false, transformFunction: null }, collisionBoundary: { classPropertyName: "collisionBoundary", publicName: "collisionBoundary", isSignal: true, isRequired: false, transformFunction: null }, collisionPadding: { classPropertyName: "collisionPadding", publicName: "collisionPadding", isSignal: true, isRequired: false, transformFunction: null }, sticky: { classPropertyName: "sticky", publicName: "sticky", isSignal: true, isRequired: false, transformFunction: null }, hideWhenDetached: { classPropertyName: "hideWhenDetached", publicName: "hideWhenDetached", isSignal: true, isRequired: false, transformFunction: null }, positionStrategy: { classPropertyName: "positionStrategy", publicName: "positionStrategy", isSignal: true, isRequired: false, transformFunction: null }, updatePositionStrategy: { classPropertyName: "updatePositionStrategy", publicName: "updatePositionStrategy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { placed: "placed", escapeKeyDown: "escapeKeyDown", pointerDownOutside: "pointerDownOutside" }, host: { properties: { "attr.data-open": "rootContext.isOpen() ? \"\" : undefined", "attr.data-closed": "rootContext.isOpen() ? undefined : \"\"", "attr.data-side": "wrapper.placedSide()", "attr.data-align": "wrapper.placedAlign()", "style": "{\n '--anchor-width': 'var(--radix-popper-anchor-width)',\n '--anchor-height': 'var(--radix-popper-anchor-height)',\n '--available-width': 'var(--radix-popper-available-width)',\n '--available-height': 'var(--radix-popper-available-height)',\n '--transform-origin': 'var(--radix-popper-transform-origin)'\n }" } }, providers: [provideRdxPopperContentConfig({ side: 'top', arrowPadding: 5, collisionPadding: 5 })], hostDirectives: [{ directive: i1$2.RdxDismissableLayer }, { directive: i1.RdxPopperContentWrapper, inputs: ["anchor", "anchor", "side", "side", "sideOffset", "sideOffset", "align", "align", "alignOffset", "alignOffset", "arrowPadding", "arrowPadding", "avoidCollisions", "avoidCollisions", "collisionBoundary", "collisionBoundary", "collisionPadding", "collisionPadding", "sticky", "sticky", "hideWhenDetached", "hideWhenDetached", "positionStrategy", "positionStrategy", "updatePositionStrategy", "updatePositionStrategy"] }], ngImport: i0 }); }
610
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxTooltipPositioner, isStandalone: true, selector: "[rdxTooltipPositioner]", outputs: { escapeKeyDown: "escapeKeyDown", pointerDownOutside: "pointerDownOutside" }, host: { properties: { "attr.data-open": "rootContext.isOpen() ? \"\" : undefined", "attr.data-closed": "rootContext.isOpen() ? undefined : \"\"" } }, providers: [
611
+ ...provideRdxPopperContentWrapper(RdxTooltipPositioner),
612
+ provideRdxPopperContentConfig({ side: 'top', arrowPadding: 5, collisionPadding: 5 })
613
+ ], usesInheritance: true, hostDirectives: [{ directive: i1$2.RdxFloatingNodeRegistration }], ngImport: i0 }); }
578
614
  }
579
615
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxTooltipPositioner, decorators: [{
580
616
  type: Directive,
581
617
  args: [{
582
618
  selector: '[rdxTooltipPositioner]',
583
- providers: [provideRdxPopperContentConfig({ side: 'top', arrowPadding: 5, collisionPadding: 5 })],
584
- hostDirectives: [
585
- RdxDismissableLayer,
586
- {
587
- directive: RdxPopperContentWrapper,
588
- inputs: [
589
- 'anchor',
590
- 'side',
591
- 'sideOffset',
592
- 'align',
593
- 'alignOffset',
594
- 'arrowPadding',
595
- 'avoidCollisions',
596
- 'collisionBoundary',
597
- 'collisionPadding',
598
- 'sticky',
599
- 'hideWhenDetached',
600
- 'positionStrategy',
601
- 'updatePositionStrategy'
602
- ]
603
- }
619
+ providers: [
620
+ ...provideRdxPopperContentWrapper(RdxTooltipPositioner),
621
+ provideRdxPopperContentConfig({ side: 'top', arrowPadding: 5, collisionPadding: 5 })
604
622
  ],
623
+ hostDirectives: [RdxFloatingNodeRegistration],
605
624
  host: {
606
625
  '[attr.data-open]': 'rootContext.isOpen() ? "" : undefined',
607
- '[attr.data-closed]': 'rootContext.isOpen() ? undefined : ""',
608
- '[attr.data-side]': 'wrapper.placedSide()',
609
- '[attr.data-align]': 'wrapper.placedAlign()',
610
- '[style]': `{
611
- '--anchor-width': 'var(--radix-popper-anchor-width)',
612
- '--anchor-height': 'var(--radix-popper-anchor-height)',
613
- '--available-width': 'var(--radix-popper-available-width)',
614
- '--available-height': 'var(--radix-popper-available-height)',
615
- '--transform-origin': 'var(--radix-popper-transform-origin)'
616
- }`
626
+ '[attr.data-closed]': 'rootContext.isOpen() ? undefined : ""'
627
+ // `data-side`/`data-align`/`data-anchor-hidden` and the unified vars come from the inherited
628
+ // wrapper (ADR 0012); tooltip exposes no legacy `--radix-*` aliases.
617
629
  }
618
630
  }]
619
- }], ctorParameters: () => [], propDecorators: { anchor: [{ type: i0.Input, args: [{ isSignal: true, alias: "anchor", required: false }] }], side: [{ type: i0.Input, args: [{ isSignal: true, alias: "side", required: false }] }], sideOffset: [{ type: i0.Input, args: [{ isSignal: true, alias: "sideOffset", required: false }] }], align: [{ type: i0.Input, args: [{ isSignal: true, alias: "align", required: false }] }], alignOffset: [{ type: i0.Input, args: [{ isSignal: true, alias: "alignOffset", required: false }] }], arrowPadding: [{ type: i0.Input, args: [{ isSignal: true, alias: "arrowPadding", required: false }] }], avoidCollisions: [{ type: i0.Input, args: [{ isSignal: true, alias: "avoidCollisions", required: false }] }], collisionBoundary: [{ type: i0.Input, args: [{ isSignal: true, alias: "collisionBoundary", required: false }] }], collisionPadding: [{ type: i0.Input, args: [{ isSignal: true, alias: "collisionPadding", required: false }] }], sticky: [{ type: i0.Input, args: [{ isSignal: true, alias: "sticky", required: false }] }], hideWhenDetached: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideWhenDetached", required: false }] }], positionStrategy: [{ type: i0.Input, args: [{ isSignal: true, alias: "positionStrategy", required: false }] }], updatePositionStrategy: [{ type: i0.Input, args: [{ isSignal: true, alias: "updatePositionStrategy", required: false }] }], placed: [{ type: i0.Output, args: ["placed"] }], escapeKeyDown: [{ type: i0.Output, args: ["escapeKeyDown"] }], pointerDownOutside: [{ type: i0.Output, args: ["pointerDownOutside"] }] } });
631
+ }], ctorParameters: () => [], propDecorators: { escapeKeyDown: [{ type: i0.Output, args: ["escapeKeyDown"] }], pointerDownOutside: [{ type: i0.Output, args: ["pointerDownOutside"] }] } });
620
632
 
633
+ const TOOLTIP_TRIGGER_ATTR = 'data-rdx-tooltip-trigger';
634
+ function getTargetElement(event) {
635
+ if ('composedPath' in event) {
636
+ for (const target of event.composedPath()) {
637
+ if (target instanceof Element) {
638
+ return target;
639
+ }
640
+ }
641
+ }
642
+ return event.target instanceof Element ? event.target : null;
643
+ }
644
+ function closestEnabledTooltipTrigger(element) {
645
+ let current = element;
646
+ while (current) {
647
+ if (current.hasAttribute(TOOLTIP_TRIGGER_ATTR)) {
648
+ return current;
649
+ }
650
+ if (current.parentElement) {
651
+ current = current.parentElement;
652
+ continue;
653
+ }
654
+ const root = current.getRootNode();
655
+ current = 'host' in root && root.host instanceof Element ? root.host : null;
656
+ }
657
+ return null;
658
+ }
659
+ function isMouseLikePointerType(pointerType) {
660
+ return pointerType !== 'touch';
661
+ }
621
662
  class RdxTooltipTrigger {
622
663
  constructor() {
623
664
  this.parentRootContext = injectRdxTooltipContext(true);
@@ -660,8 +701,16 @@ class RdxTooltipTrigger {
660
701
  this.isDisabled = computed(() => (this.rootContext()?.disabled() ?? false) || this.disabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
661
702
  /** Whether this specific trigger is the active anchor of an open tooltip. */
662
703
  this.isOpen = computed(() => this.rootContext()?.isOpen() === true && this.rootContext()?.trigger() === this.elementRef.nativeElement, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
704
+ this.triggerInteraction = createRdxTriggerInteraction({
705
+ trigger: () => this.elementRef.nativeElement,
706
+ activeTrigger: () => this.rootContext()?.trigger(),
707
+ open: () => this.rootContext()?.isOpen() ?? false,
708
+ disabled: () => this.isDisabled(),
709
+ contentId: () => this.rootContext()?.contentId
710
+ });
663
711
  this.isPointerDown = signal(false, ...(ngDevMode ? [{ debugName: "isPointerDown" }] : /* istanbul ignore next */ []));
664
712
  this.hasPointerMoveOpened = signal(false, ...(ngDevMode ? [{ debugName: "hasPointerMoveOpened" }] : /* istanbul ignore next */ []));
713
+ this.isNestedTriggerHovered = signal(false, ...(ngDevMode ? [{ debugName: "isNestedTriggerHovered" }] : /* istanbul ignore next */ []));
665
714
  effect((onCleanup) => {
666
715
  const handle = this.handle();
667
716
  if (handle) {
@@ -672,22 +721,29 @@ class RdxTooltipTrigger {
672
721
  }
673
722
  });
674
723
  }
675
- handleFocus() {
724
+ handleFocus(event) {
676
725
  const rootContext = this.rootContext();
677
726
  if (!rootContext || this.isDisabled() || this.isPointerDown()) {
678
727
  return;
679
728
  }
680
729
  rootContext.setDelays(this.delay(), this.closeDelay());
681
- rootContext.open(this.elementRef.nativeElement, this.payload());
730
+ rootContext.open(this.elementRef.nativeElement, this.payload(), event);
682
731
  }
683
- handleBlur() {
684
- this.rootContext()?.closeDelayed();
732
+ handleBlur(event) {
733
+ this.rootContext()?.closeDelayed(event);
685
734
  }
686
- handleClick() {
735
+ handleClick(event) {
687
736
  const rootContext = this.rootContext();
688
737
  if (rootContext?.isOpen() && this.closeOnClick()) {
689
- rootContext.close();
738
+ rootContext.close('trigger-press', event);
739
+ return;
690
740
  }
741
+ if (rootContext && this.closeOnClick()) {
742
+ rootContext.cancelPendingOpen();
743
+ }
744
+ }
745
+ handlePointerEnter(event) {
746
+ this.pointerType = event.pointerType;
691
747
  }
692
748
  handlePointerMove(event) {
693
749
  if (event.pointerType === 'touch') {
@@ -697,12 +753,16 @@ class RdxTooltipTrigger {
697
753
  if (!rootContext || this.isDisabled()) {
698
754
  return;
699
755
  }
756
+ this.pointerType = event.pointerType;
757
+ if (this.detectNestedTriggerHover(getTargetElement(event))) {
758
+ return;
759
+ }
700
760
  if (rootContext.trackCursorAxis() !== 'none') {
701
761
  rootContext.setCursorPosition({ x: event.clientX, y: event.clientY });
702
762
  }
703
763
  if (!this.hasPointerMoveOpened()) {
704
764
  rootContext.setDelays(this.delay(), this.closeDelay());
705
- rootContext.onTriggerEnter(this.elementRef.nativeElement, this.payload());
765
+ rootContext.onTriggerEnter(this.elementRef.nativeElement, this.payload(), event);
706
766
  this.hasPointerMoveOpened.set(true);
707
767
  }
708
768
  }
@@ -710,6 +770,35 @@ class RdxTooltipTrigger {
710
770
  this.rootContext()?.onTriggerLeave();
711
771
  this.hasPointerMoveOpened.set(false);
712
772
  }
773
+ handleMouseOver(event) {
774
+ const wasNestedTriggerHovered = this.isNestedTriggerHovered();
775
+ const target = getTargetElement(event);
776
+ const nestedTriggerHovered = this.detectNestedTriggerHover(target);
777
+ const trigger = this.elementRef.nativeElement;
778
+ const targetInsideTrigger = target ? trigger.contains(target) : false;
779
+ const rootContext = this.rootContext();
780
+ if (!rootContext) {
781
+ return;
782
+ }
783
+ if (nestedTriggerHovered && rootContext.isOpen() && rootContext.openChangeReason() === 'trigger-hover') {
784
+ rootContext.closeHoverOpen();
785
+ return;
786
+ }
787
+ if (wasNestedTriggerHovered &&
788
+ !nestedTriggerHovered &&
789
+ targetInsideTrigger &&
790
+ !this.isDisabled() &&
791
+ !rootContext.isOpen() &&
792
+ isMouseLikePointerType(this.pointerType)) {
793
+ rootContext.setDelays(this.delay(), this.closeDelay());
794
+ rootContext.onTriggerEnter(trigger, this.payload(), event);
795
+ this.hasPointerMoveOpened.set(true);
796
+ }
797
+ }
798
+ handleMouseLeave() {
799
+ this.isNestedTriggerHovered.set(false);
800
+ this.pointerType = undefined;
801
+ }
713
802
  async handlePointerDown(event) {
714
803
  const user = this.userOnPointerDown();
715
804
  let result;
@@ -722,13 +811,28 @@ class RdxTooltipTrigger {
722
811
  return;
723
812
  }
724
813
  this.isPointerDown.set(true);
814
+ this.triggerInteraction.recordPointerDown(event);
815
+ this.pointerType = event.pointerType;
816
+ if (this.closeOnClick() && !this.rootContext()?.isOpen()) {
817
+ this.rootContext()?.cancelPendingOpen();
818
+ }
725
819
  const handlePointerUp = () => {
726
820
  setTimeout(() => this.isPointerDown.set(false), 1);
727
821
  };
728
822
  document.addEventListener('pointerup', handlePointerUp, { once: true });
729
823
  }
824
+ detectNestedTriggerHover(target) {
825
+ const trigger = this.elementRef.nativeElement;
826
+ const nearestTrigger = closestEnabledTooltipTrigger(target);
827
+ const nestedTriggerHovered = nearestTrigger !== null && nearestTrigger !== trigger && trigger.contains(nearestTrigger);
828
+ this.isNestedTriggerHovered.set(nestedTriggerHovered);
829
+ if (nestedTriggerHovered) {
830
+ this.rootContext()?.cancelPendingOpen();
831
+ }
832
+ return nestedTriggerHovered;
833
+ }
730
834
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxTooltipTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
731
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxTooltipTrigger, isStandalone: true, selector: "[rdxTooltipTrigger]", inputs: { handle: { classPropertyName: "handle", publicName: "handle", isSignal: true, isRequired: false, transformFunction: null }, payload: { classPropertyName: "payload", publicName: "payload", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, closeOnClick: { classPropertyName: "closeOnClick", publicName: "closeOnClick", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, delay: { classPropertyName: "delay", publicName: "delay", isSignal: true, isRequired: false, transformFunction: null }, closeDelay: { classPropertyName: "closeDelay", publicName: "closeDelay", isSignal: true, isRequired: false, transformFunction: null }, userOnPointerDown: { classPropertyName: "userOnPointerDown", publicName: "rdxOnPointerDown", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "type": "button", "data-grace-area-trigger": "''" }, listeners: { "pointermove": "handlePointerMove($event)", "pointerleave": "handlePointerLeave()", "pointerdown": "handlePointerDown($event)", "focus": "handleFocus()", "blur": "handleBlur()", "click": "handleClick()" }, properties: { "id": "triggerId()", "attr.aria-describedby": "isOpen() ? rootContext()?.contentId : undefined", "attr.data-popup-open": "isOpen() ? \"\" : undefined", "attr.data-trigger-disabled": "isDisabled() ? \"\" : undefined" } }, hostDirectives: [{ directive: i1.RdxPopperAnchor }], ngImport: i0 }); }
835
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxTooltipTrigger, isStandalone: true, selector: "[rdxTooltipTrigger]", inputs: { handle: { classPropertyName: "handle", publicName: "handle", isSignal: true, isRequired: false, transformFunction: null }, payload: { classPropertyName: "payload", publicName: "payload", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, closeOnClick: { classPropertyName: "closeOnClick", publicName: "closeOnClick", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, delay: { classPropertyName: "delay", publicName: "delay", isSignal: true, isRequired: false, transformFunction: null }, closeDelay: { classPropertyName: "closeDelay", publicName: "closeDelay", isSignal: true, isRequired: false, transformFunction: null }, userOnPointerDown: { classPropertyName: "userOnPointerDown", publicName: "rdxOnPointerDown", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "type": "button", "data-grace-area-trigger": "''" }, listeners: { "pointerenter": "handlePointerEnter($event)", "pointermove": "handlePointerMove($event)", "pointerleave": "handlePointerLeave()", "mouseover": "handleMouseOver($event)", "mouseleave": "handleMouseLeave()", "pointerdown": "handlePointerDown($event)", "focus": "handleFocus($event)", "blur": "handleBlur($event)", "click": "handleClick($event)" }, properties: { "id": "triggerId()", "attr.aria-describedby": "isOpen() ? rootContext()?.contentId : undefined", "attr.data-popup-open": "triggerInteraction.dataPopupOpen()", "attr.data-trigger-disabled": "triggerInteraction.disabled() ? \"\" : undefined", "attr.data-rdx-tooltip-trigger": "isDisabled() ? undefined : \"\"" } }, hostDirectives: [{ directive: i1.RdxPopperAnchor }], ngImport: i0 }); }
732
836
  }
733
837
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxTooltipTrigger, decorators: [{
734
838
  type: Directive,
@@ -740,14 +844,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
740
844
  'data-grace-area-trigger': "''",
741
845
  '[id]': 'triggerId()',
742
846
  '[attr.aria-describedby]': 'isOpen() ? rootContext()?.contentId : undefined',
743
- '[attr.data-popup-open]': 'isOpen() ? "" : undefined',
744
- '[attr.data-trigger-disabled]': 'isDisabled() ? "" : undefined',
847
+ '[attr.data-popup-open]': 'triggerInteraction.dataPopupOpen()',
848
+ '[attr.data-trigger-disabled]': 'triggerInteraction.disabled() ? "" : undefined',
849
+ '[attr.data-rdx-tooltip-trigger]': 'isDisabled() ? undefined : ""',
850
+ '(pointerenter)': 'handlePointerEnter($event)',
745
851
  '(pointermove)': 'handlePointerMove($event)',
746
852
  '(pointerleave)': 'handlePointerLeave()',
853
+ '(mouseover)': 'handleMouseOver($event)',
854
+ '(mouseleave)': 'handleMouseLeave()',
747
855
  '(pointerdown)': 'handlePointerDown($event)',
748
- '(focus)': 'handleFocus()',
749
- '(blur)': 'handleBlur()',
750
- '(click)': 'handleClick()'
856
+ '(focus)': 'handleFocus($event)',
857
+ '(blur)': 'handleBlur($event)',
858
+ '(click)': 'handleClick($event)'
751
859
  }
752
860
  }]
753
861
  }], ctorParameters: () => [], propDecorators: { handle: [{ type: i0.Input, args: [{ isSignal: true, alias: "handle", required: false }] }], payload: [{ type: i0.Input, args: [{ isSignal: true, alias: "payload", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], closeOnClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnClick", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], delay: [{ type: i0.Input, args: [{ isSignal: true, alias: "delay", required: false }] }], closeDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeDelay", required: false }] }], userOnPointerDown: [{ type: i0.Input, args: [{ isSignal: true, alias: "rdxOnPointerDown", required: false }] }] } });