@nucleus-suite-pe/core 0.7.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,47 +1,47 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, makeEnvironmentProviders, inject, Injector, signal, computed, Injectable, effect, model, input, untracked, ChangeDetectionStrategy, Component, CUSTOM_ELEMENTS_SCHEMA, output, TemplateRef, Directive, contentChildren } from '@angular/core';
2
+ import { InjectionToken, makeEnvironmentProviders, inject, Injector, signal, computed, Injectable, effect, DestroyRef, ElementRef, input, Directive, model, untracked, ChangeDetectionStrategy, Component, CUSTOM_ELEMENTS_SCHEMA, viewChild, output, afterNextRender, TemplateRef, contentChildren } from '@angular/core';
3
3
  import { HttpContextToken, HttpClient, HttpContext, HttpErrorResponse } from '@angular/common/http';
4
4
  import { firstValueFrom, catchError, throwError, from, switchMap } from 'rxjs';
5
- import * as i6 from '@angular/forms';
5
+ import { DOCUMENT, NgTemplateOutlet } from '@angular/common';
6
+ import * as i1 from '@angular/forms';
6
7
  import { FormsModule } from '@angular/forms';
7
- import * as i1 from 'primeng/select';
8
+ import * as i2 from 'primeng/select';
8
9
  import { SelectModule } from 'primeng/select';
9
10
  import { definePreset } from '@primeuix/themes';
10
11
  import Lara from '@primeuix/themes/lara';
11
12
  import { providePrimeNG } from 'primeng/config';
13
+ import * as i1$1 from 'primeng/skeleton';
14
+ import { SkeletonModule } from 'primeng/skeleton';
15
+ import { RouterLink, Router } from '@angular/router';
12
16
  import { ConfirmationService, MessageService } from 'primeng/api';
13
- import * as i1$1 from 'primeng/checkbox';
14
- import { CheckboxModule } from 'primeng/checkbox';
15
- import * as i2 from 'primeng/inputtext';
17
+ import * as i2$1 from 'primeng/inputtext';
16
18
  import { InputTextModule } from 'primeng/inputtext';
17
- import * as i4 from 'primeng/textarea';
19
+ import * as i3 from 'primeng/popover';
20
+ import { PopoverModule } from 'primeng/popover';
21
+ import * as i2$2 from 'primeng/checkbox';
22
+ import { CheckboxModule } from 'primeng/checkbox';
23
+ import * as i5 from 'primeng/textarea';
18
24
  import { TextareaModule } from 'primeng/textarea';
19
- import * as i5 from 'primeng/toggleswitch';
25
+ import * as i6 from 'primeng/toggleswitch';
20
26
  import { ToggleSwitchModule } from 'primeng/toggleswitch';
21
- import { NgTemplateOutlet } from '@angular/common';
22
27
  import * as i1$2 from 'primeng/button';
23
28
  import { ButtonModule } from 'primeng/button';
24
- import * as i2$1 from 'primeng/table';
29
+ import * as i3$1 from 'primeng/table';
25
30
  import { TableModule } from 'primeng/table';
26
- import * as i3 from 'primeng/tag';
31
+ import * as i4 from 'primeng/tag';
27
32
  import { TagModule } from 'primeng/tag';
28
- import * as i4$1 from 'primeng/tooltip';
33
+ import * as i5$1 from 'primeng/tooltip';
29
34
  import { TooltipModule } from 'primeng/tooltip';
30
- import { Router } from '@angular/router';
31
35
  import * as i1$3 from 'primeng/avatar';
32
36
  import { AvatarModule } from 'primeng/avatar';
33
- import * as i2$2 from 'primeng/breadcrumb';
37
+ import * as i2$3 from 'primeng/breadcrumb';
34
38
  import { BreadcrumbModule } from 'primeng/breadcrumb';
35
- import * as i4$2 from 'primeng/confirmdialog';
39
+ import * as i4$1 from 'primeng/confirmdialog';
36
40
  import { ConfirmDialogModule } from 'primeng/confirmdialog';
37
- import * as i5$1 from 'primeng/toast';
41
+ import * as i5$2 from 'primeng/toast';
38
42
  import { ToastModule } from 'primeng/toast';
39
43
  import * as i6$1 from 'primeng/drawer';
40
44
  import { DrawerModule } from 'primeng/drawer';
41
- import * as i7 from 'primeng/listbox';
42
- import { ListboxModule } from 'primeng/listbox';
43
- import * as i8 from 'primeng/popover';
44
- import { PopoverModule } from 'primeng/popover';
45
45
 
46
46
  const NUCLEUS_CONFIG = new InjectionToken('NUCLEUS_CONFIG');
47
47
  const CONFIG_URL = 'config.json';
@@ -225,6 +225,191 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
225
225
  args: [{ providedIn: 'root' }]
226
226
  }] });
227
227
 
228
+ const COMMON_COPY = {
229
+ es: {
230
+ search: 'Buscar',
231
+ choose: 'Selecciona',
232
+ noIcon: 'Sin icono',
233
+ noColor: 'Sin color',
234
+ searchIcon: 'Busca un icono',
235
+ noMatches: 'Ningún icono coincide',
236
+ sessionExpired: 'Tu sesión expiró. Vuelve a iniciar sesión.',
237
+ errorNetwork: 'No se pudo conectar con el servidor. Revisa tu conexión o inténtalo en unos minutos.',
238
+ errorTimeout: 'El servidor tardó demasiado en responder. Inténtalo de nuevo.',
239
+ errorServer: 'Algo falló en el servidor y no pudimos completar la operación. Inténtalo de nuevo en unos ' +
240
+ 'minutos; si sigue pasando, avísale al equipo.',
241
+ errorInvalid: 'Revisa los datos: hay algo que no es válido.',
242
+ errorForbidden: 'No tienes permiso para hacer esto.',
243
+ errorNotFound: 'No encontramos lo que buscabas. Puede que ya no exista.',
244
+ errorConflict: 'Ese cambio choca con algo que ya existe.',
245
+ errorUnknown: 'No se pudo completar la operación. Inténtalo de nuevo.',
246
+ },
247
+ en: {
248
+ search: 'Search',
249
+ choose: 'Select',
250
+ noIcon: 'No icon',
251
+ noColor: 'No colour',
252
+ searchIcon: 'Search an icon',
253
+ noMatches: 'No icon matches',
254
+ sessionExpired: 'Your session expired. Sign in again.',
255
+ errorNetwork: 'Could not reach the server. Check your connection or try again in a moment.',
256
+ errorTimeout: 'The server took too long to answer. Try again.',
257
+ errorServer: 'Something broke on the server and we could not finish. Try again in a few minutes; if it ' +
258
+ 'keeps happening, tell the team.',
259
+ errorInvalid: 'Check the data: something is not valid.',
260
+ errorForbidden: 'You do not have permission to do this.',
261
+ errorNotFound: 'We could not find that. It may no longer exist.',
262
+ errorConflict: 'That change clashes with something that already exists.',
263
+ errorUnknown: 'We could not finish the operation. Try again.',
264
+ },
265
+ };
266
+ function commonCopyOf(lang) {
267
+ return lang === 'en' ? COMMON_COPY.en : COMMON_COPY.es;
268
+ }
269
+
270
+ const NUCLEUS_LANGS = ['es', 'en'];
271
+ const STORAGE_KEY$3 = 'nucleus_lang';
272
+ function isLang(value) {
273
+ return value === 'es' || value === 'en';
274
+ }
275
+ function readStored$2() {
276
+ try {
277
+ const stored = localStorage.getItem(STORAGE_KEY$3);
278
+ if (isLang(stored))
279
+ return stored;
280
+ }
281
+ catch { }
282
+ return 'es';
283
+ }
284
+ let current = readStored$2();
285
+ function nucleusLang() {
286
+ return current;
287
+ }
288
+ class NucleusLanguage {
289
+ _lang = signal(readStored$2(), ...(ngDevMode ? [{ debugName: "_lang" }] : /* istanbul ignore next */ []));
290
+ lang = this._lang.asReadonly();
291
+ constructor() {
292
+ effect(() => {
293
+ const lang = this._lang();
294
+ current = lang;
295
+ document.documentElement.lang = lang;
296
+ try {
297
+ localStorage.setItem(STORAGE_KEY$3, lang);
298
+ }
299
+ catch { }
300
+ });
301
+ }
302
+ setLang(lang) {
303
+ this._lang.set(lang);
304
+ }
305
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusLanguage, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
306
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusLanguage, providedIn: 'root' });
307
+ }
308
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusLanguage, decorators: [{
309
+ type: Injectable,
310
+ args: [{ providedIn: 'root' }]
311
+ }], ctorParameters: () => [] });
312
+
313
+ const SPOKEN = [400, 402, 403, 404, 409, 422];
314
+ function problemOf(error) {
315
+ const body = error.error;
316
+ return body !== null && typeof body === 'object' ? body : null;
317
+ }
318
+ function fieldErrorsOf(problem) {
319
+ const groups = problem?.errors;
320
+ if (!groups)
321
+ return '';
322
+ return Object.values(groups)
323
+ .flat()
324
+ .map((message) => message.trim())
325
+ .filter(Boolean)
326
+ .join(' ');
327
+ }
328
+ function nucleusErrorMessage(error, lang) {
329
+ const copy = commonCopyOf(lang ?? nucleusLang());
330
+ if (!(error instanceof HttpErrorResponse))
331
+ return copy.errorUnknown;
332
+ const status = error.status;
333
+ if (status === 0)
334
+ return copy.errorNetwork;
335
+ if (status === 401)
336
+ return copy.sessionExpired;
337
+ if (status === 408 || status === 504)
338
+ return copy.errorTimeout;
339
+ if (SPOKEN.includes(status)) {
340
+ const problem = problemOf(error);
341
+ const fields = fieldErrorsOf(problem);
342
+ if (fields)
343
+ return fields;
344
+ const detail = problem?.detail?.trim() || problem?.title?.trim();
345
+ if (detail)
346
+ return detail;
347
+ }
348
+ if (status === 400 || status === 422)
349
+ return copy.errorInvalid;
350
+ if (status === 403)
351
+ return copy.errorForbidden;
352
+ if (status === 404)
353
+ return copy.errorNotFound;
354
+ if (status === 409)
355
+ return copy.errorConflict;
356
+ if (status >= 500)
357
+ return copy.errorServer;
358
+ return copy.errorUnknown;
359
+ }
360
+
361
+ const DEFAULT_TTL_MS = 30_000;
362
+ class NucleusCache {
363
+ entries = new Map();
364
+ inFlight = new Map();
365
+ read(key, loader, ttlMs = DEFAULT_TTL_MS) {
366
+ const entry = this.entries.get(key);
367
+ if (entry && Date.now() - entry.at < ttlMs) {
368
+ return Promise.resolve(entry.value);
369
+ }
370
+ return this.load(key, loader);
371
+ }
372
+ prefetch(key, loader, ttlMs = DEFAULT_TTL_MS) {
373
+ const entry = this.entries.get(key);
374
+ if (entry && Date.now() - entry.at < ttlMs)
375
+ return;
376
+ if (this.inFlight.has(key))
377
+ return;
378
+ void this.load(key, loader).catch(() => undefined);
379
+ }
380
+ invalidate(...prefixes) {
381
+ if (prefixes.length === 0) {
382
+ this.entries.clear();
383
+ return;
384
+ }
385
+ for (const key of [...this.entries.keys()]) {
386
+ if (prefixes.some((prefix) => key.startsWith(prefix)))
387
+ this.entries.delete(key);
388
+ }
389
+ }
390
+ load(key, loader) {
391
+ const running = this.inFlight.get(key);
392
+ if (running)
393
+ return running;
394
+ const promise = loader()
395
+ .then((value) => {
396
+ this.entries.set(key, { at: Date.now(), value });
397
+ return value;
398
+ })
399
+ .finally(() => {
400
+ this.inFlight.delete(key);
401
+ });
402
+ this.inFlight.set(key, promise);
403
+ return promise;
404
+ }
405
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusCache, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
406
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusCache, providedIn: 'root' });
407
+ }
408
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusCache, decorators: [{
409
+ type: Injectable,
410
+ args: [{ providedIn: 'root' }]
411
+ }] });
412
+
228
413
  const NO_PERMISSIONS = {
229
414
  canView: false,
230
415
  canInsert: false,
@@ -263,8 +448,10 @@ class SessionService {
263
448
  _me = signal(null, ...(ngDevMode ? [{ debugName: "_me" }] : /* istanbul ignore next */ []));
264
449
  _menu = signal([], ...(ngDevMode ? [{ debugName: "_menu" }] : /* istanbul ignore next */ []));
265
450
  _access = signal(null, ...(ngDevMode ? [{ debugName: "_access" }] : /* istanbul ignore next */ []));
451
+ _globalRole = signal(null, ...(ngDevMode ? [{ debugName: "_globalRole" }] : /* istanbul ignore next */ []));
266
452
  _scope = signal(null, ...(ngDevMode ? [{ debugName: "_scope" }] : /* istanbul ignore next */ []));
267
453
  _apps = signal([], ...(ngDevMode ? [{ debugName: "_apps" }] : /* istanbul ignore next */ []));
454
+ _catalog = signal([], ...(ngDevMode ? [{ debugName: "_catalog" }] : /* istanbul ignore next */ []));
268
455
  _siteId = signal(null, ...(ngDevMode ? [{ debugName: "_siteId" }] : /* istanbul ignore next */ []));
269
456
  _loading = signal(false, ...(ngDevMode ? [{ debugName: "_loading" }] : /* istanbul ignore next */ []));
270
457
  _switching = signal(false, ...(ngDevMode ? [{ debugName: "_switching" }] : /* istanbul ignore next */ []));
@@ -277,11 +464,13 @@ class SessionService {
277
464
  access = this._access.asReadonly();
278
465
  scope = this._scope.asReadonly();
279
466
  apps = this._apps.asReadonly();
467
+ catalog = this._catalog.asReadonly();
468
+ currentApp = computed(() => this._catalog().find((app) => app.id.toLowerCase() === this.config.appId) ?? null, ...(ngDevMode ? [{ debugName: "currentApp" }] : /* istanbul ignore next */ []));
280
469
  siteId = this._siteId.asReadonly();
281
470
  loading = this._loading.asReadonly();
282
471
  switching = this._switching.asReadonly();
283
472
  error = this._error.asReadonly();
284
- roleName = computed(() => this._access()?.roleName ?? null, ...(ngDevMode ? [{ debugName: "roleName" }] : /* istanbul ignore next */ []));
473
+ roleName = computed(() => this._access()?.roleName ?? this._globalRole(), ...(ngDevMode ? [{ debugName: "roleName" }] : /* istanbul ignore next */ []));
285
474
  siteName = computed(() => this._access()?.siteName ?? null, ...(ngDevMode ? [{ debugName: "siteName" }] : /* istanbul ignore next */ []));
286
475
  companies = computed(() => {
287
476
  const me = this._me();
@@ -337,12 +526,17 @@ class SessionService {
337
526
  firstValueFrom(this.http.get(`${this.config.securityApiUrl}/apps`)),
338
527
  ]);
339
528
  this._me.set(me);
529
+ this._catalog.set([...catalog].sort((a, b) => a.sortOrder - b.sortOrder));
340
530
  this._apps.set(this.buildLauncher(catalog));
341
531
  await this.resolveMenu(me, language);
342
532
  }
343
533
  catch (error) {
344
534
  this._menu.set([]);
345
- this._error.set(describe(error));
535
+ if (isSessionRejected(error)) {
536
+ await this.signOutToLanding();
537
+ return;
538
+ }
539
+ this._error.set(nucleusErrorMessage(error, this.language));
346
540
  console.error('[session] no se pudo cargar la sesión del backend', error);
347
541
  }
348
542
  finally {
@@ -350,6 +544,22 @@ class SessionService {
350
544
  this._loading.set(false);
351
545
  }
352
546
  }
547
+ async signOutToLanding() {
548
+ const landing = this.config.landingUrl;
549
+ this.loadedKey = null;
550
+ this._me.set(null);
551
+ this._access.set(null);
552
+ this._globalRole.set(null);
553
+ this._apps.set([]);
554
+ this._catalog.set([]);
555
+ if (!landing) {
556
+ this._error.set(commonCopyOf(this.language).sessionExpired);
557
+ return;
558
+ }
559
+ await this.sessionAuth.clear();
560
+ await this.auth?.signOut?.();
561
+ window.location.href = nucleusLogoutUrl(landing);
562
+ }
353
563
  permissionsFor(route) {
354
564
  return permissionsOf(findMenuByRoute(this._menu(), route));
355
565
  }
@@ -386,7 +596,11 @@ class SessionService {
386
596
  }
387
597
  }
388
598
  catch (error) {
389
- this._error.set(describe(error));
599
+ if (isSessionRejected(error)) {
600
+ await this.signOutToLanding();
601
+ return;
602
+ }
603
+ this._error.set(nucleusErrorMessage(error, this.language));
390
604
  console.error('[session] no se pudo cambiar de sede', error);
391
605
  }
392
606
  finally {
@@ -401,12 +615,15 @@ class SessionService {
401
615
  const access = entries.find((entry) => entry.siteId === wanted) ?? entries[0] ?? null;
402
616
  if (access) {
403
617
  this._access.set(access);
618
+ this._globalRole.set(null);
404
619
  this._siteId.set(access.siteId);
405
620
  this._scope.set('site');
406
621
  this._menu.set(await this.siteMenu(access.siteId, language));
407
622
  return;
408
623
  }
624
+ const global = (me.globalAccess ?? []).find((entry) => entry.appId.toLowerCase() === this.config.appId);
409
625
  this._access.set(null);
626
+ this._globalRole.set(global?.roleName ?? null);
410
627
  this._siteId.set(wanted);
411
628
  this._scope.set('account');
412
629
  this._menu.set(await this.accountMenu(language));
@@ -439,52 +656,375 @@ function toOption(company, relation) {
439
656
  relation,
440
657
  };
441
658
  }
442
- function describe(error) {
443
- if (error instanceof HttpErrorResponse) {
444
- const problem = error.error;
445
- return problem?.detail ?? problem?.title ?? `HTTP ${error.status}`;
659
+ function isSessionRejected(error) {
660
+ return error instanceof HttpErrorResponse && (error.status === 401 || error.status === 403);
661
+ }
662
+
663
+ const DATE_LOCALES = {
664
+ es: 'es-PE',
665
+ en: 'en-US',
666
+ };
667
+ const NUMBER_LOCALE = 'es-PE';
668
+ const DAY_MS = 86400000;
669
+ const DATE_ONLY = /^\d{4}-\d{2}-\d{2}$/;
670
+ const MONEY = new Intl.NumberFormat(NUMBER_LOCALE, {
671
+ minimumFractionDigits: 2,
672
+ maximumFractionDigits: 2,
673
+ });
674
+ const QTY = new Intl.NumberFormat(NUMBER_LOCALE, { maximumFractionDigits: 4 });
675
+ const INTEGER = new Intl.NumberFormat(NUMBER_LOCALE, { maximumFractionDigits: 0 });
676
+ const dates = new Map();
677
+ function formatter(kind) {
678
+ const locale = DATE_LOCALES[nucleusLang()];
679
+ const key = `${kind}:${locale}`;
680
+ const cached = dates.get(key);
681
+ if (cached)
682
+ return cached;
683
+ const options = kind === 'date'
684
+ ? { day: '2-digit', month: 'short', year: 'numeric' }
685
+ : kind === 'stamp'
686
+ ? { day: '2-digit', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' }
687
+ : { hour: '2-digit', minute: '2-digit' };
688
+ const built = new Intl.DateTimeFormat(locale, options);
689
+ dates.set(key, built);
690
+ return built;
691
+ }
692
+ function nucleusParseDate(value) {
693
+ if (!value)
694
+ return null;
695
+ if (DATE_ONLY.test(value)) {
696
+ const [year, month, day] = value.split('-').map(Number);
697
+ return new Date(year, month - 1, day);
698
+ }
699
+ const parsed = new Date(value);
700
+ return Number.isNaN(parsed.valueOf()) ? null : parsed;
701
+ }
702
+ function money(value) {
703
+ return value === null || value === undefined ? '' : MONEY.format(value);
704
+ }
705
+ function signedMoney(value) {
706
+ if (value === null || value === undefined)
707
+ return '';
708
+ return value > 0 ? `+${MONEY.format(value)}` : MONEY.format(value);
709
+ }
710
+ function qty(value) {
711
+ return value === null || value === undefined ? '' : QTY.format(value);
712
+ }
713
+ function integer(value) {
714
+ return value === null || value === undefined ? '' : INTEGER.format(value);
715
+ }
716
+ function percent(value) {
717
+ return value === null || value === undefined ? '' : `${MONEY.format(value)}%`;
718
+ }
719
+ function day(value) {
720
+ const parsed = nucleusParseDate(value);
721
+ return parsed ? formatter('date').format(parsed) : '';
722
+ }
723
+ function stamp(value) {
724
+ const parsed = nucleusParseDate(value);
725
+ return parsed ? formatter('stamp').format(parsed) : '';
726
+ }
727
+ function clock(value) {
728
+ const parsed = nucleusParseDate(value);
729
+ return parsed ? formatter('clock').format(parsed) : '';
730
+ }
731
+ function minutesAsClock(value) {
732
+ if (value === null || value === undefined)
733
+ return '';
734
+ const total = Math.max(Math.trunc(value), 0);
735
+ const hours = Math.trunc(total / 60);
736
+ const minutes = total % 60;
737
+ return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;
738
+ }
739
+ function isoDay(value) {
740
+ const parsed = typeof value === 'string' || value === null || value === undefined
741
+ ? nucleusParseDate(value)
742
+ : value;
743
+ if (!parsed)
744
+ return '';
745
+ const year = parsed.getFullYear();
746
+ const month = String(parsed.getMonth() + 1).padStart(2, '0');
747
+ const date = String(parsed.getDate()).padStart(2, '0');
748
+ return `${year}-${month}-${date}`;
749
+ }
750
+ function toNumber(value) {
751
+ const parsed = typeof value === 'number' ? value : Number(value);
752
+ return Number.isFinite(parsed) ? parsed : 0;
753
+ }
754
+ function shiftDay(value, days) {
755
+ const parsed = nucleusParseDate(value);
756
+ return parsed ? isoDay(new Date(parsed.valueOf() + days * DAY_MS)) : '';
757
+ }
758
+ function isToday(value) {
759
+ const parsed = nucleusParseDate(value);
760
+ return parsed ? isoDay(parsed) === isoDay(new Date()) : false;
761
+ }
762
+ function isBeforeToday(value) {
763
+ const parsed = nucleusParseDate(value);
764
+ return parsed ? isoDay(parsed) < isoDay(new Date()) : false;
765
+ }
766
+ function isOnOrBeforeToday(value) {
767
+ const parsed = nucleusParseDate(value);
768
+ return parsed ? isoDay(parsed) <= isoDay(new Date()) : false;
769
+ }
770
+ function startOfDayUtc(value) {
771
+ return `${isoDay(nucleusParseDate(value))}T00:00:00.000Z`;
772
+ }
773
+ function endOfDayUtc(value) {
774
+ return `${isoDay(nucleusParseDate(value))}T23:59:59.999Z`;
775
+ }
776
+
777
+ const FIELD_TARGETS = [
778
+ 'input:not([type="hidden"]):not([disabled])',
779
+ 'textarea:not([disabled])',
780
+ 'select:not([disabled])',
781
+ '[role="combobox"]:not([aria-disabled="true"])',
782
+ ].join(',');
783
+ const TEXTUAL = ['text', 'email', 'password', 'number', 'search', 'tel', 'url'];
784
+ function isReachable(element) {
785
+ return element.tabIndex >= 0 && element.offsetParent !== null;
786
+ }
787
+ function fieldsIn(root) {
788
+ return Array.from(root.querySelectorAll(FIELD_TARGETS)).filter(isReachable);
789
+ }
790
+ function moveTo(element) {
791
+ element.focus({ preventScroll: true });
792
+ if (element instanceof HTMLInputElement && TEXTUAL.includes(element.type)) {
793
+ element.select();
446
794
  }
447
- return 'Error desconocido';
448
795
  }
449
796
 
450
- const NUCLEUS_LANGS = ['es', 'en'];
451
- const STORAGE_KEY$1 = 'nucleus_lang';
452
- function isLang(value) {
453
- return value === 'es' || value === 'en';
797
+ const MODIFIERS = ['ctrl', 'alt', 'shift'];
798
+ const ALIASES = {
799
+ cmd: 'ctrl',
800
+ meta: 'ctrl',
801
+ command: 'ctrl',
802
+ control: 'ctrl',
803
+ option: 'alt',
804
+ esc: 'escape',
805
+ ' ': 'space',
806
+ };
807
+ function normalizeKeys(keys) {
808
+ const parts = keys
809
+ .toLowerCase()
810
+ .split('+')
811
+ .map((part) => part.trim())
812
+ .filter(Boolean)
813
+ .map((part) => ALIASES[part] ?? part);
814
+ const modifiers = MODIFIERS.filter((modifier) => parts.includes(modifier));
815
+ const rest = parts.filter((part) => !MODIFIERS.includes(part));
816
+ return [...modifiers, ...rest].join('+');
454
817
  }
455
- function readStored() {
818
+ function comboOf(event) {
819
+ const key = event.key.toLowerCase();
820
+ const parts = [];
821
+ if (event.ctrlKey || event.metaKey)
822
+ parts.push('ctrl');
823
+ if (event.altKey)
824
+ parts.push('alt');
825
+ if (event.shiftKey && key.length > 1)
826
+ parts.push('shift');
827
+ parts.push(ALIASES[key] ?? key);
828
+ return parts.join('+');
829
+ }
830
+ function hasModifier(keys) {
831
+ return keys.startsWith('ctrl+') || keys.startsWith('alt+');
832
+ }
833
+ function isTypingTarget(target) {
834
+ const element = target;
835
+ if (!element || typeof element.tagName !== 'string')
836
+ return false;
837
+ if (element.isContentEditable)
838
+ return true;
839
+ return ['INPUT', 'TEXTAREA', 'SELECT'].includes(element.tagName);
840
+ }
841
+ function isApplePlatform() {
842
+ if (typeof navigator === 'undefined')
843
+ return false;
844
+ return /Mac|iPhone|iPad|iPod/.test(navigator.platform || navigator.userAgent);
845
+ }
846
+ const DISPLAY = {
847
+ escape: 'Esc',
848
+ enter: 'Enter',
849
+ space: 'Espacio',
850
+ arrowup: '↑',
851
+ arrowdown: '↓',
852
+ arrowleft: '←',
853
+ arrowright: '→',
854
+ };
855
+ function shortcutHint(keys) {
856
+ const apple = isApplePlatform();
857
+ return normalizeKeys(keys)
858
+ .split('+')
859
+ .map((part) => {
860
+ if (part === 'ctrl')
861
+ return apple ? '⌘' : 'Ctrl';
862
+ if (part === 'alt')
863
+ return apple ? '⌥' : 'Alt';
864
+ if (part === 'shift')
865
+ return apple ? '⇧' : 'Shift';
866
+ return DISPLAY[part] ?? part.toUpperCase();
867
+ })
868
+ .join(apple ? '' : '+');
869
+ }
870
+ const ARIA = {
871
+ ctrl: 'Control',
872
+ alt: 'Alt',
873
+ shift: 'Shift',
874
+ escape: 'Escape',
875
+ enter: 'Enter',
876
+ space: 'Space',
877
+ };
878
+ function ariaKeysOf(keys) {
879
+ return normalizeKeys(keys)
880
+ .split('+')
881
+ .map((part) => ARIA[part] ?? part.toUpperCase())
882
+ .join('+');
883
+ }
884
+
885
+ const MODAL_SELECTOR = '.p-drawer-mask, .p-dialog-mask, .p-overlay-mask';
886
+ const STORAGE_KEY$2 = 'nucleus_shortcuts';
887
+ function readStored$1() {
456
888
  try {
457
- const stored = localStorage.getItem(STORAGE_KEY$1);
458
- if (isLang(stored))
459
- return stored;
889
+ return localStorage.getItem(STORAGE_KEY$2) !== 'off';
890
+ }
891
+ catch {
892
+ return true;
460
893
  }
461
- catch { }
462
- return 'es';
463
894
  }
464
- class NucleusLanguage {
465
- _lang = signal(readStored(), ...(ngDevMode ? [{ debugName: "_lang" }] : /* istanbul ignore next */ []));
466
- lang = this._lang.asReadonly();
895
+ class NucleusShortcuts {
896
+ document = inject(DOCUMENT);
897
+ _bindings = signal([], ...(ngDevMode ? [{ debugName: "_bindings" }] : /* istanbul ignore next */ []));
898
+ _enabled = signal(readStored$1(), ...(ngDevMode ? [{ debugName: "_enabled" }] : /* istanbul ignore next */ []));
899
+ bindings = this._bindings.asReadonly();
900
+ enabled = this._enabled.asReadonly();
467
901
  constructor() {
468
- effect(() => {
469
- const lang = this._lang();
470
- document.documentElement.lang = lang;
471
- try {
472
- localStorage.setItem(STORAGE_KEY$1, lang);
473
- }
474
- catch { }
475
- });
902
+ const listener = (event) => this.handle(event);
903
+ this.document.addEventListener('keydown', listener);
904
+ inject(DestroyRef).onDestroy(() => this.document.removeEventListener('keydown', listener));
476
905
  }
477
- setLang(lang) {
478
- this._lang.set(lang);
906
+ setEnabled(enabled) {
907
+ this._enabled.set(enabled);
908
+ try {
909
+ localStorage.setItem(STORAGE_KEY$2, enabled ? 'on' : 'off');
910
+ }
911
+ catch { }
479
912
  }
480
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusLanguage, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
481
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusLanguage, providedIn: 'root' });
913
+ register(binding) {
914
+ this._bindings.update((list) => [...list, binding]);
915
+ return () => this._bindings.update((list) => list.filter((entry) => entry !== binding));
916
+ }
917
+ handle(event) {
918
+ if (!this._enabled() || event.isComposing || event.repeat)
919
+ return;
920
+ const combo = comboOf(event);
921
+ const typing = isTypingTarget(event.target);
922
+ const blocked = this.document.querySelector(MODAL_SELECTOR) !== null;
923
+ const bindings = this._bindings();
924
+ for (let index = bindings.length - 1; index >= 0; index -= 1) {
925
+ const binding = bindings[index];
926
+ if (!binding || binding.keys !== combo)
927
+ continue;
928
+ if (typing && !binding.whileTyping)
929
+ continue;
930
+ if (blocked && !hasModifier(binding.keys))
931
+ continue;
932
+ event.preventDefault();
933
+ binding.run();
934
+ return;
935
+ }
936
+ }
937
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusShortcuts, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
938
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusShortcuts, providedIn: 'root' });
482
939
  }
483
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusLanguage, decorators: [{
940
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusShortcuts, decorators: [{
484
941
  type: Injectable,
485
942
  args: [{ providedIn: 'root' }]
486
943
  }], ctorParameters: () => [] });
487
944
 
945
+ const CLICKABLE = 'button, a, input';
946
+ class NucleusShortcut {
947
+ host = inject(ElementRef);
948
+ shortcuts = inject(NucleusShortcuts);
949
+ nucleusShortcut = input.required(...(ngDevMode ? [{ debugName: "nucleusShortcut" }] : /* istanbul ignore next */ []));
950
+ shortcutLabel = input('', ...(ngDevMode ? [{ debugName: "shortcutLabel" }] : /* istanbul ignore next */ []));
951
+ shortcutWhileTyping = input(null, ...(ngDevMode ? [{ debugName: "shortcutWhileTyping" }] : /* istanbul ignore next */ []));
952
+ constructor() {
953
+ effect((onCleanup) => {
954
+ const keys = normalizeKeys(this.nucleusShortcut());
955
+ const label = this.shortcutLabel();
956
+ const whileTyping = this.shortcutWhileTyping() ?? hasModifier(keys);
957
+ this.host.nativeElement.setAttribute('aria-keyshortcuts', ariaKeysOf(keys));
958
+ const dispose = this.shortcuts.register({
959
+ keys,
960
+ label,
961
+ whileTyping,
962
+ run: () => this.press(),
963
+ });
964
+ onCleanup(dispose);
965
+ });
966
+ }
967
+ press() {
968
+ const element = this.host.nativeElement;
969
+ const target = element.matches(CLICKABLE)
970
+ ? element
971
+ : element.querySelector(CLICKABLE);
972
+ if (!target || target.hasAttribute('disabled'))
973
+ return;
974
+ target.click();
975
+ }
976
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusShortcut, deps: [], target: i0.ɵɵFactoryTarget.Directive });
977
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.22", type: NucleusShortcut, isStandalone: true, selector: "[nucleusShortcut]", inputs: { nucleusShortcut: { classPropertyName: "nucleusShortcut", publicName: "nucleusShortcut", isSignal: true, isRequired: true, transformFunction: null }, shortcutLabel: { classPropertyName: "shortcutLabel", publicName: "shortcutLabel", isSignal: true, isRequired: false, transformFunction: null }, shortcutWhileTyping: { classPropertyName: "shortcutWhileTyping", publicName: "shortcutWhileTyping", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
978
+ }
979
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusShortcut, decorators: [{
980
+ type: Directive,
981
+ args: [{
982
+ selector: '[nucleusShortcut]',
983
+ }]
984
+ }], ctorParameters: () => [], propDecorators: { nucleusShortcut: [{ type: i0.Input, args: [{ isSignal: true, alias: "nucleusShortcut", required: true }] }], shortcutLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "shortcutLabel", required: false }] }], shortcutWhileTyping: [{ type: i0.Input, args: [{ isSignal: true, alias: "shortcutWhileTyping", required: false }] }] } });
985
+
986
+ const SCOPES = 'nucleus-card, form, .p-drawer-content';
987
+ const ACTIONS = '.actions button:not([disabled]), .drawer-actions button:not([disabled])';
988
+ class NucleusEnterAdvance {
989
+ host = inject(ElementRef);
990
+ onKeyDown(event) {
991
+ if (event.key !== 'Enter' || event.isComposing)
992
+ return;
993
+ if (event.ctrlKey || event.metaKey || event.altKey || event.shiftKey)
994
+ return;
995
+ const source = event.target;
996
+ if (!source || source.tagName === 'TEXTAREA')
997
+ return;
998
+ if (source.getAttribute('aria-expanded') === 'true')
999
+ return;
1000
+ const scope = this.scope();
1001
+ const targets = fieldsIn(scope);
1002
+ const index = targets.indexOf(source);
1003
+ if (index === -1)
1004
+ return;
1005
+ event.preventDefault();
1006
+ const next = targets[index + 1];
1007
+ if (next) {
1008
+ moveTo(next);
1009
+ return;
1010
+ }
1011
+ scope.querySelector(ACTIONS)?.focus();
1012
+ }
1013
+ scope() {
1014
+ const element = this.host.nativeElement;
1015
+ return element.closest(SCOPES) ?? element;
1016
+ }
1017
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusEnterAdvance, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1018
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.22", type: NucleusEnterAdvance, isStandalone: true, selector: "[nucleusEnterAdvance]", host: { listeners: { "keydown": "onKeyDown($event)" } }, ngImport: i0 });
1019
+ }
1020
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusEnterAdvance, decorators: [{
1021
+ type: Directive,
1022
+ args: [{
1023
+ selector: '[nucleusEnterAdvance]',
1024
+ host: { '(keydown)': 'onKeyDown($event)' },
1025
+ }]
1026
+ }] });
1027
+
488
1028
  const EMPTY_GEO_VALUE = { countryId: null, divisionId: null };
489
1029
  function flagOf(code) {
490
1030
  if (code.length !== 2)
@@ -495,8 +1035,6 @@ function flagOf(code) {
495
1035
  const GEO_COPY = {
496
1036
  es: {
497
1037
  country: 'País',
498
- choose: 'Selecciona',
499
- search: 'Buscar',
500
1038
  levels: ['Región', 'Provincia', 'Distrito', 'Zona'],
501
1039
  kinds: {
502
1040
  department: 'Departamento',
@@ -525,8 +1063,6 @@ const GEO_COPY = {
525
1063
  },
526
1064
  en: {
527
1065
  country: 'Country',
528
- choose: 'Select',
529
- search: 'Search',
530
1066
  levels: ['Region', 'Province', 'District', 'Area'],
531
1067
  kinds: {
532
1068
  department: 'Department',
@@ -622,6 +1158,7 @@ class NucleusGeoSelect {
622
1158
  countries = signal([], ...(ngDevMode ? [{ debugName: "countries" }] : /* istanbul ignore next */ []));
623
1159
  steps = signal([], ...(ngDevMode ? [{ debugName: "steps" }] : /* istanbul ignore next */ []));
624
1160
  labels = computed(() => geoCopyOf(this.language.lang()), ...(ngDevMode ? [{ debugName: "labels" }] : /* istanbul ignore next */ []));
1161
+ words = computed(() => commonCopyOf(this.language.lang()), ...(ngDevMode ? [{ debugName: "words" }] : /* istanbul ignore next */ []));
625
1162
  syncedKey = null;
626
1163
  constructor() {
627
1164
  effect(() => {
@@ -701,8 +1238,9 @@ class NucleusGeoSelect {
701
1238
  this.steps.set(built);
702
1239
  }
703
1240
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusGeoSelect, deps: [], target: i0.ɵɵFactoryTarget.Component });
704
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: NucleusGeoSelect, isStandalone: true, selector: "nucleus-geo-select", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, ngImport: i0, template: `
1241
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: NucleusGeoSelect, isStandalone: true, selector: "nucleus-geo-select", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, hostDirectives: [{ directive: NucleusEnterAdvance }], ngImport: i0, template: `
705
1242
  @let copy = labels();
1243
+ @let shared = words();
706
1244
 
707
1245
  <div class="ns-form-grid" [class]="gridClass()">
708
1246
  <div class="ns-field is-span-1 is-size-md">
@@ -717,9 +1255,11 @@ class NucleusGeoSelect {
717
1255
  [disabled]="readonly()"
718
1256
  [filter]="true"
719
1257
  filterBy="name"
720
- [filterPlaceholder]="copy.search"
1258
+ [filterPlaceholder]="shared.search"
1259
+ [autofocusFilter]="true"
1260
+ [resetFilterOnHide]="true"
721
1261
  [showClear]="true"
722
- [placeholder]="copy.choose"
1262
+ [placeholder]="shared.choose"
723
1263
  [ngModelOptions]="{ standalone: true }"
724
1264
  fluid
725
1265
  />
@@ -736,23 +1276,26 @@ class NucleusGeoSelect {
736
1276
  [ngModel]="step.selectedId"
737
1277
  (ngModelChange)="pick(index, $event)"
738
1278
  [disabled]="readonly()"
739
- [filter]="step.items.length > 12"
1279
+ [filter]="true"
740
1280
  filterBy="name"
741
- [filterPlaceholder]="copy.search"
1281
+ [filterPlaceholder]="shared.search"
1282
+ [autofocusFilter]="true"
1283
+ [resetFilterOnHide]="true"
742
1284
  [showClear]="true"
743
- [placeholder]="copy.choose"
1285
+ [placeholder]="shared.choose"
744
1286
  [ngModelOptions]="{ standalone: true }"
745
1287
  fluid
746
1288
  />
747
1289
  </div>
748
1290
  }
749
1291
  </div>
750
- `, isInline: true, styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: SelectModule }, { kind: "component", type: i1.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i6.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i6.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1292
+ `, isInline: true, styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: SelectModule }, { kind: "component", type: i2.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
751
1293
  }
752
1294
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusGeoSelect, decorators: [{
753
1295
  type: Component,
754
- args: [{ selector: 'nucleus-geo-select', imports: [SelectModule, FormsModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
1296
+ args: [{ selector: 'nucleus-geo-select', imports: [SelectModule, FormsModule], hostDirectives: [NucleusEnterAdvance], changeDetection: ChangeDetectionStrategy.OnPush, template: `
755
1297
  @let copy = labels();
1298
+ @let shared = words();
756
1299
 
757
1300
  <div class="ns-form-grid" [class]="gridClass()">
758
1301
  <div class="ns-field is-span-1 is-size-md">
@@ -767,9 +1310,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
767
1310
  [disabled]="readonly()"
768
1311
  [filter]="true"
769
1312
  filterBy="name"
770
- [filterPlaceholder]="copy.search"
1313
+ [filterPlaceholder]="shared.search"
1314
+ [autofocusFilter]="true"
1315
+ [resetFilterOnHide]="true"
771
1316
  [showClear]="true"
772
- [placeholder]="copy.choose"
1317
+ [placeholder]="shared.choose"
773
1318
  [ngModelOptions]="{ standalone: true }"
774
1319
  fluid
775
1320
  />
@@ -786,11 +1331,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
786
1331
  [ngModel]="step.selectedId"
787
1332
  (ngModelChange)="pick(index, $event)"
788
1333
  [disabled]="readonly()"
789
- [filter]="step.items.length > 12"
1334
+ [filter]="true"
790
1335
  filterBy="name"
791
- [filterPlaceholder]="copy.search"
1336
+ [filterPlaceholder]="shared.search"
1337
+ [autofocusFilter]="true"
1338
+ [resetFilterOnHide]="true"
792
1339
  [showClear]="true"
793
- [placeholder]="copy.choose"
1340
+ [placeholder]="shared.choose"
794
1341
  [ngModelOptions]="{ standalone: true }"
795
1342
  fluid
796
1343
  />
@@ -811,306 +1358,608 @@ function deepestOf(steps) {
811
1358
  return null;
812
1359
  }
813
1360
 
814
- const THEMES = [
1361
+ const TEXT_CONTRAST = 4.5;
1362
+ const CONTROL_CONTRAST = 3;
1363
+ const DARK_INK = '#101014';
1364
+ const LIGHT_INK = '#ffffff';
1365
+ function relativeLuminance(hex) {
1366
+ const channel = (offset) => {
1367
+ const value = parseInt(hex.slice(offset, offset + 2), 16) / 255;
1368
+ return value <= 0.03928 ? value / 12.92 : Math.pow((value + 0.055) / 1.055, 2.4);
1369
+ };
1370
+ return 0.2126 * channel(1) + 0.7152 * channel(3) + 0.0722 * channel(5);
1371
+ }
1372
+ function contrastRatio(foreground, background) {
1373
+ const first = relativeLuminance(foreground);
1374
+ const second = relativeLuminance(background);
1375
+ return (Math.max(first, second) + 0.05) / (Math.min(first, second) + 0.05);
1376
+ }
1377
+ function readableOn(background) {
1378
+ return contrastRatio(DARK_INK, background) >= contrastRatio(LIGHT_INK, background)
1379
+ ? DARK_INK
1380
+ : LIGHT_INK;
1381
+ }
1382
+ function mixHex(foreground, background, ratio) {
1383
+ const amount = Math.min(Math.max(ratio, 0), 1);
1384
+ const channel = (offset) => {
1385
+ const front = parseInt(foreground.slice(offset, offset + 2), 16);
1386
+ const back = parseInt(background.slice(offset, offset + 2), 16);
1387
+ return Math.round(front * amount + back * (1 - amount))
1388
+ .toString(16)
1389
+ .padStart(2, '0');
1390
+ };
1391
+ return `#${channel(1)}${channel(3)}${channel(5)}`;
1392
+ }
1393
+ function meetsContrast(foreground, background, required) {
1394
+ return contrastRatio(foreground, background) >= required;
1395
+ }
1396
+ function contrastIssue(scope, pair, foreground, background, required) {
1397
+ const ratio = contrastRatio(foreground, background);
1398
+ return ratio >= required ? null : { scope, pair, ratio, required };
1399
+ }
1400
+
1401
+ const FRAMES = [
815
1402
  {
816
1403
  id: 'nucleus',
817
1404
  scheme: 'dark',
818
- swatch: '#7c6cf5',
819
1405
  starfield: true,
1406
+ accent: 'violet',
820
1407
  tokens: {
821
- bg: '#050508',
822
- surface: '#0d0d15',
823
- surfaceAlt: '#16161f',
824
- text: '#ffffff',
825
- textMuted: 'rgba(255, 255, 255, 0.6)',
826
- border: 'rgba(255, 255, 255, 0.14)',
827
- accent: '#7c6cf5',
828
- onAccent: '#ffffff',
1408
+ bg: '#16132b',
1409
+ raised: '#241f42',
1410
+ ink: '#ece9fb',
1411
+ muted: '#9d95c6',
1412
+ line: '#302a54',
1413
+ glow: '#8c7ff0',
829
1414
  },
830
1415
  },
831
1416
  {
832
- id: 'light',
833
- scheme: 'light',
834
- swatch: '#f5f5f5',
1417
+ id: 'midnight',
1418
+ scheme: 'dark',
835
1419
  starfield: false,
1420
+ accent: 'blue',
836
1421
  tokens: {
837
- bg: '#ffffff',
838
- surface: '#f5f5f5',
839
- surfaceAlt: '#eeeeee',
840
- text: '#1e1e1e',
841
- textMuted: '#5a5a5a',
842
- border: '#cccedb',
843
- accent: '#0078d4',
844
- onAccent: '#ffffff',
1422
+ bg: '#131f33',
1423
+ raised: '#1e2e48',
1424
+ ink: '#e3ecfa',
1425
+ muted: '#94a9c9',
1426
+ line: '#2b3d5d',
1427
+ glow: '#6ea8f0',
845
1428
  },
846
1429
  },
847
1430
  {
848
- id: 'dark',
1431
+ id: 'ocean',
849
1432
  scheme: 'dark',
850
- swatch: '#1e1e1e',
851
1433
  starfield: false,
1434
+ accent: 'cyan',
852
1435
  tokens: {
853
- bg: '#1e1e1e',
854
- surface: '#252526',
855
- surfaceAlt: '#2d2d30',
856
- text: '#f1f1f1',
857
- textMuted: '#a0a0a0',
858
- border: '#3f3f46',
859
- accent: '#0097fb',
860
- onAccent: '#ffffff',
1436
+ bg: '#0d2a30',
1437
+ raised: '#153c45',
1438
+ ink: '#e0f2f4',
1439
+ muted: '#8cb3b9',
1440
+ line: '#1d4e58',
1441
+ glow: '#4cc5e8',
861
1442
  },
862
1443
  },
863
1444
  {
864
- id: 'bubblegum',
865
- scheme: 'light',
866
- swatch: '#f7b8d4',
1445
+ id: 'forest',
1446
+ scheme: 'dark',
867
1447
  starfield: false,
1448
+ accent: 'green',
868
1449
  tokens: {
869
- bg: '#fff0f6',
870
- surface: '#ffe0ed',
871
- surfaceAlt: '#ffd0e4',
872
- text: '#3d1029',
873
- textMuted: '#8a4c6c',
874
- border: '#f0aac9',
875
- accent: '#d6337f',
876
- onAccent: '#ffffff',
1450
+ bg: '#122419',
1451
+ raised: '#1d3526',
1452
+ ink: '#e2f1e7',
1453
+ muted: '#93b8a1',
1454
+ line: '#28462f',
1455
+ glow: '#5cc47f',
877
1456
  },
878
1457
  },
879
1458
  {
880
- id: 'cool-breeze',
881
- scheme: 'light',
882
- swatch: '#a8d5e8',
1459
+ id: 'plum',
1460
+ scheme: 'dark',
883
1461
  starfield: false,
1462
+ accent: 'rose',
884
1463
  tokens: {
885
- bg: '#eef7fb',
886
- surface: '#dcecf5',
887
- surfaceAlt: '#cbe2ef',
888
- text: '#102a38',
889
- textMuted: '#4a6b7d',
890
- border: '#a5c9db',
891
- accent: '#0f7ba8',
892
- onAccent: '#ffffff',
1464
+ bg: '#241330',
1465
+ raised: '#341e43',
1466
+ ink: '#f3e8f7',
1467
+ muted: '#b596c3',
1468
+ line: '#452851',
1469
+ glow: '#cf7fdb',
893
1470
  },
894
1471
  },
895
1472
  {
896
- id: 'cool-slate',
1473
+ id: 'ember',
897
1474
  scheme: 'dark',
898
- swatch: '#3b4a5c',
899
1475
  starfield: false,
1476
+ accent: 'red',
900
1477
  tokens: {
901
- bg: '#232b36',
902
- surface: '#2c3644',
903
- surfaceAlt: '#354152',
904
- text: '#e6ecf4',
905
- textMuted: '#9fb0c4',
906
- border: '#44546a',
907
- accent: '#5fa8e8',
908
- onAccent: '#0b1118',
1478
+ bg: '#2a1113',
1479
+ raised: '#3d1b1f',
1480
+ ink: '#f8e6e7',
1481
+ muted: '#c9979c',
1482
+ line: '#4d2429',
1483
+ glow: '#f0797b',
909
1484
  },
910
1485
  },
911
1486
  {
912
- id: 'icy-mint',
913
- scheme: 'light',
914
- swatch: '#c4ece5',
1487
+ id: 'sand',
1488
+ scheme: 'dark',
915
1489
  starfield: false,
1490
+ accent: 'amber',
916
1491
  tokens: {
917
- bg: '#f0fbf8',
918
- surface: '#dcf3ee',
919
- surfaceAlt: '#c9eae3',
920
- text: '#0d2f2a',
921
- textMuted: '#4a706a',
922
- border: '#a3d6cd',
923
- accent: '#0d8f7d',
924
- onAccent: '#ffffff',
1492
+ bg: '#2b2415',
1493
+ raised: '#3d3320',
1494
+ ink: '#f6efdd',
1495
+ muted: '#c2b18b',
1496
+ line: '#4b3f28',
1497
+ glow: '#e0a33a',
925
1498
  },
926
1499
  },
927
1500
  {
928
- id: 'juicy-plum',
1501
+ id: 'slate',
929
1502
  scheme: 'dark',
930
- swatch: '#6b3a7a',
931
1503
  starfield: false,
1504
+ accent: 'blue',
932
1505
  tokens: {
933
- bg: '#2a1633',
934
- surface: '#372042',
935
- surfaceAlt: '#452a52',
936
- text: '#f3e8f7',
937
- textMuted: '#b795c4',
938
- border: '#57366a',
939
- accent: '#c86dd7',
940
- onAccent: '#1a0d20',
1506
+ bg: '#232b36',
1507
+ raised: '#313c4b',
1508
+ ink: '#e6ecf4',
1509
+ muted: '#9fb0c4',
1510
+ line: '#3d4a5b',
1511
+ glow: '#6fb2ea',
941
1512
  },
942
1513
  },
943
1514
  {
944
- id: 'mango-paradise',
945
- scheme: 'light',
946
- swatch: '#f7c48a',
1515
+ id: 'ink',
1516
+ scheme: 'dark',
947
1517
  starfield: false,
1518
+ accent: 'blue',
948
1519
  tokens: {
949
- bg: '#fff6ec',
950
- surface: '#ffe9d4',
951
- surfaceAlt: '#ffdcbc',
952
- text: '#3d2410',
953
- textMuted: '#8a5f38',
954
- border: '#f0c096',
955
- accent: '#e07314',
956
- onAccent: '#ffffff',
1520
+ bg: '#101318',
1521
+ raised: '#1b2129',
1522
+ ink: '#edf0f4',
1523
+ muted: '#97a1ae',
1524
+ line: '#262d37',
1525
+ glow: '#7fb3d9',
957
1526
  },
958
1527
  },
959
1528
  {
960
- id: 'moonlight-glow',
1529
+ id: 'graphite',
961
1530
  scheme: 'dark',
962
- swatch: '#1f3a5f',
963
1531
  starfield: false,
1532
+ accent: 'violet',
964
1533
  tokens: {
965
- bg: '#131f33',
966
- surface: '#1c2b45',
967
- surfaceAlt: '#243657',
968
- text: '#e3ecfa',
969
- textMuted: '#94a9c9',
970
- border: '#33496e',
971
- accent: '#6ea8f0',
972
- onAccent: '#0a1020',
1534
+ bg: '#17171a',
1535
+ raised: '#232327',
1536
+ ink: '#eeeef0',
1537
+ muted: '#9c9ca4',
1538
+ line: '#2f2f35',
1539
+ glow: '#b9b9c2',
973
1540
  },
974
1541
  },
975
1542
  {
976
- id: 'mystical-forest',
977
- scheme: 'dark',
978
- swatch: '#1f4a2e',
1543
+ id: 'paper',
1544
+ scheme: 'light',
979
1545
  starfield: false,
1546
+ accent: 'violet',
980
1547
  tokens: {
981
- bg: '#132419',
982
- surface: '#1b3223',
983
- surfaceAlt: '#24422e',
984
- text: '#e4f2e8',
985
- textMuted: '#93b7a0',
986
- border: '#325a41',
987
- accent: '#4caf70',
988
- onAccent: '#0a1610',
1548
+ bg: '#ffffff',
1549
+ raised: '#f1f0f5',
1550
+ ink: '#16151d',
1551
+ muted: '#5e5c6b',
1552
+ line: '#e3e2ea',
1553
+ glow: '#5b4fd6',
989
1554
  },
990
1555
  },
991
1556
  {
992
- id: 'silky-pink',
1557
+ id: 'mist',
993
1558
  scheme: 'light',
994
- swatch: '#f3c2d6',
995
1559
  starfield: false,
1560
+ accent: 'blue',
996
1561
  tokens: {
997
- bg: '#fdf2f7',
998
- surface: '#fae3ed',
999
- surfaceAlt: '#f6d3e2',
1000
- text: '#3a1626',
1001
- textMuted: '#87566a',
1002
- border: '#eeb4cb',
1003
- accent: '#c14b82',
1004
- onAccent: '#ffffff',
1562
+ bg: '#eef1f6',
1563
+ raised: '#e2e7ef',
1564
+ ink: '#141a24',
1565
+ muted: '#576274',
1566
+ line: '#d3dae5',
1567
+ glow: '#1160b8',
1005
1568
  },
1006
1569
  },
1570
+ ];
1571
+ const CANVASES = [
1007
1572
  {
1008
- id: 'spicy-red',
1009
- scheme: 'dark',
1010
- swatch: '#8f1f24',
1011
- starfield: false,
1573
+ id: 'light',
1012
1574
  tokens: {
1013
- bg: '#2a1113',
1014
- surface: '#3a181b',
1015
- surfaceAlt: '#4a1f23',
1016
- text: '#f8e6e7',
1017
- textMuted: '#c79398',
1018
- border: '#5f2a2f',
1019
- accent: '#e8555e',
1020
- onAccent: '#1a0809',
1575
+ bg: '#edeff3',
1576
+ surface: '#ffffff',
1577
+ surfaceAlt: '#e2e6ed',
1578
+ text: '#16151d',
1579
+ textMuted: '#5a5f6b',
1580
+ border: '#d3d8e1',
1581
+ },
1582
+ effects: {
1583
+ shadowOne: '0 1px 2px rgb(16 20 30 / 6%), 0 2px 8px rgb(16 20 30 / 5%)',
1584
+ shadowTwo: '0 4px 12px rgb(16 20 30 / 8%), 0 12px 32px rgb(16 20 30 / 7%)',
1585
+ wash: 0.1,
1586
+ washStrong: 0.18,
1587
+ headerBg: '#d8dee8',
1588
+ headerInk: '#39404e',
1589
+ headerLine: '#bcc5d4',
1590
+ danger: '#b02121',
1591
+ success: '#136b35',
1592
+ warning: '#8a5a00',
1021
1593
  },
1022
1594
  },
1023
1595
  {
1024
- id: 'sunny-day',
1025
- scheme: 'light',
1026
- swatch: '#efe4b0',
1027
- starfield: false,
1596
+ id: 'dim',
1597
+ tokens: {
1598
+ bg: '#1b1a22',
1599
+ surface: '#23222c',
1600
+ surfaceAlt: '#2c2b37',
1601
+ text: '#e9e8ef',
1602
+ textMuted: '#a6a4b4',
1603
+ border: '#383744',
1604
+ },
1605
+ effects: {
1606
+ shadowOne: '0 1px 2px rgb(0 0 0 / 24%)',
1607
+ shadowTwo: '0 10px 34px rgb(0 0 0 / 40%)',
1608
+ wash: 0.09,
1609
+ washStrong: 0.17,
1610
+ headerBg: '#302f3d',
1611
+ headerInk: '#c8c6d4',
1612
+ headerLine: '#423f50',
1613
+ danger: '#f0797b',
1614
+ success: '#5bd98a',
1615
+ warning: '#e0a33a',
1616
+ },
1617
+ },
1618
+ {
1619
+ id: 'dark',
1028
1620
  tokens: {
1029
- bg: '#fdf9e8',
1030
- surface: '#f7efd2',
1031
- surfaceAlt: '#f0e5bd',
1032
- text: '#332d10',
1033
- textMuted: '#736a38',
1034
- border: '#ddcf94',
1035
- accent: '#a8860b',
1036
- onAccent: '#ffffff',
1621
+ bg: '#0e0d13',
1622
+ surface: '#17161f',
1623
+ surfaceAlt: '#211f2a',
1624
+ text: '#f2f1f6',
1625
+ textMuted: '#a09eb0',
1626
+ border: '#2a2833',
1627
+ },
1628
+ effects: {
1629
+ shadowOne: '0 1px 2px rgb(0 0 0 / 28%)',
1630
+ shadowTwo: '0 10px 34px rgb(0 0 0 / 45%)',
1631
+ wash: 0.08,
1632
+ washStrong: 0.16,
1633
+ headerBg: '#262433',
1634
+ headerInk: '#c2c0ce',
1635
+ headerLine: '#3a3745',
1636
+ danger: '#f0797b',
1637
+ success: '#5bd98a',
1638
+ warning: '#e0a33a',
1037
1639
  },
1038
1640
  },
1039
1641
  ];
1040
- const THEME_LABELS = {
1642
+ const ACCENTS = [
1643
+ { id: 'violet', light: '#5b4fd6', dark: '#9b8ef5' },
1644
+ { id: 'blue', light: '#1160b8', dark: '#6fb2f2' },
1645
+ { id: 'cyan', light: '#0d6f87', dark: '#4cc5e8' },
1646
+ { id: 'green', light: '#157a3c', dark: '#5bd98a' },
1647
+ { id: 'lime', light: '#4d7c0f', dark: '#a3d94b' },
1648
+ { id: 'amber', light: '#8a5a00', dark: '#e0a33a' },
1649
+ { id: 'orange', light: '#b2430c', dark: '#f2934f' },
1650
+ { id: 'rose', light: '#b21b5c', dark: '#f07aab' },
1651
+ { id: 'red', light: '#b02121', dark: '#f0797b' },
1652
+ ];
1653
+ const DENSITIES = [
1654
+ { id: 'comfortable', scale: 1 },
1655
+ { id: 'compact', scale: 0.72 },
1656
+ ];
1657
+ const DEFAULT_APPEARANCE = {
1658
+ frame: 'nucleus',
1659
+ canvas: 'dark',
1660
+ accent: 'auto',
1661
+ density: 'comfortable',
1662
+ };
1663
+ const LEGACY_THEMES = {
1664
+ nucleus: { frame: 'nucleus', canvas: 'dark', accent: 'auto' },
1665
+ light: { frame: 'paper', canvas: 'light', accent: 'blue' },
1666
+ dark: { frame: 'graphite', canvas: 'dark', accent: 'blue' },
1667
+ bubblegum: { frame: 'plum', canvas: 'light', accent: 'rose' },
1668
+ 'cool-breeze': { frame: 'ocean', canvas: 'light', accent: 'cyan' },
1669
+ 'cool-slate': { frame: 'slate', canvas: 'dark', accent: 'blue' },
1670
+ 'icy-mint': { frame: 'ocean', canvas: 'light', accent: 'green' },
1671
+ 'juicy-plum': { frame: 'plum', canvas: 'dark', accent: 'rose' },
1672
+ 'mango-paradise': { frame: 'sand', canvas: 'light', accent: 'orange' },
1673
+ 'moonlight-glow': { frame: 'midnight', canvas: 'dark', accent: 'blue' },
1674
+ 'mystical-forest': { frame: 'forest', canvas: 'dark', accent: 'green' },
1675
+ 'silky-pink': { frame: 'plum', canvas: 'light', accent: 'rose' },
1676
+ 'spicy-red': { frame: 'ember', canvas: 'dark', accent: 'red' },
1677
+ 'sunny-day': { frame: 'sand', canvas: 'light', accent: 'amber' },
1678
+ system: { frame: 'paper', canvas: 'system', accent: 'blue' },
1679
+ };
1680
+ const FRAME_BY_ID = new Map(FRAMES.map((frame) => [frame.id, frame]));
1681
+ const CANVAS_BY_ID = new Map(CANVASES.map((canvas) => [canvas.id, canvas]));
1682
+ const ACCENT_BY_ID = new Map(ACCENTS.map((accent) => [accent.id, accent]));
1683
+ const DENSITY_BY_ID = new Map(DENSITIES.map((density) => [density.id, density]));
1684
+ function frameById(id) {
1685
+ return FRAME_BY_ID.get(id) ?? FRAME_BY_ID.get(DEFAULT_APPEARANCE.frame);
1686
+ }
1687
+ function canvasById(id) {
1688
+ return CANVAS_BY_ID.get(id) ?? CANVAS_BY_ID.get('dark');
1689
+ }
1690
+ function accentById(id) {
1691
+ return ACCENT_BY_ID.get(id) ?? ACCENT_BY_ID.get('violet');
1692
+ }
1693
+ function densityById(id) {
1694
+ return DENSITY_BY_ID.get(id) ?? DENSITY_BY_ID.get('comfortable');
1695
+ }
1696
+ function isFrameId(value) {
1697
+ return typeof value === 'string' && FRAME_BY_ID.has(value);
1698
+ }
1699
+ function isCanvasPreference(value) {
1700
+ return value === 'system' || (typeof value === 'string' && CANVAS_BY_ID.has(value));
1701
+ }
1702
+ function isAccentPreference(value) {
1703
+ return value === 'auto' || (typeof value === 'string' && ACCENT_BY_ID.has(value));
1704
+ }
1705
+ function isDensityId(value) {
1706
+ return typeof value === 'string' && DENSITY_BY_ID.has(value);
1707
+ }
1708
+ function resolveAccent(frame, preference) {
1709
+ return accentById(preference === 'auto' ? frame.accent : preference);
1710
+ }
1711
+ function accentOn(accent, canvas) {
1712
+ return canvas === 'light' ? accent.light : accent.dark;
1713
+ }
1714
+ function isDarkCanvas(canvas) {
1715
+ return canvas !== 'light';
1716
+ }
1717
+ function auditAppearance() {
1718
+ const issues = [];
1719
+ for (const canvas of CANVASES) {
1720
+ const { bg, surface, surfaceAlt } = canvas.tokens;
1721
+ const { danger, success, warning } = canvas.effects;
1722
+ const scope = `semantic:${canvas.id}`;
1723
+ for (const [name, color] of [
1724
+ ['danger', danger],
1725
+ ['success', success],
1726
+ ['warning', warning],
1727
+ ]) {
1728
+ issues.push(contrastIssue(scope, `${name}/bg`, color, bg, TEXT_CONTRAST), contrastIssue(scope, `${name}/surface`, color, surface, TEXT_CONTRAST), contrastIssue(scope, `${name}/surfaceAlt`, color, surfaceAlt, TEXT_CONTRAST));
1729
+ }
1730
+ }
1731
+ for (const frame of FRAMES) {
1732
+ const { bg, raised, ink, muted, glow } = frame.tokens;
1733
+ const scope = `frame:${frame.id}`;
1734
+ issues.push(contrastIssue(scope, 'ink/bg', ink, bg, TEXT_CONTRAST), contrastIssue(scope, 'ink/raised', ink, raised, TEXT_CONTRAST), contrastIssue(scope, 'muted/bg', muted, bg, TEXT_CONTRAST), contrastIssue(scope, 'muted/raised', muted, raised, TEXT_CONTRAST), contrastIssue(scope, 'glow/bg', glow, bg, CONTROL_CONTRAST), contrastIssue(scope, 'glow/raised', glow, raised, CONTROL_CONTRAST), contrastIssue(scope, 'onGlow/glow', readableOn(glow), glow, TEXT_CONTRAST));
1735
+ }
1736
+ for (const canvas of CANVASES) {
1737
+ const { bg, surface, surfaceAlt, text, textMuted } = canvas.tokens;
1738
+ const scope = `canvas:${canvas.id}`;
1739
+ issues.push(contrastIssue(scope, 'text/bg', text, bg, TEXT_CONTRAST), contrastIssue(scope, 'text/surface', text, surface, TEXT_CONTRAST), contrastIssue(scope, 'muted/bg', textMuted, bg, TEXT_CONTRAST), contrastIssue(scope, 'muted/surface', textMuted, surface, TEXT_CONTRAST), contrastIssue(scope, 'muted/surfaceAlt', textMuted, surfaceAlt, TEXT_CONTRAST));
1740
+ }
1741
+ for (const accent of ACCENTS) {
1742
+ for (const canvas of CANVASES) {
1743
+ const color = accentOn(accent, canvas.id);
1744
+ const scope = `accent:${accent.id}/${canvas.id}`;
1745
+ issues.push(contrastIssue(scope, 'accent/bg', color, canvas.tokens.bg, CONTROL_CONTRAST), contrastIssue(scope, 'accent/surface', color, canvas.tokens.surface, CONTROL_CONTRAST), contrastIssue(scope, 'accent/surfaceAlt', color, canvas.tokens.surfaceAlt, CONTROL_CONTRAST), contrastIssue(scope, 'onAccent/accent', readableOn(color), color, TEXT_CONTRAST));
1746
+ }
1747
+ }
1748
+ return issues.filter((issue) => issue !== null);
1749
+ }
1750
+
1751
+ const FRAMES_ES = {
1041
1752
  nucleus: 'Nucleus',
1042
- light: 'Light',
1043
- dark: 'Dark',
1044
- bubblegum: 'Bubblegum',
1045
- 'cool-breeze': 'Cool Breeze',
1046
- 'cool-slate': 'Cool Slate',
1047
- 'icy-mint': 'Icy Mint',
1048
- 'juicy-plum': 'Juicy Plum',
1049
- 'mango-paradise': 'Mango Paradise',
1050
- 'moonlight-glow': 'Moonlight Glow',
1051
- 'mystical-forest': 'Mystical Forest',
1052
- 'silky-pink': 'Silky Pink',
1053
- 'spicy-red': 'Spicy Red',
1054
- 'sunny-day': 'Sunny Day',
1753
+ midnight: 'Medianoche',
1754
+ ocean: 'Océano',
1755
+ forest: 'Bosque',
1756
+ plum: 'Ciruela',
1757
+ ember: 'Brasa',
1758
+ sand: 'Arena',
1759
+ slate: 'Pizarra',
1760
+ ink: 'Tinta',
1761
+ graphite: 'Grafito',
1762
+ paper: 'Papel',
1763
+ mist: 'Bruma',
1055
1764
  };
1056
- const DEFAULT_THEME = 'nucleus';
1057
- const BY_ID = new Map(THEMES.map((theme) => [theme.id, theme]));
1058
- function themeById(id) {
1059
- return BY_ID.get(id) ?? BY_ID.get(DEFAULT_THEME);
1765
+ const FRAMES_EN = {
1766
+ nucleus: 'Nucleus',
1767
+ midnight: 'Midnight',
1768
+ ocean: 'Ocean',
1769
+ forest: 'Forest',
1770
+ plum: 'Plum',
1771
+ ember: 'Ember',
1772
+ sand: 'Sand',
1773
+ slate: 'Slate',
1774
+ ink: 'Ink',
1775
+ graphite: 'Graphite',
1776
+ paper: 'Paper',
1777
+ mist: 'Mist',
1778
+ };
1779
+ const ACCENTS_ES = {
1780
+ auto: 'Del marco',
1781
+ violet: 'Violeta',
1782
+ blue: 'Azul',
1783
+ cyan: 'Cian',
1784
+ green: 'Verde',
1785
+ lime: 'Lima',
1786
+ amber: 'Ámbar',
1787
+ orange: 'Naranja',
1788
+ rose: 'Rosa',
1789
+ red: 'Rojo',
1790
+ };
1791
+ const ACCENTS_EN = {
1792
+ auto: 'From the frame',
1793
+ violet: 'Violet',
1794
+ blue: 'Blue',
1795
+ cyan: 'Cyan',
1796
+ green: 'Green',
1797
+ lime: 'Lime',
1798
+ amber: 'Amber',
1799
+ orange: 'Orange',
1800
+ rose: 'Rose',
1801
+ red: 'Red',
1802
+ };
1803
+ const THEME_COPY = {
1804
+ es: {
1805
+ title: 'Apariencia',
1806
+ frameTitle: 'Marco',
1807
+ frameHint: 'El color de la barra superior y del menú lateral.',
1808
+ canvasTitle: 'Lienzo',
1809
+ canvasHint: 'La superficie donde lees y escribes. Nunca se tiñe.',
1810
+ accentTitle: 'Acción',
1811
+ accentHint: 'El color del botón principal, del foco y de lo seleccionado.',
1812
+ densityTitle: 'Densidad',
1813
+ densityHint: 'Cuántas filas quieres ver de una sola pasada.',
1814
+ frames: FRAMES_ES,
1815
+ canvases: { light: 'Claro', dim: 'Atenuado', dark: 'Oscuro', system: 'Del sistema' },
1816
+ accents: ACCENTS_ES,
1817
+ densities: { comfortable: 'Cómoda', compact: 'Compacta' },
1818
+ },
1819
+ en: {
1820
+ title: 'Appearance',
1821
+ frameTitle: 'Frame',
1822
+ frameHint: 'The color of the top bar and the side menu.',
1823
+ canvasTitle: 'Canvas',
1824
+ canvasHint: 'The surface where you read and type. Never tinted.',
1825
+ accentTitle: 'Action',
1826
+ accentHint: 'The color of the primary button, the focus ring and the selected row.',
1827
+ densityTitle: 'Density',
1828
+ densityHint: 'How many rows you want to see at a glance.',
1829
+ frames: FRAMES_EN,
1830
+ canvases: { light: 'Light', dim: 'Dim', dark: 'Dark', system: 'System' },
1831
+ accents: ACCENTS_EN,
1832
+ densities: { comfortable: 'Comfortable', compact: 'Compact' },
1833
+ },
1834
+ };
1835
+ function themeCopyOf(lang) {
1836
+ return THEME_COPY[lang] ?? THEME_COPY.es;
1060
1837
  }
1061
- function isThemePreference(value) {
1062
- return value === 'system' || (typeof value === 'string' && BY_ID.has(value));
1838
+ function accentLabelOf(lang, id) {
1839
+ return themeCopyOf(lang).accents[id];
1063
1840
  }
1064
1841
 
1065
- const STORAGE_KEY = 'nucleus_theme';
1066
- function readStoredPreference() {
1842
+ const STORAGE_KEY$1 = 'nucleus_appearance';
1843
+ const LEGACY_STORAGE_KEY = 'nucleus_theme';
1844
+ const DARK_QUERY = '(prefers-color-scheme: dark)';
1845
+ function parseAppearance(raw) {
1846
+ if (!raw)
1847
+ return null;
1067
1848
  try {
1068
- const stored = localStorage.getItem(STORAGE_KEY);
1069
- if (isThemePreference(stored))
1070
- return stored;
1849
+ const parsed = JSON.parse(raw);
1850
+ return {
1851
+ frame: isFrameId(parsed.frame) ? parsed.frame : DEFAULT_APPEARANCE.frame,
1852
+ canvas: isCanvasPreference(parsed.canvas) ? parsed.canvas : DEFAULT_APPEARANCE.canvas,
1853
+ accent: isAccentPreference(parsed.accent) ? parsed.accent : DEFAULT_APPEARANCE.accent,
1854
+ density: isDensityId(parsed.density) ? parsed.density : DEFAULT_APPEARANCE.density,
1855
+ };
1856
+ }
1857
+ catch {
1858
+ return null;
1859
+ }
1860
+ }
1861
+ function migrateLegacy(raw) {
1862
+ if (!raw)
1863
+ return null;
1864
+ const legacy = LEGACY_THEMES[raw];
1865
+ return legacy ? { ...DEFAULT_APPEARANCE, ...legacy } : null;
1866
+ }
1867
+ function readStored() {
1868
+ try {
1869
+ const current = parseAppearance(localStorage.getItem(STORAGE_KEY$1));
1870
+ if (current)
1871
+ return current;
1872
+ const migrated = migrateLegacy(localStorage.getItem(LEGACY_STORAGE_KEY));
1873
+ if (migrated) {
1874
+ localStorage.removeItem(LEGACY_STORAGE_KEY);
1875
+ return migrated;
1876
+ }
1071
1877
  }
1072
1878
  catch { }
1073
- return DEFAULT_THEME;
1879
+ return DEFAULT_APPEARANCE;
1074
1880
  }
1075
1881
  function prefersDark() {
1076
- return globalThis.matchMedia?.('(prefers-color-scheme: dark)').matches ?? false;
1882
+ return globalThis.matchMedia?.(DARK_QUERY).matches ?? false;
1077
1883
  }
1078
1884
  class ThemeService {
1079
- _preference = signal(readStoredPreference(), ...(ngDevMode ? [{ debugName: "_preference" }] : /* istanbul ignore next */ []));
1885
+ _appearance = signal(readStored(), ...(ngDevMode ? [{ debugName: "_appearance" }] : /* istanbul ignore next */ []));
1080
1886
  systemDark = signal(prefersDark(), ...(ngDevMode ? [{ debugName: "systemDark" }] : /* istanbul ignore next */ []));
1081
- preference = this._preference.asReadonly();
1082
- theme = computed(() => {
1083
- const preference = this._preference();
1084
- if (preference === 'system')
1085
- return themeById(this.systemDark() ? 'dark' : 'light');
1086
- return themeById(preference);
1087
- }, ...(ngDevMode ? [{ debugName: "theme" }] : /* istanbul ignore next */ []));
1088
- starfield = computed(() => this.theme().starfield, ...(ngDevMode ? [{ debugName: "starfield" }] : /* istanbul ignore next */ []));
1887
+ appearance = this._appearance.asReadonly();
1888
+ frame = computed(() => frameById(this._appearance().frame), ...(ngDevMode ? [{ debugName: "frame" }] : /* istanbul ignore next */ []));
1889
+ canvasId = computed(() => {
1890
+ const preference = this._appearance().canvas;
1891
+ if (preference !== 'system')
1892
+ return preference;
1893
+ return this.systemCanvasId();
1894
+ }, ...(ngDevMode ? [{ debugName: "canvasId" }] : /* istanbul ignore next */ []));
1895
+ systemCanvasId = computed(() => (this.systemDark() ? 'dark' : 'light'), ...(ngDevMode ? [{ debugName: "systemCanvasId" }] : /* istanbul ignore next */ []));
1896
+ canvas = computed(() => canvasById(this.canvasId()), ...(ngDevMode ? [{ debugName: "canvas" }] : /* istanbul ignore next */ []));
1897
+ accent = computed(() => resolveAccent(this.frame(), this._appearance().accent), ...(ngDevMode ? [{ debugName: "accent" }] : /* istanbul ignore next */ []));
1898
+ accentColor = computed(() => accentOn(this.accent(), this.canvasId()), ...(ngDevMode ? [{ debugName: "accentColor" }] : /* istanbul ignore next */ []));
1899
+ density = computed(() => densityById(this._appearance().density), ...(ngDevMode ? [{ debugName: "density" }] : /* istanbul ignore next */ []));
1900
+ starfield = computed(() => this.frame().starfield && isDarkCanvas(this.canvasId()), ...(ngDevMode ? [{ debugName: "starfield" }] : /* istanbul ignore next */ []));
1089
1901
  constructor() {
1090
1902
  globalThis
1091
- .matchMedia?.('(prefers-color-scheme: dark)')
1903
+ .matchMedia?.(DARK_QUERY)
1092
1904
  .addEventListener('change', (event) => this.systemDark.set(event.matches));
1093
1905
  effect(() => {
1094
- const preference = this._preference();
1095
- const theme = this.theme();
1096
- this.applyToDom(theme);
1906
+ const appearance = this._appearance();
1907
+ this.applyToDom();
1097
1908
  try {
1098
- localStorage.setItem(STORAGE_KEY, preference);
1909
+ localStorage.setItem(STORAGE_KEY$1, JSON.stringify(appearance));
1099
1910
  }
1100
1911
  catch { }
1101
1912
  });
1102
1913
  }
1103
- setPreference(preference) {
1104
- this._preference.set(preference);
1914
+ setFrame(frame) {
1915
+ this.patch({ frame });
1916
+ }
1917
+ setCanvas(canvas) {
1918
+ this.patch({ canvas });
1919
+ }
1920
+ setAccent(accent) {
1921
+ this.patch({ accent });
1922
+ }
1923
+ setDensity(density) {
1924
+ this.patch({ density });
1925
+ }
1926
+ reset() {
1927
+ this._appearance.set(DEFAULT_APPEARANCE);
1105
1928
  }
1106
- applyToDom(theme) {
1929
+ patch(change) {
1930
+ this._appearance.update((appearance) => ({ ...appearance, ...change }));
1931
+ }
1932
+ applyToDom() {
1107
1933
  const root = document.documentElement;
1108
- for (const [name, value] of Object.entries(theme.tokens)) {
1109
- root.style.setProperty(`--ac-${name.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`)}`, value);
1934
+ const frame = this.frame();
1935
+ const canvas = this.canvas();
1936
+ const accent = this.accentColor();
1937
+ const density = this.density();
1938
+ for (const [name, value] of Object.entries(frame.tokens)) {
1939
+ root.style.setProperty(`--ns-frame-${kebab(name)}`, value);
1940
+ }
1941
+ root.style.setProperty('--ns-frame-on-glow', readableOn(frame.tokens.glow));
1942
+ for (const [name, value] of Object.entries(canvas.tokens)) {
1943
+ root.style.setProperty(`--ac-${kebab(name)}`, value);
1110
1944
  }
1111
- root.dataset['theme'] = theme.id;
1112
- root.classList.toggle('ns-dark', theme.scheme === 'dark');
1113
- root.style.colorScheme = theme.scheme;
1945
+ root.style.setProperty('--ac-accent', accent);
1946
+ root.style.setProperty('--ac-on-accent', readableOn(accent));
1947
+ root.style.setProperty('--ns-shadow-1', canvas.effects.shadowOne);
1948
+ root.style.setProperty('--ns-shadow-2', canvas.effects.shadowTwo);
1949
+ root.style.setProperty('--ns-header-bg', canvas.effects.headerBg);
1950
+ root.style.setProperty('--ns-header-ink', canvas.effects.headerInk);
1951
+ root.style.setProperty('--ns-header-line', canvas.effects.headerLine);
1952
+ root.style.setProperty('--ns-danger', canvas.effects.danger);
1953
+ root.style.setProperty('--ns-success', canvas.effects.success);
1954
+ root.style.setProperty('--ns-warning', canvas.effects.warning);
1955
+ root.style.setProperty('--ns-accent-wash', mixHex(accent, canvas.tokens.surface, canvas.effects.wash));
1956
+ root.style.setProperty('--ns-accent-wash-strong', mixHex(accent, canvas.tokens.surface, canvas.effects.washStrong));
1957
+ root.style.setProperty('--ns-density', String(density.scale));
1958
+ root.dataset['frame'] = frame.id;
1959
+ root.dataset['canvas'] = canvas.id;
1960
+ root.dataset['density'] = density.id;
1961
+ root.classList.toggle('ns-dark', isDarkCanvas(canvas.id));
1962
+ root.style.colorScheme = isDarkCanvas(canvas.id) ? 'dark' : 'light';
1114
1963
  }
1115
1964
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ThemeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1116
1965
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ThemeService, providedIn: 'root' });
@@ -1119,12 +1968,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
1119
1968
  type: Injectable,
1120
1969
  args: [{ providedIn: 'root' }]
1121
1970
  }], ctorParameters: () => [] });
1971
+ function kebab(name) {
1972
+ return name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
1973
+ }
1122
1974
 
1123
1975
  const MASK = {
1124
1976
  background: 'rgba(0, 0, 0, 0.55)',
1125
1977
  color: 'var(--ac-text)',
1126
1978
  };
1127
- const SHADOW = '0 10px 40px rgba(0, 0, 0, 0.45)';
1979
+ const SHADOW = 'var(--ns-shadow-2, 0 10px 40px rgb(0 0 0 / 45%))';
1128
1980
  const PANEL = {
1129
1981
  background: 'var(--ac-surface)',
1130
1982
  borderColor: 'var(--ac-border)',
@@ -1174,6 +2026,7 @@ const schemeTokens = {
1174
2026
  },
1175
2027
  },
1176
2028
  formField: {
2029
+ paddingY: 'var(--ns-control-y, 0.55rem)',
1177
2030
  background: 'var(--ac-bg)',
1178
2031
  disabledBackground: 'var(--ac-surface-alt)',
1179
2032
  filledBackground: 'var(--ac-surface-alt)',
@@ -1279,9 +2132,9 @@ class NucleusLordicon {
1279
2132
  size = input(48, ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
1280
2133
  tone = input('accent', ...(ngDevMode ? [{ debugName: "tone" }] : /* istanbul ignore next */ []));
1281
2134
  colors = computed(() => {
1282
- const theme = this.themes.theme();
1283
- const primary = this.tone() === 'danger' ? '#e8555e' : theme.tokens.accent;
1284
- return `primary:${primary},secondary:${theme.tokens.text}`;
2135
+ const canvas = this.themes.canvas();
2136
+ const primary = this.tone() === 'danger' ? canvas.effects.danger : this.themes.accentColor();
2137
+ return `primary:${primary},secondary:${canvas.tokens.text}`;
1285
2138
  }, ...(ngDevMode ? [{ debugName: "colors" }] : /* istanbul ignore next */ []));
1286
2139
  constructor() {
1287
2140
  void loadLordicon();
@@ -1316,8 +2169,23 @@ class NucleusCard {
1316
2169
  heading = input('', ...(ngDevMode ? [{ debugName: "heading" }] : /* istanbul ignore next */ []));
1317
2170
  subtitle = input('', ...(ngDevMode ? [{ debugName: "subtitle" }] : /* istanbul ignore next */ []));
1318
2171
  size = input('full', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
2172
+ loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
2173
+ skeletonRows = input(4, ...(ngDevMode ? [{ debugName: "skeletonRows" }] : /* istanbul ignore next */ []));
2174
+ static widths = [
2175
+ ['46%', '22%'],
2176
+ ['62%', '17%'],
2177
+ ['38%', '25%'],
2178
+ ['54%', '19%'],
2179
+ ['44%', '23%'],
2180
+ ['58%', '16%'],
2181
+ ];
2182
+ placeholders = computed(() => Array.from({ length: Math.max(this.skeletonRows(), 1) }, (_, index) => ({
2183
+ index,
2184
+ left: NucleusCard.widths[index % NucleusCard.widths.length][0],
2185
+ right: NucleusCard.widths[index % NucleusCard.widths.length][1],
2186
+ })), ...(ngDevMode ? [{ debugName: "placeholders" }] : /* istanbul ignore next */ []));
1319
2187
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusCard, deps: [], target: i0.ɵɵFactoryTarget.Component });
1320
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: NucleusCard, isStandalone: true, selector: "nucleus-card", inputs: { heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: false, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-size": "size()" } }, ngImport: i0, template: `
2188
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: NucleusCard, isStandalone: true, selector: "nucleus-card", inputs: { heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: false, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, skeletonRows: { classPropertyName: "skeletonRows", publicName: "skeletonRows", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-size": "size()" } }, ngImport: i0, template: `
1321
2189
  @if (heading() || subtitle()) {
1322
2190
  <div class="head">
1323
2191
  @if (heading()) {
@@ -1329,16 +2197,29 @@ class NucleusCard {
1329
2197
  </div>
1330
2198
  }
1331
2199
 
1332
- <ng-content />
2200
+ @if (loading()) {
2201
+ <div class="skeleton-rows">
2202
+ @for (row of placeholders(); track row.index) {
2203
+ <div class="skeleton-row">
2204
+ <p-skeleton height="13px" [width]="row.left" />
2205
+ <p-skeleton height="12px" [width]="row.right" />
2206
+ </div>
2207
+ }
2208
+ </div>
2209
+ }
2210
+
2211
+ <div class="body" [style.display]="loading() ? 'none' : null">
2212
+ <ng-content />
2213
+ </div>
1333
2214
 
1334
2215
  <div class="actions">
1335
2216
  <ng-content select="[nucleusCardActions]" />
1336
2217
  </div>
1337
- `, isInline: true, styles: [":host{display:block;padding:22px 24px;border:1px solid var(--ac-border);border-radius:12px;background:color-mix(in srgb,var(--ac-surface) 70%,transparent)}:host([data-size=\"sm\"]){max-width:34rem}:host([data-size=\"md\"]){max-width:48rem}:host([data-size=\"lg\"]){max-width:64rem}.head{margin-bottom:18px}h2{margin:0;font-size:16px;font-weight:600}.subtitle{margin:4px 0 0;color:var(--ac-text-muted);font-size:13px}.actions{display:flex;justify-content:flex-end;gap:8px;margin-top:20px}.actions:empty{display:none}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2218
+ `, isInline: true, styles: [":host{display:block;padding:22px 24px;border:1px solid var(--ac-border);border-radius:12px;background:var(--ac-surface);box-shadow:var(--ns-shadow-1)}:host([data-size=\"sm\"]){max-width:34rem}:host([data-size=\"md\"]){max-width:48rem}:host([data-size=\"lg\"]){max-width:64rem}.head{margin-bottom:18px}h2{margin:0;font-size:16px;font-weight:600}.subtitle{margin:4px 0 0;color:var(--ac-text-muted);font-size:13px}.body{display:contents}.skeleton-rows{display:grid}.skeleton-row{display:flex;align-items:center;justify-content:space-between;gap:14px;padding:13px 0;border-bottom:1px solid color-mix(in srgb,var(--ac-border) 60%,transparent)}.skeleton-row:last-child{border-bottom:0}.actions{display:flex;justify-content:flex-end;gap:8px;margin-top:20px}.actions:empty{display:none}\n"], dependencies: [{ kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i1$1.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1338
2219
  }
1339
2220
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusCard, decorators: [{
1340
2221
  type: Component,
1341
- args: [{ selector: 'nucleus-card', changeDetection: ChangeDetectionStrategy.OnPush, host: { '[attr.data-size]': 'size()' }, template: `
2222
+ args: [{ selector: 'nucleus-card', imports: [SkeletonModule], changeDetection: ChangeDetectionStrategy.OnPush, host: { '[attr.data-size]': 'size()' }, template: `
1342
2223
  @if (heading() || subtitle()) {
1343
2224
  <div class="head">
1344
2225
  @if (heading()) {
@@ -1350,13 +2231,105 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
1350
2231
  </div>
1351
2232
  }
1352
2233
 
1353
- <ng-content />
2234
+ @if (loading()) {
2235
+ <div class="skeleton-rows">
2236
+ @for (row of placeholders(); track row.index) {
2237
+ <div class="skeleton-row">
2238
+ <p-skeleton height="13px" [width]="row.left" />
2239
+ <p-skeleton height="12px" [width]="row.right" />
2240
+ </div>
2241
+ }
2242
+ </div>
2243
+ }
2244
+
2245
+ <div class="body" [style.display]="loading() ? 'none' : null">
2246
+ <ng-content />
2247
+ </div>
1354
2248
 
1355
2249
  <div class="actions">
1356
2250
  <ng-content select="[nucleusCardActions]" />
1357
2251
  </div>
1358
- `, styles: [":host{display:block;padding:22px 24px;border:1px solid var(--ac-border);border-radius:12px;background:color-mix(in srgb,var(--ac-surface) 70%,transparent)}:host([data-size=\"sm\"]){max-width:34rem}:host([data-size=\"md\"]){max-width:48rem}:host([data-size=\"lg\"]){max-width:64rem}.head{margin-bottom:18px}h2{margin:0;font-size:16px;font-weight:600}.subtitle{margin:4px 0 0;color:var(--ac-text-muted);font-size:13px}.actions{display:flex;justify-content:flex-end;gap:8px;margin-top:20px}.actions:empty{display:none}\n"] }]
1359
- }], propDecorators: { heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required: false }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
2252
+ `, styles: [":host{display:block;padding:22px 24px;border:1px solid var(--ac-border);border-radius:12px;background:var(--ac-surface);box-shadow:var(--ns-shadow-1)}:host([data-size=\"sm\"]){max-width:34rem}:host([data-size=\"md\"]){max-width:48rem}:host([data-size=\"lg\"]){max-width:64rem}.head{margin-bottom:18px}h2{margin:0;font-size:16px;font-weight:600}.subtitle{margin:4px 0 0;color:var(--ac-text-muted);font-size:13px}.body{display:contents}.skeleton-rows{display:grid}.skeleton-row{display:flex;align-items:center;justify-content:space-between;gap:14px;padding:13px 0;border-bottom:1px solid color-mix(in srgb,var(--ac-border) 60%,transparent)}.skeleton-row:last-child{border-bottom:0}.actions{display:flex;justify-content:flex-end;gap:8px;margin-top:20px}.actions:empty{display:none}\n"] }]
2253
+ }], propDecorators: { heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required: false }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], skeletonRows: [{ type: i0.Input, args: [{ isSignal: true, alias: "skeletonRows", required: false }] }] } });
2254
+
2255
+ class NucleusMetrics {
2256
+ loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
2257
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusMetrics, deps: [], target: i0.ɵɵFactoryTarget.Component });
2258
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.22", type: NucleusMetrics, isStandalone: true, selector: "nucleus-metrics", inputs: { loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: '<ng-content />', isInline: true, styles: [":host{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(var(--ns-metric-min, 210px),1fr))}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2259
+ }
2260
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusMetrics, decorators: [{
2261
+ type: Component,
2262
+ args: [{ selector: 'nucleus-metrics', changeDetection: ChangeDetectionStrategy.OnPush, template: '<ng-content />', styles: [":host{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(var(--ns-metric-min, 210px),1fr))}\n"] }]
2263
+ }], propDecorators: { loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }] } });
2264
+
2265
+ class NucleusMetric {
2266
+ group = inject(NucleusMetrics, { optional: true });
2267
+ label = input('', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
2268
+ value = input('', ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
2269
+ hint = input('', ...(ngDevMode ? [{ debugName: "hint" }] : /* istanbul ignore next */ []));
2270
+ link = input('', ...(ngDevMode ? [{ debugName: "link" }] : /* istanbul ignore next */ []));
2271
+ tone = input('plain', ...(ngDevMode ? [{ debugName: "tone" }] : /* istanbul ignore next */ []));
2272
+ loading = input(null, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
2273
+ busy = computed(() => this.loading() ?? this.group?.loading() ?? false, ...(ngDevMode ? [{ debugName: "busy" }] : /* istanbul ignore next */ []));
2274
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusMetric, deps: [], target: i0.ɵɵFactoryTarget.Component });
2275
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: NucleusMetric, isStandalone: true, selector: "nucleus-metric", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, link: { classPropertyName: "link", publicName: "link", isSignal: true, isRequired: false, transformFunction: null }, tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-tone": "tone() === 'accent' ? 'accent' : null" } }, ngImport: i0, template: `
2276
+ @if (link().length) {
2277
+ <a class="tile" [routerLink]="link()">
2278
+ <ng-container [ngTemplateOutlet]="body" />
2279
+ </a>
2280
+ } @else {
2281
+ <div class="tile">
2282
+ <ng-container [ngTemplateOutlet]="body" />
2283
+ </div>
2284
+ }
2285
+
2286
+ <ng-template #body>
2287
+ <span class="label">{{ label() }}</span>
2288
+
2289
+ @if (busy()) {
2290
+ <p-skeleton styleClass="value-skeleton" height="23px" width="92px" />
2291
+ @if (hint().length) {
2292
+ <p-skeleton styleClass="hint-skeleton" height="10px" width="132px" />
2293
+ }
2294
+ } @else {
2295
+ <strong class="value">{{ value() }}</strong>
2296
+ @if (hint().length) {
2297
+ <small class="hint">{{ hint() }}</small>
2298
+ }
2299
+ }
2300
+ </ng-template>
2301
+ `, isInline: true, styles: [":host{display:block}.tile{display:grid;align-content:start;gap:5px;height:100%;padding:18px 20px;border:1px solid var(--ac-border);border-radius:12px;background:var(--ac-surface);color:inherit;text-decoration:none;transition:border-color .14s ease}a.tile:hover{border-color:var(--ac-accent)}:host([data-tone=\"accent\"]) .tile{border-color:var(--ac-accent)}.label{color:var(--ac-text-muted);font-size:11px;font-weight:600;letter-spacing:.04em;text-transform:uppercase}.value{font-size:26px;font-variant-numeric:tabular-nums;line-height:1}.hint{color:var(--ac-text-muted);font-size:12px}.value-skeleton{margin:1px 0 2px}.hint-skeleton{margin:3px 0}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i1$1.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2302
+ }
2303
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusMetric, decorators: [{
2304
+ type: Component,
2305
+ args: [{ selector: 'nucleus-metric', imports: [NgTemplateOutlet, RouterLink, SkeletonModule], changeDetection: ChangeDetectionStrategy.OnPush, host: { '[attr.data-tone]': "tone() === 'accent' ? 'accent' : null" }, template: `
2306
+ @if (link().length) {
2307
+ <a class="tile" [routerLink]="link()">
2308
+ <ng-container [ngTemplateOutlet]="body" />
2309
+ </a>
2310
+ } @else {
2311
+ <div class="tile">
2312
+ <ng-container [ngTemplateOutlet]="body" />
2313
+ </div>
2314
+ }
2315
+
2316
+ <ng-template #body>
2317
+ <span class="label">{{ label() }}</span>
2318
+
2319
+ @if (busy()) {
2320
+ <p-skeleton styleClass="value-skeleton" height="23px" width="92px" />
2321
+ @if (hint().length) {
2322
+ <p-skeleton styleClass="hint-skeleton" height="10px" width="132px" />
2323
+ }
2324
+ } @else {
2325
+ <strong class="value">{{ value() }}</strong>
2326
+ @if (hint().length) {
2327
+ <small class="hint">{{ hint() }}</small>
2328
+ }
2329
+ }
2330
+ </ng-template>
2331
+ `, styles: [":host{display:block}.tile{display:grid;align-content:start;gap:5px;height:100%;padding:18px 20px;border:1px solid var(--ac-border);border-radius:12px;background:var(--ac-surface);color:inherit;text-decoration:none;transition:border-color .14s ease}a.tile:hover{border-color:var(--ac-accent)}:host([data-tone=\"accent\"]) .tile{border-color:var(--ac-accent)}.label{color:var(--ac-text-muted);font-size:11px;font-weight:600;letter-spacing:.04em;text-transform:uppercase}.value{font-size:26px;font-variant-numeric:tabular-nums;line-height:1}.hint{color:var(--ac-text-muted);font-size:12px}.value-skeleton{margin:1px 0 2px}.hint-skeleton{margin:3px 0}\n"] }]
2332
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], link: [{ type: i0.Input, args: [{ isSignal: true, alias: "link", required: false }] }], tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }] } });
1360
2333
 
1361
2334
  class NucleusTabs {
1362
2335
  tabs = input.required(...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
@@ -1465,8 +2438,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
1465
2438
  function resolveFieldSize(field) {
1466
2439
  if (field.size)
1467
2440
  return field.size;
1468
- if (field.kind === 'textarea')
2441
+ if (field.kind === 'textarea' || field.kind === 'color')
1469
2442
  return 'full';
2443
+ if (field.kind === 'icon')
2444
+ return 'md';
1470
2445
  const max = field.maxlength;
1471
2446
  if (max === undefined)
1472
2447
  return 'md';
@@ -1480,13 +2455,343 @@ function resolveFieldSize(field) {
1480
2455
  return 'lg';
1481
2456
  return 'full';
1482
2457
  }
2458
+ function isFieldSearchable(field) {
2459
+ return field.searchable !== false;
2460
+ }
2461
+
2462
+ const NUCLEUS_ICON_CATALOG = [
2463
+ 'address-book', 'align-center', 'align-justify', 'align-left', 'align-right', 'amazon',
2464
+ 'android', 'angle-double-down', 'angle-double-left', 'angle-double-right', 'angle-double-up',
2465
+ 'angle-down', 'angle-left', 'angle-right', 'angle-up', 'apple', 'arrow-circle-down',
2466
+ 'arrow-circle-left', 'arrow-circle-right', 'arrow-circle-up', 'arrow-down', 'arrow-down-left',
2467
+ 'arrow-down-left-and-arrow-up-right-to-center', 'arrow-down-right', 'arrow-left',
2468
+ 'arrow-right', 'arrow-right-arrow-left', 'arrow-u-turn-up-left', 'arrow-u-turn-up-right',
2469
+ 'arrow-up', 'arrow-up-left', 'arrow-up-right',
2470
+ 'arrow-up-right-and-arrow-down-left-from-center', 'arrows-alt', 'arrows-h', 'arrows-v',
2471
+ 'asterisk', 'at', 'backward', 'ban', 'barcode', 'bars', 'bell', 'bell-slash', 'bitcoin',
2472
+ 'block-quote', 'bold', 'bolt', 'book', 'bookmark', 'bookmark-fill', 'box', 'briefcase',
2473
+ 'building', 'building-columns', 'bullseye', 'calculator', 'calendar', 'calendar-clock',
2474
+ 'calendar-minus', 'calendar-plus', 'calendar-times', 'camera', 'car', 'caret-down',
2475
+ 'caret-left', 'caret-right', 'caret-up', 'cart-arrow-down', 'cart-minus', 'cart-plus',
2476
+ 'case-sensitive', 'chart-bar', 'chart-line', 'chart-pie', 'chart-scatter', 'check',
2477
+ 'check-circle', 'check-square', 'chevron-circle-down', 'chevron-circle-left',
2478
+ 'chevron-circle-right', 'chevron-circle-up', 'chevron-down', 'chevron-left', 'chevron-right',
2479
+ 'chevron-up', 'circle', 'circle-fill', 'circle-off', 'circle-on', 'clipboard', 'clock',
2480
+ 'clone', 'cloud', 'cloud-download', 'cloud-upload', 'code', 'code-branch', 'cog', 'columns-2',
2481
+ 'comment', 'comments', 'compass', 'compress', 'copy', 'credit-card', 'crown', 'database',
2482
+ 'delete-left', 'desktop', 'directions', 'directions-alt', 'discord', 'dollar', 'dot',
2483
+ 'download', 'eject', 'ellipsis-h', 'ellipsis-v', 'envelope', 'equals', 'eraser', 'ethereum',
2484
+ 'euro', 'exclamation-circle', 'exclamation-triangle', 'expand', 'external-link', 'eye',
2485
+ 'eye-dropper', 'eye-slash', 'face-smile', 'facebook', 'fast-backward', 'fast-forward', 'file',
2486
+ 'file-arrow-up', 'file-check', 'file-edit', 'file-excel', 'file-export', 'file-import',
2487
+ 'file-o', 'file-pdf', 'file-plus', 'file-word', 'filter', 'filter-fill', 'filter-slash',
2488
+ 'flag', 'flag-fill', 'folder', 'folder-open', 'folder-plus', 'forward', 'gauge', 'gift',
2489
+ 'github', 'globe', 'google', 'graduation-cap', 'grid-2', 'grip', 'grip-horizontal',
2490
+ 'grip-vertical', 'hammer', 'hashtag', 'heading', 'heading-1', 'heading-2', 'heading-3',
2491
+ 'heading-4', 'heading-5', 'heading-6', 'headphones', 'heart', 'heart-fill', 'highlighter',
2492
+ 'history', 'home', 'horizontal-rule', 'hourglass', 'id-card', 'image', 'images', 'inbox',
2493
+ 'indent', 'indian-rupee', 'info', 'info-circle', 'instagram', 'italic', 'key', 'language',
2494
+ 'lightbulb', 'link', 'linkedin', 'list', 'list-check', 'list-ol', 'list-tree', 'lock',
2495
+ 'lock-open', 'map', 'map-marker', 'mars', 'megaphone', 'microchip', 'microchip-ai',
2496
+ 'microphone', 'microsoft', 'minus', 'minus-circle', 'mobile', 'money-bill', 'moon',
2497
+ 'note-sticky', 'objects-column', 'outdent', 'palette', 'paperclip', 'paragraph-left',
2498
+ 'paragraph-right', 'pause', 'pause-circle', 'paypal', 'pen-line', 'pen-to-square', 'pencil',
2499
+ 'percentage', 'phone', 'pinterest', 'play', 'play-circle', 'plus', 'plus-circle', 'pound',
2500
+ 'power-off', 'prime', 'print', 'qrcode', 'question', 'question-circle', 'receipt',
2501
+ 'rectangle-xmark', 'reddit', 'refresh', 'replay', 'reply', 'rows-2', 'save', 'search',
2502
+ 'search-minus', 'search-plus', 'send', 'server', 'share-alt', 'shield', 'shop', 'shopping-bag',
2503
+ 'shopping-cart', 'sidebar', 'sign-in', 'sign-out', 'signature', 'sitemap', 'slack', 'slash',
2504
+ 'sliders-h', 'sliders-v', 'sort', 'sort-alpha-down', 'sort-alpha-down-alt', 'sort-alpha-up',
2505
+ 'sort-alpha-up-alt', 'sort-alt', 'sort-alt-slash', 'sort-amount-down', 'sort-amount-down-alt',
2506
+ 'sort-amount-up', 'sort-amount-up-alt', 'sort-down', 'sort-down-fill', 'sort-numeric-down',
2507
+ 'sort-numeric-down-alt', 'sort-numeric-up', 'sort-numeric-up-alt', 'sort-up', 'sort-up-fill',
2508
+ 'sparkles', 'spinner', 'spinner-dotted', 'square', 'stamp', 'star', 'star-fill', 'star-half',
2509
+ 'star-half-fill', 'step-backward', 'step-backward-alt', 'step-forward', 'step-forward-alt',
2510
+ 'stop', 'stop-circle', 'stopwatch', 'strikethrough', 'subscript', 'sun', 'superscript', 'sync',
2511
+ 'table', 'tablet', 'tag', 'tags', 'telegram', 'text', 'text-color', 'th-large', 'thumbs-down',
2512
+ 'thumbs-down-fill', 'thumbs-up', 'thumbs-up-fill', 'thumbtack', 'ticket', 'tiktok', 'times',
2513
+ 'times-circle', 'trash', 'trophy', 'truck', 'turkish-lira', 'twitch', 'twitter', 'underline',
2514
+ 'undo', 'unlock', 'upload', 'user', 'user-edit', 'user-minus', 'user-plus', 'users', 'venus',
2515
+ 'verified', 'video', 'vimeo', 'volume-down', 'volume-off', 'volume-up', 'wallet', 'warehouse',
2516
+ 'wave-pulse', 'whatsapp', 'wifi', 'window-maximize', 'window-minimize', 'wrench', 'youtube'
2517
+ ];
2518
+ function searchIcons(term) {
2519
+ const needle = term.trim().toLowerCase();
2520
+ if (!needle)
2521
+ return NUCLEUS_ICON_CATALOG;
2522
+ return NUCLEUS_ICON_CATALOG.filter((name) => name.includes(needle));
2523
+ }
2524
+ function iconClassOf(name) {
2525
+ return name ? (name.startsWith('pi ') ? name : `pi pi-${name}`) : '';
2526
+ }
2527
+ function iconNameOf(value) {
2528
+ if (!value)
2529
+ return null;
2530
+ const trimmed = value.trim().replace(/^pi\s+/, '').replace(/^pi-/, '');
2531
+ return trimmed || null;
2532
+ }
2533
+
2534
+ class NucleusIconPicker {
2535
+ language = inject(NucleusLanguage);
2536
+ value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
2537
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
2538
+ inputId = input('', ...(ngDevMode ? [{ debugName: "inputId" }] : /* istanbul ignore next */ []));
2539
+ panel = viewChild.required('popover');
2540
+ search = viewChild('searchBox', ...(ngDevMode ? [{ debugName: "search" }] : /* istanbul ignore next */ []));
2541
+ term = signal('', ...(ngDevMode ? [{ debugName: "term" }] : /* istanbul ignore next */ []));
2542
+ labels = computed(() => commonCopyOf(this.language.lang()), ...(ngDevMode ? [{ debugName: "labels" }] : /* istanbul ignore next */ []));
2543
+ name = computed(() => iconNameOf(this.value()), ...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
2544
+ iconClass = computed(() => iconClassOf(this.name()), ...(ngDevMode ? [{ debugName: "iconClass" }] : /* istanbul ignore next */ []));
2545
+ results = computed(() => searchIcons(this.term()), ...(ngDevMode ? [{ debugName: "results" }] : /* istanbul ignore next */ []));
2546
+ onShow() {
2547
+ this.term.set('');
2548
+ setTimeout(() => this.search()?.nativeElement.focus(), 0);
2549
+ }
2550
+ pick(icon) {
2551
+ this.value.set(icon ? `pi pi-${icon}` : null);
2552
+ this.panel().hide();
2553
+ }
2554
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusIconPicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
2555
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: NucleusIconPicker, isStandalone: true, selector: "nucleus-icon-picker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, inputId: { classPropertyName: "inputId", publicName: "inputId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, viewQueries: [{ propertyName: "panel", first: true, predicate: ["popover"], descendants: true, isSignal: true }, { propertyName: "search", first: true, predicate: ["searchBox"], descendants: true, isSignal: true }], ngImport: i0, template: `
2556
+ @let copy = labels();
2557
+
2558
+ <button
2559
+ type="button"
2560
+ class="trigger"
2561
+ [id]="inputId()"
2562
+ [disabled]="disabled()"
2563
+ [attr.aria-label]="copy.searchIcon"
2564
+ (click)="panel().toggle($event)"
2565
+ >
2566
+ <span class="preview" [class.is-empty]="!name()">
2567
+ <i [class]="name() ? iconClass() : 'pi pi-image'" aria-hidden="true"></i>
2568
+ </span>
2569
+ <span class="name" [class.is-empty]="!name()">{{ name() ?? copy.noIcon }}</span>
2570
+ <i class="pi pi-chevron-down caret" aria-hidden="true"></i>
2571
+ </button>
2572
+
2573
+ <p-popover #popover appendTo="body" [dismissable]="true" (onShow)="onShow()">
2574
+ <div class="panel">
2575
+ <div class="panel-head">
2576
+ <input
2577
+ #searchBox
2578
+ pInputText
2579
+ type="search"
2580
+ autocomplete="off"
2581
+ [placeholder]="copy.searchIcon"
2582
+ [ngModel]="term()"
2583
+ (ngModelChange)="term.set($event)"
2584
+ [ngModelOptions]="{ standalone: true }"
2585
+ />
2586
+ <button type="button" class="clear" (click)="pick(null)">{{ copy.noIcon }}</button>
2587
+ </div>
2588
+
2589
+ @if (results().length) {
2590
+ <div class="grid">
2591
+ @for (icon of results(); track icon) {
2592
+ <button
2593
+ type="button"
2594
+ class="cell"
2595
+ [class.is-active]="icon === name()"
2596
+ [title]="icon"
2597
+ [attr.aria-label]="icon"
2598
+ (click)="pick(icon)"
2599
+ >
2600
+ <i [class]="'pi pi-' + icon" aria-hidden="true"></i>
2601
+ </button>
2602
+ }
2603
+ </div>
2604
+ } @else {
2605
+ <p class="empty">{{ copy.noMatches }}</p>
2606
+ }
2607
+ </div>
2608
+ </p-popover>
2609
+ `, isInline: true, styles: [":host{display:block}.trigger{display:flex;align-items:center;gap:10px;width:100%;padding:0 10px;height:38px;border:1px solid var(--ac-border);border-radius:8px;background:var(--ac-surface);color:var(--ac-text);font:inherit;font-size:14px;text-align:left;cursor:pointer}.trigger:disabled{opacity:.6;cursor:default}.trigger:not(:disabled):hover{border-color:color-mix(in srgb,var(--ac-accent) 55%,var(--ac-border))}.preview{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:6px;background:color-mix(in srgb,var(--ac-accent) 16%,transparent);color:var(--ac-accent);font-size:13px}.preview.is-empty{background:color-mix(in srgb,var(--ac-text) 8%,transparent);color:var(--ac-text-muted)}.name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.name.is-empty{color:var(--ac-text-muted)}.caret{color:var(--ac-text-muted);font-size:11px}.panel{width:min(21rem,80vw)}.panel-head{display:flex;align-items:center;gap:8px;margin-bottom:10px}.panel-head input{flex:1}.clear{padding:0 10px;height:34px;border:1px solid var(--ac-border);border-radius:8px;background:transparent;color:var(--ac-text-muted);font:inherit;font-size:12px;white-space:nowrap;cursor:pointer}.clear:hover{color:var(--ac-text)}.grid{display:grid;grid-template-columns:repeat(8,1fr);gap:4px;overflow-y:auto;max-height:16rem}.cell{display:inline-flex;align-items:center;justify-content:center;aspect-ratio:1;border:1px solid transparent;border-radius:8px;background:transparent;color:var(--ac-text);font-size:15px;cursor:pointer}.cell:hover{background:color-mix(in srgb,var(--ac-text) 10%,transparent)}.cell.is-active{border-color:var(--ac-accent);background:color-mix(in srgb,var(--ac-accent) 18%,transparent);color:var(--ac-accent)}.empty{padding:18px 4px;color:var(--ac-text-muted);font-size:13px;text-align:center}@media(max-width:420px){.grid{grid-template-columns:repeat(6,1fr)}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i2$1.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "component", type: i3.Popover, selector: "p-popover", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions", "motionOptions"], outputs: ["onShow", "onHide"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2610
+ }
2611
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusIconPicker, decorators: [{
2612
+ type: Component,
2613
+ args: [{ selector: 'nucleus-icon-picker', imports: [FormsModule, InputTextModule, PopoverModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
2614
+ @let copy = labels();
2615
+
2616
+ <button
2617
+ type="button"
2618
+ class="trigger"
2619
+ [id]="inputId()"
2620
+ [disabled]="disabled()"
2621
+ [attr.aria-label]="copy.searchIcon"
2622
+ (click)="panel().toggle($event)"
2623
+ >
2624
+ <span class="preview" [class.is-empty]="!name()">
2625
+ <i [class]="name() ? iconClass() : 'pi pi-image'" aria-hidden="true"></i>
2626
+ </span>
2627
+ <span class="name" [class.is-empty]="!name()">{{ name() ?? copy.noIcon }}</span>
2628
+ <i class="pi pi-chevron-down caret" aria-hidden="true"></i>
2629
+ </button>
2630
+
2631
+ <p-popover #popover appendTo="body" [dismissable]="true" (onShow)="onShow()">
2632
+ <div class="panel">
2633
+ <div class="panel-head">
2634
+ <input
2635
+ #searchBox
2636
+ pInputText
2637
+ type="search"
2638
+ autocomplete="off"
2639
+ [placeholder]="copy.searchIcon"
2640
+ [ngModel]="term()"
2641
+ (ngModelChange)="term.set($event)"
2642
+ [ngModelOptions]="{ standalone: true }"
2643
+ />
2644
+ <button type="button" class="clear" (click)="pick(null)">{{ copy.noIcon }}</button>
2645
+ </div>
2646
+
2647
+ @if (results().length) {
2648
+ <div class="grid">
2649
+ @for (icon of results(); track icon) {
2650
+ <button
2651
+ type="button"
2652
+ class="cell"
2653
+ [class.is-active]="icon === name()"
2654
+ [title]="icon"
2655
+ [attr.aria-label]="icon"
2656
+ (click)="pick(icon)"
2657
+ >
2658
+ <i [class]="'pi pi-' + icon" aria-hidden="true"></i>
2659
+ </button>
2660
+ }
2661
+ </div>
2662
+ } @else {
2663
+ <p class="empty">{{ copy.noMatches }}</p>
2664
+ }
2665
+ </div>
2666
+ </p-popover>
2667
+ `, styles: [":host{display:block}.trigger{display:flex;align-items:center;gap:10px;width:100%;padding:0 10px;height:38px;border:1px solid var(--ac-border);border-radius:8px;background:var(--ac-surface);color:var(--ac-text);font:inherit;font-size:14px;text-align:left;cursor:pointer}.trigger:disabled{opacity:.6;cursor:default}.trigger:not(:disabled):hover{border-color:color-mix(in srgb,var(--ac-accent) 55%,var(--ac-border))}.preview{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:6px;background:color-mix(in srgb,var(--ac-accent) 16%,transparent);color:var(--ac-accent);font-size:13px}.preview.is-empty{background:color-mix(in srgb,var(--ac-text) 8%,transparent);color:var(--ac-text-muted)}.name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.name.is-empty{color:var(--ac-text-muted)}.caret{color:var(--ac-text-muted);font-size:11px}.panel{width:min(21rem,80vw)}.panel-head{display:flex;align-items:center;gap:8px;margin-bottom:10px}.panel-head input{flex:1}.clear{padding:0 10px;height:34px;border:1px solid var(--ac-border);border-radius:8px;background:transparent;color:var(--ac-text-muted);font:inherit;font-size:12px;white-space:nowrap;cursor:pointer}.clear:hover{color:var(--ac-text)}.grid{display:grid;grid-template-columns:repeat(8,1fr);gap:4px;overflow-y:auto;max-height:16rem}.cell{display:inline-flex;align-items:center;justify-content:center;aspect-ratio:1;border:1px solid transparent;border-radius:8px;background:transparent;color:var(--ac-text);font-size:15px;cursor:pointer}.cell:hover{background:color-mix(in srgb,var(--ac-text) 10%,transparent)}.cell.is-active{border-color:var(--ac-accent);background:color-mix(in srgb,var(--ac-accent) 18%,transparent);color:var(--ac-accent)}.empty{padding:18px 4px;color:var(--ac-text-muted);font-size:13px;text-align:center}@media(max-width:420px){.grid{grid-template-columns:repeat(6,1fr)}}\n"] }]
2668
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], inputId: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputId", required: false }] }], panel: [{ type: i0.ViewChild, args: ['popover', { isSignal: true }] }], search: [{ type: i0.ViewChild, args: ['searchBox', { isSignal: true }] }] } });
2669
+
2670
+ const NUCLEUS_ACCENTS = ACCENTS.map((accent) => ({
2671
+ id: accent.id,
2672
+ light: accent.light,
2673
+ dark: accent.dark,
2674
+ }));
2675
+ class NucleusColorPicker {
2676
+ language = inject(NucleusLanguage);
2677
+ value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
2678
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
2679
+ ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
2680
+ labels = computed(() => commonCopyOf(this.language.lang()), ...(ngDevMode ? [{ debugName: "labels" }] : /* istanbul ignore next */ []));
2681
+ options = computed(() => {
2682
+ const names = themeCopyOf(this.language.lang()).accents;
2683
+ return NUCLEUS_ACCENTS.map((accent) => ({ color: accent.light, label: names[accent.id] }));
2684
+ }, ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
2685
+ pick(color) {
2686
+ this.value.set(color);
2687
+ }
2688
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusColorPicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
2689
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: NucleusColorPicker, isStandalone: true, selector: "nucleus-color-picker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, ngImport: i0, template: `
2690
+ @let copy = labels();
2691
+
2692
+ <div class="row" role="radiogroup" [attr.aria-label]="ariaLabel() || copy.choose">
2693
+ <button
2694
+ type="button"
2695
+ class="none"
2696
+ role="radio"
2697
+ [class.is-active]="!value()"
2698
+ [attr.aria-checked]="!value()"
2699
+ [attr.aria-label]="copy.noColor"
2700
+ [title]="copy.noColor"
2701
+ [disabled]="disabled()"
2702
+ (click)="pick(null)"
2703
+ >
2704
+ <i class="pi pi-ban" aria-hidden="true"></i>
2705
+ </button>
2706
+
2707
+ @for (option of options(); track option.color) {
2708
+ <button
2709
+ type="button"
2710
+ class="swatch"
2711
+ role="radio"
2712
+ [class.is-active]="option.color === value()"
2713
+ [attr.aria-checked]="option.color === value()"
2714
+ [attr.aria-label]="option.label"
2715
+ [title]="option.label + ' · ' + option.color"
2716
+ [style.background]="option.color"
2717
+ [disabled]="disabled()"
2718
+ (click)="pick(option.color)"
2719
+ >
2720
+ @if (option.color === value()) {
2721
+ <i class="pi pi-check" aria-hidden="true"></i>
2722
+ }
2723
+ </button>
2724
+ }
2725
+ </div>
2726
+ `, isInline: true, styles: [":host{display:block}.row{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-height:38px}.swatch{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;padding:0;border:2px solid transparent;border-radius:50%;color:#fff;cursor:pointer;transition:transform .12s ease}.swatch:hover:not(:disabled){transform:scale(1.12)}.swatch:disabled{cursor:default;opacity:.6}.swatch.is-active{border-color:var(--ac-text);box-shadow:0 0 0 2px var(--ac-bg) inset}.swatch i{font-size:11px;text-shadow:0 0 3px rgba(0,0,0,.5)}.none{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;padding:0;border:1px dashed var(--ac-border);border-radius:50%;background:transparent;color:var(--ac-text-muted);cursor:pointer}.none.is-active{border-style:solid;border-color:var(--ac-text);color:var(--ac-text)}.none i{font-size:11px}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2727
+ }
2728
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusColorPicker, decorators: [{
2729
+ type: Component,
2730
+ args: [{ selector: 'nucleus-color-picker', changeDetection: ChangeDetectionStrategy.OnPush, template: `
2731
+ @let copy = labels();
2732
+
2733
+ <div class="row" role="radiogroup" [attr.aria-label]="ariaLabel() || copy.choose">
2734
+ <button
2735
+ type="button"
2736
+ class="none"
2737
+ role="radio"
2738
+ [class.is-active]="!value()"
2739
+ [attr.aria-checked]="!value()"
2740
+ [attr.aria-label]="copy.noColor"
2741
+ [title]="copy.noColor"
2742
+ [disabled]="disabled()"
2743
+ (click)="pick(null)"
2744
+ >
2745
+ <i class="pi pi-ban" aria-hidden="true"></i>
2746
+ </button>
2747
+
2748
+ @for (option of options(); track option.color) {
2749
+ <button
2750
+ type="button"
2751
+ class="swatch"
2752
+ role="radio"
2753
+ [class.is-active]="option.color === value()"
2754
+ [attr.aria-checked]="option.color === value()"
2755
+ [attr.aria-label]="option.label"
2756
+ [title]="option.label + ' · ' + option.color"
2757
+ [style.background]="option.color"
2758
+ [disabled]="disabled()"
2759
+ (click)="pick(option.color)"
2760
+ >
2761
+ @if (option.color === value()) {
2762
+ <i class="pi pi-check" aria-hidden="true"></i>
2763
+ }
2764
+ </button>
2765
+ }
2766
+ </div>
2767
+ `, styles: [":host{display:block}.row{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-height:38px}.swatch{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;padding:0;border:2px solid transparent;border-radius:50%;color:#fff;cursor:pointer;transition:transform .12s ease}.swatch:hover:not(:disabled){transform:scale(1.12)}.swatch:disabled{cursor:default;opacity:.6}.swatch.is-active{border-color:var(--ac-text);box-shadow:0 0 0 2px var(--ac-bg) inset}.swatch i{font-size:11px;text-shadow:0 0 3px rgba(0,0,0,.5)}.none{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;padding:0;border:1px dashed var(--ac-border);border-radius:50%;background:transparent;color:var(--ac-text-muted);cursor:pointer}.none.is-active{border-style:solid;border-color:var(--ac-text);color:var(--ac-text)}.none i{font-size:11px}\n"] }]
2768
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }] } });
1483
2769
 
1484
2770
  class NucleusForm {
2771
+ language = inject(NucleusLanguage);
2772
+ host = inject(ElementRef);
2773
+ injector = inject(Injector);
1485
2774
  fields = input.required(...(ngDevMode ? [{ debugName: "fields" }] : /* istanbul ignore next */ []));
1486
2775
  value = input.required(...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
1487
2776
  readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
1488
2777
  columns = input(null, ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
2778
+ autofocus = input(false, ...(ngDevMode ? [{ debugName: "autofocus" }] : /* istanbul ignore next */ []));
1489
2779
  valueChange = output();
2780
+ labels = computed(() => commonCopyOf(this.language.lang()), ...(ngDevMode ? [{ debugName: "labels" }] : /* istanbul ignore next */ []));
2781
+ landed = false;
2782
+ constructor() {
2783
+ effect(() => {
2784
+ const ready = this.autofocus() && !this.readonly() && this.fields().length > 0;
2785
+ if (!ready || this.landed)
2786
+ return;
2787
+ this.landed = true;
2788
+ afterNextRender(() => {
2789
+ const first = fieldsIn(this.host.nativeElement)[0];
2790
+ if (first)
2791
+ moveTo(first);
2792
+ }, { injector: this.injector });
2793
+ });
2794
+ }
1490
2795
  read(field) {
1491
2796
  return this.value()[field.key] ?? null;
1492
2797
  }
@@ -1494,6 +2799,10 @@ class NucleusForm {
1494
2799
  const raw = this.value()[field.key];
1495
2800
  return raw === null || raw === undefined ? '' : String(raw);
1496
2801
  }
2802
+ nullableText(field) {
2803
+ const raw = this.value()[field.key];
2804
+ return raw === null || raw === undefined || raw === '' ? null : String(raw);
2805
+ }
1497
2806
  checked(field) {
1498
2807
  return this.value()[field.key] === true;
1499
2808
  }
@@ -1503,6 +2812,9 @@ class NucleusForm {
1503
2812
  isDisabled(field) {
1504
2813
  return this.readonly() || field.disabled === true;
1505
2814
  }
2815
+ isSearchable(field) {
2816
+ return isFieldSearchable(field);
2817
+ }
1506
2818
  fieldClass(field) {
1507
2819
  const span = field.span ?? 1;
1508
2820
  return `is-span-${span === 'full' ? 'full' : span} is-size-${resolveFieldSize(field)}`;
@@ -1518,7 +2830,7 @@ class NucleusForm {
1518
2830
  return field.kind ?? 'text';
1519
2831
  }
1520
2832
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
1521
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: NucleusForm, isStandalone: true, selector: "nucleus-form", inputs: { fields: { classPropertyName: "fields", publicName: "fields", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: "<div class=\"ns-form-grid\" [class]=\"gridClass()\">\n @for (field of fields(); track field.key) {\n <div class=\"ns-field\" [class]=\"fieldClass(field)\">\n @if (kindOf(field) !== 'checkbox' && kindOf(field) !== 'switch') {\n <label [attr.for]=\"'nf-' + field.key\">{{ field.label }}</label>\n }\n\n @switch (kindOf(field)) {\n @case ('textarea') {\n <textarea\n pTextarea\n [id]=\"'nf-' + field.key\"\n [rows]=\"field.rows ?? 3\"\n [ngModel]=\"text(field)\"\n (ngModelChange)=\"write(field, $event)\"\n [disabled]=\"isDisabled(field)\"\n [attr.maxlength]=\"field.maxlength ?? null\"\n [attr.placeholder]=\"field.placeholder ?? null\"\n [ngModelOptions]=\"{ standalone: true }\"\n fluid\n ></textarea>\n }\n @case ('select') {\n <p-select\n [inputId]=\"'nf-' + field.key\"\n [options]=\"optionsOf(field)\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [ngModel]=\"read(field)\"\n (ngModelChange)=\"write(field, $event)\"\n [disabled]=\"isDisabled(field)\"\n [placeholder]=\"field.placeholder ?? ''\"\n [showClear]=\"field.clearable === true\"\n [ngModelOptions]=\"{ standalone: true }\"\n fluid\n />\n }\n @case ('checkbox') {\n <div class=\"ns-inline\">\n <p-checkbox\n [inputId]=\"'nf-' + field.key\"\n [binary]=\"true\"\n [ngModel]=\"checked(field)\"\n (ngModelChange)=\"write(field, $event)\"\n [disabled]=\"isDisabled(field)\"\n [ngModelOptions]=\"{ standalone: true }\"\n />\n <label [attr.for]=\"'nf-' + field.key\" class=\"ns-inline-label\">{{ field.label }}</label>\n </div>\n }\n @case ('switch') {\n <div class=\"ns-inline\">\n <p-toggleswitch\n [inputId]=\"'nf-' + field.key\"\n [ngModel]=\"checked(field)\"\n (ngModelChange)=\"write(field, $event)\"\n [disabled]=\"isDisabled(field)\"\n [ngModelOptions]=\"{ standalone: true }\"\n />\n <label [attr.for]=\"'nf-' + field.key\" class=\"ns-inline-label\">{{ field.label }}</label>\n </div>\n }\n @default {\n <input\n pInputText\n [id]=\"'nf-' + field.key\"\n [type]=\"kindOf(field) === 'number' ? 'number' : kindOf(field)\"\n [ngModel]=\"text(field)\"\n (ngModelChange)=\"write(field, $event)\"\n [disabled]=\"isDisabled(field)\"\n [attr.maxlength]=\"field.maxlength ?? null\"\n [attr.placeholder]=\"field.placeholder ?? null\"\n [ngModelOptions]=\"{ standalone: true }\"\n autocomplete=\"off\"\n fluid\n />\n }\n }\n\n @if (field.hint) {\n <p class=\"ns-hint\">{{ field.hint }}</p>\n }\n </div>\n }\n</div>\n", styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CheckboxModule }, { kind: "component", type: i1$1.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["hostName", "value", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "inputStyle", "styleClass", "inputClass", "indeterminate", "formControl", "checkboxIcon", "readonly", "autofocus", "trueValue", "falseValue", "variant", "size"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i2.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i1.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: TextareaModule }, { kind: "directive", type: i4.Textarea, selector: "[pTextarea], [pInputTextarea]", inputs: ["pTextareaPT", "pTextareaUnstyled", "autoResize", "pSize", "variant", "fluid", "invalid"], outputs: ["onResize"] }, { kind: "ngmodule", type: ToggleSwitchModule }, { kind: "component", type: i5.ToggleSwitch, selector: "p-toggleswitch, p-toggleSwitch, p-toggle-switch", inputs: ["styleClass", "tabindex", "inputId", "readonly", "trueValue", "falseValue", "ariaLabel", "size", "ariaLabelledBy", "autofocus"], outputs: ["onChange"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i6.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i6.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i6.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2833
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: NucleusForm, isStandalone: true, selector: "nucleus-form", inputs: { fields: { classPropertyName: "fields", publicName: "fields", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, autofocus: { classPropertyName: "autofocus", publicName: "autofocus", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, hostDirectives: [{ directive: NucleusEnterAdvance }], ngImport: i0, template: "<div class=\"ns-form-grid\" [class]=\"gridClass()\">\n @for (field of fields(); track field.key) {\n <div class=\"ns-field\" [class]=\"fieldClass(field)\">\n @if (kindOf(field) !== 'checkbox' && kindOf(field) !== 'switch') {\n <label [attr.for]=\"kindOf(field) === 'color' ? null : 'nf-' + field.key\">\n {{ field.label }}\n </label>\n }\n\n @switch (kindOf(field)) {\n @case ('textarea') {\n <textarea\n pTextarea\n [id]=\"'nf-' + field.key\"\n [rows]=\"field.rows ?? 3\"\n [ngModel]=\"text(field)\"\n (ngModelChange)=\"write(field, $event)\"\n [disabled]=\"isDisabled(field)\"\n [attr.maxlength]=\"field.maxlength ?? null\"\n [attr.placeholder]=\"field.placeholder ?? null\"\n [ngModelOptions]=\"{ standalone: true }\"\n fluid\n ></textarea>\n }\n @case ('select') {\n <p-select\n [inputId]=\"'nf-' + field.key\"\n [options]=\"optionsOf(field)\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [ngModel]=\"read(field)\"\n (ngModelChange)=\"write(field, $event)\"\n [disabled]=\"isDisabled(field)\"\n [placeholder]=\"field.placeholder ?? labels().choose\"\n [showClear]=\"field.clearable === true\"\n [filter]=\"isSearchable(field)\"\n filterBy=\"label\"\n [filterPlaceholder]=\"labels().search\"\n [autofocusFilter]=\"true\"\n [resetFilterOnHide]=\"true\"\n [ngModelOptions]=\"{ standalone: true }\"\n fluid\n />\n }\n @case ('icon') {\n <nucleus-icon-picker\n [inputId]=\"'nf-' + field.key\"\n [value]=\"nullableText(field)\"\n (valueChange)=\"write(field, $event)\"\n [disabled]=\"isDisabled(field)\"\n />\n }\n @case ('color') {\n <nucleus-color-picker\n [value]=\"nullableText(field)\"\n (valueChange)=\"write(field, $event)\"\n [disabled]=\"isDisabled(field)\"\n [ariaLabel]=\"field.label\"\n />\n }\n @case ('checkbox') {\n <div class=\"ns-inline\">\n <p-checkbox\n [inputId]=\"'nf-' + field.key\"\n [binary]=\"true\"\n [ngModel]=\"checked(field)\"\n (ngModelChange)=\"write(field, $event)\"\n [disabled]=\"isDisabled(field)\"\n [ngModelOptions]=\"{ standalone: true }\"\n />\n <label [attr.for]=\"'nf-' + field.key\" class=\"ns-inline-label\">{{ field.label }}</label>\n </div>\n }\n @case ('switch') {\n <div class=\"ns-inline\">\n <p-toggleswitch\n [inputId]=\"'nf-' + field.key\"\n [ngModel]=\"checked(field)\"\n (ngModelChange)=\"write(field, $event)\"\n [disabled]=\"isDisabled(field)\"\n [ngModelOptions]=\"{ standalone: true }\"\n />\n <label [attr.for]=\"'nf-' + field.key\" class=\"ns-inline-label\">{{ field.label }}</label>\n </div>\n }\n @default {\n <input\n pInputText\n [id]=\"'nf-' + field.key\"\n [type]=\"kindOf(field) === 'number' ? 'number' : kindOf(field)\"\n [ngModel]=\"text(field)\"\n (ngModelChange)=\"write(field, $event)\"\n [disabled]=\"isDisabled(field)\"\n [attr.maxlength]=\"field.maxlength ?? null\"\n [attr.placeholder]=\"field.placeholder ?? null\"\n [ngModelOptions]=\"{ standalone: true }\"\n autocomplete=\"off\"\n fluid\n />\n }\n }\n\n @if (field.hint) {\n <p class=\"ns-hint\">{{ field.hint }}</p>\n }\n </div>\n }\n</div>\n", styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CheckboxModule }, { kind: "component", type: i2$2.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["hostName", "value", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "inputStyle", "styleClass", "inputClass", "indeterminate", "formControl", "checkboxIcon", "readonly", "autofocus", "trueValue", "falseValue", "variant", "size"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i2$1.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i2.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: TextareaModule }, { kind: "directive", type: i5.Textarea, selector: "[pTextarea], [pInputTextarea]", inputs: ["pTextareaPT", "pTextareaUnstyled", "autoResize", "pSize", "variant", "fluid", "invalid"], outputs: ["onResize"] }, { kind: "ngmodule", type: ToggleSwitchModule }, { kind: "component", type: i6.ToggleSwitch, selector: "p-toggleswitch, p-toggleSwitch, p-toggle-switch", inputs: ["styleClass", "tabindex", "inputId", "readonly", "trueValue", "falseValue", "ariaLabel", "size", "ariaLabelledBy", "autofocus"], outputs: ["onChange"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: NucleusColorPicker, selector: "nucleus-color-picker", inputs: ["value", "disabled", "ariaLabel"], outputs: ["valueChange"] }, { kind: "component", type: NucleusIconPicker, selector: "nucleus-icon-picker", inputs: ["value", "disabled", "inputId"], outputs: ["valueChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1522
2834
  }
1523
2835
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusForm, decorators: [{
1524
2836
  type: Component,
@@ -1529,8 +2841,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
1529
2841
  TextareaModule,
1530
2842
  ToggleSwitchModule,
1531
2843
  FormsModule,
1532
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"ns-form-grid\" [class]=\"gridClass()\">\n @for (field of fields(); track field.key) {\n <div class=\"ns-field\" [class]=\"fieldClass(field)\">\n @if (kindOf(field) !== 'checkbox' && kindOf(field) !== 'switch') {\n <label [attr.for]=\"'nf-' + field.key\">{{ field.label }}</label>\n }\n\n @switch (kindOf(field)) {\n @case ('textarea') {\n <textarea\n pTextarea\n [id]=\"'nf-' + field.key\"\n [rows]=\"field.rows ?? 3\"\n [ngModel]=\"text(field)\"\n (ngModelChange)=\"write(field, $event)\"\n [disabled]=\"isDisabled(field)\"\n [attr.maxlength]=\"field.maxlength ?? null\"\n [attr.placeholder]=\"field.placeholder ?? null\"\n [ngModelOptions]=\"{ standalone: true }\"\n fluid\n ></textarea>\n }\n @case ('select') {\n <p-select\n [inputId]=\"'nf-' + field.key\"\n [options]=\"optionsOf(field)\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [ngModel]=\"read(field)\"\n (ngModelChange)=\"write(field, $event)\"\n [disabled]=\"isDisabled(field)\"\n [placeholder]=\"field.placeholder ?? ''\"\n [showClear]=\"field.clearable === true\"\n [ngModelOptions]=\"{ standalone: true }\"\n fluid\n />\n }\n @case ('checkbox') {\n <div class=\"ns-inline\">\n <p-checkbox\n [inputId]=\"'nf-' + field.key\"\n [binary]=\"true\"\n [ngModel]=\"checked(field)\"\n (ngModelChange)=\"write(field, $event)\"\n [disabled]=\"isDisabled(field)\"\n [ngModelOptions]=\"{ standalone: true }\"\n />\n <label [attr.for]=\"'nf-' + field.key\" class=\"ns-inline-label\">{{ field.label }}</label>\n </div>\n }\n @case ('switch') {\n <div class=\"ns-inline\">\n <p-toggleswitch\n [inputId]=\"'nf-' + field.key\"\n [ngModel]=\"checked(field)\"\n (ngModelChange)=\"write(field, $event)\"\n [disabled]=\"isDisabled(field)\"\n [ngModelOptions]=\"{ standalone: true }\"\n />\n <label [attr.for]=\"'nf-' + field.key\" class=\"ns-inline-label\">{{ field.label }}</label>\n </div>\n }\n @default {\n <input\n pInputText\n [id]=\"'nf-' + field.key\"\n [type]=\"kindOf(field) === 'number' ? 'number' : kindOf(field)\"\n [ngModel]=\"text(field)\"\n (ngModelChange)=\"write(field, $event)\"\n [disabled]=\"isDisabled(field)\"\n [attr.maxlength]=\"field.maxlength ?? null\"\n [attr.placeholder]=\"field.placeholder ?? null\"\n [ngModelOptions]=\"{ standalone: true }\"\n autocomplete=\"off\"\n fluid\n />\n }\n }\n\n @if (field.hint) {\n <p class=\"ns-hint\">{{ field.hint }}</p>\n }\n </div>\n }\n</div>\n", styles: [":host{display:block}\n"] }]
1533
- }], propDecorators: { fields: [{ type: i0.Input, args: [{ isSignal: true, alias: "fields", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }] } });
2844
+ NucleusColorPicker,
2845
+ NucleusIconPicker,
2846
+ ], hostDirectives: [NucleusEnterAdvance], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"ns-form-grid\" [class]=\"gridClass()\">\n @for (field of fields(); track field.key) {\n <div class=\"ns-field\" [class]=\"fieldClass(field)\">\n @if (kindOf(field) !== 'checkbox' && kindOf(field) !== 'switch') {\n <label [attr.for]=\"kindOf(field) === 'color' ? null : 'nf-' + field.key\">\n {{ field.label }}\n </label>\n }\n\n @switch (kindOf(field)) {\n @case ('textarea') {\n <textarea\n pTextarea\n [id]=\"'nf-' + field.key\"\n [rows]=\"field.rows ?? 3\"\n [ngModel]=\"text(field)\"\n (ngModelChange)=\"write(field, $event)\"\n [disabled]=\"isDisabled(field)\"\n [attr.maxlength]=\"field.maxlength ?? null\"\n [attr.placeholder]=\"field.placeholder ?? null\"\n [ngModelOptions]=\"{ standalone: true }\"\n fluid\n ></textarea>\n }\n @case ('select') {\n <p-select\n [inputId]=\"'nf-' + field.key\"\n [options]=\"optionsOf(field)\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [ngModel]=\"read(field)\"\n (ngModelChange)=\"write(field, $event)\"\n [disabled]=\"isDisabled(field)\"\n [placeholder]=\"field.placeholder ?? labels().choose\"\n [showClear]=\"field.clearable === true\"\n [filter]=\"isSearchable(field)\"\n filterBy=\"label\"\n [filterPlaceholder]=\"labels().search\"\n [autofocusFilter]=\"true\"\n [resetFilterOnHide]=\"true\"\n [ngModelOptions]=\"{ standalone: true }\"\n fluid\n />\n }\n @case ('icon') {\n <nucleus-icon-picker\n [inputId]=\"'nf-' + field.key\"\n [value]=\"nullableText(field)\"\n (valueChange)=\"write(field, $event)\"\n [disabled]=\"isDisabled(field)\"\n />\n }\n @case ('color') {\n <nucleus-color-picker\n [value]=\"nullableText(field)\"\n (valueChange)=\"write(field, $event)\"\n [disabled]=\"isDisabled(field)\"\n [ariaLabel]=\"field.label\"\n />\n }\n @case ('checkbox') {\n <div class=\"ns-inline\">\n <p-checkbox\n [inputId]=\"'nf-' + field.key\"\n [binary]=\"true\"\n [ngModel]=\"checked(field)\"\n (ngModelChange)=\"write(field, $event)\"\n [disabled]=\"isDisabled(field)\"\n [ngModelOptions]=\"{ standalone: true }\"\n />\n <label [attr.for]=\"'nf-' + field.key\" class=\"ns-inline-label\">{{ field.label }}</label>\n </div>\n }\n @case ('switch') {\n <div class=\"ns-inline\">\n <p-toggleswitch\n [inputId]=\"'nf-' + field.key\"\n [ngModel]=\"checked(field)\"\n (ngModelChange)=\"write(field, $event)\"\n [disabled]=\"isDisabled(field)\"\n [ngModelOptions]=\"{ standalone: true }\"\n />\n <label [attr.for]=\"'nf-' + field.key\" class=\"ns-inline-label\">{{ field.label }}</label>\n </div>\n }\n @default {\n <input\n pInputText\n [id]=\"'nf-' + field.key\"\n [type]=\"kindOf(field) === 'number' ? 'number' : kindOf(field)\"\n [ngModel]=\"text(field)\"\n (ngModelChange)=\"write(field, $event)\"\n [disabled]=\"isDisabled(field)\"\n [attr.maxlength]=\"field.maxlength ?? null\"\n [attr.placeholder]=\"field.placeholder ?? null\"\n [ngModelOptions]=\"{ standalone: true }\"\n autocomplete=\"off\"\n fluid\n />\n }\n }\n\n @if (field.hint) {\n <p class=\"ns-hint\">{{ field.hint }}</p>\n }\n </div>\n }\n</div>\n", styles: [":host{display:block}\n"] }]
2847
+ }], ctorParameters: () => [], propDecorators: { fields: [{ type: i0.Input, args: [{ isSignal: true, alias: "fields", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], autofocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "autofocus", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }] } });
1534
2848
 
1535
2849
  class NucleusCell {
1536
2850
  nucleusCell = input.required(...(ngDevMode ? [{ debugName: "nucleusCell" }] : /* istanbul ignore next */ []));
@@ -1552,12 +2866,24 @@ class NucleusList {
1552
2866
  empty = input('', ...(ngDevMode ? [{ debugName: "empty" }] : /* istanbul ignore next */ []));
1553
2867
  loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
1554
2868
  minWidth = input('640px', ...(ngDevMode ? [{ debugName: "minWidth" }] : /* istanbul ignore next */ []));
2869
+ skeletonRows = input(5, ...(ngDevMode ? [{ debugName: "skeletonRows" }] : /* istanbul ignore next */ []));
1555
2870
  rowClass = input(() => '', ...(ngDevMode ? [{ debugName: "rowClass" }] : /* istanbul ignore next */ []));
1556
2871
  action = output();
1557
2872
  toggle = output();
1558
2873
  cells = contentChildren(NucleusCell, ...(ngDevMode ? [{ debugName: "cells" }] : /* istanbul ignore next */ []));
1559
2874
  tableStyle = computed(() => ({ 'min-width': this.minWidth() }), ...(ngDevMode ? [{ debugName: "tableStyle" }] : /* istanbul ignore next */ []));
1560
2875
  showEmpty = computed(() => !this.loading() && this.rows().length === 0, ...(ngDevMode ? [{ debugName: "showEmpty" }] : /* istanbul ignore next */ []));
2876
+ placeholders = computed(() => Array.from({ length: Math.max(this.skeletonRows(), 1) }, (_, index) => ({ index })), ...(ngDevMode ? [{ debugName: "placeholders" }] : /* istanbul ignore next */ []));
2877
+ data = computed(() => this.loading() ? this.placeholders() : this.rows(), ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
2878
+ skeletonWidth(column) {
2879
+ if (column.kind === 'switch')
2880
+ return '84px';
2881
+ if (column.align === 'right')
2882
+ return '40px';
2883
+ if (column.width)
2884
+ return '60%';
2885
+ return column.strong ? '58%' : '78%';
2886
+ }
1561
2887
  cellTemplate(key) {
1562
2888
  return this.cells().find((cell) => cell.nucleusCell() === key)?.template ?? null;
1563
2889
  }
@@ -1573,6 +2899,9 @@ class NucleusList {
1573
2899
  visibleActions(row) {
1574
2900
  return this.actions().filter((entry) => !entry.visible || entry.visible(row));
1575
2901
  }
2902
+ isActionLocked(entry, row) {
2903
+ return entry.disabled ? entry.disabled(row) : false;
2904
+ }
1576
2905
  run(entry, row) {
1577
2906
  this.action.emit({ id: entry.id, row });
1578
2907
  }
@@ -1591,12 +2920,37 @@ class NucleusList {
1591
2920
  this.toggle.emit({ key: column.key, row, value: !this.isChecked(column, row) });
1592
2921
  }
1593
2922
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusList, deps: [], target: i0.ɵɵFactoryTarget.Component });
1594
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: NucleusList, isStandalone: true, selector: "nucleus-list", inputs: { rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: true, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: true, transformFunction: null }, actions: { classPropertyName: "actions", publicName: "actions", isSignal: true, isRequired: false, transformFunction: null }, empty: { classPropertyName: "empty", publicName: "empty", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, minWidth: { classPropertyName: "minWidth", publicName: "minWidth", isSignal: true, isRequired: false, transformFunction: null }, rowClass: { classPropertyName: "rowClass", publicName: "rowClass", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { action: "action", toggle: "toggle" }, queries: [{ propertyName: "cells", predicate: NucleusCell, isSignal: true }], ngImport: i0, template: "<div class=\"head\">\n <ng-content select=\"[nucleusListTitle]\" />\n <div class=\"head-actions\">\n <ng-content select=\"[nucleusListActions]\" />\n </div>\n</div>\n\n@if (showEmpty()) {\n <div class=\"placeholder\">{{ empty() }}</div>\n} @else {\n <p-table [value]=\"rows()\" styleClass=\"ns-table\" [tableStyle]=\"tableStyle()\">\n <ng-template #header>\n <tr>\n @for (column of columns(); track column.key) {\n <th [class]=\"'align-' + (column.align ?? 'left')\" [style.width]=\"column.width\">\n {{ column.header }}\n </th>\n }\n @if (actions().length) {\n <th class=\"align-right\"></th>\n }\n </tr>\n </ng-template>\n\n <ng-template #body let-row>\n <tr [class]=\"rowClass()(row)\">\n @for (column of columns(); track column.key) {\n <td [class]=\"'align-' + (column.align ?? 'left')\" [class.strong]=\"column.strong\">\n @if (cellTemplate(column.key); as custom) {\n <ng-container\n [ngTemplateOutlet]=\"custom\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column }\"\n />\n } @else if (column.kind === 'switch') {\n <span class=\"switch-cell\">\n <button\n type=\"button\"\n class=\"ns-switch\"\n role=\"switch\"\n [class.is-on]=\"isChecked(column, row)\"\n [attr.aria-checked]=\"isChecked(column, row)\"\n [attr.aria-label]=\"text(column, row)\"\n [disabled]=\"isLocked(column, row)\"\n [pTooltip]=\"hintOf(column, row)\"\n tooltipPosition=\"top\"\n (click)=\"flip(column, row)\"\n >\n <span class=\"knob\"></span>\n </button>\n <span class=\"switch-label\">{{ text(column, row) }}</span>\n </span>\n } @else {\n {{ text(column, row) }}\n @if (tagOf(column, row); as tag) {\n <p-tag [value]=\"tag.label\" [severity]=\"tag.severity ?? 'secondary'\" />\n }\n }\n </td>\n }\n\n @if (actions().length) {\n <td class=\"align-right actions\">\n @for (entry of visibleActions(row); track entry.id) {\n <p-button\n [icon]=\"entry.icon\"\n [severity]=\"entry.tone === 'danger' ? 'danger' : 'secondary'\"\n size=\"small\"\n [text]=\"true\"\n [rounded]=\"true\"\n [pTooltip]=\"entry.label\"\n tooltipPosition=\"left\"\n [ariaLabel]=\"entry.label\"\n (onClick)=\"run(entry, row)\"\n />\n }\n </td>\n }\n </tr>\n </ng-template>\n </p-table>\n}\n", styles: [":host{display:block}.head{display:flex;min-height:34px;align-items:center;justify-content:space-between;gap:12px;margin-bottom:12px}.head-actions{display:flex;gap:8px}.placeholder{padding:26px;border:1px dashed var(--ac-border);border-radius:10px;color:var(--ac-text-muted);font-size:14px}.align-left{text-align:left}.align-center{text-align:center}.align-right{text-align:right}.strong{font-weight:600}.actions{white-space:nowrap}.switch-cell{display:inline-flex;align-items:center;gap:10px}.switch-label{color:var(--ac-text-muted);font-size:12px}.ns-switch{position:relative;width:38px;height:22px;flex:none;padding:0;border:1px solid var(--ac-border);border-radius:var(--ac-radius-pill);background:color-mix(in srgb,var(--ac-text) 10%,transparent);cursor:pointer;transition:background .14s ease,border-color .14s ease}.ns-switch .knob{position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:var(--ac-text);transition:transform .14s ease}.ns-switch.is-on{border-color:var(--ac-accent);background:var(--ac-accent)}.ns-switch.is-on .knob{background:var(--ac-on-accent);transform:translate(16px)}.ns-switch:disabled{cursor:not-allowed;opacity:.45}.ns-switch:focus-visible{outline:2px solid var(--ac-accent);outline-offset:2px}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: TableModule }, { kind: "component", type: i2$1.Table, selector: "p-table", inputs: ["frozenColumns", "frozenValue", "styleClass", "tableStyle", "tableStyleClass", "paginator", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "paginatorDropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showJumpToPageInput", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "selectionMode", "selectionPageOnly", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "rowSelectable", "rowTrackBy", "lazy", "lazyLoadOnInit", "compareSelectionBy", "csvSeparator", "exportFilename", "filters", "globalFilterFields", "filterDelay", "filterLocale", "expandedRowKeys", "editingRowKeys", "rowExpandMode", "scrollable", "rowGroupMode", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "contextMenu", "resizableColumns", "columnResizeMode", "reorderableColumns", "loading", "loadingIcon", "showLoader", "rowHover", "customSort", "showInitialSortBadge", "exportFunction", "exportHeader", "stateKey", "stateStorage", "editMode", "groupRowsBy", "size", "showGridlines", "stripedRows", "groupRowsByOrder", "responsiveLayout", "breakpoint", "paginatorLocale", "value", "columns", "first", "rows", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "selectAll"], outputs: ["contextMenuSelectionChange", "selectAllChange", "selectionChange", "onRowSelect", "onRowUnselect", "onPage", "onSort", "onFilter", "onLazyLoad", "onRowExpand", "onRowCollapse", "onContextMenuSelect", "onColResize", "onColReorder", "onRowReorder", "onEditInit", "onEditComplete", "onEditCancel", "onHeaderCheckboxToggle", "sortFunction", "firstChange", "rowsChange", "onStateSave", "onStateRestore"] }, { kind: "ngmodule", type: TagModule }, { kind: "component", type: i3.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i4$1.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2923
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: NucleusList, isStandalone: true, selector: "nucleus-list", inputs: { rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: true, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: true, transformFunction: null }, actions: { classPropertyName: "actions", publicName: "actions", isSignal: true, isRequired: false, transformFunction: null }, empty: { classPropertyName: "empty", publicName: "empty", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, minWidth: { classPropertyName: "minWidth", publicName: "minWidth", isSignal: true, isRequired: false, transformFunction: null }, skeletonRows: { classPropertyName: "skeletonRows", publicName: "skeletonRows", isSignal: true, isRequired: false, transformFunction: null }, rowClass: { classPropertyName: "rowClass", publicName: "rowClass", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { action: "action", toggle: "toggle" }, queries: [{ propertyName: "cells", predicate: NucleusCell, isSignal: true }], ngImport: i0, template: "<div class=\"head\">\n <ng-content select=\"[nucleusListTitle]\" />\n <div class=\"head-actions\">\n <ng-content select=\"[nucleusListActions]\" />\n </div>\n</div>\n\n@if (showEmpty()) {\n <div class=\"placeholder\">{{ empty() }}</div>\n} @else {\n <p-table [value]=\"data()\" styleClass=\"ns-table\" [tableStyle]=\"tableStyle()\">\n <ng-template #header>\n <tr>\n @for (column of columns(); track column.key) {\n <th [class]=\"'align-' + (column.align ?? 'left')\" [style.width]=\"column.width\">\n {{ column.header }}\n </th>\n }\n @if (actions().length) {\n <th class=\"align-right\"></th>\n }\n </tr>\n </ng-template>\n\n <ng-template #body let-row>\n @if (loading()) {\n <tr class=\"is-skeleton\">\n @for (column of columns(); track column.key) {\n <td [class]=\"'align-' + (column.align ?? 'left')\">\n <p-skeleton height=\"13px\" [width]=\"skeletonWidth(column)\" />\n </td>\n }\n @if (actions().length) {\n <td class=\"align-right actions\">\n <p-skeleton shape=\"circle\" size=\"26px\" />\n </td>\n }\n </tr>\n } @else {\n <tr [class]=\"rowClass()(row)\">\n @for (column of columns(); track column.key) {\n <td [class]=\"'align-' + (column.align ?? 'left')\" [class.strong]=\"column.strong\">\n @if (cellTemplate(column.key); as custom) {\n <ng-container\n [ngTemplateOutlet]=\"custom\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column }\"\n />\n } @else if (column.kind === 'switch') {\n <span class=\"switch-cell\">\n <button\n type=\"button\"\n class=\"ns-switch\"\n role=\"switch\"\n [class.is-on]=\"isChecked(column, row)\"\n [attr.aria-checked]=\"isChecked(column, row)\"\n [attr.aria-label]=\"text(column, row)\"\n [disabled]=\"isLocked(column, row)\"\n [pTooltip]=\"hintOf(column, row)\"\n tooltipPosition=\"top\"\n (click)=\"flip(column, row)\"\n >\n <span class=\"knob\"></span>\n </button>\n <span class=\"switch-label\">{{ text(column, row) }}</span>\n </span>\n } @else {\n {{ text(column, row) }}\n @if (tagOf(column, row); as tag) {\n <p-tag [value]=\"tag.label\" [severity]=\"tag.severity ?? 'secondary'\" />\n }\n }\n </td>\n }\n\n @if (actions().length) {\n <td class=\"align-right actions\">\n @for (entry of visibleActions(row); track entry.id) {\n <p-button\n [icon]=\"entry.icon\"\n [severity]=\"entry.tone === 'danger' ? 'danger' : 'secondary'\"\n size=\"small\"\n [text]=\"true\"\n [rounded]=\"true\"\n [pTooltip]=\"entry.label\"\n tooltipPosition=\"left\"\n [ariaLabel]=\"entry.label\"\n [disabled]=\"isActionLocked(entry, row)\"\n (onClick)=\"run(entry, row)\"\n />\n }\n </td>\n }\n </tr>\n }\n </ng-template>\n </p-table>\n}\n", styles: [":host{display:block}.head{display:flex;min-height:34px;align-items:center;justify-content:space-between;gap:12px;margin-bottom:12px}.head-actions{display:flex;gap:8px}.placeholder{padding:26px;border:1px dashed var(--ac-border);border-radius:10px;color:var(--ac-text-muted);font-size:14px}.align-left{text-align:left}.align-center{text-align:center}.align-right{text-align:right}.strong{font-weight:600}.actions{white-space:nowrap}.switch-cell{display:inline-flex;align-items:center;gap:10px}.switch-label{color:var(--ac-text-muted);font-size:12px}.ns-switch{position:relative;width:38px;height:22px;flex:none;padding:0;border:1px solid var(--ac-border);border-radius:var(--ac-radius-pill);background:color-mix(in srgb,var(--ac-text) 10%,transparent);cursor:pointer;transition:background .14s ease,border-color .14s ease}.ns-switch .knob{position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:var(--ac-text);transition:transform .14s ease}.ns-switch.is-on{border-color:var(--ac-accent);background:var(--ac-accent)}.ns-switch.is-on .knob{background:var(--ac-on-accent);transform:translate(16px)}.ns-switch:disabled{cursor:not-allowed;opacity:.45}.ns-switch:focus-visible{outline:2px solid var(--ac-accent);outline-offset:2px}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i1$1.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "ngmodule", type: TableModule }, { kind: "component", type: i3$1.Table, selector: "p-table", inputs: ["frozenColumns", "frozenValue", "styleClass", "tableStyle", "tableStyleClass", "paginator", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "paginatorDropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showJumpToPageInput", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "selectionMode", "selectionPageOnly", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "rowSelectable", "rowTrackBy", "lazy", "lazyLoadOnInit", "compareSelectionBy", "csvSeparator", "exportFilename", "filters", "globalFilterFields", "filterDelay", "filterLocale", "expandedRowKeys", "editingRowKeys", "rowExpandMode", "scrollable", "rowGroupMode", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "contextMenu", "resizableColumns", "columnResizeMode", "reorderableColumns", "loading", "loadingIcon", "showLoader", "rowHover", "customSort", "showInitialSortBadge", "exportFunction", "exportHeader", "stateKey", "stateStorage", "editMode", "groupRowsBy", "size", "showGridlines", "stripedRows", "groupRowsByOrder", "responsiveLayout", "breakpoint", "paginatorLocale", "value", "columns", "first", "rows", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "selectAll"], outputs: ["contextMenuSelectionChange", "selectAllChange", "selectionChange", "onRowSelect", "onRowUnselect", "onPage", "onSort", "onFilter", "onLazyLoad", "onRowExpand", "onRowCollapse", "onContextMenuSelect", "onColResize", "onColReorder", "onRowReorder", "onEditInit", "onEditComplete", "onEditCancel", "onHeaderCheckboxToggle", "sortFunction", "firstChange", "rowsChange", "onStateSave", "onStateRestore"] }, { kind: "ngmodule", type: TagModule }, { kind: "component", type: i4.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i5$1.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1595
2924
  }
1596
2925
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusList, decorators: [{
1597
2926
  type: Component,
1598
- args: [{ selector: 'nucleus-list', imports: [NgTemplateOutlet, ButtonModule, TableModule, TagModule, TooltipModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"head\">\n <ng-content select=\"[nucleusListTitle]\" />\n <div class=\"head-actions\">\n <ng-content select=\"[nucleusListActions]\" />\n </div>\n</div>\n\n@if (showEmpty()) {\n <div class=\"placeholder\">{{ empty() }}</div>\n} @else {\n <p-table [value]=\"rows()\" styleClass=\"ns-table\" [tableStyle]=\"tableStyle()\">\n <ng-template #header>\n <tr>\n @for (column of columns(); track column.key) {\n <th [class]=\"'align-' + (column.align ?? 'left')\" [style.width]=\"column.width\">\n {{ column.header }}\n </th>\n }\n @if (actions().length) {\n <th class=\"align-right\"></th>\n }\n </tr>\n </ng-template>\n\n <ng-template #body let-row>\n <tr [class]=\"rowClass()(row)\">\n @for (column of columns(); track column.key) {\n <td [class]=\"'align-' + (column.align ?? 'left')\" [class.strong]=\"column.strong\">\n @if (cellTemplate(column.key); as custom) {\n <ng-container\n [ngTemplateOutlet]=\"custom\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column }\"\n />\n } @else if (column.kind === 'switch') {\n <span class=\"switch-cell\">\n <button\n type=\"button\"\n class=\"ns-switch\"\n role=\"switch\"\n [class.is-on]=\"isChecked(column, row)\"\n [attr.aria-checked]=\"isChecked(column, row)\"\n [attr.aria-label]=\"text(column, row)\"\n [disabled]=\"isLocked(column, row)\"\n [pTooltip]=\"hintOf(column, row)\"\n tooltipPosition=\"top\"\n (click)=\"flip(column, row)\"\n >\n <span class=\"knob\"></span>\n </button>\n <span class=\"switch-label\">{{ text(column, row) }}</span>\n </span>\n } @else {\n {{ text(column, row) }}\n @if (tagOf(column, row); as tag) {\n <p-tag [value]=\"tag.label\" [severity]=\"tag.severity ?? 'secondary'\" />\n }\n }\n </td>\n }\n\n @if (actions().length) {\n <td class=\"align-right actions\">\n @for (entry of visibleActions(row); track entry.id) {\n <p-button\n [icon]=\"entry.icon\"\n [severity]=\"entry.tone === 'danger' ? 'danger' : 'secondary'\"\n size=\"small\"\n [text]=\"true\"\n [rounded]=\"true\"\n [pTooltip]=\"entry.label\"\n tooltipPosition=\"left\"\n [ariaLabel]=\"entry.label\"\n (onClick)=\"run(entry, row)\"\n />\n }\n </td>\n }\n </tr>\n </ng-template>\n </p-table>\n}\n", styles: [":host{display:block}.head{display:flex;min-height:34px;align-items:center;justify-content:space-between;gap:12px;margin-bottom:12px}.head-actions{display:flex;gap:8px}.placeholder{padding:26px;border:1px dashed var(--ac-border);border-radius:10px;color:var(--ac-text-muted);font-size:14px}.align-left{text-align:left}.align-center{text-align:center}.align-right{text-align:right}.strong{font-weight:600}.actions{white-space:nowrap}.switch-cell{display:inline-flex;align-items:center;gap:10px}.switch-label{color:var(--ac-text-muted);font-size:12px}.ns-switch{position:relative;width:38px;height:22px;flex:none;padding:0;border:1px solid var(--ac-border);border-radius:var(--ac-radius-pill);background:color-mix(in srgb,var(--ac-text) 10%,transparent);cursor:pointer;transition:background .14s ease,border-color .14s ease}.ns-switch .knob{position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:var(--ac-text);transition:transform .14s ease}.ns-switch.is-on{border-color:var(--ac-accent);background:var(--ac-accent)}.ns-switch.is-on .knob{background:var(--ac-on-accent);transform:translate(16px)}.ns-switch:disabled{cursor:not-allowed;opacity:.45}.ns-switch:focus-visible{outline:2px solid var(--ac-accent);outline-offset:2px}\n"] }]
1599
- }], propDecorators: { rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: true }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: true }] }], actions: [{ type: i0.Input, args: [{ isSignal: true, alias: "actions", required: false }] }], empty: [{ type: i0.Input, args: [{ isSignal: true, alias: "empty", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], minWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "minWidth", required: false }] }], rowClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowClass", required: false }] }], action: [{ type: i0.Output, args: ["action"] }], toggle: [{ type: i0.Output, args: ["toggle"] }], cells: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => NucleusCell), { isSignal: true }] }] } });
2927
+ args: [{ selector: 'nucleus-list', imports: [NgTemplateOutlet, ButtonModule, SkeletonModule, TableModule, TagModule, TooltipModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"head\">\n <ng-content select=\"[nucleusListTitle]\" />\n <div class=\"head-actions\">\n <ng-content select=\"[nucleusListActions]\" />\n </div>\n</div>\n\n@if (showEmpty()) {\n <div class=\"placeholder\">{{ empty() }}</div>\n} @else {\n <p-table [value]=\"data()\" styleClass=\"ns-table\" [tableStyle]=\"tableStyle()\">\n <ng-template #header>\n <tr>\n @for (column of columns(); track column.key) {\n <th [class]=\"'align-' + (column.align ?? 'left')\" [style.width]=\"column.width\">\n {{ column.header }}\n </th>\n }\n @if (actions().length) {\n <th class=\"align-right\"></th>\n }\n </tr>\n </ng-template>\n\n <ng-template #body let-row>\n @if (loading()) {\n <tr class=\"is-skeleton\">\n @for (column of columns(); track column.key) {\n <td [class]=\"'align-' + (column.align ?? 'left')\">\n <p-skeleton height=\"13px\" [width]=\"skeletonWidth(column)\" />\n </td>\n }\n @if (actions().length) {\n <td class=\"align-right actions\">\n <p-skeleton shape=\"circle\" size=\"26px\" />\n </td>\n }\n </tr>\n } @else {\n <tr [class]=\"rowClass()(row)\">\n @for (column of columns(); track column.key) {\n <td [class]=\"'align-' + (column.align ?? 'left')\" [class.strong]=\"column.strong\">\n @if (cellTemplate(column.key); as custom) {\n <ng-container\n [ngTemplateOutlet]=\"custom\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column }\"\n />\n } @else if (column.kind === 'switch') {\n <span class=\"switch-cell\">\n <button\n type=\"button\"\n class=\"ns-switch\"\n role=\"switch\"\n [class.is-on]=\"isChecked(column, row)\"\n [attr.aria-checked]=\"isChecked(column, row)\"\n [attr.aria-label]=\"text(column, row)\"\n [disabled]=\"isLocked(column, row)\"\n [pTooltip]=\"hintOf(column, row)\"\n tooltipPosition=\"top\"\n (click)=\"flip(column, row)\"\n >\n <span class=\"knob\"></span>\n </button>\n <span class=\"switch-label\">{{ text(column, row) }}</span>\n </span>\n } @else {\n {{ text(column, row) }}\n @if (tagOf(column, row); as tag) {\n <p-tag [value]=\"tag.label\" [severity]=\"tag.severity ?? 'secondary'\" />\n }\n }\n </td>\n }\n\n @if (actions().length) {\n <td class=\"align-right actions\">\n @for (entry of visibleActions(row); track entry.id) {\n <p-button\n [icon]=\"entry.icon\"\n [severity]=\"entry.tone === 'danger' ? 'danger' : 'secondary'\"\n size=\"small\"\n [text]=\"true\"\n [rounded]=\"true\"\n [pTooltip]=\"entry.label\"\n tooltipPosition=\"left\"\n [ariaLabel]=\"entry.label\"\n [disabled]=\"isActionLocked(entry, row)\"\n (onClick)=\"run(entry, row)\"\n />\n }\n </td>\n }\n </tr>\n }\n </ng-template>\n </p-table>\n}\n", styles: [":host{display:block}.head{display:flex;min-height:34px;align-items:center;justify-content:space-between;gap:12px;margin-bottom:12px}.head-actions{display:flex;gap:8px}.placeholder{padding:26px;border:1px dashed var(--ac-border);border-radius:10px;color:var(--ac-text-muted);font-size:14px}.align-left{text-align:left}.align-center{text-align:center}.align-right{text-align:right}.strong{font-weight:600}.actions{white-space:nowrap}.switch-cell{display:inline-flex;align-items:center;gap:10px}.switch-label{color:var(--ac-text-muted);font-size:12px}.ns-switch{position:relative;width:38px;height:22px;flex:none;padding:0;border:1px solid var(--ac-border);border-radius:var(--ac-radius-pill);background:color-mix(in srgb,var(--ac-text) 10%,transparent);cursor:pointer;transition:background .14s ease,border-color .14s ease}.ns-switch .knob{position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:var(--ac-text);transition:transform .14s ease}.ns-switch.is-on{border-color:var(--ac-accent);background:var(--ac-accent)}.ns-switch.is-on .knob{background:var(--ac-on-accent);transform:translate(16px)}.ns-switch:disabled{cursor:not-allowed;opacity:.45}.ns-switch:focus-visible{outline:2px solid var(--ac-accent);outline-offset:2px}\n"] }]
2928
+ }], propDecorators: { rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: true }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: true }] }], actions: [{ type: i0.Input, args: [{ isSignal: true, alias: "actions", required: false }] }], empty: [{ type: i0.Input, args: [{ isSignal: true, alias: "empty", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], minWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "minWidth", required: false }] }], skeletonRows: [{ type: i0.Input, args: [{ isSignal: true, alias: "skeletonRows", required: false }] }], rowClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowClass", required: false }] }], action: [{ type: i0.Output, args: ["action"] }], toggle: [{ type: i0.Output, args: ["toggle"] }], cells: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => NucleusCell), { isSignal: true }] }] } });
2929
+
2930
+ function toggleCollapsed(collapsed, id) {
2931
+ const next = new Set(collapsed);
2932
+ if (!next.delete(id))
2933
+ next.add(id);
2934
+ return next;
2935
+ }
2936
+ function hasChildren(rows, id) {
2937
+ return rows.some((row) => row.parentId === id);
2938
+ }
2939
+ function visibleTreeRows(rows, collapsed) {
2940
+ if (collapsed.size === 0)
2941
+ return [...rows];
2942
+ const parents = new Map(rows.map((row) => [row.id, row.parentId]));
2943
+ const buried = (row) => {
2944
+ let parentId = row.parentId;
2945
+ while (parentId) {
2946
+ if (collapsed.has(parentId))
2947
+ return true;
2948
+ parentId = parents.get(parentId) ?? null;
2949
+ }
2950
+ return false;
2951
+ };
2952
+ return rows.filter((row) => !buried(row));
2953
+ }
1600
2954
 
1601
2955
  const ORBIT = { rx: 78, ry: 196 };
1602
2956
  class NucleusMark {
@@ -1650,6 +3004,238 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
1650
3004
  `, styles: [":host{display:block}svg{display:block}\n"] }]
1651
3005
  }], propDecorators: { accent: [{ type: i0.Input, args: [{ isSignal: true, alias: "accent", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
1652
3006
 
3007
+ class NucleusAppearance {
3008
+ theme = inject(ThemeService);
3009
+ language = inject(NucleusLanguage);
3010
+ copy = computed(() => themeCopyOf(this.language.lang()), ...(ngDevMode ? [{ debugName: "copy" }] : /* istanbul ignore next */ []));
3011
+ appearance = this.theme.appearance;
3012
+ canvasId = this.theme.canvasId;
3013
+ frames = computed(() => {
3014
+ const labels = this.copy().frames;
3015
+ return FRAMES.map((frame) => ({ id: frame.id, label: labels[frame.id], tokens: frame.tokens }));
3016
+ }, ...(ngDevMode ? [{ debugName: "frames" }] : /* istanbul ignore next */ []));
3017
+ canvases = computed(() => {
3018
+ const labels = this.copy().canvases;
3019
+ const preview = (id) => {
3020
+ const resolved = canvasById(id === 'system' ? this.theme.systemCanvasId() : id);
3021
+ return {
3022
+ id,
3023
+ label: labels[id],
3024
+ bg: resolved.tokens.bg,
3025
+ surface: resolved.tokens.surface,
3026
+ text: resolved.tokens.text,
3027
+ icon: id === 'system' ? 'pi pi-desktop' : null,
3028
+ };
3029
+ };
3030
+ return [...CANVASES.map((canvas) => preview(canvas.id)), preview('system')];
3031
+ }, ...(ngDevMode ? [{ debugName: "canvases" }] : /* istanbul ignore next */ []));
3032
+ accents = computed(() => {
3033
+ const labels = this.copy().accents;
3034
+ const canvas = this.canvasId();
3035
+ const auto = accentOn(accentById(this.theme.frame().accent), canvas);
3036
+ return [
3037
+ { id: 'auto', label: labels.auto, color: auto, ink: readableOn(auto) },
3038
+ ...ACCENTS.map((accent) => {
3039
+ const color = accentOn(accent, canvas);
3040
+ return { id: accent.id, label: labels[accent.id], color, ink: readableOn(color) };
3041
+ }),
3042
+ ];
3043
+ }, ...(ngDevMode ? [{ debugName: "accents" }] : /* istanbul ignore next */ []));
3044
+ densities = computed(() => {
3045
+ const labels = this.copy().densities;
3046
+ return DENSITIES.map((density) => ({
3047
+ id: density.id,
3048
+ label: labels[density.id],
3049
+ gap: Math.round(6 * density.scale),
3050
+ }));
3051
+ }, ...(ngDevMode ? [{ debugName: "densities" }] : /* istanbul ignore next */ []));
3052
+ pickFrame(id) {
3053
+ this.theme.setFrame(id);
3054
+ }
3055
+ pickCanvas(id) {
3056
+ this.theme.setCanvas(id);
3057
+ }
3058
+ pickAccent(id) {
3059
+ this.theme.setAccent(id);
3060
+ }
3061
+ pickDensity(id) {
3062
+ this.theme.setDensity(id);
3063
+ }
3064
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusAppearance, deps: [], target: i0.ɵɵFactoryTarget.Component });
3065
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: NucleusAppearance, isStandalone: true, selector: "nucleus-appearance", ngImport: i0, template: "@let t = copy();\n@let current = appearance();\n\n<section class=\"group\">\n <h3 class=\"group-title\">{{ t.frameTitle }}</h3>\n <p class=\"group-hint\">{{ t.frameHint }}</p>\n\n <div class=\"frames\" role=\"radiogroup\" [attr.aria-label]=\"t.frameTitle\">\n @for (frame of frames(); track frame.id) {\n <button\n type=\"button\"\n class=\"frame\"\n role=\"radio\"\n [class.is-active]=\"frame.id === current.frame\"\n [attr.aria-checked]=\"frame.id === current.frame\"\n [attr.aria-label]=\"frame.label\"\n [style.--f-bg]=\"frame.tokens.bg\"\n [style.--f-raised]=\"frame.tokens.raised\"\n [style.--f-ink]=\"frame.tokens.ink\"\n [style.--f-muted]=\"frame.tokens.muted\"\n [style.--f-line]=\"frame.tokens.line\"\n [style.--f-glow]=\"frame.tokens.glow\"\n (click)=\"pickFrame(frame.id)\"\n >\n <span class=\"frame-chip\" aria-hidden=\"true\">\n <span class=\"frame-bar\">\n <span class=\"frame-dot\"></span>\n </span>\n <span class=\"frame-lower\">\n <span class=\"frame-rail\">\n <span class=\"frame-pin\"></span>\n <span class=\"frame-row is-on\"></span>\n <span class=\"frame-row\"></span>\n <span class=\"frame-row\"></span>\n </span>\n <span class=\"frame-canvas\"></span>\n </span>\n </span>\n <span class=\"frame-name\">{{ frame.label }}</span>\n </button>\n }\n </div>\n</section>\n\n<section class=\"group\">\n <h3 class=\"group-title\">{{ t.canvasTitle }}</h3>\n <p class=\"group-hint\">{{ t.canvasHint }}</p>\n\n <div class=\"canvases\" role=\"radiogroup\" [attr.aria-label]=\"t.canvasTitle\">\n @for (canvas of canvases(); track canvas.id) {\n <button\n type=\"button\"\n class=\"canvas\"\n role=\"radio\"\n [class.is-active]=\"canvas.id === current.canvas\"\n [attr.aria-checked]=\"canvas.id === current.canvas\"\n (click)=\"pickCanvas(canvas.id)\"\n >\n <span\n class=\"canvas-chip\"\n aria-hidden=\"true\"\n [style.background]=\"canvas.bg\"\n [style.color]=\"canvas.text\"\n >\n @if (canvas.icon) {\n <i [class]=\"canvas.icon\"></i>\n } @else {\n <span class=\"canvas-sheet\" [style.background]=\"canvas.surface\"></span>\n }\n </span>\n <span class=\"canvas-name\">{{ canvas.label }}</span>\n </button>\n }\n </div>\n</section>\n\n<section class=\"group\">\n <h3 class=\"group-title\">{{ t.accentTitle }}</h3>\n <p class=\"group-hint\">{{ t.accentHint }}</p>\n\n <div class=\"accents\" role=\"radiogroup\" [attr.aria-label]=\"t.accentTitle\">\n @for (accent of accents(); track accent.id) {\n <button\n type=\"button\"\n class=\"accent\"\n role=\"radio\"\n [class.is-auto]=\"accent.id === 'auto'\"\n [class.is-active]=\"accent.id === current.accent\"\n [attr.aria-checked]=\"accent.id === current.accent\"\n [attr.aria-label]=\"accent.label\"\n [title]=\"accent.label\"\n [style.background]=\"accent.color\"\n [style.color]=\"accent.ink\"\n (click)=\"pickAccent(accent.id)\"\n >\n @if (accent.id === current.accent) {\n <i class=\"pi pi-check\" aria-hidden=\"true\"></i>\n } @else if (accent.id === 'auto') {\n <i class=\"pi pi-sparkles\" aria-hidden=\"true\"></i>\n }\n </button>\n }\n </div>\n</section>\n\n<section class=\"group\">\n <h3 class=\"group-title\">{{ t.densityTitle }}</h3>\n <p class=\"group-hint\">{{ t.densityHint }}</p>\n\n <div class=\"densities\" role=\"radiogroup\" [attr.aria-label]=\"t.densityTitle\">\n @for (density of densities(); track density.id) {\n <button\n type=\"button\"\n class=\"density\"\n role=\"radio\"\n [class.is-active]=\"density.id === current.density\"\n [attr.aria-checked]=\"density.id === current.density\"\n (click)=\"pickDensity(density.id)\"\n >\n <span class=\"density-chip\" aria-hidden=\"true\" [style.gap.px]=\"density.gap\">\n <span class=\"density-row\"></span>\n <span class=\"density-row\"></span>\n <span class=\"density-row\"></span>\n </span>\n <span class=\"density-name\">{{ density.label }}</span>\n </button>\n }\n </div>\n</section>\n", styles: [":host{display:flex;flex-direction:column;gap:26px}.group{display:flex;flex-direction:column;gap:2px}.group-title{margin:0;font-size:13px;font-weight:600;letter-spacing:.02em}.group-hint{margin:0 0 12px;color:var(--ac-text-muted);font-size:12px;font-weight:300;line-height:1.45}.frames{display:grid;grid-template-columns:repeat(auto-fill,minmax(84px,1fr));gap:10px}.frame{display:flex;flex-direction:column;gap:6px;padding:4px;border:2px solid transparent;border-radius:10px;background:transparent;color:var(--ac-text-muted);cursor:pointer}.frame:hover{color:var(--ac-text)}.frame.is-active{border-color:var(--ac-accent);color:var(--ac-text)}.frame-chip{display:flex;overflow:hidden;flex-direction:column;height:52px;border:1px solid var(--f-line);border-radius:6px;background:var(--f-bg)}.frame-bar{display:flex;flex:none;align-items:center;height:12px;padding-left:5px;border-bottom:1px solid var(--f-line)}.frame-dot{width:5px;height:5px;border-radius:50%;background:var(--f-glow)}.frame-lower{display:flex;flex:1;min-height:0}.frame-rail{display:flex;flex:none;flex-direction:column;gap:3px;width:22px;padding:4px 3px;border-right:1px solid var(--f-line)}.frame-pin{height:7px;border-left:2px solid var(--f-glow);border-radius:2px;background:var(--f-raised)}.frame-row{height:3px;border-radius:2px;background:var(--f-muted);opacity:.55}.frame-row.is-on{background:var(--f-glow);opacity:1}.frame-canvas{flex:1;background:var(--ac-bg)}.frame-name{overflow:hidden;font-size:11px;font-weight:500;text-align:center;text-overflow:ellipsis;white-space:nowrap}.canvases{display:grid;grid-template-columns:repeat(auto-fit,minmax(70px,1fr));gap:8px}.canvas{display:flex;flex-direction:column;gap:6px;padding:4px;border:2px solid transparent;border-radius:10px;background:transparent;color:var(--ac-text-muted);cursor:pointer}.canvas:hover{color:var(--ac-text)}.canvas.is-active{border-color:var(--ac-accent);color:var(--ac-text)}.canvas-chip{display:flex;align-items:center;justify-content:center;height:40px;border:1px solid var(--ac-border);border-radius:6px}.canvas-chip i{font-size:15px}.canvas-sheet{width:60%;height:58%;border-radius:3px;box-shadow:0 1px 4px #00000038}.canvas-name{font-size:11px;font-weight:500;text-align:center}.accents{display:flex;flex-wrap:wrap;gap:9px}.accent{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;border:2px solid transparent;border-radius:50%;cursor:pointer;transition:transform .12s ease}.accent:hover{transform:scale(1.12)}.accent.is-auto{border-style:dashed;border-color:var(--ac-border)}.accent.is-active{border-style:solid;border-color:var(--ac-text);box-shadow:0 0 0 2px var(--ac-surface) inset}.accent i{font-size:11px}.densities{display:flex;gap:10px}.density{display:flex;flex:1;flex-direction:column;gap:6px;padding:4px;border:2px solid transparent;border-radius:10px;background:transparent;color:var(--ac-text-muted);cursor:pointer}.density:hover{color:var(--ac-text)}.density.is-active{border-color:var(--ac-accent);color:var(--ac-text)}.density-chip{display:flex;flex-direction:column;justify-content:center;height:42px;padding:0 10px;border:1px solid var(--ac-border);border-radius:6px;background:var(--ac-surface)}.density-row{height:3px;border-radius:2px;background:var(--ac-text-muted);opacity:.5}.density-name{font-size:11px;font-weight:500;text-align:center}@media(prefers-reduced-motion:reduce){.accent{transition:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3066
+ }
3067
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusAppearance, decorators: [{
3068
+ type: Component,
3069
+ args: [{ selector: 'nucleus-appearance', changeDetection: ChangeDetectionStrategy.OnPush, template: "@let t = copy();\n@let current = appearance();\n\n<section class=\"group\">\n <h3 class=\"group-title\">{{ t.frameTitle }}</h3>\n <p class=\"group-hint\">{{ t.frameHint }}</p>\n\n <div class=\"frames\" role=\"radiogroup\" [attr.aria-label]=\"t.frameTitle\">\n @for (frame of frames(); track frame.id) {\n <button\n type=\"button\"\n class=\"frame\"\n role=\"radio\"\n [class.is-active]=\"frame.id === current.frame\"\n [attr.aria-checked]=\"frame.id === current.frame\"\n [attr.aria-label]=\"frame.label\"\n [style.--f-bg]=\"frame.tokens.bg\"\n [style.--f-raised]=\"frame.tokens.raised\"\n [style.--f-ink]=\"frame.tokens.ink\"\n [style.--f-muted]=\"frame.tokens.muted\"\n [style.--f-line]=\"frame.tokens.line\"\n [style.--f-glow]=\"frame.tokens.glow\"\n (click)=\"pickFrame(frame.id)\"\n >\n <span class=\"frame-chip\" aria-hidden=\"true\">\n <span class=\"frame-bar\">\n <span class=\"frame-dot\"></span>\n </span>\n <span class=\"frame-lower\">\n <span class=\"frame-rail\">\n <span class=\"frame-pin\"></span>\n <span class=\"frame-row is-on\"></span>\n <span class=\"frame-row\"></span>\n <span class=\"frame-row\"></span>\n </span>\n <span class=\"frame-canvas\"></span>\n </span>\n </span>\n <span class=\"frame-name\">{{ frame.label }}</span>\n </button>\n }\n </div>\n</section>\n\n<section class=\"group\">\n <h3 class=\"group-title\">{{ t.canvasTitle }}</h3>\n <p class=\"group-hint\">{{ t.canvasHint }}</p>\n\n <div class=\"canvases\" role=\"radiogroup\" [attr.aria-label]=\"t.canvasTitle\">\n @for (canvas of canvases(); track canvas.id) {\n <button\n type=\"button\"\n class=\"canvas\"\n role=\"radio\"\n [class.is-active]=\"canvas.id === current.canvas\"\n [attr.aria-checked]=\"canvas.id === current.canvas\"\n (click)=\"pickCanvas(canvas.id)\"\n >\n <span\n class=\"canvas-chip\"\n aria-hidden=\"true\"\n [style.background]=\"canvas.bg\"\n [style.color]=\"canvas.text\"\n >\n @if (canvas.icon) {\n <i [class]=\"canvas.icon\"></i>\n } @else {\n <span class=\"canvas-sheet\" [style.background]=\"canvas.surface\"></span>\n }\n </span>\n <span class=\"canvas-name\">{{ canvas.label }}</span>\n </button>\n }\n </div>\n</section>\n\n<section class=\"group\">\n <h3 class=\"group-title\">{{ t.accentTitle }}</h3>\n <p class=\"group-hint\">{{ t.accentHint }}</p>\n\n <div class=\"accents\" role=\"radiogroup\" [attr.aria-label]=\"t.accentTitle\">\n @for (accent of accents(); track accent.id) {\n <button\n type=\"button\"\n class=\"accent\"\n role=\"radio\"\n [class.is-auto]=\"accent.id === 'auto'\"\n [class.is-active]=\"accent.id === current.accent\"\n [attr.aria-checked]=\"accent.id === current.accent\"\n [attr.aria-label]=\"accent.label\"\n [title]=\"accent.label\"\n [style.background]=\"accent.color\"\n [style.color]=\"accent.ink\"\n (click)=\"pickAccent(accent.id)\"\n >\n @if (accent.id === current.accent) {\n <i class=\"pi pi-check\" aria-hidden=\"true\"></i>\n } @else if (accent.id === 'auto') {\n <i class=\"pi pi-sparkles\" aria-hidden=\"true\"></i>\n }\n </button>\n }\n </div>\n</section>\n\n<section class=\"group\">\n <h3 class=\"group-title\">{{ t.densityTitle }}</h3>\n <p class=\"group-hint\">{{ t.densityHint }}</p>\n\n <div class=\"densities\" role=\"radiogroup\" [attr.aria-label]=\"t.densityTitle\">\n @for (density of densities(); track density.id) {\n <button\n type=\"button\"\n class=\"density\"\n role=\"radio\"\n [class.is-active]=\"density.id === current.density\"\n [attr.aria-checked]=\"density.id === current.density\"\n (click)=\"pickDensity(density.id)\"\n >\n <span class=\"density-chip\" aria-hidden=\"true\" [style.gap.px]=\"density.gap\">\n <span class=\"density-row\"></span>\n <span class=\"density-row\"></span>\n <span class=\"density-row\"></span>\n </span>\n <span class=\"density-name\">{{ density.label }}</span>\n </button>\n }\n </div>\n</section>\n", styles: [":host{display:flex;flex-direction:column;gap:26px}.group{display:flex;flex-direction:column;gap:2px}.group-title{margin:0;font-size:13px;font-weight:600;letter-spacing:.02em}.group-hint{margin:0 0 12px;color:var(--ac-text-muted);font-size:12px;font-weight:300;line-height:1.45}.frames{display:grid;grid-template-columns:repeat(auto-fill,minmax(84px,1fr));gap:10px}.frame{display:flex;flex-direction:column;gap:6px;padding:4px;border:2px solid transparent;border-radius:10px;background:transparent;color:var(--ac-text-muted);cursor:pointer}.frame:hover{color:var(--ac-text)}.frame.is-active{border-color:var(--ac-accent);color:var(--ac-text)}.frame-chip{display:flex;overflow:hidden;flex-direction:column;height:52px;border:1px solid var(--f-line);border-radius:6px;background:var(--f-bg)}.frame-bar{display:flex;flex:none;align-items:center;height:12px;padding-left:5px;border-bottom:1px solid var(--f-line)}.frame-dot{width:5px;height:5px;border-radius:50%;background:var(--f-glow)}.frame-lower{display:flex;flex:1;min-height:0}.frame-rail{display:flex;flex:none;flex-direction:column;gap:3px;width:22px;padding:4px 3px;border-right:1px solid var(--f-line)}.frame-pin{height:7px;border-left:2px solid var(--f-glow);border-radius:2px;background:var(--f-raised)}.frame-row{height:3px;border-radius:2px;background:var(--f-muted);opacity:.55}.frame-row.is-on{background:var(--f-glow);opacity:1}.frame-canvas{flex:1;background:var(--ac-bg)}.frame-name{overflow:hidden;font-size:11px;font-weight:500;text-align:center;text-overflow:ellipsis;white-space:nowrap}.canvases{display:grid;grid-template-columns:repeat(auto-fit,minmax(70px,1fr));gap:8px}.canvas{display:flex;flex-direction:column;gap:6px;padding:4px;border:2px solid transparent;border-radius:10px;background:transparent;color:var(--ac-text-muted);cursor:pointer}.canvas:hover{color:var(--ac-text)}.canvas.is-active{border-color:var(--ac-accent);color:var(--ac-text)}.canvas-chip{display:flex;align-items:center;justify-content:center;height:40px;border:1px solid var(--ac-border);border-radius:6px}.canvas-chip i{font-size:15px}.canvas-sheet{width:60%;height:58%;border-radius:3px;box-shadow:0 1px 4px #00000038}.canvas-name{font-size:11px;font-weight:500;text-align:center}.accents{display:flex;flex-wrap:wrap;gap:9px}.accent{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;border:2px solid transparent;border-radius:50%;cursor:pointer;transition:transform .12s ease}.accent:hover{transform:scale(1.12)}.accent.is-auto{border-style:dashed;border-color:var(--ac-border)}.accent.is-active{border-style:solid;border-color:var(--ac-text);box-shadow:0 0 0 2px var(--ac-surface) inset}.accent i{font-size:11px}.densities{display:flex;gap:10px}.density{display:flex;flex:1;flex-direction:column;gap:6px;padding:4px;border:2px solid transparent;border-radius:10px;background:transparent;color:var(--ac-text-muted);cursor:pointer}.density:hover{color:var(--ac-text)}.density.is-active{border-color:var(--ac-accent);color:var(--ac-text)}.density-chip{display:flex;flex-direction:column;justify-content:center;height:42px;padding:0 10px;border:1px solid var(--ac-border);border-radius:6px;background:var(--ac-surface)}.density-row{height:3px;border-radius:2px;background:var(--ac-text-muted);opacity:.5}.density-name{font-size:11px;font-weight:500;text-align:center}@media(prefers-reduced-motion:reduce){.accent{transition:none}}\n"] }]
3070
+ }] });
3071
+
3072
+ function menuTile(label, icon, color) {
3073
+ return { label, icon, color, ink: color ? readableOn(color) : null, ghost: false };
3074
+ }
3075
+ function menuGlyph(icon) {
3076
+ return { label: '', icon, color: null, ink: null, ghost: true };
3077
+ }
3078
+ function foldText(value) {
3079
+ return value
3080
+ .normalize('NFD')
3081
+ .replace(/[̀-ͯ]/g, '')
3082
+ .toLowerCase();
3083
+ }
3084
+ function matchesRow(row, term) {
3085
+ return foldText(row.name).includes(term) || foldText(row.meta ?? '').includes(term);
3086
+ }
3087
+ function selectableRows(sections) {
3088
+ return sections.flatMap((section) => section.rows).filter((row) => !row.disabled && !row.current);
3089
+ }
3090
+
3091
+ class NucleusMenu {
3092
+ sections = input([], ...(ngDevMode ? [{ debugName: "sections" }] : /* istanbul ignore next */ []));
3093
+ heading = input('', ...(ngDevMode ? [{ debugName: "heading" }] : /* istanbul ignore next */ []));
3094
+ badge = input(null, ...(ngDevMode ? [{ debugName: "badge" }] : /* istanbul ignore next */ []));
3095
+ searchable = input(false, ...(ngDevMode ? [{ debugName: "searchable" }] : /* istanbul ignore next */ []));
3096
+ searchPlaceholder = input('', ...(ngDevMode ? [{ debugName: "searchPlaceholder" }] : /* istanbul ignore next */ []));
3097
+ emptyText = input('', ...(ngDevMode ? [{ debugName: "emptyText" }] : /* istanbul ignore next */ []));
3098
+ keyboardHint = input('', ...(ngDevMode ? [{ debugName: "keyboardHint" }] : /* istanbul ignore next */ []));
3099
+ width = input(320, ...(ngDevMode ? [{ debugName: "width" }] : /* istanbul ignore next */ []));
3100
+ select = output();
3101
+ dismiss = output();
3102
+ term = signal('', ...(ngDevMode ? [{ debugName: "term" }] : /* istanbul ignore next */ []));
3103
+ searchBox = viewChild('searchBox', ...(ngDevMode ? [{ debugName: "searchBox" }] : /* istanbul ignore next */ []));
3104
+ root = viewChild.required('root');
3105
+ visible = computed(() => {
3106
+ const term = foldText(this.term().trim());
3107
+ if (!term)
3108
+ return this.sections();
3109
+ const rows = this.sections()
3110
+ .flatMap((section) => section.rows)
3111
+ .filter((row) => !row.current && matchesRow(row, term));
3112
+ return [{ id: 'results', label: null, rows }];
3113
+ }, ...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
3114
+ isEmpty = computed(() => this.visible().every((s) => s.rows.length === 0), ...(ngDevMode ? [{ debugName: "isEmpty" }] : /* istanbul ignore next */ []));
3115
+ showSearch = computed(() => this.searchable(), ...(ngDevMode ? [{ debugName: "showSearch" }] : /* istanbul ignore next */ []));
3116
+ reachable = computed(() => selectableRows(this.visible()), ...(ngDevMode ? [{ debugName: "reachable" }] : /* istanbul ignore next */ []));
3117
+ activeId = signal(null, ...(ngDevMode ? [{ debugName: "activeId" }] : /* istanbul ignore next */ []));
3118
+ syncActive = effect(() => {
3119
+ const rows = this.reachable();
3120
+ const current = this.activeId();
3121
+ if (rows.some((row) => row.id === current))
3122
+ return;
3123
+ this.activeId.set(rows[0]?.id ?? null);
3124
+ }, ...(ngDevMode ? [{ debugName: "syncActive" }] : /* istanbul ignore next */ []));
3125
+ constructor() {
3126
+ queueMicrotask(() => this.focusEntry());
3127
+ }
3128
+ isActive(row) {
3129
+ return row.id === this.activeId();
3130
+ }
3131
+ onTerm(value) {
3132
+ this.term.set(value);
3133
+ }
3134
+ choose(row) {
3135
+ if (row.disabled || row.current)
3136
+ return;
3137
+ this.select.emit(row);
3138
+ }
3139
+ onKeydown(event) {
3140
+ if (event.key === 'Escape') {
3141
+ event.preventDefault();
3142
+ this.dismiss.emit();
3143
+ return;
3144
+ }
3145
+ if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
3146
+ event.preventDefault();
3147
+ this.step(event.key === 'ArrowDown' ? 1 : -1);
3148
+ return;
3149
+ }
3150
+ if (event.key === 'Enter') {
3151
+ const row = this.reachable().find((item) => item.id === this.activeId());
3152
+ if (!row)
3153
+ return;
3154
+ event.preventDefault();
3155
+ this.choose(row);
3156
+ }
3157
+ }
3158
+ step(delta) {
3159
+ const rows = this.reachable();
3160
+ if (!rows.length)
3161
+ return;
3162
+ const index = rows.findIndex((row) => row.id === this.activeId());
3163
+ const next = (index + delta + rows.length) % rows.length;
3164
+ this.activeId.set(rows[next].id);
3165
+ this.scrollIntoView(rows[next].id);
3166
+ }
3167
+ scrollIntoView(id) {
3168
+ const target = this.root().nativeElement.querySelector(`[data-row="${id}"]`);
3169
+ target?.scrollIntoView({ block: 'nearest' });
3170
+ }
3171
+ focusEntry() {
3172
+ const box = this.searchBox()?.nativeElement;
3173
+ if (box) {
3174
+ box.focus();
3175
+ return;
3176
+ }
3177
+ this.root().nativeElement.focus();
3178
+ }
3179
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusMenu, deps: [], target: i0.ɵɵFactoryTarget.Component });
3180
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: NucleusMenu, isStandalone: true, selector: "nucleus-menu", inputs: { sections: { classPropertyName: "sections", publicName: "sections", isSignal: true, isRequired: false, transformFunction: null }, heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: false, transformFunction: null }, badge: { classPropertyName: "badge", publicName: "badge", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, keyboardHint: { classPropertyName: "keyboardHint", publicName: "keyboardHint", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { select: "select", dismiss: "dismiss" }, host: { listeners: { "keydown": "onKeydown($event)" }, properties: { "style.width.px": "width()" } }, viewQueries: [{ propertyName: "searchBox", first: true, predicate: ["searchBox"], descendants: true, isSignal: true }, { propertyName: "root", first: true, predicate: ["root"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"menu\" #root tabindex=\"-1\">\n @if (heading()) {\n <div class=\"menu-head\">\n <h4>{{ heading() }}</h4>\n @if (badge(); as count) {\n <span class=\"menu-badge\">{{ count }}</span>\n }\n </div>\n }\n\n @if (showSearch()) {\n <div class=\"menu-search\">\n <label class=\"menu-field\">\n <i class=\"pi pi-search\" aria-hidden=\"true\"></i>\n <input\n #searchBox\n type=\"text\"\n autocomplete=\"off\"\n [attr.aria-label]=\"searchPlaceholder()\"\n [placeholder]=\"searchPlaceholder()\"\n [value]=\"term()\"\n (input)=\"onTerm($any($event.target).value)\"\n />\n </label>\n </div>\n }\n\n <div class=\"menu-body\" role=\"listbox\" [attr.aria-label]=\"heading()\">\n @if (isEmpty()) {\n <p class=\"menu-empty\">{{ emptyText() }}</p>\n }\n\n @for (section of visible(); track section.id) {\n @if (section.rows.length) {\n @if (section.label) {\n <p class=\"menu-section\">{{ section.label }}</p>\n }\n\n @for (row of section.rows; track row.id) {\n <button\n type=\"button\"\n class=\"menu-row\"\n role=\"option\"\n [attr.data-row]=\"row.id\"\n [class.is-current]=\"row.current\"\n [class.is-locked]=\"row.disabled\"\n [class.is-active]=\"isActive(row)\"\n [attr.aria-selected]=\"isActive(row)\"\n [disabled]=\"row.disabled || row.current\"\n (click)=\"choose(row)\"\n >\n @if (row.tile; as tile) {\n <span\n class=\"menu-tile\"\n [class.is-ghost]=\"tile.ghost\"\n [style.background]=\"tile.ghost ? null : tile.color\"\n [style.color]=\"tile.ghost ? null : tile.ink\"\n aria-hidden=\"true\"\n >\n @if (tile.icon) {\n <i [class]=\"tile.icon\"></i>\n } @else {\n {{ tile.label }}\n }\n </span>\n }\n\n <span class=\"menu-text\">\n <span class=\"menu-name\">{{ row.name }}</span>\n @if (row.meta) {\n <span class=\"menu-meta\">{{ row.meta }}</span>\n }\n </span>\n\n <span class=\"menu-end\">\n @if (row.tag) {\n <span class=\"menu-tag\" [class.is-current]=\"row.current\">{{ row.tag }}</span>\n } @else if (!row.disabled && !row.current) {\n <i class=\"pi pi-chevron-right menu-chevron\" aria-hidden=\"true\"></i>\n }\n </span>\n </button>\n }\n }\n }\n </div>\n\n @if (showSearch() && keyboardHint()) {\n <p class=\"menu-foot\">{{ keyboardHint() }}</p>\n }\n</div>\n", styles: [":host{display:block}.menu{display:flex;flex-direction:column;min-width:0}.menu:focus-visible{outline:none}.menu-head{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:12px 15px 10px;border-bottom:1px solid var(--ac-border);background:var(--ns-header-bg)}.menu-head h4{margin:0;overflow:hidden;color:var(--ns-header-ink);font-size:11px;font-weight:600;letter-spacing:.08em;text-overflow:ellipsis;text-transform:uppercase;white-space:nowrap}.menu-badge{flex:none;padding:1px 8px;border-radius:var(--ac-radius-pill);background:var(--ac-surface-alt);color:var(--ac-text-muted);font-size:10px;font-variant-numeric:tabular-nums}.menu-search{padding:9px 10px;border-bottom:1px solid var(--ac-border)}.menu-field{position:relative;display:block}.menu-field i{position:absolute;top:50%;left:11px;color:var(--ac-text-muted);font-size:12px;pointer-events:none;transform:translateY(-50%)}.menu-field input{width:100%;box-sizing:border-box;padding:7px 10px 7px 31px;border:1px solid var(--ac-border);border-radius:7px;background:var(--ac-bg);color:var(--ac-text);font:inherit;font-size:13px}.menu-field input::placeholder{color:var(--ac-text-muted)}.menu-field input:focus-visible{border-color:var(--ac-accent);outline:2px solid color-mix(in srgb,var(--ac-accent) 45%,transparent);outline-offset:1px}.menu-body{display:flex;max-height:348px;flex-direction:column;gap:1px;padding:6px;overflow-y:auto}.menu-section{margin:0;padding:11px 9px 5px;color:var(--ac-text-muted);font-size:10px;font-weight:600;letter-spacing:.08em;text-transform:uppercase}.menu-empty{margin:0;padding:26px 14px;color:var(--ac-text-muted);font-size:12px;text-align:center}.menu-row{display:flex;width:100%;align-items:center;gap:11px;padding:9px 10px;border:0;border-radius:8px;background:transparent;color:var(--ac-text);font:inherit;text-align:left;cursor:pointer}.menu-row:hover:not(:disabled),.menu-row.is-active:not(:disabled){background:var(--ns-accent-wash);box-shadow:inset 2px 0 0 var(--ac-accent)}.menu-row:focus-visible{outline:none}.menu-row.is-current{background:var(--ac-surface-alt);box-shadow:none;cursor:default}.menu-row.is-locked{cursor:default;opacity:.55}.menu-tile{display:inline-flex;width:34px;height:34px;flex:none;align-items:center;justify-content:center;border-radius:9px;font-size:11.5px;font-weight:600;letter-spacing:.02em}.menu-tile i{font-size:14px}.menu-tile.is-ghost{background:var(--ac-surface-alt);color:var(--ac-text-muted)}.menu-row:hover:not(:disabled) .menu-tile.is-ghost,.menu-row.is-active:not(:disabled) .menu-tile.is-ghost{color:var(--ac-accent)}.menu-text{display:flex;min-width:0;flex-direction:column;gap:1px}.menu-name{overflow:hidden;font-size:13px;font-weight:600;text-overflow:ellipsis;white-space:nowrap}.menu-meta{overflow:hidden;color:var(--ac-text-muted);font-size:11px;text-overflow:ellipsis;white-space:nowrap}.menu-end{display:flex;flex:none;align-items:center;margin-left:auto}.menu-chevron{color:var(--ac-accent);font-size:11px;opacity:0}.menu-row:hover:not(:disabled) .menu-chevron,.menu-row.is-active:not(:disabled) .menu-chevron{opacity:1}.menu-tag{padding:2px 7px;border-radius:4px;background:var(--ac-surface-alt);color:var(--ac-text-muted);font-size:9.5px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;white-space:nowrap}.menu-tag.is-current{background:color-mix(in srgb,var(--ac-accent) 16%,transparent);color:var(--ac-accent)}.menu-foot{margin:0;padding:8px 14px;border-top:1px solid var(--ac-border);color:var(--ac-text-muted);font-size:10.5px}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3181
+ }
3182
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusMenu, decorators: [{
3183
+ type: Component,
3184
+ args: [{ selector: 'nucleus-menu', host: {
3185
+ '[style.width.px]': 'width()',
3186
+ '(keydown)': 'onKeydown($event)',
3187
+ }, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"menu\" #root tabindex=\"-1\">\n @if (heading()) {\n <div class=\"menu-head\">\n <h4>{{ heading() }}</h4>\n @if (badge(); as count) {\n <span class=\"menu-badge\">{{ count }}</span>\n }\n </div>\n }\n\n @if (showSearch()) {\n <div class=\"menu-search\">\n <label class=\"menu-field\">\n <i class=\"pi pi-search\" aria-hidden=\"true\"></i>\n <input\n #searchBox\n type=\"text\"\n autocomplete=\"off\"\n [attr.aria-label]=\"searchPlaceholder()\"\n [placeholder]=\"searchPlaceholder()\"\n [value]=\"term()\"\n (input)=\"onTerm($any($event.target).value)\"\n />\n </label>\n </div>\n }\n\n <div class=\"menu-body\" role=\"listbox\" [attr.aria-label]=\"heading()\">\n @if (isEmpty()) {\n <p class=\"menu-empty\">{{ emptyText() }}</p>\n }\n\n @for (section of visible(); track section.id) {\n @if (section.rows.length) {\n @if (section.label) {\n <p class=\"menu-section\">{{ section.label }}</p>\n }\n\n @for (row of section.rows; track row.id) {\n <button\n type=\"button\"\n class=\"menu-row\"\n role=\"option\"\n [attr.data-row]=\"row.id\"\n [class.is-current]=\"row.current\"\n [class.is-locked]=\"row.disabled\"\n [class.is-active]=\"isActive(row)\"\n [attr.aria-selected]=\"isActive(row)\"\n [disabled]=\"row.disabled || row.current\"\n (click)=\"choose(row)\"\n >\n @if (row.tile; as tile) {\n <span\n class=\"menu-tile\"\n [class.is-ghost]=\"tile.ghost\"\n [style.background]=\"tile.ghost ? null : tile.color\"\n [style.color]=\"tile.ghost ? null : tile.ink\"\n aria-hidden=\"true\"\n >\n @if (tile.icon) {\n <i [class]=\"tile.icon\"></i>\n } @else {\n {{ tile.label }}\n }\n </span>\n }\n\n <span class=\"menu-text\">\n <span class=\"menu-name\">{{ row.name }}</span>\n @if (row.meta) {\n <span class=\"menu-meta\">{{ row.meta }}</span>\n }\n </span>\n\n <span class=\"menu-end\">\n @if (row.tag) {\n <span class=\"menu-tag\" [class.is-current]=\"row.current\">{{ row.tag }}</span>\n } @else if (!row.disabled && !row.current) {\n <i class=\"pi pi-chevron-right menu-chevron\" aria-hidden=\"true\"></i>\n }\n </span>\n </button>\n }\n }\n }\n </div>\n\n @if (showSearch() && keyboardHint()) {\n <p class=\"menu-foot\">{{ keyboardHint() }}</p>\n }\n</div>\n", styles: [":host{display:block}.menu{display:flex;flex-direction:column;min-width:0}.menu:focus-visible{outline:none}.menu-head{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:12px 15px 10px;border-bottom:1px solid var(--ac-border);background:var(--ns-header-bg)}.menu-head h4{margin:0;overflow:hidden;color:var(--ns-header-ink);font-size:11px;font-weight:600;letter-spacing:.08em;text-overflow:ellipsis;text-transform:uppercase;white-space:nowrap}.menu-badge{flex:none;padding:1px 8px;border-radius:var(--ac-radius-pill);background:var(--ac-surface-alt);color:var(--ac-text-muted);font-size:10px;font-variant-numeric:tabular-nums}.menu-search{padding:9px 10px;border-bottom:1px solid var(--ac-border)}.menu-field{position:relative;display:block}.menu-field i{position:absolute;top:50%;left:11px;color:var(--ac-text-muted);font-size:12px;pointer-events:none;transform:translateY(-50%)}.menu-field input{width:100%;box-sizing:border-box;padding:7px 10px 7px 31px;border:1px solid var(--ac-border);border-radius:7px;background:var(--ac-bg);color:var(--ac-text);font:inherit;font-size:13px}.menu-field input::placeholder{color:var(--ac-text-muted)}.menu-field input:focus-visible{border-color:var(--ac-accent);outline:2px solid color-mix(in srgb,var(--ac-accent) 45%,transparent);outline-offset:1px}.menu-body{display:flex;max-height:348px;flex-direction:column;gap:1px;padding:6px;overflow-y:auto}.menu-section{margin:0;padding:11px 9px 5px;color:var(--ac-text-muted);font-size:10px;font-weight:600;letter-spacing:.08em;text-transform:uppercase}.menu-empty{margin:0;padding:26px 14px;color:var(--ac-text-muted);font-size:12px;text-align:center}.menu-row{display:flex;width:100%;align-items:center;gap:11px;padding:9px 10px;border:0;border-radius:8px;background:transparent;color:var(--ac-text);font:inherit;text-align:left;cursor:pointer}.menu-row:hover:not(:disabled),.menu-row.is-active:not(:disabled){background:var(--ns-accent-wash);box-shadow:inset 2px 0 0 var(--ac-accent)}.menu-row:focus-visible{outline:none}.menu-row.is-current{background:var(--ac-surface-alt);box-shadow:none;cursor:default}.menu-row.is-locked{cursor:default;opacity:.55}.menu-tile{display:inline-flex;width:34px;height:34px;flex:none;align-items:center;justify-content:center;border-radius:9px;font-size:11.5px;font-weight:600;letter-spacing:.02em}.menu-tile i{font-size:14px}.menu-tile.is-ghost{background:var(--ac-surface-alt);color:var(--ac-text-muted)}.menu-row:hover:not(:disabled) .menu-tile.is-ghost,.menu-row.is-active:not(:disabled) .menu-tile.is-ghost{color:var(--ac-accent)}.menu-text{display:flex;min-width:0;flex-direction:column;gap:1px}.menu-name{overflow:hidden;font-size:13px;font-weight:600;text-overflow:ellipsis;white-space:nowrap}.menu-meta{overflow:hidden;color:var(--ac-text-muted);font-size:11px;text-overflow:ellipsis;white-space:nowrap}.menu-end{display:flex;flex:none;align-items:center;margin-left:auto}.menu-chevron{color:var(--ac-accent);font-size:11px;opacity:0}.menu-row:hover:not(:disabled) .menu-chevron,.menu-row.is-active:not(:disabled) .menu-chevron{opacity:1}.menu-tag{padding:2px 7px;border-radius:4px;background:var(--ac-surface-alt);color:var(--ac-text-muted);font-size:9.5px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;white-space:nowrap}.menu-tag.is-current{background:color-mix(in srgb,var(--ac-accent) 16%,transparent);color:var(--ac-accent)}.menu-foot{margin:0;padding:8px 14px;border-top:1px solid var(--ac-border);color:var(--ac-text-muted);font-size:10.5px}\n"] }]
3188
+ }], ctorParameters: () => [], propDecorators: { sections: [{ type: i0.Input, args: [{ isSignal: true, alias: "sections", required: false }] }], heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required: false }] }], badge: [{ type: i0.Input, args: [{ isSignal: true, alias: "badge", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], keyboardHint: [{ type: i0.Input, args: [{ isSignal: true, alias: "keyboardHint", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], select: [{ type: i0.Output, args: ["select"] }], dismiss: [{ type: i0.Output, args: ["dismiss"] }], searchBox: [{ type: i0.ViewChild, args: ['searchBox', { isSignal: true }] }], root: [{ type: i0.ViewChild, args: ['root', { isSignal: true }] }] } });
3189
+
3190
+ const STORAGE_KEY = 'nucleus_recent_apps';
3191
+ const KEEP = 8;
3192
+ function readIds() {
3193
+ try {
3194
+ const raw = localStorage.getItem(STORAGE_KEY);
3195
+ if (!raw)
3196
+ return [];
3197
+ const parsed = JSON.parse(raw);
3198
+ if (!Array.isArray(parsed))
3199
+ return [];
3200
+ return parsed.filter((value) => typeof value === 'string').slice(0, KEEP);
3201
+ }
3202
+ catch {
3203
+ return [];
3204
+ }
3205
+ }
3206
+ class NucleusRecentApps {
3207
+ _ids = signal(readIds(), ...(ngDevMode ? [{ debugName: "_ids" }] : /* istanbul ignore next */ []));
3208
+ ids = this._ids.asReadonly();
3209
+ remember(appId) {
3210
+ const key = appId.toLowerCase();
3211
+ this._ids.update((ids) => [key, ...ids.filter((id) => id !== key)].slice(0, KEEP));
3212
+ try {
3213
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(this._ids()));
3214
+ }
3215
+ catch { }
3216
+ }
3217
+ pick(source, limit) {
3218
+ const byId = new Map(source.map((item) => [item.id.toLowerCase(), item]));
3219
+ return this._ids()
3220
+ .map((id) => byId.get(id))
3221
+ .filter((item) => item !== undefined)
3222
+ .slice(0, limit);
3223
+ }
3224
+ clear() {
3225
+ this._ids.set([]);
3226
+ try {
3227
+ localStorage.removeItem(STORAGE_KEY);
3228
+ }
3229
+ catch { }
3230
+ }
3231
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusRecentApps, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3232
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusRecentApps, providedIn: 'root' });
3233
+ }
3234
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusRecentApps, decorators: [{
3235
+ type: Injectable,
3236
+ args: [{ providedIn: 'root' }]
3237
+ }] });
3238
+
1653
3239
  const W = 1280;
1654
3240
  const H = 720;
1655
3241
  const STARS = Array.from({ length: 130 }, (_, i) => {
@@ -1745,10 +3331,17 @@ const SHELL_COPY = {
1745
3331
  navToggle: 'Mostrar u ocultar el menú',
1746
3332
  appsLauncher: 'Aplicaciones',
1747
3333
  appsEmpty: 'No hay otras aplicaciones',
3334
+ appsSwitch: 'Cambiar de aplicación',
3335
+ appsSearch: 'Buscar aplicación',
3336
+ appsNoMatch: 'Ninguna aplicación coincide con lo que escribiste.',
3337
+ appsRecent: 'Recientes',
3338
+ appsAll: 'Todas',
3339
+ appsAvailable: 'Disponibles',
3340
+ appsHere: 'Aquí',
3341
+ keyboardHint: '↑ ↓ moverse · ↵ abrir · esc cerrar',
3342
+ accountSwitchHint: 'Entrar con otro correo',
3343
+ signOutHint: 'Salir de la suite',
1748
3344
  settingsTitle: 'Ajustes',
1749
- settingsTheme: 'Tema',
1750
- settingsThemeHint: 'Elige cómo se ve la suite en este dispositivo.',
1751
- themeSystem: 'Del sistema',
1752
3345
  signOut: 'Cerrar sesión',
1753
3346
  accountManage: 'Gestionar mi cuenta',
1754
3347
  accountSwitch: 'Cambiar de cuenta',
@@ -1757,19 +3350,15 @@ const SHELL_COPY = {
1757
3350
  accountScope: 'Mi cuenta',
1758
3351
  hintComingSoon: 'Próximamente',
1759
3352
  hintNotDeployed: 'Sin publicar',
1760
- switchSite: 'Cambiar sede',
1761
- contextTitle: 'Compañía y sede',
1762
- contextHint: 'Elige dónde quieres trabajar. El menú se carga según tu rol en la sede.',
3353
+ roleCaption: 'Tu rol',
1763
3354
  companyLabel: 'Compañía',
1764
3355
  companyPlaceholder: 'Selecciona una compañía',
1765
3356
  siteLabel: 'Sede',
1766
3357
  sitePlaceholder: 'Selecciona una sede',
1767
3358
  sitesLoading: 'Cargando sedes...',
1768
3359
  sitesEmpty: 'Esta compañía no tiene sedes a tu alcance',
1769
- confirmSwitch: 'Cambiar',
1770
3360
  relationOwner: 'Propietario',
1771
3361
  relationCollaborator: 'Colaborador',
1772
- noContext: 'Sin sede seleccionada',
1773
3362
  noCompanyTitle: 'Te falta una compañía',
1774
3363
  noCompanyHint: 'Para trabajar en las aplicaciones necesitas al menos una compañía con una sede, ' +
1775
3364
  'propia o donde te hayan invitado. Puedes entrar igual, pero el menú saldrá vacío.',
@@ -1778,10 +3367,17 @@ const SHELL_COPY = {
1778
3367
  navToggle: 'Show or hide the menu',
1779
3368
  appsLauncher: 'Applications',
1780
3369
  appsEmpty: 'No other applications',
3370
+ appsSwitch: 'Switch application',
3371
+ appsSearch: 'Search applications',
3372
+ appsNoMatch: 'No application matches what you typed.',
3373
+ appsRecent: 'Recent',
3374
+ appsAll: 'All',
3375
+ appsAvailable: 'Available',
3376
+ appsHere: 'Here',
3377
+ keyboardHint: '↑ ↓ move · ↵ open · esc close',
3378
+ accountSwitchHint: 'Sign in with another address',
3379
+ signOutHint: 'Leave the suite',
1781
3380
  settingsTitle: 'Settings',
1782
- settingsTheme: 'Theme',
1783
- settingsThemeHint: 'Choose how the suite looks on this device.',
1784
- themeSystem: 'System',
1785
3381
  signOut: 'Sign out',
1786
3382
  accountManage: 'Manage my account',
1787
3383
  accountSwitch: 'Switch account',
@@ -1790,29 +3386,29 @@ const SHELL_COPY = {
1790
3386
  accountScope: 'My account',
1791
3387
  hintComingSoon: 'Coming soon',
1792
3388
  hintNotDeployed: 'Not deployed',
1793
- switchSite: 'Switch site',
1794
- contextTitle: 'Company and site',
1795
- contextHint: 'Choose where you want to work. The menu loads from your role at that site.',
3389
+ roleCaption: 'Your role',
1796
3390
  companyLabel: 'Company',
1797
3391
  companyPlaceholder: 'Select a company',
1798
3392
  siteLabel: 'Site',
1799
3393
  sitePlaceholder: 'Select a site',
1800
3394
  sitesLoading: 'Loading sites...',
1801
3395
  sitesEmpty: 'This company has no sites available to you',
1802
- confirmSwitch: 'Switch',
1803
3396
  relationOwner: 'Owner',
1804
3397
  relationCollaborator: 'Collaborator',
1805
- noContext: 'No site selected',
1806
3398
  noCompanyTitle: 'You need a company',
1807
3399
  noCompanyHint: 'To work in the applications you need at least one company with a site, your own or ' +
1808
3400
  'one you have been invited to. You can still open them, but the menu will be empty.',
1809
3401
  },
1810
3402
  };
1811
3403
 
3404
+ const LAUNCHER_RICH_THRESHOLD = 6;
3405
+ const RECENT_LIMIT = 3;
3406
+ const FALLBACK_APP_COLOR = '#64748b';
1812
3407
  class NucleusShell {
1813
3408
  session = inject(SessionService);
1814
3409
  sessionAuth = inject(NucleusSessionAuth);
1815
3410
  themes = inject(ThemeService);
3411
+ recents = inject(NucleusRecentApps);
1816
3412
  language = inject(NucleusLanguage);
1817
3413
  page = inject(NucleusPage);
1818
3414
  config = inject(NUCLEUS_CONFIG);
@@ -1828,6 +3424,7 @@ class NucleusShell {
1828
3424
  menuSelect = output();
1829
3425
  lang = this.language.lang;
1830
3426
  icons = NUCLEUS_ICONS;
3427
+ menuPlaceholders = ['64%', '80%', '52%', '72%', '58%'];
1831
3428
  title = computed(() => this.heading() || this.page.heading(), ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
1832
3429
  subtitle = computed(() => this.lead() || this.page.lead(), ...(ngDevMode ? [{ debugName: "subtitle" }] : /* istanbul ignore next */ []));
1833
3430
  home = computed(() => ({ icon: 'pi pi-home', routerLink: '/' }), ...(ngDevMode ? [{ debugName: "home" }] : /* istanbul ignore next */ []));
@@ -1837,20 +3434,22 @@ class NucleusShell {
1837
3434
  })), ...(ngDevMode ? [{ debugName: "crumbs" }] : /* istanbul ignore next */ []));
1838
3435
  copy = computed(() => SHELL_COPY[this.lang()], ...(ngDevMode ? [{ debugName: "copy" }] : /* istanbul ignore next */ []));
1839
3436
  starfield = this.themes.starfield;
1840
- themePreference = this.themes.preference;
1841
3437
  menu = this.session.menu;
3438
+ collapsedIds = signal(new Set(), ...(ngDevMode ? [{ debugName: "collapsedIds" }] : /* istanbul ignore next */ []));
1842
3439
  menuLoading = this.session.loading;
1843
3440
  menuError = this.session.error;
1844
3441
  launcherApps = this.session.apps;
1845
3442
  switching = this.session.switching;
1846
- sidebarOpen = signal(true, ...(ngDevMode ? [{ debugName: "sidebarOpen" }] : /* istanbul ignore next */ []));
3443
+ narrowQuery = typeof window === 'undefined' ? null : window.matchMedia('(max-width: 720px)');
3444
+ narrow = signal(this.narrowQuery?.matches ?? false, ...(ngDevMode ? [{ debugName: "narrow" }] : /* istanbul ignore next */ []));
3445
+ sidebarOpen = signal(!(this.narrowQuery?.matches ?? false), ...(ngDevMode ? [{ debugName: "sidebarOpen" }] : /* istanbul ignore next */ []));
1847
3446
  settingsOpen = signal(false, ...(ngDevMode ? [{ debugName: "settingsOpen" }] : /* istanbul ignore next */ []));
1848
- contextOpen = signal(false, ...(ngDevMode ? [{ debugName: "contextOpen" }] : /* istanbul ignore next */ []));
1849
3447
  companyChoice = signal(null, ...(ngDevMode ? [{ debugName: "companyChoice" }] : /* istanbul ignore next */ []));
1850
3448
  siteChoice = signal(null, ...(ngDevMode ? [{ debugName: "siteChoice" }] : /* istanbul ignore next */ []));
1851
3449
  siteOptions = signal([], ...(ngDevMode ? [{ debugName: "siteOptions" }] : /* istanbul ignore next */ []));
1852
3450
  sitesLoading = signal(false, ...(ngDevMode ? [{ debugName: "sitesLoading" }] : /* istanbul ignore next */ []));
1853
3451
  auth = this.injector.get(NucleusAuth, null);
3452
+ sitesCompanyId = null;
1854
3453
  identity = computed(() => {
1855
3454
  const me = this.session.me();
1856
3455
  if (me) {
@@ -1879,26 +3478,102 @@ class NucleusShell {
1879
3478
  tag: company.relation === 'owner' ? t.relationOwner : t.relationCollaborator,
1880
3479
  }));
1881
3480
  }, ...(ngDevMode ? [{ debugName: "companyChoices" }] : /* istanbul ignore next */ []));
1882
- contextCaption = computed(() => {
1883
- const role = this.session.roleName();
1884
- const site = this.session.siteName();
1885
- const company = this.session.companyName();
1886
- if (role && site)
1887
- return company ? `${company} · ${site}` : site;
1888
- if (this.session.scope() === 'account')
1889
- return this.copy().accountScope;
1890
- return this.copy().noContext;
1891
- }, ...(ngDevMode ? [{ debugName: "contextCaption" }] : /* istanbul ignore next */ []));
1892
3481
  roleName = this.session.roleName;
1893
- canConfirm = computed(() => this.companyChoice() !== null && this.siteChoice() !== null && !this.switching(), ...(ngDevMode ? [{ debugName: "canConfirm" }] : /* istanbul ignore next */ []));
1894
- themeOptions = computed(() => [
1895
- ...THEMES.map((theme) => ({
1896
- value: theme.id,
1897
- label: THEME_LABELS[theme.id],
1898
- swatch: theme.swatch,
1899
- })),
1900
- { value: 'system', label: this.copy().themeSystem, swatch: null },
1901
- ], ...(ngDevMode ? [{ debugName: "themeOptions" }] : /* istanbul ignore next */ []));
3482
+ words = computed(() => commonCopyOf(this.lang()), ...(ngDevMode ? [{ debugName: "words" }] : /* istanbul ignore next */ []));
3483
+ hasSiteContext = computed(() => this.session.scope() === 'site', ...(ngDevMode ? [{ debugName: "hasSiteContext" }] : /* istanbul ignore next */ []));
3484
+ launcherRich = computed(() => this.launcherApps().length > LAUNCHER_RICH_THRESHOLD, ...(ngDevMode ? [{ debugName: "launcherRich" }] : /* istanbul ignore next */ []));
3485
+ launcherCount = computed(() => {
3486
+ const total = this.launcherApps().length + (this.session.currentApp() ? 1 : 0);
3487
+ return total > LAUNCHER_RICH_THRESHOLD ? String(total) : null;
3488
+ }, ...(ngDevMode ? [{ debugName: "launcherCount" }] : /* istanbul ignore next */ []));
3489
+ launcherSections = computed(() => {
3490
+ const t = this.copy();
3491
+ const apps = this.launcherApps();
3492
+ const current = this.session.currentApp();
3493
+ const sections = [];
3494
+ if (current) {
3495
+ sections.push({ id: 'current', label: null, rows: [this.appRow(current, true)] });
3496
+ }
3497
+ if (!apps.length)
3498
+ return sections;
3499
+ if (this.launcherRich()) {
3500
+ const recent = this.recents.pick(apps, RECENT_LIMIT);
3501
+ if (recent.length) {
3502
+ sections.push({
3503
+ id: 'recent',
3504
+ label: t.appsRecent,
3505
+ rows: recent.map((app) => this.appRow(app, false)),
3506
+ });
3507
+ }
3508
+ sections.push({
3509
+ id: 'all',
3510
+ label: t.appsAll,
3511
+ rows: apps.map((app) => this.appRow(app, false)),
3512
+ });
3513
+ return sections;
3514
+ }
3515
+ sections.push({
3516
+ id: 'all',
3517
+ label: current ? t.appsAvailable : null,
3518
+ rows: apps.map((app) => this.appRow(app, false)),
3519
+ });
3520
+ return sections;
3521
+ }, ...(ngDevMode ? [{ debugName: "launcherSections" }] : /* istanbul ignore next */ []));
3522
+ accountSections = computed(() => {
3523
+ const t = this.copy();
3524
+ const rows = [];
3525
+ if (this.canSwitchAccount()) {
3526
+ rows.push({
3527
+ id: 'switch',
3528
+ name: t.accountSwitch,
3529
+ meta: t.accountSwitchHint,
3530
+ tile: menuGlyph('pi pi-users'),
3531
+ tag: null,
3532
+ current: false,
3533
+ disabled: false,
3534
+ });
3535
+ }
3536
+ rows.push({
3537
+ id: 'signout',
3538
+ name: t.signOut,
3539
+ meta: t.signOutHint,
3540
+ tile: menuGlyph('pi pi-sign-out'),
3541
+ tag: null,
3542
+ current: false,
3543
+ disabled: false,
3544
+ });
3545
+ return [{ id: 'actions', label: null, rows }];
3546
+ }, ...(ngDevMode ? [{ debugName: "accountSections" }] : /* istanbul ignore next */ []));
3547
+ accountContext = computed(() => {
3548
+ const company = this.companyChoices().find((item) => item.id === this.companyChoice());
3549
+ const site = this.session.siteName();
3550
+ if (company && site)
3551
+ return `${company.name} · ${site}`;
3552
+ if (company)
3553
+ return company.name;
3554
+ return this.copy().accountScope;
3555
+ }, ...(ngDevMode ? [{ debugName: "accountContext" }] : /* istanbul ignore next */ []));
3556
+ sitePlaceholder = computed(() => {
3557
+ const t = this.copy();
3558
+ if (this.sitesLoading())
3559
+ return t.sitesLoading;
3560
+ if (!this.siteOptions().length)
3561
+ return t.sitesEmpty;
3562
+ return t.sitePlaceholder;
3563
+ }, ...(ngDevMode ? [{ debugName: "sitePlaceholder" }] : /* istanbul ignore next */ []));
3564
+ topbarHeight = computed(() => this.narrow() && this.hasSiteContext() ? '100px' : '56px', ...(ngDevMode ? [{ debugName: "topbarHeight" }] : /* istanbul ignore next */ []));
3565
+ syncContext = effect(() => {
3566
+ const companyId = this.session.companyId();
3567
+ const siteId = this.session.siteId();
3568
+ untracked(() => {
3569
+ this.companyChoice.set(companyId);
3570
+ this.siteChoice.set(siteId);
3571
+ if (companyId)
3572
+ void this.loadSites(companyId);
3573
+ else
3574
+ this.siteOptions.set([]);
3575
+ });
3576
+ }, ...(ngDevMode ? [{ debugName: "syncContext" }] : /* istanbul ignore next */ []));
1902
3577
  loadSession = effect(() => {
1903
3578
  const language = this.lang();
1904
3579
  const user = this.auth?.user() ?? null;
@@ -1906,49 +3581,97 @@ class NucleusShell {
1906
3581
  return;
1907
3582
  untracked(() => void this.session.load(language));
1908
3583
  }, ...(ngDevMode ? [{ debugName: "loadSession" }] : /* istanbul ignore next */ []));
3584
+ constructor() {
3585
+ const query = this.narrowQuery;
3586
+ if (!query)
3587
+ return;
3588
+ const onChange = (event) => {
3589
+ this.narrow.set(event.matches);
3590
+ this.sidebarOpen.set(!event.matches);
3591
+ };
3592
+ query.addEventListener('change', onChange);
3593
+ inject(DestroyRef).onDestroy(() => query.removeEventListener('change', onChange));
3594
+ }
1909
3595
  setLang(lang) {
1910
3596
  this.language.setLang(lang);
1911
3597
  }
1912
3598
  toggleSidebar() {
1913
3599
  this.sidebarOpen.update((open) => !open);
1914
3600
  }
1915
- selectTheme(preference) {
1916
- if (preference)
1917
- this.themes.setPreference(preference);
1918
- }
1919
3601
  selectNav(id) {
1920
3602
  this.navSelect.emit(id);
3603
+ this.closeSidebarOnNarrow();
3604
+ }
3605
+ isGroup(node) {
3606
+ return node.children.length > 0;
3607
+ }
3608
+ isExpanded(node) {
3609
+ return !this.collapsedIds().has(node.id);
1921
3610
  }
1922
3611
  selectMenu(node) {
3612
+ if (this.isGroup(node)) {
3613
+ this.collapsedIds.update((ids) => {
3614
+ const next = new Set(ids);
3615
+ if (!next.delete(node.id))
3616
+ next.add(node.id);
3617
+ return next;
3618
+ });
3619
+ return;
3620
+ }
3621
+ if (!node.route)
3622
+ return;
1923
3623
  this.menuSelect.emit(node);
1924
- if (node.route)
1925
- void this.router.navigateByUrl(node.route);
3624
+ void this.router.navigateByUrl(node.route);
3625
+ this.closeSidebarOnNarrow();
3626
+ }
3627
+ closeSidebarOnNarrow() {
3628
+ if (this.narrow())
3629
+ this.sidebarOpen.set(false);
1926
3630
  }
1927
3631
  isSelected(node) {
1928
3632
  return this.selectedMenuId() === node.id;
1929
3633
  }
1930
- async openContext() {
1931
- const companyId = this.session.companyId() ?? this.companyChoices()[0]?.id ?? null;
1932
- this.companyChoice.set(companyId);
1933
- this.siteChoice.set(this.session.siteId());
1934
- this.contextOpen.set(true);
1935
- if (companyId)
1936
- await this.loadSites(companyId);
1937
- }
1938
3634
  async onCompanyChange(companyId) {
3635
+ if (!companyId || companyId === this.session.companyId())
3636
+ return;
1939
3637
  this.companyChoice.set(companyId);
1940
3638
  this.siteChoice.set(null);
1941
- this.siteOptions.set([]);
1942
- if (companyId)
1943
- await this.loadSites(companyId);
3639
+ const sites = await this.loadSites(companyId);
3640
+ const first = sites[0]?.id ?? null;
3641
+ this.siteChoice.set(first);
3642
+ if (first)
3643
+ await this.session.switchContext(companyId, first);
1944
3644
  }
1945
- async confirmSwitch() {
3645
+ async onSiteChange(siteId) {
1946
3646
  const companyId = this.companyChoice();
1947
- const siteId = this.siteChoice();
1948
- if (!companyId || !siteId)
3647
+ if (!siteId || !companyId || siteId === this.session.siteId())
1949
3648
  return;
3649
+ this.siteChoice.set(siteId);
1950
3650
  await this.session.switchContext(companyId, siteId);
1951
- this.contextOpen.set(false);
3651
+ }
3652
+ appRow(app, current) {
3653
+ const hint = current ? this.copy().appsHere : this.launchHint(app);
3654
+ return {
3655
+ id: app.id,
3656
+ name: app.name,
3657
+ meta: app.description,
3658
+ tile: menuTile(initialsOf(app.name), app.icon, app.color ?? FALLBACK_APP_COLOR),
3659
+ tag: hint || null,
3660
+ current,
3661
+ disabled: !current && !this.canOpen(app),
3662
+ };
3663
+ }
3664
+ onLauncherSelect(row) {
3665
+ const app = this.launcherApps().find((item) => item.id === row.id);
3666
+ if (app)
3667
+ this.launch(app);
3668
+ }
3669
+ onAccountSelect(row) {
3670
+ if (row.id === 'switch') {
3671
+ void this.switchAccount();
3672
+ return;
3673
+ }
3674
+ void this.signOut();
1952
3675
  }
1953
3676
  canOpen(app) {
1954
3677
  return app.status === 1 && !!app.url;
@@ -1956,6 +3679,7 @@ class NucleusShell {
1956
3679
  launch(app) {
1957
3680
  if (!this.canOpen(app))
1958
3681
  return;
3682
+ this.recents.remember(app.id);
1959
3683
  window.location.href = app.url;
1960
3684
  }
1961
3685
  appInitials(app) {
@@ -1980,24 +3704,27 @@ class NucleusShell {
1980
3704
  window.location.href = nucleusLogoutUrl(landing);
1981
3705
  }
1982
3706
  async loadSites(companyId) {
3707
+ if (this.sitesCompanyId === companyId)
3708
+ return this.siteOptions();
3709
+ this.sitesCompanyId = companyId;
1983
3710
  this.sitesLoading.set(true);
1984
3711
  try {
1985
3712
  const sites = await this.session.accessibleSites(companyId);
1986
3713
  this.siteOptions.set(sites);
1987
- if (!sites.some((site) => site.id === this.siteChoice())) {
1988
- this.siteChoice.set(sites[0]?.id ?? null);
1989
- }
3714
+ return sites;
1990
3715
  }
1991
3716
  catch (error) {
3717
+ this.sitesCompanyId = null;
1992
3718
  this.siteOptions.set([]);
1993
3719
  console.error('[shell] no se pudieron cargar las sedes', error);
3720
+ return [];
1994
3721
  }
1995
3722
  finally {
1996
3723
  this.sitesLoading.set(false);
1997
3724
  }
1998
3725
  }
1999
3726
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusShell, deps: [], target: i0.ɵɵFactoryTarget.Component });
2000
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: NucleusShell, isStandalone: true, selector: "nucleus-shell", inputs: { brand: { classPropertyName: "brand", publicName: "brand", isSignal: true, isRequired: false, transformFunction: null }, heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: false, transformFunction: null }, lead: { classPropertyName: "lead", publicName: "lead", isSignal: true, isRequired: false, transformFunction: null }, navItems: { classPropertyName: "navItems", publicName: "navItems", isSignal: true, isRequired: false, transformFunction: null }, activeNavId: { classPropertyName: "activeNavId", publicName: "activeNavId", isSignal: true, isRequired: false, transformFunction: null }, selectedMenuId: { classPropertyName: "selectedMenuId", publicName: "selectedMenuId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { navSelect: "navSelect", menuSelect: "menuSelect" }, ngImport: i0, template: "@let t = copy();\n@let me = identity();\n\n@if (starfield()) {\n <nucleus-starfield />\n}\n\n<header class=\"topbar\">\n <div class=\"side\">\n <p-button\n icon=\"pi pi-bars\"\n severity=\"secondary\"\n size=\"small\"\n [text]=\"true\"\n [rounded]=\"true\"\n [ariaLabel]=\"t.navToggle\"\n [pTooltip]=\"t.navToggle\"\n tooltipPosition=\"bottom\"\n (onClick)=\"toggleSidebar()\"\n />\n\n <div class=\"brand\">\n <nucleus-mark [size]=\"20\" />\n <div class=\"brand-name\">\n <span class=\"brand-primary\">NUCLEUS</span>\n <span class=\"brand-secondary\">{{ brand() }}</span>\n </div>\n </div>\n </div>\n\n <div class=\"side\">\n <div class=\"lang-switch\" role=\"group\" aria-label=\"Idioma / Language\">\n <button\n type=\"button\"\n class=\"lang-pill\"\n [class.is-active]=\"lang() === 'es'\"\n [attr.aria-pressed]=\"lang() === 'es'\"\n (click)=\"setLang('es')\"\n >\n ES\n </button>\n <button\n type=\"button\"\n class=\"lang-pill\"\n [class.is-active]=\"lang() === 'en'\"\n [attr.aria-pressed]=\"lang() === 'en'\"\n (click)=\"setLang('en')\"\n >\n EN\n </button>\n </div>\n\n <p-button\n icon=\"pi pi-th-large\"\n severity=\"secondary\"\n size=\"small\"\n [text]=\"true\"\n [rounded]=\"true\"\n [ariaLabel]=\"t.appsLauncher\"\n [pTooltip]=\"t.appsLauncher\"\n tooltipPosition=\"bottom\"\n (onClick)=\"launcher.toggle($event)\"\n />\n\n <p-popover #launcher>\n <div class=\"launcher\">\n <p class=\"launcher-title\">{{ t.appsLauncher }}</p>\n\n @if (!hasCompany()) {\n <div class=\"launcher-notice\">\n <i class=\"pi pi-building\" aria-hidden=\"true\"></i>\n <div>\n <p class=\"launcher-notice-title\">{{ t.noCompanyTitle }}</p>\n <p class=\"launcher-notice-body\">{{ t.noCompanyHint }}</p>\n </div>\n </div>\n }\n\n @if (!launcherApps().length) {\n <p class=\"launcher-empty\">{{ t.appsEmpty }}</p>\n }\n\n <div class=\"launcher-grid\">\n @for (app of launcherApps(); track app.id) {\n <button\n type=\"button\"\n class=\"launcher-item\"\n [class.is-disabled]=\"!canOpen(app)\"\n [disabled]=\"!canOpen(app)\"\n [pTooltip]=\"launchHint(app)\"\n tooltipPosition=\"bottom\"\n (click)=\"launch(app); launcher.hide()\"\n >\n <span class=\"launcher-icon\" [style.background]=\"app.color ?? '#64748b'\">\n @if (app.icon) {\n <i [class]=\"app.icon\" aria-hidden=\"true\"></i>\n } @else {\n <span class=\"launcher-initials\">{{ appInitials(app) }}</span>\n }\n </span>\n <span class=\"launcher-name\">{{ app.name }}</span>\n @if (launchHint(app)) {\n <span class=\"launcher-badge\">{{ launchHint(app) }}</span>\n }\n </button>\n }\n </div>\n </div>\n </p-popover>\n\n <p-button\n icon=\"pi pi-cog\"\n severity=\"secondary\"\n size=\"small\"\n [text]=\"true\"\n [rounded]=\"true\"\n [ariaLabel]=\"t.settingsTitle\"\n [pTooltip]=\"t.settingsTitle\"\n tooltipPosition=\"bottom\"\n (onClick)=\"settingsOpen.set(true)\"\n />\n\n @if (me) {\n <p-button\n severity=\"secondary\"\n size=\"small\"\n [text]=\"true\"\n [rounded]=\"true\"\n styleClass=\"avatar-trigger\"\n [ariaLabel]=\"me.name\"\n [pTooltip]=\"me.name\"\n tooltipPosition=\"bottom\"\n (onClick)=\"account.toggle($event)\"\n >\n <p-avatar\n [image]=\"me.photoUrl ?? undefined\"\n [label]=\"me.photoUrl ? undefined : initials()\"\n shape=\"circle\"\n size=\"normal\"\n />\n </p-button>\n }\n </div>\n</header>\n\n<p-popover #account>\n @if (me) {\n <div class=\"account\">\n <p class=\"account-email\">{{ me.email }}</p>\n\n <div class=\"account-head\">\n @if (me.photoUrl) {\n <img class=\"account-photo\" [src]=\"me.photoUrl\" alt=\"\" width=\"72\" height=\"72\" />\n } @else {\n <span class=\"account-photo account-initials\">{{ initials() }}</span>\n }\n <p class=\"account-hi\">{{ me.name }}</p>\n </div>\n\n <div class=\"account-context\">\n @if (roleName()) {\n <p-tag [value]=\"roleName()!\" severity=\"secondary\" />\n }\n <p class=\"account-place\">{{ contextCaption() }}</p>\n <p-button\n [label]=\"t.switchSite\"\n icon=\"pi pi-building\"\n severity=\"secondary\"\n size=\"small\"\n [outlined]=\"true\"\n [rounded]=\"true\"\n [fluid]=\"true\"\n (onClick)=\"openContext(); account.hide()\"\n />\n </div>\n\n <div class=\"account-actions\">\n @if (canSwitchAccount()) {\n <p-button\n [label]=\"t.accountSwitch\"\n icon=\"pi pi-users\"\n severity=\"secondary\"\n size=\"small\"\n [text]=\"true\"\n [fluid]=\"true\"\n (onClick)=\"switchAccount(); account.hide()\"\n />\n }\n <p-button\n [label]=\"t.signOut\"\n icon=\"pi pi-sign-out\"\n severity=\"secondary\"\n size=\"small\"\n [text]=\"true\"\n [fluid]=\"true\"\n (onClick)=\"signOut(); account.hide()\"\n />\n </div>\n </div>\n }\n</p-popover>\n\n<div class=\"body\">\n <aside class=\"sidebar\" [class.is-collapsed]=\"!sidebarOpen()\">\n @if (navItems().length) {\n <nav>\n @for (item of navItems(); track item.id) {\n <button\n type=\"button\"\n class=\"nav-item\"\n [class.is-active]=\"activeNavId() === item.id\"\n [attr.aria-current]=\"activeNavId() === item.id ? 'page' : null\"\n [pTooltip]=\"sidebarOpen() ? '' : item.label\"\n tooltipPosition=\"right\"\n (click)=\"selectNav(item.id)\"\n >\n <i [class]=\"item.icon\" aria-hidden=\"true\"></i>\n <span class=\"nav-label\">{{ item.label }}</span>\n </button>\n }\n </nav>\n }\n\n <nav class=\"access-menu\">\n @if (menuLoading()) {\n <p class=\"access-state\">{{ t.accessLoading }}</p>\n } @else if (menuError()) {\n <p class=\"access-state is-error\">{{ menuError() }}</p>\n } @else if (!menu().length) {\n <p class=\"access-state\">{{ t.accessEmpty }}</p>\n } @else {\n @for (node of menu(); track node.id) {\n <button\n type=\"button\"\n class=\"nav-item\"\n [class.is-active]=\"isSelected(node)\"\n [pTooltip]=\"sidebarOpen() ? '' : node.label\"\n tooltipPosition=\"right\"\n (click)=\"selectMenu(node)\"\n >\n <i [class]=\"node.icon ?? 'pi pi-circle'\" aria-hidden=\"true\"></i>\n <span class=\"nav-label\">{{ node.label }}</span>\n </button>\n\n @for (child of node.children; track child.id) {\n <button\n type=\"button\"\n class=\"nav-item is-child\"\n [class.is-active]=\"isSelected(child)\"\n [pTooltip]=\"sidebarOpen() ? '' : child.label\"\n tooltipPosition=\"right\"\n (click)=\"selectMenu(child)\"\n >\n <i [class]=\"child.icon ?? 'pi pi-circle'\" aria-hidden=\"true\"></i>\n <span class=\"nav-label\">{{ child.label }}</span>\n </button>\n }\n }\n }\n </nav>\n </aside>\n\n <main class=\"content\">\n <div class=\"measure\">\n <ng-content select=\"[nucleusTop]\" />\n\n @if (crumbs().length) {\n <p-breadcrumb [model]=\"crumbs()\" [home]=\"home()\" styleClass=\"ns-crumbs\" />\n }\n\n @if (title()) {\n <h1>{{ title() }}</h1>\n }\n @if (subtitle()) {\n <p class=\"lead\">{{ subtitle() }}</p>\n }\n\n <ng-content />\n </div>\n </main>\n</div>\n\n<p-drawer\n [visible]=\"contextOpen()\"\n (visibleChange)=\"contextOpen.set($event)\"\n position=\"right\"\n [header]=\"t.contextTitle\"\n [style]=\"{ width: '380px' }\"\n>\n <p class=\"settings-hint\">{{ t.contextHint }}</p>\n\n <label class=\"field-label\" for=\"nucleus-company\">{{ t.companyLabel }}</label>\n <p-select\n inputId=\"nucleus-company\"\n [options]=\"companyChoices()\"\n optionValue=\"id\"\n optionLabel=\"name\"\n [ngModel]=\"companyChoice()\"\n (ngModelChange)=\"onCompanyChange($event)\"\n [placeholder]=\"t.companyPlaceholder\"\n [fluid]=\"true\"\n >\n <ng-template #item let-option>\n <span class=\"company-option\">\n <span class=\"company-name\">{{ option.name }}</span>\n <p-tag [value]=\"option.tag\" severity=\"secondary\" />\n </span>\n </ng-template>\n </p-select>\n\n <label class=\"field-label\" for=\"nucleus-site\">{{ t.siteLabel }}</label>\n\n @if (sitesLoading()) {\n <p class=\"access-state\">{{ t.sitesLoading }}</p>\n } @else if (!siteOptions().length) {\n <p class=\"access-state\">{{ t.sitesEmpty }}</p>\n } @else {\n <p-select\n inputId=\"nucleus-site\"\n [options]=\"siteOptions()\"\n optionValue=\"id\"\n optionLabel=\"name\"\n [ngModel]=\"siteChoice()\"\n (ngModelChange)=\"siteChoice.set($event)\"\n [placeholder]=\"t.sitePlaceholder\"\n [fluid]=\"true\"\n />\n }\n\n <div class=\"context-actions\">\n <p-button\n [label]=\"t.confirmSwitch\"\n icon=\"pi pi-arrow-right\"\n [disabled]=\"!canConfirm()\"\n [loading]=\"switching()\"\n [fluid]=\"true\"\n (onClick)=\"confirmSwitch()\"\n />\n </div>\n</p-drawer>\n\n<p-drawer\n [visible]=\"settingsOpen()\"\n (visibleChange)=\"settingsOpen.set($event)\"\n position=\"right\"\n [header]=\"t.settingsTitle\"\n [style]=\"{ width: '340px' }\"\n>\n <h3 class=\"settings-heading\">{{ t.settingsTheme }}</h3>\n <p class=\"settings-hint\">{{ t.settingsThemeHint }}</p>\n\n <p-listbox\n [options]=\"themeOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n [ngModel]=\"themePreference()\"\n (ngModelChange)=\"selectTheme($event)\"\n [ariaLabel]=\"t.settingsTheme\"\n >\n <ng-template #item let-option>\n <span class=\"theme-option\">\n @if (option.swatch) {\n <span class=\"theme-swatch\" [style.background]=\"option.swatch\" aria-hidden=\"true\"></span>\n } @else {\n <i class=\"pi pi-desktop theme-swatch-icon\" aria-hidden=\"true\"></i>\n }\n <span>{{ option.label }}</span>\n </span>\n </ng-template>\n </p-listbox>\n</p-drawer>\n\n<p-toast position=\"bottom-right\" styleClass=\"ns-toast\">\n <ng-template #message let-message>\n <nucleus-lordicon\n [src]=\"message.severity === 'success' ? icons.check : icons.warning\"\n [tone]=\"message.severity === 'success' ? 'accent' : 'danger'\"\n [size]=\"34\"\n trigger=\"in\"\n />\n <div class=\"toast-text\">\n <p class=\"toast-summary\">{{ message.summary }}</p>\n @if (message.detail) {\n <p class=\"toast-detail\">{{ message.detail }}</p>\n }\n </div>\n </ng-template>\n</p-toast>\n\n<p-confirmdialog [style]=\"{ width: '420px' }\">\n <ng-template #headless let-message let-onAccept=\"onAccept\" let-onReject=\"onReject\">\n <div class=\"confirm\">\n <nucleus-lordicon\n [src]=\"message.icon\"\n [tone]=\"message.acceptButtonStyleClass ? 'danger' : 'accent'\"\n [size]=\"64\"\n trigger=\"loop\"\n />\n <h2>{{ message.header }}</h2>\n <p>{{ message.message }}</p>\n <div class=\"confirm-actions\">\n <p-button\n [label]=\"message.rejectLabel\"\n severity=\"secondary\"\n [text]=\"true\"\n (onClick)=\"onReject()\"\n />\n <p-button\n [label]=\"message.acceptLabel\"\n [severity]=\"message.acceptButtonStyleClass ? 'danger' : 'primary'\"\n (onClick)=\"onAccept()\"\n />\n </div>\n </div>\n </ng-template>\n</p-confirmdialog>\n", styles: [":host{display:flex;flex-direction:column;min-height:100dvh;background:var(--ac-bg);color:var(--ac-text)}.topbar{position:sticky;top:0;z-index:20;display:flex;align-items:center;justify-content:space-between;gap:16px;height:56px;padding:0 12px 0 8px;background:var(--ac-surface);border-bottom:1px solid var(--ac-border)}.side{display:flex;align-items:center;gap:4px}.brand{display:flex;align-items:center;gap:9px;margin-left:4px}.brand-name{display:flex;align-items:baseline;gap:7px}.brand-primary{font-size:13px;font-weight:700;letter-spacing:.15em}.brand-secondary{font-size:9px;font-weight:500;letter-spacing:.22em;color:var(--ac-text-muted)}.lang-switch{display:flex;align-items:center;gap:2px;margin-right:6px;border:1px solid var(--ac-border);border-radius:999px;padding:2px}.lang-pill{font-size:10px;font-weight:600;letter-spacing:.08em;padding:4px 10px;border:none;border-radius:999px;background:transparent;color:var(--ac-text-muted);cursor:pointer}.lang-pill.is-active{background:var(--ac-accent);color:var(--ac-on-accent)}.launcher{width:320px;padding:4px}.launcher-title{margin:0 0 10px;font-size:12px;font-weight:600;color:var(--ac-text-muted)}.launcher-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:4px}.launcher-item{position:relative;display:flex;flex-direction:column;align-items:center;gap:7px;padding:12px 6px;border:none;border-radius:8px;background:transparent;color:var(--ac-text);cursor:pointer}.launcher-item:hover:not(:disabled){background:var(--ac-surface-alt)}.launcher-item.is-disabled{opacity:.5;cursor:default}.launcher-icon{display:inline-flex;align-items:center;justify-content:center;width:40px;height:40px;border-radius:10px;color:#fff}.launcher-icon i{font-size:18px}.launcher-name{font-size:11px;font-weight:500;line-height:1.3;text-align:center}.launcher-badge{font-size:9px;font-weight:500;color:var(--ac-text-muted)}.account{width:300px;padding:4px;text-align:center}.account-email{margin:0 0 12px;font-size:12px;color:var(--ac-text-muted)}.account-head{display:flex;flex-direction:column;align-items:center;gap:8px;padding-bottom:14px;border-bottom:1px solid var(--ac-border)}.account-photo{width:72px;height:72px;border-radius:999px;object-fit:cover}.account-initials{display:inline-flex;align-items:center;justify-content:center;background:var(--ac-accent);color:var(--ac-on-accent);font-size:26px;font-weight:600}.account-hi{margin:0;font-size:16px;font-weight:500}.account-actions{display:flex;flex-direction:column;gap:2px;padding-top:8px}.body{display:flex;flex:1;min-height:0}.sidebar{flex:none;width:224px;padding:12px 10px;background:var(--ac-surface);border-right:1px solid var(--ac-border);transition:width .18s ease}.sidebar.is-collapsed{width:60px}.sidebar nav{display:flex;flex-direction:column;gap:3px}.nav-item{display:flex;align-items:center;gap:12px;width:100%;padding:9px 12px;border:none;border-radius:6px;background:transparent;color:var(--ac-text-muted);font-size:13px;font-weight:500;text-align:left;white-space:nowrap;cursor:pointer}.nav-item i{flex:none;font-size:15px}.nav-item:hover{background:var(--ac-surface-alt);color:var(--ac-text)}.nav-item.is-active{background:color-mix(in srgb,var(--ac-accent) 18%,transparent);color:var(--ac-text)}.nav-item.is-active i{color:var(--ac-accent)}.sidebar.is-collapsed .nav-label{display:none}.sidebar.is-collapsed .nav-item{justify-content:center;padding-inline:0}.content{flex:1;min-width:0;padding:28px 28px 64px;display:flex;flex-direction:column;gap:14px}.measure{display:flex;width:100%;max-width:var(--ac-content-max, 1440px);flex-direction:column;gap:14px;margin-inline:auto}h1{margin:0;font-size:26px;line-height:1.2;font-weight:500;letter-spacing:-.02em}.lead{margin:0;font-size:14px;line-height:1.6;font-weight:300;color:var(--ac-text-muted)}.settings-heading{margin:0 0 4px;font-size:14px;font-weight:600}.settings-hint{margin:0 0 14px;font-size:12px;font-weight:300;color:var(--ac-text-muted)}.theme-option{display:flex;align-items:center;gap:10px}.theme-swatch{flex:none;width:16px;height:16px;border-radius:3px;border:1px solid var(--ac-border)}.theme-swatch-icon{flex:none;width:16px;font-size:14px;color:var(--ac-text-muted)}.access-menu{display:flex;flex-direction:column;gap:.15rem;margin-top:.75rem;padding-top:.75rem;border-top:1px solid color-mix(in srgb,currentColor 12%,transparent)}.access-caption{margin:0 0 .4rem;padding:0 .75rem;font-size:.68rem;letter-spacing:.06em;text-transform:uppercase;opacity:.65;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.access-state{margin:0;padding:.35rem .75rem;font-size:.78rem;opacity:.7}.access-state.is-error{color:var(--p-red-400, #f87171);opacity:1}.nav-item.is-child{padding-left:2.1rem;font-size:.86rem}.launcher-initials{font-size:.85rem;font-weight:600;letter-spacing:.02em;line-height:1;color:#fff}.launcher-empty{margin:.25rem 0 .5rem;font-size:.82rem;opacity:.7}@media(max-width:720px){.sidebar{width:60px}.sidebar .nav-label{display:none}.sidebar .nav-item{justify-content:center;padding-inline:0}.brand-secondary{display:none}.content{padding:20px 16px 48px}h1{font-size:22px}.launcher{width:260px}}.account-context{display:flex;flex-direction:column;align-items:center;gap:8px;padding:14px 0;border-top:1px solid var(--ac-border);border-bottom:1px solid var(--ac-border)}.account-place{margin:0;color:var(--ac-text-muted);font-size:12px;text-align:center}.field-label{display:block;margin:18px 0 6px;color:var(--ac-text-muted);font-size:12px;letter-spacing:.04em;text-transform:uppercase}.company-option{display:flex;align-items:center;justify-content:space-between;gap:12px;width:100%}.company-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.context-actions{margin-top:24px}.launcher-notice{display:flex;gap:10px;margin-bottom:14px;padding:12px;border:1px solid color-mix(in srgb,var(--ac-accent) 40%,transparent);border-radius:8px;background:color-mix(in srgb,var(--ac-accent) 10%,transparent)}.launcher-notice i{margin-top:2px;color:var(--ac-accent);font-size:15px}.launcher-notice-title{margin:0 0 4px;font-size:13px;font-weight:600}.launcher-notice-body{margin:0;color:var(--ac-text-muted);font-size:12px;line-height:1.45}.confirm{display:flex;flex-direction:column;align-items:center;gap:6px;padding:26px 24px 20px;border:1px solid var(--ac-border);border-radius:14px;background:var(--ac-surface);text-align:center}.confirm h2{margin:8px 0 0;font-size:17px;font-weight:600}.confirm p{margin:0;max-width:34ch;color:var(--ac-text-muted);font-size:13px;line-height:1.5}.confirm-actions{display:flex;justify-content:center;gap:8px;margin-top:18px}.toast-text{flex:1;margin-left:12px}.toast-summary{margin:0;font-size:13px;font-weight:600}.toast-detail{margin:2px 0 0;color:var(--ac-text-muted);font-size:12px}\n"], dependencies: [{ kind: "ngmodule", type: AvatarModule }, { kind: "component", type: i1$3.Avatar, selector: "p-avatar", inputs: ["label", "icon", "image", "size", "shape", "styleClass", "ariaLabel", "ariaLabelledBy"], outputs: ["onImageError"] }, { kind: "ngmodule", type: BreadcrumbModule }, { kind: "component", type: i2$2.Breadcrumb, selector: "p-breadcrumb", inputs: ["model", "style", "styleClass", "home", "homeAriaLabel"], outputs: ["onItemClick"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: ConfirmDialogModule }, { kind: "component", type: i4$2.ConfirmDialog, selector: "p-confirmDialog, p-confirmdialog, p-confirm-dialog", inputs: ["header", "icon", "message", "style", "styleClass", "maskStyleClass", "acceptIcon", "acceptLabel", "closeAriaLabel", "acceptAriaLabel", "acceptVisible", "rejectIcon", "rejectLabel", "rejectAriaLabel", "rejectVisible", "acceptButtonStyleClass", "rejectButtonStyleClass", "closeOnEscape", "dismissableMask", "blockScroll", "rtl", "closable", "appendTo", "key", "autoZIndex", "baseZIndex", "transitionOptions", "focusTrap", "defaultFocus", "breakpoints", "modal", "visible", "position", "draggable"], outputs: ["onHide"] }, { kind: "ngmodule", type: ToastModule }, { kind: "component", type: i5$1.Toast, selector: "p-toast", inputs: ["key", "autoZIndex", "baseZIndex", "life", "styleClass", "position", "preventOpenDuplicates", "preventDuplicates", "showTransformOptions", "hideTransformOptions", "showTransitionOptions", "hideTransitionOptions", "motionOptions", "breakpoints"], outputs: ["onClose"] }, { kind: "ngmodule", type: DrawerModule }, { kind: "component", type: i6$1.Drawer, selector: "p-drawer", inputs: ["appendTo", "motionOptions", "blockScroll", "style", "styleClass", "ariaCloseLabel", "autoZIndex", "baseZIndex", "modal", "closeButtonProps", "dismissible", "showCloseIcon", "closeOnEscape", "transitionOptions", "visible", "position", "fullScreen", "header", "maskStyle", "closable"], outputs: ["onShow", "onHide", "visibleChange"] }, { kind: "ngmodule", type: ListboxModule }, { kind: "component", type: i7.Listbox, selector: "p-listbox, p-listBox, p-list-box", inputs: ["hostName", "id", "searchMessage", "emptySelectionMessage", "selectionMessage", "autoOptionFocus", "ariaLabel", "selectOnFocus", "searchLocale", "focusOnHover", "filterMessage", "filterFields", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "scrollHeight", "tabindex", "multiple", "styleClass", "listStyle", "listStyleClass", "readonly", "checkbox", "filter", "filterBy", "filterMatchMode", "filterLocale", "metaKeySelection", "dataKey", "showToggleAll", "optionLabel", "optionValue", "optionGroupChildren", "optionGroupLabel", "optionDisabled", "ariaFilterLabel", "filterPlaceHolder", "emptyFilterMessage", "emptyMessage", "group", "options", "filterValue", "selectAll", "striped", "highlightOnSelect", "checkmark", "dragdrop", "dropListData", "fluid"], outputs: ["onChange", "onClick", "onDblClick", "onFilter", "onFocus", "onBlur", "onSelectAllChange", "onLazyLoad", "onDrop"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "component", type: i8.Popover, selector: "p-popover", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions", "motionOptions"], outputs: ["onShow", "onHide"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i1.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: TagModule }, { kind: "component", type: i3.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i4$1.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i6.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i6.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: NucleusLordicon, selector: "nucleus-lordicon", inputs: ["src", "trigger", "state", "size", "tone"] }, { kind: "component", type: NucleusMark, selector: "nucleus-mark", inputs: ["accent", "size"] }, { kind: "component", type: NucleusStarfield, selector: "nucleus-starfield" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3727
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: NucleusShell, isStandalone: true, selector: "nucleus-shell", inputs: { brand: { classPropertyName: "brand", publicName: "brand", isSignal: true, isRequired: false, transformFunction: null }, heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: false, transformFunction: null }, lead: { classPropertyName: "lead", publicName: "lead", isSignal: true, isRequired: false, transformFunction: null }, navItems: { classPropertyName: "navItems", publicName: "navItems", isSignal: true, isRequired: false, transformFunction: null }, activeNavId: { classPropertyName: "activeNavId", publicName: "activeNavId", isSignal: true, isRequired: false, transformFunction: null }, selectedMenuId: { classPropertyName: "selectedMenuId", publicName: "selectedMenuId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { navSelect: "navSelect", menuSelect: "menuSelect" }, host: { properties: { "style.--ns-topbar-h": "topbarHeight()" } }, ngImport: i0, template: "@let t = copy();\n@let me = identity();\n\n@if (starfield()) {\n <nucleus-starfield />\n}\n\n<header class=\"topbar ns-frame\">\n <div class=\"side\">\n <p-button\n icon=\"pi pi-bars\"\n severity=\"secondary\"\n size=\"small\"\n [text]=\"true\"\n [rounded]=\"true\"\n [ariaLabel]=\"t.navToggle\"\n [pTooltip]=\"t.navToggle\"\n tooltipPosition=\"bottom\"\n (onClick)=\"toggleSidebar()\"\n />\n\n <div class=\"brand\">\n <nucleus-mark [size]=\"20\" />\n <div class=\"brand-name\">\n <span class=\"brand-primary\">NUCLEUS</span>\n <span class=\"brand-secondary\">{{ brand() }}</span>\n </div>\n </div>\n\n </div>\n\n @if (hasSiteContext()) {\n <div class=\"context ns-context\">\n <p-select\n styleClass=\"context-select\"\n [options]=\"companyChoices()\"\n optionValue=\"id\"\n optionLabel=\"name\"\n [ngModel]=\"companyChoice()\"\n (ngModelChange)=\"onCompanyChange($event)\"\n [placeholder]=\"t.companyPlaceholder\"\n [disabled]=\"switching()\"\n [filter]=\"companyChoices().length > 8\"\n filterBy=\"name\"\n [filterPlaceholder]=\"words().search\"\n [autofocusFilter]=\"true\"\n [resetFilterOnHide]=\"true\"\n [ariaLabel]=\"t.companyLabel\"\n [ngModelOptions]=\"{ standalone: true }\"\n >\n <ng-template #item let-option>\n <span class=\"company-option\">\n <span class=\"company-name\">{{ option.name }}</span>\n <p-tag [value]=\"option.tag\" severity=\"secondary\" />\n </span>\n </ng-template>\n </p-select>\n\n <p-select\n styleClass=\"context-select\"\n [options]=\"siteOptions()\"\n optionValue=\"id\"\n optionLabel=\"name\"\n [ngModel]=\"siteChoice()\"\n (ngModelChange)=\"onSiteChange($event)\"\n [placeholder]=\"sitePlaceholder()\"\n [disabled]=\"switching() || sitesLoading() || !siteOptions().length\"\n [filter]=\"siteOptions().length > 8\"\n filterBy=\"name\"\n [filterPlaceholder]=\"words().search\"\n [autofocusFilter]=\"true\"\n [resetFilterOnHide]=\"true\"\n [ariaLabel]=\"t.siteLabel\"\n [ngModelOptions]=\"{ standalone: true }\"\n />\n </div>\n }\n\n <div class=\"side is-end\">\n <div class=\"lang-switch\" role=\"group\" aria-label=\"Idioma / Language\">\n <button\n type=\"button\"\n class=\"lang-pill\"\n [class.is-active]=\"lang() === 'es'\"\n [attr.aria-pressed]=\"lang() === 'es'\"\n (click)=\"setLang('es')\"\n >\n ES\n </button>\n <button\n type=\"button\"\n class=\"lang-pill\"\n [class.is-active]=\"lang() === 'en'\"\n [attr.aria-pressed]=\"lang() === 'en'\"\n (click)=\"setLang('en')\"\n >\n EN\n </button>\n </div>\n\n <p-button\n icon=\"pi pi-th-large\"\n severity=\"secondary\"\n size=\"small\"\n [text]=\"true\"\n [rounded]=\"true\"\n [ariaLabel]=\"t.appsLauncher\"\n [pTooltip]=\"t.appsLauncher\"\n tooltipPosition=\"bottom\"\n (onClick)=\"launcher.toggle($event)\"\n />\n\n <p-popover #launcher styleClass=\"ns-flush-popover\">\n @if (!hasCompany()) {\n <div class=\"launcher-notice\">\n <i class=\"pi pi-building\" aria-hidden=\"true\"></i>\n <div>\n <p class=\"launcher-notice-title\">{{ t.noCompanyTitle }}</p>\n <p class=\"launcher-notice-body\">{{ t.noCompanyHint }}</p>\n </div>\n </div>\n }\n\n <nucleus-menu\n [heading]=\"t.appsSwitch\"\n [badge]=\"launcherCount()\"\n [sections]=\"launcherSections()\"\n [searchable]=\"launcherRich()\"\n [searchPlaceholder]=\"t.appsSearch\"\n [emptyText]=\"launcherRich() ? t.appsNoMatch : t.appsEmpty\"\n [keyboardHint]=\"t.keyboardHint\"\n [width]=\"336\"\n (select)=\"onLauncherSelect($event); launcher.hide()\"\n (dismiss)=\"launcher.hide()\"\n />\n </p-popover>\n\n <p-button\n icon=\"pi pi-cog\"\n severity=\"secondary\"\n size=\"small\"\n [text]=\"true\"\n [rounded]=\"true\"\n [ariaLabel]=\"t.settingsTitle\"\n [pTooltip]=\"t.settingsTitle\"\n tooltipPosition=\"bottom\"\n (onClick)=\"settingsOpen.set(true)\"\n />\n\n @if (me) {\n <p-button\n severity=\"secondary\"\n size=\"small\"\n [text]=\"true\"\n [rounded]=\"true\"\n styleClass=\"avatar-trigger\"\n [ariaLabel]=\"me.name\"\n [pTooltip]=\"me.name\"\n tooltipPosition=\"bottom\"\n (onClick)=\"account.toggle($event)\"\n >\n <p-avatar\n [image]=\"me.photoUrl ?? undefined\"\n [label]=\"me.photoUrl ? undefined : initials()\"\n shape=\"circle\"\n size=\"normal\"\n />\n </p-button>\n }\n </div>\n</header>\n\n<p-popover #account styleClass=\"ns-flush-popover\">\n @if (me) {\n <div class=\"who\">\n @if (me.photoUrl) {\n <img class=\"who-photo\" [src]=\"me.photoUrl\" alt=\"\" width=\"42\" height=\"42\" />\n } @else {\n <span class=\"who-photo who-initials\">{{ initials() }}</span>\n }\n <span class=\"who-text\">\n <span class=\"who-name\">{{ me.name }}</span>\n <span class=\"who-mail\">{{ me.email }}</span>\n </span>\n </div>\n\n <p class=\"who-role\">\n <span class=\"who-dot\" aria-hidden=\"true\"></span>\n @if (roleName(); as role) {\n <b>{{ role }}</b>\n <span class=\"who-sep\">\u00B7</span>\n }\n <span class=\"who-context\">{{ accountContext() }}</span>\n </p>\n\n <nucleus-menu\n [sections]=\"accountSections()\"\n [width]=\"304\"\n (select)=\"onAccountSelect($event); account.hide()\"\n (dismiss)=\"account.hide()\"\n />\n }\n</p-popover>\n\n<div class=\"body\">\n @if (narrow() && sidebarOpen()) {\n <button\n type=\"button\"\n class=\"sidebar-scrim\"\n [attr.aria-label]=\"t.navToggle\"\n (click)=\"toggleSidebar()\"\n ></button>\n }\n\n <aside class=\"sidebar ns-frame\" [class.is-collapsed]=\"!sidebarOpen()\">\n @if (roleName(); as role) {\n <div class=\"role-badge\" [pTooltip]=\"sidebarOpen() ? '' : role\" tooltipPosition=\"right\">\n <i class=\"pi pi-key\" aria-hidden=\"true\"></i>\n <span class=\"nav-label role-text\">\n <span class=\"role-name\">{{ role }}</span>\n <span class=\"role-caption\">{{ t.roleCaption }}</span>\n </span>\n </div>\n }\n\n @if (navItems().length) {\n <nav>\n @for (item of navItems(); track item.id) {\n <button\n type=\"button\"\n class=\"nav-item\"\n [class.is-active]=\"activeNavId() === item.id\"\n [attr.aria-current]=\"activeNavId() === item.id ? 'page' : null\"\n [pTooltip]=\"sidebarOpen() ? '' : item.label\"\n tooltipPosition=\"right\"\n (click)=\"selectNav(item.id)\"\n >\n <i [class]=\"item.icon\" aria-hidden=\"true\"></i>\n <span class=\"nav-label\">{{ item.label }}</span>\n </button>\n }\n </nav>\n }\n\n <nav class=\"access-menu\">\n @if (menuLoading()) {\n <div class=\"nav-skeleton\" role=\"status\" [attr.aria-label]=\"t.accessLoading\">\n @for (width of menuPlaceholders; track $index) {\n <div class=\"nav-skeleton-item\">\n <p-skeleton shape=\"circle\" size=\"15px\" />\n <p-skeleton height=\"9px\" [width]=\"width\" />\n </div>\n }\n </div>\n } @else if (menuError()) {\n <p class=\"access-state is-error\">{{ menuError() }}</p>\n } @else if (!menu().length) {\n <p class=\"access-state\">{{ t.accessEmpty }}</p>\n } @else {\n @for (node of menu(); track node.id) {\n @let group = isGroup(node);\n @let open = isExpanded(node);\n\n <button\n type=\"button\"\n class=\"nav-item\"\n [class.is-active]=\"!group && isSelected(node)\"\n [class.is-inert]=\"!group && !node.route\"\n [attr.aria-expanded]=\"group ? open : null\"\n [disabled]=\"!group && !node.route\"\n [pTooltip]=\"sidebarOpen() ? '' : node.label\"\n tooltipPosition=\"right\"\n (click)=\"selectMenu(node)\"\n >\n <i [class]=\"node.icon ?? 'pi pi-circle'\" aria-hidden=\"true\"></i>\n <span class=\"nav-label\">{{ node.label }}</span>\n @if (group && sidebarOpen()) {\n <i\n class=\"pi nav-caret\"\n [class.pi-chevron-down]=\"open\"\n [class.pi-chevron-right]=\"!open\"\n aria-hidden=\"true\"\n ></i>\n }\n </button>\n\n @if (open) {\n @for (child of node.children; track child.id) {\n <button\n type=\"button\"\n class=\"nav-item is-child\"\n [class.is-active]=\"isSelected(child)\"\n [pTooltip]=\"sidebarOpen() ? '' : child.label\"\n tooltipPosition=\"right\"\n (click)=\"selectMenu(child)\"\n >\n <i [class]=\"child.icon ?? 'pi pi-circle'\" aria-hidden=\"true\"></i>\n <span class=\"nav-label\">{{ child.label }}</span>\n </button>\n }\n }\n }\n }\n </nav>\n </aside>\n\n <main class=\"content\">\n <div class=\"measure\">\n <ng-content select=\"[nucleusTop]\" />\n\n @if (crumbs().length) {\n <p-breadcrumb [model]=\"crumbs()\" [home]=\"home()\" styleClass=\"ns-crumbs\" />\n }\n\n @if (title()) {\n <h1>{{ title() }}</h1>\n }\n @if (subtitle()) {\n <p class=\"lead\">{{ subtitle() }}</p>\n }\n\n <ng-content />\n </div>\n </main>\n</div>\n\n<p-drawer\n [visible]=\"settingsOpen()\"\n (visibleChange)=\"settingsOpen.set($event)\"\n position=\"right\"\n [header]=\"t.settingsTitle\"\n [modal]=\"false\"\n [blockScroll]=\"false\"\n styleClass=\"ns-settings-drawer\"\n [style]=\"{ width: '380px' }\"\n>\n <nucleus-appearance />\n</p-drawer>\n\n<p-toast position=\"bottom-right\" styleClass=\"ns-toast\">\n <ng-template #message let-message>\n <nucleus-lordicon\n [src]=\"message.severity === 'success' ? icons.check : icons.warning\"\n [tone]=\"message.severity === 'success' ? 'accent' : 'danger'\"\n [size]=\"34\"\n trigger=\"in\"\n />\n <div class=\"toast-text\">\n <p class=\"toast-summary\">{{ message.summary }}</p>\n @if (message.detail) {\n <p class=\"toast-detail\">{{ message.detail }}</p>\n }\n </div>\n </ng-template>\n</p-toast>\n\n<p-confirmdialog [style]=\"{ width: '420px' }\">\n <ng-template #headless let-message let-onAccept=\"onAccept\" let-onReject=\"onReject\">\n <div class=\"confirm\">\n <nucleus-lordicon\n [src]=\"message.icon\"\n [tone]=\"message.acceptButtonStyleClass ? 'danger' : 'accent'\"\n [size]=\"64\"\n trigger=\"loop\"\n />\n <h2>{{ message.header }}</h2>\n <p>{{ message.message }}</p>\n <div class=\"confirm-actions\">\n <p-button\n [label]=\"message.rejectLabel\"\n severity=\"secondary\"\n [text]=\"true\"\n (onClick)=\"onReject()\"\n />\n <p-button\n [label]=\"message.acceptLabel\"\n [severity]=\"message.acceptButtonStyleClass ? 'danger' : 'primary'\"\n (onClick)=\"onAccept()\"\n />\n </div>\n </div>\n </ng-template>\n</p-confirmdialog>\n", styles: [":host{display:flex;flex-direction:column;min-height:100dvh;background:var(--ac-bg);color:var(--ac-text)}.topbar{position:sticky;top:0;z-index:20;display:flex;align-items:center;justify-content:flex-start;gap:16px;height:56px;padding:0 12px 0 8px;border-bottom:1px solid var(--ns-frame-line)}.side{display:flex;min-width:0;align-items:center;gap:4px}.side.is-end{margin-left:auto}.brand{display:flex;align-items:center;gap:9px;margin-left:4px}.brand-name{display:flex;align-items:baseline;gap:7px}.brand-primary{font-size:13px;font-weight:700;letter-spacing:.15em}.brand-secondary{font-size:9px;font-weight:500;letter-spacing:.22em;color:var(--ns-frame-muted)}.lang-switch{display:flex;align-items:center;gap:2px;margin-right:6px;border:1px solid var(--ns-frame-line);border-radius:999px;padding:2px}.lang-pill{font-size:10px;font-weight:600;letter-spacing:.08em;padding:4px 10px;border:none;border-radius:999px;background:transparent;color:var(--ns-frame-muted);cursor:pointer}.lang-pill:hover{color:var(--ns-frame-ink)}.lang-pill.is-active{background:var(--ns-frame-glow);color:var(--ns-frame-on-glow)}.body{display:flex;flex:1;min-height:0}.sidebar{flex:none;width:max-content;min-width:224px;max-width:340px;padding:12px 10px;border-right:1px solid var(--ns-frame-line);transition:width .18s ease}.sidebar.is-collapsed{width:60px;min-width:0}.sidebar nav{display:flex;flex-direction:column;gap:3px}.nav-item{display:flex;align-items:center;gap:12px;width:100%;padding:var(--ns-nav-y, 9px) 12px;border:none;border-radius:6px;background:transparent;color:var(--ns-frame-muted);font-size:13px;font-weight:500;text-align:left;white-space:nowrap;cursor:pointer}.nav-item i{flex:none;font-size:15px}.nav-item:hover{background:var(--ns-frame-raised);color:var(--ns-frame-ink)}.nav-item.is-active{background:var(--ns-frame-raised);box-shadow:inset 2px 0 0 var(--ns-frame-glow);color:var(--ns-frame-ink)}.nav-item.is-active i{color:var(--ns-frame-glow)}.nav-caret{margin-left:auto;font-size:11px!important;opacity:.7}.nav-item.is-inert{opacity:.45;cursor:default}.nav-item.is-inert:hover{background:transparent;color:var(--ns-frame-muted)}.nav-label{overflow:hidden;text-overflow:ellipsis}.sidebar.is-collapsed .nav-label{display:none}.sidebar.is-collapsed .nav-item{justify-content:center;padding-inline:0}.content{flex:1;min-width:0;padding:28px 28px 64px;display:flex;flex-direction:column;gap:14px}.measure{display:flex;width:100%;max-width:var(--ac-content-max, 1440px);flex-direction:column;gap:14px;margin-inline:0}h1{margin:0;font-size:26px;line-height:1.2;font-weight:500;letter-spacing:-.02em}.lead{margin:0;font-size:14px;line-height:1.6;font-weight:300;color:var(--ac-text-muted)}.access-menu{display:flex;flex-direction:column;gap:.15rem;margin-top:.75rem;padding-top:.75rem;border-top:1px solid var(--ns-frame-line)}.access-caption{margin:0 0 .4rem;padding:0 .75rem;color:var(--ns-frame-muted);font-size:.68rem;letter-spacing:.06em;text-transform:uppercase;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.access-state{margin:0;padding:.35rem .75rem;color:var(--ns-frame-muted);font-size:.78rem}.access-state.is-error{color:var(--p-red-400, #f87171)}.nav-item.is-child{padding-left:2.1rem;font-size:.86rem}@media(max-width:720px){.sidebar{max-width:none;position:fixed;top:var(--ns-topbar-h, 56px);bottom:0;left:0;z-index:30;width:268px;max-width:82vw;overflow-y:auto;box-shadow:0 18px 44px #00000073;transform:translate(-100%);transition:transform .18s ease}.sidebar.is-collapsed{width:268px;max-width:82vw;transform:translate(-100%)}.sidebar:not(.is-collapsed){transform:translate(0)}.sidebar.is-collapsed .nav-label{display:inline}.sidebar.is-collapsed .role-text{display:flex}.sidebar.is-collapsed .nav-item,.sidebar.is-collapsed .role-badge{justify-content:flex-start;padding-inline:12px}.brand-secondary{display:none}.topbar{height:auto;min-height:56px;flex-wrap:wrap;padding-bottom:8px;row-gap:8px}.context{order:3;width:100%;margin-left:0}.context-select{min-width:0;max-width:none;flex:1 1 0}.content{padding:20px 16px 48px}h1{font-size:22px}}.who{display:flex;align-items:center;gap:12px;padding:15px 15px 13px;border-bottom:1px solid var(--ac-border);background:var(--ns-header-bg)}.who-photo{width:42px;height:42px;flex:none;border-radius:999px;object-fit:cover}.who-initials{display:inline-flex;align-items:center;justify-content:center;background:var(--ac-accent);color:var(--ac-on-accent);font-size:15px;font-weight:600}.who-text{display:flex;min-width:0;flex-direction:column}.who-name{overflow:hidden;font-size:14px;font-weight:600;text-overflow:ellipsis;white-space:nowrap}.who-mail{overflow:hidden;color:var(--ac-text-muted);font-size:11.5px;text-overflow:ellipsis;white-space:nowrap}.who-role{display:flex;align-items:center;gap:7px;margin:0;overflow:hidden;padding:9px 15px;border-bottom:1px solid var(--ac-border);color:var(--ac-text-muted);font-size:11.5px;white-space:nowrap}.who-role b{flex:none;color:var(--ac-text);font-size:12px;font-weight:600}.who-sep{flex:none}.who-context{overflow:hidden;text-overflow:ellipsis}.who-dot{width:5px;height:5px;flex:none;border-radius:50%;background:var(--ac-accent)}.company-option{display:flex;align-items:center;justify-content:space-between;gap:12px;width:100%}.company-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.launcher-notice{display:flex;gap:10px;margin:12px 12px 0;padding:12px;border:1px solid color-mix(in srgb,var(--ac-accent) 40%,transparent);border-radius:8px;background:color-mix(in srgb,var(--ac-accent) 10%,transparent)}.launcher-notice i{margin-top:2px;color:var(--ac-accent);font-size:15px}.launcher-notice-title{margin:0 0 4px;font-size:13px;font-weight:600}.launcher-notice-body{margin:0;color:var(--ac-text-muted);font-size:12px;line-height:1.45}.confirm{display:flex;flex-direction:column;align-items:center;gap:6px;padding:26px 24px 20px;border:1px solid var(--ac-border);border-radius:14px;background:var(--ac-surface);text-align:center}.confirm h2{margin:8px 0 0;font-size:17px;font-weight:600}.confirm p{margin:0;max-width:34ch;color:var(--ac-text-muted);font-size:13px;line-height:1.5}.confirm-actions{display:flex;justify-content:center;gap:8px;margin-top:18px}.toast-text{flex:1;margin-left:12px}.toast-summary{margin:0;font-size:13px;font-weight:600}.toast-detail{margin:2px 0 0;color:var(--ac-text-muted);font-size:12px}.context{display:flex;min-width:0;align-items:center;gap:8px;margin-left:14px}.context-select{min-width:150px;max-width:210px}.role-badge{display:flex;align-items:center;gap:12px;margin-bottom:10px;padding:9px 12px;border-left:2px solid var(--ns-frame-glow);border-radius:0 7px 7px 0;background:var(--ns-frame-raised);box-shadow:-2px 0 12px -3px color-mix(in srgb,var(--ns-frame-glow) 65%,transparent)}.role-badge>i{flex:none;color:var(--ns-frame-glow);font-size:14px}.role-text{display:flex;min-width:0;flex-direction:column;gap:1px}.role-name{overflow:hidden;color:var(--ns-frame-ink);font-size:13px;font-weight:600;text-overflow:ellipsis;white-space:nowrap}.role-caption{color:var(--ns-frame-muted);font-size:10px;letter-spacing:.06em;text-transform:uppercase}.sidebar.is-collapsed .role-badge{justify-content:center;padding-inline:0}.sidebar-scrim{position:fixed;top:var(--ns-topbar-h, 56px);right:0;bottom:0;left:0;z-index:29;padding:0;border:0;background:#00000073;cursor:pointer}.nav-skeleton{display:flex;flex-direction:column;gap:3px}.nav-skeleton-item{display:flex;align-items:center;gap:12px;padding:11px 12px}.sidebar.is-collapsed .nav-skeleton-item .p-skeleton:last-child{display:none}\n"], dependencies: [{ kind: "ngmodule", type: AvatarModule }, { kind: "component", type: i1$3.Avatar, selector: "p-avatar", inputs: ["label", "icon", "image", "size", "shape", "styleClass", "ariaLabel", "ariaLabelledBy"], outputs: ["onImageError"] }, { kind: "ngmodule", type: BreadcrumbModule }, { kind: "component", type: i2$3.Breadcrumb, selector: "p-breadcrumb", inputs: ["model", "style", "styleClass", "home", "homeAriaLabel"], outputs: ["onItemClick"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: ConfirmDialogModule }, { kind: "component", type: i4$1.ConfirmDialog, selector: "p-confirmDialog, p-confirmdialog, p-confirm-dialog", inputs: ["header", "icon", "message", "style", "styleClass", "maskStyleClass", "acceptIcon", "acceptLabel", "closeAriaLabel", "acceptAriaLabel", "acceptVisible", "rejectIcon", "rejectLabel", "rejectAriaLabel", "rejectVisible", "acceptButtonStyleClass", "rejectButtonStyleClass", "closeOnEscape", "dismissableMask", "blockScroll", "rtl", "closable", "appendTo", "key", "autoZIndex", "baseZIndex", "transitionOptions", "focusTrap", "defaultFocus", "breakpoints", "modal", "visible", "position", "draggable"], outputs: ["onHide"] }, { kind: "ngmodule", type: ToastModule }, { kind: "component", type: i5$2.Toast, selector: "p-toast", inputs: ["key", "autoZIndex", "baseZIndex", "life", "styleClass", "position", "preventOpenDuplicates", "preventDuplicates", "showTransformOptions", "hideTransformOptions", "showTransitionOptions", "hideTransitionOptions", "motionOptions", "breakpoints"], outputs: ["onClose"] }, { kind: "ngmodule", type: DrawerModule }, { kind: "component", type: i6$1.Drawer, selector: "p-drawer", inputs: ["appendTo", "motionOptions", "blockScroll", "style", "styleClass", "ariaCloseLabel", "autoZIndex", "baseZIndex", "modal", "closeButtonProps", "dismissible", "showCloseIcon", "closeOnEscape", "transitionOptions", "visible", "position", "fullScreen", "header", "maskStyle", "closable"], outputs: ["onShow", "onHide", "visibleChange"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "component", type: i3.Popover, selector: "p-popover", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions", "motionOptions"], outputs: ["onShow", "onHide"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i2.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i1$1.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "ngmodule", type: TagModule }, { kind: "component", type: i4.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i5$1.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: NucleusAppearance, selector: "nucleus-appearance" }, { kind: "component", type: NucleusLordicon, selector: "nucleus-lordicon", inputs: ["src", "trigger", "state", "size", "tone"] }, { kind: "component", type: NucleusMenu, selector: "nucleus-menu", inputs: ["sections", "heading", "badge", "searchable", "searchPlaceholder", "emptyText", "keyboardHint", "width"], outputs: ["select", "dismiss"] }, { kind: "component", type: NucleusMark, selector: "nucleus-mark", inputs: ["accent", "size"] }, { kind: "component", type: NucleusStarfield, selector: "nucleus-starfield" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2001
3728
  }
2002
3729
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusShell, decorators: [{
2003
3730
  type: Component,
@@ -2008,17 +3735,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
2008
3735
  ConfirmDialogModule,
2009
3736
  ToastModule,
2010
3737
  DrawerModule,
2011
- ListboxModule,
2012
3738
  PopoverModule,
2013
3739
  SelectModule,
3740
+ SkeletonModule,
2014
3741
  TagModule,
2015
3742
  TooltipModule,
2016
3743
  FormsModule,
3744
+ NucleusAppearance,
2017
3745
  NucleusLordicon,
3746
+ NucleusMenu,
2018
3747
  NucleusMark,
2019
3748
  NucleusStarfield,
2020
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let t = copy();\n@let me = identity();\n\n@if (starfield()) {\n <nucleus-starfield />\n}\n\n<header class=\"topbar\">\n <div class=\"side\">\n <p-button\n icon=\"pi pi-bars\"\n severity=\"secondary\"\n size=\"small\"\n [text]=\"true\"\n [rounded]=\"true\"\n [ariaLabel]=\"t.navToggle\"\n [pTooltip]=\"t.navToggle\"\n tooltipPosition=\"bottom\"\n (onClick)=\"toggleSidebar()\"\n />\n\n <div class=\"brand\">\n <nucleus-mark [size]=\"20\" />\n <div class=\"brand-name\">\n <span class=\"brand-primary\">NUCLEUS</span>\n <span class=\"brand-secondary\">{{ brand() }}</span>\n </div>\n </div>\n </div>\n\n <div class=\"side\">\n <div class=\"lang-switch\" role=\"group\" aria-label=\"Idioma / Language\">\n <button\n type=\"button\"\n class=\"lang-pill\"\n [class.is-active]=\"lang() === 'es'\"\n [attr.aria-pressed]=\"lang() === 'es'\"\n (click)=\"setLang('es')\"\n >\n ES\n </button>\n <button\n type=\"button\"\n class=\"lang-pill\"\n [class.is-active]=\"lang() === 'en'\"\n [attr.aria-pressed]=\"lang() === 'en'\"\n (click)=\"setLang('en')\"\n >\n EN\n </button>\n </div>\n\n <p-button\n icon=\"pi pi-th-large\"\n severity=\"secondary\"\n size=\"small\"\n [text]=\"true\"\n [rounded]=\"true\"\n [ariaLabel]=\"t.appsLauncher\"\n [pTooltip]=\"t.appsLauncher\"\n tooltipPosition=\"bottom\"\n (onClick)=\"launcher.toggle($event)\"\n />\n\n <p-popover #launcher>\n <div class=\"launcher\">\n <p class=\"launcher-title\">{{ t.appsLauncher }}</p>\n\n @if (!hasCompany()) {\n <div class=\"launcher-notice\">\n <i class=\"pi pi-building\" aria-hidden=\"true\"></i>\n <div>\n <p class=\"launcher-notice-title\">{{ t.noCompanyTitle }}</p>\n <p class=\"launcher-notice-body\">{{ t.noCompanyHint }}</p>\n </div>\n </div>\n }\n\n @if (!launcherApps().length) {\n <p class=\"launcher-empty\">{{ t.appsEmpty }}</p>\n }\n\n <div class=\"launcher-grid\">\n @for (app of launcherApps(); track app.id) {\n <button\n type=\"button\"\n class=\"launcher-item\"\n [class.is-disabled]=\"!canOpen(app)\"\n [disabled]=\"!canOpen(app)\"\n [pTooltip]=\"launchHint(app)\"\n tooltipPosition=\"bottom\"\n (click)=\"launch(app); launcher.hide()\"\n >\n <span class=\"launcher-icon\" [style.background]=\"app.color ?? '#64748b'\">\n @if (app.icon) {\n <i [class]=\"app.icon\" aria-hidden=\"true\"></i>\n } @else {\n <span class=\"launcher-initials\">{{ appInitials(app) }}</span>\n }\n </span>\n <span class=\"launcher-name\">{{ app.name }}</span>\n @if (launchHint(app)) {\n <span class=\"launcher-badge\">{{ launchHint(app) }}</span>\n }\n </button>\n }\n </div>\n </div>\n </p-popover>\n\n <p-button\n icon=\"pi pi-cog\"\n severity=\"secondary\"\n size=\"small\"\n [text]=\"true\"\n [rounded]=\"true\"\n [ariaLabel]=\"t.settingsTitle\"\n [pTooltip]=\"t.settingsTitle\"\n tooltipPosition=\"bottom\"\n (onClick)=\"settingsOpen.set(true)\"\n />\n\n @if (me) {\n <p-button\n severity=\"secondary\"\n size=\"small\"\n [text]=\"true\"\n [rounded]=\"true\"\n styleClass=\"avatar-trigger\"\n [ariaLabel]=\"me.name\"\n [pTooltip]=\"me.name\"\n tooltipPosition=\"bottom\"\n (onClick)=\"account.toggle($event)\"\n >\n <p-avatar\n [image]=\"me.photoUrl ?? undefined\"\n [label]=\"me.photoUrl ? undefined : initials()\"\n shape=\"circle\"\n size=\"normal\"\n />\n </p-button>\n }\n </div>\n</header>\n\n<p-popover #account>\n @if (me) {\n <div class=\"account\">\n <p class=\"account-email\">{{ me.email }}</p>\n\n <div class=\"account-head\">\n @if (me.photoUrl) {\n <img class=\"account-photo\" [src]=\"me.photoUrl\" alt=\"\" width=\"72\" height=\"72\" />\n } @else {\n <span class=\"account-photo account-initials\">{{ initials() }}</span>\n }\n <p class=\"account-hi\">{{ me.name }}</p>\n </div>\n\n <div class=\"account-context\">\n @if (roleName()) {\n <p-tag [value]=\"roleName()!\" severity=\"secondary\" />\n }\n <p class=\"account-place\">{{ contextCaption() }}</p>\n <p-button\n [label]=\"t.switchSite\"\n icon=\"pi pi-building\"\n severity=\"secondary\"\n size=\"small\"\n [outlined]=\"true\"\n [rounded]=\"true\"\n [fluid]=\"true\"\n (onClick)=\"openContext(); account.hide()\"\n />\n </div>\n\n <div class=\"account-actions\">\n @if (canSwitchAccount()) {\n <p-button\n [label]=\"t.accountSwitch\"\n icon=\"pi pi-users\"\n severity=\"secondary\"\n size=\"small\"\n [text]=\"true\"\n [fluid]=\"true\"\n (onClick)=\"switchAccount(); account.hide()\"\n />\n }\n <p-button\n [label]=\"t.signOut\"\n icon=\"pi pi-sign-out\"\n severity=\"secondary\"\n size=\"small\"\n [text]=\"true\"\n [fluid]=\"true\"\n (onClick)=\"signOut(); account.hide()\"\n />\n </div>\n </div>\n }\n</p-popover>\n\n<div class=\"body\">\n <aside class=\"sidebar\" [class.is-collapsed]=\"!sidebarOpen()\">\n @if (navItems().length) {\n <nav>\n @for (item of navItems(); track item.id) {\n <button\n type=\"button\"\n class=\"nav-item\"\n [class.is-active]=\"activeNavId() === item.id\"\n [attr.aria-current]=\"activeNavId() === item.id ? 'page' : null\"\n [pTooltip]=\"sidebarOpen() ? '' : item.label\"\n tooltipPosition=\"right\"\n (click)=\"selectNav(item.id)\"\n >\n <i [class]=\"item.icon\" aria-hidden=\"true\"></i>\n <span class=\"nav-label\">{{ item.label }}</span>\n </button>\n }\n </nav>\n }\n\n <nav class=\"access-menu\">\n @if (menuLoading()) {\n <p class=\"access-state\">{{ t.accessLoading }}</p>\n } @else if (menuError()) {\n <p class=\"access-state is-error\">{{ menuError() }}</p>\n } @else if (!menu().length) {\n <p class=\"access-state\">{{ t.accessEmpty }}</p>\n } @else {\n @for (node of menu(); track node.id) {\n <button\n type=\"button\"\n class=\"nav-item\"\n [class.is-active]=\"isSelected(node)\"\n [pTooltip]=\"sidebarOpen() ? '' : node.label\"\n tooltipPosition=\"right\"\n (click)=\"selectMenu(node)\"\n >\n <i [class]=\"node.icon ?? 'pi pi-circle'\" aria-hidden=\"true\"></i>\n <span class=\"nav-label\">{{ node.label }}</span>\n </button>\n\n @for (child of node.children; track child.id) {\n <button\n type=\"button\"\n class=\"nav-item is-child\"\n [class.is-active]=\"isSelected(child)\"\n [pTooltip]=\"sidebarOpen() ? '' : child.label\"\n tooltipPosition=\"right\"\n (click)=\"selectMenu(child)\"\n >\n <i [class]=\"child.icon ?? 'pi pi-circle'\" aria-hidden=\"true\"></i>\n <span class=\"nav-label\">{{ child.label }}</span>\n </button>\n }\n }\n }\n </nav>\n </aside>\n\n <main class=\"content\">\n <div class=\"measure\">\n <ng-content select=\"[nucleusTop]\" />\n\n @if (crumbs().length) {\n <p-breadcrumb [model]=\"crumbs()\" [home]=\"home()\" styleClass=\"ns-crumbs\" />\n }\n\n @if (title()) {\n <h1>{{ title() }}</h1>\n }\n @if (subtitle()) {\n <p class=\"lead\">{{ subtitle() }}</p>\n }\n\n <ng-content />\n </div>\n </main>\n</div>\n\n<p-drawer\n [visible]=\"contextOpen()\"\n (visibleChange)=\"contextOpen.set($event)\"\n position=\"right\"\n [header]=\"t.contextTitle\"\n [style]=\"{ width: '380px' }\"\n>\n <p class=\"settings-hint\">{{ t.contextHint }}</p>\n\n <label class=\"field-label\" for=\"nucleus-company\">{{ t.companyLabel }}</label>\n <p-select\n inputId=\"nucleus-company\"\n [options]=\"companyChoices()\"\n optionValue=\"id\"\n optionLabel=\"name\"\n [ngModel]=\"companyChoice()\"\n (ngModelChange)=\"onCompanyChange($event)\"\n [placeholder]=\"t.companyPlaceholder\"\n [fluid]=\"true\"\n >\n <ng-template #item let-option>\n <span class=\"company-option\">\n <span class=\"company-name\">{{ option.name }}</span>\n <p-tag [value]=\"option.tag\" severity=\"secondary\" />\n </span>\n </ng-template>\n </p-select>\n\n <label class=\"field-label\" for=\"nucleus-site\">{{ t.siteLabel }}</label>\n\n @if (sitesLoading()) {\n <p class=\"access-state\">{{ t.sitesLoading }}</p>\n } @else if (!siteOptions().length) {\n <p class=\"access-state\">{{ t.sitesEmpty }}</p>\n } @else {\n <p-select\n inputId=\"nucleus-site\"\n [options]=\"siteOptions()\"\n optionValue=\"id\"\n optionLabel=\"name\"\n [ngModel]=\"siteChoice()\"\n (ngModelChange)=\"siteChoice.set($event)\"\n [placeholder]=\"t.sitePlaceholder\"\n [fluid]=\"true\"\n />\n }\n\n <div class=\"context-actions\">\n <p-button\n [label]=\"t.confirmSwitch\"\n icon=\"pi pi-arrow-right\"\n [disabled]=\"!canConfirm()\"\n [loading]=\"switching()\"\n [fluid]=\"true\"\n (onClick)=\"confirmSwitch()\"\n />\n </div>\n</p-drawer>\n\n<p-drawer\n [visible]=\"settingsOpen()\"\n (visibleChange)=\"settingsOpen.set($event)\"\n position=\"right\"\n [header]=\"t.settingsTitle\"\n [style]=\"{ width: '340px' }\"\n>\n <h3 class=\"settings-heading\">{{ t.settingsTheme }}</h3>\n <p class=\"settings-hint\">{{ t.settingsThemeHint }}</p>\n\n <p-listbox\n [options]=\"themeOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n [ngModel]=\"themePreference()\"\n (ngModelChange)=\"selectTheme($event)\"\n [ariaLabel]=\"t.settingsTheme\"\n >\n <ng-template #item let-option>\n <span class=\"theme-option\">\n @if (option.swatch) {\n <span class=\"theme-swatch\" [style.background]=\"option.swatch\" aria-hidden=\"true\"></span>\n } @else {\n <i class=\"pi pi-desktop theme-swatch-icon\" aria-hidden=\"true\"></i>\n }\n <span>{{ option.label }}</span>\n </span>\n </ng-template>\n </p-listbox>\n</p-drawer>\n\n<p-toast position=\"bottom-right\" styleClass=\"ns-toast\">\n <ng-template #message let-message>\n <nucleus-lordicon\n [src]=\"message.severity === 'success' ? icons.check : icons.warning\"\n [tone]=\"message.severity === 'success' ? 'accent' : 'danger'\"\n [size]=\"34\"\n trigger=\"in\"\n />\n <div class=\"toast-text\">\n <p class=\"toast-summary\">{{ message.summary }}</p>\n @if (message.detail) {\n <p class=\"toast-detail\">{{ message.detail }}</p>\n }\n </div>\n </ng-template>\n</p-toast>\n\n<p-confirmdialog [style]=\"{ width: '420px' }\">\n <ng-template #headless let-message let-onAccept=\"onAccept\" let-onReject=\"onReject\">\n <div class=\"confirm\">\n <nucleus-lordicon\n [src]=\"message.icon\"\n [tone]=\"message.acceptButtonStyleClass ? 'danger' : 'accent'\"\n [size]=\"64\"\n trigger=\"loop\"\n />\n <h2>{{ message.header }}</h2>\n <p>{{ message.message }}</p>\n <div class=\"confirm-actions\">\n <p-button\n [label]=\"message.rejectLabel\"\n severity=\"secondary\"\n [text]=\"true\"\n (onClick)=\"onReject()\"\n />\n <p-button\n [label]=\"message.acceptLabel\"\n [severity]=\"message.acceptButtonStyleClass ? 'danger' : 'primary'\"\n (onClick)=\"onAccept()\"\n />\n </div>\n </div>\n </ng-template>\n</p-confirmdialog>\n", styles: [":host{display:flex;flex-direction:column;min-height:100dvh;background:var(--ac-bg);color:var(--ac-text)}.topbar{position:sticky;top:0;z-index:20;display:flex;align-items:center;justify-content:space-between;gap:16px;height:56px;padding:0 12px 0 8px;background:var(--ac-surface);border-bottom:1px solid var(--ac-border)}.side{display:flex;align-items:center;gap:4px}.brand{display:flex;align-items:center;gap:9px;margin-left:4px}.brand-name{display:flex;align-items:baseline;gap:7px}.brand-primary{font-size:13px;font-weight:700;letter-spacing:.15em}.brand-secondary{font-size:9px;font-weight:500;letter-spacing:.22em;color:var(--ac-text-muted)}.lang-switch{display:flex;align-items:center;gap:2px;margin-right:6px;border:1px solid var(--ac-border);border-radius:999px;padding:2px}.lang-pill{font-size:10px;font-weight:600;letter-spacing:.08em;padding:4px 10px;border:none;border-radius:999px;background:transparent;color:var(--ac-text-muted);cursor:pointer}.lang-pill.is-active{background:var(--ac-accent);color:var(--ac-on-accent)}.launcher{width:320px;padding:4px}.launcher-title{margin:0 0 10px;font-size:12px;font-weight:600;color:var(--ac-text-muted)}.launcher-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:4px}.launcher-item{position:relative;display:flex;flex-direction:column;align-items:center;gap:7px;padding:12px 6px;border:none;border-radius:8px;background:transparent;color:var(--ac-text);cursor:pointer}.launcher-item:hover:not(:disabled){background:var(--ac-surface-alt)}.launcher-item.is-disabled{opacity:.5;cursor:default}.launcher-icon{display:inline-flex;align-items:center;justify-content:center;width:40px;height:40px;border-radius:10px;color:#fff}.launcher-icon i{font-size:18px}.launcher-name{font-size:11px;font-weight:500;line-height:1.3;text-align:center}.launcher-badge{font-size:9px;font-weight:500;color:var(--ac-text-muted)}.account{width:300px;padding:4px;text-align:center}.account-email{margin:0 0 12px;font-size:12px;color:var(--ac-text-muted)}.account-head{display:flex;flex-direction:column;align-items:center;gap:8px;padding-bottom:14px;border-bottom:1px solid var(--ac-border)}.account-photo{width:72px;height:72px;border-radius:999px;object-fit:cover}.account-initials{display:inline-flex;align-items:center;justify-content:center;background:var(--ac-accent);color:var(--ac-on-accent);font-size:26px;font-weight:600}.account-hi{margin:0;font-size:16px;font-weight:500}.account-actions{display:flex;flex-direction:column;gap:2px;padding-top:8px}.body{display:flex;flex:1;min-height:0}.sidebar{flex:none;width:224px;padding:12px 10px;background:var(--ac-surface);border-right:1px solid var(--ac-border);transition:width .18s ease}.sidebar.is-collapsed{width:60px}.sidebar nav{display:flex;flex-direction:column;gap:3px}.nav-item{display:flex;align-items:center;gap:12px;width:100%;padding:9px 12px;border:none;border-radius:6px;background:transparent;color:var(--ac-text-muted);font-size:13px;font-weight:500;text-align:left;white-space:nowrap;cursor:pointer}.nav-item i{flex:none;font-size:15px}.nav-item:hover{background:var(--ac-surface-alt);color:var(--ac-text)}.nav-item.is-active{background:color-mix(in srgb,var(--ac-accent) 18%,transparent);color:var(--ac-text)}.nav-item.is-active i{color:var(--ac-accent)}.sidebar.is-collapsed .nav-label{display:none}.sidebar.is-collapsed .nav-item{justify-content:center;padding-inline:0}.content{flex:1;min-width:0;padding:28px 28px 64px;display:flex;flex-direction:column;gap:14px}.measure{display:flex;width:100%;max-width:var(--ac-content-max, 1440px);flex-direction:column;gap:14px;margin-inline:auto}h1{margin:0;font-size:26px;line-height:1.2;font-weight:500;letter-spacing:-.02em}.lead{margin:0;font-size:14px;line-height:1.6;font-weight:300;color:var(--ac-text-muted)}.settings-heading{margin:0 0 4px;font-size:14px;font-weight:600}.settings-hint{margin:0 0 14px;font-size:12px;font-weight:300;color:var(--ac-text-muted)}.theme-option{display:flex;align-items:center;gap:10px}.theme-swatch{flex:none;width:16px;height:16px;border-radius:3px;border:1px solid var(--ac-border)}.theme-swatch-icon{flex:none;width:16px;font-size:14px;color:var(--ac-text-muted)}.access-menu{display:flex;flex-direction:column;gap:.15rem;margin-top:.75rem;padding-top:.75rem;border-top:1px solid color-mix(in srgb,currentColor 12%,transparent)}.access-caption{margin:0 0 .4rem;padding:0 .75rem;font-size:.68rem;letter-spacing:.06em;text-transform:uppercase;opacity:.65;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.access-state{margin:0;padding:.35rem .75rem;font-size:.78rem;opacity:.7}.access-state.is-error{color:var(--p-red-400, #f87171);opacity:1}.nav-item.is-child{padding-left:2.1rem;font-size:.86rem}.launcher-initials{font-size:.85rem;font-weight:600;letter-spacing:.02em;line-height:1;color:#fff}.launcher-empty{margin:.25rem 0 .5rem;font-size:.82rem;opacity:.7}@media(max-width:720px){.sidebar{width:60px}.sidebar .nav-label{display:none}.sidebar .nav-item{justify-content:center;padding-inline:0}.brand-secondary{display:none}.content{padding:20px 16px 48px}h1{font-size:22px}.launcher{width:260px}}.account-context{display:flex;flex-direction:column;align-items:center;gap:8px;padding:14px 0;border-top:1px solid var(--ac-border);border-bottom:1px solid var(--ac-border)}.account-place{margin:0;color:var(--ac-text-muted);font-size:12px;text-align:center}.field-label{display:block;margin:18px 0 6px;color:var(--ac-text-muted);font-size:12px;letter-spacing:.04em;text-transform:uppercase}.company-option{display:flex;align-items:center;justify-content:space-between;gap:12px;width:100%}.company-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.context-actions{margin-top:24px}.launcher-notice{display:flex;gap:10px;margin-bottom:14px;padding:12px;border:1px solid color-mix(in srgb,var(--ac-accent) 40%,transparent);border-radius:8px;background:color-mix(in srgb,var(--ac-accent) 10%,transparent)}.launcher-notice i{margin-top:2px;color:var(--ac-accent);font-size:15px}.launcher-notice-title{margin:0 0 4px;font-size:13px;font-weight:600}.launcher-notice-body{margin:0;color:var(--ac-text-muted);font-size:12px;line-height:1.45}.confirm{display:flex;flex-direction:column;align-items:center;gap:6px;padding:26px 24px 20px;border:1px solid var(--ac-border);border-radius:14px;background:var(--ac-surface);text-align:center}.confirm h2{margin:8px 0 0;font-size:17px;font-weight:600}.confirm p{margin:0;max-width:34ch;color:var(--ac-text-muted);font-size:13px;line-height:1.5}.confirm-actions{display:flex;justify-content:center;gap:8px;margin-top:18px}.toast-text{flex:1;margin-left:12px}.toast-summary{margin:0;font-size:13px;font-weight:600}.toast-detail{margin:2px 0 0;color:var(--ac-text-muted);font-size:12px}\n"] }]
2021
- }], propDecorators: { brand: [{ type: i0.Input, args: [{ isSignal: true, alias: "brand", required: false }] }], heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required: false }] }], lead: [{ type: i0.Input, args: [{ isSignal: true, alias: "lead", required: false }] }], navItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "navItems", required: false }] }], activeNavId: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeNavId", required: false }] }], selectedMenuId: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedMenuId", required: false }] }], navSelect: [{ type: i0.Output, args: ["navSelect"] }], menuSelect: [{ type: i0.Output, args: ["menuSelect"] }] } });
3749
+ ], host: { '[style.--ns-topbar-h]': 'topbarHeight()' }, changeDetection: ChangeDetectionStrategy.OnPush, template: "@let t = copy();\n@let me = identity();\n\n@if (starfield()) {\n <nucleus-starfield />\n}\n\n<header class=\"topbar ns-frame\">\n <div class=\"side\">\n <p-button\n icon=\"pi pi-bars\"\n severity=\"secondary\"\n size=\"small\"\n [text]=\"true\"\n [rounded]=\"true\"\n [ariaLabel]=\"t.navToggle\"\n [pTooltip]=\"t.navToggle\"\n tooltipPosition=\"bottom\"\n (onClick)=\"toggleSidebar()\"\n />\n\n <div class=\"brand\">\n <nucleus-mark [size]=\"20\" />\n <div class=\"brand-name\">\n <span class=\"brand-primary\">NUCLEUS</span>\n <span class=\"brand-secondary\">{{ brand() }}</span>\n </div>\n </div>\n\n </div>\n\n @if (hasSiteContext()) {\n <div class=\"context ns-context\">\n <p-select\n styleClass=\"context-select\"\n [options]=\"companyChoices()\"\n optionValue=\"id\"\n optionLabel=\"name\"\n [ngModel]=\"companyChoice()\"\n (ngModelChange)=\"onCompanyChange($event)\"\n [placeholder]=\"t.companyPlaceholder\"\n [disabled]=\"switching()\"\n [filter]=\"companyChoices().length > 8\"\n filterBy=\"name\"\n [filterPlaceholder]=\"words().search\"\n [autofocusFilter]=\"true\"\n [resetFilterOnHide]=\"true\"\n [ariaLabel]=\"t.companyLabel\"\n [ngModelOptions]=\"{ standalone: true }\"\n >\n <ng-template #item let-option>\n <span class=\"company-option\">\n <span class=\"company-name\">{{ option.name }}</span>\n <p-tag [value]=\"option.tag\" severity=\"secondary\" />\n </span>\n </ng-template>\n </p-select>\n\n <p-select\n styleClass=\"context-select\"\n [options]=\"siteOptions()\"\n optionValue=\"id\"\n optionLabel=\"name\"\n [ngModel]=\"siteChoice()\"\n (ngModelChange)=\"onSiteChange($event)\"\n [placeholder]=\"sitePlaceholder()\"\n [disabled]=\"switching() || sitesLoading() || !siteOptions().length\"\n [filter]=\"siteOptions().length > 8\"\n filterBy=\"name\"\n [filterPlaceholder]=\"words().search\"\n [autofocusFilter]=\"true\"\n [resetFilterOnHide]=\"true\"\n [ariaLabel]=\"t.siteLabel\"\n [ngModelOptions]=\"{ standalone: true }\"\n />\n </div>\n }\n\n <div class=\"side is-end\">\n <div class=\"lang-switch\" role=\"group\" aria-label=\"Idioma / Language\">\n <button\n type=\"button\"\n class=\"lang-pill\"\n [class.is-active]=\"lang() === 'es'\"\n [attr.aria-pressed]=\"lang() === 'es'\"\n (click)=\"setLang('es')\"\n >\n ES\n </button>\n <button\n type=\"button\"\n class=\"lang-pill\"\n [class.is-active]=\"lang() === 'en'\"\n [attr.aria-pressed]=\"lang() === 'en'\"\n (click)=\"setLang('en')\"\n >\n EN\n </button>\n </div>\n\n <p-button\n icon=\"pi pi-th-large\"\n severity=\"secondary\"\n size=\"small\"\n [text]=\"true\"\n [rounded]=\"true\"\n [ariaLabel]=\"t.appsLauncher\"\n [pTooltip]=\"t.appsLauncher\"\n tooltipPosition=\"bottom\"\n (onClick)=\"launcher.toggle($event)\"\n />\n\n <p-popover #launcher styleClass=\"ns-flush-popover\">\n @if (!hasCompany()) {\n <div class=\"launcher-notice\">\n <i class=\"pi pi-building\" aria-hidden=\"true\"></i>\n <div>\n <p class=\"launcher-notice-title\">{{ t.noCompanyTitle }}</p>\n <p class=\"launcher-notice-body\">{{ t.noCompanyHint }}</p>\n </div>\n </div>\n }\n\n <nucleus-menu\n [heading]=\"t.appsSwitch\"\n [badge]=\"launcherCount()\"\n [sections]=\"launcherSections()\"\n [searchable]=\"launcherRich()\"\n [searchPlaceholder]=\"t.appsSearch\"\n [emptyText]=\"launcherRich() ? t.appsNoMatch : t.appsEmpty\"\n [keyboardHint]=\"t.keyboardHint\"\n [width]=\"336\"\n (select)=\"onLauncherSelect($event); launcher.hide()\"\n (dismiss)=\"launcher.hide()\"\n />\n </p-popover>\n\n <p-button\n icon=\"pi pi-cog\"\n severity=\"secondary\"\n size=\"small\"\n [text]=\"true\"\n [rounded]=\"true\"\n [ariaLabel]=\"t.settingsTitle\"\n [pTooltip]=\"t.settingsTitle\"\n tooltipPosition=\"bottom\"\n (onClick)=\"settingsOpen.set(true)\"\n />\n\n @if (me) {\n <p-button\n severity=\"secondary\"\n size=\"small\"\n [text]=\"true\"\n [rounded]=\"true\"\n styleClass=\"avatar-trigger\"\n [ariaLabel]=\"me.name\"\n [pTooltip]=\"me.name\"\n tooltipPosition=\"bottom\"\n (onClick)=\"account.toggle($event)\"\n >\n <p-avatar\n [image]=\"me.photoUrl ?? undefined\"\n [label]=\"me.photoUrl ? undefined : initials()\"\n shape=\"circle\"\n size=\"normal\"\n />\n </p-button>\n }\n </div>\n</header>\n\n<p-popover #account styleClass=\"ns-flush-popover\">\n @if (me) {\n <div class=\"who\">\n @if (me.photoUrl) {\n <img class=\"who-photo\" [src]=\"me.photoUrl\" alt=\"\" width=\"42\" height=\"42\" />\n } @else {\n <span class=\"who-photo who-initials\">{{ initials() }}</span>\n }\n <span class=\"who-text\">\n <span class=\"who-name\">{{ me.name }}</span>\n <span class=\"who-mail\">{{ me.email }}</span>\n </span>\n </div>\n\n <p class=\"who-role\">\n <span class=\"who-dot\" aria-hidden=\"true\"></span>\n @if (roleName(); as role) {\n <b>{{ role }}</b>\n <span class=\"who-sep\">\u00B7</span>\n }\n <span class=\"who-context\">{{ accountContext() }}</span>\n </p>\n\n <nucleus-menu\n [sections]=\"accountSections()\"\n [width]=\"304\"\n (select)=\"onAccountSelect($event); account.hide()\"\n (dismiss)=\"account.hide()\"\n />\n }\n</p-popover>\n\n<div class=\"body\">\n @if (narrow() && sidebarOpen()) {\n <button\n type=\"button\"\n class=\"sidebar-scrim\"\n [attr.aria-label]=\"t.navToggle\"\n (click)=\"toggleSidebar()\"\n ></button>\n }\n\n <aside class=\"sidebar ns-frame\" [class.is-collapsed]=\"!sidebarOpen()\">\n @if (roleName(); as role) {\n <div class=\"role-badge\" [pTooltip]=\"sidebarOpen() ? '' : role\" tooltipPosition=\"right\">\n <i class=\"pi pi-key\" aria-hidden=\"true\"></i>\n <span class=\"nav-label role-text\">\n <span class=\"role-name\">{{ role }}</span>\n <span class=\"role-caption\">{{ t.roleCaption }}</span>\n </span>\n </div>\n }\n\n @if (navItems().length) {\n <nav>\n @for (item of navItems(); track item.id) {\n <button\n type=\"button\"\n class=\"nav-item\"\n [class.is-active]=\"activeNavId() === item.id\"\n [attr.aria-current]=\"activeNavId() === item.id ? 'page' : null\"\n [pTooltip]=\"sidebarOpen() ? '' : item.label\"\n tooltipPosition=\"right\"\n (click)=\"selectNav(item.id)\"\n >\n <i [class]=\"item.icon\" aria-hidden=\"true\"></i>\n <span class=\"nav-label\">{{ item.label }}</span>\n </button>\n }\n </nav>\n }\n\n <nav class=\"access-menu\">\n @if (menuLoading()) {\n <div class=\"nav-skeleton\" role=\"status\" [attr.aria-label]=\"t.accessLoading\">\n @for (width of menuPlaceholders; track $index) {\n <div class=\"nav-skeleton-item\">\n <p-skeleton shape=\"circle\" size=\"15px\" />\n <p-skeleton height=\"9px\" [width]=\"width\" />\n </div>\n }\n </div>\n } @else if (menuError()) {\n <p class=\"access-state is-error\">{{ menuError() }}</p>\n } @else if (!menu().length) {\n <p class=\"access-state\">{{ t.accessEmpty }}</p>\n } @else {\n @for (node of menu(); track node.id) {\n @let group = isGroup(node);\n @let open = isExpanded(node);\n\n <button\n type=\"button\"\n class=\"nav-item\"\n [class.is-active]=\"!group && isSelected(node)\"\n [class.is-inert]=\"!group && !node.route\"\n [attr.aria-expanded]=\"group ? open : null\"\n [disabled]=\"!group && !node.route\"\n [pTooltip]=\"sidebarOpen() ? '' : node.label\"\n tooltipPosition=\"right\"\n (click)=\"selectMenu(node)\"\n >\n <i [class]=\"node.icon ?? 'pi pi-circle'\" aria-hidden=\"true\"></i>\n <span class=\"nav-label\">{{ node.label }}</span>\n @if (group && sidebarOpen()) {\n <i\n class=\"pi nav-caret\"\n [class.pi-chevron-down]=\"open\"\n [class.pi-chevron-right]=\"!open\"\n aria-hidden=\"true\"\n ></i>\n }\n </button>\n\n @if (open) {\n @for (child of node.children; track child.id) {\n <button\n type=\"button\"\n class=\"nav-item is-child\"\n [class.is-active]=\"isSelected(child)\"\n [pTooltip]=\"sidebarOpen() ? '' : child.label\"\n tooltipPosition=\"right\"\n (click)=\"selectMenu(child)\"\n >\n <i [class]=\"child.icon ?? 'pi pi-circle'\" aria-hidden=\"true\"></i>\n <span class=\"nav-label\">{{ child.label }}</span>\n </button>\n }\n }\n }\n }\n </nav>\n </aside>\n\n <main class=\"content\">\n <div class=\"measure\">\n <ng-content select=\"[nucleusTop]\" />\n\n @if (crumbs().length) {\n <p-breadcrumb [model]=\"crumbs()\" [home]=\"home()\" styleClass=\"ns-crumbs\" />\n }\n\n @if (title()) {\n <h1>{{ title() }}</h1>\n }\n @if (subtitle()) {\n <p class=\"lead\">{{ subtitle() }}</p>\n }\n\n <ng-content />\n </div>\n </main>\n</div>\n\n<p-drawer\n [visible]=\"settingsOpen()\"\n (visibleChange)=\"settingsOpen.set($event)\"\n position=\"right\"\n [header]=\"t.settingsTitle\"\n [modal]=\"false\"\n [blockScroll]=\"false\"\n styleClass=\"ns-settings-drawer\"\n [style]=\"{ width: '380px' }\"\n>\n <nucleus-appearance />\n</p-drawer>\n\n<p-toast position=\"bottom-right\" styleClass=\"ns-toast\">\n <ng-template #message let-message>\n <nucleus-lordicon\n [src]=\"message.severity === 'success' ? icons.check : icons.warning\"\n [tone]=\"message.severity === 'success' ? 'accent' : 'danger'\"\n [size]=\"34\"\n trigger=\"in\"\n />\n <div class=\"toast-text\">\n <p class=\"toast-summary\">{{ message.summary }}</p>\n @if (message.detail) {\n <p class=\"toast-detail\">{{ message.detail }}</p>\n }\n </div>\n </ng-template>\n</p-toast>\n\n<p-confirmdialog [style]=\"{ width: '420px' }\">\n <ng-template #headless let-message let-onAccept=\"onAccept\" let-onReject=\"onReject\">\n <div class=\"confirm\">\n <nucleus-lordicon\n [src]=\"message.icon\"\n [tone]=\"message.acceptButtonStyleClass ? 'danger' : 'accent'\"\n [size]=\"64\"\n trigger=\"loop\"\n />\n <h2>{{ message.header }}</h2>\n <p>{{ message.message }}</p>\n <div class=\"confirm-actions\">\n <p-button\n [label]=\"message.rejectLabel\"\n severity=\"secondary\"\n [text]=\"true\"\n (onClick)=\"onReject()\"\n />\n <p-button\n [label]=\"message.acceptLabel\"\n [severity]=\"message.acceptButtonStyleClass ? 'danger' : 'primary'\"\n (onClick)=\"onAccept()\"\n />\n </div>\n </div>\n </ng-template>\n</p-confirmdialog>\n", styles: [":host{display:flex;flex-direction:column;min-height:100dvh;background:var(--ac-bg);color:var(--ac-text)}.topbar{position:sticky;top:0;z-index:20;display:flex;align-items:center;justify-content:flex-start;gap:16px;height:56px;padding:0 12px 0 8px;border-bottom:1px solid var(--ns-frame-line)}.side{display:flex;min-width:0;align-items:center;gap:4px}.side.is-end{margin-left:auto}.brand{display:flex;align-items:center;gap:9px;margin-left:4px}.brand-name{display:flex;align-items:baseline;gap:7px}.brand-primary{font-size:13px;font-weight:700;letter-spacing:.15em}.brand-secondary{font-size:9px;font-weight:500;letter-spacing:.22em;color:var(--ns-frame-muted)}.lang-switch{display:flex;align-items:center;gap:2px;margin-right:6px;border:1px solid var(--ns-frame-line);border-radius:999px;padding:2px}.lang-pill{font-size:10px;font-weight:600;letter-spacing:.08em;padding:4px 10px;border:none;border-radius:999px;background:transparent;color:var(--ns-frame-muted);cursor:pointer}.lang-pill:hover{color:var(--ns-frame-ink)}.lang-pill.is-active{background:var(--ns-frame-glow);color:var(--ns-frame-on-glow)}.body{display:flex;flex:1;min-height:0}.sidebar{flex:none;width:max-content;min-width:224px;max-width:340px;padding:12px 10px;border-right:1px solid var(--ns-frame-line);transition:width .18s ease}.sidebar.is-collapsed{width:60px;min-width:0}.sidebar nav{display:flex;flex-direction:column;gap:3px}.nav-item{display:flex;align-items:center;gap:12px;width:100%;padding:var(--ns-nav-y, 9px) 12px;border:none;border-radius:6px;background:transparent;color:var(--ns-frame-muted);font-size:13px;font-weight:500;text-align:left;white-space:nowrap;cursor:pointer}.nav-item i{flex:none;font-size:15px}.nav-item:hover{background:var(--ns-frame-raised);color:var(--ns-frame-ink)}.nav-item.is-active{background:var(--ns-frame-raised);box-shadow:inset 2px 0 0 var(--ns-frame-glow);color:var(--ns-frame-ink)}.nav-item.is-active i{color:var(--ns-frame-glow)}.nav-caret{margin-left:auto;font-size:11px!important;opacity:.7}.nav-item.is-inert{opacity:.45;cursor:default}.nav-item.is-inert:hover{background:transparent;color:var(--ns-frame-muted)}.nav-label{overflow:hidden;text-overflow:ellipsis}.sidebar.is-collapsed .nav-label{display:none}.sidebar.is-collapsed .nav-item{justify-content:center;padding-inline:0}.content{flex:1;min-width:0;padding:28px 28px 64px;display:flex;flex-direction:column;gap:14px}.measure{display:flex;width:100%;max-width:var(--ac-content-max, 1440px);flex-direction:column;gap:14px;margin-inline:0}h1{margin:0;font-size:26px;line-height:1.2;font-weight:500;letter-spacing:-.02em}.lead{margin:0;font-size:14px;line-height:1.6;font-weight:300;color:var(--ac-text-muted)}.access-menu{display:flex;flex-direction:column;gap:.15rem;margin-top:.75rem;padding-top:.75rem;border-top:1px solid var(--ns-frame-line)}.access-caption{margin:0 0 .4rem;padding:0 .75rem;color:var(--ns-frame-muted);font-size:.68rem;letter-spacing:.06em;text-transform:uppercase;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.access-state{margin:0;padding:.35rem .75rem;color:var(--ns-frame-muted);font-size:.78rem}.access-state.is-error{color:var(--p-red-400, #f87171)}.nav-item.is-child{padding-left:2.1rem;font-size:.86rem}@media(max-width:720px){.sidebar{max-width:none;position:fixed;top:var(--ns-topbar-h, 56px);bottom:0;left:0;z-index:30;width:268px;max-width:82vw;overflow-y:auto;box-shadow:0 18px 44px #00000073;transform:translate(-100%);transition:transform .18s ease}.sidebar.is-collapsed{width:268px;max-width:82vw;transform:translate(-100%)}.sidebar:not(.is-collapsed){transform:translate(0)}.sidebar.is-collapsed .nav-label{display:inline}.sidebar.is-collapsed .role-text{display:flex}.sidebar.is-collapsed .nav-item,.sidebar.is-collapsed .role-badge{justify-content:flex-start;padding-inline:12px}.brand-secondary{display:none}.topbar{height:auto;min-height:56px;flex-wrap:wrap;padding-bottom:8px;row-gap:8px}.context{order:3;width:100%;margin-left:0}.context-select{min-width:0;max-width:none;flex:1 1 0}.content{padding:20px 16px 48px}h1{font-size:22px}}.who{display:flex;align-items:center;gap:12px;padding:15px 15px 13px;border-bottom:1px solid var(--ac-border);background:var(--ns-header-bg)}.who-photo{width:42px;height:42px;flex:none;border-radius:999px;object-fit:cover}.who-initials{display:inline-flex;align-items:center;justify-content:center;background:var(--ac-accent);color:var(--ac-on-accent);font-size:15px;font-weight:600}.who-text{display:flex;min-width:0;flex-direction:column}.who-name{overflow:hidden;font-size:14px;font-weight:600;text-overflow:ellipsis;white-space:nowrap}.who-mail{overflow:hidden;color:var(--ac-text-muted);font-size:11.5px;text-overflow:ellipsis;white-space:nowrap}.who-role{display:flex;align-items:center;gap:7px;margin:0;overflow:hidden;padding:9px 15px;border-bottom:1px solid var(--ac-border);color:var(--ac-text-muted);font-size:11.5px;white-space:nowrap}.who-role b{flex:none;color:var(--ac-text);font-size:12px;font-weight:600}.who-sep{flex:none}.who-context{overflow:hidden;text-overflow:ellipsis}.who-dot{width:5px;height:5px;flex:none;border-radius:50%;background:var(--ac-accent)}.company-option{display:flex;align-items:center;justify-content:space-between;gap:12px;width:100%}.company-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.launcher-notice{display:flex;gap:10px;margin:12px 12px 0;padding:12px;border:1px solid color-mix(in srgb,var(--ac-accent) 40%,transparent);border-radius:8px;background:color-mix(in srgb,var(--ac-accent) 10%,transparent)}.launcher-notice i{margin-top:2px;color:var(--ac-accent);font-size:15px}.launcher-notice-title{margin:0 0 4px;font-size:13px;font-weight:600}.launcher-notice-body{margin:0;color:var(--ac-text-muted);font-size:12px;line-height:1.45}.confirm{display:flex;flex-direction:column;align-items:center;gap:6px;padding:26px 24px 20px;border:1px solid var(--ac-border);border-radius:14px;background:var(--ac-surface);text-align:center}.confirm h2{margin:8px 0 0;font-size:17px;font-weight:600}.confirm p{margin:0;max-width:34ch;color:var(--ac-text-muted);font-size:13px;line-height:1.5}.confirm-actions{display:flex;justify-content:center;gap:8px;margin-top:18px}.toast-text{flex:1;margin-left:12px}.toast-summary{margin:0;font-size:13px;font-weight:600}.toast-detail{margin:2px 0 0;color:var(--ac-text-muted);font-size:12px}.context{display:flex;min-width:0;align-items:center;gap:8px;margin-left:14px}.context-select{min-width:150px;max-width:210px}.role-badge{display:flex;align-items:center;gap:12px;margin-bottom:10px;padding:9px 12px;border-left:2px solid var(--ns-frame-glow);border-radius:0 7px 7px 0;background:var(--ns-frame-raised);box-shadow:-2px 0 12px -3px color-mix(in srgb,var(--ns-frame-glow) 65%,transparent)}.role-badge>i{flex:none;color:var(--ns-frame-glow);font-size:14px}.role-text{display:flex;min-width:0;flex-direction:column;gap:1px}.role-name{overflow:hidden;color:var(--ns-frame-ink);font-size:13px;font-weight:600;text-overflow:ellipsis;white-space:nowrap}.role-caption{color:var(--ns-frame-muted);font-size:10px;letter-spacing:.06em;text-transform:uppercase}.sidebar.is-collapsed .role-badge{justify-content:center;padding-inline:0}.sidebar-scrim{position:fixed;top:var(--ns-topbar-h, 56px);right:0;bottom:0;left:0;z-index:29;padding:0;border:0;background:#00000073;cursor:pointer}.nav-skeleton{display:flex;flex-direction:column;gap:3px}.nav-skeleton-item{display:flex;align-items:center;gap:12px;padding:11px 12px}.sidebar.is-collapsed .nav-skeleton-item .p-skeleton:last-child{display:none}\n"] }]
3750
+ }], ctorParameters: () => [], propDecorators: { brand: [{ type: i0.Input, args: [{ isSignal: true, alias: "brand", required: false }] }], heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required: false }] }], lead: [{ type: i0.Input, args: [{ isSignal: true, alias: "lead", required: false }] }], navItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "navItems", required: false }] }], activeNavId: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeNavId", required: false }] }], selectedMenuId: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedMenuId", required: false }] }], navSelect: [{ type: i0.Output, args: ["navSelect"] }], menuSelect: [{ type: i0.Output, args: ["menuSelect"] }] } });
2022
3751
  function initialsOf(source) {
2023
3752
  const parts = source.trim().split(/\s+/).filter(Boolean);
2024
3753
  if (!parts.length)
@@ -2032,5 +3761,5 @@ function initialsOf(source) {
2032
3761
  * Generated bundle index. Do not edit.
2033
3762
  */
2034
3763
 
2035
- export { DEFAULT_THEME, EMPTY_GEO_VALUE, GEO_COPY, NO_PERMISSIONS, NUCLEUS_CONFIG, NUCLEUS_DARK_SELECTOR, NUCLEUS_FORCE_BEARER, NUCLEUS_ICONS, NUCLEUS_LANGS, NUCLEUS_LOGOUT_PARAM, NUCLEUS_SKIP_AUTH, NucleusAuth, NucleusCard, NucleusCell, NucleusFeedback, NucleusForm, NucleusGeo, NucleusGeoSelect, NucleusLanguage, NucleusList, NucleusLordicon, NucleusMark, NucleusPage, NucleusPreset, NucleusSessionAuth, NucleusShell, NucleusStarfield, NucleusTabs, SHELL_COPY, SessionService, THEMES, THEME_LABELS, ThemeService, UserSyncService, consumeNucleusLogout, findMenuByRoute, flagOf, geoCopyOf, isNucleusConfigured, isThemePreference, levelLabel, loadLordicon, loadNucleusConfig, nucleusAuthInterceptor, nucleusLogoutUrl, ownsUrl, permissionsOf, provideNucleus, provideNucleusFeedback, provideNucleusTheme, resolveFieldSize, themeById };
3764
+ export { ACCENTS, CANVASES, COMMON_COPY, CONTROL_CONTRAST, DEFAULT_APPEARANCE, DENSITIES, EMPTY_GEO_VALUE, FIELD_TARGETS, FRAMES, GEO_COPY, LEGACY_THEMES, NO_PERMISSIONS, NUCLEUS_ACCENTS, NUCLEUS_CONFIG, NUCLEUS_DARK_SELECTOR, NUCLEUS_FORCE_BEARER, NUCLEUS_ICONS, NUCLEUS_ICON_CATALOG, NUCLEUS_LANGS, NUCLEUS_LOGOUT_PARAM, NUCLEUS_SKIP_AUTH, NucleusAppearance, NucleusAuth, NucleusCache, NucleusCard, NucleusCell, NucleusColorPicker, NucleusEnterAdvance, NucleusFeedback, NucleusForm, NucleusGeo, NucleusGeoSelect, NucleusIconPicker, NucleusLanguage, NucleusList, NucleusLordicon, NucleusMark, NucleusMenu, NucleusMetric, NucleusMetrics, NucleusPage, NucleusPreset, NucleusRecentApps, NucleusSessionAuth, NucleusShell, NucleusShortcut, NucleusShortcuts, NucleusStarfield, NucleusTabs, SHELL_COPY, SessionService, TEXT_CONTRAST, THEME_COPY, ThemeService, UserSyncService, accentById, accentLabelOf, accentOn, ariaKeysOf, auditAppearance, canvasById, clock, comboOf, commonCopyOf, consumeNucleusLogout, contrastIssue, contrastRatio, day, densityById, endOfDayUtc, fieldsIn, findMenuByRoute, flagOf, foldText, frameById, geoCopyOf, hasChildren, hasModifier, iconClassOf, iconNameOf, integer, isAccentPreference, isApplePlatform, isBeforeToday, isCanvasPreference, isDarkCanvas, isDensityId, isFieldSearchable, isFrameId, isNucleusConfigured, isOnOrBeforeToday, isReachable, isToday, isTypingTarget, isoDay, levelLabel, loadLordicon, loadNucleusConfig, matchesRow, meetsContrast, menuGlyph, menuTile, minutesAsClock, mixHex, money, moveTo, normalizeKeys, nucleusAuthInterceptor, nucleusErrorMessage, nucleusLang, nucleusLogoutUrl, nucleusParseDate, ownsUrl, percent, permissionsOf, provideNucleus, provideNucleusFeedback, provideNucleusTheme, qty, readableOn, relativeLuminance, resolveAccent, resolveFieldSize, searchIcons, selectableRows, shiftDay, shortcutHint, signedMoney, stamp, startOfDayUtc, themeCopyOf, toNumber, toggleCollapsed, visibleTreeRows };
2036
3765
  //# sourceMappingURL=nucleus-suite-pe-core.mjs.map