ngwr 9.0.1 → 10.0.0

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 (108) hide show
  1. package/README.md +14 -0
  2. package/badge/styles/_index.scss +16 -6
  3. package/badge/styles/_tag.scss +10 -1
  4. package/button/styles/_index.scss +48 -1
  5. package/date-picker/styles/_index.scss +62 -0
  6. package/dialog/styles/_index.scss +65 -0
  7. package/drawer/styles/_index.scss +83 -0
  8. package/fesm2022/ngwr-action-sheet.mjs +1 -1
  9. package/fesm2022/ngwr-action-sheet.mjs.map +1 -1
  10. package/fesm2022/ngwr-breadcrumbs.mjs +2 -2
  11. package/fesm2022/ngwr-breadcrumbs.mjs.map +1 -1
  12. package/fesm2022/ngwr-card.mjs +2 -2
  13. package/fesm2022/ngwr-card.mjs.map +1 -1
  14. package/fesm2022/ngwr-cascader.mjs +6 -5
  15. package/fesm2022/ngwr-cascader.mjs.map +1 -1
  16. package/fesm2022/ngwr-color-picker.mjs +6 -5
  17. package/fesm2022/ngwr-color-picker.mjs.map +1 -1
  18. package/fesm2022/ngwr-context-menu.mjs +7 -7
  19. package/fesm2022/ngwr-context-menu.mjs.map +1 -1
  20. package/fesm2022/ngwr-date-picker.mjs +521 -8
  21. package/fesm2022/ngwr-date-picker.mjs.map +1 -1
  22. package/fesm2022/ngwr-density.mjs +10 -2
  23. package/fesm2022/ngwr-density.mjs.map +1 -1
  24. package/fesm2022/ngwr-dialog.mjs +95 -13
  25. package/fesm2022/ngwr-dialog.mjs.map +1 -1
  26. package/fesm2022/ngwr-drag-drop.mjs +2 -2
  27. package/fesm2022/ngwr-drag-drop.mjs.map +1 -1
  28. package/fesm2022/ngwr-drawer.mjs +377 -22
  29. package/fesm2022/ngwr-drawer.mjs.map +1 -1
  30. package/fesm2022/ngwr-dropdown.mjs +4 -3
  31. package/fesm2022/ngwr-dropdown.mjs.map +1 -1
  32. package/fesm2022/ngwr-i18n-en.mjs +21 -0
  33. package/fesm2022/ngwr-i18n-en.mjs.map +1 -1
  34. package/fesm2022/ngwr-i18n-ru.mjs +22 -0
  35. package/fesm2022/ngwr-i18n-ru.mjs.map +1 -1
  36. package/fesm2022/ngwr-i18n.mjs +74 -1
  37. package/fesm2022/ngwr-i18n.mjs.map +1 -1
  38. package/fesm2022/ngwr-icon.mjs +74 -35
  39. package/fesm2022/ngwr-icon.mjs.map +1 -1
  40. package/fesm2022/ngwr-lightbox.mjs +2 -2
  41. package/fesm2022/ngwr-lightbox.mjs.map +1 -1
  42. package/fesm2022/ngwr-list.mjs +2 -2
  43. package/fesm2022/ngwr-list.mjs.map +1 -1
  44. package/fesm2022/ngwr-mention.mjs +166 -10
  45. package/fesm2022/ngwr-mention.mjs.map +1 -1
  46. package/fesm2022/ngwr-overlay.mjs +173 -3
  47. package/fesm2022/ngwr-overlay.mjs.map +1 -1
  48. package/fesm2022/ngwr-pagination.mjs +17 -4
  49. package/fesm2022/ngwr-pagination.mjs.map +1 -1
  50. package/fesm2022/ngwr-popconfirm.mjs +4 -3
  51. package/fesm2022/ngwr-popconfirm.mjs.map +1 -1
  52. package/fesm2022/ngwr-popover.mjs +10 -3
  53. package/fesm2022/ngwr-popover.mjs.map +1 -1
  54. package/fesm2022/ngwr-select.mjs +233 -43
  55. package/fesm2022/ngwr-select.mjs.map +1 -1
  56. package/fesm2022/ngwr-table.mjs +1 -1
  57. package/fesm2022/ngwr-table.mjs.map +1 -1
  58. package/fesm2022/ngwr-theme.mjs +13 -3
  59. package/fesm2022/ngwr-theme.mjs.map +1 -1
  60. package/fesm2022/ngwr-tree.mjs +4 -3
  61. package/fesm2022/ngwr-tree.mjs.map +1 -1
  62. package/fesm2022/ngwr-version.mjs +1 -1
  63. package/fesm2022/ngwr-version.mjs.map +1 -1
  64. package/fesm2022/ngwr-waves.mjs +25 -3
  65. package/fesm2022/ngwr-waves.mjs.map +1 -1
  66. package/i18n/en.json +165 -0
  67. package/i18n/ru.json +165 -0
  68. package/icon/styles/_index.scss +10 -1
  69. package/input/styles/_index.scss +16 -0
  70. package/input-number/styles/_index.scss +8 -1
  71. package/keyboard/styles/_index.scss +10 -3
  72. package/llms-full.txt +14 -14
  73. package/llms.txt +12 -6
  74. package/mention/styles/_index.scss +23 -0
  75. package/package.json +23 -5
  76. package/pagination/styles/_index.scss +12 -0
  77. package/popover/styles/_index.scss +129 -45
  78. package/schematics/ng-add/index.js +18 -4
  79. package/schematics/package.json +3 -0
  80. package/schematics/use/symbol-map.json +7 -0
  81. package/select/styles/_index.scss +13 -0
  82. package/splitter/styles/_index.scss +7 -1
  83. package/table/styles/_index.scss +28 -10
  84. package/theme/styles/_box-model.scss +29 -0
  85. package/theme/styles/_colors.scss +74 -15
  86. package/theme/styles/_dark.scss +52 -6
  87. package/theme/styles/_index.scss +1 -0
  88. package/types/ngwr-cascader.d.ts +1 -0
  89. package/types/ngwr-color-picker.d.ts +1 -0
  90. package/types/ngwr-context-menu.d.ts +1 -0
  91. package/types/ngwr-date-picker.d.ts +172 -4
  92. package/types/ngwr-density.d.ts +8 -1
  93. package/types/ngwr-dialog.d.ts +80 -8
  94. package/types/ngwr-drawer.d.ts +245 -10
  95. package/types/ngwr-dropdown.d.ts +1 -0
  96. package/types/ngwr-i18n.d.ts +65 -2
  97. package/types/ngwr-icon.d.ts +11 -19
  98. package/types/ngwr-mention.d.ts +45 -1
  99. package/types/ngwr-overlay.d.ts +80 -2
  100. package/types/ngwr-pagination.d.ts +14 -1
  101. package/types/ngwr-popconfirm.d.ts +1 -0
  102. package/types/ngwr-popover.d.ts +1 -0
  103. package/types/ngwr-select.d.ts +121 -9
  104. package/types/ngwr-theme.d.ts +8 -1
  105. package/types/ngwr-tree.d.ts +1 -0
  106. package/types/ngwr-version.d.ts +1 -1
  107. package/types/ngwr-waves.d.ts +10 -0
  108. package/waves/styles/_index.scss +21 -0
