mn-angular-lib 0.0.17 → 0.0.23
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/mn-angular-lib.mjs +916 -11
- package/fesm2022/mn-angular-lib.mjs.map +1 -1
- package/index.d.ts +841 -3
- package/package.json +9 -1
- package/src/lib/styles/index.css +2 -0
- package/src/lib/styles/styles.css +12 -8
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { signal, effect, Injectable, InjectionToken, inject, computed, Optional, Inject, Input, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
-
import { BehaviorSubject } from 'rxjs';
|
|
2
|
+
import { signal, effect, Injectable, InjectionToken, inject, computed, Optional, Inject, Input, ChangeDetectionStrategy, Component, HostBinding, Self, APP_INITIALIZER, SkipSelf, Attribute, Directive } from '@angular/core';
|
|
3
|
+
import { BehaviorSubject, firstValueFrom } from 'rxjs';
|
|
4
4
|
import * as i1 from '@angular/common';
|
|
5
|
-
import { CommonModule } from '@angular/common';
|
|
5
|
+
import { CommonModule, NgClass, NgOptimizedImage } from '@angular/common';
|
|
6
|
+
import { tv } from 'tailwind-variants';
|
|
7
|
+
import * as i1$1 from '@angular/forms';
|
|
8
|
+
import { Validators } from '@angular/forms';
|
|
9
|
+
import * as i1$2 from '@angular/common/http';
|
|
6
10
|
|
|
7
11
|
/**
|
|
8
12
|
* MnThemeService is responsible for managing the theme configuration of the application.
|
|
@@ -179,7 +183,7 @@ const DEFAULT_MN_ALERT_CONFIG = {
|
|
|
179
183
|
default: 'alert'
|
|
180
184
|
},
|
|
181
185
|
icons: {},
|
|
182
|
-
|
|
186
|
+
fallbackDuration: 4000,
|
|
183
187
|
finalize: (a) => a
|
|
184
188
|
};
|
|
185
189
|
|
|
@@ -201,7 +205,7 @@ class MnAlertStore {
|
|
|
201
205
|
alerts$ = this._alerts$.asObservable();
|
|
202
206
|
show(partial) {
|
|
203
207
|
// Ensure every alert has a numeric duration: use provided or fall back to per-kind default
|
|
204
|
-
const computedDuration = partial.duration ?? DEFAULT_MN_ALERT_CONFIG.durations[partial.kind] ?? DEFAULT_MN_ALERT_CONFIG.
|
|
208
|
+
const computedDuration = partial.duration ?? DEFAULT_MN_ALERT_CONFIG.durations[partial.kind] ?? DEFAULT_MN_ALERT_CONFIG.fallbackDuration;
|
|
205
209
|
const a = { id: uid(), ...partial, duration: computedDuration };
|
|
206
210
|
this._alerts$.next([...this._alerts$.value, a]);
|
|
207
211
|
if (typeof a.duration === 'number' && a.duration > 0) {
|
|
@@ -250,7 +254,7 @@ class MnAlertService {
|
|
|
250
254
|
let duration = input.duration;
|
|
251
255
|
if (duration == null) {
|
|
252
256
|
// Prefer user defaultDuration if provided and not null, otherwise use library per-kind default
|
|
253
|
-
const userDefault = this.cfg.
|
|
257
|
+
const userDefault = this.cfg.fallbackDuration;
|
|
254
258
|
if (typeof userDefault === 'number') {
|
|
255
259
|
duration = userDefault;
|
|
256
260
|
}
|
|
@@ -293,8 +297,8 @@ class MnAlertService {
|
|
|
293
297
|
}
|
|
294
298
|
else {
|
|
295
299
|
// userDur is undefined or null -> fallback to user defaultDuration if numeric
|
|
296
|
-
if (typeof this.cfg.
|
|
297
|
-
duration = this.cfg.
|
|
300
|
+
if (typeof this.cfg.fallbackDuration === 'number') {
|
|
301
|
+
duration = this.cfg.fallbackDuration;
|
|
298
302
|
}
|
|
299
303
|
else {
|
|
300
304
|
duration = this.cfg.durations[kind];
|
|
@@ -340,11 +344,11 @@ class MnAlertOutletComponent {
|
|
|
340
344
|
};
|
|
341
345
|
}
|
|
342
346
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: MnAlertOutletComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
343
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.9", type: MnAlertOutletComponent, isStandalone: true, selector: "mn-alert-outlet", inputs: { template: "template" }, ngImport: i0, template: "@if (alerts$ | async; as alerts) {\n @for (a of alerts; track trackById) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"contextFor(a)\">\n </ng-container>\n }\n}\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
347
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.9", type: MnAlertOutletComponent, isStandalone: true, selector: "mn-alert-outlet", inputs: { template: "template" }, ngImport: i0, template: "@if (alerts$ | async; as alerts) {\n @for (a of alerts; track trackById($index, a)) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"contextFor(a)\">\n </ng-container>\n }\n}\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
344
348
|
}
|
|
345
349
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: MnAlertOutletComponent, decorators: [{
|
|
346
350
|
type: Component,
|
|
347
|
-
args: [{ selector: 'mn-alert-outlet', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (alerts$ | async; as alerts) {\n @for (a of alerts; track trackById) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"contextFor(a)\">\n </ng-container>\n }\n}\n" }]
|
|
351
|
+
args: [{ selector: 'mn-alert-outlet', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (alerts$ | async; as alerts) {\n @for (a of alerts; track trackById($index, a)) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"contextFor(a)\">\n </ng-container>\n }\n}\n" }]
|
|
348
352
|
}], ctorParameters: () => [], propDecorators: { template: [{
|
|
349
353
|
type: Input,
|
|
350
354
|
args: [{ required: true }]
|
|
@@ -359,6 +363,907 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImpor
|
|
|
359
363
|
args: [{ selector: 'lib-test', standalone: true, imports: [], template: "<p>test works!</p>\n", styles: ["p{color:var(--mn-primary)}\n"] }]
|
|
360
364
|
}] });
|
|
361
365
|
|
|
366
|
+
const mnButtonVariants = tv({
|
|
367
|
+
base: 'hover:cursor-pointer',
|
|
368
|
+
variants: {
|
|
369
|
+
size: {
|
|
370
|
+
sm: 'px-2 py-1 text-sm',
|
|
371
|
+
md: 'px-3 py-1.5 text-base',
|
|
372
|
+
lg: 'px-4 py-2 text-lg',
|
|
373
|
+
},
|
|
374
|
+
variant: {
|
|
375
|
+
fill: '',
|
|
376
|
+
outline: 'bg-transparent border',
|
|
377
|
+
text: 'bg-transparent',
|
|
378
|
+
},
|
|
379
|
+
// Intentionally empty; resolved via compoundVariants
|
|
380
|
+
color: {
|
|
381
|
+
primary: '',
|
|
382
|
+
secondary: '',
|
|
383
|
+
danger: '',
|
|
384
|
+
warning: '',
|
|
385
|
+
success: '',
|
|
386
|
+
},
|
|
387
|
+
borderRadius: {
|
|
388
|
+
none: 'rounded-none',
|
|
389
|
+
xs: 'rounded-xs',
|
|
390
|
+
sm: 'rounded-sm',
|
|
391
|
+
md: 'rounded-md',
|
|
392
|
+
lg: 'rounded-lg',
|
|
393
|
+
xl: 'rounded-xl',
|
|
394
|
+
two_xl: 'rounded-2xl',
|
|
395
|
+
three_xl: 'rounded-3xl',
|
|
396
|
+
four_xl: 'rounded-4xl',
|
|
397
|
+
},
|
|
398
|
+
disabled: {
|
|
399
|
+
true: 'opacity-50 pointer-events-none',
|
|
400
|
+
}
|
|
401
|
+
},
|
|
402
|
+
compoundVariants: [
|
|
403
|
+
// Fill
|
|
404
|
+
{ variant: 'fill', color: 'primary', class: 'bg-blue-600 text-white hover:bg-blue-700' },
|
|
405
|
+
{ variant: 'fill', color: 'secondary', class: 'bg-gray-600 text-white hover:bg-gray-700' },
|
|
406
|
+
{ variant: 'fill', color: 'danger', class: 'bg-red-600 text-white hover:bg-red-700' },
|
|
407
|
+
{ variant: 'fill', color: 'warning', class: 'bg-amber-500 text-black hover:bg-amber-600' },
|
|
408
|
+
{ variant: 'fill', color: 'success', class: 'bg-green-600 text-white hover:bg-green-700' },
|
|
409
|
+
// Outline
|
|
410
|
+
{ variant: 'outline', color: 'primary', class: 'border-blue-600 text-blue-600 hover:bg-blue-100' },
|
|
411
|
+
{ variant: 'outline', color: 'secondary', class: 'border-gray-600 text-gray-700 hover:bg-gray-100' },
|
|
412
|
+
{ variant: 'outline', color: 'danger', class: 'border-red-600 text-red-600 hover:bg-red-100' },
|
|
413
|
+
{ variant: 'outline', color: 'warning', class: 'border-amber-500 text-amber-600 hover:bg-amber-100' },
|
|
414
|
+
{ variant: 'outline', color: 'success', class: 'border-green-600 text-green-600 hover:bg-green-100' },
|
|
415
|
+
// Text
|
|
416
|
+
{ variant: 'text', color: 'primary', class: 'text-blue-600 hover:bg-blue-100' },
|
|
417
|
+
{ variant: 'text', color: 'secondary', class: 'text-gray-700 hover:bg-gray-100' },
|
|
418
|
+
{ variant: 'text', color: 'danger', class: 'text-red-600 hover:bg-red-100' },
|
|
419
|
+
{ variant: 'text', color: 'warning', class: 'text-amber-600 hover:bg-amber-100' },
|
|
420
|
+
{ variant: 'text', color: 'success', class: 'text-green-600 hover:bg-green-100' },
|
|
421
|
+
],
|
|
422
|
+
defaultVariants: {
|
|
423
|
+
size: 'md',
|
|
424
|
+
variant: 'fill',
|
|
425
|
+
color: 'primary',
|
|
426
|
+
borderRadius: 'xl',
|
|
427
|
+
disabled: false,
|
|
428
|
+
},
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
class MnButton {
|
|
432
|
+
data = {};
|
|
433
|
+
// Bind the computed classes to the host element
|
|
434
|
+
get hostClasses() {
|
|
435
|
+
return mnButtonVariants({
|
|
436
|
+
size: this.data.size,
|
|
437
|
+
variant: this.data.variant,
|
|
438
|
+
color: this.data.color,
|
|
439
|
+
borderRadius: this.data.borderRadius,
|
|
440
|
+
disabled: this.data.disabled,
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
// For accessibility (works for both <button> and <a>)
|
|
444
|
+
get ariaDisabled() {
|
|
445
|
+
return this.data.disabled ? 'true' : null;
|
|
446
|
+
}
|
|
447
|
+
// Only meaningful for <button>. For <a> it does nothing semantically.
|
|
448
|
+
get disabledAttr() {
|
|
449
|
+
return this.data.disabled ? '' : null;
|
|
450
|
+
}
|
|
451
|
+
// Make disabled anchors unfocusable + prevent activation
|
|
452
|
+
get tabIndex() {
|
|
453
|
+
return this.data.disabled ? '-1' : null;
|
|
454
|
+
}
|
|
455
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: MnButton, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
456
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.9", type: MnButton, isStandalone: true, selector: "button[mnButton], a[mnButton]", inputs: { data: "data" }, host: { properties: { "class": "this.hostClasses", "attr.aria-disabled": "this.ariaDisabled", "attr.disabled": "this.disabledAttr", "attr.tabindex": "this.tabIndex" } }, ngImport: i0, template: "<ng-content></ng-content>\n" });
|
|
457
|
+
}
|
|
458
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: MnButton, decorators: [{
|
|
459
|
+
type: Component,
|
|
460
|
+
args: [{ selector: 'button[mnButton], a[mnButton]', standalone: true, template: "<ng-content></ng-content>\n" }]
|
|
461
|
+
}], propDecorators: { data: [{
|
|
462
|
+
type: Input
|
|
463
|
+
}], hostClasses: [{
|
|
464
|
+
type: HostBinding,
|
|
465
|
+
args: ['class']
|
|
466
|
+
}], ariaDisabled: [{
|
|
467
|
+
type: HostBinding,
|
|
468
|
+
args: ['attr.aria-disabled']
|
|
469
|
+
}], disabledAttr: [{
|
|
470
|
+
type: HostBinding,
|
|
471
|
+
args: ['attr.disabled']
|
|
472
|
+
}], tabIndex: [{
|
|
473
|
+
type: HostBinding,
|
|
474
|
+
args: ['attr.tabindex']
|
|
475
|
+
}] } });
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* MnInputField Adapters
|
|
479
|
+
*
|
|
480
|
+
* This module implements the Adapter Pattern to handle type-specific behavior
|
|
481
|
+
* for different HTML input types in the MnInputField component.
|
|
482
|
+
*
|
|
483
|
+
* The adapter pattern allows the component to support multiple input types
|
|
484
|
+
* (text, number, date, time, etc.) without coupling the component logic to
|
|
485
|
+
* type-specific implementations. Each adapter handles:
|
|
486
|
+
* - Parsing: converting raw string input to the appropriate data type
|
|
487
|
+
* - Formatting: converting typed values back to string for display
|
|
488
|
+
* - Attributes: providing type-specific DOM attributes (min, max, step, inputmode)
|
|
489
|
+
* - Validation: implementing type-specific validation rules
|
|
490
|
+
*
|
|
491
|
+
* This approach keeps the component code clean and makes it easy to add
|
|
492
|
+
* support for new input types by creating new adapters.
|
|
493
|
+
*/
|
|
494
|
+
/**
|
|
495
|
+
* Utility function to convert empty strings to null.
|
|
496
|
+
* This is a common pattern for optional form fields where empty input
|
|
497
|
+
* should be treated as "no value" rather than an empty string.
|
|
498
|
+
*
|
|
499
|
+
* @param raw - Raw input string
|
|
500
|
+
* @returns The input string if non-empty, null if empty
|
|
501
|
+
*
|
|
502
|
+
* @example
|
|
503
|
+
* emptyToNull('hello') // => 'hello'
|
|
504
|
+
* emptyToNull('') // => null
|
|
505
|
+
*/
|
|
506
|
+
const emptyToNull = (raw) => (raw === '' ? null : raw);
|
|
507
|
+
/**
|
|
508
|
+
* Default adapter for text-based input types.
|
|
509
|
+
* Used for: text, email, password, search, tel, url
|
|
510
|
+
*
|
|
511
|
+
* Behavior:
|
|
512
|
+
* - Empty strings are converted to null
|
|
513
|
+
* - Values are stored as strings in the FormControl
|
|
514
|
+
* - No special DOM attributes
|
|
515
|
+
* - No additional validation (relies on Angular's built-in validators)
|
|
516
|
+
*/
|
|
517
|
+
const defaultTextAdapter = {
|
|
518
|
+
parse: (raw) => emptyToNull(raw),
|
|
519
|
+
format: (val) => (val == null ? '' : String(val)),
|
|
520
|
+
attrs: () => ({}),
|
|
521
|
+
validate: () => null,
|
|
522
|
+
};
|
|
523
|
+
/**
|
|
524
|
+
* Adapter for date and time input types.
|
|
525
|
+
* Used for: date, time, datetime-local
|
|
526
|
+
*
|
|
527
|
+
* Behavior:
|
|
528
|
+
* - Empty strings are converted to null
|
|
529
|
+
* - Values are stored as ISO 8601 strings in the FormControl
|
|
530
|
+
* - Provides min/max attributes from startDate/endDate props
|
|
531
|
+
* - Validates date/time ranges using string comparison
|
|
532
|
+
*
|
|
533
|
+
* Note: String comparison works for ISO 8601 dates/times because they are
|
|
534
|
+
* lexicographically ordered (e.g., '2024-01-15' < '2024-12-31').
|
|
535
|
+
*/
|
|
536
|
+
const dateTimeAdapter = {
|
|
537
|
+
parse: (raw) => emptyToNull(raw),
|
|
538
|
+
format: (val) => (val == null ? '' : String(val)),
|
|
539
|
+
attrs: (props) => ({
|
|
540
|
+
min: props.startDate ?? null,
|
|
541
|
+
max: props.endDate ?? null,
|
|
542
|
+
}),
|
|
543
|
+
validate: (props, _control, currentRaw) => {
|
|
544
|
+
const value = currentRaw;
|
|
545
|
+
if (!value)
|
|
546
|
+
return null; // Don't validate empty values (use 'required' validator for that)
|
|
547
|
+
const min = props.startDate;
|
|
548
|
+
const max = props.endDate;
|
|
549
|
+
// Validate minimum date/time constraint
|
|
550
|
+
if (min && value < min) {
|
|
551
|
+
return { mnMin: { min, actual: value } };
|
|
552
|
+
}
|
|
553
|
+
// Validate maximum date/time constraint
|
|
554
|
+
if (max && value > max) {
|
|
555
|
+
return { mnMax: { max, actual: value } };
|
|
556
|
+
}
|
|
557
|
+
return null;
|
|
558
|
+
},
|
|
559
|
+
};
|
|
560
|
+
/**
|
|
561
|
+
* Adapter for number input type.
|
|
562
|
+
*
|
|
563
|
+
* Behavior:
|
|
564
|
+
* - Empty strings are converted to null
|
|
565
|
+
* - Valid numbers are parsed to number type
|
|
566
|
+
* - Invalid numbers (NaN, Infinity) are converted to null
|
|
567
|
+
* - Values are stored as numbers (or null) in the FormControl
|
|
568
|
+
* - Sets inputmode='decimal' for optimized mobile keyboards
|
|
569
|
+
* - No additional validation (relies on Angular's built-in validators)
|
|
570
|
+
*
|
|
571
|
+
* Note: The browser's native number input validation handles
|
|
572
|
+
* basic number format validation automatically.
|
|
573
|
+
*/
|
|
574
|
+
const numberAdapter = {
|
|
575
|
+
parse: (raw) => {
|
|
576
|
+
if (raw === '')
|
|
577
|
+
return null;
|
|
578
|
+
const num = Number(raw);
|
|
579
|
+
return Number.isFinite(num) ? num : null;
|
|
580
|
+
},
|
|
581
|
+
format: (val) => (val == null ? '' : String(val)),
|
|
582
|
+
attrs: () => ({
|
|
583
|
+
inputmode: 'decimal',
|
|
584
|
+
}),
|
|
585
|
+
validate: () => null,
|
|
586
|
+
};
|
|
587
|
+
/**
|
|
588
|
+
* Selects the appropriate adapter based on the input type.
|
|
589
|
+
* This is the main factory function used by the MnInputField component
|
|
590
|
+
* to determine which adapter to use for a given input type.
|
|
591
|
+
*
|
|
592
|
+
* @param type - The input type (e.g., 'text', 'email', 'date', 'number')
|
|
593
|
+
* @returns The appropriate adapter instance
|
|
594
|
+
*
|
|
595
|
+
* @example
|
|
596
|
+
* pickAdapter('text') // => defaultTextAdapter
|
|
597
|
+
* pickAdapter('email') // => defaultTextAdapter
|
|
598
|
+
* pickAdapter('date') // => dateTimeAdapter
|
|
599
|
+
* pickAdapter('number') // => numberAdapter
|
|
600
|
+
*/
|
|
601
|
+
function pickAdapter(type) {
|
|
602
|
+
// Date/time inputs use the dateTimeAdapter for range validation
|
|
603
|
+
if (type === 'date' || type === 'time' || type === 'datetime-local') {
|
|
604
|
+
return dateTimeAdapter;
|
|
605
|
+
}
|
|
606
|
+
// Number inputs use the numberAdapter for type conversion
|
|
607
|
+
if (type === 'number') {
|
|
608
|
+
return numberAdapter;
|
|
609
|
+
}
|
|
610
|
+
// All other input types use the default text adapter
|
|
611
|
+
return defaultTextAdapter;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const mnInputFieldVariants = tv({
|
|
615
|
+
base: 'bg-white border-1 border-gray-500 placeholder-gray-500 text-sm',
|
|
616
|
+
variants: {
|
|
617
|
+
shadow: {
|
|
618
|
+
true: 'shadow-lg',
|
|
619
|
+
},
|
|
620
|
+
size: {
|
|
621
|
+
sm: 'p-2',
|
|
622
|
+
md: 'p-3',
|
|
623
|
+
lg: 'p-4',
|
|
624
|
+
},
|
|
625
|
+
borderRadius: {
|
|
626
|
+
none: 'rounded-none',
|
|
627
|
+
xs: 'rounded-xs',
|
|
628
|
+
sm: 'rounded-sm',
|
|
629
|
+
md: 'rounded-md',
|
|
630
|
+
lg: 'rounded-lg',
|
|
631
|
+
xl: 'rounded-xl',
|
|
632
|
+
two_xl: 'rounded-2xl',
|
|
633
|
+
three_xl: 'rounded-3xl',
|
|
634
|
+
four_xl: 'rounded-4xl',
|
|
635
|
+
},
|
|
636
|
+
fullWidth: {
|
|
637
|
+
true: 'w-full',
|
|
638
|
+
}
|
|
639
|
+
},
|
|
640
|
+
defaultVariants: {
|
|
641
|
+
size: 'md',
|
|
642
|
+
borderRadius: 'md',
|
|
643
|
+
}
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
class MnErrorMessage {
|
|
647
|
+
errorMessage;
|
|
648
|
+
id;
|
|
649
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: MnErrorMessage, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
650
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.9", type: MnErrorMessage, isStandalone: true, selector: "lib-mn-error-message", inputs: { errorMessage: "errorMessage", id: "id" }, ngImport: i0, template: "<div [id]=\"id + '-error'\" class=\"text-red-500 mt-2 text-sm\">\n {{ errorMessage }}\n</div>\n" });
|
|
651
|
+
}
|
|
652
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: MnErrorMessage, decorators: [{
|
|
653
|
+
type: Component,
|
|
654
|
+
args: [{ selector: 'lib-mn-error-message', imports: [], template: "<div [id]=\"id + '-error'\" class=\"text-red-500 mt-2 text-sm\">\n {{ errorMessage }}\n</div>\n" }]
|
|
655
|
+
}], propDecorators: { errorMessage: [{
|
|
656
|
+
type: Input,
|
|
657
|
+
args: [{ required: true }]
|
|
658
|
+
}], id: [{
|
|
659
|
+
type: Input,
|
|
660
|
+
args: [{ required: true }]
|
|
661
|
+
}] } });
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* MnInputField Component
|
|
665
|
+
*
|
|
666
|
+
* A flexible, accessible input field component that implements Angular's ControlValueAccessor
|
|
667
|
+
* and Validator interfaces. Supports multiple input types, custom validation messages,
|
|
668
|
+
* and configurable error display (single or multiple errors).
|
|
669
|
+
*
|
|
670
|
+
* Features:
|
|
671
|
+
* - Works with Angular Reactive Forms (FormControl, FormGroup)
|
|
672
|
+
* - Supports standard and date/time input types
|
|
673
|
+
* - Built-in error messages with internationalization support
|
|
674
|
+
* - Custom error messages per field
|
|
675
|
+
* - Priority-based error display or show all errors
|
|
676
|
+
* - Full accessibility (ARIA attributes)
|
|
677
|
+
* - Type-safe adapter pattern for different input types
|
|
678
|
+
*
|
|
679
|
+
* @example
|
|
680
|
+
* ```typescript
|
|
681
|
+
* <mn-input-field
|
|
682
|
+
* formControlName="email"
|
|
683
|
+
* [props]="{
|
|
684
|
+
* id: 'email',
|
|
685
|
+
* type: 'email',
|
|
686
|
+
* label: 'Email Address',
|
|
687
|
+
* size: 'md',
|
|
688
|
+
* borderRadius: 'md',
|
|
689
|
+
* errorMessages: { required: 'Email is required' }
|
|
690
|
+
* }"
|
|
691
|
+
* ></mn-input-field>
|
|
692
|
+
* ```
|
|
693
|
+
*/
|
|
694
|
+
class MnInputField {
|
|
695
|
+
ngControl;
|
|
696
|
+
/** Configuration properties for the input field */
|
|
697
|
+
props;
|
|
698
|
+
/** Current raw string value of the input element */
|
|
699
|
+
value = null;
|
|
700
|
+
/** Whether the input is disabled */
|
|
701
|
+
isDisabled = false;
|
|
702
|
+
/** Callback function to notify Angular forms of value changes */
|
|
703
|
+
onChange = () => { };
|
|
704
|
+
/** Callback function to notify Angular forms when input is touched/blurred */
|
|
705
|
+
onTouched = () => { };
|
|
706
|
+
/**
|
|
707
|
+
* Built-in default error messages in English.
|
|
708
|
+
* These are used when useBuiltInErrorMessages is true (default).
|
|
709
|
+
* Can be overridden per-field using props.errorMessages.
|
|
710
|
+
*/
|
|
711
|
+
builtInErrorMessages = {
|
|
712
|
+
required: 'This field is required',
|
|
713
|
+
email: 'Please enter a valid email address',
|
|
714
|
+
minlength: (args) => `Minimum ${args.requiredLength} characters required`,
|
|
715
|
+
maxlength: (args) => `Maximum ${args.requiredLength} characters allowed`,
|
|
716
|
+
mnMin: (args) => `Date/time must be from ${args.min} onwards`,
|
|
717
|
+
mnMax: (args) => `Date/time must be up to ${args.max}`,
|
|
718
|
+
};
|
|
719
|
+
/**
|
|
720
|
+
* Constructor - Registers this component as the ControlValueAccessor
|
|
721
|
+
* for the injected NgControl (FormControl).
|
|
722
|
+
*
|
|
723
|
+
* @param ngControl - Angular's NgControl (injected via Dependency Injection)
|
|
724
|
+
*/
|
|
725
|
+
constructor(ngControl) {
|
|
726
|
+
this.ngControl = ngControl;
|
|
727
|
+
if (this.ngControl)
|
|
728
|
+
this.ngControl.valueAccessor = this;
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* Gets the appropriate adapter based on the input type.
|
|
732
|
+
* Adapters handle type-specific formatting, parsing, and validation.
|
|
733
|
+
*/
|
|
734
|
+
get adapter() {
|
|
735
|
+
return pickAdapter(this.props.type);
|
|
736
|
+
}
|
|
737
|
+
// ========== ControlValueAccessor Implementation ==========
|
|
738
|
+
/**
|
|
739
|
+
* Writes a new value to the input element (called by Angular Forms).
|
|
740
|
+
* Formats the value using the type-specific adapter.
|
|
741
|
+
*
|
|
742
|
+
* @param val - The value to write (type depends on input type)
|
|
743
|
+
*/
|
|
744
|
+
writeValue(val) {
|
|
745
|
+
this.value = this.adapter.format(val);
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Registers a callback function to be called when the input value changes.
|
|
749
|
+
*
|
|
750
|
+
* @param fn - Callback function to notify Angular Forms of changes
|
|
751
|
+
*/
|
|
752
|
+
registerOnChange(fn) {
|
|
753
|
+
this.onChange = fn;
|
|
754
|
+
}
|
|
755
|
+
/**
|
|
756
|
+
* Registers a callback function to be called when the input is touched/blurred.
|
|
757
|
+
*
|
|
758
|
+
* @param fn - Callback function to notify Angular Forms of touch events
|
|
759
|
+
*/
|
|
760
|
+
registerOnTouched(fn) {
|
|
761
|
+
this.onTouched = fn;
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Sets the disabled state of the input element.
|
|
765
|
+
*
|
|
766
|
+
* @param isDisabled - Whether the input should be disabled
|
|
767
|
+
*/
|
|
768
|
+
setDisabledState(isDisabled) {
|
|
769
|
+
this.isDisabled = isDisabled;
|
|
770
|
+
}
|
|
771
|
+
// ========== Event Handlers ==========
|
|
772
|
+
/**
|
|
773
|
+
* Handles input events from the input element.
|
|
774
|
+
* Parses the raw string value and notifies Angular Forms.
|
|
775
|
+
*
|
|
776
|
+
* @param raw - Raw string value from the input element
|
|
777
|
+
*/
|
|
778
|
+
handleInput(raw) {
|
|
779
|
+
this.value = raw;
|
|
780
|
+
this.onChange(this.adapter.parse(raw));
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* Handles blur events from the input element.
|
|
784
|
+
* Notifies Angular Forms that the input has been touched.
|
|
785
|
+
*/
|
|
786
|
+
handleBlur() {
|
|
787
|
+
this.onTouched();
|
|
788
|
+
}
|
|
789
|
+
// ========== Validator Implementation ==========
|
|
790
|
+
/**
|
|
791
|
+
* Validates the control using the type-specific adapter.
|
|
792
|
+
* Called by Angular Forms during validation.
|
|
793
|
+
*
|
|
794
|
+
* @param control - The AbstractControl to validate
|
|
795
|
+
* @returns ValidationErrors if invalid, null if valid
|
|
796
|
+
*/
|
|
797
|
+
validate(control) {
|
|
798
|
+
return this.adapter.validate(this.props, control, this.value);
|
|
799
|
+
}
|
|
800
|
+
// ========== Template Attribute Getters ==========
|
|
801
|
+
/**
|
|
802
|
+
* Gets all DOM attributes from the adapter.
|
|
803
|
+
* These are input-type-specific attributes (min, max, step, inputmode).
|
|
804
|
+
*/
|
|
805
|
+
get domAttrs() {
|
|
806
|
+
return this.adapter.attrs(this.props);
|
|
807
|
+
}
|
|
808
|
+
/** Min attribute for date/time/number inputs */
|
|
809
|
+
get minAttr() {
|
|
810
|
+
return this.domAttrs.min ?? null;
|
|
811
|
+
}
|
|
812
|
+
/** Max attribute for date/time/number inputs */
|
|
813
|
+
get maxAttr() {
|
|
814
|
+
return this.domAttrs.max ?? null;
|
|
815
|
+
}
|
|
816
|
+
/** Step attribute for number/date/time inputs */
|
|
817
|
+
get stepAttr() {
|
|
818
|
+
return this.domAttrs.step ?? null;
|
|
819
|
+
}
|
|
820
|
+
/** Inputmode attribute for mobile keyboard optimization */
|
|
821
|
+
get inputmodeAttr() {
|
|
822
|
+
return this.domAttrs.inputmode ?? null;
|
|
823
|
+
}
|
|
824
|
+
// ========== Error Handling ==========
|
|
825
|
+
/**
|
|
826
|
+
* Gets the FormControl instance from Angular Forms.
|
|
827
|
+
* Returns null if no control is attached.
|
|
828
|
+
*/
|
|
829
|
+
get control() {
|
|
830
|
+
return this.ngControl?.control ?? null;
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* Determines whether to show error messages.
|
|
834
|
+
* Errors are shown when the control is invalid and has been touched or modified.
|
|
835
|
+
*/
|
|
836
|
+
get showError() {
|
|
837
|
+
const c = this.control;
|
|
838
|
+
return !!c && c.invalid && (c.touched || c.dirty);
|
|
839
|
+
}
|
|
840
|
+
/**
|
|
841
|
+
* Picks the error key to display based on errorPriority.
|
|
842
|
+
* Used when showAllErrors is false (default).
|
|
843
|
+
*
|
|
844
|
+
* @param errors - ValidationErrors object from the control
|
|
845
|
+
* @returns The error key to display
|
|
846
|
+
*/
|
|
847
|
+
pickErrorKey(errors) {
|
|
848
|
+
// If priority is specified, use the first matching error from the priority list
|
|
849
|
+
if (this.props.errorPriority) {
|
|
850
|
+
for (const key of this.props.errorPriority) {
|
|
851
|
+
if (errors[key] !== undefined) {
|
|
852
|
+
return key;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
// Otherwise, use the first error key
|
|
857
|
+
return Object.keys(errors)[0];
|
|
858
|
+
}
|
|
859
|
+
isRequired() {
|
|
860
|
+
if (!this.control)
|
|
861
|
+
return false;
|
|
862
|
+
return this.control.hasValidator(Validators.required);
|
|
863
|
+
}
|
|
864
|
+
/**
|
|
865
|
+
* Resolves a single error message for a specific error key.
|
|
866
|
+
* Checks custom messages, built-in messages, and fallback in order.
|
|
867
|
+
*
|
|
868
|
+
* @param errorKey - The error key (e.g., 'required', 'email')
|
|
869
|
+
* @param errors - All validation errors on the control
|
|
870
|
+
* @returns The resolved error message string
|
|
871
|
+
*/
|
|
872
|
+
resolveErrorMessageForKey(errorKey, errors) {
|
|
873
|
+
const errorArgs = errors[errorKey];
|
|
874
|
+
// Priority: custom > built-in > fallback > default
|
|
875
|
+
const customMsg = this.props.errorMessages?.[errorKey];
|
|
876
|
+
const useBuiltIn = this.props.useBuiltInErrorMessages !== false;
|
|
877
|
+
const builtInMsg = useBuiltIn ? this.builtInErrorMessages[errorKey] : undefined;
|
|
878
|
+
const fallbackMsg = this.props.defaultErrorMessage;
|
|
879
|
+
const msgDef = customMsg ?? builtInMsg ?? fallbackMsg ?? 'Invalid input';
|
|
880
|
+
// If the message is a function, call it with error arguments
|
|
881
|
+
if (typeof msgDef === 'function') {
|
|
882
|
+
return msgDef(errorArgs, errors);
|
|
883
|
+
}
|
|
884
|
+
return msgDef;
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
887
|
+
* Gets all error messages for the current control state.
|
|
888
|
+
* Returns an array of error messages (used when showAllErrors is true).
|
|
889
|
+
*
|
|
890
|
+
* @returns Array of error message strings
|
|
891
|
+
*/
|
|
892
|
+
get errorMessages() {
|
|
893
|
+
const errors = this.control?.errors;
|
|
894
|
+
if (!errors)
|
|
895
|
+
return [];
|
|
896
|
+
const errorKeys = Object.keys(errors);
|
|
897
|
+
return errorKeys.map(key => this.resolveErrorMessageForKey(key, errors));
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* Gets a single error message for the current control state.
|
|
901
|
+
* Uses errorPriority to determine which error to show (when showAllErrors is false).
|
|
902
|
+
*
|
|
903
|
+
* @returns Single error message string, or null if no errors
|
|
904
|
+
*/
|
|
905
|
+
get errorMessage() {
|
|
906
|
+
const errors = this.control?.errors;
|
|
907
|
+
if (!errors)
|
|
908
|
+
return null;
|
|
909
|
+
const errorKey = this.pickErrorKey(errors);
|
|
910
|
+
return this.resolveErrorMessageForKey(errorKey, errors);
|
|
911
|
+
}
|
|
912
|
+
// ========== Resolved Properties ==========
|
|
913
|
+
/** Resolved ID for the input element */
|
|
914
|
+
get resolvedId() {
|
|
915
|
+
return this.props.id;
|
|
916
|
+
}
|
|
917
|
+
/** Resolved name attribute for the input element */
|
|
918
|
+
get resolvedName() {
|
|
919
|
+
return this.props?.name ?? null;
|
|
920
|
+
}
|
|
921
|
+
/**
|
|
922
|
+
* Computes the CSS classes from tailwind-variants based on the props.
|
|
923
|
+
* Returns the variant classes for styling the input element.
|
|
924
|
+
*/
|
|
925
|
+
get inputClasses() {
|
|
926
|
+
return mnInputFieldVariants({
|
|
927
|
+
size: this.props.size,
|
|
928
|
+
borderRadius: this.props.borderRadius,
|
|
929
|
+
shadow: this.props.shadow,
|
|
930
|
+
fullWidth: this.props.fullWidth,
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: MnInputField, deps: [{ token: i1$1.NgControl, optional: true, self: true }], target: i0.ɵɵFactoryTarget.Component });
|
|
934
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.9", type: MnInputField, isStandalone: true, selector: "mn-input-field", inputs: { props: "props" }, ngImport: i0, template: "<div class=\"flex flex-col\" [class.is-fullwidth]=\"props.fullWidth\">\n <!-- Label -->\n @if (props.label) {\n <label class=\"pl-2 pb-1 flex flex-row gap-x-0.5! text-base!\" [attr.for]=\"resolvedId\">\n <p>{{ props.label }}</p>\n @if (isRequired()) {\n <span class=\"text-red-500 \">*</span>\n }\n </label>\n }\n\n <!-- Input Element -->\n <input\n [id]=\"resolvedId\"\n [attr.name]=\"resolvedName\"\n [type]=\"props.type\"\n [attr.placeholder]=\"props.placeholder || null\"\n [attr.aria-label]=\"props.ariaLabel || props.label || null\"\n [attr.aria-invalid]=\"showError || null\"\n [attr.aria-describedby]=\"showError ? resolvedId + '-error' : null\"\n [disabled]=\"isDisabled\"\n [attr.min]=\"minAttr\"\n [attr.max]=\"maxAttr\"\n [value]=\"value ?? ''\"\n [ngClass]=\"inputClasses\"\n (input)=\"handleInput(($any($event.target)).value)\"\n (blur)=\"handleBlur()\"\n />\n\n <!-- Error Messages -->\n @if (showError) {\n <!-- Show all errors mode -->\n @if (props.showAllErrors) {\n <div class=\"flex flex-col gap-y-1\">\n @for (error of errorMessages; track $index) {\n <lib-mn-error-message [errorMessage]=\"error\" [id]=\"resolvedId + '-' + $index\"></lib-mn-error-message>\n }\n </div>\n } @else {\n @if (errorMessage != null) {\n <lib-mn-error-message [errorMessage]=\"errorMessage\" [id]=\"resolvedId\"></lib-mn-error-message>\n\n }\n }\n }\n</div>\n", dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: MnErrorMessage, selector: "lib-mn-error-message", inputs: ["errorMessage", "id"] }] });
|
|
935
|
+
}
|
|
936
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: MnInputField, decorators: [{
|
|
937
|
+
type: Component,
|
|
938
|
+
args: [{ selector: 'mn-input-field', standalone: true, imports: [NgClass, MnErrorMessage], template: "<div class=\"flex flex-col\" [class.is-fullwidth]=\"props.fullWidth\">\n <!-- Label -->\n @if (props.label) {\n <label class=\"pl-2 pb-1 flex flex-row gap-x-0.5! text-base!\" [attr.for]=\"resolvedId\">\n <p>{{ props.label }}</p>\n @if (isRequired()) {\n <span class=\"text-red-500 \">*</span>\n }\n </label>\n }\n\n <!-- Input Element -->\n <input\n [id]=\"resolvedId\"\n [attr.name]=\"resolvedName\"\n [type]=\"props.type\"\n [attr.placeholder]=\"props.placeholder || null\"\n [attr.aria-label]=\"props.ariaLabel || props.label || null\"\n [attr.aria-invalid]=\"showError || null\"\n [attr.aria-describedby]=\"showError ? resolvedId + '-error' : null\"\n [disabled]=\"isDisabled\"\n [attr.min]=\"minAttr\"\n [attr.max]=\"maxAttr\"\n [value]=\"value ?? ''\"\n [ngClass]=\"inputClasses\"\n (input)=\"handleInput(($any($event.target)).value)\"\n (blur)=\"handleBlur()\"\n />\n\n <!-- Error Messages -->\n @if (showError) {\n <!-- Show all errors mode -->\n @if (props.showAllErrors) {\n <div class=\"flex flex-col gap-y-1\">\n @for (error of errorMessages; track $index) {\n <lib-mn-error-message [errorMessage]=\"error\" [id]=\"resolvedId + '-' + $index\"></lib-mn-error-message>\n }\n </div>\n } @else {\n @if (errorMessage != null) {\n <lib-mn-error-message [errorMessage]=\"errorMessage\" [id]=\"resolvedId\"></lib-mn-error-message>\n\n }\n }\n }\n</div>\n" }]
|
|
939
|
+
}], ctorParameters: () => [{ type: i1$1.NgControl, decorators: [{
|
|
940
|
+
type: Optional
|
|
941
|
+
}, {
|
|
942
|
+
type: Self
|
|
943
|
+
}] }], propDecorators: { props: [{
|
|
944
|
+
type: Input,
|
|
945
|
+
args: [{ required: true }]
|
|
946
|
+
}] } });
|
|
947
|
+
|
|
948
|
+
/**
|
|
949
|
+
* Represents the current section path based on nested mn-section directives.
|
|
950
|
+
*/
|
|
951
|
+
const MN_SECTION_PATH = new InjectionToken('MN_SECTION_PATH', {
|
|
952
|
+
providedIn: 'root',
|
|
953
|
+
factory: () => [],
|
|
954
|
+
});
|
|
955
|
+
/**
|
|
956
|
+
* Represents the current component instance id provided by [mn-instance].
|
|
957
|
+
*/
|
|
958
|
+
const MN_INSTANCE_ID = new InjectionToken('MN_INSTANCE_ID', {
|
|
959
|
+
providedIn: 'root',
|
|
960
|
+
factory: () => null,
|
|
961
|
+
});
|
|
962
|
+
|
|
963
|
+
function isPlainObject(value) {
|
|
964
|
+
return (typeof value === 'object' &&
|
|
965
|
+
value !== null &&
|
|
966
|
+
Object.prototype.toString.call(value) === '[object Object]');
|
|
967
|
+
}
|
|
968
|
+
class MnConfigService {
|
|
969
|
+
http;
|
|
970
|
+
_config = null;
|
|
971
|
+
constructor(http) {
|
|
972
|
+
this.http = http;
|
|
973
|
+
}
|
|
974
|
+
/**
|
|
975
|
+
* Load the configuration JSON from the provided URL and cache it in memory.
|
|
976
|
+
* Consumers should typically call this via the APP_INITIALIZER helper.
|
|
977
|
+
*/
|
|
978
|
+
async load(url) {
|
|
979
|
+
let json = await firstValueFrom(this.http.get(url, { responseType: 'json' }));
|
|
980
|
+
if (typeof json === 'string') {
|
|
981
|
+
try {
|
|
982
|
+
json = JSON.parse(json);
|
|
983
|
+
}
|
|
984
|
+
catch {
|
|
985
|
+
json = {};
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
const cfg = (isPlainObject(json) ? json : {});
|
|
989
|
+
const defaults = (isPlainObject(cfg.defaults) ? cfg.defaults : {});
|
|
990
|
+
const overrides = isPlainObject(cfg.overrides) ? cfg.overrides : cfg.overrides ?? {};
|
|
991
|
+
this._config = { defaults, overrides };
|
|
992
|
+
}
|
|
993
|
+
/**
|
|
994
|
+
* Resolve a configuration object for a component, optionally scoped to a section path
|
|
995
|
+
* and optionally overridden by an instance id.
|
|
996
|
+
*/
|
|
997
|
+
resolve(componentName, sectionPath = [], instanceId) {
|
|
998
|
+
const baseConfig = isPlainObject(this._config?.defaults)
|
|
999
|
+
? (isPlainObject(this._config.defaults[componentName])
|
|
1000
|
+
? { ...this._config.defaults[componentName] }
|
|
1001
|
+
: {})
|
|
1002
|
+
: {};
|
|
1003
|
+
const leaf = this.walkOverrides(this._config?.overrides ?? {}, sectionPath);
|
|
1004
|
+
let resolved = baseConfig;
|
|
1005
|
+
if (leaf && isPlainObject(leaf[componentName])) {
|
|
1006
|
+
resolved = this.deepMerge(resolved, leaf[componentName]);
|
|
1007
|
+
}
|
|
1008
|
+
if (instanceId) {
|
|
1009
|
+
const instKey = `#${instanceId}`;
|
|
1010
|
+
if (leaf && isPlainObject(leaf[instKey])) {
|
|
1011
|
+
resolved = this.deepMerge(resolved, leaf[instKey]);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
return resolved;
|
|
1015
|
+
}
|
|
1016
|
+
/**
|
|
1017
|
+
* Walk the overrides nested object using the provided section path and return the leaf node.
|
|
1018
|
+
* If any segment is missing or the current node is not a plain object, returns undefined.
|
|
1019
|
+
*/
|
|
1020
|
+
walkOverrides(overridesRoot, sectionPath) {
|
|
1021
|
+
let node = overridesRoot;
|
|
1022
|
+
for (const segment of sectionPath) {
|
|
1023
|
+
if (!isPlainObject(node))
|
|
1024
|
+
return undefined;
|
|
1025
|
+
node = node[segment];
|
|
1026
|
+
if (node === undefined)
|
|
1027
|
+
return undefined;
|
|
1028
|
+
}
|
|
1029
|
+
return node;
|
|
1030
|
+
}
|
|
1031
|
+
/**
|
|
1032
|
+
* Deep merge two plain-object trees. Arrays and non-plain values are replaced by the patch.
|
|
1033
|
+
* Does not mutate inputs; returns a new object.
|
|
1034
|
+
*/
|
|
1035
|
+
deepMerge(base, patch) {
|
|
1036
|
+
const out = { ...base };
|
|
1037
|
+
for (const key of Object.keys(patch)) {
|
|
1038
|
+
const bVal = base[key];
|
|
1039
|
+
const pVal = patch[key];
|
|
1040
|
+
if (isPlainObject(bVal) && isPlainObject(pVal)) {
|
|
1041
|
+
out[key] = this.deepMerge(bVal, pVal);
|
|
1042
|
+
}
|
|
1043
|
+
else {
|
|
1044
|
+
// replace for arrays, primitives, null, undefined, and non-plain objects
|
|
1045
|
+
out[key] = pVal;
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
return out;
|
|
1049
|
+
}
|
|
1050
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: MnConfigService, deps: [{ token: i1$2.HttpClient }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1051
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: MnConfigService, providedIn: 'root' });
|
|
1052
|
+
}
|
|
1053
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: MnConfigService, decorators: [{
|
|
1054
|
+
type: Injectable,
|
|
1055
|
+
args: [{ providedIn: 'root' }]
|
|
1056
|
+
}], ctorParameters: () => [{ type: i1$2.HttpClient }] });
|
|
1057
|
+
|
|
1058
|
+
/**
|
|
1059
|
+
* Helper to provide a resolved, typed component config via DI.
|
|
1060
|
+
*
|
|
1061
|
+
* Usage in a component/module providers:
|
|
1062
|
+
* const MY_CFG = new InjectionToken<MyCfg>('MY_CFG');
|
|
1063
|
+
* providers: [ provideMnComponentConfig(MY_CFG, 'my-component') ]
|
|
1064
|
+
* Then in the component:
|
|
1065
|
+
* readonly cfg = inject(MY_CFG)
|
|
1066
|
+
*/
|
|
1067
|
+
function provideMnComponentConfig(token, componentName, initial) {
|
|
1068
|
+
return {
|
|
1069
|
+
provide: token,
|
|
1070
|
+
deps: [MnConfigService, [new Optional(), MN_SECTION_PATH], [new Optional(), MN_INSTANCE_ID]],
|
|
1071
|
+
useFactory: (svc, sectionPath, instanceId) => {
|
|
1072
|
+
const resolved = svc.resolve(componentName, sectionPath ?? [], instanceId ?? undefined);
|
|
1073
|
+
// Apply optional initial (local defaults) over resolved? We prefer resolved to override initial local defaults,
|
|
1074
|
+
// so merge initial first, then resolved on top.
|
|
1075
|
+
return Object.assign({}, initial ?? {}, resolved);
|
|
1076
|
+
},
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
const MN_TEST_COMPONENT_CONFIG = new InjectionToken('MN_TEST_COMPONENT_CONFIG');
|
|
1081
|
+
class MnTestComponent {
|
|
1082
|
+
cfg = inject(MN_TEST_COMPONENT_CONFIG);
|
|
1083
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: MnTestComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1084
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.9", type: MnTestComponent, isStandalone: true, selector: "mn-test-component", providers: [
|
|
1085
|
+
provideMnComponentConfig(MN_TEST_COMPONENT_CONFIG, 'test-component'),
|
|
1086
|
+
], ngImport: i0, template: `
|
|
1087
|
+
<div class="mn-test" [style.color]="(cfg.color ?? 'inherit')">
|
|
1088
|
+
{{ cfg.text ?? 'Hello from component' }}
|
|
1089
|
+
</div>
|
|
1090
|
+
`, isInline: true, styles: [".mn-test{font-weight:600;padding:8px 12px;border:1px dashed #ddd;border-radius:6px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
|
|
1091
|
+
}
|
|
1092
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: MnTestComponent, decorators: [{
|
|
1093
|
+
type: Component,
|
|
1094
|
+
args: [{ selector: 'mn-test-component', standalone: true, imports: [CommonModule], providers: [
|
|
1095
|
+
provideMnComponentConfig(MN_TEST_COMPONENT_CONFIG, 'test-component'),
|
|
1096
|
+
], template: `
|
|
1097
|
+
<div class="mn-test" [style.color]="(cfg.color ?? 'inherit')">
|
|
1098
|
+
{{ cfg.text ?? 'Hello from component' }}
|
|
1099
|
+
</div>
|
|
1100
|
+
`, styles: [".mn-test{font-weight:600;padding:8px 12px;border:1px dashed #ddd;border-radius:6px}\n"] }]
|
|
1101
|
+
}] });
|
|
1102
|
+
|
|
1103
|
+
class MnDualHorizontalImage {
|
|
1104
|
+
_images = [];
|
|
1105
|
+
set images(value) {
|
|
1106
|
+
this._images = (value ?? []).slice(0, 2);
|
|
1107
|
+
}
|
|
1108
|
+
get images() {
|
|
1109
|
+
return this._images;
|
|
1110
|
+
}
|
|
1111
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: MnDualHorizontalImage, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1112
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.9", type: MnDualHorizontalImage, isStandalone: true, selector: "lib-mn-dual-horizontal-image", inputs: { images: "images" }, ngImport: i0, template: "<div class=\"flex flex-col w-full -space-y-5 md:-space-y-10 lg:-space-y-5\">\n @for (image of images; track image.id) {\n <div class=\"w-[75%] overflow-hidden rounded-3xl shadow-md md:w-[65%] lg:w-[80%] first:self-start last:self-end last:mb-4\">\n <img [ngSrc]=\"image.url\" width=\"150\" height=\"100\" class=\"size-full object-cover\" [alt]=\"image.alt\" />\n </div>\n } @empty {\n <p>No images found</p>\n }\n</div>\n\n", dependencies: [{ kind: "directive", type: NgOptimizedImage, selector: "img[ngSrc]", inputs: ["ngSrc", "ngSrcset", "sizes", "width", "height", "decoding", "loading", "priority", "loaderParams", "disableOptimizedSrcset", "fill", "placeholder", "placeholderConfig", "src", "srcset"] }] });
|
|
1113
|
+
}
|
|
1114
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: MnDualHorizontalImage, decorators: [{
|
|
1115
|
+
type: Component,
|
|
1116
|
+
args: [{ selector: 'lib-mn-dual-horizontal-image', standalone: true, imports: [
|
|
1117
|
+
NgOptimizedImage
|
|
1118
|
+
], template: "<div class=\"flex flex-col w-full -space-y-5 md:-space-y-10 lg:-space-y-5\">\n @for (image of images; track image.id) {\n <div class=\"w-[75%] overflow-hidden rounded-3xl shadow-md md:w-[65%] lg:w-[80%] first:self-start last:self-end last:mb-4\">\n <img [ngSrc]=\"image.url\" width=\"150\" height=\"100\" class=\"size-full object-cover\" [alt]=\"image.alt\" />\n </div>\n } @empty {\n <p>No images found</p>\n }\n</div>\n\n" }]
|
|
1119
|
+
}], propDecorators: { images: [{
|
|
1120
|
+
type: Input
|
|
1121
|
+
}] } });
|
|
1122
|
+
|
|
1123
|
+
const mnInformationCardVariants = tv({
|
|
1124
|
+
base: '',
|
|
1125
|
+
variants: {
|
|
1126
|
+
bottomBorder: {
|
|
1127
|
+
true: 'border-b border-b-2 border-brand-500',
|
|
1128
|
+
},
|
|
1129
|
+
shadow: {
|
|
1130
|
+
true: 'shadow-lg',
|
|
1131
|
+
},
|
|
1132
|
+
textPosition: {
|
|
1133
|
+
left: 'text-left',
|
|
1134
|
+
center: 'text-center',
|
|
1135
|
+
right: 'text-right',
|
|
1136
|
+
},
|
|
1137
|
+
borderRadius: {
|
|
1138
|
+
none: 'rounded-none',
|
|
1139
|
+
xs: 'rounded-xs',
|
|
1140
|
+
sm: 'rounded-sm',
|
|
1141
|
+
md: 'rounded-md',
|
|
1142
|
+
lg: 'rounded-lg',
|
|
1143
|
+
xl: 'rounded-xl',
|
|
1144
|
+
two_xl: 'rounded-2xl',
|
|
1145
|
+
three_xl: 'rounded-3xl',
|
|
1146
|
+
four_xl: 'rounded-4xl',
|
|
1147
|
+
},
|
|
1148
|
+
},
|
|
1149
|
+
});
|
|
1150
|
+
|
|
1151
|
+
class MnInformationCard {
|
|
1152
|
+
data;
|
|
1153
|
+
get hostClasses() {
|
|
1154
|
+
return mnInformationCardVariants({
|
|
1155
|
+
bottomBorder: this.data.bottomBorder,
|
|
1156
|
+
shadow: this.data.shadow,
|
|
1157
|
+
textPosition: this.data.textPosition,
|
|
1158
|
+
});
|
|
1159
|
+
}
|
|
1160
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: MnInformationCard, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1161
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.9", type: MnInformationCard, isStandalone: true, selector: "lib-mn-information-card", inputs: { data: "data" }, ngImport: i0, template: "<div class=\"flex flex-col items-center gap-y-4 p-4 size-full\" [ngClass]=\"hostClasses\">\n<ng-content select=\"[header]\"></ng-content>\n<ng-content></ng-content>\n<ng-content select=\"[footer]\"></ng-content>\n</div>\n", dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
|
|
1162
|
+
}
|
|
1163
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: MnInformationCard, decorators: [{
|
|
1164
|
+
type: Component,
|
|
1165
|
+
args: [{ selector: 'lib-mn-information-card', standalone: true, imports: [
|
|
1166
|
+
NgClass
|
|
1167
|
+
], template: "<div class=\"flex flex-col items-center gap-y-4 p-4 size-full\" [ngClass]=\"hostClasses\">\n<ng-content select=\"[header]\"></ng-content>\n<ng-content></ng-content>\n<ng-content select=\"[footer]\"></ng-content>\n</div>\n" }]
|
|
1168
|
+
}], propDecorators: { data: [{
|
|
1169
|
+
type: Input,
|
|
1170
|
+
args: [{ required: true }]
|
|
1171
|
+
}] } });
|
|
1172
|
+
|
|
1173
|
+
/**
|
|
1174
|
+
* Types for mn-lib configuration.
|
|
1175
|
+
*/
|
|
1176
|
+
|
|
1177
|
+
/**
|
|
1178
|
+
* Provides an APP_INITIALIZER that loads the mn-lib configuration from the given URL
|
|
1179
|
+
* during application bootstrap. The consuming application is responsible for providing
|
|
1180
|
+
* HttpClient (e.g., via HttpClientModule or provideHttpClient()).
|
|
1181
|
+
*/
|
|
1182
|
+
function provideMnConfig(url) {
|
|
1183
|
+
return [
|
|
1184
|
+
{
|
|
1185
|
+
provide: APP_INITIALIZER,
|
|
1186
|
+
multi: true,
|
|
1187
|
+
useFactory: (svc) => () => svc.load(url),
|
|
1188
|
+
deps: [MnConfigService],
|
|
1189
|
+
},
|
|
1190
|
+
];
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
class MnSectionDirective {
|
|
1194
|
+
/** Section name contributed by this DOM node to the section path */
|
|
1195
|
+
mnSection;
|
|
1196
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: MnSectionDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1197
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.9", type: MnSectionDirective, isStandalone: true, selector: "[mn-section]", inputs: { mnSection: ["mn-section", "mnSection"] }, providers: [
|
|
1198
|
+
{
|
|
1199
|
+
provide: MN_SECTION_PATH,
|
|
1200
|
+
// Read parent MN_SECTION_PATH from ancestor injector (skipSelf to avoid self-reference),
|
|
1201
|
+
// and read the attribute value using Attribute so it's available at provider creation time.
|
|
1202
|
+
deps: [[new Optional(), new SkipSelf(), MN_SECTION_PATH], new Attribute('mn-section')],
|
|
1203
|
+
useFactory: (parentPath, attr) => {
|
|
1204
|
+
const parent = Array.isArray(parentPath) ? parentPath : [];
|
|
1205
|
+
const name = (attr ?? '').trim();
|
|
1206
|
+
return name ? [...parent, name] : [...parent];
|
|
1207
|
+
},
|
|
1208
|
+
},
|
|
1209
|
+
], ngImport: i0 });
|
|
1210
|
+
}
|
|
1211
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: MnSectionDirective, decorators: [{
|
|
1212
|
+
type: Directive,
|
|
1213
|
+
args: [{
|
|
1214
|
+
selector: '[mn-section]',
|
|
1215
|
+
standalone: true,
|
|
1216
|
+
providers: [
|
|
1217
|
+
{
|
|
1218
|
+
provide: MN_SECTION_PATH,
|
|
1219
|
+
// Read parent MN_SECTION_PATH from ancestor injector (skipSelf to avoid self-reference),
|
|
1220
|
+
// and read the attribute value using Attribute so it's available at provider creation time.
|
|
1221
|
+
deps: [[new Optional(), new SkipSelf(), MN_SECTION_PATH], new Attribute('mn-section')],
|
|
1222
|
+
useFactory: (parentPath, attr) => {
|
|
1223
|
+
const parent = Array.isArray(parentPath) ? parentPath : [];
|
|
1224
|
+
const name = (attr ?? '').trim();
|
|
1225
|
+
return name ? [...parent, name] : [...parent];
|
|
1226
|
+
},
|
|
1227
|
+
},
|
|
1228
|
+
],
|
|
1229
|
+
}]
|
|
1230
|
+
}], propDecorators: { mnSection: [{
|
|
1231
|
+
type: Input,
|
|
1232
|
+
args: ['mn-section']
|
|
1233
|
+
}] } });
|
|
1234
|
+
|
|
1235
|
+
class MnInstanceDirective {
|
|
1236
|
+
/** Instance id for targeting per-component instance overrides */
|
|
1237
|
+
mnInstance;
|
|
1238
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: MnInstanceDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1239
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.9", type: MnInstanceDirective, isStandalone: true, selector: "[mn-instance]", inputs: { mnInstance: ["mn-instance", "mnInstance"] }, providers: [
|
|
1240
|
+
{
|
|
1241
|
+
provide: MN_INSTANCE_ID,
|
|
1242
|
+
// Read the attribute at provider creation time using Attribute token; Inputs may not be set yet.
|
|
1243
|
+
deps: [new Attribute('mn-instance')],
|
|
1244
|
+
useFactory: (attr) => (attr ?? '').trim() || null,
|
|
1245
|
+
},
|
|
1246
|
+
], ngImport: i0 });
|
|
1247
|
+
}
|
|
1248
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: MnInstanceDirective, decorators: [{
|
|
1249
|
+
type: Directive,
|
|
1250
|
+
args: [{
|
|
1251
|
+
selector: '[mn-instance]',
|
|
1252
|
+
standalone: true,
|
|
1253
|
+
providers: [
|
|
1254
|
+
{
|
|
1255
|
+
provide: MN_INSTANCE_ID,
|
|
1256
|
+
// Read the attribute at provider creation time using Attribute token; Inputs may not be set yet.
|
|
1257
|
+
deps: [new Attribute('mn-instance')],
|
|
1258
|
+
useFactory: (attr) => (attr ?? '').trim() || null,
|
|
1259
|
+
},
|
|
1260
|
+
],
|
|
1261
|
+
}]
|
|
1262
|
+
}], propDecorators: { mnInstance: [{
|
|
1263
|
+
type: Input,
|
|
1264
|
+
args: ['mn-instance']
|
|
1265
|
+
}] } });
|
|
1266
|
+
|
|
362
1267
|
/*
|
|
363
1268
|
* Public API Surface of mn-lib
|
|
364
1269
|
*/
|
|
@@ -367,5 +1272,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImpor
|
|
|
367
1272
|
* Generated bundle index. Do not edit.
|
|
368
1273
|
*/
|
|
369
1274
|
|
|
370
|
-
export { DEFAULT_MN_ALERT_CONFIG, MN_ALERT_CONFIG, MN_THEME, MN_THEME_DEFAULTS, MnAlertOutletComponent, MnAlertService, MnAlertStore, MnThemeService, Test, injectTheme, injectThemeSignal, provideMnAlerts, provideMnTheme, provideMnThemeDynamic };
|
|
1275
|
+
export { DEFAULT_MN_ALERT_CONFIG, MN_ALERT_CONFIG, MN_INSTANCE_ID, MN_SECTION_PATH, MN_TEST_COMPONENT_CONFIG, MN_THEME, MN_THEME_DEFAULTS, MnAlertOutletComponent, MnAlertService, MnAlertStore, MnButton, MnConfigService, MnDualHorizontalImage, MnInformationCard, MnInputField, MnInstanceDirective, MnSectionDirective, MnTestComponent, MnThemeService, Test, dateTimeAdapter, defaultTextAdapter, injectTheme, injectThemeSignal, mnInformationCardVariants, mnInputFieldVariants, numberAdapter, pickAdapter, provideMnAlerts, provideMnComponentConfig, provideMnConfig, provideMnTheme, provideMnThemeDynamic };
|
|
371
1276
|
//# sourceMappingURL=mn-angular-lib.mjs.map
|