@seniorsistemas/angular-components 19.9.2 → 19.9.3

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,19 +1,28 @@
1
1
  import { trigger, transition, style, animate, state } from '@angular/animations';
2
2
  import * as i0 from '@angular/core';
3
- import { EventEmitter, Component, Input, ContentChildren, Output, NgModule } from '@angular/core';
4
- import { TemplateDirective } from '@seniorsistemas/angular-components/template';
5
- import { InteractiveContentDirective } from '@seniorsistemas/angular-components/interactive-content';
3
+ import { input, contentChildren, computed, EventEmitter, model, signal, Component, Output, NgModule } from '@angular/core';
6
4
  import { NgTemplateOutlet } from '@angular/common';
5
+ import { InteractiveContentDirective } from '@seniorsistemas/angular-components/interactive-content';
6
+ import { TemplateDirective } from '@seniorsistemas/angular-components/template';
7
7
 
8
8
  /**
9
9
  * @description Componente de fieldset com legenda, ícone configurável e suporte a
10
- * toggle (recolher/expandir) com animação. Suporta template personalizado para o
11
- * cabeçalho via diretiva `TemplateDirective` com tipo `'header'`.
10
+ * toggle (recolher/expandir) com animação. Suporta templates personalizados via
11
+ * diretiva `TemplateDirective`, com os tipos `'header'` (substitui a legenda) e
12
+ * `'body'` (conteúdo do fieldset).
13
+ *
14
+ * Prefira declarar o conteúdo em `sTemplate="body"` em vez de projetá-lo direto:
15
+ * o `ng-content` é instanciado pelo componente pai mesmo com o fieldset recolhido,
16
+ * disparando o `ngOnInit` dos filhos prematuramente. O template de corpo só é
17
+ * instanciado quando o fieldset está de fato ativo, e é obrigatório para que
18
+ * `destroyOnHide` funcione.
12
19
  *
13
20
  * @example
14
21
  * ```html
15
22
  * <s-fieldset legend="Dados pessoais" [toggleable]="true">
16
- * <p>Conteúdo do fieldset</p>
23
+ * <ng-template sTemplate="body">
24
+ * <p>Conteúdo do fieldset</p>
25
+ * </ng-template>
17
26
  * </s-fieldset>
18
27
  * ```
19
28
  *
@@ -21,33 +30,46 @@ import { NgTemplateOutlet } from '@angular/common';
21
30
  */
