@memberjunction/ng-ui-components 5.43.0 → 5.45.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 (31) hide show
  1. package/dist/lib/accordion/accordion.component.d.ts +137 -2
  2. package/dist/lib/accordion/accordion.component.d.ts.map +1 -1
  3. package/dist/lib/accordion/accordion.component.js +300 -43
  4. package/dist/lib/accordion/accordion.component.js.map +1 -1
  5. package/dist/lib/accordion/accordion.scss +256 -13
  6. package/dist/lib/alert/alert.component.d.ts +97 -0
  7. package/dist/lib/alert/alert.component.d.ts.map +1 -0
  8. package/dist/lib/alert/alert.component.js +235 -0
  9. package/dist/lib/alert/alert.component.js.map +1 -0
  10. package/dist/lib/confirm-dialog/confirm-dialog.component.d.ts +102 -0
  11. package/dist/lib/confirm-dialog/confirm-dialog.component.d.ts.map +1 -0
  12. package/dist/lib/confirm-dialog/confirm-dialog.component.js +259 -0
  13. package/dist/lib/confirm-dialog/confirm-dialog.component.js.map +1 -0
  14. package/dist/lib/confirm-dialog/confirm-dialog.service.d.ts +78 -0
  15. package/dist/lib/confirm-dialog/confirm-dialog.service.d.ts.map +1 -0
  16. package/dist/lib/confirm-dialog/confirm-dialog.service.js +125 -0
  17. package/dist/lib/confirm-dialog/confirm-dialog.service.js.map +1 -0
  18. package/dist/lib/dialog/dialog.component.d.ts +8 -1
  19. package/dist/lib/dialog/dialog.component.d.ts.map +1 -1
  20. package/dist/lib/dialog/dialog.component.js +15 -6
  21. package/dist/lib/dialog/dialog.component.js.map +1 -1
  22. package/dist/lib/empty-state/empty-state.component.d.ts +114 -0
  23. package/dist/lib/empty-state/empty-state.component.d.ts.map +1 -0
  24. package/dist/lib/empty-state/empty-state.component.js +268 -0
  25. package/dist/lib/empty-state/empty-state.component.js.map +1 -0
  26. package/dist/lib/slide-panel/slide-panel.component.js +2 -2
  27. package/dist/public-api.d.ts +4 -0
  28. package/dist/public-api.d.ts.map +1 -1
  29. package/dist/public-api.js +4 -0
  30. package/dist/public-api.js.map +1 -1
  31. package/package.json +2 -2
@@ -2,44 +2,287 @@
2
2
  * MJ Accordion Panel Styles
3
3
  */
4
4
 
5
+ /* The host is a custom element (defaults to display:inline); make it block so
6
+ consumers don't each have to set display/width on the panel. */
7
+ .mj-accordion-panel-host {
8
+ display: block;
9
+ }
10
+
5
11
  .mj-accordion-panel {
6
12
  border: 1px solid var(--mj-border-default);
7
13
  border-radius: var(--mj-radius-sm);
8
- margin-bottom: 4px;
14
+ margin-bottom: 8px;
9
15
  overflow: hidden;
10
16
  }
11
17
 
18
+ /* The header ROW owns the background/separator so the toggle button and the
19
+ optional actions cluster sit on one continuous surface. Hover lives HERE (not on
20
+ the individual title/chevron buttons) so hovering ANY part of the header — title,
21
+ actions, or chevron — lights up the whole bar as one unit. */
22
+ .mj-accordion-header-row {
23
+ display: flex; align-items: stretch;
24
+ background: var(--mj-bg-surface-sunken);
25
+ transition: background var(--mj-transition-fast);
26
+ }
27
+ .mj-accordion-panel:not(.mj-accordion-panel--disabled) > .mj-accordion-header-row:hover {
28
+ background: var(--mj-bg-surface-hover);
29
+ }
30
+
31
+ /* The toggle button is the disclosure control (title + chevron). Transparent over
32
+ the row so the row-level hover shows through; the button itself paints nothing on
33
+ hover (the whole bar reacts together). */
12
34
  .mj-accordion-header {
13
- display: flex; align-items: center; justify-content: space-between;
14
- width: 100%; padding: 12px 16px;
15
- background: var(--mj-bg-surface-sunken); border: none; cursor: pointer;
35
+ flex: 1 1 auto; min-width: 0;
36
+ display: flex; align-items: center; justify-content: space-between; gap: 12px;
37
+ padding: 12px 16px;
38
+ background: transparent; border: none; cursor: pointer;
16
39
  font-family: var(--mj-font-family); font-size: var(--mj-text-sm);
17
40
  font-weight: var(--mj-font-semibold); color: var(--mj-text-primary);
18
- text-align: left; transition: background var(--mj-transition-fast);
19
- &:hover { background: var(--mj-bg-surface-hover); }
41
+ text-align: left;
20
42
  &:focus-visible { outline: none; box-shadow: var(--mj-focus-ring); }
21
43
  }
22
44
 
45
+ /* Header actions — interactive controls (edit/copy/run/checkbox) that live in
46
+ the header but are NOT the toggle. Sibling of the button (no button-in-button).
47
+ Sits BEFORE the chevron so the chevron stays the rightmost element. */
48
+ .mj-accordion-actions {
49
+ display: flex; align-items: center; gap: 4px;
50
+ flex-shrink: 0; padding: 0 4px;
51
+ }
52
+
53
+ /* Chevron toggle — ALWAYS the rightmost element of the header row (after any
54
+ actions). Secondary, a11y-hidden affordance; the title button is the announced
55
+ disclosure control. Stretches the full header height so the whole right edge
56
+ toggles on click. */
57
+ .mj-accordion-chevron {
58
+ flex-shrink: 0;
59
+ align-self: stretch;
60
+ display: flex; align-items: center; justify-content: center;
61
+ padding: 0 16px;
62
+ background: transparent; border: none; cursor: pointer;
63
+ }
64
+
65
+ /* In dark mode the "sunken" header (near-black) sits much darker than the body
66
+ and reads as overly bold. Use the elevated surface instead — one step lighter
67
+ than the body for a subtle raised header. Light mode keeps the sunken look.
68
+ (Emphasis variants are more specific, so they keep their brand backgrounds.) */
69
+ [data-theme="dark"] .mj-accordion-header-row {
70
+ background: var(--mj-bg-surface-elevated);
71
+ }
72
+
23
73
  .mj-accordion-icon {
24
74
  font-size: 0.75rem; color: var(--mj-text-muted);
25
75
  transition: transform var(--mj-transition-base);
26
- .mj-accordion-panel--expanded & { transform: rotate(180deg); }
76
+ /* Scope to THIS panel's own header row (direct child) so a nested accordion's
77
+ chevron doesn't rotate just because an outer panel is expanded. */
78
+ .mj-accordion-panel--expanded > .mj-accordion-header-row & { transform: rotate(180deg); }
27
79
  }
