@nucleus-suite-pe/core 0.6.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,47 +1,47 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, makeEnvironmentProviders, inject, Injector, signal, computed, Injectable, effect, input, CUSTOM_ELEMENTS_SCHEMA, ChangeDetectionStrategy, Component, model, output, TemplateRef, Directive, contentChildren, untracked } 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 { DOCUMENT, NgTemplateOutlet } from '@angular/common';
6
+ import * as i1 from '@angular/forms';
7
+ import { FormsModule } from '@angular/forms';
8
+ import * as i2 from 'primeng/select';
9
+ import { SelectModule } from 'primeng/select';
5
10
  import { definePreset } from '@primeuix/themes';
6
11
  import Lara from '@primeuix/themes/lara';
7
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';
8
16
  import { ConfirmationService, MessageService } from 'primeng/api';
9
- import * as i6 from '@angular/forms';
10
- import { FormsModule } from '@angular/forms';
11
- import * as i1 from 'primeng/checkbox';
12
- import { CheckboxModule } from 'primeng/checkbox';
13
- import * as i2 from 'primeng/inputtext';
17
+ import * as i2$1 from 'primeng/inputtext';
14
18
  import { InputTextModule } from 'primeng/inputtext';
15
- import * as i3 from 'primeng/select';
16
- import { SelectModule } from 'primeng/select';
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
- import * as i1$1 from 'primeng/button';
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$1 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
- import * as i1$2 from 'primeng/avatar';
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,352 +656,1310 @@ 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
 
