ngx-dev-toolbar 1.0.4 → 1.0.5

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.
@@ -158,6 +158,55 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImpor
158
158
  }]
159
159
  }] });
160
160
 
161
+ class BoltIconComponent {
162
+ constructor() {
163
+ this.fill = input('#FFFF');
164
+ }
165
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: BoltIconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
166
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.0.5", type: BoltIconComponent, isStandalone: true, selector: "ndt-bolt-icon", inputs: { fill: { classPropertyName: "fill", publicName: "fill", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
167
+ <svg
168
+ [attr.fill]="fill()"
169
+ xmlns="http://www.w3.org/2000/svg"
170
+ width="24"
171
+ height="24"
172
+ viewBox="0 0 256 256"
173
+ >
174
+ <path
175
+ d="M160,16,144,96H208l-96,144,16-80H64Z"
176
+ opacity="0.2"
177
+ ></path>
178
+ <path
179
+ d="M215.79,118.17a8,8,0,0,0-5-5.66L153.18,90.9l14.66-73.33a8,8,0,0,0-13.69-7l-112,120a8,8,0,0,0,3,13l57.63,21.61L88.16,238.43a8,8,0,0,0,13.69,7l112-120A8,8,0,0,0,215.79,118.17ZM109.37,214l10.47-52.38a8,8,0,0,0-5-9.06L62,132.71l84.62-90.66L136.16,94.43a8,8,0,0,0,5,9.06l52.8,19.8Z"
180
+ ></path>
181
+ </svg>
182
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
183
+ }
184
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: BoltIconComponent, decorators: [{
185
+ type: Component,
186
+ args: [{
187
+ selector: 'ndt-bolt-icon',
188
+ standalone: true,
189
+ changeDetection: ChangeDetectionStrategy.OnPush,
190
+ template: `
191
+ <svg
192
+ [attr.fill]="fill()"
193
+ xmlns="http://www.w3.org/2000/svg"
194
+ width="24"
195
+ height="24"
196
+ viewBox="0 0 256 256"
197
+ >
198
+ <path
199
+ d="M160,16,144,96H208l-96,144,16-80H64Z"
200
+ opacity="0.2"
201
+ ></path>
202
+ <path
203
+ d="M215.79,118.17a8,8,0,0,0-5-5.66L153.18,90.9l14.66-73.33a8,8,0,0,0-13.69-7l-112,120a8,8,0,0,0,3,13l57.63,21.61L88.16,238.43a8,8,0,0,0,13.69,7l112-120A8,8,0,0,0,215.79,118.17ZM109.37,214l10.47-52.38a8,8,0,0,0-5-9.06L62,132.71l84.62-90.66L136.16,94.43a8,8,0,0,0,5,9.06l52.8,19.8Z"
204
+ ></path>
205
+ </svg>
206
+ `,
207
+ }]
208
+ }] });
209
+
161
210
  class BugIconComponent {
162
211
  constructor() {
163
212
  this.fill = input('#FFFF');
@@ -414,6 +463,55 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImpor
414
463
  }]
415
464
  }] });
416
465
 
466
+ class FilterIconComponent {
467
+ constructor() {
468
+ this.fill = input('#FFFF');
469
+ }
470
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: FilterIconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
471
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.0.5", type: FilterIconComponent, isStandalone: true, selector: "ndt-filter-icon", inputs: { fill: { classPropertyName: "fill", publicName: "fill", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
472
+ <svg
473
+ [attr.fill]="fill()"
474
+ xmlns="http://www.w3.org/2000/svg"
475
+ width="24"
476
+ height="24"
477
+ viewBox="0 0 256 256"
478
+ >
479
+ <path
480
+ d="M227.81,66.76l-.08.09L160,139.17v55.49A8,8,0,0,1,156.94,201l-32,21.33A8,8,0,0,1,112,216V139.17L44.27,66.85l-.08-.09A16,16,0,0,1,56,40H200a16,16,0,0,1,11.84,26.76Z"
481
+ opacity="0.2"
482
+ ></path>
483
+ <path
484
+ d="M230.6,49.53A23.94,23.94,0,0,0,200,32H56A24,24,0,0,0,38.15,65.67L104,139.37V216a8,8,0,0,0,11.58,7.16l32-16A8,8,0,0,0,152,200V139.37l65.85-73.7A23.93,23.93,0,0,0,230.6,49.53ZM203.36,54.86a8,8,0,0,1,.07,9.12L136,139.17V196.58l-16,8V139.17L52.57,64A8,8,0,0,1,56,48H200A8,8,0,0,1,203.36,54.86Z"
485
+ ></path>
486
+ </svg>
487
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
488
+ }
489
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: FilterIconComponent, decorators: [{
490
+ type: Component,
491
+ args: [{
492
+ selector: 'ndt-filter-icon',
493
+ standalone: true,
494
+ changeDetection: ChangeDetectionStrategy.OnPush,
495
+ template: `
496
+ <svg
497
+ [attr.fill]="fill()"
498
+ xmlns="http://www.w3.org/2000/svg"
499
+ width="24"
500
+ height="24"
501
+ viewBox="0 0 256 256"
502
+ >
503
+ <path
504
+ d="M227.81,66.76l-.08.09L160,139.17v55.49A8,8,0,0,1,156.94,201l-32,21.33A8,8,0,0,1,112,216V139.17L44.27,66.85l-.08-.09A16,16,0,0,1,56,40H200a16,16,0,0,1,11.84,26.76Z"
505
+ opacity="0.2"
506
+ ></path>
507
+ <path
508
+ d="M230.6,49.53A23.94,23.94,0,0,0,200,32H56A24,24,0,0,0,38.15,65.67L104,139.37V216a8,8,0,0,0,11.58,7.16l32-16A8,8,0,0,0,152,200V139.37l65.85-73.7A23.93,23.93,0,0,0,230.6,49.53ZM203.36,54.86a8,8,0,0,1,.07,9.12L136,139.17V196.58l-16,8V139.17L52.57,64A8,8,0,0,1,56,48H200A8,8,0,0,1,203.36,54.86Z"
509
+ ></path>
510
+ </svg>
511
+ `,
512
+ }]
513
+ }] });
514
+
417
515
  class GaugeIconComponent {
418
516
  constructor() {
419
517
  this.fill = input('#FFFF');
@@ -729,6 +827,47 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImpor
729
827
  }]
730
828
  }] });
731
829
 