28
80
 
81
+ /* Body animates open/close via grid-template-rows 0fr -> 1fr (smooth auto-height,
82
+ no JS measuring). The body stays in the DOM when collapsed (inert) so the
83
+ transition has something to animate. */
84
+ .mj-accordion-body-outer {
85
+ display: grid;
86
+ grid-template-rows: 0fr;
87
+ transition: grid-template-rows var(--mj-transition-base);
88
+ }
89
+ /* Child combinator (>) is critical: a descendant selector would also match a
90
+ NESTED accordion's body-outer whenever an OUTER panel is expanded, forcing the
91
+ nested panel open and breaking its collapse. Scope to this panel's own body. */
92
+ .mj-accordion-panel--expanded > .mj-accordion-body-outer {
93
+ grid-template-rows: 1fr;
94
+ }
95
+ /* The clip element is what the grid collapses — it must carry NO padding, or a
96
+ padding-height sliver survives the 0fr collapse. overflow:hidden + min-height:0
97
+ let it shrink to a true 0. */
98
+ .mj-accordion-body-clip {
99
+ overflow: hidden;
100
+ min-height: 0;
101
+ }
102
+ /* Padding lives on the inner element (fully clipped at 0fr). Keeps the
103
+ .mj-accordion-body name so existing ::ng-deep consumer overrides still apply. */
29
104
  .mj-accordion-body {
30
105
  padding: 16px;
31
- border-top: 1px solid var(--mj-border-default);
32
106
  background: var(--mj-bg-surface);
33
107
  }
108
+ /* Header/body separator lives on the header row so it only shows while open
109
+ (a border on the collapsing body would leave a stray line at 0fr). */
110
+ .mj-accordion-panel--expanded > .mj-accordion-header-row {
111
+ border-bottom: 1px solid var(--mj-border-default);
112
+ }
113
+
114
+ /* Lay the title slot out as a row so projected content (icon + label + badge/
115
+ count) spaces consistently without each consumer reinventing it. min-width:0
116
+ lets long titles truncate instead of shoving the chevron off the edge. */
117
+ .mj-accordion-title {
118
+ flex: 1;
119
+ min-width: 0;
120
+ display: flex;
121
+ align-items: center;
122
+ gap: 8px;
123
+ }
124
+
125
+ /* The accordion owns the size of icons projected into the title — consumers
126
+ should NOT set font-size on title icons (inline or otherwise). 1em tracks the
127
+ header's own font-size, so icons scale with the title text (and with any
128
+ future Size variant) instead of each form hand-tuning pixels. */
129
+ .mj-accordion-title i {
130
+ font-size: 1em;
131
+ }
132
+
133
+ /* Muted title icons — opt in via [TitleIconMuted]="true" instead of per-consumer
134
+ CSS. (Status/brand-colored icons are still set per-instance inline.) */
135
+ .mj-accordion-panel--muted-icon > .mj-accordion-header-row > .mj-accordion-header .mj-accordion-title i {
136
+ color: var(--mj-text-muted);
137
+ }
34
138
 