488
- const THEMES = [
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
+
1028
+ const EMPTY_GEO_VALUE = { countryId: null, divisionId: null };
1029
+ function flagOf(code) {
1030
+ if (code.length !== 2)
1031
+ return '';
1032
+ return String.fromCodePoint(...[...code.toUpperCase()].map((letter) => 0x1f1a5 + letter.charCodeAt(0)));
1033
+ }
1034
+
1035
+ const GEO_COPY = {
1036
+ es: {
1037
+ country: 'País',
1038
+ levels: ['Región', 'Provincia', 'Distrito', 'Zona'],
1039
+ kinds: {
1040
+ department: 'Departamento',
1041
+ province: 'Provincia',
1042
+ district: 'Distrito',
1043
+ region: 'Región',
1044
+ state: 'Estado',
1045
+ municipality: 'Municipio',
1046
+ county: 'Condado',
1047
+ parish: 'Parroquia',
1048
+ prefecture: 'Prefectura',
1049
+ governorate: 'Gobernación',
1050
+ territory: 'Territorio',
1051
+ canton: 'Cantón',
1052
+ emirate: 'Emirato',
1053
+ oblast: 'Óblast',
1054
+ city: 'Ciudad',
1055
+ division: 'División',
1056
+ republic: 'República',
1057
+ 'autonomous community': 'Comunidad autónoma',
1058
+ 'federal district': 'Distrito federal',
1059
+ 'capital city': 'Ciudad capital',
1060
+ 'metropolitan department': 'Departamento metropolitano',
1061
+ 'unitary authority': 'Autoridad unitaria',
1062
+ },
1063
+ },
1064
+ en: {
1065
+ country: 'Country',
1066
+ levels: ['Region', 'Province', 'District', 'Area'],
1067
+ kinds: {
1068
+ department: 'Department',
1069
+ province: 'Province',
1070
+ district: 'District',
1071
+ region: 'Region',
1072
+ state: 'State',
1073
+ municipality: 'Municipality',
1074
+ county: 'County',
1075
+ parish: 'Parish',
1076
+ prefecture: 'Prefecture',
1077
+ governorate: 'Governorate',
1078
+ territory: 'Territory',
1079
+ canton: 'Canton',
1080
+ emirate: 'Emirate',
1081
+ oblast: 'Oblast',
1082
+ city: 'City',
1083
+ division: 'Division',
1084
+ republic: 'Republic',
1085
+ 'autonomous community': 'Autonomous community',
1086
+ 'federal district': 'Federal district',
1087
+ 'capital city': 'Capital city',
1088
+ 'metropolitan department': 'Metropolitan department',
1089
+ 'unitary authority': 'Unitary authority',
1090
+ },
1091
+ },
1092
+ };
1093
+ function geoCopyOf(lang) {
1094
+ return lang === 'en' ? GEO_COPY.en : GEO_COPY.es;
1095
+ }
1096
+ function levelLabel(copy, level, kind) {
1097
+ if (kind) {
1098
+ const known = copy.kinds[kind.toLowerCase()];
1099
+ if (known)
1100
+ return known;
1101
+ }
1102
+ const index = Math.min(Math.max(level, 1), copy.levels.length) - 1;
1103
+ return copy.levels[index];
1104
+ }
1105
+
1106
+ const EMPTY_LEVEL = { level: 0, kind: null, items: [] };
1107
+ class NucleusGeo {
1108
+ http = inject(HttpClient);
1109
+ config = inject(NUCLEUS_CONFIG);
1110
+ cache = new Map();
1111
+ countries(language) {
1112
+ return this.once(`countries|${language}`, () => firstValueFrom(this.http.get(`${this.config.securityApiUrl}/geo/countries`, {
1113
+ params: { language },
1114
+ })));
1115
+ }
1116
+ divisions(countryId, parentId, language) {
1117
+ const params = { language };
1118
+ if (parentId)
1119
+ params['parentId'] = parentId;
1120
+ return this.once(`divisions|${countryId}|${parentId ?? ''}|${language}`, () => firstValueFrom(this.http.get(`${this.config.securityApiUrl}/geo/countries/${countryId}/divisions`, { params })));
1121
+ }
1122
+ place(divisionId, language) {
1123
+ return this.once(`place|${divisionId}|${language}`, () => firstValueFrom(this.http.get(`${this.config.securityApiUrl}/geo/divisions/${divisionId}/place`, { params: { language } })));
1124
+ }
1125
+ async levelOf(countryId, parentId, language) {
1126
+ try {
1127
+ return await this.divisions(countryId, parentId, language);
1128
+ }
1129
+ catch {
1130
+ return EMPTY_LEVEL;
1131
+ }
1132
+ }
1133
+ once(key, load) {
1134
+ const cached = this.cache.get(key);
1135
+ if (cached)
1136
+ return cached;
1137
+ const started = load().catch((error) => {
1138
+ this.cache.delete(key);
1139
+ throw error;
1140
+ });
1141
+ this.cache.set(key, started);
1142
+ return started;
1143
+ }
1144
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusGeo, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1145
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusGeo, providedIn: 'root' });
1146
+ }
1147
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusGeo, decorators: [{
1148
+ type: Injectable,
1149
+ args: [{ providedIn: 'root' }]
1150
+ }] });
1151
+
1152
+ class NucleusGeoSelect {
1153
+ geo = inject(NucleusGeo);
1154
+ language = inject(NucleusLanguage);
1155
+ value = model(EMPTY_GEO_VALUE, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
1156
+ readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
1157
+ columns = input(null, ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
1158
+ countries = signal([], ...(ngDevMode ? [{ debugName: "countries" }] : /* istanbul ignore next */ []));
1159
+ steps = signal([], ...(ngDevMode ? [{ debugName: "steps" }] : /* istanbul ignore next */ []));
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 */ []));
1162
+ syncedKey = null;
1163
+ constructor() {
1164
+ effect(() => {
1165
+ const lang = this.language.lang();
1166
+ const current = this.value();
1167
+ const key = `${lang}|${current.countryId ?? ''}|${current.divisionId ?? ''}`;
1168
+ if (key === this.syncedKey)
1169
+ return;
1170
+ this.syncedKey = key;
1171
+ untracked(() => void this.sync(current, lang));
1172
+ });
1173
+ }
1174
+ gridClass() {
1175
+ const columns = this.columns();
1176
+ return columns ? `is-cap-${Math.min(Math.max(columns, 1), 4)}` : '';
1177
+ }
1178
+ nameOf(step) {
1179
+ return levelLabel(this.labels(), step.level, step.kind);
1180
+ }
1181
+ async pickCountry(countryId) {
1182
+ const lang = this.language.lang();
1183
+ this.syncedKey = `${lang}|${countryId ?? ''}|`;
1184
+ this.value.set({ countryId, divisionId: null });
1185
+ if (!countryId) {
1186
+ this.steps.set([]);
1187
+ return;
1188
+ }
1189
+ const first = await this.geo.levelOf(countryId, null, lang);
1190
+ this.steps.set(first.items.length ? [toStep(first.level, first.kind, first.items, null)] : []);
1191
+ }
1192
+ async pick(index, divisionId) {
1193
+ const countryId = this.value().countryId;
1194
+ if (!countryId)
1195
+ return;
1196
+ const lang = this.language.lang();
1197
+ const next = this.steps().slice(0, index + 1);
1198
+ const current = next[index];
1199
+ if (!current)
1200
+ return;
1201
+ next[index] = { ...current, selectedId: divisionId };
1202
+ if (divisionId) {
1203
+ const child = await this.geo.levelOf(countryId, divisionId, lang);
1204
+ if (child.items.length) {
1205
+ next.push(toStep(child.level, child.kind, child.items, null));
1206
+ }
1207
+ }
1208
+ this.steps.set(next);
1209
+ const deepest = deepestOf(next);
1210
+ this.syncedKey = `${lang}|${countryId}|${deepest ?? ''}`;
1211
+ this.value.set({ countryId, divisionId: deepest });
1212
+ }
1213
+ async sync(value, lang) {
1214
+ this.countries.set([...(await this.geo.countries(lang))]);
1215
+ if (!value.countryId) {
1216
+ this.steps.set([]);
1217
+ return;
1218
+ }
1219
+ if (!value.divisionId) {
1220
+ const first = await this.geo.levelOf(value.countryId, null, lang);
1221
+ this.steps.set(first.items.length ? [toStep(first.level, first.kind, first.items, null)] : []);
1222
+ return;
1223
+ }
1224
+ const place = await this.geo.place(value.divisionId, lang);
1225
+ const built = [];
1226
+ let parentId = null;
1227
+ for (const node of place.trail) {
1228
+ const level = await this.geo.levelOf(place.countryId, parentId, lang);
1229
+ if (!level.items.length)
1230
+ break;
1231
+ built.push(toStep(level.level, level.kind, level.items, node.id));
1232
+ parentId = node.id;
1233
+ }
1234
+ const tail = await this.geo.levelOf(place.countryId, parentId, lang);
1235
+ if (tail.items.length) {
1236
+ built.push(toStep(tail.level, tail.kind, tail.items, null));
1237
+ }
1238
+ this.steps.set(built);
1239
+ }
1240
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusGeoSelect, deps: [], target: i0.ɵɵFactoryTarget.Component });
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: `
1242
+ @let copy = labels();
1243
+ @let shared = words();
1244
+
1245
+ <div class="ns-form-grid" [class]="gridClass()">
1246
+ <div class="ns-field is-span-1 is-size-md">
1247
+ <label for="ns-geo-country">{{ copy.country }}</label>
1248
+ <p-select
1249
+ inputId="ns-geo-country"
1250
+ [options]="countries()"
1251
+ optionLabel="name"
1252
+ optionValue="id"
1253
+ [ngModel]="value().countryId"
1254
+ (ngModelChange)="pickCountry($event)"
1255
+ [disabled]="readonly()"
1256
+ [filter]="true"
1257
+ filterBy="name"
1258
+ [filterPlaceholder]="shared.search"
1259
+ [autofocusFilter]="true"
1260
+ [resetFilterOnHide]="true"
1261
+ [showClear]="true"
1262
+ [placeholder]="shared.choose"
1263
+ [ngModelOptions]="{ standalone: true }"
1264
+ fluid
1265
+ />
1266
+ </div>
1267
+
1268
+ @for (step of steps(); track step.level; let index = $index) {
1269
+ <div class="ns-field is-span-1 is-size-md">
1270
+ <label [attr.for]="'ns-geo-' + step.level">{{ nameOf(step) }}</label>
1271
+ <p-select
1272
+ [inputId]="'ns-geo-' + step.level"
1273
+ [options]="step.items"
1274
+ optionLabel="name"
1275
+ optionValue="id"
1276
+ [ngModel]="step.selectedId"
1277
+ (ngModelChange)="pick(index, $event)"
1278
+ [disabled]="readonly()"
1279
+ [filter]="true"
1280
+ filterBy="name"
1281
+ [filterPlaceholder]="shared.search"
1282
+ [autofocusFilter]="true"
1283
+ [resetFilterOnHide]="true"
1284
+ [showClear]="true"
1285
+ [placeholder]="shared.choose"
1286
+ [ngModelOptions]="{ standalone: true }"
1287
+ fluid
1288
+ />
1289
+ </div>
1290
+ }
1291
+ </div>
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 });
1293
+ }
1294
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusGeoSelect, decorators: [{
1295
+ type: Component,
1296
+ args: [{ selector: 'nucleus-geo-select', imports: [SelectModule, FormsModule], hostDirectives: [NucleusEnterAdvance], changeDetection: ChangeDetectionStrategy.OnPush, template: `
1297
+ @let copy = labels();
1298
+ @let shared = words();
1299
+
1300
+ <div class="ns-form-grid" [class]="gridClass()">
1301
+ <div class="ns-field is-span-1 is-size-md">
1302
+ <label for="ns-geo-country">{{ copy.country }}</label>
1303
+ <p-select
1304
+ inputId="ns-geo-country"
1305
+ [options]="countries()"
1306
+ optionLabel="name"
1307
+ optionValue="id"
1308
+ [ngModel]="value().countryId"
1309
+ (ngModelChange)="pickCountry($event)"
1310
+ [disabled]="readonly()"
1311
+ [filter]="true"
1312
+ filterBy="name"
1313
+ [filterPlaceholder]="shared.search"
1314
+ [autofocusFilter]="true"
1315
+ [resetFilterOnHide]="true"
1316
+ [showClear]="true"
1317
+ [placeholder]="shared.choose"
1318
+ [ngModelOptions]="{ standalone: true }"
1319
+ fluid
1320
+ />
1321
+ </div>
1322
+
1323
+ @for (step of steps(); track step.level; let index = $index) {
1324
+ <div class="ns-field is-span-1 is-size-md">
1325
+ <label [attr.for]="'ns-geo-' + step.level">{{ nameOf(step) }}</label>
1326
+ <p-select
1327
+ [inputId]="'ns-geo-' + step.level"
1328
+ [options]="step.items"
1329
+ optionLabel="name"
1330
+ optionValue="id"
1331
+ [ngModel]="step.selectedId"
1332
+ (ngModelChange)="pick(index, $event)"
1333
+ [disabled]="readonly()"
1334
+ [filter]="true"
1335
+ filterBy="name"
1336
+ [filterPlaceholder]="shared.search"
1337
+ [autofocusFilter]="true"
1338
+ [resetFilterOnHide]="true"
1339
+ [showClear]="true"
1340
+ [placeholder]="shared.choose"
1341
+ [ngModelOptions]="{ standalone: true }"
1342
+ fluid
1343
+ />
1344
+ </div>
1345
+ }
1346
+ </div>
1347
+ `, styles: [":host{display:block}\n"] }]
1348
+ }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }] } });
1349
+ function toStep(level, kind, items, selectedId) {
1350
+ return { level, kind, items: [...items], selectedId };
1351
+ }
1352
+ function deepestOf(steps) {
1353
+ for (let index = steps.length - 1; index >= 0; index -= 1) {
1354
+ const selected = steps[index]?.selectedId;
1355
+ if (selected)
1356
+ return selected;
1357
+ }
1358
+ return null;
1359
+ }
1360
+
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 = [
489
1402
  {
490
1403
  id: 'nucleus',
491
1404
  scheme: 'dark',
492
- swatch: '#7c6cf5',
493
1405
  starfield: true,
1406
+ accent: 'violet',
494
1407
  tokens: {
495
- bg: '#050508',
496
- surface: '#0d0d15',
497
- surfaceAlt: '#16161f',
498
- text: '#ffffff',
499
- textMuted: 'rgba(255, 255, 255, 0.6)',
500
- border: 'rgba(255, 255, 255, 0.14)',
501
- accent: '#7c6cf5',
502
- onAccent: '#ffffff',
1408
+ bg: '#16132b',
1409
+ raised: '#241f42',
1410
+ ink: '#ece9fb',
1411
+ muted: '#9d95c6',
1412
+ line: '#302a54',
1413
+ glow: '#8c7ff0',
503
1414
  },
504
1415
  },
505
1416
  {
506
- id: 'light',
507
- scheme: 'light',
508
- swatch: '#f5f5f5',
1417
+ id: 'midnight',
1418
+ scheme: 'dark',
509
1419
  starfield: false,
1420
+ accent: 'blue',
510
1421
  tokens: {
511
- bg: '#ffffff',
512
- surface: '#f5f5f5',
513
- surfaceAlt: '#eeeeee',
514
- text: '#1e1e1e',
515
- textMuted: '#5a5a5a',
516
- border: '#cccedb',
517
- accent: '#0078d4',
518
- onAccent: '#ffffff',
1422
+ bg: '#131f33',
1423
+ raised: '#1e2e48',
1424
+ ink: '#e3ecfa',
1425
+ muted: '#94a9c9',
1426
+ line: '#2b3d5d',
1427
+ glow: '#6ea8f0',
519
1428
  },
520
1429
  },
521
1430
  {
522
- id: 'dark',
1431
+ id: 'ocean',
523
1432
  scheme: 'dark',
524
- swatch: '#1e1e1e',
525
1433
  starfield: false,
1434
+ accent: 'cyan',
526
1435
  tokens: {
527
- bg: '#1e1e1e',
528
- surface: '#252526',
529
- surfaceAlt: '#2d2d30',
530
- text: '#f1f1f1',
531
- textMuted: '#a0a0a0',
532
- border: '#3f3f46',
533
- accent: '#0097fb',
534
- onAccent: '#ffffff',
1436
+ bg: '#0d2a30',
1437
+ raised: '#153c45',
1438
+ ink: '#e0f2f4',
1439
+ muted: '#8cb3b9',
1440
+ line: '#1d4e58',
1441
+ glow: '#4cc5e8',
535
1442
  },
536
1443
  },
537
1444
  {
538
- id: 'bubblegum',
539
- scheme: 'light',
540
- swatch: '#f7b8d4',
1445
+ id: 'forest',
1446
+ scheme: 'dark',
541
1447
  starfield: false,
1448
+ accent: 'green',
542
1449
  tokens: {
543
- bg: '#fff0f6',
544
- surface: '#ffe0ed',
545
- surfaceAlt: '#ffd0e4',
546
- text: '#3d1029',
547
- textMuted: '#8a4c6c',
548
- border: '#f0aac9',
549
- accent: '#d6337f',
550
- onAccent: '#ffffff',
1450
+ bg: '#122419',
1451
+ raised: '#1d3526',
1452
+ ink: '#e2f1e7',
1453
+ muted: '#93b8a1',
1454
+ line: '#28462f',
1455
+ glow: '#5cc47f',
551
1456
  },
552
1457
  },
553
1458
  {
554
- id: 'cool-breeze',
555
- scheme: 'light',
556
- swatch: '#a8d5e8',
1459
+ id: 'plum',
1460
+ scheme: 'dark',
557
1461
  starfield: false,
1462
+ accent: 'rose',
558
1463
  tokens: {
559
- bg: '#eef7fb',
560
- surface: '#dcecf5',
561
- surfaceAlt: '#cbe2ef',
562
- text: '#102a38',
563
- textMuted: '#4a6b7d',
564
- border: '#a5c9db',
565
- accent: '#0f7ba8',
566
- onAccent: '#ffffff',
1464
+ bg: '#241330',
1465
+ raised: '#341e43',
1466
+ ink: '#f3e8f7',
1467
+ muted: '#b596c3',
1468
+ line: '#452851',
1469
+ glow: '#cf7fdb',
567
1470
  },
568
1471
  },
569
1472
  {
570
- id: 'cool-slate',
1473
+ id: 'ember',
571
1474
  scheme: 'dark',
572
- swatch: '#3b4a5c',
573
1475
  starfield: false,
1476
+ accent: 'red',
574
1477
  tokens: {
575
- bg: '#232b36',
576
- surface: '#2c3644',
577
- surfaceAlt: '#354152',
578
- text: '#e6ecf4',
579
- textMuted: '#9fb0c4',
580
- border: '#44546a',
581
- accent: '#5fa8e8',
582
- onAccent: '#0b1118',
1478
+ bg: '#2a1113',
1479
+ raised: '#3d1b1f',
1480
+ ink: '#f8e6e7',
1481
+ muted: '#c9979c',
1482
+ line: '#4d2429',
1483
+ glow: '#f0797b',
583
1484
  },
584
1485
  },
585
1486
  {
586
- id: 'icy-mint',
587
- scheme: 'light',
588
- swatch: '#c4ece5',
1487
+ id: 'sand',
1488
+ scheme: 'dark',
589
1489
  starfield: false,
1490
+ accent: 'amber',
590
1491
  tokens: {
591
- bg: '#f0fbf8',
592
- surface: '#dcf3ee',
593
- surfaceAlt: '#c9eae3',
594
- text: '#0d2f2a',
595
- textMuted: '#4a706a',
596
- border: '#a3d6cd',
597
- accent: '#0d8f7d',
598
- onAccent: '#ffffff',
1492
+ bg: '#2b2415',
1493
+ raised: '#3d3320',
1494
+ ink: '#f6efdd',
1495
+ muted: '#c2b18b',
1496
+ line: '#4b3f28',
1497
+ glow: '#e0a33a',
599
1498
  },
600
1499
  },
601
1500
  {
602
- id: 'juicy-plum',
1501
+ id: 'slate',
603
1502
  scheme: 'dark',
604
- swatch: '#6b3a7a',
605
1503
  starfield: false,
1504
+ accent: 'blue',
606
1505
  tokens: {
607
- bg: '#2a1633',
608
- surface: '#372042',
609
- surfaceAlt: '#452a52',
610
- text: '#f3e8f7',
611
- textMuted: '#b795c4',
612
- border: '#57366a',
613
- accent: '#c86dd7',
614
- onAccent: '#1a0d20',
1506
+ bg: '#232b36',
1507
+ raised: '#313c4b',
1508
+ ink: '#e6ecf4',
1509
+ muted: '#9fb0c4',
1510
+ line: '#3d4a5b',
1511
+ glow: '#6fb2ea',
615
1512
  },
616
1513
  },
617
1514
  {
618
- id: 'mango-paradise',
619
- scheme: 'light',
620
- swatch: '#f7c48a',
1515
+ id: 'ink',
1516
+ scheme: 'dark',
621
1517
  starfield: false,
1518
+ accent: 'blue',
622
1519
  tokens: {
623
- bg: '#fff6ec',
624
- surface: '#ffe9d4',
625
- surfaceAlt: '#ffdcbc',
626
- text: '#3d2410',
627
- textMuted: '#8a5f38',
628
- border: '#f0c096',
629
- accent: '#e07314',
630
- onAccent: '#ffffff',
1520
+ bg: '#101318',
1521
+ raised: '#1b2129',
1522
+ ink: '#edf0f4',
1523
+ muted: '#97a1ae',
1524
+ line: '#262d37',
1525
+ glow: '#7fb3d9',
631
1526
  },
632
1527
  },
633
1528
  {
634
- id: 'moonlight-glow',
1529
+ id: 'graphite',
635
1530
  scheme: 'dark',
636
- swatch: '#1f3a5f',
637
1531
  starfield: false,
1532
+ accent: 'violet',
638
1533
  tokens: {
639
- bg: '#131f33',
640
- surface: '#1c2b45',
641
- surfaceAlt: '#243657',
642
- text: '#e3ecfa',
643
- textMuted: '#94a9c9',
644
- border: '#33496e',
645
- accent: '#6ea8f0',
646
- onAccent: '#0a1020',
1534
+ bg: '#17171a',
1535
+ raised: '#232327',
1536
+ ink: '#eeeef0',
1537
+ muted: '#9c9ca4',
1538
+ line: '#2f2f35',
1539
+ glow: '#b9b9c2',
647
1540
  },
648
1541
  },
649
1542
  {
650
- id: 'mystical-forest',
651
- scheme: 'dark',
652
- swatch: '#1f4a2e',
1543
+ id: 'paper',
1544
+ scheme: 'light',
1545
+ starfield: false,
1546
+ accent: 'violet',
1547
+ tokens: {
1548
+ bg: '#ffffff',
1549
+ raised: '#f1f0f5',
1550
+ ink: '#16151d',
1551
+ muted: '#5e5c6b',
1552
+ line: '#e3e2ea',
1553
+ glow: '#5b4fd6',
1554
+ },
1555
+ },
1556
+ {
1557
+ id: 'mist',
1558
+ scheme: 'light',
653
1559
  starfield: false,
1560
+ accent: 'blue',
654
1561
  tokens: {
655
- bg: '#132419',
656
- surface: '#1b3223',
657
- surfaceAlt: '#24422e',
658
- text: '#e4f2e8',
659
- textMuted: '#93b7a0',
660
- border: '#325a41',
661
- accent: '#4caf70',
662
- onAccent: '#0a1610',
1562
+ bg: '#eef1f6',
1563
+ raised: '#e2e7ef',
1564
+ ink: '#141a24',
1565
+ muted: '#576274',
1566
+ line: '#d3dae5',
1567
+ glow: '#1160b8',
663
1568
  },
664
1569
  },
1570
+ ];
1571
+ const CANVASES = [
665
1572
  {
666
- id: 'silky-pink',
667
- scheme: 'light',
668
- swatch: '#f3c2d6',
669
- starfield: false,
1573
+ id: 'light',
670
1574
  tokens: {
671
- bg: '#fdf2f7',
672
- surface: '#fae3ed',
673
- surfaceAlt: '#f6d3e2',
674
- text: '#3a1626',
675
- textMuted: '#87566a',
676
- border: '#eeb4cb',
677
- accent: '#c14b82',
678
- onAccent: '#ffffff',
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',
679
1593
  },
680
1594
  },
681
1595
  {
682
- id: 'spicy-red',
683
- scheme: 'dark',
684
- swatch: '#8f1f24',
685
- starfield: false,
1596
+ id: 'dim',
686
1597
  tokens: {
687
- bg: '#2a1113',
688
- surface: '#3a181b',
689
- surfaceAlt: '#4a1f23',
690
- text: '#f8e6e7',
691
- textMuted: '#c79398',
692
- border: '#5f2a2f',
693
- accent: '#e8555e',
694
- onAccent: '#1a0809',
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',
695
1616
  },
696
1617
  },
697
1618
  {
698
- id: 'sunny-day',
699
- scheme: 'light',
700
- swatch: '#efe4b0',
701
- starfield: false,
1619
+ id: 'dark',
702
1620
  tokens: {
703
- bg: '#fdf9e8',
704
- surface: '#f7efd2',
705
- surfaceAlt: '#f0e5bd',
706
- text: '#332d10',
707
- textMuted: '#736a38',
708
- border: '#ddcf94',
709
- accent: '#a8860b',
710
- 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',
711
1639
  },
712
1640
  },
713
1641
  ];
714
- 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 = {
1752
+ nucleus: 'Nucleus',
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',
1764
+ };
1765
+ const FRAMES_EN = {
715
1766
  nucleus: 'Nucleus',
716
- light: 'Light',
717
- dark: 'Dark',
718
- bubblegum: 'Bubblegum',
719
- 'cool-breeze': 'Cool Breeze',
720
- 'cool-slate': 'Cool Slate',
721
- 'icy-mint': 'Icy Mint',
722
- 'juicy-plum': 'Juicy Plum',
723
- 'mango-paradise': 'Mango Paradise',
724
- 'moonlight-glow': 'Moonlight Glow',
725
- 'mystical-forest': 'Mystical Forest',
726
- 'silky-pink': 'Silky Pink',
727
- 'spicy-red': 'Spicy Red',
728
- 'sunny-day': 'Sunny Day',
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
+ },
729
1834
  };
730
- const DEFAULT_THEME = 'nucleus';
731
- const BY_ID = new Map(THEMES.map((theme) => [theme.id, theme]));
732
- function themeById(id) {
733
- return BY_ID.get(id) ?? BY_ID.get(DEFAULT_THEME);
1835
+ function themeCopyOf(lang) {
1836
+ return THEME_COPY[lang] ?? THEME_COPY.es;
734
1837
  }
735
- function isThemePreference(value) {
736
- return value === 'system' || (typeof value === 'string' && BY_ID.has(value));
1838
+ function accentLabelOf(lang, id) {
1839
+ return themeCopyOf(lang).accents[id];
737
1840
  }
738
1841
 
739
- const STORAGE_KEY = 'nucleus_theme';
740
- 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;
741
1848
  try {
742
- const stored = localStorage.getItem(STORAGE_KEY);
743
- if (isThemePreference(stored))
744
- 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
+ }
745
1877
  }
746
1878
  catch { }
747
- return DEFAULT_THEME;
1879
+ return DEFAULT_APPEARANCE;
748
1880
  }
749
1881
  function prefersDark() {
750
- return globalThis.matchMedia?.('(prefers-color-scheme: dark)').matches ?? false;
1882
+ return globalThis.matchMedia?.(DARK_QUERY).matches ?? false;
751
1883
  }
752
1884
  class ThemeService {
753
- _preference = signal(readStoredPreference(), ...(ngDevMode ? [{ debugName: "_preference" }] : /* istanbul ignore next */ []));
1885
+ _appearance = signal(readStored(), ...(ngDevMode ? [{ debugName: "_appearance" }] : /* istanbul ignore next */ []));
754
1886
  systemDark = signal(prefersDark(), ...(ngDevMode ? [{ debugName: "systemDark" }] : /* istanbul ignore next */ []));
755
- preference = this._preference.asReadonly();
756
- theme = computed(() => {
757
- const preference = this._preference();
758
- if (preference === 'system')
759
- return themeById(this.systemDark() ? 'dark' : 'light');
760
- return themeById(preference);
761
- }, ...(ngDevMode ? [{ debugName: "theme" }] : /* istanbul ignore next */ []));
762
- 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 */ []));
763
1901
  constructor() {
764
1902
  globalThis
765
- .matchMedia?.('(prefers-color-scheme: dark)')
1903
+ .matchMedia?.(DARK_QUERY)
766
1904
  .addEventListener('change', (event) => this.systemDark.set(event.matches));
767
1905
  effect(() => {
768
- const preference = this._preference();
769
- const theme = this.theme();
770
- this.applyToDom(theme);
1906
+ const appearance = this._appearance();
1907
+ this.applyToDom();
771
1908
  try {
772
- localStorage.setItem(STORAGE_KEY, preference);
1909
+ localStorage.setItem(STORAGE_KEY$1, JSON.stringify(appearance));
773
1910
  }
774
1911
  catch { }
775
1912
  });
776
1913
  }
777
- setPreference(preference) {
778
- 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 });
779
1922
  }
780
- applyToDom(theme) {
1923
+ setDensity(density) {
1924
+ this.patch({ density });
1925
+ }
1926
+ reset() {
1927
+ this._appearance.set(DEFAULT_APPEARANCE);
1928
+ }
1929
+ patch(change) {
1930
+ this._appearance.update((appearance) => ({ ...appearance, ...change }));
1931
+ }
1932
+ applyToDom() {
781
1933
  const root = document.documentElement;
782
- for (const [name, value] of Object.entries(theme.tokens)) {
783
- 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);
784
1944
  }
785
- root.dataset['theme'] = theme.id;
786
- root.classList.toggle('ns-dark', theme.scheme === 'dark');
787
- 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';
788
1963
  }
789
1964
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ThemeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
790
1965
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ThemeService, providedIn: 'root' });
@@ -793,12 +1968,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
793
1968
  type: Injectable,
794
1969
  args: [{ providedIn: 'root' }]
795
1970
  }], ctorParameters: () => [] });
1971
+ function kebab(name) {
1972
+ return name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
1973
+ }
796
1974
 
797
1975
  const MASK = {
798
1976
  background: 'rgba(0, 0, 0, 0.55)',
799
1977
  color: 'var(--ac-text)',
800
1978
  };
801
- 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%))';
802
1980
  const PANEL = {
803
1981
  background: 'var(--ac-surface)',
804
1982
  borderColor: 'var(--ac-border)',
@@ -848,6 +2026,7 @@ const schemeTokens = {
848
2026
  },
849
2027
  },
850
2028
  formField: {
2029
+ paddingY: 'var(--ns-control-y, 0.55rem)',
851
2030
  background: 'var(--ac-bg)',
852
2031
  disabledBackground: 'var(--ac-surface-alt)',
853
2032
  filledBackground: 'var(--ac-surface-alt)',
@@ -953,9 +2132,9 @@ class NucleusLordicon {
953
2132
  size = input(48, ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
954
2133
  tone = input('accent', ...(ngDevMode ? [{ debugName: "tone" }] : /* istanbul ignore next */ []));
955
2134
  colors = computed(() => {
956
- const theme = this.themes.theme();
957
- const primary = this.tone() === 'danger' ? '#e8555e' : theme.tokens.accent;
958
- 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}`;
959
2138
  }, ...(ngDevMode ? [{ debugName: "colors" }] : /* istanbul ignore next */ []));
