mn-angular-lib 0.0.88 → 0.0.90
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.
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, Injectable, Optional, Inject, HostBinding, Input, Component, inject, ChangeDetectionStrategy, signal, ElementRef, DestroyRef, Self, APP_INITIALIZER, HostListener, forwardRef, Directive, Renderer2, EventEmitter, ChangeDetectorRef, TemplateRef, Output, ViewContainerRef,
|
|
2
|
+
import { InjectionToken, Injectable, Optional, Inject, HostBinding, Input, Component, inject, ChangeDetectionStrategy, signal, ElementRef, DestroyRef, Self, APP_INITIALIZER, HostListener, ViewChild, forwardRef, Directive, Renderer2, EventEmitter, ChangeDetectorRef, TemplateRef, Output, ViewContainerRef, ViewChildren, ApplicationRef, EnvironmentInjector, createComponent, Pipe, SkipSelf, Attribute } from '@angular/core';
|
|
3
3
|
export { TemplateRef, Type } from '@angular/core';
|
|
4
4
|
import { BehaviorSubject, firstValueFrom, skip, Subject, debounceTime, of, takeUntil, map, catchError } from 'rxjs';
|
|
5
5
|
import * as i1 from '@angular/common';
|
|
@@ -2133,11 +2133,15 @@ class MnMultiSelect {
|
|
|
2133
2133
|
elRef = inject(ElementRef);
|
|
2134
2134
|
lang = inject(MnLanguageService);
|
|
2135
2135
|
destroyRef = inject(DestroyRef);
|
|
2136
|
+
/** Reference to the trigger element for positioning the dropdown */
|
|
2137
|
+
triggerRef;
|
|
2136
2138
|
/** Currently selected values */
|
|
2137
2139
|
selectedValues = [];
|
|
2138
2140
|
isOpen = false;
|
|
2139
2141
|
isDisabled = false;
|
|
2140
2142
|
searchTerm = '';
|
|
2143
|
+
/** Dropdown position calculated from trigger bounding rect */
|
|
2144
|
+
dropdownStyle = { top: '0px', left: '0px', width: '0px' };
|
|
2141
2145
|
onChange = () => { };
|
|
2142
2146
|
onTouched = () => { };
|
|
2143
2147
|
builtInErrorMessages = {
|
|
@@ -2183,10 +2187,24 @@ class MnMultiSelect {
|
|
|
2183
2187
|
if (this.isDisabled)
|
|
2184
2188
|
return;
|
|
2185
2189
|
this.isOpen = !this.isOpen;
|
|
2186
|
-
if (
|
|
2190
|
+
if (this.isOpen) {
|
|
2191
|
+
this.updateDropdownPosition();
|
|
2192
|
+
}
|
|
2193
|
+
else {
|
|
2187
2194
|
this.searchTerm = '';
|
|
2188
2195
|
}
|
|
2189
2196
|
}
|
|
2197
|
+
/** Calculates the fixed position for the dropdown based on the trigger element */
|
|
2198
|
+
updateDropdownPosition() {
|
|
2199
|
+
if (!this.triggerRef)
|
|
2200
|
+
return;
|
|
2201
|
+
const rect = this.triggerRef.nativeElement.getBoundingClientRect();
|
|
2202
|
+
this.dropdownStyle = {
|
|
2203
|
+
top: `${rect.bottom}px`,
|
|
2204
|
+
left: `${rect.left}px`,
|
|
2205
|
+
width: `${rect.width}px`,
|
|
2206
|
+
};
|
|
2207
|
+
}
|
|
2190
2208
|
onDocumentClick(event) {
|
|
2191
2209
|
if (!this.elRef.nativeElement.contains(event.target)) {
|
|
2192
2210
|
if (this.isOpen) {
|
|
@@ -2195,6 +2213,13 @@ class MnMultiSelect {
|
|
|
2195
2213
|
}
|
|
2196
2214
|
}
|
|
2197
2215
|
}
|
|
2216
|
+
/** Closes the dropdown when the page or a scrollable parent is scrolled */
|
|
2217
|
+
onWindowScrollOrResize() {
|
|
2218
|
+
if (this.isOpen) {
|
|
2219
|
+
this.isOpen = false;
|
|
2220
|
+
this.searchTerm = '';
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2198
2223
|
toggleOption(option) {
|
|
2199
2224
|
if (option.disabled)
|
|
2200
2225
|
return;
|
|
@@ -2307,11 +2332,11 @@ class MnMultiSelect {
|
|
|
2307
2332
|
});
|
|
2308
2333
|
}
|
|
2309
2334
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: MnMultiSelect, deps: [{ token: i1$2.NgControl, optional: true, self: true }], target: i0.ɵɵFactoryTarget.Component });
|
|
2310
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: MnMultiSelect, isStandalone: true, selector: "mn-lib-multi-select", inputs: { props: "props" }, host: { listeners: { "document:click": "onDocumentClick($event)" } }, ngImport: i0, template: "<div class=\"flex flex-col h-full\" [class.is-fullwidth]=\"props.fullWidth\">\n @if (uiConfig.label || props.label) {\n <label class=\"pl-2 pb-1 flex flex-row gap-x-0.5! text-base!\" [attr.for]=\"resolvedId\">\n <p>{{ uiConfig.label || props.label }}</p>\n @if (isRequired()) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n }\n\n <!-- Trigger -->\n <div\n [id]=\"resolvedId\"\n [ngClass]=\"triggerClasses\"\n class=\"relative\"\n [attr.aria-label]=\"uiConfig.ariaLabel || uiConfig.label || props.label || null\"\n [attr.aria-invalid]=\"showError || null\"\n [attr.aria-describedby]=\"showError ? resolvedId + '-error' : null\"\n [attr.aria-expanded]=\"isOpen\"\n role=\"combobox\"\n tabindex=\"0\"\n (click)=\"toggle()\"\n (keydown.enter)=\"toggle()\"\n (keydown.space)=\"toggle(); $event.preventDefault()\"\n (blur)=\"handleBlur()\"\n >\n <div class=\"flex flex-row items-center gap-x-1 flex-wrap min-h-[1.5rem]\">\n @if (selectedOptions.length === 0) {\n <span class=\"text-base-content/50\">{{ uiConfig.placeholder || props.placeholder || 'Select...' }}</span>\n } @else {\n @for (opt of selectedOptions; track opt.value) {\n <span class=\"inline-flex items-center gap-x-1 bg-base-200 text-base-content text-xs px-2 py-0.5 rounded-md\">\n {{ opt.label }}\n <button\n mnButton\n [data]=\"{ size: 'sm', variant: 'text', color: 'secondary' }\"\n type=\"button\"\n class=\"text-base-content/50 hover:text-base-content cursor-pointer\"\n (click)=\"removeOption(opt, $event)\"\n [attr.aria-label]=\"'Remove ' + opt.label\"\n >\u00D7</button>\n </span>\n }\n }\n </div>\n <div class=\"absolute right-2 top-1/2 -translate-y-1/2 pointer-events-none\">\n <svg class=\"w-4 h-4 text-base-content/50\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 12 12\">\n <path fill=\"currentColor\" d=\"M6 8L1 3h10z\"/>\n </svg>\n </div>\n </div>\n\n <!-- Dropdown -->\n @if (isOpen) {\n <div
|
|
2335
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: MnMultiSelect, isStandalone: true, selector: "mn-lib-multi-select", inputs: { props: "props" }, host: { listeners: { "document:click": "onDocumentClick($event)", "window:scroll": "onWindowScrollOrResize()", "window:resize": "onWindowScrollOrResize()" } }, viewQueries: [{ propertyName: "triggerRef", first: true, predicate: ["trigger"], descendants: true }], ngImport: i0, template: "<div class=\"flex flex-col h-full\" [class.is-fullwidth]=\"props.fullWidth\">\n @if (uiConfig.label || props.label) {\n <label class=\"pl-2 pb-1 flex flex-row gap-x-0.5! text-base!\" [attr.for]=\"resolvedId\">\n <p>{{ uiConfig.label || props.label }}</p>\n @if (isRequired()) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n }\n\n <!-- Trigger -->\n <div\n #trigger\n [id]=\"resolvedId\"\n [ngClass]=\"triggerClasses\"\n class=\"relative\"\n [attr.aria-label]=\"uiConfig.ariaLabel || uiConfig.label || props.label || null\"\n [attr.aria-invalid]=\"showError || null\"\n [attr.aria-describedby]=\"showError ? resolvedId + '-error' : null\"\n [attr.aria-expanded]=\"isOpen\"\n role=\"combobox\"\n tabindex=\"0\"\n (click)=\"toggle()\"\n (keydown.enter)=\"toggle()\"\n (keydown.space)=\"toggle(); $event.preventDefault()\"\n (blur)=\"handleBlur()\"\n >\n <div class=\"flex flex-row items-center gap-x-1 flex-wrap min-h-[1.5rem]\">\n @if (selectedOptions.length === 0) {\n <span class=\"text-base-content/50\">{{ uiConfig.placeholder || props.placeholder || 'Select...' }}</span>\n } @else {\n @for (opt of selectedOptions; track opt.value) {\n <span class=\"inline-flex items-center gap-x-1 bg-base-200 text-base-content text-xs px-2 py-0.5 rounded-md\">\n {{ opt.label }}\n <button\n mnButton\n [data]=\"{ size: 'sm', variant: 'text', color: 'secondary' }\"\n type=\"button\"\n class=\"text-base-content/50 hover:text-base-content cursor-pointer\"\n (click)=\"removeOption(opt, $event)\"\n [attr.aria-label]=\"'Remove ' + opt.label\"\n >\u00D7</button>\n </span>\n }\n }\n </div>\n <div class=\"absolute right-2 top-1/2 -translate-y-1/2 pointer-events-none\">\n <svg class=\"w-4 h-4 text-base-content/50\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 12 12\">\n <path fill=\"currentColor\" d=\"M6 8L1 3h10z\"/>\n </svg>\n </div>\n </div>\n\n <!-- Dropdown -->\n @if (isOpen) {\n <div\n class=\"fixed z-[9999] bg-base-100 border border-base-300 rounded-md shadow-lg max-h-60 overflow-auto\"\n [style.top]=\"dropdownStyle.top\"\n [style.left]=\"dropdownStyle.left\"\n [style.width]=\"dropdownStyle.width\"\n (click)=\"$event.stopPropagation()\"\n >\n @if (props.searchable) {\n <div class=\"p-2 border-b border-base-300\">\n <input\n type=\"text\"\n class=\"w-full p-1.5 text-sm border border-base-300 rounded-md outline-none focus:border-brand-500 bg-base-200 text-base-content placeholder-base-content/50\"\n [placeholder]=\"props.searchPlaceholder || 'Search...'\"\n [value]=\"searchTerm\"\n (input)=\"onSearch(($any($event.target)).value)\"\n (click)=\"$event.stopPropagation()\"\n />\n </div>\n }\n @for (opt of filteredOptions; track opt.value) {\n <div\n class=\"flex items-center gap-x-2 px-3 py-2 text-sm cursor-pointer text-base-content hover:bg-base-200\"\n [class.opacity-50]=\"opt.disabled || isMaxReached(opt)\"\n [class.pointer-events-none]=\"opt.disabled || isMaxReached(opt)\"\n (click)=\"toggleOption(opt); $event.stopPropagation()\"\n >\n <input\n type=\"checkbox\"\n class=\"w-4 h-4 accent-brand-500 pointer-events-none\"\n [checked]=\"isSelected(opt)\"\n [disabled]=\"opt.disabled || isMaxReached(opt)\"\n tabindex=\"-1\"\n />\n <span>{{ opt.label }}</span>\n </div>\n }\n @if (filteredOptions.length === 0) {\n <div class=\"px-3 py-2 text-sm text-base-content/50\">{{ uiConfig.noOptionsFound || 'No options found' }}</div>\n }\n </div>\n }\n\n @if (showError) {\n @if (props.showAllErrors) {\n <div class=\"flex flex-col gap-y-1 mt-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</div>\n", dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: MnErrorMessage, selector: "lib-mn-error-message", inputs: ["errorMessage", "id"] }, { kind: "component", type: MnButton, selector: "button[mnButton], a[mnButton]", inputs: ["data"] }] });
|
|
2311
2336
|
}
|
|
2312
2337
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: MnMultiSelect, decorators: [{
|
|
2313
2338
|
type: Component,
|
|
2314
|
-
args: [{ selector: 'mn-lib-multi-select', standalone: true, imports: [NgClass, MnErrorMessage, MnButton], template: "<div class=\"flex flex-col h-full\" [class.is-fullwidth]=\"props.fullWidth\">\n @if (uiConfig.label || props.label) {\n <label class=\"pl-2 pb-1 flex flex-row gap-x-0.5! text-base!\" [attr.for]=\"resolvedId\">\n <p>{{ uiConfig.label || props.label }}</p>\n @if (isRequired()) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n }\n\n <!-- Trigger -->\n <div\n [id]=\"resolvedId\"\n [ngClass]=\"triggerClasses\"\n class=\"relative\"\n [attr.aria-label]=\"uiConfig.ariaLabel || uiConfig.label || props.label || null\"\n [attr.aria-invalid]=\"showError || null\"\n [attr.aria-describedby]=\"showError ? resolvedId + '-error' : null\"\n [attr.aria-expanded]=\"isOpen\"\n role=\"combobox\"\n tabindex=\"0\"\n (click)=\"toggle()\"\n (keydown.enter)=\"toggle()\"\n (keydown.space)=\"toggle(); $event.preventDefault()\"\n (blur)=\"handleBlur()\"\n >\n <div class=\"flex flex-row items-center gap-x-1 flex-wrap min-h-[1.5rem]\">\n @if (selectedOptions.length === 0) {\n <span class=\"text-base-content/50\">{{ uiConfig.placeholder || props.placeholder || 'Select...' }}</span>\n } @else {\n @for (opt of selectedOptions; track opt.value) {\n <span class=\"inline-flex items-center gap-x-1 bg-base-200 text-base-content text-xs px-2 py-0.5 rounded-md\">\n {{ opt.label }}\n <button\n mnButton\n [data]=\"{ size: 'sm', variant: 'text', color: 'secondary' }\"\n type=\"button\"\n class=\"text-base-content/50 hover:text-base-content cursor-pointer\"\n (click)=\"removeOption(opt, $event)\"\n [attr.aria-label]=\"'Remove ' + opt.label\"\n >\u00D7</button>\n </span>\n }\n }\n </div>\n <div class=\"absolute right-2 top-1/2 -translate-y-1/2 pointer-events-none\">\n <svg class=\"w-4 h-4 text-base-content/50\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 12 12\">\n <path fill=\"currentColor\" d=\"M6 8L1 3h10z\"/>\n </svg>\n </div>\n </div>\n\n <!-- Dropdown -->\n @if (isOpen) {\n <div
|
|
2339
|
+
args: [{ selector: 'mn-lib-multi-select', standalone: true, imports: [NgClass, MnErrorMessage, MnButton], template: "<div class=\"flex flex-col h-full\" [class.is-fullwidth]=\"props.fullWidth\">\n @if (uiConfig.label || props.label) {\n <label class=\"pl-2 pb-1 flex flex-row gap-x-0.5! text-base!\" [attr.for]=\"resolvedId\">\n <p>{{ uiConfig.label || props.label }}</p>\n @if (isRequired()) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n }\n\n <!-- Trigger -->\n <div\n #trigger\n [id]=\"resolvedId\"\n [ngClass]=\"triggerClasses\"\n class=\"relative\"\n [attr.aria-label]=\"uiConfig.ariaLabel || uiConfig.label || props.label || null\"\n [attr.aria-invalid]=\"showError || null\"\n [attr.aria-describedby]=\"showError ? resolvedId + '-error' : null\"\n [attr.aria-expanded]=\"isOpen\"\n role=\"combobox\"\n tabindex=\"0\"\n (click)=\"toggle()\"\n (keydown.enter)=\"toggle()\"\n (keydown.space)=\"toggle(); $event.preventDefault()\"\n (blur)=\"handleBlur()\"\n >\n <div class=\"flex flex-row items-center gap-x-1 flex-wrap min-h-[1.5rem]\">\n @if (selectedOptions.length === 0) {\n <span class=\"text-base-content/50\">{{ uiConfig.placeholder || props.placeholder || 'Select...' }}</span>\n } @else {\n @for (opt of selectedOptions; track opt.value) {\n <span class=\"inline-flex items-center gap-x-1 bg-base-200 text-base-content text-xs px-2 py-0.5 rounded-md\">\n {{ opt.label }}\n <button\n mnButton\n [data]=\"{ size: 'sm', variant: 'text', color: 'secondary' }\"\n type=\"button\"\n class=\"text-base-content/50 hover:text-base-content cursor-pointer\"\n (click)=\"removeOption(opt, $event)\"\n [attr.aria-label]=\"'Remove ' + opt.label\"\n >\u00D7</button>\n </span>\n }\n }\n </div>\n <div class=\"absolute right-2 top-1/2 -translate-y-1/2 pointer-events-none\">\n <svg class=\"w-4 h-4 text-base-content/50\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 12 12\">\n <path fill=\"currentColor\" d=\"M6 8L1 3h10z\"/>\n </svg>\n </div>\n </div>\n\n <!-- Dropdown -->\n @if (isOpen) {\n <div\n class=\"fixed z-[9999] bg-base-100 border border-base-300 rounded-md shadow-lg max-h-60 overflow-auto\"\n [style.top]=\"dropdownStyle.top\"\n [style.left]=\"dropdownStyle.left\"\n [style.width]=\"dropdownStyle.width\"\n (click)=\"$event.stopPropagation()\"\n >\n @if (props.searchable) {\n <div class=\"p-2 border-b border-base-300\">\n <input\n type=\"text\"\n class=\"w-full p-1.5 text-sm border border-base-300 rounded-md outline-none focus:border-brand-500 bg-base-200 text-base-content placeholder-base-content/50\"\n [placeholder]=\"props.searchPlaceholder || 'Search...'\"\n [value]=\"searchTerm\"\n (input)=\"onSearch(($any($event.target)).value)\"\n (click)=\"$event.stopPropagation()\"\n />\n </div>\n }\n @for (opt of filteredOptions; track opt.value) {\n <div\n class=\"flex items-center gap-x-2 px-3 py-2 text-sm cursor-pointer text-base-content hover:bg-base-200\"\n [class.opacity-50]=\"opt.disabled || isMaxReached(opt)\"\n [class.pointer-events-none]=\"opt.disabled || isMaxReached(opt)\"\n (click)=\"toggleOption(opt); $event.stopPropagation()\"\n >\n <input\n type=\"checkbox\"\n class=\"w-4 h-4 accent-brand-500 pointer-events-none\"\n [checked]=\"isSelected(opt)\"\n [disabled]=\"opt.disabled || isMaxReached(opt)\"\n tabindex=\"-1\"\n />\n <span>{{ opt.label }}</span>\n </div>\n }\n @if (filteredOptions.length === 0) {\n <div class=\"px-3 py-2 text-sm text-base-content/50\">{{ uiConfig.noOptionsFound || 'No options found' }}</div>\n }\n </div>\n }\n\n @if (showError) {\n @if (props.showAllErrors) {\n <div class=\"flex flex-col gap-y-1 mt-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</div>\n" }]
|
|
2315
2340
|
}], ctorParameters: () => [{ type: i1$2.NgControl, decorators: [{
|
|
2316
2341
|
type: Optional
|
|
2317
2342
|
}, {
|
|
@@ -2319,9 +2344,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
2319
2344
|
}] }], propDecorators: { props: [{
|
|
2320
2345
|
type: Input,
|
|
2321
2346
|
args: [{ required: true }]
|
|
2347
|
+
}], triggerRef: [{
|
|
2348
|
+
type: ViewChild,
|
|
2349
|
+
args: ['trigger', { static: false }]
|
|
2322
2350
|
}], onDocumentClick: [{
|
|
2323
2351
|
type: HostListener,
|
|
2324
2352
|
args: ['document:click', ['$event']]
|
|
2353
|
+
}], onWindowScrollOrResize: [{
|
|
2354
|
+
type: HostListener,
|
|
2355
|
+
args: ['window:scroll', []]
|
|
2356
|
+
}, {
|
|
2357
|
+
type: HostListener,
|
|
2358
|
+
args: ['window:resize', []]
|
|
2325
2359
|
}] } });
|
|
2326
2360
|
|
|
2327
2361
|
// =========================
|
|
@@ -5026,10 +5060,15 @@ class MnModalShellComponent {
|
|
|
5026
5060
|
const stacked = this.isStacked ? ' is-stacked' : '';
|
|
5027
5061
|
return `modal-shell modal-${size}${closing}${animation}${stacked}`;
|
|
5028
5062
|
}
|
|
5063
|
+
/** Triggers the closing animation. Deferred to avoid NG0100 when called during a CD cycle. */
|
|
5029
5064
|
startClosing() {
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5065
|
+
return new Promise(resolve => {
|
|
5066
|
+
setTimeout(() => {
|
|
5067
|
+
this.isClosing = true;
|
|
5068
|
+
this.cdr.detectChanges();
|
|
5069
|
+
setTimeout(resolve, 150);
|
|
5070
|
+
});
|
|
5071
|
+
});
|
|
5033
5072
|
}
|
|
5034
5073
|
onEscapeKey(event) {
|
|
5035
5074
|
if (this.config.keyboard === KeyboardMode.ENABLED) {
|
|
@@ -5193,7 +5232,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
5193
5232
|
MnFormBodyComponent,
|
|
5194
5233
|
MnConfirmationBodyComponent,
|
|
5195
5234
|
MnCustomBodyHostComponent,
|
|
5196
|
-
MnButton,
|
|
5197
5235
|
MnFooterActionsComponent,
|
|
5198
5236
|
], template: "@if (showBackdrop) {\n <div class=\"modal-backdrop absolute inset-0 bg-black/50 animate-[fadeIn_0.2s_ease-in-out]\" (click)=\"onBackdropClick()\"></div>\n}\n\n<div\n class=\"modal-container relative bg-base-100 rounded-lg shadow-xl max-h-[90vh] overflow-hidden flex flex-col\"\n [ngClass]=\"[containerSizeClass, animationClass]\"\n [style.min-height]=\"containerHeightStyle\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-labelledby]=\"config.title ? 'mn-modal-title' : null\"\n [attr.aria-describedby]=\"config.description ? 'mn-modal-description' : null\"\n tabindex=\"-1\"\n (click)=\"$event.stopPropagation()\"\n>\n <div class=\"flex items-center justify-between p-6 border-b border-base-300\">\n <div class=\"flex flex-col gap-0.5\">\n @if (config.title) {\n <h2 class=\"m-0 text-xl font-semibold text-base-content\" id=\"mn-modal-title\">{{ config.title }}</h2>\n }\n @if (config.subtitle) {\n <p class=\"m-0 text-sm text-base-content/60 font-normal\">{{ config.subtitle }}</p>\n }\n </div>\n @if (showCloseButton) {\n <button\n class=\"bg-transparent border-none text-2xl cursor-pointer text-base-content/50 p-0 w-8 h-8 flex items-center justify-center rounded transition-colors hover:bg-base-200 hover:text-base-content\"\n (click)=\"onCloseButtonClick()\"\n aria-label=\"Close modal\"\n >\n \u00D7\n </button>\n }\n </div>\n @if (config.description) {\n <p class=\"m-0 px-6 text-sm text-base-content/60 leading-relaxed\" id=\"mn-modal-description\">{{ config.description }}</p>\n }\n\n <div class=\"flex-1 overflow-y-auto p-6\">\n @if (config.kind === ModalKind.WIZARD) {\n <mn-wizard-body\n [config]=\"asWizard(config)\"\n [modalRef]=\"asAny(modalRef)\"\n ></mn-wizard-body>\n }\n\n @if (config.kind === ModalKind.FORM) {\n <mn-form-body\n [config]=\"asForm(config)\"\n [modalRef]=\"asAny(modalRef)\"\n ></mn-form-body>\n }\n\n @if (config.kind === ModalKind.CONFIRMATION) {\n <mn-confirmation-body\n [config]=\"asConfirmation(config)\"\n [modalRef]=\"asAny(modalRef)\"\n ></mn-confirmation-body>\n }\n\n @if (config.kind === ModalKind.CUSTOM) {\n <mn-custom-body-host\n [config]=\"asCustom(config)\"\n [modalRef]=\"asAny(modalRef)\"\n ></mn-custom-body-host>\n }\n </div>\n\n <!-- Custom Footer Actions (not for wizard modals, they render their own) -->\n @if (hasCustomFooterActions && config.kind !== ModalKind.WIZARD) {\n <div class=\"flex gap-3 p-6 border-t border-base-300\">\n <mn-footer-actions\n [actions]=\"config.footerActions || []\"\n (actionClick)=\"onFooterAction($event)\"\n ></mn-footer-actions>\n </div>\n }\n</div>\n", styles: [":host{position:fixed;inset:0;z-index:1000;display:flex;align-items:center;justify-content:center;transition:transform .3s ease-in-out,filter .3s ease-in-out,opacity .3s ease-in-out}:host(.is-stacked){transform:scale(.96) translateY(-1rem);filter:brightness(.9) blur(1px);pointer-events:none;opacity:.8}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes slideIn{0%{opacity:0;transform:translateY(-1rem)}to{opacity:1;transform:translateY(0)}}@keyframes zoomIn{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}@keyframes fadeOut{0%{opacity:1}to{opacity:0}}@keyframes slideOut{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(1rem)}}@keyframes zoomOut{0%{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(.95)}}:host(.closing) .modal-backdrop{animation:fadeOut .15s ease-in-out forwards}:host(.closing).anim-slide .modal-container{animation:slideOut .15s ease-in-out forwards}:host(.closing).anim-fade .modal-container{animation:fadeOut .15s ease-in-out forwards}:host(.closing).anim-zoom .modal-container{animation:zoomOut .15s ease-in-out forwards}\n"] }]
|
|
5199
5237
|
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.ChangeDetectorRef }], propDecorators: { config: [{
|
|
@@ -5260,6 +5298,214 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
5260
5298
|
|
|
5261
5299
|
// Types
|
|
5262
5300
|
|
|
5301
|
+
class MnList {
|
|
5302
|
+
dataSource;
|
|
5303
|
+
selectionChange = new EventEmitter();
|
|
5304
|
+
itemClick = new EventEmitter();
|
|
5305
|
+
filteredItems = [];
|
|
5306
|
+
paginatedItems = [];
|
|
5307
|
+
searchValue = '';
|
|
5308
|
+
loadingMoreRows = false;
|
|
5309
|
+
selectedIds = new Set();
|
|
5310
|
+
currentPage = 1;
|
|
5311
|
+
pageSize = 10;
|
|
5312
|
+
cdr = inject(ChangeDetectorRef);
|
|
5313
|
+
dataSubscription;
|
|
5314
|
+
searchSubject = new Subject();
|
|
5315
|
+
searchSubscription;
|
|
5316
|
+
/** Tracks the previous toolbar template reference for change detection. */
|
|
5317
|
+
previousToolbarTemplate;
|
|
5318
|
+
ngDoCheck() {
|
|
5319
|
+
const currentTemplate = this.dataSource?.toolbarTemplate;
|
|
5320
|
+
if (currentTemplate !== this.previousToolbarTemplate) {
|
|
5321
|
+
this.previousToolbarTemplate = currentTemplate;
|
|
5322
|
+
this.cdr.markForCheck();
|
|
5323
|
+
}
|
|
5324
|
+
}
|
|
5325
|
+
ngOnInit() {
|
|
5326
|
+
this.pageSize = this.dataSource.pageSize ?? 10;
|
|
5327
|
+
// Pre-select items from initialSelectedIds if provided
|
|
5328
|
+
if (this.dataSource.initialSelectedIds?.length) {
|
|
5329
|
+
for (const id of this.dataSource.initialSelectedIds) {
|
|
5330
|
+
this.selectedIds.add(id);
|
|
5331
|
+
}
|
|
5332
|
+
this.emitSelection();
|
|
5333
|
+
}
|
|
5334
|
+
this.applyFilter(false);
|
|
5335
|
+
this.dataSubscription = this.dataSource.dataRows.pipe(skip(1)).subscribe(() => {
|
|
5336
|
+
this.applyFilter(false);
|
|
5337
|
+
this.cdr.markForCheck();
|
|
5338
|
+
});
|
|
5339
|
+
this.searchSubscription = this.searchSubject
|
|
5340
|
+
.pipe(debounceTime(300))
|
|
5341
|
+
.subscribe(value => {
|
|
5342
|
+
this.searchValue = value;
|
|
5343
|
+
this.applyFilter(true);
|
|
5344
|
+
this.cdr.markForCheck();
|
|
5345
|
+
});
|
|
5346
|
+
}
|
|
5347
|
+
ngOnDestroy() {
|
|
5348
|
+
this.dataSubscription?.unsubscribe();
|
|
5349
|
+
this.searchSubscription?.unsubscribe();
|
|
5350
|
+
}
|
|
5351
|
+
// ── Search ──
|
|
5352
|
+
onSearch(searchString) {
|
|
5353
|
+
this.currentPage = 1;
|
|
5354
|
+
this.searchSubject.next(searchString);
|
|
5355
|
+
}
|
|
5356
|
+
// ── Selection ──
|
|
5357
|
+
isSelected(item) {
|
|
5358
|
+
return this.selectedIds.has(this.dataSource.getID(item));
|
|
5359
|
+
}
|
|
5360
|
+
toggleItem(item) {
|
|
5361
|
+
const id = this.dataSource.getID(item);
|
|
5362
|
+
const mode = this.dataSource.selectionMode ?? 'none';
|
|
5363
|
+
if (mode === 'single') {
|
|
5364
|
+
this.selectedIds.clear();
|
|
5365
|
+
this.selectedIds.add(id);
|
|
5366
|
+
}
|
|
5367
|
+
else if (mode === 'multi') {
|
|
5368
|
+
if (this.selectedIds.has(id)) {
|
|
5369
|
+
this.selectedIds.delete(id);
|
|
5370
|
+
}
|
|
5371
|
+
else {
|
|
5372
|
+
this.selectedIds.add(id);
|
|
5373
|
+
}
|
|
5374
|
+
}
|
|
5375
|
+
this.emitSelection();
|
|
5376
|
+
}
|
|
5377
|
+
toggleAll() {
|
|
5378
|
+
if (this.selectedIds.size === this.filteredItems.length) {
|
|
5379
|
+
this.selectedIds.clear();
|
|
5380
|
+
}
|
|
5381
|
+
else {
|
|
5382
|
+
this.filteredItems.forEach(item => this.selectedIds.add(this.dataSource.getID(item)));
|
|
5383
|
+
}
|
|
5384
|
+
this.emitSelection();
|
|
5385
|
+
}
|
|
5386
|
+
get allSelected() {
|
|
5387
|
+
return this.filteredItems.length > 0 && this.selectedIds.size === this.filteredItems.length;
|
|
5388
|
+
}
|
|
5389
|
+
get hasSelection() {
|
|
5390
|
+
return (this.dataSource.selectionMode ?? 'none') !== 'none';
|
|
5391
|
+
}
|
|
5392
|
+
get isMultiSelect() {
|
|
5393
|
+
return this.dataSource.selectionMode === 'multi';
|
|
5394
|
+
}
|
|
5395
|
+
// ── Item interaction ──
|
|
5396
|
+
onItemClick(item) {
|
|
5397
|
+
this.dataSource.onItemClick?.(item);
|
|
5398
|
+
this.itemClick.emit(item);
|
|
5399
|
+
}
|
|
5400
|
+
// ── Pagination ──
|
|
5401
|
+
loadMoreRows() {
|
|
5402
|
+
if (!this.dataSource.loadAdditionalRows || this.loadingMoreRows)
|
|
5403
|
+
return;
|
|
5404
|
+
this.loadingMoreRows = true;
|
|
5405
|
+
const promise = (this.searchValue.length > 0 && this.dataSource.searchForAdditionalItems)
|
|
5406
|
+
? this.dataSource.searchForAdditionalItems(this.searchValue)
|
|
5407
|
+
: this.dataSource.loadAdditionalRows();
|
|
5408
|
+
promise
|
|
5409
|
+
.then(rows => this.processLoadedRows(rows))
|
|
5410
|
+
.catch(() => this.loadingMoreRows = false);
|
|
5411
|
+
}
|
|
5412
|
+
get showLoadMore() {
|
|
5413
|
+
const mode = this.dataSource.paginationMode ?? 'load-more';
|
|
5414
|
+
const strategy = this.dataSource.paginationStrategy;
|
|
5415
|
+
const hasMore = strategy ? strategy.hasMoreRows : !!this.dataSource.loadAdditionalRows;
|
|
5416
|
+
return mode === 'load-more' && hasMore;
|
|
5417
|
+
}
|
|
5418
|
+
// ── Paginated Mode ──
|
|
5419
|
+
get isPaginated() {
|
|
5420
|
+
return this.dataSource.paginationMode === 'paginated';
|
|
5421
|
+
}
|
|
5422
|
+
get totalPages() {
|
|
5423
|
+
return Math.max(1, Math.ceil(this.filteredItems.length / this.pageSize));
|
|
5424
|
+
}
|
|
5425
|
+
get resolvedPageSizeOptions() {
|
|
5426
|
+
return this.dataSource.pageSizeOptions ?? [5, 10, 25, 50];
|
|
5427
|
+
}
|
|
5428
|
+
goToPage(page) {
|
|
5429
|
+
if (page < 1 || page > this.totalPages)
|
|
5430
|
+
return;
|
|
5431
|
+
this.currentPage = page;
|
|
5432
|
+
this.applyPagination();
|
|
5433
|
+
this.cdr.markForCheck();
|
|
5434
|
+
}
|
|
5435
|
+
onPageSizeChange(newSize) {
|
|
5436
|
+
this.pageSize = newSize;
|
|
5437
|
+
this.currentPage = 1;
|
|
5438
|
+
this.applyPagination();
|
|
5439
|
+
this.cdr.markForCheck();
|
|
5440
|
+
}
|
|
5441
|
+
get visiblePages() {
|
|
5442
|
+
const total = this.totalPages;
|
|
5443
|
+
const current = this.currentPage;
|
|
5444
|
+
const delta = 2;
|
|
5445
|
+
const pages = [];
|
|
5446
|
+
for (let i = Math.max(1, current - delta); i <= Math.min(total, current + delta); i++) {
|
|
5447
|
+
pages.push(i);
|
|
5448
|
+
}
|
|
5449
|
+
return pages;
|
|
5450
|
+
}
|
|
5451
|
+
trackByID = (_index, item) => {
|
|
5452
|
+
return this.dataSource.getID(item);
|
|
5453
|
+
};
|
|
5454
|
+
// ── Skeleton rows for loading ──
|
|
5455
|
+
get skeletonRows() {
|
|
5456
|
+
return Array.from({ length: 5 });
|
|
5457
|
+
}
|
|
5458
|
+
// ── Private ──
|
|
5459
|
+
applyPagination() {
|
|
5460
|
+
if (this.isPaginated) {
|
|
5461
|
+
if (this.currentPage > this.totalPages) {
|
|
5462
|
+
this.currentPage = this.totalPages;
|
|
5463
|
+
}
|
|
5464
|
+
const start = (this.currentPage - 1) * this.pageSize;
|
|
5465
|
+
this.paginatedItems = this.filteredItems.slice(start, start + this.pageSize);
|
|
5466
|
+
}
|
|
5467
|
+
else {
|
|
5468
|
+
this.paginatedItems = this.filteredItems;
|
|
5469
|
+
}
|
|
5470
|
+
}
|
|
5471
|
+
applyFilter(searchForItems) {
|
|
5472
|
+
let items = this.dataSource.dataRows.value;
|
|
5473
|
+
// Global search filter
|
|
5474
|
+
if (this.dataSource.isInSearch && this.dataSource.canSearch && this.searchValue.length > 0) {
|
|
5475
|
+
const term = this.searchValue.toLowerCase();
|
|
5476
|
+
items = items.filter(row => this.dataSource.isInSearch(row, term));
|
|
5477
|
+
}
|
|
5478
|
+
this.filteredItems = items;
|
|
5479
|
+
this.applyPagination();
|
|
5480
|
+
if (searchForItems) {
|
|
5481
|
+
this.loadMoreRows();
|
|
5482
|
+
}
|
|
5483
|
+
}
|
|
5484
|
+
processLoadedRows(rows) {
|
|
5485
|
+
const merged = [...new Map([...this.dataSource.dataRows.value, ...rows].map(item => [this.dataSource.getID(item), item])).values()];
|
|
5486
|
+
this.dataSource.dataRows.next(merged);
|
|
5487
|
+
this.loadingMoreRows = false;
|
|
5488
|
+
this.applyFilter(false);
|
|
5489
|
+
}
|
|
5490
|
+
emitSelection() {
|
|
5491
|
+
const rows = this.dataSource.dataRows.value.filter(r => this.selectedIds.has(this.dataSource.getID(r)));
|
|
5492
|
+
this.dataSource.selectedRows?.next(rows);
|
|
5493
|
+
this.selectionChange.emit(rows);
|
|
5494
|
+
}
|
|
5495
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: MnList, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5496
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: MnList, isStandalone: true, selector: "mn-list", inputs: { dataSource: "dataSource" }, outputs: { selectionChange: "selectionChange", itemClick: "itemClick" }, ngImport: i0, template: "<!-- Toolbar: search + custom toolbar template -->\n<div class=\"flex flex-row items-center justify-end gap-2 mb-3\">\n @if (dataSource.canSearch) {\n <div>\n <input\n type=\"text\"\n class=\"input input-sm rounded border border-base-300 bg-base-200 px-3 py-1.5 text-sm text-base-content placeholder-base-content/50 focus:outline-none focus:border-brand-500 w-full max-w-xs\"\n [placeholder]=\"dataSource.searchPlaceholder ?? 'Search...'\"\n (input)=\"onSearch($any($event.target).value)\"\n aria-label=\"Search list\"\n />\n </div>\n }\n @if (dataSource.toolbarTemplate) {\n <ng-container [ngTemplateOutlet]=\"dataSource.toolbarTemplate\"></ng-container>\n }\n</div>\n\n<!-- List wrapper -->\n<div\n class=\"w-full\"\n [class.border]=\"dataSource.appearance?.bordered\"\n [class.border-base-300]=\"dataSource.appearance?.bordered\"\n [class.rounded]=\"dataSource.appearance?.bordered\"\n role=\"list\"\n aria-label=\"Data list\"\n>\n <!-- Loading state -->\n @if (dataSource.isDataLoading) {\n @for (_ of skeletonRows; track $index) {\n <div class=\"animate-pulse px-4 py-3\" [class.py-2]=\"dataSource.appearance?.compact\" role=\"listitem\">\n <div class=\"h-4 w-3/4 rounded bg-base-300 mb-1\"></div>\n <div class=\"h-3 w-1/2 rounded bg-base-300\"></div>\n </div>\n @if (!$last && (dataSource.appearance?.dividers !== false)) {\n <div class=\"border-b border-base-300\"></div>\n }\n }\n } @else {\n <!-- Empty state -->\n @if (filteredItems.length === 0) {\n <div class=\"text-center text-xs py-8\" role=\"listitem\">\n @if (dataSource.emptyTemplate) {\n <ng-container [ngTemplateOutlet]=\"dataSource.emptyTemplate\"></ng-container>\n } @else {\n <div class=\"flex flex-col items-center gap-2 text-base-content/50\">\n <p class=\"text-sm\">{{ dataSource.emptyMessage }}</p>\n </div>\n }\n </div>\n }\n\n <!-- Select all (multi-select) -->\n @if (isMultiSelect && filteredItems.length > 0) {\n <div class=\"flex items-center gap-2 px-4 py-2 bg-base-200 text-sm\">\n <label class=\"flex items-center gap-2 cursor-pointer\">\n <input\n type=\"checkbox\"\n class=\"checkbox checkbox-sm checkbox-primary\"\n [checked]=\"allSelected\"\n (change)=\"toggleAll()\"\n aria-label=\"Select all items\"\n />\n <span class=\"text-xs text-base-content/70\">Select all</span>\n </label>\n </div>\n @if (dataSource.appearance?.dividers !== false) {\n <div class=\"border-b border-base-300\"></div>\n }\n }\n\n <!-- Data items -->\n @for (item of paginatedItems; track trackByID($index, item); let odd = $odd; let last = $last) {\n <div\n class=\"flex items-center gap-2 bg-base-100 transition-colors duration-150\"\n [ngClass]=\"{'bg-primary/10': isSelected(item)}\"\n [class.bg-base-200]=\"!isSelected(item) && odd && dataSource.appearance?.dividers !== false\"\n [class.hover:bg-base-200]=\"dataSource.appearance?.hover !== false\"\n [class.cursor-pointer]=\"!!dataSource.onItemClick\"\n [class.px-4]=\"true\"\n [class.py-3]=\"!dataSource.appearance?.compact\"\n [class.py-2]=\"dataSource.appearance?.compact\"\n role=\"listitem\"\n (click)=\"onItemClick(item)\"\n >\n <!-- Selection checkbox -->\n @if (hasSelection) {\n <div class=\"flex-shrink-0\">\n <label>\n <input\n type=\"checkbox\"\n class=\"checkbox checkbox-sm checkbox-primary\"\n [checked]=\"isSelected(item)\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleItem(item)\"\n />\n </label>\n </div>\n }\n\n <!-- Item content via template -->\n <div class=\"flex-1 min-w-0\">\n <ng-container\n [ngTemplateOutlet]=\"dataSource.itemTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: item, data: item }\"\n ></ng-container>\n </div>\n </div>\n @if (!last && (dataSource.appearance?.dividers !== false)) {\n <div class=\"border-b border-base-300\"></div>\n }\n }\n }\n</div>\n\n<!-- Load more button -->\n@if (showLoadMore) {\n <div class=\"flex justify-center py-4\">\n <button\n mnButton\n [data]=\"{ size: 'sm', variant: 'outline', color: 'primary' }\"\n class=\"px-4 py-1.5 text-sm rounded border border-brand-500 text-brand-500 hover:bg-brand-100 transition-colors disabled:opacity-50\"\n (click)=\"loadMoreRows()\"\n [disabled]=\"loadingMoreRows\"\n >\n @if (loadingMoreRows) {\n <span class=\"inline-block w-3 h-3 border-2 border-brand-500 border-t-transparent rounded-full animate-spin mr-2\"></span>\n }\n {{ dataSource.labels?.loadMore || 'Load more' }}\n </button>\n </div>\n}\n\n<!-- Pagination controls -->\n@if (isPaginated && totalPages > 1) {\n <div class=\"flex items-center justify-between px-2 py-3 text-sm text-base-content\">\n <div class=\"flex items-center gap-2\">\n <span>{{ dataSource.labels?.rowsPerPage || 'Items per page:' }}</span>\n <select\n class=\"select select-xs rounded border border-base-300 bg-base-200 px-2 py-1 text-xs focus:outline-none focus:border-brand-500\"\n [value]=\"pageSize\"\n (change)=\"onPageSizeChange(+$any($event.target).value)\"\n aria-label=\"Items per page\"\n >\n @for (opt of resolvedPageSizeOptions; track opt) {\n <option [value]=\"opt\" [selected]=\"opt === pageSize\">{{ opt }}</option>\n }\n </select>\n </div>\n\n <div class=\"flex items-center gap-1\">\n <span class=\"text-xs mr-2\">{{ (currentPage - 1) * pageSize + 1 }}\u2013{{ currentPage * pageSize > filteredItems.length ? filteredItems.length : currentPage * pageSize }} of {{ filteredItems.length }}</span>\n\n <button\n mnButton\n [data]=\"{ size: 'sm', variant: 'outline', color: 'secondary' }\"\n class=\"px-2 py-1 rounded border border-base-300 hover:bg-base-200 transition-colors disabled:opacity-40 disabled:cursor-not-allowed\"\n [disabled]=\"currentPage === 1\"\n (click)=\"goToPage(1)\"\n aria-label=\"First page\"\n >\u00AB</button>\n\n <button\n mnButton\n [data]=\"{ size: 'sm', variant: 'outline', color: 'secondary' }\"\n class=\"px-2 py-1 rounded border border-base-300 hover:bg-base-200 transition-colors disabled:opacity-40 disabled:cursor-not-allowed\"\n [disabled]=\"currentPage === 1\"\n (click)=\"goToPage(currentPage - 1)\"\n aria-label=\"Previous page\"\n >\u2039</button>\n\n @for (page of visiblePages; track page) {\n <button\n mnButton\n [data]=\"{ size: 'sm', variant: 'outline', color: 'secondary' }\"\n class=\"px-2.5 py-1 rounded border transition-colors text-xs\"\n [class.border-brand-500]=\"page === currentPage\"\n [class.bg-brand-500]=\"page === currentPage\"\n [class.text-white]=\"page === currentPage\"\n [class.border-base-300]=\"page !== currentPage\"\n [class.hover:bg-base-200]=\"page !== currentPage\"\n (click)=\"goToPage(page)\"\n [attr.aria-label]=\"'Page ' + page\"\n [attr.aria-current]=\"page === currentPage ? 'page' : null\"\n >{{ page }}</button>\n }\n\n <button\n mnButton\n [data]=\"{ size: 'sm', variant: 'outline', color: 'secondary' }\"\n class=\"px-2 py-1 rounded border border-base-300 hover:bg-base-200 transition-colors disabled:opacity-40 disabled:cursor-not-allowed\"\n [disabled]=\"currentPage === totalPages\"\n (click)=\"goToPage(currentPage + 1)\"\n aria-label=\"Next page\"\n >\u203A</button>\n\n <button\n mnButton\n [data]=\"{ size: 'sm', variant: 'outline', color: 'secondary' }\"\n class=\"px-2 py-1 rounded border border-base-300 hover:bg-base-200 transition-colors disabled:opacity-40 disabled:cursor-not-allowed\"\n [disabled]=\"currentPage === totalPages\"\n (click)=\"goToPage(totalPages)\"\n aria-label=\"Last page\"\n >\u00BB</button>\n </div>\n </div>\n}\n", styles: [""], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: MnButton, selector: "button[mnButton], a[mnButton]", inputs: ["data"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5497
|
+
}
|
|
5498
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: MnList, decorators: [{
|
|
5499
|
+
type: Component,
|
|
5500
|
+
args: [{ selector: 'mn-list', standalone: true, imports: [NgClass, NgTemplateOutlet, MnButton], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- Toolbar: search + custom toolbar template -->\n<div class=\"flex flex-row items-center justify-end gap-2 mb-3\">\n @if (dataSource.canSearch) {\n <div>\n <input\n type=\"text\"\n class=\"input input-sm rounded border border-base-300 bg-base-200 px-3 py-1.5 text-sm text-base-content placeholder-base-content/50 focus:outline-none focus:border-brand-500 w-full max-w-xs\"\n [placeholder]=\"dataSource.searchPlaceholder ?? 'Search...'\"\n (input)=\"onSearch($any($event.target).value)\"\n aria-label=\"Search list\"\n />\n </div>\n }\n @if (dataSource.toolbarTemplate) {\n <ng-container [ngTemplateOutlet]=\"dataSource.toolbarTemplate\"></ng-container>\n }\n</div>\n\n<!-- List wrapper -->\n<div\n class=\"w-full\"\n [class.border]=\"dataSource.appearance?.bordered\"\n [class.border-base-300]=\"dataSource.appearance?.bordered\"\n [class.rounded]=\"dataSource.appearance?.bordered\"\n role=\"list\"\n aria-label=\"Data list\"\n>\n <!-- Loading state -->\n @if (dataSource.isDataLoading) {\n @for (_ of skeletonRows; track $index) {\n <div class=\"animate-pulse px-4 py-3\" [class.py-2]=\"dataSource.appearance?.compact\" role=\"listitem\">\n <div class=\"h-4 w-3/4 rounded bg-base-300 mb-1\"></div>\n <div class=\"h-3 w-1/2 rounded bg-base-300\"></div>\n </div>\n @if (!$last && (dataSource.appearance?.dividers !== false)) {\n <div class=\"border-b border-base-300\"></div>\n }\n }\n } @else {\n <!-- Empty state -->\n @if (filteredItems.length === 0) {\n <div class=\"text-center text-xs py-8\" role=\"listitem\">\n @if (dataSource.emptyTemplate) {\n <ng-container [ngTemplateOutlet]=\"dataSource.emptyTemplate\"></ng-container>\n } @else {\n <div class=\"flex flex-col items-center gap-2 text-base-content/50\">\n <p class=\"text-sm\">{{ dataSource.emptyMessage }}</p>\n </div>\n }\n </div>\n }\n\n <!-- Select all (multi-select) -->\n @if (isMultiSelect && filteredItems.length > 0) {\n <div class=\"flex items-center gap-2 px-4 py-2 bg-base-200 text-sm\">\n <label class=\"flex items-center gap-2 cursor-pointer\">\n <input\n type=\"checkbox\"\n class=\"checkbox checkbox-sm checkbox-primary\"\n [checked]=\"allSelected\"\n (change)=\"toggleAll()\"\n aria-label=\"Select all items\"\n />\n <span class=\"text-xs text-base-content/70\">Select all</span>\n </label>\n </div>\n @if (dataSource.appearance?.dividers !== false) {\n <div class=\"border-b border-base-300\"></div>\n }\n }\n\n <!-- Data items -->\n @for (item of paginatedItems; track trackByID($index, item); let odd = $odd; let last = $last) {\n <div\n class=\"flex items-center gap-2 bg-base-100 transition-colors duration-150\"\n [ngClass]=\"{'bg-primary/10': isSelected(item)}\"\n [class.bg-base-200]=\"!isSelected(item) && odd && dataSource.appearance?.dividers !== false\"\n [class.hover:bg-base-200]=\"dataSource.appearance?.hover !== false\"\n [class.cursor-pointer]=\"!!dataSource.onItemClick\"\n [class.px-4]=\"true\"\n [class.py-3]=\"!dataSource.appearance?.compact\"\n [class.py-2]=\"dataSource.appearance?.compact\"\n role=\"listitem\"\n (click)=\"onItemClick(item)\"\n >\n <!-- Selection checkbox -->\n @if (hasSelection) {\n <div class=\"flex-shrink-0\">\n <label>\n <input\n type=\"checkbox\"\n class=\"checkbox checkbox-sm checkbox-primary\"\n [checked]=\"isSelected(item)\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleItem(item)\"\n />\n </label>\n </div>\n }\n\n <!-- Item content via template -->\n <div class=\"flex-1 min-w-0\">\n <ng-container\n [ngTemplateOutlet]=\"dataSource.itemTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: item, data: item }\"\n ></ng-container>\n </div>\n </div>\n @if (!last && (dataSource.appearance?.dividers !== false)) {\n <div class=\"border-b border-base-300\"></div>\n }\n }\n }\n</div>\n\n<!-- Load more button -->\n@if (showLoadMore) {\n <div class=\"flex justify-center py-4\">\n <button\n mnButton\n [data]=\"{ size: 'sm', variant: 'outline', color: 'primary' }\"\n class=\"px-4 py-1.5 text-sm rounded border border-brand-500 text-brand-500 hover:bg-brand-100 transition-colors disabled:opacity-50\"\n (click)=\"loadMoreRows()\"\n [disabled]=\"loadingMoreRows\"\n >\n @if (loadingMoreRows) {\n <span class=\"inline-block w-3 h-3 border-2 border-brand-500 border-t-transparent rounded-full animate-spin mr-2\"></span>\n }\n {{ dataSource.labels?.loadMore || 'Load more' }}\n </button>\n </div>\n}\n\n<!-- Pagination controls -->\n@if (isPaginated && totalPages > 1) {\n <div class=\"flex items-center justify-between px-2 py-3 text-sm text-base-content\">\n <div class=\"flex items-center gap-2\">\n <span>{{ dataSource.labels?.rowsPerPage || 'Items per page:' }}</span>\n <select\n class=\"select select-xs rounded border border-base-300 bg-base-200 px-2 py-1 text-xs focus:outline-none focus:border-brand-500\"\n [value]=\"pageSize\"\n (change)=\"onPageSizeChange(+$any($event.target).value)\"\n aria-label=\"Items per page\"\n >\n @for (opt of resolvedPageSizeOptions; track opt) {\n <option [value]=\"opt\" [selected]=\"opt === pageSize\">{{ opt }}</option>\n }\n </select>\n </div>\n\n <div class=\"flex items-center gap-1\">\n <span class=\"text-xs mr-2\">{{ (currentPage - 1) * pageSize + 1 }}\u2013{{ currentPage * pageSize > filteredItems.length ? filteredItems.length : currentPage * pageSize }} of {{ filteredItems.length }}</span>\n\n <button\n mnButton\n [data]=\"{ size: 'sm', variant: 'outline', color: 'secondary' }\"\n class=\"px-2 py-1 rounded border border-base-300 hover:bg-base-200 transition-colors disabled:opacity-40 disabled:cursor-not-allowed\"\n [disabled]=\"currentPage === 1\"\n (click)=\"goToPage(1)\"\n aria-label=\"First page\"\n >\u00AB</button>\n\n <button\n mnButton\n [data]=\"{ size: 'sm', variant: 'outline', color: 'secondary' }\"\n class=\"px-2 py-1 rounded border border-base-300 hover:bg-base-200 transition-colors disabled:opacity-40 disabled:cursor-not-allowed\"\n [disabled]=\"currentPage === 1\"\n (click)=\"goToPage(currentPage - 1)\"\n aria-label=\"Previous page\"\n >\u2039</button>\n\n @for (page of visiblePages; track page) {\n <button\n mnButton\n [data]=\"{ size: 'sm', variant: 'outline', color: 'secondary' }\"\n class=\"px-2.5 py-1 rounded border transition-colors text-xs\"\n [class.border-brand-500]=\"page === currentPage\"\n [class.bg-brand-500]=\"page === currentPage\"\n [class.text-white]=\"page === currentPage\"\n [class.border-base-300]=\"page !== currentPage\"\n [class.hover:bg-base-200]=\"page !== currentPage\"\n (click)=\"goToPage(page)\"\n [attr.aria-label]=\"'Page ' + page\"\n [attr.aria-current]=\"page === currentPage ? 'page' : null\"\n >{{ page }}</button>\n }\n\n <button\n mnButton\n [data]=\"{ size: 'sm', variant: 'outline', color: 'secondary' }\"\n class=\"px-2 py-1 rounded border border-base-300 hover:bg-base-200 transition-colors disabled:opacity-40 disabled:cursor-not-allowed\"\n [disabled]=\"currentPage === totalPages\"\n (click)=\"goToPage(currentPage + 1)\"\n aria-label=\"Next page\"\n >\u203A</button>\n\n <button\n mnButton\n [data]=\"{ size: 'sm', variant: 'outline', color: 'secondary' }\"\n class=\"px-2 py-1 rounded border border-base-300 hover:bg-base-200 transition-colors disabled:opacity-40 disabled:cursor-not-allowed\"\n [disabled]=\"currentPage === totalPages\"\n (click)=\"goToPage(totalPages)\"\n aria-label=\"Last page\"\n >\u00BB</button>\n </div>\n </div>\n}\n" }]
|
|
5501
|
+
}], propDecorators: { dataSource: [{
|
|
5502
|
+
type: Input
|
|
5503
|
+
}], selectionChange: [{
|
|
5504
|
+
type: Output
|
|
5505
|
+
}], itemClick: [{
|
|
5506
|
+
type: Output
|
|
5507
|
+
}] } });
|
|
5508
|
+
|
|
5263
5509
|
/**
|
|
5264
5510
|
* Available calendar view modes.
|
|
5265
5511
|
*/
|
|
@@ -7130,5 +7376,5 @@ function enableMnPreviewMode(configService, langService, allowedOrigins) {
|
|
|
7130
7376
|
* Generated bundle index. Do not edit.
|
|
7131
7377
|
*/
|
|
7132
7378
|
|
|
7133
|
-
export { API_BASE_URL, ActionStyle, BackdropMode, BaseModalBuilder, CALENDAR_CONFIG, CALENDAR_DATE_FORMATTER, CalendarDayComponent, CalendarEventComponent, CalendarEventDefaultComponent, CalendarEventLayoutService, CalendarMonthComponent, CalendarUtility, CalendarView, CalendarViewComponent, CalendarWeekComponent, CloseMode, ColumnSortType, ConfirmationModalBuilder, ConfirmationTone, CrudService, CustomModalBuilder, DEFAULT_CALENDAR_CONFIG, DEFAULT_MN_ALERT_CONFIG, DefaultCalendarDateFormatter, FieldAppearance, FieldKind, FormLayoutMode, FormModalBuilder, KeyboardMode, MN_ALERT_CONFIG, MN_CALENDAR_COMPONENT_NAME, MN_CALENDAR_CONFIG, MN_CHECKBOX_CONFIG, MN_DATETIME_CONFIG, MN_INPUT_FIELD_CONFIG, MN_INSTANCE_ID, MN_LIB_DUAL_HORIZONTAL_IMAGE, MN_MULTI_SELECT_CONFIG, MN_SECTION_PATH, MN_TEXTAREA_CONFIG, MnAlertOutletComponent, MnAlertService, MnAlertStore, MnButton, MnCheckbox, MnConfigService, MnConfirmationBodyComponent, MnCustomBodyHostComponent, MnDatetime, MnDualHorizontalImage, MnFormBodyComponent, MnHiddenBelowDirective, MnInformationCard, MnInputField, MnInstanceDirective, MnLanguageService, MnModalRef, MnModalService, MnModalShellComponent, MnMultiSelect, MnSectionDirective, MnTabComponent, MnTable, MnTextarea, MnTranslatePipe, MnWizardBodyComponent, ModalBuilder, ModalCloseReason, ModalIntent, ModalKind, ModalSize, NavigationDirection, OptionState, SelectionMode, StepBuilder, StepState, SubmitMode, UpcomingEventRowComponent, UpcomingEventsComponent, ValidationCode, ValidationStatus, WizardFlowMode, WizardModalBuilder, dateTimeAdapter, defaultTextAdapter, enableMnPreviewMode, isTranslatable, mnAlertVariants, mnButtonVariants, mnCheckboxVariants, mnDatetimeVariants, mnInformationCardVariants, mnInputFieldVariants, mnMultiSelectVariants, mnTextareaVariants, numberAdapter, pickAdapter, provideMnAlerts, provideMnCalendarConfig, provideMnComponentConfig, provideMnConfig, provideMnLanguage, resolveCalendarConfig };
|
|
7379
|
+
export { API_BASE_URL, ActionStyle, BackdropMode, BaseModalBuilder, CALENDAR_CONFIG, CALENDAR_DATE_FORMATTER, CalendarDayComponent, CalendarEventComponent, CalendarEventDefaultComponent, CalendarEventLayoutService, CalendarMonthComponent, CalendarUtility, CalendarView, CalendarViewComponent, CalendarWeekComponent, CloseMode, ColumnSortType, ConfirmationModalBuilder, ConfirmationTone, CrudService, CustomModalBuilder, DEFAULT_CALENDAR_CONFIG, DEFAULT_MN_ALERT_CONFIG, DefaultCalendarDateFormatter, FieldAppearance, FieldKind, FormLayoutMode, FormModalBuilder, KeyboardMode, MN_ALERT_CONFIG, MN_CALENDAR_COMPONENT_NAME, MN_CALENDAR_CONFIG, MN_CHECKBOX_CONFIG, MN_DATETIME_CONFIG, MN_INPUT_FIELD_CONFIG, MN_INSTANCE_ID, MN_LIB_DUAL_HORIZONTAL_IMAGE, MN_MULTI_SELECT_CONFIG, MN_SECTION_PATH, MN_TEXTAREA_CONFIG, MnAlertOutletComponent, MnAlertService, MnAlertStore, MnButton, MnCheckbox, MnConfigService, MnConfirmationBodyComponent, MnCustomBodyHostComponent, MnDatetime, MnDualHorizontalImage, MnFormBodyComponent, MnHiddenBelowDirective, MnInformationCard, MnInputField, MnInstanceDirective, MnLanguageService, MnList, MnModalRef, MnModalService, MnModalShellComponent, MnMultiSelect, MnSectionDirective, MnTabComponent, MnTable, MnTextarea, MnTranslatePipe, MnWizardBodyComponent, ModalBuilder, ModalCloseReason, ModalIntent, ModalKind, ModalSize, NavigationDirection, OptionState, SelectionMode, StepBuilder, StepState, SubmitMode, UpcomingEventRowComponent, UpcomingEventsComponent, ValidationCode, ValidationStatus, WizardFlowMode, WizardModalBuilder, dateTimeAdapter, defaultTextAdapter, enableMnPreviewMode, isTranslatable, mnAlertVariants, mnButtonVariants, mnCheckboxVariants, mnDatetimeVariants, mnInformationCardVariants, mnInputFieldVariants, mnMultiSelectVariants, mnTextareaVariants, numberAdapter, pickAdapter, provideMnAlerts, provideMnCalendarConfig, provideMnComponentConfig, provideMnConfig, provideMnLanguage, resolveCalendarConfig };
|
|
7134
7380
|
//# sourceMappingURL=mn-angular-lib.mjs.map
|