@radix-ng/primitives 0.51.0 → 1.0.0-beta.1

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 (186) hide show
  1. package/fesm2022/radix-ng-primitives-accordion.mjs +105 -38
  2. package/fesm2022/radix-ng-primitives-accordion.mjs.map +1 -1
  3. package/fesm2022/radix-ng-primitives-alert-dialog.mjs +221 -129
  4. package/fesm2022/radix-ng-primitives-alert-dialog.mjs.map +1 -1
  5. package/fesm2022/radix-ng-primitives-arrow.mjs +20 -4
  6. package/fesm2022/radix-ng-primitives-arrow.mjs.map +1 -1
  7. package/fesm2022/radix-ng-primitives-aspect-ratio.mjs.map +1 -1
  8. package/fesm2022/radix-ng-primitives-avatar.mjs +54 -61
  9. package/fesm2022/radix-ng-primitives-avatar.mjs.map +1 -1
  10. package/fesm2022/radix-ng-primitives-button.mjs +123 -0
  11. package/fesm2022/radix-ng-primitives-button.mjs.map +1 -0
  12. package/fesm2022/radix-ng-primitives-calendar.mjs +95 -83
  13. package/fesm2022/radix-ng-primitives-calendar.mjs.map +1 -1
  14. package/fesm2022/radix-ng-primitives-checkbox.mjs +378 -54
  15. package/fesm2022/radix-ng-primitives-checkbox.mjs.map +1 -1
  16. package/fesm2022/radix-ng-primitives-collapsible.mjs +182 -81
  17. package/fesm2022/radix-ng-primitives-collapsible.mjs.map +1 -1
  18. package/fesm2022/radix-ng-primitives-collection.mjs +40 -57
  19. package/fesm2022/radix-ng-primitives-collection.mjs.map +1 -1
  20. package/fesm2022/radix-ng-primitives-config.mjs.map +1 -1
  21. package/fesm2022/radix-ng-primitives-context-menu.mjs +140 -424
  22. package/fesm2022/radix-ng-primitives-context-menu.mjs.map +1 -1
  23. package/fesm2022/radix-ng-primitives-core.mjs +845 -744
  24. package/fesm2022/radix-ng-primitives-core.mjs.map +1 -1
  25. package/fesm2022/radix-ng-primitives-cropper.mjs +288 -308
  26. package/fesm2022/radix-ng-primitives-cropper.mjs.map +1 -1
  27. package/fesm2022/radix-ng-primitives-date-field.mjs +104 -58
  28. package/fesm2022/radix-ng-primitives-date-field.mjs.map +1 -1
  29. package/fesm2022/radix-ng-primitives-dialog.mjs +655 -327
  30. package/fesm2022/radix-ng-primitives-dialog.mjs.map +1 -1
  31. package/fesm2022/radix-ng-primitives-dismissable-layer.mjs +70 -46
  32. package/fesm2022/radix-ng-primitives-dismissable-layer.mjs.map +1 -1
  33. package/fesm2022/radix-ng-primitives-drawer.mjs +960 -0
  34. package/fesm2022/radix-ng-primitives-drawer.mjs.map +1 -0
  35. package/fesm2022/radix-ng-primitives-editable.mjs +304 -23
  36. package/fesm2022/radix-ng-primitives-editable.mjs.map +1 -1
  37. package/fesm2022/radix-ng-primitives-field.mjs +363 -0
  38. package/fesm2022/radix-ng-primitives-field.mjs.map +1 -0
  39. package/fesm2022/radix-ng-primitives-fieldset.mjs +79 -0
  40. package/fesm2022/radix-ng-primitives-fieldset.mjs.map +1 -0
  41. package/fesm2022/radix-ng-primitives-focus-scope.mjs +23 -8
  42. package/fesm2022/radix-ng-primitives-focus-scope.mjs.map +1 -1
  43. package/fesm2022/radix-ng-primitives-input.mjs +172 -0
  44. package/fesm2022/radix-ng-primitives-input.mjs.map +1 -0
  45. package/fesm2022/radix-ng-primitives-label.mjs +6 -6
  46. package/fesm2022/radix-ng-primitives-label.mjs.map +1 -1
  47. package/fesm2022/radix-ng-primitives-menu.mjs +1907 -363
  48. package/fesm2022/radix-ng-primitives-menu.mjs.map +1 -1
  49. package/fesm2022/radix-ng-primitives-menubar.mjs +290 -162
  50. package/fesm2022/radix-ng-primitives-menubar.mjs.map +1 -1
  51. package/fesm2022/radix-ng-primitives-meter.mjs +271 -0
  52. package/fesm2022/radix-ng-primitives-meter.mjs.map +1 -0
  53. package/fesm2022/radix-ng-primitives-navigation-menu.mjs +1052 -1553
  54. package/fesm2022/radix-ng-primitives-navigation-menu.mjs.map +1 -1
  55. package/fesm2022/radix-ng-primitives-number-field.mjs +1102 -367
  56. package/fesm2022/radix-ng-primitives-number-field.mjs.map +1 -1
  57. package/fesm2022/radix-ng-primitives-pagination.mjs.map +1 -1
  58. package/fesm2022/radix-ng-primitives-popover.mjs +978 -989
  59. package/fesm2022/radix-ng-primitives-popover.mjs.map +1 -1
  60. package/fesm2022/radix-ng-primitives-popper.mjs +111 -44
  61. package/fesm2022/radix-ng-primitives-popper.mjs.map +1 -1
  62. package/fesm2022/radix-ng-primitives-portal.mjs +34 -10
  63. package/fesm2022/radix-ng-primitives-portal.mjs.map +1 -1
  64. package/fesm2022/radix-ng-primitives-presence.mjs +134 -246
  65. package/fesm2022/radix-ng-primitives-presence.mjs.map +1 -1
  66. package/fesm2022/radix-ng-primitives-preview-card.mjs +997 -0
  67. package/fesm2022/radix-ng-primitives-preview-card.mjs.map +1 -0
  68. package/fesm2022/radix-ng-primitives-progress.mjs +223 -84
  69. package/fesm2022/radix-ng-primitives-progress.mjs.map +1 -1
  70. package/fesm2022/radix-ng-primitives-radio.mjs +191 -51
  71. package/fesm2022/radix-ng-primitives-radio.mjs.map +1 -1
  72. package/fesm2022/radix-ng-primitives-roving-focus.mjs +96 -50
  73. package/fesm2022/radix-ng-primitives-roving-focus.mjs.map +1 -1
  74. package/fesm2022/radix-ng-primitives-scroll-area.mjs +923 -0
  75. package/fesm2022/radix-ng-primitives-scroll-area.mjs.map +1 -0
  76. package/fesm2022/radix-ng-primitives-select.mjs +791 -509
  77. package/fesm2022/radix-ng-primitives-select.mjs.map +1 -1
  78. package/fesm2022/radix-ng-primitives-separator.mjs +12 -35
  79. package/fesm2022/radix-ng-primitives-separator.mjs.map +1 -1
  80. package/fesm2022/radix-ng-primitives-slider.mjs +969 -717
  81. package/fesm2022/radix-ng-primitives-slider.mjs.map +1 -1
  82. package/fesm2022/radix-ng-primitives-stepper.mjs +15 -19
  83. package/fesm2022/radix-ng-primitives-stepper.mjs.map +1 -1
  84. package/fesm2022/radix-ng-primitives-switch.mjs +125 -113
  85. package/fesm2022/radix-ng-primitives-switch.mjs.map +1 -1
  86. package/fesm2022/radix-ng-primitives-tabs.mjs +390 -108
  87. package/fesm2022/radix-ng-primitives-tabs.mjs.map +1 -1
  88. package/fesm2022/radix-ng-primitives-time-field.mjs +55 -46
  89. package/fesm2022/radix-ng-primitives-time-field.mjs.map +1 -1
  90. package/fesm2022/radix-ng-primitives-toast.mjs +839 -0
  91. package/fesm2022/radix-ng-primitives-toast.mjs.map +1 -0
  92. package/fesm2022/radix-ng-primitives-toggle-group.mjs +121 -247
  93. package/fesm2022/radix-ng-primitives-toggle-group.mjs.map +1 -1
  94. package/fesm2022/radix-ng-primitives-toggle.mjs +98 -61
  95. package/fesm2022/radix-ng-primitives-toggle.mjs.map +1 -1
  96. package/fesm2022/radix-ng-primitives-toolbar.mjs +303 -92
  97. package/fesm2022/radix-ng-primitives-toolbar.mjs.map +1 -1
  98. package/fesm2022/radix-ng-primitives-tooltip.mjs +699 -1072
  99. package/fesm2022/radix-ng-primitives-tooltip.mjs.map +1 -1
  100. package/fesm2022/radix-ng-primitives-visually-hidden.mjs +25 -66
  101. package/fesm2022/radix-ng-primitives-visually-hidden.mjs.map +1 -1
  102. package/meter/README.md +3 -0
  103. package/navigation-menu/README.md +2 -1
  104. package/package.json +39 -18
  105. package/portal/README.md +2 -0
  106. package/preview-card/README.md +3 -0
  107. package/schematics/collection.json +1 -0
  108. package/schematics/ng-add/index.d.ts +3 -2
  109. package/schematics/ng-add/index.js +62 -31
  110. package/schematics/ng-add/index.js.map +1 -1
  111. package/schematics/ng-add/package-config.d.ts +4 -2
  112. package/schematics/ng-add/package-config.js +10 -2
  113. package/schematics/ng-add/package-config.js.map +1 -1
  114. package/schematics/ng-add/schema.d.ts +3 -0
  115. package/schematics/ng-add/schema.js +3 -0
  116. package/schematics/ng-add/schema.js.map +1 -0
  117. package/schematics/ng-add/schema.json +14 -0
  118. package/select/README.md +2 -0
  119. package/types/radix-ng-primitives-accordion.d.ts +51 -16
  120. package/types/radix-ng-primitives-alert-dialog.d.ts +95 -38
  121. package/types/radix-ng-primitives-arrow.d.ts +1 -1
  122. package/types/radix-ng-primitives-aspect-ratio.d.ts +1 -1
  123. package/types/radix-ng-primitives-avatar.d.ts +7 -11
  124. package/types/radix-ng-primitives-button.d.ts +73 -0
  125. package/types/radix-ng-primitives-calendar.d.ts +39 -20
  126. package/types/radix-ng-primitives-checkbox.d.ts +204 -35
  127. package/types/radix-ng-primitives-collapsible.d.ts +114 -40
  128. package/types/radix-ng-primitives-collection.d.ts +38 -34
  129. package/types/radix-ng-primitives-config.d.ts +1 -1
  130. package/types/radix-ng-primitives-context-menu.d.ts +61 -116
  131. package/types/radix-ng-primitives-core.d.ts +345 -235
  132. package/types/radix-ng-primitives-cropper.d.ts +89 -56
  133. package/types/radix-ng-primitives-date-field.d.ts +49 -28
  134. package/types/radix-ng-primitives-dialog.d.ts +283 -165
  135. package/types/radix-ng-primitives-dismissable-layer.d.ts +15 -7
  136. package/types/radix-ng-primitives-drawer.d.ts +426 -0
  137. package/types/radix-ng-primitives-editable.d.ts +91 -14
  138. package/types/radix-ng-primitives-field.d.ts +374 -0
  139. package/types/radix-ng-primitives-fieldset.d.ts +49 -0
  140. package/types/radix-ng-primitives-focus-scope.d.ts +15 -6
  141. package/types/radix-ng-primitives-input.d.ts +87 -0
  142. package/types/radix-ng-primitives-label.d.ts +0 -1
  143. package/types/radix-ng-primitives-menu.d.ts +584 -99
  144. package/types/radix-ng-primitives-menubar.d.ts +61 -50
  145. package/types/radix-ng-primitives-meter.d.ts +194 -0
  146. package/types/radix-ng-primitives-navigation-menu.d.ts +422 -340
  147. package/types/radix-ng-primitives-number-field.d.ts +405 -145
  148. package/types/radix-ng-primitives-pagination.d.ts +2 -2
  149. package/types/radix-ng-primitives-popover.d.ts +366 -351
  150. package/types/radix-ng-primitives-popper.d.ts +68 -11
  151. package/types/radix-ng-primitives-portal.d.ts +14 -6
  152. package/types/radix-ng-primitives-presence.d.ts +28 -76
  153. package/types/radix-ng-primitives-preview-card.d.ts +359 -0
  154. package/types/radix-ng-primitives-progress.d.ts +175 -48
  155. package/types/radix-ng-primitives-radio.d.ts +55 -25
  156. package/types/radix-ng-primitives-roving-focus.d.ts +33 -23
  157. package/types/radix-ng-primitives-scroll-area.d.ts +253 -0
  158. package/types/radix-ng-primitives-select.d.ts +475 -177
  159. package/types/radix-ng-primitives-separator.d.ts +7 -32
  160. package/types/radix-ng-primitives-slider.d.ts +315 -201
  161. package/types/radix-ng-primitives-stepper.d.ts +5 -7
  162. package/types/radix-ng-primitives-switch.d.ts +86 -71
  163. package/types/radix-ng-primitives-tabs.d.ts +213 -79
  164. package/types/radix-ng-primitives-time-field.d.ts +42 -27
  165. package/types/radix-ng-primitives-toast.d.ts +378 -0
  166. package/types/radix-ng-primitives-toggle-group.d.ts +86 -164
  167. package/types/radix-ng-primitives-toggle.d.ts +43 -53
  168. package/types/radix-ng-primitives-toolbar.d.ts +164 -38
  169. package/types/radix-ng-primitives-tooltip.d.ts +348 -384
  170. package/types/radix-ng-primitives-visually-hidden.d.ts +19 -19
  171. package/dropdown-menu/README.md +0 -1
  172. package/fesm2022/radix-ng-primitives-dropdown-menu.mjs +0 -581
  173. package/fesm2022/radix-ng-primitives-dropdown-menu.mjs.map +0 -1
  174. package/fesm2022/radix-ng-primitives-hover-card.mjs +0 -1238
  175. package/fesm2022/radix-ng-primitives-hover-card.mjs.map +0 -1
  176. package/fesm2022/radix-ng-primitives-select2.mjs +0 -897
  177. package/fesm2022/radix-ng-primitives-select2.mjs.map +0 -1
  178. package/fesm2022/radix-ng-primitives-tooltip2.mjs +0 -735
  179. package/fesm2022/radix-ng-primitives-tooltip2.mjs.map +0 -1
  180. package/hover-card/README.md +0 -3
  181. package/select2/README.md +0 -3
  182. package/tooltip2/README.md +0 -3
  183. package/types/radix-ng-primitives-dropdown-menu.d.ts +0 -171
  184. package/types/radix-ng-primitives-hover-card.d.ts +0 -471
  185. package/types/radix-ng-primitives-select2.d.ts +0 -511
  186. package/types/radix-ng-primitives-tooltip2.d.ts +0 -325