@@ -1,11 +1,14 @@
1
1
  import { ConfigurableFocusTrapFactory } from '@angular/cdk/a11y';
2
2
  import { coerceBooleanProperty } from '@angular/cdk/coercion';
3
- import { TemplatePortal } from '@angular/cdk/portal';
3
+ import { TemplatePortal, ComponentPortal } from '@angular/cdk/portal';
4
4
  import { isPlatformBrowser } from '@angular/common';
5
5
  import * as i0 from '@angular/core';
6
- import { model, input, viewChild, TemplateRef, inject, ViewContainerRef, DestroyRef, PLATFORM_ID, effect, ViewEncapsulation, Component, Directive, computed } from '@angular/core';
6
+ import { model, input, viewChild, TemplateRef, inject, ViewContainerRef, DestroyRef, PLATFORM_ID, effect, ViewEncapsulation, Component, InjectionToken, EnvironmentInjector, Injector, afterNextRender, Service, Directive, computed } from '@angular/core';
7
7
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
8
- import { WR_OVERLAY } from 'ngwr/overlay';
8
+ import { useI18nText, WrI18n } from 'ngwr/i18n';
9
+ import { WR_OVERLAY, wrAppendOverlayClose } from 'ngwr/overlay';
10
+ import { ScrollStrategyOptions } from '@angular/cdk/overlay';
11
+ import { ReplaySubject } from 'rxjs';
9
12
 
10
13
  /**
11
14
  * @license
@@ -75,6 +78,15 @@ class WrDrawer {
75
78
  closeOnBackdropClick = input(true, { ...(ngDevMode ? { debugName: "closeOnBackdropClick" } : /* istanbul ignore next */ {}), transform: coerceBooleanProperty });
76
79
  /** Close on Escape. @default true */
77
80
  closeOnEscape = input(true, { ...(ngDevMode ? { debugName: "closeOnEscape" } : /* istanbul ignore next */ {}), transform: coerceBooleanProperty });