830
+ class LockIconComponent {
831
+ constructor() {
832
+ this.fill = input('#FFFF');
833
+ }
834
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: LockIconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
835
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.0.5", type: LockIconComponent, isStandalone: true, selector: "ndt-lock-icon", inputs: { fill: { classPropertyName: "fill", publicName: "fill", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
836
+ <svg
837
+ [attr.fill]="fill()"
838
+ xmlns="http://www.w3.org/2000/svg"
839
+ width="24"
840
+ height="24"
841
+ viewBox="0 0 256 256"
842
+ >
843
+ <path
844
+ d="M208,80H176V56a48,48,0,0,0-96,0V80H48A16,16,0,0,0,32,96V208a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V96A16,16,0,0,0,208,80ZM96,56a32,32,0,0,1,64,0V80H96ZM208,208H48V96H208V208Zm-68-56a12,12,0,1,1-12-12A12,12,0,0,1,140,152Z"
845
+ ></path>
846
+ </svg>
847
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
848
+ }
849
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: LockIconComponent, decorators: [{
850
+ type: Component,
851
+ args: [{
852
+ selector: 'ndt-lock-icon',
853
+ standalone: true,
854
+ changeDetection: ChangeDetectionStrategy.OnPush,
855
+ template: `
856
+ <svg
857
+ [attr.fill]="fill()"
858
+ xmlns="http://www.w3.org/2000/svg"
859
+ width="24"
860
+ height="24"
861
+ viewBox="0 0 256 256"
862
+ >
863
+ <path
864
+ d="M208,80H176V56a48,48,0,0,0-96,0V80H48A16,16,0,0,0,32,96V208a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V96A16,16,0,0,0,208,80ZM96,56a32,32,0,0,1,64,0V80H96ZM208,208H48V96H208V208Zm-68-56a12,12,0,1,1-12-12A12,12,0,0,1,140,152Z"
865
+ ></path>
866
+ </svg>
867
+ `,
868
+ }]
869
+ }] });
870
+
732
871
  class MoonIconComponent {
733
872
  constructor() {
734
873
  this.fill = input('#FFFF');
@@ -1262,6 +1401,8 @@ class DevToolbarIconComponent {
1262
1401
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.5", type: DevToolbarIconComponent, isStandalone: true, selector: "ndt-icon", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
1263
1402
  @switch (name()) { @case ('angular') {
1264
1403
  <ndt-angular-icon />
1404
+ } @case ('bolt') {
1405
+ <ndt-bolt-icon [fill]="fill()" />
1265
1406
  } @case ('bug') {
1266
1407
  <ndt-bug-icon [fill]="fill()" />
1267
1408
  } @case ('code') {
@@ -1272,6 +1413,8 @@ class DevToolbarIconComponent {
1272
1413
  <ndt-docs-icon [fill]="fill()" />
1273
1414
  } @case ('export') {
1274
1415
  <ndt-export-icon [fill]="fill()" />
1416
+ } @case ('filter') {
1417
+ <ndt-filter-icon [fill]="fill()" />
1275
1418
  } @case ('gauge') {
1276
1419
  <ndt-gauge-icon [fill]="fill()" />
1277
1420
  } @case ('gear') {
@@ -1286,6 +1429,8 @@ class DevToolbarIconComponent {
1286
1429
  <ndt-lighting-icon [fill]="fill()" />
1287
1430
  } @case ('lightbulb') {
1288
1431
  <ndt-lightbulb-icon [fill]="fill()" />
1432
+ } @case ('lock') {
1433
+ <ndt-lock-icon [fill]="fill()" />
1289
1434
  } @case ('network') {
1290
1435
  <ndt-network-icon [fill]="fill()" />
1291
1436
  } @case ('puzzle') {
@@ -1311,7 +1456,7 @@ class DevToolbarIconComponent {
1311
1456
  } @case ('trash') {
1312
1457
  <ndt-trash-icon [fill]="fill()" />
1313
1458
  } }
1314
- `, isInline: true, dependencies: [{ kind: "component", type: AngularIconComponent, selector: "ndt-angular-icon" }, { kind: "component", type: BugIconComponent, selector: "ndt-bug-icon", inputs: ["fill"] }, { kind: "component", type: CodeIconComponent, selector: "ndt-code-icon", inputs: ["fill"] }, { kind: "component", type: DatabaseIconComponent, selector: "ndt-database-icon", inputs: ["fill"] }, { kind: "component", type: DocsIconComponent, selector: "ndt-docs-icon", inputs: ["fill"] }, { kind: "component", type: DiscordIconComponent, selector: "ndt-discord-icon", inputs: ["fill"] }, { kind: "component", type: ExportIconComponent, selector: "ndt-export-icon", inputs: ["fill"] }, { kind: "component", type: GaugeIconComponent, selector: "ndt-gauge-icon", inputs: ["fill"] }, { kind: "component", type: GearIconComponent, selector: "ndt-gear-icon", inputs: ["fill"] }, { kind: "component", type: GitBranchIconComponent, selector: "ndt-git-branch-icon", inputs: ["fill"] }, { kind: "component", type: ImportIconComponent, selector: "ndt-import-icon", inputs: ["fill"] }, { kind: "component", type: LayoutIconComponent, selector: "ndt-layout-icon", inputs: ["fill"] }, { kind: "component", type: LightbulbIconComponent, selector: "ndt-lightbulb-icon", inputs: ["fill"] }, { kind: "component", type: LightingIconComponent, selector: "ndt-lighting-icon", inputs: ["fill"] }, { kind: "component", type: NetworkIconComponent, selector: "ndt-network-icon", inputs: ["fill"] }, { kind: "component", type: PuzzleIconComponent, selector: "ndt-puzzle-icon", inputs: ["fill"] }, { kind: "component", type: RefreshIconComponent, selector: "ndt-refresh-icon", inputs: ["fill"] }, { kind: "component", type: StarIconComponent, selector: "ndt-star-icon", inputs: ["fill"] }, { kind: "component", type: TerminalIconComponent, selector: "ndt-terminal-icon", inputs: ["fill"] }, { kind: "component", type: ToggleLeftIconComponent, selector: "ndt-toggle-left-icon", inputs: ["fill"] }, { kind: "component", type: UsersIconComponent, selector: "ndt-users-icon", inputs: ["fill"] }, { kind: "component", type: SunIconComponent, selector: "ndt-sun-icon", inputs: ["fill"] }, { kind: "component", type: MoonIconComponent, selector: "ndt-moon-icon", inputs: ["fill"] }, { kind: "component", type: TranslateIconComponent, selector: "ndt-translate-icon", inputs: ["fill"] }, { kind: "component", type: TrashIconComponent, selector: "ndt-trash-icon", inputs: ["fill"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1459
+ `, isInline: true, dependencies: [{ kind: "component", type: AngularIconComponent, selector: "ndt-angular-icon" }, { kind: "component", type: BoltIconComponent, selector: "ndt-bolt-icon", inputs: ["fill"] }, { kind: "component", type: BugIconComponent, selector: "ndt-bug-icon", inputs: ["fill"] }, { kind: "component", type: CodeIconComponent, selector: "ndt-code-icon", inputs: ["fill"] }, { kind: "component", type: DatabaseIconComponent, selector: "ndt-database-icon", inputs: ["fill"] }, { kind: "component", type: DocsIconComponent, selector: "ndt-docs-icon", inputs: ["fill"] }, { kind: "component", type: DiscordIconComponent, selector: "ndt-discord-icon", inputs: ["fill"] }, { kind: "component", type: ExportIconComponent, selector: "ndt-export-icon", inputs: ["fill"] }, { kind: "component", type: FilterIconComponent, selector: "ndt-filter-icon", inputs: ["fill"] }, { kind: "component", type: GaugeIconComponent, selector: "ndt-gauge-icon", inputs: ["fill"] }, { kind: "component", type: GearIconComponent, selector: "ndt-gear-icon", inputs: ["fill"] }, { kind: "component", type: GitBranchIconComponent, selector: "ndt-git-branch-icon", inputs: ["fill"] }, { kind: "component", type: ImportIconComponent, selector: "ndt-import-icon", inputs: ["fill"] }, { kind: "component", type: LayoutIconComponent, selector: "ndt-layout-icon", inputs: ["fill"] }, { kind: "component", type: LightbulbIconComponent, selector: "ndt-lightbulb-icon", inputs: ["fill"] }, { kind: "component", type: LightingIconComponent, selector: "ndt-lighting-icon", inputs: ["fill"] }, { kind: "component", type: LockIconComponent, selector: "ndt-lock-icon", inputs: ["fill"] }, { kind: "component", type: NetworkIconComponent, selector: "ndt-network-icon", inputs: ["fill"] }, { kind: "component", type: PuzzleIconComponent, selector: "ndt-puzzle-icon", inputs: ["fill"] }, { kind: "component", type: RefreshIconComponent, selector: "ndt-refresh-icon", inputs: ["fill"] }, { kind: "component", type: StarIconComponent, selector: "ndt-star-icon", inputs: ["fill"] }, { kind: "component", type: TerminalIconComponent, selector: "ndt-terminal-icon", inputs: ["fill"] }, { kind: "component", type: ToggleLeftIconComponent, selector: "ndt-toggle-left-icon", inputs: ["fill"] }, { kind: "component", type: UsersIconComponent, selector: "ndt-users-icon", inputs: ["fill"] }, { kind: "component", type: SunIconComponent, selector: "ndt-sun-icon", inputs: ["fill"] }, { kind: "component", type: MoonIconComponent, selector: "ndt-moon-icon", inputs: ["fill"] }, { kind: "component", type: TranslateIconComponent, selector: "ndt-translate-icon", inputs: ["fill"] }, { kind: "component", type: TrashIconComponent, selector: "ndt-trash-icon", inputs: ["fill"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1315
1460
  }
1316
1461
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DevToolbarIconComponent, decorators: [{
1317
1462
  type: Component,
@@ -1320,12 +1465,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImpor
1320
1465
  standalone: true,
1321
1466
  imports: [
1322
1467
  AngularIconComponent,
1468
+ BoltIconComponent,
1323
1469
  BugIconComponent,
1324
1470
  CodeIconComponent,
1325
1471
  DatabaseIconComponent,
1326
1472
  DocsIconComponent,
1327
1473
  DiscordIconComponent,
1328
1474
  ExportIconComponent,
1475
+ FilterIconComponent,
1329
1476
  GaugeIconComponent,
1330
1477
  GearIconComponent,
1331
1478
  GitBranchIconComponent,
@@ -1333,6 +1480,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImpor
1333
1480
  LayoutIconComponent,
1334
1481
  LightbulbIconComponent,
1335
1482
  LightingIconComponent,
1483
+ LockIconComponent,
1336
1484
  NetworkIconComponent,
1337
1485
  PuzzleIconComponent,
1338
1486
  RefreshIconComponent,
@@ -1349,6 +1497,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImpor
1349
1497
  template: `
1350
1498
  @switch (name()) { @case ('angular') {
1351
1499
  <ndt-angular-icon />
1500
+ } @case ('bolt') {
1501
+ <ndt-bolt-icon [fill]="fill()" />
1352
1502
  } @case ('bug') {
1353
1503
  <ndt-bug-icon [fill]="fill()" />
1354
1504
  } @case ('code') {
@@ -1359,6 +1509,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImpor
1359
1509
  <ndt-docs-icon [fill]="fill()" />
1360
1510
  } @case ('export') {
1361
1511
  <ndt-export-icon [fill]="fill()" />
1512
+ } @case ('filter') {
1513
+ <ndt-filter-icon [fill]="fill()" />
1362
1514
  } @case ('gauge') {
1363
1515
  <ndt-gauge-icon [fill]="fill()" />
1364
1516
  } @case ('gear') {
@@ -1373,6 +1525,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImpor
1373
1525
  <ndt-lighting-icon [fill]="fill()" />
1374
1526
  } @case ('lightbulb') {
1375
1527
  <ndt-lightbulb-icon [fill]="fill()" />
1528
+ } @case ('lock') {
1529
+ <ndt-lock-icon [fill]="fill()" />
1376
1530
  } @case ('network') {
1377
1531
  <ndt-network-icon [fill]="fill()" />
1378
1532
  } @case ('puzzle') {
@@ -1575,7 +1729,7 @@ class DevToolbarWindowComponent {
1575
1729
  <ng-content></ng-content>
1576
1730
  </div>
1577
1731
  </div>
1578
- `, isInline: true, styles: [".dev-toolbar{--ndt-border-radius-small: 4px;--ndt-border-radius-medium: 8px;--ndt-border-radius-large: 12px;--ndt-transition-default: all .2s ease-out;--ndt-transition-smooth: all .2s ease-in-out;--ndt-bg-primary: rgb(255, 255, 255);--ndt-bg-gradient: linear-gradient(180deg, rgb(243, 244, 246) 0%, rgba(243, 244, 246, .88) 100%);--ndt-text-primary: rgb(17, 24, 39);--ndt-text-secondary: rgb(55, 65, 81);--ndt-text-muted: rgb(107, 114, 128);--ndt-border-primary: #e5e7eb;--ndt-border-subtle: rgba(17, 24, 39, .1);--ndt-hover-bg: rgba(17, 24, 39, .05);--ndt-hover-danger: rgb(239, 68, 68);--ndt-shadow-toolbar: 0 2px 8px rgba(156, 163, 175, .2);--ndt-shadow-tooltip: 0 0 0 1px rgba(17, 24, 39, .05), 0 4px 8px rgba(107, 114, 128, .15), 0 2px 4px rgba(107, 114, 128, .1);--ndt-shadow-window: 0px 0px 0px 0px rgba(156, 163, 175, .1), 0px 1px 2px 0px rgba(156, 163, 175, .12), 0px 4px 4px 0px rgba(156, 163, 175, .1), 0px 10px 6px 0px rgba(156, 163, 175, .08), 0px 17px 7px 0px rgba(156, 163, 175, .05), 0px 26px 7px 0px rgba(156, 163, 175, .02);--ndt-spacing-xs: 4px;--ndt-spacing-sm: 8px;--ndt-spacing-md: 16px;--ndt-window-padding: 24px;--ndt-font-size-xs: .75rem;--ndt-font-size-sm: .875rem;--ndt-font-size-md: 1rem;--ndt-font-size-lg: 1.25rem;--ndt-font-size-xl: 2rem;--ndt-background-secondary: var(--ndt-bg-primary);--ndt-background-hover: var(--ndt-hover-bg);--ndt-primary: #df30d4;--ndt-primary-rgb: 223, 48, 212;--ndt-text-on-primary: rgb(255, 255, 255);--ndt-border-color: var(--ndt-border-primary);--ndt-note-background: rgb(219, 234, 254);--ndt-note-border: rgba(37, 99, 235, .2);--ndt-warning-background: rgb(254, 249, 195);--ndt-warning-border: rgba(202, 138, 4, .2);--ndt-error-background: rgb(254, 226, 226);--ndt-error-border: rgba(220, 38, 38, .2)}.dev-toolbar[data-theme=dark]{--ndt-bg-primary: rgb(17, 24, 39);--ndt-bg-gradient: linear-gradient(180deg, rgb(19, 21, 26) 0%, rgba(19, 21, 26, .88) 100%);--ndt-text-primary: rgb(255, 255, 255);--ndt-text-secondary: rgb(229, 231, 235);--ndt-text-muted: rgb(156, 163, 175);--ndt-border-primary: #343841;--ndt-border-subtle: rgba(255, 255, 255, .1);--ndt-hover-bg: rgba(255, 255, 255, .12);--ndt-hover-danger: rgb(220, 38, 38);--ndt-shadow-toolbar: 0 2px 8px rgba(19, 21, 26, .3);--ndt-shadow-tooltip: 0 0 0 1px rgba(255, 255, 255, .1), 0 4px 8px rgba(0, 0, 0, .4), 0 2px 4px rgba(0, 0, 0, .3);--ndt-shadow-window: 0px 0px 0px 0px rgba(19, 21, 26, .3), 0px 1px 2px 0px rgba(19, 21, 26, .29), 0px 4px 4px 0px rgba(19, 21, 26, .26), 0px 10px 6px 0px rgba(19, 21, 26, .15), 0px 17px 7px 0px rgba(19, 21, 26, .04), 0px 26px 7px 0px rgba(19, 21, 26, .01);--ndt-note-background: rgba(37, 99, 235, .15);--ndt-note-border: rgba(37, 99, 235, .3);--ndt-warning-background: rgba(202, 138, 4, .15);--ndt-warning-border: rgba(202, 138, 4, .3);--ndt-error-background: rgba(220, 38, 38, .15);--ndt-error-border: rgba(220, 38, 38, .3)}.dev-toolbar h1,.dev-toolbar h2,.dev-toolbar h3,.dev-toolbar h4,.dev-toolbar h5{font-weight:600;color:var(--ndt-text-primary);margin:0}.dev-toolbar h1{font-size:var(--ndt-font-size-xl)}.dev-toolbar h2{font-size:var(--ndt-font-size-lg)}.dev-toolbar h3{font-size:var(--ndt-font-size-md)}.dev-toolbar h4{font-size:var(--ndt-font-size-sm)}.dev-toolbar h5{font-size:var(--ndt-font-size-xs)}.dev-toolbar hr{border:1px solid var(--ndt-border-subtle);margin:1em 0}.dev-toolbar p{line-height:1.5em;margin:0}:host{display:block;width:100%}.window{box-sizing:border-box;display:flex;flex-direction:column;width:100%;height:100%;background:var(--ndt-bg-primary);border:1px solid var(--ndt-border-primary);border-radius:var(--ndt-border-radius-large);padding:var(--ndt-window-padding);font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",Segoe UI Symbol,\"Noto Color Emoji\";color:var(--ndt-text-secondary);z-index:999999999;box-shadow:var(--ndt-shadow-window)}.header{display:flex;flex-direction:row;justify-content:space-between;align-items:flex-start}.header__title{display:flex;align-items:center;gap:var(--ndt-spacing-sm)}.header__title .beta-tag{font-size:var(--ndt-font-size-xxs, .65rem);background:var(--ndt-purple, #8b5cf6);color:var(--ndt-text-on-primary);padding:1px 4px;margin-left:var(--ndt-spacing-xs);border-radius:var(--ndt-border-radius-small);font-weight:500;text-transform:uppercase;letter-spacing:.5px}.header__description{font-size:var(--ndt-font-size-sm);color:var(--ndt-text-muted)}.header__content{display:flex;flex-direction:column}.header__controls{display:flex;gap:var(--ndt-spacing-sm)}.content{flex:1;overflow:auto}.divider{height:1px;background-color:var(--ndt-border-primary);margin-bottom:var(--ndt-spacing-md);margin-top:var(--ndt-spacing-md)}.control{background:none;border:none;color:var(--ndt-text-secondary);cursor:pointer;padding:var(--ndt-spacing-xs) var(--ndt-spacing-sm);border-radius:var(--ndt-border-radius-small);font-size:var(--ndt-font-size-md);line-height:1;transition:var(--ndt-transition-smooth)}.control:hover{background:var(--ndt-hover-bg);color:var(--ndt-text-primary)}.control--close:hover{background:var(--ndt-hover-danger)}\n"] }); }
1732
+ `, isInline: true, styles: [".dev-toolbar{--ndt-border-radius-small: 4px;--ndt-border-radius-medium: 8px;--ndt-border-radius-large: 12px;--ndt-transition-default: all .2s ease-out;--ndt-transition-smooth: all .2s ease-in-out;--ndt-bg-primary: rgb(255, 255, 255);--ndt-bg-gradient: linear-gradient(180deg, rgb(243, 244, 246) 0%, rgba(243, 244, 246, .88) 100%);--ndt-text-primary: rgb(17, 24, 39);--ndt-text-secondary: rgb(55, 65, 81);--ndt-text-muted: rgb(107, 114, 128);--ndt-border-primary: #e5e7eb;--ndt-border-subtle: rgba(17, 24, 39, .1);--ndt-hover-bg: rgba(17, 24, 39, .05);--ndt-hover-danger: rgb(239, 68, 68);--ndt-shadow-toolbar: 0 2px 8px rgba(156, 163, 175, .2);--ndt-shadow-tooltip: 0 0 0 1px rgba(17, 24, 39, .05), 0 4px 8px rgba(107, 114, 128, .15), 0 2px 4px rgba(107, 114, 128, .1);--ndt-shadow-window: 0px 0px 0px 0px rgba(156, 163, 175, .1), 0px 1px 2px 0px rgba(156, 163, 175, .12), 0px 4px 4px 0px rgba(156, 163, 175, .1), 0px 10px 6px 0px rgba(156, 163, 175, .08), 0px 17px 7px 0px rgba(156, 163, 175, .05), 0px 26px 7px 0px rgba(156, 163, 175, .02);--ndt-spacing-xs: 4px;--ndt-spacing-sm: 8px;--ndt-spacing-md: 16px;--ndt-window-padding: 24px;--ndt-font-size-xs: .75rem;--ndt-font-size-sm: .875rem;--ndt-font-size-md: 1rem;--ndt-font-size-lg: 1.25rem;--ndt-font-size-xl: 2rem;--ndt-background-secondary: var(--ndt-bg-primary);--ndt-background-hover: var(--ndt-hover-bg);--ndt-primary: #df30d4;--ndt-primary-rgb: 223, 48, 212;--ndt-text-on-primary: rgb(255, 255, 255);--ndt-border-color: var(--ndt-border-primary);--ndt-note-background: rgb(219, 234, 254);--ndt-note-border: rgba(37, 99, 235, .2);--ndt-warning-background: rgb(254, 249, 195);--ndt-warning-border: rgba(202, 138, 4, .2);--ndt-error-background: rgb(254, 226, 226);--ndt-error-border: rgba(220, 38, 38, .2)}.dev-toolbar[data-theme=dark]{--ndt-bg-primary: rgb(17, 24, 39);--ndt-bg-gradient: linear-gradient(180deg, rgb(19, 21, 26) 0%, rgba(19, 21, 26, .88) 100%);--ndt-text-primary: rgb(255, 255, 255);--ndt-text-secondary: rgb(229, 231, 235);--ndt-text-muted: rgb(156, 163, 175);--ndt-border-primary: #343841;--ndt-border-subtle: rgba(255, 255, 255, .1);--ndt-hover-bg: rgba(255, 255, 255, .12);--ndt-hover-danger: rgb(220, 38, 38);--ndt-shadow-toolbar: 0 2px 8px rgba(19, 21, 26, .3);--ndt-shadow-tooltip: 0 0 0 1px rgba(255, 255, 255, .1), 0 4px 8px rgba(0, 0, 0, .4), 0 2px 4px rgba(0, 0, 0, .3);--ndt-shadow-window: 0px 0px 0px 0px rgba(19, 21, 26, .3), 0px 1px 2px 0px rgba(19, 21, 26, .29), 0px 4px 4px 0px rgba(19, 21, 26, .26), 0px 10px 6px 0px rgba(19, 21, 26, .15), 0px 17px 7px 0px rgba(19, 21, 26, .04), 0px 26px 7px 0px rgba(19, 21, 26, .01);--ndt-note-background: rgba(37, 99, 235, .15);--ndt-note-border: rgba(37, 99, 235, .3);--ndt-warning-background: rgba(202, 138, 4, .15);--ndt-warning-border: rgba(202, 138, 4, .3);--ndt-error-background: rgba(220, 38, 38, .15);--ndt-error-border: rgba(220, 38, 38, .3)}.dev-toolbar h1,.dev-toolbar h2,.dev-toolbar h3,.dev-toolbar h4,.dev-toolbar h5{font-weight:600;color:var(--ndt-text-primary);margin:0}.dev-toolbar h1{font-size:var(--ndt-font-size-xl)}.dev-toolbar h2{font-size:var(--ndt-font-size-lg)}.dev-toolbar h3{font-size:var(--ndt-font-size-md)}.dev-toolbar h4{font-size:var(--ndt-font-size-sm)}.dev-toolbar h5{font-size:var(--ndt-font-size-xs)}.dev-toolbar hr{border:1px solid var(--ndt-border-subtle);margin:1em 0}.dev-toolbar p{line-height:1.5em;margin:0}:host{display:block;width:100%}.window{box-sizing:border-box;display:flex;flex-direction:column;width:100%;height:100%;background:var(--ndt-bg-primary);border:1px solid var(--ndt-border-primary);border-radius:var(--ndt-border-radius-large);padding:var(--ndt-window-padding);font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",Segoe UI Symbol,\"Noto Color Emoji\";color:var(--ndt-text-secondary);z-index:999999999;box-shadow:var(--ndt-shadow-window)}.header{display:flex;flex-direction:row;justify-content:space-between;align-items:flex-start}.header__title{display:flex;align-items:center;gap:var(--ndt-spacing-sm)}.header__title .beta-tag{font-size:var(--ndt-font-size-xxs, .65rem);background:var(--ndt-purple, #8b5cf6);color:var(--ndt-text-on-primary);padding:1px 4px;margin-left:var(--ndt-spacing-xs);border-radius:var(--ndt-border-radius-small);font-weight:500;text-transform:uppercase;letter-spacing:.5px}.header__description{font-size:var(--ndt-font-size-sm);color:var(--ndt-text-muted);word-wrap:break-word;overflow-wrap:break-word;max-width:100%}.header__content{display:flex;flex-direction:column}.header__controls{display:flex;gap:var(--ndt-spacing-sm)}.content{flex:1;overflow:auto}.divider{height:1px;background-color:var(--ndt-border-primary);margin-bottom:var(--ndt-spacing-md);margin-top:var(--ndt-spacing-md)}.control{background:none;border:none;color:var(--ndt-text-secondary);cursor:pointer;padding:var(--ndt-spacing-xs) var(--ndt-spacing-sm);border-radius:var(--ndt-border-radius-small);font-size:var(--ndt-font-size-md);line-height:1;transition:var(--ndt-transition-smooth)}.control:hover{background:var(--ndt-hover-bg);color:var(--ndt-text-primary)}.control--close:hover{background:var(--ndt-hover-danger)}\n"] }); }
1579
1733
  }
1580
1734
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DevToolbarWindowComponent, decorators: [{
1581
1735
  type: Component,
@@ -1617,7 +1771,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImpor
1617
1771
  <ng-content></ng-content>
1618
1772
  </div>
1619
1773
  </div>
1620
- `, styles: [".dev-toolbar{--ndt-border-radius-small: 4px;--ndt-border-radius-medium: 8px;--ndt-border-radius-large: 12px;--ndt-transition-default: all .2s ease-out;--ndt-transition-smooth: all .2s ease-in-out;--ndt-bg-primary: rgb(255, 255, 255);--ndt-bg-gradient: linear-gradient(180deg, rgb(243, 244, 246) 0%, rgba(243, 244, 246, .88) 100%);--ndt-text-primary: rgb(17, 24, 39);--ndt-text-secondary: rgb(55, 65, 81);--ndt-text-muted: rgb(107, 114, 128);--ndt-border-primary: #e5e7eb;--ndt-border-subtle: rgba(17, 24, 39, .1);--ndt-hover-bg: rgba(17, 24, 39, .05);--ndt-hover-danger: rgb(239, 68, 68);--ndt-shadow-toolbar: 0 2px 8px rgba(156, 163, 175, .2);--ndt-shadow-tooltip: 0 0 0 1px rgba(17, 24, 39, .05), 0 4px 8px rgba(107, 114, 128, .15), 0 2px 4px rgba(107, 114, 128, .1);--ndt-shadow-window: 0px 0px 0px 0px rgba(156, 163, 175, .1), 0px 1px 2px 0px rgba(156, 163, 175, .12), 0px 4px 4px 0px rgba(156, 163, 175, .1), 0px 10px 6px 0px rgba(156, 163, 175, .08), 0px 17px 7px 0px rgba(156, 163, 175, .05), 0px 26px 7px 0px rgba(156, 163, 175, .02);--ndt-spacing-xs: 4px;--ndt-spacing-sm: 8px;--ndt-spacing-md: 16px;--ndt-window-padding: 24px;--ndt-font-size-xs: .75rem;--ndt-font-size-sm: .875rem;--ndt-font-size-md: 1rem;--ndt-font-size-lg: 1.25rem;--ndt-font-size-xl: 2rem;--ndt-background-secondary: var(--ndt-bg-primary);--ndt-background-hover: var(--ndt-hover-bg);--ndt-primary: #df30d4;--ndt-primary-rgb: 223, 48, 212;--ndt-text-on-primary: rgb(255, 255, 255);--ndt-border-color: var(--ndt-border-primary);--ndt-note-background: rgb(219, 234, 254);--ndt-note-border: rgba(37, 99, 235, .2);--ndt-warning-background: rgb(254, 249, 195);--ndt-warning-border: rgba(202, 138, 4, .2);--ndt-error-background: rgb(254, 226, 226);--ndt-error-border: rgba(220, 38, 38, .2)}.dev-toolbar[data-theme=dark]{--ndt-bg-primary: rgb(17, 24, 39);--ndt-bg-gradient: linear-gradient(180deg, rgb(19, 21, 26) 0%, rgba(19, 21, 26, .88) 100%);--ndt-text-primary: rgb(255, 255, 255);--ndt-text-secondary: rgb(229, 231, 235);--ndt-text-muted: rgb(156, 163, 175);--ndt-border-primary: #343841;--ndt-border-subtle: rgba(255, 255, 255, .1);--ndt-hover-bg: rgba(255, 255, 255, .12);--ndt-hover-danger: rgb(220, 38, 38);--ndt-shadow-toolbar: 0 2px 8px rgba(19, 21, 26, .3);--ndt-shadow-tooltip: 0 0 0 1px rgba(255, 255, 255, .1), 0 4px 8px rgba(0, 0, 0, .4), 0 2px 4px rgba(0, 0, 0, .3);--ndt-shadow-window: 0px 0px 0px 0px rgba(19, 21, 26, .3), 0px 1px 2px 0px rgba(19, 21, 26, .29), 0px 4px 4px 0px rgba(19, 21, 26, .26), 0px 10px 6px 0px rgba(19, 21, 26, .15), 0px 17px 7px 0px rgba(19, 21, 26, .04), 0px 26px 7px 0px rgba(19, 21, 26, .01);--ndt-note-background: rgba(37, 99, 235, .15);--ndt-note-border: rgba(37, 99, 235, .3);--ndt-warning-background: rgba(202, 138, 4, .15);--ndt-warning-border: rgba(202, 138, 4, .3);--ndt-error-background: rgba(220, 38, 38, .15);--ndt-error-border: rgba(220, 38, 38, .3)}.dev-toolbar h1,.dev-toolbar h2,.dev-toolbar h3,.dev-toolbar h4,.dev-toolbar h5{font-weight:600;color:var(--ndt-text-primary);margin:0}.dev-toolbar h1{font-size:var(--ndt-font-size-xl)}.dev-toolbar h2{font-size:var(--ndt-font-size-lg)}.dev-toolbar h3{font-size:var(--ndt-font-size-md)}.dev-toolbar h4{font-size:var(--ndt-font-size-sm)}.dev-toolbar h5{font-size:var(--ndt-font-size-xs)}.dev-toolbar hr{border:1px solid var(--ndt-border-subtle);margin:1em 0}.dev-toolbar p{line-height:1.5em;margin:0}:host{display:block;width:100%}.window{box-sizing:border-box;display:flex;flex-direction:column;width:100%;height:100%;background:var(--ndt-bg-primary);border:1px solid var(--ndt-border-primary);border-radius:var(--ndt-border-radius-large);padding:var(--ndt-window-padding);font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",Segoe UI Symbol,\"Noto Color Emoji\";color:var(--ndt-text-secondary);z-index:999999999;box-shadow:var(--ndt-shadow-window)}.header{display:flex;flex-direction:row;justify-content:space-between;align-items:flex-start}.header__title{display:flex;align-items:center;gap:var(--ndt-spacing-sm)}.header__title .beta-tag{font-size:var(--ndt-font-size-xxs, .65rem);background:var(--ndt-purple, #8b5cf6);color:var(--ndt-text-on-primary);padding:1px 4px;margin-left:var(--ndt-spacing-xs);border-radius:var(--ndt-border-radius-small);font-weight:500;text-transform:uppercase;letter-spacing:.5px}.header__description{font-size:var(--ndt-font-size-sm);color:var(--ndt-text-muted)}.header__content{display:flex;flex-direction:column}.header__controls{display:flex;gap:var(--ndt-spacing-sm)}.content{flex:1;overflow:auto}.divider{height:1px;background-color:var(--ndt-border-primary);margin-bottom:var(--ndt-spacing-md);margin-top:var(--ndt-spacing-md)}.control{background:none;border:none;color:var(--ndt-text-secondary);cursor:pointer;padding:var(--ndt-spacing-xs) var(--ndt-spacing-sm);border-radius:var(--ndt-border-radius-small);font-size:var(--ndt-font-size-md);line-height:1;transition:var(--ndt-transition-smooth)}.control:hover{background:var(--ndt-hover-bg);color:var(--ndt-text-primary)}.control--close:hover{background:var(--ndt-hover-danger)}\n"] }]
1774
+ `, styles: [".dev-toolbar{--ndt-border-radius-small: 4px;--ndt-border-radius-medium: 8px;--ndt-border-radius-large: 12px;--ndt-transition-default: all .2s ease-out;--ndt-transition-smooth: all .2s ease-in-out;--ndt-bg-primary: rgb(255, 255, 255);--ndt-bg-gradient: linear-gradient(180deg, rgb(243, 244, 246) 0%, rgba(243, 244, 246, .88) 100%);--ndt-text-primary: rgb(17, 24, 39);--ndt-text-secondary: rgb(55, 65, 81);--ndt-text-muted: rgb(107, 114, 128);--ndt-border-primary: #e5e7eb;--ndt-border-subtle: rgba(17, 24, 39, .1);--ndt-hover-bg: rgba(17, 24, 39, .05);--ndt-hover-danger: rgb(239, 68, 68);--ndt-shadow-toolbar: 0 2px 8px rgba(156, 163, 175, .2);--ndt-shadow-tooltip: 0 0 0 1px rgba(17, 24, 39, .05), 0 4px 8px rgba(107, 114, 128, .15), 0 2px 4px rgba(107, 114, 128, .1);--ndt-shadow-window: 0px 0px 0px 0px rgba(156, 163, 175, .1), 0px 1px 2px 0px rgba(156, 163, 175, .12), 0px 4px 4px 0px rgba(156, 163, 175, .1), 0px 10px 6px 0px rgba(156, 163, 175, .08), 0px 17px 7px 0px rgba(156, 163, 175, .05), 0px 26px 7px 0px rgba(156, 163, 175, .02);--ndt-spacing-xs: 4px;--ndt-spacing-sm: 8px;--ndt-spacing-md: 16px;--ndt-window-padding: 24px;--ndt-font-size-xs: .75rem;--ndt-font-size-sm: .875rem;--ndt-font-size-md: 1rem;--ndt-font-size-lg: 1.25rem;--ndt-font-size-xl: 2rem;--ndt-background-secondary: var(--ndt-bg-primary);--ndt-background-hover: var(--ndt-hover-bg);--ndt-primary: #df30d4;--ndt-primary-rgb: 223, 48, 212;--ndt-text-on-primary: rgb(255, 255, 255);--ndt-border-color: var(--ndt-border-primary);--ndt-note-background: rgb(219, 234, 254);--ndt-note-border: rgba(37, 99, 235, .2);--ndt-warning-background: rgb(254, 249, 195);--ndt-warning-border: rgba(202, 138, 4, .2);--ndt-error-background: rgb(254, 226, 226);--ndt-error-border: rgba(220, 38, 38, .2)}.dev-toolbar[data-theme=dark]{--ndt-bg-primary: rgb(17, 24, 39);--ndt-bg-gradient: linear-gradient(180deg, rgb(19, 21, 26) 0%, rgba(19, 21, 26, .88) 100%);--ndt-text-primary: rgb(255, 255, 255);--ndt-text-secondary: rgb(229, 231, 235);--ndt-text-muted: rgb(156, 163, 175);--ndt-border-primary: #343841;--ndt-border-subtle: rgba(255, 255, 255, .1);--ndt-hover-bg: rgba(255, 255, 255, .12);--ndt-hover-danger: rgb(220, 38, 38);--ndt-shadow-toolbar: 0 2px 8px rgba(19, 21, 26, .3);--ndt-shadow-tooltip: 0 0 0 1px rgba(255, 255, 255, .1), 0 4px 8px rgba(0, 0, 0, .4), 0 2px 4px rgba(0, 0, 0, .3);--ndt-shadow-window: 0px 0px 0px 0px rgba(19, 21, 26, .3), 0px 1px 2px 0px rgba(19, 21, 26, .29), 0px 4px 4px 0px rgba(19, 21, 26, .26), 0px 10px 6px 0px rgba(19, 21, 26, .15), 0px 17px 7px 0px rgba(19, 21, 26, .04), 0px 26px 7px 0px rgba(19, 21, 26, .01);--ndt-note-background: rgba(37, 99, 235, .15);--ndt-note-border: rgba(37, 99, 235, .3);--ndt-warning-background: rgba(202, 138, 4, .15);--ndt-warning-border: rgba(202, 138, 4, .3);--ndt-error-background: rgba(220, 38, 38, .15);--ndt-error-border: rgba(220, 38, 38, .3)}.dev-toolbar h1,.dev-toolbar h2,.dev-toolbar h3,.dev-toolbar h4,.dev-toolbar h5{font-weight:600;color:var(--ndt-text-primary);margin:0}.dev-toolbar h1{font-size:var(--ndt-font-size-xl)}.dev-toolbar h2{font-size:var(--ndt-font-size-lg)}.dev-toolbar h3{font-size:var(--ndt-font-size-md)}.dev-toolbar h4{font-size:var(--ndt-font-size-sm)}.dev-toolbar h5{font-size:var(--ndt-font-size-xs)}.dev-toolbar hr{border:1px solid var(--ndt-border-subtle);margin:1em 0}.dev-toolbar p{line-height:1.5em;margin:0}:host{display:block;width:100%}.window{box-sizing:border-box;display:flex;flex-direction:column;width:100%;height:100%;background:var(--ndt-bg-primary);border:1px solid var(--ndt-border-primary);border-radius:var(--ndt-border-radius-large);padding:var(--ndt-window-padding);font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",Segoe UI Symbol,\"Noto Color Emoji\";color:var(--ndt-text-secondary);z-index:999999999;box-shadow:var(--ndt-shadow-window)}.header{display:flex;flex-direction:row;justify-content:space-between;align-items:flex-start}.header__title{display:flex;align-items:center;gap:var(--ndt-spacing-sm)}.header__title .beta-tag{font-size:var(--ndt-font-size-xxs, .65rem);background:var(--ndt-purple, #8b5cf6);color:var(--ndt-text-on-primary);padding:1px 4px;margin-left:var(--ndt-spacing-xs);border-radius:var(--ndt-border-radius-small);font-weight:500;text-transform:uppercase;letter-spacing:.5px}.header__description{font-size:var(--ndt-font-size-sm);color:var(--ndt-text-muted);word-wrap:break-word;overflow-wrap:break-word;max-width:100%}.header__content{display:flex;flex-direction:column}.header__controls{display:flex;gap:var(--ndt-spacing-sm)}.content{flex:1;overflow:auto}.divider{height:1px;background-color:var(--ndt-border-primary);margin-bottom:var(--ndt-spacing-md);margin-top:var(--ndt-spacing-md)}.control{background:none;border:none;color:var(--ndt-text-secondary);cursor:pointer;padding:var(--ndt-spacing-xs) var(--ndt-spacing-sm);border-radius:var(--ndt-border-radius-small);font-size:var(--ndt-font-size-md);line-height:1;transition:var(--ndt-transition-smooth)}.control:hover{background:var(--ndt-hover-bg);color:var(--ndt-text-primary)}.control--close:hover{background:var(--ndt-hover-danger)}\n"] }]
1621
1775
  }] });
1622
1776
 
1623
1777
  class DevToolbarToolComponent {
@@ -2076,172 +2230,641 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImpor
2076
2230
  args: [{ providedIn: 'root' }]
2077
2231
  }] });
2078
2232
 
2079
- class DevToolbarInternalFeatureFlagService {
2233
+ /**
2234
+ * Internal service for managing app features state and forced overrides.
2235
+ *
2236
+ * This service handles:
2237
+ * - Feature configuration storage
2238
+ * - Forced feature state management
2239
+ * - localStorage persistence
2240
+ * - State validation and cleanup
2241
+ *
2242
+ * @internal This service is for internal toolbar use only. Consumers should use DevToolbarAppFeaturesService.
2243
+ */
2244
+ class DevToolbarInternalAppFeaturesService {
2080
2245
  constructor() {
2081
- this.STORAGE_KEY = 'feature-flags';
2246
+ this.STORAGE_KEY = 'app-features';
2082
2247
  this.storageService = inject(DevToolsStorageService);
2083
- this.appFlags$ = new BehaviorSubject([]);
2084
- this.forcedFlagsSubject = new BehaviorSubject({
2248
+ this.appFeaturesSubject = new BehaviorSubject([]);
2249
+ this.forcedFeaturesSubject = new BehaviorSubject({
2085
2250
  enabled: [],
2086
2251
  disabled: [],
2087
2252
  });
2088
- this.forcedFlags$ = this.forcedFlagsSubject.asObservable();
2089
- this.flags$ = combineLatest([
2090
- this.appFlags$,
2091
- this.forcedFlags$,
2092
- ]).pipe(map(([appFlags, { enabled, disabled }]) => {
2093
- return appFlags.map((flag) => ({
2094
- ...flag,
2095
- isForced: enabled.includes(flag.id) || disabled.includes(flag.id),
2096
- isEnabled: enabled.includes(flag.id),
2097
- }));
2098
- }));
2099
- this.flags = toSignal(this.flags$, { initialValue: [] });
2100
- this.loadForcedFlags();
2253
+ this.forcedFeatures$ = this.forcedFeaturesSubject.asObservable();
2254
+ /**
2255
+ * Observable stream of all features with merged forced state
2256
+ */
2257
+ this.features$ = combineLatest([
2258
+ this.appFeaturesSubject.asObservable(),
2259
+ this.forcedFeatures$,
2260
+ ]).pipe(map(([appFeatures, forcedState]) => this.mergeForcedState(appFeatures, forcedState)));
2261
+ /**
2262
+ * Signal containing current features with merged forced state
2263
+ */
2264
+ this.features = toSignal(this.features$, { initialValue: [] });
2265
+ this.loadForcedFeatures();
2101
2266
  }
2102
- setAppFlags(flags) {
2103
- this.appFlags$.next(flags);
2267
+ /**
2268
+ * Set available app features for the application.
2269
+ * Validates features, trims whitespace, and triggers validation of forced state.
2270
+ *
2271
+ * @param features - Array of app features to configure
2272
+ * @throws Error if duplicate feature IDs or empty IDs are detected
2273
+ */
2274
+ setAppFeatures(features) {
2275
+ // Validate for empty IDs
2276
+ const emptyIdFeature = features.find((f) => !f.id || f.id.trim() === '');
2277
+ if (emptyIdFeature) {
2278
+ throw new Error('Feature ID cannot be empty');
2279
+ }
2280
+ // Validate for duplicate IDs
2281
+ const ids = features.map((f) => f.id);
2282
+ const duplicateIds = ids.filter((id, index) => ids.indexOf(id) !== index);
2283
+ if (duplicateIds.length > 0) {
2284
+ throw new Error(`Duplicate feature IDs detected: ${duplicateIds.join(', ')}`);
2285
+ }
2286
+ // Trim whitespace from names and warn about empty names
2287
+ const processedFeatures = features.map((feature) => {
2288
+ const trimmedName = feature.name.trim();
2289
+ if (!trimmedName) {
2290
+ console.warn(`Feature '${feature.id}' has empty name`);
2291
+ }
2292
+ return {
2293
+ ...feature,
2294
+ name: trimmedName || feature.name,
2295
+ };
2296
+ });
2297
+ this.appFeaturesSubject.next(processedFeatures);
2298
+ this.validateAndCleanForcedState();
2104
2299
  }
2105
- getAppFlags() {
2106
- return this.appFlags$.asObservable();
2300
+ /**
2301
+ * Get observable stream of app features (natural state, no forced state merged)
2302
+ */
2303
+ getAppFeatures() {
2304
+ return this.appFeaturesSubject.asObservable();
2107
2305
  }
2108
- getForcedFlags() {
2109
- return this.flags$.pipe(map((flags) => flags.filter((flag) => flag.isForced)));
2306
+ /**
2307
+ * Get observable stream of features that have forced overrides
2308
+ */
2309
+ getForcedFeatures() {
2310
+ return this.features$.pipe(map((features) => features.filter((feature) => feature.isForced)));
2110
2311
  }
2111
- setFlag(flagId, isEnabled) {
2112
- const { enabled, disabled } = this.forcedFlagsSubject.value;
2113
- const newEnabled = enabled.filter((id) => id !== flagId);
2114
- const newDisabled = disabled.filter((id) => id !== flagId);
2312
+ /**
2313
+ * Force a feature to enabled or disabled state.
2314
+ * Persists the forced state to localStorage.
2315
+ *
2316
+ * @param featureId - ID of the feature to force
2317
+ * @param isEnabled - Whether to force feature to enabled (true) or disabled (false)
2318
+ */
2319
+ setFeature(featureId, isEnabled) {
2320
+ const { enabled, disabled } = this.forcedFeaturesSubject.value;
2321
+ // Remove feature from both arrays
2322
+ const newEnabled = enabled.filter((id) => id !== featureId);
2323
+ const newDisabled = disabled.filter((id) => id !== featureId);
2324
+ // Add to appropriate array
2115
2325
  if (isEnabled) {
2116
- newEnabled.push(flagId);
2326
+ newEnabled.push(featureId);
2117
2327
  }
2118
2328
  else {
2119
- newDisabled.push(flagId);
2329
+ newDisabled.push(featureId);
2120
2330
  }
2121
2331
  const newState = { enabled: newEnabled, disabled: newDisabled };
2122
- this.forcedFlagsSubject.next(newState);
2123
- this.storageService.set(this.STORAGE_KEY, newState);
2332
+ this.forcedFeaturesSubject.next(newState);
2333
+ this.saveForcedFeatures(newState);
2124
2334
  }
2125
- removeFlagOverride(flagId) {
2126
- const { enabled, disabled } = this.forcedFlagsSubject.value;
2335
+ /**
2336
+ * Remove forced override for a feature, returning it to natural state.
2337
+ * Persists the change to localStorage.
2338
+ *
2339
+ * @param featureId - ID of the feature to unforce
2340
+ */
2341
+ removeFeatureOverride(featureId) {
2342
+ const { enabled, disabled } = this.forcedFeaturesSubject.value;
2127
2343
  const newState = {
2128
- enabled: enabled.filter((id) => id !== flagId),
2129
- disabled: disabled.filter((id) => id !== flagId),
2344
+ enabled: enabled.filter((id) => id !== featureId),
2345
+ disabled: disabled.filter((id) => id !== featureId),
2130
2346
  };
2131
- this.forcedFlagsSubject.next(newState);
2132
- this.storageService.set(this.STORAGE_KEY, newState);
2347
+ this.forcedFeaturesSubject.next(newState);
2348
+ this.saveForcedFeatures(newState);
2133
2349
  }
2134
- loadForcedFlags() {
2135
- const savedFlags = this.storageService.get(this.STORAGE_KEY);
2136
- if (savedFlags) {
2137
- this.forcedFlagsSubject.next(savedFlags);
2350
+ /**
2351
+ * Apply a preset forced state (for preset integration).
2352
+ * Validates and cleans invalid feature IDs before applying.
2353
+ *
2354
+ * @param state - Forced features state from preset
2355
+ */
2356
+ applyForcedState(state) {
2357
+ const configuredFeatureIds = this.appFeaturesSubject.value.map((f) => f.id);
2358
+ // Validate and filter to only valid IDs
2359
+ const validEnabled = state.enabled.filter((id) => configuredFeatureIds.includes(id));
2360
+ const validDisabled = state.disabled.filter((id) => configuredFeatureIds.includes(id));
2361
+ const invalidIds = [
2362
+ ...state.enabled.filter((id) => !configuredFeatureIds.includes(id)),
2363
+ ...state.disabled.filter((id) => !configuredFeatureIds.includes(id)),
2364
+ ];
2365
+ if (invalidIds.length > 0) {
2366
+ console.warn(`Preset contains invalid feature IDs (ignored): ${invalidIds.join(', ')}`);
2138
2367
  }
2368
+ const cleanedState = { enabled: validEnabled, disabled: validDisabled };
2369
+ this.forcedFeaturesSubject.next(cleanedState);
2370
+ this.saveForcedFeatures(cleanedState);
2139
2371
  }
2140
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DevToolbarInternalFeatureFlagService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2141
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DevToolbarInternalFeatureFlagService, providedIn: 'root' }); }
2372
+ /**
2373
+ * Get current forced state as a snapshot (defensive copy).
2374
+ * Useful for preset exports and debugging.
2375
+ *
2376
+ * @returns Current forced features state
2377
+ */
2378
+ getCurrentForcedState() {
2379
+ const state = this.forcedFeaturesSubject.value;
2380
+ return {
2381
+ enabled: [...state.enabled],
2382
+ disabled: [...state.disabled],
2383
+ };
2384
+ }
2385
+ /**
2386
+ * Merge natural app features with forced state.
2387
+ *
2388
+ * @param appFeatures - Natural feature configuration
2389
+ * @param forcedState - Forced overrides from localStorage/toolbar
2390
+ * @returns Features with merged forced state
2391
+ */
2392
+ mergeForcedState(appFeatures, forcedState) {
2393
+ return appFeatures.map((feature) => {
2394
+ const isInEnabled = forcedState.enabled.includes(feature.id);
2395
+ const isInDisabled = forcedState.disabled.includes(feature.id);
2396
+ const isForced = isInEnabled || isInDisabled;
2397
+ return {
2398
+ ...feature,
2399
+ isEnabled: isInEnabled ? true : isInDisabled ? false : feature.isEnabled,
2400
+ isForced,
2401
+ };
2402
+ });
2403
+ }
2404
+ /**
2405
+ * Load forced features state from localStorage on initialization.
2406
+ * Handles missing or corrupted data gracefully.
2407
+ */
2408
+ loadForcedFeatures() {
2409
+ try {
2410
+ const savedState = this.storageService.get(this.STORAGE_KEY);
2411
+ if (savedState) {
2412
+ this.forcedFeaturesSubject.next(savedState);
2413
+ }
2414
+ }
2415
+ catch (error) {
2416
+ console.error('Failed to load forced app features from localStorage:', error);
2417
+ // Use default empty state on error
2418
+ }
2419
+ }
2420
+ /**
2421
+ * Persist forced features state to localStorage.
2422
+ * Handles quota exceeded errors gracefully.
2423
+ *
2424
+ * @param state - Forced state to persist
2425
+ */
2426
+ saveForcedFeatures(state) {
2427
+ try {
2428
+ this.storageService.set(this.STORAGE_KEY, state);
2429
+ }
2430
+ catch (error) {
2431
+ console.error('Failed to persist app features to localStorage:', error);
2432
+ // Continue execution - state is still valid in memory for current session
2433
+ }
2434
+ }
2435
+ /**
2436
+ * Validate forced feature IDs against configured features and clean up invalid ones.
2437
+ * Called after setAppFeatures() to ensure forced state references valid features.
2438
+ */
2439
+ validateAndCleanForcedState() {
2440
+ const configuredFeatureIds = this.appFeaturesSubject.value.map((f) => f.id);
2441
+ const currentForcedState = this.forcedFeaturesSubject.value;
2442
+ const validEnabled = currentForcedState.enabled.filter((id) => configuredFeatureIds.includes(id));
2443
+ const validDisabled = currentForcedState.disabled.filter((id) => configuredFeatureIds.includes(id));
2444
+ const removedIds = [
2445
+ ...currentForcedState.enabled.filter((id) => !configuredFeatureIds.includes(id)),
2446
+ ...currentForcedState.disabled.filter((id) => !configuredFeatureIds.includes(id)),
2447
+ ];
2448
+ if (removedIds.length > 0) {
2449
+ console.warn(`Removed invalid feature IDs from forced state: ${removedIds.join(', ')}`);
2450
+ const cleanedState = { enabled: validEnabled, disabled: validDisabled };
2451
+ this.forcedFeaturesSubject.next(cleanedState);
2452
+ this.saveForcedFeatures(cleanedState);
2453
+ }
2454
+ }
2455
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DevToolbarInternalAppFeaturesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2456
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DevToolbarInternalAppFeaturesService, providedIn: 'root' }); }
2142
2457
  }
2143
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DevToolbarInternalFeatureFlagService, decorators: [{
2458
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DevToolbarInternalAppFeaturesService, decorators: [{
2144
2459
  type: Injectable,
2145
2460
  args: [{ providedIn: 'root' }]
2146
2461
  }], ctorParameters: () => [] });
2147
2462
 
2148
- class DevToolbarFeatureFlagsToolComponent {
2463
+ /**
2464
+ * Component for managing app features in the dev toolbar.
2465
+ *
2466
+ * Provides UI for:
2467
+ * - Searching features by name and description
2468
+ * - Filtering features by state (all/forced/enabled/disabled)
2469
+ * - Forcing features to enabled/disabled state via 3-state dropdown
2470
+ * - Viewing feature descriptions and current state
2471
+ *
2472
+ * @example
2473
+ * ```html
2474
+ * <ndt-app-features-tool />
2475
+ * ```
2476
+ */
2477
+ class DevToolbarAppFeaturesToolComponent {
2149
2478
  constructor() {
2150
2479
  // Injects
2151
- this.featureFlags = inject(DevToolbarInternalFeatureFlagService);
2480
+ this.appFeaturesService = inject(DevToolbarInternalAppFeaturesService);
2152
2481
  // Signals
2153
2482
  this.activeFilter = signal('all');
2154
2483
  this.searchQuery = signal('');
2155
- this.flags = this.featureFlags.flags;
2156
- this.hasNoFlags = computed(() => this.flags().length === 0);
2157
- this.filteredFlags = computed(() => {
2158
- return this.flags().filter((flag) => {
2484
+ this.features = this.appFeaturesService.features;
2485
+ this.hasNoFeatures = computed(() => this.features().length === 0);
2486
+ this.filteredFeatures = computed(() => {
2487
+ return this.features().filter((feature) => {
2159
2488
  const searchTerm = this.searchQuery().toLowerCase();
2160
- const flagName = flag.name.toLowerCase();
2161
- const flagDescription = flag.description?.toLowerCase() ?? '';
2489
+ const featureName = feature.name.toLowerCase();
2490
+ const featureDescription = feature.description?.toLowerCase() ?? '';
2162
2491
  const matchesSearch = !this.searchQuery() ||
2163
- flagName.toLowerCase().includes(searchTerm.toLowerCase()) ||
2164
- flagDescription.toLowerCase().includes(searchTerm.toLowerCase());
2492
+ featureName.includes(searchTerm) ||
2493
+ featureDescription.includes(searchTerm);
2165
2494
  const matchesFilter = this.activeFilter() === 'all' ||
2166
- (this.activeFilter() === 'forced' && flag.isForced) ||
2167
- (this.activeFilter() === 'enabled' && flag.isEnabled) ||
2168
- (this.activeFilter() === 'disabled' && !flag.isEnabled);
2495
+ (this.activeFilter() === 'forced' && feature.isForced) ||
2496
+ (this.activeFilter() === 'enabled' && feature.isEnabled) ||
2497
+ (this.activeFilter() === 'disabled' && !feature.isEnabled);
2169
2498
  return matchesSearch && matchesFilter;
2170
2499
  });
2171
2500
  });
2172
- this.hasNoFilteredFlags = computed(() => this.filteredFlags().length === 0);
2501
+ this.hasNoFilteredFeatures = computed(() => this.filteredFeatures().length === 0);
2173
2502
  // Other properties
2174
2503
  this.options = {
2175
- title: 'Feature Flags',
2176
- description: 'Manage the feature flags for your current session',
2504
+ title: 'App Features',
2505
+ description: 'Override product features to test different tiers and configurations',
2177
2506
  isClosable: true,
2178
2507
  size: 'tall',
2179
- id: 'ndt-feature-flags',
2180
- isBeta: true,
2508
+ id: 'ndt-app-features',
2181
2509
  };
2182
2510
  this.filterOptions = [
2183
- { value: 'all', label: 'All Flags' },
2511
+ { value: 'all', label: 'All Features' },
2184
2512
  { value: 'forced', label: 'Forced' },
2185
2513
  { value: 'enabled', label: 'Enabled' },
2186
2514
  { value: 'disabled', label: 'Disabled' },
2187
2515
  ];
2188
- this.flagValueOptions = [
2189
- { value: 'not-forced', label: 'Not Forced' },
2190
- { value: 'off', label: 'Forced Off' },
2191
- { value: 'on', label: 'Forced On' },
2516
+ this.featureValueOptions = [
2517
+ { value: '', label: 'Not Forced' },
2518
+ { value: 'off', label: 'Disabled' },
2519
+ { value: 'on', label: 'Enabled' },
2192
2520
  ];
2193
2521
  }
2194
2522
  // Public methods
2523
+ /**
2524
+ * Handle filter dropdown change.
2525
+ * Updates the active filter to show all/forced/enabled/disabled features.
2526
+ */
2195
2527
  onFilterChange(value) {
2196
2528
  const filter = this.filterOptions.find((f) => f.value === value);
2197
2529
  if (filter) {
2198
2530
  this.activeFilter.set(filter.value);
2199
2531
  }
2200
2532
  }
2201
- onFlagChange(flagId, value) {
2533
+ /**
2534
+ * Handle feature value change from 3-state dropdown.
2535
+ * - 'not-forced' (empty string): Remove forced override
2536
+ * - 'on': Force feature to enabled
2537
+ * - 'off': Force feature to disabled
2538
+ */
2539
+ onFeatureChange(featureId, value) {
2202
2540
  switch (value) {
2203
- case 'not-forced':
2204
- this.featureFlags.removeFlagOverride(flagId);
2541
+ case '':
2542
+ this.appFeaturesService.removeFeatureOverride(featureId);
2205
2543
  break;
2206
2544
  case 'on':
2207
- this.featureFlags.setFlag(flagId, true);
2545
+ this.appFeaturesService.setFeature(featureId, true);
2208
2546
  break;
2209
2547
  case 'off':
2210
- this.featureFlags.setFlag(flagId, false);
2548
+ this.appFeaturesService.setFeature(featureId, false);
2211
2549
  break;
2212
2550
  }
2213
2551
  }
2552
+ /**
2553
+ * Handle search input change.
2554
+ * Updates the search query to filter features by name/description.
2555
+ */
2214
2556
  onSearchChange(query) {
2215
2557
  this.searchQuery.set(query);
2216
2558
  }
2217
2559
  // Protected methods
2218
- getFlagValue(flag) {
2219
- if (!flag.isForced)
2560
+ /**
2561
+ * Get the dropdown value for a feature's current state.
2562
+ * - Returns empty string if not forced (natural state)
2563
+ * - Returns 'on' if forced to enabled
2564
+ * - Returns 'off' if forced to disabled
2565
+ */
2566
+ getFeatureValue(feature) {
2567
+ if (!feature.isForced)
2220
2568
  return '';
2221
- return flag.isEnabled ? 'on' : 'off';
2569
+ return feature.isEnabled ? 'on' : 'off';
2222
2570
  }
2223
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DevToolbarFeatureFlagsToolComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2224
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.5", type: DevToolbarFeatureFlagsToolComponent, isStandalone: true, selector: "ndt-feature-flags-tool", ngImport: i0, template: `
2571
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DevToolbarAppFeaturesToolComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2572
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.5", type: DevToolbarAppFeaturesToolComponent, isStandalone: true, selector: "ndt-app-features-tool", ngImport: i0, template: `
2225
2573
  <ndt-toolbar-tool
2226
2574
  [options]="options"
2227
- title="Feature Flags"
2228
- icon="toggle-left"
2575
+ title="App Features"
2576
+ icon="puzzle"
2229
2577
  >
2230
2578
  <div class="container">
2231
2579
  <div class="header">
2232
2580
  <ndt-input
2233
2581
  [value]="searchQuery()"
2234
2582
  (valueChange)="onSearchChange($event)"
2235
- placeholder="Search..."
2236
- />
2237
- <ndt-select
2238
- [value]="activeFilter()"
2239
- [options]="filterOptions"
2240
- [size]="'medium'"
2241
- (valueChange)="onFilterChange($event)"
2583
+ placeholder="Search features..."
2242
2584
  />
2243
- </div>
2244
-
2585
+ <div class="filter-wrapper">
2586
+ <ndt-icon name="filter" class="filter-icon" />
2587
+ <ndt-select
2588
+ [value]="activeFilter()"
2589
+ [options]="filterOptions"
2590
+ [size]="'medium'"
2591
+ (valueChange)="onFilterChange($event)"
2592
+ />
2593
+ </div>
2594
+ </div>
2595
+
2596
+ @if (hasNoFeatures()) {
2597
+ <div class="empty">
2598
+ <p>No app features found</p>
2599
+ <small>Call setAvailableOptions() to configure features</small>
2600
+ </div>
2601
+ } @else if (hasNoFilteredFeatures()) {
2602
+ <div class="empty">
2603
+ <p>No features match your filter</p>
2604
+ </div>
2605
+ } @else {
2606
+ <div class="feature-list">
2607
+ @for (feature of filteredFeatures(); track feature.id) {
2608
+ <div class="feature">
2609
+ <div class="info">
2610
+ <h3>{{ feature.name }}</h3>
2611
+ @if (feature.description) {
2612
+ <p>{{ feature.description }}</p>
2613
+ }
2614
+ </div>
2615
+
2616
+ <ndt-select
2617
+ [value]="getFeatureValue(feature)"
2618
+ [options]="featureValueOptions"
2619
+ [ariaLabel]="'Set value for ' + feature.name"
2620
+ (valueChange)="onFeatureChange(feature.id, $event ?? '')"
2621
+ size="small"
2622
+ />
2623
+ </div>
2624
+ }
2625
+ </div>
2626
+ }
2627
+ </div>
2628
+ </ndt-toolbar-tool>
2629
+ `, isInline: true, styles: [".container{display:flex;flex-direction:column;height:100%}.header{flex-shrink:0;display:flex;gap:var(--ndt-spacing-sm);margin-bottom:var(--ndt-spacing-md)}.header ndt-input{flex:.65}.header .filter-wrapper{flex:.35;display:flex;align-items:center;gap:var(--ndt-spacing-xs)}.header .filter-wrapper .filter-icon{width:18px;height:18px;flex-shrink:0;opacity:.6}.header .filter-wrapper ndt-select{flex:1}.empty{display:flex;flex-direction:column;gap:var(--ndt-spacing-md);flex:1;min-height:0;justify-content:center;align-items:center;border:1px solid var(--ndt-warning-border);border-radius:var(--ndt-border-radius-medium);padding:var(--ndt-spacing-md);background:var(--ndt-warning-background);color:var(--ndt-text-muted)}.empty p{margin:0;font-size:var(--ndt-font-size-md)}.empty small{font-size:var(--ndt-font-size-xs);opacity:.8}.feature-list{display:flex;flex-direction:column;gap:var(--ndt-spacing-md);flex:1;min-height:0;overflow-y:auto;padding-right:var(--ndt-spacing-sm);scrollbar-width:thin;scrollbar-color:var(--ndt-border-primary) var(--ndt-background-secondary)}.feature-list::-webkit-scrollbar{width:8px}.feature-list::-webkit-scrollbar-track{background:var(--ndt-background-secondary);border-radius:4px}.feature-list::-webkit-scrollbar-thumb{background:var(--ndt-border-primary);border-radius:4px}:is():hover{background:var(--ndt-hover-bg)}.feature{display:flex;flex-direction:row;gap:var(--ndt-spacing-sm);background:var(--ndt-background-secondary);padding:var(--ndt-spacing-md);border-radius:var(--ndt-border-radius-medium)}.feature .info{flex:0 0 65%}.feature .info h3{margin:0;font-size:var(--ndt-font-size-md);color:var(--ndt-text-primary)}.feature .info p{margin:var(--ndt-spacing-xs) 0 0 0;font-size:var(--ndt-font-size-xs);color:var(--ndt-text-muted)}.feature ndt-select{flex:0 0 35%}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "component", type: DevToolbarToolComponent, selector: "ndt-toolbar-tool", inputs: ["options", "icon", "title"] }, { kind: "component", type: DevToolbarInputComponent, selector: "ndt-input", inputs: ["value", "type", "placeholder", "ariaLabel", "inputClass"], outputs: ["valueChange"] }, { kind: "component", type: DevToolbarSelectComponent, selector: "ndt-select", inputs: ["value", "options", "ariaLabel", "label", "size"], outputs: ["valueChange"] }, { kind: "component", type: DevToolbarIconComponent, selector: "ndt-icon", inputs: ["name"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2630
+ }
2631
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DevToolbarAppFeaturesToolComponent, decorators: [{
2632
+ type: Component,
2633
+ args: [{ selector: 'ndt-app-features-tool', standalone: true, imports: [
2634
+ FormsModule,
2635
+ DevToolbarToolComponent,
2636
+ DevToolbarInputComponent,
2637
+ DevToolbarSelectComponent,
2638
+ DevToolbarIconComponent,
2639
+ ], template: `
2640
+ <ndt-toolbar-tool
2641
+ [options]="options"
2642
+ title="App Features"
2643
+ icon="puzzle"
2644
+ >
2645
+ <div class="container">
2646
+ <div class="header">
2647
+ <ndt-input
2648
+ [value]="searchQuery()"
2649
+ (valueChange)="onSearchChange($event)"
2650
+ placeholder="Search features..."
2651
+ />
2652
+ <div class="filter-wrapper">
2653
+ <ndt-icon name="filter" class="filter-icon" />
2654
+ <ndt-select
2655
+ [value]="activeFilter()"
2656
+ [options]="filterOptions"
2657
+ [size]="'medium'"
2658
+ (valueChange)="onFilterChange($event)"
2659
+ />
2660
+ </div>
2661
+ </div>
2662
+
2663
+ @if (hasNoFeatures()) {
2664
+ <div class="empty">
2665
+ <p>No app features found</p>
2666
+ <small>Call setAvailableOptions() to configure features</small>
2667
+ </div>
2668
+ } @else if (hasNoFilteredFeatures()) {
2669
+ <div class="empty">
2670
+ <p>No features match your filter</p>
2671
+ </div>
2672
+ } @else {
2673
+ <div class="feature-list">
2674
+ @for (feature of filteredFeatures(); track feature.id) {
2675
+ <div class="feature">
2676
+ <div class="info">
2677
+ <h3>{{ feature.name }}</h3>
2678
+ @if (feature.description) {
2679
+ <p>{{ feature.description }}</p>
2680
+ }
2681
+ </div>
2682
+
2683
+ <ndt-select
2684
+ [value]="getFeatureValue(feature)"
2685
+ [options]="featureValueOptions"
2686
+ [ariaLabel]="'Set value for ' + feature.name"
2687
+ (valueChange)="onFeatureChange(feature.id, $event ?? '')"
2688
+ size="small"
2689
+ />
2690
+ </div>
2691
+ }
2692
+ </div>
2693
+ }
2694
+ </div>
2695
+ </ndt-toolbar-tool>
2696
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".container{display:flex;flex-direction:column;height:100%}.header{flex-shrink:0;display:flex;gap:var(--ndt-spacing-sm);margin-bottom:var(--ndt-spacing-md)}.header ndt-input{flex:.65}.header .filter-wrapper{flex:.35;display:flex;align-items:center;gap:var(--ndt-spacing-xs)}.header .filter-wrapper .filter-icon{width:18px;height:18px;flex-shrink:0;opacity:.6}.header .filter-wrapper ndt-select{flex:1}.empty{display:flex;flex-direction:column;gap:var(--ndt-spacing-md);flex:1;min-height:0;justify-content:center;align-items:center;border:1px solid var(--ndt-warning-border);border-radius:var(--ndt-border-radius-medium);padding:var(--ndt-spacing-md);background:var(--ndt-warning-background);color:var(--ndt-text-muted)}.empty p{margin:0;font-size:var(--ndt-font-size-md)}.empty small{font-size:var(--ndt-font-size-xs);opacity:.8}.feature-list{display:flex;flex-direction:column;gap:var(--ndt-spacing-md);flex:1;min-height:0;overflow-y:auto;padding-right:var(--ndt-spacing-sm);scrollbar-width:thin;scrollbar-color:var(--ndt-border-primary) var(--ndt-background-secondary)}.feature-list::-webkit-scrollbar{width:8px}.feature-list::-webkit-scrollbar-track{background:var(--ndt-background-secondary);border-radius:4px}.feature-list::-webkit-scrollbar-thumb{background:var(--ndt-border-primary);border-radius:4px}:is():hover{background:var(--ndt-hover-bg)}.feature{display:flex;flex-direction:row;gap:var(--ndt-spacing-sm);background:var(--ndt-background-secondary);padding:var(--ndt-spacing-md);border-radius:var(--ndt-border-radius-medium)}.feature .info{flex:0 0 65%}.feature .info h3{margin:0;font-size:var(--ndt-font-size-md);color:var(--ndt-text-primary)}.feature .info p{margin:var(--ndt-spacing-xs) 0 0 0;font-size:var(--ndt-font-size-xs);color:var(--ndt-text-muted)}.feature ndt-select{flex:0 0 35%}\n"] }]
2697
+ }] });
2698
+
2699
+ class DevToolbarInternalFeatureFlagService {
2700
+ constructor() {
2701
+ this.STORAGE_KEY = 'feature-flags';
2702
+ this.storageService = inject(DevToolsStorageService);
2703
+ this.appFlags$ = new BehaviorSubject([]);
2704
+ this.forcedFlagsSubject = new BehaviorSubject({
2705
+ enabled: [],
2706
+ disabled: [],
2707
+ });
2708
+ this.forcedFlags$ = this.forcedFlagsSubject.asObservable();
2709
+ this.flags$ = combineLatest([
2710
+ this.appFlags$,
2711
+ this.forcedFlags$,
2712
+ ]).pipe(map(([appFlags, { enabled, disabled }]) => {
2713
+ return appFlags.map((flag) => ({
2714
+ ...flag,
2715
+ isForced: enabled.includes(flag.id) || disabled.includes(flag.id),
2716
+ isEnabled: enabled.includes(flag.id),
2717
+ }));
2718
+ }));
2719
+ this.flags = toSignal(this.flags$, { initialValue: [] });
2720
+ this.loadForcedFlags();
2721
+ }
2722
+ setAppFlags(flags) {
2723
+ this.appFlags$.next(flags);
2724
+ }
2725
+ getAppFlags() {
2726
+ return this.appFlags$.asObservable();
2727
+ }
2728
+ getForcedFlags() {
2729
+ return this.flags$.pipe(map((flags) => flags.filter((flag) => flag.isForced)));
2730
+ }
2731
+ setFlag(flagId, isEnabled) {
2732
+ const { enabled, disabled } = this.forcedFlagsSubject.value;
2733
+ const newEnabled = enabled.filter((id) => id !== flagId);
2734
+ const newDisabled = disabled.filter((id) => id !== flagId);
2735
+ if (isEnabled) {
2736
+ newEnabled.push(flagId);
2737
+ }
2738
+ else {
2739
+ newDisabled.push(flagId);
2740
+ }
2741
+ const newState = { enabled: newEnabled, disabled: newDisabled };
2742
+ this.forcedFlagsSubject.next(newState);
2743
+ this.storageService.set(this.STORAGE_KEY, newState);
2744
+ }
2745
+ removeFlagOverride(flagId) {
2746
+ const { enabled, disabled } = this.forcedFlagsSubject.value;
2747
+ const newState = {
2748
+ enabled: enabled.filter((id) => id !== flagId),
2749
+ disabled: disabled.filter((id) => id !== flagId),
2750
+ };
2751
+ this.forcedFlagsSubject.next(newState);
2752
+ this.storageService.set(this.STORAGE_KEY, newState);
2753
+ }
2754
+ loadForcedFlags() {
2755
+ const savedFlags = this.storageService.get(this.STORAGE_KEY);
2756
+ if (savedFlags) {
2757
+ this.forcedFlagsSubject.next(savedFlags);
2758
+ }
2759
+ }
2760
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DevToolbarInternalFeatureFlagService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2761
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DevToolbarInternalFeatureFlagService, providedIn: 'root' }); }
2762
+ }
2763
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DevToolbarInternalFeatureFlagService, decorators: [{
2764
+ type: Injectable,
2765
+ args: [{ providedIn: 'root' }]
2766
+ }], ctorParameters: () => [] });
2767
+
2768
+ class DevToolbarFeatureFlagsToolComponent {
2769
+ constructor() {
2770
+ // Injects
2771
+ this.featureFlags = inject(DevToolbarInternalFeatureFlagService);
2772
+ // Signals
2773
+ this.activeFilter = signal('all');
2774
+ this.searchQuery = signal('');
2775
+ this.flags = this.featureFlags.flags;
2776
+ this.hasNoFlags = computed(() => this.flags().length === 0);
2777
+ this.filteredFlags = computed(() => {
2778
+ return this.flags().filter((flag) => {
2779
+ const searchTerm = this.searchQuery().toLowerCase();
2780
+ const flagName = flag.name.toLowerCase();
2781
+ const flagDescription = flag.description?.toLowerCase() ?? '';
2782
+ const matchesSearch = !this.searchQuery() ||
2783
+ flagName.toLowerCase().includes(searchTerm.toLowerCase()) ||
2784
+ flagDescription.toLowerCase().includes(searchTerm.toLowerCase());
2785
+ const matchesFilter = this.activeFilter() === 'all' ||
2786
+ (this.activeFilter() === 'forced' && flag.isForced) ||
2787
+ (this.activeFilter() === 'enabled' && flag.isEnabled) ||
2788
+ (this.activeFilter() === 'disabled' && !flag.isEnabled);
2789
+ return matchesSearch && matchesFilter;
2790
+ });
2791
+ });
2792
+ this.hasNoFilteredFlags = computed(() => this.filteredFlags().length === 0);
2793
+ // Other properties
2794
+ this.options = {
2795
+ title: 'Feature Flags',
2796
+ description: 'Manage the feature flags for your current session',
2797
+ isClosable: true,
2798
+ size: 'tall',
2799
+ id: 'ndt-feature-flags',
2800
+ isBeta: true,
2801
+ };
2802
+ this.filterOptions = [
2803
+ { value: 'all', label: 'All Flags' },
2804
+ { value: 'forced', label: 'Forced' },
2805
+ { value: 'enabled', label: 'Enabled' },
2806
+ { value: 'disabled', label: 'Disabled' },
2807
+ ];
2808
+ this.flagValueOptions = [
2809
+ { value: 'not-forced', label: 'Not Forced' },
2810
+ { value: 'off', label: 'Forced Off' },
2811
+ { value: 'on', label: 'Forced On' },
2812
+ ];
2813
+ }
2814
+ // Public methods
2815
+ onFilterChange(value) {
2816
+ const filter = this.filterOptions.find((f) => f.value === value);
2817
+ if (filter) {
2818
+ this.activeFilter.set(filter.value);
2819
+ }
2820
+ }
2821
+ onFlagChange(flagId, value) {
2822
+ switch (value) {
2823
+ case 'not-forced':
2824
+ this.featureFlags.removeFlagOverride(flagId);
2825
+ break;
2826
+ case 'on':
2827
+ this.featureFlags.setFlag(flagId, true);
2828
+ break;
2829
+ case 'off':
2830
+ this.featureFlags.setFlag(flagId, false);
2831
+ break;
2832
+ }
2833
+ }
2834
+ onSearchChange(query) {
2835
+ this.searchQuery.set(query);
2836
+ }
2837
+ // Protected methods
2838
+ getFlagValue(flag) {
2839
+ if (!flag.isForced)
2840
+ return '';
2841
+ return flag.isEnabled ? 'on' : 'off';
2842
+ }
2843
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DevToolbarFeatureFlagsToolComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2844
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.5", type: DevToolbarFeatureFlagsToolComponent, isStandalone: true, selector: "ndt-feature-flags-tool", ngImport: i0, template: `
2845
+ <ndt-toolbar-tool
2846
+ [options]="options"
2847
+ title="Feature Flags"
2848
+ icon="toggle-left"
2849
+ >
2850
+ <div class="container">
2851
+ <div class="header">
2852
+ <ndt-input
2853
+ [value]="searchQuery()"
2854
+ (valueChange)="onSearchChange($event)"
2855
+ placeholder="Search..."
2856
+ />
2857
+ <div class="filter-wrapper">
2858
+ <ndt-icon name="filter" class="filter-icon" />
2859
+ <ndt-select
2860
+ [value]="activeFilter()"
2861
+ [options]="filterOptions"
2862
+ [size]="'medium'"
2863
+ (valueChange)="onFilterChange($event)"
2864
+ />
2865
+ </div>
2866
+ </div>
2867
+
2245
2868
  @if (hasNoFlags()) {
2246
2869
  <div class="empty">
2247
2870
  <p>No flags found</p>
@@ -2272,7 +2895,7 @@ class DevToolbarFeatureFlagsToolComponent {
2272
2895
  }
2273
2896
  </div>
2274
2897
  </ndt-toolbar-tool>
2275
- `, isInline: true, styles: [".container{display:flex;flex-direction:column;height:100%}.header{flex-shrink:0;display:flex;gap:var(--ndt-spacing-sm);margin-bottom:var(--ndt-spacing-md)}.header ndt-input{flex:.65}.header ndt-select{flex:.35}.empty{display:flex;flex-direction:column;gap:var(--ndt-spacing-md);flex:1;min-height:0;justify-content:center;align-items:center;border:1px solid var(--ndt-warning-border);border-radius:var(--ndt-border-radius-medium);padding:var(--ndt-spacing-md);background:var(--ndt-warning-background);color:var(--ndt-text-muted)}.flag-list{display:flex;flex-direction:column;gap:var(--ndt-spacing-md);flex:1;min-height:0;overflow-y:auto;padding-right:var(--ndt-spacing-sm);scrollbar-width:thin;scrollbar-color:var(--ndt-border-primary) var(--ndt-background-secondary)}.flag-list::-webkit-scrollbar{width:8px}.flag-list::-webkit-scrollbar-track{background:var(--ndt-background-secondary);border-radius:4px}.flag-list::-webkit-scrollbar-thumb{background:var(--ndt-border-primary);border-radius:4px}:is():hover{background:var(--ndt-hover-bg)}.flag{display:flex;flex-direction:row;gap:var(--ndt-spacing-sm);background:var(--ndt-background-secondary)}.flag .info{flex:0 0 65%}.flag .info h3{margin:0;font-size:var(--ndt-font-size-md);color:var(--ndt-text-primary)}.flag .info p{font-size:var(--ndt-font-size-xs);color:var(--ndt-text-muted)}.flag ndt-select{flex:0 0 35%}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "component", type: DevToolbarToolComponent, selector: "ndt-toolbar-tool", inputs: ["options", "icon", "title"] }, { kind: "component", type: DevToolbarInputComponent, selector: "ndt-input", inputs: ["value", "type", "placeholder", "ariaLabel", "inputClass"], outputs: ["valueChange"] }, { kind: "component", type: DevToolbarSelectComponent, selector: "ndt-select", inputs: ["value", "options", "ariaLabel", "label", "size"], outputs: ["valueChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2898
+ `, isInline: true, styles: [".container{display:flex;flex-direction:column;height:100%}.header{flex-shrink:0;display:flex;gap:var(--ndt-spacing-sm);margin-bottom:var(--ndt-spacing-md)}.header ndt-input{flex:.65}.header .filter-wrapper{flex:.35;display:flex;align-items:center;gap:var(--ndt-spacing-xs)}.header .filter-wrapper .filter-icon{width:18px;height:18px;flex-shrink:0;opacity:.6}.header .filter-wrapper ndt-select{flex:1}.empty{display:flex;flex-direction:column;gap:var(--ndt-spacing-md);flex:1;min-height:0;justify-content:center;align-items:center;border:1px solid var(--ndt-warning-border);border-radius:var(--ndt-border-radius-medium);padding:var(--ndt-spacing-md);background:var(--ndt-warning-background);color:var(--ndt-text-muted)}.flag-list{display:flex;flex-direction:column;gap:var(--ndt-spacing-md);flex:1;min-height:0;overflow-y:auto;padding-right:var(--ndt-spacing-sm);scrollbar-width:thin;scrollbar-color:var(--ndt-border-primary) var(--ndt-background-secondary)}.flag-list::-webkit-scrollbar{width:8px}.flag-list::-webkit-scrollbar-track{background:var(--ndt-background-secondary);border-radius:4px}.flag-list::-webkit-scrollbar-thumb{background:var(--ndt-border-primary);border-radius:4px}:is():hover{background:var(--ndt-hover-bg)}.flag{display:flex;flex-direction:row;gap:var(--ndt-spacing-sm);background:var(--ndt-background-secondary)}.flag .info{flex:0 0 65%}.flag .info h3{margin:0;font-size:var(--ndt-font-size-md);color:var(--ndt-text-primary)}.flag .info p{font-size:var(--ndt-font-size-xs);color:var(--ndt-text-muted)}.flag ndt-select{flex:0 0 35%}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "component", type: DevToolbarToolComponent, selector: "ndt-toolbar-tool", inputs: ["options", "icon", "title"] }, { kind: "component", type: DevToolbarInputComponent, selector: "ndt-input", inputs: ["value", "type", "placeholder", "ariaLabel", "inputClass"], outputs: ["valueChange"] }, { kind: "component", type: DevToolbarSelectComponent, selector: "ndt-select", inputs: ["value", "options", "ariaLabel", "label", "size"], outputs: ["valueChange"] }, { kind: "component", type: DevToolbarIconComponent, selector: "ndt-icon", inputs: ["name"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2276
2899
  }
2277
2900
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DevToolbarFeatureFlagsToolComponent, decorators: [{
2278
2901
  type: Component,
@@ -2281,6 +2904,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImpor
2281
2904
  DevToolbarToolComponent,
2282
2905
  DevToolbarInputComponent,
2283
2906
  DevToolbarSelectComponent,
2907
+ DevToolbarIconComponent,
2284
2908
  ], template: `
2285
2909
  <ndt-toolbar-tool
2286
2910
  [options]="options"
@@ -2294,12 +2918,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImpor
2294
2918
  (valueChange)="onSearchChange($event)"
2295
2919
  placeholder="Search..."
2296
2920
  />
2297
- <ndt-select
2298
- [value]="activeFilter()"
2299
- [options]="filterOptions"
2300
- [size]="'medium'"
2301
- (valueChange)="onFilterChange($event)"
2302
- />
2921
+ <div class="filter-wrapper">
2922
+ <ndt-icon name="filter" class="filter-icon" />
2923
+ <ndt-select
2924
+ [value]="activeFilter()"
2925
+ [options]="filterOptions"
2926
+ [size]="'medium'"
2927
+ (valueChange)="onFilterChange($event)"
2928
+ />
2929
+ </div>
2303
2930
  </div>
2304
2931
 
2305
2932
  @if (hasNoFlags()) {
@@ -2332,7 +2959,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImpor
2332
2959
  }
2333
2960
  </div>
2334
2961
  </ndt-toolbar-tool>
2335
- `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".container{display:flex;flex-direction:column;height:100%}.header{flex-shrink:0;display:flex;gap:var(--ndt-spacing-sm);margin-bottom:var(--ndt-spacing-md)}.header ndt-input{flex:.65}.header ndt-select{flex:.35}.empty{display:flex;flex-direction:column;gap:var(--ndt-spacing-md);flex:1;min-height:0;justify-content:center;align-items:center;border:1px solid var(--ndt-warning-border);border-radius:var(--ndt-border-radius-medium);padding:var(--ndt-spacing-md);background:var(--ndt-warning-background);color:var(--ndt-text-muted)}.flag-list{display:flex;flex-direction:column;gap:var(--ndt-spacing-md);flex:1;min-height:0;overflow-y:auto;padding-right:var(--ndt-spacing-sm);scrollbar-width:thin;scrollbar-color:var(--ndt-border-primary) var(--ndt-background-secondary)}.flag-list::-webkit-scrollbar{width:8px}.flag-list::-webkit-scrollbar-track{background:var(--ndt-background-secondary);border-radius:4px}.flag-list::-webkit-scrollbar-thumb{background:var(--ndt-border-primary);border-radius:4px}:is():hover{background:var(--ndt-hover-bg)}.flag{display:flex;flex-direction:row;gap:var(--ndt-spacing-sm);background:var(--ndt-background-secondary)}.flag .info{flex:0 0 65%}.flag .info h3{margin:0;font-size:var(--ndt-font-size-md);color:var(--ndt-text-primary)}.flag .info p{font-size:var(--ndt-font-size-xs);color:var(--ndt-text-muted)}.flag ndt-select{flex:0 0 35%}\n"] }]
2962
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".container{display:flex;flex-direction:column;height:100%}.header{flex-shrink:0;display:flex;gap:var(--ndt-spacing-sm);margin-bottom:var(--ndt-spacing-md)}.header ndt-input{flex:.65}.header .filter-wrapper{flex:.35;display:flex;align-items:center;gap:var(--ndt-spacing-xs)}.header .filter-wrapper .filter-icon{width:18px;height:18px;flex-shrink:0;opacity:.6}.header .filter-wrapper ndt-select{flex:1}.empty{display:flex;flex-direction:column;gap:var(--ndt-spacing-md);flex:1;min-height:0;justify-content:center;align-items:center;border:1px solid var(--ndt-warning-border);border-radius:var(--ndt-border-radius-medium);padding:var(--ndt-spacing-md);background:var(--ndt-warning-background);color:var(--ndt-text-muted)}.flag-list{display:flex;flex-direction:column;gap:var(--ndt-spacing-md);flex:1;min-height:0;overflow-y:auto;padding-right:var(--ndt-spacing-sm);scrollbar-width:thin;scrollbar-color:var(--ndt-border-primary) var(--ndt-background-secondary)}.flag-list::-webkit-scrollbar{width:8px}.flag-list::-webkit-scrollbar-track{background:var(--ndt-background-secondary);border-radius:4px}.flag-list::-webkit-scrollbar-thumb{background:var(--ndt-border-primary);border-radius:4px}:is():hover{background:var(--ndt-hover-bg)}.flag{display:flex;flex-direction:row;gap:var(--ndt-spacing-sm);background:var(--ndt-background-secondary)}.flag .info{flex:0 0 65%}.flag .info h3{margin:0;font-size:var(--ndt-font-size-md);color:var(--ndt-text-primary)}.flag .info p{font-size:var(--ndt-font-size-xs);color:var(--ndt-text-muted)}.flag ndt-select{flex:0 0 35%}\n"] }]
2336
2963
  }] });