35
- .mj-accordion-title { flex: 1; }
139
+ /* ── Title badge ────────────────────────────────────────────────────────────
140
+ Standard count/badge for the title slot. Use instead of per-consumer count
141
+ styling: <span class="mj-accordion-badge">3</span>
142
+ Modifiers: --right (push to the far right, before the chevron),
143
+ --muted (plain muted text, no pill),
144
+ --success / --error / --warning / --info (status pills).
145
+ All token-driven, so dark-mode-safe. */
146
+ .mj-accordion-badge {
147
+ display: inline-flex;
148
+ align-items: center;
149
+ justify-content: center;
150
+ flex-shrink: 0;
151
+ padding: 2px 8px;
152
+ border-radius: var(--mj-radius-full);
153
+ background: var(--mj-bg-surface-card);
154
+ color: var(--mj-text-secondary);
155
+ font-size: var(--mj-text-xs);
156
+ font-weight: var(--mj-font-medium);
157
+ line-height: 1.4;
158
+ }
159
+ .mj-accordion-badge--right { margin-left: auto; }
160
+ .mj-accordion-badge--muted {
161
+ padding: 0;
162
+ background: transparent;
163
+ color: var(--mj-text-muted);
164
+ font-weight: var(--mj-font-normal);
165
+ }
166
+ .mj-accordion-badge--success { background: var(--mj-status-success-bg); color: var(--mj-status-success-text); }
167
+ .mj-accordion-badge--error { background: var(--mj-status-error-bg); color: var(--mj-status-error-text); }
168
+ .mj-accordion-badge--warning { background: var(--mj-status-warning-bg); color: var(--mj-status-warning-text); }
169
+ .mj-accordion-badge--info { background: var(--mj-status-info-bg); color: var(--mj-status-info-text); }
170
+
171
+ .mj-accordion-panel--disabled > .mj-accordion-header-row { opacity: 0.5; }
172
+ .mj-accordion-panel--disabled > .mj-accordion-header-row > .mj-accordion-header,
173
+ .mj-accordion-panel--disabled > .mj-accordion-header-row > .mj-accordion-chevron { cursor: not-allowed; }
174
+
175
+ /* ── Density (Size="sm") — tighter padding + smaller text for dense contexts.
176
+ Child-combinator-scoped so a default panel nested inside a Size="sm" panel
177
+ doesn't inherit the dense styling. ── */
178
+ .mj-accordion-panel--sm {
179
+ > .mj-accordion-header-row > .mj-accordion-header { padding: 8px 12px; font-size: var(--mj-text-xs); }
180
+ > .mj-accordion-header-row > .mj-accordion-actions { padding-right: 4px; }
181
+ > .mj-accordion-header-row > .mj-accordion-chevron { padding: 0 12px; }
182
+ > .mj-accordion-body-outer > .mj-accordion-body-clip > .mj-accordion-body { padding: 10px 12px; }
183
+ }
184
+
185
+ /* ── Bare — drop the box (border + header background) so the panel sits cleanly
186
+ inside a host that already provides chrome (e.g. an expandable card). ── */
187
+ .mj-accordion-panel--bare {
188
+ border: none;
189
+ border-radius: 0;
190
+ > .mj-accordion-header-row { background: transparent; }
191
+ &.mj-accordion-panel--expanded > .mj-accordion-header-row { border-bottom-color: var(--mj-border-subtle); }
192
+ }
193
+
194
+ /* ── Flush body — body manages its own padding (code editors, full-bleed grids) ── */
195
+ .mj-accordion-panel--flush-body .mj-accordion-body { padding: 0; }
196
+
197
+ /* ── Fill — "rail section that owns the leftover height" (entity tree/grid that
198
+ fills remaining vertical space and scrolls internally), rather than the default
199
+ content-height disclosure. Requires the panel's parent to be a flex column with
200
+ min-height:0. The grid-fr open animation is replaced by flex sizing while
201
+ expanded; a collapsed fill panel falls back to the normal grid-0fr collapse,
202
+ so it still sits at natural header height and yields the space to its siblings.
203
+
204
+ The host only claims flex space when expanded (.mj-accordion-fill-active), so a
205
+ collapsed fill panel doesn't hog the rail. ── */
206
+ .mj-accordion-panel-host.mj-accordion-fill-active {
207
+ flex: 1 1 0;
208
+ min-height: 0;
209
+ display: flex;
210
+ flex-direction: column;
211
+ }
212
+ /* Direct-child (>) chains throughout so a nested accordion inside a fill panel's
213
+ body is never affected by this panel's fill/expanded state. */
214
+ .mj-accordion-panel--fill.mj-accordion-panel--expanded {
215
+ flex: 1 1 0;
216
+ min-height: 0;
217
+ display: flex;
218
+ flex-direction: column;
219
+
220
+ > .mj-accordion-header-row { flex: 0 0 auto; }
221
+
222
+ /* Override the grid-fr animation: the body region now fills the column. */
223
+ > .mj-accordion-body-outer {
224
+ display: flex;
225
+ flex-direction: column;
226
+ flex: 1 1 0;
227
+ min-height: 0;
228
+
229
+ > .mj-accordion-body-clip {
230
+ display: flex;
231
+ flex-direction: column;
232
+ flex: 1 1 0;
233
+ min-height: 0;
234
+
235
+ /* The body is a fill region; projected content supplies its own scroll child. */
236
+ > .mj-accordion-body {
237
+ flex: 1 1 0;
238
+ min-height: 0;
239
+ display: flex;
240
+ flex-direction: column;
241
+ overflow: hidden;
242
+ /* Fill mode can't tween the height (it fills the leftover pane, not content
243
+ height), so it gets a reveal instead: a quick fade + slide-in re-triggered
244
+ each time the --expanded class is applied. The chevron still rotates. */
245
+ animation: mj-accordion-fill-reveal var(--mj-transition-base) ease;
246
+ }
247
+ }
248
+ }
249
+ }
250
+
251
+ @keyframes mj-accordion-fill-reveal {
252
+ from { opacity: 0; transform: translateY(-6px); }
253
+ to { opacity: 1; transform: translateY(0); }
254
+ }
255
+
256
+ /* ── Emphasis variants (visual hierarchy, brand-colored — not status) ───────
257
+ Make a panel read bolder/quieter. All driven by brand tokens, so they
258
+ adapt to dark mode + white-labeling. Set via [Variant] on the panel.
259
+ The header owns its text colour; custom title slots should inherit it
260
+ (e.g. `color: inherit`) so the bold `primary` reads correctly. */
261
+
262
+ /* secondary — subtle brand tint */
263
+ .mj-accordion-panel[data-variant='secondary'] {
264
+ border-color: color-mix(in srgb, var(--mj-brand-primary) 30%, var(--mj-border-default));
265
+ > .mj-accordion-header-row { background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface)); }
266
+ &:not(.mj-accordion-panel--disabled) > .mj-accordion-header-row:hover { background: color-mix(in srgb, var(--mj-brand-primary) 14%, var(--mj-bg-surface)); }
267
+ > .mj-accordion-header-row .mj-accordion-header { color: var(--mj-brand-primary); }
268
+ > .mj-accordion-header-row .mj-accordion-icon { color: var(--mj-brand-primary); }
269
+ &.mj-accordion-panel--expanded > .mj-accordion-header-row {
270
+ border-bottom-color: color-mix(in srgb, var(--mj-brand-primary) 22%, var(--mj-border-default));
271
+ }
272
+ }
36
273
 
