@ngneers/controls 0.0.1-next.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.
- package/fesm2022/ngneers-controls-accordion.mjs +234 -0
- package/fesm2022/ngneers-controls-accordion.mjs.map +1 -0
- package/fesm2022/ngneers-controls-api-ng.mjs +959 -0
- package/fesm2022/ngneers-controls-api-ng.mjs.map +1 -0
- package/fesm2022/ngneers-controls-api-resize.mjs +712 -0
- package/fesm2022/ngneers-controls-api-resize.mjs.map +1 -0
- package/fesm2022/ngneers-controls-api.mjs +347 -0
- package/fesm2022/ngneers-controls-api.mjs.map +1 -0
- package/fesm2022/ngneers-controls-avatar.mjs +77 -0
- package/fesm2022/ngneers-controls-avatar.mjs.map +1 -0
- package/fesm2022/ngneers-controls-base.mjs +620 -0
- package/fesm2022/ngneers-controls-base.mjs.map +1 -0
- package/fesm2022/ngneers-controls-breadcrumb.mjs +100 -0
- package/fesm2022/ngneers-controls-breadcrumb.mjs.map +1 -0
- package/fesm2022/ngneers-controls-button-group.mjs +110 -0
- package/fesm2022/ngneers-controls-button-group.mjs.map +1 -0
- package/fesm2022/ngneers-controls-button.mjs +83 -0
- package/fesm2022/ngneers-controls-button.mjs.map +1 -0
- package/fesm2022/ngneers-controls-calendar-time-CJT5AFlA.mjs +74 -0
- package/fesm2022/ngneers-controls-calendar-time-CJT5AFlA.mjs.map +1 -0
- package/fesm2022/ngneers-controls-calendar.mjs +801 -0
- package/fesm2022/ngneers-controls-calendar.mjs.map +1 -0
- package/fesm2022/ngneers-controls-checkbox.mjs +63 -0
- package/fesm2022/ngneers-controls-checkbox.mjs.map +1 -0
- package/fesm2022/ngneers-controls-chip.mjs +55 -0
- package/fesm2022/ngneers-controls-chip.mjs.map +1 -0
- package/fesm2022/ngneers-controls-default-icons.mjs +82 -0
- package/fesm2022/ngneers-controls-default-icons.mjs.map +1 -0
- package/fesm2022/ngneers-controls-defer.mjs +72 -0
- package/fesm2022/ngneers-controls-defer.mjs.map +1 -0
- package/fesm2022/ngneers-controls-dialog.mjs +407 -0
- package/fesm2022/ngneers-controls-dialog.mjs.map +1 -0
- package/fesm2022/ngneers-controls-directives.mjs +498 -0
- package/fesm2022/ngneers-controls-directives.mjs.map +1 -0
- package/fesm2022/ngneers-controls-drawer.mjs +271 -0
- package/fesm2022/ngneers-controls-drawer.mjs.map +1 -0
- package/fesm2022/ngneers-controls-edit-inplace.mjs +127 -0
- package/fesm2022/ngneers-controls-edit-inplace.mjs.map +1 -0
- package/fesm2022/ngneers-controls-errors.mjs +329 -0
- package/fesm2022/ngneers-controls-errors.mjs.map +1 -0
- package/fesm2022/ngneers-controls-filter.mjs +708 -0
- package/fesm2022/ngneers-controls-filter.mjs.map +1 -0
- package/fesm2022/ngneers-controls-hint.mjs +153 -0
- package/fesm2022/ngneers-controls-hint.mjs.map +1 -0
- package/fesm2022/ngneers-controls-i18n-translations-de.mjs +170 -0
- package/fesm2022/ngneers-controls-i18n-translations-de.mjs.map +1 -0
- package/fesm2022/ngneers-controls-i18n-translations-en.mjs +170 -0
- package/fesm2022/ngneers-controls-i18n-translations-en.mjs.map +1 -0
- package/fesm2022/ngneers-controls-i18n.mjs +57 -0
- package/fesm2022/ngneers-controls-i18n.mjs.map +1 -0
- package/fesm2022/ngneers-controls-icon.mjs +195 -0
- package/fesm2022/ngneers-controls-icon.mjs.map +1 -0
- package/fesm2022/ngneers-controls-inplace.mjs +150 -0
- package/fesm2022/ngneers-controls-inplace.mjs.map +1 -0
- package/fesm2022/ngneers-controls-input-field.mjs +165 -0
- package/fesm2022/ngneers-controls-input-field.mjs.map +1 -0
- package/fesm2022/ngneers-controls-input.mjs +72 -0
- package/fesm2022/ngneers-controls-input.mjs.map +1 -0
- package/fesm2022/ngneers-controls-item-view.mjs +473 -0
- package/fesm2022/ngneers-controls-item-view.mjs.map +1 -0
- package/fesm2022/ngneers-controls-list-box.mjs +281 -0
- package/fesm2022/ngneers-controls-list-box.mjs.map +1 -0
- package/fesm2022/ngneers-controls-mask-input.mjs +888 -0
- package/fesm2022/ngneers-controls-mask-input.mjs.map +1 -0
- package/fesm2022/ngneers-controls-menu.mjs +371 -0
- package/fesm2022/ngneers-controls-menu.mjs.map +1 -0
- package/fesm2022/ngneers-controls-message.mjs +30 -0
- package/fesm2022/ngneers-controls-message.mjs.map +1 -0
- package/fesm2022/ngneers-controls-number-input.mjs +366 -0
- package/fesm2022/ngneers-controls-number-input.mjs.map +1 -0
- package/fesm2022/ngneers-controls-paginator.mjs +129 -0
- package/fesm2022/ngneers-controls-paginator.mjs.map +1 -0
- package/fesm2022/ngneers-controls-popover.mjs +215 -0
- package/fesm2022/ngneers-controls-popover.mjs.map +1 -0
- package/fesm2022/ngneers-controls-progress.mjs +98 -0
- package/fesm2022/ngneers-controls-progress.mjs.map +1 -0
- package/fesm2022/ngneers-controls-radio.mjs +152 -0
- package/fesm2022/ngneers-controls-radio.mjs.map +1 -0
- package/fesm2022/ngneers-controls-roving-focus.mjs +323 -0
- package/fesm2022/ngneers-controls-roving-focus.mjs.map +1 -0
- package/fesm2022/ngneers-controls-scroll-shadow.mjs +65 -0
- package/fesm2022/ngneers-controls-scroll-shadow.mjs.map +1 -0
- package/fesm2022/ngneers-controls-scroller.mjs +308 -0
- package/fesm2022/ngneers-controls-scroller.mjs.map +1 -0
- package/fesm2022/ngneers-controls-select-button.mjs +58 -0
- package/fesm2022/ngneers-controls-select-button.mjs.map +1 -0
- package/fesm2022/ngneers-controls-select.mjs +417 -0
- package/fesm2022/ngneers-controls-select.mjs.map +1 -0
- package/fesm2022/ngneers-controls-slider.mjs +167 -0
- package/fesm2022/ngneers-controls-slider.mjs.map +1 -0
- package/fesm2022/ngneers-controls-snackbar.mjs +414 -0
- package/fesm2022/ngneers-controls-snackbar.mjs.map +1 -0
- package/fesm2022/ngneers-controls-spin-buttons.mjs +126 -0
- package/fesm2022/ngneers-controls-spin-buttons.mjs.map +1 -0
- package/fesm2022/ngneers-controls-spinner.mjs +184 -0
- package/fesm2022/ngneers-controls-spinner.mjs.map +1 -0
- package/fesm2022/ngneers-controls-splitter.mjs +604 -0
- package/fesm2022/ngneers-controls-splitter.mjs.map +1 -0
- package/fesm2022/ngneers-controls-state.mjs +185 -0
- package/fesm2022/ngneers-controls-state.mjs.map +1 -0
- package/fesm2022/ngneers-controls-switch.mjs +39 -0
- package/fesm2022/ngneers-controls-switch.mjs.map +1 -0
- package/fesm2022/ngneers-controls-table.mjs +2634 -0
- package/fesm2022/ngneers-controls-table.mjs.map +1 -0
- package/fesm2022/ngneers-controls-tabs.mjs +336 -0
- package/fesm2022/ngneers-controls-tabs.mjs.map +1 -0
- package/fesm2022/ngneers-controls-tag.mjs +30 -0
- package/fesm2022/ngneers-controls-tag.mjs.map +1 -0
- package/fesm2022/ngneers-controls-toast.mjs +350 -0
- package/fesm2022/ngneers-controls-toast.mjs.map +1 -0
- package/fesm2022/ngneers-controls-toggle-button.mjs +70 -0
- package/fesm2022/ngneers-controls-toggle-button.mjs.map +1 -0
- package/fesm2022/ngneers-controls-tooltip.mjs +506 -0
- package/fesm2022/ngneers-controls-tooltip.mjs.map +1 -0
- package/fesm2022/ngneers-controls-tree.mjs +599 -0
- package/fesm2022/ngneers-controls-tree.mjs.map +1 -0
- package/fesm2022/ngneers-controls-upload.mjs +377 -0
- package/fesm2022/ngneers-controls-upload.mjs.map +1 -0
- package/fesm2022/ngneers-controls-utils-ng.mjs +411 -0
- package/fesm2022/ngneers-controls-utils-ng.mjs.map +1 -0
- package/fesm2022/ngneers-controls-utils.mjs +261 -0
- package/fesm2022/ngneers-controls-utils.mjs.map +1 -0
- package/fesm2022/ngneers-controls.mjs +6 -0
- package/fesm2022/ngneers-controls.mjs.map +1 -0
- package/package.json +302 -0
- package/types/ngneers-controls-accordion.d.ts +139 -0
- package/types/ngneers-controls-api-ng.d.ts +364 -0
- package/types/ngneers-controls-api-resize.d.ts +318 -0
- package/types/ngneers-controls-api.d.ts +145 -0
- package/types/ngneers-controls-avatar.d.ts +54 -0
- package/types/ngneers-controls-base.d.ts +277 -0
- package/types/ngneers-controls-breadcrumb.d.ts +272 -0
- package/types/ngneers-controls-button-group.d.ts +46 -0
- package/types/ngneers-controls-button.d.ts +48 -0
- package/types/ngneers-controls-calendar.d.ts +466 -0
- package/types/ngneers-controls-checkbox.d.ts +40 -0
- package/types/ngneers-controls-chip.d.ts +202 -0
- package/types/ngneers-controls-default-icons.d.ts +5 -0
- package/types/ngneers-controls-defer.d.ts +48 -0
- package/types/ngneers-controls-dialog.d.ts +399 -0
- package/types/ngneers-controls-directives.d.ts +171 -0
- package/types/ngneers-controls-drawer.d.ts +308 -0
- package/types/ngneers-controls-edit-inplace.d.ts +263 -0
- package/types/ngneers-controls-errors.d.ts +155 -0
- package/types/ngneers-controls-filter.d.ts +609 -0
- package/types/ngneers-controls-hint.d.ts +88 -0
- package/types/ngneers-controls-i18n-translations-de.d.ts +164 -0
- package/types/ngneers-controls-i18n-translations-en.d.ts +164 -0
- package/types/ngneers-controls-i18n.d.ts +186 -0
- package/types/ngneers-controls-icon.d.ts +68 -0
- package/types/ngneers-controls-inplace.d.ts +98 -0
- package/types/ngneers-controls-input-field.d.ts +248 -0
- package/types/ngneers-controls-input.d.ts +30 -0
- package/types/ngneers-controls-item-view.d.ts +215 -0
- package/types/ngneers-controls-list-box.d.ts +334 -0
- package/types/ngneers-controls-mask-input.d.ts +434 -0
- package/types/ngneers-controls-menu.d.ts +192 -0
- package/types/ngneers-controls-message.d.ts +21 -0
- package/types/ngneers-controls-number-input.d.ts +146 -0
- package/types/ngneers-controls-paginator.d.ts +255 -0
- package/types/ngneers-controls-popover.d.ts +116 -0
- package/types/ngneers-controls-progress.d.ts +49 -0
- package/types/ngneers-controls-radio.d.ts +91 -0
- package/types/ngneers-controls-roving-focus.d.ts +107 -0
- package/types/ngneers-controls-scroll-shadow.d.ts +25 -0
- package/types/ngneers-controls-scroller.d.ts +151 -0
- package/types/ngneers-controls-select-button.d.ts +51 -0
- package/types/ngneers-controls-select.d.ts +409 -0
- package/types/ngneers-controls-slider.d.ts +57 -0
- package/types/ngneers-controls-snackbar.d.ts +330 -0
- package/types/ngneers-controls-spin-buttons.d.ts +69 -0
- package/types/ngneers-controls-spinner.d.ts +117 -0
- package/types/ngneers-controls-splitter.d.ts +465 -0
- package/types/ngneers-controls-state.d.ts +102 -0
- package/types/ngneers-controls-switch.d.ts +18 -0
- package/types/ngneers-controls-table.d.ts +3127 -0
- package/types/ngneers-controls-tabs.d.ts +118 -0
- package/types/ngneers-controls-tag.d.ts +21 -0
- package/types/ngneers-controls-toast.d.ts +298 -0
- package/types/ngneers-controls-toggle-button.d.ts +48 -0
- package/types/ngneers-controls-tooltip.d.ts +189 -0
- package/types/ngneers-controls-tree.d.ts +409 -0
- package/types/ngneers-controls-upload.d.ts +347 -0
- package/types/ngneers-controls-utils-ng.d.ts +129 -0
- package/types/ngneers-controls-utils.d.ts +130 -0
- package/types/ngneers-controls.d.ts +2 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { input, Directive, inject, ElementRef, computed, output, Component, viewChildren, afterNextRender, signal, ApplicationRef, createComponent, Injectable, InjectionToken, runInInjectionContext } from '@angular/core';
|
|
3
|
+
import { Platform } from '@ngneers/controls/api/ng';
|
|
4
|
+
import { NgnError } from '@ngneers/controls/utils';
|
|
5
|
+
import { NotificationRegionController, injectOrThrow } from '@ngneers/controls/utils-ng';
|
|
6
|
+
import { NgnBase, NgnPt, provideSelf } from '@ngneers/controls/base';
|
|
7
|
+
import { I18n } from '@ngneers/controls/i18n';
|
|
8
|
+
import { toastControlTemplate } from '@ngneers/controls-themes/templates/toast';
|
|
9
|
+
import { NgTemplateOutlet } from '@angular/common';
|
|
10
|
+
import { NgnButton } from '@ngneers/controls/button';
|
|
11
|
+
import { NgnIcon } from '@ngneers/controls/icon';
|
|
12
|
+
|
|
13
|
+
const DEFAULT_TOAST_OPTIONS = {
|
|
14
|
+
closable: false,
|
|
15
|
+
autoHide: 5000,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Template inputs for {@link NgnToast}, extracted into a dedicated base class so the
|
|
20
|
+
* component itself stays focused on behavior.
|
|
21
|
+
*/
|
|
22
|
+
class ToastTemplates extends NgnBase {
|
|
23
|
+
/** Custom template for the toast body, rendered in place of the plain `content` string. */
|
|
24
|
+
templateContent = input(/* @ts-ignore */
|
|
25
|
+
...(ngDevMode ? [undefined, { debugName: "templateContent" }] : /* istanbul ignore next */ []));
|
|
26
|
+
/** Custom template for the toast header, rendered in place of the plain `header` string. */
|
|
27
|
+
templateHeader = input(/* @ts-ignore */
|
|
28
|
+
...(ngDevMode ? [undefined, { debugName: "templateHeader" }] : /* istanbul ignore next */ []));
|
|
29
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: ToastTemplates, deps: null, target: i0.ɵɵFactoryTarget.Directive });
|
|
30
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.5", type: ToastTemplates, isStandalone: true, inputs: { templateContent: { classPropertyName: "templateContent", publicName: "templateContent", isSignal: true, isRequired: false, transformFunction: null }, templateHeader: { classPropertyName: "templateHeader", publicName: "templateHeader", isSignal: true, isRequired: false, transformFunction: null } }, usesInheritance: true, ngImport: i0 });
|
|
31
|
+
}
|
|
32
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: ToastTemplates, decorators: [{
|
|
33
|
+
type: Directive
|
|
34
|
+
}], propDecorators: { templateContent: [{ type: i0.Input, args: [{ isSignal: true, alias: "templateContent", required: false }] }], templateHeader: [{ type: i0.Input, args: [{ isSignal: true, alias: "templateHeader", required: false }] }] } });
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @category control
|
|
38
|
+
*/
|
|
39
|
+
class NgnToast extends ToastTemplates {
|
|
40
|
+
theme = this.injectThemeTemplate(toastControlTemplate, 'root');
|
|
41
|
+
i18n = inject(I18n).translations;
|
|
42
|
+
_host = inject(ElementRef);
|
|
43
|
+
/** The toast header text. For richer markup use {@link templateHeader} instead. */
|
|
44
|
+
header = input(/* @ts-ignore */
|
|
45
|
+
...(ngDevMode ? [undefined, { debugName: "header" }] : /* istanbul ignore next */ []));
|
|
46
|
+
/** The toast body text. For richer markup use {@link templateContent} instead. */
|
|
47
|
+
content = input(/* @ts-ignore */
|
|
48
|
+
...(ngDevMode ? [undefined, { debugName: "content" }] : /* istanbul ignore next */ []));
|
|
49
|
+
/**
|
|
50
|
+
* Politeness of the live region used to announce the toast to assistive tech.
|
|
51
|
+
* When omitted, it is derived from {@link color}: `error`/`warning` announce as
|
|
52
|
+
* `assertive` (mapped to `role="alert"`), everything else as `polite`
|
|
53
|
+
* (`role="status"`).
|
|
54
|
+
*/
|
|
55
|
+
ariaLive = input(/* @ts-ignore */
|
|
56
|
+
...(ngDevMode ? [undefined, { debugName: "ariaLive" }] : /* istanbul ignore next */ []));
|
|
57
|
+
/** Resolved live-region politeness — explicit {@link ariaLive} input, else derived from color. */
|
|
58
|
+
liveMode = computed(() => {
|
|
59
|
+
const explicit = this.ariaLive();
|
|
60
|
+
if (explicit) {
|
|
61
|
+
return explicit;
|
|
62
|
+
}
|
|
63
|
+
const color = this.color();
|
|
64
|
+
return color === 'error' || color === 'warning' ? 'assertive' : 'polite';
|
|
65
|
+
}, /* @ts-ignore */
|
|
66
|
+
...(ngDevMode ? [{ debugName: "liveMode" }] : /* istanbul ignore next */ []));
|
|
67
|
+
/** Landmark role paired with {@link liveMode} — `alert` when assertive, else `status`. */
|
|
68
|
+
role = computed(() => (this.liveMode() === 'assertive' ? 'alert' : 'status'), /* @ts-ignore */
|
|
69
|
+
...(ngDevMode ? [{ debugName: "role" }] : /* istanbul ignore next */ []));
|
|
70
|
+
/** Icon to display alongside the toast content. */
|
|
71
|
+
icon = input(/* @ts-ignore */
|
|
72
|
+
...(ngDevMode ? [undefined, { debugName: "icon" }] : /* istanbul ignore next */ []));
|
|
73
|
+
/** Icon for the close button. */
|
|
74
|
+
iconClose = input(/* @ts-ignore */
|
|
75
|
+
...(ngDevMode ? [undefined, { debugName: "iconClose" }] : /* istanbul ignore next */ []));
|
|
76
|
+
/**
|
|
77
|
+
* Whether the toast renders a close button and can be dismissed by the user
|
|
78
|
+
* (e.g. via the close button or `Escape`).
|
|
79
|
+
* @default false
|
|
80
|
+
*/
|
|
81
|
+
closable = input(DEFAULT_TOAST_OPTIONS.closable, /* @ts-ignore */
|
|
82
|
+
...(ngDevMode ? [{ debugName: "closable" }] : /* istanbul ignore next */ []));
|
|
83
|
+
/**
|
|
84
|
+
* Time in milliseconds before the toast auto-hides, or `false` to disable
|
|
85
|
+
* auto-hiding. The timer pauses while the toast is hovered or focused.
|
|
86
|
+
* @default 5000
|
|
87
|
+
*/
|
|
88
|
+
autoHide = input(DEFAULT_TOAST_OPTIONS.autoHide, /* @ts-ignore */
|
|
89
|
+
...(ngDevMode ? [{ debugName: "autoHide" }] : /* istanbul ignore next */ []));
|
|
90
|
+
/** Emitted when the toast is dismissed, whether by the user or the auto-hide timer. */
|
|
91
|
+
closeToast = output();
|
|
92
|
+
_closeTimeout;
|
|
93
|
+
_remaining = 0;
|
|
94
|
+
_startedAt = 0;
|
|
95
|
+
_hovered = false;
|
|
96
|
+
_focused = false;
|
|
97
|
+
_regionPaused = false;
|
|
98
|
+
/** Element that had focus before the user moved into the toast; focus returns here on close. */
|
|
99
|
+
_returnFocus = null;
|
|
100
|
+
ngOnInit() {
|
|
101
|
+
const autoHide = this.autoHide();
|
|
102
|
+
this._remaining = autoHide && autoHide > 0 ? autoHide : 0;
|
|
103
|
+
this.startHideTimer();
|
|
104
|
+
}
|
|
105
|
+
startHideTimer() {
|
|
106
|
+
// Idempotent: a timer already running must not be restarted (would reset _remaining tracking).
|
|
107
|
+
if (this._closeTimeout !== undefined) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (this._remaining <= 0) {
|
|
111
|
+
// Distinguish "auto-hide disabled" (never started, _startedAt still 0) from
|
|
112
|
+
// "a pause consumed all the remaining time" (_startedAt > 0). In the latter
|
|
113
|
+
// case the toast is overdue, so close now rather than leave it stuck open
|
|
114
|
+
// with no active timer — otherwise a non-closable toast can never dismiss.
|
|
115
|
+
if (this._startedAt > 0) {
|
|
116
|
+
this.close();
|
|
117
|
+
}
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
this._startedAt = performance.now();
|
|
121
|
+
this._closeTimeout = setTimeout(() => this.close(), this._remaining);
|
|
122
|
+
}
|
|
123
|
+
pauseHideTimer() {
|
|
124
|
+
if (this._closeTimeout === undefined) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
clearTimeout(this._closeTimeout);
|
|
128
|
+
this._closeTimeout = undefined;
|
|
129
|
+
this._remaining -= performance.now() - this._startedAt;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Pause the auto-hide timer while the toast is hovered, focused (a WCAG 2.2.1
|
|
133
|
+
* requirement so keyboard and screen-reader users can read it), or while the
|
|
134
|
+
* whole region holds focus, resuming once none holds.
|
|
135
|
+
*/
|
|
136
|
+
updatePause() {
|
|
137
|
+
const shouldPause = this._regionPaused || this._hovered || this._focused;
|
|
138
|
+
if (shouldPause) {
|
|
139
|
+
this.pauseHideTimer();
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
this.startHideTimer();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
close() {
|
|
146
|
+
this.restoreFocus();
|
|
147
|
+
this.closeToast.emit();
|
|
148
|
+
if (this._closeTimeout) {
|
|
149
|
+
clearTimeout(this._closeTimeout);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/** Return focus to the pre-toast element when it is dismissed while focused within. */
|
|
153
|
+
restoreFocus() {
|
|
154
|
+
const host = this._host.nativeElement;
|
|
155
|
+
if (host.contains(document.activeElement) && this._returnFocus?.isConnected) {
|
|
156
|
+
this._returnFocus.focus();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/** Move DOM focus to the toast root (used by the region's roving/hotkey navigation). */
|
|
160
|
+
focus() {
|
|
161
|
+
this._host.nativeElement.focus();
|
|
162
|
+
}
|
|
163
|
+
/** Freeze the timer because the notification region as a whole holds focus. */
|
|
164
|
+
regionPause() {
|
|
165
|
+
this._regionPaused = true;
|
|
166
|
+
this.updatePause();
|
|
167
|
+
}
|
|
168
|
+
/** Release the region-level freeze; resumes unless still hovered/focused. */
|
|
169
|
+
regionResume() {
|
|
170
|
+
this._regionPaused = false;
|
|
171
|
+
this.updatePause();
|
|
172
|
+
}
|
|
173
|
+
onEscape() {
|
|
174
|
+
if (this.closable()) {
|
|
175
|
+
this.close();
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
mouseEnter() {
|
|
179
|
+
this._hovered = true;
|
|
180
|
+
this.updatePause();
|
|
181
|
+
}
|
|
182
|
+
mouseLeave() {
|
|
183
|
+
this._hovered = false;
|
|
184
|
+
this.updatePause();
|
|
185
|
+
}
|
|
186
|
+
focusEnter(event) {
|
|
187
|
+
const from = event.relatedTarget;
|
|
188
|
+
if (from && !this._host.nativeElement.contains(from)) {
|
|
189
|
+
this._returnFocus = from;
|
|
190
|
+
}
|
|
191
|
+
this._focused = true;
|
|
192
|
+
this.updatePause();
|
|
193
|
+
}
|
|
194
|
+
focusLeave(event) {
|
|
195
|
+
// focusout bubbles; ignore focus moving between the toast's own children.
|
|
196
|
+
const next = event.relatedTarget;
|
|
197
|
+
if (next && this._host.nativeElement.contains(next)) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
this._focused = false;
|
|
201
|
+
this.updatePause();
|
|
202
|
+
}
|
|
203
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: NgnToast, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
204
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: NgnToast, isStandalone: true, selector: "ngn-toast", inputs: { header: { classPropertyName: "header", publicName: "header", isSignal: true, isRequired: false, transformFunction: null }, content: { classPropertyName: "content", publicName: "content", isSignal: true, isRequired: false, transformFunction: null }, ariaLive: { classPropertyName: "ariaLive", publicName: "ariaLive", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, iconClose: { classPropertyName: "iconClose", publicName: "iconClose", isSignal: true, isRequired: false, transformFunction: null }, closable: { classPropertyName: "closable", publicName: "closable", isSignal: true, isRequired: false, transformFunction: null }, autoHide: { classPropertyName: "autoHide", publicName: "autoHide", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closeToast: "closeToast" }, host: { attributes: { "aria-atomic": "true" }, listeners: { "mouseenter": "mouseEnter()", "mouseleave": "mouseLeave()", "focusin": "focusEnter($event)", "focusout": "focusLeave($event)", "keydown.escape": "onEscape()" }, properties: { "animate.enter": "theme.class('anim-enter')", "animate.leave": "theme.class('anim-leave')", "attr.role": "role()", "attr.aria-live": "liveMode()" } }, providers: [provideSelf(NgnToast)], usesInheritance: true, ngImport: i0, template: "@if (templateHeader(); as headerTemplate) {\n <ng-container\n [ngTemplateOutlet]=\"headerTemplate\"\n [ngTemplateOutletContext]=\"{\n header: header(),\n closable: !!closable(),\n closeToast: close.bind(this),\n }\"\n ></ng-container>\n} @else {\n <div [ptInt]=\"this\" [ptClass]=\"'defaultHeader'\">\n <span [ptInt]=\"this\" [ptClass]=\"'defaultHeaderText'\">\n @if (icon(); as icon) {\n <ngn-icon [icon]=\"icon\" />\n }\n {{ header() }}\n </span>\n @if (closable()) {\n <button ngnButton kind=\"icon\" [attr.aria-label]=\"i18n['toast_close']()\" (click)=\"close()\">\n <ngn-icon [defaultIcon]=\"'toast-close'\" [icon]=\"iconClose()\" [attr.aria-hidden]=\"true\" />\n </button>\n }\n </div>\n}\n@if (templateContent(); as contentTemplate) {\n <ng-container\n [ngTemplateOutlet]=\"contentTemplate\"\n [ngTemplateOutletContext]=\"{\n content: content(),\n }\"\n ></ng-container>\n} @else {\n <div [ptInt]=\"this\" [ptClass]=\"'defaultContent'\">\n {{ content() }}\n </div>\n}\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: NgnPt, selector: "[ptInt]", inputs: ["ptInt", "ptClass", "ptDep"] }, { kind: "directive", type: NgnButton, selector: "button[ngnButton], a[ngnButton]", inputs: ["ngnButtonInline"], exportAs: ["ngnButton"] }, { kind: "component", type: NgnIcon, selector: "ngn-icon", inputs: ["defaultIcon", "icon"] }] });
|
|
205
|
+
}
|
|
206
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: NgnToast, decorators: [{
|
|
207
|
+
type: Component,
|
|
208
|
+
args: [{ selector: 'ngn-toast', imports: [NgTemplateOutlet, NgnPt, NgnButton, NgnIcon], providers: [provideSelf(NgnToast)], host: {
|
|
209
|
+
'(mouseenter)': 'mouseEnter()',
|
|
210
|
+
'(mouseleave)': 'mouseLeave()',
|
|
211
|
+
'(focusin)': 'focusEnter($event)',
|
|
212
|
+
'(focusout)': 'focusLeave($event)',
|
|
213
|
+
'(keydown.escape)': 'onEscape()',
|
|
214
|
+
'[animate.enter]': "theme.class('anim-enter')",
|
|
215
|
+
'[animate.leave]': "theme.class('anim-leave')",
|
|
216
|
+
'[attr.role]': 'role()',
|
|
217
|
+
'[attr.aria-live]': 'liveMode()',
|
|
218
|
+
'aria-atomic': 'true',
|
|
219
|
+
}, template: "@if (templateHeader(); as headerTemplate) {\n <ng-container\n [ngTemplateOutlet]=\"headerTemplate\"\n [ngTemplateOutletContext]=\"{\n header: header(),\n closable: !!closable(),\n closeToast: close.bind(this),\n }\"\n ></ng-container>\n} @else {\n <div [ptInt]=\"this\" [ptClass]=\"'defaultHeader'\">\n <span [ptInt]=\"this\" [ptClass]=\"'defaultHeaderText'\">\n @if (icon(); as icon) {\n <ngn-icon [icon]=\"icon\" />\n }\n {{ header() }}\n </span>\n @if (closable()) {\n <button ngnButton kind=\"icon\" [attr.aria-label]=\"i18n['toast_close']()\" (click)=\"close()\">\n <ngn-icon [defaultIcon]=\"'toast-close'\" [icon]=\"iconClose()\" [attr.aria-hidden]=\"true\" />\n </button>\n }\n </div>\n}\n@if (templateContent(); as contentTemplate) {\n <ng-container\n [ngTemplateOutlet]=\"contentTemplate\"\n [ngTemplateOutletContext]=\"{\n content: content(),\n }\"\n ></ng-container>\n} @else {\n <div [ptInt]=\"this\" [ptClass]=\"'defaultContent'\">\n {{ content() }}\n </div>\n}\n" }]
|
|
220
|
+
}], propDecorators: { header: [{ type: i0.Input, args: [{ isSignal: true, alias: "header", required: false }] }], content: [{ type: i0.Input, args: [{ isSignal: true, alias: "content", required: false }] }], ariaLive: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLive", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], iconClose: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconClose", required: false }] }], closable: [{ type: i0.Input, args: [{ isSignal: true, alias: "closable", required: false }] }], autoHide: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoHide", required: false }] }], closeToast: [{ type: i0.Output, args: ["closeToast"] }] } });
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* The host component that renders toasts.
|
|
224
|
+
*/
|
|
225
|
+
class NgnToastHost extends NgnBase {
|
|
226
|
+
theme = this.injectThemeTemplate(toastControlTemplate, 'host');
|
|
227
|
+
i18n = inject(I18n).translations;
|
|
228
|
+
_toastManager = inject(NgnToastManager);
|
|
229
|
+
_el = inject(ElementRef);
|
|
230
|
+
_items = viewChildren(NgnToast, /* @ts-ignore */
|
|
231
|
+
...(ngDevMode ? [{ debugName: "_items" }] : /* istanbul ignore next */ []));
|
|
232
|
+
toasts = this._toastManager.toasts;
|
|
233
|
+
/**
|
|
234
|
+
* Drives keyboard access to the region: `F6` jumps focus to the newest toast,
|
|
235
|
+
* arrow keys rove between them, and focus anywhere inside pauses every timer.
|
|
236
|
+
*/
|
|
237
|
+
region = new NotificationRegionController(this._el.nativeElement, () => this._items());
|
|
238
|
+
constructor() {
|
|
239
|
+
super();
|
|
240
|
+
afterNextRender(() => {
|
|
241
|
+
this._el.nativeElement.showPopover();
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
removeToast(id) {
|
|
245
|
+
this._toastManager.removeToast(id);
|
|
246
|
+
}
|
|
247
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: NgnToastHost, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
248
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: NgnToastHost, isStandalone: true, selector: "ngn-toast-host", host: { attributes: { "role": "region" }, listeners: { "document:keydown": "region.handleGlobalKeydown($event)", "keydown": "region.handleKeydown($event)", "focusin": "region.handleFocusIn()", "focusout": "region.handleFocusOut($event)" }, properties: { "attr.popover": "\"manual\"", "attr.aria-label": "i18n['toast_region']()" } }, viewQueries: [{ propertyName: "_items", predicate: NgnToast, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "@for (toast of toasts(); track toast.id; let i = $index) {\n <ngn-toast\n [attr.tabindex]=\"region.tabbableIndex() === i ? 0 : -1\"\n [header]=\"toast.header\"\n [content]=\"toast.content\"\n [color]=\"toast.color\"\n [icon]=\"toast.icon\"\n [autoHide]=\"toast.autoHide\"\n [templateContent]=\"toast.contentTemplate\"\n [templateHeader]=\"toast.headerTemplate\"\n [closable]=\"!!toast.closable\"\n (closeToast)=\"removeToast(toast.id)\"\n />\n}\n", dependencies: [{ kind: "component", type: NgnToast, selector: "ngn-toast", inputs: ["header", "content", "ariaLive", "icon", "iconClose", "closable", "autoHide"], outputs: ["closeToast"] }] });
|
|
249
|
+
}
|
|
250
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: NgnToastHost, decorators: [{
|
|
251
|
+
type: Component,
|
|
252
|
+
args: [{ selector: 'ngn-toast-host', imports: [NgnToast], host: {
|
|
253
|
+
'[attr.popover]': '"manual"',
|
|
254
|
+
role: 'region',
|
|
255
|
+
'[attr.aria-label]': "i18n['toast_region']()",
|
|
256
|
+
'(document:keydown)': 'region.handleGlobalKeydown($event)',
|
|
257
|
+
'(keydown)': 'region.handleKeydown($event)',
|
|
258
|
+
'(focusin)': 'region.handleFocusIn()',
|
|
259
|
+
'(focusout)': 'region.handleFocusOut($event)',
|
|
260
|
+
}, template: "@for (toast of toasts(); track toast.id; let i = $index) {\n <ngn-toast\n [attr.tabindex]=\"region.tabbableIndex() === i ? 0 : -1\"\n [header]=\"toast.header\"\n [content]=\"toast.content\"\n [color]=\"toast.color\"\n [icon]=\"toast.icon\"\n [autoHide]=\"toast.autoHide\"\n [templateContent]=\"toast.contentTemplate\"\n [templateHeader]=\"toast.headerTemplate\"\n [closable]=\"!!toast.closable\"\n (closeToast)=\"removeToast(toast.id)\"\n />\n}\n" }]
|
|
261
|
+
}], ctorParameters: () => [], propDecorators: { _items: [{ type: i0.ViewChildren, args: [i0.forwardRef(() => NgnToast), { isSignal: true }] }] } });
|
|
262
|
+
|
|
263
|
+
class NgnToastManager {
|
|
264
|
+
_toasts = signal([], /* @ts-ignore */
|
|
265
|
+
...(ngDevMode ? [{ debugName: "_toasts" }] : /* istanbul ignore next */ []));
|
|
266
|
+
_appRef = inject(ApplicationRef);
|
|
267
|
+
_userDefaults = injectOrThrow(NGN_TOAST_USER_DEFAULTS, 'NgnToastManager', 'Failed to inject NGN_TOAST_USER_DEFAULTS, make sure to use withToasts() to provide ngn toasts!');
|
|
268
|
+
_nextId = 0;
|
|
269
|
+
toasts = this._toasts.asReadonly();
|
|
270
|
+
_component;
|
|
271
|
+
constructor() {
|
|
272
|
+
if (!inject(Platform).isBrowser) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
// Defer creation to avoid issues with Angular's injection tree during app initialization
|
|
276
|
+
queueMicrotask(() => {
|
|
277
|
+
// Create and attach the toast host component to the application's root
|
|
278
|
+
this._component = createComponent(NgnToastHost, {
|
|
279
|
+
environmentInjector: this._appRef.injector,
|
|
280
|
+
});
|
|
281
|
+
this._appRef.attachView(this._component.hostView);
|
|
282
|
+
const hostEl = this._component.location.nativeElement;
|
|
283
|
+
const appRootEl = this._appRef.components[0]?.location.nativeElement;
|
|
284
|
+
if (!appRootEl) {
|
|
285
|
+
throw new NgnError('NgnToastManager', 'Failed to find application root element to attach toast host!');
|
|
286
|
+
}
|
|
287
|
+
appRootEl.appendChild(hostEl);
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
ngOnDestroy() {
|
|
291
|
+
if (this._component) {
|
|
292
|
+
this._appRef.detachView(this._component.hostView);
|
|
293
|
+
this._component.destroy();
|
|
294
|
+
this._component = undefined;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
removeToast(id) {
|
|
298
|
+
this._toasts.update(toasts => toasts.filter(t => t.id !== id));
|
|
299
|
+
}
|
|
300
|
+
addToast(options) {
|
|
301
|
+
const id = this._nextId++;
|
|
302
|
+
const toast = { ...DEFAULT_TOAST_OPTIONS, ...this._userDefaults, ...options, id };
|
|
303
|
+
this._toasts.update(toasts => [...toasts, toast]);
|
|
304
|
+
return id;
|
|
305
|
+
}
|
|
306
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: NgnToastManager, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
307
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: NgnToastManager });
|
|
308
|
+
}
|
|
309
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: NgnToastManager, decorators: [{
|
|
310
|
+
type: Injectable
|
|
311
|
+
}], ctorParameters: () => [] });
|
|
312
|
+
|
|
313
|
+
const NGN_TOAST_USER_DEFAULTS = new InjectionToken('NGN_TOAST_USER_DEFAULTS');
|
|
314
|
+
function withToasts(defaultOptions) {
|
|
315
|
+
return {
|
|
316
|
+
providers: [
|
|
317
|
+
NgnToastManager,
|
|
318
|
+
{
|
|
319
|
+
provide: NGN_TOAST_USER_DEFAULTS,
|
|
320
|
+
useValue: defaultOptions || {},
|
|
321
|
+
},
|
|
322
|
+
],
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
class ToastCreator {
|
|
327
|
+
_manager;
|
|
328
|
+
constructor() {
|
|
329
|
+
this._manager = injectOrThrow(NgnToastManager, 'injectToastCreator', 'NgnToastManager not found. Make sure to use withToasts() to provide ngn toasts!');
|
|
330
|
+
}
|
|
331
|
+
show(options) {
|
|
332
|
+
const id = this._manager.addToast(options);
|
|
333
|
+
return {
|
|
334
|
+
hide: () => this._manager.removeToast(id),
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
function injectToastCreator(injector) {
|
|
339
|
+
if (injector) {
|
|
340
|
+
return runInInjectionContext(injector, () => injectToastCreator());
|
|
341
|
+
}
|
|
342
|
+
return new ToastCreator();
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Generated bundle index. Do not edit.
|
|
347
|
+
*/
|
|
348
|
+
|
|
349
|
+
export { NGN_TOAST_USER_DEFAULTS, NgnToast, injectToastCreator, withToasts };
|
|
350
|
+
//# sourceMappingURL=ngneers-controls-toast.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ngneers-controls-toast.mjs","sources":["../../src/toast/defaults.ts","../../src/toast/toast-templates.ts","../../src/toast/toast.ts","../../src/toast/toast.html","../../src/toast/toast-host.ts","../../src/toast/toast-host.html","../../src/toast/toast-manager.ts","../../src/toast/provider.ts","../../src/toast/toast-creator.ts","../../src/toast/ngneers-controls-toast.ts"],"sourcesContent":["import type { NgnToastOptions } from './types';\n\nexport const DEFAULT_TOAST_OPTIONS: NgnToastOptions = {\n closable: false,\n autoHide: 5000,\n};\n","import { Directive, input, TemplateRef } from '@angular/core';\nimport { NgnBase } from '@ngneers/controls/base';\n\nimport type { ContentTemplateType, HeaderTemplateType } from './types';\n\n/**\n * Template inputs for {@link NgnToast}, extracted into a dedicated base class so the\n * component itself stays focused on behavior.\n */\n@Directive()\nexport abstract class ToastTemplates extends NgnBase<'toast'> {\n /** Custom template for the toast body, rendered in place of the plain `content` string. */\n public readonly templateContent = input<TemplateRef<ContentTemplateType> | null>();\n /** Custom template for the toast header, rendered in place of the plain `header` string. */\n public readonly templateHeader = input<TemplateRef<HeaderTemplateType> | null>();\n}\n","import { NgTemplateOutlet } from '@angular/common';\nimport { Component, computed, ElementRef, inject, input, output, type OnInit } from '@angular/core';\nimport { provideSelf, NgnPt } from '@ngneers/controls/base';\nimport { NgnButton } from '@ngneers/controls/button';\nimport { I18n } from '@ngneers/controls/i18n';\nimport { NgnIcon } from '@ngneers/controls/icon';\nimport { toastControlTemplate } from '@ngneers/controls-themes/templates/toast';\n\nimport { DEFAULT_TOAST_OPTIONS } from './defaults';\nimport { ToastTemplates } from './toast-templates';\n\nimport type { IconType } from '@ngneers/controls-custom-types';\n\n/**\n * @category control\n */\n@Component({\n selector: 'ngn-toast',\n templateUrl: './toast.html',\n imports: [NgTemplateOutlet, NgnPt, NgnButton, NgnIcon],\n providers: [provideSelf(NgnToast)],\n host: {\n '(mouseenter)': 'mouseEnter()',\n '(mouseleave)': 'mouseLeave()',\n '(focusin)': 'focusEnter($event)',\n '(focusout)': 'focusLeave($event)',\n '(keydown.escape)': 'onEscape()',\n '[animate.enter]': \"theme.class('anim-enter')\",\n '[animate.leave]': \"theme.class('anim-leave')\",\n '[attr.role]': 'role()',\n '[attr.aria-live]': 'liveMode()',\n 'aria-atomic': 'true',\n },\n})\nexport class NgnToast extends ToastTemplates implements OnInit {\n protected readonly theme = this.injectThemeTemplate(toastControlTemplate, 'root');\n protected readonly i18n = inject(I18n).translations;\n private readonly _host = inject<ElementRef<HTMLElement>>(ElementRef);\n\n /** The toast header text. For richer markup use {@link templateHeader} instead. */\n public readonly header = input<string>();\n /** The toast body text. For richer markup use {@link templateContent} instead. */\n public readonly content = input<string>();\n\n /**\n * Politeness of the live region used to announce the toast to assistive tech.\n * When omitted, it is derived from {@link color}: `error`/`warning` announce as\n * `assertive` (mapped to `role=\"alert\"`), everything else as `polite`\n * (`role=\"status\"`).\n */\n public readonly ariaLive = input<'polite' | 'assertive' | 'off'>();\n\n /** Resolved live-region politeness — explicit {@link ariaLive} input, else derived from color. */\n protected readonly liveMode = computed<'polite' | 'assertive' | 'off'>(() => {\n const explicit = this.ariaLive();\n if (explicit) {\n return explicit;\n }\n const color = this.color();\n return color === 'error' || color === 'warning' ? 'assertive' : 'polite';\n });\n\n /** Landmark role paired with {@link liveMode} — `alert` when assertive, else `status`. */\n protected readonly role = computed(() => (this.liveMode() === 'assertive' ? 'alert' : 'status'));\n\n /** Icon to display alongside the toast content. */\n public readonly icon = input<IconType>();\n /** Icon for the close button. */\n public readonly iconClose = input<IconType>();\n /**\n * Whether the toast renders a close button and can be dismissed by the user\n * (e.g. via the close button or `Escape`).\n * @default false\n */\n public readonly closable = input<boolean | undefined>(DEFAULT_TOAST_OPTIONS.closable);\n /**\n * Time in milliseconds before the toast auto-hides, or `false` to disable\n * auto-hiding. The timer pauses while the toast is hovered or focused.\n * @default 5000\n */\n public readonly autoHide = input<number | false | undefined>(DEFAULT_TOAST_OPTIONS.autoHide);\n\n /** Emitted when the toast is dismissed, whether by the user or the auto-hide timer. */\n public readonly closeToast = output<void>();\n\n private _closeTimeout?: number;\n private _remaining = 0;\n private _startedAt = 0;\n private _hovered = false;\n private _focused = false;\n private _regionPaused = false;\n /** Element that had focus before the user moved into the toast; focus returns here on close. */\n private _returnFocus: HTMLElement | null = null;\n\n public ngOnInit(): void {\n const autoHide = this.autoHide();\n this._remaining = autoHide && autoHide > 0 ? autoHide : 0;\n this.startHideTimer();\n }\n\n private startHideTimer() {\n // Idempotent: a timer already running must not be restarted (would reset _remaining tracking).\n if (this._closeTimeout !== undefined) {\n return;\n }\n if (this._remaining <= 0) {\n // Distinguish \"auto-hide disabled\" (never started, _startedAt still 0) from\n // \"a pause consumed all the remaining time\" (_startedAt > 0). In the latter\n // case the toast is overdue, so close now rather than leave it stuck open\n // with no active timer — otherwise a non-closable toast can never dismiss.\n if (this._startedAt > 0) {\n this.close();\n }\n return;\n }\n this._startedAt = performance.now();\n this._closeTimeout = setTimeout(() => this.close(), this._remaining) as unknown as number;\n }\n\n private pauseHideTimer() {\n if (this._closeTimeout === undefined) {\n return;\n }\n clearTimeout(this._closeTimeout);\n this._closeTimeout = undefined;\n this._remaining -= performance.now() - this._startedAt;\n }\n\n /**\n * Pause the auto-hide timer while the toast is hovered, focused (a WCAG 2.2.1\n * requirement so keyboard and screen-reader users can read it), or while the\n * whole region holds focus, resuming once none holds.\n */\n private updatePause(): void {\n const shouldPause = this._regionPaused || this._hovered || this._focused;\n if (shouldPause) {\n this.pauseHideTimer();\n } else {\n this.startHideTimer();\n }\n }\n\n protected close() {\n this.restoreFocus();\n this.closeToast.emit();\n if (this._closeTimeout) {\n clearTimeout(this._closeTimeout);\n }\n }\n\n /** Return focus to the pre-toast element when it is dismissed while focused within. */\n private restoreFocus(): void {\n const host = this._host.nativeElement;\n if (host.contains(document.activeElement) && this._returnFocus?.isConnected) {\n this._returnFocus.focus();\n }\n }\n\n /** Move DOM focus to the toast root (used by the region's roving/hotkey navigation). */\n public focus(): void {\n this._host.nativeElement.focus();\n }\n\n /** Freeze the timer because the notification region as a whole holds focus. */\n public regionPause(): void {\n this._regionPaused = true;\n this.updatePause();\n }\n\n /** Release the region-level freeze; resumes unless still hovered/focused. */\n public regionResume(): void {\n this._regionPaused = false;\n this.updatePause();\n }\n\n protected onEscape(): void {\n if (this.closable()) {\n this.close();\n }\n }\n\n protected mouseEnter(): void {\n this._hovered = true;\n this.updatePause();\n }\n\n protected mouseLeave(): void {\n this._hovered = false;\n this.updatePause();\n }\n\n protected focusEnter(event: FocusEvent): void {\n const from = event.relatedTarget as HTMLElement | null;\n if (from && !this._host.nativeElement.contains(from)) {\n this._returnFocus = from;\n }\n this._focused = true;\n this.updatePause();\n }\n\n protected focusLeave(event: FocusEvent): void {\n // focusout bubbles; ignore focus moving between the toast's own children.\n const next = event.relatedTarget as Node | null;\n if (next && this._host.nativeElement.contains(next)) {\n return;\n }\n this._focused = false;\n this.updatePause();\n }\n}\n","@if (templateHeader(); as headerTemplate) {\n <ng-container\n [ngTemplateOutlet]=\"headerTemplate\"\n [ngTemplateOutletContext]=\"{\n header: header(),\n closable: !!closable(),\n closeToast: close.bind(this),\n }\"\n ></ng-container>\n} @else {\n <div [ptInt]=\"this\" [ptClass]=\"'defaultHeader'\">\n <span [ptInt]=\"this\" [ptClass]=\"'defaultHeaderText'\">\n @if (icon(); as icon) {\n <ngn-icon [icon]=\"icon\" />\n }\n {{ header() }}\n </span>\n @if (closable()) {\n <button ngnButton kind=\"icon\" [attr.aria-label]=\"i18n['toast_close']()\" (click)=\"close()\">\n <ngn-icon [defaultIcon]=\"'toast-close'\" [icon]=\"iconClose()\" [attr.aria-hidden]=\"true\" />\n </button>\n }\n </div>\n}\n@if (templateContent(); as contentTemplate) {\n <ng-container\n [ngTemplateOutlet]=\"contentTemplate\"\n [ngTemplateOutletContext]=\"{\n content: content(),\n }\"\n ></ng-container>\n} @else {\n <div [ptInt]=\"this\" [ptClass]=\"'defaultContent'\">\n {{ content() }}\n </div>\n}\n","import { afterNextRender, Component, ElementRef, inject, viewChildren } from '@angular/core';\nimport { NgnBase } from '@ngneers/controls/base';\nimport { I18n } from '@ngneers/controls/i18n';\nimport { NotificationRegionController } from '@ngneers/controls/utils-ng';\nimport { toastControlTemplate } from '@ngneers/controls-themes/templates/toast';\n\nimport { NgnToast } from './toast';\nimport { NgnToastManager } from './toast-manager';\n\n/**\n * The host component that renders toasts.\n */\n@Component({\n selector: 'ngn-toast-host',\n templateUrl: 'toast-host.html',\n imports: [NgnToast],\n host: {\n '[attr.popover]': '\"manual\"',\n role: 'region',\n '[attr.aria-label]': \"i18n['toast_region']()\",\n '(document:keydown)': 'region.handleGlobalKeydown($event)',\n '(keydown)': 'region.handleKeydown($event)',\n '(focusin)': 'region.handleFocusIn()',\n '(focusout)': 'region.handleFocusOut($event)',\n },\n})\nexport class NgnToastHost extends NgnBase<'toast'> {\n protected readonly theme = this.injectThemeTemplate(toastControlTemplate, 'host');\n protected readonly i18n = inject(I18n).translations;\n private readonly _toastManager = inject(NgnToastManager);\n private readonly _el = inject<ElementRef<HTMLElement>>(ElementRef);\n\n private readonly _items = viewChildren(NgnToast);\n\n protected readonly toasts = this._toastManager.toasts;\n\n /**\n * Drives keyboard access to the region: `F6` jumps focus to the newest toast,\n * arrow keys rove between them, and focus anywhere inside pauses every timer.\n */\n protected readonly region = new NotificationRegionController(this._el.nativeElement, () =>\n this._items()\n );\n\n constructor() {\n super();\n afterNextRender(() => {\n this._el.nativeElement.showPopover();\n });\n }\n\n protected removeToast(id: number): void {\n this._toastManager.removeToast(id);\n }\n}\n","@for (toast of toasts(); track toast.id; let i = $index) {\n <ngn-toast\n [attr.tabindex]=\"region.tabbableIndex() === i ? 0 : -1\"\n [header]=\"toast.header\"\n [content]=\"toast.content\"\n [color]=\"toast.color\"\n [icon]=\"toast.icon\"\n [autoHide]=\"toast.autoHide\"\n [templateContent]=\"toast.contentTemplate\"\n [templateHeader]=\"toast.headerTemplate\"\n [closable]=\"!!toast.closable\"\n (closeToast)=\"removeToast(toast.id)\"\n />\n}\n","import {\n ApplicationRef,\n ComponentRef,\n createComponent,\n inject,\n Injectable,\n type OnDestroy,\n signal,\n} from '@angular/core';\nimport { Platform } from '@ngneers/controls/api/ng';\nimport { NgnError } from '@ngneers/controls/utils';\nimport { injectOrThrow } from '@ngneers/controls/utils-ng';\n\nimport { DEFAULT_TOAST_OPTIONS } from './defaults';\nimport { NGN_TOAST_USER_DEFAULTS } from './provider';\nimport { NgnToastHost } from './toast-host';\n\nimport type { NgnToastOptions } from './types';\n\ntype ToastFull = NgnToastOptions & { id: number };\n\n@Injectable()\nexport class NgnToastManager implements OnDestroy {\n private readonly _toasts = signal<ToastFull[]>([]);\n private readonly _appRef = inject(ApplicationRef);\n private readonly _userDefaults = injectOrThrow(\n NGN_TOAST_USER_DEFAULTS,\n 'NgnToastManager',\n 'Failed to inject NGN_TOAST_USER_DEFAULTS, make sure to use withToasts() to provide ngn toasts!'\n );\n private _nextId = 0;\n\n public readonly toasts = this._toasts.asReadonly();\n private _component?: ComponentRef<NgnToastHost>;\n\n constructor() {\n if (!inject(Platform).isBrowser) {\n return;\n }\n // Defer creation to avoid issues with Angular's injection tree during app initialization\n queueMicrotask(() => {\n // Create and attach the toast host component to the application's root\n this._component = createComponent(NgnToastHost, {\n environmentInjector: this._appRef.injector,\n });\n this._appRef.attachView(this._component.hostView);\n const hostEl = this._component.location.nativeElement as HTMLElement;\n const appRootEl = this._appRef.components[0]?.location.nativeElement as\n | undefined\n | HTMLElement;\n if (!appRootEl) {\n throw new NgnError(\n 'NgnToastManager',\n 'Failed to find application root element to attach toast host!'\n );\n }\n appRootEl.appendChild(hostEl);\n });\n }\n\n public ngOnDestroy(): void {\n if (this._component) {\n this._appRef.detachView(this._component.hostView);\n this._component.destroy();\n this._component = undefined;\n }\n }\n\n public removeToast(id: number): void {\n this._toasts.update(toasts => toasts.filter(t => t.id !== id));\n }\n\n public addToast(options: NgnToastOptions): number {\n const id = this._nextId++;\n const toast: ToastFull = { ...DEFAULT_TOAST_OPTIONS, ...this._userDefaults, ...options, id };\n this._toasts.update(toasts => [...toasts, toast]);\n return id;\n }\n}\n","import { InjectionToken, type Provider } from '@angular/core';\n\nimport { NgnToastManager } from './toast-manager';\n\nimport type { NgnToastOptionsMeta } from './types';\n\nexport type NgnToastFeature = {\n providers: Provider[];\n};\n\nexport const NGN_TOAST_USER_DEFAULTS = new InjectionToken<NgnToastOptionsMeta>(\n 'NGN_TOAST_USER_DEFAULTS'\n);\n\nexport function withToasts(defaultOptions?: NgnToastOptionsMeta): NgnToastFeature {\n return {\n providers: [\n NgnToastManager,\n {\n provide: NGN_TOAST_USER_DEFAULTS,\n useValue: defaultOptions || {},\n },\n ],\n };\n}\n","import { Injector, runInInjectionContext } from '@angular/core';\nimport { injectOrThrow } from '@ngneers/controls/utils-ng';\n\nimport { NgnToastManager } from './toast-manager';\n\nimport type { NgnToastOptions, NgnToastRef } from './types';\n\nclass ToastCreator {\n private readonly _manager: NgnToastManager;\n\n constructor() {\n this._manager = injectOrThrow(\n NgnToastManager,\n 'injectToastCreator',\n 'NgnToastManager not found. Make sure to use withToasts() to provide ngn toasts!'\n );\n }\n\n public show(options: NgnToastOptions): NgnToastRef {\n const id = this._manager.addToast(options);\n return {\n hide: () => this._manager.removeToast(id),\n };\n }\n}\n\nexport function injectToastCreator(injector?: Injector): ToastCreator {\n if (injector) {\n return runInInjectionContext(injector, () => injectToastCreator());\n }\n return new ToastCreator();\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { input, booleanAttribute, Component } from '@angular/core';
|
|
3
|
+
import { ValueControlBase, NgnPt, provideSelf } from '@ngneers/controls/base';
|
|
4
|
+
import { NgnIcon } from '@ngneers/controls/icon';
|
|
5
|
+
import { toggleButtonControlTemplate } from '@ngneers/controls-themes/templates/toggle-button';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @category control
|
|
9
|
+
*/
|
|
10
|
+
class NgnToggleButton extends ValueControlBase {
|
|
11
|
+
theme = this.injectThemeTemplate(toggleButtonControlTemplate, {
|
|
12
|
+
root: true,
|
|
13
|
+
active: () => this.value(),
|
|
14
|
+
invalid: () => this.invalid(),
|
|
15
|
+
});
|
|
16
|
+
/**
|
|
17
|
+
* Optional labels for on and off states.
|
|
18
|
+
* If not provided, the {@link label} input will be used.
|
|
19
|
+
*/
|
|
20
|
+
labelOn = input(/* @ts-ignore */
|
|
21
|
+
...(ngDevMode ? [undefined, { debugName: "labelOn" }] : /* istanbul ignore next */ []));
|
|
22
|
+
/**
|
|
23
|
+
* Optional labels for on and off states.
|
|
24
|
+
* If not provided, the {@link label} input will be used.
|
|
25
|
+
*/
|
|
26
|
+
labelOff = input(/* @ts-ignore */
|
|
27
|
+
...(ngDevMode ? [undefined, { debugName: "labelOff" }] : /* istanbul ignore next */ []));
|
|
28
|
+
/**
|
|
29
|
+
* Optional icon for the button. Can be used instead of the label or in combination with it.
|
|
30
|
+
* If {@link iconOn} and {@link iconOff} are provided, they will take precedence over this input for their respective states.
|
|
31
|
+
*/
|
|
32
|
+
icon = input(/* @ts-ignore */
|
|
33
|
+
...(ngDevMode ? [undefined, { debugName: "icon" }] : /* istanbul ignore next */ []));
|
|
34
|
+
/**
|
|
35
|
+
* Optional icons for on and off states.
|
|
36
|
+
* If not provided, the {@link icon} input will be used.
|
|
37
|
+
*/
|
|
38
|
+
iconOn = input(/* @ts-ignore */
|
|
39
|
+
...(ngDevMode ? [undefined, { debugName: "iconOn" }] : /* istanbul ignore next */ []));
|
|
40
|
+
/**
|
|
41
|
+
* Optional icons for on and off states.
|
|
42
|
+
* If not provided, the {@link icon} input will be used.
|
|
43
|
+
*/
|
|
44
|
+
iconOff = input(/* @ts-ignore */
|
|
45
|
+
...(ngDevMode ? [undefined, { debugName: "iconOff" }] : /* istanbul ignore next */ []));
|
|
46
|
+
/**
|
|
47
|
+
* If true, the button will always have the width of the longest possible content.
|
|
48
|
+
* @default false
|
|
49
|
+
*/
|
|
50
|
+
fixedWidth = input(false, { ...(ngDevMode ? { debugName: "fixedWidth" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
51
|
+
toggle() {
|
|
52
|
+
if (this.disabled() || this.readonly()) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
this.value.update(v => !v);
|
|
56
|
+
}
|
|
57
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: NgnToggleButton, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
58
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: NgnToggleButton, isStandalone: true, selector: "ngn-toggle-button", inputs: { labelOn: { classPropertyName: "labelOn", publicName: "labelOn", isSignal: true, isRequired: false, transformFunction: null }, labelOff: { classPropertyName: "labelOff", publicName: "labelOff", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, iconOn: { classPropertyName: "iconOn", publicName: "iconOn", isSignal: true, isRequired: false, transformFunction: null }, iconOff: { classPropertyName: "iconOff", publicName: "iconOff", isSignal: true, isRequired: false, transformFunction: null }, fixedWidth: { classPropertyName: "fixedWidth", publicName: "fixedWidth", isSignal: true, isRequired: false, transformFunction: null } }, providers: [provideSelf(NgnToggleButton)], usesInheritance: true, ngImport: i0, template: "<button\n [ptInt]=\"this\"\n [ptClass]=\"'button'\"\n (click)=\"toggle()\"\n [disabled]=\"disabled()\"\n [attr.aria-pressed]=\"value()\"\n [attr.aria-label]=\"label()\"\n [attr.aria-labelledby]=\"labelledBy()\"\n (blur)=\"touched.set(true)\"\n>\n <span [ptInt]=\"this\" [ptClass]=\"'label'\">\n @let renderedIcon = (value() ? iconOn() : iconOff()) ?? icon();\n @if (renderedIcon) {\n <ngn-icon [icon]=\"renderedIcon\" />\n }\n {{ (value() ? labelOn() : labelOff()) ?? label() }}\n </span>\n @if (fixedWidth()) {\n <span inert [ptInt]=\"this\" [ptClass]=\"'placeholder-active'\">\n @let renderedIconOn = iconOn() ?? icon();\n @if (renderedIconOn) {\n <ngn-icon [icon]=\"renderedIconOn\" />\n }\n {{ labelOn() ?? label() }}\n </span>\n <span inert [ptInt]=\"this\" [ptClass]=\"'placeholder'\">\n @let renderedIconOff = iconOff() ?? icon();\n @if (renderedIconOff) {\n <ngn-icon [icon]=\"renderedIconOff\" />\n }\n {{ labelOff() ?? label() }}\n </span>\n }\n</button>\n", dependencies: [{ kind: "directive", type: NgnPt, selector: "[ptInt]", inputs: ["ptInt", "ptClass", "ptDep"] }, { kind: "component", type: NgnIcon, selector: "ngn-icon", inputs: ["defaultIcon", "icon"] }] });
|
|
59
|
+
}
|
|
60
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: NgnToggleButton, decorators: [{
|
|
61
|
+
type: Component,
|
|
62
|
+
args: [{ selector: 'ngn-toggle-button', providers: [provideSelf(NgnToggleButton)], imports: [NgnPt, NgnIcon], template: "<button\n [ptInt]=\"this\"\n [ptClass]=\"'button'\"\n (click)=\"toggle()\"\n [disabled]=\"disabled()\"\n [attr.aria-pressed]=\"value()\"\n [attr.aria-label]=\"label()\"\n [attr.aria-labelledby]=\"labelledBy()\"\n (blur)=\"touched.set(true)\"\n>\n <span [ptInt]=\"this\" [ptClass]=\"'label'\">\n @let renderedIcon = (value() ? iconOn() : iconOff()) ?? icon();\n @if (renderedIcon) {\n <ngn-icon [icon]=\"renderedIcon\" />\n }\n {{ (value() ? labelOn() : labelOff()) ?? label() }}\n </span>\n @if (fixedWidth()) {\n <span inert [ptInt]=\"this\" [ptClass]=\"'placeholder-active'\">\n @let renderedIconOn = iconOn() ?? icon();\n @if (renderedIconOn) {\n <ngn-icon [icon]=\"renderedIconOn\" />\n }\n {{ labelOn() ?? label() }}\n </span>\n <span inert [ptInt]=\"this\" [ptClass]=\"'placeholder'\">\n @let renderedIconOff = iconOff() ?? icon();\n @if (renderedIconOff) {\n <ngn-icon [icon]=\"renderedIconOff\" />\n }\n {{ labelOff() ?? label() }}\n </span>\n }\n</button>\n" }]
|
|
63
|
+
}], propDecorators: { labelOn: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelOn", required: false }] }], labelOff: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelOff", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], iconOn: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconOn", required: false }] }], iconOff: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconOff", required: false }] }], fixedWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "fixedWidth", required: false }] }] } });
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Generated bundle index. Do not edit.
|
|
67
|
+
*/
|
|
68
|
+
|
|
69
|
+
export { NgnToggleButton };
|
|
70
|
+
//# sourceMappingURL=ngneers-controls-toggle-button.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ngneers-controls-toggle-button.mjs","sources":["../../src/toggle-button/toggle-button.ts","../../src/toggle-button/toggle-button.html","../../src/toggle-button/ngneers-controls-toggle-button.ts"],"sourcesContent":["import { booleanAttribute, Component, input } from '@angular/core';\nimport { provideSelf, ValueControlBase, NgnPt } from '@ngneers/controls/base';\nimport { NgnIcon } from '@ngneers/controls/icon';\nimport { toggleButtonControlTemplate } from '@ngneers/controls-themes/templates/toggle-button';\n\nimport type { IconType } from '@ngneers/controls-custom-types';\n\n/**\n * @category control\n */\n@Component({\n selector: 'ngn-toggle-button',\n providers: [provideSelf(NgnToggleButton)],\n\n imports: [NgnPt, NgnIcon],\n templateUrl: './toggle-button.html',\n})\nexport class NgnToggleButton extends ValueControlBase<'toggleButton', boolean> {\n protected readonly theme = this.injectThemeTemplate(toggleButtonControlTemplate, {\n root: true,\n active: () => this.value(),\n invalid: () => this.invalid(),\n });\n\n /**\n * Optional labels for on and off states.\n * If not provided, the {@link label} input will be used.\n */\n public readonly labelOn = input<string>();\n /**\n * Optional labels for on and off states.\n * If not provided, the {@link label} input will be used.\n */\n public readonly labelOff = input<string>();\n /**\n * Optional icon for the button. Can be used instead of the label or in combination with it.\n * If {@link iconOn} and {@link iconOff} are provided, they will take precedence over this input for their respective states.\n */\n public readonly icon = input<IconType>();\n /**\n * Optional icons for on and off states.\n * If not provided, the {@link icon} input will be used.\n */\n public readonly iconOn = input<IconType>();\n /**\n * Optional icons for on and off states.\n * If not provided, the {@link icon} input will be used.\n */\n public readonly iconOff = input<IconType>();\n /**\n * If true, the button will always have the width of the longest possible content.\n * @default false\n */\n public readonly fixedWidth = input(false, { transform: booleanAttribute });\n\n public toggle() {\n if (this.disabled() || this.readonly()) {\n return;\n }\n this.value.update(v => !v);\n }\n}\n","<button\n [ptInt]=\"this\"\n [ptClass]=\"'button'\"\n (click)=\"toggle()\"\n [disabled]=\"disabled()\"\n [attr.aria-pressed]=\"value()\"\n [attr.aria-label]=\"label()\"\n [attr.aria-labelledby]=\"labelledBy()\"\n (blur)=\"touched.set(true)\"\n>\n <span [ptInt]=\"this\" [ptClass]=\"'label'\">\n @let renderedIcon = (value() ? iconOn() : iconOff()) ?? icon();\n @if (renderedIcon) {\n <ngn-icon [icon]=\"renderedIcon\" />\n }\n {{ (value() ? labelOn() : labelOff()) ?? label() }}\n </span>\n @if (fixedWidth()) {\n <span inert [ptInt]=\"this\" [ptClass]=\"'placeholder-active'\">\n @let renderedIconOn = iconOn() ?? icon();\n @if (renderedIconOn) {\n <ngn-icon [icon]=\"renderedIconOn\" />\n }\n {{ labelOn() ?? label() }}\n </span>\n <span inert [ptInt]=\"this\" [ptClass]=\"'placeholder'\">\n @let renderedIconOff = iconOff() ?? icon();\n @if (renderedIconOff) {\n <ngn-icon [icon]=\"renderedIconOff\" />\n }\n {{ labelOff() ?? label() }}\n </span>\n }\n</button>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|