960
2139
  constructor() {
961
2140
  void loadLordicon();
@@ -990,8 +2169,23 @@ class NucleusCard {
990
2169
  heading = input('', ...(ngDevMode ? [{ debugName: "heading" }] : /* istanbul ignore next */ []));
991
2170
  subtitle = input('', ...(ngDevMode ? [{ debugName: "subtitle" }] : /* istanbul ignore next */ []));
992
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 */ []));
993
2187
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusCard, deps: [], target: i0.ɵɵFactoryTarget.Component });
994
- 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: `
995
2189
  @if (heading() || subtitle()) {
996
2190
  <div class="head">
997
2191
  @if (heading()) {
@@ -1003,16 +2197,29 @@ class NucleusCard {
1003
2197
  </div>
1004
2198
  }
1005
2199
 
1006
- <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>
1007
2214
 
1008
2215
  <div class="actions">
1009
2216
  <ng-content select="[nucleusCardActions]" />
1010
2217
  </div>
1011
- `, 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 });
1012
2219
  }
1013
2220
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusCard, decorators: [{
1014
2221
  type: Component,
1015
- 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: `
1016
2223
  @if (heading() || subtitle()) {
1017
2224
  <div class="head">
1018
2225
  @if (heading()) {
@@ -1024,13 +2231,105 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
1024
2231
  </div>
1025
2232
  }
1026
2233
 
1027
- <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>
1028
2248
 
1029
2249
  <div class="actions">
1030
2250
  <ng-content select="[nucleusCardActions]" />
1031
2251
  </div>
1032
- `, 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"] }]
1033
- }], 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 }] }] } });
1034
2333
 
