ngx-dev-toolbar 3.1.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,9 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, signal, computed, Injectable, inject, ChangeDetectionStrategy, Component, input, model, ElementRef, output, viewChild, contentChild, effect, DestroyRef, ViewEncapsulation, EnvironmentInjector, createComponent, TemplateRef, Directive, contentChildren } from '@angular/core';
2
+ import { InjectionToken, signal, computed, Injectable, ChangeDetectionStrategy, Component, input, inject, model, ElementRef, output, viewChild, contentChild, effect, DestroyRef, ViewEncapsulation, makeEnvironmentProviders, ENVIRONMENT_INITIALIZER, ApplicationRef, EnvironmentInjector, createComponent, TemplateRef, Directive, contentChildren } from '@angular/core';
3
+ import { trigger, state, style, transition, animate } from '@angular/animations';
4
+ import { CommonModule, DOCUMENT, NgTemplateOutlet } from '@angular/common';
3
5
  import { toSignal, takeUntilDestroyed } from '@angular/core/rxjs-interop';
4
6
  import { BehaviorSubject, combineLatest, map, firstValueFrom, fromEvent } from 'rxjs';
5
- import { trigger, state, transition, style, animate } from '@angular/animations';
6
- import { CommonModule, DOCUMENT, NgTemplateOutlet } from '@angular/common';
7
7
  import { filter, throttleTime } from 'rxjs/operators';
8
8
  import * as i1 from '@angular/forms';
9
9
  import { FormsModule } from '@angular/forms';
@@ -12,6 +12,13 @@ import { CdkMenuModule } from '@angular/cdk/menu';
12
12
  import * as i1$1 from '@angular/cdk/overlay';
13
13
  import { OverlayModule, CdkConnectedOverlay } from '@angular/cdk/overlay';
14
14
 
15
+ /**
16
+ * InjectionToken for the Toolbar configuration.
17
+ *
18
+ * Provided automatically by `provideToolbar(config)`.
19
+ * Can be injected to access the toolbar configuration at runtime.
20
+ */
21
+ const TOOLBAR_CONFIG = new InjectionToken('TOOLBAR_CONFIG');
15
22
  /**
16
23
  * InjectionToken for the Feature Flags service.
17
24
  *
@@ -175,1367 +182,257 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImpor
175
182
  }]
176
183
  }] });
177
184
 
178
- class ToolbarStorageService {
185
+ class AngularIconComponent {
186
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: AngularIconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
187
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.0.7", type: AngularIconComponent, isStandalone: true, selector: "ngt-angular-icon", ngImport: i0, template: `
188
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none">
189
+ <defs>
190
+ <linearGradient
191
+ id="angular-gradient"
192
+ x1="6"
193
+ x2="18"
194
+ y1="20"
195
+ y2="4"
196
+ gradientUnits="userSpaceOnUse"
197
+ >
198
+ <stop offset="0" stop-color="#E40035" />
199
+ <stop offset=".24" stop-color="#F60A48" />
200
+ <stop offset=".352" stop-color="#F20755" />
201
+ <stop offset=".494" stop-color="#DC087D" />
202
+ <stop offset=".745" stop-color="#9717E7" />
203
+ <stop offset="1" stop-color="#6C00F5" />
204
+ </linearGradient>
205
+ </defs>
206
+ <g fill="url(#angular-gradient)">
207
+ <polygon points="14.1,2.7 20.1,15.7 20.7,5.8" />
208
+ <polygon points="15.6,16.4 8.4,16.4 7.4,18.6 12,21.2 16.6,18.6" />
209
+ <polygon points="9.6,13.5 14.4,13.5 12,7.7" />
210
+ <polygon points="9.9,2.7 3.3,5.8 3.9,15.7" />
211
+ </g>
212
+ </svg>
213
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
214
+ }
215
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: AngularIconComponent, decorators: [{
216
+ type: Component,
217
+ args: [{
218
+ selector: 'ngt-angular-icon',
219
+ standalone: true,
220
+ changeDetection: ChangeDetectionStrategy.OnPush,
221
+ template: `
222
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none">
223
+ <defs>
224
+ <linearGradient
225
+ id="angular-gradient"
226
+ x1="6"
227
+ x2="18"
228
+ y1="20"
229
+ y2="4"
230
+ gradientUnits="userSpaceOnUse"
231
+ >
232
+ <stop offset="0" stop-color="#E40035" />
233
+ <stop offset=".24" stop-color="#F60A48" />
234
+ <stop offset=".352" stop-color="#F20755" />
235
+ <stop offset=".494" stop-color="#DC087D" />
236
+ <stop offset=".745" stop-color="#9717E7" />
237
+ <stop offset="1" stop-color="#6C00F5" />
238
+ </linearGradient>
239
+ </defs>
240
+ <g fill="url(#angular-gradient)">
241
+ <polygon points="14.1,2.7 20.1,15.7 20.7,5.8" />
242
+ <polygon points="15.6,16.4 8.4,16.4 7.4,18.6 12,21.2 16.6,18.6" />
243
+ <polygon points="9.6,13.5 14.4,13.5 12,7.7" />
244
+ <polygon points="9.9,2.7 3.3,5.8 3.9,15.7" />
245
+ </g>
246
+ </svg>
247
+ `,
248
+ }]
249
+ }] });
250
+
251
+ class BoltIconComponent {
179
252
  constructor() {
180
- this.PREFIX = 'AngularToolbar.';
181
- this.TOOLS_KEY = `${this.PREFIX}keys`;
182
- this.SETTINGS_KEY = `${this.PREFIX}settings`;
183
- }
184
- set(key, value) {
185
- const toolKey = this.getToolKey(key);
186
- this.addToolKey(toolKey);
187
- localStorage.setItem(toolKey, JSON.stringify(value));
188
- }
189
- get(key) {
190
- const toolKey = this.getToolKey(key);
191
- const item = localStorage.getItem(toolKey);
192
- return item ? JSON.parse(item) : null;
193
- }
194
- remove(key) {
195
- const toolKey = this.getToolKey(key);
196
- localStorage.removeItem(toolKey);
197
- this.removeToolKey(toolKey);
198
- }
199
- getAllSettings() {
200
- const settings = {};
201
- const keys = this.getToolKeys();
202
- keys.forEach((key) => {
203
- const value = this.get(key);
204
- if (value !== null) {
205
- settings[key] = value;
206
- }
207
- });
208
- return settings;
209
- }
210
- setAllSettings(settings) {
211
- Object.entries(settings).forEach(([key, value]) => {
212
- this.set(key, value);
213
- });
214
- }
215
- clearAllSettings() {
216
- const keys = this.getToolKeys();
217
- keys.forEach((key) => {
218
- this.remove(key);
219
- });
220
- }
221
- getToolKeys() {
222
- return JSON.parse(localStorage.getItem(this.TOOLS_KEY) ?? '[]');
223
- }
224
- addToolKey(key) {
225
- const currentKeys = this.getToolKeys();
226
- if (currentKeys.includes(key)) {
227
- return;
228
- }
229
- currentKeys.push(key);
230
- localStorage.setItem(this.TOOLS_KEY, JSON.stringify(currentKeys));
231
- }
232
- removeToolKey(key) {
233
- const currentKeys = this.getToolKeys();
234
- const index = currentKeys.indexOf(key);
235
- if (index !== -1) {
236
- currentKeys.splice(index, 1);
237
- }
238
- localStorage.setItem(this.TOOLS_KEY, JSON.stringify(currentKeys));
239
- }
240
- getToolKey(key) {
241
- return key.includes(this.PREFIX) ? key : this.PREFIX + key;
253
+ this.fill = input('#FFFF');
242
254
  }
243
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarStorageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
244
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarStorageService, providedIn: 'root' }); }
255
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: BoltIconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
256
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.0.7", type: BoltIconComponent, isStandalone: true, selector: "ngt-bolt-icon", inputs: { fill: { classPropertyName: "fill", publicName: "fill", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
257
+ <svg
258
+ [attr.fill]="fill()"
259
+ xmlns="http://www.w3.org/2000/svg"
260
+ width="24"
261
+ height="24"
262
+ viewBox="0 0 256 256"
263
+ >
264
+ <path
265
+ d="M160,16,144,96H208l-96,144,16-80H64Z"
266
+ opacity="0.2"
267
+ ></path>
268
+ <path
269
+ 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"
270
+ ></path>
271
+ </svg>
272
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
245
273
  }
246
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarStorageService, decorators: [{
247
- type: Injectable,
248
- args: [{ providedIn: 'root' }]
274
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: BoltIconComponent, decorators: [{
275
+ type: Component,
276
+ args: [{
277
+ selector: 'ngt-bolt-icon',
278
+ standalone: true,
279
+ changeDetection: ChangeDetectionStrategy.OnPush,
280
+ template: `
281
+ <svg
282
+ [attr.fill]="fill()"
283
+ xmlns="http://www.w3.org/2000/svg"
284
+ width="24"
285
+ height="24"
286
+ viewBox="0 0 256 256"
287
+ >
288
+ <path
289
+ d="M160,16,144,96H208l-96,144,16-80H64Z"
290
+ opacity="0.2"
291
+ ></path>
292
+ <path
293
+ 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"
294
+ ></path>
295
+ </svg>
296
+ `,
297
+ }]
249
298
  }] });
250
299
 
251
- class ToolbarInternalFeatureFlagService {
300
+ class BugIconComponent {
252
301
  constructor() {
253
- this.STORAGE_KEY = 'feature-flags';
254
- this.storageService = inject(ToolbarStorageService);
255
- this.stateService = inject(ToolbarStateService);
256
- this.appFlags$ = new BehaviorSubject([]);
257
- this.forcedFlagsSubject = new BehaviorSubject({
258
- enabled: [],
259
- disabled: [],
260
- });
261
- this.forcedFlags$ = this.forcedFlagsSubject.asObservable();
262
- this.flags$ = combineLatest([
263
- this.appFlags$,
264
- this.forcedFlags$,
265
- ]).pipe(map(([appFlags, { enabled, disabled }]) => {
266
- // If toolbar is disabled, return app flags without overrides
267
- if (!this.stateService.isEnabled()) {
268
- return appFlags;
269
- }
270
- return appFlags.map((flag) => {
271
- const isForced = enabled.includes(flag.id) || disabled.includes(flag.id);
272
- return {
273
- ...flag,
274
- isForced,
275
- isEnabled: enabled.includes(flag.id)
276
- ? true
277
- : disabled.includes(flag.id)
278
- ? false
279
- : flag.isEnabled,
280
- originalValue: isForced ? flag.isEnabled : undefined,
281
- };
282
- });
283
- }));
284
- this.flags = toSignal(this.flags$, { initialValue: [] });
285
- this.loadForcedFlags();
286
- }
287
- setAppFlags(flags) {
288
- this.appFlags$.next(flags);
289
- }
290
- getAppFlags() {
291
- return this.appFlags$.asObservable();
292
- }
293
- getForcedFlags() {
294
- return this.flags$.pipe(map((flags) => {
295
- // If toolbar is disabled, return empty array (no forced values)
296
- if (!this.stateService.isEnabled()) {
297
- return [];
298
- }
299
- return flags.filter((flag) => flag.isForced);
300
- }));
301
- }
302
- setFlag(flagId, isEnabled) {
303
- const { enabled, disabled } = this.forcedFlagsSubject.value;
304
- const newEnabled = enabled.filter((id) => id !== flagId);
305
- const newDisabled = disabled.filter((id) => id !== flagId);
306
- if (isEnabled) {
307
- newEnabled.push(flagId);
308
- }
309
- else {
310
- newDisabled.push(flagId);
311
- }
312
- const newState = { enabled: newEnabled, disabled: newDisabled };
313
- this.forcedFlagsSubject.next(newState);
314
- this.storageService.set(this.STORAGE_KEY, newState);
302
+ this.fill = input('#FFFF');
315
303
  }
316
- removeFlagOverride(flagId) {
317
- const { enabled, disabled } = this.forcedFlagsSubject.value;
318
- const newState = {
319
- enabled: enabled.filter((id) => id !== flagId),
320
- disabled: disabled.filter((id) => id !== flagId),
321
- };
322
- this.forcedFlagsSubject.next(newState);
323
- this.storageService.set(this.STORAGE_KEY, newState);
324
- }
325
- loadForcedFlags() {
326
- const savedFlags = this.storageService.get(this.STORAGE_KEY);
327
- if (savedFlags) {
328
- this.forcedFlagsSubject.next(savedFlags);
329
- }
330
- }
331
- /**
332
- * Apply feature flags from a preset (used by Presets Tool)
333
- * This method directly sets the forced flags state without user interaction
334
- */
335
- applyPresetFlags(presetState) {
336
- this.forcedFlagsSubject.next(presetState);
337
- this.storageService.set(this.STORAGE_KEY, presetState);
338
- }
339
- /**
340
- * Get current forced state for saving to preset
341
- * Returns the raw state of enabled and disabled flag IDs
342
- */
343
- getCurrentForcedState() {
344
- return this.forcedFlagsSubject.value;
345
- }
346
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarInternalFeatureFlagService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
347
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarInternalFeatureFlagService, providedIn: 'root' }); }
304
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: BugIconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
305
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.0.7", type: BugIconComponent, isStandalone: true, selector: "ngt-bug-icon", inputs: { fill: { classPropertyName: "fill", publicName: "fill", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
306
+ <svg
307
+ [attr.fill]="fill()"
308
+ xmlns="http://www.w3.org/2000/svg"
309
+ width="24"
310
+ height="24"
311
+ viewBox="0 0 256 256"
312
+ >
313
+ <path
314
+ d="M140,88a16,16,0,1,1,16,16A16,16,0,0,1,140,88ZM100,72a16,16,0,1,0,16,16A16,16,0,0,0,100,72Zm120,72a91.84,91.84,0,0,1-2.34,20.64L236.81,173a12,12,0,0,1-9.62,22l-18-7.85a92,92,0,0,1-162.46,0l-18,7.85a12,12,0,1,1-9.62-22l19.15-8.36A91.84,91.84,0,0,1,36,144v-4H16a12,12,0,0,1,0-24H36v-4a91.84,91.84,0,0,1,2.34-20.64L19.19,83a12,12,0,0,1,9.62-22l18,7.85a92,92,0,0,1,162.46,0l18-7.85a12,12,0,1,1,9.62,22l-19.15,8.36A91.84,91.84,0,0,1,220,112v4h20a12,12,0,0,1,0,24H220ZM60,116H196v-4a68,68,0,0,0-136,0Zm56,94.92V140H60v4A68.1,68.1,0,0,0,116,210.92ZM196,144v-4H140v70.92A68.1,68.1,0,0,0,196,144Z"
315
+ ></path>
316
+ </svg>
317
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
348
318
  }
349
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarInternalFeatureFlagService, decorators: [{
350
- type: Injectable,
351
- args: [{ providedIn: 'root' }]
352
- }], ctorParameters: () => [] });
319
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: BugIconComponent, decorators: [{
320
+ type: Component,
321
+ args: [{
322
+ selector: 'ngt-bug-icon',
323
+ standalone: true,
324
+ changeDetection: ChangeDetectionStrategy.OnPush,
325
+ template: `
326
+ <svg
327
+ [attr.fill]="fill()"
328
+ xmlns="http://www.w3.org/2000/svg"
329
+ width="24"
330
+ height="24"
331
+ viewBox="0 0 256 256"
332
+ >
333
+ <path
334
+ d="M140,88a16,16,0,1,1,16,16A16,16,0,0,1,140,88ZM100,72a16,16,0,1,0,16,16A16,16,0,0,0,100,72Zm120,72a91.84,91.84,0,0,1-2.34,20.64L236.81,173a12,12,0,0,1-9.62,22l-18-7.85a92,92,0,0,1-162.46,0l-18,7.85a12,12,0,1,1-9.62-22l19.15-8.36A91.84,91.84,0,0,1,36,144v-4H16a12,12,0,0,1,0-24H36v-4a91.84,91.84,0,0,1,2.34-20.64L19.19,83a12,12,0,0,1,9.62-22l18,7.85a92,92,0,0,1,162.46,0l18-7.85a12,12,0,1,1,9.62,22l-19.15,8.36A91.84,91.84,0,0,1,220,112v4h20a12,12,0,0,1,0,24H220ZM60,116H196v-4a68,68,0,0,0-136,0Zm56,94.92V140H60v4A68.1,68.1,0,0,0,116,210.92ZM196,144v-4H140v70.92A68.1,68.1,0,0,0,196,144Z"
335
+ ></path>
336
+ </svg>
337
+ `,
338
+ }]
339
+ }] });
353
340
 
354
- class ToolbarFeatureFlagService {
341
+ class CodeIconComponent {
355
342
  constructor() {
356
- this.internalService = inject(ToolbarInternalFeatureFlagService);
357
- }
358
- /**
359
- * Sets the available flags that will be displayed in the tool on the dev toolbar
360
- * @param flags The flags to be displayed
361
- */
362
- setAvailableOptions(flags) {
363
- this.internalService.setAppFlags(flags);
364
- }
365
- /**
366
- * Gets the flags that were forced/modified through the tool on the dev toolbar
367
- * @returns Observable of forced flags array
368
- */
369
- getForcedValues() {
370
- return this.internalService.getForcedFlags();
371
- }
372
- /**
373
- * Gets ALL flag values with overrides already applied.
374
- * Returns the complete set of flags where overridden values replace base values.
375
- * Each flag includes an `isForced` property indicating if it was overridden.
376
- *
377
- * This method simplifies integration by eliminating the need to manually merge
378
- * base flags with overrides using combineLatest.
379
- *
380
- * @returns Observable of all flags with overrides applied
381
- *
382
- * @example
383
- * ```typescript
384
- * // Get a specific flag value with overrides applied
385
- * this.featureFlagsService.getValues().pipe(
386
- * map(flags => flags.find(f => f.id === 'newFeature')),
387
- * map(flag => flag?.isEnabled ?? false)
388
- * ).subscribe(isEnabled => {
389
- * if (isEnabled) {
390
- * // Enable feature
391
- * }
392
- * });
393
- * ```
394
- */
395
- getValues() {
396
- return this.internalService.flags$;
343
+ this.fill = input('#FFFF');
397
344
  }
398
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarFeatureFlagService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
399
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarFeatureFlagService, providedIn: 'root' }); }
345
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: CodeIconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
346
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.0.7", type: CodeIconComponent, isStandalone: true, selector: "ngt-code-icon", inputs: { fill: { classPropertyName: "fill", publicName: "fill", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
347
+ <svg
348
+ [attr.fill]="fill()"
349
+ xmlns="http://www.w3.org/2000/svg"
350
+ width="24"
351
+ height="24"
352
+ viewBox="0 0 256 256"
353
+ >
354
+ <path d="M240,128l-48,40H64L16,128,64,88H192Z" opacity="0.2"></path>
355
+ <path
356
+ d="M69.12,94.15,28.5,128l40.62,33.85a8,8,0,1,1-10.24,12.29l-48-40a8,8,0,0,1,0-12.29l48-40a8,8,0,0,1,10.24,12.3Zm176,27.7-48-40a8,8,0,1,0-10.24,12.3L227.5,128l-40.62,33.85a8,8,0,1,0,10.24,12.29l48-40a8,8,0,0,0,0-12.29ZM162.73,32.48a8,8,0,0,0-10.25,4.79l-64,176a8,8,0,0,0,4.79,10.26A8.14,8.14,0,0,0,96,224a8,8,0,0,0,7.52-5.27l64-176A8,8,0,0,0,162.73,32.48Z"
357
+ ></path>
358
+ </svg>
359
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
400
360
  }
401
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarFeatureFlagService, decorators: [{
402
- type: Injectable,
403
- args: [{ providedIn: 'root' }]
361
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: CodeIconComponent, decorators: [{
362
+ type: Component,
363
+ args: [{
364
+ selector: 'ngt-code-icon',
365
+ standalone: true,
366
+ template: `
367
+ <svg
368
+ [attr.fill]="fill()"
369
+ xmlns="http://www.w3.org/2000/svg"
370
+ width="24"
371
+ height="24"
372
+ viewBox="0 0 256 256"
373
+ >
374
+ <path d="M240,128l-48,40H64L16,128,64,88H192Z" opacity="0.2"></path>
375
+ <path
376
+ d="M69.12,94.15,28.5,128l40.62,33.85a8,8,0,1,1-10.24,12.29l-48-40a8,8,0,0,1,0-12.29l48-40a8,8,0,0,1,10.24,12.3Zm176,27.7-48-40a8,8,0,1,0-10.24,12.3L227.5,128l-40.62,33.85a8,8,0,1,0,10.24,12.29l48-40a8,8,0,0,0,0-12.29ZM162.73,32.48a8,8,0,0,0-10.25,4.79l-64,176a8,8,0,0,0,4.79,10.26A8.14,8.14,0,0,0,96,224a8,8,0,0,0,7.52-5.27l64-176A8,8,0,0,0,162.73,32.48Z"
377
+ ></path>
378
+ </svg>
379
+ `,
380
+ changeDetection: ChangeDetectionStrategy.OnPush,
381
+ }]
404
382
  }] });
405
383
 