@@ -1,6 +1,6 @@
1
1
  import { DOCUMENT, isPlatformBrowser } from '@angular/common';
2
2
  import * as i0 from '@angular/core';
3
- import { inject, ElementRef, PLATFORM_ID, DestroyRef, input, linkedSignal, computed, Directive } from '@angular/core';
3
+ import { inject, ElementRef, PLATFORM_ID, DestroyRef, input, linkedSignal, computed, effect, Directive } from '@angular/core';
4
4
 
5
5
  class RdxPortal {
6
6
  constructor() {
@@ -9,13 +9,14 @@ class RdxPortal {
9
9
  this.document = inject(DOCUMENT, { optional: true });
10
10
  this.destroyRef = inject(DestroyRef);
11
11
  /**
12
- * Specify a container element to portal the content into.
12
+ * Specify a container to portal the content into. Can be an `ElementRef`, a native element, or a
13
+ * CSS selector. Defaults to `document.body` when not set (or when a selector matches nothing).
13
14
  */
14
15
  this.container = input(...(ngDevMode ? [undefined, { debugName: "container" }] : /* istanbul ignore next */ []));
15
16
  this._computedContainer = linkedSignal(this.container, ...(ngDevMode ? [{ debugName: "_computedContainer" }] : /* istanbul ignore next */ []));
16
- this.computedContainer = this._computedContainer;
17
+ this.computedContainer = this._computedContainer.asReadonly();
17
18
  this.elementContainer = computed(() => {
18
- const provided = this.computedContainer()?.nativeElement ?? null;
19
+ const provided = this.resolveContainer(this.computedContainer());
19
20
  const body = this.document?.body ?? null;
20
21
  return provided ?? body;
21
22
  }, ...(ngDevMode ? [{ debugName: "elementContainer" }] : /* istanbul ignore next */ []));
@@ -23,23 +24,46 @@ class RdxPortal {
23
24
  if (!isBrowser || !this.document) {
24
25
  return;
25
26
  }
26
- const node = this.document.createComment('rdx-portal');
27
- this.elementRef.nativeElement.parentNode?.insertBefore(node, this.elementRef.nativeElement);
28
- this.elementContainer()?.appendChild(this.elementRef.nativeElement);
27
+ const element = this.elementRef.nativeElement;
28
+ // Anchor the original DOM position with a comment node, so the element can be restored
29
+ // exactly where it was when the directive is destroyed.
30
+ const anchor = this.document.createComment('rdx-portal');
31
+ element.parentNode?.insertBefore(anchor, element);
32
+ // Move reactively: the effect runs after inputs are bound (so `container` is respected on
33
+ // first render) and re-runs whenever the target container changes. `appendChild` relocates
34
+ // the element, it does not clone it.
35
+ effect(() => {
36
+ this.elementContainer()?.appendChild(element);
37
+ });
29
38
  this.destroyRef.onDestroy(() => {
30
- node.parentNode?.replaceChild(this.elementRef.nativeElement, node);
39
+ anchor.parentNode?.replaceChild(element, anchor);
31
40
  });
32
41
  }
33
42
  setContainer(container) {
34
43
  this._computedContainer.set(container);
35
44
  }
45
+ resolveContainer(container) {
46
+ if (!container) {
47
+ return null;
48
+ }
49
+ if (typeof container === 'string') {
50
+ return this.document?.querySelector(container) ?? null;
51
+ }
52
+ if (container instanceof ElementRef) {
53
+ return container.nativeElement ?? null;
54
+ }
55
+ // Anything that isn't a real element (e.g. a TemplateRef passed by mistake) falls back to
56
+ // the default container so the element still leaves the flow instead of staying in place.
57
+ return container instanceof HTMLElement ? container : null;
58
+ }
36
59
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxPortal, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
37
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxPortal, isStandalone: true, selector: "[rdxPortal]", inputs: { container: { classPropertyName: "container", publicName: "container", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
60
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxPortal, isStandalone: true, selector: "[rdxPortal]", inputs: { container: { classPropertyName: "container", publicName: "container", isSignal: true, isRequired: false, transformFunction: null } }, exportAs: ["rdxPortal"], ngImport: i0 }); }
38
61
  }
39
62
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxPortal, decorators: [{
40
63
  type: Directive,
41
64
  args: [{
42
- selector: '[rdxPortal]'
65
+ selector: '[rdxPortal]',
66
+ exportAs: 'rdxPortal'
43
67
  }]
44
68
  }], ctorParameters: () => [], propDecorators: { container: [{ type: i0.Input, args: [{ isSignal: true, alias: "container", required: false }] }] } });
