ngx-dev-toolbar 3.0.3 → 3.1.1

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,9 +1,9 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, signal, computed, Injectable, inject, ChangeDetectionStrategy, Component, input, model, ElementRef, output, viewChild, contentChild, effect, DestroyRef, ViewEncapsulation, EnvironmentInjector, createComponent } from '@angular/core';
2
+ import { InjectionToken, signal, computed, Injectable, inject, ChangeDetectionStrategy, Component, input, model, ElementRef, output, viewChild, contentChild, effect, DestroyRef, ViewEncapsulation, EnvironmentInjector, createComponent, TemplateRef, Directive, contentChildren } from '@angular/core';
3
3
  import { toSignal, takeUntilDestroyed } from '@angular/core/rxjs-interop';
4
4
  import { BehaviorSubject, combineLatest, map, firstValueFrom, fromEvent } from 'rxjs';
5
- import { trigger, state, transition, style, animate } from '@angular/animations';
6
- import { CommonModule, DOCUMENT } from '@angular/common';
5
+ import { trigger, state, style, transition, animate } from '@angular/animations';
6
+ import { CommonModule, DOCUMENT, NgTemplateOutlet } from '@angular/common';
7
7
  import { filter, throttleTime } from 'rxjs/operators';
8
8
  import * as i1 from '@angular/forms';
9
9
  import { FormsModule } from '@angular/forms';
@@ -1615,6 +1615,55 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImpor
1615
1615
  }]
1616
1616
  }] });
1617
1617
 