406
- class ToolbarInternalPermissionsService {
384
+ class DatabaseIconComponent {
407
385
  constructor() {
408
- this.STORAGE_KEY = 'permissions';
409
- this.storageService = inject(ToolbarStorageService);
410
- this.stateService = inject(ToolbarStateService);
411
- this.appPermissions$ = new BehaviorSubject([]);
412
- this.forcedStateSubject = new BehaviorSubject({
413
- granted: [],
414
- denied: [],
415
- });
416
- this.forcedState$ = this.forcedStateSubject.asObservable();
417
- this.permissions$ = combineLatest([
418
- this.appPermissions$,
419
- this.forcedState$,
420
- ]).pipe(map(([appPermissions, { granted, denied }]) => {
421
- // If toolbar is disabled, return app permissions without overrides
422
- if (!this.stateService.isEnabled()) {
423
- return appPermissions;
424
- }
425
- return appPermissions.map((permission) => {
426
- const isForced = granted.includes(permission.id) || denied.includes(permission.id);
427
- return {
428
- ...permission,
429
- isForced,
430
- isGranted: granted.includes(permission.id)
431
- ? true
432
- : denied.includes(permission.id)
433
- ? false
434
- : permission.isGranted,
435
- originalValue: isForced ? permission.isGranted : undefined,
436
- };
437
- });
438
- }));
439
- this.permissions = toSignal(this.permissions$, { initialValue: [] });
440
- this.loadForcedState();
441
- }
442
- setAppPermissions(permissions) {
443
- this.appPermissions$.next(permissions);
444
- this.validateAndCleanForcedState(permissions);
386
+ this.fill = input('#FFFF');
445
387
  }
446
- setPermission(id, granted) {
447
- const { granted: grantedIds, denied: deniedIds } = this.forcedStateSubject.value;
448
- const newGranted = grantedIds.filter((permId) => permId !== id);
449
- const newDenied = deniedIds.filter((permId) => permId !== id);
450
- if (granted) {
451
- newGranted.push(id);
452
- }
453
- else {
454
- newDenied.push(id);
455
- }
456
- const newState = { granted: newGranted, denied: newDenied };
457
- this.forcedStateSubject.next(newState);
458
- this.storageService.set(this.STORAGE_KEY, newState);
459
- }
460
- removePermissionOverride(id) {
461
- const { granted, denied } = this.forcedStateSubject.value;
462
- const newState = {
463
- granted: granted.filter((permId) => permId !== id),
464
- denied: denied.filter((permId) => permId !== id),
465
- };
466
- this.forcedStateSubject.next(newState);
467
- this.storageService.set(this.STORAGE_KEY, newState);
468
- }
469
- getForcedPermissions() {
470
- return this.permissions$.pipe(map((permissions) => {
471
- // If toolbar is disabled, return empty array (no forced values)
472
- if (!this.stateService.isEnabled()) {
473
- return [];
474
- }
475
- return permissions.filter((permission) => permission.isForced);
476
- }));
477
- }
478
- /**
479
- * Apply a preset permissions state, replacing the current forced state.
480
- * Useful for automated testing or restoring saved configurations.
481
- * @param state The preset forced permissions state to apply
482
- */
483
- applyPresetPermissions(state) {
484
- this.forcedStateSubject.next(state);
485
- this.storageService.set(this.STORAGE_KEY, state);
486
- }
487
- /**
488
- * Get the current forced permissions state.
489
- * Returns a deep copy to prevent external mutations.
490
- * @returns Current forced permissions state
491
- */
492
- getCurrentForcedState() {
493
- const currentState = this.forcedStateSubject.value;
494
- return {
495
- granted: [...currentState.granted],
496
- denied: [...currentState.denied],
497
- };
498
- }
499
- loadForcedState() {
500
- try {
501
- const savedState = this.storageService.get(this.STORAGE_KEY);
502
- if (savedState && this.isValidForcedState(savedState)) {
503
- this.forcedStateSubject.next(savedState);
504
- }
505
- }
506
- catch (error) {
507
- console.warn('Error loading forced permissions state from localStorage:', error);
508
- }
509
- }
510
- isValidForcedState(state) {
511
- if (!state || typeof state !== 'object') {
512
- return false;
513
- }
514
- const candidate = state;
515
- return (Array.isArray(candidate['granted']) &&
516
- Array.isArray(candidate['denied']));
517
- }
518
- validateAndCleanForcedState(permissions) {
519
- const currentState = this.forcedStateSubject.value;
520
- const validIds = new Set(permissions.map((p) => p.id));
521
- const cleanedGranted = currentState.granted.filter((id) => validIds.has(id));
522
- const cleanedDenied = currentState.denied.filter((id) => validIds.has(id));
523
- const hasInvalidIds = cleanedGranted.length !== currentState.granted.length ||
524
- cleanedDenied.length !== currentState.denied.length;
525
- if (hasInvalidIds) {
526
- const cleanedState = {
527
- granted: cleanedGranted,
528
- denied: cleanedDenied,
529
- };
530
- this.forcedStateSubject.next(cleanedState);
531
- this.storageService.set(this.STORAGE_KEY, cleanedState);
532
- const invalidIds = [
533
- ...currentState.granted.filter((id) => !validIds.has(id)),
534
- ...currentState.denied.filter((id) => !validIds.has(id)),
535
- ];
536
- if (invalidIds.length > 0) {
537
- console.warn('Removed invalid permission IDs from forced state:', invalidIds);
538
- }
539
- }
540
- }
541
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarInternalPermissionsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
542
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarInternalPermissionsService, providedIn: 'root' }); }
543
- }
544
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarInternalPermissionsService, decorators: [{
545
- type: Injectable,
546
- args: [{ providedIn: 'root' }]
547
- }], ctorParameters: () => [] });
548
-
549
- /**
550
- * Public service for integrating the Permissions Tool with your application.
551
- * Use this service to:
552
- * 1. Register your application's permissions with setAvailableOptions()
553
- * 2. Listen for toolbar permission overrides with getForcedValues()
554
- * 3. Apply preset permission states for testing with applyPreset()
555
- *
556
- * @example
557
- * ```typescript
558
- * constructor(private permissionsService: ToolbarPermissionsService) {
559
- * // Register permissions
560
- * this.permissionsService.setAvailableOptions([
561
- * { id: 'can-edit', name: 'Can Edit', isGranted: false, isForced: false }
562
- * ]);
563
- *
564
- * // Listen for overrides
565
- * this.permissionsService.getForcedValues().subscribe(forcedPermissions => {
566
- * // Update your app's permission state
567
- * });
568
- * }
569
- * ```
570
- */
571
- class ToolbarPermissionsService {
572
- constructor() {
573
- this.internalService = inject(ToolbarInternalPermissionsService);
574
- }
575
- /**
576
- * Sets the available permissions that will be displayed in the toolbar tool.
577
- * Call this in your app initialization to register permissions.
578
- *
579
- * @param permissions Array of permissions to display in the toolbar
580
- *
581
- * @example
582
- * ```typescript
583
- * this.permissionsService.setAvailableOptions([
584
- * {
585
- * id: 'can-edit-posts',
586
- * name: 'Edit Posts',
587
- * description: 'Can edit blog posts',
588
- * isGranted: false,
589
- * isForced: false
590
- * }
591
- * ]);
592
- * ```
593
- */
594
- setAvailableOptions(permissions) {
595
- this.internalService.setAppPermissions(permissions);
596
- }
597
- /**
598
- * Gets an observable of permissions that were forced/overridden through the toolbar.
599
- * Subscribe to this to update your application's permission state.
600
- *
601
- * @returns Observable emitting array of forced permissions whenever changes occur
602
- *
603
- * @example
604
- * ```typescript
605
- * this.permissionsService.getForcedValues().subscribe(forcedPermissions => {
606
- * forcedPermissions.forEach(permission => {
607
- * this.updatePermission(permission.id, permission.isGranted);
608
- * });
609
- * });
610
- * ```
611
- */
612
- getForcedValues() {
613
- return this.internalService.getForcedPermissions();
614
- }
615
- /**
616
- * Gets ALL permission values with overrides already applied.
617
- * Returns the complete set of permissions where overridden values replace base values.
618
- * Each permission includes an `isForced` property indicating if it was overridden.
619
- *
620
- * This method simplifies integration by eliminating the need to manually merge
621
- * base permissions with overrides using combineLatest.
622
- *
623
- * @returns Observable of all permissions with overrides applied
624
- *
625
- * @example
626
- * ```typescript
627
- * // Simple permission check with overrides applied
628
- * this.permissionsService.getValues().pipe(
629
- * map(permissions => permissions.find(p => p.id === 'can-edit')),
630
- * map(permission => permission?.isGranted ?? false)
631
- * ).subscribe(canEdit => {
632
- * if (canEdit) {
633
- * // Enable edit functionality
634
- * }
635
- * });
636
- * ```
637
- */
638
- getValues() {
639
- return this.internalService.permissions$;
640
- }
641
- /**
642
- * Apply a preset permission state. Useful for automated testing scenarios.
643
- *
644
- * @param state The forced permissions state to apply
645
- *
646
- * @example
647
- * ```typescript
648
- * this.permissionsService.applyPreset({
649
- * granted: ['can-edit-posts'],
650
- * denied: ['can-delete-posts']
651
- * });
652
- * ```
653
- */
654
- applyPreset(state) {
655
- this.internalService.applyPresetPermissions(state);
656
- }
657
- /**
658
- * Get the current forced permission state.
659
- *
660
- * @returns Current forced permissions state
661
- */
662
- getCurrentState() {
663
- return this.internalService.getCurrentForcedState();
664
- }
665
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarPermissionsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
666
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarPermissionsService, providedIn: 'root' }); }
667
- }
668
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarPermissionsService, decorators: [{
669
- type: Injectable,
670
- args: [{ providedIn: 'root' }]
671
- }] });
672
-
673
- class ToolbarInternalLanguageService {
674
- constructor() {
675
- this.STORAGE_KEY = 'language';
676
- this.storageService = inject(ToolbarStorageService);
677
- this.stateService = inject(ToolbarStateService);
678
- this.languages$ = new BehaviorSubject([]);
679
- this.forcedLanguage$ = new BehaviorSubject(null);
680
- this.languages = toSignal(this.languages$, { initialValue: [] });
681
- this.loadForcedLanguage();
682
- }
683
- setAppLanguages(languages) {
684
- this.languages$.next(languages);
685
- }
686
- getAppLanguages() {
687
- return this.languages$.asObservable();
688
- }
689
- setForcedLanguage(language) {
690
- this.forcedLanguage$.next(language);
691
- this.storageService.set(this.STORAGE_KEY, language);
692
- }
693
- getForcedLanguage() {
694
- return this.forcedLanguage$.pipe(map((language) => {
695
- // If toolbar is disabled, return empty array (no forced values)
696
- if (!this.stateService.isEnabled()) {
697
- return [];
698
- }
699
- return language ? [language] : [];
700
- }));
701
- }
702
- removeForcedLanguage() {
703
- this.forcedLanguage$.next(null);
704
- this.storageService.remove(this.STORAGE_KEY);
705
- }
706
- loadForcedLanguage() {
707
- const savedLanguage = this.storageService.get(this.STORAGE_KEY);
708
- if (savedLanguage) {
709
- this.forcedLanguage$.next(savedLanguage);
710
- }
711
- }
712
- /**
713
- * Apply language from a preset (used by Presets Tool)
714
- * Accepts a language ID and finds the corresponding Language object from available languages
715
- */
716
- async applyPresetLanguage(languageId) {
717
- if (languageId === null) {
718
- this.removeForcedLanguage();
719
- return;
720
- }
721
- // Get available languages and find matching one
722
- const languages = await firstValueFrom(this.languages$);
723
- const language = languages.find((lang) => lang.id === languageId);
724
- if (language) {
725
- this.setForcedLanguage(language);
726
- }
727
- else {
728
- console.warn(`Language ${languageId} not found in available languages. Skipping.`);
729
- }
730
- }
731
- /**
732
- * Get current forced language ID for saving to preset
733
- * Returns the language ID or null if no language is forced
734
- */
735
- getCurrentForcedLanguage() {
736
- return this.forcedLanguage$.value?.id ?? null;
737
- }
738
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarInternalLanguageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
739
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarInternalLanguageService, providedIn: 'root' }); }
740
- }
741
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarInternalLanguageService, decorators: [{
742
- type: Injectable,
743
- args: [{ providedIn: 'root' }]
744
- }], ctorParameters: () => [] });
745
-
746
- class ToolbarLanguageService {
747
- constructor() {
748
- this.internalService = inject(ToolbarInternalLanguageService);
749
- }
750
- /**
751
- * Sets the available languages that will be displayed in the tool on the dev toolbar
752
- * @param languages The languages to be displayed
753
- */
754
- setAvailableOptions(languages) {
755
- this.internalService.setAppLanguages(languages);
756
- }
757
- /**
758
- * Gets the languages that were forced/modified through the tool on the dev toolbar
759
- * @returns Observable of forced languages array
760
- */
761
- getForcedValues() {
762
- return this.internalService.getForcedLanguage();
763
- }
764
- /**
765
- * Gets the forced language value.
766
- * For the language tool, this returns the same as getForcedValues() since
767
- * only one language can be selected at a time.
768
- * @returns Observable of forced language array (single item or empty)
769
- */
770
- getValues() {
771
- return this.internalService.getForcedLanguage();
772
- }
773
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarLanguageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
774
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarLanguageService, providedIn: 'root' }); }
775
- }
776
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarLanguageService, decorators: [{
777
- type: Injectable,
778
- args: [{ providedIn: 'root' }]
779
- }] });
780
-
781
- /**
782
- * Internal service for managing app features state and forced overrides.
783
- *
784
- * This service handles:
785
- * - Feature configuration storage
786
- * - Forced feature state management
787
- * - localStorage persistence
788
- * - State validation and cleanup
789
- *
790
- * @internal This service is for internal toolbar use only. Consumers should use ToolbarAppFeaturesService.
791
- */
792
- class ToolbarInternalAppFeaturesService {
793
- constructor() {
794
- this.STORAGE_KEY = 'app-features';
795
- this.storageService = inject(ToolbarStorageService);
796
- this.stateService = inject(ToolbarStateService);
797
- this.appFeaturesSubject = new BehaviorSubject([]);
798
- this.forcedFeaturesSubject = new BehaviorSubject({
799
- enabled: [],
800
- disabled: [],
801
- });
802
- this.forcedFeatures$ = this.forcedFeaturesSubject.asObservable();
803
- /**
804
- * Observable stream of all features with merged forced state
805
- */
806
- this.features$ = combineLatest([
807
- this.appFeaturesSubject.asObservable(),
808
- this.forcedFeatures$,
809
- ]).pipe(map(([appFeatures, forcedState]) => this.mergeForcedState(appFeatures, forcedState)));
810
- /**
811
- * Signal containing current features with merged forced state
812
- */
813
- this.features = toSignal(this.features$, { initialValue: [] });
814
- this.loadForcedFeatures();
815
- }
816
- /**
817
- * Set available app features for the application.
818
- * Validates features, trims whitespace, and triggers validation of forced state.
819
- *
820
- * @param features - Array of app features to configure
821
- * @throws Error if duplicate feature IDs or empty IDs are detected
822
- */
823
- setAppFeatures(features) {
824
- // Validate for empty IDs
825
- const emptyIdFeature = features.find((f) => !f.id || f.id.trim() === '');
826
- if (emptyIdFeature) {
827
- throw new Error('Feature ID cannot be empty');
828
- }
829
- // Validate for duplicate IDs
830
- const ids = features.map((f) => f.id);
831
- const duplicateIds = ids.filter((id, index) => ids.indexOf(id) !== index);
832
- if (duplicateIds.length > 0) {
833
- throw new Error(`Duplicate feature IDs detected: ${duplicateIds.join(', ')}`);
834
- }
835
- // Trim whitespace from names and warn about empty names
836
- const processedFeatures = features.map((feature) => {
837
- const trimmedName = feature.name.trim();
838
- if (!trimmedName) {
839
- console.warn(`Feature '${feature.id}' has empty name`);
840
- }
841
- return {
842
- ...feature,
843
- name: trimmedName || feature.name,
844
- };
845
- });
846
- this.appFeaturesSubject.next(processedFeatures);
847
- this.validateAndCleanForcedState();
848
- }
849
- /**
850
- * Get observable stream of app features (natural state, no forced state merged)
851
- */
852
- getAppFeatures() {
853
- return this.appFeaturesSubject.asObservable();
854
- }
855
- /**
856
- * Get observable stream of features that have forced overrides
857
- */
858
- getForcedFeatures() {
859
- return this.features$.pipe(map((features) => {
860
- // If toolbar is disabled, return empty array (no forced values)
861
- if (!this.stateService.isEnabled()) {
862
- return [];
863
- }
864
- return features.filter((feature) => feature.isForced);
865
- }));
866
- }
867
- /**
868
- * Force a feature to enabled or disabled state.
869
- * Persists the forced state to localStorage.
870
- *
871
- * @param featureId - ID of the feature to force
872
- * @param isEnabled - Whether to force feature to enabled (true) or disabled (false)
873
- */
874
- setFeature(featureId, isEnabled) {
875
- const { enabled, disabled } = this.forcedFeaturesSubject.value;
876
- // Remove feature from both arrays
877
- const newEnabled = enabled.filter((id) => id !== featureId);
878
- const newDisabled = disabled.filter((id) => id !== featureId);
879
- // Add to appropriate array
880
- if (isEnabled) {
881
- newEnabled.push(featureId);
882
- }
883
- else {
884
- newDisabled.push(featureId);
885
- }
886
- const newState = { enabled: newEnabled, disabled: newDisabled };
887
- this.forcedFeaturesSubject.next(newState);
888
- this.saveForcedFeatures(newState);
889
- }
890
- /**
891
- * Remove forced override for a feature, returning it to natural state.
892
- * Persists the change to localStorage.
893
- *
894
- * @param featureId - ID of the feature to unforce
895
- */
896
- removeFeatureOverride(featureId) {
897
- const { enabled, disabled } = this.forcedFeaturesSubject.value;
898
- const newState = {
899
- enabled: enabled.filter((id) => id !== featureId),
900
- disabled: disabled.filter((id) => id !== featureId),
901
- };
902
- this.forcedFeaturesSubject.next(newState);
903
- this.saveForcedFeatures(newState);
904
- }
905
- /**
906
- * Apply a preset forced state (for preset integration).
907
- * Validates and cleans invalid feature IDs before applying.
908
- *
909
- * @param state - Forced features state from preset
910
- */
911
- applyForcedState(state) {
912
- const configuredFeatureIds = this.appFeaturesSubject.value.map((f) => f.id);
913
- // Validate and filter to only valid IDs
914
- const validEnabled = state.enabled.filter((id) => configuredFeatureIds.includes(id));
915
- const validDisabled = state.disabled.filter((id) => configuredFeatureIds.includes(id));
916
- const invalidIds = [
917
- ...state.enabled.filter((id) => !configuredFeatureIds.includes(id)),
918
- ...state.disabled.filter((id) => !configuredFeatureIds.includes(id)),
919
- ];
920
- if (invalidIds.length > 0) {
921
- console.warn(`Preset contains invalid feature IDs (ignored): ${invalidIds.join(', ')}`);
922
- }
923
- const cleanedState = { enabled: validEnabled, disabled: validDisabled };
924
- this.forcedFeaturesSubject.next(cleanedState);
925
- this.saveForcedFeatures(cleanedState);
926
- }
927
- /**
928
- * Get current forced state as a snapshot (defensive copy).
929
- * Useful for preset exports and debugging.
930
- *
931
- * @returns Current forced features state
932
- */
933
- getCurrentForcedState() {
934
- const state = this.forcedFeaturesSubject.value;
935
- return {
936
- enabled: [...state.enabled],
937
- disabled: [...state.disabled],
938
- };
939
- }
940
- /**
941
- * Merge natural app features with forced state.
942
- *
943
- * @param appFeatures - Natural feature configuration
944
- * @param forcedState - Forced overrides from localStorage/toolbar
945
- * @returns Features with merged forced state
946
- */
947
- mergeForcedState(appFeatures, forcedState) {
948
- // If toolbar is disabled, return app features without overrides
949
- if (!this.stateService.isEnabled()) {
950
- return appFeatures;
951
- }
952
- return appFeatures.map((feature) => {
953
- const isInEnabled = forcedState.enabled.includes(feature.id);
954
- const isInDisabled = forcedState.disabled.includes(feature.id);
955
- const isForced = isInEnabled || isInDisabled;
956
- return {
957
- ...feature,
958
- isEnabled: isInEnabled ? true : isInDisabled ? false : feature.isEnabled,
959
- isForced,
960
- originalValue: isForced ? feature.isEnabled : undefined,
961
- };
962
- });
963
- }
964
- /**
965
- * Load forced features state from localStorage on initialization.
966
- * Handles missing or corrupted data gracefully.
967
- */
968
- loadForcedFeatures() {
969
- try {
970
- const savedState = this.storageService.get(this.STORAGE_KEY);
971
- if (savedState) {
972
- this.forcedFeaturesSubject.next(savedState);
973
- }
974
- }
975
- catch (error) {
976
- console.error('Failed to load forced app features from localStorage:', error);
977
- // Use default empty state on error
978
- }
979
- }
980
- /**
981
- * Persist forced features state to localStorage.
982
- * Handles quota exceeded errors gracefully.
983
- *
984
- * @param state - Forced state to persist
985
- */
986
- saveForcedFeatures(state) {
987
- try {
988
- this.storageService.set(this.STORAGE_KEY, state);
989
- }
990
- catch (error) {
991
- console.error('Failed to persist app features to localStorage:', error);
992
- // Continue execution - state is still valid in memory for current session
993
- }
994
- }
995
- /**
996
- * Validate forced feature IDs against configured features and clean up invalid ones.
997
- * Called after setAppFeatures() to ensure forced state references valid features.
998
- */
999
- validateAndCleanForcedState() {
1000
- const configuredFeatureIds = this.appFeaturesSubject.value.map((f) => f.id);
1001
- const currentForcedState = this.forcedFeaturesSubject.value;
1002
- const validEnabled = currentForcedState.enabled.filter((id) => configuredFeatureIds.includes(id));
1003
- const validDisabled = currentForcedState.disabled.filter((id) => configuredFeatureIds.includes(id));
1004
- const removedIds = [
1005
- ...currentForcedState.enabled.filter((id) => !configuredFeatureIds.includes(id)),
1006
- ...currentForcedState.disabled.filter((id) => !configuredFeatureIds.includes(id)),
1007
- ];
1008
- if (removedIds.length > 0) {
1009
- console.warn(`Removed invalid feature IDs from forced state: ${removedIds.join(', ')}`);
1010
- const cleanedState = { enabled: validEnabled, disabled: validDisabled };
1011
- this.forcedFeaturesSubject.next(cleanedState);
1012
- this.saveForcedFeatures(cleanedState);
1013
- }
1014
- }
1015
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarInternalAppFeaturesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1016
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarInternalAppFeaturesService, providedIn: 'root' }); }
1017
- }
1018
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarInternalAppFeaturesService, decorators: [{
1019
- type: Injectable,
1020
- args: [{ providedIn: 'root' }]
1021
- }], ctorParameters: () => [] });
1022
-
1023
- /**
1024
- * Public service for managing app features in the dev toolbar.
1025
- *
1026
- * This service implements the ToolbarService interface and provides methods for:
1027
- * - Configuring available product features
1028
- * - Retrieving forced feature overrides
1029
- * - Applying preset feature configurations
1030
- * - Exporting current forced state for presets
1031
- *
1032
- * @example
1033
- * ```typescript
1034
- * import { ToolbarAppFeaturesService } from 'ngx-dev-toolbar';
1035
- *
1036
- * @Component({...})
1037
- * export class AppComponent implements OnInit {
1038
- * private appFeaturesService = inject(ToolbarAppFeaturesService);
1039
- *
1040
- * ngOnInit() {
1041
- * // Configure available features based on current tier
1042
- * const features: ToolbarAppFeature[] = [
1043
- * { id: 'analytics', name: 'Analytics Dashboard', isEnabled: true, isForced: false },
1044
- * { id: 'multi-user', name: 'Multi-User Support', isEnabled: false, isForced: false }
1045
- * ];
1046
- * this.appFeaturesService.setAvailableOptions(features);
1047
- *
1048
- * // Subscribe to forced overrides
1049
- * this.appFeaturesService.getForcedValues().subscribe(forcedFeatures => {
1050
- * forcedFeatures.forEach(feature => {
1051
- * // Apply forced feature state to application logic
1052
- * this.applyFeatureState(feature.id, feature.isEnabled);
1053
- * });
1054
- * });
1055
- * }
1056
- * }
1057
- * ```
1058
- */
1059
- class ToolbarAppFeaturesService {
1060
- constructor() {
1061
- this.internalService = inject(ToolbarInternalAppFeaturesService);
1062
- }
1063
- /**
1064
- * Set available app features for the application.
1065
- *
1066
- * Configures the list of product features that can be toggled in the dev toolbar.
1067
- * Features should represent product-level capabilities like license tiers,
1068
- * deployment configurations, or environment flags.
1069
- *
1070
- * @param features - Array of app features to display in the toolbar
1071
- * @throws Error if duplicate feature IDs or empty IDs are detected
1072
- *
1073
- * @example
1074
- * ```typescript
1075
- * const features: ToolbarAppFeature[] = [
1076
- * {
1077
- * id: 'advanced-analytics',
1078
- * name: 'Advanced Analytics Dashboard',
1079
- * description: 'Access premium reporting and data visualization',
1080
- * isEnabled: false, // Not available in current tier
1081
- * isForced: false
1082
- * },
1083
- * {
1084
- * id: 'multi-user-support',
1085
- * name: 'Multi-User Collaboration',
1086
- * description: 'Enable team features and user management',
1087
- * isEnabled: true, // Available in current tier
1088
- * isForced: false
1089
- * }
1090
- * ];
1091
- * this.appFeaturesService.setAvailableOptions(features);
1092
- * ```
1093
- */
1094
- setAvailableOptions(features) {
1095
- this.internalService.setAppFeatures(features);
1096
- }
1097
- /**
1098
- * Get observable stream of features that have forced overrides.
1099
- *
1100
- * Emits an array of features that have been forced via the dev toolbar.
1101
- * Only features with `isForced = true` are included in the emissions.
1102
- * Use this to react to feature override changes and update application behavior.
1103
- *
1104
- * @returns Observable that emits array of forced features whenever state changes
1105
- *
1106
- * @example
1107
- * ```typescript
1108
- * this.appFeaturesService.getForcedValues()
1109
- * .pipe(takeUntilDestroyed())
1110
- * .subscribe(forcedFeatures => {
1111
- * forcedFeatures.forEach(feature => {
1112
- * if (feature.isEnabled) {
1113
- * this.enableFeature(feature.id);
1114
- * } else {
1115
- * this.disableFeature(feature.id);
1116
- * }
1117
- * });
1118
- * });
1119
- * ```
1120
- */
1121
- getForcedValues() {
1122
- return this.internalService.getForcedFeatures();
1123
- }
1124
- /**
1125
- * Gets ALL app feature values with overrides already applied.
1126
- * Returns the complete set of features where overridden values replace base values.
1127
- * Each feature includes an `isForced` property indicating if it was overridden.
1128
- *
1129
- * This method simplifies integration by eliminating the need to manually merge
1130
- * base features with overrides using combineLatest.
1131
- *
1132
- * @returns Observable of all features with overrides applied
1133
- *
1134
- * @example
1135
- * ```typescript
1136
- * // Check if a feature is enabled with overrides applied
1137
- * this.appFeaturesService.getValues().pipe(
1138
- * map(features => features.find(f => f.id === 'premium-analytics')),
1139
- * map(feature => feature?.isEnabled ?? false)
1140
- * ).subscribe(isEnabled => {
1141
- * if (isEnabled) {
1142
- * // Enable premium analytics
1143
- * }
1144
- * });
1145
- * ```
1146
- */
1147
- getValues() {
1148
- return this.internalService.features$;
1149
- }
1150
- /**
1151
- * Apply a preset feature configuration (for preset tool integration).
1152
- *
1153
- * Accepts a forced features state object and applies it to the current configuration.
1154
- * Invalid feature IDs (not in configured features) are filtered out with a warning.
1155
- *
1156
- * @param state - Forced features state containing enabled/disabled arrays
1157
- *
1158
- * @example
1159
- * ```typescript
1160
- * // Apply "Enterprise Tier" preset
1161
- * const enterprisePreset: ForcedAppFeaturesState = {
1162
- * enabled: ['analytics', 'multi-user', 'white-label', 'sso'],
1163
- * disabled: []
1164
- * };
1165
- * this.appFeaturesService.applyPresetFeatures(enterprisePreset);
1166
- *
1167
- * // Apply "Basic Tier" preset
1168
- * const basicPreset: ForcedAppFeaturesState = {
1169
- * enabled: [],
1170
- * disabled: ['analytics', 'multi-user', 'white-label', 'sso']
1171
- * };
1172
- * this.appFeaturesService.applyPresetFeatures(basicPreset);
1173
- * ```
1174
- */
1175
- applyPresetFeatures(state) {
1176
- this.internalService.applyForcedState(state);
1177
- }
1178
- /**
1179
- * Get current forced feature state as a snapshot (for preset export).
1180
- *
1181
- * Returns the current forced features state with enabled/disabled arrays.
1182
- * Useful for exporting toolbar state to save as a preset or for debugging.
1183
- * Returns a defensive copy - mutations will not affect internal state.
1184
- *
1185
- * @returns Current forced features state
1186
- *
1187
- * @example
1188
- * ```typescript
1189
- * // Export current toolbar state to save as preset
1190
- * const currentState = this.appFeaturesService.getCurrentForcedState();
1191
- * this.presetsService.savePreset('my-config', {
1192
- * appFeatures: currentState,
1193
- * // ... other tool states
1194
- * });
1195
- *
1196
- * console.log(currentState);
1197
- * // { enabled: ['analytics'], disabled: ['white-label'] }
1198
- * ```
1199
- */
1200
- getCurrentForcedState() {
1201
- return this.internalService.getCurrentForcedState();
1202
- }
1203
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarAppFeaturesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1204
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarAppFeaturesService, providedIn: 'root' }); }
1205
- }
1206
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarAppFeaturesService, decorators: [{
1207
- type: Injectable,
1208
- args: [{ providedIn: 'root' }]
1209
- }] });
1210
-
1211
- /**
1212
- * Provides all ngx-dev-toolbar services using InjectionTokens.
1213
- *
1214
- * Use this function for tree-shakeable toolbar integration. When combined with
1215
- * dynamic imports, the toolbar code is completely excluded from production builds.
1216
- *
1217
- * ## Basic Usage (Development Only)
1218
- *
1219
- * ```typescript
1220
- * // main.ts
1221
- * import { bootstrapApplication } from '@angular/platform-browser';
1222
- * import { AppComponent } from './app/app.component';
1223
- * import { environment } from './environments/environment';
1224
- *
1225
- * async function bootstrap() {
1226
- * const providers = [];
1227
- *
1228
- * // Only import toolbar in development - completely tree-shaken in prod
1229
- * if (!environment.production) {
1230
- * const { provideToolbar } = await import('ngx-dev-toolbar');
1231
- * providers.push(...provideToolbar());
1232
- * }
1233
- *
1234
- * bootstrapApplication(AppComponent, { providers });
1235
- * }
1236
- *
1237
- * bootstrap();
1238
- * ```
1239
- *
1240
- * ## Using Services with Tokens
1241
- *
1242
- * ```typescript
1243
- * import { inject } from '@angular/core';
1244
- * import { TOOLBAR_FEATURE_FLAGS } from 'ngx-dev-toolbar';
1245
- * import type { ToolbarService, ToolbarFlag } from 'ngx-dev-toolbar';
1246
- *
1247
- * @Injectable({ providedIn: 'root' })
1248
- * export class FeatureFlagsService {
1249
- * // Optional injection - null in production
1250
- * private devToolbar = inject<ToolbarService<ToolbarFlag>>(
1251
- * TOOLBAR_FEATURE_FLAGS,
1252
- * { optional: true }
1253
- * );
1254
- *
1255
- * constructor() {
1256
- * // Safe to call - no-op if devToolbar is null
1257
- * this.devToolbar?.setAvailableOptions(this.flags);
1258
- *
1259
- * // Subscribe to forced values only if toolbar exists
1260
- * this.devToolbar?.getForcedValues().subscribe(forced => {
1261
- * // Merge forced values
1262
- * });
1263
- * }
1264
- * }
1265
- * ```
1266
- *
1267
- * ## Production Bundle Impact
1268
- *
1269
- * | What's Included | Production | Development |
1270
- * |-----------------|------------|-------------|
1271
- * | InjectionTokens | ~500 bytes | ~500 bytes |
1272
- * | Type interfaces | 0 (types only) | 0 (types only) |
1273
- * | Service implementations | 0 | ~15KB |
1274
- * | UI Components | 0 | ~30KB |
1275
- * | Total | **~500 bytes** | ~45KB |
1276
- *
1277
- * @returns Array of Angular providers for all toolbar services
1278
- */
1279
- function provideToolbar() {
1280
- return [
1281
- { provide: TOOLBAR_FEATURE_FLAGS, useClass: ToolbarFeatureFlagService },
1282
- { provide: TOOLBAR_PERMISSIONS, useClass: ToolbarPermissionsService },
1283
- { provide: TOOLBAR_LANGUAGE, useClass: ToolbarLanguageService },
1284
- { provide: TOOLBAR_APP_FEATURES, useClass: ToolbarAppFeaturesService },
1285
- ];
1286
- }
1287
-
1288
- class AngularIconComponent {
1289
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: AngularIconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1290
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.0.7", type: AngularIconComponent, isStandalone: true, selector: "ngt-angular-icon", ngImport: i0, template: `
1291
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none">
1292
- <defs>
1293
- <linearGradient
1294
- id="angular-gradient"
1295
- x1="6"
1296
- x2="18"
1297
- y1="20"
1298
- y2="4"
1299
- gradientUnits="userSpaceOnUse"
1300
- >
1301
- <stop offset="0" stop-color="#E40035" />
1302
- <stop offset=".24" stop-color="#F60A48" />
1303
- <stop offset=".352" stop-color="#F20755" />
1304
- <stop offset=".494" stop-color="#DC087D" />
1305
- <stop offset=".745" stop-color="#9717E7" />
1306
- <stop offset="1" stop-color="#6C00F5" />
1307
- </linearGradient>
1308
- </defs>
1309
- <g fill="url(#angular-gradient)">
1310
- <polygon points="14.1,2.7 20.1,15.7 20.7,5.8" />
1311
- <polygon points="15.6,16.4 8.4,16.4 7.4,18.6 12,21.2 16.6,18.6" />
1312
- <polygon points="9.6,13.5 14.4,13.5 12,7.7" />
1313
- <polygon points="9.9,2.7 3.3,5.8 3.9,15.7" />
1314
- </g>
1315
- </svg>
1316
- `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1317
- }
1318
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: AngularIconComponent, decorators: [{
1319
- type: Component,
1320
- args: [{
1321
- selector: 'ngt-angular-icon',
1322
- standalone: true,
1323
- changeDetection: ChangeDetectionStrategy.OnPush,
1324
- template: `
1325
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none">
1326
- <defs>
1327
- <linearGradient
1328
- id="angular-gradient"
1329
- x1="6"
1330
- x2="18"
1331
- y1="20"
1332
- y2="4"
1333
- gradientUnits="userSpaceOnUse"
1334
- >
1335
- <stop offset="0" stop-color="#E40035" />
1336
- <stop offset=".24" stop-color="#F60A48" />
1337
- <stop offset=".352" stop-color="#F20755" />
1338
- <stop offset=".494" stop-color="#DC087D" />
1339
- <stop offset=".745" stop-color="#9717E7" />
1340
- <stop offset="1" stop-color="#6C00F5" />
1341
- </linearGradient>
1342
- </defs>
1343
- <g fill="url(#angular-gradient)">
1344
- <polygon points="14.1,2.7 20.1,15.7 20.7,5.8" />
1345
- <polygon points="15.6,16.4 8.4,16.4 7.4,18.6 12,21.2 16.6,18.6" />
1346
- <polygon points="9.6,13.5 14.4,13.5 12,7.7" />
1347
- <polygon points="9.9,2.7 3.3,5.8 3.9,15.7" />
1348
- </g>
1349
- </svg>
1350
- `,
1351
- }]
1352
- }] });
1353
-
1354
- class BoltIconComponent {
1355
- constructor() {
1356
- this.fill = input('#FFFF');
1357
- }
1358
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: BoltIconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1359
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.0.7", type: BoltIconComponent, isStandalone: true, selector: "ngt-bolt-icon", inputs: { fill: { classPropertyName: "fill", publicName: "fill", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
1360
- <svg
1361
- [attr.fill]="fill()"
1362
- xmlns="http://www.w3.org/2000/svg"
1363
- width="24"
1364
- height="24"
1365
- viewBox="0 0 256 256"
1366
- >
1367
- <path
1368
- d="M160,16,144,96H208l-96,144,16-80H64Z"
1369
- opacity="0.2"
1370
- ></path>
1371
- <path
1372
- 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"
1373
- ></path>
1374
- </svg>
1375
- `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1376
- }
1377
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: BoltIconComponent, decorators: [{
1378
- type: Component,
1379
- args: [{
1380
- selector: 'ngt-bolt-icon',
1381
- standalone: true,
1382
- changeDetection: ChangeDetectionStrategy.OnPush,
1383
- template: `
1384
- <svg
1385
- [attr.fill]="fill()"
1386
- xmlns="http://www.w3.org/2000/svg"
1387
- width="24"
1388
- height="24"
1389
- viewBox="0 0 256 256"
1390
- >
1391
- <path
1392
- d="M160,16,144,96H208l-96,144,16-80H64Z"
1393
- opacity="0.2"
1394
- ></path>
1395
- <path
1396
- 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"
1397
- ></path>
1398
- </svg>
1399
- `,
1400
- }]
1401
- }] });
1402
-
1403
- class BugIconComponent {
1404
- constructor() {
1405
- this.fill = input('#FFFF');
1406
- }
1407
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: BugIconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1408
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.0.7", type: BugIconComponent, isStandalone: true, selector: "ngt-bug-icon", inputs: { fill: { classPropertyName: "fill", publicName: "fill", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
1409
- <svg
1410
- [attr.fill]="fill()"
1411
- xmlns="http://www.w3.org/2000/svg"
1412
- width="24"
1413
- height="24"
1414
- viewBox="0 0 256 256"
1415
- >
1416
- <path
1417
- d="M140,88a16,16,0,1,1,16,16A16,16,0,0,1,140,88ZM100,72a16,16,0,1,0,16,16A16,16,0,0,0,100,72Zm120,72a91.84,91.84,0,0,1-2.34,20.64L236.81,173a12,12,0,0,1-9.62,22l-18-7.85a92,92,0,0,1-162.46,0l-18,7.85a12,12,0,1,1-9.62-22l19.15-8.36A91.84,91.84,0,0,1,36,144v-4H16a12,12,0,0,1,0-24H36v-4a91.84,91.84,0,0,1,2.34-20.64L19.19,83a12,12,0,0,1,9.62-22l18,7.85a92,92,0,0,1,162.46,0l18-7.85a12,12,0,1,1,9.62,22l-19.15,8.36A91.84,91.84,0,0,1,220,112v4h20a12,12,0,0,1,0,24H220ZM60,116H196v-4a68,68,0,0,0-136,0Zm56,94.92V140H60v4A68.1,68.1,0,0,0,116,210.92ZM196,144v-4H140v70.92A68.1,68.1,0,0,0,196,144Z"
1418
- ></path>
1419
- </svg>
1420
- `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1421
- }
1422
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: BugIconComponent, decorators: [{
1423
- type: Component,
1424
- args: [{
1425
- selector: 'ngt-bug-icon',
1426
- standalone: true,
1427
- changeDetection: ChangeDetectionStrategy.OnPush,
1428
- template: `
1429
- <svg
1430
- [attr.fill]="fill()"
1431
- xmlns="http://www.w3.org/2000/svg"
1432
- width="24"
1433
- height="24"
1434
- viewBox="0 0 256 256"
1435
- >
1436
- <path
1437
- d="M140,88a16,16,0,1,1,16,16A16,16,0,0,1,140,88ZM100,72a16,16,0,1,0,16,16A16,16,0,0,0,100,72Zm120,72a91.84,91.84,0,0,1-2.34,20.64L236.81,173a12,12,0,0,1-9.62,22l-18-7.85a92,92,0,0,1-162.46,0l-18,7.85a12,12,0,1,1-9.62-22l19.15-8.36A91.84,91.84,0,0,1,36,144v-4H16a12,12,0,0,1,0-24H36v-4a91.84,91.84,0,0,1,2.34-20.64L19.19,83a12,12,0,0,1,9.62-22l18,7.85a92,92,0,0,1,162.46,0l18-7.85a12,12,0,1,1,9.62,22l-19.15,8.36A91.84,91.84,0,0,1,220,112v4h20a12,12,0,0,1,0,24H220ZM60,116H196v-4a68,68,0,0,0-136,0Zm56,94.92V140H60v4A68.1,68.1,0,0,0,116,210.92ZM196,144v-4H140v70.92A68.1,68.1,0,0,0,196,144Z"
1438
- ></path>
1439
- </svg>
1440
- `,
1441
- }]
1442
- }] });
1443
-
1444
- class CodeIconComponent {
1445
- constructor() {
1446
- this.fill = input('#FFFF');
1447
- }
1448
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: CodeIconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1449
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.0.7", type: CodeIconComponent, isStandalone: true, selector: "ngt-code-icon", inputs: { fill: { classPropertyName: "fill", publicName: "fill", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
1450
- <svg
1451
- [attr.fill]="fill()"
1452
- xmlns="http://www.w3.org/2000/svg"
1453
- width="24"
1454
- height="24"
1455
- viewBox="0 0 256 256"
1456
- >
1457
- <path d="M240,128l-48,40H64L16,128,64,88H192Z" opacity="0.2"></path>
1458
- <path
1459
- d="M69.12,94.15,28.5,128l40.62,33.85a8,8,0,1,1-10.24,12.29l-48-40a8,8,0,0,1,0-12.29l48-40a8,8,0,0,1,10.24,12.3Zm176,27.7-48-40a8,8,0,1,0-10.24,12.3L227.5,128l-40.62,33.85a8,8,0,1,0,10.24,12.29l48-40a8,8,0,0,0,0-12.29ZM162.73,32.48a8,8,0,0,0-10.25,4.79l-64,176a8,8,0,0,0,4.79,10.26A8.14,8.14,0,0,0,96,224a8,8,0,0,0,7.52-5.27l64-176A8,8,0,0,0,162.73,32.48Z"
1460
- ></path>
1461
- </svg>
1462
- `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1463
- }
1464
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: CodeIconComponent, decorators: [{
1465
- type: Component,
1466
- args: [{
1467
- selector: 'ngt-code-icon',
1468
- standalone: true,
1469
- template: `
1470
- <svg
1471
- [attr.fill]="fill()"
1472
- xmlns="http://www.w3.org/2000/svg"
1473
- width="24"
1474
- height="24"
1475
- viewBox="0 0 256 256"
1476
- >
1477
- <path d="M240,128l-48,40H64L16,128,64,88H192Z" opacity="0.2"></path>
1478
- <path
1479
- d="M69.12,94.15,28.5,128l40.62,33.85a8,8,0,1,1-10.24,12.29l-48-40a8,8,0,0,1,0-12.29l48-40a8,8,0,0,1,10.24,12.3Zm176,27.7-48-40a8,8,0,1,0-10.24,12.3L227.5,128l-40.62,33.85a8,8,0,1,0,10.24,12.29l48-40a8,8,0,0,0,0-12.29ZM162.73,32.48a8,8,0,0,0-10.25,4.79l-64,176a8,8,0,0,0,4.79,10.26A8.14,8.14,0,0,0,96,224a8,8,0,0,0,7.52-5.27l64-176A8,8,0,0,0,162.73,32.48Z"
1480
- ></path>
1481
- </svg>
1482
- `,
1483
- changeDetection: ChangeDetectionStrategy.OnPush,
1484
- }]
1485
- }] });
1486
-
1487
- class DatabaseIconComponent {
1488
- constructor() {
1489
- this.fill = input('#FFFF');
1490
- }
1491
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: DatabaseIconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1492
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.0.7", type: DatabaseIconComponent, isStandalone: true, selector: "ngt-database-icon", inputs: { fill: { classPropertyName: "fill", publicName: "fill", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
1493
- <svg
1494
- [attr.fill]="fill()"
1495
- xmlns="http://www.w3.org/2000/svg"
1496
- width="24"
1497
- height="24"
1498
- viewBox="0 0 256 256"
1499
- >
1500
- <path
1501
- d="M216,80c0,26.51-39.4,48-88,48S40,106.51,40,80s39.4-48,88-48S216,53.49,216,80Z"
1502
- opacity="0.2"
1503
- ></path>
1504
- <path
1505
- d="M128,24C74.17,24,32,48.6,32,80v96c0,31.4,42.17,56,96,56s96-24.6,96-56V80C224,48.6,181.83,24,128,24Zm80,104c0,9.62-7.88,19.43-21.61,26.92C170.93,163.35,150.19,168,128,168s-42.93-4.65-58.39-13.08C55.88,147.43,48,137.62,48,128V111.36c17.06,15,46.23,24.64,80,24.64s62.94-9.68,80-24.64ZM69.61,53.08C85.07,44.65,105.81,40,128,40s42.93,4.65,58.39,13.08C200.12,60.57,208,70.38,208,80s-7.88,19.43-21.61,26.92C170.93,115.35,150.19,120,128,120s-42.93-4.65-58.39-13.08C55.88,99.43,48,89.62,48,80S55.88,60.57,69.61,53.08ZM186.39,202.92C170.93,211.35,150.19,216,128,216s-42.93-4.65-58.39-13.08C55.88,195.43,48,185.62,48,176V159.36c17.06,15,46.23,24.64,80,24.64s62.94-9.68,80-24.64V176C208,185.62,200.12,195.43,186.39,202.92Z"
1506
- ></path>
1507
- </svg>
1508
- `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1509
- }
1510
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: DatabaseIconComponent, decorators: [{
1511
- type: Component,
1512
- args: [{
1513
- selector: 'ngt-database-icon',
1514
- standalone: true,
1515
- changeDetection: ChangeDetectionStrategy.OnPush,
1516
- template: `
1517
- <svg
1518
- [attr.fill]="fill()"
1519
- xmlns="http://www.w3.org/2000/svg"
1520
- width="24"
1521
- height="24"
1522
- viewBox="0 0 256 256"
1523
- >
1524
- <path
1525
- d="M216,80c0,26.51-39.4,48-88,48S40,106.51,40,80s39.4-48,88-48S216,53.49,216,80Z"
1526
- opacity="0.2"
1527
- ></path>
1528
- <path
1529
- d="M128,24C74.17,24,32,48.6,32,80v96c0,31.4,42.17,56,96,56s96-24.6,96-56V80C224,48.6,181.83,24,128,24Zm80,104c0,9.62-7.88,19.43-21.61,26.92C170.93,163.35,150.19,168,128,168s-42.93-4.65-58.39-13.08C55.88,147.43,48,137.62,48,128V111.36c17.06,15,46.23,24.64,80,24.64s62.94-9.68,80-24.64ZM69.61,53.08C85.07,44.65,105.81,40,128,40s42.93,4.65,58.39,13.08C200.12,60.57,208,70.38,208,80s-7.88,19.43-21.61,26.92C170.93,115.35,150.19,120,128,120s-42.93-4.65-58.39-13.08C55.88,99.43,48,89.62,48,80S55.88,60.57,69.61,53.08ZM186.39,202.92C170.93,211.35,150.19,216,128,216s-42.93-4.65-58.39-13.08C55.88,195.43,48,185.62,48,176V159.36c17.06,15,46.23,24.64,80,24.64s62.94-9.68,80-24.64V176C208,185.62,200.12,195.43,186.39,202.92Z"
1530
- ></path>
1531
- </svg>
1532
- `,
1533
- }]
1534
- }] });
1535
-
1536
- class DiscordIconComponent {
1537
- constructor() {
1538
- this.fill = input('#000000');
388
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: DatabaseIconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
389
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.0.7", type: DatabaseIconComponent, isStandalone: true, selector: "ngt-database-icon", inputs: { fill: { classPropertyName: "fill", publicName: "fill", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
390
+ <svg
391
+ [attr.fill]="fill()"
392
+ xmlns="http://www.w3.org/2000/svg"
393
+ width="24"
394
+ height="24"
395
+ viewBox="0 0 256 256"
396
+ >
397
+ <path
398
+ d="M216,80c0,26.51-39.4,48-88,48S40,106.51,40,80s39.4-48,88-48S216,53.49,216,80Z"
399
+ opacity="0.2"
400
+ ></path>
401
+ <path
402
+ d="M128,24C74.17,24,32,48.6,32,80v96c0,31.4,42.17,56,96,56s96-24.6,96-56V80C224,48.6,181.83,24,128,24Zm80,104c0,9.62-7.88,19.43-21.61,26.92C170.93,163.35,150.19,168,128,168s-42.93-4.65-58.39-13.08C55.88,147.43,48,137.62,48,128V111.36c17.06,15,46.23,24.64,80,24.64s62.94-9.68,80-24.64ZM69.61,53.08C85.07,44.65,105.81,40,128,40s42.93,4.65,58.39,13.08C200.12,60.57,208,70.38,208,80s-7.88,19.43-21.61,26.92C170.93,115.35,150.19,120,128,120s-42.93-4.65-58.39-13.08C55.88,99.43,48,89.62,48,80S55.88,60.57,69.61,53.08ZM186.39,202.92C170.93,211.35,150.19,216,128,216s-42.93-4.65-58.39-13.08C55.88,195.43,48,185.62,48,176V159.36c17.06,15,46.23,24.64,80,24.64s62.94-9.68,80-24.64V176C208,185.62,200.12,195.43,186.39,202.92Z"
403
+ ></path>
404
+ </svg>
405
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
406
+ }
407
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: DatabaseIconComponent, decorators: [{
408
+ type: Component,
409
+ args: [{
410
+ selector: 'ngt-database-icon',
411
+ standalone: true,
412
+ changeDetection: ChangeDetectionStrategy.OnPush,
413
+ template: `
414
+ <svg
415
+ [attr.fill]="fill()"
416
+ xmlns="http://www.w3.org/2000/svg"
417
+ width="24"
418
+ height="24"
419
+ viewBox="0 0 256 256"
420
+ >
421
+ <path
422
+ d="M216,80c0,26.51-39.4,48-88,48S40,106.51,40,80s39.4-48,88-48S216,53.49,216,80Z"
423
+ opacity="0.2"
424
+ ></path>
425
+ <path
426
+ d="M128,24C74.17,24,32,48.6,32,80v96c0,31.4,42.17,56,96,56s96-24.6,96-56V80C224,48.6,181.83,24,128,24Zm80,104c0,9.62-7.88,19.43-21.61,26.92C170.93,163.35,150.19,168,128,168s-42.93-4.65-58.39-13.08C55.88,147.43,48,137.62,48,128V111.36c17.06,15,46.23,24.64,80,24.64s62.94-9.68,80-24.64ZM69.61,53.08C85.07,44.65,105.81,40,128,40s42.93,4.65,58.39,13.08C200.12,60.57,208,70.38,208,80s-7.88,19.43-21.61,26.92C170.93,115.35,150.19,120,128,120s-42.93-4.65-58.39-13.08C55.88,99.43,48,89.62,48,80S55.88,60.57,69.61,53.08ZM186.39,202.92C170.93,211.35,150.19,216,128,216s-42.93-4.65-58.39-13.08C55.88,195.43,48,185.62,48,176V159.36c17.06,15,46.23,24.64,80,24.64s62.94-9.68,80-24.64V176C208,185.62,200.12,195.43,186.39,202.92Z"
427
+ ></path>
428
+ </svg>
429
+ `,
430
+ }]
431
+ }] });
432
+
433
+ class DiscordIconComponent {
434
+ constructor() {
435
+ this.fill = input('#000000');
1539
436
  }
1540
437
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: DiscordIconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1541
438
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.0.7", type: DiscordIconComponent, isStandalone: true, selector: "ngt-discord-icon", inputs: { fill: { classPropertyName: "fill", publicName: "fill", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
@@ -3045,14 +1942,19 @@ class ToolbarSelectComponent {
3045
1942
  this.ariaLabel = input('');
3046
1943
  this.label = input('');
3047
1944
  this.size = input('medium');
1945
+ this.placeholder = input('Select an option');
3048
1946
  this.theme = computed(() => this.devToolbarStateService.theme());
3049
1947
  this.selectMenuId = `select-menu-${Math.random()
3050
1948
  .toString(36)
3051
1949
  .slice(2, 11)}`;
3052
1950
  this.isOpen = signal(false);
1951
+ this.isPlaceholder = computed(() => {
1952
+ const options = this.options();
1953
+ return !!options?.length && !options.some((opt) => opt.value === this.value());
1954
+ });
3053
1955
  this.selectedLabel = computed(() => {
3054
1956
  const selected = this.options()?.find((opt) => opt.value === this.value());
3055
- return selected?.label ?? '';
1957
+ return selected?.label ?? this.placeholder();
3056
1958
  });
3057
1959
  this.positions = [
3058
1960
  {
@@ -3082,11 +1984,12 @@ class ToolbarSelectComponent {
3082
1984
  this.close();
3083
1985
  }
3084
1986
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3085
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.7", type: ToolbarSelectComponent, isStandalone: true, selector: "ngt-select", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, ngImport: i0, template: `
1987
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.7", type: ToolbarSelectComponent, isStandalone: true, selector: "ngt-select", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, ngImport: i0, template: `
3086
1988
  <div
3087
1989
  class="ngt-select"
3088
1990
  [class.small]="size() === 'small'"
3089
1991
  [class.open]="isOpen()"
1992
+ [class.placeholder]="isPlaceholder()"
3090
1993
  [attr.aria-label]="ariaLabel()"
3091
1994
  [attr.aria-expanded]="isOpen()"
3092
1995
  [attr.aria-controls]="selectMenuId"
@@ -3119,20 +2022,20 @@ class ToolbarSelectComponent {
3119
2022
  [attr.data-theme]="theme()"
3120
2023
  >
3121
2024
  @for (option of options(); track option.value) {
3122
- <button
3123
- class="select-menu-item"
3124
- [class.selected]="option.value === value()"
3125
- [attr.aria-selected]="option.value === value()"
3126
- cdkMenuItem
3127
- type="button"
3128
- (click)="selectOption(option)"
3129
- >
3130
- {{ option.label }}
3131
- </button>
2025
+ <button
2026
+ class="select-menu-item"
2027
+ [class.selected]="option.value === value()"
2028
+ [attr.aria-selected]="option.value === value()"
2029
+ cdkMenuItem
2030
+ type="button"
2031
+ (click)="selectOption(option)"
2032
+ >
2033
+ {{ option.label }}
2034
+ </button>
3132
2035
  }
3133
2036
  </div>
3134
2037
  </ng-template>
3135
- `, isInline: true, styles: [":host{--ngt-border-radius-small: 4px;--ngt-border-radius-medium: 8px;--ngt-border-radius-large: 12px;--ngt-transition-default: all .2s ease-out;--ngt-transition-smooth: all .2s ease-in-out;--ngt-bg-primary: rgb(255, 255, 255);--ngt-bg-gradient: linear-gradient(180deg, rgb(243, 244, 246) 0%, rgba(243, 244, 246, .88) 100%);--ngt-text-primary: rgb(17, 24, 39);--ngt-text-secondary: rgb(55, 65, 81);--ngt-text-muted: rgb(107, 114, 128);--ngt-border-primary: #e5e7eb;--ngt-border-subtle: rgba(17, 24, 39, .1);--ngt-hover-bg: rgba(17, 24, 39, .05);--ngt-hover-danger: rgb(239, 68, 68);--ngt-shadow-toolbar: 0 2px 8px rgba(156, 163, 175, .2);--ngt-shadow-tooltip: 0 0 0 1px rgba(17, 24, 39, .05), 0 4px 8px rgba(107, 114, 128, .15), 0 2px 4px rgba(107, 114, 128, .1);--ngt-shadow-window: 0px 0px 0px 0px rgba(156, 163, 175, .1), 0px 1px 2px 0px rgba(156, 163, 175, .12), 0px 4px 4px 0px rgba(156, 163, 175, .1), 0px 10px 6px 0px rgba(156, 163, 175, .08), 0px 17px 7px 0px rgba(156, 163, 175, .05), 0px 26px 7px 0px rgba(156, 163, 175, .02);--ngt-spacing-xs: 4px;--ngt-spacing-sm: 6px;--ngt-spacing-md: 12px;--ngt-spacing-lg: 16px;--ngt-window-padding: 16px;--ngt-font-size-xxs: .65rem;--ngt-font-size-xs: .75rem;--ngt-font-size-sm: .875rem;--ngt-font-size-md: 1rem;--ngt-font-size-lg: 1.25rem;--ngt-font-size-xl: 2rem;--ngt-background-secondary: var(--ngt-bg-primary);--ngt-background-hover: var(--ngt-hover-bg);--ngt-primary: #df30d4;--ngt-primary-rgb: 223, 48, 212;--ngt-text-on-primary: rgb(255, 255, 255);--ngt-border-color: var(--ngt-border-primary);--ngt-note-background: rgb(219, 234, 254);--ngt-note-border: rgba(37, 99, 235, .2);--ngt-warning-background: rgb(254, 249, 195);--ngt-warning-border: rgba(202, 138, 4, .2);--ngt-error-background: rgb(254, 226, 226);--ngt-error-border: rgba(220, 38, 38, .2);display:inline-block;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",Segoe UI Symbol,\"Noto Color Emoji\"}.ngt-select{position:relative;width:100%;min-width:120px;display:flex;align-items:center;justify-content:space-between;padding:var(--ngt-spacing-sm) var(--ngt-spacing-md);border:1px solid var(--ngt-border-primary);border-radius:var(--ngt-border-radius-small);background-color:var(--ngt-bg-primary);color:var(--ngt-text-primary);font-size:var(--ngt-font-size-sm);cursor:pointer;-webkit-user-select:none;user-select:none;transition:var(--ngt-transition-default);outline:none;min-height:36px;box-sizing:border-box;box-shadow:0 1px 2px #0000000d}.ngt-select:hover{background-color:var(--ngt-hover-bg);border-color:var(--ngt-primary);box-shadow:0 1px 3px #0000001a}.ngt-select:focus-visible{outline:none;border-color:var(--ngt-primary);box-shadow:0 0 0 2px rgba(var(--ngt-primary-rgb),.2),0 1px 3px #0000001a}.ngt-select.small{padding:var(--ngt-spacing-xs) var(--ngt-spacing-sm);font-size:var(--ngt-font-size-sm);height:24px}.ngt-select.open{border-color:var(--ngt-primary);box-shadow:0 0 0 2px rgba(var(--ngt-primary-rgb),.2),0 1px 3px #0000001a}.ngt-select.open .select__arrow{transform:rotate(180deg)}.ngt-select__value{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:1.4;margin-right:var(--ngt-spacing-sm);min-width:0;display:flex;align-items:center}.ngt-select__arrow{width:16px;height:16px;flex-shrink:0;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:center;background-size:contain;transition:transform .2s ease;opacity:.9}.ngt-select-menu{display:inline-flex;flex-direction:column;min-width:180px;background-color:var(--ngt-bg-primary);padding:var(--ngt-spacing-sm) 0;border:2px solid var(--ngt-border-primary);border-radius:var(--ngt-border-radius-large);box-shadow:0 4px 12px #00000026,0 2px 4px #0000001a;color:var(--ngt-text-primary);max-height:min(400px,70vh);overflow-y:auto;backdrop-filter:blur(8px);z-index:1000}.ngt-select-menu::-webkit-scrollbar{width:8px;height:8px}.ngt-select-menu::-webkit-scrollbar-track{background:transparent}.ngt-select-menu::-webkit-scrollbar-thumb{background-color:var(--ngt-border-primary);border-radius:4px;border:2px solid var(--ngt-bg-primary)}.ngt-select-menu::-webkit-scrollbar-thumb:hover{background-color:var(--ngt-text-secondary)}.select-menu-item{background-color:transparent;cursor:pointer;border:none;color:var(--ngt-text-primary);-webkit-user-select:none;user-select:none;min-width:64px;padding:var(--ngt-spacing-sm) var(--ngt-spacing-md) var(--ngt-spacing-sm) var(--ngt-spacing-lg);display:flex;align-items:center;flex-direction:row;flex:1;font-size:var(--ngt-font-size-xs);font-family:inherit;position:relative;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;font-weight:400;line-height:1.5;transition:background-color .15s ease}.select-menu-item:hover{background-color:#0000000f}.select-menu-item:active{background-color:rgba(var(--ngt-primary-rgb),.15)}.select-menu-item.selected{color:var(--ngt-primary);background-color:rgba(var(--ngt-primary-rgb),.08);font-weight:400}.select-menu-item.selected:hover{background-color:rgba(var(--ngt-primary-rgb),.12)}.select-menu-item.selected:before{content:\"\";position:absolute;left:0;top:8px;width:3px;height:calc(100% - 16px);background-color:var(--ngt-primary);border-radius:2px}.select-menu-item:focus-visible{outline:none;background-color:#0000000f}.select-menu-item:first-child{margin-top:var(--ngt-spacing-xs)}.select-menu-item:last-child{margin-bottom:var(--ngt-spacing-xs)}.select-overlay{backdrop-filter:blur(8px)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$1.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$1.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "ngmodule", type: CdkMenuModule }, { kind: "directive", type: i2.CdkMenu, selector: "[cdkMenu]", outputs: ["closed"], exportAs: ["cdkMenu"] }, { kind: "directive", type: i2.CdkMenuItem, selector: "[cdkMenuItem]", inputs: ["cdkMenuItemDisabled", "cdkMenuitemTypeaheadLabel"], outputs: ["cdkMenuItemTriggered"], exportAs: ["cdkMenuItem"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2038
+ `, isInline: true, styles: [":host{--ngt-border-radius-small: 4px;--ngt-border-radius-medium: 8px;--ngt-border-radius-large: 12px;--ngt-transition-default: all .2s ease-out;--ngt-transition-smooth: all .2s ease-in-out;--ngt-bg-primary: rgb(255, 255, 255);--ngt-bg-gradient: linear-gradient(180deg, rgb(243, 244, 246) 0%, rgba(243, 244, 246, .88) 100%);--ngt-text-primary: rgb(17, 24, 39);--ngt-text-secondary: rgb(55, 65, 81);--ngt-text-muted: rgb(107, 114, 128);--ngt-border-primary: #e5e7eb;--ngt-border-subtle: rgba(17, 24, 39, .1);--ngt-hover-bg: rgba(17, 24, 39, .05);--ngt-hover-danger: rgb(239, 68, 68);--ngt-shadow-toolbar: 0 2px 8px rgba(156, 163, 175, .2);--ngt-shadow-tooltip: 0 0 0 1px rgba(17, 24, 39, .05), 0 4px 8px rgba(107, 114, 128, .15), 0 2px 4px rgba(107, 114, 128, .1);--ngt-shadow-window: 0px 0px 0px 0px rgba(156, 163, 175, .1), 0px 1px 2px 0px rgba(156, 163, 175, .12), 0px 4px 4px 0px rgba(156, 163, 175, .1), 0px 10px 6px 0px rgba(156, 163, 175, .08), 0px 17px 7px 0px rgba(156, 163, 175, .05), 0px 26px 7px 0px rgba(156, 163, 175, .02);--ngt-spacing-xs: 4px;--ngt-spacing-sm: 6px;--ngt-spacing-md: 12px;--ngt-spacing-lg: 16px;--ngt-window-padding: 16px;--ngt-font-size-xxs: .65rem;--ngt-font-size-xs: .75rem;--ngt-font-size-sm: .875rem;--ngt-font-size-md: 1rem;--ngt-font-size-lg: 1.25rem;--ngt-font-size-xl: 2rem;--ngt-background-secondary: var(--ngt-bg-primary);--ngt-background-hover: var(--ngt-hover-bg);--ngt-primary: #df30d4;--ngt-primary-rgb: 223, 48, 212;--ngt-text-on-primary: rgb(255, 255, 255);--ngt-border-color: var(--ngt-border-primary);--ngt-note-background: rgb(219, 234, 254);--ngt-note-border: rgba(37, 99, 235, .2);--ngt-warning-background: rgb(254, 249, 195);--ngt-warning-border: rgba(202, 138, 4, .2);--ngt-error-background: rgb(254, 226, 226);--ngt-error-border: rgba(220, 38, 38, .2);display:inline-block;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",Segoe UI Symbol,\"Noto Color Emoji\"}.ngt-select{position:relative;width:100%;min-width:120px;display:flex;align-items:center;justify-content:space-between;padding:var(--ngt-spacing-sm) var(--ngt-spacing-md);border:1px solid var(--ngt-border-primary);border-radius:var(--ngt-border-radius-small);background-color:var(--ngt-bg-primary);color:var(--ngt-text-primary);font-size:var(--ngt-font-size-sm);cursor:pointer;-webkit-user-select:none;user-select:none;transition:var(--ngt-transition-default);outline:none;min-height:36px;box-sizing:border-box;box-shadow:0 1px 2px #0000000d}.ngt-select:hover{background-color:var(--ngt-hover-bg);border-color:var(--ngt-primary);box-shadow:0 1px 3px #0000001a}.ngt-select:focus-visible{outline:none;border-color:var(--ngt-primary);box-shadow:0 0 0 2px rgba(var(--ngt-primary-rgb),.2),0 1px 3px #0000001a}.ngt-select.small{padding:var(--ngt-spacing-xs) var(--ngt-spacing-sm);font-size:var(--ngt-font-size-sm);height:24px}.ngt-select.open{border-color:var(--ngt-primary);box-shadow:0 0 0 2px rgba(var(--ngt-primary-rgb),.2),0 1px 3px #0000001a}.ngt-select.open .select__arrow{transform:rotate(180deg)}.ngt-select.placeholder{color:var(--ngt-text-muted)}.ngt-select__value{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:1.4;margin-right:var(--ngt-spacing-sm);min-width:0;display:flex;align-items:center}.ngt-select__arrow{width:16px;height:16px;flex-shrink:0;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:center;background-size:contain;transition:transform .2s ease;opacity:.9}.ngt-select-menu{display:inline-flex;flex-direction:column;min-width:180px;background-color:var(--ngt-bg-primary);padding:var(--ngt-spacing-sm) 0;border:2px solid var(--ngt-border-primary);border-radius:var(--ngt-border-radius-large);box-shadow:0 4px 12px #00000026,0 2px 4px #0000001a;color:var(--ngt-text-primary);max-height:min(400px,70vh);overflow-y:auto;backdrop-filter:blur(8px);z-index:1000}.ngt-select-menu::-webkit-scrollbar{width:8px;height:8px}.ngt-select-menu::-webkit-scrollbar-track{background:transparent}.ngt-select-menu::-webkit-scrollbar-thumb{background-color:var(--ngt-border-primary);border-radius:4px;border:2px solid var(--ngt-bg-primary)}.ngt-select-menu::-webkit-scrollbar-thumb:hover{background-color:var(--ngt-text-secondary)}.select-menu-item{background-color:transparent;cursor:pointer;border:none;color:var(--ngt-text-primary);-webkit-user-select:none;user-select:none;min-width:64px;padding:var(--ngt-spacing-sm) var(--ngt-spacing-md) var(--ngt-spacing-sm) var(--ngt-spacing-lg);display:flex;align-items:center;flex-direction:row;flex:1;font-size:var(--ngt-font-size-xs);font-family:inherit;position:relative;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;font-weight:400;line-height:1.5;transition:background-color .15s ease}.select-menu-item:hover{background-color:#0000000f}.select-menu-item:active{background-color:rgba(var(--ngt-primary-rgb),.15)}.select-menu-item.selected{color:var(--ngt-primary);background-color:rgba(var(--ngt-primary-rgb),.08);font-weight:400}.select-menu-item.selected:hover{background-color:rgba(var(--ngt-primary-rgb),.12)}.select-menu-item.selected:before{content:\"\";position:absolute;left:0;top:8px;width:3px;height:calc(100% - 16px);background-color:var(--ngt-primary);border-radius:2px}.select-menu-item:focus-visible{outline:none;background-color:#0000000f}.select-menu-item:first-child{margin-top:var(--ngt-spacing-xs)}.select-menu-item:last-child{margin-bottom:var(--ngt-spacing-xs)}.select-overlay{backdrop-filter:blur(8px)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$1.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$1.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "ngmodule", type: CdkMenuModule }, { kind: "directive", type: i2.CdkMenu, selector: "[cdkMenu]", outputs: ["closed"], exportAs: ["cdkMenu"] }, { kind: "directive", type: i2.CdkMenuItem, selector: "[cdkMenuItem]", inputs: ["cdkMenuItemDisabled", "cdkMenuitemTypeaheadLabel"], outputs: ["cdkMenuItemTriggered"], exportAs: ["cdkMenuItem"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3136
2039
  }
3137
2040
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarSelectComponent, decorators: [{
3138
2041
  type: Component,
@@ -3141,6 +2044,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImpor
3141
2044
  class="ngt-select"
3142
2045
  [class.small]="size() === 'small'"
3143
2046
  [class.open]="isOpen()"
2047
+ [class.placeholder]="isPlaceholder()"
3144
2048
  [attr.aria-label]="ariaLabel()"
3145
2049
  [attr.aria-expanded]="isOpen()"
3146
2050
  [attr.aria-controls]="selectMenuId"
@@ -3173,20 +2077,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImpor
3173
2077
  [attr.data-theme]="theme()"
3174
2078
  >
3175
2079
  @for (option of options(); track option.value) {
3176
- <button
3177
- class="select-menu-item"
3178
- [class.selected]="option.value === value()"
3179
- [attr.aria-selected]="option.value === value()"
3180
- cdkMenuItem
3181
- type="button"
3182
- (click)="selectOption(option)"
3183
- >
3184
- {{ option.label }}
3185
- </button>
2080
+ <button
2081
+ class="select-menu-item"
2082
+ [class.selected]="option.value === value()"
2083
+ [attr.aria-selected]="option.value === value()"
2084
+ cdkMenuItem
2085
+ type="button"
2086
+ (click)="selectOption(option)"
2087
+ >
2088
+ {{ option.label }}
2089
+ </button>
3186
2090
  }
3187
2091
  </div>
3188
2092
  </ng-template>
3189
- `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{--ngt-border-radius-small: 4px;--ngt-border-radius-medium: 8px;--ngt-border-radius-large: 12px;--ngt-transition-default: all .2s ease-out;--ngt-transition-smooth: all .2s ease-in-out;--ngt-bg-primary: rgb(255, 255, 255);--ngt-bg-gradient: linear-gradient(180deg, rgb(243, 244, 246) 0%, rgba(243, 244, 246, .88) 100%);--ngt-text-primary: rgb(17, 24, 39);--ngt-text-secondary: rgb(55, 65, 81);--ngt-text-muted: rgb(107, 114, 128);--ngt-border-primary: #e5e7eb;--ngt-border-subtle: rgba(17, 24, 39, .1);--ngt-hover-bg: rgba(17, 24, 39, .05);--ngt-hover-danger: rgb(239, 68, 68);--ngt-shadow-toolbar: 0 2px 8px rgba(156, 163, 175, .2);--ngt-shadow-tooltip: 0 0 0 1px rgba(17, 24, 39, .05), 0 4px 8px rgba(107, 114, 128, .15), 0 2px 4px rgba(107, 114, 128, .1);--ngt-shadow-window: 0px 0px 0px 0px rgba(156, 163, 175, .1), 0px 1px 2px 0px rgba(156, 163, 175, .12), 0px 4px 4px 0px rgba(156, 163, 175, .1), 0px 10px 6px 0px rgba(156, 163, 175, .08), 0px 17px 7px 0px rgba(156, 163, 175, .05), 0px 26px 7px 0px rgba(156, 163, 175, .02);--ngt-spacing-xs: 4px;--ngt-spacing-sm: 6px;--ngt-spacing-md: 12px;--ngt-spacing-lg: 16px;--ngt-window-padding: 16px;--ngt-font-size-xxs: .65rem;--ngt-font-size-xs: .75rem;--ngt-font-size-sm: .875rem;--ngt-font-size-md: 1rem;--ngt-font-size-lg: 1.25rem;--ngt-font-size-xl: 2rem;--ngt-background-secondary: var(--ngt-bg-primary);--ngt-background-hover: var(--ngt-hover-bg);--ngt-primary: #df30d4;--ngt-primary-rgb: 223, 48, 212;--ngt-text-on-primary: rgb(255, 255, 255);--ngt-border-color: var(--ngt-border-primary);--ngt-note-background: rgb(219, 234, 254);--ngt-note-border: rgba(37, 99, 235, .2);--ngt-warning-background: rgb(254, 249, 195);--ngt-warning-border: rgba(202, 138, 4, .2);--ngt-error-background: rgb(254, 226, 226);--ngt-error-border: rgba(220, 38, 38, .2);display:inline-block;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",Segoe UI Symbol,\"Noto Color Emoji\"}.ngt-select{position:relative;width:100%;min-width:120px;display:flex;align-items:center;justify-content:space-between;padding:var(--ngt-spacing-sm) var(--ngt-spacing-md);border:1px solid var(--ngt-border-primary);border-radius:var(--ngt-border-radius-small);background-color:var(--ngt-bg-primary);color:var(--ngt-text-primary);font-size:var(--ngt-font-size-sm);cursor:pointer;-webkit-user-select:none;user-select:none;transition:var(--ngt-transition-default);outline:none;min-height:36px;box-sizing:border-box;box-shadow:0 1px 2px #0000000d}.ngt-select:hover{background-color:var(--ngt-hover-bg);border-color:var(--ngt-primary);box-shadow:0 1px 3px #0000001a}.ngt-select:focus-visible{outline:none;border-color:var(--ngt-primary);box-shadow:0 0 0 2px rgba(var(--ngt-primary-rgb),.2),0 1px 3px #0000001a}.ngt-select.small{padding:var(--ngt-spacing-xs) var(--ngt-spacing-sm);font-size:var(--ngt-font-size-sm);height:24px}.ngt-select.open{border-color:var(--ngt-primary);box-shadow:0 0 0 2px rgba(var(--ngt-primary-rgb),.2),0 1px 3px #0000001a}.ngt-select.open .select__arrow{transform:rotate(180deg)}.ngt-select__value{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:1.4;margin-right:var(--ngt-spacing-sm);min-width:0;display:flex;align-items:center}.ngt-select__arrow{width:16px;height:16px;flex-shrink:0;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:center;background-size:contain;transition:transform .2s ease;opacity:.9}.ngt-select-menu{display:inline-flex;flex-direction:column;min-width:180px;background-color:var(--ngt-bg-primary);padding:var(--ngt-spacing-sm) 0;border:2px solid var(--ngt-border-primary);border-radius:var(--ngt-border-radius-large);box-shadow:0 4px 12px #00000026,0 2px 4px #0000001a;color:var(--ngt-text-primary);max-height:min(400px,70vh);overflow-y:auto;backdrop-filter:blur(8px);z-index:1000}.ngt-select-menu::-webkit-scrollbar{width:8px;height:8px}.ngt-select-menu::-webkit-scrollbar-track{background:transparent}.ngt-select-menu::-webkit-scrollbar-thumb{background-color:var(--ngt-border-primary);border-radius:4px;border:2px solid var(--ngt-bg-primary)}.ngt-select-menu::-webkit-scrollbar-thumb:hover{background-color:var(--ngt-text-secondary)}.select-menu-item{background-color:transparent;cursor:pointer;border:none;color:var(--ngt-text-primary);-webkit-user-select:none;user-select:none;min-width:64px;padding:var(--ngt-spacing-sm) var(--ngt-spacing-md) var(--ngt-spacing-sm) var(--ngt-spacing-lg);display:flex;align-items:center;flex-direction:row;flex:1;font-size:var(--ngt-font-size-xs);font-family:inherit;position:relative;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;font-weight:400;line-height:1.5;transition:background-color .15s ease}.select-menu-item:hover{background-color:#0000000f}.select-menu-item:active{background-color:rgba(var(--ngt-primary-rgb),.15)}.select-menu-item.selected{color:var(--ngt-primary);background-color:rgba(var(--ngt-primary-rgb),.08);font-weight:400}.select-menu-item.selected:hover{background-color:rgba(var(--ngt-primary-rgb),.12)}.select-menu-item.selected:before{content:\"\";position:absolute;left:0;top:8px;width:3px;height:calc(100% - 16px);background-color:var(--ngt-primary);border-radius:2px}.select-menu-item:focus-visible{outline:none;background-color:#0000000f}.select-menu-item:first-child{margin-top:var(--ngt-spacing-xs)}.select-menu-item:last-child{margin-bottom:var(--ngt-spacing-xs)}.select-overlay{backdrop-filter:blur(8px)}\n"] }]
2093
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{--ngt-border-radius-small: 4px;--ngt-border-radius-medium: 8px;--ngt-border-radius-large: 12px;--ngt-transition-default: all .2s ease-out;--ngt-transition-smooth: all .2s ease-in-out;--ngt-bg-primary: rgb(255, 255, 255);--ngt-bg-gradient: linear-gradient(180deg, rgb(243, 244, 246) 0%, rgba(243, 244, 246, .88) 100%);--ngt-text-primary: rgb(17, 24, 39);--ngt-text-secondary: rgb(55, 65, 81);--ngt-text-muted: rgb(107, 114, 128);--ngt-border-primary: #e5e7eb;--ngt-border-subtle: rgba(17, 24, 39, .1);--ngt-hover-bg: rgba(17, 24, 39, .05);--ngt-hover-danger: rgb(239, 68, 68);--ngt-shadow-toolbar: 0 2px 8px rgba(156, 163, 175, .2);--ngt-shadow-tooltip: 0 0 0 1px rgba(17, 24, 39, .05), 0 4px 8px rgba(107, 114, 128, .15), 0 2px 4px rgba(107, 114, 128, .1);--ngt-shadow-window: 0px 0px 0px 0px rgba(156, 163, 175, .1), 0px 1px 2px 0px rgba(156, 163, 175, .12), 0px 4px 4px 0px rgba(156, 163, 175, .1), 0px 10px 6px 0px rgba(156, 163, 175, .08), 0px 17px 7px 0px rgba(156, 163, 175, .05), 0px 26px 7px 0px rgba(156, 163, 175, .02);--ngt-spacing-xs: 4px;--ngt-spacing-sm: 6px;--ngt-spacing-md: 12px;--ngt-spacing-lg: 16px;--ngt-window-padding: 16px;--ngt-font-size-xxs: .65rem;--ngt-font-size-xs: .75rem;--ngt-font-size-sm: .875rem;--ngt-font-size-md: 1rem;--ngt-font-size-lg: 1.25rem;--ngt-font-size-xl: 2rem;--ngt-background-secondary: var(--ngt-bg-primary);--ngt-background-hover: var(--ngt-hover-bg);--ngt-primary: #df30d4;--ngt-primary-rgb: 223, 48, 212;--ngt-text-on-primary: rgb(255, 255, 255);--ngt-border-color: var(--ngt-border-primary);--ngt-note-background: rgb(219, 234, 254);--ngt-note-border: rgba(37, 99, 235, .2);--ngt-warning-background: rgb(254, 249, 195);--ngt-warning-border: rgba(202, 138, 4, .2);--ngt-error-background: rgb(254, 226, 226);--ngt-error-border: rgba(220, 38, 38, .2);display:inline-block;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",Segoe UI Symbol,\"Noto Color Emoji\"}.ngt-select{position:relative;width:100%;min-width:120px;display:flex;align-items:center;justify-content:space-between;padding:var(--ngt-spacing-sm) var(--ngt-spacing-md);border:1px solid var(--ngt-border-primary);border-radius:var(--ngt-border-radius-small);background-color:var(--ngt-bg-primary);color:var(--ngt-text-primary);font-size:var(--ngt-font-size-sm);cursor:pointer;-webkit-user-select:none;user-select:none;transition:var(--ngt-transition-default);outline:none;min-height:36px;box-sizing:border-box;box-shadow:0 1px 2px #0000000d}.ngt-select:hover{background-color:var(--ngt-hover-bg);border-color:var(--ngt-primary);box-shadow:0 1px 3px #0000001a}.ngt-select:focus-visible{outline:none;border-color:var(--ngt-primary);box-shadow:0 0 0 2px rgba(var(--ngt-primary-rgb),.2),0 1px 3px #0000001a}.ngt-select.small{padding:var(--ngt-spacing-xs) var(--ngt-spacing-sm);font-size:var(--ngt-font-size-sm);height:24px}.ngt-select.open{border-color:var(--ngt-primary);box-shadow:0 0 0 2px rgba(var(--ngt-primary-rgb),.2),0 1px 3px #0000001a}.ngt-select.open .select__arrow{transform:rotate(180deg)}.ngt-select.placeholder{color:var(--ngt-text-muted)}.ngt-select__value{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:1.4;margin-right:var(--ngt-spacing-sm);min-width:0;display:flex;align-items:center}.ngt-select__arrow{width:16px;height:16px;flex-shrink:0;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:center;background-size:contain;transition:transform .2s ease;opacity:.9}.ngt-select-menu{display:inline-flex;flex-direction:column;min-width:180px;background-color:var(--ngt-bg-primary);padding:var(--ngt-spacing-sm) 0;border:2px solid var(--ngt-border-primary);border-radius:var(--ngt-border-radius-large);box-shadow:0 4px 12px #00000026,0 2px 4px #0000001a;color:var(--ngt-text-primary);max-height:min(400px,70vh);overflow-y:auto;backdrop-filter:blur(8px);z-index:1000}.ngt-select-menu::-webkit-scrollbar{width:8px;height:8px}.ngt-select-menu::-webkit-scrollbar-track{background:transparent}.ngt-select-menu::-webkit-scrollbar-thumb{background-color:var(--ngt-border-primary);border-radius:4px;border:2px solid var(--ngt-bg-primary)}.ngt-select-menu::-webkit-scrollbar-thumb:hover{background-color:var(--ngt-text-secondary)}.select-menu-item{background-color:transparent;cursor:pointer;border:none;color:var(--ngt-text-primary);-webkit-user-select:none;user-select:none;min-width:64px;padding:var(--ngt-spacing-sm) var(--ngt-spacing-md) var(--ngt-spacing-sm) var(--ngt-spacing-lg);display:flex;align-items:center;flex-direction:row;flex:1;font-size:var(--ngt-font-size-xs);font-family:inherit;position:relative;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;font-weight:400;line-height:1.5;transition:background-color .15s ease}.select-menu-item:hover{background-color:#0000000f}.select-menu-item:active{background-color:rgba(var(--ngt-primary-rgb),.15)}.select-menu-item.selected{color:var(--ngt-primary);background-color:rgba(var(--ngt-primary-rgb),.08);font-weight:400}.select-menu-item.selected:hover{background-color:rgba(var(--ngt-primary-rgb),.12)}.select-menu-item.selected:before{content:\"\";position:absolute;left:0;top:8px;width:3px;height:calc(100% - 16px);background-color:var(--ngt-primary);border-radius:2px}.select-menu-item:focus-visible{outline:none;background-color:#0000000f}.select-menu-item:first-child{margin-top:var(--ngt-spacing-xs)}.select-menu-item:last-child{margin-bottom:var(--ngt-spacing-xs)}.select-overlay{backdrop-filter:blur(8px)}\n"] }]
3190
2094
  }] });
3191
2095
 
3192
2096
  class ToolbarToolButtonComponent {
@@ -3366,255 +2270,570 @@ class ToolbarWindowComponent {
3366
2270
  </div>
3367
2271
  </div>
3368
2272
 
3369
- <div class="divider"></div>
3370
- <div class="ngt-content">
3371
- <ng-content></ng-content>
3372
- </div>
2273
+ <div class="divider"></div>
2274
+ <div class="ngt-content">
2275
+ <ng-content></ng-content>
2276
+ </div>
2277
+ </div>
2278
+ `, isInline: true, styles: [":host{--ngt-border-radius-small: 4px;--ngt-border-radius-medium: 8px;--ngt-border-radius-large: 12px;--ngt-transition-default: all .2s ease-out;--ngt-transition-smooth: all .2s ease-in-out;--ngt-bg-primary: rgb(255, 255, 255);--ngt-bg-gradient: linear-gradient(180deg, rgb(243, 244, 246) 0%, rgba(243, 244, 246, .88) 100%);--ngt-text-primary: rgb(17, 24, 39);--ngt-text-secondary: rgb(55, 65, 81);--ngt-text-muted: rgb(107, 114, 128);--ngt-border-primary: #e5e7eb;--ngt-border-subtle: rgba(17, 24, 39, .1);--ngt-hover-bg: rgba(17, 24, 39, .05);--ngt-hover-danger: rgb(239, 68, 68);--ngt-shadow-toolbar: 0 2px 8px rgba(156, 163, 175, .2);--ngt-shadow-tooltip: 0 0 0 1px rgba(17, 24, 39, .05), 0 4px 8px rgba(107, 114, 128, .15), 0 2px 4px rgba(107, 114, 128, .1);--ngt-shadow-window: 0px 0px 0px 0px rgba(156, 163, 175, .1), 0px 1px 2px 0px rgba(156, 163, 175, .12), 0px 4px 4px 0px rgba(156, 163, 175, .1), 0px 10px 6px 0px rgba(156, 163, 175, .08), 0px 17px 7px 0px rgba(156, 163, 175, .05), 0px 26px 7px 0px rgba(156, 163, 175, .02);--ngt-spacing-xs: 4px;--ngt-spacing-sm: 6px;--ngt-spacing-md: 12px;--ngt-spacing-lg: 16px;--ngt-window-padding: 16px;--ngt-font-size-xxs: .65rem;--ngt-font-size-xs: .75rem;--ngt-font-size-sm: .875rem;--ngt-font-size-md: 1rem;--ngt-font-size-lg: 1.25rem;--ngt-font-size-xl: 2rem;--ngt-background-secondary: var(--ngt-bg-primary);--ngt-background-hover: var(--ngt-hover-bg);--ngt-primary: #df30d4;--ngt-primary-rgb: 223, 48, 212;--ngt-text-on-primary: rgb(255, 255, 255);--ngt-border-color: var(--ngt-border-primary);--ngt-note-background: rgb(219, 234, 254);--ngt-note-border: rgba(37, 99, 235, .2);--ngt-warning-background: rgb(254, 249, 195);--ngt-warning-border: rgba(202, 138, 4, .2);--ngt-error-background: rgb(254, 226, 226);--ngt-error-border: rgba(220, 38, 38, .2);display:block;width:100%;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",Segoe UI Symbol,\"Noto Color Emoji\"}.ngt-window{box-sizing:border-box;display:flex;flex-direction:column;position:relative;width:100%;height:100%;background:var(--ngt-bg-primary);border:1px solid var(--ngt-border-primary);border-radius:var(--ngt-border-radius-large);padding:var(--ngt-window-padding) var(--ngt-window-padding) var(--ngt-spacing-xs);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(--ngt-text-secondary);z-index:999999999;box-shadow:var(--ngt-shadow-window);contain:layout style}.ngt-header{display:flex;flex-direction:row;justify-content:space-between;align-items:flex-start;position:relative;z-index:10;flex-shrink:0}.ngt-header h1{font-size:var(--ngt-font-size-lg);line-height:1.2;margin:0;color:var(--ngt-text-primary)}.ngt-header__title{display:flex;align-items:center;gap:var(--ngt-spacing-sm);flex-wrap:wrap;margin-bottom:var(--ngt-spacing-xs)}.ngt-header__title .beta-tag{font-size:var(--ngt-font-size-xxs, .65rem);background:var(--ngt-purple, #8b5cf6);color:var(--ngt-text-on-primary);padding:2px 6px;border-radius:var(--ngt-border-radius-small);font-weight:600;text-transform:uppercase;letter-spacing:.5px;line-height:1;white-space:nowrap}.ngt-header__description{font-size:var(--ngt-font-size-sm);color:var(--ngt-text-muted);word-wrap:break-word;overflow-wrap:break-word;max-width:100%;line-height:1.4;margin:0}.ngt-header__content{display:flex;flex-direction:column;gap:var(--ngt-spacing-xs);flex:1;min-width:0}.ngt-header__controls{display:flex;gap:var(--ngt-spacing-sm);flex-shrink:0;align-items:flex-start}.ngt-content{position:relative;flex:1;overflow:auto;min-height:0}.divider{height:1px;background-color:var(--ngt-border-primary);margin-bottom:var(--ngt-spacing-md);margin-top:var(--ngt-spacing-md);flex-shrink:0;position:relative;z-index:5}.control{background:none;border:none;color:var(--ngt-text-secondary);cursor:pointer;padding:var(--ngt-spacing-xs) var(--ngt-spacing-sm);border-radius:var(--ngt-border-radius-small);font-size:var(--ngt-font-size-md);line-height:1;transition:var(--ngt-transition-smooth)}.control:hover{background:var(--ngt-hover-bg);color:var(--ngt-text-primary)}.control--close:hover{background:var(--ngt-hover-danger)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2279
+ }
2280
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarWindowComponent, decorators: [{
2281
+ type: Component,
2282
+ args: [{ selector: 'ngt-window', standalone: true, template: `
2283
+ <div class="ngt-window" [attr.data-theme]="theme()">
2284
+ <div class="ngt-header">
2285
+ <div class="ngt-header__content">
2286
+ <div class="ngt-header__title">
2287
+ <h1>{{ config().title }}</h1>
2288
+ @if (config().isBeta) {
2289
+ <span class="beta-tag">BETA</span>
2290
+ }
2291
+ </div>
2292
+ @if (config().description) {
2293
+ <p class="ngt-header__description">{{ config().description }}</p>
2294
+ }
2295
+ </div>
2296
+ <div class="ngt-header__controls">
2297
+ @if (config().isMinimizable) {
2298
+ <button aria-label="Minimize" class="control" (click)="onMinimize()">
2299
+
2300
+ </button>
2301
+ } @if (config().isMaximizable) {
2302
+ <button aria-label="Maximize" class="control" (click)="onMaximize()">
2303
+
2304
+ </button>
2305
+ } @if (config().isClosable) {
2306
+ <button
2307
+ aria-label="Close"
2308
+ class="control control--close"
2309
+ (click)="onClose()"
2310
+ >
2311
+ ×
2312
+ </button>
2313
+ }
2314
+ </div>
2315
+ </div>
2316
+
2317
+ <div class="divider"></div>
2318
+ <div class="ngt-content">
2319
+ <ng-content></ng-content>
2320
+ </div>
2321
+ </div>
2322
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{--ngt-border-radius-small: 4px;--ngt-border-radius-medium: 8px;--ngt-border-radius-large: 12px;--ngt-transition-default: all .2s ease-out;--ngt-transition-smooth: all .2s ease-in-out;--ngt-bg-primary: rgb(255, 255, 255);--ngt-bg-gradient: linear-gradient(180deg, rgb(243, 244, 246) 0%, rgba(243, 244, 246, .88) 100%);--ngt-text-primary: rgb(17, 24, 39);--ngt-text-secondary: rgb(55, 65, 81);--ngt-text-muted: rgb(107, 114, 128);--ngt-border-primary: #e5e7eb;--ngt-border-subtle: rgba(17, 24, 39, .1);--ngt-hover-bg: rgba(17, 24, 39, .05);--ngt-hover-danger: rgb(239, 68, 68);--ngt-shadow-toolbar: 0 2px 8px rgba(156, 163, 175, .2);--ngt-shadow-tooltip: 0 0 0 1px rgba(17, 24, 39, .05), 0 4px 8px rgba(107, 114, 128, .15), 0 2px 4px rgba(107, 114, 128, .1);--ngt-shadow-window: 0px 0px 0px 0px rgba(156, 163, 175, .1), 0px 1px 2px 0px rgba(156, 163, 175, .12), 0px 4px 4px 0px rgba(156, 163, 175, .1), 0px 10px 6px 0px rgba(156, 163, 175, .08), 0px 17px 7px 0px rgba(156, 163, 175, .05), 0px 26px 7px 0px rgba(156, 163, 175, .02);--ngt-spacing-xs: 4px;--ngt-spacing-sm: 6px;--ngt-spacing-md: 12px;--ngt-spacing-lg: 16px;--ngt-window-padding: 16px;--ngt-font-size-xxs: .65rem;--ngt-font-size-xs: .75rem;--ngt-font-size-sm: .875rem;--ngt-font-size-md: 1rem;--ngt-font-size-lg: 1.25rem;--ngt-font-size-xl: 2rem;--ngt-background-secondary: var(--ngt-bg-primary);--ngt-background-hover: var(--ngt-hover-bg);--ngt-primary: #df30d4;--ngt-primary-rgb: 223, 48, 212;--ngt-text-on-primary: rgb(255, 255, 255);--ngt-border-color: var(--ngt-border-primary);--ngt-note-background: rgb(219, 234, 254);--ngt-note-border: rgba(37, 99, 235, .2);--ngt-warning-background: rgb(254, 249, 195);--ngt-warning-border: rgba(202, 138, 4, .2);--ngt-error-background: rgb(254, 226, 226);--ngt-error-border: rgba(220, 38, 38, .2);display:block;width:100%;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",Segoe UI Symbol,\"Noto Color Emoji\"}.ngt-window{box-sizing:border-box;display:flex;flex-direction:column;position:relative;width:100%;height:100%;background:var(--ngt-bg-primary);border:1px solid var(--ngt-border-primary);border-radius:var(--ngt-border-radius-large);padding:var(--ngt-window-padding) var(--ngt-window-padding) var(--ngt-spacing-xs);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(--ngt-text-secondary);z-index:999999999;box-shadow:var(--ngt-shadow-window);contain:layout style}.ngt-header{display:flex;flex-direction:row;justify-content:space-between;align-items:flex-start;position:relative;z-index:10;flex-shrink:0}.ngt-header h1{font-size:var(--ngt-font-size-lg);line-height:1.2;margin:0;color:var(--ngt-text-primary)}.ngt-header__title{display:flex;align-items:center;gap:var(--ngt-spacing-sm);flex-wrap:wrap;margin-bottom:var(--ngt-spacing-xs)}.ngt-header__title .beta-tag{font-size:var(--ngt-font-size-xxs, .65rem);background:var(--ngt-purple, #8b5cf6);color:var(--ngt-text-on-primary);padding:2px 6px;border-radius:var(--ngt-border-radius-small);font-weight:600;text-transform:uppercase;letter-spacing:.5px;line-height:1;white-space:nowrap}.ngt-header__description{font-size:var(--ngt-font-size-sm);color:var(--ngt-text-muted);word-wrap:break-word;overflow-wrap:break-word;max-width:100%;line-height:1.4;margin:0}.ngt-header__content{display:flex;flex-direction:column;gap:var(--ngt-spacing-xs);flex:1;min-width:0}.ngt-header__controls{display:flex;gap:var(--ngt-spacing-sm);flex-shrink:0;align-items:flex-start}.ngt-content{position:relative;flex:1;overflow:auto;min-height:0}.divider{height:1px;background-color:var(--ngt-border-primary);margin-bottom:var(--ngt-spacing-md);margin-top:var(--ngt-spacing-md);flex-shrink:0;position:relative;z-index:5}.control{background:none;border:none;color:var(--ngt-text-secondary);cursor:pointer;padding:var(--ngt-spacing-xs) var(--ngt-spacing-sm);border-radius:var(--ngt-border-radius-small);font-size:var(--ngt-font-size-md);line-height:1;transition:var(--ngt-transition-smooth)}.control:hover{background:var(--ngt-hover-bg);color:var(--ngt-text-primary)}.control--close:hover{background:var(--ngt-hover-danger)}\n"] }]
2323
+ }] });
2324
+
2325
+ class ToolbarToolComponent {
2326
+ constructor() {
2327
+ this.state = inject(ToolbarStateService);
2328
+ this.buttonContainer = viewChild.required('buttonContainer');
2329
+ this.buttonComponent = contentChild(ToolbarToolButtonComponent);
2330
+ this.options = input.required();
2331
+ this.icon = input.required();
2332
+ this.title = input.required();
2333
+ this.badge = input();
2334
+ this.isActive = computed(() => this.state.activeToolId() === this.options().id);
2335
+ this.height = computed(() => {
2336
+ switch (this.options().size) {
2337
+ case 'small':
2338
+ return 320;
2339
+ case 'medium':
2340
+ return 480;
2341
+ case 'tall':
2342
+ return 620;
2343
+ case 'large':
2344
+ return 620;
2345
+ default:
2346
+ return 480;
2347
+ }
2348
+ });
2349
+ this.width = computed(() => {
2350
+ switch (this.options().size) {
2351
+ case 'small':
2352
+ return 320;
2353
+ case 'medium':
2354
+ return 480;
2355
+ case 'tall':
2356
+ return 480;
2357
+ case 'large':
2358
+ return 620;
2359
+ default:
2360
+ return 400;
2361
+ }
2362
+ });
2363
+ this.positions = computed(() => {
2364
+ const triggerXPosition = this.getButtonContainerXPosition();
2365
+ const windowCenter = window.innerWidth / 2;
2366
+ const offsetX = windowCenter - triggerXPosition - 22;
2367
+ return [
2368
+ {
2369
+ originX: 'center',
2370
+ originY: 'center',
2371
+ overlayX: 'center',
2372
+ overlayY: 'center',
2373
+ offsetY: -(Math.ceil(this.height() / 2) + 36),
2374
+ offsetX,
2375
+ },
2376
+ ];
2377
+ });
2378
+ }
2379
+ onOpen() {
2380
+ this.state.setActiveTool(this.options().id);
2381
+ }
2382
+ onClose() {
2383
+ this.state.setActiveTool(null);
2384
+ }
2385
+ getButtonContainerXPosition() {
2386
+ const buttonContainerRect = this.buttonContainer()?.nativeElement?.getBoundingClientRect();
2387
+ return buttonContainerRect?.left ?? 0;
2388
+ }
2389
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarToolComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2390
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.7", type: ToolbarToolComponent, isStandalone: true, selector: "ngt-toolbar-tool", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: true, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, badge: { classPropertyName: "badge", publicName: "badge", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "buttonComponent", first: true, predicate: ToolbarToolButtonComponent, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "buttonContainer", first: true, predicate: ["buttonContainer"], descendants: true, isSignal: true }], ngImport: i0, template: `
2391
+ <div #trigger="cdkOverlayOrigin" class="ngt-toolbar-tool" cdkOverlayOrigin>
2392
+ <div
2393
+ class="ngt-toolbar-tool__icon"
2394
+ (click)="onOpen()"
2395
+ (keydown.enter)="onOpen()"
2396
+ (keydown.space)="onOpen()"
2397
+ tabindex="0"
2398
+ >
2399
+ <div #buttonContainer [attr.data-tooltip]="title()">
2400
+ @if (icon()) {
2401
+ <ngt-tool-button [title]="title()" [toolId]="options().id" [badge]="badge()">
2402
+ <ngt-icon [name]="icon()" />
2403
+ </ngt-tool-button>
2404
+ } @else {
2405
+ <ng-content select="ngt-tool-button"></ng-content>
2406
+ }
2407
+ </div>
2408
+ </div>
2409
+
2410
+ @if (isActive()) {
2411
+ <ng-template
2412
+ #contentTemplate
2413
+ [cdkConnectedOverlayOrigin]="trigger"
2414
+ [cdkConnectedOverlayOpen]="isActive()"
2415
+ [cdkConnectedOverlayPositions]="positions()"
2416
+ [cdkConnectedOverlayWidth]="width()"
2417
+ [cdkConnectedOverlayHeight]="height()"
2418
+ [cdkConnectedOverlayPanelClass]="['ngt-overlay-panel', 'ngt-tool-overlay']"
2419
+ cdkConnectedOverlay
2420
+ >
2421
+ <ngt-window [@slideAnimation] [config]="options()" (closed)="onClose()">
2422
+ <ng-content />
2423
+ </ngt-window>
2424
+ </ng-template>
2425
+ }
3373
2426
  </div>
3374
- `, isInline: true, styles: [":host{--ngt-border-radius-small: 4px;--ngt-border-radius-medium: 8px;--ngt-border-radius-large: 12px;--ngt-transition-default: all .2s ease-out;--ngt-transition-smooth: all .2s ease-in-out;--ngt-bg-primary: rgb(255, 255, 255);--ngt-bg-gradient: linear-gradient(180deg, rgb(243, 244, 246) 0%, rgba(243, 244, 246, .88) 100%);--ngt-text-primary: rgb(17, 24, 39);--ngt-text-secondary: rgb(55, 65, 81);--ngt-text-muted: rgb(107, 114, 128);--ngt-border-primary: #e5e7eb;--ngt-border-subtle: rgba(17, 24, 39, .1);--ngt-hover-bg: rgba(17, 24, 39, .05);--ngt-hover-danger: rgb(239, 68, 68);--ngt-shadow-toolbar: 0 2px 8px rgba(156, 163, 175, .2);--ngt-shadow-tooltip: 0 0 0 1px rgba(17, 24, 39, .05), 0 4px 8px rgba(107, 114, 128, .15), 0 2px 4px rgba(107, 114, 128, .1);--ngt-shadow-window: 0px 0px 0px 0px rgba(156, 163, 175, .1), 0px 1px 2px 0px rgba(156, 163, 175, .12), 0px 4px 4px 0px rgba(156, 163, 175, .1), 0px 10px 6px 0px rgba(156, 163, 175, .08), 0px 17px 7px 0px rgba(156, 163, 175, .05), 0px 26px 7px 0px rgba(156, 163, 175, .02);--ngt-spacing-xs: 4px;--ngt-spacing-sm: 6px;--ngt-spacing-md: 12px;--ngt-spacing-lg: 16px;--ngt-window-padding: 16px;--ngt-font-size-xxs: .65rem;--ngt-font-size-xs: .75rem;--ngt-font-size-sm: .875rem;--ngt-font-size-md: 1rem;--ngt-font-size-lg: 1.25rem;--ngt-font-size-xl: 2rem;--ngt-background-secondary: var(--ngt-bg-primary);--ngt-background-hover: var(--ngt-hover-bg);--ngt-primary: #df30d4;--ngt-primary-rgb: 223, 48, 212;--ngt-text-on-primary: rgb(255, 255, 255);--ngt-border-color: var(--ngt-border-primary);--ngt-note-background: rgb(219, 234, 254);--ngt-note-border: rgba(37, 99, 235, .2);--ngt-warning-background: rgb(254, 249, 195);--ngt-warning-border: rgba(202, 138, 4, .2);--ngt-error-background: rgb(254, 226, 226);--ngt-error-border: rgba(220, 38, 38, .2);display:block;width:100%;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",Segoe UI Symbol,\"Noto Color Emoji\"}.ngt-window{box-sizing:border-box;display:flex;flex-direction:column;position:relative;width:100%;height:100%;background:var(--ngt-bg-primary);border:1px solid var(--ngt-border-primary);border-radius:var(--ngt-border-radius-large);padding:var(--ngt-window-padding) var(--ngt-window-padding) var(--ngt-spacing-xs);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(--ngt-text-secondary);z-index:999999999;box-shadow:var(--ngt-shadow-window);contain:layout style}.ngt-header{display:flex;flex-direction:row;justify-content:space-between;align-items:flex-start;position:relative;z-index:10;flex-shrink:0}.ngt-header h1{font-size:var(--ngt-font-size-lg);line-height:1.2;margin:0;color:var(--ngt-text-primary)}.ngt-header__title{display:flex;align-items:center;gap:var(--ngt-spacing-sm);flex-wrap:wrap;margin-bottom:var(--ngt-spacing-xs)}.ngt-header__title .beta-tag{font-size:var(--ngt-font-size-xxs, .65rem);background:var(--ngt-purple, #8b5cf6);color:var(--ngt-text-on-primary);padding:2px 6px;border-radius:var(--ngt-border-radius-small);font-weight:600;text-transform:uppercase;letter-spacing:.5px;line-height:1;white-space:nowrap}.ngt-header__description{font-size:var(--ngt-font-size-sm);color:var(--ngt-text-muted);word-wrap:break-word;overflow-wrap:break-word;max-width:100%;line-height:1.4;margin:0}.ngt-header__content{display:flex;flex-direction:column;gap:var(--ngt-spacing-xs);flex:1;min-width:0}.ngt-header__controls{display:flex;gap:var(--ngt-spacing-sm);flex-shrink:0;align-items:flex-start}.ngt-content{position:relative;flex:1;overflow:auto;min-height:0}.divider{height:1px;background-color:var(--ngt-border-primary);margin-bottom:var(--ngt-spacing-md);margin-top:var(--ngt-spacing-md);flex-shrink:0;position:relative;z-index:5}.control{background:none;border:none;color:var(--ngt-text-secondary);cursor:pointer;padding:var(--ngt-spacing-xs) var(--ngt-spacing-sm);border-radius:var(--ngt-border-radius-small);font-size:var(--ngt-font-size-md);line-height:1;transition:var(--ngt-transition-smooth)}.control:hover{background:var(--ngt-hover-bg);color:var(--ngt-text-primary)}.control--close:hover{background:var(--ngt-hover-danger)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2427
+ `, isInline: true, styles: [":host{--ngt-border-radius-small: 4px;--ngt-border-radius-medium: 8px;--ngt-border-radius-large: 12px;--ngt-transition-default: all .2s ease-out;--ngt-transition-smooth: all .2s ease-in-out;--ngt-bg-primary: rgb(255, 255, 255);--ngt-bg-gradient: linear-gradient(180deg, rgb(243, 244, 246) 0%, rgba(243, 244, 246, .88) 100%);--ngt-text-primary: rgb(17, 24, 39);--ngt-text-secondary: rgb(55, 65, 81);--ngt-text-muted: rgb(107, 114, 128);--ngt-border-primary: #e5e7eb;--ngt-border-subtle: rgba(17, 24, 39, .1);--ngt-hover-bg: rgba(17, 24, 39, .05);--ngt-hover-danger: rgb(239, 68, 68);--ngt-shadow-toolbar: 0 2px 8px rgba(156, 163, 175, .2);--ngt-shadow-tooltip: 0 0 0 1px rgba(17, 24, 39, .05), 0 4px 8px rgba(107, 114, 128, .15), 0 2px 4px rgba(107, 114, 128, .1);--ngt-shadow-window: 0px 0px 0px 0px rgba(156, 163, 175, .1), 0px 1px 2px 0px rgba(156, 163, 175, .12), 0px 4px 4px 0px rgba(156, 163, 175, .1), 0px 10px 6px 0px rgba(156, 163, 175, .08), 0px 17px 7px 0px rgba(156, 163, 175, .05), 0px 26px 7px 0px rgba(156, 163, 175, .02);--ngt-spacing-xs: 4px;--ngt-spacing-sm: 6px;--ngt-spacing-md: 12px;--ngt-spacing-lg: 16px;--ngt-window-padding: 16px;--ngt-font-size-xxs: .65rem;--ngt-font-size-xs: .75rem;--ngt-font-size-sm: .875rem;--ngt-font-size-md: 1rem;--ngt-font-size-lg: 1.25rem;--ngt-font-size-xl: 2rem;--ngt-background-secondary: var(--ngt-bg-primary);--ngt-background-hover: var(--ngt-hover-bg);--ngt-primary: #df30d4;--ngt-primary-rgb: 223, 48, 212;--ngt-text-on-primary: rgb(255, 255, 255);--ngt-border-color: var(--ngt-border-primary);--ngt-note-background: rgb(219, 234, 254);--ngt-note-border: rgba(37, 99, 235, .2);--ngt-warning-background: rgb(254, 249, 195);--ngt-warning-border: rgba(202, 138, 4, .2);--ngt-error-background: rgb(254, 226, 226);--ngt-error-border: rgba(220, 38, 38, .2)}.tool{position:relative}.trigger{cursor:pointer}\n"], dependencies: [{ kind: "directive", type: CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$1.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: ToolbarWindowComponent, selector: "ngt-window", inputs: ["config"], outputs: ["closed", "maximize", "minimize"] }, { kind: "component", type: ToolbarToolButtonComponent, selector: "ngt-tool-button", inputs: ["title", "toolId", "badge"], outputs: ["open"] }, { kind: "component", type: ToolbarIconComponent, selector: "ngt-icon", inputs: ["name"] }], animations: [
2428
+ trigger('slideAnimation', [
2429
+ transition(':enter', [
2430
+ style({
2431
+ transform: 'translateY(20px)',
2432
+ opacity: 0,
2433
+ }),
2434
+ animate('400ms cubic-bezier(0.4, 0, 0.2, 1)', style({
2435
+ transform: 'translateY(0)',
2436
+ opacity: 1,
2437
+ })),
2438
+ ]),
2439
+ transition(':leave', [
2440
+ style({
2441
+ transform: 'translateY(0)',
2442
+ opacity: 1,
2443
+ }),
2444
+ animate('400ms cubic-bezier(0.4, 0, 0.2, 1)', style({
2445
+ transform: 'translateY(20px)',
2446
+ opacity: 0,
2447
+ })),
2448
+ ]),
2449
+ ]),
2450
+ ], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3375
2451
  }
3376
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarWindowComponent, decorators: [{
2452
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarToolComponent, decorators: [{
3377
2453
  type: Component,
3378
- args: [{ selector: 'ngt-window', standalone: true, template: `
3379
- <div class="ngt-window" [attr.data-theme]="theme()">
3380
- <div class="ngt-header">
3381
- <div class="ngt-header__content">
3382
- <div class="ngt-header__title">
3383
- <h1>{{ config().title }}</h1>
3384
- @if (config().isBeta) {
3385
- <span class="beta-tag">BETA</span>
3386
- }
3387
- </div>
3388
- @if (config().description) {
3389
- <p class="ngt-header__description">{{ config().description }}</p>
3390
- }
3391
- </div>
3392
- <div class="ngt-header__controls">
3393
- @if (config().isMinimizable) {
3394
- <button aria-label="Minimize" class="control" (click)="onMinimize()">
3395
-
3396
- </button>
3397
- } @if (config().isMaximizable) {
3398
- <button aria-label="Maximize" class="control" (click)="onMaximize()">
3399
-
3400
- </button>
3401
- } @if (config().isClosable) {
3402
- <button
3403
- aria-label="Close"
3404
- class="control control--close"
3405
- (click)="onClose()"
3406
- >
3407
- ×
3408
- </button>
2454
+ args: [{ selector: 'ngt-toolbar-tool', standalone: true, imports: [
2455
+ CdkConnectedOverlay,
2456
+ OverlayModule,
2457
+ ToolbarWindowComponent,
2458
+ ToolbarToolButtonComponent,
2459
+ ToolbarIconComponent,
2460
+ ], template: `
2461
+ <div #trigger="cdkOverlayOrigin" class="ngt-toolbar-tool" cdkOverlayOrigin>
2462
+ <div
2463
+ class="ngt-toolbar-tool__icon"
2464
+ (click)="onOpen()"
2465
+ (keydown.enter)="onOpen()"
2466
+ (keydown.space)="onOpen()"
2467
+ tabindex="0"
2468
+ >
2469
+ <div #buttonContainer [attr.data-tooltip]="title()">
2470
+ @if (icon()) {
2471
+ <ngt-tool-button [title]="title()" [toolId]="options().id" [badge]="badge()">
2472
+ <ngt-icon [name]="icon()" />
2473
+ </ngt-tool-button>
2474
+ } @else {
2475
+ <ng-content select="ngt-tool-button"></ng-content>
3409
2476
  }
3410
2477
  </div>
3411
2478
  </div>
3412
2479
 
3413
- <div class="divider"></div>
3414
- <div class="ngt-content">
3415
- <ng-content></ng-content>
3416
- </div>
2480
+ @if (isActive()) {
2481
+ <ng-template
2482
+ #contentTemplate
2483
+ [cdkConnectedOverlayOrigin]="trigger"
2484
+ [cdkConnectedOverlayOpen]="isActive()"
2485
+ [cdkConnectedOverlayPositions]="positions()"
2486
+ [cdkConnectedOverlayWidth]="width()"
2487
+ [cdkConnectedOverlayHeight]="height()"
2488
+ [cdkConnectedOverlayPanelClass]="['ngt-overlay-panel', 'ngt-tool-overlay']"
2489
+ cdkConnectedOverlay
2490
+ >
2491
+ <ngt-window [@slideAnimation] [config]="options()" (closed)="onClose()">
2492
+ <ng-content />
2493
+ </ngt-window>
2494
+ </ng-template>
2495
+ }
3417
2496
  </div>
3418
- `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{--ngt-border-radius-small: 4px;--ngt-border-radius-medium: 8px;--ngt-border-radius-large: 12px;--ngt-transition-default: all .2s ease-out;--ngt-transition-smooth: all .2s ease-in-out;--ngt-bg-primary: rgb(255, 255, 255);--ngt-bg-gradient: linear-gradient(180deg, rgb(243, 244, 246) 0%, rgba(243, 244, 246, .88) 100%);--ngt-text-primary: rgb(17, 24, 39);--ngt-text-secondary: rgb(55, 65, 81);--ngt-text-muted: rgb(107, 114, 128);--ngt-border-primary: #e5e7eb;--ngt-border-subtle: rgba(17, 24, 39, .1);--ngt-hover-bg: rgba(17, 24, 39, .05);--ngt-hover-danger: rgb(239, 68, 68);--ngt-shadow-toolbar: 0 2px 8px rgba(156, 163, 175, .2);--ngt-shadow-tooltip: 0 0 0 1px rgba(17, 24, 39, .05), 0 4px 8px rgba(107, 114, 128, .15), 0 2px 4px rgba(107, 114, 128, .1);--ngt-shadow-window: 0px 0px 0px 0px rgba(156, 163, 175, .1), 0px 1px 2px 0px rgba(156, 163, 175, .12), 0px 4px 4px 0px rgba(156, 163, 175, .1), 0px 10px 6px 0px rgba(156, 163, 175, .08), 0px 17px 7px 0px rgba(156, 163, 175, .05), 0px 26px 7px 0px rgba(156, 163, 175, .02);--ngt-spacing-xs: 4px;--ngt-spacing-sm: 6px;--ngt-spacing-md: 12px;--ngt-spacing-lg: 16px;--ngt-window-padding: 16px;--ngt-font-size-xxs: .65rem;--ngt-font-size-xs: .75rem;--ngt-font-size-sm: .875rem;--ngt-font-size-md: 1rem;--ngt-font-size-lg: 1.25rem;--ngt-font-size-xl: 2rem;--ngt-background-secondary: var(--ngt-bg-primary);--ngt-background-hover: var(--ngt-hover-bg);--ngt-primary: #df30d4;--ngt-primary-rgb: 223, 48, 212;--ngt-text-on-primary: rgb(255, 255, 255);--ngt-border-color: var(--ngt-border-primary);--ngt-note-background: rgb(219, 234, 254);--ngt-note-border: rgba(37, 99, 235, .2);--ngt-warning-background: rgb(254, 249, 195);--ngt-warning-border: rgba(202, 138, 4, .2);--ngt-error-background: rgb(254, 226, 226);--ngt-error-border: rgba(220, 38, 38, .2);display:block;width:100%;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",Segoe UI Symbol,\"Noto Color Emoji\"}.ngt-window{box-sizing:border-box;display:flex;flex-direction:column;position:relative;width:100%;height:100%;background:var(--ngt-bg-primary);border:1px solid var(--ngt-border-primary);border-radius:var(--ngt-border-radius-large);padding:var(--ngt-window-padding) var(--ngt-window-padding) var(--ngt-spacing-xs);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(--ngt-text-secondary);z-index:999999999;box-shadow:var(--ngt-shadow-window);contain:layout style}.ngt-header{display:flex;flex-direction:row;justify-content:space-between;align-items:flex-start;position:relative;z-index:10;flex-shrink:0}.ngt-header h1{font-size:var(--ngt-font-size-lg);line-height:1.2;margin:0;color:var(--ngt-text-primary)}.ngt-header__title{display:flex;align-items:center;gap:var(--ngt-spacing-sm);flex-wrap:wrap;margin-bottom:var(--ngt-spacing-xs)}.ngt-header__title .beta-tag{font-size:var(--ngt-font-size-xxs, .65rem);background:var(--ngt-purple, #8b5cf6);color:var(--ngt-text-on-primary);padding:2px 6px;border-radius:var(--ngt-border-radius-small);font-weight:600;text-transform:uppercase;letter-spacing:.5px;line-height:1;white-space:nowrap}.ngt-header__description{font-size:var(--ngt-font-size-sm);color:var(--ngt-text-muted);word-wrap:break-word;overflow-wrap:break-word;max-width:100%;line-height:1.4;margin:0}.ngt-header__content{display:flex;flex-direction:column;gap:var(--ngt-spacing-xs);flex:1;min-width:0}.ngt-header__controls{display:flex;gap:var(--ngt-spacing-sm);flex-shrink:0;align-items:flex-start}.ngt-content{position:relative;flex:1;overflow:auto;min-height:0}.divider{height:1px;background-color:var(--ngt-border-primary);margin-bottom:var(--ngt-spacing-md);margin-top:var(--ngt-spacing-md);flex-shrink:0;position:relative;z-index:5}.control{background:none;border:none;color:var(--ngt-text-secondary);cursor:pointer;padding:var(--ngt-spacing-xs) var(--ngt-spacing-sm);border-radius:var(--ngt-border-radius-small);font-size:var(--ngt-font-size-md);line-height:1;transition:var(--ngt-transition-smooth)}.control:hover{background:var(--ngt-hover-bg);color:var(--ngt-text-primary)}.control--close:hover{background:var(--ngt-hover-danger)}\n"] }]
2497
+ `, changeDetection: ChangeDetectionStrategy.OnPush, animations: [
2498
+ trigger('slideAnimation', [
2499
+ transition(':enter', [
2500
+ style({
2501
+ transform: 'translateY(20px)',
2502
+ opacity: 0,
2503
+ }),
2504
+ animate('400ms cubic-bezier(0.4, 0, 0.2, 1)', style({
2505
+ transform: 'translateY(0)',
2506
+ opacity: 1,
2507
+ })),
2508
+ ]),
2509
+ transition(':leave', [
2510
+ style({
2511
+ transform: 'translateY(0)',
2512
+ opacity: 1,
2513
+ }),
2514
+ animate('400ms cubic-bezier(0.4, 0, 0.2, 1)', style({
2515
+ transform: 'translateY(20px)',
2516
+ opacity: 0,
2517
+ })),
2518
+ ]),
2519
+ ]),
2520
+ ], styles: [":host{--ngt-border-radius-small: 4px;--ngt-border-radius-medium: 8px;--ngt-border-radius-large: 12px;--ngt-transition-default: all .2s ease-out;--ngt-transition-smooth: all .2s ease-in-out;--ngt-bg-primary: rgb(255, 255, 255);--ngt-bg-gradient: linear-gradient(180deg, rgb(243, 244, 246) 0%, rgba(243, 244, 246, .88) 100%);--ngt-text-primary: rgb(17, 24, 39);--ngt-text-secondary: rgb(55, 65, 81);--ngt-text-muted: rgb(107, 114, 128);--ngt-border-primary: #e5e7eb;--ngt-border-subtle: rgba(17, 24, 39, .1);--ngt-hover-bg: rgba(17, 24, 39, .05);--ngt-hover-danger: rgb(239, 68, 68);--ngt-shadow-toolbar: 0 2px 8px rgba(156, 163, 175, .2);--ngt-shadow-tooltip: 0 0 0 1px rgba(17, 24, 39, .05), 0 4px 8px rgba(107, 114, 128, .15), 0 2px 4px rgba(107, 114, 128, .1);--ngt-shadow-window: 0px 0px 0px 0px rgba(156, 163, 175, .1), 0px 1px 2px 0px rgba(156, 163, 175, .12), 0px 4px 4px 0px rgba(156, 163, 175, .1), 0px 10px 6px 0px rgba(156, 163, 175, .08), 0px 17px 7px 0px rgba(156, 163, 175, .05), 0px 26px 7px 0px rgba(156, 163, 175, .02);--ngt-spacing-xs: 4px;--ngt-spacing-sm: 6px;--ngt-spacing-md: 12px;--ngt-spacing-lg: 16px;--ngt-window-padding: 16px;--ngt-font-size-xxs: .65rem;--ngt-font-size-xs: .75rem;--ngt-font-size-sm: .875rem;--ngt-font-size-md: 1rem;--ngt-font-size-lg: 1.25rem;--ngt-font-size-xl: 2rem;--ngt-background-secondary: var(--ngt-bg-primary);--ngt-background-hover: var(--ngt-hover-bg);--ngt-primary: #df30d4;--ngt-primary-rgb: 223, 48, 212;--ngt-text-on-primary: rgb(255, 255, 255);--ngt-border-color: var(--ngt-border-primary);--ngt-note-background: rgb(219, 234, 254);--ngt-note-border: rgba(37, 99, 235, .2);--ngt-warning-background: rgb(254, 249, 195);--ngt-warning-border: rgba(202, 138, 4, .2);--ngt-error-background: rgb(254, 226, 226);--ngt-error-border: rgba(220, 38, 38, .2)}.tool{position:relative}.trigger{cursor:pointer}\n"] }]
2521
+ }] });
2522
+
2523
+ class ToolbarStorageService {
2524
+ constructor() {
2525
+ this.PREFIX = 'AngularToolbar.';
2526
+ this.TOOLS_KEY = `${this.PREFIX}keys`;
2527
+ this.SETTINGS_KEY = `${this.PREFIX}settings`;
2528
+ }
2529
+ set(key, value) {
2530
+ const toolKey = this.getToolKey(key);
2531
+ this.addToolKey(toolKey);
2532
+ localStorage.setItem(toolKey, JSON.stringify(value));
2533
+ }
2534
+ get(key) {
2535
+ const toolKey = this.getToolKey(key);
2536
+ const item = localStorage.getItem(toolKey);
2537
+ return item ? JSON.parse(item) : null;
2538
+ }
2539
+ remove(key) {
2540
+ const toolKey = this.getToolKey(key);
2541
+ localStorage.removeItem(toolKey);
2542
+ this.removeToolKey(toolKey);
2543
+ }
2544
+ getAllSettings() {
2545
+ const settings = {};
2546
+ const keys = this.getToolKeys();
2547
+ keys.forEach((key) => {
2548
+ const value = this.get(key);
2549
+ if (value !== null) {
2550
+ settings[key] = value;
2551
+ }
2552
+ });
2553
+ return settings;
2554
+ }
2555
+ setAllSettings(settings) {
2556
+ Object.entries(settings).forEach(([key, value]) => {
2557
+ this.set(key, value);
2558
+ });
2559
+ }
2560
+ clearAllSettings() {
2561
+ const keys = this.getToolKeys();
2562
+ keys.forEach((key) => {
2563
+ this.remove(key);
2564
+ });
2565
+ }
2566
+ getToolKeys() {
2567
+ return JSON.parse(localStorage.getItem(this.TOOLS_KEY) ?? '[]');
2568
+ }
2569
+ addToolKey(key) {
2570
+ const currentKeys = this.getToolKeys();
2571
+ if (currentKeys.includes(key)) {
2572
+ return;
2573
+ }
2574
+ currentKeys.push(key);
2575
+ localStorage.setItem(this.TOOLS_KEY, JSON.stringify(currentKeys));
2576
+ }
2577
+ removeToolKey(key) {
2578
+ const currentKeys = this.getToolKeys();
2579
+ const index = currentKeys.indexOf(key);
2580
+ if (index !== -1) {
2581
+ currentKeys.splice(index, 1);
2582
+ }
2583
+ localStorage.setItem(this.TOOLS_KEY, JSON.stringify(currentKeys));
2584
+ }
2585
+ getToolKey(key) {
2586
+ return key.includes(this.PREFIX) ? key : this.PREFIX + key;
2587
+ }
2588
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarStorageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2589
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarStorageService, providedIn: 'root' }); }
2590
+ }
2591
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarStorageService, decorators: [{
2592
+ type: Injectable,
2593
+ args: [{ providedIn: 'root' }]
3419
2594
  }] });
3420
2595
 
3421
- class ToolbarToolComponent {
2596
+ /**
2597
+ * Internal service for managing app features state and forced overrides.
2598
+ *
2599
+ * This service handles:
2600
+ * - Feature configuration storage
2601
+ * - Forced feature state management
2602
+ * - localStorage persistence
2603
+ * - State validation and cleanup
2604
+ *
2605
+ * @internal This service is for internal toolbar use only. Consumers should use ToolbarAppFeaturesService.
2606
+ */
2607
+ class ToolbarInternalAppFeaturesService {
3422
2608
  constructor() {
3423
- this.state = inject(ToolbarStateService);
3424
- this.buttonContainer = viewChild.required('buttonContainer');
3425
- this.buttonComponent = contentChild(ToolbarToolButtonComponent);
3426
- this.options = input.required();
3427
- this.icon = input.required();
3428
- this.title = input.required();
3429
- this.badge = input();
3430
- this.isActive = computed(() => this.state.activeToolId() === this.options().id);
3431
- this.height = computed(() => {
3432
- switch (this.options().size) {
3433
- case 'small':
3434
- return 320;
3435
- case 'medium':
3436
- return 480;
3437
- case 'tall':
3438
- return 620;
3439
- case 'large':
3440
- return 620;
3441
- default:
3442
- return 480;
3443
- }
2609
+ this.STORAGE_KEY = 'app-features';
2610
+ this.storageService = inject(ToolbarStorageService);
2611
+ this.stateService = inject(ToolbarStateService);
2612
+ this.appFeaturesSubject = new BehaviorSubject([]);
2613
+ this.forcedFeaturesSubject = new BehaviorSubject({
2614
+ enabled: [],
2615
+ disabled: [],
3444
2616
  });
3445
- this.width = computed(() => {
3446
- switch (this.options().size) {
3447
- case 'small':
3448
- return 320;
3449
- case 'medium':
3450
- return 480;
3451
- case 'tall':
3452
- return 480;
3453
- case 'large':
3454
- return 620;
3455
- default:
3456
- return 400;
2617
+ this.forcedFeatures$ = this.forcedFeaturesSubject.asObservable();
2618
+ /**
2619
+ * Observable stream of all features with merged forced state
2620
+ */
2621
+ this.features$ = combineLatest([
2622
+ this.appFeaturesSubject.asObservable(),
2623
+ this.forcedFeatures$,
2624
+ ]).pipe(map(([appFeatures, forcedState]) => this.mergeForcedState(appFeatures, forcedState)));
2625
+ /**
2626
+ * Signal containing current features with merged forced state
2627
+ */
2628
+ this.features = toSignal(this.features$, { initialValue: [] });
2629
+ this.loadForcedFeatures();
2630
+ }
2631
+ /**
2632
+ * Set available app features for the application.
2633
+ * Validates features, trims whitespace, and triggers validation of forced state.
2634
+ *
2635
+ * @param features - Array of app features to configure
2636
+ * @throws Error if duplicate feature IDs or empty IDs are detected
2637
+ */
2638
+ setAppFeatures(features) {
2639
+ // Validate for empty IDs
2640
+ const emptyIdFeature = features.find((f) => !f.id || f.id.trim() === '');
2641
+ if (emptyIdFeature) {
2642
+ throw new Error('Feature ID cannot be empty');
2643
+ }
2644
+ // Validate for duplicate IDs
2645
+ const ids = features.map((f) => f.id);
2646
+ const duplicateIds = ids.filter((id, index) => ids.indexOf(id) !== index);
2647
+ if (duplicateIds.length > 0) {
2648
+ throw new Error(`Duplicate feature IDs detected: ${duplicateIds.join(', ')}`);
2649
+ }
2650
+ // Trim whitespace from names and warn about empty names
2651
+ const processedFeatures = features.map((feature) => {
2652
+ const trimmedName = feature.name.trim();
2653
+ if (!trimmedName) {
2654
+ console.warn(`Feature '${feature.id}' has empty name`);
3457
2655
  }
2656
+ return {
2657
+ ...feature,
2658
+ name: trimmedName || feature.name,
2659
+ };
3458
2660
  });
3459
- this.positions = computed(() => {
3460
- const triggerXPosition = this.getButtonContainerXPosition();
3461
- const windowCenter = window.innerWidth / 2;
3462
- const offsetX = windowCenter - triggerXPosition - 22;
3463
- return [
3464
- {
3465
- originX: 'center',
3466
- originY: 'center',
3467
- overlayX: 'center',
3468
- overlayY: 'center',
3469
- offsetY: -(Math.ceil(this.height() / 2) + 36),
3470
- offsetX,
3471
- },
3472
- ];
2661
+ this.appFeaturesSubject.next(processedFeatures);
2662
+ this.validateAndCleanForcedState();
2663
+ }
2664
+ /**
2665
+ * Get observable stream of app features (natural state, no forced state merged)
2666
+ */
2667
+ getAppFeatures() {
2668
+ return this.appFeaturesSubject.asObservable();
2669
+ }
2670
+ /**
2671
+ * Get observable stream of features that have forced overrides
2672
+ */
2673
+ getForcedFeatures() {
2674
+ return this.features$.pipe(map((features) => {
2675
+ // If toolbar is disabled, return empty array (no forced values)
2676
+ if (!this.stateService.isEnabled()) {
2677
+ return [];
2678
+ }
2679
+ return features.filter((feature) => feature.isForced);
2680
+ }));
2681
+ }
2682
+ /**
2683
+ * Force a feature to enabled or disabled state.
2684
+ * Persists the forced state to localStorage.
2685
+ *
2686
+ * @param featureId - ID of the feature to force
2687
+ * @param isEnabled - Whether to force feature to enabled (true) or disabled (false)
2688
+ */
2689
+ setFeature(featureId, isEnabled) {
2690
+ const { enabled, disabled } = this.forcedFeaturesSubject.value;
2691
+ // Remove feature from both arrays
2692
+ const newEnabled = enabled.filter((id) => id !== featureId);
2693
+ const newDisabled = disabled.filter((id) => id !== featureId);
2694
+ // Add to appropriate array
2695
+ if (isEnabled) {
2696
+ newEnabled.push(featureId);
2697
+ }
2698
+ else {
2699
+ newDisabled.push(featureId);
2700
+ }
2701
+ const newState = { enabled: newEnabled, disabled: newDisabled };
2702
+ this.forcedFeaturesSubject.next(newState);
2703
+ this.saveForcedFeatures(newState);
2704
+ }
2705
+ /**
2706
+ * Remove forced override for a feature, returning it to natural state.
2707
+ * Persists the change to localStorage.
2708
+ *
2709
+ * @param featureId - ID of the feature to unforce
2710
+ */
2711
+ removeFeatureOverride(featureId) {
2712
+ const { enabled, disabled } = this.forcedFeaturesSubject.value;
2713
+ const newState = {
2714
+ enabled: enabled.filter((id) => id !== featureId),
2715
+ disabled: disabled.filter((id) => id !== featureId),
2716
+ };
2717
+ this.forcedFeaturesSubject.next(newState);
2718
+ this.saveForcedFeatures(newState);
2719
+ }
2720
+ /**
2721
+ * Apply a preset forced state (for preset integration).
2722
+ * Validates and cleans invalid feature IDs before applying.
2723
+ *
2724
+ * @param state - Forced features state from preset
2725
+ */
2726
+ applyForcedState(state) {
2727
+ const configuredFeatureIds = this.appFeaturesSubject.value.map((f) => f.id);
2728
+ // Validate and filter to only valid IDs
2729
+ const validEnabled = state.enabled.filter((id) => configuredFeatureIds.includes(id));
2730
+ const validDisabled = state.disabled.filter((id) => configuredFeatureIds.includes(id));
2731
+ const invalidIds = [
2732
+ ...state.enabled.filter((id) => !configuredFeatureIds.includes(id)),
2733
+ ...state.disabled.filter((id) => !configuredFeatureIds.includes(id)),
2734
+ ];
2735
+ if (invalidIds.length > 0) {
2736
+ console.warn(`Preset contains invalid feature IDs (ignored): ${invalidIds.join(', ')}`);
2737
+ }
2738
+ const cleanedState = { enabled: validEnabled, disabled: validDisabled };
2739
+ this.forcedFeaturesSubject.next(cleanedState);
2740
+ this.saveForcedFeatures(cleanedState);
2741
+ }
2742
+ /**
2743
+ * Get current forced state as a snapshot (defensive copy).
2744
+ * Useful for preset exports and debugging.
2745
+ *
2746
+ * @returns Current forced features state
2747
+ */
2748
+ getCurrentForcedState() {
2749
+ const state = this.forcedFeaturesSubject.value;
2750
+ return {
2751
+ enabled: [...state.enabled],
2752
+ disabled: [...state.disabled],
2753
+ };
2754
+ }
2755
+ /**
2756
+ * Merge natural app features with forced state.
2757
+ *
2758
+ * @param appFeatures - Natural feature configuration
2759
+ * @param forcedState - Forced overrides from localStorage/toolbar
2760
+ * @returns Features with merged forced state
2761
+ */
2762
+ mergeForcedState(appFeatures, forcedState) {
2763
+ // If toolbar is disabled, return app features without overrides
2764
+ if (!this.stateService.isEnabled()) {
2765
+ return appFeatures;
2766
+ }
2767
+ return appFeatures.map((feature) => {
2768
+ const isInEnabled = forcedState.enabled.includes(feature.id);
2769
+ const isInDisabled = forcedState.disabled.includes(feature.id);
2770
+ const isForced = isInEnabled || isInDisabled;
2771
+ return {
2772
+ ...feature,
2773
+ isEnabled: isInEnabled ? true : isInDisabled ? false : feature.isEnabled,
2774
+ isForced,
2775
+ originalValue: isForced ? feature.isEnabled : undefined,
2776
+ };
3473
2777
  });
3474
2778
  }
3475
- onOpen() {
3476
- this.state.setActiveTool(this.options().id);
2779
+ /**
2780
+ * Load forced features state from localStorage on initialization.
2781
+ * Handles missing or corrupted data gracefully.
2782
+ */
2783
+ loadForcedFeatures() {
2784
+ try {
2785
+ const savedState = this.storageService.get(this.STORAGE_KEY);
2786
+ if (savedState) {
2787
+ this.forcedFeaturesSubject.next(savedState);
2788
+ }
2789
+ }
2790
+ catch (error) {
2791
+ console.error('Failed to load forced app features from localStorage:', error);
2792
+ // Use default empty state on error
2793
+ }
3477
2794
  }
3478
- onClose() {
3479
- this.state.setActiveTool(null);
2795
+ /**
2796
+ * Persist forced features state to localStorage.
2797
+ * Handles quota exceeded errors gracefully.
2798
+ *
2799
+ * @param state - Forced state to persist
2800
+ */
2801
+ saveForcedFeatures(state) {
2802
+ try {
2803
+ this.storageService.set(this.STORAGE_KEY, state);
2804
+ }
2805
+ catch (error) {
2806
+ console.error('Failed to persist app features to localStorage:', error);
2807
+ // Continue execution - state is still valid in memory for current session
2808
+ }
3480
2809
  }
3481
- getButtonContainerXPosition() {
3482
- const buttonContainerRect = this.buttonContainer()?.nativeElement?.getBoundingClientRect();
3483
- return buttonContainerRect?.left ?? 0;
2810
+ /**
2811
+ * Validate forced feature IDs against configured features and clean up invalid ones.
2812
+ * Called after setAppFeatures() to ensure forced state references valid features.
2813
+ */
2814
+ validateAndCleanForcedState() {
2815
+ const configuredFeatureIds = this.appFeaturesSubject.value.map((f) => f.id);
2816
+ const currentForcedState = this.forcedFeaturesSubject.value;
2817
+ const validEnabled = currentForcedState.enabled.filter((id) => configuredFeatureIds.includes(id));
2818
+ const validDisabled = currentForcedState.disabled.filter((id) => configuredFeatureIds.includes(id));
2819
+ const removedIds = [
2820
+ ...currentForcedState.enabled.filter((id) => !configuredFeatureIds.includes(id)),
2821
+ ...currentForcedState.disabled.filter((id) => !configuredFeatureIds.includes(id)),
2822
+ ];
2823
+ if (removedIds.length > 0) {
2824
+ console.warn(`Removed invalid feature IDs from forced state: ${removedIds.join(', ')}`);
2825
+ const cleanedState = { enabled: validEnabled, disabled: validDisabled };
2826
+ this.forcedFeaturesSubject.next(cleanedState);
2827
+ this.saveForcedFeatures(cleanedState);
2828
+ }
3484
2829
  }
3485
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarToolComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3486
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.7", type: ToolbarToolComponent, isStandalone: true, selector: "ngt-toolbar-tool", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: true, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, badge: { classPropertyName: "badge", publicName: "badge", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "buttonComponent", first: true, predicate: ToolbarToolButtonComponent, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "buttonContainer", first: true, predicate: ["buttonContainer"], descendants: true, isSignal: true }], ngImport: i0, template: `
3487
- <div #trigger="cdkOverlayOrigin" class="ngt-toolbar-tool" cdkOverlayOrigin>
3488
- <div
3489
- class="ngt-toolbar-tool__icon"
3490
- (click)="onOpen()"
3491
- (keydown.enter)="onOpen()"
3492
- (keydown.space)="onOpen()"
3493
- tabindex="0"
3494
- >
3495
- <div #buttonContainer [attr.data-tooltip]="title()">
3496
- @if (icon()) {
3497
- <ngt-tool-button [title]="title()" [toolId]="options().id" [badge]="badge()">
3498
- <ngt-icon [name]="icon()" />
3499
- </ngt-tool-button>
3500
- } @else {
3501
- <ng-content select="ngt-tool-button"></ng-content>
3502
- }
3503
- </div>
3504
- </div>
3505
-
3506
- @if (isActive()) {
3507
- <ng-template
3508
- #contentTemplate
3509
- [cdkConnectedOverlayOrigin]="trigger"
3510
- [cdkConnectedOverlayOpen]="isActive()"
3511
- [cdkConnectedOverlayPositions]="positions()"
3512
- [cdkConnectedOverlayWidth]="width()"
3513
- [cdkConnectedOverlayHeight]="height()"
3514
- [cdkConnectedOverlayPanelClass]="['ngt-overlay-panel', 'ngt-tool-overlay']"
3515
- cdkConnectedOverlay
3516
- >
3517
- <ngt-window [@slideAnimation] [config]="options()" (closed)="onClose()">
3518
- <ng-content />
3519
- </ngt-window>
3520
- </ng-template>
3521
- }
3522
- </div>
3523
- `, isInline: true, styles: [":host{--ngt-border-radius-small: 4px;--ngt-border-radius-medium: 8px;--ngt-border-radius-large: 12px;--ngt-transition-default: all .2s ease-out;--ngt-transition-smooth: all .2s ease-in-out;--ngt-bg-primary: rgb(255, 255, 255);--ngt-bg-gradient: linear-gradient(180deg, rgb(243, 244, 246) 0%, rgba(243, 244, 246, .88) 100%);--ngt-text-primary: rgb(17, 24, 39);--ngt-text-secondary: rgb(55, 65, 81);--ngt-text-muted: rgb(107, 114, 128);--ngt-border-primary: #e5e7eb;--ngt-border-subtle: rgba(17, 24, 39, .1);--ngt-hover-bg: rgba(17, 24, 39, .05);--ngt-hover-danger: rgb(239, 68, 68);--ngt-shadow-toolbar: 0 2px 8px rgba(156, 163, 175, .2);--ngt-shadow-tooltip: 0 0 0 1px rgba(17, 24, 39, .05), 0 4px 8px rgba(107, 114, 128, .15), 0 2px 4px rgba(107, 114, 128, .1);--ngt-shadow-window: 0px 0px 0px 0px rgba(156, 163, 175, .1), 0px 1px 2px 0px rgba(156, 163, 175, .12), 0px 4px 4px 0px rgba(156, 163, 175, .1), 0px 10px 6px 0px rgba(156, 163, 175, .08), 0px 17px 7px 0px rgba(156, 163, 175, .05), 0px 26px 7px 0px rgba(156, 163, 175, .02);--ngt-spacing-xs: 4px;--ngt-spacing-sm: 6px;--ngt-spacing-md: 12px;--ngt-spacing-lg: 16px;--ngt-window-padding: 16px;--ngt-font-size-xxs: .65rem;--ngt-font-size-xs: .75rem;--ngt-font-size-sm: .875rem;--ngt-font-size-md: 1rem;--ngt-font-size-lg: 1.25rem;--ngt-font-size-xl: 2rem;--ngt-background-secondary: var(--ngt-bg-primary);--ngt-background-hover: var(--ngt-hover-bg);--ngt-primary: #df30d4;--ngt-primary-rgb: 223, 48, 212;--ngt-text-on-primary: rgb(255, 255, 255);--ngt-border-color: var(--ngt-border-primary);--ngt-note-background: rgb(219, 234, 254);--ngt-note-border: rgba(37, 99, 235, .2);--ngt-warning-background: rgb(254, 249, 195);--ngt-warning-border: rgba(202, 138, 4, .2);--ngt-error-background: rgb(254, 226, 226);--ngt-error-border: rgba(220, 38, 38, .2)}.tool{position:relative}.trigger{cursor:pointer}\n"], dependencies: [{ kind: "directive", type: CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$1.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: ToolbarWindowComponent, selector: "ngt-window", inputs: ["config"], outputs: ["closed", "maximize", "minimize"] }, { kind: "component", type: ToolbarToolButtonComponent, selector: "ngt-tool-button", inputs: ["title", "toolId", "badge"], outputs: ["open"] }, { kind: "component", type: ToolbarIconComponent, selector: "ngt-icon", inputs: ["name"] }], animations: [
3524
- trigger('slideAnimation', [
3525
- transition(':enter', [
3526
- style({
3527
- transform: 'translateY(20px)',
3528
- opacity: 0,
3529
- }),
3530
- animate('400ms cubic-bezier(0.4, 0, 0.2, 1)', style({
3531
- transform: 'translateY(0)',
3532
- opacity: 1,
3533
- })),
3534
- ]),
3535
- transition(':leave', [
3536
- style({
3537
- transform: 'translateY(0)',
3538
- opacity: 1,
3539
- }),
3540
- animate('400ms cubic-bezier(0.4, 0, 0.2, 1)', style({
3541
- transform: 'translateY(20px)',
3542
- opacity: 0,
3543
- })),
3544
- ]),
3545
- ]),
3546
- ], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2830
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarInternalAppFeaturesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2831
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarInternalAppFeaturesService, providedIn: 'root' }); }
3547
2832
  }
3548
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarToolComponent, decorators: [{
3549
- type: Component,
3550
- args: [{ selector: 'ngt-toolbar-tool', standalone: true, imports: [
3551
- CdkConnectedOverlay,
3552
- OverlayModule,
3553
- ToolbarWindowComponent,
3554
- ToolbarToolButtonComponent,
3555
- ToolbarIconComponent,
3556
- ], template: `
3557
- <div #trigger="cdkOverlayOrigin" class="ngt-toolbar-tool" cdkOverlayOrigin>
3558
- <div
3559
- class="ngt-toolbar-tool__icon"
3560
- (click)="onOpen()"
3561
- (keydown.enter)="onOpen()"
3562
- (keydown.space)="onOpen()"
3563
- tabindex="0"
3564
- >
3565
- <div #buttonContainer [attr.data-tooltip]="title()">
3566
- @if (icon()) {
3567
- <ngt-tool-button [title]="title()" [toolId]="options().id" [badge]="badge()">
3568
- <ngt-icon [name]="icon()" />
3569
- </ngt-tool-button>
3570
- } @else {
3571
- <ng-content select="ngt-tool-button"></ng-content>
3572
- }
3573
- </div>
3574
- </div>
3575
-
3576
- @if (isActive()) {
3577
- <ng-template
3578
- #contentTemplate
3579
- [cdkConnectedOverlayOrigin]="trigger"
3580
- [cdkConnectedOverlayOpen]="isActive()"
3581
- [cdkConnectedOverlayPositions]="positions()"
3582
- [cdkConnectedOverlayWidth]="width()"
3583
- [cdkConnectedOverlayHeight]="height()"
3584
- [cdkConnectedOverlayPanelClass]="['ngt-overlay-panel', 'ngt-tool-overlay']"
3585
- cdkConnectedOverlay
3586
- >
3587
- <ngt-window [@slideAnimation] [config]="options()" (closed)="onClose()">
3588
- <ng-content />
3589
- </ngt-window>
3590
- </ng-template>
3591
- }
3592
- </div>
3593
- `, changeDetection: ChangeDetectionStrategy.OnPush, animations: [
3594
- trigger('slideAnimation', [
3595
- transition(':enter', [
3596
- style({
3597
- transform: 'translateY(20px)',
3598
- opacity: 0,
3599
- }),
3600
- animate('400ms cubic-bezier(0.4, 0, 0.2, 1)', style({
3601
- transform: 'translateY(0)',
3602
- opacity: 1,
3603
- })),
3604
- ]),
3605
- transition(':leave', [
3606
- style({
3607
- transform: 'translateY(0)',
3608
- opacity: 1,
3609
- }),
3610
- animate('400ms cubic-bezier(0.4, 0, 0.2, 1)', style({
3611
- transform: 'translateY(20px)',
3612
- opacity: 0,
3613
- })),
3614
- ]),
3615
- ]),
3616
- ], styles: [":host{--ngt-border-radius-small: 4px;--ngt-border-radius-medium: 8px;--ngt-border-radius-large: 12px;--ngt-transition-default: all .2s ease-out;--ngt-transition-smooth: all .2s ease-in-out;--ngt-bg-primary: rgb(255, 255, 255);--ngt-bg-gradient: linear-gradient(180deg, rgb(243, 244, 246) 0%, rgba(243, 244, 246, .88) 100%);--ngt-text-primary: rgb(17, 24, 39);--ngt-text-secondary: rgb(55, 65, 81);--ngt-text-muted: rgb(107, 114, 128);--ngt-border-primary: #e5e7eb;--ngt-border-subtle: rgba(17, 24, 39, .1);--ngt-hover-bg: rgba(17, 24, 39, .05);--ngt-hover-danger: rgb(239, 68, 68);--ngt-shadow-toolbar: 0 2px 8px rgba(156, 163, 175, .2);--ngt-shadow-tooltip: 0 0 0 1px rgba(17, 24, 39, .05), 0 4px 8px rgba(107, 114, 128, .15), 0 2px 4px rgba(107, 114, 128, .1);--ngt-shadow-window: 0px 0px 0px 0px rgba(156, 163, 175, .1), 0px 1px 2px 0px rgba(156, 163, 175, .12), 0px 4px 4px 0px rgba(156, 163, 175, .1), 0px 10px 6px 0px rgba(156, 163, 175, .08), 0px 17px 7px 0px rgba(156, 163, 175, .05), 0px 26px 7px 0px rgba(156, 163, 175, .02);--ngt-spacing-xs: 4px;--ngt-spacing-sm: 6px;--ngt-spacing-md: 12px;--ngt-spacing-lg: 16px;--ngt-window-padding: 16px;--ngt-font-size-xxs: .65rem;--ngt-font-size-xs: .75rem;--ngt-font-size-sm: .875rem;--ngt-font-size-md: 1rem;--ngt-font-size-lg: 1.25rem;--ngt-font-size-xl: 2rem;--ngt-background-secondary: var(--ngt-bg-primary);--ngt-background-hover: var(--ngt-hover-bg);--ngt-primary: #df30d4;--ngt-primary-rgb: 223, 48, 212;--ngt-text-on-primary: rgb(255, 255, 255);--ngt-border-color: var(--ngt-border-primary);--ngt-note-background: rgb(219, 234, 254);--ngt-note-border: rgba(37, 99, 235, .2);--ngt-warning-background: rgb(254, 249, 195);--ngt-warning-border: rgba(202, 138, 4, .2);--ngt-error-background: rgb(254, 226, 226);--ngt-error-border: rgba(220, 38, 38, .2)}.tool{position:relative}.trigger{cursor:pointer}\n"] }]
3617
- }] });
2833
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarInternalAppFeaturesService, decorators: [{
2834
+ type: Injectable,
2835
+ args: [{ providedIn: 'root' }]
2836
+ }], ctorParameters: () => [] });
3618
2837
 
3619
2838
  /**
3620
2839
  * Component for managing app features in the dev toolbar.
@@ -3816,7 +3035,7 @@ class ToolbarAppFeaturesToolComponent {
3816
3035
  </ngt-list>
3817
3036
  </div>
3818
3037
  </ngt-toolbar-tool>
3819
- `, isInline: true, styles: [".container{position:relative;display:flex;flex-direction:column;height:100%;padding:0}.tool-header{position:relative;flex-shrink:0;display:flex;gap:var(--ngt-spacing-sm);margin-bottom:var(--ngt-spacing-sm);ngt-input{flex:1}.filter-wrapper{flex:0 0 auto;display:flex;align-items:center;gap:var(--ngt-spacing-md);.filter-icon{width:18px;height:18px;flex-shrink:0;opacity:.6}ngt-select{flex:0 0 auto;min-width:180px}}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "component", type: ToolbarToolComponent, selector: "ngt-toolbar-tool", inputs: ["options", "icon", "title", "badge"] }, { kind: "component", type: ToolbarInputComponent, selector: "ngt-input", inputs: ["value", "type", "placeholder", "ariaLabel", "inputClass"], outputs: ["valueChange"] }, { kind: "component", type: ToolbarSelectComponent, selector: "ngt-select", inputs: ["value", "options", "ariaLabel", "label", "size"], outputs: ["valueChange"] }, { kind: "component", type: ToolbarIconComponent, selector: "ngt-icon", inputs: ["name"] }, { kind: "component", type: ToolbarListComponent, selector: "ngt-list", inputs: ["hasItems", "hasResults", "emptyMessage", "emptyHint", "noResultsMessage"] }, { kind: "component", type: ToolbarListItemComponent, selector: "ngt-list-item", inputs: ["title", "description", "isForced", "currentValue", "originalValue"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3038
+ `, isInline: true, styles: [".container{position:relative;display:flex;flex-direction:column;height:100%;padding:0}.tool-header{position:relative;flex-shrink:0;display:flex;gap:var(--ngt-spacing-sm);margin-bottom:var(--ngt-spacing-sm);ngt-input{flex:1}.filter-wrapper{flex:0 0 auto;display:flex;align-items:center;gap:var(--ngt-spacing-md);.filter-icon{width:18px;height:18px;flex-shrink:0;opacity:.6}ngt-select{flex:0 0 auto;min-width:180px}}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "component", type: ToolbarToolComponent, selector: "ngt-toolbar-tool", inputs: ["options", "icon", "title", "badge"] }, { kind: "component", type: ToolbarInputComponent, selector: "ngt-input", inputs: ["value", "type", "placeholder", "ariaLabel", "inputClass"], outputs: ["valueChange"] }, { kind: "component", type: ToolbarSelectComponent, selector: "ngt-select", inputs: ["value", "options", "ariaLabel", "label", "size", "placeholder"], outputs: ["valueChange"] }, { kind: "component", type: ToolbarIconComponent, selector: "ngt-icon", inputs: ["name"] }, { kind: "component", type: ToolbarListComponent, selector: "ngt-list", inputs: ["hasItems", "hasResults", "emptyMessage", "emptyHint", "noResultsMessage"] }, { kind: "component", type: ToolbarListItemComponent, selector: "ngt-list-item", inputs: ["title", "description", "isForced", "currentValue", "originalValue"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3820
3039
  }
3821
3040
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarAppFeaturesToolComponent, decorators: [{
3822
3041
  type: Component,
@@ -3883,6 +3102,109 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImpor
3883
3102
  `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".container{position:relative;display:flex;flex-direction:column;height:100%;padding:0}.tool-header{position:relative;flex-shrink:0;display:flex;gap:var(--ngt-spacing-sm);margin-bottom:var(--ngt-spacing-sm);ngt-input{flex:1}.filter-wrapper{flex:0 0 auto;display:flex;align-items:center;gap:var(--ngt-spacing-md);.filter-icon{width:18px;height:18px;flex-shrink:0;opacity:.6}ngt-select{flex:0 0 auto;min-width:180px}}}\n"] }]
3884
3103
  }], ctorParameters: () => [] });
3885
3104
 
3105
+ class ToolbarInternalFeatureFlagService {
3106
+ constructor() {
3107
+ this.STORAGE_KEY = 'feature-flags';
3108
+ this.storageService = inject(ToolbarStorageService);
3109
+ this.stateService = inject(ToolbarStateService);
3110
+ this.appFlags$ = new BehaviorSubject([]);
3111
+ this.forcedFlagsSubject = new BehaviorSubject({
3112
+ enabled: [],
3113
+ disabled: [],
3114
+ });
3115
+ this.forcedFlags$ = this.forcedFlagsSubject.asObservable();
3116
+ this.flags$ = combineLatest([
3117
+ this.appFlags$,
3118
+ this.forcedFlags$,
3119
+ ]).pipe(map(([appFlags, { enabled, disabled }]) => {
3120
+ // If toolbar is disabled, return app flags without overrides
3121
+ if (!this.stateService.isEnabled()) {
3122
+ return appFlags;
3123
+ }
3124
+ return appFlags.map((flag) => {
3125
+ const isForced = enabled.includes(flag.id) || disabled.includes(flag.id);
3126
+ return {
3127
+ ...flag,
3128
+ isForced,
3129
+ isEnabled: enabled.includes(flag.id)
3130
+ ? true
3131
+ : disabled.includes(flag.id)
3132
+ ? false
3133
+ : flag.isEnabled,
3134
+ originalValue: isForced ? flag.isEnabled : undefined,
3135
+ };
3136
+ });
3137
+ }));
3138
+ this.flags = toSignal(this.flags$, { initialValue: [] });
3139
+ this.loadForcedFlags();
3140
+ }
3141
+ setAppFlags(flags) {
3142
+ this.appFlags$.next(flags);
3143
+ }
3144
+ getAppFlags() {
3145
+ return this.appFlags$.asObservable();
3146
+ }
3147
+ getForcedFlags() {
3148
+ return this.flags$.pipe(map((flags) => {
3149
+ // If toolbar is disabled, return empty array (no forced values)
3150
+ if (!this.stateService.isEnabled()) {
3151
+ return [];
3152
+ }
3153
+ return flags.filter((flag) => flag.isForced);
3154
+ }));
3155
+ }
3156
+ setFlag(flagId, isEnabled) {
3157
+ const { enabled, disabled } = this.forcedFlagsSubject.value;
3158
+ const newEnabled = enabled.filter((id) => id !== flagId);
3159
+ const newDisabled = disabled.filter((id) => id !== flagId);
3160
+ if (isEnabled) {
3161
+ newEnabled.push(flagId);
3162
+ }
3163
+ else {
3164
+ newDisabled.push(flagId);
3165
+ }
3166
+ const newState = { enabled: newEnabled, disabled: newDisabled };
3167
+ this.forcedFlagsSubject.next(newState);
3168
+ this.storageService.set(this.STORAGE_KEY, newState);
3169
+ }
3170
+ removeFlagOverride(flagId) {
3171
+ const { enabled, disabled } = this.forcedFlagsSubject.value;
3172
+ const newState = {
3173
+ enabled: enabled.filter((id) => id !== flagId),
3174
+ disabled: disabled.filter((id) => id !== flagId),
3175
+ };
3176
+ this.forcedFlagsSubject.next(newState);
3177
+ this.storageService.set(this.STORAGE_KEY, newState);
3178
+ }
3179
+ loadForcedFlags() {
3180
+ const savedFlags = this.storageService.get(this.STORAGE_KEY);
3181
+ if (savedFlags) {
3182
+ this.forcedFlagsSubject.next(savedFlags);
3183
+ }
3184
+ }
3185
+ /**
3186
+ * Apply feature flags from a preset (used by Presets Tool)
3187
+ * This method directly sets the forced flags state without user interaction
3188
+ */
3189
+ applyPresetFlags(presetState) {
3190
+ this.forcedFlagsSubject.next(presetState);
3191
+ this.storageService.set(this.STORAGE_KEY, presetState);
3192
+ }
3193
+ /**
3194
+ * Get current forced state for saving to preset
3195
+ * Returns the raw state of enabled and disabled flag IDs
3196
+ */
3197
+ getCurrentForcedState() {
3198
+ return this.forcedFlagsSubject.value;
3199
+ }
3200
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarInternalFeatureFlagService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3201
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarInternalFeatureFlagService, providedIn: 'root' }); }
3202
+ }
3203
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarInternalFeatureFlagService, decorators: [{
3204
+ type: Injectable,
3205
+ args: [{ providedIn: 'root' }]
3206
+ }], ctorParameters: () => [] });
3207
+
3886
3208
  class ToolbarFeatureFlagsToolComponent {
3887
3209
  constructor() {
3888
3210
  // Injects
@@ -3997,6 +3319,9 @@ class ToolbarFeatureFlagsToolComponent {
3997
3319
  return '';
3998
3320
  return flag.isEnabled ? 'on' : 'off';
3999
3321
  }
3322
+ getFlagPlaceholder(flag) {
3323
+ return flag.isEnabled ? 'On' : 'Off';
3324
+ }
4000
3325
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarFeatureFlagsToolComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4001
3326
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.7", type: ToolbarFeatureFlagsToolComponent, isStandalone: true, selector: "ngt-feature-flags-tool", ngImport: i0, template: `
4002
3327
  <ngt-toolbar-tool
@@ -4030,26 +3355,27 @@ class ToolbarFeatureFlagsToolComponent {
4030
3355
  noResultsMessage="No flags found matching your filter"
4031
3356
  >
4032
3357
  @for (flag of filteredFlags(); track flag.id) {
4033
- <ngt-list-item
4034
- [title]="flag.name"
4035
- [description]="flag.description"
4036
- [isForced]="flag.isForced"
4037
- [currentValue]="flag.isEnabled"
4038
- [originalValue]="flag.originalValue"
4039
- >
4040
- <ngt-select
4041
- [value]="getFlagValue(flag)"
4042
- [options]="flagValueOptions"
4043
- [ariaLabel]="'Set value for ' + flag.name"
4044
- (valueChange)="onFlagChange(flag.id, $event ?? '')"
4045
- size="small"
4046
- />
4047
- </ngt-list-item>
3358
+ <ngt-list-item
3359
+ [title]="flag.name"
3360
+ [description]="flag.description"
3361
+ [isForced]="flag.isForced"
3362
+ [currentValue]="flag.isEnabled"
3363
+ [originalValue]="flag.originalValue"
3364
+ >
3365
+ <ngt-select
3366
+ [value]="getFlagValue(flag)"
3367
+ [options]="flagValueOptions"
3368
+ [placeholder]="getFlagPlaceholder(flag)"
3369
+ [ariaLabel]="'Set value for ' + flag.name"
3370
+ (valueChange)="onFlagChange(flag.id, $event ?? '')"
3371
+ size="small"
3372
+ />
3373
+ </ngt-list-item>
4048
3374
  }
4049
3375
  </ngt-list>
4050
3376
  </div>
4051
3377
  </ngt-toolbar-tool>
4052
- `, isInline: true, styles: [".container{position:relative;display:flex;flex-direction:column;height:100%;padding:0}.tool-header{position:relative;flex-shrink:0;display:flex;gap:var(--ngt-spacing-sm);margin-bottom:var(--ngt-spacing-sm);ngt-input{flex:1}.filter-wrapper{flex:0 0 auto;display:flex;align-items:center;gap:var(--ngt-spacing-md);.filter-icon{width:18px;height:18px;flex-shrink:0;opacity:.6}ngt-select{flex:0 0 auto;min-width:180px}}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "component", type: ToolbarToolComponent, selector: "ngt-toolbar-tool", inputs: ["options", "icon", "title", "badge"] }, { kind: "component", type: ToolbarInputComponent, selector: "ngt-input", inputs: ["value", "type", "placeholder", "ariaLabel", "inputClass"], outputs: ["valueChange"] }, { kind: "component", type: ToolbarSelectComponent, selector: "ngt-select", inputs: ["value", "options", "ariaLabel", "label", "size"], outputs: ["valueChange"] }, { kind: "component", type: ToolbarIconComponent, selector: "ngt-icon", inputs: ["name"] }, { kind: "component", type: ToolbarListComponent, selector: "ngt-list", inputs: ["hasItems", "hasResults", "emptyMessage", "emptyHint", "noResultsMessage"] }, { kind: "component", type: ToolbarListItemComponent, selector: "ngt-list-item", inputs: ["title", "description", "isForced", "currentValue", "originalValue"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3378
+ `, isInline: true, styles: [".container{position:relative;display:flex;flex-direction:column;height:100%;padding:0}.tool-header{position:relative;flex-shrink:0;display:flex;gap:var(--ngt-spacing-sm);margin-bottom:var(--ngt-spacing-sm);ngt-input{flex:1}.filter-wrapper{flex:0 0 auto;display:flex;align-items:center;gap:var(--ngt-spacing-md);.filter-icon{width:18px;height:18px;flex-shrink:0;opacity:.6}ngt-select{flex:0 0 auto;min-width:180px}}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "component", type: ToolbarToolComponent, selector: "ngt-toolbar-tool", inputs: ["options", "icon", "title", "badge"] }, { kind: "component", type: ToolbarInputComponent, selector: "ngt-input", inputs: ["value", "type", "placeholder", "ariaLabel", "inputClass"], outputs: ["valueChange"] }, { kind: "component", type: ToolbarSelectComponent, selector: "ngt-select", inputs: ["value", "options", "ariaLabel", "label", "size", "placeholder"], outputs: ["valueChange"] }, { kind: "component", type: ToolbarIconComponent, selector: "ngt-icon", inputs: ["name"] }, { kind: "component", type: ToolbarListComponent, selector: "ngt-list", inputs: ["hasItems", "hasResults", "emptyMessage", "emptyHint", "noResultsMessage"] }, { kind: "component", type: ToolbarListItemComponent, selector: "ngt-list-item", inputs: ["title", "description", "isForced", "currentValue", "originalValue"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4053
3379
  }
4054
3380
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarFeatureFlagsToolComponent, decorators: [{
4055
3381
  type: Component,
@@ -4093,21 +3419,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImpor
4093
3419
  noResultsMessage="No flags found matching your filter"
4094
3420
  >
4095
3421
  @for (flag of filteredFlags(); track flag.id) {
4096
- <ngt-list-item
4097
- [title]="flag.name"
4098
- [description]="flag.description"
4099
- [isForced]="flag.isForced"
4100
- [currentValue]="flag.isEnabled"
4101
- [originalValue]="flag.originalValue"
4102
- >
4103
- <ngt-select
4104
- [value]="getFlagValue(flag)"
4105
- [options]="flagValueOptions"
4106
- [ariaLabel]="'Set value for ' + flag.name"
4107
- (valueChange)="onFlagChange(flag.id, $event ?? '')"
4108
- size="small"
4109
- />
4110
- </ngt-list-item>
3422
+ <ngt-list-item
3423
+ [title]="flag.name"
3424
+ [description]="flag.description"
3425
+ [isForced]="flag.isForced"
3426
+ [currentValue]="flag.isEnabled"
3427
+ [originalValue]="flag.originalValue"
3428
+ >
3429
+ <ngt-select
3430
+ [value]="getFlagValue(flag)"
3431
+ [options]="flagValueOptions"
3432
+ [placeholder]="getFlagPlaceholder(flag)"
3433
+ [ariaLabel]="'Set value for ' + flag.name"
3434
+ (valueChange)="onFlagChange(flag.id, $event ?? '')"
3435
+ size="small"
3436
+ />
3437
+ </ngt-list-item>
4111
3438
  }
4112
3439
  </ngt-list>
4113
3440
  </div>
@@ -4505,6 +3832,79 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImpor
4505
3832
  `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{--ngt-border-radius-small: 4px;--ngt-border-radius-medium: 8px;--ngt-border-radius-large: 12px;--ngt-transition-default: all .2s ease-out;--ngt-transition-smooth: all .2s ease-in-out;--ngt-bg-primary: rgb(255, 255, 255);--ngt-bg-gradient: linear-gradient(180deg, rgb(243, 244, 246) 0%, rgba(243, 244, 246, .88) 100%);--ngt-text-primary: rgb(17, 24, 39);--ngt-text-secondary: rgb(55, 65, 81);--ngt-text-muted: rgb(107, 114, 128);--ngt-border-primary: #e5e7eb;--ngt-border-subtle: rgba(17, 24, 39, .1);--ngt-hover-bg: rgba(17, 24, 39, .05);--ngt-hover-danger: rgb(239, 68, 68);--ngt-shadow-toolbar: 0 2px 8px rgba(156, 163, 175, .2);--ngt-shadow-tooltip: 0 0 0 1px rgba(17, 24, 39, .05), 0 4px 8px rgba(107, 114, 128, .15), 0 2px 4px rgba(107, 114, 128, .1);--ngt-shadow-window: 0px 0px 0px 0px rgba(156, 163, 175, .1), 0px 1px 2px 0px rgba(156, 163, 175, .12), 0px 4px 4px 0px rgba(156, 163, 175, .1), 0px 10px 6px 0px rgba(156, 163, 175, .08), 0px 17px 7px 0px rgba(156, 163, 175, .05), 0px 26px 7px 0px rgba(156, 163, 175, .02);--ngt-spacing-xs: 4px;--ngt-spacing-sm: 6px;--ngt-spacing-md: 12px;--ngt-spacing-lg: 16px;--ngt-window-padding: 16px;--ngt-font-size-xxs: .65rem;--ngt-font-size-xs: .75rem;--ngt-font-size-sm: .875rem;--ngt-font-size-md: 1rem;--ngt-font-size-lg: 1.25rem;--ngt-font-size-xl: 2rem;--ngt-background-secondary: var(--ngt-bg-primary);--ngt-background-hover: var(--ngt-hover-bg);--ngt-primary: #df30d4;--ngt-primary-rgb: 223, 48, 212;--ngt-text-on-primary: rgb(255, 255, 255);--ngt-border-color: var(--ngt-border-primary);--ngt-note-background: rgb(219, 234, 254);--ngt-note-border: rgba(37, 99, 235, .2);--ngt-warning-background: rgb(254, 249, 195);--ngt-warning-border: rgba(202, 138, 4, .2);--ngt-error-background: rgb(254, 226, 226);--ngt-error-border: rgba(220, 38, 38, .2)}.settings{display:flex;flex-direction:column;height:100%}.instruction{display:flex;justify-content:space-between;align-items:flex-start;gap:var(--ngt-spacing-sm)}.instruction__label{display:flex;flex-direction:column;gap:var(--ngt-spacing-xs)}.instruction__label-text{color:var(--ngt-text-primary);font-size:var(--ngt-font-size-sm);font-weight:500}.instruction__label-description{color:var(--ngt-text-muted);font-size:var(--ngt-font-size-xs)}.instruction__control{flex:1}.instruction__control-button{display:flex;gap:var(--ngt-spacing-xs);justify-content:flex-end}.settings-container{display:flex;flex-direction:column;gap:var(--ngt-spacing-md)}.settings-container .settings-actions{display:flex;gap:var(--ngt-spacing-md)}.settings-container .settings-actions>*{width:50%;min-width:0}.footer-links{margin-top:auto;padding-top:var(--ngt-spacing-lg);border-top:1px solid var(--ngt-border-subtle);display:flex;flex-direction:row;justify-content:space-between;gap:var(--ngt-spacing-lg)}\n"] }]
4506
3833
  }] });
4507
3834
 
3835
+ class ToolbarInternalLanguageService {
3836
+ constructor() {
3837
+ this.STORAGE_KEY = 'language';
3838
+ this.storageService = inject(ToolbarStorageService);
3839
+ this.stateService = inject(ToolbarStateService);
3840
+ this.languages$ = new BehaviorSubject([]);
3841
+ this.forcedLanguage$ = new BehaviorSubject(null);
3842
+ this.languages = toSignal(this.languages$, { initialValue: [] });
3843
+ this.loadForcedLanguage();
3844
+ }
3845
+ setAppLanguages(languages) {
3846
+ this.languages$.next(languages);
3847
+ }
3848
+ getAppLanguages() {
3849
+ return this.languages$.asObservable();
3850
+ }
3851
+ setForcedLanguage(language) {
3852
+ this.forcedLanguage$.next(language);
3853
+ this.storageService.set(this.STORAGE_KEY, language);
3854
+ }
3855
+ getForcedLanguage() {
3856
+ return this.forcedLanguage$.pipe(map((language) => {
3857
+ // If toolbar is disabled, return empty array (no forced values)
3858
+ if (!this.stateService.isEnabled()) {
3859
+ return [];
3860
+ }
3861
+ return language ? [language] : [];
3862
+ }));
3863
+ }
3864
+ removeForcedLanguage() {
3865
+ this.forcedLanguage$.next(null);
3866
+ this.storageService.remove(this.STORAGE_KEY);
3867
+ }
3868
+ loadForcedLanguage() {
3869
+ const savedLanguage = this.storageService.get(this.STORAGE_KEY);
3870
+ if (savedLanguage) {
3871
+ this.forcedLanguage$.next(savedLanguage);
3872
+ }
3873
+ }
3874
+ /**
3875
+ * Apply language from a preset (used by Presets Tool)
3876
+ * Accepts a language ID and finds the corresponding Language object from available languages
3877
+ */
3878
+ async applyPresetLanguage(languageId) {
3879
+ if (languageId === null) {
3880
+ this.removeForcedLanguage();
3881
+ return;
3882
+ }
3883
+ // Get available languages and find matching one
3884
+ const languages = await firstValueFrom(this.languages$);
3885
+ const language = languages.find((lang) => lang.id === languageId);
3886
+ if (language) {
3887
+ this.setForcedLanguage(language);
3888
+ }
3889
+ else {
3890
+ console.warn(`Language ${languageId} not found in available languages. Skipping.`);
3891
+ }
3892
+ }
3893
+ /**
3894
+ * Get current forced language ID for saving to preset
3895
+ * Returns the language ID or null if no language is forced
3896
+ */
3897
+ getCurrentForcedLanguage() {
3898
+ return this.forcedLanguage$.value?.id ?? null;
3899
+ }
3900
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarInternalLanguageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3901
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarInternalLanguageService, providedIn: 'root' }); }
3902
+ }
3903
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarInternalLanguageService, decorators: [{
3904
+ type: Injectable,
3905
+ args: [{ providedIn: 'root' }]
3906
+ }], ctorParameters: () => [] });
3907
+
4508
3908
  class ToolbarLanguageToolComponent {
4509
3909
  constructor() {
4510
3910
  this.languageService = inject(ToolbarInternalLanguageService);
@@ -4536,39 +3936,182 @@ class ToolbarLanguageToolComponent {
4536
3936
  this.languageService.setForcedLanguage(selectedLanguage);
4537
3937
  }
4538
3938
  }
4539
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarLanguageToolComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4540
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.0.7", type: ToolbarLanguageToolComponent, isStandalone: true, selector: "ngt-language-tool", ngImport: i0, template: `
4541
- <ngt-toolbar-tool title="Languages" icon="translate" [options]="options">
4542
- <div class="language-select">
4543
- <label for="language-select">Language</label>
4544
- <ngt-select
4545
- id="language-select"
4546
- [value]="activeLanguage()"
4547
- [options]="languageOptions()"
4548
- [size]="'medium'"
4549
- (valueChange)="onLanguageChange($event ?? '')"
4550
- />
4551
- </div>
4552
- </ngt-toolbar-tool>
4553
- `, isInline: true, styles: [":host{--ngt-border-radius-small: 4px;--ngt-border-radius-medium: 8px;--ngt-border-radius-large: 12px;--ngt-transition-default: all .2s ease-out;--ngt-transition-smooth: all .2s ease-in-out;--ngt-bg-primary: rgb(255, 255, 255);--ngt-bg-gradient: linear-gradient(180deg, rgb(243, 244, 246) 0%, rgba(243, 244, 246, .88) 100%);--ngt-text-primary: rgb(17, 24, 39);--ngt-text-secondary: rgb(55, 65, 81);--ngt-text-muted: rgb(107, 114, 128);--ngt-border-primary: #e5e7eb;--ngt-border-subtle: rgba(17, 24, 39, .1);--ngt-hover-bg: rgba(17, 24, 39, .05);--ngt-hover-danger: rgb(239, 68, 68);--ngt-shadow-toolbar: 0 2px 8px rgba(156, 163, 175, .2);--ngt-shadow-tooltip: 0 0 0 1px rgba(17, 24, 39, .05), 0 4px 8px rgba(107, 114, 128, .15), 0 2px 4px rgba(107, 114, 128, .1);--ngt-shadow-window: 0px 0px 0px 0px rgba(156, 163, 175, .1), 0px 1px 2px 0px rgba(156, 163, 175, .12), 0px 4px 4px 0px rgba(156, 163, 175, .1), 0px 10px 6px 0px rgba(156, 163, 175, .08), 0px 17px 7px 0px rgba(156, 163, 175, .05), 0px 26px 7px 0px rgba(156, 163, 175, .02);--ngt-spacing-xs: 4px;--ngt-spacing-sm: 6px;--ngt-spacing-md: 12px;--ngt-spacing-lg: 16px;--ngt-window-padding: 16px;--ngt-font-size-xxs: .65rem;--ngt-font-size-xs: .75rem;--ngt-font-size-sm: .875rem;--ngt-font-size-md: 1rem;--ngt-font-size-lg: 1.25rem;--ngt-font-size-xl: 2rem;--ngt-background-secondary: var(--ngt-bg-primary);--ngt-background-hover: var(--ngt-hover-bg);--ngt-primary: #df30d4;--ngt-primary-rgb: 223, 48, 212;--ngt-text-on-primary: rgb(255, 255, 255);--ngt-border-color: var(--ngt-border-primary);--ngt-note-background: rgb(219, 234, 254);--ngt-note-border: rgba(37, 99, 235, .2);--ngt-warning-background: rgb(254, 249, 195);--ngt-warning-border: rgba(202, 138, 4, .2);--ngt-error-background: rgb(254, 226, 226);--ngt-error-border: rgba(220, 38, 38, .2)}.language-select{display:flex;flex-direction:row;gap:.5rem;align-items:center;justify-content:space-between}\n"], dependencies: [{ kind: "component", type: ToolbarToolComponent, selector: "ngt-toolbar-tool", inputs: ["options", "icon", "title", "badge"] }, { kind: "component", type: ToolbarSelectComponent, selector: "ngt-select", inputs: ["value", "options", "ariaLabel", "label", "size"], outputs: ["valueChange"] }] }); }
3939
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarLanguageToolComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3940
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.0.7", type: ToolbarLanguageToolComponent, isStandalone: true, selector: "ngt-language-tool", ngImport: i0, template: `
3941
+ <ngt-toolbar-tool title="Languages" icon="translate" [options]="options">
3942
+ <div class="language-select">
3943
+ <label for="language-select">Language</label>
3944
+ <ngt-select
3945
+ id="language-select"
3946
+ [value]="activeLanguage()"
3947
+ [options]="languageOptions()"
3948
+ [size]="'medium'"
3949
+ (valueChange)="onLanguageChange($event ?? '')"
3950
+ />
3951
+ </div>
3952
+ </ngt-toolbar-tool>
3953
+ `, isInline: true, styles: [":host{--ngt-border-radius-small: 4px;--ngt-border-radius-medium: 8px;--ngt-border-radius-large: 12px;--ngt-transition-default: all .2s ease-out;--ngt-transition-smooth: all .2s ease-in-out;--ngt-bg-primary: rgb(255, 255, 255);--ngt-bg-gradient: linear-gradient(180deg, rgb(243, 244, 246) 0%, rgba(243, 244, 246, .88) 100%);--ngt-text-primary: rgb(17, 24, 39);--ngt-text-secondary: rgb(55, 65, 81);--ngt-text-muted: rgb(107, 114, 128);--ngt-border-primary: #e5e7eb;--ngt-border-subtle: rgba(17, 24, 39, .1);--ngt-hover-bg: rgba(17, 24, 39, .05);--ngt-hover-danger: rgb(239, 68, 68);--ngt-shadow-toolbar: 0 2px 8px rgba(156, 163, 175, .2);--ngt-shadow-tooltip: 0 0 0 1px rgba(17, 24, 39, .05), 0 4px 8px rgba(107, 114, 128, .15), 0 2px 4px rgba(107, 114, 128, .1);--ngt-shadow-window: 0px 0px 0px 0px rgba(156, 163, 175, .1), 0px 1px 2px 0px rgba(156, 163, 175, .12), 0px 4px 4px 0px rgba(156, 163, 175, .1), 0px 10px 6px 0px rgba(156, 163, 175, .08), 0px 17px 7px 0px rgba(156, 163, 175, .05), 0px 26px 7px 0px rgba(156, 163, 175, .02);--ngt-spacing-xs: 4px;--ngt-spacing-sm: 6px;--ngt-spacing-md: 12px;--ngt-spacing-lg: 16px;--ngt-window-padding: 16px;--ngt-font-size-xxs: .65rem;--ngt-font-size-xs: .75rem;--ngt-font-size-sm: .875rem;--ngt-font-size-md: 1rem;--ngt-font-size-lg: 1.25rem;--ngt-font-size-xl: 2rem;--ngt-background-secondary: var(--ngt-bg-primary);--ngt-background-hover: var(--ngt-hover-bg);--ngt-primary: #df30d4;--ngt-primary-rgb: 223, 48, 212;--ngt-text-on-primary: rgb(255, 255, 255);--ngt-border-color: var(--ngt-border-primary);--ngt-note-background: rgb(219, 234, 254);--ngt-note-border: rgba(37, 99, 235, .2);--ngt-warning-background: rgb(254, 249, 195);--ngt-warning-border: rgba(202, 138, 4, .2);--ngt-error-background: rgb(254, 226, 226);--ngt-error-border: rgba(220, 38, 38, .2)}.language-select{display:flex;flex-direction:row;gap:.5rem;align-items:center;justify-content:space-between}\n"], dependencies: [{ kind: "component", type: ToolbarToolComponent, selector: "ngt-toolbar-tool", inputs: ["options", "icon", "title", "badge"] }, { kind: "component", type: ToolbarSelectComponent, selector: "ngt-select", inputs: ["value", "options", "ariaLabel", "label", "size", "placeholder"], outputs: ["valueChange"] }] }); }
3954
+ }
3955
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarLanguageToolComponent, decorators: [{
3956
+ type: Component,
3957
+ args: [{ selector: 'ngt-language-tool', standalone: true, imports: [ToolbarToolComponent, ToolbarSelectComponent], template: `
3958
+ <ngt-toolbar-tool title="Languages" icon="translate" [options]="options">
3959
+ <div class="language-select">
3960
+ <label for="language-select">Language</label>
3961
+ <ngt-select
3962
+ id="language-select"
3963
+ [value]="activeLanguage()"
3964
+ [options]="languageOptions()"
3965
+ [size]="'medium'"
3966
+ (valueChange)="onLanguageChange($event ?? '')"
3967
+ />
3968
+ </div>
3969
+ </ngt-toolbar-tool>
3970
+ `, styles: [":host{--ngt-border-radius-small: 4px;--ngt-border-radius-medium: 8px;--ngt-border-radius-large: 12px;--ngt-transition-default: all .2s ease-out;--ngt-transition-smooth: all .2s ease-in-out;--ngt-bg-primary: rgb(255, 255, 255);--ngt-bg-gradient: linear-gradient(180deg, rgb(243, 244, 246) 0%, rgba(243, 244, 246, .88) 100%);--ngt-text-primary: rgb(17, 24, 39);--ngt-text-secondary: rgb(55, 65, 81);--ngt-text-muted: rgb(107, 114, 128);--ngt-border-primary: #e5e7eb;--ngt-border-subtle: rgba(17, 24, 39, .1);--ngt-hover-bg: rgba(17, 24, 39, .05);--ngt-hover-danger: rgb(239, 68, 68);--ngt-shadow-toolbar: 0 2px 8px rgba(156, 163, 175, .2);--ngt-shadow-tooltip: 0 0 0 1px rgba(17, 24, 39, .05), 0 4px 8px rgba(107, 114, 128, .15), 0 2px 4px rgba(107, 114, 128, .1);--ngt-shadow-window: 0px 0px 0px 0px rgba(156, 163, 175, .1), 0px 1px 2px 0px rgba(156, 163, 175, .12), 0px 4px 4px 0px rgba(156, 163, 175, .1), 0px 10px 6px 0px rgba(156, 163, 175, .08), 0px 17px 7px 0px rgba(156, 163, 175, .05), 0px 26px 7px 0px rgba(156, 163, 175, .02);--ngt-spacing-xs: 4px;--ngt-spacing-sm: 6px;--ngt-spacing-md: 12px;--ngt-spacing-lg: 16px;--ngt-window-padding: 16px;--ngt-font-size-xxs: .65rem;--ngt-font-size-xs: .75rem;--ngt-font-size-sm: .875rem;--ngt-font-size-md: 1rem;--ngt-font-size-lg: 1.25rem;--ngt-font-size-xl: 2rem;--ngt-background-secondary: var(--ngt-bg-primary);--ngt-background-hover: var(--ngt-hover-bg);--ngt-primary: #df30d4;--ngt-primary-rgb: 223, 48, 212;--ngt-text-on-primary: rgb(255, 255, 255);--ngt-border-color: var(--ngt-border-primary);--ngt-note-background: rgb(219, 234, 254);--ngt-note-border: rgba(37, 99, 235, .2);--ngt-warning-background: rgb(254, 249, 195);--ngt-warning-border: rgba(202, 138, 4, .2);--ngt-error-background: rgb(254, 226, 226);--ngt-error-border: rgba(220, 38, 38, .2)}.language-select{display:flex;flex-direction:row;gap:.5rem;align-items:center;justify-content:space-between}\n"] }]
3971
+ }] });
3972
+
3973
+ class ToolbarInternalPermissionsService {
3974
+ constructor() {
3975
+ this.STORAGE_KEY = 'permissions';
3976
+ this.storageService = inject(ToolbarStorageService);
3977
+ this.stateService = inject(ToolbarStateService);
3978
+ this.appPermissions$ = new BehaviorSubject([]);
3979
+ this.forcedStateSubject = new BehaviorSubject({
3980
+ granted: [],
3981
+ denied: [],
3982
+ });
3983
+ this.forcedState$ = this.forcedStateSubject.asObservable();
3984
+ this.permissions$ = combineLatest([
3985
+ this.appPermissions$,
3986
+ this.forcedState$,
3987
+ ]).pipe(map(([appPermissions, { granted, denied }]) => {
3988
+ // If toolbar is disabled, return app permissions without overrides
3989
+ if (!this.stateService.isEnabled()) {
3990
+ return appPermissions;
3991
+ }
3992
+ return appPermissions.map((permission) => {
3993
+ const isForced = granted.includes(permission.id) || denied.includes(permission.id);
3994
+ return {
3995
+ ...permission,
3996
+ isForced,
3997
+ isGranted: granted.includes(permission.id)
3998
+ ? true
3999
+ : denied.includes(permission.id)
4000
+ ? false
4001
+ : permission.isGranted,
4002
+ originalValue: isForced ? permission.isGranted : undefined,
4003
+ };
4004
+ });
4005
+ }));
4006
+ this.permissions = toSignal(this.permissions$, { initialValue: [] });
4007
+ this.loadForcedState();
4008
+ }
4009
+ setAppPermissions(permissions) {
4010
+ this.appPermissions$.next(permissions);
4011
+ this.validateAndCleanForcedState(permissions);
4012
+ }
4013
+ setPermission(id, granted) {
4014
+ const { granted: grantedIds, denied: deniedIds } = this.forcedStateSubject.value;
4015
+ const newGranted = grantedIds.filter((permId) => permId !== id);
4016
+ const newDenied = deniedIds.filter((permId) => permId !== id);
4017
+ if (granted) {
4018
+ newGranted.push(id);
4019
+ }
4020
+ else {
4021
+ newDenied.push(id);
4022
+ }
4023
+ const newState = { granted: newGranted, denied: newDenied };
4024
+ this.forcedStateSubject.next(newState);
4025
+ this.storageService.set(this.STORAGE_KEY, newState);
4026
+ }
4027
+ removePermissionOverride(id) {
4028
+ const { granted, denied } = this.forcedStateSubject.value;
4029
+ const newState = {
4030
+ granted: granted.filter((permId) => permId !== id),
4031
+ denied: denied.filter((permId) => permId !== id),
4032
+ };
4033
+ this.forcedStateSubject.next(newState);
4034
+ this.storageService.set(this.STORAGE_KEY, newState);
4035
+ }
4036
+ getForcedPermissions() {
4037
+ return this.permissions$.pipe(map((permissions) => {
4038
+ // If toolbar is disabled, return empty array (no forced values)
4039
+ if (!this.stateService.isEnabled()) {
4040
+ return [];
4041
+ }
4042
+ return permissions.filter((permission) => permission.isForced);
4043
+ }));
4044
+ }
4045
+ /**
4046
+ * Apply a preset permissions state, replacing the current forced state.
4047
+ * Useful for automated testing or restoring saved configurations.
4048
+ * @param state The preset forced permissions state to apply
4049
+ */
4050
+ applyPresetPermissions(state) {
4051
+ this.forcedStateSubject.next(state);
4052
+ this.storageService.set(this.STORAGE_KEY, state);
4053
+ }
4054
+ /**
4055
+ * Get the current forced permissions state.
4056
+ * Returns a deep copy to prevent external mutations.
4057
+ * @returns Current forced permissions state
4058
+ */
4059
+ getCurrentForcedState() {
4060
+ const currentState = this.forcedStateSubject.value;
4061
+ return {
4062
+ granted: [...currentState.granted],
4063
+ denied: [...currentState.denied],
4064
+ };
4065
+ }
4066
+ loadForcedState() {
4067
+ try {
4068
+ const savedState = this.storageService.get(this.STORAGE_KEY);
4069
+ if (savedState && this.isValidForcedState(savedState)) {
4070
+ this.forcedStateSubject.next(savedState);
4071
+ }
4072
+ }
4073
+ catch (error) {
4074
+ console.warn('Error loading forced permissions state from localStorage:', error);
4075
+ }
4076
+ }
4077
+ isValidForcedState(state) {
4078
+ if (!state || typeof state !== 'object') {
4079
+ return false;
4080
+ }
4081
+ const candidate = state;
4082
+ return (Array.isArray(candidate['granted']) &&
4083
+ Array.isArray(candidate['denied']));
4084
+ }
4085
+ validateAndCleanForcedState(permissions) {
4086
+ const currentState = this.forcedStateSubject.value;
4087
+ const validIds = new Set(permissions.map((p) => p.id));
4088
+ const cleanedGranted = currentState.granted.filter((id) => validIds.has(id));
4089
+ const cleanedDenied = currentState.denied.filter((id) => validIds.has(id));
4090
+ const hasInvalidIds = cleanedGranted.length !== currentState.granted.length ||
4091
+ cleanedDenied.length !== currentState.denied.length;
4092
+ if (hasInvalidIds) {
4093
+ const cleanedState = {
4094
+ granted: cleanedGranted,
4095
+ denied: cleanedDenied,
4096
+ };
4097
+ this.forcedStateSubject.next(cleanedState);
4098
+ this.storageService.set(this.STORAGE_KEY, cleanedState);
4099
+ const invalidIds = [
4100
+ ...currentState.granted.filter((id) => !validIds.has(id)),
4101
+ ...currentState.denied.filter((id) => !validIds.has(id)),
4102
+ ];
4103
+ if (invalidIds.length > 0) {
4104
+ console.warn('Removed invalid permission IDs from forced state:', invalidIds);
4105
+ }
4106
+ }
4107
+ }
4108
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarInternalPermissionsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
4109
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarInternalPermissionsService, providedIn: 'root' }); }
4554
4110
  }
4555
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarLanguageToolComponent, decorators: [{
4556
- type: Component,
4557
- args: [{ selector: 'ngt-language-tool', standalone: true, imports: [ToolbarToolComponent, ToolbarSelectComponent], template: `
4558
- <ngt-toolbar-tool title="Languages" icon="translate" [options]="options">
4559
- <div class="language-select">
4560
- <label for="language-select">Language</label>
4561
- <ngt-select
4562
- id="language-select"
4563
- [value]="activeLanguage()"
4564
- [options]="languageOptions()"
4565
- [size]="'medium'"
4566
- (valueChange)="onLanguageChange($event ?? '')"
4567
- />
4568
- </div>
4569
- </ngt-toolbar-tool>
4570
- `, styles: [":host{--ngt-border-radius-small: 4px;--ngt-border-radius-medium: 8px;--ngt-border-radius-large: 12px;--ngt-transition-default: all .2s ease-out;--ngt-transition-smooth: all .2s ease-in-out;--ngt-bg-primary: rgb(255, 255, 255);--ngt-bg-gradient: linear-gradient(180deg, rgb(243, 244, 246) 0%, rgba(243, 244, 246, .88) 100%);--ngt-text-primary: rgb(17, 24, 39);--ngt-text-secondary: rgb(55, 65, 81);--ngt-text-muted: rgb(107, 114, 128);--ngt-border-primary: #e5e7eb;--ngt-border-subtle: rgba(17, 24, 39, .1);--ngt-hover-bg: rgba(17, 24, 39, .05);--ngt-hover-danger: rgb(239, 68, 68);--ngt-shadow-toolbar: 0 2px 8px rgba(156, 163, 175, .2);--ngt-shadow-tooltip: 0 0 0 1px rgba(17, 24, 39, .05), 0 4px 8px rgba(107, 114, 128, .15), 0 2px 4px rgba(107, 114, 128, .1);--ngt-shadow-window: 0px 0px 0px 0px rgba(156, 163, 175, .1), 0px 1px 2px 0px rgba(156, 163, 175, .12), 0px 4px 4px 0px rgba(156, 163, 175, .1), 0px 10px 6px 0px rgba(156, 163, 175, .08), 0px 17px 7px 0px rgba(156, 163, 175, .05), 0px 26px 7px 0px rgba(156, 163, 175, .02);--ngt-spacing-xs: 4px;--ngt-spacing-sm: 6px;--ngt-spacing-md: 12px;--ngt-spacing-lg: 16px;--ngt-window-padding: 16px;--ngt-font-size-xxs: .65rem;--ngt-font-size-xs: .75rem;--ngt-font-size-sm: .875rem;--ngt-font-size-md: 1rem;--ngt-font-size-lg: 1.25rem;--ngt-font-size-xl: 2rem;--ngt-background-secondary: var(--ngt-bg-primary);--ngt-background-hover: var(--ngt-hover-bg);--ngt-primary: #df30d4;--ngt-primary-rgb: 223, 48, 212;--ngt-text-on-primary: rgb(255, 255, 255);--ngt-border-color: var(--ngt-border-primary);--ngt-note-background: rgb(219, 234, 254);--ngt-note-border: rgba(37, 99, 235, .2);--ngt-warning-background: rgb(254, 249, 195);--ngt-warning-border: rgba(202, 138, 4, .2);--ngt-error-background: rgb(254, 226, 226);--ngt-error-border: rgba(220, 38, 38, .2)}.language-select{display:flex;flex-direction:row;gap:.5rem;align-items:center;justify-content:space-between}\n"] }]
4571
- }] });
4111
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarInternalPermissionsService, decorators: [{
4112
+ type: Injectable,
4113
+ args: [{ providedIn: 'root' }]
4114
+ }], ctorParameters: () => [] });
4572
4115
 
4573
4116
  class ToolbarPermissionsToolComponent {
4574
4117
  constructor() {
@@ -4739,7 +4282,7 @@ class ToolbarPermissionsToolComponent {
4739
4282
  </ngt-list>
4740
4283
  </div>
4741
4284
  </ngt-toolbar-tool>
4742
- `, isInline: true, styles: [".container{position:relative;display:flex;flex-direction:column;height:100%;padding:0}.tool-header{position:relative;flex-shrink:0;display:flex;gap:var(--ngt-spacing-sm);margin-bottom:var(--ngt-spacing-sm);ngt-input{flex:1}.filter-wrapper{flex:0 0 auto;display:flex;align-items:center;gap:var(--ngt-spacing-md);.filter-icon{width:18px;height:18px;flex-shrink:0;opacity:.6}ngt-select{flex:0 0 auto;min-width:180px}}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "component", type: ToolbarToolComponent, selector: "ngt-toolbar-tool", inputs: ["options", "icon", "title", "badge"] }, { kind: "component", type: ToolbarInputComponent, selector: "ngt-input", inputs: ["value", "type", "placeholder", "ariaLabel", "inputClass"], outputs: ["valueChange"] }, { kind: "component", type: ToolbarSelectComponent, selector: "ngt-select", inputs: ["value", "options", "ariaLabel", "label", "size"], outputs: ["valueChange"] }, { kind: "component", type: ToolbarIconComponent, selector: "ngt-icon", inputs: ["name"] }, { kind: "component", type: ToolbarListComponent, selector: "ngt-list", inputs: ["hasItems", "hasResults", "emptyMessage", "emptyHint", "noResultsMessage"] }, { kind: "component", type: ToolbarListItemComponent, selector: "ngt-list-item", inputs: ["title", "description", "isForced", "currentValue", "originalValue"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4285
+ `, isInline: true, styles: [".container{position:relative;display:flex;flex-direction:column;height:100%;padding:0}.tool-header{position:relative;flex-shrink:0;display:flex;gap:var(--ngt-spacing-sm);margin-bottom:var(--ngt-spacing-sm);ngt-input{flex:1}.filter-wrapper{flex:0 0 auto;display:flex;align-items:center;gap:var(--ngt-spacing-md);.filter-icon{width:18px;height:18px;flex-shrink:0;opacity:.6}ngt-select{flex:0 0 auto;min-width:180px}}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "component", type: ToolbarToolComponent, selector: "ngt-toolbar-tool", inputs: ["options", "icon", "title", "badge"] }, { kind: "component", type: ToolbarInputComponent, selector: "ngt-input", inputs: ["value", "type", "placeholder", "ariaLabel", "inputClass"], outputs: ["valueChange"] }, { kind: "component", type: ToolbarSelectComponent, selector: "ngt-select", inputs: ["value", "options", "ariaLabel", "label", "size", "placeholder"], outputs: ["valueChange"] }, { kind: "component", type: ToolbarIconComponent, selector: "ngt-icon", inputs: ["name"] }, { kind: "component", type: ToolbarListComponent, selector: "ngt-list", inputs: ["hasItems", "hasResults", "emptyMessage", "emptyHint", "noResultsMessage"] }, { kind: "component", type: ToolbarListItemComponent, selector: "ngt-list-item", inputs: ["title", "description", "isForced", "currentValue", "originalValue"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4743
4286
  }
4744
4287
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarPermissionsToolComponent, decorators: [{
4745
4288
  type: Component,
@@ -5747,6 +5290,10 @@ class ToolbarPresetsToolComponent {
5747
5290
  (dragleave)="onDragLeave($event)"
5748
5291
  (drop)="onFileDrop($event)"
5749
5292
  (click)="fileInput.click()"
5293
+ (keydown.enter)="fileInput.click()"
5294
+ (keydown.space)="fileInput.click()"
5295
+ tabindex="0"
5296
+ role="button"
5750
5297
  >
5751
5298
  <input
5752
5299
  #fileInput
@@ -6309,6 +5856,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImpor
6309
5856
  (dragleave)="onDragLeave($event)"
6310
5857
  (drop)="onFileDrop($event)"
6311
5858
  (click)="fileInput.click()"
5859
+ (keydown.enter)="fileInput.click()"
5860
+ (keydown.space)="fileInput.click()"
5861
+ tabindex="0"
5862
+ role="button"
6312
5863
  >
6313
5864
  <input
6314
5865
  #fileInput
@@ -6762,112 +6313,503 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImpor
6762
6313
  }
6763
6314
  </div>
6764
6315
  }
6765
- `, animations: [
6766
- trigger('toolbarState', [
6767
- state('hidden', style({
6768
- transform: 'translate(-50%, calc(100% + -1.2rem))',
6769
- })),
6770
- state('visible', style({
6771
- transform: 'translate(-50%, -1rem)',
6772
- })),
6773
- transition('hidden <=> visible', [
6774
- animate('300ms cubic-bezier(0.4, 0, 0.2, 1)'),
6775
- ]),
6776
- ]),
6777
- ], styles: [":host{display:contents;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",Segoe UI Symbol,\"Noto Color Emoji\"}.ngt-toolbar{--ngt-border-radius-small: 4px;--ngt-border-radius-medium: 8px;--ngt-border-radius-large: 12px;--ngt-transition-default: all .2s ease-out;--ngt-transition-smooth: all .2s ease-in-out;--ngt-bg-primary: rgb(255, 255, 255);--ngt-bg-gradient: linear-gradient(180deg, rgb(243, 244, 246) 0%, rgba(243, 244, 246, .88) 100%);--ngt-text-primary: rgb(17, 24, 39);--ngt-text-secondary: rgb(55, 65, 81);--ngt-text-muted: rgb(107, 114, 128);--ngt-border-primary: #e5e7eb;--ngt-border-subtle: rgba(17, 24, 39, .1);--ngt-hover-bg: rgba(17, 24, 39, .05);--ngt-hover-danger: rgb(239, 68, 68);--ngt-shadow-toolbar: 0 2px 8px rgba(156, 163, 175, .2);--ngt-shadow-tooltip: 0 0 0 1px rgba(17, 24, 39, .05), 0 4px 8px rgba(107, 114, 128, .15), 0 2px 4px rgba(107, 114, 128, .1);--ngt-shadow-window: 0px 0px 0px 0px rgba(156, 163, 175, .1), 0px 1px 2px 0px rgba(156, 163, 175, .12), 0px 4px 4px 0px rgba(156, 163, 175, .1), 0px 10px 6px 0px rgba(156, 163, 175, .08), 0px 17px 7px 0px rgba(156, 163, 175, .05), 0px 26px 7px 0px rgba(156, 163, 175, .02);--ngt-spacing-xs: 4px;--ngt-spacing-sm: 6px;--ngt-spacing-md: 12px;--ngt-spacing-lg: 16px;--ngt-window-padding: 16px;--ngt-font-size-xxs: .65rem;--ngt-font-size-xs: .75rem;--ngt-font-size-sm: .875rem;--ngt-font-size-md: 1rem;--ngt-font-size-lg: 1.25rem;--ngt-font-size-xl: 2rem;--ngt-background-secondary: var(--ngt-bg-primary);--ngt-background-hover: var(--ngt-hover-bg);--ngt-primary: #df30d4;--ngt-primary-rgb: 223, 48, 212;--ngt-text-on-primary: rgb(255, 255, 255);--ngt-border-color: var(--ngt-border-primary);--ngt-note-background: rgb(219, 234, 254);--ngt-note-border: rgba(37, 99, 235, .2);--ngt-warning-background: rgb(254, 249, 195);--ngt-warning-border: rgba(202, 138, 4, .2);--ngt-error-background: rgb(254, 226, 226);--ngt-error-border: rgba(220, 38, 38, .2);position:fixed;bottom:0;left:50%;z-index:999999;transform:translate(-50%);display:flex;pointer-events:auto;background:var(--ngt-bg-primary);border:1px solid var(--ngt-border-primary);border-radius:9999px;box-shadow:var(--ngt-shadow-toolbar);height:40px;overflow:visible}.ngt-toolbar--active{opacity:1}h1,h2,h3,h4,h5{font-weight:600;color:var(--ngt-text-primary);margin:0}h1{font-size:var(--ngt-font-size-xl)}h2{font-size:var(--ngt-font-size-lg)}h3{font-size:var(--ngt-font-size-md)}h4{font-size:var(--ngt-font-size-sm)}h5{font-size:var(--ngt-font-size-xs)}hr{border:1px solid var(--ngt-border-subtle);margin:1em 0}p{line-height:1.5em;margin:0}\n"] }]
6778
- }], ctorParameters: () => [] });
6316
+ `, animations: [
6317
+ trigger('toolbarState', [
6318
+ state('hidden', style({
6319
+ transform: 'translate(-50%, calc(100% + -1.2rem))',
6320
+ })),
6321
+ state('visible', style({
6322
+ transform: 'translate(-50%, -1rem)',
6323
+ })),
6324
+ transition('hidden <=> visible', [
6325
+ animate('300ms cubic-bezier(0.4, 0, 0.2, 1)'),
6326
+ ]),
6327
+ ]),
6328
+ ], styles: [":host{display:contents;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",Segoe UI Symbol,\"Noto Color Emoji\"}.ngt-toolbar{--ngt-border-radius-small: 4px;--ngt-border-radius-medium: 8px;--ngt-border-radius-large: 12px;--ngt-transition-default: all .2s ease-out;--ngt-transition-smooth: all .2s ease-in-out;--ngt-bg-primary: rgb(255, 255, 255);--ngt-bg-gradient: linear-gradient(180deg, rgb(243, 244, 246) 0%, rgba(243, 244, 246, .88) 100%);--ngt-text-primary: rgb(17, 24, 39);--ngt-text-secondary: rgb(55, 65, 81);--ngt-text-muted: rgb(107, 114, 128);--ngt-border-primary: #e5e7eb;--ngt-border-subtle: rgba(17, 24, 39, .1);--ngt-hover-bg: rgba(17, 24, 39, .05);--ngt-hover-danger: rgb(239, 68, 68);--ngt-shadow-toolbar: 0 2px 8px rgba(156, 163, 175, .2);--ngt-shadow-tooltip: 0 0 0 1px rgba(17, 24, 39, .05), 0 4px 8px rgba(107, 114, 128, .15), 0 2px 4px rgba(107, 114, 128, .1);--ngt-shadow-window: 0px 0px 0px 0px rgba(156, 163, 175, .1), 0px 1px 2px 0px rgba(156, 163, 175, .12), 0px 4px 4px 0px rgba(156, 163, 175, .1), 0px 10px 6px 0px rgba(156, 163, 175, .08), 0px 17px 7px 0px rgba(156, 163, 175, .05), 0px 26px 7px 0px rgba(156, 163, 175, .02);--ngt-spacing-xs: 4px;--ngt-spacing-sm: 6px;--ngt-spacing-md: 12px;--ngt-spacing-lg: 16px;--ngt-window-padding: 16px;--ngt-font-size-xxs: .65rem;--ngt-font-size-xs: .75rem;--ngt-font-size-sm: .875rem;--ngt-font-size-md: 1rem;--ngt-font-size-lg: 1.25rem;--ngt-font-size-xl: 2rem;--ngt-background-secondary: var(--ngt-bg-primary);--ngt-background-hover: var(--ngt-hover-bg);--ngt-primary: #df30d4;--ngt-primary-rgb: 223, 48, 212;--ngt-text-on-primary: rgb(255, 255, 255);--ngt-border-color: var(--ngt-border-primary);--ngt-note-background: rgb(219, 234, 254);--ngt-note-border: rgba(37, 99, 235, .2);--ngt-warning-background: rgb(254, 249, 195);--ngt-warning-border: rgba(202, 138, 4, .2);--ngt-error-background: rgb(254, 226, 226);--ngt-error-border: rgba(220, 38, 38, .2);position:fixed;bottom:0;left:50%;z-index:999999;transform:translate(-50%);display:flex;pointer-events:auto;background:var(--ngt-bg-primary);border:1px solid var(--ngt-border-primary);border-radius:9999px;box-shadow:var(--ngt-shadow-toolbar);height:40px;overflow:visible}.ngt-toolbar--active{opacity:1}h1,h2,h3,h4,h5{font-weight:600;color:var(--ngt-text-primary);margin:0}h1{font-size:var(--ngt-font-size-xl)}h2{font-size:var(--ngt-font-size-lg)}h3{font-size:var(--ngt-font-size-md)}h4{font-size:var(--ngt-font-size-sm)}h5{font-size:var(--ngt-font-size-xs)}hr{border:1px solid var(--ngt-border-subtle);margin:1em 0}p{line-height:1.5em;margin:0}\n"] }]
6329
+ }], ctorParameters: () => [] });
6330
+
6331
+ class ToolbarFeatureFlagService {
6332
+ constructor() {
6333
+ this.internalService = inject(ToolbarInternalFeatureFlagService);
6334
+ }
6335
+ /**
6336
+ * Sets the available flags that will be displayed in the tool on the dev toolbar
6337
+ * @param flags The flags to be displayed
6338
+ */
6339
+ setAvailableOptions(flags) {
6340
+ this.internalService.setAppFlags(flags);
6341
+ }
6342
+ /**
6343
+ * Gets the flags that were forced/modified through the tool on the dev toolbar
6344
+ * @returns Observable of forced flags array
6345
+ */
6346
+ getForcedValues() {
6347
+ return this.internalService.getForcedFlags();
6348
+ }
6349
+ /**
6350
+ * Gets ALL flag values with overrides already applied.
6351
+ * Returns the complete set of flags where overridden values replace base values.
6352
+ * Each flag includes an `isForced` property indicating if it was overridden.
6353
+ *
6354
+ * This method simplifies integration by eliminating the need to manually merge
6355
+ * base flags with overrides using combineLatest.
6356
+ *
6357
+ * @returns Observable of all flags with overrides applied
6358
+ *
6359
+ * @example
6360
+ * ```typescript
6361
+ * // Get a specific flag value with overrides applied
6362
+ * this.featureFlagsService.getValues().pipe(
6363
+ * map(flags => flags.find(f => f.id === 'newFeature')),
6364
+ * map(flag => flag?.isEnabled ?? false)
6365
+ * ).subscribe(isEnabled => {
6366
+ * if (isEnabled) {
6367
+ * // Enable feature
6368
+ * }
6369
+ * });
6370
+ * ```
6371
+ */
6372
+ getValues() {
6373
+ return this.internalService.flags$;
6374
+ }
6375
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarFeatureFlagService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
6376
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarFeatureFlagService, providedIn: 'root' }); }
6377
+ }
6378
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarFeatureFlagService, decorators: [{
6379
+ type: Injectable,
6380
+ args: [{ providedIn: 'root' }]
6381
+ }] });
6382
+
6383
+ /**
6384
+ * Public service for integrating the Permissions Tool with your application.
6385
+ * Use this service to:
6386
+ * 1. Register your application's permissions with setAvailableOptions()
6387
+ * 2. Listen for toolbar permission overrides with getForcedValues()
6388
+ * 3. Apply preset permission states for testing with applyPreset()
6389
+ *
6390
+ * @example
6391
+ * ```typescript
6392
+ * constructor(private permissionsService: ToolbarPermissionsService) {
6393
+ * // Register permissions
6394
+ * this.permissionsService.setAvailableOptions([
6395
+ * { id: 'can-edit', name: 'Can Edit', isGranted: false, isForced: false }
6396
+ * ]);
6397
+ *
6398
+ * // Listen for overrides
6399
+ * this.permissionsService.getForcedValues().subscribe(forcedPermissions => {
6400
+ * // Update your app's permission state
6401
+ * });
6402
+ * }
6403
+ * ```
6404
+ */
6405
+ class ToolbarPermissionsService {
6406
+ constructor() {
6407
+ this.internalService = inject(ToolbarInternalPermissionsService);
6408
+ }
6409
+ /**
6410
+ * Sets the available permissions that will be displayed in the toolbar tool.
6411
+ * Call this in your app initialization to register permissions.
6412
+ *
6413
+ * @param permissions Array of permissions to display in the toolbar
6414
+ *
6415
+ * @example
6416
+ * ```typescript
6417
+ * this.permissionsService.setAvailableOptions([
6418
+ * {
6419
+ * id: 'can-edit-posts',
6420
+ * name: 'Edit Posts',
6421
+ * description: 'Can edit blog posts',
6422
+ * isGranted: false,
6423
+ * isForced: false
6424
+ * }
6425
+ * ]);
6426
+ * ```
6427
+ */
6428
+ setAvailableOptions(permissions) {
6429
+ this.internalService.setAppPermissions(permissions);
6430
+ }
6431
+ /**
6432
+ * Gets an observable of permissions that were forced/overridden through the toolbar.
6433
+ * Subscribe to this to update your application's permission state.
6434
+ *
6435
+ * @returns Observable emitting array of forced permissions whenever changes occur
6436
+ *
6437
+ * @example
6438
+ * ```typescript
6439
+ * this.permissionsService.getForcedValues().subscribe(forcedPermissions => {
6440
+ * forcedPermissions.forEach(permission => {
6441
+ * this.updatePermission(permission.id, permission.isGranted);
6442
+ * });
6443
+ * });
6444
+ * ```
6445
+ */
6446
+ getForcedValues() {
6447
+ return this.internalService.getForcedPermissions();
6448
+ }
6449
+ /**
6450
+ * Gets ALL permission values with overrides already applied.
6451
+ * Returns the complete set of permissions where overridden values replace base values.
6452
+ * Each permission includes an `isForced` property indicating if it was overridden.
6453
+ *
6454
+ * This method simplifies integration by eliminating the need to manually merge
6455
+ * base permissions with overrides using combineLatest.
6456
+ *
6457
+ * @returns Observable of all permissions with overrides applied
6458
+ *
6459
+ * @example
6460
+ * ```typescript
6461
+ * // Simple permission check with overrides applied
6462
+ * this.permissionsService.getValues().pipe(
6463
+ * map(permissions => permissions.find(p => p.id === 'can-edit')),
6464
+ * map(permission => permission?.isGranted ?? false)
6465
+ * ).subscribe(canEdit => {
6466
+ * if (canEdit) {
6467
+ * // Enable edit functionality
6468
+ * }
6469
+ * });
6470
+ * ```
6471
+ */
6472
+ getValues() {
6473
+ return this.internalService.permissions$;
6474
+ }
6475
+ /**
6476
+ * Apply a preset permission state. Useful for automated testing scenarios.
6477
+ *
6478
+ * @param state The forced permissions state to apply
6479
+ *
6480
+ * @example
6481
+ * ```typescript
6482
+ * this.permissionsService.applyPreset({
6483
+ * granted: ['can-edit-posts'],
6484
+ * denied: ['can-delete-posts']
6485
+ * });
6486
+ * ```
6487
+ */
6488
+ applyPreset(state) {
6489
+ this.internalService.applyPresetPermissions(state);
6490
+ }
6491
+ /**
6492
+ * Get the current forced permission state.
6493
+ *
6494
+ * @returns Current forced permissions state
6495
+ */
6496
+ getCurrentState() {
6497
+ return this.internalService.getCurrentForcedState();
6498
+ }
6499
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarPermissionsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
6500
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarPermissionsService, providedIn: 'root' }); }
6501
+ }
6502
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarPermissionsService, decorators: [{
6503
+ type: Injectable,
6504
+ args: [{ providedIn: 'root' }]
6505
+ }] });
6506
+
6507
+ class ToolbarLanguageService {
6508
+ constructor() {
6509
+ this.internalService = inject(ToolbarInternalLanguageService);
6510
+ }
6511
+ /**
6512
+ * Sets the available languages that will be displayed in the tool on the dev toolbar
6513
+ * @param languages The languages to be displayed
6514
+ */
6515
+ setAvailableOptions(languages) {
6516
+ this.internalService.setAppLanguages(languages);
6517
+ }
6518
+ /**
6519
+ * Gets the languages that were forced/modified through the tool on the dev toolbar
6520
+ * @returns Observable of forced languages array
6521
+ */
6522
+ getForcedValues() {
6523
+ return this.internalService.getForcedLanguage();
6524
+ }
6525
+ /**
6526
+ * Gets the forced language value.
6527
+ * For the language tool, this returns the same as getForcedValues() since
6528
+ * only one language can be selected at a time.
6529
+ * @returns Observable of forced language array (single item or empty)
6530
+ */
6531
+ getValues() {
6532
+ return this.internalService.getForcedLanguage();
6533
+ }
6534
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarLanguageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
6535
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarLanguageService, providedIn: 'root' }); }
6536
+ }
6537
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarLanguageService, decorators: [{
6538
+ type: Injectable,
6539
+ args: [{ providedIn: 'root' }]
6540
+ }] });
6779
6541
 
6780
6542
  /**
6781
- * Dynamically attaches the Angular Toolbar to the DOM without template changes.
6782
- *
6783
- * This function creates and attaches the toolbar component directly to the document body,
6784
- * eliminating the need to add `<ngt-toolbar>` to your component templates. Combined with
6785
- * dynamic imports, this provides true zero production bundle impact.
6543
+ * Public service for managing app features in the dev toolbar.
6786
6544
  *
6787
- * ## Basic Usage
6545
+ * This service implements the ToolbarService interface and provides methods for:
6546
+ * - Configuring available product features
6547
+ * - Retrieving forced feature overrides
6548
+ * - Applying preset feature configurations
6549
+ * - Exporting current forced state for presets
6788
6550
  *
6551
+ * @example
6789
6552
  * ```typescript
6790
- * // main.ts
6791
- * import { bootstrapApplication } from '@angular/platform-browser';
6792
- * import { AppComponent } from './app/app.component';
6793
- * import { environment } from './environments/environment';
6553
+ * import { ToolbarAppFeaturesService } from 'ngx-dev-toolbar';
6554
+ *
6555
+ * @Component({...})
6556
+ * export class AppComponent implements OnInit {
6557
+ * private appFeaturesService = inject(ToolbarAppFeaturesService);
6794
6558
  *
6795
- * async function bootstrap() {
6796
- * const appRef = await bootstrapApplication(AppComponent);
6559
+ * ngOnInit() {
6560
+ * // Configure available features based on current tier
6561
+ * const features: ToolbarAppFeature[] = [
6562
+ * { id: 'analytics', name: 'Analytics Dashboard', isEnabled: true, isForced: false },
6563
+ * { id: 'multi-user', name: 'Multi-User Support', isEnabled: false, isForced: false }
6564
+ * ];
6565
+ * this.appFeaturesService.setAvailableOptions(features);
6797
6566
  *
6798
- * // Only import and initialize toolbar in development
6799
- * if (!environment.production) {
6800
- * const { initToolbar } = await import('ngx-dev-toolbar');
6801
- * initToolbar(appRef);
6567
+ * // Subscribe to forced overrides
6568
+ * this.appFeaturesService.getForcedValues().subscribe(forcedFeatures => {
6569
+ * forcedFeatures.forEach(feature => {
6570
+ * // Apply forced feature state to application logic
6571
+ * this.applyFeatureState(feature.id, feature.isEnabled);
6572
+ * });
6573
+ * });
6802
6574
  * }
6803
6575
  * }
6804
- *
6805
- * bootstrap();
6806
6576
  * ```
6577
+ */
6578
+ class ToolbarAppFeaturesService {
6579
+ constructor() {
6580
+ this.internalService = inject(ToolbarInternalAppFeaturesService);
6581
+ }
6582
+ /**
6583
+ * Set available app features for the application.
6584
+ *
6585
+ * Configures the list of product features that can be toggled in the dev toolbar.
6586
+ * Features should represent product-level capabilities like license tiers,
6587
+ * deployment configurations, or environment flags.
6588
+ *
6589
+ * @param features - Array of app features to display in the toolbar
6590
+ * @throws Error if duplicate feature IDs or empty IDs are detected
6591
+ *
6592
+ * @example
6593
+ * ```typescript
6594
+ * const features: ToolbarAppFeature[] = [
6595
+ * {
6596
+ * id: 'advanced-analytics',
6597
+ * name: 'Advanced Analytics Dashboard',
6598
+ * description: 'Access premium reporting and data visualization',
6599
+ * isEnabled: false, // Not available in current tier
6600
+ * isForced: false
6601
+ * },
6602
+ * {
6603
+ * id: 'multi-user-support',
6604
+ * name: 'Multi-User Collaboration',
6605
+ * description: 'Enable team features and user management',
6606
+ * isEnabled: true, // Available in current tier
6607
+ * isForced: false
6608
+ * }
6609
+ * ];
6610
+ * this.appFeaturesService.setAvailableOptions(features);
6611
+ * ```
6612
+ */
6613
+ setAvailableOptions(features) {
6614
+ this.internalService.setAppFeatures(features);
6615
+ }
6616
+ /**
6617
+ * Get observable stream of features that have forced overrides.
6618
+ *
6619
+ * Emits an array of features that have been forced via the dev toolbar.
6620
+ * Only features with `isForced = true` are included in the emissions.
6621
+ * Use this to react to feature override changes and update application behavior.
6622
+ *
6623
+ * @returns Observable that emits array of forced features whenever state changes
6624
+ *
6625
+ * @example
6626
+ * ```typescript
6627
+ * this.appFeaturesService.getForcedValues()
6628
+ * .pipe(takeUntilDestroyed())
6629
+ * .subscribe(forcedFeatures => {
6630
+ * forcedFeatures.forEach(feature => {
6631
+ * if (feature.isEnabled) {
6632
+ * this.enableFeature(feature.id);
6633
+ * } else {
6634
+ * this.disableFeature(feature.id);
6635
+ * }
6636
+ * });
6637
+ * });
6638
+ * ```
6639
+ */
6640
+ getForcedValues() {
6641
+ return this.internalService.getForcedFeatures();
6642
+ }
6643
+ /**
6644
+ * Gets ALL app feature values with overrides already applied.
6645
+ * Returns the complete set of features where overridden values replace base values.
6646
+ * Each feature includes an `isForced` property indicating if it was overridden.
6647
+ *
6648
+ * This method simplifies integration by eliminating the need to manually merge
6649
+ * base features with overrides using combineLatest.
6650
+ *
6651
+ * @returns Observable of all features with overrides applied
6652
+ *
6653
+ * @example
6654
+ * ```typescript
6655
+ * // Check if a feature is enabled with overrides applied
6656
+ * this.appFeaturesService.getValues().pipe(
6657
+ * map(features => features.find(f => f.id === 'premium-analytics')),
6658
+ * map(feature => feature?.isEnabled ?? false)
6659
+ * ).subscribe(isEnabled => {
6660
+ * if (isEnabled) {
6661
+ * // Enable premium analytics
6662
+ * }
6663
+ * });
6664
+ * ```
6665
+ */
6666
+ getValues() {
6667
+ return this.internalService.features$;
6668
+ }
6669
+ /**
6670
+ * Apply a preset feature configuration (for preset tool integration).
6671
+ *
6672
+ * Accepts a forced features state object and applies it to the current configuration.
6673
+ * Invalid feature IDs (not in configured features) are filtered out with a warning.
6674
+ *
6675
+ * @param state - Forced features state containing enabled/disabled arrays
6676
+ *
6677
+ * @example
6678
+ * ```typescript
6679
+ * // Apply "Enterprise Tier" preset
6680
+ * const enterprisePreset: ForcedAppFeaturesState = {
6681
+ * enabled: ['analytics', 'multi-user', 'white-label', 'sso'],
6682
+ * disabled: []
6683
+ * };
6684
+ * this.appFeaturesService.applyPresetFeatures(enterprisePreset);
6685
+ *
6686
+ * // Apply "Basic Tier" preset
6687
+ * const basicPreset: ForcedAppFeaturesState = {
6688
+ * enabled: [],
6689
+ * disabled: ['analytics', 'multi-user', 'white-label', 'sso']
6690
+ * };
6691
+ * this.appFeaturesService.applyPresetFeatures(basicPreset);
6692
+ * ```
6693
+ */
6694
+ applyPresetFeatures(state) {
6695
+ this.internalService.applyForcedState(state);
6696
+ }
6697
+ /**
6698
+ * Get current forced feature state as a snapshot (for preset export).
6699
+ *
6700
+ * Returns the current forced features state with enabled/disabled arrays.
6701
+ * Useful for exporting toolbar state to save as a preset or for debugging.
6702
+ * Returns a defensive copy - mutations will not affect internal state.
6703
+ *
6704
+ * @returns Current forced features state
6705
+ *
6706
+ * @example
6707
+ * ```typescript
6708
+ * // Export current toolbar state to save as preset
6709
+ * const currentState = this.appFeaturesService.getCurrentForcedState();
6710
+ * this.presetsService.savePreset('my-config', {
6711
+ * appFeatures: currentState,
6712
+ * // ... other tool states
6713
+ * });
6714
+ *
6715
+ * console.log(currentState);
6716
+ * // { enabled: ['analytics'], disabled: ['white-label'] }
6717
+ * ```
6718
+ */
6719
+ getCurrentForcedState() {
6720
+ return this.internalService.getCurrentForcedState();
6721
+ }
6722
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarAppFeaturesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
6723
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarAppFeaturesService, providedIn: 'root' }); }
6724
+ }
6725
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: ToolbarAppFeaturesService, decorators: [{
6726
+ type: Injectable,
6727
+ args: [{ providedIn: 'root' }]
6728
+ }] });
6729
+
6730
+ /**
6731
+ * Provides all ngx-dev-toolbar services and automatically attaches the toolbar to the DOM.
6807
6732
  *
6808
- * ## With Configuration
6733
+ * This is the recommended way to set up the toolbar. Add it to your `app.config.ts`
6734
+ * alongside other providers like `provideRouter()` and `provideHttpClient()`.
6735
+ *
6736
+ * ## Basic Usage
6809
6737
  *
6810
6738
  * ```typescript
6811
- * if (!environment.production) {
6812
- * const { initToolbar } = await import('ngx-dev-toolbar');
6813
- * initToolbar(appRef, {
6814
- * config: {
6815
- * enabled: true,
6739
+ * // app.config.ts
6740
+ * import { ApplicationConfig, isDevMode } from '@angular/core';
6741
+ * import { provideToolbar } from 'ngx-dev-toolbar';
6742
+ *
6743
+ * export const appConfig: ApplicationConfig = {
6744
+ * providers: [
6745
+ * provideRouter(appRoutes),
6746
+ * provideToolbar({
6747
+ * enabled: isDevMode(),
6816
6748
  * showFeatureFlagsTool: true,
6817
6749
  * showPermissionsTool: true,
6818
- * showLanguageTool: false,
6819
- * }
6820
- * });
6821
- * }
6750
+ * }),
6751
+ * ],
6752
+ * };
6822
6753
  * ```
6823
6754
  *
6824
- * ## Cleanup
6825
- *
6826
6755
  * ```typescript
6827
- * const { destroy } = initToolbar(appRef);
6828
- *
6829
- * // Later, to remove the toolbar
6830
- * destroy();
6756
+ * // main.ts
6757
+ * bootstrapApplication(AppComponent, appConfig);
6831
6758
  * ```
6832
6759
  *
6833
- * ## Benefits
6760
+ * ## Using Services with Tokens
6761
+ *
6762
+ * ```typescript
6763
+ * import { inject } from '@angular/core';
6764
+ * import { TOOLBAR_FEATURE_FLAGS } from 'ngx-dev-toolbar';
6765
+ * import type { ToolbarService, ToolbarFlag } from 'ngx-dev-toolbar';
6834
6766
  *
6835
- * 1. **Zero template changes** - No `<ngt-toolbar>` needed in any component
6836
- * 2. **Complete isolation** - Toolbar exists outside the app's component tree
6837
- * 3. **Easy to add/remove** - Single line in main.ts
6838
- * 4. **True tree-shaking** - Dynamic import means it's completely excluded from prod
6767
+ * @Injectable({ providedIn: 'root' })
6768
+ * export class FeatureFlagsService {
6769
+ * // Optional injection - null in production
6770
+ * private devToolbar = inject<ToolbarService<ToolbarFlag>>(
6771
+ * TOOLBAR_FEATURE_FLAGS,
6772
+ * { optional: true }
6773
+ * );
6839
6774
  *
6840
- * ## Production Bundle Impact
6775
+ * constructor() {
6776
+ * // Safe to call - no-op if devToolbar is null
6777
+ * this.devToolbar?.setAvailableOptions(this.flags);
6841
6778
  *
6842
- * When using dynamic imports with `initToolbar()`:
6843
- * - Production bundle: 0 bytes from toolbar
6844
- * - Development only: ~45KB (services + UI components)
6779
+ * // Subscribe to forced values only if toolbar exists
6780
+ * this.devToolbar?.getForcedValues().subscribe(forced => {
6781
+ * // Merge forced values
6782
+ * });
6783
+ * }
6784
+ * }
6785
+ * ```
6845
6786
  *
6846
- * @param appRef - The ApplicationRef from bootstrapApplication()
6847
- * @param options - Optional configuration for the toolbar
6848
- * @returns Object with destroy() method for cleanup
6787
+ * @param config - Optional configuration for toolbar behavior and tool visibility
6788
+ * @returns Angular EnvironmentProviders for all toolbar services
6849
6789
  */
6850
- function initToolbar(appRef, options) {
6851
- const injector = appRef.injector.get(EnvironmentInjector);
6852
- // Create the toolbar component dynamically
6853
- const componentRef = createComponent(ToolbarComponent, {
6854
- environmentInjector: injector,
6855
- });
6856
- // Apply config if provided
6857
- if (options?.config) {
6858
- componentRef.setInput('config', options.config);
6859
- }
6860
- // Append to document body
6861
- document.body.appendChild(componentRef.location.nativeElement);
6862
- // Register for change detection
6863
- appRef.attachView(componentRef.hostView);
6864
- return {
6865
- destroy: () => {
6866
- appRef.detachView(componentRef.hostView);
6867
- componentRef.destroy();
6868
- componentRef.location.nativeElement.remove();
6790
+ function provideToolbar(config) {
6791
+ return makeEnvironmentProviders([
6792
+ { provide: TOOLBAR_FEATURE_FLAGS, useClass: ToolbarFeatureFlagService },
6793
+ { provide: TOOLBAR_PERMISSIONS, useClass: ToolbarPermissionsService },
6794
+ { provide: TOOLBAR_LANGUAGE, useClass: ToolbarLanguageService },
6795
+ { provide: TOOLBAR_APP_FEATURES, useClass: ToolbarAppFeaturesService },
6796
+ { provide: TOOLBAR_CONFIG, useValue: config ?? {} },
6797
+ {
6798
+ provide: ENVIRONMENT_INITIALIZER,
6799
+ multi: true,
6800
+ useValue: () => {
6801
+ const appRef = inject(ApplicationRef);
6802
+ const injector = inject(EnvironmentInjector);
6803
+ const toolbarConfig = inject(TOOLBAR_CONFIG);
6804
+ const componentRef = createComponent(ToolbarComponent, {
6805
+ environmentInjector: injector,
6806
+ });
6807
+ componentRef.setInput('config', toolbarConfig);
6808
+ document.body.appendChild(componentRef.location.nativeElement);
6809
+ appRef.attachView(componentRef.hostView);
6810
+ },
6869
6811
  },
6870
- };
6812
+ ]);
6871
6813
  }
6872
6814
 
6873
6815
  class ToolbarStepDirective {
@@ -7118,5 +7060,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImpor
7118
7060
  * Generated bundle index. Do not edit.
7119
7061
  */
7120
7062
 
7121
- export { TOOLBAR_APP_FEATURES, TOOLBAR_FEATURE_FLAGS, TOOLBAR_LANGUAGE, TOOLBAR_PERMISSIONS, ToolbarAppFeaturesService, ToolbarAppFeaturesToolComponent, ToolbarButtonComponent, ToolbarCardComponent, ToolbarClickableCardComponent, ToolbarComponent, ToolbarFeatureFlagService, ToolbarIconComponent, ToolbarInputComponent, ToolbarLanguageService, ToolbarLinkButtonComponent, ToolbarListComponent, ToolbarListItemComponent, ToolbarPermissionsService, ToolbarPermissionsToolComponent, ToolbarPresetsService, ToolbarPresetsToolComponent, ToolbarSelectComponent, ToolbarStepDirective, ToolbarStepViewComponent, ToolbarToolComponent, initToolbar, provideToolbar };
7063
+ export { TOOLBAR_APP_FEATURES, TOOLBAR_CONFIG, TOOLBAR_FEATURE_FLAGS, TOOLBAR_LANGUAGE, TOOLBAR_PERMISSIONS, ToolbarAppFeaturesService, ToolbarAppFeaturesToolComponent, ToolbarButtonComponent, ToolbarCardComponent, ToolbarClickableCardComponent, ToolbarComponent, ToolbarFeatureFlagService, ToolbarIconComponent, ToolbarInputComponent, ToolbarLanguageService, ToolbarLinkButtonComponent, ToolbarListComponent, ToolbarListItemComponent, ToolbarPermissionsService, ToolbarPermissionsToolComponent, ToolbarPresetsService, ToolbarPresetsToolComponent, ToolbarSelectComponent, ToolbarStepDirective, ToolbarStepViewComponent, ToolbarToolComponent, provideToolbar };
7122
7064
  //# sourceMappingURL=ngx-dev-toolbar.mjs.map