37
- .mj-accordion-panel--disabled .mj-accordion-header {
38
- opacity: 0.5;
39
- cursor: not-allowed;
40
- &:hover { background: var(--mj-bg-surface-sunken); }
274
+ /* primary — bold, solid brand header (its own colour separates it; no extra line) */
275
+ .mj-accordion-panel[data-variant='primary'] {
276
+ border-color: var(--mj-brand-primary);
277
+ > .mj-accordion-header-row { background: var(--mj-brand-primary); }
278
+ &:not(.mj-accordion-panel--disabled) > .mj-accordion-header-row:hover { background: var(--mj-brand-primary-hover); }
279
+ > .mj-accordion-header-row .mj-accordion-header { color: var(--mj-text-inverse); }
280
+ > .mj-accordion-header-row .mj-accordion-icon { color: var(--mj-text-inverse); }
281
+ &.mj-accordion-panel--expanded > .mj-accordion-header-row { border-bottom-color: transparent; }
41
282
  }
42
283
 
43
284
  @media (prefers-reduced-motion: reduce) {
44
285
  .mj-accordion-icon { transition: none; }
286
+ .mj-accordion-body-outer { transition: none; }
287
+ .mj-accordion-panel--fill.mj-accordion-panel--expanded > .mj-accordion-body-outer > .mj-accordion-body-clip > .mj-accordion-body { animation: none; }
45
288
  }
@@ -0,0 +1,97 @@
1
+ import { EventEmitter } from '@angular/core';
2
+ import * as i0 from "@angular/core";
3
+ /**
4
+ * Visual intent of an `<mj-alert>`. Drives the color treatment and the default
5
+ * icon, so callers don't restate either for the common cases.
6
+ *
7
+ * - `info` — neutral informational notice (default).
8
+ * - `success` — a positive confirmation that stays on screen.
9
+ * - `warning` — a non-blocking caution.
10
+ * - `error` — a failure / blocking problem.
11
+ */
12
+ export type MJAlertVariant = 'info' | 'success' | 'warning' | 'error';
13
+ /** Density of an `<mj-alert>` — controls padding and font size. */
14
+ export type MJAlertSize = 'sm' | 'md';
15
+ /**
16
+ * mj-alert — Canonical inline alert / banner. The persistent, in-flow message box
17
+ * (info / success / warning / error) — NOT the transient corner toast (that's
18
+ * `NotificationService` from `@memberjunction/ng-notifications`).
19
+ *
20
+ * Replaces the ~50 hand-rolled `.error-message` / `.alert` / `.warning-banner`
21
+ * `<div>`s that pages each style themselves, e.g.:
22
+ * ```html
23
+ * <div class="error-message">
24
+ * <i class="fa-solid fa-circle-exclamation"></i> Couldn't save changes.
25
+ * </div>
26
+ * ```
27
+ *
28
+ * Usage:
29
+ * ```html
30
+ * <!-- Simplest -->
31
+ * <mj-alert Variant="error" Message="Couldn't save changes." />
32
+ *
33
+ * <!-- Title + message + dismiss -->
34
+ * <mj-alert Variant="warning" Title="Unsaved changes"
35
+ * Message="Leaving now will discard them." Dismissible
36
+ * (Dismissed)="onDismiss()" />
37
+ *
38
+ * <!-- Rich body + action buttons -->
39
+ * <mj-alert Variant="info" Title="Read-only mode">
40
+ * You don't have edit permission for this record.
41
+ * <button mjButton variant="primary" actions (click)="requestAccess()">Request access</button>
42
+ * </mj-alert>
43
+ * ```
44
+ *
45
+ * Colors come from the `--mj-status-*` design tokens, so it's dark-mode-safe and
46
+ * themeable. The host gets an ARIA live role (`alert` for error/warning, `status`
47
+ * for info/success) so dynamically-shown alerts announce themselves.
48
+ */
49
+ export declare class MJAlertComponent {
50
+ /** Visual intent — drives color + default icon. Defaults to 'info'. */
51
+ Variant: MJAlertVariant;
52
+ /** Density — 'sm' tightens padding/font for inline/dense placements. */
53
+ Size: MJAlertSize;
54
+ /** Optional bold lead line. */
55
+ Title: string;
56
+ /** Body text. For rich content, project default `<ng-content>` instead. */
57
+ Message: string;
58
+ /**
59
+ * Font Awesome icon class (e.g. "fa-solid fa-key"). When omitted, a sensible
60
+ * default is chosen from `Variant`. Set to `''` to render no icon.
61
+ */
62
+ Icon: string | null;
63
+ /** Show a dismiss (×) button. Accepts a bare attribute (`Dismissible`) or `[Dismissible]="expr"`. */
64
+ Dismissible: boolean;
65
+ /**
66
+ * ARIA live role on the host. When unset, defaults from `Variant`:
67
+ * `error`/`warning` → `'alert'` (assertive), `info`/`success` → `'status'`
68
+ * (polite), so a dynamically-shown alert announces itself. Pass `''` to opt out.
69
+ */
70
+ Role: string | null;
71
+ /** Fired when the user dismisses the alert. */
72
+ Dismissed: EventEmitter<void>;
73
+ protected isDismissed: boolean;
74
+ get IsInfo(): boolean;
75
+ get IsSuccess(): boolean;
76
+ get IsWarning(): boolean;
77
+ get IsError(): boolean;
78
+ get IsSmall(): boolean;
79
+ get IsHidden(): boolean;
80
+ /**
81
+ * Live-region role applied to the host. Explicit `Role` wins (`''` ⇒ no role);
82
+ * otherwise error/warning are an assertive `alert`, info/success a polite `status`.
83
+ */
84
+ get HostRole(): string | null;
85
+ /**
86
+ * Icon actually rendered: the explicit `Icon` when provided (including ""
87
+ * to suppress the icon), otherwise the per-variant default.
88
+ */
89
+ get ResolvedIcon(): string;
90
+ /** Default Font Awesome icon for each variant. */
91
+ static DefaultIconForVariant(variant: MJAlertVariant): string;
92
+ Dismiss(): void;
93
+ static ɵfac: i0.ɵɵFactoryDeclaration<MJAlertComponent, never>;
94
+ static ɵcmp: i0.ɵɵComponentDeclaration<MJAlertComponent, "mj-alert", never, { "Variant": { "alias": "Variant"; "required": false; }; "Size": { "alias": "Size"; "required": false; }; "Title": { "alias": "Title"; "required": false; }; "Message": { "alias": "Message"; "required": false; }; "Icon": { "alias": "Icon"; "required": false; }; "Dismissible": { "alias": "Dismissible"; "required": false; }; "Role": { "alias": "Role"; "required": false; }; }, { "Dismissed": "Dismissed"; }, never, ["*", "[actions]"], true, never>;
95
+ static ngAcceptInputType_Dismissible: unknown;
96
+ }
97
+ //# sourceMappingURL=alert.component.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alert.component.d.ts","sourceRoot":"","sources":["../../../src/lib/alert/alert.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAA+B,YAAY,EAA8B,MAAM,eAAe,CAAC;;AAEtG;;;;;;;;GAQG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;AAEtE,mEAAmE;AACnE,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG,IAAI,CAAC;AAStC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,qBA0Ha,gBAAgB;IAC3B,uEAAuE;IAC9D,OAAO,EAAE,cAAc,CAAU;IAE1C,wEAAwE;IAC/D,IAAI,EAAE,WAAW,CAAQ;IAElC,+BAA+B;IACtB,KAAK,SAAM;IAEpB,2EAA2E;IAClE,OAAO,SAAM;IAEtB;;;OAGG;IACM,IAAI,EAAE,MAAM,GAAG,IAAI,CAAQ;IAEpC,qGAAqG;IAC7D,WAAW,UAAS;IAE5D;;;;OAIG;IACM,IAAI,EAAE,MAAM,GAAG,IAAI,CAAQ;IAEpC,+CAA+C;IACrC,SAAS,qBAA4B;IAE/C,SAAS,CAAC,WAAW,UAAS;IAI9B,IAAyC,MAAM,IAAI,OAAO,CAAoC;IAC9F,IAA4C,SAAS,IAAI,OAAO,CAAuC;IACvG,IAA4C,SAAS,IAAI,OAAO,CAAuC;IACvG,IAA0C,OAAO,IAAI,OAAO,CAAqC;IACjG,IAAuC,OAAO,IAAI,OAAO,CAA+B;IACxF,IAA8C,QAAQ,IAAI,OAAO,CAA6B;IAE9F;;;OAGG;IACH,IACI,QAAQ,IAAI,MAAM,GAAG,IAAI,CAK5B;IAED;;;OAGG;IACH,IAAI,YAAY,IAAI,MAAM,CAKzB;IAED,kDAAkD;IAClD,MAAM,CAAC,qBAAqB,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM;IAI7D,OAAO,IAAI,IAAI;yCAvEJ,gBAAgB;2CAAhB,gBAAgB;0CA4Ewj3L,OAAQ;CAD5l3L"}
@@ -0,0 +1,235 @@
1
+ import { booleanAttribute, Component, EventEmitter, HostBinding, Input, Output } from '@angular/core';
2
+ import * as i0 from "@angular/core";
3
+ const _c0 = ["*", [["", "actions", ""]]];
4
+ const _c1 = ["*", "[actions]"];
5
+ function MJAlertComponent_Conditional_0_Conditional_0_Template(rf, ctx) { if (rf & 1) {
6
+ i0.ɵɵdomElement(0, "i", 6);
7
+ } if (rf & 2) {
8
+ const ctx_r0 = i0.ɵɵnextContext(2);
9
+ i0.ɵɵclassMap(i0.ɵɵinterpolate1("mj-alert__icon ", ctx_r0.ResolvedIcon));
10
+ } }
11
+ function MJAlertComponent_Conditional_0_Conditional_2_Template(rf, ctx) { if (rf & 1) {
12
+ i0.ɵɵdomElementStart(0, "div", 2);
13
+ i0.ɵɵtext(1);
14
+ i0.ɵɵdomElementEnd();
15
+ } if (rf & 2) {
16
+ const ctx_r0 = i0.ɵɵnextContext(2);
17
+ i0.ɵɵadvance();
18
+ i0.ɵɵtextInterpolate(ctx_r0.Title);
19
+ } }
20
+ function MJAlertComponent_Conditional_0_Conditional_3_Template(rf, ctx) { if (rf & 1) {
21
+ i0.ɵɵdomElementStart(0, "div", 3);
22
+ i0.ɵɵtext(1);
23
+ i0.ɵɵdomElementEnd();
24
+ } if (rf & 2) {
25
+ const ctx_r0 = i0.ɵɵnextContext(2);
26
+ i0.ɵɵadvance();
27
+ i0.ɵɵtextInterpolate(ctx_r0.Message);
28
+ } }
29
+ function MJAlertComponent_Conditional_0_Conditional_7_Template(rf, ctx) { if (rf & 1) {
30
+ const _r2 = i0.ɵɵgetCurrentView();
31
+ i0.ɵɵdomElementStart(0, "button", 7);
32
+ i0.ɵɵdomListener("click", function MJAlertComponent_Conditional_0_Conditional_7_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.Dismiss()); });
33
+ i0.ɵɵdomElement(1, "i", 8);
34
+ i0.ɵɵdomElementEnd();
35
+ } }
36
+ function MJAlertComponent_Conditional_0_Template(rf, ctx) { if (rf & 1) {
37
+ i0.ɵɵconditionalCreate(0, MJAlertComponent_Conditional_0_Conditional_0_Template, 1, 3, "i", 0);
38
+ i0.ɵɵdomElementStart(1, "div", 1);
39
+ i0.ɵɵconditionalCreate(2, MJAlertComponent_Conditional_0_Conditional_2_Template, 2, 1, "div", 2);
40
+ i0.ɵɵconditionalCreate(3, MJAlertComponent_Conditional_0_Conditional_3_Template, 2, 1, "div", 3);
41
+ i0.ɵɵprojection(4);
42
+ i0.ɵɵdomElementEnd();
43
+ i0.ɵɵdomElementStart(5, "div", 4);
44
+ i0.ɵɵprojection(6, 1);
45
+ i0.ɵɵdomElementEnd();
46
+ i0.ɵɵconditionalCreate(7, MJAlertComponent_Conditional_0_Conditional_7_Template, 2, 0, "button", 5);
47
+ } if (rf & 2) {
48
+ const ctx_r0 = i0.ɵɵnextContext();
49
+ i0.ɵɵconditional(ctx_r0.ResolvedIcon ? 0 : -1);
50
+ i0.ɵɵadvance(2);
51
+ i0.ɵɵconditional(ctx_r0.Title ? 2 : -1);
52
+ i0.ɵɵadvance();
53
+ i0.ɵɵconditional(ctx_r0.Message ? 3 : -1);
54
+ i0.ɵɵadvance(4);
55
+ i0.ɵɵconditional(ctx_r0.Dismissible ? 7 : -1);
56
+ } }
57
+ const DEFAULT_ICONS = {
58
+ info: 'fa-solid fa-circle-info',
59
+ success: 'fa-solid fa-circle-check',
60
+ warning: 'fa-solid fa-triangle-exclamation',
61
+ error: 'fa-solid fa-circle-exclamation',
62
+ };
63
+ /**
64
+ * mj-alert — Canonical inline alert / banner. The persistent, in-flow message box
65
+ * (info / success / warning / error) — NOT the transient corner toast (that's
66
+ * `NotificationService` from `@memberjunction/ng-notifications`).
67
+ *
68
+ * Replaces the ~50 hand-rolled `.error-message` / `.alert` / `.warning-banner`
69
+ * `<div>`s that pages each style themselves, e.g.:
70
+ * ```html
71
+ * <div class="error-message">
72
+ * <i class="fa-solid fa-circle-exclamation"></i> Couldn't save changes.
73
+ * </div>
74
+ * ```
75
+ *
76
+ * Usage:
77
+ * ```html
78
+ * <!-- Simplest -->
79
+ * <mj-alert Variant="error" Message="Couldn't save changes." />
80
+ *
81
+ * <!-- Title + message + dismiss -->
82
+ * <mj-alert Variant="warning" Title="Unsaved changes"
83
+ * Message="Leaving now will discard them." Dismissible
84
+ * (Dismissed)="onDismiss()" />
85
+ *
86
+ * <!-- Rich body + action buttons -->
87
+ * <mj-alert Variant="info" Title="Read-only mode">
88
+ * You don't have edit permission for this record.
89
+ * <button mjButton variant="primary" actions (click)="requestAccess()">Request access</button>
90
+ * </mj-alert>
91
+ * ```
92
+ *
93
+ * Colors come from the `--mj-status-*` design tokens, so it's dark-mode-safe and
94
+ * themeable. The host gets an ARIA live role (`alert` for error/warning, `status`
95
+ * for info/success) so dynamically-shown alerts announce themselves.
96
+ */
97
+ export class MJAlertComponent {
98
+ /** Visual intent — drives color + default icon. Defaults to 'info'. */
99
+ Variant = 'info';
100
+ /** Density — 'sm' tightens padding/font for inline/dense placements. */
101
+ Size = 'md';
102
+ /** Optional bold lead line. */
103
+ Title = '';
104
+ /** Body text. For rich content, project default `<ng-content>` instead. */
105
+ Message = '';
106
+ /**
107
+ * Font Awesome icon class (e.g. "fa-solid fa-key"). When omitted, a sensible
108
+ * default is chosen from `Variant`. Set to `''` to render no icon.
109
+ */
110
+ Icon = null;
111
+ /** Show a dismiss (×) button. Accepts a bare attribute (`Dismissible`) or `[Dismissible]="expr"`. */
112
+ Dismissible = false;
113
+ /**
114
+ * ARIA live role on the host. When unset, defaults from `Variant`:
115
+ * `error`/`warning` → `'alert'` (assertive), `info`/`success` → `'status'`
116
+ * (polite), so a dynamically-shown alert announces itself. Pass `''` to opt out.
117
+ */
118
+ Role = null;
119
+ /** Fired when the user dismisses the alert. */
120
+ Dismissed = new EventEmitter();
121
+ isDismissed = false;
122
+ // Host classes — individual ADDITIVE bindings so a consumer's own `class="…"`
123
+ // on <mj-alert> is never clobbered (a single `@HostBinding('class')` would).
124
+ get IsInfo() { return this.Variant === 'info'; }
125
+ get IsSuccess() { return this.Variant === 'success'; }
126
+ get IsWarning() { return this.Variant === 'warning'; }
127
+ get IsError() { return this.Variant === 'error'; }
128
+ get IsSmall() { return this.Size === 'sm'; }
129
+ get IsHidden() { return this.isDismissed; }
130
+ /**
131
+ * Live-region role applied to the host. Explicit `Role` wins (`''` ⇒ no role);
132
+ * otherwise error/warning are an assertive `alert`, info/success a polite `status`.
133
+ */
134
+ get HostRole() {
135
+ if (this.Role != null) {
136
+ return this.Role || null;
137
+ }
138
+ return this.Variant === 'error' || this.Variant === 'warning' ? 'alert' : 'status';
139
+ }
140
+ /**
141
+ * Icon actually rendered: the explicit `Icon` when provided (including ""
142
+ * to suppress the icon), otherwise the per-variant default.
143
+ */
144
+ get ResolvedIcon() {
145
+ if (this.Icon != null) {
146
+ return this.Icon;
147
+ }
148
+ return MJAlertComponent.DefaultIconForVariant(this.Variant);
149
+ }
150
+ /** Default Font Awesome icon for each variant. */
151
+ static DefaultIconForVariant(variant) {
152
+ return DEFAULT_ICONS[variant] ?? DEFAULT_ICONS.info;
153
+ }
154
+ Dismiss() {
155
+ this.isDismissed = true;
156
+ this.Dismissed.emit();
157
+ }
158
+ static ɵfac = function MJAlertComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || MJAlertComponent)(); };
159
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: MJAlertComponent, selectors: [["mj-alert"]], hostVars: 13, hostBindings: function MJAlertComponent_HostBindings(rf, ctx) { if (rf & 2) {
160
+ i0.ɵɵattribute("role", ctx.HostRole);
161
+ i0.ɵɵclassProp("mj-alert--info", ctx.IsInfo)("mj-alert--success", ctx.IsSuccess)("mj-alert--warning", ctx.IsWarning)("mj-alert--error", ctx.IsError)("mj-alert--sm", ctx.IsSmall)("mj-alert--dismissed", ctx.IsHidden);
162
+ } }, inputs: { Variant: "Variant", Size: "Size", Title: "Title", Message: "Message", Icon: "Icon", Dismissible: [2, "Dismissible", "Dismissible", booleanAttribute], Role: "Role" }, outputs: { Dismissed: "Dismissed" }, ngContentSelectors: _c1, decls: 1, vars: 1, consts: [["aria-hidden", "true", 3, "class"], [1, "mj-alert__content"], [1, "mj-alert__title"], [1, "mj-alert__message"], [1, "mj-alert__actions"], ["type", "button", "aria-label", "Dismiss", 1, "mj-alert__dismiss"], ["aria-hidden", "true"], ["type", "button", "aria-label", "Dismiss", 1, "mj-alert__dismiss", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-xmark"]], template: function MJAlertComponent_Template(rf, ctx) { if (rf & 1) {
163
+ i0.ɵɵprojectionDef(_c0);
164
+ i0.ɵɵconditionalCreate(0, MJAlertComponent_Conditional_0_Template, 8, 4);
165
+ } if (rf & 2) {
166
+ i0.ɵɵconditional(!ctx.isDismissed ? 0 : -1);
167
+ } }, styles: ["[_nghost-%COMP%] {\n display: flex;\n align-items: flex-start;\n gap: var(--mj-space-3);\n padding: var(--mj-space-3) var(--mj-space-4);\n margin-bottom: var(--mj-space-4);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n font-size: var(--mj-text-sm);\n line-height: 1.45;\n }\n \n\n\n\n [_nghost-%COMP%]:last-child { margin-bottom: 0; }\n .mj-alert--dismissed[_nghost-%COMP%] { display: none; }\n\n .mj-alert__icon[_ngcontent-%COMP%] {\n flex-shrink: 0;\n font-size: 1rem;\n margin-top: 0.1em;\n }\n .mj-alert__content[_ngcontent-%COMP%] { flex: 1; min-width: 0; }\n .mj-alert__title[_ngcontent-%COMP%] { font-weight: var(--mj-font-semibold); }\n .mj-alert__title[_ngcontent-%COMP%] + .mj-alert__message[_ngcontent-%COMP%] { margin-top: var(--mj-space-1); }\n .mj-alert__message[_ngcontent-%COMP%] { color: inherit; opacity: 0.9; }\n\n .mj-alert__actions[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: var(--mj-space-2);\n flex-shrink: 0;\n }\n .mj-alert__actions[_ngcontent-%COMP%]:empty { display: none; }\n\n .mj-alert__dismiss[_ngcontent-%COMP%] {\n flex-shrink: 0;\n border: none;\n background: transparent;\n color: inherit;\n opacity: 0.6;\n cursor: pointer;\n padding: 0;\n width: 20px; height: 20px;\n display: flex; align-items: center; justify-content: center;\n border-radius: var(--mj-radius-sm);\n transition: opacity var(--mj-transition-fast), background var(--mj-transition-fast);\n }\n .mj-alert__dismiss[_ngcontent-%COMP%]:hover { opacity: 1; background: color-mix(in srgb, currentColor 12%, transparent); }\n\n \n\n .mj-alert--sm[_nghost-%COMP%] {\n padding: var(--mj-space-2) var(--mj-space-3);\n font-size: var(--mj-text-xs);\n gap: var(--mj-space-2);\n }\n .mj-alert--sm[_nghost-%COMP%] .mj-alert__icon[_ngcontent-%COMP%] { font-size: 0.875rem; }\n\n \n\n\n\n\n\n\n\n .mj-alert--info[_nghost-%COMP%] {\n background: color-mix(in srgb, var(--mj-status-info) 12%, var(--mj-bg-surface));\n border-color: var(--mj-status-info-border);\n color: var(--mj-status-info-text);\n }\n .mj-alert--info[_nghost-%COMP%] .mj-alert__icon[_ngcontent-%COMP%] { color: var(--mj-status-info); }\n\n .mj-alert--success[_nghost-%COMP%] {\n background: color-mix(in srgb, var(--mj-status-success) 12%, var(--mj-bg-surface));\n border-color: var(--mj-status-success-border);\n color: var(--mj-status-success-text);\n }\n .mj-alert--success[_nghost-%COMP%] .mj-alert__icon[_ngcontent-%COMP%] { color: var(--mj-status-success); }\n\n .mj-alert--warning[_nghost-%COMP%] {\n background: color-mix(in srgb, var(--mj-status-warning) 12%, var(--mj-bg-surface));\n border-color: var(--mj-status-warning-border);\n color: var(--mj-status-warning-text);\n }\n .mj-alert--warning[_nghost-%COMP%] .mj-alert__icon[_ngcontent-%COMP%] { color: var(--mj-status-warning); }\n\n .mj-alert--error[_nghost-%COMP%] {\n background: color-mix(in srgb, var(--mj-status-error) 12%, var(--mj-bg-surface));\n border-color: var(--mj-status-error-border);\n color: var(--mj-status-error-text);\n }\n .mj-alert--error[_nghost-%COMP%] .mj-alert__icon[_ngcontent-%COMP%] { color: var(--mj-status-error); }"] });
168
+ }
169
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MJAlertComponent, [{
170
+ type: Component,
171
+ args: [{ selector: 'mj-alert', standalone: true, template: `
172
+ @if (!isDismissed) {
173
+ @if (ResolvedIcon) {
174
+ <i class="mj-alert__icon {{ ResolvedIcon }}" aria-hidden="true"></i>
175
+ }
176
+ <div class="mj-alert__content">
177
+ @if (Title) {
178
+ <div class="mj-alert__title">{{ Title }}</div>
179
+ }
180
+ @if (Message) {
181
+ <div class="mj-alert__message">{{ Message }}</div>
182
+ }
183
+ <ng-content></ng-content>
184
+ </div>
185
+ <div class="mj-alert__actions">
186
+ <ng-content select="[actions]"></ng-content>
187
+ </div>
188
+ @if (Dismissible) {
189
+ <button type="button" class="mj-alert__dismiss" aria-label="Dismiss" (click)="Dismiss()">
190
+ <i class="fa-solid fa-xmark" aria-hidden="true"></i>
191
+ </button>
192
+ }
193
+ }
194
+ `, styles: ["\n :host {\n display: flex;\n align-items: flex-start;\n gap: var(--mj-space-3);\n padding: var(--mj-space-3) var(--mj-space-4);\n margin-bottom: var(--mj-space-4);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n font-size: var(--mj-text-sm);\n line-height: 1.45;\n }\n /* Alerts are flow content almost always followed by other content, so they\n carry a default bottom margin (like a <p>; cf. Bootstrap's .alert). When an\n alert is the only/last child of its container, drop the trailing gap. */\n :host(:last-child) { margin-bottom: 0; }\n :host(.mj-alert--dismissed) { display: none; }\n\n .mj-alert__icon {\n flex-shrink: 0;\n font-size: 1rem;\n margin-top: 0.1em;\n }\n .mj-alert__content { flex: 1; min-width: 0; }\n .mj-alert__title { font-weight: var(--mj-font-semibold); }\n .mj-alert__title + .mj-alert__message { margin-top: var(--mj-space-1); }\n .mj-alert__message { color: inherit; opacity: 0.9; }\n\n .mj-alert__actions {\n display: flex;\n align-items: center;\n gap: var(--mj-space-2);\n flex-shrink: 0;\n }\n .mj-alert__actions:empty { display: none; }\n\n .mj-alert__dismiss {\n flex-shrink: 0;\n border: none;\n background: transparent;\n color: inherit;\n opacity: 0.6;\n cursor: pointer;\n padding: 0;\n width: 20px; height: 20px;\n display: flex; align-items: center; justify-content: center;\n border-radius: var(--mj-radius-sm);\n transition: opacity var(--mj-transition-fast), background var(--mj-transition-fast);\n }\n .mj-alert__dismiss:hover { opacity: 1; background: color-mix(in srgb, currentColor 12%, transparent); }\n\n /* \u2500\u2500 Density \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n :host(.mj-alert--sm) {\n padding: var(--mj-space-2) var(--mj-space-3);\n font-size: var(--mj-text-xs);\n gap: var(--mj-space-2);\n }\n :host(.mj-alert--sm) .mj-alert__icon { font-size: 0.875rem; }\n\n /* \u2500\u2500 Variants (token-driven, dark-mode-safe) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Background is an OPAQUE tint (status color mixed into the surface), NOT the\n translucent --mj-status-*-bg token. Translucent backgrounds let whatever\n sits behind the alert show through, so the same alert rendered on two\n different dialog backdrops looked different. An opaque tint is identical\n regardless of what's behind it, and still adapts to light/dark via the\n surface + status tokens. */\n :host(.mj-alert--info) {\n background: color-mix(in srgb, var(--mj-status-info) 12%, var(--mj-bg-surface));\n border-color: var(--mj-status-info-border);\n color: var(--mj-status-info-text);\n }\n :host(.mj-alert--info) .mj-alert__icon { color: var(--mj-status-info); }\n\n :host(.mj-alert--success) {\n background: color-mix(in srgb, var(--mj-status-success) 12%, var(--mj-bg-surface));\n border-color: var(--mj-status-success-border);\n color: var(--mj-status-success-text);\n }\n :host(.mj-alert--success) .mj-alert__icon { color: var(--mj-status-success); }\n\n :host(.mj-alert--warning) {\n background: color-mix(in srgb, var(--mj-status-warning) 12%, var(--mj-bg-surface));\n border-color: var(--mj-status-warning-border);\n color: var(--mj-status-warning-text);\n }\n :host(.mj-alert--warning) .mj-alert__icon { color: var(--mj-status-warning); }\n\n :host(.mj-alert--error) {\n background: color-mix(in srgb, var(--mj-status-error) 12%, var(--mj-bg-surface));\n border-color: var(--mj-status-error-border);\n color: var(--mj-status-error-text);\n }\n :host(.mj-alert--error) .mj-alert__icon { color: var(--mj-status-error); }\n "] }]
195
+ }], null, { Variant: [{
196
+ type: Input
197
+ }], Size: [{
198
+ type: Input
199
+ }], Title: [{
200
+ type: Input
201
+ }], Message: [{
202
+ type: Input
203
+ }], Icon: [{
204
+ type: Input
205
+ }], Dismissible: [{
206
+ type: Input,
207
+ args: [{ transform: booleanAttribute }]
208
+ }], Role: [{
209
+ type: Input
210
+ }], Dismissed: [{
211
+ type: Output
212
+ }], IsInfo: [{
213
+ type: HostBinding,
214
+ args: ['class.mj-alert--info']
215
+ }], IsSuccess: [{
216
+ type: HostBinding,
217
+ args: ['class.mj-alert--success']
218
+ }], IsWarning: [{
219
+ type: HostBinding,
220
+ args: ['class.mj-alert--warning']
221
+ }], IsError: [{
222
+ type: HostBinding,
223
+ args: ['class.mj-alert--error']
224
+ }], IsSmall: [{
225
+ type: HostBinding,
226
+ args: ['class.mj-alert--sm']
227
+ }], IsHidden: [{
228
+ type: HostBinding,
229
+ args: ['class.mj-alert--dismissed']
230
+ }], HostRole: [{
231
+ type: HostBinding,
232
+ args: ['attr.role']
233
+ }] }); })();
234
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MJAlertComponent, { className: "MJAlertComponent", filePath: "lib/alert/alert.component.ts", lineNumber: 180 }); })();
235
+ //# sourceMappingURL=alert.component.js.map