1618
+ class EditIconComponent {
1619
+ constructor() {
1620
+ this.fill = input('#FFFF');
1621
+ }
1622
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: EditIconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1623
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.0.7", type: EditIconComponent, isStandalone: true, selector: "ngt-edit-icon", inputs: { fill: { classPropertyName: "fill", publicName: "fill", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
1624
+ <svg
1625
+ [attr.fill]="fill()"
1626
+ xmlns="http://www.w3.org/2000/svg"
1627
+ width="24"
1628
+ height="24"
1629
+ viewBox="0 0 256 256"
1630
+ >
1631
+ <path
1632
+ d="M221.66,90.34,192,120,136,64l29.66-29.66a8,8,0,0,1,11.31,0L221.66,79A8,8,0,0,1,221.66,90.34Z"
1633
+ opacity="0.2"
1634
+ ></path>
1635
+ <path
1636
+ d="M227.31,73.37,182.63,28.68a16,16,0,0,0-22.63,0L36.69,152A15.86,15.86,0,0,0,32,163.31V208a16,16,0,0,0,16,16H92.69A15.86,15.86,0,0,0,104,219.31L227.31,96a16,16,0,0,0,0-22.63ZM92.69,208H48V163.31l88-88L180.69,120ZM192,108.68,147.31,64l24-24L216,84.68Z"
1637
+ ></path>
1638
+ </svg>
1639
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1640
+ }
1641
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: EditIconComponent, decorators: [{
1642
+ type: Component,
1643
+ args: [{
1644
+ selector: 'ngt-edit-icon',
1645
+ standalone: true,
1646
+ changeDetection: ChangeDetectionStrategy.OnPush,
1647
+ template: `
1648
+ <svg
1649
+ [attr.fill]="fill()"
1650
+ xmlns="http://www.w3.org/2000/svg"
1651
+ width="24"
1652
+ height="24"
1653
+ viewBox="0 0 256 256"
1654
+ >
1655
+ <path
1656
+ d="M221.66,90.34,192,120,136,64l29.66-29.66a8,8,0,0,1,11.31,0L221.66,79A8,8,0,0,1,221.66,90.34Z"
1657
+ opacity="0.2"
1658
+ ></path>
1659
+ <path
1660
+ d="M227.31,73.37,182.63,28.68a16,16,0,0,0-22.63,0L36.69,152A15.86,15.86,0,0,0,32,163.31V208a16,16,0,0,0,16,16H92.69A15.86,15.86,0,0,0,104,219.31L227.31,96a16,16,0,0,0,0-22.63ZM92.69,208H48V163.31l88-88L180.69,120ZM192,108.68,147.31,64l24-24L216,84.68Z"
1661
+ ></path>
1662
+ </svg>
1663
+ `,
1664
+ }]
1665
+ }] });
1666
+
1618
1667
  class ExportIconComponent {
1619
1668
  constructor() {
1620
1669
  this.fill = input('#FFFF');
@@ -2604,6 +2653,8 @@ class ToolbarIconComponent {
2604
2653
  <ngt-database-icon [fill]="fill()" />
2605
2654
  } @case ('docs') {
2606
2655
  <ngt-docs-icon [fill]="fill()" />
2656
+ } @case ('edit') {
2657
+ <ngt-edit-icon [fill]="fill()" />
2607
2658
  } @case ('export') {
2608
2659
  <ngt-export-icon [fill]="fill()" />
2609
2660
  } @case ('filter') {
@@ -2649,7 +2700,7 @@ class ToolbarIconComponent {
2649
2700
  } @case ('trash') {
2650
2701
  <ngt-trash-icon [fill]="fill()" />
2651
2702
  } }
2652
- `, isInline: true, dependencies: [{ kind: "component", type: AngularIconComponent, selector: "ngt-angular-icon" }, { kind: "component", type: BoltIconComponent, selector: "ngt-bolt-icon", inputs: ["fill"] }, { kind: "component", type: BugIconComponent, selector: "ngt-bug-icon", inputs: ["fill"] }, { kind: "component", type: CodeIconComponent, selector: "ngt-code-icon", inputs: ["fill"] }, { kind: "component", type: DatabaseIconComponent, selector: "ngt-database-icon", inputs: ["fill"] }, { kind: "component", type: DocsIconComponent, selector: "ngt-docs-icon", inputs: ["fill"] }, { kind: "component", type: DiscordIconComponent, selector: "ngt-discord-icon", inputs: ["fill"] }, { kind: "component", type: ExportIconComponent, selector: "ngt-export-icon", inputs: ["fill"] }, { kind: "component", type: FilterIconComponent, selector: "ngt-filter-icon", inputs: ["fill"] }, { kind: "component", type: GaugeIconComponent, selector: "ngt-gauge-icon", inputs: ["fill"] }, { kind: "component", type: GearIconComponent, selector: "ngt-gear-icon", inputs: ["fill"] }, { kind: "component", type: GitBranchIconComponent, selector: "ngt-git-branch-icon", inputs: ["fill"] }, { kind: "component", type: ImportIconComponent, selector: "ngt-import-icon", inputs: ["fill"] }, { kind: "component", type: LayoutIconComponent, selector: "ngt-layout-icon", inputs: ["fill"] }, { kind: "component", type: LightbulbIconComponent, selector: "ngt-lightbulb-icon", inputs: ["fill"] }, { kind: "component", type: LightingIconComponent, selector: "ngt-lighting-icon", inputs: ["fill"] }, { kind: "component", type: LockIconComponent, selector: "ngt-lock-icon", inputs: ["fill"] }, { kind: "component", type: NetworkIconComponent, selector: "ngt-network-icon", inputs: ["fill"] }, { kind: "component", type: PuzzleIconComponent, selector: "ngt-puzzle-icon", inputs: ["fill"] }, { kind: "component", type: RefreshIconComponent, selector: "ngt-refresh-icon", inputs: ["fill"] }, { kind: "component", type: StarIconComponent, selector: "ngt-star-icon", inputs: ["fill"] }, { kind: "component", type: TerminalIconComponent, selector: "ngt-terminal-icon", inputs: ["fill"] }, { kind: "component", type: ToggleLeftIconComponent, selector: "ngt-toggle-left-icon", inputs: ["fill"] }, { kind: "component", type: UsersIconComponent, selector: "ngt-users-icon", inputs: ["fill"] }, { kind: "component", type: SunIconComponent, selector: "ngt-sun-icon", inputs: ["fill"] }, { kind: "component", type: MoonIconComponent, selector: "ngt-moon-icon", inputs: ["fill"] }, { kind: "component", type: TranslateIconComponent, selector: "ngt-translate-icon", inputs: ["fill"] }, { kind: "component", type: TrashIconComponent, selector: "ngt-trash-icon", inputs: ["fill"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2703
+ `, isInline: true, dependencies: [{ kind: "component", type: AngularIconComponent, selector: "ngt-angular-icon" }, { kind: "component", type: BoltIconComponent, selector: "ngt-bolt-icon", inputs: ["fill"] }, { kind: "component", type: BugIconComponent, selector: "ngt-bug-icon", inputs: ["fill"] }, { kind: "component", type: CodeIconComponent, selector: "ngt-code-icon", inputs: ["fill"] }, { kind: "component", type: DatabaseIconComponent, selector: "ngt-database-icon", inputs: ["fill"] }, { kind: "component", type: DocsIconComponent, selector: "ngt-docs-icon", inputs: ["fill"] }, { kind: "component", type: DiscordIconComponent, selector: "ngt-discord-icon", inputs: ["fill"] }, { kind: "component", type: EditIconComponent, selector: "ngt-edit-icon", inputs: ["fill"] }, { kind: "component", type: ExportIconComponent, selector: "ngt-export-icon", inputs: ["fill"] }, { kind: "component", type: FilterIconComponent, selector: "ngt-filter-icon", inputs: ["fill"] }, { kind: "component", type: GaugeIconComponent, selector: "ngt-gauge-icon", inputs: ["fill"] }, { kind: "component", type: GearIconComponent, selector: "ngt-gear-icon", inputs: ["fill"] }, { kind: "component", type: GitBranchIconComponent, selector: "ngt-git-branch-icon", inputs: ["fill"] }, { kind: "component", type: ImportIconComponent, selector: "ngt-import-icon", inputs: ["fill"] }, { kind: "component", type: LayoutIconComponent, selector: "ngt-layout-icon", inputs: ["fill"] }, { kind: "component", type: LightbulbIconComponent, selector: "ngt-lightbulb-icon", inputs: ["fill"] }, { kind: "component", type: LightingIconComponent, selector: "ngt-lighting-icon", inputs: ["fill"] }, { kind: "component", type: LockIconComponent, selector: "ngt-lock-icon", inputs: ["fill"] }, { kind: "component", type: NetworkIconComponent, selector: "ngt-network-icon", inputs: ["fill"] }, { kind: "component", type: PuzzleIconComponent, selector: "ngt-puzzle-icon", inputs: ["fill"] }, { kind: "component", type: RefreshIconComponent, selector: "ngt-refresh-icon", inputs: ["fill"] }, { kind: "component", type: StarIconComponent, selector: "ngt-star-icon", inputs: ["fill"] }, { kind: "component", type: TerminalIconComponent, selector: "ngt-terminal-icon", inputs: ["fill"] }, { kind: "component", type: ToggleLeftIconComponent, selector: "ngt-toggle-left-icon", inputs: ["fill"] }, { kind: "component", type: UsersIconComponent, selector: "ngt-users-icon", inputs: ["fill"] }, { kind: "component", type: SunIconComponent, selector: "ngt-sun-icon", inputs: ["fill"] }, { kind: "component", type: MoonIconComponent, selector: "ngt-moon-icon", inputs: ["fill"] }, { kind: "component", type: TranslateIconComponent, selector: "ngt-translate-icon", inputs: ["fill"] }, { kind: "component", type: TrashIconComponent, selector: "ngt-trash-icon", inputs: ["fill"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2653
2704
  }
2654
2705
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarIconComponent, decorators: [{
2655
2706
  type: Component,
@@ -2664,6 +2715,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImpor
2664
2715
  DatabaseIconComponent,
2665
2716
  DocsIconComponent,
2666
2717
  DiscordIconComponent,
2718
+ EditIconComponent,
2667
2719
  ExportIconComponent,
2668
2720
  FilterIconComponent,
2669
2721
  GaugeIconComponent,
@@ -2700,6 +2752,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImpor
2700
2752
  <ngt-database-icon [fill]="fill()" />
2701
2753
  } @case ('docs') {
2702
2754
  <ngt-docs-icon [fill]="fill()" />
2755
+ } @case ('edit') {
2756
+ <ngt-edit-icon [fill]="fill()" />
2703
2757
  } @case ('export') {
2704
2758
  <ngt-export-icon [fill]="fill()" />
2705
2759
  } @case ('filter') {
@@ -2991,14 +3045,19 @@ class ToolbarSelectComponent {
2991
3045
  this.ariaLabel = input('');
2992
3046
  this.label = input('');
2993
3047
  this.size = input('medium');
3048
+ this.placeholder = input('Select an option');
2994
3049
  this.theme = computed(() => this.devToolbarStateService.theme());
2995
3050
  this.selectMenuId = `select-menu-${Math.random()
2996
3051
  .toString(36)
2997
3052
  .slice(2, 11)}`;
2998
3053
  this.isOpen = signal(false);
3054
+ this.isPlaceholder = computed(() => {
3055
+ const options = this.options();
3056
+ return !!options?.length && !options.some((opt) => opt.value === this.value());
3057
+ });
2999
3058
  this.selectedLabel = computed(() => {
3000
3059
  const selected = this.options()?.find((opt) => opt.value === this.value());
3001
- return selected?.label ?? '';
3060
+ return selected?.label ?? this.placeholder();
3002
3061
  });
3003
3062
  this.positions = [
3004
3063
  {
@@ -3028,11 +3087,12 @@ class ToolbarSelectComponent {
3028
3087
  this.close();
3029
3088
  }
3030
3089
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3031
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.7", type: ToolbarSelectComponent, isStandalone: true, selector: "ngt-select", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, ngImport: i0, template: `
3090
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.7", type: ToolbarSelectComponent, isStandalone: true, selector: "ngt-select", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, ngImport: i0, template: `
3032
3091
  <div
3033
3092
  class="ngt-select"
3034
3093
  [class.small]="size() === 'small'"
3035
3094
  [class.open]="isOpen()"
3095
+ [class.placeholder]="isPlaceholder()"
3036
3096
  [attr.aria-label]="ariaLabel()"
3037
3097
  [attr.aria-expanded]="isOpen()"
3038
3098
  [attr.aria-controls]="selectMenuId"
@@ -3065,20 +3125,20 @@ class ToolbarSelectComponent {
3065
3125
  [attr.data-theme]="theme()"
3066
3126
  >
3067
3127
  @for (option of options(); track option.value) {
3068
- <button
3069
- class="select-menu-item"
3070
- [class.selected]="option.value === value()"
3071
- [attr.aria-selected]="option.value === value()"
3072
- cdkMenuItem
3073
- type="button"
3074
- (click)="selectOption(option)"
3075
- >
3076
- {{ option.label }}
3077
- </button>
3128
+ <button
3129
+ class="select-menu-item"
3130
+ [class.selected]="option.value === value()"
3131
+ [attr.aria-selected]="option.value === value()"
3132
+ cdkMenuItem
3133
+ type="button"
3134
+ (click)="selectOption(option)"
3135
+ >
3136
+ {{ option.label }}
3137
+ </button>
3078
3138
  }
3079
3139
  </div>
3080
3140
  </ng-template>
3081
- `, isInline: true, styles: [":host{--ngt-border-radius-small: 4px;--ngt-border-radius-medium: 8px;--ngt-border-radius-large: 12px;--ngt-transition-default: all .2s ease-out;--ngt-transition-smooth: all .2s ease-in-out;--ngt-bg-primary: rgb(255, 255, 255);--ngt-bg-gradient: linear-gradient(180deg, rgb(243, 244, 246) 0%, rgba(243, 244, 246, .88) 100%);--ngt-text-primary: rgb(17, 24, 39);--ngt-text-secondary: rgb(55, 65, 81);--ngt-text-muted: rgb(107, 114, 128);--ngt-border-primary: #e5e7eb;--ngt-border-subtle: rgba(17, 24, 39, .1);--ngt-hover-bg: rgba(17, 24, 39, .05);--ngt-hover-danger: rgb(239, 68, 68);--ngt-shadow-toolbar: 0 2px 8px rgba(156, 163, 175, .2);--ngt-shadow-tooltip: 0 0 0 1px rgba(17, 24, 39, .05), 0 4px 8px rgba(107, 114, 128, .15), 0 2px 4px rgba(107, 114, 128, .1);--ngt-shadow-window: 0px 0px 0px 0px rgba(156, 163, 175, .1), 0px 1px 2px 0px rgba(156, 163, 175, .12), 0px 4px 4px 0px rgba(156, 163, 175, .1), 0px 10px 6px 0px rgba(156, 163, 175, .08), 0px 17px 7px 0px rgba(156, 163, 175, .05), 0px 26px 7px 0px rgba(156, 163, 175, .02);--ngt-spacing-xs: 4px;--ngt-spacing-sm: 6px;--ngt-spacing-md: 12px;--ngt-spacing-lg: 16px;--ngt-window-padding: 16px;--ngt-font-size-xxs: .65rem;--ngt-font-size-xs: .75rem;--ngt-font-size-sm: .875rem;--ngt-font-size-md: 1rem;--ngt-font-size-lg: 1.25rem;--ngt-font-size-xl: 2rem;--ngt-background-secondary: var(--ngt-bg-primary);--ngt-background-hover: var(--ngt-hover-bg);--ngt-primary: #df30d4;--ngt-primary-rgb: 223, 48, 212;--ngt-text-on-primary: rgb(255, 255, 255);--ngt-border-color: var(--ngt-border-primary);--ngt-note-background: rgb(219, 234, 254);--ngt-note-border: rgba(37, 99, 235, .2);--ngt-warning-background: rgb(254, 249, 195);--ngt-warning-border: rgba(202, 138, 4, .2);--ngt-error-background: rgb(254, 226, 226);--ngt-error-border: rgba(220, 38, 38, .2);display:inline-block;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",Segoe UI Symbol,\"Noto Color Emoji\"}.ngt-select{position:relative;width:100%;min-width:120px;display:flex;align-items:center;justify-content:space-between;padding:var(--ngt-spacing-sm) var(--ngt-spacing-md);border:1px solid var(--ngt-border-primary);border-radius:var(--ngt-border-radius-small);background-color:var(--ngt-bg-primary);color:var(--ngt-text-primary);font-size:var(--ngt-font-size-sm);cursor:pointer;-webkit-user-select:none;user-select:none;transition:var(--ngt-transition-default);outline:none;min-height:36px;box-sizing:border-box;box-shadow:0 1px 2px #0000000d}.ngt-select:hover{background-color:var(--ngt-hover-bg);border-color:var(--ngt-primary);box-shadow:0 1px 3px #0000001a}.ngt-select:focus-visible{outline:none;border-color:var(--ngt-primary);box-shadow:0 0 0 2px rgba(var(--ngt-primary-rgb),.2),0 1px 3px #0000001a}.ngt-select.small{padding:var(--ngt-spacing-xs) var(--ngt-spacing-sm);font-size:var(--ngt-font-size-sm);height:24px}.ngt-select.open{border-color:var(--ngt-primary);box-shadow:0 0 0 2px rgba(var(--ngt-primary-rgb),.2),0 1px 3px #0000001a}.ngt-select.open .select__arrow{transform:rotate(180deg)}.ngt-select__value{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:1.4;margin-right:var(--ngt-spacing-sm);min-width:0;display:flex;align-items:center}.ngt-select__arrow{width:16px;height:16px;flex-shrink:0;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:center;background-size:contain;transition:transform .2s ease;opacity:.9}.ngt-select-menu{display:inline-flex;flex-direction:column;min-width:180px;background-color:var(--ngt-bg-primary);padding:var(--ngt-spacing-sm) 0;border:2px solid var(--ngt-border-primary);border-radius:var(--ngt-border-radius-large);box-shadow:0 4px 12px #00000026,0 2px 4px #0000001a;color:var(--ngt-text-primary);max-height:min(400px,70vh);overflow-y:auto;backdrop-filter:blur(8px);z-index:1000}.ngt-select-menu::-webkit-scrollbar{width:8px;height:8px}.ngt-select-menu::-webkit-scrollbar-track{background:transparent}.ngt-select-menu::-webkit-scrollbar-thumb{background-color:var(--ngt-border-primary);border-radius:4px;border:2px solid var(--ngt-bg-primary)}.ngt-select-menu::-webkit-scrollbar-thumb:hover{background-color:var(--ngt-text-secondary)}.select-menu-item{background-color:transparent;cursor:pointer;border:none;color:var(--ngt-text-primary);-webkit-user-select:none;user-select:none;min-width:64px;padding:var(--ngt-spacing-sm) var(--ngt-spacing-md) var(--ngt-spacing-sm) var(--ngt-spacing-lg);display:flex;align-items:center;flex-direction:row;flex:1;font-size:var(--ngt-font-size-xs);font-family:inherit;position:relative;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;font-weight:400;line-height:1.5;transition:background-color .15s ease}.select-menu-item:hover{background-color:#0000000f}.select-menu-item:active{background-color:rgba(var(--ngt-primary-rgb),.15)}.select-menu-item.selected{color:var(--ngt-primary);background-color:rgba(var(--ngt-primary-rgb),.08);font-weight:400}.select-menu-item.selected:hover{background-color:rgba(var(--ngt-primary-rgb),.12)}.select-menu-item.selected:before{content:\"\";position:absolute;left:0;top:8px;width:3px;height:calc(100% - 16px);background-color:var(--ngt-primary);border-radius:2px}.select-menu-item:focus-visible{outline:none;background-color:#0000000f}.select-menu-item:first-child{margin-top:var(--ngt-spacing-xs)}.select-menu-item:last-child{margin-bottom:var(--ngt-spacing-xs)}.select-overlay{backdrop-filter:blur(8px)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$1.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$1.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "ngmodule", type: CdkMenuModule }, { kind: "directive", type: i2.CdkMenu, selector: "[cdkMenu]", outputs: ["closed"], exportAs: ["cdkMenu"] }, { kind: "directive", type: i2.CdkMenuItem, selector: "[cdkMenuItem]", inputs: ["cdkMenuItemDisabled", "cdkMenuitemTypeaheadLabel"], outputs: ["cdkMenuItemTriggered"], exportAs: ["cdkMenuItem"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3141
+ `, isInline: true, styles: [":host{--ngt-border-radius-small: 4px;--ngt-border-radius-medium: 8px;--ngt-border-radius-large: 12px;--ngt-transition-default: all .2s ease-out;--ngt-transition-smooth: all .2s ease-in-out;--ngt-bg-primary: rgb(255, 255, 255);--ngt-bg-gradient: linear-gradient(180deg, rgb(243, 244, 246) 0%, rgba(243, 244, 246, .88) 100%);--ngt-text-primary: rgb(17, 24, 39);--ngt-text-secondary: rgb(55, 65, 81);--ngt-text-muted: rgb(107, 114, 128);--ngt-border-primary: #e5e7eb;--ngt-border-subtle: rgba(17, 24, 39, .1);--ngt-hover-bg: rgba(17, 24, 39, .05);--ngt-hover-danger: rgb(239, 68, 68);--ngt-shadow-toolbar: 0 2px 8px rgba(156, 163, 175, .2);--ngt-shadow-tooltip: 0 0 0 1px rgba(17, 24, 39, .05), 0 4px 8px rgba(107, 114, 128, .15), 0 2px 4px rgba(107, 114, 128, .1);--ngt-shadow-window: 0px 0px 0px 0px rgba(156, 163, 175, .1), 0px 1px 2px 0px rgba(156, 163, 175, .12), 0px 4px 4px 0px rgba(156, 163, 175, .1), 0px 10px 6px 0px rgba(156, 163, 175, .08), 0px 17px 7px 0px rgba(156, 163, 175, .05), 0px 26px 7px 0px rgba(156, 163, 175, .02);--ngt-spacing-xs: 4px;--ngt-spacing-sm: 6px;--ngt-spacing-md: 12px;--ngt-spacing-lg: 16px;--ngt-window-padding: 16px;--ngt-font-size-xxs: .65rem;--ngt-font-size-xs: .75rem;--ngt-font-size-sm: .875rem;--ngt-font-size-md: 1rem;--ngt-font-size-lg: 1.25rem;--ngt-font-size-xl: 2rem;--ngt-background-secondary: var(--ngt-bg-primary);--ngt-background-hover: var(--ngt-hover-bg);--ngt-primary: #df30d4;--ngt-primary-rgb: 223, 48, 212;--ngt-text-on-primary: rgb(255, 255, 255);--ngt-border-color: var(--ngt-border-primary);--ngt-note-background: rgb(219, 234, 254);--ngt-note-border: rgba(37, 99, 235, .2);--ngt-warning-background: rgb(254, 249, 195);--ngt-warning-border: rgba(202, 138, 4, .2);--ngt-error-background: rgb(254, 226, 226);--ngt-error-border: rgba(220, 38, 38, .2);display:inline-block;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",Segoe UI Symbol,\"Noto Color Emoji\"}.ngt-select{position:relative;width:100%;min-width:120px;display:flex;align-items:center;justify-content:space-between;padding:var(--ngt-spacing-sm) var(--ngt-spacing-md);border:1px solid var(--ngt-border-primary);border-radius:var(--ngt-border-radius-small);background-color:var(--ngt-bg-primary);color:var(--ngt-text-primary);font-size:var(--ngt-font-size-sm);cursor:pointer;-webkit-user-select:none;user-select:none;transition:var(--ngt-transition-default);outline:none;min-height:36px;box-sizing:border-box;box-shadow:0 1px 2px #0000000d}.ngt-select:hover{background-color:var(--ngt-hover-bg);border-color:var(--ngt-primary);box-shadow:0 1px 3px #0000001a}.ngt-select:focus-visible{outline:none;border-color:var(--ngt-primary);box-shadow:0 0 0 2px rgba(var(--ngt-primary-rgb),.2),0 1px 3px #0000001a}.ngt-select.small{padding:var(--ngt-spacing-xs) var(--ngt-spacing-sm);font-size:var(--ngt-font-size-sm);height:24px}.ngt-select.open{border-color:var(--ngt-primary);box-shadow:0 0 0 2px rgba(var(--ngt-primary-rgb),.2),0 1px 3px #0000001a}.ngt-select.open .select__arrow{transform:rotate(180deg)}.ngt-select.placeholder{color:var(--ngt-text-muted)}.ngt-select__value{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:1.4;margin-right:var(--ngt-spacing-sm);min-width:0;display:flex;align-items:center}.ngt-select__arrow{width:16px;height:16px;flex-shrink:0;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:center;background-size:contain;transition:transform .2s ease;opacity:.9}.ngt-select-menu{display:inline-flex;flex-direction:column;min-width:180px;background-color:var(--ngt-bg-primary);padding:var(--ngt-spacing-sm) 0;border:2px solid var(--ngt-border-primary);border-radius:var(--ngt-border-radius-large);box-shadow:0 4px 12px #00000026,0 2px 4px #0000001a;color:var(--ngt-text-primary);max-height:min(400px,70vh);overflow-y:auto;backdrop-filter:blur(8px);z-index:1000}.ngt-select-menu::-webkit-scrollbar{width:8px;height:8px}.ngt-select-menu::-webkit-scrollbar-track{background:transparent}.ngt-select-menu::-webkit-scrollbar-thumb{background-color:var(--ngt-border-primary);border-radius:4px;border:2px solid var(--ngt-bg-primary)}.ngt-select-menu::-webkit-scrollbar-thumb:hover{background-color:var(--ngt-text-secondary)}.select-menu-item{background-color:transparent;cursor:pointer;border:none;color:var(--ngt-text-primary);-webkit-user-select:none;user-select:none;min-width:64px;padding:var(--ngt-spacing-sm) var(--ngt-spacing-md) var(--ngt-spacing-sm) var(--ngt-spacing-lg);display:flex;align-items:center;flex-direction:row;flex:1;font-size:var(--ngt-font-size-xs);font-family:inherit;position:relative;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;font-weight:400;line-height:1.5;transition:background-color .15s ease}.select-menu-item:hover{background-color:#0000000f}.select-menu-item:active{background-color:rgba(var(--ngt-primary-rgb),.15)}.select-menu-item.selected{color:var(--ngt-primary);background-color:rgba(var(--ngt-primary-rgb),.08);font-weight:400}.select-menu-item.selected:hover{background-color:rgba(var(--ngt-primary-rgb),.12)}.select-menu-item.selected:before{content:\"\";position:absolute;left:0;top:8px;width:3px;height:calc(100% - 16px);background-color:var(--ngt-primary);border-radius:2px}.select-menu-item:focus-visible{outline:none;background-color:#0000000f}.select-menu-item:first-child{margin-top:var(--ngt-spacing-xs)}.select-menu-item:last-child{margin-bottom:var(--ngt-spacing-xs)}.select-overlay{backdrop-filter:blur(8px)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$1.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$1.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "ngmodule", type: CdkMenuModule }, { kind: "directive", type: i2.CdkMenu, selector: "[cdkMenu]", outputs: ["closed"], exportAs: ["cdkMenu"] }, { kind: "directive", type: i2.CdkMenuItem, selector: "[cdkMenuItem]", inputs: ["cdkMenuItemDisabled", "cdkMenuitemTypeaheadLabel"], outputs: ["cdkMenuItemTriggered"], exportAs: ["cdkMenuItem"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3082
3142
  }
3083
3143
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarSelectComponent, decorators: [{
3084
3144
  type: Component,
@@ -3087,6 +3147,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImpor
3087
3147
  class="ngt-select"
3088
3148
  [class.small]="size() === 'small'"
3089
3149
  [class.open]="isOpen()"
3150
+ [class.placeholder]="isPlaceholder()"
3090
3151
  [attr.aria-label]="ariaLabel()"
3091
3152
  [attr.aria-expanded]="isOpen()"
3092
3153
  [attr.aria-controls]="selectMenuId"
@@ -3119,20 +3180,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImpor
3119
3180
  [attr.data-theme]="theme()"
3120
3181
  >
3121
3182
  @for (option of options(); track option.value) {
3122
- <button
3123
- class="select-menu-item"
3124
- [class.selected]="option.value === value()"
3125
- [attr.aria-selected]="option.value === value()"
3126
- cdkMenuItem
3127
- type="button"
3128
- (click)="selectOption(option)"
3129
- >
3130
- {{ option.label }}
3131
- </button>
3183
+ <button
3184
+ class="select-menu-item"
3185
+ [class.selected]="option.value === value()"
3186
+ [attr.aria-selected]="option.value === value()"
3187
+ cdkMenuItem
3188
+ type="button"
3189
+ (click)="selectOption(option)"
3190
+ >
3191
+ {{ option.label }}
3192
+ </button>
3132
3193
  }
3133
3194
  </div>
3134
3195
  </ng-template>
3135
- `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{--ngt-border-radius-small: 4px;--ngt-border-radius-medium: 8px;--ngt-border-radius-large: 12px;--ngt-transition-default: all .2s ease-out;--ngt-transition-smooth: all .2s ease-in-out;--ngt-bg-primary: rgb(255, 255, 255);--ngt-bg-gradient: linear-gradient(180deg, rgb(243, 244, 246) 0%, rgba(243, 244, 246, .88) 100%);--ngt-text-primary: rgb(17, 24, 39);--ngt-text-secondary: rgb(55, 65, 81);--ngt-text-muted: rgb(107, 114, 128);--ngt-border-primary: #e5e7eb;--ngt-border-subtle: rgba(17, 24, 39, .1);--ngt-hover-bg: rgba(17, 24, 39, .05);--ngt-hover-danger: rgb(239, 68, 68);--ngt-shadow-toolbar: 0 2px 8px rgba(156, 163, 175, .2);--ngt-shadow-tooltip: 0 0 0 1px rgba(17, 24, 39, .05), 0 4px 8px rgba(107, 114, 128, .15), 0 2px 4px rgba(107, 114, 128, .1);--ngt-shadow-window: 0px 0px 0px 0px rgba(156, 163, 175, .1), 0px 1px 2px 0px rgba(156, 163, 175, .12), 0px 4px 4px 0px rgba(156, 163, 175, .1), 0px 10px 6px 0px rgba(156, 163, 175, .08), 0px 17px 7px 0px rgba(156, 163, 175, .05), 0px 26px 7px 0px rgba(156, 163, 175, .02);--ngt-spacing-xs: 4px;--ngt-spacing-sm: 6px;--ngt-spacing-md: 12px;--ngt-spacing-lg: 16px;--ngt-window-padding: 16px;--ngt-font-size-xxs: .65rem;--ngt-font-size-xs: .75rem;--ngt-font-size-sm: .875rem;--ngt-font-size-md: 1rem;--ngt-font-size-lg: 1.25rem;--ngt-font-size-xl: 2rem;--ngt-background-secondary: var(--ngt-bg-primary);--ngt-background-hover: var(--ngt-hover-bg);--ngt-primary: #df30d4;--ngt-primary-rgb: 223, 48, 212;--ngt-text-on-primary: rgb(255, 255, 255);--ngt-border-color: var(--ngt-border-primary);--ngt-note-background: rgb(219, 234, 254);--ngt-note-border: rgba(37, 99, 235, .2);--ngt-warning-background: rgb(254, 249, 195);--ngt-warning-border: rgba(202, 138, 4, .2);--ngt-error-background: rgb(254, 226, 226);--ngt-error-border: rgba(220, 38, 38, .2);display:inline-block;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",Segoe UI Symbol,\"Noto Color Emoji\"}.ngt-select{position:relative;width:100%;min-width:120px;display:flex;align-items:center;justify-content:space-between;padding:var(--ngt-spacing-sm) var(--ngt-spacing-md);border:1px solid var(--ngt-border-primary);border-radius:var(--ngt-border-radius-small);background-color:var(--ngt-bg-primary);color:var(--ngt-text-primary);font-size:var(--ngt-font-size-sm);cursor:pointer;-webkit-user-select:none;user-select:none;transition:var(--ngt-transition-default);outline:none;min-height:36px;box-sizing:border-box;box-shadow:0 1px 2px #0000000d}.ngt-select:hover{background-color:var(--ngt-hover-bg);border-color:var(--ngt-primary);box-shadow:0 1px 3px #0000001a}.ngt-select:focus-visible{outline:none;border-color:var(--ngt-primary);box-shadow:0 0 0 2px rgba(var(--ngt-primary-rgb),.2),0 1px 3px #0000001a}.ngt-select.small{padding:var(--ngt-spacing-xs) var(--ngt-spacing-sm);font-size:var(--ngt-font-size-sm);height:24px}.ngt-select.open{border-color:var(--ngt-primary);box-shadow:0 0 0 2px rgba(var(--ngt-primary-rgb),.2),0 1px 3px #0000001a}.ngt-select.open .select__arrow{transform:rotate(180deg)}.ngt-select__value{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:1.4;margin-right:var(--ngt-spacing-sm);min-width:0;display:flex;align-items:center}.ngt-select__arrow{width:16px;height:16px;flex-shrink:0;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:center;background-size:contain;transition:transform .2s ease;opacity:.9}.ngt-select-menu{display:inline-flex;flex-direction:column;min-width:180px;background-color:var(--ngt-bg-primary);padding:var(--ngt-spacing-sm) 0;border:2px solid var(--ngt-border-primary);border-radius:var(--ngt-border-radius-large);box-shadow:0 4px 12px #00000026,0 2px 4px #0000001a;color:var(--ngt-text-primary);max-height:min(400px,70vh);overflow-y:auto;backdrop-filter:blur(8px);z-index:1000}.ngt-select-menu::-webkit-scrollbar{width:8px;height:8px}.ngt-select-menu::-webkit-scrollbar-track{background:transparent}.ngt-select-menu::-webkit-scrollbar-thumb{background-color:var(--ngt-border-primary);border-radius:4px;border:2px solid var(--ngt-bg-primary)}.ngt-select-menu::-webkit-scrollbar-thumb:hover{background-color:var(--ngt-text-secondary)}.select-menu-item{background-color:transparent;cursor:pointer;border:none;color:var(--ngt-text-primary);-webkit-user-select:none;user-select:none;min-width:64px;padding:var(--ngt-spacing-sm) var(--ngt-spacing-md) var(--ngt-spacing-sm) var(--ngt-spacing-lg);display:flex;align-items:center;flex-direction:row;flex:1;font-size:var(--ngt-font-size-xs);font-family:inherit;position:relative;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;font-weight:400;line-height:1.5;transition:background-color .15s ease}.select-menu-item:hover{background-color:#0000000f}.select-menu-item:active{background-color:rgba(var(--ngt-primary-rgb),.15)}.select-menu-item.selected{color:var(--ngt-primary);background-color:rgba(var(--ngt-primary-rgb),.08);font-weight:400}.select-menu-item.selected:hover{background-color:rgba(var(--ngt-primary-rgb),.12)}.select-menu-item.selected:before{content:\"\";position:absolute;left:0;top:8px;width:3px;height:calc(100% - 16px);background-color:var(--ngt-primary);border-radius:2px}.select-menu-item:focus-visible{outline:none;background-color:#0000000f}.select-menu-item:first-child{margin-top:var(--ngt-spacing-xs)}.select-menu-item:last-child{margin-bottom:var(--ngt-spacing-xs)}.select-overlay{backdrop-filter:blur(8px)}\n"] }]
3196
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{--ngt-border-radius-small: 4px;--ngt-border-radius-medium: 8px;--ngt-border-radius-large: 12px;--ngt-transition-default: all .2s ease-out;--ngt-transition-smooth: all .2s ease-in-out;--ngt-bg-primary: rgb(255, 255, 255);--ngt-bg-gradient: linear-gradient(180deg, rgb(243, 244, 246) 0%, rgba(243, 244, 246, .88) 100%);--ngt-text-primary: rgb(17, 24, 39);--ngt-text-secondary: rgb(55, 65, 81);--ngt-text-muted: rgb(107, 114, 128);--ngt-border-primary: #e5e7eb;--ngt-border-subtle: rgba(17, 24, 39, .1);--ngt-hover-bg: rgba(17, 24, 39, .05);--ngt-hover-danger: rgb(239, 68, 68);--ngt-shadow-toolbar: 0 2px 8px rgba(156, 163, 175, .2);--ngt-shadow-tooltip: 0 0 0 1px rgba(17, 24, 39, .05), 0 4px 8px rgba(107, 114, 128, .15), 0 2px 4px rgba(107, 114, 128, .1);--ngt-shadow-window: 0px 0px 0px 0px rgba(156, 163, 175, .1), 0px 1px 2px 0px rgba(156, 163, 175, .12), 0px 4px 4px 0px rgba(156, 163, 175, .1), 0px 10px 6px 0px rgba(156, 163, 175, .08), 0px 17px 7px 0px rgba(156, 163, 175, .05), 0px 26px 7px 0px rgba(156, 163, 175, .02);--ngt-spacing-xs: 4px;--ngt-spacing-sm: 6px;--ngt-spacing-md: 12px;--ngt-spacing-lg: 16px;--ngt-window-padding: 16px;--ngt-font-size-xxs: .65rem;--ngt-font-size-xs: .75rem;--ngt-font-size-sm: .875rem;--ngt-font-size-md: 1rem;--ngt-font-size-lg: 1.25rem;--ngt-font-size-xl: 2rem;--ngt-background-secondary: var(--ngt-bg-primary);--ngt-background-hover: var(--ngt-hover-bg);--ngt-primary: #df30d4;--ngt-primary-rgb: 223, 48, 212;--ngt-text-on-primary: rgb(255, 255, 255);--ngt-border-color: var(--ngt-border-primary);--ngt-note-background: rgb(219, 234, 254);--ngt-note-border: rgba(37, 99, 235, .2);--ngt-warning-background: rgb(254, 249, 195);--ngt-warning-border: rgba(202, 138, 4, .2);--ngt-error-background: rgb(254, 226, 226);--ngt-error-border: rgba(220, 38, 38, .2);display:inline-block;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",Segoe UI Symbol,\"Noto Color Emoji\"}.ngt-select{position:relative;width:100%;min-width:120px;display:flex;align-items:center;justify-content:space-between;padding:var(--ngt-spacing-sm) var(--ngt-spacing-md);border:1px solid var(--ngt-border-primary);border-radius:var(--ngt-border-radius-small);background-color:var(--ngt-bg-primary);color:var(--ngt-text-primary);font-size:var(--ngt-font-size-sm);cursor:pointer;-webkit-user-select:none;user-select:none;transition:var(--ngt-transition-default);outline:none;min-height:36px;box-sizing:border-box;box-shadow:0 1px 2px #0000000d}.ngt-select:hover{background-color:var(--ngt-hover-bg);border-color:var(--ngt-primary);box-shadow:0 1px 3px #0000001a}.ngt-select:focus-visible{outline:none;border-color:var(--ngt-primary);box-shadow:0 0 0 2px rgba(var(--ngt-primary-rgb),.2),0 1px 3px #0000001a}.ngt-select.small{padding:var(--ngt-spacing-xs) var(--ngt-spacing-sm);font-size:var(--ngt-font-size-sm);height:24px}.ngt-select.open{border-color:var(--ngt-primary);box-shadow:0 0 0 2px rgba(var(--ngt-primary-rgb),.2),0 1px 3px #0000001a}.ngt-select.open .select__arrow{transform:rotate(180deg)}.ngt-select.placeholder{color:var(--ngt-text-muted)}.ngt-select__value{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:1.4;margin-right:var(--ngt-spacing-sm);min-width:0;display:flex;align-items:center}.ngt-select__arrow{width:16px;height:16px;flex-shrink:0;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:center;background-size:contain;transition:transform .2s ease;opacity:.9}.ngt-select-menu{display:inline-flex;flex-direction:column;min-width:180px;background-color:var(--ngt-bg-primary);padding:var(--ngt-spacing-sm) 0;border:2px solid var(--ngt-border-primary);border-radius:var(--ngt-border-radius-large);box-shadow:0 4px 12px #00000026,0 2px 4px #0000001a;color:var(--ngt-text-primary);max-height:min(400px,70vh);overflow-y:auto;backdrop-filter:blur(8px);z-index:1000}.ngt-select-menu::-webkit-scrollbar{width:8px;height:8px}.ngt-select-menu::-webkit-scrollbar-track{background:transparent}.ngt-select-menu::-webkit-scrollbar-thumb{background-color:var(--ngt-border-primary);border-radius:4px;border:2px solid var(--ngt-bg-primary)}.ngt-select-menu::-webkit-scrollbar-thumb:hover{background-color:var(--ngt-text-secondary)}.select-menu-item{background-color:transparent;cursor:pointer;border:none;color:var(--ngt-text-primary);-webkit-user-select:none;user-select:none;min-width:64px;padding:var(--ngt-spacing-sm) var(--ngt-spacing-md) var(--ngt-spacing-sm) var(--ngt-spacing-lg);display:flex;align-items:center;flex-direction:row;flex:1;font-size:var(--ngt-font-size-xs);font-family:inherit;position:relative;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;font-weight:400;line-height:1.5;transition:background-color .15s ease}.select-menu-item:hover{background-color:#0000000f}.select-menu-item:active{background-color:rgba(var(--ngt-primary-rgb),.15)}.select-menu-item.selected{color:var(--ngt-primary);background-color:rgba(var(--ngt-primary-rgb),.08);font-weight:400}.select-menu-item.selected:hover{background-color:rgba(var(--ngt-primary-rgb),.12)}.select-menu-item.selected:before{content:\"\";position:absolute;left:0;top:8px;width:3px;height:calc(100% - 16px);background-color:var(--ngt-primary);border-radius:2px}.select-menu-item:focus-visible{outline:none;background-color:#0000000f}.select-menu-item:first-child{margin-top:var(--ngt-spacing-xs)}.select-menu-item:last-child{margin-bottom:var(--ngt-spacing-xs)}.select-overlay{backdrop-filter:blur(8px)}\n"] }]
3136
3197
  }] });
3137
3198
 
3138
3199
  class ToolbarToolButtonComponent {
@@ -3762,7 +3823,7 @@ class ToolbarAppFeaturesToolComponent {
3762
3823
  </ngt-list>
3763
3824
  </div>
3764
3825
  </ngt-toolbar-tool>
3765
- `, isInline: true, styles: [".container{position:relative;display:flex;flex-direction:column;height:100%;padding:0}.tool-header{position:relative;flex-shrink:0;display:flex;gap:var(--ngt-spacing-sm);margin-bottom:var(--ngt-spacing-sm);ngt-input{flex:1}.filter-wrapper{flex:0 0 auto;display:flex;align-items:center;gap:var(--ngt-spacing-md);.filter-icon{width:18px;height:18px;flex-shrink:0;opacity:.6}ngt-select{flex:0 0 auto;min-width:180px}}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "component", type: ToolbarToolComponent, selector: "ngt-toolbar-tool", inputs: ["options", "icon", "title", "badge"] }, { kind: "component", type: ToolbarInputComponent, selector: "ngt-input", inputs: ["value", "type", "placeholder", "ariaLabel", "inputClass"], outputs: ["valueChange"] }, { kind: "component", type: ToolbarSelectComponent, selector: "ngt-select", inputs: ["value", "options", "ariaLabel", "label", "size"], outputs: ["valueChange"] }, { kind: "component", type: ToolbarIconComponent, selector: "ngt-icon", inputs: ["name"] }, { kind: "component", type: ToolbarListComponent, selector: "ngt-list", inputs: ["hasItems", "hasResults", "emptyMessage", "emptyHint", "noResultsMessage"] }, { kind: "component", type: ToolbarListItemComponent, selector: "ngt-list-item", inputs: ["title", "description", "isForced", "currentValue", "originalValue"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3826
+ `, isInline: true, styles: [".container{position:relative;display:flex;flex-direction:column;height:100%;padding:0}.tool-header{position:relative;flex-shrink:0;display:flex;gap:var(--ngt-spacing-sm);margin-bottom:var(--ngt-spacing-sm);ngt-input{flex:1}.filter-wrapper{flex:0 0 auto;display:flex;align-items:center;gap:var(--ngt-spacing-md);.filter-icon{width:18px;height:18px;flex-shrink:0;opacity:.6}ngt-select{flex:0 0 auto;min-width:180px}}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "component", type: ToolbarToolComponent, selector: "ngt-toolbar-tool", inputs: ["options", "icon", "title", "badge"] }, { kind: "component", type: ToolbarInputComponent, selector: "ngt-input", inputs: ["value", "type", "placeholder", "ariaLabel", "inputClass"], outputs: ["valueChange"] }, { kind: "component", type: ToolbarSelectComponent, selector: "ngt-select", inputs: ["value", "options", "ariaLabel", "label", "size", "placeholder"], outputs: ["valueChange"] }, { kind: "component", type: ToolbarIconComponent, selector: "ngt-icon", inputs: ["name"] }, { kind: "component", type: ToolbarListComponent, selector: "ngt-list", inputs: ["hasItems", "hasResults", "emptyMessage", "emptyHint", "noResultsMessage"] }, { kind: "component", type: ToolbarListItemComponent, selector: "ngt-list-item", inputs: ["title", "description", "isForced", "currentValue", "originalValue"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3766
3827
  }
3767
3828
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarAppFeaturesToolComponent, decorators: [{
3768
3829
  type: Component,
@@ -3943,6 +4004,9 @@ class ToolbarFeatureFlagsToolComponent {
3943
4004
  return '';
3944
4005
  return flag.isEnabled ? 'on' : 'off';
3945
4006
  }
4007
+ getFlagPlaceholder(flag) {
4008
+ return flag.isEnabled ? 'On' : 'Off';
4009
+ }
3946
4010
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarFeatureFlagsToolComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3947
4011
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.7", type: ToolbarFeatureFlagsToolComponent, isStandalone: true, selector: "ngt-feature-flags-tool", ngImport: i0, template: `
3948
4012
  <ngt-toolbar-tool
@@ -3976,26 +4040,27 @@ class ToolbarFeatureFlagsToolComponent {
3976
4040
  noResultsMessage="No flags found matching your filter"
3977
4041
  >
3978
4042
  @for (flag of filteredFlags(); track flag.id) {
3979
- <ngt-list-item
3980
- [title]="flag.name"
3981
- [description]="flag.description"
3982
- [isForced]="flag.isForced"
3983
- [currentValue]="flag.isEnabled"
3984
- [originalValue]="flag.originalValue"
3985
- >
3986
- <ngt-select
3987
- [value]="getFlagValue(flag)"
3988
- [options]="flagValueOptions"
3989
- [ariaLabel]="'Set value for ' + flag.name"
3990
- (valueChange)="onFlagChange(flag.id, $event ?? '')"
3991
- size="small"
3992
- />
3993
- </ngt-list-item>
4043
+ <ngt-list-item
4044
+ [title]="flag.name"
4045
+ [description]="flag.description"
4046
+ [isForced]="flag.isForced"
4047
+ [currentValue]="flag.isEnabled"
4048
+ [originalValue]="flag.originalValue"
4049
+ >
4050
+ <ngt-select
4051
+ [value]="getFlagValue(flag)"
4052
+ [options]="flagValueOptions"
4053
+ [placeholder]="getFlagPlaceholder(flag)"
4054
+ [ariaLabel]="'Set value for ' + flag.name"
4055
+ (valueChange)="onFlagChange(flag.id, $event ?? '')"
4056
+ size="small"
4057
+ />
4058
+ </ngt-list-item>
3994
4059
  }
3995
4060
  </ngt-list>
3996
4061
  </div>
3997
4062
  </ngt-toolbar-tool>
3998
- `, isInline: true, styles: [".container{position:relative;display:flex;flex-direction:column;height:100%;padding:0}.tool-header{position:relative;flex-shrink:0;display:flex;gap:var(--ngt-spacing-sm);margin-bottom:var(--ngt-spacing-sm);ngt-input{flex:1}.filter-wrapper{flex:0 0 auto;display:flex;align-items:center;gap:var(--ngt-spacing-md);.filter-icon{width:18px;height:18px;flex-shrink:0;opacity:.6}ngt-select{flex:0 0 auto;min-width:180px}}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "component", type: ToolbarToolComponent, selector: "ngt-toolbar-tool", inputs: ["options", "icon", "title", "badge"] }, { kind: "component", type: ToolbarInputComponent, selector: "ngt-input", inputs: ["value", "type", "placeholder", "ariaLabel", "inputClass"], outputs: ["valueChange"] }, { kind: "component", type: ToolbarSelectComponent, selector: "ngt-select", inputs: ["value", "options", "ariaLabel", "label", "size"], outputs: ["valueChange"] }, { kind: "component", type: ToolbarIconComponent, selector: "ngt-icon", inputs: ["name"] }, { kind: "component", type: ToolbarListComponent, selector: "ngt-list", inputs: ["hasItems", "hasResults", "emptyMessage", "emptyHint", "noResultsMessage"] }, { kind: "component", type: ToolbarListItemComponent, selector: "ngt-list-item", inputs: ["title", "description", "isForced", "currentValue", "originalValue"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4063
+ `, isInline: true, styles: [".container{position:relative;display:flex;flex-direction:column;height:100%;padding:0}.tool-header{position:relative;flex-shrink:0;display:flex;gap:var(--ngt-spacing-sm);margin-bottom:var(--ngt-spacing-sm);ngt-input{flex:1}.filter-wrapper{flex:0 0 auto;display:flex;align-items:center;gap:var(--ngt-spacing-md);.filter-icon{width:18px;height:18px;flex-shrink:0;opacity:.6}ngt-select{flex:0 0 auto;min-width:180px}}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "component", type: ToolbarToolComponent, selector: "ngt-toolbar-tool", inputs: ["options", "icon", "title", "badge"] }, { kind: "component", type: ToolbarInputComponent, selector: "ngt-input", inputs: ["value", "type", "placeholder", "ariaLabel", "inputClass"], outputs: ["valueChange"] }, { kind: "component", type: ToolbarSelectComponent, selector: "ngt-select", inputs: ["value", "options", "ariaLabel", "label", "size", "placeholder"], outputs: ["valueChange"] }, { kind: "component", type: ToolbarIconComponent, selector: "ngt-icon", inputs: ["name"] }, { kind: "component", type: ToolbarListComponent, selector: "ngt-list", inputs: ["hasItems", "hasResults", "emptyMessage", "emptyHint", "noResultsMessage"] }, { kind: "component", type: ToolbarListItemComponent, selector: "ngt-list-item", inputs: ["title", "description", "isForced", "currentValue", "originalValue"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3999
4064
  }
4000
4065
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarFeatureFlagsToolComponent, decorators: [{
4001
4066
  type: Component,
@@ -4039,21 +4104,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImpor
4039
4104
  noResultsMessage="No flags found matching your filter"
4040
4105
  >
4041
4106
  @for (flag of filteredFlags(); track flag.id) {
4042
- <ngt-list-item
4043
- [title]="flag.name"
4044
- [description]="flag.description"
4045
- [isForced]="flag.isForced"
4046
- [currentValue]="flag.isEnabled"
4047
- [originalValue]="flag.originalValue"
4048
- >
4049
- <ngt-select
4050
- [value]="getFlagValue(flag)"
4051
- [options]="flagValueOptions"
4052
- [ariaLabel]="'Set value for ' + flag.name"
4053
- (valueChange)="onFlagChange(flag.id, $event ?? '')"
4054
- size="small"
4055
- />
4056
- </ngt-list-item>
4107
+ <ngt-list-item
4108
+ [title]="flag.name"
4109
+ [description]="flag.description"
4110
+ [isForced]="flag.isForced"
4111
+ [currentValue]="flag.isEnabled"
4112
+ [originalValue]="flag.originalValue"
4113
+ >
4114
+ <ngt-select
4115
+ [value]="getFlagValue(flag)"
4116
+ [options]="flagValueOptions"
4117
+ [placeholder]="getFlagPlaceholder(flag)"
4118
+ [ariaLabel]="'Set value for ' + flag.name"
4119
+ (valueChange)="onFlagChange(flag.id, $event ?? '')"
4120
+ size="small"
4121
+ />
4122
+ </ngt-list-item>
4057
4123
  }
4058
4124
  </ngt-list>
4059
4125
  </div>
@@ -4496,7 +4562,7 @@ class ToolbarLanguageToolComponent {
4496
4562
  />
4497
4563
  </div>
4498
4564
  </ngt-toolbar-tool>
4499
- `, isInline: true, styles: [":host{--ngt-border-radius-small: 4px;--ngt-border-radius-medium: 8px;--ngt-border-radius-large: 12px;--ngt-transition-default: all .2s ease-out;--ngt-transition-smooth: all .2s ease-in-out;--ngt-bg-primary: rgb(255, 255, 255);--ngt-bg-gradient: linear-gradient(180deg, rgb(243, 244, 246) 0%, rgba(243, 244, 246, .88) 100%);--ngt-text-primary: rgb(17, 24, 39);--ngt-text-secondary: rgb(55, 65, 81);--ngt-text-muted: rgb(107, 114, 128);--ngt-border-primary: #e5e7eb;--ngt-border-subtle: rgba(17, 24, 39, .1);--ngt-hover-bg: rgba(17, 24, 39, .05);--ngt-hover-danger: rgb(239, 68, 68);--ngt-shadow-toolbar: 0 2px 8px rgba(156, 163, 175, .2);--ngt-shadow-tooltip: 0 0 0 1px rgba(17, 24, 39, .05), 0 4px 8px rgba(107, 114, 128, .15), 0 2px 4px rgba(107, 114, 128, .1);--ngt-shadow-window: 0px 0px 0px 0px rgba(156, 163, 175, .1), 0px 1px 2px 0px rgba(156, 163, 175, .12), 0px 4px 4px 0px rgba(156, 163, 175, .1), 0px 10px 6px 0px rgba(156, 163, 175, .08), 0px 17px 7px 0px rgba(156, 163, 175, .05), 0px 26px 7px 0px rgba(156, 163, 175, .02);--ngt-spacing-xs: 4px;--ngt-spacing-sm: 6px;--ngt-spacing-md: 12px;--ngt-spacing-lg: 16px;--ngt-window-padding: 16px;--ngt-font-size-xxs: .65rem;--ngt-font-size-xs: .75rem;--ngt-font-size-sm: .875rem;--ngt-font-size-md: 1rem;--ngt-font-size-lg: 1.25rem;--ngt-font-size-xl: 2rem;--ngt-background-secondary: var(--ngt-bg-primary);--ngt-background-hover: var(--ngt-hover-bg);--ngt-primary: #df30d4;--ngt-primary-rgb: 223, 48, 212;--ngt-text-on-primary: rgb(255, 255, 255);--ngt-border-color: var(--ngt-border-primary);--ngt-note-background: rgb(219, 234, 254);--ngt-note-border: rgba(37, 99, 235, .2);--ngt-warning-background: rgb(254, 249, 195);--ngt-warning-border: rgba(202, 138, 4, .2);--ngt-error-background: rgb(254, 226, 226);--ngt-error-border: rgba(220, 38, 38, .2)}.language-select{display:flex;flex-direction:row;gap:.5rem;align-items:center;justify-content:space-between}\n"], dependencies: [{ kind: "component", type: ToolbarToolComponent, selector: "ngt-toolbar-tool", inputs: ["options", "icon", "title", "badge"] }, { kind: "component", type: ToolbarSelectComponent, selector: "ngt-select", inputs: ["value", "options", "ariaLabel", "label", "size"], outputs: ["valueChange"] }] }); }
4565
+ `, isInline: true, styles: [":host{--ngt-border-radius-small: 4px;--ngt-border-radius-medium: 8px;--ngt-border-radius-large: 12px;--ngt-transition-default: all .2s ease-out;--ngt-transition-smooth: all .2s ease-in-out;--ngt-bg-primary: rgb(255, 255, 255);--ngt-bg-gradient: linear-gradient(180deg, rgb(243, 244, 246) 0%, rgba(243, 244, 246, .88) 100%);--ngt-text-primary: rgb(17, 24, 39);--ngt-text-secondary: rgb(55, 65, 81);--ngt-text-muted: rgb(107, 114, 128);--ngt-border-primary: #e5e7eb;--ngt-border-subtle: rgba(17, 24, 39, .1);--ngt-hover-bg: rgba(17, 24, 39, .05);--ngt-hover-danger: rgb(239, 68, 68);--ngt-shadow-toolbar: 0 2px 8px rgba(156, 163, 175, .2);--ngt-shadow-tooltip: 0 0 0 1px rgba(17, 24, 39, .05), 0 4px 8px rgba(107, 114, 128, .15), 0 2px 4px rgba(107, 114, 128, .1);--ngt-shadow-window: 0px 0px 0px 0px rgba(156, 163, 175, .1), 0px 1px 2px 0px rgba(156, 163, 175, .12), 0px 4px 4px 0px rgba(156, 163, 175, .1), 0px 10px 6px 0px rgba(156, 163, 175, .08), 0px 17px 7px 0px rgba(156, 163, 175, .05), 0px 26px 7px 0px rgba(156, 163, 175, .02);--ngt-spacing-xs: 4px;--ngt-spacing-sm: 6px;--ngt-spacing-md: 12px;--ngt-spacing-lg: 16px;--ngt-window-padding: 16px;--ngt-font-size-xxs: .65rem;--ngt-font-size-xs: .75rem;--ngt-font-size-sm: .875rem;--ngt-font-size-md: 1rem;--ngt-font-size-lg: 1.25rem;--ngt-font-size-xl: 2rem;--ngt-background-secondary: var(--ngt-bg-primary);--ngt-background-hover: var(--ngt-hover-bg);--ngt-primary: #df30d4;--ngt-primary-rgb: 223, 48, 212;--ngt-text-on-primary: rgb(255, 255, 255);--ngt-border-color: var(--ngt-border-primary);--ngt-note-background: rgb(219, 234, 254);--ngt-note-border: rgba(37, 99, 235, .2);--ngt-warning-background: rgb(254, 249, 195);--ngt-warning-border: rgba(202, 138, 4, .2);--ngt-error-background: rgb(254, 226, 226);--ngt-error-border: rgba(220, 38, 38, .2)}.language-select{display:flex;flex-direction:row;gap:.5rem;align-items:center;justify-content:space-between}\n"], dependencies: [{ kind: "component", type: ToolbarToolComponent, selector: "ngt-toolbar-tool", inputs: ["options", "icon", "title", "badge"] }, { kind: "component", type: ToolbarSelectComponent, selector: "ngt-select", inputs: ["value", "options", "ariaLabel", "label", "size", "placeholder"], outputs: ["valueChange"] }] }); }
4500
4566
  }
4501
4567
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarLanguageToolComponent, decorators: [{
4502
4568
  type: Component,
@@ -4685,7 +4751,7 @@ class ToolbarPermissionsToolComponent {
4685
4751
  </ngt-list>
4686
4752
  </div>
4687
4753
  </ngt-toolbar-tool>
4688
- `, isInline: true, styles: [".container{position:relative;display:flex;flex-direction:column;height:100%;padding:0}.tool-header{position:relative;flex-shrink:0;display:flex;gap:var(--ngt-spacing-sm);margin-bottom:var(--ngt-spacing-sm);ngt-input{flex:1}.filter-wrapper{flex:0 0 auto;display:flex;align-items:center;gap:var(--ngt-spacing-md);.filter-icon{width:18px;height:18px;flex-shrink:0;opacity:.6}ngt-select{flex:0 0 auto;min-width:180px}}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "component", type: ToolbarToolComponent, selector: "ngt-toolbar-tool", inputs: ["options", "icon", "title", "badge"] }, { kind: "component", type: ToolbarInputComponent, selector: "ngt-input", inputs: ["value", "type", "placeholder", "ariaLabel", "inputClass"], outputs: ["valueChange"] }, { kind: "component", type: ToolbarSelectComponent, selector: "ngt-select", inputs: ["value", "options", "ariaLabel", "label", "size"], outputs: ["valueChange"] }, { kind: "component", type: ToolbarIconComponent, selector: "ngt-icon", inputs: ["name"] }, { kind: "component", type: ToolbarListComponent, selector: "ngt-list", inputs: ["hasItems", "hasResults", "emptyMessage", "emptyHint", "noResultsMessage"] }, { kind: "component", type: ToolbarListItemComponent, selector: "ngt-list-item", inputs: ["title", "description", "isForced", "currentValue", "originalValue"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4754
+ `, isInline: true, styles: [".container{position:relative;display:flex;flex-direction:column;height:100%;padding:0}.tool-header{position:relative;flex-shrink:0;display:flex;gap:var(--ngt-spacing-sm);margin-bottom:var(--ngt-spacing-sm);ngt-input{flex:1}.filter-wrapper{flex:0 0 auto;display:flex;align-items:center;gap:var(--ngt-spacing-md);.filter-icon{width:18px;height:18px;flex-shrink:0;opacity:.6}ngt-select{flex:0 0 auto;min-width:180px}}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "component", type: ToolbarToolComponent, selector: "ngt-toolbar-tool", inputs: ["options", "icon", "title", "badge"] }, { kind: "component", type: ToolbarInputComponent, selector: "ngt-input", inputs: ["value", "type", "placeholder", "ariaLabel", "inputClass"], outputs: ["valueChange"] }, { kind: "component", type: ToolbarSelectComponent, selector: "ngt-select", inputs: ["value", "options", "ariaLabel", "label", "size", "placeholder"], outputs: ["valueChange"] }, { kind: "component", type: ToolbarIconComponent, selector: "ngt-icon", inputs: ["name"] }, { kind: "component", type: ToolbarListComponent, selector: "ngt-list", inputs: ["hasItems", "hasResults", "emptyMessage", "emptyHint", "noResultsMessage"] }, { kind: "component", type: ToolbarListItemComponent, selector: "ngt-list-item", inputs: ["title", "description", "isForced", "currentValue", "originalValue"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4689
4755
  }
4690
4756
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarPermissionsToolComponent, decorators: [{
4691
4757
  type: Component,
@@ -4849,6 +4915,52 @@ class ToolbarInternalPresetsService {
4849
4915
  getPresetById(presetId) {
4850
4916
  return this.presetsSubject.value.find((p) => p.id === presetId);
4851
4917
  }
4918
+ /**
4919
+ * Toggle favorite status for a preset
4920
+ */
4921
+ toggleFavorite(presetId) {
4922
+ const presets = this.presetsSubject.value.map((p) => p.id === presetId ? { ...p, isFavorite: !p.isFavorite } : p);
4923
+ this.presetsSubject.next(presets);
4924
+ this.storageService.set(this.STORAGE_KEY, presets);
4925
+ }
4926
+ /**
4927
+ * Apply a preset with partial options (only selected categories)
4928
+ */
4929
+ async partialApplyPreset(presetId, options) {
4930
+ const preset = this.presetsSubject.value.find((p) => p.id === presetId);
4931
+ if (!preset)
4932
+ return;
4933
+ if (options.applyFeatureFlags) {
4934
+ this.featureFlagsService.applyPresetFlags(preset.config.featureFlags);
4935
+ }
4936
+ if (options.applyLanguage) {
4937
+ await this.languageService.applyPresetLanguage(preset.config.language);
4938
+ }
4939
+ if (options.applyPermissions) {
4940
+ this.permissionsService.applyPresetPermissions(preset.config.permissions);
4941
+ }
4942
+ if (options.applyAppFeatures) {
4943
+ this.appFeaturesService.applyForcedState(preset.config.appFeatures);
4944
+ }
4945
+ }
4946
+ /**
4947
+ * Update only the metadata (name, description) of a preset without changing config
4948
+ */
4949
+ updatePresetMetadata(presetId, name, description) {
4950
+ const presets = this.presetsSubject.value.map((preset) => {
4951
+ if (preset.id === presetId) {
4952
+ return {
4953
+ ...preset,
4954
+ name,
4955
+ description,
4956
+ updatedAt: new Date().toISOString(),
4957
+ };
4958
+ }
4959
+ return preset;
4960
+ });
4961
+ this.presetsSubject.next(presets);
4962
+ this.storageService.set(this.STORAGE_KEY, presets);
4963
+ }
4852
4964
  /**
4853
4965
  * Capture current configuration from all tools
4854
4966
  */
@@ -4927,27 +5039,80 @@ class ToolbarPresetsToolComponent {
4927
5039
  this.appFeaturesService = inject(ToolbarInternalAppFeaturesService);
4928
5040
  this.languageService = inject(ToolbarInternalLanguageService);
4929
5041
  this.state = inject(ToolbarStateService);
4930
- // Signals
5042
+ // View state signals
4931
5043
  this.viewMode = signal('list');
4932
5044
  this.searchQuery = signal('');
5045
+ // Create form signals
4933
5046
  this.presetName = signal('');
4934
5047
  this.presetDescription = signal('');
4935
5048
  this.includeFeatureFlags = signal(true);
4936
5049
  this.includePermissions = signal(true);
4937
5050
  this.includeAppFeatures = signal(true);
4938
5051
  this.includeLanguage = signal(true);
4939
- // Track selected individual items
4940
5052
  this.selectedFlagIds = signal(new Set());
4941
5053
  this.selectedPermissionIds = signal(new Set());
4942
5054
  this.selectedFeatureIds = signal(new Set());
5055
+ // Edit mode signals
5056
+ this.editingPresetId = signal(null);
5057
+ this.editName = signal('');
5058
+ this.editDescription = signal('');
5059
+ // Import mode signals
5060
+ this.importJson = signal('');
5061
+ this.importError = signal(null);
5062
+ this.isDragOver = signal(false);
5063
+ // Apply mode signals
5064
+ this.applyingPresetId = signal(null);
5065
+ this.applyFeatureFlags = signal(true);
5066
+ this.applyPermissions = signal(true);
5067
+ this.applyAppFeatures = signal(true);
5068
+ this.applyLanguage = signal(true);
5069
+ // Delete confirmation signals
5070
+ this.deletePresetId = signal(null);
5071
+ // Toast signals
5072
+ this.toastMessage = signal(null);
5073
+ this.toastType = signal('success');
5074
+ // Validation signals
5075
+ this.nameError = signal(null);
5076
+ // Auto-dismiss toast effect
5077
+ this.toastTimeout = null;
5078
+ // Computed values
4943
5079
  this.presets = this.presetsService.presets;
4944
5080
  this.filteredPresets = computed(() => {
4945
5081
  const query = this.searchQuery().toLowerCase();
4946
5082
  return this.presets().filter((preset) => preset.name.toLowerCase().includes(query) ||
4947
5083
  preset.description?.toLowerCase().includes(query));
4948
5084
  });
5085
+ this.sortedPresets = computed(() => {
5086
+ const presets = this.filteredPresets();
5087
+ return [...presets].sort((a, b) => {
5088
+ if (a.isFavorite && !b.isFavorite)
5089
+ return -1;
5090
+ if (!a.isFavorite && b.isFavorite)
5091
+ return 1;
5092
+ return 0;
5093
+ });
5094
+ });
4949
5095
  this.hasNoPresets = computed(() => this.presets().length === 0);
4950
5096
  this.hasNoFilteredPresets = computed(() => this.filteredPresets().length === 0);
5097
+ this.editingPreset = computed(() => {
5098
+ const id = this.editingPresetId();
5099
+ if (!id)
5100
+ return null;
5101
+ return this.presets().find((p) => p.id === id) || null;
5102
+ });
5103
+ this.applyingPreset = computed(() => {
5104
+ const id = this.applyingPresetId();
5105
+ if (!id)
5106
+ return null;
5107
+ return this.presets().find((p) => p.id === id) || null;
5108
+ });
5109
+ this.deletePresetName = computed(() => {
5110
+ const id = this.deletePresetId();
5111
+ if (!id)
5112
+ return '';
5113
+ const preset = this.presets().find((p) => p.id === id);
5114
+ return preset?.name || '';
5115
+ });
4951
5116
  // Tool availability (based on config)
4952
5117
  this.isFeatureFlagsEnabled = computed(() => this.state.config().showFeatureFlagsTool ?? true);
4953
5118
  this.isPermissionsEnabled = computed(() => this.state.config().showPermissionsTool ?? true);
@@ -4972,15 +5137,38 @@ class ToolbarPresetsToolComponent {
4972
5137
  id: 'ngt-presets',
4973
5138
  isBeta: true,
4974
5139
  };
5140
+ effect(() => {
5141
+ const message = this.toastMessage();
5142
+ if (message) {
5143
+ if (this.toastTimeout) {
5144
+ clearTimeout(this.toastTimeout);
5145
+ }
5146
+ this.toastTimeout = setTimeout(() => {
5147
+ this.toastMessage.set(null);
5148
+ }, 3000);
5149
+ }
5150
+ });
4975
5151
  }
4976
- // Public methods
5152
+ // Toast helper
5153
+ showToast(message, type = 'success') {
5154
+ this.toastMessage.set(message);
5155
+ this.toastType.set(type);
5156
+ }
5157
+ // View mode switching
4977
5158
  onSearchChange(query) {
4978
5159
  this.searchQuery.set(query);
4979
5160
  }
5161
+ onSwitchToListMode() {
5162
+ this.viewMode.set('list');
5163
+ this.nameError.set(null);
5164
+ this.importError.set(null);
5165
+ this.deletePresetId.set(null);
5166
+ }
4980
5167
  onSwitchToCreateMode() {
4981
5168
  this.viewMode.set('create');
4982
5169
  this.presetName.set('');
4983
5170
  this.presetDescription.set('');
5171
+ this.nameError.set(null);
4984
5172
  // Reset checkboxes - only enable categories that have forced items
4985
5173
  this.includeFeatureFlags.set(this.getCurrentFlagsCount() > 0);
4986
5174
  this.includePermissions.set(this.getCurrentPermissionsCount() > 0);
@@ -4992,14 +5180,33 @@ class ToolbarPresetsToolComponent {
4992
5180
  this.selectedPermissionIds.set(new Set(this.forcedPermissions().map((p) => p.id)));
4993
5181
  this.selectedFeatureIds.set(new Set(this.forcedAppFeatures().map((f) => f.id)));
4994
5182
  }
4995
- onSwitchToListMode() {
4996
- this.viewMode.set('list');
5183
+ onSwitchToImportMode() {
5184
+ this.viewMode.set('import');
5185
+ this.importJson.set('');
5186
+ this.importError.set(null);
5187
+ this.isDragOver.set(false);
5188
+ }
5189
+ // Create preset
5190
+ onPresetNameChange(value) {
5191
+ this.presetName.set(value);
5192
+ if (value.trim()) {
5193
+ this.nameError.set(null);
5194
+ }
4997
5195
  }
4998
5196
  onSavePreset(event) {
4999
5197
  event.preventDefault();
5000
- if (!this.presetName())
5198
+ const name = this.presetName().trim();
5199
+ if (!name) {
5200
+ this.nameError.set('Name is required');
5001
5201
  return;
5002
- this.presetsService.saveCurrentAsPreset(this.presetName(), this.presetDescription(), {
5202
+ }
5203
+ // Check for duplicate names
5204
+ const existingPreset = this.presets().find((p) => p.name.toLowerCase() === name.toLowerCase());
5205
+ if (existingPreset) {
5206
+ this.nameError.set('A preset with this name already exists');
5207
+ return;
5208
+ }
5209
+ this.presetsService.saveCurrentAsPreset(name, this.presetDescription(), {
5003
5210
  includeFeatureFlags: this.includeFeatureFlags(),
5004
5211
  includePermissions: this.includePermissions(),
5005
5212
  includeAppFeatures: this.includeAppFeatures(),
@@ -5008,14 +5215,153 @@ class ToolbarPresetsToolComponent {
5008
5215
  selectedPermissionIds: Array.from(this.selectedPermissionIds()),
5009
5216
  selectedFeatureIds: Array.from(this.selectedFeatureIds()),
5010
5217
  });
5218
+ this.showToast('Preset created successfully');
5219
+ this.onSwitchToListMode();
5220
+ }
5221
+ // Edit preset
5222
+ onStartEdit(presetId) {
5223
+ const preset = this.presets().find((p) => p.id === presetId);
5224
+ if (!preset)
5225
+ return;
5226
+ this.editingPresetId.set(presetId);
5227
+ this.editName.set(preset.name);
5228
+ this.editDescription.set(preset.description || '');
5229
+ this.nameError.set(null);
5230
+ this.viewMode.set('edit');
5231
+ }
5232
+ onEditNameChange(value) {
5233
+ this.editName.set(value);
5234
+ if (value.trim()) {
5235
+ this.nameError.set(null);
5236
+ }
5237
+ }
5238
+ onSaveEdit(event) {
5239
+ event.preventDefault();
5240
+ const name = this.editName().trim();
5241
+ const presetId = this.editingPresetId();
5242
+ if (!name) {
5243
+ this.nameError.set('Name is required');
5244
+ return;
5245
+ }
5246
+ if (!presetId)
5247
+ return;
5248
+ // Check for duplicate names (excluding current preset)
5249
+ const existingPreset = this.presets().find((p) => p.id !== presetId && p.name.toLowerCase() === name.toLowerCase());
5250
+ if (existingPreset) {
5251
+ this.nameError.set('A preset with this name already exists');
5252
+ return;
5253
+ }
5254
+ this.presetsService.updatePresetMetadata(presetId, name, this.editDescription());
5255
+ this.showToast('Preset updated successfully');
5011
5256
  this.onSwitchToListMode();
5012
5257
  }
5013
- onApplyPreset(presetId) {
5014
- this.presetsService.applyPreset(presetId);
5258
+ onReplaceConfig() {
5259
+ const presetId = this.editingPresetId();
5260
+ if (!presetId)
5261
+ return;
5262
+ this.presetsService.updatePreset(presetId);
5263
+ this.showToast('Configuration replaced with current state');
5264
+ }
5265
+ // Import preset
5266
+ onDragOver(event) {
5267
+ event.preventDefault();
5268
+ event.stopPropagation();
5269
+ this.isDragOver.set(true);
5270
+ }
5271
+ onDragLeave(event) {
5272
+ event.preventDefault();
5273
+ event.stopPropagation();
5274
+ this.isDragOver.set(false);
5275
+ }
5276
+ onFileDrop(event) {
5277
+ event.preventDefault();
5278
+ event.stopPropagation();
5279
+ this.isDragOver.set(false);
5280
+ const files = event.dataTransfer?.files;
5281
+ if (files && files.length > 0) {
5282
+ this.processFile(files[0]);
5283
+ }
5284
+ }
5285
+ onFileSelect(event) {
5286
+ const input = event.target;
5287
+ if (input.files && input.files.length > 0) {
5288
+ this.processFile(input.files[0]);
5289
+ }
5290
+ }
5291
+ processFile(file) {
5292
+ if (!file.name.endsWith('.json')) {
5293
+ this.importError.set('Please select a .json file');
5294
+ return;
5295
+ }
5296
+ const reader = new FileReader();
5297
+ reader.onload = (e) => {
5298
+ const content = e.target?.result;
5299
+ this.importJson.set(content);
5300
+ this.importError.set(null);
5301
+ };
5302
+ reader.onerror = () => {
5303
+ this.importError.set('Failed to read file');
5304
+ };
5305
+ reader.readAsText(file);
5306
+ }
5307
+ onImportJsonChange(event) {
5308
+ const textarea = event.target;
5309
+ this.importJson.set(textarea.value);
5310
+ this.importError.set(null);
5015
5311
  }
5312
+ onImportPreset() {
5313
+ const json = this.importJson().trim();
5314
+ if (!json) {
5315
+ this.importError.set('Please provide JSON content');
5316
+ return;
5317
+ }
5318
+ try {
5319
+ const parsed = JSON.parse(json);
5320
+ // Validate required fields
5321
+ if (!parsed.name) {
5322
+ this.importError.set('Preset must have a name');
5323
+ return;
5324
+ }
5325
+ if (!parsed.config) {
5326
+ this.importError.set('Preset must have a config object');
5327
+ return;
5328
+ }
5329
+ this.presetsService.addPreset(parsed);
5330
+ this.showToast('Preset imported successfully');
5331
+ this.onSwitchToListMode();
5332
+ }
5333
+ catch {
5334
+ this.importError.set('Invalid JSON format');
5335
+ }
5336
+ }
5337
+ // Apply preset (partial)
5338
+ onStartApply(presetId) {
5339
+ this.applyingPresetId.set(presetId);
5340
+ this.applyFeatureFlags.set(true);
5341
+ this.applyPermissions.set(true);
5342
+ this.applyAppFeatures.set(true);
5343
+ this.applyLanguage.set(true);
5344
+ this.viewMode.set('apply');
5345
+ }
5346
+ onConfirmPartialApply() {
5347
+ const presetId = this.applyingPresetId();
5348
+ if (!presetId)
5349
+ return;
5350
+ this.presetsService.partialApplyPreset(presetId, {
5351
+ applyFeatureFlags: this.applyFeatureFlags(),
5352
+ applyPermissions: this.applyPermissions(),
5353
+ applyAppFeatures: this.applyAppFeatures(),
5354
+ applyLanguage: this.applyLanguage(),
5355
+ });
5356
+ this.showToast('Preset applied successfully');
5357
+ this.onSwitchToListMode();
5358
+ }
5359
+ // Update preset (with current state)
5016
5360
  onUpdatePreset(presetId) {
5017
5361
  this.presetsService.updatePreset(presetId);
5362
+ this.showToast('Preset updated with current state');
5018
5363
  }
5364
+ // Export preset
5019
5365
  onExportPreset(presetId) {
5020
5366
  const preset = this.presets().find((p) => p.id === presetId);
5021
5367
  if (!preset)
@@ -5028,11 +5374,25 @@ class ToolbarPresetsToolComponent {
5028
5374
  link.download = `preset-${preset.name.toLowerCase().replace(/\s+/g, '-')}.json`;
5029
5375
  link.click();
5030
5376
  URL.revokeObjectURL(url);
5377
+ this.showToast('Preset exported');
5031
5378
  }
5379
+ // Delete preset
5032
5380
  onDeletePreset(presetId) {
5033
- if (confirm('Are you sure you want to delete this preset?')) {
5381
+ this.deletePresetId.set(presetId);
5382
+ this.viewMode.set('delete');
5383
+ }
5384
+ onConfirmDelete() {
5385
+ const presetId = this.deletePresetId();
5386
+ if (presetId) {
5034
5387
  this.presetsService.deletePreset(presetId);
5388
+ this.showToast('Preset deleted');
5035
5389
  }
5390
+ this.deletePresetId.set(null);
5391
+ this.viewMode.set('list');
5392
+ }
5393
+ // Favorites
5394
+ onToggleFavorite(presetId) {
5395
+ this.presetsService.toggleFavorite(presetId);
5036
5396
  }
5037
5397
  // Protected methods
5038
5398
  getCurrentFlagsCount() {
@@ -5053,9 +5413,44 @@ class ToolbarPresetsToolComponent {
5053
5413
  formatDate(isoString) {
5054
5414
  return new Date(isoString).toLocaleDateString();
5055
5415
  }
5056
- /**
5057
- * Toggle selection of an individual item
5058
- */
5416
+ getPresetTooltip(preset) {
5417
+ const lines = [];
5418
+ // Feature Flags
5419
+ const flagsEnabled = preset.config.featureFlags.enabled;
5420
+ const flagsDisabled = preset.config.featureFlags.disabled;
5421
+ if (flagsEnabled.length > 0 || flagsDisabled.length > 0) {
5422
+ lines.push('Feature Flags:');
5423
+ flagsEnabled.forEach(id => lines.push(` ✓ ${id}: ON`));
5424
+ flagsDisabled.forEach(id => lines.push(` ✗ ${id}: OFF`));
5425
+ }
5426
+ // Permissions
5427
+ const permsGranted = preset.config.permissions.granted;
5428
+ const permsDenied = preset.config.permissions.denied;
5429
+ if (permsGranted.length > 0 || permsDenied.length > 0) {
5430
+ if (lines.length > 0)
5431
+ lines.push('');
5432
+ lines.push('Permissions:');
5433
+ permsGranted.forEach(id => lines.push(` ✓ ${id}: GRANTED`));
5434
+ permsDenied.forEach(id => lines.push(` ✗ ${id}: DENIED`));
5435
+ }
5436
+ // App Features
5437
+ const featuresEnabled = preset.config.appFeatures.enabled;
5438
+ const featuresDisabled = preset.config.appFeatures.disabled;
5439
+ if (featuresEnabled.length > 0 || featuresDisabled.length > 0) {
5440
+ if (lines.length > 0)
5441
+ lines.push('');
5442
+ lines.push('App Features:');
5443
+ featuresEnabled.forEach(id => lines.push(` ✓ ${id}: ON`));
5444
+ featuresDisabled.forEach(id => lines.push(` ✗ ${id}: OFF`));
5445
+ }
5446
+ // Language
5447
+ if (preset.config.language) {
5448
+ if (lines.length > 0)
5449
+ lines.push('');
5450
+ lines.push(`Language: ${preset.config.language}`);
5451
+ }
5452
+ return lines.length > 0 ? lines.join('\n') : 'No configuration';
5453
+ }
5059
5454
  toggleItemSelection(signal, itemId) {
5060
5455
  const current = new Set(signal());
5061
5456
  if (current.has(itemId)) {
@@ -5066,9 +5461,6 @@ class ToolbarPresetsToolComponent {
5066
5461
  }
5067
5462
  signal.set(current);
5068
5463
  }
5069
- /**
5070
- * Check if an item is selected
5071
- */
5072
5464
  isItemSelected(selectedSet, itemId) {
5073
5465
  return selectedSet.has(itemId);
5074
5466
  }
@@ -5076,8 +5468,17 @@ class ToolbarPresetsToolComponent {
5076
5468
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.7", type: ToolbarPresetsToolComponent, isStandalone: true, selector: "ngt-presets-tool", ngImport: i0, template: `
5077
5469
  <ngt-toolbar-tool [options]="options" title="Presets" icon="layout">
5078
5470
  <div class="container">
5471
+ <!-- Toast Notification -->
5472
+ @if (toastMessage()) {
5473
+ <div class="toast" [class.toast--success]="toastType() === 'success'" [class.toast--error]="toastType() === 'error'">
5474
+ <span class="toast__icon">{{ toastType() === 'success' ? '✓' : '✕' }}</span>
5475
+ <span class="toast__message">{{ toastMessage() }}</span>
5476
+ </div>
5477
+ }
5478
+
5479
+
5079
5480
  <!-- Mode Toggle -->
5080
- @if (!hasNoPresets() || viewMode() === 'create') {
5481
+ @if (!hasNoPresets() || viewMode() !== 'list') {
5081
5482
  <div class="tool-header">
5082
5483
  @if (viewMode() === 'list') {
5083
5484
  <ngt-input
@@ -5086,18 +5487,24 @@ class ToolbarPresetsToolComponent {
5086
5487
  placeholder="Search presets..."
5087
5488
  [ariaLabel]="'Search presets'"
5088
5489
  />
5490
+ <ngt-button
5491
+ (click)="onSwitchToImportMode()"
5492
+ [ariaLabel]="'Import preset'"
5493
+ >
5494
+ Import
5495
+ </ngt-button>
5089
5496
  <ngt-button
5090
5497
  (click)="onSwitchToCreateMode()"
5091
5498
  [ariaLabel]="'Create new preset'"
5092
5499
  >
5093
- New Preset
5500
+ New
5094
5501
  </ngt-button>
5095
5502
  } @else {
5096
5503
  <ngt-button
5097
5504
  (click)="onSwitchToListMode()"
5098
5505
  [ariaLabel]="'Back to list'"
5099
5506
  >
5100
- ← Back to List
5507
+ ← Back
5101
5508
  </ngt-button>
5102
5509
  }
5103
5510
  </div>
@@ -5106,13 +5513,20 @@ class ToolbarPresetsToolComponent {
5106
5513
  <!-- Create Form -->
5107
5514
  @if (viewMode() === 'create') {
5108
5515
  <form (submit)="onSavePreset($event)" class="preset-form">
5109
- <ngt-input
5110
- label="Preset Name"
5111
- [value]="presetName()"
5112
- (valueChange)="presetName.set($event)"
5113
- placeholder="e.g., Admin User - Full Access"
5114
- [ariaLabel]="'Preset name'"
5115
- />
5516
+ <h3 class="form-title">Create Preset</h3>
5517
+ <p class="form-hint">This will save the current forced values from other tools. Only items you've explicitly set will be included.</p>
5518
+ <div class="form-field">
5519
+ <ngt-input
5520
+ label="Preset Name *"
5521
+ [value]="presetName()"
5522
+ (valueChange)="onPresetNameChange($event)"
5523
+ placeholder="e.g., Admin User - Full Access"
5524
+ [ariaLabel]="'Preset name'"
5525
+ />
5526
+ @if (nameError()) {
5527
+ <span class="field-error">{{ nameError() }}</span>
5528
+ }
5529
+ </div>
5116
5530
  <ngt-input
5117
5531
  label="Description (optional)"
5118
5532
  [value]="presetDescription()"
@@ -5240,11 +5654,305 @@ class ToolbarPresetsToolComponent {
5240
5654
  </div>
5241
5655
 
5242
5656
  <div class="form-actions">
5657
+ <ngt-button type="button" (click)="onSwitchToListMode()">Cancel</ngt-button>
5243
5658
  <ngt-button type="submit">Save Preset</ngt-button>
5244
5659
  </div>
5245
5660
  </form>
5246
5661
  }
5247
5662
 
5663
+ <!-- Edit Form -->
5664
+ @if (viewMode() === 'edit') {
5665
+ <form (submit)="onSaveEdit($event)" class="preset-form">
5666
+ <h3 class="form-title">Edit Preset</h3>
5667
+ <div class="form-field">
5668
+ <ngt-input
5669
+ label="Preset Name *"
5670
+ [value]="editName()"
5671
+ (valueChange)="onEditNameChange($event)"
5672
+ placeholder="e.g., Admin User - Full Access"
5673
+ [ariaLabel]="'Preset name'"
5674
+ />
5675
+ @if (nameError()) {
5676
+ <span class="field-error">{{ nameError() }}</span>
5677
+ }
5678
+ </div>
5679
+ <ngt-input
5680
+ label="Description (optional)"
5681
+ [value]="editDescription()"
5682
+ (valueChange)="editDescription.set($event)"
5683
+ placeholder="Brief description of this preset"
5684
+ [ariaLabel]="'Preset description'"
5685
+ />
5686
+
5687
+ <!-- Show saved configuration read-only -->
5688
+ @if (editingPreset()) {
5689
+ <div class="config-preview">
5690
+ <h4>Saved Configuration</h4>
5691
+ @if (editingPreset()!.config.featureFlags.enabled.length > 0 || editingPreset()!.config.featureFlags.disabled.length > 0) {
5692
+ <div class="config-category">
5693
+ <span class="config-category__title">Feature Flags ({{ editingPreset()!.config.featureFlags.enabled.length + editingPreset()!.config.featureFlags.disabled.length }})</span>
5694
+ <div class="config-items">
5695
+ @for (id of editingPreset()!.config.featureFlags.enabled; track id) {
5696
+ <span class="config-item config-item--on">{{ id }}: ON</span>
5697
+ }
5698
+ @for (id of editingPreset()!.config.featureFlags.disabled; track id) {
5699
+ <span class="config-item config-item--off">{{ id }}: OFF</span>
5700
+ }
5701
+ </div>
5702
+ </div>
5703
+ }
5704
+ @if (editingPreset()!.config.permissions.granted.length > 0 || editingPreset()!.config.permissions.denied.length > 0) {
5705
+ <div class="config-category">
5706
+ <span class="config-category__title">Permissions ({{ editingPreset()!.config.permissions.granted.length + editingPreset()!.config.permissions.denied.length }})</span>
5707
+ <div class="config-items">
5708
+ @for (id of editingPreset()!.config.permissions.granted; track id) {
5709
+ <span class="config-item config-item--on">{{ id }}: GRANTED</span>
5710
+ }
5711
+ @for (id of editingPreset()!.config.permissions.denied; track id) {
5712
+ <span class="config-item config-item--off">{{ id }}: DENIED</span>
5713
+ }
5714
+ </div>
5715
+ </div>
5716
+ }
5717
+ @if (editingPreset()!.config.appFeatures.enabled.length > 0 || editingPreset()!.config.appFeatures.disabled.length > 0) {
5718
+ <div class="config-category">
5719
+ <span class="config-category__title">App Features ({{ editingPreset()!.config.appFeatures.enabled.length + editingPreset()!.config.appFeatures.disabled.length }})</span>
5720
+ <div class="config-items">
5721
+ @for (id of editingPreset()!.config.appFeatures.enabled; track id) {
5722
+ <span class="config-item config-item--on">{{ id }}: ON</span>
5723
+ }
5724
+ @for (id of editingPreset()!.config.appFeatures.disabled; track id) {
5725
+ <span class="config-item config-item--off">{{ id }}: OFF</span>
5726
+ }
5727
+ </div>
5728
+ </div>
5729
+ }
5730
+ @if (editingPreset()!.config.language) {
5731
+ <div class="config-category">
5732
+ <span class="config-category__title">Language</span>
5733
+ <span class="config-item">{{ editingPreset()!.config.language }}</span>
5734
+ </div>
5735
+ }
5736
+ <button type="button" class="replace-config-button" (click)="onReplaceConfig()">
5737
+ ↻ Replace with current toolbar state
5738
+ </button>
5739
+ </div>
5740
+ }
5741
+
5742
+ <div class="form-actions">
5743
+ <ngt-button type="button" (click)="onSwitchToListMode()">Cancel</ngt-button>
5744
+ <ngt-button type="submit">Save Changes</ngt-button>
5745
+ </div>
5746
+ </form>
5747
+ }
5748
+
5749
+ <!-- Import View -->
5750
+ @if (viewMode() === 'import') {
5751
+ <div class="import-view">
5752
+ <h3 class="form-title">Import Preset</h3>
5753
+
5754
+ <!-- File Drop Zone -->
5755
+ <div
5756
+ class="drop-zone"
5757
+ [class.drop-zone--active]="isDragOver()"
5758
+ (dragover)="onDragOver($event)"
5759
+ (dragleave)="onDragLeave($event)"
5760
+ (drop)="onFileDrop($event)"
5761
+ (click)="fileInput.click()"
5762
+ (keydown.enter)="fileInput.click()"
5763
+ (keydown.space)="fileInput.click()"
5764
+ tabindex="0"
5765
+ role="button"
5766
+ >
5767
+ <input
5768
+ #fileInput
5769
+ type="file"
5770
+ accept=".json"
5771
+ (change)="onFileSelect($event)"
5772
+ hidden
5773
+ />
5774
+ <span class="drop-zone__icon">📁</span>
5775
+ <span class="drop-zone__text">Drop a .json file here</span>
5776
+ <span class="drop-zone__hint">or click to browse</span>
5777
+ </div>
5778
+
5779
+ <div class="divider">
5780
+ <span>or paste JSON</span>
5781
+ </div>
5782
+
5783
+ <!-- JSON Textarea -->
5784
+ <textarea
5785
+ class="json-textarea"
5786
+ [value]="importJson()"
5787
+ (input)="onImportJsonChange($event)"
5788
+ placeholder='{"name": "...", "config": { ... }}'
5789
+ ></textarea>
5790
+
5791
+ @if (importError()) {
5792
+ <span class="field-error">{{ importError() }}</span>
5793
+ }
5794
+
5795
+ <div class="form-actions">
5796
+ <ngt-button type="button" (click)="onSwitchToListMode()">Cancel</ngt-button>
5797
+ <ngt-button (click)="onImportPreset()">Import Preset</ngt-button>
5798
+ </div>
5799
+ </div>
5800
+ }
5801
+
5802
+ <!-- Partial Apply View -->
5803
+ @if (viewMode() === 'apply') {
5804
+ <div class="apply-view">
5805
+ <h3 class="form-title">Apply: {{ applyingPreset()?.name }}</h3>
5806
+ <p class="apply-description">Select which parts to apply</p>
5807
+
5808
+ @if (applyingPreset()) {
5809
+ <div class="apply-categories">
5810
+ <!-- Feature Flags -->
5811
+ @if (applyingPreset()!.config.featureFlags.enabled.length > 0 || applyingPreset()!.config.featureFlags.disabled.length > 0) {
5812
+ <div class="apply-category" [class.apply-category--disabled]="!applyFeatureFlags()">
5813
+ <label class="apply-category__header">
5814
+ <input
5815
+ type="checkbox"
5816
+ [checked]="applyFeatureFlags()"
5817
+ (change)="applyFeatureFlags.set(!applyFeatureFlags())"
5818
+ />
5819
+ <span>Feature Flags ({{ applyingPreset()!.config.featureFlags.enabled.length + applyingPreset()!.config.featureFlags.disabled.length }})</span>
5820
+ </label>
5821
+ @if (applyFeatureFlags()) {
5822
+ <div class="apply-diff">
5823
+ @for (id of applyingPreset()!.config.featureFlags.enabled; track id) {
5824
+ <div class="diff-item">
5825
+ <span class="diff-item__name">{{ id }}</span>
5826
+ <span class="diff-item__arrow">→</span>
5827
+ <span class="diff-item__value diff-item__value--on">ON</span>
5828
+ </div>
5829
+ }
5830
+ @for (id of applyingPreset()!.config.featureFlags.disabled; track id) {
5831
+ <div class="diff-item">
5832
+ <span class="diff-item__name">{{ id }}</span>
5833
+ <span class="diff-item__arrow">→</span>
5834
+ <span class="diff-item__value diff-item__value--off">OFF</span>
5835
+ </div>
5836
+ }
5837
+ </div>
5838
+ }
5839
+ </div>
5840
+ }
5841
+
5842
+ <!-- Permissions -->
5843
+ @if (applyingPreset()!.config.permissions.granted.length > 0 || applyingPreset()!.config.permissions.denied.length > 0) {
5844
+ <div class="apply-category" [class.apply-category--disabled]="!applyPermissions()">
5845
+ <label class="apply-category__header">
5846
+ <input
5847
+ type="checkbox"
5848
+ [checked]="applyPermissions()"
5849
+ (change)="applyPermissions.set(!applyPermissions())"
5850
+ />
5851
+ <span>Permissions ({{ applyingPreset()!.config.permissions.granted.length + applyingPreset()!.config.permissions.denied.length }})</span>
5852
+ </label>
5853
+ @if (applyPermissions()) {
5854
+ <div class="apply-diff">
5855
+ @for (id of applyingPreset()!.config.permissions.granted; track id) {
5856
+ <div class="diff-item">
5857
+ <span class="diff-item__name">{{ id }}</span>
5858
+ <span class="diff-item__arrow">→</span>
5859
+ <span class="diff-item__value diff-item__value--on">GRANTED</span>
5860
+ </div>
5861
+ }
5862
+ @for (id of applyingPreset()!.config.permissions.denied; track id) {
5863
+ <div class="diff-item">
5864
+ <span class="diff-item__name">{{ id }}</span>
5865
+ <span class="diff-item__arrow">→</span>
5866
+ <span class="diff-item__value diff-item__value--off">DENIED</span>
5867
+ </div>
5868
+ }
5869
+ </div>
5870
+ }
5871
+ </div>
5872
+ }
5873
+
5874
+ <!-- App Features -->
5875
+ @if (applyingPreset()!.config.appFeatures.enabled.length > 0 || applyingPreset()!.config.appFeatures.disabled.length > 0) {
5876
+ <div class="apply-category" [class.apply-category--disabled]="!applyAppFeatures()">
5877
+ <label class="apply-category__header">
5878
+ <input
5879
+ type="checkbox"
5880
+ [checked]="applyAppFeatures()"
5881
+ (change)="applyAppFeatures.set(!applyAppFeatures())"
5882
+ />
5883
+ <span>App Features ({{ applyingPreset()!.config.appFeatures.enabled.length + applyingPreset()!.config.appFeatures.disabled.length }})</span>
5884
+ </label>
5885
+ @if (applyAppFeatures()) {
5886
+ <div class="apply-diff">
5887
+ @for (id of applyingPreset()!.config.appFeatures.enabled; track id) {
5888
+ <div class="diff-item">
5889
+ <span class="diff-item__name">{{ id }}</span>
5890
+ <span class="diff-item__arrow">→</span>
5891
+ <span class="diff-item__value diff-item__value--on">ON</span>
5892
+ </div>
5893
+ }
5894
+ @for (id of applyingPreset()!.config.appFeatures.disabled; track id) {
5895
+ <div class="diff-item">
5896
+ <span class="diff-item__name">{{ id }}</span>
5897
+ <span class="diff-item__arrow">→</span>
5898
+ <span class="diff-item__value diff-item__value--off">OFF</span>
5899
+ </div>
5900
+ }
5901
+ </div>
5902
+ }
5903
+ </div>
5904
+ }
5905
+
5906
+ <!-- Language -->
5907
+ @if (applyingPreset()!.config.language) {
5908
+ <div class="apply-category" [class.apply-category--disabled]="!applyLanguage()">
5909
+ <label class="apply-category__header">
5910
+ <input
5911
+ type="checkbox"
5912
+ [checked]="applyLanguage()"
5913
+ (change)="applyLanguage.set(!applyLanguage())"
5914
+ />
5915
+ <span>Language</span>
5916
+ </label>
5917
+ @if (applyLanguage()) {
5918
+ <div class="apply-diff">
5919
+ <div class="diff-item">
5920
+ <span class="diff-item__name">Language</span>
5921
+ <span class="diff-item__arrow">→</span>
5922
+ <span class="diff-item__value">{{ applyingPreset()!.config.language }}</span>
5923
+ </div>
5924
+ </div>
5925
+ }
5926
+ </div>
5927
+ }
5928
+ </div>
5929
+ }
5930
+
5931
+ <div class="form-actions">
5932
+ <ngt-button type="button" (click)="onSwitchToListMode()">Cancel</ngt-button>
5933
+ <ngt-button (click)="onConfirmPartialApply()">Apply Selected</ngt-button>
5934
+ </div>
5935
+ </div>
5936
+ }
5937
+
5938
+ <!-- Delete Confirmation View -->
5939
+ @if (viewMode() === 'delete') {
5940
+ <div class="delete-view">
5941
+ <div class="delete-view__content">
5942
+ <div class="delete-view__icon">🗑️</div>
5943
+ <h3 class="delete-view__title">Delete "{{ deletePresetName() }}"?</h3>
5944
+ <p class="delete-view__description">
5945
+ This preset will be permanently removed.
5946
+ This action cannot be undone.
5947
+ </p>
5948
+ </div>
5949
+ <div class="form-actions">
5950
+ <ngt-button type="button" (click)="onSwitchToListMode()">← Back</ngt-button>
5951
+ <button class="delete-button" (click)="onConfirmDelete()">Delete Preset</button>
5952
+ </div>
5953
+ </div>
5954
+ }
5955
+
5248
5956
  <!-- Empty State -->
5249
5957
  @if (viewMode() === 'list' && hasNoPresets()) {
5250
5958
  <div class="empty">
@@ -5263,83 +5971,49 @@ class ToolbarPresetsToolComponent {
5263
5971
  } @else if (viewMode() === 'list') {
5264
5972
  <!-- Preset List -->
5265
5973
  <div class="preset-list">
5266
- @for (preset of filteredPresets(); track preset.id) {
5267
- <div class="preset-card">
5974
+ @for (preset of sortedPresets(); track preset.id) {
5975
+ <div class="preset-card" [title]="getPresetTooltip(preset)">
5268
5976
  <div class="preset-card__header">
5269
- <h3>{{ preset.name }}</h3>
5977
+ <button
5978
+ class="favorite-button"
5979
+ [class.favorite-button--active]="preset.isFavorite"
5980
+ (click)="onToggleFavorite(preset.id); $event.stopPropagation()"
5981
+ [attr.aria-label]="preset.isFavorite ? 'Remove from favorites' : 'Add to favorites'"
5982
+ >{{ preset.isFavorite ? '★' : '☆' }}</button>
5983
+ <span class="preset-card__name">{{ preset.name }}</span>
5984
+ @if (preset.isSystem) {<span class="system-badge">SYS</span>}
5985
+ <span class="preset-card__spacer"></span>
5270
5986
  <div class="preset-card__actions">
5271
- <button
5272
- class="icon-button"
5273
- (click)="onApplyPreset(preset.id)"
5274
- [attr.aria-label]="'Apply preset ' + preset.name"
5275
- title="Apply preset"
5276
- >
5277
- <ngt-icon name="refresh" />
5278
- </button>
5279
- <button
5280
- class="icon-button"
5281
- (click)="onUpdatePreset(preset.id)"
5282
- [attr.aria-label]="'Update preset ' + preset.name"
5283
- title="Update with current state"
5284
- >
5285
- <ngt-icon name="gear" />
5286
- </button>
5287
- <button
5288
- class="icon-button"
5289
- (click)="onExportPreset(preset.id)"
5290
- [attr.aria-label]="'Export preset ' + preset.name"
5291
- title="Export as JSON"
5292
- >
5293
- <ngt-icon name="export" />
5294
- </button>
5295
- <button
5296
- class="icon-button"
5297
- (click)="onDeletePreset(preset.id)"
5298
- [attr.aria-label]="'Delete preset ' + preset.name"
5299
- title="Delete preset"
5300
- >
5301
- <ngt-icon name="trash" />
5302
- </button>
5987
+ <button class="icon-button" (click)="onStartApply(preset.id)"><ngt-icon name="refresh" /></button>
5988
+ @if (!preset.isSystem) {
5989
+ <button class="icon-button" (click)="onStartEdit(preset.id)"><ngt-icon name="edit" /></button>
5990
+ <button class="icon-button" (click)="onUpdatePreset(preset.id)"><ngt-icon name="gear" /></button>
5991
+ }
5992
+ <button class="icon-button" (click)="onExportPreset(preset.id)"><ngt-icon name="export" /></button>
5993
+ @if (!preset.isSystem) {
5994
+ <button class="icon-button" (click)="onDeletePreset(preset.id)"><ngt-icon name="trash" /></button>
5995
+ }
5303
5996
  </div>
5304
5997
  </div>
5305
5998
  @if (preset.description) {
5306
- <p class="preset-card__description">{{ preset.description }}</p>
5307
- }
5308
- <div class="preset-card__meta">
5309
- <span>Updated: {{ formatDate(preset.updatedAt) }}</span>
5999
+ <div class="preset-card__row preset-card__row--meta">
6000
+ <span class="preset-card__description">{{ preset.description }}</span>
5310
6001
  </div>
5311
- <!-- Preview of preset config -->
5312
- <div class="preset-card__preview">
5313
- @if (preset.config.featureFlags.enabled.length > 0 ||
5314
- preset.config.featureFlags.disabled.length > 0) {
5315
- <span class="badge">
5316
- {{
5317
- preset.config.featureFlags.enabled.length +
5318
- preset.config.featureFlags.disabled.length
5319
- }}
5320
- flags
5321
- </span>
5322
- } @if (preset.config.permissions.granted.length > 0 ||
5323
- preset.config.permissions.denied.length > 0) {
5324
- <span class="badge">
5325
- {{
5326
- preset.config.permissions.granted.length +
5327
- preset.config.permissions.denied.length
5328
- }}
5329
- perms
5330
- </span>
5331
- } @if (preset.config.appFeatures.enabled.length > 0 ||
5332
- preset.config.appFeatures.disabled.length > 0) {
5333
- <span class="badge">
5334
- {{
5335
- preset.config.appFeatures.enabled.length +
5336
- preset.config.appFeatures.disabled.length
5337
- }}
5338
- features
5339
- </span>
5340
- } @if (preset.config.language) {
5341
- <span class="badge">{{ preset.config.language }}</span>
6002
+ }
6003
+ <div class="preset-card__row preset-card__row--badges">
6004
+ @if (preset.config.featureFlags.enabled.length > 0 || preset.config.featureFlags.disabled.length > 0) {
6005
+ <span class="badge">{{ preset.config.featureFlags.enabled.length + preset.config.featureFlags.disabled.length }} flags</span>
6006
+ }
6007
+ @if (preset.config.permissions.granted.length > 0 || preset.config.permissions.denied.length > 0) {
6008
+ <span class="badge">{{ preset.config.permissions.granted.length + preset.config.permissions.denied.length }} perms</span>
5342
6009
  }
6010
+ @if (preset.config.appFeatures.enabled.length > 0 || preset.config.appFeatures.disabled.length > 0) {
6011
+ <span class="badge">{{ preset.config.appFeatures.enabled.length + preset.config.appFeatures.disabled.length }} features</span>
6012
+ }
6013
+ @if (preset.config.language) {
6014
+ <span class="badge badge--lang">{{ preset.config.language }}</span>
6015
+ }
6016
+ <span class="preset-card__date">{{ formatDate(preset.updatedAt) }}</span>
5343
6017
  </div>
5344
6018
  </div>
5345
6019
  }
@@ -5347,7 +6021,7 @@ class ToolbarPresetsToolComponent {
5347
6021
  }
5348
6022
  </div>
5349
6023
  </ngt-toolbar-tool>
5350
- `, isInline: true, styles: [".container{position:relative;display:flex;flex-direction:column;height:100%;padding:0}.tool-header{position:relative;flex-shrink:0;display:flex;gap:var(--ngt-spacing-sm);margin-bottom:var(--ngt-spacing-md);ngt-input{flex:1}ngt-button{flex-shrink:0}}.empty{display:flex;flex-direction:column;gap:var(--ngt-spacing-md);flex:1;min-height:0;justify-content:center;align-items:center;border:1px solid var(--ngt-border-subtle);border-radius:var(--ngt-border-radius-medium);padding:var(--ngt-spacing-md);background:transparent;color:var(--ngt-text-muted);text-align:center;p{margin:0}.hint{font-size:var(--ngt-font-size-xs)}}.preset-list{display:flex;flex-direction:column;gap:var(--ngt-spacing-md);flex:1;min-height:0;overflow-y:auto;padding-right:var(--ngt-spacing-sm);&::-webkit-scrollbar{width:8px}&::-webkit-scrollbar-track{background:var(--ngt-background-secondary);border-radius:4px}&::-webkit-scrollbar-thumb{background:var(--ngt-border-primary);border-radius:4px;&:hover{background:var(--ngt-hover-bg)}}scrollbar-width:thin;scrollbar-color:var(--ngt-border-primary) var(--ngt-background-secondary)}.preset-card{background:var(--ngt-background-secondary);padding:var(--ngt-spacing-md);border-radius:var(--ngt-border-radius-medium);display:flex;flex-direction:column;gap:var(--ngt-spacing-sm)}.preset-card__header{display:flex;justify-content:space-between;align-items:center;gap:var(--ngt-spacing-sm);h3{margin:0;font-size:var(--ngt-font-size-md);color:var(--ngt-text-primary);flex:1}}.preset-card__actions{display:flex;gap:var(--ngt-spacing-xs)}.icon-button{background:transparent;border:none;cursor:pointer;padding:var(--ngt-spacing-xs);border-radius:var(--ngt-border-radius-small);color:var(--ngt-text-secondary);display:flex;align-items:center;justify-content:center;&:hover{background:var(--ngt-hover-bg);color:var(--ngt-text-primary)}ngt-icon{width:16px;height:16px}}.preset-card__description{margin:0;font-size:var(--ngt-font-size-sm);color:var(--ngt-text-secondary)}.preset-card__meta{font-size:var(--ngt-font-size-xs);color:var(--ngt-text-muted);span{margin-right:var(--ngt-spacing-sm)}}.preset-card__preview{display:flex;gap:var(--ngt-spacing-xs);flex-wrap:wrap}.badge{background:var(--ngt-primary-color);color:#fff;padding:2px 8px;border-radius:12px;font-size:var(--ngt-font-size-xs);font-weight:500}.preset-form{flex:1;min-height:0;overflow-y:auto;display:flex;flex-direction:column;gap:var(--ngt-spacing-md);padding:var(--ngt-spacing-md);ngt-input{width:100%}}.preset-summary{background:var(--ngt-background-secondary);padding:var(--ngt-spacing-md);border-radius:var(--ngt-border-radius-medium);display:flex;flex-direction:column;gap:var(--ngt-spacing-sm);h4{margin:0;font-size:var(--ngt-font-size-sm);color:var(--ngt-text-primary)}}.category-section{display:flex;flex-direction:column;gap:var(--ngt-spacing-xs)}.checkbox-option{display:flex;align-items:center;gap:var(--ngt-spacing-sm);cursor:pointer;color:var(--ngt-text-secondary);font-size:var(--ngt-font-size-sm);input[type=checkbox]{cursor:pointer;width:16px;height:16px;accent-color:var(--ngt-primary);&:disabled{cursor:not-allowed;opacity:.5}}&:has(input:disabled){opacity:.6;cursor:not-allowed}}.forced-items-list{list-style:none;padding:0;margin:0 0 0 var(--ngt-spacing-lg);display:flex;flex-direction:column;gap:var(--ngt-spacing-xs);font-size:var(--ngt-font-size-xs);li{background:rgba(var(--ngt-primary-rgb),.05);border-radius:var(--ngt-border-radius-small);border-left:2px solid rgba(var(--ngt-primary-rgb),.3)}.item-checkbox{display:flex;justify-content:space-between;align-items:center;padding:var(--ngt-spacing-xs) var(--ngt-spacing-sm);cursor:pointer;gap:var(--ngt-spacing-sm);input[type=checkbox]{cursor:pointer;width:14px;height:14px;accent-color:var(--ngt-primary);flex-shrink:0}&:hover{background:rgba(var(--ngt-primary-rgb),.08)}}.item-name{color:var(--ngt-text-primary);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.item-status{font-weight:600;padding:2px 6px;border-radius:var(--ngt-border-radius-small);font-size:10px;text-transform:uppercase;letter-spacing:.5px;flex-shrink:0;&.enabled{background:#22c55e26;color:#22c55e}&.disabled{background:#ef444426;color:#ef4444}}}.form-actions{display:flex;justify-content:flex-end;gap:var(--ngt-spacing-sm)}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: ToolbarToolComponent, selector: "ngt-toolbar-tool", inputs: ["options", "icon", "title", "badge"] }, { kind: "component", type: ToolbarInputComponent, selector: "ngt-input", inputs: ["value", "type", "placeholder", "ariaLabel", "inputClass"], outputs: ["valueChange"] }, { kind: "component", type: ToolbarButtonComponent, selector: "ngt-button", inputs: ["type", "variant", "icon", "label", "ariaLabel", "isActive"] }, { kind: "component", type: ToolbarIconComponent, selector: "ngt-icon", inputs: ["name"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
6024
+ `, isInline: true, styles: [".container{position:relative;display:flex;flex-direction:column;height:100%;padding:0}.toast{position:absolute;bottom:var(--ngt-spacing-md);left:var(--ngt-spacing-md);right:var(--ngt-spacing-md);z-index:10;display:flex;align-items:center;gap:var(--ngt-spacing-sm);padding:var(--ngt-spacing-sm) var(--ngt-spacing-md);border-radius:var(--ngt-border-radius-small);font-size:var(--ngt-font-size-sm);animation:slideUp .15s ease-out}.toast--success{background:#22c55e;color:#fff}.toast--error{background:#ef4444;color:#fff}.toast__icon{font-weight:700}@keyframes slideUp{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.delete-view{flex:1;display:flex;flex-direction:column;justify-content:center;padding:var(--ngt-spacing-md)}.delete-view__content{text-align:center;padding:var(--ngt-spacing-lg) var(--ngt-spacing-md)}.delete-view__icon{font-size:48px;margin-bottom:var(--ngt-spacing-md)}.delete-view__title{margin:0 0 var(--ngt-spacing-sm);font-size:var(--ngt-font-size-md);color:var(--ngt-text-primary)}.delete-view__description{margin:0;font-size:var(--ngt-font-size-sm);color:var(--ngt-text-secondary);line-height:1.5}.delete-button{background:#ef4444;color:#fff;border:none;padding:var(--ngt-spacing-sm) var(--ngt-spacing-md);border-radius:var(--ngt-border-radius-small);cursor:pointer;font-size:var(--ngt-font-size-sm);font-weight:500;transition:background .2s ease-out;&:hover{background:#dc2626}}.tool-header{position:relative;flex-shrink:0;display:flex;gap:var(--ngt-spacing-sm);margin-bottom:var(--ngt-spacing-md);ngt-input{flex:1}ngt-button{flex-shrink:0}}.empty{display:flex;flex-direction:column;gap:var(--ngt-spacing-md);flex:1;min-height:0;justify-content:center;align-items:center;border:1px solid var(--ngt-border-subtle);border-radius:var(--ngt-border-radius-medium);padding:var(--ngt-spacing-md);background:transparent;color:var(--ngt-text-muted);text-align:center;p{margin:0}.hint{font-size:var(--ngt-font-size-xs)}}.preset-list{display:flex;flex-direction:column;gap:var(--ngt-spacing-xs);flex:1;min-height:0;overflow-y:auto;padding-right:var(--ngt-spacing-xs);&::-webkit-scrollbar{width:8px}&::-webkit-scrollbar-track{background:var(--ngt-background-secondary);border-radius:4px}&::-webkit-scrollbar-thumb{background:var(--ngt-border-primary);border-radius:4px;&:hover{background:var(--ngt-hover-bg)}}scrollbar-width:thin;scrollbar-color:var(--ngt-border-primary) var(--ngt-background-secondary)}.preset-card{background:var(--ngt-background-secondary);padding:6px var(--ngt-spacing-sm);border-radius:var(--ngt-border-radius-medium);display:flex;flex-direction:column;gap:2px}.preset-card__header{display:flex;align-items:center;gap:6px;flex-wrap:nowrap}.preset-card__row{display:flex;align-items:center;gap:6px;min-height:18px}.preset-card__row--meta{padding-left:18px}.preset-card__row--badges{padding-left:18px;gap:4px}.preset-card__name{font-size:var(--ngt-font-size-sm);font-weight:600;color:var(--ngt-text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.preset-card__spacer{flex:1;min-width:8px}.system-badge{font-size:8px;font-weight:600;text-transform:uppercase;letter-spacing:.3px;padding:1px 4px;border-radius:3px;background:var(--ngt-border-primary);color:var(--ngt-text-muted);flex-shrink:0}.favorite-button{background:transparent;border:none;cursor:pointer;font-size:12px;color:var(--ngt-text-muted);padding:0;line-height:1;transition:color .15s ease-out;flex-shrink:0}.favorite-button:hover,.favorite-button--active{color:#f59e0b}.preset-card__actions{display:flex;gap:0;flex-shrink:0}.icon-button{appearance:none;background:none;border:none;cursor:pointer;margin:0;padding:4px;border-radius:4px;color:var(--ngt-text-muted);display:inline-flex;align-items:center;justify-content:center;opacity:.5;transition:opacity .15s ease-out,color .15s ease-out;line-height:0;&:hover{background:var(--ngt-hover-bg);color:var(--ngt-text-primary);opacity:1}ngt-icon,ngt-icon>*,svg{display:block;width:12px!important;height:12px!important}}.preset-card__description{font-size:11px;color:var(--ngt-text-secondary);flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.preset-card__date{font-size:10px;color:var(--ngt-text-muted);margin-left:auto;flex-shrink:0}.badge{background:#6366f126;color:#6366f1;padding:1px 6px;border-radius:8px;font-size:10px;font-weight:500;white-space:nowrap}.badge--lang{background:#22c55e26;color:#22c55e}.preset-form,.import-view,.apply-view{flex:1;min-height:0;overflow-y:auto;display:flex;flex-direction:column;gap:var(--ngt-spacing-sm);padding:var(--ngt-spacing-sm);ngt-input{width:100%}}.form-title{margin:0;font-size:var(--ngt-font-size-sm);font-weight:600;color:var(--ngt-text-primary)}.form-hint{margin:0;font-size:11px;color:var(--ngt-text-muted);line-height:1.3}.form-field{display:flex;flex-direction:column;gap:2px}.field-error{color:#ef4444;font-size:11px}.preset-summary{background:var(--ngt-background-secondary);padding:var(--ngt-spacing-sm);border-radius:var(--ngt-border-radius-medium);display:flex;flex-direction:column;gap:var(--ngt-spacing-xs);h4{margin:0;font-size:11px;font-weight:500;color:var(--ngt-text-secondary)}}.category-section{display:flex;flex-direction:column;gap:2px}.checkbox-option{display:flex;align-items:center;gap:var(--ngt-spacing-xs);cursor:pointer;color:var(--ngt-text-primary);font-size:var(--ngt-font-size-sm);padding:2px 0;input[type=checkbox]{cursor:pointer;width:14px;height:14px;accent-color:rgb(99,102,241);border-radius:3px;&:disabled{cursor:not-allowed;opacity:.4}}&:has(input:disabled){opacity:.5;cursor:not-allowed}}.forced-items-list{list-style:none;padding:0;margin:0 0 0 20px;display:flex;flex-direction:column;gap:2px;font-size:11px;max-height:100px;overflow-y:auto;li{background:#6366f10d;border-radius:3px;border-left:2px solid rgba(99,102,241,.3)}.item-checkbox{display:flex;justify-content:space-between;align-items:center;padding:3px 6px;cursor:pointer;gap:6px;input[type=checkbox]{cursor:pointer;width:12px;height:12px;accent-color:rgb(99,102,241);flex-shrink:0}&:hover{background:#6366f114}}.item-name{color:var(--ngt-text-primary);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.item-status{font-weight:600;padding:1px 4px;border-radius:3px;font-size:9px;text-transform:uppercase;letter-spacing:.3px;flex-shrink:0;&.enabled{background:#22c55e26;color:#22c55e}&.disabled{background:#ef444426;color:#ef4444}}}.form-actions{display:flex;justify-content:flex-end;gap:var(--ngt-spacing-sm);margin-top:auto;padding-top:var(--ngt-spacing-sm)}.config-preview{background:var(--ngt-background-secondary);padding:var(--ngt-spacing-sm);border-radius:var(--ngt-border-radius-medium);display:flex;flex-direction:column;gap:var(--ngt-spacing-xs);h4{margin:0;font-size:11px;font-weight:500;color:var(--ngt-text-secondary)}}.config-category{display:flex;flex-direction:column;gap:2px}.config-category__title{font-size:10px;color:var(--ngt-text-muted);font-weight:500}.config-items{display:flex;flex-wrap:wrap;gap:4px}.config-item{font-size:10px;padding:1px 6px;border-radius:3px;background:var(--ngt-background-primary);color:var(--ngt-text-secondary)}.config-item--on{background:#22c55e1a;color:#22c55e}.config-item--off{background:#ef44441a;color:#ef4444}.replace-config-button{background:transparent;border:1px dashed var(--ngt-border-primary);padding:6px var(--ngt-spacing-sm);border-radius:var(--ngt-border-radius-small);color:var(--ngt-text-secondary);cursor:pointer;font-size:11px;transition:all .15s ease-out;&:hover{background:var(--ngt-hover-bg);border-color:#6366f1;color:#6366f1}}.drop-zone{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;padding:var(--ngt-spacing-md);border:2px dashed var(--ngt-border-primary);border-radius:var(--ngt-border-radius-medium);cursor:pointer;transition:all .15s ease-out}.drop-zone:hover,.drop-zone--active{border-color:#6366f1;background:#6366f10d}.drop-zone__icon{font-size:24px}.drop-zone__text{font-size:var(--ngt-font-size-sm);color:var(--ngt-text-primary)}.drop-zone__hint{font-size:11px;color:var(--ngt-text-muted)}.divider{display:flex;align-items:center;gap:var(--ngt-spacing-sm);color:var(--ngt-text-muted);font-size:10px;&:before,&:after{content:\"\";flex:1;height:1px;background:var(--ngt-border-primary)}}.json-textarea{width:100%;min-height:80px;padding:var(--ngt-spacing-xs);border:1px solid var(--ngt-border-primary);border-radius:var(--ngt-border-radius-small);background:var(--ngt-background-secondary);color:var(--ngt-text-primary);font-family:monospace;font-size:11px;resize:vertical;&:focus{outline:none;border-color:#6366f1}&::placeholder{color:var(--ngt-text-muted)}}.apply-description{margin:0;font-size:11px;color:var(--ngt-text-secondary)}.apply-categories{display:flex;flex-direction:column;gap:var(--ngt-spacing-xs)}.apply-category{background:var(--ngt-background-secondary);border-radius:var(--ngt-border-radius-small);overflow:hidden;transition:opacity .15s ease-out}.apply-category--disabled{opacity:.5}.apply-category__header{display:flex;align-items:center;gap:var(--ngt-spacing-xs);padding:6px var(--ngt-spacing-sm);cursor:pointer;font-size:var(--ngt-font-size-sm);color:var(--ngt-text-primary);font-weight:500;input[type=checkbox]{cursor:pointer;width:14px;height:14px;accent-color:rgb(99,102,241)}}.apply-diff{padding:0 var(--ngt-spacing-sm) 6px;display:flex;flex-direction:column;gap:2px}.diff-item{display:flex;align-items:center;gap:6px;font-size:11px;padding:3px 6px;background:var(--ngt-background-primary);border-radius:3px}.diff-item__name{flex:1;color:var(--ngt-text-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.diff-item__arrow{color:var(--ngt-text-muted);font-size:10px}.diff-item__value{font-weight:600;padding:1px 4px;border-radius:3px;font-size:9px;text-transform:uppercase;letter-spacing:.3px}.diff-item__value--on{background:#22c55e26;color:#22c55e}.diff-item__value--off{background:#ef444426;color:#ef4444}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: ToolbarToolComponent, selector: "ngt-toolbar-tool", inputs: ["options", "icon", "title", "badge"] }, { kind: "component", type: ToolbarInputComponent, selector: "ngt-input", inputs: ["value", "type", "placeholder", "ariaLabel", "inputClass"], outputs: ["valueChange"] }, { kind: "component", type: ToolbarButtonComponent, selector: "ngt-button", inputs: ["type", "variant", "icon", "label", "ariaLabel", "isActive"] }, { kind: "component", type: ToolbarIconComponent, selector: "ngt-icon", inputs: ["name"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
5351
6025
  }
5352
6026
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarPresetsToolComponent, decorators: [{
5353
6027
  type: Component,
@@ -5360,8 +6034,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImpor
5360
6034
  ], template: `
5361
6035
  <ngt-toolbar-tool [options]="options" title="Presets" icon="layout">
5362
6036
  <div class="container">
6037
+ <!-- Toast Notification -->
6038
+ @if (toastMessage()) {
6039
+ <div class="toast" [class.toast--success]="toastType() === 'success'" [class.toast--error]="toastType() === 'error'">
6040
+ <span class="toast__icon">{{ toastType() === 'success' ? '✓' : '✕' }}</span>
6041
+ <span class="toast__message">{{ toastMessage() }}</span>
6042
+ </div>
6043
+ }
6044
+
6045
+
5363
6046
  <!-- Mode Toggle -->
5364
- @if (!hasNoPresets() || viewMode() === 'create') {
6047
+ @if (!hasNoPresets() || viewMode() !== 'list') {
5365
6048
  <div class="tool-header">
5366
6049
  @if (viewMode() === 'list') {
5367
6050
  <ngt-input
@@ -5370,18 +6053,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImpor
5370
6053
  placeholder="Search presets..."
5371
6054
  [ariaLabel]="'Search presets'"
5372
6055
  />
6056
+ <ngt-button
6057
+ (click)="onSwitchToImportMode()"
6058
+ [ariaLabel]="'Import preset'"
6059
+ >
6060
+ Import
6061
+ </ngt-button>
5373
6062
  <ngt-button
5374
6063
  (click)="onSwitchToCreateMode()"
5375
6064
  [ariaLabel]="'Create new preset'"
5376
6065
  >
5377
- New Preset
6066
+ New
5378
6067
  </ngt-button>
5379
6068
  } @else {
5380
6069
  <ngt-button
5381
6070
  (click)="onSwitchToListMode()"
5382
6071
  [ariaLabel]="'Back to list'"
5383
6072
  >
5384
- ← Back to List
6073
+ ← Back
5385
6074
  </ngt-button>
5386
6075
  }
5387
6076
  </div>
@@ -5390,13 +6079,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImpor
5390
6079
  <!-- Create Form -->
5391
6080
  @if (viewMode() === 'create') {
5392
6081
  <form (submit)="onSavePreset($event)" class="preset-form">
5393
- <ngt-input
5394
- label="Preset Name"
5395
- [value]="presetName()"
5396
- (valueChange)="presetName.set($event)"
5397
- placeholder="e.g., Admin User - Full Access"
5398
- [ariaLabel]="'Preset name'"
5399
- />
6082
+ <h3 class="form-title">Create Preset</h3>
6083
+ <p class="form-hint">This will save the current forced values from other tools. Only items you've explicitly set will be included.</p>
6084
+ <div class="form-field">
6085
+ <ngt-input
6086
+ label="Preset Name *"
6087
+ [value]="presetName()"
6088
+ (valueChange)="onPresetNameChange($event)"
6089
+ placeholder="e.g., Admin User - Full Access"
6090
+ [ariaLabel]="'Preset name'"
6091
+ />
6092
+ @if (nameError()) {
6093
+ <span class="field-error">{{ nameError() }}</span>
6094
+ }
6095
+ </div>
5400
6096
  <ngt-input
5401
6097
  label="Description (optional)"
5402
6098
  [value]="presetDescription()"
@@ -5524,11 +6220,305 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImpor
5524
6220
  </div>
5525
6221
 
5526
6222
  <div class="form-actions">
6223
+ <ngt-button type="button" (click)="onSwitchToListMode()">Cancel</ngt-button>
5527
6224
  <ngt-button type="submit">Save Preset</ngt-button>
5528
6225
  </div>
5529
6226
  </form>
5530
6227
  }
5531
6228
 
6229
+ <!-- Edit Form -->
6230
+ @if (viewMode() === 'edit') {
6231
+ <form (submit)="onSaveEdit($event)" class="preset-form">
6232
+ <h3 class="form-title">Edit Preset</h3>
6233
+ <div class="form-field">
6234
+ <ngt-input
6235
+ label="Preset Name *"
6236
+ [value]="editName()"
6237
+ (valueChange)="onEditNameChange($event)"
6238
+ placeholder="e.g., Admin User - Full Access"
6239
+ [ariaLabel]="'Preset name'"
6240
+ />
6241
+ @if (nameError()) {
6242
+ <span class="field-error">{{ nameError() }}</span>
6243
+ }
6244
+ </div>
6245
+ <ngt-input
6246
+ label="Description (optional)"
6247
+ [value]="editDescription()"
6248
+ (valueChange)="editDescription.set($event)"
6249
+ placeholder="Brief description of this preset"
6250
+ [ariaLabel]="'Preset description'"
6251
+ />
6252
+
6253
+ <!-- Show saved configuration read-only -->
6254
+ @if (editingPreset()) {
6255
+ <div class="config-preview">
6256
+ <h4>Saved Configuration</h4>
6257
+ @if (editingPreset()!.config.featureFlags.enabled.length > 0 || editingPreset()!.config.featureFlags.disabled.length > 0) {
6258
+ <div class="config-category">
6259
+ <span class="config-category__title">Feature Flags ({{ editingPreset()!.config.featureFlags.enabled.length + editingPreset()!.config.featureFlags.disabled.length }})</span>
6260
+ <div class="config-items">
6261
+ @for (id of editingPreset()!.config.featureFlags.enabled; track id) {
6262
+ <span class="config-item config-item--on">{{ id }}: ON</span>
6263
+ }
6264
+ @for (id of editingPreset()!.config.featureFlags.disabled; track id) {
6265
+ <span class="config-item config-item--off">{{ id }}: OFF</span>
6266
+ }
6267
+ </div>
6268
+ </div>
6269
+ }
6270
+ @if (editingPreset()!.config.permissions.granted.length > 0 || editingPreset()!.config.permissions.denied.length > 0) {
6271
+ <div class="config-category">
6272
+ <span class="config-category__title">Permissions ({{ editingPreset()!.config.permissions.granted.length + editingPreset()!.config.permissions.denied.length }})</span>
6273
+ <div class="config-items">
6274
+ @for (id of editingPreset()!.config.permissions.granted; track id) {
6275
+ <span class="config-item config-item--on">{{ id }}: GRANTED</span>
6276
+ }
6277
+ @for (id of editingPreset()!.config.permissions.denied; track id) {
6278
+ <span class="config-item config-item--off">{{ id }}: DENIED</span>
6279
+ }
6280
+ </div>
6281
+ </div>
6282
+ }
6283
+ @if (editingPreset()!.config.appFeatures.enabled.length > 0 || editingPreset()!.config.appFeatures.disabled.length > 0) {
6284
+ <div class="config-category">
6285
+ <span class="config-category__title">App Features ({{ editingPreset()!.config.appFeatures.enabled.length + editingPreset()!.config.appFeatures.disabled.length }})</span>
6286
+ <div class="config-items">
6287
+ @for (id of editingPreset()!.config.appFeatures.enabled; track id) {
6288
+ <span class="config-item config-item--on">{{ id }}: ON</span>
6289
+ }
6290
+ @for (id of editingPreset()!.config.appFeatures.disabled; track id) {
6291
+ <span class="config-item config-item--off">{{ id }}: OFF</span>
6292
+ }
6293
+ </div>
6294
+ </div>
6295
+ }
6296
+ @if (editingPreset()!.config.language) {
6297
+ <div class="config-category">
6298
+ <span class="config-category__title">Language</span>
6299
+ <span class="config-item">{{ editingPreset()!.config.language }}</span>
6300
+ </div>
6301
+ }
6302
+ <button type="button" class="replace-config-button" (click)="onReplaceConfig()">
6303
+ ↻ Replace with current toolbar state
6304
+ </button>
6305
+ </div>
6306
+ }
6307
+
6308
+ <div class="form-actions">
6309
+ <ngt-button type="button" (click)="onSwitchToListMode()">Cancel</ngt-button>
6310
+ <ngt-button type="submit">Save Changes</ngt-button>
6311
+ </div>
6312
+ </form>
6313
+ }
6314
+
6315
+ <!-- Import View -->
6316
+ @if (viewMode() === 'import') {
6317
+ <div class="import-view">
6318
+ <h3 class="form-title">Import Preset</h3>
6319
+
6320
+ <!-- File Drop Zone -->
6321
+ <div
6322
+ class="drop-zone"
6323
+ [class.drop-zone--active]="isDragOver()"
6324
+ (dragover)="onDragOver($event)"
6325
+ (dragleave)="onDragLeave($event)"
6326
+ (drop)="onFileDrop($event)"
6327
+ (click)="fileInput.click()"
6328
+ (keydown.enter)="fileInput.click()"
6329
+ (keydown.space)="fileInput.click()"
6330
+ tabindex="0"
6331
+ role="button"
6332
+ >
6333
+ <input
6334
+ #fileInput
6335
+ type="file"
6336
+ accept=".json"
6337
+ (change)="onFileSelect($event)"
6338
+ hidden
6339
+ />
6340
+ <span class="drop-zone__icon">📁</span>
6341
+ <span class="drop-zone__text">Drop a .json file here</span>
6342
+ <span class="drop-zone__hint">or click to browse</span>
6343
+ </div>
6344
+
6345
+ <div class="divider">
6346
+ <span>or paste JSON</span>
6347
+ </div>
6348
+
6349
+ <!-- JSON Textarea -->
6350
+ <textarea
6351
+ class="json-textarea"
6352
+ [value]="importJson()"
6353
+ (input)="onImportJsonChange($event)"
6354
+ placeholder='{"name": "...", "config": { ... }}'
6355
+ ></textarea>
6356
+
6357
+ @if (importError()) {
6358
+ <span class="field-error">{{ importError() }}</span>
6359
+ }
6360
+
6361
+ <div class="form-actions">
6362
+ <ngt-button type="button" (click)="onSwitchToListMode()">Cancel</ngt-button>
6363
+ <ngt-button (click)="onImportPreset()">Import Preset</ngt-button>
6364
+ </div>
6365
+ </div>
6366
+ }
6367
+
6368
+ <!-- Partial Apply View -->
6369
+ @if (viewMode() === 'apply') {
6370
+ <div class="apply-view">
6371
+ <h3 class="form-title">Apply: {{ applyingPreset()?.name }}</h3>
6372
+ <p class="apply-description">Select which parts to apply</p>
6373
+
6374
+ @if (applyingPreset()) {
6375
+ <div class="apply-categories">
6376
+ <!-- Feature Flags -->
6377
+ @if (applyingPreset()!.config.featureFlags.enabled.length > 0 || applyingPreset()!.config.featureFlags.disabled.length > 0) {
6378
+ <div class="apply-category" [class.apply-category--disabled]="!applyFeatureFlags()">
6379
+ <label class="apply-category__header">
6380
+ <input
6381
+ type="checkbox"
6382
+ [checked]="applyFeatureFlags()"
6383
+ (change)="applyFeatureFlags.set(!applyFeatureFlags())"
6384
+ />
6385
+ <span>Feature Flags ({{ applyingPreset()!.config.featureFlags.enabled.length + applyingPreset()!.config.featureFlags.disabled.length }})</span>
6386
+ </label>
6387
+ @if (applyFeatureFlags()) {
6388
+ <div class="apply-diff">
6389
+ @for (id of applyingPreset()!.config.featureFlags.enabled; track id) {
6390
+ <div class="diff-item">
6391
+ <span class="diff-item__name">{{ id }}</span>
6392
+ <span class="diff-item__arrow">→</span>
6393
+ <span class="diff-item__value diff-item__value--on">ON</span>
6394
+ </div>
6395
+ }
6396
+ @for (id of applyingPreset()!.config.featureFlags.disabled; track id) {
6397
+ <div class="diff-item">
6398
+ <span class="diff-item__name">{{ id }}</span>
6399
+ <span class="diff-item__arrow">→</span>
6400
+ <span class="diff-item__value diff-item__value--off">OFF</span>
6401
+ </div>
6402
+ }
6403
+ </div>
6404
+ }
6405
+ </div>
6406
+ }
6407
+
6408
+ <!-- Permissions -->
6409
+ @if (applyingPreset()!.config.permissions.granted.length > 0 || applyingPreset()!.config.permissions.denied.length > 0) {
6410
+ <div class="apply-category" [class.apply-category--disabled]="!applyPermissions()">
6411
+ <label class="apply-category__header">
6412
+ <input
6413
+ type="checkbox"
6414
+ [checked]="applyPermissions()"
6415
+ (change)="applyPermissions.set(!applyPermissions())"
6416
+ />
6417
+ <span>Permissions ({{ applyingPreset()!.config.permissions.granted.length + applyingPreset()!.config.permissions.denied.length }})</span>
6418
+ </label>
6419
+ @if (applyPermissions()) {
6420
+ <div class="apply-diff">
6421
+ @for (id of applyingPreset()!.config.permissions.granted; track id) {
6422
+ <div class="diff-item">
6423
+ <span class="diff-item__name">{{ id }}</span>
6424
+ <span class="diff-item__arrow">→</span>
6425
+ <span class="diff-item__value diff-item__value--on">GRANTED</span>
6426
+ </div>
6427
+ }
6428
+ @for (id of applyingPreset()!.config.permissions.denied; track id) {
6429
+ <div class="diff-item">
6430
+ <span class="diff-item__name">{{ id }}</span>
6431
+ <span class="diff-item__arrow">→</span>
6432
+ <span class="diff-item__value diff-item__value--off">DENIED</span>
6433
+ </div>
6434
+ }
6435
+ </div>
6436
+ }
6437
+ </div>
6438
+ }
6439
+
6440
+ <!-- App Features -->
6441
+ @if (applyingPreset()!.config.appFeatures.enabled.length > 0 || applyingPreset()!.config.appFeatures.disabled.length > 0) {
6442
+ <div class="apply-category" [class.apply-category--disabled]="!applyAppFeatures()">
6443
+ <label class="apply-category__header">
6444
+ <input
6445
+ type="checkbox"
6446
+ [checked]="applyAppFeatures()"
6447
+ (change)="applyAppFeatures.set(!applyAppFeatures())"
6448
+ />
6449
+ <span>App Features ({{ applyingPreset()!.config.appFeatures.enabled.length + applyingPreset()!.config.appFeatures.disabled.length }})</span>
6450
+ </label>
6451
+ @if (applyAppFeatures()) {
6452
+ <div class="apply-diff">
6453
+ @for (id of applyingPreset()!.config.appFeatures.enabled; track id) {
6454
+ <div class="diff-item">
6455
+ <span class="diff-item__name">{{ id }}</span>
6456
+ <span class="diff-item__arrow">→</span>
6457
+ <span class="diff-item__value diff-item__value--on">ON</span>
6458
+ </div>
6459
+ }
6460
+ @for (id of applyingPreset()!.config.appFeatures.disabled; track id) {
6461
+ <div class="diff-item">
6462
+ <span class="diff-item__name">{{ id }}</span>
6463
+ <span class="diff-item__arrow">→</span>
6464
+ <span class="diff-item__value diff-item__value--off">OFF</span>
6465
+ </div>
6466
+ }
6467
+ </div>
6468
+ }
6469
+ </div>
6470
+ }
6471
+
6472
+ <!-- Language -->
6473
+ @if (applyingPreset()!.config.language) {
6474
+ <div class="apply-category" [class.apply-category--disabled]="!applyLanguage()">
6475
+ <label class="apply-category__header">
6476
+ <input
6477
+ type="checkbox"
6478
+ [checked]="applyLanguage()"
6479
+ (change)="applyLanguage.set(!applyLanguage())"
6480
+ />
6481
+ <span>Language</span>
6482
+ </label>
6483
+ @if (applyLanguage()) {
6484
+ <div class="apply-diff">
6485
+ <div class="diff-item">
6486
+ <span class="diff-item__name">Language</span>
6487
+ <span class="diff-item__arrow">→</span>
6488
+ <span class="diff-item__value">{{ applyingPreset()!.config.language }}</span>
6489
+ </div>
6490
+ </div>
6491
+ }
6492
+ </div>
6493
+ }
6494
+ </div>
6495
+ }
6496
+
6497
+ <div class="form-actions">
6498
+ <ngt-button type="button" (click)="onSwitchToListMode()">Cancel</ngt-button>
6499
+ <ngt-button (click)="onConfirmPartialApply()">Apply Selected</ngt-button>
6500
+ </div>
6501
+ </div>
6502
+ }
6503
+
6504
+ <!-- Delete Confirmation View -->
6505
+ @if (viewMode() === 'delete') {
6506
+ <div class="delete-view">
6507
+ <div class="delete-view__content">
6508
+ <div class="delete-view__icon">🗑️</div>
6509
+ <h3 class="delete-view__title">Delete "{{ deletePresetName() }}"?</h3>
6510
+ <p class="delete-view__description">
6511
+ This preset will be permanently removed.
6512
+ This action cannot be undone.
6513
+ </p>
6514
+ </div>
6515
+ <div class="form-actions">
6516
+ <ngt-button type="button" (click)="onSwitchToListMode()">← Back</ngt-button>
6517
+ <button class="delete-button" (click)="onConfirmDelete()">Delete Preset</button>
6518
+ </div>
6519
+ </div>
6520
+ }
6521
+
5532
6522
  <!-- Empty State -->
5533
6523
  @if (viewMode() === 'list' && hasNoPresets()) {
5534
6524
  <div class="empty">
@@ -5547,83 +6537,49 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImpor
5547
6537
  } @else if (viewMode() === 'list') {
5548
6538
  <!-- Preset List -->
5549
6539
  <div class="preset-list">
5550
- @for (preset of filteredPresets(); track preset.id) {
5551
- <div class="preset-card">
6540
+ @for (preset of sortedPresets(); track preset.id) {
6541
+ <div class="preset-card" [title]="getPresetTooltip(preset)">
5552
6542
  <div class="preset-card__header">
5553
- <h3>{{ preset.name }}</h3>
6543
+ <button
6544
+ class="favorite-button"
6545
+ [class.favorite-button--active]="preset.isFavorite"
6546
+ (click)="onToggleFavorite(preset.id); $event.stopPropagation()"
6547
+ [attr.aria-label]="preset.isFavorite ? 'Remove from favorites' : 'Add to favorites'"
6548
+ >{{ preset.isFavorite ? '★' : '☆' }}</button>
6549
+ <span class="preset-card__name">{{ preset.name }}</span>
6550
+ @if (preset.isSystem) {<span class="system-badge">SYS</span>}
6551
+ <span class="preset-card__spacer"></span>
5554
6552
  <div class="preset-card__actions">
5555
- <button
5556
- class="icon-button"
5557
- (click)="onApplyPreset(preset.id)"
5558
- [attr.aria-label]="'Apply preset ' + preset.name"
5559
- title="Apply preset"
5560
- >
5561
- <ngt-icon name="refresh" />
5562
- </button>
5563
- <button
5564
- class="icon-button"
5565
- (click)="onUpdatePreset(preset.id)"
5566
- [attr.aria-label]="'Update preset ' + preset.name"
5567
- title="Update with current state"
5568
- >
5569
- <ngt-icon name="gear" />
5570
- </button>
5571
- <button
5572
- class="icon-button"
5573
- (click)="onExportPreset(preset.id)"
5574
- [attr.aria-label]="'Export preset ' + preset.name"
5575
- title="Export as JSON"
5576
- >
5577
- <ngt-icon name="export" />
5578
- </button>
5579
- <button
5580
- class="icon-button"
5581
- (click)="onDeletePreset(preset.id)"
5582
- [attr.aria-label]="'Delete preset ' + preset.name"
5583
- title="Delete preset"
5584
- >
5585
- <ngt-icon name="trash" />
5586
- </button>
6553
+ <button class="icon-button" (click)="onStartApply(preset.id)"><ngt-icon name="refresh" /></button>
6554
+ @if (!preset.isSystem) {
6555
+ <button class="icon-button" (click)="onStartEdit(preset.id)"><ngt-icon name="edit" /></button>
6556
+ <button class="icon-button" (click)="onUpdatePreset(preset.id)"><ngt-icon name="gear" /></button>
6557
+ }
6558
+ <button class="icon-button" (click)="onExportPreset(preset.id)"><ngt-icon name="export" /></button>
6559
+ @if (!preset.isSystem) {
6560
+ <button class="icon-button" (click)="onDeletePreset(preset.id)"><ngt-icon name="trash" /></button>
6561
+ }
5587
6562
  </div>
5588
6563
  </div>
5589
6564
  @if (preset.description) {
5590
- <p class="preset-card__description">{{ preset.description }}</p>
5591
- }
5592
- <div class="preset-card__meta">
5593
- <span>Updated: {{ formatDate(preset.updatedAt) }}</span>
6565
+ <div class="preset-card__row preset-card__row--meta">
6566
+ <span class="preset-card__description">{{ preset.description }}</span>
5594
6567
  </div>
5595
- <!-- Preview of preset config -->
5596
- <div class="preset-card__preview">
5597
- @if (preset.config.featureFlags.enabled.length > 0 ||
5598
- preset.config.featureFlags.disabled.length > 0) {
5599
- <span class="badge">
5600
- {{
5601
- preset.config.featureFlags.enabled.length +
5602
- preset.config.featureFlags.disabled.length
5603
- }}
5604
- flags
5605
- </span>
5606
- } @if (preset.config.permissions.granted.length > 0 ||
5607
- preset.config.permissions.denied.length > 0) {
5608
- <span class="badge">
5609
- {{
5610
- preset.config.permissions.granted.length +
5611
- preset.config.permissions.denied.length
5612
- }}
5613
- perms
5614
- </span>
5615
- } @if (preset.config.appFeatures.enabled.length > 0 ||
5616
- preset.config.appFeatures.disabled.length > 0) {
5617
- <span class="badge">
5618
- {{
5619
- preset.config.appFeatures.enabled.length +
5620
- preset.config.appFeatures.disabled.length
5621
- }}
5622
- features
5623
- </span>
5624
- } @if (preset.config.language) {
5625
- <span class="badge">{{ preset.config.language }}</span>
6568
+ }
6569
+ <div class="preset-card__row preset-card__row--badges">
6570
+ @if (preset.config.featureFlags.enabled.length > 0 || preset.config.featureFlags.disabled.length > 0) {
6571
+ <span class="badge">{{ preset.config.featureFlags.enabled.length + preset.config.featureFlags.disabled.length }} flags</span>
6572
+ }
6573
+ @if (preset.config.permissions.granted.length > 0 || preset.config.permissions.denied.length > 0) {
6574
+ <span class="badge">{{ preset.config.permissions.granted.length + preset.config.permissions.denied.length }} perms</span>
5626
6575
  }
6576
+ @if (preset.config.appFeatures.enabled.length > 0 || preset.config.appFeatures.disabled.length > 0) {
6577
+ <span class="badge">{{ preset.config.appFeatures.enabled.length + preset.config.appFeatures.disabled.length }} features</span>
6578
+ }
6579
+ @if (preset.config.language) {
6580
+ <span class="badge badge--lang">{{ preset.config.language }}</span>
6581
+ }
6582
+ <span class="preset-card__date">{{ formatDate(preset.updatedAt) }}</span>
5627
6583
  </div>
5628
6584
  </div>
5629
6585
  }
@@ -5631,8 +6587,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImpor
5631
6587
  }
5632
6588
  </div>
5633
6589
  </ngt-toolbar-tool>
5634
- `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".container{position:relative;display:flex;flex-direction:column;height:100%;padding:0}.tool-header{position:relative;flex-shrink:0;display:flex;gap:var(--ngt-spacing-sm);margin-bottom:var(--ngt-spacing-md);ngt-input{flex:1}ngt-button{flex-shrink:0}}.empty{display:flex;flex-direction:column;gap:var(--ngt-spacing-md);flex:1;min-height:0;justify-content:center;align-items:center;border:1px solid var(--ngt-border-subtle);border-radius:var(--ngt-border-radius-medium);padding:var(--ngt-spacing-md);background:transparent;color:var(--ngt-text-muted);text-align:center;p{margin:0}.hint{font-size:var(--ngt-font-size-xs)}}.preset-list{display:flex;flex-direction:column;gap:var(--ngt-spacing-md);flex:1;min-height:0;overflow-y:auto;padding-right:var(--ngt-spacing-sm);&::-webkit-scrollbar{width:8px}&::-webkit-scrollbar-track{background:var(--ngt-background-secondary);border-radius:4px}&::-webkit-scrollbar-thumb{background:var(--ngt-border-primary);border-radius:4px;&:hover{background:var(--ngt-hover-bg)}}scrollbar-width:thin;scrollbar-color:var(--ngt-border-primary) var(--ngt-background-secondary)}.preset-card{background:var(--ngt-background-secondary);padding:var(--ngt-spacing-md);border-radius:var(--ngt-border-radius-medium);display:flex;flex-direction:column;gap:var(--ngt-spacing-sm)}.preset-card__header{display:flex;justify-content:space-between;align-items:center;gap:var(--ngt-spacing-sm);h3{margin:0;font-size:var(--ngt-font-size-md);color:var(--ngt-text-primary);flex:1}}.preset-card__actions{display:flex;gap:var(--ngt-spacing-xs)}.icon-button{background:transparent;border:none;cursor:pointer;padding:var(--ngt-spacing-xs);border-radius:var(--ngt-border-radius-small);color:var(--ngt-text-secondary);display:flex;align-items:center;justify-content:center;&:hover{background:var(--ngt-hover-bg);color:var(--ngt-text-primary)}ngt-icon{width:16px;height:16px}}.preset-card__description{margin:0;font-size:var(--ngt-font-size-sm);color:var(--ngt-text-secondary)}.preset-card__meta{font-size:var(--ngt-font-size-xs);color:var(--ngt-text-muted);span{margin-right:var(--ngt-spacing-sm)}}.preset-card__preview{display:flex;gap:var(--ngt-spacing-xs);flex-wrap:wrap}.badge{background:var(--ngt-primary-color);color:#fff;padding:2px 8px;border-radius:12px;font-size:var(--ngt-font-size-xs);font-weight:500}.preset-form{flex:1;min-height:0;overflow-y:auto;display:flex;flex-direction:column;gap:var(--ngt-spacing-md);padding:var(--ngt-spacing-md);ngt-input{width:100%}}.preset-summary{background:var(--ngt-background-secondary);padding:var(--ngt-spacing-md);border-radius:var(--ngt-border-radius-medium);display:flex;flex-direction:column;gap:var(--ngt-spacing-sm);h4{margin:0;font-size:var(--ngt-font-size-sm);color:var(--ngt-text-primary)}}.category-section{display:flex;flex-direction:column;gap:var(--ngt-spacing-xs)}.checkbox-option{display:flex;align-items:center;gap:var(--ngt-spacing-sm);cursor:pointer;color:var(--ngt-text-secondary);font-size:var(--ngt-font-size-sm);input[type=checkbox]{cursor:pointer;width:16px;height:16px;accent-color:var(--ngt-primary);&:disabled{cursor:not-allowed;opacity:.5}}&:has(input:disabled){opacity:.6;cursor:not-allowed}}.forced-items-list{list-style:none;padding:0;margin:0 0 0 var(--ngt-spacing-lg);display:flex;flex-direction:column;gap:var(--ngt-spacing-xs);font-size:var(--ngt-font-size-xs);li{background:rgba(var(--ngt-primary-rgb),.05);border-radius:var(--ngt-border-radius-small);border-left:2px solid rgba(var(--ngt-primary-rgb),.3)}.item-checkbox{display:flex;justify-content:space-between;align-items:center;padding:var(--ngt-spacing-xs) var(--ngt-spacing-sm);cursor:pointer;gap:var(--ngt-spacing-sm);input[type=checkbox]{cursor:pointer;width:14px;height:14px;accent-color:var(--ngt-primary);flex-shrink:0}&:hover{background:rgba(var(--ngt-primary-rgb),.08)}}.item-name{color:var(--ngt-text-primary);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.item-status{font-weight:600;padding:2px 6px;border-radius:var(--ngt-border-radius-small);font-size:10px;text-transform:uppercase;letter-spacing:.5px;flex-shrink:0;&.enabled{background:#22c55e26;color:#22c55e}&.disabled{background:#ef444426;color:#ef4444}}}.form-actions{display:flex;justify-content:flex-end;gap:var(--ngt-spacing-sm)}\n"] }]
5635
- }] });
6590
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".container{position:relative;display:flex;flex-direction:column;height:100%;padding:0}.toast{position:absolute;bottom:var(--ngt-spacing-md);left:var(--ngt-spacing-md);right:var(--ngt-spacing-md);z-index:10;display:flex;align-items:center;gap:var(--ngt-spacing-sm);padding:var(--ngt-spacing-sm) var(--ngt-spacing-md);border-radius:var(--ngt-border-radius-small);font-size:var(--ngt-font-size-sm);animation:slideUp .15s ease-out}.toast--success{background:#22c55e;color:#fff}.toast--error{background:#ef4444;color:#fff}.toast__icon{font-weight:700}@keyframes slideUp{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.delete-view{flex:1;display:flex;flex-direction:column;justify-content:center;padding:var(--ngt-spacing-md)}.delete-view__content{text-align:center;padding:var(--ngt-spacing-lg) var(--ngt-spacing-md)}.delete-view__icon{font-size:48px;margin-bottom:var(--ngt-spacing-md)}.delete-view__title{margin:0 0 var(--ngt-spacing-sm);font-size:var(--ngt-font-size-md);color:var(--ngt-text-primary)}.delete-view__description{margin:0;font-size:var(--ngt-font-size-sm);color:var(--ngt-text-secondary);line-height:1.5}.delete-button{background:#ef4444;color:#fff;border:none;padding:var(--ngt-spacing-sm) var(--ngt-spacing-md);border-radius:var(--ngt-border-radius-small);cursor:pointer;font-size:var(--ngt-font-size-sm);font-weight:500;transition:background .2s ease-out;&:hover{background:#dc2626}}.tool-header{position:relative;flex-shrink:0;display:flex;gap:var(--ngt-spacing-sm);margin-bottom:var(--ngt-spacing-md);ngt-input{flex:1}ngt-button{flex-shrink:0}}.empty{display:flex;flex-direction:column;gap:var(--ngt-spacing-md);flex:1;min-height:0;justify-content:center;align-items:center;border:1px solid var(--ngt-border-subtle);border-radius:var(--ngt-border-radius-medium);padding:var(--ngt-spacing-md);background:transparent;color:var(--ngt-text-muted);text-align:center;p{margin:0}.hint{font-size:var(--ngt-font-size-xs)}}.preset-list{display:flex;flex-direction:column;gap:var(--ngt-spacing-xs);flex:1;min-height:0;overflow-y:auto;padding-right:var(--ngt-spacing-xs);&::-webkit-scrollbar{width:8px}&::-webkit-scrollbar-track{background:var(--ngt-background-secondary);border-radius:4px}&::-webkit-scrollbar-thumb{background:var(--ngt-border-primary);border-radius:4px;&:hover{background:var(--ngt-hover-bg)}}scrollbar-width:thin;scrollbar-color:var(--ngt-border-primary) var(--ngt-background-secondary)}.preset-card{background:var(--ngt-background-secondary);padding:6px var(--ngt-spacing-sm);border-radius:var(--ngt-border-radius-medium);display:flex;flex-direction:column;gap:2px}.preset-card__header{display:flex;align-items:center;gap:6px;flex-wrap:nowrap}.preset-card__row{display:flex;align-items:center;gap:6px;min-height:18px}.preset-card__row--meta{padding-left:18px}.preset-card__row--badges{padding-left:18px;gap:4px}.preset-card__name{font-size:var(--ngt-font-size-sm);font-weight:600;color:var(--ngt-text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.preset-card__spacer{flex:1;min-width:8px}.system-badge{font-size:8px;font-weight:600;text-transform:uppercase;letter-spacing:.3px;padding:1px 4px;border-radius:3px;background:var(--ngt-border-primary);color:var(--ngt-text-muted);flex-shrink:0}.favorite-button{background:transparent;border:none;cursor:pointer;font-size:12px;color:var(--ngt-text-muted);padding:0;line-height:1;transition:color .15s ease-out;flex-shrink:0}.favorite-button:hover,.favorite-button--active{color:#f59e0b}.preset-card__actions{display:flex;gap:0;flex-shrink:0}.icon-button{appearance:none;background:none;border:none;cursor:pointer;margin:0;padding:4px;border-radius:4px;color:var(--ngt-text-muted);display:inline-flex;align-items:center;justify-content:center;opacity:.5;transition:opacity .15s ease-out,color .15s ease-out;line-height:0;&:hover{background:var(--ngt-hover-bg);color:var(--ngt-text-primary);opacity:1}ngt-icon,ngt-icon>*,svg{display:block;width:12px!important;height:12px!important}}.preset-card__description{font-size:11px;color:var(--ngt-text-secondary);flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.preset-card__date{font-size:10px;color:var(--ngt-text-muted);margin-left:auto;flex-shrink:0}.badge{background:#6366f126;color:#6366f1;padding:1px 6px;border-radius:8px;font-size:10px;font-weight:500;white-space:nowrap}.badge--lang{background:#22c55e26;color:#22c55e}.preset-form,.import-view,.apply-view{flex:1;min-height:0;overflow-y:auto;display:flex;flex-direction:column;gap:var(--ngt-spacing-sm);padding:var(--ngt-spacing-sm);ngt-input{width:100%}}.form-title{margin:0;font-size:var(--ngt-font-size-sm);font-weight:600;color:var(--ngt-text-primary)}.form-hint{margin:0;font-size:11px;color:var(--ngt-text-muted);line-height:1.3}.form-field{display:flex;flex-direction:column;gap:2px}.field-error{color:#ef4444;font-size:11px}.preset-summary{background:var(--ngt-background-secondary);padding:var(--ngt-spacing-sm);border-radius:var(--ngt-border-radius-medium);display:flex;flex-direction:column;gap:var(--ngt-spacing-xs);h4{margin:0;font-size:11px;font-weight:500;color:var(--ngt-text-secondary)}}.category-section{display:flex;flex-direction:column;gap:2px}.checkbox-option{display:flex;align-items:center;gap:var(--ngt-spacing-xs);cursor:pointer;color:var(--ngt-text-primary);font-size:var(--ngt-font-size-sm);padding:2px 0;input[type=checkbox]{cursor:pointer;width:14px;height:14px;accent-color:rgb(99,102,241);border-radius:3px;&:disabled{cursor:not-allowed;opacity:.4}}&:has(input:disabled){opacity:.5;cursor:not-allowed}}.forced-items-list{list-style:none;padding:0;margin:0 0 0 20px;display:flex;flex-direction:column;gap:2px;font-size:11px;max-height:100px;overflow-y:auto;li{background:#6366f10d;border-radius:3px;border-left:2px solid rgba(99,102,241,.3)}.item-checkbox{display:flex;justify-content:space-between;align-items:center;padding:3px 6px;cursor:pointer;gap:6px;input[type=checkbox]{cursor:pointer;width:12px;height:12px;accent-color:rgb(99,102,241);flex-shrink:0}&:hover{background:#6366f114}}.item-name{color:var(--ngt-text-primary);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.item-status{font-weight:600;padding:1px 4px;border-radius:3px;font-size:9px;text-transform:uppercase;letter-spacing:.3px;flex-shrink:0;&.enabled{background:#22c55e26;color:#22c55e}&.disabled{background:#ef444426;color:#ef4444}}}.form-actions{display:flex;justify-content:flex-end;gap:var(--ngt-spacing-sm);margin-top:auto;padding-top:var(--ngt-spacing-sm)}.config-preview{background:var(--ngt-background-secondary);padding:var(--ngt-spacing-sm);border-radius:var(--ngt-border-radius-medium);display:flex;flex-direction:column;gap:var(--ngt-spacing-xs);h4{margin:0;font-size:11px;font-weight:500;color:var(--ngt-text-secondary)}}.config-category{display:flex;flex-direction:column;gap:2px}.config-category__title{font-size:10px;color:var(--ngt-text-muted);font-weight:500}.config-items{display:flex;flex-wrap:wrap;gap:4px}.config-item{font-size:10px;padding:1px 6px;border-radius:3px;background:var(--ngt-background-primary);color:var(--ngt-text-secondary)}.config-item--on{background:#22c55e1a;color:#22c55e}.config-item--off{background:#ef44441a;color:#ef4444}.replace-config-button{background:transparent;border:1px dashed var(--ngt-border-primary);padding:6px var(--ngt-spacing-sm);border-radius:var(--ngt-border-radius-small);color:var(--ngt-text-secondary);cursor:pointer;font-size:11px;transition:all .15s ease-out;&:hover{background:var(--ngt-hover-bg);border-color:#6366f1;color:#6366f1}}.drop-zone{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;padding:var(--ngt-spacing-md);border:2px dashed var(--ngt-border-primary);border-radius:var(--ngt-border-radius-medium);cursor:pointer;transition:all .15s ease-out}.drop-zone:hover,.drop-zone--active{border-color:#6366f1;background:#6366f10d}.drop-zone__icon{font-size:24px}.drop-zone__text{font-size:var(--ngt-font-size-sm);color:var(--ngt-text-primary)}.drop-zone__hint{font-size:11px;color:var(--ngt-text-muted)}.divider{display:flex;align-items:center;gap:var(--ngt-spacing-sm);color:var(--ngt-text-muted);font-size:10px;&:before,&:after{content:\"\";flex:1;height:1px;background:var(--ngt-border-primary)}}.json-textarea{width:100%;min-height:80px;padding:var(--ngt-spacing-xs);border:1px solid var(--ngt-border-primary);border-radius:var(--ngt-border-radius-small);background:var(--ngt-background-secondary);color:var(--ngt-text-primary);font-family:monospace;font-size:11px;resize:vertical;&:focus{outline:none;border-color:#6366f1}&::placeholder{color:var(--ngt-text-muted)}}.apply-description{margin:0;font-size:11px;color:var(--ngt-text-secondary)}.apply-categories{display:flex;flex-direction:column;gap:var(--ngt-spacing-xs)}.apply-category{background:var(--ngt-background-secondary);border-radius:var(--ngt-border-radius-small);overflow:hidden;transition:opacity .15s ease-out}.apply-category--disabled{opacity:.5}.apply-category__header{display:flex;align-items:center;gap:var(--ngt-spacing-xs);padding:6px var(--ngt-spacing-sm);cursor:pointer;font-size:var(--ngt-font-size-sm);color:var(--ngt-text-primary);font-weight:500;input[type=checkbox]{cursor:pointer;width:14px;height:14px;accent-color:rgb(99,102,241)}}.apply-diff{padding:0 var(--ngt-spacing-sm) 6px;display:flex;flex-direction:column;gap:2px}.diff-item{display:flex;align-items:center;gap:6px;font-size:11px;padding:3px 6px;background:var(--ngt-background-primary);border-radius:3px}.diff-item__name{flex:1;color:var(--ngt-text-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.diff-item__arrow{color:var(--ngt-text-muted);font-size:10px}.diff-item__value{font-weight:600;padding:1px 4px;border-radius:3px;font-size:9px;text-transform:uppercase;letter-spacing:.3px}.diff-item__value--on{background:#22c55e26;color:#22c55e}.diff-item__value--off{background:#ef444426;color:#ef4444}\n"] }]
6591
+ }], ctorParameters: () => [] });
5636
6592
 
5637
6593
  class ToolbarComponent {
5638
6594
  constructor() {
@@ -5934,6 +6890,68 @@ function initToolbar(appRef, options) {
5934
6890
  };
5935
6891
  }
5936
6892
 
6893
+ class ToolbarStepDirective {
6894
+ constructor() {
6895
+ this.ngtStep = input.required();
6896
+ this.stepTitle = input('');
6897
+ this.templateRef = inject(TemplateRef);
6898
+ }
6899
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarStepDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
6900
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.0.7", type: ToolbarStepDirective, isStandalone: true, selector: "[ngtStep]", inputs: { ngtStep: { classPropertyName: "ngtStep", publicName: "ngtStep", isSignal: true, isRequired: true, transformFunction: null }, stepTitle: { classPropertyName: "stepTitle", publicName: "stepTitle", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
6901
+ }
6902
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarStepDirective, decorators: [{
6903
+ type: Directive,
6904
+ args: [{
6905
+ selector: '[ngtStep]',
6906
+ standalone: true,
6907
+ }]
6908
+ }] });
6909
+
6910
+ class ToolbarStepViewComponent {
6911
+ constructor() {
6912
+ this.currentStep = input.required();
6913
+ this.defaultStep = input('list');
6914
+ this.back = output();
6915
+ this.steps = contentChildren(ToolbarStepDirective);
6916
+ this.isDefaultStep = computed(() => this.currentStep() === this.defaultStep());
6917
+ this.activeStep = computed(() => this.steps().find((s) => s.ngtStep() === this.currentStep()));
6918
+ this.activeTitle = computed(() => this.activeStep()?.stepTitle() ?? '');
6919
+ this.activeTemplate = computed(() => this.activeStep()?.templateRef ?? null);
6920
+ }
6921
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarStepViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
6922
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.7", type: ToolbarStepViewComponent, isStandalone: true, selector: "ngt-step-view", inputs: { currentStep: { classPropertyName: "currentStep", publicName: "currentStep", isSignal: true, isRequired: true, transformFunction: null }, defaultStep: { classPropertyName: "defaultStep", publicName: "defaultStep", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { back: "back" }, queries: [{ propertyName: "steps", predicate: ToolbarStepDirective, isSignal: true }], ngImport: i0, template: `
6923
+ @if (!isDefaultStep()) {
6924
+ <div class="step-header">
6925
+ <ngt-button (click)="back.emit()" ariaLabel="Back">\u2190 Back</ngt-button>
6926
+ @if (activeTitle()) {
6927
+ <span class="step-title">{{ activeTitle() }}</span>
6928
+ }
6929
+ </div>
6930
+ }
6931
+
6932
+ @if (activeTemplate(); as tmpl) {
6933
+ <ng-container [ngTemplateOutlet]="tmpl" />
6934
+ }
6935
+ `, isInline: true, styles: [":host{display:flex;flex-direction:column}.step-header{display:flex;align-items:center;gap:var(--ngt-spacing-sm);padding-bottom:var(--ngt-spacing-sm);margin-bottom:var(--ngt-spacing-sm);border-bottom:1px solid var(--ngt-border-primary)}.step-title{font-size:var(--ngt-font-size-md);font-weight:600;color:var(--ngt-text-primary)}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ToolbarButtonComponent, selector: "ngt-button", inputs: ["type", "variant", "icon", "label", "ariaLabel", "isActive"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
6936
+ }
6937
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarStepViewComponent, decorators: [{
6938
+ type: Component,
6939
+ args: [{ selector: 'ngt-step-view', standalone: true, imports: [NgTemplateOutlet, ToolbarButtonComponent], template: `
6940
+ @if (!isDefaultStep()) {
6941
+ <div class="step-header">
6942
+ <ngt-button (click)="back.emit()" ariaLabel="Back">\u2190 Back</ngt-button>
6943
+ @if (activeTitle()) {
6944
+ <span class="step-title">{{ activeTitle() }}</span>
6945
+ }
6946
+ </div>
6947
+ }
6948
+
6949
+ @if (activeTemplate(); as tmpl) {
6950
+ <ng-container [ngTemplateOutlet]="tmpl" />
6951
+ }
6952
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:flex;flex-direction:column}.step-header{display:flex;align-items:center;gap:var(--ngt-spacing-sm);padding-bottom:var(--ngt-spacing-sm);margin-bottom:var(--ngt-spacing-sm);border-bottom:1px solid var(--ngt-border-primary)}.step-title{font-size:var(--ngt-font-size-md);font-weight:600;color:var(--ngt-text-primary)}\n"] }]
6953
+ }] });
6954
+
5937
6955
  /**
5938
6956
  * Public service for managing dev toolbar presets.
5939
6957
  * Allows developers to programmatically save, load, and manage presets.
@@ -5978,6 +6996,30 @@ class ToolbarPresetsService {
5978
6996
  deletePreset(presetId) {
5979
6997
  this.internalService.deletePreset(presetId);
5980
6998
  }
6999
+ /**
7000
+ * Toggle favorite status for a preset
7001
+ * @param presetId - ID of the preset to toggle
7002
+ */
7003
+ toggleFavorite(presetId) {
7004
+ this.internalService.toggleFavorite(presetId);
7005
+ }
7006
+ /**
7007
+ * Apply a preset with partial options (only selected categories)
7008
+ * @param presetId - ID of the preset to apply
7009
+ * @param options - Options for which categories to apply
7010
+ */
7011
+ async partialApplyPreset(presetId, options) {
7012
+ return this.internalService.partialApplyPreset(presetId, options);
7013
+ }
7014
+ /**
7015
+ * Update only the metadata (name, description) of a preset
7016
+ * @param presetId - ID of the preset to update
7017
+ * @param name - New name for the preset
7018
+ * @param description - New description for the preset
7019
+ */
7020
+ updatePresetMetadata(presetId, name, description) {
7021
+ this.internalService.updatePresetMetadata(presetId, name, description);
7022
+ }
5981
7023
  /**
5982
7024
  * Export a preset as JSON string
5983
7025
  * @param presetId - ID of the preset to export
@@ -6002,9 +7044,10 @@ class ToolbarPresetsService {
6002
7044
  /**
6003
7045
  * Initialize presets with predefined configurations.
6004
7046
  * Useful for setting up default presets that all developers can use.
7047
+ * Skips presets that already exist (by name) to avoid duplicates.
6005
7048
  *
6006
7049
  * @param presets - Array of preset configurations to initialize
6007
- * @returns Array of created presets
7050
+ * @returns Array of created presets (only newly added ones)
6008
7051
  *
6009
7052
  * @example
6010
7053
  * ```typescript
@@ -6053,10 +7096,15 @@ class ToolbarPresetsService {
6053
7096
  * ```
6054
7097
  */
6055
7098
  initializePresets(presets) {
6056
- return presets.map((preset) => this.internalService.addPreset({
7099
+ const existingPresets = this.internalService.presets();
7100
+ const existingNames = new Set(existingPresets.map((p) => p.name.toLowerCase()));
7101
+ return presets
7102
+ .filter((preset) => !existingNames.has(preset.name.toLowerCase()))
7103
+ .map((preset) => this.internalService.addPreset({
6057
7104
  id: '', // Will be generated
6058
7105
  createdAt: '', // Will be generated
6059
7106
  updatedAt: '', // Will be generated
7107
+ isSystem: true, // Mark as system preset (not editable)
6060
7108
  ...preset,
6061
7109
  }));
6062
7110
  }
@@ -6085,36 +7133,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImpor
6085
7133
  }] });
6086
7134
 
6087
7135
  // Tree-shakeable tokens for dependency injection
6088
- /** @deprecated Use TOOLBAR_FEATURE_FLAGS instead. Will be removed in v4.0 */
6089
- const DEV_TOOLBAR_FEATURE_FLAGS = TOOLBAR_FEATURE_FLAGS;
6090
- /** @deprecated Use TOOLBAR_PERMISSIONS instead. Will be removed in v4.0 */
6091
- const DEV_TOOLBAR_PERMISSIONS = TOOLBAR_PERMISSIONS;
6092
- /** @deprecated Use TOOLBAR_LANGUAGE instead. Will be removed in v4.0 */
6093
- const DEV_TOOLBAR_LANGUAGE = TOOLBAR_LANGUAGE;
6094
- /** @deprecated Use TOOLBAR_APP_FEATURES instead. Will be removed in v4.0 */
6095
- const DEV_TOOLBAR_APP_FEATURES = TOOLBAR_APP_FEATURES;
6096
- /** @deprecated Use provideToolbar instead. Will be removed in v4.0 */
6097
- const provideDevToolbar = provideToolbar;
6098
- /** @deprecated Use initToolbar instead. Will be removed in v4.0 */
6099
- const initDevToolbar = initToolbar;
6100
- /** @deprecated Use ToolbarComponent instead. Will be removed in v4.0 */
6101
- const DevToolbarComponent = ToolbarComponent;
6102
- /** @deprecated Use ToolbarToolComponent instead. Will be removed in v4.0 */
6103
- const DevToolbarToolComponent = ToolbarToolComponent;
6104
- /** @deprecated Use ToolbarFeatureFlagService instead. Will be removed in v4.0 */
6105
- const DevToolbarFeatureFlagService = ToolbarFeatureFlagService;
6106
- /** @deprecated Use ToolbarLanguageService instead. Will be removed in v4.0 */
6107
- const DevToolbarLanguageService = ToolbarLanguageService;
6108
- /** @deprecated Use ToolbarAppFeaturesService instead. Will be removed in v4.0 */
6109
- const DevToolbarAppFeaturesService = ToolbarAppFeaturesService;
6110
- /** @deprecated Use ToolbarPermissionsService instead. Will be removed in v4.0 */
6111
- const DevToolbarPermissionsService = ToolbarPermissionsService;
6112
- /** @deprecated Use ToolbarPresetsService instead. Will be removed in v4.0 */
6113
- const DevToolbarPresetsService = ToolbarPresetsService;
6114
7136
 
6115
7137
  /**
6116
7138
  * Generated bundle index. Do not edit.
6117
7139
  */
6118
7140
 
6119
- export { DEV_TOOLBAR_APP_FEATURES, DEV_TOOLBAR_FEATURE_FLAGS, DEV_TOOLBAR_LANGUAGE, DEV_TOOLBAR_PERMISSIONS, DevToolbarAppFeaturesService, DevToolbarComponent, DevToolbarFeatureFlagService, DevToolbarLanguageService, DevToolbarPermissionsService, DevToolbarPresetsService, DevToolbarToolComponent, TOOLBAR_APP_FEATURES, TOOLBAR_FEATURE_FLAGS, TOOLBAR_LANGUAGE, TOOLBAR_PERMISSIONS, ToolbarAppFeaturesService, ToolbarAppFeaturesToolComponent, ToolbarComponent, ToolbarFeatureFlagService, ToolbarIconComponent, ToolbarLanguageService, ToolbarListComponent, ToolbarListItemComponent, ToolbarPermissionsService, ToolbarPermissionsToolComponent, ToolbarPresetsService, ToolbarPresetsToolComponent, ToolbarToolComponent, initDevToolbar, initToolbar, provideDevToolbar, provideToolbar };
7141
+ export { TOOLBAR_APP_FEATURES, TOOLBAR_FEATURE_FLAGS, TOOLBAR_LANGUAGE, TOOLBAR_PERMISSIONS, ToolbarAppFeaturesService, ToolbarAppFeaturesToolComponent, ToolbarButtonComponent, ToolbarCardComponent, ToolbarClickableCardComponent, ToolbarComponent, ToolbarFeatureFlagService, ToolbarIconComponent, ToolbarInputComponent, ToolbarLanguageService, ToolbarLinkButtonComponent, ToolbarListComponent, ToolbarListItemComponent, ToolbarPermissionsService, ToolbarPermissionsToolComponent, ToolbarPresetsService, ToolbarPresetsToolComponent, ToolbarSelectComponent, ToolbarStepDirective, ToolbarStepViewComponent, ToolbarToolComponent, initToolbar, provideToolbar };
6120
7142
  //# sourceMappingURL=ngx-dev-toolbar.mjs.map