45
69
 
@@ -1 +1 @@
1
- {"version":3,"file":"radix-ng-primitives-portal.mjs","sources":["../../../packages/primitives/portal/src/portal.ts","../../../packages/primitives/portal/radix-ng-primitives-portal.ts"],"sourcesContent":["import { DOCUMENT, isPlatformBrowser } from '@angular/common';\nimport { computed, DestroyRef, Directive, ElementRef, inject, input, linkedSignal, PLATFORM_ID } from '@angular/core';\n\n@Directive({\n selector: '[rdxPortal]'\n})\nexport class RdxPortal {\n private readonly elementRef = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly platformId = inject(PLATFORM_ID);\n private readonly document = inject(DOCUMENT, { optional: true }) as Document | null;\n private readonly destroyRef = inject(DestroyRef);\n\n /**\n * Specify a container element to portal the content into.\n */\n readonly container = input<ElementRef<HTMLElement>>();\n\n private _computedContainer = linkedSignal(this.container);\n readonly computedContainer = this._computedContainer;\n\n private readonly elementContainer = computed<HTMLElement | null>(() => {\n const provided = this.computedContainer()?.nativeElement ?? null;\n const body = this.document?.body ?? null;\n return provided ?? body;\n });\n\n constructor() {\n const isBrowser = isPlatformBrowser(this.platformId);\n if (!isBrowser || !this.document) {\n return;\n }\n\n const node = this.document.createComment('rdx-portal');\n\n this.elementRef.nativeElement.parentNode?.insertBefore(node, this.elementRef.nativeElement);\n this.elementContainer()?.appendChild(this.elementRef.nativeElement);\n\n this.destroyRef.onDestroy(() => {\n node.parentNode?.replaceChild(this.elementRef.nativeElement, node);\n });\n }\n\n setContainer(container: ElementRef<HTMLElement>) {\n this._computedContainer.set(container);\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;MAMa,SAAS,CAAA;AAoBlB,IAAA,WAAA,GAAA;AAnBiB,QAAA,IAAA,CAAA,UAAU,GAAG,MAAM,CAA0B,UAAU,CAAC;AACxD,QAAA,IAAA,CAAA,UAAU,GAAG,MAAM,CAAC,WAAW,CAAC;QAChC,IAAA,CAAA,QAAQ,GAAG,MAAM,CAAC,QAAQ,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAoB;AAClE,QAAA,IAAA,CAAA,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;AAEhD;;AAEG;QACM,IAAA,CAAA,SAAS,GAAG,KAAK,CAAA,IAAA,SAAA,GAAA,CAAA,SAAA,EAAA,EAAA,SAAA,EAAA,WAAA,EAAA,CAAA,8BAAA,EAAA,CAAA,CAA2B;AAE7C,QAAA,IAAA,CAAA,kBAAkB,GAAG,YAAY,CAAC,IAAI,CAAC,SAAS,yFAAC;AAChD,QAAA,IAAA,CAAA,iBAAiB,GAAG,IAAI,CAAC,kBAAkB;AAEnC,QAAA,IAAA,CAAA,gBAAgB,GAAG,QAAQ,CAAqB,MAAK;YAClE,MAAM,QAAQ,GAAG,IAAI,CAAC,iBAAiB,EAAE,EAAE,aAAa,IAAI,IAAI;YAChE,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,EAAE,IAAI,IAAI,IAAI;YACxC,OAAO,QAAQ,IAAI,IAAI;AAC3B,QAAA,CAAC,uFAAC;QAGE,MAAM,SAAS,GAAG,iBAAiB,CAAC,IAAI,CAAC,UAAU,CAAC;QACpD,IAAI,CAAC,SAAS,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE;YAC9B;QACJ;QAEA,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,YAAY,CAAC;AAEtD,QAAA,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,UAAU,EAAE,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC;AAC3F,QAAA,IAAI,CAAC,gBAAgB,EAAE,EAAE,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC;AAEnE,QAAA,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,MAAK;AAC3B,YAAA,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,aAAa,EAAE,IAAI,CAAC;AACtE,QAAA,CAAC,CAAC;IACN;AAEA,IAAA,YAAY,CAAC,SAAkC,EAAA;AAC3C,QAAA,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,SAAS,CAAC;IAC1C;8GAtCS,SAAS,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA,CAAA;kGAAT,SAAS,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,aAAA,EAAA,MAAA,EAAA,EAAA,SAAA,EAAA,EAAA,iBAAA,EAAA,WAAA,EAAA,UAAA,EAAA,WAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,CAAA,CAAA;;2FAAT,SAAS,EAAA,UAAA,EAAA,CAAA;kBAHrB,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACP,oBAAA,QAAQ,EAAE;AACb,iBAAA;;;ACLD;;AAEG;;;;"}
1
+ {"version":3,"file":"radix-ng-primitives-portal.mjs","sources":["../../../packages/primitives/portal/src/portal.ts","../../../packages/primitives/portal/radix-ng-primitives-portal.ts"],"sourcesContent":["import { DOCUMENT, isPlatformBrowser } from '@angular/common';\nimport {\n computed,\n DestroyRef,\n Directive,\n effect,\n ElementRef,\n inject,\n input,\n linkedSignal,\n PLATFORM_ID\n} from '@angular/core';\n\n/**\n * A target container for the portal. Accepts an `ElementRef`, a native element, or a CSS selector\n * resolved against the document.\n */\nexport type RdxPortalContainer = ElementRef<HTMLElement> | HTMLElement | string;\n\n@Directive({\n selector: '[rdxPortal]',\n exportAs: 'rdxPortal'\n})\nexport class RdxPortal {\n private readonly elementRef = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly platformId = inject(PLATFORM_ID);\n private readonly document = inject(DOCUMENT, { optional: true });\n private readonly destroyRef = inject(DestroyRef);\n\n /**\n * Specify a container to portal the content into. Can be an `ElementRef`, a native element, or a\n * CSS selector. Defaults to `document.body` when not set (or when a selector matches nothing).\n */\n readonly container = input<RdxPortalContainer>();\n\n private readonly _computedContainer = linkedSignal(this.container);\n readonly computedContainer = this._computedContainer.asReadonly();\n\n private readonly elementContainer = computed<HTMLElement | null>(() => {\n const provided = this.resolveContainer(this.computedContainer());\n const body = this.document?.body ?? null;\n return provided ?? body;\n });\n\n constructor() {\n const isBrowser = isPlatformBrowser(this.platformId);\n if (!isBrowser || !this.document) {\n return;\n }\n\n const element = this.elementRef.nativeElement;\n // Anchor the original DOM position with a comment node, so the element can be restored\n // exactly where it was when the directive is destroyed.\n const anchor = this.document.createComment('rdx-portal');\n element.parentNode?.insertBefore(anchor, element);\n\n // Move reactively: the effect runs after inputs are bound (so `container` is respected on\n // first render) and re-runs whenever the target container changes. `appendChild` relocates\n // the element, it does not clone it.\n effect(() => {\n this.elementContainer()?.appendChild(element);\n });\n\n this.destroyRef.onDestroy(() => {\n anchor.parentNode?.replaceChild(element, anchor);\n });\n }\n\n setContainer(container: RdxPortalContainer) {\n this._computedContainer.set(container);\n }\n\n private resolveContainer(container: RdxPortalContainer | undefined): HTMLElement | null {\n if (!container) {\n return null;\n }\n if (typeof container === 'string') {\n return this.document?.querySelector<HTMLElement>(container) ?? null;\n }\n if (container instanceof ElementRef) {\n return container.nativeElement ?? null;\n }\n // Anything that isn't a real element (e.g. a TemplateRef passed by mistake) falls back to\n // the default container so the element still leaves the flow instead of staying in place.\n return container instanceof HTMLElement ? container : null;\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
@@ -1,152 +1,6 @@
1
- import { Observable, EMPTY, of, Subject, endWith, fromEvent, filter, takeUntil, timer, race } from 'rxjs';
1
+ import { isPlatformBrowser } from '@angular/common';
2
2
  import * as i0 from '@angular/core';
3
- import { InjectionToken, inject, ViewContainerRef, TemplateRef, effect, Directive } from '@angular/core';
4
-
5
- /**
6
- * Ensures that the observable stream runs inside Angular's NgZone.
7
- *
8
- * This function is a higher-order function that takes an observable stream as input and ensures
9
- * that all emissions, errors, and completion notifications are run inside Angular's NgZone. This
10
- * is particularly useful for ensuring that change detection is triggered properly in Angular
11
- * applications.
12
- *
13
- * @template T - The type of the items emitted by the observable.
14
- * @param {NgZone} zone - The Angular zone to control the change detection context.
15
- * @returns {(source: Observable<T>) => Observable<T>} - A function that takes an observable as input
16
- * and returns an observable that runs inside Angular's NgZone.
17
- *
18
- * Example usage:
19
- *
20
- * const source$ = of('some value');
21
- * const zoned$ = source$.pipe(runInZone(zone));
22
- * zoned$.subscribe(value => {
23
- * console.log('Value:', value);
24
- * });
25
- */
26
- function runInZone(zone) {
27
- return (source) => new Observable((observer) => source.subscribe({
28
- next: (value) => zone.run(() => observer.next(value)),
29
- error: (err) => zone.run(() => observer.error(err)),
30
- complete: () => zone.run(() => observer.complete())
31
- }));
32
- }
33
- /**
34
- * Calculates the total transition duration in milliseconds for a given HTML element.
35
- *
36
- * This function retrieves the computed style of the specified element and extracts the
37
- * transition duration and delay properties. It then converts these values from seconds
38
- * to milliseconds and returns their sum, representing the total transition duration.
39
- *
40
- * @param {HTMLElement} element - The HTML element for which to calculate the transition duration.
41
- * @returns {number} - The total transition duration in milliseconds.
42
- *
43
- * Example usage:
44
- *
45
- * const durationMs = getTransitionDurationMs(element);
46
- * console.log(`Transition duration: ${durationMs} ms`);
47
- */
48
- function getTransitionDurationMs(element) {
49
- const { transitionDelay, transitionDuration } = window.getComputedStyle(element);
50
- const transitionDelaySec = parseFloat(transitionDelay);
51
- const transitionDurationSec = parseFloat(transitionDuration);
52
- return (transitionDelaySec + transitionDurationSec) * 1000;
53
- }
54
- function triggerReflow(element) {
55
- return (element || document.body).getBoundingClientRect();
56
- }
57
-
58
- const noopFn = () => {
59
- /* Noop */
60
- };
61
- const TransitionsMap = new Map();
62
- /**
63
- * Manages the presence of an element with optional transition animation.
64
- *
65
- * @template T - The type of the context object used in the transition.
66
- * @param {NgZone} zone - The Angular zone to control the change detection context.
67
- * @param {HTMLElement} element - The target HTML element to apply the transition.
68
- * @param {TransitionOptions<T>} options - Options for controlling the transition behavior.
69
- * @param {T} [options.context] - An optional context object to pass through the transition.
70
- * @param {boolean} options.animation - A flag indicating if the transition should be animated.
71
- * @param {'start' | 'continue' | 'stop'} options.state - The desired state of the transition.
72
- * @param {TransitionStartFn<T>} startFn - A function to start the transition.
73
- * @returns {Observable<void>} - An observable that emits when the transition completes.
74
- *
75
- * The `usePresence` function is designed to manage the presence and visibility of an HTML element,
76
- * optionally applying a transition animation. It utilizes Angular's NgZone for efficient change
77
- * detection management and allows for different states of transitions ('start', 'continue', 'stop').
78
- * The function takes a start function to handle the beginning of the transition and returns an
79
- * observable that completes when the transition ends.
80
- *
81
- * Example usage:
82
- *
83
- * const options: TransitionOptions<MyContext> = {
84
- * context: {}, // your context object
85
- * animation: true,
86
- * state: 'start'
87
- * };
88
- *
89
- * const startFn: TransitionStartFn<MyContext> = (el, animation, context) => {
90
- * el.classList.add('active');
91
- * return () => el.classList.remove('active');
92
- * };
93
- *
94
- * usePresence(zone, element, startFn, options).subscribe(() => {
95
- * console.log('Transition completed');
96
- * });
97
- */
98
- const usePresence = (zone, element, startFn, options) => {
99
- let context = options.context || {};
100
- const transitionTimerDelayMs = options.transitionTimerDelayMs ?? 5;
101
- const state = options.state ?? 'stop';
102
- const running = TransitionsMap.get(element);
103
- if (running) {
104
- switch (state) {
105
- case 'continue':
106
- return EMPTY;
107
- case 'stop':
108
- zone.run(() => running.transition$.complete());
109
- context = { ...running.context, ...context };
110
- TransitionsMap.delete(element);
111
- break;
112
- }
113
- }
114
- const endFn = startFn(element, options.animation, context) || noopFn;
115
- if (!options.animation || window.getComputedStyle(element).transitionProperty === 'none') {
116
- zone.run(() => endFn());
117
- return of(undefined).pipe(runInZone(zone));
118
- }
119
- const transition$ = new Subject();
120
- const finishTransition$ = new Subject();
121
- const stop$ = transition$.pipe(endWith(true));
122
- TransitionsMap.set(element, {
123
- transition$,
124
- complete: () => {
125
- finishTransition$.next();
126
- finishTransition$.complete();
127
- },
128
- context
129
- });
130
- const transitionDurationMs = getTransitionDurationMs(element);
131
- zone.runOutsideAngular(() => {
132
- const transitionEnd$ = fromEvent(element, 'transitionend').pipe(filter(({ target }) => target === element), takeUntil(stop$));
133
- const timer$ = timer(transitionDurationMs + transitionTimerDelayMs).pipe(takeUntil(stop$));
134
- race(timer$, transitionEnd$, finishTransition$)
135
- .pipe(takeUntil(stop$))
136
- .subscribe(() => {
137
- TransitionsMap.delete(element);
138
- zone.run(() => {
139
- endFn();
140
- transition$.next();
141
- transition$.complete();
142
- });
143
- });
144
- });
145
- return transition$.asObservable();
146
- };
147
- const completeTransition = (element) => {
148
- TransitionsMap.get(element)?.complete();
149
- };
3
+ import { InjectionToken, inject, ViewContainerRef, TemplateRef, Injector, PLATFORM_ID, effect, afterNextRender, DestroyRef, Directive } from '@angular/core';
150
4
 
151
5
  const RDX_PRESENCE_CONTEXT = new InjectionToken('RdxPresenceContext');
152
6
  /**
@@ -160,124 +14,158 @@ const RDX_PRESENCE_CONTEXT = new InjectionToken('RdxPresenceContext');
160
14
  function provideRdxPresenceContext(contextFactory) {
161
15
  return { provide: RDX_PRESENCE_CONTEXT, useFactory: contextFactory };
162
16
  }
17
+ /**
18
+ * State machine mirroring `@radix-ui/react-presence`.
19
+ *
20
+ * - `mounted` — content rendered, `present` is `true`.
21
+ * - `unmountSuspended` — `present` flipped to `false` but an exit animation is running;
22
+ * the content stays in the DOM until the animation ends.
23
+ * - `unmounted` — content removed.
24
+ */
25
+ const MACHINE = {
26
+ mounted: { UNMOUNT: 'unmounted', ANIMATION_OUT: 'unmountSuspended' },
27
+ unmountSuspended: { MOUNT: 'mounted', ANIMATION_END: 'unmounted' },
28
+ unmounted: { MOUNT: 'mounted' }
29
+ };
30
+ /**
31
+ * Headless structural directive that conditionally renders its template based on a reactive
32
+ * `present` signal supplied through {@link RDX_PRESENCE_CONTEXT}.
33
+ *
34
+ * Unlike a plain `*ngIf`, it keeps the content mounted while a CSS exit animation
35
+ * (`@keyframes` applied for the closed state) is running, and unmounts it only once that
36
+ * animation finishes. If the content has no exit animation, it unmounts immediately.
37
+ */
163
38
  class RdxPresenceDirective {
164
39
  constructor() {
165
40
  this.context = inject(RDX_PRESENCE_CONTEXT);
166
41
  this.viewContainerRef = inject(ViewContainerRef);
167
42
  this.templateRef = inject((TemplateRef));
168
- this.effectRef = effect((onCleanup) => {
169
- if (this.context.present()) {
170
- const view = this.viewContainerRef.createEmbeddedView(this.templateRef, { $implicit: this.context });
171
- onCleanup(() => view.destroy());
43
+ this.injector = inject(Injector);
44
+ this.isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
45
+ this.viewRef = null;
46
+ this.node = null;
47
+ this.removeListeners = null;
48
+ this.prevAnimationName = 'none';
49
+ this.prevPresent = this.context.present();
50
+ this.state = this.prevPresent ? 'mounted' : 'unmounted';
51
+ if (this.prevPresent) {
52
+ this.mountView();
53
+ }
54
+ effect(() => {
55
+ const present = this.context.present();
56
+ if (present === this.prevPresent) {
57
+ return;
58
+ }
59
+ this.prevPresent = present;
60
+ if (present) {
61
+ // Mount synchronously so the enter animation can start on this frame.
62
+ this.send('MOUNT');
63
+ }
64
+ else if (this.isBrowser) {
65
+ // Defer the unmount decision until the next render, so the consumer's
66
+ // `data-state` (and therefore the exit `@keyframes`) is applied to the DOM
67
+ // before we read the computed animation name.
68
+ afterNextRender(() => this.evaluateExit(), { injector: this.injector });
172
69
  }
173
- }, ...(ngDevMode ? [{ debugName: "effectRef" }] : /* istanbul ignore next */ []));
70
+ else {
71
+ this.send('UNMOUNT');
72
+ }
73
+ });
74
+ inject(DestroyRef).onDestroy(() => this.destroyView());
174
75
  }
175
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxPresenceDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
176
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxPresenceDirective, isStandalone: true, ngImport: i0 }); }
177
- }
178
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxPresenceDirective, decorators: [{
179
- type: Directive
180
- }] });
181
-
182
- // Define constants for class names
183
- const COLLAPSE_CLASS = 'collapse';
184
- const COLLAPSING_CLASS = 'collapsing';
185
- const SHOW_CLASS = 'show';
186
- /**
187
- * Function to handle the start of a collapsing transition.
188
- *
189
- * @param element - The HTML element to animate.
190
- * @param animation - Whether to use animation or not.
191
- * @param context - The context containing direction and dimension information.
192
- * @returns A function to clean up the animation.
193
- */
194
- const transitionCollapsing = (element, animation, context) => {
195
- const { direction, dimension } = context;
196
- let { maxSize } = context;
197
- const { classList } = element;
198
- /**
199
- * Sets initial classes based on the direction.
200
- */
201
- function setInitialClasses() {
202
- classList.add(COLLAPSE_CLASS);
203
- if (direction === 'show') {
204
- classList.add(SHOW_CLASS);
76
+ /** Decides whether to suspend the unmount for an exit animation (port of Radix' logic). */
77
+ evaluateExit() {
78
+ // Re-opened before this callback ran — keep the content mounted.
79
+ if (this.state !== 'mounted' || this.context.present()) {
80
+ return;
81
+ }
82
+ const styles = this.getComputedStyles();
83
+ const currentAnimationName = styles?.animationName || 'none';
84
+ if (currentAnimationName === 'none' || styles?.display === 'none') {
85
+ // No exit animation (or the element is hidden) — unmount right away.
86
+ this.send('UNMOUNT');
205
87
  }
206
88
  else {
207
- classList.remove(SHOW_CLASS);
89
+ // Only suspend the unmount if the closed state actually starts a *different* animation.
90
+ const isAnimating = this.prevAnimationName !== currentAnimationName;
91
+ this.send(isAnimating ? 'ANIMATION_OUT' : 'UNMOUNT');
208
92
  }
209
93
  }
210
- if (!animation) {
211
- setInitialClasses();
212
- return;
213
- }
214
- if (!maxSize) {
215
- maxSize = measureCollapsingElementDimensionPx(element, dimension);
216
- context.maxSize = maxSize;
217
- // Fix the height before starting the animation
218
- element.style[dimension] = direction !== 'show' ? maxSize : '0px';
219
- classList.remove(COLLAPSE_CLASS, COLLAPSING_CLASS, 'show');
220
- triggerReflow(element);
221
- // Start the animation
222
- classList.add(COLLAPSING_CLASS);
94
+ send(event) {
95
+ const next = MACHINE[this.state][event];
96
+ if (next === undefined || next === this.state) {
97
+ return;
98
+ }
99
+ this.state = next;
100
+ if (next === 'mounted') {
101
+ if (this.viewRef) {
102
+ // Re-opened while an exit animation was running — refresh the tracked animation.
103
+ this.prevAnimationName = this.getAnimationName();
104
+ }
105
+ else {
106
+ this.mountView();
107
+ }
108
+ }
109
+ else if (next === 'unmounted') {
110
+ this.destroyView();
111
+ }
112
+ // `unmountSuspended` keeps the existing view mounted until ANIMATION_END.
223
113
  }
224
- element.style[dimension] = direction === 'show' ? maxSize : '0px';
225
- return () => {
226
- setInitialClasses();
227
- classList.remove(COLLAPSING_CLASS);
228
- element.style[dimension] = '';
229
- };
230
- };
231
- /**
232
- * Measures the dimension of the collapsing element in pixels.
233
- *
234
- * @param element - The HTML element to measure.
235
- * @param dimension - The dimension ('width' or 'height') to measure.
236
- * @returns The size of the dimension in pixels.
237
- */
238
- function measureCollapsingElementDimensionPx(element, dimension) {
239
- // SSR fix
240
- if (typeof navigator === 'undefined') {
241
- return '0px';
114
+ mountView() {
115
+ this.viewRef = this.viewContainerRef.createEmbeddedView(this.templateRef);
116
+ this.node = this.viewRef.rootNodes.find((n) => n instanceof HTMLElement) ?? null;
117
+ if (this.node && this.isBrowser) {
118
+ this.prevAnimationName = this.getAnimationName();
119
+ this.addAnimationListeners(this.node);
120
+ }
242
121
  }
243
- const { classList } = element;
244
- const hasShownClass = classList.contains(SHOW_CLASS);
245
- if (!hasShownClass) {
246
- classList.add(SHOW_CLASS);
122
+ destroyView() {
123
+ this.removeListeners?.();
124
+ this.removeListeners = null;
125
+ this.viewRef?.destroy();
126
+ this.viewRef = null;
127
+ this.node = null;
247
128
  }
248
- element.style[dimension] = '';
249
- const dimensionSize = element.getBoundingClientRect()[dimension] + 'px';
250
- if (!hasShownClass) {
251
- classList.remove(SHOW_CLASS);
129
+ addAnimationListeners(node) {
130
+ const onStart = (event) => {
131
+ if (event.target === node) {
132
+ this.prevAnimationName = this.getAnimationName();
133
+ }
134
+ };
135
+ const onEnd = (event) => {
136
+ const isCurrentAnimation = this.getAnimationName().includes(event.animationName);
137
+ if (event.target === node && isCurrentAnimation) {
138
+ this.send('ANIMATION_END');
139
+ }
140
+ };
141
+ node.addEventListener('animationstart', onStart);
142
+ node.addEventListener('animationcancel', onEnd);
143
+ node.addEventListener('animationend', onEnd);
144
+ this.removeListeners = () => {
145
+ node.removeEventListener('animationstart', onStart);
146
+ node.removeEventListener('animationcancel', onEnd);
147
+ node.removeEventListener('animationend', onEnd);
148
+ };
252
149
  }
253
- return dimensionSize;
254
- }
255
-
256
- const toastFadeInTransition = (element, animation) => {
257
- const { classList } = element;
258
- if (animation) {
259
- classList.add('fade');
150
+ getComputedStyles() {
151
+ return this.node && this.isBrowser ? getComputedStyle(this.node) : null;
260
152
  }
261
- else {
262
- classList.add('show');
263
- return;
153
+ getAnimationName() {
154
+ return this.getComputedStyles()?.animationName || 'none';
264
155
  }
265
- triggerReflow(element);
266
- classList.add('show', 'showing');
267
- return () => {
268
- classList.remove('showing');
269
- };
270
- };
271
- const toastFadeOutTransition = ({ classList }) => {
272
- classList.add('showing');
273
- return () => {
274
- classList.remove('show', 'showing');
275
- };
276
- };
156
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxPresenceDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
157
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxPresenceDirective, isStandalone: true, ngImport: i0 }); }
158
+ }
159
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxPresenceDirective, decorators: [{
160
+ type: Directive,
161
+ args: [{
162
+ standalone: true
163
+ }]
164
+ }], ctorParameters: () => [] });
277
165
 
278
166
  /**
279
167
  * Generated bundle index. Do not edit.
280
168
  */
281
169
 
282
- export { RDX_PRESENCE_CONTEXT, RdxPresenceDirective, completeTransition, provideRdxPresenceContext, toastFadeInTransition, toastFadeOutTransition, transitionCollapsing, usePresence };
170
+ export { RDX_PRESENCE_CONTEXT, RdxPresenceDirective, provideRdxPresenceContext };
283
171
  //# sourceMappingURL=radix-ng-primitives-presence.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"radix-ng-primitives-presence.mjs","sources":["../../../packages/primitives/presence/src/utils.ts","../../../packages/primitives/presence/src/presence.ts","../../../packages/primitives/presence/src/presence.directive.ts","../../../packages/primitives/presence/src/transitions/transition.collapse.ts","../../../packages/primitives/presence/src/transitions/transition.toast.ts","../../../packages/primitives/presence/radix-ng-primitives-presence.ts"],"sourcesContent":["import { NgZone } from '@angular/core';\nimport { Observable } from 'rxjs';\n\n/**\n * Ensures that the observable stream runs inside Angular's NgZone.\n *\n * This function is a higher-order function that takes an observable stream as input and ensures\n * that all emissions, errors, and completion notifications are run inside Angular's NgZone. This\n * is particularly useful for ensuring that change detection is triggered properly in Angular\n * applications.\n *\n * @template T - The type of the items emitted by the observable.\n * @param {NgZone} zone - The Angular zone to control the change detection context.\n * @returns {(source: Observable<T>) => Observable<T>} - A function that takes an observable as input\n * and returns an observable that runs inside Angular's NgZone.\n *\n * Example usage:\n *\n * const source$ = of('some value');\n * const zoned$ = source$.pipe(runInZone(zone));\n * zoned$.subscribe(value => {\n * console.log('Value:', value);\n * });\n */\nfunction runInZone<T>(zone: NgZone): (source: Observable<T>) => Observable<T> {\n return (source: Observable<T>) =>\n new Observable((observer) =>\n source.subscribe({\n next: (value) => zone.run(() => observer.next(value)),\n error: (err) => zone.run(() => observer.error(err)),\n complete: () => zone.run(() => observer.complete())\n })\n );\n}\n\n/**\n * Calculates the total transition duration in milliseconds for a given HTML element.\n *\n * This function retrieves the computed style of the specified element and extracts the\n * transition duration and delay properties. It then converts these values from seconds\n * to milliseconds and returns their sum, representing the total transition duration.\n *\n * @param {HTMLElement} element - The HTML element for which to calculate the transition duration.\n * @returns {number} - The total transition duration in milliseconds.\n *\n * Example usage:\n *\n * const durationMs = getTransitionDurationMs(element);\n * console.log(`Transition duration: ${durationMs} ms`);\n */\nfunction getTransitionDurationMs(element: HTMLElement): number {\n const { transitionDelay, transitionDuration } = window.getComputedStyle(element);\n const transitionDelaySec = parseFloat(transitionDelay);\n const transitionDurationSec = parseFloat(transitionDuration);\n\n return (transitionDelaySec + transitionDurationSec) * 1000;\n}\n\nexport { getTransitionDurationMs, runInZone };\n\nexport function triggerReflow(element: HTMLElement) {\n return (element || document.body).getBoundingClientRect();\n}\n","import { NgZone } from '@angular/core';\nimport { EMPTY, endWith, filter, fromEvent, Observable, of, race, Subject, takeUntil, timer } from 'rxjs';\nimport { TransitionContext, TransitionEndFn, TransitionOptions, TransitionStartFn } from './types';\nimport { getTransitionDurationMs, runInZone } from './utils';\n\nconst noopFn: TransitionEndFn = () => {\n /* Noop */\n};\nconst TransitionsMap = new Map<HTMLElement, TransitionContext<any>>();\n\n/**\n * Manages the presence of an element with optional transition animation.\n *\n * @template T - The type of the context object used in the transition.\n * @param {NgZone} zone - The Angular zone to control the change detection context.\n * @param {HTMLElement} element - The target HTML element to apply the transition.\n * @param {TransitionOptions<T>} options - Options for controlling the transition behavior.\n * @param {T} [options.context] - An optional context object to pass through the transition.\n * @param {boolean} options.animation - A flag indicating if the transition should be animated.\n * @param {'start' | 'continue' | 'stop'} options.state - The desired state of the transition.\n * @param {TransitionStartFn<T>} startFn - A function to start the transition.\n * @returns {Observable<void>} - An observable that emits when the transition completes.\n *\n * The `usePresence` function is designed to manage the presence and visibility of an HTML element,\n * optionally applying a transition animation. It utilizes Angular's NgZone for efficient change\n * detection management and allows for different states of transitions ('start', 'continue', 'stop').\n * The function takes a start function to handle the beginning of the transition and returns an\n * observable that completes when the transition ends.\n *\n * Example usage:\n *\n * const options: TransitionOptions<MyContext> = {\n * context: {}, // your context object\n * animation: true,\n * state: 'start'\n * };\n *\n * const startFn: TransitionStartFn<MyContext> = (el, animation, context) => {\n * el.classList.add('active');\n * return () => el.classList.remove('active');\n * };\n *\n * usePresence(zone, element, startFn, options).subscribe(() => {\n * console.log('Transition completed');\n * });\n */\nconst usePresence = <T>(\n zone: NgZone,\n element: HTMLElement,\n startFn: TransitionStartFn<T>,\n options: TransitionOptions<T>\n): Observable<void> => {\n let context = options.context || <T>{};\n\n const transitionTimerDelayMs = options.transitionTimerDelayMs ?? 5;\n const state = options.state ?? 'stop';\n\n const running = TransitionsMap.get(element);\n\n if (running) {\n switch (state) {\n case 'continue':\n return EMPTY;\n case 'stop':\n zone.run(() => running.transition$.complete());\n context = { ...running.context, ...context };\n TransitionsMap.delete(element);\n break;\n }\n }\n const endFn = startFn(element, options.animation, context) || noopFn;\n\n if (!options.animation || window.getComputedStyle(element).transitionProperty === 'none') {\n zone.run(() => endFn());\n return of(undefined).pipe(runInZone(zone));\n }\n\n const transition$ = new Subject<void>();\n const finishTransition$ = new Subject<void>();\n const stop$ = transition$.pipe(endWith(true));\n\n TransitionsMap.set(element, {\n transition$,\n complete: () => {\n finishTransition$.next();\n finishTransition$.complete();\n },\n context\n });\n\n const transitionDurationMs = getTransitionDurationMs(element);\n\n zone.runOutsideAngular(() => {\n const transitionEnd$ = fromEvent<TransitionEvent>(element, 'transitionend').pipe(\n filter(({ target }) => target === element),\n takeUntil(stop$)\n );\n const timer$ = timer(transitionDurationMs + transitionTimerDelayMs).pipe(takeUntil(stop$));\n\n race(timer$, transitionEnd$, finishTransition$)\n .pipe(takeUntil(stop$))\n .subscribe(() => {\n TransitionsMap.delete(element);\n zone.run(() => {\n endFn();\n transition$.next();\n transition$.complete();\n });\n });\n });\n\n return transition$.asObservable();\n};\n\nconst completeTransition = (element: HTMLElement) => {\n TransitionsMap.get(element)?.complete();\n};\n\nexport { completeTransition, usePresence };\n","import {\n Directive,\n effect,\n inject,\n InjectionToken,\n Provider,\n Signal,\n TemplateRef,\n ViewContainerRef\n} from '@angular/core';\n\n/**\n * Context interface for RdxPresence directive\n * Contains a Signal that indicates whether the content should be present in the DOM\n */\nexport type RdxPresenceContext = {\n present: Signal<boolean>;\n};\n\nexport const RDX_PRESENCE_CONTEXT = new InjectionToken<RdxPresenceContext>('RdxPresenceContext');\n\n/**\n * Factory provider helper.\n * In your parent component/directive you can write:\n *\n * providers: [\n * provideRdxPresenceContext(() => ({ present: myBooleanSignal }))\n * ]\n */\nexport function provideRdxPresenceContext(contextFactory: () => RdxPresenceContext): Provider {\n return { provide: RDX_PRESENCE_CONTEXT, useFactory: contextFactory };\n}\n\n@Directive()\nexport class RdxPresenceDirective {\n private readonly context = inject(RDX_PRESENCE_CONTEXT);\n private readonly viewContainerRef = inject(ViewContainerRef);\n private readonly templateRef = inject(TemplateRef<RdxPresenceContext>);\n\n private effectRef = effect((onCleanup) => {\n if (this.context.present()) {\n const view = this.viewContainerRef.createEmbeddedView(this.templateRef, { $implicit: this.context });\n\n onCleanup(() => view.destroy());\n }\n });\n}\n","import { TransitionStartFn } from '../types';\nimport { triggerReflow } from '../utils';\n\nexport type CollapseContext = {\n direction: 'show' | 'hide';\n dimension: 'width' | 'height';\n maxSize?: string;\n};\n\n// Define constants for class names\nconst COLLAPSE_CLASS = 'collapse';\nconst COLLAPSING_CLASS = 'collapsing';\nconst SHOW_CLASS = 'show';\n/**\n * Function to handle the start of a collapsing transition.\n *\n * @param element - The HTML element to animate.\n * @param animation - Whether to use animation or not.\n * @param context - The context containing direction and dimension information.\n * @returns A function to clean up the animation.\n */\nexport const transitionCollapsing: TransitionStartFn<CollapseContext> = (\n element: HTMLElement,\n animation: boolean,\n context: CollapseContext\n) => {\n const { direction, dimension } = context;\n let { maxSize } = context;\n const { classList } = element;\n\n /**\n * Sets initial classes based on the direction.\n */\n function setInitialClasses() {\n classList.add(COLLAPSE_CLASS);\n if (direction === 'show') {\n classList.add(SHOW_CLASS);\n } else {\n classList.remove(SHOW_CLASS);\n }\n }\n\n if (!animation) {\n setInitialClasses();\n return;\n }\n\n if (!maxSize) {\n maxSize = measureCollapsingElementDimensionPx(element, dimension);\n context.maxSize = maxSize;\n\n // Fix the height before starting the animation\n element.style[dimension] = direction !== 'show' ? maxSize : '0px';\n\n classList.remove(COLLAPSE_CLASS, COLLAPSING_CLASS, 'show');\n\n triggerReflow(element);\n\n // Start the animation\n classList.add(COLLAPSING_CLASS);\n }\n\n element.style[dimension] = direction === 'show' ? maxSize : '0px';\n\n return () => {\n setInitialClasses();\n classList.remove(COLLAPSING_CLASS);\n element.style[dimension] = '';\n };\n};\n\n/**\n * Measures the dimension of the collapsing element in pixels.\n *\n * @param element - The HTML element to measure.\n * @param dimension - The dimension ('width' or 'height') to measure.\n * @returns The size of the dimension in pixels.\n */\nfunction measureCollapsingElementDimensionPx(element: HTMLElement, dimension: 'width' | 'height'): string {\n // SSR fix\n if (typeof navigator === 'undefined') {\n return '0px';\n }\n\n const { classList } = element;\n const hasShownClass = classList.contains(SHOW_CLASS);\n if (!hasShownClass) {\n classList.add(SHOW_CLASS);\n }\n\n element.style[dimension] = '';\n const dimensionSize = element.getBoundingClientRect()[dimension] + 'px';\n\n if (!hasShownClass) {\n classList.remove(SHOW_CLASS);\n }\n\n return dimensionSize;\n}\n","import { TransitionStartFn } from '../types';\nimport { triggerReflow } from '../utils';\n\nexport const toastFadeInTransition: TransitionStartFn = (element: HTMLElement, animation: boolean) => {\n const { classList } = element;\n\n if (animation) {\n classList.add('fade');\n } else {\n classList.add('show');\n return;\n }\n\n triggerReflow(element);\n classList.add('show', 'showing');\n\n return () => {\n classList.remove('showing');\n };\n};\n\nexport const toastFadeOutTransition: TransitionStartFn = ({ classList }: HTMLElement) => {\n classList.add('showing');\n return () => {\n classList.remove('show', 'showing');\n };\n};\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;AAGA;;;;;;;;;;;;;;;;;;;;AAoBG;AACH,SAAS,SAAS,CAAI,IAAY,EAAA;AAC9B,IAAA,OAAO,CAAC,MAAqB,KACzB,IAAI,UAAU,CAAC,CAAC,QAAQ,KACpB,MAAM,CAAC,SAAS,CAAC;AACb,QAAA,IAAI,EAAE,CAAC,KAAK,KAAK,IAAI,CAAC,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACrD,QAAA,KAAK,EAAE,CAAC,GAAG,KAAK,IAAI,CAAC,GAAG,CAAC,MAAM,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;AACnD,QAAA,QAAQ,EAAE,MAAM,IAAI,CAAC,GAAG,CAAC,MAAM,QAAQ,CAAC,QAAQ,EAAE;AACrD,KAAA,CAAC,CACL;AACT;AAEA;;;;;;;;;;;;;;AAcG;AACH,SAAS,uBAAuB,CAAC,OAAoB,EAAA;AACjD,IAAA,MAAM,EAAE,eAAe,EAAE,kBAAkB,EAAE,GAAG,MAAM,CAAC,gBAAgB,CAAC,OAAO,CAAC;AAChF,IAAA,MAAM,kBAAkB,GAAG,UAAU,CAAC,eAAe,CAAC;AACtD,IAAA,MAAM,qBAAqB,GAAG,UAAU,CAAC,kBAAkB,CAAC;AAE5D,IAAA,OAAO,CAAC,kBAAkB,GAAG,qBAAqB,IAAI,IAAI;AAC9D;AAIM,SAAU,aAAa,CAAC,OAAoB,EAAA;IAC9C,OAAO,CAAC,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,qBAAqB,EAAE;AAC7D;;ACzDA,MAAM,MAAM,GAAoB,MAAK;;AAErC,CAAC;AACD,MAAM,cAAc,GAAG,IAAI,GAAG,EAAuC;AAErE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCG;AACH,MAAM,WAAW,GAAG,CAChB,IAAY,EACZ,OAAoB,EACpB,OAA6B,EAC7B,OAA6B,KACX;AAClB,IAAA,IAAI,OAAO,GAAG,OAAO,CAAC,OAAO,IAAO,EAAE;AAEtC,IAAA,MAAM,sBAAsB,GAAG,OAAO,CAAC,sBAAsB,IAAI,CAAC;AAClE,IAAA,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,MAAM;IAErC,MAAM,OAAO,GAAG,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC;IAE3C,IAAI,OAAO,EAAE;QACT,QAAQ,KAAK;AACT,YAAA,KAAK,UAAU;AACX,gBAAA,OAAO,KAAK;AAChB,YAAA,KAAK,MAAM;AACP,gBAAA,IAAI,CAAC,GAAG,CAAC,MAAM,OAAO,CAAC,WAAW,CAAC,QAAQ,EAAE,CAAC;gBAC9C,OAAO,GAAG,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,GAAG,OAAO,EAAE;AAC5C,gBAAA,cAAc,CAAC,MAAM,CAAC,OAAO,CAAC;gBAC9B;;IAEZ;AACA,IAAA,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,IAAI,MAAM;AAEpE,IAAA,IAAI,CAAC,OAAO,CAAC,SAAS,IAAI,MAAM,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,kBAAkB,KAAK,MAAM,EAAE;QACtF,IAAI,CAAC,GAAG,CAAC,MAAM,KAAK,EAAE,CAAC;AACvB,QAAA,OAAO,EAAE,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IAC9C;AAEA,IAAA,MAAM,WAAW,GAAG,IAAI,OAAO,EAAQ;AACvC,IAAA,MAAM,iBAAiB,GAAG,IAAI,OAAO,EAAQ;IAC7C,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;AAE7C,IAAA,cAAc,CAAC,GAAG,CAAC,OAAO,EAAE;QACxB,WAAW;QACX,QAAQ,EAAE,MAAK;YACX,iBAAiB,CAAC,IAAI,EAAE;YACxB,iBAAiB,CAAC,QAAQ,EAAE;QAChC,CAAC;QACD;AACH,KAAA,CAAC;AAEF,IAAA,MAAM,oBAAoB,GAAG,uBAAuB,CAAC,OAAO,CAAC;AAE7D,IAAA,IAAI,CAAC,iBAAiB,CAAC,MAAK;AACxB,QAAA,MAAM,cAAc,GAAG,SAAS,CAAkB,OAAO,EAAE,eAAe,CAAC,CAAC,IAAI,CAC5E,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,MAAM,KAAK,OAAO,CAAC,EAC1C,SAAS,CAAC,KAAK,CAAC,CACnB;AACD,QAAA,MAAM,MAAM,GAAG,KAAK,CAAC,oBAAoB,GAAG,sBAAsB,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;AAE1F,QAAA,IAAI,CAAC,MAAM,EAAE,cAAc,EAAE,iBAAiB;AACzC,aAAA,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;aACrB,SAAS,CAAC,MAAK;AACZ,YAAA,cAAc,CAAC,MAAM,CAAC,OAAO,CAAC;AAC9B,YAAA,IAAI,CAAC,GAAG,CAAC,MAAK;AACV,gBAAA,KAAK,EAAE;gBACP,WAAW,CAAC,IAAI,EAAE;gBAClB,WAAW,CAAC,QAAQ,EAAE;AAC1B,YAAA,CAAC,CAAC;AACN,QAAA,CAAC,CAAC;AACV,IAAA,CAAC,CAAC;AAEF,IAAA,OAAO,WAAW,CAAC,YAAY,EAAE;AACrC;AAEA,MAAM,kBAAkB,GAAG,CAAC,OAAoB,KAAI;IAChD,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE;AAC3C;;MCjGa,oBAAoB,GAAG,IAAI,cAAc,CAAqB,oBAAoB;AAE/F;;;;;;;AAOG;AACG,SAAU,yBAAyB,CAAC,cAAwC,EAAA;IAC9E,OAAO,EAAE,OAAO,EAAE,oBAAoB,EAAE,UAAU,EAAE,cAAc,EAAE;AACxE;MAGa,oBAAoB,CAAA;AADjC,IAAA,WAAA,GAAA;AAEqB,QAAA,IAAA,CAAA,OAAO,GAAG,MAAM,CAAC,oBAAoB,CAAC;AACtC,QAAA,IAAA,CAAA,gBAAgB,GAAG,MAAM,CAAC,gBAAgB,CAAC;AAC3C,QAAA,IAAA,CAAA,WAAW,GAAG,MAAM,EAAC,WAA+B,EAAC;AAE9D,QAAA,IAAA,CAAA,SAAS,GAAG,MAAM,CAAC,CAAC,SAAS,KAAI;AACrC,YAAA,IAAI,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE;gBACxB,MAAM,IAAI,GAAG,IAAI,CAAC,gBAAgB,CAAC,kBAAkB,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC;gBAEpG,SAAS,CAAC,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;YACnC;AACJ,QAAA,CAAC,gFAAC;AACL,IAAA;8GAZY,oBAAoB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA,CAAA;kGAApB,oBAAoB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,EAAA,EAAA,CAAA,CAAA;;2FAApB,oBAAoB,EAAA,UAAA,EAAA,CAAA;kBADhC;;;ACxBD;AACA,MAAM,cAAc,GAAG,UAAU;AACjC,MAAM,gBAAgB,GAAG,YAAY;AACrC,MAAM,UAAU,GAAG,MAAM;AACzB;;;;;;;AAOG;AACI,MAAM,oBAAoB,GAAuC,CACpE,OAAoB,EACpB,SAAkB,EAClB,OAAwB,KACxB;AACA,IAAA,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,OAAO;AACxC,IAAA,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO;AACzB,IAAA,MAAM,EAAE,SAAS,EAAE,GAAG,OAAO;AAE7B;;AAEG;AACH,IAAA,SAAS,iBAAiB,GAAA;AACtB,QAAA,SAAS,CAAC,GAAG,CAAC,cAAc,CAAC;AAC7B,QAAA,IAAI,SAAS,KAAK,MAAM,EAAE;AACtB,YAAA,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC;QAC7B;aAAO;AACH,YAAA,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC;QAChC;IACJ;IAEA,IAAI,CAAC,SAAS,EAAE;AACZ,QAAA,iBAAiB,EAAE;QACnB;IACJ;IAEA,IAAI,CAAC,OAAO,EAAE;AACV,QAAA,OAAO,GAAG,mCAAmC,CAAC,OAAO,EAAE,SAAS,CAAC;AACjE,QAAA,OAAO,CAAC,OAAO,GAAG,OAAO;;AAGzB,QAAA,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,SAAS,KAAK,MAAM,GAAG,OAAO,GAAG,KAAK;QAEjE,SAAS,CAAC,MAAM,CAAC,cAAc,EAAE,gBAAgB,EAAE,MAAM,CAAC;QAE1D,aAAa,CAAC,OAAO,CAAC;;AAGtB,QAAA,SAAS,CAAC,GAAG,CAAC,gBAAgB,CAAC;IACnC;AAEA,IAAA,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,SAAS,KAAK,MAAM,GAAG,OAAO,GAAG,KAAK;AAEjE,IAAA,OAAO,MAAK;AACR,QAAA,iBAAiB,EAAE;AACnB,QAAA,SAAS,CAAC,MAAM,CAAC,gBAAgB,CAAC;AAClC,QAAA,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;AACjC,IAAA,CAAC;AACL;AAEA;;;;;;AAMG;AACH,SAAS,mCAAmC,CAAC,OAAoB,EAAE,SAA6B,EAAA;;AAE5F,IAAA,IAAI,OAAO,SAAS,KAAK,WAAW,EAAE;AAClC,QAAA,OAAO,KAAK;IAChB;AAEA,IAAA,MAAM,EAAE,SAAS,EAAE,GAAG,OAAO;IAC7B,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,UAAU,CAAC;IACpD,IAAI,CAAC,aAAa,EAAE;AAChB,QAAA,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC;IAC7B;AAEA,IAAA,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;IAC7B,MAAM,aAAa,GAAG,OAAO,CAAC,qBAAqB,EAAE,CAAC,SAAS,CAAC,GAAG,IAAI;IAEvE,IAAI,CAAC,aAAa,EAAE;AAChB,QAAA,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC;IAChC;AAEA,IAAA,OAAO,aAAa;AACxB;;MC/Fa,qBAAqB,GAAsB,CAAC,OAAoB,EAAE,SAAkB,KAAI;AACjG,IAAA,MAAM,EAAE,SAAS,EAAE,GAAG,OAAO;IAE7B,IAAI,SAAS,EAAE;AACX,QAAA,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC;IACzB;SAAO;AACH,QAAA,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC;QACrB;IACJ;IAEA,aAAa,CAAC,OAAO,CAAC;AACtB,IAAA,SAAS,CAAC,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC;AAEhC,IAAA,OAAO,MAAK;AACR,QAAA,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC;AAC/B,IAAA,CAAC;AACL;MAEa,sBAAsB,GAAsB,CAAC,EAAE,SAAS,EAAe,KAAI;AACpF,IAAA,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC;AACxB,IAAA,OAAO,MAAK;AACR,QAAA,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC;AACvC,IAAA,CAAC;AACL;;AC1BA;;AAEG;;;;"}
1
+ {"version":3,"file":"radix-ng-primitives-presence.mjs","sources":["../../../packages/primitives/presence/src/presence.directive.ts","../../../packages/primitives/presence/radix-ng-primitives-presence.ts"],"sourcesContent":["import { isPlatformBrowser } from '@angular/common';\nimport {\n afterNextRender,\n DestroyRef,\n Directive,\n effect,\n EmbeddedViewRef,\n inject,\n InjectionToken,\n Injector,\n PLATFORM_ID,\n Provider,\n Signal,\n TemplateRef,\n ViewContainerRef\n} from '@angular/core';\n\n/**\n * Context interface for RdxPresence directive\n * Contains a Signal that indicates whether the content should be present in the DOM\n */\nexport type RdxPresenceContext = {\n present: Signal<boolean>;\n};\n\nexport const RDX_PRESENCE_CONTEXT = new InjectionToken<RdxPresenceContext>('RdxPresenceContext');\n\n/**\n * Factory provider helper.\n * In your parent component/directive you can write:\n *\n * providers: [\n * provideRdxPresenceContext(() => ({ present: myBooleanSignal }))\n * ]\n */\nexport function provideRdxPresenceContext(contextFactory: () => RdxPresenceContext): Provider {\n return { provide: RDX_PRESENCE_CONTEXT, useFactory: contextFactory };\n}\n\ntype PresenceState = 'mounted' | 'unmountSuspended' | 'unmounted';\ntype PresenceEvent = 'MOUNT' | 'UNMOUNT' | 'ANIMATION_OUT' | 'ANIMATION_END';\n\n/**\n * State machine mirroring `@radix-ui/react-presence`.\n *\n * - `mounted` — content rendered, `present` is `true`.\n * - `unmountSuspended` — `present` flipped to `false` but an exit animation is running;\n * the content stays in the DOM until the animation ends.\n * - `unmounted` — content removed.\n */\nconst MACHINE: Record<PresenceState, Partial<Record<PresenceEvent, PresenceState>>> = {\n mounted: { UNMOUNT: 'unmounted', ANIMATION_OUT: 'unmountSuspended' },\n unmountSuspended: { MOUNT: 'mounted', ANIMATION_END: 'unmounted' },\n unmounted: { MOUNT: 'mounted' }\n};\n\n/**\n * Headless structural directive that conditionally renders its template based on a reactive\n * `present` signal supplied through {@link RDX_PRESENCE_CONTEXT}.\n *\n * Unlike a plain `*ngIf`, it keeps the content mounted while a CSS exit animation\n * (`@keyframes` applied for the closed state) is running, and unmounts it only once that\n * animation finishes. If the content has no exit animation, it unmounts immediately.\n */\n@Directive({\n standalone: true\n})\nexport class RdxPresenceDirective {\n private readonly context = inject(RDX_PRESENCE_CONTEXT);\n private readonly viewContainerRef = inject(ViewContainerRef);\n private readonly templateRef = inject(TemplateRef<void>);\n private readonly injector = inject(Injector);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n private viewRef: EmbeddedViewRef<void> | null = null;\n private node: HTMLElement | null = null;\n private removeListeners: (() => void) | null = null;\n\n private state: PresenceState;\n private prevPresent: boolean;\n private prevAnimationName = 'none';\n\n constructor() {\n this.prevPresent = this.context.present();\n this.state = this.prevPresent ? 'mounted' : 'unmounted';\n\n if (this.prevPresent) {\n this.mountView();\n }\n\n effect(() => {\n const present = this.context.present();\n\n if (present === this.prevPresent) {\n return;\n }\n this.prevPresent = present;\n\n if (present) {\n // Mount synchronously so the enter animation can start on this frame.\n this.send('MOUNT');\n } else if (this.isBrowser) {\n // Defer the unmount decision until the next render, so the consumer's\n // `data-state` (and therefore the exit `@keyframes`) is applied to the DOM\n // before we read the computed animation name.\n afterNextRender(() => this.evaluateExit(), { injector: this.injector });\n } else {\n this.send('UNMOUNT');\n }\n });\n\n inject(DestroyRef).onDestroy(() => this.destroyView());\n }\n\n /** Decides whether to suspend the unmount for an exit animation (port of Radix' logic). */\n private evaluateExit(): void {\n // Re-opened before this callback ran — keep the content mounted.\n if (this.state !== 'mounted' || this.context.present()) {\n return;\n }\n\n const styles = this.getComputedStyles();\n const currentAnimationName = styles?.animationName || 'none';\n\n if (currentAnimationName === 'none' || styles?.display === 'none') {\n // No exit animation (or the element is hidden) — unmount right away.\n this.send('UNMOUNT');\n } else {\n // Only suspend the unmount if the closed state actually starts a *different* animation.\n const isAnimating = this.prevAnimationName !== currentAnimationName;\n this.send(isAnimating ? 'ANIMATION_OUT' : 'UNMOUNT');\n }\n }\n\n private send(event: PresenceEvent): void {\n const next = MACHINE[this.state][event];\n if (next === undefined || next === this.state) {\n return;\n }\n\n this.state = next;\n\n if (next === 'mounted') {\n if (this.viewRef) {\n // Re-opened while an exit animation was running — refresh the tracked animation.\n this.prevAnimationName = this.getAnimationName();\n } else {\n this.mountView();\n }\n } else if (next === 'unmounted') {\n this.destroyView();\n }\n // `unmountSuspended` keeps the existing view mounted until ANIMATION_END.\n }\n\n private mountView(): void {\n this.viewRef = this.viewContainerRef.createEmbeddedView(this.templateRef);\n this.node = this.viewRef.rootNodes.find((n): n is HTMLElement => n instanceof HTMLElement) ?? null;\n\n if (this.node && this.isBrowser) {\n this.prevAnimationName = this.getAnimationName();\n this.addAnimationListeners(this.node);\n }\n }\n\n private destroyView(): void {\n this.removeListeners?.();\n this.removeListeners = null;\n this.viewRef?.destroy();\n this.viewRef = null;\n this.node = null;\n }\n\n private addAnimationListeners(node: HTMLElement): void {\n const onStart = (event: AnimationEvent) => {\n if (event.target === node) {\n this.prevAnimationName = this.getAnimationName();\n }\n };\n const onEnd = (event: AnimationEvent) => {\n const isCurrentAnimation = this.getAnimationName().includes(event.animationName);\n if (event.target === node && isCurrentAnimation) {\n this.send('ANIMATION_END');\n }\n };\n\n node.addEventListener('animationstart', onStart);\n node.addEventListener('animationcancel', onEnd);\n node.addEventListener('animationend', onEnd);\n\n this.removeListeners = () => {\n node.removeEventListener('animationstart', onStart);\n node.removeEventListener('animationcancel', onEnd);\n node.removeEventListener('animationend', onEnd);\n };\n }\n\n private getComputedStyles(): CSSStyleDeclaration | null {\n return this.node && this.isBrowser ? getComputedStyle(this.node) : null;\n }\n\n private getAnimationName(): string {\n return this.getComputedStyles()?.animationName || 'none';\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}