81
+ /**
82
+ * Show a dismiss (×) button in the panel's top-right corner. Set `false` when
83
+ * the projected content supplies its own close affordance. @default true
84
+ */
85
+ closable = input(true, { ...(ngDevMode ? { debugName: "closable" } : /* istanbul ignore next */ {}), transform: coerceBooleanProperty });
86
+ /** Accessible name for the dismiss button. Falls back to `drawer.close`. */
87
+ closeLabel = input(null, /* @ts-ignore */
88
+ ...(ngDevMode ? [{ debugName: "closeLabel" }] : /* istanbul ignore next */ []));
89
+ resolvedCloseLabel = useI18nText(this.closeLabel, 'drawer.close', 'Close drawer');
78
90
  panelTpl = viewChild.required(TemplateRef, /* @ts-ignore */
79
91
  ...(ngDevMode ? [{ debugName: "panelTpl" }] : /* istanbul ignore next */ []));
80
92
  panelClass() {
@@ -83,6 +95,8 @@ class WrDrawer {
83
95
  parts.push('wr-drawer__panel--rounded');
84
96
  if (this.safeArea())
85
97
  parts.push('wr-drawer__panel--safe-area');
98
+ if (this.closable())
99
+ parts.push('wr-drawer__panel--closable');
86
100
  return parts.join(' ');
87
101
  }
88
102
  // Swipe-to-dismiss — gated to the grab handle, so it never fights the
@@ -91,28 +105,31 @@ class WrDrawer {
91
105
  swipeStartX = 0;
92
106
  swipeStartY = 0;
93
107
  swiping = false;
108
+ // Pointer, not Touch. The handle has always advertised itself to a mouse with
109
+ // `cursor: grab`, but the gesture was bound to touch events only, so on a
110
+ // desktop it could not be dragged at all. Pointer Events cover touch, mouse and
111
+ // pen through one path — the same idiom `wr-splitter` already uses.
94
112
  onSwipeStart(event) {
95
- const touch = event.touches[0];
96
- if (!touch)
113
+ // Ignore secondary contacts of a multi-touch gesture.
114
+ if (!event.isPrimary)
97
115
  return;
98
- this.swipeStartX = touch.clientX;
99
- this.swipeStartY = touch.clientY;
116
+ this.swipeStartX = event.clientX;
117
+ this.swipeStartY = event.clientY;
100
118
  this.swiping = true;
119
+ // Capture, so the drag keeps reporting once the cursor leaves the 2.5rem
120
+ // grabber — without it a mouse drag dies the moment it slips off the handle.
121
+ event.currentTarget.setPointerCapture(event.pointerId);
101
122
  }
102
123
  onSwipeMove(event, panel) {
103
124
  if (!this.swiping)
104
125
  return;
105
- const touch = event.touches[0];
106
- if (!touch)
107
- return;
108
126
  const { axis, sign } = this.closeAxis();
109
- const delta = axis === 'y' ? touch.clientY - this.swipeStartY : touch.clientX - this.swipeStartX;
110
- // Only follow the finger when dragging toward the closing edge.
127
+ const delta = axis === 'y' ? event.clientY - this.swipeStartY : event.clientX - this.swipeStartX;
128
+ // Only follow the pointer when dragging toward the closing edge.
111
129
  if (delta * sign <= 0) {
112
130
  panel.style.transform = '';
113
131
  return;
114
132
  }
115
- event.preventDefault();
116
133
  panel.style.transition = 'none';
117
134
  panel.style.transform = axis === 'y' ? `translateY(${delta}px)` : `translateX(${delta}px)`;
118
135
  }
@@ -120,9 +137,8 @@ class WrDrawer {
120
137
  if (!this.swiping)
121
138
  return;
122
139
  this.swiping = false;
123
- const touch = event.changedTouches[0];
124
140
  const { axis, sign } = this.closeAxis();
125
- const delta = touch ? (axis === 'y' ? touch.clientY - this.swipeStartY : touch.clientX - this.swipeStartX) : 0;
141
+ const delta = axis === 'y' ? event.clientY - this.swipeStartY : event.clientX - this.swipeStartX;
126
142
  const panelSize = axis === 'y' ? panel.offsetHeight : panel.offsetWidth;
127
143
  if (delta * sign > panelSize * 0.3) {
128
144
  this.open.set(false); // keep the dragged offset as the overlay disposes
@@ -238,12 +254,329 @@ class WrDrawer {
238
254
  }
239
255
  }
240
256
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: WrDrawer, deps: [], target: i0.ɵɵFactoryTarget.Component });
241
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: WrDrawer, isStandalone: true, selector: "wr-drawer", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, rounded: { classPropertyName: "rounded", publicName: "rounded", isSignal: true, isRequired: false, transformFunction: null }, showHandle: { classPropertyName: "showHandle", publicName: "showHandle", isSignal: true, isRequired: false, transformFunction: null }, safeArea: { classPropertyName: "safeArea", publicName: "safeArea", isSignal: true, isRequired: false, transformFunction: null }, hasBackdrop: { classPropertyName: "hasBackdrop", publicName: "hasBackdrop", isSignal: true, isRequired: false, transformFunction: null }, closeOnBackdropClick: { classPropertyName: "closeOnBackdropClick", publicName: "closeOnBackdropClick", isSignal: true, isRequired: false, transformFunction: null }, closeOnEscape: { classPropertyName: "closeOnEscape", publicName: "closeOnEscape", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange" }, host: { styleAttribute: "display:none" }, viewQueries: [{ propertyName: "panelTpl", first: true, predicate: TemplateRef, descendants: true, isSignal: true }], ngImport: i0, template: "<ng-template>\n <div #panel [class]=\"panelClass()\">\n @if (showHandle()) {\n <div\n class=\"wr-drawer__handle\"\n aria-hidden=\"true\"\n (touchstart)=\"onSwipeStart($event)\"\n (touchmove)=\"onSwipeMove($event, panel)\"\n (touchend)=\"onSwipeEnd($event, panel)\"\n (touchcancel)=\"onSwipeEnd($event, panel)\"\n >\n <span class=\"wr-drawer__grabber\"></span>\n </div>\n }\n <ng-content />\n </div>\n</ng-template>\n", encapsulation: i0.ViewEncapsulation.None });
257
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: WrDrawer, isStandalone: true, selector: "wr-drawer", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, rounded: { classPropertyName: "rounded", publicName: "rounded", isSignal: true, isRequired: false, transformFunction: null }, showHandle: { classPropertyName: "showHandle", publicName: "showHandle", isSignal: true, isRequired: false, transformFunction: null }, safeArea: { classPropertyName: "safeArea", publicName: "safeArea", isSignal: true, isRequired: false, transformFunction: null }, hasBackdrop: { classPropertyName: "hasBackdrop", publicName: "hasBackdrop", isSignal: true, isRequired: false, transformFunction: null }, closeOnBackdropClick: { classPropertyName: "closeOnBackdropClick", publicName: "closeOnBackdropClick", isSignal: true, isRequired: false, transformFunction: null }, closeOnEscape: { classPropertyName: "closeOnEscape", publicName: "closeOnEscape", isSignal: true, isRequired: false, transformFunction: null }, closable: { classPropertyName: "closable", publicName: "closable", isSignal: true, isRequired: false, transformFunction: null }, closeLabel: { classPropertyName: "closeLabel", publicName: "closeLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange" }, host: { styleAttribute: "display:none" }, viewQueries: [{ propertyName: "panelTpl", first: true, predicate: TemplateRef, descendants: true, isSignal: true }], ngImport: i0, template: "<ng-template>\n <div #panel [class]=\"panelClass()\">\n @if (showHandle()) {\n <div\n class=\"wr-drawer__handle\"\n aria-hidden=\"true\"\n (pointerdown)=\"onSwipeStart($event)\"\n (pointermove)=\"onSwipeMove($event, panel)\"\n (pointerup)=\"onSwipeEnd($event, panel)\"\n (pointercancel)=\"onSwipeEnd($event, panel)\"\n >\n <span class=\"wr-drawer__grabber\"></span>\n </div>\n }\n @if (closable()) {\n <button type=\"button\" class=\"wr-drawer__close\" [attr.aria-label]=\"resolvedCloseLabel()\" (click)=\"open.set(false)\">\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </button>\n }\n <ng-content />\n </div>\n</ng-template>\n", encapsulation: i0.ViewEncapsulation.None });
242
258
  }