2337
2964
 
2338
2965
  class DevToolbarButtonComponent {
@@ -2883,36 +3510,671 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImpor
2883
3510
  `, styles: [".language-select{display:flex;flex-direction:row;gap:.5rem;align-items:center;justify-content:space-between}\n"] }]
2884
3511
  }] });
2885
3512
 
2886
- class DevToolbarComponent {
3513
+ class DevToolbarNetworkMockerService {
2887
3514
  constructor() {
2888
- this.state = inject(DevToolbarStateService);
2889
- this.destroyRef = inject(DestroyRef);
2890
- this.settingsService = inject(SettingsService);
2891
- this.isDevMode = isDevMode();
2892
- this.keyboardShortcut = fromEvent(window, 'keydown')
2893
- .pipe(filter((event) => event.ctrlKey && event.shiftKey && event.key === 'D'), takeUntilDestroyed(this.destroyRef))
2894
- .subscribe(() => this.toggleDevTools());
2895
- this.mouseLeave = fromEvent(document, 'mouseleave')
2896
- .pipe(throttleTime(3000), takeUntilDestroyed(this.destroyRef))
2897
- .subscribe(() => this.onMouseLeave());
3515
+ this.mockRequests = signal([]);
3516
+ this.isMockingEnabled = signal(false);
2898
3517
  }
2899
- ngOnInit() {
2900
- const settings = this.settingsService.getSettings();
2901
- this.state.setTheme(settings.isDarkMode ? 'dark' : 'light');
3518
+ getMockRequests() {
3519
+ return this.mockRequests.asReadonly();
2902
3520
  }
2903
- onMouseEnter() {
2904
- this.state.setVisibility(true);
3521
+ getIsMockingEnabled() {
3522
+ return this.isMockingEnabled.asReadonly();
2905
3523
  }
2906
- onMouseLeave() {
2907
- setTimeout(() => this.state.setVisibility(false), this.state.delay());
3524
+ addMockRequest(config) {
3525
+ const mockRequest = {
3526
+ id: crypto.randomUUID(),
3527
+ url: config.url,
3528
+ method: config.method,
3529
+ status: config.status || 200,
3530
+ response: config.response || {},
3531
+ isActive: true,
3532
+ createdAt: new Date(),
3533
+ };
3534
+ this.mockRequests.update((requests) => [mockRequest, ...requests]);
2908
3535
  }
2909
- toggleDevTools() {
2910
- this.state.setVisibility(!this.state.isVisible());
3536
+ removeMockRequest(id) {
3537
+ this.mockRequests.update((requests) => requests.filter((request) => request.id !== id));
2911
3538
  }
2912
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DevToolbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2913
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.5", type: DevToolbarComponent, isStandalone: true, selector: "ndt-toolbar", ngImport: i0, template: `
2914
- @if (isDevMode) {
2915
- <div
3539
+ toggleMockRequest(id) {
3540
+ this.mockRequests.update((requests) => requests.map((request) => request.id === id
3541
+ ? { ...request, isActive: !request.isActive }
3542
+ : request));
3543
+ }
3544
+ enableMocking() {
3545
+ this.isMockingEnabled.set(true);
3546
+ console.log('Network mocking enabled');
3547
+ }
3548
+ disableMocking() {
3549
+ this.isMockingEnabled.set(false);
3550
+ console.log('Network mocking disabled');
3551
+ }
3552
+ clearAllMocks() {
3553
+ this.mockRequests.set([]);
3554
+ this.isMockingEnabled.set(false);
3555
+ }
3556
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DevToolbarNetworkMockerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3557
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DevToolbarNetworkMockerService, providedIn: 'root' }); }
3558
+ }
3559
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DevToolbarNetworkMockerService, decorators: [{
3560
+ type: Injectable,
3561
+ args: [{
3562
+ providedIn: 'root',
3563
+ }]
3564
+ }] });
3565
+
3566
+ class DevToolbarNetworkMockerToolComponent {
3567
+ constructor() {
3568
+ // Injects
3569
+ this.networkMockerService = inject(DevToolbarNetworkMockerService);
3570
+ // Signals
3571
+ this.newMockUrl = signal('');
3572
+ this.newMockMethod = signal('GET');
3573
+ this.newMockStatus = signal('200');
3574
+ this.newMockResponse = signal('');
3575
+ // Computed values
3576
+ this.mockRequests = this.networkMockerService.getMockRequests();
3577
+ this.isMockingEnabled = this.networkMockerService.getIsMockingEnabled();
3578
+ this.hasNoMocks = computed(() => this.mockRequests().length === 0);
3579
+ // Window options
3580
+ this.options = {
3581
+ title: 'Network Mocker',
3582
+ description: 'Intercept and mock HTTP requests for testing and development',
3583
+ isClosable: true,
3584
+ size: 'tall',
3585
+ id: 'ndt-network-mocker',
3586
+ isBeta: true,
3587
+ };
3588
+ // Other properties
3589
+ this.httpMethods = [
3590
+ { value: 'GET', label: 'GET' },
3591
+ { value: 'POST', label: 'POST' },
3592
+ { value: 'PUT', label: 'PUT' },
3593
+ { value: 'DELETE', label: 'DELETE' },
3594
+ { value: 'PATCH', label: 'PATCH' },
3595
+ ];
3596
+ }
3597
+ // Public methods
3598
+ onAddMock() {
3599
+ const url = this.newMockUrl().trim();
3600
+ if (!url)
3601
+ return;
3602
+ const config = {
3603
+ url,
3604
+ method: this.newMockMethod(),
3605
+ status: parseInt(this.newMockStatus()) || 200,
3606
+ response: this.newMockResponse()
3607
+ ? JSON.parse(this.newMockResponse())
3608
+ : {},
3609
+ };
3610
+ this.networkMockerService.addMockRequest(config);
3611
+ // Reset form
3612
+ this.newMockUrl.set('');
3613
+ this.newMockMethod.set('GET');
3614
+ this.newMockStatus.set('200');
3615
+ this.newMockResponse.set('');
3616
+ }
3617
+ onToggleMocking() {
3618
+ if (this.isMockingEnabled()) {
3619
+ this.networkMockerService.disableMocking();
3620
+ }
3621
+ else {
3622
+ this.networkMockerService.enableMocking();
3623
+ }
3624
+ }
3625
+ onToggleMock(mockId) {
3626
+ this.networkMockerService.toggleMockRequest(mockId);
3627
+ }
3628
+ onRemoveMock(mockId) {
3629
+ this.networkMockerService.removeMockRequest(mockId);
3630
+ }
3631
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DevToolbarNetworkMockerToolComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3632
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.5", type: DevToolbarNetworkMockerToolComponent, isStandalone: true, selector: "ndt-network-mocker-tool", ngImport: i0, template: `
3633
+ <ndt-toolbar-tool [options]="options" title="Network Mocker" icon="network">
3634
+ <div class="container">
3635
+ <div class="header">
3636
+ <h3>Mock Network Requests</h3>
3637
+ <p>Intercept and mock HTTP requests for testing and development.</p>
3638
+ </div>
3639
+
3640
+ <div class="form">
3641
+ <div class="form-row">
3642
+ <ndt-input
3643
+ [(value)]="newMockUrl"
3644
+ placeholder="Enter URL pattern (e.g., /api/users/*)"
3645
+ ariaLabel="Mock URL"
3646
+ />
3647
+ <ndt-select
3648
+ [(value)]="newMockMethod"
3649
+ [options]="httpMethods"
3650
+ size="small"
3651
+ ariaLabel="HTTP Method"
3652
+ />
3653
+ </div>
3654
+
3655
+ <div class="form-row">
3656
+ <ndt-input
3657
+ [(value)]="newMockStatus"
3658
+ placeholder="Status code (default: 200)"
3659
+ type="number"
3660
+ ariaLabel="Status Code"
3661
+ />
3662
+ <ndt-input
3663
+ [(value)]="newMockResponse"
3664
+ placeholder="Response JSON (optional)"
3665
+ ariaLabel="Response JSON"
3666
+ />
3667
+ </div>
3668
+
3669
+ <div class="actions">
3670
+ <ndt-button label="Add Mock" icon="import" (click)="onAddMock()" />
3671
+ <ndt-button
3672
+ [label]="
3673
+ isMockingEnabled() ? 'Disable Mocking' : 'Enable Mocking'
3674
+ "
3675
+ [icon]="isMockingEnabled() ? 'trash' : 'network'"
3676
+ (click)="onToggleMocking()"
3677
+ />
3678
+ </div>
3679
+ </div>
3680
+
3681
+ @if (hasNoMocks()) {
3682
+ <div class="empty">
3683
+ <p>No mock requests configured. Add your first mock above.</p>
3684
+ </div>
3685
+ } @else {
3686
+ <div class="mocks-list">
3687
+ <h4>Configured Mocks</h4>
3688
+ @for (mock of mockRequests(); track mock.id) {
3689
+ <div class="mock-item">
3690
+ <div class="mock-info">
3691
+ <div class="mock-method">{{ mock.method }}</div>
3692
+ <div class="mock-url">{{ mock.url }}</div>
3693
+ <div class="mock-status">{{ mock.status }}</div>
3694
+ </div>
3695
+ <div class="mock-actions">
3696
+ <ndt-button
3697
+ variant="icon"
3698
+ [icon]="mock.isActive ? 'star' : 'moon'"
3699
+ [ariaLabel]="mock.isActive ? 'Disable mock' : 'Enable mock'"
3700
+ (click)="onToggleMock(mock.id)"
3701
+ />
3702
+ <ndt-button
3703
+ variant="icon"
3704
+ icon="trash"
3705
+ ariaLabel="Remove mock"
3706
+ (click)="onRemoveMock(mock.id)"
3707
+ />
3708
+ </div>
3709
+ </div>
3710
+ }
3711
+ </div>
3712
+ } @if (isMockingEnabled()) {
3713
+ <div class="status">
3714
+ <p>✅ Network mocking is active</p>
3715
+ </div>
3716
+ }
3717
+ </div>
3718
+ </ndt-toolbar-tool>
3719
+ `, isInline: true, styles: [".container{display:flex;flex-direction:column;height:100%;gap:var(--ndt-spacing-md);padding:var(--ndt-spacing-md)}.header{text-align:center;flex-shrink:0}.header h3{margin:0 0 var(--ndt-spacing-sm) 0;font-size:var(--ndt-font-size-lg);color:var(--ndt-text-primary);font-weight:600}.header p{margin:0;font-size:var(--ndt-font-size-sm);color:var(--ndt-text-muted)}.form{display:flex;flex-direction:column;gap:var(--ndt-spacing-sm);flex-shrink:0;padding:var(--ndt-spacing-md);background:var(--ndt-background-secondary);border:1px solid var(--ndt-border-primary);border-radius:var(--ndt-border-radius-medium)}.form-row{display:flex;gap:var(--ndt-spacing-sm)}.form-row ndt-input{flex:1}.form-row ndt-select{flex:0 0 120px}.actions{display:flex;gap:var(--ndt-spacing-sm);margin-top:var(--ndt-spacing-sm)}.empty{display:flex;align-items:center;justify-content:center;flex:1;color:var(--ndt-text-muted);text-align:center;font-size:var(--ndt-font-size-sm)}.mocks-list{display:flex;flex-direction:column;gap:var(--ndt-spacing-sm);flex:1;min-height:0;overflow-y:auto}.mocks-list h4{margin:0 0 var(--ndt-spacing-sm) 0;font-size:var(--ndt-font-size-md);color:var(--ndt-text-primary);font-weight:600}.mocks-list::-webkit-scrollbar{width:8px}.mocks-list::-webkit-scrollbar-track{background:var(--ndt-background-secondary);border-radius:4px}.mocks-list::-webkit-scrollbar-thumb{background:var(--ndt-border-primary);border-radius:4px}:is():hover{background:var(--ndt-hover-bg)}.mock-item{display:flex;align-items:center;gap:var(--ndt-spacing-sm);padding:var(--ndt-spacing-sm);background:var(--ndt-background-secondary);border:1px solid var(--ndt-border-primary);border-radius:var(--ndt-border-radius-small)}.mock-item .mock-info{flex:1;display:flex;align-items:center;gap:var(--ndt-spacing-sm)}.mock-item .mock-method{font-weight:600;font-size:var(--ndt-font-size-xs);color:var(--ndt-primary);background:var(--ndt-primary-background);padding:2px 6px;border-radius:var(--ndt-border-radius-small);min-width:40px;text-align:center}.mock-item .mock-url{flex:1;font-size:var(--ndt-font-size-sm);color:var(--ndt-text-primary);font-family:monospace}.mock-item .mock-status{font-size:var(--ndt-font-size-xs);color:var(--ndt-text-muted);min-width:30px;text-align:center}.mock-item .mock-actions{display:flex;gap:var(--ndt-spacing-xs)}.status{text-align:center;padding:var(--ndt-spacing-sm) var(--ndt-spacing-md);background:var(--ndt-success-background);border:1px solid var(--ndt-success-border);border-radius:var(--ndt-border-radius-small);color:var(--ndt-success-text);font-size:var(--ndt-font-size-sm);flex-shrink:0}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "component", type: DevToolbarToolComponent, selector: "ndt-toolbar-tool", inputs: ["options", "icon", "title"] }, { kind: "component", type: DevToolbarButtonComponent, selector: "ndt-button", inputs: ["type", "variant", "icon", "label", "ariaLabel", "isActive"] }, { kind: "component", type: DevToolbarInputComponent, selector: "ndt-input", inputs: ["value", "type", "placeholder", "ariaLabel", "inputClass"], outputs: ["valueChange"] }, { kind: "component", type: DevToolbarSelectComponent, selector: "ndt-select", inputs: ["value", "options", "ariaLabel", "label", "size"], outputs: ["valueChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3720
+ }
3721
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DevToolbarNetworkMockerToolComponent, decorators: [{
3722
+ type: Component,
3723
+ args: [{ selector: 'ndt-network-mocker-tool', standalone: true, imports: [
3724
+ FormsModule,
3725
+ DevToolbarToolComponent,
3726
+ DevToolbarButtonComponent,
3727
+ DevToolbarInputComponent,
3728
+ DevToolbarSelectComponent,
3729
+ ], template: `
3730
+ <ndt-toolbar-tool [options]="options" title="Network Mocker" icon="network">
3731
+ <div class="container">
3732
+ <div class="header">
3733
+ <h3>Mock Network Requests</h3>
3734
+ <p>Intercept and mock HTTP requests for testing and development.</p>
3735
+ </div>
3736
+
3737
+ <div class="form">
3738
+ <div class="form-row">
3739
+ <ndt-input
3740
+ [(value)]="newMockUrl"
3741
+ placeholder="Enter URL pattern (e.g., /api/users/*)"
3742
+ ariaLabel="Mock URL"
3743
+ />
3744
+ <ndt-select
3745
+ [(value)]="newMockMethod"
3746
+ [options]="httpMethods"
3747
+ size="small"
3748
+ ariaLabel="HTTP Method"
3749
+ />
3750
+ </div>
3751
+
3752
+ <div class="form-row">
3753
+ <ndt-input
3754
+ [(value)]="newMockStatus"
3755
+ placeholder="Status code (default: 200)"
3756
+ type="number"
3757
+ ariaLabel="Status Code"
3758
+ />
3759
+ <ndt-input
3760
+ [(value)]="newMockResponse"
3761
+ placeholder="Response JSON (optional)"
3762
+ ariaLabel="Response JSON"
3763
+ />
3764
+ </div>
3765
+
3766
+ <div class="actions">
3767
+ <ndt-button label="Add Mock" icon="import" (click)="onAddMock()" />
3768
+ <ndt-button
3769
+ [label]="
3770
+ isMockingEnabled() ? 'Disable Mocking' : 'Enable Mocking'
3771
+ "
3772
+ [icon]="isMockingEnabled() ? 'trash' : 'network'"
3773
+ (click)="onToggleMocking()"
3774
+ />
3775
+ </div>
3776
+ </div>
3777
+
3778
+ @if (hasNoMocks()) {
3779
+ <div class="empty">
3780
+ <p>No mock requests configured. Add your first mock above.</p>
3781
+ </div>
3782
+ } @else {
3783
+ <div class="mocks-list">
3784
+ <h4>Configured Mocks</h4>
3785
+ @for (mock of mockRequests(); track mock.id) {
3786
+ <div class="mock-item">
3787
+ <div class="mock-info">
3788
+ <div class="mock-method">{{ mock.method }}</div>
3789
+ <div class="mock-url">{{ mock.url }}</div>
3790
+ <div class="mock-status">{{ mock.status }}</div>
3791
+ </div>
3792
+ <div class="mock-actions">
3793
+ <ndt-button
3794
+ variant="icon"
3795
+ [icon]="mock.isActive ? 'star' : 'moon'"
3796
+ [ariaLabel]="mock.isActive ? 'Disable mock' : 'Enable mock'"
3797
+ (click)="onToggleMock(mock.id)"
3798
+ />
3799
+ <ndt-button
3800
+ variant="icon"
3801
+ icon="trash"
3802
+ ariaLabel="Remove mock"
3803
+ (click)="onRemoveMock(mock.id)"
3804
+ />
3805
+ </div>
3806
+ </div>
3807
+ }
3808
+ </div>
3809
+ } @if (isMockingEnabled()) {
3810
+ <div class="status">
3811
+ <p>✅ Network mocking is active</p>
3812
+ </div>
3813
+ }
3814
+ </div>
3815
+ </ndt-toolbar-tool>
3816
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".container{display:flex;flex-direction:column;height:100%;gap:var(--ndt-spacing-md);padding:var(--ndt-spacing-md)}.header{text-align:center;flex-shrink:0}.header h3{margin:0 0 var(--ndt-spacing-sm) 0;font-size:var(--ndt-font-size-lg);color:var(--ndt-text-primary);font-weight:600}.header p{margin:0;font-size:var(--ndt-font-size-sm);color:var(--ndt-text-muted)}.form{display:flex;flex-direction:column;gap:var(--ndt-spacing-sm);flex-shrink:0;padding:var(--ndt-spacing-md);background:var(--ndt-background-secondary);border:1px solid var(--ndt-border-primary);border-radius:var(--ndt-border-radius-medium)}.form-row{display:flex;gap:var(--ndt-spacing-sm)}.form-row ndt-input{flex:1}.form-row ndt-select{flex:0 0 120px}.actions{display:flex;gap:var(--ndt-spacing-sm);margin-top:var(--ndt-spacing-sm)}.empty{display:flex;align-items:center;justify-content:center;flex:1;color:var(--ndt-text-muted);text-align:center;font-size:var(--ndt-font-size-sm)}.mocks-list{display:flex;flex-direction:column;gap:var(--ndt-spacing-sm);flex:1;min-height:0;overflow-y:auto}.mocks-list h4{margin:0 0 var(--ndt-spacing-sm) 0;font-size:var(--ndt-font-size-md);color:var(--ndt-text-primary);font-weight:600}.mocks-list::-webkit-scrollbar{width:8px}.mocks-list::-webkit-scrollbar-track{background:var(--ndt-background-secondary);border-radius:4px}.mocks-list::-webkit-scrollbar-thumb{background:var(--ndt-border-primary);border-radius:4px}:is():hover{background:var(--ndt-hover-bg)}.mock-item{display:flex;align-items:center;gap:var(--ndt-spacing-sm);padding:var(--ndt-spacing-sm);background:var(--ndt-background-secondary);border:1px solid var(--ndt-border-primary);border-radius:var(--ndt-border-radius-small)}.mock-item .mock-info{flex:1;display:flex;align-items:center;gap:var(--ndt-spacing-sm)}.mock-item .mock-method{font-weight:600;font-size:var(--ndt-font-size-xs);color:var(--ndt-primary);background:var(--ndt-primary-background);padding:2px 6px;border-radius:var(--ndt-border-radius-small);min-width:40px;text-align:center}.mock-item .mock-url{flex:1;font-size:var(--ndt-font-size-sm);color:var(--ndt-text-primary);font-family:monospace}.mock-item .mock-status{font-size:var(--ndt-font-size-xs);color:var(--ndt-text-muted);min-width:30px;text-align:center}.mock-item .mock-actions{display:flex;gap:var(--ndt-spacing-xs)}.status{text-align:center;padding:var(--ndt-spacing-sm) var(--ndt-spacing-md);background:var(--ndt-success-background);border:1px solid var(--ndt-success-border);border-radius:var(--ndt-border-radius-small);color:var(--ndt-success-text);font-size:var(--ndt-font-size-sm);flex-shrink:0}\n"] }]
3817
+ }] });
3818
+
3819
+ class DevToolbarInternalPermissionsService {
3820
+ constructor() {
3821
+ this.STORAGE_KEY = 'permissions';
3822
+ this.storageService = inject(DevToolsStorageService);
3823
+ this.appPermissions$ = new BehaviorSubject([]);
3824
+ this.forcedStateSubject = new BehaviorSubject({
3825
+ granted: [],
3826
+ denied: [],
3827
+ });
3828
+ this.forcedState$ = this.forcedStateSubject.asObservable();
3829
+ this.permissions$ = combineLatest([
3830
+ this.appPermissions$,
3831
+ this.forcedState$,
3832
+ ]).pipe(map(([appPermissions, { granted, denied }]) => {
3833
+ return appPermissions.map((permission) => ({
3834
+ ...permission,
3835
+ isForced: granted.includes(permission.id) || denied.includes(permission.id),
3836
+ isGranted: granted.includes(permission.id)
3837
+ ? true
3838
+ : denied.includes(permission.id)
3839
+ ? false
3840
+ : permission.isGranted,
3841
+ }));
3842
+ }));
3843
+ this.permissions = toSignal(this.permissions$, { initialValue: [] });
3844
+ this.loadForcedState();
3845
+ }
3846
+ setAppPermissions(permissions) {
3847
+ this.appPermissions$.next(permissions);
3848
+ this.validateAndCleanForcedState(permissions);
3849
+ }
3850
+ setPermission(id, granted) {
3851
+ const { granted: grantedIds, denied: deniedIds } = this.forcedStateSubject.value;
3852
+ const newGranted = grantedIds.filter((permId) => permId !== id);
3853
+ const newDenied = deniedIds.filter((permId) => permId !== id);
3854
+ if (granted) {
3855
+ newGranted.push(id);
3856
+ }
3857
+ else {
3858
+ newDenied.push(id);
3859
+ }
3860
+ const newState = { granted: newGranted, denied: newDenied };
3861
+ this.forcedStateSubject.next(newState);
3862
+ this.storageService.set(this.STORAGE_KEY, newState);
3863
+ }
3864
+ removePermissionOverride(id) {
3865
+ const { granted, denied } = this.forcedStateSubject.value;
3866
+ const newState = {
3867
+ granted: granted.filter((permId) => permId !== id),
3868
+ denied: denied.filter((permId) => permId !== id),
3869
+ };
3870
+ this.forcedStateSubject.next(newState);
3871
+ this.storageService.set(this.STORAGE_KEY, newState);
3872
+ }
3873
+ getForcedPermissions() {
3874
+ return this.permissions$.pipe(map((permissions) => permissions.filter((permission) => permission.isForced)));
3875
+ }
3876
+ /**
3877
+ * Apply a preset permissions state, replacing the current forced state.
3878
+ * Useful for automated testing or restoring saved configurations.
3879
+ * @param state The preset forced permissions state to apply
3880
+ */
3881
+ applyPresetPermissions(state) {
3882
+ this.forcedStateSubject.next(state);
3883
+ this.storageService.set(this.STORAGE_KEY, state);
3884
+ }
3885
+ /**
3886
+ * Get the current forced permissions state.
3887
+ * Returns a deep copy to prevent external mutations.
3888
+ * @returns Current forced permissions state
3889
+ */
3890
+ getCurrentForcedState() {
3891
+ const currentState = this.forcedStateSubject.value;
3892
+ return {
3893
+ granted: [...currentState.granted],
3894
+ denied: [...currentState.denied],
3895
+ };
3896
+ }
3897
+ loadForcedState() {
3898
+ try {
3899
+ const savedState = this.storageService.get(this.STORAGE_KEY);
3900
+ if (savedState && this.isValidForcedState(savedState)) {
3901
+ this.forcedStateSubject.next(savedState);
3902
+ }
3903
+ }
3904
+ catch (error) {
3905
+ console.warn('Error loading forced permissions state from localStorage:', error);
3906
+ }
3907
+ }
3908
+ isValidForcedState(state) {
3909
+ return (state &&
3910
+ typeof state === 'object' &&
3911
+ Array.isArray(state.granted) &&
3912
+ Array.isArray(state.denied));
3913
+ }
3914
+ validateAndCleanForcedState(permissions) {
3915
+ const currentState = this.forcedStateSubject.value;
3916
+ const validIds = new Set(permissions.map((p) => p.id));
3917
+ const cleanedGranted = currentState.granted.filter((id) => validIds.has(id));
3918
+ const cleanedDenied = currentState.denied.filter((id) => validIds.has(id));
3919
+ const hasInvalidIds = cleanedGranted.length !== currentState.granted.length ||
3920
+ cleanedDenied.length !== currentState.denied.length;
3921
+ if (hasInvalidIds) {
3922
+ const cleanedState = {
3923
+ granted: cleanedGranted,
3924
+ denied: cleanedDenied,
3925
+ };
3926
+ this.forcedStateSubject.next(cleanedState);
3927
+ this.storageService.set(this.STORAGE_KEY, cleanedState);
3928
+ const invalidIds = [
3929
+ ...currentState.granted.filter((id) => !validIds.has(id)),
3930
+ ...currentState.denied.filter((id) => !validIds.has(id)),
3931
+ ];
3932
+ if (invalidIds.length > 0) {
3933
+ console.warn('Removed invalid permission IDs from forced state:', invalidIds);
3934
+ }
3935
+ }
3936
+ }
3937
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DevToolbarInternalPermissionsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3938
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DevToolbarInternalPermissionsService, providedIn: 'root' }); }
3939
+ }
3940
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DevToolbarInternalPermissionsService, decorators: [{
3941
+ type: Injectable,
3942
+ args: [{ providedIn: 'root' }]
3943
+ }], ctorParameters: () => [] });
3944
+
3945
+ class DevToolbarPermissionsToolComponent {
3946
+ constructor() {
3947
+ // Injects
3948
+ this.permissionsService = inject(DevToolbarInternalPermissionsService);
3949
+ // Signals
3950
+ this.activeFilter = signal('all');
3951
+ this.searchQuery = signal('');
3952
+ this.permissions = this.permissionsService.permissions;
3953
+ this.hasNoPermissions = computed(() => this.permissions().length === 0);
3954
+ this.filteredPermissions = computed(() => {
3955
+ return this.permissions().filter((permission) => {
3956
+ const searchTerm = this.searchQuery().toLowerCase();
3957
+ const permissionName = permission.name.toLowerCase();
3958
+ const permissionDescription = permission.description?.toLowerCase() ?? '';
3959
+ const matchesSearch = !this.searchQuery() ||
3960
+ permissionName.includes(searchTerm) ||
3961
+ permissionDescription.includes(searchTerm);
3962
+ const matchesFilter = this.activeFilter() === 'all' ||
3963
+ (this.activeFilter() === 'forced' && permission.isForced) ||
3964
+ (this.activeFilter() === 'granted' && permission.isGranted) ||
3965
+ (this.activeFilter() === 'denied' && !permission.isGranted);
3966
+ return matchesSearch && matchesFilter;
3967
+ });
3968
+ });
3969
+ this.hasNoFilteredPermissions = computed(() => this.filteredPermissions().length === 0);
3970
+ // Other properties
3971
+ this.options = {
3972
+ title: 'Permissions',
3973
+ description: 'Manage permission overrides for your current session',
3974
+ isClosable: true,
3975
+ size: 'tall',
3976
+ id: 'ndt-permissions',
3977
+ isBeta: true,
3978
+ };
3979
+ this.filterOptions = [
3980
+ { value: 'all', label: 'All Permissions' },
3981
+ { value: 'forced', label: 'Forced' },
3982
+ { value: 'granted', label: 'Granted' },
3983
+ { value: 'denied', label: 'Denied' },
3984
+ ];
3985
+ this.permissionValueOptions = [
3986
+ { value: 'not-forced', label: 'Not Forced' },
3987
+ { value: 'denied', label: 'Forced Denied' },
3988
+ { value: 'granted', label: 'Forced Granted' },
3989
+ ];
3990
+ }
3991
+ // Public methods
3992
+ onFilterChange(value) {
3993
+ const filter = this.filterOptions.find((f) => f.value === value);
3994
+ if (filter) {
3995
+ this.activeFilter.set(filter.value);
3996
+ }
3997
+ }
3998
+ onPermissionChange(id, value) {
3999
+ switch (value) {
4000
+ case 'not-forced':
4001
+ this.permissionsService.removePermissionOverride(id);
4002
+ break;
4003
+ case 'granted':
4004
+ this.permissionsService.setPermission(id, true);
4005
+ break;
4006
+ case 'denied':
4007
+ this.permissionsService.setPermission(id, false);
4008
+ break;
4009
+ }
4010
+ }
4011
+ onSearchChange(query) {
4012
+ this.searchQuery.set(query);
4013
+ }
4014
+ // Protected methods
4015
+ getPermissionValue(permission) {
4016
+ if (!permission.isForced)
4017
+ return '';
4018
+ return permission.isGranted ? 'granted' : 'denied';
4019
+ }
4020
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DevToolbarPermissionsToolComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4021
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.5", type: DevToolbarPermissionsToolComponent, isStandalone: true, selector: "ndt-permissions-tool", ngImport: i0, template: `
4022
+ <ndt-toolbar-tool
4023
+ [options]="options"
4024
+ title="Permissions"
4025
+ icon="lock"
4026
+ >
4027
+ <div class="container">
4028
+ <div class="header">
4029
+ <ndt-input
4030
+ [value]="searchQuery()"
4031
+ (valueChange)="onSearchChange($event)"
4032
+ placeholder="Search permissions..."
4033
+ [ariaLabel]="'Search permissions'"
4034
+ />
4035
+ <div class="filter-wrapper">
4036
+ <ndt-icon name="filter" class="filter-icon" />
4037
+ <ndt-select
4038
+ [value]="activeFilter()"
4039
+ [options]="filterOptions"
4040
+ [size]="'medium'"
4041
+ (valueChange)="onFilterChange($event)"
4042
+ [ariaLabel]="'Filter permissions by state'"
4043
+ />
4044
+ </div>
4045
+ </div>
4046
+
4047
+ @if (hasNoPermissions()) {
4048
+ <div class="empty">
4049
+ <p>No permissions found</p>
4050
+ <p class="hint">Call setAvailableOptions() to configure permissions</p>
4051
+ </div>
4052
+ } @else if (hasNoFilteredPermissions()) {
4053
+ <div class="empty">
4054
+ <p>No permissions match your filter</p>
4055
+ </div>
4056
+ } @else {
4057
+ <div class="permission-list">
4058
+ @for (permission of filteredPermissions(); track permission.id) {
4059
+ <div class="permission">
4060
+ <div class="info">
4061
+ <h3>{{ permission.name }}</h3>
4062
+ <p>{{ permission?.description }}</p>
4063
+ </div>
4064
+
4065
+ <ndt-select
4066
+ [value]="getPermissionValue(permission)"
4067
+ [options]="permissionValueOptions"
4068
+ [ariaLabel]="'Override state for ' + permission.name"
4069
+ (valueChange)="onPermissionChange(permission.id, $event ?? '')"
4070
+ size="small"
4071
+ />
4072
+ </div>
4073
+ }
4074
+ </div>
4075
+ }
4076
+ </div>
4077
+ </ndt-toolbar-tool>
4078
+ `, isInline: true, styles: [".container{display:flex;flex-direction:column;height:100%}.header{flex-shrink:0;display:flex;gap:var(--ndt-spacing-sm);margin-bottom:var(--ndt-spacing-md)}.header ndt-input{flex:.65}.header .filter-wrapper{flex:.35;display:flex;align-items:center;gap:var(--ndt-spacing-xs)}.header .filter-wrapper .filter-icon{width:18px;height:18px;flex-shrink:0;opacity:.6}.header .filter-wrapper ndt-select{flex:1}.empty{display:flex;flex-direction:column;gap:var(--ndt-spacing-md);flex:1;min-height:0;justify-content:center;align-items:center;border:1px solid var(--ndt-warning-border);border-radius:var(--ndt-border-radius-medium);padding:var(--ndt-spacing-md);background:var(--ndt-warning-background);color:var(--ndt-text-muted)}.empty p{margin:0}.empty .hint{font-size:var(--ndt-font-size-xs)}.permission-list{display:flex;flex-direction:column;gap:var(--ndt-spacing-md);flex:1;min-height:0;overflow-y:auto;padding-right:var(--ndt-spacing-sm);scrollbar-width:thin;scrollbar-color:var(--ndt-border-primary) var(--ndt-background-secondary)}.permission-list::-webkit-scrollbar{width:8px}.permission-list::-webkit-scrollbar-track{background:var(--ndt-background-secondary);border-radius:4px}.permission-list::-webkit-scrollbar-thumb{background:var(--ndt-border-primary);border-radius:4px}:is():hover{background:var(--ndt-hover-bg)}.permission{display:flex;flex-direction:row;gap:var(--ndt-spacing-sm);background:var(--ndt-background-secondary)}.permission .info{flex:0 0 65%}.permission .info h3{margin:0;font-size:var(--ndt-font-size-md);color:var(--ndt-text-primary)}.permission .info p{font-size:var(--ndt-font-size-xs);color:var(--ndt-text-muted)}.permission ndt-select{flex:0 0 35%}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "component", type: DevToolbarToolComponent, selector: "ndt-toolbar-tool", inputs: ["options", "icon", "title"] }, { kind: "component", type: DevToolbarInputComponent, selector: "ndt-input", inputs: ["value", "type", "placeholder", "ariaLabel", "inputClass"], outputs: ["valueChange"] }, { kind: "component", type: DevToolbarSelectComponent, selector: "ndt-select", inputs: ["value", "options", "ariaLabel", "label", "size"], outputs: ["valueChange"] }, { kind: "component", type: DevToolbarIconComponent, selector: "ndt-icon", inputs: ["name"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4079
+ }
4080
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DevToolbarPermissionsToolComponent, decorators: [{
4081
+ type: Component,
4082
+ args: [{ selector: 'ndt-permissions-tool', standalone: true, imports: [
4083
+ FormsModule,
4084
+ DevToolbarToolComponent,
4085
+ DevToolbarInputComponent,
4086
+ DevToolbarSelectComponent,
4087
+ DevToolbarIconComponent,
4088
+ ], template: `
4089
+ <ndt-toolbar-tool
4090
+ [options]="options"
4091
+ title="Permissions"
4092
+ icon="lock"
4093
+ >
4094
+ <div class="container">
4095
+ <div class="header">
4096
+ <ndt-input
4097
+ [value]="searchQuery()"
4098
+ (valueChange)="onSearchChange($event)"
4099
+ placeholder="Search permissions..."
4100
+ [ariaLabel]="'Search permissions'"
4101
+ />
4102
+ <div class="filter-wrapper">
4103
+ <ndt-icon name="filter" class="filter-icon" />
4104
+ <ndt-select
4105
+ [value]="activeFilter()"
4106
+ [options]="filterOptions"
4107
+ [size]="'medium'"
4108
+ (valueChange)="onFilterChange($event)"
4109
+ [ariaLabel]="'Filter permissions by state'"
4110
+ />
4111
+ </div>
4112
+ </div>
4113
+
4114
+ @if (hasNoPermissions()) {
4115
+ <div class="empty">
4116
+ <p>No permissions found</p>
4117
+ <p class="hint">Call setAvailableOptions() to configure permissions</p>
4118
+ </div>
4119
+ } @else if (hasNoFilteredPermissions()) {
4120
+ <div class="empty">
4121
+ <p>No permissions match your filter</p>
4122
+ </div>
4123
+ } @else {
4124
+ <div class="permission-list">
4125
+ @for (permission of filteredPermissions(); track permission.id) {
4126
+ <div class="permission">
4127
+ <div class="info">
4128
+ <h3>{{ permission.name }}</h3>
4129
+ <p>{{ permission?.description }}</p>
4130
+ </div>
4131
+
4132
+ <ndt-select
4133
+ [value]="getPermissionValue(permission)"
4134
+ [options]="permissionValueOptions"
4135
+ [ariaLabel]="'Override state for ' + permission.name"
4136
+ (valueChange)="onPermissionChange(permission.id, $event ?? '')"
4137
+ size="small"
4138
+ />
4139
+ </div>
4140
+ }
4141
+ </div>
4142
+ }
4143
+ </div>
4144
+ </ndt-toolbar-tool>
4145
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".container{display:flex;flex-direction:column;height:100%}.header{flex-shrink:0;display:flex;gap:var(--ndt-spacing-sm);margin-bottom:var(--ndt-spacing-md)}.header ndt-input{flex:.65}.header .filter-wrapper{flex:.35;display:flex;align-items:center;gap:var(--ndt-spacing-xs)}.header .filter-wrapper .filter-icon{width:18px;height:18px;flex-shrink:0;opacity:.6}.header .filter-wrapper ndt-select{flex:1}.empty{display:flex;flex-direction:column;gap:var(--ndt-spacing-md);flex:1;min-height:0;justify-content:center;align-items:center;border:1px solid var(--ndt-warning-border);border-radius:var(--ndt-border-radius-medium);padding:var(--ndt-spacing-md);background:var(--ndt-warning-background);color:var(--ndt-text-muted)}.empty p{margin:0}.empty .hint{font-size:var(--ndt-font-size-xs)}.permission-list{display:flex;flex-direction:column;gap:var(--ndt-spacing-md);flex:1;min-height:0;overflow-y:auto;padding-right:var(--ndt-spacing-sm);scrollbar-width:thin;scrollbar-color:var(--ndt-border-primary) var(--ndt-background-secondary)}.permission-list::-webkit-scrollbar{width:8px}.permission-list::-webkit-scrollbar-track{background:var(--ndt-background-secondary);border-radius:4px}.permission-list::-webkit-scrollbar-thumb{background:var(--ndt-border-primary);border-radius:4px}:is():hover{background:var(--ndt-hover-bg)}.permission{display:flex;flex-direction:row;gap:var(--ndt-spacing-sm);background:var(--ndt-background-secondary)}.permission .info{flex:0 0 65%}.permission .info h3{margin:0;font-size:var(--ndt-font-size-md);color:var(--ndt-text-primary)}.permission .info p{font-size:var(--ndt-font-size-xs);color:var(--ndt-text-muted)}.permission ndt-select{flex:0 0 35%}\n"] }]
4146
+ }] });
4147
+
4148
+ class DevToolbarComponent {
4149
+ constructor() {
4150
+ this.state = inject(DevToolbarStateService);
4151
+ this.destroyRef = inject(DestroyRef);
4152
+ this.settingsService = inject(SettingsService);
4153
+ this.isDevMode = isDevMode();
4154
+ this.keyboardShortcut = fromEvent(window, 'keydown')
4155
+ .pipe(filter((event) => event.ctrlKey && event.shiftKey && event.key === 'D'), takeUntilDestroyed(this.destroyRef))
4156
+ .subscribe(() => this.toggleDevTools());
4157
+ this.mouseLeave = fromEvent(document, 'mouseleave')
4158
+ .pipe(throttleTime(3000), takeUntilDestroyed(this.destroyRef))
4159
+ .subscribe(() => this.onMouseLeave());
4160
+ }
4161
+ ngOnInit() {
4162
+ const settings = this.settingsService.getSettings();
4163
+ this.state.setTheme(settings.isDarkMode ? 'dark' : 'light');
4164
+ }
4165
+ onMouseEnter() {
4166
+ this.state.setVisibility(true);
4167
+ }
4168
+ onMouseLeave() {
4169
+ setTimeout(() => this.state.setVisibility(false), this.state.delay());
4170
+ }
4171
+ toggleDevTools() {
4172
+ this.state.setVisibility(!this.state.isVisible());
4173
+ }
4174
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DevToolbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4175
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.5", type: DevToolbarComponent, isStandalone: true, selector: "ndt-toolbar", ngImport: i0, template: `
4176
+ @if (isDevMode) {
4177
+ <div
2916
4178
  aria-label="Developer tools"
2917
4179
  role="toolbar"
2918
4180
  class="dev-toolbar"
@@ -2924,10 +4186,13 @@ class DevToolbarComponent {
2924
4186
  <ndt-home-tool />
2925
4187
  <ndt-language-tool />
2926
4188
  <ndt-feature-flags-tool />
4189
+ <ndt-app-features-tool />
4190
+ <ndt-permissions-tool />
4191
+ <ndt-network-mocker-tool />
2927
4192
  <ng-content />
2928
4193
  </div>
2929
4194
  }
2930
- `, isInline: true, styles: [".dev-toolbar{--ndt-border-radius-small: 4px;--ndt-border-radius-medium: 8px;--ndt-border-radius-large: 12px;--ndt-transition-default: all .2s ease-out;--ndt-transition-smooth: all .2s ease-in-out;--ndt-bg-primary: rgb(255, 255, 255);--ndt-bg-gradient: linear-gradient(180deg, rgb(243, 244, 246) 0%, rgba(243, 244, 246, .88) 100%);--ndt-text-primary: rgb(17, 24, 39);--ndt-text-secondary: rgb(55, 65, 81);--ndt-text-muted: rgb(107, 114, 128);--ndt-border-primary: #e5e7eb;--ndt-border-subtle: rgba(17, 24, 39, .1);--ndt-hover-bg: rgba(17, 24, 39, .05);--ndt-hover-danger: rgb(239, 68, 68);--ndt-shadow-toolbar: 0 2px 8px rgba(156, 163, 175, .2);--ndt-shadow-tooltip: 0 0 0 1px rgba(17, 24, 39, .05), 0 4px 8px rgba(107, 114, 128, .15), 0 2px 4px rgba(107, 114, 128, .1);--ndt-shadow-window: 0px 0px 0px 0px rgba(156, 163, 175, .1), 0px 1px 2px 0px rgba(156, 163, 175, .12), 0px 4px 4px 0px rgba(156, 163, 175, .1), 0px 10px 6px 0px rgba(156, 163, 175, .08), 0px 17px 7px 0px rgba(156, 163, 175, .05), 0px 26px 7px 0px rgba(156, 163, 175, .02);--ndt-spacing-xs: 4px;--ndt-spacing-sm: 8px;--ndt-spacing-md: 16px;--ndt-window-padding: 24px;--ndt-font-size-xs: .75rem;--ndt-font-size-sm: .875rem;--ndt-font-size-md: 1rem;--ndt-font-size-lg: 1.25rem;--ndt-font-size-xl: 2rem;--ndt-background-secondary: var(--ndt-bg-primary);--ndt-background-hover: var(--ndt-hover-bg);--ndt-primary: #df30d4;--ndt-primary-rgb: 223, 48, 212;--ndt-text-on-primary: rgb(255, 255, 255);--ndt-border-color: var(--ndt-border-primary);--ndt-note-background: rgb(219, 234, 254);--ndt-note-border: rgba(37, 99, 235, .2);--ndt-warning-background: rgb(254, 249, 195);--ndt-warning-border: rgba(202, 138, 4, .2);--ndt-error-background: rgb(254, 226, 226);--ndt-error-border: rgba(220, 38, 38, .2)}.dev-toolbar[data-theme=dark]{--ndt-bg-primary: rgb(17, 24, 39);--ndt-bg-gradient: linear-gradient(180deg, rgb(19, 21, 26) 0%, rgba(19, 21, 26, .88) 100%);--ndt-text-primary: rgb(255, 255, 255);--ndt-text-secondary: rgb(229, 231, 235);--ndt-text-muted: rgb(156, 163, 175);--ndt-border-primary: #343841;--ndt-border-subtle: rgba(255, 255, 255, .1);--ndt-hover-bg: rgba(255, 255, 255, .12);--ndt-hover-danger: rgb(220, 38, 38);--ndt-shadow-toolbar: 0 2px 8px rgba(19, 21, 26, .3);--ndt-shadow-tooltip: 0 0 0 1px rgba(255, 255, 255, .1), 0 4px 8px rgba(0, 0, 0, .4), 0 2px 4px rgba(0, 0, 0, .3);--ndt-shadow-window: 0px 0px 0px 0px rgba(19, 21, 26, .3), 0px 1px 2px 0px rgba(19, 21, 26, .29), 0px 4px 4px 0px rgba(19, 21, 26, .26), 0px 10px 6px 0px rgba(19, 21, 26, .15), 0px 17px 7px 0px rgba(19, 21, 26, .04), 0px 26px 7px 0px rgba(19, 21, 26, .01);--ndt-note-background: rgba(37, 99, 235, .15);--ndt-note-border: rgba(37, 99, 235, .3);--ndt-warning-background: rgba(202, 138, 4, .15);--ndt-warning-border: rgba(202, 138, 4, .3);--ndt-error-background: rgba(220, 38, 38, .15);--ndt-error-border: rgba(220, 38, 38, .3)}.dev-toolbar h1,.dev-toolbar h2,.dev-toolbar h3,.dev-toolbar h4,.dev-toolbar h5{font-weight:600;color:var(--ndt-text-primary);margin:0}.dev-toolbar h1{font-size:var(--ndt-font-size-xl)}.dev-toolbar h2{font-size:var(--ndt-font-size-lg)}.dev-toolbar h3{font-size:var(--ndt-font-size-md)}.dev-toolbar h4{font-size:var(--ndt-font-size-sm)}.dev-toolbar h5{font-size:var(--ndt-font-size-xs)}.dev-toolbar hr{border:1px solid var(--ndt-border-subtle);margin:1em 0}.dev-toolbar p{line-height:1.5em;margin:0}.dev-toolbar{position:fixed;bottom:0;left:50%;z-index:999999;transform:translate(-50%);display:flex;pointer-events:auto;background:var(--ndt-bg-primary);border:1px solid var(--ndt-border-primary);border-radius:9999px;box-shadow:var(--ndt-shadow-toolbar);height:40px;overflow:hidden}.dev-toolbar--active{opacity:1}\n"], dependencies: [{ kind: "component", type: DevToolbarHomeToolComponent, selector: "ndt-home-tool", inputs: ["badge"] }, { kind: "component", type: DevToolbarLanguageToolComponent, selector: "ndt-language-tool" }, { kind: "component", type: DevToolbarFeatureFlagsToolComponent, selector: "ndt-feature-flags-tool" }], animations: [
4195
+ `, isInline: true, styles: [".dev-toolbar{--ndt-border-radius-small: 4px;--ndt-border-radius-medium: 8px;--ndt-border-radius-large: 12px;--ndt-transition-default: all .2s ease-out;--ndt-transition-smooth: all .2s ease-in-out;--ndt-bg-primary: rgb(255, 255, 255);--ndt-bg-gradient: linear-gradient(180deg, rgb(243, 244, 246) 0%, rgba(243, 244, 246, .88) 100%);--ndt-text-primary: rgb(17, 24, 39);--ndt-text-secondary: rgb(55, 65, 81);--ndt-text-muted: rgb(107, 114, 128);--ndt-border-primary: #e5e7eb;--ndt-border-subtle: rgba(17, 24, 39, .1);--ndt-hover-bg: rgba(17, 24, 39, .05);--ndt-hover-danger: rgb(239, 68, 68);--ndt-shadow-toolbar: 0 2px 8px rgba(156, 163, 175, .2);--ndt-shadow-tooltip: 0 0 0 1px rgba(17, 24, 39, .05), 0 4px 8px rgba(107, 114, 128, .15), 0 2px 4px rgba(107, 114, 128, .1);--ndt-shadow-window: 0px 0px 0px 0px rgba(156, 163, 175, .1), 0px 1px 2px 0px rgba(156, 163, 175, .12), 0px 4px 4px 0px rgba(156, 163, 175, .1), 0px 10px 6px 0px rgba(156, 163, 175, .08), 0px 17px 7px 0px rgba(156, 163, 175, .05), 0px 26px 7px 0px rgba(156, 163, 175, .02);--ndt-spacing-xs: 4px;--ndt-spacing-sm: 8px;--ndt-spacing-md: 16px;--ndt-window-padding: 24px;--ndt-font-size-xs: .75rem;--ndt-font-size-sm: .875rem;--ndt-font-size-md: 1rem;--ndt-font-size-lg: 1.25rem;--ndt-font-size-xl: 2rem;--ndt-background-secondary: var(--ndt-bg-primary);--ndt-background-hover: var(--ndt-hover-bg);--ndt-primary: #df30d4;--ndt-primary-rgb: 223, 48, 212;--ndt-text-on-primary: rgb(255, 255, 255);--ndt-border-color: var(--ndt-border-primary);--ndt-note-background: rgb(219, 234, 254);--ndt-note-border: rgba(37, 99, 235, .2);--ndt-warning-background: rgb(254, 249, 195);--ndt-warning-border: rgba(202, 138, 4, .2);--ndt-error-background: rgb(254, 226, 226);--ndt-error-border: rgba(220, 38, 38, .2)}.dev-toolbar[data-theme=dark]{--ndt-bg-primary: rgb(17, 24, 39);--ndt-bg-gradient: linear-gradient(180deg, rgb(19, 21, 26) 0%, rgba(19, 21, 26, .88) 100%);--ndt-text-primary: rgb(255, 255, 255);--ndt-text-secondary: rgb(229, 231, 235);--ndt-text-muted: rgb(156, 163, 175);--ndt-border-primary: #343841;--ndt-border-subtle: rgba(255, 255, 255, .1);--ndt-hover-bg: rgba(255, 255, 255, .12);--ndt-hover-danger: rgb(220, 38, 38);--ndt-shadow-toolbar: 0 2px 8px rgba(19, 21, 26, .3);--ndt-shadow-tooltip: 0 0 0 1px rgba(255, 255, 255, .1), 0 4px 8px rgba(0, 0, 0, .4), 0 2px 4px rgba(0, 0, 0, .3);--ndt-shadow-window: 0px 0px 0px 0px rgba(19, 21, 26, .3), 0px 1px 2px 0px rgba(19, 21, 26, .29), 0px 4px 4px 0px rgba(19, 21, 26, .26), 0px 10px 6px 0px rgba(19, 21, 26, .15), 0px 17px 7px 0px rgba(19, 21, 26, .04), 0px 26px 7px 0px rgba(19, 21, 26, .01);--ndt-note-background: rgba(37, 99, 235, .15);--ndt-note-border: rgba(37, 99, 235, .3);--ndt-warning-background: rgba(202, 138, 4, .15);--ndt-warning-border: rgba(202, 138, 4, .3);--ndt-error-background: rgba(220, 38, 38, .15);--ndt-error-border: rgba(220, 38, 38, .3)}.dev-toolbar h1,.dev-toolbar h2,.dev-toolbar h3,.dev-toolbar h4,.dev-toolbar h5{font-weight:600;color:var(--ndt-text-primary);margin:0}.dev-toolbar h1{font-size:var(--ndt-font-size-xl)}.dev-toolbar h2{font-size:var(--ndt-font-size-lg)}.dev-toolbar h3{font-size:var(--ndt-font-size-md)}.dev-toolbar h4{font-size:var(--ndt-font-size-sm)}.dev-toolbar h5{font-size:var(--ndt-font-size-xs)}.dev-toolbar hr{border:1px solid var(--ndt-border-subtle);margin:1em 0}.dev-toolbar p{line-height:1.5em;margin:0}.dev-toolbar{position:fixed;bottom:0;left:50%;z-index:999999;transform:translate(-50%);display:flex;pointer-events:auto;background:var(--ndt-bg-primary);border:1px solid var(--ndt-border-primary);border-radius:9999px;box-shadow:var(--ndt-shadow-toolbar);height:40px;overflow:hidden}.dev-toolbar--active{opacity:1}\n"], dependencies: [{ kind: "component", type: DevToolbarHomeToolComponent, selector: "ndt-home-tool", inputs: ["badge"] }, { kind: "component", type: DevToolbarLanguageToolComponent, selector: "ndt-language-tool" }, { kind: "component", type: DevToolbarFeatureFlagsToolComponent, selector: "ndt-feature-flags-tool" }, { kind: "component", type: DevToolbarAppFeaturesToolComponent, selector: "ndt-app-features-tool" }, { kind: "component", type: DevToolbarNetworkMockerToolComponent, selector: "ndt-network-mocker-tool" }, { kind: "component", type: DevToolbarPermissionsToolComponent, selector: "ndt-permissions-tool" }], animations: [
2931
4196
  trigger('toolbarState', [
2932
4197
  state('hidden', style({
2933
4198
  transform: 'translate(-50%, calc(100% + -1.2rem))',
@@ -2947,6 +4212,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImpor
2947
4212
  DevToolbarHomeToolComponent,
2948
4213
  DevToolbarLanguageToolComponent,
2949
4214
  DevToolbarFeatureFlagsToolComponent,
4215
+ DevToolbarAppFeaturesToolComponent,
4216
+ DevToolbarNetworkMockerToolComponent,
4217
+ DevToolbarPermissionsToolComponent,
2950
4218
  ], template: `
2951
4219
  @if (isDevMode) {
2952
4220
  <div
@@ -2961,6 +4229,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImpor
2961
4229
  <ndt-home-tool />
2962
4230
  <ndt-language-tool />
2963
4231
  <ndt-feature-flags-tool />
4232
+ <ndt-app-features-tool />
4233
+ <ndt-permissions-tool />
4234
+ <ndt-network-mocker-tool />
2964
4235
  <ng-content />
2965
4236
  </div>
2966
4237
  }
@@ -3031,9 +4302,269 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImpor
3031
4302
  args: [{ providedIn: 'root' }]
3032
4303
  }] });
3033
4304
 
4305
+ /**
4306
+ * Public service for managing app features in the dev toolbar.
4307
+ *
4308
+ * This service implements the DevToolsService interface and provides methods for:
4309
+ * - Configuring available product features
4310
+ * - Retrieving forced feature overrides
4311
+ * - Applying preset feature configurations
4312
+ * - Exporting current forced state for presets
4313
+ *
4314
+ * @example
4315
+ * ```typescript
4316
+ * import { DevToolbarAppFeaturesService } from 'ngx-dev-toolbar';
4317
+ *
4318
+ * @Component({...})
4319
+ * export class AppComponent implements OnInit {
4320
+ * private appFeaturesService = inject(DevToolbarAppFeaturesService);
4321
+ *
4322
+ * ngOnInit() {
4323
+ * // Configure available features based on current tier
4324
+ * const features: DevToolbarAppFeature[] = [
4325
+ * { id: 'analytics', name: 'Analytics Dashboard', isEnabled: true, isForced: false },
4326
+ * { id: 'multi-user', name: 'Multi-User Support', isEnabled: false, isForced: false }
4327
+ * ];
4328
+ * this.appFeaturesService.setAvailableOptions(features);
4329
+ *
4330
+ * // Subscribe to forced overrides
4331
+ * this.appFeaturesService.getForcedValues().subscribe(forcedFeatures => {
4332
+ * forcedFeatures.forEach(feature => {
4333
+ * // Apply forced feature state to application logic
4334
+ * this.applyFeatureState(feature.id, feature.isEnabled);
4335
+ * });
4336
+ * });
4337
+ * }
4338
+ * }
4339
+ * ```
4340
+ */
4341
+ class DevToolbarAppFeaturesService {
4342
+ constructor() {
4343
+ this.internalService = inject(DevToolbarInternalAppFeaturesService);
4344
+ }
4345
+ /**
4346
+ * Set available app features for the application.
4347
+ *
4348
+ * Configures the list of product features that can be toggled in the dev toolbar.
4349
+ * Features should represent product-level capabilities like license tiers,
4350
+ * deployment configurations, or environment flags.
4351
+ *
4352
+ * @param features - Array of app features to display in the toolbar
4353
+ * @throws Error if duplicate feature IDs or empty IDs are detected
4354
+ *
4355
+ * @example
4356
+ * ```typescript
4357
+ * const features: DevToolbarAppFeature[] = [
4358
+ * {
4359
+ * id: 'advanced-analytics',
4360
+ * name: 'Advanced Analytics Dashboard',
4361
+ * description: 'Access premium reporting and data visualization',
4362
+ * isEnabled: false, // Not available in current tier
4363
+ * isForced: false
4364
+ * },
4365
+ * {
4366
+ * id: 'multi-user-support',
4367
+ * name: 'Multi-User Collaboration',
4368
+ * description: 'Enable team features and user management',
4369
+ * isEnabled: true, // Available in current tier
4370
+ * isForced: false
4371
+ * }
4372
+ * ];
4373
+ * this.appFeaturesService.setAvailableOptions(features);
4374
+ * ```
4375
+ */
4376
+ setAvailableOptions(features) {
4377
+ this.internalService.setAppFeatures(features);
4378
+ }
4379
+ /**
4380
+ * Get observable stream of features that have forced overrides.
4381
+ *
4382
+ * Emits an array of features that have been forced via the dev toolbar.
4383
+ * Only features with `isForced = true` are included in the emissions.
4384
+ * Use this to react to feature override changes and update application behavior.
4385
+ *
4386
+ * @returns Observable that emits array of forced features whenever state changes
4387
+ *
4388
+ * @example
4389
+ * ```typescript
4390
+ * this.appFeaturesService.getForcedValues()
4391
+ * .pipe(takeUntilDestroyed())
4392
+ * .subscribe(forcedFeatures => {
4393
+ * forcedFeatures.forEach(feature => {
4394
+ * if (feature.isEnabled) {
4395
+ * this.enableFeature(feature.id);
4396
+ * } else {
4397
+ * this.disableFeature(feature.id);
4398
+ * }
4399
+ * });
4400
+ * });
4401
+ * ```
4402
+ */
4403
+ getForcedValues() {
4404
+ return this.internalService.getForcedFeatures();
4405
+ }
4406
+ /**
4407
+ * Apply a preset feature configuration (for preset tool integration).
4408
+ *
4409
+ * Accepts a forced features state object and applies it to the current configuration.
4410
+ * Invalid feature IDs (not in configured features) are filtered out with a warning.
4411
+ *
4412
+ * @param state - Forced features state containing enabled/disabled arrays
4413
+ *
4414
+ * @example
4415
+ * ```typescript
4416
+ * // Apply "Enterprise Tier" preset
4417
+ * const enterprisePreset: ForcedAppFeaturesState = {
4418
+ * enabled: ['analytics', 'multi-user', 'white-label', 'sso'],
4419
+ * disabled: []
4420
+ * };
4421
+ * this.appFeaturesService.applyPresetFeatures(enterprisePreset);
4422
+ *
4423
+ * // Apply "Basic Tier" preset
4424
+ * const basicPreset: ForcedAppFeaturesState = {
4425
+ * enabled: [],
4426
+ * disabled: ['analytics', 'multi-user', 'white-label', 'sso']
4427
+ * };
4428
+ * this.appFeaturesService.applyPresetFeatures(basicPreset);
4429
+ * ```
4430
+ */
4431
+ applyPresetFeatures(state) {
4432
+ this.internalService.applyForcedState(state);
4433
+ }
4434
+ /**
4435
+ * Get current forced feature state as a snapshot (for preset export).
4436
+ *
4437
+ * Returns the current forced features state with enabled/disabled arrays.
4438
+ * Useful for exporting toolbar state to save as a preset or for debugging.
4439
+ * Returns a defensive copy - mutations will not affect internal state.
4440
+ *
4441
+ * @returns Current forced features state
4442
+ *
4443
+ * @example
4444
+ * ```typescript
4445
+ * // Export current toolbar state to save as preset
4446
+ * const currentState = this.appFeaturesService.getCurrentForcedState();
4447
+ * this.presetsService.savePreset('my-config', {
4448
+ * appFeatures: currentState,
4449
+ * // ... other tool states
4450
+ * });
4451
+ *
4452
+ * console.log(currentState);
4453
+ * // { enabled: ['analytics'], disabled: ['white-label'] }
4454
+ * ```
4455
+ */
4456
+ getCurrentForcedState() {
4457
+ return this.internalService.getCurrentForcedState();
4458
+ }
4459
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DevToolbarAppFeaturesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
4460
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DevToolbarAppFeaturesService, providedIn: 'root' }); }
4461
+ }
4462
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DevToolbarAppFeaturesService, decorators: [{
4463
+ type: Injectable,
4464
+ args: [{ providedIn: 'root' }]
4465
+ }] });
4466
+
4467
+ /**
4468
+ * Public service for integrating the Permissions Tool with your application.
4469
+ * Use this service to:
4470
+ * 1. Register your application's permissions with setAvailableOptions()
4471
+ * 2. Listen for toolbar permission overrides with getForcedValues()
4472
+ * 3. Apply preset permission states for testing with applyPreset()
4473
+ *
4474
+ * @example
4475
+ * ```typescript
4476
+ * constructor(private permissionsService: DevToolbarPermissionsService) {
4477
+ * // Register permissions
4478
+ * this.permissionsService.setAvailableOptions([
4479
+ * { id: 'can-edit', name: 'Can Edit', isGranted: false, isForced: false }
4480
+ * ]);
4481
+ *
4482
+ * // Listen for overrides
4483
+ * this.permissionsService.getForcedValues().subscribe(forcedPermissions => {
4484
+ * // Update your app's permission state
4485
+ * });
4486
+ * }
4487
+ * ```
4488
+ */
4489
+ class DevToolbarPermissionsService {
4490
+ constructor() {
4491
+ this.internalService = inject(DevToolbarInternalPermissionsService);
4492
+ }
4493
+ /**
4494
+ * Sets the available permissions that will be displayed in the toolbar tool.
4495
+ * Call this in your app initialization to register permissions.
4496
+ *
4497
+ * @param permissions Array of permissions to display in the toolbar
4498
+ *
4499
+ * @example
4500
+ * ```typescript
4501
+ * this.permissionsService.setAvailableOptions([
4502
+ * {
4503
+ * id: 'can-edit-posts',
4504
+ * name: 'Edit Posts',
4505
+ * description: 'Can edit blog posts',
4506
+ * isGranted: false,
4507
+ * isForced: false
4508
+ * }
4509
+ * ]);
4510
+ * ```
4511
+ */
4512
+ setAvailableOptions(permissions) {
4513
+ this.internalService.setAppPermissions(permissions);
4514
+ }
4515
+ /**
4516
+ * Gets an observable of permissions that were forced/overridden through the toolbar.
4517
+ * Subscribe to this to update your application's permission state.
4518
+ *
4519
+ * @returns Observable emitting array of forced permissions whenever changes occur
4520
+ *
4521
+ * @example
4522
+ * ```typescript
4523
+ * this.permissionsService.getForcedValues().subscribe(forcedPermissions => {
4524
+ * forcedPermissions.forEach(permission => {
4525
+ * this.updatePermission(permission.id, permission.isGranted);
4526
+ * });
4527
+ * });
4528
+ * ```
4529
+ */
4530
+ getForcedValues() {
4531
+ return this.internalService.getForcedPermissions();
4532
+ }
4533
+ /**
4534
+ * Apply a preset permission state. Useful for automated testing scenarios.
4535
+ *
4536
+ * @param state The forced permissions state to apply
4537
+ *
4538
+ * @example
4539
+ * ```typescript
4540
+ * this.permissionsService.applyPreset({
4541
+ * granted: ['can-edit-posts'],
4542
+ * denied: ['can-delete-posts']
4543
+ * });
4544
+ * ```
4545
+ */
4546
+ applyPreset(state) {
4547
+ this.internalService.applyPresetPermissions(state);
4548
+ }
4549
+ /**
4550
+ * Get the current forced permission state.
4551
+ *
4552
+ * @returns Current forced permissions state
4553
+ */
4554
+ getCurrentState() {
4555
+ return this.internalService.getCurrentForcedState();
4556
+ }
4557
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DevToolbarPermissionsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
4558
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DevToolbarPermissionsService, providedIn: 'root' }); }
4559
+ }
4560
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DevToolbarPermissionsService, decorators: [{
4561
+ type: Injectable,
4562
+ args: [{ providedIn: 'root' }]
4563
+ }] });
4564
+
3034
4565
  /**
3035
4566
  * Generated bundle index. Do not edit.
3036
4567
  */
3037
4568
 
3038
- export { DevToolbarComponent, DevToolbarFeatureFlagService, DevToolbarIconComponent, DevToolbarLanguageService, DevToolbarToolComponent };
4569
+ export { DevToolbarAppFeaturesService, DevToolbarAppFeaturesToolComponent, DevToolbarComponent, DevToolbarFeatureFlagService, DevToolbarIconComponent, DevToolbarLanguageService, DevToolbarNetworkMockerService, DevToolbarNetworkMockerToolComponent, DevToolbarPermissionsService, DevToolbarPermissionsToolComponent, DevToolbarToolComponent };
3039
4570
  //# sourceMappingURL=ngx-dev-toolbar.mjs.map