ngx-dev-toolbar 3.0.3 → 3.1.0

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
5
  import { trigger, state, transition, style, animate } from '@angular/animations';
6
- import { CommonModule, DOCUMENT } from '@angular/common';
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') {
@@ -4849,6 +4903,52 @@ class ToolbarInternalPresetsService {
4849
4903
  getPresetById(presetId) {
4850
4904
  return this.presetsSubject.value.find((p) => p.id === presetId);
4851
4905
  }
4906
+ /**
4907
+ * Toggle favorite status for a preset
4908
+ */
4909
+ toggleFavorite(presetId) {
4910
+ const presets = this.presetsSubject.value.map((p) => p.id === presetId ? { ...p, isFavorite: !p.isFavorite } : p);
4911
+ this.presetsSubject.next(presets);
4912
+ this.storageService.set(this.STORAGE_KEY, presets);
4913
+ }
4914
+ /**
4915
+ * Apply a preset with partial options (only selected categories)
4916
+ */
4917
+ async partialApplyPreset(presetId, options) {
4918
+ const preset = this.presetsSubject.value.find((p) => p.id === presetId);
4919
+ if (!preset)
4920
+ return;
4921
+ if (options.applyFeatureFlags) {
4922
+ this.featureFlagsService.applyPresetFlags(preset.config.featureFlags);
4923
+ }
4924
+ if (options.applyLanguage) {
4925
+ await this.languageService.applyPresetLanguage(preset.config.language);
4926
+ }
4927
+ if (options.applyPermissions) {
4928
+ this.permissionsService.applyPresetPermissions(preset.config.permissions);
4929
+ }
4930
+ if (options.applyAppFeatures) {
4931
+ this.appFeaturesService.applyForcedState(preset.config.appFeatures);
4932
+ }
4933
+ }
4934
+ /**
4935
+ * Update only the metadata (name, description) of a preset without changing config
4936
+ */
4937
+ updatePresetMetadata(presetId, name, description) {
4938
+ const presets = this.presetsSubject.value.map((preset) => {
4939
+ if (preset.id === presetId) {
4940
+ return {
4941
+ ...preset,
4942
+ name,
4943
+ description,
4944
+ updatedAt: new Date().toISOString(),
4945
+ };
4946
+ }
4947
+ return preset;
4948
+ });
4949
+ this.presetsSubject.next(presets);
4950
+ this.storageService.set(this.STORAGE_KEY, presets);
4951
+ }
4852
4952
  /**
4853
4953
  * Capture current configuration from all tools
4854
4954
  */
@@ -4927,27 +5027,80 @@ class ToolbarPresetsToolComponent {
4927
5027
  this.appFeaturesService = inject(ToolbarInternalAppFeaturesService);
4928
5028
  this.languageService = inject(ToolbarInternalLanguageService);
4929
5029
  this.state = inject(ToolbarStateService);
4930
- // Signals
5030
+ // View state signals
4931
5031
  this.viewMode = signal('list');
4932
5032
  this.searchQuery = signal('');
5033
+ // Create form signals
4933
5034
  this.presetName = signal('');
4934
5035
  this.presetDescription = signal('');
4935
5036
  this.includeFeatureFlags = signal(true);
4936
5037
  this.includePermissions = signal(true);
4937
5038
  this.includeAppFeatures = signal(true);
4938
5039
  this.includeLanguage = signal(true);
4939
- // Track selected individual items
4940
5040
  this.selectedFlagIds = signal(new Set());
4941
5041
  this.selectedPermissionIds = signal(new Set());
4942
5042
  this.selectedFeatureIds = signal(new Set());
5043
+ // Edit mode signals
5044
+ this.editingPresetId = signal(null);
5045
+ this.editName = signal('');
5046
+ this.editDescription = signal('');
5047
+ // Import mode signals
5048
+ this.importJson = signal('');
5049
+ this.importError = signal(null);
5050
+ this.isDragOver = signal(false);
5051
+ // Apply mode signals
5052
+ this.applyingPresetId = signal(null);
5053
+ this.applyFeatureFlags = signal(true);
5054
+ this.applyPermissions = signal(true);
5055
+ this.applyAppFeatures = signal(true);
5056
+ this.applyLanguage = signal(true);
5057
+ // Delete confirmation signals
5058
+ this.deletePresetId = signal(null);
5059
+ // Toast signals
5060
+ this.toastMessage = signal(null);
5061
+ this.toastType = signal('success');
5062
+ // Validation signals
5063
+ this.nameError = signal(null);
5064
+ // Auto-dismiss toast effect
5065
+ this.toastTimeout = null;
5066
+ // Computed values
4943
5067
  this.presets = this.presetsService.presets;
4944
5068
  this.filteredPresets = computed(() => {
4945
5069
  const query = this.searchQuery().toLowerCase();
4946
5070
  return this.presets().filter((preset) => preset.name.toLowerCase().includes(query) ||
4947
5071
  preset.description?.toLowerCase().includes(query));
4948
5072
  });
5073
+ this.sortedPresets = computed(() => {
5074
+ const presets = this.filteredPresets();
5075
+ return [...presets].sort((a, b) => {
5076
+ if (a.isFavorite && !b.isFavorite)
5077
+ return -1;
5078
+ if (!a.isFavorite && b.isFavorite)
5079
+ return 1;
5080
+ return 0;
5081
+ });
5082
+ });
4949
5083
  this.hasNoPresets = computed(() => this.presets().length === 0);
4950
5084
  this.hasNoFilteredPresets = computed(() => this.filteredPresets().length === 0);
5085
+ this.editingPreset = computed(() => {
5086
+ const id = this.editingPresetId();
5087
+ if (!id)
5088
+ return null;
5089
+ return this.presets().find((p) => p.id === id) || null;
5090
+ });
5091
+ this.applyingPreset = computed(() => {
5092
+ const id = this.applyingPresetId();
5093
+ if (!id)
5094
+ return null;
5095
+ return this.presets().find((p) => p.id === id) || null;
5096
+ });
5097
+ this.deletePresetName = computed(() => {
5098
+ const id = this.deletePresetId();
5099
+ if (!id)
5100
+ return '';
5101
+ const preset = this.presets().find((p) => p.id === id);
5102
+ return preset?.name || '';
5103
+ });
4951
5104
  // Tool availability (based on config)
4952
5105
  this.isFeatureFlagsEnabled = computed(() => this.state.config().showFeatureFlagsTool ?? true);
4953
5106
  this.isPermissionsEnabled = computed(() => this.state.config().showPermissionsTool ?? true);
@@ -4972,15 +5125,38 @@ class ToolbarPresetsToolComponent {
4972
5125
  id: 'ngt-presets',
4973
5126
  isBeta: true,
4974
5127
  };
5128
+ effect(() => {
5129
+ const message = this.toastMessage();
5130
+ if (message) {
5131
+ if (this.toastTimeout) {
5132
+ clearTimeout(this.toastTimeout);
5133
+ }
5134
+ this.toastTimeout = setTimeout(() => {
5135
+ this.toastMessage.set(null);
5136
+ }, 3000);
5137
+ }
5138
+ });
4975
5139
  }
4976
- // Public methods
5140
+ // Toast helper
5141
+ showToast(message, type = 'success') {
5142
+ this.toastMessage.set(message);
5143
+ this.toastType.set(type);
5144
+ }
5145
+ // View mode switching
4977
5146
  onSearchChange(query) {
4978
5147
  this.searchQuery.set(query);
4979
5148
  }
5149
+ onSwitchToListMode() {
5150
+ this.viewMode.set('list');
5151
+ this.nameError.set(null);
5152
+ this.importError.set(null);
5153
+ this.deletePresetId.set(null);
5154
+ }
4980
5155
  onSwitchToCreateMode() {
4981
5156
  this.viewMode.set('create');
4982
5157
  this.presetName.set('');
4983
5158
  this.presetDescription.set('');
5159
+ this.nameError.set(null);
4984
5160
  // Reset checkboxes - only enable categories that have forced items
4985
5161
  this.includeFeatureFlags.set(this.getCurrentFlagsCount() > 0);
4986
5162
  this.includePermissions.set(this.getCurrentPermissionsCount() > 0);
@@ -4992,14 +5168,33 @@ class ToolbarPresetsToolComponent {
4992
5168
  this.selectedPermissionIds.set(new Set(this.forcedPermissions().map((p) => p.id)));
4993
5169
  this.selectedFeatureIds.set(new Set(this.forcedAppFeatures().map((f) => f.id)));
4994
5170
  }
4995
- onSwitchToListMode() {
4996
- this.viewMode.set('list');
5171
+ onSwitchToImportMode() {
5172
+ this.viewMode.set('import');
5173
+ this.importJson.set('');
5174
+ this.importError.set(null);
5175
+ this.isDragOver.set(false);
5176
+ }
5177
+ // Create preset
5178
+ onPresetNameChange(value) {
5179
+ this.presetName.set(value);
5180
+ if (value.trim()) {
5181
+ this.nameError.set(null);
5182
+ }
4997
5183
  }
4998
5184
  onSavePreset(event) {
4999
5185
  event.preventDefault();
5000
- if (!this.presetName())
5186
+ const name = this.presetName().trim();
5187
+ if (!name) {
5188
+ this.nameError.set('Name is required');
5189
+ return;
5190
+ }
5191
+ // Check for duplicate names
5192
+ const existingPreset = this.presets().find((p) => p.name.toLowerCase() === name.toLowerCase());
5193
+ if (existingPreset) {
5194
+ this.nameError.set('A preset with this name already exists');
5001
5195
  return;
5002
- this.presetsService.saveCurrentAsPreset(this.presetName(), this.presetDescription(), {
5196
+ }
5197
+ this.presetsService.saveCurrentAsPreset(name, this.presetDescription(), {
5003
5198
  includeFeatureFlags: this.includeFeatureFlags(),
5004
5199
  includePermissions: this.includePermissions(),
5005
5200
  includeAppFeatures: this.includeAppFeatures(),
@@ -5008,14 +5203,153 @@ class ToolbarPresetsToolComponent {
5008
5203
  selectedPermissionIds: Array.from(this.selectedPermissionIds()),
5009
5204
  selectedFeatureIds: Array.from(this.selectedFeatureIds()),
5010
5205
  });
5206
+ this.showToast('Preset created successfully');
5011
5207
  this.onSwitchToListMode();
5012
5208
  }
5013
- onApplyPreset(presetId) {
5014
- this.presetsService.applyPreset(presetId);
5209
+ // Edit preset
5210
+ onStartEdit(presetId) {
5211
+ const preset = this.presets().find((p) => p.id === presetId);
5212
+ if (!preset)
5213
+ return;
5214
+ this.editingPresetId.set(presetId);
5215
+ this.editName.set(preset.name);
5216
+ this.editDescription.set(preset.description || '');
5217
+ this.nameError.set(null);
5218
+ this.viewMode.set('edit');
5219
+ }
5220
+ onEditNameChange(value) {
5221
+ this.editName.set(value);
5222
+ if (value.trim()) {
5223
+ this.nameError.set(null);
5224
+ }
5225
+ }
5226
+ onSaveEdit(event) {
5227
+ event.preventDefault();
5228
+ const name = this.editName().trim();
5229
+ const presetId = this.editingPresetId();
5230
+ if (!name) {
5231
+ this.nameError.set('Name is required');
5232
+ return;
5233
+ }
5234
+ if (!presetId)
5235
+ return;
5236
+ // Check for duplicate names (excluding current preset)
5237
+ const existingPreset = this.presets().find((p) => p.id !== presetId && p.name.toLowerCase() === name.toLowerCase());
5238
+ if (existingPreset) {
5239
+ this.nameError.set('A preset with this name already exists');
5240
+ return;
5241
+ }
5242
+ this.presetsService.updatePresetMetadata(presetId, name, this.editDescription());
5243
+ this.showToast('Preset updated successfully');
5244
+ this.onSwitchToListMode();
5245
+ }
5246
+ onReplaceConfig() {
5247
+ const presetId = this.editingPresetId();
5248
+ if (!presetId)
5249
+ return;
5250
+ this.presetsService.updatePreset(presetId);
5251
+ this.showToast('Configuration replaced with current state');
5252
+ }
5253
+ // Import preset
5254
+ onDragOver(event) {
5255
+ event.preventDefault();
5256
+ event.stopPropagation();
5257
+ this.isDragOver.set(true);
5258
+ }
5259
+ onDragLeave(event) {
5260
+ event.preventDefault();
5261
+ event.stopPropagation();
5262
+ this.isDragOver.set(false);
5015
5263
  }
5264
+ onFileDrop(event) {
5265
+ event.preventDefault();
5266
+ event.stopPropagation();
5267
+ this.isDragOver.set(false);
5268
+ const files = event.dataTransfer?.files;
5269
+ if (files && files.length > 0) {
5270
+ this.processFile(files[0]);
5271
+ }
5272
+ }
5273
+ onFileSelect(event) {
5274
+ const input = event.target;
5275
+ if (input.files && input.files.length > 0) {
5276
+ this.processFile(input.files[0]);
5277
+ }
5278
+ }
5279
+ processFile(file) {
5280
+ if (!file.name.endsWith('.json')) {
5281
+ this.importError.set('Please select a .json file');
5282
+ return;
5283
+ }
5284
+ const reader = new FileReader();
5285
+ reader.onload = (e) => {
5286
+ const content = e.target?.result;
5287
+ this.importJson.set(content);
5288
+ this.importError.set(null);
5289
+ };
5290
+ reader.onerror = () => {
5291
+ this.importError.set('Failed to read file');
5292
+ };
5293
+ reader.readAsText(file);
5294
+ }
5295
+ onImportJsonChange(event) {
5296
+ const textarea = event.target;
5297
+ this.importJson.set(textarea.value);
5298
+ this.importError.set(null);
5299
+ }
5300
+ onImportPreset() {
5301
+ const json = this.importJson().trim();
5302
+ if (!json) {
5303
+ this.importError.set('Please provide JSON content');
5304
+ return;
5305
+ }
5306
+ try {
5307
+ const parsed = JSON.parse(json);
5308
+ // Validate required fields
5309
+ if (!parsed.name) {
5310
+ this.importError.set('Preset must have a name');
5311
+ return;
5312
+ }
5313
+ if (!parsed.config) {
5314
+ this.importError.set('Preset must have a config object');
5315
+ return;
5316
+ }
5317
+ this.presetsService.addPreset(parsed);
5318
+ this.showToast('Preset imported successfully');
5319
+ this.onSwitchToListMode();
5320
+ }
5321
+ catch {
5322
+ this.importError.set('Invalid JSON format');
5323
+ }
5324
+ }
5325
+ // Apply preset (partial)
5326
+ onStartApply(presetId) {
5327
+ this.applyingPresetId.set(presetId);
5328
+ this.applyFeatureFlags.set(true);
5329
+ this.applyPermissions.set(true);
5330
+ this.applyAppFeatures.set(true);
5331
+ this.applyLanguage.set(true);
5332
+ this.viewMode.set('apply');
5333
+ }
5334
+ onConfirmPartialApply() {
5335
+ const presetId = this.applyingPresetId();
5336
+ if (!presetId)
5337
+ return;
5338
+ this.presetsService.partialApplyPreset(presetId, {
5339
+ applyFeatureFlags: this.applyFeatureFlags(),
5340
+ applyPermissions: this.applyPermissions(),
5341
+ applyAppFeatures: this.applyAppFeatures(),
5342
+ applyLanguage: this.applyLanguage(),
5343
+ });
5344
+ this.showToast('Preset applied successfully');
5345
+ this.onSwitchToListMode();
5346
+ }
5347
+ // Update preset (with current state)
5016
5348
  onUpdatePreset(presetId) {
5017
5349
  this.presetsService.updatePreset(presetId);
5350
+ this.showToast('Preset updated with current state');
5018
5351
  }
5352
+ // Export preset
5019
5353
  onExportPreset(presetId) {
5020
5354
  const preset = this.presets().find((p) => p.id === presetId);
5021
5355
  if (!preset)
@@ -5028,11 +5362,25 @@ class ToolbarPresetsToolComponent {
5028
5362
  link.download = `preset-${preset.name.toLowerCase().replace(/\s+/g, '-')}.json`;
5029
5363
  link.click();
5030
5364
  URL.revokeObjectURL(url);
5365
+ this.showToast('Preset exported');
5031
5366
  }
5367
+ // Delete preset
5032
5368
  onDeletePreset(presetId) {
5033
- if (confirm('Are you sure you want to delete this preset?')) {
5369
+ this.deletePresetId.set(presetId);
5370
+ this.viewMode.set('delete');
5371
+ }
5372
+ onConfirmDelete() {
5373
+ const presetId = this.deletePresetId();
5374
+ if (presetId) {
5034
5375
  this.presetsService.deletePreset(presetId);
5376
+ this.showToast('Preset deleted');
5035
5377
  }
5378
+ this.deletePresetId.set(null);
5379
+ this.viewMode.set('list');
5380
+ }
5381
+ // Favorites
5382
+ onToggleFavorite(presetId) {
5383
+ this.presetsService.toggleFavorite(presetId);
5036
5384
  }
5037
5385
  // Protected methods
5038
5386
  getCurrentFlagsCount() {
@@ -5053,9 +5401,44 @@ class ToolbarPresetsToolComponent {
5053
5401
  formatDate(isoString) {
5054
5402
  return new Date(isoString).toLocaleDateString();
5055
5403
  }
5056
- /**
5057
- * Toggle selection of an individual item
5058
- */
5404
+ getPresetTooltip(preset) {
5405
+ const lines = [];
5406
+ // Feature Flags
5407
+ const flagsEnabled = preset.config.featureFlags.enabled;
5408
+ const flagsDisabled = preset.config.featureFlags.disabled;
5409
+ if (flagsEnabled.length > 0 || flagsDisabled.length > 0) {
5410
+ lines.push('Feature Flags:');
5411
+ flagsEnabled.forEach(id => lines.push(` ✓ ${id}: ON`));
5412
+ flagsDisabled.forEach(id => lines.push(` ✗ ${id}: OFF`));
5413
+ }
5414
+ // Permissions
5415
+ const permsGranted = preset.config.permissions.granted;
5416
+ const permsDenied = preset.config.permissions.denied;
5417
+ if (permsGranted.length > 0 || permsDenied.length > 0) {
5418
+ if (lines.length > 0)
5419
+ lines.push('');
5420
+ lines.push('Permissions:');
5421
+ permsGranted.forEach(id => lines.push(` ✓ ${id}: GRANTED`));
5422
+ permsDenied.forEach(id => lines.push(` ✗ ${id}: DENIED`));
5423
+ }
5424
+ // App Features
5425
+ const featuresEnabled = preset.config.appFeatures.enabled;
5426
+ const featuresDisabled = preset.config.appFeatures.disabled;
5427
+ if (featuresEnabled.length > 0 || featuresDisabled.length > 0) {
5428
+ if (lines.length > 0)
5429
+ lines.push('');
5430
+ lines.push('App Features:');
5431
+ featuresEnabled.forEach(id => lines.push(` ✓ ${id}: ON`));
5432
+ featuresDisabled.forEach(id => lines.push(` ✗ ${id}: OFF`));
5433
+ }
5434
+ // Language
5435
+ if (preset.config.language) {
5436
+ if (lines.length > 0)
5437
+ lines.push('');
5438
+ lines.push(`Language: ${preset.config.language}`);
5439
+ }
5440
+ return lines.length > 0 ? lines.join('\n') : 'No configuration';
5441
+ }
5059
5442
  toggleItemSelection(signal, itemId) {
5060
5443
  const current = new Set(signal());
5061
5444
  if (current.has(itemId)) {
@@ -5066,9 +5449,6 @@ class ToolbarPresetsToolComponent {
5066
5449
  }
5067
5450
  signal.set(current);
5068
5451
  }
5069
- /**
5070
- * Check if an item is selected
5071
- */
5072
5452
  isItemSelected(selectedSet, itemId) {
5073
5453
  return selectedSet.has(itemId);
5074
5454
  }
@@ -5076,8 +5456,17 @@ class ToolbarPresetsToolComponent {
5076
5456
  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
5457
  <ngt-toolbar-tool [options]="options" title="Presets" icon="layout">
5078
5458
  <div class="container">
5459
+ <!-- Toast Notification -->
5460
+ @if (toastMessage()) {
5461
+ <div class="toast" [class.toast--success]="toastType() === 'success'" [class.toast--error]="toastType() === 'error'">
5462
+ <span class="toast__icon">{{ toastType() === 'success' ? '✓' : '✕' }}</span>
5463
+ <span class="toast__message">{{ toastMessage() }}</span>
5464
+ </div>
5465
+ }
5466
+
5467
+
5079
5468
  <!-- Mode Toggle -->
5080
- @if (!hasNoPresets() || viewMode() === 'create') {
5469
+ @if (!hasNoPresets() || viewMode() !== 'list') {
5081
5470
  <div class="tool-header">
5082
5471
  @if (viewMode() === 'list') {
5083
5472
  <ngt-input
@@ -5086,18 +5475,24 @@ class ToolbarPresetsToolComponent {
5086
5475
  placeholder="Search presets..."
5087
5476
  [ariaLabel]="'Search presets'"
5088
5477
  />
5478
+ <ngt-button
5479
+ (click)="onSwitchToImportMode()"
5480
+ [ariaLabel]="'Import preset'"
5481
+ >
5482
+ Import
5483
+ </ngt-button>
5089
5484
  <ngt-button
5090
5485
  (click)="onSwitchToCreateMode()"
5091
5486
  [ariaLabel]="'Create new preset'"
5092
5487
  >
5093
- New Preset
5488
+ New
5094
5489
  </ngt-button>
5095
5490
  } @else {
5096
5491
  <ngt-button
5097
5492
  (click)="onSwitchToListMode()"
5098
5493
  [ariaLabel]="'Back to list'"
5099
5494
  >
5100
- ← Back to List
5495
+ ← Back
5101
5496
  </ngt-button>
5102
5497
  }
5103
5498
  </div>
@@ -5106,13 +5501,20 @@ class ToolbarPresetsToolComponent {
5106
5501
  <!-- Create Form -->
5107
5502
  @if (viewMode() === 'create') {
5108
5503
  <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
- />
5504
+ <h3 class="form-title">Create Preset</h3>
5505
+ <p class="form-hint">This will save the current forced values from other tools. Only items you've explicitly set will be included.</p>
5506
+ <div class="form-field">
5507
+ <ngt-input
5508
+ label="Preset Name *"
5509
+ [value]="presetName()"
5510
+ (valueChange)="onPresetNameChange($event)"
5511
+ placeholder="e.g., Admin User - Full Access"
5512
+ [ariaLabel]="'Preset name'"
5513
+ />
5514
+ @if (nameError()) {
5515
+ <span class="field-error">{{ nameError() }}</span>
5516
+ }
5517
+ </div>
5116
5518
  <ngt-input
5117
5519
  label="Description (optional)"
5118
5520
  [value]="presetDescription()"
@@ -5240,11 +5642,301 @@ class ToolbarPresetsToolComponent {
5240
5642
  </div>
5241
5643
 
5242
5644
  <div class="form-actions">
5645
+ <ngt-button type="button" (click)="onSwitchToListMode()">Cancel</ngt-button>
5243
5646
  <ngt-button type="submit">Save Preset</ngt-button>
5244
5647
  </div>
5245
5648
  </form>
5246
5649
  }
5247
5650
 
5651
+ <!-- Edit Form -->
5652
+ @if (viewMode() === 'edit') {
5653
+ <form (submit)="onSaveEdit($event)" class="preset-form">
5654
+ <h3 class="form-title">Edit Preset</h3>
5655
+ <div class="form-field">
5656
+ <ngt-input
5657
+ label="Preset Name *"
5658
+ [value]="editName()"
5659
+ (valueChange)="onEditNameChange($event)"
5660
+ placeholder="e.g., Admin User - Full Access"
5661
+ [ariaLabel]="'Preset name'"
5662
+ />
5663
+ @if (nameError()) {
5664
+ <span class="field-error">{{ nameError() }}</span>
5665
+ }
5666
+ </div>
5667
+ <ngt-input
5668
+ label="Description (optional)"
5669
+ [value]="editDescription()"
5670
+ (valueChange)="editDescription.set($event)"
5671
+ placeholder="Brief description of this preset"
5672
+ [ariaLabel]="'Preset description'"
5673
+ />
5674
+
5675
+ <!-- Show saved configuration read-only -->
5676
+ @if (editingPreset()) {
5677
+ <div class="config-preview">
5678
+ <h4>Saved Configuration</h4>
5679
+ @if (editingPreset()!.config.featureFlags.enabled.length > 0 || editingPreset()!.config.featureFlags.disabled.length > 0) {
5680
+ <div class="config-category">
5681
+ <span class="config-category__title">Feature Flags ({{ editingPreset()!.config.featureFlags.enabled.length + editingPreset()!.config.featureFlags.disabled.length }})</span>
5682
+ <div class="config-items">
5683
+ @for (id of editingPreset()!.config.featureFlags.enabled; track id) {
5684
+ <span class="config-item config-item--on">{{ id }}: ON</span>
5685
+ }
5686
+ @for (id of editingPreset()!.config.featureFlags.disabled; track id) {
5687
+ <span class="config-item config-item--off">{{ id }}: OFF</span>
5688
+ }
5689
+ </div>
5690
+ </div>
5691
+ }
5692
+ @if (editingPreset()!.config.permissions.granted.length > 0 || editingPreset()!.config.permissions.denied.length > 0) {
5693
+ <div class="config-category">
5694
+ <span class="config-category__title">Permissions ({{ editingPreset()!.config.permissions.granted.length + editingPreset()!.config.permissions.denied.length }})</span>
5695
+ <div class="config-items">
5696
+ @for (id of editingPreset()!.config.permissions.granted; track id) {
5697
+ <span class="config-item config-item--on">{{ id }}: GRANTED</span>
5698
+ }
5699
+ @for (id of editingPreset()!.config.permissions.denied; track id) {
5700
+ <span class="config-item config-item--off">{{ id }}: DENIED</span>
5701
+ }
5702
+ </div>
5703
+ </div>
5704
+ }
5705
+ @if (editingPreset()!.config.appFeatures.enabled.length > 0 || editingPreset()!.config.appFeatures.disabled.length > 0) {
5706
+ <div class="config-category">
5707
+ <span class="config-category__title">App Features ({{ editingPreset()!.config.appFeatures.enabled.length + editingPreset()!.config.appFeatures.disabled.length }})</span>
5708
+ <div class="config-items">
5709
+ @for (id of editingPreset()!.config.appFeatures.enabled; track id) {
5710
+ <span class="config-item config-item--on">{{ id }}: ON</span>
5711
+ }
5712
+ @for (id of editingPreset()!.config.appFeatures.disabled; track id) {
5713
+ <span class="config-item config-item--off">{{ id }}: OFF</span>
5714
+ }
5715
+ </div>
5716
+ </div>
5717
+ }
5718
+ @if (editingPreset()!.config.language) {
5719
+ <div class="config-category">
5720
+ <span class="config-category__title">Language</span>
5721
+ <span class="config-item">{{ editingPreset()!.config.language }}</span>
5722
+ </div>
5723
+ }
5724
+ <button type="button" class="replace-config-button" (click)="onReplaceConfig()">
5725
+ ↻ Replace with current toolbar state
5726
+ </button>
5727
+ </div>
5728
+ }
5729
+
5730
+ <div class="form-actions">
5731
+ <ngt-button type="button" (click)="onSwitchToListMode()">Cancel</ngt-button>
5732
+ <ngt-button type="submit">Save Changes</ngt-button>
5733
+ </div>
5734
+ </form>
5735
+ }
5736
+
5737
+ <!-- Import View -->
5738
+ @if (viewMode() === 'import') {
5739
+ <div class="import-view">
5740
+ <h3 class="form-title">Import Preset</h3>
5741
+
5742
+ <!-- File Drop Zone -->
5743
+ <div
5744
+ class="drop-zone"
5745
+ [class.drop-zone--active]="isDragOver()"
5746
+ (dragover)="onDragOver($event)"
5747
+ (dragleave)="onDragLeave($event)"
5748
+ (drop)="onFileDrop($event)"
5749
+ (click)="fileInput.click()"
5750
+ >
5751
+ <input
5752
+ #fileInput
5753
+ type="file"
5754
+ accept=".json"
5755
+ (change)="onFileSelect($event)"
5756
+ hidden
5757
+ />
5758
+ <span class="drop-zone__icon">📁</span>
5759
+ <span class="drop-zone__text">Drop a .json file here</span>
5760
+ <span class="drop-zone__hint">or click to browse</span>
5761
+ </div>
5762
+
5763
+ <div class="divider">
5764
+ <span>or paste JSON</span>
5765
+ </div>
5766
+
5767
+ <!-- JSON Textarea -->
5768
+ <textarea
5769
+ class="json-textarea"
5770
+ [value]="importJson()"
5771
+ (input)="onImportJsonChange($event)"
5772
+ placeholder='{"name": "...", "config": { ... }}'
5773
+ ></textarea>
5774
+
5775
+ @if (importError()) {
5776
+ <span class="field-error">{{ importError() }}</span>
5777
+ }
5778
+
5779
+ <div class="form-actions">
5780
+ <ngt-button type="button" (click)="onSwitchToListMode()">Cancel</ngt-button>
5781
+ <ngt-button (click)="onImportPreset()">Import Preset</ngt-button>
5782
+ </div>
5783
+ </div>
5784
+ }
5785
+
5786
+ <!-- Partial Apply View -->
5787
+ @if (viewMode() === 'apply') {
5788
+ <div class="apply-view">
5789
+ <h3 class="form-title">Apply: {{ applyingPreset()?.name }}</h3>
5790
+ <p class="apply-description">Select which parts to apply</p>
5791
+
5792
+ @if (applyingPreset()) {
5793
+ <div class="apply-categories">
5794
+ <!-- Feature Flags -->
5795
+ @if (applyingPreset()!.config.featureFlags.enabled.length > 0 || applyingPreset()!.config.featureFlags.disabled.length > 0) {
5796
+ <div class="apply-category" [class.apply-category--disabled]="!applyFeatureFlags()">
5797
+ <label class="apply-category__header">
5798
+ <input
5799
+ type="checkbox"
5800
+ [checked]="applyFeatureFlags()"
5801
+ (change)="applyFeatureFlags.set(!applyFeatureFlags())"
5802
+ />
5803
+ <span>Feature Flags ({{ applyingPreset()!.config.featureFlags.enabled.length + applyingPreset()!.config.featureFlags.disabled.length }})</span>
5804
+ </label>
5805
+ @if (applyFeatureFlags()) {
5806
+ <div class="apply-diff">
5807
+ @for (id of applyingPreset()!.config.featureFlags.enabled; track id) {
5808
+ <div class="diff-item">
5809
+ <span class="diff-item__name">{{ id }}</span>
5810
+ <span class="diff-item__arrow">→</span>
5811
+ <span class="diff-item__value diff-item__value--on">ON</span>
5812
+ </div>
5813
+ }
5814
+ @for (id of applyingPreset()!.config.featureFlags.disabled; track id) {
5815
+ <div class="diff-item">
5816
+ <span class="diff-item__name">{{ id }}</span>
5817
+ <span class="diff-item__arrow">→</span>
5818
+ <span class="diff-item__value diff-item__value--off">OFF</span>
5819
+ </div>
5820
+ }
5821
+ </div>
5822
+ }
5823
+ </div>
5824
+ }
5825
+
5826
+ <!-- Permissions -->
5827
+ @if (applyingPreset()!.config.permissions.granted.length > 0 || applyingPreset()!.config.permissions.denied.length > 0) {
5828
+ <div class="apply-category" [class.apply-category--disabled]="!applyPermissions()">
5829
+ <label class="apply-category__header">
5830
+ <input
5831
+ type="checkbox"
5832
+ [checked]="applyPermissions()"
5833
+ (change)="applyPermissions.set(!applyPermissions())"
5834
+ />
5835
+ <span>Permissions ({{ applyingPreset()!.config.permissions.granted.length + applyingPreset()!.config.permissions.denied.length }})</span>
5836
+ </label>
5837
+ @if (applyPermissions()) {
5838
+ <div class="apply-diff">
5839
+ @for (id of applyingPreset()!.config.permissions.granted; track id) {
5840
+ <div class="diff-item">
5841
+ <span class="diff-item__name">{{ id }}</span>
5842
+ <span class="diff-item__arrow">→</span>
5843
+ <span class="diff-item__value diff-item__value--on">GRANTED</span>
5844
+ </div>
5845
+ }
5846
+ @for (id of applyingPreset()!.config.permissions.denied; track id) {
5847
+ <div class="diff-item">
5848
+ <span class="diff-item__name">{{ id }}</span>
5849
+ <span class="diff-item__arrow">→</span>
5850
+ <span class="diff-item__value diff-item__value--off">DENIED</span>
5851
+ </div>
5852
+ }
5853
+ </div>
5854
+ }
5855
+ </div>
5856
+ }
5857
+
5858
+ <!-- App Features -->
5859
+ @if (applyingPreset()!.config.appFeatures.enabled.length > 0 || applyingPreset()!.config.appFeatures.disabled.length > 0) {
5860
+ <div class="apply-category" [class.apply-category--disabled]="!applyAppFeatures()">
5861
+ <label class="apply-category__header">
5862
+ <input
5863
+ type="checkbox"
5864
+ [checked]="applyAppFeatures()"
5865
+ (change)="applyAppFeatures.set(!applyAppFeatures())"
5866
+ />
5867
+ <span>App Features ({{ applyingPreset()!.config.appFeatures.enabled.length + applyingPreset()!.config.appFeatures.disabled.length }})</span>
5868
+ </label>
5869
+ @if (applyAppFeatures()) {
5870
+ <div class="apply-diff">
5871
+ @for (id of applyingPreset()!.config.appFeatures.enabled; track id) {
5872
+ <div class="diff-item">
5873
+ <span class="diff-item__name">{{ id }}</span>
5874
+ <span class="diff-item__arrow">→</span>
5875
+ <span class="diff-item__value diff-item__value--on">ON</span>
5876
+ </div>
5877
+ }
5878
+ @for (id of applyingPreset()!.config.appFeatures.disabled; track id) {
5879
+ <div class="diff-item">
5880
+ <span class="diff-item__name">{{ id }}</span>
5881
+ <span class="diff-item__arrow">→</span>
5882
+ <span class="diff-item__value diff-item__value--off">OFF</span>
5883
+ </div>
5884
+ }
5885
+ </div>
5886
+ }
5887
+ </div>
5888
+ }
5889
+
5890
+ <!-- Language -->
5891
+ @if (applyingPreset()!.config.language) {
5892
+ <div class="apply-category" [class.apply-category--disabled]="!applyLanguage()">
5893
+ <label class="apply-category__header">
5894
+ <input
5895
+ type="checkbox"
5896
+ [checked]="applyLanguage()"
5897
+ (change)="applyLanguage.set(!applyLanguage())"
5898
+ />
5899
+ <span>Language</span>
5900
+ </label>
5901
+ @if (applyLanguage()) {
5902
+ <div class="apply-diff">
5903
+ <div class="diff-item">
5904
+ <span class="diff-item__name">Language</span>
5905
+ <span class="diff-item__arrow">→</span>
5906
+ <span class="diff-item__value">{{ applyingPreset()!.config.language }}</span>
5907
+ </div>
5908
+ </div>
5909
+ }
5910
+ </div>
5911
+ }
5912
+ </div>
5913
+ }
5914
+
5915
+ <div class="form-actions">
5916
+ <ngt-button type="button" (click)="onSwitchToListMode()">Cancel</ngt-button>
5917
+ <ngt-button (click)="onConfirmPartialApply()">Apply Selected</ngt-button>
5918
+ </div>
5919
+ </div>
5920
+ }
5921
+
5922
+ <!-- Delete Confirmation View -->
5923
+ @if (viewMode() === 'delete') {
5924
+ <div class="delete-view">
5925
+ <div class="delete-view__content">
5926
+ <div class="delete-view__icon">🗑️</div>
5927
+ <h3 class="delete-view__title">Delete "{{ deletePresetName() }}"?</h3>
5928
+ <p class="delete-view__description">
5929
+ This preset will be permanently removed.
5930
+ This action cannot be undone.
5931
+ </p>
5932
+ </div>
5933
+ <div class="form-actions">
5934
+ <ngt-button type="button" (click)="onSwitchToListMode()">← Back</ngt-button>
5935
+ <button class="delete-button" (click)="onConfirmDelete()">Delete Preset</button>
5936
+ </div>
5937
+ </div>
5938
+ }
5939
+
5248
5940
  <!-- Empty State -->
5249
5941
  @if (viewMode() === 'list' && hasNoPresets()) {
5250
5942
  <div class="empty">
@@ -5263,83 +5955,49 @@ class ToolbarPresetsToolComponent {
5263
5955
  } @else if (viewMode() === 'list') {
5264
5956
  <!-- Preset List -->
5265
5957
  <div class="preset-list">
5266
- @for (preset of filteredPresets(); track preset.id) {
5267
- <div class="preset-card">
5958
+ @for (preset of sortedPresets(); track preset.id) {
5959
+ <div class="preset-card" [title]="getPresetTooltip(preset)">
5268
5960
  <div class="preset-card__header">
5269
- <h3>{{ preset.name }}</h3>
5961
+ <button
5962
+ class="favorite-button"
5963
+ [class.favorite-button--active]="preset.isFavorite"
5964
+ (click)="onToggleFavorite(preset.id); $event.stopPropagation()"
5965
+ [attr.aria-label]="preset.isFavorite ? 'Remove from favorites' : 'Add to favorites'"
5966
+ >{{ preset.isFavorite ? '★' : '☆' }}</button>
5967
+ <span class="preset-card__name">{{ preset.name }}</span>
5968
+ @if (preset.isSystem) {<span class="system-badge">SYS</span>}
5969
+ <span class="preset-card__spacer"></span>
5270
5970
  <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>
5971
+ <button class="icon-button" (click)="onStartApply(preset.id)"><ngt-icon name="refresh" /></button>
5972
+ @if (!preset.isSystem) {
5973
+ <button class="icon-button" (click)="onStartEdit(preset.id)"><ngt-icon name="edit" /></button>
5974
+ <button class="icon-button" (click)="onUpdatePreset(preset.id)"><ngt-icon name="gear" /></button>
5975
+ }
5976
+ <button class="icon-button" (click)="onExportPreset(preset.id)"><ngt-icon name="export" /></button>
5977
+ @if (!preset.isSystem) {
5978
+ <button class="icon-button" (click)="onDeletePreset(preset.id)"><ngt-icon name="trash" /></button>
5979
+ }
5303
5980
  </div>
5304
5981
  </div>
5305
5982
  @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>
5983
+ <div class="preset-card__row preset-card__row--meta">
5984
+ <span class="preset-card__description">{{ preset.description }}</span>
5310
5985
  </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>
5986
+ }
5987
+ <div class="preset-card__row preset-card__row--badges">
5988
+ @if (preset.config.featureFlags.enabled.length > 0 || preset.config.featureFlags.disabled.length > 0) {
5989
+ <span class="badge">{{ preset.config.featureFlags.enabled.length + preset.config.featureFlags.disabled.length }} flags</span>
5990
+ }
5991
+ @if (preset.config.permissions.granted.length > 0 || preset.config.permissions.denied.length > 0) {
5992
+ <span class="badge">{{ preset.config.permissions.granted.length + preset.config.permissions.denied.length }} perms</span>
5342
5993
  }
5994
+ @if (preset.config.appFeatures.enabled.length > 0 || preset.config.appFeatures.disabled.length > 0) {
5995
+ <span class="badge">{{ preset.config.appFeatures.enabled.length + preset.config.appFeatures.disabled.length }} features</span>
5996
+ }
5997
+ @if (preset.config.language) {
5998
+ <span class="badge badge--lang">{{ preset.config.language }}</span>
5999
+ }
6000
+ <span class="preset-card__date">{{ formatDate(preset.updatedAt) }}</span>
5343
6001
  </div>
5344
6002
  </div>
5345
6003
  }
@@ -5347,7 +6005,7 @@ class ToolbarPresetsToolComponent {
5347
6005
  }
5348
6006
  </div>
5349
6007
  </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 }); }
6008
+ `, 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
6009
  }
5352
6010
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarPresetsToolComponent, decorators: [{
5353
6011
  type: Component,
@@ -5360,8 +6018,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImpor
5360
6018
  ], template: `
5361
6019
  <ngt-toolbar-tool [options]="options" title="Presets" icon="layout">
5362
6020
  <div class="container">
6021
+ <!-- Toast Notification -->
6022
+ @if (toastMessage()) {
6023
+ <div class="toast" [class.toast--success]="toastType() === 'success'" [class.toast--error]="toastType() === 'error'">
6024
+ <span class="toast__icon">{{ toastType() === 'success' ? '✓' : '✕' }}</span>
6025
+ <span class="toast__message">{{ toastMessage() }}</span>
6026
+ </div>
6027
+ }
6028
+
6029
+
5363
6030
  <!-- Mode Toggle -->
5364
- @if (!hasNoPresets() || viewMode() === 'create') {
6031
+ @if (!hasNoPresets() || viewMode() !== 'list') {
5365
6032
  <div class="tool-header">
5366
6033
  @if (viewMode() === 'list') {
5367
6034
  <ngt-input
@@ -5370,18 +6037,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImpor
5370
6037
  placeholder="Search presets..."
5371
6038
  [ariaLabel]="'Search presets'"
5372
6039
  />
6040
+ <ngt-button
6041
+ (click)="onSwitchToImportMode()"
6042
+ [ariaLabel]="'Import preset'"
6043
+ >
6044
+ Import
6045
+ </ngt-button>
5373
6046
  <ngt-button
5374
6047
  (click)="onSwitchToCreateMode()"
5375
6048
  [ariaLabel]="'Create new preset'"
5376
6049
  >
5377
- New Preset
6050
+ New
5378
6051
  </ngt-button>
5379
6052
  } @else {
5380
6053
  <ngt-button
5381
6054
  (click)="onSwitchToListMode()"
5382
6055
  [ariaLabel]="'Back to list'"
5383
6056
  >
5384
- ← Back to List
6057
+ ← Back
5385
6058
  </ngt-button>
5386
6059
  }
5387
6060
  </div>
@@ -5390,13 +6063,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImpor
5390
6063
  <!-- Create Form -->
5391
6064
  @if (viewMode() === 'create') {
5392
6065
  <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
- />
6066
+ <h3 class="form-title">Create Preset</h3>
6067
+ <p class="form-hint">This will save the current forced values from other tools. Only items you've explicitly set will be included.</p>
6068
+ <div class="form-field">
6069
+ <ngt-input
6070
+ label="Preset Name *"
6071
+ [value]="presetName()"
6072
+ (valueChange)="onPresetNameChange($event)"
6073
+ placeholder="e.g., Admin User - Full Access"
6074
+ [ariaLabel]="'Preset name'"
6075
+ />
6076
+ @if (nameError()) {
6077
+ <span class="field-error">{{ nameError() }}</span>
6078
+ }
6079
+ </div>
5400
6080
  <ngt-input
5401
6081
  label="Description (optional)"
5402
6082
  [value]="presetDescription()"
@@ -5524,11 +6204,301 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImpor
5524
6204
  </div>
5525
6205
 
5526
6206
  <div class="form-actions">
6207
+ <ngt-button type="button" (click)="onSwitchToListMode()">Cancel</ngt-button>
5527
6208
  <ngt-button type="submit">Save Preset</ngt-button>
5528
6209
  </div>
5529
6210
  </form>
5530
6211
  }
5531
6212
 
6213
+ <!-- Edit Form -->
6214
+ @if (viewMode() === 'edit') {
6215
+ <form (submit)="onSaveEdit($event)" class="preset-form">
6216
+ <h3 class="form-title">Edit Preset</h3>
6217
+ <div class="form-field">
6218
+ <ngt-input
6219
+ label="Preset Name *"
6220
+ [value]="editName()"
6221
+ (valueChange)="onEditNameChange($event)"
6222
+ placeholder="e.g., Admin User - Full Access"
6223
+ [ariaLabel]="'Preset name'"
6224
+ />
6225
+ @if (nameError()) {
6226
+ <span class="field-error">{{ nameError() }}</span>
6227
+ }
6228
+ </div>
6229
+ <ngt-input
6230
+ label="Description (optional)"
6231
+ [value]="editDescription()"
6232
+ (valueChange)="editDescription.set($event)"
6233
+ placeholder="Brief description of this preset"
6234
+ [ariaLabel]="'Preset description'"
6235
+ />
6236
+
6237
+ <!-- Show saved configuration read-only -->
6238
+ @if (editingPreset()) {
6239
+ <div class="config-preview">
6240
+ <h4>Saved Configuration</h4>
6241
+ @if (editingPreset()!.config.featureFlags.enabled.length > 0 || editingPreset()!.config.featureFlags.disabled.length > 0) {
6242
+ <div class="config-category">
6243
+ <span class="config-category__title">Feature Flags ({{ editingPreset()!.config.featureFlags.enabled.length + editingPreset()!.config.featureFlags.disabled.length }})</span>
6244
+ <div class="config-items">
6245
+ @for (id of editingPreset()!.config.featureFlags.enabled; track id) {
6246
+ <span class="config-item config-item--on">{{ id }}: ON</span>
6247
+ }
6248
+ @for (id of editingPreset()!.config.featureFlags.disabled; track id) {
6249
+ <span class="config-item config-item--off">{{ id }}: OFF</span>
6250
+ }
6251
+ </div>
6252
+ </div>
6253
+ }
6254
+ @if (editingPreset()!.config.permissions.granted.length > 0 || editingPreset()!.config.permissions.denied.length > 0) {
6255
+ <div class="config-category">
6256
+ <span class="config-category__title">Permissions ({{ editingPreset()!.config.permissions.granted.length + editingPreset()!.config.permissions.denied.length }})</span>
6257
+ <div class="config-items">
6258
+ @for (id of editingPreset()!.config.permissions.granted; track id) {
6259
+ <span class="config-item config-item--on">{{ id }}: GRANTED</span>
6260
+ }
6261
+ @for (id of editingPreset()!.config.permissions.denied; track id) {
6262
+ <span class="config-item config-item--off">{{ id }}: DENIED</span>
6263
+ }
6264
+ </div>
6265
+ </div>
6266
+ }
6267
+ @if (editingPreset()!.config.appFeatures.enabled.length > 0 || editingPreset()!.config.appFeatures.disabled.length > 0) {
6268
+ <div class="config-category">
6269
+ <span class="config-category__title">App Features ({{ editingPreset()!.config.appFeatures.enabled.length + editingPreset()!.config.appFeatures.disabled.length }})</span>
6270
+ <div class="config-items">
6271
+ @for (id of editingPreset()!.config.appFeatures.enabled; track id) {
6272
+ <span class="config-item config-item--on">{{ id }}: ON</span>
6273
+ }
6274
+ @for (id of editingPreset()!.config.appFeatures.disabled; track id) {
6275
+ <span class="config-item config-item--off">{{ id }}: OFF</span>
6276
+ }
6277
+ </div>
6278
+ </div>
6279
+ }
6280
+ @if (editingPreset()!.config.language) {
6281
+ <div class="config-category">
6282
+ <span class="config-category__title">Language</span>
6283
+ <span class="config-item">{{ editingPreset()!.config.language }}</span>
6284
+ </div>
6285
+ }
6286
+ <button type="button" class="replace-config-button" (click)="onReplaceConfig()">
6287
+ ↻ Replace with current toolbar state
6288
+ </button>
6289
+ </div>
6290
+ }
6291
+
6292
+ <div class="form-actions">
6293
+ <ngt-button type="button" (click)="onSwitchToListMode()">Cancel</ngt-button>
6294
+ <ngt-button type="submit">Save Changes</ngt-button>
6295
+ </div>
6296
+ </form>
6297
+ }
6298
+
6299
+ <!-- Import View -->
6300
+ @if (viewMode() === 'import') {
6301
+ <div class="import-view">
6302
+ <h3 class="form-title">Import Preset</h3>
6303
+
6304
+ <!-- File Drop Zone -->
6305
+ <div
6306
+ class="drop-zone"
6307
+ [class.drop-zone--active]="isDragOver()"
6308
+ (dragover)="onDragOver($event)"
6309
+ (dragleave)="onDragLeave($event)"
6310
+ (drop)="onFileDrop($event)"
6311
+ (click)="fileInput.click()"
6312
+ >
6313
+ <input
6314
+ #fileInput
6315
+ type="file"
6316
+ accept=".json"
6317
+ (change)="onFileSelect($event)"
6318
+ hidden
6319
+ />
6320
+ <span class="drop-zone__icon">📁</span>
6321
+ <span class="drop-zone__text">Drop a .json file here</span>
6322
+ <span class="drop-zone__hint">or click to browse</span>
6323
+ </div>
6324
+
6325
+ <div class="divider">
6326
+ <span>or paste JSON</span>
6327
+ </div>
6328
+
6329
+ <!-- JSON Textarea -->
6330
+ <textarea
6331
+ class="json-textarea"
6332
+ [value]="importJson()"
6333
+ (input)="onImportJsonChange($event)"
6334
+ placeholder='{"name": "...", "config": { ... }}'
6335
+ ></textarea>
6336
+
6337
+ @if (importError()) {
6338
+ <span class="field-error">{{ importError() }}</span>
6339
+ }
6340
+
6341
+ <div class="form-actions">
6342
+ <ngt-button type="button" (click)="onSwitchToListMode()">Cancel</ngt-button>
6343
+ <ngt-button (click)="onImportPreset()">Import Preset</ngt-button>
6344
+ </div>
6345
+ </div>
6346
+ }
6347
+
6348
+ <!-- Partial Apply View -->
6349
+ @if (viewMode() === 'apply') {
6350
+ <div class="apply-view">
6351
+ <h3 class="form-title">Apply: {{ applyingPreset()?.name }}</h3>
6352
+ <p class="apply-description">Select which parts to apply</p>
6353
+
6354
+ @if (applyingPreset()) {
6355
+ <div class="apply-categories">
6356
+ <!-- Feature Flags -->
6357
+ @if (applyingPreset()!.config.featureFlags.enabled.length > 0 || applyingPreset()!.config.featureFlags.disabled.length > 0) {
6358
+ <div class="apply-category" [class.apply-category--disabled]="!applyFeatureFlags()">
6359
+ <label class="apply-category__header">
6360
+ <input
6361
+ type="checkbox"
6362
+ [checked]="applyFeatureFlags()"
6363
+ (change)="applyFeatureFlags.set(!applyFeatureFlags())"
6364
+ />
6365
+ <span>Feature Flags ({{ applyingPreset()!.config.featureFlags.enabled.length + applyingPreset()!.config.featureFlags.disabled.length }})</span>
6366
+ </label>
6367
+ @if (applyFeatureFlags()) {
6368
+ <div class="apply-diff">
6369
+ @for (id of applyingPreset()!.config.featureFlags.enabled; track id) {
6370
+ <div class="diff-item">
6371
+ <span class="diff-item__name">{{ id }}</span>
6372
+ <span class="diff-item__arrow">→</span>
6373
+ <span class="diff-item__value diff-item__value--on">ON</span>
6374
+ </div>
6375
+ }
6376
+ @for (id of applyingPreset()!.config.featureFlags.disabled; track id) {
6377
+ <div class="diff-item">
6378
+ <span class="diff-item__name">{{ id }}</span>
6379
+ <span class="diff-item__arrow">→</span>
6380
+ <span class="diff-item__value diff-item__value--off">OFF</span>
6381
+ </div>
6382
+ }
6383
+ </div>
6384
+ }
6385
+ </div>
6386
+ }
6387
+
6388
+ <!-- Permissions -->
6389
+ @if (applyingPreset()!.config.permissions.granted.length > 0 || applyingPreset()!.config.permissions.denied.length > 0) {
6390
+ <div class="apply-category" [class.apply-category--disabled]="!applyPermissions()">
6391
+ <label class="apply-category__header">
6392
+ <input
6393
+ type="checkbox"
6394
+ [checked]="applyPermissions()"
6395
+ (change)="applyPermissions.set(!applyPermissions())"
6396
+ />
6397
+ <span>Permissions ({{ applyingPreset()!.config.permissions.granted.length + applyingPreset()!.config.permissions.denied.length }})</span>
6398
+ </label>
6399
+ @if (applyPermissions()) {
6400
+ <div class="apply-diff">
6401
+ @for (id of applyingPreset()!.config.permissions.granted; track id) {
6402
+ <div class="diff-item">
6403
+ <span class="diff-item__name">{{ id }}</span>
6404
+ <span class="diff-item__arrow">→</span>
6405
+ <span class="diff-item__value diff-item__value--on">GRANTED</span>
6406
+ </div>
6407
+ }
6408
+ @for (id of applyingPreset()!.config.permissions.denied; track id) {
6409
+ <div class="diff-item">
6410
+ <span class="diff-item__name">{{ id }}</span>
6411
+ <span class="diff-item__arrow">→</span>
6412
+ <span class="diff-item__value diff-item__value--off">DENIED</span>
6413
+ </div>
6414
+ }
6415
+ </div>
6416
+ }
6417
+ </div>
6418
+ }
6419
+
6420
+ <!-- App Features -->
6421
+ @if (applyingPreset()!.config.appFeatures.enabled.length > 0 || applyingPreset()!.config.appFeatures.disabled.length > 0) {
6422
+ <div class="apply-category" [class.apply-category--disabled]="!applyAppFeatures()">
6423
+ <label class="apply-category__header">
6424
+ <input
6425
+ type="checkbox"
6426
+ [checked]="applyAppFeatures()"
6427
+ (change)="applyAppFeatures.set(!applyAppFeatures())"
6428
+ />
6429
+ <span>App Features ({{ applyingPreset()!.config.appFeatures.enabled.length + applyingPreset()!.config.appFeatures.disabled.length }})</span>
6430
+ </label>
6431
+ @if (applyAppFeatures()) {
6432
+ <div class="apply-diff">
6433
+ @for (id of applyingPreset()!.config.appFeatures.enabled; track id) {
6434
+ <div class="diff-item">
6435
+ <span class="diff-item__name">{{ id }}</span>
6436
+ <span class="diff-item__arrow">→</span>
6437
+ <span class="diff-item__value diff-item__value--on">ON</span>
6438
+ </div>
6439
+ }
6440
+ @for (id of applyingPreset()!.config.appFeatures.disabled; track id) {
6441
+ <div class="diff-item">
6442
+ <span class="diff-item__name">{{ id }}</span>
6443
+ <span class="diff-item__arrow">→</span>
6444
+ <span class="diff-item__value diff-item__value--off">OFF</span>
6445
+ </div>
6446
+ }
6447
+ </div>
6448
+ }
6449
+ </div>
6450
+ }
6451
+
6452
+ <!-- Language -->
6453
+ @if (applyingPreset()!.config.language) {
6454
+ <div class="apply-category" [class.apply-category--disabled]="!applyLanguage()">
6455
+ <label class="apply-category__header">
6456
+ <input
6457
+ type="checkbox"
6458
+ [checked]="applyLanguage()"
6459
+ (change)="applyLanguage.set(!applyLanguage())"
6460
+ />
6461
+ <span>Language</span>
6462
+ </label>
6463
+ @if (applyLanguage()) {
6464
+ <div class="apply-diff">
6465
+ <div class="diff-item">
6466
+ <span class="diff-item__name">Language</span>
6467
+ <span class="diff-item__arrow">→</span>
6468
+ <span class="diff-item__value">{{ applyingPreset()!.config.language }}</span>
6469
+ </div>
6470
+ </div>
6471
+ }
6472
+ </div>
6473
+ }
6474
+ </div>
6475
+ }
6476
+
6477
+ <div class="form-actions">
6478
+ <ngt-button type="button" (click)="onSwitchToListMode()">Cancel</ngt-button>
6479
+ <ngt-button (click)="onConfirmPartialApply()">Apply Selected</ngt-button>
6480
+ </div>
6481
+ </div>
6482
+ }
6483
+
6484
+ <!-- Delete Confirmation View -->
6485
+ @if (viewMode() === 'delete') {
6486
+ <div class="delete-view">
6487
+ <div class="delete-view__content">
6488
+ <div class="delete-view__icon">🗑️</div>
6489
+ <h3 class="delete-view__title">Delete "{{ deletePresetName() }}"?</h3>
6490
+ <p class="delete-view__description">
6491
+ This preset will be permanently removed.
6492
+ This action cannot be undone.
6493
+ </p>
6494
+ </div>
6495
+ <div class="form-actions">
6496
+ <ngt-button type="button" (click)="onSwitchToListMode()">← Back</ngt-button>
6497
+ <button class="delete-button" (click)="onConfirmDelete()">Delete Preset</button>
6498
+ </div>
6499
+ </div>
6500
+ }
6501
+
5532
6502
  <!-- Empty State -->
5533
6503
  @if (viewMode() === 'list' && hasNoPresets()) {
5534
6504
  <div class="empty">
@@ -5547,83 +6517,49 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImpor
5547
6517
  } @else if (viewMode() === 'list') {
5548
6518
  <!-- Preset List -->
5549
6519
  <div class="preset-list">
5550
- @for (preset of filteredPresets(); track preset.id) {
5551
- <div class="preset-card">
6520
+ @for (preset of sortedPresets(); track preset.id) {
6521
+ <div class="preset-card" [title]="getPresetTooltip(preset)">
5552
6522
  <div class="preset-card__header">
5553
- <h3>{{ preset.name }}</h3>
6523
+ <button
6524
+ class="favorite-button"
6525
+ [class.favorite-button--active]="preset.isFavorite"
6526
+ (click)="onToggleFavorite(preset.id); $event.stopPropagation()"
6527
+ [attr.aria-label]="preset.isFavorite ? 'Remove from favorites' : 'Add to favorites'"
6528
+ >{{ preset.isFavorite ? '★' : '☆' }}</button>
6529
+ <span class="preset-card__name">{{ preset.name }}</span>
6530
+ @if (preset.isSystem) {<span class="system-badge">SYS</span>}
6531
+ <span class="preset-card__spacer"></span>
5554
6532
  <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>
6533
+ <button class="icon-button" (click)="onStartApply(preset.id)"><ngt-icon name="refresh" /></button>
6534
+ @if (!preset.isSystem) {
6535
+ <button class="icon-button" (click)="onStartEdit(preset.id)"><ngt-icon name="edit" /></button>
6536
+ <button class="icon-button" (click)="onUpdatePreset(preset.id)"><ngt-icon name="gear" /></button>
6537
+ }
6538
+ <button class="icon-button" (click)="onExportPreset(preset.id)"><ngt-icon name="export" /></button>
6539
+ @if (!preset.isSystem) {
6540
+ <button class="icon-button" (click)="onDeletePreset(preset.id)"><ngt-icon name="trash" /></button>
6541
+ }
5587
6542
  </div>
5588
6543
  </div>
5589
6544
  @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>
6545
+ <div class="preset-card__row preset-card__row--meta">
6546
+ <span class="preset-card__description">{{ preset.description }}</span>
5594
6547
  </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>
6548
+ }
6549
+ <div class="preset-card__row preset-card__row--badges">
6550
+ @if (preset.config.featureFlags.enabled.length > 0 || preset.config.featureFlags.disabled.length > 0) {
6551
+ <span class="badge">{{ preset.config.featureFlags.enabled.length + preset.config.featureFlags.disabled.length }} flags</span>
5626
6552
  }
6553
+ @if (preset.config.permissions.granted.length > 0 || preset.config.permissions.denied.length > 0) {
6554
+ <span class="badge">{{ preset.config.permissions.granted.length + preset.config.permissions.denied.length }} perms</span>
6555
+ }
6556
+ @if (preset.config.appFeatures.enabled.length > 0 || preset.config.appFeatures.disabled.length > 0) {
6557
+ <span class="badge">{{ preset.config.appFeatures.enabled.length + preset.config.appFeatures.disabled.length }} features</span>
6558
+ }
6559
+ @if (preset.config.language) {
6560
+ <span class="badge badge--lang">{{ preset.config.language }}</span>
6561
+ }
6562
+ <span class="preset-card__date">{{ formatDate(preset.updatedAt) }}</span>
5627
6563
  </div>
5628
6564
  </div>
5629
6565
  }
@@ -5631,8 +6567,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImpor
5631
6567
  }
5632
6568
  </div>
5633
6569
  </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
- }] });
6570
+ `, 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"] }]
6571
+ }], ctorParameters: () => [] });
5636
6572
 
5637
6573
  class ToolbarComponent {
5638
6574
  constructor() {
@@ -5934,6 +6870,68 @@ function initToolbar(appRef, options) {
5934
6870
  };
5935
6871
  }
5936
6872
 
6873
+ class ToolbarStepDirective {
6874
+ constructor() {
6875
+ this.ngtStep = input.required();
6876
+ this.stepTitle = input('');
6877
+ this.templateRef = inject(TemplateRef);
6878
+ }
6879
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarStepDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
6880
+ 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 }); }
6881
+ }
6882
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarStepDirective, decorators: [{
6883
+ type: Directive,
6884
+ args: [{
6885
+ selector: '[ngtStep]',
6886
+ standalone: true,
6887
+ }]
6888
+ }] });
6889
+
6890
+ class ToolbarStepViewComponent {
6891
+ constructor() {
6892
+ this.currentStep = input.required();
6893
+ this.defaultStep = input('list');
6894
+ this.back = output();
6895
+ this.steps = contentChildren(ToolbarStepDirective);
6896
+ this.isDefaultStep = computed(() => this.currentStep() === this.defaultStep());
6897
+ this.activeStep = computed(() => this.steps().find((s) => s.ngtStep() === this.currentStep()));
6898
+ this.activeTitle = computed(() => this.activeStep()?.stepTitle() ?? '');
6899
+ this.activeTemplate = computed(() => this.activeStep()?.templateRef ?? null);
6900
+ }
6901
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarStepViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
6902
+ 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: `
6903
+ @if (!isDefaultStep()) {
6904
+ <div class="step-header">
6905
+ <ngt-button (click)="back.emit()" ariaLabel="Back">\u2190 Back</ngt-button>
6906
+ @if (activeTitle()) {
6907
+ <span class="step-title">{{ activeTitle() }}</span>
6908
+ }
6909
+ </div>
6910
+ }
6911
+
6912
+ @if (activeTemplate(); as tmpl) {
6913
+ <ng-container [ngTemplateOutlet]="tmpl" />
6914
+ }
6915
+ `, 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 }); }
6916
+ }
6917
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarStepViewComponent, decorators: [{
6918
+ type: Component,
6919
+ args: [{ selector: 'ngt-step-view', standalone: true, imports: [NgTemplateOutlet, ToolbarButtonComponent], template: `
6920
+ @if (!isDefaultStep()) {
6921
+ <div class="step-header">
6922
+ <ngt-button (click)="back.emit()" ariaLabel="Back">\u2190 Back</ngt-button>
6923
+ @if (activeTitle()) {
6924
+ <span class="step-title">{{ activeTitle() }}</span>
6925
+ }
6926
+ </div>
6927
+ }
6928
+
6929
+ @if (activeTemplate(); as tmpl) {
6930
+ <ng-container [ngTemplateOutlet]="tmpl" />
6931
+ }
6932
+ `, 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"] }]
6933
+ }] });
6934
+
5937
6935
  /**
5938
6936
  * Public service for managing dev toolbar presets.
5939
6937
  * Allows developers to programmatically save, load, and manage presets.
@@ -5978,6 +6976,30 @@ class ToolbarPresetsService {
5978
6976
  deletePreset(presetId) {
5979
6977
  this.internalService.deletePreset(presetId);
5980
6978
  }
6979
+ /**
6980
+ * Toggle favorite status for a preset
6981
+ * @param presetId - ID of the preset to toggle
6982
+ */
6983
+ toggleFavorite(presetId) {
6984
+ this.internalService.toggleFavorite(presetId);
6985
+ }
6986
+ /**
6987
+ * Apply a preset with partial options (only selected categories)
6988
+ * @param presetId - ID of the preset to apply
6989
+ * @param options - Options for which categories to apply
6990
+ */
6991
+ async partialApplyPreset(presetId, options) {
6992
+ return this.internalService.partialApplyPreset(presetId, options);
6993
+ }
6994
+ /**
6995
+ * Update only the metadata (name, description) of a preset
6996
+ * @param presetId - ID of the preset to update
6997
+ * @param name - New name for the preset
6998
+ * @param description - New description for the preset
6999
+ */
7000
+ updatePresetMetadata(presetId, name, description) {
7001
+ this.internalService.updatePresetMetadata(presetId, name, description);
7002
+ }
5981
7003
  /**
5982
7004
  * Export a preset as JSON string
5983
7005
  * @param presetId - ID of the preset to export
@@ -6002,9 +7024,10 @@ class ToolbarPresetsService {
6002
7024
  /**
6003
7025
  * Initialize presets with predefined configurations.
6004
7026
  * Useful for setting up default presets that all developers can use.
7027
+ * Skips presets that already exist (by name) to avoid duplicates.
6005
7028
  *
6006
7029
  * @param presets - Array of preset configurations to initialize
6007
- * @returns Array of created presets
7030
+ * @returns Array of created presets (only newly added ones)
6008
7031
  *
6009
7032
  * @example
6010
7033
  * ```typescript
@@ -6053,10 +7076,15 @@ class ToolbarPresetsService {
6053
7076
  * ```
6054
7077
  */
6055
7078
  initializePresets(presets) {
6056
- return presets.map((preset) => this.internalService.addPreset({
7079
+ const existingPresets = this.internalService.presets();
7080
+ const existingNames = new Set(existingPresets.map((p) => p.name.toLowerCase()));
7081
+ return presets
7082
+ .filter((preset) => !existingNames.has(preset.name.toLowerCase()))
7083
+ .map((preset) => this.internalService.addPreset({
6057
7084
  id: '', // Will be generated
6058
7085
  createdAt: '', // Will be generated
6059
7086
  updatedAt: '', // Will be generated
7087
+ isSystem: true, // Mark as system preset (not editable)
6060
7088
  ...preset,
6061
7089
  }));
6062
7090
  }
@@ -6085,36 +7113,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImpor
6085
7113
  }] });
6086
7114
 
6087
7115
  // 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
7116
 
6115
7117
  /**
6116
7118
  * Generated bundle index. Do not edit.
6117
7119
  */
6118
7120
 
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 };
7121
+ 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
7122
  //# sourceMappingURL=ngx-dev-toolbar.mjs.map