243
259
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: WrDrawer, decorators: [{
244
260
  type: Component,
245
- args: [{ selector: 'wr-drawer', encapsulation: ViewEncapsulation.None, host: { style: 'display:none' }, template: "<ng-template>\n <div #panel [class]=\"panelClass()\">\n @if (showHandle()) {\n <div\n class=\"wr-drawer__handle\"\n aria-hidden=\"true\"\n (touchstart)=\"onSwipeStart($event)\"\n (touchmove)=\"onSwipeMove($event, panel)\"\n (touchend)=\"onSwipeEnd($event, panel)\"\n (touchcancel)=\"onSwipeEnd($event, panel)\"\n >\n <span class=\"wr-drawer__grabber\"></span>\n </div>\n }\n <ng-content />\n </div>\n</ng-template>\n" }]
246
- }], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], maxHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxHeight", required: false }] }], rounded: [{ type: i0.Input, args: [{ isSignal: true, alias: "rounded", required: false }] }], showHandle: [{ type: i0.Input, args: [{ isSignal: true, alias: "showHandle", required: false }] }], safeArea: [{ type: i0.Input, args: [{ isSignal: true, alias: "safeArea", required: false }] }], hasBackdrop: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasBackdrop", required: false }] }], closeOnBackdropClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnBackdropClick", required: false }] }], closeOnEscape: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnEscape", required: false }] }], panelTpl: [{ type: i0.ViewChild, args: [i0.forwardRef(() => TemplateRef), { isSignal: true }] }] } });
261
+ args: [{ selector: 'wr-drawer', encapsulation: ViewEncapsulation.None, host: { style: 'display:none' }, template: "<ng-template>\n <div #panel [class]=\"panelClass()\">\n @if (showHandle()) {\n <div\n class=\"wr-drawer__handle\"\n aria-hidden=\"true\"\n (pointerdown)=\"onSwipeStart($event)\"\n (pointermove)=\"onSwipeMove($event, panel)\"\n (pointerup)=\"onSwipeEnd($event, panel)\"\n (pointercancel)=\"onSwipeEnd($event, panel)\"\n >\n <span class=\"wr-drawer__grabber\"></span>\n </div>\n }\n @if (closable()) {\n <button type=\"button\" class=\"wr-drawer__close\" [attr.aria-label]=\"resolvedCloseLabel()\" (click)=\"open.set(false)\">\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </button>\n }\n <ng-content />\n </div>\n</ng-template>\n" }]
262
+ }], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], maxHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxHeight", required: false }] }], rounded: [{ type: i0.Input, args: [{ isSignal: true, alias: "rounded", required: false }] }], showHandle: [{ type: i0.Input, args: [{ isSignal: true, alias: "showHandle", required: false }] }], safeArea: [{ type: i0.Input, args: [{ isSignal: true, alias: "safeArea", required: false }] }], hasBackdrop: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasBackdrop", required: false }] }], closeOnBackdropClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnBackdropClick", required: false }] }], closeOnEscape: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnEscape", required: false }] }], closable: [{ type: i0.Input, args: [{ isSignal: true, alias: "closable", required: false }] }], closeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeLabel", required: false }] }], panelTpl: [{ type: i0.ViewChild, args: [i0.forwardRef(() => TemplateRef), { isSignal: true }] }] } });
263
+
264
+ /**
265
+ * @license
266
+ *
267
+ * Use of this source code is governed by an MIT-style license that can be
268
+ * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
269
+ */
270
+ /**
271
+ * Handle returned by `WrDrawerManager.open()`.
272
+ *
273
+ * Wraps the underlying `OverlayRef` and tracks the close result. It is also
274
+ * provided inside the drawer's own injector, so the projected content can
275
+ * inject it and close itself — the counterpart to `[wrDrawerClose]` when the
276
+ * close is programmatic (after a save succeeds, say) rather than a click.
277
+ *
278
+ * @example
279
+ * ```ts
280
+ * // Caller side.
281
+ * const ref = drawers.open<ChatComponent, boolean>(ChatComponent, { position: 'right' });
282
+ * const result = await ref.awaitClose(); // boolean | undefined
283
+ * ```
284
+ *
285
+ * @example
286
+ * ```ts
287
+ * // Content side — the drawer closes itself.
288
+ * @Component({...})
289
+ * export class ChatComponent {
290
+ * private readonly ref = inject(WrDrawerRef);
291
+ *
292
+ * send(): void {
293
+ * this.store.send(this.draft());
294
+ * this.ref.close(true);
295
+ * }
296
+ * }
297
+ * ```
298
+ */
299
+ class WrDrawerRef {
300
+ _overlayRef;
301
+ /**
302
+ * Emits the close result once and completes.
303
+ *
304
+ * A `ReplaySubject`, not a plain `Subject`, so the result survives a late
305
+ * subscriber: content can close itself before `open()` has even returned, and
306
+ * `awaitClose()` may be called long after the dismissal — both would otherwise
307
+ * see only `complete` and resolve `undefined`.
308
+ */
309
+ closed = new ReplaySubject(1);
310
+ /** @internal */
311
+ componentRef = null;
312
+ /** @internal */
313
+ focusTrap = null;
314
+ /** @internal */
315
+ previouslyFocused = null;
316
+ constructor(_overlayRef) {
317
+ this._overlayRef = _overlayRef;
318
+ }
319
+ /** The instantiated drawer component. */
320
+ get componentInstance() {
321
+ if (!this.componentRef) {
322
+ throw new Error('WrDrawerRef: component not yet attached');
323
+ }
324
+ return this.componentRef.instance;
325
+ }
326
+ isClosed = false;
327
+ /** Close the drawer, optionally returning a result. */
328
+ close(result) {
329
+ if (this.isClosed)
330
+ return;
331
+ this.isClosed = true;
332
+ this.closed.next(result);
333
+ this.closed.complete();
334
+ // Content that closes itself while constructing gets here with
335
+ // `OverlayRef.attach()` still on the stack — `componentRef` is assigned
336
+ // only once attach returns. Disposing mid-attach leaves the CDK writing to
337
+ // a detached host, so let the current task finish first.
338
+ if (!this.componentRef) {
339
+ queueMicrotask(() => this.teardown());
340
+ return;
341
+ }
342
+ this.teardown();
343
+ }
344
+ teardown() {
345
+ this.focusTrap?.destroy();
346
+ this.focusTrap = null;
347
+ const restore = this.previouslyFocused;
348
+ this.previouslyFocused = null;
349
+ this._overlayRef.dispose();
350
+ // Drop the destroyed component so a retained ref doesn't pin its instance,
351
+ // host element and view in memory.
352
+ this.componentRef = null;
353
+ // Restore focus after disposal so the trigger is reachable again.
354
+ if (restore && typeof restore.focus === 'function') {
355
+ restore.focus();
356
+ }
357
+ }
358
+ /** Resolves with the close result when the drawer is dismissed. */
359
+ awaitClose() {
360
+ return new Promise(resolve => {
361
+ this.closed.subscribe({
362
+ next: value => resolve(value),
363
+ complete: () => resolve(undefined),
364
+ });
365
+ });
366
+ }
367
+ /** Underlying overlay ref — escape hatch for advanced cases. */
368
+ get overlayRef() {
369
+ return this._overlayRef;
370
+ }
371
+ }
372
+
373
+ /**
374
+ * @license
375
+ *
376
+ * Use of this source code is governed by an MIT-style license that can be
377
+ * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
378
+ */
379
+ /**
380
+ * Token that exposes the `data` payload passed to `WrDrawerManager.open()`.
381
+ *
382
+ * @example
383
+ * ```ts
384
+ * @Component({...})
385
+ * export class ChatComponent {
386
+ * readonly data = inject<ChatData>(WR_DRAWER_DATA);
387
+ * }
388
+ * ```
389
+ */
390
+ const WR_DRAWER_DATA = new InjectionToken('WR_DRAWER_DATA');
391
+
392
+ /**
393
+ * @license
394
+ *
395
+ * Use of this source code is governed by an MIT-style license that can be
396
+ * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
397
+ */
398
+ /**
399
+ * Token exposing the open drawer's `WrDrawerRef` to components and
400
+ * directives rendered inside it (e.g. `[wrDrawerClose]`).
401
+ *
402
+ * **Prefer `inject(WrDrawerRef)`** — the ref is provided under the class token
403
+ * too, and the class path is the documented one. It already covers both cases
404
+ * you might reach for a token for: `inject(WrDrawerRef, { optional: true })`
405
+ * works for content that also renders outside a drawer, and
406
+ * `inject<WrDrawerRef<ChatComponent, boolean>>(WrDrawerRef)` types the generics
407
+ * without a cast.
408
+ *
409
+ * This token is the same object under a second key. It exists for the library's
410
+ * own directives (`[wrDrawerClose]`) and for swapping the provider out — a test
411
+ * stub, say — without touching the class.
412
+ *
413
+ * @example
414
+ * ```ts
415
+ * @Component({...})
416
+ * export class ChatComponent {
417
+ * private readonly ref = inject<WrDrawerRef<ChatComponent, boolean>>(WrDrawerRef);
418
+ *
419
+ * dismiss(): void {
420
+ * this.ref.close(true);
421
+ * }
422
+ * }
423
+ * ```
424
+ */
425
+ const WR_DRAWER_REF = new InjectionToken('WR_DRAWER_REF');
426
+
427
+ /**
428
+ * @license
429
+ *
430
+ * Use of this source code is governed by an MIT-style license that can be
431
+ * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
432
+ */
433
+ const DEFAULT_POSITION = 'right';
434
+ const DEFAULT_WIDTH = '20rem';
435
+ const DEFAULT_HEIGHT = '16rem';
436
+ const BACKDROP_CLASS = 'wr-drawer-backdrop';
437
+ /**
438
+ * Opens drawer components in an isolated NGWR overlay — the programmatic
439
+ * counterpart to `<wr-drawer [(open)]="…">`.
440
+ *
441
+ * Use the component when the drawer is part of a template and its open state
442
+ * is a signal you already own; use this service when the caller doesn't own
443
+ * the markup (opening a chat panel from a toolbar action, from an effect, from
444
+ * a route guard) or when you need the close result.
445
+ *
446
+ * Uses `WR_OVERLAY` so it composes cleanly with `provideWrOverlay()`
447
+ * — drawers render into NGWR's own overlay container and never collide
448
+ * with other CDK consumers (Material, NG-ZORRO, etc.).
449
+ *
450
+ * `showHandle` / swipe-to-dismiss is component-only: it needs the drawer's own
451
+ * wrapper markup, which the service path replaces with your component.
452
+ *
453
+ * @example
454
+ * ```ts
455
+ * const drawers = inject(WrDrawerManager);
456
+ *
457
+ * const ref = drawers.open(ChatComponent, {
458
+ * data: { threadId },
459
+ * position: 'right',
460
+ * width: '28rem',
461
+ * });
462
+ *
463
+ * await ref.awaitClose();
464
+ * ```
465
+ *
466
+ * @see https://ngwr.dev/reference/components/drawer
467
+ */
468
+ class WrDrawerManager {
469
+ overlay = inject(WR_OVERLAY);
470
+ scrollStrategies = inject(ScrollStrategyOptions);
471
+ parentInjector = inject(EnvironmentInjector);
472
+ focusTrapFactory = inject(ConfigurableFocusTrapFactory);
473
+ isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
474
+ i18n = inject(WrI18n);
475
+ open(component, options = {}) {
476
+ const position = options.position ?? DEFAULT_POSITION;
477
+ const isHorizontal = position === 'left' || position === 'right';
478
+ // The content component is attached straight into the overlay pane, so the
479
+ // panel styles land on the pane rather than on an inner wrapper.
480
+ const panelClasses = [
481
+ 'wr-drawer-overlay',
482
+ `wr-drawer-overlay--${position}`,
483
+ 'wr-drawer__panel',
484
+ `wr-drawer__panel--${position}`,
485
+ ];
486
+ if (options.rounded)
487
+ panelClasses.push('wr-drawer__panel--rounded');
488
+ if (options.safeArea)
489
+ panelClasses.push('wr-drawer__panel--safe-area');
490
+ const extra = options.panelClass;
491
+ if (typeof extra === 'string') {
492
+ panelClasses.push(extra);
493
+ }
494
+ else if (extra) {
495
+ for (const cls of extra)
496
+ panelClasses.push(cls);
497
+ }
498
+ const positionStrategy = this.overlay
499
+ .position()
500
+ .global()[position === 'left' ? 'left' : position === 'right' ? 'right' : 'centerHorizontally']();
501
+ if (position === 'top')
502
+ positionStrategy.top();
503
+ else if (position === 'bottom')
504
+ positionStrategy.bottom();
505
+ else
506
+ positionStrategy.centerVertically();
507
+ const cap = options.maxHeight ?? null;
508
+ const overlayRef = this.overlay.create({
509
+ positionStrategy,
510
+ scrollStrategy: this.scrollStrategies.block(),
511
+ hasBackdrop: options.hasBackdrop !== false,
512
+ backdropClass: BACKDROP_CLASS,
513
+ panelClass: panelClasses,
514
+ width: isHorizontal ? (options.width ?? DEFAULT_WIDTH) : '100vw',
515
+ height: isHorizontal ? undefined : (options.height ?? DEFAULT_HEIGHT),
516
+ maxHeight: isHorizontal ? undefined : (cap ?? undefined),
517
+ });
518
+ const drawerRef = new WrDrawerRef(overlayRef);
519
+ if (this.isBrowser) {
520
+ const active = document.activeElement;
521
+ drawerRef.previouslyFocused = active instanceof HTMLElement ? active : null;
522
+ }
523
+ const injector = Injector.create({
524
+ parent: this.parentInjector,
525
+ providers: [
526
+ { provide: WR_DRAWER_DATA, useValue: options.data },
527
+ { provide: WR_DRAWER_REF, useValue: drawerRef },
528
+ // Provided as its own token as well, so drawer content can reach for
529
+ // the familiar `inject(WrDrawerRef)` and close itself.
530
+ { provide: WrDrawerRef, useValue: drawerRef },
531
+ ],
532
+ });
533
+ const portal = new ComponentPortal(component, null, injector);
534
+ drawerRef.componentRef = overlayRef.attach(portal);
535
+ if (this.isBrowser) {
536
+ const host = overlayRef.overlayElement;
537
+ host.setAttribute('role', 'dialog');
538
+ host.setAttribute('aria-modal', 'true');
539
+ // Built-in dismiss, same contract as WrDialog's `closable`.
540
+ if (options.closable !== false) {
541
+ host.classList.add('wr-drawer__panel--closable');
542
+ const label = options.closeLabel ?? this.i18n.t('drawer.close');
543
+ wrAppendOverlayClose(host, 'wr-drawer__close', label, () => drawerRef.close());
544
+ }
545
+ // Wire aria-labelledby to wrDrawerTitle's auto-id once content is in DOM.
546
+ // Has to wait for a render, not a microtask: attaching the portal only
547
+ // schedules change detection, so under zoneless CD a microtask runs while
548
+ // the content is still an empty view and the title id doesn't exist yet.
549
+ afterNextRender(() => {
550
+ const titleEl = host.querySelector('[wrDrawerTitle], [wr-drawer-title]');
551
+ if (titleEl?.id)
552
+ host.setAttribute('aria-labelledby', titleEl.id);
553
+ }, { injector: this.parentInjector });
554
+ // Trap focus inside the drawer and move initial focus in.
555
+ const trap = this.focusTrapFactory.create(host);
556
+ drawerRef.focusTrap = trap;
557
+ void trap.focusInitialElementWhenReady();
558
+ }
559
+ // Overlay subscriptions complete when the overlay is disposed, so no
560
+ // explicit teardown is required.
561
+ if (options.closeOnBackdropClick !== false) {
562
+ overlayRef.backdropClick().subscribe(() => drawerRef.close());
563
+ }
564
+ if (options.closeOnEscape !== false) {
565
+ overlayRef.keydownEvents().subscribe(event => {
566
+ if (event.key === 'Escape') {
567
+ event.preventDefault();
568
+ drawerRef.close();
569
+ }
570
+ });
571
+ }
572
+ return drawerRef;
573
+ }
574
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: WrDrawerManager, deps: [], target: i0.ɵɵFactoryTarget.Service });
575
+ static ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.1.0", ngImport: i0, type: WrDrawerManager });
576
+ }
577
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: WrDrawerManager, decorators: [{
578
+ type: Service
579
+ }] });
247
580
 
