mn-angular-lib 1.0.149 → 1.0.151

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.
@@ -5093,11 +5093,11 @@ class MnDropdown {
5093
5093
  return this.datasource.id ?? this.autoId;
5094
5094
  }
5095
5095
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: MnDropdown, deps: [], target: i0.ɵɵFactoryTarget.Component });
5096
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: MnDropdown, isStandalone: true, selector: "mn-lib-dropdown", inputs: { datasource: "datasource" }, host: { listeners: { "document:click": "onDocumentClick($event)", "document:keydown.escape": "onEscape()", "window:scroll": "onWindowScrollOrResize()", "window:resize": "onWindowScrollOrResize()" } }, viewQueries: [{ propertyName: "triggerRef", first: true, predicate: ["trigger"], descendants: true, read: ElementRef }, { propertyName: "dropdownRef", first: true, predicate: ["dropdown"], descendants: true }, { propertyName: "sheetRef", first: true, predicate: ["sheet"], descendants: true, read: ElementRef }], ngImport: i0, template: "<div class=\"relative inline-flex\">\n <!-- Trigger -->\n <button\n #trigger\n mnButton\n type=\"button\"\n [id]=\"resolvedId\"\n [data]=\"triggerData\"\n [ngClass]=\"triggerClasses\"\n [attr.aria-label]=\"triggerLabelText ? null : triggerAriaLabel\"\n [attr.aria-haspopup]=\"'menu'\"\n [attr.aria-expanded]=\"isOpen\"\n [attr.aria-controls]=\"isOpen ? resolvedId + '-menu' : null\"\n (click)=\"$event.stopPropagation(); toggle()\"\n >\n @if (triggerLabelText) {\n <span class=\"truncate\">{{ triggerLabelText }}</span>\n }\n <!-- One glyph, rendered like a menu item's icon: a caller's template, else lucide data\n (a preset resolves to its own data). No per-preset switch. -->\n @if (triggerIconTemplate; as tpl) {\n <span class=\"shrink-0 inline-flex items-center\">\n <ng-container [ngTemplateOutlet]=\"tpl\"></ng-container>\n </span>\n } @else if (triggerIconData; as glyph) {\n <span class=\"shrink-0 inline-flex items-center\">\n <svg [lucideIcon]=\"glyph.data\" [size]=\"glyph.size\" [class.opacity-70]=\"glyph.dim\"></svg>\n </span>\n }\n </button>\n\n <!-- Menu -->\n @if (isOpen) {\n @if (isSheet) {\n <!-- On mobile the menu is presented as the shared bottom sheet: chrome (backdrop,\n grabber, swipe-to-dismiss, slide animation) lives in mn-bottom-sheet; this\n component only projects the item list into it. The host is portalled to\n document.body (see the `sheet` ViewChild). -->\n <mn-bottom-sheet\n #sheet\n (dismiss)=\"close()\"\n [ariaLabel]=\"menuLabel || triggerAriaLabel\"\n [maxHeightVh]=\"80\"\n [minHeightPx]=\"sheetFloorPx\"\n >\n <div [id]=\"resolvedId + '-menu'\" role=\"menu\" class=\"flex flex-col flex-1 min-h-0 overflow-hidden\">\n <!-- The sheet covers its own trigger, so it needs a header and an explicit way out. -->\n <div class=\"flex items-center justify-between gap-x-2 px-4 pt-1 pb-2 shrink-0\">\n <p class=\"text-base font-bold text-base-content truncate\">{{ menuLabel || triggerAriaLabel }}</p>\n <button\n mnButton\n type=\"button\"\n [data]=\"{ size: 'sm', variant: 'text', color: 'secondary' }\"\n class=\"shrink-0 cursor-pointer\"\n (click)=\"close()\"\n [attr.aria-label]=\"closeLabel\"\n ><svg lucideX [size]=\"20\"></svg></button>\n </div>\n <ng-container [ngTemplateOutlet]=\"searchBox\" [ngTemplateOutletContext]=\"{ sheet: true }\"></ng-container>\n <div class=\"flex-1 flex flex-col overflow-auto overscroll-contain\">\n <ng-container [ngTemplateOutlet]=\"items\" [ngTemplateOutletContext]=\"{ sheet: true }\"></ng-container>\n </div>\n </div>\n </mn-bottom-sheet>\n } @else {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <div\n #dropdown\n (click)=\"$event.stopPropagation()\"\n [id]=\"resolvedId + '-menu'\"\n [ngClass]=\"panelClasses\"\n [style.left]=\"dropdownStyle.left\"\n [style.top]=\"dropdownStyle.top\"\n [style.height.px]=\"panelFloorPx\"\n role=\"menu\"\n >\n @if (menuLabel) {\n <p class=\"px-3 pt-1 pb-1.5 text-xs font-medium text-base-content/50 truncate shrink-0\">{{ menuLabel }}</p>\n }\n <ng-container [ngTemplateOutlet]=\"searchBox\" [ngTemplateOutletContext]=\"{ sheet: false }\"></ng-container>\n @if (isSearchable) {\n <!-- Its own scroll region so the pinned search box above stays fixed and the\n locked panel height (panelFloorPx) doesn't change as the list filters. The\n flex column lets the empty state fill and centre in the reserved space. -->\n <div class=\"flex-1 min-h-0 flex flex-col overflow-auto\">\n <ng-container [ngTemplateOutlet]=\"items\" [ngTemplateOutletContext]=\"{ sheet: false }\"></ng-container>\n </div>\n } @else {\n <ng-container [ngTemplateOutlet]=\"items\" [ngTemplateOutletContext]=\"{ sheet: false }\"></ng-container>\n }\n </div>\n }\n }\n\n <!-- The item list, shared verbatim by the sheet and the anchored popover. `sheet`\n only tunes the spacing so touch targets are roomier on mobile. -->\n <ng-template #items let-sheet=\"sheet\">\n @for (item of filteredActions; track $index) {\n @if (asAction(item); as action) {\n <button\n type=\"button\"\n role=\"menuitem\"\n [disabled]=\"action.disabled\"\n [class.opacity-50]=\"action.disabled\"\n [class.pointer-events-none]=\"action.disabled\"\n [ngClass]=\"[actionColorClass(action), sheet ? 'px-4 py-3 text-base' : 'px-3 py-2 text-sm']\"\n class=\"flex w-full shrink-0 items-center gap-x-2.5 text-left cursor-pointer hover:bg-base-200 focus-visible:bg-base-200 focus:outline-none transition-colors\"\n (click)=\"select(action)\"\n >\n @if (action.icon) {\n <span class=\"shrink-0 inline-flex items-center\">\n @if (isTemplateRef(action.icon)) {\n <ng-container [ngTemplateOutlet]=\"action.icon\"></ng-container>\n } @else {\n <!-- Data icon: rendered here so the item sizes it to match its own text\n (larger in the sheet, where the rows are touch-sized). -->\n <svg [lucideIcon]=\"$any(action.icon)\" [size]=\"sheet ? 18 : 16\"></svg>\n }\n </span>\n }\n <span class=\"truncate\">{{ actionLabel(action) }}</span>\n </button>\n } @else {\n <hr role=\"separator\" [ngClass]=\"sheet ? 'mx-4 my-1.5' : 'mx-2 my-1'\" class=\"shrink-0 border-t border-base-300\" />\n }\n }\n @if (isSearchable && filteredActions.length === 0) {\n <div class=\"flex-1 flex flex-col items-center justify-center gap-2 px-4 py-6 text-center text-base-content/50\">\n <svg lucideSearchX [size]=\"sheet ? 28 : 24\" class=\"opacity-60\"></svg>\n <span [ngClass]=\"sheet ? 'text-base' : 'text-sm'\">{{ searchEmptyLabel }}</span>\n </div>\n }\n </ng-template>\n\n <!-- The filter input, shared by the sheet and the anchored popover. Autofocused on\n desktop (`!sheet`) so typing starts immediately; deliberately not on mobile, where\n it would pop the soft keyboard and fight the viewport-anchored sheet. Enter runs\n the first visible action via the wrapper's bubbled keydown. -->\n <ng-template #searchBox let-sheet=\"sheet\">\n @if (isSearchable) {\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n <div\n (click)=\"$event.stopPropagation()\"\n (keydown.enter)=\"selectFirstVisible()\"\n [ngClass]=\"sheet ? 'px-4 py-2' : 'p-2'\"\n class=\"border-b border-base-300 shrink-0\"\n >\n <mn-lib-input-field\n (ngModelChange)=\"onSearch($event)\"\n [ngModelOptions]=\"{ standalone: true }\"\n [ngModel]=\"searchTerm\"\n [props]=\"{\n id: resolvedId + '-search',\n type: 'search',\n placeholder: searchPlaceholder,\n ariaLabel: searchPlaceholder,\n fullWidth: true,\n size: 'sm',\n autoFocus: !sheet\n }\"\n ></mn-lib-input-field>\n </div>\n }\n </ng-template>\n</div>\n", styles: [""], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: MnButton, selector: "button[mnButton], a[mnButton]", inputs: ["data"] }, { kind: "component", type: MnBottomSheet, selector: "mn-bottom-sheet", inputs: ["showBackdrop", "showGrabber", "dismissible", "minHeightPx", "maxHeightVh", "containerClass", "ariaLabel", "ariaLabelledby", "growWithKeyboard", "dismissGuard"], outputs: ["dismiss"] }, { kind: "component", type: MnInputField, selector: "mn-lib-input-field", inputs: ["props"] }, { kind: "component", type: LucideX, selector: "svg[lucideX]" }, { kind: "component", type: LucideSearchX, selector: "svg[lucideSearchX]" }, { kind: "component", type: LucideDynamicIcon, selector: "svg[lucideIcon]", inputs: ["lucideIcon"] }] });
5096
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: MnDropdown, isStandalone: true, selector: "mn-lib-dropdown", inputs: { datasource: "datasource" }, host: { listeners: { "document:click": "onDocumentClick($event)", "document:keydown.escape": "onEscape()", "window:scroll": "onWindowScrollOrResize()", "window:resize": "onWindowScrollOrResize()" } }, viewQueries: [{ propertyName: "triggerRef", first: true, predicate: ["trigger"], descendants: true, read: ElementRef }, { propertyName: "dropdownRef", first: true, predicate: ["dropdown"], descendants: true }, { propertyName: "sheetRef", first: true, predicate: ["sheet"], descendants: true, read: ElementRef }], ngImport: i0, template: "<div class=\"relative inline-flex\">\n <!-- Trigger -->\n <button\n #trigger\n mnButton\n type=\"button\"\n [id]=\"resolvedId\"\n [data]=\"triggerData\"\n [ngClass]=\"triggerClasses\"\n [attr.aria-label]=\"triggerLabelText ? null : triggerAriaLabel\"\n [attr.aria-haspopup]=\"'menu'\"\n [attr.aria-expanded]=\"isOpen\"\n [attr.aria-controls]=\"isOpen ? resolvedId + '-menu' : null\"\n (click)=\"toggle()\"\n >\n @if (triggerLabelText) {\n <span class=\"truncate\">{{ triggerLabelText }}</span>\n }\n <!-- One glyph, rendered like a menu item's icon: a caller's template, else lucide data\n (a preset resolves to its own data). No per-preset switch. -->\n @if (triggerIconTemplate; as tpl) {\n <span class=\"shrink-0 inline-flex items-center\">\n <ng-container [ngTemplateOutlet]=\"tpl\"></ng-container>\n </span>\n } @else if (triggerIconData; as glyph) {\n <span class=\"shrink-0 inline-flex items-center\">\n <svg [lucideIcon]=\"glyph.data\" [size]=\"glyph.size\" [class.opacity-70]=\"glyph.dim\"></svg>\n </span>\n }\n </button>\n\n <!-- Menu -->\n @if (isOpen) {\n @if (isSheet) {\n <!-- On mobile the menu is presented as the shared bottom sheet: chrome (backdrop,\n grabber, swipe-to-dismiss, slide animation) lives in mn-bottom-sheet; this\n component only projects the item list into it. The host is portalled to\n document.body (see the `sheet` ViewChild). -->\n <mn-bottom-sheet\n #sheet\n (dismiss)=\"close()\"\n [ariaLabel]=\"menuLabel || triggerAriaLabel\"\n [maxHeightVh]=\"80\"\n [minHeightPx]=\"sheetFloorPx\"\n >\n <div [id]=\"resolvedId + '-menu'\" role=\"menu\" class=\"flex flex-col flex-1 min-h-0 overflow-hidden\">\n <!-- The sheet covers its own trigger, so it needs a header and an explicit way out. -->\n <div class=\"flex items-center justify-between gap-x-2 px-4 pt-1 pb-2 shrink-0\">\n <p class=\"text-base font-bold text-base-content truncate\">{{ menuLabel || triggerAriaLabel }}</p>\n <button\n mnButton\n type=\"button\"\n [data]=\"{ size: 'sm', variant: 'text', color: 'secondary' }\"\n class=\"shrink-0 cursor-pointer\"\n (click)=\"close()\"\n [attr.aria-label]=\"closeLabel\"\n ><svg lucideX [size]=\"20\"></svg></button>\n </div>\n <ng-container [ngTemplateOutlet]=\"searchBox\" [ngTemplateOutletContext]=\"{ sheet: true }\"></ng-container>\n <div class=\"flex-1 flex flex-col overflow-auto overscroll-contain\">\n <ng-container [ngTemplateOutlet]=\"items\" [ngTemplateOutletContext]=\"{ sheet: true }\"></ng-container>\n </div>\n </div>\n </mn-bottom-sheet>\n } @else {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <div\n #dropdown\n (click)=\"$event.stopPropagation()\"\n [id]=\"resolvedId + '-menu'\"\n [ngClass]=\"panelClasses\"\n [style.left]=\"dropdownStyle.left\"\n [style.top]=\"dropdownStyle.top\"\n [style.height.px]=\"panelFloorPx\"\n role=\"menu\"\n >\n @if (menuLabel) {\n <p class=\"px-3 pt-1 pb-1.5 text-xs font-medium text-base-content/50 truncate shrink-0\">{{ menuLabel }}</p>\n }\n <ng-container [ngTemplateOutlet]=\"searchBox\" [ngTemplateOutletContext]=\"{ sheet: false }\"></ng-container>\n @if (isSearchable) {\n <!-- Its own scroll region so the pinned search box above stays fixed and the\n locked panel height (panelFloorPx) doesn't change as the list filters. The\n flex column lets the empty state fill and centre in the reserved space. -->\n <div class=\"flex-1 min-h-0 flex flex-col overflow-auto\">\n <ng-container [ngTemplateOutlet]=\"items\" [ngTemplateOutletContext]=\"{ sheet: false }\"></ng-container>\n </div>\n } @else {\n <ng-container [ngTemplateOutlet]=\"items\" [ngTemplateOutletContext]=\"{ sheet: false }\"></ng-container>\n }\n </div>\n }\n }\n\n <!-- The item list, shared verbatim by the sheet and the anchored popover. `sheet`\n only tunes the spacing so touch targets are roomier on mobile. -->\n <ng-template #items let-sheet=\"sheet\">\n @for (item of filteredActions; track $index) {\n @if (asAction(item); as action) {\n <button\n type=\"button\"\n role=\"menuitem\"\n [disabled]=\"action.disabled\"\n [class.opacity-50]=\"action.disabled\"\n [class.pointer-events-none]=\"action.disabled\"\n [ngClass]=\"[actionColorClass(action), sheet ? 'px-4 py-3 text-base' : 'px-3 py-2 text-sm']\"\n class=\"flex w-full shrink-0 items-center gap-x-2.5 text-left cursor-pointer hover:bg-base-200 focus-visible:bg-base-200 focus:outline-none transition-colors\"\n (click)=\"select(action)\"\n >\n @if (action.icon) {\n <span class=\"shrink-0 inline-flex items-center\">\n @if (isTemplateRef(action.icon)) {\n <ng-container [ngTemplateOutlet]=\"action.icon\"></ng-container>\n } @else {\n <!-- Data icon: rendered here so the item sizes it to match its own text\n (larger in the sheet, where the rows are touch-sized). -->\n <svg [lucideIcon]=\"$any(action.icon)\" [size]=\"sheet ? 18 : 16\"></svg>\n }\n </span>\n }\n <span class=\"truncate\">{{ actionLabel(action) }}</span>\n </button>\n } @else {\n <hr role=\"separator\" [ngClass]=\"sheet ? 'mx-4 my-1.5' : 'mx-2 my-1'\" class=\"shrink-0 border-t border-base-300\" />\n }\n }\n @if (isSearchable && filteredActions.length === 0) {\n <div class=\"flex-1 flex flex-col items-center justify-center gap-2 px-4 py-6 text-center text-base-content/50\">\n <svg lucideSearchX [size]=\"sheet ? 28 : 24\" class=\"opacity-60\"></svg>\n <span [ngClass]=\"sheet ? 'text-base' : 'text-sm'\">{{ searchEmptyLabel }}</span>\n </div>\n }\n </ng-template>\n\n <!-- The filter input, shared by the sheet and the anchored popover. Autofocused on\n desktop (`!sheet`) so typing starts immediately; deliberately not on mobile, where\n it would pop the soft keyboard and fight the viewport-anchored sheet. Enter runs\n the first visible action via the wrapper's bubbled keydown. -->\n <ng-template #searchBox let-sheet=\"sheet\">\n @if (isSearchable) {\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n <div\n (click)=\"$event.stopPropagation()\"\n (keydown.enter)=\"selectFirstVisible()\"\n [ngClass]=\"sheet ? 'px-4 py-2' : 'p-2'\"\n class=\"border-b border-base-300 shrink-0\"\n >\n <mn-lib-input-field\n (ngModelChange)=\"onSearch($event)\"\n [ngModelOptions]=\"{ standalone: true }\"\n [ngModel]=\"searchTerm\"\n [props]=\"{\n id: resolvedId + '-search',\n type: 'search',\n placeholder: searchPlaceholder,\n ariaLabel: searchPlaceholder,\n fullWidth: true,\n size: 'sm',\n autoFocus: !sheet\n }\"\n ></mn-lib-input-field>\n </div>\n }\n </ng-template>\n</div>\n", styles: [""], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: MnButton, selector: "button[mnButton], a[mnButton]", inputs: ["data"] }, { kind: "component", type: MnBottomSheet, selector: "mn-bottom-sheet", inputs: ["showBackdrop", "showGrabber", "dismissible", "minHeightPx", "maxHeightVh", "containerClass", "ariaLabel", "ariaLabelledby", "growWithKeyboard", "dismissGuard"], outputs: ["dismiss"] }, { kind: "component", type: MnInputField, selector: "mn-lib-input-field", inputs: ["props"] }, { kind: "component", type: LucideX, selector: "svg[lucideX]" }, { kind: "component", type: LucideSearchX, selector: "svg[lucideSearchX]" }, { kind: "component", type: LucideDynamicIcon, selector: "svg[lucideIcon]", inputs: ["lucideIcon"] }] });
5097
5097
  }