1035
2334
  class NucleusTabs {
1036
2335
  tabs = input.required(...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
@@ -1139,8 +2438,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
1139
2438
  function resolveFieldSize(field) {
1140
2439
  if (field.size)
1141
2440
  return field.size;
1142
- if (field.kind === 'textarea')
2441
+ if (field.kind === 'textarea' || field.kind === 'color')
1143
2442
  return 'full';
2443
+ if (field.kind === 'icon')
2444
+ return 'md';
1144
2445
  const max = field.maxlength;
1145
2446
  if (max === undefined)
1146
2447
  return 'md';
@@ -1154,13 +2455,343 @@ function resolveFieldSize(field) {
1154
2455
  return 'lg';
1155
2456
  return 'full';
1156
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 }] }] } });
1157
2769
 
1158
2770
  class NucleusForm {
2771
+ language = inject(NucleusLanguage);
2772
+ host = inject(ElementRef);
2773
+ injector = inject(Injector);
1159
2774
  fields = input.required(...(ngDevMode ? [{ debugName: "fields" }] : /* istanbul ignore next */ []));
1160
2775
  value = input.required(...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
1161
2776
  readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
1162
2777
  columns = input(null, ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
2778
+ autofocus = input(false, ...(ngDevMode ? [{ debugName: "autofocus" }] : /* istanbul ignore next */ []));
1163
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
+ }
1164
2795
  read(field) {
1165
2796
  return this.value()[field.key] ?? null;
1166
2797
  }
@@ -1168,6 +2799,10 @@ class NucleusForm {
1168
2799
  const raw = this.value()[field.key];
1169
2800
  return raw === null || raw === undefined ? '' : String(raw);
1170
2801
  }
2802
+ nullableText(field) {
2803
+ const raw = this.value()[field.key];
2804
+ return raw === null || raw === undefined || raw === '' ? null : String(raw);
2805
+ }
1171
2806
  checked(field) {
1172
2807
  return this.value()[field.key] === true;
1173
2808
  }
@@ -1177,6 +2812,9 @@ class NucleusForm {
1177
2812
  isDisabled(field) {
1178
2813
  return this.readonly() || field.disabled === true;
1179
2814
  }
2815
+ isSearchable(field) {
2816
+ return isFieldSearchable(field);
2817
+ }
1180
2818
  fieldClass(field) {
1181
2819
  const span = field.span ?? 1;
1182
2820
  return `is-span-${span === 'full' ? 'full' : span} is-size-${resolveFieldSize(field)}`;
@@ -1192,7 +2830,7 @@ class NucleusForm {
1192
2830
  return field.kind ?? 'text';
1193
2831
  }
1194
2832
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
1195
- 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=\"grid\" [class]=\"gridClass()\">\n @for (field of fields(); track field.key) {\n <div class=\"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=\"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=\"inline-label\">{{ field.label }}</label>\n </div>\n }\n @case ('switch') {\n <div class=\"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=\"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=\"hint\">{{ field.hint }}</p>\n }\n </div>\n }\n</div>\n", styles: [":host{display:block}.grid{display:grid;grid-template-columns:1fr;gap:18px 20px}.field{display:flex;min-width:0;flex-direction:column;gap:6px}.field label{color:var(--ac-text-muted);font-size:12px;letter-spacing:.04em;text-transform:uppercase}.field .hint{margin:0;max-width:52ch;color:var(--ac-text-muted);font-size:12px;line-height:1.45}.inline{display:flex;min-height:38px;align-items:center;gap:10px}.inline-label{color:var(--ac-text);font-size:13px;letter-spacing:normal;text-transform:none}.is-size-xs :is(input,textarea,.p-select,.p-inputtext){max-width:6rem}.is-size-sm :is(input,textarea,.p-select,.p-inputtext){max-width:11rem}.is-size-md :is(input,textarea,.p-select,.p-inputtext){max-width:17rem}.is-size-lg :is(input,textarea,.p-select,.p-inputtext){max-width:26rem}.is-span-full{grid-column:1 / -1}@media(min-width:640px){.grid{grid-template-columns:repeat(2,minmax(0,1fr))}.is-span-2,.is-span-3,.is-span-4{grid-column:span 2}}@media(min-width:1080px){.grid:not(.is-cap-1):not(.is-cap-2){grid-template-columns:repeat(3,minmax(0,1fr))}.grid:not(.is-cap-1):not(.is-cap-2) .is-span-2{grid-column:span 2}.grid:not(.is-cap-1):not(.is-cap-2) :is(.is-span-3,.is-span-4){grid-column:span 3}}@media(min-width:1500px){.grid:not(.is-cap-1):not(.is-cap-2):not(.is-cap-3){grid-template-columns:repeat(4,minmax(0,1fr))}.grid:not(.is-cap-1):not(.is-cap-2):not(.is-cap-3) .is-span-3{grid-column:span 3}.grid:not(.is-cap-1):not(.is-cap-2):not(.is-cap-3) .is-span-4{grid-column:span 4}}.grid.is-cap-1{grid-template-columns:1fr}.grid.is-cap-1 .field{grid-column:1 / -1}\n"], dependencies: [{ kind: "ngmodule", type: CheckboxModule }, { kind: "component", type: i1.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: i3.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 });
1196
2834
  }
1197
2835
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusForm, decorators: [{
1198
2836
  type: Component,
@@ -1203,8 +2841,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
1203
2841
  TextareaModule,
1204
2842
  ToggleSwitchModule,
1205
2843
  FormsModule,
1206
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"grid\" [class]=\"gridClass()\">\n @for (field of fields(); track field.key) {\n <div class=\"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=\"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=\"inline-label\">{{ field.label }}</label>\n </div>\n }\n @case ('switch') {\n <div class=\"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=\"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=\"hint\">{{ field.hint }}</p>\n }\n </div>\n }\n</div>\n", styles: [":host{display:block}.grid{display:grid;grid-template-columns:1fr;gap:18px 20px}.field{display:flex;min-width:0;flex-direction:column;gap:6px}.field label{color:var(--ac-text-muted);font-size:12px;letter-spacing:.04em;text-transform:uppercase}.field .hint{margin:0;max-width:52ch;color:var(--ac-text-muted);font-size:12px;line-height:1.45}.inline{display:flex;min-height:38px;align-items:center;gap:10px}.inline-label{color:var(--ac-text);font-size:13px;letter-spacing:normal;text-transform:none}.is-size-xs :is(input,textarea,.p-select,.p-inputtext){max-width:6rem}.is-size-sm :is(input,textarea,.p-select,.p-inputtext){max-width:11rem}.is-size-md :is(input,textarea,.p-select,.p-inputtext){max-width:17rem}.is-size-lg :is(input,textarea,.p-select,.p-inputtext){max-width:26rem}.is-span-full{grid-column:1 / -1}@media(min-width:640px){.grid{grid-template-columns:repeat(2,minmax(0,1fr))}.is-span-2,.is-span-3,.is-span-4{grid-column:span 2}}@media(min-width:1080px){.grid:not(.is-cap-1):not(.is-cap-2){grid-template-columns:repeat(3,minmax(0,1fr))}.grid:not(.is-cap-1):not(.is-cap-2) .is-span-2{grid-column:span 2}.grid:not(.is-cap-1):not(.is-cap-2) :is(.is-span-3,.is-span-4){grid-column:span 3}}@media(min-width:1500px){.grid:not(.is-cap-1):not(.is-cap-2):not(.is-cap-3){grid-template-columns:repeat(4,minmax(0,1fr))}.grid:not(.is-cap-1):not(.is-cap-2):not(.is-cap-3) .is-span-3{grid-column:span 3}.grid:not(.is-cap-1):not(.is-cap-2):not(.is-cap-3) .is-span-4{grid-column:span 4}}.grid.is-cap-1{grid-template-columns:1fr}.grid.is-cap-1 .field{grid-column:1 / -1}\n"] }]
1207
- }], 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"] }] } });
1208
2848
 
1209
2849
  class NucleusCell {
1210
2850
  nucleusCell = input.required(...(ngDevMode ? [{ debugName: "nucleusCell" }] : /* istanbul ignore next */ []));
@@ -1226,12 +2866,24 @@ class NucleusList {
1226
2866
  empty = input('', ...(ngDevMode ? [{ debugName: "empty" }] : /* istanbul ignore next */ []));
1227
2867
  loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
1228
2868
  minWidth = input('640px', ...(ngDevMode ? [{ debugName: "minWidth" }] : /* istanbul ignore next */ []));
2869
+ skeletonRows = input(5, ...(ngDevMode ? [{ debugName: "skeletonRows" }] : /* istanbul ignore next */ []));
1229
2870
  rowClass = input(() => '', ...(ngDevMode ? [{ debugName: "rowClass" }] : /* istanbul ignore next */ []));
1230
2871
  action = output();
1231
2872
  toggle = output();
1232
2873
  cells = contentChildren(NucleusCell, ...(ngDevMode ? [{ debugName: "cells" }] : /* istanbul ignore next */ []));
1233
2874
  tableStyle = computed(() => ({ 'min-width': this.minWidth() }), ...(ngDevMode ? [{ debugName: "tableStyle" }] : /* istanbul ignore next */ []));
1234
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
+ }
1235
2887
  cellTemplate(key) {
1236
2888
  return this.cells().find((cell) => cell.nucleusCell() === key)?.template ?? null;
1237
2889
  }
@@ -1247,6 +2899,9 @@ class NucleusList {
1247
2899
  visibleActions(row) {
1248
2900
  return this.actions().filter((entry) => !entry.visible || entry.visible(row));
1249
2901
  }
2902
+ isActionLocked(entry, row) {
2903
+ return entry.disabled ? entry.disabled(row) : false;
2904
+ }
1250
2905
  run(entry, row) {
1251
2906
  this.action.emit({ id: entry.id, row });
1252
2907
  }
@@ -1265,12 +2920,37 @@ class NucleusList {
1265
2920
  this.toggle.emit({ key: column.key, row, value: !this.isChecked(column, row) });
1266
2921
  }
1267
2922
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusList, deps: [], target: i0.ɵɵFactoryTarget.Component });
1268
- 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$1.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$1.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 });
1269
2924
  }
