ngx-dev-toolbar 1.0.0-beta.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/README.md +254 -4
  2. package/eslint.config.cjs +47 -0
  3. package/ng-package.json +7 -0
  4. package/package.json +8 -23
  5. package/project.json +37 -0
  6. package/src/components/button/button.component.scss +49 -0
  7. package/src/components/button/button.component.ts +36 -0
  8. package/src/components/card/card.component.scss +18 -0
  9. package/src/components/card/card.component.ts +30 -0
  10. package/src/components/clickable-card/clickable-card.component.scss +39 -0
  11. package/src/components/clickable-card/clickable-card.component.ts +34 -0
  12. package/src/components/icons/angular-icon.component.ts +35 -0
  13. package/src/components/icons/bug-icon.component.ts +23 -0
  14. package/src/components/icons/code-icon.component.ts +24 -0
  15. package/src/components/icons/database-icon.component.ts +27 -0
  16. package/src/components/icons/discord-icon.component.ts +23 -0
  17. package/src/components/icons/docs-icon.component.ts +23 -0
  18. package/src/components/icons/export-icon.component.ts +23 -0
  19. package/src/components/icons/gauge-icon.component.ts +27 -0
  20. package/src/components/icons/gear-icon.component.ts +27 -0
  21. package/src/components/icons/git-branch-icon.component.ts +27 -0
  22. package/src/components/icons/icon.component.ts +129 -0
  23. package/src/components/icons/icon.models.ts +27 -0
  24. package/src/components/icons/import-icon.component.ts +23 -0
  25. package/src/components/icons/layout-icon.component.ts +24 -0
  26. package/src/components/icons/lightbulb-icon.component.ts +23 -0
  27. package/src/components/icons/lighting-icon.component.ts +24 -0
  28. package/src/components/icons/moon-icon.component.ts +27 -0
  29. package/src/components/icons/network-icon.component.ts +27 -0
  30. package/src/components/icons/puzzle-icon.component.ts +27 -0
  31. package/src/components/icons/refresh-icon.component.ts +27 -0
  32. package/src/components/icons/star-icon.component.ts +27 -0
  33. package/src/components/icons/sun-icon.component.ts +27 -0
  34. package/src/components/icons/terminal-icon.component.ts +27 -0
  35. package/src/components/icons/toggle-left-icon.component.ts +27 -0
  36. package/src/components/icons/translate-icon.component.ts +23 -0
  37. package/src/components/icons/trash-icon.component.ts +23 -0
  38. package/src/components/icons/users-icon.component.ts +27 -0
  39. package/src/components/input/input.component.ts +67 -0
  40. package/src/components/link-button/link-button.component.scss +36 -0
  41. package/src/components/link-button/link-button.component.ts +29 -0
  42. package/src/components/select/select.component.scss +162 -0
  43. package/src/components/select/select.component.ts +127 -0
  44. package/src/components/tool-button/tool-button.component.scss +67 -0
  45. package/src/components/tool-button/tool-button.component.ts +126 -0
  46. package/src/components/toolbar-tool/toolbar-tool.component.scss +9 -0
  47. package/src/components/toolbar-tool/toolbar-tool.component.ts +169 -0
  48. package/src/components/toolbar-tool/toolbar-tool.models.ts +33 -0
  49. package/src/components/window/window.component.scss +95 -0
  50. package/src/components/window/window.component.ts +69 -0
  51. package/src/dev-toolbar-state.service.ts +89 -0
  52. package/src/dev-toolbar.component.scss +22 -0
  53. package/src/dev-toolbar.component.ts +105 -0
  54. package/src/index.ts +10 -0
  55. package/src/models/dev-tools.interface.ts +19 -0
  56. package/src/styles.scss +342 -0
  57. package/src/test-setup.ts +12 -0
  58. package/src/tools/feature-flags-tool/feature-flags-internal.service.ts +96 -0
  59. package/src/tools/feature-flags-tool/feature-flags-tool.component.ts +261 -0
  60. package/src/tools/feature-flags-tool/feature-flags.models.ts +10 -0
  61. package/src/tools/feature-flags-tool/feature-flags.service.ts +28 -0
  62. package/src/tools/home-tool/home-tool.component.scss +67 -0
  63. package/src/tools/home-tool/home-tool.component.ts +197 -0
  64. package/{tools/settings-tool/settings.models.d.ts → src/tools/home-tool/settings.models.ts} +1 -1
  65. package/src/tools/home-tool/settings.service.spec.ts +59 -0
  66. package/src/tools/home-tool/settings.service.ts +21 -0
  67. package/src/tools/language-tool/language-internal.service.ts +51 -0
  68. package/src/tools/language-tool/language-tool.component.scss +7 -0
  69. package/src/tools/language-tool/language-tool.component.ts +71 -0
  70. package/src/tools/language-tool/language.models.ts +4 -0
  71. package/src/tools/language-tool/language.service.ts +26 -0
  72. package/src/utils/storage.service.spec.ts +179 -0
  73. package/src/utils/storage.service.ts +80 -0
  74. package/tsconfig.json +28 -0
  75. package/tsconfig.lib.json +28 -0
  76. package/tsconfig.lib.prod.json +9 -0
  77. package/tsconfig.spec.json +29 -0
  78. package/vite.config.mts +27 -0
  79. package/components/button/button.component.d.ts +0 -12
  80. package/components/icons/angular-icon.component.d.ts +0 -5
  81. package/components/icons/bug-icon.component.d.ts +0 -6
  82. package/components/icons/code-icon.component.d.ts +0 -6
  83. package/components/icons/database-icon.component.d.ts +0 -6
  84. package/components/icons/gauge-icon.component.d.ts +0 -6
  85. package/components/icons/gear-icon.component.d.ts +0 -6
  86. package/components/icons/git-branch-icon.component.d.ts +0 -6
  87. package/components/icons/icon.component.d.ts +0 -9
  88. package/components/icons/icon.models.d.ts +0 -1
  89. package/components/icons/layout-icon.component.d.ts +0 -6
  90. package/components/icons/lighting-icon.component.d.ts +0 -6
  91. package/components/icons/moon-icon.component.d.ts +0 -6
  92. package/components/icons/network-icon.component.d.ts +0 -6
  93. package/components/icons/puzzle-icon.component.d.ts +0 -6
  94. package/components/icons/refresh-icon.component.d.ts +0 -6
  95. package/components/icons/star-icon.component.d.ts +0 -6
  96. package/components/icons/sun-icon.component.d.ts +0 -6
  97. package/components/icons/terminal-icon.component.d.ts +0 -6
  98. package/components/icons/toggle-left-icon.component.d.ts +0 -6
  99. package/components/icons/users-icon.component.d.ts +0 -6
  100. package/components/input/input.component.d.ts +0 -10
  101. package/components/select/select.component.d.ts +0 -14
  102. package/components/tool-button/tool-button.component.d.ts +0 -23
  103. package/components/toolbar-tool/toolbar-tool.component.d.ts +0 -28
  104. package/components/window/window.component.d.ts +0 -16
  105. package/components/window/window.models.d.ts +0 -20
  106. package/dev-toolbar-state.service.d.ts +0 -18
  107. package/dev-toolbar.component.d.ts +0 -17
  108. package/fesm2022/ngx-dev-toolbar.mjs +0 -2073
  109. package/fesm2022/ngx-dev-toolbar.mjs.map +0 -1
  110. package/index.d.ts +0 -3
  111. package/tools/feature-flags-tool/feature-flags-tool.component.d.ts +0 -33
  112. package/tools/feature-flags-tool/feature-flags.models.d.ts +0 -9
  113. package/tools/feature-flags-tool/feature-flags.service.d.ts +0 -35
  114. package/tools/settings-tool/settings-tool.component.d.ts +0 -15
  115. package/tools/settings-tool/settings.service.d.ts +0 -10
  116. package/utils/storage.service.d.ts +0 -9