5098
5098
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: MnDropdown, decorators: [{
5099
5099
  type: Component,
5100
- args: [{ selector: 'mn-lib-dropdown', standalone: true, imports: [NgClass, NgTemplateOutlet, FormsModule, MnButton, MnBottomSheet, MnInputField, LucideX, LucideSearchX, LucideDynamicIcon], template: "<div class=\"relative inline-flex\">\n <!-- Trigger -->\n <button\n #trigger\n mnButton\n type=\"button\"\n [id]=\"resolvedId\"\n [data]=\"triggerData\"\n [ngClass]=\"triggerClasses\"\n [attr.aria-label]=\"triggerLabelText ? null : triggerAriaLabel\"\n [attr.aria-haspopup]=\"'menu'\"\n [attr.aria-expanded]=\"isOpen\"\n [attr.aria-controls]=\"isOpen ? resolvedId + '-menu' : null\"\n (click)=\"$event.stopPropagation(); toggle()\"\n >\n @if (triggerLabelText) {\n <span class=\"truncate\">{{ triggerLabelText }}</span>\n }\n <!-- One glyph, rendered like a menu item's icon: a caller's template, else lucide data\n (a preset resolves to its own data). No per-preset switch. -->\n @if (triggerIconTemplate; as tpl) {\n <span class=\"shrink-0 inline-flex items-center\">\n <ng-container [ngTemplateOutlet]=\"tpl\"></ng-container>\n </span>\n } @else if (triggerIconData; as glyph) {\n <span class=\"shrink-0 inline-flex items-center\">\n <svg [lucideIcon]=\"glyph.data\" [size]=\"glyph.size\" [class.opacity-70]=\"glyph.dim\"></svg>\n </span>\n }\n </button>\n\n <!-- Menu -->\n @if (isOpen) {\n @if (isSheet) {\n <!-- On mobile the menu is presented as the shared bottom sheet: chrome (backdrop,\n grabber, swipe-to-dismiss, slide animation) lives in mn-bottom-sheet; this\n component only projects the item list into it. The host is portalled to\n document.body (see the `sheet` ViewChild). -->\n <mn-bottom-sheet\n #sheet\n (dismiss)=\"close()\"\n [ariaLabel]=\"menuLabel || triggerAriaLabel\"\n [maxHeightVh]=\"80\"\n [minHeightPx]=\"sheetFloorPx\"\n >\n <div [id]=\"resolvedId + '-menu'\" role=\"menu\" class=\"flex flex-col flex-1 min-h-0 overflow-hidden\">\n <!-- The sheet covers its own trigger, so it needs a header and an explicit way out. -->\n <div class=\"flex items-center justify-between gap-x-2 px-4 pt-1 pb-2 shrink-0\">\n <p class=\"text-base font-bold text-base-content truncate\">{{ menuLabel || triggerAriaLabel }}</p>\n <button\n mnButton\n type=\"button\"\n [data]=\"{ size: 'sm', variant: 'text', color: 'secondary' }\"\n class=\"shrink-0 cursor-pointer\"\n (click)=\"close()\"\n [attr.aria-label]=\"closeLabel\"\n ><svg lucideX [size]=\"20\"></svg></button>\n </div>\n <ng-container [ngTemplateOutlet]=\"searchBox\" [ngTemplateOutletContext]=\"{ sheet: true }\"></ng-container>\n <div class=\"flex-1 flex flex-col overflow-auto overscroll-contain\">\n <ng-container [ngTemplateOutlet]=\"items\" [ngTemplateOutletContext]=\"{ sheet: true }\"></ng-container>\n </div>\n </div>\n </mn-bottom-sheet>\n } @else {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <div\n #dropdown\n (click)=\"$event.stopPropagation()\"\n [id]=\"resolvedId + '-menu'\"\n [ngClass]=\"panelClasses\"\n [style.left]=\"dropdownStyle.left\"\n [style.top]=\"dropdownStyle.top\"\n [style.height.px]=\"panelFloorPx\"\n role=\"menu\"\n >\n @if (menuLabel) {\n <p class=\"px-3 pt-1 pb-1.5 text-xs font-medium text-base-content/50 truncate shrink-0\">{{ menuLabel }}</p>\n }\n <ng-container [ngTemplateOutlet]=\"searchBox\" [ngTemplateOutletContext]=\"{ sheet: false }\"></ng-container>\n @if (isSearchable) {\n <!-- Its own scroll region so the pinned search box above stays fixed and the\n locked panel height (panelFloorPx) doesn't change as the list filters. The\n flex column lets the empty state fill and centre in the reserved space. -->\n <div class=\"flex-1 min-h-0 flex flex-col overflow-auto\">\n <ng-container [ngTemplateOutlet]=\"items\" [ngTemplateOutletContext]=\"{ sheet: false }\"></ng-container>\n </div>\n } @else {\n <ng-container [ngTemplateOutlet]=\"items\" [ngTemplateOutletContext]=\"{ sheet: false }\"></ng-container>\n }\n </div>\n }\n }\n\n <!-- The item list, shared verbatim by the sheet and the anchored popover. `sheet`\n only tunes the spacing so touch targets are roomier on mobile. -->\n <ng-template #items let-sheet=\"sheet\">\n @for (item of filteredActions; track $index) {\n @if (asAction(item); as action) {\n <button\n type=\"button\"\n role=\"menuitem\"\n [disabled]=\"action.disabled\"\n [class.opacity-50]=\"action.disabled\"\n [class.pointer-events-none]=\"action.disabled\"\n [ngClass]=\"[actionColorClass(action), sheet ? 'px-4 py-3 text-base' : 'px-3 py-2 text-sm']\"\n class=\"flex w-full shrink-0 items-center gap-x-2.5 text-left cursor-pointer hover:bg-base-200 focus-visible:bg-base-200 focus:outline-none transition-colors\"\n (click)=\"select(action)\"\n >\n @if (action.icon) {\n <span class=\"shrink-0 inline-flex items-center\">\n @if (isTemplateRef(action.icon)) {\n <ng-container [ngTemplateOutlet]=\"action.icon\"></ng-container>\n } @else {\n <!-- Data icon: rendered here so the item sizes it to match its own text\n (larger in the sheet, where the rows are touch-sized). -->\n <svg [lucideIcon]=\"$any(action.icon)\" [size]=\"sheet ? 18 : 16\"></svg>\n }\n </span>\n }\n <span class=\"truncate\">{{ actionLabel(action) }}</span>\n </button>\n } @else {\n <hr role=\"separator\" [ngClass]=\"sheet ? 'mx-4 my-1.5' : 'mx-2 my-1'\" class=\"shrink-0 border-t border-base-300\" />\n }\n }\n @if (isSearchable && filteredActions.length === 0) {\n <div class=\"flex-1 flex flex-col items-center justify-center gap-2 px-4 py-6 text-center text-base-content/50\">\n <svg lucideSearchX [size]=\"sheet ? 28 : 24\" class=\"opacity-60\"></svg>\n <span [ngClass]=\"sheet ? 'text-base' : 'text-sm'\">{{ searchEmptyLabel }}</span>\n </div>\n }\n </ng-template>\n\n <!-- The filter input, shared by the sheet and the anchored popover. Autofocused on\n desktop (`!sheet`) so typing starts immediately; deliberately not on mobile, where\n it would pop the soft keyboard and fight the viewport-anchored sheet. Enter runs\n the first visible action via the wrapper's bubbled keydown. -->\n <ng-template #searchBox let-sheet=\"sheet\">\n @if (isSearchable) {\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n <div\n (click)=\"$event.stopPropagation()\"\n (keydown.enter)=\"selectFirstVisible()\"\n [ngClass]=\"sheet ? 'px-4 py-2' : 'p-2'\"\n class=\"border-b border-base-300 shrink-0\"\n >\n <mn-lib-input-field\n (ngModelChange)=\"onSearch($event)\"\n [ngModelOptions]=\"{ standalone: true }\"\n [ngModel]=\"searchTerm\"\n [props]=\"{\n id: resolvedId + '-search',\n type: 'search',\n placeholder: searchPlaceholder,\n ariaLabel: searchPlaceholder,\n fullWidth: true,\n size: 'sm',\n autoFocus: !sheet\n }\"\n ></mn-lib-input-field>\n </div>\n }\n </ng-template>\n</div>\n" }]
5100
+ args: [{ selector: 'mn-lib-dropdown', standalone: true, imports: [NgClass, NgTemplateOutlet, FormsModule, MnButton, MnBottomSheet, MnInputField, LucideX, LucideSearchX, LucideDynamicIcon], template: "<div class=\"relative inline-flex\">\n <!-- Trigger -->\n <button\n #trigger\n mnButton\n type=\"button\"\n [id]=\"resolvedId\"\n [data]=\"triggerData\"\n [ngClass]=\"triggerClasses\"\n [attr.aria-label]=\"triggerLabelText ? null : triggerAriaLabel\"\n [attr.aria-haspopup]=\"'menu'\"\n [attr.aria-expanded]=\"isOpen\"\n [attr.aria-controls]=\"isOpen ? resolvedId + '-menu' : null\"\n (click)=\"toggle()\"\n >\n @if (triggerLabelText) {\n <span class=\"truncate\">{{ triggerLabelText }}</span>\n }\n <!-- One glyph, rendered like a menu item's icon: a caller's template, else lucide data\n (a preset resolves to its own data). No per-preset switch. -->\n @if (triggerIconTemplate; as tpl) {\n <span class=\"shrink-0 inline-flex items-center\">\n <ng-container [ngTemplateOutlet]=\"tpl\"></ng-container>\n </span>\n } @else if (triggerIconData; as glyph) {\n <span class=\"shrink-0 inline-flex items-center\">\n <svg [lucideIcon]=\"glyph.data\" [size]=\"glyph.size\" [class.opacity-70]=\"glyph.dim\"></svg>\n </span>\n }\n </button>\n\n <!-- Menu -->\n @if (isOpen) {\n @if (isSheet) {\n <!-- On mobile the menu is presented as the shared bottom sheet: chrome (backdrop,\n grabber, swipe-to-dismiss, slide animation) lives in mn-bottom-sheet; this\n component only projects the item list into it. The host is portalled to\n document.body (see the `sheet` ViewChild). -->\n <mn-bottom-sheet\n #sheet\n (dismiss)=\"close()\"\n [ariaLabel]=\"menuLabel || triggerAriaLabel\"\n [maxHeightVh]=\"80\"\n [minHeightPx]=\"sheetFloorPx\"\n >\n <div [id]=\"resolvedId + '-menu'\" role=\"menu\" class=\"flex flex-col flex-1 min-h-0 overflow-hidden\">\n <!-- The sheet covers its own trigger, so it needs a header and an explicit way out. -->\n <div class=\"flex items-center justify-between gap-x-2 px-4 pt-1 pb-2 shrink-0\">\n <p class=\"text-base font-bold text-base-content truncate\">{{ menuLabel || triggerAriaLabel }}</p>\n <button\n mnButton\n type=\"button\"\n [data]=\"{ size: 'sm', variant: 'text', color: 'secondary' }\"\n class=\"shrink-0 cursor-pointer\"\n (click)=\"close()\"\n [attr.aria-label]=\"closeLabel\"\n ><svg lucideX [size]=\"20\"></svg></button>\n </div>\n <ng-container [ngTemplateOutlet]=\"searchBox\" [ngTemplateOutletContext]=\"{ sheet: true }\"></ng-container>\n <div class=\"flex-1 flex flex-col overflow-auto overscroll-contain\">\n <ng-container [ngTemplateOutlet]=\"items\" [ngTemplateOutletContext]=\"{ sheet: true }\"></ng-container>\n </div>\n </div>\n </mn-bottom-sheet>\n } @else {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <div\n #dropdown\n (click)=\"$event.stopPropagation()\"\n [id]=\"resolvedId + '-menu'\"\n [ngClass]=\"panelClasses\"\n [style.left]=\"dropdownStyle.left\"\n [style.top]=\"dropdownStyle.top\"\n [style.height.px]=\"panelFloorPx\"\n role=\"menu\"\n >\n @if (menuLabel) {\n <p class=\"px-3 pt-1 pb-1.5 text-xs font-medium text-base-content/50 truncate shrink-0\">{{ menuLabel }}</p>\n }\n <ng-container [ngTemplateOutlet]=\"searchBox\" [ngTemplateOutletContext]=\"{ sheet: false }\"></ng-container>\n @if (isSearchable) {\n <!-- Its own scroll region so the pinned search box above stays fixed and the\n locked panel height (panelFloorPx) doesn't change as the list filters. The\n flex column lets the empty state fill and centre in the reserved space. -->\n <div class=\"flex-1 min-h-0 flex flex-col overflow-auto\">\n <ng-container [ngTemplateOutlet]=\"items\" [ngTemplateOutletContext]=\"{ sheet: false }\"></ng-container>\n </div>\n } @else {\n <ng-container [ngTemplateOutlet]=\"items\" [ngTemplateOutletContext]=\"{ sheet: false }\"></ng-container>\n }\n </div>\n }\n }\n\n <!-- The item list, shared verbatim by the sheet and the anchored popover. `sheet`\n only tunes the spacing so touch targets are roomier on mobile. -->\n <ng-template #items let-sheet=\"sheet\">\n @for (item of filteredActions; track $index) {\n @if (asAction(item); as action) {\n <button\n type=\"button\"\n role=\"menuitem\"\n [disabled]=\"action.disabled\"\n [class.opacity-50]=\"action.disabled\"\n [class.pointer-events-none]=\"action.disabled\"\n [ngClass]=\"[actionColorClass(action), sheet ? 'px-4 py-3 text-base' : 'px-3 py-2 text-sm']\"\n class=\"flex w-full shrink-0 items-center gap-x-2.5 text-left cursor-pointer hover:bg-base-200 focus-visible:bg-base-200 focus:outline-none transition-colors\"\n (click)=\"select(action)\"\n >\n @if (action.icon) {\n <span class=\"shrink-0 inline-flex items-center\">\n @if (isTemplateRef(action.icon)) {\n <ng-container [ngTemplateOutlet]=\"action.icon\"></ng-container>\n } @else {\n <!-- Data icon: rendered here so the item sizes it to match its own text\n (larger in the sheet, where the rows are touch-sized). -->\n <svg [lucideIcon]=\"$any(action.icon)\" [size]=\"sheet ? 18 : 16\"></svg>\n }\n </span>\n }\n <span class=\"truncate\">{{ actionLabel(action) }}</span>\n </button>\n } @else {\n <hr role=\"separator\" [ngClass]=\"sheet ? 'mx-4 my-1.5' : 'mx-2 my-1'\" class=\"shrink-0 border-t border-base-300\" />\n }\n }\n @if (isSearchable && filteredActions.length === 0) {\n <div class=\"flex-1 flex flex-col items-center justify-center gap-2 px-4 py-6 text-center text-base-content/50\">\n <svg lucideSearchX [size]=\"sheet ? 28 : 24\" class=\"opacity-60\"></svg>\n <span [ngClass]=\"sheet ? 'text-base' : 'text-sm'\">{{ searchEmptyLabel }}</span>\n </div>\n }\n </ng-template>\n\n <!-- The filter input, shared by the sheet and the anchored popover. Autofocused on\n desktop (`!sheet`) so typing starts immediately; deliberately not on mobile, where\n it would pop the soft keyboard and fight the viewport-anchored sheet. Enter runs\n the first visible action via the wrapper's bubbled keydown. -->\n <ng-template #searchBox let-sheet=\"sheet\">\n @if (isSearchable) {\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n <div\n (click)=\"$event.stopPropagation()\"\n (keydown.enter)=\"selectFirstVisible()\"\n [ngClass]=\"sheet ? 'px-4 py-2' : 'p-2'\"\n class=\"border-b border-base-300 shrink-0\"\n >\n <mn-lib-input-field\n (ngModelChange)=\"onSearch($event)\"\n [ngModelOptions]=\"{ standalone: true }\"\n [ngModel]=\"searchTerm\"\n [props]=\"{\n id: resolvedId + '-search',\n type: 'search',\n placeholder: searchPlaceholder,\n ariaLabel: searchPlaceholder,\n fullWidth: true,\n size: 'sm',\n autoFocus: !sheet\n }\"\n ></mn-lib-input-field>\n </div>\n }\n </ng-template>\n</div>\n" }]
5101
5101
  }], propDecorators: { datasource: [{
5102
5102
  type: Input,
5103
5103
  args: [{ required: true }]