1270
2925
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusList, decorators: [{
1271
2926
  type: Component,
1272
- 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"] }]
1273
- }], 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
+ }
1274
2954
 
1275
2955
  const ORBIT = { rx: 78, ry: 196 };
1276
2956
  class NucleusMark {
@@ -1324,6 +3004,238 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
1324
3004
  `, styles: [":host{display:block}svg{display:block}\n"] }]
1325
3005
  }], propDecorators: { accent: [{ type: i0.Input, args: [{ isSignal: true, alias: "accent", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
1326
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
+
1327
3239
  const W = 1280;
1328
3240
  const H = 720;
1329
3241
  const STARS = Array.from({ length: 130 }, (_, i) => {
@@ -1419,10 +3331,17 @@ const SHELL_COPY = {
1419
3331
  navToggle: 'Mostrar u ocultar el menú',
1420
3332
  appsLauncher: 'Aplicaciones',
1421
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',
1422
3344
  settingsTitle: 'Ajustes',
1423
- settingsTheme: 'Tema',
1424
- settingsThemeHint: 'Elige cómo se ve la suite en este dispositivo.',
1425
- themeSystem: 'Del sistema',
1426
3345
  signOut: 'Cerrar sesión',
1427
3346
  accountManage: 'Gestionar mi cuenta',
1428
3347
  accountSwitch: 'Cambiar de cuenta',
@@ -1431,19 +3350,15 @@ const SHELL_COPY = {
1431
3350
  accountScope: 'Mi cuenta',
1432
3351
  hintComingSoon: 'Próximamente',
1433
3352
  hintNotDeployed: 'Sin publicar',
1434
- switchSite: 'Cambiar sede',
1435
- contextTitle: 'Compañía y sede',
1436
- contextHint: 'Elige dónde quieres trabajar. El menú se carga según tu rol en la sede.',
3353
+ roleCaption: 'Tu rol',
1437
3354
  companyLabel: 'Compañía',
1438
3355
  companyPlaceholder: 'Selecciona una compañía',
1439
3356
  siteLabel: 'Sede',
1440
3357
  sitePlaceholder: 'Selecciona una sede',
1441
3358
  sitesLoading: 'Cargando sedes...',
1442
3359
  sitesEmpty: 'Esta compañía no tiene sedes a tu alcance',
1443
- confirmSwitch: 'Cambiar',
1444
3360
  relationOwner: 'Propietario',
1445
3361
  relationCollaborator: 'Colaborador',
1446
- noContext: 'Sin sede seleccionada',
1447
3362
  noCompanyTitle: 'Te falta una compañía',
1448
3363
  noCompanyHint: 'Para trabajar en las aplicaciones necesitas al menos una compañía con una sede, ' +
1449
3364
  'propia o donde te hayan invitado. Puedes entrar igual, pero el menú saldrá vacío.',
@@ -1452,10 +3367,17 @@ const SHELL_COPY = {
1452
3367
  navToggle: 'Show or hide the menu',
1453
3368
  appsLauncher: 'Applications',
1454
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',
1455
3380
  settingsTitle: 'Settings',
1456
- settingsTheme: 'Theme',
1457
- settingsThemeHint: 'Choose how the suite looks on this device.',
1458
- themeSystem: 'System',
1459
3381
  signOut: 'Sign out',
1460
3382
  accountManage: 'Manage my account',
1461
3383
  accountSwitch: 'Switch account',
@@ -1464,29 +3386,29 @@ const SHELL_COPY = {
1464
3386
  accountScope: 'My account',
1465
3387
  hintComingSoon: 'Coming soon',
1466
3388
  hintNotDeployed: 'Not deployed',
1467
- switchSite: 'Switch site',
1468
- contextTitle: 'Company and site',
1469
- contextHint: 'Choose where you want to work. The menu loads from your role at that site.',
3389
+ roleCaption: 'Your role',
1470
3390
  companyLabel: 'Company',
1471
3391
  companyPlaceholder: 'Select a company',
1472
3392
  siteLabel: 'Site',
1473
3393
  sitePlaceholder: 'Select a site',
1474
3394
  sitesLoading: 'Loading sites...',
1475
3395
  sitesEmpty: 'This company has no sites available to you',
1476
- confirmSwitch: 'Switch',
1477
3396
  relationOwner: 'Owner',
1478
3397
  relationCollaborator: 'Collaborator',
1479
- noContext: 'No site selected',
1480
3398
  noCompanyTitle: 'You need a company',
1481
3399
  noCompanyHint: 'To work in the applications you need at least one company with a site, your own or ' +
1482
3400
  'one you have been invited to. You can still open them, but the menu will be empty.',
1483
3401
  },
1484
3402
  };
1485
3403
 
3404
+ const LAUNCHER_RICH_THRESHOLD = 6;
3405
+ const RECENT_LIMIT = 3;
3406
+ const FALLBACK_APP_COLOR = '#64748b';
1486
3407
  class NucleusShell {
1487
3408
  session = inject(SessionService);
1488
3409
  sessionAuth = inject(NucleusSessionAuth);
1489
3410
  themes = inject(ThemeService);
3411
+ recents = inject(NucleusRecentApps);
1490
3412
  language = inject(NucleusLanguage);
1491
3413
  page = inject(NucleusPage);
1492
3414
  config = inject(NUCLEUS_CONFIG);
@@ -1502,6 +3424,7 @@ class NucleusShell {
1502
3424
  menuSelect = output();
1503
3425
  lang = this.language.lang;
1504
3426
  icons = NUCLEUS_ICONS;
3427
+ menuPlaceholders = ['64%', '80%', '52%', '72%', '58%'];
1505
3428
  title = computed(() => this.heading() || this.page.heading(), ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
1506
3429
  subtitle = computed(() => this.lead() || this.page.lead(), ...(ngDevMode ? [{ debugName: "subtitle" }] : /* istanbul ignore next */ []));
1507
3430
  home = computed(() => ({ icon: 'pi pi-home', routerLink: '/' }), ...(ngDevMode ? [{ debugName: "home" }] : /* istanbul ignore next */ []));
@@ -1511,20 +3434,22 @@ class NucleusShell {
1511
3434
  })), ...(ngDevMode ? [{ debugName: "crumbs" }] : /* istanbul ignore next */ []));
1512
3435
  copy = computed(() => SHELL_COPY[this.lang()], ...(ngDevMode ? [{ debugName: "copy" }] : /* istanbul ignore next */ []));
1513
3436
  starfield = this.themes.starfield;
1514
- themePreference = this.themes.preference;
1515
3437
  menu = this.session.menu;
3438
+ collapsedIds = signal(new Set(), ...(ngDevMode ? [{ debugName: "collapsedIds" }] : /* istanbul ignore next */ []));
1516
3439
  menuLoading = this.session.loading;
1517
3440
  menuError = this.session.error;
1518
3441
  launcherApps = this.session.apps;
1519
3442
  switching = this.session.switching;
1520
- 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 */ []));
1521
3446
  settingsOpen = signal(false, ...(ngDevMode ? [{ debugName: "settingsOpen" }] : /* istanbul ignore next */ []));
1522
- contextOpen = signal(false, ...(ngDevMode ? [{ debugName: "contextOpen" }] : /* istanbul ignore next */ []));
1523
3447
  companyChoice = signal(null, ...(ngDevMode ? [{ debugName: "companyChoice" }] : /* istanbul ignore next */ []));
1524
3448
  siteChoice = signal(null, ...(ngDevMode ? [{ debugName: "siteChoice" }] : /* istanbul ignore next */ []));
1525
3449
  siteOptions = signal([], ...(ngDevMode ? [{ debugName: "siteOptions" }] : /* istanbul ignore next */ []));
1526
3450
  sitesLoading = signal(false, ...(ngDevMode ? [{ debugName: "sitesLoading" }] : /* istanbul ignore next */ []));
1527
3451
  auth = this.injector.get(NucleusAuth, null);
3452
+ sitesCompanyId = null;
1528
3453
  identity = computed(() => {
1529
3454
  const me = this.session.me();
1530
3455
  if (me) {
@@ -1553,26 +3478,102 @@ class NucleusShell {
1553
3478
  tag: company.relation === 'owner' ? t.relationOwner : t.relationCollaborator,
1554
3479
  }));
1555
3480
  }, ...(ngDevMode ? [{ debugName: "companyChoices" }] : /* istanbul ignore next */ []));
1556
- contextCaption = computed(() => {
1557
- const role = this.session.roleName();
1558
- const site = this.session.siteName();
1559
- const company = this.session.companyName();
1560
- if (role && site)
1561
- return company ? `${company} · ${site}` : site;
1562
- if (this.session.scope() === 'account')
1563
- return this.copy().accountScope;
1564
- return this.copy().noContext;
1565
- }, ...(ngDevMode ? [{ debugName: "contextCaption" }] : /* istanbul ignore next */ []));
1566
3481
  roleName = this.session.roleName;
1567
- canConfirm = computed(() => this.companyChoice() !== null && this.siteChoice() !== null && !this.switching(), ...(ngDevMode ? [{ debugName: "canConfirm" }] : /* istanbul ignore next */ []));
1568
- themeOptions = computed(() => [
1569
- ...THEMES.map((theme) => ({
1570
- value: theme.id,
1571
- label: THEME_LABELS[theme.id],
1572
- swatch: theme.swatch,
1573
- })),
1574
- { value: 'system', label: this.copy().themeSystem, swatch: null },
1575
- ], ...(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 */ []));
1576
3577
  loadSession = effect(() => {
1577
3578
  const language = this.lang();
1578
3579
  const user = this.auth?.user() ?? null;
@@ -1580,49 +3581,97 @@ class NucleusShell {
1580
3581
  return;
1581
3582
  untracked(() => void this.session.load(language));
1582
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
+ }
1583
3595
  setLang(lang) {
1584
3596
  this.language.setLang(lang);
1585
3597
  }
1586
3598
  toggleSidebar() {
1587
3599
  this.sidebarOpen.update((open) => !open);
1588
3600
  }
1589
- selectTheme(preference) {
1590
- if (preference)
1591
- this.themes.setPreference(preference);
1592
- }
1593
3601
  selectNav(id) {
1594
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);
1595
3610
  }
1596
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;
1597
3623
  this.menuSelect.emit(node);
1598
- if (node.route)
1599
- 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);
1600
3630
  }
1601
3631
  isSelected(node) {
1602
3632
  return this.selectedMenuId() === node.id;
1603
3633
  }
1604
- async openContext() {
1605
- const companyId = this.session.companyId() ?? this.companyChoices()[0]?.id ?? null;
1606
- this.companyChoice.set(companyId);
1607
- this.siteChoice.set(this.session.siteId());
1608
- this.contextOpen.set(true);
1609
- if (companyId)
1610
- await this.loadSites(companyId);
1611
- }
1612
3634
  async onCompanyChange(companyId) {
3635
+ if (!companyId || companyId === this.session.companyId())
3636
+ return;
1613
3637
  this.companyChoice.set(companyId);
1614
3638
  this.siteChoice.set(null);
1615
- this.siteOptions.set([]);
1616
- if (companyId)
1617
- 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);
1618
3644
  }
1619
- async confirmSwitch() {
3645
+ async onSiteChange(siteId) {
1620
3646
  const companyId = this.companyChoice();
1621
- const siteId = this.siteChoice();
1622
- if (!companyId || !siteId)
3647
+ if (!siteId || !companyId || siteId === this.session.siteId())
1623
3648
  return;
3649
+ this.siteChoice.set(siteId);
1624
3650
  await this.session.switchContext(companyId, siteId);
1625
- 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();
1626
3675
  }
1627
3676
  canOpen(app) {
1628
3677
  return app.status === 1 && !!app.url;
@@ -1630,6 +3679,7 @@ class NucleusShell {
1630
3679
  launch(app) {
1631
3680
  if (!this.canOpen(app))
1632
3681
  return;
3682
+ this.recents.remember(app.id);
1633
3683
  window.location.href = app.url;
1634
3684
  }
1635
3685
  appInitials(app) {
@@ -1654,24 +3704,27 @@ class NucleusShell {
1654
3704
  window.location.href = nucleusLogoutUrl(landing);
1655
3705
  }
1656
3706
  async loadSites(companyId) {
3707
+ if (this.sitesCompanyId === companyId)
3708
+ return this.siteOptions();
3709
+ this.sitesCompanyId = companyId;
1657
3710
  this.sitesLoading.set(true);
1658
3711
  try {
1659
3712
  const sites = await this.session.accessibleSites(companyId);
1660
3713
  this.siteOptions.set(sites);
1661
- if (!sites.some((site) => site.id === this.siteChoice())) {
1662
- this.siteChoice.set(sites[0]?.id ?? null);
1663
- }
3714
+ return sites;
1664
3715
  }
1665
3716
  catch (error) {
3717
+ this.sitesCompanyId = null;
1666
3718
  this.siteOptions.set([]);
1667
3719
  console.error('[shell] no se pudieron cargar las sedes', error);
3720
+ return [];
1668
3721
  }
1669
3722
  finally {
1670
3723
  this.sitesLoading.set(false);
1671
3724
  }
1672
3725
  }
1673
3726
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusShell, deps: [], target: i0.ɵɵFactoryTarget.Component });
1674
- 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$2.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$1.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: i3.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$1.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 });
1675
3728
  }
1676
3729
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NucleusShell, decorators: [{
1677
3730
  type: Component,
@@ -1682,17 +3735,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
1682
3735
  ConfirmDialogModule,
1683
3736
  ToastModule,
1684
3737
  DrawerModule,
1685
- ListboxModule,
1686
3738
  PopoverModule,
1687
3739
  SelectModule,
3740
+ SkeletonModule,
1688
3741
  TagModule,
1689
3742
  TooltipModule,
1690
3743
  FormsModule,
3744
+ NucleusAppearance,
1691
3745
  NucleusLordicon,
3746
+ NucleusMenu,
1692
3747
  NucleusMark,
1693
3748
  NucleusStarfield,
1694
- ], 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"] }]
1695
- }], 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"] }] } });
1696
3751
  function initialsOf(source) {
1697
3752
  const parts = source.trim().split(/\s+/).filter(Boolean);
1698
3753
  if (!parts.length)
@@ -1706,5 +3761,5 @@ function initialsOf(source) {
1706
3761
  * Generated bundle index. Do not edit.
1707
3762
  */
1708
3763
 
1709
- export { DEFAULT_THEME, 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, NucleusLanguage, NucleusList, NucleusLordicon, NucleusMark, NucleusPage, NucleusPreset, NucleusSessionAuth, NucleusShell, NucleusStarfield, NucleusTabs, SHELL_COPY, SessionService, THEMES, THEME_LABELS, ThemeService, UserSyncService, consumeNucleusLogout, findMenuByRoute, isNucleusConfigured, isThemePreference, 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 };
1710
3765
  //# sourceMappingURL=nucleus-suite-pe-core.mjs.map