@@ -0,0 +1,105 @@
1
+ import {
2
+ animate,
3
+ state,
4
+ style,
5
+ transition,
6
+ trigger,
7
+ } from '@angular/animations';
8
+ import {
9
+ Component,
10
+ DestroyRef,
11
+ OnInit,
12
+ inject,
13
+ isDevMode,
14
+ } from '@angular/core';
15
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
16
+ import { fromEvent } from 'rxjs';
17
+ import { filter, throttleTime } from 'rxjs/operators';
18
+ import { DevToolbarStateService } from './dev-toolbar-state.service';
19
+ import { DevToolbarFeatureFlagsToolComponent } from './tools/feature-flags-tool/feature-flags-tool.component';
20
+ import { DevToolbarHomeToolComponent } from './tools/home-tool/home-tool.component';
21
+ import { SettingsService } from './tools/home-tool/settings.service';
22
+ import { DevToolbarLanguageToolComponent } from './tools/language-tool/language-tool.component';
23
+
24
+ @Component({
25
+ standalone: true,
26
+ selector: 'ndt-toolbar',
27
+ styleUrls: ['./dev-toolbar.component.scss'],
28
+ imports: [
29
+ DevToolbarHomeToolComponent,
30
+ DevToolbarLanguageToolComponent,
31
+ DevToolbarFeatureFlagsToolComponent,
32
+ ],
33
+ template: `
34
+ @if (isDevMode) {
35
+ <div
36
+ aria-label="Developer tools"
37
+ role="toolbar"
38
+ class="dev-toolbar"
39
+ [@toolbarState]="state.isVisible() ? 'visible' : 'hidden'"
40
+ [attr.data-theme]="state.theme()"
41
+ [class.dev-toolbar--active]="state.isVisible()"
42
+ (mouseenter)="onMouseEnter()"
43
+ >
44
+ <ndt-home-tool />
45
+ <ndt-language-tool />
46
+ <ndt-feature-flags-tool />
47
+ <ng-content />
48
+ </div>
49
+ }
50
+ `,
51
+ animations: [
52
+ trigger('toolbarState', [
53
+ state(
54
+ 'hidden',
55
+ style({
56
+ transform: 'translate(-50%, calc(100% + -1.2rem))',
57
+ })
58
+ ),
59
+ state(
60
+ 'visible',
61
+ style({
62
+ transform: 'translate(-50%, -1rem)',
63
+ })
64
+ ),
65
+ transition('hidden <=> visible', [
66
+ animate('300ms cubic-bezier(0.4, 0, 0.2, 1)'),
67
+ ]),
68
+ ]),
69
+ ],
70
+ })
71
+ export class DevToolbarComponent implements OnInit {
72
+ state = inject(DevToolbarStateService);
73
+ destroyRef = inject(DestroyRef);
74
+ settingsService = inject(SettingsService);
75
+
76
+ isDevMode = isDevMode();
77
+
78
+ private keyboardShortcut = fromEvent<KeyboardEvent>(window, 'keydown')
79
+ .pipe(
80
+ filter((event) => event.ctrlKey && event.shiftKey && event.key === 'D'),
81
+ takeUntilDestroyed(this.destroyRef)
82
+ )
83
+ .subscribe(() => this.toggleDevTools());
84
+
85
+ private mouseLeave = fromEvent<MouseEvent>(document, 'mouseleave')
86
+ .pipe(throttleTime(3000), takeUntilDestroyed(this.destroyRef))
87
+ .subscribe(() => this.onMouseLeave());
88
+
89
+ ngOnInit(): void {
90
+ const settings = this.settingsService.getSettings();
91
+ this.state.setTheme(settings.isDarkMode ? 'dark' : 'light');
92
+ }
93
+
94
+ onMouseEnter(): void {
95
+ this.state.setVisibility(true);
96
+ }
97
+
98
+ onMouseLeave(): void {
99
+ setTimeout(() => this.state.setVisibility(false), this.state.delay());
100
+ }
101
+
102
+ private toggleDevTools(): void {
103
+ this.state.setVisibility(!this.state.isVisible());
104
+ }
105
+ }
package/src/index.ts ADDED
@@ -0,0 +1,10 @@
1
+ export * from './components/icons/icon.component';
2
+ export * from './components/icons/icon.models';
3
+ export * from './components/toolbar-tool/toolbar-tool.component';
4
+ export * from './components/toolbar-tool/toolbar-tool.models';
5
+ export * from './dev-toolbar.component';
6
+ export * from './models/dev-tools.interface';
7
+ export * from './tools/feature-flags-tool/feature-flags.models';
8
+ export * from './tools/feature-flags-tool/feature-flags.service';
9
+ export * from './tools/language-tool/language.models';
10
+ export * from './tools/language-tool/language.service';
@@ -0,0 +1,19 @@
1
+ import { Observable } from 'rxjs';
2
+
3
+ /**
4
+ * Interface that should be implemented by any tool service that is used in the dev toolbar
5
+ */
6
+ export interface DevToolsService<OptionType> {
7
+ /**
8
+ * Sets the available options that will be displayed in the tool on the dev toolbar
9
+ * @param options The options to be displayed
10
+ */
11
+ setAvailableOptions(options: OptionType[]): void;
12
+
13
+ /**
14
+ * Gets the values that were forced/modified through the tool on the dev toolbar.
15
+ * If the tool only supports a single option, the returned array will have a single element.
16
+ * @returns Observable of forced values array
17
+ */
18
+ getForcedValues(): Observable<OptionType[]>;
19
+ }
@@ -0,0 +1,342 @@
1
+ @use 'sass:map';
2
+ // Color primitives
3
+ $color-black-900: rgb(17, 24, 39); // darkest
4
+ $color-black-800: rgb(19, 21, 26);
5
+ $color-gray-700: rgb(55, 65, 81);
6
+ $color-gray-600: rgb(107, 114, 128);
7
+ $color-gray-500: rgb(156, 163, 175);
8
+ $color-gray-300: rgb(229, 231, 235);
9
+ $color-gray-200: rgb(243, 244, 246);
10
+ $color-white: rgb(255, 255, 255);
11
+ $color-red-600: rgb(220, 38, 38); // dark red
12
+ $color-red-500: rgb(239, 68, 68); // light red
13
+ $color-blue-100: rgb(219, 234, 254);
14
+ $color-blue-600: rgb(37, 99, 235);
15
+ $color-yellow-100: rgb(254, 249, 195);
16
+ $color-yellow-600: rgb(202, 138, 4);
17
+ $color-red-100: rgb(254, 226, 226);
18
+ // Colors and Themes
19
+ $colors: (
20
+ dark: (
21
+ background: (
22
+ primary: $color-black-900,
23
+ gradient:
24
+ linear-gradient(
25
+ 180deg,
26
+ $color-black-800 0%,
27
+ rgba($color-black-800, 0.88) 100%
28
+ ),
29
+ ),
30
+ text: (
31
+ primary: $color-white,
32
+ secondary: $color-gray-300,
33
+ muted: $color-gray-500,
34
+ ),
35
+ border: (
36
+ primary: #343841,
37
+ subtle: rgba($color-white, 0.1),
38
+ ),
39
+ hover: (
40
+ background: rgba($color-white, 0.12),
41
+ danger: $color-red-600,
42
+ ),
43
+ annotation: (
44
+ note: (
45
+ background: rgba($color-blue-600, 0.15),
46
+ border: rgba($color-blue-600, 0.3),
47
+ ),
48
+ warning: (
49
+ background: rgba($color-yellow-600, 0.15),
50
+ border: rgba($color-yellow-600, 0.3),
51
+ ),
52
+ error: (
53
+ background: rgba($color-red-600, 0.15),
54
+ border: rgba($color-red-600, 0.3),
55
+ ),
56
+ ),
57
+ ),
58
+ light: (
59
+ background: (
60
+ primary: $color-white,
61
+ gradient:
62
+ linear-gradient(
63
+ 180deg,
64
+ $color-gray-200 0%,
65
+ rgba($color-gray-200, 0.88) 100%
66
+ ),
67
+ ),
68
+ text: (
69
+ primary: $color-black-900,
70
+ secondary: $color-gray-700,
71
+ muted: $color-gray-600,
72
+ ),
73
+ border: (
74
+ primary: #e5e7eb,
75
+ subtle: rgba($color-black-900, 0.1),
76
+ ),
77
+ hover: (
78
+ background: rgba($color-black-900, 0.05),
79
+ danger: $color-red-500,
80
+ ),
81
+ annotation: (
82
+ note: (
83
+ background: $color-blue-100,
84
+ border: rgba($color-blue-600, 0.2),
85
+ ),
86
+ warning: (
87
+ background: $color-yellow-100,
88
+ border: rgba($color-yellow-600, 0.2),
89
+ ),
90
+ error: (
91
+ background: $color-red-100,
92
+ border: rgba($color-red-600, 0.2),
93
+ ),
94
+ ),
95
+ ),
96
+ );
97
+
98
+ // Shadows
99
+ $shadow-themes: (
100
+ dark: (
101
+ toolbar: 0 2px 8px rgba(19, 21, 26, 0.3),
102
+ tooltip: (
103
+ 0 0 0 1px rgba(255, 255, 255, 0.1),
104
+ 0 4px 8px rgba(0, 0, 0, 0.4),
105
+ 0 2px 4px rgba(0, 0, 0, 0.3),
106
+ ),
107
+ window: (
108
+ 0px 0px 0px 0px rgba(19, 21, 26, 0.3),
109
+ 0px 1px 2px 0px rgba(19, 21, 26, 0.29),
110
+ 0px 4px 4px 0px rgba(19, 21, 26, 0.26),
111
+ 0px 10px 6px 0px rgba(19, 21, 26, 0.15),
112
+ 0px 17px 7px 0px rgba(19, 21, 26, 0.04),
113
+ 0px 26px 7px 0px rgba(19, 21, 26, 0.01),
114
+ ),
115
+ ),
116
+ light: (
117
+ toolbar: 0 2px 8px rgba(156, 163, 175, 0.2),
118
+ tooltip: (
119
+ 0 0 0 1px rgba(17, 24, 39, 0.05),
120
+ 0 4px 8px rgba(107, 114, 128, 0.15),
121
+ 0 2px 4px rgba(107, 114, 128, 0.1),
122
+ ),
123
+ window: (
124
+ 0px 0px 0px 0px rgba(156, 163, 175, 0.1),
125
+ 0px 1px 2px 0px rgba(156, 163, 175, 0.12),
126
+ 0px 4px 4px 0px rgba(156, 163, 175, 0.1),
127
+ 0px 10px 6px 0px rgba(156, 163, 175, 0.08),
128
+ 0px 17px 7px 0px rgba(156, 163, 175, 0.05),
129
+ 0px 26px 7px 0px rgba(156, 163, 175, 0.02),
130
+ ),
131
+ ),
132
+ );
133
+
134
+ // Typography
135
+ $font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
136
+ 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif,
137
+ 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
138
+
139
+ // Add font size variables
140
+ $font-sizes: (
141
+ xs: 0.75rem,
142
+ sm: 0.875rem,
143
+ md: 1rem,
144
+ lg: 1.25rem,
145
+ xl: 2rem,
146
+ );
147
+
148
+ // Dimensions
149
+ $dimensions: (
150
+ toolbar-height: 40px,
151
+ toolbar-button-width: 44px,
152
+ window-max-width: 640px,
153
+ window-max-height: 480px,
154
+ border-radius: (
155
+ small: 4px,
156
+ medium: 8px,
157
+ large: 12px,
158
+ full: 9999px,
159
+ ),
160
+ );
161
+
162
+ // Transitions
163
+ $transitions: (
164
+ default: all 0.2s ease-out,
165
+ smooth: all 0.2s ease-in-out,
166
+ );
167
+
168
+ // Z-indices
169
+ $z-indices: (
170
+ toolbar: 999999,
171
+ window: 999999999,
172
+ );
173
+
174
+ // Create a mixin for the CSS variables that can be used by components
175
+ @mixin devtools-theme {
176
+ --ndt-border-radius-small: #{map.get(
177
+ map.get($dimensions, border-radius),
178
+ small
179
+ )};
180
+ --ndt-border-radius-medium: #{map.get(
181
+ map.get($dimensions, border-radius),
182
+ medium
183
+ )};
184
+ --ndt-border-radius-large: #{map.get(
185
+ map.get($dimensions, border-radius),
186
+ large
187
+ )};
188
+
189
+ --ndt-transition-default: #{map.get($transitions, default)};
190
+ --ndt-transition-smooth: #{map.get($transitions, smooth)};
191
+
192
+ --ndt-bg-primary: #{map.get(map.get($colors, light), background, primary)};
193
+ --ndt-bg-gradient: #{map.get(map.get($colors, light), background, gradient)};
194
+ --ndt-text-primary: #{map.get(map.get($colors, light), text, primary)};
195
+ --ndt-text-secondary: #{map.get(map.get($colors, light), text, secondary)};
196
+ --ndt-text-muted: #{map.get(map.get($colors, light), text, muted)};
197
+ --ndt-border-primary: #{map.get(map.get($colors, light), border, primary)};
198
+ --ndt-border-subtle: #{map.get(map.get($colors, light), border, subtle)};
199
+ --ndt-hover-bg: #{map.get(map.get($colors, light), hover, background)};
200
+ --ndt-hover-danger: #{map.get(map.get($colors, light), hover, danger)};
201
+ --ndt-shadow-toolbar: #{map.get(map.get($shadow-themes, light), toolbar)};
202
+ --ndt-shadow-tooltip: #{map.get(map.get($shadow-themes, light), tooltip)};
203
+ --ndt-shadow-window: #{map.get(map.get($shadow-themes, light), window)};
204
+
205
+ --ndt-spacing-xs: 4px;
206
+ --ndt-spacing-sm: 8px;
207
+ --ndt-spacing-md: 16px;
208
+ --ndt-window-padding: 24px;
209
+
210
+ --ndt-font-size-xs: #{map.get($font-sizes, xs)};
211
+ --ndt-font-size-sm: #{map.get($font-sizes, sm)};
212
+ --ndt-font-size-md: #{map.get($font-sizes, md)};
213
+ --ndt-font-size-lg: #{map.get($font-sizes, lg)};
214
+ --ndt-font-size-xl: #{map.get($font-sizes, xl)};
215
+
216
+ --ndt-background-secondary: var(--ndt-bg-primary);
217
+ --ndt-background-hover: var(--ndt-hover-bg);
218
+
219
+ --ndt-primary: #df30d4;
220
+ --ndt-primary-rgb: 223, 48, 212;
221
+ --ndt-text-on-primary: #{$color-white};
222
+
223
+ --ndt-border-color: var(--ndt-border-primary);
224
+
225
+ --ndt-note-background: #{map.get(
226
+ map.get(map.get($colors, light), annotation),
227
+ note,
228
+ background
229
+ )};
230
+ --ndt-note-border: #{map.get(
231
+ map.get(map.get($colors, light), annotation),
232
+ note,
233
+ border
234
+ )};
235
+ --ndt-warning-background: #{map.get(
236
+ map.get(map.get($colors, light), annotation),
237
+ warning,
238
+ background
239
+ )};
240
+ --ndt-warning-border: #{map.get(
241
+ map.get(map.get($colors, light), annotation),
242
+ warning,
243
+ border
244
+ )};
245
+ --ndt-error-background: #{map.get(
246
+ map.get(map.get($colors, light), annotation),
247
+ error,
248
+ background
249
+ )};
250
+ --ndt-error-border: #{map.get(
251
+ map.get(map.get($colors, light), annotation),
252
+ error,
253
+ border
254
+ )};
255
+
256
+ &[data-theme='dark'] {
257
+ --ndt-bg-primary: #{map.get(map.get($colors, dark), background, primary)};
258
+ --ndt-bg-gradient: #{map.get(map.get($colors, dark), background, gradient)};
259
+ --ndt-text-primary: #{map.get(map.get($colors, dark), text, primary)};
260
+ --ndt-text-secondary: #{map.get(map.get($colors, dark), text, secondary)};
261
+ --ndt-text-muted: #{map.get(map.get($colors, dark), text, muted)};
262
+ --ndt-border-primary: #{map.get(map.get($colors, dark), border, primary)};
263
+ --ndt-border-subtle: #{map.get(map.get($colors, dark), border, subtle)};
264
+ --ndt-hover-bg: #{map.get(map.get($colors, dark), hover, background)};
265
+ --ndt-hover-danger: #{map.get(map.get($colors, dark), hover, danger)};
266
+ --ndt-shadow-toolbar: #{map.get(map.get($shadow-themes, dark), toolbar)};
267
+ --ndt-shadow-tooltip: #{map.get(map.get($shadow-themes, dark), tooltip)};
268
+ --ndt-shadow-window: #{map.get(map.get($shadow-themes, dark), window)};
269
+
270
+ --ndt-note-background: #{map.get(
271
+ map.get(map.get($colors, dark), annotation),
272
+ note,
273
+ background
274
+ )};
275
+ --ndt-note-border: #{map.get(
276
+ map.get(map.get($colors, dark), annotation),
277
+ note,
278
+ border
279
+ )};
280
+ --ndt-warning-background: #{map.get(
281
+ map.get(map.get($colors, dark), annotation),
282
+ warning,
283
+ background
284
+ )};
285
+ --ndt-warning-border: #{map.get(
286
+ map.get(map.get($colors, dark), annotation),
287
+ warning,
288
+ border
289
+ )};
290
+ --ndt-error-background: #{map.get(
291
+ map.get(map.get($colors, dark), annotation),
292
+ error,
293
+ background
294
+ )};
295
+ --ndt-error-border: #{map.get(
296
+ map.get(map.get($colors, dark), annotation),
297
+ error,
298
+ border
299
+ )};
300
+ }
301
+ }
302
+
303
+ .dev-toolbar {
304
+ @include devtools-theme;
305
+
306
+ // Typography styles
307
+ h1,
308
+ h2,
309
+ h3,
310
+ h4,
311
+ h5 {
312
+ font-weight: 600;
313
+ color: var(--ndt-text-primary);
314
+ margin: 0;
315
+ }
316
+
317
+ h1 {
318
+ font-size: var(--ndt-font-size-xl);
319
+ }
320
+ h2 {
321
+ font-size: var(--ndt-font-size-lg);
322
+ }
323
+ h3 {
324
+ font-size: var(--ndt-font-size-md);
325
+ }
326
+ h4 {
327
+ font-size: var(--ndt-font-size-sm);
328
+ }
329
+ h5 {
330
+ font-size: var(--ndt-font-size-xs);
331
+ }
332
+
333
+ hr {
334
+ border: 1px solid var(--ndt-border-subtle);
335
+ margin: 1em 0;
336
+ }
337
+
338
+ p {
339
+ line-height: 1.5em;
340
+ margin: 0;
341
+ }
342
+ }
@@ -0,0 +1,12 @@
1
+ import '@analogjs/vitest-angular/setup-zone';
2
+
3
+ import {
4
+ BrowserDynamicTestingModule,
5
+ platformBrowserDynamicTesting,
6
+ } from '@angular/platform-browser-dynamic/testing';
7
+ import { getTestBed } from '@angular/core/testing';
8
+
9
+ getTestBed().initTestEnvironment(
10
+ BrowserDynamicTestingModule,
11
+ platformBrowserDynamicTesting()
12
+ );
@@ -0,0 +1,96 @@
1
+ import { Injectable, inject } from '@angular/core';
2
+ import { toSignal } from '@angular/core/rxjs-interop';
3
+ import { BehaviorSubject, Observable, combineLatest, map } from 'rxjs';
4
+ import { DevToolsStorageService } from '../../utils/storage.service';
5
+ import { DevToolbarFlag } from './feature-flags.models';
6
+
7
+ interface ForcedFlagsState {
8
+ enabled: string[];
9
+ disabled: string[];
10
+ }
11
+
12
+ @Injectable({ providedIn: 'root' })
13
+ export class DevToolbarInternalFeatureFlagService {
14
+ private readonly STORAGE_KEY = 'feature-flags';
15
+ private storageService = inject(DevToolsStorageService);
16
+
17
+ private appFlags$ = new BehaviorSubject<DevToolbarFlag[]>([]);
18
+ private forcedFlagsSubject = new BehaviorSubject<ForcedFlagsState>({
19
+ enabled: [],
20
+ disabled: [],
21
+ });
22
+
23
+ private readonly forcedFlags$ = this.forcedFlagsSubject.asObservable();
24
+
25
+ public flags$: Observable<DevToolbarFlag[]> = combineLatest([
26
+ this.appFlags$,
27
+ this.forcedFlags$,
28
+ ]).pipe(
29
+ map(([appFlags, { enabled, disabled }]) => {
30
+ return appFlags.map((flag) => ({
31
+ ...flag,
32
+ isForced: enabled.includes(flag.id) || disabled.includes(flag.id),
33
+ isEnabled: enabled.includes(flag.id),
34
+ }));
35
+ })
36
+ );
37
+
38
+ public flags = toSignal(this.flags$, { initialValue: [] });
39
+
40
+ constructor() {
41
+ this.loadForcedFlags();
42
+ }
43
+
44
+ setAppFlags(flags: DevToolbarFlag[]): void {
45
+ this.appFlags$.next(flags);
46
+ }
47
+
48
+ getAppFlags(): Observable<DevToolbarFlag[]> {
49
+ return this.appFlags$.asObservable();
50
+ }
51
+
52
+ getForcedFlags(): Observable<DevToolbarFlag[]> {
53
+ return this.flags$.pipe(
54
+ map((flags) => flags.filter((flag) => flag.isForced))
55
+ );
56
+ }
57
+
58
+ setFlag(flagId: string, isEnabled: boolean): void {
59
+ const { enabled, disabled } = this.forcedFlagsSubject.value;
60
+
61
+ const newEnabled = enabled.filter((id) => id !== flagId);
62
+ const newDisabled = disabled.filter((id) => id !== flagId);
63
+
64
+ if (isEnabled) {
65
+ newEnabled.push(flagId);
66
+ } else {
67
+ newDisabled.push(flagId);
68
+ }
69
+
70
+ const newState = { enabled: newEnabled, disabled: newDisabled };
71
+ this.forcedFlagsSubject.next(newState);
72
+ this.storageService.set(this.STORAGE_KEY, newState);
73
+ }
74
+
75
+ removeFlagOverride(flagId: string): void {
76
+ const { enabled, disabled } = this.forcedFlagsSubject.value;
77
+
78
+ const newState = {
79
+ enabled: enabled.filter((id) => id !== flagId),
80
+ disabled: disabled.filter((id) => id !== flagId),
81
+ };
82
+
83
+ this.forcedFlagsSubject.next(newState);
84
+ this.storageService.set(this.STORAGE_KEY, newState);
85
+ }
86
+
87
+ private loadForcedFlags(): void {
88
+ const savedFlags = this.storageService.get<ForcedFlagsState>(
89
+ this.STORAGE_KEY
90
+ );
91
+
92
+ if (savedFlags) {
93
+ this.forcedFlagsSubject.next(savedFlags);
94
+ }
95
+ }
96
+ }