mn-angular-lib 0.0.89 → 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.
|
@@ -5232,7 +5232,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
5232
5232
|
MnFormBodyComponent,
|
|
5233
5233
|
MnConfirmationBodyComponent,
|
|
5234
5234
|
MnCustomBodyHostComponent,
|
|
5235
|
-
MnButton,
|
|
5236
5235
|
MnFooterActionsComponent,
|
|
5237
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"] }]
|
|
5238
5237
|
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.ChangeDetectorRef }], propDecorators: { config: [{
|
|
@@ -5299,6 +5298,214 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
5299
5298
|
|
|
5300
5299
|
// Types
|
|
5301
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
|
+
|
|
5302
5509
|
/**
|
|
5303
5510
|
* Available calendar view modes.
|
|
5304
5511
|
*/
|
|
@@ -7169,5 +7376,5 @@ function enableMnPreviewMode(configService, langService, allowedOrigins) {
|
|
|
7169
7376
|
* Generated bundle index. Do not edit.
|
|
7170
7377
|
*/
|
|
7171
7378
|
|
|
7172
|
-
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 };
|
|
7173
7380
|
//# sourceMappingURL=mn-angular-lib.mjs.map
|