248
581
  /**
249
582
  * @license
@@ -348,18 +681,40 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
348
681
  /**
349
682
  * Closes the parent drawer on click. Attach to any clickable element.
350
683
  *
684
+ * Works for both drawer flavours: inside `<wr-drawer>` it clears the
685
+ * component's `open` model, and inside a `WrDrawerManager.open()` drawer it
686
+ * closes the ref — optionally passing a `[wrDrawerClose]` value as the close
687
+ * result (the component flavour has no result to carry, so the value is
688
+ * ignored there).
689
+ *
351
690
  * @example
352
691
  * ```html
353
692
  * <wr-btn wrDrawerClose>Close</wr-btn>
693
+ * <wr-btn color="primary" [wrDrawerClose]="true">Save</wr-btn>
354
694
  * ```
355
695
  */
356
696
  class WrDrawerClose {
697
+ /**
698
+ * Value to pass to `close()` for service-opened drawers. When unset — or
699
+ * used as a bare attribute — the drawer closes with `undefined`.
700
+ */
701
+ result = input(undefined, { ...(ngDevMode ? { debugName: "result" } : /* istanbul ignore next */ {}), alias: 'wrDrawerClose',
702
+ // A bare `wrDrawerClose` attribute arrives as the empty string; that means
703
+ // "no result", not a result of `''`.
704
+ transform: value => (value === '' ? undefined : value) });
357
705
  drawer = inject(WrDrawer, { optional: true });
706
+ ref = inject(WR_DRAWER_REF, { optional: true });
358
707
  close() {
359
- this.drawer?.open.set(false);
708
+ // Element injectors win, so a surrounding `<wr-drawer>` is the nearer
709
+ // owner even when a service-opened drawer sits further up the tree.
710
+ if (this.drawer) {
711
+ this.drawer.open.set(false);
712
+ return;
713
+ }
714
+ this.ref?.close(this.result());
360
715
  }
361
716
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: WrDrawerClose, deps: [], target: i0.ɵɵFactoryTarget.Directive });
362
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.0", type: WrDrawerClose, isStandalone: true, selector: "[wrDrawerClose]", host: { listeners: { "click": "close()" } }, ngImport: i0 });
717
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.0", type: WrDrawerClose, isStandalone: true, selector: "[wrDrawerClose]", inputs: { result: { classPropertyName: "result", publicName: "wrDrawerClose", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "click": "close()" } }, ngImport: i0 });
363
718
  }
364
719
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: WrDrawerClose, decorators: [{
365
720
  type: Directive,
@@ -367,11 +722,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
367
722
  selector: '[wrDrawerClose]',
368
723
  host: { '(click)': 'close()' },
369
724
  }]
370
- }] });
725
+ }], propDecorators: { result: [{ type: i0.Input, args: [{ isSignal: true, alias: "wrDrawerClose", required: false }] }] } });
371
726
 
372
727
  /**
373
728
  * Generated bundle index. Do not edit.
374
729
  */
375
730
 
376
- export { WrDrawer, WrDrawerClose, WrDrawerContent, WrDrawerFooter, WrDrawerTitle };
731
+ export { WR_DRAWER_DATA, WR_DRAWER_REF, WrDrawer, WrDrawerClose, WrDrawerContent, WrDrawerFooter, WrDrawerManager, WrDrawerRef, WrDrawerTitle };
377
732
  //# sourceMappingURL=ngwr-drawer.mjs.map