@praxisui/dynamic-fields 1.0.0-beta.13 → 1.0.0-beta.15

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.
package/README.md CHANGED
@@ -92,6 +92,7 @@ O sistema usa as constantes do `@praxisui/core` para garantir consistência:
92
92
  - `FieldControlType.TIME_PICKER` - Seletor de horário
93
93
  - `FieldControlType.RATING` - Classificação por estrelas
94
94
  - `FieldControlType.COLOR_PICKER` - Seletor de cores
95
+ - `FieldControlType.AVATAR` - Avatar visual (imagem/ícone/iniciais)
95
96
 
96
97
  Campos com `FieldControlType.AUTO_COMPLETE` utilizam internamente o `MaterialSearchableSelectComponent`, habilitando busca automaticamente.
97
98
 
@@ -122,6 +123,44 @@ Grupo de botões de rádio que consome metadados ou dados remotos para criar sel
122
123
  - **Carregamento Dinâmico**: mesmas chaves de configuração de `MaterialSelectComponent` para buscar opções.
123
124
  - **Layout Flexível**: configuração de orientação, `labelPosition` e `color` segundo a [documentação oficial](https://material.angular.dev/components/radio/overview).
124
125
 
126
+ ## 🧩 Avatar Field
127
+
128
+ Componente visual para exibir representação de usuário/entidade/estado, com prioridade de conteúdo:
129
+
130
+ - imageSrc > icon > initials > ng-content
131
+
132
+ Propriedades principais: `themeColor`, `rounded`, `size`, `fillMode`, `border`, `tooltip`, `ariaLabel`.
133
+
134
+ Exemplos:
135
+
136
+ ```html
137
+ <pdx-material-avatar [imageSrc]="user.img" size="large" themeColor="tertiary"></pdx-material-avatar>
138
+ <pdx-material-avatar initials="MB" themeColor="secondary"></pdx-material-avatar>
139
+ <pdx-material-avatar icon="person" fillMode="outline"></pdx-material-avatar>
140
+ <pdx-material-avatar><app-status-badge></app-status-badge></pdx-material-avatar>
141
+ ```
142
+
143
+ Uso em formulários dinâmicos (via metadata):
144
+
145
+ ```ts
146
+ import { FieldControlType, type FieldMetadata } from '@praxisui/core';
147
+
148
+ const fields: FieldMetadata[] = [
149
+ {
150
+ name: 'avatar',
151
+ label: 'Avatar',
152
+ controlType: FieldControlType.AVATAR,
153
+ // opções específicas via `extra` (quando aplicável)
154
+ extra: { imageSrc: 'https://example.com/u/42.png', size: 'large' }
155
+ }
156
+ ];
157
+ ```
158
+
159
+ Tokens M3 aplicados:
160
+
161
+ - `--pfx-avatar-bg`, `--pfx-avatar-fg`, `--pfx-avatar-border-color`, `--pfx-avatar-border-w`
162
+ - `--pfx-avatar-size`, `--pfx-avatar-radius-[full|lg|md|sm]`
163
+
125
164
  ## 📦 Instalação
126
165
 
127
166
  ```bash
@@ -1,9 +1,9 @@
1
1
  import * as i4 from '@angular/forms';
2
2
  import { NgControl, FormControl, Validators, ReactiveFormsModule, NG_VALUE_ACCESSOR, FormGroup, FormsModule } from '@angular/forms';
3
3
  import * as i0 from '@angular/core';
4
- import { inject, DestroyRef, ElementRef, ChangeDetectorRef, Injector, signal, output, computed, Input, Directive, viewChild, effect, forwardRef, Component, ViewChild, LOCALE_ID, EventEmitter, Injectable, ViewContainerRef, HostListener, HostBinding, Output, ViewEncapsulation, ChangeDetectionStrategy, Inject, TemplateRef, ContentChild, APP_INITIALIZER } from '@angular/core';
4
+ import { inject, DestroyRef, ElementRef, ChangeDetectorRef, Injector, signal, output, computed, Input, Directive, viewChild, effect, forwardRef, Component, ViewChild, LOCALE_ID, EventEmitter, Injectable, Output, ViewContainerRef, HostListener, HostBinding, ViewEncapsulation, ChangeDetectionStrategy, Inject, TemplateRef, ContentChild, ENVIRONMENT_INITIALIZER, APP_INITIALIZER } from '@angular/core';
5
5
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
6
- import { isCssTextTransform, getTextTransformer, GenericCrudService, GlobalConfigService, PraxisIconDirective, createCpfCnpjValidator, FieldControlType } from '@praxisui/core';
6
+ import { isCssTextTransform, getTextTransformer, GenericCrudService, GlobalConfigService, PraxisIconDirective, createCpfCnpjValidator, FieldControlType, ComponentMetadataRegistry } from '@praxisui/core';
7
7
  import { BehaviorSubject, combineLatest, take as take$1, of, EMPTY, firstValueFrom, fromEvent, Subscription } from 'rxjs';
8
8
  import { Router } from '@angular/router';
9
9
  import * as i1$4 from '@angular/material/dialog';
@@ -59,6 +59,7 @@ import * as i2$3 from '@angular/material/slide-toggle';
59
59
  import { MatSlideToggleModule } from '@angular/material/slide-toggle';
60
60
  import * as i3$7 from '@angular/material/list';
61
61
  import { MatListModule } from '@angular/material/list';
62
+ import { DomSanitizer } from '@angular/platform-browser';
62
63
  import * as i2$4 from '@angular/cdk/drag-drop';
63
64
  import { moveItemInArray, transferArrayItem, DragDropModule } from '@angular/cdk/drag-drop';
64
65
  import { PdxCronBuilderComponent } from '@praxisui/cron-builder';
@@ -13196,6 +13197,341 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
13196
13197
  type: Input
13197
13198
  }] } });
13198
13199
 
13200
+ class MaterialAvatarComponent {
13201
+ sanitizer = inject(DomSanitizer);
13202
+ elementRef = inject(ElementRef);
13203
+ // Dynamic field metadata (optional, for DynamicFieldLoader integration)
13204
+ metadata = signal(null, ...(ngDevMode ? [{ debugName: "metadata" }] : []));
13205
+ componentId = signal('pdx-avatar-' + Math.random().toString(36).slice(2), ...(ngDevMode ? [{ debugName: "componentId" }] : []));
13206
+ // Optional reactive control from DynamicFieldLoader (read-only consumption)
13207
+ // The loader will set this when available. We use its current value as a
13208
+ // fallback for image source when the input [imageSrc] is not provided.
13209
+ formControl = signal(null, ...(ngDevMode ? [{ debugName: "formControl" }] : []));
13210
+ // Content sources (priority: imageSrc > icon > initials > content)
13211
+ imageSrc;
13212
+ imageAlt;
13213
+ initials;
13214
+ icon;
13215
+ /** Fallback icon used when no image/icon/initials provided via inputs */
13216
+ defaultIcon;
13217
+ // Visual props
13218
+ themeColor = 'primary';
13219
+ rounded = 'full';
13220
+ size = 'medium';
13221
+ fillMode = 'solid';
13222
+ border = false;
13223
+ // UX/ARIA
13224
+ tooltip;
13225
+ ariaLabel;
13226
+ // Dynamic field global states (herdadas do shell/loader)
13227
+ readonlyMode;
13228
+ disabledMode;
13229
+ visible;
13230
+ presentationMode;
13231
+ // Style/class passthroughs (optional)
13232
+ class;
13233
+ style;
13234
+ imageError = new EventEmitter();
13235
+ // Internal image error flag
13236
+ _imgFailed = signal(false, ...(ngDevMode ? [{ debugName: "_imgFailed" }] : []));
13237
+ // Resolved sources
13238
+ resolvedImageSrc = computed(() => {
13239
+ // Priority: explicit input > form control value > metadata.defaultValue
13240
+ const byInput = (this.imageSrc || '').trim();
13241
+ if (byInput)
13242
+ return byInput;
13243
+ const c = this.formControl();
13244
+ const val = (c?.value ?? '');
13245
+ if (typeof val === 'string' && val.trim())
13246
+ return val.trim();
13247
+ const byMeta = String(this.metadata()?.defaultValue || '').trim();
13248
+ if (byMeta)
13249
+ return byMeta;
13250
+ return '';
13251
+ }, ...(ngDevMode ? [{ debugName: "resolvedImageSrc" }] : []));
13252
+ // Reset image error flag whenever source changes
13253
+ _resetOnSrcChange = effect(() => {
13254
+ // touch computed to track its deps
13255
+ this.resolvedImageSrc();
13256
+ this._imgFailed.set(false);
13257
+ }, ...(ngDevMode ? [{ debugName: "_resetOnSrcChange" }] : []));
13258
+ // Derived flags
13259
+ hasImage = computed(() => !!this.resolvedImageSrc() && !this._imgFailed(), ...(ngDevMode ? [{ debugName: "hasImage" }] : []));
13260
+ hasIcon = computed(() => this.resolveIconKind(this.icon ?? this.defaultIcon) !== null, ...(ngDevMode ? [{ debugName: "hasIcon" }] : []));
13261
+ hasInitials = computed(() => this.normalizedInitials().length > 0, ...(ngDevMode ? [{ debugName: "hasInitials" }] : []));
13262
+ // Host classes
13263
+ componentCssClasses = computed(() => {
13264
+ const classes = ['pfx-avatar'];
13265
+ classes.push(`theme-${this.themeColor || 'primary'}`);
13266
+ classes.push(`rounded-${this.rounded || 'full'}`);
13267
+ classes.push(`size-${this.size || 'medium'}`);
13268
+ classes.push(`fill-${this.fillMode || 'solid'}`);
13269
+ if (this.border)
13270
+ classes.push('has-border');
13271
+ if (this.hasImage())
13272
+ classes.push('has-image');
13273
+ else if (this.hasIcon())
13274
+ classes.push('has-icon');
13275
+ else if (this.hasInitials())
13276
+ classes.push('has-initials');
13277
+ if (this.class)
13278
+ classes.push(String(this.class));
13279
+ return classes.join(' ');
13280
+ }, ...(ngDevMode ? [{ debugName: "componentCssClasses" }] : []));
13281
+ // Accessible label fallback
13282
+ computedAriaLabel = computed(() => {
13283
+ const explicit = (this.ariaLabel || '').trim();
13284
+ if (explicit)
13285
+ return explicit;
13286
+ const meta = this.metadata();
13287
+ if (meta?.label)
13288
+ return String(meta.label);
13289
+ if (this.imageAlt)
13290
+ return this.imageAlt;
13291
+ if (this.initials)
13292
+ return `Avatar ${this.normalizedInitials()}`;
13293
+ const name = this.iconName();
13294
+ if (name)
13295
+ return `Avatar ${name}`;
13296
+ return 'Avatar';
13297
+ }, ...(ngDevMode ? [{ debugName: "computedAriaLabel" }] : []));
13298
+ // Helpers for icon resolution
13299
+ resolveIconKind(i) {
13300
+ if (!i)
13301
+ return null;
13302
+ if (typeof i === 'string')
13303
+ return i.trim() ? 'name' : null;
13304
+ if (i['changingThisBreaksApplicationSecurity'])
13305
+ return 'svg';
13306
+ if (i.svg)
13307
+ return 'svg';
13308
+ if (i.fontClass)
13309
+ return 'font';
13310
+ if (i.name)
13311
+ return 'name';
13312
+ return null;
13313
+ }
13314
+ iconName() {
13315
+ const i = (this.icon ?? this.defaultIcon);
13316
+ const kind = this.resolveIconKind(i);
13317
+ if (kind === 'name') {
13318
+ return typeof i === 'string' ? i : i?.name || null;
13319
+ }
13320
+ return null;
13321
+ }
13322
+ iconSvgSafe() {
13323
+ const i = (this.icon ?? this.defaultIcon);
13324
+ const kind = this.resolveIconKind(i);
13325
+ if (kind === 'svg') {
13326
+ const raw = typeof i === 'string' ? undefined : (i?.svg ?? i);
13327
+ if (typeof raw === 'string') {
13328
+ return this.sanitizer.bypassSecurityTrustHtml(raw);
13329
+ }
13330
+ return raw || null;
13331
+ }
13332
+ return null;
13333
+ }
13334
+ iconFontClass() {
13335
+ const i = (this.icon ?? this.defaultIcon);
13336
+ const kind = this.resolveIconKind(i);
13337
+ if (kind === 'font') {
13338
+ return i?.fontClass || null;
13339
+ }
13340
+ return null;
13341
+ }
13342
+ shouldShowImage() {
13343
+ return this.hasImage();
13344
+ }
13345
+ shouldShowIcon() {
13346
+ return !this.hasImage() && this.hasIcon();
13347
+ }
13348
+ shouldShowInitials() {
13349
+ return !this.hasImage() && !this.hasIcon() && this.hasInitials();
13350
+ }
13351
+ normalizedInitials() {
13352
+ const s = (this.initials || '').trim();
13353
+ if (!s)
13354
+ return '';
13355
+ // Keep max 3 chars, uppercase
13356
+ return s.slice(0, 3).toUpperCase();
13357
+ }
13358
+ onImgError() {
13359
+ this._imgFailed.set(true);
13360
+ this.imageError.emit();
13361
+ }
13362
+ // BaseDynamicFieldComponent minimal API
13363
+ focus() {
13364
+ try {
13365
+ this.elementRef.nativeElement?.focus?.();
13366
+ }
13367
+ catch { }
13368
+ }
13369
+ blur() {
13370
+ try {
13371
+ this.elementRef.nativeElement?.blur?.();
13372
+ }
13373
+ catch { }
13374
+ }
13375
+ // Allow DynamicFieldLoader to apply metadata extras
13376
+ setInputMetadata(meta) {
13377
+ this.metadata.set(meta);
13378
+ const extras = meta?.extra || {};
13379
+ // Known keys: imageSrc, imageAlt, initials, icon, themeColor, rounded, size, fillMode, border, tooltip, ariaLabel
13380
+ if (extras.imageSrc !== undefined)
13381
+ this.imageSrc = extras.imageSrc;
13382
+ if (extras.imageAlt !== undefined)
13383
+ this.imageAlt = extras.imageAlt;
13384
+ if (extras.initials !== undefined)
13385
+ this.initials = extras.initials;
13386
+ if (extras.icon !== undefined)
13387
+ this.icon = extras.icon;
13388
+ if (extras.defaultIcon !== undefined)
13389
+ this.defaultIcon = extras.defaultIcon;
13390
+ if (extras.themeColor !== undefined)
13391
+ this.themeColor = extras.themeColor;
13392
+ if (extras.rounded !== undefined)
13393
+ this.rounded = extras.rounded;
13394
+ if (extras.size !== undefined)
13395
+ this.size = extras.size;
13396
+ if (extras.fillMode !== undefined)
13397
+ this.fillMode = extras.fillMode;
13398
+ if (extras.border !== undefined)
13399
+ this.border = !!extras.border;
13400
+ if (extras.tooltip !== undefined)
13401
+ this.tooltip = extras.tooltip;
13402
+ if (extras.ariaLabel !== undefined)
13403
+ this.ariaLabel = extras.ariaLabel;
13404
+ if (extras.class !== undefined)
13405
+ this.class = extras.class;
13406
+ if (extras.style !== undefined) {
13407
+ if (typeof extras.style === 'string') {
13408
+ try {
13409
+ const obj = JSON.parse(extras.style);
13410
+ if (obj && typeof obj === 'object')
13411
+ this.style = obj;
13412
+ }
13413
+ catch {
13414
+ // ignore invalid JSON
13415
+ }
13416
+ }
13417
+ else if (typeof extras.style === 'object') {
13418
+ this.style = extras.style;
13419
+ }
13420
+ }
13421
+ }
13422
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: MaterialAvatarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
13423
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: MaterialAvatarComponent, isStandalone: true, selector: "pdx-material-avatar", inputs: { imageSrc: "imageSrc", imageAlt: "imageAlt", initials: "initials", icon: "icon", defaultIcon: "defaultIcon", themeColor: "themeColor", rounded: "rounded", size: "size", fillMode: "fillMode", border: "border", tooltip: "tooltip", ariaLabel: "ariaLabel", readonlyMode: "readonlyMode", disabledMode: "disabledMode", visible: "visible", presentationMode: "presentationMode", class: "class", style: "style" }, outputs: { imageError: "imageError" }, host: { properties: { "class": "componentCssClasses()", "style.display": "visible === false ? \"none\" : null", "attr.role": "\"img\"", "attr.aria-label": "computedAriaLabel() || null", "attr.data-field-type": "\"avatar\"", "attr.data-field-name": "metadata()?.name", "attr.data-component-id": "componentId()", "class.praxis-disabled": "disabledMode === true", "class.praxis-readonly": "readonlyMode === true", "class.presentation-mode": "presentationMode === true" } }, ngImport: i0, template: `
13424
+ <span class="pfx-avatar__inner"
13425
+ [attr.aria-hidden]="presentationMode ? 'true' : null"
13426
+ [ngStyle]="style || null"
13427
+ [matTooltip]="tooltip || null"
13428
+ [matTooltipDisabled]="!tooltip">
13429
+ <!-- Image (highest priority) -->
13430
+ @if (shouldShowImage()) {
13431
+ <img class="pfx-avatar__img"
13432
+ [src]="resolvedImageSrc()!"
13433
+ [alt]="imageAlt || computedAriaLabel() || ''"
13434
+ (error)="onImgError()" />
13435
+ } @else if (shouldShowIcon()) {
13436
+ <!-- Icon (Material, Praxis, SVG or font class) -->
13437
+ @if (iconName()) {
13438
+ <mat-icon class="pfx-avatar__icon" [praxisIcon]="iconName()"></mat-icon>
13439
+ } @else if (iconSvgSafe()) {
13440
+ <span class="pfx-avatar__icon-svg" [innerHTML]="iconSvgSafe()"></span>
13441
+ } @else if (iconFontClass()) {
13442
+ <span class="pfx-avatar__icon-font" [class]="iconFontClass()" aria-hidden="true"></span>
13443
+ }
13444
+ } @else if (shouldShowInitials()) {
13445
+ <span class="pfx-avatar__initials">{{ normalizedInitials() }}</span>
13446
+ } @else {
13447
+ <!-- Custom projected content (lowest priority) -->
13448
+ <ng-content></ng-content>
13449
+ }
13450
+ </span>
13451
+ `, isInline: true, styles: [":host{--pfx-avatar-size: 40px;--pfx-avatar-radius-full: 9999px;--pfx-avatar-radius-lg: 16px;--pfx-avatar-radius-md: 12px;--pfx-avatar-radius-sm: 8px;--pfx-avatar-border-w: 1px;--pfx-avatar-border-color: var(--md-sys-color-outline-variant, #e0e0e0);--pfx-avatar-bg: var(--md-sys-color-primary, #6750a4);--pfx-avatar-fg: var(--md-sys-color-on-primary, #ffffff);display:inline-flex;align-items:center;justify-content:center;vertical-align:middle}.pfx-avatar__inner{position:relative;box-sizing:border-box;width:var(--pfx-avatar-size);height:var(--pfx-avatar-size);border-radius:var(--_pfx-avatar-radius, var(--pfx-avatar-radius-full));display:inline-flex;align-items:center;justify-content:center;overflow:hidden;-webkit-user-select:none;user-select:none;line-height:1;background:var(--_pfx-avatar-bg, var(--pfx-avatar-bg));color:var(--_pfx-avatar-fg, var(--pfx-avatar-fg));border:var(--_pfx-avatar-border, 0)}:host(.fill-outline) .pfx-avatar__inner{background:transparent;color:var(--_pfx-avatar-fg, var(--pfx-avatar-fg));border:var(--pfx-avatar-border-w) solid var(--pfx-avatar-border-color)}:host(.has-border) .pfx-avatar__inner{border:var(--pfx-avatar-border-w) solid var(--pfx-avatar-border-color)}:host(.size-small){--pfx-avatar-size: 24px}:host(.size-medium){--pfx-avatar-size: 40px}:host(.size-large){--pfx-avatar-size: 56px}:host(.size-none){--pfx-avatar-size: auto}:host(.rounded-full){--_pfx-avatar-radius: var(--pfx-avatar-radius-full)}:host(.rounded-large){--_pfx-avatar-radius: var(--pfx-avatar-radius-lg)}:host(.rounded-medium){--_pfx-avatar-radius: var(--pfx-avatar-radius-md)}:host(.rounded-small){--_pfx-avatar-radius: var(--pfx-avatar-radius-sm)}:host(.rounded-none){--_pfx-avatar-radius: 0}.pfx-avatar__img{width:100%;height:100%;object-fit:cover;display:block}.pfx-avatar__icon,.pfx-avatar__icon-svg,.pfx-avatar__icon-font{font-size:calc(var(--pfx-avatar-size) * .6);width:calc(var(--pfx-avatar-size) * .6);height:calc(var(--pfx-avatar-size) * .6);display:inline-flex;align-items:center;justify-content:center}.pfx-avatar__initials{font-weight:600;letter-spacing:.02em;font-size:calc(var(--pfx-avatar-size) * .4);line-height:1}:host(.theme-primary){--_pfx-avatar-bg: var(--md-sys-color-primary);--_pfx-avatar-fg: var(--md-sys-color-on-primary)}:host(.theme-secondary){--_pfx-avatar-bg: var(--md-sys-color-secondary);--_pfx-avatar-fg: var(--md-sys-color-on-secondary)}:host(.theme-tertiary){--_pfx-avatar-bg: var(--md-sys-color-tertiary);--_pfx-avatar-fg: var(--md-sys-color-on-tertiary)}:host(.theme-base){--_pfx-avatar-bg: var(--md-sys-color-surface-variant);--_pfx-avatar-fg: var(--md-sys-color-on-surface-variant)}:host(.theme-info){--_pfx-avatar-bg: var(--md-sys-color-primary-container);--_pfx-avatar-fg: var(--md-sys-color-on-primary-container)}:host(.theme-success){--_pfx-avatar-bg: var(--md-sys-color-secondary-container);--_pfx-avatar-fg: var(--md-sys-color-on-secondary-container)}:host(.theme-warning){--_pfx-avatar-bg: var(--md-sys-color-tertiary-container);--_pfx-avatar-fg: var(--md-sys-color-on-tertiary-container)}:host(.theme-error){--_pfx-avatar-bg: var(--md-sys-color-error-container);--_pfx-avatar-fg: var(--md-sys-color-on-error-container)}:host(.theme-dark){--_pfx-avatar-bg: #111318;--_pfx-avatar-fg: #f1f1f1}:host(.theme-light){--_pfx-avatar-bg: #ffffff;--_pfx-avatar-fg: #1a1a1a}:host(.theme-inverse){--_pfx-avatar-bg: var(--md-sys-color-on-surface);--_pfx-avatar-fg: var(--md-sys-color-surface)}:host(.theme-none){--_pfx-avatar-bg: transparent;--_pfx-avatar-fg: inherit}:host(.fill-none) .pfx-avatar__inner{background:none;color:inherit;border:0}:host(.size-none) .pfx-avatar__inner{width:auto;height:auto}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i3.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }] });
13452
+ }
13453
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: MaterialAvatarComponent, decorators: [{
13454
+ type: Component,
13455
+ args: [{ selector: 'pdx-material-avatar', standalone: true, imports: [CommonModule, MatIconModule, MatTooltipModule, PraxisIconDirective], host: {
13456
+ '[class]': 'componentCssClasses()',
13457
+ '[style.display]': 'visible === false ? "none" : null',
13458
+ '[attr.role]': '"img"',
13459
+ '[attr.aria-label]': 'computedAriaLabel() || null',
13460
+ '[attr.data-field-type]': '"avatar"',
13461
+ '[attr.data-field-name]': 'metadata()?.name',
13462
+ '[attr.data-component-id]': 'componentId()',
13463
+ '[class.praxis-disabled]': 'disabledMode === true',
13464
+ '[class.praxis-readonly]': 'readonlyMode === true',
13465
+ '[class.presentation-mode]': 'presentationMode === true',
13466
+ }, template: `
13467
+ <span class="pfx-avatar__inner"
13468
+ [attr.aria-hidden]="presentationMode ? 'true' : null"
13469
+ [ngStyle]="style || null"
13470
+ [matTooltip]="tooltip || null"
13471
+ [matTooltipDisabled]="!tooltip">
13472
+ <!-- Image (highest priority) -->
13473
+ @if (shouldShowImage()) {
13474
+ <img class="pfx-avatar__img"
13475
+ [src]="resolvedImageSrc()!"
13476
+ [alt]="imageAlt || computedAriaLabel() || ''"
13477
+ (error)="onImgError()" />
13478
+ } @else if (shouldShowIcon()) {
13479
+ <!-- Icon (Material, Praxis, SVG or font class) -->
13480
+ @if (iconName()) {
13481
+ <mat-icon class="pfx-avatar__icon" [praxisIcon]="iconName()"></mat-icon>
13482
+ } @else if (iconSvgSafe()) {
13483
+ <span class="pfx-avatar__icon-svg" [innerHTML]="iconSvgSafe()"></span>
13484
+ } @else if (iconFontClass()) {
13485
+ <span class="pfx-avatar__icon-font" [class]="iconFontClass()" aria-hidden="true"></span>
13486
+ }
13487
+ } @else if (shouldShowInitials()) {
13488
+ <span class="pfx-avatar__initials">{{ normalizedInitials() }}</span>
13489
+ } @else {
13490
+ <!-- Custom projected content (lowest priority) -->
13491
+ <ng-content></ng-content>
13492
+ }
13493
+ </span>
13494
+ `, styles: [":host{--pfx-avatar-size: 40px;--pfx-avatar-radius-full: 9999px;--pfx-avatar-radius-lg: 16px;--pfx-avatar-radius-md: 12px;--pfx-avatar-radius-sm: 8px;--pfx-avatar-border-w: 1px;--pfx-avatar-border-color: var(--md-sys-color-outline-variant, #e0e0e0);--pfx-avatar-bg: var(--md-sys-color-primary, #6750a4);--pfx-avatar-fg: var(--md-sys-color-on-primary, #ffffff);display:inline-flex;align-items:center;justify-content:center;vertical-align:middle}.pfx-avatar__inner{position:relative;box-sizing:border-box;width:var(--pfx-avatar-size);height:var(--pfx-avatar-size);border-radius:var(--_pfx-avatar-radius, var(--pfx-avatar-radius-full));display:inline-flex;align-items:center;justify-content:center;overflow:hidden;-webkit-user-select:none;user-select:none;line-height:1;background:var(--_pfx-avatar-bg, var(--pfx-avatar-bg));color:var(--_pfx-avatar-fg, var(--pfx-avatar-fg));border:var(--_pfx-avatar-border, 0)}:host(.fill-outline) .pfx-avatar__inner{background:transparent;color:var(--_pfx-avatar-fg, var(--pfx-avatar-fg));border:var(--pfx-avatar-border-w) solid var(--pfx-avatar-border-color)}:host(.has-border) .pfx-avatar__inner{border:var(--pfx-avatar-border-w) solid var(--pfx-avatar-border-color)}:host(.size-small){--pfx-avatar-size: 24px}:host(.size-medium){--pfx-avatar-size: 40px}:host(.size-large){--pfx-avatar-size: 56px}:host(.size-none){--pfx-avatar-size: auto}:host(.rounded-full){--_pfx-avatar-radius: var(--pfx-avatar-radius-full)}:host(.rounded-large){--_pfx-avatar-radius: var(--pfx-avatar-radius-lg)}:host(.rounded-medium){--_pfx-avatar-radius: var(--pfx-avatar-radius-md)}:host(.rounded-small){--_pfx-avatar-radius: var(--pfx-avatar-radius-sm)}:host(.rounded-none){--_pfx-avatar-radius: 0}.pfx-avatar__img{width:100%;height:100%;object-fit:cover;display:block}.pfx-avatar__icon,.pfx-avatar__icon-svg,.pfx-avatar__icon-font{font-size:calc(var(--pfx-avatar-size) * .6);width:calc(var(--pfx-avatar-size) * .6);height:calc(var(--pfx-avatar-size) * .6);display:inline-flex;align-items:center;justify-content:center}.pfx-avatar__initials{font-weight:600;letter-spacing:.02em;font-size:calc(var(--pfx-avatar-size) * .4);line-height:1}:host(.theme-primary){--_pfx-avatar-bg: var(--md-sys-color-primary);--_pfx-avatar-fg: var(--md-sys-color-on-primary)}:host(.theme-secondary){--_pfx-avatar-bg: var(--md-sys-color-secondary);--_pfx-avatar-fg: var(--md-sys-color-on-secondary)}:host(.theme-tertiary){--_pfx-avatar-bg: var(--md-sys-color-tertiary);--_pfx-avatar-fg: var(--md-sys-color-on-tertiary)}:host(.theme-base){--_pfx-avatar-bg: var(--md-sys-color-surface-variant);--_pfx-avatar-fg: var(--md-sys-color-on-surface-variant)}:host(.theme-info){--_pfx-avatar-bg: var(--md-sys-color-primary-container);--_pfx-avatar-fg: var(--md-sys-color-on-primary-container)}:host(.theme-success){--_pfx-avatar-bg: var(--md-sys-color-secondary-container);--_pfx-avatar-fg: var(--md-sys-color-on-secondary-container)}:host(.theme-warning){--_pfx-avatar-bg: var(--md-sys-color-tertiary-container);--_pfx-avatar-fg: var(--md-sys-color-on-tertiary-container)}:host(.theme-error){--_pfx-avatar-bg: var(--md-sys-color-error-container);--_pfx-avatar-fg: var(--md-sys-color-on-error-container)}:host(.theme-dark){--_pfx-avatar-bg: #111318;--_pfx-avatar-fg: #f1f1f1}:host(.theme-light){--_pfx-avatar-bg: #ffffff;--_pfx-avatar-fg: #1a1a1a}:host(.theme-inverse){--_pfx-avatar-bg: var(--md-sys-color-on-surface);--_pfx-avatar-fg: var(--md-sys-color-surface)}:host(.theme-none){--_pfx-avatar-bg: transparent;--_pfx-avatar-fg: inherit}:host(.fill-none) .pfx-avatar__inner{background:none;color:inherit;border:0}:host(.size-none) .pfx-avatar__inner{width:auto;height:auto}\n"] }]
13495
+ }], propDecorators: { imageSrc: [{
13496
+ type: Input
13497
+ }], imageAlt: [{
13498
+ type: Input
13499
+ }], initials: [{
13500
+ type: Input
13501
+ }], icon: [{
13502
+ type: Input
13503
+ }], defaultIcon: [{
13504
+ type: Input
13505
+ }], themeColor: [{
13506
+ type: Input
13507
+ }], rounded: [{
13508
+ type: Input
13509
+ }], size: [{
13510
+ type: Input
13511
+ }], fillMode: [{
13512
+ type: Input
13513
+ }], border: [{
13514
+ type: Input
13515
+ }], tooltip: [{
13516
+ type: Input
13517
+ }], ariaLabel: [{
13518
+ type: Input
13519
+ }], readonlyMode: [{
13520
+ type: Input
13521
+ }], disabledMode: [{
13522
+ type: Input
13523
+ }], visible: [{
13524
+ type: Input
13525
+ }], presentationMode: [{
13526
+ type: Input
13527
+ }], class: [{
13528
+ type: Input
13529
+ }], style: [{
13530
+ type: Input
13531
+ }], imageError: [{
13532
+ type: Output
13533
+ }] } });
13534
+
13199
13535
  class MaterialTransferListComponent extends SimpleBaseSelectComponent {
13200
13536
  // Praxis Field States
13201
13537
  readonlyMode = false;
@@ -14291,6 +14627,8 @@ class ComponentRegistryService {
14291
14627
  this.register(FieldControlType.MONTH_INPUT, () => wrap(MonthInputComponent));
14292
14628
  this.register(FieldControlType.NUMERIC_TEXT_BOX, () => wrap(NumberInputComponent));
14293
14629
  this.register(FieldControlType.SLIDER, () => wrap(MaterialSliderComponent));
14630
+ // Avatar (display component)
14631
+ this.register(FieldControlType.AVATAR, () => wrap(MaterialAvatarComponent));
14294
14632
  this.register(FieldControlType.CURRENCY_INPUT, () => wrap(MaterialCurrencyComponent));
14295
14633
  this.register(FieldControlType.CPF_CNPJ_INPUT, () => wrap(MaterialCpfCnpjInputComponent));
14296
14634
  this.register(FieldControlType.RANGE_SLIDER, () => wrap(PdxMaterialRangeSliderComponent));
@@ -18380,6 +18718,49 @@ const PDX_MATERIAL_SLIDER_COMPONENT_METADATA = {
18380
18718
  lib: '@praxisui/dynamic-fields',
18381
18719
  };
18382
18720
 
18721
+ const PDX_MATERIAL_AVATAR_COMPONENT_METADATA = {
18722
+ id: 'pdx-material-avatar',
18723
+ selector: 'pdx-material-avatar',
18724
+ component: MaterialAvatarComponent,
18725
+ friendlyName: 'Avatar (Material)',
18726
+ description: 'Avatar reusável com imagem, ícone, iniciais e tokens M3; integra com DynamicFieldLoader.',
18727
+ icon: 'person',
18728
+ inputs: [
18729
+ { name: 'metadata', type: 'FieldMetadata', description: 'Configuração do campo' },
18730
+ { name: 'imageSrc', type: 'string', description: 'URL da imagem' },
18731
+ { name: 'imageAlt', type: 'string', description: 'Texto alternativo da imagem' },
18732
+ { name: 'initials', type: 'string', description: 'Iniciais a exibir (ex.: MB)' },
18733
+ { name: 'icon', type: "string | SafeHtml | { name?: string; svg?: string; fontClass?: string }", description: 'Ícone Material/Praxis, SVG inline ou classe de fonte' },
18734
+ { name: 'defaultIcon', type: "string | SafeHtml | { name?: string; svg?: string; fontClass?: string }", description: 'Ícone padrão (fallback) quando não houver imagem/ícone/iniciais' },
18735
+ { name: 'themeColor', type: "'primary'|'secondary'|'tertiary'|'base'|'info'|'success'|'warning'|'error'|'dark'|'light'|'inverse'|'none'", default: 'primary' },
18736
+ { name: 'rounded', type: "'full'|'large'|'medium'|'small'|'none'", default: 'full' },
18737
+ { name: 'size', type: "'small'|'medium'|'large'|'none'", default: 'medium' },
18738
+ { name: 'fillMode', type: "'solid'|'outline'|'none'", default: 'solid' },
18739
+ { name: 'border', type: 'boolean', default: false },
18740
+ { name: 'tooltip', type: 'string' },
18741
+ { name: 'ariaLabel', type: 'string' },
18742
+ { name: 'class', type: 'string', description: 'Classes CSS adicionais no host' },
18743
+ { name: 'style', type: 'NgStyle | Record<string, any>', description: 'Estilos inline (objeto ou JSON via extra.style)' },
18744
+ { name: 'readonlyMode', type: 'boolean', default: false },
18745
+ { name: 'disabledMode', type: 'boolean', default: false },
18746
+ { name: 'visible', type: 'boolean', default: true },
18747
+ { name: 'presentationMode', type: 'boolean', default: false },
18748
+ ],
18749
+ tags: ['widget', 'field', 'avatar', 'material'],
18750
+ lib: '@praxisui/dynamic-fields',
18751
+ };
18752
+ /** Optional provider to auto-register the metadata for editors/palettes */
18753
+ function provideMaterialAvatarMetadata() {
18754
+ return {
18755
+ provide: ENVIRONMENT_INITIALIZER,
18756
+ multi: true,
18757
+ useFactory: (registry) => () => {
18758
+ registry.register(PDX_MATERIAL_AVATAR_COMPONENT_METADATA);
18759
+ },
18760
+ deps: [ComponentMetadataRegistry],
18761
+ };
18762
+ }
18763
+
18383
18764
  const PDX_MATERIAL_TIMEPICKER_COMPONENT_METADATA = {
18384
18765
  id: 'pdx-material-timepicker',
18385
18766
  selector: 'pdx-material-timepicker',
@@ -19363,5 +19744,5 @@ function normalizeFormMetadata(input) {
19363
19744
  * Generated bundle index. Do not edit.
19364
19745
  */
19365
19746
 
19366
- export { ActionResolverService, CACHE_TTL, ColorInputComponent, ComponentPreloaderService, ComponentRegistryService, ConfirmDialogComponent, DateInputComponent, DateUtilsService, DatetimeLocalInputComponent, DynamicFieldLoaderDirective, EmailInputComponent, KeyboardShortcutService, LoggerPresets, MAX_LOAD_ATTEMPTS, MaterialAsyncSelectComponent, MaterialAutocompleteComponent, MaterialButtonComponent, MaterialButtonToggleComponent, MaterialCheckboxGroupComponent, MaterialChipsComponent, MaterialColorPickerComponent, MaterialCpfCnpjInputComponent, MaterialCurrencyComponent, MaterialDateRangeComponent, MaterialDatepickerComponent, MaterialFileUploadComponent, MaterialMultiSelectComponent, MaterialMultiSelectTreeComponent, MaterialPriceRangeComponent, MaterialRadioGroupComponent, MaterialRatingComponent, MaterialSearchableSelectComponent, MaterialSelectComponent, MaterialSelectionListComponent, MaterialSlideToggleComponent, MaterialSliderComponent, MaterialTextareaComponent, MaterialTimepickerComponent, MaterialTransferListComponent, MaterialTreeSelectComponent, MonthInputComponent, NumberInputComponent, OptionStore, PDX_COLOR_INPUT_COMPONENT_METADATA, PDX_COLOR_PICKER_COMPONENT_METADATA, PDX_DATETIME_LOCAL_INPUT_COMPONENT_METADATA, PDX_DATE_INPUT_COMPONENT_METADATA, PDX_EMAIL_INPUT_COMPONENT_METADATA, PDX_FIELD_SHELL_COMPONENT_METADATA, PDX_MATERIAL_BUTTON_COMPONENT_METADATA, PDX_MATERIAL_BUTTON_TOGGLE_COMPONENT_METADATA, PDX_MATERIAL_CHECKBOX_GROUP_COMPONENT_METADATA, PDX_MATERIAL_CHIPS_COMPONENT_METADATA, PDX_MATERIAL_COLORPICKER_COMPONENT_METADATA, PDX_MATERIAL_CPF_CNPJ_INPUT_COMPONENT_METADATA, PDX_MATERIAL_CURRENCY_COMPONENT_METADATA, PDX_MATERIAL_DATEPICKER_COMPONENT_METADATA, PDX_MATERIAL_DATE_RANGE_COMPONENT_METADATA, PDX_MATERIAL_FILE_UPLOAD_COMPONENT_METADATA, PDX_MATERIAL_MULTI_SELECT_COMPONENT_METADATA, PDX_MATERIAL_MULTI_SELECT_TREE_COMPONENT_METADATA, PDX_MATERIAL_PRICE_RANGE_COMPONENT_METADATA, PDX_MATERIAL_RADIO_GROUP_COMPONENT_METADATA, PDX_MATERIAL_RANGE_SLIDER_COMPONENT_METADATA, PDX_MATERIAL_RATING_COMPONENT_METADATA, PDX_MATERIAL_SEARCHABLE_SELECT_COMPONENT_METADATA, PDX_MATERIAL_SELECTION_LIST_COMPONENT_METADATA, PDX_MATERIAL_SELECT_COMPONENT_METADATA, PDX_MATERIAL_SLIDER_COMPONENT_METADATA, PDX_MATERIAL_TEXTAREA_COMPONENT_METADATA, PDX_MATERIAL_TIMEPICKER_COMPONENT_METADATA, PDX_MATERIAL_TIME_RANGE_COMPONENT_METADATA, PDX_MATERIAL_TRANSFER_LIST_COMPONENT_METADATA, PDX_MATERIAL_TREE_SELECT_COMPONENT_METADATA, PDX_MONTH_INPUT_COMPONENT_METADATA, PDX_NUMBER_INPUT_COMPONENT_METADATA, PDX_PASSWORD_INPUT_COMPONENT_METADATA, PDX_PHONE_INPUT_COMPONENT_METADATA, PDX_PRELOAD_STATUS_COMPONENT_METADATA, PDX_SEARCH_INPUT_COMPONENT_METADATA, PDX_TEXT_INPUT_COMPONENT_METADATA, PDX_TIME_INPUT_COMPONENT_METADATA, PDX_URL_INPUT_COMPONENT_METADATA, PDX_WEEK_INPUT_COMPONENT_METADATA, PDX_YEAR_INPUT_COMPONENT_METADATA, PasswordInputComponent, PdxColorPickerComponent, PdxMaterialRangeSliderComponent, PdxMaterialTimeRangeComponent, PdxYearInputComponent, PhoneInputComponent, PraxisErrorStateMatcher, PreloadStatusComponent, RETRY_DELAY, SearchInputComponent, SimpleBaseButtonComponent, SimpleBaseInputComponent, SimpleBaseSelectComponent, TextInputComponent, TimeInputComponent, UrlInputComponent, WeekInputComponent, configureDynamicFieldsLogger, createErrorStateMatcher, enableDebugForComponent, getErrorStateMatcherForField, inferErrorStateStrategy, initializeComponentSystem, initializeComponentSystemSync, isBaseDynamicFieldComponent, isLoadingCapableComponent, isValidJsonSchema, isValueBasedComponent, logger, mapJsonSchemaToFields, mapPropertyToFieldMetadata, normalizeFormMetadata, providePraxisDynamicFields, providePraxisDynamicFieldsCore, silenceComponent };
19747
+ export { ActionResolverService, CACHE_TTL, ColorInputComponent, ComponentPreloaderService, ComponentRegistryService, ConfirmDialogComponent, DateInputComponent, DateUtilsService, DatetimeLocalInputComponent, DynamicFieldLoaderDirective, EmailInputComponent, KeyboardShortcutService, LoggerPresets, MAX_LOAD_ATTEMPTS, MaterialAsyncSelectComponent, MaterialAutocompleteComponent, MaterialAvatarComponent, MaterialButtonComponent, MaterialButtonToggleComponent, MaterialCheckboxGroupComponent, MaterialChipsComponent, MaterialColorPickerComponent, MaterialCpfCnpjInputComponent, MaterialCurrencyComponent, MaterialDateRangeComponent, MaterialDatepickerComponent, MaterialFileUploadComponent, MaterialMultiSelectComponent, MaterialMultiSelectTreeComponent, MaterialPriceRangeComponent, MaterialRadioGroupComponent, MaterialRatingComponent, MaterialSearchableSelectComponent, MaterialSelectComponent, MaterialSelectionListComponent, MaterialSlideToggleComponent, MaterialSliderComponent, MaterialTextareaComponent, MaterialTimepickerComponent, MaterialTransferListComponent, MaterialTreeSelectComponent, MonthInputComponent, NumberInputComponent, OptionStore, PDX_COLOR_INPUT_COMPONENT_METADATA, PDX_COLOR_PICKER_COMPONENT_METADATA, PDX_DATETIME_LOCAL_INPUT_COMPONENT_METADATA, PDX_DATE_INPUT_COMPONENT_METADATA, PDX_EMAIL_INPUT_COMPONENT_METADATA, PDX_FIELD_SHELL_COMPONENT_METADATA, PDX_MATERIAL_AVATAR_COMPONENT_METADATA, PDX_MATERIAL_BUTTON_COMPONENT_METADATA, PDX_MATERIAL_BUTTON_TOGGLE_COMPONENT_METADATA, PDX_MATERIAL_CHECKBOX_GROUP_COMPONENT_METADATA, PDX_MATERIAL_CHIPS_COMPONENT_METADATA, PDX_MATERIAL_COLORPICKER_COMPONENT_METADATA, PDX_MATERIAL_CPF_CNPJ_INPUT_COMPONENT_METADATA, PDX_MATERIAL_CURRENCY_COMPONENT_METADATA, PDX_MATERIAL_DATEPICKER_COMPONENT_METADATA, PDX_MATERIAL_DATE_RANGE_COMPONENT_METADATA, PDX_MATERIAL_FILE_UPLOAD_COMPONENT_METADATA, PDX_MATERIAL_MULTI_SELECT_COMPONENT_METADATA, PDX_MATERIAL_MULTI_SELECT_TREE_COMPONENT_METADATA, PDX_MATERIAL_PRICE_RANGE_COMPONENT_METADATA, PDX_MATERIAL_RADIO_GROUP_COMPONENT_METADATA, PDX_MATERIAL_RANGE_SLIDER_COMPONENT_METADATA, PDX_MATERIAL_RATING_COMPONENT_METADATA, PDX_MATERIAL_SEARCHABLE_SELECT_COMPONENT_METADATA, PDX_MATERIAL_SELECTION_LIST_COMPONENT_METADATA, PDX_MATERIAL_SELECT_COMPONENT_METADATA, PDX_MATERIAL_SLIDER_COMPONENT_METADATA, PDX_MATERIAL_TEXTAREA_COMPONENT_METADATA, PDX_MATERIAL_TIMEPICKER_COMPONENT_METADATA, PDX_MATERIAL_TIME_RANGE_COMPONENT_METADATA, PDX_MATERIAL_TRANSFER_LIST_COMPONENT_METADATA, PDX_MATERIAL_TREE_SELECT_COMPONENT_METADATA, PDX_MONTH_INPUT_COMPONENT_METADATA, PDX_NUMBER_INPUT_COMPONENT_METADATA, PDX_PASSWORD_INPUT_COMPONENT_METADATA, PDX_PHONE_INPUT_COMPONENT_METADATA, PDX_PRELOAD_STATUS_COMPONENT_METADATA, PDX_SEARCH_INPUT_COMPONENT_METADATA, PDX_TEXT_INPUT_COMPONENT_METADATA, PDX_TIME_INPUT_COMPONENT_METADATA, PDX_URL_INPUT_COMPONENT_METADATA, PDX_WEEK_INPUT_COMPONENT_METADATA, PDX_YEAR_INPUT_COMPONENT_METADATA, PasswordInputComponent, PdxColorPickerComponent, PdxMaterialRangeSliderComponent, PdxMaterialTimeRangeComponent, PdxYearInputComponent, PhoneInputComponent, PraxisErrorStateMatcher, PreloadStatusComponent, RETRY_DELAY, SearchInputComponent, SimpleBaseButtonComponent, SimpleBaseInputComponent, SimpleBaseSelectComponent, TextInputComponent, TimeInputComponent, UrlInputComponent, WeekInputComponent, configureDynamicFieldsLogger, createErrorStateMatcher, enableDebugForComponent, getErrorStateMatcherForField, inferErrorStateStrategy, initializeComponentSystem, initializeComponentSystemSync, isBaseDynamicFieldComponent, isLoadingCapableComponent, isValidJsonSchema, isValueBasedComponent, logger, mapJsonSchemaToFields, mapPropertyToFieldMetadata, normalizeFormMetadata, provideMaterialAvatarMetadata, providePraxisDynamicFields, providePraxisDynamicFieldsCore, silenceComponent };
19367
19748
  //# sourceMappingURL=praxisui-dynamic-fields.mjs.map