22
31
  class FieldsetComponent {
23
32
  /** @description Texto exibido como título do fieldset. @default '' */
24
- legend = '';
33
+ legend = input('');
25
34
  /** @description Habilita o botão de toggle que permite recolher e expandir o conteúdo. @default false */
26
- toggleable = false;
35
+ toggleable = input(false);
27
36
  /** @description Classe de ícone exibida ao lado da legenda (ex.: `'fas fa-cog'`). @default '' */
28
- icon = '';
29
- /** @description Quando `true`, remove o conteúdo do DOM ao recolher o fieldset. @default true */
30
- destroyOnHide = true;
31
- templateDirectives;
32
- headerTemplate;
37
+ icon = input('');
38
+ /** @description Quando `true`, remove o conteúdo do DOM ao recolher o fieldset. @default false */
39
+ destroyOnHide = input(false);
40
+ templateDirectives = contentChildren(TemplateDirective);
41
+ headerTemplate = computed(() => {
42
+ return this.templateDirectives()?.find((template) => template.type === 'header')?.template;
43
+ });
44
+ bodyTemplate = computed(() => {
45
+ return this.templateDirectives()?.find((template) => template.type === 'body')?.template;
46
+ });
33
47
  /** @description Emitido imediatamente antes da animação de toggle iniciar, com o estado atual. */
34
48
  beforeToggle = new EventEmitter();
35
49
  /** @description Emitido após a animação de toggle concluir, com o novo estado. */
36
50
  afterToggle = new EventEmitter();
37
- active = true;
51
+ active = model(true);
52
+ /** @description `true` quando a animação de fechamento já terminou, aplicando `overflow-hidden` ao conteúdo. */
53
+ collapsed = signal(false);
38
54
  ngAfterViewInit() {
39
- this.headerTemplate = this.templateDirectives?.find((template) => template.type === 'header')?.template;
55
+ const hasBodyTemplate = this.bodyTemplate();
56
+ if (!hasBodyTemplate && this.destroyOnHide()) {
57
+ console.warn('FieldsetComponent: O uso de destroyOnHide=true requer um template de corpo definido com sTemplate="body".');
58
+ }
40
59
  }
41
60
  toggleActive(originalEvent) {
42
- if (!this.toggleable) {
61
+ if (!this.toggleable()) {
43
62
  return;
44
63
  }
45
- this.beforeToggle.next({ collapsed: this.active, originalEvent });
46
- this.active = !this.active;
47
- this.afterToggle.next({ collapsed: this.active, originalEvent });
64
+ this.beforeToggle.next({ collapsed: this.active(), originalEvent });
65
+ this.active.set(!this.active());
66
+ this.afterToggle.next({ collapsed: this.active(), originalEvent });
67
+ }
68
+ onToggleAnimationDone(event) {
69
+ this.collapsed.set(event.toState === 'closed');
48
70
  }
49
71
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FieldsetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
50
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: FieldsetComponent, isStandalone: true, selector: "s-fieldset", inputs: { legend: "legend", toggleable: "toggleable", icon: "icon", destroyOnHide: "destroyOnHide" }, outputs: { beforeToggle: "beforeToggle", afterToggle: "afterToggle" }, queries: [{ propertyName: "templateDirectives", predicate: TemplateDirective }], ngImport: i0, template: "<fieldset\r\n class=\"bg-grayscale-0 border border-grayscale-20 rounded-xbig mb-[10px] min-h-[4.5em] px-2 py-1.5 w-full min-w-0\"\r\n>\r\n <legend\r\n class=\"flex items-center text-grayscale-90 font-open-sans text-sm font-bold px-1 gap-1 ml-4 select-none\"\r\n [class.cursor-pointer]=\"toggleable\"\r\n (sInteractiveContent)=\"toggleActive($event)\"\r\n >\r\n @if (toggleable) {\r\n @if (active) {\r\n <i class=\"fas fa-plus\"></i>\r\n } @else {\r\n <i class=\"fas fa-minus\"></i>\r\n }\r\n }\r\n @if (headerTemplate) {\r\n <ng-container *ngTemplateOutlet=\"headerTemplate\"></ng-container>\r\n } @else {\r\n @if (icon) {\r\n <i [class]=\"icon\"></i>\r\n }\r\n {{ legend }}\r\n }\r\n </legend>\r\n @if (destroyOnHide ? (toggleable ? active : true) : true) {\r\n <div [@toggleAnimation]=\"destroyOnHide ? null : active ? 'open' : 'closed'\">\r\n <ng-content></ng-content>\r\n </div>\r\n }\r\n</fieldset>\r\n", styles: [":host{display:block}\n"], dependencies: [{ kind: "directive", type: InteractiveContentDirective, selector: "[sInteractiveContent]", inputs: ["focusable", "disabled", "stopPropagation"], outputs: ["sInteractiveContent"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], animations: [
72
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: FieldsetComponent, isStandalone: true, selector: "s-fieldset", inputs: { legend: { classPropertyName: "legend", publicName: "legend", isSignal: true, isRequired: false, transformFunction: null }, toggleable: { classPropertyName: "toggleable", publicName: "toggleable", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, destroyOnHide: { classPropertyName: "destroyOnHide", publicName: "destroyOnHide", isSignal: true, isRequired: false, transformFunction: null }, active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { beforeToggle: "beforeToggle", afterToggle: "afterToggle", active: "activeChange" }, queries: [{ propertyName: "templateDirectives", predicate: TemplateDirective, isSignal: true }], ngImport: i0, template: "<fieldset\r\n class=\"mb-[10px] min-h-[4.5em] w-full min-w-0 rounded-xbig border border-grayscale-20 bg-grayscale-0 px-2 py-1.5\"\r\n>\r\n <legend\r\n class=\"ml-4 flex select-none items-center gap-1 px-1 font-open-sans text-sm font-bold text-grayscale-90\"\r\n [class.cursor-pointer]=\"toggleable()\"\r\n (sInteractiveContent)=\"toggleActive($event)\"\r\n >\r\n @if (toggleable()) {\r\n @if (active()) {\r\n <i class=\"fas fa-minus\"></i>\r\n } @else {\r\n <i class=\"fas fa-plus\"></i>\r\n }\r\n }\r\n @let _headerTemplate = headerTemplate();\r\n @if (_headerTemplate) {\r\n <ng-container *ngTemplateOutlet=\"_headerTemplate\"></ng-container>\r\n } @else {\r\n @if (icon()) {\r\n <i [class]=\"icon()\"></i>\r\n }\r\n {{ legend() }}\r\n }\r\n </legend>\r\n @if (destroyOnHide() ? (toggleable() ? active() : true) : true) {\r\n <div\r\n [@toggleAnimation]=\"destroyOnHide() ? null : active() ? 'open' : 'closed'\"\r\n (@toggleAnimation.done)=\"onToggleAnimationDone($event)\"\r\n [class.overflow-hidden]=\"collapsed()\"\r\n >\r\n @let _bodyTemplate = bodyTemplate();\r\n @if (_bodyTemplate) {\r\n <ng-container *ngTemplateOutlet=\"_bodyTemplate\"></ng-container>\r\n } @else {\r\n <ng-content></ng-content>\r\n }\r\n </div>\r\n }\r\n</fieldset>\r\n", styles: [":host{display:block}\n"], dependencies: [{ kind: "directive", type: InteractiveContentDirective, selector: "[sInteractiveContent]", inputs: ["focusable", "disabled", "stopPropagation"], outputs: ["sInteractiveContent"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], animations: [
51
73
  trigger('toggleAnimation', [
52
74
  transition(':enter', [
53
75
  style({ opacity: 0, height: 0 }),
@@ -79,19 +101,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
79
101
  state('closed', style({ opacity: 0, height: 0 })),
80
102
  transition('open <=> closed', animate('300ms ease')),
81
103
  ]),
82
- ], standalone: true, imports: [InteractiveContentDirective, NgTemplateOutlet], template: "<fieldset\r\n class=\"bg-grayscale-0 border border-grayscale-20 rounded-xbig mb-[10px] min-h-[4.5em] px-2 py-1.5 w-full min-w-0\"\r\n>\r\n <legend\r\n class=\"flex items-center text-grayscale-90 font-open-sans text-sm font-bold px-1 gap-1 ml-4 select-none\"\r\n [class.cursor-pointer]=\"toggleable\"\r\n (sInteractiveContent)=\"toggleActive($event)\"\r\n >\r\n @if (toggleable) {\r\n @if (active) {\r\n <i class=\"fas fa-plus\"></i>\r\n } @else {\r\n <i class=\"fas fa-minus\"></i>\r\n }\r\n }\r\n @if (headerTemplate) {\r\n <ng-container *ngTemplateOutlet=\"headerTemplate\"></ng-container>\r\n } @else {\r\n @if (icon) {\r\n <i [class]=\"icon\"></i>\r\n }\r\n {{ legend }}\r\n }\r\n </legend>\r\n @if (destroyOnHide ? (toggleable ? active : true) : true) {\r\n <div [@toggleAnimation]=\"destroyOnHide ? null : active ? 'open' : 'closed'\">\r\n <ng-content></ng-content>\r\n </div>\r\n }\r\n</fieldset>\r\n", styles: [":host{display:block}\n"] }]
83
- }], propDecorators: { legend: [{
84
- type: Input
85
- }], toggleable: [{
86
- type: Input
87
- }], icon: [{
88
- type: Input
89
- }], destroyOnHide: [{
90
- type: Input
91
- }], templateDirectives: [{
92
- type: ContentChildren,
93
- args: [TemplateDirective]
94
- }], beforeToggle: [{
104
+ ], standalone: true, imports: [InteractiveContentDirective, NgTemplateOutlet], template: "<fieldset\r\n class=\"mb-[10px] min-h-[4.5em] w-full min-w-0 rounded-xbig border border-grayscale-20 bg-grayscale-0 px-2 py-1.5\"\r\n>\r\n <legend\r\n class=\"ml-4 flex select-none items-center gap-1 px-1 font-open-sans text-sm font-bold text-grayscale-90\"\r\n [class.cursor-pointer]=\"toggleable()\"\r\n (sInteractiveContent)=\"toggleActive($event)\"\r\n >\r\n @if (toggleable()) {\r\n @if (active()) {\r\n <i class=\"fas fa-minus\"></i>\r\n } @else {\r\n <i class=\"fas fa-plus\"></i>\r\n }\r\n }\r\n @let _headerTemplate = headerTemplate();\r\n @if (_headerTemplate) {\r\n <ng-container *ngTemplateOutlet=\"_headerTemplate\"></ng-container>\r\n } @else {\r\n @if (icon()) {\r\n <i [class]=\"icon()\"></i>\r\n }\r\n {{ legend() }}\r\n }\r\n </legend>\r\n @if (destroyOnHide() ? (toggleable() ? active() : true) : true) {\r\n <div\r\n [@toggleAnimation]=\"destroyOnHide() ? null : active() ? 'open' : 'closed'\"\r\n (@toggleAnimation.done)=\"onToggleAnimationDone($event)\"\r\n [class.overflow-hidden]=\"collapsed()\"\r\n >\r\n @let _bodyTemplate = bodyTemplate();\r\n @if (_bodyTemplate) {\r\n <ng-container *ngTemplateOutlet=\"_bodyTemplate\"></ng-container>\r\n } @else {\r\n <ng-content></ng-content>\r\n }\r\n </div>\r\n }\r\n</fieldset>\r\n", styles: [":host{display:block}\n"] }]
105
+ }], propDecorators: { beforeToggle: [{
95
106
  type: Output
96
107
  }], afterToggle: [{
97
108
  type: Output
@@ -1 +1 @@
1
- {"version":3,"file":"seniorsistemas-angular-components-fieldset.mjs","sources":["../../projects/angular-components/fieldset/src/lib/fieldset/fieldset.component.ts","../../projects/angular-components/fieldset/src/lib/fieldset/fieldset.component.html","../../projects/angular-components/fieldset/src/lib/fieldset/fieldset.module.ts","../../projects/angular-components/fieldset/src/seniorsistemas-angular-components-fieldset.ts"],"sourcesContent":["import { animate, state, style, transition, trigger } from '@angular/animations';\r\nimport {\r\n AfterViewInit,\r\n Component,\r\n ContentChildren,\r\n EventEmitter,\r\n Input,\r\n Output,\r\n QueryList,\r\n TemplateRef,\r\n} from '@angular/core';\r\n\r\nimport { TemplateDirective } from '@seniorsistemas/angular-components/template';\r\nimport { FieldSetToggle } from './fieldset.models';\r\nimport { InteractiveContentDirective } from '@seniorsistemas/angular-components/interactive-content';\r\nimport { NgTemplateOutlet } from '@angular/common';\r\n\r\n/**\r\n * @description Componente de fieldset com legenda, ícone configurável e suporte a\r\n * toggle (recolher/expandir) com animação. Suporta template personalizado para o\r\n * cabeçalho via diretiva `TemplateDirective` com tipo `'header'`.\r\n *\r\n * @example\r\n * ```html\r\n * <s-fieldset legend=\"Dados pessoais\" [toggleable]=\"true\">\r\n * <p>Conteúdo do fieldset</p>\r\n * </s-fieldset>\r\n * ```\r\n *\r\n * @category Inputs\r\n */\r\n@Component({\r\n selector: 's-fieldset',\r\n templateUrl: './fieldset.component.html',\r\n styles: [':host { display: block; }'],\r\n animations: [\r\n trigger('toggleAnimation', [\r\n transition(':enter', [\r\n style({ opacity: 0, height: 0 }),\r\n animate('300ms ease-out', style({ opacity: 1, height: '*' })),\r\n ]),\r\n transition(':leave', [\r\n style({ opacity: 1, height: '*' }),\r\n animate('300ms ease-in', style({ opacity: 0, height: 0 })),\r\n ]),\r\n state('open', style({ opacity: 1, height: '*' })),\r\n state('closed', style({ opacity: 0, height: 0 })),\r\n transition('open <=> closed', animate('300ms ease')),\r\n ]),\r\n ],\r\n standalone: true,\r\n imports: [InteractiveContentDirective, NgTemplateOutlet],\r\n})\r\nexport class FieldsetComponent implements AfterViewInit {\r\n /** @description Texto exibido como título do fieldset. @default '' */\r\n @Input() legend = '';\r\n /** @description Habilita o botão de toggle que permite recolher e expandir o conteúdo. @default false */\r\n @Input() toggleable = false;\r\n /** @description Classe de ícone exibida ao lado da legenda (ex.: `'fas fa-cog'`). @default '' */\r\n @Input() icon = '';\r\n /** @description Quando `true`, remove o conteúdo do DOM ao recolher o fieldset. @default true */\r\n @Input() destroyOnHide = true;\r\n @ContentChildren(TemplateDirective) readonly templateDirectives: QueryList<TemplateDirective> | undefined;\r\n headerTemplate: TemplateRef<any> | undefined;\r\n /** @description Emitido imediatamente antes da animação de toggle iniciar, com o estado atual. */\r\n @Output() beforeToggle = new EventEmitter<FieldSetToggle>();\r\n /** @description Emitido após a animação de toggle concluir, com o novo estado. */\r\n @Output() afterToggle = new EventEmitter<FieldSetToggle>();\r\n active = true;\r\n\r\n ngAfterViewInit(): void {\r\n this.headerTemplate = this.templateDirectives?.find((template) => template.type === 'header')?.template;\r\n }\r\n\r\n toggleActive(originalEvent: PointerEvent | KeyboardEvent) {\r\n if (!this.toggleable) {\r\n return;\r\n }\r\n this.beforeToggle.next({ collapsed: this.active, originalEvent });\r\n this.active = !this.active;\r\n this.afterToggle.next({ collapsed: this.active, originalEvent });\r\n }\r\n}\r\n","<fieldset\r\n class=\"bg-grayscale-0 border border-grayscale-20 rounded-xbig mb-[10px] min-h-[4.5em] px-2 py-1.5 w-full min-w-0\"\r\n>\r\n <legend\r\n class=\"flex items-center text-grayscale-90 font-open-sans text-sm font-bold px-1 gap-1 ml-4 select-none\"\r\n [class.cursor-pointer]=\"toggleable\"\r\n (sInteractiveContent)=\"toggleActive($event)\"\r\n >\r\n @if (toggleable) {\r\n @if (active) {\r\n <i class=\"fas fa-plus\"></i>\r\n } @else {\r\n <i class=\"fas fa-minus\"></i>\r\n }\r\n }\r\n @if (headerTemplate) {\r\n <ng-container *ngTemplateOutlet=\"headerTemplate\"></ng-container>\r\n } @else {\r\n @if (icon) {\r\n <i [class]=\"icon\"></i>\r\n }\r\n {{ legend }}\r\n }\r\n </legend>\r\n @if (destroyOnHide ? (toggleable ? active : true) : true) {\r\n <div [@toggleAnimation]=\"destroyOnHide ? null : active ? 'open' : 'closed'\">\r\n <ng-content></ng-content>\r\n </div>\r\n }\r\n</fieldset>\r\n","import { NgModule } from '@angular/core';\r\n\r\nimport { FieldsetComponent } from './fieldset.component';\r\n\r\n@NgModule({\r\n imports: [FieldsetComponent],\r\n exports: [FieldsetComponent],\r\n})\r\nexport class FieldsetModule {}\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"seniorsistemas-angular-components-fieldset.mjs","sources":["../../projects/angular-components/fieldset/src/lib/fieldset/fieldset.component.ts","../../projects/angular-components/fieldset/src/lib/fieldset/fieldset.component.html","../../projects/angular-components/fieldset/src/lib/fieldset/fieldset.module.ts","../../projects/angular-components/fieldset/src/seniorsistemas-angular-components-fieldset.ts"],"sourcesContent":["import { animate, AnimationEvent, state, style, transition, trigger } from '@angular/animations';\r\nimport {\r\n AfterViewInit,\r\n Component,\r\n computed,\r\n contentChildren,\r\n EventEmitter,\r\n input,\r\n model,\r\n Output,\r\n signal,\r\n} from '@angular/core';\r\n\r\nimport { NgTemplateOutlet } from '@angular/common';\r\nimport { InteractiveContentDirective } from '@seniorsistemas/angular-components/interactive-content';\r\nimport { TemplateDirective } from '@seniorsistemas/angular-components/template';\r\nimport { FieldSetToggle } from './fieldset.models';\r\n\r\n/**\r\n * @description Componente de fieldset com legenda, ícone configurável e suporte a\r\n * toggle (recolher/expandir) com animação. Suporta templates personalizados via\r\n * diretiva `TemplateDirective`, com os tipos `'header'` (substitui a legenda) e\r\n * `'body'` (conteúdo do fieldset).\r\n *\r\n * Prefira declarar o conteúdo em `sTemplate=\"body\"` em vez de projetá-lo direto:\r\n * o `ng-content` é instanciado pelo componente pai mesmo com o fieldset recolhido,\r\n * disparando o `ngOnInit` dos filhos prematuramente. O template de corpo só é\r\n * instanciado quando o fieldset está de fato ativo, e é obrigatório para que\r\n * `destroyOnHide` funcione.\r\n *\r\n * @example\r\n * ```html\r\n * <s-fieldset legend=\"Dados pessoais\" [toggleable]=\"true\">\r\n * <ng-template sTemplate=\"body\">\r\n * <p>Conteúdo do fieldset</p>\r\n * </ng-template>\r\n * </s-fieldset>\r\n * ```\r\n *\r\n * @category Inputs\r\n */\r\n@Component({\r\n selector: 's-fieldset',\r\n templateUrl: './fieldset.component.html',\r\n styles: [':host { display: block; }'],\r\n animations: [\r\n trigger('toggleAnimation', [\r\n transition(':enter', [\r\n style({ opacity: 0, height: 0 }),\r\n animate('300ms ease-out', style({ opacity: 1, height: '*' })),\r\n ]),\r\n transition(':leave', [\r\n style({ opacity: 1, height: '*' }),\r\n animate('300ms ease-in', style({ opacity: 0, height: 0 })),\r\n ]),\r\n state('open', style({ opacity: 1, height: '*' })),\r\n state('closed', style({ opacity: 0, height: 0 })),\r\n transition('open <=> closed', animate('300ms ease')),\r\n ]),\r\n ],\r\n standalone: true,\r\n imports: [InteractiveContentDirective, NgTemplateOutlet],\r\n})\r\nexport class FieldsetComponent implements AfterViewInit {\r\n /** @description Texto exibido como título do fieldset. @default '' */\r\n legend = input('');\r\n /** @description Habilita o botão de toggle que permite recolher e expandir o conteúdo. @default false */\r\n toggleable = input(false);\r\n /** @description Classe de ícone exibida ao lado da legenda (ex.: `'fas fa-cog'`). @default '' */\r\n icon = input('');\r\n /** @description Quando `true`, remove o conteúdo do DOM ao recolher o fieldset. @default false */\r\n destroyOnHide = input(false);\r\n\r\n templateDirectives = contentChildren(TemplateDirective);\r\n headerTemplate = computed(() => {\r\n return this.templateDirectives()?.find((template) => template.type === 'header')?.template;\r\n });\r\n\r\n bodyTemplate = computed(() => {\r\n return this.templateDirectives()?.find((template) => template.type === 'body')?.template;\r\n });\r\n /** @description Emitido imediatamente antes da animação de toggle iniciar, com o estado atual. */\r\n @Output() beforeToggle = new EventEmitter<FieldSetToggle>();\r\n /** @description Emitido após a animação de toggle concluir, com o novo estado. */\r\n @Output() afterToggle = new EventEmitter<FieldSetToggle>();\r\n\r\n active = model(true);\r\n\r\n /** @description `true` quando a animação de fechamento já terminou, aplicando `overflow-hidden` ao conteúdo. */\r\n protected collapsed = signal(false);\r\n\r\n ngAfterViewInit() {\r\n const hasBodyTemplate = this.bodyTemplate();\r\n if (!hasBodyTemplate && this.destroyOnHide()) {\r\n console.warn(\r\n 'FieldsetComponent: O uso de destroyOnHide=true requer um template de corpo definido com sTemplate=\"body\".',\r\n );\r\n }\r\n }\r\n\r\n protected toggleActive(originalEvent: PointerEvent | KeyboardEvent) {\r\n if (!this.toggleable()) {\r\n return;\r\n }\r\n this.beforeToggle.next({ collapsed: this.active(), originalEvent });\r\n this.active.set(!this.active());\r\n this.afterToggle.next({ collapsed: this.active(), originalEvent });\r\n }\r\n\r\n protected onToggleAnimationDone(event: AnimationEvent) {\r\n this.collapsed.set(event.toState === 'closed');\r\n }\r\n}\r\n","<fieldset\r\n class=\"mb-[10px] min-h-[4.5em] w-full min-w-0 rounded-xbig border border-grayscale-20 bg-grayscale-0 px-2 py-1.5\"\r\n>\r\n <legend\r\n class=\"ml-4 flex select-none items-center gap-1 px-1 font-open-sans text-sm font-bold text-grayscale-90\"\r\n [class.cursor-pointer]=\"toggleable()\"\r\n (sInteractiveContent)=\"toggleActive($event)\"\r\n >\r\n @if (toggleable()) {\r\n @if (active()) {\r\n <i class=\"fas fa-minus\"></i>\r\n } @else {\r\n <i class=\"fas fa-plus\"></i>\r\n }\r\n }\r\n @let _headerTemplate = headerTemplate();\r\n @if (_headerTemplate) {\r\n <ng-container *ngTemplateOutlet=\"_headerTemplate\"></ng-container>\r\n } @else {\r\n @if (icon()) {\r\n <i [class]=\"icon()\"></i>\r\n }\r\n {{ legend() }}\r\n }\r\n </legend>\r\n @if (destroyOnHide() ? (toggleable() ? active() : true) : true) {\r\n <div\r\n [@toggleAnimation]=\"destroyOnHide() ? null : active() ? 'open' : 'closed'\"\r\n (@toggleAnimation.done)=\"onToggleAnimationDone($event)\"\r\n [class.overflow-hidden]=\"collapsed()\"\r\n >\r\n @let _bodyTemplate = bodyTemplate();\r\n @if (_bodyTemplate) {\r\n <ng-container *ngTemplateOutlet=\"_bodyTemplate\"></ng-container>\r\n } @else {\r\n <ng-content></ng-content>\r\n }\r\n </div>\r\n }\r\n</fieldset>\r\n","import { NgModule } from '@angular/core';\r\n\r\nimport { FieldsetComponent } from './fieldset.component';\r\n\r\n@NgModule({\r\n imports: [FieldsetComponent],\r\n exports: [FieldsetComponent],\r\n})\r\nexport class FieldsetModule {}\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
@@ -2,7 +2,7 @@
2
2
 
3
3
  ![Status](https://img.shields.io/badge/status-stable-brightgreen)
4
4
 
5
- Componente de agrupamento de campos de formulário com legenda, ícone e suporte a toggle (recolher/expandir) com animação. Aceita template customizado para o cabeçalho via `sTemplate="header"`.
5
+ Componente de agrupamento de campos de formulário com legenda, ícone e suporte a toggle (recolher/expandir) com animação. Aceita templates customizados via `sTemplate="header"` (cabeçalho) e `sTemplate="body"` (conteúdo).
6
6
 
7
7
  ## Quando usar
8
8
 
@@ -19,8 +19,9 @@ Componente de agrupamento de campos de formulário com legenda, ícone e suporte
19
19
 
20
20
  ```typescript
21
21
  import { FieldsetModule } from '@seniorsistemas/angular-components/fieldset';
22
+ import { TemplateDirective } from '@seniorsistemas/angular-components/template';
22
23
 
23
- @Component({ standalone: true, imports: [FieldsetModule] })
24
+ @Component({ standalone: true, imports: [FieldsetModule, TemplateDirective] })
24
25
  export class MeuComponent {}
25
26
  ```
26
27
 
@@ -28,10 +29,16 @@ export class MeuComponent {}
28
29
 
29
30
  ```html
30
31
  <s-fieldset legend="Dados Pessoais">
31
- <p>Conteúdo do fieldset</p>
32
+ <ng-template sTemplate="body">
33
+ <p>Conteúdo do fieldset</p>
34
+ </ng-template>
32
35
  </s-fieldset>
33
36
  ```
34
37
 
38
+ > **Declare o conteúdo em `sTemplate="body"`.** Projetar direto via `ng-content` continua funcionando, mas o conteúdo é instanciado pelo componente pai mesmo com o fieldset recolhido — o que dispara o `ngOnInit` dos componentes filhos prematuramente. O template de corpo só é instanciado quando o fieldset está de fato ativo, e é obrigatório para que `destroyOnHide` funcione (sem ele o componente emite um aviso no console).
39
+ >
40
+ > Projetos existentes podem ser convertidos automaticamente com `ng update @seniorsistemas/angular-components` — veja [Migração automática](#migração-automática).
41
+
35
42
  ## API
36
43
 
37
44
  ### Inputs
@@ -41,7 +48,16 @@ export class MeuComponent {}
41
48
  | `legend` | `string` | `''` | — | Texto exibido como título do fieldset |
42
49
  | `icon` | `string` | `''` | — | Classe de ícone exibida ao lado da legenda (ex: `'fas fa-cog'`) |
43
50
  | `toggleable` | `boolean` | `false` | — | Habilita o botão +/- para recolher e expandir com animação |
44
- | `destroyOnHide` | `boolean` | `true` | — | Remove o conteúdo do DOM ao recolher. Use `false` para preservar estado de formulários |
51
+ | `destroyOnHide` | `boolean` | `false` | — | Remove o conteúdo do DOM ao recolher. Requer o conteúdo declarado em `sTemplate="body"` |
52
+ | `active` | `boolean` | `true` | — | Estado aberto/recolhido. Suporta two-way binding via `[(active)]` |
53
+
54
+ ### Slots de conteúdo
55
+
56
+ | Slot | Descrição |
57
+ |------|-----------|
58
+ | `sTemplate="header"` | Substitui completamente o cabeçalho padrão (legenda + ícone). O ícone +/- do toggle continua sendo renderizado |
59
+ | `sTemplate="body"` | Conteúdo do fieldset, instanciado somente quando o fieldset está ativo |
60
+ | `ng-content` (default) | Fallback usado apenas quando não há `sTemplate="body"` |
45
61
 
46
62
  ### Outputs
47
63
 
@@ -65,10 +81,12 @@ type FieldSetToggle = {
65
81
 
66
82
  ```html
67
83
  <s-fieldset legend="Dados Pessoais">
68
- <div style="display: flex; flex-direction: column; gap: 12px;">
69
- <input type="text" placeholder="Nome completo" />
70
- <input type="text" placeholder="CPF" />
71
- </div>
84
+ <ng-template sTemplate="body">
85
+ <div style="display: flex; flex-direction: column; gap: 12px;">
86
+ <input type="text" placeholder="Nome completo" />
87
+ <input type="text" placeholder="CPF" />
88
+ </div>
89
+ </ng-template>
72
90
  </s-fieldset>
73
91
  ```
74
92
 
@@ -82,16 +100,44 @@ type FieldSetToggle = {
82
100
  (beforeToggle)="onBeforeToggle($event)"
83
101
  (afterToggle)="onAfterToggle($event)"
84
102
  >
85
- <input type="tel" placeholder="(00) 00000-0000" />
86
- <input type="email" placeholder="email@empresa.com" />
103
+ <ng-template sTemplate="body">
104
+ <input type="tel" placeholder="(00) 00000-0000" />
105
+ <input type="email" placeholder="email@empresa.com" />
106
+ </ng-template>
87
107
  </s-fieldset>
88
108
  ```
89
109
 
90
110
  ### Colapsável preservando estado do formulário
91
111
 
112
+ Com `destroyOnHide=false` (padrão) o conteúdo permanece no DOM ao recolher, apenas oculto:
113
+
92
114
  ```html
93
- <s-fieldset legend="Dados Opcionais" [toggleable]="true" [destroyOnHide]="false">
94
- <input formControlName="complemento" placeholder="Complemento" />
115
+ <s-fieldset legend="Dados Opcionais" [toggleable]="true">
116
+ <ng-template sTemplate="body">
117
+ <input formControlName="complemento" placeholder="Complemento" />
118
+ </ng-template>
119
+ </s-fieldset>
120
+ ```
121
+
122
+ ### Colapsável destruindo o conteúdo ao recolher
123
+
124
+ Use `destroyOnHide=true` para adiar a construção dos componentes filhos até a primeira abertura e liberá-los ao recolher. **Requer** o conteúdo em `sTemplate="body"`:
125
+
126
+ ```html
127
+ <s-fieldset legend="Relatório Pesado" [toggleable]="true" [destroyOnHide]="true" [active]="false">
128
+ <ng-template sTemplate="body">
129
+ <app-relatorio-pesado></app-relatorio-pesado>
130
+ </ng-template>
131
+ </s-fieldset>
132
+ ```
133
+
134
+ ### Controlando o estado por código
135
+
136
+ ```html
137
+ <s-fieldset legend="Filtros" [toggleable]="true" [(active)]="filtrosAbertos">
138
+ <ng-template sTemplate="body">
139
+ <input formControlName="busca" />
140
+ </ng-template>
95
141
  </s-fieldset>
96
142
  ```
97
143
 
@@ -106,7 +152,9 @@ type FieldSetToggle = {
106
152
  <span style="font-size: 0.75rem; color: #dc2626;">(campos obrigatórios)</span>
107
153
  </div>
108
154
  </ng-template>
109
- <input type="text" placeholder="Banco" />
155
+ <ng-template sTemplate="body">
156
+ <input type="text" placeholder="Banco" />
157
+ </ng-template>
110
158
  </s-fieldset>
111
159
  ```
112
160
 
@@ -115,14 +163,28 @@ type FieldSetToggle = {
115
163
  ```html
116
164
  <div style="display: flex; flex-direction: column; gap: 16px;">
117
165
  <s-fieldset legend="Dados Pessoais" icon="fas fa-user" [toggleable]="true">
118
- <!-- campos -->
166
+ <ng-template sTemplate="body">
167
+ <!-- campos -->
168
+ </ng-template>
119
169
  </s-fieldset>
120
170
  <s-fieldset legend="Endereço" icon="fas fa-map-marker-alt" [toggleable]="true">
121
- <!-- campos -->
171
+ <ng-template sTemplate="body">
172
+ <!-- campos -->
173
+ </ng-template>
122
174
  </s-fieldset>
123
175
  </div>
124
176
  ```
125
177
 
178
+ ## Migração automática
179
+
180
+ Projetos que declaram o conteúdo via `ng-content` podem ser convertidos para `sTemplate="body"` automaticamente:
181
+
182
+ ```bash
183
+ ng update @seniorsistemas/angular-components
184
+ ```
185
+
186
+ A migration `migration-v19-9-3-fieldset-body-template` envolve o conteúdo projetado de cada `<s-fieldset>` em `<ng-template sTemplate="body">`, mantém os templates de slot já existentes (como `sTemplate="header"`) fora do corpo e adiciona o `TemplateDirective` ao escopo do componente quando ele ainda não estiver disponível — inclusive verificando se já vem reexportado por um `SharedModule`. Fieldsets que já usam `sTemplate="body"` são ignorados, então a execução é idempotente.
187
+
126
188
  ## Acessibilidade
127
189
 
128
190
  - O botão de toggle é ativável por teclado (Enter e Space)
@@ -1,16 +1,26 @@
1
- import { AfterViewInit, EventEmitter, QueryList, TemplateRef } from '@angular/core';
1
+ import { AnimationEvent } from '@angular/animations';
2
+ import { AfterViewInit, EventEmitter } from '@angular/core';
2
3
  import { TemplateDirective } from '@seniorsistemas/angular-components/template';
3
4
  import { FieldSetToggle } from './fieldset.models';
4
5
  import * as i0 from "@angular/core";
5
6
  /**
6
7
  * @description Componente de fieldset com legenda, ícone configurável e suporte a
7
- * toggle (recolher/expandir) com animação. Suporta template personalizado para o
8
- * cabeçalho via diretiva `TemplateDirective` com tipo `'header'`.
8
+ * toggle (recolher/expandir) com animação. Suporta templates personalizados via
9
+ * diretiva `TemplateDirective`, com os tipos `'header'` (substitui a legenda) e
10
+ * `'body'` (conteúdo do fieldset).
11
+ *
12
+ * Prefira declarar o conteúdo em `sTemplate="body"` em vez de projetá-lo direto:
13
+ * o `ng-content` é instanciado pelo componente pai mesmo com o fieldset recolhido,
14
+ * disparando o `ngOnInit` dos filhos prematuramente. O template de corpo só é
15
+ * instanciado quando o fieldset está de fato ativo, e é obrigatório para que
16
+ * `destroyOnHide` funcione.
9
17
  *
10
18
  * @example
11
19
  * ```html
12
20
  * <s-fieldset legend="Dados pessoais" [toggleable]="true">
13
- * <p>Conteúdo do fieldset</p>
21
+ * <ng-template sTemplate="body">
22
+ * <p>Conteúdo do fieldset</p>
23
+ * </ng-template>
14
24
  * </s-fieldset>
15
25
  * ```
16
26
  *
@@ -18,22 +28,26 @@ import * as i0 from "@angular/core";
18
28
  */
19
29
  export declare class FieldsetComponent implements AfterViewInit {
20
30
  /** @description Texto exibido como título do fieldset. @default '' */
21
- legend: string;
31
+ legend: import("@angular/core").InputSignal<string>;
22
32
  /** @description Habilita o botão de toggle que permite recolher e expandir o conteúdo. @default false */
23
- toggleable: boolean;
33
+ toggleable: import("@angular/core").InputSignal<boolean>;
24
34
  /** @description Classe de ícone exibida ao lado da legenda (ex.: `'fas fa-cog'`). @default '' */
25
- icon: string;
26
- /** @description Quando `true`, remove o conteúdo do DOM ao recolher o fieldset. @default true */
27
- destroyOnHide: boolean;
28
- readonly templateDirectives: QueryList<TemplateDirective> | undefined;
29
- headerTemplate: TemplateRef<any> | undefined;
35
+ icon: import("@angular/core").InputSignal<string>;
36
+ /** @description Quando `true`, remove o conteúdo do DOM ao recolher o fieldset. @default false */
37
+ destroyOnHide: import("@angular/core").InputSignal<boolean>;
38
+ templateDirectives: import("@angular/core").Signal<readonly TemplateDirective[]>;
39
+ headerTemplate: import("@angular/core").Signal<import("@angular/core").TemplateRef<any> | undefined>;
40
+ bodyTemplate: import("@angular/core").Signal<import("@angular/core").TemplateRef<any> | undefined>;
30
41
  /** @description Emitido imediatamente antes da animação de toggle iniciar, com o estado atual. */
31
42
  beforeToggle: EventEmitter<FieldSetToggle>;
32
43
  /** @description Emitido após a animação de toggle concluir, com o novo estado. */
33
44
  afterToggle: EventEmitter<FieldSetToggle>;
34
- active: boolean;
45
+ active: import("@angular/core").ModelSignal<boolean>;
46
+ /** @description `true` quando a animação de fechamento já terminou, aplicando `overflow-hidden` ao conteúdo. */
47
+ protected collapsed: import("@angular/core").WritableSignal<boolean>;
35
48
  ngAfterViewInit(): void;
36
- toggleActive(originalEvent: PointerEvent | KeyboardEvent): void;
49
+ protected toggleActive(originalEvent: PointerEvent | KeyboardEvent): void;
50
+ protected onToggleAnimationDone(event: AnimationEvent): void;
37
51
  static ɵfac: i0.ɵɵFactoryDeclaration<FieldsetComponent, never>;
38
- static ɵcmp: i0.ɵɵComponentDeclaration<FieldsetComponent, "s-fieldset", never, { "legend": { "alias": "legend"; "required": false; }; "toggleable": { "alias": "toggleable"; "required": false; }; "icon": { "alias": "icon"; "required": false; }; "destroyOnHide": { "alias": "destroyOnHide"; "required": false; }; }, { "beforeToggle": "beforeToggle"; "afterToggle": "afterToggle"; }, ["templateDirectives"], ["*"], true, never>;
52
+ static ɵcmp: i0.ɵɵComponentDeclaration<FieldsetComponent, "s-fieldset", never, { "legend": { "alias": "legend"; "required": false; "isSignal": true; }; "toggleable": { "alias": "toggleable"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "destroyOnHide": { "alias": "destroyOnHide"; "required": false; "isSignal": true; }; "active": { "alias": "active"; "required": false; "isSignal": true; }; }, { "beforeToggle": "beforeToggle"; "afterToggle": "afterToggle"; "active": "activeChange"; }, ["templateDirectives"], ["*"], true, never>;
39
53
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seniorsistemas/angular-components",
3
- "version": "19.9.2",
3
+ "version": "19.9.3",
4
4
  "peerDependencies": {
5
5
  "@angular/cdk": "^18.2.14",
6
6
  "@angular/common": "^18.2.0",
@@ -97,36 +97,36 @@
97
97
  "esm": "./esm2022/accordion/seniorsistemas-angular-components-accordion.mjs",
98
98
  "default": "./fesm2022/seniorsistemas-angular-components-accordion.mjs"
99
99
  },
100
- "./alert": {
101
- "types": "./alert/index.d.ts",
102
- "esm2022": "./esm2022/alert/seniorsistemas-angular-components-alert.mjs",
103
- "esm": "./esm2022/alert/seniorsistemas-angular-components-alert.mjs",
104
- "default": "./fesm2022/seniorsistemas-angular-components-alert.mjs"
105
- },
106
100
  "./autocomplete": {
107
101
  "types": "./autocomplete/index.d.ts",
108
102
  "esm2022": "./esm2022/autocomplete/seniorsistemas-angular-components-autocomplete.mjs",
109
103
  "esm": "./esm2022/autocomplete/seniorsistemas-angular-components-autocomplete.mjs",
110
104
  "default": "./fesm2022/seniorsistemas-angular-components-autocomplete.mjs"
111
105
  },
106
+ "./alert": {
107
+ "types": "./alert/index.d.ts",
108
+ "esm2022": "./esm2022/alert/seniorsistemas-angular-components-alert.mjs",
109
+ "esm": "./esm2022/alert/seniorsistemas-angular-components-alert.mjs",
110
+ "default": "./fesm2022/seniorsistemas-angular-components-alert.mjs"
111
+ },
112
112
  "./badge": {
113
113
  "types": "./badge/index.d.ts",
114
114
  "esm2022": "./esm2022/badge/seniorsistemas-angular-components-badge.mjs",
115
115
  "esm": "./esm2022/badge/seniorsistemas-angular-components-badge.mjs",
116
116
  "default": "./fesm2022/seniorsistemas-angular-components-badge.mjs"
117
117
  },
118
- "./breadcrumb": {
119
- "types": "./breadcrumb/index.d.ts",
120
- "esm2022": "./esm2022/breadcrumb/seniorsistemas-angular-components-breadcrumb.mjs",
121
- "esm": "./esm2022/breadcrumb/seniorsistemas-angular-components-breadcrumb.mjs",
122
- "default": "./fesm2022/seniorsistemas-angular-components-breadcrumb.mjs"
123
- },
124
118
  "./bignumber-input": {
125
119
  "types": "./bignumber-input/index.d.ts",
126
120
  "esm2022": "./esm2022/bignumber-input/seniorsistemas-angular-components-bignumber-input.mjs",
127
121
  "esm": "./esm2022/bignumber-input/seniorsistemas-angular-components-bignumber-input.mjs",
128
122
  "default": "./fesm2022/seniorsistemas-angular-components-bignumber-input.mjs"
129
123
  },
124
+ "./breadcrumb": {
125
+ "types": "./breadcrumb/index.d.ts",
126
+ "esm2022": "./esm2022/breadcrumb/seniorsistemas-angular-components-breadcrumb.mjs",
127
+ "esm": "./esm2022/breadcrumb/seniorsistemas-angular-components-breadcrumb.mjs",
128
+ "default": "./fesm2022/seniorsistemas-angular-components-breadcrumb.mjs"
129
+ },
130
130
  "./button": {
131
131
  "types": "./button/index.d.ts",
132
132
  "esm2022": "./esm2022/button/seniorsistemas-angular-components-button.mjs",
@@ -1,4 +1,29 @@
1
1
  {
2
2
  "$schema": "../../node_modules/@angular-devkit/schematics/collection-schema.json",
3
- "schematics": {}
3
+ "schematics": {
4
+ "migrate-dynamic-form": {
5
+ "description": "Migra DynamicForm v18→v19: construtores → plain objects, enums → string literals, [fields] → [configs], Editor → provideEditorField(), estilos → angular.json",
6
+ "factory": "./migrations/migrate-dynamic-form-v19/index#migrate"
7
+ },
8
+ "migrate-steps-to-stepper": {
9
+ "description": "Migra Steps para Stepper: <s-steps> → <s-stepper>, import '@seniorsistemas/angular-components/steps' → 'stepper', StepsComponent → StepperComponent, e remove StepsModule (standalone) substituindo por StepperComponent",
10
+ "factory": "./migrations/migrate-steps-to-stepper/index#migrate"
11
+ },
12
+ "migrate-s-chips-outputs": {
13
+ "description": "Renomeia outputs legados do s-chips: onAdd→added, onRemove→removed, onChipClick→chipClicked, onFocus→focused, onBlur→focusLost",
14
+ "factory": "./migrations/migrate-s-chips-outputs/index#migrate"
15
+ },
16
+ "migrate-p-datepicker": {
17
+ "description": "Migra p-datepicker (PrimeNG) para s-datepicker (@seniorsistemas/angular-components)",
18
+ "factory": "./migrations/migrate-p-datepicker/index#migrate"
19
+ },
20
+ "migrate-p-editor": {
21
+ "description": "Migra p-editor (PrimeNG/Quill) para s-editor (@seniorsistemas/angular-components): renomeia tag e eventos, remove atributos sem equivalente, limpa configs legadas do DynamicForm e atualiza dependências do package.json",
22
+ "factory": "./migrations/migrate-p-editor/index#migrate"
23
+ },
24
+ "migrate-fieldset-body-template": {
25
+ "description": "Envolve o conteúdo projetado do s-fieldset em <ng-template sTemplate=\"body\">, mantendo templates de slot como sTemplate=\"header\" fora do body, e garante o TemplateDirective no escopo do componente (standalone ou via NgModule)",
26
+ "factory": "./migrations/migrate-fieldset-body-template/index#migrate"
27
+ }
28
+ }
4
29
  }
@@ -495,29 +495,29 @@ function migrateImports(content, originalImportedNames, editorDetected, filePath
495
495
  const currentlyImported = getImportedNamesFromSenior(updated);
496
496
  // Ensure every XxxFieldConfig interface introduced by isolated casts in Pass 2 is imported
497
497
  for (const typeName of castTypesUsed) {
498
- if (!isSymbolImportedFrom(updated, typeName, DYNAMIC_FORM_MODULE)) {
499
- updated = ensureNamedImport(updated, typeName, DYNAMIC_FORM_MODULE);
498
+ if (!(0, ast_utils_1.isSymbolImportedFrom)(updated, typeName, DYNAMIC_FORM_MODULE)) {
499
+ updated = (0, ast_utils_1.ensureNamedImport)(updated, typeName, DYNAMIC_FORM_MODULE);
500
500
  }
501
501
  }
502
502
  // Add v19 types as needed
503
503
  if (dynamicTypeUsedAsAnnotation &&
504
504
  !currentlyImported.has('DynamicType') &&
505
- !isSymbolImportedFrom(updated, 'DynamicType', DYNAMIC_FORM_MODULE)) {
506
- updated = ensureNamedImport(updated, 'DynamicType', DYNAMIC_FORM_MODULE);
505
+ !(0, ast_utils_1.isSymbolImportedFrom)(updated, 'DynamicType', DYNAMIC_FORM_MODULE)) {
506
+ updated = (0, ast_utils_1.ensureNamedImport)(updated, 'DynamicType', DYNAMIC_FORM_MODULE);
507
507
  }
508
508
  if (updated.includes('ExpandedDynamicType') &&
509
- !isSymbolImportedFrom(updated, 'ExpandedDynamicType', DYNAMIC_FORM_MODULE)) {
510
- updated = ensureNamedImport(updated, 'ExpandedDynamicType', DYNAMIC_FORM_MODULE);
509
+ !(0, ast_utils_1.isSymbolImportedFrom)(updated, 'ExpandedDynamicType', DYNAMIC_FORM_MODULE)) {
510
+ updated = (0, ast_utils_1.ensureNamedImport)(updated, 'ExpandedDynamicType', DYNAMIC_FORM_MODULE);
511
511
  }
512
512
  if (updated.includes('DynamicStructure') &&
513
- !isSymbolImportedFrom(updated, 'DynamicStructure', DYNAMIC_FORM_MODULE)) {
514
- updated = ensureNamedImport(updated, 'DynamicStructure', DYNAMIC_FORM_MODULE);
513
+ !(0, ast_utils_1.isSymbolImportedFrom)(updated, 'DynamicStructure', DYNAMIC_FORM_MODULE)) {
514
+ updated = (0, ast_utils_1.ensureNamedImport)(updated, 'DynamicStructure', DYNAMIC_FORM_MODULE);
515
515
  }
516
516
  if (updated.includes('dynamicTypeToDynamicStructure') &&
517
- !isSymbolImportedFrom(updated, 'dynamicTypeToDynamicStructure', DYNAMIC_FORM_MODULE)) {
518
- updated = ensureNamedImport(updated, 'dynamicTypeToDynamicStructure', DYNAMIC_FORM_MODULE);
517
+ !(0, ast_utils_1.isSymbolImportedFrom)(updated, 'dynamicTypeToDynamicStructure', DYNAMIC_FORM_MODULE)) {
518
+ updated = (0, ast_utils_1.ensureNamedImport)(updated, 'dynamicTypeToDynamicStructure', DYNAMIC_FORM_MODULE);
519
519
  }
520
- if (editorDetected && !isSymbolImportedFrom(updated, 'provideEditorField', EDITOR_MODULE)) {
520
+ if (editorDetected && !(0, ast_utils_1.isSymbolImportedFrom)(updated, 'provideEditorField', EDITOR_MODULE)) {
521
521
  updated = (0, ast_utils_1.insertImportStatement)(updated, 'provideEditorField', EDITOR_MODULE);
522
522
  }
523
523
  return updated;
@@ -595,7 +595,7 @@ function addProviderToApp(tree, providerCall, importFrom, context) {
595
595
  const updated = injectProvider(content, providerCall);
596
596
  if (updated !== content) {
597
597
  const symbolName = providerCall.replace(/\(.*\)$/, '');
598
- const withImport = ensureNamedImport(updated, symbolName, importFrom);
598
+ const withImport = (0, ast_utils_1.ensureNamedImport)(updated, symbolName, importFrom);
599
599
  tree.overwrite(candidate, withImport);
600
600
  context.logger.info(`${candidate}: adicionado ${providerCall} aos providers.`);
601
601
  return;
@@ -666,29 +666,10 @@ function isUsedAsType(content, symbolName) {
666
666
  const regex = new RegExp(`\\b${symbolName}\\b(?!\\s*\\.)`);
667
667
  return regex.test(withoutImports);
668
668
  }
669
- function isSymbolImportedFrom(content, symbolName, fromModule) {
670
- const escapedModule = fromModule.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
671
- const regex = new RegExp(`import\\s*\\{[^}]*\\b${symbolName}\\b[^}]*\\}\\s*from\\s*['"]${escapedModule}['"]`);
672
- return regex.test(content);
673
- }
674
- function ensureNamedImport(content, symbolName, fromModule) {
675
- if (isSymbolImportedFrom(content, symbolName, fromModule))
676
- return content;
677
- // Try to append to an existing import from the same module
678
- const escapedModule = fromModule.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
679
- const existingImportRegex = new RegExp(`(import\\s*\\{)([^}]*)(\\}\\s*from\\s*['"]${escapedModule}['"])`);
680
- const match = existingImportRegex.exec(content);
681
- if (match) {
682
- const newNames = match[2].trimEnd();
683
- const separator = newNames.endsWith(',') ? ' ' : ', ';
684
- return content.replace(match[0], `${match[1]}${newNames}${separator}${symbolName}${match[3]}`);
685
- }
686
- return (0, ast_utils_1.insertImportStatement)(content, symbolName, fromModule);
687
- }
688
669
  function ensureMultipleImports(content, symbols, fromModule) {
689
670
  let result = content;
690
671
  for (const sym of symbols) {
691
- result = ensureNamedImport(result, sym, fromModule);
672
+ result = (0, ast_utils_1.ensureNamedImport)(result, sym, fromModule);
692
673
  }
693
674
  return result;
694
675
  }
@@ -734,7 +715,7 @@ function migrateBaseFieldInputs(content, filePath, context) {
734
715
  return content;
735
716
  let updated = (0, ast_utils_1.applyChanges)(content, changes);
736
717
  // Add input() to @angular/core imports
737
- updated = ensureNamedImport(updated, 'input', '@angular/core');
718
+ updated = (0, ast_utils_1.ensureNamedImport)(updated, 'input', '@angular/core');
738
719
  // Remove Input from @angular/core if no longer used as decorator
739
720
  if (!/@Input\b/.test(updated)) {
740
721
  const updatedSf = ts.createSourceFile('_temp.ts', updated, ts.ScriptTarget.Latest, true);
@@ -913,7 +894,7 @@ function migrateFieldDefinitions(content, filePath, context) {
913
894
  visit(sourceFile);
914
895
  let updated = changes.length > 0 ? (0, ast_utils_1.applyChanges)(content, changes) : content;
915
896
  if (needsDynamicTypeImport) {
916
- updated = ensureNamedImport(updated, 'DynamicType', DYNAMIC_FORM_MODULE);
897
+ updated = (0, ast_utils_1.ensureNamedImport)(updated, 'DynamicType', DYNAMIC_FORM_MODULE);
917
898
  }
918
899
  return updated;
919
900
  }